diff --git a/devstack/lib/ironic b/devstack/lib/ironic index b7961a04a9..79707f93d9 100644 --- a/devstack/lib/ironic +++ b/devstack/lib/ironic @@ -587,10 +587,7 @@ function configure_ironic_networks { } function configure_ironic_cleaning_network { - local cleaning_network_uuid - cleaning_network_uuid=$(openstack network show "$IRONIC_CLEAN_NET_NAME" -c id -f value) - die_if_not_set $LINENO cleaning_network_uuid "Failed to get ironic cleaning network id" - iniset $IRONIC_CONF_FILE neutron cleaning_network_uuid ${cleaning_network_uuid} + iniset $IRONIC_CONF_FILE neutron cleaning_network $IRONIC_CLEAN_NET_NAME } function configure_ironic_provision_network { @@ -639,7 +636,7 @@ function configure_ironic_provision_network { sudo ip addr add dev $OVS_PHYSICAL_BRIDGE $ironic_provision_network_ip/$provision_net_prefix fi - iniset $IRONIC_CONF_FILE neutron provisioning_network_uuid $net_id + iniset $IRONIC_CONF_FILE neutron provisioning_network $IRONIC_PROVISION_NETWORK_NAME } function cleanup_ironic_provision_network { diff --git a/doc/source/deploy/multitenancy.rst b/doc/source/deploy/multitenancy.rst index d61940152a..aa4226662b 100644 --- a/doc/source/deploy/multitenancy.rst +++ b/doc/source/deploy/multitenancy.rst @@ -74,14 +74,14 @@ interface as stated above): #. Define a provider network in neutron, which we shall refer to as the "provisioning" network, and add it in under the neutron section in ironic-conductor configuration file. Using ``neutron`` network interface - requires that ``provisioning_network_uuid`` and ``cleaning_network_uuid`` - configuration options are set to a valid neutron network UUIDs, otherwise - ironic-conductor will fail to start:: + requires that ``provisioning_network`` and ``cleaning_network`` + configuration options are set to a valid neutron network UUIDs or names, + otherwise cleaning or provisioning will fail to start:: [neutron] ... - cleaning_network_uuid=$CLEAN_UUID - provisioning_network_uuid=$PROVISION_UUID + cleaning_network=$CLEAN_UUID_OR_NAME + provisioning_network=$PROVISION_UUID_OR_NAME Please refer to `Configure the Bare Metal service for cleaning`_ for more information about cleaning. diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample index d1af2073f1..5ac737230b 100644 --- a/etc/ironic/ironic.conf.sample +++ b/etc/ironic/ironic.conf.sample @@ -1927,17 +1927,20 @@ # PEM encoded client certificate cert file (string value) #certfile = -# Neutron network UUID for the ramdisk to be booted into for -# cleaning nodes. Required for "neutron" network interface. It -# is also required if cleaning nodes when using "flat" network -# interface or "neutron" DHCP provider. (string value) -#cleaning_network_uuid = +# Neutron network UUID or name for the ramdisk to be booted +# into for cleaning nodes. Required for "neutron" network +# interface. It is also required if cleaning nodes when using +# "flat" network interface or "neutron" DHCP provider. If a +# name is provided, it must be unique among all networks or +# cleaning will fail. (string value) +# Deprecated group/name - [neutron]/cleaning_network_uuid +#cleaning_network = # List of Neutron Security Group UUIDs to be applied during # cleaning of the nodes. Optional for the "neutron" network # interface and not used for the "flat" or "noop" network -# interfaces. If not specified, default security -# group is used. (list value) +# interfaces. If not specified, default security group is +# used. (list value) #cleaning_network_security_groups = # Optional domain ID to use with v3 and v2 parameters. It will @@ -1986,14 +1989,16 @@ # Neutron network UUID for the ramdisk to be booted into for # provisioning nodes. Required for "neutron" network -# interface. (string value) -#provisioning_network_uuid = +# interface. If a name is provided, it must be unique among +# all networks or deploy will fail. (string value) +# Deprecated group/name - [neutron]/provisioning_network_uuid +#provisioning_network = # List of Neutron Security Group UUIDs to be applied during # provisioning of the nodes. Optional for the "neutron" # network interface and not used for the "flat" or "noop" -# network interfaces. If not specified, default -# security group is used. (list value) +# network interfaces. If not specified, default security group +# is used. (list value) #provisioning_network_security_groups = # Client retries in the case of a failed request. (integer diff --git a/install-guide/source/configure-cleaning.rst b/install-guide/source/configure-cleaning.rst index d77d44adc5..8dfefec152 100644 --- a/install-guide/source/configure-cleaning.rst +++ b/install-guide/source/configure-cleaning.rst @@ -5,7 +5,7 @@ Configure the Bare Metal service for cleaning .. note:: If you configured the Bare Metal service to use `Node cleaning`_ (which is enabled by default), you will need to set the - ``cleaning_network_uuid`` configuration option. + ``cleaning_network`` configuration option. .. _`Node cleaning`: http://docs.openstack.org/developer/ironic/deploy/cleaning.html#node-cleaning @@ -16,7 +16,7 @@ Configure the Bare Metal service for cleaning $ neutron net-list -#. Configure the cleaning network UUID via the ``cleaning_network_uuid`` +#. Configure the cleaning network UUID via the ``cleaning_network`` option in the Bare Metal service configuration file (``/etc/ironic/ironic.conf``). In the following, replace ``NETWORK_UUID`` with the UUID you noted in the previous step: @@ -24,7 +24,7 @@ Configure the Bare Metal service for cleaning .. code-block:: ini [neutron] - cleaning_network_uuid = NETWORK_UUID + cleaning_network = NETWORK_UUID #. Restart the Bare Metal service's ironic-conductor: diff --git a/ironic/common/neutron.py b/ironic/common/neutron.py index ec62f04a07..3f63c8066e 100644 --- a/ironic/common/neutron.py +++ b/ironic/common/neutron.py @@ -13,6 +13,7 @@ from neutronclient.common import exceptions as neutron_exceptions from neutronclient.v2_0 import client as clientv20 from oslo_log import log +from oslo_utils import uuidutils from ironic.common import exception from ironic.common.i18n import _, _LE, _LI, _LW @@ -286,3 +287,65 @@ def rollback_ports(task, network_uuid): 'Failed to rollback port changes for node %(node)s ' 'on network %(network)s'), {'node': task.node.uuid, 'network': network_uuid}) + + +def validate_network(uuid_or_name, net_type=_('network')): + """Check that the given network is present. + + :param uuid_or_name: network UUID or name + :param net_type: human-readable network type for error messages + :return: network UUID + :raises: MissingParameterValue if uuid_or_name is empty + :raises: NetworkError on failure to contact Neutron + :raises: InvalidParameterValue for missing or duplicated network + """ + if not uuid_or_name: + raise exception.MissingParameterValue( + _('UUID or name of %s is not set in configuration') % net_type) + + if uuidutils.is_uuid_like(uuid_or_name): + filters = {'id': uuid_or_name} + else: + filters = {'name': uuid_or_name} + + try: + client = get_client() + networks = client.list_networks(fields=['id'], **filters) + except neutron_exceptions.NeutronClientException as exc: + raise exception.NetworkError(_('Could not retrieve network list: %s') % + exc) + + LOG.debug('Got list of networks matching %(cond)s: %(result)s', + {'cond': filters, 'result': networks}) + networks = [n['id'] for n in networks.get('networks', [])] + if not networks: + raise exception.InvalidParameterValue( + _('%(type)s with name or UUID %(uuid_or_name)s was not found') % + {'type': net_type, 'uuid_or_name': uuid_or_name}) + elif len(networks) > 1: + raise exception.InvalidParameterValue( + _('More than one %(type)s was found for name %(name)s: %(nets)s') % + {'name': uuid_or_name, 'nets': ', '.join(networks), + 'type': net_type}) + + return networks[0] + + +class NeutronNetworkInterfaceMixin(object): + + _cleaning_network_uuid = None + _provisioning_network_uuid = None + + def get_cleaning_network_uuid(self): + if self._cleaning_network_uuid is None: + self._cleaning_network_uuid = validate_network( + CONF.neutron.cleaning_network, + _('cleaning network')) + return self._cleaning_network_uuid + + def get_provisioning_network_uuid(self): + if self._provisioning_network_uuid is None: + self._provisioning_network_uuid = validate_network( + CONF.neutron.provisioning_network, + _('provisioning network')) + return self._provisioning_network_uuid diff --git a/ironic/conf/neutron.py b/ironic/conf/neutron.py index 44fdf4c762..21d19b8c42 100644 --- a/ironic/conf/neutron.py +++ b/ironic/conf/neutron.py @@ -44,16 +44,20 @@ opts = [ 'neutron. Running neutron in noauth mode (related to ' 'but not affected by this setting) is insecure and ' 'should only be used for testing.')), - cfg.StrOpt('cleaning_network_uuid', - help=_('Neutron network UUID for the ramdisk to be booted ' - 'into for cleaning nodes. Required for "neutron" ' + cfg.StrOpt('cleaning_network', + help=_('Neutron network UUID or name for the ramdisk to be ' + 'booted into for cleaning nodes. Required for "neutron" ' 'network interface. It is also required if cleaning ' 'nodes when using "flat" network interface or "neutron" ' - 'DHCP provider.')), - cfg.StrOpt('provisioning_network_uuid', + 'DHCP provider. If a name is provided, it must be ' + 'unique among all networks or cleaning will fail.'), + deprecated_name='cleaning_network_uuid'), + cfg.StrOpt('provisioning_network', help=_('Neutron network UUID for the ramdisk to be booted ' 'into for provisioning nodes. Required for "neutron" ' - 'network interface.')), + 'network interface. If a name is provided, it must be ' + 'unique among all networks or deploy will fail.'), + deprecated_name='provisioning_network_uuid'), cfg.ListOpt('provisioning_network_security_groups', default=[], help=_('List of Neutron Security Group UUIDs to be ' diff --git a/ironic/drivers/modules/network/flat.py b/ironic/drivers/modules/network/flat.py index ff3c540065..af4b886662 100644 --- a/ironic/drivers/modules/network/flat.py +++ b/ironic/drivers/modules/network/flat.py @@ -16,10 +16,8 @@ Flat network interface. Useful for shared, flat networks. from oslo_config import cfg from oslo_log import log -from oslo_utils import uuidutils -from ironic.common import exception -from ironic.common.i18n import _, _LI, _LW +from ironic.common.i18n import _LI, _LW from ironic.common import neutron from ironic.drivers import base @@ -29,22 +27,32 @@ LOG = log.getLogger(__name__) CONF = cfg.CONF -class FlatNetwork(base.NetworkInterface): +class FlatNetwork(neutron.NeutronNetworkInterfaceMixin, base.NetworkInterface): """Flat network interface.""" def __init__(self): - cleaning_net = CONF.neutron.cleaning_network_uuid + cleaning_net = CONF.neutron.cleaning_network # TODO(vdrok): Switch to DriverLoadError in Ocata - if not uuidutils.is_uuid_like(cleaning_net): + if not cleaning_net: LOG.warning(_LW( - 'Please specify a valid UUID for ' - '[neutron]/cleaning_network_uuid configuration option so that ' + 'Please specify a valid UUID or name for ' + '[neutron]/cleaning_network configuration option so that ' 'this interface is able to perform cleaning. It will be ' 'required starting with the Ocata release, and if not ' 'specified then, the conductor service will fail to start if ' '"flat" is in the list of values for ' '[DEFAULT]enabled_network_interfaces configuration option.')) + def validate(self, task): + """Validates the network interface. + + :param task: a TaskManager instance. + :raises: InvalidParameterValue, if the network interface configuration + is invalid. + :raises: MissingParameterValue, if some parameters are missing. + """ + self.get_cleaning_network_uuid() + def add_provisioning_network(self, task): """Add the provisioning network to a node. @@ -84,15 +92,11 @@ class FlatNetwork(base.NetworkInterface): :returns: a dictionary in the form {port.uuid: neutron_port['id']} :raises: NetworkError, InvalidParameterValue """ - if not uuidutils.is_uuid_like(CONF.neutron.cleaning_network_uuid): - raise exception.InvalidParameterValue(_( - 'You must provide a valid cleaning network UUID in ' - '[neutron]cleaning_network_uuid configuration option.')) # If we have left over ports from a previous cleaning, remove them - neutron.rollback_ports(task, CONF.neutron.cleaning_network_uuid) + neutron.rollback_ports(task, self.get_cleaning_network_uuid()) LOG.info(_LI('Adding cleaning network to node %s'), task.node.uuid) vifs = neutron.add_ports_to_network( - task, CONF.neutron.cleaning_network_uuid, is_flat=True) + task, self.get_cleaning_network_uuid(), is_flat=True) for port in task.ports: if port.uuid in vifs: internal_info = port.internal_info @@ -109,8 +113,8 @@ class FlatNetwork(base.NetworkInterface): """ LOG.info(_LI('Removing ports from cleaning network for node %s'), task.node.uuid) - neutron.remove_ports_from_network( - task, CONF.neutron.cleaning_network_uuid) + neutron.remove_ports_from_network(task, + self.get_cleaning_network_uuid()) for port in task.ports: if 'cleaning_vif_port_id' in port.internal_info: internal_info = port.internal_info diff --git a/ironic/drivers/modules/network/neutron.py b/ironic/drivers/modules/network/neutron.py index 301537e720..82918e114d 100644 --- a/ironic/drivers/modules/network/neutron.py +++ b/ironic/drivers/modules/network/neutron.py @@ -17,7 +17,6 @@ from neutronclient.common import exceptions as neutron_exceptions from oslo_config import cfg from oslo_log import log -from oslo_utils import uuidutils from ironic.common import exception from ironic.common.i18n import _, _LI @@ -30,25 +29,36 @@ LOG = log.getLogger(__name__) CONF = cfg.CONF -class NeutronNetwork(base.NetworkInterface): +class NeutronNetwork(neutron.NeutronNetworkInterfaceMixin, + base.NetworkInterface): """Neutron v2 network interface""" def __init__(self): failures = [] - cleaning_net = CONF.neutron.cleaning_network_uuid - if not uuidutils.is_uuid_like(cleaning_net): - failures.append('cleaning_network_uuid=%s' % cleaning_net) + cleaning_net = CONF.neutron.cleaning_network + if not cleaning_net: + failures.append('cleaning_network') - provisioning_net = CONF.neutron.provisioning_network_uuid - if not uuidutils.is_uuid_like(provisioning_net): - failures.append('provisioning_network_uuid=%s' % provisioning_net) + provisioning_net = CONF.neutron.provisioning_network + if not provisioning_net: + failures.append('provisioning_network') if failures: raise exception.DriverLoadError( driver=self.__class__.__name__, reason=(_('The following [neutron] group configuration ' - 'options are incorrect, they must be valid UUIDs: ' - '%s') % ', '.join(failures))) + 'options are missing: %s') % ', '.join(failures))) + + def validate(self, task): + """Validates the network interface. + + :param task: a TaskManager instance. + :raises: InvalidParameterValue, if the network interface configuration + is invalid. + :raises: MissingParameterValue, if some parameters are missing. + """ + self.get_cleaning_network_uuid() + self.get_provisioning_network_uuid() def add_provisioning_network(self, task): """Add the provisioning network to a node. @@ -58,11 +68,11 @@ class NeutronNetwork(base.NetworkInterface): """ # If we have left over ports from a previous provision attempt, remove # them - neutron.rollback_ports(task, CONF.neutron.provisioning_network_uuid) + neutron.rollback_ports(task, self.get_provisioning_network_uuid()) LOG.info(_LI('Adding provisioning network to node %s'), task.node.uuid) vifs = neutron.add_ports_to_network( - task, CONF.neutron.provisioning_network_uuid, + task, self.get_provisioning_network_uuid(), security_groups=CONF.neutron.provisioning_network_security_groups) for port in task.ports: if port.uuid in vifs: @@ -80,7 +90,7 @@ class NeutronNetwork(base.NetworkInterface): LOG.info(_LI('Removing provisioning network from node %s'), task.node.uuid) neutron.remove_ports_from_network( - task, CONF.neutron.provisioning_network_uuid) + task, self.get_provisioning_network_uuid()) for port in task.ports: if 'provisioning_vif_port_id' in port.internal_info: internal_info = port.internal_info @@ -96,11 +106,11 @@ class NeutronNetwork(base.NetworkInterface): :returns: a dictionary in the form {port.uuid: neutron_port['id']} """ # If we have left over ports from a previous cleaning, remove them - neutron.rollback_ports(task, CONF.neutron.cleaning_network_uuid) + neutron.rollback_ports(task, self.get_cleaning_network_uuid()) LOG.info(_LI('Adding cleaning network to node %s'), task.node.uuid) security_groups = CONF.neutron.cleaning_network_security_groups vifs = neutron.add_ports_to_network(task, - CONF.neutron.cleaning_network_uuid, + self.get_cleaning_network_uuid(), security_groups=security_groups) for port in task.ports: if port.uuid in vifs: @@ -118,8 +128,8 @@ class NeutronNetwork(base.NetworkInterface): """ LOG.info(_LI('Removing cleaning network from node %s'), task.node.uuid) - neutron.remove_ports_from_network( - task, CONF.neutron.cleaning_network_uuid) + neutron.remove_ports_from_network(task, + self.get_cleaning_network_uuid()) for port in task.ports: if 'cleaning_vif_port_id' in port.internal_info: internal_info = port.internal_info diff --git a/ironic/tests/base.py b/ironic/tests/base.py index 50a4a6ee3b..f0afb5f207 100644 --- a/ironic/tests/base.py +++ b/ironic/tests/base.py @@ -119,9 +119,9 @@ class TestCase(testtools.TestCase): self.config(use_stderr=False, fatal_exception_format_errors=True, tempdir=tempfile.tempdir) - self.config(cleaning_network_uuid=uuidutils.generate_uuid(), + self.config(cleaning_network=uuidutils.generate_uuid(), group='neutron') - self.config(provisioning_network_uuid=uuidutils.generate_uuid(), + self.config(provisioning_network=uuidutils.generate_uuid(), group='neutron') self.config(enabled_drivers=['fake']) self.config(enabled_network_interfaces=['flat', 'noop', 'neutron'], diff --git a/ironic/tests/unit/common/test_neutron.py b/ironic/tests/unit/common/test_neutron.py index 99448eb1ab..bcaefa21fd 100644 --- a/ironic/tests/unit/common/test_neutron.py +++ b/ironic/tests/unit/common/test_neutron.py @@ -447,3 +447,69 @@ class TestNeutronNetworkActions(db_base.DbTestCase): with task_manager.acquire(self.context, self.node.uuid) as task: neutron.rollback_ports(task, self.network_uuid) self.assertTrue(log_mock.exception.called) + + +@mock.patch.object(neutron, 'get_client', autospec=True) +class TestValidateNetwork(base.TestCase): + def setUp(self): + super(TestValidateNetwork, self).setUp() + + self.uuid = uuidutils.generate_uuid() + + def test_by_uuid(self, client_mock): + net_mock = client_mock.return_value.list_networks + net_mock.return_value = { + 'networks': [ + {'id': self.uuid}, + ] + } + + self.assertEqual(self.uuid, neutron.validate_network(self.uuid)) + net_mock.assert_called_once_with(fields=['id'], + id=self.uuid) + + def test_by_name(self, client_mock): + net_mock = client_mock.return_value.list_networks + net_mock.return_value = { + 'networks': [ + {'id': self.uuid}, + ] + } + + self.assertEqual(self.uuid, neutron.validate_network('name')) + net_mock.assert_called_once_with(fields=['id'], + name='name') + + def test_not_found(self, client_mock): + net_mock = client_mock.return_value.list_networks + net_mock.return_value = { + 'networks': [] + } + + self.assertRaisesRegex(exception.InvalidParameterValue, + 'was not found', + neutron.validate_network, self.uuid) + net_mock.assert_called_once_with(fields=['id'], + id=self.uuid) + + def test_failure(self, client_mock): + net_mock = client_mock.return_value.list_networks + net_mock.side_effect = neutron_client_exc.NeutronClientException('foo') + + self.assertRaisesRegex(exception.NetworkError, 'foo', + neutron.validate_network, 'name') + net_mock.assert_called_once_with(fields=['id'], + name='name') + + def test_duplicate(self, client_mock): + net_mock = client_mock.return_value.list_networks + net_mock.return_value = { + 'networks': [{'id': self.uuid}, + {'id': 'uuid2'}] + } + + self.assertRaisesRegex(exception.InvalidParameterValue, + 'More than one network', + neutron.validate_network, 'name') + net_mock.assert_called_once_with(fields=['id'], + name='name') diff --git a/ironic/tests/unit/conductor/test_manager.py b/ironic/tests/unit/conductor/test_manager.py index 7bf875cb56..39d9c3d312 100644 --- a/ironic/tests/unit/conductor/test_manager.py +++ b/ironic/tests/unit/conductor/test_manager.py @@ -2579,7 +2579,8 @@ class MiscTestCase(mgr_utils.ServiceSetUpMixin, mgr_utils.CommonMixIn, target_raid_config = {'logical_disks': [{'size_gb': 1, 'raid_level': '1'}]} node = obj_utils.create_test_node( - self.context, driver='fake', target_raid_config=target_raid_config) + self.context, driver='fake', target_raid_config=target_raid_config, + network_interface='noop') ret = self.service.validate_driver_interfaces(self.context, node.uuid) expected = {'console': {'result': True}, @@ -2596,7 +2597,8 @@ class MiscTestCase(mgr_utils.ServiceSetUpMixin, mgr_utils.CommonMixIn, @mock.patch.object(images, 'is_whole_disk_image') def test_validate_driver_interfaces_validation_fail(self, mock_iwdi): mock_iwdi.return_value = False - node = obj_utils.create_test_node(self.context, driver='fake') + node = obj_utils.create_test_node(self.context, driver='fake', + network_interface='noop') with mock.patch( 'ironic.drivers.modules.fake.FakeDeploy.validate' ) as deploy: diff --git a/ironic/tests/unit/dhcp/test_neutron.py b/ironic/tests/unit/dhcp/test_neutron.py index 00ef5f874f..ce300519af 100644 --- a/ironic/tests/unit/dhcp/test_neutron.py +++ b/ironic/tests/unit/dhcp/test_neutron.py @@ -37,7 +37,7 @@ class TestNeutron(db_base.DbTestCase): super(TestNeutron, self).setUp() mgr_utils.mock_the_extension_manager(driver='fake') self.config( - cleaning_network_uuid='00000000-0000-0000-0000-000000000000', + cleaning_network='00000000-0000-0000-0000-000000000000', group='neutron') self.config(enabled_drivers=['fake']) self.config(dhcp_provider='neutron', diff --git a/ironic/tests/unit/drivers/modules/network/test_flat.py b/ironic/tests/unit/drivers/modules/network/test_flat.py index 19d3cb053a..240da257dc 100644 --- a/ironic/tests/unit/drivers/modules/network/test_flat.py +++ b/ironic/tests/unit/drivers/modules/network/test_flat.py @@ -14,7 +14,6 @@ import mock from oslo_config import cfg from oslo_utils import uuidutils -from ironic.common import exception from ironic.common import neutron from ironic.conductor import task_manager from ironic.drivers.modules.network import flat as flat_interface @@ -40,43 +39,48 @@ class TestFlatInterface(db_base.DbTestCase): @mock.patch.object(flat_interface, 'LOG') def test_init_incorrect_cleaning_net(self, mock_log): - self.config(cleaning_network_uuid=None, group='neutron') + self.config(cleaning_network=None, group='neutron') flat_interface.FlatNetwork() self.assertTrue(mock_log.warning.called) + @mock.patch.object(neutron, 'validate_network', autospec=True) + def test_validate(self, validate_mock): + with task_manager.acquire(self.context, self.node.id) as task: + self.interface.validate(task) + validate_mock.assert_called_once_with(CONF.neutron.cleaning_network, + 'cleaning network') + + @mock.patch.object(neutron, 'validate_network', + side_effect=lambda n, t: n) @mock.patch.object(neutron, 'add_ports_to_network') @mock.patch.object(neutron, 'rollback_ports') - def test_add_cleaning_network(self, rollback_mock, add_mock): + def test_add_cleaning_network(self, rollback_mock, add_mock, + validate_mock): add_mock.return_value = {self.port.uuid: 'vif-port-id'} with task_manager.acquire(self.context, self.node.id) as task: self.interface.add_cleaning_network(task) rollback_mock.assert_called_once_with( - task, CONF.neutron.cleaning_network_uuid) + task, CONF.neutron.cleaning_network) add_mock.assert_called_once_with( - task, CONF.neutron.cleaning_network_uuid, is_flat=True) + task, CONF.neutron.cleaning_network, is_flat=True) + validate_mock.assert_called_once_with( + CONF.neutron.cleaning_network, + 'cleaning network') self.port.refresh() self.assertEqual('vif-port-id', self.port.internal_info['cleaning_vif_port_id']) - @mock.patch.object(neutron, 'add_ports_to_network') - @mock.patch.object(neutron, 'rollback_ports') - def test_add_cleaning_network_no_cleaning_net_uuid(self, rollback_mock, - add_mock): - with task_manager.acquire(self.context, self.node.id) as task: - # This has to go after acquire, or acquire will raise - # DriverLoadError. - self.config(cleaning_network_uuid='abc', group='neutron') - self.assertRaises(exception.InvalidParameterValue, - self.interface.add_cleaning_network, task) - self.assertFalse(rollback_mock.called) - self.assertFalse(add_mock.called) - + @mock.patch.object(neutron, 'validate_network', + side_effect=lambda n, t: n) @mock.patch.object(neutron, 'remove_ports_from_network') - def test_remove_cleaning_network(self, remove_mock): + def test_remove_cleaning_network(self, remove_mock, validate_mock): with task_manager.acquire(self.context, self.node.id) as task: self.interface.remove_cleaning_network(task) remove_mock.assert_called_once_with( - task, CONF.neutron.cleaning_network_uuid) + task, CONF.neutron.cleaning_network) + validate_mock.assert_called_once_with( + CONF.neutron.cleaning_network, + 'cleaning network') self.port.refresh() self.assertNotIn('cleaning_vif_port_id', self.port.internal_info) diff --git a/ironic/tests/unit/drivers/modules/network/test_neutron.py b/ironic/tests/unit/drivers/modules/network/test_neutron.py index 33b15030d3..22742a6d84 100644 --- a/ironic/tests/unit/drivers/modules/network/test_neutron.py +++ b/ironic/tests/unit/drivers/modules/network/test_neutron.py @@ -47,30 +47,48 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): 'mac_address': '52:54:00:cf:2d:32'} def test_init_incorrect_provisioning_net(self): - self.config(provisioning_network_uuid=None, group='neutron') + self.config(provisioning_network=None, group='neutron') self.assertRaises(exception.DriverLoadError, neutron.NeutronNetwork) - self.config(provisioning_network_uuid=uuidutils.generate_uuid(), + self.config(provisioning_network=uuidutils.generate_uuid(), group='neutron') - self.config(cleaning_network_uuid='asdf', group='neutron') + self.config(cleaning_network=None, group='neutron') self.assertRaises(exception.DriverLoadError, neutron.NeutronNetwork) + @mock.patch.object(neutron_common, 'validate_network', autospec=True) + def test_validate(self, validate_mock): + with task_manager.acquire(self.context, self.node.id) as task: + self.interface.validate(task) + self.assertEqual([mock.call(CONF.neutron.cleaning_network, + 'cleaning network'), + mock.call(CONF.neutron.provisioning_network, + 'provisioning network')], + validate_mock.call_args_list) + + @mock.patch.object(neutron_common, 'validate_network', + side_effect=lambda n, t: n) @mock.patch.object(neutron_common, 'rollback_ports') @mock.patch.object(neutron_common, 'add_ports_to_network') - def test_add_provisioning_network(self, add_ports_mock, rollback_mock): + def test_add_provisioning_network(self, add_ports_mock, rollback_mock, + validate_mock): self.port.internal_info = {'provisioning_vif_port_id': 'vif-port-id'} self.port.save() add_ports_mock.return_value = {self.port.uuid: self.neutron_port['id']} with task_manager.acquire(self.context, self.node.id) as task: self.interface.add_provisioning_network(task) rollback_mock.assert_called_once_with( - task, CONF.neutron.provisioning_network_uuid) + task, CONF.neutron.provisioning_network) add_ports_mock.assert_called_once_with( - task, CONF.neutron.provisioning_network_uuid, + task, CONF.neutron.provisioning_network, security_groups=[]) + validate_mock.assert_called_once_with( + CONF.neutron.provisioning_network, + 'provisioning network') self.port.refresh() self.assertEqual(self.neutron_port['id'], self.port.internal_info['provisioning_vif_port_id']) + @mock.patch.object(neutron_common, 'validate_network', + lambda n, t: n) @mock.patch.object(neutron_common, 'rollback_ports') @mock.patch.object(neutron_common, 'add_ports_to_network') def test_add_provisioning_network_with_sg(self, add_ports_mock, @@ -85,39 +103,53 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): with task_manager.acquire(self.context, self.node.id) as task: self.interface.add_provisioning_network(task) rollback_mock.assert_called_once_with( - task, CONF.neutron.provisioning_network_uuid) + task, CONF.neutron.provisioning_network) add_ports_mock.assert_called_once_with( - task, CONF.neutron.provisioning_network_uuid, + task, CONF.neutron.provisioning_network, security_groups=( CONF.neutron.provisioning_network_security_groups)) self.port.refresh() self.assertEqual(self.neutron_port['id'], self.port.internal_info['provisioning_vif_port_id']) + @mock.patch.object(neutron_common, 'validate_network', + side_effect=lambda n, t: n) @mock.patch.object(neutron_common, 'remove_ports_from_network') - def test_remove_provisioning_network(self, remove_ports_mock): + def test_remove_provisioning_network(self, remove_ports_mock, + validate_mock): self.port.internal_info = {'provisioning_vif_port_id': 'vif-port-id'} self.port.save() with task_manager.acquire(self.context, self.node.id) as task: self.interface.remove_provisioning_network(task) remove_ports_mock.assert_called_once_with( - task, CONF.neutron.provisioning_network_uuid) + task, CONF.neutron.provisioning_network) + validate_mock.assert_called_once_with( + CONF.neutron.provisioning_network, + 'provisioning network') self.port.refresh() self.assertNotIn('provisioning_vif_port_id', self.port.internal_info) + @mock.patch.object(neutron_common, 'validate_network', + side_effect=lambda n, t: n) @mock.patch.object(neutron_common, 'rollback_ports') @mock.patch.object(neutron_common, 'add_ports_to_network') - def test_add_cleaning_network(self, add_ports_mock, rollback_mock): + def test_add_cleaning_network(self, add_ports_mock, rollback_mock, + validate_mock): add_ports_mock.return_value = {self.port.uuid: self.neutron_port['id']} with task_manager.acquire(self.context, self.node.id) as task: res = self.interface.add_cleaning_network(task) rollback_mock.assert_called_once_with( - task, CONF.neutron.cleaning_network_uuid) + task, CONF.neutron.cleaning_network) self.assertEqual(res, add_ports_mock.return_value) + validate_mock.assert_called_once_with( + CONF.neutron.cleaning_network, + 'cleaning network') self.port.refresh() self.assertEqual(self.neutron_port['id'], self.port.internal_info['cleaning_vif_port_id']) + @mock.patch.object(neutron_common, 'validate_network', + lambda n, t: n) @mock.patch.object(neutron_common, 'rollback_ports') @mock.patch.object(neutron_common, 'add_ports_to_network') def test_add_cleaning_network_with_sg(self, add_ports_mock, rollback_mock): @@ -129,23 +161,29 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): with task_manager.acquire(self.context, self.node.id) as task: res = self.interface.add_cleaning_network(task) add_ports_mock.assert_called_once_with( - task, CONF.neutron.cleaning_network_uuid, + task, CONF.neutron.cleaning_network, security_groups=CONF.neutron.cleaning_network_security_groups) rollback_mock.assert_called_once_with( - task, CONF.neutron.cleaning_network_uuid) + task, CONF.neutron.cleaning_network) self.assertEqual(res, add_ports_mock.return_value) self.port.refresh() self.assertEqual(self.neutron_port['id'], self.port.internal_info['cleaning_vif_port_id']) + @mock.patch.object(neutron_common, 'validate_network', + side_effect=lambda n, t: n) @mock.patch.object(neutron_common, 'remove_ports_from_network') - def test_remove_cleaning_network(self, remove_ports_mock): + def test_remove_cleaning_network(self, remove_ports_mock, + validate_mock): self.port.internal_info = {'cleaning_vif_port_id': 'vif-port-id'} self.port.save() with task_manager.acquire(self.context, self.node.id) as task: self.interface.remove_cleaning_network(task) remove_ports_mock.assert_called_once_with( - task, CONF.neutron.cleaning_network_uuid) + task, CONF.neutron.cleaning_network) + validate_mock.assert_called_once_with( + CONF.neutron.cleaning_network, + 'cleaning network') self.port.refresh() self.assertNotIn('cleaning_vif_port_id', self.port.internal_info) diff --git a/releasenotes/notes/net-names-b8a36aa30659ce2f.yaml b/releasenotes/notes/net-names-b8a36aa30659ce2f.yaml new file mode 100644 index 0000000000..1bbe44246f --- /dev/null +++ b/releasenotes/notes/net-names-b8a36aa30659ce2f.yaml @@ -0,0 +1,11 @@ +--- +features: + - Names can now be used instead of UUIDs for "cleaning_network" and + "provisioning_network" options (former "cleaning_network_uuid" and + "provisioning_network_uuid"). Care has to be taken to ensure that the + names are unique among all networks in this case. Note that mapping between + a name and a UUID is cached for the lifetime of the conductor. +deprecations: + - Configuration options "[neutron]cleaning_network_uuid" and + "[neutron]provisioning_network_uuid" were deprecated in favor of new + "[neutron]cleaning_network" and "[neutron]provisioning_network".