feat: Disable ipv4 address conflict detection score. This is useful when setting up bonds as the wrong mac may get a dhcp offer and then the system will perceive it as a conflict when it sets up the bond correctly #263

Open
johnride wants to merge 2 commits from feat/disableDadScore into master
7 changed files with 234 additions and 16 deletions

16
Cargo.lock generated
View File

@@ -1262,22 +1262,6 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "brocade-switch-oricom-configuration"
version = "0.1.0"
dependencies = [
"async-trait",
"brocade",
"env_logger",
"harmony",
"harmony_cli",
"harmony_macros",
"harmony_types",
"log",
"serde",
"tokio",
]
[[package]] [[package]]
name = "brotli" name = "brotli"
version = "8.0.2" version = "8.0.2"

View File

@@ -0,0 +1,132 @@
use std::collections::BTreeMap;
use base64::prelude::*;
use kube::{api::ObjectMeta, CustomResource};
use serde::{Deserialize, Serialize};
#[derive(CustomResource, Deserialize, Serialize, Clone, Debug, Default)]
#[kube(
group = "machineconfiguration.openshift.io",
version = "v1",
kind = "MachineConfig",
plural = "machineconfigs",
namespaced = false,
schema = "disabled"
)]
#[serde(rename_all = "camelCase")]
pub struct MachineConfigSpec {
#[serde(skip_serializing_if = "Option::is_none")]
pub config: Option<IgnitionConfig>,
}
impl Default for MachineConfig {
fn default() -> Self {
Self {
metadata: ObjectMeta::default(),
spec: MachineConfigSpec::default(),
}
}
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct IgnitionConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub ignition: Option<Ignition>,
#[serde(skip_serializing_if = "Option::is_none")]
pub storage: Option<Storage>,
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct Ignition {
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct Storage {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub files: Vec<IgnitionFile>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct IgnitionFile {
pub path: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub overwrite: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub contents: Option<IgnitionFileContents>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct IgnitionFileContents {
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub compression: Option<String>,
}
impl MachineConfig {
pub fn with_file(
pool: MachineConfigPoolRole,
resource_name: &str,
path: &str,
content: &str,
mode: Option<u32>,
) -> Self {
let encoded = BASE64_STANDARD.encode(content);
let source = format!("data:text/plain;charset=utf-8;base64,{encoded}");
Self {
metadata: ObjectMeta {
name: Some(format!("{}-{}", pool.label_value(), resource_name)),
labels: Some(pool.labels()),
..Default::default()
},
spec: MachineConfigSpec {
config: Some(IgnitionConfig {
ignition: Some(Ignition {
version: Some("3.2.0".to_string()),
}),
storage: Some(Storage {
files: vec![IgnitionFile {
path: path.to_string(),
mode,
overwrite: Some(true),
contents: Some(IgnitionFileContents {
source: Some(source),
compression: None,
}),
}],
}),
}),
},
}
}
}
#[derive(Debug, Clone, Copy, Serialize)]
pub enum MachineConfigPoolRole {
Master,
Worker,
}
impl MachineConfigPoolRole {
pub fn label_value(&self) -> &'static str {
match self {
Self::Master => "master",
Self::Worker => "worker",
}
}
pub fn labels(&self) -> BTreeMap<String, String> {
let mut labels = BTreeMap::new();
labels.insert(
"machineconfiguration.openshift.io/role".to_string(),
self.label_value().to_string(),
);
labels
}
}

View File

@@ -1,4 +1,5 @@
pub mod ingresses_config; pub mod ingresses_config;
pub mod kubelet_config; pub mod kubelet_config;
pub mod machine_config;
pub mod nmstate; pub mod nmstate;
pub mod route; pub mod route;

View File

@@ -0,0 +1,47 @@
use serde::Serialize;
use crate::{
interpret::Interpret,
modules::{
k8s::resource::K8sResourceScore,
okd::{crd::machine_config::MachineConfigPoolRole, node_file_score::NodeFileScore},
},
score::Score,
topology::{K8sclient, Topology},
};
#[derive(Debug, Clone, Serialize)]
pub struct DisableDadScore {
pub pool: MachineConfigPoolRole,
}
impl Default for DisableDadScore {
fn default() -> Self {
Self {
pool: MachineConfigPoolRole::Worker,
}
}
}
impl<T: Topology + K8sclient> Score<T> for DisableDadScore {
fn name(&self) -> String {
"DisableDadScore".to_string()
}
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
let score = NodeFileScore {
pool: self.pool,
resource_name: "disable-dad".to_string(),
path: "/etc/NetworkManager/conf.d/99-disable-ipv4-dad.conf".to_string(),
content: "# Disable IPv4 Address Conflict Detection (ACD/DAD)\n\
# Workaround for false positive conflict detection on\n\
# 802.3ad LACP bonds where the second member's permanent\n\
# MAC address triggers a spurious duplicate detection.\n\
[connection]\n\
ipv4.dad-timeout=0\n"
.to_string(),
mode: Some(0o644),
};
score.create_interpret()
}
}

View File

@@ -25,5 +25,7 @@ pub use bootstrap_05_sanity_check::*;
pub use bootstrap_06_installation_report::*; pub use bootstrap_06_installation_report::*;
pub use bootstrap_persist_network_bond::*; pub use bootstrap_persist_network_bond::*;
pub mod crd; pub mod crd;
pub mod disable_dad_score;
pub mod host_network; pub mod host_network;
pub mod node_file_score;
pub mod system_reserved_score; pub mod system_reserved_score;

View File

@@ -0,0 +1,49 @@
use serde::Serialize;
use crate::{
interpret::Interpret,
modules::{
k8s::resource::K8sResourceScore,
okd::crd::machine_config::{MachineConfig, MachineConfigPoolRole},
},
score::Score,
topology::{K8sclient, Topology},
};
#[derive(Debug, Clone, Serialize)]
pub struct NodeFileScore {
pub pool: MachineConfigPoolRole,
pub resource_name: String,
pub path: String,
pub content: String,
pub mode: Option<u32>,
}
impl Default for NodeFileScore {
fn default() -> Self {
Self {
pool: MachineConfigPoolRole::Worker,
resource_name: "generic-file".to_string(),
path: "/etc/placeholder".to_string(),
content: "".to_string(),
mode: None,
}
}
}
impl<T: Topology + K8sclient> Score<T> for NodeFileScore {
fn name(&self) -> String {
format!("NodeFileScore({})", self.path)
}
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
let mc = MachineConfig::with_file(
self.pool,
&self.resource_name,
&self.path,
&self.content,
self.mode,
);
K8sResourceScore::single(mc, None).create_interpret()
}
}

View File

@@ -10,6 +10,9 @@
"models": { "models": {
"qwen3-coder-next:q4_K_M": { "qwen3-coder-next:q4_K_M": {
"name": "qwen3-coder-next:q4_K_M" "name": "qwen3-coder-next:q4_K_M"
},
"gemma4:31b": {
"name": "Gemma 4 31b"
} }
} }
}, },