5a2e6e
From 4aa2305022e5d42d858102941c51d7186ef8fef9 Mon Sep 17 00:00:00 2001
5a2e6e
From: Scott Moser <smoser@brickies.net>
5a2e6e
Date: Wed, 15 Mar 2017 12:06:40 -0400
5a2e6e
Subject: [PATCH 1/3] support 'loopback' as a device type.
5a2e6e
5a2e6e
As reported in bug 1671927, sysconfig had an issue with rendering
5a2e6e
a loopback device.  The problem was that some as yet unknown issue was
5a2e6e
causing the openstack config drive to parse the provided ENI file rather
5a2e6e
than reading the network_data.json.  Parsing an ENI file would add a
5a2e6e
a 'lo' device of type 'physical', and sysconfig was failing to render
5a2e6e
that.
5a2e6e
5a2e6e
The change here is:
5a2e6e
 a.) add a 'loopback' type rather than 'physical' for network config.
5a2e6e
     {'name': 'lo', 'type': 'loopback', 'subnets': ['type': 'loopback']}
5a2e6e
 b.) support skipping that type in the eni and sysconfig renderers.
5a2e6e
 c.) make network_state just piggy back on 'physical' renderer for
5a2e6e
     loopback (this was what was happening before).
5a2e6e
5a2e6e
Tests are added for eni and sysconfig renderer.
5a2e6e
5a2e6e
(cherry picked from commit 1a2ca7530518d819cbab7287b12f942743427e38)
5a2e6e
5a2e6e
Related: rhbz#1540094
5a2e6e
---
5a2e6e
 cloudinit/net/eni.py           | 16 +++++++++------
5a2e6e
 cloudinit/net/network_state.py |  4 ++++
5a2e6e
 cloudinit/net/sysconfig.py     |  2 ++
5a2e6e
 tests/unittests/test_net.py    | 44 ++++++++++++++++++++++++++++++++++++++++++
5a2e6e
 4 files changed, 60 insertions(+), 6 deletions(-)
5a2e6e
5a2e6e
diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py
5a2e6e
index b06ffac9..0d5e9712 100644
5a2e6e
--- a/cloudinit/net/eni.py
5a2e6e
+++ b/cloudinit/net/eni.py
5a2e6e
@@ -265,8 +265,11 @@ def _ifaces_to_net_config_data(ifaces):
5a2e6e
         # devname is 'eth0' for name='eth0:1'
5a2e6e
         devname = name.partition(":")[0]
5a2e6e
         if devname not in devs:
5a2e6e
-            devs[devname] = {'type': 'physical', 'name': devname,
5a2e6e
-                             'subnets': []}
5a2e6e
+            if devname == "lo":
5a2e6e
+                dtype = "loopback"
5a2e6e
+            else:
5a2e6e
+                dtype = "physical"
5a2e6e
+            devs[devname] = {'type': dtype, 'name': devname, 'subnets': []}
5a2e6e
             # this isnt strictly correct, but some might specify
5a2e6e
             # hwaddress on a nic for matching / declaring name.
5a2e6e
             if 'hwaddress' in data:
5a2e6e
@@ -418,10 +421,11 @@ class Renderer(renderer.Renderer):
5a2e6e
             bonding
5a2e6e
         '''
5a2e6e
         order = {
5a2e6e
-            'physical': 0,
5a2e6e
-            'bond': 1,
5a2e6e
-            'bridge': 2,
5a2e6e
-            'vlan': 3,
5a2e6e
+            'loopback': 0,
5a2e6e
+            'physical': 1,
5a2e6e
+            'bond': 2,
5a2e6e
+            'bridge': 3,
5a2e6e
+            'vlan': 4,
5a2e6e
         }
5a2e6e
 
5a2e6e
         sections = []
5a2e6e
diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py
5a2e6e
index 11ef585b..90b2835a 100644
5a2e6e
--- a/cloudinit/net/network_state.py
5a2e6e
+++ b/cloudinit/net/network_state.py
5a2e6e
@@ -211,6 +211,10 @@ class NetworkStateInterpreter(object):
5a2e6e
                              exc_info=True)
5a2e6e
                     LOG.debug(self.dump_network_state())
5a2e6e
 
5a2e6e
+    @ensure_command_keys(['name'])
5a2e6e
+    def handle_loopback(self, command):
5a2e6e
+        return self.handle_physical(command)
5a2e6e
+
5a2e6e
     @ensure_command_keys(['name'])
5a2e6e
     def handle_physical(self, command):
5a2e6e
         '''
