7120099 "installadm list -p" should show criteria
authorTomas Dzik <Tomas.Dzik@oracle.com>
Sun, 15 Apr 2012 23:31:35 +0200
changeset 1636 40142742ced2
parent 1635 e546d83e74e0
child 1637 363448d5d44a
7120099 "installadm list -p" should show criteria
usr/src/cmd/installadm/list.py
usr/src/cmd/installadm/test/test_list.py
--- a/usr/src/cmd/installadm/list.py	Fri Apr 13 09:49:35 2012 -0700
+++ b/usr/src/cmd/installadm/list.py	Sun Apr 15 23:31:35 2012 +0200
@@ -45,6 +45,10 @@
 }
 
 STATUS_WORDS = [_('Status'), _('Default'), _('Inactive')]
+
+# Find the width of Status column
+STWIDTH = max([len(item) for item in STATUS_WORDS]) + 1
+
 DEFAULT = _("Default")
 IGNORED = _("Ignored")
 INACTIVE = _("Inactive")
@@ -72,11 +76,951 @@
         signal.signal(signal.SIGPIPE, signal.SIG_IGN)
 
 
+class PrintObject(object):
+    '''
+    Class containing basic accessor utility functions and functions common
+    for all descendants (criteria, manifests and profiles)
+    '''
+
+    @classmethod
+    def get_header_len(cls):
+        '''
+        Returns
+            length of header
+        '''
+        return len(cls.header)
+
+    def get_max_crit_len(self):
+        '''
+        Returns
+            length of the longest criteria name
+        '''
+        return self.max_crit_len
+
+    def get_has_crit(self):
+        '''
+        Returns
+            True if some criteria is non-empty, otherwise return False
+        '''
+        return self.has_crit
+
+
+class CriteriaPrintObject(PrintObject):
+    '''
+    Class representing set of criteria
+    Class constructor reads criteria from database.
+    '''
+
+    # header is common for all instances of this class
+    header = _('Criteria')
+
+    def order_criteria_list(self):
+        '''
+        Sorts the list of criteria in order in which criteria are printed.
+
+        Returns
+            ordered list of criteria - always contain arch, mac, ipv4
+        Raises
+            None
+        '''
+        ordered_keys = ['arch', 'mac', 'ipv4']
+        keys = self.crit.keys()
+        keys.sort()
+        for crit in keys:
+            if crit not in ordered_keys:
+                ordered_keys.append(crit)
+
+        return ordered_keys
+
+    @classmethod
+    def get_header(cls, indent, cwidth):
+        '''
+        Args
+            indent = string which should be printed before header
+            cwidth = width of criteria names
+
+        Returns
+            formatted header string suitable for printing
+        '''
+        return indent + cls.header.ljust(cwidth) + '\n'
+
+    @classmethod
+    def get_underline(cls, indent, cwidth):
+        '''
+        Args
+            indent = string which should be printed before underline
+            cwidth = width of criteria names
+
+        Returns
+            formatted string which should underline the header
+        '''
+        return indent + ('-' * len(cls.header)).ljust(cwidth) + '\n'
+
+    def get_lines(self, first_line_indent, other_lines_indent, width=None):
+        '''
+        Args
+            first_line_indent = string which should be printed before first
+                                criteria line
+            other_lines_indent = string which should be printed before other
+                                 lines
+            width = width of criteria names
+
+        Returns
+            string containing formatted criteria
+        '''
+
+        # If width is not set justify to longest criteria
+        if width is None:
+            width = self.max_crit_len
+
+        line = ''
+        first = True
+
+        # Always print criteria in the same order
+        ordered_keys = self.order_criteria_list()
+        for akey in ordered_keys:
+            val = self.crit.get(akey, '')
+            if val != '':
+                if first:
+                    indent = first_line_indent
+                    first = False
+                else:
+                    indent = other_lines_indent
+                line += indent + akey.ljust(width) + ' = ' + val + '\n'
+
+        if first:  # we didn't print anything so we must print at least None
+            line = first_line_indent + _('None') + '\n'
+
+        return line
+
+    def get_criteria_info(self, crit_dict):
+        '''
+        Iterates over the criteria which consists of a dictionary with
+        possibly arch, min memory, max memory, min ipv4, max ipv4, min mac,
+        max mac, cpu, platform, min network, max network and zonename
+        converting it into a dictionary with arch, mem, ipv4, mac, cpu,
+        platform, network and zonename.  Any min/max attributes are stored as
+        a range within the new dictionary.
+
+        Args
+            crit_dict = dictionary of the criteria
+
+        Returns
+            None
+
+        Raises
+            None
+        '''
+
+        self.crit = dict()
+        self.max_crit_len = 0
+
+        if crit_dict is None:
+            return
+
+        # self.criteria values are formatted strings, with possible endings
+        # such as MB.
+
+        for key in crit_dict.keys():
+            if key == 'service':
+                continue
+            is_range_crit = key.startswith('MIN') or key.startswith('MAX')
+            # strip off the MAX or MIN for a new keyname
+            if is_range_crit:
+                keyname = key[3:]  # strip off the MAX or MIN for a new keyname
+            else:
+                keyname = key
+            self.crit.setdefault(keyname, '')
+            db_value = crit_dict[key]
+            if not db_value and not is_range_crit:
+                # For non-range (value) criteria, None means it isn't set.
+                # For range criteria, None means unbounded if the other
+                # criteria in the MIN/MAX pair is set.
+                continue    # value criteria not set
+            self.max_crit_len = max(self.max_crit_len, len(keyname))
+            fmt_value = AIdb.formatValue(key, db_value)
+            if is_range_crit:
+                if not db_value:
+                    fmt_value = "unbounded"
+                if self.crit[keyname] != '':
+                    if self.crit[keyname] != fmt_value:  # dealing with range
+                        if key.startswith('MAX'):
+                            self.crit[keyname] = self.crit[keyname] + \
+                                                 ' - ' + fmt_value
+                        else:
+                            self.crit[keyname] = fmt_value + ' - ' + \
+                                                 self.crit[keyname]
+                    elif self.crit[keyname] == "unbounded":
+                        # MIN and MAX both unbounded, which means neither is
+                        # set in db. Clear crit value.
+                        self.crit[keyname] = ''   # no values for range
+                else:  # first value, not range yet
+                    self.crit[keyname] = fmt_value
+                    # if the partner MIN/MAX criterion is not set in the db,
+                    # handle now because otherwise it won't be processed.
+                    if key.startswith('MIN'):
+                        if 'MAX' + keyname not in crit_dict.keys():
+                            if fmt_value == "unbounded":
+                                self.crit[keyname] = ''
+                            else:
+                                self.crit[keyname] = self.crit[keyname] + \
+                                                     ' - unbounded'
+                    else:
+                        if 'MIN' + keyname not in crit_dict.keys():
+                            if fmt_value == "unbounded":
+                                self.crit[keyname] = ''
+                            else:
+                                self.crit[keyname] = 'unbounded - ' + \
+                                                     self.crit[keyname]
+            else:
+                self.crit[keyname] = fmt_value
+
+    def __init__(self, aiqueue, name, instance, dbtable):
+        '''
+        Reads criteria from database and stores them into class attribute
+
+        Args:
+            aiqueue = database request queue
+
+            name = either the name of the manifest or the name of
+                   the profile to which this set of criteria belongs
+
+            instance = instance number
+
+            dbtable = database table, distinguishing manifests from profiles
+                Assumed to be one of AIdb.MANIFESTS_TABLE or
+                AIdb.PROFILES_TABLE
+        '''
+        # Set to True if there is at least one non-empty criteria
+        self.has_crit = False
+        # Store criteria in dictionary
+        self.crit = dict()
+        # Initialize length of the longest criteria to length of 'None' word
+        self.max_crit_len = len(_('None'))
+        # We need non-human output to be able to distiguish empty criteria
+        criteria = AIdb.getTableCriteria(name, instance, aiqueue, dbtable,
+                                         humanOutput=False, onlyUsed=True)
+        if criteria is not None:
+            for key in criteria.keys():
+                if criteria[key] is not None:
+                    self.has_crit = True
+                break
+            if self.has_crit:
+                # We need criteria in human readable form to be able to
+                # print it
+                hrcrit = AIdb.getTableCriteria(name, instance, aiqueue,
+                                               dbtable,
+                                               humanOutput=True,
+                                               onlyUsed=True)
+                # convert criteria into printable values
+                self.get_criteria_info(hrcrit)
+
+
+class ManifestPrintObject(PrintObject):
+    '''
+    Class representing a manifest. This class also contains a list of criteria
+    associated with this manifest
+    '''
+
+    header = _('Manifest')
+
+    def __init__(self, aiqueue, name, default=False):
+        '''
+        Reads manifest from database
+
+        Args:
+            aiqueue = database request queue
+
+            name = name of the manifest
+
+            default = boolean whether this manifest is default or not
+        '''
+        # Save manifest name
+        self.name = name
+        # Manifest itself don't know whether it is default for its service so
+        # this information should be passed in constructor
+        self.default = default
+        # Each manifest can have several sets of criteria - put them into list
+        self.criteria = list()
+        # Length of the longest criteria
+        self.max_crit_len = 0
+        # One manifest can have associated more sets of criteria
+        instances = AIdb.numInstances(name, aiqueue)
+        for instance in range(0, instances):
+            self.criteria.append(CriteriaPrintObject(aiqueue, name, instance,
+                                          AIdb.MANIFESTS_TABLE))
+        # Check whether this manifest has at least one active criteria
+        # and find the longest one
+        self.has_crit = False
+        for crit in self.criteria:
+            self.has_crit = crit.get_has_crit()
+            if crit.get_max_crit_len() > self.max_crit_len:
+                self.max_crit_len = crit.get_max_crit_len()
+        # Set status
+        if self.default:
+            self.status = DEFAULT
+        else:
+            if not self.has_crit:
+                # It is not default and doesn't have criteria - it is Inactive
+                self.status = INACTIVE
+            else:
+                self.status = ''
+
+    @classmethod
+    def get_header(cls, indent, mwidth, cwidth):
+        '''
+        Args
+            indent = string which should be printed before header
+            mwidth = manifest name width
+            cwidth = width of criteria names
+
+        Returns
+            formatted header string suitable for printing
+        '''
+        hdr = indent + cls.header.ljust(mwidth) + \
+              _('Status').ljust(STWIDTH)
+
+        # Print just header of the first criteria
+        return CriteriaPrintObject.get_header(hdr, cwidth)
+
+    @classmethod
+    def get_underline(cls, indent, mwidth, cwidth):
+        '''
+        Args
+            indent = string which should be printed before underline
+            mwidth = manifest name width
+            cwidth = width of criteria names
+
+        Returns
+            formatted underline string suitable for printing
+        '''
+        hdr = indent + ('-' * len(cls.header)).ljust(mwidth) + \
+              ('-' * len(_('Status'))).ljust(STWIDTH)
+        return CriteriaPrintObject.get_underline(hdr, cwidth)
+
+    def get_lines(self, first_line_indent, other_lines_indent,
+                       delim='\n', mwidth=None, cwidth=None):
+        '''
+        Args
+            first_line_indent = string which should be printed before first
+                                line of output
+            other_lines_indent = string which should be printed before other
+                                 lines of output
+            mwidth = manifest name width
+            cwidth = width of criteria names
+
+        Returns
+            string containing formatted manifest together with it's associated
+            criteria
+        '''
+        if mwidth is None:
+            mwidth = len(self.name)
+
+        if cwidth is None:
+            cwidth = self.max_crit_len
+
+        fl = first_line_indent + self.name.ljust(mwidth) + \
+             self.status.ljust(STWIDTH)
+        ol = other_lines_indent + ''.ljust(mwidth) + ''.ljust(STWIDTH)
+
+        line = ''
+        first = True
+        for crit in self.criteria:
+            # Print delimiter before each line except the first one
+            if not first:
+                line += delim
+            # If default manifest has criteria, print Ignored before them
+            if self.default and self.has_crit:
+                line += fl + '(' + IGNORED + ':' + '\n'
+                fl = ol
+            line += crit.get_lines(fl, ol, width=cwidth)
+            if self.default and self.has_crit:
+                # Insert the right bracket before the last character (\n)
+                line = line[0:-1] + ')' + line[-1]
+            fl = ol
+            first = False
+        return line
+
+
+class ProfilePrintObject(PrintObject):
+    '''
+    Class representing a profile. This class also contains criteria
+    associated with this profile
+    '''
+
+    header = _('Profile')
+
+    def __init__(self, aiqueue, name):
+        '''
+        Reads profile from database
+
+        Args:
+            aiqueue = database request queue
+
+            name = name of the profile
+        '''
+        # Save profile name
+        self.name = name
+        self.criteria = CriteriaPrintObject(aiqueue, name, None,
+                                            AIdb.PROFILES_TABLE)
+        self.max_crit_len = self.criteria.get_max_crit_len()
+        self.has_crit = self.criteria.get_has_crit()
+
+    def get_lines(self, first_line_indent, other_lines_indent,
+                       pwidth=None, cwidth=None):
+        '''
+        Args
+            first_line_indent = string which should be printed before first
+                                criteria line
+            other_lines_indent = string which should be printed before other
+                                 lines
+            pwidth = profile names width
+            cwidth = width of criteria names
+
+        Returns
+            string containing formatted profile together with it's associated
+            criteria
+        '''
+        if pwidth is None:
+            pwidth = len(self.name)
+
+        if cwidth is None:
+            cwidth = len(self.max_crit_len)
+
+        fl = first_line_indent + self.name.ljust(pwidth)
+        ol = other_lines_indent + ' '.ljust(pwidth)
+        return self.criteria.get_lines(fl, ol, width=cwidth)
+
+    @classmethod
+    def get_header(cls, indent, pwidth, cwidth):
+        '''
+        Args
+            indent = string which should be printed before header
+            pwidth = profile name width
+            cwidth = width of criteria names
+
+        Returns
+            formatted header string suitable for printing
+        '''
+        hdr = indent + cls.header.ljust(pwidth)
+        return CriteriaPrintObject.get_header(hdr, cwidth)
+
+    @classmethod
+    def get_underline(cls, indent, pwidth, cwidth):
+        '''
+        Args
+            indent = string which should be printed before underline
+            pwidth = manifest name width
+            cwidth = width of criteria names
+
+        Returns
+            formatted underline string suitable for printing
+        '''
+        hdr = indent + ('-' * len(cls.header)).ljust(pwidth)
+        return CriteriaPrintObject.get_underline(hdr, cwidth)
+
+
+class ServicePrintObject(PrintObject):
+    '''
+    Common base class for classes representing one service
+    '''
+
+    def __init__(self, sname):
+        '''
+        Opens database for given service and sets database request queue
+        '''
+        self.name = sname
+        try:
+            self.service = AIService(sname)
+
+        except VersionError as err:
+            warn_version(err)
+            raise
+
+        path = self.service.database_path
+
+        if os.path.exists(path):
+            try:
+                maisql = AIdb.DB(path)
+                maisql.verifyDBStructure()
+                self.aiqueue = maisql.getQueue()
+
+            except StandardError as err:
+                sys.stderr.write(_('Error: AI database access error\n%s\n')
+                                    % err)
+                raise
+        else:
+            sys.stderr.write(_('Error: unable to locate AI database for "%s" '
+                               'on server\n') % sname)
+            # I can't read from service database and I should raise an error
+            # for this condition.
+            raise StandardError
+
+    def get_max_profman_len(self):
+        '''
+        Returns
+            length of the longest manifest/profile name
+        '''
+        return self.max_profman_len
+
+    def get_name(self):
+        '''
+        Returns
+            name of the service
+        '''
+        return self.name
+
+    def __str__(self):
+        if self.non_empty():
+            return self.print_header() + self.get_lines()
+        else:
+            return self.empty_msg % self.name
+
+
+class ServiceProfilePrint(ServicePrintObject):
+    '''
+    Class representing one service and its associated profiles
+    '''
+
+    empty_msg = _('There are no profiles configured for local service '
+                  '"%s".\n')
+
+    def __init__(self, sname):
+        '''
+        Reads profile names associated with this service from database,
+        creates objects for them and stores these objects into instance
+        attributes
+        '''
+        ServicePrintObject.__init__(self, sname)
+
+        self.profiles = list()
+        self.max_profman_len = 0
+        self.max_crit_len = 0
+
+        try:
+            # Read all profiles for this service
+            for name in AIdb.getNames(self.aiqueue, AIdb.PROFILES_TABLE):
+                # Record the longest profile name
+                if self.max_profman_len < len(name):
+                    self.max_profman_len = len(name)
+                profile = ProfilePrintObject(self.aiqueue, name)
+                # Record the longest criteria in this service
+                if profile.get_max_crit_len() > self.max_crit_len:
+                    self.max_crit_len = profile.get_max_crit_len()
+                self.profiles.append(profile)
+
+        except StandardError as err:
+            sys.stderr.write(_('Error: AI database access error\n%s\n')
+                                % err)
+            raise
+
+    @staticmethod
+    def get_header(indent, pwidth, cwidth):
+        '''
+        Args
+            indent = string which should be printed before header
+            pwidth = profile name width
+            cwidth = width of criteria names
+
+        Returns
+            formatted header string suitable for printing
+        '''
+        return ProfilePrintObject.get_header(indent, pwidth, cwidth)
+
+    @staticmethod
+    def get_underline(indent, pwidth, cwidth):
+        '''
+        Args
+            indent = string which should be printed before underline
+            pwidth = profile name width
+            cwidth = width of criteria names
+
+        Returns
+            formatted underline string suitable for printing
+        '''
+        return ProfilePrintObject.get_underline(indent, pwidth, cwidth)
+
+    def print_header(self):
+        '''
+        Returns underlined header justified according to length of header and
+        length of the longest printed profile and criteria
+
+        Returns
+            formatted underline string suitable for printing
+        '''
+        plen = max(ProfilePrintObject.get_header_len(), self.max_profman_len)
+        clen = max(CriteriaPrintObject.get_header_len(), self.max_crit_len)
+
+        line = self.get_header('', plen + 2, clen)
+        line += self.get_underline('', plen + 2, clen)
+
+        return line
+
+    def get_lines(self, first_line_indent='',
+                   other_lines_indent='', pwidth=None, cwidth=None):
+        '''
+        Args
+            first_line_indent = string which should be printed before first
+                                line
+            other_lines_indent = string which should be printed before other
+                                 lines
+            pwidth = profile names width
+            cwidth = width of criteria names
+
+        Returns
+            string containing formatted profiles together with their associated
+            criteria
+        '''
+        if pwidth is None:
+            # We want 2 spaces between columns
+            pwidth = self.max_profman_len + 2
+
+        if cwidth is None:
+            cwidth = self.max_crit_len
+
+        line = ''
+        fl = first_line_indent
+        ol = other_lines_indent
+        for prof in self.profiles:
+            line += prof.get_lines(fl, ol, pwidth=pwidth, cwidth=cwidth)
+            fl = ol
+
+        return line
+
+    def non_empty(self):
+        '''
+        Returns
+            True if this service has at least one profile, otherwise returns
+            False
+        '''
+        if self.profiles:
+            return True
+        else:
+            return False
+
+
+class ServiceManifestPrint(ServicePrintObject):
+    '''
+    Class representing one service and its associated manifests
+    '''
+
+    def __init__(self, sname):
+        '''
+        Reads manifest names associated with this service from database,
+        creates objects for them and stores these objects into instance
+        attributes. Manifests are sorted into 3 groups - Active, Inactive
+        and Default.
+        '''
+        ServicePrintObject.__init__(self, sname)
+
+        try:
+            default_mname = self.service.get_default_manifest()
+        except StandardError:
+            default_mname = ""
+
+        self.default_manifest = None
+        self.active_manifests = list()
+        self.inactive_manifests = list()
+        self.max_profman_len = 0
+        self.max_crit_len = 0
+
+        try:
+            # Read all manifests for this service
+            for name in AIdb.getNames(self.aiqueue, AIdb.MANIFESTS_TABLE):
+                if self.max_profman_len < len(name):
+                    self.max_profman_len = len(name)
+                if name == default_mname:
+                    manifest = ManifestPrintObject(self.aiqueue, name,
+                                             default=True)
+                    self.default_manifest = manifest
+                else:
+                    manifest = ManifestPrintObject(self.aiqueue, name)
+                    if manifest.get_has_crit():
+                        self.active_manifests.append(manifest)
+                    else:
+                        self.inactive_manifests.append(manifest)
+                if manifest.get_max_crit_len() > self.max_crit_len:
+                    self.max_crit_len = manifest.get_max_crit_len()
+
+        except StandardError as err:
+            sys.stderr.write(_('Error: AI database access error\n%s\n')
+                                % err)
+            raise
+
+    @staticmethod
+    def get_header(indent, mwidth, cwidth):
+        '''
+        Args
+            indent = string which should be printed before header
+            mwidth = manifest name width
+            cwidth = width of criteria names
+
+        Returns
+            formatted header string suitable for printing
+        '''
+        return ManifestPrintObject.get_header(indent, mwidth, cwidth)
+
+    @staticmethod
+    def get_underline(indent, mwidth, cwidth):
+        '''
+        Args
+            indent = string which should be printed before underline
+            mwidth = manifest name width
+            cwidth = width of criteria names
+
+        Returns
+            formatted underline string suitable for printing
+        '''
+        return ManifestPrintObject.get_underline(indent, mwidth, cwidth)
+
+    def print_header(self):
+        '''
+        Returns underlined header justified according to length of header and
+        length of the longest printed manifest and criteria
+
+        Returns
+            formatted underline string suitable for printing
+        '''
+        plen = max(ManifestPrintObject.get_header_len(), self.max_profman_len)
+        clen = max(CriteriaPrintObject.get_header_len(), self.max_crit_len)
+
+        return self.get_header('', plen + 2, clen) + \
+               self.get_underline('', plen + 2, clen)
+
+    def get_lines(self, first_line_indent='',
+                   other_lines_indent='', mwidth=None, cwidth=None):
+        '''
+        Args
+            first_line_indent = string which should be printed before first
+                                line
+            other_lines_indent = string which should be printed before other
+                                 lines
+            mwidth = manifest name width
+            cwidth = width of criteria names
+
+        Returns
+            string containing formatted manifests together with their
+            associated criteria
+        '''
+        if mwidth is None:
+            # We want 2 spaces between columns
+            mwidth = self.max_profman_len + 2
+
+        if cwidth is None:
+            cwidth = self.max_crit_len
+
+        line = ''
+        fl = first_line_indent
+        ol = other_lines_indent
+        # Print active manifests first
+        for manifest in self.active_manifests:
+            line += manifest.get_lines(fl, ol, mwidth=mwidth,
+                                                cwidth=cwidth)
+            fl = ol
+
+        # Then print default manifest
+        if self.default_manifest:
+            line += self.default_manifest.get_lines(fl, ol, mwidth=mwidth,
+                                                     cwidth=cwidth)
+            fl = ol
+
+        # Print inactive manifests last
+        for manifest in self.inactive_manifests:
+            line += manifest.get_lines(fl, ol, mwidth=mwidth,
+                                                cwidth=cwidth)
+            fl = ol
+
+        return line
+
+    def non_empty(self):
+        '''
+        Returns
+            True because each service has at least one Default manifest
+        '''
+        return True
+
+
+class ServicesList(PrintObject):
+    '''
+    Base class for list of services
+    '''
+
+    def get_lines(self, indent='', delim='\n',
+                    width=None, cwidth=None):
+        '''
+        '''
+        line = ''
+        first = True
+        for s in self.services:
+            # Print just services containing some profiles/manifests
+            if s.non_empty():
+                if not first:
+                    line += delim
+                line += s.get_name() + '\n'
+                line += s.get_lines(indent, indent, width, cwidth)
+                first = False
+
+        return line
+
+    def __str__(self):
+        '''
+        '''
+        if not self.services:
+            return self.empty_msg
+
+        ind = '   '
+
+        # Count the width of manifests/profiles column
+        j = max(self.max_name_len, len(ind) + self.max_profman_len,
+                self.get_header_len()) + 2
+        return self.get_header(j, self.max_crit_len) + \
+               self.get_underline(j, self.max_crit_len) + \
+               self.get_lines(ind, '\n', j - len(ind), self.max_crit_len)
+
+
+class ServicesManifestList(ServicesList):
+    '''
+    Class representing list of services. Services contain manifests
+    '''
+
+    # Message printed in case that there are no services configured
+    # (each service should have at least Default profile
+    empty_msg = _('There are no manifests configured for local services.\n')
+    header = _('Service/Manifest Name')
+
+    def __init__(self, srvcs, name=None):
+        '''
+        Reads dictionary of services and creates list of service objects
+
+        Args:
+            srvcs = dictionary of service properties
+        '''
+        self.services = list()
+        self.max_crit_len = 0
+        self.max_profman_len = 0
+        if name:
+            # We are going to print only one service
+            srvlist = [name]
+            self.max_name_len = len(name)
+        else:
+            self.max_name_len = len(max(srvcs.keys(), key=len))
+            srvlist = srvcs.keys()
+            srvlist.sort()
+        for s in srvlist:
+            try:
+                service = ServiceManifestPrint(s)
+            except (StandardError, VersionError):
+                # Ignore these errors and just skip these services
+                # Errors/Warnings should be already printed
+                pass
+            else:
+                if self.max_crit_len < service.get_max_crit_len():
+                    self.max_crit_len = service.get_max_crit_len()
+                if self.max_profman_len < service.get_max_profman_len():
+                    self.max_profman_len = service.get_max_profman_len()
+
+                if service.non_empty():
+                    self.services.append(service)
+
+    @classmethod
+    def get_header(cls, mwidth, cwidth):
+        '''
+        Args
+            mwidth = manifest name width
+            cwidth = width of criteria names
+
+        Returns
+            formatted header string suitable for printing
+        '''
+        hdr = cls.header.ljust(mwidth) + _('Status').ljust(STWIDTH)
+        return CriteriaPrintObject.get_header(hdr, cwidth)
+
+    @classmethod
+    def get_underline(cls, mwidth, cwidth):
+        '''
+        Args
+            mwidth = manifest name width
+            cwidth = width of criteria names
+
+        Returns
+            formatted underline string suitable for printing
+        '''
+        hdr = ('-' * len(cls.header)).ljust(mwidth) +\
+              ('-' * len(_('Status'))).ljust(STWIDTH)
+        return CriteriaPrintObject.get_underline(hdr, cwidth)
+
+
+class ServicesProfileList(ServicesList):
+    '''
+    Class representing list of services. Services contain profiles
+    '''
+
+    empty_msg = _('There are no profiles configured for local services.\n')
+    header = _('Service/Profile Name')
+
+    def __init__(self, srvcs, name=None):
+        '''
+        '''
+        self.services = list()
+        self.max_crit_len = 0
+        self.max_profman_len = 0
+        if name:
+            srvlist = [name]
+            self.max_name_len = len(name)
+            # There is different error msg if we are printing just one service
+            self.empty_msg = ServiceProfilePrint.empty_msg % name
+        else:
+            self.max_name_len = len(max(srvcs.keys(), key=len))
+            srvlist = srvcs.keys()
+            srvlist.sort()
+        for s in srvlist:
+            try:
+                service = ServiceProfilePrint(s)
+            except (StandardError, VersionError):
+                # Ignore these errors and just skip these services
+                # Errors/Warnings should be already printed
+                pass
+            else:
+                if self.max_crit_len < service.get_max_crit_len():
+                    self.max_crit_len = service.get_max_crit_len()
+                if self.max_profman_len < service.get_max_profman_len():
+                    self.max_profman_len = service.get_max_profman_len()
+
+                if service.non_empty():
+                    self.services.append(service)
+
+    @staticmethod
+    def get_header(pwidth, cwidth):
+        '''
+        Args
+            pwidth = profile width
+            cwidth = width of criteria names
+
+        Returns
+            formatted header string suitable for printing
+        '''
+        hdr = ServicesProfileList.header.ljust(pwidth)
+        return CriteriaPrintObject.get_header(hdr, cwidth)
+
+    @staticmethod
+    def get_underline(pwidth, cwidth):
+        '''
+        Args
+            pwidth = profile width
+            cwidth = width of criteria names
+
+        Returns
+            formatted underline string suitable for printing
+        '''
+        hdr = ('-' * len(ServicesProfileList.header)).ljust(pwidth)
+        return CriteriaPrintObject.get_underline(hdr, cwidth)
+
+
 def warn_version(version_err):
     '''Prints a short warning about version incompatibility to stderr
     for a given service. For any one invocation of "installadm list"
     the warning will only be printed once.
-    
+
     '''
     if version_err.service_name not in _WARNED_ABOUT:
         print >> sys.stderr, version_err.short_str()
