618586d1846430bba8d1b96cecd3a3cc2138b0b5
[ric-plt/sdlpy.git] / ricsdl-package / examples / sync.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 """
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= [DB service port]
44      DBAAS_MASTER_NAME = [DB name]. Needed to set only if Redis sentinel is used to provide high
45      availability for Redis DB solution.
46      DBAAS_SERVICE_SENTINEL_PORT = [Redis sentinel port number]. Needed to set only if Redis
47      sentinel is in use.
48      DBASS_CLUSTER_ADDR_LIST = [list of DB service addresses]. Is set only if more than one
49      Redis sentinel groups are in use.
50    In official RIC deployments four first environment variables are defined in Helm configMaps
51    of the DBaaS and these configurations can be loaded automatically as environment variables
52    into application pods via `envFrom dbaas-appconfig` statement in an application Helm Charts.
53    The last environment variable is not for time being in use in official RIC deployments, only
54    in Nokia SEP deployments.
55 """
56 from ricsdl.syncstorage import SyncStorage
57 from ricsdl.exceptions import RejectedByBackend, NotConnected, BackendError
58
59
60 # Constants used in the examples below.
61 MY_NS = 'my_ns'
62 MY_GRP_NS = 'my_group_ns'
63 MY_LOCK_NS = 'my_group_ns'
64
65
66 def _try_func_return(func):
67     """
68     Generic wrapper function to call SDL API function and handle exceptions if they are raised.
69     """
70     try:
71         return func()
72     except RejectedByBackend as exp:
73         print(f'SDL function {func.__name__} failed: {str(exp)}')
74         # Permanent failure, just forward the exception
75         raise
76     except (NotConnected, BackendError) as exp:
77         print(f'SDL function {func.__name__} failed for a temporal error: {str(exp)}')
78         # Here we could have a retry logic
79
80
81 # Creates SDL instance. The call creates connection to the SDL database backend.
82 mysdl = _try_func_return(SyncStorage)
83
84 # Creates SDL instance what utilizes a fake database backend. Fake database is meant to
85 # be used only at development phase of SDL clients. It does not provide more advanced
86 # database services.
87 # mysdl = _try_func_return(lambda: SyncStorage(fake_db_backend='dict'))
88
89 # Checks if SDL is operational. Note that it is not necessary to call `is_active()` after each
90 # SDL instance creation. Below example is here just to show how to call it spontaneously
91 # when SDL healthiness is needed to check.
92 is_active = mysdl.is_active()
93 assert is_active is True
94
95 # Sets a value 'my_value' for a key 'my_key' under given namespace. Note that value
96 # type must be bytes and multiple key values can be set in one set function call.
97 _try_func_return(lambda: mysdl.set(MY_NS, {'my_key': b'my_value'}))
98
99
100 # Gets the value of 'my_value' under given namespace.
101 # Note that the type of returned value is bytes.
102 my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, {'my_key', 'someting not existing'}))
103 for key, val in my_ret_dict.items():
104     assert val.decode("utf-8") == u'my_value'
105
106
107 # Sets a value 'my_value2' for a key 'my_key' under given namespace only if the old value is
108 # 'my_value'.
109 # Note that value types must be bytes.
110 was_set = _try_func_return(lambda: mysdl.set_if(MY_NS, 'my_key', b'my_value', b'my_value2'))
111 assert was_set is True
112 # Try again. This time value 'my_value2' won't be set, because the key has already 'my_value2'
113 # value.
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 False
116
117
118 # Sets a value 'my_value' for a key 'my_key2' under given namespace only if the key doesn't exist.
119 # Note that value types must be bytes.
120 was_set = _try_func_return(lambda: mysdl.set_if_not_exists(MY_NS, 'my_key2', b'my_value'))
121 assert was_set is True
122 # Try again. This time the key 'my_key2' already exists.
123 was_set = _try_func_return(lambda: mysdl.set_if_not_exists(MY_NS, 'my_key2', b'my_value'))
124 assert was_set is False
125
126
127 # Removes a key 'my_key' under given namespace.
128 _try_func_return(lambda: mysdl.remove(MY_NS, 'my_key'))
129 my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, 'my_key'))
130 assert my_ret_dict == {}
131
132
133 # Removes a key 'my_key' under given namespace only if the old value is 'my_value'.
134 was_removed = _try_func_return(lambda: mysdl.remove_if(MY_NS, 'my_key2', b'my_value'))
135 assert was_removed is True
136 # Try again to remove not anymore existing key 'my_key'.
137 was_removed = _try_func_return(lambda: mysdl.remove_if(MY_NS, 'my_key2', b'my_value'))
138 assert was_removed is False
139
140
141 # Removes all the keys under given namespace.
142 _try_func_return(lambda: mysdl.set(MY_NS, {'my_key': b'something'}))
143 my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, {'my_key'}))
144 assert my_ret_dict != {}
145
146 _try_func_return(lambda: mysdl.remove_all(MY_NS))
147 my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, {'my_key'}))
148 assert my_ret_dict == {}
149
150
151 # Finds keys under given namespace that are matching to given key prefix 'my_k'.
152 _try_func_return(lambda: mysdl.set(MY_NS, {'my_key': b'my_value'}))
153 ret_keys = _try_func_return(lambda: mysdl.find_keys(MY_NS, 'my_k*'))
154 assert ret_keys == ['my_key']
155
156
157 # Finds keys and their values under given namespace that are matching to given key search
158 # pattern 'my_k*'.
159 # Note that the type of returned value is bytes.
160 ret_key_values = _try_func_return(lambda: mysdl.find_and_get(MY_NS, 'my_k*'))
161 assert ret_key_values == {'my_key': b'my_value'}
162
163 _try_func_return(lambda: mysdl.remove_all(MY_NS))
164
165
166 # Adds a member 'a' to a group 'my_group' under given namespace. A group is a unique collection of
167 # members.
168 # Note that member type must be bytes and multiple members can be set in one set function call.
169 _try_func_return(lambda: mysdl.add_member(MY_GRP_NS, 'my_group', {b'a'}))
170 # Try again to add a member 'a'. This time 'a' won't be added, because 'a' belongs already to
171 # the group.
172 _try_func_return(lambda: mysdl.add_member(MY_GRP_NS, 'my_group', {b'a'}))
173
174
175 # Gets group 'my_group' members under given namespace.
176 # Note that the type of returned member is bytes.
177 ret_members = _try_func_return(lambda: mysdl.get_members(MY_GRP_NS, 'my_group'))
178 assert ret_members == {b'a'}
179
180
181 # Checks if 'a' is a member of the group 'my_group' under given namespace.
182 was_member = _try_func_return(lambda: mysdl.is_member(MY_GRP_NS, 'my_group', b'a'))
183 assert was_member is True
184 was_member = _try_func_return(lambda: mysdl.is_member(MY_GRP_NS, 'my_group', b'not a member'))
185 assert was_member is False
186
187
188 # Returns the count of members of a group 'my_group' under given namespace.
189 ret_count = _try_func_return(lambda: mysdl.group_size(MY_GRP_NS, 'my_group'))
190 assert ret_count == 1
191
192
193 # Removes the member 'a' of the group 'my_group' under given namespace.
194 _try_func_return(lambda: mysdl.remove_member(MY_GRP_NS, 'my_group', {b'a', b'not exists'}))
195 ret_count = _try_func_return(lambda: mysdl.group_size(MY_GRP_NS, 'my_group'))
196 assert ret_count == 0
197
198
199 # Removes the group 'my_group' under given namespace.
200 _try_func_return(lambda: mysdl.add_member(MY_GRP_NS, 'my_group', {b'a', b'b', b'c'}))
201 ret_count = _try_func_return(lambda: mysdl.group_size(MY_GRP_NS, 'my_group'))
202 assert ret_count == 3
203
204 _try_func_return(lambda: mysdl.remove_group(MY_GRP_NS, 'my_group'))
205 ret_count = _try_func_return(lambda: mysdl.group_size(MY_GRP_NS, 'my_group'))
206 ret_members = _try_func_return(lambda: mysdl.get_members(MY_GRP_NS, 'my_group'))
207 assert ret_count == 0
208 assert ret_members == set()
209
210
211 # Gets a lock 'my_lock' resource under given namespace.
212 # Note that this function does not take a lock, you need to call 'acquire' function to take
213 # the lock to yourself.
214 my_lock = _try_func_return(lambda: mysdl.get_lock_resource(MY_LOCK_NS, "my_lock", expiration=5.5))
215 assert my_lock is not None
216
217
218 # Acquires a lock from the lock resource. Return True if lock was taken within given retry limits.
219 was_acquired = _try_func_return(lambda: my_lock.acquire(retry_interval=0.5, retry_timeout=2))
220 assert was_acquired is True
221 # Try again. This time a lock won't be acquired successfully, because we have a lock already.
222 was_acquired = _try_func_return(lambda: my_lock.acquire(retry_interval=0.1, retry_timeout=0.2))
223 assert was_acquired is False
224
225
226 # Refreshs the remaining validity time of the existing lock back to the initial value.
227 _try_func_return(my_lock.refresh)
228
229
230 # Gets the remaining validity time of the lock.
231 ret_time = _try_func_return(my_lock.get_validity_time)
232 assert ret_time != 0
233
234
235 # Releases the lock.
236 _try_func_return(my_lock.release)
237
238
239 # Locking example what utilizes python 'with' statement with SDL lock.
240 # The lock is released automatically when we are out of the scope of
241 # 'the with my_lock' statement.
242 my_lock = _try_func_return(lambda: mysdl.get_lock_resource(MY_LOCK_NS, "my_lock", 2.5))
243 with my_lock:
244     # Just an example how to use lock API
245     time_left = _try_func_return(my_lock.get_validity_time)
246
247     # Add here operations what needs to be done under a lock, for example some
248     # operations with a shared resources what needs to be done in a mutually
249     # exclusive way.
250
251 # Lock is not anymore hold here
252
253
254 # Closes the SDL connection.
255 mysdl.close()