PANTHER Configuration Quickstart Tutorial¤
Introduction¤
This tutorial walks you through the essential concepts and practical usage of the PANTHER configuration system. You'll learn how to load, validate, and work with configurations for network testing scenarios.
Prerequisites¤
- Python 3.8+
- Basic familiarity with YAML
- Understanding of network testing concepts
Setup¤
First, ensure you have PANTHER installed:
pip install panther-config
Lesson 1: Basic Configuration Loading¤
Let's start with a simple configuration file and learn how to load it.
Create Your First Configuration¤
Create a file named simple_experiment.yaml:
# simple_experiment.yaml
logging:
level: INFO
paths:
output_dir: "./outputs"
tests:
- name: "Basic QUIC Test"
network_environment:
type: "docker_compose"
services:
- name: "quic_server"
implementation:
name: "picoquic"
type: "iut"
protocol:
name: "quic"
role: "server"
timeout: 60
Load the Configuration¤
from panther.config.core.manager import ConfigurationManager
# Initialize the configuration manager
config_manager = ConfigurationManager()
# Load and validate the configuration
config = config_manager.load_and_validate_config("simple_experiment.yaml")
# Access configuration data
print(f"Test name: {config.tests[0].name}")
print(f"Output directory: {config.global_config.paths.output_dir}")
print(f"Logging level: {config.global_config.logging.level}")
What happened? 1. The configuration manager loaded the YAML file 2. It validated the structure against the built-in schema 3. It created type-safe configuration objects you can access with dot notation
Lesson 2: Understanding Auto-Fix¤
The configuration system can automatically fix common issues. Let's see this in action.
Create a Configuration with Issues¤
Create problematic_config.yaml:
# problematic_config.yaml - has missing required fields
tests:
- name: "QUIC Server Test"
services:
- name: "quic_server"
protocol:
name: "quic"
role: "server"
# Missing: ports, implementation, timeout
Load with Auto-Fix¤
# Load configuration with auto-fix enabled
try:
config = config_manager.load_and_validate_config(
"problematic_config.yaml",
auto_fix=True
)
# Check what was auto-fixed
server = config.tests[0].services[0]
print(f"Auto-assigned ports: {server.ports}")
print(f"Default timeout: {server.timeout}")
except Exception as e:
print(f"Configuration error: {e}")
What happened? - The system detected missing ports for a QUIC server - It automatically assigned the default QUIC port (4443:4443) - It provided sensible defaults for other missing fields
Lesson 3: Environment Variables¤
Configurations often need to adapt to different environments. Let's use environment variables.
Create Environment-Aware Configuration¤
Create env_config.yaml:
# env_config.yaml
logging:
level: "${LOG_LEVEL:INFO}"
database:
host: "${DB_HOST:localhost}"
port: "${DB_PORT:5432}"
password: "${DB_PASSWORD}"
tests:
- name: "Environment Test"
services:
- name: "test_service"
implementation:
name: "${SERVICE_IMPL:picoquic}"
type: "iut"
timeout: "${SERVICE_TIMEOUT:60}"
Load with Environment Variables¤
import os
# Set environment variables
os.environ["LOG_LEVEL"] = "DEBUG"
os.environ["DB_HOST"] = "production-db"
os.environ["SERVICE_TIMEOUT"] = "120"
# Note: DB_PASSWORD is required and must be set
# Load with environment resolution
config = config_manager.load_with_environment(
"env_config.yaml",
env_vars={
"DB_PASSWORD": "secret-password",
"SERVICE_IMPL": "quiche"
}
)
print(f"Logging level: {config.global_config.logging.level}") # DEBUG
print(f"Database host: {config.global_config.database.host}") # production-db
print(f"Service timeout: {config.tests[0].services[0].timeout}") # 120
Environment Variable Syntax:
- ${VARIABLE}: Required variable, fails if not set
- ${VARIABLE:default}: Optional variable with default value
Lesson 4: Validation and Error Handling¤
Understanding validation helps you write better configurations.
Detailed Validation¤
from panther.config.core.components.validators import UnifiedValidator
# Create a validator for detailed analysis
validator = UnifiedValidator()
# Load a configuration (may have issues)
config_dict = config_manager.load_from_file("simple_experiment.yaml")
# Validate step by step
schema_result = validator.validate_schema_only(config_dict)
business_result = validator.validate_business_rules_only(config_dict)
print("Schema validation:", "✓" if schema_result.is_valid else "✗")
print("Business rules:", "✓" if business_result.is_valid else "✗")
# Get detailed validation report
full_result = validator.validate_experiment_config(config_dict)
if not full_result.is_valid:
print("\nValidation issues:")
for error in full_result.errors:
print(f" Error: {error}")
for warning in full_result.warnings:
print(f" Warning: {warning}")
Lesson 5: Working with Plugins¤
PANTHER uses plugins for different implementations. Let's explore plugin capabilities.
Discover Available Plugins¤
# Discover all available plugins
plugins = config_manager.discover_plugins()
print("Available plugins:")
for plugin in plugins:
print(f" {plugin.name} ({plugin.plugin_type}) - {plugin.protocol}")
# Discover specific plugin types
iut_plugins = config_manager.discover_plugins(plugin_type="iut")
print(f"\nIUT implementations: {[p.name for p in iut_plugins]}")
Plugin-Specific Configuration¤
# Get configuration schema for a specific plugin
try:
quiche_schema = config_manager.load_plugin_schema("quiche")
print("Quiche configuration options:")
for field, details in quiche_schema.items():
print(f" {field}: {details.get('type', 'unknown')}")
except Exception as e:
print(f"Plugin not available: {e}")
Lesson 6: Performance Optimization¤
For larger configurations or repeated operations, use caching.
Enable Caching¤
# Enable caching with 10-minute TTL
config_manager.enable_cache(ttl=600, max_size=50)
# Load configurations (first time - cache miss)
config1 = config_manager.load_from_file("simple_experiment.yaml")
config2 = config_manager.load_from_file("simple_experiment.yaml") # cache hit
# Check cache performance
stats = config_manager.get_cache_stats()
print(f"Cache hit rate: {stats.hit_rate}%")
print(f"Cache entries: {stats.entry_count}")
Pre-warm Cache¤
# Pre-load frequently used configurations
config_manager.warm_cache([
"experiment1.yaml",
"experiment2.yaml",
"base_config.yaml"
])
print("Cache warmed with common configurations")
Lesson 7: Configuration Merging¤
Combine multiple configurations for complex scenarios.
Create Base and Override Configurations¤
Base configuration (base.yaml):
# base.yaml
logging:
level: INFO
paths:
output_dir: "./outputs"
tests:
- name: "Base Test"
services:
- name: "base_service"
timeout: 60
Override configuration (override.yaml):
# override.yaml
logging:
level: DEBUG
tests:
- name: "Base Test"
services:
- name: "base_service"
timeout: 120
debug: true
Merge Configurations¤
# Load both configurations
base_config = config_manager.load_from_file("base.yaml")
override_config = config_manager.load_from_file("override.yaml")
# Merge with different strategies
merged = config_manager.merge_configs(
base_config,
override_config,
strategy="deep_merge"
)
print(f"Merged logging level: {merged['logging']['level']}") # DEBUG
print(f"Merged timeout: {merged['tests'][0]['services'][0]['timeout']}") # 120
Lesson 8: Custom Configuration Classes¤
Extend the configuration system for your specific needs.
Create Custom Configuration¤
from panther.config.core.base import BaseConfig
from dataclasses import dataclass
from typing import List, Optional
@dataclass
class DatabaseConfig(BaseConfig):
host: str
port: int = 5432
database: str = "panther"
ssl_enabled: bool = False
@dataclass
class MyAppConfig(BaseConfig):
app_name: str
version: str
database: DatabaseConfig
feature_flags: List[str] = None
debug_mode: bool = False
# Create and use custom configuration
db_config = DatabaseConfig(host="localhost", ssl_enabled=True)
app_config = MyAppConfig(
app_name="PANTHER Test",
version="1.0.0",
database=db_config,
feature_flags=["new_ui", "advanced_metrics"]
)
# Convert to various formats
config_dict = app_config.to_dict()
omega_config = app_config.to_omega()
yaml_string = app_config.to_yaml()
print(f"App: {app_config.app_name}")
print(f"Database host: {app_config.database.host}")
Common Patterns and Best Practices¤
Pattern 1: Configuration Validation in Tests¤
import pytest
def test_configuration_validity():
"""Ensure all test configurations are valid."""
config_files = [
"experiment1.yaml",
"experiment2.yaml",
"production.yaml"
]
manager = ConfigurationManager()
for config_file in config_files:
config = manager.load_and_validate_config(config_file)
assert config is not None
assert len(config.tests) > 0
Pattern 2: Environment-Specific Loading¤
def load_config_for_environment(env: str):
"""Load configuration appropriate for the environment."""
base_config = f"configs/base.yaml"
env_config = f"configs/{env}.yaml"
manager = ConfigurationManager()
# Load base configuration
base = manager.load_from_file(base_config)
# Load environment-specific overrides
try:
env_overrides = manager.load_from_file(env_config)
return manager.merge_configs(base, env_overrides)
except FileNotFoundError:
print(f"No environment config for {env}, using base")
return base
# Usage
prod_config = load_config_for_environment("production")
test_config = load_config_for_environment("testing")
Pattern 3: Configuration with Fallbacks¤
def robust_config_loading(primary_path: str, fallback_path: str):
"""Load configuration with fallback support."""
manager = ConfigurationManager()
try:
return manager.load_and_validate_config(primary_path, auto_fix=True)
except Exception as e:
print(f"Primary config failed: {e}")
print(f"Falling back to: {fallback_path}")
return manager.load_and_validate_config(fallback_path, auto_fix=True)
# Usage
config = robust_config_loading("experiment.yaml", "default_experiment.yaml")
Next Steps¤
Now that you understand the basics, you can:
- Explore Advanced Features: Look into plugin development and custom validators
- Read the Developer Guide: Learn about contributing to the configuration system
- Check the API Reference: Dive deep into all available methods and classes
- Study Real Examples: Examine the
experiment-config/directory for complex scenarios
Troubleshooting¤
Common Issues¤
Configuration not loading:
- Check file path and permissions
- Verify YAML syntax with an online validator
- Enable debug logging: logging.basicConfig(level=logging.DEBUG)
Validation errors:
- Use auto_fix=True to see suggested corrections
- Check plugin availability with discover_plugins()
- Verify all required fields are present
Environment variable issues:
- Ensure required variables are set
- Use ${VAR:default} syntax for optional variables
- Check variable names for typos
Performance problems:
- Enable caching for repeated operations
- Use warm_cache() for frequently accessed configurations
- Monitor cache statistics with get_cache_stats()
Summary¤
You've learned how to: - Load and validate configurations - Use auto-fix for common issues - Work with environment variables - Handle validation errors - Discover and use plugins - Optimize performance with caching - Merge multiple configurations - Create custom configuration classes
The PANTHER configuration system provides a powerful, flexible foundation for managing complex network testing scenarios while maintaining type safety and developer productivity.