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
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -1262,22 +1262,6 @@ dependencies = [
|
||||
"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]]
|
||||
name = "brotli"
|
||||
version = "8.0.2"
|
||||
|
||||
132
harmony/src/modules/okd/crd/machine_config.rs
Normal file
132
harmony/src/modules/okd/crd/machine_config.rs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod ingresses_config;
|
||||
pub mod kubelet_config;
|
||||
pub mod machine_config;
|
||||
pub mod nmstate;
|
||||
pub mod route;
|
||||
|
||||
47
harmony/src/modules/okd/disable_dad_score.rs
Normal file
47
harmony/src/modules/okd/disable_dad_score.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -25,5 +25,7 @@ pub use bootstrap_05_sanity_check::*;
|
||||
pub use bootstrap_06_installation_report::*;
|
||||
pub use bootstrap_persist_network_bond::*;
|
||||
pub mod crd;
|
||||
pub mod disable_dad_score;
|
||||
pub mod host_network;
|
||||
pub mod node_file_score;
|
||||
pub mod system_reserved_score;
|
||||
|
||||
49
harmony/src/modules/okd/node_file_score.rs
Normal file
49
harmony/src/modules/okd/node_file_score.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,9 @@
|
||||
"models": {
|
||||
"qwen3-coder-next:q4_K_M": {
|
||||
"name": "qwen3-coder-next:q4_K_M"
|
||||
},
|
||||
"gemma4:31b": {
|
||||
"name": "Gemma 4 31b"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user