blob: ce6ad206205cb9befbcfe4ec0ee1b77e0efb4a1c [file] [log] [blame]
Timo Tietavainendada8462019-11-27 11:50:01 +02001# Copyright (c) 2019 AT&T Intellectual Property.
Timo Tietavainene67b9ab2022-03-14 07:27:30 +02002# Copyright (c) 2018-2022 Nokia.
Timo Tietavainendada8462019-11-27 11:50:01 +02003#
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.
Timo Tietavainenc6c9af22021-05-11 14:43:09 +030042 DBAAS_SERVICE_HOST = [DB service address]
Timo Tietavainene67b9ab2022-03-14 07:27:30 +020043 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.
Timo Tietavainenc6c9af22021-05-11 14:43:09 +030053 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.
Timo Tietavainene67b9ab2022-03-14 07:27:30 +020058
Timo Tietavainendada8462019-11-27 11:50:01 +020059"""
60from ricsdl.syncstorage import SyncStorage
61from ricsdl.exceptions import RejectedByBackend, NotConnected, BackendError
62
63
64# Constants used in the examples below.
65MY_NS = 'my_ns'
66MY_GRP_NS = 'my_group_ns'
67MY_LOCK_NS = 'my_group_ns'
68
69
70def _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.
86mysdl = _try_func_return(SyncStorage)
87
Timo Tietavainen598ca392020-01-08 16:49:11 +020088# 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
Timo Tietavainenc979c0d2020-01-21 21:57:17 +020093# 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.
96is_active = mysdl.is_active()
97assert is_active is True
Timo Tietavainendada8462019-11-27 11:50:01 +020098
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.
106my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, {'my_key', 'someting not existing'}))
107for 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.
114was_set = _try_func_return(lambda: mysdl.set_if(MY_NS, 'my_key', b'my_value', b'my_value2'))
115assert 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.
118was_set = _try_func_return(lambda: mysdl.set_if(MY_NS, 'my_key', b'my_value', b'my_value2'))
119assert 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.
124was_set = _try_func_return(lambda: mysdl.set_if_not_exists(MY_NS, 'my_key2', b'my_value'))
125assert was_set is True
126# Try again. This time the key 'my_key2' already exists.
127was_set = _try_func_return(lambda: mysdl.set_if_not_exists(MY_NS, 'my_key2', b'my_value'))
128assert 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'))
133my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, 'my_key'))
134assert my_ret_dict == {}
135
136
137# Removes a key 'my_key' under given namespace only if the old value is 'my_value'.
138was_removed = _try_func_return(lambda: mysdl.remove_if(MY_NS, 'my_key2', b'my_value'))
139assert was_removed is True
140# Try again to remove not anymore existing key 'my_key'.
141was_removed = _try_func_return(lambda: mysdl.remove_if(MY_NS, 'my_key2', b'my_value'))
142assert 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'}))
147my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, {'my_key'}))
148assert my_ret_dict != {}
149
150_try_func_return(lambda: mysdl.remove_all(MY_NS))
151my_ret_dict = _try_func_return(lambda: mysdl.get(MY_NS, {'my_key'}))
152assert 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'}))
Timo Tietavainen276ed3c2019-12-15 20:16:23 +0200157ret_keys = _try_func_return(lambda: mysdl.find_keys(MY_NS, 'my_k*'))
Timo Tietavainendada8462019-11-27 11:50:01 +0200158assert ret_keys == ['my_key']
159
160
Timo Tietavainen276ed3c2019-12-15 20:16:23 +0200161# Finds keys and their values under given namespace that are matching to given key search
162# pattern 'my_k*'.
Timo Tietavainendada8462019-11-27 11:50:01 +0200163# Note that the type of returned value is bytes.
Timo Tietavainen276ed3c2019-12-15 20:16:23 +0200164ret_key_values = _try_func_return(lambda: mysdl.find_and_get(MY_NS, 'my_k*'))
Timo Tietavainendada8462019-11-27 11:50:01 +0200165assert 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.
181ret_members = _try_func_return(lambda: mysdl.get_members(MY_GRP_NS, 'my_group'))
182assert ret_members == {b'a'}
183
184
185# Checks if 'a' is a member of the group 'my_group' under given namespace.
186was_member = _try_func_return(lambda: mysdl.is_member(MY_GRP_NS, 'my_group', b'a'))
187assert was_member is True
188was_member = _try_func_return(lambda: mysdl.is_member(MY_GRP_NS, 'my_group', b'not a member'))
189assert was_member is False
190
191
192# Returns the count of members of a group 'my_group' under given namespace.
193ret_count = _try_func_return(lambda: mysdl.group_size(MY_GRP_NS, 'my_group'))
194assert 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'}))
199ret_count = _try_func_return(lambda: mysdl.group_size(MY_GRP_NS, 'my_group'))
200assert 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'}))
205ret_count = _try_func_return(lambda: mysdl.group_size(MY_GRP_NS, 'my_group'))
206assert ret_count == 3
207
208_try_func_return(lambda: mysdl.remove_group(MY_GRP_NS, 'my_group'))
209ret_count = _try_func_return(lambda: mysdl.group_size(MY_GRP_NS, 'my_group'))
210ret_members = _try_func_return(lambda: mysdl.get_members(MY_GRP_NS, 'my_group'))
211assert ret_count == 0
212assert 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.
218my_lock = _try_func_return(lambda: mysdl.get_lock_resource(MY_LOCK_NS, "my_lock", expiration=5.5))
219assert 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.
223was_acquired = _try_func_return(lambda: my_lock.acquire(retry_interval=0.5, retry_timeout=2))
224assert was_acquired is True
225# Try again. This time a lock won't be acquired successfully, because we have a lock already.
226was_acquired = _try_func_return(lambda: my_lock.acquire(retry_interval=0.1, retry_timeout=0.2))
227assert 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.
235ret_time = _try_func_return(my_lock.get_validity_time)
236assert 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.
246my_lock = _try_func_return(lambda: mysdl.get_lock_resource(MY_LOCK_NS, "my_lock", 2.5))
247with 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.
259mysdl.close()