Repository: openwisp/netjsonconfig

Issue:
[bug:openwrt] Creating VLAN 802.1q interface without "network" field
**Describe the bug**
Creating VLAN 802.1q field with a blank interface will generate an invalid configuration.


**Steps To Reproduce**

Try generating OpenWrt configuration using the following NetJSON 


```json
{
            "type": "8021q",
            "name": "br-wifi",
            "network": "",
            "disabled": false,
            "vid": 200,
            "ingress_qos_mapping": [],
            "egress_qos_mapping": []
}
```


**Generated Configuration**

```text
config device 'device_'
	option ifname 'br-wifi'
	option name 'br-wifi.200'
	option type '8021q'
	option vid '200'

config interface ''
	option device 'br-wifi.200'
	option enabled '1'
	option proto 'none'
```

Hints from issue comments:
@shwetd19: 
Hey @pandafy @nemesifier , I've analyzed the problem and have a proposed solution.

## Issue Analysis

The bug occurs when creating an 802.1q VLAN interface with an empty network field. The current behavior generates an invalid OpenWrt configuration by creating an interface section with an empty name (`config interface ''`), which is not a valid UCI configuration.

## Current Behavior

When the `network` field is an empty string, the converter still creates an interface section but with an empty name, resulting in:

```
config interface ''    # <-- This is invalid
    option device 'br-wifi.200'
    option enabled '1'
    option proto 'none'
```

## Expected Behavior

When the `network` field is empty or not specified, we should only create the device configuration without generating an associated interface section, resulting in:

```
config device 'device_'
    option ifname 'br-wifi'
    option name 'br-wifi.200'
    option type '8021q'
    option vid '200'
```

## Proposed Solution

We should modify the converter to only generate the interface section when the network field has a valid value. Here's a proposed code change:

```python
def render_interface(self, interface, **kwargs):
    network = interface.get('network')
    # Only generate interface section if network field has a value
    if not network:
        return self._render_device_config(interface)
    
    # Rest of the existing interface rendering code
    ...
```

This change ensures we maintain backward compatibility while fixing the invalid configuration generation.

Would you like me to prepare a pull request with this fix? Please let me know if you need any clarification or have questions about the proposed solution.@nemesifier: @shwetd19 yes go ahead, no need to ask for permission.

Solution patch:
diff --git a/netjsonconfig/backends/openwrt/converters/interfaces.py b/netjsonconfig/backends/openwrt/converters/interfaces.py
index c076814..3cdd754 100644
--- a/netjsonconfig/backends/openwrt/converters/interfaces.py
+++ b/netjsonconfig/backends/openwrt/converters/interfaces.py
@@ -232,8 +232,8 @@ class Interfaces(OpenWrtConverter):
 
     def _intermediate_8021_vlan(self, interface):
         interface['name'] = '{}.{}'.format(interface['ifname'], interface['vid'])
-        interface['.name'] = interface.get(
-            'network', 'vlan_{}_{}'.format(interface['.name'], interface['vid'])
+        interface['.name'] = interface.get('network') or 'vlan_{}_{}'.format(
+            interface['.name'], interface['vid']
         )
         return interface
 


Test patch:
diff --git a/tests/openwrt/test_interfaces_dsa.py b/tests/openwrt/test_interfaces_dsa.py
index 5e3d524..e82b121 100644
--- a/tests/openwrt/test_interfaces_dsa.py
+++ b/tests/openwrt/test_interfaces_dsa.py
@@ -2029,6 +2029,13 @@ config interface 'vlan_br_lan_1'
         expected['interfaces'][0]['network'] = 'vlan_br_lan_1'
         self.assertEqual(expected, o.config)
 
+    def test_render_vlan8021q_empty_network(self):
+        netjson = deepcopy(self._vlan8021q_netjson)
+        netjson['interfaces'][0]['network'] = ''
+        o = OpenWrt(netjson)
+        expected = self._tabs(self._vlan8021q_uci)
+        self.assertEqual(o.render(), expected)
+
     _vlan8021ad_netjson = {
         "interfaces": [
             {"type": "8021ad", "vid": 6, "name": "eth0", "network": "iot_vlan"}