Add Linux Bridge capability to os-net-config ifcfg

This patch adds support for Linux Bridges to os-net-config. This is
done completely with ifcfg files, brctl is not used directly.
Hierarchy is preserved, so a Linux Bridge may have a Linux Bond
as a member, which in turn may have multiple interfaces as members.
This changeset has been updated to include a more specific example
for Linux bridge configuration (that doesn't combine bridging and
bonding).

This change depends on the change to add support for Linux Bonds.

Change-Id: I1ddacd514b02af30139a868071d82cde19b1f946
This commit is contained in:
Dan Sneddon 2015-09-02 17:07:48 -07:00
parent 62bc734ad5
commit d01acefc15
6 changed files with 241 additions and 2 deletions

View File

@ -0,0 +1,9 @@
network_config:
-
type: linux_bridge
name: br-ctlplane
use_dhcp: true
members:
-
type: interface
name: em1

View File

@ -52,6 +52,10 @@ class NetConfig(object):
self.add_bridge(obj)
for member in obj.members:
self.add_object(member)
elif isinstance(obj, objects.LinuxBridge):
self.add_linux_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:
@ -82,6 +86,13 @@ class NetConfig(object):
"""
raise NotImplemented("add_bridge is not implemented.")
def add_linux_bridge(self, bridge):
"""Add a LinuxBridge object to the net config object.
:param bridge: The LinuxBridge object to add.
"""
raise NotImplemented("add_linux_bridge is not implemented.")
def add_bond(self, bond):
"""Add an OvsBond object to the net config object.

View File

@ -50,6 +50,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
self.interface_data = {}
self.route_data = {}
self.bridge_data = {}
self.linuxbridge_data = {}
self.linuxbond_data = {}
self.member_names = {}
self.bond_slaves = {}
@ -92,6 +93,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
else:
data += "TYPE=OVSPort\n"
data += "OVS_BRIDGE=%s\n" % base_opt.bridge_name
if base_opt.linux_bridge_name:
data += "BRIDGE=%s\n" % base_opt.linux_bridge_name
if isinstance(base_opt, objects.OvsBridge):
data += "DEVICETYPE=ovs\n"
data += "TYPE=OVSBridge\n"
@ -124,6 +127,18 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if base_opt.ovs_options:
data += "OVS_OPTIONS=\"%s\"\n" % base_opt.ovs_options
ovs_extra.extend(base_opt.ovs_extra)
elif isinstance(base_opt, objects.LinuxBridge):
data += "TYPE=Bridge\n"
data += "DELAY=0\n"
if base_opt.use_dhcp:
data += "BOOTPROTO=dhcp\n"
if base_opt.members:
members = [member.name for member in base_opt.members]
self.member_names[base_opt.name] = members
if base_opt.primary_interface_name:
primary_name = base_opt.primary_interface_name
primary_mac = utils.interface_mac(primary_name)
data += "MACADDR=\"%s\"\n" % primary_mac
elif isinstance(base_opt, objects.LinuxBond):
if base_opt.primary_interface_name:
primary_name = base_opt.primary_interface_name
@ -240,6 +255,18 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if bridge.routes:
self._add_routes(bridge.name, bridge.routes)
def add_linux_bridge(self, bridge):
"""Add a LinuxBridge object to the net config object.
:param bridge: The LinuxBridge object to add.
"""
logger.info('adding linux bridge: %s' % bridge.name)
data = self._add_common(bridge)
logger.debug('bridge data: %s' % data)
self.linuxbridge_data[bridge.name] = data
if bridge.routes:
self._add_routes(bridge.name, bridge.routes)
def add_bond(self, bond):
"""Add an OvsBond object to the net config object.
@ -315,6 +342,20 @@ class IfcfgNetConfig(os_net_config.NetConfig):
update_files[bridge_route_path] = route_data
logger.info('No changes required for bridge: %s' % bridge_name)
for bridge_name, bridge_data in self.linuxbridge_data.iteritems():
route_data = self.route_data.get(bridge_name, '')
bridge_path = self.root_dir + bridge_config_path(bridge_name)
bridge_route_path = self.root_dir + route_config_path(bridge_name)
all_file_names.append(bridge_path)
all_file_names.append(bridge_route_path)
if (utils.diff(bridge_path, bridge_data) or
utils.diff(bridge_route_path, route_data)):
restart_bridges.append(bridge_name)
restart_interfaces.extend(self.child_members(bridge_name))
update_files[bridge_path] = bridge_data
update_files[bridge_route_path] = route_data
logger.info('No changes required for bridge: %s' % bridge_name)
for bond_name, bond_data in self.linuxbond_data.iteritems():
route_data = self.route_data.get(bond_name, '')
bond_path = self.root_dir + bridge_config_path(bond_name)

View File

@ -42,6 +42,8 @@ def object_from_json(json):
return OvsBond.from_json(json)
elif obj_type == "linux_bond":
return LinuxBond.from_json(json)
elif obj_type == "linux_bridge":
return LinuxBridge.from_json(json)
def _get_required_field(json, name, object_name):
@ -159,6 +161,7 @@ class _BaseOpts(object):
self.dhclient_args = dhclient_args
self.dns_servers = dns_servers
self.bridge_name = None # internal
self.linux_bridge_name = None # internal
self.ovs_port = False # internal
self.primary_interface_name = None # internal
@ -332,6 +335,61 @@ class OvsBridge(_BaseOpts):
dhclient_args=dhclient_args, dns_servers=dns_servers)
class LinuxBridge(_BaseOpts):
"""Base class for Linux bridges."""
def __init__(self, name, 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(LinuxBridge, 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:
member.linux_bridge_name = name
member.ovs_port = False
if member.primary:
if self.primary_interface_name:
msg = 'Only one primary interface allowed per bridge.'
raise InvalidConfigException(msg)
if member.primary_interface_name:
self.primary_interface_name = member.primary_interface_name
else:
self.primary_interface_name = member.name
@staticmethod
def from_json(json):
name = _get_required_field(json, 'name', 'LinuxBridge')
(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 LinuxBridge(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."""
@ -420,8 +478,8 @@ class OvsBond(_BaseOpts):
def from_json(json):
name = _get_required_field(json, 'name', 'OvsBond')
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
persist_mapping, defroute,
dhclient_args, dns_servers) = _BaseOpts.base_opts_from_json(
persist_mapping, defroute, dhclient_args,
dns_servers) = _BaseOpts.base_opts_from_json(
json, include_primary=False)
ovs_options = json.get('ovs_options')
ovs_extra = json.get('ovs_extra', [])

