Substation Modular Ecosystem Documentation¶
Introduction¶
The Substation TUI has evolved from a monolithic architecture to a sophisticated modular ecosystem, transforming how OpenStack services are integrated and managed. This architectural shift brings significant benefits:
- Isolated Responsibilities: Each OpenStack service is encapsulated in its own module with clear boundaries
- Dynamic Loading: Modules can be enabled/disabled at runtime based on cloud capabilities
- Dependency Management: Automatic resolution and validation of inter-module dependencies
- Performance Optimization: Lazy loading and resource pooling reduce memory footprint
- Maintainability: Independent module development and testing cycles
- Extensibility: New OpenStack services can be added without core modifications
Core Architecture¶
The Module System¶
At the heart of the modular ecosystem is the OpenStackModule protocol, which defines the contract between modules and the TUI system:
@MainActor
protocol OpenStackModule {
// Identification
var identifier: String { get }
var displayName: String { get }
var version: String { get }
var dependencies: [String] { get }
// Lifecycle
init(tui: TUI)
func configure() async throws
func cleanup() async
func healthCheck() async -> ModuleHealthStatus
// Registration
func registerViews() -> [ModuleViewRegistration]
func registerFormHandlers() -> [ModuleFormHandlerRegistration]
func registerDataRefreshHandlers() -> [ModuleDataRefreshRegistration]
func registerActions() -> [ModuleActionRegistration]
// Configuration
var configurationSchema: ConfigurationSchema { get }
func loadConfiguration(_ config: ModuleConfig?)
// Navigation
var navigationProvider: (any ModuleNavigationProvider)? { get }
var handledViewModes: Set<ViewMode> { get }
}
Module Lifecycle¶
- Discovery: ModuleCatalog maintains metadata for all available modules
- Registration: ModuleRegistry validates dependencies and registers modules
- Configuration: Modules load configuration from ModuleConfigurationManager
- Integration: Views, handlers, and actions are registered with respective registries
- Operation: Modules handle their specific ViewModes and data operations
- Health Monitoring: Periodic health checks ensure module stability
Module Registration Process¶
The ModuleRegistry orchestrates module loading with dependency resolution:
func register(_ module: any OpenStackModule) async throws {
// Validate dependencies
for dep in module.dependencies {
guard modules[dep] != nil else {
throw ModuleError.missingDependency(...)
}
}
// Load configuration
let moduleConfig = configManager.configuration(for: module.identifier)
module.loadConfiguration(moduleConfig)
// Configure module
try await module.configure()
// Integrate with TUI
await integrateModule(module)
}
Module Structure Pattern¶
Each module follows a consistent directory structure that promotes organization and discoverability:
Modules/[ServiceName]/
[ServiceName]Module.swift # Main module implementation
[ServiceName]DataProvider.swift # Data fetching logic
TUI+[ServiceName]FormState.swift # Form state extensions
Models/ # Service-specific data models
Views/ # UI views for the service
Extensions/ # TUI extensions and handlers
Example: Servers Module Structure¶
Modules/Servers/
ServersModule.swift # OpenStackModule implementation
ServersDataProvider.swift # DataProvider for Nova instances
TUI+ServersFormState.swift # Server-specific form state
Models/
ServerOperations.swift
ServerState.swift
Views/
ServerViews.swift
ServerCreateView.swift
ServerSelectionView.swift
SnapshotManagementView.swift
Extensions/
TUI+ServersHandlers.swift
TUI+ServersNavigation.swift
TUI+ServersActions.swift
DataProvider Pattern¶
The DataProvider protocol enables modules to handle their own data fetching:
@MainActor
protocol DataProvider {
var resourceType: String { get }
var lastRefreshTime: Date? { get }
var currentItemCount: Int { get }
func fetchData(priority: DataFetchPriority, forceRefresh: Bool) async -> DataFetchResult
func refreshResource(id: String, priority: DataFetchPriority) async -> DataFetchResult
func clearCache() async
func needsRefresh(threshold: TimeInterval) -> Bool
}
Each module implements its own DataProvider, registered with the DataProviderRegistry for centralized data management while maintaining module isolation.
Core Packages¶
The modular ecosystem relies on several foundational packages that provide cross-cutting functionality:
MemoryKit¶
Advanced memory management system providing:
- Thread-safe caching using Swift actors
- Intelligent cache eviction policies (LRU, LFU, TTL)
- Real-time memory monitoring and alerting
- Cross-platform compatibility (macOS/Linux)
public actor MemoryKit {
public let memoryManager: MemoryManager
public let performanceMonitor: PerformanceMonitor
}
OSClient¶
OpenStack API client library handling:
- Service catalog discovery
- Authentication and token management
- API request/response handling
- Error handling and retries
- Response parsing and model mapping
SwiftNCurses¶
Terminal UI framework providing:
- NCurses abstraction layer
- Window and panel management
- Input handling and key mapping
- Color and styling support
- Component library for consistent UI
CrossPlatformTimer¶
Platform-agnostic timer implementation:
- Consistent timer behavior across macOS/Linux
- High-precision timing for performance monitoring
- Scheduled task execution
- Timer lifecycle management
Service Modules¶
The ecosystem includes 14 specialized modules, each handling a specific OpenStack service:
Module Dependency Graph¶
graph TD
Servers --> Networks
Servers --> Images
Servers --> Flavors
Servers --> KeyPairs
Servers --> Volumes
Servers --> SecurityGroups
Ports --> Networks
Ports --> SecurityGroups
FloatingIPs --> Networks
Routers --> Networks
Subnets --> Networks
Independent Modules (Phase 1)¶
Modules with no dependencies, loaded first:
- Barbican (
barbican): Key management service for secrets and certificates - Swift (
swift): Object storage with container and object management - KeyPairs (
keypairs): SSH key pair management for instance access - ServerGroups (
servergroups): Anti-affinity and affinity policies - Flavors (
flavors): Hardware profiles for instances - Images (
images): Boot images and snapshots - SecurityGroups (
securitygroups): Firewall rules and network security - Volumes (
volumes): Block storage management
Network-Dependent Modules (Phase 2)¶
Modules requiring network functionality:
- Networks (
networks): Virtual network management - Subnets (
subnets): IP allocation pools and DHCP configuration - Routers (
routers): Network routing and NAT gateways - FloatingIPs (
floatingips): Public IP address management - Ports (
ports): Network interface management
Multi-Dependent Modules (Phase 3)¶
Complex modules with multiple dependencies:
- Servers (
servers): Compute instance management - Dependencies: networks, images, flavors, keypairs, volumes, securitygroups
- Most complex module with comprehensive lifecycle management
Data Flow Architecture¶
flowchart LR
subgraph UI
TUI[TUI Controller]
end
subgraph Orchestration
MO[ModuleOrchestrator]
VR[ViewRegistry]
DR[DataProviderRegistry]
end
subgraph Caching
CM[CacheManager]
L1[L1 Memory]
L2[L2 Memory]
L3[L3 Disk]
end
subgraph External
API[OpenStack API]
end
TUI --> MO
MO --> VR
MO --> DR
DR --> CM
CM --> L1
L1 --> L2
L2 --> L3
L3 --> API
Module Lifecycle State Diagram¶
stateDiagram-v2
[*] --> Discovery
Discovery --> Registration
Registration --> Validation
Validation --> Configuration
Configuration --> Integration
Integration --> Operation
Operation --> HealthCheck
HealthCheck --> Operation
Operation --> Cleanup
Cleanup --> [*]
Inter-module Communication¶
Modules communicate through well-defined interfaces and shared registries:
Action Registry¶
Modules register actions that can be triggered from various contexts:
struct ModuleActionRegistration {
let identifier: String
let title: String
let keybinding: Character?
let viewModes: Set<ViewMode>
let handler: @MainActor @Sendable (OpaquePointer?) async -> Void
}
Actions are categorized (lifecycle, network, storage, security) and can be invoked across module boundaries.
DataProvider Registry¶
Centralized registry for all module DataProviders:
@MainActor
final class DataProviderRegistry {
func register(provider: any DataProvider, for resourceType: String)
func provider(for resourceType: String) -> (any DataProvider)?
func refreshAll(priority: DataFetchPriority) async
}
This enables coordinated data fetching and cache management across modules.
View Registry¶
Modules register their views for dynamic navigation:
struct ModuleViewRegistration {
let viewMode: ViewMode
let title: String
let renderHandler: @MainActor (OpaquePointer?, Int32, Int32, Int32, Int32) async -> Void
let inputHandler: (@MainActor (Int32, OpaquePointer?) async -> Bool)?
let category: ViewCategory
}
The TUI dynamically routes to appropriate module views based on ViewMode.
Event Broadcasting¶
Modules can broadcast and subscribe to events through the notification system:
// Broadcasting module
NotificationCenter.default.post(
name: .serverCreated,
object: nil,
userInfo: ["serverId": newServer.id]
)
// Subscribing module
NotificationCenter.default.addObserver(
self,
selector: #selector(handleServerCreated),
name: .serverCreated,
object: nil
)
Shared State¶
The TUI instance provides shared state accessible to all modules:
cacheManager: Centralized cache for OpenStack resourcesformStateManager: Shared form state across modulesnavigationStack: Navigation history and stateclient: OpenStack API client instance
Request Flow Sequence¶
sequenceDiagram
participant User
participant TUI
participant Module
participant DataProvider
participant Cache
participant API
User->>TUI: Navigate to view
TUI->>Module: Request render
Module->>DataProvider: Fetch data
DataProvider->>Cache: Check cache
alt Cache hit
Cache-->>DataProvider: Return cached
else Cache miss
Cache->>API: Fetch from API
API-->>Cache: Return data
Cache-->>DataProvider: Return data
end
DataProvider-->>Module: Return data
Module-->>TUI: Render view
TUI-->>User: Display
Development Guide¶
Creating a New Module¶
To add support for a new OpenStack service:
- Define the module in ModuleCatalog:
ModuleDefinition(
identifier: "heat",
displayName: "Orchestration (Heat)",
dependencies: ["networks", "servers"],
phase: .multiDependent
)
- Create module structure:
Modules/Heat/
HeatModule.swift
HeatDataProvider.swift
TUI+HeatFormState.swift
Models/
Views/
Extensions/
- Implement OpenStackModule protocol:
@MainActor
final class HeatModule: OpenStackModule {
let identifier = "heat"
let displayName = "Orchestration (Heat)"
let version = "1.0.0"
let dependencies = ["networks", "servers"]
// Implementation...
}
- Register with ModuleRegistry in
loadCoreModules():
if enabledModules.contains("heat") {
let heatModule = HeatModule(tui: tui)
try await registry.register(heatModule)
}
Module Best Practices¶
- Dependency Management: Keep dependencies minimal and explicit
- Error Handling: Gracefully handle service unavailability
- Performance: Implement efficient data fetching with caching
- Testing: Provide unit tests for module logic
- Documentation: Include SwiftDoc comments for public interfaces
- Configuration: Define sensible defaults in configuration schema
For detailed module development instructions, see the Module Development Guide.
Performance Considerations¶
The modular architecture provides several performance benefits:
Lazy Loading¶
Modules are loaded only when needed, reducing initial startup time:
class LazyModuleLoader {
func loadModuleIfNeeded(_ identifier: String) async throws
func preloadDependencies(for identifier: String) async
}
Resource Pooling¶
Shared resource pools minimize memory allocation:
actor ResourcePool<T> {
func acquire() async throws -> T
func release(_ resource: T) async
func drain() async
}
Performance Metrics¶
Each module tracks its performance:
struct ModulePerformanceMetrics {
let loadTime: TimeInterval
let memoryUsage: Int
let apiCallCount: Int
let cacheHitRate: Double
}
Configuration Management¶
Modules support runtime configuration through ModuleConfigurationManager:
struct ConfigurationSchema {
let entries: [ConfigurationEntry]
}
struct ConfigurationEntry {
let key: String
let type: ConfigurationType
let defaultValue: Any?
let description: String
let validation: ValidationRule?
}
Configuration can be loaded from:
- Default values in schema
- Configuration files (YAML/JSON)
- Environment variables
- Runtime updates
Hot Reload Support¶
The module system supports hot reloading for development:
class HotReloadManager {
func watchForChanges(in module: String)
func reloadModule(_ identifier: String) async throws
func reloadConfiguration() async throws
}
This enables rapid development without restarting the TUI.
Future Enhancements¶
The modular ecosystem is designed for extensibility:
- Plugin System: Load third-party modules dynamically
- Remote Modules: Load modules from package repositories
- Module Marketplace: Community-contributed modules
- Auto-discovery: Detect available OpenStack services
- Module Versioning: Support multiple module versions
- Cross-module Transactions: Coordinated operations across modules
Conclusion¶
The Substation modular ecosystem provides a robust, scalable foundation for OpenStack management. By isolating service-specific logic into discrete modules with well-defined interfaces, the architecture promotes maintainability, testability, and extensibility while delivering excellent performance and user experience.
The consistent module structure, comprehensive registries, and sophisticated lifecycle management ensure that new OpenStack services can be integrated seamlessly, making Substation a future-proof solution for cloud infrastructure management.