|
28 | 28 | from opentelemetry.test.test_base import TestBase |
29 | 29 | from opentelemetry.trace import SpanKind |
30 | 30 |
|
| 31 | +default_cluster_slots = [ |
| 32 | + [0, 8191, ["1.1.1.1", 6380, "node_0"], ["1.1.1.1", 6383, "node_3"]], |
| 33 | + [8192, 16383, ["1.1.1.1", 6381, "node_1"], ["1.1.1.1", 6382, "node_2"]], |
| 34 | +] |
| 35 | + |
| 36 | + |
| 37 | +def get_mocked_redis_cluster_client( |
| 38 | + func=None, cluster_slots_raise_error=False, *args, **kwargs |
| 39 | +): |
| 40 | + """ |
| 41 | + Return a stable RedisCluster object that have deterministic |
| 42 | + nodes and slots setup to remove the problem of different IP addresses |
| 43 | + on different installations and machines. |
| 44 | + """ |
| 45 | + cluster_slots = kwargs.pop("cluster_slots", default_cluster_slots) |
| 46 | + coverage_res = kwargs.pop("coverage_result", "yes") |
| 47 | + cluster_enabled = kwargs.pop("cluster_enabled", True) |
| 48 | + with mock.patch.object( |
| 49 | + redis.Redis, "execute_command" |
| 50 | + ) as execute_command_mock: |
| 51 | + |
| 52 | + def execute_command(*_args, **_kwargs): |
| 53 | + if _args[0] == "CLUSTER SLOTS": |
| 54 | + if cluster_slots_raise_error: |
| 55 | + raise redis.exceptions.ResponseError() |
| 56 | + else: |
| 57 | + mock_cluster_slots = cluster_slots |
| 58 | + return mock_cluster_slots |
| 59 | + elif _args[0] == "COMMAND": |
| 60 | + return {"get": [], "set": []} |
| 61 | + elif _args[0] == "INFO": |
| 62 | + return {"cluster_enabled": cluster_enabled} |
| 63 | + elif ( |
| 64 | + len(_args) > 1 and _args[1] == "cluster-require-full-coverage" |
| 65 | + ): |
| 66 | + return {"cluster-require-full-coverage": coverage_res} |
| 67 | + elif func is not None: |
| 68 | + return func(*args, **kwargs) |
| 69 | + else: |
| 70 | + return execute_command_mock(*_args, **_kwargs) |
| 71 | + |
| 72 | + execute_command_mock.side_effect = execute_command |
| 73 | + |
| 74 | + with mock.patch.object( |
| 75 | + redis._parsers.CommandsParser, "initialize", autospec=True |
| 76 | + ) as cmd_parser_initialize: |
| 77 | + |
| 78 | + def cmd_init_mock(self, r): |
| 79 | + self.commands = { |
| 80 | + "get": { |
| 81 | + "name": "get", |
| 82 | + "arity": 2, |
| 83 | + "flags": ["readonly", "fast"], |
| 84 | + "first_key_pos": 1, |
| 85 | + "last_key_pos": 1, |
| 86 | + "step_count": 1, |
| 87 | + } |
| 88 | + } |
| 89 | + |
| 90 | + cmd_parser_initialize.side_effect = cmd_init_mock |
| 91 | + |
| 92 | + return redis.RedisCluster(*args, **kwargs) |
| 93 | + |
31 | 94 |
|
32 | 95 | class TestRedis(TestBase): |
33 | 96 | def setUp(self): |
@@ -311,3 +374,65 @@ def test_attributes_unix_socket(self): |
311 | 374 | span.attributes[SpanAttributes.NET_TRANSPORT], |
312 | 375 | NetTransportValues.OTHER.value, |
313 | 376 | ) |
| 377 | + |
| 378 | + def test_attributes_redis_cluster(self): |
| 379 | + with mock.patch.object(redis.RedisCluster, "from_url") as from_url: |
| 380 | + |
| 381 | + def from_url_mocked(_url, **_kwargs): |
| 382 | + return get_mocked_redis_cluster_client(url=_url, **_kwargs) |
| 383 | + |
| 384 | + from_url.side_effect = from_url_mocked |
| 385 | + redis_client = redis.RedisCluster.from_url( |
| 386 | + "redis://foo:bar@1.1.1.1:6380/0" |
| 387 | + ) |
| 388 | + |
| 389 | + with mock.patch.object( |
| 390 | + redis._parsers.CommandsParser, "initialize", autospec=True |
| 391 | + ) as cmd_parser_initialize: |
| 392 | + |
| 393 | + def cmd_init_mock(self, r): |
| 394 | + self.commands = { |
| 395 | + "get": { |
| 396 | + "name": "get", |
| 397 | + "arity": 2, |
| 398 | + "flags": ["readonly", "fast"], |
| 399 | + "first_key_pos": 1, |
| 400 | + "last_key_pos": 1, |
| 401 | + "step_count": 1, |
| 402 | + }, |
| 403 | + "set": { |
| 404 | + "name": "set", |
| 405 | + "arity": -3, |
| 406 | + "flags": ["write", "denyoom"], |
| 407 | + "first_key_pos": 1, |
| 408 | + "last_key_pos": 1, |
| 409 | + "step_count": 1, |
| 410 | + }, |
| 411 | + } |
| 412 | + |
| 413 | + cmd_parser_initialize.side_effect = cmd_init_mock |
| 414 | + with mock.patch.object( |
| 415 | + redis.connection.ConnectionPool, "get_connection" |
| 416 | + ) as get_connection: |
| 417 | + get_connection.return_value = mock.MagicMock() |
| 418 | + redis_client.set("key", "value") |
| 419 | + |
| 420 | + spans = self.memory_exporter.get_finished_spans() |
| 421 | + self.assertEqual(len(spans), 1) |
| 422 | + |
| 423 | + span = spans[0] |
| 424 | + self.assertEqual( |
| 425 | + span.attributes[SpanAttributes.DB_SYSTEM], |
| 426 | + DbSystemValues.REDIS.value, |
| 427 | + ) |
| 428 | + self.assertEqual( |
| 429 | + span.attributes[SpanAttributes.DB_REDIS_DATABASE_INDEX], 0 |
| 430 | + ) |
| 431 | + self.assertEqual( |
| 432 | + span.attributes[SpanAttributes.NET_PEER_NAME], "1.1.1.1" |
| 433 | + ) |
| 434 | + self.assertEqual(span.attributes[SpanAttributes.NET_PEER_PORT], 6380) |
| 435 | + self.assertEqual( |
| 436 | + span.attributes[SpanAttributes.NET_TRANSPORT], |
| 437 | + NetTransportValues.IP_TCP.value, |
| 438 | + ) |
0 commit comments