Example #1
0
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
Example #2
0
    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]
Example #3
0
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))}
Example #4
0
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))
Example #5
0
    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))
Example #6
0
    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))
Example #7
0
    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))
Example #8
0
    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))
Example #9
0
    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]
Example #10
0
    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]
Example #11
0
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))
Example #12
0
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)}
Example #13
0
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
Example #14
0
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))
Example #15
0
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))
Example #16
0
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()
Example #17
0
    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))
Example #18
0
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))
Example #19
0
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))
Example #20
0
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
Example #21
0
    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]
Example #22
0
    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]
Example #23
0
    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))
Example #24
0
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.')
Example #25
0
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']))
Example #26
0
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
Example #27
0
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
Example #28
0
    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))
Example #29
0
    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.')
Example #30
0
    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]