def create_chameleon_board(dut_hostname, args): """Given either DUT's hostname or argments, creates a ChameleonBoard object. If the DUT's hostname is in the lab zone, it connects to the Chameleon by append the hostname with '-chameleon' suffix. If not, checks if the args contains the key-value pair 'chameleon_host=IP'. @param dut_hostname: Hostname of a DUT. @param args: A string of arguments passed from the command line. @return A ChameleonBoard object. @raise ChameleonConnectionError if unknown hostname. """ connection = None hostname = make_chameleon_hostname(dut_hostname) if utils.host_is_in_lab_zone(hostname): connection = ChameleonConnection(hostname) else: args_dict = utils.args_to_dict(args) hostname = args_dict.get('chameleon_host', None) port = args_dict.get('chameleon_port', CHAMELEON_PORT) if hostname: connection = ChameleonConnection(hostname, port) else: raise ChameleonConnectionError( 'No chameleon_host is given in args') return ChameleonBoard(connection)
def _check_if_is_in_lab(self): """Checks if Chameleon host is in lab and set self._is_in_lab. If self.hostname is an IP address, we treat it as is not in lab zone. """ self._is_in_lab = (False if dnsname_mangler.is_ip_address( self.hostname) else utils.host_is_in_lab_zone(self.hostname))
def create_chameleon_host(dut, chameleon_args): """Create a ChameleonHost object. There three possible cases: 1) If the DUT is in Cros Lab and has a chameleon board, then create a ChameleonHost object pointing to the board. chameleon_args is ignored. 2) If not case 1) and chameleon_args is neither None nor empty, then create a ChameleonHost object using chameleon_args. 3) If neither case 1) or 2) applies, return None. @param dut: host name of the host that chameleon connects. It can be used to lookup the chameleon in test lab using naming convention. If dut is an IP address, it can not be used to lookup the chameleon in test lab. @param chameleon_args: A dictionary that contains args for creating a ChameleonHost object, e.g. {'chameleon_host': '172.11.11.112', 'chameleon_port': 9992}. @returns: A ChameleonHost object or None. """ if not utils.is_in_container(): is_moblab = utils.is_moblab() else: is_moblab = _CONFIG.get_config_value('SSP', 'is_moblab', type=bool, default=False) if not is_moblab: dut_is_hostname = not dnsname_mangler.is_ip_address(dut) if dut_is_hostname: chameleon_hostname = chameleon.make_chameleon_hostname(dut) if utils.host_is_in_lab_zone(chameleon_hostname): # Be more tolerant on chameleon in the lab because # we don't want dead chameleon blocks non-chameleon tests. if utils.ping(chameleon_hostname, deadline=3): logging.warning( 'Chameleon %s is not accessible. Please file a bug' ' to test lab', chameleon_hostname) return None return ChameleonHost(chameleon_host=chameleon_hostname) if chameleon_args: return ChameleonHost(**chameleon_args) else: return None else: afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10) hosts = afe.get_hosts(hostname=dut) if hosts and CHAMELEON_HOST_ATTR in hosts[0].attributes: return ChameleonHost( chameleon_host=hosts[0].attributes[CHAMELEON_HOST_ATTR], chameleon_port=hosts[0].attributes.get(CHAMELEON_PORT_ATTR, 9992)) else: return None
def _initialize(self, servo_host='localhost', servo_port=DEFAULT_PORT, servo_board=None, servo_model=None, servo_serial=None, is_in_lab=None, *args, **dargs): """Initialize a ServoHost instance. A ServoHost instance represents a host that controls a servo. @param servo_host: Name of the host where the servod process is running. @param servo_port: Port the servod process is listening on. Defaults to the SERVOD_PORT environment variable if set, otherwise 9999. @param servo_board: Board that the servo is connected to. @param servo_model: Model that the servo is connected to. @param is_in_lab: True if the servo host is in Cros Lab. Default is set to None, for which utils.host_is_in_lab_zone will be called to check if the servo host is in Cros lab. """ super(ServoHost, self)._initialize(hostname=servo_host, *args, **dargs) self.servo_port = int(servo_port) self.servo_board = servo_board self.servo_model = servo_model self.servo_serial = servo_serial self._servo = None self._repair_strategy = (servo_repair.create_servo_repair_strategy()) self._is_localhost = (self.hostname == 'localhost') if self._is_localhost: self._is_in_lab = False elif is_in_lab is None: self._is_in_lab = utils.host_is_in_lab_zone(self.hostname) else: self._is_in_lab = is_in_lab # Commands on the servo host must be run by the superuser. # Our account on a remote host is root, but if our target is # localhost then we might be running unprivileged. If so, # `sudo` will have to be added to the commands. if self._is_localhost: self._sudo_required = utils.system_output('id -u') != '0' else: self._sudo_required = False
def _get_standard_servo_args(dut_host): """Return servo data associated with a given DUT. This checks for the presence of servo host and port attached to the given `dut_host`. This data should be stored in the `_afe_host.attributes` field in the provided `dut_host` parameter. @param dut_host Instance of `Host` on which to find the servo attributes. @return A tuple of `servo_args` dict with host and an option port, plus an `is_in_lab` flag indicating whether this in the CrOS test lab, or some different environment. """ servo_args = None is_in_lab = False is_ssp_moblab = False if utils.is_in_container(): is_moblab = _CONFIG.get_config_value('SSP', 'is_moblab', type=bool, default=False) is_ssp_moblab = is_moblab else: is_moblab = utils.is_moblab() attrs = dut_host._afe_host.attributes if attrs and SERVO_HOST_ATTR in attrs: servo_host = attrs[SERVO_HOST_ATTR] if (is_ssp_moblab and servo_host in ['localhost', '127.0.0.1']): servo_host = _CONFIG.get_config_value('SSP', 'host_container_ip', type=str, default=None) servo_args = {SERVO_HOST_ATTR: servo_host} if SERVO_PORT_ATTR in attrs: try: servo_port = attrs[SERVO_PORT_ATTR] servo_args[SERVO_PORT_ATTR] = int(servo_port) except ValueError: logging.error('servo port is not an int: %s', servo_port) # Let's set the servo args to None since we're not creating # the ServoHost object with the proper port now. servo_args = None if SERVO_SERIAL_ATTR in attrs: servo_args[SERVO_SERIAL_ATTR] = attrs[SERVO_SERIAL_ATTR] is_in_lab = (not is_moblab and utils.host_is_in_lab_zone(servo_host)) # TODO(jrbarnette): This test to use the default lab servo hostname # is a legacy that we need only until every host in the DB has # proper attributes. elif (not is_moblab and not dnsname_mangler.is_ip_address(dut_host.hostname)): servo_host = make_servo_hostname(dut_host.hostname) is_in_lab = utils.host_is_in_lab_zone(servo_host) if is_in_lab: servo_args = {SERVO_HOST_ATTR: servo_host} if servo_args is not None: info = dut_host.host_info_store.get() if info.board: servo_args[SERVO_BOARD_ATTR] = _map_afe_board_to_servo_board( info.board) return servo_args, is_in_lab
def _generate_repair_recommendation(inventory, num_recommend): """Return a summary of selected DUTs needing repair. Returns a message recommending a list of broken DUTs to be repaired. The list of DUTs is selected based on these criteria: * No more than `num_recommend` DUTs will be listed. * All DUTs must be in the same lab. * DUTs should be selected for some degree of physical proximity. * DUTs for boards with a low spares buffer are more important than DUTs with larger buffers. The algorithm used will guarantee that at least one DUT from a board with the smallest spares buffer will be recommended. If the worst spares buffer number is shared by more than one board, the algorithm will tend to prefer repair sets that include more of those boards over sets that cover fewer boards. @param inventory Inventory for generating recommendations. @param num_recommend Number of DUTs to recommend for repair. """ logging.debug('Creating DUT repair recommendations') board_buffer_counts = {} broken_list = [] for board in inventory.get_managed_boards(): logging.debug('Listing failed DUTs for %s', board) counts = inventory[board] if counts.get_broken() != 0: board_buffer_counts[board] = counts.get_spares_buffer() broken_list.extend(counts.get_broken_list()) # N.B. The logic inside this loop may seem complicated, but # simplification is hard: # * Calculating an initial recommendation outside of # the loop likely would make things more complicated, # not less. # * It's necessary to calculate an initial lab slice once per # lab _before_ the while loop, in case the number of broken # DUTs in a lab is less than `num_recommend`. recommendation = None best_score = None for lab_duts in _sort_by_location(broken_list): start = 0 end = num_recommend lab_slice = lab_duts[start:end] lab_score = _score_repair_set(board_buffer_counts, lab_slice) while end < len(lab_duts): start += 1 end += 1 new_slice = lab_duts[start:end] new_score = _score_repair_set(board_buffer_counts, new_slice) if new_score > lab_score: lab_slice = new_slice lab_score = new_score if recommendation is None or lab_score > best_score: recommendation = lab_slice best_score = lab_score message = [ 'Repair recommendations:\n', '%-30s %-16s %s' % ('Hostname', 'Board', 'Servo instructions') ] for h in recommendation: servo_name = servo_host.make_servo_hostname(h.host.hostname) if utils.host_is_in_lab_zone(servo_name): servo_message = 'Repair servo first' else: servo_message = 'No servo present' line = '%-30s %-16s %s' % (h.host.hostname, h.host_board, servo_message) message.append(line) return '\n'.join(message)
def _generate_repair_recommendation(inventory, num_recommend): """Return a summary of selected DUTs needing repair. Returns a message recommending a list of broken DUTs to be repaired. The list of DUTs is selected based on these criteria: * No more than `num_recommend` DUTs will be listed. * All DUTs must be in the same lab. * DUTs should be selected for some degree of physical proximity. * DUTs for models with a low spares buffer are more important than DUTs with larger buffers. The algorithm used will guarantee that at least one DUT from a model with the lowest spares buffer will be recommended. If the worst spares buffer number is shared by more than one model, the algorithm will tend to prefer repair sets that include more of those models over sets that cover fewer models. @param inventory `_LabInventory` object from which to generate recommendations. @param num_recommend Number of DUTs to recommend for repair. """ logging.debug('Creating DUT repair recommendations') model_buffer_counts = {} broken_list = [] for model, counts in inventory.reportable_items(): logging.debug('Listing failed DUTs for %s', model) if counts.get_broken() != 0: model_buffer_counts[model] = counts.get_spares_buffer() broken_list.extend(counts.get_broken_list()) # N.B. The logic inside this loop may seem complicated, but # simplification is hard: # * Calculating an initial recommendation outside of # the loop likely would make things more complicated, # not less. # * It's necessary to calculate an initial lab slice once per # lab _before_ the while loop, in case the number of broken # DUTs in a lab is less than `num_recommend`. recommendation = None best_score = None for lab_duts in _sort_by_location(broken_list): start = 0 end = num_recommend lab_slice = lab_duts[start:end] lab_score = _score_repair_set(model_buffer_counts, lab_slice) while end < len(lab_duts): start += 1 end += 1 new_slice = lab_duts[start:end] new_score = _score_repair_set(model_buffer_counts, new_slice) if new_score > lab_score: lab_slice = new_slice lab_score = new_score if recommendation is None or lab_score > best_score: recommendation = lab_slice best_score = lab_score # N.B. The trailing space in `line_fmt` is manadatory: Without it, # Gmail will parse the URL wrong. Don't ask. If you simply _must_ # know more, go try it yourself... line_fmt = '%-30s %-16s %-6s\n %s ' message = [ 'Repair recommendations:\n', line_fmt % ('Hostname', 'Model', 'Servo?', 'Logs URL') ] for h in recommendation: servo_name = servo_host.make_servo_hostname(h.host.hostname) servo_present = utils.host_is_in_lab_zone(servo_name) _, event = h.last_diagnosis() line = line_fmt % (h.host.hostname, h.host_model, 'Yes' if servo_present else 'No', event.job_url) message.append(line) return '\n'.join(message)