Skip to content

Commit 918f222

Browse files
Add suse network config support
Signed-off-by: Mihaela Balutoiu <mbalutoiu@cloudbasesolutions.com>
1 parent e34e0b7 commit 918f222

2 files changed

Lines changed: 347 additions & 4 deletions

File tree

coriolis/osmorphing/suse.py

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@
1111
from coriolis import exception
1212
from coriolis.osmorphing import base
1313
from coriolis.osmorphing.osdetect import suse as suse_detect
14+
from coriolis.osmorphing import redhat as redhat_osmorphing
1415
from coriolis import utils
1516

17+
IFCFG_TEMPLATE = redhat_osmorphing.IFCFG_TEMPLATE
18+
NMCONNECTION_TEMPLATE = redhat_osmorphing.NMCONNECTION_TEMPLATE
19+
1620
LOG = logging.getLogger(__name__)
1721

1822
DETECTED_SUSE_RELEASE_FIELD_NAME = suse_detect.DETECTED_SUSE_RELEASE_FIELD_NAME
@@ -29,6 +33,8 @@
2933

3034
class BaseSUSEMorphingTools(base.BaseLinuxOSMorphingTools):
3135

36+
_NETWORK_SCRIPTS_PATH = "etc/sysconfig/network-scripts"
37+
_NM_CONNECTIONS_PATH = "etc/NetworkManager/system-connections"
3238
BIOS_GRUB_LOCATION = "/boot/grub2"
3339
UEFI_GRUB_LOCATION = "/boot/efi/EFI/suse"
3440

@@ -61,12 +67,107 @@ def check_os_supported(cls, detected_os_info):
6167
return False
6268

6369
def disable_predictable_nic_names(self):
64-
# TODO(gsamfira): implement once we have networking support
65-
pass
70+
grub_cfg = "etc/default/grub"
71+
if not self._test_path(grub_cfg):
72+
LOG.warning(
73+
"Could not find /%s. Skipping predictable NIC names "
74+
"disabling.", grub_cfg)
75+
return
76+
contents = self._read_file_sudo(grub_cfg)
77+
cfg = utils.Grub2ConfigEditor(contents)
78+
cfg.append_to_option(
79+
"GRUB_CMDLINE_LINUX_DEFAULT",
80+
{"opt_type": "key_val", "opt_key": "net.ifnames", "opt_val": 0})
81+
cfg.append_to_option(
82+
"GRUB_CMDLINE_LINUX_DEFAULT",
83+
{"opt_type": "key_val", "opt_key": "biosdevname", "opt_val": 0})
84+
cfg.append_to_option(
85+
"GRUB_CMDLINE_LINUX",
86+
{"opt_type": "key_val", "opt_key": "net.ifnames", "opt_val": 0})
87+
cfg.append_to_option(
88+
"GRUB_CMDLINE_LINUX",
89+
{"opt_type": "key_val", "opt_key": "biosdevname", "opt_val": 0})
90+
self._write_file_sudo("etc/default/grub", cfg.dump())
91+
self._execute_update_grub()
92+
93+
def _get_ifcfg_nm_controlled(self):
94+
if self._version_supported_util(self._version, minimum=15):
95+
return "yes"
96+
return "no"
97+
98+
def _get_ethernet_keyfiles(self):
99+
if not self._test_path(self._NM_CONNECTIONS_PATH):
100+
return []
101+
return self._get_keyfiles_by_type(
102+
"ethernet", self._NM_CONNECTIONS_PATH)
103+
104+
def _backup_nmconnection_files(self, backup_file_suffix=".bak"):
105+
"""Back up all existing nmconnection profiles."""
106+
if not self._test_path(self._NM_CONNECTIONS_PATH):
107+
return
108+
for cfg_path in self._get_nmconnection_files(
109+
self._NM_CONNECTIONS_PATH):
110+
self._exec_cmd_chroot(
111+
'mv "%s" "%s%s"' % (cfg_path, cfg_path, backup_file_suffix))
112+
LOG.debug("Backed up nmconnection profile '%s'", cfg_path)
113+
114+
def _backup_ifcfg_configs(self, device_names, backup_file_suffix=".bak"):
115+
"""Back up ifcfg profiles for the given device names."""
116+
for dev_name in device_names:
117+
cfg_path = "%s/ifcfg-%s" % (self._NETWORK_SCRIPTS_PATH, dev_name)
118+
if self._test_path(cfg_path):
119+
self._exec_cmd_chroot(
120+
'mv "%s" "%s%s"' % (
121+
cfg_path, cfg_path, backup_file_suffix))
122+
LOG.debug("Backed up ifcfg profile '%s'", cfg_path)
123+
124+
def _write_nic_configs(self, nics_info):
125+
for idx, _ in enumerate(nics_info):
126+
dev_name = "eth%d" % idx
127+
cfg_path = "%s/ifcfg-%s" % (self._NETWORK_SCRIPTS_PATH, dev_name)
128+
if self._test_path(cfg_path):
129+
self._exec_cmd_chroot(
130+
"cp %s %s.bak" % (cfg_path, cfg_path)
131+
)
132+
self._write_file_sudo(
133+
cfg_path,
134+
IFCFG_TEMPLATE % {
135+
"device_name": dev_name,
136+
"nm_controlled": self._get_ifcfg_nm_controlled(),
137+
})
138+
139+
def _write_nmconnection_configs(self, nics_info):
140+
self._backup_nmconnection_files()
141+
device_names = ["eth%d" % idx for idx, _ in enumerate(nics_info)]
142+
self._backup_ifcfg_configs(device_names)
143+
144+
for idx, _ in enumerate(nics_info):
145+
dev_name = "eth%d" % idx
146+
cfg_path = "%s/%s.nmconnection" % (
147+
self._NM_CONNECTIONS_PATH, dev_name)
148+
self._write_file_sudo(
149+
cfg_path,
150+
NMCONNECTION_TEMPLATE % {
151+
"device_name": dev_name,
152+
"connection_uuid": str(uuid.uuid4()),
153+
})
154+
self._exec_cmd_chroot("chmod 600 /%s" % cfg_path)
155+
156+
def _write_dhcp_net_config(self, nics_info):
157+
self.disable_predictable_nic_names()
158+
ethernet_keyfiles = self._get_ethernet_keyfiles()
159+
if ethernet_keyfiles:
160+
self._write_nmconnection_configs(nics_info)
161+
else:
162+
self._write_nic_configs(nics_info)
66163

