diff --git a/src/api2/node/network.rs b/src/api2/node/network.rs index a4c82f87..796e52e8 100644 --- a/src/api2/node/network.rs +++ b/src/api2/node/network.rs @@ -187,7 +187,11 @@ pub fn read_interface(iface: String) -> Result { type: bool, optional: true, }, - bond_slaves: { + bond_mode: { + type: LinuxBondMode, + optional: true, + }, + slaves: { schema: NETWORK_INTERFACE_LIST_SCHEMA, optional: true, }, @@ -212,7 +216,8 @@ pub fn create_interface( mtu: Option, bridge_ports: Option>, bridge_vlan_aware: Option, - bond_slaves: Option>, + bond_mode: Option, + slaves: Option>, param: Value, ) -> Result<(), Error> { @@ -269,7 +274,8 @@ pub fn create_interface( if bridge_vlan_aware.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; } } NetworkInterfaceType::Bond => { - if let Some(slaves) = bond_slaves { interface.set_bond_slaves(slaves)?; } + if bond_mode.is_some() { interface.bond_mode = bond_mode; } + if let Some(slaves) = slaves { interface.set_bond_slaves(slaves)?; } } _ => bail!("creating network interface type '{:?}' is not supported", interface_type), } @@ -323,7 +329,7 @@ pub enum DeletableProperty { /// Delet bridge-vlan-aware flag bridge_vlan_aware, /// Delete bond-slaves (set to 'none') - bond_slaves, + slaves, } @@ -397,7 +403,11 @@ pub enum DeletableProperty { type: bool, optional: true, }, - bond_slaves: { + bond_mode: { + type: LinuxBondMode, + optional: true, + }, + slaves: { schema: NETWORK_INTERFACE_LIST_SCHEMA, optional: true, }, @@ -434,7 +444,8 @@ pub fn update_interface( mtu: Option, bridge_ports: Option>, bridge_vlan_aware: Option, - bond_slaves: Option>, + bond_mode: Option, + slaves: Option>, delete: Option>, digest: Option, param: Value, @@ -476,7 +487,7 @@ pub fn update_interface( DeletableProperty::autostart => { interface.autostart = false; }, DeletableProperty::bridge_ports => { interface.set_bridge_ports(Vec::new())?; } DeletableProperty::bridge_vlan_aware => { interface.bridge_vlan_aware = None; } - DeletableProperty::bond_slaves => { interface.set_bond_slaves(Vec::new())?; } + DeletableProperty::slaves => { interface.set_bond_slaves(Vec::new())?; } } } } @@ -487,7 +498,8 @@ pub fn update_interface( if mtu.is_some() { interface.mtu = mtu; } if let Some(ports) = bridge_ports { interface.set_bridge_ports(ports)?; } if bridge_vlan_aware.is_some() { interface.bridge_vlan_aware = bridge_vlan_aware; } - if let Some(slaves) = bond_slaves { interface.set_bond_slaves(slaves)?; } + if let Some(slaves) = slaves { interface.set_bond_slaves(slaves)?; } + if bond_mode.is_some() { interface.bond_mode = bond_mode; } if let Some(cidr) = cidr { let (_, _, is_v6) = network::parse_cidr(&cidr)?; diff --git a/src/api2/types.rs b/src/api2/types.rs index 9278d946..f79e714d 100644 --- a/src/api2/types.rs +++ b/src/api2/types.rs @@ -549,6 +549,30 @@ pub enum NetworkConfigMethod { Loopback, } +#[api()] +#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[allow(non_camel_case_types)] +#[repr(u8)] +/// Linux Bond Mode +pub enum LinuxBondMode { + /// Round-robin policy + balance_rr = 0, + /// Active-backup policy + active_backup = 1, + /// XOR policy + balance_xor = 2, + /// Broadcast policy + broadcast = 3, + /// IEEE 802.3ad Dynamic link aggregation + //#[serde(rename = "802.3ad")] + ieee802_3ad = 4, + /// Adaptive transmit load balancing + balance_tlb = 5, + /// Adaptive load balancing + balance_alb = 6, +} + #[api()] #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] @@ -642,10 +666,14 @@ pub const NETWORK_INTERFACE_LIST_SCHEMA: Schema = ArraySchema::new( schema: NETWORK_INTERFACE_LIST_SCHEMA, optional: true, }, - bond_slaves: { + slaves: { schema: NETWORK_INTERFACE_LIST_SCHEMA, optional: true, }, + bond_mode: { + type: LinuxBondMode, + optional: true, + } } )] #[derive(Debug, Serialize, Deserialize)] @@ -699,7 +727,9 @@ pub struct Interface { pub bridge_vlan_aware: Option, #[serde(skip_serializing_if="Option::is_none")] - pub bond_slaves: Option>, + pub slaves: Option>, + #[serde(skip_serializing_if="Option::is_none")] + pub bond_mode: Option, } // Regression tests diff --git a/src/config/network.rs b/src/config/network.rs index ef8bf73a..bba7ee66 100644 --- a/src/config/network.rs +++ b/src/config/network.rs @@ -2,6 +2,7 @@ use std::io::{Write}; use std::collections::{HashSet, HashMap}; use anyhow::{Error, format_err, bail}; +use serde::de::{value, IntoDeserializer, Deserialize}; use proxmox::tools::{fs::replace_file, fs::CreateOptions}; @@ -14,7 +15,24 @@ pub use lexer::*; mod parser; pub use parser::*; -use crate::api2::types::{Interface, NetworkConfigMethod, NetworkInterfaceType}; +use crate::api2::types::{Interface, NetworkConfigMethod, NetworkInterfaceType, LinuxBondMode}; + +pub fn bond_mode_from_str(s: &str) -> Result { + LinuxBondMode::deserialize(s.into_deserializer()) + .map_err(|_: value::Error| format_err!("invalid bond_mode '{}'", s)) +} + +pub fn bond_mode_to_str(mode: LinuxBondMode) -> &'static str { + match mode { + LinuxBondMode::balance_rr => "balance-rr", + LinuxBondMode::active_backup => "active-backup", + LinuxBondMode::balance_xor => "balance-xor", + LinuxBondMode::broadcast => "broadcast", + LinuxBondMode::ieee802_3ad => "802.3ad", + LinuxBondMode::balance_tlb => "balance-tlb", + LinuxBondMode::balance_alb => "balance-alb", + } +} impl Interface { @@ -37,7 +55,8 @@ impl Interface { mtu: None, bridge_ports: None, bridge_vlan_aware: None, - bond_slaves: None, + slaves: None, + bond_mode: None, } } @@ -116,7 +135,7 @@ impl Interface { if self.interface_type != NetworkInterfaceType::Bond { bail!("interface '{}' is no bond (type is {:?})", self.name, self.interface_type); } - self.bond_slaves = Some(slaves); + self.slaves = Some(slaves); Ok(()) } @@ -137,7 +156,10 @@ impl Interface { } } NetworkInterfaceType::Bond => { - if let Some(ref slaves) = self.bond_slaves { + let mode = self.bond_mode.unwrap_or(LinuxBondMode::balance_rr); + writeln!(w, "\tbond-mode {}", bond_mode_to_str(mode))?; + + if let Some(ref slaves) = self.slaves { if slaves.is_empty() { writeln!(w, "\tbond-slaves none")?; } else { @@ -226,7 +248,8 @@ impl Interface { mtu: _mtu, bridge_ports: _bridge_ports, bridge_vlan_aware: _bridge_vlan_aware, - bond_slaves: _bond_slaves, + slaves: _slaves, + bond_mode: _bond_mode, } => { method == method6 && comments.is_none() @@ -264,20 +287,20 @@ impl Interface { } return Ok(()); } - + if let Some(method) = self.method { writeln!(w, "iface {} inet {}", self.name, method_to_str(method))?; self.write_iface_attributes_v4(w, method)?; self.write_iface_attributes(w)?; writeln!(w)?; } - + if let Some(method6) = self.method6 { let mut skip_v6 = false; // avoid empty inet6 manual entry if self.method.is_some() && method6 == NetworkConfigMethod::Manual { if self.comments6.is_none() && self.options6.is_empty() { skip_v6 = true; } } - + if !skip_v6 { writeln!(w, "iface {} inet6 {}", self.name, method_to_str(method6))?; self.write_iface_attributes_v6(w, method6)?; diff --git a/src/config/network/lexer.rs b/src/config/network/lexer.rs index d8a769b2..c050745f 100644 --- a/src/config/network/lexer.rs +++ b/src/config/network/lexer.rs @@ -25,6 +25,7 @@ pub enum Token { BridgePorts, BridgeVlanAware, BondSlaves, + BondMode, EOF, } @@ -49,6 +50,8 @@ lazy_static! { map.insert("bridge_vlan_aware", Token::BridgeVlanAware); map.insert("bond-slaves", Token::BondSlaves); map.insert("bond_slaves", Token::BondSlaves); + map.insert("bond-mode", Token::BondMode); + map.insert("bond_mode", Token::BondMode); map }; } diff --git a/src/config/network/parser.rs b/src/config/network/parser.rs index f9f950b4..7684d7bc 100644 --- a/src/config/network/parser.rs +++ b/src/config/network/parser.rs @@ -9,7 +9,7 @@ use regex::Regex; use super::helper::*; use super::lexer::*; -use super::{NetworkConfig, NetworkOrderEntry, Interface, NetworkConfigMethod, NetworkInterfaceType}; +use super::{NetworkConfig, NetworkOrderEntry, Interface, NetworkConfigMethod, NetworkInterfaceType, bond_mode_from_str}; pub struct NetworkParser { input: Peekable>, @@ -236,9 +236,15 @@ impl NetworkParser { Token::BondSlaves => { self.eat(Token::BondSlaves)?; let slaves = self.parse_iface_list()?; - interface.bond_slaves = Some(slaves); + interface.slaves = Some(slaves); interface.set_interface_type(NetworkInterfaceType::Bond)?; } + Token::BondMode => { + self.eat(Token::BondMode)?; + let mode = self.next_text()?; + interface.bond_mode = Some(bond_mode_from_str(&mode)?); + self.eat(Token::Newline)?; + } Token::Netmask => bail!("netmask is deprecated and no longer supported"), _ => { // parse addon attributes