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",
|
"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"
|
||||||
|
|||||||
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 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;
|
||||||
|
|||||||
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_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;
|
||||||
|
|||||||
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": {
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user