67164
def set_net_config(self, nics_info, dhcp):
68-
# TODO(alexpilotti): add networking support
69-
pass
165+
if dhcp:
166+
self._write_dhcp_net_config(nics_info)
167+
return
168+
169+
LOG.info("Setting static IP configuration")
170+
self._setup_network_preservation(nics_info)
70171

71172
def get_installed_packages(self):
72173
cmd = 'rpm -qa --qf "%{NAME}\\n"'

coriolis/tests/osmorphing/test_suse.py

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,3 +434,245 @@ def test_pre_packages_install_no_packages(
434434
mock_super_pre.assert_called_once_with([])
435435
mock_enable_sles_module.assert_not_called()
436436
mock_add_cloud_tools_repo.assert_not_called()
437+
438+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_keyfiles_by_type')
439+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
440+
def test__get_ethernet_keyfiles(
441+
self, mock_test_path, mock_get_keyfiles_by_type):
442+
mock_test_path.return_value = True
443+
keyfiles = [
444+
('etc/NetworkManager/system-connections/eth0.nmconnection', {}),
445+
('etc/NetworkManager/system-connections/eth1.nmconnection', {})]
446+
mock_get_keyfiles_by_type.return_value = keyfiles
447+
448+
result = self.morphing_tools._get_ethernet_keyfiles()
449+
450+
self.assertEqual(result, keyfiles)
451+
mock_get_keyfiles_by_type.assert_called_once_with(
452+
"ethernet", "etc/NetworkManager/system-connections")
453+
454+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_get_keyfiles_by_type')
455+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
456+
def test__get_ethernet_keyfiles_no_path(
457+
self, mock_test_path, mock_get_keyfiles_by_type):
458+
mock_test_path.return_value = False
459+
460+
result = self.morphing_tools._get_ethernet_keyfiles()
461+
462+
self.assertEqual(result, [])
463+
mock_get_keyfiles_by_type.assert_not_called()
464+
465+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
466+
@mock.patch.object(
467+
base.BaseLinuxOSMorphingTools, '_get_nmconnection_files')
468+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
469+
def test__backup_nmconnection_files(
470+
self, mock_test_path, mock_get_nmconnection_files,
471+
mock_exec_cmd_chroot):
472+
mock_test_path.return_value = True
473+
# All nmconnection profiles must be backed up, not only ethernet ones.
474+
mock_get_nmconnection_files.return_value = [
475+
'etc/NetworkManager/system-connections/eth0.nmconnection',
476+
'etc/NetworkManager/system-connections/wifi.nmconnection']
477+
478+
with self.assertLogs('coriolis.osmorphing.suse', level=logging.DEBUG):
479+
self.morphing_tools._backup_nmconnection_files()
480+
481+
mock_get_nmconnection_files.assert_called_once_with(
482+
"etc/NetworkManager/system-connections")
483+
mock_exec_cmd_chroot.assert_has_calls([
484+
mock.call(
485+
'mv "etc/NetworkManager/system-connections/eth0.nmconnection" '
486+
'"etc/NetworkManager/system-connections/'
487+
'eth0.nmconnection.bak"'),
488+
mock.call(
489+
'mv "etc/NetworkManager/system-connections/wifi.nmconnection" '
490+
'"etc/NetworkManager/system-connections/'
491+
'wifi.nmconnection.bak"'),
492+
])
493+
494+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
495+
@mock.patch.object(
496+
base.BaseLinuxOSMorphingTools, '_get_nmconnection_files')
497+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
498+
def test__backup_nmconnection_files_no_path(
499+
self, mock_test_path, mock_get_nmconnection_files,
500+
mock_exec_cmd_chroot):
501+
mock_test_path.return_value = False
502+
503+
self.morphing_tools._backup_nmconnection_files()
504+
505+
mock_get_nmconnection_files.assert_not_called()
506+
mock_exec_cmd_chroot.assert_not_called()
507+
508+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
509+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
510+
def test__backup_ifcfg_configs(
511+
self, mock_test_path, mock_exec_cmd_chroot):
512+
# Only the targeted devices that actually exist are backed up.
513+
mock_test_path.side_effect = [True, False]
514+
515+
with self.assertLogs('coriolis.osmorphing.suse', level=logging.DEBUG):
516+
self.morphing_tools._backup_ifcfg_configs(['eth0', 'eth1'])
517+
518+
mock_test_path.assert_has_calls([
519+
mock.call("etc/sysconfig/network-scripts/ifcfg-eth0"),
520+
mock.call("etc/sysconfig/network-scripts/ifcfg-eth1"),
521+
])
522+
mock_exec_cmd_chroot.assert_called_once_with(
523+
'mv "etc/sysconfig/network-scripts/ifcfg-eth0" '
524+
'"etc/sysconfig/network-scripts/ifcfg-eth0.bak"')
525+
526+
def test__get_ifcfg_nm_controlled_old_version(self):
527+
result = self.morphing_tools._get_ifcfg_nm_controlled()
528+
529+
self.assertEqual("no", result)
530+
531+
def test__get_ifcfg_nm_controlled_sles15(self):
532+
self.morphing_tools._version = "15"
533+
534+
result = self.morphing_tools._get_ifcfg_nm_controlled()
535+
536+
self.assertEqual("yes", result)
537+
538+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_write_file_sudo')
539+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
540+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
541+
def test__write_nic_configs_with_existing_file(
542+
self, mock_test_path, mock_exec_cmd_chroot, mock_write_file_sudo):
543+
nics_info = [{'name': 'eth0'}, {'name': 'eth1'}]
544+
mock_test_path.return_value = True
545+
546+
self.morphing_tools._write_nic_configs(nics_info)
547+
548+
mock_exec_cmd_chroot.assert_has_calls([
549+
mock.call("cp etc/sysconfig/network-scripts/ifcfg-eth0 "
550+
"etc/sysconfig/network-scripts/ifcfg-eth0.bak"),
551+
mock.call("cp etc/sysconfig/network-scripts/ifcfg-eth1 "
552+
"etc/sysconfig/network-scripts/ifcfg-eth1.bak"),
553+
])
554+
mock_write_file_sudo.assert_has_calls([
555+
mock.call(
556+
"etc/sysconfig/network-scripts/ifcfg-eth0",
557+
suse.IFCFG_TEMPLATE % {
558+
"device_name": "eth0",
559+
"nm_controlled": "no",
560+
},
561+
),
562+
mock.call(
563+
"etc/sysconfig/network-scripts/ifcfg-eth1",
564+
suse.IFCFG_TEMPLATE % {
565+
"device_name": "eth1",
566+
"nm_controlled": "no",
567+
},
568+
),
569+
])
570+
571+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_write_file_sudo')
572+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
573+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_test_path')
574+
def test__write_nic_configs_sles15_no_existing_file(
575+
self, mock_test_path, mock_exec_cmd_chroot, mock_write_file_sudo):
576+
self.morphing_tools._version = "15"
577+
nics_info = [{'name': 'eth0'}]
578+
mock_test_path.return_value = False
579+
580+
self.morphing_tools._write_nic_configs(nics_info)
581+
582+
mock_exec_cmd_chroot.assert_not_called()
583+
mock_write_file_sudo.assert_called_once_with(
584+
"etc/sysconfig/network-scripts/ifcfg-eth0",
585+
suse.IFCFG_TEMPLATE % {
586+
"device_name": "eth0",
587+
"nm_controlled": "yes",
588+
},
589+
)
590+
591+
@mock.patch.object(
592+
suse.BaseSUSEMorphingTools, '_backup_ifcfg_configs')
593+
@mock.patch.object(
594+
suse.BaseSUSEMorphingTools, '_backup_nmconnection_files')
595+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_write_file_sudo')
596+
@mock.patch.object(base.BaseLinuxOSMorphingTools, '_exec_cmd_chroot')
597+
def test__write_nmconnection_configs(
598+
self, mock_exec_cmd_chroot, mock_write_file_sudo,
599+
mock_backup_nmconnection_files,
600+
mock_backup_ifcfg_configs):
601+
nics_info = [{'name': 'eth0'}]
602+
603+
self.morphing_tools._write_nmconnection_configs(nics_info)
604+
605+
mock_backup_nmconnection_files.assert_called_once_with()
606+
mock_backup_ifcfg_configs.assert_called_once_with(['eth0'])
607+
mock_write_file_sudo.assert_called_once()
608+
args, _ = mock_write_file_sudo.call_args
609+
self.assertEqual(
610+
args[0],
611+
"etc/NetworkManager/system-connections/eth0.nmconnection")
612+
self.assertIn("[connection]", args[1])
613+
self.assertIn("interface-name=eth0", args[1])
614+
self.assertIn("method=auto", args[1])
615+
self.assertIn("may-fail=false", args[1])
616+
mock_exec_cmd_chroot.assert_called_once_with(
617+
"chmod 600 /etc/NetworkManager/system-connections/"
618+
"eth0.nmconnection")
619+
620+
@mock.patch.object(suse.BaseSUSEMorphingTools, '_write_nic_configs')
621+
@mock.patch.object(
622+
suse.BaseSUSEMorphingTools, '_write_nmconnection_configs')
623+
@mock.patch.object(
624+
suse.BaseSUSEMorphingTools, '_get_ethernet_keyfiles')
625+
@mock.patch.object(
626+
suse.BaseSUSEMorphingTools, 'disable_predictable_nic_names')
627+
def test__write_dhcp_net_config_no_ethernet_keyfiles(
628+
self, mock_disable_predictable_nic_names,
629+
mock_get_ethernet_keyfiles,
630+
mock_write_nmconnection_configs, mock_write_nic_configs):
631+
mock_get_ethernet_keyfiles.return_value = []
632+
nics_info = [{'name': 'eth0'}]
633+
634+
self.morphing_tools._write_dhcp_net_config(nics_info)
635+
636+
mock_disable_predictable_nic_names.assert_called_once()
637+
mock_get_ethernet_keyfiles.assert_called_once_with()
638+
mock_write_nic_configs.assert_called_once_with(nics_info)
639+
mock_write_nmconnection_configs.assert_not_called()
640+
641+
@mock.patch.object(suse.BaseSUSEMorphingTools, '_write_nic_configs')
642+
@mock.patch.object(
643+
suse.BaseSUSEMorphingTools, '_write_nmconnection_configs')
644+
@mock.patch.object(
645+
suse.BaseSUSEMorphingTools, '_get_ethernet_keyfiles')
646+
@mock.patch.object(
647+
suse.BaseSUSEMorphingTools, 'disable_predictable_nic_names')
648+
def test__write_dhcp_net_config_with_ethernet_keyfiles(
649+
self, mock_disable_predictable_nic_names,
650+
mock_get_ethernet_keyfiles,
651+
mock_write_nmconnection_configs, mock_write_nic_configs):
652+
mock_get_ethernet_keyfiles.return_value = [
653+
('etc/NetworkManager/system-connections/eth0.nmconnection', {})]
654+
nics_info = [{'name': 'eth0'}]
655+
656+
self.morphing_tools._write_dhcp_net_config(nics_info)
657+
658+
mock_disable_predictable_nic_names.assert_called_once()
659+
mock_get_ethernet_keyfiles.assert_called_once_with()
660+
mock_write_nmconnection_configs.assert_called_once_with(nics_info)
661+
mock_write_nic_configs.assert_not_called()
662+
663+
@mock.patch.object(suse.BaseSUSEMorphingTools, '_write_dhcp_net_config')
664+
def test_set_net_config_dhcp(self, mock_write_dhcp_net_config):
665+
nics_info = [{'name': 'eth0'}]
666+
667+
self.morphing_tools.set_net_config(nics_info, dhcp=True)
668+
669+
mock_write_dhcp_net_config.assert_called_once_with(nics_info)
670+
671+
@mock.patch.object(
672+
base.BaseLinuxOSMorphingTools, '_setup_network_preservation')
673+
def test_set_net_config_static(self, mock_setup_network_preservation):
674+
nics_info = [{'name': 'eth0'}]
675+
676+
self.morphing_tools.set_net_config(nics_info, dhcp=False)
677+
678+
mock_setup_network_preservation.assert_called_once_with(nics_info)

0 commit comments

Comments
 (0)