Enable os_net_config to configure IVS
This change generates /etc/sysconf/network-scripts/ifcfg-* for ivs. It also generates /etc/sysconf/ivs configuration file for ivs. It supports only RedHat at this point. Indigo Virtual Switch (IVS, https://github.com/floodlight/ivs) is a virtual switch for Linux. It is compatible with the KVM hypervisor and leveraging the Open vSwitch kernel module for packet forwarding. There are three major differences between IVS and OVS: 1. Each node can have at most one ivs, name is not required. 2. Bond is not allowed to attach to an ivs. It is the SDN controller's job to dynamically form bonds on ivs. 3. IP address can only be statically assigned. Change-Id: I276d736794d123405de793c2a4eb2c1ee55a0fad
This commit is contained in:
parent
c545e46f8f
commit
63659fe4a6
37
etc/os-net-config/samples/ivs.json
Normal file
37
etc/os-net-config/samples/ivs.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"network_config": [
|
||||
{
|
||||
"type": "ivs_bridge",
|
||||
"members": [
|
||||
{
|
||||
"type": "interface",
|
||||
"name": "nic2",
|
||||
},
|
||||
{
|
||||
"type": "interface",
|
||||
"name": "nic3"
|
||||
},
|
||||
{
|
||||
"type": "ivs_interface",
|
||||
"name": "api",
|
||||
"addresses": [
|
||||
{
|
||||
"ip_netmask": "172.16.2.7/24"
|
||||
}
|
||||
],
|
||||
"vlan_id": 201
|
||||
},
|
||||
{
|
||||
"type": "ivs_interface",
|
||||
"name": "storage",
|
||||
"addresses": [
|
||||
{
|
||||
"ip_netmask": "172.16.1.6/24"
|
||||
}
|
||||
],
|
||||
"vlan_id": 202
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
24
etc/os-net-config/samples/ivs.yaml
Normal file
24
etc/os-net-config/samples/ivs.yaml
Normal file
@ -0,0 +1,24 @@
|
||||
network_config:
|
||||
-
|
||||
type: ivs_bridge
|
||||
members:
|
||||
-
|
||||
type: interface
|
||||
name: nic2
|
||||
-
|
||||
type: interface
|
||||
name: nic3
|
||||
-
|
||||
type: ivs_interface
|
||||
name: api
|
||||
vlan_id: 201
|
||||
addresses:
|
||||
-
|
||||
ip_netmask: 172.16.2.7/24
|
||||
-
|
||||
type: ivs_interface
|
||||
name: storage
|
||||
vlan_id: 202
|
||||
addresses:
|
||||
-
|
||||
ip_netmask: 172.16.1.6/24
|
@ -48,6 +48,8 @@ class NetConfig(object):
|
||||
self.add_interface(obj)
|
||||
elif isinstance(obj, objects.Vlan):
|
||||
self.add_vlan(obj)
|
||||
elif isinstance(obj, objects.IvsInterface):
|
||||
self.add_ivs_interface(obj)
|
||||
elif isinstance(obj, objects.OvsBridge):
|
||||
self.add_bridge(obj)
|
||||
for member in obj.members:
|
||||
@ -56,6 +58,10 @@ class NetConfig(object):
|
||||
self.add_linux_bridge(obj)
|
||||
for member in obj.members:
|
||||
self.add_object(member)
|
||||
elif isinstance(obj, objects.IvsBridge):
|
||||
self.add_ivs_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:
|
||||
@ -93,6 +99,13 @@ class NetConfig(object):
|
||||
"""
|
||||
raise NotImplemented("add_linux_bridge is not implemented.")
|
||||
|
||||
def add_ivs_bridge(self, bridge):
|
||||
"""Add a IvsBridge object to the net config object.
|
||||
|
||||
:param bridge: The IvsBridge object to add.
|
||||
"""
|
||||
raise NotImplemented("add_ivs_bridge is not implemented.")
|
||||
|
||||
def add_bond(self, bond):
|
||||
"""Add an OvsBond object to the net config object.
|
||||
|
||||
|
@ -35,6 +35,10 @@ def bridge_config_path(name):
|
||||
return ifcfg_config_path(name)
|
||||
|
||||
|
||||
def ivs_config_path():
|
||||
return "/etc/sysconfig/ivs"
|
||||
|
||||
|
||||
def route_config_path(name):
|
||||
return "/etc/sysconfig/network-scripts/route-%s" % name
|
||||
|
||||
@ -49,6 +53,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
||||
def __init__(self, noop=False, root_dir=''):
|
||||
super(IfcfgNetConfig, self).__init__(noop, root_dir)
|
||||
self.interface_data = {}
|
||||
self.ivsinterface_data = {}
|
||||
self.route_data = {}
|
||||
self.bridge_data = {}
|
||||
self.linuxbridge_data = {}
|
||||
@ -84,8 +89,13 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
||||
data += "VLAN=yes\n"
|
||||
if base_opt.device:
|
||||
data += "PHYSDEV=%s\n" % base_opt.device
|
||||
elif isinstance(base_opt, objects.IvsInterface):
|
||||
data += "TYPE=IVSIntPort\n"
|
||||
elif re.match('\w+\.\d+$', base_opt.name):
|
||||
data += "VLAN=yes\n"
|
||||
if base_opt.ivs_bridge_name:
|
||||
data += "DEVICETYPE=ivs\n"
|
||||
data += "IVS_BRIDGE=%s\n" % base_opt.ivs_bridge_name
|
||||
if base_opt.ovs_port:
|
||||
data += "DEVICETYPE=ovs\n"
|
||||
if base_opt.bridge_name:
|
||||
@ -251,6 +261,18 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
||||
if vlan.routes:
|
||||
self._add_routes(vlan.name, vlan.routes)
|
||||
|
||||
def add_ivs_interface(self, ivs_interface):
|
||||
"""Add a ivs_interface object to the net config object.
|
||||
|
||||
:param ivs_interface: The ivs_interface object to add.
|
||||
"""
|
||||
logger.info('adding ivs_interface: %s' % ivs_interface.name)
|
||||
data = self._add_common(ivs_interface)
|
||||
logger.debug('ivs_interface data: %s' % data)
|
||||
self.ivsinterface_data[ivs_interface.name] = data
|
||||
if ivs_interface.routes:
|
||||
self._add_routes(ivs_interface.name, ivs_interface.routes)
|
||||
|
||||
def add_bridge(self, bridge):
|
||||
"""Add an OvsBridge object to the net config object.
|
||||
|
||||
@ -275,6 +297,18 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
||||
if bridge.routes:
|
||||
self._add_routes(bridge.name, bridge.routes)
|
||||
|
||||
def add_ivs_bridge(self, bridge):
|
||||
"""Add a IvsBridge object to the net config object.
|
||||
|
||||
IVS can only support one virtual switch per node,
|
||||
using "ivs" as its name. As long as the ivs service
|
||||
is running, the ivs virtual switch will be there.
|
||||
It is impossible to add multiple ivs virtual switches
|
||||
per node.
|
||||
:param bridge: The IvsBridge object to add.
|
||||
"""
|
||||
pass
|
||||
|
||||
def add_bond(self, bond):
|
||||
"""Add an OvsBond object to the net config object.
|
||||
|
||||
@ -300,6 +334,26 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
||||
if bond.routes:
|
||||
self._add_routes(bond.name, bond.routes)
|
||||
|
||||
def generate_ivs_config(self, ivs_uplinks, ivs_interfaces):
|
||||
"""Generate configuration content for ivs."""
|
||||
|
||||
intfs = []
|
||||
for intf in ivs_uplinks:
|
||||
intfs.append(' -u ')
|
||||
intfs.append(intf)
|
||||
uplink_str = ''.join(intfs)
|
||||
|
||||
intfs = []
|
||||
for intf in ivs_interfaces:
|
||||
intfs.append(' --internal-port=')
|
||||
intfs.append(intf)
|
||||
intf_str = ''.join(intfs)
|
||||
|
||||
data = ("DAEMON_ARGS=\"--hitless --certificate /etc/ivs "
|
||||
"--inband-vlan 4092%s%s\""
|
||||
% (uplink_str, intf_str))
|
||||
return data
|
||||
|
||||
def apply(self, cleanup=False, activate=True):
|
||||
"""Apply the network configuration.
|
||||
|
||||
@ -320,6 +374,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
||||
restart_bridges = []
|
||||
update_files = {}
|
||||
all_file_names = []
|
||||
ivs_uplinks = [] # ivs physical uplinks
|
||||
ivs_interfaces = [] # ivs internal ports
|
||||
|
||||
for interface_name, iface_data in self.interface_data.iteritems():
|
||||
route_data = self.route_data.get(interface_name, '')
|
||||
@ -327,6 +383,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
||||
route_path = self.root_dir + route_config_path(interface_name)
|
||||
all_file_names.append(interface_path)
|
||||
all_file_names.append(route_path)
|
||||
if "IVS_BRIDGE" in iface_data:
|
||||
ivs_uplinks.append(interface_name)
|
||||
if (utils.diff(interface_path, iface_data) or
|
||||
utils.diff(route_path, route_data)):
|
||||
restart_interfaces.append(interface_name)
|
||||
@ -336,6 +394,22 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
||||
logger.info('No changes required for interface: %s' %
|
||||
interface_name)
|
||||
|
||||
for interface_name, iface_data in self.ivsinterface_data.iteritems():
|
||||
route_data = self.route_data.get(interface_name, '')
|
||||
interface_path = self.root_dir + ifcfg_config_path(interface_name)
|
||||
route_path = self.root_dir + route_config_path(interface_name)
|
||||
all_file_names.append(interface_path)
|
||||
all_file_names.append(route_path)
|
||||
ivs_interfaces.append(interface_name)
|
||||
if (utils.diff(interface_path, iface_data) or
|
||||
utils.diff(route_path, route_data)):
|
||||
restart_interfaces.append(interface_name)
|
||||
restart_interfaces.extend(self.child_members(interface_name))
|
||||
update_files[interface_path] = iface_data
|
||||
update_files[route_path] = route_data
|
||||
logger.info('No changes required for ivs interface: %s' %
|
||||
interface_name)
|
||||
|
||||
for bridge_name, bridge_data in self.bridge_data.iteritems():
|
||||
route_data = self.route_data.get(bridge_name, '')
|
||||
bridge_path = self.root_dir + bridge_config_path(bridge_name)
|
||||
@ -402,6 +476,11 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
||||
for location, data in update_files.iteritems():
|
||||
self.write_config(location, data)
|
||||
|
||||
if ivs_uplinks or ivs_interfaces:
|
||||
location = ivs_config_path()
|
||||
data = self.generate_ivs_config(ivs_uplinks, ivs_interfaces)
|
||||
self.write_config(location, data)
|
||||
|
||||
if activate:
|
||||
for bridge in restart_bridges:
|
||||
self.ifup(bridge, iftype='bridge')
|
||||
@ -413,4 +492,17 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
||||
self.ovs_appctl('bond/set-active-slave', bond,
|
||||
self.bond_primary_ifaces[bond])
|
||||
|
||||
if ivs_uplinks or ivs_interfaces:
|
||||
logger.info("Attach to ivs with "
|
||||
"uplinks: %s, "
|
||||
"interfaces: %s" %
|
||||
(ivs_uplinks, ivs_interfaces))
|
||||
for ivs_uplink in ivs_uplinks:
|
||||
self.ifup(ivs_uplink)
|
||||
for ivs_interface in ivs_interfaces:
|
||||
self.ifup(ivs_interface)
|
||||
msg = "Restart ivs"
|
||||
self.execute(msg, '/usr/bin/systemctl',
|
||||
'restart', 'ivs')
|
||||
|
||||
return update_files
|
||||
|
@ -44,6 +44,10 @@ def object_from_json(json):
|
||||
return LinuxBond.from_json(json)
|
||||
elif obj_type == "linux_bridge":
|
||||
return LinuxBridge.from_json(json)
|
||||
elif obj_type == "ivs_bridge":
|
||||
return IvsBridge.from_json(json)
|
||||
elif obj_type == "ivs_interface":
|
||||
return IvsInterface.from_json(json)
|
||||
|
||||
|
||||
def _get_required_field(json, name, object_name):
|
||||
@ -166,6 +170,7 @@ class _BaseOpts(object):
|
||||
self.dns_servers = dns_servers
|
||||
self.bridge_name = None # internal
|
||||
self.linux_bridge_name = None # internal
|
||||
self.ivs_bridge_name = None # internal
|
||||
self.ovs_port = False # internal
|
||||
self.primary_interface_name = None # internal
|
||||
|
||||
@ -290,6 +295,32 @@ class Vlan(_BaseOpts):
|
||||
return Vlan(device, vlan_id, *opts)
|
||||
|
||||
|
||||
class IvsInterface(_BaseOpts):
|
||||
"""Base class for ivs interfaces."""
|
||||
|
||||
def __init__(self, vlan_id, name='ivs', 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(IvsInterface, 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', 'IvsInterface')
|
||||
opts = _BaseOpts.base_opts_from_json(json)
|
||||
return IvsInterface(vlan_id, name, *opts)
|
||||
|
||||
|
||||
class OvsBridge(_BaseOpts):
|
||||
"""Base class for OVS bridges."""
|
||||
|
||||
@ -405,6 +436,67 @@ class LinuxBridge(_BaseOpts):
|
||||
dns_servers=dns_servers)
|
||||
|
||||
|
||||
class IvsBridge(_BaseOpts):
|
||||
"""Base class for IVS bridges.
|
||||
|
||||
Indigo Virtual Switch (IVS) is a virtual switch for Linux.
|
||||
It is compatible with the KVM hypervisor and leveraging the
|
||||
Open vSwitch kernel module for packet forwarding. There are
|
||||
three major differences between IVS and OVS:
|
||||
1. Each node can have at most one ivs, no name required.
|
||||
2. Bond is not allowed to attach to an ivs. It is the SDN
|
||||
controller's job to dynamically form bonds on ivs.
|
||||
3. IP address can only be statically assigned.
|
||||
"""
|
||||
|
||||
def __init__(self, name='ivs', 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):
|
||||
addresses = addresses or []
|
||||
routes = routes or []
|
||||
members = members or []
|
||||
dns_servers = dns_servers or []
|
||||
super(IvsBridge, self).__init__(name, use_dhcp, use_dhcpv6,
|
||||
addresses, routes, mtu, False,
|
||||
nic_mapping, persist_mapping,
|
||||
defroute, dhclient_args, dns_servers)
|
||||
self.members = members
|
||||
for member in self.members:
|
||||
if isinstance(member, OvsBond) or isinstance(member, LinuxBond):
|
||||
msg = 'IVS does not support bond interfaces.'
|
||||
raise InvalidConfigException(msg)
|
||||
member.ivs_bridge_name = name
|
||||
member.ovs_port = False
|
||||
self.primary_interface_name = None # ivs doesn't use primary intf
|
||||
|
||||
@staticmethod
|
||||
def from_json(json):
|
||||
name = 'ivs'
|
||||
(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)
|
||||
|
||||
return IvsBridge(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)
|
||||
|
||||
|
||||
class LinuxBond(_BaseOpts):
|
||||
"""Base class for Linux bonds."""
|
||||
|
||||
|
@ -187,6 +187,34 @@ BOOTPROTO=dhcp
|
||||
"""
|
||||
|
||||
|
||||
_IVS_UPLINK = """# This file is autogenerated by os-net-config
|
||||
DEVICE=em1
|
||||
ONBOOT=yes
|
||||
HOTPLUG=no
|
||||
NM_CONTROLLED=no
|
||||
DEVICETYPE=ivs
|
||||
IVS_BRIDGE=ivs
|
||||
BOOTPROTO=none
|
||||
"""
|
||||
|
||||
_IVS_INTERFACE = """# This file is autogenerated by os-net-config
|
||||
DEVICE=storage5
|
||||
ONBOOT=yes
|
||||
HOTPLUG=no
|
||||
NM_CONTROLLED=no
|
||||
TYPE=IVSIntPort
|
||||
DEVICETYPE=ivs
|
||||
IVS_BRIDGE=ivs
|
||||
MTU=1500
|
||||
BOOTPROTO=static
|
||||
IPADDR=172.16.2.7
|
||||
NETMASK=255.255.255.0
|
||||
"""
|
||||
|
||||
_IVS_CONFIG = ('DAEMON_ARGS=\"--hitless --certificate /etc/ivs '
|
||||
'--inband-vlan 4092 -u em1 --internal-port=storage5\"')
|
||||
|
||||
|
||||
class TestIfcfgNetConfig(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
@ -348,6 +376,22 @@ class TestIfcfgNetConfig(base.TestCase):
|
||||
self.assertEqual(_OVS_BRIDGE_DHCP_OVS_EXTRA,
|
||||
self.provider.bridge_data['br-ctlplane'])
|
||||
|
||||
def test_network_ivs_with_uplink_and_interface(self):
|
||||
interface = objects.Interface('em1')
|
||||
v4_addr = objects.Address('172.16.2.7/24')
|
||||
ivs_interface = objects.IvsInterface(vlan_id=5,
|
||||
name='storage',
|
||||
addresses=[v4_addr])
|
||||
bridge = objects.IvsBridge(members=[interface, ivs_interface])
|
||||
self.provider.add_interface(interface)
|
||||
self.provider.add_ivs_interface(ivs_interface)
|
||||
self.provider.add_bridge(bridge)
|
||||
self.assertEqual(_IVS_UPLINK, self.get_interface_config())
|
||||
self.assertEqual(_IVS_INTERFACE,
|
||||
self.provider.ivsinterface_data[ivs_interface.name])
|
||||
data = self.provider.generate_ivs_config(['em1'], ['storage5'])
|
||||
self.assertEqual(_IVS_CONFIG, data)
|
||||
|
||||
def test_add_vlan(self):
|
||||
vlan = objects.Vlan('em1', 5)
|
||||
self.provider.add_vlan(vlan)
|
||||
|
@ -343,6 +343,61 @@ class TestLinuxBridge(base.TestCase):
|
||||
self.assertEqual("br-foo", interface2.linux_bridge_name)
|
||||
|
||||
|
||||
class TestIvsBridge(base.TestCase):
|
||||
|
||||
def test_interface_from_json(self):
|
||||
data = """{
|
||||
"type": "ivs_bridge",
|
||||
"members": [{
|
||||
"type": "interface",
|
||||
"name": "nic2"
|
||||
}]
|
||||
}
|
||||
"""
|
||||
bridge = objects.object_from_json(json.loads(data))
|
||||
self.assertEqual("ivs", bridge.name)
|
||||
interface1 = bridge.members[0]
|
||||
self.assertEqual("nic2", interface1.name)
|
||||
self.assertEqual(False, interface1.ovs_port)
|
||||
self.assertEqual("ivs", interface1.ivs_bridge_name)
|
||||
|
||||
def test_ivs_interface_from_json(self):
|
||||
data = """{
|
||||
"type": "ivs_bridge",
|
||||
"members": [{
|
||||
"type": "ivs_interface",
|
||||
"name": "storage",
|
||||
"vlan_id": 202
|
||||
}]
|
||||
}
|
||||
"""
|
||||
bridge = objects.object_from_json(json.loads(data))
|
||||
self.assertEqual("ivs", bridge.name)
|
||||
interface1 = bridge.members[0]
|
||||
self.assertEqual("storage202", interface1.name)
|
||||
self.assertEqual(False, interface1.ovs_port)
|
||||
self.assertEqual("ivs", interface1.ivs_bridge_name)
|
||||
|
||||
def test_bond_interface_from_json(self):
|
||||
data = """{
|
||||
"type": "ivs_bridge",
|
||||
"members": [{
|
||||
"type": "linux_bond",
|
||||
"name": "bond1",
|
||||
"members": [
|
||||
{"type": "interface", "name": "nic2"},
|
||||
{"type": "interface", "name": "nic3"}
|
||||
]
|
||||
}]
|
||||
}
|
||||
"""
|
||||
err = self.assertRaises(objects.InvalidConfigException,
|
||||
objects.IvsBridge.from_json,
|
||||
json.loads(data))
|
||||
expected = 'IVS does not support bond interfaces.'
|
||||
self.assertIn(expected, err)
|
||||
|
||||
|
||||
class TestBond(base.TestCase):
|
||||
|
||||
def test_from_json_dhcp(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user