From 7cfdf2fb49a5c7cef6adff7f4230b445f7298425 Mon Sep 17 00:00:00 2001 From: Ian Letourneau Date: Mon, 3 Nov 2025 17:20:27 -0500 Subject: [PATCH] Small example to show how to compose components (capabilities) in a Topology to simplify reusing existing components for different topologies --- .gitignore | 1 + Cargo.lock | 72 +++++++++++++++++++++++++++++++ Cargo.toml | 7 +++ src/components/mod.rs | 2 + src/components/network_manager.rs | 7 +++ src/components/switch.rs | 7 +++ src/core/mod.rs | 1 + src/core/topology.rs | 3 ++ src/infra/brocade.rs | 15 +++++++ src/infra/k8s_client.rs | 1 + src/infra/mod.rs | 4 ++ src/infra/nmcli.rs | 15 +++++++ src/infra/openshift.rs | 22 ++++++++++ src/main.rs | 38 ++++++++++++++++ src/topologies/k8s.rs | 35 +++++++++++++++ src/topologies/k8s_anywhere.rs | 60 ++++++++++++++++++++++++++ src/topologies/mod.rs | 2 + 17 files changed, 292 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/components/mod.rs create mode 100644 src/components/network_manager.rs create mode 100644 src/components/switch.rs create mode 100644 src/core/mod.rs create mode 100644 src/core/topology.rs create mode 100644 src/infra/brocade.rs create mode 100644 src/infra/k8s_client.rs create mode 100644 src/infra/mod.rs create mode 100644 src/infra/nmcli.rs create mode 100644 src/infra/openshift.rs create mode 100644 src/main.rs create mode 100644 src/topologies/k8s.rs create mode 100644 src/topologies/k8s_anywhere.rs create mode 100644 src/topologies/mod.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5847602 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,72 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ambassador" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e68de4cdc6006162265d0957edb4a860fe4e711b1dc17a5746fd95f952f08285" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "harmony-composable" +version = "0.1.0" +dependencies = [ + "ambassador", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5487cc5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "harmony-composable" +version = "0.1.0" +edition = "2024" + +[dependencies] +ambassador = "0.4.2" diff --git a/src/components/mod.rs b/src/components/mod.rs new file mode 100644 index 0000000..1e683cf --- /dev/null +++ b/src/components/mod.rs @@ -0,0 +1,2 @@ +pub mod network_manager; +pub mod switch; diff --git a/src/components/network_manager.rs b/src/components/network_manager.rs new file mode 100644 index 0000000..ab1f9bb --- /dev/null +++ b/src/components/network_manager.rs @@ -0,0 +1,7 @@ +use ambassador::delegatable_trait; + +#[delegatable_trait] +pub trait NetworkManager { + fn ensure_network_manager_installed(&self) -> Result<(), String>; + fn configure_bond(&self, bond: String) -> Result<(), String>; +} diff --git a/src/components/switch.rs b/src/components/switch.rs new file mode 100644 index 0000000..b53d967 --- /dev/null +++ b/src/components/switch.rs @@ -0,0 +1,7 @@ +use ambassador::delegatable_trait; + +#[delegatable_trait] +pub trait Switch { + fn setup_switch(&self) -> Result<(), String>; + fn create_port_channel(&self, port_channel: String) -> Result<(), String>; +} diff --git a/src/core/mod.rs b/src/core/mod.rs new file mode 100644 index 0000000..494bd26 --- /dev/null +++ b/src/core/mod.rs @@ -0,0 +1 @@ +pub mod topology; diff --git a/src/core/topology.rs b/src/core/topology.rs new file mode 100644 index 0000000..64a055f --- /dev/null +++ b/src/core/topology.rs @@ -0,0 +1,3 @@ +pub trait Topology { + fn name(&self) -> String; +} diff --git a/src/infra/brocade.rs b/src/infra/brocade.rs new file mode 100644 index 0000000..20006a8 --- /dev/null +++ b/src/infra/brocade.rs @@ -0,0 +1,15 @@ +use crate::components::switch::Switch; + +pub struct BrocadeSwitch {} + +impl Switch for BrocadeSwitch { + fn setup_switch(&self) -> Result<(), String> { + println!("BrocadeSwitch: Installing..."); + Ok(()) + } + + fn create_port_channel(&self, port_channel: String) -> Result<(), String> { + println!("BrocadeSwitch: Creating port channel {}", port_channel); + Ok(()) + } +} diff --git a/src/infra/k8s_client.rs b/src/infra/k8s_client.rs new file mode 100644 index 0000000..4e75131 --- /dev/null +++ b/src/infra/k8s_client.rs @@ -0,0 +1 @@ +pub struct Client {} diff --git a/src/infra/mod.rs b/src/infra/mod.rs new file mode 100644 index 0000000..d6fb334 --- /dev/null +++ b/src/infra/mod.rs @@ -0,0 +1,4 @@ +pub mod brocade; +pub mod k8s_client; +pub mod nmcli; +pub mod openshift; diff --git a/src/infra/nmcli.rs b/src/infra/nmcli.rs new file mode 100644 index 0000000..d1afd9f --- /dev/null +++ b/src/infra/nmcli.rs @@ -0,0 +1,15 @@ +use crate::components::network_manager::NetworkManager; + +pub struct NmcliNetworkManager {} + +impl NetworkManager for NmcliNetworkManager { + fn ensure_network_manager_installed(&self) -> Result<(), String> { + println!("Nmcli: Installing..."); + Ok(()) + } + + fn configure_bond(&self, bond: String) -> Result<(), String> { + println!("Nmcli: Configuring bond {}", bond); + Ok(()) + } +} diff --git a/src/infra/openshift.rs b/src/infra/openshift.rs new file mode 100644 index 0000000..7c9fd40 --- /dev/null +++ b/src/infra/openshift.rs @@ -0,0 +1,22 @@ +use std::sync::Arc; + +use crate::{components::network_manager::NetworkManager, infra::k8s_client}; + +pub struct OpenShiftNmStateNetworkManager { + pub k8s_client: Arc, +} + +impl NetworkManager for OpenShiftNmStateNetworkManager { + fn ensure_network_manager_installed(&self) -> Result<(), String> { + println!("OpenShift NM State: Installing..."); + + // self.k8s_client.apply(...) + + Ok(()) + } + + fn configure_bond(&self, bond: String) -> Result<(), String> { + println!("OpenShift NM State: Configuring bond {}", bond); + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..db02c86 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,38 @@ +use std::sync::Arc; + +use crate::{ + components::{network_manager::NetworkManager, switch::Switch}, + core::topology::Topology, + infra::brocade::BrocadeSwitch, + topologies::{k8s::NetworkManagerProvider, k8s_anywhere::K8sAnywhereTopology}, +}; + +mod components; +mod core; +mod infra; +mod topologies; + +fn configure_port_channel(topology: &T) -> Result<(), String> { + println!( + "--- Running configure_port_channel for: {} ---", + topology.name() + ); + topology.setup_switch()?; + topology.create_port_channel("pc-123".to_string())?; + Ok(()) +} + +fn configure_bond(topology: &T) -> Result<(), String> { + println!("--- Running configure_bond for: {} ---", topology.name()); + topology.ensure_network_manager_installed()?; + topology.configure_bond("bond0".to_string())?; + Ok(()) +} + +fn main() { + let topology = K8sAnywhereTopology::new(NetworkManagerProvider {}, Arc::new(BrocadeSwitch {})); + + // Simulate the execution of a Score's Interpret + configure_bond(&topology).unwrap(); + configure_port_channel(&topology).unwrap(); +} diff --git a/src/topologies/k8s.rs b/src/topologies/k8s.rs new file mode 100644 index 0000000..6bbedba --- /dev/null +++ b/src/topologies/k8s.rs @@ -0,0 +1,35 @@ +use std::sync::Arc; + +use crate::{ + components::network_manager::NetworkManager, + core::topology::Topology, + infra::{k8s_client, nmcli::NmcliNetworkManager, openshift::OpenShiftNmStateNetworkManager}, +}; + +pub enum K8sSource { + K3dFamily, + OpenShiftFamily, +} + +pub struct K8sState { + pub source: K8sSource, +} + +pub trait K8sClient { + fn k8s_state(&self) -> K8sState; + fn k8s_client(&self) -> Arc; +} + +pub struct NetworkManagerProvider {} + +impl NetworkManagerProvider { + pub fn provide(&self, topology: &T) -> Arc { + match topology.k8s_state().source { + K8sSource::K3dFamily => Arc::new(NmcliNetworkManager {}), + K8sSource::OpenShiftFamily => { + let k8s_client = topology.k8s_client().clone(); + Arc::new(OpenShiftNmStateNetworkManager { k8s_client }) + } + } + } +} diff --git a/src/topologies/k8s_anywhere.rs b/src/topologies/k8s_anywhere.rs new file mode 100644 index 0000000..2888594 --- /dev/null +++ b/src/topologies/k8s_anywhere.rs @@ -0,0 +1,60 @@ +use std::{cell::OnceCell, sync::Arc}; + +use ambassador::delegate_to_methods; + +use crate::{ + components::{ + network_manager::{NetworkManager, ambassador_impl_NetworkManager}, + switch::{Switch, ambassador_impl_Switch}, + }, + core::topology::Topology, + infra::k8s_client, + topologies::k8s::{K8sClient, K8sSource, K8sState, NetworkManagerProvider}, +}; + +pub struct K8sAnywhereTopology { + network_manager_provider: NetworkManagerProvider, + network_manager: OnceCell>, + switch: Arc, +} + +#[delegate_to_methods] +#[delegate(NetworkManager, target_ref = "network_manager")] +#[delegate(Switch, target_ref = "switch")] +impl K8sAnywhereTopology { + pub fn new(network_manager_provider: NetworkManagerProvider, switch: Arc) -> Self { + Self { + network_manager_provider, + network_manager: OnceCell::new(), + switch, + } + } + + fn network_manager(&self) -> &dyn NetworkManager { + self.network_manager + .get_or_init(|| self.network_manager_provider.provide(self)) + .as_ref() + } + + fn switch(&self) -> &dyn Switch { + self.switch.as_ref() + } +} + +impl Topology for K8sAnywhereTopology { + fn name(&self) -> String { + "K8sAnywhere".to_string() + } +} + +impl K8sClient for K8sAnywhereTopology { + fn k8s_state(&self) -> K8sState { + K8sState { + source: K8sSource::OpenShiftFamily, + } + } + + fn k8s_client(&self) -> Arc { + Arc::new(k8s_client::Client {}) + } +} diff --git a/src/topologies/mod.rs b/src/topologies/mod.rs new file mode 100644 index 0000000..8b85b7e --- /dev/null +++ b/src/topologies/mod.rs @@ -0,0 +1,2 @@ +pub mod k8s; +pub mod k8s_anywhere;