From 260cdbf00c779479e382b1041ec5f03d8af1cf1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Jens=C3=A5s?= Date: Sun, 6 Jun 2021 23:07:46 +0200 Subject: [PATCH] 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 --- ...tripleo_overcloud_network_vip_provision.py | 28 ++++++++++--- .../playbooks/cli-overcloud-delete.yaml | 3 ++ ...cli-overcloud-network-vip-unprovision.yaml | 35 ++++++++++++++++ ...tripleo_overcloud_network_vip_provision.py | 42 +++++++++++++++++-- 4 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 tripleo_ansible/playbooks/cli-overcloud-network-vip-unprovision.yaml diff --git a/tripleo_ansible/ansible_plugins/modules/tripleo_overcloud_network_vip_provision.py b/tripleo_ansible/ansible_plugins/modules/tripleo_overcloud_network_vip_provision.py index 44e09e965..b037bef07 100644 --- a/tripleo_ansible/ansible_plugins/modules/tripleo_overcloud_network_vip_provision.py +++ b/tripleo_ansible/ansible_plugins/modules/tripleo_overcloud_network_vip_provision.py @@ -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: diff --git a/tripleo_ansible/playbooks/cli-overcloud-delete.yaml b/tripleo_ansible/playbooks/cli-overcloud-delete.yaml index e6430be44..1b52f69a0 100644 --- a/tripleo_ansible/playbooks/cli-overcloud-delete.yaml +++ b/tripleo_ansible/playbooks/cli-overcloud-delete.yaml @@ -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: diff --git a/tripleo_ansible/playbooks/cli-overcloud-network-vip-unprovision.yaml b/tripleo_ansible/playbooks/cli-overcloud-network-vip-unprovision.yaml new file mode 100644 index 000000000..aa49d1194 --- /dev/null +++ b/tripleo_ansible/playbooks/cli-overcloud-network-vip-unprovision.yaml @@ -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') }}" diff --git a/tripleo_ansible/tests/modules/test_tripleo_overcloud_network_vip_provision.py b/tripleo_ansible/tests/modules/test_tripleo_overcloud_network_vip_provision.py index 6b97374ee..dfe6e4c10 100644 --- a/tripleo_ansible/tests/modules/test_tripleo_overcloud_network_vip_provision.py +++ b/tripleo_ansible/tests/modules/test_tripleo_overcloud_network_vip_provision.py @@ -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()