Small example to show how to compose components (capabilities) in a Topology to simplify reusing existing components for different topologies

This commit is contained in:
Ian Letourneau
2025-11-03 17:20:27 -05:00
commit 7cfdf2fb49
17 changed files with 292 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

72
Cargo.lock generated Normal file
View File

@@ -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"

7
Cargo.toml Normal file
View File

@@ -0,0 +1,7 @@
[package]
name = "harmony-composable"
version = "0.1.0"
edition = "2024"
[dependencies]
ambassador = "0.4.2"

2
src/components/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod network_manager;
pub mod switch;

View File

@@ -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>;
}

7
src/components/switch.rs Normal file
View File

@@ -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>;
}

1
src/core/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod topology;

3
src/core/topology.rs Normal file
View File

@@ -0,0 +1,3 @@
pub trait Topology {
fn name(&self) -> String;
}

15
src/infra/brocade.rs Normal file
View File

@@ -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(())
}
}

1
src/infra/k8s_client.rs Normal file
View File

@@ -0,0 +1 @@
pub struct Client {}

4
src/infra/mod.rs Normal file
View File

@@ -0,0 +1,4 @@
pub mod brocade;
pub mod k8s_client;
pub mod nmcli;
pub mod openshift;

15
src/infra/nmcli.rs Normal file
View File

@@ -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(())
}
}

22
src/infra/openshift.rs Normal file
View File

@@ -0,0 +1,22 @@
use std::sync::Arc;
use crate::{components::network_manager::NetworkManager, infra::k8s_client};
pub struct OpenShiftNmStateNetworkManager {
pub k8s_client: Arc<k8s_client::Client>,
}
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(())
}
}

38
src/main.rs Normal file
View File

@@ -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<T: Topology + Switch>(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<T: Topology + NetworkManager>(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();
}

35
src/topologies/k8s.rs Normal file
View File

@@ -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<k8s_client::Client>;
}
pub struct NetworkManagerProvider {}
impl NetworkManagerProvider {
pub fn provide<T: Topology + K8sClient>(&self, topology: &T) -> Arc<dyn NetworkManager> {
match topology.k8s_state().source {
K8sSource::K3dFamily => Arc::new(NmcliNetworkManager {}),
K8sSource::OpenShiftFamily => {
let k8s_client = topology.k8s_client().clone();
Arc::new(OpenShiftNmStateNetworkManager { k8s_client })
}
}
}
}

View File

@@ -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<Arc<dyn NetworkManager>>,
switch: Arc<dyn Switch>,
}
#[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<dyn Switch>) -> 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<k8s_client::Client> {
Arc::new(k8s_client::Client {})
}
}

2
src/topologies/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod k8s;
pub mod k8s_anywhere;