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
(cherry picked from commit ceef78a938)
tags/1.7.0
mpardhi23 1 year ago
committed by Christopher Dearborn
parent
commit
48f6133383
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

@@ -388,9 +388,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.
@@ -399,7 +402,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
@@ -469,7 +514,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
@@ -495,6 +542,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
@@ -512,7 +563,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,
@@ -651,6 +704,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

@@ -118,7 +118,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
@@ -145,6 +147,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
@@ -169,10 +175,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

@@ -354,7 +354,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

@@ -251,7 +251,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.
@@ -263,6 +263,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
@@ -274,7 +277,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' % (
@@ -284,10 +287,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:
@@ -316,7 +319,8 @@ def set_settings(settings_type,
cim_name,
target,
name_formatter=None,
include_commit_required=False):
include_commit_required=False,
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
@@ -339,6 +343,9 @@ def set_settings(settings_type,
attribute.name will be used.
:parm include_commit_required: Indicates if the deprecated commit_required
should be returned in the result.
: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 commit_required key with a boolean value indicating
whether a config job must be created for the values to be
@@ -361,12 +368,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 = []
@@ -421,12 +431,15 @@ 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,
include_commit_required=include_commit_required)

Loading…
Cancel
Save