blob: a1bf722891395f8ce7c5bdecc4a625deb63c69de [file] [log] [blame]
Tommy Carpenter21f659c2020-02-26 14:12:54 -05001# ==================================================================================
2# Copyright (c) 2020 Nokia
3# Copyright (c) 2020 AT&T Intellectual Property.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16# ==================================================================================
17
Lott, Christopher (cl778h)61270902020-05-06 09:23:55 -040018"""
19sdl functionality
20"""
Tommy Carpenter21f659c2020-02-26 14:12:54 -050021
22import msgpack
23from ricsdl.syncstorage import SyncStorage
24
25
26class SDLWrapper:
27 """
Lott, Christopher (cl778h)e87ea192020-06-16 16:12:26 -040028 Provides convenient wrapper methods for using the SDL Python interface.
29 Optionally uses msgpack for binary (de)serialization:
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -040030 see https://msgpack.org/index.html
Lott, Christopher (cl778h)e87ea192020-06-16 16:12:26 -040031
32 Published as a standalone module (and kept separate from the Xapp
33 framework classes) so these features can be used outside Xapps.
Tommy Carpenter21f659c2020-02-26 14:12:54 -050034 """
35
36 def __init__(self, use_fake_sdl=False):
37 """
38 init
39
40 Parameters
41 ----------
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -040042 use_fake_sdl: bool (optional, default False)
43 if this is True, then use SDL's in-memory backend,
44 which is very useful for testing since it allows use
45 of SDL without a running SDL or Redis instance.
46 This can be used while developing an xapp and also
47 for monkeypatching during unit testing; e.g., the xapp
48 framework unit tests do this.
Tommy Carpenter21f659c2020-02-26 14:12:54 -050049 """
50 if use_fake_sdl:
51 self._sdl = SyncStorage(fake_db_backend="dict")
52 else:
53 self._sdl = SyncStorage()
54
55 def set(self, ns, key, value, usemsgpack=True):
56 """
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -040057 Stores a key-value pair,
58 optionally serializing the value to bytes using msgpack.
Tommy Carpenter21f659c2020-02-26 14:12:54 -050059
Lott, Christopher (cl778h)bbc90282020-05-07 08:39:49 -040060 TODO: discuss whether usemsgpack should *default* to True or
61 False here. This seems like a usage statistic question (that we
62 don't have enough data for yet). Are more uses for an xapp to
63 write/read their own data, or will more xapps end up reading data
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -040064 written by some other thing? I think it's too early to know.
Tommy Carpenter21f659c2020-02-26 14:12:54 -050065
66 Parameters
67 ----------
68 ns: string
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -040069 SDL namespace
Tommy Carpenter21f659c2020-02-26 14:12:54 -050070 key: string
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -040071 SDL key
Tommy Carpenter21f659c2020-02-26 14:12:54 -050072 value:
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -040073 Object or byte array to store. See the `usemsgpack` parameter.
74 usemsgpack: boolean (optional, default is True)
75 Determines whether the value is serialized using msgpack before storing.
76 If usemsgpack is True, the msgpack function `packb` is invoked
77 on the value to yield a byte array that is then sent to SDL.
78 Stated differently, if usemsgpack is True, the value can be anything
79 that is serializable by msgpack.
80 If usemsgpack is False, the value must be bytes.
Tommy Carpenter21f659c2020-02-26 14:12:54 -050081 """
82 if usemsgpack:
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -040083 value = msgpack.packb(value, use_bin_type=True)
84 self._sdl.set(ns, {key: value})
Tommy Carpenter21f659c2020-02-26 14:12:54 -050085
yc999.jangda911992020-10-20 16:36:02 +090086 def set_if(self, ns, key, old_value, new_value, usemsgpack=True):
87 """
88 Conditionally modify the value of a key if the current value in data storage matches the
89 user's last known value.
90
91 Parameters
92 ----------
93 ns: string
94 SDL namespace
95 key: string
96 SDL key
97 old_value:
98 Lask known object or byte array. See the `usemsgpack` parameter.
99 new_value:
100 Object or byte array to be written. See the `usemsgpack` parameter.
101 usemsgpack: boolean (optional, default is True)
102 Determines whether the value is serialized using msgpack before storing.
103 If usemsgpack is True, the msgpack function `packb` is invoked
104 on the value to yield a byte array that is then sent to SDL.
105 Stated differently, if usemsgpack is True, the value can be anything
106 that is serializable by msgpack.
107 If usemsgpack is False, the value must be bytes.
108
109 Returns
110 -------
111 bool
112 True for successful modification, false if the user's last known data did not
113 match the current value in data storage.
114 """
115 if usemsgpack:
116 old_value = msgpack.packb(old_value, use_bin_type=True)
117 new_value = msgpack.packb(new_value, use_bin_type=True)
118 return self._sdl.set_if(ns, key, old_value, new_value)
119
120 def set_if_not_exists(self, ns, key, value, usemsgpack=True):
121 """
122 Write data to SDL storage if key does not exist.
123
124 Parameters
125 ----------
126 ns: string
127 SDL namespace
128 key: string
129 SDL key
130 value:
131 Object or byte array to store. See the `usemsgpack` parameter.
132 usemsgpack: boolean (optional, default is True)
133 Determines whether the value is serialized using msgpack before storing.
134 If usemsgpack is True, the msgpack function `packb` is invoked
135 on the value to yield a byte array that is then sent to SDL.
136 Stated differently, if usemsgpack is True, the value can be anything
137 that is serializable by msgpack.
138 If usemsgpack is False, the value must be bytes.
139
140 Returns
141 -------
142 bool
143 True for successful modification, false if the user's last known data did not
144 match the current value in data storage.
145 """
146 if usemsgpack:
147 value = msgpack.packb(value, use_bin_type=True)
148 return self._sdl.set_if_not_exists(ns, key, value)
149
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500150 def get(self, ns, key, usemsgpack=True):
151 """
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -0400152 Gets the value for the specified namespace and key,
153 optionally deserializing stored bytes using msgpack.
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500154
155 Parameters
156 ----------
157 ns: string
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -0400158 SDL namespace
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500159 key: string
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -0400160 SDL key
161 usemsgpack: boolean (optional, default is True)
162 If usemsgpack is True, the byte array stored by SDL is deserialized
163 using msgpack to yield the original object that was stored.
164 If usemsgpack is False, the byte array stored by SDL is returned
165 without further processing.
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500166
167 Returns
168 -------
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -0400169 Value
170 See the usemsgpack parameter for an explanation of the returned value type.
171 Answers None if the key is not found.
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500172 """
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -0400173 result = None
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500174 ret_dict = self._sdl.get(ns, {key})
175 if key in ret_dict:
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -0400176 result = ret_dict[key]
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500177 if usemsgpack:
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -0400178 result = msgpack.unpackb(result, raw=False)
179 return result
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500180
yc999.jangda911992020-10-20 16:36:02 +0900181 def find_keys(self, ns, prefix):
182 """
183 Find all keys matching search pattern under the namespace.
184
185 Parameters
186 ----------
187 ns: string
188 SDL namespace
189 prefix: string
190 Key search pattern
191
192 Returns
193 -------
194 keys: list
195 A list of found keys.
196 """
197 return self._sdl.find_keys(ns, f"{prefix}*")
198
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500199 def find_and_get(self, ns, prefix, usemsgpack=True):
200 """
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -0400201 Gets all key-value pairs in the specified namespace
202 with keys that start with the specified prefix,
203 optionally deserializing stored bytes using msgpack.
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500204
205 Parameters
206 ----------
207 ns: string
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -0400208 SDL namespace
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500209 prefix: string
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -0400210 the key prefix
211 usemsgpack: boolean (optional, default is True)
212 If usemsgpack is True, every byte array stored by SDL is deserialized
213 using msgpack to yield the original value that was stored.
214 If usemsgpack is False, every byte array stored by SDL is returned
215 without further processing.
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500216
217 Returns
218 -------
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -0400219 Dictionary of key-value pairs
220 Each key has the specified prefix.
221 See the usemsgpack parameter for an explanation of the returned value types.
222 Answers an empty dictionary if no keys matched the prefix.
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500223 """
224
225 # note: SDL "*" usage is inconsistent with real python regex, where it would be ".*"
yc999.jangda911992020-10-20 16:36:02 +0900226 ret_dict = self._sdl.find_and_get(ns, f"{prefix}*")
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500227 if usemsgpack:
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -0400228 ret_dict = {k: msgpack.unpackb(v, raw=False) for k, v in ret_dict.items()}
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500229 return ret_dict
230
231 def delete(self, ns, key):
232 """
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -0400233 Deletes the key-value pair with the specified key in the specified namespace.
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500234
235 Parameters
236 ----------
237 ns: string
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -0400238 SDL namespace
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500239 key: string
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -0400240 SDL key
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500241 """
242 self._sdl.remove(ns, {key})
243
yc999.jangda911992020-10-20 16:36:02 +0900244 def delete_if(self, ns, key, value, usemsgpack=True):
245 """
246 Conditionally remove data from SDL storage if the current data value matches the user's
247 last known value.
248
249 Parameters
250 ----------
251 ns: string
252 SDL namespace
253 key: string
254 SDL key
255 value:
256 Object or byte array to store. See the `usemsgpack` parameter.
257 usemsgpack: boolean (optional, default is True)
258 Determines whether the value is serialized using msgpack before storing.
259 If usemsgpack is True, the msgpack function `packb` is invoked
260 on the value to yield a byte array that is then sent to SDL.
261 Stated differently, if usemsgpack is True, the value can be anything
262 that is serializable by msgpack.
263 If usemsgpack is False, the value must be bytes.
264
265 Returns
266 -------
267 bool
268 True if successful removal, false if the user's last known data did not match the
269 current value in data storage.
270 """
271 if usemsgpack:
272 value = msgpack.packb(value, use_bin_type=True)
273 return self._sdl.remove_if(ns, key, value)
274
275 def add_member(self, ns, group, member, usemsgpack=True):
276 """
277 Add new members to a SDL group under the namespace.
278
279 Parameters
280 ----------
281 ns: string
282 SDL namespace
283 group: string
284 group name
285 member:
286 member to be added
287 usemsgpack: boolean (optional, default is True)
288 Determines whether the member is serialized using msgpack before storing.
289 If usemsgpack is True, the msgpack function `packb` is invoked
290 on the member to yield a byte array that is then sent to SDL.
291 Stated differently, if usemsgpack is True, the member can be anything
292 that is serializable by msgpack.
293 If usemsgpack is False, the member must be bytes.
294 """
295 if usemsgpack:
296 member = msgpack.packb(member, use_bin_type=True)
297 self._sdl.add_member(ns, group, {member})
298
299 def remove_member(self, ns, group, member, usemsgpack=True):
300 """
301 Remove members from a SDL group.
302
303 Parameters
304 ----------
305 ns: string
306 SDL namespace
307 group: string
308 group name
309 member:
310 member to be removed
311 usemsgpack: boolean (optional, default is True)
312 Determines whether the member is serialized using msgpack before storing.
313 If usemsgpack is True, the msgpack function `packb` is invoked
314 on the member to yield a byte array that is then sent to SDL.
315 Stated differently, if usemsgpack is True, the member can be anything
316 that is serializable by msgpack.
317 If usemsgpack is False, the member must be bytes.
318 """
319 if usemsgpack:
320 member = msgpack.packb(member, use_bin_type=True)
321 self._sdl.remove_member(ns, group, {member})
322
323 def remove_group(self, ns, group):
324 """
325 Remove a SDL group along with its members.
326
327 Parameters
328 ----------
329 ns: string
330 SDL namespace
331 group: string
332 group name to remove
333 usemsgpack: boolean (optional, default is True)
334 Determines whether the member is serialized using msgpack before storing.
335 If usemsgpack is True, the msgpack function `packb` is invoked
336 on the member to yield a byte array that is then sent to SDL.
337 Stated differently, if usemsgpack is True, the member can be anything
338 that is serializable by msgpack.
339 If usemsgpack is False, the member must be bytes.
340 """
341 self._sdl.remove_group(ns, group)
342
343 def get_members(self, ns, group, usemsgpack=True):
344 """
345 Get all the members of a SDL group.
346
347 Parameters
348 ----------
349 ns: string
350 SDL namespace
351 group: string
352 group name to retrive
353 usemsgpack: boolean (optional, default is True)
354 Determines whether the member is serialized using msgpack before storing.
355 If usemsgpack is True, the msgpack function `packb` is invoked
356 on the member to yield a byte array that is then sent to SDL.
357 Stated differently, if usemsgpack is True, the member can be anything
358 that is serializable by msgpack.
359 If usemsgpack is False, the member must be bytes.
360
361 Returns
362 -------
363 Set[str] or Set[bytes]
364 A set of the members of the group.
365 None
366 """
367 ret_set = self._sdl.get_members(ns, group)
368 if usemsgpack:
369 ret_set = {msgpack.unpackb(m, raw=False) for m in ret_set}
370 return ret_set
371
372 def is_member(self, ns, group, member, usemsgpack=True):
373 """
374 Validate if a given member is in the SDL group.
375
376 Parameters
377 ----------
378 ns: string
379 SDL namespace
380 group: string
381 group name
382 member:
383 member to validate
384 usemsgpack: boolean (optional, default is True)
385 Determines whether the member is serialized using msgpack before storing.
386 If usemsgpack is True, the msgpack function `packb` is invoked
387 on the member to yield a byte array that is then sent to SDL.
388 Stated differently, if usemsgpack is True, the member can be anything
389 that is serializable by msgpack.
390 If usemsgpack is False, the member must be bytes.
391
392 Returns
393 -------
394 bool
395 True if member was in the group, false otherwise.
396 """
397 if usemsgpack:
398 member = msgpack.packb(member, use_bin_type=True)
399 return self._sdl.is_member(ns, group, member)
400
401 def group_size(self, ns, group):
402 """
403 Return the number of members in a group.
404 If the group does not exist, value 0 is returned.
405
406 Parameters
407 ----------
408 ns: string
409 SDL namespace
410 group: string
411 group name to retrive size
412 usemsgpack: boolean (optional, default is True)
413 Determines whether the member is serialized using msgpack before storing.
414 If usemsgpack is True, the msgpack function `packb` is invoked
415 on the member to yield a byte array that is then sent to SDL.
416 Stated differently, if usemsgpack is True, the member can be anything
417 that is serializable by msgpack.
418 If usemsgpack is False, the member must be bytes.
419
420 Returns
421 -------
422 int
423 Number of members in a group.
424 """
425 return self._sdl.group_size(ns, group)
426
427 def set_and_publish(self, ns, channel, event, key, value, usemsgpack=True):
428 """
429 Publish event to channel after writing data.
430
431 Parameters
432 ----------
433 ns: string
434 SDL namespace
435 channel: string
436 channel to publish event
437 event: string
438 published message
439 key: string
440 SDL key
441 value:
442 Object or byte array to store. See the `usemsgpack` parameter.
443 usemsgpack: boolean (optional, default is True)
444 Determines whether the value is serialized using msgpack before storing.
445 If usemsgpack is True, the msgpack function `packb` is invoked
446 on the value to yield a byte array that is then sent to SDL.
447 Stated differently, if usemsgpack is True, the value can be anything
448 that is serializable by msgpack.
449 If usemsgpack is False, the value must be bytes.
450 """
451 if usemsgpack:
452 value = msgpack.packb(value, use_bin_type=True)
453 self._sdl.set_and_publish(ns, {channel: event}, {key: value})
454
455 def set_if_and_publish(self, ns, channel, event, key, old_value, new_value, usemsgpack=True):
456 """
457 Publish event to channel after conditionally modifying the value of a key if the
458 current value in data storage matches the user's last known value.
459
460 Parameters
461 ----------
462 ns: string
463 SDL namespace
464 channel: string
465 channel to publish event
466 event: string
467 published message
468 key: string
469 SDL key
470 old_value:
471 Lask known object or byte array. See the `usemsgpack` parameter.
472 new_value:
473 Object or byte array to be written. See the `usemsgpack` parameter.
474 usemsgpack: boolean (optional, default is True)
475 Determines whether the old_value & new_value is serialized using msgpack before storing.
476 If usemsgpack is True, the msgpack function `packb` is invoked
477 on the old_value & new_value to yield a byte array that is then sent to SDL.
478 Stated differently, if usemsgpack is True, the old_value & new_value can be anything
479 that is serializable by msgpack.
480 If usemsgpack is False, the old_value & new_value must be bytes.
481
482 Returns
483 -------
484 bool
485 True for successful modification, false if the user's last known data did not
486 match the current value in data storage.
487 """
488 if usemsgpack:
489 old_value = msgpack.packb(old_value, use_bin_type=True)
490 new_value = msgpack.packb(new_value, use_bin_type=True)
491 return self._sdl.set_if_and_publish(ns, {channel: event}, key, old_value, new_value)
492
493 def set_if_not_exists_and_publish(self, ns, channel, event, key, value, usemsgpack=True):
494 """
495 Publish event to channel after writing data to SDL storage if key does not exist.
496
497 Parameters
498 ----------
499 ns: string
500 SDL namespace
501 channel: string
502 channel to publish event
503 event: string
504 published message
505 key: string
506 SDL key
507 value:
508 Object or byte array to store. See the `usemsgpack` parameter.
509 usemsgpack: boolean (optional, default is True)
510 Determines whether the value is serialized using msgpack before storing.
511 If usemsgpack is True, the msgpack function `packb` is invoked
512 on the value to yield a byte array that is then sent to SDL.
513 Stated differently, if usemsgpack is True, the value can be anything
514 that is serializable by msgpack.
515 If usemsgpack is False, the value must be bytes.
516
517 Returns
518 -------
519 bool
520 True if key didn't exist yet and set operation was executed, false if key already
521 existed and thus its value was left untouched.
522 """
523 if usemsgpack:
524 value = msgpack.packb(value, use_bin_type=True)
525 return self._sdl.set_if_not_exists_and_publish(ns, {channel: event}, key, value)
526
527 def remove_and_publish(self, ns, channel, event, key):
528 """
529 Publish event to channel after removing data.
530
531 Parameters
532 ----------
533 ns: string
534 SDL namespace
535 channel: string
536 channel to publish event
537 event: string
538 published message
539 key: string
540 SDL key
541 """
542 self._sdl.remove_and_publish(ns, {channel: event}, {key})
543
544 def remove_if_and_publish(self, ns, channel, event, key, value, usemsgpack=True):
545 """
546 Publish event to channel after removing key and its data from database if the
547 current data value is expected one.
548
549 Parameters
550 ----------
551 ns: string
552 SDL namespace
553 channel: string
554 channel to publish event
555 event: string
556 published message
557 key: string
558 SDL key
559 value:
560 Object or byte array to store. See the `usemsgpack` parameter.
561 usemsgpack: boolean (optional, default is True)
562 Determines whether the value is serialized using msgpack before storing.
563 If usemsgpack is True, the msgpack function `packb` is invoked
564 on the value to yield a byte array that is then sent to SDL.
565 Stated differently, if usemsgpack is True, the value can be anything
566 that is serializable by msgpack.
567 If usemsgpack is False, the value must be bytes.
568
569 Returns
570 -------
571 bool
572 True if successful removal, false if the user's last known data did not match the
573 current value in data storage.
574 """
575 if usemsgpack:
576 value = msgpack.packb(value, use_bin_type=True)
577 return self._sdl.remove_if_and_publish(ns, {channel: event}, key, value)
578
579 def remove_all_and_publish(self, ns, channel, event):
580 """
581 Publish event to channel after removing all keys under the namespace.
582
583 Parameters
584 ----------
585 ns: string
586 SDL namespace
587 channel: string
588 channel to publish event
589 event: string
590 published message
591 """
592 self._sdl.remove_all_and_publish(ns, {channel: event})
593
594 def subscribe_channel(self, ns, cb, channel):
595 """
596 Subscribes the client to the specified channels.
597
598 Parameters
599 ----------
600 ns: string
601 SDL namespace
602 cb:
603 A function that is called when an event on channel is received.
604 channel: string
605 channel to subscribe
606 """
607 self._sdl.subscribe_channel(ns, cb, {channel})
608
609 def unsubscribe_channel(self, ns, channel):
610 """
611 unsubscribe_channel removes subscription from one or several channels.
612
613 Parameters
614 ----------
615 ns: string
616 SDL namespace
617 channel: string
618 channel to unsubscribe
619 """
620 self._sdl.unsubscribe_channel(ns, {channel})
621
622 def start_event_listener(self):
623 """
624 start_event_listener creates an event loop in a separate thread for handling
625 events from subscriptions. The registered callback function will be called
626 when an event is received.
627 """
628 self._sdl.start_event_listener()
629
630 def handle_events(self):
631 """
632 handle_events is a non-blocking function that returns a tuple containing channel
633 name and message received from an event. The registered callback function will
634 still be called when an event is received.
635
636 This function is called if SDL user decides to handle notifications in its own
637 event loop. Calling this function after start_event_listener raises an exception.
638 If there are no notifications, these returns None.
639
640 Returns
641 -------
642 Tuple:
643 (channel: str, message: str)
644 """
645 return self._sdl.handle_events()
646
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500647 def healthcheck(self):
648 """
Lott, Christopher (cl778h)ca170d32020-05-12 15:05:59 -0400649 Checks if the sdl connection is healthy.
Tommy Carpenter21f659c2020-02-26 14:12:54 -0500650
651 Returns
652 -------
653 bool
654 """
655 return self._sdl.is_active()