Merge "[k8s_fedora_atomic] Delete floating ip for load balancer"
This commit is contained in:
commit
a4164eddd8
55
magnum/common/neutron.py
Normal file
55
magnum/common/neutron.py
Normal file
@ -0,0 +1,55 @@
|
||||
# Copyright 2019 Catalyst Cloud Ltd.
|
||||
#
|
||||
# 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.
|
||||
import re
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from magnum.common import clients
|
||||
from magnum.common import exception
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def delete_floatingip(context, fix_port_id, cluster):
|
||||
"""Deletes the floating IP associated with the fix_port_id.
|
||||
|
||||
Only delete the floating IP if it's created and associated with the
|
||||
LoadBalancer type service in Kubernetes cluster.
|
||||
|
||||
This method only works with the Kubernetes cluster with
|
||||
cloud-provider-openstack controller manager deployed, patched with
|
||||
this PR:
|
||||
https://github.com/kubernetes/cloud-provider-openstack/pull/433
|
||||
"""
|
||||
pattern = (r'Floating IP for Kubernetes external service \w+ from cluster '
|
||||
r'%s$' % cluster.uuid)
|
||||
|
||||
try:
|
||||
n_client = clients.OpenStackClients(context).neutron()
|
||||
fips = n_client.list_floatingips(port_id=fix_port_id)
|
||||
if len(fips["floatingips"]) == 0:
|
||||
return
|
||||
|
||||
# Liberty Neutron doesn't support description field, although Liberty
|
||||
# is no longer supported, we write good code here.
|
||||
desc = fips["floatingips"][0].get("description", "")
|
||||
id = fips["floatingips"][0]["id"]
|
||||
|
||||
if re.match(pattern, desc):
|
||||
LOG.debug("Deleting floating ip %s for cluster %s", id,
|
||||
cluster.uuid)
|
||||
n_client.delete_floatingip(id)
|
||||
except Exception as e:
|
||||
raise exception.PreDeletionFailed(cluster_uuid=cluster.uuid,
|
||||
msg=str(e))
|
@ -19,6 +19,7 @@ import time
|
||||
|
||||
from magnum.common import clients
|
||||
from magnum.common import exception
|
||||
from magnum.common import neutron
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
@ -74,6 +75,10 @@ def delete_loadbalancers(context, cluster):
|
||||
invalids.add(lb["id"])
|
||||
continue
|
||||
if lb["provisioning_status"] in ["ACTIVE", "ERROR"]:
|
||||
# Delete VIP floating ip if needed.
|
||||
neutron.delete_floatingip(context, lb["vip_port_id"],
|
||||
cluster)
|
||||
|
||||
LOG.debug("Deleting load balancer %s for cluster %s",
|
||||
lb["id"], cluster.uuid)
|
||||
o_client.load_balancer_delete(lb["id"], cascade=True)
|
||||
|
138
magnum/tests/unit/common/test_neutron.py
Normal file
138
magnum/tests/unit/common/test_neutron.py
Normal file
@ -0,0 +1,138 @@
|
||||
# Copyright 2019 Catalyst Cloud Ltd.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
|
||||
from magnum.common import exception
|
||||
from magnum.common import neutron
|
||||
from magnum import objects
|
||||
from magnum.tests import base
|
||||
from magnum.tests.unit.db import utils
|
||||
|
||||
|
||||
class NeutronTest(base.TestCase):
|
||||
def setUp(self):
|
||||
super(NeutronTest, self).setUp()
|
||||
|
||||
cluster_dict = utils.get_test_cluster(node_count=1)
|
||||
self.cluster = objects.Cluster(self.context, **cluster_dict)
|
||||
|
||||
@mock.patch('magnum.common.clients.OpenStackClients')
|
||||
def test_delete_floatingip(self, mock_clients):
|
||||
mock_nclient = mock.MagicMock()
|
||||
fake_port_id = "b4518944-c2cf-4c69-a1e3-774041fd5d14"
|
||||
fake_fip_id = "0f8c6849-af85-424c-aa8e-745ade9a46a7"
|
||||
mock_nclient.list_floatingips.return_value = {
|
||||
'floatingips': [
|
||||
{
|
||||
'router_id': '6ed4f7ef-b8c3-4711-93cf-d53cf0e8bdf5',
|
||||
'status': 'ACTIVE',
|
||||
'description': 'Floating IP for Kubernetes external '
|
||||
'service ad3080723f1c211e88adbfa163ee1203 '
|
||||
'from cluster %s' % self.cluster.uuid,
|
||||
'tags': [],
|
||||
'tenant_id': 'cd08a539b7c845ddb92c5d08752101d1',
|
||||
'floating_network_id': 'd0b9a8c5-33e5-4ce1-869a-1e2ec7c2f'
|
||||
'74b',
|
||||
'port_details': {
|
||||
'status': 'ACTIVE',
|
||||
'name': 'test-k8s-master',
|
||||
'admin_state_up': True,
|
||||
'network_id': '7b9110b5-90a2-40bc-b892-07d641387760 ',
|
||||
'device_owner': 'compute:nova',
|
||||
'mac_address': 'fa:16:3e:6f:ad:6c',
|
||||
'device_id': 'a5c1689f-dd76-4164-8562-6990071701cd'
|
||||
},
|
||||
'fixed_ip_address': '10.0.0.4',
|
||||
'floating_ip_address': '172.24.4.74',
|
||||
'revision_number': 14,
|
||||
'project_id': 'cd08a539b7c845ddb92c5d08752101d1',
|
||||
'port_id': fake_port_id,
|
||||
'id': fake_fip_id
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
osc = mock.MagicMock()
|
||||
mock_clients.return_value = osc
|
||||
osc.neutron.return_value = mock_nclient
|
||||
|
||||
neutron.delete_floatingip(self.context, fake_port_id, self.cluster)
|
||||
|
||||
mock_nclient.delete_floatingip.assert_called_once_with(fake_fip_id)
|
||||
|
||||
@mock.patch('magnum.common.clients.OpenStackClients')
|
||||
def test_delete_floatingip_empty(self, mock_clients):
|
||||
mock_nclient = mock.MagicMock()
|
||||
fake_port_id = "b4518944-c2cf-4c69-a1e3-774041fd5d14"
|
||||
mock_nclient.list_floatingips.return_value = {
|
||||
'floatingips': []
|
||||
}
|
||||
|
||||
osc = mock.MagicMock()
|
||||
mock_clients.return_value = osc
|
||||
osc.neutron.return_value = mock_nclient
|
||||
|
||||
neutron.delete_floatingip(self.context, fake_port_id, self.cluster)
|
||||
|
||||
self.assertFalse(mock_nclient.delete_floatingip.called)
|
||||
|
||||
@mock.patch('magnum.common.clients.OpenStackClients')
|
||||
def test_delete_floatingip_exception(self, mock_clients):
|
||||
mock_nclient = mock.MagicMock()
|
||||
fake_port_id = "b4518944-c2cf-4c69-a1e3-774041fd5d14"
|
||||
fake_fip_id = "0f8c6849-af85-424c-aa8e-745ade9a46a7"
|
||||
mock_nclient.list_floatingips.return_value = {
|
||||
'floatingips': [
|
||||
{
|
||||
'router_id': '6ed4f7ef-b8c3-4711-93cf-d53cf0e8bdf5',
|
||||
'status': 'ACTIVE',
|
||||
'description': 'Floating IP for Kubernetes external '
|
||||
'service ad3080723f1c211e88adbfa163ee1203 '
|
||||
'from cluster %s' % self.cluster.uuid,
|
||||
'tags': [],
|
||||
'tenant_id': 'cd08a539b7c845ddb92c5d08752101d1',
|
||||
'floating_network_id': 'd0b9a8c5-33e5-4ce1-869a-1e2ec7c2f'
|
||||
'74b',
|
||||
'port_details': {
|
||||
'status': 'ACTIVE',
|
||||
'name': 'test-k8s-master',
|
||||
'admin_state_up': True,
|
||||
'network_id': '7b9110b5-90a2-40bc-b892-07d641387760 ',
|
||||
'device_owner': 'compute:nova',
|
||||
'mac_address': 'fa:16:3e:6f:ad:6c',
|
||||
'device_id': 'a5c1689f-dd76-4164-8562-6990071701cd'
|
||||
},
|
||||
'fixed_ip_address': '10.0.0.4',
|
||||
'floating_ip_address': '172.24.4.74',
|
||||
'revision_number': 14,
|
||||
'project_id': 'cd08a539b7c845ddb92c5d08752101d1',
|
||||
'port_id': fake_port_id,
|
||||
'id': fake_fip_id
|
||||
}
|
||||
]
|
||||
}
|
||||
mock_nclient.delete_floatingip.side_effect = Exception
|
||||
|
||||
osc = mock.MagicMock()
|
||||
mock_clients.return_value = osc
|
||||
osc.neutron.return_value = mock_nclient
|
||||
|
||||
self.assertRaises(
|
||||
exception.PreDeletionFailed,
|
||||
neutron.delete_floatingip,
|
||||
self.context,
|
||||
fake_port_id,
|
||||
self.cluster
|
||||
)
|
@ -28,8 +28,9 @@ class OctaviaTest(base.TestCase):
|
||||
cluster_dict = utils.get_test_cluster(node_count=1)
|
||||
self.cluster = objects.Cluster(self.context, **cluster_dict)
|
||||
|
||||
@mock.patch("magnum.common.neutron.delete_floatingip")
|
||||
@mock.patch('magnum.common.clients.OpenStackClients')
|
||||
def test_delete_loadbalancers(self, mock_clients):
|
||||
def test_delete_loadbalancers(self, mock_clients, mock_delete_fip):
|
||||
mock_lbs = {
|
||||
"loadbalancers": [
|
||||
{
|
||||
@ -38,7 +39,8 @@ class OctaviaTest(base.TestCase):
|
||||
"ad3080723f1c211e88adbfa163ee1203 from "
|
||||
"cluster %s" % self.cluster.uuid,
|
||||
"name": "fake_name_1",
|
||||
"provisioning_status": "ACTIVE"
|
||||
"provisioning_status": "ACTIVE",
|
||||
"vip_port_id": "b4ca07d1-a31e-43e2-891a-7d14f419f342"
|
||||
},
|
||||
{
|
||||
"id": "fake_id_2",
|
||||
@ -46,7 +48,8 @@ class OctaviaTest(base.TestCase):
|
||||
"a9f9ba08cf28811e89547fa163ea824f from "
|
||||
"cluster %s" % self.cluster.uuid,
|
||||
"name": "fake_name_2",
|
||||
"provisioning_status": "ERROR"
|
||||
"provisioning_status": "ERROR",
|
||||
"vip_port_id": "c17c1a6e-1868-11e9-84cd-00224d6b7bc1"
|
||||
},
|
||||
]
|
||||
}
|
||||
@ -67,7 +70,24 @@ class OctaviaTest(base.TestCase):
|
||||
mock_octavie_client.load_balancer_delete.assert_has_calls(calls)
|
||||
|
||||
@mock.patch('magnum.common.clients.OpenStackClients')
|
||||
def test_delete_loadbalancers_with_invalid_lb(self, mock_clients):
|
||||
def test_delete_loadbalancers_no_candidate(self, mock_clients):
|
||||
mock_lbs = {
|
||||
"loadbalancers": []
|
||||
}
|
||||
mock_octavie_client = mock.MagicMock()
|
||||
mock_octavie_client.load_balancer_list.return_value = mock_lbs
|
||||
osc = mock.MagicMock()
|
||||
mock_clients.return_value = osc
|
||||
osc.octavia.return_value = mock_octavie_client
|
||||
|
||||
octavia.delete_loadbalancers(self.context, self.cluster)
|
||||
|
||||
self.assertFalse(mock_octavie_client.load_balancer_delete.called)
|
||||
|
||||
@mock.patch("magnum.common.neutron.delete_floatingip")
|
||||
@mock.patch('magnum.common.clients.OpenStackClients')
|
||||
def test_delete_loadbalancers_with_invalid_lb(self, mock_clients,
|
||||
mock_delete_fip):
|
||||
osc = mock.MagicMock()
|
||||
mock_clients.return_value = osc
|
||||
mock_octavie_client = mock.MagicMock()
|
||||
@ -81,7 +101,8 @@ class OctaviaTest(base.TestCase):
|
||||
"ad3080723f1c211e88adbfa163ee1203 from "
|
||||
"cluster %s" % self.cluster.uuid,
|
||||
"name": "fake_name_1",
|
||||
"provisioning_status": "ACTIVE"
|
||||
"provisioning_status": "ACTIVE",
|
||||
"vip_port_id": "c17c1a6e-1868-11e9-84cd-00224d6b7bc1"
|
||||
},
|
||||
{
|
||||
"id": "fake_id_2",
|
||||
@ -89,7 +110,8 @@ class OctaviaTest(base.TestCase):
|
||||
"a9f9ba08cf28811e89547fa163ea824f from "
|
||||
"cluster %s" % self.cluster.uuid,
|
||||
"name": "fake_name_2",
|
||||
"provisioning_status": "PENDING_UPDATE"
|
||||
"provisioning_status": "PENDING_UPDATE",
|
||||
"vip_port_id": "b4ca07d1-a31e-43e2-891a-7d14f419f342"
|
||||
},
|
||||
]
|
||||
}
|
||||
@ -104,8 +126,9 @@ class OctaviaTest(base.TestCase):
|
||||
mock_octavie_client.load_balancer_delete.assert_called_once_with(
|
||||
"fake_id_1", cascade=True)
|
||||
|
||||
@mock.patch("magnum.common.neutron.delete_floatingip")
|
||||
@mock.patch('magnum.common.clients.OpenStackClients')
|
||||
def test_delete_loadbalancers_timeout(self, mock_clients):
|
||||
def test_delete_loadbalancers_timeout(self, mock_clients, mock_delete_fip):
|
||||
osc = mock.MagicMock()
|
||||
mock_clients.return_value = osc
|
||||
mock_octavie_client = mock.MagicMock()
|
||||
@ -119,7 +142,8 @@ class OctaviaTest(base.TestCase):
|
||||
"ad3080723f1c211e88adbfa163ee1203 from "
|
||||
"cluster %s" % self.cluster.uuid,
|
||||
"name": "fake_name_1",
|
||||
"provisioning_status": "ACTIVE"
|
||||
"provisioning_status": "ACTIVE",
|
||||
"vip_port_id": "b4ca07d1-a31e-43e2-891a-7d14f419f342"
|
||||
},
|
||||
{
|
||||
"id": "fake_id_2",
|
||||
@ -127,7 +151,8 @@ class OctaviaTest(base.TestCase):
|
||||
"a9f9ba08cf28811e89547fa163ea824f from "
|
||||
"cluster %s" % self.cluster.uuid,
|
||||
"name": "fake_name_2",
|
||||
"provisioning_status": "ACTIVE"
|
||||
"provisioning_status": "ACTIVE",
|
||||
"vip_port_id": "c17c1a6e-1868-11e9-84cd-00224d6b7bc1"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
fixes:
|
||||
- |
|
||||
In kubernetes cluster, a floating IP is created and associated with the vip
|
||||
of a load balancer which is created corresponding to the service of
|
||||
LoadBalancer type inside kubernetes, it should be deleted when the cluster
|
||||
is deleted.
|
Loading…
Reference in New Issue
Block a user