Skip to content

Commit 78c770a

Browse files
Adds relaxed_timeouts for maint notifications
Signed-off-by: Elena Kolevska <elena@kolevska.com>
1 parent 00f3c37 commit 78c770a

File tree

6 files changed

+171
-100
lines changed

6 files changed

+171
-100
lines changed

.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ REDIS_SSL_CERT_REQS=required
1010
REDIS_SSL_CA_CERTS=
1111
REDIS_SSL_CERTFILE=
1212
REDIS_SSL_KEYFILE=
13+
REDIS_RELAXED_TIMEOUT=
1314
REDIS_SOCKET_TIMEOUT=5.0
1415
REDIS_SOCKET_CONNECT_TIMEOUT=5.0
1516
REDIS_MAX_CONNECTIONS=50
1617
REDIS_CLIENT_RETRY_ATTEMPTS=3
17-
REDIS_MAINT_EVENTS_ENABLED=true
18+
REDIS_MAINT_NOTIFICATIONS_ENABLED=true
1819
REDIS_PROTOCOL=3
1920

2021
# Test Configuration

Makefile

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# Redis Python Test App - Makefile
2-
.PHONY: help install-deps test test-connection build clean
2+
.PHONY: help install-python39 install-deps-venv test test-connection build clean
33

44
# Default target
55
help:
66
@echo "Redis Python Test App - Available Commands:"
77
@echo ""
88
@echo "🚀 Development Commands:"
9-
@echo " make install-deps - Install Python dependencies"
9+
@echo " make install-python39 - Install Python 3.9 on Ubuntu/Debian systems"
10+
@echo " make install-deps-venv - Create virtual environment and install dependencies"
1011
@echo " make test - Run basic test (60 seconds)"
1112
@echo " make test-connection - Test Redis connection"
1213
@echo ""
@@ -17,6 +18,9 @@ help:
1718
@echo " make clean - Clean up Python cache and virtual environment"
1819
@echo ""
1920
@echo "📋 Prerequisites:"
21+
@echo " • Python 3.9+ installed (required for redis==7.0.0b1)"
22+
@echo " • Run 'make install-python39' to install Python 3.9 on Ubuntu/Debian"
23+
@echo " • Run 'make install-deps-venv' to set up virtual environment"
2024
@echo " • Redis Metrics Stack must be running (separate repository)"
2125
@echo " • Redis accessible at localhost:6379"
2226
@echo " • OpenTelemetry Collector at localhost:4317"
@@ -29,20 +33,70 @@ IMAGE_TAG ?= latest
2933
# Development Commands
3034
#==============================================================================
3135

32-
install-deps: ## Install Python dependencies
33-
@echo "📦 Installing Python dependencies..."
34-
python3 -m pip install --upgrade pip
35-
python3 -m pip install -r requirements.txt
36-
@echo "✅ Dependencies installed"
36+
install-python39: ## Install Python 3.9 on Ubuntu/Debian systems
37+
@echo "🐍 Installing Python 3.9..."
38+
@if command -v python3.9 >/dev/null 2>&1; then \
39+
echo "✓ Python 3.9 already installed"; \
40+
python3.9 --version; \
41+
else \
42+
echo "📦 Installing Python 3.9 and required packages..."; \
43+
sudo apt update; \
44+
sudo apt install -y python3.9 python3.9-venv python3.9-dev python3.9-distutils; \
45+
echo "✅ Python 3.9 installation complete"; \
46+
python3.9 --version; \
47+
fi
48+
49+
install-deps-venv: ## Create virtual environment and install dependencies
50+
@echo "📦 Setting up Python virtual environment..."
51+
@if [ ! -d "venv" ]; then \
52+
echo "🔧 Creating virtual environment with Python 3.9..."; \
53+
if command -v python3.9 >/dev/null 2>&1; then \
54+
python3.9 -m venv venv; \
55+
else \
56+
echo "❌ Python 3.9 not found. Run 'make install-python39' first."; \
57+
exit 1; \
58+
fi; \
59+
else \
60+
echo "✓ Virtual environment already exists"; \
61+
fi
62+
@echo "📦 Ensuring pip is available in virtual environment..."
63+
@if [ ! -f "./venv/bin/pip" ]; then \
64+
echo "🔧 Pip not found, bootstrapping pip in virtual environment..."; \
65+
./venv/bin/python -m ensurepip --upgrade || { \
66+
echo "❌ Failed to bootstrap pip. Recreating virtual environment..."; \
67+
rm -rf venv; \
68+
python3 -m venv venv --system-site-packages; \
69+
./venv/bin/python -m ensurepip --upgrade; \
70+
}; \
71+
fi
72+
@echo "📦 Installing dependencies in virtual environment..."
73+
@echo "🔧 Clearing pip cache (if supported)..."
74+
@./venv/bin/python -m pip cache purge 2>/dev/null || echo "ℹ️ Cache purge not supported, continuing..."
75+
./venv/bin/python -m pip install --no-cache-dir --index-url https://pypi.org/simple/ --upgrade pip
76+
./venv/bin/python -m pip install --no-cache-dir --index-url https://pypi.org/simple/ -r requirements.txt
77+
@echo "✅ Virtual environment setup complete"
78+
@echo ""
79+
@echo "💡 To activate the virtual environment, run:"
80+
@echo " source venv/bin/activate"
3781

