A minor comment bellow.
Stefan Hanreich <s.hanre...@proxmox.com> writes: > When the firewall key wasn't present in the network device > configuration of a guest, the firewall defaulted to on instead of off. > Since the UI omitted the firewall setting in the API calls when it is > unchecked, there was no way for the firewall to be turned off for a > specific network device of a guest with the firewall enabled. > > Since the whole section didn't even properly parse the property string > via the existing proxmox-schema facilities, also refactor the whole > property string parsing logic while we're at it. This is done by > splitting the network configuration structs into different ones for > LXC and QEMU and then using those to deserialize the PropertyString. > > There is also a slight breakage for callers utilizing NetworkDevice, > since it now returns owned values instead of references, which makes > life easier and is okay since the values all implement Copy. > > At this time pve-api-types isn't packaged yet, but as soon as it is we > can use the API schemas generated from pve-api-types instead of > manually creating our own subset schema. > > Signed-off-by: Stefan Hanreich <s.hanre...@proxmox.com> > --- > .../src/firewall/types/address.rs | 4 +- > proxmox-ve-config/src/guest/vm.rs | 344 ++++++++++++------ > 2 files changed, 244 insertions(+), 104 deletions(-) > > diff --git a/proxmox-ve-config/src/firewall/types/address.rs > b/proxmox-ve-config/src/firewall/types/address.rs > index 9b73d3d..c218d37 100644 > --- a/proxmox-ve-config/src/firewall/types/address.rs > +++ b/proxmox-ve-config/src/firewall/types/address.rs > @@ -119,7 +119,7 @@ impl From<IpAddr> for Cidr { > > const IPV4_LENGTH: u8 = 32; > > -#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] > +#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, > DeserializeFromStr)] > pub struct Ipv4Cidr { > addr: Ipv4Addr, > mask: u8, > @@ -193,7 +193,7 @@ impl fmt::Display for Ipv4Cidr { > > const IPV6_LENGTH: u8 = 128; > > -#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] > +#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, > DeserializeFromStr)] > pub struct Ipv6Cidr { > addr: Ipv6Addr, > mask: u8, > diff --git a/proxmox-ve-config/src/guest/vm.rs > b/proxmox-ve-config/src/guest/vm.rs > index 3476b93..8e226bd 100644 > --- a/proxmox-ve-config/src/guest/vm.rs > +++ b/proxmox-ve-config/src/guest/vm.rs > @@ -3,15 +3,18 @@ use std::io; > use std::str::FromStr; > use std::{collections::HashMap, net::Ipv6Addr}; > > -use proxmox_schema::property_string::PropertyIterator; > +use proxmox_schema::property_string::PropertyString; > +use proxmox_sortable_macro::sortable; > > use anyhow::{bail, Error}; > +use proxmox_schema::{ApiType, BooleanSchema, KeyAliasInfo, ObjectSchema, > StringSchema}; > +use serde::Deserialize; > use serde_with::DeserializeFromStr; > > -use crate::firewall::parse::{match_digits, parse_bool}; > +use crate::firewall::parse::match_digits; > use crate::firewall::types::address::{Ipv4Cidr, Ipv6Cidr}; > > -#[derive(Clone, Debug, DeserializeFromStr, PartialEq, Eq, Hash, PartialOrd, > Ord)] > +#[derive(Clone, Copy, Debug, DeserializeFromStr, PartialEq, Eq, Hash, > PartialOrd, Ord)] > pub struct MacAddress([u8; 6]); > > static LOCAL_PART: [u8; 8] = [0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, > 0x00]; > @@ -75,7 +78,7 @@ impl Display for MacAddress { > } > } > > -#[derive(Debug, Clone, Copy)] > +#[derive(Debug, Clone, Copy, DeserializeFromStr)] > #[cfg_attr(test, derive(Eq, PartialEq))] > pub enum NetworkDeviceModel { > VirtIO, > @@ -100,35 +103,199 @@ impl FromStr for NetworkDeviceModel { > } > } > > -#[derive(Debug)] > +#[derive(Debug, Deserialize)] > #[cfg_attr(test, derive(Eq, PartialEq))] > -pub struct NetworkDevice { > +pub struct QemuNetworkDevice { > model: NetworkDeviceModel, > + #[serde(rename = "macaddr")] > + mac_address: MacAddress, > + firewall: Option<bool>, Why not use `#[serde(default)]` and always get a boolean? The only place it is used uses .unwrap_or(false) (unwrap_or_default would be preferable imho). > +} > + > +impl ApiType for QemuNetworkDevice { > + #[sortable] > + const API_SCHEMA: proxmox_schema::Schema = ObjectSchema::new( > + "QEMU Network Device", > + &sorted!([ > + ( > + "firewall", > + true, > + &BooleanSchema::new("firewall enabled for this network > device").schema(), > + ), > + ( > + "macaddr", > + false, > + &StringSchema::new("mac address for this network > device").schema(), > + ), > + ( > + "model", > + false, > + &StringSchema::new("type of this network device").schema(), > + ), > + ]), > + ) > + .additional_properties(true) > + .key_alias_info(KeyAliasInfo::new( > + "model", > + &sorted!(["e1000", "rtl8139", "virtio", "vmxnet3"]), > + "macaddr", > + )) > + .schema(); > +} > + > +#[derive(Debug, DeserializeFromStr, Copy, Clone)] > +#[cfg_attr(test, derive(Eq, PartialEq))] > +pub enum LxcIpv4Addr { > + Ip(Ipv4Cidr), > + Dhcp, > + Manual, > +} > + > +impl LxcIpv4Addr { > + pub fn cidr(&self) -> Option<Ipv4Cidr> { > + match self { > + LxcIpv4Addr::Ip(ipv4_cidr) => Some(*ipv4_cidr), > + _ => None, > + } > + } > +} > + > +impl FromStr for LxcIpv4Addr { > + type Err = Error; > + > + fn from_str(s: &str) -> Result<Self, Self::Err> { > + Ok(match s { > + "dhcp" => LxcIpv4Addr::Dhcp, > + "manual" => LxcIpv4Addr::Manual, > + _ => LxcIpv4Addr::Ip(s.parse()?), > + }) > + } > +} > + > +#[derive(Debug, DeserializeFromStr, Copy, Clone)] > +#[cfg_attr(test, derive(Eq, PartialEq))] > +pub enum LxcIpv6Addr { > + Ip(Ipv6Cidr), > + Dhcp, > + Auto, > + Manual, > +} > + > +impl LxcIpv6Addr { > + pub fn cidr(&self) -> Option<Ipv6Cidr> { > + match self { > + LxcIpv6Addr::Ip(ipv6_cidr) => Some(*ipv6_cidr), > + _ => None, > + } > + } > +} > + > +impl FromStr for LxcIpv6Addr { > + type Err = Error; > + > + fn from_str(s: &str) -> Result<Self, Self::Err> { > + Ok(match s { > + "dhcp" => LxcIpv6Addr::Dhcp, > + "manual" => LxcIpv6Addr::Manual, > + "auto" => LxcIpv6Addr::Auto, > + _ => LxcIpv6Addr::Ip(s.parse()?), > + }) > + } > +} > + > +#[derive(Debug, Deserialize)] > +#[cfg_attr(test, derive(Eq, PartialEq))] > +pub struct LxcNetworkDevice { > + #[serde(rename = "type")] > + ty: NetworkDeviceModel, > + #[serde(rename = "hwaddr")] > mac_address: MacAddress, > - firewall: bool, > - ip: Option<Ipv4Cidr>, > - ip6: Option<Ipv6Cidr>, > + firewall: Option<bool>, > + ip: Option<LxcIpv4Addr>, > + ip6: Option<LxcIpv6Addr>, > +} > + > +impl ApiType for LxcNetworkDevice { > + #[sortable] > + const API_SCHEMA: proxmox_schema::Schema = ObjectSchema::new( > + "LXC Network Device", > + &sorted!([ > + ( > + "firewall", > + true, > + &BooleanSchema::new("firewall enabled for this network > device").schema(), > + ), > + ( > + "hwaddr", > + false, > + &StringSchema::new("mac address for this network > device").schema(), > + ), > + ( > + "ip", > + true, > + &StringSchema::new("IP settings for this network > device").schema(), > + ), > + ( > + "ip6", > + true, > + &StringSchema::new("IPv6 settings for this network > device").schema(), > + ), > + ( > + "type", > + false, > + &StringSchema::new("type of the network device").schema(), > + ), > + ]), > + ) > + .additional_properties(true) > + .schema(); > +} > + > +#[derive(Debug)] > +#[cfg_attr(test, derive(Eq, PartialEq))] > +pub enum NetworkDevice { > + Qemu(QemuNetworkDevice), > + Lxc(LxcNetworkDevice), > } > > impl NetworkDevice { > pub fn model(&self) -> NetworkDeviceModel { > - self.model > + match self { > + NetworkDevice::Qemu(qemu_network_device) => > qemu_network_device.model, > + NetworkDevice::Lxc(lxc_network_device) => lxc_network_device.ty, > + } > } > > - pub fn mac_address(&self) -> &MacAddress { > - &self.mac_address > + pub fn mac_address(&self) -> MacAddress { > + match self { > + NetworkDevice::Qemu(qemu_network_device) => > qemu_network_device.mac_address, > + NetworkDevice::Lxc(lxc_network_device) => > lxc_network_device.mac_address, > + } > } > > - pub fn ip(&self) -> Option<&Ipv4Cidr> { > - self.ip.as_ref() > + pub fn ip(&self) -> Option<Ipv4Cidr> { > + if let NetworkDevice::Lxc(device) = self { > + return device.ip?.cidr(); > + } > + > + None > } > > - pub fn ip6(&self) -> Option<&Ipv6Cidr> { > - self.ip6.as_ref() > + pub fn ip6(&self) -> Option<Ipv6Cidr> { > + if let NetworkDevice::Lxc(device) = self { > + return device.ip6?.cidr(); > + } > + > + None > } > > pub fn has_firewall(&self) -> bool { > - self.firewall > + let firewall_option = match self { > + NetworkDevice::Qemu(qemu_network_device) => > qemu_network_device.firewall, > + NetworkDevice::Lxc(lxc_network_device) => > lxc_network_device.firewall, > + }; > + > + firewall_option.unwrap_or(false) > } > } > > @@ -136,57 +303,15 @@ impl FromStr for NetworkDevice { > type Err = Error; > > fn from_str(s: &str) -> Result<Self, Self::Err> { > - let (mut ty, mut hwaddr, mut firewall, mut ip, mut ip6) = (None, > None, true, None, None); > - > - for entry in PropertyIterator::new(s) { > - let (key, value) = entry.unwrap(); > - > - if let Some(key) = key { > - match key { > - "type" | "model" => { > - ty = Some(NetworkDeviceModel::from_str(&value)?); > - } > - "hwaddr" | "macaddr" => { > - hwaddr = Some(MacAddress::from_str(&value)?); > - } > - "firewall" => { > - firewall = parse_bool(&value)?; > - } > - "ip" => { > - if value == "dhcp" { > - continue; > - } > - > - ip = Some(Ipv4Cidr::from_str(&value)?); > - } > - "ip6" => { > - if value == "dhcp" || value == "auto" { > - continue; > - } > - > - ip6 = Some(Ipv6Cidr::from_str(&value)?); > - } > - _ => { > - if let Ok(model) = NetworkDeviceModel::from_str(key) > { > - ty = Some(model); > - hwaddr = Some(MacAddress::from_str(&value)?); > - } > - } > - } > - } > + if let Ok(qemu_device) = > s.parse::<PropertyString<QemuNetworkDevice>>() { > + return Ok(NetworkDevice::Qemu(qemu_device.into_inner())); > } > > - if let (Some(ty), Some(hwaddr)) = (ty, hwaddr) { > - return Ok(NetworkDevice { > - model: ty, > - mac_address: hwaddr, > - firewall, > - ip, > - ip6, > - }); > + if let Ok(lxc_device) = > s.parse::<PropertyString<LxcNetworkDevice>>() { > + return Ok(NetworkDevice::Lxc(lxc_device.into_inner())); > } > > - bail!("No valid network device detected in string {s}"); > + bail!("not a valid network device property string: {s}") > } > } > > @@ -312,30 +437,43 @@ mod tests { > > assert_eq!( > network_device, > - NetworkDevice { > + NetworkDevice::Qemu(QemuNetworkDevice { > model: NetworkDeviceModel::VirtIO, > mac_address: MacAddress([0xAA, 0xAA, 0xAA, 0x17, 0x19, > 0x81]), > - firewall: true, > - ip: None, > - ip6: None, > - } > + firewall: Some(true), > + }) > + ); > + > + network_device = > "model=virtio,macaddr=AA:AA:AA:17:19:81,bridge=public" > + .parse() > + .expect("valid network configuration"); > + > + assert_eq!( > + network_device, > + NetworkDevice::Qemu(QemuNetworkDevice { > + model: NetworkDeviceModel::VirtIO, > + mac_address: MacAddress([0xAA, 0xAA, 0xAA, 0x17, 0x19, > 0x81]), > + firewall: None, > + }) > ); > > + assert!(!network_device.has_firewall()); > + > network_device = > "model=virtio,macaddr=AA:AA:AA:17:19:81,bridge=public,firewall=1,queues=4" > .parse() > .expect("valid network configuration"); > > assert_eq!( > network_device, > - NetworkDevice { > + NetworkDevice::Qemu(QemuNetworkDevice { > model: NetworkDeviceModel::VirtIO, > mac_address: MacAddress([0xAA, 0xAA, 0xAA, 0x17, 0x19, > 0x81]), > - firewall: true, > - ip: None, > - ip6: None, > - } > + firewall: Some(true), > + }) > ); > > + assert!(network_device.has_firewall()); > + > network_device = > > "name=eth0,bridge=public,firewall=0,hwaddr=AA:AA:AA:E2:3E:24,ip=dhcp,type=veth" > .parse() > @@ -343,13 +481,13 @@ mod tests { > > assert_eq!( > network_device, > - NetworkDevice { > - model: NetworkDeviceModel::Veth, > + NetworkDevice::Lxc(LxcNetworkDevice { > + ty: NetworkDeviceModel::Veth, > mac_address: MacAddress([0xAA, 0xAA, 0xAA, 0xE2, 0x3E, > 0x24]), > - firewall: false, > - ip: None, > + firewall: Some(false), > + ip: Some(LxcIpv4Addr::Dhcp), > ip6: None, > - } > + }) > ); > > "model=virtio" > @@ -369,7 +507,7 @@ mod tests { > } > > #[test] > - fn test_parse_network_confg() { > + fn test_parse_network_config() { > let mut guest_config = "\ > boot: order=scsi0;net0 > cores: 4 > @@ -433,13 +571,11 @@ vmgenid: 706fbe99-d28b-4047-a9cd-3677c859ca8a" > > assert_eq!( > network_config.network_devices()[&0], > - NetworkDevice { > + NetworkDevice::Qemu(QemuNetworkDevice { > model: NetworkDeviceModel::VirtIO, > mac_address: MacAddress([0xAA, 0xBB, 0xCC, 0xF2, 0xFE, > 0x75]), > - firewall: true, > - ip: None, > - ip6: None, > - } > + firewall: None, > + }) > ); > > guest_config = "\ > @@ -448,7 +584,7 @@ cores: 1 > features: nesting=1 > hostname: dnsct > memory: 512 > -net0: > name=eth0,bridge=data,firewall=1,hwaddr=BC:24:11:47:83:11,ip=dhcp,type=veth > +net0: > name=eth0,bridge=data,firewall=1,hwaddr=BC:24:11:47:83:11,ip=dhcp,ip6=auto,type=veth > net2: > name=eth0,bridge=data,firewall=0,hwaddr=BC:24:11:47:83:12,ip=123.123.123.123/24,type=veth > net5: > name=eth0,bridge=data,firewall=1,hwaddr=BC:24:11:47:83:13,ip6=fd80::1/64,type=veth > ostype: alpine > @@ -463,35 +599,39 @@ unprivileged: 1" > > assert_eq!( > network_config.network_devices()[&0], > - NetworkDevice { > - model: NetworkDeviceModel::Veth, > + NetworkDevice::Lxc(LxcNetworkDevice { > + ty: NetworkDeviceModel::Veth, > mac_address: MacAddress([0xBC, 0x24, 0x11, 0x47, 0x83, > 0x11]), > - firewall: true, > - ip: None, > - ip6: None, > - } > + firewall: Some(true), > + ip: Some(LxcIpv4Addr::Dhcp), > + ip6: Some(LxcIpv6Addr::Auto), > + }) > ); > > assert_eq!( > network_config.network_devices()[&2], > - NetworkDevice { > - model: NetworkDeviceModel::Veth, > + NetworkDevice::Lxc(LxcNetworkDevice { > + ty: NetworkDeviceModel::Veth, > mac_address: MacAddress([0xBC, 0x24, 0x11, 0x47, 0x83, > 0x12]), > - firewall: false, > - ip: > Some(Ipv4Cidr::from_str("123.123.123.123/24").expect("valid ipv4")), > + firewall: Some(false), > + ip: Some(LxcIpv4Addr::Ip( > + Ipv4Cidr::from_str("123.123.123.123/24").expect("valid > ipv4") > + )), > ip6: None, > - } > + }) > ); > > assert_eq!( > network_config.network_devices()[&5], > - NetworkDevice { > - model: NetworkDeviceModel::Veth, > + NetworkDevice::Lxc(LxcNetworkDevice { > + ty: NetworkDeviceModel::Veth, > mac_address: MacAddress([0xBC, 0x24, 0x11, 0x47, 0x83, > 0x13]), > - firewall: true, > + firewall: Some(true), > ip: None, > - ip6: Some(Ipv6Cidr::from_str("fd80::1/64").expect("valid > ipv6")), > - } > + ip6: Some(LxcIpv6Addr::Ip( > + Ipv6Cidr::from_str("fd80::1/64").expect("valid ipv6") > + )), > + }) > ); > > NetworkConfig::parse( _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel