0a07cd
From 86bd1e20fc802edfb920fa53bd611d469f83250b Mon Sep 17 00:00:00 2001
222d62
From: Eduardo Otubo <otubo@redhat.com>
222d62
Date: Fri, 18 Jan 2019 16:55:36 +0100
0a07cd
Subject: net: Make sysconfig renderer compatible with Network Manager.
222d62
222d62
RH-Author: Eduardo Otubo <otubo@redhat.com>
222d62
Message-id: <20190118165536.25963-1-otubo@redhat.com>
222d62
Patchwork-id: 84052
222d62
O-Subject: [RHEL-8.0 cloud-init PATCH] net: Make sysconfig renderer compatible with Network Manager.
222d62
Bugzilla: 1602784
222d62
RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
222d62
RH-Acked-by: Mohammed Gamal <mgamal@redhat.com>
222d62
222d62
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1602784
222d62
Brew: https://brewweb.engineering.redhat.com/brew/taskinfo?taskID=19877292
222d62
Tested by: upstream maintainers and me
222d62
222d62
commit 3861102fcaf47a882516d8b6daab518308eb3086
222d62
Author: Eduardo Otubo <otubo@redhat.com>
222d62
Date:   Fri Jan 18 15:36:19 2019 +0000
222d62
222d62
    net: Make sysconfig renderer compatible with Network Manager.
222d62
222d62
    The 'sysconfig' renderer is activated if, and only if, there's ifup and
222d62
    ifdown commands present in its search dictonary or the network-scripts
222d62
    configuration files are found. This patch adds a check for Network-
222d62
    Manager configuration file as well.
222d62
222d62
    This solution is based on the use of the plugin 'ifcfg-rh' present in
222d62
    Network-Manager and is designed to support Fedora 29 or other
222d62
    distributions that also replaced network-scripts by Network-Manager.
222d62
222d62
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
222d62
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
0a07cd
Signed-off-by: Danilo C. L. de Paula <ddepaula@redhat.com>
222d62
---
0a07cd
 cloudinit/net/sysconfig.py  | 36 +++++++++++++++++++
0a07cd
 tests/unittests/test_net.py | 71 +++++++++++++++++++++++++++++++++++++
222d62
 2 files changed, 107 insertions(+)
222d62
222d62
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
0a07cd
index ae0554ef..dc1815d9 100644
222d62
--- a/cloudinit/net/sysconfig.py
222d62
+++ b/cloudinit/net/sysconfig.py
222d62
@@ -10,11 +10,14 @@ from cloudinit.distros.parsers import resolv_conf
222d62
 from cloudinit import log as logging
222d62
 from cloudinit import util
222d62
 
222d62
+from configobj import ConfigObj
222d62
+
222d62
 from . import renderer
222d62
 from .network_state import (
222d62
     is_ipv6_addr, net_prefix_to_ipv4_mask, subnet_is_ipv6)
222d62
 
222d62
 LOG = logging.getLogger(__name__)
222d62
+NM_CFG_FILE = "/etc/NetworkManager/NetworkManager.conf"
222d62
 
222d62
 
222d62
 def _make_header(sep='#'):
222d62
@@ -46,6 +49,24 @@ def _quote_value(value):
222d62
         return value
222d62
 
222d62
 
222d62
+def enable_ifcfg_rh(path):
222d62
+    """Add ifcfg-rh to NetworkManager.cfg plugins if main section is present"""
222d62
+    config = ConfigObj(path)
222d62
+    if 'main' in config:
222d62
+        if 'plugins' in config['main']:
222d62
+            if 'ifcfg-rh' in config['main']['plugins']:
222d62
+                return
222d62
+        else:
222d62
+            config['main']['plugins'] = []
222d62
+
222d62
+        if isinstance(config['main']['plugins'], list):
222d62
+            config['main']['plugins'].append('ifcfg-rh')
222d62
+        else:
222d62
+            config['main']['plugins'] = [config['main']['plugins'], 'ifcfg-rh']
222d62
+        config.write()
222d62
+        LOG.debug('Enabled ifcfg-rh NetworkManager plugins')
222d62
+
222d62
+
222d62
 class ConfigMap(object):
222d62
     """Sysconfig like dictionary object."""
222d62
 
0a07cd
@@ -656,6 +677,8 @@ class Renderer(renderer.Renderer):
222d62
             netrules_content = self._render_persistent_net(network_state)
222d62
             netrules_path = util.target_path(target, self.netrules_path)
222d62
             util.write_file(netrules_path, netrules_content, file_mode)
222d62
+        if available_nm(target=target):
222d62
+            enable_ifcfg_rh(util.target_path(target, path=NM_CFG_FILE))
222d62
 
0a07cd
         sysconfig_path = util.target_path(target, templates.get('control'))
0a07cd
         # Distros configuring /etc/sysconfig/network as a file e.g. Centos
0a07cd
@@ -670,6 +693,13 @@ class Renderer(renderer.Renderer):
222d62
 
222d62
 
222d62
 def available(target=None):
222d62
+    sysconfig = available_sysconfig(target=target)
222d62
+    nm = available_nm(target=target)
222d62
+
222d62
+    return any([nm, sysconfig])
222d62
+
222d62
+
222d62
+def available_sysconfig(target=None):
222d62
     expected = ['ifup', 'ifdown']
222d62
     search = ['/sbin', '/usr/sbin']
222d62
     for p in expected:
0a07cd
@@ -685,4 +715,10 @@ def available(target=None):
222d62
     return True
222d62
 
222d62
 
