
When a namespace is created-deleted-created, the last namespace creation event might notices that a KuryrNet CRD still exists, as it can take a while for the previous deletion event to complete and will trigger again another deletion causing NotFound SubPort on Trunk error. This commit handles the exception by ignoring the port deletion. Change-Id: Id0683a00b2c90529e0106296b5f157cf6885388b Closes-bug: 1854096
230 lines
10 KiB
Python
230 lines
10 KiB
Python
# Copyright 2018 Red Hat, Inc.
|
|
#
|
|
# 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.
|
|
|
|
from kuryr.lib._i18n import _
|
|
from oslo_config import cfg as oslo_cfg
|
|
from oslo_log import log as logging
|
|
|
|
from kuryr_kubernetes import clients
|
|
from kuryr_kubernetes import constants
|
|
from kuryr_kubernetes.controller.drivers import default_subnet
|
|
from kuryr_kubernetes.controller.drivers import utils as c_utils
|
|
from kuryr_kubernetes import exceptions
|
|
from kuryr_kubernetes import utils
|
|
|
|
from neutronclient.common import exceptions as n_exc
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
namespace_subnet_driver_opts = [
|
|
oslo_cfg.StrOpt('pod_router',
|
|
help=_("Default Neutron router ID where pod subnet(s) is "
|
|
"connected")),
|
|
oslo_cfg.StrOpt('pod_subnet_pool',
|
|
help=_("Default Neutron subnet pool ID where pod subnets "
|
|
"get their cidr from")),
|
|
]
|
|
|
|
oslo_cfg.CONF.register_opts(namespace_subnet_driver_opts, "namespace_subnet")
|
|
|
|
|
|
class NamespacePodSubnetDriver(default_subnet.DefaultPodSubnetDriver):
|
|
"""Provides subnet for Pod port based on a Pod's namespace."""
|
|
|
|
def get_subnets(self, pod, project_id):
|
|
pod_namespace = pod['metadata']['namespace']
|
|
return self.get_namespace_subnet(pod_namespace)
|
|
|
|
def get_namespace_subnet(self, namespace, subnet_id=None):
|
|
if not subnet_id:
|
|
subnet_id = self._get_namespace_subnet_id(namespace)
|
|
return {subnet_id: utils.get_subnet(subnet_id)}
|
|
|
|
def _get_namespace_subnet_id(self, namespace):
|
|
kubernetes = clients.get_kubernetes_client()
|
|
try:
|
|
ns = kubernetes.get('%s/namespaces/%s' % (constants.K8S_API_BASE,
|
|
namespace))
|
|
except exceptions.K8sResourceNotFound:
|
|
LOG.warning("Namespace %s not found", namespace)
|
|
raise
|
|
except exceptions.K8sClientException:
|
|
LOG.exception("Kubernetes Client Exception.")
|
|
raise exceptions.ResourceNotReady(namespace)
|
|
|
|
try:
|
|
annotations = ns['metadata']['annotations']
|
|
net_crd_name = annotations[constants.K8S_ANNOTATION_NET_CRD]
|
|
except KeyError:
|
|
LOG.debug("Namespace missing CRD annotations for selecting "
|
|
"the corresponding subnet.")
|
|
raise exceptions.ResourceNotReady(namespace)
|
|
|
|
try:
|
|
net_crd = kubernetes.get('%s/kuryrnets/%s' % (
|
|
constants.K8S_API_CRD, net_crd_name))
|
|
except exceptions.K8sResourceNotFound:
|
|
LOG.debug("Kuryrnet resource not yet created, retrying...")
|
|
raise exceptions.ResourceNotReady(net_crd_name)
|
|
except exceptions.K8sClientException:
|
|
LOG.exception("Kubernetes Client Exception.")
|
|
raise
|
|
|
|
return net_crd['spec']['subnetId']
|
|
|
|
def delete_namespace_subnet(self, net_crd):
|
|
subnet_id = net_crd['spec']['subnetId']
|
|
net_id = net_crd['spec']['netId']
|
|
|
|
self._delete_namespace_network_resources(subnet_id, net_id)
|
|
|
|
def _delete_namespace_network_resources(self, subnet_id, net_id):
|
|
neutron = clients.get_neutron_client()
|
|
if subnet_id:
|
|
router_id = oslo_cfg.CONF.namespace_subnet.pod_router
|
|
try:
|
|
neutron.remove_interface_router(router_id,
|
|
{"subnet_id": subnet_id})
|
|
except n_exc.NotFound:
|
|
LOG.debug("Subnet %(subnet)s not attached to router "
|
|
"%(router)s", {'subnet': subnet_id,
|
|
'router': router_id})
|
|
except n_exc.NeutronClientException:
|
|
LOG.exception("Error deleting subnet %(subnet)s from router "
|
|
"%(router)s.", {'subnet': subnet_id, 'router':
|
|
router_id})
|
|
raise
|
|
|
|
try:
|
|
neutron.delete_network(net_id)
|
|
except n_exc.NotFound:
|
|
LOG.debug("Neutron Network not found: %s", net_id)
|
|
except n_exc.NetworkInUseClient:
|
|
LOG.exception("One or more ports in use on the network %s. "
|
|
"Deleting leftovers ports before retrying", net_id)
|
|
leftover_ports = c_utils.get_ports_by_attrs(status='DOWN',
|
|
network_id=net_id)
|
|
for leftover_port in leftover_ports:
|
|
try:
|
|
neutron.delete_port(leftover_port['id'])
|
|
except n_exc.PortNotFoundClient:
|
|
LOG.debug("Port already deleted.")
|
|
except n_exc.NeutronClientException as e:
|
|
if "currently a subport for trunk" in str(e):
|
|
LOG.warning("Port %s is in DOWN status but still "
|
|
"associated to a trunk. This should not "
|
|
"happen. Trying to delete it from the "
|
|
"trunk.", leftover_port['id'])
|
|
# Get the trunk_id from the error message
|
|
trunk_id = (
|
|
str(e).split('trunk')[1].split('.')[0].strip())
|
|
try:
|
|
neutron.trunk_remove_subports(
|
|
trunk_id, {'sub_ports': [
|
|
{'port_id': leftover_port['id']}]})
|
|
except n_exc.NotFound:
|
|
LOG.debug("Port %s already removed from trunk %s",
|
|
leftover_port['id'], trunk_id)
|
|
else:
|
|
LOG.exception("Unexpected error deleting leftover "
|
|
"port %s. Skiping it and continue with "
|
|
"the other rest.", leftover_port['id'])
|
|
raise exceptions.ResourceNotReady(net_id)
|
|
except n_exc.NeutronClientException:
|
|
LOG.exception("Error deleting network %s.", net_id)
|
|
raise
|
|
|
|
def create_namespace_network(self, namespace, project_id):
|
|
neutron = clients.get_neutron_client()
|
|
|
|
router_id = oslo_cfg.CONF.namespace_subnet.pod_router
|
|
subnet_pool_id = oslo_cfg.CONF.namespace_subnet.pod_subnet_pool
|
|
|
|
# create network with namespace as name
|
|
network_name = "ns/" + namespace + "-net"
|
|
subnet_name = "ns/" + namespace + "-subnet"
|
|
try:
|
|
neutron_net = neutron.create_network(
|
|
{
|
|
"network": {
|
|
"name": network_name,
|
|
"project_id": project_id
|
|
}
|
|
}).get('network')
|
|
c_utils.tag_neutron_resources('networks', [neutron_net['id']])
|
|
|
|
# create a subnet within that network
|
|
try:
|
|
neutron_subnet = neutron.create_subnet(
|
|
{
|
|
"subnet": {
|
|
"network_id": neutron_net['id'],
|
|
"ip_version": 4,
|
|
"name": subnet_name,
|
|
"enable_dhcp": False,
|
|
"subnetpool_id": subnet_pool_id,
|
|
"project_id": project_id
|
|
}
|
|
}).get('subnet')
|
|
except n_exc.Conflict:
|
|
LOG.debug("Max number of retries on neutron side achieved, "
|
|
"raising ResourceNotReady to retry subnet creation "
|
|
"for %s", subnet_name)
|
|
raise exceptions.ResourceNotReady(subnet_name)
|
|
c_utils.tag_neutron_resources('subnets', [neutron_subnet['id']])
|
|
|
|
# connect the subnet to the router
|
|
neutron.add_interface_router(router_id,
|
|
{"subnet_id": neutron_subnet['id']})
|
|
except n_exc.NeutronClientException:
|
|
LOG.exception("Error creating neutron resources for the namespace "
|
|
"%s", namespace)
|
|
raise
|
|
return {'netId': neutron_net['id'],
|
|
'routerId': router_id,
|
|
'subnetId': neutron_subnet['id'],
|
|
'subnetCIDR': neutron_subnet['cidr']}
|
|
|
|
def rollback_network_resources(self, net_crd_spec, namespace):
|
|
neutron = clients.get_neutron_client()
|
|
try:
|
|
neutron.remove_interface_router(net_crd_spec['routerId'],
|
|
{'subnet_id':
|
|
net_crd_spec['subnetId']})
|
|
neutron.delete_network(net_crd_spec['netId'])
|
|
except n_exc.NeutronClientException:
|
|
LOG.exception("Failed to clean up network resources associated to "
|
|
"%(net_id)s, created for the namespace: "
|
|
"%(namespace)s." % {'net_id': net_crd_spec['netId'],
|
|
'namespace': namespace})
|
|
|
|
def cleanup_namespace_networks(self, namespace):
|
|
neutron = clients.get_neutron_client()
|
|
net_name = 'ns/' + namespace + '-net'
|
|
filters = {'name': net_name}
|
|
tags = oslo_cfg.CONF.neutron_defaults.resource_tags
|
|
if tags:
|
|
filters['tags'] = tags
|
|
networks = neutron.list_networks(**filters)['networks']
|
|
if networks:
|
|
for net in networks:
|
|
net_id = net['id']
|
|
subnets = net.get('subnets')
|
|
subnet_id = None
|
|
if subnets:
|
|
# NOTE(ltomasbo): Each network created by kuryr only has
|
|
# one subnet
|
|
subnet_id = subnets[0]
|
|
self._delete_namespace_network_resources(subnet_id, net_id)
|