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 should_be_default_for_arch(newservice): ''' Determine if newservice should be the baseservice of default-<arch> (i.e., first service of architecture and aliasable) Input: service object for newly created service Returns: True if default-<arch> alias should be created False otherwise ''' if newservice.image.version < 3: return False services = config.get_all_service_names() make_default = True for service in services: if service == newservice.name: continue svc = AIService(service) if svc.arch == newservice.arch and svc.image.version >= 3: make_default = False break logging.debug("should_be_default_for_arch service %s, arch=%s, returns %s", newservice.name, newservice.arch, make_default) return make_default
def remove_client(client_id, suppress_dhcp_msgs=False): ''' Remove client configuration If client configuration incomplete (e.g., dangling symlink), cleanup anyway. Optionally suppress dhcp informational messages. ''' logging.debug("Removing client config for %s, suppress_dhcp_msgs=%s", client_id, suppress_dhcp_msgs) (service, datadict) = config.find_client(client_id) if datadict: more_files = datadict.get(config.FILES, list()) else: more_files = list() if service: # remove client info from .config file config.remove_client_from_config(service, client_id) if AIService(service).arch == 'i386': # suggest dhcp unconfiguration remove_client_dhcp_config(client_id, suppress_dhcp_msgs) # remove client specific symlinks/files logging.debug("Cleaning up files %s", more_files) _cleanup_files(client_id, more_files)
def __init__(self, sname): ''' Opens database for given service and sets database request queue ''' self.name = sname try: self.service = AIService(sname) except VersionError as err: warn_version(err) raise path = self.service.database_path if os.path.exists(path): try: maisql = AIdb.DB(path) maisql.verifyDBStructure() self.aiqueue = maisql.getQueue() except StandardError as err: sys.stderr.write( _('Error: AI database access error\n%s\n') % err) raise else: sys.stderr.write( _('Error: unable to locate AI database for "%s" ' 'on server\n') % sname) # I can't read from service database and I should raise an error # for this condition. raise StandardError
def do_publish_manifest(cmd_options=None): ''' Publish a manifest, associating it with an install service. ''' # check that we are root if os.geteuid() != 0: raise SystemExit(_("Error:\tRoot privileges are required for " "this command.")) # load in all the options and file data. Validate proper manifests. data = parse_options(DO_CREATE, cmd_options) service = AIService(data.service_name) # Disallow multiple manifests or scripts with the same mname. manifest_path = os.path.join(service.manifest_dir, data.manifest_name) if os.path.exists(manifest_path): raise SystemExit(_("Error:\tName %s is already registered with " "this service.") % data.manifest_name) # if criteria are provided, make sure they are a unique set. if data.criteria: find_colliding_manifests(data.criteria, data.database, find_colliding_criteria(data.criteria, data.database)) # Add all manifests to the database, whether default or not, and whether # they have criteria or not. df.insert_SQL(data) # move the manifest into place df.place_manifest(data, manifest_path) # if we have a default manifest do default manifest handling if data.set_as_default: service.set_default_manifest(data.manifest_name)
def do_delete_profile(cmd_options=None): ''' external entry point for installadm Arg: cmd_options - command line options Effect: delete profiles per command line ''' # check for authorization and euid try: check_auth_and_euid(PROFILE_AUTH) except UnauthorizedUserError as err: raise SystemExit(err) options = parse_options(cmd_options) # get AI service directory, database name service = AIService(options.service_name) dbname = service.database_path # Open the database aisql = AIdb.DB(dbname, commit=True) aisql.verifyDBStructure() # delete profiles per command line errs = delete_profiles(options.profile_name, aisql, AIdb.PROFILES_TABLE) if errs: sys.exit(1)
def do_update_manifest(cmd_options=None): ''' Update the contents of an existing manifest. ''' # check that we are root if os.geteuid() != 0: raise SystemExit( _("Error:\tRoot privileges are required for " "this command.")) # load in all the options and file data. Validate proper manifests. data = parse_options(DO_UPDATE, cmd_options) service = AIService(data.service_name) manifest_path = os.path.join(service.manifest_dir, data.manifest_name) if not os.path.exists(manifest_path): raise SystemExit( _("Error:\tNo manifest or script with name " "%s is registered with this service.\n" "\tPlease use installadm " "create-manifest instead.") % data.manifest_name) # move the manifest into place df.place_manifest(data, manifest_path)
def should_be_default_for_arch(newservice): ''' Determine if newservice should be the baseservice of default-<arch> (i.e., first service of architecture and aliasable) Input: service object for newly created service Returns: True if default-<arch> alias should be created False otherwise ''' if newservice.image.version < 3: return False services = config.get_all_service_names() make_default = True for service in services: if service == newservice.name: continue svc = AIService(service) try: props = config.get_service_props(service) config.verify_key_properties(service, props) except config.ServiceCfgError as err: # if service is missing keys, print info and skip it print >> sys.stderr, err continue if svc.arch == newservice.arch and svc.image.version >= 3: make_default = False break logging.debug("should_be_default_for_arch service %s, arch=%s, returns %s", newservice.name, newservice.arch, make_default) return make_default
def do_update_manifest(cmd_options=None): ''' Update the contents of an existing manifest. ''' # check for authorization and euid try: check_auth_and_euid(MANIFEST_AUTH) except UnauthorizedUserError as err: raise SystemExit(err) # load in all the options and file data. Validate proper manifests. data = parse_options(DO_UPDATE, cmd_options) service = AIService(data.service_name) manifest_path = os.path.join(service.manifest_dir, data.manifest_name) if not os.path.exists(manifest_path): raise SystemExit(_("Error:\tNo manifest or script with name " "%s is registered with this service.\n" "\tPlease use installadm " "create-manifest instead.") % data.manifest_name) # move the manifest into place df.place_manifest(data, manifest_path)
def parse_options(cmd_options=None): """ Parse and validate options """ usage = '\n' + get_usage() parser = OptionParser(usage=usage) parser.add_option("-m", "--manifest", dest="manifest_name", default=None, help=_("Name of manifest")) parser.add_option("-n", "--service", dest="service_name", default=None, help=_("Name of install service.")) parser.add_option("-i", "--instance", dest="instance", default=None, help=_("manifest instance to remove (internal option)"), type="int", metavar="manifest instance") (options, args) = parser.parse_args(cmd_options) # check for required options if options.service_name is None: parser.error(_("Service name is required " "(-n|--service <service name>).")) if options.manifest_name is None: parser.error(_("Manifest name is required " "(-m|--manifest <manifest_name>).")) if args: parser.error(_("Unexpected argument(s): %s" % args)) if not config.is_service(options.service_name): raise SystemExit(_("Not a valid service: %s") % options.service_name) options.svcdir_path = AIService(options.service_name).config_dir logging.debug("options = %s", options) return options
def do_alias_service(options): ''' Create an alias of a service ''' # Ensure that the base service is a service if not config.is_service(options.aliasof): raise SystemExit(_("\nService does not exist: %s\n") % options.aliasof) basesvc = AIService(options.aliasof) # Ensure that the base service is not an alias if basesvc.is_alias(): raise SystemExit(_("\nError: Cannot create alias of another alias.\n")) image = basesvc.image if options.svcname in DEFAULT_ARCH: if ((image.arch == 'sparc' and 'sparc' not in options.svcname) or (image.arch == 'i386' and 'i386' not in options.svcname)): raise SystemExit( cw( _("\nError: %s can not be an alias of a " "service with a different architecture.\n") % options.svcname)) logging.debug("Creating alias of service %s", options.aliasof) print _("\nCreating %(arch)s alias: %(name)s\n") % \ {'arch': image.arch, 'name': options.svcname} logging.debug("Creating AIService aliasname %s base svc=%s, bootargs=%s", options.svcname, options.aliasof, options.bootargs) try: service = AIService.create(options.svcname, image, alias=options.aliasof, bootargs=options.bootargs) except AIServiceError as err: raise SystemExit(err) # if recreating default-sparc alias, recreate symlinks if service.is_default_arch_service() and image.arch == 'sparc': logging.debug("Recreating default-sparc symlinks") service.do_default_sparc_symlinks(options.svcname) # Register & enable service # (Also enables system/install/server, as needed) try: service.enable() except (config.ServiceCfgError, MountError) as err: raise SystemExit(err) except aismf.ServicesError as err: # don't error out if the service is successfully created # but the services fail to start - just print out the error # and proceed print err
def find_clients(lservices, sname=None): """ find_clients() returns a dictionary that contains a list of dictionaries. The service name is the key for the main dictionary and the client, image path, and arch are members of the subdictionary, as follows: { 'service1': [ { 'ipath':<path1>, 'client':<client1>, 'arch': <arch>}, .... ], .... } Args lservices = config.get_all_service_props() sname - service name, if only interesetd in clients of a specific service Returns dictionary of a list of dictionaries Raises None """ sdict = dict() for servicename in lservices.keys(): if sname and sname != servicename: continue try: service = AIService(servicename) except VersionError as version_err: warn_version(version_err) continue arch = which_arch(service) image_path = [service.image.path] client_info = config.get_clients(servicename) for clientkey in client_info: # strip off the leading '01' and reinsert ':'s client = AIdb.formatValue('mac', clientkey[2:]) tdict = {'client': client, 'ipath': image_path, 'arch': arch} if servicename in sdict: # existing service name slist = sdict[servicename] slist.extend([tdict]) sdict[servicename] = slist else: # new service name key sdict[servicename] = [tdict] return sdict
def do_delete_profile(cmd_options=None): ''' external entry point for installadm Arg: cmd_options - command line options Effect: delete profiles per command line ''' options = parse_options(cmd_options) # get AI service directory, database name service = AIService(options.service_name) dbname = service.database_path # Open the database aisql = AIdb.DB(dbname, commit=True) aisql.verifyDBStructure() # delete profiles per command line errs = delete_profiles(options.profile_name, aisql, AIdb.PROFILES_TABLE) if errs: sys.exit(1)
def remove_client(client_id): ''' Remove client configuration If client configuration incomplete (e.g., dangling symlink), cleanup anyway. ''' logging.debug("Removing client config for %s", client_id) (service, datadict) = config.find_client(client_id) more_files = list() if service: # remove client info from .config file config.remove_client_from_config(service, client_id) if AIService(service).arch == 'i386': # suggest dhcp unconfiguration remove_client_dhcp_config(client_id) # remove client specific symlinks/files _cleanup_files(client_id, more_files)
def do_validate_profile(cmd_options=None): ''' external entry point for installadm Arg: cmd_options - command line options Effect: validate per command line ''' options = parse_options(cmd_options) isvalid = True # get AI service directory, database name service = AIService(options.service_name) image_dir = service.image.path dbname = service.database_path if options.profile_name: isvalid = validate_internal(options.profile_name, dbname, AIdb.PROFILES_TABLE, image_dir) if options.profile_path: # iterate through profile files on command line for fname in options.profile_path: if not df.validate_file(os.path.basename(fname), fname, image_dir): isvalid = False # return failure status if any profiles failed validation if not isvalid: sys.exit(1)
def parse_options(do_create, cmd_options=None): """ Parse and validate options Args: - do_create (True) or do_update (False) - Optional cmd_options, used for unit testing. Otherwise, cmd line options handled by OptionParser Returns: the DataFiles object populated and initialized Raises: The DataFiles initialization of manifest(s) A/I, SC, SMF looks for many error conditions and, when caught, are flagged to the user via raising SystemExit exceptions. """ if do_create: usage = '\n' + get_create_usage() else: usage = '\n' + get_update_usage() parser = OptionParser(usage=usage) if do_create: parser.add_option("-c", "--criteria", dest="criteria_c", action="append", default=list(), help=_("Criteria: " "<-c criteria=value|range> ..."), metavar="CRITERIA") parser.add_option("-C", "--criteria-file", dest="criteria_file", default=None, help=_("Path to criteria XML file.")) parser.add_option("-d", "--default", dest="set_as_default", default=False, action='store_true', help=_("Set manifest as default ")) parser.add_option("-f", "--file", dest="manifest_path", default=None, help=_("Path to manifest file ")) parser.add_option("-m", "--manifest", dest="manifest_name", default=None, help=_("Name of manifest")) parser.add_option("-n", "--service", dest="service_name", default=None, help=_("Name of install service.")) # Get the parsed options using parse_args(). We know we don't have # args, so we're just grabbing the first item of the tuple returned. options, args = parser.parse_args(cmd_options) if len(args): parser.error(_("Unexpected argument(s): %s" % args)) if not do_create: options.criteria_file = None options.criteria_c = None options.set_as_default = False # options are: # -c criteria=<value/range> ... (create only) # -C XML file with criteria specified (create only) # -d set manifest as default (create only) # -n service name # -f path to manifest file # -m manifest name # check that we got the install service's name and an AI manifest. if options.manifest_path is None or options.service_name is None: parser.error(_("Missing one or more required options.")) logging.debug("options = %s", options) criteria_dict = None if do_create: # check that we aren't mixing -c and -C # Note: -c and -C will be accepted for create, not for update. if options.criteria_c and options.criteria_file: parser.error(_("Options used are mutually exclusive.")) # if we have criteria from cmd line, convert into dictionary if options.criteria_c: try: criteria_dict = criteria_to_dict(options.criteria_c) except ValueError as err: parser.error(err) elif options.criteria_file: if not os.path.exists(options.criteria_file): parser.error( _("Unable to find criteria file: %s") % options.criteria_file) if not config.is_service(options.service_name): raise SystemExit(_("Failed to find service %s") % options.service_name) # Get the service's imagepath. If service is an alias, the # base service's imagepath is obtained. service = AIService(options.service_name) try: image_path = service.image.path except KeyError as err: raise SystemExit( _("Data for service %s is corrupt. Missing " "property: %s\n") % (options.service_name, err)) service_dir = service.config_dir dbname = service.database_path try: files = df.DataFiles(service_dir=service_dir, image_path=image_path, database_path=dbname, manifest_file=options.manifest_path, manifest_name=options.manifest_name, criteria_dict=criteria_dict, criteria_file=options.criteria_file, service_name=options.service_name, set_as_default=options.set_as_default) except (AssertionError, IOError, ValueError) as err: raise SystemExit(err) except (lxml.etree.LxmlError) as err: raise SystemExit(_("Error:\tmanifest error: %s") % err) return (files)
def parse_options(cmd_options=None): '''Parse commandline options for export command''' parser = OptionParser(usage=get_usage(), prog="export") parser.add_option('-p', '--profile', dest='pnames', action="append", default=list(), help=_("Name of profile to export.")) parser.add_option('-m', '--manifest', dest='mnames', action="append", default=list(), help=_("Name of manifest to export.")) parser.add_option('-n', '--service', dest='service_name', default=None, help=_("Name of install service.")) parser.add_option('-o', '--output', dest='output_name', default=None, help=_("Name of output file.")) (options, args) = parser.parse_args(cmd_options) if args: parser.error(_("Extra args given.")) if not options.service_name: parser.error(_("Service name is required.")) if not config.is_service(options.service_name): raise SystemExit(_("No such service: %s") % options.service_name) service = AIService(options.service_name) options.service = service if not len(options.mnames) and not len(options.pnames): parser.error(_("A manifest or profile name is required.")) # based on the argument, check for authorization and euid try: if len(options.mnames): check_auth_and_euid(MANIFEST_AUTH) if len(options.pnames): check_auth_and_euid(PROFILE_AUTH) except UnauthorizedUserError as err: raise SystemExit(err) options.file_count = len(options.mnames) + len(options.pnames) if not options.output_name: options.output_name = SCREEN options.output_isdir = None else: # Non-stdout -o processing: # if output_name is an existing directory: write all files out to it. # if output_name is an existing file and output one file: # overwrite the existing file. # if file exists with output_name and mult output files desired: # error # if file or dir doesn't exist w/output name and mult files desired: # create new directory with output name and write files there. # if file or dir doesn't exist with output name and one file desired: # write the one file to that output name options.output_isdir = False if os.path.isdir(options.output_name): options.output_isdir = True elif os.path.exists(options.output_name): if (options.file_count > 1): parser.error(_("-o must specify a directory when multiple " "files are requested.")) else: if (options.file_count > 1): os.mkdir(options.output_name) options.output_isdir = True return options
def do_create_baseservice(options): ''' This method sets up the install service by: - creating the target image directory from an iso or pkg - creating the /var/ai service structure - enabling tftp service or configuring wanboot - configuring dhcp if desired ''' tempdir = None print _("\nCreating service from: %s") % options.srcimage if is_iso(options.srcimage): have_iso = True # get default service name, if needed logging.debug("Creating ISO based service") else: have_iso = False logging.debug("Creating pkg(5) based service") # If imagepath specified by user, use that. # If imagepath not specified by user: # a) if svcname specified by user, set up image in # <default image path>/<svcname> # b) if svcname not specified by user, set up image in # <tmp location> and move to <default image path>/<svcname> # once svcname is determined. # If imagepath not specified, verify that default image path is # ok with user if not options.imagepath: if options.svcname: imagepath = os.path.join(BASE_IMAGE_DIR, options.svcname) prompt = (_("OK to use default image path: %s? [y/N]: " % imagepath)) else: prompt = (_("OK to use subdir of %s to store image? [y/N]: " % BASE_IMAGE_DIR)) try: if not options.noprompt: if not com.ask_yes_or_no(prompt): raise SystemExit( _('\nPlease re-enter command with ' 'desired --imagepath\n')) except KeyboardInterrupt: raise SystemExit(1) # If we know the svcname, we know where to put the image. # Otherwise, put the image into a temp directory and move # it to correct location when we know it later if options.svcname: options.imagepath = os.path.join(BASE_IMAGE_DIR, options.svcname) try: img.check_imagepath(options.imagepath) except ValueError as error: raise SystemExit(error) else: try: # This directory need to have at least o+x permission. orig_umask = os.umask(00) modified_umask = orig_umask & 0776 os.umask(modified_umask) os.makedirs(BASE_IMAGE_DIR) os.umask(orig_umask) except OSError as err: if err.errno != errno.EEXIST: raise if not os.path.isdir(BASE_IMAGE_DIR): raise SystemExit( cw( _('\nThe default image base ' 'directory, %(dir)s, is not a directory. Check the ' 'SMF setting for property %(prop)s in servce ' '%(svc)s.') % { 'dir': BASE_IMAGE_DIR, 'prop': com.BASEDIR_PROP, 'svc': com.SRVINST })) tempdir = tempfile.mkdtemp(dir=BASE_IMAGE_DIR) options.imagepath = tempdir logging.debug('Using default image path: %s', options.imagepath) # create the image area if have_iso: try: image = InstalladmIsoImage.unpack(options.srcimage, options.imagepath) except CalledProcessError as err: raise SystemExit(err.popen.stderr) except ImageError as err: print >> sys.stderr, str(err) shutil.rmtree(options.imagepath, ignore_errors=True) raise SystemExit( cw( _('Please re-enter command and specify ' 'a valid Automated Installer ISO file'))) else: try: image = InstalladmPkgImage.image_create( options.srcimage, options.imagepath, arch=options.arch, publisher=options.publisher) except (ImageError, pkg.client.api_errors.ApiException) as err: print >> sys.stderr, cw(_("The specified data source, %s, " "for the service is not a path to an existing ISO image.") % \ options.srcimage) print >> sys.stderr, cw(_("Attempting to create the service from" " pkg(5) package, %s, failed for the following reasons:") % \ options.srcimage) if isinstance(err, pkg.client.api_errors.VersionException): print >> sys.stderr, cw( _("The IPS API version specified, " "%(specver)s, is incompatible with the expected version, " "%(expver)s.") % { 'specver': str(err.received_version), 'expver': str(err.expected_version) }) elif isinstance(err, pkg.client.api_errors.CatalogRefreshException): for pub, error in err.failed: print >> sys.stderr, " " print >> sys.stderr, str(error) if err.errmessage: print >> sys.stderr, err.errmessage shutil.rmtree(options.imagepath, ignore_errors=True) raise SystemExit(err) # get default service name, if needed if not options.svcname: if tempdir and options.imagepath == tempdir: specified_path = None else: specified_path = options.imagepath options.svcname = img.get_default_service_name(specified_path, image=image, iso=have_iso) print _("\nCreating %(arch)s service: %(name)s\n") % \ {'arch': image.arch, 'name': options.svcname} # If image was created in temporary location, move to correct # location now that we know the svcname. if tempdir is not None: new_imagepath = os.path.join(BASE_IMAGE_DIR, options.svcname) try: img.check_imagepath(new_imagepath) except ValueError as error: # leave image in temp location so that service can be created logging.debug('unable to move image to %s: %s', new_imagepath, error) else: options.imagepath = image.move(new_imagepath) logging.debug('image moved to %s', options.imagepath) img.set_permissions(options.imagepath) print _("Image path: %s\n") % options.imagepath try: if options.dhcp_ip_start: service = AIService.create(options.svcname, image, options.dhcp_ip_start, options.dhcp_ip_count, options.dhcp_bootserver, bootargs=options.bootargs) else: service = AIService.create(options.svcname, image, bootargs=options.bootargs) except (GrubError, BootmgmtError) as err: AIService(options.svcname).delete() raise SystemExit( _('\nError: Unable to create service, ' '%(service)s:\n%(error)s') % { 'service': options.svcname, 'error': err }) except AIServiceError as err: raise SystemExit(err) # Register & enable service # (Also enables system/install/server, as needed) got_services_error = False try: service.enable() except (config.ServiceCfgError, MountError) as err: raise SystemExit(err) except aismf.ServicesError as svc_err: # Don't print the error now. It will either get printed out # upon exit or when services are enabled after creating the # alias got_services_error = True # create default-<arch> alias if this is the first aliasable # service of this architecture if should_be_default_for_arch(service): defaultarch = 'default-' + image.arch print(_("\nCreating %s alias\n") % defaultarch) try: defaultarchsvc = AIService.create(defaultarch, image, bootargs=options.bootargs, alias=options.svcname) except (GrubError, BootmgmtError) as err: AIService(defaultarch).delete() raise SystemExit( _('\nError: Unable to create alias, ' '%(arch)s:\n%(error)s') % { 'arch': defaultarch, 'error': err }) except AIServiceError as err: raise SystemExit(err) except UnsupportedAliasError as err: if got_services_error: # Print the services error string before printing the # unsupported alias error print svc_err, '\n' # Print the error, but have installadm exit successfully. # Since the user did not explicitly request this alias, # it's not a problem if an alias can't be made for this service print err return 0 # For sparc, create symlinks for default sparc service if image.arch == 'sparc': logging.debug("Creating default-sparc symlinks") defaultarchsvc.do_default_sparc_symlinks(defaultarch) # Register & enable default-<arch> service try: defaultarchsvc.enable() except (config.ServiceCfgError, MountError) as err: raise SystemExit(err) except aismf.ServicesError as err: print err elif got_services_error: # Print the services start error generated when creating the service print svc_err
def do_set_criteria(cmd_options=None): ''' Modify the criteria associated with a manifest. ''' # check that we are root if os.geteuid() != 0: raise SystemExit( _("Error: Root privileges are required for " "this command.")) options = parse_options(cmd_options) # Get the install service's properties. if not config.is_service(options.service_name): raise SystemExit(_("Failed to find service %s") % options.service_name) service = AIService(options.service_name) database = service.database_path # Open the database dbn = AIdb.DB(database, commit=True) # Check to make sure that the manifest whose criteria we're # updating exists in the install service. if (options.manifest_name and not check_published_manifest(service, dbn, options.manifest_name)): raise SystemExit(1) # Process and validate criteria from -a, -c, or -C, and store # store the criteria in a Criteria object. try: if options.criteria_file: root = df.verifyCriteria(df.DataFiles.criteriaSchema, options.criteria_file, dbn, AIdb.MANIFESTS_TABLE) elif options.criteria_a: criteria_dict = pub_man.criteria_to_dict(options.criteria_a) root = df.verifyCriteriaDict(df.DataFiles.criteriaSchema, criteria_dict, dbn, AIdb.MANIFESTS_TABLE) elif options.criteria_c: criteria_dict = pub_man.criteria_to_dict(options.criteria_c) root = df.verifyCriteriaDict(df.DataFiles.criteriaSchema, criteria_dict, dbn, AIdb.MANIFESTS_TABLE) else: raise SystemExit("Error: Missing required criteria.") except (AssertionError, IOError, ValueError) as err: raise SystemExit(err) except (lxml.etree.LxmlError) as err: raise SystemExit(_("Error:\tmanifest error: %s") % err) # Instantiate a Criteria object with the XML DOM of the criteria. criteria = df.Criteria(root) if options.manifest_name: # Ensure the criteria we're adding/setting for this manifest doesn't # cause a criteria collision in the DB. colliding_criteria = pub_man.find_colliding_criteria( criteria, dbn, exclude_manifests=[options.manifest_name]) # If we're appending criteria pass the manifest name if options.criteria_a: pub_man.find_colliding_manifests( criteria, dbn, colliding_criteria, append_manifest=options.manifest_name) else: pub_man.find_colliding_manifests(criteria, dbn, colliding_criteria, append_manifest=None) # validate criteria for profile for pname in options.profile_name: if not sc.is_name_in_table(pname, dbn.getQueue(), AIdb.PROFILES_TABLE): raise SystemExit( _("Error:\tservice has no profile named %s." % pname)) # Validate profile criteria sc.validate_criteria_from_user(criteria, dbn, AIdb.PROFILES_TABLE) # all validation complete - update database # indicate whether criteria are added or replaced if options.criteria_a: append = True # add new criteria else: append = False # replace any existing criteria with new if options.manifest_name: # Update the criteria for manifest set_criteria(criteria, options.manifest_name, dbn, AIdb.MANIFESTS_TABLE, append) print >> sys.stderr, _("Criteria updated for manifest %s.") % \ options.manifest_name for pname in options.profile_name: # Update the criteria for profile set_criteria(criteria, pname, dbn, AIdb.PROFILES_TABLE, append) print >> sys.stderr, _("Criteria updated for profile %s.") % pname
def do_update_profile(cmd_options=None): ''' Updates exisiting profile Arg: cmd_options - command line options Effect: update existing profile Raises SystemExit if condition cannot be handled ''' # check for authorization and euid try: check_auth_and_euid(PROFILE_AUTH) except UnauthorizedUserError as err: raise SystemExit(err) options = parse_options(DO_UPDATE, cmd_options) # verify the file profile_file = options.profile_file[0] if not os.path.exists(profile_file): raise SystemExit(_("Error:\tFile does not exist: %s\n") % profile_file) # get profile name if not options.profile_name: profile_name = os.path.basename(profile_file) else: profile_name = options.profile_name # get AI service image path and database name service = AIService(options.service_name) dbname = service.database_path image_dir = service.image.path # open database dbn = AIdb.DB(dbname, commit=True) dbn.verifyDBStructure() queue = dbn.getQueue() # Handle old DB versions which did not store a profile. if not AIdb.tableExists(queue, AIdb.PROFILES_TABLE): raise SystemExit( _("Error:\tService %s does not support profiles") % options.service_name) # check for the existence of profile missing_profile_error = _("Error:\tService {service} has no profile " "named {profile}.") if not sc.is_name_in_table(profile_name, queue, AIdb.PROFILES_TABLE): raise SystemExit( missing_profile_error.format(service=options.service_name, profile=profile_name)) # validates the profile and report the errors if found raw_profile = df.validate_file(profile_name, profile_file, image_dir, verbose=False) if not raw_profile: raise SystemExit(1) # create file from string and report failures tmp_profile_path = copy_profile_internally(raw_profile) if not tmp_profile_path: raise SystemExit(1) # get the path of profile in db q_str = "SELECT file FROM " + AIdb.PROFILES_TABLE + " WHERE name=" \ + AIdb.format_value('name', profile_name) query = AIdb.DBrequest(q_str) queue.put(query) query.waitAns() response = query.getResponse() # database error if response is None: raise SystemExit( missing_profile_error.format(service=options.service_name, profile=profile_name)) db_profile_path = response[0][0] # replace the file try: shutil.copyfile(tmp_profile_path, db_profile_path) except IOError as err: raise SystemExit( _("Error writing profile %(profile)s: %(err)s") % { 'profile': profile_name, 'err': err }) finally: os.unlink(tmp_profile_path) print >> sys.stderr, _("Profile updated successfully.")
def print_local_manifests(sdict, smwidth, mfindent, stwidth, cwidth): """ Iterates over the name dictionary printing each manifest or criteria within the dictionary. The name dictionary is populated via get_manifest_or_profile_names(). Args sdict = service manifest dictionary { 'servicename1': [ [ manifestfile1, has_criteria (boolean), {} ], ... ], ... } smwidth = the length of the widest service or manifest name mfindent = how many spaces will be manifest name indented stwidth = width of status column cwidth = the length of the widest criteria Returns None Raises None """ tkeys = sdict.keys() tkeys.sort() smwidth += 1 stwidth += 1 for akey in tkeys: default_mfest = None inactive_mfests = list() active_mfests = list() try: default_mname = AIService(akey).get_default_manifest() except StandardError: default_mname = "" for manifest_item in sdict[akey]: # manifest_items are a list of # [ name, number of criteria, criteria_dictionary ] if manifest_item[0] == default_mname: default_mfest = manifest_item # There could be max 1 default elif manifest_item[1]: # has_criteria and not default active_mfests.append(manifest_item) else: inactive_mfests.append(manifest_item) print akey # print service name on separate line line = ''.ljust(mfindent) # Manifest is indented for manifest_i in active_mfests: line += manifest_i[0].ljust(smwidth - mfindent) # Manifest line += ''.ljust(stwidth) # Status is empty for active mfests ordered_keys = ['arch', 'mac', 'ipv4'] keys = manifest_i[2].keys() keys.sort() for k in keys: if k not in ordered_keys: ordered_keys.append(k) crit_printed = False for k in ordered_keys: if k in manifest_i[2] and manifest_i[2][k] != '': line += k.ljust(cwidth) + ' = ' + manifest_i[2][k] print line crit_printed = True line = ''.ljust(mfindent) + \ ''.ljust(smwidth - mfindent) + ''.ljust(stwidth) if not crit_printed: line += _("None") print line print # Blank line after each manifest line = ''.ljust(mfindent) if default_mfest: line += default_mfest[0].ljust(smwidth - mfindent) # Manifest name line += DEFAULT.ljust(stwidth) # Status is Default # Default manifest can have ignored criteria ordered_keys = ['arch', 'mac', 'ipv4'] keys = default_mfest[2].keys() keys.sort() for k in keys: if k not in ordered_keys: ordered_keys.append(k) crit_printed = False for k in ordered_keys: if k in default_mfest[2] and default_mfest[2][k] != '': line += '(' + IGNORED + ': ' + k.ljust(cwidth) + \ ' = ' + default_mfest[2][k] + ')' print line crit_printed = True line = ''.ljust(mfindent) + \ ''.ljust(smwidth - mfindent) + ''.ljust(stwidth) if not crit_printed: line += _("None") print line line = ''.ljust(mfindent) print # Blank line after each manifest for manifest_i in inactive_mfests: line += manifest_i[0].ljust(smwidth - mfindent) # Manifest line += INACTIVE.ljust(stwidth) line += _("None") # Inactive manifests have no criteria print line print # Blank line after each manifest line = ''.ljust(mfindent)
def send_manifest(form_data, port=0, servicename=None, protocolversion=COMPATIBILITY_VERSION, no_default=False): '''Replies to the client with matching service for a service. Args form_data - the postData passed in from the client request port - the port of the old client servicename - the name of the service being used protocolversion - the version of the AI service RE: handshake no_default - boolean flag to signify whether or not we should hand back the default manifest and profiles if one cannot be matched based on the client criteria. Returns None Raises None ''' # figure out the appropriate path for the AI database, # and get service name if necessary. # currently service information is stored in a port directory. # When the cherrypy webserver new service directories should be # separated via service-name only. Old services will still use # port numbers as the separation mechanism. path = None found_servicename = None service = None port = str(port) if servicename: service = AIService(servicename) path = service.database_path else: for name in config.get_all_service_names(): if config.get_service_port(name) == port: found_servicename = name service = AIService(name) path = service.database_path break # Check to insure that a valid path was found if not path or not os.path.exists(path): print 'Content-Type: text/html' # HTML is following print # blank line, end of headers if servicename: print '<pre><b>Error</b>:unable to find<i>', servicename + '</i>.' else: print '<pre><b>Error</b>:unable to find<i>', port + '</i>.' print 'Available services are:<p><ol><i>' hostname = socket.gethostname() for name in config.get_all_service_names(): port = config.get_service_port(name) sys.stdout.write( '<a href="http://%s:%d/cgi-bin/' 'cgi_get_manifest.py?version=%s&service=%s">%s</a><br>\n' % (hostname, port, VERSION, name, name)) print '</i></ol>Please select a service from the above list.' return if found_servicename: servicename = found_servicename # load to the AI database aisql = AIdb.DB(path) aisql.verifyDBStructure() # convert the form data into a criteria dictionary criteria = dict() orig_data = form_data while form_data: try: [key_value, form_data] = form_data.split(';', 1) except (ValueError, NameError, TypeError, KeyError): key_value = form_data form_data = '' try: [key, value] = key_value.split('=') criteria[key] = value except (ValueError, NameError, TypeError, KeyError): criteria = dict() # Generate templating dictionary from criteria template_dict = dict() for crit in criteria: template_dict["AI_" + crit.upper()] = \ AIdb.formatValue(crit, criteria[crit], units=False) # find the appropriate manifest try: manifest = AIdb.findManifest(criteria, aisql) except StandardError as err: print 'Content-Type: text/html' # HTML is following print # blank line, end of headers print '<pre><b>Error</b>:findManifest criteria<br>' print err, '<br>' print '<ol>servicename =', servicename print 'port =', port print 'path =', path print 'form_data =', orig_data print 'criteria =', criteria print 'servicename found by port =', found_servicename, '</ol>' print '</pre>' return # check if findManifest() returned a number equal to 0 # (means we got no manifests back -- thus we serve the default if desired) if manifest is None and not no_default: manifest = service.get_default_manifest() # if we have a manifest to return, prepare its return if manifest is not None: try: # construct the fully qualified filename filename = os.path.abspath( os.path.join(service.manifest_dir, manifest)) # open and read the manifest with open(filename, 'rb') as mfp: manifest_str = mfp.read() # maintain compability with older AI client if servicename is None or \ float(protocolversion) < float(PROFILES_VERSION): content_type = mimetypes.types_map.get('.xml', 'text/plain') print 'Content-Length:', len( manifest_str) # Length of the file print 'Content-Type:', content_type # XML is following print # blank line, end of headers print manifest_str logging.info('Manifest sent from %s.' % filename) return except OSError as err: print 'Content-Type: text/html' # HTML is following print # blank line, end of headers print '<pre>' # report the internal error to error_log and requesting client sys.stderr.write(_('error:manifest (%s) %s\n') % \ (str(manifest), err)) sys.stdout.write(_('error:manifest (%s) %s\n') % \ (str(manifest), err)) print '</pre>' return # get AI service image path service = AIService(servicename) image_dir = service.image.path # construct object to contain MIME multipart message outermime = MIMEMultipart() client_msg = list() # accumulate message output for AI client # If we have a manifest, attach it to the return message if manifest is not None: # add manifest as attachment msg = MIMEText(manifest_str, 'xml') # indicate manifest using special name msg.add_header('Content-Disposition', 'attachment', filename=sc.AI_MANIFEST_ATTACHMENT_NAME) outermime.attach(msg) # add manifest as an attachment # search for any profiles matching client criteria # formulate database query to profiles table q_str = "SELECT DISTINCT name, file FROM " + \ AIdb.PROFILES_TABLE + " WHERE " nvpairs = list() # accumulate criteria values from post-data # for all AI client criteria for crit in AIdb.getCriteria(aisql.getQueue(), table=AIdb.PROFILES_TABLE, onlyUsed=False): if crit not in criteria: msgtxt = _("Warning: client criteria \"%s\" not provided in " "request. Setting value to NULL for profile lookup.") \ % crit client_msg += [msgtxt] logging.warn(msgtxt) # fetch only global profiles destined for all clients if AIdb.isRangeCriteria(aisql.getQueue(), crit, AIdb.PROFILES_TABLE): nvpairs += ["MIN" + crit + " IS NULL"] nvpairs += ["MAX" + crit + " IS NULL"] else: nvpairs += [crit + " IS NULL"] continue # prepare criteria value to add to query envval = AIdb.sanitizeSQL(criteria[crit]) if AIdb.isRangeCriteria(aisql.getQueue(), crit, AIdb.PROFILES_TABLE): # If no default profiles are requested, then we mustn't allow # this criteria to be NULL. It must match the client's given # value for this criteria. if no_default: if crit == "mac": nvpairs += ["(HEX(MIN" + crit + ")<=HEX(X'" + envval + \ "'))"] nvpairs += ["(HEX(MAX" + crit + ")>=HEX(X'" + envval + \ "'))"] else: nvpairs += ["(MIN" + crit + "<='" + envval + "')"] nvpairs += ["(MAX" + crit + ">='" + envval + "')"] else: if crit == "mac": nvpairs += [ "(MIN" + crit + " IS NULL OR " "HEX(MIN" + crit + ")<=HEX(X'" + envval + "'))" ] nvpairs += [ "(MAX" + crit + " IS NULL OR HEX(MAX" + crit + ")>=HEX(X'" + envval + "'))" ] else: nvpairs += [ "(MIN" + crit + " IS NULL OR MIN" + crit + "<='" + envval + "')" ] nvpairs += [ "(MAX" + crit + " IS NULL OR MAX" + crit + ">='" + envval + "')" ] else: # If no default profiles are requested, then we mustn't allow # this criteria to be NULL. It must match the client's given # value for this criteria. # # Also, since this is a non-range criteria, the value stored # in the DB may be a whitespace separated list of single # values. We use a special user-defined function in the # determine if the given criteria is in that textual list. if no_default: nvpairs += ["(is_in_list('" + crit + "', '" + envval + \ "', " + crit + ", 'None') == 1)"] else: nvpairs += ["(" + crit + " IS NULL OR is_in_list('" + crit + \ "', '" + envval + "', " + crit + ", 'None') == 1)"] if len(nvpairs) > 0: q_str += " AND ".join(nvpairs) # issue database query logging.info("Profile query: " + q_str) query = AIdb.DBrequest(q_str) aisql.getQueue().put(query) query.waitAns() if query.getResponse() is None or len(query.getResponse()) == 0: msgtxt = _("No profiles found.") client_msg += [msgtxt] logging.info(msgtxt) else: for row in query.getResponse(): profpath = row['file'] profname = row['name'] if profname is None: # should not happen profname = 'unnamed' try: if profpath is None: msgtxt = "Database record error - profile path is " \ "empty." client_msg += [msgtxt] logging.error(msgtxt) continue msgtxt = _('Processing profile %s') % profname client_msg += [msgtxt] logging.info(msgtxt) with open(profpath, 'r') as pfp: raw_profile = pfp.read() # do any template variable replacement {{AI_xxx}} tmpl_profile = sc.perform_templating( raw_profile, template_dict) # precautionary validation of profile, logging only sc.validate_profile_string(tmpl_profile, image_dir, dtd_validation=True, warn_if_dtd_missing=True) except IOError as err: msgtxt = _("Error: I/O error: ") + str(err) client_msg += [msgtxt] logging.error(msgtxt) continue except OSError: msgtxt = _("Error: OS error on profile ") + profpath client_msg += [msgtxt] logging.error(msgtxt) continue except KeyError: msgtxt = _('Error: could not find criteria to substitute ' 'in template: ') + profpath client_msg += [msgtxt] logging.error(msgtxt) logging.error('Profile with template substitution error:' + raw_profile) continue except lxml.etree.XMLSyntaxError as err: # log validation error and proceed msgtxt = _( 'Warning: syntax error found in profile: ') \ + profpath client_msg += [msgtxt] logging.error(msgtxt) for error in err.error_log: msgtxt = _('Error: ') + error.message client_msg += [msgtxt] logging.error(msgtxt) logging.info([ _('Profile failing validation: ') + lxml.etree.tostring(root) ]) # build MIME message and attach to outer MIME message msg = MIMEText(tmpl_profile, 'xml') # indicate in header that this is an attachment msg.add_header('Content-Disposition', 'attachment', filename=profname) # attach this profile to the manifest and any other profiles outermime.attach(msg) msgtxt = _('Parsed and loaded profile: ') + profname client_msg += [msgtxt] logging.info(msgtxt) # any profiles and AI manifest have been attached to MIME message # specially format list of messages for display on AI client console if client_msg: outtxt = '' for msgtxt in client_msg: msgtxt = _('SC profile locator:') + msgtxt outtxt += str(msgtxt) + '\n' # add AI client console messages as single plain text attachment msg = MIMEText(outtxt, 'plain') # create MIME message outermime.attach(msg) # attach MIME message to response print outermime.as_string() # send MIME-formatted message
def do_create_profile(cmd_options=None): ''' external entry point for installadm Arg: cmd_options - command line options Effect: add profiles to database per command line Raises SystemExit if condition cannot be handled ''' # check for authorization and euid try: check_auth_and_euid(PROFILE_AUTH) except UnauthorizedUserError as err: raise SystemExit(err) options = parse_options(DO_CREATE, cmd_options) # get AI service image path and database name service = AIService(options.service_name) image_dir = service.image.path dbname = service.database_path # open database dbn = AIdb.DB(dbname, commit=True) dbn.verifyDBStructure() queue = dbn.getQueue() root = None criteria_dict = dict() # Handle old DB versions which did not store a profile. if not AIdb.tableExists(queue, AIdb.PROFILES_TABLE): raise SystemExit( _("Error:\tService %s does not support profiles") % options.service_name) try: if options.criteria_file: # extract criteria from file root = df.verifyCriteria(df.DataFiles.criteriaSchema, options.criteria_file, dbn, AIdb.PROFILES_TABLE) elif options.criteria_c: # if we have criteria from cmd line, convert into dictionary criteria_dict = pub_man.criteria_to_dict(options.criteria_c) root = df.verifyCriteriaDict(df.DataFiles.criteriaSchema, criteria_dict, dbn, AIdb.PROFILES_TABLE) except ValueError as err: raise SystemExit(_("Error:\tcriteria error: %s") % err) # Instantiate a Criteria object with the XML DOM of the criteria. criteria = df.Criteria(root) sc.validate_criteria_from_user(criteria, dbn, AIdb.PROFILES_TABLE) # track exit status for all profiles, assuming no errors has_errors = False # loop through each profile on command line for profile_file in options.profile_file: # take option name either from command line or from basename of profile if options.profile_name: profile_name = options.profile_name else: profile_name = os.path.basename(profile_file) # check for any scope violations if sc.is_name_in_table(profile_name, queue, AIdb.PROFILES_TABLE): print >> sys.stderr, \ _("Error: A profile named %(name)s is already in the " "database for service %(service)s.") % \ {'name': profile_name, 'service': options.service_name} has_errors = True continue # open profile file specified by user on command line if not os.path.exists(profile_file): print >> sys.stderr, _("File %s does not exist") % profile_file has_errors = True continue # validates the profile and report errors if found raw_profile = df.validate_file(profile_name, profile_file, image_dir, verbose=False) if not raw_profile: has_errors = True continue # create file from profile string and report failures full_profile_path = copy_profile_internally(raw_profile) if not full_profile_path: has_errors = True continue # add new profile to database if not add_profile(criteria, profile_name, full_profile_path, queue, AIdb.PROFILES_TABLE): os.unlink(full_profile_path) # failure, back out internal profile has_errors = True # exit with status if any errors in any profiles if has_errors: sys.exit(1)
def get_local_services(services, sname=None): """ Iterates over the local services on a host creating a dictionary with the service name as the key and status, path, architecture, and aliasof as the value. If name is not None then it ensures that only the named service is retrieved. Args services = config.get_all_service_props() name = service name Returns a service dictionary made up of a list of dictionary of services. { service1: [ {'status':on1, 'path':path1, 'arch':arch1, 'aliasof':aliasof1}, ... ], ... } the width of the longest service name the width of the longest aliasof name Raises None """ width = 0 aliasofwidth = 1 sdict = dict() for akey in services: serv = services[akey] servicename = akey # ensure that the current service has the keys we need. # if not, print error, but continue listing other services try: config.verify_key_properties(akey, serv) except config.ServiceCfgError as err: print >> sys.stderr, err continue try: service = AIService(servicename) except VersionError as err: warn_version(err) continue if config.PROP_ALIAS_OF in serv: image_path = service.image.path serv[config.PROP_IMAGE_PATH] = image_path info = dict() # if a service name is passed in then # ensure it matches the current name if not sname or sname == servicename: width = max(len(servicename), width) info['status'] = serv[config.PROP_STATUS] info['path'] = serv[config.PROP_IMAGE_PATH] info['arch'] = which_arch(service) if config.PROP_ALIAS_OF in serv: # have an alias aliasof = serv[config.PROP_ALIAS_OF] else: aliasof = '-' info['aliasof'] = aliasof aliasofwidth = max(len(aliasof), aliasofwidth) if servicename in sdict: slist = sdict[servicename] slist.extend([info]) sdict[servicename] = slist else: sdict[servicename] = [info] return sdict, width, aliasofwidth
def delete_specified_service(service_name, auto_remove, noprompt): ''' Delete the specified Automated Install Service Input: service_name - service name auto_remove - boolean, True if dep. aliases and clients should be removed, False otherwise noprompt - boolean, True if warning about removing default-<arch> service should be suppressed ''' logging.debug("delete_specified_service %s %s %s", service_name, auto_remove, noprompt) service = AIService(service_name) # If the '-r' option has not been specified, look for all # dependent aliases and clients all_aliases = config.get_aliased_services(service_name, recurse=True) if not auto_remove: all_clients = config.get_clients(service_name).keys() for ale in all_aliases: all_clients.extend(config.get_clients(ale).keys()) # if any aliases or clients are dependent on this service, exit if all_aliases or all_clients: raise SystemExit( cw( _("\nError: The following aliases and/or " "clients are dependent on this service:\n\n" "%s\n\nPlease update or delete them prior " "to deleting this service or rerun this " "command using the -r|--autoremove option " "to have them automatically removed.\n") % '\n'.join(all_aliases + all_clients))) # Prompt user if they are deleting the default-sparc or default-i386 alias if not noprompt: sname = None if service_name in DEFAULT_ARCH: sname = service_name else: default_alias = set(DEFAULT_ARCH) & set(all_aliases) if default_alias: sname = ''.join(default_alias) if sname: arch = sname.split('default-')[1] _warning = """ WARNING: The service you are deleting, or a dependent alias, is the alias for the default %(arch)s service. Without the '%(name)s' service, %(arch)s clients will fail to boot unless explicitly assigned to a service using the create-client command. """ % { 'arch': arch, 'name': sname } print >> sys.stderr, cw(_(_warning)) prompt = _("Are you sure you want to delete this alias? [y/N]: ") if not com.ask_yes_or_no(prompt): raise SystemExit(1) # If there are dependent aliases or clients, then remove these first aliases = config.get_aliased_services(service_name) for dependent in aliases: logging.debug("recursively calling delete_specified_service for %s", dependent) delete_specified_service(dependent, True, True) clients = config.get_clients(service_name).keys() for dependent in clients: logging.debug("calling remove_client for %s", dependent) clientctrl.remove_client(dependent) logging.debug("now deleting service %s", service_name) # remove DHCP bootfile configuration for this service, if set remove_dhcp_configuration(service) # stop the service first (avoid pulling files out from under programs) try: service.delete() except StandardError as err: # Bail out if the service could not be unmounted during the disable, # as it won't be possible to delete necessary files. print >> sys.stderr, _("\nService could not be deleted.") raise SystemExit(err) # if this was the last service, go to maintenance config.check_for_enabled_services()
def get_manifest_or_profile_names(services, dbtable): """ Iterate through the services retrieving all the stored manifest or profile names. Args services = dictionary of service properties dbtable = database table, distinguishing manifests from profiles Returns a dictionary of service manifests or profiles within a list: { servicename1: [ [name, has_criteria (boolean), {crit:value, ... }], ... ], ... } the width of the longest service name (swidth) the width of the longest manifest name (mwidth) the width of the longest criteria (cwidth) Raises None """ swidth = 0 mwidth = 0 cwidth = 0 sdict = dict() for sname in sorted(services.keys()): try: service = AIService(sname) except VersionError as err: warn_version(err) continue path = service.database_path if os.path.exists(path): try: maisql = AIdb.DB(path) maisql.verifyDBStructure() aiqueue = maisql.getQueue() swidth = max(len(sname), swidth) if not AIdb.tableExists(aiqueue, dbtable): continue for name in AIdb.getNames(aiqueue, dbtable): mwidth = max(len(name), mwidth) tdict = dict() if dbtable == 'manifests': instances = AIdb.numInstances(name, aiqueue) for instance in range(0, instances): criteria = AIdb.getTableCriteria(name, instance, aiqueue, dbtable, humanOutput=False, onlyUsed=True) has_criteria = False if criteria is not None: for key in criteria.keys(): if criteria[key] is not None: has_criteria = True break if has_criteria: # We need criteria in human readable form hrcrit = AIdb.getTableCriteria( name, instance, aiqueue, dbtable, humanOutput=True, onlyUsed=True) tdict, twidth = get_criteria_info(hrcrit) cwidth = max(twidth, cwidth) else: criteria = AIdb.getTableCriteria(name, None, aiqueue, dbtable, humanOutput=False, onlyUsed=True) has_criteria = False if criteria is not None: for key in criteria.keys(): if criteria[key] is not None: has_criteria = True break if sname in sdict: slist = sdict[sname] slist.append([name, has_criteria, tdict]) sdict[sname] = slist else: sdict[sname] = [[name, has_criteria, tdict]] except StandardError as err: sys.stderr.write( _('Error: AI database access error\n%s\n') % err) continue else: sys.stderr.write( _('Error: unable to locate AI database for "%s" ' 'on server\n') % sname) continue return sdict, swidth, mwidth, cwidth
def list_manifests(service): '''Replies to the client with criteria list for a service. The output should be similar to installadm list. Args service - the name of the service being listed Returns None Raises None ''' print 'Content-Type: text/html' # HTML is following print # blank line, end of headers print '<html>' print '<head>' sys.stdout.write('<title>%s %s</title>' % (_('Manifest list for'), service)) print '</head><body>' port = 0 try: smf.AISCF(FMRI="system/install/server") except KeyError: # report the internal error to error_log and requesting client sys.stderr.write( _("error:The system does not have the " "system/install/server SMF service.")) sys.stdout.write( _("error:The system does not have the " "system/install/server SMF service.")) return services = config.get_all_service_names() if not services: # report the error to the requesting client only sys.stdout.write(_('error:no services on this server.\n')) return found = False if config.is_service(service): service_ctrl = AIService(service) found = True # assume new service setup path = service_ctrl.database_path if os.path.exists(path): try: aisql = AIdb.DB(path) aisql.verifyDBStructure() except StandardError as err: # report the internal error to error_log and # requesting client sys.stderr.write( _('error:AI database access ' 'error\n%s\n') % err) sys.stdout.write( _('error:AI database access ' 'error\n%s\n') % err) return # generate the list of criteria for the criteria table header criteria_header = E.TR() for crit in AIdb.getCriteria(aisql.getQueue(), strip=False): criteria_header.append(E.TH(crit)) # generate the manifest rows for the criteria table body names = AIdb.getManNames(aisql.getQueue()) table_body = E.TR() allcrit = AIdb.getCriteria(aisql.getQueue(), strip=False) colspan = str(max(len(list(allcrit)), 1)) for manifest in names: # iterate through each manifest (and instance) for instance in range( 0, AIdb.numInstances(manifest, aisql.getQueue())): table_body.append(E.TR()) # print the manifest name only once (from instance 0) if instance == 0: href = '../' + service + '/' + manifest row = str(AIdb.numInstances(manifest, aisql.getQueue())) table_body.append( E.TD(E.A(manifest, href=href, rowspan=row))) else: table_body.append(E.TD()) crit_pairs = AIdb.getManifestCriteria(manifest, instance, aisql.getQueue(), onlyUsed=True, humanOutput=True) # crit_pairs is an SQLite3 row object which doesn't # support iteritems(), etc. for crit in crit_pairs.keys(): formatted_val = AIdb.formatValue( crit, crit_pairs[crit]) # if we do not get back a valid value ensure a # hyphen is printed (prevents "" from printing) if formatted_val and crit_pairs[crit]: table_body.append( E.TD(formatted_val, align="center")) else: table_body.append( E.TD(lxml.etree.Entity("nbsp"), align="center")) # print the default manifest at the end of the table, # which has the same colspan as the Criteria List label else: href = '../' + service + '/default.xml' table_body.append( E.TR( E.TD(E.A("Default", href=href)), E.TD(lxml.etree.Entity("nbsp"), colspan=colspan, align="center"))) web_page = E.HTML( E.HEAD(E.TITLE(_("OmniOS Automated " "Installation Webserver"))), E.BODY( E.H1( _("Welcome to the OmniOS " "Automated Installation webserver!")), E.P( _("Service '%s' has the following " "manifests available, served to clients " "matching required criteria.") % service), E.TABLE(E.TR(E.TH(_("Manifest"), rowspan="2"), E.TH(_("Criteria List"), colspan=colspan)), criteria_header, table_body, border="1", align="center"), )) print lxml.etree.tostring(web_page, pretty_print=True) # service is not found, provide available services on host if not found: sys.stdout.write(_('Service <i>%s</i> not found. ') % service) sys.stdout.write(_('Available services are:<p><ol><i>')) host = socket.gethostname() for service_name in config.get_all_service_names(): # assume new service setup port = config.get_service_port(service_name) sys.stdout.write( '<a href="http://%s:%d/cgi-bin/' 'cgi_get_manifest.py?version=%s&service=%s">%s</a><br>\n' % (host, port, VERSION, service_name, service_name)) sys.stdout.write('</i></ol>%s' % _('Please select a service ' 'from the above list.')) print '</body></html>'
def get_mfest_or_profile_criteria(sname, services, dbtable): """ Iterate through all the manifests or profiles for the named service (sname) pointed to by the SCF service. Args sname = service name services = config.get_all_service_props() dbtable = database table, distinguishing manifests from profiles Assumed to be one of AIdb.MANIFESTS_TABLE or AIdb.PROFILES_TABLE Returns a dictionary of the criteria for the named service within a list: { servicename1:[ { 'arch':arch1, 'mem':memory1, 'ipv4':ipaddress1, 'mac':macaddr1, 'platform':platform1, 'network':network1, 'cpu':cpu1, 'zonename':z1 }, ... ] } * Note1: platform, network and cpu are currently not-implemented upstream. * Note2: could simply use a list of dictionaries but implemented as a dictionary of a list of dictionary which will allow for multiple services to be listed at the same time. width of longest manifest or profile name width of longest criteria Raises None """ sdict = dict() width = 0 cwidth = 0 # ensure the named service is in our service dictionary. lservices = services.keys() if sname in lservices: try: path = AIService(sname).database_path except VersionError as version_err: warn_version(version_err) return sdict, width, cwidth if os.path.exists(path): try: maisql = AIdb.DB(path) maisql.verifyDBStructure() aiqueue = maisql.getQueue() if dbtable == AIdb.MANIFESTS_TABLE: for name in AIdb.getNames(aiqueue, dbtable): sdict[name] = list() instances = AIdb.numInstances(name, aiqueue) for instance in range(0, instances): width = max(len(name), width) criteria = AIdb.getManifestCriteria( name, instance, aiqueue, humanOutput=True, onlyUsed=True) if criteria: tdict, twidth = get_criteria_info(criteria) cwidth = max(twidth, cwidth) sdict[name].append(tdict) elif dbtable == AIdb.PROFILES_TABLE: for name in AIdb.getNames(aiqueue, dbtable): sdict[name] = list() criteria = AIdb.getProfileCriteria(name, aiqueue, humanOutput=True, onlyUsed=True) width = max(len(name), width) tdict, twidth = get_criteria_info(criteria) cwidth = max(twidth, cwidth) sdict[name].append(tdict) else: raise ValueError("Invalid value for dbtable: %s" % dbtable) except StandardError as err: sys.stderr.write( _('Error: AI database access error\n%s\n') % err) sys.exit(1) else: sys.stderr.write( _('Error: unable to locate AI database on server ' 'for %s\n') % sname) sys.exit(1) return sdict, width, cwidth
def delete_manifest_from_db(db, manifest_instance, service_name, data_loc): """ Remove manifest from DB """ instance = manifest_instance[1] # check to see that the manifest is found in the database (as entered) if manifest_instance[0] not in AIdb.getManNames(db.getQueue()): # since all manifest names have to have .xml appended try adding that if manifest_instance[0] + '.xml' in AIdb.getManNames(db.getQueue()): man_name = manifest_instance[0] + '.xml' else: raise SystemExit(_("Error:\tManifest %s not found in database!" % manifest_instance[0])) else: man_name = manifest_instance[0] service = AIService(service_name) # Do not delete if this manifest is set up as the default. if man_name == service.get_default_manifest(): raise ValueError(_("Error:\tCannot delete default manifest %s.") % man_name) # if we do not have an instance remove the entire manifest if instance is None: # remove manifest from database query = AIdb.DBrequest("DELETE FROM manifests WHERE name = '%s'" % AIdb.sanitizeSQL(man_name), commit=True) db.getQueue().put(query) query.waitAns() # run getResponse to handle and errors query.getResponse() # clean up file on file system try: os.remove(os.path.join(service.manifest_dir, man_name)) except OSError: print >> sys.stderr, _("Warning:\tUnable to find file %s for " + "removal!") % man_name # we are removing a specific instance else: # check that the instance number is within bounds for that manifest # (0..numInstances) if instance > AIdb.numInstances(man_name, db.getQueue()) or \ instance < 0: raise SystemExit(_("Error:\tManifest %(name)s has %(num)i " "instances" % {'name': man_name, 'num': AIdb.numInstances(man_name, db.getQueue())})) # remove instance from database query = ("DELETE FROM manifests WHERE name = '%s' AND " "instance = '%i'") % (AIdb.sanitizeSQL(man_name), instance) query = AIdb.DBrequest(query, commit=True) db.getQueue().put(query) query.waitAns() # run getResponse to handle and errors query.getResponse() # We may need to reshuffle manifests to prevent gaps in instance # numbering as the DB routines expect instances to be contiguous and # increasing. We may have removed an instance with instances numbered # above thus leaving a gap. # get the number of instances with a larger instance for num in range(instance, AIdb.numInstances(man_name, db.getQueue()) + 1): # now decrement the instance number query = ("UPDATE manifests SET instance = '%i' WHERE " "name = '%s' ") % (num - 1, AIdb.sanitizeSQL(man_name)) query += "AND instance = '%i'" % num query = AIdb.DBrequest(query, commit=True) db.getQueue().put(query) query.waitAns() # run getResponse to handle and errors query.getResponse() # remove file if manifest is no longer in database if man_name not in AIdb.getManNames(db.getQueue()): try: os.remove(os.path.join(service.manifest_dir, man_name)) except OSError: print >> sys.stderr, _("Warning: Unable to find file %s for " + "removal!") % man_name
def print_service_manifests(sdict, sname, width, swidth, cwidth): """ Iterates over the criteria dictionary printing each non blank criteria. The manifest dictionary is populated via get_mfest_or_profile_criteria(). Args sdict = criteria dictionary (same as in get_mfest_or_profile_criteria() description) sname = name of service width = widest manifest name swidth = width of status column cwidth = widest criteria name (0 if no criteria) Returns None Raises None """ default_mfest = None inactive_mfests = list() active_mfests = list() width += 1 swidth += 1 mnames = sdict.keys() if not mnames: return mnames.sort() try: default_mname = AIService(sname).get_default_manifest() except StandardError: default_mname = "" ordered_keys = ['arch', 'mac', 'ipv4'] if cwidth > 0: # Criteria are present. keys = sdict[mnames[0]][0].keys() keys.sort() for akey in keys: if akey not in ordered_keys: ordered_keys.append(akey) for name in mnames: manifest_list = [name] if cwidth > 0: for ldict in sdict[name]: for akey in ordered_keys: if akey in ldict and ldict[akey] != '': manifest_list.append( akey.ljust(cwidth) + ' = ' + ldict[akey]) if name == default_mname: default_mfest = manifest_list elif len(manifest_list) == 1: inactive_mfests.append(manifest_list) else: active_mfests.append(manifest_list) for mfest in active_mfests: # Active manifests have at least one criterion. print mfest[0].ljust(width) + ''.ljust(swidth) + mfest[1] for other_crit in range(2, len(mfest)): print ' '.ljust(width + swidth) + mfest[other_crit] print if default_mfest: # Since 'Default' is used in status column, it is in STATUS_WORDS # and so swidth accommodates it. first_line = default_mfest[0].ljust(width) + \ DEFAULT.ljust(swidth) if len(default_mfest) > 1: first_line += "(" + IGNORED + ": " + default_mfest[1] + ")" else: first_line += "None" print first_line for other_crit in range(2, len(default_mfest)): print ''.ljust(width + swidth) + \ "(" + IGNORED + ": " + default_mfest[other_crit] + ")" print for mfest in inactive_mfests: # Since 'Inactive' is used in status column, it is in STATUS_WORDS. # and so swidth accommodates it. print mfest[0].ljust(width) + INACTIVE.ljust(swidth) + \ _("None") print