Browse Source

Take Lifecycle Controller out of recovery mode

This patch is to check if a node is in recovery mode and take it
out of recovery mode by setting LifecycleControllerState attribute
value to 'Enabled'.

Modified list_lifecycle_settings() method to use
utils.list_settings() for retrieving lifecycle settings.

Change-Id: I4287f317b2413b70cd00fd4cf8aa69bff6ae5e2f
changes/01/661901/9 3.1.0
mpardhi23 2 years ago
parent
commit
ceef78a938
16 changed files with 642 additions and 75 deletions
  1. +88
    -4
      dracclient/client.py
  2. +3
    -0
      dracclient/constants.py
  3. +10
    -4
      dracclient/resources/job.py
  4. +105
    -42
      dracclient/resources/lifecycle_controller.py
  5. +2
    -1
      dracclient/tests/test_bios.py
  6. +4
    -2
      dracclient/tests/test_idrac_card.py
  7. +64
    -5
      dracclient/tests/test_job.py
  8. +212
    -4
      dracclient/tests/test_lifecycle_controller.py
  9. +6
    -3
      dracclient/tests/test_nic.py
  10. +17
    -1
      dracclient/tests/utils.py
  11. +19
    -0
      dracclient/tests/wsman_mocks/lc_getremoteservicesapistatus_recovery.xml
  12. +17
    -0
      dracclient/tests/wsman_mocks/lc_service-invoke-create_config_job-error.xml
  13. +28
    -0
      dracclient/tests/wsman_mocks/lc_service-invoke-create_config_job-ok.xml
  14. +21
    -0
      dracclient/tests/wsman_mocks/lc_service-invoke-set_attributes-error.xml
  15. +24
    -0
      dracclient/tests/wsman_mocks/lc_service-invoke-set_attributes-ok.xml
  16. +22
    -9
      dracclient/utils.py

+ 88
- 4
dracclient/client.py View File

