def collect_logs(tarfile, include_userdata): """Collect all cloud-init logs and tar them up into the provided tarfile. @param tarfile: The path of the tar-gzipped file to create. @param include_userdata: Boolean, true means include user-data. """ tarfile = os.path.abspath(tarfile) date = datetime.utcnow().date().strftime('%Y-%m-%d') log_dir = 'cloud-init-logs-{0}'.format(date) with tempdir(dir='/tmp') as tmp_dir: log_dir = os.path.join(tmp_dir, log_dir) _write_command_output_to_file( ['dpkg-query', '--show', "-f=${Version}\n", 'cloud-init'], os.path.join(log_dir, 'version')) _write_command_output_to_file(['dmesg'], os.path.join(log_dir, 'dmesg.txt')) _write_command_output_to_file(['journalctl', '-o', 'short-precise'], os.path.join(log_dir, 'journal.txt')) for log in CLOUDINIT_LOGS: copy(log, log_dir) if include_userdata: copy(USER_DATA_FILE, log_dir) run_dir = os.path.join(log_dir, 'run') ensure_dir(run_dir) shutil.copytree(CLOUDINIT_RUN_DIR, os.path.join(run_dir, 'cloud-init')) with chdir(tmp_dir): subp(['tar', 'czvf', tarfile, log_dir.replace(tmp_dir + '/', '')])
def dhcp_discovery(dhclient_cmd_path, interface, cleandir): """Run dhclient on the interface without scripts or filesystem artifacts. @param dhclient_cmd_path: Full path to the dhclient used. @param interface: Name of the network inteface on which to dhclient. @param cleandir: The directory from which to run dhclient as well as store dhcp leases. @return: A dict of dhcp options parsed from the dhcp.leases file or empty dict. """ LOG.debug('Performing a dhcp discovery on %s', interface) # XXX We copy dhclient out of /sbin/dhclient to avoid dealing with strict # app armor profiles which disallow running dhclient -sf <our-script-file>. # We want to avoid running /sbin/dhclient-script because of side-effects in # /etc/resolv.conf any any other vendor specific scripts in # /etc/dhcp/dhclient*hooks.d. sandbox_dhclient_cmd = os.path.join(cleandir, 'dhclient') util.copy(dhclient_cmd_path, sandbox_dhclient_cmd) pid_file = os.path.join(cleandir, 'dhclient.pid') lease_file = os.path.join(cleandir, 'dhcp.leases') # ISC dhclient needs the interface up to send initial discovery packets. # Generally dhclient relies on dhclient-script PREINIT action to bring the # link up before attempting discovery. Since we are using -sf /bin/true, # we need to do that "link up" ourselves first. util.subp(['ip', 'link', 'set', 'dev', interface, 'up'], capture=True) cmd = [ sandbox_dhclient_cmd, '-1', '-v', '-lf', lease_file, '-pf', pid_file, interface, '-sf', '/bin/true' ] util.subp(cmd, capture=True) return parse_dhcp_lease_file(lease_file)
def apply_locale(self, locale, out_fn=None): # Adjust the locals value to the new value newconf = StringIO() for line in util.load_file(self.login_conf_fn).splitlines(): newconf.write( re.sub(r'^default:', r'default:lang=%s:' % locale, line)) newconf.write("\n") # Make a backup of login.conf. util.copy(self.login_conf_fn, self.login_conf_fn_bak) # And write the new login.conf. util.write_file(self.login_conf_fn, newconf.getvalue()) try: LOG.debug("Running cap_mkdb for %s", locale) util.subp(['cap_mkdb', self.login_conf_fn]) except util.ProcessExecutionError: # cap_mkdb failed, so restore the backup. util.logexc(LOG, "Failed to apply locale %s", locale) try: util.copy(self.login_conf_fn_bak, self.login_conf_fn) except IOError: util.logexc(LOG, "Failed to restore %s backup", self.login_conf_fn)
def execute(self): """ This method executes post-customization script before or after reboot based on the presence of rc local. """ self.prepare_script() self.install_agent() if not self.postreboot: LOG.warning("Executing post-customization script inline") util.subp(["/bin/sh", self.scriptpath, "postcustomization"]) else: LOG.debug("Scheduling custom script to run post reboot") if not os.path.isdir(CustomScriptConstant.POST_CUST_TMP_DIR): os.mkdir(CustomScriptConstant.POST_CUST_TMP_DIR) # Script "post-customize-guest.sh" and user uploaded script are # are present in the same directory and needs to copied to a temp # directory to be executed post reboot. User uploaded script is # saved as customize.sh in the temp directory. # post-customize-guest.sh excutes customize.sh after reboot. LOG.debug("Copying post-customization script") util.copy(self.scriptpath, CustomScriptConstant.POST_CUST_TMP_DIR + "/customize.sh") LOG.debug("Copying script to run post-customization script") util.copy( os.path.join(self.directory, CustomScriptConstant.POST_CUST_RUN_SCRIPT_NAME), CustomScriptConstant.POST_CUST_RUN_SCRIPT) LOG.info("Creating post-reboot pending marker") util.ensure_file(CustomScriptConstant.POST_REBOOT_PENDING_MARKER)
def set_etc_timezone(tz, tz_file=None, tz_conf="/etc/timezone", tz_local="/etc/localtime"): util.write_file(tz_conf, str(tz).rstrip() + "\n") # This ensures that the correct tz will be used for the system if tz_local and tz_file: util.copy(tz_file, tz_local) return
def set_timezone(self, tz): tz_file = self._find_tz_file(tz) # Adjust the sysconfig clock zone setting clock_cfg = {"TIMEZONE": str(tz)} rhel_util.update_sysconfig_file(self.clock_conf_fn, clock_cfg) # This ensures that the correct tz will be used for the system util.copy(tz_file, self.tz_local_fn)
def download_extension(extension_url): """Downloads an iControl LX RPM package prior to installation processing""" if not os.path.isdir(PKG_INSTALL_DIR): os.makedirs(PKG_INSTALL_DIR) try: parsed_url = urlparse.urlparse(extension_url) fqdn = parsed_url.netloc if wait_for_dns_resolution(fqdn, 120): tmp_file_name = '/tmp/download_file.part' dest_file = os.path.basename(extension_url) if os.path.isfile(tmp_file_name): util.del_file(tmp_file_name) LOG.debug('GET %s', extension_url) resp = requests.get(extension_url, stream=True, allow_redirects=True) resp.raise_for_status() cont_disp = resp.headers.get('content-disposition') if cont_disp: cont_disp_fn = re.findall('filename=(.+)', cont_disp) if cont_disp_fn > 0: dest_file = cont_disp_fn[0] with open(tmp_file_name, 'wb') as out_file: for chunk in resp.iter_content(chunk_size=8192): if chunk: out_file.write(chunk) if os.path.isfile(tmp_file_name): dest_file = PKG_INSTALL_DIR + '/' + dest_file util.copy(tmp_file_name, dest_file) return True LOG.error('could not copy %s to %s', extension_url, dest_file) return False except Exception as err: LOG.error("could not download: %s - %s", extension_url, err) return False
def config_dhcp(interface, info, create=True): infile = "/etc/dhcpcd.ini" eat = 0 updated = 0 if interface is not None: with open(infile, 'r+') as f, util.tempdir() as tmpd: tmpf = "%s/dhcpcd.ini" % tmpd for line in f.readlines(): if create is False: util.append_file(tmpf, line) else: if eat == 0 and not line.startswith("interface "): util.append_file(tmpf, line) elif eat == 0 and line.startswith("interface "): eat = 1 elif eat == 1 and re.match("{", line.strip()): eat = 2 elif eat == 2: update_dhcp(tmpf, interface, info) updated = 1 eat = 3 if create is False: update_dhcp(tmpf, interface, info) else: if updated == 0: update_dhcp(tmpf, interface, info) util.copy(tmpf, infile)
def apply_locale(self, locale, out_fn=None): # Adjust the locals value to the new value newconf = StringIO() for line in util.load_file(self.login_conf_fn).splitlines(): newconf.write(re.sub(r'^default:', r'default:lang=%s:' % locale, line)) newconf.write("\n") # Make a backup of login.conf. util.copy(self.login_conf_fn, self.login_conf_fn_bak) # And write the new login.conf. util.write_file(self.login_conf_fn, newconf.getvalue()) try: LOG.debug("Running cap_mkdb for %s", locale) util.subp(['cap_mkdb', self.login_conf_fn]) except util.ProcessExecutionError: # cap_mkdb failed, so restore the backup. util.logexc(LOG, "Failed to apply locale %s", locale) try: util.copy(self.login_conf_fn_bak, self.login_conf_fn) except IOError: util.logexc(LOG, "Failed to restore %s backup", self.login_conf_fn)
def configure( config, server_cfg=SERVER_CFG, pubcert_file=PUBCERT_FILE, pricert_file=PRICERT_FILE, ): # Read server.cfg (if it exists) values from the # original file in order to be able to mix the rest up. try: old_contents = util.load_file(server_cfg, quiet=False, decode=False) mcollective_config = ConfigObj(io.BytesIO(old_contents)) except IOError as e: if e.errno != errno.ENOENT: raise else: LOG.debug( "Did not find file %s (starting with an empty config)", server_cfg, ) mcollective_config = ConfigObj() for (cfg_name, cfg) in config.items(): if cfg_name == "public-cert": util.write_file(pubcert_file, cfg, mode=0o644) mcollective_config["plugin.ssl_server_public"] = pubcert_file mcollective_config["securityprovider"] = "ssl" elif cfg_name == "private-cert": util.write_file(pricert_file, cfg, mode=0o600) mcollective_config["plugin.ssl_server_private"] = pricert_file mcollective_config["securityprovider"] = "ssl" else: if isinstance(cfg, str): # Just set it in the 'main' section mcollective_config[cfg_name] = cfg elif isinstance(cfg, (dict)): # Iterate through the config items, create a section if # it is needed and then add/or create items as needed if cfg_name not in mcollective_config.sections: mcollective_config[cfg_name] = {} for (o, v) in cfg.items(): mcollective_config[cfg_name][o] = v else: # Otherwise just try to convert it to a string mcollective_config[cfg_name] = str(cfg) try: # We got all our config as wanted we'll copy # the previous server.cfg and overwrite the old with our new one util.copy(server_cfg, "%s.old" % (server_cfg)) except IOError as e: if e.errno == errno.ENOENT: # Doesn't exist to copy... pass else: raise # Now we got the whole (new) file, write to disk... contents = io.BytesIO() mcollective_config.write(contents) util.write_file(server_cfg, contents.getvalue(), mode=0o644)
def set_timezone(self, tz): tz_file = self._find_tz_file(tz) # Adjust the sysconfig clock zone setting clock_cfg = { 'TIMEZONE': str(tz), } rhel_util.update_sysconfig_file(self.clock_conf_fn, clock_cfg) # This ensures that the correct tz will be used for the system util.copy(tz_file, self.tz_local_fn)
def configure(config, server_cfg=SERVER_CFG, pubcert_file=PUBCERT_FILE, pricert_file=PRICERT_FILE): # Read server.cfg (if it exists) values from the # original file in order to be able to mix the rest up. try: old_contents = util.load_file(server_cfg, quiet=False, decode=False) mcollective_config = ConfigObj(BytesIO(old_contents)) except IOError as e: if e.errno != errno.ENOENT: raise else: LOG.debug("Did not find file %s (starting with an empty" " config)", server_cfg) mcollective_config = ConfigObj() for (cfg_name, cfg) in config.items(): if cfg_name == 'public-cert': util.write_file(pubcert_file, cfg, mode=0o644) mcollective_config[ 'plugin.ssl_server_public'] = pubcert_file mcollective_config['securityprovider'] = 'ssl' elif cfg_name == 'private-cert': util.write_file(pricert_file, cfg, mode=0o600) mcollective_config[ 'plugin.ssl_server_private'] = pricert_file mcollective_config['securityprovider'] = 'ssl' else: if isinstance(cfg, six.string_types): # Just set it in the 'main' section mcollective_config[cfg_name] = cfg elif isinstance(cfg, (dict)): # Iterate through the config items, create a section if # it is needed and then add/or create items as needed if cfg_name not in mcollective_config.sections: mcollective_config[cfg_name] = {} for (o, v) in cfg.items(): mcollective_config[cfg_name][o] = v else: # Otherwise just try to convert it to a string mcollective_config[cfg_name] = str(cfg) try: # We got all our config as wanted we'll copy # the previous server.cfg and overwrite the old with our new one util.copy(server_cfg, "%s.old" % (server_cfg)) except IOError as e: if e.errno == errno.ENOENT: # Doesn't exist to copy... pass else: raise # Now we got the whole (new) file, write to disk... contents = BytesIO() mcollective_config.write(contents) util.write_file(server_cfg, contents.getvalue(), mode=0o644)
def set_timezone(self, tz): tz_file = self._find_tz_file(tz) # Note: "" provides trailing newline during join tz_lines = [ util.make_header(), str(tz), "", ] util.write_file(self.tz_conf_fn, "\n".join(tz_lines)) # This ensures that the correct tz will be used for the system util.copy(tz_file, self.tz_local_fn)
def set_etc_timezone(tz, tz_file=None, tz_conf="/etc/timezone", tz_local="/etc/localtime"): util.write_file(tz_conf, str(tz).rstrip() + "\n") # This ensures that the correct tz will be used for the system if tz_local and tz_file: # use a symlink if there exists a symlink or tz_local is not present if os.path.islink(tz_local) or not os.path.exists(tz_local): os.symlink(tz_file, tz_local) else: util.copy(tz_file, tz_local) return
def dhcp_discovery(dhclient_cmd_path, interface, cleandir): """Run dhclient on the interface without scripts or filesystem artifacts. @param dhclient_cmd_path: Full path to the dhclient used. @param interface: Name of the network inteface on which to dhclient. @param cleandir: The directory from which to run dhclient as well as store dhcp leases. @return: A list of dicts of representing the dhcp leases parsed from the dhcp.leases file or empty list. """ LOG.debug('Performing a dhcp discovery on %s', interface) # XXX We copy dhclient out of /sbin/dhclient to avoid dealing with strict # app armor profiles which disallow running dhclient -sf <our-script-file>. # We want to avoid running /sbin/dhclient-script because of side-effects in # /etc/resolv.conf any any other vendor specific scripts in # /etc/dhcp/dhclient*hooks.d. sandbox_dhclient_cmd = os.path.join(cleandir, 'dhclient') util.copy(dhclient_cmd_path, sandbox_dhclient_cmd) pid_file = os.path.join(cleandir, 'dhclient.pid') lease_file = os.path.join(cleandir, 'dhcp.leases') # ISC dhclient needs the interface up to send initial discovery packets. # Generally dhclient relies on dhclient-script PREINIT action to bring the # link up before attempting discovery. Since we are using -sf /bin/true, # we need to do that "link up" ourselves first. util.subp(['ip', 'link', 'set', 'dev', interface, 'up'], capture=True) cmd = [ sandbox_dhclient_cmd, '-1', '-v', '-lf', lease_file, '-pf', pid_file, interface, '-sf', '/bin/true' ] util.subp(cmd, capture=True) # dhclient doesn't write a pid file until after it forks when it gets a # proper lease response. Since cleandir is a temp directory that gets # removed, we need to wait for that pidfile creation before the # cleandir is removed, otherwise we get FileNotFound errors. missing = util.wait_for_files([pid_file, lease_file], maxwait=5, naplen=0.01) if missing: LOG.warning("dhclient did not produce expected files: %s", ', '.join(os.path.basename(f) for f in missing)) return [] pid_content = util.load_file(pid_file).strip() try: pid = int(pid_content) except ValueError: LOG.debug("pid file contains non-integer content '%s'", pid_content) else: os.kill(pid, signal.SIGKILL) return parse_dhcp_lease_file(lease_file)
def set_etc_timezone(tz, tz_file=None, tz_conf="/etc/timezone", tz_local="/etc/localtime"): util.write_file(tz_conf, str(tz).rstrip() + "\n") # This ensures that the correct tz will be used for the system if tz_local and tz_file: # use a symlink if there exists a symlink or tz_local is not present islink = os.path.islink(tz_local) if islink or not os.path.exists(tz_local): if islink: util.del_file(tz_local) os.symlink(tz_file, tz_local) else: util.copy(tz_file, tz_local) return
def set_timezone(self, tz): tz_file = self._find_tz_file(tz) if self._dist_uses_systemd(): # Currently, timedatectl complains if invoked during startup # so for compatibility, create the link manually. util.del_file(self.tz_local_fn) util.sym_link(tz_file, self.tz_local_fn) else: # Adjust the sysconfig clock zone setting clock_cfg = { 'ZONE': str(tz), } rhel_util.update_sysconfig_file(self.clock_conf_fn, clock_cfg) # This ensures that the correct tz will be used for the system util.copy(tz_file, self.tz_local_fn)
def set_timezone(self, tz): tz_file = self._find_tz_file(tz) if self.uses_systemd(): # Currently, timedatectl complains if invoked during startup # so for compatibility, create the link manually. util.del_file(self.tz_local_fn) util.sym_link(tz_file, self.tz_local_fn) else: # Adjust the sysconfig clock zone setting clock_cfg = { 'ZONE': str(tz), } rhel_util.update_sysconfig_file(self.clock_conf_fn, clock_cfg) # This ensures that the correct tz will be used for the system util.copy(tz_file, self.tz_local_fn)
def execute(self): """ This method copy the post customize run script to cc_scripts_per_instance directory and let this module to run post custom script. """ self.prepare_script() LOG.debug("Copying post customize run script to %s", self.ccScriptPath) util.copy( os.path.join(self.directory, CustomScriptConstant.POST_CUSTOM_SCRIPT_NAME), self.ccScriptPath) st = os.stat(self.ccScriptPath) os.chmod(self.ccScriptPath, st.st_mode | stat.S_IEXEC) LOG.info("Creating post customization pending marker") util.ensure_file(CustomScriptConstant.POST_CUSTOM_PENDING_MARKER)
def prepare_script(self): if not os.path.exists(self.scriptpath): raise CustomScriptNotFound( "Script %s not found!! Cannot execute custom script!" % self.scriptpath) util.ensure_dir(CustomScriptConstant.CUSTOM_TMP_DIR) LOG.debug("Copying custom script to %s", CustomScriptConstant.CUSTOM_SCRIPT) util.copy(self.scriptpath, CustomScriptConstant.CUSTOM_SCRIPT) # Strip any CR characters from the decoded script content = util.load_file(CustomScriptConstant.CUSTOM_SCRIPT).replace( "\r", "") util.write_file(CustomScriptConstant.CUSTOM_SCRIPT, content, mode=0o544)
def make_dhcp4_request(interface, timeout=120): """Makes DHCPv4 queries out a linux link device""" dhcp_lease_dir_exists() tmp_conf_file = DHCP_LEASE_DIR + '/dhclient.conf' lease_file = DHCP_LEASE_DIR + '/' + interface + '.lease' tmp_lease_file = '/tmp/' + interface + '.lease' fnull = open(os.devnull, 'w') dhclient_cf = open(tmp_conf_file, 'w') dhclient_cf.write( "\nrequest subnet-mask, broadcast-address, time-offset, routers,\n") dhclient_cf.write( " domain-name, domain-name-servers, domain-search, host-name,\n") dhclient_cf.write( " root-path, interface-mtu, classless-static-routes;\n") dhclient_cf.close() if os.path.isfile(lease_file): util.del_file(lease_file) subprocess.call(['/bin/pkill', 'dhclient'], stdout=fnull) subprocess.call(['/sbin/ip', 'link', 'set', interface, 'up'], stdout=fnull) subprocess.call(['/sbin/dhclient', '-lf', tmp_lease_file, '-cf', tmp_conf_file, '-1', '-timeout', str(timeout), '-pf', '/tmp/dhclient.' + interface + '.pid', '-sf', '/bin/echo', interface], stdout=fnull) if os.path.getsize(tmp_lease_file) > 0: util.copy(tmp_lease_file, lease_file) subprocess.call(['/bin/pkill', 'dhclient'], stdout=fnull) util.del_file('/tmp/dhclient.' + interface + '.pid') util.del_file(tmp_lease_file) return True else: subprocess.call(['/bin/pkill', 'dhclient'], stdout=fnull) util.del_file('/tmp/dhclient.' + interface + '.pid') util.del_file(tmp_lease_file) return False
def _collect_file(path, out_dir, verbosity): if os.path.isfile(path): copy(path, out_dir) _debug("collected file: %s\n" % path, 1, verbosity) else: _debug("file %s did not exist\n" % path, 2, verbosity)
def dhcp_discovery(dhclient_cmd_path, interface, cleandir): """Run dhclient on the interface without scripts or filesystem artifacts. @param dhclient_cmd_path: Full path to the dhclient used. @param interface: Name of the network inteface on which to dhclient. @param cleandir: The directory from which to run dhclient as well as store dhcp leases. @return: A list of dicts of representing the dhcp leases parsed from the dhcp.leases file or empty list. """ LOG.debug('Performing a dhcp discovery on %s', interface) # XXX We copy dhclient out of /sbin/dhclient to avoid dealing with strict # app armor profiles which disallow running dhclient -sf <our-script-file>. # We want to avoid running /sbin/dhclient-script because of side-effects in # /etc/resolv.conf any any other vendor specific scripts in # /etc/dhcp/dhclient*hooks.d. sandbox_dhclient_cmd = os.path.join(cleandir, 'dhclient') util.copy(dhclient_cmd_path, sandbox_dhclient_cmd) pid_file = os.path.join(cleandir, 'dhclient.pid') lease_file = os.path.join(cleandir, 'dhcp.leases') # ISC dhclient needs the interface up to send initial discovery packets. # Generally dhclient relies on dhclient-script PREINIT action to bring the # link up before attempting discovery. Since we are using -sf /bin/true, # we need to do that "link up" ourselves first. util.subp(['ip', 'link', 'set', 'dev', interface, 'up'], capture=True) cmd = [sandbox_dhclient_cmd, '-1', '-v', '-lf', lease_file, '-pf', pid_file, interface, '-sf', '/bin/true'] util.subp(cmd, capture=True) # Wait for pid file and lease file to appear, and for the process # named by the pid file to daemonize (have pid 1 as its parent). If we # try to read the lease file before daemonization happens, we might try # to read it before the dhclient has actually written it. We also have # to wait until the dhclient has become a daemon so we can be sure to # kill the correct process, thus freeing cleandir to be deleted back # up the callstack. missing = util.wait_for_files( [pid_file, lease_file], maxwait=5, naplen=0.01) if missing: LOG.warning("dhclient did not produce expected files: %s", ', '.join(os.path.basename(f) for f in missing)) return [] ppid = 'unknown' for _ in range(0, 1000): pid_content = util.load_file(pid_file).strip() try: pid = int(pid_content) except ValueError: pass else: ppid = util.get_proc_ppid(pid) if ppid == 1: LOG.debug('killing dhclient with pid=%s', pid) os.kill(pid, signal.SIGKILL) return parse_dhcp_lease_file(lease_file) time.sleep(0.01) LOG.error( 'dhclient(pid=%s, parentpid=%s) failed to daemonize after %s seconds', pid_content, ppid, 0.01 * 1000 ) return parse_dhcp_lease_file(lease_file)
def dhcp_discovery(dhclient_cmd_path, interface, cleandir, dhcp_log_func=None): """Run dhclient on the interface without scripts or filesystem artifacts. @param dhclient_cmd_path: Full path to the dhclient used. @param interface: Name of the network inteface on which to dhclient. @param cleandir: The directory from which to run dhclient as well as store dhcp leases. @param dhcp_log_func: A callable accepting the dhclient output and error streams. @return: A list of dicts of representing the dhcp leases parsed from the dhcp.leases file or empty list. """ LOG.debug("Performing a dhcp discovery on %s", interface) # XXX We copy dhclient out of /sbin/dhclient to avoid dealing with strict # app armor profiles which disallow running dhclient -sf <our-script-file>. # We want to avoid running /sbin/dhclient-script because of side-effects in # /etc/resolv.conf any any other vendor specific scripts in # /etc/dhcp/dhclient*hooks.d. sandbox_dhclient_cmd = os.path.join(cleandir, "dhclient") util.copy(dhclient_cmd_path, sandbox_dhclient_cmd) pid_file = os.path.join(cleandir, "dhclient.pid") lease_file = os.path.join(cleandir, "dhcp.leases") # In some cases files in /var/tmp may not be executable, launching dhclient # from there will certainly raise 'Permission denied' error. Try launching # the original dhclient instead. if not os.access(sandbox_dhclient_cmd, os.X_OK): sandbox_dhclient_cmd = dhclient_cmd_path # ISC dhclient needs the interface up to send initial discovery packets. # Generally dhclient relies on dhclient-script PREINIT action to bring the # link up before attempting discovery. Since we are using -sf /bin/true, # we need to do that "link up" ourselves first. subp.subp(["ip", "link", "set", "dev", interface, "up"], capture=True) cmd = [ sandbox_dhclient_cmd, "-1", "-v", "-lf", lease_file, "-pf", pid_file, interface, "-sf", "/bin/true", ] out, err = subp.subp(cmd, capture=True) # Wait for pid file and lease file to appear, and for the process # named by the pid file to daemonize (have pid 1 as its parent). If we # try to read the lease file before daemonization happens, we might try # to read it before the dhclient has actually written it. We also have # to wait until the dhclient has become a daemon so we can be sure to # kill the correct process, thus freeing cleandir to be deleted back # up the callstack. missing = util.wait_for_files([pid_file, lease_file], maxwait=5, naplen=0.01) if missing: LOG.warning( "dhclient did not produce expected files: %s", ", ".join(os.path.basename(f) for f in missing), ) return [] ppid = "unknown" daemonized = False for _ in range(0, 1000): pid_content = util.load_file(pid_file).strip() try: pid = int(pid_content) except ValueError: pass else: ppid = util.get_proc_ppid(pid) if ppid == 1: LOG.debug("killing dhclient with pid=%s", pid) os.kill(pid, signal.SIGKILL) daemonized = True break time.sleep(0.01) if not daemonized: LOG.error( "dhclient(pid=%s, parentpid=%s) failed to daemonize after %s " "seconds", pid_content, ppid, 0.01 * 1000, ) if dhcp_log_func is not None: dhcp_log_func(out, err) return parse_dhcp_lease_file(lease_file)