View File

@ -51,6 +51,7 @@ _OVS_IFCFG = _BASE_IFCFG + "DEVICETYPE=ovs\nBOOTPROTO=none\n"
_OVS_BRIDGE_IFCFG = _BASE_IFCFG + "DEVICETYPE=ovs\n"
_LINUX_BRIDGE_IFCFG = _BASE_IFCFG + "BRIDGE=br-ctlplane\nBOOTPROTO=none\n"
_ROUTES = """default via 192.168.1.1 dev em1
172.19.0.0/24 via 192.168.1.1 dev em1
@ -73,6 +74,16 @@ OVSBOOTPROTO=dhcp
OVSDHCPINTERFACES="em1"
"""
_LINUX_BRIDGE_DHCP = """# This file is autogenerated by os-net-config
DEVICE=br-ctlplane
ONBOOT=yes
HOTPLUG=no
NM_CONTROLLED=no
TYPE=Bridge
DELAY=0
BOOTPROTO=dhcp
"""
_OVS_BRIDGE_STATIC = """# This file is autogenerated by os-net-config
DEVICE=br-ctlplane
ONBOOT=yes
@ -85,6 +96,18 @@ IPADDR=192.168.1.2
NETMASK=255.255.255.0
"""
_LINUX_BRIDGE_STATIC = """# This file is autogenerated by os-net-config
DEVICE=br-ctlplane
ONBOOT=yes
HOTPLUG=no
NM_CONTROLLED=no
TYPE=Bridge
DELAY=0
BOOTPROTO=static
IPADDR=192.168.1.2
NETMASK=255.255.255.0
"""
_OVS_BRIDGE_DHCP_PRIMARY_INTERFACE = _OVS_BRIDGE_DHCP + \
"OVS_EXTRA=\"set bridge br-ctlplane other-config:hwaddr=a1:b2:c3:d4:e5\"\n"
@ -223,6 +246,16 @@ class TestIfcfgNetConfig(base.TestCase):
self.assertEqual(_OVS_BRIDGE_DHCP,
self.provider.bridge_data['br-ctlplane'])
def test_network_linux_bridge_with_dhcp(self):
interface = objects.Interface('em1')
bridge = objects.LinuxBridge('br-ctlplane', use_dhcp=True,
members=[interface])
self.provider.add_linux_bridge(bridge)
self.provider.add_interface(interface)
self.assertEqual(_LINUX_BRIDGE_IFCFG, self.get_interface_config())
self.assertEqual(_LINUX_BRIDGE_DHCP,
self.provider.linuxbridge_data['br-ctlplane'])
def test_network_ovs_bridge_static(self):
v4_addr = objects.Address('192.168.1.2/24')
interface = objects.Interface('em1')
@ -234,6 +267,17 @@ class TestIfcfgNetConfig(base.TestCase):
self.assertEqual(_OVS_BRIDGE_STATIC,
self.provider.bridge_data['br-ctlplane'])
def test_network_linux_bridge_static(self):
v4_addr = objects.Address('192.168.1.2/24')
interface = objects.Interface('em1')
bridge = objects.LinuxBridge('br-ctlplane', members=[interface],
addresses=[v4_addr])
self.provider.add_interface(interface)
self.provider.add_bridge(bridge)
self.assertEqual(_LINUX_BRIDGE_IFCFG, self.get_interface_config())
self.assertEqual(_LINUX_BRIDGE_STATIC,
self.provider.bridge_data['br-ctlplane'])
def test_network_ovs_bridge_with_dhcp_primary_interface(self):
def test_interface_mac(name):
return "a1:b2:c3:d4:e5"

