1 # Copyright (c) 2019 AT&T Intellectual Property.
2 # Copyright (c) 2018-2022 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).
23 Examples how to use synchronous API functions of the Shared Data Layer (SDL).
24 Execution of these examples requires:
25 * Following Redis extension commands have been installed to runtime environment:
33 Redis v4.0 or greater is required. Older versions do not support extension modules.
34 Implementation of above commands is produced by RIC DBaaS:
35 https://gerrit.o-ran-sc.org/r/admin/repos/ric-plt/dbaas
36 In official RIC deployments these commands are installed by `dbaas` service to Redis
38 In development environment you may want install commands manually to pod/container, which is
40 * Following environment variables are needed to set to the pod/container where the application
41 utilizing SDL is going to be run.
42 DBAAS_SERVICE_HOST = [DB service address]
43 DBAAS_SERVICE_PORT= [Comma separated list of DB service ports]. Only one port supported in
44 RIC deployments, Nokia SEP deployments can have multiple ports.
45 DBAAS_MASTER_NAME = [Comma separated list of DB names]. Needed to set only if Redis
46 sentinel is used to provide high availability for Redis DB solution. Only one DB name
47 supported in RIC deployments, Nokia SEP deployments can have multiple DB names.
48 DBAAS_SERVICE_SENTINEL_PORT = [Comma separated list of Redis sentinel port number]. Needed
49 to set only if Redis sentinel is in use. Only one port supported in RIC deployments, Nokia
50 SEP deployments can have multiple ports.
51 DBASS_CLUSTER_ADDR_LIST = [Comma separated list of DB service addresses]. Is set only if
52 more than one Redis sentinel groups are in use. Only in use in Nokia SEP deployments.
53 In official RIC deployments four first environment variables are defined in Helm configMaps
54 of the DBaaS and these configurations can be loaded automatically as environment variables
55 into application pods via `envFrom dbaas-appconfig` statement in an application Helm Charts.
56 The last environment variable is not for time being in use in official RIC deployments, only
57 in Nokia SEP deployments.
60 from ricsdl.syncstorage import SyncStorage
61 from ricsdl.exceptions import RejectedByBackend, NotConnected, BackendError
64 # Constants used in the examples below.
66 MY_GRP_NS = 'my_group_ns'
67 MY_LOCK_NS = 'my_group_ns'
70 def _try_func_return(func):
72 Generic wrapper function to call SDL API function and handle exceptions if they are raised.
76 except RejectedByBackend as exp:
77 print(f'SDL function {func.__name__} failed: {str(exp)}')
78 # Permanent failure, just forward the exception
80 except (NotConnected, BackendError) as exp:
81 print(f'SDL function {func.__name__} failed for a temporal error: {str(exp)}')
82 # Here we could have a retry logic
85 # Creates SDL instance. The call creates connection to the SDL database backend.
86 mysdl = _try_func_return(SyncStorage)
88 # Creates SDL instance what utilizes a fake database backend. Fake database is meant to
89 # be used only at development phase of SDL clients. It does not provide more advanced
91 # mysdl = _try_func_return(lambda: SyncStorage(fake_db_backend='dict'))
93 # Checks if SDL is operational. Note that it is not necessary to call `is_active()` after each
94 # SDL instance creation. Below example is here just to show how to call it spontaneously
95 # when SDL healthiness is needed to check.
96 is_active = mysdl.is_active()
97 assert is_active is True
99 # Sets a value 'my_value' for a key 'my_key' under given namespace. Note that value
100 # type must be bytes and multiple key values can be set in one set function call.
101 _try_func_return(lambda: mysdl.set(MY_NS, {'my_key': b'my_value'}))
104 # Gets the value of 'my_value' under given namespace.
105 # Note that the type of returned value is bytes.
106 my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, {'my_key', 'someting not existing'}))
107 for key, val in my_ret_dict.items():
108 assert val.decode("utf-8") == u'my_value'
111 # Sets a value 'my_value2' for a key 'my_key' under given namespace only if the old value is
113 # Note that value types must be bytes.
114 was_set = _try_func_return(lambda: mysdl.set_if(MY_NS, 'my_key', b'my_value', b'my_value2'))
115 assert was_set is True
116 # Try again. This time value 'my_value2' won't be set, because the key has already 'my_value2'
118 was_set = _try_func_return(lambda: mysdl.set_if(MY_NS, 'my_key', b'my_value', b'my_value2'))
119 assert was_set is False
122 # Sets a value 'my_value' for a key 'my_key2' under given namespace only if the key doesn't exist.
123 # Note that value types must be bytes.
124 was_set = _try_func_return(lambda: mysdl.set_if_not_exists(MY_NS, 'my_key2', b'my_value'))
125 assert was_set is True
126 # Try again. This time the key 'my_key2' already exists.
127 was_set = _try_func_return(lambda: mysdl.set_if_not_exists(MY_NS, 'my_key2', b'my_value'))
128 assert was_set is False
131 # Removes a key 'my_key' under given namespace.
132 _try_func_return(lambda: mysdl.remove(MY_NS, 'my_key'))
133 my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, 'my_key'))
134 assert my_ret_dict == {}
137 # Removes a key 'my_key' under given namespace only if the old value is 'my_value'.
138 was_removed = _try_func_return(lambda: mysdl.remove_if(MY_NS, 'my_key2', b'my_value'))
139 assert was_removed is True
140 # Try again to remove not anymore existing key 'my_key'.
141 was_removed = _try_func_return(lambda: mysdl.remove_if(MY_NS, 'my_key2', b'my_value'))
142 assert was_removed is False
145 # Removes all the keys under given namespace.
146 _try_func_return(lambda: mysdl.set(MY_NS, {'my_key': b'something'}))
147 my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, {'my_key'}))
148 assert my_ret_dict != {}
150 _try_func_return(lambda: mysdl.remove_all(MY_NS))
151 my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, {'my_key'}))
152 assert my_ret_dict == {}
155 # Finds keys under given namespace that are matching to given key prefix 'my_k'.
156 _try_func_return(lambda: mysdl.set(MY_NS, {'my_key': b'my_value'}))
157 ret_keys = _try_func_return(lambda: mysdl.find_keys(MY_NS, 'my_k*'))
158 assert ret_keys == ['my_key']
161 # Finds keys and their values under given namespace that are matching to given key search
163 # Note that the type of returned value is bytes.
164 ret_key_values = _try_func_return(lambda: mysdl.find_and_get(MY_NS, 'my_k*'))
165 assert ret_key_values == {'my_key': b'my_value'}
167 _try_func_return(lambda: mysdl.remove_all(MY_NS))
170 # Adds a member 'a' to a group 'my_group' under given namespace. A group is a unique collection of
172 # Note that member type must be bytes and multiple members can be set in one set function call.
173 _try_func_return(lambda: mysdl.add_member(MY_GRP_NS, 'my_group', {b'a'}))
174 # Try again to add a member 'a'. This time 'a' won't be added, because 'a' belongs already to
176 _try_func_return(lambda: mysdl.add_member(MY_GRP_NS, 'my_group', {b'a'}))
179 # Gets group 'my_group' members under given namespace.
180 # Note that the type of returned member is bytes.
181 ret_members = _try_func_return(lambda: mysdl.get_members(MY_GRP_NS, 'my_group'))
182 assert ret_members == {b'a'}
185 # Checks if 'a' is a member of the group 'my_group' under given namespace.
186 was_member = _try_func_return(lambda: mysdl.is_member(MY_GRP_NS, 'my_group', b'a'))
187 assert was_member is True
188 was_member = _try_func_return(lambda: mysdl.is_member(MY_GRP_NS, 'my_group', b'not a member'))
189 assert was_member is False
192 # Returns the count of members of a group 'my_group' under given namespace.
193 ret_count = _try_func_return(lambda: mysdl.group_size(MY_GRP_NS, 'my_group'))
194 assert ret_count == 1
197 # Removes the member 'a' of the group 'my_group' under given namespace.
198 _try_func_return(lambda: mysdl.remove_member(MY_GRP_NS, 'my_group', {b'a', b'not exists'}))
199 ret_count = _try_func_return(lambda: mysdl.group_size(MY_GRP_NS, 'my_group'))
200 assert ret_count == 0
203 # Removes the group 'my_group' under given namespace.
204 _try_func_return(lambda: mysdl.add_member(MY_GRP_NS, 'my_group', {b'a', b'b', b'c'}))
205 ret_count = _try_func_return(lambda: mysdl.group_size(MY_GRP_NS, 'my_group'))
206 assert ret_count == 3
208 _try_func_return(lambda: mysdl.remove_group(MY_GRP_NS, 'my_group'))
209 ret_count = _try_func_return(lambda: mysdl.group_size(MY_GRP_NS, 'my_group'))
210 ret_members = _try_func_return(lambda: mysdl.get_members(MY_GRP_NS, 'my_group'))
211 assert ret_count == 0
212 assert ret_members == set()
215 # Gets a lock 'my_lock' resource under given namespace.
216 # Note that this function does not take a lock, you need to call 'acquire' function to take
217 # the lock to yourself.
218 my_lock = _try_func_return(lambda: mysdl.get_lock_resource(MY_LOCK_NS, "my_lock", expiration=5.5))
219 assert my_lock is not None
222 # Acquires a lock from the lock resource. Return True if lock was taken within given retry limits.
223 was_acquired = _try_func_return(lambda: my_lock.acquire(retry_interval=0.5, retry_timeout=2))
224 assert was_acquired is True
225 # Try again. This time a lock won't be acquired successfully, because we have a lock already.
226 was_acquired = _try_func_return(lambda: my_lock.acquire(retry_interval=0.1, retry_timeout=0.2))
227 assert was_acquired is False
230 # Refreshs the remaining validity time of the existing lock back to the initial value.
231 _try_func_return(my_lock.refresh)
234 # Gets the remaining validity time of the lock.
235 ret_time = _try_func_return(my_lock.get_validity_time)
240 _try_func_return(my_lock.release)
243 # Locking example what utilizes python 'with' statement with SDL lock.
244 # The lock is released automatically when we are out of the scope of
245 # 'the with my_lock' statement.
246 my_lock = _try_func_return(lambda: mysdl.get_lock_resource(MY_LOCK_NS, "my_lock", 2.5))
248 # Just an example how to use lock API
249 time_left = _try_func_return(my_lock.get_validity_time)
251 # Add here operations what needs to be done under a lock, for example some
252 # operations with a shared resources what needs to be done in a mutually
255 # Lock is not anymore hold here
258 # Closes the SDL connection.