vici: Return a Python generator instead of a list for streamed responses
[strongswan.git] / src / libcharon / plugins / vici / python / vici / session.py
1 import collections
2 import socket
3
4 from .exception import SessionException, CommandException
5 from .protocol import Transport, Packet, Message
6
7
8 class Session(object):
9 def __init__(self, sock=None):
10 if sock is None:
11 sock = socket.socket(socket.AF_UNIX)
12 sock.connect("/var/run/charon.vici")
13 self.handler = SessionHandler(Transport(sock))
14
15 def version(self):
16 """Retrieve daemon and system specific version information.
17
18 :return: daemon and system specific version information
19 :rtype: dict
20 """
21 return self.handler.request("version")
22
23 def stats(self):
24 """Retrieve IKE daemon statistics and load information.
25
26 :return: IKE daemon statistics and load information
27 :rtype: dict
28 """
29 return self.handler.request("stats")
30
31 def reload_settings(self):
32 """Reload strongswan.conf settings and any plugins supporting reload.
33 """
34 self.handler.request("reload-settings")
35
36 def initiate(self, sa):
37 """Initiate an SA.
38
39 :param sa: the SA to initiate
40 :type sa: dict
41 :return: generator for logs emitted as dict
42 :rtype: generator
43 """
44 return self.handler.streamed_request("initiate", "control-log", sa)
45
46 def terminate(self, sa):
47 """Terminate an SA.
48
49 :param sa: the SA to terminate
50 :type sa: dict
51 :return: generator for logs emitted as dict
52 :rtype: generator
53 """
54 return self.handler.streamed_request("terminate", "control-log", sa)
55
56 def install(self, policy):
57 """Install a trap, drop or bypass policy defined by a CHILD_SA config.
58
59 :param policy: policy to install
60 :type policy: dict
61 """
62 self.handler.request("install", policy)
63
64 def uninstall(self, policy):
65 """Uninstall a trap, drop or bypass policy defined by a CHILD_SA config.
66
67 :param policy: policy to uninstall
68 :type policy: dict
69 """
70 self.handler.request("uninstall", policy)
71
72 def list_sas(self, filters=None):
73 """Retrieve active IKE_SAs and associated CHILD_SAs.
74
75 :param filters: retrieve only matching IKE_SAs (optional)
76 :type filters: dict
77 :return: generator for active IKE_SAs and associated CHILD_SAs as dict
78 :rtype: generator
79 """
80 return self.handler.streamed_request("list-sas", "list-sa", filters)
81
82 def list_policies(self, filters=None):
83 """Retrieve installed trap, drop and bypass policies.
84
85 :param filters: retrieve only matching policies (optional)
86 :type filters: dict
87 :return: generator for installed trap, drop and bypass policies as dict
88 :rtype: generator
89 """
90 return self.handler.streamed_request("list-policies", "list-policy",
91 filters)
92
93 def list_conns(self, filters=None):
94 """Retrieve loaded connections.
95
96 :param filters: retrieve only matching configuration names (optional)
97 :type filters: dict
98 :return: generator for loaded connections as dict
99 :rtype: generator
100 """
101 return self.handler.streamed_request("list-conns", "list-conn",
102 filters)
103
104 def get_conns(self):
105 """Retrieve connection names loaded exclusively over vici.
106
107 :return: connection names
108 :rtype: dict
109 """
110 return self.handler.request("get-conns")
111
112 def list_certs(self, filters=None):
113 """Retrieve loaded certificates.
114
115 :param filters: retrieve only matching certificates (optional)
116 :type filters: dict
117 :return: generator for loaded certificates as dict
118 :rtype: generator
119 """
120 return self.handler.streamed_request("list-certs", "list-cert", filters)
121
122 def load_conn(self, connection):
123 """Load a connection definition into the daemon.
124
125 :param connection: connection definition
126 :type connection: dict
127 """
128 self.handler.request("load-conn", connection)
129
130 def unload_conn(self, name):
131 """Unload a connection definition.
132
133 :param name: connection definition name
134 :type name: dict
135 """
136 self.handler.request("unload-conn", name)
137
138 def load_cert(self, certificate):
139 """Load a certificate into the daemon.
140
141 :param certificate: PEM or DER encoded certificate
142 :type certificate: dict
143 """
144 self.handler.request("load-cert", certificate)
145
146 def load_key(self, private_key):
147 """Load a private key into the daemon.
148
149 :param private_key: PEM or DER encoded key
150 """
151 self.handler.request("load-key", private_key)
152
153 def load_shared(self, secret):
154 """Load a shared IKE PSK, EAP or XAuth secret into the daemon.
155
156 :param secret: shared IKE PSK, EAP or XAuth secret
157 :type secret: dict
158 """
159 self.handler.request("load-shared", secret)
160
161 def clear_creds(self):
162 """Clear credentials loaded over vici.
163
164 Clear all loaded certificate, private key and shared key credentials.
165 This affects only credentials loaded over vici, but additionally
166 flushes the credential cache.
167 """
168 self.handler.request("clear-creds")
169
170 def load_pool(self, pool):
171 """Load a virtual IP pool.
172
173 Load an in-memory virtual IP and configuration attribute pool.
174 Existing pools with the same name get updated, if possible.
175
176 :param pool: virtual IP and configuration attribute pool
177 :type pool: dict
178 """
179 return self.handler.request("load-pool", pool)
180
181 def unload_pool(self, pool_name):
182 """Unload a virtual IP pool.
183
184 Unload a previously loaded virtual IP and configuration attribute pool.
185 Unloading fails for pools with leases currently online.
186
187 :param pool_name: pool by name
188 :type pool_name: dict
189 """
190 self.handler.request("unload-pool", pool_name)
191
192 def get_pools(self):
193 """Retrieve loaded pools.
194
195 :return: loaded pools
196 :rtype: dict
197 """
198 return self.handler.request("get-pools")
199
200
201 class SessionHandler(object):
202 """Handles client command execution requests over vici."""
203
204 def __init__(self, transport):
205 self.transport = transport
206
207 def _communicate(self, packet):
208 """Send packet over transport and parse response.
209
210 :param packet: packet to send
211 :type packet: :py:class:`vici.protocol.Packet`
212 :return: parsed packet in a tuple with message type and payload
213 :rtype: :py:class:`collections.namedtuple`
214 """
215 self.transport.send(packet)
216 return Packet.parse(self.transport.receive())
217
218 def request(self, command, message=None):
219 """Send request with an optional message.
220
221 :param command: command to send
222 :type command: str
223 :param message: message (optional)
224 :type message: str
225 :return: command result
226 :rtype: dict
227 """
228 if message is not None:
229 message = Message.serialize(message)
230 packet = Packet.request(command, message)
231 response = self._communicate(packet)
232
233 if response.response_type != Packet.CMD_RESPONSE:
234 raise SessionException(
235 "Unexpected response type {type}, "
236 "expected '{response}' (CMD_RESPONSE)".format(
237 type=response.response_type,
238 response=Packet.CMD_RESPONSE
239 )
240 )
241
242 command_response = Message.deserialize(response.payload)
243 if "success" in command_response:
244 if command_response["success"] != "yes":
245 raise CommandException(
246 "Command failed: {errmsg}".format(
247 errmsg=command_response["errmsg"]
248 )
249 )
250
251 return command_response
252
253 def streamed_request(self, command, event_stream_type, message=None):
254 """Send command request and collect and return all emitted events.
255
256 :param command: command to send
257 :type command: str
258 :param event_stream_type: event type emitted on command execution
259 :type event_stream_type: str
260 :param message: message (optional)
261 :type message: str
262 :return: generator for streamed event responses as dict
263 :rtype: generator
264 """
265 if message is not None:
266 message = Message.serialize(message)
267
268 # subscribe to event stream
269 packet = Packet.register_event(event_stream_type)
270 response = self._communicate(packet)
271
272 if response.response_type != Packet.EVENT_CONFIRM:
273 raise SessionException(
274 "Unexpected response type {type}, "
275 "expected '{confirm}' (EVENT_CONFIRM)".format(
276 type=response.response_type,
277 confirm=Packet.EVENT_CONFIRM,
278 )
279 )
280
281 # issue command, and read any event messages
282 packet = Packet.request(command, message)
283 self.transport.send(packet)
284 while True:
285 response = Packet.parse(self.transport.receive())
286 if response.response_type == Packet.EVENT:
287 yield Message.deserialize(response.payload)
288 else:
289 break
290
291 if response.response_type == Packet.CMD_RESPONSE:
292 Message.deserialize(response.payload)
293 else:
294 raise SessionException(
295 "Unexpected response type {type}, "
296 "expected '{response}' (CMD_RESPONSE)".format(
297 type=response.response_type,
298 response=Packet.CMD_RESPONSE
299 )
300 )
301
302 # unsubscribe from event stream
303 packet = Packet.unregister_event(event_stream_type)
304 response = self._communicate(packet)
305 if response.response_type != Packet.EVENT_CONFIRM:
306 raise SessionException(
307 "Unexpected response type {type}, "
308 "expected '{confirm}' (EVENT_CONFIRM)".format(
309 type=response.response_type,
310 confirm=Packet.EVENT_CONFIRM,
311 )
312 )