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)
|
self.add_bridge(obj)
|
||||||
for member in obj.members:
|
for member in obj.members:
|
||||||
self.add_object(member)
|
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):
|
elif isinstance(obj, objects.OvsBond):
|
||||||
self.add_bond(obj)
|
self.add_bond(obj)
|
||||||
for member in obj.members:
|
for member in obj.members:
|
||||||
@ -82,6 +86,13 @@ class NetConfig(object):
|
|||||||
"""
|
"""
|
||||||
raise NotImplemented("add_bridge is not implemented.")
|
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):
|
def add_bond(self, bond):
|
||||||
"""Add an OvsBond object to the net config object.
|
"""Add an OvsBond object to the net config object.
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
|||||||
self.interface_data = {}
|
self.interface_data = {}
|
||||||
self.route_data = {}
|
self.route_data = {}
|
||||||
self.bridge_data = {}
|
self.bridge_data = {}
|
||||||
|
self.linuxbridge_data = {}
|
||||||
self.linuxbond_data = {}
|
self.linuxbond_data = {}
|
||||||
self.member_names = {}
|
self.member_names = {}
|
||||||
self.bond_slaves = {}
|
self.bond_slaves = {}
|
||||||
@ -92,6 +93,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
|||||||
else:
|
else:
|
||||||
data += "TYPE=OVSPort\n"
|
data += "TYPE=OVSPort\n"
|
||||||
data += "OVS_BRIDGE=%s\n" % base_opt.bridge_name
|
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):
|
if isinstance(base_opt, objects.OvsBridge):
|
||||||
data += "DEVICETYPE=ovs\n"
|
data += "DEVICETYPE=ovs\n"
|
||||||
data += "TYPE=OVSBridge\n"
|
data += "TYPE=OVSBridge\n"
|
||||||
@ -124,6 +127,18 @@ class IfcfgNetConfig(os_net_config.NetConfig):
|
|||||||
if base_opt.ovs_options:
|
if base_opt.ovs_options:
|
||||||
data += "OVS_OPTIONS=\"%s\"\n" % base_opt.ovs_options
|
data += "OVS_OPTIONS=\"%s\"\n" % base_opt.ovs_options
|
||||||
ovs_extra.extend(base_opt.ovs_extra)
|
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):
|
elif isinstance(base_opt, objects.LinuxBond):
|
||||||
if base_opt.primary_interface_name:
|
if base_opt.primary_interface_name:
|
||||||
primary_name = 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:
|
if bridge.routes:
|
||||||
self._add_routes(bridge.name, 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):
|
def add_bond(self, bond):
|
||||||
"""Add an OvsBond object to the net config object.
|
"""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
|
update_files[bridge_route_path] = route_data
|
||||||
logger.info('No changes required for bridge: %s' % bridge_name)
|
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():
|
for bond_name, bond_data in self.linuxbond_data.iteritems():
|
||||||
route_data = self.route_data.get(bond_name, '')
|
route_data = self.route_data.get(bond_name, '')
|
||||||
bond_path = self.root_dir + bridge_config_path(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)
|
return OvsBond.from_json(json)
|
||||||
elif obj_type == "linux_bond":
|
elif obj_type == "linux_bond":
|
||||||
return LinuxBond.from_json(json)
|
return LinuxBond.from_json(json)
|
||||||
|
elif obj_type == "linux_bridge":
|
||||||
|
return LinuxBridge.from_json(json)
|
||||||
|
|
||||||
|
|
||||||
def _get_required_field(json, name, object_name):
|
def _get_required_field(json, name, object_name):
|
||||||
@ -159,6 +161,7 @@ class _BaseOpts(object):
|
|||||||
self.dhclient_args = dhclient_args
|
self.dhclient_args = dhclient_args
|
||||||
self.dns_servers = dns_servers
|
self.dns_servers = dns_servers
|
||||||
self.bridge_name = None # internal
|
self.bridge_name = None # internal
|
||||||
|
self.linux_bridge_name = None # internal
|
||||||
self.ovs_port = False # internal
|
self.ovs_port = False # internal
|
||||||
self.primary_interface_name = None # internal
|
self.primary_interface_name = None # internal
|
||||||
|
|
||||||
@ -332,6 +335,61 @@ class OvsBridge(_BaseOpts):
|
|||||||
dhclient_args=dhclient_args, dns_servers=dns_servers)
|
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):
|
class LinuxBond(_BaseOpts):
|
||||||
"""Base class for Linux bonds."""
|
"""Base class for Linux bonds."""
|
||||||
|
|
||||||
@ -420,8 +478,8 @@ class OvsBond(_BaseOpts):
|
|||||||
def from_json(json):
|
def from_json(json):
|
||||||
name = _get_required_field(json, 'name', 'OvsBond')
|
name = _get_required_field(json, 'name', 'OvsBond')
|
||||||
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
|
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
|
||||||
persist_mapping, defroute,
|
persist_mapping, defroute, dhclient_args,
|
||||||
dhclient_args, dns_servers) = _BaseOpts.base_opts_from_json(
|
dns_servers) = _BaseOpts.base_opts_from_json(
|
||||||
json, include_primary=False)
|
json, include_primary=False)
|
||||||
ovs_options = json.get('ovs_options')
|
ovs_options = json.get('ovs_options')
|
||||||
ovs_extra = json.get('ovs_extra', [])
|
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"
|
_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
|
_ROUTES = """default via 192.168.1.1 dev em1
|
||||||
172.19.0.0/24 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"
|
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
|
_OVS_BRIDGE_STATIC = """# This file is autogenerated by os-net-config
|
||||||
DEVICE=br-ctlplane
|
DEVICE=br-ctlplane
|
||||||
ONBOOT=yes
|
ONBOOT=yes
|
||||||
@ -85,6 +96,18 @@ IPADDR=192.168.1.2
|
|||||||
NETMASK=255.255.255.0
|
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_BRIDGE_DHCP_PRIMARY_INTERFACE = _OVS_BRIDGE_DHCP + \
|
||||||
"OVS_EXTRA=\"set bridge br-ctlplane other-config:hwaddr=a1:b2:c3:d4:e5\"\n"
|
"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.assertEqual(_OVS_BRIDGE_DHCP,
|
||||||
self.provider.bridge_data['br-ctlplane'])
|
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):
|
def test_network_ovs_bridge_static(self):
|
||||||
v4_addr = objects.Address('192.168.1.2/24')
|
v4_addr = objects.Address('192.168.1.2/24')
|
||||||
interface = objects.Interface('em1')
|
interface = objects.Interface('em1')
|
||||||
@ -234,6 +267,17 @@ class TestIfcfgNetConfig(base.TestCase):
|
|||||||
self.assertEqual(_OVS_BRIDGE_STATIC,
|
self.assertEqual(_OVS_BRIDGE_STATIC,
|
||||||
self.provider.bridge_data['br-ctlplane'])
|
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_network_ovs_bridge_with_dhcp_primary_interface(self):
|
||||||
def test_interface_mac(name):
|
def test_interface_mac(name):
|
||||||
return "a1:b2:c3:d4:e5"
|
return "a1:b2:c3:d4:e5"
|
||||||
|
@ -267,6 +267,82 @@ class TestBridge(base.TestCase):
|
|||||||
self.assertEqual("br-foo", interface2.bridge_name)
|
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):
|
class TestBond(base.TestCase):
|
||||||
|
|
||||||
def test_from_json_dhcp(self):
|
def test_from_json_dhcp(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user