vici: Add a python vici command execution handler
authorBjörn Schuberg <bjorn.schuberg@gmail.com>
Sat, 14 Feb 2015 14:53:25 +0000 (15:53 +0100)
committerMartin Willi <martin@revosec.ch>
Wed, 18 Mar 2015 12:59:13 +0000 (13:59 +0100)
src/libcharon/plugins/vici/python/vici/exception.py
src/libcharon/plugins/vici/python/vici/session.py [new file with mode: 0644]

index 25d7349..89d76ab 100644 (file)
@@ -1,4 +1,7 @@
 """Exception types that may be thrown by this library."""
 
 class DeserializationException(Exception):
-    """Encountered an unexpected byte sequence or missing element type."""
\ No newline at end of file
+    """Encountered an unexpected byte sequence or missing element type."""
+
+class SessionException(Exception):
+    """Session request exception."""
\ No newline at end of file
diff --git a/src/libcharon/plugins/vici/python/vici/session.py b/src/libcharon/plugins/vici/python/vici/session.py
new file mode 100644 (file)
index 0000000..ef708fe
--- /dev/null
@@ -0,0 +1,130 @@
+import collections
+
+from .exception import SessionException
+from .protocol import Packet, Message
+
+
+class SessionHandler(object):
+    """Handles client command execution requests over vici."""
+
+    def __init__(self, transport):
+        self.transport = transport
+        self.log_events = collections.deque()
+
+    def _communicate(self, packet):
+        """Send packet over transport and parse response.
+
+        :param packet: packet to send
+        :type packet: :py:class:`vici.protocol.Packet`
+        :return: parsed packet in a tuple with message type and payload
+        :rtype: :py:class:`collections.namedtuple`
+        """
+        self.transport.send(packet)
+        return self._read()
+
+    def request(self, command, message=None):
+        """Send command request with an optional message.
+
+        :param command: command to send
+        :type command: str
+        :param message: message (optional)
+        :type message: str
+        :return: command result
+        :rtype: dict
+        """
+        if message is not None:
+            message = Message.serialize(message)
+        packet = Packet.request(command, message)
+        response = self._communicate(packet)
+
+        if response.response_type != Packet.CMD_RESPONSE:
+            raise SessionException(
+                "Unexpected response type {type}, "
+                "expected '{response}' (CMD_RESPONSE)".format(
+                    type=response.response_type,
+                    response=Packet.CMD_RESPONSE
+                )
+            )
+
+        return Message.deserialize(response.payload)
+
+    def streamed_request(self, command, event_stream_type, message=None):
+        """Send command request and collect and return all emitted events.
+
+        :param command: command to send
+        :type command: str
+        :param event_stream_type: event type emitted on command execution
+        :type event_stream_type: str
+        :param message: message (optional)
+        :type message: str
+        :return: a pair of the command result and a list of emitted events
+        :rtype: tuple
+        """
+        result = []
+
+        if message is not None:
+            message = Message.serialize(message)
+
+        # subscribe to event stream
+        packet = Packet.register_event(event_stream_type)
+        response = self._communicate(packet)
+
+        if response.response_type != Packet.EVENT_CONFIRM:
+            raise SessionException(
+                "Unexpected response type {type}, "
+                "expected '{confirm}' (EVENT_CONFIRM)".format(
+                    type=response.response_type,
+                    confirm=Packet.EVENT_CONFIRM,
+                )
+            )
+
+        # issue command, and read any event messages
+        packet = Packet.request(command, message)
+        self.transport.send(packet)
+        response = self._read()
+        while response.response_type == Packet.EVENT:
+            result.append(Message.deserialize(response.payload))
+            response = self._read()
+
+        if response.response_type == Packet.CMD_RESPONSE:
+            response_message = Message.deserialize(response.payload)
+        else:
+            raise SessionException(
+                "Unexpected response type {type}, "
+                "expected '{response}' (CMD_RESPONSE)".format(
+                    type=response.response_type,
+                    response=Packet.CMD_RESPONSE
+                )
+            )
+
+        # unsubscribe from event stream
+        packet = Packet.unregister_event(event_stream_type)
+        response = self._communicate(packet)
+        if response.response_type != Packet.EVENT_CONFIRM:
+            raise SessionException(
+                "Unexpected response type {type}, "
+                "expected '{confirm}' (EVENT_CONFIRM)".format(
+                    type=response.response_type,
+                    confirm=Packet.EVENT_CONFIRM,
+                )
+            )
+
+        return (response_message, result)
+
+    def _read(self):
+        """Get next packet from transport.
+
+        :return: parsed packet in a tuple with message type and payload
+        :rtype: :py:class:`collections.namedtuple`
+        """
+        raw_response = self.transport.receive()
+        response = Packet.parse(raw_response)
+
+        # FIXME
+        if response.response_type == Packet.EVENT and response.event_type == "log":
+            # queue up any debug log messages, and get next
+            self.log_events.append(response)
+            # do something?
+            self._read()
+        else:
+            return response