@@ -355,7 +1299,7 @@
         if config.PROP_ALIAS_OF in serv:
             image_path = service.image.path
             serv[config.PROP_IMAGE_PATH] = image_path
-        
+
         info = dict()
         # if a service name is passed in then
         # ensure it matches the current name
@@ -377,7 +1321,7 @@
                 sdict[servicename] = slist
             else:
                 sdict[servicename] = [info]
-    
+
     return sdict, width, aliasofwidth
 
 
@@ -395,10 +1339,10 @@
 
     Raises
         None
-    
+
     """
     sdict, width, awidth = get_local_services(services, sname=name)
-    
+
     width = max(width, len(_('Service Name')))
     awidth = max(awidth, len(_('Alias Of')))
     fields = [[_('Service Name'), width]]
@@ -534,697 +1478,6 @@
     print
 
 
-def get_manifest_or_profile_names(services, dbtable):
-    """
-    Iterate through the services retrieving
-    all the stored manifest or profile names.
-
-    Args
-        services = dictionary of service properties
-        dbtable = database table, distinguishing manifests from profiles
-
-    Returns
-        a dictionary of service manifests or profiles within a list:
-
-            {
-                servicename1:
-                    [
-                        [name, has_criteria (boolean), {crit:value, ... }],
-                        ... 
-                    ],
-                ...
-            }
-
-        the width of the longest service name (swidth)
-
-        the width of the longest manifest name (mwidth)
-
-        the width of the longest criteria (cwidth)
-
-    Raises
-        None
-    """
-    swidth = 0
-    mwidth = 0
-    cwidth = 0
-    sdict = dict()
-    for sname in sorted(services.keys()):
-        try:
-            service = AIService(sname)
-        except VersionError as err:
-            warn_version(err)
-            continue
-        
-        path = service.database_path
-        
-        if os.path.exists(path):
-            try:
-                maisql = AIdb.DB(path)
-                maisql.verifyDBStructure()
-                aiqueue = maisql.getQueue()
-                swidth = max(len(sname), swidth)
-                if not AIdb.tableExists(aiqueue, dbtable):
-                    continue
-                for name in AIdb.getNames(aiqueue, dbtable):
-                    mwidth = max(len(name), mwidth)
-                    tdict = dict()
-                    if dbtable == 'manifests':
-                        instances = AIdb.numInstances(name, aiqueue)
-                        for instance in range(0, instances):
-                            criteria = AIdb.getTableCriteria(name,
-                                            instance, aiqueue, dbtable,
-                                            humanOutput=False,
-                                            onlyUsed=True)
-                            has_criteria = False
-                            if criteria is not None:
-                                for key in criteria.keys():
-                                    if criteria[key] is not None:
-                                        has_criteria = True
-                                        break
-                                if has_criteria:
-                                    # We need criteria in human readable form
-                                    hrcrit = AIdb.getTableCriteria(name,
-                                                  instance, aiqueue, dbtable,
-                                                  humanOutput=True,
-                                                  onlyUsed=True)
-                                    tdict, twidth = get_criteria_info(hrcrit)
-                                    cwidth = max(twidth, cwidth)
-                    else:
-                        criteria = AIdb.getTableCriteria(name,
-                                        None, aiqueue, dbtable,
-                                        humanOutput=False,
-                                        onlyUsed=True)
-                        has_criteria = False
-                        if criteria is not None:
-                            for key in criteria.keys():
-                                if criteria[key] is not None:
-                                    has_criteria = True
-                                    break
-                    if sname in sdict:
-                        slist = sdict[sname]
-                        slist.append([name, has_criteria, tdict])
-                        sdict[sname] = slist
-                    else:
-                        sdict[sname] = [[name, has_criteria, tdict]]
-            except StandardError as err:
-                sys.stderr.write(_('Error: AI database access error\n%s\n')
-                                   % err)
-                continue
-        else:
-            sys.stderr.write(_('Error: unable to locate AI database for "%s" '
-                               'on server\n') % sname)
-            continue
-
-    return sdict, swidth, mwidth, cwidth
-
-
-def get_criteria_info(crit_dict):
-    """
-    Iterates over the criteria which consists of a dictionary with
-    possibly arch, min memory, max memory, min ipv4, max ipv4, min mac,
-    max mac, cpu, platform, min network, max network and zonename converting
-    it into a dictionary with arch, mem, ipv4, mac, cpu, platform, network
-    and zonename.  Any min/max attributes are stored as a range within the
-    new dictionary.
-
-    Args
-        crit_dict = dictionary of the criteria
-
-    Returns
-        dictionary of combined min/max and other criteria, formatted
-           with possible endings such as MB
-        maximum criteria width
-
-    Raises
-        None
-    """
-
-    if crit_dict is None:
-        return dict(), 0
-
-    # tdict values are formatted strings, with possible endings
-    # such as MB.
-    tdict = dict()
-
-    crit_width = 0
-    for key in crit_dict.keys():
-        if key == 'service':
-            continue
-        is_range_crit = key.startswith('MIN') or key.startswith('MAX')
-        # strip off the MAX or MIN for a new keyname
-        if is_range_crit:
-            keyname = key[3:]  # strip off the MAX or MIN for a new keyname
-        else:
-            keyname = key
-        tdict.setdefault(keyname, '')
-        db_value = crit_dict[key]
-        if not db_value and not is_range_crit:
-            # For non-range (value) criteria, None means it isn't set.
-            # For range criteria, None means unbounded if the other
-            # criteria in the MIN/MAX pair is set.
-            continue    # value criteria not set
-        crit_width = max(crit_width, len(keyname))
-        fmt_value = AIdb.formatValue(key, db_value)
-        if is_range_crit:
-            if not db_value:
-                fmt_value = "unbounded"
-            if tdict[keyname] != '':
-                if tdict[keyname] != fmt_value:  # dealing with range
-                    if key.startswith('MAX'):
-                        tdict[keyname] = tdict[keyname] + ' - ' + fmt_value
-                    else:
-                        tdict[keyname] = fmt_value + ' - ' + tdict[keyname]
-                elif tdict[keyname] == "unbounded":
-                    # MIN and MAX both unbounded, which means neither is
-                    # set in db. Clear tdict value.
-                    tdict[keyname] = ''   # no values for range, reset tdict
-            else:  # first value, not range yet
-                tdict[keyname] = fmt_value
-                # if the partner MIN/MAX criterion is not set in the db,
-                # handle now because otherwise it won't be processed.
-                if key.startswith('MIN'):
-                    if 'MAX' + keyname not in crit_dict.keys():
-                        if fmt_value == "unbounded":
-                            tdict[keyname] = ''
-                        else:
-                            tdict[keyname] = tdict[keyname] + ' - unbounded'
-                else:
-                    if 'MIN' + keyname not in crit_dict.keys():
-                        if fmt_value == "unbounded":
-                            tdict[keyname] = ''
-                        else:
-                            tdict[keyname] = 'unbounded - ' + tdict[keyname]
-        else:
-            tdict[keyname] = fmt_value
-
-    return tdict, crit_width
-
-
-def get_mfest_or_profile_criteria(sname, services, dbtable):
-    """
-    Iterate through all the manifests or profiles for the named service (sname)
-    pointed to by the SCF service.
-
-    Args
-        sname = service name
-        services = config.get_all_service_props()
-        dbtable = database table, distinguishing manifests from profiles
-            Assumed to be one of AIdb.MANIFESTS_TABLE or AIdb.PROFILES_TABLE
-
-    Returns
-        a dictionary of the criteria for the named service within a list:
-
-            {
-                servicename1:[
-                             { 'arch':arch1, 'mem':memory1, 'ipv4':ipaddress1,
-                               'mac':macaddr1, 'platform':platform1,
-                               'network':network1, 'cpu':cpu1, 'zonename':z1 },
-                             ...
-                            ]
-            }
-
-        * Note1: platform, network and cpu are currently not-implemented
-                 upstream.
-        * Note2: could simply use a list of dictionaries but implemented as a
-                 dictionary of a list of dictionary which will allow for
-                 multiple services to be listed at the same time.
-
-        width of longest manifest or profile name
-
-        width of longest criteria
-
-    Raises
-        None
-    """
-    sdict = dict()
-    width = 0
-    cwidth = 0
-    # ensure the named service is in our service dictionary.
-    lservices = services.keys()
-    if sname in lservices:
-        try:
-            path = AIService(sname).database_path
-        except VersionError as version_err:
-            warn_version(version_err)
-            return sdict, width, cwidth
-
-        if os.path.exists(path):
-            try:
-                maisql = AIdb.DB(path)
-                maisql.verifyDBStructure()
-                aiqueue = maisql.getQueue()
-                if dbtable == AIdb.MANIFESTS_TABLE:
-                    for name in AIdb.getNames(aiqueue, dbtable):
-                        sdict[name] = list()
-                        instances = AIdb.numInstances(name, aiqueue)
-                        for instance in range(0, instances):
-                            width = max(len(name), width)
-                            criteria = AIdb.getManifestCriteria(name,
-                                            instance, aiqueue,
-                                            humanOutput=True,
-                                            onlyUsed=True)
-                            if criteria:
-                                tdict, twidth = get_criteria_info(criteria)
-                                cwidth = max(twidth, cwidth)
-                                sdict[name].append(tdict)
-                elif dbtable == AIdb.PROFILES_TABLE:
-                    for name in AIdb.getNames(aiqueue, dbtable):
-                        sdict[name] = list()
-                        criteria = AIdb.getProfileCriteria(name,
-                                        aiqueue,
-                                        humanOutput=True,
-                                        onlyUsed=True)
-                        width = max(len(name), width)
-                        tdict, twidth = get_criteria_info(criteria)
-                        cwidth = max(twidth, cwidth)
-
-                        sdict[name].append(tdict)
-                else:
-                    raise ValueError("Invalid value for dbtable: %s" % dbtable)
-
-            except StandardError as err:
-                sys.stderr.write(_('Error: AI database access error\n%s\n')
-                                   % err)
-                sys.exit(1)
-        else:
-            sys.stderr.write(_('Error: unable to locate AI database on server '
-                               'for %s\n') % sname)
-            sys.exit(1)
-
-    return sdict, width, cwidth
-
-
-def print_service_manifests(sdict, sname, width, swidth, cwidth):
-    """
-    Iterates over the criteria dictionary printing each non blank
-    criteria.  The manifest dictionary is populated via
-    get_mfest_or_profile_criteria().
-
-    Args
-        sdict = criteria dictionary
-                (same as in get_mfest_or_profile_criteria() description)
-
-        sname = name of service
-
-        width = widest manifest name
-
-        swidth = width of status column
-
-        cwidth = widest criteria name (0 if no criteria)
-
-    Returns
-        None
-
-    Raises
-        None
-    """
-    default_mfest = None
-    inactive_mfests = list()
-    active_mfests = list()
-
-    width += 1
-    swidth += 1
-
-    mnames = sdict.keys()
-    if not mnames:
-        return
-    mnames.sort()
-
-    try:
-        default_mname = AIService(sname).get_default_manifest()
-    except StandardError:
-        default_mname = ""
-
-    ordered_keys = ['arch', 'mac', 'ipv4']
-    if cwidth > 0:
-        # Criteria are present.
-        keys = sdict[mnames[0]][0].keys()
-        keys.sort()
-        for akey in keys:
-            if akey not in ordered_keys:
-                ordered_keys.append(akey)
-
-    for name in mnames:
-        manifest_list = [name]
-        if cwidth > 0:
-            for ldict in sdict[name]:
-                for akey in ordered_keys:
-                    if akey in ldict and ldict[akey] != '':
-                        manifest_list.append(akey.ljust(cwidth) + ' = ' +
-                                             ldict[akey])
-        if name == default_mname:
-            default_mfest = manifest_list
-        elif len(manifest_list) == 1:
-            inactive_mfests.append(manifest_list)
-        else:
-            active_mfests.append(manifest_list)
-
-    for mfest in active_mfests:
-        # Active manifests have at least one criterion.
-        print mfest[0].ljust(width) + ''.ljust(swidth) + mfest[1]
-        for other_crit in range(2, len(mfest)):
-            print ' '.ljust(width + swidth) + mfest[other_crit]
-        print
-    if default_mfest:
-        # Since 'Default' is used in status column, it is in STATUS_WORDS
-        # and so swidth accommodates it.
-        first_line = default_mfest[0].ljust(width) + \
-            DEFAULT.ljust(swidth)
-        if len(default_mfest) > 1:
-            first_line += "(" + IGNORED + ": " + default_mfest[1] + ")"
-        else:
-            first_line += "None"
-        print first_line
-        for other_crit in range(2, len(default_mfest)):
-            print ''.ljust(width + swidth) + \
-                "(" + IGNORED + ": " + default_mfest[other_crit] + ")"
-        print
-    for mfest in inactive_mfests:
-        # Since 'Inactive' is used in status column, it is in STATUS_WORDS.
-        # and so swidth accommodates it.
-        print mfest[0].ljust(width) + INACTIVE.ljust(swidth) + \
-            _("None")
-        print
-
-
-def print_service_profiles(sdict, width, cwidth):
-    """
-    Iterates over the criteria dictionary printing each non blank
-    criteria.  The profile dictionary is populated via
-    get_mfest_or_profile_criteria().
-
-    Args
-        sdict = criteria dictionary
-                (same as in get_mfest_or_profile_criteria() description)
-
-        width = widest profile name
-
-        cwidth = widest criteria name (0 if no criteria)
-
-    Returns
-        None
-
-    Raises
-        None
-    """
-    pnames = sdict.keys()
-    if not pnames:
-        return
-    pnames.sort()
-
-    ordered_keys = ['arch', 'mac', 'ipv4']
-    if cwidth > 0:
-        # Criteria are present.
-        keys = sdict[pnames[0]][0].keys()
-        keys.sort()
-        for akey in keys:
-            if akey not in ordered_keys:
-                ordered_keys.append(akey)
-
-    for name in pnames:
-        print name.ljust(width),
-        first = True
-        if cwidth > 0:
-            for ldict in sdict[name]:
-                for akey in ordered_keys:
-                    if akey in ldict and ldict[akey] != '':
-                        if not first:
-                            print ''.ljust(width),
-                        first = False
-                        print akey.ljust(cwidth) + ' = ' + ldict[akey]
-        # Flush line if no criteria displayed.
-        if first:
-            print "None"
-        print
-
-
-def print_local_manifests(sdict, smwidth, mfindent, stwidth, cwidth):
-    """
-    Iterates over the name dictionary printing each
-    manifest or criteria within the dictionary.  The name dictionary
-    is populated via get_manifest_or_profile_names().
-
-    Args
-        sdict = service manifest dictionary
-
-            {
-                'servicename1':
-                    [
-                        [ manifestfile1, has_criteria (boolean), {} ],
-                        ...
-                    ],
-                ...
-            }
-
-        smwidth = the length of the widest service or manifest name
-
-        mfindent = how many spaces will be manifest name indented
-
-        stwidth = width of status column
-
-        cwidth = the length of the widest criteria
-
-    Returns
-        None
-
-    Raises
-        None
-    """
-
-    tkeys = sdict.keys()
-    tkeys.sort()
-    smwidth += 1
-    stwidth += 1
-    for akey in tkeys:
-        default_mfest = None
-        inactive_mfests = list()
-        active_mfests = list()
-        try:
-            default_mname = AIService(akey).get_default_manifest()
-        except StandardError:
-            default_mname = ""
-        for manifest_item in sdict[akey]:
-            # manifest_items are a list of
-            # [ name, number of criteria, criteria_dictionary ]
-
-            if manifest_item[0] == default_mname:
-                default_mfest = manifest_item  # There could be max 1 default
-            elif manifest_item[1]:  # has_criteria and not default
-                active_mfests.append(manifest_item)
-            else:
-                inactive_mfests.append(manifest_item)
-
-        print akey  # print service name on separate line
-
-        line = ''.ljust(mfindent)  # Manifest is indented
-        for manifest_i in active_mfests:
-            line += manifest_i[0].ljust(smwidth - mfindent)  # Manifest
-            line += ''.ljust(stwidth)  # Status is empty for active mfests
-            ordered_keys = ['arch', 'mac', 'ipv4']
-            keys = manifest_i[2].keys()
-            keys.sort()
-            for k in keys:
-                if k not in ordered_keys:
-                    ordered_keys.append(k)
-            crit_printed = False
-            for k in ordered_keys:
-                if k in manifest_i[2] and manifest_i[2][k] != '':
-                    line += k.ljust(cwidth) + ' = ' + manifest_i[2][k]
-                    print line
-                    crit_printed = True
-                    line = ''.ljust(mfindent) + \
-                        ''.ljust(smwidth - mfindent) + ''.ljust(stwidth)
-            if not crit_printed:
-                line += _("None")
-                print line
-            print  # Blank line after each manifest
-            line = ''.ljust(mfindent)
-
-        if default_mfest:
-            line += default_mfest[0].ljust(smwidth - mfindent)  # Manifest name
-            line += DEFAULT.ljust(stwidth)  # Status is Default
-            # Default manifest can have ignored criteria
-            ordered_keys = ['arch', 'mac', 'ipv4']
-            keys = default_mfest[2].keys()
-            keys.sort()
-            for k in keys:
-                if k not in ordered_keys:
-                    ordered_keys.append(k)
-            crit_printed = False
-            for k in ordered_keys:
-                if k in default_mfest[2] and default_mfest[2][k] != '':
-                    line += '(' + IGNORED + ': ' + k.ljust(cwidth) + \
-                        ' = ' + default_mfest[2][k] + ')'
-                    print line
-                    crit_printed = True
-                    line = ''.ljust(mfindent) + \
-                        ''.ljust(smwidth - mfindent) + ''.ljust(stwidth)
-            if not crit_printed:
-                line += _("None")
-                print line
-            line = ''.ljust(mfindent)
-            print  # Blank line after each manifest
-        for manifest_i in inactive_mfests:
-            line += manifest_i[0].ljust(smwidth - mfindent)  # Manifest
-            line += INACTIVE.ljust(stwidth)
-            line += _("None")  # Inactive manifests have no criteria
-            print line
-            print  # Blank line after each manifest
-            line = ''.ljust(mfindent)
-
-
-def print_local_profiles(sdict, swidth):
-    """
-    Iterates over the name dictionary printing each
-    profile or criteria within the dictionary.  The name dictionary
-    is populated via get_manifest_or_profile_names().
-
-    Args
-        sdict = service profile dictionary
-
-            {
-                'servicename1':
-                    [
-                        [ profile1, has_criteria (boolean) ],
-                        ...
-                    ],
-                ...
-            }
-
-        swidth = the length of the widest service name
-
-    Returns
-        None
-
-    Raises
-        None
-    """
-    tkeys = sdict.keys()
-    tkeys.sort()
-    swidth += 1
-    for akey in tkeys:
-        first_for_service = True
-        # profile_items are a list of [ name, number of criteria ]
-        for profile_item in sdict[akey]:
-            if first_for_service:
-                print akey.ljust(swidth) + profile_item[0]
-                first_for_service = False
-            else:
-                print ''.ljust(swidth) + profile_item[0]
-    print
-
-
-def list_local_manifests(services, name=None):
-    """
-    list the local manifests.  If name is not passed in then
-    print all the local manifests. List also the associated
-    manifest criteria.
-
-    Args
-        services = config.get_all_service_props()
-        name = service name
-
-    Returns
-        None
-
-    Raises
-        None
-    """
-    # list -m
-    if not name:
-        sdict, swidth, mwidth, cwidth = get_manifest_or_profile_names(services,
-                                                     AIdb.MANIFESTS_TABLE)
-        if not sdict:
-            output = _('There are no manifests configured for local '
-                       'services.\n')
-            sys.stdout.write(output)
-            return
-        # manifest should be indented 3 spaces
-        mfindent = 3
-        smwidth = max(swidth, mwidth + mfindent,
-            len(_('Service/Manifest Name'))) + 1
-        fields = [[_('Service/Manifest Name'), smwidth]]
-        stwidth = max([len(item) for item in STATUS_WORDS]) + 1
-        fields.extend([[_('Status'), stwidth]])
-        fields.extend([[_('Criteria'), len(_('Criteria'))]])
-
-        do_header(fields)
-        # set the SIGPIPE signal to SIG_DFL
-        with SigpipeHandler():
-            print_local_manifests(sdict, smwidth, mfindent, stwidth, cwidth)
-    # list -m -n <service>
-    else:
-        sdict, mwidth, cwidth = \
-            get_mfest_or_profile_criteria(name, services, AIdb.MANIFESTS_TABLE)
-        if not sdict:
-            output = _('There are no manifests configured for local service, '
-                       '"%s".\n') % name
-            sys.stdout.write(output)
-            return
-
-        mwidth = max(mwidth, len(_('Manifest'))) + 1
-        fields = [[_('Manifest'), mwidth]]
-        stwidth = max([len(item) for item in STATUS_WORDS]) + 1
-        fields.extend([[_('Status'), stwidth]])
-        fields.extend([[_('Criteria'), len(_('Criteria'))]])
-
-        do_header(fields)
-        print_service_manifests(sdict, name, mwidth, stwidth, cwidth)
-
-
-def list_local_profiles(linst, name=None):
-    """
-    list the local profiles.  If name is not passed in then
-    print all the local profiles.  Otherwise list the named
-    service's profiles' criteria.
-
-    Args
-        inst = smf.AISCF()
-        name = service name
-
-    Returns
-        None
-
-    Raises
-        None
-    """
-    # list -p
-    if not name:
-        sdict, swidth, mwidth, cwidth = \
-                get_manifest_or_profile_names(linst, AIdb.PROFILES_TABLE)
-        if not sdict:
-            output = _('There are no profiles configured for local '
-                       'services.\n')
-            sys.stdout.write(output)
-            return
-
-        swidth = max(swidth, len(_('Service Name'))) + 1
-        fields = [[_('Service Name'), swidth]]
-        mwidth = max(mwidth, len(_('Profile')))
-        fields.extend([[_('Profile'), mwidth]])
-
-        do_header(fields)
-        # set the SIGPIPE signal to SIG_DFL
-        with SigpipeHandler():
-            print_local_profiles(sdict, swidth)
-    # list -p -n <service>
-    else:
-        sdict, mwidth, cwidth = \
-            get_mfest_or_profile_criteria(name, linst, AIdb.PROFILES_TABLE)
-        if not sdict:
-            output = _('There are no profiles configured for local service, '
-                       '"%s".\n') % name
-            sys.stdout.write(output)
-            return
-
-        mwidth = max(mwidth, len(_('Profile'))) + 1
-        fields = [[_('Profile'), mwidth]]
-        fields.extend([[_('Criteria'), len(_('Criteria'))]])
-
-        do_header(fields)
-        print_service_profiles(sdict, mwidth, cwidth)
-
-
 def do_list(cmd_options=None):
     '''
     List information about AI services, clients, and manifests.
@@ -1265,12 +1518,12 @@
         if options.manifest:
             if options.client:
                 print
-            list_local_manifests(services, name=options.service)
+            print ServicesManifestList(services, name=options.service)
         # list -p
         if options.profile:
             if options.client or options.manifest:
                 print
-            list_local_profiles(services, name=options.service)
+            print ServicesProfileList(services, name=options.service)
 
 
 if __name__ == '__main__':
--- a/usr/src/cmd/installadm/test/test_list.py	Fri Apr 13 09:49:35 2012 -0700
+++ b/usr/src/cmd/installadm/test/test_list.py	Sun Apr 15 23:31:35 2012 +0200
@@ -19,7 +19,7 @@
 #
 # CDDL HEADER END
 #
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
 #
 
 '''
@@ -33,60 +33,70 @@
 import osol_install.auto_install.list as list
 
 
+class DummyCriteriaPrint(list.CriteriaPrintObject):
+    def __init__(self):
+        pass
+
+
 class GetCriteriaInfo(unittest.TestCase):
     '''Tests for get_criteria_info.'''
 
+    def setUp(self):
+        '''unit test set up'''
+        self.crit = DummyCriteriaPrint()
+
     def test_list_range_bounded_both_sides(self):
         '''Ensure range bounded with different values list correctly'''
-        mycriteria = {"MAXmem": 2048, "MINmem": 512} 
-        cri_dict, width = list.get_criteria_info(mycriteria) 
-        self.assertEquals(width, len("mem"))
-        self.assertEquals(cri_dict["mem"], "512 MB - 2048 MB")
+        mycriteria = {"MAXmem": 2048, "MINmem": 512}
+        self.crit.get_criteria_info(mycriteria)
+        self.assertEquals(self.crit.max_crit_len, len("mem"))
+        self.assertEquals(self.crit.crit["mem"], "512 MB - 2048 MB")
 
     def test_list_range_bounded_same_on_both_sides(self):
         '''Ensure range bounded with same values list correctly'''
-        mycriteria = {"MINmac": u'00ABCDEF0122', "MAXmac": u'00ABCDEF0122'} 
-        cri_dict, width = list.get_criteria_info(mycriteria) 
-        self.assertEquals(width, len("mac"))
-        self.assertEquals(cri_dict["mac"], "00:AB:CD:EF:01:22")
+        mycriteria = {"MINmac": u'00ABCDEF0122', "MAXmac": u'00ABCDEF0122'}
+        self.crit.get_criteria_info(mycriteria)
+        self.assertEquals(self.crit.max_crit_len, len("mac"))
+        self.assertEquals(self.crit.crit["mac"], "00:AB:CD:EF:01:22")
 
     def test_list_range_unbounded_min_side(self):
         '''Ensure range with unbounded minimum list correctly'''
-        mycriteria = {"MAXipv4": '124213023291'} 
-        cri_dict, width = list.get_criteria_info(mycriteria) 
-        self.assertEquals(width, len("ipv4"))
-        self.assertEquals(cri_dict["ipv4"], "unbounded - 124.213.23.291")
+        mycriteria = {"MAXipv4": '124213023291'}
+        self.crit.get_criteria_info(mycriteria)
+        self.assertEquals(self.crit.max_crit_len, len("ipv4"))
+        self.assertEquals(self.crit.crit["ipv4"], "unbounded - 124.213.23.291")
 
     def test_list_range_unbounded_max_side(self):
         '''Ensure range with unbounded maximum list correctly'''
-        mycriteria = {"MINmem": 2048} 
-        cri_dict, width = list.get_criteria_info(mycriteria) 
-        self.assertEquals(width, len("mem"))
-        self.assertEquals(cri_dict["mem"], "2048 MB - unbounded")
+        mycriteria = {"MINmem": 2048}
+        self.crit.get_criteria_info(mycriteria)
+        self.assertEquals(self.crit.max_crit_len, len("mem"))
+        self.assertEquals(self.crit.crit["mem"], "2048 MB - unbounded")
 
     def test_list_non_range_criteria(self):
         '''Ensure non-range criteria lists correctly'''
         myplatform = "SUNW,Sun-Fire-V250"
-        mycriteria = {"platform": myplatform} 
-        cri_dict, width = list.get_criteria_info(mycriteria) 
-        self.assertEquals(width, len("platform"))
-        self.assertEquals(cri_dict["platform"], myplatform)
+        mycriteria = {"platform": myplatform}
+        self.crit.get_criteria_info(mycriteria)
+        self.assertEquals(self.crit.max_crit_len, len("platform"))
+        self.assertEquals(self.crit.crit["platform"], myplatform)
 
     def test_list_setting_none(self):
         '''Ensure None setting handled correctly'''
 
-        mycriteria = {"arch": "sun4u", "cpu": None} 
-        cri_dict, width = list.get_criteria_info(mycriteria) 
-        self.assertEquals(cri_dict["arch"], "sun4u")
-        self.assertEquals(cri_dict["cpu"], "")
+        mycriteria = {"arch": "sun4u", "cpu": None}
+        self.crit.get_criteria_info(mycriteria)
+        self.assertEquals(self.crit.crit["arch"], "sun4u")
+        self.assertEquals(self.crit.crit["cpu"], "")
 
     def test_width(self):
         '''Ensure width returned correctly'''
 
-        mycriteria = {"arch": "sun4v", "platform":"SUNW,Sun-Fire-V250",
-                      "cpu": "sparc"} 
-        cri_dict, width = list.get_criteria_info(mycriteria) 
-        self.assertEquals(width, len("platform"))
+        mycriteria = {"arch": "sun4v", "platform": "SUNW,Sun-Fire-V250",
+                      "cpu": "sparc"}
+        self.crit.get_criteria_info(mycriteria)
+        self.assertEquals(self.crit.max_crit_len, len("platform"))
+
 
 class ParseOptions(unittest.TestCase):
     '''Tests for parse_options.'''
@@ -116,15 +126,15 @@
         '''Ensure valid options ok'''
         myargs = []
         options = list.parse_options(cmd_options=myargs)
-        self.assertFalse(options.client) 
-        self.assertFalse(options.manifest) 
-        self.assertFalse(options.service) 
+        self.assertFalse(options.client)
+        self.assertFalse(options.manifest)
+        self.assertFalse(options.service)
 
         myargs = ["-m", "-c", "-n", "mysvc"]
         options = list.parse_options(cmd_options=myargs)
-        self.assertTrue(options.client) 
-        self.assertTrue(options.manifest) 
-        self.assertEqual(options.service, "mysvc") 
+        self.assertTrue(options.client)
+        self.assertTrue(options.manifest)
+        self.assertEqual(options.service, "mysvc")
 
 
 if __name__ == '__main__':