Skip to main content

product_os_command_control/
lib.rs

1//! # Product OS Command and Control
2//!
3//! This crate provides a distributed command and control system for coordinating
4//! multiple Product OS server instances. It enables secure communication, service
5//! discovery, and workload distribution across a cluster of nodes.
6//!
7//! ## Features
8//!
9//! - **Secure Communication**: Authentication framework using Diffie-Hellman key exchange
10//! - **Service Discovery**: Automatic node registration and discovery
11//! - **Load Balancing**: Smart routing to available nodes based on capabilities
12//! - **Health Monitoring**: Pulse checks and automatic failure detection
13//! - **Feature Management**: Dynamic feature and service registration
14//!
15//! ## Basic Usage
16//!
17//! ```no_run
18//! use product_os_command_control::ProductOSController;
19//! use product_os_command_control::CommandControl;
20//! use product_os_security::certificates::Certificates;
21//!
22//! # async fn example() {
23//! let config = CommandControl::default();
24//! let certs = Certificates::new_self_signed(
25//!     vec![("CN".to_string(), "localhost".to_string())],
26//!     None, None, None, None, None,
27//! );
28//!
29//! let controller = ProductOSController::new(
30//!     config,
31//!     certs,
32//!     None, // Optional key-value store
33//!     #[cfg(feature = "relational_store")]
34//!     None, // Optional relational store
35//! );
36//!
37//! let registry = controller.get_registry();
38//! let my_node = registry.get_me();
39//! println!("Node ID: {}", my_node.get_identifier());
40//! # }
41//! ```
42//!
43//! ## Architecture
44//!
45//! The command and control system consists of several key components:
46//!
47//! - **ProductOSController**: Main coordinator that manages nodes and services
48//! - **Registry**: Tracks available nodes and their capabilities
49//! - **Node**: Represents a single server instance in the cluster
50//! - **Commands**: Structured way to send instructions to remote nodes
51//!
52//! ## Security
53//!
54//! All node-to-node communication is secured using:
55//! - TLS/SSL certificates for transport security
56//! - Diffie-Hellman key exchange for establishing shared secrets
57//! - Message authentication using HMAC
58//!
59//! ## Cargo Features
60//!
61//! | Feature | Default | Description |
62//! |---------|---------|-------------|
63//! | `std_base` | Yes | Std support with core Product OS dependencies |
64//! | `framework_axum` | Yes | Axum HTTP framework |
65//! | `framework_flow` | No | Flow HTTP framework |
66//! | `monitor` | Yes | Monitoring via `product-os-monitoring` |
67//! | `tokio` | No | Tokio async executor |
68//! | `distributed` | No | Distributed node discovery (placeholder) |
69//! | `relational_store` | No | Base relational store support |
70//! | `postgres_store` | No | PostgreSQL relational store |
71//! | `sqlite_store` | No | SQLite relational store |
72//! | `redis_key_value_store` | No | Redis key-value store |
73//! | `memory_key_value_store` | No | In-memory key-value store |
74//! | `file_key_value_store` | No | File-based key-value store |
75//! | `redis_queue_store` | No | Redis queue store |
76//! | `memory_queue_store` | No | In-memory queue store |
77
78#![no_std]
79#![warn(rust_2018_idioms)]
80#![warn(clippy::all)]
81#![allow(clippy::module_name_repetitions)]
82#![allow(clippy::must_use_candidate)]
83#![allow(clippy::too_many_arguments)]
84#![allow(clippy::missing_errors_doc)]
85#![allow(clippy::missing_panics_doc)]
86#![allow(missing_docs)]
87#![allow(ambiguous_glob_imports)]
88#![allow(ambiguous_panic_imports)]
89
90extern crate no_std_compat as std;
91
92use std::prelude::v1::*;
93
94mod config;
95pub use config::CommandControl;
96
97pub mod authentication;
98mod registry;
99pub mod commands;
100
101use std::collections::BTreeMap;
102
103use std::sync::Arc;
104#[cfg(feature = "distributed")]
105use product_os_request::Bytes;
106use parking_lot::Mutex;
107#[cfg(feature = "distributed")]
108use parking_lot::MutexGuard;
109#[cfg(feature = "relational_store")]
110use product_os_store::{KeyValueKind, KeyValueStore, RelationalKind, RelationalStore};
111#[cfg(not(feature = "relational_store"))]
112use product_os_store::{KeyValueKind, KeyValueStore};
113
114use std::str::FromStr;
115use std::time::Duration;
116
117pub use product_os_request::Method;
118use product_os_request::{ProductOSClient, ProductOSResponse};
119pub use registry::Node;
120
121/// Constant for the "true" string used in query maps.
122const ENABLED_STR: &str = "true";
123
124
125/// Main controller for managing a distributed command and control system.
126///
127/// `ProductOSController` is the primary interface for coordinating multiple
128/// Product OS server instances. It handles node discovery, service management,
129/// secure communication, and workload distribution.
130///
131/// # Examples
132///
133/// ```no_run
134/// use product_os_command_control::ProductOSController;
135/// use product_os_command_control::CommandControl;
136/// use product_os_security::certificates::Certificates;
137///
138/// let config = CommandControl::default();
139/// let certs = Certificates::new_self_signed(
140///     vec![("CN".to_string(), "localhost".to_string())],
141///     None, None, None, None, None,
142/// );
143///
144/// let controller = ProductOSController::new(
145///     config,
146///     certs,
147///     None,
148///     #[cfg(feature = "relational_store")]
149///     None,
150/// );
151/// ```
152pub struct ProductOSController {
153    command_control: crate::config::CommandControl,
154
155    certificates: product_os_security::certificates::Certificates,
156
157    max_servers: u8,
158
159    registry: registry::Registry,
160
161    pulse_check: bool,
162    pulse_check_cron: String,
163
164    #[cfg(feature = "monitor")]
165    monitor: bool,
166    #[cfg(feature = "monitor")]
167    monitor_cron: String,
168
169    requester: product_os_request::ProductOSRequester,
170    client: product_os_request::ProductOSRequestClient,
171
172    #[cfg(feature = "relational_store")]
173    relational_store: Arc<product_os_store::ProductOSRelationalStore>,
174    key_value_store: Arc<product_os_store::ProductOSKeyValueStore>,
175
176    /// Optional callback invoked when the node loses its presence on the remote registry.
177    /// If `None`, `std::process::exit(8)` is called instead (the legacy behavior).
178    pub on_presence_lost: Option<Arc<dyn Fn() + Send + Sync>>,
179}
180
181impl ProductOSController {
182    /// Creates a new `ProductOSController` instance.
183    ///
184    /// Initializes the command and control system with the provided configuration,
185    /// certificates, and optional data stores.
186    ///
187    /// # Arguments
188    ///
189    /// * `command_control` - Command and control configuration
190    /// * `certificates` - TLS/SSL certificates for secure communication
191    /// * `key_value_store` - Optional key-value store for node registry persistence
192    /// * `relational_store` - Optional relational database store (requires `relational_store` feature)
193    pub fn new(command_control: crate::config::CommandControl, certificates:
194               product_os_security::certificates::Certificates,
195               key_value_store: Option<Arc<product_os_store::ProductOSKeyValueStore>>,
196               #[cfg(feature = "relational_store")] relational_store: Option<Arc<product_os_store::ProductOSRelationalStore>>) -> Self {
197        let key_value_store: Arc<product_os_store::ProductOSKeyValueStore> = key_value_store.unwrap_or_else(|| Arc::new(product_os_store::ProductOSKeyValueStore::try_new(&KeyValueStore {
198            enabled: false,
199            kind: KeyValueKind::Sink,
200            host: "".to_string(),
201            port: 0,
202            secure: false,
203            db_number: 0,
204            db_name: None,
205            username: None,
206            password: None,
207            pool_size: 0,
208            default_limit: 0,
209            default_offset: 0,
210            prefix: None,
211        }).expect("Sink key-value store creation should not fail")));
212
213        #[cfg(feature = "relational_store")]
214        let relational_store: Arc<product_os_store::ProductOSRelationalStore> = relational_store.unwrap_or_else(|| Arc::new(product_os_store::ProductOSRelationalStore::try_new(&RelationalStore {
215            enabled: false,
216            kind: RelationalKind::Sink,
217            host: "".to_string(),
218            port: 0,
219            secure: false,
220            db_name: "".to_string(),
221            username: None,
222            password: None,
223            pool_size: 0,
224            default_limit: 0,
225            default_offset: 0,
226            prefix: None,
227        }).expect("Sink relational store creation should not fail")));
228
229        let registry = registry::Registry::new(&command_control, key_value_store.clone(), certificates.to_owned());
230
231        let mut requester = product_os_request::ProductOSRequester::new();
232        requester.add_header("x-product-os-command", registry.get_me().get_identifier().as_str(), false);
233        let mut request_client = product_os_request::ProductOSRequestClient::new();
234        request_client.build(&requester);
235
236        Self {
237            certificates,
238
239            max_servers: command_control.max_servers,
240
241            pulse_check: command_control.pulse_check,
242            pulse_check_cron: command_control.pulse_check_cron.clone(),
243
244            #[cfg(feature = "monitor")]
245            monitor: command_control.monitor,
246            #[cfg(feature = "monitor")]
247            monitor_cron: command_control.monitor_cron.clone(),
248
249            command_control,
250
251            #[cfg(feature = "relational_store")]
252            relational_store,
253            key_value_store,
254
255            registry,
256
257            requester,
258            client: request_client,
259
260            on_presence_lost: None,
261        }
262    }
263
264    /// Returns a reference to the internal node registry.
265    ///
266    /// The registry contains information about all known nodes in the cluster,
267    /// including their capabilities, services, and features.
268    pub fn get_registry(&self) -> &registry::Registry {
269        &self.registry
270    }
271
272    /// Discovers remote nodes and adds their certificates to the trusted set.
273    pub async fn discover_nodes(&mut self) {
274        self.registry.discover_nodes().await;
275
276        for cert in self.registry.get_nodes_raw_certificates(0, true) {
277            self.requester.add_trusted_certificate_der(cert);
278        }
279
280        self.client.build(&self.requester);
281    }
282
283    /// Returns the maximum number of servers this controller manages.
284    pub fn get_max_servers(&self) -> u8 {
285        self.max_servers
286    }
287
288    /// Inserts or updates a node in the local registry.
289    pub fn upsert_node_local(&mut self, identifier: String, node: Node) {
290        self.registry.upsert_node_local(identifier, node);
291    }
292
293    /// Returns the TLS certificate chain as a vector of byte slices.
294    pub fn get_certificates(&self) -> Vec<&[u8]> {
295        self.certificates.certificates.iter().map(|cert| cert.as_slice()).collect()
296    }
297
298    /// Returns the TLS private key as a byte slice.
299    pub fn get_private_key(&self) -> &[u8] {
300        self.certificates.private.as_slice()
301    }
302
303    /// Returns the full certificates and private key structure.
304    pub fn get_certificates_and_private_key(&self) -> product_os_security::certificates::Certificates {
305        self.certificates.to_owned()
306    }
307
308    /// Validates that the given certificate matches one of the controller's certificates.
309    pub fn validate_certificate(&self, certificate: &[u8]) -> bool {
310        tracing::trace!("Checking certificate stored {:?} vs given {:?}", self.certificates.certificates, certificate);
311
312        for cert in self.get_certificates() {
313            if cert.eq(certificate) { return true; }
314        }
315
316        false
317    }
318
319    /// Validates a verification tag against the stored key for the given identifier.
320    pub fn validate_verify_tag<T>(&self, query: Option<&BTreeMap<String, String>>, payload: Option<T>,
321                                  headers: Option<&BTreeMap<String, String>>, verify_identifier: &str, verify_tag: &str) -> bool
322    where T: product_os_security::AsByteVector
323    {
324        tracing::trace!("Checking verification provided {} from {}", verify_tag, verify_identifier);
325        let key = self.registry.get_key(verify_identifier);
326        tracing::trace!("Checking verification with key {:?}", key);
327
328        match key {
329            Some(verify_key) => {
330                product_os_security::verify_auth_request(query, false, payload, headers,
331                                                       &["x-product-os-verify", "x-product-os-command", "x-product-os-control"],
332                                                       verify_tag, Some(verify_key.as_slice()))
333            },
334            None => false
335        }
336    }
337
338    /// Authenticates a command and control request using the verify identifier and tag.
339    ///
340    /// Returns `Ok(true)` if authentication succeeds, or an error if the keys are invalid.
341    pub fn authenticate_command_control<T>(&self, query: Option<&BTreeMap<String, String>>, payload: Option<T>, headers: Option<&BTreeMap<String, String>>,
342                                        verify_identifier: Option<&str>, verify_tag: Option<&str>) -> Result<bool, authentication::CommandControlAuthenticateError>
343    where T: product_os_security::AsByteVector
344    {
345        tracing::trace!("Verify identifier {:?} and tag {:?} verify result", &verify_identifier, &verify_tag);
346        if verify_identifier.is_some() && verify_tag.is_some() && self.validate_verify_tag(query, payload, headers, verify_identifier.unwrap(), verify_tag.unwrap()) {
347            Ok(true)
348        }
349        else {
350            Err(authentication::CommandControlAuthenticateError {
351                error: authentication::CommandControlAuthenticateErrorState::KeyError("One of the keys provided was not valid".to_string())
352            })
353        }
354    }
355
356    /// Searches for a node matching the query and prepares a command to send to it.
357    pub fn search_and_prepare_command(&self, query: BTreeMap<&str, &str>, module: String, instruction: String, data: Option<serde_json::Value>)  -> Result<crate::commands::Command, product_os_request::ProductOSRequestError> {
358        let matching_node = self.registry.pick_node(query);
359        let client = &self.client;
360
361        match matching_node {
362            Some(node) => {
363                match self.registry.get_key(node.get_identifier().as_str()) {
364                    None => Err(product_os_request::ProductOSRequestError::Error("No key found for matching node".to_string())),
365                    Some(key) => Ok(commands::Command {
366                        requester: client.clone(),
367                        #[allow(deprecated)]
368                        node_url: node.get_address().to_owned(),
369                        verify_key: key,
370                        module,
371                        instruction,
372                        data
373                    })
374                }
375            },
376            None => Err(product_os_request::ProductOSRequestError::Error("No matching node found".to_string()))
377        }
378    }
379
380    /// Finds a node with a matching manager key/kind and prepares a command.
381    pub fn find_and_prepare_command(&self, key: &str, manager: &str, instruction: String, data: Option<serde_json::Value>) -> Result<commands::Command, product_os_request::ProductOSRequestError> {
382        let query = BTreeMap::from([
383            ("manager.key", key),
384            ("manager.kind", manager),
385            ("manager.enabled", ENABLED_STR)
386        ]);
387
388        self.search_and_prepare_command(query, manager.to_string(), instruction, data)
389    }
390
391    /// Finds a node with a matching capability and prepares a command.
392    pub fn find_any_and_prepare_command(&self, capability: &str, manager: &str, instruction: &str, data: Option<serde_json::Value>) -> Result<commands::Command, product_os_request::ProductOSRequestError> {
393        let query = BTreeMap::from([
394            ("capability", capability),
395            ("manager.enabled", ENABLED_STR)
396        ]);
397
398        self.search_and_prepare_command(query, manager.to_string(), instruction.to_string(), data)
399    }
400
401    /// Finds a node with a matching kind and prepares a command.
402    ///
403    /// Note: despite the `async` signature, this method performs no asynchronous work.
404    /// A non-async version is available as `find_kind_and_prepare_command_sync`.
405    #[deprecated(since = "0.0.28", note = "Use find_kind_and_prepare_command_sync instead; this method is not actually async")]
406    pub async fn find_kind_and_prepare_command(&self, kind: &str, manager: &str, instruction: &str, data: Option<serde_json::Value>) -> Result<commands::Command, product_os_request::ProductOSRequestError> {
407        self.find_kind_and_prepare_command_sync(kind, manager, instruction, data)
408    }
409
410    /// Finds a node with a matching kind and prepares a command (non-async).
411    pub fn find_kind_and_prepare_command_sync(&self, kind: &str, manager: &str, instruction: &str, data: Option<serde_json::Value>) -> Result<commands::Command, product_os_request::ProductOSRequestError> {
412        let query = BTreeMap::from([
413            ("kind", kind),
414            ("manager.enabled", ENABLED_STR)
415        ]);
416
417        self.search_and_prepare_command(query, manager.to_string(), instruction.to_string(), data)
418    }
419
420    /// Searches for a node matching the query and prepares an HTTP request to send to it.
421    pub fn search_and_prepare_ask(&self, query: BTreeMap<&str, &str>, path: &str, data: Option<serde_json::Value>, headers: BTreeMap<String, String>,
422                                params: BTreeMap<String, String>, method: Method) -> Result<commands::Ask, product_os_request::ProductOSRequestError> {
423        let matching_node = self.registry.pick_node(query);
424        let client = &self.client;
425        match matching_node {
426            Some(node) => {
427                match self.registry.get_key(node.get_identifier().as_str()) {
428                    None => Err(product_os_request::ProductOSRequestError::Error("No key found for matching node".to_string())),
429                    Some(key) => Ok(commands::Ask {
430                        requester: client.clone(),
431                        #[allow(deprecated)]
432                        node_url: node.get_address().to_owned(),
433                        verify_key: key,
434                        path: path.to_string(),
435                        data,
436                        headers,
437                        params,
438                        method
439                    })
440                }
441
442            },
443            None => Err(product_os_request::ProductOSRequestError::Error("No matching node found".to_string()))
444        }
445    }
446
447    /// Finds a node with the specified feature and sends an HTTP request to it.
448    pub async fn find_feature_and_ask(&self, feature: &str, path: &str, data: &Option<serde_json::Value>, headers: &BTreeMap<String, String>,
449                                params: &BTreeMap<String, String>, method: &Method) -> Result<ProductOSResponse<product_os_request::BodyBytes>, product_os_request::ProductOSRequestError> {
450        let matching_node = self.registry.pick_node_for_feature(feature);
451        let client = &self.client;
452        match matching_node {
453            Some(node) => {
454                match self.registry.get_key(node.get_identifier().as_str()) {
455                    None => Err(product_os_request::ProductOSRequestError::Error("No key found for matching node".to_string())),
456                    Some(key) => commands::ask_node(client, node, key.as_slice(), path, data, headers, params, method).await
457                }
458
459            },
460            None => Err(product_os_request::ProductOSRequestError::Error("No matching node found".to_string()))
461        }
462    }
463
464    /// Finds a node with the specified feature and prepares an HTTP request to send to it.
465    pub fn find_feature_and_prepare_ask(&self, feature: &str, path: &str, data: Option<serde_json::Value>, headers: BTreeMap<String, String>,
466                                      params: BTreeMap<String, String>, method: Method) -> Result<commands::Ask, product_os_request::ProductOSRequestError> {
467        let matching_node = self.registry.pick_node_for_feature(feature);
468        let client = &self.client;
469        match matching_node {
470            Some(node) => {
471                match self.registry.get_key(node.get_identifier().as_str()) {
472                    None => Err(product_os_request::ProductOSRequestError::Error("No key found for matching node".to_string())),
473                    Some(key) => Ok(commands::Ask {
474                        requester: client.clone(),
475                        #[allow(deprecated)]
476                        node_url: node.get_address().to_owned(),
477                        verify_key: key,
478                        path: path.to_string(),
479                        data,
480                        headers,
481                        params,
482                        method
483                    })
484                }
485
486            },
487            None => Err(product_os_request::ProductOSRequestError::Error("No matching node found".to_string()))
488        }
489    }
490
491    /// Returns a clone of the command and control configuration.
492    pub fn get_configuration(&self) -> crate::config::CommandControl {
493        self.command_control.clone()
494    }
495
496    /// Returns the cryptographic key associated with the given node identifier.
497    pub fn get_key(&self, identifier: &str) -> Option<Vec<u8>> {
498        self.registry.get_key(identifier)
499    }
500
501    /// Creates a new Diffie-Hellman key session and returns the session ID and public key.
502    pub fn create_key_session(&mut self) -> (String, [u8; 32]) {
503        self.registry.create_key_session()
504    }
505
506    /// Generates a shared key from a Diffie-Hellman exchange session.
507    pub fn generate_key(&mut self, session_identifier: &str, remote_public_key: &[u8], association: String, remote_session_identifier: Option<String>) {
508        self.registry.generate_key(session_identifier, remote_public_key, association, remote_session_identifier);
509    }
510
511    /// Returns the relational store (requires `relational_store` feature).
512    #[cfg(feature = "relational_store")]
513    pub fn get_relational_store(&mut self) -> Arc<product_os_store::ProductOSRelationalStore> {
514        self.relational_store.clone()
515    }
516
517    /// Returns the key-value store.
518    pub fn get_key_value_store(&mut self) -> Arc<product_os_store::ProductOSKeyValueStore> {
519        self.key_value_store.clone()
520    }
521
522    /// Registers a feature on the local node and updates the router.
523    pub async fn add_feature(&mut self, feature: Arc<dyn product_os_capabilities::Feature>, base_path: String, router: &mut product_os_router::ProductOSRouter) {
524        self.registry.add_feature(feature, base_path, router).await;
525    }
526
527    /// Registers a mutable feature on the local node and updates the router.
528    pub async fn add_feature_mut(&mut self, feature: Arc<Mutex<dyn product_os_capabilities::Feature>>, base_path: String, router: &mut product_os_router::ProductOSRouter) {
529        self.registry.add_feature_mut(feature, base_path, router).await;
530    }
531
532    /// Removes a feature from the local node.
533    pub async fn remove_feature(&mut self, identifier: &str) {
534        self.registry.remove_feature(identifier).await;
535    }
536
537    /// Registers a service on the local node.
538    pub async fn add_service(&mut self, service: Arc<dyn product_os_capabilities::Service>) {
539        self.registry.add_service(service).await;
540    }
541
542    /// Registers a mutable service on the local node.
543    pub async fn add_service_mut(&mut self, service: Arc<Mutex<dyn product_os_capabilities::Service>>) {
544        self.registry.add_service_mut(service).await;
545    }
546
547    /// Sets the active status of a service on the local node.
548    pub async fn set_service_active(&mut self, identifier: String, status: bool) {
549        self.registry.set_service_active(identifier, status).await;
550    }
551
552    /// Removes a service from the local node.
553    pub async fn remove_service(&mut self, identifier: &str) {
554        self.registry.remove_service(identifier).await;
555    }
556
557    /// Removes all inactive services matching the query.
558    pub async fn remove_inactive_services(&mut self, query: BTreeMap<&str, &str>) {
559        self.registry.remove_inactive_services(query).await;
560    }
561
562    /// Starts all registered services on the local node.
563    pub async fn start_services(&mut self) -> Result<(), ()> {
564        self.registry.start_services().await
565    }
566
567    /// Starts the service with the given identifier.
568    pub async fn start_service(&mut self, identifier: &str) -> Result<(), ()> {
569        self.registry.start_service(identifier).await
570    }
571
572    /// Stops the service with the given identifier.
573    pub async fn stop_service(&mut self, identifier: &str) -> Result<(), ()> {
574        self.registry.stop_service(identifier).await
575    }
576}
577
578
579
580
581/// Runs a single pulse check cycle, verifying node health and connectivity.
582///
583/// Acquires a lock on the controller, checks the local node's
584/// presence on the remote registry (when `distributed` feature is enabled),
585/// pings remote nodes, and handles failures.
586///
587/// If the node loses presence and no `on_presence_lost` callback is set,
588/// `std::process::exit(8)` is called.
589#[allow(clippy::await_holding_lock)]
590pub async fn pulse_run(controller_unlocked: Arc<Mutex<ProductOSController>>) {
591    tracing::info!("Starting pulse run...");
592    let controller_locked = controller_unlocked.try_lock_for(core::time::Duration::new(10, 0));
593    match controller_locked {
594        #[allow(unused_mut)]
595        Some(mut controller) => {
596            #[cfg(feature = "distributed")]
597            controller.discover_nodes().await;
598
599            #[cfg(feature = "distributed")]
600            let self_identifier = controller.registry.get_me().get_identifier();
601            #[cfg(feature = "distributed")]
602            #[allow(deprecated)]
603            let control_url = controller.registry.get_me().get_address();
604
605            #[cfg(feature = "distributed")]
606            let nodes = controller.registry.get_nodes_endpoints(0, true);
607
608            #[cfg(feature = "distributed")] {
609                match controller.registry.check_me_remote().await {
610                    Some(_) => { controller.registry.update_me_status(true); },
611                    None => {
612                        let alive = controller.registry.update_me_status(false);
613
614                        if !alive {
615                            tracing::error!("Terminating server due to lost presence on remote registry");
616                            match &controller.on_presence_lost {
617                                Some(callback) => callback(),
618                                None => std::process::exit(8),
619                            }
620                        };
621                    }
622                }
623            }
624
625            // Check status of services
626            let services = controller.get_registry().get_me().get_services().list();
627            for (_, service) in services {
628                let _ = service.status().await;
629            }
630
631            #[cfg(feature = "distributed")]
632            let client = controller.client.clone();
633
634            // Explicitly drop controller
635            std::mem::drop(controller);
636
637            #[cfg(feature = "distributed")]
638            for (identifier, (url, key)) in nodes {
639                if url != control_url {
640                    match key {
641                        Some(verify_key) => {
642                            match commands::command(&client, url.clone(), verify_key, "status", "ping", None).await {
643                                Ok(response) => {
644                                    let Some(status) = response.try_status() else {
645                                        tracing::error!("Failed to get status from response for {}", url);
646                                        continue;
647                                    };
648
649                                    match status {
650                                        product_os_request::StatusCode::UNAUTHORIZED => {
651                                            let text = {
652                                                let controller_locked = controller_unlocked.try_lock_for(core::time::Duration::new(10, 0));
653                                                match controller_locked {
654                                                    Some(controller) => {
655                                                        controller.client.text(response).await.unwrap_or_else(|e| {
656                                                            tracing::error!("Failed to parse response text: {:?}", e);
657                                                            String::new()
658                                                        })
659                                                    }
660                                                    None => String::new()
661                                                }
662                                            };
663
664                                            let auth: authentication::CommandControlAuthenticateError = match serde_json::from_str(text.as_str()) {
665                                                Ok(auth_error) => auth_error,
666                                                Err(_) => authentication::CommandControlAuthenticateError { error: authentication::CommandControlAuthenticateErrorState::None }
667                                            };
668
669                                            tracing::error!("Error object auth {:?}", auth);
670
671                                            match auth.error {
672                                                authentication::CommandControlAuthenticateErrorState::KeyError(_) => {
673                                                    tracing::info!("Attempting to purge remote node - keys don't match {}", identifier);
674
675                                                    let controller_locked = controller_unlocked.try_lock_for(core::time::Duration::new(10, 0));
676                                                    match controller_locked {
677                                                        Some(mut controller) => {
678                                                            controller.registry.remove_node(identifier.as_str()).await;
679                                                        }
680                                                        None => tracing::error!("Failed to lock controller")
681                                                    }
682                                                },
683                                                authentication::CommandControlAuthenticateErrorState::None => ()
684                                            };
685                                        },
686                                        product_os_request::StatusCode::OK => {
687                                            let body = {
688                                                let controller_locked = controller_unlocked.try_lock_for(core::time::Duration::new(10, 0));
689                                                match controller_locked {
690                                                    Some(controller) => {
691                                                        controller.client.bytes(response).await.unwrap_or_else(|e| {
692                                                            tracing::error!("Failed to parse response bytes: {:?}", e);
693                                                            Bytes::new()
694                                                        })
695                                                    }
696                                                    None => Bytes::new()
697                                                }
698                                            };
699
700                                            tracing::info!("Response received from {}: {} {:?}", url, status, body);
701                                        }
702                                        _ => {
703                                            let body = {
704                                                let controller_locked = controller_unlocked.try_lock_for(core::time::Duration::new(10, 0));
705                                                match controller_locked {
706                                                    Some(controller) => {
707                                                        controller.client.bytes(response).await.unwrap_or_else(|e| {
708                                                            tracing::error!("Failed to parse error response bytes: {:?}", e);
709                                                            Bytes::new()
710                                                        })
711                                                    }
712                                                    None => Bytes::new()
713                                                }
714                                            };
715                                            tracing::error!("Error response received from {}: {} {:?}", url, status, body);
716                                            tracing::info!("Attempting to purge remote node - keys don't match {}", identifier);
717
718                                            let controller_locked = controller_unlocked.try_lock_for(core::time::Duration::new(10, 0));
719                                            match controller_locked {
720                                                Some(mut controller) => {
721                                                    controller.registry.update_pulse_status(identifier.as_str(), false).await;
722                                                }
723                                                None => tracing::error!("Failed to lock controller")
724                                            }
725                                        }
726                                    }
727                                },
728                                Err(e) => {
729                                    tracing::error!("Error encountered {:?} from {}", e, url);
730
731                                    let controller_locked = controller_unlocked.try_lock_for(core::time::Duration::new(10, 0));
732                                    match controller_locked {
733                                        Some(mut controller) => {
734                                            controller.registry.update_pulse_status(identifier.as_str(), false).await;
735                                        }
736                                        None => tracing::error!("Failed to lock controller")
737                                    }
738                                }
739                            }
740                        },
741                        None => ()
742                    }
743                } else if self_identifier != identifier {
744                    tracing::info!("Attempting to purge self duplicate node - identifier mismatch {}", identifier);
745                    let controller_locked = controller_unlocked.try_lock_for(core::time::Duration::new(10, 0));
746                    match controller_locked {
747                        Some(mut controller) => {
748                            controller.registry.remove_node(identifier.as_str()).await;
749                        }
750                        None => tracing::error!("Failed to lock controller")
751                    }
752                }
753            }
754
755
756            tracing::info!("Finished pulse run...");
757        }
758        None => tracing::error!("Failed to lock controller")
759    }
760}
761
762
763/// Runs the controller lifecycle: self-trust, discovery, announcements, and periodic tasks.
764///
765/// Sets up pulse check and monitoring timers using the provided async executor.
766#[allow(clippy::await_holding_lock)]
767pub async fn run_controller<X, E>(controller_mutex: Arc<Mutex<ProductOSController>>, executor: Arc<E>)
768where
769    E: product_os_async_executor::Executor<X> + product_os_async_executor::ExecutorPerform<X> + product_os_async_executor::Timer
770{
771    let controller_unlocked = controller_mutex.clone();
772    let controller_locked = controller_unlocked.try_lock_for(core::time::Duration::new(10, 0));
773    match controller_locked {
774        Some(mut controller) => {
775
776            #[cfg(feature = "distributed")] {
777                authentication::perform_self_trust(&mut controller);
778
779                controller.registry.update_me().await;
780                tracing::info!("Added self...");
781
782                tracing::info!("Command and Control registered for {}", controller.registry.get_me().get_identifier());
783
784                controller.discover_nodes().await;
785                perform_announce(&mut controller).await;
786            }
787
788            // Start up services
789            if controller.command_control.auto_start_services {
790                match controller.start_services().await {
791                    Ok(_) => {
792                        tracing::info!("Command Control services started");
793                    },
794                    Err(_) => {
795                        tracing::error!("Command Control services failed to start");
796                    }
797                }
798            }
799
800            let pulse_check = controller.pulse_check;
801            let pulse_check_cron = cron::Schedule::from_str(controller.pulse_check_cron.as_str()).unwrap();
802            let pulse_check_next = pulse_check_cron.upcoming(chrono::Utc).next().unwrap();
803            let pulse_check_following = pulse_check_cron.upcoming(chrono::Utc).nth(1).unwrap();
804
805            #[cfg(feature = "monitor")]
806            let monitor = controller.monitor;
807            #[cfg(feature = "monitor")]
808            let monitor_cron = cron::Schedule::from_str(controller.monitor_cron.as_str()).unwrap();
809            #[cfg(feature = "monitor")]
810            let _monitor_next = monitor_cron.upcoming(chrono::Utc).next().unwrap();
811            #[cfg(feature = "monitor")]
812            let _monitor_following = monitor_cron.upcoming(chrono::Utc).nth(1).unwrap();
813
814            // Explicitly drop controller
815            std::mem::drop(controller);
816
817            if pulse_check {
818                drop(E::spawn_from_executor(executor.as_ref(), async move {
819                    tracing::info!("Pulse check service starting...");
820
821                    let start_duration = match u32::try_from(match pulse_check_next.signed_duration_since(chrono::Utc::now()).to_std() {
822                        Ok(d) => d,
823                        Err(_) => Duration::new(1, 0)
824                    }.as_millis()) {
825                        Ok(u) => u,
826                        Err(_) => panic!("Period defined is too large for timer")
827                    };
828                    let interval_duration = match u32::try_from(match pulse_check_following.signed_duration_since(pulse_check_next).to_std() {
829                        Ok(d) => d,
830                        Err(_) => Duration::new(1, 0)
831                    }.as_millis()) {
832                        Ok(u) => u,
833                        Err(_) => panic!("Period defined is too large for timer")
834                    };
835
836                    let mut delay = E::once(start_duration).await;
837                    let mut interval = E::interval(interval_duration).await;
838
839                    delay.tick().await;
840                    loop {
841                        interval.tick().await;
842                        tracing::debug!("Pulse check service running");
843
844                        pulse_run(controller_mutex.clone()).await;
845                    }
846                }));
847            }
848
849            #[cfg(feature = "monitor")]
850            if monitor {
851                drop(E::spawn_from_executor(executor.as_ref(), async move {
852                    tracing::info!("Monitor service starting...");
853
854                    let start_duration = match u32::try_from(match pulse_check_next.signed_duration_since(chrono::Utc::now()).to_std() {
855                        Ok(d) => d,
856                        Err(_) => Duration::new(1, 0)
857                    }.as_millis()) {
858                        Ok(u) => u,
859                        Err(_) => panic!("Period defined is too large for timer")
860                    };
861                    let interval_duration = match u32::try_from(match pulse_check_following.signed_duration_since(pulse_check_next).to_std() {
862                        Ok(d) => d,
863                        Err(_) => Duration::new(1, 0)
864                    }.as_millis()) {
865                        Ok(u) => u,
866                        Err(_) => panic!("Period defined is too large for timer")
867                    };
868
869                    let mut delay = E::once(start_duration).await;
870                    let mut interval = E::interval(interval_duration).await;
871
872                    delay.tick().await;
873                    loop {
874                        interval.tick().await;
875                        tracing::debug!("Monitor service running");
876
877                        let _ = product_os_monitoring::process_statistics_info(None);
878                    }
879                }));
880            }
881        }
882        None => tracing::error!("Failed to lock controller")
883    }
884}
885
886
887/// Announces the local node to all other nodes in the cluster.
888///
889/// Performs key exchange and sends the local node's information to each
890/// remote node in the registry.
891#[cfg(feature = "distributed")]
892pub async fn perform_announce(controller: &mut MutexGuard<'_, ProductOSController>) {
893    tracing::info!("Announce searching for nodes...");
894
895    authentication::perform_key_exchange(controller).await;
896
897    let self_identifier = controller.registry.get_me().get_identifier();
898    #[allow(deprecated)]
899    let control_url = controller.registry.get_me().get_address();
900
901    let nodes = controller.registry.get_nodes_endpoints(0, true);
902
903    tracing::info!("Starting announce...");
904    tracing::info!("Nodes data {:?}", nodes);
905
906    for (identifier, (url, key)) in nodes {
907        if url != control_url {
908            match key {
909                Some(verify_key) => {
910                    tracing::info!("Announcing {}: {}", identifier, url);
911
912                    match commands::command(&controller.client, url.clone(), verify_key, "status", "announce", Some(serde_json::value::to_value(controller.get_registry().get_me()).unwrap())).await {
913                        Ok(response) => {
914                            let Some(status) = response.try_status() else {
915                                tracing::error!("Failed to get status from announce response for {}", url);
916                                continue;
917                            };
918
919                            match status {
920                                product_os_request::StatusCode::UNAUTHORIZED => {
921                                    let text = controller.client.text(response).await.unwrap();
922                                    let auth: authentication::CommandControlAuthenticateError = match serde_json::from_str(text.as_str()) {
923                                        Ok(auth_error) => auth_error,
924                                        Err(_) => authentication::CommandControlAuthenticateError { error: authentication::CommandControlAuthenticateErrorState::None }
925                                    };
926
927                                    tracing::error!("Error object auth {:?}", auth);
928
929                                    match auth.error {
930                                        authentication::CommandControlAuthenticateErrorState::KeyError(_) => {
931                                            tracing::info!("Error from remote node - keys don't match {}", identifier);
932                                        },
933                                        authentication::CommandControlAuthenticateErrorState::None => ()
934                                    };
935                                },
936                                product_os_request::StatusCode::OK => {
937                                    let body = controller.client.bytes(response).await.unwrap();
938                                    tracing::info!("Response received from {}: {} {:?}", url, status, body);
939                                }
940                                _ => {
941                                    let body = controller.client.bytes(response).await.unwrap();
942                                    tracing::error!("Error response received from {}: {} {:?}", url, status, body);
943                                }
944                            }
945                        },
946                        Err(e) => {
947                            tracing::error!("Error encountered {:?} from {}", e, url);
948                        }
949                    };
950                },
951                None => ()
952            }
953        }
954        else {
955            if identifier != self_identifier {
956                tracing::info!("Attempting to purge self duplicate node - identifier mismatch {}", identifier);
957                controller.registry.remove_node(identifier.as_str()).await;
958            }
959        }
960    }
961
962    tracing::info!("Finished announcing...");
963}