kuryr-kubernetes/kuryr_kubernetes/cni/binding/base.py
Michał Dulko 24c4b351ea Remove the pod interface even if VIF is gone
It seems that latest crio versions moved removal of network namespace to
later in the container lifecycle. This means that sometimes the network
namespace of a container will hang after pod is already gone from the
API and Kuryr assigned it's VIF to another pod. This might cause VLAN ID
conflicts, but normally that's not an issue as kuryr-daemon is removing
interfaces from container namespace on CNI DEL.

It may however happen that if kuryr-daemon was down when CNI DEL
happened, the info about the VIF saved in KuryrPort will already be
gone. In that case we simply returned success to the CNI without doing
any unplugging. Now if the netns is not removed immediately after that
we might end up with VLAN ID conflicts.

This commit makes sure that even if VIF info is gone, kuryr-daemon will
at least attempt to remove the container interface from the container
netns. This should limit the problem.

Change-Id: Ie7d4966473c83554786e79aea0d28a26de902a66
Closes-Bug: 1892388
2020-08-26 15:16:59 +02:00

178 lines
6.4 KiB
Python

# Copyright (c) 2016 Mirantis, 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.
import abc
import errno
import os_vif
from os_vif.objects import vif as osv_objects
from oslo_log import log as logging
import pyroute2
from stevedore import driver as stv_driver
from kuryr_kubernetes import config
from kuryr_kubernetes import constants
from kuryr_kubernetes import utils
_BINDING_NAMESPACE = 'kuryr_kubernetes.cni.binding'
LOG = logging.getLogger(__name__)
class BaseBindingDriver(object, metaclass=abc.ABCMeta):
"""Interface to attach ports to pods."""
def _remove_ifaces(self, ipdb, ifnames, netns='host'):
"""Check if any of `ifnames` exists and remove it.
:param ipdb: ipdb of the network namespace to check
:param ifnames: iterable of interface names to remove
:param netns: network namespace name (used for logging)
"""
for ifname in ifnames:
if ifname in ipdb.interfaces:
LOG.warning('Found hanging interface %(ifname)s inside '
'%(netns)s netns. Most likely it is a leftover '
'from a kuryr-daemon restart. Trying to delete '
'it.', {'ifname': ifname, 'netns': netns})
with ipdb.interfaces[ifname] as iface:
iface.remove()
@abc.abstractmethod
def connect(self, vif, ifname, netns, container_id):
raise NotImplementedError()
@abc.abstractmethod
def disconnect(self, vif, ifname, netns, container_id):
raise NotImplementedError()
def _get_binding_driver(vif):
mgr = stv_driver.DriverManager(namespace=_BINDING_NAMESPACE,
name=type(vif).__name__,
invoke_on_load=True)
return mgr.driver
def get_ipdb(netns=None):
if netns:
netns = utils.convert_netns(netns)
ipdb = pyroute2.IPDB(nl=pyroute2.NetNS(netns))
else:
ipdb = pyroute2.IPDB()
return ipdb
def _enable_ipv6(netns):
# Docker disables IPv6 for --net=none containers
# TODO(apuimedo) remove when it is no longer the case
try:
netns = utils.convert_netns(netns)
path = utils.convert_netns('/proc/self/ns/net')
self_ns_fd = open(path)
pyroute2.netns.setns(netns)
path = utils.convert_netns('/proc/sys/net/ipv6/conf/all/disable_ipv6')
with open(path, 'w') as disable_ipv6:
disable_ipv6.write('0')
except Exception:
raise
finally:
pyroute2.netns.setns(self_ns_fd)
def _configure_l3(vif, ifname, netns, is_default_gateway):
with get_ipdb(netns) as ipdb:
with ipdb.interfaces[ifname] as iface:
for subnet in vif.network.subnets.objects:
if subnet.cidr.version == 6:
_enable_ipv6(netns)
for fip in subnet.ips.objects:
iface.add_ip('%s/%s' % (fip.address,
subnet.cidr.prefixlen))
routes = ipdb.routes
for subnet in vif.network.subnets.objects:
for route in subnet.routes.objects:
routes.add(gateway=str(route.gateway),
dst=str(route.cidr)).commit()
if is_default_gateway and hasattr(subnet, 'gateway'):
try:
routes.add(gateway=str(subnet.gateway),
dst='default').commit()
except pyroute2.NetlinkError as ex:
if ex.code != errno.EEXIST:
raise
LOG.debug("Default route already exists in pod for vif=%s."
" Did not overwrite with requested gateway=%s",
vif, subnet.gateway)
def _need_configure_l3(vif):
if isinstance(vif, osv_objects.VIFVHostUser):
return False
if not hasattr(vif, 'physnet'):
# NOTE(danil): non-sriov vif. Figure out if it is nested-dpdk
if vif.obj_attr_is_set('port_profile') and hasattr(vif.port_profile,
'l3_setup'):
return vif.port_profile.l3_setup
# NOTE(danil): by default kuryr-kubernetes has to setup l3
return True
# NOTE(danil): sriov vif. Figure out what driver should compute it
physnet = vif.physnet
mapping_res = config.CONF.sriov.physnet_resource_mappings
try:
resource = mapping_res[physnet]
except KeyError:
LOG.exception("No resource name for physnet %s", physnet)
raise
mapping_driver = config.CONF.sriov.resource_driver_mappings
try:
driver_name = mapping_driver[resource]
except KeyError:
LOG.exception("No driver for resource_name %s", resource)
raise
if driver_name in constants.USERSPACE_DRIVERS:
LOG.info("_configure_l3 will not be called for vif %s "
"because of it's driver", vif)
return False
# NOTE(danil): sriov vif computed by kernel driver
return True
def connect(vif, instance_info, ifname, netns=None, report_health=None,
is_default_gateway=True, container_id=None):
driver = _get_binding_driver(vif)
if report_health:
report_health(driver.is_alive())
os_vif.plug(vif, instance_info)
driver.connect(vif, ifname, netns, container_id)
if _need_configure_l3(vif):
_configure_l3(vif, ifname, netns, is_default_gateway)
def disconnect(vif, instance_info, ifname, netns=None, report_health=None,
container_id=None, **kwargs):
driver = _get_binding_driver(vif)
if report_health:
report_health(driver.is_alive())
driver.disconnect(vif, ifname, netns, container_id)
os_vif.unplug(vif, instance_info)
def cleanup(ifname, netns):
with get_ipdb(netns) as c_ipdb:
if ifname in c_ipdb.interfaces:
with c_ipdb.interfaces[ifname] as iface:
iface.remove()