exception from driver_factory.default_interface()

This changes driver_factory.default_interface() so that instead
of returning None if there is no calculated default interface,
it raises exception.NoValidDefaultForInterface.

This is a follow up to 6206c47720.

Change-Id: I0c3d5d75b5a37af02f3660968cf3f2c669e52019
Partial-Bug: #1524745
This commit is contained in:
Ruby Loo 2017-02-02 22:27:01 +00:00
parent 6206c47720
commit 0a7fc6059a
10 changed files with 195 additions and 48 deletions

View File

@ -142,7 +142,8 @@ def get_interface(driver_or_hw_type, interface_type, interface_name):
return impl_instance return impl_instance
def default_interface(driver_or_hw_type, interface_type): def default_interface(driver_or_hw_type, interface_type,
driver_name=None, node=None):
"""Calculate and return the default interface implementation. """Calculate and return the default interface implementation.
Finds the first implementation that is supported by the hardware type Finds the first implementation that is supported by the hardware type
@ -150,9 +151,13 @@ def default_interface(driver_or_hw_type, interface_type):
:param driver_or_hw_type: classic driver or hardware type instance object. :param driver_or_hw_type: classic driver or hardware type instance object.
:param interface_type: type of the interface (e.g. 'boot'). :param interface_type: type of the interface (e.g. 'boot').
:returns: an entrypoint name of the calculated default implementation :param driver_name: entrypoint name of the driver_or_hw_type object. Is
or None if no default implementation can be found. used for exception message.
:param node: the identifier of a node. If specified, is used for exception
message.
:returns: an entrypoint name of the calculated default implementation.
:raises: InterfaceNotFoundInEntrypoint if the entry point was not found. :raises: InterfaceNotFoundInEntrypoint if the entry point was not found.
:raises: NoValidDefaultForInterface if no default interface can be found.
""" """
factory = _INTERFACE_LOADERS[interface_type] factory = _INTERFACE_LOADERS[interface_type]
is_hardware_type = isinstance(driver_or_hw_type, is_hardware_type = isinstance(driver_or_hw_type,
@ -185,6 +190,21 @@ def default_interface(driver_or_hw_type, interface_type):
except KeyError: except KeyError:
continue continue
if impl_name is None:
# NOTE(rloo). No i18n on driver_type_str because translating substrings
# on their own may cause the final string to look odd.
if is_hardware_type:
driver_type_str = 'hardware type'
else:
driver_type_str = 'driver'
driver_name = driver_name or driver_or_hw_type.__class__.__name__
node_info = ""
if node is not None:
node_info = _(' node %s with') % node
raise exception.NoValidDefaultForInterface(
interface_type=interface_type, driver_type=driver_type_str,
driver=driver_name, node_info=node_info)
return impl_name return impl_name
@ -234,11 +254,8 @@ def check_and_update_node_interfaces(node, driver_or_hw_type=None):
# Not changing the result, proceeding with the next interface # Not changing the result, proceeding with the next interface
continue continue
impl_name = default_interface(driver_or_hw_type, iface) impl_name = default_interface(driver_or_hw_type, iface,
driver_name=node.driver, node=node.uuid)
if impl_name is None:
raise exception.NoValidDefaultForInterface(
interface_type=iface, node=node.uuid, driver=node.driver)
# Set the calculated default and set result to True # Set the calculated default and set result to True
setattr(node, field_name, impl_name) setattr(node, field_name, impl_name)

View File

@ -343,8 +343,11 @@ class IncompatibleInterface(InvalidParameterValue):
class NoValidDefaultForInterface(InvalidParameterValue): class NoValidDefaultForInterface(InvalidParameterValue):
_msg_fmt = _("No default value found for %(interface_type)s interface " # NOTE(rloo): in the line below, there is no blank space after 'For'
"for node %(node)s with driver or hardware type %(driver)s.") # because node_info could be an empty string. If node_info
# is not empty, it should start with a space.
_msg_fmt = _("For%(node_info)s %(driver_type)s '%(driver)s', no default "
"value found for %(interface_type)s interface.")
class ImageNotFound(NotFound): class ImageNotFound(NotFound):

View File

@ -274,10 +274,7 @@ class BaseConductorManager(object):
interface_map = driver_factory.enabled_supported_interfaces(ht) interface_map = driver_factory.enabled_supported_interfaces(ht)
for interface_type, interface_names in interface_map.items(): for interface_type, interface_names in interface_map.items():
default_interface = driver_factory.default_interface( default_interface = driver_factory.default_interface(
ht, interface_type) ht, interface_type, driver_name=ht_name)
if default_interface is None:
raise exception.NoValidDefaultForInterface(
interface_type=interface_type, node=None, driver=ht)
self.conductor.register_hardware_interfaces(ht_name, self.conductor.register_hardware_interfaces(ht_name,
interface_type, interface_type,
interface_names, interface_names,

View File

@ -136,6 +136,8 @@ class ConductorManager(base_manager.BaseConductorManager):
:param context: an admin context :param context: an admin context
:param node_obj: a changed (but not saved) node object. :param node_obj: a changed (but not saved) node object.
:raises: NoValidDefaultForInterface if no default can be calculated
for some interfaces, and explicit values must be provided.
""" """
node_id = node_obj.uuid node_id = node_obj.uuid
@ -334,6 +336,7 @@ class ConductorManager(base_manager.BaseConductorManager):
exception.InvalidParameterValue, exception.InvalidParameterValue,
exception.UnsupportedDriverExtension, exception.UnsupportedDriverExtension,
exception.DriverNotFound, exception.DriverNotFound,
exception.NoValidDefaultForInterface,
exception.InterfaceNotFoundInEntrypoint) exception.InterfaceNotFoundInEntrypoint)
def driver_vendor_passthru(self, context, driver_name, driver_method, def driver_vendor_passthru(self, context, driver_name, driver_method,
http_method, info): http_method, info):
@ -362,6 +365,9 @@ class ConductorManager(base_manager.BaseConductorManager):
:raises: DriverNotFound if the supplied driver is not loaded. :raises: DriverNotFound if the supplied driver is not loaded.
:raises: NoFreeConductorWorker when there is no free worker to start :raises: NoFreeConductorWorker when there is no free worker to start
async task. async task.
:raises: NoValidDefaultForInterface if no default interface
implementation can be found for this driver's vendor
interface.
:raises: InterfaceNotFoundInEntrypoint if the default interface for a :raises: InterfaceNotFoundInEntrypoint if the default interface for a
hardware type is invalid. hardware type is invalid.
:returns: A dictionary containing: :returns: A dictionary containing:
@ -381,17 +387,16 @@ class ConductorManager(base_manager.BaseConductorManager):
driver = driver_factory.get_driver_or_hardware_type(driver_name) driver = driver_factory.get_driver_or_hardware_type(driver_name)
vendor = None vendor = None
if isinstance(driver, hardware_type.AbstractHardwareType): if isinstance(driver, hardware_type.AbstractHardwareType):
vendor_name = driver_factory.default_interface(driver, 'vendor') vendor_name = driver_factory.default_interface(
if vendor_name is not None: driver, 'vendor', driver_name=driver_name)
vendor = driver_factory.get_interface(driver, 'vendor', vendor = driver_factory.get_interface(driver, 'vendor',
vendor_name) vendor_name)
else: else:
vendor = getattr(driver, 'vendor', None) vendor = getattr(driver, 'vendor', None)
if not vendor:
if not vendor: raise exception.UnsupportedDriverExtension(
raise exception.UnsupportedDriverExtension( driver=driver_name,
driver=driver_name, extension='vendor interface')
extension='vendor interface')
try: try:
vendor_opts = vendor.driver_routes[driver_method] vendor_opts = vendor.driver_routes[driver_method]
@ -449,6 +454,7 @@ class ConductorManager(base_manager.BaseConductorManager):
@METRICS.timer('ConductorManager.get_driver_vendor_passthru_methods') @METRICS.timer('ConductorManager.get_driver_vendor_passthru_methods')
@messaging.expected_exceptions(exception.UnsupportedDriverExtension, @messaging.expected_exceptions(exception.UnsupportedDriverExtension,
exception.DriverNotFound, exception.DriverNotFound,
exception.NoValidDefaultForInterface,
exception.InterfaceNotFoundInEntrypoint) exception.InterfaceNotFoundInEntrypoint)
def get_driver_vendor_passthru_methods(self, context, driver_name): def get_driver_vendor_passthru_methods(self, context, driver_name):
"""Retrieve information about vendor methods of the given driver. """Retrieve information about vendor methods of the given driver.
@ -460,6 +466,9 @@ class ConductorManager(base_manager.BaseConductorManager):
:raises: UnsupportedDriverExtension if current driver does not have :raises: UnsupportedDriverExtension if current driver does not have
vendor interface. vendor interface.
:raises: DriverNotFound if the supplied driver is not loaded. :raises: DriverNotFound if the supplied driver is not loaded.
:raises: NoValidDefaultForInterface if no default interface
implementation can be found for this driver's vendor
interface.
:raises: InterfaceNotFoundInEntrypoint if the default interface for a :raises: InterfaceNotFoundInEntrypoint if the default interface for a
hardware type is invalid. hardware type is invalid.
:returns: dictionary of <method name>:<method metadata> entries. :returns: dictionary of <method name>:<method metadata> entries.
@ -472,17 +481,16 @@ class ConductorManager(base_manager.BaseConductorManager):
driver = driver_factory.get_driver_or_hardware_type(driver_name) driver = driver_factory.get_driver_or_hardware_type(driver_name)
vendor = None vendor = None
if isinstance(driver, hardware_type.AbstractHardwareType): if isinstance(driver, hardware_type.AbstractHardwareType):
vendor_name = driver_factory.default_interface(driver, 'vendor') vendor_name = driver_factory.default_interface(
if vendor_name is not None: driver, 'vendor', driver_name=driver_name)
vendor = driver_factory.get_interface(driver, 'vendor', vendor = driver_factory.get_interface(driver, 'vendor',
vendor_name) vendor_name)
else: else:
vendor = getattr(driver, 'vendor', None) vendor = getattr(driver, 'vendor', None)
if not vendor:
if not vendor: raise exception.UnsupportedDriverExtension(
raise exception.UnsupportedDriverExtension( driver=driver_name,
driver=driver_name, extension='vendor interface')
extension='vendor interface')
return get_vendor_passthru_metadata(vendor.driver_routes) return get_vendor_passthru_metadata(vendor.driver_routes)
@ -2380,6 +2388,7 @@ class ConductorManager(base_manager.BaseConductorManager):
@METRICS.timer('ConductorManager.get_raid_logical_disk_properties') @METRICS.timer('ConductorManager.get_raid_logical_disk_properties')
@messaging.expected_exceptions(exception.UnsupportedDriverExtension, @messaging.expected_exceptions(exception.UnsupportedDriverExtension,
exception.NoValidDefaultForInterface,
exception.InterfaceNotFoundInEntrypoint) exception.InterfaceNotFoundInEntrypoint)
def get_raid_logical_disk_properties(self, context, driver_name): def get_raid_logical_disk_properties(self, context, driver_name):
"""Get the logical disk properties for RAID configuration. """Get the logical disk properties for RAID configuration.
@ -2392,6 +2401,9 @@ class ConductorManager(base_manager.BaseConductorManager):
:param driver_name: name of the driver :param driver_name: name of the driver
:raises: UnsupportedDriverExtension, if the driver doesn't :raises: UnsupportedDriverExtension, if the driver doesn't
support RAID configuration. support RAID configuration.
:raises: NoValidDefaultForInterface if no default interface
implementation can be found for this driver's RAID
interface.
:raises: InterfaceNotFoundInEntrypoint if the default interface for a :raises: InterfaceNotFoundInEntrypoint if the default interface for a
hardware type is invalid. hardware type is invalid.
:returns: A dictionary containing the properties and a textual :returns: A dictionary containing the properties and a textual
@ -2403,16 +2415,15 @@ class ConductorManager(base_manager.BaseConductorManager):
driver = driver_factory.get_driver_or_hardware_type(driver_name) driver = driver_factory.get_driver_or_hardware_type(driver_name)
raid_iface = None raid_iface = None
if isinstance(driver, hardware_type.AbstractHardwareType): if isinstance(driver, hardware_type.AbstractHardwareType):
raid_iface_name = driver_factory.default_interface(driver, 'raid') raid_iface_name = driver_factory.default_interface(
if raid_iface_name is not None: driver, 'raid', driver_name=driver_name)
raid_iface = driver_factory.get_interface(driver, 'raid', raid_iface = driver_factory.get_interface(driver, 'raid',
raid_iface_name) raid_iface_name)
else: else:
raid_iface = getattr(driver, 'raid', None) raid_iface = getattr(driver, 'raid', None)
if not raid_iface:
if not raid_iface: raise exception.UnsupportedDriverExtension(
raise exception.UnsupportedDriverExtension( driver=driver_name, extension='raid')
driver=driver_name, extension='raid')
return raid_iface.get_logical_disk_properties() return raid_iface.get_logical_disk_properties()

View File

@ -158,6 +158,8 @@ class ConductorAPI(object):
:returns: created node object. :returns: created node object.
:raises: InterfaceNotFoundInEntrypoint if validation fails for any :raises: InterfaceNotFoundInEntrypoint if validation fails for any
dynamic interfaces (e.g. network_interface). dynamic interfaces (e.g. network_interface).
:raises: NoValidDefaultForInterface if no default can be calculated
for some interfaces, and explicit values must be provided.
""" """
cctxt = self.client.prepare(topic=topic or self.topic, version='1.36') cctxt = self.client.prepare(topic=topic or self.topic, version='1.36')
return cctxt.call(context, 'create_node', node_obj=node_obj) return cctxt.call(context, 'create_node', node_obj=node_obj)
@ -178,6 +180,8 @@ class ConductorAPI(object):
:param node_obj: a changed (but not saved) node object. :param node_obj: a changed (but not saved) node object.
:param topic: RPC topic. Defaults to self.topic. :param topic: RPC topic. Defaults to self.topic.
:returns: updated node object, including all fields. :returns: updated node object, including all fields.
:raises: NoValidDefaultForInterface if no default can be calculated
for some interfaces, and explicit values must be provided.
""" """
cctxt = self.client.prepare(topic=topic or self.topic, version='1.1') cctxt = self.client.prepare(topic=topic or self.topic, version='1.1')
@ -268,6 +272,9 @@ class ConductorAPI(object):
async task. async task.
:raises: InterfaceNotFoundInEntrypoint if the default interface for a :raises: InterfaceNotFoundInEntrypoint if the default interface for a
hardware type is invalid. hardware type is invalid.
:raises: NoValidDefaultForInterface if no default interface
implementation can be found for this driver's vendor
interface.
:returns: A dictionary containing: :returns: A dictionary containing:
:return: The response of the invoked vendor method :return: The response of the invoked vendor method
@ -311,6 +318,9 @@ class ConductorAPI(object):
:raises: DriverNotFound if the supplied driver is not loaded. :raises: DriverNotFound if the supplied driver is not loaded.
:raises: InterfaceNotFoundInEntrypoint if the default interface for a :raises: InterfaceNotFoundInEntrypoint if the default interface for a
hardware type is invalid. hardware type is invalid.
:raises: NoValidDefaultForInterface if no default interface
implementation can be found for this driver's vendor
interface.
:returns: dictionary of <method name>:<method metadata> entries. :returns: dictionary of <method name>:<method metadata> entries.
""" """
@ -675,6 +685,9 @@ class ConductorAPI(object):
support RAID configuration. support RAID configuration.
:raises: InterfaceNotFoundInEntrypoint if the default interface for a :raises: InterfaceNotFoundInEntrypoint if the default interface for a
hardware type is invalid. hardware type is invalid.
:raises: NoValidDefaultForInterface if no default interface
implementation can be found for this driver's RAID
interface.
:returns: A dictionary containing the properties that can be mentioned :returns: A dictionary containing the properties that can be mentioned
for logical disks and a textual description for them. for logical disks and a textual description for them.
""" """

View File

@ -20,6 +20,7 @@ import abc
import six import six
from ironic.common import exception
from ironic.drivers import base as driver_base from ironic.drivers import base as driver_base
from ironic.drivers.modules.network import noop as noop_net from ironic.drivers.modules.network import noop as noop_net
from ironic.drivers.modules import noop from ironic.drivers.modules import noop
@ -106,9 +107,14 @@ class AbstractHardwareType(object):
properties = {} properties = {}
for iface_type in driver_base.ALL_INTERFACES: for iface_type in driver_base.ALL_INTERFACES:
default_iface = driver_factory.default_interface(self, iface_type) try:
if default_iface is not None: default_iface = driver_factory.default_interface(self,
iface = driver_factory.get_interface(self, iface_type, iface_type)
default_iface) except (exception.InterfaceNotFoundInEntrypoint,
properties.update(iface.get_properties()) exception.NoValidDefaultForInterface):
continue
iface = driver_factory.get_interface(self, iface_type,
default_iface)
properties.update(iface.get_properties())
return properties return properties

View File

@ -310,8 +310,35 @@ class DefaultInterfaceTestCase(db_base.DbTestCase):
# manual-management supports no power interfaces # manual-management supports no power interfaces
self.config(default_power_interface=None) self.config(default_power_interface=None)
self.config(enabled_power_interfaces=[]) self.config(enabled_power_interfaces=[])
iface = driver_factory.default_interface(self.driver, 'power') self.assertRaisesRegex(
self.assertIsNone(iface) exception.NoValidDefaultForInterface,
"For hardware type 'ManualManagementHardware', no default "
"value found for power interface.",
driver_factory.default_interface, self.driver, 'power')
def test_calculated_no_answer_drivername(self):
# manual-management instance (of entry-point driver named 'foo')
# supports no power interfaces
self.config(default_power_interface=None)
self.config(enabled_power_interfaces=[])
self.assertRaisesRegex(
exception.NoValidDefaultForInterface,
"For hardware type 'foo', no default value found for power "
"interface.",
driver_factory.default_interface, self.driver, 'power',
driver_name='foo')
def test_calculated_no_answer_drivername_node(self):
# for a node with manual-management instance (of entry-point driver
# named 'foo'), no default power interface is supported
self.config(default_power_interface=None)
self.config(enabled_power_interfaces=[])
self.assertRaisesRegex(
exception.NoValidDefaultForInterface,
"For node bar with hardware type 'foo', no default "
"value found for power interface.",
driver_factory.default_interface, self.driver, 'power',
driver_name='foo', node='bar')
class TestFakeHardware(hardware_type.AbstractHardwareType): class TestFakeHardware(hardware_type.AbstractHardwareType):
@ -502,6 +529,16 @@ class HardwareTypeLoadTestCase(db_base.DbTestCase):
driver_factory.check_and_update_node_interfaces, driver_factory.check_and_update_node_interfaces,
node) node)
def test_no_raid_interface_no_default(self):
# NOTE(rloo): It doesn't seem possible to not have a default interface
# for storage, so we'll test this case with raid.
self.config(enabled_raid_interfaces=[])
node = obj_utils.get_test_node(self.context, driver='fake-hardware')
self.assertRaisesRegex(
exception.NoValidDefaultForInterface,
"raid interface",
driver_factory.check_and_update_node_interfaces, node)
def _test_enabled_supported_interfaces(self, enable_storage): def _test_enabled_supported_interfaces(self, enable_storage):
ht = fake_hardware.FakeHardware() ht = fake_hardware.FakeHardware()
expected = { expected = {

View File

@ -353,13 +353,16 @@ class RegisterInterfacesTestCase(mgr_utils.ServiceSetUpMixin,
('deploy', ['agent', 'iscsi']), ('deploy', ['agent', 'iscsi']),
)), )),
] ]
default_mock.return_value = None default_mock.side_effect = exception.NoValidDefaultForInterface("boo")
self.assertRaises( self.assertRaises(
exception.NoValidDefaultForInterface, exception.NoValidDefaultForInterface,
self.service._register_and_validate_hardware_interfaces, self.service._register_and_validate_hardware_interfaces,
hardware_types) hardware_types)
default_mock.assert_called_once_with(
hardware_types['fake-hardware'],
mock.ANY, driver_name='fake-hardware')
unreg_mock.assert_called_once_with(mock.ANY) unreg_mock.assert_called_once_with(mock.ANY)
self.assertFalse(reg_mock.called) self.assertFalse(reg_mock.called)

