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