5a2e6e
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
5a2e6e
index efd101ca..25c29104 100644
5a2e6e
--- a/cloudinit/net/sysconfig.py
5a2e6e
+++ b/cloudinit/net/sysconfig.py
5a2e6e
@@ -500,6 +500,8 @@ class Renderer(renderer.Renderer):
5a2e6e
         '''Given state, return /etc/sysconfig files + contents'''
5a2e6e
         iface_contents = {}
5a2e6e
         for iface in network_state.iter_interfaces():
5a2e6e
+            if iface['type'] == "loopback":
5a2e6e
+                continue
5a2e6e
             iface_name = iface['name']
5a2e6e
             iface_cfg = NetInterface(iface_name, base_sysconf_dir)
5a2e6e
             cls._render_iface_shared(iface, iface_cfg)
5a2e6e
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
5a2e6e
index ffa911cc..d75742be 100644
5a2e6e
--- a/tests/unittests/test_net.py
5a2e6e
+++ b/tests/unittests/test_net.py
5a2e6e
@@ -600,6 +600,14 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true
5a2e6e
     }
5a2e6e
 }
5a2e6e
 
5a2e6e
+CONFIG_V1_EXPLICIT_LOOPBACK = {
5a2e6e
+    'version': 1,
5a2e6e
+    'config': [{'name': 'eth0', 'type': 'physical',
5a2e6e
+               'subnets': [{'control': 'auto', 'type': 'dhcp'}]},
5a2e6e
+               {'name': 'lo', 'type': 'loopback',
5a2e6e
+                'subnets': [{'control': 'auto', 'type': 'loopback'}]},
5a2e6e
+               ]}
5a2e6e
+
5a2e6e
 
5a2e6e
 def _setup_test(tmp_dir, mock_get_devicelist, mock_read_sys_net,
5a2e6e
                 mock_sys_dev_path):
5a2e6e
@@ -770,6 +778,27 @@ USERCTL=no
5a2e6e
                 with open(os.path.join(render_dir, fn)) as fh:
5a2e6e
                     self.assertEqual(expected_content, fh.read())
5a2e6e
 
5a2e6e
+    def test_config_with_explicit_loopback(self):
5a2e6e
+        ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK)
5a2e6e
+        render_dir = self.tmp_path("render")
5a2e6e
+        os.makedirs(render_dir)
5a2e6e
+        renderer = sysconfig.Renderer()
5a2e6e
+        renderer.render_network_state(render_dir, ns)
5a2e6e
+        found = dir2dict(render_dir)
5a2e6e
+        nspath = '/etc/sysconfig/network-scripts/'
5a2e6e
+        self.assertNotIn(nspath + 'ifcfg-lo', found.keys())
5a2e6e
+        expected = """\
5a2e6e
+# Created by cloud-init on instance boot automatically, do not edit.
5a2e6e
+#
5a2e6e
+BOOTPROTO=dhcp
5a2e6e
+DEVICE=eth0
5a2e6e
+NM_CONTROLLED=no
5a2e6e
+ONBOOT=yes
5a2e6e
+TYPE=Ethernet
5a2e6e
+USERCTL=no
5a2e6e
+"""
5a2e6e
+        self.assertEqual(expected, found[nspath + 'ifcfg-eth0'])
5a2e6e
+
5a2e6e
 
5a2e6e
 class TestEniNetRendering(TestCase):
5a2e6e
 
5a2e6e
@@ -811,6 +840,21 @@ iface eth1000 inet dhcp
5a2e6e
 """
5a2e6e
         self.assertEqual(expected.lstrip(), contents.lstrip())
5a2e6e
 
5a2e6e
+    def test_config_with_explicit_loopback(self):
5a2e6e
+        tmp_dir = self.tmp_dir()
5a2e6e
+        ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK)
5a2e6e
+        renderer = eni.Renderer()
5a2e6e
+        renderer.render_network_state(tmp_dir, ns)
5a2e6e
+        expected = """\
5a2e6e
+auto lo
5a2e6e
+iface lo inet loopback
5a2e6e
+
5a2e6e
+auto eth0
5a2e6e
+iface eth0 inet dhcp
5a2e6e
+"""
5a2e6e
+        self.assertEqual(
5a2e6e
+            expected, dir2dict(tmp_dir)['/etc/network/interfaces'])
5a2e6e
+
5a2e6e
 
5a2e6e
 class TestEniNetworkStateToEni(TestCase):
5a2e6e
     mycfg = {
5a2e6e
-- 
5a2e6e
2.14.3
5a2e6e