|
|
|
|
@@ -8,7 +8,10 @@ use crate::{
|
|
|
|
|
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
|
|
|
|
inventory::Inventory,
|
|
|
|
|
score::Score,
|
|
|
|
|
topology::{self, Bond, HostNetworkConfig, SlaveInterface, Switch, Topology},
|
|
|
|
|
topology::{
|
|
|
|
|
self, Bond, HostNetworkConfig, PortChannel, SlaveInterface, Switch, SwitchNetworkConfig,
|
|
|
|
|
Topology,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
|
|
|
@@ -56,20 +59,34 @@ impl<T: Topology + Switch> Interpret<T> for HostNetworkConfigurationInterpret {
|
|
|
|
|
_inventory: &Inventory,
|
|
|
|
|
topology: &T,
|
|
|
|
|
) -> Result<Outcome, InterpretError> {
|
|
|
|
|
let host = self.score.hosts.first().unwrap();
|
|
|
|
|
let mac_addresses = host.get_mac_address();
|
|
|
|
|
let mac_address = mac_addresses.first().unwrap();
|
|
|
|
|
let host_network_config = HostNetworkConfig {
|
|
|
|
|
bond: Bond {
|
|
|
|
|
interfaces: vec![SlaveInterface {
|
|
|
|
|
mac_address: *mac_address,
|
|
|
|
|
}],
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
for host in &self.score.hosts {
|
|
|
|
|
let mut interfaces = vec![];
|
|
|
|
|
let mut ports = vec![];
|
|
|
|
|
|
|
|
|
|
for mac_address in host.get_mac_address() {
|
|
|
|
|
if let Some(port) = topology.get_port_for_mac_address(&mac_address).await {
|
|
|
|
|
interfaces.push(SlaveInterface { mac_address });
|
|
|
|
|
ports.push(port);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let _ = topology
|
|
|
|
|
.configure_host_network(host, host_network_config)
|
|
|
|
|
.configure_host_network(
|
|
|
|
|
host,
|
|
|
|
|
HostNetworkConfig {
|
|
|
|
|
bond: Bond { interfaces },
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
let _ = topology
|
|
|
|
|
.configure_switch_network(
|
|
|
|
|
host,
|
|
|
|
|
SwitchNetworkConfig {
|
|
|
|
|
port_channel: PortChannel { ports },
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// foreach hosts
|
|
|
|
|
// foreach mac addresses
|
|
|
|
|
@@ -78,17 +95,12 @@ impl<T: Topology + Switch> Interpret<T> for HostNetworkConfigurationInterpret {
|
|
|
|
|
// create bond for all valid addresses (port found)
|
|
|
|
|
// apply network to host first, then switch (to avoid losing hosts that are already connected)
|
|
|
|
|
// topology.configure_host_network(host, config) <--- will create bonds
|
|
|
|
|
// topology.configure_switch_network(port, config) <--- will create port channels
|
|
|
|
|
// topology.configure_switch_network(host, config) <--- will create port channels
|
|
|
|
|
|
|
|
|
|
Ok(Outcome::success("".into()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct PortMapping {
|
|
|
|
|
port: String,
|
|
|
|
|
mac_address: MacAddress,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use assertor::*;
|
|
|
|
|
@@ -98,8 +110,8 @@ mod tests {
|
|
|
|
|
use crate::{
|
|
|
|
|
hardware::HostCategory,
|
|
|
|
|
topology::{
|
|
|
|
|
Bond, HostNetworkConfig, PreparationError, PreparationOutcome, SlaveInterface,
|
|
|
|
|
SwitchError,
|
|
|
|
|
Bond, HostNetworkConfig, PortChannel, PreparationError, PreparationOutcome,
|
|
|
|
|
SlaveInterface, SwitchError, SwitchNetworkConfig,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
use std::{
|
|
|
|
|
@@ -111,15 +123,22 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
|
pub static ref HOST_ID: Id = Id::from_str("host-1").unwrap();
|
|
|
|
|
pub static ref INTERFACE: MacAddress =
|
|
|
|
|
MacAddress::try_from("00:11:22:33:44:55".to_string()).unwrap();
|
|
|
|
|
pub static ref ANOTHER_HOST_ID: Id = Id::from_str("host-2").unwrap();
|
|
|
|
|
pub static ref EXISTING_INTERFACE: MacAddress =
|
|
|
|
|
MacAddress::try_from("00:00:00:00:00:00".to_string()).unwrap();
|
|
|
|
|
pub static ref ANOTHER_EXISTING_INTERFACE: MacAddress =
|
|
|
|
|
MacAddress::try_from("42:42:42:42:42:42".to_string()).unwrap();
|
|
|
|
|
pub static ref UNKNOWN_INTERFACE: MacAddress =
|
|
|
|
|
MacAddress::try_from("99:99:99:99:99:99".to_string()).unwrap();
|
|
|
|
|
pub static ref PORT: String = "1/0/42".into();
|
|
|
|
|
pub static ref ANOTHER_PORT: String = "2/0/42".into();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn one_host_one_mac_address_should_create_bond_with_one_interface() {
|
|
|
|
|
let host = given_host(&HOST_ID, *INTERFACE);
|
|
|
|
|
async fn host_with_one_mac_address_should_create_bond_with_one_interface() {
|
|
|
|
|
let host = given_host(&HOST_ID, vec![*EXISTING_INTERFACE]);
|
|
|
|
|
let score = given_score(vec![host]);
|
|
|
|
|
let topology = SwitchWithPortTopology::new();
|
|
|
|
|
let topology = TopologyWithSwitch::new();
|
|
|
|
|
|
|
|
|
|
let _ = score.interpret(&Inventory::empty(), &topology).await;
|
|
|
|
|
|
|
|
|
|
@@ -129,19 +148,171 @@ mod tests {
|
|
|
|
|
HostNetworkConfig {
|
|
|
|
|
bond: Bond {
|
|
|
|
|
interfaces: vec![SlaveInterface {
|
|
|
|
|
mac_address: *INTERFACE,
|
|
|
|
|
mac_address: *EXISTING_INTERFACE,
|
|
|
|
|
}],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
)]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn given_host(id: &Id, mac_address: MacAddress) -> PhysicalHost {
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn host_with_one_mac_address_should_create_port_channel_with_one_port() {
|
|
|
|
|
let host = given_host(&HOST_ID, vec![*EXISTING_INTERFACE]);
|
|
|
|
|
let score = given_score(vec![host]);
|
|
|
|
|
let topology = TopologyWithSwitch::new();
|
|
|
|
|
|
|
|
|
|
let _ = score.interpret(&Inventory::empty(), &topology).await;
|
|
|
|
|
|
|
|
|
|
let configured_switch_networks = topology.configured_switch_networks.lock().unwrap();
|
|
|
|
|
assert_that!(*configured_switch_networks).contains_exactly(vec![(
|
|
|
|
|
HOST_ID.clone(),
|
|
|
|
|
SwitchNetworkConfig {
|
|
|
|
|
port_channel: PortChannel {
|
|
|
|
|
ports: vec![PORT.clone()],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
)]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn host_with_multiple_mac_addresses_should_create_one_bond_with_all_interfaces() {
|
|
|
|
|
let score = given_score(vec![given_host(
|
|
|
|
|
&HOST_ID,
|
|
|
|
|
vec![*EXISTING_INTERFACE, *ANOTHER_EXISTING_INTERFACE],
|
|
|
|
|
)]);
|
|
|
|
|
let topology = TopologyWithSwitch::new();
|
|
|
|
|
|
|
|
|
|
let _ = score.interpret(&Inventory::empty(), &topology).await;
|
|
|
|
|
|
|
|
|
|
let configured_host_networks = topology.configured_host_networks.lock().unwrap();
|
|
|
|
|
assert_that!(*configured_host_networks).contains_exactly(vec![(
|
|
|
|
|
HOST_ID.clone(),
|
|
|
|
|
HostNetworkConfig {
|
|
|
|
|
bond: Bond {
|
|
|
|
|
interfaces: vec![
|
|
|
|
|
SlaveInterface {
|
|
|
|
|
mac_address: *EXISTING_INTERFACE,
|
|
|
|
|
},
|
|
|
|
|
SlaveInterface {
|
|
|
|
|
mac_address: *ANOTHER_EXISTING_INTERFACE,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
)]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn multiple_hosts_should_create_one_bond_per_host() {
|
|
|
|
|
let score = given_score(vec![
|
|
|
|
|
given_host(&HOST_ID, vec![*EXISTING_INTERFACE]),
|
|
|
|
|
given_host(&ANOTHER_HOST_ID, vec![*ANOTHER_EXISTING_INTERFACE]),
|
|
|
|
|
]);
|
|
|
|
|
let topology = TopologyWithSwitch::new();
|
|
|
|
|
|
|
|
|
|
let _ = score.interpret(&Inventory::empty(), &topology).await;
|
|
|
|
|
|
|
|
|
|
let configured_host_networks = topology.configured_host_networks.lock().unwrap();
|
|
|
|
|
assert_that!(*configured_host_networks).contains_exactly(vec![
|
|
|
|
|
(
|
|
|
|
|
HOST_ID.clone(),
|
|
|
|
|
HostNetworkConfig {
|
|
|
|
|
bond: Bond {
|
|
|
|
|
interfaces: vec![SlaveInterface {
|
|
|
|
|
mac_address: *EXISTING_INTERFACE,
|
|
|
|
|
}],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
ANOTHER_HOST_ID.clone(),
|
|
|
|
|
HostNetworkConfig {
|
|
|
|
|
bond: Bond {
|
|
|
|
|
interfaces: vec![SlaveInterface {
|
|
|
|
|
mac_address: *ANOTHER_EXISTING_INTERFACE,
|
|
|
|
|
}],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn multiple_hosts_should_create_one_port_channel_per_host() {
|
|
|
|
|
let score = given_score(vec![
|
|
|
|
|
given_host(&HOST_ID, vec![*EXISTING_INTERFACE]),
|
|
|
|
|
given_host(&ANOTHER_HOST_ID, vec![*ANOTHER_EXISTING_INTERFACE]),
|
|
|
|
|
]);
|
|
|
|
|
let topology = TopologyWithSwitch::new();
|
|
|
|
|
|
|
|
|
|
let _ = score.interpret(&Inventory::empty(), &topology).await;
|
|
|
|
|
|
|
|
|
|
let configured_switch_networks = topology.configured_switch_networks.lock().unwrap();
|
|
|
|
|
assert_that!(*configured_switch_networks).contains_exactly(vec![
|
|
|
|
|
(
|
|
|
|
|
HOST_ID.clone(),
|
|
|
|
|
SwitchNetworkConfig {
|
|
|
|
|
port_channel: PortChannel {
|
|
|
|
|
ports: vec![PORT.clone()],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
ANOTHER_HOST_ID.clone(),
|
|
|
|
|
SwitchNetworkConfig {
|
|
|
|
|
port_channel: PortChannel {
|
|
|
|
|
ports: vec![ANOTHER_PORT.clone()],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn port_not_found_for_mac_address_should_not_configure_interface() {
|
|
|
|
|
// FIXME: Should it still configure an empty bond/port channel?
|
|
|
|
|
let score = given_score(vec![given_host(&HOST_ID, vec![*UNKNOWN_INTERFACE])]);
|
|
|
|
|
let topology = TopologyWithSwitch::new_port_not_found();
|
|
|
|
|
|
|
|
|
|
let _ = score.interpret(&Inventory::empty(), &topology).await;
|
|
|
|
|
|
|
|
|
|
let configured_host_networks = topology.configured_host_networks.lock().unwrap();
|
|
|
|
|
assert_that!(*configured_host_networks).contains_exactly(vec![(
|
|
|
|
|
HOST_ID.clone(),
|
|
|
|
|
HostNetworkConfig {
|
|
|
|
|
bond: Bond { interfaces: vec![] },
|
|
|
|
|
},
|
|
|
|
|
)]);
|
|
|
|
|
let configured_switch_networks = topology.configured_switch_networks.lock().unwrap();
|
|
|
|
|
assert_that!(*configured_switch_networks).contains_exactly(vec![(
|
|
|
|
|
HOST_ID.clone(),
|
|
|
|
|
SwitchNetworkConfig {
|
|
|
|
|
port_channel: PortChannel { ports: vec![] },
|
|
|
|
|
},
|
|
|
|
|
)]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn given_score(hosts: Vec<PhysicalHost>) -> HostNetworkConfigurationScore {
|
|
|
|
|
HostNetworkConfigurationScore { hosts }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn given_host(id: &Id, mac_addresses: Vec<MacAddress>) -> PhysicalHost {
|
|
|
|
|
let network = mac_addresses.iter().map(|m| given_interface(*m)).collect();
|
|
|
|
|
|
|
|
|
|
PhysicalHost {
|
|
|
|
|
id: id.clone(),
|
|
|
|
|
category: HostCategory::Server,
|
|
|
|
|
network: vec![NetworkInterface {
|
|
|
|
|
name: "interface-1".into(),
|
|
|
|
|
network,
|
|
|
|
|
storage: vec![],
|
|
|
|
|
labels: vec![],
|
|
|
|
|
memory_modules: vec![],
|
|
|
|
|
cpus: vec![],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn given_interface(mac_address: MacAddress) -> NetworkInterface {
|
|
|
|
|
NetworkInterface {
|
|
|
|
|
name: format!("{mac_address}"),
|
|
|
|
|
mac_address,
|
|
|
|
|
speed_mbps: None,
|
|
|
|
|
is_up: true,
|
|
|
|
|
@@ -150,32 +321,35 @@ mod tests {
|
|
|
|
|
ipv6_addresses: vec![],
|
|
|
|
|
driver: "driver".into(),
|
|
|
|
|
firmware_version: None,
|
|
|
|
|
}],
|
|
|
|
|
storage: vec![],
|
|
|
|
|
labels: vec![],
|
|
|
|
|
memory_modules: vec![],
|
|
|
|
|
cpus: vec![],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn given_score(hosts: Vec<PhysicalHost>) -> HostNetworkConfigurationScore {
|
|
|
|
|
HostNetworkConfigurationScore { hosts }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct SwitchWithPortTopology {
|
|
|
|
|
struct TopologyWithSwitch {
|
|
|
|
|
available_ports: Vec<String>,
|
|
|
|
|
configured_host_networks: Arc<Mutex<Vec<(Id, HostNetworkConfig)>>>,
|
|
|
|
|
configured_switch_networks: Arc<Mutex<Vec<(Id, SwitchNetworkConfig)>>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SwitchWithPortTopology {
|
|
|
|
|
impl TopologyWithSwitch {
|
|
|
|
|
fn new() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
available_ports: vec![PORT.clone(), ANOTHER_PORT.clone()],
|
|
|
|
|
configured_host_networks: Arc::new(Mutex::new(vec![])),
|
|
|
|
|
configured_switch_networks: Arc::new(Mutex::new(vec![])),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn new_port_not_found() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
available_ports: vec![],
|
|
|
|
|
configured_host_networks: Arc::new(Mutex::new(vec![])),
|
|
|
|
|
configured_switch_networks: Arc::new(Mutex::new(vec![])),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
|
impl Topology for SwitchWithPortTopology {
|
|
|
|
|
impl Topology for TopologyWithSwitch {
|
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
|
"SwitchWithPortTopology"
|
|
|
|
|
}
|
|
|
|
|
@@ -186,9 +360,18 @@ mod tests {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
|
impl Switch for SwitchWithPortTopology {
|
|
|
|
|
async fn get_port_for_mac_address(&self, mac_address: &MacAddress) -> Option<String> {
|
|
|
|
|
Some("1/0/42".into())
|
|
|
|
|
impl Switch for TopologyWithSwitch {
|
|
|
|
|
async fn get_port_for_mac_address(&self, _mac_address: &MacAddress) -> Option<String> {
|
|
|
|
|
if self.available_ports.is_empty() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Some(
|
|
|
|
|
self.available_ports
|
|
|
|
|
.get(self.configured_host_networks.lock().unwrap().len() % 2)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.clone(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn configure_host_network(
|
|
|
|
|
@@ -201,5 +384,16 @@ mod tests {
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn configure_switch_network(
|
|
|
|
|
&self,
|
|
|
|
|
host: &PhysicalHost,
|
|
|
|
|
config: SwitchNetworkConfig,
|
|
|
|
|
) -> Result<(), SwitchError> {
|
|
|
|
|
let mut configured_switch_networks = self.configured_switch_networks.lock().unwrap();
|
|
|
|
|
configured_switch_networks.push((host.id.clone(), config.clone()));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|