Delete network VIPs on stack delete

Update the tripleo_overcloud_network_vip_provision
module to remove obsolete network vip ports for a
stack by keeping a list of managed vip ports in
'managed_ports', then run a cleanup to delete any
port with the apropriate tags that are not in the
'managed_ports' list to keep the provisioned resources
state matching the VIPs defined in the vip_data.

On stack delete, call the module without specifying any
vip_data, so that the remove_obsolete_ports method will
delete any network VIP port resources associated with
the stack.

Also add cli-overcloud-network-vip-unprovision.yaml.

Related-Blueprint: blueprint network-data-v2-ports
Change-Id: Idcfd47dcfe6d635da4cc6bb6f18cbaa2a3bc4a6e
This commit is contained in:
Harald Jensås 2021-06-06 23:07:46 +02:00
parent 8011f82f1c
commit 260cdbf00c
4 changed files with 98 additions and 10 deletions

View File

@ -56,6 +56,7 @@ options:
description:
- Dictionary of network Virtual IP definitions
type: list
default: []
elements: dict
suboptions:
name:
@ -146,13 +147,13 @@ def create_port_def(vip_spec, net_maps):
else:
raise Exception(
'Network {} has multiple subnets, please add a subnet or an '
'ip_address for the vip on whit network.'.format(
'ip_address for the vip on this network.'.format(
vip_spec['network']))
return port_def
def provision_vip_port(conn, stack, net_maps, vip_spec):
def provision_vip_port(conn, stack, net_maps, vip_spec, managed_ports):
port_def = create_port_def(vip_spec, net_maps)
tags = ['tripleo_stack_name={}'.format(stack),
@ -164,6 +165,7 @@ def provision_vip_port(conn, stack, net_maps, vip_spec):
try:
port = next(ports)
managed_ports.append(port.id)
del port_def['network_id']
for k, v in port_def.items():
if port.get(k) != v:
@ -172,6 +174,7 @@ def provision_vip_port(conn, stack, net_maps, vip_spec):
except StopIteration:
port = conn.network.create_port(**port_def)
conn.network.set_tags(port, tags)
managed_ports.append(port.id)
def validate_vip_nets_in_net_map(vip_data, net_maps):
@ -187,6 +190,15 @@ def validate_vip_nets_in_net_map(vip_data, net_maps):
vip['subnet'], vip['network']))
def remove_obsolete_ports(conn, stack, managed_ports):
ports = conn.network.ports(tags=['tripleo_stack_name={}'.format(stack)])
ports = [p for p in ports if any("tripleo_vip_net" in t for t in p.tags)]
for port in ports:
if port.id not in managed_ports:
conn.network.delete_port(port.id)
def run_module():
result = dict(
success=False,
@ -205,8 +217,8 @@ def run_module():
)
concurrency = module.params['concurrency']
stack = module.params['stack_name']
vip_data = module.params['vip_data']
stack = module.params.get('stack_name')
vip_data = module.params.get('vip_data')
try:
_, conn = openstack_cloud_from_module(module)
@ -215,14 +227,16 @@ def run_module():
# no limit on concurrency, create a worker for every vip
if concurrency < 1:
concurrency = len(vip_data)
concurrency = len(vip_data) if len(vip_data) > 0 else 1
exceptions = list()
provision_jobs = list()
managed_ports = list()
with futures.ThreadPoolExecutor(max_workers=concurrency) as p:
for vip_spec in vip_data:
provision_jobs.append(p.submit(
provision_vip_port, conn, stack, net_maps, vip_spec))
provision_vip_port, conn, stack, net_maps, vip_spec,
managed_ports))
for job in futures.as_completed(provision_jobs):
e = job.exception()
@ -232,6 +246,8 @@ def run_module():
if exceptions:
raise exceptions[0]
remove_obsolete_ports(conn, stack, managed_ports)
result['success'] = True
module.exit_json(**result)
except Exception as err:

View File

@ -45,6 +45,9 @@
until: stack_delete is success
delay: 4
retries: 16
- name: Delete overcloud network Virtual IPs
tripleo_overcloud_network_vip_provision:
stack_name: "{{ stack_name }}"
post_tasks:
- name: Workflow notice
debug:

View File

