Enable os-net-config to support and configure NFVSwitch

These changes are to generate /etc/sysconf/network-scripts/ifcfg-* and
/etc/sysconfig/nfvswitch configuration files for nfvswitch and its interfaces.
NFVSwitch is a virtual switch implementation based on DPDK for datacenter
workloads with very high throughput needs.

Change-Id: If02edb9c4c54c014f67290fe0c34e2fc73cb95bd
This commit is contained in:
Sarath Kumar 2016-07-21 11:14:39 -07:00
parent 311df0c382
commit 15974244f6
8 changed files with 424 additions and 0 deletions

View File

@ -0,0 +1,38 @@
{
"network_config": [
{
"type": "nfvswitch_bridge",
"cpus": "2,3,4,5",
"members": [
{
"type": "interface",
"name": "nic2",
},
{
"type": "interface",
"name": "nic3"
},
{
"type": "nfvswitch_internal",
"name": "api",
"addresses": [
{
"ip_netmask": "172.16.2.7/24"
}
],
"vlan_id": 201
},
{
"type": "nfvswitch_internal",
"name": "storage",
"addresses": [
{
"ip_netmask": "172.16.1.6/24"
}
],
"vlan_id": 202
}
]
}
]
}

View File

@ -0,0 +1,25 @@
network_config:
-
type: nfvswitch_bridge
cpus: "2,3,4,5"
members:
-
type: interface
name: nic2
-
type: interface
name: nic3
-
type: nfvswitch_internal
name: api
vlan_id: 201
addresses:
-
ip_netmask: 172.16.2.7/24
-
type: nfvswitch_internal
name: storage
vlan_id: 202
addresses:
-
ip_netmask: 172.16.1.6/24

View File

@ -51,6 +51,8 @@ class NetConfig(object):
self.add_vlan(obj)
elif isinstance(obj, objects.IvsInterface):
self.add_ivs_interface(obj)
elif isinstance(obj, objects.NfvswitchInternal):
self.add_nfvswitch_internal(obj)
elif isinstance(obj, objects.OvsBridge):
self.add_bridge(obj)
for member in obj.members:
@ -63,6 +65,10 @@ class NetConfig(object):
self.add_ivs_bridge(obj)
for member in obj.members:
self.add_object(member)
elif isinstance(obj, objects.NfvswitchBridge):
self.add_nfvswitch_bridge(obj)
for member in obj.members:
self.add_object(member)
elif isinstance(obj, objects.OvsBond):
self.add_bond(obj)
for member in obj.members:
@ -117,6 +123,13 @@ class NetConfig(object):
"""
raise NotImplemented("add_ivs_bridge is not implemented.")
def add_nfvswitch_bridge(self, bridge):
"""Add a NfvswitchBridge object to the net config object.
:param bridge: The NfvswitchBridge object to add.
"""
raise NotImplemented("add_nfvswitch_bridge is not implemented.")
def add_bond(self, bond):
"""Add an OvsBond object to the net config object.

View File

