diff --git a/ironic/drivers/modules/drac/job.py b/ironic/drivers/modules/drac/job.py new file mode 100644 index 0000000000..e7cf94c30c --- /dev/null +++ b/ironic/drivers/modules/drac/job.py @@ -0,0 +1,53 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +DRAC Lifecycle job specific methods +""" + +from oslo_log import log as logging +from oslo_utils import importutils + +from ironic.common import exception +from ironic.common.i18n import _ +from ironic.common.i18n import _LE +from ironic.drivers.modules.drac import common as drac_common + +drac_exceptions = importutils.try_import('dracclient.exceptions') + +LOG = logging.getLogger(__name__) + + +def validate_job_queue(node): + """Validates the job queue on the node. + + It raises an exception if an unfinished configuration job exists. + + :param node: an ironic node object. + :raises: DracOperationError on an error from python-dracclient. + """ + client = drac_common.get_drac_client(node) + + try: + unfinished_jobs = client.list_jobs(only_unfinished=True) + except drac_exceptions.BaseClientException as exc: + LOG.error(_LE('DRAC driver failed to get the list of unfinished jobs ' + 'for node %(node_uuid)s. Reason: %(error)s.'), + {'node_uuid': node.uuid, + 'error': exc}) + raise exception.DracOperationError(error=exc) + + if unfinished_jobs: + msg = _('Unfinished config jobs found: %(jobs)r. Make sure they are ' + 'completed before retrying.') % {'jobs': unfinished_jobs} + raise exception.DracOperationError(error=msg) diff --git a/ironic/drivers/modules/drac/management.py b/ironic/drivers/modules/drac/management.py index 018f5066ef..4ecd4c454f 100644 --- a/ironic/drivers/modules/drac/management.py +++ b/ironic/drivers/modules/drac/management.py @@ -16,7 +16,7 @@ # under the License. """ -DRAC Management Driver +DRAC management interface """ from oslo_log import log as logging @@ -25,14 +25,17 @@ from oslo_utils import importutils from ironic.common import boot_devices from ironic.common import exception +from ironic.common.i18n import _ from ironic.common.i18n import _LE from ironic.conductor import task_manager from ironic.drivers import base from ironic.drivers.modules.drac import client as drac_client from ironic.drivers.modules.drac import common as drac_common +from ironic.drivers.modules.drac import job as drac_job from ironic.drivers.modules.drac import resource_uris -pywsman = importutils.try_import('pywsman') + +drac_exceptions = importutils.try_import('dracclient.exceptions') LOG = logging.getLogger(__name__) @@ -45,186 +48,79 @@ _BOOT_DEVICES_MAP = { TARGET_DEVICE = 'BIOS.Setup.1-1' # RebootJobType constants - _GRACEFUL_REBOOT_WITH_FORCED_SHUTDOWN = '3' -# IsNext constants - -PERSISTENT = '1' -""" Is the next boot config the system will use. """ - -ONE_TIME_BOOT = '3' -""" Is the next boot config the system will use, one time boot only. """ +# BootMode constants +PERSISTENT_BOOT_MODE = 'IPL' +NON_PERSISTENT_BOOT_MODE = 'OneTime' -def _get_boot_device(node, controller_version=None): - if controller_version is None: - controller_version = _get_lifecycle_controller_version(node) +def _get_boot_device(node, drac_boot_devices=None): + client = drac_common.get_drac_client(node) - boot_list = _get_next_boot_list(node) - persistent = boot_list['is_next'] == PERSISTENT - boot_list_id = boot_list['instance_id'] + try: + boot_modes = client.list_boot_modes() + next_boot_modes = [mode.id for mode in boot_modes if mode.is_next] + if NON_PERSISTENT_BOOT_MODE in next_boot_modes: + next_boot_mode = NON_PERSISTENT_BOOT_MODE + else: + next_boot_mode = next_boot_modes[0] - boot_device_id = _get_boot_device_for_boot_list(node, boot_list_id, - controller_version) - boot_device = next((key for (key, value) in _BOOT_DEVICES_MAP.items() - if value in boot_device_id), None) - return {'boot_device': boot_device, 'persistent': persistent} + if drac_boot_devices is None: + drac_boot_devices = client.list_boot_devices() + drac_boot_device = drac_boot_devices[next_boot_mode][0] + + boot_device = next(key for (key, value) in _BOOT_DEVICES_MAP.items() + if value in drac_boot_device.id) + return {'boot_device': boot_device, + 'persistent': next_boot_mode == PERSISTENT_BOOT_MODE} + except (drac_exceptions.BaseClientException, IndexError) as exc: + LOG.error(_LE('DRAC driver failed to get next boot mode for ' + 'node %(node_uuid)s. Reason: %(error)s.'), + {'node_uuid': node.uuid, 'error': exc}) + raise exception.DracOperationError(error=exc) -def _get_next_boot_list(node): - """Get the next boot list. +def set_boot_device(node, device, persistent=False): + """Set the boot device for a node. - The DCIM_BootConfigSetting resource represents each boot list (eg. - IPL/BIOS, BCV, UEFI, vFlash Partition, One Time Boot). - The DCIM_BootSourceSetting resource represents each of the boot list boot - devices or sources that are shown under their corresponding boot list. + Set the boot device to use on next boot of the node. :param node: an ironic node object. - :raises: DracClientError on an error from pywsman library. - :returns: a dictionary containing: - - :instance_id: the instance id of the boot list. - :is_next: whether it's the next device to boot or not. One of - PERSISTENT, ONE_TIME_BOOT constants. + :param device: the boot device, one of + :mod:`ironic.common.boot_devices`. + :param persistent: Boolean value. True if the boot device will + persist to all future boots, False if not. + Default: False. + :raises: DracOperationError on an error from python-dracclient. """ - client = drac_client.get_wsman_client(node) - filter_query = ('select * from DCIM_BootConfigSetting where IsNext=%s ' - 'or IsNext=%s' % (PERSISTENT, ONE_TIME_BOOT)) - try: - doc = client.wsman_enumerate(resource_uris.DCIM_BootConfigSetting, - filter_query=filter_query) - except exception.DracClientError as exc: - with excutils.save_and_reraise_exception(): - LOG.error(_LE('DRAC driver failed to get next boot mode for ' - 'node %(node_uuid)s. Reason: %(error)s.'), - {'node_uuid': node.uuid, 'error': exc}) - items = drac_common.find_xml(doc, 'DCIM_BootConfigSetting', - resource_uris.DCIM_BootConfigSetting, - find_all=True) + drac_job.validate_job_queue(node) - # This list will have 2 items maximum, one for the persistent element - # and another one for the OneTime if set - boot_list = None - for i in items: - boot_list_id = drac_common.find_xml( - i, 'InstanceID', resource_uris.DCIM_BootConfigSetting).text - is_next = drac_common.find_xml( - i, 'IsNext', resource_uris.DCIM_BootConfigSetting).text + client = drac_common.get_drac_client(node) + drac_boot_devices = client.list_boot_devices() - boot_list = {'instance_id': boot_list_id, 'is_next': is_next} - # If OneTime is set we should return it, because that's - # where the next boot device is - if is_next == ONE_TIME_BOOT: - break + current_boot_device = _get_boot_device(node, drac_boot_devices) + # If we are already booting from the right device, do nothing. + if current_boot_device == {'boot_device': device, + 'persistent': persistent}: + LOG.debug('DRAC already set to boot from %s', device) + return - return boot_list + drac_boot_device = next(drac_device.id for drac_device + in drac_boot_devices[PERSISTENT_BOOT_MODE] + if _BOOT_DEVICES_MAP[device] in drac_device.id) - -def _get_boot_device_for_boot_list(node, boot_list_id, controller_version): - """Get the next boot device for a given boot list. - - The DCIM_BootConfigSetting resource represents each boot list (eg. - IPL/BIOS, BCV, UEFI, vFlash Partition, One Time Boot). - The DCIM_BootSourceSetting resource represents each of the boot list boot - devices or sources that are shown under their corresponding boot list. - - :param node: ironic node object. - :param boot_list_id: boot list id. - :param controller_version: version of the Lifecycle controller. - :raises: DracClientError on an error from pywsman library. - :returns: boot device id. - """ - client = drac_client.get_wsman_client(node) - - if controller_version < '2.0.0': - filter_query = ('select * from DCIM_BootSourceSetting where ' - 'PendingAssignedSequence=0') + if persistent: + boot_list = PERSISTENT_BOOT_MODE else: - filter_query = ('select * from DCIM_BootSourceSetting where ' - 'PendingAssignedSequence=0 and ' - 'BootSourceType="%s"' % boot_list_id) - try: - doc = client.wsman_enumerate(resource_uris.DCIM_BootSourceSetting, - filter_query=filter_query) - except exception.DracClientError as exc: - with excutils.save_and_reraise_exception(): - LOG.error(_LE('DRAC driver failed to get the current boot ' - 'device for node %(node_uuid)s. ' - 'Reason: %(error)s.'), - {'node_uuid': node.uuid, 'error': exc}) + boot_list = NON_PERSISTENT_BOOT_MODE - if controller_version < '2.0.0': - boot_devices = drac_common.find_xml( - doc, 'InstanceID', resource_uris.DCIM_BootSourceSetting, - find_all=True) - for device in boot_devices: - if device.text.startswith(boot_list_id): - boot_device_id = device.text - break - else: - boot_device_id = drac_common.find_xml( - doc, 'InstanceID', resource_uris.DCIM_BootSourceSetting).text - - return boot_device_id - - -def _get_boot_list_for_boot_device(node, device, controller_version): - """Get the boot list for a given boot device. - - The DCIM_BootConfigSetting resource represents each boot list (eg. - IPL/BIOS, BCV, UEFI, vFlash Partition, One Time Boot). - The DCIM_BootSourceSetting resource represents each of the boot list boot - devices or sources that are shown under their corresponding boot list. - - :param node: ironic node object. - :param device: boot device. - :param controller_version: version of the Lifecycle controller. - :raises: DracClientError on an error from pywsman library. - :returns: dictionary containing: - - :boot_list: boot list. - :boot_device_id: boot device id. - """ - client = drac_client.get_wsman_client(node) - - if controller_version < '2.0.0': - filter_query = None - else: - filter_query = ("select * from DCIM_BootSourceSetting where " - "InstanceID like '%%#%s%%'" % - _BOOT_DEVICES_MAP[device]) - - try: - doc = client.wsman_enumerate(resource_uris.DCIM_BootSourceSetting, - filter_query=filter_query) - except exception.DracClientError as exc: - with excutils.save_and_reraise_exception(): - LOG.error(_LE('DRAC driver failed to set the boot device ' - 'for node %(node_uuid)s. Can\'t find the ID ' - 'for the %(device)s type. Reason: %(error)s.'), - {'node_uuid': node.uuid, 'error': exc, - 'device': device}) - - if controller_version < '2.0.0': - boot_devices = drac_common.find_xml( - doc, 'InstanceID', resource_uris.DCIM_BootSourceSetting, - find_all=True) - for boot_device in boot_devices: - if _BOOT_DEVICES_MAP[device] in boot_device.text: - boot_device_id = boot_device.text - boot_list = boot_device_id.split(':')[0] - break - else: - boot_device_id = drac_common.find_xml( - doc, 'InstanceID', resource_uris.DCIM_BootSourceSetting).text - boot_list = drac_common.find_xml( - doc, 'BootSourceType', resource_uris.DCIM_BootSourceSetting).text - - return {'boot_list': boot_list, 'boot_device_id': boot_device_id} + client.change_boot_device_order(boot_list, drac_boot_device) + client.commit_pending_bios_changes() +# TODO(ifarkas): delete this during BIOS vendor_passthru refactor def create_config_job(node, reboot=False): """Create a configuration job. @@ -263,6 +159,7 @@ def create_config_job(node, reboot=False): {'node_uuid': node.uuid, 'error': exc}) +# TODO(ifarkas): delete this during BIOS vendor_passthru refactor def check_for_config_job(node): """Check if a configuration job is already created. @@ -303,33 +200,10 @@ def check_for_config_job(node): target=TARGET_DEVICE) -def _get_lifecycle_controller_version(node): - """Returns the Lifecycle controller version of the DRAC card of the node - - :param node: the node. - :returns: the Lifecycle controller version. - :raises: DracClientError if the client received unexpected response. - :raises: InvalidParameterValue if required DRAC credentials are missing. - """ - client = drac_client.get_wsman_client(node) - filter_query = ('select LifecycleControllerVersion from DCIM_SystemView') - try: - doc = client.wsman_enumerate(resource_uris.DCIM_SystemView, - filter_query=filter_query) - except exception.DracClientError as exc: - with excutils.save_and_reraise_exception(): - LOG.error(_LE('DRAC driver failed to get power state for node ' - '%(node_uuid)s. Reason: %(error)s.'), - {'node_uuid': node.uuid, 'error': exc}) - - version = drac_common.find_xml(doc, 'LifecycleControllerVersion', - resource_uris.DCIM_SystemView).text - return version - - class DracManagement(base.ManagementInterface): def get_properties(self): + """Return the properties of the interface.""" return drac_common.COMMON_PROPERTIES def validate(self, task): @@ -349,93 +223,70 @@ class DracManagement(base.ManagementInterface): def get_supported_boot_devices(self, task): """Get a list of the supported boot devices. - :param task: a task from TaskManager. + :param task: a TaskManager instance containing the node to act on. :returns: A list with the supported boot devices defined in :mod:`ironic.common.boot_devices`. """ return list(_BOOT_DEVICES_MAP.keys()) + def get_boot_device(self, task): + """Get the current boot device for a node. + + Returns the current boot device of the node. + + :param task: a TaskManager instance containing the node to act on. + :raises: DracOperationError on an error from python-dracclient. + :returns: a dictionary containing: + :boot_device: the boot device, one of + :mod:`ironic.common.boot_devices` or None if it is unknown. + :persistent: whether the boot device will persist to all + future boots or not, None if it is unknown. + + """ + node = task.node + + if ('drac_boot_device' in node.driver_internal_info and + node.driver_internal_info['drac_boot_device'] is not None): + return node.driver_internal_info['drac_boot_device'] + else: + return _get_boot_device(node) + @task_manager.require_exclusive_lock def set_boot_device(self, task, device, persistent=False): """Set the boot device for a node. Set the boot device to use on next reboot of the node. - :param task: a task from TaskManager. + :param task: a TaskManager instance containing the node to act on. :param device: the boot device, one of :mod:`ironic.common.boot_devices`. :param persistent: Boolean value. True if the boot device will persist to all future boots, False if not. Default: False. - :raises: DracClientError if the client received unexpected response. - :raises: DracOperationFailed if the client received response with an - error message. - :raises: DracUnexpectedReturnValue if the client received a response - with unexpected return value. - :raises: InvalidParameterValue if an invalid boot device is - specified. - :raises: DracPendingConfigJobExists on an error when creating the job. - + :raises: InvalidParameterValue if an invalid boot device is specified. """ + node = task.node - client = drac_client.get_wsman_client(task.node) - controller_version = _get_lifecycle_controller_version(task.node) - current_boot_device = _get_boot_device(task.node, controller_version) + if device not in _BOOT_DEVICES_MAP: + raise exception.InvalidParameterValue( + _("set_boot_device called with invalid device '%(device)s' " + "for node %(node_id)s.") % {'device': device, + 'node_id': node.uuid}) - # If we are already booting from the right device, do nothing. - if current_boot_device == {'boot_device': device, - 'persistent': persistent}: - LOG.debug('DRAC already set to boot from %s', device) - return - - # Check for an existing configuration job - check_for_config_job(task.node) - - # Querying the boot device attributes - boot_device = _get_boot_list_for_boot_device(task.node, device, - controller_version) - boot_list = boot_device['boot_list'] - boot_device_id = boot_device['boot_device_id'] - - if not persistent: - boot_list = 'OneTime' - - # Send the request to DRAC - selectors = {'InstanceID': boot_list} - properties = {'source': boot_device_id} - try: - client.wsman_invoke(resource_uris.DCIM_BootConfigSetting, - 'ChangeBootOrderByInstanceID', selectors, - properties, drac_client.RET_SUCCESS) - except exception.DracRequestFailed as exc: - with excutils.save_and_reraise_exception(): - LOG.error(_LE('DRAC driver failed to set the boot device for ' - 'node %(node_uuid)s to %(target_boot_device)s. ' - 'Reason: %(error)s.'), - {'node_uuid': task.node.uuid, - 'target_boot_device': device, - 'error': exc}) - - # Create a configuration job - create_config_job(task.node) - - def get_boot_device(self, task): - """Get the current boot device for a node. - - Returns the current boot device of the node. - - :param task: a task from TaskManager. - :raises: DracClientError on an error from pywsman library. - :returns: a dictionary containing: - - :boot_device: the boot device, one of - :mod:`ironic.common.boot_devices` or None if it is unknown. - :persistent: Whether the boot device will persist to all - future boots or not, None if it is unknown. - - """ - return _get_boot_device(task.node) + # NOTE(ifarkas): DRAC interface doesn't allow changing the boot device + # multiple times in a row without a reboot. This is + # because a change need to be committed via a + # configuration job, and further configuration jobs + # cannot be created until the previous one is processed + # at the next boot. As a workaround, saving it to + # driver_internal_info and committing the change during + # power state change. + driver_internal_info = node.driver_internal_info + driver_internal_info['drac_boot_device'] = {'boot_device': device, + 'persistent': persistent} + node.driver_internal_info = driver_internal_info + node.save() def get_sensors_data(self, task): """Get sensors data. diff --git a/ironic/drivers/modules/drac/power.py b/ironic/drivers/modules/drac/power.py index 1977e1192d..9f3bce5882 100644 --- a/ironic/drivers/modules/drac/power.py +++ b/ironic/drivers/modules/drac/power.py @@ -24,6 +24,7 @@ from ironic.common import states from ironic.conductor import task_manager from ironic.drivers import base from ironic.drivers.modules.drac import common as drac_common +from ironic.drivers.modules.drac import management as drac_management drac_constants = importutils.try_import('dracclient.constants') drac_exceptions = importutils.try_import('dracclient.exceptions') @@ -62,6 +63,20 @@ def _get_power_state(node): return POWER_STATES[drac_power_state] +def _commit_boot_list_change(node): + driver_internal_info = node.driver_internal_info + + if ('drac_boot_device' in driver_internal_info and + driver_internal_info['drac_boot_device'] is not None): + boot_device = driver_internal_info['drac_boot_device'] + drac_management.set_boot_device(node, boot_device['boot_device'], + boot_device['persistent']) + + driver_internal_info['drac_boot_device'] = None + node.driver_internal_info = driver_internal_info + node.save() + + def _set_power_state(node, power_state): """Turns the server power on/off or do a reboot. @@ -71,6 +86,8 @@ def _set_power_state(node, power_state): :raises: DracOperationError on an error from python-dracclient """ + _commit_boot_list_change(node) + client = drac_common.get_drac_client(node) target_power_state = REVERSE_POWER_STATES[power_state] diff --git a/ironic/tests/unit/drivers/modules/drac/test_job.py b/ironic/tests/unit/drivers/modules/drac/test_job.py new file mode 100644 index 0000000000..dd66382580 --- /dev/null +++ b/ironic/tests/unit/drivers/modules/drac/test_job.py @@ -0,0 +1,67 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Test class for DRAC job specific methods +""" + +from dracclient import exceptions as drac_exceptions +import mock + +from ironic.common import exception +from ironic.drivers.modules.drac import common as drac_common +from ironic.drivers.modules.drac import job as drac_job +from ironic.tests.unit.conductor import mgr_utils +from ironic.tests.unit.db import base as db_base +from ironic.tests.unit.db import utils as db_utils +from ironic.tests.unit.objects import utils as obj_utils + +INFO_DICT = db_utils.get_test_drac_info() + + +@mock.patch.object(drac_common, 'get_drac_client', spec_set=True, + autospec=True) +class DracJobTestCase(db_base.DbTestCase): + + def setUp(self): + super(DracJobTestCase, self).setUp() + mgr_utils.mock_the_extension_manager(driver='fake_drac') + self.node = obj_utils.create_test_node(self.context, + driver='fake_drac', + driver_info=INFO_DICT) + + def test_validate_job_queue(self, mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + mock_client.list_jobs.return_value = [] + + drac_job.validate_job_queue(self.node) + + mock_client.list_jobs.assert_called_once_with(only_unfinished=True) + + def test_validate_job_queue_fail(self, mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + exc = drac_exceptions.BaseClientException('boom') + mock_client.list_jobs.side_effect = exc + + self.assertRaises(exception.DracOperationError, + drac_job.validate_job_queue, self.node) + + def test_validate_job_queue_invalid(self, mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + mock_client.list_jobs.return_value = [42] + + self.assertRaises(exception.DracOperationError, + drac_job.validate_job_queue, self.node) diff --git a/ironic/tests/unit/drivers/modules/drac/test_management.py b/ironic/tests/unit/drivers/modules/drac/test_management.py index ff93edcd23..6031041c41 100644 --- a/ironic/tests/unit/drivers/modules/drac/test_management.py +++ b/ironic/tests/unit/drivers/modules/drac/test_management.py @@ -16,16 +16,17 @@ # under the License. """ -Test class for DRAC ManagementInterface +Test class for DRAC management interface """ import mock -from ironic.common import boot_devices +import ironic.common.boot_devices from ironic.common import exception from ironic.conductor import task_manager from ironic.drivers.modules.drac import client as drac_client from ironic.drivers.modules.drac import common as drac_common +from ironic.drivers.modules.drac import job as drac_job from ironic.drivers.modules.drac import management as drac_mgmt from ironic.drivers.modules.drac import resource_uris from ironic.tests.unit.conductor import mgr_utils @@ -39,7 +40,8 @@ from ironic.tests.unit.objects import utils as obj_utils INFO_DICT = db_utils.get_test_drac_info() -@mock.patch.object(drac_client, 'pywsman', spec_set=mock_specs.PYWSMAN_SPEC) +@mock.patch.object(drac_common, 'get_drac_client', spec_set=True, + autospec=True) class DracManagementInternalMethodsTestCase(db_base.DbTestCase): def setUp(self): @@ -48,43 +50,235 @@ class DracManagementInternalMethodsTestCase(db_base.DbTestCase): self.node = obj_utils.create_test_node(self.context, driver='fake_drac', driver_info=INFO_DICT) + self.boot_mode_ipl = {'id': 'IPL', 'name': 'BootSeq', + 'is_current': True, 'is_next': True} + self.boot_mode_one_time = {'id': 'OneTime', 'name': 'OneTimeBootMode', + 'is_current': False, 'is_next': False} - def test__get_next_boot_list(self, mock_client_pywsman): - result_xml = test_utils.build_soap_xml( - [{'DCIM_BootConfigSetting': {'InstanceID': 'IPL', - 'IsNext': drac_mgmt.PERSISTENT}}], - resource_uris.DCIM_BootConfigSetting) + self.boot_device_pxe = { + 'id': 'BIOS.Setup.1-1#BootSeq#NIC.Embedded.1-1-1', + 'boot_mode': 'IPL', + 'current_assigned_sequence': 0, + 'pending_assigned_sequence': 0, + 'bios_boot_string': 'Embedded NIC 1 Port 1 Partition 1'} + self.boot_device_disk = { + 'id': 'BIOS.Setup.1-1#BootSeq#HardDisk.List.1-1', + 'boot_mode': 'IPL', + 'current_assigned_sequence': 1, + 'pending_assigned_sequence': 1, + 'bios_boot_string': 'Hard drive C: BootSeq'} - mock_xml = test_utils.mock_wsman_root(result_xml) - mock_pywsman = mock_client_pywsman.Client.return_value - mock_pywsman.enumerate.return_value = mock_xml + def test__get_boot_device(self, mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + mock_client.list_boot_modes.return_value = [ + mock.Mock(**self.boot_mode_ipl), + mock.Mock(**self.boot_mode_one_time)] + mock_client.list_boot_devices.return_value = { + 'IPL': [mock.Mock(**self.boot_device_pxe), + mock.Mock(**self.boot_device_disk)]} - expected = {'instance_id': 'IPL', 'is_next': drac_mgmt.PERSISTENT} - result = drac_mgmt._get_next_boot_list(self.node) + boot_device = drac_mgmt._get_boot_device(self.node) - self.assertEqual(expected, result) - mock_pywsman.enumerate.assert_called_once_with( - mock.ANY, mock.ANY, resource_uris.DCIM_BootConfigSetting) + expected_boot_device = {'boot_device': 'pxe', 'persistent': True} + self.assertEqual(expected_boot_device, boot_device) + mock_client.list_boot_modes.assert_called_once_with() + mock_client.list_boot_devices.assert_called_once_with() - def test__get_next_boot_list_onetime(self, mock_client_pywsman): - result_xml = test_utils.build_soap_xml( - [{'DCIM_BootConfigSetting': {'InstanceID': 'IPL', - 'IsNext': drac_mgmt.PERSISTENT}}, - {'DCIM_BootConfigSetting': {'InstanceID': 'OneTime', - 'IsNext': drac_mgmt.ONE_TIME_BOOT}}], - resource_uris.DCIM_BootConfigSetting) + def test__get_boot_device_not_persistent(self, mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + self.boot_mode_one_time['is_next'] = True + mock_client.list_boot_modes.return_value = [ + mock.Mock(**self.boot_mode_ipl), + mock.Mock(**self.boot_mode_one_time)] + mock_client.list_boot_devices.return_value = { + 'OneTime': [mock.Mock(**self.boot_device_pxe), + mock.Mock(**self.boot_device_disk)]} - mock_xml = test_utils.mock_wsman_root(result_xml) - mock_pywsman = mock_client_pywsman.Client.return_value - mock_pywsman.enumerate.return_value = mock_xml + boot_device = drac_mgmt._get_boot_device(self.node) - expected = {'instance_id': 'OneTime', - 'is_next': drac_mgmt.ONE_TIME_BOOT} - result = drac_mgmt._get_next_boot_list(self.node) + expected_boot_device = {'boot_device': 'pxe', 'persistent': False} + self.assertEqual(expected_boot_device, boot_device) + mock_client.list_boot_modes.assert_called_once_with() + mock_client.list_boot_devices.assert_called_once_with() - self.assertEqual(expected, result) - mock_pywsman.enumerate.assert_called_once_with( - mock.ANY, mock.ANY, resource_uris.DCIM_BootConfigSetting) + def test__get_boot_device_with_empty_boot_mode_list(self, + mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + mock_client.list_boot_modes.return_value = [] + + self.assertRaises(exception.DracOperationError, + drac_mgmt._get_boot_device, self.node) + + @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True, + autospec=True) + @mock.patch.object(drac_job, 'validate_job_queue', spec_set=True, + autospec=True) + def test_set_boot_device(self, mock_validate_job_queue, + mock__get_boot_device, mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + mock_client.list_boot_modes.return_value = [ + mock.Mock(**self.boot_mode_ipl), + mock.Mock(**self.boot_mode_one_time)] + mock_client.list_boot_devices.return_value = { + 'IPL': [mock.Mock(**self.boot_device_pxe), + mock.Mock(**self.boot_device_disk)]} + boot_device = {'boot_device': ironic.common.boot_devices.DISK, + 'persistent': True} + mock__get_boot_device.return_value = boot_device + + boot_device = drac_mgmt.set_boot_device( + self.node, ironic.common.boot_devices.PXE, persistent=False) + + mock_client.change_boot_device_order.assert_called_once_with( + 'OneTime', 'BIOS.Setup.1-1#BootSeq#NIC.Embedded.1-1-1') + mock_client.commit_pending_bios_changes.assert_called_once_with() + + @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True, + autospec=True) + @mock.patch.object(drac_job, 'validate_job_queue', spec_set=True, + autospec=True) + def test_set_boot_device_called_with_no_change( + self, mock_validate_job_queue, mock__get_boot_device, + mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + mock_client.list_boot_modes.return_value = [ + mock.Mock(**self.boot_mode_ipl), + mock.Mock(**self.boot_mode_one_time)] + mock_client.list_boot_devices.return_value = { + 'IPL': [mock.Mock(**self.boot_device_pxe), + mock.Mock(**self.boot_device_disk)]} + boot_device = {'boot_device': ironic.common.boot_devices.PXE, + 'persistent': True} + mock__get_boot_device.return_value = boot_device + + boot_device = drac_mgmt.set_boot_device( + self.node, ironic.common.boot_devices.PXE, persistent=True) + + self.assertEqual(0, mock_client.change_boot_device_order.call_count) + self.assertEqual(0, mock_client.commit_pending_bios_changes.call_count) + + @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True, + autospec=True) + @mock.patch.object(drac_job, 'validate_job_queue', spec_set=True, + autospec=True) + def test_set_boot_device_with_invalid_job_queue( + self, mock_validate_job_queue, mock__get_boot_device, + mock_get_drac_client): + mock_client = mock.Mock() + mock_get_drac_client.return_value = mock_client + mock_validate_job_queue.side_effect = exception.DracOperationError( + 'boom') + + self.assertRaises(exception.DracOperationError, + drac_mgmt.set_boot_device, self.node, + ironic.common.boot_devices.PXE, persistent=True) + + self.assertEqual(0, mock_client.change_boot_device_order.call_count) + self.assertEqual(0, mock_client.commit_pending_bios_changes.call_count) + + +@mock.patch.object(drac_common, 'get_drac_client', spec_set=True, + autospec=True) +class DracManagementTestCase(db_base.DbTestCase): + + def setUp(self): + super(DracManagementTestCase, self).setUp() + mgr_utils.mock_the_extension_manager(driver='fake_drac') + self.node = obj_utils.create_test_node(self.context, + driver='fake_drac', + driver_info=INFO_DICT) + + def test_get_properties(self, mock_get_drac_client): + expected = drac_common.COMMON_PROPERTIES + driver = drac_mgmt.DracManagement() + self.assertEqual(expected, driver.get_properties()) + + def test_get_supported_boot_devices(self, mock_get_drac_client): + expected_boot_devices = [ironic.common.boot_devices.PXE, + ironic.common.boot_devices.DISK, + ironic.common.boot_devices.CDROM] + + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + boot_devices = ( + task.driver.management.get_supported_boot_devices(task)) + + self.assertEqual(sorted(expected_boot_devices), sorted(boot_devices)) + + @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True, + autospec=True) + def test_get_boot_device(self, mock__get_boot_device, + mock_get_drac_client): + expected_boot_device = {'boot_device': ironic.common.boot_devices.DISK, + 'persistent': True} + mock__get_boot_device.return_value = expected_boot_device + + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + boot_device = task.driver.management.get_boot_device(task) + + self.assertEqual(expected_boot_device, boot_device) + mock__get_boot_device.assert_called_once_with(task.node) + + @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True, + autospec=True) + def test_get_boot_device_from_driver_internal_info(self, + mock__get_boot_device, + mock_get_drac_client): + expected_boot_device = {'boot_device': ironic.common.boot_devices.DISK, + 'persistent': True} + + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + task.node.driver_internal_info['drac_boot_device'] = ( + expected_boot_device) + boot_device = task.driver.management.get_boot_device(task) + + self.assertEqual(expected_boot_device, boot_device) + self.assertEqual(0, mock__get_boot_device.call_count) + + def test_set_boot_device(self, mock_get_drac_client): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.driver.management.set_boot_device( + task, ironic.common.boot_devices.DISK, persistent=True) + + expected_boot_device = { + 'boot_device': ironic.common.boot_devices.DISK, + 'persistent': True} + self.assertEqual( + task.node.driver_internal_info['drac_boot_device'], + expected_boot_device) + + def test_set_boot_device_fail(self, mock_get_drac_client): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + self.assertRaises(exception.InvalidParameterValue, + task.driver.management.set_boot_device, task, + 'foo') + + def test_get_sensors_data(self, mock_get_drac_client): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + self.assertRaises(NotImplementedError, + task.driver.management.get_sensors_data, task) + + +# TODO(ifarkas): delete this during BIOS vendor_passthru refactor +@mock.patch.object(drac_client, 'pywsman', spec_set=mock_specs.PYWSMAN_SPEC) +class DracConfigJobMethodsTestCase(db_base.DbTestCase): + + def setUp(self): + super(DracConfigJobMethodsTestCase, self).setUp() + mgr_utils.mock_the_extension_manager(driver='fake_drac') + self.node = obj_utils.create_test_node(self.context, + driver='fake_drac', + driver_info=INFO_DICT) def test__check_for_config_job(self, mock_client_pywsman): result_xml = test_utils.build_soap_xml( @@ -189,294 +383,3 @@ class DracManagementInternalMethodsTestCase(db_base.DbTestCase): mock_pywsman.invoke.assert_called_once_with( mock.ANY, resource_uris.DCIM_BIOSService, 'CreateTargetedConfigJob', None) - - def test__get_lifecycle_controller_version(self, mock_client_pywsman): - result_xml = test_utils.build_soap_xml( - [{'DCIM_SystemView': {'LifecycleControllerVersion': '42'}}], - resource_uris.DCIM_SystemView) - - mock_xml = test_utils.mock_wsman_root(result_xml) - mock_pywsman = mock_client_pywsman.Client.return_value - mock_pywsman.enumerate.return_value = mock_xml - - result = drac_mgmt._get_lifecycle_controller_version(self.node) - - self.assertEqual('42', result) - mock_pywsman.enumerate.assert_called_once_with( - mock.ANY, mock.ANY, resource_uris.DCIM_SystemView) - - -@mock.patch.object(drac_client, 'pywsman', spec_set=mock_specs.PYWSMAN_SPEC) -class DracManagementTestCase(db_base.DbTestCase): - - def setUp(self): - super(DracManagementTestCase, self).setUp() - mgr_utils.mock_the_extension_manager(driver='fake_drac') - self.node = obj_utils.create_test_node(self.context, - driver='fake_drac', - driver_info=INFO_DICT) - self.driver = drac_mgmt.DracManagement() - self.task = mock.Mock(spec=['node']) - self.task.node = self.node - - def test_get_properties(self, mock_client_pywsman): - expected = drac_common.COMMON_PROPERTIES - self.assertEqual(expected, self.driver.get_properties()) - - def test_get_supported_boot_devices(self, mock_client_pywsman): - expected = [boot_devices.PXE, boot_devices.DISK, boot_devices.CDROM] - self.assertEqual(sorted(expected), - sorted(self.driver. - get_supported_boot_devices(self.task))) - - @mock.patch.object(drac_mgmt, '_get_next_boot_list', spec_set=True, - autospec=True) - @mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version', - spec_set=True, autospec=True) - def test_get_boot_device(self, mock_glcv, mock_gnbl, mock_client_pywsman): - controller_version = '2.1.5.0' - mock_glcv.return_value = controller_version - mock_gnbl.return_value = {'instance_id': 'OneTime', - 'is_next': drac_mgmt.ONE_TIME_BOOT} - - result_xml = test_utils.build_soap_xml( - [{'InstanceID': 'HardDisk'}], resource_uris.DCIM_BootSourceSetting) - - mock_xml = test_utils.mock_wsman_root(result_xml) - mock_pywsman = mock_client_pywsman.Client.return_value - mock_pywsman.enumerate.return_value = mock_xml - - result = self.driver.get_boot_device(self.task) - expected = {'boot_device': boot_devices.DISK, 'persistent': False} - - self.assertEqual(expected, result) - mock_pywsman.enumerate.assert_called_once_with( - mock.ANY, mock.ANY, resource_uris.DCIM_BootSourceSetting) - - @mock.patch.object(drac_mgmt, '_get_next_boot_list', spec_set=True, - autospec=True) - @mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version', - spec_set=True, autospec=True) - def test_get_boot_device_persistent(self, mock_glcv, mock_gnbl, - mock_client_pywsman): - controller_version = '2.1.5.0' - mock_glcv.return_value = controller_version - mock_gnbl.return_value = {'instance_id': 'IPL', - 'is_next': drac_mgmt.PERSISTENT} - - result_xml = test_utils.build_soap_xml( - [{'InstanceID': 'NIC', 'BootSourceType': 'IPL'}], - resource_uris.DCIM_BootSourceSetting) - - mock_xml = test_utils.mock_wsman_root(result_xml) - mock_pywsman = mock_client_pywsman.Client.return_value - mock_pywsman.enumerate.return_value = mock_xml - - result = self.driver.get_boot_device(self.task) - expected = {'boot_device': boot_devices.PXE, 'persistent': True} - - self.assertEqual(expected, result) - mock_pywsman.enumerate.assert_called_once_with( - mock.ANY, mock.ANY, resource_uris.DCIM_BootSourceSetting) - - @mock.patch.object(drac_client.Client, 'wsman_enumerate', spec_set=True, - autospec=True) - @mock.patch.object(drac_mgmt, '_get_next_boot_list', spec_set=True, - autospec=True) - @mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version', - spec_set=True, autospec=True) - def test_get_boot_device_client_error(self, mock_glcv, mock_gnbl, mock_we, - mock_client_pywsman): - controller_version = '2.1.5.0' - mock_glcv.return_value = controller_version - mock_gnbl.return_value = {'instance_id': 'OneTime', - 'is_next': drac_mgmt.ONE_TIME_BOOT} - mock_we.side_effect = iter([exception.DracClientError('E_FAKE')]) - - self.assertRaises(exception.DracClientError, - self.driver.get_boot_device, self.task) - mock_we.assert_called_once_with( - mock.ANY, resource_uris.DCIM_BootSourceSetting, - filter_query=mock.ANY) - - @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True, - autospec=True) - @mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version', - spec_set=True, autospec=True) - @mock.patch.object(drac_mgmt, 'check_for_config_job', spec_set=True, - autospec=True) - @mock.patch.object(drac_mgmt, 'create_config_job', spec_set=True, - autospec=True) - def test_set_boot_device(self, mock_ccj, mock_cfcj, mock_glcv, mock_gbd, - mock_client_pywsman): - controller_version = '2.1.5.0' - mock_glcv.return_value = controller_version - mock_gbd.return_value = {'boot_device': boot_devices.PXE, - 'persistent': True} - result_xml_enum = test_utils.build_soap_xml( - [{'InstanceID': 'NIC', 'BootSourceType': 'IPL'}], - resource_uris.DCIM_BootSourceSetting) - result_xml_invk = test_utils.build_soap_xml( - [{'ReturnValue': drac_client.RET_SUCCESS}], - resource_uris.DCIM_BootConfigSetting) - - mock_xml_enum = test_utils.mock_wsman_root(result_xml_enum) - mock_xml_invk = test_utils.mock_wsman_root(result_xml_invk) - mock_pywsman = mock_client_pywsman.Client.return_value - mock_pywsman.enumerate.return_value = mock_xml_enum - mock_pywsman.invoke.return_value = mock_xml_invk - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node = self.node - result = self.driver.set_boot_device(task, boot_devices.PXE) - - self.assertIsNone(result) - mock_pywsman.enumerate.assert_called_once_with( - mock.ANY, mock.ANY, resource_uris.DCIM_BootSourceSetting) - mock_pywsman.invoke.assert_called_once_with( - mock.ANY, resource_uris.DCIM_BootConfigSetting, - 'ChangeBootOrderByInstanceID', None) - mock_glcv.assert_called_once_with(self.node) - mock_gbd.assert_called_once_with(self.node, controller_version) - mock_cfcj.assert_called_once_with(self.node) - mock_ccj.assert_called_once_with(self.node) - - @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True, - autospec=True) - @mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version', - spec_set=True, autospec=True) - @mock.patch.object(drac_mgmt, 'check_for_config_job', spec_set=True, - autospec=True) - @mock.patch.object(drac_mgmt, 'create_config_job', spec_set=True, - autospec=True) - def test_set_boot_device_fail(self, mock_ccj, mock_cfcj, mock_glcv, - mock_gbd, mock_client_pywsman): - controller_version = '2.1.5.0' - mock_glcv.return_value = controller_version - mock_gbd.return_value = {'boot_device': boot_devices.PXE, - 'persistent': True} - result_xml_enum = test_utils.build_soap_xml( - [{'InstanceID': 'NIC', 'BootSourceType': 'IPL'}], - resource_uris.DCIM_BootSourceSetting) - result_xml_invk = test_utils.build_soap_xml( - [{'ReturnValue': drac_client.RET_ERROR, 'Message': 'E_FAKE'}], - resource_uris.DCIM_BootConfigSetting) - - mock_xml_enum = test_utils.mock_wsman_root(result_xml_enum) - mock_xml_invk = test_utils.mock_wsman_root(result_xml_invk) - mock_pywsman = mock_client_pywsman.Client.return_value - mock_pywsman.enumerate.return_value = mock_xml_enum - mock_pywsman.invoke.return_value = mock_xml_invk - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node = self.node - self.assertRaises(exception.DracOperationFailed, - self.driver.set_boot_device, task, - boot_devices.PXE) - - mock_pywsman.enumerate.assert_called_once_with( - mock.ANY, mock.ANY, resource_uris.DCIM_BootSourceSetting) - mock_pywsman.invoke.assert_called_once_with( - mock.ANY, resource_uris.DCIM_BootConfigSetting, - 'ChangeBootOrderByInstanceID', None) - mock_glcv.assert_called_once_with(self.node) - mock_gbd.assert_called_once_with(self.node, controller_version) - mock_cfcj.assert_called_once_with(self.node) - self.assertFalse(mock_ccj.called) - - @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True, - autospec=True) - @mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version', - spec_set=True, autospec=True) - @mock.patch.object(drac_client.Client, 'wsman_enumerate', spec_set=True, - autospec=True) - @mock.patch.object(drac_mgmt, 'check_for_config_job', spec_set=True, - autospec=True) - def test_set_boot_device_client_error(self, mock_cfcj, mock_we, mock_glcv, - mock_gbd, - mock_client_pywsman): - controller_version = '2.1.5.0' - mock_glcv.return_value = controller_version - mock_gbd.return_value = {'boot_device': boot_devices.PXE, - 'persistent': True} - mock_we.side_effect = iter([exception.DracClientError('E_FAKE')]) - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node = self.node - self.assertRaises(exception.DracClientError, - self.driver.set_boot_device, task, - boot_devices.PXE) - mock_glcv.assert_called_once_with(self.node) - mock_gbd.assert_called_once_with(self.node, controller_version) - mock_we.assert_called_once_with( - mock.ANY, resource_uris.DCIM_BootSourceSetting, - filter_query=mock.ANY) - - @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True, - autospec=True) - @mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version', - spec_set=True, autospec=True) - @mock.patch.object(drac_mgmt, 'check_for_config_job', spec_set=True, - autospec=True) - def test_set_boot_device_noop(self, mock_cfcj, mock_glcv, mock_gbd, - mock_client_pywsman): - controller_version = '2.1.5.0' - mock_glcv.return_value = controller_version - mock_gbd.return_value = {'boot_device': boot_devices.PXE, - 'persistent': False} - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node = self.node - result = self.driver.set_boot_device(task, boot_devices.PXE) - self.assertIsNone(result) - mock_glcv.assert_called_once_with(self.node) - mock_gbd.assert_called_once_with(self.node, controller_version) - self.assertFalse(mock_cfcj.called) - - def test_get_sensors_data(self, mock_client_pywsman): - self.assertRaises(NotImplementedError, - self.driver.get_sensors_data, self.task) - - @mock.patch.object(drac_mgmt, '_get_boot_device', spec_set=True, - autospec=True) - @mock.patch.object(drac_mgmt, '_get_lifecycle_controller_version', - spec_set=True, autospec=True) - @mock.patch.object(drac_mgmt, 'check_for_config_job', spec_set=True, - autospec=True) - @mock.patch.object(drac_mgmt, 'create_config_job', spec_set=True, - autospec=True) - def test_set_boot_device_11g(self, mock_ccj, mock_cfcj, mock_glcv, - mock_gbd, mock_client_pywsman): - controller_version = '1.5.0.0' - mock_glcv.return_value = controller_version - mock_gbd.return_value = {'boot_device': boot_devices.PXE, - 'persistent': True} - result_xml_enum = test_utils.build_soap_xml( - [{'InstanceID': 'NIC'}], - resource_uris.DCIM_BootSourceSetting) - result_xml_invk = test_utils.build_soap_xml( - [{'ReturnValue': drac_client.RET_SUCCESS}], - resource_uris.DCIM_BootConfigSetting) - - mock_xml_enum = test_utils.mock_wsman_root(result_xml_enum) - mock_xml_invk = test_utils.mock_wsman_root(result_xml_invk) - mock_pywsman = mock_client_pywsman.Client.return_value - mock_pywsman.enumerate.return_value = mock_xml_enum - mock_pywsman.invoke.return_value = mock_xml_invk - - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node = self.node - result = self.driver.set_boot_device(task, boot_devices.PXE) - - self.assertIsNone(result) - mock_pywsman.enumerate.assert_called_once_with( - mock.ANY, mock.ANY, resource_uris.DCIM_BootSourceSetting) - mock_pywsman.invoke.assert_called_once_with( - mock.ANY, resource_uris.DCIM_BootConfigSetting, - 'ChangeBootOrderByInstanceID', None) - mock_glcv.assert_called_once_with(self.node) - mock_gbd.assert_called_once_with(self.node, controller_version) - mock_cfcj.assert_called_once_with(self.node) - mock_ccj.assert_called_once_with(self.node)