From 36eabd877f0a4a7871de52381dd41f118f219549 Mon Sep 17 00:00:00 2001 From: Lucas Ratusznei Fonseca Date: Fri, 21 Mar 2025 03:38:58 -0300 Subject: [PATCH] Fix ifup error message of vlan that is already configured by kickstart The first time apply_network_config.py runs in non controllers with mgmt over VLAN, the pxeboot/mgmt interface is already configured by ifupdown. When ifup is called for the label that holds the mgmt address, the following error occurs: Error: ipv6: address already assigned. ifup: failed to bring up vlan10:1-22 This happens because since the /etc/network/interfaces.d/auto file does not exist yet, all the interfaces defined by puppet are considered "new" and are not required to be set down before ifup. This change adds logic to check if any of the newly introduced interfaces are already up, and if it is the case, adds them to the down list. Test plan [PASS] STANDARD IPv6 full install on VirtualBox (2 storages + 1 compute) Partial-Bug: 2103645 Change-Id: Id7968521606ea54085468b1e55798c75663e7561 Signed-off-by: Lucas Ratusznei Fonseca --- .../src/bin/apply_network_config.py | 79 ++-- puppet-manifests/tests/filesystem_mock.py | 2 +- .../tests/test_apply_network_config.py | 370 +++++++++++++----- 3 files changed, 316 insertions(+), 135 deletions(-) diff --git a/puppet-manifests/src/bin/apply_network_config.py b/puppet-manifests/src/bin/apply_network_config.py index 822e3bc21..c7ca8f2a8 100644 --- a/puppet-manifests/src/bin/apply_network_config.py +++ b/puppet-manifests/src/bin/apply_network_config.py @@ -270,8 +270,11 @@ def parse_interface_stanzas(): def get_current_config(): '''Gets current network config in etc directory''' + LOG.info(f"Parsing contents of the {ETC_DIR} directory to gather current network configuration") auto = parse_auto_file() - ifaces = parse_ifcfg_files(auto) + ifaces = parse_etc_dir() + if len(ifaces) == 0: + LOG.warning(f"No interface config found in {ETC_DIR}") return build_config(auto, ifaces, is_from_puppet=False) @@ -317,31 +320,16 @@ def get_ifcfg_path(iface): return os.path.join(ETC_DIR, CFG_PREFIX + iface) -def parse_ifcfg_files(ifaces): - iface_configs = dict() - for iface in ifaces: - iface_configs[iface] = parse_ifcfg_file(iface) - return iface_configs - - -def parse_ifcfg_file(iface): - path = get_ifcfg_path(iface) - if not os.path.isfile(path): - LOG.warning(f"Interface config file not found: '{path}'") - return dict() - lines = read_file_lines(path) - _, ifaces = StanzaParser.ParseLines(lines) - if len(ifaces) == 0: - LOG.warning(f"No interface config found in '{path}'") - return dict() - if (ifconfig := ifaces.get(iface, None)) is None: - LOG.warning(f"Config for interface '{iface}' not found in '{path}'. Instead, file has " - f"config(s) for the following interface(s): {' '.join(sorted(ifaces.keys()))}") - return dict() - if len(ifaces) > 1: - LOG.warning(f"Multiple interface configs found in '{path}': " - f"{' '.join(sorted(ifaces.keys()))}") - return ifconfig +def parse_etc_dir(): + parser = StanzaParser() + files = os.listdir(ETC_DIR) + for file in files: + file_path = ETC_DIR + "/" + file + if os.path.isfile(file_path): + LOG.info(f"Parsing file {file_path}") + lines = read_file_lines(file_path) + parser.parse_lines(lines) + return parser.get_auto_and_ifaces()[1] def get_types_and_dependencies(iface_configs): @@ -412,7 +400,10 @@ def get_modified_ifaces(new_config, current_config): modified = set() new_ifaces = new_config["ifaces"] current_ifaces = current_config["ifaces"] - for iface, new_if_config in new_ifaces.items(): + for iface in new_config["auto"]: + if iface not in current_config["auto"]: + continue + new_if_config = new_ifaces[iface] current_if_config = current_ifaces.get(iface, None) if not current_if_config: continue @@ -468,10 +459,18 @@ def get_dependent_list(config, ifaces): return covered -def get_down_list(current_config, comparison): +def get_down_list(current_config, new_config, comparison): base_set = comparison["modified"].union(comparison["removed"]) + for iface in sorted(comparison["added"]): + if iface not in base_set and is_iface_up(iface): + LOG.info(f"Interface {iface} not in {ETC_DIR}/auto but currently up, " + "adding to DOWN list") + base_set.add(iface) + if iface not in current_config["ifaces_types"]: + current_config["ifaces_types"][iface] = new_config["ifaces_types"][iface] dependents = get_dependent_list(current_config, base_set) - return base_set.union(dependents) + down_list = base_set.union(dependents) + return down_list def get_up_list(new_config, comparison): @@ -814,10 +813,6 @@ def disable_pxeboot_interface(): for iface in ifaces.keys(): LOG.info(f"Turn off pxeboot install config for {iface}, will be turned on later") set_iface_down(iface) - if is_label(iface): - base_iface = get_base_iface(iface) - LOG.info(f"Turn off pxeboot for base interface {base_iface}") - set_iface_down(base_iface) LOG.info("Remove ifcfg-pxeboot, left from kickstart install phase") remove_iface_config_file("pxeboot") @@ -826,8 +821,8 @@ def disable_pxeboot_interface(): def update_ifaces_ifupdown(new_config): current_config = get_current_config() comparison = compare_configs(new_config, current_config) - down_list = get_down_list(current_config, comparison) up_list = get_up_list(new_config, comparison) + down_list = get_down_list(current_config, new_config, comparison) lock = acquire_sysinv_agent_lock() if down_list or up_list else None try: @@ -852,11 +847,25 @@ def update_ifaces_online(config): return get_updated_ifaces(config, sorted_ifaces) +def is_iface_up(iface): + ifstate_path = IFSTATE_BASE_PATH + iface + if os.path.isfile(ifstate_path) and read_file_text(ifstate_path).strip() == iface: + return True + if is_label(iface): + return False + operstate_path = f"{DEVLINK_BASE_PATH}{iface}/operstate" + if os.path.isfile(operstate_path): + state = read_file_text(operstate_path) + if state.strip() == "up": + return True + return False + + def is_iface_missing_or_down(iface): path = f"{DEVLINK_BASE_PATH}{iface}/operstate" if os.path.isfile(path): state = read_file_text(path) - if state != "down": + if state.strip() != "down": return False return True diff --git a/puppet-manifests/tests/filesystem_mock.py b/puppet-manifests/tests/filesystem_mock.py index 109c4b077..4be56855f 100644 --- a/puppet-manifests/tests/filesystem_mock.py +++ b/puppet-manifests/tests/filesystem_mock.py @@ -283,7 +283,7 @@ class FilesystemMock(): entry[TARGET] = target_path self._call_listeners(entry) - def get_file_list(self, path): + def listdir(self, path): entry = self._get_entry(path, translate_link=True) if entry is None: raise FilesystemMockError("Path does not exist") diff --git a/puppet-manifests/tests/test_apply_network_config.py b/puppet-manifests/tests/test_apply_network_config.py index 0c7f81b8c..62761a3d1 100644 --- a/puppet-manifests/tests/test_apply_network_config.py +++ b/puppet-manifests/tests/test_apply_network_config.py @@ -21,7 +21,7 @@ class NetworkingMockError(BaseException): pass -class NetworkingMock(): # pylint: disable=too-many-instance-attributes +class NetworkingMock(): # pylint: disable=too-many-instance-attributes,too-many-public-methods def __init__(self, fs: FilesystemMock, ifaces: list): self._stdout = '' self._history = [] @@ -32,6 +32,7 @@ class NetworkingMock(): # pylint: disable=too-many-instance-attributes self._routes = dict() self._next_route_id = 0 self._allow_multiple_default_gateways = False + self._dhcp = dict() self._add_eth_ifaces(ifaces) self._fs.add_listener(anc.ETC_DIR, self._etc_dir_changed) @@ -50,13 +51,13 @@ class NetworkingMock(): # pylint: disable=too-many-instance-attributes def _add_eth_iface(self, iface): phys_path = self._get_device_path(iface) - self._fs.set_file_contents(phys_path + "/operstate", "down") + self._fs.set_file_contents(phys_path + "/operstate", "down\n") self._fs.set_link_contents(anc.DEVLINK_BASE_PATH + iface, phys_path) self._links[iface] = {"adm_state": False, "virtual": False, "addresses": set(), "routes": set()} def _parse_etc_interfaces(self): - file_list = self._fs.get_file_list(anc.ETC_DIR) + file_list = self._fs.listdir(anc.ETC_DIR) parser = anc.StanzaParser() for file in file_list: file_contents = self._fs.get_file_contents(anc.ETC_DIR + "/" + file) @@ -149,6 +150,9 @@ class NetworkingMock(): # pylint: disable=too-many-instance-attributes def get_history(self): return self._history + def enable_dhcp(self, iface_addresses): + self._dhcp = iface_addresses + def _add_history(self, command, *args): self._history.append((command, *args)) @@ -170,7 +174,7 @@ class NetworkingMock(): # pylint: disable=too-many-instance-attributes return link["adm_state"] = state operstate_path = self._get_device_path(iface, link["virtual"]) + "/operstate" - value = "up" if state else "down" + value = "up\n" if state else "down\n" self._fs.set_file_contents(operstate_path, value) def _create_virtual_link(self, name): @@ -178,7 +182,7 @@ class NetworkingMock(): # pylint: disable=too-many-instance-attributes self._print_stdout("RTNETLINK answers: File exists") return link, 1 phys_path = self._get_device_path(name, True) - self._fs.set_file_contents(phys_path + "/operstate", "down") + self._fs.set_file_contents(phys_path + "/operstate", "down\n") self._fs.set_link_contents(anc.DEVLINK_BASE_PATH + name, phys_path) link = {"adm_state": False, "virtual": True, "addresses": set(), "routes": set()} self._links[name] = link @@ -238,6 +242,12 @@ class NetworkingMock(): # pylint: disable=too-many-instance-attributes link["addresses"].add(address) if gateway := config.get("gateway", None): self._add_default_gateway(iface, link, gateway) + elif mode == "dhcp": + if address := self._dhcp.get(iface, None): + if address in link["addresses"]: + raise NetworkingMockError("DHCP lease address already assigned to link " + f"{iface}: {address}") + link["addresses"].add(address) return 0 def _remove_routes_associated_to_address(self, link, address): @@ -250,10 +260,14 @@ class NetworkingMock(): # pylint: disable=too-many-instance-attributes self._routes.pop(route_id) link["routes"].remove(route_id) - def _remove_address(self, config, link): + def _remove_address(self, iface, config, link): mode = config["mode"] + address = None if mode == "static": address = config["address"] + elif mode == "dhcp": + address = self._dhcp.get(iface, None) + if address: if address not in link["addresses"]: self._print_stdout(f"Error: ipv{address.version}: Address not found.") return 1 @@ -296,7 +310,7 @@ class NetworkingMock(): # pylint: disable=too-many-instance-attributes if retcode != 0: return 0 self._set_link_state(iface, link, False) - self._remove_address(config, link) + self._remove_address(iface, config, link) return 0 def _set_slave_up(self, iface, config): # pylint: disable=no-self-use,unused-argument @@ -324,7 +338,7 @@ class NetworkingMock(): # pylint: disable=too-many-instance-attributes link, retcode = self._get_link(iface) if retcode != 0: return 0 - self._remove_address(config, link) + self._remove_address(iface, config, link) self._set_link_state(iface, link, False) for slave in config["slaves"]: self._unenslave_iface(slave) @@ -359,7 +373,7 @@ class NetworkingMock(): # pylint: disable=too-many-instance-attributes link, retcode = self._get_link(iface) if retcode != 0: return 0 - self._remove_address(config, link) + self._remove_address(iface, config, link) self._set_link_state(iface, link, False) self._remove_virtual_link(iface) return 0 @@ -375,7 +389,7 @@ class NetworkingMock(): # pylint: disable=too-many-instance-attributes parent = config["parent"] link, retcode = self._get_link(parent) if retcode == 0: - self._remove_address(config, link) + self._remove_address(parent, config, link) return 0 def _set_ifstate(self, iface, state): @@ -1038,8 +1052,10 @@ class BaseTestCase(testtools.TestCase): def _add_logger_mock(self): self._log = LoggerMock() - def _add_nw_mock(self, static_links): + def _add_nw_mock(self, static_links, dhcp_config=None): self._nwmock = NetworkingMock(self._fs, static_links) + if dhcp_config: + self._nwmock.enable_dhcp(dhcp_config) def _add_scmd_mock(self): self._scmdmock = SystemCommandMock(self._nwmock) @@ -1048,6 +1064,7 @@ class BaseTestCase(testtools.TestCase): with ( mock.patch("src.bin.apply_network_config.path_exists", self._fs.exists), mock.patch("os.remove", self._fs.delete), + mock.patch("os.listdir", self._fs.listdir), mock.patch("builtins.open", self._fs.open), mock.patch.multiple("os.path", isfile=self._fs.isfile, @@ -1174,70 +1191,6 @@ class GeneralTests(BaseTestCase): # pylint: disable=too-many-public-methods "post-up echo # > /proc/sys/net/ipv6/conf/enp0s8/autoconf\n" "stx-description ifname:etc0,net:None\n") - def test_parse_valid_ifcfg_file(self): - self._add_fs_mock({anc.ETC_DIR + "/ifcfg-enp0s8": self._IFACE_FILE}) - config = self._mocked_call([self._mock_fs], anc.parse_ifcfg_file, "enp0s8") - self.assertEqual(6, len(config)) - self.assertEqual("enp0s8 inet static", config["iface"]) - self.assertEqual("12.12.1.55", config["address"]) - self.assertEqual("255.255.255.0", config["netmask"]) - self.assertEqual("9000", config["mtu"]) - self.assertEqual("echo # > /proc/sys/net/ipv6/conf/enp0s8/autoconf", config["post-up"]) - self.assertEqual("ifname:etc0,net:None", config["stx-description"]) - - def test_parse_missing_ifcfg_file(self): - self._add_fs_mock() - self._add_logger_mock() - config = self._mocked_call([self._mock_fs, self._mock_logger], - anc.parse_ifcfg_file, "enp0s8") - self.assertEqual(0, len(config)) - self.assertEqual(LoggerMock.WARNING, self._log.get_history()[-1][0]) - self.assertEqual(f"Interface config file not found: '{anc.ETC_DIR + '/ifcfg-enp0s8'}'", - self._log.get_history()[-1][1]) - - def test_parse_ifcfg_file_with_multiple_config(self): - path = anc.ETC_DIR + "/ifcfg-enp0s8" - self._add_fs_mock({path: self._IFACE_FILE + - "iface enp0s9 inet static\n" - "mtu 9000\n" - "stx-description ifname:etc1,net:None\n"}) - self._add_logger_mock() - config = self._mocked_call([self._mock_fs, self._mock_logger], - anc.parse_ifcfg_file, "enp0s8") - self.assertEqual(6, len(config)) - self.assertEqual(LoggerMock.WARNING, self._log.get_history()[-1][0]) - self.assertEqual(f"Multiple interface configs found in '{path}': enp0s8 enp0s9", - self._log.get_history()[-1][1]) - - def test_parse_invalid_ifcfg_file(self): - path = anc.ETC_DIR + "/ifcfg-enp0s8" - self._add_fs_mock({path: "invalid content line 1\n" - "invalid content line 2\n" - "invalid content line 3\n"}) - self._add_logger_mock() - config = self._mocked_call([self._mock_fs, self._mock_logger], - anc.parse_ifcfg_file, "enp0s8") - self.assertEqual(0, len(config)) - self.assertEqual(LoggerMock.WARNING, self._log.get_history()[-1][0]) - self.assertEqual(f"No interface config found in '{path}'", self._log.get_history()[-1][1]) - - def test_parse_ifcfg_file_with_unrelated_ifaces(self): - path = anc.ETC_DIR + "/ifcfg-enp0s8" - self._add_fs_mock({path: "iface enp0s9 inet static\n" - "mtu 9000\n" - "stx-description ifname:etc1,net:None\n" - "iface enp0s10 inet static\n" - "mtu 9000\n" - "stx-description ifname:etc2,net:None\n"}) - self._add_logger_mock() - config = self._mocked_call([self._mock_fs, self._mock_logger], - anc.parse_ifcfg_file, "enp0s8") - self.assertEqual(0, len(config)) - self.assertEqual(LoggerMock.WARNING, self._log.get_history()[-1][0]) - self.assertEqual(f"Config for interface 'enp0s8' not found in '{path}'. Instead, " - f"file has config(s) for the following interface(s): enp0s10 enp0s9", - self._log.get_history()[-1][1]) - def test_parse_auto_file(self): self._add_fs_mock({anc.ETC_DIR + "/auto": "auto lo enp0s3\tenp0s3:1-17 enp0s8 vlan100"}) @@ -1253,6 +1206,87 @@ class GeneralTests(BaseTestCase): # pylint: disable=too-many-public-methods self.assertEqual(f"Auto file not found: '{anc.ETC_DIR + '/auto'}'", self._log.get_history()[-1][1]) + def test_parse_etc_dir(self): + contents = dict() + contents[anc.ETC_DIR + "/auto"] = ( + "auto lo enp0s3 vlan20\n") + contents[anc.ETC_DIR + "/oam-config"] = ( + "iface enp0s3 inet manual\n" + "iface vlan20 inet static\n" + "address 177.122.10.34\n" + "netmask 255.255.255.0\n" + "gateway 177.122.10.1\n" + "vlan-raw-device enp0s3\n") + contents[anc.ETC_DIR + "/ifcfg-lo"] = ( + "auto lo\n" + "iface lo inet loopback\n") + contents[anc.ETC_DIR + "/ifcfg-enp0s8"] = ( + "auto enp0s8\n" + "iface enp0s8 inet manual\n" + "post-up echo 0 > /proc/sys/net/ipv6/conf/enp0s8/autoconf; " + "echo 0 > /proc/sys/net/ipv6/conf/enp0s8/accept_ra; " # noqa: E131 + "echo 0 > /proc/sys/net/ipv6/conf/enp0s8/accept_redirects\n") + contents[anc.ETC_DIR + "/ifcfg-pxeboot"] = ( + "auto enp0s8:2\n" + "iface enp0s8:2 inet dhcp\n") + contents[anc.ETC_DIR + "/ifcfg-vlan10"] = ( + "auto vlan10\n" + "iface vlan10 inet static\n" + "address 192.168.204.75\n" + "netmask 255.255.255.0\n" + "vlan-raw-device enp0s8\n") + + self._add_fs_mock(contents) + self._add_logger_mock() + + iface_configs = self._mocked_call([self._mock_fs, self._mock_logger], anc.parse_etc_dir) + + sorted_contents = [(ifname, sorted(iface_configs[ifname].items())) + for ifname in sorted(iface_configs.keys())] + self.assertEqual([ + ('enp0s3', [('iface', 'enp0s3 inet manual')]), + ('enp0s8', [('iface', 'enp0s8 inet manual'), + ('post-up', 'echo 0 > /proc/sys/net/ipv6/conf/enp0s8/autoconf; echo 0 > ' + '/proc/sys/net/ipv6/conf/enp0s8/accept_ra; echo 0 > ' + '/proc/sys/net/ipv6/conf/enp0s8/accept_redirects')]), + ('enp0s8:2', [('iface', 'enp0s8:2 inet dhcp')]), + ('lo', [('iface', 'lo inet loopback')]), + ('vlan10', [('address', '192.168.204.75'), + ('iface', 'vlan10 inet static'), + ('netmask', '255.255.255.0'), + ('vlan-raw-device', 'enp0s8')]), + ('vlan20', [('address', '177.122.10.34'), + ('gateway', '177.122.10.1'), + ('iface', 'vlan20 inet static'), + ('netmask', '255.255.255.0'), + ('vlan-raw-device', 'enp0s3')])], + sorted_contents) + + self.assertEqual([ + ('info', 'Parsing file /etc/network/interfaces.d/auto'), + ('info', 'Parsing file /etc/network/interfaces.d/ifcfg-enp0s8'), + ('info', 'Parsing file /etc/network/interfaces.d/ifcfg-lo'), + ('info', 'Parsing file /etc/network/interfaces.d/ifcfg-pxeboot'), + ('info', 'Parsing file /etc/network/interfaces.d/ifcfg-vlan10'), + ('info', 'Parsing file /etc/network/interfaces.d/oam-config')], + self._log.get_history()) + + def test_get_current_config_empty(self): + self._add_fs_mock({anc.ETC_DIR: None}) + self._add_logger_mock() + + config = self._mocked_call([self._mock_fs, self._mock_logger], anc.get_current_config) + + self.assertEqual({"auto": set(), "dependencies": {}, "ifaces": {}, "ifaces_types": {}}, + config) + + self.assertEqual([ + ('info', 'Parsing contents of the /etc/network/interfaces.d directory to gather ' + 'current network configuration'), + ('info', "Auto file not found: '/etc/network/interfaces.d/auto'"), + ('warning', 'No interface config found in /etc/network/interfaces.d')], + self._log.get_history()) + def test_get_vlan_attributes_vlanNNN(self): dev, vlan_id = anc.get_vlan_attributes("vlan123", {"vlan-raw-device": "enp0s8"}) self.assertEqual("enp0s8", dev) @@ -1407,7 +1441,7 @@ class GeneralTests(BaseTestCase): # pylint: disable=too-many-public-methods def test_is_iface_missing_or_down(self): dev_path = "/sys/devices/pci0000:00/net/enp0s8" - self._add_fs_mock({dev_path + "/operstate": "up", + self._add_fs_mock({dev_path + "/operstate": "up\n", anc.DEVLINK_BASE_PATH + "enp0s8": (dev_path, )}) def check_result(value): @@ -1416,7 +1450,7 @@ class GeneralTests(BaseTestCase): # pylint: disable=too-many-public-methods check_result(False) - self._fs.set_file_contents(anc.DEVLINK_BASE_PATH + "enp0s8/operstate", "down") + self._fs.set_file_contents(anc.DEVLINK_BASE_PATH + "enp0s8/operstate", "down\n") check_result(True) self._fs.delete(anc.DEVLINK_BASE_PATH + "enp0s8") @@ -1523,7 +1557,7 @@ class GeneralTests(BaseTestCase): # pylint: disable=too-many-public-methods raise Exception(f"Unexpected system command: '{cmd}'") dev_path = "/sys/devices/pci0000:00/net/enp0s8" - self._add_fs_mock({dev_path + "/operstate": "up", + self._add_fs_mock({dev_path + "/operstate": "up\n", anc.DEVLINK_BASE_PATH + "enp0s8": (dev_path, ), anc.IFSTATE_BASE_PATH + "enp0s8": "enp0s8"}) self._add_logger_mock() @@ -2155,32 +2189,45 @@ class GeneralTests(BaseTestCase): # pylint: disable=too-many-public-methods self._log.get_history()) def test_disable_kickstart_pxeboot(self): - etc_cfg = { - "interfaces": { - "auto": ["lo", "enp0s8"], - "lo": {}, - "enp0s8": {}, }, - } - puppet_cfg = { "interfaces": { - "auto": ["lo", "enp0s8", "enp0s8:2-3", "enp0s8:2-4"], + "auto": ["lo", "enp0s8", "vlan10", "vlan10:1-5"], "lo": {}, - "enp0s8": {"address": "169.254.202.2/24"}, - "enp0s8:2-3": {"address": "192.168.204.2/24"}, - "enp0s8:2-4": {"address": "fd01::2/64"}}, + "enp0s8": {"mode": "dhcp"}, + "vlan10": {"raw_dev": "enp0s8"}, + "vlan10:1-5": {"raw_dev": "enp0s8", "address": "192.168.204.75/24", + "gateway": "192.168.204.2"}}, } - contents = FILE_GEN.generate_file_tree(puppet_files=puppet_cfg, etc_files=etc_cfg) + contents = FILE_GEN.generate_file_tree(puppet_files=puppet_cfg) + contents[anc.ETC_DIR + "/ifcfg-lo"] = ( + "auto lo\n" + "iface lo inet loopback\n") + contents[anc.ETC_DIR + "/ifcfg-enp0s8"] = ( + "auto enp0s8\n" + "iface enp0s8 inet manual\n" + "post-up echo 0 > /proc/sys/net/ipv6/conf/enp0s8/autoconf; " + "echo 0 > /proc/sys/net/ipv6/conf/enp0s8/accept_ra; " # noqa: E131 + "echo 0 > /proc/sys/net/ipv6/conf/enp0s8/accept_redirects\n") contents[anc.ETC_DIR + "/ifcfg-pxeboot"] = ( "auto enp0s8:2\n" "iface enp0s8:2 inet dhcp\n" - " post-up echo 0 > /proc/sys/net/ipv6/conf/enp0s8/autoconf; " - "echo 0 > /proc/sys/net/ipv6/conf/enp0s8/accept_ra; " # noqa: E131 - "echo 0 > /proc/sys/net/ipv6/conf/enp0s8/accept_redirects\n") + "post-up echo 0 > /proc/sys/net/ipv6/conf/enp0s8/autoconf; " + "echo 0 > /proc/sys/net/ipv6/conf/enp0s8/accept_ra; " # noqa: E131 + "echo 0 > /proc/sys/net/ipv6/conf/enp0s8/accept_redirects\n") + contents[anc.ETC_DIR + "/ifcfg-vlan10"] = ( + "auto vlan10\n" + "iface vlan10 inet static\n" + "address 192.168.204.75\n" + "netmask 255.255.255.0\n" + "gateway 192.168.204.2\n" + "vlan-raw-device enp0s8\n" + "post-up echo 0 > /proc/sys/net/ipv6/conf/vlan10/autoconf; " + "echo 0 > /proc/sys/net/ipv6/conf/vlan10/accept_ra; " # noqa: E131 + "echo 0 > /proc/sys/net/ipv6/conf/vlan10/accept_redirects\n") self._add_fs_mock(contents) - self._add_nw_mock(["lo", "enp0s8"]) + self._add_nw_mock(["lo", "enp0s8"], {"enp0s8": IPNetwork("169.254.202.131/24")}) self._add_scmd_mock() self._add_logger_mock() self._nwmock.apply_auto() @@ -2188,14 +2235,139 @@ class GeneralTests(BaseTestCase): # pylint: disable=too-many-public-methods self._mocked_call([self._mock_fs, self._mock_syscmd, self._mock_sysinv_lock, self._mock_logger], anc.update_interfaces) + self.assertEqual([ + 'enp0s8 UP 169.254.202.131/24', + 'lo UP', + 'vlan10 UP VLAN(enp0s8,10) 192.168.204.75/24'], + self._nwmock.get_links_status()) + self.assertEqual([ ('info', 'Turn off pxeboot install config for enp0s8:2, will be turned on later'), ('info', 'Bringing enp0s8:2 down'), - ('info', 'Turn off pxeboot for base interface enp0s8'), - ('info', 'Bringing enp0s8 down'), ('info', 'Remove ifcfg-pxeboot, left from kickstart install phase'), - ('info', 'Removing /etc/network/interfaces.d/ifcfg-pxeboot')], - self._log.get_history()[:6]) + ('info', 'Removing /etc/network/interfaces.d/ifcfg-pxeboot'), + ('info', 'Parsing contents of the /etc/network/interfaces.d directory to gather ' + 'current network configuration'), + ('info', "Auto file not found: '/etc/network/interfaces.d/auto'"), + ('info', 'Parsing file /etc/network/interfaces.d/ifcfg-enp0s8'), + ('info', 'Parsing file /etc/network/interfaces.d/ifcfg-lo'), + ('info', 'Parsing file /etc/network/interfaces.d/ifcfg-vlan10'), + ('info', 'Added interfaces: enp0s8 lo vlan10 vlan10:1-5'), + ('info', 'Interface enp0s8 not in /etc/network/interfaces.d/auto but currently up, ' + 'adding to DOWN list'), + ('info', 'Interface lo not in /etc/network/interfaces.d/auto but currently up, ' + 'adding to DOWN list'), + ('info', 'Interface vlan10 not in /etc/network/interfaces.d/auto but currently up, ' + 'adding to DOWN list'), + ('info', 'Bringing vlan10 down'), + ('info', 'Bringing enp0s8 down'), + ('info', 'Bringing lo down'), + ('info', 'Bringing lo up'), + ('info', 'Bringing enp0s8 up'), + ('info', 'Bringing vlan10 up'), + ('info', 'Bringing vlan10:1-5 up')], + self._log.get_history()) + + def test_add_interface_link_up(self): + etc_cfg = { + "interfaces": { + "auto": ["lo"], + "lo": {}}, + } + + puppet_cfg = { + "interfaces": { + "auto": ["lo", "enp0s9", "enp0s9:4-17"], + "lo": {}, + "enp0s9": {}, + "enp0s9:4-17": {"address": "188.177.12.44/24"}}, + } + + contents = FILE_GEN.generate_file_tree(puppet_files=puppet_cfg, etc_files=etc_cfg) + self._add_fs_mock(contents) + self._add_nw_mock(["lo", "enp0s9"]) + self._add_scmd_mock() + self._add_logger_mock() + self._nwmock.apply_auto() + self._nwmock.ip_link_set_up("enp0s9") + self._nwmock.ip_addr_add("12.12.12.77/24", "enp0s9") + + self.assertEqual([ + 'enp0s9 UP 12.12.12.77/24', + 'lo UP'], + self._nwmock.get_links_status()) + + self._mocked_call([self._mock_fs, self._mock_syscmd, + self._mock_sysinv_lock, self._mock_logger], anc.update_interfaces) + + self.assertEqual([ + 'enp0s9 UP 188.177.12.44/24', + 'lo UP'], + self._nwmock.get_links_status()) + + self.assertEqual([ + ('info', 'Parsing contents of the /etc/network/interfaces.d directory to gather ' + 'current network configuration'), + ('info', 'Parsing file /etc/network/interfaces.d/auto'), + ('info', 'Parsing file /etc/network/interfaces.d/ifcfg-lo'), + ('info', 'Added interfaces: enp0s9 enp0s9:4-17'), + ('info', 'Interface enp0s9 not in /etc/network/interfaces.d/auto but currently up, ' + 'adding to DOWN list'), + ('info', 'Bringing enp0s9 down'), + ('info', 'Bringing enp0s9 up'), + ('info', 'Bringing enp0s9:4-17 up')], + self._log.get_history()) + + def test_add_interface_currently_up(self): + etc_cfg = { + "interfaces": { + "auto": ["lo"], + "lo": {}, + "enp0s9": {"address": "192.168.12.45/24"}}, + } + + puppet_cfg = { + "interfaces": { + "auto": ["lo", "enp0s9", "enp0s9:4-17"], + "lo": {}, + "enp0s9": {}, + "enp0s9:4-17": {"address": "188.177.12.44/24"}}, + } + + contents = FILE_GEN.generate_file_tree(puppet_files=puppet_cfg, etc_files=etc_cfg) + self._add_fs_mock(contents) + self._add_nw_mock(["lo", "enp0s9"]) + self._add_scmd_mock() + self._add_logger_mock() + self._nwmock.apply_auto() + self._nwmock.ifup("enp0s9") + + self.assertEqual([ + 'enp0s9 UP 192.168.12.45/24', + 'lo UP'], + self._nwmock.get_links_status()) + + self._mocked_call([self._mock_fs, self._mock_syscmd, + self._mock_sysinv_lock, self._mock_logger], anc.update_interfaces) + + self.assertEqual([ + 'enp0s9 UP 188.177.12.44/24', + 'lo UP'], + self._nwmock.get_links_status()) + + self.assertEqual([ + ('info', 'Parsing contents of the /etc/network/interfaces.d directory to gather ' + 'current network configuration'), + ('info', 'Parsing file /etc/network/interfaces.d/auto'), + ('info', 'Parsing file /etc/network/interfaces.d/ifcfg-enp0s9'), + ('info', 'Parsing file /etc/network/interfaces.d/ifcfg-lo'), + ('info', 'Added interfaces: enp0s9 enp0s9:4-17'), + ('info', 'Interface enp0s9 not in /etc/network/interfaces.d/auto but currently up, ' + 'adding to DOWN list'), + ('info', 'Bringing enp0s9 down'), + ('info', 'Bringing enp0s9 up'), + ('info', 'Bringing enp0s9:4-17 up')], + self._log.get_history()) def test_execute_system_cmd(self): retcode, stdout = anc.execute_system_cmd('echo "test_execute_system_cmd"') @@ -2451,7 +2623,7 @@ class MigrationBaseTestCase(BaseTestCase): self._mock_sysinv_lock, self._mock_logger], anc.apply_config, False) def _check_etc_file_list(self, to_cfg): - files = self._fs.get_file_list(anc.ETC_DIR) + files = self._fs.listdir(anc.ETC_DIR) etc_ifaces = [] has_auto = False for file in files: