Merge "Add support for VPP Bond"

This commit is contained in:
Zuul 2017-11-14 00:48:52 +00:00 committed by Gerrit Code Review
commit 3cbe5c9215
9 changed files with 411 additions and 51 deletions

View File

@ -0,0 +1,23 @@
{ "network_config": [
{
"type": "vpp_bond",
"name": "net_bonding0",
"addresses": [
{
"ip_netmask": "192.0.2.1/24"
}
],
"bonding_options": "mode=2,xmit_policy=l34",
"members": [
{
"type": "vpp_interface",
"name": "eth1"
},
{
"type": "vpp_interface",
"name": "eth2"
}
]
}
]
}

View File

@ -0,0 +1,17 @@
network_config:
-
type: vpp_bond
# name must be in the format of eth_bondX or net_bondingX, where X is a
# unique number for each bond.
name: net_bonding0
addresses:
-
ip_netmask: 192.0.2.1/24
bonding_options: "mode=2,xmit_policy=l34"
members:
-
type: vpp_interface
name: eth1
-
type: vpp_interface
name: eth2

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.VppBond):
self.add_vpp_bond(obj)
for member in obj.members:
self.add_object(member)
elif isinstance(obj, objects.ContrailVrouter):
self.add_contrail_vrouter(obj)
elif isinstance(obj, objects.ContrailVrouterDpdk):
@ -219,6 +223,13 @@ class NetConfig(object):
"""
raise NotImplementedError("add_vpp_interface is not implemented.")
def add_vpp_bond(self, vpp_bond):
"""Add a VppBond object to the net config object.
:param vpp_bond: The VppBond object to add.
"""
raise NotImplementedError("add_vpp_bond is not implemented.")
def add_contrail_vrouter(self, contrail_vrouter):
"""Add a ContrailVrouter object to the net config object.

View File

@ -121,6 +121,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
self.ib_interface_data = {}
self.linuxteam_data = {}
self.vpp_interface_data = {}
self.vpp_bond_data = {}
self.member_names = {}
self.renamed_interfaces = {}
self.bond_primary_ifaces = {}
@ -676,6 +677,9 @@ class IfcfgNetConfig(os_net_config.NetConfig):
"""
vpp_interface.pci_dev = utils.get_pci_address(vpp_interface.name,
False)
if not vpp_interface.pci_dev:
vpp_interface.pci_dev = utils.get_stored_pci_address(
vpp_interface.name, False)
vpp_interface.hwaddr = utils.interface_mac(vpp_interface.name)
if not self.noop:
self.ifdown(vpp_interface.name)
@ -684,6 +688,14 @@ class IfcfgNetConfig(os_net_config.NetConfig):
% (vpp_interface.name, vpp_interface.pci_dev))
self.vpp_interface_data[vpp_interface.name] = vpp_interface
def add_vpp_bond(self, vpp_bond):
"""Add a VppInterface object to the net config object
:param vpp_bond: The VPPBond object to add
"""
logger.info('adding vpp bond: %s' % vpp_bond.name)
self.vpp_bond_data[vpp_bond.name] = vpp_bond
def add_contrail_vrouter(self, contrail_vrouter):
"""Add a ContraiVrouter object to the net config object
@ -806,6 +818,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
stop_dhclient_interfaces = []
ovs_needs_restart = False
vpp_interfaces = self.vpp_interface_data.values()
vpp_bonds = self.vpp_bond_data.values()
for interface_name, iface_data in self.interface_data.items():
route_data = self.route_data.get(interface_name, '')
@ -1014,9 +1027,10 @@ class IfcfgNetConfig(os_net_config.NetConfig):
logger.info('No changes required for InfiniBand iface: %s' %
interface_name)
if self.vpp_interface_data:
if self.vpp_interface_data or self.vpp_bond_data:
vpp_path = self.root_dir + vpp_config_path()
vpp_config = utils.generate_vpp_config(vpp_path, vpp_interfaces)
vpp_config = utils.generate_vpp_config(vpp_path, vpp_interfaces,
vpp_bonds)
if utils.diff(vpp_path, vpp_config):
restart_vpp = True
update_files[vpp_path] = vpp_config
@ -1139,7 +1153,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if self.vpp_interface_data:
logger.info('Updating VPP mapping')
utils.update_vpp_mapping(vpp_interfaces)
utils.update_vpp_mapping(vpp_interfaces, vpp_bonds)
if self.errors:
message = 'Failure(s) occurred when applying configuration'

