diff --git a/Cargo.lock b/Cargo.lock index 4cf88dd..b53845b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/harmony/src/modules/okd/crd/machine_config.rs b/harmony/src/modules/okd/crd/machine_config.rs new file mode 100644 index 0000000..c426a9c --- /dev/null +++ b/harmony/src/modules/okd/crd/machine_config.rs @@ -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, +} + +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, + #[serde(skip_serializing_if = "Option::is_none")] + pub storage: Option, +} + +#[derive(Deserialize, Serialize, Clone, Debug, Default)] +pub struct Ignition { + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, +} + +#[derive(Deserialize, Serialize, Clone, Debug, Default)] +pub struct Storage { + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub files: Vec, +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +pub struct IgnitionFile { + pub path: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub overwrite: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub contents: Option, +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +pub struct IgnitionFileContents { + #[serde(skip_serializing_if = "Option::is_none")] + pub source: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub compression: Option, +} + +impl MachineConfig { + pub fn with_file( + pool: MachineConfigPoolRole, + resource_name: &str, + path: &str, + content: &str, + mode: Option, + ) -> 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 { + let mut labels = BTreeMap::new(); + labels.insert( + "machineconfiguration.openshift.io/role".to_string(), + self.label_value().to_string(), + ); + labels + } +} diff --git a/harmony/src/modules/okd/crd/mod.rs b/harmony/src/modules/okd/crd/mod.rs index dae9c51..f2af923 100644 --- a/harmony/src/modules/okd/crd/mod.rs +++ b/harmony/src/modules/okd/crd/mod.rs @@ -1,4 +1,5 @@ pub mod ingresses_config; pub mod kubelet_config; +pub mod machine_config; pub mod nmstate; pub mod route; diff --git a/harmony/src/modules/okd/disable_dad_score.rs b/harmony/src/modules/okd/disable_dad_score.rs new file mode 100644 index 0000000..dfbf734 --- /dev/null +++ b/harmony/src/modules/okd/disable_dad_score.rs @@ -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 Score for DisableDadScore { + fn name(&self) -> String { + "DisableDadScore".to_string() + } + + fn create_interpret(&self) -> Box> { + 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() + } +} diff --git a/harmony/src/modules/okd/mod.rs b/harmony/src/modules/okd/mod.rs index e1719e9..5fafe15 100644 --- a/harmony/src/modules/okd/mod.rs +++ b/harmony/src/modules/okd/mod.rs @@ -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; diff --git a/harmony/src/modules/okd/node_file_score.rs b/harmony/src/modules/okd/node_file_score.rs new file mode 100644 index 0000000..c1710e3 --- /dev/null +++ b/harmony/src/modules/okd/node_file_score.rs @@ -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, +} + +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 Score for NodeFileScore { + fn name(&self) -> String { + format!("NodeFileScore({})", self.path) + } + + fn create_interpret(&self) -> Box> { + let mc = MachineConfig::with_file( + self.pool, + &self.resource_name, + &self.path, + &self.content, + self.mode, + ); + K8sResourceScore::single(mc, None).create_interpret() + } +} diff --git a/opencode.json b/opencode.json index 536a5bf..81b3ae2 100644 --- a/opencode.json +++ b/opencode.json @@ -10,6 +10,9 @@ "models": { "qwen3-coder-next:q4_K_M": { "name": "qwen3-coder-next:q4_K_M" + }, + "gemma4:31b": { + "name": "Gemma 4 31b" } } },