Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions redis/asyncio/multidb/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
from redis.multidb.circuit import State as CBState
from redis.multidb.exception import NoValidDatabaseException, UnhealthyDatabaseException
from redis.typing import ChannelT, EncodableT, KeyT
from redis.utils import experimental

logger = logging.getLogger(__name__)


@experimental
class MultiDBClient(AsyncRedisModuleCommands, AsyncCoreCommands):
"""
Client that operates on multiple logical Redis databases.
Expand Down
4 changes: 2 additions & 2 deletions redis/asyncio/multidb/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
DEFAULT_HEALTH_CHECK_INTERVAL,
DEFAULT_HEALTH_CHECK_POLICY,
DEFAULT_HEALTH_CHECK_PROBES,
EchoHealthCheck,
HealthCheck,
HealthCheckPolicies,
PingHealthCheck,
)
from redis.asyncio.retry import Retry
from redis.backoff import ExponentialWithJitterBackoff, NoBackoff
Expand Down Expand Up @@ -203,7 +203,7 @@ def default_failure_detectors(self) -> List[AsyncFailureDetector]:

def default_health_checks(self) -> List[HealthCheck]:
return [
EchoHealthCheck(),
PingHealthCheck(),
]

def default_failover_strategy(self) -> AsyncFailoverStrategy:
Expand Down
15 changes: 4 additions & 11 deletions redis/asyncio/multidb/healthcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,26 +170,19 @@ class HealthCheckPolicies(Enum):
DEFAULT_HEALTH_CHECK_POLICY: HealthCheckPolicies = HealthCheckPolicies.HEALTHY_ALL


class EchoHealthCheck(HealthCheck):
class PingHealthCheck(HealthCheck):
"""
Health check based on ECHO command.
Health check based on PING command.
"""

async def check_health(self, database) -> bool:
expected_message = ["healthcheck", b"healthcheck"]

if isinstance(database.client, Redis):
actual_message = await database.client.execute_command(
"ECHO", "healthcheck"
)
return actual_message in expected_message
return await database.client.execute_command("PING")
else:
# For a cluster checks if all nodes are healthy.
all_nodes = database.client.get_nodes()
for node in all_nodes:
actual_message = await node.execute_command("ECHO", "healthcheck")

if actual_message not in expected_message:
if not await node.redis_connection.execute_command("PING"):
return False

return True
Expand Down
2 changes: 2 additions & 0 deletions redis/multidb/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
from redis.multidb.exception import NoValidDatabaseException, UnhealthyDatabaseException
from redis.multidb.failure_detector import FailureDetector
from redis.multidb.healthcheck import HealthCheck, HealthCheckPolicy
from redis.utils import experimental

logger = logging.getLogger(__name__)


@experimental
class MultiDBClient(RedisModuleCommands, CoreCommands):
"""
Client that operates on multiple logical Redis databases.
Expand Down
4 changes: 2 additions & 2 deletions redis/multidb/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@
DEFAULT_HEALTH_CHECK_INTERVAL,
DEFAULT_HEALTH_CHECK_POLICY,
DEFAULT_HEALTH_CHECK_PROBES,
EchoHealthCheck,
HealthCheck,
HealthCheckPolicies,
PingHealthCheck,
)
from redis.retry import Retry

Expand Down Expand Up @@ -200,7 +200,7 @@ def default_failure_detectors(self) -> List[FailureDetector]:

def default_health_checks(self) -> List[HealthCheck]:
return [
EchoHealthCheck(),
PingHealthCheck(),
]

def default_failover_strategy(self) -> FailoverStrategy:
Expand Down
15 changes: 4 additions & 11 deletions redis/multidb/healthcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,26 +169,19 @@ class HealthCheckPolicies(Enum):
DEFAULT_HEALTH_CHECK_POLICY: HealthCheckPolicies = HealthCheckPolicies.HEALTHY_ALL