@ -39,6 +39,10 @@ def ivs_config_path():
return "/etc/sysconfig/ivs"
def nfvswitch_config_path():
return "/etc/sysconfig/nfvswitch"
def route_config_path(name):
return "/etc/sysconfig/network-scripts/route-%s" % name
@ -58,6 +62,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
super(IfcfgNetConfig, self).__init__(noop, root_dir)
self.interface_data = {}
self.ivsinterface_data = {}
self.nfvswitch_intiface_data = {}
self.nfvswitch_cpus = None
self.vlan_data = {}
self.route_data = {}
self.route6_data = {}
@ -103,6 +109,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
data += "PHYSDEV=%s\n" % base_opt.linux_bond_name
elif isinstance(base_opt, objects.IvsInterface):
data += "TYPE=IVSIntPort\n"
elif isinstance(base_opt, objects.NfvswitchInternal):
data += "TYPE=NFVSWITCHIntPort\n"
elif isinstance(base_opt, objects.IbInterface):
data += "TYPE=Infiniband\n"
elif re.match('\w+\.\d+$', base_opt.name):
@ -117,6 +125,9 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if base_opt.ivs_bridge_name:
data += "DEVICETYPE=ivs\n"
data += "IVS_BRIDGE=%s\n" % base_opt.ivs_bridge_name
if base_opt.nfvswitch_bridge_name:
data += "DEVICETYPE=nfvswitch\n"
data += "NFVSWITCH_BRIDGE=%s\n" % base_opt.nfvswitch_bridge_name
if base_opt.ovs_port:
if not isinstance(base_opt, objects.LinuxTeam):
data += "DEVICETYPE=ovs\n"
@ -333,6 +344,19 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if ivs_interface.routes:
self._add_routes(ivs_interface.name, ivs_interface.routes)
def add_nfvswitch_internal(self, nfvswitch_internal):
"""Add a nfvswitch_internal interface object to the net config object.
:param nfvswitch_internal: The nfvswitch_internal object to add.
"""
iface_name = nfvswitch_internal.name
logger.info('adding nfvswitch_internal interface: %s' % iface_name)
data = self._add_common(nfvswitch_internal)
logger.debug('nfvswitch_internal interface data: %s' % data)
self.nfvswitch_intiface_data[iface_name] = data
if nfvswitch_internal.routes:
self._add_routes(iface_name, nfvswitch_internal.routes)
def add_bridge(self, bridge):
"""Add an OvsBridge object to the net config object.
@ -369,6 +393,16 @@ class IfcfgNetConfig(os_net_config.NetConfig):
"""
pass
def add_nfvswitch_bridge(self, bridge):
"""Add a NFVSwitchBridge object to the net config object.
NFVSwitch can only support one virtual switch per node,
using "nfvswitch" as its name. As long as the nfvswitch service
is running, the nfvswitch virtual switch will be available.
:param bridge: The NfvswitchBridge object to add.
"""
self.nfvswitch_cpus = bridge.cpus
def add_bond(self, bond):
"""Add an OvsBond object to the net config object.
@ -462,6 +496,29 @@ class IfcfgNetConfig(os_net_config.NetConfig):
% (uplink_str, intf_str))
return data
def generate_nfvswitch_config(self, nfvswitch_ifaces,
nfvswitch_internal_ifaces):
"""Generate configuration content for nfvswitch."""
cpu_str = ""
if self.nfvswitch_cpus:
cpu_str = " -c " + self.nfvswitch_cpus
ifaces = []
for iface in nfvswitch_ifaces:
ifaces.append(' -u ')
ifaces.append(iface)
iface_str = ''.join(ifaces)
ifaces = []
for iface in nfvswitch_internal_ifaces:
ifaces.append(' -m ')
ifaces.append(iface)
internal_str = ''.join(ifaces)
data = ("SETUP_ARGS=\"%s%s%s\"" % (cpu_str, iface_str, internal_str))
return data
def apply(self, cleanup=False, activate=True):
"""Apply the network configuration.
@ -487,6 +544,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
all_file_names = []
ivs_uplinks = [] # ivs physical uplinks
ivs_interfaces = [] # ivs internal ports
nfvswitch_interfaces = [] # nfvswitch physical interfaces
nfvswitch_internal_ifaces = [] # nfvswitch internal/management ports
for interface_name, iface_data in self.interface_data.iteritems():
route_data = self.route_data.get(interface_name, '')
@ -499,6 +558,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
all_file_names.append(route6_path)
if "IVS_BRIDGE" in iface_data:
ivs_uplinks.append(interface_name)
if "NFVSWITCH_BRIDGE" in iface_data:
nfvswitch_interfaces.append(interface_name)
all_file_names.append(route6_path)
if (utils.diff(interface_path, iface_data) or
utils.diff(route_path, route_data) or
@ -533,6 +594,27 @@ class IfcfgNetConfig(os_net_config.NetConfig):
logger.info('No changes required for ivs interface: %s' %
interface_name)
for iface_name, iface_data in self.nfvswitch_intiface_data.iteritems():
route_data = self.route_data.get(iface_name, '')
route6_data = self.route6_data.get(iface_name, '')
iface_path = self.root_dir + ifcfg_config_path(iface_name)
route_path = self.root_dir + route_config_path(iface_name)
route6_path = self.root_dir + route6_config_path(iface_name)
all_file_names.append(iface_path)
all_file_names.append(route_path)
all_file_names.append(route6_path)
nfvswitch_internal_ifaces.append(iface_name)
if (utils.diff(iface_path, iface_data) or
utils.diff(route_path, route_data)):
restart_interfaces.append(iface_name)
restart_interfaces.extend(self.child_members(iface_name))
update_files[iface_path] = iface_data
update_files[route_path] = route_data
update_files[route6_path] = route6_data
else:
logger.info('No changes required for nfvswitch interface: %s' %
iface_name)
for vlan_name, vlan_data in self.vlan_data.iteritems():
route_data = self.route_data.get(vlan_name, '')
route6_data = self.route6_data.get(vlan_name, '')
@ -698,6 +780,12 @@ class IfcfgNetConfig(os_net_config.NetConfig):
data = self.generate_ivs_config(ivs_uplinks, ivs_interfaces)
self.write_config(location, data)
if nfvswitch_interfaces or nfvswitch_internal_ifaces:
location = nfvswitch_config_path()
data = self.generate_nfvswitch_config(nfvswitch_interfaces,
nfvswitch_internal_ifaces)
self.write_config(location, data)
if activate:
for linux_bond in restart_linux_bonds:
self.ifup(linux_bond)
@ -728,6 +816,19 @@ class IfcfgNetConfig(os_net_config.NetConfig):
self.execute(msg, '/usr/bin/systemctl',
'restart', 'ivs')
if nfvswitch_interfaces or nfvswitch_internal_ifaces:
logger.info("Attach to nfvswitch with "
"interfaces: %s, "
"internal interfaces: %s" %
(nfvswitch_interfaces, nfvswitch_internal_ifaces))
for nfvswitch_interface in nfvswitch_interfaces:
self.ifup(nfvswitch_interface)
for nfvswitch_internal in nfvswitch_internal_ifaces:
self.ifup(nfvswitch_internal)
msg = "Restart nfvswitch"
self.execute(msg, '/usr/bin/systemctl',
'restart', 'nfvswitch')
for vlan in restart_vlans:
self.ifup(vlan)

View File

@ -50,6 +50,10 @@ def object_from_json(json):
return IvsBridge.from_json(json)
elif obj_type == "ivs_interface":
return IvsInterface.from_json(json)
elif obj_type == "nfvswitch_bridge":
return NfvswitchBridge.from_json(json)
elif obj_type == "nfvswitch_internal":
return NfvswitchInternal.from_json(json)
elif obj_type == "ovs_tunnel":
return OvsTunnel.from_json(json)
elif obj_type == "ovs_patch_port":
@ -181,6 +185,7 @@ class _BaseOpts(object):
self.bridge_name = None # internal
self.linux_bridge_name = None # internal
self.ivs_bridge_name = None # internal
self.nfvswitch_bridge_name = None # internal
self.linux_bond_name = None # internal
self.linux_team_name = None # internal
self.ovs_port = False # internal
@ -333,6 +338,32 @@ class IvsInterface(_BaseOpts):
return IvsInterface(vlan_id, name, *opts)
class NfvswitchInternal(_BaseOpts):
"""Base class for nfvswitch internal interfaces."""
def __init__(self, vlan_id, name='nfvswitch', use_dhcp=False,
use_dhcpv6=False, addresses=None, routes=None, mtu=1500,
primary=False, nic_mapping=None, persist_mapping=False,
defroute=True, dhclient_args=None, dns_servers=None):
addresses = addresses or []
routes = routes or []
dns_servers = dns_servers or []
name_vlan = '%s%i' % (name, vlan_id)
super(NfvswitchInternal, self).__init__(name_vlan, use_dhcp,
use_dhcpv6, addresses, routes,
mtu, primary, nic_mapping,
persist_mapping, defroute,
dhclient_args, dns_servers)
self.vlan_id = int(vlan_id)
@staticmethod
def from_json(json):
name = json.get('name')
vlan_id = _get_required_field(json, 'vlan_id', 'NfvswitchInternal')
opts = _BaseOpts.base_opts_from_json(json)
return NfvswitchInternal(vlan_id, name, *opts)
class OvsBridge(_BaseOpts):
"""Base class for OVS bridges."""
@ -510,6 +541,76 @@ class IvsBridge(_BaseOpts):
dns_servers=dns_servers)
class NfvswitchBridge(_BaseOpts):
"""Base class for NFVSwitch bridges.
NFVSwitch is a virtual switch for Linux.
It is compatible with the KVM hypervisor and uses DPDK for packet
forwarding.
"""
def __init__(self, name='nfvswitch', use_dhcp=False, use_dhcpv6=False,
addresses=None, routes=None, mtu=1500, members=None,
nic_mapping=None, persist_mapping=False, defroute=True,
dhclient_args=None, dns_servers=None, cpus=""):
addresses = addresses or []
routes = routes or []
members = members or []
dns_servers = dns_servers or []
super(NfvswitchBridge, self).__init__(name, use_dhcp, use_dhcpv6,
addresses, routes, mtu, False,
nic_mapping, persist_mapping,
defroute, dhclient_args,
dns_servers)
self.cpus = cpus
self.members = members
for member in self.members:
if isinstance(member, OvsBond) or isinstance(member, LinuxBond):
msg = 'NFVSwitch does not support bond interfaces.'
raise InvalidConfigException(msg)
member.nfvswitch_bridge_name = name
member.ovs_port = False
self.primary_interface_name = None
@staticmethod
def from_json(json):
name = 'nfvswitch'
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
persist_mapping, defroute, dhclient_args,
dns_servers) = _BaseOpts.base_opts_from_json(
json, include_primary=False)
# members
members = []
members_json = json.get('members')
if members_json:
if isinstance(members_json, list):
for member in members_json:
members.append(object_from_json(member))
else:
msg = 'Members must be a list.'
raise InvalidConfigException(msg)
cpus = ''
cpus_json = json.get('cpus')
if cpus_json:
if isinstance(cpus_json, basestring):
cpus = cpus_json
else:
msg = '"cpus" must be a string of numbers separated by commas.'
raise InvalidConfigException(msg)
else:
msg = 'Config "cpus" is mandatory.'
raise InvalidConfigException(msg)
return NfvswitchBridge(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, cpus=cpus)
class LinuxTeam(_BaseOpts):
"""Base class for Linux bonds using teamd."""

