replaces enumeration method used to get a list of interfaces
ip_lib was parsing tunnel links incorrectly. We can create interface names with any character the filesystem supports (not '..', '/', ':'). Given this we do not know what to delimit on so parsing iproute2 output is probably not a good idea. I asked the iproute2 devs what the proper way we should get interface names is and was told NOT to parse iproute2 output but to use something like sysfs instead. http://www.spinics.net/lists/netdev/msg316577.html This patch pulls interfaces from sysfs (/sys/class/net) and verifies them via checking if they are links (bonding creates files for instance and needs to be skipped). Currently it is not possible without jumping through a ton of hoops to access a network namespace without iproute2 or cython, so we use ip to run find to find the correct sysfs directory. We also only call out to iproute2 _ONLY_ if needed. Change-Id: I07d1d297f07857d216649cccf717896574aac301 Closes-Bug: 1374663
This commit is contained in:
parent
0244e59df0
commit
c717a6365c
@ -13,4 +13,5 @@ lldpctl: CommandFilter, lldpctl, root
|
||||
|
||||
# ip_lib filters
|
||||
ip: IpFilter, ip, root
|
||||
find: RegExpFilter, find, root, find, /sys/class/net, -maxdepth, 1, -type, l, -printf, %.*
|
||||
ip_exec: IpNetnsExecFilter, ip, root
|
||||
|
@ -32,4 +32,5 @@ kill_metadata7: KillFilter, root, python2.7, -9
|
||||
|
||||
# ip_lib
|
||||
ip: IpFilter, ip, root
|
||||
find: RegExpFilter, find, root, find, /sys/class/net, -maxdepth, 1, -type, l, -printf, %.*
|
||||
ip_exec: IpNetnsExecFilter, ip, root
|
||||
|
@ -29,6 +29,7 @@ kill_radvd: KillFilter, root, /sbin/radvd, -9, -HUP
|
||||
|
||||
# ip_lib
|
||||
ip: IpFilter, ip, root
|
||||
find: RegExpFilter, find, root, find, /sys/class/net, -maxdepth, 1, -type, l, -printf, %.*
|
||||
ip_exec: IpNetnsExecFilter, ip, root
|
||||
|
||||
# For ip monitor
|
||||
|
@ -16,4 +16,5 @@ bridge: CommandFilter, bridge, root
|
||||
|
||||
# ip_lib
|
||||
ip: IpFilter, ip, root
|
||||
find: RegExpFilter, find, root, find, /sys/class/net, -maxdepth, 1, -type, l, -printf, %.*
|
||||
ip_exec: IpNetnsExecFilter, ip, root
|
||||
|
@ -19,4 +19,5 @@ xe: CommandFilter, xe, root
|
||||
|
||||
# ip_lib
|
||||
ip: IpFilter, ip, root
|
||||
find: RegExpFilter, find, root, find, /sys/class/net, -maxdepth, 1, -type, l, -printf, %.*
|
||||
ip_exec: IpNetnsExecFilter, ip, root
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
import eventlet
|
||||
import netaddr
|
||||
import os
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
@ -32,11 +33,8 @@ OPTS = [
|
||||
|
||||
|
||||
LOOPBACK_DEVNAME = 'lo'
|
||||
# NOTE(ethuleau): depend of the version of iproute2, the vlan
|
||||
# interface details vary.
|
||||
VLAN_INTERFACE_DETAIL = ['vlan protocol 802.1q',
|
||||
'vlan protocol 802.1Q',
|
||||
'vlan id']
|
||||
|
||||
SYS_NET_PATH = '/sys/class/net'
|
||||
|
||||
|
||||
class SubProcessBase(object):
|
||||
@ -93,22 +91,27 @@ class IPWrapper(SubProcessBase):
|
||||
|
||||
def get_devices(self, exclude_loopback=False):
|
||||
retval = []
|
||||
output = self._run(['o', 'd'], 'link', ('list',))
|
||||
for line in output.split('\n'):
|
||||
if '<' not in line:
|
||||
if self.namespace:
|
||||
# we call out manually because in order to avoid screen scraping
|
||||
# iproute2 we use find to see what is in the sysfs directory, as
|
||||
# suggested by Stephen Hemminger (iproute2 dev).
|
||||
output = utils.execute(['ip', 'netns', 'exec', self.namespace,
|
||||
'find', SYS_NET_PATH, '-maxdepth', '1',
|
||||
'-type', 'l', '-printf', '%f '],
|
||||
run_as_root=True,
|
||||
log_fail_as_error=self.log_fail_as_error
|
||||
).split()
|
||||
else:
|
||||
output = (
|
||||
i for i in os.listdir(SYS_NET_PATH)
|
||||
if os.path.islink(os.path.join(SYS_NET_PATH, i))
|
||||
)
|
||||
|
||||
for name in output:
|
||||
if exclude_loopback and name == LOOPBACK_DEVNAME:
|
||||
continue
|
||||
tokens = line.split(' ', 2)
|
||||
if len(tokens) == 3:
|
||||
if any(v in tokens[2] for v in VLAN_INTERFACE_DETAIL):
|
||||
delimiter = '@'
|
||||
else:
|
||||
delimiter = ':'
|
||||
name = tokens[1].rpartition(delimiter)[0].strip()
|
||||
retval.append(IPDevice(name, namespace=self.namespace))
|
||||
|
||||
if exclude_loopback and name == LOOPBACK_DEVNAME:
|
||||
continue
|
||||
|
||||
retval.append(IPDevice(name, namespace=self.namespace))
|
||||
return retval
|
||||
|
||||
def add_tuntap(self, name, mode='tap'):
|
||||
|
@ -17,6 +17,7 @@ import mock
|
||||
import netaddr
|
||||
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.agent.linux import utils # noqa
|
||||
from neutron.common import exceptions
|
||||
from neutron.tests import base
|
||||
|
||||
@ -235,49 +236,25 @@ class TestIpWrapper(base.BaseTestCase):
|
||||
self.execute_p = mock.patch.object(ip_lib.IPWrapper, '_execute')
|
||||
self.execute = self.execute_p.start()
|
||||
|
||||
def test_get_devices(self):
|
||||
self.execute.return_value = '\n'.join(LINK_SAMPLE)
|
||||
@mock.patch('os.path.islink')
|
||||
@mock.patch('os.listdir', return_value=['lo'])
|
||||
def test_get_devices(self, mocked_listdir, mocked_islink):
|
||||
retval = ip_lib.IPWrapper().get_devices()
|
||||
self.assertEqual(retval,
|
||||
[ip_lib.IPDevice('lo'),
|
||||
ip_lib.IPDevice('eth0'),
|
||||
ip_lib.IPDevice('br-int'),
|
||||
ip_lib.IPDevice('gw-ddc717df-49'),
|
||||
ip_lib.IPDevice('foo:foo'),
|
||||
ip_lib.IPDevice('foo@foo'),
|
||||
ip_lib.IPDevice('foo:foo@foo'),
|
||||
ip_lib.IPDevice('foo@foo:foo'),
|
||||
ip_lib.IPDevice('bar.9'),
|
||||
ip_lib.IPDevice('bar'),
|
||||
ip_lib.IPDevice('bar:bar'),
|
||||
ip_lib.IPDevice('bar@bar'),
|
||||
ip_lib.IPDevice('bar:bar@bar'),
|
||||
ip_lib.IPDevice('bar@bar:bar')])
|
||||
mocked_islink.assert_called_once_with('/sys/class/net/lo')
|
||||
self.assertEqual(retval, [ip_lib.IPDevice('lo')])
|
||||
|
||||
self.execute.assert_called_once_with(['o', 'd'], 'link', ('list',),
|
||||
log_fail_as_error=True)
|
||||
|
||||
def test_get_devices_malformed_line(self):
|
||||
self.execute.return_value = '\n'.join(LINK_SAMPLE + ['gibberish'])
|
||||
retval = ip_lib.IPWrapper().get_devices()
|
||||
self.assertEqual(retval,
|
||||
[ip_lib.IPDevice('lo'),
|
||||
ip_lib.IPDevice('eth0'),
|
||||
ip_lib.IPDevice('br-int'),
|
||||
ip_lib.IPDevice('gw-ddc717df-49'),
|
||||
ip_lib.IPDevice('foo:foo'),
|
||||
ip_lib.IPDevice('foo@foo'),
|
||||
ip_lib.IPDevice('foo:foo@foo'),
|
||||
ip_lib.IPDevice('foo@foo:foo'),
|
||||
ip_lib.IPDevice('bar.9'),
|
||||
ip_lib.IPDevice('bar'),
|
||||
ip_lib.IPDevice('bar:bar'),
|
||||
ip_lib.IPDevice('bar@bar'),
|
||||
ip_lib.IPDevice('bar:bar@bar'),
|
||||
ip_lib.IPDevice('bar@bar:bar')])
|
||||
|
||||
self.execute.assert_called_once_with(['o', 'd'], 'link', ('list',),
|
||||
log_fail_as_error=True)
|
||||
@mock.patch('neutron.agent.linux.utils.execute')
|
||||
def test_get_devices_namespaces(self, mocked_execute):
|
||||
fake_str = mock.Mock()
|
||||
fake_str.split.return_value = ['lo']
|
||||
mocked_execute.return_value = fake_str
|
||||
retval = ip_lib.IPWrapper(namespace='foo').get_devices()
|
||||
mocked_execute.assert_called_once_with(
|
||||
['ip', 'netns', 'exec', 'foo', 'find', '/sys/class/net',
|
||||
'-maxdepth', '1', '-type', 'l', '-printf', '%f '],
|
||||
run_as_root=True, log_fail_as_error=True)
|
||||
self.assertTrue(fake_str.split.called)
|
||||
self.assertEqual(retval, [ip_lib.IPDevice('lo', namespace='foo')])
|
||||
|
||||
def test_get_namespaces(self):
|
||||
self.execute.return_value = '\n'.join(NETNS_SAMPLE)
|
||||
|
Loading…
Reference in New Issue
Block a user