← Back to team overview

cloud-init-dev team mailing list archive

[Merge] ~smoser/cloud-init:fix/1788915-vlan-sysconfig-rendering into cloud-init:master

 

Scott Moser has proposed merging ~smoser/cloud-init:fix/1788915-vlan-sysconfig-rendering into cloud-init:master.

Commit message:
network: Fix type and respect name when rendering vlan in sysconfig.

Prior to this change, vlans were rendered in sysconfig with
'TYPE=Ethernet', and incorrectly renders the PHYSDEV based on
the name of the vlan device rather than the 'link' provided
in the network config.

The change here fixes:
 * rendering of TYPE=Ethernet for a vlan
 * adds a warning if the configured device name is not supported
   per the RHEL 7 docs "11.5. Naming Scheme for VLAN Interfaces"

LP: #1788915
LP: #1826608

Requested reviews:
  cloud-init commiters (cloud-init-dev)
Related bugs:
  Bug #1788915 in cloud-init: "sysconfig renders vlan with TYPE=Ethernet"
  https://bugs.launchpad.net/cloud-init/+bug/1788915
  Bug #1826608 in cloud-init: "sysconfig rendering ignores vlan name"
  https://bugs.launchpad.net/cloud-init/+bug/1826608

For more details, see:
https://code.launchpad.net/~smoser/cloud-init/+git/cloud-init/+merge/366602

see commit message
-- 
Your team cloud-init commiters is requested to review the proposed merge of ~smoser/cloud-init:fix/1788915-vlan-sysconfig-rendering into cloud-init:master.
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
index a47da0a..6118552 100644
--- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py
@@ -96,6 +96,9 @@ class ConfigMap(object):
     def __len__(self):
         return len(self._conf)
 
+    def str_skipped(self, key, val):
+        return False
+
     def to_string(self):
         buf = six.StringIO()
         buf.write(_make_header())
@@ -103,6 +106,8 @@ class ConfigMap(object):
             buf.write("\n")
         for key in sorted(self._conf.keys()):
             value = self._conf[key]
+            if self.str_skipped(key, value):
+                continue
             if isinstance(value, bool):
                 value = self._bool_map[value]
             if not isinstance(value, six.string_types):
@@ -208,6 +213,7 @@ class NetInterface(ConfigMap):
         'bond': 'Bond',
         'bridge': 'Bridge',
         'infiniband': 'InfiniBand',
+        'vlan': 'Vlan',
     }
 
     def __init__(self, iface_name, base_sysconf_dir, templates,
@@ -261,6 +267,11 @@ class NetInterface(ConfigMap):
             c.routes = self.routes.copy()
         return c
 
+    def str_skipped(self, key, val):
+        if key == 'TYPE' and val == 'Vlan':
+            return True
+        return False
+
 
 class Renderer(renderer.Renderer):
     """Renders network information in a /etc/sysconfig format."""
@@ -555,7 +566,16 @@ class Renderer(renderer.Renderer):
             iface_name = iface['name']
             iface_cfg = iface_contents[iface_name]
             iface_cfg['VLAN'] = True
-            iface_cfg['PHYSDEV'] = iface_name[:iface_name.rfind('.')]
+            iface_cfg.kind = 'vlan'
+
+            rdev = iface['vlan-raw-device']
+            supported = _supported_vlan_names(rdev, iface['vlan_id'])
+            if iface_name not in supported:
+                LOG.warning(
+                    "Name '%s' not officially supported for vlan "
+                    "device backed by '%s'. Supported: %s",
+                    iface_name, rdev, ' '.join(supported))
+            iface_cfg['PHYSDEV'] = rdev
 
             iface_subnets = iface.get("subnets", [])
             route_cfg = iface_cfg.routes
@@ -716,6 +736,15 @@ class Renderer(renderer.Renderer):
                             "\n".join(netcfg) + "\n", file_mode)
 
 
