1064ba
From 8e8e1dffc528b738f92cd508d7a6faf58e40af00 Mon Sep 17 00:00:00 2001
5a2e6e
From: Scott Moser <smoser@brickies.net>
5a2e6e
Date: Wed, 15 Mar 2017 12:06:40 -0400
1064ba
Subject: [PATCH 1/2] 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
1064ba
Related: rhbz#1492726
1064ba
Signed-off-by: Ryan McCabe <rmccabe@redhat.com>
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
1064ba
index 5b249f1f..69ecbb5d 100644
5a2e6e
--- a/cloudinit/net/eni.py
5a2e6e
+++ b/cloudinit/net/eni.py
1064ba
@@ -273,8 +273,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:
1064ba
@@ -423,10 +426,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
1064ba
@@ -212,6 +212,10 @@ class NetworkStateInterpreter(object):
5a2e6e
                     LOG.debug(self.dump_network_state())
5a2e6e
 
1064ba
     @ensure_command_keys(['name'])
5a2e6e
+    def handle_loopback(self, command):
5a2e6e
+        return self.handle_physical(command)
5a2e6e
+
1064ba
+    @ensure_command_keys(['name'])
5a2e6e
     def handle_physical(self, command):
5a2e6e
         '''
1064ba
         command = {
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
1064ba
index 4c0e3ad3..7e389c10 100644
5a2e6e
--- a/tests/unittests/test_net.py
5a2e6e
+++ b/tests/unittests/test_net.py
1064ba
@@ -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
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):
1064ba
@@ -785,6 +793,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
 
1064ba
@@ -826,6 +855,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
-- 
1064ba
2.13.6
5a2e6e