# # Copyright 2008 Google Inc. All Rights Reserved. """ The shard module contains the objects and methods used to manage shards in Autotest. The valid actions are: create: creates shard remove: deletes shard(s) list: lists shards with label add_boards: add boards to a given shard remove_board: remove board from a given shard See topic_common.py for a High Level Design and Algorithm. """ import sys from autotest_lib.cli import topic_common, action_common class shard(topic_common.atest): """shard class atest shard [create|delete|list|add_boards|remove_board] <options>""" usage_action = '[create|delete|list|add_boards|remove_board]' topic = msg_topic = 'shard' msg_items = '<shards>' def __init__(self): """Add to the parser the options common to all the shard actions""" super(shard, self).__init__() self.topic_parse_info = topic_common.item_parse_info( attribute_name='shards', use_leftover=True) def get_items(self): return self.shards class shard_help(shard): """Just here to get the atest logic working. Usage is set by its parent""" pass class shard_list(action_common.atest_list, shard): """Class for running atest shard list""" def execute(self): filters = {} if self.shards: filters['hostname__in'] = self.shards return super(shard_list, self).execute(op='get_shards', filters=filters) def warn_if_label_assigned_to_multiple_shards(self, results): """Prints a warning if one label is assigned to multiple shards. This should never happen, but if it does, better be safe. @param results: Results as passed to output(). """ assigned_labels = set() for line in results: for label in line['labels']: if label in assigned_labels: sys.stderr.write('WARNING: label %s is assigned to ' 'multiple shards.\n' 'This will lead to unpredictable behavor ' 'in which hosts and jobs will be assigned ' 'to which shard.\n') assigned_labels.add(label) def output(self, results): self.warn_if_label_assigned_to_multiple_shards(results) super(shard_list, self).output(results, ['id', 'hostname', 'labels']) class shard_create(action_common.atest_create, shard): """Class for running atest shard create -l <label> <shard>""" def __init__(self): super(shard_create, self).__init__() self.parser.add_option('-l', '--labels', help=('Assign LABELs to the SHARD. All jobs that ' 'require one of the labels will be run on ' 'the shard. List multiple labels separated ' 'by a comma.'), type='string', metavar='LABELS') def parse(self): (options, leftover) = super(shard_create, self).parse( req_items='shards') self.data_item_key = 'hostname' self.data['labels'] = options.labels or '' return (options, leftover) class shard_add_boards(shard_create): """Class for running atest shard add_boards -l <label> <shard>""" usage_action = 'add_boards' op_action = 'add_boards' msg_done = 'Added boards for' def execute(self): """Running the rpc to add boards to the target shard. Returns: A tuple, 1st element is the target shard. 2nd element is the list of boards labels to be added to the shard. """ target_shard = self.shards[0] self.data[self.data_item_key] = target_shard super(shard_add_boards, self).execute_rpc(op='add_board_to_shard', item=target_shard, **self.data) return (target_shard, self.data['labels']) class shard_delete(action_common.atest_delete, shard): """Class for running atest shard delete <shards>""" def parse(self): (options, leftover) = super(shard_delete, self).parse() self.data_item_key = 'hostname' return (options, leftover) def execute(self, *args, **kwargs): print 'Please ensure the shard host is powered off.' print ('Otherwise DUTs might be used by multiple shards at the same ' 'time, which will lead to serious correctness problems.') return super(shard_delete, self).execute(*args, **kwargs) class shard_remove_board(shard): """Class for running atest shard remove_board -l <label> <shard>""" usage_action = 'remove_board' op_action = 'remove_board' msg_done = 'Removed board' def __init__(self): super(shard_remove_board, self).__init__() self.parser.add_option('-l', '--board_label', type='string', metavar='BOARD_LABEL', help=('Remove the board with the given ' 'BOARD_LABEL from shard.')) def parse(self): (options, leftover) = super(shard_remove_board, self).parse( req_items='shards') self.data['board_label'] = options.board_label self.data['hostname'] = self.shards[0] return (options, leftover) def execute(self): """Validate args and execute the remove_board_from_shard rpc.""" if not self.data.get('board_label'): self.invalid_syntax('Must provide exactly 1 BOARD_LABEL') return if not self.data['board_label'].startswith('board:'): self.invalid_arg('BOARD_LABEL must begin with "board:"') return return super(shard_remove_board, self).execute_rpc( op='remove_board_from_shard', hostname=self.data['hostname'], label=self.data['board_label']) def output(self, results): """Print command results. @param results: Results of rpc execution. """ print results