diff --git a/dracclient/client.py b/dracclient/client.py
index ff15ba1..ec802bc 100644
--- a/dracclient/client.py
+++ b/dracclient/client.py
@@ -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
diff --git a/dracclient/constants.py b/dracclient/constants.py
index 9356060..ecaffa1 100644
--- a/dracclient/constants.py
+++ b/dracclient/constants.py
@@ -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
diff --git a/dracclient/resources/job.py b/dracclient/resources/job.py
index 32618db..5ec3443 100644
--- a/dracclient/resources/job.py
+++ b/dracclient/resources/job.py
@@ -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(
diff --git a/dracclient/resources/lifecycle_controller.py b/dracclient/resources/lifecycle_controller.py
index 9d903ef..c42bfd1 100644
--- a/dracclient/resources/lifecycle_controller.py
+++ b/dracclient/resources/lifecycle_controller.py
@@ -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)
diff --git a/dracclient/tests/test_bios.py b/dracclient/tests/test_bios.py
index 22c0ff1..139ad03 100644
--- a/dracclient/tests/test_bios.py
+++ b/dracclient/tests/test_bios.py
@@ -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):
diff --git a/dracclient/tests/test_idrac_card.py b/dracclient/tests/test_idrac_card.py
index 21e46d7..6228554 100644
--- a/dracclient/tests/test_idrac_card.py
+++ b/dracclient/tests/test_idrac_card.py
@@ -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):
diff --git a/dracclient/tests/test_job.py b/dracclient/tests/test_job.py
index 051b847..adb1a34 100644
--- a/dracclient/tests/test_job.py
+++ b/dracclient/tests/test_job.py
@@ -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,
diff --git a/dracclient/tests/test_lifecycle_controller.py b/dracclient/tests/test_lifecycle_controller.py
index 3427cc5..58352bc 100644
--- a/dracclient/tests/test_lifecycle_controller.py
+++ b/dracclient/tests/test_lifecycle_controller.py
@@ -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')
diff --git a/dracclient/tests/test_nic.py b/dracclient/tests/test_nic.py
index e393d5c..7029df3 100644
--- a/dracclient/tests/test_nic.py
+++ b/dracclient/tests/test_nic.py
@@ -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):
diff --git a/dracclient/tests/utils.py b/dracclient/tests/utils.py
index 49acc2f..0ac622b 100644
--- a/dracclient/tests/utils.py
+++ b/dracclient/tests/utils.py
@@ -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'),
}
}
}
diff --git a/dracclient/tests/wsman_mocks/lc_getremoteservicesapistatus_recovery.xml b/dracclient/tests/wsman_mocks/lc_getremoteservicesapistatus_recovery.xml
new file mode 100644
index 0000000..97b3a3a
--- /dev/null
+++ b/dracclient/tests/wsman_mocks/lc_getremoteservicesapistatus_recovery.xml
@@ -0,0 +1,19 @@
+
+
+ http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
+ http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LCService/GetRemoteServicesAPIStatusResponse
+ uuid:18745811-2782-4d30-a288-8f001a895215
+ uuid:9ec203ba-4fc0-1fc0-8094-98d61742a844
+
+
+
+ 4
+ Lifecycle Controller Remote Services is not ready.
+ LC060
+ 0
+ 0
+ 7
+ 1
+
+
+
diff --git a/dracclient/tests/wsman_mocks/lc_service-invoke-create_config_job-error.xml b/dracclient/tests/wsman_mocks/lc_service-invoke-create_config_job-error.xml
new file mode 100644
index 0000000..c375bb7
--- /dev/null
+++ b/dracclient/tests/wsman_mocks/lc_service-invoke-create_config_job-error.xml
@@ -0,0 +1,17 @@
+
+
+ http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
+ http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LCService/CreateConfigJobResponse
+ uuid:80cf5e1b-b109-4ef5-87c8-5b03ce6ba117
+ uuid:e57fa514-2189-1189-8ec1-a36fc6fe83b0
+
+
+
+ Configuration job already created, cannot create another config job on specified target until existing job is completed or is cancelled
+ LC007
+ 2
+
+
+
diff --git a/dracclient/tests/wsman_mocks/lc_service-invoke-create_config_job-ok.xml b/dracclient/tests/wsman_mocks/lc_service-invoke-create_config_job-ok.xml
new file mode 100644
index 0000000..b7ec83c
--- /dev/null
+++ b/dracclient/tests/wsman_mocks/lc_service-invoke-create_config_job-ok.xml
@@ -0,0 +1,28 @@
+
+
+ http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
+ http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LCService/CreateConfigJobResponse
+ uuid:fc2fdae5-6ac2-4338-9b2e-e69b813af829
+ uuid:d7d89957-2189-1189-8ec0-a36fc6fe83b0
+
+
+
+
+
+ http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
+
+ http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LifecycleJob
+
+ JID_442507917525
+ root/dcim
+
+
+
+
+ 4096
+
+
+
diff --git a/dracclient/tests/wsman_mocks/lc_service-invoke-set_attributes-error.xml b/dracclient/tests/wsman_mocks/lc_service-invoke-set_attributes-error.xml
new file mode 100644
index 0000000..c2c0b75
--- /dev/null
+++ b/dracclient/tests/wsman_mocks/lc_service-invoke-set_attributes-error.xml
@@ -0,0 +1,21 @@
+
+
+ http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
+
+ http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LCService/SetAttributesResponse
+
+ uuid:bf8adefe-6fc0-456d-b97c-fd8d4aca2d6c
+
+ uuid:84abf7b9-7176-1176-a11c-a53ffbd9bed4
+
+
+
+
+ Invalid AttributeName.
+ LC057
+ 2
+
+
+
diff --git a/dracclient/tests/wsman_mocks/lc_service-invoke-set_attributes-ok.xml b/dracclient/tests/wsman_mocks/lc_service-invoke-set_attributes-ok.xml
new file mode 100644
index 0000000..7c4ff98
--- /dev/null
+++ b/dracclient/tests/wsman_mocks/lc_service-invoke-set_attributes-ok.xml
@@ -0,0 +1,24 @@
+
+
+ http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
+
+ http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_LCService/SetAttributesResponse
+
+ uuid:bf8adefe-6fc0-456d-b97c-fd8d4aca2d6c
+
+ uuid:84abf7b9-7176-1176-a11c-a53ffbd9bed4
+
+
+
+
+ LC001
+ The command was successful
+ 0
+ No
+ Set PendingValue
+
+
+
+
diff --git a/dracclient/utils.py b/dracclient/utils.py
index 86b6828..1814cda 100644
--- a/dracclient/utils.py
+++ b/dracclient/utils.py
@@ -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)