View File

@ -267,6 +267,82 @@ class TestBridge(base.TestCase):
self.assertEqual("br-foo", interface2.bridge_name)
class TestLinuxBridge(base.TestCase):
def test_from_json_dhcp(self):
data = """{
"type": "linux_bridge",
"name": "br-foo",
"use_dhcp": true,
"members": [{
"type": "interface",
"name": "em1"
}]
}
"""
bridge = objects.object_from_json(json.loads(data))
self.assertEqual("br-foo", bridge.name)
self.assertEqual(True, bridge.use_dhcp)
interface1 = bridge.members[0]
self.assertEqual("em1", interface1.name)
self.assertEqual(False, interface1.ovs_port)
self.assertEqual("br-foo", interface1.linux_bridge_name)
def test_from_json_dhcp_with_nic1(self):
def dummy_numbered_nics(nic_mapping=None):
return {"nic1": "em5"}
self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics)
data = """{
"type": "linux_bridge",
"name": "br-foo",
"use_dhcp": true,
"members": [{
"type": "interface",
"name": "nic1"
}]
}
"""
bridge = objects.object_from_json(json.loads(data))
self.assertEqual("br-foo", bridge.name)
self.assertEqual(True, bridge.use_dhcp)
interface1 = bridge.members[0]
self.assertEqual("em5", interface1.name)
self.assertEqual(False, interface1.ovs_port)
self.assertEqual("br-foo", interface1.linux_bridge_name)
def test_from_json_primary_interface(self):
data = """{
"type": "linux_bridge",
"name": "br-foo",
"use_dhcp": true,
"members": [
{
"type": "interface",
"name": "em1",
"primary": "true"
},
{
"type": "interface",
"name": "em2"
}]
}
"""
bridge = objects.object_from_json(json.loads(data))
self.assertEqual("br-foo", bridge.name)
self.assertEqual(True, bridge.use_dhcp)
self.assertEqual("em1", bridge.primary_interface_name)
interface1 = bridge.members[0]
self.assertEqual("em1", interface1.name)
self.assertEqual(False, interface1.ovs_port)
self.assertEqual(True, interface1.primary)
self.assertEqual("br-foo", interface1.linux_bridge_name)
interface2 = bridge.members[1]
self.assertEqual("em2", interface2.name)
self.assertEqual(False, interface2.ovs_port)
self.assertEqual("br-foo", interface2.linux_bridge_name)
class TestBond(base.TestCase):
def test_from_json_dhcp(self):