3882
test-connection: ## Test Redis connection
3983
@echo "🔍 Testing Redis connection..."
40-
python3 main.py test-connection
84+
@if [ -d "venv" ]; then \
85+
./venv/bin/python main.py test-connection; \
86+
else \
87+
echo "❌ Virtual environment not found. Run 'make install-deps-venv' first."; \
88+
exit 1; \
89+
fi
4190
@echo "✅ Connection test complete"
4291

4392
test: ## Run basic test (60 seconds)
4493
@echo "🧪 Running basic test..."
45-
python3 main.py run --workload-profile basic_rw --duration 60
94+
@if [ -d "venv" ]; then \
95+
./venv/bin/python main.py run --workload-profile basic_rw --duration 60; \
96+
else \
97+
echo "❌ Virtual environment not found. Run 'make install-deps-venv' first."; \
98+
exit 1; \
99+
fi
46100
@echo "✅ Test complete"
47101
#==============================================================================
48102
# Build Commands

cli.py

Lines changed: 82 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,74 @@ def cli():
4040
pass
4141

4242

43+
@cli.command()
44+
def list_profiles():
45+
"""List available workload profiles."""
46+
profiles = WorkloadProfiles.list_profiles()
47+
click.echo("Available workload profiles:")
48+
for profile in profiles:
49+
workload = WorkloadProfiles.get_profile(profile)
50+
operations = workload.get_option("operations", [workload.type])
51+
if isinstance(operations, list):
52+
ops_str = ', '.join(operations)
53+
else:
54+
ops_str = workload.type
55+
click.echo(f" {profile}: {ops_str}")
56+
57+
58+
@cli.command()
59+
@click.argument('profile_name', type=click.Choice(WorkloadProfiles.list_profiles()))
60+
def describe_profile(profile_name):
61+
"""Describe a specific workload profile."""
62+
workload = WorkloadProfiles.get_profile(profile_name)
63+
64+
click.echo(f"Workload Profile: {profile_name}")
65+
click.echo(f"Type: {workload.type}")
66+
click.echo(f"Duration: {workload.max_duration}")
67+
68+
# Show operations if available
69+
operations = workload.get_option("operations")
70+
if operations:
71+
click.echo(f"Operations: {', '.join(operations)}")
72+
73+
# Show operation weights if available
74+
weights = workload.get_option("operation_weights")
75+
if weights:
76+
click.echo("Operation Weights:")
77+
for op, weight in weights.items():
78+
click.echo(f" {op}: {weight}")
79+
80+
# Show key configuration options
81+
value_size = workload.get_option("valueSize")
82+
if value_size:
83+
click.echo(f"Value Size: {value_size} bytes")
84+
85+
iteration_count = workload.get_option("iterationCount")
86+
if iteration_count:
87+
click.echo(f"Iteration Count: {iteration_count}")
88+
89+
# Show pipeline configuration
90+
use_pipeline = workload.get_option("usePipeline", False)
91+
click.echo(f"Pipeline: {'Yes' if use_pipeline else 'No'}")
92+
if use_pipeline:
93+
pipeline_size = workload.get_option("pipelineSize", 10)
94+
click.echo(f"Pipeline Size: {pipeline_size}")
95+
96+
# Show async configuration
97+
async_mode = workload.get_option("asyncMode", False)
98+
click.echo(f"Async Mode: {'Yes' if async_mode else 'No'}")
99+
100+
# Show channels if available
101+
channels = workload.get_option("channels")
102+
if channels:
103+
click.echo(f"Channels: {', '.join(channels)}")
104+
105+
# Show all other options
106+
click.echo("All Options:")
107+
for key, value in workload.options.items():
108+
click.echo(f" {key}: {value}")
109+
110+
43111
@cli.command()
44112
# ============================================================================
45113
# Redis Connection Parameters
@@ -55,11 +123,12 @@ def cli():
55123
@click.option('--ssl-ca-certs', default=lambda: get_env_or_default('REDIS_SSL_CA_CERTS', None), help='Path to CA certificates file')
56124
@click.option('--ssl-certfile', default=lambda: get_env_or_default('REDIS_SSL_CERTFILE', None), help='Path to client certificate file')
57125
@click.option('--ssl-keyfile', default=lambda: get_env_or_default('REDIS_SSL_KEYFILE', None), help='Path to client private key file')
58-
@click.option('--socket-timeout', type=float, default=lambda: get_env_or_default('REDIS_SOCKET_TIMEOUT', 5.0, float), help='Socket timeout in seconds')
59-
@click.option('--socket-connect-timeout', type=float, default=lambda: get_env_or_default('REDIS_SOCKET_CONNECT_TIMEOUT', 5.0, float), help='Socket connect timeout in seconds')
126+
@click.option('--socket-timeout', type=float, default=lambda: get_env_or_default('REDIS_SOCKET_TIMEOUT', None), help='Socket timeout in seconds')
127+
@click.option('--socket-connect-timeout', type=float, default=lambda: get_env_or_default('REDIS_SOCKET_CONNECT_TIMEOUT', None), help='Socket connect timeout in seconds')
60128
@click.option('--max-connections', type=int, default=lambda: get_env_or_default('REDIS_MAX_CONNECTIONS', 50, int), help='Maximum connections per client')
61129
@click.option('--client-retry-attempts', type=int, default=lambda: get_env_or_default('REDIS_CLIENT_RETRY_ATTEMPTS', 3, int), help='Number of client-level retry attempts for network/connection issues (uses redis-py Retry class)')
62-
@click.option('--maintenance-events-enabled', type=bool, default=lambda: get_env_or_default('REDIS_MAINT_EVENTS_ENABLED', True, bool), help='Server maintenance events (hitless upgrades push notifications)')
130+
@click.option('--maintenance-notifications-enabled', type=bool, default=lambda: get_env_or_default('REDIS_MAINT_NOTIFICATIONS_ENABLED', True, bool), help='Server maintenance events (hitless upgrades push notifications)')
131+
@click.option('--maintenance-relaxed-timeout', type=float, default=lambda: get_env_or_default('REDIS_MAINT_RELAXED_TIMEOUT', None), help='Relaxedimeout during maintenance events')
63132
@click.option('--protocol',type=int, default=lambda: get_env_or_default('REDIS_PROTOCOL', 3, int), help='RESP Version (2 or 3)')
64133