View File

@ -946,6 +946,25 @@ class VendorPassthruTestCase(mgr_utils.ServiceSetUpMixin,
self.context, 'does_not_exist', 'test_method', self.context, 'does_not_exist', 'test_method',
'POST', {}) 'POST', {})
@mock.patch.object(driver_factory, 'default_interface', autospec=True)
def test_driver_vendor_passthru_no_default_interface(self,
mock_def_iface):
self.service.init_host()
# NOTE(rloo): service.init_host() will call
# driver_factory.default_interface() and we want these to
# succeed, so we set the side effect *after* that call.
mock_def_iface.reset_mock()
mock_def_iface.side_effect = exception.NoValidDefaultForInterface('no')
exc = self.assertRaises(messaging.ExpectedException,
self.service.driver_vendor_passthru,
self.context, 'fake-hardware', 'test_method',
'POST', {})
mock_def_iface.assert_called_once_with(mock.ANY, 'vendor',
driver_name='fake-hardware')
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NoValidDefaultForInterface,
exc.exc_info[0])
@mock.patch.object(driver_factory, 'get_interface') @mock.patch.object(driver_factory, 'get_interface')
def _test_get_driver_vendor_passthru_methods(self, is_hw_type, def _test_get_driver_vendor_passthru_methods(self, is_hw_type,
mock_get_if): mock_get_if):
@ -994,6 +1013,25 @@ class VendorPassthruTestCase(mgr_utils.ServiceSetUpMixin,
self.assertEqual(exception.UnsupportedDriverExtension, self.assertEqual(exception.UnsupportedDriverExtension,
exc.exc_info[0]) exc.exc_info[0])
@mock.patch.object(driver_factory, 'default_interface', autospec=True)
def test_get_driver_vendor_passthru_methods_no_default_interface(
self, mock_def_iface):
self.service.init_host()
# NOTE(rloo): service.init_host() will call
# driver_factory.default_interface() and we want these to
# succeed, so we set the side effect *after* that call.
mock_def_iface.reset_mock()
mock_def_iface.side_effect = exception.NoValidDefaultForInterface('no')
exc = self.assertRaises(
messaging.rpc.ExpectedException,
self.service.get_driver_vendor_passthru_methods,
self.context, 'fake-hardware')
mock_def_iface.assert_called_once_with(mock.ANY, 'vendor',
driver_name='fake-hardware')
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.NoValidDefaultForInterface,
exc.exc_info[0])
@mock.patch.object(drivers_base.VendorInterface, 'driver_validate') @mock.patch.object(drivers_base.VendorInterface, 'driver_validate')
def test_driver_vendor_passthru_validation_failed(self, validate_mock): def test_driver_vendor_passthru_validation_failed(self, validate_mock):
validate_mock.side_effect = exception.MissingParameterValue('error') validate_mock.side_effect = exception.MissingParameterValue('error')

