1 # Copyright (c) 2019 AT&T Intellectual Property.
2 # Copyright (c) 2018-2019 Nokia.
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 # This source code is part of the near-RT RIC (RAN Intelligent Controller)
18 # platform project (RICP).
22 from unittest.mock import patch, Mock
24 from redis import exceptions as redis_exceptions
26 from ricsdl.backend.redis import (RedisBackendLock, _map_to_sdl_exception)
27 from ricsdl.configuration import _Configuration
28 from ricsdl.configuration import DbBackendType
29 import ricsdl.exceptions
33 def redis_backend_fixture(request):
34 request.cls.ns = 'some-ns'
35 request.cls.dl_redis = [b'1', b'2']
36 request.cls.dm = {'a': b'1', 'b': b'2'}
37 request.cls.dm_redis = {'{some-ns},a': b'1', '{some-ns},b': b'2'}
38 request.cls.dm_redis_flat = ['{some-ns},a', b'1', '{some-ns},b', b'2']
40 request.cls.key_redis = '{some-ns},a'
41 request.cls.keys = ['a', 'b']
42 request.cls.keys_redis = ['{some-ns},a', '{some-ns},b']
43 request.cls.data = b'123'
44 request.cls.old_data = b'1'
45 request.cls.new_data = b'3'
46 request.cls.keypattern = r'[Aa]bc-\[1\].?-*'
47 request.cls.keypattern_redis = r'{some-ns},[Aa]bc-\[1\].?-*'
48 request.cls.matchedkeys = ['Abc-[1].0-def', 'abc-[1].1-ghi']
49 request.cls.matchedkeys_redis = [b'{some-ns},Abc-[1].0-def',
50 b'{some-ns},abc-[1].1-ghi']
51 request.cls.matcheddata_redis = [b'10', b'11']
52 request.cls.matchedkeydata = {'Abc-[1].0-def': b'10',
53 'abc-[1].1-ghi': b'11'}
54 request.cls.group = 'some-group'
55 request.cls.group_redis = '{some-ns},some-group'
56 request.cls.groupmembers = set([b'm1', b'm2'])
57 request.cls.groupmember = b'm1'
58 request.cls.channels = ['abs', 'gma']
59 request.cls.channels_and_events = {'abs': ['cbn']}
60 request.cls.channels_and_events_redis = ['{some-ns},abs', 'cbn']
62 request.cls.configuration = Mock()
63 mock_conf_params = _Configuration.Params(db_host=None,
65 db_sentinel_port=None,
66 db_sentinel_master_name=None,
67 db_type=DbBackendType.REDIS)
68 request.cls.configuration.get_params.return_value = mock_conf_params
69 with patch('ricsdl.backend.redis.Redis') as mock_redis, patch(
70 'ricsdl.backend.redis.PubSub') as mock_pubsub:
71 db = ricsdl.backend.get_backend_instance(request.cls.configuration)
72 request.cls.mock_redis = mock_redis.return_value
73 request.cls.mock_pubsub = mock_pubsub.return_value
79 @pytest.mark.usefixtures('redis_backend_fixture')
80 class TestRedisBackend:
81 def test_is_connected_function_success(self):
82 self.mock_redis.ping.return_value = True
83 ret = self.db.is_connected()
84 self.mock_redis.ping.assert_called_once()
87 def test_is_connected_function_returns_false_if_ping_fails(self):
88 self.mock_redis.ping.return_value = False
89 ret = self.db.is_connected()
90 self.mock_redis.ping.assert_called_once()
93 def test_is_connected_function_can_map_redis_exception_to_sdl_exception(self):
94 self.mock_redis.ping.side_effect = redis_exceptions.ResponseError('redis error!')
95 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
96 self.db.is_connected()
98 def test_set_function_success(self):
99 self.db.set(self.ns, self.dm)
100 self.mock_redis.mset.assert_called_once_with(self.dm_redis)
102 def test_set_function_can_map_redis_exception_to_sdl_exception(self):
103 self.mock_redis.mset.side_effect = redis_exceptions.ResponseError('redis error!')
104 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
105 self.db.set(self.ns, self.dm)
107 def test_set_if_function_success(self):
108 self.mock_redis.execute_command.return_value = True
109 ret = self.db.set_if(self.ns, self.key, self.old_data, self.new_data)
110 self.mock_redis.execute_command.assert_called_once_with('SETIE', self.key_redis,
111 self.new_data, self.old_data)
114 def test_set_if_function_returns_false_if_existing_key_value_not_expected(self):
115 self.mock_redis.execute_command.return_value = False
116 ret = self.db.set_if(self.ns, self.key, self.old_data, self.new_data)
117 self.mock_redis.execute_command.assert_called_once_with('SETIE', self.key_redis,
118 self.new_data, self.old_data)
121 def test_set_if_function_can_map_redis_exception_to_sdl_exception(self):
122 self.mock_redis.execute_command.side_effect = redis_exceptions.ResponseError('redis error!')
123 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
124 self.db.set_if(self.ns, self.key, self.old_data, self.new_data)
126 def test_set_if_not_exists_function_success(self):
127 self.mock_redis.setnx.return_value = True
128 ret = self.db.set_if_not_exists(self.ns, self.key, self.new_data)
129 self.mock_redis.setnx.assert_called_once_with(self.key_redis, self.new_data)
132 def test_set_if_not_exists_function_returns_false_if_key_already_exists(self):
133 self.mock_redis.setnx.return_value = False
134 ret = self.db.set_if_not_exists(self.ns, self.key, self.new_data)
135 self.mock_redis.setnx.assert_called_once_with(self.key_redis, self.new_data)
138 def test_set_if_not_exists_function_can_map_redis_exception_to_sdl_exception(self):
139 self.mock_redis.setnx.side_effect = redis_exceptions.ResponseError('redis error!')
140 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
141 self.db.set_if_not_exists(self.ns, self.key, self.new_data)
143 def test_get_function_success(self):
144 self.mock_redis.mget.return_value = self.dl_redis
145 ret = self.db.get(self.ns, self.keys)
146 self.mock_redis.mget.assert_called_once_with(self.keys_redis)
147 assert ret == self.dm
149 def test_get_function_returns_empty_dict_when_no_key_values_exist(self):
150 self.mock_redis.mget.return_value = [None, None]
151 ret = self.db.get(self.ns, self.keys)
152 self.mock_redis.mget.assert_called_once_with(self.keys_redis)
155 def test_get_function_returns_dict_only_with_found_key_values_when_some_keys_exist(self):
156 self.mock_redis.mget.return_value = [self.data, None]
157 ret = self.db.get(self.ns, self.keys)
158 self.mock_redis.mget.assert_called_once_with(self.keys_redis)
159 assert ret == {self.key: self.data}
161 def test_get_function_can_map_redis_exception_to_sdl_exception(self):
162 self.mock_redis.mget.side_effect = redis_exceptions.ResponseError('redis error!')
163 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
164 self.db.get(self.ns, self.keys)
166 def test_find_keys_function_success(self):
167 self.mock_redis.keys.return_value = self.matchedkeys_redis
168 ret = self.db.find_keys(self.ns, self.keypattern)
169 self.mock_redis.keys.assert_called_once_with(self.keypattern_redis)
170 assert ret == self.matchedkeys
172 def test_find_keys_function_returns_empty_list_when_no_matching_keys_found(self):
173 self.mock_redis.keys.return_value = []
174 ret = self.db.find_keys(self.ns, self.keypattern)
175 self.mock_redis.keys.assert_called_once_with(self.keypattern_redis)
178 def test_find_keys_function_can_map_redis_exception_to_sdl_exception(self):
179 self.mock_redis.keys.side_effect = redis_exceptions.ResponseError('redis error!')
180 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
181 self.db.find_keys(self.ns, self.keypattern)
183 def test_find_keys_function_can_raise_exception_when_redis_key_convert_to_string_fails(self):
184 # Redis returns an illegal key, which conversion to string fails
185 corrupt_redis_key = b'\x81'
186 self.mock_redis.keys.return_value = [corrupt_redis_key]
187 with pytest.raises(ricsdl.exceptions.RejectedByBackend) as excinfo:
188 self.db.find_keys(self.ns, self.keypattern)
189 assert f"Namespace {self.ns} key:{corrupt_redis_key} "
190 "has no namespace prefix" in str(excinfo.value)
192 def test_find_keys_function_can_raise_exception_when_redis_key_is_without_prefix(self):
193 # Redis returns an illegal key, which doesn't have comma separated namespace prefix
194 corrupt_redis_key = 'some-corrupt-key'
195 self.mock_redis.keys.return_value = [f'{corrupt_redis_key}'.encode()]
196 with pytest.raises(ricsdl.exceptions.RejectedByBackend) as excinfo:
197 self.db.find_keys(self.ns, self.keypattern)
198 assert f"Namespace {self.ns} key:{corrupt_redis_key} "
199 "has no namespace prefix" in str(excinfo.value)
201 def test_find_and_get_function_success(self):
202 self.mock_redis.keys.return_value = self.matchedkeys_redis
203 self.mock_redis.mget.return_value = self.matcheddata_redis
204 ret = self.db.find_and_get(self.ns, self.keypattern)
205 self.mock_redis.keys.assert_called_once_with(self.keypattern_redis)
206 self.mock_redis.mget.assert_called_once_with([i.decode() for i in self.matchedkeys_redis])
207 assert ret == self.matchedkeydata
209 def test_find_and_get_function_returns_empty_dict_when_no_matching_keys_exist(self):
210 self.mock_redis.keys.return_value = list()
211 ret = self.db.find_and_get(self.ns, self.keypattern)
212 self.mock_redis.keys.assert_called_once_with(self.keypattern_redis)
213 assert not self.mock_redis.mget.called
216 def test_find_and_get_function_can_map_redis_exception_to_sdl_exception(self):
217 self.mock_redis.keys.side_effect = redis_exceptions.ResponseError('redis error!')
218 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
219 self.db.find_and_get(self.ns, self.keypattern)
221 def test_find_and_get_function_can_raise_exception_when_redis_key_convert_to_string_fails(self):
222 # Redis returns an illegal key, which conversion to string fails
223 corrupt_redis_key = b'\x81'
224 self.mock_redis.keys.return_value = [corrupt_redis_key]
225 with pytest.raises(ricsdl.exceptions.RejectedByBackend) as excinfo:
226 self.db.find_and_get(self.ns, self.keypattern)
227 assert f"Namespace {self.ns} key:{corrupt_redis_key} "
228 "has no namespace prefix" in str(excinfo.value)
230 def test_find_and_get_function_can_raise_exception_when_redis_key_is_without_prefix(self):
231 # Redis returns an illegal key, which doesn't have comma separated namespace prefix
232 corrupt_redis_key = 'some-corrupt-key'
233 self.mock_redis.keys.return_value = [f'{corrupt_redis_key}'.encode()]
234 with pytest.raises(ricsdl.exceptions.RejectedByBackend) as excinfo:
235 self.db.find_and_get(self.ns, self.keypattern)
236 assert f"Namespace {self.ns} key:{corrupt_redis_key} "
237 "has no namespace prefix" in str(excinfo.value)
239 def test_remove_function_success(self):
240 self.db.remove(self.ns, self.keys)
241 self.mock_redis.delete.assert_called_once_with(*self.keys_redis)
243 def test_remove_function_can_map_redis_exception_to_sdl_exception(self):
244 self.mock_redis.delete.side_effect = redis_exceptions.ResponseError('redis error!')
245 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
246 self.db.remove(self.ns, self.keys)
248 def test_remove_if_function_success(self):
249 self.mock_redis.execute_command.return_value = True
250 ret = self.db.remove_if(self.ns, self.key, self.new_data)
251 self.mock_redis.execute_command.assert_called_once_with('DELIE', self.key_redis,
255 def test_remove_if_function_returns_false_if_data_does_not_match(self):
256 self.mock_redis.execute_command.return_value = False
257 ret = self.db.remove_if(self.ns, self.key, self.new_data)
258 self.mock_redis.execute_command.assert_called_once_with('DELIE', self.key_redis,
262 def test_remove_if_function_can_map_redis_exception_to_sdl_exception(self):
263 self.mock_redis.execute_command.side_effect = redis_exceptions.ResponseError('redis error!')
264 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
265 self.db.remove_if(self.ns, self.key, self.new_data)
267 def test_add_member_function_success(self):
268 self.db.add_member(self.ns, self.group, self.groupmembers)
269 self.mock_redis.sadd.assert_called_once_with(self.group_redis, *self.groupmembers)
271 def test_add_member_function_can_map_redis_exception_to_sdl_exception(self):
272 self.mock_redis.sadd.side_effect = redis_exceptions.ResponseError('redis error!')
273 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
274 self.db.add_member(self.ns, self.group, self.groupmembers)
276 def test_remove_member_function_success(self):
277 self.db.remove_member(self.ns, self.group, self.groupmembers)
278 self.mock_redis.srem.assert_called_once_with(self.group_redis, *self.groupmembers)
280 def test_remove_member_function_can_map_redis_exception_to_sdl_exception(self):
281 self.mock_redis.srem.side_effect = redis_exceptions.ResponseError('redis error!')
282 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
283 self.db.remove_member(self.ns, self.group, self.groupmembers)
285 def test_remove_group_function_success(self):
286 self.db.remove_group(self.ns, self.group)
287 self.mock_redis.delete.assert_called_once_with(self.group_redis)
289 def test_remove_group_function_can_map_redis_exception_to_sdl_exception(self):
290 self.mock_redis.delete.side_effect = redis_exceptions.ResponseError('redis error!')
291 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
292 self.db.remove_group(self.ns, self.group)
294 def test_get_members_function_success(self):
295 self.mock_redis.smembers.return_value = self.groupmembers
296 ret = self.db.get_members(self.ns, self.group)
297 self.mock_redis.smembers.assert_called_once_with(self.group_redis)
298 assert ret is self.groupmembers
300 def test_get_members_function_can_map_redis_exception_to_sdl_exception(self):
301 self.mock_redis.smembers.side_effect = redis_exceptions.ResponseError('redis error!')
302 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
303 self.db.get_members(self.ns, self.group)
305 def test_is_member_function_success(self):
306 self.mock_redis.sismember.return_value = True
307 ret = self.db.is_member(self.ns, self.group, self.groupmember)
308 self.mock_redis.sismember.assert_called_once_with(self.group_redis, self.groupmember)
311 def test_is_member_function_can_map_redis_exception_to_sdl_exception(self):
312 self.mock_redis.sismember.side_effect = redis_exceptions.ResponseError('redis error!')
313 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
314 self.db.is_member(self.ns, self.group, self.groupmember)
316 def test_group_size_function_success(self):
317 self.mock_redis.scard.return_value = 100
318 ret = self.db.group_size(self.ns, self.group)
319 self.mock_redis.scard.assert_called_once_with(self.group_redis)
322 def test_group_size_function_can_map_redis_exception_to_sdl_exception(self):
323 self.mock_redis.scard.side_effect = redis_exceptions.ResponseError('Some redis error!')
324 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
325 self.db.group_size(self.ns, self.group)
327 def test_set_and_publish_success(self):
328 self.db.set_and_publish(self.ns, self.channels_and_events, self.dm)
329 self.mock_redis.execute_command.assert_called_once_with('MSETMPUB', len(self.dm),
330 len(self.channels_and_events),
332 *self.channels_and_events_redis)
334 def test_set_and_publish_can_map_redis_exception_to_sdl_exception(self):
335 self.mock_redis.execute_command.side_effect = redis_exceptions.ResponseError('redis error!')
336 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
337 self.db.set_and_publish(self.ns, self.channels_and_events, self.dm)
339 def test_set_if_and_publish_success(self):
340 self.mock_redis.execute_command.return_value = b"OK"
341 ret = self.db.set_if_and_publish(self.ns, self.channels_and_events, self.key, self.old_data,
343 self.mock_redis.execute_command.assert_called_once_with('SETIEMPUB', self.key_redis,
344 self.new_data, self.old_data,
345 *self.channels_and_events_redis)
348 def test_set_if_and_publish_returns_false_if_existing_key_value_not_expected(self):
349 self.mock_redis.execute_command.return_value = None
350 ret = self.db.set_if_and_publish(self.ns, self.channels_and_events, self.key, self.old_data,
352 self.mock_redis.execute_command.assert_called_once_with('SETIEMPUB', self.key_redis,
353 self.new_data, self.old_data,
354 *self.channels_and_events_redis)
357 def test_set_if_and_publish_can_map_redis_exception_to_sdl_exception(self):
358 self.mock_redis.execute_command.side_effect = redis_exceptions.ResponseError('redis error!')
359 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
360 self.db.set_if_and_publish(self.ns, self.channels_and_events, self.key, self.old_data,
363 def test_set_if_not_exists_and_publish_success(self):
364 self.mock_redis.execute_command.return_value = b"OK"
365 ret = self.db.set_if_not_exists_and_publish(self.ns, self.channels_and_events, self.key,
367 self.mock_redis.execute_command.assert_called_once_with('SETNXMPUB', self.key_redis,
369 *self.channels_and_events_redis)
372 def test_set_if_not_exists_and_publish_returns_false_if_key_already_exists(self):
373 self.mock_redis.execute_command.return_value = None
374 ret = self.db.set_if_not_exists_and_publish(self.ns, self.channels_and_events, self.key,
376 self.mock_redis.execute_command.assert_called_once_with('SETNXMPUB', self.key_redis,
378 *self.channels_and_events_redis)
381 def set_if_not_exists_and_publish_can_map_redis_exception_to_sdl_exception(self):
382 self.mock_redis.execute_command.side_effect = redis_exceptions.ResponseError('redis error!')
383 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
384 self.db.set_if_not_exists_and_publish(self.ns, self.channels_and_events, self.key,
387 def test_remove_and_publish_success(self):
388 self.db.remove_and_publish(self.ns, self.channels_and_events, self.key)
389 self.mock_redis.execute_command.assert_called_once_with('DELMPUB', len(self.key),
390 len(self.channels_and_events),
392 *self.channels_and_events_redis)
394 def test_remove_if_and_publish_success(self):
395 self.mock_redis.execute_command.return_value = 1
396 ret = self.db.remove_if_and_publish(self.ns, self.channels_and_events, self.key,
398 self.mock_redis.execute_command.assert_called_once_with('DELIEMPUB', self.key_redis,
400 *self.channels_and_events_redis)
403 def test_remove_if_and_publish_returns_false_if_data_does_not_match(self):
404 self.mock_redis.execute_command.return_value = 0
405 ret = self.db.remove_if_and_publish(self.ns, self.channels_and_events, self.key,
407 self.mock_redis.execute_command.assert_called_once_with('DELIEMPUB', self.key_redis,
409 *self.channels_and_events_redis)
412 def test_remove_if_and_publish_can_map_redis_exception_to_sdl_exception(self):
413 self.mock_redis.execute_command.side_effect = redis_exceptions.ResponseError('redis error!')
414 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
415 self.db.remove_if_and_publish(self.ns, self.channels_and_events, self.key,
418 def test_remove_all_and_publish_success(self):
419 self.mock_redis.keys.return_value = ['{some-ns},a']
420 self.db.remove_all_and_publish(self.ns, self.channels_and_events)
421 self.mock_redis.keys.assert_called_once()
422 self.mock_redis.execute_command.assert_called_once_with('DELMPUB', len(self.key),
423 len(self.channels_and_events),
425 *self.channels_and_events_redis)
427 def test_remove_all_and_publish_can_map_redis_exception_to_sdl_exception(self):
428 self.mock_redis.execute_command.side_effect = redis_exceptions.ResponseError('redis error!')
429 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
430 self.db.remove_all_and_publish(self.ns, self.channels_and_events)
432 def test_subscribe_channel_success(self):
434 self.db.subscribe_channel(self.ns, cb, self.channels)
435 for channel in self.channels:
436 self.mock_pubsub.subscribe.assert_any_call(**{f'{{some-ns}},{channel}': cb})
438 def test_subscribe_channel_with_thread_success(self):
440 self.db.pubsub_thread.is_alive = Mock()
441 self.db.pubsub_thread.is_alive.return_value = False
442 self.db._run_in_thread = True
443 self.db.subscribe_channel(self.ns, cb, self.channels)
444 self.mock_pubsub.run_in_thread.assert_called_once_with(daemon=True, sleep_time=0.001)
446 def test_subscribe_can_map_redis_exception_to_sdl_exeception(self):
447 self.mock_pubsub.subscribe.side_effect = redis_exceptions.ResponseError('redis error!')
448 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
449 self.db.subscribe_channel(self.ns, Mock(), self.channels)
451 def test_unsubscribe_channel_success(self):
452 self.db.unsubscribe_channel(self.ns, [self.channels[0]])
453 self.mock_pubsub.unsubscribe.assert_called_with('{some-ns},abs')
455 def test_unsubscribe_channel_can_map_redis_exception_to_sdl_exeception(self):
456 self.mock_pubsub.unsubscribe.side_effect = redis_exceptions.ResponseError('redis error!')
457 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
458 self.db.unsubscribe_channel(self.ns, [self.channels[0]])
460 def test_start_event_listener_success(self):
461 self.db.start_event_listener()
462 assert self.db._run_in_thread
464 def test_start_event_listener_subscribe_first(self):
465 self.mock_pubsub.run_in_thread.return_value = Mock()
466 self.mock_redis.pubsub_channels.return_value = [b'{some-ns},abs']
467 self.db.subscribe_channel(self.ns, Mock(), self.channels)
468 self.db.start_event_listener()
469 self.mock_pubsub.run_in_thread.assert_called_once_with(daemon=True, sleep_time=0.001)
471 def test_start_event_listener_fail(self):
472 self.db.pubsub_thread.is_alive = Mock()
473 self.db.pubsub_thread.is_alive.return_value = True
474 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
475 self.db.start_event_listener()
477 def test_handle_events_success(self):
478 self.db.handle_events()
479 self.mock_pubsub.get_message.assert_called_once_with(ignore_subscribe_messages=True)
481 def test_handle_events_fail_already_started(self):
482 self.db.pubsub_thread.is_alive = Mock()
483 self.db.pubsub_thread.is_alive.return_value = True
484 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
485 self.db.handle_events()
487 def test_handle_events_fail_already_set(self):
488 self.db._run_in_thread = True
489 with pytest.raises(ricsdl.exceptions.RejectedByBackend):
490 self.db.handle_events()
492 def test_get_redis_connection_function_success(self):
493 ret = self.db.get_redis_connection()
494 assert ret is self.mock_redis
496 def test_redis_backend_object_string_representation(self):
497 str_out = str(self.db)
498 assert str_out is not None
502 def __init__(self, redis, name, timeout=None, sleep=0.1,
503 blocking=True, blocking_timeout=None, thread_local=True):
506 self.timeout = timeout
508 self.blocking = blocking
509 self.blocking_timeout = blocking_timeout
510 self.thread_local = bool(thread_local)
513 @pytest.fixture(scope="module")
514 def mock_redis_lock():
515 def _mock_redis_lock(name, timeout=None, sleep=0.1,
516 blocking=True, blocking_timeout=None, thread_local=True):
517 return MockRedisLock(name, timeout, sleep, blocking, blocking_timeout, thread_local)
518 return _mock_redis_lock
522 def redis_backend_lock_fixture(request, mock_redis_lock):
523 request.cls.ns = 'some-ns'
524 request.cls.lockname = 'some-lock-name'
525 request.cls.lockname_redis = '{some-ns},some-lock-name'
526 request.cls.expiration = 10
527 request.cls.retry_interval = 0.1
528 request.cls.retry_timeout = 1
530 request.cls.mock_lua_get_validity_time = Mock()
531 request.cls.mock_lua_get_validity_time.return_value = 2000
533 request.cls.mock_redis = Mock()
534 request.cls.mock_redis.register_script = Mock()
535 request.cls.mock_redis.register_script.return_value = request.cls.mock_lua_get_validity_time
537 mocked_dbbackend = Mock()
538 mocked_dbbackend.get_redis_connection.return_value = request.cls.mock_redis
540 request.cls.configuration = Mock()
541 mock_conf_params = _Configuration.Params(db_host=None,
543 db_sentinel_port=None,
544 db_sentinel_master_name=None,
545 db_type=DbBackendType.REDIS)
546 request.cls.configuration.get_params.return_value = mock_conf_params
548 with patch('ricsdl.backend.redis.Lock') as mock_redis_lock:
549 lock = ricsdl.backend.get_backend_lock_instance(request.cls.configuration,
550 request.cls.ns, request.cls.lockname,
551 request.cls.expiration, mocked_dbbackend)
552 request.cls.mock_redis_lock = mock_redis_lock.return_value
553 request.cls.lock = lock
555 RedisBackendLock.lua_get_validity_time = None
558 @pytest.mark.usefixtures('redis_backend_lock_fixture')
559 class TestRedisBackendLock:
560 def test_acquire_function_success(self):
561 self.mock_redis_lock.acquire.return_value = True
562 ret = self.lock.acquire(self.retry_interval, self.retry_timeout)
563 self.mock_redis_lock.acquire.assert_called_once_with(blocking_timeout=self.retry_timeout)
566 def test_acquire_function_returns_false_if_lock_is_not_acquired(self):
567 self.mock_redis_lock.acquire.return_value = False
568 ret = self.lock.acquire(self.retry_interval, self.retry_timeout)
569 self.mock_redis_lock.acquire.assert_called_once_with(blocking_timeout=self.retry_timeout)
572 def test_acquire_function_can_map_redis_exception_to_sdl_exception(self):
573 self.mock_redis_lock.acquire.side_effect = redis_exceptions.LockError('redis lock error!')
574 with pytest.raises(ricsdl.exceptions.BackendError):
575 self.lock.acquire(self.retry_interval, self.retry_timeout)
577 def test_release_function_success(self):
579 self.mock_redis_lock.release.assert_called_once()
581 def test_release_function_can_map_redis_exception_to_sdl_exception(self):
582 self.mock_redis_lock.release.side_effect = redis_exceptions.LockError('redis lock error!')
583 with pytest.raises(ricsdl.exceptions.BackendError):
586 def test_refresh_function_success(self):
588 self.mock_redis_lock.reacquire.assert_called_once()
590 def test_refresh_function_can_map_redis_exception_to_sdl_exception(self):
591 self.mock_redis_lock.reacquire.side_effect = redis_exceptions.LockError('redis lock error!')
592 with pytest.raises(ricsdl.exceptions.BackendError):
595 def test_get_validity_time_function_success(self):
596 self.mock_redis_lock.name = self.lockname_redis
597 self.mock_redis_lock.local.token = 123
599 ret = self.lock.get_validity_time()
600 self.mock_lua_get_validity_time.assert_called_once_with(
601 keys=[self.lockname_redis], args=[123], client=self.mock_redis)
604 def test_get_validity_time_function_second_fraction_success(self):
605 self.mock_redis_lock.name = self.lockname_redis
606 self.mock_redis_lock.local.token = 123
607 self.mock_lua_get_validity_time.return_value = 234
609 ret = self.lock.get_validity_time()
610 self.mock_lua_get_validity_time.assert_called_once_with(
611 keys=[self.lockname_redis], args=[123], client=self.mock_redis)
614 def test_get_validity_time_function_can_raise_exception_if_lock_is_unlocked(self):
615 self.mock_redis_lock.name = self.lockname_redis
616 self.mock_redis_lock.local.token = None
618 with pytest.raises(ricsdl.exceptions.RejectedByBackend) as excinfo:
619 self.lock.get_validity_time()
620 assert f"Cannot get validity time of an unlocked lock {self.lockname}" in str(excinfo.value)
622 def test_get_validity_time_function_can_raise_exception_if_lua_script_fails(self):
623 self.mock_redis_lock.name = self.lockname_redis
624 self.mock_redis_lock.local.token = 123
625 self.mock_lua_get_validity_time.return_value = -10
627 with pytest.raises(ricsdl.exceptions.RejectedByBackend) as excinfo:
628 self.lock.get_validity_time()
629 assert f"Getting validity time of a lock {self.lockname} failed with error code: -10" in str(excinfo.value)
631 def test_redis_backend_lock_object_string_representation(self):
632 expected_lock_info = {'lock DB type': 'Redis',
633 'lock namespace': 'some-ns',
634 'lock name': 'some-lock-name',
635 'lock status': 'locked'}
636 assert str(self.lock) == str(expected_lock_info)
638 def test_redis_backend_lock_object_string_representation_can_catch_redis_exception(self):
639 self.mock_redis_lock.owned.side_effect = redis_exceptions.LockError('redis lock error!')
640 expected_lock_info = {'lock DB type': 'Redis',
641 'lock namespace': 'some-ns',
642 'lock name': 'some-lock-name',
643 'lock status': 'Error: redis lock error!'}
644 assert str(self.lock) == str(expected_lock_info)
647 def test_redis_response_error_exception_is_mapped_to_rejected_by_backend_sdl_exception():
648 with pytest.raises(ricsdl.exceptions.RejectedByBackend) as excinfo:
649 with _map_to_sdl_exception():
650 raise redis_exceptions.ResponseError('Some redis error!')
651 assert "SDL backend rejected the request: Some redis error!" in str(excinfo.value)
654 def test_redis_connection_error_exception_is_mapped_to_not_connected_sdl_exception():
655 with pytest.raises(ricsdl.exceptions.NotConnected) as excinfo:
656 with _map_to_sdl_exception():
657 raise redis_exceptions.ConnectionError('Some redis error!')
658 assert "SDL not connected to backend: Some redis error!" in str(excinfo.value)
661 def test_rest_redis_exceptions_are_mapped_to_backend_error_sdl_exception():
662 with pytest.raises(ricsdl.exceptions.BackendError) as excinfo:
663 with _map_to_sdl_exception():
664 raise redis_exceptions.RedisError('Some redis error!')
665 assert "SDL backend failed to process the request: Some redis error!" in str(excinfo.value)
668 def test_system_error_exceptions_are_not_mapped_to_any_sdl_exception():
669 with pytest.raises(SystemExit):
670 with _map_to_sdl_exception():
671 raise SystemExit('Fatal error')
674 class TestRedisClient:
676 def setup_class(cls):
677 cls.pubsub = ricsdl.backend.redis.PubSub(Mock())
678 cls.pubsub.channels = {b'{some-ns},abs': Mock()}
680 def test_handle_pubsub_message(self):
681 assert self.pubsub.handle_message([b'message', b'{some-ns},abs', b'cbn']) == ('abs', 'cbn')
682 self.pubsub.channels.get(b'{some-ns},abs').assert_called_once_with('abs', 'cbn')