Jakob Juelich | 8b110ee | 2014-09-15 16:13:42 -0700 | [diff] [blame] | 1 | # |
| 2 | # Copyright 2008 Google Inc. All Rights Reserved. |
| 3 | |
| 4 | """ |
| 5 | The shard module contains the objects and methods used to |
| 6 | manage shards in Autotest. |
| 7 | |
| 8 | The valid actions are: |
Aviv Keshet | 3077315 | 2017-04-06 09:01:21 -0700 | [diff] [blame] | 9 | create: creates shard |
| 10 | remove: deletes shard(s) |
| 11 | list: lists shards with label |
| 12 | add_boards: add boards to a given shard |
| 13 | remove_board: remove board from a given shard |
Jakob Juelich | 8b110ee | 2014-09-15 16:13:42 -0700 | [diff] [blame] | 14 | |
| 15 | See topic_common.py for a High Level Design and Algorithm. |
| 16 | """ |
| 17 | |
Dan Shi | 25e1fd4 | 2014-12-19 14:36:42 -0800 | [diff] [blame] | 18 | import sys |
Jakob Juelich | 8b110ee | 2014-09-15 16:13:42 -0700 | [diff] [blame] | 19 | from autotest_lib.cli import topic_common, action_common |
| 20 | |
| 21 | |
| 22 | class shard(topic_common.atest): |
| 23 | """shard class |
Aviv Keshet | 3077315 | 2017-04-06 09:01:21 -0700 | [diff] [blame] | 24 | atest shard [create|delete|list|add_boards|remove_board] <options>""" |
| 25 | usage_action = '[create|delete|list|add_boards|remove_board]' |
Jakob Juelich | 8b110ee | 2014-09-15 16:13:42 -0700 | [diff] [blame] | 26 | topic = msg_topic = 'shard' |
| 27 | msg_items = '<shards>' |
| 28 | |
| 29 | def __init__(self): |
| 30 | """Add to the parser the options common to all the |
| 31 | shard actions""" |
| 32 | super(shard, self).__init__() |
| 33 | |
| 34 | self.topic_parse_info = topic_common.item_parse_info( |
| 35 | attribute_name='shards', |
| 36 | use_leftover=True) |
| 37 | |
| 38 | |
| 39 | def get_items(self): |
| 40 | return self.shards |
| 41 | |
| 42 | |
| 43 | class shard_help(shard): |
| 44 | """Just here to get the atest logic working. |
| 45 | Usage is set by its parent""" |
| 46 | pass |
| 47 | |
| 48 | |
| 49 | class shard_list(action_common.atest_list, shard): |
MK Ryu | 5dfcc89 | 2015-07-16 15:34:04 -0700 | [diff] [blame] | 50 | """Class for running atest shard list""" |
Jakob Juelich | 8b110ee | 2014-09-15 16:13:42 -0700 | [diff] [blame] | 51 | |
| 52 | def execute(self): |
MK Ryu | 5dfcc89 | 2015-07-16 15:34:04 -0700 | [diff] [blame] | 53 | filters = {} |
| 54 | if self.shards: |
| 55 | filters['hostname__in'] = self.shards |
| 56 | return super(shard_list, self).execute(op='get_shards', |
| 57 | filters=filters) |
Jakob Juelich | 8b110ee | 2014-09-15 16:13:42 -0700 | [diff] [blame] | 58 | |
| 59 | |
| 60 | def warn_if_label_assigned_to_multiple_shards(self, results): |
| 61 | """Prints a warning if one label is assigned to multiple shards. |
| 62 | |
| 63 | This should never happen, but if it does, better be safe. |
| 64 | |
| 65 | @param results: Results as passed to output(). |
| 66 | """ |
| 67 | assigned_labels = set() |
| 68 | for line in results: |
| 69 | for label in line['labels']: |
| 70 | if label in assigned_labels: |
| 71 | sys.stderr.write('WARNING: label %s is assigned to ' |
| 72 | 'multiple shards.\n' |
| 73 | 'This will lead to unpredictable behavor ' |
| 74 | 'in which hosts and jobs will be assigned ' |
| 75 | 'to which shard.\n') |
| 76 | assigned_labels.add(label) |
| 77 | |
| 78 | |
| 79 | def output(self, results): |
| 80 | self.warn_if_label_assigned_to_multiple_shards(results) |
Aviv Keshet | a124fa3 | 2017-04-24 15:43:46 -0700 | [diff] [blame] | 81 | super(shard_list, self).output(results, ['id', 'hostname', 'labels']) |
Jakob Juelich | 8b110ee | 2014-09-15 16:13:42 -0700 | [diff] [blame] | 82 | |
| 83 | |
| 84 | class shard_create(action_common.atest_create, shard): |
| 85 | """Class for running atest shard create -l <label> <shard>""" |
| 86 | def __init__(self): |
| 87 | super(shard_create, self).__init__() |
MK Ryu | 5dfcc89 | 2015-07-16 15:34:04 -0700 | [diff] [blame] | 88 | self.parser.add_option('-l', '--labels', |
| 89 | help=('Assign LABELs to the SHARD. All jobs that ' |
| 90 | 'require one of the labels will be run on ' |
| 91 | 'the shard. List multiple labels separated ' |
| 92 | 'by a comma.'), |
Jakob Juelich | 8b110ee | 2014-09-15 16:13:42 -0700 | [diff] [blame] | 93 | type='string', |
MK Ryu | 5dfcc89 | 2015-07-16 15:34:04 -0700 | [diff] [blame] | 94 | metavar='LABELS') |
Jakob Juelich | 8b110ee | 2014-09-15 16:13:42 -0700 | [diff] [blame] | 95 | |
| 96 | |
| 97 | def parse(self): |
MK Ryu | 5dfcc89 | 2015-07-16 15:34:04 -0700 | [diff] [blame] | 98 | (options, leftover) = super(shard_create, self).parse( |
| 99 | req_items='shards') |
Jakob Juelich | 8b110ee | 2014-09-15 16:13:42 -0700 | [diff] [blame] | 100 | self.data_item_key = 'hostname' |
Aviv Keshet | 6120a15 | 2017-03-22 22:37:55 -0700 | [diff] [blame] | 101 | self.data['labels'] = options.labels or '' |
Jakob Juelich | 8b110ee | 2014-09-15 16:13:42 -0700 | [diff] [blame] | 102 | return (options, leftover) |
| 103 | |
| 104 | |
Shuqian Zhao | b8b13d8 | 2016-08-04 09:35:17 -0700 | [diff] [blame] | 105 | class shard_add_boards(shard_create): |
| 106 | """Class for running atest shard add_boards -l <label> <shard>""" |
| 107 | usage_action = 'add_boards' |
| 108 | op_action = 'add_boards' |
| 109 | msg_done = 'Added boards for' |
| 110 | |
| 111 | def execute(self): |
| 112 | """Running the rpc to add boards to the target shard. |
| 113 | |
| 114 | Returns: |
| 115 | A tuple, 1st element is the target shard. 2nd element is the list of |
| 116 | boards labels to be added to the shard. |
| 117 | """ |
| 118 | target_shard = self.shards[0] |
| 119 | self.data[self.data_item_key] = target_shard |
| 120 | super(shard_add_boards, self).execute_rpc(op='add_board_to_shard', |
| 121 | item=target_shard, |
| 122 | **self.data) |
| 123 | return (target_shard, self.data['labels']) |
| 124 | |
| 125 | |
Jakob Juelich | 8b110ee | 2014-09-15 16:13:42 -0700 | [diff] [blame] | 126 | class shard_delete(action_common.atest_delete, shard): |
| 127 | """Class for running atest shard delete <shards>""" |
Jakob Juelich | 8b110ee | 2014-09-15 16:13:42 -0700 | [diff] [blame] | 128 | |
| 129 | def parse(self): |
| 130 | (options, leftover) = super(shard_delete, self).parse() |
Jakob Juelich | 8b110ee | 2014-09-15 16:13:42 -0700 | [diff] [blame] | 131 | self.data_item_key = 'hostname' |
| 132 | return (options, leftover) |
| 133 | |
| 134 | |
| 135 | def execute(self, *args, **kwargs): |
Jakob Juelich | 8b110ee | 2014-09-15 16:13:42 -0700 | [diff] [blame] | 136 | print 'Please ensure the shard host is powered off.' |
| 137 | print ('Otherwise DUTs might be used by multiple shards at the same ' |
| 138 | 'time, which will lead to serious correctness problems.') |
Dan Shi | 25e1fd4 | 2014-12-19 14:36:42 -0800 | [diff] [blame] | 139 | return super(shard_delete, self).execute(*args, **kwargs) |
Aviv Keshet | 3077315 | 2017-04-06 09:01:21 -0700 | [diff] [blame] | 140 | |
| 141 | |
| 142 | class shard_remove_board(shard): |
| 143 | """Class for running atest shard remove_board -l <label> <shard>""" |
| 144 | usage_action = 'remove_board' |
| 145 | op_action = 'remove_board' |
| 146 | msg_done = 'Removed board' |
| 147 | |
| 148 | def __init__(self): |
| 149 | super(shard_remove_board, self).__init__() |
| 150 | self.parser.add_option('-l', '--board_label', type='string', |
| 151 | metavar='BOARD_LABEL', |
| 152 | help=('Remove the board with the given ' |
| 153 | 'BOARD_LABEL from shard.')) |
| 154 | |
| 155 | def parse(self): |
| 156 | (options, leftover) = super(shard_remove_board, self).parse( |
| 157 | req_items='shards') |
| 158 | self.data['board_label'] = options.board_label |
| 159 | self.data['hostname'] = self.shards[0] |
| 160 | return (options, leftover) |
| 161 | |
| 162 | |
| 163 | def execute(self): |
| 164 | """Validate args and execute the remove_board_from_shard rpc.""" |
| 165 | if not self.data.get('board_label'): |
| 166 | self.invalid_syntax('Must provide exactly 1 BOARD_LABEL') |
| 167 | return |
| 168 | if not self.data['board_label'].startswith('board:'): |
| 169 | self.invalid_arg('BOARD_LABEL must begin with "board:"') |
| 170 | return |
| 171 | return super(shard_remove_board, self).execute_rpc( |
| 172 | op='remove_board_from_shard', |
| 173 | hostname=self.data['hostname'], |
| 174 | label=self.data['board_label']) |
| 175 | |
| 176 | |
| 177 | def output(self, results): |
| 178 | """Print command results. |
| 179 | |
| 180 | @param results: Results of rpc execution. |
| 181 | """ |
| 182 | print results |