def get_dcmi_management_controller_id(username=None, password=None, ip_address=None, port=BMC_PORT): """ Get DCMI management controller ID from remote BMC. Args: username (str): Username to connect to BMC with. password (str): Password to connect to BMC with. ip_address (str): BMC IP Address. port (int): BMC port Returns: str: The management controller ID. Raises an Exception if error. """ id_string = '' # read the mgmt controller ID out in blocks of 16 bytes, up to the limit of 64 bytes with IpmiCommand(ip_address=ip_address, username=username, password=password, port=port) as ipmicmd: for x in range(0, 4): result = ipmicmd.raw_command(netfn=0x2c, command=0x01, data=(0xdc, x * 0x10)) if 'error' in result: raise OpenDCREException( 'Error executing get DCMI controller ID command on {} : {}'.format(ip_address, result['error']) ) # byte 0 should always be 0xdc if result['data'][0] != 0xdc: raise OpenDCREException( 'Error in response to get DCMI controller ID command on {}: Invalid first byte.'.format(ip_address) ) # byte 1 should be the string length - ignore as remaining bytes are 0x00 (null) # tack on the remaining characters to the id string id_string.join([chr(c) for c in result['data'][2:]]) return id_string
def _asset(self, command): """ Asset info 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 asset response. """ # get the command data out from the incoming command device_id = command.data['device_id'] try: # validate that asset is supported for the device self._get_device_by_id(device_id, 'system') asset_data = vapor_ipmi.get_inventory(**self._ipmi_kwargs) asset_data['bmc_ip'] = self.bmc_ip if asset_data is not None: return Response(command=command, response_data=asset_data) logger.error('No response getting asset info for {}'.format( command.data)) raise OpenDCREException( 'No response from BMC when retrieving asset information via IPMI.' ) except Exception: raise OpenDCREException( 'Error getting IPMI asset info (device id: {})'.format( hex(device_id))), None, sys.exc_info()[2]
def _get_power_reading(username=None, password=None, ip_address=None, port=BMC_PORT): """ Internal wrapper for the Get Power Reading DCMI command. Args: username (str): The username to connect to BMC with. password (str): The password to connect to BMC with. ip_address (str): The IP Address of the BMC. port (int): BMC port Returns: dict: Power reading information from the remote system. Raises: OpenDCREException in cases where power reading is not active. """ with IpmiCommand(ip_address=ip_address, username=username, password=password, port=port) as ipmicmd: result = ipmicmd.raw_command(netfn=0x2c, command=0x02, data=(0xdc, 0x01, 0x00, 0x00)) if 'error' in result: raise OpenDCREException( 'Error executing DCMI power reading command on {} : {}'.format(ip_address, result['error']) ) # if power measurement is inactive, we may be giving back back data. if (result['data'][17] >> 6) & 0x01 == 0x00: raise OpenDCREException('Error reading DCMI power - power measurement is not active.') return {'input_power': float(result['data'][1] | (result['data'][2] << 8))}
def get_led(links, timeout, username, password): """ Retrieve remote system LED status. Args: links (list[str]): the list of links to connect to via HTTP timeout (int | float): the number of seconds a GET will wait for a connection before timing out on the request username (str): the username for basic HTTP authentication password (str): the password for basic HTTP authentication Returns: dict: LED Status as reported by remote system. """ response = dict() try: led_status = get_data( link=links[0], timeout=timeout, username=username, password=password ) except ValueError as e: logger.error('No data retrieved for LED status on GET of chassis schema: {}'.format(e.message)) raise OpenDCREException('Cannot retrieve data from chassis schema: {}'.format(e.message)) try: led_status = led_status['IndicatorLED'].lower() response['led_state'] = 'on' if led_status == 'lit' else led_status return response except KeyError as e: logger.error('Incomplete or no data for LED from GET on chassis schema. {} not found.'.format(e.message)) raise OpenDCREException('Incomplete or no data from chassis schema. {} not found.'.format(e.message))
def _led(self, command): """ LED command for a given board and device. If an LED state is specified, this will attempt to set the LED state of the given device. Otherwise, it will just retrieve the currently configured LED state. 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 LED response. """ # get the command data from the incoming command: device_id = command.data['device_id'] led_state = command.data['led_state'] try: # check if the device raises an exception self._get_device_by_id(device_id, 'led') links_list = [self._redfish_links['chassis']] if led_state is not None and led_state.lower() != 'no_override': if led_state.lower() in ['on', 'off']: led_state = 'Off' if led_state.lower() == 'off' else 'Lit' response = vapor_redfish.set_led( led_state=led_state, links=links_list, **self._redfish_request_kwargs) else: logger.error( 'LED state unsupported for LED control operation: {}'. format(led_state)) raise OpenDCREException( 'Unsupported state change to Redfish server on LED operation.' ) else: response = vapor_redfish.get_led( links=links_list, **self._redfish_request_kwargs) if response: return Response(command=command, response_data=response) logger.error('No response for LED control operation: {}'.format( command.data)) raise OpenDCREException( 'No response from Redfish server on LED operation.') except ValueError as e: logger.error('Error with LED control: {}'.format(command.data)) raise OpenDCREException( 'Error returned from Redfish server during LED operation via Redfish ({}).' .format(e))
def _fan(self, command): """ Fan speed control command for a given board and device. Gets the fan speed for a given device. Since this is not NOT a Vapor Fan, we can not control the fan speed, so any attempts to set the fan speed are met with an OpenDCREException being raised. 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. """ device_id = command.data['device_id'] device_name = command.data['device_name'] fan_speed = command.data['fan_speed'] try: if fan_speed is not None: raise OpenDCREException( 'Setting of fan speed is not permitted for this device.') else: # check if the device raises an exception device = self._get_device_by_id(device_id, 'fan_speed') if device['device_type'] != 'fan_speed': raise OpenDCREException( "Attempt to get fan speed for non-fan device.") links_list = [self._redfish_links['thermal']] response = vapor_redfish.get_thermal_sensor( device_type='Fans', device_name=device_name, links=links_list, **self._redfish_request_kwargs) if response: return Response(command=command, response_data=response) logger.error('No response for fan control operation: {}'.format( command.data)) raise OpenDCREException( 'No response from Redfish server on fan operation via Redfish.' ) except ValueError as e: logger.error('Error with fan control: {}'.format(command.data)) raise OpenDCREException( 'Error returned from Redfish server during fan operation via Redfish ({}).' .format(e))
def _boot_target(self, command): """ Boot target command for a given board and device. If a boot target is specified, this will attempt to set the boot device. Otherwise, it will just retrieve the currently configured boot 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 boot target response. """ # get the command data out from the incoming command device_id = command.data['device_id'] boot_target = command.data['boot_target'] try: # check if the device raises an exception self._get_device_by_id(device_id, 'system') links_list = [self._redfish_links['system']] if boot_target in [None, 'status']: response = vapor_redfish.get_boot( links=links_list, **self._redfish_request_kwargs) else: boot_target = 'no_override' if boot_target not in [ 'pxe', 'hdd' ] else boot_target response = vapor_redfish.set_boot( target=boot_target, links=links_list, **self._redfish_request_kwargs) if response: return Response(command=command, response_data=response) logger.error('No response for boot target operation: {}'.format( command.data)) raise OpenDCREException( 'No response from Redfish server on boot target operation via Redfish.' ) except ValueError as e: logger.error( 'Error getting or setting Redfish boot target: {}'.format( command.data)) raise OpenDCREException( 'Error returned from Redfish server during boot target operation via Redfish ({}).' .format(e))
def _power(self, command): """ Power 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 power response. """ # get the command data out from the incoming command device_id = command.data['device_id'] power_action = command.data['power_action'] try: # validate device supports power control self._get_device_by_id(device_id, 'power') if power_action not in ['on', 'off', 'cycle', 'status']: raise ValueError( 'Invalid Redfish power action {} for board {} device {}.'. format(power_action, hex(self.board_id), hex(device_id))) else: links_list = [ self._redfish_links['system'], self._redfish_links['power'] ] if power_action == 'status': response = vapor_redfish.get_power( links=links_list, **self._redfish_request_kwargs) else: response = vapor_redfish.set_power( power_action, links=links_list, **self._redfish_request_kwargs) if response: return Response(command=command, response_data=response) # if we get here there is either no device found, or response received logger.error('Power control attempt returned no data: {}'.format( command.data)) raise OpenDCREException( 'No response from Redfish server for power control action.') except ValueError as e: logger.error('Error controlling Redfish power: {}'.format( command.data)) raise OpenDCREException( 'Error returned from Redfish server in controlling power via Redfish ({}).' .format(e))
def _fan(self, command): """ Fan speed control command for a given board and device. Gets the fan speed for a given device. Since this is not NOT a Vapor Fan, we can not control the fan speed, so any attempts to set the fan speed are met with an OpenDCREException being raised. 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. """ # get the command data out from the incoming command device_id = command.data['device_id'] device_name = command.data['device_name'] fan_speed = command.data['fan_speed'] try: if fan_speed is not None: raise OpenDCREException( 'Setting of fan speed is not permitted for this device.') else: device = self._get_device_by_id(device_id, 'fan_speed') reading = vapor_ipmi.read_sensor(sensor_name=device_name, **self._ipmi_kwargs) response = dict() if device['device_type'] == 'fan_speed': response['speed_rpm'] = reading['sensor_reading'] else: raise OpenDCREException( 'Attempt to get fan speed for non-fan device.') response['health'] = reading['health'] response['states'] = reading['states'] if response is not None: return Response(command=command, response_data=response) logger.error('No response for fan control operation: {}'.format( command.data)) raise OpenDCREException( 'No response from BMC on fan operation via IPMI.') except Exception: raise OpenDCREException( 'Error with fan control (device id: {})'.format( hex(device_id))), None, sys.exc_info()[2]
def _read(self, command): """ Read the data off of a given board's 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 read response. """ # get the command data out from the incoming command device_id = command.data['device_id'] device_type_string = command.data['device_type_string'] try: device = self._get_device_by_id(device_id, device_type_string) reading = vapor_ipmi.read_sensor(sensor_name=device['device_info'], **self._ipmi_kwargs) response = dict() # TODO (etd) - this could be consolidated a bit if we had a helper fn which did device type -> reading # measure lookup, e.g. lookup('temperature') --> 'temperature_c' ; could also be useful in other places if device['device_type'] == const.DEVICE_TEMPERATURE: response['temperature_c'] = reading['sensor_reading'] elif device['device_type'] == const.DEVICE_FAN_SPEED: response['speed_rpm'] = reading['sensor_reading'] elif device['device_type'] == const.DEVICE_VOLTAGE: response['voltage'] = reading['sensor_reading'] response['health'] = reading['health'] response['states'] = reading['states'] if response is not None: return Response(command=command, response_data=response) # if we get here, there was no sensor device found, so we must raise logger.error( 'No response for sensor reading for command: {}'.format( command.data)) raise OpenDCREException('No sensor reading returned from BMC.') except Exception: raise OpenDCREException( 'Error reading IPMI sensor (device id: {})'.format( hex(device_id))), None, sys.exc_info()[2]
def get_thermal_sensor(device_type, device_name, links, timeout, username, password): """ Get thermal sensor information from remote host. Args: device_type (str): the type of device to get information about on the remote system. device_name (str): the name of the device to get information about on the remote system. links (list[str]): the list of links to connect to via HTTP timeout (int | float): the number of seconds a GET will wait for a connection before timing out on the request username (str): the username for basic HTTP authentication password (str): the password for basic HTTP authentication Returns: dict: Thermal sensor information from the remote system. """ response = dict() try: thermal_sensors = get_data( link=links[0], timeout=timeout, username=username, password=password ) except ValueError as e: logger.error('No data retrieved on GET of thermal schema: {}'.format(e.message)) raise OpenDCREException('Cannot retrieve data from thermal schema: {}'.format(e.message)) try: thermal_sensors = thermal_sensors[device_type] for device in thermal_sensors: if device['Name'] == device_name: device_health = device['Status']['Health'].lower() response['health'] = 'ok' if device_health == 'ok' else device_health response['states'] = [] if response['health'] == 'ok' else [device['Status']['State'].lower()] if device_type == 'Fans': response['speed_rpm'] = float(device['Reading']) elif device_type == 'Temperatures': response['temperature_c'] = float(device['ReadingCelsius']) if response: return response else: logger.error('Device information not a match to devices from GET on thermal schema.') raise ValueError('No device matching information from GET on thermal schema.') except KeyError as e: logger.error('Incomplete data for sensor reading from GET on thermal schema: {}'.format(e.message)) raise OpenDCREException('Incomplete data from thermal schema. Sensor information not found: {}'.format(e.message))
def get_flex_victoria_power_reading(username=None, password=None, ip_address=None, port=BMC_PORT): """ Flex Ciii Victoria 2508 power reading retrieval. Uses master r/w command to retrieve the power status from the two PSUs; the readings are then summed together and returned. As with many other commands, a 'good' power reading is only produced when chassis power is 'on'. Otherwise, we end up seeing readings that are just 0W. If a PSU is missing, we still read both PSUs, and sum what we've got. Even if only 1/2 of the PSUs are present, we can still get an accurate reading. Args: username (str): The username to connect to BMC with. password (str): The password to connect to BMC with. ip_address (str): The IP Address of the BMC. port (int): BMC port Returns: dict: Power reading information from the remote system. Raises: OpenDCRException: in cases where BMC is unreachable or an error is encountered processing the command. """ psu0_power = 0 psu1_power = 0 with IpmiCommand(ip_address=ip_address, username=username, password=password, port=port) as ipmicmd: # get PSU0 consumption try: result = ipmicmd.raw_command(netfn=0x06, command=0x52, data=(0xa0, 0xb0, 0x02, 0x96)) if 'error' in result: raise OpenDCREException( 'Error executing master r/w command on {} : {}'.format(ip_address, result['error']) ) psu0_power = _convert_linear_11((result['data'][1] << 8) | result['data'][0]) except Exception: # PSU 0 or 1 may be missing, which is fine, so no action needed pass # get PSU1 consumption try: result = ipmicmd.raw_command(netfn=0x06, command=0x52, data=(0xa0, 0xb2, 0x02, 0x96)) if 'error' in result: raise OpenDCREException( 'Error executing master r/w command on {} : {}'.format(ip_address, result['error']) ) psu1_power = _convert_linear_11((result['data'][1] << 8) | result['data'][0]) except Exception: # PSU 0 or 1 may be missing, which is fine, so no action needed pass return {'input_power': float(psu0_power + psu1_power)}
def set_boot(username=None, password=None, ip_address=None, port=BMC_PORT, target=None): """ Set the boot target on remote host. Args: username (str): Username to connect to BMC with. password (str): Password to connect to BMC with. ip_address (str): BMC IP Address. port (int): BMC port target (str): The boot target to set the remote host to. Returns: dict: boot target as observed, or IpmiException from pyghmi. """ response = dict() with IpmiCommand(ip_address=ip_address, username=username, password=password, port=port) as ipmicmd: target = dict(pxe='network', hdd='hd', no_override='default').get(target) result = ipmicmd.set_bootdev(bootdev=target) if 'error' in result: raise OpenDCREException( 'Error setting boot device from IPMI BMC {} : {}'.format(ip_address, result['error']) ) # FIXME (etd) - here, if bootdev doesn't exist, we use 'unknown'. in the dict `get` # in the following line, there is no record for unknown, so it will default to the # value of `None` -- is this desired? bootdev = result.get('bootdev', 'unknown') bootdev = dict(network='pxe', hd='hdd', default='no_override').get(bootdev) response['target'] = bootdev return response
def get_boot(links, timeout, username, password): """ Get boot target from remote host. Args: links (list[str]): the list of links to connect to via HTTP timeout (int | float): the number of seconds a GET will wait for a connection before timing out on the request username (str): the username for basic HTTP authentication password (str): the password for basic HTTP authentication Returns: dict: Boot target information from the remote system. """ response = dict() try: boot_data = get_data( link=links[0], timeout=timeout, username=username, password=password ) except ValueError as e: logger.error('No data retrieved on GET of systems schema: {}'.format(e.message)) raise OpenDCREException('Cannot retrieve data from systems schema: {}'.format(e.message)) try: boot_data = boot_data['Boot'] response['target'] = 'no_override' if boot_data['BootSourceOverrideTarget'].lower() == 'none' \ else boot_data['BootSourceOverrideTarget'].lower() return response except KeyError as e: logger.error('Incomplete or no data for boot target reading from GET on systems schema: {}'.format(e.message)) raise KeyError('Incomplete or no data from systems schema. {} not found.'.format(e.message))
def set_led(led_state, links, timeout, username, password): """ Turn the remote system LED on or off. Args: led_state (str): the state to set the led to on the remote device links (list[str]): the list of links to connect to via HTTP timeout (int | float): the number of seconds a PATCH will wait for a connection before timing out on the request username (str): the username for basic HTTP authentication password (str): the password for basic HTTP authentication Returns: dict: LED State as set. """ _payload = {'IndicatorLED': led_state} try: patch_data( link=links[0], payload=_payload, timeout=timeout, username=username, password=password, ) response = get_led(links=links, timeout=timeout, username=username, password=password) return response except ValueError as e: logger.error('LED state not set on PATCH or response not returned on GET: {}'.format(e.message)) raise OpenDCREException('LED state cannot be set. POST error or GET error: {}'.format(e.message))
def get_data(link, timeout, username=None, password=None): """ Gets the json data from the Redfish server via the link specified. Args: link (str): the link to which information is requested. timeout (int | float): the number of seconds a GET will wait for a connection before timing out on the request username (str): the username for basic authentication. password (str): the password for basic authentication. Returns: dict: a representation of the json data from the Redfish server. """ try: if username is not None and password is not None: r = requests.get(link, timeout=timeout, auth=HTTPBasicAuth(username, password)) else: r = requests.get(link, timeout=timeout) except requests.exceptions.ConnectionError as e: raise OpenDCREException( 'Unable to GET link {} due to ConnectionError: {}'.format( link, e.message)) if r.status_code != 200: logger.error('Unexpected status code for GET method: {}'.format( r.status_code)) raise ValueError('Unable to GET link {}. Status code: {}'.format( link, r.status_code)) else: return r.json()
def _get_device_by_id(self, device_id, device_type_string): """ Get a device that can be used for device-relative commands. When device_id is numeric, this can be used to validate the device_id and type match, and get its device entry for use elsewhere. When it's a string value we validate the device_info and device_type match what is given, and return the device entry. Args: device_id (str | int): the device_id to look up. device_type_string (str): the type of the device that device_id should be represented by in the board record. Returns: dict: the device entry from the board_record corresponding to this device. """ if isinstance(device_id, int): # look up device by device_id and return its record if found for device in self.board_record['devices']: if (format(device_id, '04x') ) == device['device_id'] and device_type_string == device[ 'device_type']: return device elif isinstance(device_id, basestring): # if this is a non-numeric device_id, we'll look for the device by the string id and return its record for device in self.board_record['devices']: if 'device_info' in device: if device_id.lower() == device['device_info'].lower() and \ device_type_string == device['device_type']: return device # if we get here, numeric and string device_id search has failed, so raise exception raise OpenDCREException( 'Device ID {} not found in board record for {}.'.format( device_id, self.board_id))
def get_power(links, timeout, username, password): """ Get power information from the remote system. Args: links (list[str]): the list of links to connect to via HTTP timeout (int | float): the number of seconds a GET will wait for a connection before timing out on the request username (str): the username for basic HTTP authentication password (str): the password for basic HTTP authentication Returns: dict: Power information from the remote system. """ response = dict() power_data = dict() try: power_data['power'] = get_data( link=links[1], timeout=timeout, username=username, password=password ) power_data['systems'] = get_data( link=links[0], timeout=timeout, username=username, password=password ) except ValueError as e: unfound = ', '.join({'power', 'systems'}.difference(power_data.keys())) logger.error('No data retrieved for {} schema(s) on GET: {}'.format(unfound, e.message)) raise OpenDCREException('Cannot retrieve data from {} schema(s): {}'.format(unfound, e.message)) try: response['power_status'] = power_data['systems']['PowerState'].lower() power_data['power'] = power_data['power']['PowerControl'][0] if float(power_data['power']['PowerConsumedWatts']) > float(power_data['power']['PowerLimit']['LimitInWatts']): response['over_current'] = True else: response['over_current'] = False response['power_ok'] = True if power_data['power']['Status']['Health'].lower() == 'ok' else False response['input_power'] = float(power_data['power']['PowerConsumedWatts']) return response except KeyError as e: logger.error('Incomplete or no data from GET on systems and power schemas'.format(e.message)) raise OpenDCREException('Cannot retrieve power data.'.format(e.message))
def get_power_sensor(device_type, device_name, links, timeout, username, password): """ Get power sensor information from remote host. Args: device_type (str): 'Voltages' for a voltage device, 'PowerSupplies' for a power_supply device device_name (str): the name of the device. links (list[str]): the list of links to connect to via HTTP timeout (int | float): the number of seconds a GET will wait for a connection before timing out on the request username (str): the username for basic HTTP authentication password (str): the password for basic HTTP authentication Returns: dict: Power sensor information from the remote system. """ response = dict() try: power_sensors = get_data( link=links[0], timeout=timeout, username=username, password=password ) except ValueError as e: logger.error('No data retrieved on GET of power schema: {}'.format(e.message)) raise OpenDCREException('Cannot retrieve data from power schema: {}'.format(e.message)) try: power_sensors = power_sensors[device_type] for device in power_sensors: if device['Name'] == device_name: response['health'] = 'ok' if device['Status']['Health'].lower() == 'ok' \ else device['Status']['Health'].lower() response['states'] = [] if response['health'] == 'ok' else [device['Status']['State'].lower()] if device_type == 'Voltages': response['voltage'] = float(device['ReadingVolts']) if response: return response else: logger.error('Device information not a match to devices from GET on power schema.') raise ValueError('No device matching information from GET on power schema.') except KeyError as e: logger.error('Incomplete data for sensor reading from GET on power schema: {}'.format(e.message)) raise OpenDCREException('Incomplete data from power schema. Sensor information not found: {}'.format(e.message))
def _get_temperature_readings(username=None, password=None, ip_address=None, port=BMC_PORT, entity=None): """ Internal wrapper for Get Temperature Reading DCMI command. Args: username (str): The username to connect to BMC with. password (str): The password to connect to BMC with. ip_address (str): The IP address of the BMC to connect to. port (int): BMC port entity (str): Which entity to get temperature readings for: 'inlet', 'cpu', and 'system_board'. Returns: dict: A dictionary of 'readings' containing a list of readings retrieved for the given entity type. These readings include entity and instance IDs for use elsewhere, in addition to a temperature_c reading. """ with IpmiCommand(ip_address=ip_address, username=username, password=password, port=port) as ipmicmd: cmd_data = [0xdc, 0x01] if entity == 'inlet': cmd_data.append(0x40) # entity ID (air inlet) cmd_data.append(0x00) # instance ID (0x00 means return all instances) cmd_data.append(0x01) # starting instance ID (0x01 means start with the first) elif entity == 'cpu': cmd_data.append(0x41) cmd_data.append(0x00) cmd_data.append(0x01) elif entity == 'system_board': cmd_data.append(0x41) cmd_data.append(0x00) cmd_data.append(0x01) else: raise ValueError('Invalid DCMI entity type specified for temperature reading command.') result = ipmicmd.raw_command(netfn=0x2c, command=0x10, data=tuple(cmd_data)) if 'error' in result: raise OpenDCREException( 'Error executing DCMI temperature readings command on {} : {}'.format(ip_address, result['error']) ) readings = {'readings': []} """ TODO: read byte 1 instead which has the actual total number of responses, as opposed to byte 2 which has the total in the response for a single command (no more than 8). This means we will iterate via the starting instance id through all of the possible instances until none remain. """ start_byte = 3 # start on first byte of reading for x in range(0, result['data'][2]): # get the reading from the lower 7 bits temperature = float(result['data'][start_byte] & 0b01111111) # if the upper bit is a 1, the sign is negative if result['data'][start_byte] >> 7 & 0b01 == 0b01: temperature *= -1.0 instance = result['data'][start_byte + 1] start_byte += 2 readings['readings'].append([{'temperature_c': temperature, 'entity': entity, 'instance': instance}]) return readings
def _led(self, command): """ LED command for a given board and device. If an LED state is specified, this will attempt to set the LED state of the given device. Otherwise, it will just retrieve the currently configured LED state. 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 LED response. """ # get the command data out from the incoming command device_id = command.data['device_id'] led_state = command.data['led_state'] try: if led_state is not None: self._get_device_by_id(device_id, 'led') led_state = 0 if led_state.lower() == 'off' else 1 led_response = vapor_ipmi.set_identify(led_state=led_state, **self._ipmi_kwargs) led_response['led_state'] = 'off' if led_response[ 'led_state'] == 0 else 'on' else: self._get_device_by_id(device_id, 'led') led_response = vapor_ipmi.get_identify(**self._ipmi_kwargs) if led_response is not None: return Response(command=command, response_data=led_response) logger.error('No response for LED control operation: {}'.format( command.data)) raise OpenDCREException( 'No response from BMC on LED operation via IPMI.') except Exception: raise OpenDCREException( 'Error with LED control (device id: {})'.format( device_id)), None, sys.exc_info()[2]
def _boot_target(self, command): """ Boot target command for a given board and device. If a boot target is specified, this will attempt to set the boot device. Otherwise, it will just retrieve the currently configured boot 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 boot target response. """ # get the command data out from the incoming command device_id = command.data['device_id'] boot_target = command.data['boot_target'] try: if boot_target is None or boot_target == 'status': self._get_device_by_id(device_id, 'system') boot_info = vapor_ipmi.get_boot(**self._ipmi_kwargs) else: boot_target = 'no_override' if boot_target not in [ 'pxe', 'hdd' ] else boot_target self._get_device_by_id(device_id, 'system') boot_info = vapor_ipmi.set_boot(target=boot_target, **self._ipmi_kwargs) if boot_info is not None: return Response(command=command, response_data=boot_info) logger.error('No response for boot target operation: {}'.format( command.data)) raise OpenDCREException( 'No response from BMC on boot target operation via IPMI.') except Exception: raise OpenDCREException( 'Error getting or setting IPMI boot target (device id: {})'. format(hex(device_id))), None, sys.exc_info()[2]
def _asset(self, command): """ Asset info 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 asset response. """ # get the command data out from the incoming command device_id = command.data['device_id'] try: # validate that asset is supported for the device self._get_device_by_id(device_id, 'system') links_list = [ self._redfish_links['chassis'], self._redfish_links['system'], self._redfish_links['bmc'] ] response = vapor_redfish.get_asset(links=links_list, **self._redfish_request_kwargs) response['redfish_ip'] = self.redfish_ip if 'chassis_info' in response: return Response(command=command, response_data=response) logger.error('No response getting asset info for {}'.format( command.data)) raise OpenDCREException( 'No response from Redfish server when retrieving asset information via Redfish.' ) except ValueError as e: logger.error('Error getting Redfish asset info: {}'.format( command.data)) raise OpenDCREException( 'Error returned from Redfish server when retrieving asset info via Redfish ({}).' .format(e))
def set_power(power_action, links, timeout, username, password): """ Set power state on the remote system. Args: power_action (str): 'on'/'off'/'cycle' - the state of the power of the remote device links (list[str]): the list of links to connect to via HTTP timeout (int | float): the number of seconds a POST will wait for a connection before timing out on the request username (str): the username for basic HTTP authentication password (str): the password for basic HTTP authentication Returns: dict: Power information from the remote system. """ action_link = links[0] + '/Actions/ComputerSystem.Reset' _payload = str() if power_action.lower() == 'on': _payload = 'On' elif power_action.lower() == 'off': _payload = 'ForceOff' elif power_action.lower() == 'cycle': _payload = 'ForceRestart' if _payload: _payload = {'ResetType': _payload} try: post_action( link=action_link, payload=_payload, timeout=timeout, username=username, password=password ) response = get_power( links=links, timeout=timeout, username=username, password=password ) return response except ValueError as e: logger.error('Power not set with POST or response not returned on GET: {}'.format(e.message)) raise OpenDCREException('Power cannot be set. POST error or GET error: {}'.format(e.message)) else: logger.error('No payload data for POST on systems schema. Power cannot be set.') raise ValueError('No payload data for POST action. Power cannot be set.')
def _get_members(json_data, ip_address, port): """ Get all the members from the collection data on a redfish device. Args: json_data (dict): the data too be searched through for a Member collection. ip_address (str): the ip_address of the redfish server port (str | int): the port of the redfish server Returns: list[str]: all the links from the members collection. """ members_list = list() if 'Members' in json_data: try: if int(json_data['*****@*****.**']) == 1: return [ _build_link(ip_address=ip_address, port=port, path=str(json_data['Members'][0]['@odata.id'])) ] else: for item in json_data['Members']: members_list.append( _build_link(ip_address=ip_address, port=port, path=str(item['@odata.id']))) return members_list except KeyError as e: logger.error('Collection defined without {}.'.format(e.message)) raise OpenDCREException( 'Unable to verify number of members. {} not present.'.format( e.message)) else: logger.error('No Members collection defined in {} schema.'.format( json_data['Name'])) raise ValueError( 'Cannot find Members collection in the data specified: {}'.format( json_data['Name']))
def sensors(username=None, password=None, ip_address=None, port=BMC_PORT): """ Get list of sensors from remote system. Args: username (str): The username to use to connect to the remote BMC. password (str): The password to use to connect to the remote BMC. ip_address (str): The IP Address of the BMC. port (int): BMC port Returns: list: Sensor number, id string, and type for each sensor available. """ with IpmiCommand(ip_address=ip_address, username=username, password=password, port=port) as ipmicmd: sdr = ipmicmd.init_sdr() if sdr is None: raise OpenDCREException('Error initializing SDR from IPMI BMC {}'.format(ip_address)) response = [ { 'sensor_number': sensor.sensor_number, 'id_string': sensor.name, 'sensor_type': sensor.sensor_type } for sensor in sdr.sensors.values()] return response
def get_inventory(username=None, password=None, ip_address=None, port=BMC_PORT): """ Get inventory information from the FRU of the remote system. Args: username (str): The username to connect to BMC with. password (str): The password to connect to BMC with. ip_address (str): The IP Address of the BMC. port (int): BMC port Returns: dict: inventory information from the remote system. """ response = dict() with IpmiCommand(ip_address=ip_address, username=username, password=password, port=port) as ipmicmd: result = ipmicmd.get_inventory_of_component('System') if 'error' in result: raise OpenDCREException( 'Error retrieving System inventory from IPMI BMC {} : {}'.format(ip_address, result['error']) ) response['chassis_info'] = dict() response['chassis_info']['chassis_type'] = result.get('Chassis type', '') response['chassis_info']['part_number'] = result.get('Chassis part number', '') response['chassis_info']['serial_number'] = result.get('Chassis serial number', '') response['board_info'] = dict() response['board_info']['manufacturer'] = result.get('Board manufacturer', '') response['board_info']['part_number'] = result.get('Board part number', '') response['board_info']['product_name'] = result.get('Board product name', '') response['board_info']['serial_number'] = result.get('Board serial number', '') response['product_info'] = dict() response['product_info']['asset_tag'] = result.get('Asset Number', '') response['product_info']['part_number'] = result.get('Model', '') response['product_info']['manufacturer'] = result.get('Manufacturer', '') response['product_info']['product_name'] = result.get('Product name', '') response['product_info']['serial_number'] = result.get('Serial Number', '') response['product_info']['version'] = result.get('Hardware Version', '') return response
def _read(self, command): """ Read the data off of a given board's 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 read response. """ # get the command data out from the incoming command device_id = command.data['device_id'] device_type_string = command.data['device_type_string'] response = dict() if device_type_string.lower() == 'fan_speed': device_type = 'Fans' elif device_type_string.lower() == 'temperature': device_type = 'Temperatures' elif device_type_string.lower() == 'voltage': device_type = 'Voltages' elif device_type_string.lower() == 'power_supply': device_type = 'PowerSupplies' else: logger.error( 'Unsupported device type for Redfish device: {}'.format( device_type_string)) raise OpenDCREException( '{} not a supported device type for Redfish.'.format( device_type_string)) try: # check if the device raises an exception device = self._get_device_by_id(device_id, device_type_string) device_name = device['device_info'] if device_type_string.lower() in ['fan_speed', 'temperature']: links_list = [self._redfish_links['thermal']] response = vapor_redfish.get_thermal_sensor( device_type=device_type, device_name=device_name, links=links_list, **self._redfish_request_kwargs) elif device_type_string.lower() in ['voltage', 'power_supply']: links_list = [self._redfish_links['power']] response = vapor_redfish.get_power_sensor( device_type=device_type, device_name=device_name, links=links_list, **self._redfish_request_kwargs) if response: return Response(command=command, response_data=response) # if we get here, there was no sensor device found, so we must raise logger.error( 'No response for sensor reading for command: {}'.format( command.data)) raise OpenDCREException( 'No sensor reading returned from Redfish server.') except ValueError as e: logger.error('Error reading Redfish sensor: {}'.format(e.message)) logger.exception(e) raise OpenDCREException('Error reading Redfish sensor: {}'.format( e.message))
def register(cls, devicebus_config, app_config, app_cache): """ Register Redfish devices. Args: devicebus_config (dict): a dictionary containing the devicebus configurations for Redfish. within this dict is a list or reference to the actual configs for the Redfish devices themselves. app_config (dict): Flask application config, where application-wide configurations and constants are stored. app_cache (tuple): a three-tuple which contains the mutable structures which make up the app's device cache and lookup tables. The first item is a mapping of UUIDs to the devices registered here. The second item is a collection of "single board devices". The third item is a collection of "range devices". All collections are mutated by this method to add all devices which are successfully registered, making them available to the Flask app. """ if len(app_cache) != 3: raise ValueError( 'App cache passed to registrar expects 3 collections, but found {}' .format(len(app_cache))) device_cache, single_board_devices, range_devices = app_cache # define a thread lock so we can have synchronous mutations to the input # lists and dicts mutate_lock = threading.Lock() # list to hold errors occurred during device initialization device_init_failure = [] # get config associated with all redfish devices thread_count = devicebus_config.get('device_initializer_threads', 1) scan_on_init = devicebus_config.get('scan_on_init', True) # create a thread pool which will be used for the lifetime of device registration thread_pool = ThreadPool(thread_count) # configuration defined in separate file if 'from_config' in devicebus_config: with open(devicebus_config['from_config']) as f: device_config = json.load(f) # configuration is defined inline of the current file elif 'config' in devicebus_config: device_config = devicebus_config['config'] # otherwise, there is no valid device config specified else: raise ValueError( 'No valid device config found for Redfish configuration. Requires either the ' '"from_config" field or the "config" field.') # now, for each redfish server, we create a device instance if 'racks' in device_config: for rack in device_config['racks']: rack_id = rack['rack_id'] for server in rack['servers']: # pass through scan on init value for all redfish devices server['scan_on_init'] = scan_on_init # check to see if there are any duplicate redfish servers already defined. # this may be the case with the periodic registering of remote # devices. if device_cache: duplicates = False for dev in device_cache.values(): if isinstance(dev, RedfishDevice): if dev.duplicate_config(server): duplicates = True break if not duplicates: RedfishDevice._process_server( server, app_config, rack_id, device_config.get('board_id_range', const.REDFISH_BOARD_RANGE), device_init_failure, mutate_lock, device_cache, single_board_devices) # FIXME (etd) - # a GIL issue appears to be causing the threaded _process_server resolution to hang. this # can likely be fixed by figuring out what the GIL is deadlocking on (probably an import # dependency?) and updating OpenDCRE's caching component to import in the main thread. # Until then, its easier and faster to just disable threading and do device registration # serially. # thread_pool.add_task( # RedfishDevice._process_server, server, app_config, rack_id, # device_config.get('board_id_range', const.REDFISH_BOARD_RANGE), # device_init_failure, mutate_lock, device_cache, single_board_devices # ) else: RedfishDevice._process_server( server, app_config, rack_id, device_config.get('board_id_range', const.REDFISH_BOARD_RANGE), device_init_failure, mutate_lock, device_cache, single_board_devices) # FIXME (etd) - # a GIL issue appears to be causing the threaded _process_server resolution to hang. this # can likely be fixed by figuring out what the GIL is deadlocking on (probably an import # dependency?) and updating OpenDCRE's caching component to import in the main thread. # Until then, its easier and faster to just disable threading and do device registration # serially. # thread_pool.add_task( # RedfishDevice._process_server, server, app_config, rack_id, # device_config.get('board_id_range', const.REDFISH_BOARD_RANGE), # device_init_failure, mutate_lock, device_cache, single_board_devices # ) # wait for all devices to be initialized thread_pool.wait_for_task_completion() # check for device initialization failures if device_init_failure: logger.error('Failed to initialize Redfish devices: {}'.format( device_init_failure)) raise OpenDCREException('Failed to initialize Redfish devices.')
def _power(self, command): """ Power 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 power response. """ # get the command data out from the incoming command device_id = command.data['device_id'] power_action = command.data['power_action'] try: if power_action not in ['on', 'off', 'cycle', 'status']: raise ValueError( 'Invalid IPMI power action {} for board {} device {}.'. format(power_action, hex(self.board_id), hex(device_id))) # determine if there are OEM considerations # for reading power reading_method = None if self.dcmi_supported: reading_method = 'dcmi' if self.fru_info is not None: if self.fru_info['board_info'][ 'manufacturer'] == 'Ciii USA Inc': # flex OEM support should override dcmi support reading_method = 'flex-victoria' # validate device supports power control self._get_device_by_id(device_id, 'power') power_status = vapor_ipmi.power(cmd=power_action, reading_method=reading_method, **self._ipmi_kwargs) """ NB(ABC): disabled this but it could be re-enabled - if the DCMI Power Reading command is giving trouble elsewhere, we could re-enable this, but the checks done at startup should obviate the need for this logic for now. # check reading method, and if 'dcmi' and input_power is 'unknown', set reading_method to # 'None' in the future; 'unknown' indicates an exception occurred, which we do not wish to repeat if reading_method == 'dcmi' and power_status['input_power'] == 'uknown': self.dcmi_supported = False """ if power_status is not None: return Response(command=command, response_data=power_status) # if we get here there is either no device found, or response received logger.error('Power control attempt returned no data: {}'.format( command.data)) raise OpenDCREException( 'No response from BMC for power control action.') except Exception: raise OpenDCREException( 'Error for power control via IPMI (device id: {}).'.format( hex(device_id))), None, sys.exc_info()[2]