class EchoHealthCheck(HealthCheck):
class PingHealthCheck(HealthCheck):
"""
Health check based on ECHO command.
Health check based on PING command.
"""

def check_health(self, database) -> bool:
expected_message = ["healthcheck", b"healthcheck"]

if isinstance(database.client, Redis):
actual_message = database.client.execute_command("ECHO", "healthcheck")
return actual_message in expected_message
return database.client.execute_command("PING")
else:
# For a cluster checks if all nodes are healthy.
all_nodes = database.client.get_nodes()
for node in all_nodes:
actual_message = node.redis_connection.execute_command(
"ECHO", "healthcheck"
)

if actual_message not in expected_message:
if not node.redis_connection.execute_command("PING"):
return False

return True
Expand Down
20 changes: 20 additions & 0 deletions redis/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import datetime
import logging
import textwrap
import warnings
from collections.abc import Callable
from contextlib import contextmanager
from functools import wraps
Expand Down Expand Up @@ -326,3 +327,22 @@ async def dummy_fail_async():
Async fake function for a Retry object if you don't need to handle each failure.
"""
pass


def experimental(cls):
"""
Decorator to mark a class as experimental.
"""
original_init = cls.__init__

@wraps(original_init)
def new_init(self, *args, **kwargs):
warnings.warn(
f"{cls.__name__} is an experimental and may change or be removed in future versions.",
category=UserWarning,
stacklevel=2,
)
original_init(self, *args, **kwargs)

cls.__init__ = new_init
return cls
4 changes: 2 additions & 2 deletions tests/test_asyncio/test_multidb/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
FailureDetectorAsyncWrapper,
AsyncFailureDetector,
)
from redis.asyncio.multidb.healthcheck import EchoHealthCheck, HealthCheck
from redis.asyncio.multidb.healthcheck import PingHealthCheck, HealthCheck
from redis.asyncio.retry import Retry
from redis.multidb.circuit import CircuitBreaker

Expand Down Expand Up @@ -58,7 +58,7 @@ def test_default_config(self):
config.default_failure_detectors()[0], FailureDetectorAsyncWrapper
)
assert len(config.default_health_checks()) == 1
assert isinstance(config.default_health_checks()[0], EchoHealthCheck)
assert isinstance(config.default_health_checks()[0], PingHealthCheck)
assert config.health_check_interval == DEFAULT_HEALTH_CHECK_INTERVAL
assert isinstance(
config.default_failover_strategy(), WeightBasedFailoverStrategy
Expand Down
16 changes: 8 additions & 8 deletions tests/test_asyncio/test_multidb/test_healthcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from redis.asyncio.multidb.database import Database
from redis.asyncio.multidb.healthcheck import (
EchoHealthCheck,
PingHealthCheck,
LagAwareHealthCheck,
HealthCheck,
HealthyAllPolicy,
Expand Down Expand Up @@ -208,15 +208,15 @@ async def test_policy_raise_unhealthy_database_exception_if_exception_occurs_on_


@pytest.mark.onlynoncluster
class TestEchoHealthCheck:
class TestPingHealthCheck:
@pytest.mark.asyncio
async def test_database_is_healthy_on_echo_response(self, mock_client, mock_cb):
"""
Mocking responses to mix error and actual responses to ensure that health check retry
according to given configuration.
"""
mock_client.execute_command = AsyncMock(side_effect=["healthcheck"])
hc = EchoHealthCheck()
mock_client.execute_command = AsyncMock(side_effect=["PONG"])
hc = PingHealthCheck()
db = Database(mock_client, mock_cb, 0.9)

assert await hc.check_health(db)
Expand All @@ -230,8 +230,8 @@ async def test_database_is_unhealthy_on_incorrect_echo_response(
Mocking responses to mix error and actual responses to ensure that health check retry
according to given configuration.
"""
mock_client.execute_command = AsyncMock(side_effect=["wrong"])
hc = EchoHealthCheck()
mock_client.execute_command = AsyncMock(side_effect=[False])
hc = PingHealthCheck()
db = Database(mock_client, mock_cb, 0.9)

