public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pbs-devel] [PATCH widget-toolkit v3 1/9] form: include vlan field widget from PVE
       [not found] <20240404095151.184141-1-s.lendl@proxmox.com>
@ 2024-04-04  9:51 ` Stefan Lendl
  2024-04-04 16:29   ` Thomas Lamprecht
  2024-04-18  9:26   ` Folke Gleumes
  2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 2/9] tests: move network tests to parser.rs Stefan Lendl
                   ` (7 subsequent siblings)
  8 siblings, 2 replies; 14+ messages in thread
From: Stefan Lendl @ 2024-04-04  9:51 UTC (permalink / raw)
  To: s.lendl, pbs-devel

Copied from PVE to use in PBS network configuration.

Signed-off-by: Stefan Lendl <s.lendl@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
---
 src/Makefile            |  1 +
 src/form/VlanField.js   | 40 ++++++++++++++++++++++++++++++++++++++++
 src/node/NetworkEdit.js |  6 +++---
 3 files changed, 44 insertions(+), 3 deletions(-)
 create mode 100644 src/form/VlanField.js

diff --git a/src/Makefile b/src/Makefile
index 89f9962..0b1e88c 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -31,6 +31,7 @@ JSSRC=					\
 	form/ExpireDate.js		\
 	form/IntegerField.js		\
 	form/TextField.js		\
+	form/VlanField.js		\
 	form/DateTimeField.js		\
 	form/Checkbox.js		\
 	form/KVComboBox.js		\
diff --git a/src/form/VlanField.js b/src/form/VlanField.js
new file mode 100644
index 0000000..71b580d
--- /dev/null
+++ b/src/form/VlanField.js
@@ -0,0 +1,40 @@
+Ext.define('Proxmox.form.field.VlanField', {
+    extend: 'Ext.form.field.Number',
+    alias: ['widget.proxmoxvlanfield'],
+
+    deleteEmpty: false,
+
+    emptyText: gettext('no VLAN'),
+
+    fieldLabel: gettext('VLAN Tag'),
+
+    allowBlank: true,
+
+    getSubmitData: function() {
+	var me = this,
+	    data = null,
+	    val;
+	if (!me.disabled && me.submitValue) {
+	    val = me.getSubmitValue();
+	    if (val) {
+		data = {};
+		data[me.getName()] = val;
+	    } else if (me.deleteEmpty) {
+		data = {};
+		data.delete = me.getName();
+	    }
+	}
+	return data;
+    },
+
+    initComponent: function() {
+	var me = this;
+
+	Ext.apply(me, {
+	    minValue: 1,
+	    maxValue: 4094,
+	});
+
+	me.callParent();
+    },
+});
diff --git a/src/node/NetworkEdit.js b/src/node/NetworkEdit.js
index bb9add3..b81a21d 100644
--- a/src/node/NetworkEdit.js
+++ b/src/node/NetworkEdit.js
@@ -97,7 +97,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
 		name: 'ovs_bridge',
 	    });
 	    column2.push({
-		xtype: 'pveVlanField',
+		xtype: 'proxmoxvlanfield',
 		deleteEmpty: !me.isCreate,
 		name: 'ovs_tag',
 		value: '',
@@ -140,7 +140,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
 	    });
 
 	    column2.push({
-		xtype: 'pveVlanField',
+		xtype: 'proxmoxvlanfield',
 		name: 'vlan-id',
 		value: me.vlanidvalue,
 		disabled: me.disablevlanid,
@@ -211,7 +211,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
 		name: 'ovs_bridge',
 	    });
 	    column2.push({
-		xtype: 'pveVlanField',
+		xtype: 'proxmoxvlanfield',
 		deleteEmpty: !me.isCreate,
 		name: 'ovs_tag',
 		value: '',
-- 
2.44.0





^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v3 2/9] tests: move network tests to parser.rs
       [not found] <20240404095151.184141-1-s.lendl@proxmox.com>
  2024-04-04  9:51 ` [pbs-devel] [PATCH widget-toolkit v3 1/9] form: include vlan field widget from PVE Stefan Lendl
@ 2024-04-04  9:51 ` Stefan Lendl
  2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 3/9] tests: simple tests for writing the network config Stefan Lendl
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 14+ messages in thread
From: Stefan Lendl @ 2024-04-04  9:51 UTC (permalink / raw)
  To: s.lendl, pbs-devel

All current tests in network/mod.rs only test parser functionality and
  should therefore live in the parser module.

Signed-off-by: Stefan Lendl <s.lendl@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
---
 pbs-config/src/network/mod.rs    | 147 -------------------------------
 pbs-config/src/network/parser.rs | 147 +++++++++++++++++++++++++++++++
 2 files changed, 147 insertions(+), 147 deletions(-)

diff --git a/pbs-config/src/network/mod.rs b/pbs-config/src/network/mod.rs
index e2008b7c..21187ec2 100644
--- a/pbs-config/src/network/mod.rs
+++ b/pbs-config/src/network/mod.rs
@@ -503,150 +503,3 @@ pub fn complete_port_list(arg: &str, _param: &HashMap<String, String>) -> Vec<St
         .map(|port| format!("{}{}", prefix, port))
         .collect()
 }
