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:
parent
62bc734ad5
commit
d01acefc15
9
etc/os-net-config/samples/linux_bridge.yaml
Normal file
9
etc/os-net-config/samples/linux_bridge.yaml
Normal file
@ -0,0 +1,9 @@
|
||||
network_config:
|
||||
-
|
||||
type: linux_bridge
|
||||
name: br-ctlplane
|
||||
use_dhcp: true
|
||||
members:
|
||||
-
|
||||
type: interface
|
||||
name: em1
|
@ -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.
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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', [])
|
||||
|
@ -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"
|
||||
|
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user