diff --git a/rust/migrate-wicked/Cargo.lock b/rust/migrate-wicked/Cargo.lock index ce97022113..59726210ba 100644 --- a/rust/migrate-wicked/Cargo.lock +++ b/rust/migrate-wicked/Cargo.lock @@ -1260,6 +1260,7 @@ dependencies = [ "strum", "strum_macros", "tokio", + "uuid", ] [[package]] diff --git a/rust/migrate-wicked/Cargo.toml b/rust/migrate-wicked/Cargo.toml index 16e30705d5..b1c8361b13 100644 --- a/rust/migrate-wicked/Cargo.toml +++ b/rust/migrate-wicked/Cargo.toml @@ -23,6 +23,7 @@ strum_macros = "0.25.2" serde_with = "3.3.0" tokio = { version = "1.33.0", features = ["macros", "rt-multi-thread"] } serde_ignored = "0.1.9" +uuid = { version = "1.3.4", features = ["v4"] } [[bin]] name = "migrate-wicked" diff --git a/rust/migrate-wicked/src/interface.rs b/rust/migrate-wicked/src/interface.rs index ac1673b8d6..84022ad8a5 100644 --- a/rust/migrate-wicked/src/interface.rs +++ b/rust/migrate-wicked/src/interface.rs @@ -2,15 +2,18 @@ use crate::MIGRATION_SETTINGS; use agama_dbus_server::network::model::{ self, IpConfig, IpRoute, Ipv4Method, Ipv6Method, MacAddress, }; +use agama_lib::network::types::BondMode as AgamaBondMode; use cidr::IpInet; -use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, skip_serializing_none}; -use std::{net::IpAddr, str::FromStr}; +use serde::{Deserialize, Deserializer, Serialize}; +use serde_with::{serde_as, skip_serializing_none, DeserializeFromStr, SerializeDisplay}; +use std::{collections::HashMap, net::IpAddr, str::FromStr}; +use strum_macros::{Display, EnumString}; #[derive(Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(default)] pub struct Interface { pub name: String, + pub link: Link, pub ipv4: Ipv4, #[serde(rename = "ipv4-static", skip_serializing_if = "Option::is_none")] pub ipv4_static: Option, @@ -25,10 +28,19 @@ pub struct Interface { pub dummy: Option, #[serde(skip_serializing_if = "Option::is_none")] pub ethernet: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub bond: Option, #[serde(rename = "@origin")] pub origin: String, } +#[derive(Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(default)] +#[skip_serializing_none] +pub struct Link { + pub master: Option, +} + #[derive(Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(default)] pub struct Ipv4 { @@ -79,10 +91,10 @@ pub struct Ipv6Auto { pub enabled: bool, } -#[skip_serializing_none] #[derive(Debug, PartialEq, Default, Serialize, Deserialize)] pub struct Dummy { - pub address: Option, + #[serde(default, skip_serializing_if = "String::is_empty")] + pub address: String, } #[skip_serializing_none] @@ -94,10 +106,327 @@ pub struct Route { pub priority: Option, } +#[derive(Debug, PartialEq, Serialize, Clone, Deserialize)] +pub enum ParentKind { + Bond, +} + +#[derive(Debug, PartialEq, SerializeDisplay, DeserializeFromStr, EnumString, Display)] +#[strum(serialize_all = "kebab_case")] +pub enum FailOverMac { + None, + Active, + Follow, +} + +#[derive(Debug, PartialEq, SerializeDisplay, DeserializeFromStr, EnumString, Display)] +#[strum(serialize_all = "kebab_case")] +pub enum XmitHashPolicy { + Layer2, + Layer23, + Layer34, + Encap23, + Encap34, +} + +#[derive(Debug, PartialEq, SerializeDisplay, DeserializeFromStr, EnumString, Display)] +#[strum(serialize_all = "kebab_case")] +pub enum LacpRate { + Slow, + Fast, +} + +#[derive(Debug, PartialEq, SerializeDisplay, DeserializeFromStr, EnumString, Display)] +#[strum(serialize_all = "kebab_case")] +pub enum AdSelect { + Stable, + Bandwidth, + Count, +} + +#[derive(Debug, PartialEq, SerializeDisplay, DeserializeFromStr, EnumString, Display)] +#[strum(serialize_all = "kebab_case")] +pub enum PrimaryReselect { + Always, + Better, + Failure, +} + +#[derive(Debug, PartialEq, SerializeDisplay, DeserializeFromStr, EnumString, Display)] +#[strum(serialize_all = "kebab_case")] +pub enum WickedBondMode { + BalanceRr = 0, + ActiveBackup, + BalanceXor, + Broadcast, + #[strum(serialize = "802.3ad")] + IEEE8023ad, + BalanceTlb, + BalanceAlb, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[skip_serializing_none] +#[serde(rename_all = "kebab-case")] +pub struct Bond { + pub mode: WickedBondMode, + pub miimon: Option, + pub arpmon: Option, + #[serde(deserialize_with = "unwrap_slaves")] + pub slaves: Vec, + /* only on mode=[802.3ad, balance_xor] */ + pub xmit_hash_policy: Option, + /* only on mode=balance_rr */ + pub packets_per_slave: Option, + /* only on mode=balance_tlb */ + pub tlb_dynamic_lb: Option, + /* only on mode=802.3ad */ + pub lacp_rate: Option, + /* only on mode=802.3ad */ + pub ad_select: Option, + /* only on mode=802.3ad */ + pub ad_user_port_key: Option, + /* only on mode=802.3ad */ + pub ad_actor_sys_prio: Option, + /* only on mode=802.3ad */ + pub ad_actor_system: Option, + /* only on mode=802.3ad */ + pub min_links: Option, + /* only on mode=active-backup */ + pub primary_reselect: Option, + /* only on mode=active-backup */ + pub fail_over_mac: Option, + /* only on mode=active-backup */ + pub num_grat_arp: Option, + /* only on mode=active-backup */ + pub num_unsol_na: Option, + /* only on mode=[balance_tlb|balance_alb] */ + pub lp_interval: Option, + /* only on mode=[balance_tlb|balance_alb|balance_RR|active-backup] */ + pub resend_igmp: Option, + pub all_slaves_active: Option, + #[serde(default, skip_serializing_if = "String::is_empty")] + pub address: String, +} + +impl Bond { + pub fn primary(self: &Bond) -> Option<&String> { + for s in self.slaves.iter() { + if s.primary.unwrap_or(false) { + return Some(&s.device); + } + } + None + } +} + +#[derive(Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct Slave { + pub device: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub primary: Option, +} + +#[derive(Debug, PartialEq, Default, SerializeDisplay, DeserializeFromStr, EnumString, Display)] +#[strum(serialize_all = "kebab_case")] +pub enum CarrierDetect { + Ioctl = 0, + #[default] + Netif = 1, +} + +#[derive(Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct Miimon { + pub frequency: u32, + #[serde(rename = "carrier-detect")] + pub carrier_detect: CarrierDetect, + pub downdelay: Option, + pub updelay: Option, +} + +#[derive(Debug, PartialEq, SerializeDisplay, DeserializeFromStr, EnumString, Display)] +#[strum(serialize_all = "kebab_case")] +pub enum ArpValidateTargets { + Any = 0, + All = 1, +} + +#[derive(Debug, PartialEq, SerializeDisplay, DeserializeFromStr, EnumString, Display)] +#[strum(serialize_all = "snake_case")] +pub enum ArpValidate { + None = 0, + Active = 1, + Backup = 2, + All = 3, + Filter = 4, + FilterActive = 5, + FilterBackup = 6, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct ArpMon { + pub interval: u32, + pub validate: ArpValidate, + #[serde(rename = "validate-targets", skip_serializing_if = "Option::is_none")] + pub validate_targets: Option, + #[serde(deserialize_with = "unwrap_arpmon_targets")] + pub targets: Vec, +} + +fn unwrap_arpmon_targets<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Debug, PartialEq, Default, Serialize, Deserialize)] + pub struct ArpMonTargetAddressV4 { + #[serde(rename = "ipv4-address")] + pub ipv4_address: Vec, + } + Ok(ArpMonTargetAddressV4::deserialize(deserializer)?.ipv4_address) +} + +fn unwrap_slaves<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Debug, PartialEq, Default, Serialize, Deserialize)] + struct Slaves { + slave: Vec, + } + Ok(Slaves::deserialize(deserializer)?.slave) +} + +impl From<&WickedBondMode> for AgamaBondMode { + fn from(bondmode: &WickedBondMode) -> AgamaBondMode { + match bondmode { + WickedBondMode::BalanceRr => AgamaBondMode::RoundRobin, + WickedBondMode::ActiveBackup => AgamaBondMode::ActiveBackup, + WickedBondMode::BalanceXor => AgamaBondMode::BalanceXOR, + WickedBondMode::Broadcast => AgamaBondMode::Broadcast, + WickedBondMode::IEEE8023ad => AgamaBondMode::LACP, + WickedBondMode::BalanceTlb => AgamaBondMode::BalanceTLB, + WickedBondMode::BalanceAlb => AgamaBondMode::BalanceALB, + } + } +} + +impl From<&Bond> for model::ConnectionConfig { + fn from(bond: &Bond) -> model::ConnectionConfig { + let mut h: HashMap = HashMap::new(); + + if let Some(p) = bond.primary() { + h.insert(String::from("primary"), p.clone()); + } + + if let Some(m) = &bond.miimon { + h.insert(String::from("miimon"), format!("{}", m.frequency)); + h.insert( + String::from("use_carrier"), + match m.carrier_detect { + CarrierDetect::Ioctl => 0, + CarrierDetect::Netif => 1, + } + .to_string(), + ); + if let Some(v) = m.downdelay { + h.insert(String::from("downdelay"), v.to_string()); + } + if let Some(v) = m.updelay { + h.insert(String::from("updelay"), v.to_string()); + } + } + + if let Some(a) = &bond.arpmon { + h.insert(String::from("arp_interval"), format!("{}", a.interval)); + h.insert(String::from("arp_validate"), a.validate.to_string()); + + if !a.targets.is_empty() { + let sv = a + .targets + .iter() + .map(|c| c.as_ref()) + .collect::>() + .join(","); + h.insert(String::from("arp_ip_target"), sv); + } + + if let Some(v) = &a.validate_targets { + h.insert(String::from("arp_all_targets"), v.to_string()); + } + } + + if let Some(fom) = &bond.fail_over_mac { + h.insert(String::from("fail_over_mac"), fom.to_string()); + } + + if let Some(v) = &bond.xmit_hash_policy { + h.insert(String::from("xmit_hash_policy"), v.to_string()); + } + + if let Some(v) = &bond.packets_per_slave { + h.insert(String::from("packets_per_slave"), v.to_string()); + } + + if let Some(v) = &bond.tlb_dynamic_lb { + h.insert( + String::from("tlb_dynamic_lb"), + if *v { 1.to_string() } else { 0.to_string() }, + ); + } + + if let Some(v) = &bond.lacp_rate { + h.insert(String::from("lacp_rate"), v.to_string()); + } + + if let Some(v) = &bond.ad_select { + h.insert(String::from("ad_select"), v.to_string()); + } + if let Some(v) = &bond.ad_user_port_key { + h.insert(String::from("ad_user_port_key"), v.to_string()); + } + if let Some(v) = &bond.ad_actor_sys_prio { + h.insert(String::from("ad_actor_sys_prio"), v.to_string()); + } + if let Some(v) = &bond.ad_actor_system { + h.insert(String::from("ad_actor_system"), v.clone()); + } + if let Some(v) = &bond.min_links { + h.insert(String::from("min_links"), v.to_string()); + } + if let Some(v) = &bond.primary_reselect { + h.insert(String::from("primary_reselect"), v.to_string()); + } + if let Some(v) = &bond.num_grat_arp { + h.insert(String::from("num_grat_arp"), v.to_string()); + } + if let Some(v) = &bond.num_unsol_na { + h.insert(String::from("num_unsol_na"), v.to_string()); + } + if let Some(v) = &bond.lp_interval { + h.insert(String::from("lp_interval"), v.to_string()); + } + if let Some(v) = &bond.resend_igmp { + h.insert(String::from("resend_igmp"), v.to_string()); + } + if let Some(v) = &bond.all_slaves_active { + h.insert( + String::from("all_slaves_active"), + if *v { 1.to_string() } else { 0.to_string() }, + ); + } + + model::ConnectionConfig::Bond(model::BondConfig { + options: model::BondOptions(h), + mode: AgamaBondMode::from(&bond.mode), + }) + } +} + #[skip_serializing_none] #[derive(Debug, PartialEq, Default, Serialize, Deserialize)] pub struct Ethernet { - pub address: Option, + #[serde(default, skip_serializing_if = "String::is_empty")] + pub address: String, } #[derive(Debug, PartialEq, Serialize, Deserialize)] @@ -132,21 +461,16 @@ impl Interface { } } - if let Some(dummy) = &self.dummy { - if let Some(address) = &dummy.address { - connection.mac_address = MacAddress::from_str(address)?; - } - } else if let Some(ethernet) = &self.ethernet { - if let Some(address) = ðernet.address { - connection.mac_address = MacAddress::from_str(address)?; - } - } - - if self.dummy.is_some() { - connection.config = model::ConnectionConfig::Dummy - } else { + if let Some(ethernet) = &self.ethernet { + connection.mac_address = MacAddress::from_str(ðernet.address)?; connection.config = model::ConnectionConfig::Ethernet - }; + } else if let Some(dummy) = &self.dummy { + connection.mac_address = MacAddress::from_str(&dummy.address)?; + connection.config = model::ConnectionConfig::Dummy + } else if let Some(bond) = &self.bond { + connection.mac_address = MacAddress::from_str(&bond.address)?; + connection.config = bond.into() + } Ok(ConnectionResult { connection, @@ -379,7 +703,7 @@ mod tests { fn test_dummy_interface_to_connection() { let dummy_interface = Interface { dummy: Some(Dummy { - address: Some("12:34:56:78:9A:BC".to_string()), + address: "12:34:56:78:9A:BC".to_string(), }), ..Default::default() }; @@ -387,5 +711,112 @@ mod tests { let connection: model::Connection = dummy_interface.to_connection().unwrap().connection; assert!(matches!(connection.config, model::ConnectionConfig::Dummy)); assert_eq!(connection.mac_address.to_string(), "12:34:56:78:9A:BC"); + + let dummy_interface = Interface { + dummy: Some(Dummy { + ..Default::default() + }), + ..Default::default() + }; + + let connection: model::Connection = dummy_interface.to_connection().unwrap().connection; + assert!(matches!(connection.config, model::ConnectionConfig::Dummy)); + assert_eq!(dummy_interface.dummy.unwrap().address, String::from("")); + assert!(matches!(connection.mac_address, MacAddress::Unset)); + } + + #[test] + fn test_bond_options() { + let bond_interface = Interface { + bond: Some(Bond { + mode: WickedBondMode::IEEE8023ad, + xmit_hash_policy: Some(XmitHashPolicy::Encap34), + fail_over_mac: Some(FailOverMac::Active), + packets_per_slave: Some(23), + tlb_dynamic_lb: Some(true), + lacp_rate: Some(LacpRate::Slow), + ad_select: Some(AdSelect::Bandwidth), + ad_user_port_key: Some(42), + ad_actor_sys_prio: Some(5), + ad_actor_system: Some(String::from("00:de:ad:be:ef:00")), + min_links: Some(3), + primary_reselect: Some(PrimaryReselect::Better), + num_grat_arp: Some(7), + num_unsol_na: Some(13), + lp_interval: Some(17), + resend_igmp: Some(19), + all_slaves_active: Some(true), + miimon: Some(Miimon { + frequency: 42, + carrier_detect: CarrierDetect::Netif, + downdelay: Some(23), + updelay: Some(5), + }), + arpmon: Some(ArpMon { + interval: 23, + validate: ArpValidate::FilterBackup, + validate_targets: Some(ArpValidateTargets::Any), + targets: vec![String::from("1.2.3.4"), String::from("4.3.2.1")], + }), + slaves: vec![], + address: String::from("02:11:22:33:44:55"), + }), + ..Default::default() + }; + + let connection: model::Connection = bond_interface.to_connection().unwrap().connection; + assert!(matches!( + connection.config, + model::ConnectionConfig::Bond(_) + )); + assert_eq!(connection.mac_address.to_string(), "02:11:22:33:44:55"); + + if let model::ConnectionConfig::Bond(bond) = connection.config { + assert_eq!(bond.mode, AgamaBondMode::LACP); + let s = HashMap::from([ + ("xmit_hash_policy", String::from("encap34")), + ("packets_per_slave", 23.to_string()), + ("tlb_dynamic_lb", 1.to_string()), + ("lacp_rate", String::from("slow")), + ("ad_select", String::from("bandwidth")), + ("ad_user_port_key", 42.to_string()), + ("ad_actor_sys_prio", 5.to_string()), + ("ad_actor_system", String::from("00:de:ad:be:ef:00")), + ("min_links", 3.to_string()), + ("primary_reselect", String::from("better")), + ("fail_over_mac", String::from("active")), + ("num_grat_arp", 7.to_string()), + ("num_unsol_na", 13.to_string()), + ("lp_interval", 17.to_string()), + ("resend_igmp", 19.to_string()), + ("all_slaves_active", 1.to_string()), + // miimon + ("miimon", 42.to_string()), + ("use_carrier", 1.to_string()), + ("downdelay", 23.to_string()), + ("updelay", 5.to_string()), + // arpmon + ("arp_validate", String::from("filter_backup")), + ("arp_all_targets", String::from("any")), + ("arp_ip_target", String::from("1.2.3.4,4.3.2.1")), + ("arp_interval", 23.to_string()), + ]); + + for (k, v) in s.iter() { + assert!( + bond.options.0.contains_key(*k), + "Missing key '{}' in bond hash {:?}", + *k, + bond.options.0 + ); + assert_eq!( + bond.options.0.get(*k).unwrap(), + v, + "Unexpected value '{}' in key '{}'", + *k, + v + ); + } + } } } diff --git a/rust/migrate-wicked/src/migrate.rs b/rust/migrate-wicked/src/migrate.rs index f6213a2c3a..af5fcb6b4e 100644 --- a/rust/migrate-wicked/src/migrate.rs +++ b/rust/migrate-wicked/src/migrate.rs @@ -1,6 +1,7 @@ use crate::{reader::read as wicked_read, MIGRATION_SETTINGS}; use agama_dbus_server::network::{model, Adapter, NetworkManagerAdapter, NetworkState}; -use std::error::Error; +use std::{collections::HashMap, error::Error}; +use uuid::Uuid; struct WickedAdapter { paths: Vec, @@ -12,10 +13,40 @@ impl WickedAdapter { } } +fn update_parent_connection( + state: &mut model::NetworkState, + parents: HashMap, +) -> Result<(), Box> { + let settings = MIGRATION_SETTINGS.get().unwrap(); + let mut parent_uuid: HashMap = HashMap::new(); + + for (id, parent) in parents { + if let Some(parent_con) = state.get_connection_by_interface(&parent) { + parent_uuid.insert(id, parent_con.uuid); + } else { + log::warn!("Missing parent {} connection for {}", parent, id); + if !settings.continue_migration { + return Err(anyhow::anyhow!("Migration of {} failed", id).into()); + } + } + } + + for (id, uuid) in parent_uuid { + if let Some(connection) = state.get_connection_mut(&id) { + connection.controller = Some(uuid); + } else { + return Err(anyhow::anyhow!("Unexpected failure - missing connection {}", id).into()); + } + } + + Ok(()) +} + impl Adapter for WickedAdapter { fn read(&self) -> Result> { let interfaces = wicked_read(self.paths.clone())?; let settings = MIGRATION_SETTINGS.get().unwrap(); + let mut parents: HashMap = HashMap::new(); if !settings.continue_migration && interfaces.warning.is_some() { return Err(interfaces.warning.unwrap().into()); @@ -37,8 +68,15 @@ impl Adapter for WickedAdapter { .into()); } } + + if let Some(parent) = interface.link.master { + parents.insert(connection_result.connection.id.clone(), parent.clone()); + } state.add_connection(connection_result.connection)?; } + + update_parent_connection(&mut state, parents)?; + Ok(state) } diff --git a/rust/migrate-wicked/src/reader.rs b/rust/migrate-wicked/src/reader.rs index 0f312c85be..a47b93fc6b 100644 --- a/rust/migrate-wicked/src/reader.rs +++ b/rust/migrate-wicked/src/reader.rs @@ -104,6 +104,99 @@ mod tests { use super::*; use crate::interface::*; + #[test] + fn test_bond_options_from_xml() { + let xml = r##" + + bond0 + + active-backup + layer34 + none + 1 + true + slow + bandwidth + 5 + 7 + 00:de:ad:be:ef:00 + 11 + better + 13 + 17 + 19 + 23 + true + + en0 + + + 23 + 27 + 31 + ioctl + + + 23 + filter_backup + any + + 1.2.3.4 + 4.3.2.1 + + +
02:11:22:33:44:55
+
+
+ "##; + let ifc = quick_xml::de::from_str::>(replace_colons(xml).as_str()) + .unwrap() + .pop() + .unwrap(); + assert!(ifc.bond.is_some()); + let bond = ifc.bond.unwrap(); + + assert_eq!( + bond, + Bond { + mode: WickedBondMode::ActiveBackup, + xmit_hash_policy: Some(XmitHashPolicy::Layer34), + fail_over_mac: Some(FailOverMac::None), + packets_per_slave: Some(1), + tlb_dynamic_lb: Some(true), + lacp_rate: Some(LacpRate::Slow), + ad_select: Some(AdSelect::Bandwidth), + ad_user_port_key: Some(5), + ad_actor_sys_prio: Some(7), + ad_actor_system: Some(String::from("00:de:ad:be:ef:00")), + min_links: Some(11), + primary_reselect: Some(PrimaryReselect::Better), + num_grat_arp: Some(13), + num_unsol_na: Some(17), + lp_interval: Some(19), + resend_igmp: Some(23), + all_slaves_active: Some(true), + slaves: vec![Slave { + device: String::from("en0"), + primary: None + }], + miimon: Some(Miimon { + frequency: 23, + carrier_detect: CarrierDetect::Ioctl, + downdelay: Some(31), + updelay: Some(27), + }), + arpmon: Some(ArpMon { + interval: 23, + validate: ArpValidate::FilterBackup, + validate_targets: Some(ArpValidateTargets::Any), + targets: vec![String::from("1.2.3.4"), String::from("4.3.2.1")] + }), + address: String::from("02:11:22:33:44:55"), + } + ); + } + #[test] fn test_broken_xml() { let xml = r##" diff --git a/rust/migrate-wicked/tests/bond_active-backup/system-connections/bond0.nmconnection b/rust/migrate-wicked/tests/bond_active-backup/system-connections/bond0.nmconnection new file mode 100644 index 0000000000..a14c852a1b --- /dev/null +++ b/rust/migrate-wicked/tests/bond_active-backup/system-connections/bond0.nmconnection @@ -0,0 +1,25 @@ +[connection] +id=bond0 +uuid=766a5c05-2ba1-45be-960d-9f2dbf89b293 +type=bond +interface-name=bond0 + +[ethernet] +cloned-mac-address=02:00:33:44:55:11 + +[bond] +miimon=100 +mode=active-backup +primary=en0 +use_carrier=1 + +[match] + +[ipv4] +method=auto + +[ipv6] +addr-gen-mode=default +method=auto + +[proxy] diff --git a/rust/migrate-wicked/tests/bond_active-backup/system-connections/en0.nmconnection b/rust/migrate-wicked/tests/bond_active-backup/system-connections/en0.nmconnection new file mode 100644 index 0000000000..046d8e7906 --- /dev/null +++ b/rust/migrate-wicked/tests/bond_active-backup/system-connections/en0.nmconnection @@ -0,0 +1,11 @@ +[connection] +id=en0 +uuid=38d979e6-aa0e-4bae-8cff-a6dda281d0c7 +type=ethernet +interface-name=en0 +master=bond0 +slave-type=bond + +[ethernet] + +[match] diff --git a/rust/migrate-wicked/tests/bond_active-backup/system-connections/en1.nmconnection b/rust/migrate-wicked/tests/bond_active-backup/system-connections/en1.nmconnection new file mode 100644 index 0000000000..1e079a2789 --- /dev/null +++ b/rust/migrate-wicked/tests/bond_active-backup/system-connections/en1.nmconnection @@ -0,0 +1,11 @@ +[connection] +id=en1 +uuid=9269bbb4-a771-406f-ba66-3f65ed15634c +type=ethernet +interface-name=en1 +master=bond0 +slave-type=bond + +[ethernet] + +[match] diff --git a/rust/migrate-wicked/tests/bond_active-backup/wicked_xml/bond_active-backup.xml b/rust/migrate-wicked/tests/bond_active-backup/wicked_xml/bond_active-backup.xml new file mode 100644 index 0000000000..b6228ab717 --- /dev/null +++ b/rust/migrate-wicked/tests/bond_active-backup/wicked_xml/bond_active-backup.xml @@ -0,0 +1,85 @@ + + bond0 + + boot + + + public + + + active-backup + + 100 + netif + + + + en0 + true + + + en1 + + +
02:00:33:44:55:11
+
+ + + true + true + + + true + group + default-route,hostname,dns,nis,ntp,smb,nds,mtu,tz,boot + 15 + true + false + + + true + prefer-public + false + + + true + group + dns,nis,ntp,tz,boot + auto + true + 15 + true + false + false + +
+ + en0 + + hotplug + + + bond0 + + + false + + + false + + + + en1 + + hotplug + + + bond0 + + + false + + + false + +