def wait_until_idrac_is_ready(self, retries=24, retry_delay=10): """Waits until the iDRAC is in a ready state :param retries: The number of times to check if the iDRAC is ready :param retry_delay: The number of seconds to wait between retries :raises: WSManRequestFailure on request failures :raises: WSManInvalidResponse when receiving invalid response :raises: DRACOperationFailed on error reported back by the DRAC interface or timeout :raises: DRACUnexpectedReturnValue on return value mismatch """ # Try every 10 seconds over 4 minutes for the iDRAC to become ready while retries > 0: LOG.debug("Checking to see if the iDRAC is ready") if self.is_idrac_ready(): LOG.debug("The iDRAC is ready") return LOG.debug("The iDRAC is not ready") retries -= 1 if retries > 0: time.sleep(retry_delay) if retries == 0: err_msg = "Timed out waiting for the iDRAC to become ready" LOG.error(err_msg) raise exceptions.DRACOperationFailed(drac_messages=err_msg)
def list_bios_settings(self, by_name=True): """List the BIOS configuration settings :param by_name: Controls whether returned dictionary uses BIOS attribute name or instance_id as key. :returns: a dictionary with the BIOS settings using its name as the key. The attributes are either BIOSEnumerableAttribute, BIOSStringAttribute or BIOSIntegerAttribute objects. :raises: WSManRequestFailure on request failures :raises: WSManInvalidResponse when receiving invalid response :raises: DRACOperationFailed on error reported back by the DRAC interface """ result = {} namespaces = [(uris.DCIM_BIOSEnumeration, BIOSEnumerableAttribute), (uris.DCIM_BIOSString, BIOSStringAttribute), (uris.DCIM_BIOSInteger, BIOSIntegerAttribute)] for (namespace, attr_cls) in namespaces: attribs = self._get_config(namespace, attr_cls, by_name) if not set(result).isdisjoint(set(attribs)): raise exceptions.DRACOperationFailed( drac_messages=('Colliding attributes %r' % ( set(result) & set(attribs)))) result.update(attribs) return result
def clear_foreign_config(self, raid_controller): """Free up foreign drives The job to clear foreign config will be in pending state. For the changes to be applied, a config job must be created. :param raid_controller: id of the RAID controller :returns: a dictionary containing: - The is_commit_required key with the value always set to True indicating that a config job must be created to clear foreign configuration. - The is_reboot_required key with a RebootRequired enumerated value indicating whether the server must be rebooted to clear foreign configuration. :raises: WSManRequestFailure on request failures :raises: WSManInvalidResponse when receiving invalid response :raises: DRACOperationFailed on error reported back by the DRAC interface :raises: DRACUnexpectedReturnValue on return value mismatch """ selectors = {'SystemCreationClassName': 'DCIM_ComputerSystem', 'CreationClassName': 'DCIM_RAIDService', 'SystemName': 'DCIM:ComputerSystem', 'Name': 'DCIM:RAIDService'} properties = {'Target': raid_controller} doc = self.client.invoke(uris.DCIM_RAIDService, 'ClearForeignConfig', selectors, properties, check_return_value=False) is_commit_required_value = True is_reboot_required_value = None ret_value = utils.find_xml(doc, 'ReturnValue', uris.DCIM_RAIDService).text if ret_value == utils.RET_ERROR: message_id = utils.find_xml(doc, 'MessageID', uris.DCIM_RAIDService).text # A MessageID 'STOR018' indicates no foreign drive was # detected. Return a value which informs the caller nothing # further needs to be done. if message_id == NO_FOREIGN_DRIVE: is_commit_required_value = False is_reboot_required_value = constants.RebootRequired.false else: message = utils.find_xml(doc, 'Message', uris.DCIM_RAIDService).text raise exceptions.DRACOperationFailed( drac_messages=message) return utils.build_return_dict( doc, uris.DCIM_RAIDService, is_commit_required_value=is_commit_required_value, is_reboot_required_value=is_reboot_required_value)
def invoke(self, resource_uri, method, selectors=None, properties=None, expected_return_value=None, wait_for_idrac=True, check_return_value=True): """Invokes a remote WS-Man method :param resource_uri: URI of the resource :param method: name of the method to invoke :param selectors: dictionary of selectors :param properties: dictionary of properties :param expected_return_value: expected return value reported back by the DRAC card. For return value codes check the profile documentation of the resource used in the method call. If not set, return value checking is skipped. :param wait_for_idrac: indicates whether or not to wait for the iDRAC to be ready to accept commands before issuing the command :param check_return_value: indicates if the ReturnValue should be checked and an exception thrown on an unexpected value :returns: an lxml.etree.Element object of the response received :raises: WSManRequestFailure on request failures :raises: WSManInvalidResponse when receiving invalid response :raises: DRACOperationFailed on error reported back by the DRAC interface :raises: DRACUnexpectedReturnValue on return value mismatch """ if wait_for_idrac: self.wait_until_idrac_is_ready() if selectors is None: selectors = {} if properties is None: properties = {} resp = super(WSManClient, self).invoke(resource_uri, method, selectors, properties) if check_return_value: return_value = utils.find_xml(resp, 'ReturnValue', resource_uri).text if return_value == utils.RET_ERROR: message_elems = utils.find_xml(resp, 'Message', resource_uri, True) messages = [ message_elem.text for message_elem in message_elems ] raise exceptions.DRACOperationFailed(drac_messages=messages) if (expected_return_value is not None and return_value != expected_return_value): raise exceptions.DRACUnexpectedReturnValue( expected_return_value=expected_return_value, actual_return_value=return_value) return resp
def list_settings(client, namespaces, by_name=True, fqdd_filter=None, name_formatter=None): """List the configuration settings :param client: an instance of WSManClient. :param namespaces: a list of URI/class pairs to retrieve. :param by_name: controls whether returned dictionary uses attribute name or instance_id as key. :param fqdd_filter: An FQDD used to filter the instances. Note that this is only used when by_name is True. :param name_formatter: a method used to format the keys in the returned dictionary. By default, attribute.name will be used. :returns: a dictionary with the settings using name or instance_id as the key. :raises: WSManRequestFailure on request failures :raises: WSManInvalidResponse when receiving invalid response :raises: DRACOperationFailed on error reported back by the DRAC interface """ result = {} for (namespace, attr_cls) in namespaces: attribs = _get_config(client, namespace, attr_cls, by_name, fqdd_filter, name_formatter) if not set(result).isdisjoint(set(attribs)): raise exceptions.DRACOperationFailed( drac_messages=('Colliding attributes %r' % ( set(result) & set(attribs)))) result.update(attribs) return result
def test_reboot_retries_fail(self, mock_sleep, mock_get_drac_client): mock_client = mock_get_drac_client.return_value mock_client.get_power_state.return_value = drac_constants.POWER_OFF exc = drac_exceptions.DRACOperationFailed( drac_messages=['The command failed to set RequestedState']) mock_client.set_power_state.side_effect = exc with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: self.assertRaises(exception.DracOperationError, task.driver.power.reboot, task) self.assertEqual(drac_power.POWER_STATE_TRIES, mock_client.set_power_state.call_count)
def test_raid_controller_jbod_ex_no_match(self, mock_requests, mock_convert_physical_disks, mock_wait_until_idrac_is_ready): mock_requests.post( 'https://1.2.3.4:443/wsman', text=test_utils.RAIDEnumerations[uris.DCIM_PhysicalDiskView]['ok']) msg = "NON_MATCHING_MESSAGE" exc = exceptions.DRACOperationFailed(drac_messages=msg) mock_convert_physical_disks.side_effect = exc self.assertRaises(exceptions.DRACOperationFailed, self.drac_client.is_jbod_capable, self.raid_controller_fqdd)
def test_raid_controller_jbod_not_supported(self, mock_requests, mock_convert_physical_disks, mock_wait_idrac_is_ready): msg = " operation is not supported on th" exc = exceptions.DRACOperationFailed(drac_messages=msg) mock_convert_physical_disks.side_effect = exc mock_requests.post( 'https://1.2.3.4:443/wsman', text=test_utils.RAIDEnumerations[uris.DCIM_PhysicalDiskView]['ok']) is_jbod = self.drac_client.is_jbod_capable(self.raid_controller_fqdd) self.assertFalse(is_jbod, msg="is_jbod is false")
def test_reboot_retries_success(self, mock_sleep, mock_get_drac_client): mock_client = mock_get_drac_client.return_value mock_client.get_power_state.return_value = drac_constants.POWER_OFF exc = drac_exceptions.DRACOperationFailed( drac_messages=['The command failed to set RequestedState']) mock_client.set_power_state.side_effect = [exc, None] with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: task.driver.power.reboot(task) drac_power_state = drac_power.REVERSE_POWER_STATES[states.POWER_ON] self.assertEqual(2, mock_client.set_power_state.call_count) mock_client.set_power_state.assert_has_calls( [mock.call(drac_power_state), mock.call(drac_power_state)])
def _list_nic_settings(self, selection_expression): result = {} configurable_attributes = [ (uris.DCIM_NICEnumeration, 'DCIM_NICEnumeration', NICEnumerationAttribute), (uris.DCIM_NICString, 'DCIM_NICString', NICStringAttribute), (uris.DCIM_NICInteger, 'DCIM_NICInteger', NICIntegerAttribute) ] for (resource, class_name, attr_cls) in configurable_attributes: attribs = self._get_config(resource, class_name, selection_expression, attr_cls) if not set(result).isdisjoint(set(attribs)): raise ironic_exceptions.DRACOperationFailed( drac_messages=('Colliding attributes %r' % (set(result) & set(attribs)))) result.update(attribs) return result
def list_raid_settings(self, by_name=True): """Returns the list of RAID controllers :returns: a list of RAIDController objects :raises: WSManRequestFailure on request failures :raises: WSManInvalidResponse when receiving invalid response :raises: DRACOperationFailed on error reported back by the DRAC interface """ result = {} namespaces = [(uris.DCIM_RAIDEnumeration, RAIDEnumerableAttribute), (uris.DCIM_RAIDString, RAIDStringAttribute), (uris.DCIM_RAIDInteger, RAIDIntegerAttribute)] for (namespace, attr_cls) in namespaces: attribs = self._get_config(namespace, attr_cls, by_name) if not set(result).isdisjoint(set(attribs)): raise exceptions.DRACOperationFailed( drac_messages=('Colliding attributes %r' % (set(result) & set(attribs)))) result.update(attribs) return result
def delete_jobs(self, job_ids=['JID_CLEARALL']): """Deletes the given jobs, or all jobs if none specified :raises: WSManRequestFailure on request failures :raises: WSManInvalidResponse when receiving invalid response :raises: DRACOperationFailed on error reported back by the iDRAC interface :raises: DRACUnexpectedReturnValue on non-success """ selectors = { 'SystemCreationClassName': 'DCIM_ComputerSystem', 'SystemName': 'idrac', 'CreationClassName': 'DCIM_JobService', 'Name': 'JobService' } if job_ids is None: return messages = [] for job_id in job_ids: properties = {'JobID': job_id} try: self.client.invoke(uris.DCIM_JobService, 'DeleteJobQueue', selectors, properties, expected_return_value=utils.RET_SUCCESS) except exceptions.DRACOperationFailed as dof: for message in dof.args: messages.append(message + " " + job_id) if len(messages): raise exceptions.DRACOperationFailed(drac_messages=messages)
def set_bios_settings(self, new_settings): """Sets the BIOS configuration To be more precise, it sets the pending_value parameter for each of the attributes passed in. For the values to be applied, a config job must be created and the node must be rebooted. :param new_settings: a dictionary containing the proposed values, with each key being the name of attribute and the value being the proposed value. :returns: a dictionary containing the commit_needed key with a boolean value indicating whether a config job must be created for the values to be applied. :raises: WSManRequestFailure on request failures :raises: WSManInvalidResponse when receiving invalid response :raises: DRACOperationFailed on error reported back by the DRAC interface :raises: DRACUnexpectedReturnValue on return value mismatch :raises: InvalidParameterValue on invalid BIOS attribute """ current_settings = self.list_bios_settings() unknown_keys = set(new_settings) - set(current_settings) if unknown_keys: msg = ('Unknown BIOS attributes found: %(unknown_keys)r' % {'unknown_keys': unknown_keys}) raise exceptions.InvalidParameterValue(reason=msg) read_only_keys = [] unchanged_attribs = [] invalid_attribs_msgs = [] attrib_names = [] candidates = set(new_settings) for attr in candidates: if str(new_settings[attr]) == str( current_settings[attr].current_value): unchanged_attribs.append(attr) elif current_settings[attr].read_only: read_only_keys.append(attr) else: validation_msg = current_settings[attr].validate( new_settings[attr]) if validation_msg is None: attrib_names.append(attr) else: invalid_attribs_msgs.append(validation_msg) if unchanged_attribs: LOG.warning('Ignoring unchanged BIOS attributes: %r', unchanged_attribs) if invalid_attribs_msgs or read_only_keys: if read_only_keys: read_only_msg = ['Cannot set read-only BIOS attributes: %r.' % read_only_keys] else: read_only_msg = [] drac_messages = '\n'.join(invalid_attribs_msgs + read_only_msg) raise exceptions.DRACOperationFailed( drac_messages=drac_messages) if not attrib_names: return {'commit_required': False} selectors = {'CreationClassName': 'DCIM_BIOSService', 'Name': 'DCIM:BIOSService', 'SystemCreationClassName': 'DCIM_ComputerSystem', 'SystemName': 'DCIM:ComputerSystem'} properties = {'Target': 'BIOS.Setup.1-1', 'AttributeName': attrib_names, 'AttributeValue': [new_settings[attr] for attr in attrib_names]} doc = self.client.invoke(uris.DCIM_BIOSService, 'SetAttributes', selectors, properties) return {'commit_required': utils.is_reboot_required( doc, uris.DCIM_BIOSService)}
def set_settings(client, list_settings, new_settings, resource_uri, cim_creation_class_name, cim_name, target): current_settings = list_settings() unknown_keys = set(new_settings) - set(current_settings) if unknown_keys: msg = ('Unknown attributes found: %(unknown_keys)r' % { 'unknown_keys': unknown_keys }) raise exceptions.InvalidParameterValue(reason=msg) read_only_keys = [] unchanged_attribs = [] invalid_attribs_msgs = [] attrib_names = [] candidates = set(new_settings) for attr in candidates: if str(new_settings[attr]) == str( current_settings[attr].current_value): unchanged_attribs.append(attr) elif current_settings[attr].read_only: read_only_keys.append(attr) else: validation_msg = current_settings[attr].validate( new_settings[attr]) if validation_msg: invalid_attribs_msgs.append(validation_msg) else: attrib_names.append(attr) if unchanged_attribs: LOG.debug('Ignoring unchanged attributes: %r', unchanged_attribs) if invalid_attribs_msgs or read_only_keys: if read_only_keys: read_only_msg = [ 'Cannot set read-only attributes: %r.' % read_only_keys ] else: read_only_msg = [] drac_messages = '\n'.join(invalid_attribs_msgs + read_only_msg) raise exceptions.DRACOperationFailed(drac_messages=drac_messages) if not attrib_names: return {'commit_required': False, 'reboot_required': False} selectors = { 'CreationClassName': cim_creation_class_name, 'Name': cim_name, 'SystemCreationClassName': 'DCIM_ComputerSystem', 'SystemName': 'DCIM:ComputerSystem' } properties = { 'Target': target, 'AttributeName': attrib_names, 'AttributeValue': [new_settings[attr] for attr in attrib_names] } doc = client.invoke(resource_uri, 'SetAttributes', selectors, properties) return { 'commit_required': is_commit_required(doc, resource_uri), 'reboot_required': utils.is_reboot_required(doc, resource_uri) }
def wait_until_idrac_is_reset(self, force=False): """Resets the iDRAC and waits for it to become ready :param force: does a force reset when True and a graceful reset when False :returns: None. :raises: WSManRequestFailure on request failures :raises: WSManInvalidResponse when receiving invalid response :raises: DRACOperationFailed on failure to reset iDRAC """ return_value = self.reset_idrac(force) if not return_value: LOG.debug("iDRAC failed to reset") raise exceptions.DRACOperationFailed( drac_messages="Failed to reset iDRAC") else: LOG.debug("iDRAC was successfully reset") LOG.info("Waiting for the iDRAC to become not pingable") required_ping_fail_count = 2 retries = 24 ping_fail_count = 0 while retries > 0: response = os.system("ping -c 1 {} 2>&1 1>/dev/null".format( self.client.host)) retries -= 1 if response != 0: ping_fail_count += 1 LOG.debug("The iDRAC is not pingable, ping_fail_count=" "{}".format(ping_fail_count)) if ping_fail_count == required_ping_fail_count: LOG.debug("Breaking") break else: ping_fail_count = 0 LOG.debug("The iDRAC is pingable") sleep(10) if retries == 0 and ping_fail_count < required_ping_fail_count: raise exceptions.DRACOperationFailed( drac_messages="Timed out " "waiting for the " + self.client.host + " iDRAC to " "become not pingable") LOG.info("The iDRAC has become not pingable") LOG.info("Waiting for the iDRAC to become pingable") retries = 24 ping_success_count = 0 while retries > 0: response = os.system("ping -c 1 {} 2>&1 1>/dev/null".format( self.client.host)) retries -= 1 if response != 0: LOG.debug("The iDRAC is not pingable") ping_success_count = 0 else: ping_success_count += 1 LOG.debug("The iDRAC is pingable, ping_success_count=" "{}".format(ping_success_count)) if ping_success_count == 3: LOG.debug("Breaking") break sleep(10) if retries == 0 and ping_success_count < 3: raise exceptions.DRACOperationFailed( drac_messages="Timed out " "waiting for the " + self.client.host + " iDRAC to " "become pingable") LOG.info("The iDRAC has become pingable") sleep(30) LOG.info("Waiting for the iDRAC to become ready") retries = 24 while retries > 0: try: is_ready = self.is_idrac_ready() if is_ready: LOG.info("The iDRAC is ready") break else: LOG.debug("The iDRAC is not ready") except: # It is normal to get a series of connection errors before # the iDRAC becomes ready ex = sys.exc_info()[0] LOG.debug("An exception occurred while checking iDRAC ready " "state. Ignoring.: {}".format(str(ex))) pass retries -= 1 sleep(10) if retries == 0: raise exceptions.DRACOperationFailed( drac_messages="Timed out " "waiting for the " + self.client.host + " iDRAC to " "become ready")
def set_nic_settings(self, nic_id, settings): """Modify one or more settings of a NIC. If successful, the pending values of the attributes are set. For the new values to be applied, a configuration job must be created and the node must be rebooted. :param nic_id: id of the network interface controller (NIC) :param settings: dictionary containing the proposed values, with each key being the name of an attribute and the value being the proposed value :returns: dictionary containing a 'commit_required' key with a boolean value indicating whether a configuration job must be created for the new settings to be applied and also containing a 'reboot_required' key with a boolean value indicating whether or not a reboot is required :raises: WSManRequestFailure on request failures :raises: WSManInvalidResponse when receiving invalid response :raises: DRACOperationFailed on error reported back by the iDRAC interface :raises: InvalidParameterValue on invalid NIC attribute """ current_settings = self.list_nic_settings(nic_id) unknown_keys = set(settings) - set(current_settings) if unknown_keys: msg = ('Unknown NIC attributes found: %(unknown_keys)r' % { 'unknown_keys': unknown_keys }) raise ironic_exceptions.InvalidParameterValue(reason=msg) read_only_keys = [] unchanged_attribs = [] invalid_attribs_msgs = [] attrib_names = [] candidates = set(settings) for attr in candidates: if str(settings[attr]) == str( current_settings[attr].current_value): unchanged_attribs.append(attr) elif current_settings[attr].read_only: read_only_keys.append(attr) else: validation_msg = current_settings[attr].validate( settings[attr]) if validation_msg is None: attrib_names.append(attr) else: invalid_attribs_msgs.append(validation_msg) if unchanged_attribs: LOG.warning('Ignoring unchanged NIC attributes: %r' % unchanged_attribs) if invalid_attribs_msgs or read_only_keys: if read_only_keys: read_only_msg = [ 'Cannot set read-only NIC attributes: %r.' % read_only_keys ] else: read_only_msg = [] drac_messages = '\n'.join(invalid_attribs_msgs + read_only_msg) raise ironic_exceptions.DRACOperationFailed( drac_messages=drac_messages) if not attrib_names: return {'commit_required': False} selectors = { 'CreationClassName': 'DCIM_NICService', 'Name': 'DCIM:NICService', 'SystemCreationClassName': 'DCIM_ComputerSystem', 'SystemName': 'DCIM:ComputerSystem' } properties = { 'Target': nic_id, 'AttributeName': attrib_names, 'AttributeValue': [settings[attr] for attr in attrib_names] } doc = self.client.invoke(uris.DCIM_NICService, 'SetAttributes', selectors, properties) return { 'reboot_required': utils.is_reboot_required(doc, uris.DCIM_NICService), 'commit_required': utils_additional.is_commit_required(doc, uris.DCIM_NICService) }
def set_settings(settings_type, client, namespaces, new_settings, resource_uri, cim_creation_class_name, cim_name, target, name_formatter=None): """Generically handles setting various types of settings on the iDRAC This method pulls the current list of settings from the iDRAC then compares that list against the passed new settings to determine if there are any errors. If no errors exist then the settings are sent to the iDRAC using the passed resource, target, etc. :param settings_type: a string indicating the settings type :param client: an instance of WSManClient :param namespaces: a list of URI/class pairs to retrieve. :param new_settings: a dictionary containing the proposed values, with each key being the name of attribute and the value being the proposed value. :param resource_uri: URI of resource to invoke :param cim_creation_class_name: creation class name of the CIM object :param cim_name: name of the CIM object :param target: target device :param name_formatter: a method used to format the keys in the returned dictionary. By default, attribute.name will be used. :returns: a dictionary containing: - The is_commit_required key with a boolean value indicating whether a config job must be created for the values to be applied. - The is_reboot_required key with a RebootRequired enumerated value indicating whether the server must be rebooted for the values to be applied. Possible values are true and false. :raises: WSManRequestFailure on request failures :raises: WSManInvalidResponse when receiving invalid response :raises: DRACOperationFailed on new settings with invalid values or attempting to set read-only settings or when an error is reported back by the iDRAC interface :raises: DRACUnexpectedReturnValue on return value mismatch :raises: InvalidParameterValue on invalid new setting """ current_settings = list_settings(client, namespaces, by_name=True, name_formatter=name_formatter) unknown_keys = set(new_settings) - set(current_settings) if unknown_keys: msg = ('Unknown %(settings_type)s attributes found: %(unknown_keys)r' % {'settings_type': settings_type, 'unknown_keys': unknown_keys}) raise exceptions.InvalidParameterValue(reason=msg) read_only_keys = [] unchanged_attribs = [] invalid_attribs_msgs = [] attrib_names = [] candidates = set(new_settings) for attr in candidates: if str(new_settings[attr]) == str( current_settings[attr].current_value): unchanged_attribs.append(attr) elif current_settings[attr].read_only: read_only_keys.append(attr) else: validation_msg = current_settings[attr].validate( new_settings[attr]) if not validation_msg: attrib_names.append(attr) else: invalid_attribs_msgs.append(validation_msg) if unchanged_attribs: LOG.debug('Ignoring unchanged %(settings_type)s attributes: ' '%(unchanged_attribs)r' % {'settings_type': settings_type, 'unchanged_attribs': unchanged_attribs}) if invalid_attribs_msgs or read_only_keys: if read_only_keys: read_only_msg = ['Cannot set read-only %(settings_type)s ' 'attributes: %(read_only_keys)r.' % {'settings_type': settings_type, 'read_only_keys': read_only_keys}] else: read_only_msg = [] drac_messages = '\n'.join(invalid_attribs_msgs + read_only_msg) raise exceptions.DRACOperationFailed( drac_messages=drac_messages) if not attrib_names: return build_return_dict( None, resource_uri, is_commit_required_value=False, is_reboot_required_value=constants.RebootRequired.false) selectors = {'CreationClassName': cim_creation_class_name, 'Name': cim_name, 'SystemCreationClassName': 'DCIM_ComputerSystem', 'SystemName': 'DCIM:ComputerSystem'} properties = {'Target': target, 'AttributeName': attrib_names, 'AttributeValue': [new_settings[attr] for attr in attrib_names]} doc = client.invoke(resource_uri, 'SetAttributes', selectors, properties) return build_return_dict(doc, resource_uri)
def set_settings(settings_type, client, namespaces, new_settings, resource_uri, cim_creation_class_name, cim_name, target, name_formatter=None, wait_for_idrac=True, by_name=True): """Generically handles setting various types of settings on the iDRAC This method pulls the current list of settings from the iDRAC then compares that list against the passed new settings to determine if there are any errors. If no errors exist then the settings are sent to the iDRAC using the passed resource, target, etc. :param settings_type: a string indicating the settings type :param client: an instance of WSManClient :param namespaces: a list of URI/class pairs to retrieve. :param new_settings: a dictionary containing the proposed values, with each key being the name of attribute and the value being the proposed value. :param resource_uri: URI of resource to invoke :param cim_creation_class_name: creation class name of the CIM object :param cim_name: name of the CIM object :param target: target device :param name_formatter: a method used to format the keys in the returned dictionary. By default, attribute.name will be used. :param wait_for_idrac: indicates whether or not to wait for the iDRAC to be ready to accept commands before issuing the command :param by_name: Controls whether returned dictionary uses RAID attribute name or instance_id as key. :returns: a dictionary containing: - The is_commit_required key with a boolean value indicating whether a config job must be created for the values to be applied. - The is_reboot_required key with a RebootRequired enumerated value indicating whether the server must be rebooted for the values to be applied. Possible values are true and false. :raises: WSManRequestFailure on request failures :raises: WSManInvalidResponse when receiving invalid response :raises: DRACOperationFailed on new settings with invalid values or attempting to set read-only settings or when an error is reported back by the iDRAC interface :raises: DRACUnexpectedReturnValue on return value mismatch :raises: InvalidParameterValue on invalid new setting """ current_settings = list_settings(client, namespaces, by_name=by_name, name_formatter=name_formatter, wait_for_idrac=wait_for_idrac) unknown_keys = set(new_settings) - set(current_settings) if unknown_keys: msg = ('Unknown %(settings_type)s attributes found: %(unknown_keys)r' % { 'settings_type': settings_type, 'unknown_keys': unknown_keys }) raise exceptions.InvalidParameterValue(reason=msg) read_only_keys = [] unchanged_attribs = [] invalid_attribs_msgs = [] attrib_names = [] candidates = set(new_settings) for attr in candidates: # There are RAID settings that can have multiple values, # however these are all read-only attributes. # Filter out all read-only attributes first so that we exclude # these settings from further consideration current_setting_value = current_settings[attr].current_value if type(current_setting_value) is list: current_setting_value = current_setting_value[0] unchanged_attribute = str( new_settings[attr]) == str(current_setting_value) # check if read-only attribute is unchanged if current_settings[attr].read_only and not unchanged_attribute: read_only_keys.append(attr) if unchanged_attribute: unchanged_attribs.append(attr) else: validation_msg = current_settings[attr].validate( new_settings[attr]) if not validation_msg: attrib_names.append(attr) else: invalid_attribs_msgs.append(validation_msg) if unchanged_attribs: LOG.debug('Ignoring unchanged %(settings_type)s attributes: ' '%(unchanged_attribs)r' % { 'settings_type': settings_type, 'unchanged_attribs': unchanged_attribs }) if invalid_attribs_msgs or read_only_keys: if read_only_keys: read_only_msg = [ 'Cannot set read-only %(settings_type)s ' 'attributes: %(read_only_keys)r.' % { 'settings_type': settings_type, 'read_only_keys': read_only_keys } ] else: read_only_msg = [] drac_messages = '\n'.join(invalid_attribs_msgs + read_only_msg) raise exceptions.DRACOperationFailed(drac_messages=drac_messages) if not attrib_names: return build_return_dict( None, resource_uri, is_commit_required_value=False, is_reboot_required_value=constants.RebootRequired.false) selectors = { 'CreationClassName': cim_creation_class_name, 'Name': cim_name, 'SystemCreationClassName': 'DCIM_ComputerSystem', 'SystemName': 'DCIM:ComputerSystem' } properties = { 'Target': target, 'AttributeValue': [new_settings[attr] for attr in attrib_names] } # To set RAID settings, above we fetched list raid settings using # instance_id to retrieve attribute values. When we pass instance_id in # setattribute method for setting any new RAID settings, wsman raises # an error. So another approach to set those settings is to list raid # settings using instance_id and for settings new settings, pass the # attribute names in list to SetAttributes method along with the target. # That's the reason, we need to handle RAID specific settings like below if settings_type == 'RAID': properties['AttributeName'] = [ current_settings[attr].name for attr in attrib_names ] else: properties['AttributeName'] = attrib_names doc = client.invoke(resource_uri, 'SetAttributes', selectors, properties, wait_for_idrac=wait_for_idrac) return build_return_dict(doc, resource_uri)