Roman Dobosz 5afa4925fc Fix potential issue with network/subnet name length.
In OpenStack Neutron and Octavia, name and descriptions of the objects
are limited to the 255 characters, while on Kubernetes names are limited
to the 253 characters. Kuryr often creates names for the networks and
subnets using Kubernetes object name with additional prefix and suffix,
which may exceed allowed character limit. In this patch there is
proposed solution for this issue by simply truncate kubernetes resource
name, while keeping prefix and suffix intact, to fit the Neutron name
limit.

Change-Id: I6e404104034f11593fc313797ad464458bbdf82d
2022-04-07 15:57:50 +02:00

200 lines
8.3 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 kuryr.lib import constants as kl_const
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 openstack import exceptions as os_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:
net_crd_path = (f"{constants.K8S_API_CRD_NAMESPACES}/"
f"{namespace}/kuryrnetworks/{namespace}")
net_crd = kubernetes.get(net_crd_path)
except exceptions.K8sResourceNotFound:
LOG.debug("Kuryrnetwork resource not yet created, retrying...")
raise exceptions.ResourceNotReady(namespace)
except exceptions.K8sClientException:
LOG.exception("Kubernetes Client Exception.")
raise
try:
subnet_id = net_crd['status']['subnetId']
except KeyError:
LOG.debug("Subnet for namespace %s not yet created, retrying.",
namespace)
raise exceptions.ResourceNotReady(namespace)
return subnet_id
def delete_namespace_subnet(self, net_crd):
subnet_id = net_crd['status'].get('subnetId')
net_id = net_crd['status'].get('netId')
if net_id:
self._delete_namespace_network_resources(subnet_id, net_id)
def _delete_namespace_network_resources(self, subnet_id, net_id):
os_net = clients.get_network_client()
if subnet_id:
router_id = oslo_cfg.CONF.namespace_subnet.pod_router
try:
clients.handle_neutron_errors(
os_net.remove_interface_from_router, router_id,
subnet_id=subnet_id)
except os_exc.NotFoundException as e:
# Nothing to worry about, either router or subnet is no more,
# or subnet is already detached.
LOG.debug(e.message)
pass
except os_exc.SDKException:
LOG.exception("Error deleting subnet %(subnet)s from router "
"%(router)s.",
{'subnet': subnet_id, 'router': router_id})
raise
try:
os_net.delete_network(net_id)
except os_exc.ConflictException:
LOG.warning("One or more ports in use on the network %s. "
"Deleting leftovers ports before retrying", net_id)
leftover_ports = os_net.ports(network_id=net_id)
for leftover_port in leftover_ports:
# NOTE(dulek): '' is there because Neutron seems to unset
# device_owner on detach.
if leftover_port.device_owner not in ['', 'trunk:subport',
kl_const.DEVICE_OWNER]:
continue
c_utils.delete_port(leftover_port)
raise exceptions.ResourceNotReady(net_id)
except os_exc.SDKException:
LOG.exception("Error deleting network %s.", net_id)
raise
def create_network(self, ns_name, project_id):
os_net = clients.get_network_client()
net_name = c_utils.get_resource_name(ns_name, prefix='ns/',
suffix='-net')
tags = oslo_cfg.CONF.neutron_defaults.resource_tags
if tags:
networks = os_net.networks(name=net_name, tags=tags)
else:
networks = os_net.networks(name=net_name)
try:
# NOTE(ltomasbo): only one network must exists
return next(networks).id
except StopIteration:
LOG.debug('Network does not exist. Creating.')
mtu_cfg = oslo_cfg.CONF.neutron_defaults.network_device_mtu
attrs = {'name': net_name, 'project_id': project_id}
if mtu_cfg:
attrs['mtu'] = mtu_cfg
# create network with namespace as name
try:
neutron_net = os_net.create_network(**attrs)
c_utils.tag_neutron_resources([neutron_net])
except os_exc.SDKException:
LOG.exception("Error creating neutron resources for the namespace "
"%s", ns_name)
raise
return neutron_net.id
def create_subnet(self, ns_name, project_id, net_id):
os_net = clients.get_network_client()
subnet_name = c_utils.get_resource_name(ns_name, prefix='ns/',
suffix='-subnet')
tags = oslo_cfg.CONF.neutron_defaults.resource_tags
if tags:
subnets = os_net.subnets(name=subnet_name, tags=tags)
else:
subnets = os_net.subnets(name=subnet_name)
try:
# NOTE(ltomasbo): only one subnet must exists
subnet = next(subnets)
return subnet.id, subnet.cidr
except StopIteration:
LOG.debug('Subnet does not exist. Creating.')
# create subnet with namespace as name
subnet_pool_id = oslo_cfg.CONF.namespace_subnet.pod_subnet_pool
ip_version = utils.get_subnetpool_version(subnet_pool_id)
try:
neutron_subnet = (os_net
.create_subnet(network_id=net_id,
ip_version=ip_version,
name=subnet_name,
enable_dhcp=False,
subnetpool_id=subnet_pool_id,
project_id=project_id))
except os_exc.ConflictException:
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([neutron_subnet])
return neutron_subnet.id, neutron_subnet.cidr
def add_subnet_to_router(self, subnet_id):
os_net = clients.get_network_client()
router_id = oslo_cfg.CONF.namespace_subnet.pod_router
try:
# connect the subnet to the router
os_net.add_interface_to_router(router_id, subnet_id=subnet_id)
except os_exc.BadRequestException:
LOG.debug("Subnet %s already connected to the router", subnet_id)
except os_exc.SDKException:
LOG.exception("Error attaching the subnet %s to the router",
subnet_id)
raise
return router_id