This document specifies the architecture for automated VM lifecycle management in azlin, implementing health monitoring, self-healing, and lifecycle event hooks.
graph TB
CLI[azlin CLI] --> LM[Lifecycle Manager]
CLI --> DaemonCtl[Daemon Controller]
DaemonCtl --> Daemon[Lifecycle Daemon Process]
Daemon --> Monitor[Health Monitor]
Daemon --> Healer[Self-Healer]
Daemon --> Hooks[Hook Executor]
Monitor --> |check| Azure[Azure API]
Monitor --> |check| SSH[SSH Connection]
Healer --> |restart| Azure
Healer --> |trigger| Hooks
LM --> Config[Lifecycle Config TOML]
Daemon --> Config
Hooks --> |execute| Scripts[User Hook Scripts]
style Daemon fill:#f9f,stroke:#333,stroke-width:4px
style Config fill:#bbf,stroke:#333,stroke-width:2px
Responsibility: Configuration and daemon lifecycle management
Public API (Studs):
class LifecycleManager:
def enable_monitoring(self, vm_name: str, config: MonitoringConfig) -> None
def disable_monitoring(self, vm_name: str) -> None
def get_monitoring_status(self, vm_name: str) -> MonitoringStatus
def update_config(self, vm_name: str, config: MonitoringConfig) -> NoneConfiguration Schema (~/.azlin/lifecycle-config.toml):
[vms.my-vm]
enabled = true
check_interval_seconds = 60
restart_policy = "never" # never | always | on-failure
ssh_failure_threshold = 3
health_check_timeout = 30
[vms.my-vm.hooks]
on_start = "/path/to/script.sh"
on_stop = ""
on_failure = ""
on_restart = ""
on_destroy = ""
on_healthy = ""
[daemon]
pid_file = "~/.azlin/lifecycle-daemon.pid"
log_file = "~/.azlin/lifecycle-daemon.log"
log_level = "INFO"Responsibility: Background monitoring process
Public API:
class LifecycleDaemon:
def start(self) -> None
def stop(self) -> None
def reload_config(self) -> None
def get_status(self) -> DaemonStatusArchitecture:
- Single background process using daemon library or multiprocessing
- Monitoring loop with configurable intervals
- Graceful shutdown on SIGTERM
- PID file for process management
- Logging to
~/.azlin/lifecycle-daemon.log
Responsibility: VM health checking
Public API:
class HealthMonitor:
def check_vm_health(self, vm_name: str) -> HealthStatus
def get_vm_state(self, vm_name: str) -> VMState
def check_ssh_connectivity(self, vm_name: str) -> bool
def get_metrics(self, vm_name: str) -> VMMetricsHealth Checks:
- VM State (Azure API): Running, Stopped, Deallocated
- SSH Connectivity: TCP connection test
- Basic Metrics (SSH): CPU, Memory, Disk (optional, only if SSH works)
Failure Thresholds:
- SSH failures counted
- Threshold configurable per VM (default: 3)
- Reset on successful check
Responsibility: Automatic recovery actions
Public API:
class SelfHealer:
def handle_failure(self, vm_name: str, failure: HealthFailure) -> None
def restart_vm(self, vm_name: str) -> RestartResult
def should_restart(self, vm_name: str, failure: HealthFailure) -> boolRestart Logic:
- Check restart policy
- Verify failure threshold met
- Execute restart via Azure API
- Trigger
on_restarthook - Reset failure counter on success
Policies:
never: No automatic restart (default, safe)on-failure: Restart after threshold SSH failuresalways: Restart on any health check failure (aggressive)
Responsibility: Execute user-defined lifecycle hooks
Public API:
class HookExecutor:
def execute_hook(self, hook_type: HookType, vm_name: str, context: dict) -> HookResult
def validate_hook_script(self, script_path: str) -> boolHook Types:
on_start: VM startedon_stop: VM stoppedon_failure: Health check failedon_restart: VM auto-restartedon_destroy: VM deletedon_healthy: VM passed health check
Execution:
- Runs on local machine (not VM)
- Environment variables provided:
AZLIN_VM_NAMEAZLIN_EVENT_TYPEAZLIN_TIMESTAMPAZLIN_FAILURE_COUNT(for on_failure)
- Timeout: 60 seconds default
- Non-blocking (daemon doesn't wait)
Responsibility: CLI interface for daemon operations
Public API:
class DaemonController:
def start_daemon(self) -> None
def stop_daemon(self) -> None
def restart_daemon(self) -> None
def daemon_status(self) -> DaemonStatusNew Commands:
azlin lifecycle enable <vm-name> # Enable monitoring
azlin lifecycle disable <vm-name> # Disable monitoring
azlin lifecycle status [vm-name] # Show monitoring status
azlin lifecycle daemon start # Start daemon
azlin lifecycle daemon stop # Stop daemon
azlin lifecycle daemon status # Daemon status
azlin lifecycle daemon logs # Show logsModified Commands:
azlin list # Add health column
azlin status # Add health sectionModified Output:
SESSION VM NAME STATUS IP REGION SIZE HEALTH
proj1 vm-001 Running 1.2.3.4 eastus D2s_v3 Healthy ✓
proj2 vm-002 Running 1.2.3.5 westus D2s_v3 Unhealthy (SSH down) ✗
- vm-003 Stopped N/A eastus B2s N/A
Implementation:
- Read
~/.azlin/lifecycle-config.toml - Query daemon for real-time health (if running)
- Fallback to "N/A" if daemon not running
New Section:
Lifecycle Monitoring:
Enabled: Yes
Policy: on-failure
Check Interval: 60s
SSH Failures: 0/3
Last Check: 2 minutes ago
Last Status: Healthy ✓
Hooks:
on_failure: /home/user/scripts/alert.sh
on_restart: /home/user/scripts/notify.sh
@dataclass
class MonitoringConfig:
enabled: bool
check_interval_seconds: int = 60
restart_policy: str = "never"
ssh_failure_threshold: int = 3
health_check_timeout: int = 30
hooks: dict[str, str] = field(default_factory=dict)
@dataclass
class HealthStatus:
vm_name: str
state: str # running, stopped, deallocated
ssh_reachable: bool
ssh_failures: int
last_check: datetime
metrics: VMMetrics | None
@dataclass
class VMMetrics:
cpu_percent: float | None
memory_percent: float | None
disk_percent: float | None
@dataclass
class DaemonStatus:
running: bool
pid: int | None
uptime: timedelta | None
monitored_vms: list[str]- Lifecycle Manager (config CRUD)
- Daemon Controller (start/stop/status)
- Basic daemon process
- Health Monitor (VM state + SSH only)
- CLI commands skeleton
Deliverable: Can enable monitoring, start daemon, see basic health
- Self-Healer implementation
- Restart policy logic
- Failure threshold tracking
- Integration with daemon loop
Deliverable: Automatic restart works
- Hook Executor
- Environment variable passing
- Timeout handling
- Hook validation
Deliverable: Basic hooks (on_start, on_stop, on_failure) work
- Modify
azlin listfor health column - Modify
azlin statusfor health section - Advanced hooks (on_restart, on_destroy, on_healthy)
- Metrics collection (CPU/mem/disk via SSH)
Deliverable: Fully integrated with azlin
- Comprehensive error handling
- Logging improvements
- Documentation
- Testing coverage >75%
test_lifecycle_manager.py: Config CRUD operationstest_health_monitor.py: Health checking logic (mocked Azure/SSH)test_self_healer.py: Restart decision logictest_hook_executor.py: Hook execution with mockstest_daemon_controller.py: Daemon management
test_daemon_integration.py: Full daemon lifecycletest_health_workflow.py: Health check → failure → restart flowtest_hook_workflow.py: Hook execution end-to-endtest_cli_integration.py: CLI commands with daemon
test_full_lifecycle.py: Create VM, enable monitoring, trigger failure, verify restart- Manual testing documented in test plan
-
Hook Scripts:
- Validate script path exists
- Check execute permissions
- Run with user's permissions (not elevated)
- Log all hook executions
-
Daemon:
- PID file secured (0600)
- Log file secured (0600)
- Daemon runs as user (not root)
- Graceful shutdown on signals
-
Configuration:
- Config file secured (0600)
- Input validation on all config fields
- No secrets in config (Azure creds from SDK)
-
SSH Checks:
- Use existing azlin SSH key
- Timeout all SSH operations
- No password auth
- Architecture designed
- Health monitoring daemon works (60s intervals)
- Automatic restart triggers after 3 SSH failures
- Basic hooks work (on_start, on_stop, on_failure)
- Health status visible in
azlin list - Health status visible in
azlin status - Config persists in
~/.azlin/lifecycle-config.toml - Tests coverage >75%
- CI passes
- Documentation complete
-
Daemon Management: Use systemd/launchd or simple background process?
- Decision: Start with simple background process (multiprocessing), defer systemd integration
-
Notification: Desktop notifications for health events?
- Decision: Phase 6 (post-MVP), use platform-specific notify
-
Metrics Storage: Historical health data?
- Decision: Not MVP, consider SQLite in Phase 6
- azlin codebase:
src/azlin/ - Configuration pattern:
src/azlin/config_manager.py - CLI pattern:
src/azlin/commands/ - Module pattern:
src/azlin/modules/