Adding IPoIB child interfaces to the os-net-config

This patch adds support for implementing IPoIB child interfaces using
the os-net-config. It allows the user to create persistent IB childs
using ifcfg implementation. It adds the type ib_child_interface to the
os-net-config interfaces which needs the parent interface and the pkey
id as required fields. The IPoIB child interface should be a seperate
interface from the parent interface.

Change-Id: I9ae739c892318c28ad6165a96644719fa1aa693d
This commit is contained in:
abdallahyas 2020-02-11 14:31:50 +00:00
parent c9d3ba4cc4
commit fd9be8fdb5
8 changed files with 301 additions and 1 deletions

View File

@ -0,0 +1,20 @@
{
"network_config": [
{
"type": "ib_interface",
"name": "ib0",
"use_dhcp": false
},
{
"type": "ib_child_interface",
"parent": "ib0",
"pkey_id": "100",
"use_dhcp": false,
"addresses": [
{
"ip_netmask": "10.20.30.40/24"
}
]
}
]
}

View File

@ -0,0 +1,18 @@
network_config:
# Note(abdallahyas): a parent InfiniBand interface is needed to be up for
# the IPoIB pkey interface to work. The ib0 interface here is just there
# to make sure that it is up, it can be configured separately.
-
type: ib_interface
name: ib0
use_dhcp: false
-
type: ib_child_interface
parent: ib0
pkey_id: "100"
use_dhcp: false
addresses:
-
ip_netmask: 10.20.30.40/24

View File

@ -98,6 +98,8 @@ class NetConfig(object):
self.add_ovs_patch_port(obj)
elif isinstance(obj, objects.IbInterface):
self.add_ib_interface(obj)
elif isinstance(obj, objects.IbChildInterface):
self.add_ib_child_interface(obj)
elif isinstance(obj, objects.OvsDpdkPort):
self.add_ovs_dpdk_port(obj)
elif isinstance(obj, objects.OvsDpdkBond):
@ -215,6 +217,14 @@ class NetConfig(object):
"""
raise NotImplementedError("add_ib_interface is not implemented.")
def add_ib_child_interface(self, ib_child_interface):
"""Add an InfiniBand child interface object to the net config object.
:param ib_child_interface: The InfiniBand child
interface object to add.
"""
raise NotImplementedError("add_ib_child_interface is not implemented.")
def add_ovs_dpdk_port(self, ovs_dpdk_port):
"""Add a OvsDpdkPort object to the net config object.

View File

