def test_systemctl_is_active(self): self.mock_cmd = MockCmd('systemctl') path_env = os.environ['PATH'] os.environ['PATH'] = os.path.dirname( self.mock_cmd.path) + os.pathsep + path_env self.assertTrue(utils.systemctl_is_active('some.service')) self.assertEquals( self.mock_cmd.calls(), [['systemctl', '--quiet', 'is-active', 'some.service']])
def command_apply(self, run_generate=True, sync=False, exit_on_error=True): # pragma: nocover (covered in autopkgtest) config_manager = ConfigManager() # For certain use-cases, we might want to only apply specific configuration. # If we only need SR-IOV configuration, do that and exit early. if self.sriov_only: NetplanApply.process_sriov_config(config_manager, exit_on_error) return # If we only need OpenVSwitch cleanup, do that and exit early. elif self.only_ovs_cleanup: NetplanApply.process_ovs_cleanup(config_manager, False, False, exit_on_error) return # 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") # XXX: DO NOT TOUCH or change this API call, it is used by snapd to communicate # using core20 netplan binary/client/CLI on core18 base systems. Any change # must be agreed upon with the snapd team, so we don't break support for # base systems running older netplan versions. 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 ovs_cleanup_service = '/run/systemd/system/netplan-ovs-cleanup.service' old_files_networkd = bool(glob.glob('/run/systemd/network/*netplan-*')) old_ovs_glob = glob.glob('/run/systemd/system/netplan-ovs-*') # Ignore netplan-ovs-cleanup.service, as it can always be there if ovs_cleanup_service in old_ovs_glob: old_ovs_glob.remove(ovs_cleanup_service) old_files_ovs = bool(old_ovs_glob) old_nm_glob = glob.glob('/run/NetworkManager/system-connections/netplan-*') nm_ifaces = utils.nm_interfaces(old_nm_glob, netifaces.interfaces()) old_files_nm = bool(old_nm_glob) 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") 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_ovs_glob = glob.glob('/run/systemd/system/netplan-ovs-*') # Ignore netplan-ovs-cleanup.service, as it can always be there if ovs_cleanup_service in restart_ovs_glob: restart_ovs_glob.remove(ovs_cleanup_service) restart_ovs = bool(restart_ovs_glob) if not restart_ovs and old_files_ovs: # OVS is managed via systemd units restart_networkd = True restart_nm_glob = glob.glob('/run/NetworkManager/system-connections/netplan-*') nm_ifaces.update(utils.nm_interfaces(restart_nm_glob, devices)) restart_nm = bool(restart_nm_glob) if not restart_nm and old_files_nm: restart_nm = True # stop backends if restart_networkd: logging.debug('netplan generated networkd configuration changed, reloading 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() # Clean up any old netplan related OVS ports/bonds/bridges, if applicable NetplanApply.process_ovs_cleanup(config_manager, old_files_ovs, restart_ovs, exit_on_error) 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('stop', wpa_services, sync=sync) 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: if device not in nm_ifaces: continue # do not touch this interface # 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) # if the interface is up, we can still apply some .link file changes # but we cannot apply the interface rename via udev, as it won't touch # the interface name, if it was already renamed once (e.g. during boot), # because of the NamePolicy=keep default: # https://www.freedesktop.org/software/systemd/man/systemd.net-naming-scheme.html 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 some more changes manually for iface, settings in changes.items(): # rename non-critical network interfaces if settings.get('name'): # bring down the interface, using its current (matched) interface name subprocess.check_call(['ip', 'link', 'set', 'dev', iface, 'down'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # rename the interface to the name given via 'set-name' subprocess.check_call(['ip', 'link', 'set', 'dev', iface, 'name', settings.get('name')], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) subprocess.check_call(['udevadm', 'settle']) # apply any SR-IOV related changes, if applicable NetplanApply.process_sriov_config(config_manager, exit_on_error) # (re)start backends if restart_networkd: netplan_wpa = [os.path.basename(f) for f in glob.glob('/run/systemd/system/*.wants/netplan-wpa-*.service')] # exclude the special 'netplan-ovs-cleanup.service' unit netplan_ovs = [os.path.basename(f) for f in glob.glob('/run/systemd/system/*.wants/netplan-ovs-*.service') if not f.endswith('/' + OVS_CLEANUP_SERVICE)] # Run 'systemctl start' command synchronously, to avoid race conditions # with 'oneshot' systemd service units, e.g. netplan-ovs-*.service. utils.networkctl_reconfigure(utils.networkd_interfaces()) # 1st: execute OVS cleanup, to avoid races while applying OVS config utils.systemctl('start', [OVS_CLEANUP_SERVICE], sync=True) # 2nd: start all other services utils.systemctl('start', netplan_wpa + netplan_ovs, sync=True) if restart_nm: # Flush all IP addresses of NM managed interfaces, to avoid NM creating # new, non netplan-* connection profiles, using the existing IPs. for iface in utils.nm_interfaces(restart_nm_glob, devices): utils.ip_addr_flush(iface) utils.systemctl_network_manager('start', sync=sync)
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)