-
-#[cfg(test)]
-mod test {
-
-    use anyhow::Error;
-
-    use super::*;
-
-    #[test]
-    fn test_network_config_create_lo_1() -> Result<(), Error> {
-        let input = "";
-
-        let mut parser = NetworkParser::new(input.as_bytes());
-
-        let config = parser.parse_interfaces(None)?;
-
-        let output = String::try_from(config)?;
-
-        let expected = "auto lo\niface lo inet loopback\n\n";
-        assert_eq!(output, expected);
-
-        // run again using output as input
-        let mut parser = NetworkParser::new(output.as_bytes());
-
-        let config = parser.parse_interfaces(None)?;
-
-        let output = String::try_from(config)?;
-
-        assert_eq!(output, expected);
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_network_config_create_lo_2() -> Result<(), Error> {
-        let input = "#c1\n\n#c2\n\niface test inet manual\n";
-
-        let mut parser = NetworkParser::new(input.as_bytes());
-
-        let config = parser.parse_interfaces(None)?;
-
-        let output = String::try_from(config)?;
-
-        // Note: loopback should be added in front of other interfaces
-        let expected = "#c1\n#c2\n\nauto lo\niface lo inet loopback\n\niface test inet manual\n\n";
-        assert_eq!(output, expected);
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_network_config_parser_no_blank_1() -> Result<(), Error> {
-        let input = "auto lo\n\
-                     iface lo inet loopback\n\
-                     iface lo inet6 loopback\n\
-                     auto ens18\n\
-                     iface ens18 inet static\n\
-                     \taddress 192.168.20.144/20\n\
-                     \tgateway 192.168.16.1\n\
-                     # comment\n\
-                     iface ens20 inet static\n\
-                     \taddress 192.168.20.145/20\n\
-                     iface ens21 inet manual\n\
-                     iface ens22 inet manual\n";
-
-        let mut parser = NetworkParser::new(input.as_bytes());
-
-        let config = parser.parse_interfaces(None)?;
-
-        let output = String::try_from(config)?;
-
-        let expected = "auto lo\n\
-                        iface lo inet loopback\n\
-                        \n\
-                        iface lo inet6 loopback\n\
-                        \n\
-                        auto ens18\n\
-                        iface ens18 inet static\n\
-                        \taddress 192.168.20.144/20\n\
-                        \tgateway 192.168.16.1\n\
-                        #comment\n\
-                        \n\
-                        iface ens20 inet static\n\
-                        \taddress 192.168.20.145/20\n\
-                        \n\
-                        iface ens21 inet manual\n\
-                        \n\
-                        iface ens22 inet manual\n\
-                        \n";
-        assert_eq!(output, expected);
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_network_config_parser_no_blank_2() -> Result<(), Error> {
-        // Adapted from bug 2926
-        let input = "### Hetzner Online GmbH installimage\n\
-                     \n\
-                     source /etc/network/interfaces.d/*\n\
-                     \n\
-                     auto lo\n\
-                     iface lo inet loopback\n\
-                     iface lo inet6 loopback\n\
-                     \n\
-                     auto enp4s0\n\
-                     iface enp4s0 inet static\n\
-                     \taddress 10.10.10.10/24\n\
-                     \tgateway 10.10.10.1\n\
-                     \t# route 10.10.20.10/24 via 10.10.20.1\n\
-                     \tup route add -net 10.10.20.10 netmask 255.255.255.0 gw 10.10.20.1 dev enp4s0\n\
-                     \n\
-                     iface enp4s0 inet6 static\n\
-                     \taddress fe80::5496:35ff:fe99:5a6a/64\n\
-                     \tgateway fe80::1\n";
-
-        let mut parser = NetworkParser::new(input.as_bytes());
-
-        let config = parser.parse_interfaces(None)?;
-
-        let output = String::try_from(config)?;
-
-        let expected = "### Hetzner Online GmbH installimage\n\
-                        \n\
-                        source /etc/network/interfaces.d/*\n\
-                        \n\
-                        auto lo\n\
-                        iface lo inet loopback\n\
-                        \n\
-                        iface lo inet6 loopback\n\
-                        \n\
-                        auto enp4s0\n\
-                        iface enp4s0 inet static\n\
-                        \taddress 10.10.10.10/24\n\
-                        \tgateway 10.10.10.1\n\
-                        \t# route 10.10.20.10/24 via 10.10.20.1\n\
-                        \tup route add -net 10.10.20.10 netmask 255.255.255.0 gw 10.10.20.1 dev enp4s0\n\
-                        \n\
-                        iface enp4s0 inet6 static\n\
-                        \taddress fe80::5496:35ff:fe99:5a6a/64\n\
-                        \tgateway fe80::1\n\
-                        \n";
-        assert_eq!(output, expected);
-
-        Ok(())
-    }
-}
diff --git a/pbs-config/src/network/parser.rs b/pbs-config/src/network/parser.rs
index ec2c64eb..1b55569a 100644
--- a/pbs-config/src/network/parser.rs
+++ b/pbs-config/src/network/parser.rs
@@ -602,3 +602,150 @@ impl<R: BufRead> NetworkParser<R> {
         Ok(config)
     }
 }
+
+#[cfg(test)]
+mod test {
+
+    use anyhow::Error;
+
+    use super::*;
+
+    #[test]
+    fn test_network_config_create_lo_1() -> Result<(), Error> {
+        let input = "";
+
+        let mut parser = NetworkParser::new(input.as_bytes());
+
+        let config = parser.parse_interfaces(None)?;
+
+        let output = String::try_from(config)?;
+
+        let expected = "auto lo\niface lo inet loopback\n\n";
+        assert_eq!(output, expected);
+
+        // run again using output as input
+        let mut parser = NetworkParser::new(output.as_bytes());
+
+        let config = parser.parse_interfaces(None)?;
+
+        let output = String::try_from(config)?;
+
+        assert_eq!(output, expected);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_network_config_create_lo_2() -> Result<(), Error> {
+        let input = "#c1\n\n#c2\n\niface test inet manual\n";
+
+        let mut parser = NetworkParser::new(input.as_bytes());
+
+        let config = parser.parse_interfaces(None)?;
+
+        let output = String::try_from(config)?;
+
+        // Note: loopback should be added in front of other interfaces
+        let expected = "#c1\n#c2\n\nauto lo\niface lo inet loopback\n\niface test inet manual\n\n";
+        assert_eq!(output, expected);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_network_config_parser_no_blank_1() -> Result<(), Error> {
+        let input = "auto lo\n\
+                     iface lo inet loopback\n\
+                     iface lo inet6 loopback\n\
+                     auto ens18\n\
+                     iface ens18 inet static\n\
+                     \taddress 192.168.20.144/20\n\
+                     \tgateway 192.168.16.1\n\
+                     # comment\n\
+                     iface ens20 inet static\n\
+                     \taddress 192.168.20.145/20\n\
+                     iface ens21 inet manual\n\
+                     iface ens22 inet manual\n";
+
+        let mut parser = NetworkParser::new(input.as_bytes());
+
+        let config = parser.parse_interfaces(None)?;
+
+        let output = String::try_from(config)?;
+
+        let expected = "auto lo\n\
+                        iface lo inet loopback\n\
+                        \n\
+                        iface lo inet6 loopback\n\
+                        \n\
+                        auto ens18\n\
+                        iface ens18 inet static\n\
+                        \taddress 192.168.20.144/20\n\
+                        \tgateway 192.168.16.1\n\
+                        #comment\n\
+                        \n\
+                        iface ens20 inet static\n\
+                        \taddress 192.168.20.145/20\n\
+                        \n\
+                        iface ens21 inet manual\n\
+                        \n\
+                        iface ens22 inet manual\n\
+                        \n";
+        assert_eq!(output, expected);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_network_config_parser_no_blank_2() -> Result<(), Error> {
+        // Adapted from bug 2926
+        let input = "### Hetzner Online GmbH installimage\n\
+                     \n\
+                     source /etc/network/interfaces.d/*\n\
+                     \n\
+                     auto lo\n\
+                     iface lo inet loopback\n\
+                     iface lo inet6 loopback\n\
+                     \n\
+                     auto enp4s0\n\
+                     iface enp4s0 inet static\n\
+                     \taddress 10.10.10.10/24\n\
+                     \tgateway 10.10.10.1\n\
+                     \t# route 10.10.20.10/24 via 10.10.20.1\n\
+                     \tup route add -net 10.10.20.10 netmask 255.255.255.0 gw 10.10.20.1 dev enp4s0\n\
+                     \n\
+                     iface enp4s0 inet6 static\n\
+                     \taddress fe80::5496:35ff:fe99:5a6a/64\n\
+                     \tgateway fe80::1\n";
+
+        let mut parser = NetworkParser::new(input.as_bytes());
+
+        let config = parser.parse_interfaces(None)?;
+
+        let output = String::try_from(config)?;
+
+        let expected = "### Hetzner Online GmbH installimage\n\
+                        \n\
+                        source /etc/network/interfaces.d/*\n\
+                        \n\
+                        auto lo\n\
+                        iface lo inet loopback\n\
+                        \n\
+                        iface lo inet6 loopback\n\
+                        \n\
+                        auto enp4s0\n\
+                        iface enp4s0 inet static\n\
+                        \taddress 10.10.10.10/24\n\
+                        \tgateway 10.10.10.1\n\
+                        \t# route 10.10.20.10/24 via 10.10.20.1\n\
+                        \tup route add -net 10.10.20.10 netmask 255.255.255.0 gw 10.10.20.1 dev enp4s0\n\
+                        \n\
+                        iface enp4s0 inet6 static\n\
+                        \taddress fe80::5496:35ff:fe99:5a6a/64\n\
+                        \tgateway fe80::1\n\
+                        \n";
+        assert_eq!(output, expected);
+
+        Ok(())
+    }
+}
-- 
2.44.0





^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v3 3/9] tests: simple tests for writing the network config
       [not found] <20240404095151.184141-1-s.lendl@proxmox.com>
  2024-04-04  9:51 ` [pbs-devel] [PATCH widget-toolkit v3 1/9] form: include vlan field widget from PVE Stefan Lendl
  2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 2/9] tests: move network tests to parser.rs Stefan Lendl
@ 2024-04-04  9:51 ` Stefan Lendl
  2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 4/9] config: write vlan network interface Stefan Lendl
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 14+ messages in thread
From: Stefan Lendl @ 2024-04-04  9:51 UTC (permalink / raw)
  To: s.lendl, pbs-devel

Simple tests for manual and static configurations.

Signed-off-by: Stefan Lendl <s.lendl@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
---
 pbs-config/src/network/mod.rs | 79 +++++++++++++++++++++++++++++++++++
 1 file changed, 79 insertions(+)

diff --git a/pbs-config/src/network/mod.rs b/pbs-config/src/network/mod.rs
index 21187ec2..7fec7e29 100644
--- a/pbs-config/src/network/mod.rs
+++ b/pbs-config/src/network/mod.rs
@@ -503,3 +503,82 @@ pub fn complete_port_list(arg: &str, _param: &HashMap<String, String>) -> Vec<St
         .map(|port| format!("{}{}", prefix, port))
         .collect()
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use NetworkConfigMethod::*;
+    use NetworkInterfaceType::*;
+    use NetworkOrderEntry::*;
+
+    #[test]
+    fn test_write_network_config_manual() {
+        let iface_name = String::from("enp3s0");
+        let mut iface = Interface::new(iface_name.clone());
+        iface.interface_type = Eth;
+        iface.method = Some(Manual);
+        iface.active = true;
+
+        let nw_config = NetworkConfig {
+            interfaces: BTreeMap::from([(iface_name.clone(), iface)]),
+            order: vec![Iface(iface_name.clone())],
+        };
+
+        assert_eq!(
+            String::try_from(nw_config).unwrap().trim(),
+            r#"iface enp3s0 inet manual"#
+        );
+    }
+
+    #[test]
+    fn test_write_network_config_static() {
+        let iface_name = String::from("enp3s0");
+        let mut iface = Interface::new(iface_name.clone());
+        iface.interface_type = Eth;
+        iface.method = Some(Static);
+        iface.cidr = Some(String::from("10.0.0.100/16"));
+        iface.active = true;
+
+        let nw_config = NetworkConfig {
+            interfaces: BTreeMap::from([(iface_name.clone(), iface)]),
+            order: vec![Iface(iface_name.clone())],
+        };
+        assert_eq!(
+            String::try_from(nw_config).unwrap().trim(),
+            format!(
+                r#"
+iface enp3s0 inet static
+	address 10.0.0.100/16"#
+            )
+            .trim()
+        );
+    }
+
+    #[test]
+    fn test_write_network_config_static_with_gateway() {
+        let iface_name = String::from("enp3s0");
+        let mut iface = Interface::new(iface_name.clone());
+        iface.interface_type = Eth;
+        iface.method = Some(Static);
+        iface.cidr = Some(String::from("10.0.0.100/16"));
+        iface.gateway = Some(String::from("10.0.0.1"));
+        iface.active = true;
+
+        let nw_config = NetworkConfig {
+            interfaces: BTreeMap::from([(iface_name.clone(), iface)]),
+            order: vec![Iface(iface_name.clone())],
+        };
+        assert_eq!(
+            String::try_from(nw_config).unwrap().trim(),
+            format!(
+                r#"
+iface enp3s0 inet static
+	address 10.0.0.100/16
+	gateway 10.0.0.1"#
+            )
+            .trim()
+        );
+    }
+}
+
-- 
2.44.0





^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v3 4/9] config: write vlan network interface
       [not found] <20240404095151.184141-1-s.lendl@proxmox.com>
                   ` (2 preceding siblings ...)
  2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 3/9] tests: simple tests for writing the network config Stefan Lendl
@ 2024-04-04  9:51 ` Stefan Lendl
  2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 5/9] config: parse vlan interface from config Stefan Lendl
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 14+ messages in thread
From: Stefan Lendl @ 2024-04-04  9:51 UTC (permalink / raw)
  To: s.lendl, pbs-devel

