From 3404dc913e0a0a595c3696454b878b716833d612 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Harald=20Jens=C3=A5s?= <hjensas@redhat.com>
Date: Fri, 7 Jan 2022 13:16:09 +0100
Subject: [PATCH] Ensure 'port' is up2date after binding:host_id

On neutron routed provider networks IP allocation is
deferred until 'binding:host_id' is set. When ironic
creates neutron ports it first creates the port, then
updates the port setting binding information.

When using IPv6 networking ironic adds additional address
allocations to ensure network chain-booting will succeed.
When address allocation is deferred on port create ironic
cannot detect that IPv6 is used and does not add the
required additional addresses.

This change ensures the 'port' object is updated after the
port update setting the port binding required for neutron
to allocate the address. This allows ironic to correctly
detect IPv6 is used, and it will add the required IP
address allocations.

Story: 2009773
Task: 44254
Change-Id: I863dd4ab9615a9ce3b3dcb8798af674ac9966bf2
---
 ironic/common/neutron.py                                    | 3 ++-
 ironic/tests/unit/common/test_neutron.py                    | 5 +++++
 ...ovisioning-routed-provider-network-bbd0c46559f618ac.yaml | 6 ++++++
 3 files changed, 13 insertions(+), 1 deletion(-)
 create mode 100644 releasenotes/notes/fix-ipv6-provisioning-routed-provider-network-bbd0c46559f618ac.yaml

diff --git a/ironic/common/neutron.py b/ironic/common/neutron.py
index 3ab23424ba..df5b5bd7a8 100644
--- a/ironic/common/neutron.py
+++ b/ironic/common/neutron.py
@@ -345,7 +345,8 @@ def add_ports_to_network(task, network_uuid, security_groups=None):
                 wait_for_host_agent(
                     client, update_port_attrs['binding:host_id'])
             port = client.create_port(**port_attrs)
-            update_neutron_port(task.context, port.id, update_port_attrs)
+            port = update_neutron_port(task.context, port.id,
+                                       update_port_attrs)
             if CONF.neutron.dhcpv6_stateful_address_count > 1:
                 _add_ip_addresses_for_ipv6_stateful(task.context, port, client)
             if is_smart_nic:
diff --git a/ironic/tests/unit/common/test_neutron.py b/ironic/tests/unit/common/test_neutron.py
index 900049df69..7dc67ab329 100644
--- a/ironic/tests/unit/common/test_neutron.py
+++ b/ironic/tests/unit/common/test_neutron.py
@@ -290,11 +290,13 @@ class TestNeutronNetworkActions(db_base.DbTestCase):
                 fixed_ips=[])
             self.client_mock.create_port.side_effect = [self.neutron_port,
                                                         neutron_port2]
+            update_mock.side_effect = [self.neutron_port, neutron_port2]
             expected = {port.uuid: self.neutron_port.id,
                         port2.uuid: neutron_port2.id}
 
         else:
             self.client_mock.create_port.return_value = self.neutron_port
+            update_mock.return_value = self.neutron_port
             expected = {port.uuid: self.neutron_port['id']}
 
         with task_manager.acquire(self.context, self.node.uuid) as task:
@@ -458,6 +460,7 @@ class TestNeutronNetworkActions(db_base.DbTestCase):
         vpi_mock.return_value = True
         # Ensure we can create ports
         self.client_mock.create_port.return_value = self.neutron_port
+        update_mock.return_value = self.neutron_port
         expected = {port.uuid: self.neutron_port.id}
         with task_manager.acquire(self.context, self.node.uuid) as task:
             ports = neutron.add_ports_to_network(task, self.network_uuid)
@@ -492,6 +495,7 @@ class TestNeutronNetworkActions(db_base.DbTestCase):
         )
         self.client_mock.create_port.side_effect = [
             self.neutron_port, openstack_exc.OpenStackCloudException]
+        update_mock.return_value = self.neutron_port
         with task_manager.acquire(self.context, self.node.uuid) as task:
             neutron.add_ports_to_network(task, self.network_uuid)
             self.assertIn("Could not create neutron port for node's",
@@ -999,6 +1003,7 @@ class TestNeutronNetworkActions(db_base.DbTestCase):
 
         # Ensure we can create ports
         self.client_mock.create_port.return_value = self.neutron_port
+        update_mock.return_value = self.neutron_port
         expected = {port.uuid: self.neutron_port.id}
         with task_manager.acquire(self.context, self.node.uuid) as task:
             ports = neutron.add_ports_to_network(task, self.network_uuid)
diff --git a/releasenotes/notes/fix-ipv6-provisioning-routed-provider-network-bbd0c46559f618ac.yaml b/releasenotes/notes/fix-ipv6-provisioning-routed-provider-network-bbd0c46559f618ac.yaml
new file mode 100644
index 0000000000..beb270f611
--- /dev/null
+++ b/releasenotes/notes/fix-ipv6-provisioning-routed-provider-network-bbd0c46559f618ac.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+  - |
+    Fixed an issue where provisioning/cleaning would fail on IPv6 routed provider
+    networks. See bug:
+    `2009773 <https://storyboard.openstack.org/#!/story/2009773>`_.