Attached is the CLI code tarball. It is documented at http://test.kernel.org/autotest/CLIHowTo

From: jmeurin@google.com



git-svn-id: http://test.kernel.org/svn/autotest/trunk@1950 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/cli/host.py b/cli/host.py
new file mode 100755
index 0000000..a2de6d8
--- /dev/null
+++ b/cli/host.py
@@ -0,0 +1,405 @@
+#
+# Copyright 2008 Google Inc. All Rights Reserved.
+
+"""
+The host module contains the objects and method used to
+manage a host in Autotest.
+
+The valid actions are:
+create:  adds host(s)
+delete:  deletes host(s)
+list:    lists host(s)
+stat:    displays host(s) information
+mod:     modifies host(s)
+jobs:    lists all jobs that ran on host(s)
+
+The common options are:
+-M|--mlist:   file containing a list of machines
+
+
+stat as has additional options:
+--lock/-l:      Locks host(s)
+--unlock/-u:    Unlock host(s)
+--ready/-y:     Marks host(s) ready
+--dead/-d:      Marks host(s) dead
+
+See topic_common.py for a High Level Design and Algorithm.
+
+"""
+
+import os, sys
+from autotest_lib.cli import topic_common, action_common
+
+
+class host(topic_common.atest):
+    """Host class
+    atest host [create|delete|list|stat|mod|jobs] <options>"""
+    usage_action = '[create|delete|list|stat|mod|jobs]'
+    topic = msg_topic = 'host'
+    msg_items = '<hosts>'
+
+
+    def __init__(self):
+        """Add to the parser the options common to all the
+        host actions"""
+        super(host, self).__init__()
+
+        self.parser.add_option('-M', '--mlist',
+                               help='File listing the machines',
+                               type='string',
+                               default=None,
+                               metavar='MACHINE_FLIST')
+
+
+    def parse(self, flists=None, req_items='hosts'):
+        """Consume the common host options"""
+        if flists:
+            flists.append(('hosts', 'mlist', '', True))
+        else:
+            flists = [('hosts', 'mlist', '', True)]
+        return self.parse_with_flist(flists, req_items)
+
+
+    def _parse_lock_options(self, options):
+        if options.lock and options.unlock:
+            self.invalid_syntax('Only specify one of '
+                                '--lock and --unlock.')
+
+        if options.lock:
+            self.data['locked'] = True
+            self.messages.append('Locked host')
+        elif options.unlock:
+            self.data['locked'] = False
+            self.messages.append('Unlocked host')
+
+
+    def _cleanup_labels(self, labels, platform=None):
+        """Removes the platform label from the overall labels"""
+        if platform:
+            return [label for label in labels
+                    if label != platform]
+        else:
+            try:
+                return [label for label in labels
+                        if not label['platform']]
+            except TypeError:
+                # This is a hack - the server will soon
+                # do this, so all this code should be removed.
+                return labels
+
+
+    def get_items(self):
+        return self.hosts
+
+
+class host_help(host):
+    """Just here to get the atest logic working.
+    Usage is set by its parent"""
+    pass
+
+
+class host_list(action_common.atest_list, host):
+    """atest host list [--mlist <file>|<hosts>] [--label <label>]
+       [--status <status1,status2>]"""
+
+    def __init__(self):
+        super(host_list, self).__init__()
+
+        self.parser.add_option('-b', '--label',
+                               help='Only list hosts with this label')
+        self.parser.add_option('-s', '--status',
+                               help='Only list hosts with this status')
+
+
+    def parse(self):
+        """Consume the specific options"""
+        (options, leftover) = super(host_list, self).parse(req_items=None)
+        self.label = options.label
+        self.status = options.status
+        return (options, leftover)
+
+
+    def execute(self):
+        filters = {}
+        check_results = {}
+        if self.hosts:
+            filters['hostname__in'] = self.hosts
+            check_results['hostname__in'] = 'hostname'
+        if self.label:
+            filters['labels__name'] = self.label
+            check_results['labels__name'] = None
+        if self.status:
+            filters['status__in'] = self.status.split(',')
+            check_results['status__in'] = 'status'
+        return super(host_list, self).execute(op='get_hosts',
+                                              filters=filters,
+                                              check_results=check_results)
+
+
+    def output(self, results):
+        if results:
+            # Remove the platform from the labels.
+            for result in results:
+                result['labels'] = self._cleanup_labels(result['labels'],
+                                                        result['platform'])
+        super(host_list, self).output(results,
+                                      keys=['hostname', 'status',
+                                            'locked', 'platform',
+                                            'labels'])
+
+
+class host_stat(host):
+    """atest host stat --mlist <file>|<hosts>"""
+    usage_action = 'stat'
+
+    def execute(self):
+        results = []
+        # Convert wildcards into real host stats.
+        existing_hosts = []
+        for host in self.hosts:
+            if host.endswith('*'):
+                stats = self.execute_rpc('get_hosts',
+                                         hostname__startswith=host.rstrip('*'))
+                if len(stats) == 0:
+                    self.failure('No hosts matching %s' % host, item=host,
+                                 what_failed='Failed to stat')
+                    continue
+            else:
+                stats = self.execute_rpc('get_hosts', hostname=host)
+                if len(stats) == 0:
+                    self.failure('Unknown host %s' % host, item=host,
+                                 what_failed='Failed to stat')
+                    continue
+            existing_hosts.extend(stats)
+
+        for stat in existing_hosts:
+            host = stat['hostname']
+            # The host exists, these should succeed
+            acls = self.execute_rpc('get_acl_groups', hosts__hostname=host)
+
+            labels = self.execute_rpc('get_labels', host__hostname=host)
+            results.append ([[stat], acls, labels])
+        return results
+
+
+    def output(self, results):
+        for stats, acls, labels in results:
+            print '-'*5
+            self.print_fields(stats,
+                              keys=['hostname', 'platform',
+                                    'status', 'locked', 'locked_by'])
+            self.print_by_ids(acls, 'ACLs', line_before=True)
+            labels = self._cleanup_labels(labels)
+            self.print_by_ids(labels, 'Labels', line_before=True)
+
+
+class host_jobs(host):
+    """atest host jobs --mlist <file>|<hosts>"""
+    usage_action = 'jobs'
+
+    def execute(self):
+        results = []
+        real_hosts = []
+        for host in self.hosts:
+            if host.endswith('*'):
+                stats = self.execute_rpc('get_hosts',
+                                         hostname__startswith=host.rstrip('*'))
+                if len(stats) == 0:
+                    self.failure('No host matching %s' % host, item=host,
+                                 what_failed='Failed to stat')
+                [real_hosts.append(stat['hostname']) for stat in stats]
+            else:
+                real_hosts.append(host)
+
+        for host in real_hosts:
+            queue_entries = self.execute_rpc('get_host_queue_entries',
+                                             host__hostname=host)
+            queue_entries.sort(key=lambda qe: qe['job']['id'])
+            queue_entries.reverse()
+            jobs = []
+            for entry in queue_entries:
+                job = {'job_id': entry['job']['id'],
+                       'job_owner': entry['job']['owner'],
+                       'job_name': entry['job']['name'],
+                       'status': entry['status']}
+                jobs.append(job)
+            results.append((host, jobs))
+        return results
+
+
+    def output(self, results):
+        for host, jobs in results:
+            print '-'*5
+            print 'Hostname: %s' % host
+            self.print_table(jobs, keys_header=['job_id',
+                                                 'job_owner',
+                                                 'job_name',
+                                                 'status'])
+
+
+class host_mod(host):
+    """atest host mod --lock|--unlock|--ready|--dead
+    --mlist <file>|<hosts>"""
+    usage_action = 'mod'
+
+    def __init__(self):
+        """Add the options specific to the mod action"""
+        self.data = {}
+        self.messages = []
+        super(host_mod, self).__init__()
+        self.parser.add_option('-y', '--ready',
+                               help='Mark this host ready',
+                               action='store_true')
+        self.parser.add_option('-d', '--dead',
+                               help='Mark this host dead',
+                               action='store_true')
+
+        self.parser.add_option('-l', '--lock',
+                               help='Lock hosts',
+                               action='store_true')
+        self.parser.add_option('-u', '--unlock',
+                               help='Unlock hosts',
+                               action='store_true')
+
+
+    def parse(self):
+        """Consume the specific options"""
+        (options, leftover) = super(host_mod, self).parse()
+
+        self._parse_lock_options(options)
+
+        if options.ready and options.dead:
+            self.invalid_syntax('Only specify one of '
+                                '--ready and --dead')
+
+        if options.ready:
+            self.data['status'] = 'Ready'
+            self.messages.append('Set status to Ready for host')
+        elif options.dead:
+            self.data['status'] = 'Dead'
+            self.messages.append('Set status to Dead for host')
+
+        if len(self.data) == 0:
+            self.invalid_syntax('No modification requested')
+        return (options, leftover)
+
+
+    def execute(self):
+        successes = []
+        for host in self.hosts:
+            res = self.execute_rpc('modify_host', id=host, **self.data)
+            # TODO: Make the AFE return True or False,
+            # especially for lock
+            if res is None:
+                successes.append(host)
+            else:
+                self.invalid_arg("Unknown host %s" % host)
+        return successes
+
+
+    def output(self, hosts):
+        for msg in self.messages:
+            self.print_wrapped(msg, hosts)
+
+
+
+class host_create(host):
+    """atest host create [--lock|--unlock --platform <arch>
+    --labels <labels>|--blist <label_file>
+    --acls <acls>|--alist <acl_file>
+    --mlist <mach_file>] <hosts>"""
+    usage_action = 'create'
+
+    def __init__(self):
+        self.messages = []
+        super(host_create, self).__init__()
+        self.parser.add_option('-l', '--lock',
+                               help='Create the hosts as locked',
+                               action='store_true')
+        self.parser.add_option('-u', '--unlock',
+                               help='Create the hosts as '
+                               'unlocked (default)',
+                               action='store_true')
+        self.parser.add_option('-t', '--platform',
+                               help='Sets the platform label')
+        self.parser.add_option('-b', '--labels',
+                               help='Comma separated list of labels')
+        self.parser.add_option('-B', '--blist',
+                               help='File listing the labels',
+                               type='string',
+                               metavar='LABEL_FLIST')
+        self.parser.add_option('-a', '--acls',
+                               help='Comma separated list of ACLs')
+        self.parser.add_option('-A', '--alist',
+                               help='File listing the acls',
+                               type='string',
+                               metavar='ACL_FLIST')
+
+
+    def parse(self):
+        flists = [('labels', 'blist', 'labels', False),
+                  ('acls', 'alist', 'acls', False)]
+        (options, leftover) = super(host_create, self).parse(flists)
+
+        self._parse_lock_options(options)
+        self.platform = getattr(options, 'platform', None)
+        return (options, leftover)
+
+
+    def _execute_add_one_host(self, host):
+        self.execute_rpc('add_host', hostname=host,
+                         status="Ready", **self.data)
+
+        # Now add the platform label
+        labels = self.labels[:]
+        if self.platform:
+            labels.append(self.platform)
+        if len (labels):
+            self.execute_rpc('host_add_labels', id=host, labels=labels)
+
+
+    def execute(self):
+        # We need to check if these labels & ACLs exist,
+        # and create them if not.
+        if self.platform:
+            self.check_and_create_items('get_labels', 'add_label',
+                                        [self.platform],
+                                        platform=True)
+
+        if self.labels:
+            self.check_and_create_items('get_labels', 'add_label',
+                                        self.labels,
+                                        platform=False)
+
+        if self.acls:
+            self.check_and_create_items('get_acl_groups',
+                                        'add_acl_group',
+                                        self.acls)
+
+        success = self.site_create_hosts_hook()
+
+        if len(success):
+            for acl in self.acls:
+                self.execute_rpc('acl_group_add_hosts', id=acl, hosts=success)
+        return success
+
+
+    def site_create_hosts_hook(self):
+        success = []
+        for host in self.hosts:
+            try:
+                self._execute_add_one_host(host)
+                success.append(host)
+            except topic_common.CliError:
+                pass
+
+        return success
+
+
+    def output(self, hosts):
+        self.print_wrapped('Added host', hosts)
+
+
+class host_delete(action_common.atest_delete, host):
+    """atest host delete [--mlist <mach_file>] <hosts>"""
+    pass