find lowest available bond id
This commit is contained in:
@@ -13,7 +13,7 @@ use kube::{
|
|||||||
use log::debug;
|
use log::debug;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
use crate::modules::okd::crd::nmstate::{self, NodeNetworkConfigurationPolicy, NodeNetworkState};
|
use crate::modules::okd::crd::nmstate::{self, NodeNetworkConfigurationPolicy};
|
||||||
use crate::topology::PxeOptions;
|
use crate::topology::PxeOptions;
|
||||||
use crate::{data::FileContent, modules::okd::crd::nmstate::NMState};
|
use crate::{data::FileContent, modules::okd::crd::nmstate::NMState};
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -27,7 +27,7 @@ use super::{
|
|||||||
Topology, k8s::K8sClient,
|
Topology, k8s::K8sClient,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::{BTreeMap, HashSet};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -157,21 +157,6 @@ impl HAClusterTopology {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_next_bond_id(&self, hostname: &str) -> Result<String, String> {
|
|
||||||
let network_state: Option<nmstate::NodeNetworkState> = self
|
|
||||||
.k8s_client()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.get_resource(hostname, None)
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("Failed to list nodes: {e}"))?;
|
|
||||||
|
|
||||||
println!("HELLLOOOO NETWORK STATE: {network_state:#?}");
|
|
||||||
|
|
||||||
let bond_id = 42; // FIXME: Find a better way to declare the bond id
|
|
||||||
Ok(format!("bond{bond_id}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn configure_bond(&self, config: &HostNetworkConfig) -> Result<(), SwitchError> {
|
async fn configure_bond(&self, config: &HostNetworkConfig) -> Result<(), SwitchError> {
|
||||||
self.ensure_nmstate_operator_installed()
|
self.ensure_nmstate_operator_installed()
|
||||||
.await
|
.await
|
||||||
@@ -228,7 +213,7 @@ impl HAClusterTopology {
|
|||||||
interfaces.push(nmstate::Interface {
|
interfaces.push(nmstate::Interface {
|
||||||
name: interface_name.clone(),
|
name: interface_name.clone(),
|
||||||
description: Some(format!("Member of bond {bond_name}")),
|
description: Some(format!("Member of bond {bond_name}")),
|
||||||
r#type: "ethernet".to_string(),
|
r#type: nmstate::InterfaceType::Ethernet,
|
||||||
state: "up".to_string(),
|
state: "up".to_string(),
|
||||||
mtu: Some(switch_port.interface.mtu),
|
mtu: Some(switch_port.interface.mtu),
|
||||||
mac_address: Some(switch_port.interface.mac_address.to_string()),
|
mac_address: Some(switch_port.interface.mac_address.to_string()),
|
||||||
@@ -258,7 +243,7 @@ impl HAClusterTopology {
|
|||||||
interfaces.push(nmstate::Interface {
|
interfaces.push(nmstate::Interface {
|
||||||
name: bond_name.to_string(),
|
name: bond_name.to_string(),
|
||||||
description: Some(format!("Network bond for host {host}")),
|
description: Some(format!("Network bond for host {host}")),
|
||||||
r#type: "bond".to_string(),
|
r#type: nmstate::InterfaceType::Bond,
|
||||||
state: "up".to_string(),
|
state: "up".to_string(),
|
||||||
copy_mac_from,
|
copy_mac_from,
|
||||||
ipv4: Some(nmstate::IpStackSpec {
|
ipv4: Some(nmstate::IpStackSpec {
|
||||||
@@ -325,6 +310,37 @@ impl HAClusterTopology {
|
|||||||
.cloned()
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_next_bond_id(&self, hostname: &str) -> Result<String, String> {
|
||||||
|
let network_state: Option<nmstate::NodeNetworkState> = self
|
||||||
|
.k8s_client()
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.get_resource(hostname, None)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to list nodes: {e}"))?;
|
||||||
|
|
||||||
|
let interfaces = vec![];
|
||||||
|
let existing_bonds: Vec<&nmstate::Interface> = network_state
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|network_state| network_state.status.current_state.as_ref())
|
||||||
|
.map_or(&interfaces, |current_state| ¤t_state.interfaces)
|
||||||
|
.iter()
|
||||||
|
.filter(|i| i.r#type == nmstate::InterfaceType::Bond && i.link_aggregation.is_some())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let used_ids: HashSet<u32> = existing_bonds
|
||||||
|
.iter()
|
||||||
|
.filter_map(|i| {
|
||||||
|
i.name
|
||||||
|
.strip_prefix("bond")
|
||||||
|
.and_then(|id| id.parse::<u32>().ok())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let next_id = (0..).find(|id| !used_ids.contains(id)).unwrap();
|
||||||
|
Ok(format!("bond{next_id}"))
|
||||||
|
}
|
||||||
|
|
||||||
async fn configure_port_channel(&self, config: &HostNetworkConfig) -> Result<(), SwitchError> {
|
async fn configure_port_channel(&self, config: &HostNetworkConfig) -> Result<(), SwitchError> {
|
||||||
debug!("Configuring port channel: {config:#?}");
|
debug!("Configuring port channel: {config:#?}");
|
||||||
let switch_ports = config.switch_ports.iter().map(|s| s.port.clone()).collect();
|
let switch_ports = config.switch_ports.iter().map(|s| s.port.clone()).collect();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use kube::CustomResource;
|
use k8s_openapi::{ClusterResourceScope, Resource};
|
||||||
|
use kube::{CustomResource, api::ObjectMeta};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
@@ -47,7 +48,7 @@ pub struct ProbeDns {
|
|||||||
group = "nmstate.io",
|
group = "nmstate.io",
|
||||||
version = "v1",
|
version = "v1",
|
||||||
kind = "NodeNetworkConfigurationPolicy",
|
kind = "NodeNetworkConfigurationPolicy",
|
||||||
namespaced
|
namespaced = false
|
||||||
)]
|
)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct NodeNetworkConfigurationPolicySpec {
|
pub struct NodeNetworkConfigurationPolicySpec {
|
||||||
@@ -56,18 +57,38 @@ pub struct NodeNetworkConfigurationPolicySpec {
|
|||||||
pub desired_state: NetworkState,
|
pub desired_state: NetworkState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(CustomResource, Deserialize, Serialize, Clone, Debug, JsonSchema)]
|
// Currently, kube-rs derive doesn't support resources without a `spec` field, so we have
|
||||||
#[kube(
|
// to implement it ourselves.
|
||||||
group = "nmstate.io",
|
//
|
||||||
version = "v1beta1",
|
// Ref:
|
||||||
kind = "NodeNetworkState",
|
// - https://github.com/kube-rs/kube/issues/1763
|
||||||
plural = "nodenetworkstates",
|
// - https://github.com/kube-rs/kube/discussions/1762
|
||||||
namespaced = false,
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
status = "NodeNetworkStateStatus"
|
|
||||||
)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct NodeNetworkStateSpec {
|
pub struct NodeNetworkState {
|
||||||
// This resource is read-only and has no spec.
|
metadata: ObjectMeta,
|
||||||
|
pub status: NodeNetworkStateStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resource for NodeNetworkState {
|
||||||
|
const API_VERSION: &'static str = "nmstate.io/v1beta1";
|
||||||
|
const GROUP: &'static str = "nmstate.io";
|
||||||
|
const VERSION: &'static str = "v1beta1";
|
||||||
|
const KIND: &'static str = "NodeNetworkState";
|
||||||
|
const URL_PATH_SEGMENT: &'static str = "nodenetworkstates";
|
||||||
|
type Scope = ClusterResourceScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl k8s_openapi::Metadata for NodeNetworkState {
|
||||||
|
type Ty = ObjectMeta;
|
||||||
|
|
||||||
|
fn metadata(&self) -> &Self::Ty {
|
||||||
|
&self.metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
fn metadata_mut(&mut self) -> &mut Self::Ty {
|
||||||
|
&mut self.metadata
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug, JsonSchema)]
|
#[derive(Deserialize, Serialize, Clone, Debug, JsonSchema)]
|
||||||
@@ -243,7 +264,7 @@ pub struct Interface {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub r#type: String,
|
pub r#type: InterfaceType,
|
||||||
pub state: String,
|
pub state: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub mac_address: Option<String>,
|
pub mac_address: Option<String>,
|
||||||
@@ -273,14 +294,11 @@ pub struct Interface {
|
|||||||
pub infiniband: Option<InfinibandSpec>,
|
pub infiniband: Option<InfinibandSpec>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub linux_bridge: Option<LinuxBridgeSpec>,
|
pub linux_bridge: Option<LinuxBridgeSpec>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[serde(alias = "bridge")]
|
#[serde(alias = "bridge")]
|
||||||
pub ovs_bridge: Option<OvsBridgeSpec>,
|
pub ovs_bridge: Option<OvsBridgeSpec>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub ethtool: Option<Value>,
|
pub ethtool: Option<Value>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub accept_all_mac_addresses: Option<bool>,
|
pub accept_all_mac_addresses: Option<bool>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -307,6 +325,53 @@ pub struct Interface {
|
|||||||
pub patch: Option<PatchState>,
|
pub patch: Option<PatchState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, JsonSchema)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum InterfaceType {
|
||||||
|
#[serde(rename = "unknown")]
|
||||||
|
Unknown,
|
||||||
|
#[serde(rename = "dummy")]
|
||||||
|
Dummy,
|
||||||
|
#[serde(rename = "loopback")]
|
||||||
|
Loopback,
|
||||||
|
#[serde(rename = "linux-bridge")]
|
||||||
|
LinuxBridge,
|
||||||
|
#[serde(rename = "ovs-bridge")]
|
||||||
|
OvsBridge,
|
||||||
|
#[serde(rename = "ovs-interface")]
|
||||||
|
OvsInterface,
|
||||||
|
#[serde(rename = "bond")]
|
||||||
|
Bond,
|
||||||
|
#[serde(rename = "ipvlan")]
|
||||||
|
IpVlan,
|
||||||
|
#[serde(rename = "vlan")]
|
||||||
|
Vlan,
|
||||||
|
#[serde(rename = "vxlan")]
|
||||||
|
Vxlan,
|
||||||
|
#[serde(rename = "mac-vlan")]
|
||||||
|
Macvlan,
|
||||||
|
#[serde(rename = "mac-vtap")]
|
||||||
|
Macvtap,
|
||||||
|
#[serde(rename = "ethernet")]
|
||||||
|
Ethernet,
|
||||||
|
#[serde(rename = "infiniband")]
|
||||||
|
Infiniband,
|
||||||
|
#[serde(rename = "vrf")]
|
||||||
|
Vrf,
|
||||||
|
#[serde(rename = "veth")]
|
||||||
|
Veth,
|
||||||
|
#[serde(rename = "ipsec")]
|
||||||
|
Ipsec,
|
||||||
|
#[serde(rename = "hsr")]
|
||||||
|
Hrs,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for InterfaceType {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Loopback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug, Default, JsonSchema)]
|
#[derive(Deserialize, Serialize, Clone, Debug, Default, JsonSchema)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct IpStackSpec {
|
pub struct IpStackSpec {
|
||||||
|
|||||||
Reference in New Issue
Block a user