| # |
| # 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 |