* Add vlan_id and vlan_raw_device fields to the Interface api type
* Write to the network config the vlan specific properties for vlan
  interface type
* Add several tests to verify the functionally

Signed-off-by: Stefan Lendl <s.lendl@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
---
 pbs-api-types/src/network.rs  | 17 ++++++++
 pbs-config/src/network/mod.rs | 73 ++++++++++++++++++++++++++++++++++-
 2 files changed, 89 insertions(+), 1 deletion(-)

diff --git a/pbs-api-types/src/network.rs b/pbs-api-types/src/network.rs
index e3a5e481..fe083dc6 100644
--- a/pbs-api-types/src/network.rs
+++ b/pbs-api-types/src/network.rs
@@ -224,6 +224,15 @@ pub const NETWORK_INTERFACE_LIST_SCHEMA: Schema =
             schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
             optional: true,
         },
+        "vlan-id": {
+            description: "VLAN ID.",
+            type: u16,
+            optional: true,
+        },
+        "vlan-raw-device": {
+            schema: NETWORK_INTERFACE_NAME_SCHEMA,
+            optional: true,
+        },
         bond_mode: {
             type: LinuxBondMode,
             optional: true,
@@ -287,6 +296,12 @@ pub struct Interface {
     /// Enable bridge vlan support.
     #[serde(skip_serializing_if = "Option::is_none")]
     pub bridge_vlan_aware: Option<bool>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(rename = "vlan-id")]
+    pub vlan_id: Option<u16>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(rename = "vlan-raw-device")]
+    pub vlan_raw_device: Option<String>,
 
     #[serde(skip_serializing_if = "Option::is_none")]
     pub slaves: Option<Vec<String>>,
@@ -319,6 +334,8 @@ impl Interface {
             mtu: None,
             bridge_ports: None,
             bridge_vlan_aware: None,
+            vlan_id: None,
+            vlan_raw_device: None,
             slaves: None,
             bond_mode: None,
             bond_primary: None,
diff --git a/pbs-config/src/network/mod.rs b/pbs-config/src/network/mod.rs
index 7fec7e29..02117535 100644
--- a/pbs-config/src/network/mod.rs
+++ b/pbs-config/src/network/mod.rs
@@ -79,6 +79,14 @@ fn write_iface_attributes(iface: &Interface, w: &mut dyn Write) -> Result<(), Er
                 writeln!(w, "\tbond-slaves {}", slaves.join(" "))?;
             }
         }
+        NetworkInterfaceType::Vlan => {
+            if let Some(vlan_id) = iface.vlan_id {
+                writeln!(w, "\tvlan-id {vlan_id}")?;
+            }
+            if let Some(vlan_raw_device) = &iface.vlan_raw_device {
+                writeln!(w, "\tvlan-raw-device {vlan_raw_device}")?;
+            }
+        }
         _ => {}
     }
 
@@ -580,5 +588,68 @@ iface enp3s0 inet static
             .trim()
         );
     }
-}
 
+    #[test]
+    fn test_write_network_config_vlan_id_in_name() {
+        let iface_name = String::from("vmbr0.100");
+        let mut iface = Interface::new(iface_name.clone());
+        iface.interface_type = Vlan;
+        iface.method = Some(Manual);
+        iface.active = true;
+
+        let nw_config = NetworkConfig {
+            interfaces: BTreeMap::from([(iface_name.clone(), iface)]),
+            order: vec![Iface(iface_name.clone())],
+        };
+        assert_eq!(
+            String::try_from(nw_config).unwrap().trim(),
+            "iface vmbr0.100 inet manual"
+        );
+    }
+
+    #[test]
+    fn test_write_network_config_vlan_with_raw_device() {
+        let iface_name = String::from("vlan100");
+        let mut iface = Interface::new(iface_name.clone());
+        iface.interface_type = Vlan;
+        iface.vlan_raw_device = Some(String::from("vmbr0"));
+        iface.method = Some(Manual);
+        iface.active = true;
+
+        let nw_config = NetworkConfig {
+            interfaces: BTreeMap::from([(iface_name.clone(), iface)]),
+            order: vec![Iface(iface_name.clone())],
+        };
+        assert_eq!(
+            String::try_from(nw_config).unwrap().trim(),
+            r#"
+iface vlan100 inet manual
+	vlan-raw-device vmbr0"#
+                .trim()
+        );
+    }
+
+    #[test]
+    fn test_write_network_config_vlan_with_individual_name() {
+        let iface_name = String::from("individual_name");
+        let mut iface = Interface::new(iface_name.clone());
+        iface.interface_type = Vlan;
+        iface.vlan_raw_device = Some(String::from("vmbr0"));
+        iface.vlan_id = Some(100);
+        iface.method = Some(Manual);
+        iface.active = true;
+
+        let nw_config = NetworkConfig {
+            interfaces: BTreeMap::from([(iface_name.clone(), iface)]),
+            order: vec![Iface(iface_name.clone())],
+        };
+        assert_eq!(
+            String::try_from(nw_config).unwrap().trim(),
+            r#"
+iface individual_name inet manual
+	vlan-id 100
+	vlan-raw-device vmbr0"#
+                .trim()
+        );
+    }
+}
-- 
2.44.0





^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v3 5/9] config: parse vlan interface from config
       [not found] <20240404095151.184141-1-s.lendl@proxmox.com>
                   ` (3 preceding siblings ...)
  2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 4/9] config: write vlan network interface Stefan Lendl
@ 2024-04-04  9:51 ` Stefan Lendl
  2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 6/9] config: remove unnecessary pub in various methods in NetworkConfig Stefan Lendl
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 14+ messages in thread
From: Stefan Lendl @ 2024-04-04  9:51 UTC (permalink / raw)
  To: s.lendl, pbs-devel

