aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--etc/os-net-config/samples/team.json13
-rw-r--r--etc/os-net-config/samples/team.yaml18
-rw-r--r--os_net_config/__init__.py11
-rw-r--r--os_net_config/impl_ifcfg.py61
-rw-r--r--os_net_config/objects.py59
-rw-r--r--os_net_config/tests/test_impl_ifcfg.py40
-rw-r--r--os_net_config/tests/test_objects.py22
7 files changed, 223 insertions, 1 deletions
diff --git a/etc/os-net-config/samples/team.json b/etc/os-net-config/samples/team.json
new file mode 100644
index 0000000..4524b3c
--- /dev/null
+++ b/etc/os-net-config/samples/team.json
@@ -0,0 +1,13 @@
+{ "network_config": [
+ {
+ "type": "team",
+ "name": "team1",
+ "use_dhcp": true,
+ "bonding_options": "{\"runner\": {\"name\": \"activebackup\"}}",
+ "members": [
+ { "type": "interface", "name": "em1", "primary": true },
+ { "type": "interface", "name": "em2" }
+ ]
+ }
+ ]
+}
diff --git a/etc/os-net-config/samples/team.yaml b/etc/os-net-config/samples/team.yaml
new file mode 100644
index 0000000..3770de0
--- /dev/null
+++ b/etc/os-net-config/samples/team.yaml
@@ -0,0 +1,18 @@
+# Config for bonding with teamd. Bonding options are provided as a JSON
+# string. The following runners are available in teamd: broadcast,
+# roundrobin, activebackup, loadbalance, and lacp.
+# Please see the teamd.conf(5) man page for more information.
+network_config:
+ -
+ type: team
+ name: team1
+ use_dhcp: true
+ bonding_options: '{"runner": {"name": "activebackup"}}'
+ members:
+ -
+ type: interface
+ name: em1
+ primary: true
+ -
+ type: interface
+ name: em2
diff --git a/os_net_config/__init__.py b/os_net_config/__init__.py
index fc1a38a..18c96a2 100644
--- a/os_net_config/__init__.py
+++ b/os_net_config/__init__.py
@@ -71,6 +71,10 @@ class NetConfig(object):
self.add_linux_bond(obj)
for member in obj.members:
self.add_object(member)
+ elif isinstance(obj, objects.LinuxTeam):
+ self.add_linux_team(obj)
+ for member in obj.members:
+ self.add_object(member)
elif isinstance(obj, objects.OvsTunnel):
self.add_ovs_tunnel(obj)
elif isinstance(obj, objects.OvsPatchPort):
@@ -127,6 +131,13 @@ class NetConfig(object):
"""
raise NotImplemented("add_linux_bond is not implemented.")
+ def add_linux_team(self, team):
+ """Add a LinuxTeam object to the net config object.
+
+ :param team: The LinuxTeam object to add.
+ """
+ raise NotImplemented("add_linux_team is not implemented.")
+
def add_ovs_tunnel(self, tunnel):
"""Add a OvsTunnel object to the net config object.
diff --git a/os_net_config/impl_ifcfg.py b/os_net_config/impl_ifcfg.py
index 22aef22..49ad317 100644
--- a/os_net_config/impl_ifcfg.py
+++ b/os_net_config/impl_ifcfg.py
@@ -65,6 +65,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
self.linuxbridge_data = {}
self.linuxbond_data = {}
self.ib_interface_data = {}
+ self.linuxteam_data = {}
self.member_names = {}
self.renamed_interfaces = {}
self.bond_primary_ifaces = {}
@@ -109,11 +110,16 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if base_opt.linux_bond_name:
data += "MASTER=%s\n" % base_opt.linux_bond_name
data += "SLAVE=yes\n"
+ if base_opt.linux_team_name:
+ data += "TEAM_MASTER=%s\n" % base_opt.linux_team_name
+ if base_opt.primary:
+ data += "TEAM_PORT_CONFIG='{\"prio\": 100}'\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 not isinstance(base_opt, objects.LinuxTeam):
+ data += "DEVICETYPE=ovs\n"
if base_opt.bridge_name:
if isinstance(base_opt, objects.Vlan):
data += "TYPE=OVSIntPort\n"
@@ -180,6 +186,19 @@ class IfcfgNetConfig(os_net_config.NetConfig):
self.member_names[base_opt.name] = members
if base_opt.bonding_options:
data += "BONDING_OPTS=\"%s\"\n" % base_opt.bonding_options
+ elif isinstance(base_opt, objects.LinuxTeam):
+ 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
+ data += "DEVICETYPE=Team\n"
+ if base_opt.bonding_options:
+ data += "TEAM_CONFIG='%s'\n" % base_opt.bonding_options
elif isinstance(base_opt, objects.OvsTunnel):
ovs_extra.extend(base_opt.ovs_extra)
data += "DEVICETYPE=ovs\n"
@@ -374,6 +393,18 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if bond.routes:
self._add_routes(bond.name, bond.routes)
+ def add_linux_team(self, team):
+ """Add a LinuxTeam object to the net config object.
+
+ :param team: The LinuxTeam object to add.
+ """
+ logger.info('adding linux team: %s' % team.name)
+ data = self._add_common(team)
+ logger.debug('team data: %s' % data)
+ self.linuxteam_data[team.name] = data
+ if team.routes:
+ self._add_routes(team.name, team.routes)
+
def add_ovs_tunnel(self, tunnel):
"""Add a OvsTunnel object to the net config object.
@@ -451,6 +482,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
restart_vlans = []
restart_bridges = []
restart_linux_bonds = []
+ restart_linux_teams = []
update_files = {}
all_file_names = []
ivs_uplinks = [] # ivs physical uplinks
@@ -561,6 +593,27 @@ class IfcfgNetConfig(os_net_config.NetConfig):
else:
logger.info('No changes required for bridge: %s' % bridge_name)
+ for team_name, team_data in self.linuxteam_data.iteritems():
+ route_data = self.route_data.get(team_name, '')
+ route6_data = self.route6_data.get(team_name, '')
+ team_path = self.root_dir + bridge_config_path(team_name)
+ team_route_path = self.root_dir + route_config_path(team_name)
+ team_route6_path = self.root_dir + route6_config_path(team_name)
+ all_file_names.append(team_path)
+ all_file_names.append(team_route_path)
+ all_file_names.append(team_route6_path)
+ if (utils.diff(team_path, team_data) or
+ utils.diff(team_route_path, route_data) or
+ utils.diff(team_route6_path, route6_data)):
+ restart_linux_teams.append(team_name)
+ restart_interfaces.extend(self.child_members(team_name))
+ update_files[team_path] = team_data
+ update_files[team_route_path] = route_data
+ update_files[team_route6_path] = route6_data
+ else:
+ logger.info('No changes required for linux team: %s' %
+ team_name)
+
for bond_name, bond_data in self.linuxbond_data.iteritems():
route_data = self.route_data.get(bond_name, '')
route6_data = self.route6_data.get(bond_name, '')
@@ -628,6 +681,9 @@ class IfcfgNetConfig(os_net_config.NetConfig):
for linux_bond in restart_linux_bonds:
self.ifdown(linux_bond)
+ for linux_team in restart_linux_teams:
+ self.ifdown(linux_team)
+
for bridge in restart_bridges:
self.ifdown(bridge, iftype='bridge')
@@ -646,6 +702,9 @@ class IfcfgNetConfig(os_net_config.NetConfig):
for linux_bond in restart_linux_bonds:
self.ifup(linux_bond)
+ for linux_team in restart_linux_teams:
+ self.ifup(linux_team)
+
for bridge in restart_bridges:
self.ifup(bridge, iftype='bridge')
diff --git a/os_net_config/objects.py b/os_net_config/objects.py
index 289a0da..5137263 100644
--- a/os_net_config/objects.py
+++ b/os_net_config/objects.py
@@ -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 == "team":
+ return LinuxTeam.from_json(json)
elif obj_type == "linux_bridge":
return LinuxBridge.from_json(json)
elif obj_type == "ivs_bridge":
@@ -180,6 +182,7 @@ class _BaseOpts(object):
self.linux_bridge_name = None # internal
self.ivs_bridge_name = None # internal
self.linux_bond_name = None # internal
+ self.linux_team_name = None # internal
self.ovs_port = False # internal
self.primary_interface_name = None # internal
@@ -507,6 +510,62 @@ class IvsBridge(_BaseOpts):
dns_servers=dns_servers)
+class LinuxTeam(_BaseOpts):
+ """Base class for Linux bonds using teamd."""
+
+ def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
+ routes=None, mtu=None, 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(LinuxTeam, 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:
+ member.linux_team_name = name
+ if member.primary:
+ if self.primary_interface_name:
+ msg = 'Only one primary interface allowed per team.'
+ 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', 'LinuxTeam')
+ (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 LinuxTeam(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 LinuxBond(_BaseOpts):
"""Base class for Linux bonds."""
diff --git a/os_net_config/tests/test_impl_ifcfg.py b/os_net_config/tests/test_impl_ifcfg.py
index f30eb89..b8a8402 100644
--- a/os_net_config/tests/test_impl_ifcfg.py
+++ b/os_net_config/tests/test_impl_ifcfg.py
@@ -244,11 +244,24 @@ BOOTPROTO=dhcp
"""
+_LINUX_TEAM_DHCP = """# This file is autogenerated by os-net-config
+DEVICE=team0
+ONBOOT=yes
+HOTPLUG=no
+NM_CONTROLLED=no
+BOOTPROTO=dhcp
+DEVICETYPE=Team
+"""
+
+
_LINUX_BOND_INTERFACE = _BASE_IFCFG + """MASTER=bond0
SLAVE=yes
BOOTPROTO=none
"""
+_LINUX_TEAM_INTERFACE = _BASE_IFCFG + """TEAM_MASTER=team0
+BOOTPROTO=none
+"""
_IVS_UPLINK = """# This file is autogenerated by os-net-config
DEVICE=em1
@@ -291,6 +304,17 @@ OVS_BRIDGE=br-ex
OVS_PATCH_PEER=br-ex-patch
"""
+_LINUX_TEAM_PRIMARY_IFACE = """# This file is autogenerated by os-net-config
+DEVICE=em1
+ONBOOT=yes
+HOTPLUG=no
+NM_CONTROLLED=no
+PEERDNS=no
+TEAM_MASTER=team1
+TEAM_PORT_CONFIG='{"prio": 100}'
+BOOTPROTO=none
+"""
+
class TestIfcfgNetConfig(base.TestCase):
@@ -311,6 +335,9 @@ class TestIfcfgNetConfig(base.TestCase):
def get_linux_bond_config(self, name='bond0'):
return self.provider.linuxbond_data[name]
+ def get_linux_team_config(self, name='team0'):
+ return self.provider.linuxteam_data[name]
+
def get_route_config(self, name='em1'):
return self.provider.route_data.get(name, '')
@@ -604,6 +631,19 @@ BOOTPROTO=none
self.assertEqual(_LINUX_BOND_INTERFACE,
self.get_interface_config('em1'))
+ def test_linux_team(self):
+ interface1 = objects.Interface('em1')
+ interface2 = objects.Interface('em2')
+ team = objects.LinuxTeam('team0', use_dhcp=True,
+ members=[interface1, interface2])
+ self.provider.add_linux_team(team)
+ self.provider.add_interface(interface1)
+ self.provider.add_interface(interface2)
+ self.assertEqual(_LINUX_TEAM_DHCP,
+ self.get_linux_team_config('team0'))
+ self.assertEqual(_LINUX_TEAM_INTERFACE,
+ self.get_interface_config('em1'))
+
def test_interface_defroute(self):
interface1 = objects.Interface('em1')
interface2 = objects.Interface('em2', defroute=False)
diff --git a/os_net_config/tests/test_objects.py b/os_net_config/tests/test_objects.py
index 51119ec..17b6927 100644
--- a/os_net_config/tests/test_objects.py
+++ b/os_net_config/tests/test_objects.py
@@ -456,6 +456,28 @@ class TestBond(base.TestCase):
self.assertEqual("em2", interface2.name)
+class TestLinuxTeam(base.TestCase):
+
+ def test_from_json_dhcp(self):
+ data = """{
+"type": "team",
+"name": "team1",
+"use_dhcp": true,
+"members": [
+ { "type": "interface", "name": "em1", "primary": true },
+ { "type": "interface", "name": "em2" }
+]
+}
+"""
+ team = objects.object_from_json(json.loads(data))
+ self.assertEqual("team1", team.name)
+ self.assertTrue(team.use_dhcp)
+ interface1 = team.members[0]
+ self.assertEqual("em1", interface1.name)
+ interface2 = team.members[1]
+ self.assertEqual("em2", interface2.name)
+
+
class TestLinuxBond(base.TestCase):
def test_from_json_dhcp(self):