# Copyright 2018 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """IloClient module""" from proliantutils import exception from proliantutils.ilo import ipmi from proliantutils.ilo import operations from proliantutils.ilo import ribcl from proliantutils.ilo import ris from proliantutils.ilo.snmp import snmp_cpqdisk_sizes as snmp from proliantutils import log from proliantutils.redfish import redfish SUPPORTED_RIS_METHODS = [ 'activate_license', 'clear_secure_boot_keys', 'delete_raid_configuration', 'eject_virtual_media', 'get_current_bios_settings', 'get_current_boot_mode', 'get_host_post_state', 'get_default_bios_settings', 'get_host_power_status', 'get_http_boot_url', 'get_ilo_firmware_version_as_major_minor', 'get_one_time_boot', 'get_pending_bios_settings', 'get_pending_boot_mode', 'get_persistent_boot_device', 'get_product_name', 'get_secure_boot_mode', 'get_server_capabilities', 'get_supported_boot_mode', 'get_vm_status', 'hold_pwr_btn', 'inject_nmi', 'insert_virtual_media', 'press_pwr_btn', 'reset_bios_to_default', 'reset_ilo_credential', 'reset_secure_boot_keys', 'reset_server', 'set_bios_settings', 'set_host_power', 'set_http_boot_url', 'set_one_time_boot', 'set_pending_boot_mode', 'set_secure_boot_mode', 'set_iscsi_boot_info', 'set_iscsi_info', 'unset_iscsi_boot_info', 'unset_iscsi_info', 'get_iscsi_initiator_info', 'set_iscsi_initiator_info', 'set_vm_status', 'update_firmware', 'update_persistent_boot', ] SUPPORTED_REDFISH_METHODS = [ 'delete_raid_configuration', 'get_product_name', 'get_host_post_state', 'get_host_power_status', 'set_host_power', 'reset_server', 'press_pwr_btn', 'hold_pwr_btn', 'get_one_time_boot', 'get_pending_boot_mode', 'get_current_boot_mode', 'activate_license', 'eject_virtual_media', 'inject_nmi', 'insert_virtual_media', 'set_vm_status', 'update_firmware', 'get_persistent_boot_device', 'set_one_time_boot', 'update_persistent_boot', 'set_pending_boot_mode', 'reset_ilo_credential', 'reset_bios_to_default', 'get_secure_boot_mode', 'set_secure_boot_mode', 'reset_secure_boot_keys', 'clear_secure_boot_keys', 'get_server_capabilities', 'get_supported_boot_mode', 'get_essential_properties', 'set_iscsi_boot_info', 'set_iscsi_info', 'unset_iscsi_boot_info', 'unset_iscsi_info', 'get_iscsi_initiator_info', 'set_iscsi_initiator_info', ] LOG = log.get_logger(__name__) class IloClient(operations.IloOperations): def __init__(self, host, login, password, timeout=60, port=443, bios_password=None, cacert=None, snmp_credentials=None, use_redfish_only=False): self.ribcl = ribcl.RIBCLOperations(host, login, password, timeout, port, cacert=cacert) self.info = {'address': host, 'username': login, 'password': password} self.host = host self.use_redfish_only = use_redfish_only if use_redfish_only: self._init_redfish_object(None, host, login, password, bios_password=bios_password, cacert=cacert) LOG.debug(self._("Forced to use 'redfish' way to interact " "with iLO. Model: %(model)s"), {'model': self.model}) else: try: self.model = self.ribcl.get_product_name() except exception.IloError: # Note(deray): This can be a potential scenario where # RIBCL is disabled on a Gen10 (iLO 5) hardware. # So, trying out the redfish operation object instantiation. # If that passes we know that our assumption is right. # If that errors out, then alas! we are left with no other # choice. self._init_redfish_object(False, host, login, password, bios_password=bios_password, cacert=cacert) else: self.ribcl.init_model_based_tags(self.model) if ('Gen10' in self.model): self._init_redfish_object(True, host, login, password, bios_password=bios_password, cacert=cacert, should_set_model=False) else: # Gen9 self.ris = ris.RISOperations( host, login, password, bios_password=bios_password, cacert=cacert) self.snmp_credentials = snmp_credentials self._validate_snmp() LOG.debug(self._("IloClient object created. " "Model: %(model)s"), {'model': self.model}) def _init_redfish_object(self, is_ribcl_enabled, redfish_controller_ip, username, password, bios_password=None, cacert=None, should_set_model=True): self.redfish = redfish.RedfishOperations( redfish_controller_ip, username, password, bios_password=bios_password, cacert=cacert) self.is_ribcl_enabled = is_ribcl_enabled if should_set_model: self.model = self.redfish.get_product_name() def _validate_snmp(self): """Validates SNMP credentials. :raises exception.IloInvalidInputError """ cred = self.snmp_credentials if cred is not None: if cred.get('snmp_inspection') is True: if not all([cred.get('auth_user'), cred.get('auth_prot_pp'), cred.get('auth_priv_pp')]): msg = self._('Either few or all mandatory ' 'SNMP credentials ' 'are missing.') LOG.error(msg) raise exception.IloInvalidInputError(msg) try: auth_protocol = cred['auth_protocol'] if auth_protocol not in ["SHA", "MD5"]: msg = self._('Invalid SNMP auth protocol ' 'provided. ' 'Valid values are SHA or MD5') LOG.error(msg) raise exception.IloInvalidInputError(msg) except KeyError: msg = self._('Auth protocol not provided by user. ' 'The default value of MD5 will ' 'be considered.') LOG.debug(msg) pass try: priv_protocol = cred['priv_protocol'] if priv_protocol not in ["AES", "DES"]: msg = self._('Invalid SNMP privacy protocol ' 'provided. ' 'Valid values are AES or DES') LOG.error(msg) raise exception.IloInvalidInputError(msg) except KeyError: msg = self._('Privacy protocol not provided ' 'by user. ' 'The default value of DES will ' 'be considered.') LOG.debug(msg) pass else: LOG.debug(self._('snmp_inspection set to False. SNMP' 'inspection will not be performed.')) else: LOG.debug(self._('SNMP credentials not provided. SNMP ' 'inspection will not be performed.')) def _call_method(self, method_name, *args, **kwargs): """Call the corresponding method using RIBCL, RIS or REDFISH Make the decision to invoke the corresponding method using RIBCL, RIS or REDFISH way. In case of none, throw out ``NotImplementedError`` """ if self.use_redfish_only: if method_name in SUPPORTED_REDFISH_METHODS: the_operation_object = self.redfish else: raise NotImplementedError() else: the_operation_object = self.ribcl if 'Gen10' in self.model: if method_name in SUPPORTED_REDFISH_METHODS: the_operation_object = self.redfish else: if (self.is_ribcl_enabled is not None and not self.is_ribcl_enabled): raise NotImplementedError() elif ('Gen9' in self.model) and (method_name in SUPPORTED_RIS_METHODS): the_operation_object = self.ris method = getattr(the_operation_object, method_name) LOG.debug(self._("Using %(class)s for method %(method)s."), {'class': type(the_operation_object).__name__, 'method': method_name}) return method(*args, **kwargs) def get_all_licenses(self): """Retrieve license type, key, installation date, etc.""" return self._call_method('get_all_licenses') def get_product_name(self): """Get the model name of the queried server.""" return self._call_method('get_product_name') def get_host_power_status(self): """Request the power state of the server.""" return self._call_method('get_host_power_status') def get_http_boot_url(self): """Request the http boot url. :returns: URL for http boot. :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedError, if the command is not supported on the server. """ return self._call_method('get_http_boot_url') def set_http_boot_url(self, url): """Set the url to the UefiShellStartupUrl. :param url: URL for http boot. :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedError, if the command is not supported on the server. """ return self._call_method('set_http_boot_url', url) def set_iscsi_info(self, target_name, lun, ip_address, port='3260', auth_method=None, username=None, password=None): """Set iscsi details of the system in uefi boot mode. The initiator system is set with the target details like IQN, LUN, IP, Port etc. :param target_name: Target Name for iscsi. :param lun: logical unit number. :param ip_address: IP address of the target. :param port: port of the target. :param auth_method : either None or CHAP. :param username: CHAP Username for authentication. :param password: CHAP secret. :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedInBiosError, if the system is in the bios boot mode. """ return self._call_method('set_iscsi_info', target_name, lun, ip_address, port, auth_method, username, password) def set_iscsi_boot_info(self, mac, target_name, lun, ip_address, port='3260', auth_method=None, username=None, password=None): """Set iscsi details of the system in uefi boot mode. The initiator system is set with the target details like IQN, LUN, IP, Port etc. :param mac: The MAC of the NIC to be set with iSCSI information :param target_name: Target Name for iscsi. :param lun: logical unit number. :param ip_address: IP address of the target. :param port: port of the target. :param auth_method : either None or CHAP. :param username: CHAP Username for authentication. :param password: CHAP secret. :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedInBiosError, if the system is in the bios boot mode. """ LOG.warning("'set_iscsi_boot_info' is deprecated. The 'MAC' parameter" "passed in is ignored. Use 'set_iscsi_info' instead.") return self._call_method('set_iscsi_info', target_name, lun, ip_address, port, auth_method, username, password) def unset_iscsi_info(self): """Disable iscsi boot option of the system in uefi boot mode. :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedInBiosError, if the system is in the bios boot mode. """ return self._call_method('unset_iscsi_info') def unset_iscsi_boot_info(self, mac): """Disable iscsi boot option of the system in uefi boot mode. :param mac: The MAC of the NIC to be set with iSCSI information. :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedInBiosError, if the system is in the bios boot mode. """ LOG.warning("'unset_iscsi_boot_info' is deprecated. The 'MAC' " "parameter passed in is ignored. Use 'unset_iscsi_info' " "instead.") return self._call_method('unset_iscsi_info') def get_iscsi_initiator_info(self): """Returns iSCSI initiator information of iLO. :returns: iSCSI initiator information. :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedInBiosError, if the system is in the bios boot mode. """ return self._call_method('get_iscsi_initiator_info') def set_iscsi_initiator_info(self, initiator_iqn): """Set iSCSI initiator information in iLO. :param initiator_iqn: Initiator iqn for iLO. :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedInBiosError, if the system is in the bios boot mode. """ return self._call_method('set_iscsi_initiator_info', initiator_iqn) def get_one_time_boot(self): """Retrieves the current setting for the one time boot.""" return self._call_method('get_one_time_boot') def get_vm_status(self, device='FLOPPY'): """Returns the virtual media drive status like url, is connected, etc. """ return self._call_method('get_vm_status', device) def reset_server(self): """Resets the server.""" return self._call_method('reset_server') def press_pwr_btn(self): """Simulates a physical press of the server power button.""" return self._call_method('press_pwr_btn') def hold_pwr_btn(self): """Simulate a physical press and hold of the server power button.""" return self._call_method('hold_pwr_btn') def set_host_power(self, power): """Toggle the power button of server. :param power: 'ON' or 'OFF' """ return self._call_method('set_host_power', power) def set_one_time_boot(self, value): """Configures a single boot from a specific device.""" return self._call_method('set_one_time_boot', value) def insert_virtual_media(self, url, device='FLOPPY'): """Notifies iLO of the location of a virtual media diskette image.""" return self._call_method('insert_virtual_media', url, device) def eject_virtual_media(self, device='FLOPPY'): """Ejects the Virtual Media image if one is inserted.""" return self._call_method('eject_virtual_media', device) def set_vm_status(self, device='FLOPPY', boot_option='BOOT_ONCE', write_protect='YES'): """Sets the Virtual Media drive status and allows the boot options for booting from the virtual media. """ return self._call_method('set_vm_status', device, boot_option, write_protect) def get_current_boot_mode(self): """Retrieves the current boot mode settings.""" return self._call_method('get_current_boot_mode') def get_pending_boot_mode(self): """Retrieves the pending boot mode settings.""" return self._call_method('get_pending_boot_mode') def get_supported_boot_mode(self): """Retrieves the supported boot mode.""" return self._call_method('get_supported_boot_mode') def set_pending_boot_mode(self, value): """Sets the boot mode of the system for next boot.""" return self._call_method('set_pending_boot_mode', value) def get_persistent_boot_device(self): """Get the current persistent boot device set for the host.""" return self._call_method('get_persistent_boot_device') def update_persistent_boot(self, device_type=[]): """Updates persistent boot based on the boot mode.""" return self._call_method('update_persistent_boot', device_type) def get_secure_boot_mode(self): """Get the status if secure boot is enabled or not.""" return self._call_method('get_secure_boot_mode') def set_secure_boot_mode(self, secure_boot_enable): """Enable/Disable secure boot on the server.""" return self._call_method('set_secure_boot_mode', secure_boot_enable) def reset_secure_boot_keys(self): """Reset secure boot keys to manufacturing defaults.""" return self._call_method('reset_secure_boot_keys') def clear_secure_boot_keys(self): """Reset all keys.""" return self._call_method('clear_secure_boot_keys') def reset_ilo_credential(self, password): """Resets the iLO password. :param password: The password to be set. :raises: IloError, if account not found or on an error from iLO. :raises: IloCommandNotSupportedError, if the command is not supported on the server. """ return self._call_method('reset_ilo_credential', password) def reset_ilo(self): """Resets the iLO. :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedError, if the command is not supported on the server. """ return self._call_method('reset_ilo') def reset_bios_to_default(self): """Resets the BIOS settings to default values. :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedError, if the command is not supported on the server. """ return self._call_method('reset_bios_to_default') def get_host_uuid(self): """Request host UUID of the server. :returns: the host UUID of the server :raises: IloConnectionError if failed connecting to the iLO. """ return self._call_method('get_host_uuid') def get_host_health_data(self, data=None): """Request host health data of the server. :param: the data to retrieve from the server, defaults to None. :returns: the dictionary containing the embedded health data. :raises: IloConnectionError if failed connecting to the iLO. :raises: IloError, on an error from iLO. """ return self._call_method('get_host_health_data', data) def get_host_health_present_power_reading(self, data=None): """Request the power consumption of the server. :param: the data to retrieve from the server, defaults to None. :returns: the dictionary containing the power readings. :raises: IloConnectionError if failed connecting to the iLO. :raises: IloError, on an error from iLO. """ return self._call_method('get_host_health_present_power_reading', data) def get_host_health_power_supplies(self, data=None): """Request the health power supply information. :param: the data to retrieve from the server, defaults to None. :returns: the dictionary containing the power supply information. :raises: IloConnectionError if failed connecting to the iLO. :raises: IloError, on an error from iLO. """ return self._call_method('get_host_health_power_supplies', data) def get_host_health_fan_sensors(self, data=None): """Get the health Fan Sensor Report. :param: the data to retrieve from the server, defaults to None. :returns: the dictionary containing the fan sensor information. :raises: IloConnectionError if failed connecting to the iLO. :raises: IloError, on an error from iLO. """ return self._call_method('get_host_health_fan_sensors', data) def get_host_health_temperature_sensors(self, data=None): """Get the health Temp Sensor report. :param: the data to retrieve from the server, defaults to None. :returns: the dictionary containing the temperature sensors information. :raises: IloConnectionError if failed connecting to the iLO. :raises: IloError, on an error from iLO. """ return self._call_method('get_host_health_temperature_sensors', data) def get_host_health_at_a_glance(self, data=None): """Get the health at a glance Report. :param: the data to retrieve from the server, defaults to None. :returns: the dictionary containing the health at a glance information. :raises: IloConnectionError if failed connecting to the iLO. :raises: IloError, on an error from iLO. """ return self._call_method('get_host_health_at_a_glance', data) def get_host_power_readings(self): """Retrieves the host power readings. :param: the data to retrieve from the server, defaults to None. :returns: the dictionary containing the power readings. :raises: IloConnectionError if failed connecting to the iLO. :raises: IloError, on an error from iLO. """ return self._call_method('get_host_power_readings') def get_essential_properties(self): """Get the essential scheduling properties :returns: a dictionary containing memory size, disk size, number of cpus, cpu arch, port numbers and mac addresses. :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedError, if the command is not supported on the server. """ data = self._call_method('get_essential_properties') if (data['properties']['local_gb'] == 0): cred = self.snmp_credentials if cred and cred.get('snmp_inspection'): disksize = snmp.get_local_gb(self.host, cred) if disksize: data['properties']['local_gb'] = disksize else: msg = self._('SNMP inspection failed to ' 'get the disk size. Returning ' 'local_gb as 0.') LOG.debug(msg) else: msg = self._("SNMP credentials were not set and " "RIBCL/Redfish failed to get the disk size. " "Returning local_gb as 0.") LOG.debug(msg) return data def get_server_capabilities(self): """Get hardware properties which can be used for scheduling :return: a dictionary of server capabilities. :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedError, if the command is not supported on the server. """ capabilities = self._call_method('get_server_capabilities') # TODO(nisha): Assumption is that Redfish always see the pci_device # member name field populated similarly to IPMI. # If redfish is not able to get nic_capacity, we can fall back to # IPMI way of retrieving nic_capacity in the future. As of now # the IPMI is not tested on Gen10, hence assuming that # Redfish will always be able to give the data. if ('Gen10' not in self.model): major_minor = ( self._call_method('get_ilo_firmware_version_as_major_minor')) # NOTE(vmud213): Even if it is None, pass it on to get_nic_capacity # as we still want to try getting nic capacity through ipmitool # irrespective of what firmware we are using. nic_capacity = ipmi.get_nic_capacity(self.info, major_minor) if nic_capacity: capabilities.update({'nic_capacity': nic_capacity}) if capabilities: return capabilities def activate_license(self, key): """Activates iLO license. :param key: iLO license key. :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedError, if the command is not supported on the server. """ return self._call_method('activate_license', key) def delete_raid_configuration(self): """Deletes the logical drives from the system :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedError, if the command is not supported on the server. """ return self._call_method('delete_raid_configuration') def update_firmware(self, firmware_url, component_type): """Updates the given firmware on the server :param firmware_url: location of the firmware :param component_type: Type of component to be applied to. :raises: InvalidInputError, if the validation of the input fails :raises: IloError, on an error from iLO :raises: IloCommandNotSupportedError, if the command is not supported on the server """ return self._call_method( 'update_firmware', firmware_url, component_type) def inject_nmi(self): """Inject NMI, Non Maskable Interrupt. Inject NMI (Non Maskable Interrupt) for a node immediately. :raises: IloError, on an error from iLO :raises: IloConnectionError, if not able to reach iLO. :raises: IloCommandNotSupportedError, if the command is not supported on the server """ return self._call_method('inject_nmi') def get_host_post_state(self): """Request the current state of system POST. Retrieves current state of system POST. :raises: IloError, on an error from iLO :raises: IloCommandNotSupportedError, if the command is not supported on the server """ return self._call_method('get_host_post_state') def get_current_bios_settings(self, only_allowed_settings=True): """Get current BIOS settings. :param: only_allowed_settings: True when only allowed BIOS settings are to be returned. If False, All the BIOS settings supported by iLO are returned. :return: a dictionary of current BIOS settings is returned. Depending on the 'only_allowed_settings', either only the allowed settings are returned or all the supported settings are returned. :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedError, if the command is not supported on the server. """ return self._call_method('get_current_bios_settings', only_allowed_settings) def get_pending_bios_settings(self, only_allowed_settings=True): """Get current BIOS settings. :param: only_allowed_settings: True when only allowed BIOS settings are to be returned. If False, All the BIOS settings supported by iLO are returned. :return: a dictionary of pending BIOS settings. Depending on the 'only_allowed_settings', either only the allowed settings are returned or all the supported settings are returned. :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedError, if the command is not supported on the server. """ return self._call_method('get_pending_bios_settings', only_allowed_settings) def set_bios_settings(self, data=None, only_allowed_settings=True): """Sets current BIOS settings to the provided data. :param: only_allowed_settings: True when only allowed BIOS settings are to be set. If False, all the BIOS settings supported by iLO and present in the 'data' are set. :param: data: a dictionary of BIOS settings to be applied. Depending on the 'only_allowed_settings', either only the allowed settings are set or all the supported settings that are in the 'data' are set. :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedError, if the command is not supported on the server. """ return self._call_method('set_bios_settings', data, only_allowed_settings) def get_default_bios_settings(self, only_allowed_settings=True): """Get default BIOS settings. :param: only_allowed_settings: True when only allowed BIOS settings are to be returned. If False, All the BIOS settings supported by iLO are returned. :return: a dictionary of default BIOS settings(factory settings). Depending on the 'only_allowed_settings', either only the allowed settings are returned or all the supported settings are returned. :raises: IloError, on an error from iLO. :raises: IloCommandNotSupportedError, if the command is not supported on the server. """ return self._call_method('get_default_bios_settings', only_allowed_settings)