aboutsummaryrefslogtreecommitdiffstats
path: root/os_net_config
diff options
context:
space:
mode:
authorDan Prince <dprince@redhat.com>2014-08-12 11:06:46 -0400
committerDan Prince <dprince@redhat.com>2014-08-12 11:51:31 -0400
commitf0ae3282450c2bb1bb35f22414858768f08d2653 (patch)
treeedcf2d322f4b222bd0b11034ce4e09689caf10ef /os_net_config
parentf57b67880d5c70bbc287ab56395895bb88a1b011 (diff)
Set the MAC to the primary interface
Adds support for a new 'primary' interface option exposed via the object model and JSON parsers which can be used to force the MAC address on a bridge. Only one interface on a given bridge (or bond) may be set as the primary interface. Also, update the ifcfg and eni providers so that they use OVS_EXTRA (or ovs_extra) to pin the mac accordingly.
Diffstat (limited to 'os_net_config')
-rw-r--r--os_net_config/impl_eni.py5
-rw-r--r--os_net_config/impl_ifcfg.py4
-rw-r--r--os_net_config/objects.py50
-rw-r--r--os_net_config/tests/test_impl_eni.py18
-rw-r--r--os_net_config/tests/test_impl_ifcfg.py17
-rw-r--r--os_net_config/tests/test_objects.py31
-rw-r--r--os_net_config/utils.py9
7 files changed, 121 insertions, 13 deletions
diff --git a/os_net_config/impl_eni.py b/os_net_config/impl_eni.py
index c32fc82..b4a64e0 100644
--- a/os_net_config/impl_eni.py
+++ b/os_net_config/impl_eni.py
@@ -91,6 +91,11 @@ class ENINetConfig(os_net_config.NetConfig):
data += "\n"
for i in interface.members:
data += " pre-up ip addr flush dev %s\n" % i.name
+ if interface.primary_interface_name:
+ mac = utils.interface_mac(interface.primary_interface_name)
+ data += (" ovs_extra set bridge %s "
+ "other-config:hwaddr=%s\n"
+ % (interface.name, mac))
elif interface.ovs_port:
if isinstance(interface, objects.Vlan):
data += "auto vlan%i\n" % interface.vlan_id
diff --git a/os_net_config/impl_ifcfg.py b/os_net_config/impl_ifcfg.py
index 87cf3d5..0904943 100644
--- a/os_net_config/impl_ifcfg.py
+++ b/os_net_config/impl_ifcfg.py
@@ -74,6 +74,10 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if base_opt.members:
members = [member.name for member in base_opt.members]
data += ("OVSDHCPINTERFACES=\"%s\"\n" % " ".join(members))
+ if base_opt.primary_interface_name:
+ mac = utils.interface_mac(base_opt.primary_interface_name)
+ data += ("OVS_EXTRA=\"set bridge %s "
+ "other-config:hwaddr=%s\"\n" % (base_opt.name, mac))
if base_opt.ovs_options:
data += "OVS_OPTIONS=\"%s\"\n" % base_opt.ovs_options
elif isinstance(base_opt, objects.OvsBond):
diff --git a/os_net_config/objects.py b/os_net_config/objects.py
index baef767..14ecfc6 100644
--- a/os_net_config/objects.py
+++ b/os_net_config/objects.py
@@ -79,15 +79,17 @@ class _BaseOpts(object):
"""Base abstraction for logical port options."""
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=[],
- routes=[], mtu=1500):
+ routes=[], mtu=1500, primary=False):
self.name = name
self.mtu = mtu
self.use_dhcp = use_dhcp
self.use_dhcpv6 = use_dhcpv6
self.addresses = addresses
self.routes = routes
- self.bridge_name = None
- self.ovs_port = False
+ self.primary = primary
+ self.bridge_name = None # internal
+ self.ovs_port = False # internal
+ self.primary_interface_name = None # internal
def v4_addresses(self):
v4_addresses = []
@@ -106,11 +108,12 @@ class _BaseOpts(object):
return v6_addresses
@staticmethod
- def base_opts_from_json(json):
+ def base_opts_from_json(json, include_primary=True):
use_dhcp = strutils.bool_from_string(str(json.get('use_dhcp', False)))
use_dhcpv6 = strutils.bool_from_string(str(json.get('use_dhcpv6',
False)))
mtu = json.get('mtu', 1500)
+ primary = strutils.bool_from_string(str(json.get('primary', False)))
addresses = []
routes = []
@@ -134,16 +137,19 @@ class _BaseOpts(object):
msg = 'Routes must be a list.'
raise InvalidConfigException(msg)
- return (use_dhcp, use_dhcpv6, addresses, routes, mtu)
+ if include_primary:
+ return (use_dhcp, use_dhcpv6, addresses, routes, mtu, primary)
+ else:
+ return (use_dhcp, use_dhcpv6, addresses, routes, mtu)
class Interface(_BaseOpts):
"""Base class for network interfaces."""
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=[],
- routes=[], mtu=1500):
+ routes=[], mtu=1500, primary=False):
super(Interface, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
- routes, mtu)
+ routes, mtu, primary)
@staticmethod
def from_json(json):
@@ -160,10 +166,10 @@ class Vlan(_BaseOpts):
"""
def __init__(self, device, vlan_id, use_dhcp=False, use_dhcpv6=False,
- addresses=[], routes=[], mtu=1500):
+ addresses=[], routes=[], mtu=1500, primary=False):
name = 'vlan%i' % vlan_id
super(Vlan, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
- routes, mtu)
+ routes, mtu, primary)
self.vlan_id = int(vlan_id)
self.device = device
@@ -181,17 +187,25 @@ class OvsBridge(_BaseOpts):
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=[],
routes=[], mtu=1500, members=[], ovs_options=None):
super(OvsBridge, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
- routes, mtu)
+ routes, mtu, False)
self.members = members
self.ovs_options = ovs_options
for member in self.members:
member.bridge_name = name
member.ovs_port = True
+ 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', 'OvsBridge')
- opts = _BaseOpts.base_opts_from_json(json)
+ opts = _BaseOpts.base_opts_from_json(json, include_primary=False)
ovs_options = json.get('ovs_options')
members = []
@@ -212,11 +226,21 @@ class OvsBond(_BaseOpts):
"""Base class for OVS bonds."""
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=[],
- routes=[], mtu=1500, members=[], ovs_options=None):
+ routes=[], mtu=1500, primary=False, members=[],
+ ovs_options=None):
super(OvsBond, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
- routes, mtu)
+ routes, mtu, primary)
self.members = members
self.ovs_options = ovs_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):
diff --git a/os_net_config/tests/test_impl_eni.py b/os_net_config/tests/test_impl_eni.py
index c05b7c6..55502b3 100644
--- a/os_net_config/tests/test_impl_eni.py
+++ b/os_net_config/tests/test_impl_eni.py
@@ -53,6 +53,9 @@ iface br0 inet dhcp
pre-up ip addr flush dev eth0
"""
+_OVS_BRIDGE_DHCP_PRIMARY_INTERFACE = _OVS_BRIDGE_DHCP + \
+ " ovs_extra set bridge br0 other-config:hwaddr=a1:b2:c3:d4:e5\n"
+
_VLAN_NO_IP = """auto vlan5
iface vlan5 inet manual
vlan-raw-device eth0
@@ -146,6 +149,21 @@ class TestENINetConfig(base.TestCase):
self.assertEqual(_OVS_PORT_IFACE, self.get_interface_config())
self.assertEqual(_OVS_BRIDGE_DHCP, self.provider.bridges['br0'])
+ def test_network_ovs_bridge_with_dhcp_and_primary_interface(self):
+
+ def test_interface_mac(name):
+ return "a1:b2:c3:d4:e5"
+ self.stubs.Set(utils, 'interface_mac', test_interface_mac)
+
+ interface = objects.Interface(self.if_name, primary=True)
+ bridge = objects.OvsBridge('br0', use_dhcp=True,
+ members=[interface])
+ self.provider.addBridge(bridge)
+ self.provider.addInterface(interface)
+ self.assertEqual(_OVS_PORT_IFACE, self.get_interface_config())
+ self.assertEqual(_OVS_BRIDGE_DHCP_PRIMARY_INTERFACE,
+ self.provider.bridges['br0'])
+
def test_vlan(self):
vlan = objects.Vlan('eth0', 5)
self.provider.addVlan(vlan)
diff --git a/os_net_config/tests/test_impl_ifcfg.py b/os_net_config/tests/test_impl_ifcfg.py
index 629f1cf..2157cd0 100644
--- a/os_net_config/tests/test_impl_ifcfg.py
+++ b/os_net_config/tests/test_impl_ifcfg.py
@@ -65,6 +65,9 @@ OVSBOOTPROTO=dhcp
OVSDHCPINTERFACES="em1"
"""
+_OVS_BRIDGE_DHCP_PRIMARY_INTERFACE = _OVS_BRIDGE_DHCP + \
+ "OVS_EXTRA=\"set bridge br-ctlplane other-config:hwaddr=a1:b2:c3:d4:e5\"\n"
+
_BASE_VLAN = """DEVICE=vlan5
ONBOOT=yes
HOTPLUG=no
@@ -157,6 +160,20 @@ class TestIfcfgNetConfig(base.TestCase):
self.assertEqual(_OVS_BRIDGE_DHCP,
self.provider.bridges['br-ctlplane'])
+ def test_network_ovs_bridge_with_dhcp_primary_interface(self):
+ def test_interface_mac(name):
+ return "a1:b2:c3:d4:e5"
+ self.stubs.Set(utils, 'interface_mac', test_interface_mac)
+
+ interface = objects.Interface('em1', primary=True)
+ bridge = objects.OvsBridge('br-ctlplane', use_dhcp=True,
+ members=[interface])
+ self.provider.addInterface(interface)
+ self.provider.addBridge(bridge)
+ self.assertEqual(_OVS_INTERFACE, self.get_interface_config())
+ self.assertEqual(_OVS_BRIDGE_DHCP_PRIMARY_INTERFACE,
+ self.provider.bridges['br-ctlplane'])
+
def test_add_vlan(self):
vlan = objects.Vlan('em1', 5)
self.provider.addVlan(vlan)
diff --git a/os_net_config/tests/test_objects.py b/os_net_config/tests/test_objects.py
index ecf8d7d..830a809 100644
--- a/os_net_config/tests/test_objects.py
+++ b/os_net_config/tests/test_objects.py
@@ -152,6 +152,37 @@ class TestBridge(base.TestCase):
self.assertEqual(True, interface1.ovs_port)
self.assertEqual("br-foo", interface1.bridge_name)
+ def test_from_json_primary_interface(self):
+ data = """{
+"type": "ovs_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(True, interface1.ovs_port)
+ self.assertEqual(True, interface1.primary)
+ self.assertEqual("br-foo", interface1.bridge_name)
+ interface2 = bridge.members[1]
+ self.assertEqual("em2", interface2.name)
+ self.assertEqual(True, interface2.ovs_port)
+ self.assertEqual("br-foo", interface2.bridge_name)
+
class TestBond(base.TestCase):
diff --git a/os_net_config/utils.py b/os_net_config/utils.py
index a2df823..cd7ee65 100644
--- a/os_net_config/utils.py
+++ b/os_net_config/utils.py
@@ -34,6 +34,15 @@ def get_file_data(filename):
return ""
+def interface_mac(name):
+ try:
+ with open('/sys/class/net/%s/address' % name, "r") as f:
+ return f.read().rstrip()
+ except IOError:
+ logger.error("Unable to read file: %s" % name)
+ raise
+
+
def diff(filename, data):
file_data = get_file_data(filename)
logger.debug("Diff file data:\n%s" % file_data)