312 lines
9.9 KiB
Python
312 lines
9.9 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
"""
|
|
Test script for LMCache Controller Configuration
|
|
|
|
This script tests the ControllerConfig class and its functionality:
|
|
- Loading from environment variables
|
|
- Loading from YAML file
|
|
- Command-line parameter overrides
|
|
- Global singleton pattern
|
|
"""
|
|
|
|
# Standard
|
|
import os
|
|
import tempfile
|
|
|
|
# First Party
|
|
from lmcache.logging import init_logger
|
|
from lmcache.v1.cache_controller.config import (
|
|
ControllerConfig,
|
|
controller_get_or_create_config,
|
|
load_controller_config_with_overrides,
|
|
override_controller_config_from_dict,
|
|
)
|
|
|
|
logger = init_logger(__name__)
|
|
|
|
|
|
def test_from_env():
|
|
"""Test loading configuration from environment variables"""
|
|
logger.info("=" * 80)
|
|
logger.info("Testing: Loading from environment variables")
|
|
logger.info("=" * 80)
|
|
|
|
# Set some environment variables
|
|
os.environ["LMCACHE_CONTROLLER_CONTROLLER_PORT"] = "9001"
|
|
os.environ["LMCACHE_CONTROLLER_HEALTH_CHECK_INTERVAL"] = "30"
|
|
|
|
config = ControllerConfig.from_env()
|
|
config.validate()
|
|
config.log_config()
|
|
|
|
# Verify values
|
|
assert config.controller_port == 9001
|
|
assert config.health_check_interval == 30
|
|
|
|
logger.info("✓ Environment variable loading test passed")
|
|
|
|
|
|
def test_from_file():
|
|
"""Test loading configuration from YAML file"""
|
|
logger.info("=" * 80)
|
|
logger.info("Testing: Loading from YAML file")
|
|
logger.info("=" * 80)
|
|
|
|
# Create a temporary YAML config file
|
|
config_content = """
|
|
controller_port: 9002
|
|
health_check_interval: 60
|
|
"""
|
|
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
|
|
f.write(config_content)
|
|
config_file = f.name
|
|
|
|
try:
|
|
config = ControllerConfig.from_file(config_file)
|
|
config.validate()
|
|
config.log_config()
|
|
|
|
# Verify values
|
|
assert config.controller_port == 9002
|
|
assert config.health_check_interval == 60
|
|
|
|
logger.info("✓ File loading test passed")
|
|
finally:
|
|
os.unlink(config_file)
|
|
|
|
|
|
def test_override_from_dict():
|
|
"""Test overriding configuration with dictionary"""
|
|
logger.info("=" * 80)
|
|
logger.info("Testing: Override configuration with dictionary")
|
|
logger.info("=" * 80)
|
|
|
|
config = ControllerConfig.from_env()
|
|
|
|
# Override with dictionary
|
|
overrides = {
|
|
"controller_port": 9003,
|
|
"health_check_interval": 45,
|
|
}
|
|
|
|
override_controller_config_from_dict(config, overrides)
|
|
config.validate()
|
|
config.log_config()
|
|
|
|
# Verify overrides
|
|
assert config.controller_port == 9003
|
|
assert config.health_check_interval == 45
|
|
|
|
logger.info("✓ Dictionary override test passed")
|
|
|
|
|
|
def test_singleton_pattern():
|
|
"""Test thread-safe singleton pattern"""
|
|
logger.info("=" * 80)
|
|
logger.info("Testing: Singleton pattern")
|
|
logger.info("=" * 80)
|
|
|
|
# Clear any existing instance using the new reset method
|
|
controller_get_or_create_config.reset()
|
|
|
|
# Set environment variable for config file
|
|
os.environ["LMCACHE_CONTROLLER_CONTROLLER_PORT"] = "9004"
|
|
|
|
# Get config instance multiple times
|
|
config1 = controller_get_or_create_config()
|
|
config2 = controller_get_or_create_config()
|
|
|
|
# Verify they are the same instance
|
|
assert config1 is config2
|
|
assert config1.controller_port == 9004
|
|
|
|
# Test reset functionality
|
|
controller_get_or_create_config.reset()
|
|
os.environ["LMCACHE_CONTROLLER_CONTROLLER_PORT"] = "9005"
|
|
config3 = controller_get_or_create_config()
|
|
assert config3.controller_port == 9005
|
|
assert config3 is not config1 # Should be a new instance after reset
|
|
|
|
logger.info("✓ Singleton pattern test passed")
|
|
|
|
|
|
def test_to_from_dict():
|
|
"""Test dictionary serialization/deserialization"""
|
|
logger.info("=" * 80)
|
|
logger.info("Testing: Dictionary serialization")
|
|
logger.info("=" * 80)
|
|
|
|
config = ControllerConfig.from_env()
|
|
config.controller_port = 9005
|
|
|
|
# Convert to dictionary
|
|
config_dict = config.to_dict()
|
|
|
|
# Create new config from dictionary
|
|
new_config = ControllerConfig.from_dict(config_dict)
|
|
|
|
# Verify they are equivalent
|
|
assert new_config.controller_port == config.controller_port
|
|
|
|
logger.info("✓ Dictionary serialization test passed")
|
|
|
|
|
|
def test_to_from_json():
|
|
"""Test JSON serialization/deserialization"""
|
|
logger.info("=" * 80)
|
|
logger.info("Testing: JSON serialization")
|
|
logger.info("=" * 80)
|
|
|
|
config = ControllerConfig.from_env()
|
|
config.controller_port = 9006
|
|
|
|
# Convert to JSON
|
|
json_str = config.to_json()
|
|
|
|
# Create new config from JSON
|
|
new_config = ControllerConfig.from_json(json_str)
|
|
|
|
# Verify they are equivalent
|
|
assert new_config.controller_port == config.controller_port
|
|
|
|
logger.info("✓ JSON serialization test passed")
|
|
|
|
|
|
def test_controller_monitor_ports_json_parsing():
|
|
"""Test that controller_monitor_ports JSON string is correctly parsed as dict"""
|
|
logger.info("=" * 80)
|
|
logger.info("Testing: controller_monitor_ports JSON parsing")
|
|
logger.info("=" * 80)
|
|
|
|
# Test 1: From environment variable (string JSON)
|
|
original_env = os.environ.get("LMCACHE_CONTROLLER_CONTROLLER_MONITOR_PORTS")
|
|
os.environ["LMCACHE_CONTROLLER_CONTROLLER_MONITOR_PORTS"] = (
|
|
'{"pull": 8300, "reply": 8400}'
|
|
)
|
|
|
|
try:
|
|
config = ControllerConfig.from_env()
|
|
config.validate()
|
|
|
|
# Check that controller_monitor_ports is a dict, not a string
|
|
expected_type_msg = (
|
|
f"Expected dict but got {type(config.controller_monitor_ports)}: "
|
|
f"{config.controller_monitor_ports}"
|
|
)
|
|
assert isinstance(config.controller_monitor_ports, dict), expected_type_msg
|
|
assert config.controller_monitor_ports["pull"] == 8300
|
|
assert config.controller_monitor_ports["reply"] == 8400
|
|
|
|
logger.info("✓ Environment variable JSON parsing test passed")
|
|
|
|
# Test 2: From file with YAML string
|
|
config_content = """
|
|
controller_monitor_ports: '{"pull": 8500, "reply": 8600}'
|
|
"""
|
|
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
|
|
f.write(config_content)
|
|
config_file = f.name
|
|
|
|
try:
|
|
config2 = ControllerConfig.from_file(config_file)
|
|
config2.validate()
|
|
|
|
expected_type_msg2 = (
|
|
f"Expected dict but got {type(config2.controller_monitor_ports)}: "
|
|
f"{config2.controller_monitor_ports}"
|
|
)
|
|
assert isinstance(config2.controller_monitor_ports, dict), (
|
|
expected_type_msg2
|
|
)
|
|
assert config2.controller_monitor_ports["pull"] == 8500
|
|
assert config2.controller_monitor_ports["reply"] == 8600
|
|
|
|
logger.info("✓ File YAML JSON parsing test passed")
|
|
|
|
# Test 3: Dictionary override with JSON string
|
|
config3 = ControllerConfig.from_env()
|
|
override_dict = {
|
|
"controller_monitor_ports": '{"pull": 8700, "reply": 8800}'
|
|
}
|
|
override_controller_config_from_dict(config3, override_dict)
|
|
|
|
expected_type_msg3 = (
|
|
f"Expected dict but got {type(config3.controller_monitor_ports)}: "
|
|
f"{config3.controller_monitor_ports}"
|
|
)
|
|
assert isinstance(config3.controller_monitor_ports, dict), (
|
|
expected_type_msg3
|
|
)
|
|
assert config3.controller_monitor_ports["pull"] == 8700
|
|
assert config3.controller_monitor_ports["reply"] == 8800
|
|
|
|
logger.info("✓ Dictionary override JSON parsing test passed")
|
|
|
|
# Test 4: Dictionary override with actual dict
|
|
config4 = ControllerConfig.from_env()
|
|
override_dict2 = {"controller_monitor_ports": {"pull": 8900, "reply": 9000}}
|
|
override_controller_config_from_dict(config4, override_dict2)
|
|
|
|
expected_type_msg4 = (
|
|
f"Expected dict but got {type(config4.controller_monitor_ports)}: "
|
|
f"{config4.controller_monitor_ports}"
|
|
)
|
|
assert isinstance(config4.controller_monitor_ports, dict), (
|
|
expected_type_msg4
|
|
)
|
|
assert config4.controller_monitor_ports["pull"] == 8900
|
|
assert config4.controller_monitor_ports["reply"] == 9000
|
|
|
|
logger.info("✓ Dictionary override with dict test passed")
|
|
|
|
finally:
|
|
os.unlink(config_file)
|
|
|
|
finally:
|
|
# Restore original environment variable
|
|
if original_env is not None:
|
|
os.environ["LMCACHE_CONTROLLER_CONTROLLER_MONITOR_PORTS"] = original_env
|
|
else:
|
|
os.environ.pop("LMCACHE_CONTROLLER_CONTROLLER_MONITOR_PORTS", None)
|
|
|
|
logger.info("✓ controller_monitor_ports JSON parsing test passed")
|
|
|
|
|
|
def test_load_controller_config_with_overrides():
|
|
"""
|
|
Test loading controller config with overrides,
|
|
especially for controller_monitor_ports
|
|
"""
|
|
logger.info("=" * 80)
|
|
logger.info("Testing: load_controller_config_with_overrides")
|
|
logger.info("=" * 80)
|
|
|
|
# Test with monitor ports as JSON string in overrides
|
|
overrides = {
|
|
"controller_host": "localhost",
|
|
"controller_port": 8000,
|
|
"controller_monitor_ports": '{"pull": 8300, "reply": 8400}',
|
|
}
|
|
|
|
# Load config without config file (from env with overrides)
|
|
config = load_controller_config_with_overrides(overrides=overrides)
|
|
|
|
# Verify values
|
|
assert config.controller_host == "localhost"
|
|
assert config.controller_port == 8000
|
|
|
|
expected_type_msg = (
|
|
f"Expected dict but got {type(config.controller_monitor_ports)}: "
|
|
f"{config.controller_monitor_ports}"
|
|
)
|
|
assert isinstance(config.controller_monitor_ports, dict), expected_type_msg
|
|
assert config.controller_monitor_ports["pull"] == 8300
|
|
assert config.controller_monitor_ports["reply"] == 8400
|
|
|
|
# Log config to see the output
|
|
config.log_config()
|
|
|
|
logger.info("✓ load_controller_config_with_overrides test passed")
|