diff --git a/etc/ironic/ironic.conf.sample b/etc/ironic/ironic.conf.sample index 03eb8b5308..e14443de9f 100644 --- a/etc/ironic/ironic.conf.sample +++ b/etc/ironic/ironic.conf.sample @@ -7,7 +7,9 @@ # Authentication strategy used by ironic-api. "noauth" should # not be used in a production environment because all # authentication will be disabled. (string value) -# Allowed values: noauth, keystone +# Possible values: +# noauth - +# keystone - #auth_strategy = keystone # Return server tracebacks in the API response for any error @@ -362,7 +364,12 @@ # Specifies the minimum level for which to send notifications. # If not set, no notifications will be sent. The default is # for this option to be unset. (string value) -# Allowed values: debug, info, warning, error, critical +# Possible values: +# debug - +# info - +# warning - +# error - +# critical - #notification_level = # Directory where the ironic python module is installed. @@ -397,7 +404,13 @@ # doing a rolling upgrade from version N to version N+1, set # (to pin) this to N. To unpin (default), leave it unset and # the latest versions will be used. (string value) -# Allowed values: pike, 9.2, 9.1, 9.0, 8.0, 10.0 +# Possible values: +# pike - +# 9.2 - +# 9.1 - +# 9.0 - +# 8.0 - +# 10.0 - #pin_release_version = # Path to the rootwrap configuration file to use for running @@ -554,7 +567,10 @@ #rpc_zmq_bind_address = * # MatchMaker driver. (string value) -# Allowed values: redis, sentinel, dummy +# Possible values: +# redis - +# sentinel - +# dummy - #rpc_zmq_matchmaker = redis # Number of ZeroMQ contexts, defaults to 1. (integer value) @@ -630,7 +646,9 @@ # Default serialization mechanism for # serializing/deserializing outgoing/incoming messages (string # value) -# Allowed values: json, msgpack +# Possible values: +# json - +# msgpack - #rpc_zmq_serialization = json # This option configures round-robin mode in zmq socket. True @@ -821,12 +839,17 @@ # Whether Ironic should collect the deployment logs on # deployment failure (on_failure), always or never. (string # value) -# Allowed values: always, on_failure, never +# Possible values: +# always - +# on_failure - +# never - #deploy_logs_collect = on_failure # The name of the storage backend where the logs will be # stored. (string value) -# Allowed values: local, swift +# Possible values: +# local - +# swift - #deploy_logs_storage_backend = local # The path to the directory where the logs should be stored, @@ -1596,7 +1619,9 @@ # but it will be changed to "local" in the future. It is # recommended to set an explicit value for this option. # (string value) -# Allowed values: netboot, local +# Possible values: +# netboot - +# local - #default_boot_option = # Whether to upload the config drive to object store. Set this @@ -1607,7 +1632,9 @@ # Type of object store endpoint type to be used as a backend # (string value) -# Allowed values: swift, radosgw +# Possible values: +# swift - +# radosgw - # Deprecated group/name - [glance]/temp_url_endpoint_type #object_store_endpoint_type = swift @@ -1694,7 +1721,9 @@ # DEPRECATED: Authentication strategy to use when connecting # to glance. (string value) -# Allowed values: keystone, noauth +# Possible values: +# keystone - +# noauth - # This option is deprecated for removal. # Its value may be silently ignored in the future. # Reason: To configure glance in noauth mode, set @@ -1999,7 +2028,10 @@ # for backward compatibility. When "auto" is specified, # default boot mode will be selected based on boot mode # settings on the system. (string value) -# Allowed values: auto, bios, uefi +# Possible values: +# auto - +# bios - +# uefi - #default_boot_mode = auto @@ -2191,7 +2223,9 @@ #remote_image_server = # Share type of virtual media (string value) -# Allowed values: CIFS, NFS +# Possible values: +# CIFS - +# NFS - #remote_image_share_type = CIFS # share name of remote_image_server (string value) @@ -2209,23 +2243,32 @@ # Port to be used for iRMC operations (port value) # Minimum value: 0 # Maximum value: 65535 -# Allowed values: 443, 80 +# Possible values: +# 443 - +# 80 - #port = 443 # Authentication method to be used for iRMC operations (string # value) -# Allowed values: basic, digest +# Possible values: +# basic - +# digest - #auth_method = basic # Timeout (in seconds) for iRMC operations (integer value) #client_timeout = 60 # Sensor data retrieval method. (string value) -# Allowed values: ipmitool, scci +# Possible values: +# ipmitool - +# scci - #sensor_method = ipmitool # SNMP protocol version (string value) -# Allowed values: v1, v2c, v3 +# Possible values: +# v1 - +# v2c - +# v3 - #snmp_version = v2c # SNMP port (port value) @@ -2416,7 +2459,10 @@ # token data is encrypted and authenticated in the cache. If # the value is not one of these options or empty, auth_token # will raise an exception on initialization. (string value) -# Allowed values: None, MAC, ENCRYPT +# Possible values: +# None - +# MAC - +# ENCRYPT - #memcache_security_strategy = None # (Optional, mandatory if memcache_security_strategy is @@ -2603,7 +2649,9 @@ # # Backend to use for the metrics system. (string value) -# Allowed values: noop, statsd +# Possible values: +# noop - +# statsd - #backend = noop # Prepend the hostname to all metric names. The format of @@ -2667,7 +2715,9 @@ # to neutron. Running neutron in noauth mode (related to but # not affected by this setting) is insecure and should only be # used for testing. (string value) -# Allowed values: keystone, noauth +# Possible values: +# keystone - +# noauth - # This option is deprecated for removal. # Its value may be silently ignored in the future. # Reason: To configure neutron for noauth mode, set @@ -2782,6 +2832,25 @@ # value) #region_name = +# Neutron network UUID or name for booting the ramdisk for +# rescue mode. This is not the network that the rescue ramdisk +# will use post-boot -- the tenant network is used for that. +# Required for "neutron" network interface, if rescue mode +# will be used. It is not used for the "flat" or "noop" +# network interfaces. If a name is provided, it must be unique +# among all networks or rescue will fail. This option is part +# of rescue feature work, which is not currently exposed to +# users. (string value) +#rescuing_network = + +# List of Neutron Security Group UUIDs to be applied during +# the node rescue process. Optional for the "neutron" network +# interface and not used for the "flat" or "noop" network +# interfaces. If not specified, the default security group is +# used. This option is part of rescue feature work, which is +# not currently exposed to users. (list value) +#rescuing_network_security_groups = + # Client retries in the case of a failed request. (integer # value) #retries = 3 @@ -3164,15 +3233,24 @@ # value) #kafka_consumer_timeout = 1.0 -# Pool Size for Kafka Consumers (integer value) +# DEPRECATED: Pool Size for Kafka Consumers (integer value) +# This option is deprecated for removal. +# Its value may be silently ignored in the future. +# Reason: Driver no longer uses connection pool. #pool_size = 10 -# The pool size limit for connections expiration policy -# (integer value) +# DEPRECATED: The pool size limit for connections expiration +# policy (integer value) +# This option is deprecated for removal. +# Its value may be silently ignored in the future. +# Reason: Driver no longer uses connection pool. #conn_pool_min_size = 2 -# The time-to-live in sec of idle connections in the pool -# (integer value) +# DEPRECATED: The time-to-live in sec of idle connections in +# the pool (integer value) +# This option is deprecated for removal. +# Its value may be silently ignored in the future. +# Reason: Driver no longer uses connection pool. #conn_pool_ttl = 1200 # Group id for Kafka consumer. Consumers in one group will @@ -3271,7 +3349,9 @@ # one we are currently connected to becomes unavailable. Takes # effect only if more than one RabbitMQ node is provided in # config. (string value) -# Allowed values: round-robin, shuffle +# Possible values: +# round-robin - +# shuffle - #kombu_failover_strategy = round-robin # DEPRECATED: The RabbitMQ broker address where a single node @@ -3310,7 +3390,10 @@ #rabbit_password = guest # The RabbitMQ login method. (string value) -# Allowed values: PLAIN, AMQPLAIN, RABBIT-CR-DEMO +# Possible values: +# PLAIN - +# AMQPLAIN - +# RABBIT-CR-DEMO - #rabbit_login_method = AMQPLAIN # DEPRECATED: The RabbitMQ virtual host. (string value) @@ -3397,7 +3480,10 @@ #host_connection_reconnect_delay = 0.25 # Connection factory implementation (string value) -# Allowed values: new, single, read_write +# Possible values: +# new - +# single - +# read_write - #connection_factory = single # Maximum number of connections to keep queued. (integer @@ -3425,7 +3511,9 @@ # Default serialization mechanism for # serializing/deserializing outgoing/incoming messages (string # value) -# Allowed values: json, msgpack +# Possible values: +# json - +# msgpack - #default_serializer_type = json # Persist notification messages. (boolean value) @@ -3497,7 +3585,10 @@ #rpc_zmq_bind_address = * # MatchMaker driver. (string value) -# Allowed values: redis, sentinel, dummy +# Possible values: +# redis - +# sentinel - +# dummy - #rpc_zmq_matchmaker = redis # Number of ZeroMQ contexts, defaults to 1. (integer value) @@ -3573,7 +3664,9 @@ # Default serialization mechanism for # serializing/deserializing outgoing/incoming messages (string # value) -# Allowed values: json, msgpack +# Possible values: +# json - +# msgpack - #rpc_zmq_serialization = json # This option configures round-robin mode in zmq socket. True @@ -3682,7 +3775,10 @@ # Content Type to send and receive data for REST based policy # check (string value) -# Allowed values: application/x-www-form-urlencoded, application/json +# Possible values: +# application/x-www-form-urlencoded - +# application/json - #remote_content_type = application/x-www-form-urlencoded # server identity verification for REST based policy check @@ -3914,7 +4010,9 @@ # The IP version that will be used for PXE booting. Defaults # to 4. EXPERIMENTAL (string value) -# Allowed values: 4, 6 +# Possible values: +# 4 - +# 6 - #ip_version = 4 # Download deploy images directly from swift using temporary diff --git a/ironic/common/neutron.py b/ironic/common/neutron.py index aa30c0a50f..aecf73591c 100644 --- a/ironic/common/neutron.py +++ b/ironic/common/neutron.py @@ -566,6 +566,7 @@ class NeutronNetworkInterfaceMixin(object): _cleaning_network_uuid = None _provisioning_network_uuid = None + _rescuing_network_uuid = None def get_cleaning_network_uuid(self, context=None): if self._cleaning_network_uuid is None: @@ -580,3 +581,12 @@ class NeutronNetworkInterfaceMixin(object): CONF.neutron.provisioning_network, _('provisioning network'), context=context) return self._provisioning_network_uuid + + def get_rescuing_network_uuid(self, context=None): + # TODO(stendulker): FlatNetwork should not use this method. + # FlatNetwork uses tenant network for rescue operation. + if self._rescuing_network_uuid is None: + self._rescuing_network_uuid = validate_network( + CONF.neutron.rescuing_network, + _('rescuing network'), context=context) + return self._rescuing_network_uuid diff --git a/ironic/conf/neutron.py b/ironic/conf/neutron.py index 86d0fca1b4..1d1227cd40 100644 --- a/ironic/conf/neutron.py +++ b/ironic/conf/neutron.py @@ -90,6 +90,26 @@ opts = [ 'used for the "flat" or "noop" network interfaces. ' 'If not specified, default security group ' 'is used.')), + cfg.StrOpt('rescuing_network', + help=_('Neutron network UUID or name for booting the ramdisk ' + 'for rescue mode. This is not the network that the ' + 'rescue ramdisk will use post-boot -- the tenant ' + 'network is used for that. Required for "neutron" ' + 'network interface, if rescue mode will be used. It ' + 'is not used for the "flat" or "noop" network ' + 'interfaces. If a name is provided, it must be unique ' + 'among all networks or rescue will fail. This option ' + 'is part of rescue feature work, which is not currently ' + 'exposed to users.')), + cfg.ListOpt('rescuing_network_security_groups', + default=[], + help=_('List of Neutron Security Group UUIDs to be applied ' + 'during the node rescue process. Optional for the ' + '"neutron" network interface and not used for the ' + '"flat" or "noop" network interfaces. If not ' + 'specified, the default security group is used. This ' + 'option is part of rescue feature work, which is ' + 'not currently exposed to users.')), ] diff --git a/ironic/drivers/base.py b/ironic/drivers/base.py index 1f1e4d2b93..ab00736ecd 100644 --- a/ironic/drivers/base.py +++ b/ironic/drivers/base.py @@ -1036,8 +1036,9 @@ class NetworkInterface(BaseInterface): """Returns the currently used VIF associated with port or portgroup We are booting the node only in one network at a time, and presence of - cleaning_vif_port_id means we're doing cleaning, of - provisioning_vif_port_id - provisioning. + cleaning_vif_port_id means we're doing cleaning, + of provisioning_vif_port_id - provisioning, + of rescuing_vif_port_id - rescuing. Otherwise it's a tenant network. :param task: A TaskManager instance. @@ -1092,6 +1093,28 @@ class NetworkInterface(BaseInterface): :raises: NetworkError """ + def add_rescuing_network(self, task): + """Add the rescuing network to the node. + + :param task: A TaskManager instance. + :returns: a dictionary in the form {port.uuid: neutron_port['id']} + :raises: NetworkError + :raises: InvalidParameterValue, if the network interface configuration + is invalid. + """ + return {} + + def remove_rescuing_network(self, task): + """Removes the rescuing network from a node. + + :param task: A TaskManager instance. + :raises: NetworkError + :raises: InvalidParameterValue, if the network interface configuration + is invalid. + :raises: MissingParameterValue, if some parameters are missing. + """ + pass + @six.add_metaclass(abc.ABCMeta) class StorageInterface(BaseInterface): diff --git a/ironic/drivers/modules/network/common.py b/ironic/drivers/modules/network/common.py index 563f09130f..dd99da1411 100644 --- a/ironic/drivers/modules/network/common.py +++ b/ironic/drivers/modules/network/common.py @@ -362,8 +362,9 @@ class VIFPortIDMixin(object): """Returns the currently used VIF associated with port or portgroup We are booting the node only in one network at a time, and presence of - cleaning_vif_port_id means we're doing cleaning, of - provisioning_vif_port_id - provisioning. + cleaning_vif_port_id means we're doing cleaning, + of provisioning_vif_port_id - provisioning, + of rescuing_vif_port_id - rescuing. Otherwise it's a tenant network :param task: A TaskManager instance. @@ -373,6 +374,7 @@ class VIFPortIDMixin(object): return (p_obj.internal_info.get('cleaning_vif_port_id') or p_obj.internal_info.get('provisioning_vif_port_id') or + p_obj.internal_info.get('rescuing_vif_port_id') or self._get_vif_id_by_port_like_obj(p_obj) or None) diff --git a/ironic/drivers/modules/network/neutron.py b/ironic/drivers/modules/network/neutron.py index b88b477031..0f4048cc5b 100644 --- a/ironic/drivers/modules/network/neutron.py +++ b/ironic/drivers/modules/network/neutron.py @@ -140,6 +140,46 @@ class NeutronNetwork(common.NeutronVIFPortIDMixin, port.internal_info = internal_info port.save() + def add_rescuing_network(self, task): + """Create neutron ports for each port to boot the rescue ramdisk. + + :param task: a TaskManager instance. + :returns: a dictionary in the form {port.uuid: neutron_port['id']} + """ + # If we have left over ports from a previous rescue, remove them + neutron.rollback_ports(task, self.get_rescuing_network_uuid( + context=task.context)) + LOG.info('Adding rescuing network to node %s', task.node.uuid) + security_groups = CONF.neutron.rescuing_network_security_groups + vifs = neutron.add_ports_to_network( + task, + self.get_rescuing_network_uuid(context=task.context), + security_groups=security_groups) + for port in task.ports: + if port.uuid in vifs: + internal_info = port.internal_info + internal_info['rescuing_vif_port_id'] = vifs[port.uuid] + port.internal_info = internal_info + port.save() + return vifs + + def remove_rescuing_network(self, task): + """Deletes neutron port created for booting the rescue ramdisk. + + :param task: a TaskManager instance. + :raises: NetworkError + """ + LOG.info('Removing rescuing network from node %s', + task.node.uuid) + neutron.remove_ports_from_network( + task, self.get_rescuing_network_uuid(context=task.context)) + for port in task.ports: + if 'rescuing_vif_port_id' in port.internal_info: + internal_info = port.internal_info + del internal_info['rescuing_vif_port_id'] + port.internal_info = internal_info + port.save() + def configure_tenant_networks(self, task): """Configure tenant networks for a node. diff --git a/ironic/drivers/modules/network/noop.py b/ironic/drivers/modules/network/noop.py index 5d19ecd591..5f50162ad3 100644 --- a/ironic/drivers/modules/network/noop.py +++ b/ironic/drivers/modules/network/noop.py @@ -67,8 +67,9 @@ class NoopNetwork(base.NetworkInterface): """Returns the currently used VIF associated with port or portgroup We are booting the node only in one network at a time, and presence of - cleaning_vif_port_id means we're doing cleaning, of - provisioning_vif_port_id - provisioning. + cleaning_vif_port_id means we're doing cleaning, + of provisioning_vif_port_id - provisioning + of rescuing_vif_port_id - rescuing. Otherwise it's a tenant network :param task: A TaskManager instance. diff --git a/ironic/tests/base.py b/ironic/tests/base.py index 7482d762e2..b2d380bbd1 100644 --- a/ironic/tests/base.py +++ b/ironic/tests/base.py @@ -129,6 +129,8 @@ class TestCase(oslo_test_base.BaseTestCase): group='neutron') self.config(provisioning_network=uuidutils.generate_uuid(), group='neutron') + self.config(rescuing_network=uuidutils.generate_uuid(), + group='neutron') self.config(enabled_drivers=['fake']) self.config(enabled_hardware_types=['fake-hardware']) self.config(enabled_network_interfaces=['flat', 'noop', 'neutron']) diff --git a/ironic/tests/unit/common/test_network.py b/ironic/tests/unit/common/test_network.py index bc5e26b6b5..d59846385f 100644 --- a/ironic/tests/unit/common/test_network.py +++ b/ironic/tests/unit/common/test_network.py @@ -153,6 +153,9 @@ class TestNetwork(db_base.DbTestCase): def test_get_node_vif_ids_during_provisioning(self): self._test_get_node_vif_ids_multitenancy('provisioning_vif_port_id') + def test_get_node_vif_ids_during_rescuing(self): + self._test_get_node_vif_ids_multitenancy('rescuing_vif_port_id') + class GetPortgroupByIdTestCase(db_base.DbTestCase): diff --git a/ironic/tests/unit/drivers/modules/network/test_common.py b/ironic/tests/unit/drivers/modules/network/test_common.py index 4fc0d513a3..a64fc348fe 100644 --- a/ironic/tests/unit/drivers/modules/network/test_common.py +++ b/ironic/tests/unit/drivers/modules/network/test_common.py @@ -626,7 +626,7 @@ class TestVifPortIDMixin(db_base.DbTestCase): def test_get_current_vif_internal_info_provisioning(self): internal_info = {'provisioning_vif_port_id': 'foo', - 'vif_port_id': 'bar'} + 'tenant_vif_port_id': 'bar'} self.port.internal_info = internal_info self.port.save() with task_manager.acquire(self.context, self.node.id) as task: @@ -641,6 +641,15 @@ class TestVifPortIDMixin(db_base.DbTestCase): vif = self.interface.get_current_vif(task, self.port) self.assertEqual('bar', vif) + def test_get_current_vif_internal_info_rescuing(self): + internal_info = {'rescuing_vif_port_id': 'foo', + 'tenant_vif_port_id': 'bar'} + self.port.internal_info = internal_info + self.port.save() + with task_manager.acquire(self.context, self.node.id) as task: + vif = self.interface.get_current_vif(task, self.port) + self.assertEqual('foo', vif) + def test_get_current_vif_none(self): internal_info = extra = {} self.port.internal_info = 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 704b6f9984..df8ff531c2 100644 --- a/ironic/tests/unit/drivers/modules/network/test_neutron.py +++ b/ironic/tests/unit/drivers/modules/network/test_neutron.py @@ -20,6 +20,7 @@ from oslo_utils import uuidutils from ironic.common import exception from ironic.common import neutron as neutron_common from ironic.conductor import task_manager +from ironic.drivers import base as drivers_base from ironic.drivers.modules.network import neutron from ironic.tests.unit.conductor import mgr_utils from ironic.tests.unit.db import base as db_base @@ -35,10 +36,19 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): def setUp(self): super(NeutronInterfaceTestCase, self).setUp() - self.config(enabled_drivers=['fake']) + self.config(enabled_hardware_types=['fake-hardware']) + for iface in drivers_base.ALL_INTERFACES: + name = 'fake' + if iface == 'network': + name = 'neutron' + config_kwarg = {'enabled_%s_interfaces' % iface: [name], + 'default_%s_interface' % iface: name} + self.config(**config_kwarg) + mgr_utils.mock_the_extension_manager() self.interface = neutron.NeutronNetwork() self.node = utils.create_test_node(self.context, + driver='fake-hardware', network_interface='neutron') self.port = utils.create_test_port( self.context, node_id=self.node.id, @@ -217,6 +227,82 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): self.port.refresh() self.assertNotIn('cleaning_vif_port_id', self.port.internal_info) + @mock.patch.object(neutron_common, 'validate_network', + side_effect=lambda n, t, context=None: n) + @mock.patch.object(neutron_common, 'rollback_ports') + @mock.patch.object(neutron_common, 'add_ports_to_network') + def test_add_rescuing_network(self, add_ports_mock, rollback_mock, + validate_mock): + other_port = utils.create_test_port( + self.context, node_id=self.node.id, + address='52:54:00:cf:2d:33', + uuid=uuidutils.generate_uuid(), + extra={'vif_port_id': uuidutils.generate_uuid()}) + neutron_other_port = {'id': uuidutils.generate_uuid(), + 'mac_address': '52:54:00:cf:2d:33'} + add_ports_mock.return_value = { + other_port.uuid: neutron_other_port['id']} + with task_manager.acquire(self.context, self.node.id) as task: + res = self.interface.add_rescuing_network(task) + add_ports_mock.assert_called_once_with( + task, CONF.neutron.rescuing_network, + security_groups=[]) + rollback_mock.assert_called_once_with( + task, CONF.neutron.rescuing_network) + self.assertEqual(add_ports_mock.return_value, res) + validate_mock.assert_called_once_with( + CONF.neutron.rescuing_network, + 'rescuing network', context=task.context) + other_port.refresh() + self.assertEqual(neutron_other_port['id'], + other_port.internal_info['rescuing_vif_port_id']) + self.assertNotIn('rescuing_vif_port_id', self.port.internal_info) + + @mock.patch.object(neutron_common, 'validate_network', + lambda n, t, context=None: n) + @mock.patch.object(neutron_common, 'rollback_ports') + @mock.patch.object(neutron_common, 'add_ports_to_network') + def test_add_rescuing_network_with_sg(self, add_ports_mock, rollback_mock): + add_ports_mock.return_value = {self.port.uuid: self.neutron_port['id']} + sg_ids = [] + for i in range(2): + sg_ids.append(uuidutils.generate_uuid()) + self.config(rescuing_network_security_groups=sg_ids, group='neutron') + with task_manager.acquire(self.context, self.node.id) as task: + res = self.interface.add_rescuing_network(task) + add_ports_mock.assert_called_once_with( + task, CONF.neutron.rescuing_network, + security_groups=CONF.neutron.rescuing_network_security_groups) + rollback_mock.assert_called_once_with( + task, CONF.neutron.rescuing_network) + self.assertEqual(add_ports_mock.return_value, res) + self.port.refresh() + self.assertEqual(self.neutron_port['id'], + self.port.internal_info['rescuing_vif_port_id']) + + @mock.patch.object(neutron_common, 'validate_network', + side_effect=lambda n, t, context=None: n) + @mock.patch.object(neutron_common, 'remove_ports_from_network') + def test_remove_rescuing_network(self, remove_ports_mock, + validate_mock): + other_port = utils.create_test_port( + self.context, node_id=self.node.id, + address='52:54:00:cf:2d:33', + uuid=uuidutils.generate_uuid(), + extra={'vif_port_id': uuidutils.generate_uuid()}) + other_port.internal_info = {'rescuing_vif_port_id': 'vif-port-id'} + other_port.save() + with task_manager.acquire(self.context, self.node.id) as task: + self.interface.remove_rescuing_network(task) + remove_ports_mock.assert_called_once_with( + task, CONF.neutron.rescuing_network) + validate_mock.assert_called_once_with( + CONF.neutron.rescuing_network, + 'rescuing network', context=task.context) + other_port.refresh() + self.assertNotIn('rescuing_vif_port_id', self.port.internal_info) + self.assertNotIn('rescuing_vif_port_id', other_port.internal_info) + @mock.patch.object(neutron_common, 'unbind_neutron_port') def test_unconfigure_tenant_networks(self, mock_unbind_port): with task_manager.acquire(self.context, self.node.id) as task: