This patch adds initial support for the Contrail vRouter interface

The patch introduces the Contrail vRouter interface object. It can be
configured as a kernel module or in dpdk mode. The parameters are:
bind_int (required): the interface the vRouter interface is bound to
dpdk (optional): boolean if dpdk or not

Closes bug: 1709862

Change-Id: Iecb739e3964bf5307b0605ea1d80d2369f60918b
This commit is contained in:
Michael Henkel 2017-08-09 17:20:12 +02:00 committed by Feodor Tersin
parent 314ce2dc86
commit e1c9456768
13 changed files with 551 additions and 0 deletions

View File

@ -0,0 +1,14 @@
{ "network_config": [
{
"type": "contrail_vrouter",
"name": "vhost0",
"members": [
{
"type": "interface",
"name": "em3"
}
],
"mtu": 1500
}
]
}

View File

@ -0,0 +1,9 @@
network_config:
-
type: contrail_vrouter
name: vhost0
members:
-
type: interface
name: em3
mtu: 1500

View File

@ -0,0 +1,14 @@
{ "network_config": [
{
"type": "contrail_vrouter_dpdk",
"name": "vhost0",
"members": [
{
"type": "interface",
"name": "em3"
}
],
"mtu": 1500
}
]
}

View File

@ -0,0 +1,9 @@
network_config:
-
type: contrail_vrouter_dpdk
name: vhost0
members:
-
type: interface
name: em3
mtu: 1500

View File

@ -0,0 +1,20 @@
{ "network_config": [
{
"type": "contrail_vrouter_dpdk",
"name": "vhost0",
"members": [
{
"type": "interface",
"name": "em3"
},
{
"type": "interface",
"name": "em1"
}
],
"bond_mode": "2",
"bond_policy": "802.3ad",
"mtu": 1500
}
]
}

View File

@ -0,0 +1,14 @@
network_config:
-
type: contrail_vrouter_dpdk
name: vhost0
members:
-
type: interface
name: em3
-
type: interface
name: em1
mtu: 1500
bond_mode: 2
bond_policy: 802.3ad

View File

