def command_generate(self): argv = [utils.get_generator_path()] if self.root_dir: argv += ['--root-dir', self.root_dir] if self.mapping: argv += ['--mapping', self.mapping] logging.debug('command generate: running %s', argv) # FIXME: os.execv(argv[0], argv) would be better but fails coverage sys.exit(subprocess.call(argv))
def command_generate(self): # 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") # pragma: nocover # 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. # https://github.com/snapcore/snapd/pull/10212 res = subprocess.call([busctl, "call", "--quiet", "--system", "io.netplan.Netplan", # the service "/io/netplan/Netplan", # the object "io.netplan.Netplan", # the interface "Generate", # the method ]) if res != 0: if res == 130: raise PermissionError( "failed to communicate with dbus service") else: raise RuntimeError( "failed to communicate with dbus service: error %s" % res) else: return argv = [utils.get_generator_path()] if self.root_dir: argv += ['--root-dir', self.root_dir] if self.mapping: argv += ['--mapping', self.mapping] logging.debug('command generate: running %s', argv) # FIXME: os.execv(argv[0], argv) would be better but fails coverage sys.exit(subprocess.call(argv))
def command_apply( run_generate=True, sync=False, exit_on_error=True): # pragma: nocover (covered in autopkgtest) if run_generate and subprocess.call([utils.get_generator_path()]) != 0: if exit_on_error: sys.exit(os.EX_CONFIG) else: raise ConfigurationError( "the configuration could not be generated") devices = os.listdir('/sys/class/net') restart_networkd = bool(glob.glob('/run/systemd/network/*netplan-*')) restart_nm = bool( glob.glob('/run/NetworkManager/system-connections/netplan-*')) # stop backends if restart_networkd: logging.debug( 'netplan generated networkd configuration exists, restarting networkd' ) utils.systemctl_networkd('stop', sync=sync, extra_services=['netplan-wpa@*.service']) else: logging.debug('no netplan generated networkd configuration exists') if restart_nm: logging.debug( 'netplan generated NM configuration exists, 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') # force-hotplug all "down" network interfaces to apply renames any_replug = False for device in devices: if not os.path.islink('/sys/class/net/' + device): continue if NetplanApply.replug(device): any_replug = True else: # if the interface is up, we can still apply .link file changes logging.debug('netplan triggering .link rules for %s', device) with open(os.devnull, 'w') as fd: subprocess.check_call([ 'udevadm', 'test-builtin', 'net_setup_link', '/sys/class/net/' + device ], stdout=fd, stderr=fd) if any_replug: 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)
def command_ip_leases(self): if self.interface == 'help': # pragma: nocover (covered in autopkgtest) self.print_usage() def find_lease_file(mapping): def lease_method_ifindex(): ifindex_f = os.path.join('/sys/class/net', self.interface, 'ifindex') try: with open(ifindex_f) as f: return f.readlines()[0].strip() except Exception as e: logging.debug('Cannot read file %s: %s', ifindex_f, str(e)) raise def lease_method_nm_connection( ): # pragma: nocover (covered in autopkgtest) # FIXME: handle older versions of NM where 'nmcli dev show' doesn't exist try: nmcli_dev_out = subprocess.Popen( ['nmcli', 'dev', 'show', self.interface], env={'LC_ALL': 'C'}, stdout=subprocess.PIPE) for line in nmcli_dev_out.stdout: line = line.decode('utf-8') if 'GENERAL.CONNECTION' in line: conn_id = line.split(':')[1].rstrip().strip() nmcli_con_out = subprocess.Popen( ['nmcli', 'con', 'show', 'id', conn_id], env={'LC_ALL': 'C'}, stdout=subprocess.PIPE) for line in nmcli_con_out.stdout: line = line.decode('utf-8') if 'connection.uuid' in line: return line.split(':')[1].rstrip().strip() except Exception as e: raise Exception( 'Could not find a NetworkManager connection for the interface: %s' % str(e)) raise Exception( 'Could not find a NetworkManager connection for the interface' ) lease_pattern = lease_path[mapping['backend']]['pattern'] lease_method = lease_path[mapping['backend']]['method'] try: lease_id = eval("lease_method_" + lease_method)() # We found something to build the path to the lease file with, # at this point we may have something to look at; but if not, # we'll rely on open() throwing an error. # This might happen if networkd doesn't use DHCP for the interface, # for instance. with open( os.path.join( '/', os.path.abspath(self.root_dir) if self.root_dir else "", lease_pattern.format(interface=self.interface, lease_id=lease_id))) as f: for line in f.readlines(): print(line.rstrip()) except Exception as e: print("No lease found for interface '%s': %s" % (self.interface, str(e)), file=sys.stderr) sys.exit(1) argv = [utils.get_generator_path()] if self.root_dir: argv += ['--root-dir', self.root_dir] argv += ['--mapping', self.interface] # Extract out of the generator our mapping in a dict. logging.debug('command ip leases: running %s', argv) try: out = subprocess.check_output(argv, universal_newlines=True) except CalledProcessError: # pragma: nocover (better be covered in autopkgtest) sys.exit(1) mapping = {} mapping_s = out.split(',') for keyvalue in mapping_s: key, value = keyvalue.strip().split('=') mapping[key] = value find_lease_file(mapping)
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' ) utils.systemctl_networkd('stop', sync=sync, extra_services=['netplan-wpa@*.service']) 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) # 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)
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 run_generate and subprocess.call([utils.get_generator_path()]) != 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() restart_networkd = bool(glob.glob('/run/systemd/network/*netplan-*')) restart_nm = bool( glob.glob('/run/NetworkManager/system-connections/netplan-*')) # stop backends if restart_networkd: logging.debug( 'netplan generated networkd configuration exists, restarting networkd' ) utils.systemctl_networkd('stop', sync=sync, extra_services=['netplan-wpa@*.service']) else: logging.debug('no netplan generated networkd configuration exists') if restart_nm: logging.debug( 'netplan generated NM configuration exists, 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') # 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 for device in devices: logging.debug('netplan triggering .link rules for %s', device) subprocess.check_call([ 'udevadm', 'test-builtin', 'net_setup_link', '/sys/class/net/' + device ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # 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)