View File

@ -12,7 +12,12 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import mock
from ironic.common import driver_factory
from ironic.common import exception
from ironic.conductor import task_manager from ironic.conductor import task_manager
from ironic.drivers import base as driver_base
from ironic.drivers.modules import agent from ironic.drivers.modules import agent
from ironic.drivers.modules import fake from ironic.drivers.modules import fake
from ironic.drivers.modules import inspector from ironic.drivers.modules import inspector
@ -57,3 +62,20 @@ class ManualManagementHardwareTestCase(db_base.DbTestCase):
self.assertIsInstance(task.driver.deploy, agent.AgentDeploy) self.assertIsInstance(task.driver.deploy, agent.AgentDeploy)
self.assertIsInstance(task.driver.inspect, inspector.Inspector) self.assertIsInstance(task.driver.inspect, inspector.Inspector)
self.assertIsInstance(task.driver.raid, agent.AgentRAID) self.assertIsInstance(task.driver.raid, agent.AgentRAID)
def test_get_properties(self):
# These properties are from vendor (agent) and boot (pxe) interfaces
expected_prop_keys = [
'deploy_forces_oob_reboot', 'deploy_kernel', 'deploy_ramdisk']
hardware_type = driver_factory.get_hardware_type("manual-management")
properties = hardware_type.get_properties()
self.assertEqual(sorted(expected_prop_keys), sorted(properties.keys()))
@mock.patch.object(driver_factory, 'default_interface', autospec=True)
def test_get_properties_none(self, mock_def_iface):
hardware_type = driver_factory.get_hardware_type("manual-management")
mock_def_iface.side_effect = exception.NoValidDefaultForInterface("no")
properties = hardware_type.get_properties()
self.assertEqual({}, properties)
self.assertEqual(len(driver_base.ALL_INTERFACES),
mock_def_iface.call_count)