| # |
| # 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 |
| |
| |
| See topic_common.py for a High Level Design and Algorithm. |
| |
| """ |
| |
| from autotest_lib.cli import action_common, topic_common |
| from autotest_lib.client.common_lib import host_protections |
| |
| |
| 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>' |
| |
| protections = host_protections.Protection.names |
| |
| |
| 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') |
| |
| self.topic_parse_info = topic_common.item_parse_info( |
| attribute_name='hosts', |
| filename_option='mlist', |
| use_leftover=True) |
| |
| |
| 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>] [--acl <ACL>] [--user <user>]""" |
| |
| def __init__(self): |
| super(host_list, self).__init__() |
| |
| self.parser.add_option('-b', '--label', |
| default='', |
| help='Only list hosts with all these labels ' |
| '(comma separated)') |
| self.parser.add_option('-s', '--status', |
| default='', |
| help='Only list hosts with any of these ' |
| 'statuses (comma separated)') |
| self.parser.add_option('-a', '--acl', |
| default='', |
| help='Only list hosts within this ACL') |
| self.parser.add_option('-u', '--user', |
| default='', |
| help='Only list hosts available to this user') |
| self.parser.add_option('-N', '--hostnames-only', help='Only return ' |
| 'hostnames for the machines queried.', |
| action='store_true') |
| self.parser.add_option('--locked', |
| default=False, |
| help='Only list locked hosts', |
| action='store_true') |
| self.parser.add_option('--unlocked', |
| default=False, |
| help='Only list unlocked hosts', |
| action='store_true') |
| |
| |
| |
| def parse(self): |
| """Consume the specific options""" |
| label_info = topic_common.item_parse_info(attribute_name='labels', |
| inline_option='label') |
| |
| (options, leftover) = super(host_list, self).parse([label_info]) |
| |
| self.status = options.status |
| self.acl = options.acl |
| self.user = options.user |
| self.hostnames_only = options.hostnames_only |
| |
| if options.locked and options.unlocked: |
| self.invalid_syntax('--locked and --unlocked are ' |
| 'mutually exclusive') |
| self.locked = options.locked |
| self.unlocked = options.unlocked |
| return (options, leftover) |
| |
| |
| def execute(self): |
| filters = {} |
| check_results = {} |
| if self.hosts: |
| filters['hostname__in'] = self.hosts |
| check_results['hostname__in'] = 'hostname' |
| |
| if self.labels: |
| if len(self.labels) == 1: |
| # This is needed for labels with wildcards (x86*) |
| filters['labels__name__in'] = self.labels |
| check_results['labels__name__in'] = None |
| else: |
| filters['multiple_labels'] = self.labels |
| check_results['multiple_labels'] = None |
| |
| if self.status: |
| statuses = self.status.split(',') |
| statuses = [status.strip() for status in statuses |
| if status.strip()] |
| |
| filters['status__in'] = statuses |
| check_results['status__in'] = None |
| |
| if self.acl: |
| filters['aclgroup__name'] = self.acl |
| check_results['aclgroup__name'] = None |
| if self.user: |
| filters['aclgroup__users__login'] = self.user |
| check_results['aclgroup__users__login'] = None |
| |
| if self.locked or self.unlocked: |
| filters['locked'] = self.locked |
| check_results['locked'] = None |
| |
| 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']) |
| if self.hostnames_only: |
| self.print_list(results, key='hostname') |
| else: |
| 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', |
| 'lock_time', 'protection',]) |
| 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 [--max-query] --mlist <file>|<hosts>""" |
| usage_action = 'jobs' |
| |
| def __init__(self): |
| super(host_jobs, self).__init__() |
| self.parser.add_option('-q', '--max-query', |
| help='Limits the number of results ' |
| '(20 by default)', |
| type='int', default=20) |
| |
| |
| def parse(self): |
| """Consume the specific options""" |
| (options, leftover) = super(host_jobs, self).parse() |
| self.max_queries = options.max_query |
| return (options, leftover) |
| |
| |
| 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, |
| query_limit=self.max_queries, |
| sort_by=['-job__id']) |
| 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|--protection |
| --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('-l', '--lock', |
| help='Lock hosts', |
| action='store_true') |
| self.parser.add_option('-u', '--unlock', |
| help='Unlock hosts', |
| action='store_true') |
| self.parser.add_option('-p', '--protection', type='choice', |
| help=('Set the protection level on a host. ' |
| 'Must be one of: %s' % |
| ', '.join('"%s"' % p |
| for p in self.protections)), |
| choices=self.protections) |
| |
| |
| def parse(self): |
| """Consume the specific options""" |
| (options, leftover) = super(host_mod, self).parse() |
| |
| self._parse_lock_options(options) |
| |
| if options.protection: |
| self.data['protection'] = options.protection |
| self.messages.append('Protection set to "%s"' % options.protection) |
| |
| if len(self.data) == 0: |
| self.invalid_syntax('No modification requested') |
| return (options, leftover) |
| |
| |
| def execute(self): |
| successes = [] |
| for host in self.hosts: |
| try: |
| res = self.execute_rpc('modify_host', item=host, |
| id=host, **self.data) |
| # TODO: Make the AFE return True or False, |
| # especially for lock |
| successes.append(host) |
| except topic_common.CliError, full_error: |
| # Already logged by execute_rpc() |
| pass |
| |
| 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> |
| --protection <protection_type> |
| --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', default=False) |
| 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') |
| self.parser.add_option('-p', '--protection', type='choice', |
| help=('Set the protection level on a host. ' |
| 'Must be one of: %s' % |
| ', '.join('"%s"' % p |
| for p in self.protections)), |
| choices=self.protections) |
| |
| |
| def parse(self): |
| label_info = topic_common.item_parse_info(attribute_name='labels', |
| inline_option='labels', |
| filename_option='blist') |
| acl_info = topic_common.item_parse_info(attribute_name='acls', |
| inline_option='acls', |
| filename_option='alist') |
| |
| (options, leftover) = super(host_create, self).parse([label_info, |
| acl_info], |
| req_items='hosts') |
| |
| self._parse_lock_options(options) |
| self.locked = options.lock |
| self.platform = getattr(options, 'platform', None) |
| if options.protection: |
| self.data['protection'] = options.protection |
| return (options, leftover) |
| |
| |
| def _execute_add_one_host(self, host): |
| # Always add the hosts as locked to avoid the host |
| # being picked up by the scheduler before it's ACL'ed |
| self.data['locked'] = True |
| 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_add_hosts(self): |
| successful_hosts = self.site_create_hosts_hook() |
| |
| if successful_hosts: |
| for acl in self.acls: |
| self.execute_rpc('acl_group_add_hosts', |
| id=acl, |
| hosts=successful_hosts) |
| |
| if not self.locked: |
| for host in successful_hosts: |
| self.execute_rpc('modify_host', id=host, locked=False) |
| return successful_hosts |
| |
| |
| 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) |
| |
| return self._execute_add_hosts() |
| |
| |
| def site_create_hosts_hook(self): |
| successful_hosts = [] |
| for host in self.hosts: |
| try: |
| self._execute_add_one_host(host) |
| successful_hosts.append(host) |
| except topic_common.CliError: |
| pass |
| |
| return successful_hosts |
| |
| |
| 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 |