Add support for Linux Bonding to os-net-config ifcfg

This change adds support for Linux Bonding to the impl_ifcfg
in os-net-config. This change adds support for configuring Linux
Bonds using the Bonding module rather than Open vSwitch. Most of
the options for Linux Bonds are the same as OVS, with the exception
of bonding_options instead of ovs_options.

Change-Id: If8c6de1554234277843de9fac58536dd5b0a941b
This commit is contained in:
Dan Sneddon 2015-08-28 00:22:27 -07:00
parent 2497f596be
commit 62bc734ad5
6 changed files with 204 additions and 0 deletions

View File

@ -0,0 +1,13 @@
network_config:
-
type: linux_bond
name: bond1
use_dhcp: true
bonding_options: "mode=active-backup"
members:
-
type: interface
name: em1
-
type: interface
name: em2

View File

@ -56,6 +56,10 @@ class NetConfig(object):
self.add_bond(obj)
for member in obj.members:
self.add_object(member)
elif isinstance(obj, objects.LinuxBond):
self.add_linux_bond(obj)
for member in obj.members:
self.add_object(member)
def add_interface(self, interface):
"""Add an Interface object to the net config object.
@ -85,6 +89,13 @@ class NetConfig(object):
"""
raise NotImplemented("add_bond is not implemented.")
def add_linux_bond(self, bond):
"""Add a LinuxBond object to the net config object.
:param bridge: The LinuxBond object to add.
"""
raise NotImplemented("add_linuxbond is not implemented.")
def apply(self, cleanup=False):
"""Apply the network configuration.

View File

@ -50,7 +50,9 @@ class IfcfgNetConfig(os_net_config.NetConfig):
self.interface_data = {}
self.route_data = {}
self.bridge_data = {}
self.linuxbond_data = {}
self.member_names = {}
self.bond_slaves = {}
self.renamed_interfaces = {}
self.bond_primary_ifaces = {}
logger.info('Ifcfg net config provider created.')
@ -122,7 +124,24 @@ 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.LinuxBond):
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
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
for member in members:
self.bond_slaves[member] = base_opt.name
if base_opt.bonding_options:
data += "BONDING_OPTS=\"%s\"\n" % base_opt.bonding_options
else:
if base_opt.name in self.bond_slaves:
data += "MASTER=%s\n" % self.bond_slaves[base_opt.name]
data += "SLAVE=yes\n"
if base_opt.use_dhcp:
data += "BOOTPROTO=dhcp\n"
elif not base_opt.addresses:
@ -233,6 +252,19 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if bond.routes:
self._add_routes(bond.name, bond.routes)
def add_linux_bond(self, bond):
"""Add a LinuxBond object to the net config object.
:param bond: The LinuxBond object to add.
"""
logger.info('adding linux bond: %s' % bond.name)
data = self._add_common(bond)
logger.debug('bond data: %s' % data)
self.interface_data[bond.name] = data
self.linuxbond_data[bond.name] = data
if bond.routes:
self._add_routes(bond.name, bond.routes)
def apply(self, cleanup=False, activate=True):
"""Apply the network configuration.
@ -283,6 +315,21 @@ class IfcfgNetConfig(os_net_config.NetConfig):
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)
bond_route_path = self.root_dir + route_config_path(bond_name)
all_file_names.append(bond_path)
all_file_names.append(bond_route_path)
if (utils.diff(bond_path, bond_data) or
utils.diff(bond_route_path, route_data)):
restart_interfaces.append(bond_name)
restart_interfaces.extend(self.child_members(bond_name))
update_files[bond_path] = bond_data
update_files[bond_route_path] = route_data
logger.info('No changes required for linux bond: %s' %
bond_name)
if cleanup:
for ifcfg_file in glob.iglob(cleanup_pattern()):
if ifcfg_file not in all_file_names:

View File

