Modular Role Design Philosophy¶
This document explains why the Qubinode KVM Host Setup Collection adopts a modular role-based architecture and how this design philosophy benefits users, developers, and the overall project ecosystem.
🎯 Why Modular Design?¶
The Problem with Monolithic Approaches¶
Traditional “all-in-one” automation approaches suffer from several limitations:
Tight Coupling: Changes to one feature affect unrelated functionality
Testing Complexity: Difficult to test individual components in isolation
Maintenance Burden: Large, complex roles are hard to understand and modify
Reusability Issues: Can’t use parts of the functionality independently
Collaboration Challenges: Multiple developers working on the same large codebase
The Modular Solution¶
Our modular approach addresses these challenges by:
Separation of Concerns: Each role has a single, well-defined responsibility
Loose Coupling: Roles interact through well-defined interfaces
Independent Testing: Each role can be tested and validated separately
Selective Usage: Users can choose which components they need
Parallel Development: Teams can work on different roles simultaneously
🏗️ Architectural Principles¶
1. Single Responsibility Principle¶
Each role focuses on one specific aspect of KVM host management:
kvmhost_base: Foundation
OS detection and validation
Base package installation
Essential service configuration
EPEL repository management
kvmhost_networking: Network Infrastructure
Bridge creation and management
Network interface configuration
Connectivity validation
kvmhost_libvirt: Virtualization Platform
Libvirt daemon configuration
Virtual network management
Hardware feature detection
kvmhost_storage: Storage Management
Storage pool creation
LVM configuration
Performance optimization
kvmhost_cockpit: Management Interface
Web interface installation
SSL configuration
User access management
kvmhost_user_config: User Environment
User account management
Shell configuration
Development tools
2. Dependency Inversion Principle¶
Roles depend on abstractions, not concrete implementations:
# Instead of hard-coding specific implementations
tasks:
- name: Configure bridge
command: brctl addbr br0 # Concrete implementation
# We use abstracted interfaces
tasks:
- name: Configure bridge
community.general.nmcli: # Abstract interface
conn_name: "{{ bridge_name }}"
type: bridge
3. Interface Segregation¶
Each role exposes only the interfaces it needs:
# kvmhost_networking role interface
input_interface:
required_vars:
- kvm_host_interface
- qubinode_bridge_name
optional_vars:
- bridge_config
- validation_enabled
output_interface:
facts_provided:
- bridge_created
- primary_interface_detected
- network_validation_results
🔄 Role Interaction Patterns¶
1. Sequential Dependency Pattern¶
Roles execute in a specific order based on dependencies:
kvmhost_base (foundation)
├── kvmhost_networking (requires: base)
├── kvmhost_user_config (requires: base)
└── kvmhost_libvirt (requires: base, networking)
├── kvmhost_storage (requires: base, libvirt)
└── kvmhost_cockpit (requires: base, libvirt)
2. Configuration Inheritance Pattern¶
Roles inherit and extend configuration from their dependencies:
# kvmhost_base provides
base_packages: [curl, wget, git]
# kvmhost_libvirt extends
libvirt_packages: "{{ base_packages + ['libvirt-daemon', 'qemu-kvm'] }}"
# kvmhost_storage further extends
storage_packages: "{{ libvirt_packages + ['lvm2', 'parted'] }}"
3. Event-Driven Pattern¶
Roles communicate through Ansible facts and handlers:
# kvmhost_networking sets facts
- name: Set bridge creation fact
ansible.builtin.set_fact:
bridge_created: true
bridge_name: "{{ qubinode_bridge_name }}"
# kvmhost_libvirt uses these facts
- name: Configure libvirt network
when: bridge_created | default(false)
🎨 Design Benefits¶
For End Users¶
Flexibility¶
Users can choose which components to deploy:
# Minimal setup - just base and libvirt
roles:
- kvmhost_base
- kvmhost_libvirt
# Full setup - all components
roles:
- kvmhost_setup # Orchestrates all roles
Customization¶
Each role can be configured independently:
# Custom networking without storage
- role: kvmhost_networking
vars:
qubinode_bridge_name: "custom-br0"
network_validation_enabled: false
# Skip user configuration
- role: kvmhost_setup
vars:
configure_shell: false
Troubleshooting¶
Issues can be isolated to specific components:
# Test only networking
ansible-playbook playbook.yml --tags networking
# Skip problematic components
ansible-playbook playbook.yml --skip-tags cockpit
For Developers¶
Focused Development¶
Developers can focus on specific areas of expertise:
Network specialists work on kvmhost_networking
Storage experts enhance kvmhost_storage
Security teams improve kvmhost_base
Independent Testing¶
Each role has its own test suite:
# Test individual roles
cd roles/kvmhost_networking
molecule test
# Test role interactions
ansible-playbook test-modular.yml
Parallel Development¶
Multiple teams can work simultaneously:
Team A: Enhancing storage management
Team B: Improving network configuration
Team C: Adding new user features
For Operations¶
Selective Deployment¶
Deploy only needed components:
# Edge deployment - minimal footprint
roles:
- kvmhost_base
- kvmhost_libvirt
# Full datacenter deployment
roles:
- kvmhost_setup # Everything
Incremental Updates¶
Update components independently:
# Update only networking components
ansible-playbook playbook.yml --tags networking
# Update storage without affecting network
ansible-playbook playbook.yml --tags storage
🔧 Implementation Strategies¶
1. Role Interface Design¶
Each role defines clear interfaces:
# Role input interface (defaults/main.yml)
role_input:
required_variables:
- admin_user
- kvm_host_interface
optional_variables:
- custom_config
- feature_flags
# Role output interface (facts set)
role_output:
facts_provided:
- component_configured
- configuration_status
- validation_results
2. Configuration Management¶
Centralized configuration with role-specific overrides:
# Global configuration (group_vars/all.yml)
admin_user: "kvmadmin"
kvm_host_domain: "lab.example.com"
# Role-specific configuration (host_vars/host.yml)
kvmhost_networking_bridge_name: "custom-br0"
kvmhost_storage_pool_size: "100G"
3. Error Handling Strategy¶
Each role implements consistent error handling:
# Validation with clear error messages
- name: Validate required variables
ansible.builtin.assert:
that:
- admin_user is defined
- admin_user | length > 0
fail_msg: "admin_user must be defined and non-empty"
# Graceful degradation
- name: Optional feature configuration
block:
- name: Configure advanced feature
# ... configuration tasks
rescue:
- name: Log feature unavailable
ansible.builtin.debug:
msg: "Advanced feature not available, continuing with basic setup"
📈 Scalability Considerations¶
Horizontal Scaling¶
The modular design supports scaling across multiple hosts:
# Different roles for different host types
- hosts: kvm_compute_nodes
roles:
- kvmhost_base
- kvmhost_libvirt
- kvmhost_storage
- hosts: kvm_management_nodes
roles:
- kvmhost_base
- kvmhost_cockpit
- kvmhost_user_config
Vertical Scaling¶
Roles adapt to different resource levels:
# High-performance configuration
kvmhost_libvirt_performance_mode: high
kvmhost_storage_use_ssd: true
kvmhost_networking_jumbo_frames: true
# Resource-constrained configuration
kvmhost_libvirt_performance_mode: minimal
kvmhost_storage_use_compression: true
🔄 Evolution and Extensibility¶
Adding New Roles¶
The modular design makes it easy to add new functionality:
# New role: kvmhost_monitoring
dependencies:
- kvmhost_base # Foundation
- kvmhost_libvirt # For VM metrics
responsibilities:
- Monitoring agent installation
- Metrics collection configuration
- Alerting setup
Extending Existing Roles¶
Roles can be extended without breaking existing functionality:
# kvmhost_storage extension for cloud storage
new_features:
- cloud_storage_integration
- automated_backup
- disaster_recovery
backward_compatibility:
- existing_variables_preserved
- default_behavior_unchanged
- migration_path_provided
🎓 Learning from the Design¶
Design Patterns Applied¶
Facade Pattern: kvmhost_setup provides a simple interface to complex subsystems
Strategy Pattern: Different implementations for different platforms
Observer Pattern: Roles react to changes in system state
Template Method Pattern: Common task patterns with role-specific implementations
Lessons Learned¶
Start Simple: Begin with basic functionality, add complexity gradually
Define Interfaces Early: Clear interfaces prevent integration issues
Test Boundaries: Test role interactions as much as individual roles
Document Decisions: ADRs capture the reasoning behind design choices