Add implementation of SDL in python
[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 close(self):
175         """
176         Close the connection to SDL storage.
177
178         Args:
179             None
180
181         Returns:
182             None
183
184         Raises:
185             NotConnected: If SDL is not connected to the backend data storage.
186             RejectedByBackend: If backend data storage rejects the request.
187             BackendError: If the backend data storage fails to process the request.
188         """
189         pass
190
191     @abstractmethod
192     def set(self, ns: str, data_map: Dict[str, bytes]) -> None:
193         """
194         Write data to SDL storage.
195
196         Writing is done atomically, i.e. either all succeeds, or all fails.
197         All the exceptions except SdlTypeError are derived from SdlException base class. Client
198         can catch only that exception if separate handling for different SDL error situations is
199         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
200         misuse of the SDL API.
201
202         Args:
203             ns (str): Namespace under which this operation is targeted.
204             data_map (dict of str: bytes): Data to be written.
205
206         Returns:
207             None
208
209         Raises:
210             SdlTypeError: If function's argument is of an inappropriate type.
211             NotConnected: If SDL is not connected to the backend data storage.
212             RejectedByBackend: If backend data storage rejects the request.
213             BackendError: If the backend data storage fails to process the request.
214         """
215         pass
216
217     @abstractmethod
218     def set_if(self, ns: str, key: str, old_data: bytes, new_data: bytes) -> bool:
219         """
220         Conditionally modify the value of a key if the current value in data storage matches the
221         user's last known value.
222
223         All the exceptions except SdlTypeError are derived from SdlException base class. Client
224         can catch only that exception if separate handling for different SDL error situations is
225         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
226         misuse of the SDL API.
227
228         Args:
229             ns (str): Namespace under which this operation is targeted.
230             key (str): Key for which data modification will be executed.
231             old_data (bytes): Last known data.
232             new_data (bytes): Data to be written.
233
234         Returns:
235             bool: True for successful modification, false if the user's last known data did not
236                   match the current value in data storage.
237
238         Raises:
239             SdlTypeError: If function's argument is of an inappropriate type.
240             NotConnected: If SDL is not connected to the backend data storage.
241             RejectedByBackend: If backend data storage rejects the request.
242             BackendError: If the backend data storage fails to process the request.
243         """
244         pass
245
246     @abstractmethod
247     def set_if_not_exists(self, ns: str, key: str, data: bytes) -> bool:
248         """
249         Write data to SDL storage if key does not exist.
250
251         Conditionally set the value of a key. If key already exists, then its value is not
252         modified. Checking the key existence and potential set operation is done as a one atomic
253         operation.
254         All the exceptions except SdlTypeError are derived from SdlException base class. Client
255         can catch only that exception if separate handling for different SDL error situations is
256         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
257         misuse of the SDL API.
258
259         Args:
260             ns (str): Namespace under which this operation is targeted.
261             key (str): Key to be set.
262             data (bytes): Data to be written.
263
264         Returns:
265             bool: True if key didn't exist yet and set operation was executed, false if key already
266                   existed and thus its value was left untouched.
267
268         Raises:
269             SdlTypeError: If function's argument is of an inappropriate type.
270             NotConnected: If SDL is not connected to the backend data storage.
271             RejectedByBackend: If backend data storage rejects the request.
272             BackendError: If the backend data storage fails to process the request.
273         """
274         pass
275
276     @abstractmethod
277     def get(self, ns: str, keys: Union[str, Set[str]]) -> Dict[str, bytes]:
278         """
279         Read data from SDL storage.
280
281         Only those entries that are found will be returned.
282         All the exceptions except SdlTypeError are derived from SdlException base class. Client
283         can catch only that exception if separate handling for different SDL error situations is
284         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
285         misuse of the SDL API.
286
287         Args:
288             ns (str): Namespace under which this operation is targeted.
289             keys (str or set of str): One or multiple keys to be read.
290
291         Returns:
292             (dict of str: bytes): A dictionary mapping of a key to the read data from the storage.
293
294         Raises:
295             SdlTypeError: If function's argument is of an inappropriate type.
296             NotConnected: If SDL is not connected to the backend data storage.
297             RejectedByBackend: If backend data storage rejects the request.
298             BackendError: If the backend data storage fails to process the request.
299         """
300         pass
301
302     @abstractmethod
303     def find_keys(self, ns: str, key_prefix: str) -> List[str]:
304         """
305         Find all keys matching search pattern under the namespace.
306
307         No prior knowledge about the keys in the given namespace exists, thus operation is not
308         guaranteed to be atomic or isolated.
309         All the exceptions except SdlTypeError are derived from SdlException base class. Client
310         can catch only that exception if separate handling for different SDL error situations is
311         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
312         misuse of the SDL API.
313
314         Args:
315             ns (str): Namespace under which this operation is targeted.
316             key_prefix (str): Only keys starting with given keyPrefix are returned. Passing empty
317                               string as keyPrefix will return all the keys.
318
319         Returns:
320             (list of str): A list of found keys.
321
322         Raises:
323             SdlTypeError: If function's argument is of an inappropriate type.
324             NotConnected: If SDL is not connected to the backend data storage.
325             RejectedByBackend: If backend data storage rejects the request.
326             BackendError: If the backend data storage fails to process the request.
327         """
328         pass
329
330     @abstractmethod
331     def find_and_get(self, ns: str, key_prefix: str, atomic: bool) -> Dict[str, bytes]:
332         """
333         Find keys and get their respective data from SDL storage.
334
335         Only those entries that are matching prefix will be returned.
336         NOTE: In atomic action, if the prefix produces huge number of matches, that can have
337         a severe impact on system performance, due to DB is blocked for long time.
338         All the exceptions except SdlTypeError are derived from SdlException base class. Client
339         can catch only that exception if separate handling for different SDL error situations is
340         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
341         misuse of the SDL API.
342
343         Args:
344             ns (str): Namespace under which this operation is targeted.
345             key_prefix (str): Only keys starting with given keyPrefix are returned. Passing empty
346                               string as keyPrefix will return all the keys.
347             atomic (bool): True to find keys and get their respective data in one atomic operation,
348                            false to find keys and get their respective data non-atomically.
349
350         Returns:
351             (dict of str: bytes): A dictionary mapping of a key to the read data from the storage.
352
353         Raises:
354             SdlTypeError: If function's argument is of an inappropriate type.
355             NotConnected: If SDL is not connected to the backend data storage.
356             RejectedByBackend: If backend data storage rejects the request.
357             BackendError: If the backend data storage fails to process the request.
358         """
359         pass
360
361     @abstractmethod
362     def remove(self, ns: str, keys: Union[str, Set[str]]) -> None:
363         """
364         Remove data from SDL storage. Existing keys are removed.
365
366         Removing is done atomically, i.e. either all succeeds, or all fails.
367         All the exceptions except SdlTypeError are derived from SdlException base class. Client
368         can catch only that exception if separate handling for different SDL error situations is
369         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
370         misuse of the SDL API.
371
372         Args:
373             ns (str): Namespace under which this operation is targeted.
374             keys (str or set of str): One key or multiple keys, which data is to be removed.
375
376         Returns:
377             None
378
379         Raises:
380             SdlTypeError: If function's argument is of an inappropriate type.
381             NotConnected: If SDL is not connected to the backend data storage.
382             RejectedByBackend: If backend data storage rejects the request.
383             BackendError: If the backend data storage fails to process the request.
384         """
385         pass
386
387     @abstractmethod
388     def remove_if(self, ns: str, key: str, data: bytes) -> bool:
389         """
390         Conditionally remove data from SDL storage if the current data value matches the user's
391         last known value.
392
393         All the exceptions except SdlTypeError are derived from SdlException base class. Client
394         can catch only that exception if separate handling for different SDL error situations is
395         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
396         misuse of the SDL API.
397
398         Args:
399             ns (str): Namespace under which this operation is targeted.
400             key (str): Key, which data is to be removed.
401             data (bytes): Last known value of data
402
403         Returns:
404             bool: True if successful removal, false if the user's last known data did not match the
405                   current value in data storage.
406
407         Raises:
408             SdlTypeError: If function's argument is of an inappropriate type.
409             NotConnected: If SDL is not connected to the backend data storage.
410             RejectedByBackend: If backend data storage rejects the request.
411             BackendError: If the backend data storage fails to process the request.
412         """
413         pass
414
415     @abstractmethod
416     def remove_all(self, ns: str) -> None:
417         """
418         Remove all keys under the namespace.
419
420         No prior knowledge about the keys in the given namespace exists, thus operation is not
421         guaranteed to be atomic or isolated.
422         All the exceptions except SdlTypeError are derived from SdlException base class. Client
423         can catch only that exception if separate handling for different SDL error situations is
424         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
425         misuse of the SDL API.
426
427         Args:
428             ns (str): Namespace under which this operation is targeted.
429
430         Returns:
431             None
432
433         Raises:
434             SdlTypeError: If function's argument is of an inappropriate type.
435             NotConnected: If SDL is not connected to the backend data storage.
436             RejectedByBackend: If backend data storage rejects the request.
437             BackendError: If the backend data storage fails to process the request.
438         """
439         pass
440
441     @abstractmethod
442     def add_member(self, ns: str, group: str, members: Union[bytes, Set[bytes]]) -> None:
443         """
444         Add new members to a SDL group under the namespace.
445
446         SDL groups are identified by their name, which is a key in storage. SDL groups are
447         unordered collections of members where each member is unique. If a member to be added is
448         already a member of the group, its addition is silently ignored. If the group does not
449         exist, it is created, and specified members are added to the group.
450         All the exceptions except SdlTypeError are derived from SdlException base class. Client
451         can catch only that exception if separate handling for different SDL error situations is
452         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
453         misuse of the SDL API.
454
455         Args:
456             ns (str): Namespace under which this operation is targeted.
457             group (str): Group name.
458             members (bytes or set of bytes): One or multiple members to be added.
459
460         Returns:
461             None
462
463         Raises:
464             SdlTypeError: If function's argument is of an inappropriate type.
465             NotConnected: If SDL is not connected to the backend data storage.
466             RejectedByBackend: If backend data storage rejects the request.
467             BackendError: If the backend data storage fails to process the request.
468         """
469         pass
470
471     @abstractmethod
472     def remove_member(self, ns: str, group: str, members: Union[bytes, Set[bytes]]) -> None:
473         """
474         Remove members from a SDL group.
475
476         SDL groups are unordered collections of members where each member is unique. If a member to
477         be removed does not exist in the group, its removal is silently ignored. If a group does
478         not exist, it is treated as an empty group and hence members removal is silently ignored.
479         All the exceptions except SdlTypeError are derived from SdlException base class. Client
480         can catch only that exception if separate handling for different SDL error situations is
481         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
482         misuse of the SDL API.
483
484         Args:
485             ns (str): Namespace under which this operation is targeted.
486             group (str): Group name.
487             members (bytes or set of bytes): One or multiple members to be removed.
488
489         Returns:
490             None
491
492         Raises:
493             SdlTypeError: If function's argument is of an inappropriate type.
494             NotConnected: If SDL is not connected to the backend data storage.
495             RejectedByBackend: If backend data storage rejects the request.
496             BackendError: If the backend data storage fails to process the request.
497         """
498         pass
499
500     @abstractmethod
501     def remove_group(self, ns: str, group: str) -> None:
502         """
503         Remove a SDL group along with its members.
504
505         SDL groups are unordered collections of members where each member is unique. If a group to
506         be removed does not exist, its removal is silently ignored.
507         All the exceptions except SdlTypeError are derived from SdlException base class. Client
508         can catch only that exception if separate handling for different SDL error situations is
509         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
510         misuse of the SDL API.
511
512         Args:
513             ns (str): Namespace under which this operation is targeted.
514             group (str): Group name to be removed.
515
516         Returns:
517             None
518
519         Raises:
520             SdlTypeError: If function's argument is of an inappropriate type.
521             NotConnected: If SDL is not connected to the backend data storage.
522             RejectedByBackend: If backend data storage rejects the request.
523             BackendError: If the backend data storage fails to process the request.
524         """
525         pass
526
527     @abstractmethod
528     def get_members(self, ns: str, group: str) -> Set[bytes]:
529         """
530         Get all the members of a SDL group.
531
532         SDL groups are unordered collections of members where each member is unique. If the group
533         does not exist, empty set is returned.
534         All the exceptions except SdlTypeError are derived from SdlException base class. Client
535         can catch only that exception if separate handling for different SDL error situations is
536         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
537         misuse of the SDL API.
538
539         Args:
540             ns (str): Namespace under which this operation is targeted.
541             group (str): Group name of which members are to be returned.
542
543         Returns:
544             (set of bytes): A set of the members of the group.
545             None
546
547         Raises:
548             SdlTypeError: If function's argument is of an inappropriate type.
549             NotConnected: If SDL is not connected to the backend data storage.
550             RejectedByBackend: If backend data storage rejects the request.
551             BackendError: If the backend data storage fails to process the request.
552         """
553         pass
554
555     @abstractmethod
556     def is_member(self, ns: str, group: str, member: bytes) -> bool:
557         """
558         Validate if a given member is in the SDL group.
559
560         SDL groups are unordered collections of members where each member is unique. If the group
561         does not exist, false is returned.
562         All the exceptions except SdlTypeError are derived from SdlException base class. Client
563         can catch only that exception if separate handling for different SDL error situations is
564         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
565         misuse of the SDL API.
566
567         Args:
568             ns (str): Namespace under which this operation is targeted.
569             group (str): Group name of which member existence is to be validated.
570             member (bytes): A member, which existence is to be validated.
571
572         Returns:
573             bool: True if member was in the group, false otherwise.
574
575         Raises:
576             SdlTypeError: If function's argument is of an inappropriate type.
577             NotConnected: If SDL is not connected to the backend data storage.
578             RejectedByBackend: If backend data storage rejects the request.
579             BackendError: If the backend data storage fails to process the request.
580         """
581         pass
582
583     @abstractmethod
584     def group_size(self, ns: str, group: str) -> int:
585         """
586         Return the number of members in a group.
587
588         SDL groups are unordered collections of members where each member is unique. If the group
589         does not exist, value 0 is returned.
590         All the exceptions except SdlTypeError are derived from SdlException base class. Client
591         can catch only that exception if separate handling for different SDL error situations is
592         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
593         misuse of the SDL API.
594
595         Args:
596             ns (str): Namespace under which this operation is targeted.
597             group (str): Group name of which members count is queried.
598
599         Returns:
600             int: Number of members in a group.
601
602         Raises:
603             SdlTypeError: If function's argument is of an inappropriate type.
604             NotConnected: If SDL is not connected to the backend data storage.
605             RejectedByBackend: If backend data storage rejects the request.
606             BackendError: If the backend data storage fails to process the request.
607         """
608         pass
609
610     @abstractmethod
611     def get_lock_resource(self, ns: str, resource: str,
612                           expiration: Union[int, float]) -> SyncLockAbc:
613         """
614         Return a lock resource for SDL.
615
616         A lock resource instance is created per namespace and it is identified by its `name` within
617         a namespace. A `get_lock_resource` returns a lock resource instance, it does not acquire
618         a lock. Lock resource provides lock handling methods such as acquiring a lock, extend
619         expiration time and releasing a lock.
620         All the exceptions except SdlTypeError are derived from SdlException base class. Client
621         can catch only that exception if separate handling for different SDL error situations is
622         not needed. Exception SdlTypeError is derived from build-in TypeError and it indicates
623         misuse of the SDL API.
624
625         Args:
626             ns (str): Namespace under which this operation is targeted.
627             resource (str): Resource is used within namespace as a key for a lock entry in SDL.
628             expiration (int, float): Expiration time of a lock
629
630         Returns:
631             SyncLockAbc: Lock resource instance.
632
633         Raises:
634             SdlTypeError: If function's argument is of an inappropriate type.
635             NotConnected: If SDL is not connected to the backend data storage.
636             RejectedByBackend: If backend data storage rejects the request.
637             BackendError: If the backend data storage fails to process the request.
638         """
639         pass