@ -383,9 +383,12 @@ class DRACClient(object):
cim_name='DCIM:iDRACCardService',
target=idrac_fqdd)
def list_lifecycle_settings(self):
def list_lifecycle_settings(self, by_name=False):
"""List the Lifecycle Controller configuration settings
:param by_name: Controls whether returned dictionary uses Lifecycle
attribute name as key. If set to False, instance_id
will be used.
:returns: a dictionary with the Lifecycle Controller settings using its
InstanceID as the key. The attributes are either
LCEnumerableAttribute or LCStringAttribute objects.
@ -394,7 +397,49 @@ class DRACClient(object):
:raises: DRACOperationFailed on error reported back by the DRAC
interface
"""
return self._lifecycle_cfg.list_lifecycle_settings()
return self._lifecycle_cfg.list_lifecycle_settings(by_name)
def is_lifecycle_in_recovery(self):
"""Checks if Lifecycle Controller in recovery mode or not
This method checks the LCStatus value to determine if lifecycle
controller is in recovery mode by invoking GetRemoteServicesAPIStatus
from iDRAC.
:returns: a boolean indicating if lifecycle controller is in recovery
:raises: WSManRequestFailure on request failures
:raises: WSManInvalidResponse when receiving invalid response
:raises: DRACOperationFailed on error reported back by the DRAC
interface
"""
return self._lifecycle_cfg.is_lifecycle_in_recovery()
def set_lifecycle_settings(self, settings):
"""Sets lifecycle controller configuration
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.
:param 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 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 error reported back by the DRAC
interface
:raises: DRACUnexpectedReturnValue on return value mismatch
:raises: InvalidParameterValue on invalid Lifecycle attribute
"""
return self._lifecycle_cfg.set_lifecycle_settings(settings)
def list_system_settings(self):
"""List the System configuration settings
@ -464,7 +509,9 @@ class DRACClient(object):
cim_system_name='DCIM:ComputerSystem',
reboot=False,
start_time='TIME_NOW',
realtime=False):
realtime=False,
wait_for_idrac=True,
method_name='CreateTargetedConfigJob'):
"""Creates a configuration job.
In CIM (Common Information Model), weak association is used to name an
@ -490,6 +537,10 @@ class DRACClient(object):
schedule_job_execution is called
:param realtime: Indicates if reatime mode should be used.
Valid values are True and False.
:param wait_for_idrac: indicates whether or not to wait for the
iDRAC to be ready to accept commands before
issuing the command.
:param method_name: method of CIM object to invoke
:returns: id of the created job
:raises: WSManRequestFailure on request failures
:raises: WSManInvalidResponse when receiving invalid response
@ -507,7 +558,9 @@ class DRACClient(object):
cim_system_name=cim_system_name,
reboot=reboot,
start_time=start_time,
realtime=realtime)
realtime=realtime,
wait_for_idrac=wait_for_idrac,
method_name=method_name)
def create_nic_config_job(
self,
@ -646,6 +699,37 @@ class DRACClient(object):
cim_creation_class_name='DCIM_BIOSService',
cim_name='DCIM:BIOSService', target=self.BIOS_DEVICE_FQDD)
def commit_pending_lifecycle_changes(
self,
reboot=False,
start_time='TIME_NOW'):
"""Applies all pending changes on Lifecycle by creating a config job
:param reboot: indicates whether a RebootJob should also be
created or not
:param start_time: start time for job execution in format
yyyymmddhhmmss, the string 'TIME_NOW' which
means execute immediately or None which means
the job will not execute until
schedule_job_execution is called
:returns: id of the created job
:raises: WSManRequestFailure on request failures
:raises: WSManInvalidResponse when receiving invalid response
:raises: DRACOperationFailed on error reported back by the DRAC
interface, including start_time being in the past or
badly formatted start_time
:raises: DRACUnexpectedReturnValue on return value mismatch
"""
return self._job_mgmt.create_config_job(
resource_uri=uris.DCIM_LCService,
cim_creation_class_name='DCIM_LCService',
cim_name='DCIM:LCService',
target='',
reboot=reboot,
start_time=start_time,
wait_for_idrac=False,
method_name='CreateConfigJob')
def get_lifecycle_controller_version(self):
"""Returns the Lifecycle controller version


+ 3
- 0
dracclient/constants.py View File

@ -37,6 +37,9 @@ PRIMARY_STATUS = {
# binary unit constants
UNITS_KI = 2 ** 10
# Lifecycle Controller status constant
LC_IN_RECOVERY = '4'
# Reboot required indicator
# Note: When the iDRAC returns optional for this value, this indicates that


+ 10
- 4
dracclient/resources/job.py View File

@ -102,7 +102,9 @@ class JobManagement(object):
cim_system_name='DCIM:ComputerSystem',
reboot=False,
start_time='TIME_NOW',
realtime=False):
realtime=False,
wait_for_idrac=True,
method_name='CreateTargetedConfigJob'):
"""Creates a config job
In CIM (Common Information Model), weak association is used to name an
@ -129,6 +131,10 @@ class JobManagement(object):
job id.
:param realtime: Indicates if reatime mode should be used.
Valid values are True and False. Default value is False.
:param wait_for_idrac: indicates whether or not to wait for the
iDRAC to be ready to accept commands before
issuing the command.
:param method_name: method of CIM object to invoke
:returns: id of the created job
:raises: WSManRequestFailure on request failures
:raises: WSManInvalidResponse when receiving invalid response
@ -153,10 +159,10 @@ class JobManagement(object):
if start_time is not None:
properties['ScheduledStartTime'] = start_time
doc = self.client.invoke(resource_uri, 'CreateTargetedConfigJob',
doc = self.client.invoke(resource_uri, method_name,
selectors, properties,
expected_return_value=utils.RET_CREATED)
expected_return_value=utils.RET_CREATED,
wait_for_idrac=wait_for_idrac)
return self._get_job_id(doc)
def create_reboot_job(


+ 105
- 42
dracclient/resources/lifecycle_controller.py View File

@ -11,9 +11,9 @@
# License for the specific language governing permissions and limitations
# under the License.
from dracclient import constants
from dracclient.resources import uris
from dracclient import utils
from dracclient import wsman
class LifecycleControllerManagement(object):
@ -42,47 +42,6 @@ class LifecycleControllerManagement(object):
return tuple(map(int, (lc_version_str.split('.'))))
class LCConfiguration(object):
def __init__(self, client):
"""Creates LifecycleControllerManagement object
:param client: an instance of WSManClient
"""
self.client = client
def list_lifecycle_settings(self):
"""List the LC configuration settings
:returns: a dictionary with the LC settings using InstanceID as the
key. The attributes are either LCEnumerableAttribute,
LCStringAttribute or LCIntegerAttribute 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_LCEnumeration, LCEnumerableAttribute),
(uris.DCIM_LCString, LCStringAttribute)]
for (namespace, attr_cls) in namespaces:
attribs = self._get_config(namespace, attr_cls)
result.update(attribs)
return result
def _get_config(self, resource, attr_cls):
result = {}
doc = self.client.enumerate(resource)
items = doc.find('.//{%s}Items' % wsman.NS_WSMAN)
for item in items:
attribute = attr_cls.parse(item)
result[attribute.instance_id] = attribute
return result
class LCAttribute(object):
"""Generic LC attribute class"""
@ -161,6 +120,17 @@ class LCEnumerableAttribute(LCAttribute):
lifecycle_attr.current_value, lifecycle_attr.pending_value,
lifecycle_attr.read_only, possible_values)
def validate(self, new_value):
"""Validates new value"""
if str(new_value) not in self.possible_values:
msg = ("Attribute '%(attr)s' cannot be set to value '%(val)s'."
" It must be in %(possible_values)r.") % {
'attr': self.name,
'val': new_value,
'possible_values': self.possible_values}
return msg
class LCStringAttribute(LCAttribute):
"""String LC attribute class"""
@ -199,3 +169,96 @@ class LCStringAttribute(LCAttribute):
return cls(lifecycle_attr.name, lifecycle_attr.instance_id,
lifecycle_attr.current_value, lifecycle_attr.pending_value,
lifecycle_attr.read_only, min_length, max_length)
class LCConfiguration(object):
NAMESPACES = [(uris.DCIM_LCEnumeration, LCEnumerableAttribute),
(uris.DCIM_LCString, LCStringAttribute)]
def __init__(self, client):
"""Creates LifecycleControllerManagement object
:param client: an instance of WSManClient
"""
self.client = client
def list_lifecycle_settings(self, by_name=False):
"""List the LC configuration settings
:param by_name: Controls whether returned dictionary uses Lifecycle
attribute name or instance_id as key.
:returns: a dictionary with the LC settings using InstanceID as the
key. The attributes are either LCEnumerableAttribute,
LCStringAttribute or LCIntegerAttribute objects.
:raises: WSManRequestFailure on request failures
:raises: WSManInvalidResponse when receiving invalid response
:raises: DRACOperationFailed on error reported back by the DRAC
interface
"""
return utils.list_settings(self.client, self.NAMESPACES, by_name)
def is_lifecycle_in_recovery(self):
"""Check if Lifecycle Controller in recovery mode or not
This method checks the LCStatus value to determine if lifecycle
controller is in recovery mode by invoking GetRemoteServicesAPIStatus
from iDRAC.
:returns: a boolean indicating if lifecycle controller is in recovery
:raises: WSManRequestFailure on request failures
:raises: WSManInvalidResponse when receiving invalid response
:raises: DRACOperationFailed on error reported back by the DRAC
interface
"""
selectors = {'SystemCreationClassName': 'DCIM_ComputerSystem',
'SystemName': 'DCIM:ComputerSystem',
'CreationClassName': 'DCIM_LCService',
'Name': 'DCIM:LCService'}
doc = self.client.invoke(uris.DCIM_LCService,
'GetRemoteServicesAPIStatus',
selectors,
{},
expected_return_value=utils.RET_SUCCESS,
wait_for_idrac=False)
lc_status = utils.find_xml(doc,
'LCStatus',
uris.DCIM_LCService).text
return lc_status == constants.LC_IN_RECOVERY
def set_lifecycle_settings(self, settings):
"""Sets the Lifecycle Controller configuration
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.
:param 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 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 error reported back by the DRAC
interface
"""
return utils.set_settings('Lifecycle',
self.client,
self.NAMESPACES,
settings,
uris.DCIM_LCService,
"DCIM_LCService",
"DCIM:LCService",
'',
wait_for_idrac=False)

+ 2
- 1
dracclient/tests/test_bios.py View File

@ -353,7 +353,8 @@ class ClientBIOSConfigurationTestCase(base.BaseTest):
result)
mock_invoke.assert_called_once_with(
mock.ANY, uris.DCIM_BIOSService, 'SetAttributes',
expected_selectors, expected_properties)
expected_selectors, expected_properties,
wait_for_idrac=True)
def test_set_bios_settings_error(self, mock_requests,
mock_wait_until_idrac_is_ready):


+ 4
- 2
dracclient/tests/test_idrac_card.py View File

@ -214,7 +214,8 @@ class ClientiDRACCardConfigurationTestCase(base.BaseTest):
result)
mock_invoke.assert_called_once_with(
mock.ANY, uris.DCIM_iDRACCardService, 'SetAttributes',
expected_selectors, expected_properties)
expected_selectors, expected_properties,
wait_for_idrac=True)
@mock.patch.object(dracclient.client.WSManClient, 'invoke',
spec_set=True, autospec=True)
@ -245,7 +246,8 @@ class ClientiDRACCardConfigurationTestCase(base.BaseTest):
result)
mock_invoke.assert_called_once_with(
mock.ANY, uris.DCIM_iDRACCardService, 'SetAttributes',
expected_selectors, expected_properties)
expected_selectors, expected_properties,
wait_for_idrac=True)
def test_set_idrac_settings_with_too_long_string(
self, mock_requests, mock_wait_until_idrac_is_ready):


+ 64
- 5
dracclient/tests/test_job.py View File

@ -226,12 +226,43 @@ class ClientJobManagementTestCase(base.BaseTest):
self.assertEqual(mock_requests.call_count, 2)
@mock.patch.object(dracclient.client.WSManClient, 'invoke',
spec_set=True, autospec=True)
def test_create_config_job_for_lifecycle(self, mock_invoke):
cim_creation_class_name = 'DCIM_LCService'
cim_name = 'DCIM:LCService'
target = ''
expected_selectors = {'CreationClassName': cim_creation_class_name,
'Name': cim_name,
'SystemCreationClassName': 'DCIM_ComputerSystem',
'SystemName': 'DCIM:ComputerSystem'}
expected_properties = {'Target': target,
'ScheduledStartTime': 'TIME_NOW'}
mock_invoke.return_value = lxml.etree.fromstring(
test_utils.JobInvocations[uris.DCIM_LCService][
'CreateConfigJob']['ok'])
job_id = self.drac_client.create_config_job(
uris.DCIM_LCService, cim_creation_class_name, cim_name, target,
start_time='TIME_NOW',
wait_for_idrac=False, method_name='CreateConfigJob')
mock_invoke.assert_called_once_with(
mock.ANY, uris.DCIM_LCService, 'CreateConfigJob',
expected_selectors, expected_properties,
expected_return_value=utils.RET_CREATED,
wait_for_idrac=False)
self.assertEqual('JID_442507917525', job_id)
@mock.patch.object(dracclient.client.WSManClient, 'invoke',
spec_set=True, autospec=True)
def test_create_config_job(self, mock_invoke):
cim_creation_class_name = 'DCIM_BIOSService'
cim_name = 'DCIM:BIOSService'
target = 'BIOS.Setup.1-1'
wait_for_idrac = True
expected_selectors = {'CreationClassName': cim_creation_class_name,
'Name': cim_name,
'SystemCreationClassName': 'DCIM_ComputerSystem',
@ -249,7 +280,8 @@ class ClientJobManagementTestCase(base.BaseTest):
mock_invoke.assert_called_once_with(
mock.ANY, uris.DCIM_BIOSService, 'CreateTargetedConfigJob',
expected_selectors, expected_properties,
expected_return_value=utils.RET_CREATED)
expected_return_value=utils.RET_CREATED,
wait_for_idrac=wait_for_idrac)
self.assertEqual('JID_442507917525', job_id)
@mock.patch.object(dracclient.client.WSManClient, 'invoke',
@ -259,6 +291,7 @@ class ClientJobManagementTestCase(base.BaseTest):
cim_name = 'DCIM:BIOSService'
target = 'BIOS.Setup.1-1'
start_time = "20140924120105"
wait_for_idrac = True
expected_selectors = {'CreationClassName': cim_creation_class_name,
'Name': cim_name,
'SystemCreationClassName': 'DCIM_ComputerSystem',
@ -276,7 +309,8 @@ class ClientJobManagementTestCase(base.BaseTest):
mock_invoke.assert_called_once_with(
mock.ANY, uris.DCIM_BIOSService, 'CreateTargetedConfigJob',
expected_selectors, expected_properties,
expected_return_value=utils.RET_CREATED)
expected_return_value=utils.RET_CREATED,
wait_for_idrac=wait_for_idrac)
self.assertEqual('JID_442507917525', job_id)
@mock.patch.object(dracclient.client.WSManClient, 'invoke',
@ -286,6 +320,7 @@ class ClientJobManagementTestCase(base.BaseTest):
cim_name = 'DCIM:BIOSService'
target = 'BIOS.Setup.1-1'
start_time = None
wait_for_idrac = True
expected_selectors = {'CreationClassName': cim_creation_class_name,
'Name': cim_name,
'SystemCreationClassName': 'DCIM_ComputerSystem',
@ -302,7 +337,8 @@ class ClientJobManagementTestCase(base.BaseTest):
mock_invoke.assert_called_once_with(
mock.ANY, uris.DCIM_BIOSService, 'CreateTargetedConfigJob',
expected_selectors, expected_properties,
expected_return_value=utils.RET_CREATED)
expected_return_value=utils.RET_CREATED,
wait_for_idrac=wait_for_idrac)
self.assertEqual('JID_442507917525', job_id)
@requests_mock.Mocker()
@ -323,12 +359,32 @@ class ClientJobManagementTestCase(base.BaseTest):
exceptions.DRACOperationFailed, self.drac_client.create_config_job,
uris.DCIM_BIOSService, cim_creation_class_name, cim_name, target)
@requests_mock.Mocker()
@mock.patch.object(dracclient.client.WSManClient,
'wait_until_idrac_is_ready', spec_set=True,
autospec=True)
def test_create_config_job_for_lifecycle_failed(
self, mock_requests,
mock_wait_until_idrac_is_ready):
cim_creation_class_name = 'DCIM_LCService'
cim_name = 'DCIM:LCService'
target = ''
mock_requests.post(
'https://1.2.3.4:443/wsman',
text=test_utils.JobInvocations[uris.DCIM_LCService][
'CreateConfigJob']['error'])
self.assertRaises(
exceptions.DRACOperationFailed, self.drac_client.create_config_job,
uris.DCIM_LCService, cim_creation_class_name, cim_name, target)
@mock.patch.object(dracclient.client.WSManClient, 'invoke', spec_set=True,
autospec=True)
def test_create_config_job_with_reboot(self, mock_invoke):
cim_creation_class_name = 'DCIM_BIOSService'
cim_name = 'DCIM:BIOSService'
target = 'BIOS.Setup.1-1'
wait_for_idrac = True
expected_selectors = {'CreationClassName': cim_creation_class_name,
'Name': cim_name,
'SystemCreationClassName': 'DCIM_ComputerSystem',
@ -347,7 +403,8 @@ class ClientJobManagementTestCase(base.BaseTest):
mock_invoke.assert_called_once_with(
mock.ANY, uris.DCIM_BIOSService, 'CreateTargetedConfigJob',
expected_selectors, expected_properties,
expected_return_value=utils.RET_CREATED)
expected_return_value=utils.RET_CREATED,
wait_for_idrac=wait_for_idrac)
self.assertEqual('JID_442507917525', job_id)
@mock.patch.object(dracclient.client.WSManClient, 'invoke', spec_set=True,
@ -356,6 +413,7 @@ class ClientJobManagementTestCase(base.BaseTest):
cim_creation_class_name = 'DCIM_BIOSService'
cim_name = 'DCIM:BIOSService'
target = 'BIOS.Setup.1-1'
wait_for_idrac = True
expected_selectors = {'CreationClassName': cim_creation_class_name,
'Name': cim_name,
'SystemCreationClassName': 'DCIM_ComputerSystem',
@ -374,7 +432,8 @@ class ClientJobManagementTestCase(base.BaseTest):
mock_invoke.assert_called_once_with(
mock.ANY, uris.DCIM_BIOSService, 'CreateTargetedConfigJob',
expected_selectors, expected_properties,
expected_return_value=utils.RET_CREATED)
expected_return_value=utils.RET_CREATED,
wait_for_idrac=wait_for_idrac)
self.assertEqual('JID_442507917525', job_id)
@mock.patch.object(dracclient.client.WSManClient, 'invoke', spec_set=True,


+ 212
- 4
dracclient/tests/test_lifecycle_controller.py View File

@ -11,14 +11,20 @@
# License for the specific language governing permissions and limitations
# under the License.
import lxml.etree
import mock
import re
import requests_mock
import dracclient.client
from dracclient import constants
from dracclient import exceptions
import dracclient.resources.job
from dracclient.resources import lifecycle_controller
from dracclient.resources import uris
from dracclient.tests import base
from dracclient.tests import utils as test_utils
from dracclient import utils
class ClientLifecycleControllerManagementTestCase(base.BaseTest):
@ -40,6 +46,7 @@ class ClientLifecycleControllerManagementTestCase(base.BaseTest):
self.assertEqual((2, 1, 0), version)
@requests_mock.Mocker()
class ClientLCConfigurationTestCase(base.BaseTest):
def setUp(self):
@ -47,12 +54,12 @@ class ClientLCConfigurationTestCase(base.BaseTest):
self.drac_client = dracclient.client.DRACClient(
**test_utils.FAKE_ENDPOINT)
@requests_mock.Mocker()
@mock.patch.object(dracclient.client.WSManClient,
'wait_until_idrac_is_ready', spec_set=True,
autospec=True)
def test_list_lifecycle_settings(self, mock_requests,
mock_wait_until_idrac_is_ready):
def test_list_lifecycle_settings_by_instance_id(
self, mock_requests,
mock_wait_until_idrac_is_ready):
expected_enum_attr = lifecycle_controller.LCEnumerableAttribute(
name='Lifecycle Controller State',
instance_id='LifecycleController.Embedded.1#LCAttributes.1#LifecycleControllerState', # noqa
@ -74,7 +81,8 @@ class ClientLCConfigurationTestCase(base.BaseTest):
{'text': test_utils.LifecycleControllerEnumerations[
uris.DCIM_LCString]['ok']}])
lifecycle_settings = self.drac_client.list_lifecycle_settings()
lifecycle_settings = self.drac_client.list_lifecycle_settings(
by_name=False)
self.assertEqual(14, len(lifecycle_settings))
# enumerable attribute
@ -89,3 +97,203 @@ class ClientLCConfigurationTestCase(base.BaseTest):
lifecycle_settings)
self.assertEqual(expected_string_attr,
lifecycle_settings['LifecycleController.Embedded.1#LCAttributes.1#SystemID']) # noqa
@mock.patch.object(dracclient.client.WSManClient,
'wait_until_idrac_is_ready', spec_set=True,
autospec=True)
def test_list_lifecycle_settings_by_name(
self, mock_requests,
mock_wait_until_idrac_is_ready):
expected_enum_attr = lifecycle_controller.LCEnumerableAttribute(
name='Lifecycle Controller State',
instance_id='LifecycleController.Embedded.1#LCAttributes.1#LifecycleControllerState', # noqa
read_only=False,
current_value='Enabled',
pending_value=None,
possible_values=['Disabled', 'Enabled', 'Recovery'])
expected_string_attr = lifecycle_controller.LCStringAttribute(
name='SYSID',
instance_id='LifecycleController.Embedded.1#LCAttributes.1#SystemID', # noqa
read_only=True,
current_value='639',
pending_value=None,
min_length=0,
max_length=3)
mock_requests.post('https://1.2.3.4:443/wsman', [
{'text': test_utils.LifecycleControllerEnumerations[
uris.DCIM_LCEnumeration]['ok']},
{'text': test_utils.LifecycleControllerEnumerations[
uris.DCIM_LCString]['ok']}])
lifecycle_settings = self.drac_client.list_lifecycle_settings(
by_name=True)
self.assertEqual(14, len(lifecycle_settings))
# enumerable attribute
self.assertIn(
'Lifecycle Controller State',
lifecycle_settings)
self.assertEqual(expected_enum_attr, lifecycle_settings[
'Lifecycle Controller State'])
# string attribute
self.assertIn(
'SYSID',
lifecycle_settings)
self.assertEqual(expected_string_attr,
lifecycle_settings['SYSID'])
@mock.patch.object(dracclient.client.WSManClient, 'invoke',
spec_set=True, autospec=True)
def test_is_lifecycle_in_recovery(self, mock_requests,
mock_invoke):
expected_selectors = {'CreationClassName': 'DCIM_LCService',
'SystemName': 'DCIM:ComputerSystem',
'Name': 'DCIM:LCService',
'SystemCreationClassName': 'DCIM_ComputerSystem'}
mock_invoke.return_value = lxml.etree.fromstring(
test_utils.LifecycleControllerInvocations[uris.DCIM_LCService][
'GetRemoteServicesAPIStatus']['is_recovery'])
result = self.drac_client.is_lifecycle_in_recovery()
mock_invoke.assert_called_once_with(
mock.ANY, uris.DCIM_LCService, 'GetRemoteServicesAPIStatus',
expected_selectors, {},
expected_return_value=utils.RET_SUCCESS,
wait_for_idrac=False)
self.assertEqual(True, result)
@mock.patch.object(dracclient.client.WSManClient,
'invoke', spec_set=True,
autospec=True)
def test_set_lifecycle_settings(self, mock_requests,
mock_invoke):
mock_requests.post('https://1.2.3.4:443/wsman', [
{'text': test_utils.LifecycleControllerEnumerations[
uris.DCIM_LCEnumeration]['ok']},
{'text': test_utils.LifecycleControllerEnumerations[
uris.DCIM_LCString]['ok']}])
mock_invoke.return_value = lxml.etree.fromstring(
test_utils.LifecycleControllerInvocations[uris.DCIM_LCService][
'SetAttributes']['ok'])
result = self.drac_client.set_lifecycle_settings(
{'Collect System Inventory on Restart': 'Disabled'})
self.assertEqual({'is_commit_required': True,
'is_reboot_required': constants.RebootRequired.false
},
result)
@mock.patch.object(dracclient.client.WSManClient,
'wait_until_idrac_is_ready', spec_set=True,
autospec=True)
def test_set_lifecycle_settings_with_unknown_attr(
self, mock_requests, mock_wait_until_idrac_is_ready):
mock_requests.post('https://1.2.3.4:443/wsman', [
{'text': test_utils.LifecycleControllerEnumerations[
uris.DCIM_LCEnumeration]['ok']},
{'text': test_utils.LifecycleControllerEnumerations[
uris.DCIM_LCString]['ok']},
{'text': test_utils.LifecycleControllerInvocations[
uris.DCIM_LCService]['SetAttributes']['error']}])
self.assertRaises(exceptions.InvalidParameterValue,
self.drac_client.set_lifecycle_settings,
{'foo': 'bar'})
@mock.patch.object(dracclient.client.WSManClient,
'wait_until_idrac_is_ready', spec_set=True,
autospec=True)
def test_set_lifecycle_settings_with_unchanged_attr(
self, mock_requests, mock_wait_until_idrac_is_ready):
mock_requests.post('https://1.2.3.4:443/wsman', [
{'text': test_utils.LifecycleControllerEnumerations[
uris.DCIM_LCEnumeration]['ok']},
{'text': test_utils.LifecycleControllerEnumerations[
uris.DCIM_LCString]['ok']}])
result = self.drac_client.set_lifecycle_settings(
{'Lifecycle Controller State': 'Enabled'})
self.assertEqual({'is_commit_required': False,
'is_reboot_required':
constants.RebootRequired.false},
result)
@mock.patch.object(dracclient.client.WSManClient,
'wait_until_idrac_is_ready', spec_set=True,
autospec=True)
def test_set_lifecycle_settings_with_readonly_attr(
self, mock_requests, mock_wait_until_idrac_is_ready):
expected_message = ("Cannot set read-only Lifecycle attributes: "
"['Licensed'].")
mock_requests.post('https://1.2.3.4:443/wsman', [
{'text': test_utils.LifecycleControllerEnumerations[
uris.DCIM_LCEnumeration]['ok']},
{'text': test_utils.LifecycleControllerEnumerations[
uris.DCIM_LCString]['ok']}])
self.assertRaisesRegexp(
exceptions.DRACOperationFailed, re.escape(expected_message),
self.drac_client.set_lifecycle_settings, {'Licensed': 'yes'})
@mock.patch.object(dracclient.client.WSManClient,
'wait_until_idrac_is_ready', spec_set=True,
autospec=True)
def test_set_lifecycle_settings_with_incorrect_enum_value(
self, mock_requests, mock_wait_until_idrac_is_ready):
expected_message = ("Attribute 'Lifecycle Controller State' cannot "
"be set to value 'foo'. It must be in "
"['Disabled', 'Enabled', 'Recovery'].")
mock_requests.post('https://1.2.3.4:443/wsman', [
{'text': test_utils.LifecycleControllerEnumerations[
uris.DCIM_LCEnumeration]['ok']},
{'text': test_utils.LifecycleControllerEnumerations[
uris.DCIM_LCString]['ok']}])
self.assertRaisesRegexp(
exceptions.DRACOperationFailed, re.escape(expected_message),
self.drac_client.set_lifecycle_settings,
{'Lifecycle Controller State': 'foo'})
class ClientLCChangesTestCase(base.BaseTest):
def setUp(self):
super(ClientLCChangesTestCase, self).setUp()
self.drac_client = dracclient.client.DRACClient(
**test_utils.FAKE_ENDPOINT)
@mock.patch.object(dracclient.resources.job.JobManagement,
'create_config_job', spec_set=True, autospec=True)
def test_commit_pending_lifecycle_changes(self, mock_create_config_job):
self.drac_client.commit_pending_lifecycle_changes()
mock_create_config_job.assert_called_once_with(
mock.ANY, resource_uri=uris.DCIM_LCService,
cim_creation_class_name='DCIM_LCService',
cim_name='DCIM:LCService', target='',
reboot=False, start_time='TIME_NOW',
wait_for_idrac=False,
method_name='CreateConfigJob')
@mock.patch.object(dracclient.resources.job.JobManagement,
'create_config_job', spec_set=True, autospec=True)
def test_commit_pending_lifecycle_changes_with_time(
self, mock_create_config_job):
timestamp = '20140924140201'
self.drac_client.commit_pending_lifecycle_changes(
start_time=timestamp)
mock_create_config_job.assert_called_once_with(
mock.ANY, resource_uri=uris.DCIM_LCService,
cim_creation_class_name='DCIM_LCService',
cim_name='DCIM:LCService', target='',
reboot=False, start_time=timestamp,
wait_for_idrac=False,
method_name='CreateConfigJob')

+ 6
- 3
dracclient/tests/test_nic.py View File

@ -214,7 +214,8 @@ class ClientNICTestCase(base.BaseTest):
mock_invoke.assert_called_once_with(
mock.ANY, uris.DCIM_NICService, 'SetAttributes',
expected_selectors, expected_properties)
expected_selectors, expected_properties,
wait_for_idrac=True)
@mock.patch.object(dracclient.client.WSManClient, 'invoke', spec_set=True,
autospec=True)
@ -250,7 +251,8 @@ class ClientNICTestCase(base.BaseTest):
mock_invoke.assert_called_once_with(
mock.ANY, uris.DCIM_NICService, 'SetAttributes',
expected_selectors, expected_properties)
expected_selectors, expected_properties,
wait_for_idrac=True)
@mock.patch.object(dracclient.client.WSManClient, 'invoke', spec_set=True,
autospec=True)
@ -286,7 +288,8 @@ class ClientNICTestCase(base.BaseTest):
mock_invoke.assert_called_once_with(
mock.ANY, uris.DCIM_NICService, 'SetAttributes',
expected_selectors, expected_properties)
expected_selectors, expected_properties,
wait_for_idrac=True)
def test_set_nic_settings_error(self, mock_requests,
mock_wait_until_idrac_is_ready):


+ 17
- 1
dracclient/tests/utils.py View File

@ -133,6 +133,14 @@ JobInvocations = {
'error': load_wsman_xml(
'bios_service-invoke-delete_pending_configuration-error'),
},
},
uris.DCIM_LCService: {
'CreateConfigJob': {
'ok': load_wsman_xml(
'lc_service-invoke-create_config_job-ok'),
'error': load_wsman_xml(
'lc_service-invoke-create_config_job-error'),
},
}
}
@ -192,7 +200,15 @@ LifecycleControllerInvocations = {
'GetRemoteServicesAPIStatus': {
'is_ready': load_wsman_xml('lc_getremoteservicesapistatus_ready'),
'is_not_ready': load_wsman_xml(
'lc_getremoteservicesapistatus_not_ready')
'lc_getremoteservicesapistatus_not_ready'),
'is_recovery': load_wsman_xml(
'lc_getremoteservicesapistatus_recovery'),
},
'SetAttributes': {
'ok': load_wsman_xml(
'lc_service-invoke-set_attributes-ok'),
'error': load_wsman_xml(
'lc_service-invoke-set_attributes-error'),
}
}
}


+ 19
- 0
dracclient/tests/wsman_mocks/lc_getremoteservicesapistatus_recovery.xml View File

@ -0,0 +1,19 @@
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:n1="http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LCService">
<s:Header>
<wsa:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To>
<wsa:Action>http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LCService/GetRemoteServicesAPIStatusResponse</wsa:Action>
<wsa:RelatesTo>uuid:18745811-2782-4d30-a288-8f001a895215</wsa:RelatesTo>
<wsa:MessageID>uuid:9ec203ba-4fc0-1fc0-8094-98d61742a844</wsa:MessageID>
</s:Header>
<s:Body>
<n1:GetRemoteServicesAPIStatus_OUTPUT>
<n1:LCStatus>4</n1:LCStatus>
<n1:Message>Lifecycle Controller Remote Services is not ready.</n1:Message>
<n1:MessageID>LC060</n1:MessageID>
<n1:RTStatus>0</n1:RTStatus>
<n1:ReturnValue>0</n1:ReturnValue>
<n1:ServerStatus>7</n1:ServerStatus>
<n1:Status>1</n1:Status>
</n1:GetRemoteServicesAPIStatus_OUTPUT>
</s:Body>
</s:Envelope>

+ 17
- 0
dracclient/tests/wsman_mocks/lc_service-invoke-create_config_job-error.xml View File

@ -0,0 +1,17 @@
<s:Envelope xmlns:n1="http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LCService"
xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">
<s:Header>
<wsa:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To>
<wsa:Action>http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LCService/CreateConfigJobResponse</wsa:Action>
<wsa:RelatesTo>uuid:80cf5e1b-b109-4ef5-87c8-5b03ce6ba117</wsa:RelatesTo>
<wsa:MessageID>uuid:e57fa514-2189-1189-8ec1-a36fc6fe83b0</wsa:MessageID>
</s:Header>
<s:Body>
<n1:CreateConfigJob_OUTPUT>
<n1:Message>Configuration job already created, cannot create another config job on specified target until existing job is completed or is cancelled</n1:Message>
<n1:MessageID>LC007</n1:MessageID>
<n1:ReturnValue>2</n1:ReturnValue>
</n1:CreateConfigJob_OUTPUT>
</s:Body>
</s:Envelope>

+ 28
- 0
dracclient/tests/wsman_mocks/lc_service-invoke-create_config_job-ok.xml View File

@ -0,0 +1,28 @@
<s:Envelope xmlns:n1="http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LCService"
xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:wsman="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">
<s:Header>
<wsa:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:To>
<wsa:Action>http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LCService/CreateConfigJobResponse</wsa:Action>
<wsa:RelatesTo>uuid:fc2fdae5-6ac2-4338-9b2e-e69b813af829</wsa:RelatesTo>
<wsa:MessageID>uuid:d7d89957-2189-1189-8ec0-a36fc6fe83b0</wsa:MessageID>
</s:Header>
<s:Body>
<n1:CreateConfigJob_OUTPUT>
<n1:Job>
<wsa:EndpointReference>
<wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address>
<wsa:ReferenceParameters>
<wsman:ResourceURI>http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LifecycleJob</wsman:ResourceURI>
<wsman:SelectorSet>
<wsman:Selector Name="InstanceID">JID_442507917525</wsman:Selector>
<wsman:Selector Name="__cimnamespace">root/dcim</wsman:Selector>
</wsman:SelectorSet>
</wsa:ReferenceParameters>
</wsa:EndpointReference>
</n1:Job>
<n1:ReturnValue>4096</n1:ReturnValue>
</n1:CreateConfigJob_OUTPUT>
</s:Body>
</s:Envelope>

+ 21
- 0
dracclient/tests/wsman_mocks/lc_service-invoke-set_attributes-error.xml View File

@ -0,0 +1,21 @@
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:n1="http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LCService">
<s:Header>
<wsa:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
</wsa:To>
<wsa:Action>http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LCService/SetAttributesResponse
</wsa:Action>
<wsa:RelatesTo>uuid:bf8adefe-6fc0-456d-b97c-fd8d4aca2d6c
</wsa:RelatesTo>
<wsa:MessageID>uuid:84abf7b9-7176-1176-a11c-a53ffbd9bed4
</wsa:MessageID>
</s:Header>
<s:Body>
<n1:SetAttributes_OUTPUT>
<n1:Message>Invalid AttributeName.</n1:Message>
<n1:MessageID>LC057</n1:MessageID>
<n1:ReturnValue>2</n1:ReturnValue>
</n1:SetAttributes_OUTPUT>
</s:Body>
</s:Envelope>

+ 24
- 0
dracclient/tests/wsman_mocks/lc_service-invoke-set_attributes-ok.xml View File

@ -0,0 +1,24 @@
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:n1="http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LCService">
<s:Header>
<wsa:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
</wsa:To>
<wsa:Action>http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LCService/SetAttributesResponse
</wsa:Action>
<wsa:RelatesTo>uuid:bf8adefe-6fc0-456d-b97c-fd8d4aca2d6c
</wsa:RelatesTo>
<wsa:MessageID>uuid:84abf7b9-7176-1176-a11c-a53ffbd9bed4
</wsa:MessageID>
</s:Header>
<s:Body>
<n1:SetAttributes_OUTPUT>
<n1:MessageID>LC001</n1:MessageID>
<n1:Message>The command was successful</n1:Message>
<n1:ReturnValue>0</n1:ReturnValue>
<n1:RebootRequired>No</n1:RebootRequired>
<n1:SetResult>Set PendingValue</n1:SetResult>
</n1:SetAttributes_OUTPUT>
</s:Body>
</s:Envelope>

+ 22
- 9
dracclient/utils.py View File

@ -233,7 +233,7 @@ def validate_integer_value(value, attr_name, error_msgs):
def list_settings(client, namespaces, by_name=True, fqdd_filter=None,
name_formatter=None):
name_formatter=None, wait_for_idrac=True):
"""List the configuration settings
:param client: an instance of WSManClient.
@ -245,6 +245,9 @@ def list_settings(client, namespaces, by_name=True, fqdd_filter=None,
: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.
:returns: a dictionary with the settings using name or instance_id as
the key.
:raises: WSManRequestFailure on request failures
@ -256,7 +259,7 @@ def list_settings(client, namespaces, by_name=True, fqdd_filter=None,
result = {}
for (namespace, attr_cls) in namespaces:
attribs = _get_config(client, namespace, attr_cls, by_name,
fqdd_filter, name_formatter)
fqdd_filter, name_formatter, wait_for_idrac)
if not set(result).isdisjoint(set(attribs)):
raise exceptions.DRACOperationFailed(
drac_messages=('Colliding attributes %r' % (
@ -266,10 +269,10 @@ def list_settings(client, namespaces, by_name=True, fqdd_filter=None,
def _get_config(client, resource, attr_cls, by_name, fqdd_filter,
name_formatter):
name_formatter, wait_for_idrac):
result = {}
doc = client.enumerate(resource)
doc = client.enumerate(resource, wait_for_idrac=wait_for_idrac)
items = doc.find('.//{%s}Items' % wsman.NS_WSMAN)
for item in items:
@ -297,7 +300,8 @@ def set_settings(settings_type,
cim_creation_class_name,
cim_name,
target,
name_formatter=None):
name_formatter=None,
wait_for_idrac=True):
"""Generically handles setting various types of settings on the iDRAC
This method pulls the current list of settings from the iDRAC then compares
@ -318,6 +322,9 @@ def set_settings(settings_type,
: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
: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
@ -335,12 +342,15 @@ def set_settings(settings_type,
"""
current_settings = list_settings(client, namespaces, by_name=True,
name_formatter=name_formatter)
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})
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 = []
@ -393,11 +403,14 @@ def set_settings(settings_type,
'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)
selectors, properties,
wait_for_idrac=wait_for_idrac)
return build_return_dict(doc, resource_uri)

Loading…
Cancel
Save