@ -102,6 +102,10 @@ class NetConfig(object):
self.add_ovs_dpdk_bond(obj)
elif isinstance(obj, objects.VppInterface):
self.add_vpp_interface(obj)
elif isinstance(obj, objects.ContrailVrouter):
self.add_contrail_vrouter(obj)
elif isinstance(obj, objects.ContrailVrouterDpdk):
self.add_contrail_vrouter_dpdk(obj)
def add_interface(self, interface):
"""Add an Interface object to the net config object.
@ -215,6 +219,23 @@ class NetConfig(object):
"""
raise NotImplementedError("add_vpp_interface is not implemented.")
def add_contrail_vrouter(self, contrail_vrouter):
"""Add a ContrailVrouter object to the net config object.
:param contrail_vrouter:
The ContrailVrouter object to add.
"""
raise NotImplementedError("add_contrail_vrouter is not implemented.")
def add_contrail_vrouter_dpdk(self, contrail_vrouter_dpdk):
"""Add a ContrailVrouterDpdk object to the net config object.
:param contrail_vrouter_dpdk:
The ContrailVrouterDpdk object to add.
"""
raise NotImplementedError(
"add_contrail_vrouter_dpdk is not implemented.")
def apply(self, cleanup=False):
"""Apply the network configuration.

View File

@ -684,6 +684,53 @@ class IfcfgNetConfig(os_net_config.NetConfig):
% (vpp_interface.name, vpp_interface.pci_dev))
self.vpp_interface_data[vpp_interface.name] = vpp_interface
def add_contrail_vrouter(self, contrail_vrouter):
"""Add a ContraiVrouter object to the net config object
:param contrail_vrouter:
The ContrailVrouter object to add
"""
logger.info('adding contrail_vrouter interface: %s'
% contrail_vrouter.name)
# Contrail vrouter will have theeeee only member (of type Interface)
ifname = contrail_vrouter.members[0].name
data = self._add_common(contrail_vrouter)
data += "DEVICETYPE=vhost\n"
data += "TYPE=kernel_mode\n"
data += "BIND_INT=%s\n" % ifname
logger.debug('contrail data: %s' % data)
self.interface_data[contrail_vrouter.name] = data
if contrail_vrouter.routes:
self._add_routes(contrail_vrouter.name,
contrail_vrouter.routes)
def add_contrail_vrouter_dpdk(self, contrail_vrouter_dpdk):
"""Add a ContraiVrouterDpdk object to the net config object
:param contrail_vrouter_dpdk:
The ContrailVrouterDpdk object to add
"""
logger.info('adding contrail vrouter dpdk interface: %s'
% contrail_vrouter_dpdk.name)
pci_string = ",".join(
utils.translate_ifname_to_pci_address(bind_int.name, self.noop)
for bind_int in contrail_vrouter_dpdk.members)
data = self._add_common(contrail_vrouter_dpdk)
data += "DEVICETYPE=vhost\n"
data += "TYPE=dpdk\n"
data += "BIND_INT=%s\n" % pci_string
if len(contrail_vrouter_dpdk.members) > 1:
data += "BOND_MODE=%s\n" % contrail_vrouter_dpdk.bond_mode
data += "BOND_POLICY=%s\n" % contrail_vrouter_dpdk.bond_policy
data += "CPU_LIST=%s\n" % contrail_vrouter_dpdk.cpu_list
if contrail_vrouter_dpdk.vlan_id:
data += "VLAN_ID=%s\n" % contrail_vrouter_dpdk.vlan_id
logger.debug('contrail dpdk data: %s' % data)
self.interface_data[contrail_vrouter_dpdk.name] = data
if contrail_vrouter_dpdk.routes:
self._add_routes(contrail_vrouter_dpdk.name,
contrail_vrouter_dpdk.routes)
def generate_ivs_config(self, ivs_uplinks, ivs_interfaces):
"""Generate configuration content for ivs."""

View File

@ -76,6 +76,10 @@ def object_from_json(json):
return OvsDpdkBond.from_json(json)
elif obj_type == "vpp_interface":
return VppInterface.from_json(json)
elif obj_type == "contrail_vrouter":
return ContrailVrouter.from_json(json)
elif obj_type == "contrail_vrouter_dpdk":
return ContrailVrouterDpdk.from_json(json)
def _get_required_field(json, name, object_name):
@ -1219,3 +1223,92 @@ class VppInterface(_BaseOpts):
opts = _BaseOpts.base_opts_from_json(json)
return VppInterface(name, *opts, uio_driver=uio_driver,
options=options)
class ContrailVrouter(_BaseOpts):
"""Base class for Contrail Interface.
Contrail Vrouter is the interface transporting traffic for the Contrail
SDN Controller.
The following parameters can be specified in addition to base Interface:
- members: List of sole interface to use by vhost0
"""
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
routes=None, mtu=None, primary=False, nic_mapping=None,
persist_mapping=False, defroute=True, dhclient_args=None,
dns_servers=None, nm_controlled=False, onboot=True,
members=None):
addresses = addresses or []
super(ContrailVrouter, self).__init__(name, use_dhcp, use_dhcpv6,
addresses, routes, mtu,
primary, nic_mapping,
persist_mapping, defroute,
dhclient_args, dns_servers,
nm_controlled, onboot)
self.members = members or []
@staticmethod
def from_json(json):
name = _get_required_field(json, 'name', 'ContrailVrouter')
(_use_dhcp, _use_dhcpv6, _addresses, _routes, _mtu, _primary,
nic_mapping, persist_mapping, _defroute, _dhclient_args, _dns_servers,
_nm_controlled, _onboot) = opts = _BaseOpts.base_opts_from_json(json)
members = _update_members(json, nic_mapping, persist_mapping)
return ContrailVrouter(name, *opts, members=members)
class ContrailVrouterDpdk(_BaseOpts):
"""Base class for Contrail DPDK Interface.
Contrail Vrouter is the interface transporting traffic for the Contrail
SDN Controller.
The following parameters can be specified in addition to base Interface:
- members: List of interfaces to use by vhost0
- bond_mode: Bonding mode
- bond_policy: Bonding transmit hash policy
- cpu_list: CPU set string eg "1-4,6,7-15:2"
- vlan_id:
"""
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
routes=None, mtu=None, primary=False, nic_mapping=None,
persist_mapping=False, defroute=True, dhclient_args=None,
dns_servers=None, nm_controlled=False, onboot=True,
members=None, bond_mode=None, bond_policy=None,
cpu_list='0-31', vlan_id=None):
addresses = addresses or []
super(ContrailVrouterDpdk, self).__init__(name, use_dhcp, use_dhcpv6,
addresses, routes, mtu,
primary, nic_mapping,
persist_mapping, defroute,
dhclient_args, dns_servers,
nm_controlled, onboot)
self.members = members or []
self.bond_mode = bond_mode
self.bond_policy = bond_policy
self.cpu_list = cpu_list
self.vlan_id = vlan_id
@staticmethod
def from_json(json):
name = _get_required_field(json, 'name', 'ContrailVrouterDpdk')
bond_mode = json.get('bond_mode', '')
bond_policy = json.get('bond_policy', '')
cpu_list = json.get('cpu_list', '0-31')
vlan_id = json.get('vlan_id', '')
(_use_dhcp, _use_dhcpv6, _addresses, _routes, _mtu, _primary,
nic_mapping, persist_mapping, _defroute, _dhclient_args, _dns_servers,
_nm_controlled, _onboot) = opts = _BaseOpts.base_opts_from_json(json)
members = _update_members(json, nic_mapping, persist_mapping)
return ContrailVrouterDpdk(name, *opts, members=members,
bond_mode=bond_mode,
bond_policy=bond_policy,
cpu_list=cpu_list, vlan_id=vlan_id)

View File

@ -669,6 +669,103 @@ definitions:
- name
additionalProperties: False
contrail_vrouter:
type: object
properties:
type:
enum: ["contrail_vrouter"]
name:
$ref: "#/definitions/string_or_param"
members:
type: array
items:
- $ref: "#/definitions/interface"
minItems: 1
maxItems: 1
# common options:
use_dhcp:
$ref: "#/definitions/bool_or_param"
use_dhcp6:
$ref: "#/definitions/bool_or_param"
addresses:
$ref: "#/definitions/list_of_address"
routes:
$ref: "#/definitions/list_of_route"
mtu:
$ref: "#/definitions/int_or_param"
nic_mapping:
$ref: "#/definitions/nic_mapping"
persist_mapping:
$ref: "#/definitions/bool_or_param"
defroute:
$ref: "#/definitions/bool_or_param"
dhclient_args:
$ref: "#/definitions/string_or_param"
dns_servers:
$ref: "#/definitions/list_of_ip_address_string_or_param"
nm_controlled:
$ref: "#/definitions/bool_or_param"
required:
- type
- name
- members
additionalProperties: False
contrail_vrouter_dpdk:
type: object
properties:
type:
enum: ["contrail_vrouter_dpdk"]
name:
$ref: "#/definitions/string_or_param"
members:
type: array
items:
- $ref: "#/definitions/interface"
minItems: 1
bond_mode:
oneOf:
- $ref: "#/definitions/string_or_param"
- $ref: "#/definitions/int_or_param"
bond_policy:
oneOf:
- $ref: "#/definitions/string_or_param"
- $ref: "#/definitions/int_or_param"
coremask:
$ref: "#/definitions/string_or_param"
vlan_id:
oneOf:
- $ref: "#/definitions/string_or_param"
- $ref: "#/definitions/int_or_param"
# common options:
use_dhcp:
$ref: "#/definitions/bool_or_param"
use_dhcp6:
$ref: "#/definitions/bool_or_param"
addresses:
$ref: "#/definitions/list_of_address"
routes:
$ref: "#/definitions/list_of_route"
mtu:
$ref: "#/definitions/int_or_param"
nic_mapping:
$ref: "#/definitions/nic_mapping"
persist_mapping:
$ref: "#/definitions/bool_or_param"
defroute:
$ref: "#/definitions/bool_or_param"
dhclient_args:
$ref: "#/definitions/string_or_param"
dns_servers:
$ref: "#/definitions/list_of_ip_address_string_or_param"
nm_controlled:
$ref: "#/definitions/bool_or_param"
required:
- type
- name
- members
additionalProperties: False
linux_bridge:
type: object
properties:
@ -1036,4 +1133,6 @@ items:
- $ref: "#/definitions/nfvswitch_internal"
- $ref: "#/definitions/ib_interface"
- $ref: "#/definitions/vpp_interface"
- $ref: "#/definitions/contrail_vrouter"
- $ref: "#/definitions/contrail_vrouter_dpdk"
minItems: 1

View File

@ -23,6 +23,7 @@ from os_net_config import cli
from os_net_config import impl_ifcfg
from os_net_config import objects
from os_net_config.tests import base
from os_net_config import utils
import six
@ -50,6 +51,18 @@ class TestCli(base.TestCase):
sys.stderr = orig_stderr
return (stdout, stderr)
def stub_get_stored_pci_address(self, ifname, noop):
if 'eth0' in ifname:
return "0000:00:07.0"
if 'eth1' in ifname:
return "0000:00:08.0"
if 'eth2' in ifname:
return "0000:00:09.0"
if 'em3' in ifname:
return "0000:00:03.0"
if 'em1' in ifname:
return "0000:00:01.0"
def test_bond_noop_output(self):
bond_yaml = os.path.join(SAMPLE_BASE, 'bond.yaml')
bond_json = os.path.join(SAMPLE_BASE, 'bond.json')
@ -273,3 +286,45 @@ class TestCli(base.TestCase):
self.assertEqual(stdout_list['em3'], 'em3')
self.assertNotIn('em4', stdout_list.keys())
self.assertNotIn('em4', stdout_list.values())
def test_contrail_vrouter_noop_output(self):
cvi_yaml = os.path.join(SAMPLE_BASE, 'contrail_vrouter.yaml')
cvi_json = os.path.join(SAMPLE_BASE, 'contrail_vrouter.json')
stdout_yaml, stderr = self.run_cli('ARG0 --provider=ifcfg --noop '
'--exit-on-validation-errors '
'-c %s' % cvi_yaml)
self.assertEqual('', stderr)
stdout_json, stderr = self.run_cli('ARG0 --provider=ifcfg --noop '
'--exit-on-validation-errors '
'-c %s' % cvi_json)
self.assertEqual('', stderr)
sanity_devices = ['DEVICE=vhost0',
'BIND_INT=em3',
'DEVICETYPE=vhost',
'TYPE=kernel_mode']
for dev in sanity_devices:
self.assertIn(dev, stdout_yaml)
self.assertEqual(stdout_yaml, stdout_json)
def test_contrail_vrouter_dpdk_noop_output(self):
cvi_yaml = os.path.join(SAMPLE_BASE, 'contrail_vrouter_dpdk.yaml')
cvi_json = os.path.join(SAMPLE_BASE, 'contrail_vrouter_dpdk.json')
self.stubs.Set(utils, 'get_stored_pci_address',
self.stub_get_stored_pci_address)
stdout_yaml, stderr = self.run_cli('ARG0 --provider=ifcfg --noop '
'--exit-on-validation-errors '
'--debug '
'-c %s' % cvi_yaml)
self.assertEqual('', stderr)
stdout_json, stderr = self.run_cli('ARG0 --provider=ifcfg --noop '
'--exit-on-validation-errors '
'--debug '
'-c %s' % cvi_json)
self.assertEqual('', stderr)
sanity_devices = ['DEVICE=vhost0',
'BIND_INT=0000:00:03.0',
'DEVICETYPE=vhost',
'TYPE=dpdk']
for dev in sanity_devices:
self.assertIn(dev, stdout_yaml)
self.assertEqual(stdout_yaml, stdout_json)

View File

@ -394,6 +394,53 @@ TEAM_PORT_CONFIG='{"prio": 100}'
BOOTPROTO=none
"""
_CONTRAIL_VROUTER_IFACE = """# This file is autogenerated by os-net-config
DEVICE=vhost0
HOTPLUG=no
ONBOOT=yes
NM_CONTROLLED=no
PEERDNS=no
BOOTPROTO=static
IPADDR=10.0.0.30
NETMASK=255.255.255.0
DEVICETYPE=vhost
TYPE=kernel_mode
BIND_INT=em3
"""
_CONTRAIL_VROUTER_DPDK_IFACE = """# This file is autogenerated by os-net-config
DEVICE=vhost0
HOTPLUG=no
ONBOOT=yes
NM_CONTROLLED=no
PEERDNS=no
BOOTPROTO=static
IPADDR=10.0.0.30
NETMASK=255.255.255.0
DEVICETYPE=vhost
TYPE=dpdk
BIND_INT=0000:00:03.0
CPU_LIST=0-31
"""
_CONTRAIL_VROUTER_DPDK_BOND_IFACE = """# This file is autogenerated by \
os-net-config
DEVICE=vhost0
HOTPLUG=no
ONBOOT=yes
NM_CONTROLLED=no
PEERDNS=no
BOOTPROTO=static
IPADDR=10.0.0.30
NETMASK=255.255.255.0
DEVICETYPE=vhost
TYPE=dpdk
BIND_INT=0000:00:03.0,0000:00:01.0
BOND_MODE=2
BOND_POLICY=802.3ad
CPU_LIST=2,3
"""
class TestIfcfgNetConfig(base.TestCase):
@ -430,6 +477,10 @@ class TestIfcfgNetConfig(base.TestCase):
return "0000:00:08.0"
if 'eth2' in ifname:
return "0000:00:09.0"
if 'em3' in ifname:
return "0000:00:03.0"
if 'em1' in ifname:
return "0000:00:01.0"
def test_add_base_interface(self):
interface = objects.Interface('em1')
@ -727,6 +778,98 @@ class TestIfcfgNetConfig(base.TestCase):
self.get_interface_config('ib0'))
self.assertEqual('', self.get_route_config())
def test_add_contrail_vrouter(self):
addresses = [objects.Address('10.0.0.30/24')]
interface1 = objects.Interface('em3')
cvi = objects.ContrailVrouter('vhost0', addresses=addresses,
members=[interface1],)
self.provider.add_contrail_vrouter(cvi)
self.assertEqual(
_CONTRAIL_VROUTER_IFACE,
self.provider.interface_data['vhost0'])
self.assertEqual('', self.get_route_config('vhost0'))
def test_add_contrail_vrouter_with_nic_mapping(self):
nic_mapping = {'nic1': 'em3'}
self.stubbed_mapped_nics = nic_mapping
addresses = [objects.Address('10.0.0.30/24')]
interface1 = objects.Interface('nic1')
cvi = objects.ContrailVrouter('vhost0', addresses=addresses,
members=[interface1])
self.provider.add_contrail_vrouter(cvi)
self.assertEqual(
_CONTRAIL_VROUTER_IFACE,
self.provider.interface_data['vhost0'])
self.assertEqual('', self.get_route_config('vhost0'))
def test_add_contrail_vrouter_dpdk_interface(self):
addresses = [objects.Address('10.0.0.30/24')]
interface1 = objects.Interface('em3')
self.stubs.Set(utils, 'get_stored_pci_address',
self.stub_get_stored_pci_address)
cvi = objects.ContrailVrouterDpdk('vhost0', addresses=addresses,
members=[interface1])
self.provider.noop = True
self.provider.add_contrail_vrouter_dpdk(cvi)
self.assertEqual(
_CONTRAIL_VROUTER_DPDK_IFACE,
self.provider.interface_data['vhost0'])
self.assertEqual('', self.get_route_config('vhost0'))
def test_add_contrail_vrouter_dpdk_interface_nic_mapping(self):
nic_mapping = {'nic1': 'em3'}
self.stubbed_mapped_nics = nic_mapping
addresses = [objects.Address('10.0.0.30/24')]
interface1 = objects.Interface('nic1')
self.stubs.Set(utils, 'get_stored_pci_address',
self.stub_get_stored_pci_address)
cvi = objects.ContrailVrouterDpdk('vhost0', addresses=addresses,
members=[interface1])
self.provider.noop = True
self.provider.add_contrail_vrouter_dpdk(cvi)
self.assertEqual(
_CONTRAIL_VROUTER_DPDK_IFACE,
self.provider.interface_data['vhost0'])
self.assertEqual('', self.get_route_config('vhost0'))
def test_add_contrail_vrouter_dpdk_bond_interface(self):
addresses = [objects.Address('10.0.0.30/24')]
interface1 = objects.Interface('em3')
interface2 = objects.Interface('em1')
self.stubs.Set(utils, 'get_stored_pci_address',
self.stub_get_stored_pci_address)
cvi = objects.ContrailVrouterDpdk('vhost0', addresses=addresses,
members=[interface1, interface2],
bond_mode="2",
bond_policy="802.3ad",
cpu_list="2,3")
self.provider.noop = True
self.provider.add_contrail_vrouter_dpdk(cvi)
self.assertEqual(
_CONTRAIL_VROUTER_DPDK_BOND_IFACE,
self.provider.interface_data['vhost0'])
self.assertEqual('', self.get_route_config('vhost0'))
def test_add_contrail_vrouter_dpdk_bond_interface_nic_mapping(self):
nic_mapping = {'nic1': 'em3', 'nic2': 'em1'}
self.stubbed_mapped_nics = nic_mapping
addresses = [objects.Address('10.0.0.30/24')]
interface1 = objects.Interface('nic1')
interface2 = objects.Interface('nic2')
self.stubs.Set(utils, 'get_stored_pci_address',
self.stub_get_stored_pci_address)
cvi = objects.ContrailVrouterDpdk('vhost0', addresses=addresses,
members=[interface1, interface2],
bond_mode="2",
bond_policy="802.3ad",
cpu_list="2,3")
self.provider.noop = True
self.provider.add_contrail_vrouter_dpdk(cvi)
self.assertEqual(
_CONTRAIL_VROUTER_DPDK_BOND_IFACE,
self.provider.interface_data['vhost0'])
self.assertEqual('', self.get_route_config('vhost0'))
def test_add_vlan(self):
vlan = objects.Vlan('em1', 5)
self.provider.add_vlan(vlan)

View File

@ -48,6 +48,10 @@ class VppException(ValueError):
pass
class ContrailVrouterException(ValueError):
pass
def write_config(filename, data):
with open(filename, 'w') as f:
f.write(str(data))
@ -295,6 +299,15 @@ def get_stored_pci_address(ifname, noop):
'ethtool' % ifname)
def translate_ifname_to_pci_address(ifname, noop):
pci_address = get_stored_pci_address(ifname, noop)
if pci_address is None and not noop:
pci_address = get_pci_address(ifname, noop=False)
mac_address = interface_mac(ifname)
_update_dpdk_map(ifname, pci_address, mac_address, driver=None)
return pci_address
# Once the interface is bound to a DPDK driver, all the references to the
# interface including '/sys' and '/proc', will be removed. And there is no
# way to identify the nic name after it is bound. So, the DPDK bound nic info