summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ironic/common/neutron.py35
-rw-r--r--ironic/drivers/modules/network/common.py1
-rw-r--r--ironic/drivers/modules/network/flat.py67
-rw-r--r--ironic/tests/unit/common/test_neutron.py61
-rw-r--r--ironic/tests/unit/drivers/modules/network/test_flat.py81
-rw-r--r--ironic/tests/unit/drivers/modules/network/test_neutron.py10
-rw-r--r--ironic/tests/unit/drivers/modules/test_agent.py16
-rw-r--r--ironic/tests/unit/drivers/modules/test_agent_base_vendor.py17
-rw-r--r--releasenotes/notes/ensure-unbind-flat-vifs-and-clear-macs-34eec149618e5964.yaml13
9 files changed, 251 insertions, 50 deletions
diff --git a/ironic/common/neutron.py b/ironic/common/neutron.py
index babc16b1f..15a3a9e63 100644
--- a/ironic/common/neutron.py
+++ b/ironic/common/neutron.py
@@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import random
+
from neutronclient.common import exceptions as neutron_exceptions
from neutronclient.v2_0 import client as clientv20
from oslo_log import log
@@ -38,6 +40,26 @@ PHYSNET_PARAM_NAME = 'provider:physical_network'
SEGMENTS_PARAM_NAME = 'segments'
"""Name of the neutron network API segments parameter."""
+BASE_MAC = ['fa', '16', '3e', '00', '00', '00']
+
+
+def _get_random_mac(base_mac):
+ """Get a random MAC address string of the specified base format.
+
+ The first 3 octets will remain unchanged. If the 4th octet is not
+ 00, it will also be used. The others will be randomly generated.
+
+ :param base_mac: Base MAC address represented by an array of 6 strings/int
+ :returns: The MAC address string.
+ """
+
+ mac = [int(base_mac[0], 16), int(base_mac[1], 16),
+ int(base_mac[2], 16), random.getrandbits(8),
+ random.getrandbits(8), random.getrandbits(8)]
+ if base_mac[3] != '00':
+ mac[3] = int(base_mac[3], 16)
+ return ':'.join(["%02x" % x for x in mac])
+
def _get_neutron_session():
global _NEUTRON_SESSION
@@ -104,11 +126,18 @@ def unbind_neutron_port(port_id, client=None, context=None):
if not client:
client = get_client(context=context)
- body = {'port': {'binding:host_id': '',
- 'binding:profile': {}}}
+ body_unbind = {'port': {'binding:host_id': '',
+ 'binding:profile': {}}}
+ body_reset_mac = {'port': {
+ 'mac_address': _get_random_mac(BASE_MAC)}}
try:
- client.update_port(port_id, body)
+ client.update_port(port_id, body_unbind)
+ # NOTE(hjensas): We need to reset the mac address in a separate step.
+ # Exception PortBound will be raised by neutron as it refuses to
+ # update the mac address of a bound port if we attempt to unbind and
+ # reset the mac in the same call.
+ client.update_port(port_id, body_reset_mac)
# NOTE(vsaienko): Ignore if port was deleted before calling vif detach.
except neutron_exceptions.PortNotFoundClient:
LOG.info('Port %s was not found while unbinding.', port_id)
diff --git a/ironic/drivers/modules/network/common.py b/ironic/drivers/modules/network/common.py
index e9ebabe86..2db3d78e1 100644
--- a/ironic/drivers/modules/network/common.py
+++ b/ironic/drivers/modules/network/common.py
@@ -256,6 +256,7 @@ def plug_port_to_tenant_network(task, port_like_obj, client=None):
'port': {
'binding:vnic_type': neutron.VNIC_BAREMETAL,
'binding:host_id': node.uuid,
+ 'mac_address': port_like_obj.address
}
}
binding_profile = {'local_link_information': local_link_info}
diff --git a/ironic/drivers/modules/network/flat.py b/ironic/drivers/modules/network/flat.py
index a8b85cbbd..df0129d45 100644
--- a/ironic/drivers/modules/network/flat.py
+++ b/ironic/drivers/modules/network/flat.py
@@ -53,12 +53,7 @@ class FlatNetwork(common.NeutronVIFPortIDMixin,
"""
self.get_cleaning_network_uuid(task)
- def add_provisioning_network(self, task):
- """Add the provisioning network to a node.
-
- :param task: A TaskManager instance.
- :raises: NetworkError when failed to set binding:host_id
- """
+ def _bind_flat_ports(self, task):
LOG.debug("Binding flat network ports")
client = neutron.get_client(context=task.context)
for port_like_obj in task.ports + task.portgroups:
@@ -71,7 +66,8 @@ class FlatNetwork(common.NeutronVIFPortIDMixin,
body = {
'port': {
'binding:host_id': task.node.uuid,
- 'binding:vnic_type': neutron.VNIC_BAREMETAL
+ 'binding:vnic_type': neutron.VNIC_BAREMETAL,
+ 'mac_address': port_like_obj.address
}
}
try:
@@ -83,26 +79,52 @@ class FlatNetwork(common.NeutronVIFPortIDMixin,
LOG.exception(msg)
raise exception.NetworkError(msg)
+ def _unbind_flat_ports(self, task):
+ node = task.node
+ LOG.info('Unbinding instance ports from node %s', node.uuid)
+
+ ports = [p for p in task.ports if not p.portgroup_id]
+ portgroups = task.portgroups
+ for port_like_obj in ports + portgroups:
+ vif_port_id = (
+ port_like_obj.internal_info.get(common.TENANT_VIF_KEY) or
+ port_like_obj.extra.get('vif_port_id'))
+ if not vif_port_id:
+ continue
+ neutron.unbind_neutron_port(vif_port_id, context=task.context)
+
+ def add_provisioning_network(self, task):
+ """Add the provisioning network to a node.
+
+ :param task: A TaskManager instance.
+ :raises: NetworkError when failed to set binding:host_id
+ """
+ self._bind_flat_ports(task)
+
def remove_provisioning_network(self, task):
"""Remove the provisioning network from a node.
:param task: A TaskManager instance.
"""
- pass
+ self._unbind_flat_ports(task)
def configure_tenant_networks(self, task):
"""Configure tenant networks for a node.
:param task: A TaskManager instance.
"""
- pass
+ self._bind_flat_ports(task)
def unconfigure_tenant_networks(self, task):
"""Unconfigure tenant networks for a node.
+ Unbind the port here/now to avoid the possibility of the ironic port
+ being bound to the tenant and cleaning networks at the same time.
+
:param task: A TaskManager instance.
+ :raises: NetworkError
"""
- pass
+ self._unbind_flat_ports(task)
def add_cleaning_network(self, task):
"""Add the cleaning network to a node.
@@ -141,3 +163,28 @@ class FlatNetwork(common.NeutronVIFPortIDMixin,
del internal_info['cleaning_vif_port_id']
port.internal_info = internal_info
port.save()
+
+ def add_rescuing_network(self, task):
+ """Add the rescuing network to a node.
+
+ Flat network does not use the rescuing network.
+ Bind the port again since unconfigure_tenant_network() unbound it.
+
+ :param task: A TaskManager instance.
+ :returns: a dictionary in the form {port.uuid: neutron_port['id']}
+ :raises: NetworkError, InvalidParameterValue
+ """
+ LOG.info('Bind ports for rescuing node %s', task.node.uuid)
+ self._bind_flat_ports(task)
+
+ def remove_rescuing_network(self, task):
+ """Remove the rescuing network from a node.
+
+ Flat network does not use the rescuing network.
+ Unbind the port again since add_rescuing_network() bound it.
+
+ :param task: A TaskManager instance.
+ :raises: NetworkError
+ """
+ LOG.info('Unbind ports for rescuing node %s', task.node.uuid)
+ self._unbind_flat_ports(task)
diff --git a/ironic/tests/unit/common/test_neutron.py b/ironic/tests/unit/common/test_neutron.py
index 3e2c6e458..c161860fa 100644
--- a/ironic/tests/unit/common/test_neutron.py
+++ b/ironic/tests/unit/common/test_neutron.py
@@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import random
+
from keystoneauth1 import loading as kaloading
import mock
from neutronclient.common import exceptions as neutron_client_exc
@@ -754,29 +756,42 @@ class TestUpdatePortAddress(base.TestCase):
@mock.patch.object(neutron, 'get_client', autospec=True)
+@mock.patch.object(neutron, '_get_random_mac', autospec=True)
class TestUnbindPort(base.TestCase):
def setUp(self):
super(TestUnbindPort, self).setUp()
self.context = context.RequestContext()
- def test_unbind_neutron_port_client_passed(self, mock_client):
+ def test_unbind_neutron_port_client_passed(self, mock_random_mac,
+ mock_client):
port_id = 'fake-port-id'
- body = {
+ address = 'fe:54:00:77:07:d9'
+ mock_random_mac.return_value = address
+ body_unbind = {
'port': {
'binding:host_id': '',
'binding:profile': {}
}
}
+ body_reset_mac = {
+ 'port': {
+ 'mac_address': address
+ }
+ }
+ update_calls = [
+ mock.call(port_id, body_unbind),
+ mock.call(port_id, body_reset_mac)
+ ]
neutron.unbind_neutron_port(port_id,
mock_client(context=self.context),
context=self.context)
self.assertEqual(1, mock_client.call_count)
- mock_client.return_value.update_port.assert_called_once_with(port_id,
- body)
+ mock_client.return_value.update_port.assert_has_calls(update_calls)
@mock.patch.object(neutron, 'LOG', autospec=True)
- def test_unbind_neutron_port_failure(self, mock_log, mock_client):
+ def test_unbind_neutron_port_failure(self, mock_log, mock_random_mac,
+ mock_client):
mock_client.return_value.update_port.side_effect = (
neutron_client_exc.NeutronClientException())
body = {
@@ -793,21 +808,32 @@ class TestUnbindPort(base.TestCase):
body)
mock_log.exception.assert_called_once()
- def test_unbind_neutron_port(self, mock_client):
+ def test_unbind_neutron_port(self, mock_random_mac, mock_client):
port_id = 'fake-port-id'
- body = {
+ address = 'fe:54:00:77:07:d9'
+ mock_random_mac.return_value = address
+ body_unbind = {
'port': {
'binding:host_id': '',
'binding:profile': {}
}
}
+ body_reset_mac = {
+ 'port': {
+ 'mac_address': address
+ }
+ }
+ update_calls = [
+ mock.call(port_id, body_unbind),
+ mock.call(port_id, body_reset_mac)
+ ]
neutron.unbind_neutron_port(port_id, context=self.context)
mock_client.assert_called_once_with(context=self.context)
- mock_client.return_value.update_port.assert_called_once_with(port_id,
- body)
+ mock_client.return_value.update_port.assert_has_calls(update_calls)
@mock.patch.object(neutron, 'LOG', autospec=True)
- def test_unbind_neutron_port_not_found(self, mock_log, mock_client):
+ def test_unbind_neutron_port_not_found(self, mock_log, mock_random_mac,
+ mock_client):
port_id = 'fake-port-id'
mock_client.return_value.update_port.side_effect = (
neutron_client_exc.PortNotFoundClient())
@@ -1048,3 +1074,18 @@ class TestGetPhysnetsByPortUUID(base.TestCase):
fields=self.PORT_FIELDS)
mock_gn.assert_called_once_with(self.client, network_uuid,
fields=self.NETWORK_FIELDS)
+
+
+class TestGetRandomMac(base.TestCase):
+
+ @mock.patch.object(random, 'getrandbits', return_value=0xa2)
+ def test_first_4_octets_unchanged(self, mock_rnd):
+ mac = neutron._get_random_mac(['aa', 'bb', '00', 'dd', 'ee', 'ff'])
+ self.assertEqual('aa:bb:00:dd:a2:a2', mac)
+ mock_rnd.assert_called_with(8)
+
+ @mock.patch.object(random, 'getrandbits', return_value=0xa2)
+ def test_first_4th_octet_generated(self, mock_rnd):
+ mac = neutron._get_random_mac(['aa', 'bb', 'cc', '00', 'ee', 'ff'])
+ self.assertEqual('aa:bb:cc:a2:a2:a2', mac)
+ mock_rnd.assert_called_with(8)
diff --git a/ironic/tests/unit/drivers/modules/network/test_flat.py b/ironic/tests/unit/drivers/modules/network/test_flat.py
index 316efdd55..d93b34609 100644
--- a/ironic/tests/unit/drivers/modules/network/test_flat.py
+++ b/ironic/tests/unit/drivers/modules/network/test_flat.py
@@ -178,8 +178,7 @@ class TestFlatInterface(db_base.DbTestCase):
self.assertNotIn('cleaning_vif_port_id', self.port.internal_info)
@mock.patch.object(neutron, 'get_client')
- def test_add_provisioning_network_set_binding_host_id(
- self, client_mock):
+ def test__bind_flat_ports_set_binding_host_id(self, client_mock):
upd_mock = mock.Mock()
client_mock.return_value.update_port = upd_mock
extra = {'vif_port_id': 'foo'}
@@ -187,14 +186,14 @@ class TestFlatInterface(db_base.DbTestCase):
address='52:54:00:cf:2d:33', extra=extra,
uuid=uuidutils.generate_uuid())
exp_body = {'port': {'binding:host_id': self.node.uuid,
- 'binding:vnic_type': neutron.VNIC_BAREMETAL}}
+ 'binding:vnic_type': neutron.VNIC_BAREMETAL,
+ 'mac_address': '52:54:00:cf:2d:33'}}
with task_manager.acquire(self.context, self.node.id) as task:
- self.interface.add_provisioning_network(task)
+ self.interface._bind_flat_ports(task)
upd_mock.assert_called_once_with('foo', exp_body)
@mock.patch.object(neutron, 'get_client')
- def test_add_provisioning_network_set_binding_host_id_portgroup(
- self, client_mock):
+ def test__bind_flat_ports_set_binding_host_id_portgroup(self, client_mock):
upd_mock = mock.Mock()
client_mock.return_value.update_port = upd_mock
internal_info = {'tenant_vif_port_id': 'foo'}
@@ -204,17 +203,46 @@ class TestFlatInterface(db_base.DbTestCase):
utils.create_test_port(
self.context, node_id=self.node.id, address='52:54:00:cf:2d:33',
extra={'vif_port_id': 'bar'}, uuid=uuidutils.generate_uuid())
- exp_body = {'port': {'binding:host_id': self.node.uuid,
- 'binding:vnic_type': neutron.VNIC_BAREMETAL}}
+ exp_body1 = {'port': {'binding:host_id': self.node.uuid,
+ 'binding:vnic_type': neutron.VNIC_BAREMETAL,
+ 'mac_address': '52:54:00:cf:2d:33'}}
+ exp_body2 = {'port': {'binding:host_id': self.node.uuid,
+ 'binding:vnic_type': neutron.VNIC_BAREMETAL,
+ 'mac_address': '52:54:00:cf:2d:31'}}
with task_manager.acquire(self.context, self.node.id) as task:
- self.interface.add_provisioning_network(task)
+ self.interface._bind_flat_ports(task)
upd_mock.assert_has_calls([
- mock.call('bar', exp_body), mock.call('foo', exp_body)
- ])
+ mock.call('bar', exp_body1), mock.call('foo', exp_body2)])
+
+ @mock.patch.object(neutron, 'unbind_neutron_port')
+ def test__unbind_flat_ports(self, unbind_neutron_port_mock):
+ extra = {'vif_port_id': 'foo'}
+ utils.create_test_port(self.context, node_id=self.node.id,
+ address='52:54:00:cf:2d:33', extra=extra,
+ uuid=uuidutils.generate_uuid())
+ with task_manager.acquire(self.context, self.node.id) as task:
+ self.interface._unbind_flat_ports(task)
+ unbind_neutron_port_mock.assert_called_once_with('foo',
+ context=self.context)
+
+ @mock.patch.object(neutron, 'unbind_neutron_port')
+ def test__unbind_flat_ports_portgroup(self, unbind_neutron_port_mock):
+ internal_info = {'tenant_vif_port_id': 'foo'}
+ utils.create_test_portgroup(self.context, node_id=self.node.id,
+ internal_info=internal_info,
+ uuid=uuidutils.generate_uuid())
+ extra = {'vif_port_id': 'bar'}
+ utils.create_test_port(self.context, node_id=self.node.id,
+ address='52:54:00:cf:2d:33', extra=extra,
+ uuid=uuidutils.generate_uuid())
+ with task_manager.acquire(self.context, self.node.id) as task:
+ self.interface._unbind_flat_ports(task)
+ unbind_neutron_port_mock.has_calls(
+ [mock.call('foo', context=self.context),
+ mock.call('bar', context=self.context)])
@mock.patch.object(neutron, 'get_client')
- def test_add_provisioning_network_binding_host_id_raise(
- self, client_mock):
+ def test__bind_flat_ports_set_binding_host_id_raise(self, client_mock):
client_mock.return_value.update_port.side_effect = \
(neutron_exceptions.ConnectionFailed())
extra = {'vif_port_id': 'foo'}
@@ -223,5 +251,28 @@ class TestFlatInterface(db_base.DbTestCase):
uuid=uuidutils.generate_uuid())
with task_manager.acquire(self.context, self.node.id) as task:
self.assertRaises(exception.NetworkError,
- self.interface.add_provisioning_network,
- task)
+ self.interface._bind_flat_ports, task)
+
+ @mock.patch.object(flat_interface.FlatNetwork, '_bind_flat_ports')
+ def test_add_rescuing_network(self, bind_mock):
+ with task_manager.acquire(self.context, self.node.id) as task:
+ self.interface.add_rescuing_network(task)
+ bind_mock.assert_called_once_with(task)
+
+ @mock.patch.object(flat_interface.FlatNetwork, '_unbind_flat_ports')
+ def test_remove_rescuing_network(self, unbind_mock):
+ with task_manager.acquire(self.context, self.node.id) as task:
+ self.interface.remove_rescuing_network(task)
+ unbind_mock.assert_called_once_with(task)
+
+ @mock.patch.object(flat_interface.FlatNetwork, '_bind_flat_ports')
+ def test_add_provisioning_network(self, bind_mock):
+ with task_manager.acquire(self.context, self.node.id) as task:
+ self.interface.add_provisioning_network(task)
+ bind_mock.assert_called_once_with(task)
+
+ @mock.patch.object(flat_interface.FlatNetwork, '_unbind_flat_ports')
+ def test_remove_provisioning_network(self, unbind_mock):
+ with task_manager.acquire(self.context, self.node.id) as task:
+ self.interface.remove_provisioning_network(task)
+ unbind_mock.assert_called_once_with(task)
diff --git a/ironic/tests/unit/drivers/modules/network/test_neutron.py b/ironic/tests/unit/drivers/modules/network/test_neutron.py
index 0b7381471..1643f8a90 100644
--- a/ironic/tests/unit/drivers/modules/network/test_neutron.py
+++ b/ironic/tests/unit/drivers/modules/network/test_neutron.py
@@ -582,12 +582,10 @@ class NeutronInterfaceTestCase(db_base.DbTestCase):
'binding:vnic_type': 'baremetal',
'binding:host_id': self.node.uuid,
'binding:profile': {'local_link_information':
- [self.port.local_link_connection]}
+ [self.port.local_link_connection]},
+ 'mac_address': '52:54:00:cf:2d:32'
}
}
- utils.create_test_port(self.context, node_id=self.node.id,
- address='52:54:00:cf:2d:33', extra={},
- uuid=uuidutils.generate_uuid())
upd_mock = mock.Mock()
client_mock.return_value.update_port = upd_mock
with task_manager.acquire(self.context, self.node.id) as task:
@@ -647,10 +645,12 @@ class NeutronInterfaceTestCase(db_base.DbTestCase):
port1_body['port']['binding:profile'] = {
'local_link_information': [self.port.local_link_connection]
}
+ port1_body['port']['mac_address'] = '52:54:00:cf:2d:32'
port2_body = copy.deepcopy(expected_body)
port2_body['port']['binding:profile'] = {
'local_link_information': [second_port.local_link_connection]
}
+ port2_body['port']['mac_address'] = '52:54:00:cf:2d:33'
if is_client_id:
port1_body['port']['extra_dhcp_opts'] = (
[{'opt_name': '61', 'opt_value': client_ids[0]}])
@@ -727,12 +727,14 @@ class NeutronInterfaceTestCase(db_base.DbTestCase):
call1_body['port']['binding:profile'] = {
'local_link_information': [self.port.local_link_connection],
}
+ call1_body['port']['mac_address'] = '52:54:00:cf:2d:32'
call2_body = copy.deepcopy(expected_body)
call2_body['port']['binding:profile'] = {
'local_link_information': [port1.local_link_connection,
port2.local_link_connection],
'local_group_information': local_group_info
}
+ call2_body['port']['mac_address'] = 'ff:54:00:cf:2d:32'
with task_manager.acquire(self.context, self.node.id) as task:
# Override task.portgroups here, to have ability to check
# that mocked get_local_group_information was called with
diff --git a/ironic/tests/unit/drivers/modules/test_agent.py b/ironic/tests/unit/drivers/modules/test_agent.py
index 72e7356e7..8241dc9fd 100644
--- a/ironic/tests/unit/drivers/modules/test_agent.py
+++ b/ironic/tests/unit/drivers/modules/test_agent.py
@@ -159,6 +159,7 @@ class TestAgentDeploy(db_base.DbTestCase):
'driver_info': DRIVER_INFO,
'driver_internal_info': DRIVER_INTERNAL_INFO,
'storage_interface': 'noop',
+ 'network_interface': 'noop'
}
self.node = object_utils.create_test_node(self.context, **n)
self.ports = [
@@ -317,6 +318,9 @@ class TestAgentDeploy(db_base.DbTestCase):
storage_detach_volumes_mock):
object_utils.create_test_volume_target(
self.context, node_id=self.node.id)
+ node = self.node
+ node.network_interface = 'flat'
+ node.save()
with task_manager.acquire(
self.context, self.node['uuid'], shared=False) as task:
driver_return = self.driver.tear_down(task)
@@ -352,6 +356,9 @@ class TestAgentDeploy(db_base.DbTestCase):
build_instance_info_mock, build_options_mock,
pxe_prepare_ramdisk_mock, storage_driver_info_mock,
storage_attach_volumes_mock):
+ node = self.node
+ node.network_interface = 'flat'
+ node.save()
with task_manager.acquire(
self.context, self.node['uuid'], shared=False) as task:
task.node.provision_state = states.DEPLOYING
@@ -567,6 +574,9 @@ class TestAgentDeploy(db_base.DbTestCase):
build_options_mock, pxe_prepare_ramdisk_mock,
validate_net_mock, add_provisioning_net_mock):
self.config(group='agent', manage_agent_boot=False)
+ node = self.node
+ node.network_interface = 'flat'
+ node.save()
with task_manager.acquire(
self.context, self.node['uuid'], shared=False) as task:
task.node.provision_state = states.DEPLOYING
@@ -666,6 +676,9 @@ class TestAgentDeploy(db_base.DbTestCase):
add_provisioning_net_mock, storage_driver_info_mock,
storage_attach_volumes_mock, should_write_image_mock):
should_write_image_mock.return_value = False
+ node = self.node
+ node.network_interface = 'flat'
+ node.save()
with task_manager.acquire(
self.context, self.node['uuid'], shared=False) as task:
task.node.provision_state = states.DEPLOYING
@@ -716,6 +729,9 @@ class TestAgentDeploy(db_base.DbTestCase):
validate_net_mock,
add_provisioning_net_mock):
mock_write.return_value = False
+ node = self.node
+ node.network_interface = 'flat'
+ node.save()
with task_manager.acquire(
self.context, self.node['uuid'], shared=False) as task:
task.node.provision_state = states.DEPLOYING
diff --git a/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py b/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py
index 177002e3c..50f0759f6 100644
--- a/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py
+++ b/ironic/tests/unit/drivers/modules/test_agent_base_vendor.py
@@ -68,6 +68,7 @@ class AgentDeployMixinBaseTest(db_base.DbTestCase):
'instance_info': INSTANCE_INFO,
'driver_info': DRIVER_INFO,
'driver_internal_info': DRIVER_INTERNAL_INFO,
+ 'network_interface': 'noop'
}
self.node = object_utils.create_test_node(self.context, **n)
@@ -432,9 +433,9 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
spec=types.FunctionType)
@mock.patch.object(agent_client.AgentClient, 'power_off',
spec=types.FunctionType)
- @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.'
+ @mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
'remove_provisioning_network', spec_set=True, autospec=True)
- @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.'
+ @mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
'configure_tenant_networks', spec_set=True, autospec=True)
def test_reboot_and_finish_deploy_soft_poweroff_doesnt_complete(
self, configure_tenant_net_mock, remove_provisioning_net_mock,
@@ -463,9 +464,9 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
@mock.patch.object(manager_utils, 'node_power_action', autospec=True)
@mock.patch.object(agent_client.AgentClient, 'power_off',
spec=types.FunctionType)
- @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.'
+ @mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
'remove_provisioning_network', spec_set=True, autospec=True)
- @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.'
+ @mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
'configure_tenant_networks', spec_set=True, autospec=True)
def test_reboot_and_finish_deploy_soft_poweroff_fails(
self, configure_tenant_net_mock, remove_provisioning_net_mock,
@@ -495,9 +496,9 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
spec=types.FunctionType)
@mock.patch.object(agent_client.AgentClient, 'power_off',
spec=types.FunctionType)
- @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.'
+ @mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
'remove_provisioning_network', spec_set=True, autospec=True)
- @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.'
+ @mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
'configure_tenant_networks', spec_set=True, autospec=True)
def test_reboot_and_finish_deploy_get_power_state_fails(
self, configure_tenant_net_mock, remove_provisioning_net_mock,
@@ -590,9 +591,9 @@ class AgentDeployMixinTest(AgentDeployMixinBaseTest):
spec=types.FunctionType)
@mock.patch.object(agent_client.AgentClient, 'power_off',
spec=types.FunctionType)
- @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.'
+ @mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
'remove_provisioning_network', spec_set=True, autospec=True)
- @mock.patch('ironic.drivers.modules.network.flat.FlatNetwork.'
+ @mock.patch('ironic.drivers.modules.network.noop.NoopNetwork.'
'configure_tenant_networks', spec_set=True, autospec=True)
def test_reboot_and_finish_deploy_power_on_fails(
self, configure_tenant_net_mock, remove_provisioning_net_mock,
diff --git a/releasenotes/notes/ensure-unbind-flat-vifs-and-clear-macs-34eec149618e5964.yaml b/releasenotes/notes/ensure-unbind-flat-vifs-and-clear-macs-34eec149618e5964.yaml
new file mode 100644
index 000000000..347b7d3c5
--- /dev/null
+++ b/releasenotes/notes/ensure-unbind-flat-vifs-and-clear-macs-34eec149618e5964.yaml
@@ -0,0 +1,13 @@
+---
+fixes:
+ - |
+ Fixes an issue where Neutron ports would be left with a baremetal MAC
+ address associated after an instance is deleted from a baremetal host. This
+ caused problems with MAC address conflicts in follow up deployments to the
+ same baremetal host. `bug 2004428
+ <https://storyboard.openstack.org/#!/story/2004428>`_.
+ - |
+ Fixes an issue where a flat Neutron port would be left with a host ID
+ associated with it after an instance is deleted from a baremetal host. This
+ caused problems with reusing the same port for a new instance as it is
+ already bound to the old instance.