222d62
+def available_nm(target=None):
222d62
+    if not os.path.isfile(util.target_path(target, path=NM_CFG_FILE)):
222d62
+        return False
222d62
+    return True
222d62
+
222d62
+
222d62
 # vi: ts=4 expandtab
222d62
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
0a07cd
index 8bcafe08..526a30ed 100644
222d62
--- a/tests/unittests/test_net.py
222d62
+++ b/tests/unittests/test_net.py
0a07cd
@@ -22,6 +22,7 @@ import os
222d62
 import textwrap
222d62
 import yaml
222d62
 
222d62
+
222d62
 DHCP_CONTENT_1 = """
222d62
 DEVICE='eth0'
222d62
 PROTO='dhcp'
0a07cd
@@ -1854,6 +1855,7 @@ class TestRhelSysConfigRendering(CiTestCase):
222d62
 
0a07cd
     with_logs = True
222d62
 
222d62
+    nm_cfg_file = "/etc/NetworkManager/NetworkManager.conf"
222d62
     scripts_dir = '/etc/sysconfig/network-scripts'
222d62
     header = ('# Created by cloud-init on instance boot automatically, '
222d62
               'do not edit.\n#\n')
0a07cd
@@ -2497,6 +2499,75 @@ iface eth0 inet dhcp
222d62
         self.assertEqual(
222d62
             expected, dir2dict(tmp_dir)['/etc/network/interfaces'])
222d62
 
222d62
+    def test_check_ifcfg_rh(self):
222d62
+        """ifcfg-rh plugin is added NetworkManager.conf if conf present."""
222d62
+        render_dir = self.tmp_dir()
222d62
+        nm_cfg = util.target_path(render_dir, path=self.nm_cfg_file)
222d62
+        util.ensure_dir(os.path.dirname(nm_cfg))
222d62
+
222d62
+        # write a template nm.conf, note plugins is a list here
222d62
+        with open(nm_cfg, 'w') as fh:
222d62
+            fh.write('# test_check_ifcfg_rh\n[main]\nplugins=foo,bar\n')
222d62
+        self.assertTrue(os.path.exists(nm_cfg))
222d62
+
222d62
+        # render and read
222d62
+        entry = NETWORK_CONFIGS['small']
222d62
+        found = self._render_and_read(network_config=yaml.load(entry['yaml']),
222d62
+                                      dir=render_dir)
222d62
+        self._compare_files_to_expected(entry[self.expected_name], found)
222d62
+        self._assert_headers(found)
222d62
+
222d62
+        # check ifcfg-rh is in the 'plugins' list
222d62
+        config = sysconfig.ConfigObj(nm_cfg)
222d62
+        self.assertIn('ifcfg-rh', config['main']['plugins'])
222d62
+
222d62
+    def test_check_ifcfg_rh_plugins_string(self):
222d62
+        """ifcfg-rh plugin is append when plugins is a string."""
222d62
+        render_dir = self.tmp_path("render")
222d62
+        os.makedirs(render_dir)
222d62
+        nm_cfg = util.target_path(render_dir, path=self.nm_cfg_file)
222d62
+        util.ensure_dir(os.path.dirname(nm_cfg))
222d62
+
222d62
+        # write a template nm.conf, note plugins is a value here
222d62
+        util.write_file(nm_cfg, '# test_check_ifcfg_rh\n[main]\nplugins=foo\n')
222d62
+
222d62
+        # render and read
222d62
+        entry = NETWORK_CONFIGS['small']
222d62
+        found = self._render_and_read(network_config=yaml.load(entry['yaml']),
222d62
+                                      dir=render_dir)
222d62
+        self._compare_files_to_expected(entry[self.expected_name], found)
222d62
+        self._assert_headers(found)
222d62
+
222d62
+        # check raw content has plugin
222d62
+        nm_file_content = util.load_file(nm_cfg)
222d62
+        self.assertIn('ifcfg-rh', nm_file_content)
222d62
+
222d62
+        # check ifcfg-rh is in the 'plugins' list
222d62
+        config = sysconfig.ConfigObj(nm_cfg)
222d62
+        self.assertIn('ifcfg-rh', config['main']['plugins'])
222d62
+
222d62
+    def test_check_ifcfg_rh_plugins_no_plugins(self):
222d62
+        """enable_ifcfg_plugin creates plugins value if missing."""
222d62
+        render_dir = self.tmp_path("render")
222d62
+        os.makedirs(render_dir)
222d62
+        nm_cfg = util.target_path(render_dir, path=self.nm_cfg_file)
222d62
+        util.ensure_dir(os.path.dirname(nm_cfg))
222d62
+
222d62
+        # write a template nm.conf, note plugins is missing
222d62
+        util.write_file(nm_cfg, '# test_check_ifcfg_rh\n[main]\n')
222d62
+        self.assertTrue(os.path.exists(nm_cfg))
222d62
+
222d62
+        # render and read
222d62
+        entry = NETWORK_CONFIGS['small']
222d62
+        found = self._render_and_read(network_config=yaml.load(entry['yaml']),
222d62
+                                      dir=render_dir)
222d62
+        self._compare_files_to_expected(entry[self.expected_name], found)
222d62
+        self._assert_headers(found)
222d62
+
222d62
+        # check ifcfg-rh is in the 'plugins' list
222d62
+        config = sysconfig.ConfigObj(nm_cfg)
222d62
+        self.assertIn('ifcfg-rh', config['main']['plugins'])
222d62
+
222d62
 
222d62
 class TestNetplanNetRendering(CiTestCase):
222d62
 
222d62
-- 
0a07cd
2.20.1
222d62