Support three types of vlan configurations defined in interfaces,
conforming to the PVE configurations:

iface nic.<vlan-id> inet

iface vlan<vlan-id> inet
	vlan-raw-device <nic>

iface <arbitraty-name> inet
	vlan-id <vlan-id>
	vlan-raw-device <nic>

* Add lexer Token enum variants for vlan-id and vlan-raw-device and parse
  them in parse_iface_attributes.
* Add tests to verify this works in the above scenarios

Signed-off-by: Stefan Lendl <s.lendl@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
---
 pbs-config/src/network/lexer.rs  |  6 ++
 pbs-config/src/network/parser.rs | 97 +++++++++++++++++++++++++++++++-
 2 files changed, 102 insertions(+), 1 deletion(-)

diff --git a/pbs-config/src/network/lexer.rs b/pbs-config/src/network/lexer.rs
index fd23e3d8..d0b7d8cd 100644
--- a/pbs-config/src/network/lexer.rs
+++ b/pbs-config/src/network/lexer.rs
@@ -24,6 +24,8 @@ pub enum Token {
     MTU,
     BridgePorts,
     BridgeVlanAware,
+    VlanId,
+    VlanRawDevice,
     BondSlaves,
     BondMode,
     BondPrimary,
@@ -50,6 +52,10 @@ lazy_static! {
         map.insert("bridge_ports", Token::BridgePorts);
         map.insert("bridge-vlan-aware", Token::BridgeVlanAware);
         map.insert("bridge_vlan_aware", Token::BridgeVlanAware);
+        map.insert("vlan-id", Token::VlanId);
+        map.insert("vlan_id", Token::VlanId);
+        map.insert("vlan-raw-device", Token::VlanRawDevice);
+        map.insert("vlan_raw_device", Token::VlanRawDevice);
         map.insert("bond-slaves", Token::BondSlaves);
         map.insert("bond_slaves", Token::BondSlaves);
         map.insert("bond-mode", Token::BondMode);
diff --git a/pbs-config/src/network/parser.rs b/pbs-config/src/network/parser.rs
index 1b55569a..796e9308 100644
--- a/pbs-config/src/network/parser.rs
+++ b/pbs-config/src/network/parser.rs
@@ -361,6 +361,20 @@ impl<R: BufRead> NetworkParser<R> {
                     interface.bond_xmit_hash_policy = Some(policy);
                     self.eat(Token::Newline)?;
                 }
+                Token::VlanId => {
+                    self.eat(Token::VlanId)?;
+                    let vlan_id = self.next_text()?.parse()?;
+                    interface.vlan_id = Some(vlan_id);
+                    set_interface_type(interface, NetworkInterfaceType::Vlan)?;
+                    self.eat(Token::Newline)?;
+                }
+                Token::VlanRawDevice => {
+                    self.eat(Token::VlanRawDevice)?;
+                    let vlan_raw_device = self.next_text()?;
+                    interface.vlan_raw_device = Some(vlan_raw_device);
+                    set_interface_type(interface, NetworkInterfaceType::Vlan)?;
+                    self.eat(Token::Newline)?;
+                }
                 _ => {
                     // parse addon attributes
                     let option = self.parse_to_eol()?;
@@ -522,7 +536,7 @@ impl<R: BufRead> NetworkParser<R> {
 
         lazy_static! {
             static ref INTERFACE_ALIAS_REGEX: Regex = Regex::new(r"^\S+:\d+$").unwrap();
-            static ref VLAN_INTERFACE_REGEX: Regex = Regex::new(r"^\S+\.\d+$").unwrap();
+            static ref VLAN_INTERFACE_REGEX: Regex = Regex::new(r"^\S+\.\d+|vlan\d+$").unwrap();
         }
 
         if let Some(existing_interfaces) = existing_interfaces {
@@ -748,4 +762,85 @@ mod test {
 
         Ok(())
     }
+
+    #[test]
+    fn test_network_config_parser_vlan_id_in_name() {
+        let input = "iface vmbr0.100 inet static manual";
+        let mut parser = NetworkParser::new(input.as_bytes());
+        let config = parser.parse_interfaces(None).unwrap();
+
+        let iface = config.interfaces.get("vmbr0.100").unwrap();
+        assert_eq!(iface.interface_type, NetworkInterfaceType::Vlan);
+        assert_eq!(iface.vlan_raw_device, None);
+        assert_eq!(iface.vlan_id, None);
+    }
+
+    #[test]
+    fn test_network_config_parser_vlan_with_raw_device() {
+        let input = r#"
+iface vlan100 inet manual
+	vlan-raw-device vmbr0"#;
+
+        let mut parser = NetworkParser::new(input.as_bytes());
+        let config = parser.parse_interfaces(None).unwrap();
+
+        let iface = config.interfaces.get("vlan100").unwrap();
+        assert_eq!(iface.interface_type, NetworkInterfaceType::Vlan);
+        assert_eq!(iface.vlan_raw_device, Some(String::from("vmbr0")));
+        assert_eq!(iface.vlan_id, None);
+    }
+
+    #[test]
+    fn test_network_config_parser_vlan_with_raw_device_static() {
+        let input = r#"
+iface vlan100 inet static
+	vlan-raw-device vmbr0
+	address 10.0.0.100/16"#;
+
+        let mut parser = NetworkParser::new(input.as_bytes());
+        let config = parser.parse_interfaces(None).unwrap();
+
+        let iface = config.interfaces.get("vlan100").unwrap();
+        assert_eq!(iface.interface_type, NetworkInterfaceType::Vlan);
+        assert_eq!(iface.vlan_raw_device, Some(String::from("vmbr0")));
+        assert_eq!(iface.vlan_id, None);
+        assert_eq!(iface.method, Some(NetworkConfigMethod::Static));
+        assert_eq!(iface.cidr, Some(String::from("10.0.0.100/16")));
+    }
+
+    #[test]
+    fn test_network_config_parser_vlan_individual_name() {
+        let input = r#"
+iface individual_name inet manual
+	vlan-id 100
+	vlan-raw-device vmbr0"#;
+
+        let mut parser = NetworkParser::new(input.as_bytes());
+        let config = parser.parse_interfaces(None).unwrap();
+
+        let iface = config.interfaces.get("individual_name").unwrap();
+        assert_eq!(iface.interface_type, NetworkInterfaceType::Vlan);
+        assert_eq!(iface.vlan_raw_device, Some(String::from("vmbr0")));
+        assert_eq!(iface.vlan_id, Some(100));
+    }
+
+    #[test]
+    fn test_network_config_parser_vlan_individual_name_static() {
+        let input = r#"
+iface individual_name inet static
+	vlan-id 100
+	vlan-raw-device vmbr0
+	address 10.0.0.100/16
+"#;
+
+        let mut parser = NetworkParser::new(input.as_bytes());
+        let config = parser.parse_interfaces(None).unwrap();
+
+        let iface = config.interfaces.get("individual_name").unwrap();
+        assert_eq!(iface.interface_type, NetworkInterfaceType::Vlan);
+        assert_eq!(iface.vlan_raw_device, Some(String::from("vmbr0")));
+        assert_eq!(iface.vlan_id, Some(100));
+        assert_eq!(iface.method, Some(NetworkConfigMethod::Static));
+        assert_eq!(iface.cidr, Some(String::from("10.0.0.100/16")));
+    }
 }
-- 
2.44.0





^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v3 6/9] config: remove unnecessary pub in various methods in NetworkConfig
       [not found] <20240404095151.184141-1-s.lendl@proxmox.com>
                   ` (4 preceding siblings ...)
  2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 5/9] config: parse vlan interface from config Stefan Lendl
@ 2024-04-04  9:51 ` Stefan Lendl
  2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 7/9] fmt: fix intendation in api macro Stefan Lendl
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 14+ messages in thread
From: Stefan Lendl @ 2024-04-04  9:51 UTC (permalink / raw)
  To: s.lendl, pbs-devel

Signed-off-by: Stefan Lendl <s.lendl@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
---
 pbs-config/src/network/mod.rs    | 10 +++++-----
 pbs-config/src/network/parser.rs |  4 ++--
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/pbs-config/src/network/mod.rs b/pbs-config/src/network/mod.rs
index 02117535..51b09937 100644
--- a/pbs-config/src/network/mod.rs
+++ b/pbs-config/src/network/mod.rs
@@ -251,7 +251,7 @@ impl NetworkConfig {
     }
 
     /// Check if ports are used only once
-    pub fn check_port_usage(&self) -> Result<(), Error> {
+    fn check_port_usage(&self) -> Result<(), Error> {
         let mut used_ports = HashMap::new();
         let mut check_port_usage = |iface, ports: &Vec<String>| {
             for port in ports.iter() {
@@ -280,7 +280,7 @@ impl NetworkConfig {
     }
 
     /// Check if child mtu is less or equal than parent mtu
-    pub fn check_mtu(&self, parent_name: &str, child_name: &str) -> Result<(), Error> {
+    fn check_mtu(&self, parent_name: &str, child_name: &str) -> Result<(), Error> {
         let parent = self
             .interfaces
             .get(parent_name)
@@ -320,7 +320,7 @@ impl NetworkConfig {
     }
 
     /// Check if bond slaves exists
-    pub fn check_bond_slaves(&self) -> Result<(), Error> {
+    fn check_bond_slaves(&self) -> Result<(), Error> {
         for (iface, interface) in self.interfaces.iter() {
             if let Some(slaves) = &interface.slaves {
                 for slave in slaves.iter() {
@@ -348,7 +348,7 @@ impl NetworkConfig {
     }
 
     /// Check if bridge ports exists
-    pub fn check_bridge_ports(&self) -> Result<(), Error> {
+    fn check_bridge_ports(&self) -> Result<(), Error> {
         lazy_static! {
             static ref VLAN_INTERFACE_REGEX: Regex = Regex::new(r"^(\S+)\.(\d+)$").unwrap();
         }
@@ -372,7 +372,7 @@ impl NetworkConfig {
         Ok(())
     }
 
-    pub fn write_config(&self, w: &mut dyn Write) -> Result<(), Error> {
+    fn write_config(&self, w: &mut dyn Write) -> Result<(), Error> {
         self.check_port_usage()?;
         self.check_bond_slaves()?;
         self.check_bridge_ports()?;
diff --git a/pbs-config/src/network/parser.rs b/pbs-config/src/network/parser.rs
index 796e9308..e409c94c 100644
--- a/pbs-config/src/network/parser.rs
+++ b/pbs-config/src/network/parser.rs
@@ -487,11 +487,11 @@ impl<R: BufRead> NetworkParser<R> {
         &mut self,
         existing_interfaces: Option<&HashMap<String, bool>>,
     ) -> Result<NetworkConfig, Error> {
-        self._parse_interfaces(existing_interfaces)
+        self.do_parse_interfaces(existing_interfaces)
             .map_err(|err| format_err!("line {}: {}", self.line_nr, err))
     }
 
-    pub fn _parse_interfaces(
+    fn do_parse_interfaces(
         &mut self,
         existing_interfaces: Option<&HashMap<String, bool>>,
     ) -> Result<NetworkConfig, Error> {
-- 
2.44.0





^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v3 7/9] fmt: fix intendation in api macro
       [not found] <20240404095151.184141-1-s.lendl@proxmox.com>
                   ` (5 preceding siblings ...)
  2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 6/9] config: remove unnecessary pub in various methods in NetworkConfig Stefan Lendl
@ 2024-04-04  9:51 ` Stefan Lendl
  2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 8/9] api: create and update vlan interfaces Stefan Lendl
  2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 9/9] ui: enable vlan widget Stefan Lendl
  8 siblings, 0 replies; 14+ messages in thread
From: Stefan Lendl @ 2024-04-04  9:51 UTC (permalink / raw)
  To: s.lendl, pbs-devel

Signed-off-by: Stefan Lendl <s.lendl@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
---
 src/api2/node/network.rs | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/api2/node/network.rs b/src/api2/node/network.rs
index ade6fe40..187b27a0 100644
--- a/src/api2/node/network.rs
+++ b/src/api2/node/network.rs
@@ -227,9 +227,9 @@ pub fn read_interface(iface: String) -> Result<Value, Error> {
                 optional: true,
             },
             bridge_vlan_aware: {
-	        description: "Enable bridge vlan support.",
-	        type: bool,
-	        optional: true,
+                description: "Enable bridge vlan support.",
+                type: bool,
+                optional: true,
             },
             bond_mode: {
                 type: LinuxBondMode,
@@ -503,9 +503,9 @@ pub enum DeletableProperty {
                 optional: true,
             },
             bridge_vlan_aware: {
-	        description: "Enable bridge vlan support.",
-	        type: bool,
-	        optional: true,
+                description: "Enable bridge vlan support.",
+                type: bool,
+                optional: true,
             },
             bond_mode: {
                 type: LinuxBondMode,
-- 
2.44.0





^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v3 8/9] api: create and update vlan interfaces
       [not found] <20240404095151.184141-1-s.lendl@proxmox.com>
                   ` (6 preceding siblings ...)
  2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 7/9] fmt: fix intendation in api macro Stefan Lendl
@ 2024-04-04  9:51 ` Stefan Lendl
  2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 9/9] ui: enable vlan widget Stefan Lendl
  8 siblings, 0 replies; 14+ messages in thread
From: Stefan Lendl @ 2024-04-04  9:51 UTC (permalink / raw)
  To: s.lendl, pbs-devel

* Implement setting vlan-id and vlan-raw-device in the create and update api.
* Checking if the provided vlan-raw-device exists
* Moved VLAN_INTERFACE_REGEX to top level network module to use it in
  the checking functions there. Changed to match with named capture groups.
* Unit tests to verify parsing vlan_id and vlan_raw_device from name.

Signed-off-by: Stefan Lendl <s.lendl@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
---
 pbs-config/src/network/mod.rs    | 35 +++++++++++++++++++
 pbs-config/src/network/parser.rs |  3 +-
 src/api2/node/network.rs         | 60 +++++++++++++++++++++++++++++++-
 3 files changed, 96 insertions(+), 2 deletions(-)

diff --git a/pbs-config/src/network/mod.rs b/pbs-config/src/network/mod.rs
index 51b09937..19a2df8c 100644
--- a/pbs-config/src/network/mod.rs
+++ b/pbs-config/src/network/mod.rs
@@ -25,6 +25,8 @@ use crate::{open_backup_lockfile, BackupLockGuard};
 
 lazy_static! {
     static ref PHYSICAL_NIC_REGEX: Regex = Regex::new(r"^(?:eth\d+|en[^:.]+|ib\d+)$").unwrap();
+    static ref VLAN_INTERFACE_REGEX: Regex =
+        Regex::new(r"^(?P<vlan_raw_device>\S+)\.(?P<vlan_id>\d+)|vlan(?P<vlan_id2>\d+)$").unwrap();
 }
 
 pub fn is_physical_nic(iface: &str) -> bool {
@@ -41,6 +43,21 @@ pub fn bond_xmit_hash_policy_from_str(s: &str) -> Result<BondXmitHashPolicy, Err
         .map_err(|_: value::Error| format_err!("invalid bond_xmit_hash_policy '{}'", s))
 }
 
+pub fn parse_vlan_id_from_name(iface_name: &str) -> Option<u16> {
+    VLAN_INTERFACE_REGEX.captures(iface_name).and_then(|cap| {
+        cap.name("vlan_id")
+            .or(cap.name("vlan_id2"))
+            .and_then(|id| id.as_str().parse::<u16>().ok())
+    })
+}
+
+pub fn parse_vlan_raw_device_from_name(iface_name: &str) -> Option<&str> {
+    VLAN_INTERFACE_REGEX
+        .captures(iface_name)
+        .and_then(|cap| cap.name("vlan_raw_device"))
+        .map(Into::into)
+}
+
 // Write attributes not depending on address family
 fn write_iface_attributes(iface: &Interface, w: &mut dyn Write) -> Result<(), Error> {
     static EMPTY_LIST: Vec<String> = Vec::new();
@@ -652,4 +669,22 @@ iface individual_name inet manual
                 .trim()
         );
     }
+
+    #[test]
+    fn test_vlan_parse_vlan_id_from_name() {
+        assert_eq!(parse_vlan_id_from_name("vlan100"), Some(100));
+        assert_eq!(parse_vlan_id_from_name("vlan"), None);
+        assert_eq!(parse_vlan_id_from_name("arbitrary"), None);
+        assert_eq!(parse_vlan_id_from_name("vmbr0.100"), Some(100));
+        assert_eq!(parse_vlan_id_from_name("vmbr0"), None);
+        // assert_eq!(parse_vlan_id_from_name("vmbr0.1.400"), Some(400));   // NOTE ifupdown2 does actually support this
+    }
+
+    #[test]
+    fn test_vlan_parse_vlan_raw_device_from_name() {
+        assert_eq!(parse_vlan_raw_device_from_name("vlan100"), None);
+        assert_eq!(parse_vlan_raw_device_from_name("arbitrary"), None);
+        assert_eq!(parse_vlan_raw_device_from_name("vmbr0"), None);
+        assert_eq!(parse_vlan_raw_device_from_name("vmbr0.200"), Some("vmbr0"));
+    }
 }
diff --git a/pbs-config/src/network/parser.rs b/pbs-config/src/network/parser.rs
index e409c94c..2158a04f 100644
--- a/pbs-config/src/network/parser.rs
+++ b/pbs-config/src/network/parser.rs
@@ -1,3 +1,5 @@
+use crate::network::VLAN_INTERFACE_REGEX;
+
 use std::collections::{HashMap, HashSet};
 use std::io::BufRead;
 use std::iter::{Iterator, Peekable};
@@ -536,7 +538,6 @@ impl<R: BufRead> NetworkParser<R> {
 
         lazy_static! {
             static ref INTERFACE_ALIAS_REGEX: Regex = Regex::new(r"^\S+:\d+$").unwrap();
-            static ref VLAN_INTERFACE_REGEX: Regex = Regex::new(r"^\S+\.\d+|vlan\d+$").unwrap();
         }
 
         if let Some(existing_interfaces) = existing_interfaces {
diff --git a/src/api2/node/network.rs b/src/api2/node/network.rs
index 187b27a0..92297421 100644
--- a/src/api2/node/network.rs
+++ b/src/api2/node/network.rs
@@ -12,7 +12,9 @@ use pbs_api_types::{
     NETWORK_INTERFACE_ARRAY_SCHEMA, NETWORK_INTERFACE_LIST_SCHEMA, NETWORK_INTERFACE_NAME_SCHEMA,
     NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY, PROXMOX_CONFIG_DIGEST_SCHEMA,
 };
-use pbs_config::network::{self, NetworkConfig};
+use pbs_config::network::{
+    self, parse_vlan_id_from_name, parse_vlan_raw_device_from_name, NetworkConfig,
+};
 
 use proxmox_rest_server::WorkerTask;
 
@@ -231,6 +233,15 @@ pub fn read_interface(iface: String) -> Result<Value, Error> {
                 type: bool,
                 optional: true,
             },
+            "vlan-id": {
+                description: "VLAN ID.",
+                type: u16,
+                optional: true,
+            },
+            "vlan-raw-device": {
+                schema: NETWORK_INTERFACE_NAME_SCHEMA,
+                optional: true,
+            },
             bond_mode: {
                 type: LinuxBondMode,
                 optional: true,
@@ -269,6 +280,8 @@ pub fn create_interface(
     mtu: Option<u64>,
     bridge_ports: Option<String>,
     bridge_vlan_aware: Option<bool>,
+    vlan_id: Option<u16>,
+    vlan_raw_device: Option<String>,
     bond_mode: Option<LinuxBondMode>,
     bond_primary: Option<String>,
     bond_xmit_hash_policy: Option<BondXmitHashPolicy>,
@@ -373,6 +386,24 @@ pub fn create_interface(
                 set_bond_slaves(&mut interface, slaves)?;
             }
         }
+        NetworkInterfaceType::Vlan => {
+            if vlan_id.is_none() && parse_vlan_id_from_name(&iface).is_none() {
+                bail!("vlan-id must be set");
+            }
+            interface.vlan_id = vlan_id;
+
+            if let Some(dev) = vlan_raw_device
+                .as_deref()
+                .or_else(|| parse_vlan_raw_device_from_name(&iface))
+            {
+                if !config.interfaces.contains_key(dev) {
+                    bail!("vlan-raw-device {dev} does not exist");
+                }
+            } else {
+                bail!("vlan-raw-device must be set");
+            }
+            interface.vlan_raw_device = vlan_raw_device;
+        }
         _ => bail!(
             "creating network interface type '{:?}' is not supported",
             interface_type
@@ -507,6 +538,15 @@ pub enum DeletableProperty {
                 type: bool,
                 optional: true,
             },
+            "vlan-id": {
+                description: "VLAN ID.",
+                type: u16,
+                optional: true,
+            },
+            "vlan-raw-device": {
+                schema: NETWORK_INTERFACE_NAME_SCHEMA,
+                optional: true,
+            },
             bond_mode: {
                 type: LinuxBondMode,
                 optional: true,
@@ -557,6 +597,8 @@ pub fn update_interface(
     mtu: Option<u64>,
     bridge_ports: Option<String>,
     bridge_vlan_aware: Option<bool>,
+    vlan_id: Option<u16>,
+    vlan_raw_device: Option<String>,
     bond_mode: Option<LinuxBondMode>,
     bond_primary: Option<String>,
     bond_xmit_hash_policy: Option<BondXmitHashPolicy>,
@@ -581,6 +623,15 @@ pub fn update_interface(
         check_duplicate_gateway_v6(&config, &iface)?;
     }
 
+    if let Some(dev) = vlan_raw_device
+        .as_deref()
+        .or_else(|| parse_vlan_raw_device_from_name(&iface))
+    {
+        if !config.interfaces.contains_key(dev) {
+            bail!("vlan-raw-device {dev} does not exist");
+        }
+    }
+
     let interface = config.lookup_mut(&iface)?;
 
     if let Some(interface_type) = param.get("type") {
@@ -734,6 +785,13 @@ pub fn update_interface(
         interface.method6 = Some(NetworkConfigMethod::Manual);
     }
 
+    if vlan_id.is_some() {
+        interface.vlan_id = vlan_id;
+    }
+    if vlan_raw_device.is_some() {
+        interface.vlan_raw_device = vlan_raw_device;
+    }
+
     network::save_config(&config)?;
 
     Ok(())
-- 
2.44.0





^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v3 9/9] ui: enable vlan widget
       [not found] <20240404095151.184141-1-s.lendl@proxmox.com>
                   ` (7 preceding siblings ...)
  2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 8/9] api: create and update vlan interfaces Stefan Lendl
@ 2024-04-04  9:51 ` Stefan Lendl
  8 siblings, 0 replies; 14+ messages in thread
From: Stefan Lendl @ 2024-04-04  9:51 UTC (permalink / raw)
  To: s.lendl, pbs-devel

* Enabled the "Linux VLAN" option when creating a new interface.
* This requires the updated widget-toolkit to contain vlan field widget.

Signed-off-by: Stefan Lendl <s.lendl@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
---
 www/SystemConfiguration.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/www/SystemConfiguration.js b/www/SystemConfiguration.js
index e94fe7ca..23330b6a 100644
--- a/www/SystemConfiguration.js
+++ b/www/SystemConfiguration.js
@@ -41,7 +41,7 @@ Ext.define('PBS.SystemConfiguration', {
 		    flex: 1,
 		    minHeight: 200,
 		    showApplyBtn: true,
-		    types: ['bond', 'bridge'],
+		    types: ['bond', 'bridge', 'vlan'],
 		    nodename: 'localhost',
 		},
 	    ],
-- 
2.44.0





^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [pbs-devel] [PATCH widget-toolkit v3 1/9] form: include vlan field widget from PVE
  2024-04-04  9:51 ` [pbs-devel] [PATCH widget-toolkit v3 1/9] form: include vlan field widget from PVE Stefan Lendl
@ 2024-04-04 16:29   ` Thomas Lamprecht
  2024-04-05  8:06     ` Stefan Lendl
  2024-04-18  9:26   ` Folke Gleumes
  1 sibling, 1 reply; 14+ messages in thread
From: Thomas Lamprecht @ 2024-04-04 16:29 UTC (permalink / raw)
  To: Proxmox Backup Server development discussion, Stefan Lendl

I guess this can be ignored over the re-send that includes widget-toolkit
and cover-letter:

https://lists.proxmox.com/pipermail/pbs-devel/2024-April/008339.html




^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [pbs-devel] [PATCH widget-toolkit v3 1/9] form: include vlan field widget from PVE
  2024-04-04 16:29   ` Thomas Lamprecht
@ 2024-04-05  8:06     ` Stefan Lendl
  0 siblings, 0 replies; 14+ messages in thread
From: Stefan Lendl @ 2024-04-05  8:06 UTC (permalink / raw)
  To: Thomas Lamprecht, Proxmox Backup Server development discussion

Thomas Lamprecht <t.lamprecht@proxmox.com> writes:

> I guess this can be ignored over the re-send that includes widget-toolkit
> and cover-letter:
>
> https://lists.proxmox.com/pipermail/pbs-devel/2024-April/008339.html

Yes please.




^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [pbs-devel] [PATCH widget-toolkit v3 1/9] form: include vlan field widget from PVE
  2024-04-04  9:51 ` [pbs-devel] [PATCH widget-toolkit v3 1/9] form: include vlan field widget from PVE Stefan Lendl
  2024-04-04 16:29   ` Thomas Lamprecht
@ 2024-04-18  9:26   ` Folke Gleumes
  2024-04-18  9:40     ` Folke Gleumes
  1 sibling, 1 reply; 14+ messages in thread
From: Folke Gleumes @ 2024-04-18  9:26 UTC (permalink / raw)
  To: pbs-devel

Retested the series against the current master on the request of Lukas.



Tested-by: Folke Gleumes <f.gleumes@proxmox.com>

On Thu, 2024-04-04 at 11:51 +0200, Stefan Lendl wrote:
> Copied from PVE to use in PBS network configuration.
> 
> Signed-off-by: Stefan Lendl <s.lendl@proxmox.com>
> Tested-by: Lukas Wagner <l.wagner@proxmox.com>
> Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
> ---
>  src/Makefile            |  1 +
>  src/form/VlanField.js   | 40
> ++++++++++++++++++++++++++++++++++++++++
>  src/node/NetworkEdit.js |  6 +++---
>  3 files changed, 44 insertions(+), 3 deletions(-)
>  create mode 100644 src/form/VlanField.js
> 
> diff --git a/src/Makefile b/src/Makefile
> index 89f9962..0b1e88c 100644
> --- a/src/Makefile
> +++ b/src/Makefile
> @@ -31,6 +31,7 @@ JSSRC=                                        \
>         form/ExpireDate.js              \
>         form/IntegerField.js            \
>         form/TextField.js               \
> +       form/VlanField.js               \
>         form/DateTimeField.js           \
>         form/Checkbox.js                \
>         form/KVComboBox.js              \
> diff --git a/src/form/VlanField.js b/src/form/VlanField.js
> new file mode 100644
> index 0000000..71b580d
> --- /dev/null
> +++ b/src/form/VlanField.js
> @@ -0,0 +1,40 @@
> +Ext.define('Proxmox.form.field.VlanField', {
> +    extend: 'Ext.form.field.Number',
> +    alias: ['widget.proxmoxvlanfield'],
> +
> +    deleteEmpty: false,
> +
> +    emptyText: gettext('no VLAN'),
> +
> +    fieldLabel: gettext('VLAN Tag'),
> +
> +    allowBlank: true,
> +
> +    getSubmitData: function() {
> +       var me = this,
> +           data = null,
> +           val;
> +       if (!me.disabled && me.submitValue) {
> +           val = me.getSubmitValue();
> +           if (val) {
> +               data = {};
> +               data[me.getName()] = val;
> +           } else if (me.deleteEmpty) {
> +               data = {};
> +               data.delete = me.getName();
> +           }
> +       }
> +       return data;
> +    },
> +
> +    initComponent: function() {
> +       var me = this;
> +
> +       Ext.apply(me, {
> +           minValue: 1,
> +           maxValue: 4094,
> +       });
> +
> +       me.callParent();
> +    },
> +});
> diff --git a/src/node/NetworkEdit.js b/src/node/NetworkEdit.js
> index bb9add3..b81a21d 100644
> --- a/src/node/NetworkEdit.js
> +++ b/src/node/NetworkEdit.js
> @@ -97,7 +97,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
>                 name: 'ovs_bridge',
>             });
>             column2.push({
> -               xtype: 'pveVlanField',
> +               xtype: 'proxmoxvlanfield',
>                 deleteEmpty: !me.isCreate,
>                 name: 'ovs_tag',
>                 value: '',
> @@ -140,7 +140,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
>             });
>  
>             column2.push({
> -               xtype: 'pveVlanField',
> +               xtype: 'proxmoxvlanfield',
>                 name: 'vlan-id',
>                 value: me.vlanidvalue,
>                 disabled: me.disablevlanid,
> @@ -211,7 +211,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
>                 name: 'ovs_bridge',
>             });
>             column2.push({
> -               xtype: 'pveVlanField',
> +               xtype: 'proxmoxvlanfield',
>                 deleteEmpty: !me.isCreate,
>                 name: 'ovs_tag',
>                 value: '',

_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [pbs-devel] [PATCH widget-toolkit v3 1/9] form: include vlan field widget from PVE
  2024-04-18  9:26   ` Folke Gleumes
@ 2024-04-18  9:40     ` Folke Gleumes
  0 siblings, 0 replies; 14+ messages in thread
From: Folke Gleumes @ 2024-04-18  9:40 UTC (permalink / raw)
  To: pbs-devel

Just found that the patch was posted again with the same version, found
a small bug, will elaborate there.

On Thu, 2024-04-18 at 11:26 +0200, Folke Gleumes wrote:
> Retested the series against the current master on the request of
> Lukas.
> 
> 
> 
> Tested-by: Folke Gleumes <f.gleumes@proxmox.com>
> 
> On Thu, 2024-04-04 at 11:51 +0200, Stefan Lendl wrote:
> > Copied from PVE to use in PBS network configuration.
> > 
> > Signed-off-by: Stefan Lendl <s.lendl@proxmox.com>
> > Tested-by: Lukas Wagner <l.wagner@proxmox.com>
> > Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
> > ---
> >  src/Makefile            |  1 +
> >  src/form/VlanField.js   | 40
> > ++++++++++++++++++++++++++++++++++++++++
> >  src/node/NetworkEdit.js |  6 +++---
> >  3 files changed, 44 insertions(+), 3 deletions(-)
> >  create mode 100644 src/form/VlanField.js
> > 
> > diff --git a/src/Makefile b/src/Makefile
> > index 89f9962..0b1e88c 100644
> > --- a/src/Makefile
> > +++ b/src/Makefile
> > @@ -31,6 +31,7 @@ JSSRC=                                        \
> >         form/ExpireDate.js              \
> >         form/IntegerField.js            \
> >         form/TextField.js               \
> > +       form/VlanField.js               \
> >         form/DateTimeField.js           \
> >         form/Checkbox.js                \
> >         form/KVComboBox.js              \
> > diff --git a/src/form/VlanField.js b/src/form/VlanField.js
> > new file mode 100644
> > index 0000000..71b580d
> > --- /dev/null
> > +++ b/src/form/VlanField.js
> > @@ -0,0 +1,40 @@
> > +Ext.define('Proxmox.form.field.VlanField', {
> > +    extend: 'Ext.form.field.Number',
> > +    alias: ['widget.proxmoxvlanfield'],
> > +
> > +    deleteEmpty: false,
> > +
> > +    emptyText: gettext('no VLAN'),
> > +
> > +    fieldLabel: gettext('VLAN Tag'),
> > +
> > +    allowBlank: true,
> > +
> > +    getSubmitData: function() {
> > +       var me = this,
> > +           data = null,
> > +           val;
> > +       if (!me.disabled && me.submitValue) {
> > +           val = me.getSubmitValue();
> > +           if (val) {
> > +               data = {};
> > +               data[me.getName()] = val;
> > +           } else if (me.deleteEmpty) {
> > +               data = {};
> > +               data.delete = me.getName();
> > +           }
> > +       }
> > +       return data;
> > +    },
> > +
> > +    initComponent: function() {
> > +       var me = this;
> > +
> > +       Ext.apply(me, {
> > +           minValue: 1,
> > +           maxValue: 4094,
> > +       });
> > +
> > +       me.callParent();
> > +    },
> > +});
> > diff --git a/src/node/NetworkEdit.js b/src/node/NetworkEdit.js
> > index bb9add3..b81a21d 100644
> > --- a/src/node/NetworkEdit.js
> > +++ b/src/node/NetworkEdit.js
> > @@ -97,7 +97,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
> >                 name: 'ovs_bridge',
> >             });
> >             column2.push({
> > -               xtype: 'pveVlanField',
> > +               xtype: 'proxmoxvlanfield',
> >                 deleteEmpty: !me.isCreate,
> >                 name: 'ovs_tag',
> >                 value: '',
> > @@ -140,7 +140,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
> >             });
> >  
> >             column2.push({
> > -               xtype: 'pveVlanField',
> > +               xtype: 'proxmoxvlanfield',
> >                 name: 'vlan-id',
> >                 value: me.vlanidvalue,
> >                 disabled: me.disablevlanid,
> > @@ -211,7 +211,7 @@ Ext.define('Proxmox.node.NetworkEdit', {
> >                 name: 'ovs_bridge',
> >             });
> >             column2.push({
> > -               xtype: 'pveVlanField',
> > +               xtype: 'proxmoxvlanfield',
> >                 deleteEmpty: !me.isCreate,
> >                 name: 'ovs_tag',
> >                 value: '',
> 
> _______________________________________________
> pbs-devel mailing list
> pbs-devel@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel

_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [pbs-devel] [PATCH proxmox-backup v3 4/9] config: write vlan network interface
  2024-04-04 10:00 [pbs-devel] [PATCH widget-toolkit/proxmox-backup v3 0/9] Fix #3115: VLAN Network Interface Configuration Stefan Lendl
@ 2024-04-04 10:00 ` Stefan Lendl
  0 siblings, 0 replies; 14+ messages in thread
From: Stefan Lendl @ 2024-04-04 10:00 UTC (permalink / raw)
  To: pbs-devel

* Add vlan_id and vlan_raw_device fields to the Interface api type
* Write to the network config the vlan specific properties for vlan
  interface type
* Add several tests to verify the functionally

Signed-off-by: Stefan Lendl <s.lendl@proxmox.com>
Tested-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
---
 pbs-api-types/src/network.rs  | 17 ++++++++
 pbs-config/src/network/mod.rs | 73 ++++++++++++++++++++++++++++++++++-
 2 files changed, 89 insertions(+), 1 deletion(-)

diff --git a/pbs-api-types/src/network.rs b/pbs-api-types/src/network.rs
index e3a5e481..fe083dc6 100644
--- a/pbs-api-types/src/network.rs
+++ b/pbs-api-types/src/network.rs
@@ -224,6 +224,15 @@ pub const NETWORK_INTERFACE_LIST_SCHEMA: Schema =
             schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
             optional: true,
         },
+        "vlan-id": {
+            description: "VLAN ID.",
+            type: u16,
+            optional: true,
+        },
+        "vlan-raw-device": {
+            schema: NETWORK_INTERFACE_NAME_SCHEMA,
+            optional: true,
+        },
         bond_mode: {
             type: LinuxBondMode,
             optional: true,
@@ -287,6 +296,12 @@ pub struct Interface {
     /// Enable bridge vlan support.
     #[serde(skip_serializing_if = "Option::is_none")]
     pub bridge_vlan_aware: Option<bool>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(rename = "vlan-id")]
+    pub vlan_id: Option<u16>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(rename = "vlan-raw-device")]
+    pub vlan_raw_device: Option<String>,
 
     #[serde(skip_serializing_if = "Option::is_none")]
     pub slaves: Option<Vec<String>>,
@@ -319,6 +334,8 @@ impl Interface {
             mtu: None,
             bridge_ports: None,
             bridge_vlan_aware: None,
+            vlan_id: None,
+            vlan_raw_device: None,
             slaves: None,
             bond_mode: None,
             bond_primary: None,
diff --git a/pbs-config/src/network/mod.rs b/pbs-config/src/network/mod.rs
index 7fec7e29..02117535 100644
--- a/pbs-config/src/network/mod.rs
+++ b/pbs-config/src/network/mod.rs
@@ -79,6 +79,14 @@ fn write_iface_attributes(iface: &Interface, w: &mut dyn Write) -> Result<(), Er
                 writeln!(w, "\tbond-slaves {}", slaves.join(" "))?;
             }
         }
+        NetworkInterfaceType::Vlan => {
+            if let Some(vlan_id) = iface.vlan_id {
+                writeln!(w, "\tvlan-id {vlan_id}")?;
+            }
+            if let Some(vlan_raw_device) = &iface.vlan_raw_device {
+                writeln!(w, "\tvlan-raw-device {vlan_raw_device}")?;
+            }
+        }
         _ => {}
     }
 
@@ -580,5 +588,68 @@ iface enp3s0 inet static
             .trim()
         );
     }
-}
 
+    #[test]
+    fn test_write_network_config_vlan_id_in_name() {
+        let iface_name = String::from("vmbr0.100");
+        let mut iface = Interface::new(iface_name.clone());
+        iface.interface_type = Vlan;
+        iface.method = Some(Manual);
+        iface.active = true;
+
+        let nw_config = NetworkConfig {
+            interfaces: BTreeMap::from([(iface_name.clone(), iface)]),
+            order: vec![Iface(iface_name.clone())],
+        };
+        assert_eq!(
+            String::try_from(nw_config).unwrap().trim(),
+            "iface vmbr0.100 inet manual"
+        );
+    }
+
+    #[test]
+    fn test_write_network_config_vlan_with_raw_device() {
+        let iface_name = String::from("vlan100");
+        let mut iface = Interface::new(iface_name.clone());
+        iface.interface_type = Vlan;
+        iface.vlan_raw_device = Some(String::from("vmbr0"));
+        iface.method = Some(Manual);
+        iface.active = true;
+
+        let nw_config = NetworkConfig {
+            interfaces: BTreeMap::from([(iface_name.clone(), iface)]),
+            order: vec![Iface(iface_name.clone())],
+        };
+        assert_eq!(
+            String::try_from(nw_config).unwrap().trim(),
+            r#"
+iface vlan100 inet manual
+	vlan-raw-device vmbr0"#
+                .trim()
+        );
+    }
+
+    #[test]
+    fn test_write_network_config_vlan_with_individual_name() {
+        let iface_name = String::from("individual_name");
+        let mut iface = Interface::new(iface_name.clone());
+        iface.interface_type = Vlan;
+        iface.vlan_raw_device = Some(String::from("vmbr0"));
+        iface.vlan_id = Some(100);
+        iface.method = Some(Manual);
+        iface.active = true;
+
+        let nw_config = NetworkConfig {
+            interfaces: BTreeMap::from([(iface_name.clone(), iface)]),
+            order: vec![Iface(iface_name.clone())],
+        };
+        assert_eq!(
+            String::try_from(nw_config).unwrap().trim(),
+            r#"
+iface individual_name inet manual
+	vlan-id 100
+	vlan-raw-device vmbr0"#
+                .trim()
+        );
+    }
+}
-- 
2.44.0





^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2024-04-18  9:40 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <20240404095151.184141-1-s.lendl@proxmox.com>
2024-04-04  9:51 ` [pbs-devel] [PATCH widget-toolkit v3 1/9] form: include vlan field widget from PVE Stefan Lendl
2024-04-04 16:29   ` Thomas Lamprecht
2024-04-05  8:06     ` Stefan Lendl
2024-04-18  9:26   ` Folke Gleumes
2024-04-18  9:40     ` Folke Gleumes
2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 2/9] tests: move network tests to parser.rs Stefan Lendl
2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 3/9] tests: simple tests for writing the network config Stefan Lendl
2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 4/9] config: write vlan network interface Stefan Lendl
2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 5/9] config: parse vlan interface from config Stefan Lendl
2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 6/9] config: remove unnecessary pub in various methods in NetworkConfig Stefan Lendl
2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 7/9] fmt: fix intendation in api macro Stefan Lendl
2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 8/9] api: create and update vlan interfaces Stefan Lendl
2024-04-04  9:51 ` [pbs-devel] [PATCH proxmox-backup v3 9/9] ui: enable vlan widget Stefan Lendl
2024-04-04 10:00 [pbs-devel] [PATCH widget-toolkit/proxmox-backup v3 0/9] Fix #3115: VLAN Network Interface Configuration Stefan Lendl
2024-04-04 10:00 ` [pbs-devel] [PATCH proxmox-backup v3 4/9] config: write vlan network interface Stefan Lendl

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal