def process_sriov_config(config_manager, exit_on_error=True): # pragma: nocover (covered in autopkgtest) try: apply_sriov_config(config_manager) except (ConfigurationError, RuntimeError) as e: logging.error(str(e)) if exit_on_error: sys.exit(1)
def test_apply_sriov_config(self, gim, gidn, apply_vlan, quirks, set_numvfs, get_counts, netifs): # set up the environment with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd: print('''network: version: 2 renderer: networkd ethernets: enp1: mtu: 9000 enpx: match: name: enp[2-3] enp1s16f1: link: enp1 macaddress: 01:02:03:04:05:00 enp1s16f2: link: enp1 customvf1: match: name: enp[2-3]s16f[1-4] link: enpx vlans: vf1.15: renderer: sriov id: 15 link: customvf1 vf1.16: renderer: sriov id: 16 link: foobar ''', file=fd) self.configmanager.parse() interfaces = ['enp1', 'enp2', 'enp5', 'wlp6s0'] # set up all the mock objects netifs.return_value = [ 'enp1', 'enp2', 'enp5', 'wlp6s0', 'enp1s16f1', 'enp1s16f2', 'enp2s16f1' ] get_counts.side_effect = mock_set_counts set_numvfs.side_effect = lambda pf, _: False if pf == 'enp2' else True gidn.return_value = 'foodriver' gim.return_value = '00:01:02:03:04:05' # call method under test sriov.apply_sriov_config(interfaces, self.configmanager) # check if the config got applied as expected # we had 2 PFs, one having two VFs and the other only one self.assertEqual(set_numvfs.call_count, 2) self.assertListEqual( set_numvfs.call_args_list, [call('enp1', 2), call('enp2', 1)]) # one of the pfs already had sufficient VFs allocated, so only enp1 # changed the vf count and only that one should trigger quirks quirks.assert_called_once_with('enp1') # only one had a hardware vlan apply_vlan.assert_called_once_with('enp2', 'enp2s16f1', 'vf1.15', 15)
def test_apply_sriov_config_too_many_vlans(self, gim, gidn, apply_vlan, quirks, set_numvfs, get_counts, netifs): # set up the environment with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd: print('''network: version: 2 renderer: networkd ethernets: enp1: mtu: 9000 enpx: match: name: enp[2-3] enp1s16f1: link: enp1 macaddress: 01:02:03:04:05:00 enp1s16f2: link: enp1 customvf1: match: name: enp[2-3]s16f[1-4] link: enpx vlans: vf1.15: renderer: sriov id: 15 link: customvf1 vf1.16: renderer: sriov id: 16 link: customvf1 ''', file=fd) self.configmanager.parse() interfaces = ['enp1', 'enp2', 'enp5', 'wlp6s0'] # set up all the mock objects netifs.return_value = [ 'enp1', 'enp2', 'enp5', 'wlp6s0', 'enp1s16f1', 'enp1s16f2', 'enp2s16f1' ] get_counts.side_effect = mock_set_counts set_numvfs.side_effect = lambda pf, _: False if pf == 'enp2' else True gidn.return_value = 'foodriver' gim.return_value = '00:01:02:03:04:05' # call method under test with self.assertRaises(ConfigurationError) as e: sriov.apply_sriov_config(interfaces, self.configmanager) self.assertIn( 'interface enp2s16f1 for netplan device customvf1 (vf1.16) already has an SR-IOV vlan defined', str(e.exception)) self.assertEqual(apply_vlan.call_count, 1)
def test_apply_sriov_config_many_match(self, gim, gidn, apply_vlan, quirks, set_numvfs, get_counts, netifs): # set up the environment with open(os.path.join(self.workdir.name, "etc/netplan/test.yaml"), 'w') as fd: print('''network: version: 2 renderer: networkd ethernets: enp1: mtu: 9000 enpx: match: name: enp[2-3] enp1s16f1: link: enp1 macaddress: 01:02:03:04:05:00 enp1s16f2: link: enp1 customvf1: match: name: enp*s16f[1-4] link: enpx ''', file=fd) # set up all the mock objects netifs.return_value = [ 'enp1', 'enp2', 'enp5', 'wlp6s0', 'enp1s16f1', 'enp1s16f2', 'enp2s16f1' ] get_counts.side_effect = mock_set_counts set_numvfs.side_effect = lambda pf, _: False if pf == 'enp2' else True gidn.return_value = 'foodriver' gim.return_value = '00:01:02:03:04:05' # call method under test with self.assertRaises(ConfigurationError) as e: sriov.apply_sriov_config(self.configmanager) self.assertIn( 'matched more than one interface for a VF device: customvf1', str(e.exception))
def command_apply( run_generate=True, sync=False, exit_on_error=True): # pragma: nocover (covered in autopkgtest) # if we are inside a snap, then call dbus to run netplan apply instead if "SNAP" in os.environ: # TODO: maybe check if we are inside a classic snap and don't do # this if we are in a classic snap? busctl = shutil.which("busctl") if busctl is None: raise RuntimeError("missing busctl utility") res = subprocess.call([ busctl, "call", "--quiet", "--system", "io.netplan.Netplan", # the service "/io/netplan/Netplan", # the object "io.netplan.Netplan", # the interface "Apply", # the method ]) if res != 0: if exit_on_error: sys.exit(res) elif res == 130: raise PermissionError( "failed to communicate with dbus service") elif res == 1: raise RuntimeError( "failed to communicate with dbus service") else: return old_files_networkd = bool(glob.glob('/run/systemd/network/*netplan-*')) old_files_nm = bool( glob.glob('/run/NetworkManager/system-connections/netplan-*')) generator_call = [] generate_out = None if 'NETPLAN_PROFILE' in os.environ: generator_call.extend(['valgrind', '--leak-check=full']) generate_out = subprocess.STDOUT generator_call.append(utils.get_generator_path()) if run_generate and subprocess.call(generator_call, stderr=generate_out) != 0: if exit_on_error: sys.exit(os.EX_CONFIG) else: raise ConfigurationError( "the configuration could not be generated") config_manager = ConfigManager() devices = netifaces.interfaces() # Re-start service when # 1. We have configuration files for it # 2. Previously we had config files for it but not anymore # Ideally we should compare the content of the *netplan-* files before and # after generation to minimize the number of re-starts, but the conditions # above works too. restart_networkd = bool(glob.glob('/run/systemd/network/*netplan-*')) if not restart_networkd and old_files_networkd: restart_networkd = True restart_nm = bool( glob.glob('/run/NetworkManager/system-connections/netplan-*')) if not restart_nm and old_files_nm: restart_nm = True # stop backends if restart_networkd: logging.debug( 'netplan generated networkd configuration changed, restarting networkd' ) # Running 'systemctl daemon-reload' will re-run the netplan systemd generator, # so let's make sure we only run it iff we're willing to run 'netplan generate' if run_generate: utils.systemctl_daemon_reload() wpa_services = ['netplan-wpa-*.service'] # Historically (up to v0.98) we had netplan-wpa@*.service files, in case of an # upgraded system, we need to make sure to stop those. if utils.systemctl_is_active('netplan-wpa@*.service'): wpa_services.insert(0, 'netplan-wpa@*.service') utils.systemctl_networkd('stop', sync=sync, extra_services=wpa_services) else: logging.debug('no netplan generated networkd configuration exists') if restart_nm: logging.debug( 'netplan generated NM configuration changed, restarting NM') if utils.nm_running(): # restarting NM does not cause new config to be applied, need to shut down devices first for device in devices: # ignore failures here -- some/many devices might not be managed by NM try: utils.nmcli(['device', 'disconnect', device]) except subprocess.CalledProcessError: pass utils.systemctl_network_manager('stop', sync=sync) else: logging.debug('no netplan generated NM configuration exists') # Refresh devices now; restarting a backend might have made something appear. devices = netifaces.interfaces() # evaluate config for extra steps we need to take (like renaming) # for now, only applies to non-virtual (real) devices. config_manager.parse() changes = NetplanApply.process_link_changes(devices, config_manager) # apply any SR-IOV related changes, if applicable try: apply_sriov_config(devices, config_manager) except (ConfigurationError, RuntimeError) as e: logging.error(str(e)) if exit_on_error: sys.exit(1) # if the interface is up, we can still apply some .link file changes devices = netifaces.interfaces() for device in devices: logging.debug('netplan triggering .link rules for %s', device) try: subprocess.check_call([ 'udevadm', 'test-builtin', 'net_setup_link', '/sys/class/net/' + device ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except subprocess.CalledProcessError: logging.debug('Ignoring device without syspath: %s', device) # apply renames to "down" devices for iface, settings in changes.items(): if settings.get('name'): subprocess.check_call([ 'ip', 'link', 'set', 'dev', iface, 'name', settings.get('name') ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) subprocess.check_call(['udevadm', 'settle']) # (re)start backends if restart_networkd: netplan_wpa = [ os.path.basename(f) for f in glob.glob( '/run/systemd/system/*.wants/netplan-wpa-*.service') ] utils.systemctl_networkd('start', sync=sync, extra_services=netplan_wpa) if restart_nm: utils.systemctl_network_manager('start', sync=sync)