Read list type of environment variables
[ric-plt/sdlpy.git] / ricsdl-package / examples / sync.py
1 # Copyright (c) 2019 AT&T Intellectual Property.
2 # Copyright (c) 2018-2022 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 """
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:
26    - MSETPUB
27    - SETIE
28    - SETIEMPUB
29    - SETNXMPUB
30    - DELMPUB
31    - DELIE
32    - DELIEMPUB
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
37    container(s).
38    In development environment you may want install commands manually to pod/container, which is
39    running Redis.
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.
58
59 """
60 from ricsdl.syncstorage import SyncStorage
61 from ricsdl.exceptions import RejectedByBackend, NotConnected, BackendError
62
63
64 # Constants used in the examples below.
65 MY_NS = 'my_ns'
66 MY_GRP_NS = 'my_group_ns'
67 MY_LOCK_NS = 'my_group_ns'
68
69
70 def _try_func_return(func):
71     """
72     Generic wrapper function to call SDL API function and handle exceptions if they are raised.
73     """
74     try:
75         return func()
76     except RejectedByBackend as exp:
77         print(f'SDL function {func.__name__} failed: {str(exp)}')
78         # Permanent failure, just forward the exception
79         raise
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
83
84
85 # Creates SDL instance. The call creates connection to the SDL database backend.
86 mysdl = _try_func_return(SyncStorage)
87
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
90 # database services.
91 # mysdl = _try_func_return(lambda: SyncStorage(fake_db_backend='dict'))
92
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
98
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'}))
102
103
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'
109
110
111 # Sets a value 'my_value2' for a key 'my_key' under given namespace only if the old value is
112 # 'my_value'.
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'
117 # value.
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
120
121
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
129
130
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 == {}
135
136
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
143
144
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 != {}
149
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 == {}
153
154
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']
159
160
161 # Finds keys and their values under given namespace that are matching to given key search
162 # pattern 'my_k*'.
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'}
166
167 _try_func_return(lambda: mysdl.remove_all(MY_NS))
168
169
170 # Adds a member 'a' to a group 'my_group' under given namespace. A group is a unique collection of
171 # members.
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
175 # the group.
176 _try_func_return(lambda: mysdl.add_member(MY_GRP_NS, 'my_group', {b'a'}))
177
178
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'}
183
184
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
190
191
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
195
196
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
201
202
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
207
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()
213
214
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
220
221
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
228
229
230 # Refreshs the remaining validity time of the existing lock back to the initial value.
231 _try_func_return(my_lock.refresh)
232
233
234 # Gets the remaining validity time of the lock.
235 ret_time = _try_func_return(my_lock.get_validity_time)
236 assert ret_time != 0
237
238
239 # Releases the lock.
240 _try_func_return(my_lock.release)
241
242
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))
247 with my_lock:
248     # Just an example how to use lock API
249     time_left = _try_func_return(my_lock.get_validity_time)
250
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
253     # exclusive way.
254
255 # Lock is not anymore hold here
256
257
258 # Closes the SDL connection.
259 mysdl.close()