Add a new SDL storage API function 'is_active()'
[ric-plt/sdlpy.git] / ricsdl-package / ricsdl / syncstorage_abc.py
1 # Copyright (c) 2019 AT&T Intellectual Property.
2 # Copyright (c) 2018-2019 Nokia.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 #
17 # This source code is part of the near-RT RIC (RAN Intelligent Controller)
18 # platform project (RICP).
19 #
20
21
22 """The module provides synchronous Shared Data Layer (SDL) interface."""
23 from typing import (Dict, Set, List, Union)
24 from abc import ABC, abstractmethod
25 from ricsdl.exceptions import (
26     RejectedByBackend
27 )
28
29 __all__ = [
30     'SyncStorageAbc',
31     'SyncLockAbc'
32 ]
33
34
35 class SyncLockAbc(ABC):
36     """
37     An abstract synchronous Shared Data Layer (SDL) lock class providing a shared, distributed
38     locking mechanism, which can be utilized by clients to be able to operate with a shared
39     resource in a mutually exclusive way.
40
41     A lock instance is created per namespace and it is identified by its `name` within a
42     namespace.
43
44     A concrete implementation subclass 'SyncLock' derives from this abstract class.
45
46     Args:
47         ns (str): Namespace under which this lock is targeted.
48         name (str): Lock name, identifies the lock key in SDL storage.
49         expiration (int, float): Lock expiration time after which the lock is removed if it hasn't
50                                  been released earlier by a 'release' method.
51
52     """
53     def __init__(self, ns: str, name: str, expiration: Union[int, float]) -> None:
54         super().__init__()
55         self._ns = ns
56         self._name = name
57         self._expiration = expiration
58
59     def __enter__(self, *args, **kwargs):
60         if self.acquire(*args, **kwargs):
61             return self
62         raise RejectedByBackend("Unable to acquire lock within the time specified")
63
64     def __exit__(self, exception_type, exception_value, traceback):
65         self.release()
66
67     def acquire(self, retry_interval: Union[int, float] = 0.1,
68                 retry_timeout: Union[int, float] = 10) -> bool:
69         """
70         Acquire a shared, distributed lock atomically.
71
72         A lock can be used as a mutual exclusion locking entry for a shared resources.
73
74         All the exceptions except SdlTypeError are derived from SdlException base class. Client
75         can catch only that exception if separate handling for different SDL error situations is
76         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
77         misuse of the SDL API.
78
79         Args:
80             retry_interval (int, float): Lock acquiring retry interval in seconds. Supports both
81                                          integer and float numbers.
82             retry_timeout (int, float): Lock acquiring timeout after which retries are stopped and
83                                         error status is returned. Supports both integer and float
84                                         numbers.
85
86         Returns:
87             bool: True for successful lock acquiring, false otherwise.
88
89         Raises:
90             SdlTypeError: If function's argument is of an inappropriate type.
91             NotConnected: If SDL is not connected to the backend data storage.
92             RejectedByBackend: If backend data storage rejects the request.
93             BackendError: If the backend data storage fails to process the request.
94         """
95         pass
96
97     def release(self) -> None:
98         """
99         Release a lock atomically.
100
101         Release the already acquired lock.
102
103         Exceptions thrown are all derived from SdlException base class. Client can catch only that
104         exception if separate handling for different SDL error situations is not needed.
105
106         Args:
107             None
108
109         Returns:
110             None
111
112         Raises:
113             NotConnected: If SDL is not connected to the backend data storage.
114             RejectedByBackend: If backend data storage rejects the request.
115             BackendError: If the backend data storage fails to process the request.
116         """
117         pass
118
119     def refresh(self) -> None:
120         """
121         Refresh the remaining validity time of the existing lock back to an initial value.
122
123         Exceptions thrown are all derived from SdlException base class. Client can catch only that
124         exception if separate handling for different SDL error situations is not needed.
125
126         Args:
127             None
128
129         Returns:
130             None
131
132         Raises:
133             NotConnected: If SDL is not connected to the backend data storage.
134             RejectedByBackend: If backend data storage rejects the request.
135             BackendError: If the backend data storage fails to process the request.
136         """
137         pass
138
139     def get_validity_time(self) -> Union[int, float]:
140         """
141         Get atomically the remaining validity time of the lock in seconds.
142
143         Return atomically time in seconds until the lock expires.
144
145         Exceptions thrown are all derived from SdlException base class. Client can catch only that
146         exception if separate handling for different SDL error situations is not needed.
147
148         Args:
149             None
150
151         Returns:
152             (int, float): Validity time of the lock in seconds.
153
154         Raises:
155             NotConnected: If SDL is not connected to the backend data storage.
156             RejectedByBackend: If backend data storage rejects the request.
157             BackendError: If the backend data storage fails to process the request.
158         """
159         pass
160
161
162 class SyncStorageAbc(ABC):
163     """
164     An abstract class providing synchronous access to Shared Data Layer (SDL) storage.
165
166     This class provides synchronous access to all the namespaces in SDL storage.
167     Data can be written, read and removed based on keys known to clients. Keys are unique within
168     a namespace, namespace identifier is passed as a parameter to all the operations.
169
170     A concrete implementation subclass 'SyncStorage' derives from this abstract class.
171     """
172
173     @abstractmethod
174     def is_active(self):
175         """
176         Verify SDL storage healthiness.
177
178         Verify SDL connection to the backend data storage.
179
180         Args:
181             None
182
183         Returns:
184             bool: True if SDL is operational, false otherwise.
185
186         Raises:
187             None
188         """
189         pass
190
191     @abstractmethod
192     def close(self):
193         """
194         Close the connection to SDL storage.
195
196         Args:
197             None
198
199         Returns:
200             None
201
202         Raises:
203             NotConnected: If SDL is not connected to the backend data storage.
204             RejectedByBackend: If backend data storage rejects the request.
205             BackendError: If the backend data storage fails to process the request.
206         """
207         pass
208
209     @abstractmethod
210     def set(self, ns: str, data_map: Dict[str, bytes]) -> None:
211         """
212         Write data to SDL storage.
213
214         Writing is done atomically, i.e. either all succeeds, or all fails.
215         All the exceptions except SdlTypeError are derived from SdlException base class. Client
216         can catch only that exception if separate handling for different SDL error situations is
217         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
218         misuse of the SDL API.
219
220         Args:
221             ns (str): Namespace under which this operation is targeted.
222             data_map (dict of str: bytes): Data to be written.
223
224         Returns:
225             None
226
227         Raises:
228             SdlTypeError: If function's argument is of an inappropriate type.
229             NotConnected: If SDL is not connected to the backend data storage.
230             RejectedByBackend: If backend data storage rejects the request.
231             BackendError: If the backend data storage fails to process the request.
232         """
233         pass
234
235     @abstractmethod
236     def set_if(self, ns: str, key: str, old_data: bytes, new_data: bytes) -> bool:
237         """
238         Conditionally modify the value of a key if the current value in data storage matches the
239         user's last known value.
240
241         All the exceptions except SdlTypeError are derived from SdlException base class. Client
242         can catch only that exception if separate handling for different SDL error situations is
243         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
244         misuse of the SDL API.
245
246         Args:
247             ns (str): Namespace under which this operation is targeted.
248             key (str): Key for which data modification will be executed.
249             old_data (bytes): Last known data.
250             new_data (bytes): Data to be written.
251
252         Returns:
253             bool: True for successful modification, false if the user's last known data did not
254                   match the current value in data storage.
255
256         Raises:
257             SdlTypeError: If function's argument is of an inappropriate type.
258             NotConnected: If SDL is not connected to the backend data storage.
259             RejectedByBackend: If backend data storage rejects the request.
260             BackendError: If the backend data storage fails to process the request.
261         """
262         pass
263
264     @abstractmethod
265     def set_if_not_exists(self, ns: str, key: str, data: bytes) -> bool:
266         """
267         Write data to SDL storage if key does not exist.
268
269         Conditionally set the value of a key. If key already exists, then its value is not
270         modified. Checking the key existence and potential set operation is done as a one atomic
271         operation.
272         All the exceptions except SdlTypeError are derived from SdlException base class. Client
273         can catch only that exception if separate handling for different SDL error situations is
274         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
275         misuse of the SDL API.
276
277         Args:
278             ns (str): Namespace under which this operation is targeted.
279             key (str): Key to be set.
280             data (bytes): Data to be written.
281
282         Returns:
283             bool: True if key didn't exist yet and set operation was executed, false if key already
284                   existed and thus its value was left untouched.
285
286         Raises:
287             SdlTypeError: If function's argument is of an inappropriate type.
288             NotConnected: If SDL is not connected to the backend data storage.
289             RejectedByBackend: If backend data storage rejects the request.
290             BackendError: If the backend data storage fails to process the request.
291         """
292         pass
293
294     @abstractmethod
295     def get(self, ns: str, keys: Union[str, Set[str]]) -> Dict[str, bytes]:
296         """
297         Read data from SDL storage.
298
299         Only those entries that are found will be returned.
300         All the exceptions except SdlTypeError are derived from SdlException base class. Client
301         can catch only that exception if separate handling for different SDL error situations is
302         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
303         misuse of the SDL API.
304
305         Args:
306             ns (str): Namespace under which this operation is targeted.
307             keys (str or set of str): One or multiple keys to be read.
308
309         Returns:
310             (dict of str: bytes): A dictionary mapping of a key to the read data from the storage.
311                                   Dictionary is sorted by key values in alphabetical order.
312
313         Raises:
314             SdlTypeError: If function's argument is of an inappropriate type.
315             NotConnected: If SDL is not connected to the backend data storage.
316             RejectedByBackend: If backend data storage rejects the request.
317             BackendError: If the backend data storage fails to process the request.
318         """
319         pass
320
321     @abstractmethod
322     def find_keys(self, ns: str, key_pattern: str) -> List[str]:
323         r"""
324         Find all keys matching search pattern under the namespace.
325
326         Supported glob-style patterns:
327             `?` matches any single character. For example `?at` matches Cat, cat, Bat or bat.
328             `*` matches any number of any characters including none. For example `*Law*` matches
329                 Law, GrokLaw, or Lawyer.
330             `[abc]` matches one character given in the bracket. For example `[CB]at` matches Cat or
331                     Bat.
332             `[a-z]` matches one character from the range given in the bracket. For example
333                     `Letter[0-9]` matches Letter0 up to Letter9.
334             `[^abc]` matches any single character what is not given in the bracket. For example
335                      `h[^e]llo` matches hallo, hillo but not hello.
336
337         If searched key itself contains a special character, use a backslash (\) character to
338         escape the special character to match it verbatim.
339
340         NOTE: `find_keys` function is not guaranteed to be atomic or isolated.
341
342         All the exceptions except SdlTypeError are derived from SdlException base class. Client
343         can catch only that exception if separate handling for different SDL error situations is
344         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
345         misuse of the SDL API.
346
347         Args:
348             ns (str): Namespace under which this operation is targeted.
349             key_pattern (str): Key search pattern.
350
351         Returns:
352             (list of str): A list of found keys.
353
354         Raises:
355             SdlTypeError: If function's argument is of an inappropriate type.
356             NotConnected: If SDL is not connected to the backend data storage.
357             RejectedByBackend: If backend data storage rejects the request.
358             BackendError: If the backend data storage fails to process the request.
359         """
360         pass
361
362     @abstractmethod
363     def find_and_get(self, ns: str, key_pattern: str) -> Dict[str, bytes]:
364         r"""
365         Find keys and get their respective data from SDL storage.
366
367         Supported glob-style patterns:
368             `?` matches any single character. For example `?at` matches Cat, cat, Bat or bat.
369             `*` matches any number of any characters including none. For example `*Law*` matches
370                 Law, GrokLaw, or Lawyer.
371             `[abc]` matches one character given in the bracket. For example `[CB]at` matches Cat or
372                     Bat.
373             `[a-z]` matches one character from the range given in the bracket. For example
374                     `Letter[0-9]` matches Letter0 up to Letter9.
375             `[^abc]` matches any single character what is not given in the bracket. For example
376                      `h[^e]llo` matches hallo, hillo but not hello.
377
378         If searched key itself contains a special character, use a backslash (\) character to
379         escape the special character to match it verbatim.
380
381         NOTE: `find_and_get` function is not guaranteed to be atomic or isolated.
382
383         All the exceptions except SdlTypeError are derived from SdlException base class. Client
384         can catch only that exception if separate handling for different SDL error situations is
385         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
386         misuse of the SDL API.
387
388         Args:
389             ns (str): Namespace under which this operation is targeted.
390             key_pattern (str): Key search pattern.
391
392         Returns:
393             (dict of str: bytes): A dictionary mapping of a key to the read data from the storage.
394                                   Dictionary is sorted by key values in alphabetical order.
395
396         Raises:
397             SdlTypeError: If function's argument is of an inappropriate type.
398             NotConnected: If SDL is not connected to the backend data storage.
399             RejectedByBackend: If backend data storage rejects the request.
400             BackendError: If the backend data storage fails to process the request.
401         """
402         pass
403
404     @abstractmethod
405     def remove(self, ns: str, keys: Union[str, Set[str]]) -> None:
406         """
407         Remove data from SDL storage. Existing keys are removed.
408
409         Removing is done atomically, i.e. either all succeeds, or all fails.
410         All the exceptions except SdlTypeError are derived from SdlException base class. Client
411         can catch only that exception if separate handling for different SDL error situations is
412         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
413         misuse of the SDL API.
414
415         Args:
416             ns (str): Namespace under which this operation is targeted.
417             keys (str or set of str): One key or multiple keys, which data is to be removed.
418
419         Returns:
420             None
421
422         Raises:
423             SdlTypeError: If function's argument is of an inappropriate type.
424             NotConnected: If SDL is not connected to the backend data storage.
425             RejectedByBackend: If backend data storage rejects the request.
426             BackendError: If the backend data storage fails to process the request.
427         """
428         pass
429
430     @abstractmethod
431     def remove_if(self, ns: str, key: str, data: bytes) -> bool:
432         """
433         Conditionally remove data from SDL storage if the current data value matches the user's
434         last known value.
435
436         All the exceptions except SdlTypeError are derived from SdlException base class. Client
437         can catch only that exception if separate handling for different SDL error situations is
438         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
439         misuse of the SDL API.
440
441         Args:
442             ns (str): Namespace under which this operation is targeted.
443             key (str): Key, which data is to be removed.
444             data (bytes): Last known value of data
445
446         Returns:
447             bool: True if successful removal, false if the user's last known data did not match the
448                   current value in data storage.
449
450         Raises:
451             SdlTypeError: If function's argument is of an inappropriate type.
452             NotConnected: If SDL is not connected to the backend data storage.
453             RejectedByBackend: If backend data storage rejects the request.
454             BackendError: If the backend data storage fails to process the request.
455         """
456         pass
457
458     @abstractmethod
459     def remove_all(self, ns: str) -> None:
460         """
461         Remove all keys under the namespace.
462
463         No prior knowledge about the keys in the given namespace exists, thus operation is not
464         guaranteed to be atomic or isolated.
465         All the exceptions except SdlTypeError are derived from SdlException base class. Client
466         can catch only that exception if separate handling for different SDL error situations is
467         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
468         misuse of the SDL API.
469
470         Args:
471             ns (str): Namespace under which this operation is targeted.
472
473         Returns:
474             None
475
476         Raises:
477             SdlTypeError: If function's argument is of an inappropriate type.
478             NotConnected: If SDL is not connected to the backend data storage.
479             RejectedByBackend: If backend data storage rejects the request.
480             BackendError: If the backend data storage fails to process the request.
481         """
482         pass
483
484     @abstractmethod
485     def add_member(self, ns: str, group: str, members: Union[bytes, Set[bytes]]) -> None:
486         """
487         Add new members to a SDL group under the namespace.
488
489         SDL groups are identified by their name, which is a key in storage. SDL groups are
490         unordered collections of members where each member is unique. If a member to be added is
491         already a member of the group, its addition is silently ignored. If the group does not
492         exist, it is created, and specified members are added to the group.
493         All the exceptions except SdlTypeError are derived from SdlException base class. Client
494         can catch only that exception if separate handling for different SDL error situations is
495         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
496         misuse of the SDL API.
497
498         Args:
499             ns (str): Namespace under which this operation is targeted.
500             group (str): Group name.
501             members (bytes or set of bytes): One or multiple members to be added.
502
503         Returns:
504             None
505
506         Raises:
507             SdlTypeError: If function's argument is of an inappropriate type.
508             NotConnected: If SDL is not connected to the backend data storage.
509             RejectedByBackend: If backend data storage rejects the request.
510             BackendError: If the backend data storage fails to process the request.
511         """
512         pass
513
514     @abstractmethod
515     def remove_member(self, ns: str, group: str, members: Union[bytes, Set[bytes]]) -> None:
516         """
517         Remove members from a SDL group.
518
519         SDL groups are unordered collections of members where each member is unique. If a member to
520         be removed does not exist in the group, its removal is silently ignored. If a group does
521         not exist, it is treated as an empty group and hence members removal is silently ignored.
522         All the exceptions except SdlTypeError are derived from SdlException base class. Client
523         can catch only that exception if separate handling for different SDL error situations is
524         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
525         misuse of the SDL API.
526
527         Args:
528             ns (str): Namespace under which this operation is targeted.
529             group (str): Group name.
530             members (bytes or set of bytes): One or multiple members to be removed.
531
532         Returns:
533             None
534
535         Raises:
536             SdlTypeError: If function's argument is of an inappropriate type.
537             NotConnected: If SDL is not connected to the backend data storage.
538             RejectedByBackend: If backend data storage rejects the request.
539             BackendError: If the backend data storage fails to process the request.
540         """
541         pass
542
543     @abstractmethod
544     def remove_group(self, ns: str, group: str) -> None:
545         """
546         Remove a SDL group along with its members.
547
548         SDL groups are unordered collections of members where each member is unique. If a group to
549         be removed does not exist, its removal is silently ignored.
550         All the exceptions except SdlTypeError are derived from SdlException base class. Client
551         can catch only that exception if separate handling for different SDL error situations is
552         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
553         misuse of the SDL API.
554
555         Args:
556             ns (str): Namespace under which this operation is targeted.
557             group (str): Group name to be removed.
558
559         Returns:
560             None
561
562         Raises:
563             SdlTypeError: If function's argument is of an inappropriate type.
564             NotConnected: If SDL is not connected to the backend data storage.
565             RejectedByBackend: If backend data storage rejects the request.
566             BackendError: If the backend data storage fails to process the request.
567         """
568         pass
569
570     @abstractmethod
571     def get_members(self, ns: str, group: str) -> Set[bytes]:
572         """
573         Get all the members of a SDL group.
574
575         SDL groups are unordered collections of members where each member is unique. If the group
576         does not exist, empty set is returned.
577         All the exceptions except SdlTypeError are derived from SdlException base class. Client
578         can catch only that exception if separate handling for different SDL error situations is
579         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
580         misuse of the SDL API.
581
582         Args:
583             ns (str): Namespace under which this operation is targeted.
584             group (str): Group name of which members are to be returned.
585
586         Returns:
587             (set of bytes): A set of the members of the group.
588             None
589
590         Raises:
591             SdlTypeError: If function's argument is of an inappropriate type.
592             NotConnected: If SDL is not connected to the backend data storage.
593             RejectedByBackend: If backend data storage rejects the request.
594             BackendError: If the backend data storage fails to process the request.
595         """
596         pass
597
598     @abstractmethod
599     def is_member(self, ns: str, group: str, member: bytes) -> bool:
600         """
601         Validate if a given member is in the SDL group.
602
603         SDL groups are unordered collections of members where each member is unique. If the group
604         does not exist, false is returned.
605         All the exceptions except SdlTypeError are derived from SdlException base class. Client
606         can catch only that exception if separate handling for different SDL error situations is
607         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
608         misuse of the SDL API.
609
610         Args:
611             ns (str): Namespace under which this operation is targeted.
612             group (str): Group name of which member existence is to be validated.
613             member (bytes): A member, which existence is to be validated.
614
615         Returns:
616             bool: True if member was in the group, false otherwise.
617
618         Raises:
619             SdlTypeError: If function's argument is of an inappropriate type.
620             NotConnected: If SDL is not connected to the backend data storage.
621             RejectedByBackend: If backend data storage rejects the request.
622             BackendError: If the backend data storage fails to process the request.
623         """
624         pass
625
626     @abstractmethod
627     def group_size(self, ns: str, group: str) -> int:
628         """
629         Return the number of members in a group.
630
631         SDL groups are unordered collections of members where each member is unique. If the group
632         does not exist, value 0 is returned.
633         All the exceptions except SdlTypeError are derived from SdlException base class. Client
634         can catch only that exception if separate handling for different SDL error situations is
635         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
636         misuse of the SDL API.
637
638         Args:
639             ns (str): Namespace under which this operation is targeted.
640             group (str): Group name of which members count is queried.
641
642         Returns:
643             int: Number of members in a group.
644
645         Raises:
646             SdlTypeError: If function's argument is of an inappropriate type.
647             NotConnected: If SDL is not connected to the backend data storage.
648             RejectedByBackend: If backend data storage rejects the request.
649             BackendError: If the backend data storage fails to process the request.
650         """
651         pass
652
653     @abstractmethod
654     def get_lock_resource(self, ns: str, resource: str,
655                           expiration: Union[int, float]) -> SyncLockAbc:
656         """
657         Return a lock resource for SDL.
658
659         A lock resource instance is created per namespace and it is identified by its `name` within
660         a namespace. A `get_lock_resource` returns a lock resource instance, it does not acquire
661         a lock. Lock resource provides lock handling methods such as acquiring a lock, extend
662         expiration time and releasing a lock.
663         All the exceptions except SdlTypeError are derived from SdlException base class. Client
664         can catch only that exception if separate handling for different SDL error situations is
665         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
666         misuse of the SDL API.
667
668         Args:
669             ns (str): Namespace under which this operation is targeted.
670             resource (str): Resource is used within namespace as a key for a lock entry in SDL.
671             expiration (int, float): Expiration time of a lock
672
673         Returns:
674             SyncLockAbc: Lock resource instance.
675
676         Raises:
677             SdlTypeError: If function's argument is of an inappropriate type.
678             NotConnected: If SDL is not connected to the backend data storage.
679             RejectedByBackend: If backend data storage rejects the request.
680             BackendError: If the backend data storage fails to process the request.
681         """
682         pass