Merge "Attempt to autodetect interface in nested setups"

This commit is contained in:
Zuul 2020-07-03 09:21:30 +00:00 committed by Gerrit Code Review
commit 58617a9b54
2 changed files with 104 additions and 1 deletions

View File

@ -16,6 +16,7 @@ import abc
import errno
from oslo_log import log as logging
import psutil
import pyroute2
from kuryr_kubernetes.cni.binding import base as b_base
@ -27,6 +28,7 @@ from kuryr_kubernetes import utils
VLAN_KIND = 'vlan'
MACVLAN_KIND = 'macvlan'
MACVLAN_MODE_BRIDGE = 'bridge'
KUBELET_PORT = 10250
LOG = logging.getLogger(__name__)
@ -41,6 +43,44 @@ class NestedDriver(health.HealthHandler, b_base.BaseBindingDriver,
def _get_iface_create_args(self, vif):
raise NotImplementedError()
def _detect_iface_name(self, h_ipdb):
# Let's try config first
if config.CONF.binding.link_iface in h_ipdb.interfaces:
LOG.debug(f'Using configured interface '
f'{config.CONF.binding.link_iface} as bridge interface.')
return config.CONF.binding.link_iface
# Then let's try choosing the one where kubelet listens to
conns = [x for x in psutil.net_connections()
if x.status == psutil.CONN_LISTEN
and x.laddr.port == KUBELET_PORT]
if len(conns) == 1:
lookup_addr = conns[0].laddr.ip
for name, iface in h_ipdb.interfaces.items():
if type(name) is int: # Skip ones duplicated by id
continue
for addr in iface['ipaddr']:
if addr[0] == lookup_addr:
LOG.debug(f'Using kubelet bind interface {name} as '
f'bridge interface.')
return name
# Alright, just try the first non-loopback interface
for name, iface in h_ipdb.interfaces.items():
if type(name) is int: # Skip ones duplicated by id
continue
if iface['flags'] & pyroute2.netlink.rtnl.ifinfmsg.IFF_LOOPBACK:
continue # Skip loopback
LOG.debug(f'Using interface {name} as bridge interface.')
return name
raise exceptions.CNIBindingFailure('Cannot find bridge interface for '
'nested driver to use. Please set '
'[binding]link_iface option.')
def connect(self, vif, ifname, netns, container_id):
# NOTE(vikasc): Ideally 'ifname' should be used here but instead a
# temporary name is being used while creating the device for
@ -67,7 +107,7 @@ class NestedDriver(health.HealthHandler, b_base.BaseBindingDriver,
with b_base.get_ipdb() as h_ipdb:
# TODO(vikasc): evaluate whether we should have stevedore
# driver for getting the link device.
vm_iface_name = config.CONF.binding.link_iface
vm_iface_name = self._detect_iface_name(h_ipdb)
mtu = h_ipdb.interfaces[vm_iface_name].mtu
if mtu != vif.network.mtu:
# NOTE(dulek): This might happen if Neutron and DHCP agent

View File

@ -12,6 +12,7 @@
# 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 collections
import os
from unittest import mock
import uuid
@ -23,6 +24,7 @@ from oslo_config import cfg
from oslo_utils import uuidutils
from kuryr_kubernetes.cni.binding import base
from kuryr_kubernetes.cni.binding import nested
from kuryr_kubernetes.cni.binding import sriov
from kuryr_kubernetes.cni.binding import vhostuser
from kuryr_kubernetes import constants as k_const
@ -180,6 +182,67 @@ class TestBridgeDriver(TestDriverMixin, test_base.TestCase):
self._test_disconnect()
class TestNestedDriver(TestDriverMixin, test_base.TestCase):
def setUp(self):
super(TestNestedDriver, self).setUp()
ifaces = {
'lo': {'flags': 0x8, 'ipaddr': (('127.0.0.1', 8),)},
'first': {'flags': 0, 'ipaddr': (('192.168.0.1', 8),)},
'kubelet': {'flags': 0, 'ipaddr': (('192.168.1.1', 8),)},
'bridge': {'flags': 0, 'ipaddr': (('192.168.2.1', 8),)},
}
self.h_ipdb = mock.Mock(interfaces=ifaces)
self.h_ipdb_loopback = mock.Mock(interfaces=ifaces)
self.sconn = collections.namedtuple(
'sconn', ['fd', 'family', 'type', 'laddr', 'raddr', 'status',
'pid'])
self.addr = collections.namedtuple('addr', ['ip', 'port'])
@mock.patch.multiple(nested.NestedDriver, __abstractmethods__=set())
def test_detect_config(self):
driver = nested.NestedDriver()
self.addCleanup(CONF.clear_override, 'link_iface', group='binding')
CONF.set_override('link_iface', 'bridge', group='binding')
iface = driver._detect_iface_name(self.h_ipdb)
self.assertEqual('bridge', iface)
@mock.patch.multiple(nested.NestedDriver, __abstractmethods__=set())
@mock.patch('psutil.net_connections')
def test_detect_kubelet_port(self, m_net_connections):
driver = nested.NestedDriver()
m_net_connections.return_value = [
self.sconn(-1, 2, 2, laddr=self.addr(ip='192.168.1.1', port=53),
raddr=(), status='LISTEN', pid=None),
self.sconn(-1, 2, 2, laddr=self.addr(ip='192.168.1.1', port=10250),
raddr=(), status='ESTABLISHED', pid=None),
self.sconn(-1, 2, 2, laddr=self.addr(ip='192.168.1.1', port=10250),
raddr=(), status='LISTEN', pid=None),
]
iface = driver._detect_iface_name(self.h_ipdb)
self.assertEqual('kubelet', iface)
@mock.patch.multiple(nested.NestedDriver, __abstractmethods__=set())
@mock.patch('psutil.net_connections')
def test_detect_non_loopback(self, m_net_connections):
driver = nested.NestedDriver()
m_net_connections.return_value = []
iface = driver._detect_iface_name(self.h_ipdb)
self.assertEqual('first', iface)
@mock.patch.multiple(nested.NestedDriver, __abstractmethods__=set())
@mock.patch('psutil.net_connections')
def test_detect_none(self, m_net_connections):
driver = nested.NestedDriver()
m_net_connections.return_value = []
self.h_ipdb.interfaces = {
'lo': {'flags': 0x8, 'ipaddr': (('127.0.0.1', 8),)},
}
self.assertRaises(exceptions.CNIBindingFailure,
driver._detect_iface_name, self.h_ipdb)
class TestNestedVlanDriver(TestDriverMixin, test_base.TestCase):
def setUp(self):
super(TestNestedVlanDriver, self).setUp()