@ -0,0 +1,35 @@
---
# Copyright 2021 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
- name: Overcloud Virtual IPs Unprovision
connection: "{{ (tripleo_target_host is defined) | ternary('ssh', 'local') }}"
hosts: "{{ tripleo_target_host | default('localhost') }}"
remote_user: "{{ tripleo_target_user | default(lookup('env', 'USER')) }}"
gather_facts: "{{ (tripleo_target_host is defined) | ternary(true, false) }}"
any_errors_fatal: true
vars:
overwrite: false
pre_tasks:
- fail:
msg: stack_name is a required input
when:
- stack_name is undefined
tasks:
- name: Delete Overcloud Virtual IPs
tripleo_overcloud_network_vip_provision:
stack_name: "{{ stack_name | default('overcloud') }}"

View File

@ -109,7 +109,7 @@ class TestTripleoOvercloudVipProvision(tests_base.TestCase):
vip_spec = {'network': 'network1'}
msg = (
'Network {} has multiple subnets, please add a subnet or an '
'ip_address for the vip on whit network.'.format(
'ip_address for the vip on this network.'.format(
vip_spec['network']))
self.assertRaisesRegex(Exception, msg,
plugin.create_port_def, vip_spec, NET_MAPS)
@ -120,7 +120,9 @@ class TestTripleoOvercloudVipProvision(tests_base.TestCase):
'ip_address': '1.2.3.4',
'dns_name': 'overcloud'}
mock_conn.network.ports.return_value = self.a2g([])
plugin.provision_vip_port(mock_conn, 'stack', NET_MAPS, vip_spec)
managed_ports = list()
plugin.provision_vip_port(mock_conn, 'stack', NET_MAPS, vip_spec,
managed_ports)
mock_conn.network.create_port.assert_called_with(
dns_name='overcloud',
fixed_ips=[{'ip_address': '1.2.3.4'}],
@ -142,7 +144,10 @@ class TestTripleoOvercloudVipProvision(tests_base.TestCase):
tags=['tripleo_stack_name=stack', 'tripleo_vip_net=network1']
)
mock_conn.network.ports.return_value = self.a2g([fake_port])
plugin.provision_vip_port(mock_conn, 'stack', NET_MAPS, vip_spec)
managed_ports = list()
plugin.provision_vip_port(mock_conn, 'stack', NET_MAPS, vip_spec,
managed_ports)
self.assertEqual([fake_port.id], managed_ports)
mock_conn.network.create_port.assert_not_called()
mock_conn.network.update_port.assert_not_called()
mock_conn.network.set_tags.assert_not_called()
@ -164,8 +169,37 @@ class TestTripleoOvercloudVipProvision(tests_base.TestCase):
'fixed_ips': [{'ip_address': '11.22.33.44'}],
'name': 'network1_virtual_ip'}
mock_conn.network.ports.return_value = self.a2g([fake_port])
plugin.provision_vip_port(mock_conn, 'stack', NET_MAPS, vip_spec)
managed_ports = list()
plugin.provision_vip_port(mock_conn, 'stack', NET_MAPS, vip_spec,
managed_ports)
self.assertEqual([fake_port.id], managed_ports)
mock_conn.network.create_port.assert_not_called()
mock_conn.network.update_port.assert_called_with(fake_port.id,
**port_def)
mock_conn.network.set_tags.assert_not_called()
def test_remove_obsolete_ports_deletes_port(self, mock_conn):
fake_port = stubs.FakeNeutronPort(
id='port_id',
name='network1_virtual_ip',
network_id='network1_id',
fixed_ips=[{'ip_address': '1.2.3.4'}],
dns_name='overcloud',
tags=['tripleo_stack_name=stack', 'tripleo_vip_net=network1']
)
mock_conn.network.ports.return_value = self.a2g([fake_port])
plugin.remove_obsolete_ports(mock_conn, 'stack', [])
mock_conn.network.delete_port.assert_called_once_with(fake_port.id)
def test_remove_obsolete_ports_does_not_delete_managed(self, mock_conn):
fake_port = stubs.FakeNeutronPort(
id='port_id',
name='network1_virtual_ip',
network_id='network1_id',
fixed_ips=[{'ip_address': '1.2.3.4'}],
dns_name='overcloud',
tags=['tripleo_stack_name=stack', 'tripleo_vip_net=network1']
)
mock_conn.network.ports.return_value = self.a2g([fake_port])
plugin.remove_obsolete_ports(mock_conn, 'stack', [fake_port.id])
mock_conn.network.delete_port.assert_not_called()