Skip to main content

hyperion_framework/containerisation/
hyperion_container_factory.rs

1// -------------------------------------------------------------------------------------------------
2// Hyperion Framework
3// https://github.com/robert-hannah/hyperion-framework
4//
5// A lightweight component-based TCP framework for building service-oriented Rust applications with
6// CLI control, async messaging, and lifecycle management.
7//
8// Copyright 2025 Robert Hannah
9//
10// Licensed under the Apache License, Version 2.0 (the "License");
11// you may not use this file except in compliance with the License.
12// You may obtain a copy of the License at
13//
14//     http://www.apache.org/licenses/LICENSE-2.0
15//
16// Unless required by applicable law or agreed to in writing, software
17// distributed under the License is distributed on an "AS IS" BASIS,
18// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// See the License for the specific language governing permissions and
20// limitations under the License.
21// -------------------------------------------------------------------------------------------------
22
23// Standard
24use std::fmt::Debug;
25use std::fs;
26use std::path::PathBuf;
27use std::str::FromStr;
28use std::sync::{Arc as StdArc, atomic::AtomicUsize};
29
30// Package
31use log::LevelFilter;
32use serde::{Serialize, de::DeserializeOwned};
33use tokio::sync::{Notify, mpsc};
34use tokio::task;
35use tokio::time::{Duration, sleep};
36
37// Local
38use crate::containerisation::client_broker::ClientBroker;
39use crate::containerisation::hyperion_container::HyperionContainer;
40use crate::containerisation::traits::{
41    ContainerIdentidy, HeartbeatConfigProvider, HyperionContainerDirectiveMessage,
42    HyperionHeartbeatMessage, Initialisable, LogLevel, Run,
43};
44use crate::heartbeat::handler::{HeartbeatMissedHandler, HeartbeatTimeoutHandler};
45use crate::logging::logging_service::initialise_logger;
46use crate::network::network_topology::NetworkTopology;
47use crate::network::server::Server;
48use crate::utilities::load_config;
49
50// A is the HyperionContainer Component template - must implement Initialisable and Run traits
51// C is an StdArc instance of a populated config struct - specific to the component
52// T is primary Component message type
53pub async fn create<A, C, T>(
54    config_path_str: &str,
55    network_topology_path_str: &str,
56    container_state: StdArc<AtomicUsize>,
57    container_state_notify: StdArc<Notify>,
58    main_rx: mpsc::Receiver<T>,
59    timeout_handler: Option<Box<dyn HeartbeatTimeoutHandler>>,
60    missed_handler: Option<Box<dyn HeartbeatMissedHandler>>,
61) -> HyperionContainer<T>
62where
63    A: Initialisable<ConfigType = C> + Run<Message = T> + Send + 'static + Sync + Debug,
64    C: Debug
65        + Send
66        + 'static
67        + DeserializeOwned
68        + Sync
69        + LogLevel
70        + ContainerIdentidy
71        + HeartbeatConfigProvider,
72    T: HyperionContainerDirectiveMessage
73        + HyperionHeartbeatMessage
74        + Debug
75        + Send
76        + 'static
77        + DeserializeOwned
78        + Sync
79        + Clone
80        + Serialize,
81{
82    // Read Component and network configs (program should exit if this fails)
83    let config_path: PathBuf = fs::canonicalize(config_path_str)
84        .unwrap_or_else(|e| panic!("Could not canonicalize '{config_path_str}': {e}"));
85    let component_config: StdArc<C> = load_config::load_config::<C>(&config_path)
86        .unwrap_or_else(|e| panic!("Failed to load component config from '{config_path:?}': {e}"));
87    let network_topology_path: PathBuf = fs::canonicalize(network_topology_path_str)
88        .unwrap_or_else(|e| panic!("Could not canonicalize '{network_topology_path_str}': {e}"));
89    let network_topology: StdArc<NetworkTopology> =
90        load_config::load_config::<NetworkTopology>(&network_topology_path).unwrap_or_else(|e| {
91            panic!("Failed to load network topology from '{network_topology_path:?}': {e}")
92        });
93
94    // Initialise logger
95    let log_level: LevelFilter = LevelFilter::from_str(component_config.log_level())
96        .unwrap_or_else(|e| {
97            // Can't use logger here as it doesn't exist yet
98            println!("Log level was not parsed correctly: {e:?}\nDefaulting to 'Trace' log level.");
99            LevelFilter::Trace
100        });
101    initialise_logger(log_level).unwrap_or_else(|e| panic!("Failed to initialise logger: {e:?}"));
102
103    // Initialise console - temporary startup printout
104    for (key, value) in component_config.container_identity().iter() {
105        log::debug!("{key}: {value}");
106    }
107
108    // TODO: Improve startup messaging. Implement project boilerplate printout etc
109    log::info!(
110        "Building Hyperion Container for {}...",
111        component_config
112            .container_identity()
113            .get("name")
114            .unwrap_or(&"Unknown".to_string())
115    );
116
117    // Initialise component - Ensure the component can build without errors before starting comms
118    let component_archetype = A::initialise(
119        container_state.clone(),
120        container_state_notify.clone(),
121        component_config.clone(),
122    );
123
124    // Initialise and run Server
125    let (server_tx, server_rx) = mpsc::channel::<T>(32);
126    let arc_server: StdArc<Server<T>> = Server::new(
127        network_topology.server_address.clone(),
128        server_tx,
129        container_state.clone(),
130        container_state_notify.clone(),
131    );
132    task::spawn(async move {
133        // No need to handle return as Server will set state to shutdown if it fails
134        if let Err(e) = Server::run(arc_server).await {
135            log::error!("Server encountered an error: {e:?}");
136        }
137    });
138
139    // Allow time for server to stabilise
140    sleep(Duration::from_secs(2)).await;
141
142    // Initialise and run client broker
143    let client_broker: ClientBroker<T> = ClientBroker::init(
144        network_topology,
145        container_state.clone(),
146        container_state_notify.clone(),
147    );
148
149    // Allow time for client(s) to stabilise
150    sleep(Duration::from_secs(2)).await;
151
152    let container_name = component_config
153        .container_identity()
154        .get("name")
155        .cloned()
156        .unwrap_or_else(|| "Unknown".to_string());
157
158    let heartbeat_config = component_config.heartbeat_config();
159
160    // Using previous elements, build HyperionContainer
161    HyperionContainer::<T>::create(
162        component_archetype,
163        container_state,
164        container_state_notify,
165        client_broker,
166        main_rx,
167        server_rx,
168        heartbeat_config,
169        container_name,
170        timeout_handler,
171        missed_handler,
172    )
173}