View File

@ -146,3 +146,21 @@ class TestCli(base.TestCase):
stdout_yaml, stderr = self.run_cli('ARG0 --provider=ifcfg --noop '
'-c %s --detailed-exit-codes'
% interface_yaml, exitcodes=(0,))
def test_nfvswitch_noop_output(self):
nfvswitch_yaml = os.path.join(SAMPLE_BASE, 'nfvswitch.yaml')
nfvswitch_json = os.path.join(SAMPLE_BASE, 'nfvswitch.json')
stdout_yaml, stderr = self.run_cli('ARG0 --provider=ifcfg --noop '
'-c %s' % nfvswitch_yaml)
self.assertEqual('', stderr)
stdout_json, stderr = self.run_cli('ARG0 --provider=ifcfg --noop '
'-c %s' % nfvswitch_json)
self.assertEqual('', stderr)
sanity_devices = ['DEVICE=nic2',
'DEVICE=nic3',
'DEVICE=api201',
'DEVICE=storage202',
'DEVICETYPE=nfvswitch']
for dev in sanity_devices:
self.assertIn(dev, stdout_yaml)
self.assertEqual(stdout_yaml, stdout_json)

View File

@ -292,6 +292,34 @@ NETMASK=255.255.255.0
_IVS_CONFIG = ('DAEMON_ARGS=\"--hitless --certificate /etc/ivs '
'--inband-vlan 4092 -u em1 --internal-port=storage5\"')
_NFVSWITCH_INTERFACE = """# This file is autogenerated by os-net-config
DEVICE=em1
ONBOOT=yes
HOTPLUG=no
NM_CONTROLLED=no
PEERDNS=no
DEVICETYPE=nfvswitch
NFVSWITCH_BRIDGE=nfvswitch
BOOTPROTO=none
"""
_NFVSWITCH_INTERNAL = """# This file is autogenerated by os-net-config
DEVICE=storage5
ONBOOT=yes
HOTPLUG=no
NM_CONTROLLED=no
PEERDNS=no
TYPE=NFVSWITCHIntPort
DEVICETYPE=nfvswitch
NFVSWITCH_BRIDGE=nfvswitch
MTU=1500
BOOTPROTO=static
IPADDR=172.16.2.7
NETMASK=255.255.255.0
"""
_NFVSWITCH_CONFIG = ('SETUP_ARGS=\" -c 2,3,4,5 -u em1 -m storage5\"')
_OVS_IFCFG_PATCH_PORT = """# This file is autogenerated by os-net-config
DEVICE=br-pub-patch
ONBOOT=yes
@ -553,6 +581,25 @@ class TestIfcfgNetConfig(base.TestCase):
data = self.provider.generate_ivs_config(['em1'], ['storage5'])
self.assertEqual(_IVS_CONFIG, data)
def test_network_nfvswitch_with_interfaces_and_internal_interfaces(self):
interface = objects.Interface('em1')
v4_addr = objects.Address('172.16.2.7/24')
nfvswitch_internal = objects.NfvswitchInternal(vlan_id=5,
name='storage',
addresses=[v4_addr])
iface_name = nfvswitch_internal.name
bridge = objects.NfvswitchBridge(members=[interface,
nfvswitch_internal],
cpus="2,3,4,5")
self.provider.add_interface(interface)
self.provider.add_nfvswitch_internal(nfvswitch_internal)
self.provider.add_nfvswitch_bridge(bridge)
self.assertEqual(_NFVSWITCH_INTERFACE, self.get_interface_config())
self.assertEqual(_NFVSWITCH_INTERNAL,
self.provider.nfvswitch_intiface_data[iface_name])
data = self.provider.generate_nfvswitch_config(['em1'], ['storage5'])
self.assertEqual(_NFVSWITCH_CONFIG, data)
def test_add_ib_interface_with_v4_multiple(self):
addresses = [objects.Address('192.168.1.2/24'),
objects.Address('192.168.1.3/32'),

View File

@ -398,6 +398,87 @@ class TestIvsBridge(base.TestCase):
self.assertIn(expected, err)
class TestNfvswitchBridge(base.TestCase):
def test_from_json(self):
data = """{
"type": "nfvswitch_bridge",
"cpus": "2,3,4,5",
"members": [
{"type": "interface", "name": "nic2"}
]
}
"""
bridge = objects.object_from_json(json.loads(data))
self.assertEqual("nfvswitch", bridge.name)
self.assertEqual("2,3,4,5", bridge.cpus)
interface1 = bridge.members[0]
self.assertEqual("nic2", interface1.name)
self.assertEqual(False, interface1.ovs_port)
self.assertEqual("nfvswitch", interface1.nfvswitch_bridge_name)
class TestNfvswitchInterface(base.TestCase):
def test_interface_from_json(self):
data = """{
"type": "nfvswitch_bridge",
"cpus": "2,3,4,5",
"members": [
{"type": "interface","name": "nic1"},
{"type": "interface","name": "nic2"}
]
}
"""
bridge = objects.object_from_json(json.loads(data))
self.assertEqual("nfvswitch", bridge.name)
self.assertEqual("2,3,4,5", bridge.cpus)
interface1 = bridge.members[0]
self.assertEqual("nic1", interface1.name)
interface2 = bridge.members[1]
self.assertEqual("nic2", interface2.name)
self.assertEqual(False, interface2.ovs_port)
self.assertEqual("nfvswitch", interface1.nfvswitch_bridge_name)
def test_nfvswitch_internal_from_json(self):
data = """{
"type": "nfvswitch_bridge",
"cpus": "2,3,4,5",
"members": [
{"type": "nfvswitch_internal", "name": "storage", "vlan_id": 202},
{"type": "nfvswitch_internal", "name": "api", "vlan_id": 201}
]
}
"""
bridge = objects.object_from_json(json.loads(data))
self.assertEqual("nfvswitch", bridge.name)
self.assertEqual("2,3,4,5", bridge.cpus)
interface1 = bridge.members[0]
self.assertEqual("storage202", interface1.name)
interface2 = bridge.members[1]
self.assertEqual("api201", interface2.name)
self.assertEqual(False, interface1.ovs_port)
self.assertEqual("nfvswitch", interface1.nfvswitch_bridge_name)
def test_bond_interface_from_json(self):
data = """{
"type": "nfvswitch_bridge",
"cpus": "2,3,4,5",
"members": [{
"type": "linux_bond", "name": "bond1", "members":
[{"type": "interface", "name": "nic2"},
{"type": "interface", "name": "nic3"}]
}
]
}
"""
err = self.assertRaises(objects.InvalidConfigException,
objects.NfvswitchBridge.from_json,
json.loads(data))
expected = 'NFVSwitch does not support bond interfaces.'
self.assertIn(expected, err)
class TestBond(base.TestCase):
def test_from_json_dhcp(self):