diff options
author | Dan Prince <dprince@redhat.com> | 2014-08-12 11:06:46 -0400 |
---|---|---|
committer | Dan Prince <dprince@redhat.com> | 2014-08-12 11:51:31 -0400 |
commit | f0ae3282450c2bb1bb35f22414858768f08d2653 (patch) | |
tree | edcf2d322f4b222bd0b11034ce4e09689caf10ef /os_net_config | |
parent | f57b67880d5c70bbc287ab56395895bb88a1b011 (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.py | 5 | ||||
-rw-r--r-- | os_net_config/impl_ifcfg.py | 4 | ||||
-rw-r--r-- | os_net_config/objects.py | 50 | ||||
-rw-r--r-- | os_net_config/tests/test_impl_eni.py | 18 | ||||
-rw-r--r-- | os_net_config/tests/test_impl_ifcfg.py | 17 | ||||
-rw-r--r-- | os_net_config/tests/test_objects.py | 31 | ||||
-rw-r--r-- | os_net_config/utils.py | 9 |
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) |