def __init__(self, serial_reader=None, board_id=0x00000000, data_bytes=None, sequence=0x01, device_type=0xFF, raw_data=None, device_id=0xFFFF): """ Three ways to initialize the command - it may be read, when expected, via a serial reader (via serial_reader - e.g. for testing with the emulator). Alternately, a byte buffer (data_bytes) may be given to the constructor, which is deserialized and populates the appropriate fields of the object. Finally, a DeviceWriteCommand may be constructed from a board_id, device_type, raw_value, and device_id (and sequence number) - this is what is most likely to be used by a client application (e.g. the flask server). """ if serial_reader is not None: super(DeviceWriteCommand, self).__init__(serial_reader=serial_reader) elif data_bytes is not None: super(DeviceWriteCommand, self).__init__(data_bytes=data_bytes) else: data_val = [ord('W')] if raw_data is not None and isinstance(raw_data, basestring): # TODO: this is a temporary workaround until fan motor pole is settled if device_type == get_device_type_code('fan_speed'): data_val.append(0xFF) for c in raw_data: data_val.append(ord(c)) else: raise BusDataException('Invalid data provided to be written to device.') super(DeviceWriteCommand, self).__init__(sequence=sequence, device_type=device_type, board_id=board_id, device_id=device_id, data=data_val)
def boot_target(rack_id, board_num, device_num, target=None): """ Get or set boot target for a given board and device. Args: rack_id: The id of the rack where the target board resides board_num: The board number to get/set boot target for. device_num: The device number to get/set boot target for (must be system). target: The boot target to choose, or, if None, just get info. Returns: Boot target of the device. """ board_num, device_num = check_valid_board_and_device(board_num, device_num) if target is not None and target not in ['pxe', 'hdd', 'no_override']: logger.error( 'Boot Target: Invalid boot target specified: %s board_num: %s device_num: %s', target, board_num, device_num) raise OpenDCREException('Invalid boot target specified.') cmd = current_app.config['CMD_FACTORY'].get_boot_target_command({ 'board_id': board_num, 'device_id': device_num, 'device_type': get_device_type_code(const.DEVICE_SYSTEM), 'boot_target': target if target is not None else 'status' }) device = get_device_instance(board_num) response = device.handle(cmd) return jsonify(response.data)
def host_info(rack_id, board_num, device_num): """ Get hostname and ip address for a given host (of type 'system'. This uses the PLC TTY to login to the server, and retrieve the hostname[s] and ip address[es] Args: rack_id: The id of the rack where the target board resides board_num: The board number to get host info for. device_num: The device number to get host info for. Returns: hostname(s) and IP address(es) """ board_num, device_num = check_valid_board_and_device(board_num, device_num) cmd = current_app.config['CMD_FACTORY'].get_host_info_command({ 'board_id': board_num, 'device_id': device_num, 'device_type': get_device_type_code(const.DEVICE_SYSTEM), 'device_name': const.DEVICE_SYSTEM }) device = get_device_instance(board_num) response = device.handle(cmd) return jsonify(response.data)
def asset_info(rack_id, board_num, device_num): """ Get asset information for a given board and device. Args: rack_id: The id of the rack where the target board resides board_num: The board number to get asset information for. device_num: The device number to get asset information for (must be system). Returns: Asset information about the given device. """ board_num, device_num = check_valid_board_and_device(board_num, device_num) cmd = current_app.config['CMD_FACTORY'].get_asset_command({ 'board_id': board_num, 'device_id': device_num, 'device_type': get_device_type_code(const.DEVICE_SYSTEM), }) device = get_device_instance(board_num) response = device.handle(cmd) return jsonify(response.data)
def fan_control(rack_id, board_num, device_num, fan_speed=None): """ Control fan on system or Vapor Chamber. System fan may only be polled for status unless explicitly supported. Args: rack_id: The rack id to control fan for. board_num: The board number to control fan for. device_num: The device number to control fan for. fan_speed: The speed to set fan to. Returns: fan speed in rpms """ board_id, device_id = check_valid_board_and_device(board_num, device_num) # if we are reading the fan speed only, forward on to the device_read method and pass along response if fan_speed is None: return read_device(rack_id, 'fan_speed', board_num, device_num) # convert fan speed to int if fan_speed is not None: try: fan_speed_int = int(fan_speed) # FIXME: we can move this to individual device instances which will give us finer-toothed control if definitions.MAX_FAN_SPEED < fan_speed_int or definitions.MIN_FAN_SPEED > fan_speed_int: raise ValueError('Fan speed out of acceptable range.') except ValueError as e: logger.error('Location: Error converting fan_speed: %s', str(fan_speed)) raise OpenDCREException( 'Error converting fan_speed to integer ({}).'.format(e)) cmd = current_app.config['CMD_FACTORY'].get_fan_command({ 'board_id': board_id, 'device_id': device_id, 'device_type': get_device_type_code(const.DEVICE_FAN_SPEED), 'device_name': const.DEVICE_FAN_SPEED, 'fan_speed': fan_speed }) device = get_device_instance(board_id) response = device.handle(cmd) return jsonify(response.data)
def power_control(power_action='status', rack_id=None, board_num=None, device_num=None, device_type='power'): """ Power on/off/cycle/status for the given board and port and device. Args: power_action (str): may be on/off/cycle/status and corresponds to the action to take. rack_id (str): the id of the rack which contains the board to accept the power control command board_num (str): the id of the board which contains the device that accepts power control commands. device_num (str): the id of the device which accepts power control commands. device_type (str): the type of device to accept power control command for. Returns: Power status of the given device. Raises: Returns a 500 error if the power command fails. """ board_num, device_num = check_valid_board_and_device(board_num, device_num) if rack_id.lower() in ['on', 'off', 'cycle', 'status']: # for backwards-compatibility, we allow the command to come in as: # power_action/board_id/device_id # therefore, if we see a power action in the rack_id field, then # we need to rearrange the parameters power_action = rack_id cmd = current_app.config['CMD_FACTORY'].get_power_command({ 'board_id': board_num, 'device_id': device_num, 'device_type': get_device_type_code(device_type.lower()), 'power_action': power_action }) device = get_device_instance(board_num) response = device.handle(cmd) return jsonify(response.data)
def read_device(rack_id, device_type, board_num, device_num): """ Get a device reading for the given board and port and device type. We could filter on the upper ID of board_num in case an unusual board number is provided; however, the bus should simply time out in these cases. Args: rack_id (str): The id of the rack where target board & device reside device_type (str): corresponds to the type of device to get a reading for. It must match the actual type of device that is present on the bus, and is used to interpret the raw device reading. board_num (str): specifies which Pi hat to get the reading from device_num (str): specifies which device of the Pi hat should be polled for device reading. Returns: Interpreted and raw device reading, based on the specified device type. Raises: Returns a 500 error if the read command fails. """ board_num, device_num = check_valid_board_and_device(board_num, device_num) cmd = current_app.config['CMD_FACTORY'].get_read_command({ 'board_id': board_num, 'device_id': device_num, 'device_type': get_device_type_code(device_type.lower()), 'device_type_string': device_type.lower() }) device = get_device_instance(board_num) response = device.handle(cmd) return jsonify(response.data)
def _chamber_led_control(board_num, device_num, led_state, rack_id, led_color, blink_state): """ Control chamber LED via PLC. Args: board_num: The board number of the LED controler for vapor_led. device_num: The device number of the LED controller for vapor_led. led_state: The state to set the specified LED to (on, off, no_override) rack_id: The ID of the rack whose LED segment is to be controlled (MIN_RACK_ID..MAX_RACK_ID) led_color: The RGB hex value of the color to set the LED to, or 'no_override'. blink_state: The blink state of the LED (blink, steady, no_override). """ cmd = current_app.config['CMD_FACTORY'].get_chamber_led_command({ 'board_id': board_num, 'device_id': device_num, 'device_type': get_device_type_code(const.DEVICE_VAPOR_LED), 'device_type_string': const.DEVICE_VAPOR_LED, 'device_name': const.DEVICE_VAPOR_LED, 'rack_id': rack_id, 'led_state': led_state, 'led_color': led_color, 'blink_state': blink_state, }) device = get_device_instance(board_num) response = device.handle(cmd) return jsonify(response.data)
def led_control(rack_id, board_num, device_num, led_state=None, led_color=None, blink_state=None): """ Control LED on system or wedge rack. System led may only be turned on/off. Wedge rack supports color Args: rack_id: The rack id to control led for. board_num: The board number to control led for. IPMI boards only support on/off. device_num: The device number to control led for. IPMI devices only support on/off. led_state: The state to set LED to (on/off for IPMI, on/off/blink for PLC). led_color: The hex RGB color to set LED to (for PLC wedge LED only). blink_state: The blink state (blink|steady|no_override) for PLC wedge LED only. Returns: LED state for chassis LED; LED state, color and blink state for PLC wedge LED. """ board_id, device_id = check_valid_board_and_device(board_num, device_num) if led_state not in ['on', 'off', 'no_override', None]: logger.error( 'Invalid LED state {} provided for LED control.'.format(led_state)) raise OpenDCREException('Invalid LED state provided for LED control.') if led_color is not None and led_color != 'no_override': try: led_color_int = int(led_color, 16) if (led_color_int < 0x000000) or (led_color_int > 0xffffff): raise ValueError( 'LED Color must be between 0x000000 and 0xffffff.') except ValueError as e: raise OpenDCREException('Invalid LED color specified. ({})'.format( e.message)) if blink_state is not None and blink_state not in [ 'blink', 'steady', 'no_override' ]: raise OpenDCREException('Invalid blink state specified for LED.') elif (led_color is not None) and (blink_state is not None): return _chamber_led_control(board_num=board_id, device_num=device_id, led_state=led_state, rack_id=rack_id, led_color=led_color, blink_state=blink_state) cmd = current_app.config['CMD_FACTORY'].get_led_command({ 'board_id': board_id, 'device_id': device_id, 'device_type': get_device_type_code(const.DEVICE_LED), 'device_type_string': const.DEVICE_LED, 'device_name': const.DEVICE_LED, 'rack_id': rack_id, 'led_state': led_state, 'led_color': led_color, 'blink_state': blink_state, }) device = get_device_instance(board_id) response = device.handle(cmd) return jsonify(response.data)
def _fan(self, command): """ Fan speed control command for a given board and device. Args: command (Command): the command issued by the OpenDCRE endpoint containing the data and sequence for the request. Returns: Response: a Response object corresponding to the incoming Command object, containing the data from the fan response. """ with self._lock: bus = self._get_bus() # get the command data out from the incoming command board_id = command.data['board_id'] device_id = command.data['device_id'] device_type = command.data['device_type'] fan_speed = command.data['fan_speed'] request = DeviceWriteCommand( board_id=board_id, device_id=device_id, device_type=device_type, raw_data=fan_speed, sequence=command.sequence ) bus.write(request.serialize()) bus.flush() logger.debug('>>Fan_Speed: {}'.format([hex(x) for x in request.serialize()])) response = None try: response = DeviceWriteResponse( serial_reader=bus, expected_sequence=request.sequence ) logger.debug('<<Fan_Speed: {}'.format([hex(x) for x in response.serialize()])) except BusTimeoutException: raise OpenDCREException('Fan command bus timeout.'), None, sys.exc_info()[2] except (BusDataException, ChecksumException): response = self._retry_command(bus, request, DeviceWriteResponse) # get raw value to ensure remote device took the write. try: device_raw = str(''.join([chr(x) for x in response.data])) except (ValueError, TypeError): # abort if unable to convert to int (ValueError), unable to convert to chr (TypeError) raise OpenDCREException('Fan control: Error converting device response.'), None, sys.exc_info()[2] if device_raw == 'W1': c = Command( cmd_id=cid.READ, data={ 'board_id': board_id, 'device_id': device_id, 'device_type': get_device_type_code(const.DEVICE_FAN_SPEED), 'device_type_string': const.DEVICE_FAN_SPEED }, sequence=next(self._count) ) return self._read(c) else: raise OpenDCREException('Error writing to device {} on board {}'.format(hex(device_id), hex(board_id)))