From 31a431d35178427a00ce03c7661aa3550a0180e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Jens=C3=A5s?= Date: Mon, 20 Dec 2021 22:44:08 +0100 Subject: [PATCH] Fix IPv6 router on UC re-install Running 'openstack undercloud install' a second time failed with a ConflictException because a subnet with a router port cannot have the gateway_ip updated. Ensure that the router port is always deleted prior to the subnet update and then add the router port again after updating the subnet Closes-Bug: #1955417 Change-Id: I0e075153e99876ddf613caecc92b4442e8b6ca03 (cherry picked from commit 62ded067d53f8e12591f5ef7392b472cb8e9e985) --- .../undercloud_ctlplane_network.py | 75 ++++++++++++++----- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/extraconfig/post_deploy/undercloud_ctlplane_network.py b/extraconfig/post_deploy/undercloud_ctlplane_network.py index cd7d09df70..ead552681d 100755 --- a/extraconfig/post_deploy/undercloud_ctlplane_network.py +++ b/extraconfig/post_deploy/undercloud_ctlplane_network.py @@ -175,16 +175,42 @@ def _neutron_segment_update(sdk, segment_id, name): raise -def _ensure_neutron_router(sdk, name, subnet_id): - # If the router already exist, don't try to create it again. - if list(sdk.network.routers(name=name)): - return - +def _ensure_neutron_router(sdk, subnet): try: - router = sdk.network.create_router(name=name, admin_state_up='true') - sdk.network.add_interface_to_router(router.id, subnet_id=subnet_id) + router = sdk.network.find_router(name_or_id=subnet.name) + if router is None: + sdk.network.create_router(name=subnet.name, + admin_state_up='true') except Exception: - print('ERROR: Create router for subnet %s failed.' % name) + print('ERROR: Create router for subnet %s failed.' % subnet.name) + raise + + +def _add_router_port(sdk, subnet): + try: + router = sdk.network.find_router(name_or_id=subnet.name) + sdk.network.add_interface_to_router(router.id, subnet_id=subnet.id) + except Exception: + print('ERROR: Add router port for subnet %s failed.' % subnet.name) + raise + + +def _remove_router_port(sdk, subnet): + try: + router = sdk.network.find_router(name_or_id=subnet.name) + if router is None: + return + + router_port = next(sdk.network.ports( + network_id=subnet.network_id, + device_owner='network:router_interface')) + sdk.network.remove_interface_from_router(router.id, subnet.id, + router_port.id) + except StopIteration: + # There is no router port + pass + except Exception: + print('ERROR: Delete router %s failed.' % subnet.name) raise @@ -224,11 +250,20 @@ def _local_neutron_segments_and_subnets(sdk, ctlplane_id, net_cidrs): """Create's and updates the ctlplane subnet on the segment that is local to the underclud. """ - s = CONF['subnets'][CONF['local_subnet']] + # If the subnet is IPv6 we need to start a router so that router + # advertisements are sent out for stateless IP addressing to work. + # NOTE(hjensas): Don't do this for routed networks. The router will + # use the address defined as gateway for the subnet, and in a + # deployment with routed networks this will conflict with the router + # in the infrastructure. The router in the infrastructure must be + # configured to send router advertisements. + enable_ipv6_router = (CONF['enable_routed_networks'] is False + and netaddr.IPNetwork(s['NetworkCidr']).version == 6) name = CONF['local_subnet'] subnet = _get_subnet(sdk, s['NetworkCidr'], ctlplane_id) segment = _get_segment(sdk, CONF['physical_network'], ctlplane_id) + if subnet: if CONF['enable_routed_networks'] and subnet.segment_id is None: # The subnet exists and does not have a segment association. Since @@ -237,6 +272,12 @@ def _local_neutron_segments_and_subnets(sdk, ctlplane_id, net_cidrs): # networks subnet by associating the network segment_id with the # subnet. _neutron_add_subnet_segment_association(sdk, subnet.id, segment.id) + + # The router interface must be removed prior to updating the subnet + # in case the gateway_ip of the subnet changed. + if enable_ipv6_router: + _remove_router_port(sdk, subnet) + _neutron_subnet_update( sdk, subnet.id, s['NetworkCidr'], s['NetworkGateway'], s['HostRoutes'], s.get('AllocationPools'), name, @@ -246,20 +287,16 @@ def _local_neutron_segments_and_subnets(sdk, ctlplane_id, net_cidrs): segment_id = segment.id else: segment_id = None + subnet = _neutron_subnet_create( sdk, ctlplane_id, s['NetworkCidr'], s['NetworkGateway'], s['HostRoutes'], s.get('AllocationPools'), name, segment_id, s['DnsNameServers']) - # If the subnet is IPv6 we need to start a router so that router - # advertisments are sent out for stateless IP addressing to work. - # NOTE(hjensas): Don't do this for routed networks. The router will - # use the address defined as gateway for the subnet, and in a - # deployment with routed networks this will conflict with the router - # in the infrastructure. The router in the infrastucture must be - # configured to send router advertisements. - if not CONF['enable_routed_networks']: - if netaddr.IPNetwork(s['NetworkCidr']).version == 6: - _ensure_neutron_router(sdk, name, subnet.id) + + if enable_ipv6_router: + _ensure_neutron_router(sdk, subnet) + _add_router_port(sdk, subnet) + net_cidrs.append(s['NetworkCidr']) return net_cidrs