2
0
Эх сурвалжийг харах

Add suse network config support

Signed-off-by: Mihaela Balutoiu <mbalutoiu@cloudbasesolutions.com>
Mihaela Balutoiu 1 сар өмнө
parent
commit
0132f8ace7

+ 46 - 4
coriolis/osmorphing/suse.py

@@ -26,11 +26,20 @@ CLOUD_TOOLS_REPO_URI_VERSION_ONLY_FORMAT = (
     "https://download.opensuse.org/repositories/Cloud:/Tools/%s/")
 CLOUD_TOOLS_NEW_URL_MINIMUM_VERSION = 16
 
+# SUSE (SLES/openSUSE 12 and 15) manages the network with wicked, which reads
+# ifcfg files. SLES 16 switched to NetworkManager keyfiles and is handled by
+# the inherited '_write_nmconnection_configs'.
+SUSE_IFCFG_TEMPLATE = """BOOTPROTO='dhcp'
+STARTMODE='auto'
+"""
+
 
 class BaseSUSEMorphingTools(base.BaseLinuxOSMorphingTools):
 
     BIOS_GRUB_LOCATION = "/boot/grub2"
     UEFI_GRUB_LOCATION = "/boot/efi/EFI/suse"
+    _NETWORK_SCRIPTS_PATH = "etc/sysconfig/network"
+    _IFCFG_TEMPLATE = SUSE_IFCFG_TEMPLATE
 
     @classmethod
     def get_required_detected_os_info_fields(cls):
@@ -61,12 +70,45 @@ class BaseSUSEMorphingTools(base.BaseLinuxOSMorphingTools):
         return False
 
     def disable_predictable_nic_names(self):
-        # TODO(gsamfira): implement once we have networking support
-        pass
+        grub_cfg = "etc/default/grub"
+        if not self._test_path(grub_cfg):
+            LOG.warning(
+                "Could not find /%s. Skipping predictable NIC names "
+                "disabling.", grub_cfg)
+            return
+        contents = self._read_file_sudo(grub_cfg)
+        cfg = utils.Grub2ConfigEditor(contents)
+        cfg.append_to_option(
+            "GRUB_CMDLINE_LINUX_DEFAULT",
+            {"opt_type": "key_val", "opt_key": "net.ifnames", "opt_val": 0})
+        cfg.append_to_option(
+            "GRUB_CMDLINE_LINUX_DEFAULT",
+            {"opt_type": "key_val", "opt_key": "biosdevname", "opt_val": 0})
+        cfg.append_to_option(
+            "GRUB_CMDLINE_LINUX",
+            {"opt_type": "key_val", "opt_key": "net.ifnames", "opt_val": 0})
+        cfg.append_to_option(
+            "GRUB_CMDLINE_LINUX",
+            {"opt_type": "key_val", "opt_key": "biosdevname", "opt_val": 0})
+        self._write_file_sudo("etc/default/grub", cfg.dump())
+        self._schedule_grub2_update()
 
     def set_net_config(self, nics_info, dhcp):
-        # TODO(alexpilotti): add networking support
-        pass
+        if dhcp:
+            nics_info = nics_info or []
+            if not nics_info:
+                return
+            self.disable_predictable_nic_names()
+            nmconnection_files = (
+                self._get_existing_ethernet_nmconnection_files())
+            if nmconnection_files:
+                self._write_nmconnection_configs(nics_info, nmconnection_files)
+            else:
+                self._write_nic_configs(nics_info)
+            return
+
+        LOG.info("Setting static IP configuration")
+        self._setup_network_preservation(nics_info)
 
     def get_installed_packages(self):
         cmd = 'rpm -qa --qf "%{NAME}\\n"'

+ 197 - 0
coriolis/tests/osmorphing/test_suse.py

