def remove_client_dhcp_config(client_id): ''' If a local DHCP server is running, remove any client configuration for this client from its configuration. If not, inform end-user that the client-service binding should no longer be referenced in the DHCP configuration. ''' server = dhcp.DHCPServer() if server.is_configured(): # A local DHCP server is configured. Check for a host entry and remove # it if found. mac_address = client_id[2:] mac_address = AIdb.formatValue('mac', mac_address) if server.host_is_configured(mac_address): print cw(_("Removing host entry '%s' from local DHCP " "configuration.") % mac_address) server.remove_host(mac_address) if server.is_online(): try: server.control('restart') except dhcp.DHCPServerError as err: print >> sys.stderr, cw(_("Unable to restart the DHCP " "SMF service: %s" % err)) return else: # No local DHCP configuration, inform user that it needs to be # unconfigured elsewhere. print cw(_("No local DHCP configuration found. Unless it will be " "reused, the bootfile '%s' may be removed from the DHCP " "configuration\n" % client_id))
def remove_client_dhcp_config(client_id): ''' If a local DHCP server is running, remove any client configuration for this client from its configuration. If not, inform end-user that the client-service binding should no longer be referenced in the DHCP configuration. ''' server = dhcp.DHCPServer() if server.is_configured(): # A local DHCP server is configured. Check for a host entry and remove # it if found. mac_address = client_id[2:] mac_address = AIdb.formatValue('mac', mac_address) if server.host_is_configured(mac_address): print cw( _("Removing host entry '%s' from local DHCP " "configuration.") % mac_address) server.remove_host(mac_address) if server.is_online(): try: server.control('restart') except dhcp.DHCPServerError as err: print >> sys.stderr, cw( _("Unable to restart the DHCP " "SMF service: %s" % err)) return else: # No local DHCP configuration, inform user that it needs to be # unconfigured elsewhere. print cw( _("No local DHCP configuration found. Unless it will be " "reused, the bootfile '%s' may be removed from the DHCP " "configuration\n" % client_id))
def create_new_client(arch, service, mac_address, bootargs=None): '''Create a new client of a service and ensure the Automated Install SMF service is enabled. Input: arch - architecture of service ('i386' or 'sparc') service - The AIService to attach to mac_address - mac address of client bootargs - boot arguments to insert in client menu.lst file (x86) Returns: Nothing ''' logging.debug("creating new client for service %s, mac %s, " "arch %s, bootargs %s", service.name, mac_address, arch, bootargs) if arch == 'i386': clientctrl.setup_x86_client(service, mac_address, bootargs=bootargs) else: clientctrl.setup_sparc_client(service, mac_address) # If the installation service this client is being created for # is not enabled, print warning to the user. if not config.is_enabled(service.name): logging.debug("service is disabled: %s", service.name) print cw(_("\nWarning: the installation service, %s, is disabled. " "To enable it, use 'installadm enable %s'.") % (service.name, service.name))
def _register_a_service(self, name, interfaces=None, port=0, comments=None): '''Method: _register_a_service, private to class Description: Register a single service on the interfaces Args interfaces - the interfaces to register the service on instance - the SMF service instance handle name - the service name to be registered port - the port that the service is listening on, if port is 0 then registering a service listed in the AI SMF service instance. comments - comments for the ad hoc registered service Returns list_sdrefs - list of service references Raises AImDNSError - if SMF status property does not exist, OR if SMF txt_record property does not exist, OR if SMF port property does not exist. ''' if not self.register_initialized: self.exclude = libaimdns.getboolean_property(common.SRVINST, common.EXCLPROP) self.networks = libaimdns.getstrings_property(common.SRVINST, common.NETSPROP) self.register_initialized = True smf_port = None # if port is 0 then processing an AI service if port == 0: serv = config.get_service_props(name) if not serv: raise AIMDNSError(cw(_('error: aiMDNSError: no such ' 'installation service "%s"') % name)) # ensure the service is enabled if config.PROP_STATUS not in serv: raise AIMDNSError(cw(_('error: aiMDNSError: installation ' 'service key "status" property does ' 'not exist'))) if serv[config.PROP_STATUS] != config.STATUS_ON: print(cw(_('warning: Installation service "%s" is not enabled ' % name))) return None smf_port = config.get_service_port(name) if not smf_port: try: smf_port = libaimdns.getinteger_property(common.SRVINST, common.PORTPROP) smf_port = str(smf_port) except libaimdns.aiMDNSError, err: raise AIMDNSError(cw(_('error: aiMDNSError: port property ' 'failure (%s)') % err))
def verify_key_properties(svcname, props): '''Verify key properties are present for a service Input: svcname - service name props - A dictionary of service properties to check Raises: ServiceCfgError if not all required service properties present ''' logging.log(com.XDEBUG, "*** START service_config.verify_key_properties ***") logging.log(com.XDEBUG, "svcname %s, props=%s", svcname, props) # verify that all required keys present missing = set() if PROP_SERVICE_NAME not in props.keys(): missing.add(PROP_SERVICE_NAME) else: prop_name = props[PROP_SERVICE_NAME] if prop_name != svcname: raise ServiceCfgError( cw( _("\nError: service name '%(name)s' does " "not match %(service)s property " "'%(prop_name)s'\n") % { 'name': svcname, 'service': PROP_SERVICE_NAME, 'prop_name': prop_name })) if PROP_STATUS not in props.keys(): missing.add(PROP_STATUS) if PROP_TXT_RECORD not in props.keys(): missing.add(PROP_TXT_RECORD) else: port = props[PROP_TXT_RECORD].partition(':')[1] if not port: raise ServiceCfgError( cw( _("\nError: Unable to determine service " "directory for service %s\n") % svcname)) if PROP_IMAGE_PATH not in props.keys(): if PROP_ALIAS_OF not in props.keys(): missing.add(PROP_IMAGE_PATH + _(' or ') + PROP_ALIAS_OF) if missing: raise ServiceCfgError( cw( _('\nError: installation service key ' 'properties missing for service %(name)s:' ' %(properties)s\n') % { 'name': svcname, 'properties': ', '.join(missing) }))
def print_local_services(sdict, width, awidth): """ Iterates over the local service dictionary and prints out service name, aliasof, status, architecture, and image path. All fields are left justified according to FDICT[field], width, awidth or simply printed in the case of path. The service dictionary looks like: { service1: [ { 'status':on1, 'path':path1, 'arch':arch1 }, ... ], ... } Args sdict = service dictionary width = length of longest service name awidth = length of longest aliasof service name Returns None Raises None """ tkeys = sdict.keys() tkeys.sort() missing_image = False for aservice in tkeys: firstone = True for info in sdict[aservice]: if firstone == True: print aservice.ljust(width), firstone = False else: print ' ' * width print info['aliasof'].ljust(awidth), print info['status'].ljust(FDICT['status']), print info['arch'].ljust(FDICT['arch']), # If the architecture can't be identified, either the image is # missing or not accessible. if info['arch'] == "*": missing_image = True print info['path'] if missing_image: print cw(ARCH_UNKNOWN) print
def print_local_services(sdict, width, awidth): """ Iterates over the local service dictionary and prints out service name, aliasof, status, architecture, and image path. All fields are left justified according to FDICT[field], width, awidth or simply printed in the case of path. The service dictionary looks like: { service1: [ { 'status':on1, 'path':path1, 'arch':arch1 }, ... ], ... } Args sdict = service dictionary width = length of longest service name awidth = length of longest aliasof service name Returns None Raises None """ tkeys = sdict.keys() tkeys.sort() missing_image = False for aservice in tkeys: firstone = True for info in sdict[aservice]: if firstone == True: print aservice.ljust(width), firstone = False else: print ' ' * width print info['aliasof'].ljust(awidth), print info['status'].ljust(FDICT['status']), print info['arch'].ljust(FDICT['arch']), # If the architecture can't be identified, either the image is # missing or not accessible. if info['arch'] == "*": missing_image = True print info['path'] if missing_image: print cw(ARCH_UNKNOWN) print
def set_imagepath(options): '''Change the location of a service's image''' logging.debug("set %s imagepath to %s", options.svcname, options.value) new_imagepath = options.value.strip() service = svc.AIService(options.svcname) if service.is_alias(): raise SystemExit(cw(_('\nError: Can not change the imagepath of an ' 'alias.'))) if not os.path.isabs(new_imagepath): raise SystemExit(_("\nError: A full pathname is required for the " "imagepath.\n")) if os.path.exists(new_imagepath): raise SystemExit(_("\nError: The imagepath already exists: %s\n") % new_imagepath) if os.path.islink(new_imagepath): raise SystemExit(_("\nError: The imagepath may not be a symlink.\n")) new_imagepath = new_imagepath.rstrip('/') try: service.relocate_imagedir(new_imagepath) except (svc.MountError, aismf.ServicesError) as error: raise SystemExit(error)
def _set_instance(state, wait_for): '''Set the install/server service to a given state, and wait for it to finish transitioning. state - desired state, e.g. 'MAINTENANCE' or 'RESTORE' wait_for - Function will block until the SMF state of the install/server instance is one of the values passed in this list, e.g. ['DISABLED', 'OFFLINE']. List items should be upper case. Raises ServicesError if the transition doesn't complete before MAX_WAIT_TIME seconds pass. ''' libaiscf.AISCF(FMRI=AI_SVC_FMRI).state = state.upper() # Wait a reasonable amount of time to confirm state change. wait_cnt = 0 while libaiscf.AISCF(FMRI=AI_SVC_FMRI).state.upper() not in wait_for: if wait_cnt >= MAX_WAIT_TIME: logging.debug("Wait time exceeded on attempt to move " "installadm SMF service to %s.", state.lower()) raise ServicesError(cw(_("Error: Failed to place %(svc)s in " "%(state)s. See the output of 'svcs " "-xv %(svc)s' for more information.") % {'svc': AI_SVC_FMRI, 'state': state.lower()})) else: time.sleep(1) wait_cnt += 1 logging.log(com.XDEBUG, "Time to set installadm SMF service to '%s' is %i" " seconds", state, wait_cnt)
def _start_tftpd(): '''Start the tftp/udp6 service, a dependency of installadm. If necessary, adjust the inetd_start/exec property to run tftp out of /etc/netboot. Raises ServicesError if tftp/udp6 is configured to use a different directory, and that directory exists and has files. ''' getprop = [SVCPROP, '-p', INET_START_PROP, TFTP_FMRI] svcprop_popen = Popen.check_call(getprop, stdout=Popen.STORE, stderr=Popen.STORE) inet_start = svcprop_popen.stdout.strip().split() if inet_start[-1] != com.BOOT_DIR: if (os.path.exists(inet_start[-1]) and os.path.isdir(inet_start[-1]) and os.listdir(inet_start[-1])): raise ServicesError(cw(_("The %(svc)s service has been configured " "to use the %(dir)s directory; " "installadm is incompatible with these " "settings. Please use svccfg to change " "the %(prop)s property of the %(svc)s " "service to migrate to the %(desired)s " "directory.") % {'svc': TFTP_FMRI, 'dir': inet_start[-1], 'desired': com.BOOT_DIR, 'prop': INET_START_PROP})) setprop = [SVCCFG, '-s', TFTP_FMRI, 'setprop', 'inetd_start/exec', '=', INET_START % com.BOOT_DIR] Popen.check_call(setprop) Popen.check_call([SVCADM, 'refresh', TFTP_FMRI]) Popen.check_call([SVCADM, 'enable', TFTP_FMRI])
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 _start_tftpd(): '''Start the tftp/udp6 service, a dependency of installadm. If necessary, adjust the inetd_start/exec property to run tftp out of /etc/netboot. Raises ServicesError if tftp/udp6 is configured to use a different directory, and that directory exists and has files. ''' getprop = [SVCPROP, '-p', INET_START_PROP, TFTP_FMRI] svcprop_popen = Popen.check_call(getprop, stdout=Popen.STORE, stderr=Popen.STORE) inet_start = svcprop_popen.stdout.strip().split() if inet_start[-1] != com.BOOT_DIR: if (os.path.exists(inet_start[-1]) and os.path.isdir(inet_start[-1]) and os.listdir(inet_start[-1])): raise ServicesError(cw(_("The %(svc)s service has been configured " "to use the %(dir)s directory; " "installadm is incompatible with these " "settings. Please use svccfg to change " "the %(prop)s property of the %(svc)s " "service to migrate to the %(desired)s " "directory.") % {'svc': TFTP_FMRI, 'dir': inet_start[-1], 'desired': com.BOOT_DIR, 'prop': INET_START_PROP})) setprop = [SVCCFG, '-s', TFTP_FMRI, 'setprop', 'inetd_start/exec', '=', INET_START % com.BOOT_DIR] Popen.check_call(setprop) Popen.check_call([SVCADM, 'refresh', TFTP_FMRI]) Popen.check_call([SVCADM, 'enable', TFTP_FMRI])
def _set_instance(state, wait_for): '''Set the install/server service to a given state, and wait for it to finish transitioning. state - desired state, e.g. 'MAINTENANCE' or 'RESTORE' wait_for - Function will block until the SMF state of the install/server instance is one of the values passed in this list, e.g. ['DISABLED', 'OFFLINE']. List items should be upper case. Raises ServicesError if the transition doesn't complete before MAX_WAIT_TIME seconds pass. ''' libaiscf.AISCF(FMRI=AI_SVC_FMRI).state = state.upper() # Wait a reasonable amount of time to confirm state change. wait_cnt = 0 while libaiscf.AISCF(FMRI=AI_SVC_FMRI).state.upper() not in wait_for: if wait_cnt >= MAX_WAIT_TIME: logging.debug("Wait time exceeded on attempt to move " "installadm SMF service to %s.", state.lower()) raise ServicesError(cw(_("Error: Failed to place %(svc)s in " "%(state)s. See the output of 'svcs " "-xv %(svc)s' for more information.") % {'svc': AI_SVC_FMRI, 'state': state.lower()})) else: time.sleep(1) wait_cnt += 1 logging.log(com.XDEBUG, "Time to set installadm SMF service to '%s' is %i" " seconds", state, wait_cnt)
def print_clients(width, sdict): """ Iterates over a dictionary of service clients printing information about each one. Args width = widest service name sdict = service dictionary of clients (same as in get_clients() description) Returns None Raises None """ # sort the keys so that the service names are in alphabetic order. tkeys = sdict.keys() tkeys.sort() missing_image = False for aservice in tkeys: service_firstone = True for aclient in sdict[aservice]: if service_firstone == True: print aservice.ljust(width), service_firstone = False else: print ' ' * width, print aclient['client'].ljust(FDICT['cadd']), print aclient['arch'].ljust(FDICT['arch']), # If the architecture can't be identified, either the image is # missing or not accessible. if aclient['arch'] == '*': missing_image = True path_firstone = True cpaths = list() for cpath in aclient['ipath']: if cpath not in cpaths: if path_firstone == False: spaces = width + FDICT['cadd'] + FDICT['arch'] + 2 print ' '.ljust(spaces), else: path_firstone = False print cpath cpaths.insert(0, cpath) if missing_image: print cw(ARCH_UNKNOWN)
def print_clients(width, sdict): """ Iterates over a dictionary of service clients printing information about each one. Args width = widest service name sdict = service dictionary of clients (same as in get_clients() description) Returns None Raises None """ # sort the keys so that the service names are in alphabetic order. tkeys = sdict.keys() tkeys.sort() missing_image = False for aservice in tkeys: service_firstone = True for aclient in sdict[aservice]: if service_firstone == True: print aservice.ljust(width), service_firstone = False else: print ' ' * width, print aclient['client'].ljust(FDICT['cadd']), print aclient['arch'].ljust(FDICT['arch']), # If the architecture can't be identified, either the image is # missing or not accessible. if aclient['arch'] == '*': missing_image = True path_firstone = True cpaths = list() for cpath in aclient['ipath']: if cpath not in cpaths: if path_firstone == False: spaces = width + FDICT['cadd'] + FDICT['arch'] + 2 print ' '.ljust(spaces), else: path_firstone = False print cpath cpaths.insert(0, cpath) if missing_image: print cw(ARCH_UNKNOWN)
def set_aliasof(options): '''Change a service's base service''' logging.debug("set alias %s's basesvc to %s", options.svcname, options.value) basesvcname = options.value aliasname = options.svcname if not config.is_service(basesvcname): raise SystemExit( _('\nError: Service does not exist: %s\n') % basesvcname) if aliasname == basesvcname: raise SystemExit( _('\nError: Alias name same as service name: %s\n') % aliasname) aliassvc = svc.AIService(aliasname) if not aliassvc.is_alias(): raise SystemExit( _('\nError: Service exists, but is not an ' 'alias: %s\n') % aliasname) basesvc_arch = svc.AIService(basesvcname).arch aliassvc_arch = aliassvc.arch if basesvc_arch != aliassvc_arch: raise SystemExit( _("\nError: Architectures of service and alias " "are different.\n")) if aliassvc.is_aliasof(basesvcname): raise SystemExit( _("\nError: %s is already an alias of %s\n") % (aliasname, basesvcname)) if svc.AIService(basesvcname).is_alias(): raise SystemExit(_("\nError: Cannot alias to another alias.\n")) # Make sure we aren't creating inter dependencies all_aliases = config.get_aliased_services(aliasname, recurse=True) if basesvcname in all_aliases: raise SystemExit( cw( _("\nError: %s can not be made an alias of %s " "because %s is dependent on %s\n") % (aliasname, basesvcname, basesvcname, aliasname))) try: aliassvc.update_basesvc(basesvcname) except (OSError, config.ServiceCfgError) as err: raise SystemExit( _("Failed to set 'aliasof' property of : %s") % aliasname) except svc.MultipleUnmountError as err: print >> sys.stderr, _("Failed to disable alias") raise SystemExit(err) except svc.MountError as err: print >> sys.stderr, _("Failed to enable alias") raise SystemExit(err) except svc.UnsupportedAliasError as err: raise SystemExit(err)
def parse_options(cmd_options=None): '''Parse and validate options Args: Optional cmd_options, used for unit testing. Otherwise, cmd line options handled by OptionParser Returns: options object ''' usage = '\n' + get_usage() parser = OptionParser(usage=usage) parser.add_option('-s', '--source', dest='srcimage', help=_('FMRI')) parser.add_option( '-p', '--publisher', help=_("A pkg(5) publisher, in " "the form '<prefix>=<uri>', from which to update the " "image. Specified publisher becomes the sole publisher " "of the updated image.")) # Get the parsed arguments using parse_args() options, args = parser.parse_args(cmd_options) # Confirm service name was passed in if not args: parser.error(_("Missing required argument, <svcname>")) elif len(args) > 1: parser.error(_("Too many arguments: %s") % args) options.service_name = args[0] # ensure service exists if not config.is_service(options.service_name): raise SystemExit( _("\nError: The specified service does " "not exist: %s\n") % options.service_name) service = svc.AIService(options.service_name, image_class=InstalladmPkgImage) if not service.is_alias(): raise SystemExit( cw( _("Error: '%s' is not an alias. The target of " "'update-service' must be an alias.") % service.name)) options.service = service if options.publisher is not None: # Convert options.publisher from a string of form 'prefix=uri' to a # tuple (prefix, uri) and strip each entry publisher = map(str.strip, options.publisher.split("=")) if len(publisher) != 2: parser.error( _('Publisher information must match the form: ' '"<prefix>=<URI>"')) options.publisher = publisher 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 register(self, servicename=None, port=0, interfaces=None, comments=None): '''Method: register Description: Registers an ad hoc service. This method will loop until the the application is killed. Args servicename - the name of the ad hoc service port - the port to use for the ad hoc service interfaces - the interfaces to register the ad hoc service on comments - the service comments for the ad hoc service Returns None Raises SystemError - if the SMF service instance can not be loaded. AImDNSError - if unable to register the service OR if no servicename is present. ''' self._do_lookup = False if servicename is not None: self.servicename = servicename if self.servicename is None: raise ValueError(_('must specify a service to register')) if self.verbose: print _('Registering "%s"...') % self.servicename # get the AI SMF service instance information try: self.instance = smf.AISCF(FMRI="system/install/server") except SystemError: raise SystemError(_("error: the system does not have the " "system/install/server SMF service")) # use the interfaces within the class if none are passed in if interfaces is None: interfaces = self.interfaces sdrefs = self._register_a_service(name=self.servicename, interfaces=interfaces, port=port, comments=comments) if sdrefs is not None: self.sdrefs[servicename] = sdrefs self._handle_events() else: raise AIMDNSError(cw(_('error: aiMDNSError: mDNS ad hoc ' 'registration failed for "%s" service') % self.servicename))
def verify_key_properties(svcname, props): '''Verify key properties are present for a service Input: svcname - service name props - A dictionary of service properties to check Raises: ServiceCfgError if not all required service properties present ''' logging.log(com.XDEBUG, "*** START service_config.verify_key_properties ***") logging.log(com.XDEBUG, "svcname %s, props=%s", svcname, props) # verify that all required keys present missing = set() if PROP_SERVICE_NAME not in props.keys(): missing.add(PROP_SERVICE_NAME) else: prop_name = props[PROP_SERVICE_NAME] if prop_name != svcname: raise ServiceCfgError(cw(_("\nError: service name '%s' does not " "match %s property '%s'\n") % (svcname, PROP_SERVICE_NAME, prop_name))) if PROP_STATUS not in props.keys(): missing.add(PROP_STATUS) if PROP_TXT_RECORD not in props.keys(): missing.add(PROP_TXT_RECORD) else: port = props[PROP_TXT_RECORD].partition(':')[1] if not port: raise ServiceCfgError(cw(_("\nError: Unable to determine service " "directory for service %s\n") % svcname)) if PROP_IMAGE_PATH not in props.keys(): if PROP_ALIAS_OF not in props.keys(): missing.add(PROP_IMAGE_PATH + _(' or ') + PROP_ALIAS_OF) if missing: raise ServiceCfgError(cw(_('\nError: installation service key ' 'properties missing for service %s: %s\n') % (svcname, ', '.join(missing))))
def verify(self): ''' Check that the image directory exists, appears to be a valid net boot image (has a solaris.zlib file), and is a valid Automated Installer image (has an auto_install/ai.dtd file). Raises: ImageError if path checks fail Pre-conditions: Expects self.path to return a valid image_path Returns: None ''' # check image_path exists if not os.path.isdir(self.path): raise ImageError(cw(_("\nError:\tThe image path (%s) is not " "a directory. Please provide a " "different image path.\n") % self.path)) # check that the image_path has solaris.zlib and # auto_install/ai.dtd files if not (os.path.exists(os.path.join(self.path, "solaris.zlib")) and os.path.exists(os.path.join(self.path, "auto_install/ai.dtd"))): raise ImageError(cw(self.INVALID_AI_IMAGE % {"path": self.path}))
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 set_aliasof(options): '''Change a service's base service''' logging.debug("set alias %s's basesvc to %s", options.svcname, options.value) basesvcname = options.value aliasname = options.svcname if not config.is_service(basesvcname): raise SystemExit(_('\nError: Service does not exist: %s\n') % basesvcname) if aliasname == basesvcname: raise SystemExit(_('\nError: Alias name same as service name: %s\n') % aliasname) aliassvc = svc.AIService(aliasname) if not aliassvc.is_alias(): raise SystemExit(_('\nError: Service exists, but is not an ' 'alias: %s\n') % aliasname) basesvc_arch = svc.AIService(basesvcname).arch aliassvc_arch = aliassvc.arch if basesvc_arch != aliassvc_arch: raise SystemExit(_("\nError: Architectures of service and alias " "are different.\n")) if aliassvc.is_aliasof(basesvcname): raise SystemExit(_("\nError: %s is already an alias of %s\n") % (aliasname, basesvcname)) if svc.AIService(basesvcname).is_alias(): raise SystemExit(_("\nError: Cannot alias to another alias.\n")) # Make sure we aren't creating inter dependencies all_aliases = config.get_aliased_services(aliasname, recurse=True) if basesvcname in all_aliases: raise SystemExit(cw(_("\nError: %s can not be made an alias of %s " "because %s is dependent on %s\n") % (aliasname, basesvcname, basesvcname, aliasname))) try: aliassvc.update_basesvc(basesvcname) except (OSError, config.ServiceCfgError) as err: raise SystemExit(_("Failed to set 'aliasof' property of : %s") % aliasname) except svc.MultipleUnmountError as err: print >> sys.stderr, _("Failed to disable alias") raise SystemExit(err) except svc.MountError as err: print >> sys.stderr, _("Failed to enable alias") raise SystemExit(err) except svc.UnsupportedAliasError as err: raise SystemExit(err)
def remove_dhcp_configuration(service): ''' Determines if a local DHCP server is running and if this service's bootfile is set as the architecture's default boot service. If it is, we'll unset it as we're deleting the service. If the DHCP configuration isn't local, inform the end-user that the DHCP configuration should not reference this bootfile any longer. ''' server = dhcp.DHCPServer() if server.is_configured(): # Server is configured. Regardless of it's current state, check for # this bootfile in the service's architecture class. If it is set as # the default for this architecture, unset it. try: arch_class = dhcp.DHCPArchClass.get_arch(server, service.arch) except dhcp.DHCPServerError as err: print >> sys.stderr, cw(_("\nUnable to access DHCP configuration: " "%s\n" % err)) return # Skip SPARC services, since they share a global bootfile if (service.arch != 'sparc' and arch_class is not None and arch_class.bootfile == service.dhcp_bootfile): try: print cw(_("Removing this service's bootfile from local DHCP " "configuration\n")) arch_class.unset_bootfile() except dhcp.DHCPServerError as err: print >> sys.stderr, cw(_("\nUnable to unset this service's " "bootfile in the DHCP " "configuration: %s\n" % err)) return if server.is_online(): try: server.control('restart') except dhcp.DHCPServerError as err: print >> sys.stderr, cw(_("\nUnable to restart the DHCP " "SMF service: %s\n" % err))
def verify(self): ''' Check that the image directory exists, appears to be a valid net boot image (has a solaris.zlib file), and is a valid Automated Installer image (has an auto_install/ai.dtd file). Raises: ImageError if path checks fail Pre-conditions: Expects self.path to return a valid image_path Returns: None ''' # check image_path exists if not os.path.isdir(self.path): raise ImageError( cw( _("\nError:\tThe image path (%s) is not " "a directory. Please provide a " "different image path.\n") % self.path)) # check that the image_path has solaris.zlib and # auto_install/ai.dtd files if not (os.path.exists(os.path.join(self.path, "solaris.zlib")) and os.path.exists( os.path.join(self.path, "auto_install/ai.dtd"))): raise ImageError(cw(self.INVALID_AI_IMAGE % {"path": self.path}))
def set_aliasof(options): '''Change a service's base service''' logging.debug("set alias %s's basesvc to %s", options.svcname, options.value) basesvcname = options.value aliasname = options.svcname if not config.is_service(basesvcname): raise SystemExit(_('\nError: Service does not exist: %s\n') % basesvcname) if aliasname == basesvcname: raise SystemExit(_('\nError: Alias name same as service name: %s\n') % aliasname) aliassvc = svc.AIService(aliasname) if not aliassvc.is_alias(): raise SystemExit(_('\nError: Service exists, but is not an ' 'alias: %s\n') % aliasname) basesvc_arch = svc.AIService(basesvcname).arch aliassvc_arch = aliassvc.arch if basesvc_arch != aliassvc_arch: raise SystemExit(_("\nError: Architectures of service and alias " "are different.\n")) if aliassvc.is_aliasof(basesvcname): raise SystemExit(_("\nError: %(aliasname)s is already an alias " "of %(svcname)s\n") % {'aliasname': aliasname, 'svcname': basesvcname}) if svc.AIService(basesvcname).is_alias(): raise SystemExit(_("\nError: Cannot alias to another alias.\n")) # Make sure we aren't creating inter dependencies all_aliases = config.get_aliased_services(aliasname, recurse=True) if basesvcname in all_aliases: raise SystemExit(cw(_("\nError: %(aliasname)s can not be made an " "alias of %(svcname)s because %(svcname)s is " "dependent on %(aliasname)s\n") % {'aliasname': aliasname, 'svcname': basesvcname})) failures = do_update_basesvc(aliassvc, basesvcname) if failures: return 1 return 0
def maintain_instance(): ''' Move the Automated Installer SMF service to the maintenance state. This function is roughly analogous to smf_maintain_instance(3SCF) Input: None Return: None Raises: ServicesError if service fails to transition to 'MAINTENANCE' within reasonable time. Whatever exceptions AISCF encounters ''' logging.log(com.XDEBUG, '**** START ai_smf_service.maintain_instance ****') _set_instance('MAINTENANCE', ('MAINTENANCE',)) sys.stderr.write(cw(_("The installadm SMF service is no longer online " "because the last install service has been disabled " "or deleted.\n")))
def set_service_props(service_name, props): ''' Set the property values for a specified service name. Input: service_name - An AI service name props - A dictionary of properties to update for the specified service_name. Raises: ServiceCfgError if config file does not exist for the service (to create it, use create_service_props()) ''' logging.log( com.XDEBUG, '**** START service_config.set_service_props ' 'service_name=%s ****', service_name) if not os.path.exists(_get_configfile_path(service_name)): raise ServiceCfgError( cw( _('\nError: attempting to set service ' 'properties, but config file does not exist for "%s"') % service_name)) _write_service_config(service_name, props)
def maintain_instance(): ''' Move the Automated Installer SMF service to the maintenance state. This function is roughly analogous to smf_maintain_instance(3SCF) Input: None Return: None Raises: ServicesError if service fails to transition to 'MAINTENANCE' within reasonable time. Whatever exceptions AISCF encounters ''' logging.log(com.XDEBUG, '**** START ai_smf_service.maintain_instance ****') _set_instance('MAINTENANCE', ('MAINTENANCE',)) sys.stderr.write(cw(_("The installadm SMF service is no longer online " "because the last install service has been disabled " "or deleted.\n")))
def check_fmri(self, fmri): '''Calls pkg.client.api.ImageInterface.parse_fmri_patterns() to check if fmri is valid Input: fmri to check Returns: PkgFmri object Raises: ValueError if there is a problem with the fmri ''' for pattern, err, pfmri, matcher in \ self.pkg_image.parse_fmri_patterns(fmri): if err: if isinstance(err, pkg.version.VersionError): # For version errors, include the pattern so # that the user understands why it failed. print >> sys.stderr, \ cw(_("Illegal FMRI '%(patt)s': %(error)s" % {'patt': pattern, 'error': err})) raise ValueError(err) else: # Including the pattern is redundant for other # exceptions. raise ValueError(err) return pfmri
class AImDNS(object): ''' Class: AImDNS - base class for registering, browsing and looking up AI and ad hoc mDNS records. ''' # a _handle_event() loop control variable, used to restart the loop # after modification to the self.sdrefs variable, private _restart_loop = False # find/browse mode variables, private _do_lookup = False _found = False # mDNS record resolved variable, used as a stack to indicate that the # service has been found, private _resolved = list() def __init__(self, servicename=None, domain='local', comment=None): '''Method: __init__, class private Parameters: servicename - the AI servicename domain - the domain for the registered service comment - the text comment for the service Raises: AImDNSError - when unable to retrieve setup information from the host about the available interfaces or the AI SMF service. ''' gettext.install("ai", "/usr/lib/locale") # find sdref handle self._find = None self._lookup = False self.services = dict() self.servicename = servicename self.domain = domain self.txt = comment self.inter = None self.port = 0 self.verbose = False self.timeout = 5 self.done = False self.count = 0 self.sdrefs = dict() self.interfaces = libaimdns.getifaddrs() self.register_initialized = False self.exclude = False self.networks = ['0.0.0.0/0'] self.instance = None self.instance_services = None def __del__(self): '''Method: __del__ Parameters: None Raises: None ''' self.done = True self.clear_sdrefs() def _resolve_callback(self, sdref, flags, interfaceindex, errorcode, fullname, hosttarget, port, txtrecord): '''Method: _resolve_callback, class private Description: DNS Callback for the resolve process, stories the service information within the self.services variable. Args sdref - service reference, standard argument for callback, not used flags - flag to determine what action is taking place standard argument for callback, not used interfaceindex - the index for the interface that the service was found on errorcode - flag to determine if a registration error occurred fullname - name of the service, should be <service>._OSInstall._tcp.local. hosttarget - name of the host, should be <nodename>.local. port - the service port being used txtrecord - the text record associated with the service, standard argument for callback Returns None Raises None ''' # handle errors from within the _browse_callback # after the select() call if errorcode == pyb.kDNSServiceErr_NoError: self._found = True # get the interface name for the index interface = netif.if_indextoname(interfaceindex) # interested in the service name and the domain only parts = fullname.split('.') service = dict() service['flags'] = not (flags & pyb.kDNSServiceFlagsAdd) service['hosttarget'] = hosttarget service['servicename'] = parts[0] service['domain'] = parts[-2] service['port'] = port service['comments'] = str(pyb.TXTRecord.parse(txtrecord))[1:] self.services.setdefault(interface, list()).append(service) # update the resolve stack flag self._resolved.append(True) def _browse_callback(self, sdref, flags, interfaceindex, errorcode, servicename, regtype, replydomain): '''Method: _browse_callback, class private Description: DNS Callback for the browse process Args sdref - service reference, standard argument for callback, not used flags - flag to determine what action is taking place standard argument for callback, not used interfaceindex - the index for the interface that the service was found on errorcode - flag to determine if a registration error occurred servicename - name of the service hosttarget - name of the host, should be <nodename>.local. regtype - registration type, should be _OSInstall._tcp. replydomain - DNS domain, either local or remote Returns None Raises None ''' if errorcode != pyb.kDNSServiceErr_NoError: return # error handled in the _handle_event() method if self._lookup and servicename != self.servicename: return resolve_sdref = pyb.DNSServiceResolve(0, interfaceindex, servicename, regtype, replydomain, self._resolve_callback) # wait for and process resolve the current request try: while not self._resolved: try: ready = select.select([resolve_sdref], list(), list(), self.timeout) except select.error: # purposely ignore errors. continue if resolve_sdref not in ready[0]: # not a catastrophic error for the class, therefore, # simply warn that the mDNS service record needed # additional time to process and do not issue an # exception. sys.stderr.write( cw( _('warning: unable to resolve "%s", ' 'try using a longer timeout\n') % servicename)) break # process the service pyb.DNSServiceProcessResult(resolve_sdref) else: self._resolved.pop() # allow exceptions to fall through finally: # clean up when there is no exception resolve_sdref.close() def _handle_events(self): ''' Method: __handle_events, class private Description: Handle the event processing for the registered service requests. Args None Returns None Raises None ''' self.done = False while not self.done: # The self.sdrefs is a dictionary of the form: # # for the find mode: # { 'find':[list of sdrefs] } # # OR for the browse mode: # { 'browse':[list of sdrefs] } # # OR for the register mode: # { <service-name>:[list of sdrefs] } # # OR for the register all mode: # { <service-name1>:[list of sdrefs], # <service-name2>:[list of sdrefs], # ... } # # This must be converted to a simple list of sdrefs for the # select() call. therefs = list() # iterate through the dictionary for srv in self.sdrefs: for refs in self.sdrefs.get(srv, list()): if refs is not None: therefs.append(refs) # loop until done or we need to redo the service reference # list mentioned above. The service reference list will be # updated when the SMF service is refreshed which sends a # SIGHUP to the application in daemon mode. This processing # of the SIGHUP is done in the signal_hup() method below. self._restart_loop = False count = 0 while not self._restart_loop and not self.done: try: # process the appropriate service reference try: ready = select.select(therefs, list(), list(), self.timeout) except select.error: continue # check to ensure that the __del__ method was not called # between the select and the DNS processing. if self.done: continue for sdref in therefs: if sdref in ready[0]: pyb.DNSServiceProcessResult(sdref) # if browse or find loop then loop only long enough to # ensure that all the registered mDNS records are # retrieved per interface configured if self._do_lookup is True: count += 1 if count >= self.count: self.done = True # <CTL>-C will exit the loop, application # needed for command line invocation except KeyboardInterrupt: self.done = True def _register_callback(self, sdref, flags, errorcode, name, regtype, domain): '''Method: _register_callback, private to class Description: DNS Callback for the registration process Args sdref - service reference standard argument for callback, not used flags - flag to determine what action is taking place standard argument for callback, not used errorcode - flag to determine if a registration error occurred name - name of the service regtype - registration type, should be _OSInstall._tcp. domain - DNS domain, either local or remote Returns None Raises None ''' # note: DNSService Errors are ignored here and handled elsewhere. if errorcode == pyb.kDNSServiceErr_NoError and \ self.verbose: print _('Registered service:') print _('\tname = %s') % name print _('\tregtype = %s') % regtype print _('\tdomain = %s') % domain def _register_a_service(self, name, interfaces=None, port=0, comments=None): '''Method: _register_a_service, private to class Description: Register a single service on the interfaces Args interfaces - the interfaces to register the service on instance - the SMF service instance handle name - the service name to be registered port - the port that the service is listening on, if port is 0 then registering a service listed in the AI SMF service instance. comments - comments for the ad hoc registered service Returns list_sdrefs - list of service references Raises AImDNSError - if SMF status property does not exist, OR if SMF txt_record property does not exist, OR if SMF port property does not exist. ''' if not self.register_initialized: self.exclude = libaimdns.getboolean_property( common.SRVINST, common.EXCLPROP) self.networks = libaimdns.getstrings_property( common.SRVINST, common.NETSPROP) self.register_initialized = True smf_port = None # if port is 0 then processing an AI service if port == 0: serv = config.get_service_props(name) if not serv: raise AIMDNSError( cw( _('error: aiMDNSError: no such ' 'installation service "%s"') % name)) # ensure the service is enabled if config.PROP_STATUS not in serv: raise AIMDNSError( cw( _('error: aiMDNSError: installation ' 'service key "status" property does ' 'not exist'))) if serv[config.PROP_STATUS] != config.STATUS_ON: print( cw( _('warning: Installation service "%s" is not enabled ' % name))) return None smf_port = config.get_service_port(name) if not smf_port: try: smf_port = libaimdns.getinteger_property( common.SRVINST, common.PORTPROP) smf_port = str(smf_port) except libaimdns.aiMDNSError, err: raise AIMDNSError( cw( _('error: aiMDNSError: port property ' 'failure (%s)') % err)) # iterate over the interfaces saving the service references list_sdrefs = list() valid_networks = common.get_valid_networks() for inf in interfaces: include_it = False for ip in valid_networks: if interfaces[inf].startswith(ip): include_it = True break if not include_it: continue if self.verbose: print cw(_('Registering %s on %s (%s)') % \ (name, inf, interfaces[inf])) if smf_port is not None: # comments are part of the service record commentkey = serv[config.PROP_TXT_RECORD].split('=')[0] commenttxt = interfaces[inf].split('/')[0] + ':' + smf_port text = pyb.TXTRecord({commentkey: commenttxt}) try: port = int(smf_port) except ValueError: # not a catastrophic error, just # assume the default port of 5555. port = common.DEFAULT_PORT # processing an ad hoc registration elif comments is None: adhoc_dict = {'service': 'ad hoc registration'} text = pyb.TXTRecord(adhoc_dict) else: text = pyb.TXTRecord({'service': comments}) # register the service on the appropriate interface index try: interfaceindex = netif.if_nametoindex(inf) except netif.NetIFError, err: raise AIMDNSError(err) sdref = pyb.DNSServiceRegister(name=name, interfaceIndex=interfaceindex, regtype=common.REGTYPE, port=port, callBack=self._register_callback, txtRecord=text) # DNSServiceUpdateRecord will update the default record if # RecordRef is None. Time-to-live (ttl) for the record is being # set to 10 seconds. This value allows enough time for the # record to be looked up and it is short enough that when the # service is deleted then the mdns daemon will remove it from # the cache after this value expires but prior to another service # with the same name being created. pyb.DNSServiceUpdateRecord(sdRef=sdref, RecordRef=None, rdata=text, ttl=10) # save the registered service reference list_sdrefs.append(sdref)
def register(self, servicename=None, port=0, interfaces=None, comments=None): '''Method: register Description: Registers an ad hoc service. This method will loop until the the application is killed. Args servicename - the name of the ad hoc service port - the port to use for the ad hoc service interfaces - the interfaces to register the ad hoc service on comments - the service comments for the ad hoc service Returns None Raises SystemError - if the SMF service instance can not be loaded. AImDNSError - if unable to register the service OR if no servicename is present. ''' self._do_lookup = False if servicename is not None: self.servicename = servicename if self.servicename is None: raise ValueError(_('must specify a service to register')) if self.verbose: print _('Registering "%s"...') % self.servicename # get the AI SMF service instance information try: self.instance = smf.AISCF(FMRI="system/install/server") except SystemError: raise SystemError( _("error: the system does not have the " "system/install/server SMF service")) # use the interfaces within the class if none are passed in if interfaces is None: interfaces = self.interfaces sdrefs = self._register_a_service(name=self.servicename, interfaces=interfaces, port=port, comments=comments) if sdrefs is not None: self.sdrefs[servicename] = sdrefs self._handle_events() else: raise AIMDNSError( cw( _('error: aiMDNSError: mDNS ad hoc ' 'registration failed for "%s" service') % self.servicename))
def _register_a_service(self, name, interfaces=None, port=0, comments=None): '''Method: _register_a_service, private to class Description: Register a single service on the interfaces Args interfaces - the interfaces to register the service on instance - the SMF service instance handle name - the service name to be registered port - the port that the service is listening on, if port is 0 then registering a service listed in the AI SMF service instance. comments - comments for the ad hoc registered service Returns list_sdrefs - list of service references Raises AImDNSError - if SMF status property does not exist, OR if SMF txt_record property does not exist, OR if SMF port property does not exist. ''' if not self.register_initialized: self.exclude = libaimdns.getboolean_property( common.SRVINST, common.EXCLPROP) self.networks = libaimdns.getstrings_property( common.SRVINST, common.NETSPROP) self.register_initialized = True smf_port = None # if port is 0 then processing an AI service if port == 0: serv = config.get_service_props(name) if not serv: raise AIMDNSError( cw( _('error: aiMDNSError: no such ' 'installation service "%s"') % name)) # ensure the service is enabled if config.PROP_STATUS not in serv: raise AIMDNSError( cw( _('error: aiMDNSError: installation ' 'service key "status" property does ' 'not exist'))) if serv[config.PROP_STATUS] != config.STATUS_ON: print( cw( _('warning: Installation service "%s" is not enabled ' % name))) return None smf_port = config.get_service_port(name) if not smf_port: try: smf_port = libaimdns.getinteger_property( common.SRVINST, common.PORTPROP) smf_port = str(smf_port) except libaimdns.aiMDNSError, err: raise AIMDNSError( cw( _('error: aiMDNSError: port property ' 'failure (%s)') % err))
def _browse_callback(self, sdref, flags, interfaceindex, errorcode, servicename, regtype, replydomain): '''Method: _browse_callback, class private Description: DNS Callback for the browse process Args sdref - service reference, standard argument for callback, not used flags - flag to determine what action is taking place standard argument for callback, not used interfaceindex - the index for the interface that the service was found on errorcode - flag to determine if a registration error occurred servicename - name of the service hosttarget - name of the host, should be <nodename>.local. regtype - registration type, should be _OSInstall._tcp. replydomain - DNS domain, either local or remote Returns None Raises None ''' if errorcode != pyb.kDNSServiceErr_NoError: return # error handled in the _handle_event() method if self._lookup and servicename != self.servicename: return resolve_sdref = pyb.DNSServiceResolve(0, interfaceindex, servicename, regtype, replydomain, self._resolve_callback) # wait for and process resolve the current request try: while not self._resolved: try: ready = select.select([resolve_sdref], list(), list(), self.timeout) except select.error: # purposely ignore errors. continue if resolve_sdref not in ready[0]: # not a catastrophic error for the class, therefore, # simply warn that the mDNS service record needed # additional time to process and do not issue an # exception. sys.stderr.write( cw( _('warning: unable to resolve "%s", ' 'try using a longer timeout\n') % servicename)) break # process the service pyb.DNSServiceProcessResult(resolve_sdref) else: self._resolved.pop() # allow exceptions to fall through finally: # clean up when there is no exception resolve_sdref.close()
def _browse_callback(self, sdref, flags, interfaceindex, errorcode, servicename, regtype, replydomain): '''Method: _browse_callback, class private Description: DNS Callback for the browse process Args sdref - service reference, standard argument for callback, not used flags - flag to determine what action is taking place standard argument for callback, not used interfaceindex - the index for the interface that the service was found on errorcode - flag to determine if a registration error occurred servicename - name of the service hosttarget - name of the host, should be <nodename>.local. regtype - registration type, should be _OSInstall._tcp. replydomain - DNS domain, either local or remote Returns None Raises None ''' if errorcode != pyb.kDNSServiceErr_NoError: return # error handled in the _handle_event() method if self._lookup and servicename != self.servicename: return resolve_sdref = pyb.DNSServiceResolve(0, interfaceindex, servicename, regtype, replydomain, self._resolve_callback) # wait for and process resolve the current request try: while not self._resolved: try: ready = select.select([resolve_sdref], list(), list(), self.timeout) except select.error: # purposely ignore errors. continue if resolve_sdref not in ready[0]: # not a catastrophic error for the class, therefore, # simply warn that the mDNS service record needed # additional time to process and do not issue an # exception. sys.stderr.write(cw(_('warning: unable to resolve "%s", ' 'try using a longer timeout\n') % servicename)) break # process the service pyb.DNSServiceProcessResult(resolve_sdref) else: self._resolved.pop() # allow exceptions to fall through finally: # clean up when there is no exception resolve_sdref.close()
def parse_options(cmd_options=None): ''' Parse and validate options Returns: An options record containing arch aliasof bootargs dhcp_ip_count dhcp_ip_start dhcp_bootserver noprompt publisher srcimage svcname imagepath ''' logging.log(com.XDEBUG, '**** START installadm.create_service.' 'parse_options ****\n') usage = '\n' + get_usage() description = _('Establishes an Automated Install network service.') parser = OptionParser(usage=usage, prog="create-service", description=description) parser.add_option('-b', '--boot-args', dest='bootargs', action='append', default=list(), help=_('Comma separated list of <property>=<value>' ' pairs to add to the x86 Grub menu entry')) parser.add_option('-a', '--arch', dest='arch', default=None, choices=("i386", "sparc"), help=_("ARCHITECTURE (sparc or i386), desired " "architecture of resulting service when creating " "from a pkg.")) parser.add_option('-d', '--imagepath', dest='imagepath', default=None, help=_("Path at which to create the net image")) parser.add_option('-t', '--aliasof', dest='aliasof', default=None, help=_("Service being created is alias of this serivce")) parser.add_option('-n', '--service', dest='svcname', help=_('service name')) parser.add_option('-i', '--ip-start', dest='dhcp_ip_start', type='string', help=_('DHCP Starting IP Address'), action="callback", callback=check_ip_address) parser.add_option('-c', '--ip-count', dest='dhcp_ip_count', type='int', help=_('DHCP Count of IP Addresses')) parser.add_option('-B', '--bootfile-server', dest='dhcp_bootserver', type='string', help=_('DHCP Boot Server Address'), action="callback", callback=check_ip_address) parser.add_option('-s', '--source', dest='srcimage', type='string', help=_('FMRI or Auto Install ISO')) parser.add_option('-p', '--publisher', help=_("A pkg(5) publisher, in the" " form '<prefix>=<uri>', from which to install the " "client image")) parser.add_option('-y', "--noprompt", action="store_true", dest="noprompt", default=False, help=_('Suppress confirmation prompts and proceed with ' 'service creation using default values')) options, args = parser.parse_args(cmd_options) if args: parser.error(_('Unexpected argument(s): %s') % args) # if service name provided, validate it if options.svcname: try: com.validate_service_name(options.svcname) except ValueError as err: parser.error(err) # Give error if service already exists if config.is_service(options.svcname): parser.error(_('\nService already exists: %s\n') % options.svcname) # If creating an alias, only allow additional options -n, -b, # and -y if options.aliasof: if (options.dhcp_ip_start or options.dhcp_ip_count or options.imagepath or options.srcimage): parser.error(_('\nOnly options -n|--service, -b|--boot-args, ' 'and -y|--noprompt\nmay be specified with ' '-t|--aliasof.')) if not options.svcname: parser.error(_('\nOption -n|--service is required with the ' '-t|--aliasof option')) else: name = options.svcname if name in DEFAULT_ARCH: raise SystemExit(_('\nDefault services must be created as ' 'aliases. Use -t|--aliasof.\n')) # provide default for srcimage, now that we're done option checking if options.srcimage is None: options.srcimage = "pkg:/install-image/solaris-auto-install" # check dhcp related options if options.dhcp_ip_start or options.dhcp_ip_count: if com.is_multihomed(): # don't allow DHCP setup if multihomed parser.error(cw(_('\nDHCP server configuration is unavailable on ' 'hosts with multiple network interfaces (-i and ' '-c options are disallowed).\n'))) # Confirm options -i and -c are both provided if options.dhcp_ip_count is None: parser.error(_('\nIf -i option is provided, -c option must ' 'also be provided\n')) if not options.dhcp_ip_start: parser.error(_('\nIf -c option is provided, -i option must ' 'also be provided\n')) # Confirm count of ip addresses is positive if options.dhcp_ip_count < 1: parser.error(_('\n"-c <count_of_ipaddr>" must be greater than ' 'zero.\n')) if options.dhcp_bootserver: # Confirm if the -B is provided, that -i/-c are also if options.dhcp_ip_count is None: parser.error(_('\nIf -B option is provided, -i option must ' 'also be provided\n')) if is_iso(options.srcimage): if options.arch is not None: parser.error(_("The --arch option is invalid for ISO-based " "services")) if options.publisher is not None: parser.error(_("The --publisher option is invalid for " "ISO-based services")) if options.publisher: # Convert options.publisher from a string of form 'prefix=uri' to a # tuple (prefix, uri) publisher = options.publisher.split("=") if len(publisher) != 2: parser.error(_('Publisher information must match the form: ' '"<prefix>=<URI>"')) options.publisher = publisher # Make sure imagepath meets requirements if options.imagepath: options.imagepath = options.imagepath.strip() if options.imagepath: if not options.imagepath == '/': options.imagepath = options.imagepath.rstrip('/') try: check_imagepath(options.imagepath) except ValueError as error: raise SystemExit(error) return options
def remove_dhcp_configuration(service): ''' Determines if a local DHCP server is running and if this service's bootfile is set as the architecture's default boot service. If it is, we'll unset it as we're deleting the service. If the DHCP configuration isn't local, inform the end-user that the DHCP configuration should not reference this bootfile any longer. ''' logging.debug("in remove_dhcp_configuration, service=%s", service.name) # Skip SPARC services, since they share a global bootfile if service.arch == 'sparc': return server = dhcp.DHCPServer() if server.is_configured(): # Server is configured. Regardless of its current state, check for # this bootfile in the service's architecture class. If it is set as # the default for this architecture, unset it. try: arch_class = dhcp.DHCPArchClass.get_arch(server, service.arch) except dhcp.DHCPServerError as err: print >> sys.stderr, cw( _("\nUnable to access DHCP configuration: " "%s\n" % err)) return if arch_class is None or arch_class.bootfile is None: # nothing to do return logging.debug("arch_class.bootfile is %s", arch_class.bootfile) if isinstance(arch_class.bootfile, list): # The list consists of tuples: (archval, relpath to bootfile) # e.g., [('00:00', '<svcname>/boot/grub/pxegrub2'),..] # Using the first tuple, get the service name. relpath = arch_class.bootfile[0][1] else: relpath = arch_class.bootfile parts = relpath.partition('/') arch_svcname = parts[0] if arch_svcname == service.name: try: print cw( _("Removing this service's bootfile(s) from local " "DHCP configuration\n")) arch_class.unset_bootfile() except dhcp.DHCPServerError as err: print >> sys.stderr, cw( _("\nUnable to unset this service's " "bootfile(s) in the DHCP " "configuration: %s\n" % err)) return if server.is_online(): try: server.control('restart') except dhcp.DHCPServerError as err: print >> sys.stderr, cw( _("\nUnable to restart the DHCP " "SMF service: %s\n" % err))
def setup_x86_client(service, mac_address, bootargs=''): ''' Set up an x86 client Creates a relative symlink from the <svcname>'s bootfile to /etc/netboot/<client_id> Creates /etc/netboot/menu.lst.<client_id> boot configuration file Adds client info to AI_SERVICE_DIR_PATH/<svcname>/.config file Arguments: image_path - directory path to AI image mac_address - client MAC address (as formed by MACAddress class, i.e., 'ABABABABABAB') bootargs = bootargs of client (x86) Returns: Nothing ''' # create a client-identifier (01 + MAC ADDRESS) client_id = "01" + mac_address menulst = os.path.join(service.config_dir, grub.MENULST) client_menulst = _menulst_path(client_id) # copy service's menu.lst file to menu.lst.<client_id> try: shutil.copy(menulst, client_menulst) except IOError as err: print >> sys.stderr, cw(_("Unable to copy grub menu.lst file: %s") % err.strerror) return # create a symlink from the boot directory to the sevice's bootfile. # note this must be relative from the boot directory. bootfile, pxegrub_path = _pxegrub_path(client_id) os.symlink("./" + service.dhcp_bootfile, pxegrub_path) clientinfo = {config.FILES: [client_menulst, pxegrub_path]} # if the client specifies bootargs, use them. Otherwise, inherit # the bootargs specified in the service (do nothing) if bootargs: grub.update_bootargs(client_menulst, service.bootargs, bootargs) clientinfo[config.BOOTARGS] = bootargs config.add_client_info(service.name, client_id, clientinfo) # Configure DHCP for this client if the configuration is local, otherwise # suggest the configuration addition. Note we only need to do this for # x86-based clients, not SPARC. server = dhcp.DHCPServer() if server.is_configured(): # We'll need the actual hardware ethernet address for the DHCP entry, # rather than the non-delimited string that 'mac_address' is. full_mac = AIdb.formatValue('mac', mac_address) try: print cw(_("Adding host entry for %s to local DHCP configuration.") % full_mac) server.add_host(full_mac, bootfile) except dhcp.DHCPServerError as err: print cw(_("Unable to add host (%s) to DHCP configuration: %s") % (full_mac, err)) return if server.is_online(): try: server.control('restart') except dhcp.DHCPServerError as err: print >> sys.stderr, cw(_("\nUnable to restart the DHCP SMF " "service: %s\n" % err)) return else: print cw(_("\nLocal DHCP configuration complete, but the DHCP " "server SMF service is offline. To enable the " "changes made, enable: %s.\nPlease see svcadm(1M) " "for further information.\n") % dhcp.DHCP_SERVER_IPV4_SVC) else: # No local DHCP, tell the user all about their boot configuration valid_nets = list(com.get_valid_networks()) if valid_nets: server_ip = valid_nets[0] print _(_PXE_CLIENT_DHCP_CONFIG % (server_ip, bootfile)) if len(valid_nets) > 1: print cw(_("\nNote: determined more than one IP address " "configured for use with AI. Please ensure the above " "'Boot server IP' is correct.\n"))
def do_rename_service(cmd_options=None): '''Rename a service. Note: Errors that occur during the various rename stages are printed, but the other stages will continue, with the hopes of leaving the final product as close to functional as possible ''' # check that we are root if os.geteuid() != 0: raise SystemExit(_("Error: Root privileges are required for this " "command.\n")) (svcname, newsvcname) = parse_options(cmd_options) # Ensure the service to rename is a valid service if not config.is_service(svcname): raise SystemExit(_("\nFailed to find service %s\n") % svcname) # Ensure the new name is not already a service if config.is_service(newsvcname): raise SystemExit(_("\nService or alias already exists: %s\n") % newsvcname) # Don't allow renaming to/from the 'default-<arch>' aliases if svcname in DEFAULT_ARCH: raise SystemExit(_('\nYou may not rename the "%s" service.\n') % svcname) if newsvcname in DEFAULT_ARCH: raise SystemExit(cw(_('\nYou may not rename a service to be the ' 'default service for an architecture. To create ' 'the default-sparc or default-i386 service ' 'aliases, use "installadm create-service ' '-t|--aliasof."\n'))) # Unmount old service was_mounted = False try: oldservice = AIService(svcname) if oldservice.mounted(): was_mounted = True logging.debug("disabling %s", svcname) oldservice.disable(force=True) except (MountError, ImageError) as err: raise SystemExit(err) # remove old mountpoint try: os.rmdir(oldservice.mountpoint) except OSError as err: # Just make a note if unable to cleanup mountpoint logging.debug(err) # Remove clients whose base service has been renamed clients = config.get_clients(svcname) for clientid in clients.keys(): clientctrl.remove_client(clientid) oldservice.rename(newsvcname) # Update aliases whose base service has been renamed aliases = config.get_aliased_services(svcname) failures = list() for alias in aliases: props = {config.PROP_ALIAS_OF: newsvcname} config.set_service_props(alias, props) # Mount the renamed service if it was mounted newservice = AIService(newsvcname) if was_mounted: try: logging.debug("enabling %s", newsvcname) newservice.enable() except (MountError, ImageError) as err: failures.append(err) print >> sys.stderr, err # Re-add clients whose base service has been renamed arch = newservice.arch for clientid in clients.keys(): # strip off leading '01' client = clientid[2:] bootargs = None if config.BOOTARGS in clients[clientid]: bootargs = clients[clientid][config.BOOTARGS] create_client.create_new_client(arch, newservice, client, bootargs=bootargs) if failures: return 1 else: return 0
def setup_x86_client(service, mac_address, bootargs='', suppress_dhcp_msgs=False): ''' Set up an x86 client Creates relative symlink(s) in /etc/netboot:: <client_id>.<archtype> -> ./<svcname>/<bootfile_path> e.g., 01223344223344.bios -> ./mysvc/boot/grub/pxegrub Creates /etc/netboot/<cfg_file>.<client_id> boot configuration file Adds client info to AI_SERVICE_DIR_PATH/<svcname>/.config file Arguments: service - the AIService to associate with client mac_address - client MAC address (as formed by MACAddress class, i.e., 'ABABABABABAB') bootargs = bootargs of client (x86) suppress_dhcp_msgs - if True, suppresses output of DHCP configuration messages Returns: Nothing ''' # create a client-identifier (01 + MAC ADDRESS) client_id = "01" + mac_address clientinfo = dict() svcgrub = grubcfg(service.name, path=service.image.path, config_dir=service.config_dir) # Call setup_client - it will return netconfig_files, config_files, # and tuples, e.g.: # netconfig_files: ['/etc/netboot/menu.lst.01234234234234'] # config_files: ['/etc/netboot/menu.conf.01234234234234'] # boot_tuples: [('00:00', 'bios', 'mysvc/boot/grub/pxegrub2'), # ('00:07', 'uefi', 'mysvc/boot/grub/grub2netx64.efi')] # If the client specifies bootargs, use them. Otherwise, inherit # the bootargs specified in the service. netconfig_files, config_files, boot_tuples = \ svcgrub.setup_client(mac_address, bootargs=bootargs, service_bootargs=service.bootargs) # update the bootargs in the service .config file if bootargs: clientinfo[config.BOOTARGS] = bootargs # keep track of client files to delete when client is removed client_files = list(netconfig_files) client_files.extend(config_files) # create symlink(s) from the boot directory to the sevice's bootfile(s). # note these must be relative from the boot directory. for arch, archtype, relpath in boot_tuples: # name of symlink is /etc/netboot/<clientid>.<archtype> bootfile_symlink = _bootfile_path(client_id, archtype) dotpath = './' + relpath logging.debug('creating symlink %s->%s', bootfile_symlink, dotpath) os.symlink(dotpath, bootfile_symlink) client_files.append(bootfile_symlink) # if this is archtype bios, create <clientid> symlink to # <clientid>.bios for backward compatibility with existing dhcp # servers. if archtype == 'bios': clientid_path = os.path.join(com.BOOT_DIR, client_id) dot_client_arch = './' + client_id + '.' + archtype logging.debug('creating symlink %s->%s', clientid_path, dot_client_arch) os.symlink(dot_client_arch, clientid_path) logging.debug('adding client_files to .config: %s', client_files) clientinfo[config.FILES] = client_files config.add_client_info(service.name, client_id, clientinfo) # Configure DHCP for this client if the configuration is local, otherwise # suggest the configuration addition. Note we only need to do this for # x86-based clients, not SPARC. server = dhcp.DHCPServer() if server.is_configured(): # We'll need the actual hardware ethernet address for the DHCP entry, # rather than the non-delimited string that 'mac_address' is. full_mac = AIdb.formatValue('mac', mac_address) try: if not suppress_dhcp_msgs: print cw( _("Adding host entry for %s to local DHCP " "configuration.") % full_mac) server.add_option_arch() server.add_host(full_mac, boot_tuples) except dhcp.DHCPServerError as err: print cw( _("Unable to add host (%(mac)s) to DHCP " "configuration: %(error)s") % { 'mac': full_mac, 'error': err }) return if server.is_online(): try: server.control('restart') except dhcp.DHCPServerError as err: print >> sys.stderr, cw( _("\nUnable to restart the DHCP SMF " "service: %s\n" % err)) return elif not suppress_dhcp_msgs: print cw( _("\nLocal DHCP configuration complete, but the DHCP " "server SMF service is offline. To enable the " "changes made, enable: %s.\nPlease see svcadm(1M) " "for further information.\n") % dhcp.DHCP_SERVER_IPV4_SVC) else: # No local DHCP, tell the user all about their boot configuration valid_nets = list(com.get_valid_networks()) if valid_nets: server_ip = valid_nets[0] if not suppress_dhcp_msgs: boofile_text = '\n' for archval, archtype, relpath in boot_tuples: bootfilename = client_id + '.' + archtype boofile_text = (boofile_text + '\t' + archtype + ' clients (arch ' + archval + '): ' + bootfilename + '\n') print _(_PXE_CLIENT_DHCP_CONFIG % (server_ip, boofile_text)) if len(valid_nets) > 1: print cw( _("\nNote: determined more than one IP address " "configured for use with AI. Please ensure the " "above 'Boot server IP' is correct.\n"))
def setup_x86_client(service, mac_address, bootargs=''): ''' Set up an x86 client Creates a relative symlink from the <svcname>'s bootfile to /etc/netboot/<client_id> Creates /etc/netboot/menu.lst.<client_id> boot configuration file Adds client info to AI_SERVICE_DIR_PATH/<svcname>/.config file Arguments: image_path - directory path to AI image mac_address - client MAC address (as formed by MACAddress class, i.e., 'ABABABABABAB') bootargs = bootargs of client (x86) Returns: Nothing ''' # create a client-identifier (01 + MAC ADDRESS) client_id = "01" + mac_address menulst = os.path.join(service.config_dir, grub.MENULST) client_menulst = _menulst_path(client_id) # copy service's menu.lst file to menu.lst.<client_id> try: shutil.copy(menulst, client_menulst) except IOError as err: print >> sys.stderr, cw( _("Unable to copy grub menu.lst file: %s") % err.strerror) return # create a symlink from the boot directory to the sevice's bootfile. # note this must be relative from the boot directory. bootfile, pxegrub_path = _pxegrub_path(client_id) os.symlink("./" + service.dhcp_bootfile, pxegrub_path) clientinfo = {config.FILES: [client_menulst, pxegrub_path]} # if the client specifies bootargs, use them. Otherwise, inherit # the bootargs specified in the service (do nothing) if bootargs: grub.update_bootargs(client_menulst, service.bootargs, bootargs) clientinfo[config.BOOTARGS] = bootargs config.add_client_info(service.name, client_id, clientinfo) # Configure DHCP for this client if the configuration is local, otherwise # suggest the configuration addition. Note we only need to do this for # x86-based clients, not SPARC. server = dhcp.DHCPServer() if server.is_configured(): # We'll need the actual hardware ethernet address for the DHCP entry, # rather than the non-delimited string that 'mac_address' is. full_mac = AIdb.formatValue('mac', mac_address) try: print cw( _("Adding host entry for %s to local DHCP configuration.") % full_mac) server.add_host(full_mac, bootfile) except dhcp.DHCPServerError as err: print cw( _("Unable to add host (%s) to DHCP configuration: %s") % (full_mac, err)) return if server.is_online(): try: server.control('restart') except dhcp.DHCPServerError as err: print >> sys.stderr, cw( _("\nUnable to restart the DHCP SMF " "service: %s\n" % err)) return else: print cw( _("\nLocal DHCP configuration complete, but the DHCP " "server SMF service is offline. To enable the " "changes made, enable: %s.\nPlease see svcadm(1M) " "for further information.\n") % dhcp.DHCP_SERVER_IPV4_SVC) else: # No local DHCP, tell the user all about their boot configuration valid_nets = list(com.get_valid_networks()) if valid_nets: server_ip = valid_nets[0] print _(_PXE_CLIENT_DHCP_CONFIG % (server_ip, bootfile)) if len(valid_nets) > 1: print cw( _("\nNote: determined more than one IP address " "configured for use with AI. Please ensure the above " "'Boot server IP' is correct.\n"))
def do_update_service(cmd_options=None): '''Update a service - currently only an alias Copy the baseservice of an alias, update it, create a service using the updated image, and re-alias the alias to the new service. ''' # check for authorization and euid try: check_auth_and_euid(SERVICE_AUTH) except UnauthorizedUserError as err: raise SystemExit(err) options = parse_options(cmd_options) service = options.service try: if not service.image.is_pkg_based(): raise SystemExit( cw( _("\nError: '%s' is aliased to an iso based service. Only " "aliases of pkg(5) based services are updatable.") % options.service_name)) except pkg.client.api_errors.VersionException as err: 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) }) raise SystemExit() fmri = [options.srcimage] if options.srcimage else None base_svc = svc.AIService(service.basesvc, image_class=InstalladmPkgImage) new_image = None try: print _("Copying image ...") new_image = base_svc.image.copy(prefix=base_svc.name) print _('Updating image ...') update_needed = new_image.update(fmri=fmri, publisher=options.publisher) except (ValueError, img.ImageError, pkg.client.api_errors.ApiException) as err: # clean up copied image if new_image: new_image.delete() print >> sys.stderr, cw( _("Attempting to update the service %s" " failed for the following reasons:") % service.name) if 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 raise SystemExit(err) if not update_needed: # No update needed, remove copied image new_image.delete() print _("No updates are available.") return 4 # Get the service name from the updated image metadata new_svcname = img.get_default_service_name(image=new_image) # Determine imagepath and move image there new_path = new_image.path new_imagepath = os.path.join(os.path.dirname(new_path), new_svcname) try: img.check_imagepath(new_imagepath) except ValueError as error: # Leave image in temp location rather than fail. User can # update imagepath later if desired. logging.debug('unable to move image from %s to %s: %s', new_path, new_imagepath, error) else: new_path = new_image.move(new_imagepath) logging.debug('image moved to %s', new_path) finally: img.set_permissions(new_path) # create new service based on updated image print _("Creating new %(arch)s service: %(newsvc)s") % \ {'arch': new_image.arch, 'newsvc': new_svcname} new_service = svc.AIService.create(new_svcname, new_image) # Register & enable service # (Also enables system/install/server, as needed) try: service.enable() except (config.ServiceCfgError, svc.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 print _("Aliasing %(alename)s to %(newsvc)s ...\n") % \ {'alename': service.name, 'newsvc': new_svcname} failures = setsvc.do_update_basesvc(service, new_service.name) if failures: return 1 return 0
def parse_options(cmd_options=None): ''' Parse and validate options Returns: An options record containing arch aliasof bootargs dhcp_ip_count dhcp_ip_start dhcp_bootserver noprompt publisher srcimage svcname imagepath ''' logging.log(com.XDEBUG, '**** START installadm.create_service.' 'parse_options ****\n') usage = '\n' + get_usage() description = _('Establishes an Automated Install network service.') parser = OptionParser(usage=usage, prog="create-service", description=description) parser.add_option('-b', '--boot-args', dest='bootargs', action='append', default=list(), help=_('Comma separated list of <property>=<value>' ' pairs to add to the x86 Grub menu entry')) parser.add_option('-a', '--arch', dest='arch', default=None, choices=("i386", "sparc"), help=_("ARCHITECTURE (sparc or i386), desired " "architecture of resulting service when creating " "from a pkg.")) parser.add_option('-d', '--imagepath', dest='imagepath', default=None, help=_("Path at which to create the net image")) parser.add_option('-t', '--aliasof', dest='aliasof', default=None, help=_("Service being created is alias of this serivce")) parser.add_option('-n', '--service', dest='svcname', help=_('service name')) parser.add_option('-i', '--ip-start', dest='dhcp_ip_start', type='string', help=_('DHCP Starting IP Address'), action="callback", callback=check_ip_address) parser.add_option('-c', '--ip-count', dest='dhcp_ip_count', type='int', help=_('DHCP Count of IP Addresses')) parser.add_option('-B', '--bootfile-server', dest='dhcp_bootserver', type='string', help=_('DHCP Boot Server Address'), action="callback", callback=check_ip_address) parser.add_option('-s', '--source', dest='srcimage', type='string', help=_('FMRI or Auto Install ISO')) parser.add_option('-p', '--publisher', help=_( "A pkg(5) publisher, in the" " form '<prefix>=<uri>', from which to install the " "client image")) parser.add_option('-y', "--noprompt", action="store_true", dest="noprompt", default=False, help=_('Suppress confirmation prompts and proceed with ' 'service creation using default values')) options, args = parser.parse_args(cmd_options) if args: parser.error(_('Unexpected argument(s): %s') % args) # if service name provided, validate it if options.svcname: try: com.validate_service_name(options.svcname) except ValueError as err: parser.error(err) # Give error if service already exists if config.is_service(options.svcname): parser.error(_('\nService already exists: %s\n') % options.svcname) # If creating an alias, only allow additional options -n, -b, # and -y if options.aliasof: if (options.dhcp_ip_start or options.dhcp_ip_count or options.imagepath or options.srcimage): parser.error( _('\nOnly options -n|--service, -b|--boot-args, ' 'and -y|--noprompt\nmay be specified with ' '-t|--aliasof.')) if not options.svcname: parser.error( _('\nOption -n|--service is required with the ' '-t|--aliasof option')) else: name = options.svcname if name in DEFAULT_ARCH: raise SystemExit( _('\nDefault services must be created as ' 'aliases. Use -t|--aliasof.\n')) # provide default for srcimage, now that we're done option checking if options.srcimage is None: options.srcimage = "pkg:/install-image/solaris-auto-install" # check dhcp related options if options.dhcp_ip_start or options.dhcp_ip_count: if com.is_multihomed(): # don't allow DHCP setup if multihomed parser.error( cw( _('\nDHCP server configuration is unavailable on ' 'hosts with multiple network interfaces (-i and ' '-c options are disallowed).\n'))) # Confirm options -i and -c are both provided if options.dhcp_ip_count is None: parser.error( _('\nIf -i option is provided, -c option must ' 'also be provided\n')) if not options.dhcp_ip_start: parser.error( _('\nIf -c option is provided, -i option must ' 'also be provided\n')) # Confirm count of ip addresses is positive if options.dhcp_ip_count < 1: parser.error( _('\n"-c <count_of_ipaddr>" must be greater than ' 'zero.\n')) if options.dhcp_bootserver: # Confirm if the -B is provided, that -i/-c are also if options.dhcp_ip_count is None: parser.error( _('\nIf -B option is provided, -i option must ' 'also be provided\n')) if is_iso(options.srcimage): if options.arch is not None: parser.error( _("The --arch option is invalid for ISO-based " "services")) if options.publisher is not None: parser.error( _("The --publisher option is invalid for " "ISO-based services")) if options.publisher: # Convert options.publisher from a string of form 'prefix=uri' to a # tuple (prefix, uri) publisher = options.publisher.split("=") if len(publisher) != 2: parser.error( _('Publisher information must match the form: ' '"<prefix>=<URI>"')) options.publisher = publisher # Make sure imagepath meets requirements if options.imagepath: options.imagepath = options.imagepath.strip() if options.imagepath: if not options.imagepath == '/': options.imagepath = options.imagepath.rstrip('/') try: check_imagepath(options.imagepath) except ValueError as error: raise SystemExit(error) return options
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 do_rename_service(cmd_options=None): '''Rename a service. Note: Errors that occur during the various rename stages are printed, but the other stages will continue, with the hopes of leaving the final product as close to functional as possible ''' # check that we are root if os.geteuid() != 0: raise SystemExit(_("Error: Root privileges are required for this " "command.\n")) (svcname, newsvcname) = parse_options(cmd_options) # Ensure the service to rename is a valid service if not config.is_service(svcname): raise SystemExit(_("\nFailed to find service %s\n") % svcname) # Ensure the new name is not already a service if config.is_service(newsvcname): raise SystemExit(_("\nService or alias already exists: %s\n") % newsvcname) # Don't allow renaming to/from the 'default-<arch>' aliases if svcname in DEFAULT_ARCH: raise SystemExit(_('\nYou may not rename the "%s" service.\n') % svcname) if newsvcname in DEFAULT_ARCH: raise SystemExit(cw(_('\nYou may not rename a service to be the ' 'default service for an architecture. To create ' 'the default-sparc or default-i386 service ' 'aliases, use "installadm create-service ' '-t|--aliasof."\n'))) # Unmount old service was_mounted = False try: oldservice = AIService(svcname) if oldservice.mounted(): was_mounted = True logging.debug("disabling %s", svcname) oldservice.disable(force=True) except (MountError, ImageError) as err: raise SystemExit(err) # remove old mountpoint try: os.rmdir(oldservice.mountpoint) except OSError as err: # Just make a note if unable to cleanup mountpoint logging.debug(err) # Remove clients whose base service has been renamed clients = config.get_clients(svcname) for clientid in clients.keys(): clientctrl.remove_client(clientid) oldservice.rename(newsvcname) # Update aliases whose base service has been renamed aliases = config.get_aliased_services(svcname) failures = list() for alias in aliases: props = {config.PROP_ALIAS_OF: newsvcname} config.set_service_props(alias, props) # Mount the renamed service if it was mounted newservice = AIService(newsvcname) if was_mounted: try: logging.debug("enabling %s", newsvcname) newservice.enable() except (MountError, ImageError) as err: failures.append(err) print >> sys.stderr, err # Re-add clients whose base service has been renamed arch = newservice.arch for clientid in clients.keys(): # strip off leading '01' client = clientid[2:] bootargs = None if config.BOOTARGS in clients[clientid]: bootargs = clients[clientid][config.BOOTARGS] create_client.create_new_client(arch, newservice, client, bootargs=bootargs) if failures: return 1 else: return 0
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: check_imagepath(options.imagepath) except ValueError as error: raise SystemExit(error) else: try: os.makedirs(BASE_IMAGE_DIR) 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, " + str(err.received_version) + ", is incompatible with the expected version, " + 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 = 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: 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) 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 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 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_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: check_imagepath(options.imagepath) except ValueError as error: raise SystemExit(error) else: try: os.makedirs(BASE_IMAGE_DIR) 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, " + str(err.received_version) + ", is incompatible with the expected version, " + 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 = 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: 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) 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 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 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