def test_logging_no_hang(self): '''Try to ensure Popen.check_call doesn't hang when trying to do logging''' # To ensure the logger keyword arg is implemented in a way that # doesn't cause hangs, and since the use of logger causes blocking # behavior, spawn a non-blocking subprocess that spawns a blocking # subprocess. If the non-blocking subprocess doesn't complete # in a reasonable amount of time, kill both and fail cmd = [ sys.executable, "-c", "from solaris_install import Popen; import logging; " "Popen.check_call(['/usr/bin/pkg', 'foo'], " "logger=logging.getLogger())" ] popen = Popen(cmd, stdout=Popen.DEVNULL, stderr=Popen.DEVNULL) for wait_count in xrange(15): # If it's not done nearly instantly, something is wrong. # However, give the benefit of the doubt by waiting up to # 5 seconds for completion if popen.poll() is not None: break else: time.sleep(0.5) else: popen.kill() self.fail("subprocess hung while attempting logging")
def do_enable_service(cmd_options=None): ''' Enable a service Parse the supplied arguments then enable the specified service. Input: List of command line options Return: None Raises: SystemExit if missing permissions, invalid service name, or if attempt to enable the service or the smf service fails. ''' logging.log(XDEBUG, '**** START do_enable_service ****') # check for authorization and euid try: check_auth_and_euid(SERVICE_AUTH) except UnauthorizedUserError as err: raise SystemExit(err) usage = '\n' + get_enable_usage() parser = OptionParser(usage=usage) args = parser.parse_args(cmd_options)[1] # Check for correct number of args if len(args) != 1: if len(args) == 0: parser.error(_("Missing required argument, <svcname>")) else: parser.error(_("Too many arguments: %s") % args) svcname = args[0] if not config.is_service(svcname): err_msg = _("The service does not exist: %s\n") % svcname parser.error(err_msg) # Verify that the server settings are not obviously broken. # These checks cannot be complete, but do check for things # which will definitely cause failure. ret = Popen([CHECK_SETUP_SCRIPT]).wait() if ret: return 1 logging.log(XDEBUG, 'Enabling install service %s', svcname) try: service = AIService(svcname) service.enable() except (aismf.ServicesError, config.ServiceCfgError, ImageError, MountError) as err: raise SystemExit(err) except InvalidServiceError as err: raise SystemExit( cw( _("\nThis service may not be enabled until all " "invalid manifests and profiles have been " "corrected or removed.\n")))
def _show_url_cb(self): '''timer callback to show the URL outside the main thread. ''' if self.show_url: Popen([FIREFOX, self.show_url]) self.show_url = None return False
def is_multihomed(): ''' Determines if system is multihomed Returns True if multihomed, False if not ''' global _IS_MULTIHOMED if _IS_MULTIHOMED is None: logging.debug("is_multihomed(): Calling %s", MULTIHOMED_TEST) _IS_MULTIHOMED = Popen(MULTIHOMED_TEST, shell=True).wait() return (_IS_MULTIHOMED != 0)
def call_cmd(self, cmdlist, error_string): ''' Call a command. ''' subproc = Popen(cmdlist, stderr=Popen.STDOUT, stdout=Popen.PIPE) if subproc.returncode: self.logger.critical(MSG_HEADER + error_string) return "" else: return subproc.stdout.readline().strip()
def _fork_proc(cmd, timeout=15): '''Run a command in a forked process, with a timeout. Kills the process on timeout. Logs errors. Handles processes with large amounts of data so check_call() is not used. Args: - cmd: a list of commandline arguments - timeout: timeout in seconds Returns: - Return status: - return status of the command run, if command completes. - SupportInfo.PH_TIMEOUT if timeout occurs. - stdout and stderr of the run process. ''' timeout_tenths = timeout * 10 def read_subproc(subproc, outbuf, errbuf): intoutbuf = interrbuf = "" try: intoutbuf = subproc.stdout.read() outbuf = "".join([outbuf, intoutbuf]) except IOError as err: if err.errno != errno.EAGAIN: raise try: interrbuf = subproc.stderr.read() errbuf = "".join([errbuf, interrbuf]) except IOError as err: if err.errno != errno.EAGAIN: raise return (outbuf, errbuf) # Can throw an child_exception if command cannot be started. subproc = Popen(cmd, stdout=Popen.PIPE, stderr=Popen.PIPE) flags = fcntl.fcntl(subproc.stdout.fileno(), fcntl.F_GETFL) fcntl.fcntl(subproc.stdout.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK) flags = fcntl.fcntl(subproc.stderr.fileno(), fcntl.F_GETFL) fcntl.fcntl(subproc.stderr.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK) (outbuf, errbuf) = read_subproc(subproc, "", "") while subproc.poll() is None and timeout_tenths > 0: timeout_tenths -= 1 time.sleep(0.1) (outbuf, errbuf) = read_subproc(subproc, outbuf, errbuf) if subproc.returncode is None: subproc.terminate() return (SupportInfo.PH_TIMEOUT, outbuf, errbuf) else: (outbuf, errbuf) = read_subproc(subproc, outbuf, errbuf) return (subproc.returncode, outbuf, errbuf)
def do_create_service(cmd_options=None): ''' Create either a base service or an alias ''' # check that we are root if os.geteuid() != 0: raise SystemExit( _("Error: Root privileges are required for this " "command.\n")) logging.log(com.XDEBUG, '**** START do_create_service ****') options = parse_options(cmd_options) logging.debug('options: %s', options) # Check the network configuration. Verify that the server settings # are not obviously broken (i.e., check for things which will definitely # cause failure). logging.debug('Check if the host server can support AI Install services.') cmd = [ com.CHECK_SETUP_SCRIPT, options.dhcp_ip_start if options.dhcp_ip_start else '' ] logging.debug('Calling %s', cmd) # CHECK_SETUP_SCRIPT does math processing that needs to run in "C" locale # to avoid problems with alternative # radix point representations # (e.g. ',' instead of '.' in cs_CZ.*-locales). # Because ksh script uses built-in math we need to set locale here and we # can't set it in script itself modified_env = os.environ.copy() lc_all = modified_env.get('LC_ALL', '') if lc_all != '': modified_env['LC_MONETARY'] = lc_all modified_env['LC_MESSAGES'] = lc_all modified_env['LC_COLLATE'] = lc_all modified_env['LC_TIME'] = lc_all modified_env['LC_CTYPE'] = lc_all del modified_env['LC_ALL'] modified_env['LC_NUMERIC'] = 'C' if Popen(cmd, env=modified_env).wait(): raise SystemExit(1) # convert options.bootargs to a string if options.bootargs: options.bootargs = ",".join(options.bootargs) + "," else: options.bootargs = '' if options.aliasof: return (do_alias_service(options)) else: return (do_create_baseservice(options))
def create(self, dry_run): """ create - method to create, newfs and mount a lofi device """ # create the ramdisk (if needed) self.create_ramdisk(dry_run) # create the lofi device cmd = [LOFIADM, "-a", self.ramdisk] if not dry_run: p = Popen.check_call(cmd, stdout=Popen.STORE, stderr=Popen.STORE, logger=ILN) self.lofi_device = p.stdout.strip() # newfs it cmd = [NEWFS, "-m", "0", "-o", "space"] if self.nbpi is not None: cmd.append("-i") cmd.append(str(self.nbpi)) cmd.append(self.lofi_device.replace("lofi", "rlofi")) if not dry_run: # due to the way Popen works, we can not assign a logger to the # call, otherwise the process will complete before we can pass the # "y" to newfs logger = logging.getLogger(ILN) logger.debug("Executing: %s" % " ".join(cmd)) p = Popen(cmd, stdin=Popen.PIPE, stdout=Popen.DEVNULL, stderr=Popen.DEVNULL) p.communicate("y\n") # ensure a directory exists to mount the lofi device to if not os.path.exists(self.mountpoint) and not dry_run: os.makedirs(self.mountpoint) cmd = [ MOUNT, "-F", "ufs", "-o", "rw", self.lofi_device, self.mountpoint ] if not dry_run: Popen.check_call(cmd, stdout=Popen.STORE, stderr=Popen.STORE, logger=ILN) self.mounted = True
def test_devnull(self): '''Test using Popen.DEVNULL for stdin''' popen = Popen(["/usr/bin/cat"], stdin=Popen.DEVNULL, stdout=Popen.PIPE) # Use PIPE for stdout as, for a failure case, the subprocess call # could hang indefinitely, so we can't block on it for wait_count in xrange(10): # If it's not done nearly instantly, something is wrong. # However, give the benefit of the doubt by waiting up to # 5 seconds for completion if popen.poll() is not None: break else: time.sleep(0.5) else: popen.kill() self.fail("stdin=Popen.DEVNULL did not work") stdout = popen.communicate()[0] self.assertEqual("", stdout)
# Generate incorporation pkg incorp_pkg_name = "pkg://%s/%s@%s" % \ (args.publisher, INCORP_NAME, incorp_version) dep_pkg_name = "%s@%s" % (AI_PKG_NAME, ai_pkg_version) # Generate the manifest manifest = ('set name=pkg.fmri value=%(incorppkg)s\n' 'depend type=incorporate fmri=%(depname)s' % {'incorppkg': incorp_pkg_name, 'depname': dep_pkg_name}) print "\nManifest contents:\n%s" % manifest print "\nPublishing %s" % incorp_pkg_name cmd = [PKGSEND, "-s", args.repo, "publish"] pkgsend = Popen(cmd, stdin=Popen.PIPE, stdout=Popen.PIPE, stderr=Popen.PIPE) stdout, stderr = pkgsend.communicate(manifest) if stderr.strip() or pkgsend.returncode: pkgsend.stdout = stdout pkgsend.stderr = stderr raise CalledProcessError(pkgsend.returncode, cmd, popen=pkgsend) else: print stdout.strip() # Refresh the repository cmd = [PKGREPO, "-s", args.repo, "refresh"] run(cmd) print "Finished at " + time.asctime()
def _transfer(self): '''Method to transfer from the source to the destination''' if self.give_progress: # Start up the ProgressMon to report progress # while the actual transfer is taking place. self.pmon = ProgressMon(logger=self.logger) # Note: startmonitor assumes there is another thread creating a # file system. If this is not the case (as may be when testing # this module in abnormal conditions), startmonitor will hang. # Just create the self.dst as a directory in this case. self.pmon.startmonitor(self.dst, self.total_size, 0, 100) # Perform the transfer specific operations. try: for trans_val in self._transfer_list: # Get the arguments for the transfer process arglist = trans_val.get(SVR4_ARGS).split(' ') # Parse the components to determine the transfer action if trans_val.get(ACTION) == 'install': self.check_cancel_event() self.logger.info("Installing SVR4 packages") cmd = [AbstractSVR4.PKGADD] + arglist + \ trans_val.get(CONTENTS) elif trans_val.get(ACTION) == 'uninstall': self.check_cancel_event() self.logger.info("Uninstalling SVR4 packages") cmd = [AbstractSVR4.PKGRM] + arglist + \ trans_val.get(CONTENTS) else: self.logger.warning("Transfer action, %s, is not valid" % trans_val.get(ACTION)) self.check_cancel_event() continue if self.dry_run: self.logger.debug("Would execute the following transfer " "command: %s" % cmd) else: self.logger.debug("Executing the following transfer " "command: %s" % cmd) self.check_cancel_event() pkg_proc = Popen(cmd, shell=False, stdout=Popen.PIPE, stderr=Popen.STDOUT) while 1: self.check_cancel_event() pkgoutput = pkg_proc.stdout.readline() if not pkgoutput: retcode = pkg_proc.poll() if retcode != 0: self.svr4_process = None raise OSError( retcode, "SVR4 transfer error while " "adding packages") break pkgoutput = pkgoutput[:-1] if not pkgoutput.strip(): continue self.logger.debug("%s", pkgoutput) self.svr4_process = None finally: if self.pmon: self.pmon.done = True self.pmon.wait() self.pmon = None
def execute(self, dry_run=False): '''Validate script and then run it.''' script_name = os.path.abspath(self.dmd.script) # Verify type of script. Assumes this module is being run with enough # privilege to read the script. linecache.checkcache(script_name) first_line = linecache.getline(script_name, 1) if (first_line == ""): errmsg = (MSG_HEADER + "Error opening scriptfile %s" % script_name) self.logger.critical(errmsg) raise DMMScriptAccessError(errmsg) # Look for appropriate shebang line to denote a supported script. # Note their appearance may have "/usr" prepended to them. first_line = first_line.strip() if not first_line.startswith("#!"): errmsg = ( MSG_HEADER + 'File %s: first line does not start with "#!".' % script_name) self.logger.critical(errmsg) raise DMMScriptInvalidError(errmsg) # Verify accessibility of script. # Owner must be aiuser, or file mode must include o+rx. script_stat = os.stat(script_name) mode = stat.S_IMODE(script_stat.st_mode) self.logger.info(MSG_HEADER + "Script to run: " + script_name) self.logger.info(MSG_HEADER + "script mode is 0%o, uid is %d, gid is %d\n" % (mode, script_stat.st_uid, script_stat.st_gid)) self.logger.info(MSG_HEADER + "Script validated. Running in subprocess...") # Verify that the aiuser can access the script. cmdlist = [ SU, AIUSER_ACCOUNT_NAME, "-c", TEST + " -r " + script_name + " -a -x " + script_name ] try: Popen.check_call(cmdlist) except CalledProcessError: errmsg = MSG_HEADER + \ "Error accessing Derived Manifest script as aiuser" self.logger.critical(errmsg) raise DMMScriptInvalidError(errmsg) cmdlist = [SU, AIUSER_ACCOUNT_NAME, "-c", script_name] subproc = Popen(cmdlist, stderr=Popen.STDOUT, stdout=Popen.PIPE, preexec_fn=self.subproc_env_setup) self.logger.info(MSG_HEADER + "script output follows: ") outerr, dummy = subproc.communicate() while (subproc.returncode is None): outerr = outerr.split("\n") for line in outerr: self.logger.info("> " + line) outerr, dummy = subproc.communicate() outerr = outerr.split("\n") for line in outerr: self.logger.info("> " + line) self.logger.info(MSG_HEADER + "aimanifest logfile output follows: ") try: with open(self.aim_logfile, 'r') as aim_log: for line in aim_log: self.logger.info(">> " + line.strip()) except (OSError, IOError) as err: self.logger.error("Error reading aimanifest logfile: %s:%s" % (err.filename, err.strerror)) try: os.unlink(self.aim_logfile) except OSError as err: self.logger.warning("MSG_HEADER: Warning: Could not delete " "aimanifest logfile %s: %s" % (self.aim_logfile, err.strerror)) if subproc.returncode < 0: # Would be nice to convert number to signal string, but no # facility for this exists in python. errmsg = ( MSG_HEADER + "Script was terminated by signal %d" % -subproc.returncode) elif subproc.returncode > 0: # Note: can't get 128 or 129 (as can be returned by a shell when it # cannot access or run a script) because that has already been # checked for. errmsg = (MSG_HEADER + "Script \"" + self.dmd.script + \ "\" terminated on error.") if subproc.returncode != 0: self.logger.critical(errmsg) raise DMMExecutionError(errmsg) else: self.logger.info(MSG_HEADER + "script completed successfully") # Try to validate against a schema specified in the manifest DOCTYPE # header, if it is there. Else fallback to a hardwired default. try: tree = etree.parse(self.mpd.manifest) except etree.XMLSyntaxError as err: self.logger.critical(MSG_HEADER + "Error parsing final manifest") self.logger.critical(str(err)) errmsg = MSG_HEADER + "Final manifest failed XML validation" self.logger.critical(errmsg) raise DMMValidationError(errmsg) if ((tree.docinfo is not None) and (tree.docinfo.system_url is not None) and os.access(tree.docinfo.system_url, os.R_OK)): dtd = tree.docinfo.system_url self.logger.info(MSG_HEADER + "Using DTD from header of manifest.") else: dtd = SYS_AI_MANIFEST_DTD self.logger.info(MSG_HEADER + "Manifest header refers to no DTD.") self.logger.info(MSG_HEADER + "Validating against DTD: %s" % dtd) try: validate_manifest(tree, dtd, self.logger) except (ManifestError) as err: # Note: validate_manifest already logged the errors. errmsg = MSG_HEADER + "Final manifest failed XML validation" self.logger.critical(errmsg) raise DMMValidationError(errmsg) else: self.logger.info(MSG_HEADER + "XML validation completed successfully ")
def parse_options(cmd_options=None): ''' Parse and validate options ''' def check_MAC_address(option, opt_str, value, parser): ''' Check MAC address as an OptionParser callback Postcondition: sets value to proper option if check passes Raises: OptionValueError if MAC address is malformed ''' try: value = str(com.MACAddress(value)) except com.MACAddress.MACAddressError as err: raise OptionValueError(str(err)) setattr(parser.values, option.dest, value) usage = '\n' + get_usage() parser = OptionParser(usage=usage) # accept multiple -b options (so append to a list) parser.add_option("-b", "--boot-args", dest="boot_args", action="append", type="string", nargs=1, help=_("boot arguments to pass to Solaris kernel")) parser.add_option("-e", "--macaddr", dest="mac_address", action="callback", nargs=1, type="string", help=_("MAC address of client to add"), callback=check_MAC_address) parser.add_option("-n", "--service", dest="service_name", action="store", type="string", help=_("Service to associate client with"), nargs=1) (options, args) = parser.parse_args(cmd_options) if args: parser.error(_("Unexpected argument(s): %s" % args)) # check that we got a service name and mac address if options.service_name is None: parser.error( _("Service name is required " "(-n|--service <service name>).")) if options.mac_address is None: parser.error(_("MAC address is required (-e|--macaddr <macaddr>).")) # Verify that the server settings are not obviously broken. # These checks cannot be complete, but check for things which # will definitely cause failure. logging.debug("Calling %s", com.CHECK_SETUP_SCRIPT) ret = Popen([com.CHECK_SETUP_SCRIPT]).wait() if ret: raise SystemExit(1) # validate service name try: com.validate_service_name(options.service_name) except ValueError as err: raise SystemExit(err) # check that the service exists service_props = config.get_service_props(options.service_name) if not service_props: raise SystemExit( _("The specified service does not exist: %s\n") % options.service_name) # get the image_path from the service try: # set image to be a InstalladmImage object image = svc.AIService(options.service_name).image except KeyError: raise SystemExit( _("\nThe specified service does not have an " "image_path property.\n")) # ensure we are not passed bootargs for a SPARC as we do not # support that if options.boot_args and image.arch == "sparc": parser.error(_("Boot arguments not supported for SPARC clients.\n")) options.arch = image.arch logging.debug("options = %s", options) return options
def releasenotes_cb(self): '''timer callback to show the release notes outside the main thread. ''' Popen([FIREFOX, self.RELEASENOTESURL])
def create_repository(self): """ class method to create the repository """ self.logger.info("Creating repository") # Create the repository (as needed) if it's a local path (no scheme) # or file:/// scheme. scheme = urlparse.urlsplit(self.pkg_repo).scheme if scheme in ("file", ""): # Try to create the repo (it may already exist) cmd = [cli.PKGREPO, "create", self.pkg_repo] repo = run(cmd, check_result=Popen.ANY) if repo.returncode == 0: # New repo was created. Add the publisher and make it default cmd = [ cli.PKGREPO, "-s", self.pkg_repo, "add-publisher", self.publisher ] run(cmd) cmd = [ cli.PKGREPO, "-s", self.pkg_repo, "set", "publisher/prefix=%s" % self.publisher ] run(cmd) # Generate a manifest file cmd = [cli.PKGSEND, "generate", self.pkg_img_path] generate = run(cmd) manifest = [generate.stdout] manifest.append('license lic_OTN license=lic_OTN must-accept=true\n') manifest.append('set name=pkg.summary ' 'value="Automated Installation boot image"\n') manifest.append("set name=org.opensolaris.consolidation " "value=install\n") manifest.append('set name=info.classification ' 'value="org.opensolaris.category.2008:' 'System/Administration and Configuration"\n') arch = platform.processor() manifest.append("set name=variant.arch value=%s\n" % arch) manifest.append("set name=%s value=%s variant.arch=%s\n" % (self.SVC_NAME_ATTR, self.service_name, arch)) manifest.append("set name=pkg.fmri value=%s\n" % self.pkg_name) manifest.append("depend fmri=pkg:/system/core-os type=exclude\n") manifest = "".join(manifest) self.logger.info("Publishing %s", self.pkg_name) cmd = [ cli.PKGSEND, "-s", self.pkg_repo, "publish", "-d", self.pkg_img_path, "-d", self.tmp_dir ] pkgsend = Popen(cmd, stdin=Popen.PIPE, stdout=Popen.PIPE, stderr=Popen.PIPE) stdout, stderr = pkgsend.communicate(manifest) if stderr.strip() or pkgsend.returncode: pkgsend.stdout = stdout pkgsend.stderr = stderr raise CalledProcessError(pkgsend.returncode, cmd, popen=pkgsend) else: self.logger.info(stdout.strip())