@@ -434,3 +434,200 @@ class BaseSUSEMorphingToolsTestCase(test_base.CoriolisBaseTestCase):
         mock_super_pre.assert_called_once_with([])
         mock_enable_sles_module.assert_not_called()
         mock_add_cloud_tools_repo.assert_not_called()
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_keyfiles_by_type')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
+    def test__get_existing_ethernet_nmconnection_files(
+            self, mock_test_path, mock_get_keyfiles_by_type):
+        mock_test_path.return_value = True
+        mock_get_keyfiles_by_type.return_value = [
+            ('etc/NetworkManager/system-connections/eth0.nmconnection', {}),
+            ('etc/NetworkManager/system-connections/eth1.nmconnection', {})]
+
+        result = (
+            self.morphing_tools._get_existing_ethernet_nmconnection_files())
+
+        self.assertEqual(result, [
+            'etc/NetworkManager/system-connections/eth0.nmconnection',
+            'etc/NetworkManager/system-connections/eth1.nmconnection'])
+        mock_get_keyfiles_by_type.assert_called_once_with(
+            "ethernet", "etc/NetworkManager/system-connections")
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_keyfiles_by_type')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
+    def test__get_existing_ethernet_nmconnection_files_no_path(
+            self, mock_test_path, mock_get_keyfiles_by_type):
+        mock_test_path.return_value = False
+
+        result = (
+            self.morphing_tools._get_existing_ethernet_nmconnection_files())
+
+        self.assertEqual(result, [])
+        mock_get_keyfiles_by_type.assert_not_called()
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_write_file_sudo')
+    @mock.patch.object(suse.BaseSUSEMorphingTools, '_schedule_grub2_update')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_read_file_sudo')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
+    def test_disable_predictable_nic_names(
+            self, mock_test_path, mock_read_file_sudo,
+            mock_schedule_grub2_update, mock_write_file_sudo):
+        mock_test_path.return_value = True
+        mock_read_file_sudo.return_value = (
+            'GRUB_CMDLINE_LINUX_DEFAULT=""\nGRUB_CMDLINE_LINUX=""\n')
+
+        self.morphing_tools.disable_predictable_nic_names()
+
+        mock_read_file_sudo.assert_called_once_with("etc/default/grub")
+        mock_write_file_sudo.assert_called_once()
+        written_path, written_contents = mock_write_file_sudo.call_args[0]
+        self.assertEqual("etc/default/grub", written_path)
+        self.assertIn("net.ifnames=0", written_contents)
+        self.assertIn("biosdevname=0", written_contents)
+        # The (slow) grub regeneration must be deferred, not run eagerly.
+        mock_schedule_grub2_update.assert_called_once_with()
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
+    def test_disable_predictable_nic_names_no_grub_cfg(self, mock_test_path):
+        mock_test_path.return_value = False
+
+        with self.assertLogs(
+                'coriolis.osmorphing.suse', level=logging.WARNING):
+            self.morphing_tools.disable_predictable_nic_names()
+
+    def test__ifcfg_class_attributes(self):
+        self.assertEqual(
+            "etc/sysconfig/network",
+            self.morphing_tools._NETWORK_SCRIPTS_PATH)
+        self.assertEqual(
+            suse.SUSE_IFCFG_TEMPLATE, self.morphing_tools._IFCFG_TEMPLATE)
+        self.assertNotIn("NM_CONTROLLED", suse.SUSE_IFCFG_TEMPLATE)
+        self.assertIn("BOOTPROTO='dhcp'", suse.SUSE_IFCFG_TEMPLATE)
+        self.assertIn("STARTMODE='auto'", suse.SUSE_IFCFG_TEMPLATE)
+
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_write_file_sudo')
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools, '_backup_ethernet_ifcfg_configs')
+    def test__write_nic_configs(
+            self, mock_backup_ethernet_ifcfg_configs, mock_write_file_sudo):
+        nics_info = [{'name': 'eth0'}, {'name': 'eth1'}]
+
+        self.morphing_tools._write_nic_configs(nics_info)
+
+        mock_backup_ethernet_ifcfg_configs.assert_called_once_with()
+        mock_write_file_sudo.assert_has_calls([
+            mock.call(
+                "etc/sysconfig/network/ifcfg-eth0", suse.SUSE_IFCFG_TEMPLATE),
+            mock.call(
+                "etc/sysconfig/network/ifcfg-eth1", suse.SUSE_IFCFG_TEMPLATE),
+        ])
+
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools, '_backup_ethernet_ifcfg_configs')
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools, '_backup_nmconnection_files')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_write_file_sudo')
+    @mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
+    def test__write_nmconnection_configs(
+            self, mock_exec_cmd_chroot, mock_write_file_sudo,
+            mock_backup_nmconnection_files,
+            mock_backup_ethernet_ifcfg_configs):
+        nics_info = [{'name': 'eth0'}]
+        nmconnection_files = [
+            'etc/NetworkManager/system-connections/eth0.nmconnection']
+
+        self.morphing_tools._write_nmconnection_configs(
+            nics_info, nmconnection_files)
+
+        mock_backup_nmconnection_files.assert_called_once_with(
+            nmconnection_files)
+        mock_backup_ethernet_ifcfg_configs.assert_called_once_with()
+        mock_write_file_sudo.assert_called_once()
+        args, _ = mock_write_file_sudo.call_args
+        self.assertEqual(
+            args[0],
+            "etc/NetworkManager/system-connections/eth0.nmconnection")
+        self.assertIn("[connection]", args[1])
+        self.assertIn("interface-name=eth0", args[1])
+        self.assertIn("method=auto", args[1])
+        self.assertIn("may-fail=false", args[1])
+        mock_exec_cmd_chroot.assert_called_once_with(
+            "chmod 600 /etc/NetworkManager/system-connections/"
+            "eth0.nmconnection")
+
+    @mock.patch.object(
+        suse.BaseSUSEMorphingTools, 'disable_predictable_nic_names')
+    @mock.patch.object(suse.BaseSUSEMorphingTools, '_write_nic_configs')
+    @mock.patch.object(
+        suse.BaseSUSEMorphingTools, '_write_nmconnection_configs')
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools,
+        '_get_existing_ethernet_nmconnection_files')
+    def test_set_net_config_dhcp(
+            self, mock_get_existing_ethernet_nmconnection_files,
+            mock_write_nmconnection_configs,
+            mock_write_nic_configs,
+            mock_disable_predictable_nic_names):
+        mock_get_existing_ethernet_nmconnection_files.return_value = []
+        nics_info = [{'name': 'eth0'}]
+
+        self.morphing_tools.set_net_config(nics_info, dhcp=True)
+
+        mock_get_existing_ethernet_nmconnection_files.assert_called_once_with()
+        mock_write_nmconnection_configs.assert_not_called()
+        mock_disable_predictable_nic_names.assert_called_once()
+        mock_write_nic_configs.assert_called_once_with(nics_info)
+
+    @mock.patch.object(
+        suse.BaseSUSEMorphingTools, 'disable_predictable_nic_names')
+    @mock.patch.object(suse.BaseSUSEMorphingTools, '_write_nic_configs')
+    @mock.patch.object(
+        suse.BaseSUSEMorphingTools, '_write_nmconnection_configs')
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools,
+        '_get_existing_ethernet_nmconnection_files')
+    def test_set_net_config_dhcp_nmconnection(
+            self, mock_get_existing_ethernet_nmconnection_files,
+            mock_write_nmconnection_configs,
+            mock_write_nic_configs,
+            mock_disable_predictable_nic_names):
+        nm_files = [
+            'etc/NetworkManager/system-connections/eth0.nmconnection']
+        mock_get_existing_ethernet_nmconnection_files.return_value = nm_files
+        nics_info = [{'name': 'eth0'}]
+
+        self.morphing_tools.set_net_config(nics_info, dhcp=True)
+
+        mock_disable_predictable_nic_names.assert_called_once()
+        mock_write_nmconnection_configs.assert_called_once_with(
+            nics_info, nm_files)
+        mock_write_nic_configs.assert_not_called()
+
+    @mock.patch.object(
+        suse.BaseSUSEMorphingTools, 'disable_predictable_nic_names')
+    @mock.patch.object(suse.BaseSUSEMorphingTools, '_write_nic_configs')
+    @mock.patch.object(
+        suse.BaseSUSEMorphingTools, '_write_nmconnection_configs')
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools,
+        '_get_existing_ethernet_nmconnection_files')
+    def test_set_net_config_dhcp_no_nics(
+            self, mock_get_existing_ethernet_nmconnection_files,
+            mock_write_nmconnection_configs,
+            mock_write_nic_configs,
+            mock_disable_predictable_nic_names):
+        self.morphing_tools.set_net_config(None, dhcp=True)
+
+        mock_get_existing_ethernet_nmconnection_files.assert_not_called()
+        mock_disable_predictable_nic_names.assert_not_called()
+        mock_write_nmconnection_configs.assert_not_called()
+        mock_write_nic_configs.assert_not_called()
+
+    @mock.patch.object(
+        base.BaseLinuxOSMorphingTools, '_setup_network_preservation')
+    def test_set_net_config_static(self, mock_setup_network_preservation):
+        nics_info = [{'name': 'eth0'}]
+
+        self.morphing_tools.set_net_config(nics_info, dhcp=False)
+
+        mock_setup_network_preservation.assert_called_once_with(nics_info)