Blob Blame History Raw
From 8e8e1dffc528b738f92cd508d7a6faf58e40af00 Mon Sep 17 00:00:00 2001
From: Scott Moser <smoser@brickies.net>
Date: Wed, 15 Mar 2017 12:06:40 -0400
Subject: [PATCH 1/2] support 'loopback' as a device type.

As reported in bug 1671927, sysconfig had an issue with rendering
a loopback device.  The problem was that some as yet unknown issue was
causing the openstack config drive to parse the provided ENI file rather
than reading the network_data.json.  Parsing an ENI file would add a
a 'lo' device of type 'physical', and sysconfig was failing to render
that.

The change here is:
 a.) add a 'loopback' type rather than 'physical' for network config.
     {'name': 'lo', 'type': 'loopback', 'subnets': ['type': 'loopback']}
 b.) support skipping that type in the eni and sysconfig renderers.
 c.) make network_state just piggy back on 'physical' renderer for
     loopback (this was what was happening before).

Tests are added for eni and sysconfig renderer.

(cherry picked from commit 1a2ca7530518d819cbab7287b12f942743427e38)

Related: rhbz#1492726
Signed-off-by: Ryan McCabe <rmccabe@redhat.com>
---
 cloudinit/net/eni.py           | 16 +++++++++------
 cloudinit/net/network_state.py |  4 ++++
 cloudinit/net/sysconfig.py     |  2 ++
 tests/unittests/test_net.py    | 44 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 60 insertions(+), 6 deletions(-)

diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py
index 5b249f1f..69ecbb5d 100644
--- a/cloudinit/net/eni.py
+++ b/cloudinit/net/eni.py
@@ -273,8 +273,11 @@ def _ifaces_to_net_config_data(ifaces):
         # devname is 'eth0' for name='eth0:1'
         devname = name.partition(":")[0]
         if devname not in devs:
-            devs[devname] = {'type': 'physical', 'name': devname,
-                             'subnets': []}
+            if devname == "lo":
+                dtype = "loopback"
+            else:
+                dtype = "physical"
+            devs[devname] = {'type': dtype, 'name': devname, 'subnets': []}
             # this isnt strictly correct, but some might specify
             # hwaddress on a nic for matching / declaring name.
             if 'hwaddress' in data:
@@ -423,10 +426,11 @@ class Renderer(renderer.Renderer):
             bonding
         '''
         order = {
-            'physical': 0,
-            'bond': 1,
-            'bridge': 2,
-            'vlan': 3,
+            'loopback': 0,
+            'physical': 1,
+            'bond': 2,
+            'bridge': 3,
+            'vlan': 4,
         }
 
         sections = []
diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py
index 11ef585b..90b2835a 100644
--- a/cloudinit/net/network_state.py
+++ b/cloudinit/net/network_state.py
@@ -212,6 +212,10 @@ class NetworkStateInterpreter(object):
                     LOG.debug(self.dump_network_state())
 
     @ensure_command_keys(['name'])
+    def handle_loopback(self, command):
+        return self.handle_physical(command)
+
+    @ensure_command_keys(['name'])
     def handle_physical(self, command):
         '''
         command = {
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
index efd101ca..25c29104 100644
--- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py
@@ -500,6 +500,8 @@ class Renderer(renderer.Renderer):
         '''Given state, return /etc/sysconfig files + contents'''
         iface_contents = {}
         for iface in network_state.iter_interfaces():
+            if iface['type'] == "loopback":
+                continue
             iface_name = iface['name']
             iface_cfg = NetInterface(iface_name, base_sysconf_dir)
             cls._render_iface_shared(iface, iface_cfg)
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 4c0e3ad3..7e389c10 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -615,6 +615,14 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true
     }
 }
 
+CONFIG_V1_EXPLICIT_LOOPBACK = {
+    'version': 1,
+    'config': [{'name': 'eth0', 'type': 'physical',
+               'subnets': [{'control': 'auto', 'type': 'dhcp'}]},
+               {'name': 'lo', 'type': 'loopback',
+                'subnets': [{'control': 'auto', 'type': 'loopback'}]},
+               ]}
+
 
 def _setup_test(tmp_dir, mock_get_devicelist, mock_read_sys_net,
                 mock_sys_dev_path):
@@ -785,6 +793,27 @@ USERCTL=no
                 with open(os.path.join(render_dir, fn)) as fh:
                     self.assertEqual(expected_content, fh.read())
 
+    def test_config_with_explicit_loopback(self):
+        ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK)
+        render_dir = self.tmp_path("render")
+        os.makedirs(render_dir)
+        renderer = sysconfig.Renderer()
+        renderer.render_network_state(render_dir, ns)
+        found = dir2dict(render_dir)
+        nspath = '/etc/sysconfig/network-scripts/'
+        self.assertNotIn(nspath + 'ifcfg-lo', found.keys())
+        expected = """\
+# Created by cloud-init on instance boot automatically, do not edit.
+#
+BOOTPROTO=dhcp
+DEVICE=eth0
+NM_CONTROLLED=no
+ONBOOT=yes
+TYPE=Ethernet
+USERCTL=no
+"""
+        self.assertEqual(expected, found[nspath + 'ifcfg-eth0'])
+
 
 class TestEniNetRendering(TestCase):
 
@@ -826,6 +855,21 @@ iface eth1000 inet dhcp
 """
         self.assertEqual(expected.lstrip(), contents.lstrip())
 
+    def test_config_with_explicit_loopback(self):
+        tmp_dir = self.tmp_dir()
+        ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK)
+        renderer = eni.Renderer()
+        renderer.render_network_state(tmp_dir, ns)
+        expected = """\
+auto lo
+iface lo inet loopback
+
+auto eth0
+iface eth0 inet dhcp
+"""
+        self.assertEqual(
+            expected, dir2dict(tmp_dir)['/etc/network/interfaces'])
+
 
 class TestEniNetworkStateToEni(TestCase):
     mycfg = {
-- 
2.13.6