@ -40,6 +40,8 @@ def object_from_json(json):
return OvsBridge.from_json(json)
elif obj_type == "ovs_bond":
return OvsBond.from_json(json)
elif obj_type == "linux_bond":
return LinuxBond.from_json(json)
def _get_required_field(json, name, object_name):
@ -330,6 +332,61 @@ class OvsBridge(_BaseOpts):
dhclient_args=dhclient_args, dns_servers=dns_servers)
class LinuxBond(_BaseOpts):
"""Base class for Linux bonds."""
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
routes=None, mtu=1500, primary=False, members=None,
bonding_options=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(LinuxBond, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
routes, mtu, primary, nic_mapping,
persist_mapping, defroute,
dhclient_args, dns_servers)
self.members = members
self.bonding_options = bonding_options
for member in self.members:
if member.primary:
if self.primary_interface_name:
msg = 'Only one primary interface allowed per bond.'
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', 'LinuxBond')
(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)
bonding_options = json.get('bonding_options')
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 LinuxBond(name, use_dhcp=use_dhcp, use_dhcpv6=use_dhcpv6,
addresses=addresses, routes=routes, mtu=mtu,
members=members, bonding_options=bonding_options,
nic_mapping=nic_mapping,
persist_mapping=persist_mapping, defroute=defroute,
dhclient_args=dhclient_args, dns_servers=dns_servers)
class OvsBond(_BaseOpts):
"""Base class for OVS bonds."""

View File

@ -137,6 +137,15 @@ BOND_IFACES="em1 em2"
"""
_LINUX_BOND_DHCP = """# This file is autogenerated by os-net-config
DEVICE=bond0
ONBOOT=yes
HOTPLUG=no
NM_CONTROLLED=no
BOOTPROTO=dhcp
"""
class TestIfcfgNetConfig(base.TestCase):
def setUp(self):
@ -295,6 +304,15 @@ BOOTPROTO=none
self.assertEqual(_OVS_BOND_DHCP,
self.get_interface_config('bond0'))
def test_linux_bond(self):
interface1 = objects.Interface('em1')
interface2 = objects.Interface('em2')
bond = objects.LinuxBond('bond0', use_dhcp=True,
members=[interface1, interface2])
self.provider.add_linux_bond(bond)
self.assertEqual(_LINUX_BOND_DHCP,
self.get_interface_config('bond0'))
def test_interface_defroute(self):
interface1 = objects.Interface('em1')
interface2 = objects.Interface('em2', defroute=False)

View File

@ -325,6 +325,64 @@ class TestBond(base.TestCase):
self.assertEqual("em2", interface2.name)
class TestLinuxBond(base.TestCase):
def test_from_json_dhcp(self):
data = """{
"type": "linux_bond",
"name": "bond1",
"use_dhcp": true,
"members": [
{
"type": "interface",
"name": "em1"
},
{
"type": "interface",
"name": "em2"
}
]
}
"""
bridge = objects.object_from_json(json.loads(data))
self.assertEqual("bond1", bridge.name)
self.assertEqual(True, bridge.use_dhcp)
interface1 = bridge.members[0]
self.assertEqual("em1", interface1.name)
interface2 = bridge.members[1]
self.assertEqual("em2", interface2.name)
def test_from_json_dhcp_with_nic1_nic2(self):
def dummy_numbered_nics(nic_mapping=None):
return {"nic1": "em1", "nic2": "em2"}
self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics)
data = """{
"type": "ovs_bond",
"name": "bond1",
"use_dhcp": true,
"members": [
{
"type": "interface",
"name": "nic1"
},
{
"type": "interface",
"name": "nic2"
}
]
}
"""
bridge = objects.object_from_json(json.loads(data))
self.assertEqual("bond1", bridge.name)
self.assertEqual(True, bridge.use_dhcp)
interface1 = bridge.members[0]
self.assertEqual("em1", interface1.name)
interface2 = bridge.members[1]
self.assertEqual("em2", interface2.name)
class TestNumberedNicsMapping(base.TestCase):
# We want to test the function, not the dummy..