+def _supported_vlan_names(rdev, vid):
+    """Return list of supported names for vlan devices per RHEL doc
+    11.5. Naming Scheme for VLAN Interfaces."""
+    return [
+        v.format(rdev=rdev, vid=int(vid))
+        for v in ("{rdev}{vid:04}", "{rdev}{vid}",
+                  "{rdev}.{vid:04}", "{rdev}.{vid}")]
+
+
 def available(target=None):
     sysconfig = available_sysconfig(target=target)
     nm = available_nm(target=target)
diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py
index c3c0c8c..b0ef4aa 100644
--- a/tests/unittests/test_distros/test_netconfig.py
+++ b/tests/unittests/test_distros/test_netconfig.py
@@ -526,6 +526,91 @@ class TestNetCfgDistroRedhat(TestNetCfgDistroBase):
                                V1_NET_CFG_IPV6,
                                expected_cfgs=expected_cfgs.copy())
 
+    def test_vlan_render_unsupported(self):
+        """Render officially unsupported vlan names."""
+        cfg = {
+            'version': 2,
+            'ethernets': {
+                'eth0': {'addresses': ["192.10.1.2/24"],
+                         'match': {'macaddress': "00:16:3e:60:7c:df"}}},
+            'vlans': {
+                'infra0': {'addresses': ["10.0.1.2/16"],
+                           'id': 1001, 'link': 'eth0'}},
+        }
+        expected_cfgs = {
+            self.ifcfg_path('eth0'): dedent("""\
+                BOOTPROTO=none
+                DEVICE=eth0
+                HWADDR=00:16:3e:60:7c:df
+                IPADDR=192.10.1.2
+                NETMASK=255.255.255.0
+                NM_CONTROLLED=no
+                ONBOOT=yes
+                STARTMODE=auto
+                TYPE=Ethernet
+                USERCTL=no
+                """),
+            self.ifcfg_path('infra0'): dedent("""\
+                BOOTPROTO=none
+                DEVICE=infra0
+                IPADDR=10.0.1.2
+                NETMASK=255.255.0.0
+                NM_CONTROLLED=no
+                ONBOOT=yes
+                PHYSDEV=eth0
+                STARTMODE=auto
+                USERCTL=no
+                VLAN=yes
+                """),
+            self.control_path(): dedent("""\
+                NETWORKING=yes
+                """),
+        }
+        self._apply_and_verify(
+            self.distro.apply_network_config, cfg,
+            expected_cfgs=expected_cfgs)
+
+    def test_vlan_render(self):
+        cfg = {
+            'version': 2,
+            'ethernets': {
+                'eth0': {'addresses': ["192.10.1.2/24"]}},
+            'vlans': {
+                'eth0.1001': {'addresses': ["10.0.1.2/16"],
+                              'id': 1001, 'link': 'eth0'}},
+        }
+        expected_cfgs = {
+            self.ifcfg_path('eth0'): dedent("""\
+                BOOTPROTO=none
+                DEVICE=eth0
+                IPADDR=192.10.1.2
+                NETMASK=255.255.255.0
+                NM_CONTROLLED=no
+                ONBOOT=yes
+                STARTMODE=auto
+                TYPE=Ethernet
+                USERCTL=no
+                """),
+            self.ifcfg_path('eth0.1001'): dedent("""\
+                BOOTPROTO=none
+                DEVICE=eth0.1001
+                IPADDR=10.0.1.2
+                NETMASK=255.255.0.0
+                NM_CONTROLLED=no
+                ONBOOT=yes
+                PHYSDEV=eth0
+                STARTMODE=auto
+                USERCTL=no
+                VLAN=yes
+                """),
+            self.control_path(): dedent("""\
+                NETWORKING=yes
+                """),
+        }
+        self._apply_and_verify(
+            self.distro.apply_network_config, cfg,
+            expected_cfgs=expected_cfgs)
+
 
 class TestNetCfgDistroOpensuse(TestNetCfgDistroBase):
 

Follow ups