65134
# ============================================================================
@@ -123,7 +192,7 @@ def cli():
123192
@click.option('--save-config', help='Save current configuration to file')
124193
def run(**kwargs):
125194
"""Run Redis load test with specified configuration."""
126-
195+
127196
try:
128197
# Load configuration from file if specified
129198
if kwargs['config_file']:
@@ -140,89 +209,21 @@ def run(**kwargs):
140209
save_config_to_file(config, kwargs['save_config'])
141210
click.echo(f"Configuration saved to {kwargs['save_config']}")
142211
return
143-
212+
144213
# Validate configuration
145214
_validate_config(config)
146-
215+
147216
# Run the test
148217
runner = TestRunner(config)
149218
runner.start()
150-
219+
151220
except KeyboardInterrupt:
152221
click.echo("\nTest interrupted by user")
153222
sys.exit(0)
154223
except Exception as e:
155224
click.echo(f"Error: {e}", err=True)
156225
sys.exit(1)
157226

158-
159-
@cli.command()
160-
def list_profiles():
161-
"""List available workload profiles."""
162-
profiles = WorkloadProfiles.list_profiles()
163-
click.echo("Available workload profiles:")
164-
for profile in profiles:
165-
workload = WorkloadProfiles.get_profile(profile)
166-
operations = workload.get_option("operations", [workload.type])
167-
if isinstance(operations, list):
168-
ops_str = ', '.join(operations)
169-
else:
170-
ops_str = workload.type
171-
click.echo(f" {profile}: {ops_str}")
172-
173-
174-
@cli.command()
175-
@click.argument('profile_name', type=click.Choice(WorkloadProfiles.list_profiles()))
176-
def describe_profile(profile_name):
177-
"""Describe a specific workload profile."""
178-
workload = WorkloadProfiles.get_profile(profile_name)
179-
180-
click.echo(f"Workload Profile: {profile_name}")
181-
click.echo(f"Type: {workload.type}")
182-
click.echo(f"Duration: {workload.max_duration}")
183-
184-
# Show operations if available
185-
operations = workload.get_option("operations")
186-
if operations:
187-
click.echo(f"Operations: {', '.join(operations)}")
188-
189-
# Show operation weights if available
190-
weights = workload.get_option("operation_weights")
191-
if weights:
192-
click.echo("Operation Weights:")
193-
for op, weight in weights.items():
194-
click.echo(f" {op}: {weight}")
195-
196-
# Show key configuration options
197-
value_size = workload.get_option("valueSize")
198-
if value_size:
199-
click.echo(f"Value Size: {value_size} bytes")
200-
201-
iteration_count = workload.get_option("iterationCount")
202-
if iteration_count:
203-
click.echo(f"Iteration Count: {iteration_count}")
204-
205-
# Show pipeline configuration
206-
use_pipeline = workload.get_option("usePipeline", False)
207-
click.echo(f"Pipeline: {'Yes' if use_pipeline else 'No'}")
208-
if use_pipeline:
209-
pipeline_size = workload.get_option("pipelineSize", 10)
210-
click.echo(f"Pipeline Size: {pipeline_size}")
211-
212-
# Show async configuration
213-
async_mode = workload.get_option("asyncMode", False)
214-
click.echo(f"Async Mode: {'Yes' if async_mode else 'No'}")
215-
216-
# Show channels if available
217-
channels = workload.get_option("channels")
218-
if channels:
219-
click.echo(f"Channels: {', '.join(channels)}")
220-
221-
# Show all other options
222-
click.echo("All Options:")
223-
for key, value in workload.options.items():
224-
click.echo(f" {key}: {value}")
225-
226227
@cli.command()
227228
def test_connection():
228229
"""Test Redis connection with current configuration."""
@@ -236,19 +237,19 @@ def test_connection():
236237
cluster_mode=get_env_or_default('REDIS_CLUSTER', False, bool),
237238
ssl=get_env_or_default('REDIS_SSL', False, bool)
238239
)
239-
240+
240241
from redis_client import RedisClient
241242
client = RedisClient(redis_config)
242-
243+
243244
# Test basic operations
244245
client.ping()
245246
info = client.get_info()
246-
247+
247248
click.echo("✓ Redis connection successful!")
248249
click.echo(f"Redis version: {info.get('redis_version', 'unknown')}")
249250
click.echo(f"Redis mode: {'cluster' if redis_config.cluster_mode else 'standalone'}")
250251
click.echo(f"Connected clients: {info.get('connected_clients', 'unknown')}")
251-
252+
252253
client.close()
253254

