Merge "Switch create/delete interfaces to pyroute2"
This commit is contained in:
commit
3ff872b913
@ -167,10 +167,14 @@ class IPWrapper(SubProcessBase):
|
||||
return IPDevice(devices[0]['name'], namespace=self.namespace)
|
||||
|
||||
def add_tuntap(self, name, mode='tap'):
|
||||
self._as_root([], 'tuntap', ('add', name, 'mode', mode))
|
||||
privileged.create_interface(
|
||||
name, self.namespace, "tuntap", mode=mode)
|
||||
return IPDevice(name, namespace=self.namespace)
|
||||
|
||||
def add_veth(self, name1, name2, namespace2=None):
|
||||
# TODO(slaweq): switch to pyroute2 when issue
|
||||
# https://github.com/svinota/pyroute2/issues/463
|
||||
# will be closed
|
||||
args = ['add', name1, 'type', 'veth', 'peer', 'name', name2]
|
||||
|
||||
if namespace2 is None:
|
||||
@ -185,18 +189,20 @@ class IPWrapper(SubProcessBase):
|
||||
IPDevice(name2, namespace=namespace2))
|
||||
|
||||
def add_macvtap(self, name, src_dev, mode='bridge'):
|
||||
args = ['add', 'link', src_dev, 'name', name, 'type', 'macvtap',
|
||||
'mode', mode]
|
||||
self._as_root([], 'link', tuple(args))
|
||||
privileged.create_interface(name,
|
||||
self.namespace,
|
||||
"macvtap",
|
||||
physical_interface=src_dev,
|
||||
mode=mode)
|
||||
return IPDevice(name, namespace=self.namespace)
|
||||
|
||||
def del_veth(self, name):
|
||||
"""Delete a virtual interface between two namespaces."""
|
||||
self._as_root([], 'link', ('del', name))
|
||||
privileged.delete_interface(name, self.namespace)
|
||||
|
||||
def add_dummy(self, name):
|
||||
"""Create a Linux dummy interface with the given name."""
|
||||
self._as_root([], 'link', ('add', name, 'type', 'dummy'))
|
||||
privileged.create_interface(name, self.namespace, "dummy")
|
||||
return IPDevice(name, namespace=self.namespace)
|
||||
|
||||
def ensure_namespace(self, name):
|
||||
@ -224,35 +230,37 @@ class IPWrapper(SubProcessBase):
|
||||
device.link.set_netns(self.namespace)
|
||||
|
||||
def add_vlan(self, name, physical_interface, vlan_id):
|
||||
cmd = ['add', 'link', physical_interface, 'name', name,
|
||||
'type', 'vlan', 'id', vlan_id]
|
||||
self._as_root([], 'link', cmd)
|
||||
privileged.create_interface(name,
|
||||
self.namespace,
|
||||
"vlan",
|
||||
physical_interface=physical_interface,
|
||||
vlan_id=vlan_id)
|
||||
return IPDevice(name, namespace=self.namespace)
|
||||
|
||||
def add_vxlan(self, name, vni, group=None, dev=None, ttl=None, tos=None,
|
||||
local=None, srcport=None, dstport=None, proxy=False):
|
||||
cmd = ['add', name, 'type', 'vxlan', 'id', vni]
|
||||
kwargs = {'vxlan_id': vni}
|
||||
if group:
|
||||
cmd.extend(['group', group])
|
||||
kwargs['vxlan_group'] = group
|
||||
if dev:
|
||||
cmd.extend(['dev', dev])
|
||||
kwargs['physical_interface'] = dev
|
||||
if ttl:
|
||||
cmd.extend(['ttl', ttl])
|
||||
kwargs['vxlan_ttl'] = ttl
|
||||
if tos:
|
||||
cmd.extend(['tos', tos])
|
||||
kwargs['vxlan_tos'] = tos
|
||||
if local:
|
||||
cmd.extend(['local', local])
|
||||
kwargs['vxlan_local'] = local
|
||||
if proxy:
|
||||
cmd.append('proxy')
|
||||
kwargs['vxlan_proxy'] = proxy
|
||||
# tuple: min,max
|
||||
if srcport:
|
||||
if len(srcport) == 2 and srcport[0] <= srcport[1]:
|
||||
cmd.extend(['srcport', str(srcport[0]), str(srcport[1])])
|
||||
kwargs['vxlan_port_range'] = (str(srcport[0]), str(srcport[1]))
|
||||
else:
|
||||
raise n_exc.NetworkVxlanPortRangeError(vxlan_range=srcport)
|
||||
if dstport:
|
||||
cmd.extend(['dstport', str(dstport)])
|
||||
self._as_root([], 'link', cmd)
|
||||
kwargs['vxlan_port'] = str(dstport)
|
||||
privileged.create_interface(name, self.namespace, "vxlan", **kwargs)
|
||||
return (IPDevice(name, namespace=self.namespace))
|
||||
|
||||
@removals.remove(version='Queens', removal_version='Rocky',
|
||||
|
@ -13,6 +13,7 @@
|
||||
import errno
|
||||
import socket
|
||||
|
||||
from neutron_lib import constants
|
||||
import pyroute2
|
||||
from pyroute2.netlink import rtnl
|
||||
from pyroute2.netlink.rtnl import ndmsg
|
||||
@ -90,16 +91,33 @@ def _get_iproute(namespace):
|
||||
return pyroute2.IPRoute()
|
||||
|
||||
|
||||
def _run_iproute_neigh(command, device, namespace, **kwargs):
|
||||
def _get_link_id(device, namespace):
|
||||
try:
|
||||
with _get_iproute(namespace) as ip:
|
||||
idx = ip.link_lookup(ifname=device)[0]
|
||||
return ip.neigh(command, ifindex=idx, **kwargs)
|
||||
return ip.link_lookup(ifname=device)[0]
|
||||
except IndexError:
|
||||
msg = _("Network interface %(device)s not found in namespace "
|
||||
"%(namespace)s.") % {'device': device,
|
||||
'namespace': namespace}
|
||||
raise NetworkInterfaceNotFound(msg)
|
||||
|
||||
|
||||
def _run_iproute_link(command, device, namespace, **kwargs):
|
||||
try:
|
||||
with _get_iproute(namespace) as ip:
|
||||
idx = _get_link_id(device, namespace)
|
||||
return ip.link(command, index=idx, **kwargs)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
raise NetworkNamespaceNotFound(netns_name=namespace)
|
||||
raise
|
||||
|
||||
|
||||
def _run_iproute_neigh(command, device, namespace, **kwargs):
|
||||
try:
|
||||
with _get_iproute(namespace) as ip:
|
||||
idx = _get_link_id(device, namespace)
|
||||
return ip.neigh(command, ifindex=idx, **kwargs)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
raise NetworkNamespaceNotFound(netns_name=namespace)
|
||||
@ -109,13 +127,8 @@ def _run_iproute_neigh(command, device, namespace, **kwargs):
|
||||
def _run_iproute_addr(command, device, namespace, **kwargs):
|
||||
try:
|
||||
with _get_iproute(namespace) as ip:
|
||||
idx = ip.link_lookup(ifname=device)[0]
|
||||
idx = _get_link_id(device, namespace)
|
||||
return ip.addr(command, index=idx, **kwargs)
|
||||
except IndexError:
|
||||
msg = _("Network interface %(device)s not found in namespace "
|
||||
"%(namespace)s.") % {'device': device,
|
||||
'namespace': namespace}
|
||||
raise NetworkInterfaceNotFound(msg)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
raise NetworkNamespaceNotFound(netns_name=namespace)
|
||||
@ -152,19 +165,35 @@ def flush_ip_addresses(ip_version, device, namespace):
|
||||
family = _IP_VERSION_FAMILY_MAP[ip_version]
|
||||
try:
|
||||
with _get_iproute(namespace) as ip:
|
||||
idx = ip.link_lookup(ifname=device)[0]
|
||||
idx = _get_link_id(device, namespace)
|
||||
ip.flush_addr(index=idx, family=family)
|
||||
except IndexError:
|
||||
msg = _("Network interface %(device)s not found in namespace "
|
||||
"%(namespace)s.") % {'device': device,
|
||||
'namespace': namespace}
|
||||
raise NetworkInterfaceNotFound(msg)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
raise NetworkNamespaceNotFound(netns_name=namespace)
|
||||
raise
|
||||
|
||||
|
||||
@privileged.default.entrypoint
|
||||
def create_interface(ifname, namespace, kind, **kwargs):
|
||||
ifname = ifname[:constants.DEVICE_NAME_MAX_LEN]
|
||||
try:
|
||||
with _get_iproute(namespace) as ip:
|
||||
physical_interface = kwargs.pop("physical_interface", None)
|
||||
if physical_interface:
|
||||
link_key = "vxlan_link" if kind == "vxlan" else "link"
|
||||
kwargs[link_key] = _get_link_id(physical_interface, namespace)
|
||||
return ip.link("add", ifname=ifname, kind=kind, **kwargs)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
raise NetworkNamespaceNotFound(netns_name=namespace)
|
||||
raise
|
||||
|
||||
|
||||
@privileged.default.entrypoint
|
||||
def delete_interface(ifname, namespace, **kwargs):
|
||||
_run_iproute_link("del", ifname, namespace, **kwargs)
|
||||
|
||||
|
||||
@privileged.default.entrypoint
|
||||
def add_neigh_entry(ip_version, ip_address, mac_address, device, namespace,
|
||||
**kwargs):
|
||||
|
@ -341,11 +341,10 @@ class TestIpWrapper(base.BaseTestCase):
|
||||
self.assertEqual(1, priv_listnetns.call_count)
|
||||
self.assertFalse(listnetns.called)
|
||||
|
||||
def test_add_tuntap(self):
|
||||
@mock.patch.object(priv_lib, 'create_interface')
|
||||
def test_add_tuntap(self, create):
|
||||
ip_lib.IPWrapper().add_tuntap('tap0')
|
||||
self.execute.assert_called_once_with([], 'tuntap',
|
||||
('add', 'tap0', 'mode', 'tap'),
|
||||
run_as_root=True, namespace=None)
|
||||
create.assert_called_once_with('tap0', None, 'tuntap', mode='tap')
|
||||
|
||||
def test_add_veth(self):
|
||||
ip_lib.IPWrapper().add_veth('tap0', 'tap1')
|
||||
@ -354,19 +353,17 @@ class TestIpWrapper(base.BaseTestCase):
|
||||
'peer', 'name', 'tap1'),
|
||||
run_as_root=True, namespace=None)
|
||||
|
||||
def test_add_macvtap(self):
|
||||
@mock.patch.object(priv_lib, 'create_interface')
|
||||
def test_add_macvtap(self, create):
|
||||
ip_lib.IPWrapper().add_macvtap('macvtap0', 'eth0', 'bridge')
|
||||
self.execute.assert_called_once_with([], 'link',
|
||||
('add', 'link', 'eth0', 'name',
|
||||
'macvtap0', 'type', 'macvtap',
|
||||
'mode', 'bridge'),
|
||||
run_as_root=True, namespace=None)
|
||||
create.assert_called_once_with(
|
||||
'macvtap0', None, 'macvtap', physical_interface='eth0',
|
||||
mode='bridge')
|
||||
|
||||
def test_del_veth(self):
|
||||
@mock.patch.object(priv_lib, 'delete_interface')
|
||||
def test_del_veth(self, delete):
|
||||
ip_lib.IPWrapper().del_veth('fpr-1234')
|
||||
self.execute.assert_called_once_with([], 'link',
|
||||
('del', 'fpr-1234'),
|
||||
run_as_root=True, namespace=None)
|
||||
delete.assert_called_once_with('fpr-1234', None)
|
||||
|
||||
def test_add_veth_with_namespaces(self):
|
||||
ns2 = 'ns2'
|
||||
@ -379,12 +376,10 @@ class TestIpWrapper(base.BaseTestCase):
|
||||
'netns', ns2),
|
||||
run_as_root=True, namespace=None)
|
||||
|
||||
def test_add_dummy(self):
|
||||
@mock.patch.object(priv_lib, 'create_interface')
|
||||
def test_add_dummy(self, create):
|
||||
ip_lib.IPWrapper().add_dummy('dummy0')
|
||||
self.execute.assert_called_once_with([], 'link',
|
||||
('add', 'dummy0',
|
||||
'type', 'dummy'),
|
||||
run_as_root=True, namespace=None)
|
||||
create.assert_called_once_with('dummy0', None, 'dummy')
|
||||
|
||||
def test_get_device(self):
|
||||
dev = ip_lib.IPWrapper(namespace='ns').device('eth0')
|
||||
@ -477,17 +472,42 @@ class TestIpWrapper(base.BaseTestCase):
|
||||
self.assertNotIn(mock.call().delete('ns'),
|
||||
ip_ns_cmd_cls.mock_calls)
|
||||
|
||||
def test_add_vlan(self):
|
||||
@mock.patch.object(priv_lib, 'create_interface')
|
||||
def test_add_vlan(self, create):
|
||||
retval = ip_lib.IPWrapper().add_vlan('eth0.1', 'eth0', '1')
|
||||
self.assertIsInstance(retval, ip_lib.IPDevice)
|
||||
self.assertEqual(retval.name, 'eth0.1')
|
||||
self.execute.assert_called_once_with([], 'link',
|
||||
['add', 'link', 'eth0',
|
||||
'name', 'eth0.1',
|
||||
'type', 'vlan', 'id', '1'],
|
||||
run_as_root=True, namespace=None)
|
||||
create.assert_called_once_with('eth0.1',
|
||||
None,
|
||||
'vlan',
|
||||
physical_interface='eth0',
|
||||
vlan_id='1')
|
||||
|
||||
@mock.patch.object(priv_lib, 'create_interface')
|
||||
def test_add_vxlan_valid_srcport_length(self, create):
|
||||
self.call_params = {}
|
||||
|
||||
def fake_create_interface(ifname, namespace, kind, **kwargs):
|
||||
self.call_params = dict(
|
||||
ifname=ifname,
|
||||
namespace=namespace,
|
||||
kind=kind,
|
||||
**kwargs)
|
||||
|
||||
create.side_effect = fake_create_interface
|
||||
expected_call_params = {
|
||||
'ifname': 'vxlan0',
|
||||
'namespace': None,
|
||||
'kind': 'vxlan',
|
||||
'vxlan_id': 'vni0',
|
||||
'vxlan_group': 'group0',
|
||||
'physical_interface': 'dev0',
|
||||
'vxlan_ttl': 'ttl0',
|
||||
'vxlan_tos': 'tos0',
|
||||
'vxlan_local': 'local0',
|
||||
'vxlan_proxy': True,
|
||||
'vxlan_port_range': ('1', '2')}
|
||||
|
||||
def test_add_vxlan_valid_srcport_length(self):
|
||||
retval = ip_lib.IPWrapper().add_vxlan('vxlan0', 'vni0',
|
||||
group='group0',
|
||||
dev='dev0', ttl='ttl0',
|
||||
@ -496,14 +516,7 @@ class TestIpWrapper(base.BaseTestCase):
|
||||
srcport=(1, 2))
|
||||
self.assertIsInstance(retval, ip_lib.IPDevice)
|
||||
self.assertEqual(retval.name, 'vxlan0')
|
||||
self.execute.assert_called_once_with([], 'link',
|
||||
['add', 'vxlan0', 'type',
|
||||
'vxlan', 'id', 'vni0', 'group',
|
||||
'group0', 'dev', 'dev0',
|
||||
'ttl', 'ttl0', 'tos', 'tos0',
|
||||
'local', 'local0', 'proxy',
|
||||
'srcport', '1', '2'],
|
||||
run_as_root=True, namespace=None)
|
||||
self.assertDictEqual(expected_call_params, self.call_params)
|
||||
|
||||
def test_add_vxlan_invalid_srcport_length(self):
|
||||
wrapper = ip_lib.IPWrapper()
|
||||
@ -521,7 +534,32 @@ class TestIpWrapper(base.BaseTestCase):
|
||||
local='local0', proxy=True,
|
||||
srcport=(2000, 1000))
|
||||
|
||||
def test_add_vxlan_dstport(self):
|
||||
@mock.patch.object(priv_lib, 'create_interface')
|
||||
def test_add_vxlan_dstport(self, create):
|
||||
self.call_params = {}
|
||||
|
||||
def fake_create_interface(ifname, namespace, kind, **kwargs):
|
||||
self.call_params = dict(
|
||||
ifname=ifname,
|
||||
namespace=namespace,
|
||||
kind=kind,
|
||||
**kwargs)
|
||||
|
||||
create.side_effect = fake_create_interface
|
||||
expected_call_params = {
|
||||
'ifname': 'vxlan0',
|
||||
'namespace': None,
|
||||
'kind': 'vxlan',
|
||||
'vxlan_id': 'vni0',
|
||||
'vxlan_group': 'group0',
|
||||
'physical_interface': 'dev0',
|
||||
'vxlan_ttl': 'ttl0',
|
||||
'vxlan_tos': 'tos0',
|
||||
'vxlan_local': 'local0',
|
||||
'vxlan_proxy': True,
|
||||
'vxlan_port_range': ('1', '2'),
|
||||
'vxlan_port': '4789'}
|
||||
|
||||
retval = ip_lib.IPWrapper().add_vxlan('vxlan0', 'vni0',
|
||||
group='group0',
|
||||
dev='dev0', ttl='ttl0',
|
||||
@ -532,15 +570,7 @@ class TestIpWrapper(base.BaseTestCase):
|
||||
|
||||
self.assertIsInstance(retval, ip_lib.IPDevice)
|
||||
self.assertEqual(retval.name, 'vxlan0')
|
||||
self.execute.assert_called_once_with([], 'link',
|
||||
['add', 'vxlan0', 'type',
|
||||
'vxlan', 'id', 'vni0', 'group',
|
||||
'group0', 'dev', 'dev0',
|
||||
'ttl', 'ttl0', 'tos', 'tos0',
|
||||
'local', 'local0', 'proxy',
|
||||
'srcport', '1', '2',
|
||||
'dstport', '4789'],
|
||||
run_as_root=True, namespace=None)
|
||||
self.assertDictEqual(expected_call_params, self.call_params)
|
||||
|
||||
def test_add_device_to_namespace(self):
|
||||
dev = mock.Mock()
|
||||
|
@ -23,6 +23,54 @@ from neutron.tests import base
|
||||
|
||||
class IpLibTestCase(base.BaseTestCase):
|
||||
|
||||
def _test_run_iproute_link(self, namespace=None):
|
||||
ip_obj = "NetNS" if namespace else "IPRoute"
|
||||
with mock.patch.object(pyroute2, ip_obj) as ip_mock_cls:
|
||||
ip_mock = ip_mock_cls()
|
||||
ip_mock.__enter__().link_lookup.return_value = [2]
|
||||
priv_lib._run_iproute_link("test_cmd", "eth0", namespace,
|
||||
test_param="test_value")
|
||||
ip_mock.assert_has_calls([
|
||||
mock.call.__enter__().link_lookup(ifname="eth0"),
|
||||
mock.call.__exit__(None, None, None),
|
||||
mock.call.__enter__().link("test_cmd", index=2,
|
||||
test_param="test_value")])
|
||||
|
||||
def test_run_iproute_link_no_namespace(self):
|
||||
self._test_run_iproute_link()
|
||||
|
||||
def test_run_iproute_link_in_namespace(self):
|
||||
self._test_run_iproute_link(namespace="testns")
|
||||
|
||||
def test_run_iproute_link_interface_not_exists(self):
|
||||
with mock.patch.object(pyroute2, "IPRoute") as iproute_mock:
|
||||
ip_mock = iproute_mock()
|
||||
ip_mock.__enter__().link_lookup.return_value = []
|
||||
self.assertRaises(
|
||||
priv_lib.NetworkInterfaceNotFound,
|
||||
priv_lib._run_iproute_link,
|
||||
"test_cmd", "eth0", None, test_param="test_value")
|
||||
|
||||
def test_run_iproute_link_namespace_not_exists(self):
|
||||
with mock.patch.object(pyroute2, "IPRoute") as iproute_mock:
|
||||
iproute_mock.side_effect = OSError(
|
||||
errno.ENOENT, "Test no netns exception")
|
||||
self.assertRaises(
|
||||
priv_lib.NetworkNamespaceNotFound,
|
||||
priv_lib._run_iproute_link,
|
||||
"test_cmd", "eth0", None, test_param="test_value")
|
||||
|
||||
def test_run_iproute_link_error(self):
|
||||
with mock.patch.object(pyroute2, "IPRoute") as iproute_mock:
|
||||
iproute_mock.side_effect = OSError(
|
||||
errno.EINVAL, "Test invalid argument exception")
|
||||
try:
|
||||
priv_lib._run_iproute_link(
|
||||
"test_cmd", "eth0", None, test_param="test_value")
|
||||
self.fail("OSError exception not raised")
|
||||
except OSError as e:
|
||||
self.assertEqual(errno.EINVAL, e.errno)
|
||||
|
||||
def _test_run_iproute_neigh(self, namespace=None):
|
||||
ip_obj = "NetNS" if namespace else "IPRoute"
|
||||
with mock.patch.object(pyroute2, ip_obj) as ip_mock_cls:
|
||||
@ -32,6 +80,7 @@ class IpLibTestCase(base.BaseTestCase):
|
||||
test_param="test_value")
|
||||
ip_mock.assert_has_calls([
|
||||
mock.call.__enter__().link_lookup(ifname="eth0"),
|
||||
mock.call.__exit__(None, None, None),
|
||||
mock.call.__enter__().neigh("test_cmd", ifindex=2,
|
||||
test_param="test_value")])
|
||||
|
||||
@ -79,6 +128,7 @@ class IpLibTestCase(base.BaseTestCase):
|
||||
test_param="test_value")
|
||||
ip_mock.assert_has_calls([
|
||||
mock.call.__enter__().link_lookup(ifname="eth0"),
|
||||
mock.call.__exit__(None, None, None),
|
||||
mock.call.__enter__().addr("test_cmd", index=2,
|
||||
test_param="test_value")])
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user