blob: 1fa5742bd648b923b1a0b0f32ce1c51bb68200f5 [file] [log] [blame]
Timo Tietavainendada8462019-11-27 11:50:01 +02001# 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"""
23Examples how to use synchronous API functions of the Shared Data Layer (SDL).
24Execution of these examples requires:
25 * Following Redis extension commands have been installed to runtime environment:
26 - MSETPUB
27 - SETIE
Timo Tietavainen6589d4d2021-03-09 14:37:14 +020028 - SETIEMPUB
29 - SETNXMPUB
30 - DELMPUB
Timo Tietavainendada8462019-11-27 11:50:01 +020031 - DELIE
Timo Tietavainen6589d4d2021-03-09 14:37:14 +020032 - DELIEMPUB
Timo Tietavainendada8462019-11-27 11:50:01 +020033 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"""
48from ricsdl.syncstorage import SyncStorage
49from ricsdl.exceptions import RejectedByBackend, NotConnected, BackendError
50
51
52# Constants used in the examples below.
53MY_NS = 'my_ns'
54MY_GRP_NS = 'my_group_ns'
55MY_LOCK_NS = 'my_group_ns'
56
57
58def _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.
74mysdl = _try_func_return(SyncStorage)
75
Timo Tietavainen598ca392020-01-08 16:49:11 +020076# 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
Timo Tietavainenc979c0d2020-01-21 21:57:17 +020081# 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.
84is_active = mysdl.is_active()
85assert is_active is True
Timo Tietavainendada8462019-11-27 11:50:01 +020086
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.
94my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, {'my_key', 'someting not existing'}))
95for 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.
102was_set = _try_func_return(lambda: mysdl.set_if(MY_NS, 'my_key', b'my_value', b'my_value2'))
103assert 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.
106was_set = _try_func_return(lambda: mysdl.set_if(MY_NS, 'my_key', b'my_value', b'my_value2'))
107assert 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.
112was_set = _try_func_return(lambda: mysdl.set_if_not_exists(MY_NS, 'my_key2', b'my_value'))
113assert was_set is True
114# Try again. This time the key 'my_key2' already exists.
115was_set = _try_func_return(lambda: mysdl.set_if_not_exists(MY_NS, 'my_key2', b'my_value'))
116assert 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'))
121my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, 'my_key'))
122assert my_ret_dict == {}
123
124
125# Removes a key 'my_key' under given namespace only if the old value is 'my_value'.
126was_removed = _try_func_return(lambda: mysdl.remove_if(MY_NS, 'my_key2', b'my_value'))
127assert was_removed is True
128# Try again to remove not anymore existing key 'my_key'.
129was_removed = _try_func_return(lambda: mysdl.remove_if(MY_NS, 'my_key2', b'my_value'))
130assert 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'}))
135my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, {'my_key'}))
136assert my_ret_dict != {}
137
138_try_func_return(lambda: mysdl.remove_all(MY_NS))
139my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, {'my_key'}))
140assert 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'}))
Timo Tietavainen276ed3c2019-12-15 20:16:23 +0200145ret_keys = _try_func_return(lambda: mysdl.find_keys(MY_NS, 'my_k*'))
Timo Tietavainendada8462019-11-27 11:50:01 +0200146assert ret_keys == ['my_key']
147
148
Timo Tietavainen276ed3c2019-12-15 20:16:23 +0200149# Finds keys and their values under given namespace that are matching to given key search
150# pattern 'my_k*'.
Timo Tietavainendada8462019-11-27 11:50:01 +0200151# Note that the type of returned value is bytes.
Timo Tietavainen276ed3c2019-12-15 20:16:23 +0200152ret_key_values = _try_func_return(lambda: mysdl.find_and_get(MY_NS, 'my_k*'))
Timo Tietavainendada8462019-11-27 11:50:01 +0200153assert 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.
169ret_members = _try_func_return(lambda: mysdl.get_members(MY_GRP_NS, 'my_group'))
170assert ret_members == {b'a'}
171
172
173# Checks if 'a' is a member of the group 'my_group' under given namespace.
174was_member = _try_func_return(lambda: mysdl.is_member(MY_GRP_NS, 'my_group', b'a'))
175assert was_member is True
176was_member = _try_func_return(lambda: mysdl.is_member(MY_GRP_NS, 'my_group', b'not a member'))
177assert was_member is False
178
179
180# Returns the count of members of a group 'my_group' under given namespace.
181ret_count = _try_func_return(lambda: mysdl.group_size(MY_GRP_NS, 'my_group'))
182assert 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'}))
187ret_count = _try_func_return(lambda: mysdl.group_size(MY_GRP_NS, 'my_group'))
188assert 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'}))
193ret_count = _try_func_return(lambda: mysdl.group_size(MY_GRP_NS, 'my_group'))
194assert ret_count == 3
195
196_try_func_return(lambda: mysdl.remove_group(MY_GRP_NS, 'my_group'))
197ret_count = _try_func_return(lambda: mysdl.group_size(MY_GRP_NS, 'my_group'))
198ret_members = _try_func_return(lambda: mysdl.get_members(MY_GRP_NS, 'my_group'))
199assert ret_count == 0
200assert 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.
206my_lock = _try_func_return(lambda: mysdl.get_lock_resource(MY_LOCK_NS, "my_lock", expiration=5.5))
207assert 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.
211was_acquired = _try_func_return(lambda: my_lock.acquire(retry_interval=0.5, retry_timeout=2))
212assert was_acquired is True
213# Try again. This time a lock won't be acquired successfully, because we have a lock already.
214was_acquired = _try_func_return(lambda: my_lock.acquire(retry_interval=0.1, retry_timeout=0.2))
215assert 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.
223ret_time = _try_func_return(my_lock.get_validity_time)
224assert 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.
234my_lock = _try_func_return(lambda: mysdl.get_lock_resource(MY_LOCK_NS, "my_lock", 2.5))
235with 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.
247mysdl.close()