Namespace deletion functionality for namespace_subnet driver
This patch extends the namespace_subnet driver to handle namespace deletion. It ensures the created resources during namespace creation are removed upon namespace deletion. Note it does not currently support deleting the extra ports created by the ports pool feature, so it should not be used if ports pool feature is enabled. A follow up patch will address this issue Partially Implements: blueprint network-namespace Change-Id: I2eed278dafacd5090a902bacfd366f7cdf9edca4
This commit is contained in:
parent
51c57ec31f
commit
c1e8f458d4
@ -108,3 +108,11 @@ Testing the network per namespace functionality
|
||||
$ curl 10.0.0.141
|
||||
demo-5995548848-lmmjc: HELLO! I AM ALIVE!!!
|
||||
|
||||
|
||||
6. And finally, to remove the namespace and all its resources, including
|
||||
openstack networks, kuryrnet CRD, svc, pods, you just need to do::
|
||||
|
||||
$ kubectl delete namespace test
|
||||
|
||||
Note it does not currently support deleting the extra ports created by the
|
||||
ports pool feature, so it should not be used if ports pool feature is enabled.
|
||||
|
@ -137,6 +137,14 @@ class PodSubnetsDriver(DriverBase):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def delete_namespace_subnet(self, kuryr_net_crd):
|
||||
"""Delete network resources associated to a namespace.
|
||||
|
||||
:param kuryr_net_crd: kuryrnet CRD obj dict that contains Neutron's
|
||||
network resources associated to a namespace
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def rollback_network_resources(self, router_id, net_id, subnet_id,
|
||||
namespace):
|
||||
"""Rollback created network resources for a namespace.
|
||||
|
@ -57,7 +57,7 @@ class NamespacePodSubnetDriver(default_subnet.DefaultPodSubnetDriver):
|
||||
|
||||
try:
|
||||
annotations = ns['metadata']['annotations']
|
||||
net_crd_url = annotations[constants.K8S_ANNOTATION_NET_CRD]
|
||||
net_crd_name = annotations[constants.K8S_ANNOTATION_NET_CRD]
|
||||
except KeyError:
|
||||
LOG.exception("Namespace missing CRD annotations for selecting "
|
||||
"the corresponding subnet.")
|
||||
@ -65,13 +65,40 @@ class NamespacePodSubnetDriver(default_subnet.DefaultPodSubnetDriver):
|
||||
|
||||
try:
|
||||
net_crd = kubernetes.get('%s/kuryrnets/%s' % (
|
||||
constants.K8S_API_CRD, net_crd_url))
|
||||
except exceptions.K8sClientException as ex:
|
||||
constants.K8S_API_CRD, net_crd_name))
|
||||
except exceptions.K8sClientException:
|
||||
LOG.exception("Kubernetes Client Exception.")
|
||||
raise ex
|
||||
raise
|
||||
|
||||
return net_crd['spec']['subnetId']
|
||||
|
||||
def delete_namespace_subnet(self, net_crd_name):
|
||||
router_id = oslo_cfg.CONF.namespace_subnet.pod_router
|
||||
|
||||
neutron = clients.get_neutron_client()
|
||||
kubernetes = clients.get_kubernetes_client()
|
||||
|
||||
try:
|
||||
net_crd = kubernetes.get('%s/kuryrnets/%s' % (
|
||||
constants.K8S_API_CRD, net_crd_name))
|
||||
except exceptions.K8sClientException:
|
||||
LOG.exception("Kubernetes Client Exception.")
|
||||
raise
|
||||
subnet_id = net_crd['spec']['subnetId']
|
||||
net_id = net_crd['spec']['netId']
|
||||
|
||||
try:
|
||||
neutron.remove_interface_router(router_id,
|
||||
{"subnet_id": subnet_id})
|
||||
neutron.delete_network(net_id)
|
||||
except n_exc.NotFound as ex:
|
||||
LOG.debug("Neutron resource not found: %s", ex)
|
||||
except n_exc.NeutronClientException:
|
||||
LOG.exception("Error deleting neutron resources.")
|
||||
raise
|
||||
|
||||
self._del_kuryrnet_crd(net_crd_name)
|
||||
|
||||
def create_namespace_network(self, namespace):
|
||||
neutron = clients.get_neutron_client()
|
||||
|
||||
@ -111,12 +138,12 @@ class NamespacePodSubnetDriver(default_subnet.DefaultPodSubnetDriver):
|
||||
try:
|
||||
net_crd = self._add_kuryrnet_crd(namespace, neutron_net['id'],
|
||||
router_id, neutron_subnet['id'])
|
||||
except exceptions.K8sClientException as ex:
|
||||
except exceptions.K8sClientException:
|
||||
LOG.exception("Kuryrnet CRD could not be added. Rolling back "
|
||||
"network resources created for the namespace.")
|
||||
self.rollback_network_resources(router_id, neutron_net['id'],
|
||||
neutron_subnet['id'], namespace)
|
||||
raise ex
|
||||
raise
|
||||
return net_crd
|
||||
|
||||
def rollback_network_resources(self, router_id, net_id, subnet_id,
|
||||
@ -152,8 +179,18 @@ class NamespacePodSubnetDriver(default_subnet.DefaultPodSubnetDriver):
|
||||
}
|
||||
try:
|
||||
kubernetes.post('%s/kuryrnets' % constants.K8S_API_CRD, net_crd)
|
||||
except exceptions.K8sClientException as ex:
|
||||
except exceptions.K8sClientException:
|
||||
LOG.exception("Kubernetes Client Exception creating kuryrnet "
|
||||
"CRD.")
|
||||
raise ex
|
||||
raise
|
||||
return net_crd
|
||||
|
||||
def _del_kuryrnet_crd(self, net_crd_name):
|
||||
kubernetes = clients.get_kubernetes_client()
|
||||
try:
|
||||
kubernetes.delete('%s/kuryrnets/%s' % (constants.K8S_API_CRD,
|
||||
net_crd_name))
|
||||
except exceptions.K8sClientException:
|
||||
LOG.exception("Kubernetes Client Exception deleting kuryrnet "
|
||||
"CRD.")
|
||||
raise
|
||||
|
@ -52,7 +52,13 @@ class NamespaceHandler(k8s_base.ResourceEventHandler):
|
||||
ns_name)
|
||||
|
||||
def on_deleted(self, namespace):
|
||||
pass
|
||||
LOG.debug("Deleting namespace: %s", namespace)
|
||||
net_crd = self._get_net_crd(namespace)
|
||||
if not net_crd:
|
||||
LOG.warning("There is no CRD annotated at the namespace %s",
|
||||
namespace)
|
||||
return
|
||||
self._drv_subnets.delete_namespace_subnet(net_crd)
|
||||
|
||||
def _get_net_crd(self, namespace):
|
||||
try:
|
||||
|
@ -117,6 +117,19 @@ class K8sClient(object):
|
||||
return response.json()
|
||||
raise exc.K8sClientException(response)
|
||||
|
||||
def delete(self, path):
|
||||
LOG.debug("Delete %(path)s", {'path': path})
|
||||
url = self._base_url + path
|
||||
header = {'Content-Type': 'application/json'}
|
||||
if self.token:
|
||||
header.update({'Authorization': 'Bearer %s' % self.token})
|
||||
|
||||
response = requests.delete(url, cert=self.cert,
|
||||
verify=self.verify_server, headers=header)
|
||||
if response.ok:
|
||||
return response.json()
|
||||
raise exc.K8sClientException(response)
|
||||
|
||||
def annotate(self, path, annotations, resource_version=None):
|
||||
"""Pushes a resource annotation to the K8s API resource
|
||||
|
||||
|
@ -172,6 +172,103 @@ class TestNamespacePodSubnetDriver(test_base.TestCase):
|
||||
m_driver, namespace)
|
||||
kubernetes.get.assert_called()
|
||||
|
||||
def test_delete_namespace_subnet(self):
|
||||
cls = subnet_drv.NamespacePodSubnetDriver
|
||||
m_driver = mock.MagicMock(spec=cls)
|
||||
|
||||
net_crd_name = 'test'
|
||||
net_id = mock.sentinel.net_id
|
||||
subnet_id = mock.sentinel.subnet_id
|
||||
crd = {
|
||||
'spec': {
|
||||
'subnetId': subnet_id,
|
||||
'netId': net_id
|
||||
}
|
||||
}
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
kubernetes = self.useFixture(k_fix.MockK8sClient()).client
|
||||
|
||||
kubernetes.get.return_value = crd
|
||||
|
||||
cls.delete_namespace_subnet(m_driver, net_crd_name)
|
||||
|
||||
kubernetes.get.assert_called_once()
|
||||
m_driver._del_kuryrnet_crd.assert_called_once_with(net_crd_name)
|
||||
neutron.remove_interface_router.assert_called_once()
|
||||
neutron.delete_network.assert_called_once_with(net_id)
|
||||
|
||||
def test_delete_namespace_subnet_k8s_get_exception(self):
|
||||
cls = subnet_drv.NamespacePodSubnetDriver
|
||||
m_driver = mock.MagicMock(spec=cls)
|
||||
|
||||
net_crd_name = 'test'
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
kubernetes = self.useFixture(k_fix.MockK8sClient()).client
|
||||
|
||||
kubernetes.get.side_effect = k_exc.K8sClientException
|
||||
|
||||
self.assertRaises(k_exc.K8sClientException,
|
||||
cls.delete_namespace_subnet, m_driver, net_crd_name)
|
||||
|
||||
kubernetes.get.assert_called_once()
|
||||
m_driver._del_kuryrnet_crd.assert_not_called()
|
||||
neutron.remove_interface_router.assert_not_called()
|
||||
neutron.delete_network.assert_not_called()
|
||||
|
||||
def test_delete_namespace_subnet_k8s_del_crd_exception(self):
|
||||
cls = subnet_drv.NamespacePodSubnetDriver
|
||||
m_driver = mock.MagicMock(spec=cls)
|
||||
|
||||
net_crd_name = 'test'
|
||||
net_id = mock.sentinel.net_id
|
||||
subnet_id = mock.sentinel.subnet_id
|
||||
crd = {
|
||||
'spec': {
|
||||
'subnetId': subnet_id,
|
||||
'netId': net_id
|
||||
}
|
||||
}
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
kubernetes = self.useFixture(k_fix.MockK8sClient()).client
|
||||
|
||||
kubernetes.get.return_value = crd
|
||||
m_driver._del_kuryrnet_crd.side_effect = k_exc.K8sClientException
|
||||
|
||||
self.assertRaises(k_exc.K8sClientException,
|
||||
cls.delete_namespace_subnet, m_driver, net_crd_name)
|
||||
|
||||
kubernetes.get.assert_called_once()
|
||||
m_driver._del_kuryrnet_crd.assert_called_once_with(net_crd_name)
|
||||
neutron.remove_interface_router.assert_called_once()
|
||||
neutron.delete_network.assert_called_once_with(net_id)
|
||||
|
||||
def test_delete_namespace_subnet_neutron_exception(self):
|
||||
cls = subnet_drv.NamespacePodSubnetDriver
|
||||
m_driver = mock.MagicMock(spec=cls)
|
||||
|
||||
net_crd_name = 'test'
|
||||
net_id = mock.sentinel.net_id
|
||||
subnet_id = mock.sentinel.subnet_id
|
||||
crd = {
|
||||
'spec': {
|
||||
'subnetId': subnet_id,
|
||||
'netId': net_id
|
||||
}
|
||||
}
|
||||
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||
kubernetes = self.useFixture(k_fix.MockK8sClient()).client
|
||||
|
||||
kubernetes.get.return_value = crd
|
||||
neutron.delete_network.side_effect = n_exc.NeutronClientException
|
||||
|
||||
self.assertRaises(n_exc.NeutronClientException,
|
||||
cls.delete_namespace_subnet, m_driver, net_crd_name)
|
||||
|
||||
kubernetes.get.assert_called_once()
|
||||
neutron.remove_interface_router.assert_called_once()
|
||||
neutron.delete_network.assert_called_once_with(net_id)
|
||||
m_driver._del_kuryrnet_crd.assert_not_called()
|
||||
|
||||
def test_create_namespace_network(self):
|
||||
cls = subnet_drv.NamespacePodSubnetDriver
|
||||
m_driver = mock.MagicMock(spec=cls)
|
||||
|
@ -57,6 +57,8 @@ class TestNamespaceHandler(test_base.TestCase):
|
||||
|
||||
self._create_namespace_network = (
|
||||
self._handler._drv_subnets.create_namespace_network)
|
||||
self._delete_namespace_subnet = (
|
||||
self._handler._drv_subnets.delete_namespace_subnet)
|
||||
self._get_net_crd = self._handler._get_net_crd
|
||||
self._set_net_crd = self._handler._set_net_crd
|
||||
self._rollback_network_resources = (
|
||||
@ -169,3 +171,21 @@ class TestNamespaceHandler(test_base.TestCase):
|
||||
self._namespace_name)
|
||||
self._set_net_crd.assert_called_once_with(self._namespace, net_crd)
|
||||
self._rollback_network_resources.assert_called_once()
|
||||
|
||||
def test_on_deleted(self):
|
||||
net_crd = self._get_crd()
|
||||
|
||||
self._get_net_crd.return_value = net_crd
|
||||
|
||||
namespace.NamespaceHandler.on_deleted(self._handler, self._namespace)
|
||||
|
||||
self._get_net_crd.assert_called_once_with(self._namespace)
|
||||
self._delete_namespace_subnet.assert_called_once_with(net_crd)
|
||||
|
||||
def test_on_deleted_missing_crd_annotation(self):
|
||||
self._get_net_crd.return_value = None
|
||||
|
||||
namespace.NamespaceHandler.on_deleted(self._handler, self._namespace)
|
||||
|
||||
self._get_net_crd.assert_called_once_with(self._namespace)
|
||||
self._delete_namespace_subnet.assert_not_called()
|
||||
|
@ -368,3 +368,29 @@ class TestK8sClient(test_base.TestCase):
|
||||
|
||||
self.assertRaises(exc.K8sClientException,
|
||||
self.client.post, path, body)
|
||||
|
||||
@mock.patch('requests.delete')
|
||||
def test_delete(self, m_delete):
|
||||
path = '/test'
|
||||
ret = {'test': 'value'}
|
||||
|
||||
m_resp = mock.MagicMock()
|
||||
m_resp.ok = True
|
||||
m_resp.json.return_value = ret
|
||||
m_delete.return_value = m_resp
|
||||
|
||||
self.assertEqual(ret, self.client.delete(path))
|
||||
m_delete.assert_called_once_with(self.base_url + path,
|
||||
headers=mock.ANY, cert=(None, None),
|
||||
verify=False)
|
||||
|
||||
@mock.patch('requests.delete')
|
||||
def test_delete_exception(self, m_delete):
|
||||
path = '/test'
|
||||
|
||||
m_resp = mock.MagicMock()
|
||||
m_resp.ok = False
|
||||
m_delete.return_value = m_resp
|
||||
|
||||
self.assertRaises(exc.K8sClientException,
|
||||
self.client.delete, path)
|
||||
|
Loading…
Reference in New Issue
Block a user