254255
except Exception as e:
@@ -293,7 +294,8 @@ def _build_config_from_args(kwargs) -> RunnerConfig:
293294
socket_connect_timeout=kwargs['socket_connect_timeout'],
294295
max_connections=kwargs['max_connections'],
295296
client_retry_attempts=kwargs['client_retry_attempts'],
296-
maintenance_events_enabled=kwargs['maintenance_events_enabled']
297+
maintenance_notifications_enabled=kwargs['maintenance_notifications_enabled'],
298+
maintenance_relaxed_timeout=kwargs['maintenance_relaxed_timeout']
297299
)
298300

299301

config.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ class RedisConnectionConfig:
8282
database: int = 0 # Changed from 'db' to match lettuce
8383
use_tls: bool = False # Changed from 'ssl' to match lettuce
8484
verify_peer: bool = False
85-
timeout: Optional[float] = None # Connection timeout in seconds
8685
protocol: int = 3 # 2 for RESP2, 3 for RESP3
8786

8887
# Cluster configuration
@@ -97,8 +96,8 @@ class RedisConnectionConfig:
9796
ssl_keyfile: Optional[str] = None
9897

9998
# Connection settings
100-
socket_timeout: float = 5.0
101-
socket_connect_timeout: float = 5.0
99+
socket_timeout: Optional[float] = None
100+
socket_connect_timeout: Optional[float] = None
102101
socket_keepalive: bool = True
103102
socket_keepalive_options: Dict[str, int] = field(default_factory=dict)
104103

@@ -109,7 +108,8 @@ class RedisConnectionConfig:
109108
# Redis client-level retry configuration (redis-py Retry object)
110109
# These retries happen at the Redis client level for network/connection issues
111110
client_retry_attempts: int = 3
112-
maintenance_events_enabled: bool = True
111+
maintenance_notifications_enabled: bool = True
112+
maintenance_relaxed_timeout: Optional[float] = None
113113

114114

115115
@dataclass

0 commit comments

Comments
 (0)