View File

@ -76,6 +76,8 @@ def object_from_json(json):
return OvsDpdkBond.from_json(json)
elif obj_type == "vpp_interface":
return VppInterface.from_json(json)
elif obj_type == "vpp_bond":
return VppBond.from_json(json)
elif obj_type == "contrail_vrouter":
return ContrailVrouter.from_json(json)
elif obj_type == "contrail_vrouter_dpdk":
@ -1225,6 +1227,61 @@ class VppInterface(_BaseOpts):
options=options)
class VppBond(_BaseOpts):
"""Base class for VPP Bond."""
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, bonding_options=None):
addresses = addresses or []
members = members or []
super(VppBond, 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
self.bonding_options = bonding_options
@staticmethod
def from_json(json):
name = _get_required_field(json, 'name', 'VppBond')
bonding_options = json.get('bonding_options', '')
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
persist_mapping, defroute, dhclient_args,
dns_servers, nm_controlled, onboot) = _BaseOpts.base_opts_from_json(
json, include_primary=False)
members = []
members_json = json.get('members', None)
if members_json:
if isinstance(members_json, list):
for member in members_json:
if not member.get('nic_mapping'):
member.update({'nic_mapping': nic_mapping})
member.update({'persist_mapping': persist_mapping})
obj = object_from_json(member)
if isinstance(obj, VppInterface):
members.append(obj)
else:
msg = 'Members must be of type vpp_interface'
raise InvalidConfigException(msg)
else:
msg = 'Members must be a list.'
raise InvalidConfigException(msg)
return VppBond(name, use_dhcp=use_dhcp, use_dhcpv6=use_dhcpv6,
addresses=addresses, routes=routes, mtu=mtu,
members=members, nic_mapping=nic_mapping,
persist_mapping=persist_mapping,
defroute=defroute, dhclient_args=dhclient_args,
dns_servers=dns_servers, nm_controlled=nm_controlled,
onboot=onboot, bonding_options=bonding_options)
class ContrailVrouter(_BaseOpts):
"""Base class for Contrail Interface.

View File

@ -669,6 +669,54 @@ definitions:
- name
additionalProperties: False
vpp_bond:
type: object
properties:
type:
enum: ["vpp_bond"]
name:
$ref: "#/definitions/string_or_param"
uio_driver:
$ref: "#/definitions/string_or_param"
options:
$ref: "#/definitions/string_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"
onboot:
$ref: "#/definitions/bool_or_param"
members:
type: array
items:
oneOf:
- $ref: "#/definitions/vpp_interface"
bonding_options:
$ref: "#/definitions/string_or_param"
required:
- type
- name
additionalProperties: False
contrail_vrouter:
type: object
properties:
@ -1135,6 +1183,7 @@ items:
- $ref: "#/definitions/nfvswitch_internal"
- $ref: "#/definitions/ib_interface"
- $ref: "#/definitions/vpp_interface"
- $ref: "#/definitions/vpp_bond"
- $ref: "#/definitions/contrail_vrouter"
- $ref: "#/definitions/contrail_vrouter_dpdk"
minItems: 1

View File

@ -1256,3 +1256,55 @@ class TestVppInterface(base.TestCase):
self.assertEqual("em1", vpp_interface.name)
self.assertEqual("uio_pci_generic", vpp_interface.uio_driver)
self.assertEqual("vlan-strip-offload off", vpp_interface.options)
class TestVppBond(base.TestCase):
def test_vpp_interface_from_json(self):
data = """{
"type": "vpp_bond",
"name": "net_bonding0",
"members": [
{
"type": "vpp_interface",
"name": "eth1"
},
{
"type": "vpp_interface",
"name": "eth2"
}
],
"bonding_options": "mode=2,xmit_policy=l34"
}
"""
vpp_bond = objects.object_from_json(json.loads(data))
self.assertEqual("net_bonding0", vpp_bond.name)
self.assertEqual("mode=2,xmit_policy=l34", vpp_bond.bonding_options)
vpp_int1 = vpp_bond.members[0]
self.assertEqual("eth1", vpp_int1.name)
vpp_int2 = vpp_bond.members[1]
self.assertEqual("eth2", vpp_int2.name)
def test_invalid_vpp_interface_from_json(self):
data = """{
"type": "vpp_bond",
"name": "net_bonding0",
"members": [
{
"type": "vpp_interface",
"name": "eth1"
},
{
"type": "interface",
"name": "eth2"
}
],
"bonding_options": "mode=2,xmit_policy=l34"
}
"""
err = self.assertRaises(objects.InvalidConfigException,
objects.object_from_json,
json.loads(data))
expected = 'Members must be of type vpp_interface'
self.assertIn(expected, six.text_type(err))

View File

@ -47,6 +47,13 @@ local0 0 down
'''
_VPPBOND_OUTPUT = """
Name Idx Link Hardware
BondEthernet0 3 up Slave-Idx: 1 2
TenGigabitEthernet2/0/0 1 slave TenGigabitEthernet2/0/0
TenGigabitEthernet2/0/1 2 slave TenGigabitEthernet2/0/1
"""
_INITIAL_VPP_CONFIG = '''
unix {
nodaemon
@ -339,8 +346,8 @@ class TestUtils(base.TestCase):
shutil.rmtree(tmpdir)
def test_get_vpp_interface_name(self):
def test_execute(name, dummy1, dummy2=None, dummy3=None):
def test_get_vpp_interface(self):
def test_execute(name, *args, **kwargs):
if 'systemctl' in name:
return None, None
if 'vppctl' in name:
@ -348,19 +355,37 @@ class TestUtils(base.TestCase):
self.stubs.Set(processutils, 'execute', test_execute)
self.assertEqual('GigabitEthernet0/9/0',
utils._get_vpp_interface_name('0000:00:09.0'))
self.assertIsNone(utils._get_vpp_interface_name(None))
self.assertIsNone(utils._get_vpp_interface_name('0000:01:09.0'))
int_info = utils._get_vpp_interface('0000:00:09.0')
self.assertIsNotNone(int_info)
self.assertEqual('GigabitEthernet0/9/0', int_info['name'])
self.assertEqual('1', int_info['index'])
self.assertIsNone(utils._get_vpp_interface(None))
self.assertIsNone(utils._get_vpp_interface('0000:01:09.0'))
self.assertRaises(utils.VppException,
utils._get_vpp_interface_name, '0000:09.0')
utils._get_vpp_interface, '0000:09.0')
@mock.patch('os_net_config.utils.processutils.execute',
return_value=('', None))
def test_get_vpp_interface_name_multiple_iterations(self, mock_execute):
self.assertIsNone(utils._get_vpp_interface_name('0000:00:09.0', 2, 1))
self.assertIsNone(utils._get_vpp_interface('0000:00:09.0', 2, 1))
self.assertEqual(4, mock_execute.call_count)
def test_get_vpp_bond(self):
def test_execute(name, *args, **kwargs):
if 'systemctl' in name:
return None, None
if 'vppctl' in name:
return _VPPBOND_OUTPUT, None
self.stubs.Set(processutils, 'execute', test_execute)
bond_info = utils._get_vpp_bond(['1', '2'])
self.assertIsNotNone(bond_info)
self.assertEqual('BondEthernet0', bond_info['name'])
self.assertEqual('3', bond_info['index'])
self.assertIsNone(utils._get_vpp_bond(['1']))
self.assertIsNone(utils._get_vpp_bond(['1', '2', '3']))
self.assertIsNone(utils._get_vpp_bond([]))
def test_generate_vpp_config(self):
tmpdir = tempfile.mkdtemp()
config_path = os.path.join(tmpdir, 'startup.conf')
@ -374,6 +399,7 @@ class TestUtils(base.TestCase):
int2 = objects.VppInterface('em2')
int2.pci_dev = '0000:00:09.1'
interfaces = [int1, int2]
bonds = []
expected_config = '''
unix {
exec %s
@ -399,9 +425,45 @@ dpdk {
}
''' % vpp_exec_path
self.assertEqual(expected_config,
utils.generate_vpp_config(config_path, interfaces))
utils.generate_vpp_config(config_path, interfaces,
bonds))
bonds = [objects.VppBond('net_bonding0', members=interfaces,
bonding_options='mode=2,xmit_policy=l3')]
expected_config = '''
unix {
exec %s
nodaemon
log /tmp/vpp.log
full-coredump
}
api-trace {
on
}
api-segment {
gid vpp
}
dpdk {
vdev net_bonding0,slave=0000:00:09.0,slave=0000:00:09.1,mode=2,xmit_policy=l3
dev 0000:00:09.1
uio-driver vfio-pci
dev 0000:00:09.0 {vlan-strip-offload off}
}
''' % vpp_exec_path
self.assertEqual(expected_config,
utils.generate_vpp_config(config_path, interfaces,
bonds))
def test_update_vpp_mapping(self):
tmpdir = tempfile.mkdtemp()
vpp_exec_path = os.path.join(tmpdir, 'vpp-exec')
utils._VPP_EXEC_FILE = vpp_exec_path
def test_get_dpdk_map():
return [{'name': 'eth1', 'pci_address': '0000:00:09.0',
'mac_address': '01:02:03:04:05:06',
@ -409,15 +471,15 @@ dpdk {
self.stubs.Set(utils, '_get_dpdk_map', test_get_dpdk_map)
def test_execute(name, dummy1, dummy2=None, dummy3=None):
def test_execute(name, *args, **kwargs):
return None, None
self.stubs.Set(processutils, 'execute', test_execute)
def test_get_vpp_interface_name(pci_dev, tries, timeout):
return 'GigabitEthernet0/9/0'
def test_get_vpp_interface(pci_dev, tries, timeout):
return {'name': 'GigabitEthernet0/9/0', 'index': '1'}
self.stubs.Set(utils, '_get_vpp_interface_name',
test_get_vpp_interface_name)
self.stubs.Set(utils, '_get_vpp_interface',
test_get_vpp_interface)
int1 = objects.VppInterface('eth1', options="vlan-strip-offload off")
int1.pci_dev = '0000:00:09.0'
@ -427,7 +489,7 @@ dpdk {
int2.hwaddr = '01:02:03:04:05:07'
interfaces = [int1, int2]
utils.update_vpp_mapping(interfaces)
utils.update_vpp_mapping(interfaces, [])
contents = utils.get_file_data(utils._DPDK_MAPPING_FILE)

View File

@ -355,11 +355,12 @@ def restart_vpp(vpp_interfaces):
processutils.execute('systemctl', 'restart', 'vpp')
def _get_vpp_interface_name(pci_addr, tries=1, timeout=5):
"""Get VPP interface name from a given PCI address
def _get_vpp_interface(pci_addr, tries=1, timeout=5):
"""Get VPP interface information from a given PCI address
From a running VPP instance, attempt to find the interface name from
a given PCI address of a NIC.
From a running VPP instance, attempt to find the interface name and index
from a given PCI address of a NIC. The index is used to identify VPP bond
interface associated with the VPP interface.
:param pci_addr: PCI address to lookup, in the form of DDDD:BB:SS.F, where
- DDDD = Domain
@ -377,7 +378,8 @@ def _get_vpp_interface_name(pci_addr, tries=1, timeout=5):
try:
timestamp = time.time()
processutils.execute('systemctl', 'is-active', 'vpp')
out, err = processutils.execute('vppctl', 'show', 'interface')
out, err = processutils.execute('vppctl', 'show', 'interface',
check_exit_code=False)
logger.debug("vppctl show interface\n%s\n%s\n" % (out, err))
m = re.search(r':([0-9a-fA-F]{2}):([0-9a-fA-F]{2}).([0-9a-fA-F])',
pci_addr)
@ -388,10 +390,12 @@ def _get_vpp_interface_name(pci_addr, tries=1, timeout=5):
else:
raise VppException('Invalid PCI address format: %s' % pci_addr)
m = re.search(r'^(\w+%s)\s+' % formatted_pci, out, re.MULTILINE)
m = re.search(r'^(\w+%s)\s+(\d+)' % formatted_pci, out,
re.MULTILINE)
if m:
logger.debug('VPP interface found: %s' % m.group(1))
return m.group(1)
logger.debug('VPP interface found: %s, index: %s' %
(m.group(1), m.group(2)))
return {'name': m.group(1), 'index': m.group(2)}
except processutils.ProcessExecutionError:
pass
@ -402,7 +406,36 @@ def _get_vpp_interface_name(pci_addr, tries=1, timeout=5):
return None
def generate_vpp_config(vpp_config_path, vpp_interfaces):
def _get_vpp_bond(member_ids):
"""Get VPP bond information from a given list of VPP interface indices
:param member_ids: list of VPP interfaces indices for the bond
:return: VPP bond name and index. None if an interface is not found.
"""
if not member_ids:
return None
member_ids.sort()
member_ids_str = ' '.join(member_ids)
out, err = processutils.execute('vppctl', 'show',
'hardware-interfaces', 'bond', 'brief',
check_exit_code=False)
logger.debug('vppctl show hardware-interfaces bond brief\n%s' % out)
m = re.search(r'^\s*(BondEthernet\d+)\s+(\d+)\s+.+Slave-Idx:\s+%s\s*$' %
member_ids_str,
out,
re.MULTILINE)
if m:
logger.debug('Bond found: %s, index: %s' % (m.group(1), m.group(2)))
return {'name': m.group(1), 'index': m.group(2)}
else:
logger.debug('Bond with member indices "%s" not found in VPP'
% member_ids_str)
return None
def generate_vpp_config(vpp_config_path, vpp_interfaces, vpp_bonds):
"""Generate configuration content for VPP
Generate interface related configuration content for VPP. Current
@ -419,6 +452,7 @@ def generate_vpp_config(vpp_config_path, vpp_interfaces):
:param vpp_config_path: VPP Configuration file path
:param vpp_interfaces: List of VPP interface objects
:param vpp_bonds: List of VPP bond objects
:return: updated VPP config content.
"""
@ -473,8 +507,32 @@ def generate_vpp_config(vpp_config_path, vpp_interfaces):
data,
flags=re.MULTILINE)
else:
logger.debug('pci address not found for interface %s, may have'
'already been bound to vpp' % vpp_interface.name)
raise VppException('Interface %s has no PCI address and is not'
' found in mapping file' % vpp_interface.name)
# Add bond config to 'dpdk' section
for vpp_bond in vpp_bonds:
slave_str = ''
for member in vpp_bond.members:
slave_str += ",slave=%s" % member.pci_dev
if vpp_bond.bonding_options:
options_str = ',' + vpp_bond.bonding_options.strip(' ,')
else:
options_str = ''
if slave_str:
m = re.search(r'^\s*vdev\s+%s.*$' % vpp_bond.name,
data, re.MULTILINE)
if m:
data = re.sub(m.group(0), r' vdev %s%s%s'
% (vpp_bond.name, slave_str, options_str),
data)
else:
data = re.sub(r'(^\s*dpdk\s*\{)',
r'\1\n vdev %s%s%s'
% (vpp_bond.name, slave_str, options_str),
data,
flags=re.MULTILINE)
# Add start up script for VPP to config. This script will be executed by
# VPP on service start.
@ -497,36 +555,29 @@ def generate_vpp_config(vpp_config_path, vpp_interfaces):
return data
def update_vpp_mapping(vpp_interfaces):
def update_vpp_mapping(vpp_interfaces, vpp_bonds):
"""Verify VPP interface binding and update mapping file
VppException will be raised if interfaces are not properly bound.
:param vpp_interfaces: List of VPP interface objects
:param vpp_bonds: List of VPP bond objects
"""
vpp_start_cli = ""
cli_list = []
for vpp_int in vpp_interfaces:
if not vpp_int.pci_dev:
dpdk_map = _get_dpdk_map()
for dpdk_int in dpdk_map:
if dpdk_int['name'] == vpp_int.name:
vpp_int.pci_dev = dpdk_int['pci_address']
break
else:
raise VppException('Interface %s has no PCI address and is not'
' found in mapping file' % vpp_int.name)
# Try to get VPP interface name. In case VPP service is down
# for some reason, we will restart VPP and try again. Currently
# only trying one more time, can turn into a retry_counter if needed
# in the future.
for i in range(2):
vpp_name = _get_vpp_interface_name(vpp_int.pci_dev,
tries=12, timeout=5)
if not vpp_name:
int_info = _get_vpp_interface(vpp_int.pci_dev,
tries=12, timeout=5)
if not int_info:
restart_vpp(vpp_interfaces)
else:
vpp_int.vpp_name = int_info['name']
vpp_int.vpp_idx = int_info['index']
break
else:
raise VppException('Interface %s with pci address %s not '
@ -534,10 +585,13 @@ def update_vpp_mapping(vpp_interfaces):
% (vpp_int.name, vpp_int.pci_dev))
# Generate content of startup script for VPP
for address in vpp_int.addresses:
vpp_start_cli += 'set interface state %s up\n' % vpp_name
vpp_start_cli += 'set interface ip address %s %s/%s\n' \
% (vpp_name, address.ip, address.prefixlen)
if not vpp_bonds:
cli_list.append('set interface state %s up'
% int_info['name'])
for address in vpp_int.addresses:
cli_list.append('set interface ip address %s %s/%s\n'
% (int_info['name'], address.ip,
address.prefixlen))
logger.info('Updating mapping for vpp interface %s:'
'pci_dev: %s mac address: %s uio driver: %s'
@ -545,9 +599,30 @@ def update_vpp_mapping(vpp_interfaces):
vpp_int.uio_driver))
_update_dpdk_map(vpp_int.name, vpp_int.pci_dev, vpp_int.hwaddr,
vpp_int.uio_driver)
# Enable VPP service to make the VPP interface configuration
# persistent.
processutils.execute('systemctl', 'enable', 'vpp')
for vpp_bond in vpp_bonds:
bond_ids = [member.vpp_idx for member in vpp_bond.members]
bond_info = _get_vpp_bond(bond_ids)
if bond_info:
cli_list.append('set interface state %s up'
% bond_info['name'])
for address in vpp_bond.addresses:
cli_list.append('set interface ip address %s %s/%s'
% (bond_info['name'], address.ip,
address.prefixlen))
else:
raise VppException('Bond %s not found in VPP.' % vpp_bond.name)
vpp_start_cli = get_file_data(_VPP_EXEC_FILE)
for cli_line in cli_list:
if not re.search(r'^\s*%s\s*$' % cli_line,
vpp_start_cli, re.MULTILINE):
vpp_start_cli += cli_line + '\n'
if diff(_VPP_EXEC_FILE, vpp_start_cli):
write_config(_VPP_EXEC_FILE, vpp_start_cli)
restart_vpp(vpp_interfaces)
# Enable VPP service to make the VPP interface configuration
# persistent.
processutils.execute('systemctl', 'enable', 'vpp')