← Back to team overview

cloud-init-dev team mailing list archive

[Merge] ~raharper/cloud-init:fix/cloud-init-network-rename-with-v2 into cloud-init:master

 

Ryan Harper has proposed merging ~raharper/cloud-init:fix/cloud-init-network-rename-with-v2 into cloud-init:master.

Requested reviews:
  cloud-init commiters (cloud-init-dev)
Related bugs:
  Bug #1709715 in cloud-init: "cloud-init apply_net_config_names doesn't grok v2 configs"
  https://bugs.launchpad.net/cloud-init/+bug/1709715

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

net: accept network-config in netplan format for renaming interfaces
    
net.apply_network_config_names currently only accepts network-config
in version 1 format.  When users include a netplan format network-config the
rename code does not find any of the 'set-name' directives and does not rename
any of the interfaces.  This causes some netplan configurations to fail.

This patch adds support for parsing netplan format and extracts the needed
information (macaddress and set-name values) to allow cloud-init to issue
interface rename commands.

LP: #1709715

-- 
Your team cloud-init commiters is requested to review the proposed merge of ~raharper/cloud-init:fix/cloud-init-network-rename-with-v2 into cloud-init:master.
diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py
index a1b0db1..c3a8add 100644
--- a/cloudinit/net/__init__.py
+++ b/cloudinit/net/__init__.py
@@ -275,20 +275,45 @@ def apply_network_config_names(netcfg, strict_present=True, strict_busy=True):
     expected that the network system will create other devices with the
     correct name in place."""
     renames = []
-    for ent in netcfg.get('config', {}):
-        if ent.get('type') != 'physical':
-            continue
-        mac = ent.get('mac_address')
-        if not mac:
-            continue
-        name = ent.get('name')
-        driver = ent.get('params', {}).get('driver')
-        device_id = ent.get('params', {}).get('device_id')
-        if not driver:
-            driver = device_driver(name)
-        if not device_id:
-            device_id = device_devid(name)
-        renames.append([mac, name, driver, device_id])
+
+    def _version_1(netcfg):
+        for ent in netcfg.get('config', {}):
+            if ent.get('type') != 'physical':
+                continue
+            mac = ent.get('mac_address')
+            if not mac:
+                continue
+            name = ent.get('name')
+            driver = ent.get('params', {}).get('driver')
+            device_id = ent.get('params', {}).get('device_id')
+            if not driver:
+                driver = device_driver(name)
+            if not device_id:
+                device_id = device_devid(name)
+            renames.append([mac, name, driver, device_id])
+
+    def _version_2(netcfg):
+        for key, ent in netcfg.get('ethernets', {}).items():
+            # only rename if configured to do so
+            name = ent.get('set-name')
+            if not name:
+                continue
+            # cloud-init requires macaddress for renaming
+            mac = ent.get('match', {}).get('macaddress')
+            if not mac:
+                continue
+            driver = ent.get('match', {}).get('driver')
+            device_id = ent.get('match', {}).get('device_id')
+            if not driver:
+                driver = device_driver(name)
+            if not device_id:
+                device_id = device_devid(name)
+            renames.append([mac, name, driver, device_id])
+
+    if netcfg.get('version') == 1:
+        _version_1(netcfg)
+    elif netcfg.get('version') == 2:
+        _version_2(netcfg)
 
     return _rename_interfaces(renames)
 
diff --git a/cloudinit/net/tests/test_init.py b/cloudinit/net/tests/test_init.py
index 8cb4114..7ac9041 100644
--- a/cloudinit/net/tests/test_init.py
+++ b/cloudinit/net/tests/test_init.py
@@ -4,6 +4,8 @@ import copy
 import errno
 import mock
 import os
+import textwrap
+import yaml
 
 import cloudinit.net as net
 from cloudinit.util import ensure_file, write_file, ProcessExecutionError
@@ -520,3 +522,88 @@ class TestEphemeralIPV4Network(CiTestCase):
         with net.EphemeralIPv4Network(**params):
             self.assertEqual(expected_setup_calls, m_subp.call_args_list)
         m_subp.assert_has_calls(expected_teardown_calls)
+
+
+class TestApplyNetworkCfgNames(CiTestCase):
+    V1_CONFIG = textwrap.dedent("""\
+        version: 1
+        config:
+            - type: physical
+              name: interface0
+              mac_address: "52:54:00:12:34:00"
+              subnets:
+                  - type: static
+                    address: 10.0.2.15
+                    netmask: 255.255.255.0
+                    gateway: 10.0.2.2
+    """)
+    V2_CONFIG = textwrap.dedent("""\
+      version: 2
+      ethernets:
+          interface0:
+            match:
+              macaddress: "52:54:00:12:34:00"
+            addresses:
+              - 10.0.2.15/24
+            gateway4: 10.0.2.2
+            set-name: interface0
+    """)
+
+    V2_CONFIG_NO_SETNAME = textwrap.dedent("""\
+      version: 2
+      ethernets:
+          interface0:
+            match:
+              macaddress: "52:54:00:12:34:00"
+            addresses:
+              - 10.0.2.15/24
+            gateway4: 10.0.2.2
+    """)
+
+    V2_CONFIG_NO_MAC = textwrap.dedent("""\
+      version: 2
+      ethernets:
+          interface0:
+            match:
+              driver: virtio-net
+            addresses:
+              - 10.0.2.15/24
+            gateway4: 10.0.2.2
+            set-name: interface0
+    """)
+
+    @mock.patch('cloudinit.net.device_devid')
+    @mock.patch('cloudinit.net.device_driver')
+    @mock.patch('cloudinit.net._rename_interfaces')
+    def test_apply_v1_renames(self, m_rename_interfaces, m_device_driver,
+                              m_device_devid):
+        m_device_driver.return_value = 'virtio_net'
+        m_device_devid.return_value = '0x15d8'
+
+        net.apply_network_config_names(yaml.load(self.V1_CONFIG))
+
+        call = ['52:54:00:12:34:00', 'interface0', 'virtio_net', '0x15d8']
+        m_rename_interfaces.assert_called_with([call])
+
+    @mock.patch('cloudinit.net.device_devid')
+    @mock.patch('cloudinit.net.device_driver')
+    @mock.patch('cloudinit.net._rename_interfaces')
+    def test_apply_v2_renames(self, m_rename_interfaces, m_device_driver,
+                              m_device_devid):
+        m_device_driver.return_value = 'virtio_net'
+        m_device_devid.return_value = '0x15d8'
+
+        net.apply_network_config_names(yaml.load(self.V2_CONFIG))
+
+        call = ['52:54:00:12:34:00', 'interface0', 'virtio_net', '0x15d8']
+        m_rename_interfaces.assert_called_with([call])
+
+    @mock.patch('cloudinit.net._rename_interfaces')
+    def test_apply_v2_renames_skips_without_setname(self, m_rename_interfaces):
+        net.apply_network_config_names(yaml.load(self.V2_CONFIG_NO_SETNAME))
+        m_rename_interfaces.assert_called_with([])
+
+    @mock.patch('cloudinit.net._rename_interfaces')
+    def test_apply_v2_renames_skips_without_mac(self, m_rename_interfaces):
+        net.apply_network_config_names(yaml.load(self.V2_CONFIG_NO_MAC))
+        m_rename_interfaces.assert_called_with([])

Follow ups