1 # Copyright (c) 2019 AT&T Intellectual Property.
2 # Copyright (c) 2018-2019 Nokia.
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 # This source code is part of the near-RT RIC (RAN Intelligent Controller)
18 # platform project (RICP).
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 (
35 class SyncLockAbc(ABC):
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.
41 A lock instance is created per namespace and it is identified by its `name` within a
44 A concrete implementation subclass 'SyncLock' derives from this abstract class.
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.
53 def __init__(self, ns: str, name: str, expiration: Union[int, float]) -> None:
57 self._expiration = expiration
59 def __enter__(self, *args, **kwargs):
60 if self.acquire(*args, **kwargs):
62 raise RejectedByBackend("Unable to acquire lock within the time specified")
64 def __exit__(self, exception_type, exception_value, traceback):
67 def acquire(self, retry_interval: Union[int, float] = 0.1,
68 retry_timeout: Union[int, float] = 10) -> bool:
70 Acquire a shared, distributed lock atomically.
72 A lock can be used as a mutual exclusion locking entry for a shared resources.
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.
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
87 bool: True for successful lock acquiring, false otherwise.
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.
97 def release(self) -> None:
99 Release a lock atomically.
101 Release the already acquired lock.
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.
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.
119 def refresh(self) -> None:
121 Refresh the remaining validity time of the existing lock back to an initial value.
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.
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.
139 def get_validity_time(self) -> Union[int, float]:
141 Get atomically the remaining validity time of the lock in seconds.
143 Return atomically time in seconds until the lock expires.
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.
152 (int, float): Validity time of the lock in seconds.
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.
162 class SyncStorageAbc(ABC):
164 An abstract class providing synchronous access to Shared Data Layer (SDL) storage.
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.
170 A concrete implementation subclass 'SyncStorage' derives from this abstract class.
176 Verify SDL storage healthiness.
178 Verify SDL connection to the backend data storage.
184 bool: True if SDL is operational, false otherwise.
194 Close the connection to SDL storage.
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.
210 def set(self, ns: str, data_map: Dict[str, bytes]) -> None:
212 Write data to SDL storage.
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.
221 ns (str): Namespace under which this operation is targeted.
222 data_map (dict of str: bytes): Data to be written.
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.
236 def set_if(self, ns: str, key: str, old_data: bytes, new_data: bytes) -> bool:
238 Conditionally modify the value of a key if the current value in data storage matches the
239 user's last known value.
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.
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.
253 bool: True for successful modification, false if the user's last known data did not
254 match the current value in data storage.
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.
265 def set_if_not_exists(self, ns: str, key: str, data: bytes) -> bool:
267 Write data to SDL storage if key does not exist.
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
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.
278 ns (str): Namespace under which this operation is targeted.
279 key (str): Key to be set.
280 data (bytes): Data to be written.
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.
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.
295 def get(self, ns: str, keys: Union[str, Set[str]]) -> Dict[str, bytes]:
297 Read data from SDL storage.
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.
306 ns (str): Namespace under which this operation is targeted.
307 keys (str or set of str): One or multiple keys to be read.
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.
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.
322 def find_keys(self, ns: str, key_pattern: str) -> List[str]:
324 Find all keys matching search pattern under the namespace.
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
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.
337 If searched key itself contains a special character, use a backslash (\) character to
338 escape the special character to match it verbatim.
340 NOTE: `find_keys` function is not guaranteed to be atomic or isolated.
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.
348 ns (str): Namespace under which this operation is targeted.
349 key_pattern (str): Key search pattern.
352 (list of str): A list of found keys.
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.
363 def find_and_get(self, ns: str, key_pattern: str) -> Dict[str, bytes]:
365 Find keys and get their respective data from SDL storage.
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
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.
378 If searched key itself contains a special character, use a backslash (\) character to
379 escape the special character to match it verbatim.
381 NOTE: `find_and_get` function is not guaranteed to be atomic or isolated.
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.
389 ns (str): Namespace under which this operation is targeted.
390 key_pattern (str): Key search pattern.
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.
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.
405 def remove(self, ns: str, keys: Union[str, Set[str]]) -> None:
407 Remove data from SDL storage. Existing keys are removed.
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.
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.
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.
431 def remove_if(self, ns: str, key: str, data: bytes) -> bool:
433 Conditionally remove data from SDL storage if the current data value matches the user's
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.
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
447 bool: True if successful removal, false if the user's last known data did not match the
448 current value in data storage.
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.
459 def remove_all(self, ns: str) -> None:
461 Remove all keys under the namespace.
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.
471 ns (str): Namespace under which this operation is targeted.
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.
485 def add_member(self, ns: str, group: str, members: Union[bytes, Set[bytes]]) -> None:
487 Add new members to a SDL group under the namespace.
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.
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.
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.
515 def remove_member(self, ns: str, group: str, members: Union[bytes, Set[bytes]]) -> None:
517 Remove members from a SDL group.
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.
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.
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.
544 def remove_group(self, ns: str, group: str) -> None:
546 Remove a SDL group along with its members.
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.
556 ns (str): Namespace under which this operation is targeted.
557 group (str): Group name to be removed.
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.
571 def get_members(self, ns: str, group: str) -> Set[bytes]:
573 Get all the members of a SDL group.
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.
583 ns (str): Namespace under which this operation is targeted.
584 group (str): Group name of which members are to be returned.
587 (set of bytes): A set of the members of the group.
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.
599 def is_member(self, ns: str, group: str, member: bytes) -> bool:
601 Validate if a given member is in the SDL group.
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.
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.
616 bool: True if member was in the group, false otherwise.
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.
627 def group_size(self, ns: str, group: str) -> int:
629 Return the number of members in a group.
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.
639 ns (str): Namespace under which this operation is targeted.
640 group (str): Group name of which members count is queried.
643 int: Number of members in a group.
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.
654 def get_lock_resource(self, ns: str, resource: str,
655 expiration: Union[int, float]) -> SyncLockAbc:
657 Return a lock resource for SDL.
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.
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
674 SyncLockAbc: Lock resource instance.
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.