assert not await hc.check_health(db)
Expand All @@ -241,9 +241,9 @@ async def test_database_is_unhealthy_on_incorrect_echo_response(
async def test_database_close_circuit_on_successful_healthcheck(
self, mock_client, mock_cb
):
mock_client.execute_command = AsyncMock(side_effect=["healthcheck"])
mock_client.execute_command = AsyncMock(side_effect=["PONG"])
mock_cb.state = CBState.HALF_OPEN
hc = EchoHealthCheck()
hc = PingHealthCheck()
db = Database(mock_client, mock_cb, 0.9)

assert await hc.check_health(db)
Expand Down
4 changes: 2 additions & 2 deletions tests/test_multidb/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
)
from redis.multidb.database import Database
from redis.multidb.failure_detector import CommandFailureDetector, FailureDetector
from redis.multidb.healthcheck import EchoHealthCheck, HealthCheck
from redis.multidb.healthcheck import PingHealthCheck, HealthCheck
from redis.multidb.failover import WeightBasedFailoverStrategy, FailoverStrategy
from redis.retry import Retry

Expand Down Expand Up @@ -53,7 +53,7 @@ def test_default_config(self):
assert len(config.default_failure_detectors()) == 1
assert isinstance(config.default_failure_detectors()[0], CommandFailureDetector)
assert len(config.default_health_checks()) == 1
assert isinstance(config.default_health_checks()[0], EchoHealthCheck)
assert isinstance(config.default_health_checks()[0], PingHealthCheck)
assert config.health_check_interval == DEFAULT_HEALTH_CHECK_INTERVAL
assert isinstance(
config.default_failover_strategy(), WeightBasedFailoverStrategy
Expand Down
16 changes: 8 additions & 8 deletions tests/test_multidb/test_healthcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from redis.multidb.database import Database
from redis.http.http_client import HttpError
from redis.multidb.healthcheck import (
EchoHealthCheck,
PingHealthCheck,
LagAwareHealthCheck,
HealthCheck,
HealthyAllPolicy,
Expand Down Expand Up @@ -201,14 +201,14 @@ def test_policy_raise_unhealthy_database_exception_if_exception_occurs_on_failed


@pytest.mark.onlynoncluster
class TestEchoHealthCheck:
class TestPingHealthCheck:
def test_database_is_healthy_on_echo_response(self, mock_client, mock_cb):
"""
Mocking responses to mix error and actual responses to ensure that health check retry
according to given configuration.
"""
mock_client.execute_command.return_value = "healthcheck"
hc = EchoHealthCheck()
mock_client.execute_command.return_value = "PONG"
hc = PingHealthCheck()
db = Database(mock_client, mock_cb, 0.9)

assert hc.check_health(db)
Expand All @@ -221,8 +221,8 @@ def test_database_is_unhealthy_on_incorrect_echo_response(
Mocking responses to mix error and actual responses to ensure that health check retry
according to given configuration.
"""
mock_client.execute_command.return_value = "wrong"
hc = EchoHealthCheck()
mock_client.execute_command.return_value = False
hc = PingHealthCheck()
db = Database(mock_client, mock_cb, 0.9)

assert not hc.check_health(db)
Expand All @@ -231,9 +231,9 @@ def test_database_is_unhealthy_on_incorrect_echo_response(
def test_database_close_circuit_on_successful_healthcheck(
self, mock_client, mock_cb
):
mock_client.execute_command.return_value = "healthcheck"
mock_client.execute_command.return_value = "PONG"
mock_cb.state = CBState.HALF_OPEN
hc = EchoHealthCheck()
hc = PingHealthCheck()
db = Database(mock_client, mock_cb, 0.9)

assert hc.check_health(db)
Expand Down