From c75a070520b02deafe61343157d3a4219374f6d3 Mon Sep 17 00:00:00 2001 From: Ruby Loo Date: Tue, 15 Jul 2014 10:02:18 -0400 Subject: [PATCH] Add drivers.base.BaseDriver.get_properties() Adds ironic.drivers.base.BaseDriver.get_properties() which returns a dictionary of : entries. The driver interfaces (DeployInterface, PowerInterface, ...) have a new get_properties() method that returns a dictionary of :. These changes are needed in order to provide an API to get driver_info properties. Change-Id: I5994e990deb26841633ca26de1a5fb63b743271a Blueprint: get-required-driver-info Partial-Bug: #1261915 --- ironic/drivers/base.py | 57 +++++++++++++++++++++++++ ironic/drivers/modules/fake.py | 20 +++++++++ ironic/drivers/modules/ilo/common.py | 17 +++++++- ironic/drivers/modules/ilo/power.py | 3 ++ ironic/drivers/modules/ipminative.py | 24 ++++++++--- ironic/drivers/modules/ipmitool.py | 42 ++++++++++++++++-- ironic/drivers/modules/pxe.py | 14 ++++++ ironic/drivers/modules/seamicro.py | 40 +++++++++++------ ironic/drivers/modules/ssh.py | 38 ++++++++++++++--- ironic/drivers/utils.py | 12 ++++++ ironic/tests/drivers/ilo/test_power.py | 6 +++ ironic/tests/drivers/test_fake.py | 11 +++++ ironic/tests/drivers/test_ipminative.py | 4 ++ ironic/tests/drivers/test_ipmitool.py | 11 +++++ ironic/tests/drivers/test_pxe.py | 6 +++ ironic/tests/drivers/test_seamicro.py | 6 +++ ironic/tests/drivers/test_ssh.py | 7 +++ ironic/tests/drivers/test_utils.py | 8 ++++ 18 files changed, 294 insertions(+), 32 deletions(-) diff --git a/ironic/drivers/base.py b/ironic/drivers/base.py index ceb48703c3..4ba16a804d 100644 --- a/ironic/drivers/base.py +++ b/ironic/drivers/base.py @@ -88,11 +88,33 @@ class BaseDriver(object): def __init__(self): pass + def get_properties(self): + """Get the properties of the driver. + + :returns: dictionary of : entries. + """ + + properties = {} + for iface_name in (self.core_interfaces + + self.standard_interfaces + + ['vendor']): + iface = getattr(self, iface_name, None) + if iface: + properties.update(iface.get_properties()) + return properties + @six.add_metaclass(abc.ABCMeta) class DeployInterface(object): """Interface for deploy-related actions.""" + @abc.abstractmethod + def get_properties(self): + """Return the properties of the interface. + + :returns: dictionary of : entries. + """ + @abc.abstractmethod def validate(self, task): """Validate the driver-specific Node deployment info. @@ -189,6 +211,13 @@ class DeployInterface(object): class PowerInterface(object): """Interface for power-related actions.""" + @abc.abstractmethod + def get_properties(self): + """Return the properties of the interface. + + :returns: dictionary of : entries. + """ + @abc.abstractmethod def validate(self, task): """Validate the driver-specific Node power info. @@ -230,6 +259,13 @@ class PowerInterface(object): class ConsoleInterface(object): """Interface for console-related actions.""" + @abc.abstractmethod + def get_properties(self): + """Return the properties of the interface. + + :returns: dictionary of : entries. + """ + @abc.abstractmethod def validate(self, task): """Validate the driver-specific Node console info. @@ -273,6 +309,13 @@ class ConsoleInterface(object): class RescueInterface(object): """Interface for rescue-related actions.""" + @abc.abstractmethod + def get_properties(self): + """Return the properties of the interface. + + :returns: dictionary of : entries. + """ + @abc.abstractmethod def validate(self, task): """Validate the rescue info stored in the node' properties. @@ -310,6 +353,13 @@ class VendorInterface(object): should be short-lived. """ + @abc.abstractmethod + def get_properties(self): + """Return the properties of the interface. + + :returns: dictionary of : entries. + """ + @abc.abstractmethod def validate(self, task, **kwargs): """Validate vendor-specific actions. @@ -357,6 +407,13 @@ class VendorInterface(object): class ManagementInterface(object): """Interface for management related actions.""" + @abc.abstractmethod + def get_properties(self): + """Return the properties of the interface. + + :returns: dictionary of : entries. + """ + @abc.abstractmethod def validate(self, task): """Validate the driver-specific management information. diff --git a/ironic/drivers/modules/fake.py b/ironic/drivers/modules/fake.py index 95c3bf5304..1ae10f7f67 100644 --- a/ironic/drivers/modules/fake.py +++ b/ironic/drivers/modules/fake.py @@ -42,6 +42,9 @@ def _raise_unsupported_error(method=None): class FakePower(base.PowerInterface): """Example implementation of a simple power interface.""" + def get_properties(self): + return {} + def validate(self, task): pass @@ -63,6 +66,9 @@ class FakeDeploy(base.DeployInterface): separate power interface. """ + def get_properties(self): + return {} + def validate(self, task): pass @@ -85,6 +91,10 @@ class FakeDeploy(base.DeployInterface): class FakeVendorA(base.VendorInterface): """Example implementation of a vendor passthru interface.""" + def get_properties(self): + return {'A1': 'A1 description. Required.', + 'A2': 'A2 description. Optional.'} + def validate(self, task, **kwargs): method = kwargs.get('method') if method == 'first_method': @@ -109,6 +119,10 @@ class FakeVendorA(base.VendorInterface): class FakeVendorB(base.VendorInterface): """Example implementation of a secondary vendor passthru.""" + def get_properties(self): + return {'B1': 'B1 description. Required.', + 'B2': 'B2 description. Required.'} + def validate(self, task, **kwargs): method = kwargs.get('method') if method == 'second_method': @@ -133,6 +147,9 @@ class FakeVendorB(base.VendorInterface): class FakeConsole(base.ConsoleInterface): """Example implementation of a simple console interface.""" + def get_properties(self): + return {} + def validate(self, task): pass @@ -149,6 +166,9 @@ class FakeConsole(base.ConsoleInterface): class FakeManagement(base.ManagementInterface): """Example implementation of a simple management interface.""" + def get_properties(self): + return {} + def validate(self, task): pass diff --git a/ironic/drivers/modules/ilo/common.py b/ironic/drivers/modules/ilo/common.py index f645139ae8..80d503bd43 100644 --- a/ironic/drivers/modules/ilo/common.py +++ b/ironic/drivers/modules/ilo/common.py @@ -44,6 +44,19 @@ CONF.register_opts(opts, group='ilo') LOG = logging.getLogger(__name__) +REQUIRED_PROPERTIES = { + 'ilo_address': _("IP address or hostname of the iLO. Required."), + 'ilo_username': _("username for the iLO with administrator privileges. " + "Required."), + 'ilo_password': _("password for ilo_username. Required.") +} +OPTIONAL_PROPERTIES = { + 'client_port': _("port to be used for iLO operations. Optional."), + 'client_timeout': _("timeout (in seconds) for iLO operations. Optional.") +} +COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy() +COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES) + def parse_driver_info(node): """Gets the driver specific Node deployment info. @@ -61,13 +74,13 @@ def parse_driver_info(node): d_info = {} error_msgs = [] - for param in ('ilo_address', 'ilo_username', 'ilo_password'): + for param in REQUIRED_PROPERTIES: try: d_info[param] = info[param] except KeyError: error_msgs.append(_("'%s' not supplied to IloDriver.") % param) - for param in ('client_port', 'client_timeout'): + for param in OPTIONAL_PROPERTIES: value = info.get(param, CONF.ilo.get(param)) try: value = int(value) diff --git a/ironic/drivers/modules/ilo/power.py b/ironic/drivers/modules/ilo/power.py index cabf51193f..5faf74282b 100644 --- a/ironic/drivers/modules/ilo/power.py +++ b/ironic/drivers/modules/ilo/power.py @@ -152,6 +152,9 @@ def _set_power_state(node, target_state): class IloPower(base.PowerInterface): + def get_properties(self): + return ilo_common.COMMON_PROPERTIES + def validate(self, task): """Check if node.driver_info contains the required iLO credentials. diff --git a/ironic/drivers/modules/ipminative.py b/ironic/drivers/modules/ipminative.py index 19c572fa22..4be2b943b6 100644 --- a/ironic/drivers/modules/ipminative.py +++ b/ironic/drivers/modules/ipminative.py @@ -50,6 +50,11 @@ CONF.register_opts(opts, group='ipmi') LOG = logging.getLogger(__name__) +REQUIRED_PROPERTIES = {'ipmi_address': _("IP of the node's BMC. Required."), + 'ipmi_password': _("IPMI password. Required."), + 'ipmi_username': _("IPMI username. Required.")} +COMMON_PROPERTIES = REQUIRED_PROPERTIES + def _parse_driver_info(node): """Gets the bmc access info for the given node. @@ -58,19 +63,18 @@ def _parse_driver_info(node): """ info = node.driver_info or {} - bmc_info = {} - bmc_info['address'] = info.get('ipmi_address') - bmc_info['username'] = info.get('ipmi_username') - bmc_info['password'] = info.get('ipmi_password') - - # address, username and password must be present - missing_info = [key for key in bmc_info if not bmc_info[key]] + missing_info = [key for key in REQUIRED_PROPERTIES if not info.get(key)] if missing_info: raise exception.InvalidParameterValue(_( "The following IPMI credentials are not supplied" " to IPMI driver: %s." ) % missing_info) + bmc_info = {} + bmc_info['address'] = info.get('ipmi_address') + bmc_info['username'] = info.get('ipmi_username') + bmc_info['password'] = info.get('ipmi_password') + # get additional info bmc_info['uuid'] = node.uuid @@ -207,6 +211,9 @@ def _power_status(driver_info): class NativeIPMIPower(base.PowerInterface): """The power driver using native python-ipmi library.""" + def get_properties(self): + return COMMON_PROPERTIES + def validate(self, task): """Check that node['driver_info'] contains IPMI credentials. @@ -299,6 +306,9 @@ class VendorPassthru(base.VendorInterface): % {'node_id': driver_info['uuid'], 'error': str(e)}) raise exception.IPMIFailure(cmd=str(e)) + def get_properties(self): + return COMMON_PROPERTIES + def validate(self, task, **kwargs): """Validate vendor-specific actions. :param task: a TaskManager instance. diff --git a/ironic/drivers/modules/ipmitool.py b/ironic/drivers/modules/ipmitool.py index e4239c3279..9060eb68a5 100644 --- a/ironic/drivers/modules/ipmitool.py +++ b/ironic/drivers/modules/ipmitool.py @@ -65,6 +65,23 @@ CONF.import_opt('min_command_interval', LOG = logging.getLogger(__name__) VALID_PRIV_LEVELS = ['ADMINISTRATOR', 'CALLBACK', 'OPERATOR', 'USER'] + +REQUIRED_PROPERTIES = { + 'ipmi_address': _("IP address or hostname of the node. Required.") +} +OPTIONAL_PROPERTIES = { + 'ipmi_password': _("password. Optional."), + 'ipmi_priv_level': _("privilege level; default is ADMINISTRATOR. One of " + "%s. Optional.") % ', '.join(VALID_PRIV_LEVELS), + 'ipmi_username': _("username; default is NULL user. Optional.") +} +COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy() +COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES) +CONSOLE_PROPERTIES = { + 'ipmi_terminal_port': _("node's UDP port to connect to. Only required for " + "console access.") +} + LAST_CMD_TIME = {} TIMING_SUPPORT = None @@ -145,6 +162,13 @@ def _parse_driver_info(node): """ info = node.driver_info or {} + missing_info = [key for key in REQUIRED_PROPERTIES if not info.get(key)] + if missing_info: + raise exception.InvalidParameterValue(_( + "The following IPMI credentials are not supplied" + " to IPMI driver: %s." + ) % missing_info) + address = info.get('ipmi_address') username = info.get('ipmi_username') password = info.get('ipmi_password') @@ -158,10 +182,6 @@ def _parse_driver_info(node): raise exception.InvalidParameterValue(_( "IPMI terminal port is not an integer.")) - if not address: - raise exception.InvalidParameterValue(_( - "IPMI address not supplied to IPMI driver.")) - if priv_level not in VALID_PRIV_LEVELS: valid_priv_lvls = ', '.join(VALID_PRIV_LEVELS) raise exception.InvalidParameterValue(_( @@ -365,6 +385,9 @@ class IPMIPower(base.PowerInterface): reason="Unable to locate usable ipmitool command in " "the system path when checking ipmitool version") + def get_properties(self): + return COMMON_PROPERTIES + def validate(self, task): """Validate driver_info for ipmitool driver. @@ -438,6 +461,9 @@ class IPMIPower(base.PowerInterface): class IPMIManagement(base.ManagementInterface): + def get_properties(self): + return COMMON_PROPERTIES + def validate(self, task): """Check that 'driver_info' contains IPMI credentials. @@ -602,6 +628,9 @@ class VendorPassthru(base.VendorInterface): {'node_id': node_uuid, 'error': e}) raise exception.IPMIFailure(cmd=cmd) + def get_properties(self): + return COMMON_PROPERTIES + def validate(self, task, **kwargs): """Validate vendor-specific actions. @@ -668,6 +697,11 @@ class IPMIShellinaboxConsole(base.ConsoleInterface): reason="Unable to locate usable ipmitool command in " "the system path when checking ipmitool version") + def get_properties(self): + d = COMMON_PROPERTIES.copy() + d.update(CONSOLE_PROPERTIES) + return d + def validate(self, task): """Validate the Node console info. diff --git a/ironic/drivers/modules/pxe.py b/ironic/drivers/modules/pxe.py index 4af3c4aa18..07a3bc3f4a 100644 --- a/ironic/drivers/modules/pxe.py +++ b/ironic/drivers/modules/pxe.py @@ -90,6 +90,14 @@ CONF = cfg.CONF CONF.register_opts(pxe_opts, group='pxe') CONF.import_opt('use_ipv6', 'ironic.netconf') +REQUIRED_PROPERTIES = { + 'pxe_deploy_kernel': _("UUID (from Glance) of the deployment kernel. " + "Required."), + 'pxe_deploy_ramdisk': _("UUID (from Glance) of the ramdisk that is " + "mounted at boot time. Required."), +} +COMMON_PROPERTIES = REQUIRED_PROPERTIES + def _check_for_missing_params(info_dict, param_prefix=''): missing_info = [] @@ -463,6 +471,9 @@ def _validate_glance_image(ctx, deploy_info): class PXEDeploy(base.DeployInterface): """PXE Deploy Interface: just a stub until the real driver is ported.""" + def get_properties(self): + return COMMON_PROPERTIES + def validate(self, task): """Validate the deployment information for the task's node. @@ -609,6 +620,9 @@ class VendorPassthru(base.VendorInterface): return params + def get_properties(self): + return COMMON_PROPERTIES + def validate(self, task, **kwargs): method = kwargs['method'] if method == 'pass_deploy_info': diff --git a/ironic/drivers/modules/seamicro.py b/ironic/drivers/modules/seamicro.py index 491c0b2935..265131aa67 100644 --- a/ironic/drivers/modules/seamicro.py +++ b/ironic/drivers/modules/seamicro.py @@ -62,6 +62,19 @@ _BOOT_DEVICES_MAP = { boot_devices.PXE: 'pxe', } +REQUIRED_PROPERTIES = { + 'seamicro_api_endpoint': _("API endpoint. Required."), + 'seamicro_password': _("password. Required."), + 'seamicro_server_id': _("server ID. Required."), + 'seamicro_username': _("username. Required."), +} +OPTIONAL_PROPERTIES = { + 'seamicro_api_version': _("version of SeaMicro API client; default is 2. " + "Optional.") +} +COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy() +COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES) + def _get_client(*args, **kwargs): """Creates the python-seamicro_client @@ -87,24 +100,18 @@ def _parse_driver_info(node): """ info = node.driver_info or {} + missing_info = [key for key in REQUIRED_PROPERTIES if not info.get(key)] + if missing_info: + raise exception.InvalidParameterValue(_( + "SeaMicro driver requires the following to be set: %s.") + % missing_info) + api_endpoint = info.get('seamicro_api_endpoint') username = info.get('seamicro_username') password = info.get('seamicro_password') server_id = info.get('seamicro_server_id') api_version = info.get('seamicro_api_version', "2") - if not api_endpoint: - raise exception.InvalidParameterValue(_( - "SeaMicro driver requires api_endpoint be set")) - - if not username or not password: - raise exception.InvalidParameterValue(_( - "SeaMicro driver requires both username and password be set")) - - if not server_id: - raise exception.InvalidParameterValue(_( - "SeaMicro driver requires server_id be set")) - res = {'username': username, 'password': password, 'api_endpoint': api_endpoint, @@ -322,6 +329,9 @@ class Power(base.PowerInterface): state of servers in a seamicro chassis. """ + def get_properties(self): + return COMMON_PROPERTIES + def validate(self, task): """Check that node 'driver_info' is valid. @@ -389,6 +399,9 @@ class Power(base.PowerInterface): class VendorPassthru(base.VendorInterface): """SeaMicro vendor-specific methods.""" + def get_properties(self): + return COMMON_PROPERTIES + def validate(self, task, **kwargs): method = kwargs['method'] if method not in VENDOR_PASSTHRU_METHODS: @@ -470,6 +483,9 @@ class VendorPassthru(base.VendorInterface): class Management(base.ManagementInterface): + def get_properties(self): + return COMMON_PROPERTIES + def validate(self, task): """Check that 'driver_info' contains SeaMicro credentials. diff --git a/ironic/drivers/modules/ssh.py b/ironic/drivers/modules/ssh.py index 2d8d470f89..50cc0270ae 100644 --- a/ironic/drivers/modules/ssh.py +++ b/ironic/drivers/modules/ssh.py @@ -35,6 +35,7 @@ from ironic.common import utils from ironic.conductor import task_manager from ironic.drivers import base from ironic.drivers import utils as driver_utils +from ironic.openstack.common.gettextutils import _ from ironic.openstack.common import log as logging from ironic.openstack.common import processutils @@ -49,6 +50,27 @@ CONF.register_opts(libvirt_opts, group='ssh') LOG = logging.getLogger(__name__) +REQUIRED_PROPERTIES = { + 'ssh_address': _("IP address or hostname of the node to ssh into. " + "Required."), + 'ssh_username': _("username to authenticate as. Required."), + 'ssh_virt_type': _("virtualization software to use; one of vbox, virsh, " + "vmware. Required.") +} +OTHER_PROPERTIES = { + 'ssh_key_contents': _("private key(s). One of this, ssh_key_filename, " + "or ssh_password must be specified."), + 'ssh_key_filename': _("(list of) filename(s) of optional private key(s) " + "for authentication. One of this, ssh_key_contents, " + "or ssh_password must be specified."), + 'ssh_password': _("password to use for authentication or for unlocking a " + "private key. One of this, ssh_key_contents, or " + "ssh_key_filename must be specified."), + 'ssh_port': _("port on the node to connect to; default is 22. Optional.") +} +COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy() +COMMON_PROPERTIES.update(OTHER_PROPERTIES) + def _get_command_sets(virt_type): if virt_type == 'vbox': @@ -155,6 +177,12 @@ def _parse_driver_info(node): """ info = node.driver_info or {} + missing_info = [key for key in REQUIRED_PROPERTIES if not info.get(key)] + if missing_info: + raise exception.InvalidParameterValue(_( + "SSHPowerDriver requires the following to be set: %s.") + % missing_info) + address = info.get('ssh_address') username = info.get('ssh_username') password = info.get('ssh_password') @@ -176,16 +204,9 @@ def _parse_driver_info(node): 'uuid': node.uuid } - if not virt_type: - raise exception.InvalidParameterValue(_( - "SSHPowerDriver requires virt_type be set.")) - cmd_set = _get_command_sets(virt_type) res['cmd_set'] = cmd_set - if not address or not username: - raise exception.InvalidParameterValue(_( - "SSHPowerDriver requires both address and username be set.")) # Only one credential may be set (avoids complexity around having # precedence etc). if len(filter(None, (password, key_filename, key_contents))) != 1: @@ -354,6 +375,9 @@ class SSHPower(base.PowerInterface): NOTE: This driver does not currently support multi-node operations. """ + def get_properties(self): + return COMMON_PROPERTIES + def validate(self, task): """Check that the node's 'driver_info' is valid. diff --git a/ironic/drivers/utils.py b/ironic/drivers/utils.py index d0435fc819..47ceef275b 100644 --- a/ironic/drivers/utils.py +++ b/ironic/drivers/utils.py @@ -46,6 +46,18 @@ class MixinVendorInterface(base.VendorInterface): method = kwargs.get('method') return self.mapping.get(method) or _raise_unsupported_error(method) + def get_properties(self): + """Return the properties from all the VendorInterfaces. + + :returns: a dictionary of : + entries. + """ + properties = {} + interfaces = set(self.mapping.values()) + for interface in interfaces: + properties.update(interface.get_properties()) + return properties + def validate(self, *args, **kwargs): """Call validate on the appropriate interface only. diff --git a/ironic/tests/drivers/ilo/test_power.py b/ironic/tests/drivers/ilo/test_power.py index 61a221f8a8..2dbfe72ae2 100644 --- a/ironic/tests/drivers/ilo/test_power.py +++ b/ironic/tests/drivers/ilo/test_power.py @@ -149,6 +149,12 @@ class IloPowerTestCase(base.TestCase): driver='ilo', driver_info=driver_info) + def test_get_properties(self): + expected = ilo_common.COMMON_PROPERTIES + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + self.assertEqual(expected, task.driver.get_properties()) + @mock.patch.object(ilo_common, 'parse_driver_info') def test_validate(self, mock_drvinfo): with task_manager.acquire(self.context, self.node.uuid, diff --git a/ironic/tests/drivers/test_fake.py b/ironic/tests/drivers/test_fake.py index 1e401c7d0e..0f9cfb13c6 100644 --- a/ironic/tests/drivers/test_fake.py +++ b/ironic/tests/drivers/test_fake.py @@ -53,7 +53,13 @@ class FakeDriverTestCase(base.TestCase): driver_base.ConsoleInterface) self.assertIsNone(self.driver.rescue) + def test_get_properties(self): + expected = ['A1', 'A2', 'B1', 'B2'] + properties = self.driver.get_properties() + self.assertEqual(sorted(expected), sorted(properties.keys())) + def test_power_interface(self): + self.assertEqual({}, self.driver.power.get_properties()) self.driver.power.validate(self.task) self.driver.power.get_power_state(self.task) self.assertRaises(exception.InvalidParameterValue, @@ -63,6 +69,7 @@ class FakeDriverTestCase(base.TestCase): self.driver.power.reboot(self.task) def test_deploy_interface(self): + self.assertEqual({}, self.driver.deploy.get_properties()) self.driver.deploy.validate(None) self.driver.deploy.prepare(None) @@ -74,11 +81,15 @@ class FakeDriverTestCase(base.TestCase): self.driver.deploy.tear_down(None) def test_console_interface(self): + self.assertEqual({}, self.driver.console.get_properties()) self.driver.console.validate(self.task) self.driver.console.start_console(self.task) self.driver.console.stop_console(self.task) self.driver.console.get_console(self.task) + def test_management_interface_get_properties(self): + self.assertEqual({}, self.driver.management.get_properties()) + def test_management_interface_validate(self): self.driver.management.validate(self.task) diff --git a/ironic/tests/drivers/test_ipminative.py b/ironic/tests/drivers/test_ipminative.py index 5340a8132e..d0649513cf 100644 --- a/ironic/tests/drivers/test_ipminative.py +++ b/ironic/tests/drivers/test_ipminative.py @@ -143,6 +143,10 @@ class IPMINativeDriverTestCase(db_base.DbTestCase): self.dbapi = db_api.get_instance() self.info = ipminative._parse_driver_info(self.node) + def test_get_properties(self): + expected = ipminative.COMMON_PROPERTIES + self.assertEqual(expected, self.driver.get_properties()) + @mock.patch('pyghmi.ipmi.command.Command') def test_get_power_state(self, ipmi_mock): # Getting the mocked command. diff --git a/ironic/tests/drivers/test_ipmitool.py b/ironic/tests/drivers/test_ipmitool.py index ae29af36cf..55751a987a 100644 --- a/ironic/tests/drivers/test_ipmitool.py +++ b/ironic/tests/drivers/test_ipmitool.py @@ -484,6 +484,17 @@ class IPMIToolDriverTestCase(db_base.DbTestCase): driver_info=INFO_DICT) self.info = ipmi._parse_driver_info(self.node) + def test_get_properties(self): + expected = ipmi.COMMON_PROPERTIES + self.assertEqual(expected, self.driver.power.get_properties()) + + expected = ipmi.COMMON_PROPERTIES.keys() + expected += ipmi.CONSOLE_PROPERTIES.keys() + self.assertEqual(sorted(expected), + sorted(self.driver.console.get_properties().keys())) + self.assertEqual(sorted(expected), + sorted(self.driver.get_properties().keys())) + @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True) def test_get_power_state(self, mock_exec): returns = iter([["Chassis Power is off\n", None], diff --git a/ironic/tests/drivers/test_pxe.py b/ironic/tests/drivers/test_pxe.py index cd1ff9a7e4..f870eca7da 100644 --- a/ironic/tests/drivers/test_pxe.py +++ b/ironic/tests/drivers/test_pxe.py @@ -519,6 +519,12 @@ class PXEDriverTestCase(db_base.DbTestCase): open(token_path, 'w').close() return token_path + def test_get_properties(self): + expected = pxe.COMMON_PROPERTIES + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + self.assertEqual(expected, task.driver.get_properties()) + @mock.patch.object(base_image_service.BaseImageService, '_show') def test_validate_good(self, mock_glance): mock_glance.return_value = {'properties': {'kernel_id': 'fake-kernel', diff --git a/ironic/tests/drivers/test_seamicro.py b/ironic/tests/drivers/test_seamicro.py index 56a5370879..42c8f4ced9 100644 --- a/ironic/tests/drivers/test_seamicro.py +++ b/ironic/tests/drivers/test_seamicro.py @@ -274,6 +274,12 @@ class SeaMicroPowerDriverTestCase(db_base.DbTestCase): self.Server = Fake_Server self.Volume = Fake_Volume + def test_get_properties(self): + expected = seamicro.COMMON_PROPERTIES + with task_manager.acquire(self.context, self.node['uuid'], + shared=True) as task: + self.assertEqual(expected, task.driver.get_properties()) + @mock.patch.object(seamicro, '_parse_driver_info') def test_power_interface_validate_good(self, parse_drv_info_mock): with task_manager.acquire(self.context, self.node['uuid'], diff --git a/ironic/tests/drivers/test_ssh.py b/ironic/tests/drivers/test_ssh.py index 6f09f0dc4a..ff8ea22ae0 100644 --- a/ironic/tests/drivers/test_ssh.py +++ b/ironic/tests/drivers/test_ssh.py @@ -580,6 +580,13 @@ class SSHDriverTestCase(db_base.DbTestCase): driver_info = ssh._parse_driver_info(task.node) ssh_connect_mock.assert_called_once_with(driver_info) + def test_get_properties(self): + expected = ssh.COMMON_PROPERTIES + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + self.assertEqual(expected, task.driver.power.get_properties()) + self.assertEqual(expected, task.driver.get_properties()) + def test_validate_fail_no_port(self): new_node = obj_utils.create_test_node( self.context, diff --git a/ironic/tests/drivers/test_utils.py b/ironic/tests/drivers/test_utils.py index 799d3ca097..6a1f94e916 100644 --- a/ironic/tests/drivers/test_utils.py +++ b/ironic/tests/drivers/test_utils.py @@ -38,6 +38,14 @@ class UtilsTestCase(base.TestCase): self.driver = driver_factory.get_driver("fake") self.node = obj_utils.create_test_node(self.context) + def test_vendor_interface_get_properties(self): + expected = {'A1': 'A1 description. Required.', + 'A2': 'A2 description. Optional.', + 'B1': 'B1 description. Required.', + 'B2': 'B2 description. Required.'} + props = self.driver.vendor.get_properties() + self.assertEqual(expected, props) + @mock.patch.object(fake.FakeVendorA, 'validate') def test_vendor_interface_validate_valid_methods(self, mock_fakea_validate):