rufish 0.4.0

An asynchronous Redfish client library for BMC/server management in Rust.
Documentation
//! Multi-host batch operations and connection pool for managing fleets of BMCs.

use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Semaphore;

use crate::error::Result;
use crate::RedfishClient;

/// Result of a batch operation on a single host.
#[derive(Debug)]
pub struct HostResult<T> {
    pub host: String,
    pub result: Result<T>,
}

/// A pool of Redfish clients for managing multiple BMCs concurrently.
pub struct RedfishPool {
    clients: HashMap<String, RedfishClient>,
    concurrency: Arc<Semaphore>,
}

impl RedfishPool {
    /// Create a new pool with a concurrency limit.
    pub fn new(max_concurrent: usize) -> Self {
        Self {
            clients: HashMap::new(),
            concurrency: Arc::new(Semaphore::new(max_concurrent)),
        }
    }

    /// Add a BMC to the pool.
    pub fn add(&mut self, host: &str, username: &str, password: &str) -> Result<()> {
        let client = RedfishClient::new(host, username, password)?;
        self.clients.insert(host.to_string(), client);
        Ok(())
    }

    /// Add a pre-configured client to the pool.
    pub fn add_client(&mut self, host: &str, client: RedfishClient) {
        self.clients.insert(host.to_string(), client);
    }

    /// Remove a BMC from the pool.
    pub fn remove(&mut self, host: &str) {
        self.clients.remove(host);
    }

    /// List all hosts in the pool.
    pub fn hosts(&self) -> Vec<&str> {
        self.clients.keys().map(|s| s.as_str()).collect()
    }

    /// Login to all BMCs concurrently.
    pub async fn login_all(&mut self) -> Vec<HostResult<()>> {
        let hosts: Vec<String> = self.clients.keys().cloned().collect();
        let mut results = Vec::new();

        // We need mutable access, so do them sequentially with semaphore for rate limiting
        for host in hosts {
            let _permit = self.concurrency.acquire().await.unwrap();
            let result = if let Some(client) = self.clients.get_mut(&host) {
                client.login().await
            } else {
                continue;
            };
            results.push(HostResult { host, result });
        }
        results
    }

    /// Logout from all BMCs.
    pub async fn logout_all(&mut self) {
        for client in self.clients.values_mut() {
            let _ = client.logout().await;
        }
    }

    /// Get a reference to a specific client.
    pub fn get(&self, host: &str) -> Option<&RedfishClient> {
        self.clients.get(host)
    }

    /// Get a mutable reference to a specific client.
    pub fn get_mut(&mut self, host: &str) -> Option<&mut RedfishClient> {
        self.clients.get_mut(host)
    }

    /// Execute an async operation on all hosts concurrently.
    ///
    /// ```ignore
    /// let results = pool.for_each(|client| async move {
    ///     client.power_on("1").await
    /// }).await;
    /// ```
    pub async fn for_each<F, Fut, T>(&self, op: F) -> Vec<HostResult<T>>
    where
        F: Fn(&RedfishClient) -> Fut,
        Fut: std::future::Future<Output = Result<T>>,
    {
        let mut results = Vec::new();
        for (host, client) in &self.clients {
            let _permit = self.concurrency.acquire().await.unwrap();
            let result = op(client).await;
            results.push(HostResult { host: host.clone(), result });
        }
        results
    }
}