|  | 
|  | 1 | +import json | 
|  | 2 | +import logging | 
|  | 3 | +import os | 
|  | 4 | +from typing import Optional | 
|  | 5 | +from urllib.parse import urlparse | 
|  | 6 | +import pytest | 
|  | 7 | + | 
|  | 8 | +from redis.backoff import ExponentialWithJitterBackoff, NoBackoff | 
|  | 9 | +from redis.client import Redis | 
|  | 10 | +from redis.maintenance_events import EndpointType, MaintenanceEventsConfig | 
|  | 11 | +from redis.retry import Retry | 
|  | 12 | +from tests.test_scenario.fault_injector_client import FaultInjectorClient | 
|  | 13 | + | 
|  | 14 | +RELAX_TIMEOUT = 30 | 
|  | 15 | +CLIENT_TIMEOUT = 5 | 
|  | 16 | + | 
|  | 17 | +DEFAULT_ENDPOINT_NAME = "m-standard" | 
|  | 18 | + | 
|  | 19 | + | 
|  | 20 | +@pytest.fixture() | 
|  | 21 | +def endpoint_name(request): | 
|  | 22 | +    return request.config.getoption("--endpoint-name") or os.getenv( | 
|  | 23 | +        "REDIS_ENDPOINT_NAME", DEFAULT_ENDPOINT_NAME | 
|  | 24 | +    ) | 
|  | 25 | + | 
|  | 26 | + | 
|  | 27 | +@pytest.fixture() | 
|  | 28 | +def endpoints_config(endpoint_name: str): | 
|  | 29 | +    endpoints_config = os.getenv("REDIS_ENDPOINTS_CONFIG_PATH", None) | 
|  | 30 | + | 
|  | 31 | +    if not (endpoints_config and os.path.exists(endpoints_config)): | 
|  | 32 | +        raise FileNotFoundError(f"Endpoints config file not found: {endpoints_config}") | 
|  | 33 | + | 
|  | 34 | +    try: | 
|  | 35 | +        with open(endpoints_config, "r") as f: | 
|  | 36 | +            data = json.load(f) | 
|  | 37 | +            db = data[endpoint_name] | 
|  | 38 | +            return db | 
|  | 39 | +    except Exception as e: | 
|  | 40 | +        raise ValueError( | 
|  | 41 | +            f"Failed to load endpoints config file: {endpoints_config}" | 
|  | 42 | +        ) from e | 
|  | 43 | + | 
|  | 44 | + | 
|  | 45 | +@pytest.fixture() | 
|  | 46 | +def fault_injector_client(): | 
|  | 47 | +    url = os.getenv("FAULT_INJECTION_API_URL", "http://127.0.0.1:20324") | 
|  | 48 | +    return FaultInjectorClient(url) | 
|  | 49 | + | 
|  | 50 | + | 
|  | 51 | +@pytest.fixture() | 
|  | 52 | +def client_maint_events(endpoints_config): | 
|  | 53 | +    return _get_client_maint_events(endpoints_config) | 
|  | 54 | + | 
|  | 55 | + | 
|  | 56 | +def _get_client_maint_events( | 
|  | 57 | +    endpoints_config, | 
|  | 58 | +    enable_maintenance_events: bool = True, | 
|  | 59 | +    endpoint_type: Optional[EndpointType] = None, | 
|  | 60 | +    enable_relax_timeout: bool = True, | 
|  | 61 | +    enable_proactive_reconnect: bool = True, | 
|  | 62 | +    disable_retries: bool = False, | 
|  | 63 | +    socket_timeout: Optional[float] = None, | 
|  | 64 | +): | 
|  | 65 | +    """Create Redis client with maintenance events enabled.""" | 
|  | 66 | + | 
|  | 67 | +    # Get credentials from the configuration | 
|  | 68 | +    username = endpoints_config.get("username") | 
|  | 69 | +    password = endpoints_config.get("password") | 
|  | 70 | + | 
|  | 71 | +    # Parse host and port from endpoints URL | 
|  | 72 | +    endpoints = endpoints_config.get("endpoints", []) | 
|  | 73 | +    if not endpoints: | 
|  | 74 | +        raise ValueError("No endpoints found in configuration") | 
|  | 75 | + | 
|  | 76 | +    parsed = urlparse(endpoints[0]) | 
|  | 77 | +    host = parsed.hostname | 
|  | 78 | +    port = parsed.port | 
|  | 79 | + | 
|  | 80 | +    tls_enabled = True if parsed.scheme == "rediss" else False | 
|  | 81 | + | 
|  | 82 | +    if not host: | 
|  | 83 | +        raise ValueError(f"Could not parse host from endpoint URL: {endpoints[0]}") | 
|  | 84 | + | 
|  | 85 | +    logging.info(f"Connecting to Redis Enterprise: {host}:{port} with user: {username}") | 
|  | 86 | + | 
|  | 87 | +    # Configure maintenance events | 
|  | 88 | +    maintenance_config = MaintenanceEventsConfig( | 
|  | 89 | +        enabled=enable_maintenance_events, | 
|  | 90 | +        proactive_reconnect=enable_proactive_reconnect, | 
|  | 91 | +        relax_timeout=RELAX_TIMEOUT if enable_relax_timeout else -1, | 
|  | 92 | +        endpoint_type=endpoint_type, | 
|  | 93 | +    ) | 
|  | 94 | + | 
|  | 95 | +    # Create Redis client with maintenance events config | 
|  | 96 | +    # This will automatically create the MaintenanceEventPoolHandler | 
|  | 97 | +    if disable_retries: | 
|  | 98 | +        retry = Retry(NoBackoff(), 0) | 
|  | 99 | +    else: | 
|  | 100 | +        retry = Retry(backoff=ExponentialWithJitterBackoff(base=1, cap=10), retries=3) | 
|  | 101 | + | 
|  | 102 | +    client = Redis( | 
|  | 103 | +        host=host, | 
|  | 104 | +        port=port, | 
|  | 105 | +        socket_timeout=CLIENT_TIMEOUT if socket_timeout is None else socket_timeout, | 
|  | 106 | +        username=username, | 
|  | 107 | +        password=password, | 
|  | 108 | +        ssl=tls_enabled, | 
|  | 109 | +        ssl_cert_reqs="none", | 
|  | 110 | +        ssl_check_hostname=False, | 
|  | 111 | +        protocol=3,  # RESP3 required for push notifications | 
|  | 112 | +        maintenance_events_config=maintenance_config, | 
|  | 113 | +        retry=retry, | 
|  | 114 | +    ) | 
|  | 115 | +    logging.info("Redis client created with maintenance events enabled") | 
|  | 116 | +    logging.info(f"Client uses Protocol: {client.connection_pool.get_protocol()}") | 
|  | 117 | +    maintenance_handler_exists = client.maintenance_events_pool_handler is not None | 
|  | 118 | +    logging.info(f"Maintenance events pool handler: {maintenance_handler_exists}") | 
|  | 119 | + | 
|  | 120 | +    return client | 
0 commit comments