@ -25,7 +25,6 @@ import os_net_config
from os_net_config import objects
from os_net_config import utils
logger = logging.getLogger(__name__)
# Import the raw NetConfig object so we can call its methods
@ -134,6 +133,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
self.nfvswitch_intiface_data = {}
self.nfvswitch_options = None
self.vlan_data = {}
self.ib_childs_data = {}
self.route_data = {}
self.route6_data = {}
self.route_table_data = {}
@ -374,6 +374,11 @@ class IfcfgNetConfig(os_net_config.NetConfig):
data += "TYPE=NFVSWITCHIntPort\n"
elif isinstance(base_opt, objects.IbInterface):
data += "TYPE=Infiniband\n"
elif isinstance(base_opt, objects.IbChildInterface):
data += "TYPE=Infiniband\n"
data += "PKEY=yes\n"
data += "PHYSDEV=%s\n" % base_opt.parent
data += "PKEY_ID=%s\n" % base_opt.pkey_id
elif re.match('\w+\.\d+$', base_opt.name):
data += "VLAN=yes\n"
elif isinstance(base_opt, objects.Interface):
@ -887,6 +892,22 @@ class IfcfgNetConfig(os_net_config.NetConfig):
% (ib_interface.hwname, ib_interface.name))
self.renamed_interfaces[ib_interface.hwname] = ib_interface.name
def add_ib_child_interface(self, ib_child_interface):
"""Add an InfiniBand child interface object to the net config object.
:param ib_child_interface: The InfiniBand child
interface object to add.
"""
logger.info('adding ib_child_interface: %s' % ib_child_interface.name)
data = self._add_common(ib_child_interface)
logger.debug('ib_child_interface data: %s' % data)
self.ib_childs_data[ib_child_interface.name] = data
if ib_child_interface.routes:
self._add_routes(ib_child_interface.name,
ib_child_interface.routes)
if ib_child_interface.rules:
self._add_rules(ib_child_interface.name, ib_child_interface.rules)
def add_ovs_dpdk_port(self, ovs_dpdk_port):
"""Add a OvsDpdkPort object to the net config object.
@ -1153,6 +1174,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
logger.info('applying network configs...')
restart_interfaces = []
restart_vlans = []
restart_ib_childs = []
restart_bridges = []
restart_linux_bonds = []
restart_linux_teams = []
@ -1514,6 +1536,51 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if utils.diff(vlan_rule_path, rule_data):
update_files[vlan_rule_path] = rule_data
for ib_child_name, ib_child_data in self.ib_childs_data.items():
route_data = self.route_data.get(ib_child_name, '')
route6_data = self.route6_data.get(ib_child_name, '')
rule_data = self.rule_data.get(ib_child_name, '')
ib_child_path = self.root_dir + ifcfg_config_path(ib_child_name)
ib_child_route_path = \
self.root_dir + route_config_path(ib_child_name)
ib_child_route6_path = \
self.root_dir + route6_config_path(ib_child_name)
ib_child_rule_path = \
self.root_dir + route_rule_config_path(ib_child_name)
all_file_names.append(ib_child_path)
all_file_names.append(ib_child_route_path)
all_file_names.append(ib_child_route6_path)
all_file_names.append(ib_child_rule_path)
restarts_concatenated = itertools.chain(restart_interfaces,
restart_bridges,
restart_linux_bonds,
restart_linux_teams)
if (self.parse_ifcfg(ib_child_data).get('PHYSDEV') in
restarts_concatenated):
if ib_child_name not in restart_ib_childs:
restart_ib_childs.append(ib_child_name)
update_files[ib_child_path] = ib_child_data
elif utils.diff(ib_child_path, ib_child_data):
if self.ifcfg_requires_restart(ib_child_path, ib_child_data):
restart_ib_childs.append(ib_child_name)
else:
apply_interfaces.append(
(ib_child_name, ib_child_path, ib_child_data))
update_files[ib_child_path] = ib_child_data
else:
logger.info('No changes required for the ib child interface: '
'%s' % ib_child_name)
if utils.diff(ib_child_route_path, route_data):
update_files[ib_child_route_path] = route_data
if ib_child_name not in restart_ib_childs:
apply_routes.append((ib_child_name, route_data))
if utils.diff(ib_child_route6_path, route6_data):
update_files[ib_child_route6_path] = route6_data
if ib_child_name not in restart_ib_childs:
apply_routes.append((ib_child_name, route6_data))
if utils.diff(ib_child_rule_path, rule_data):
update_files[ib_child_rule_path] = rule_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,
@ -1592,6 +1659,9 @@ class IfcfgNetConfig(os_net_config.NetConfig):
for vlan in restart_vlans:
self.ifdown(vlan)
for ib_child in restart_ib_childs:
self.ifdown(ib_child)
for interface in restart_interfaces:
self.ifdown(interface)
@ -1691,6 +1761,9 @@ class IfcfgNetConfig(os_net_config.NetConfig):
for nfvswitch_internal in nfvswitch_internal_ifaces:
self.ifup(nfvswitch_internal)
for ib_child in restart_ib_childs:
self.ifup(ib_child)
for vlan in restart_vlans:
self.ifup(vlan)

View File

@ -73,6 +73,8 @@ def object_from_json(json):
return OvsPatchPort.from_json(json)
elif obj_type == "ib_interface":
return IbInterface.from_json(json)
elif obj_type == "ib_child_interface":
return IbChildInterface.from_json(json)
elif obj_type == "ovs_dpdk_port":
return OvsDpdkPort.from_json(json)
elif obj_type == "ovs_dpdk_bond":
@ -1244,6 +1246,55 @@ class IbInterface(_BaseOpts):
return IbInterface(name, *opts, ethtool_opts=ethtool_opts)
class IbChildInterface(_BaseOpts):
"""Base class for InfiniBand child network interfaces."""
def __init__(self, parent, pkey_id, use_dhcp=False, use_dhcpv6=False,
addresses=None, routes=None, rules=None, mtu=None,
primary=False, nic_mapping=None, persist_mapping=False,
defroute=True, dhclient_args=None, dns_servers=None,
nm_controlled=True, onboot=True, domain=None):
addresses = addresses or []
routes = routes or []
rules = rules or []
dns_servers = dns_servers or []
self.pkey_id = pkey_id
self.parent = parent
full_pkey_id = 0x8000 | pkey_id
name = "%s.%04x" % (parent, full_pkey_id)
nm_controlled = True
super(IbChildInterface, self).__init__(name, use_dhcp, use_dhcpv6,
addresses, routes, rules, mtu,
primary, nic_mapping,
persist_mapping, defroute,
dhclient_args, dns_servers,
nm_controlled, onboot, domain)
@staticmethod
def from_json(json):
parent = _get_required_field(json, 'parent', 'IbChildInterface')
pkey_id = _get_required_field(json, 'pkey_id', 'IbChildInterface')
if type(pkey_id) == str:
try:
pkey_id = int(pkey_id)
except ValueError:
try:
pkey_id = int(pkey_id, base=16)
except Exception:
# Note (Abdallahyas): We do not care for other
# bases other than decimal and hexa
msg = "pkey only support decimal and hex bases, not int"
raise InvalidConfigException(msg)
else:
msg = "Invalid pkey type: got %s" % type(pkey_id)
raise InvalidConfigException(msg)
if (pkey_id < 0x0001 or pkey_id >= 0x7fff):
msg = "Invalid pkey value 0x%X" % pkey_id
raise InvalidConfigException(msg)
opts = _BaseOpts.base_opts_from_json(json)
return IbChildInterface(parent, pkey_id, *opts)
class OvsDpdkPort(_BaseOpts):
"""Base class for OVS Dpdk Ports."""

View File

@ -1455,6 +1455,52 @@ definitions:
- name
additionalProperties: False
ib_child_interface:
type: object
properties:
type:
enum: ["ib_child_interface"]
parent:
$ref: "#/definitions/string_or_param"
pkey_id:
$ref: "#/definitions/string_or_param"
primary:
$ref: "#/definitions/bool_or_param"
# common options:
use_dhcp:
$ref: "#/definitions/bool_or_param"
use_dhcpv6:
$ref: "#/definitions/bool_or_param"
addresses:
$ref: "#/definitions/list_of_address"
routes:
$ref: "#/definitions/list_of_route"
rules:
$ref: "#/definitions/list_of_rule"
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"
domain:
$ref: "#/definitions/list_of_domain_name_string_or_domain_name_string"
required:
- type
- parent
- pkey_id
additionalProperties: False
type: array
items:
oneOf:
@ -1478,6 +1524,7 @@ items:
- $ref: "#/definitions/nfvswitch_bridge"
- $ref: "#/definitions/nfvswitch_internal"
- $ref: "#/definitions/ib_interface"
- $ref: "#/definitions/ib_child_interface"
- $ref: "#/definitions/vpp_interface"
- $ref: "#/definitions/vpp_bond"
- $ref: "#/definitions/contrail_vrouter"

View File

@ -174,6 +174,19 @@ IPADDR2=10.0.0.2
NETMASK2=255.0.0.0
"""
_BASE_IB_CHILD_IFCFG = """# This file is autogenerated by os-net-config
DEVICE=ib0.8001
ONBOOT=yes
HOTPLUG=no
NM_CONTROLLED=yes
PEERDNS=no
TYPE=Infiniband
PKEY=yes
PHYSDEV=ib0
PKEY_ID=1
BOOTPROTO=none
"""
_V6_IFCFG = _BASE_IFCFG + """IPV6INIT=yes
IPV6_AUTOCONF=no
IPV6ADDR=2001:abc:a::/64
@ -974,6 +987,13 @@ class TestIfcfgNetConfig(base.TestCase):
self.get_interface_config('ib0'))
self.assertEqual('', self.get_route_config())
def test_add_ib_child_interface(self):
ib_child_interface = objects.IbChildInterface('ib0', 1)
self.provider.add_interface(ib_child_interface)
self.assertEqual(_BASE_IB_CHILD_IFCFG,
self.get_interface_config('ib0.8001'))
self.assertEqual('', self.get_route_config())
def test_add_contrail_vrouter(self):
addresses = [objects.Address('10.0.0.30/24')]
interface1 = objects.Interface('em3')

View File

@ -1597,6 +1597,67 @@ class TestIbInterface(base.TestCase):
self.assertEqual("metric 10", route1.route_options)
class TestIbChildInterface(base.TestCase):
def test_ib_child_interface_name(self):
ib_child_interface = objects.IbChildInterface('foo', pkey_id=100)
self.assertEqual("foo.8064", ib_child_interface.name)
self.assertEqual("foo", ib_child_interface.parent)
def test_ib_child_interface_pkey_id_valid(self):
ib_child_interface = objects.IbChildInterface('foo', pkey_id=100)
self.assertEqual(100, ib_child_interface.pkey_id)
def test_ib_child_interface_pkek_id_str(self):
data = '{"type": "ib_child_interface", ' \
'"parent": "foo", "pkey_id": "100"}'
ib_child_interface = \
objects.IbChildInterface.from_json(json.loads(data))
self.assertEqual(100, ib_child_interface.pkey_id)
def test_ib_child_interface_pkey_id_str_hexa(self):
data = '{"type": "ib_child_interface", ' \
'"parent": "foo", "pkey_id": "0x64"}'
ib_child_interface = \
objects.IbChildInterface.from_json(json.loads(data))
self.assertEqual(100, ib_child_interface.pkey_id)
def test_ib_child_interface_pkey_id_str_invalid_base(self):
data = '{"type": "ib_child_interface", ' \
'"parent": "foo", "pkey_id": "0b01100100"}'
self.assertRaises(objects.InvalidConfigException,
objects.object_from_json,
json.loads(data))
def test_ib_child_interface_nm_controlled_true(self):
data = '{"type": "ib_child_interface", ' \
'"parent": "foo", "pkey_id": "100"}'
ib_child_interface = \
objects.IbChildInterface.from_json(json.loads(data))
self.assertEqual(True, ib_child_interface.nm_controlled)
def test_ib_child_interface_pkey_id_zero(self):
data = '{"type": "ib_child_interface", ' \
'"parent": "foo", "pkey_id": "0"}'
self.assertRaises(objects.InvalidConfigException,
objects.object_from_json,
json.loads(data))
def test_ib_child_interface_pkey_id_managment(self):
data = '{"type": "ib_child_interface", ' \
'"parent": "foo", "pkey_id": "0x7fff"}'
self.assertRaises(objects.InvalidConfigException,
objects.object_from_json,
json.loads(data))
def test_ib_child_interface_pkey_id_max(self):
data = '{"type": "ib_child_interface", ' \
'"parent": "foo", "pkey_id": "0x8001"}'
self.assertRaises(objects.InvalidConfigException,
objects.object_from_json,
json.loads(data))
class TestNicMapping(base.TestCase):
# We want to test the function, not the dummy..