7129773 installadm create-service outputs some error
authorTomas Dzik <Tomas.Dzik@oracle.com>
Fri, 24 Feb 2012 09:48:42 +0100
changeset 1592 43c864a2dc45
parent 1591 1ade32ea64fa
child 1593 98bbf9625333
7129773 installadm create-service outputs some error
usr/src/cmd/installadm/service.py
--- a/usr/src/cmd/installadm/service.py	Fri Feb 24 08:46:58 2012 +0100
+++ b/usr/src/cmd/installadm/service.py	Fri Feb 24 09:48:42 2012 +0100
@@ -101,19 +101,19 @@
 class VersionError(AIServiceError):
     '''Raised if the service's version is not supported by
     this version of installadm
-    
+
     '''
     def __init__(self, service_name=None, version=None, alt_msg=None):
         self.service_name = service_name
         self.version = version
         self.alt_msg = alt_msg
-    
+
     def __str__(self):
         if self.alt_msg:
             return self.alt_msg
         else:
             return self.short_str()
-    
+
     def short_str(self):
         '''Returns a one-line string message for this error'''
         return (cw(_("Service '%(name)s' is incompatible: "
@@ -125,7 +125,7 @@
     def __str__(self):
         if self.alt_msg:
             return self.alt_msg
-        
+
         return (cw(_("\nService '%(name)s' cannot be modified because it was "
                      "created with an older version of installadm (service "
                      "version: %(version)s).\n") %
@@ -136,7 +136,7 @@
     def __str__(self):
         if self.alt_msg:
             return self.alt_msg
-        
+
         return cw(_("\nService '%(name)s' cannot be modified because it was "
                     "created with a newer version of installadm (service "
                     "version: %(version)s).\n") %
@@ -149,7 +149,7 @@
         self.source = source
         self.target = target
         self.reason = reason
-    
+
     def __str__(self):
         return cw(_("\nUnable to mount '%s' at '%s': %s\n" %
                     (self.source, self.target, self.reason)))
@@ -160,7 +160,7 @@
     def __init__(self, target, reason):
         self.target = target
         self.reason = reason
-    
+
     def __str__(self):
         return cw(_("\nUnable to unmount '%s': %s\n") %
                     (self.target, self.reason))
@@ -171,7 +171,7 @@
     def __init__(self, still_mounted, failures):
         self.still_mounted = still_mounted
         self.failures = failures
-    
+
     def __str__(self):
         result = list()
         if self.still_mounted:
@@ -187,7 +187,7 @@
     and a service is already mounted, it is first unmounted (otherwise, the
     mount is left as-is. Returns a count of how many services failed to
     mount properly, and prints to stderr as the errors are encountered.
-    
+
     '''
     failures = 0
     for svc_name in config.get_all_service_names():
@@ -238,23 +238,23 @@
 
 class AIService(object):
     '''Class to represent an AI service'''
-    
+
     MOUNT_ON = com.BOOT_DIR
     X86_BOOTFILE = "boot/grub/pxegrub"
     EARLIEST_VERSION = 2
     LATEST_VERSION = 2
-    
+
     @classmethod
     def create(cls, name, source_image, ip_start=None, ip_count=None,
                bootserver=None, alias=None, bootargs=''):
         '''Creates a service, setting up configuration, initial manifests,
         and DHCP (if desired)
-        
+
         Args:
             name: Desired service name (not checked for validity)
             source_image: An InstalladmImage object representing an already
                 unpacked image area
-        
+
         Optional Args:
             ip_start, ip_count: Starting IP address and number of IP addresses
                 to allocate in DHCP. If not specified, no DHCP setup will be
@@ -263,23 +263,23 @@
                 address for the bootfile server
             alias: An AIService object to which this service will be aliased
             bootargs (x86 only): Additional bootargs to add to menu.lst
-        
+
         '''
         service = cls(name, _check_version=False)
         service._image = source_image
         service._alias = alias
         service._bootargs = bootargs
-        
+
         if alias is not None and service.image.version < 3:
             raise UnsupportedAliasError(cw(_("\nCannot create alias: Aliased "
                                              "service '%s' does not support "
                                              "aliasing. Please use a service "
                                              "with a newer image.\n") % alias))
-        
+
         compatibility_port = (service.image.version < 1)
         logging.debug("compatibility port=%s", compatibility_port)
         server_hostname = socket.gethostname()
-        
+
         # Determine port information
         if compatibility_port:
             port = get_a_free_tcp_port(server_hostname)
@@ -288,9 +288,9 @@
                                        "web server."))
         else:
             port = libaimdns.getinteger_property(com.SRVINST, com.PORTPROP)
-        
+
         props = service._init_service_config(server_hostname, port)
-        
+
         # Create AI_SERVICE_DIR_PATH/<svcdir> structure
         cmd = [com.SETUP_SERVICE_SCRIPT, com.SERVICE_CREATE, name,
                props[config.PROP_TXT_RECORD], service._image.path]
@@ -303,12 +303,12 @@
         except CalledProcessError:
             print >> sys.stderr, _("Failed to setup service directory for "
                                    "service, %s\n" % name)
-        
+
         service._create_default_manifest()
 
         # Configure DHCP for this service
         service._configure_dhcp(ip_start, ip_count, bootserver)
-        
+
         # Supported client images will interpolate the bootserver's IP
         # address (as retrieved from the DHCP server) in place of the
         # '$serverIP' string. We choose to utilize this functionality when
@@ -334,13 +334,13 @@
             server_ip = "$serverIP"
 
         logging.debug("server_ip=%s", server_ip)
-        
+
         # Save location of service in format <server_ip_address>:<port>
         # It will be used later for setting service discovery fallback
         # mechanism.
         srv_address = '%s:%u' % (server_ip, port)
         logging.debug("srv_address=%s", srv_address)
-        
+
         # Setup wanboot for SPARC or Grub for x86
         if service.image.arch == 'sparc':
             service._init_wanboot(srv_address)
@@ -348,12 +348,12 @@
             service._init_grub(srv_address)
 
         return service
-    
+
     def __init__(self, name, image=None, image_class=InstalladmImage,
                  _check_version=True):
         '''Create a reference to an AIService, which can be used to control
         and manage installadm services.
-        
+
         Required arguments:
             name: The name of the service. It should already exist.
                      (Use classmethod AIService.create() to make a new service)
@@ -364,14 +364,14 @@
             image_class: If 'image' is omitted, when an image reference is
                             needed, it will be instantiated as an instance
                             of the given class. Defaults to InstalladmImage.
-        
+
         '''
         self._name = name
         self._image = image
         self._alias = None
         self._bootargs = None
         self._image_class = image_class
-        
+
         # _check_version should ONLY be used by AIService.create during service
         # creation. Bypassing the version check when the service has already
         # been created may lead to indeterminate results.
@@ -386,77 +386,77 @@
             raise OlderVersionError(name, version)
         elif version > self.LATEST_VERSION:
             raise NewerVersionError(name, version)
-    
+
     def enable(self):
         '''Enable this service. May fail, if this service is marked in need
         of re-verification, and re-verification fails.
-        
+
         '''
         if config.get_service_props(self.name).get(config.PROP_REVERIFY,
                                                    False):
             self.check_valid()
         self.mount()
         config.enable_install_service(self.name)
-    
+
     def disable(self, arch_safe=False, force=False):
         '''Disable this service'''
         props = {config.PROP_STATUS: config.STATUS_OFF}
         config.set_service_props(self.name, props)
-        
+
         self._unregister()
         self.unmount(arch_safe=arch_safe, force=force)
-        
+
         # If no longer needed, put install instance into
         # maintenance
         config.check_for_enabled_services()
-    
+
     def _unregister(self):
         cmd = [com.SETUP_SERVICE_SCRIPT, com.SERVICE_DISABLE, self.name]
         Popen.check_call(cmd, check_result=Popen.ANY)
-    
+
     def delete(self):
         '''Deletes this service, removing the image area, mountpoints,
         and lingering configuration.
-        
+
         '''
         self.disable(arch_safe=True, force=True)
-        
+
         self.remove_profiles()
         for path in self.get_files_to_remove():
             force_delete(path)
-    
+
     def version(self):
         '''Look up and return the version of this service. See module
         docstring for info on what each service version supports
-        
+
         '''
         props = config.get_service_props(self.name)
         try:
             return int(props[config.PROP_VERSION])
         except (KeyError, ValueError, TypeError):
             return None
-    
+
     @property
     def config_dir(self):
         '''Returns the path to the configuration directory for this service'''
         return os.path.join(AI_SERVICE_DIR_PATH, self.name)
-    
+
     @property
     def database_path(self):
         '''Returns the path to the database file for this service'''
         return os.path.join(self.config_dir, "AI.db")
-    
+
     def database(self):
         '''Opens an AI_database.DB() object pointing to this service's
         database
-        
+
         '''
         return AIdb.DB(self.database_path)
-    
+
     def rename(self, new_name):
         '''Change this service's name to "new_name" updating all relevant
         files, aliases and clients.
-        
+
         '''
         self._update_name_in_service_props(new_name)
         new_svcdir = os.path.join(AI_SERVICE_DIR_PATH, new_name)
@@ -486,7 +486,7 @@
 
         self._migrate_service_dir(new_svcdir)
         self._setup_manifest_dir(new_name=new_name)
-    
+
     def is_default_arch_service(self):
         ''' Determine if this is the default-<arch> service'''
 
@@ -500,7 +500,7 @@
         '''
         for linkname in WANBOOTCONF, SYSTEMCONF:
             self._do_default_sparc_symlink(linkname, svcname)
-        
+
     def _do_default_sparc_symlink(self, linkname, svcname):
         ''' Do symlinks for default sparc service
         Input:
@@ -516,7 +516,7 @@
                 raise
         new_target = os.path.join(self.MOUNT_ON, svcname, linkname)
         os.symlink(new_target, symlink)
-    
+
     def delete_default_sparc_symlinks(self):
         '''Delete symlinks for default sparc service'''
         for linkname in WANBOOTCONF, SYSTEMCONF:
@@ -531,10 +531,10 @@
         '''Copy or move the AI_SERVICE_DIR_PATH/<svc> directory
            Additionally update any compatibility symlinks pointing to it
                (or create new ones)
-           
+
            Input:
                   newsvcdir - full path to new service dir
-        
+
         '''
         # update any symlinks pointing to svcdir
         dirlist = os.listdir(AI_SERVICE_DIR_PATH)
@@ -556,7 +556,7 @@
                         raise
                 logging.debug("symlink from %s to %s", fullpath, newsvcdir)
                 os.symlink(newsvcdir, fullpath)
-        
+
         logging.debug("rename from %s to %s", self.config_dir, newsvcdir)
         os.rename(self.config_dir, newsvcdir)
 
@@ -578,7 +578,7 @@
     def name(self):
         '''Service name'''
         return self._name
-    
+
     @staticmethod
     def _prepare_target(mountpoint):
         logging.debug("_prepare_target: mountpoint is %s", mountpoint)
@@ -587,43 +587,43 @@
         except OSError as err:
             if err.errno != errno.EEXIST:
                 raise
-    
+
     @property
     def mountpoint(self):
         '''Returns the path to where this service's image should be mounted'''
         return os.path.join(self.MOUNT_ON, self.name)
-    
+
     @property
     def bootmountpt(self):
         '''Returns the path to where this service's boot specific config
         file should be mounted
-        
+
         '''
         if self.arch == 'i386':
             return os.path.join(self.mountpoint, MENULST)
         else:
             return os.path.join(self.mountpoint, SYSTEMCONF)
-    
+
     def all_bootmountpts(self):
         return [os.path.join(self.mountpoint, MENULST),
                 os.path.join(self.mountpoint, SYSTEMCONF)]
-    
+
     @property
     def bootsource(self):
         '''Returns the path to the file that should be mounted at
         self.bootmountpt
-        
+
         '''
         if self.arch == 'i386':
             return os.path.join(self.config_dir, MENULST)
         else:
             return os.path.join(self.config_dir, SYSTEMCONF)
-    
+
     @property
     def dhcp_bootfile(self):
         '''Returns a string that represents what should be added to DHCP
         as this service's bootfile
-        
+
         '''
         if self.arch == 'sparc':
             http_port = libaimdns.getinteger_property(com.SRVINST,
@@ -636,13 +636,13 @@
             abs_dhcpbfile = os.path.join(self.mountpoint, self.X86_BOOTFILE)
             dhcpbfile = os.path.relpath(abs_dhcpbfile, self.MOUNT_ON)
         return dhcpbfile
-    
+
     def _lofs_mount(self, from_path, to_mountpoint):
         '''Internal lofs mount function. If the mountpoint
         is already in use by this service, returns without doing anything.
         Otherwise, will mount from_path on to_mountpoint, raising MountError
         for any failure.
-        
+
         '''
         mount_points = com.MNTTab()['mount_point']
         specials = com.MNTTab()['special']
@@ -659,10 +659,10 @@
                              check_result=Popen.SUCCESS)
         except CalledProcessError as err:
             raise MountError(from_path, to_mountpoint, err.popen.stderr)
-    
+
     def mount(self):
         '''Perform service lofs mounts
-        
+
         For all services,
           /etc/netboot/<svcname> is an lofs mount of <targetdir> (or
                self.image.path for an alias)
@@ -679,7 +679,7 @@
         try:
             self.image.verify()
         except ImageError as error:
-            # verify doesn't know anything about services so prepend the 
+            # verify doesn't know anything about services so prepend the
             # service name to the exception to give the user sufficient
             # context to fix the problem and re-raise the exception
             raise ImageError(cw(_('Service Name: %s\n') %
@@ -689,38 +689,38 @@
         self._lofs_mount(self.image.path, self.mountpoint)
         # Do second lofi mount of menu.lst(x86) or system.conf(sparc)
         self._lofs_mount(self.bootsource, self.bootmountpt)
-    
+
     def unmount(self, force=False, arch_safe=False):
         '''Unmount the service, reversing the effects of AIService.mount()
-        
+
         Will raise MultipleUnmountFailure if the service does not cleanly
         unmount. If errors occur during unmounting, but the unmounts
         are otherwise successful, then failures are ignored (logged)
-        
+
         If arch_safe is True, this command will umount all potential
         mountpoints, without checking the underlying image arch
         (useful for deleting a service when the underlying image
         is corrupted or missing)
-        
+
         DO NOT UNMOUNT AN ENABLED SERVICE OR YOU WILL HAVE WEIRD ERRORS
-        
+
         SMF will see a service as enabled and remount it at arbitrary
         points in time
-        
+
         '''
         umount_cmd = [UNMOUNT]
         if force:
             umount_cmd.append("-f")
-        
+
         if arch_safe:
             mountpoints = self.all_bootmountpts()
         else:
             mountpoints = [self.bootmountpt]
-        
+
         # The image mountpoint must be unmounted last, as the boot mounts
         # are mounted inside of it.
         mountpoints.append(self.mountpoint)
-        
+
         failures = list()
         for mountpoint in mountpoints:
             cmd = list(umount_cmd)
@@ -731,23 +731,23 @@
                                  check_result=Popen.SUCCESS)
             except CalledProcessError as err:
                 failures.append(UnmountError(mountpoint, err.popen.stderr))
-        
+
         still_mounted = self._currently_mounted()
         if still_mounted:
             raise MultipleUnmountError(still_mounted, failures)
-    
+
     def mounted(self):
         '''Returns True if ALL mounts for this service are active.'''
         mount_points = com.MNTTab()['mount_point']
         return (self.mountpoint in mount_points and
                 self.bootmountpt in mount_points)
-    
+
     def _currently_mounted(self):
         '''Returns True if ANY mounts for this service are active.
-        
+
         If AIService.mounted returns False while AIService.partially_mounted
         returns True, the service failed to properly mount or unmount
-        
+
         '''
         current_mounts = list()
         mount_points = com.MNTTab()['mount_point']
@@ -757,7 +757,7 @@
             if boot_mount in mount_points:
                 current_mounts.append(boot_mount)
         return current_mounts
-    
+
     def is_alias(self):
         '''Determine if this service is an alias'''
         return (self.basesvc is not None)
@@ -834,7 +834,7 @@
         logging.debug("updating alias service props for %s", self.name)
         props = {config.PROP_ALIAS_OF: newbasesvc_name}
         config.set_service_props(self.name, props)
-        
+
         # disable the alias
         was_mounted = False
         if self.mounted():
@@ -843,7 +843,7 @@
 
         if self.arch == 'i386':
             self_menulstpath = os.path.join(self.config_dir, MENULST)
-            
+
             all_aliases = config.get_aliased_services(self.name, recurse=True)
             all_clients = config.get_clients(self.name).keys()
             for alias in all_aliases:
@@ -899,7 +899,7 @@
                 if client_bootargs:
                     grub.update_bootargs(client_menulst, self.bootargs,
                                          client_bootargs)
-            
+
         # Turning point: Function calls prior to this line will reference
         # the 'old' base service's image. Function calls after this line
         # will reference the *new* base service's image. Keep that in mind
@@ -913,7 +913,7 @@
                                       "or profiles is no longer valid. This "
                                       "service will be disabled until they "
                                       "have been removed or fixed.\n"))
-        
+
         # Enable the alias to pick up the new image
         if was_mounted:
             self.enable()
@@ -922,20 +922,20 @@
     def manifest_dir(self):
         '''Full path to the directory where this service's
         manifests are stored
-        
+
         '''
         return os.path.join(self.config_dir, "AI_data")
-    
+
     def validate_manifests(self):
         '''Re-verifies all relevant AI XML manifests associated with this
         service. For any that are no longer valid according to the image's
         DTD, an error is printed. A count of the number of invalid manifests
         is returned.
-        
+
         '''
         db_conn = self.database()
         manifests = AIdb.getNames(db_conn.getQueue(), AIdb.MANIFESTS_TABLE)
-        
+
         failures = 0
         for manifest in manifests:
             manifest_path = os.path.join(self.manifest_dir, manifest)
@@ -949,7 +949,7 @@
                 print >> sys.stderr, error
                 failures += 1
         return failures
-    
+
     def remove_profiles(self):
         ''' delete profile files from internal database
         Arg: service - object of service to delete profile files for
@@ -979,10 +979,10 @@
         service. For any that are no longer valid according to the image's
         DTD, an error is printed. A count of the number of invalid profiles
         is returned.
-        
+
         '''
         failures = 0
-        
+
         if self.image.version < 2:
             # Older client images do not support profiles
             return failures
@@ -993,7 +993,7 @@
         queue.put(profile_request)
         profile_request.waitAns()
         profiles = profile_request.getResponse() or list()
-  
+
         for profile_name, profile_path in profiles:
             valid = validate_file(profile_name, profile_path,
                                   self.image.path, verbose=False)
@@ -1004,16 +1004,16 @@
                 print >> sys.stderr, error
                 failures += 1
         return failures
-    
+
     def check_valid(self):
         '''Validates whether or not this service can be enabled. If
         this service can be enabled, then the PROP_REVERIFY flag is set
         to False. If it CANNOT be enabled, the PROP_REVERIFY flag is set to
         True, the service is disabled, and InvalidServiceError is raised.
-        
+
         Currently, this function only validates manifests/profiles against
         this service's image (valuable for aliases).
-        
+
         '''
         invalid_manifest_count = self.validate_manifests()
         invalid_profile_count = self.validate_profiles()
@@ -1026,7 +1026,7 @@
         else:
             # Invalid flag is cleared, but user must manually enable service
             config.set_service_props(self.name, {config.PROP_REVERIFY: False})
-    
+
     @property
     def basesvc(self):
         '''get reference to basesvc of an alias or None if not an alias'''
@@ -1035,7 +1035,7 @@
         if config.PROP_ALIAS_OF in props:
             basesvc = props[config.PROP_ALIAS_OF]
         return basesvc
-    
+
     @property
     def bootargs(self):
         '''get bootargs of a service'''
@@ -1044,7 +1044,7 @@
         if config.PROP_BOOT_ARGS in props:
             bootargs = props[config.PROP_BOOT_ARGS]
         return bootargs
-    
+
     @property
     def arch(self):
         '''Defer to underlying image object'''
@@ -1052,12 +1052,12 @@
             return self.image.arch
         else:
             return None
-    
+
     @property
     def image(self):
         '''Returns a reference to an InstalladmImage object (or subclass)
         pointed at this service's image path (following aliases as needed).
-        
+
         The actual class of the returned object can be modified by
         passing in the desired class as the "image_class" keyword arg
         to the AIService constructor.
@@ -1077,7 +1077,7 @@
                     path = alias_svc.image.path
                 self._image = self._image_class(path)
         return self._image
-    
+
     def _configure_dhcp(self, ip_start, ip_count, bootserver):
         '''Add DHCP configuration elements for this service.'''
 
@@ -1085,13 +1085,13 @@
             ip_start, ip_count, bootserver, self.dhcp_bootfile, self.arch)
 
         setup_dhcp_server(self, ip_start, ip_count, bootserver)
-    
+
     def _init_grub(self, srv_address):
         '''Initialize grub (menu.lst) for this service'''
         grub.setup_grub(self.name, self.image.path,
                         self.image.read_image_info(), srv_address,
                         self.config_dir, self._bootargs)
-    
+
     def _init_wanboot(self, srv_address):
         '''Create system.conf file in AI_SERVICE_DIR_PATH/<svcname>'''
         cmd = [com.SETUP_SPARC_SCRIPT, com.SPARC_SERVER, self.image.path,
@@ -1100,13 +1100,28 @@
         # the setup-service-script relies on sending information to
         # stdout/stderr
         logging.debug('Calling %s', cmd)
-        Popen.check_call(cmd)
+        # SETUP_SPARC_SCRIPT does math processing that needs to run in "C"
+        # locale to avoid problems with alternative # radix point
+        # representations (e.g. ',' instead of '.' in cs_CZ.*-locales).
+        # Because ksh script uses built-in math we need to set locale here and
+        # we can't set it in script itself
+        modified_env = os.environ.copy()
+        lc_all = modified_env.get('LC_ALL', '')
+        if lc_all != '':
+            modified_env['LC_MONETARY'] = lc_all
+            modified_env['LC_MESSAGES'] = lc_all
+            modified_env['LC_COLLATE'] = lc_all
+            modified_env['LC_TIME'] = lc_all
+            modified_env['LC_CTYPE'] = lc_all
+            del modified_env['LC_ALL']
+        modified_env['LC_NUMERIC'] = 'C'
+        Popen.check_call(cmd, env=modified_env)
         self._setup_install_conf()
-    
+
     def _setup_install_conf(self):
         '''Creates the install.conf file, as needed for compatibility
         with older sparc clients
-        
+
         '''
         if self.image.version < 3:
             # AI clients starting with this version use
@@ -1128,37 +1143,37 @@
                 if err.errno != errno.ENOENT:
                     raise
             os.symlink(system_conf, install_conf)
-    
+
     def _init_service_config(self, hostname, port):
         '''Initialize this service's .config file. This should only be
         called during service creation, from AIService.create()
-        
+
         '''
         txt_record = '%s=%s:%u' % (com.AIWEBSERVER, hostname, port)
         logging.debug("txt_record=%s", txt_record)
-        
+
         service_data = {config.PROP_SERVICE_NAME: self.name,
                         config.PROP_TXT_RECORD: txt_record,
                         config.PROP_GLOBAL_MENU: True,
                         config.PROP_REVERIFY: False,
                         config.PROP_STATUS: config.STATUS_ON}
-        
+
         if self._alias:
             service_data[config.PROP_ALIAS_OF] = self._alias
         else:
             service_data[config.PROP_IMAGE_PATH] = self.image.path
-        
+
         if self.arch == 'i386' and self._bootargs:
             service_data[config.PROP_BOOT_ARGS] = self._bootargs
         if self.arch == 'sparc':
             service_data[config.PROP_GLOBAL_MENU] = False
-        
+
         logging.debug("service_data=%s", service_data)
-        
+
         config.create_service_props(self.name, service_data)
-        
+
         return service_data
-    
+
     def get_files_to_remove(self):
         '''Returns a list of file paths to clean up on service deletion.
         For version 1 services, this includes:
@@ -1167,28 +1182,28 @@
             * The symlink from AI_SERVICE_DIR_PATH/<port> to the config dir,
               if applicable
             * The symlink from the webserver's docroot to the manifest dir
-        
+
         '''
         files = [self.mountpoint,
                  self.config_dir,
                  os.path.join(AI_SERVICE_DIR_PATH,
                               config.get_service_port(self.name)),
                  os.path.join(com.WEBSERVER_DOCROOT, self.name)]
-        
+
         if not self.is_alias():
             files.append(self.image.path)
-            
+
             # must strip the leading path separator from image_path as
             # os.join won't concatenate two absolute paths
             webserver_path = os.path.join(com.WEBSERVER_DOCROOT,
                                           self.image.path.lstrip(os.sep))
             files.append(webserver_path)
-            
+
             # find the longest empty path leading up to webserver image path
             if os.path.lexists(webserver_path):
                 # get the parent dir of the webserver path
                 directory = os.path.dirname(webserver_path)
-            
+
                 # iterate up the directory structure (toward / from the
                 # image server's image path) adding empty directories
                 while len(os.listdir(directory)) == 1:
@@ -1200,13 +1215,13 @@
         else:
             if self.is_default_arch_service() and self.arch == 'sparc':
                 self.delete_default_sparc_symlinks()
-        
+
         return files
-    
+
     def _setup_manifest_dir(self, new_name=None):
         '''Create a symlink in the AI webserver's docroot pointing to
         the directory containing this service's manifests
-        
+
         '''
         if new_name is not None:
             try:
@@ -1222,18 +1237,18 @@
         if os.path.islink(linkname) or os.path.exists(linkname):
             os.remove(linkname)
         os.symlink(data_dir, linkname)
-    
+
     def _create_default_manifest(self):
         '''Create an initial, default manifest for the service (preferring
         the manifest contained in the image, but falling back to the one
         on the host system as necessary.
-        
+
         The manifest directory is setup first, as well.
         (See _setup_manifest_dir)
-        
+
         '''
         self._setup_manifest_dir()
-        
+
         default_xml = os.path.join(self.image.path,
                                    IMG_AI_DEFAULT_MANIFEST)
         if not os.path.exists(default_xml):
@@ -1245,7 +1260,7 @@
                 print (_("Warning: Using default manifest %s") %
                        SYS_AI_DEFAULT_MANIFEST)
                 default_xml = SYS_AI_DEFAULT_MANIFEST
-        
+
         manifest_name = "orig_default"
         data = DataFiles.from_service(self,
                                       manifest_file=default_xml,
@@ -1255,7 +1270,7 @@
         manifest_path = os.path.join(self.manifest_dir, manifest_name)
         place_manifest(data, manifest_path)
         self.set_default_manifest(manifest_name)
-    
+
     def set_default_manifest(self, manifest_name, skip_manifest_check=False):
         '''Make manifest "manifest_name" the default for this service'''
         if not skip_manifest_check:
@@ -1263,10 +1278,10 @@
             if not os.path.isfile(manifest_path):
                 raise ValueError(_("\nManifest '%s' does not exist.\n") %
                                  manifest_path)
-        
+
         config.set_service_props(self.name,
                                  {config.PROP_DEFAULT_MANIFEST: manifest_name})
-    
+
     def get_default_manifest(self):
         '''Return the name of the default for this service'''
         props = config.get_service_props(self.name)
@@ -1339,13 +1354,13 @@
 
 def get_a_free_tcp_port(hostname):
     ''' get next free tcp port
-        
+
         Looks for next free tcp port number, starting from 46501
-        
+
         Input:   hostname
         Returns: next free tcp port number if one found or
                  None if no free port found
-             
+
     '''
     # determine ports in use by other install services
     existing_ports = set()
@@ -1353,9 +1368,9 @@
     for svcname in allprops:
         port = config.get_service_port(svcname)
         existing_ports.add(int(port))
-    
+
     logging.debug("get_a_free_tcp_port, existing_ports=%s", existing_ports)
-    
+
     mysock = None
     for port in xrange(STARTING_PORT, ENDING_PORT):
         try:
@@ -1373,7 +1388,7 @@
     else:
         logging.debug("no available tcp port found")
         return None
-    
+
     return port
 
 
@@ -1389,7 +1404,7 @@
 
 def setup_dhcp_server(service, ip_start, ip_count, bootserver):
     '''Set-up DHCP server for given AI service and IP address range'''
-    
+
     _incomplete_msg = cw(_("\nThe install service has been created but the "
                            "DHCP configuration has not been completed. Please "
                            "see dhcpd(8) for further information.\n"))
@@ -1427,7 +1442,7 @@
             print >> sys.stderr, cw(_("\nUnable to add IP range: %s\n" % err))
             print >> sys.stderr, _incomplete_msg
             return
-    
+
     # Regardless of whether the IP arguments are passed or not, now check the
     # current DHCP configuration for default service configuration. If we're
     # creating a new default alias for this architecture, set this service's