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: 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 14 15 See topic_common.py for a High Level Design and Algorithm. 16 """ 17 18 import sys 19 from autotest_lib.cli import topic_common, action_common 20 21 22 class shard(topic_common.atest): 23 """shard class 24 atest shard [create|delete|list|add_boards|remove_board] <options>""" 25 usage_action = '[create|delete|list|add_boards|remove_board]' 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): 50 """Class for running atest shard list""" 51 52 def execute(self): 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) 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) 81 super(shard_list, self).output(results, ['id', 'hostname', 'labels']) 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__() 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.'), 93 type='string', 94 metavar='LABELS') 95 96 97 def parse(self): 98 (options, leftover) = super(shard_create, self).parse( 99 req_items='shards') 100 self.data_item_key = 'hostname' 101 self.data['labels'] = options.labels or '' 102 return (options, leftover) 103 104 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 126 class shard_delete(action_common.atest_delete, shard): 127 """Class for running atest shard delete <shards>""" 128 129 def parse(self): 130 (options, leftover) = super(shard_delete, self).parse() 131 self.data_item_key = 'hostname' 132 return (options, leftover) 133 134 135 def execute(self, *args, **kwargs): 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.') 139 return super(shard_delete, self).execute(*args, **kwargs) 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 183