diff options
-rw-r--r-- | ironic/conductor/utils.py | 20 | ||||
-rw-r--r-- | ironic/drivers/fake.py | 2 | ||||
-rw-r--r-- | ironic/drivers/modules/ipminative.py | 145 | ||||
-rw-r--r-- | ironic/drivers/pxe.py | 7 | ||||
-rw-r--r-- | ironic/tests/drivers/test_ipminative.py | 92 |
5 files changed, 159 insertions, 107 deletions
diff --git a/ironic/conductor/utils.py b/ironic/conductor/utils.py index e8189e4ea..fe771189d 100644 --- a/ironic/conductor/utils.py +++ b/ironic/conductor/utils.py @@ -34,23 +34,11 @@ def node_set_boot_device(task, device, persistent=False): ManagementInterface fails. """ - try: - # TODO(lucasagomes): Remove this conditional once all drivers - # are ported to use the management interface - if getattr(task.driver, 'management', None): - task.driver.management.validate(task) - task.driver.management.set_boot_device(task, - device=device, - persistent=persistent) - else: - task.driver.vendor.vendor_passthru(task, + if getattr(task.driver, 'management', None): + task.driver.management.validate(task) + task.driver.management.set_boot_device(task, device=device, - persistent=persistent, - method='set_boot_device') - except exception.UnsupportedDriverExtension: - # NOTE(deva): Some drivers, like SSH, do not support set_boot_device. - # This is not a fatal exception. - pass + persistent=persistent) @task_manager.require_exclusive_lock diff --git a/ironic/drivers/fake.py b/ironic/drivers/fake.py index eeed764dc..936dabc4d 100644 --- a/ironic/drivers/fake.py +++ b/ironic/drivers/fake.py @@ -79,7 +79,7 @@ class FakeIPMINativeDriver(base.BaseDriver): def __init__(self): self.power = ipminative.NativeIPMIPower() self.deploy = fake.FakeDeploy() - self.vendor = ipminative.VendorPassthru() + self.management = ipminative.NativeIPMIManagement() class FakeSeaMicroDriver(base.BaseDriver): diff --git a/ironic/drivers/modules/ipminative.py b/ironic/drivers/modules/ipminative.py index 4be2b943b..f2a05d7b8 100644 --- a/ironic/drivers/modules/ipminative.py +++ b/ironic/drivers/modules/ipminative.py @@ -21,7 +21,9 @@ Ironic Native IPMI power manager. from oslo.config import cfg +from ironic.common import boot_devices from ironic.common import exception +from ironic.common import i18n from ironic.common import states from ironic.conductor import task_manager from ironic.drivers import base @@ -45,6 +47,8 @@ opts = [ 'Recommended setting is 5 seconds.'), ] +_LE = i18n._LE + CONF = cfg.CONF CONF.register_opts(opts, group='ipmi') @@ -55,6 +59,13 @@ REQUIRED_PROPERTIES = {'ipmi_address': _("IP of the node's BMC. Required."), 'ipmi_username': _("IPMI username. Required.")} COMMON_PROPERTIES = REQUIRED_PROPERTIES +_BOOT_DEVICES_MAP = { + boot_devices.DISK: 'hd', + boot_devices.PXE: 'net', + boot_devices.CDROM: 'cdrom', + boot_devices.BIOS: 'setup', +} + def _parse_driver_info(node): """Gets the bmc access info for the given node. @@ -276,22 +287,51 @@ class NativeIPMIPower(base.PowerInterface): _reboot(driver_info) -class VendorPassthru(base.VendorInterface): +class NativeIPMIManagement(base.ManagementInterface): + + def get_properties(self): + return COMMON_PROPERTIES + + def validate(self, task): + """Check that 'driver_info' contains IPMI credentials. + + Validates whether the 'driver_info' property of the supplied + task's node contains the required credentials information. + + :param task: a task from TaskManager. + :raises: InvalidParameterValue when required ipmi credentials + are missing. + + """ + _parse_driver_info(task.node) + + def get_supported_boot_devices(self): + """Get a list of the supported boot devices. + + :returns: A list with the supported boot devices defined + in :mod:`ironic.common.boot_devices`. + + """ + return list(_BOOT_DEVICES_MAP.keys()) @task_manager.require_exclusive_lock - def _set_boot_device(self, task, device, persistent=False): - """Set the boot device for a node. - - :param task: a TaskManager instance. - :param device: Boot device. One of [net, network, pxe, hd, cd, - cdrom, dvd, floppy, default, setup, f1] - :param persistent: Whether to set next-boot, or make the change - permanent. Default: False. - :raises: InvalidParameterValue if an invalid boot device is specified - or required ipmi credentials are missing. - :raises: IPMIFailure when the native ipmi call fails. + def set_boot_device(self, task, device, persistent=False): + """Set the boot device for the task's node. + + Set the boot device to use on next reboot of the node. + + :param task: a task from TaskManager. + :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: InvalidParameterValue if an invalid boot device is + specified or if required ipmi parameters are missing. + :raises: IPMIFailure on an error from pyghmi. + """ - if device not in ipmi_command.boot_devices: + if device not in self.get_supported_boot_devices(): raise exception.InvalidParameterValue(_( "Invalid boot device %s specified.") % device) driver_info = _parse_driver_info(task.node) @@ -299,45 +339,52 @@ class VendorPassthru(base.VendorInterface): ipmicmd = ipmi_command.Command(bmc=driver_info['address'], userid=driver_info['username'], password=driver_info['password']) - ipmicmd.set_bootdev(device) + bootdev = _BOOT_DEVICES_MAP[device] + ipmicmd.set_bootdev(bootdev, persist=persistent) except pyghmi_exception.IpmiException as e: - LOG.warning(_("IPMI set boot device failed for node %(node_id)s " - "with the following error: %(error)s") - % {'node_id': driver_info['uuid'], 'error': str(e)}) - raise exception.IPMIFailure(cmd=str(e)) + LOG.error(_LE("IPMI set boot device failed for node %(node_id)s " + "with the following error: %(error)s"), + {'node_id': driver_info['uuid'], 'error': e}) + raise exception.IPMIFailure(cmd=e) - def get_properties(self): - return COMMON_PROPERTIES + def get_boot_device(self, task): + """Get the current boot device for the task's node. - def validate(self, task, **kwargs): - """Validate vendor-specific actions. - :param task: a TaskManager instance. - :param kwargs: the keyword arguments supplied + Returns the current boot device of the node. - :raises: InvalidParameterValue if an invalid boot device is specified, - required ipmi credentials are missing or an invalid method - is requested to the driver. - """ - method = kwargs['method'] - if method == 'set_boot_device': - device = kwargs.get('device') - if device not in ipmi_command.boot_devices: - raise exception.InvalidParameterValue(_( - "Invalid boot device %s specified.") % device) - else: - raise exception.InvalidParameterValue(_( - "Unsupported method (%s) passed to IPMINative driver.") - % method) - _parse_driver_info(task.node) + :param task: a task from TaskManager. + :raises: InvalidParameterValue if required IPMI parameters + are missing. + :raises: IPMIFailure on an error from pyghmi. + :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. - def vendor_passthru(self, task, **kwargs): - """Receive requests for vendor-specific actions. - :param task: a TaskManager instance. - :param kwargs: the keyword arguments supplied """ - method = kwargs['method'] - if method == 'set_boot_device': - return self._set_boot_device( - task, - kwargs.get('device'), - kwargs.get('persistent', False)) + driver_info = _parse_driver_info(task.node) + response = {'boot_device': None, 'persistent': None} + try: + ipmicmd = ipmi_command.Command(bmc=driver_info['address'], + userid=driver_info['username'], + password=driver_info['password']) + ret = ipmicmd.get_bootdev() + # FIXME(lucasagomes): pyghmi doesn't seem to handle errors + # consistently, for some errors it raises an exception + # others it just returns a dictionary with the error. + if 'error' in ret: + raise pyghmi_exception.IpmiException(ret['error']) + except pyghmi_exception.IpmiException as e: + LOG.error(_LE("IPMI get boot device failed for node %(node_id)s " + "with the following error: %(error)s"), + {'node_id': driver_info['uuid'], 'error': e}) + raise exception.IPMIFailure(cmd=e) + + bootdev = ret.get('bootdev') + if bootdev: + response['boot_device'] = next((dev for dev, hdev in + _BOOT_DEVICES_MAP.items() + if hdev == bootdev), None) + return response diff --git a/ironic/drivers/pxe.py b/ironic/drivers/pxe.py index 5c7ed3ec3..f0c95d878 100644 --- a/ironic/drivers/pxe.py +++ b/ironic/drivers/pxe.py @@ -81,11 +81,8 @@ class PXEAndIPMINativeDriver(base.BaseDriver): reason=_("Unable to import pyghmi library")) self.power = ipminative.NativeIPMIPower() self.deploy = pxe.PXEDeploy() - self.pxe_vendor = pxe.VendorPassthru() - self.ipmi_vendor = ipminative.VendorPassthru() - self.mapping = {'pass_deploy_info': self.pxe_vendor, - 'set_boot_device': self.ipmi_vendor} - self.vendor = utils.MixinVendorInterface(self.mapping) + self.management = ipminative.NativeIPMIManagement() + self.vendor = pxe.VendorPassthru() class PXEAndSeaMicroDriver(base.BaseDriver): diff --git a/ironic/tests/drivers/test_ipminative.py b/ironic/tests/drivers/test_ipminative.py index d0649513c..03b60093e 100644 --- a/ironic/tests/drivers/test_ipminative.py +++ b/ironic/tests/drivers/test_ipminative.py @@ -22,10 +22,13 @@ Test class for Native IPMI power driver module. import mock from oslo.config import cfg +from pyghmi import exceptions as pyghmi_exception +from ironic.common import boot_devices from ironic.common import driver_factory from ironic.common import exception from ironic.common import states +from ironic.common import utils from ironic.conductor import task_manager from ironic.db import api as db_api from ironic.drivers.modules import ipminative @@ -146,6 +149,7 @@ class IPMINativeDriverTestCase(db_base.DbTestCase): def test_get_properties(self): expected = ipminative.COMMON_PROPERTIES self.assertEqual(expected, self.driver.get_properties()) + self.assertEqual(expected, self.driver.management.get_properties()) @mock.patch('pyghmi.ipmi.command.Command') def test_get_power_state(self, ipmi_mock): @@ -214,13 +218,14 @@ class IPMINativeDriverTestCase(db_base.DbTestCase): with task_manager.acquire(self.context, self.node.uuid) as task: - self.driver.vendor._set_boot_device(task, 'pxe') - ipmicmd.set_bootdev.assert_called_once_with('pxe') + self.driver.management.set_boot_device(task, boot_devices.PXE) + # PXE is converted to 'net' internally by ipminative + ipmicmd.set_bootdev.assert_called_once_with('net', persist=False) def test_set_boot_device_bad_device(self): with task_manager.acquire(self.context, self.node.uuid) as task: self.assertRaises(exception.InvalidParameterValue, - self.driver.vendor._set_boot_device, + self.driver.management.set_boot_device, task, 'fake-device') @@ -246,40 +251,55 @@ class IPMINativeDriverTestCase(db_base.DbTestCase): task) ipmicmd.set_power.assert_called_once_with('boot', 500) - def test_vendor_passthru_validate__set_boot_device_good(self): - with task_manager.acquire(self.context, - self.node['uuid']) as task: - self.driver.vendor.validate(task, - method='set_boot_device', - device='pxe') + def test_management_interface_get_supported_boot_devices(self): + with task_manager.acquire(self.context, self.node.uuid) as task: + expected = [boot_devices.PXE, boot_devices.DISK, + boot_devices.CDROM, boot_devices.BIOS] + self.assertEqual(sorted(expected), sorted(task.driver.management. + get_supported_boot_devices())) - def test_vendor_passthru_val__set_boot_device_fail_unknown_device(self): - with task_manager.acquire(self.context, - self.node['uuid']) as task: - self.assertRaises(exception.InvalidParameterValue, - self.driver.vendor.validate, - task, method='set_boot_device', - device='non-existent') + @mock.patch('pyghmi.ipmi.command.Command') + def test_management_interface_get_boot_device_good(self, ipmi_mock): + ipmicmd = ipmi_mock.return_value + ipmicmd.get_bootdev.return_value = {'bootdev': 'hd'} + with task_manager.acquire(self.context, self.node.uuid) as task: + bootdev = self.driver.management.get_boot_device(task) + self.assertEqual(boot_devices.DISK, bootdev['boot_device']) - def test_vendor_passthru_val__set_boot_device_fail_missed_device_arg(self): - with task_manager.acquire(self.context, - self.node['uuid']) as task: - self.assertRaises(exception.InvalidParameterValue, - self.driver.vendor.validate, - task, method='set_boot_device') + @mock.patch('pyghmi.ipmi.command.Command') + def test_management_interface_get_boot_device_fail(self, ipmi_mock): + ipmicmd = ipmi_mock.return_value + ipmicmd.get_bootdev.side_effect = pyghmi_exception.IpmiException + with task_manager.acquire(self.context, self.node.uuid) as task: + self.assertRaises(exception.IPMIFailure, + self.driver.management.get_boot_device, task) - def test_vendor_passthru_validate_method_notmatch(self): - with task_manager.acquire(self.context, - self.node['uuid']) as task: + @mock.patch('pyghmi.ipmi.command.Command') + def test_management_interface_get_boot_device_fail_dict(self, ipmi_mock): + ipmicmd = ipmi_mock.return_value + ipmicmd.get_bootdev.return_value = {'error': 'boooom'} + with task_manager.acquire(self.context, self.node.uuid) as task: + self.assertRaises(exception.IPMIFailure, + self.driver.management.get_boot_device, task) + + @mock.patch('pyghmi.ipmi.command.Command') + def test_management_interface_get_boot_device_unknown(self, ipmi_mock): + ipmicmd = ipmi_mock.return_value + ipmicmd.get_bootdev.return_value = {'bootdev': 'unknown'} + with task_manager.acquire(self.context, self.node.uuid) as task: + expected = {'boot_device': None, 'persistent': None} + self.assertEqual(expected, + self.driver.management.get_boot_device(task)) + + def test_management_interface_validate_good(self): + with task_manager.acquire(self.context, self.node.uuid) as task: + task.driver.management.validate(task) + + def test_management_interface_validate_fail(self): + # Missing IPMI driver_info information + node = obj_utils.create_test_node(self.context, id=2, + uuid=utils.generate_uuid(), + driver='fake_ipminative') + with task_manager.acquire(self.context, node.uuid) as task: self.assertRaises(exception.InvalidParameterValue, - self.driver.vendor.validate, - task, method='non-existent-method') - - @mock.patch.object(ipminative.VendorPassthru, '_set_boot_device') - def test_vendor_passthru_call__set_boot_device(self, boot_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - self.driver.vendor.vendor_passthru(task, - method='set_boot_device', - device='pxe') - boot_mock.assert_called_once_with(task, 'pxe', False) + task.driver.management.validate, task) |