diff options
-rw-r--r-- | os_net_config/impl_ifcfg.py | 44 | ||||
-rw-r--r-- | os_net_config/objects.py | 49 | ||||
-rw-r--r-- | os_net_config/tests/test_impl_eni.py | 50 | ||||
-rw-r--r-- | os_net_config/tests/test_impl_ifcfg.py | 66 | ||||
-rw-r--r-- | os_net_config/tests/test_objects.py | 56 |
5 files changed, 245 insertions, 20 deletions
diff --git a/os_net_config/impl_ifcfg.py b/os_net_config/impl_ifcfg.py index e8dbd46..41d5f8c 100644 --- a/os_net_config/impl_ifcfg.py +++ b/os_net_config/impl_ifcfg.py @@ -16,6 +16,7 @@ import glob import logging +import os import re import os_net_config @@ -25,6 +26,9 @@ from os_net_config import utils logger = logging.getLogger(__name__) +# Import the raw NetConfig object so we can call its methods +netconfig = os_net_config.NetConfig() + def ifcfg_config_path(name): return "/etc/sysconfig/network-scripts/ifcfg-%s" % name @@ -55,6 +59,39 @@ def cleanup_pattern(): return "/etc/sysconfig/network-scripts/ifcfg-*" +def dhclient_path(): + if os.path.exists("/usr/sbin/dhclient"): + return "/usr/sbin/dhclient" + elif os.path.exists("/sbin/dhclient"): + return "/sbin/dhclient" + else: + raise RuntimeError("Could not find dhclient") + + +def stop_dhclient_process(interface): + """Stop a DHCP process when no longer needed. + + This method exists so that it may be stubbed out for unit tests. + :param interface: The interface on which to stop dhclient. + """ + pid_file = '/var/run/dhclient-%s.pid' % (interface) + try: + dhclient = dhclient_path() + except RuntimeError as err: + logger.info('Exception when stopping dhclient: %s' % err) + return + + if os.path.exists(pid_file): + msg = 'Stopping %s on interface %s' % (dhclient, interface) + netconfig.execute(msg, dhclient, '-r', '-pf', + pid_file, interface) + try: + os.unlink(pid_file) + except OSError as err: + logger.error('Could not remove dhclient pid file \'%s\': %s' % + (pid_file, err)) + + class IfcfgNetConfig(os_net_config.NetConfig): """Configure network interfaces using the ifcfg format.""" @@ -639,6 +676,7 @@ class IfcfgNetConfig(os_net_config.NetConfig): ivs_interfaces = [] # ivs internal ports nfvswitch_interfaces = [] # nfvswitch physical interfaces nfvswitch_internal_ifaces = [] # nfvswitch internal/management ports + stop_dhclient_interfaces = [] for interface_name, iface_data in self.interface_data.iteritems(): route_data = self.route_data.get(interface_name, '') @@ -662,6 +700,8 @@ class IfcfgNetConfig(os_net_config.NetConfig): update_files[interface_path] = iface_data update_files[route_path] = route_data update_files[route6_path] = route6_data + if "BOOTPROTO=dhcp" not in iface_data: + stop_dhclient_interfaces.append(interface_name) else: logger.info('No changes required for interface: %s' % interface_name) @@ -890,6 +930,10 @@ class IfcfgNetConfig(os_net_config.NetConfig): for bridge in restart_bridges: self.ifup(bridge, iftype='bridge') + # If dhclient is running and dhcp not set, stop dhclient + for interface in stop_dhclient_interfaces: + stop_dhclient_process(interface) + for interface in restart_interfaces: self.ifup(interface) diff --git a/os_net_config/objects.py b/os_net_config/objects.py index 8d7ee4c..8d52e73 100644 --- a/os_net_config/objects.py +++ b/os_net_config/objects.py @@ -25,6 +25,8 @@ logger = logging.getLogger(__name__) _MAPPED_NICS = None +DEFAULT_OVS_BRIDGE_FAIL_MODE = 'standalone' + class InvalidConfigException(ValueError): pass @@ -131,6 +133,12 @@ def _mapped_nics(nic_mapping=None): return _MAPPED_NICS +def format_ovs_extra(obj, templates): + """Map OVS object properties into a string to be used for ovs_extra.""" + + return [t.format(name=obj.name) for t in templates or []] + + class Route(object): """Base class for network routes.""" @@ -391,11 +399,11 @@ class OvsBridge(_BaseOpts): def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None, routes=None, mtu=None, members=None, ovs_options=None, ovs_extra=None, nic_mapping=None, persist_mapping=False, - defroute=True, dhclient_args=None, dns_servers=None): + defroute=True, dhclient_args=None, dns_servers=None, + fail_mode=None): addresses = addresses or [] routes = routes or [] members = members or [] - ovs_extra = ovs_extra or [] dns_servers = dns_servers or [] super(OvsBridge, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, False, nic_mapping, @@ -403,7 +411,10 @@ class OvsBridge(_BaseOpts): dhclient_args, dns_servers) self.members = members self.ovs_options = ovs_options - self.ovs_extra = ovs_extra + ovs_extra = ovs_extra or [] + if fail_mode: + ovs_extra.append('set bridge {name} fail_mode=%s' % fail_mode) + self.ovs_extra = format_ovs_extra(self, ovs_extra) for member in self.members: member.bridge_name = name if not isinstance(member, OvsTunnel): @@ -425,7 +436,8 @@ class OvsBridge(_BaseOpts): dhclient_args, dns_servers) = _BaseOpts.base_opts_from_json( json, include_primary=False) ovs_options = json.get('ovs_options') - ovs_extra = json.get('ovs_extra', []) + ovs_extra = json.get('ovs_extra') + fail_mode = json.get('ovs_fail_mode', DEFAULT_OVS_BRIDGE_FAIL_MODE) members = [] # members @@ -443,7 +455,8 @@ class OvsBridge(_BaseOpts): members=members, ovs_options=ovs_options, ovs_extra=ovs_extra, nic_mapping=nic_mapping, persist_mapping=persist_mapping, defroute=defroute, - dhclient_args=dhclient_args, dns_servers=dns_servers) + dhclient_args=dhclient_args, dns_servers=dns_servers, + fail_mode=fail_mode) class OvsUserBridge(_BaseOpts): @@ -452,7 +465,8 @@ class OvsUserBridge(_BaseOpts): def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None, routes=None, mtu=None, members=None, ovs_options=None, ovs_extra=None, nic_mapping=None, persist_mapping=False, - defroute=True, dhclient_args=None, dns_servers=None): + defroute=True, dhclient_args=None, dns_servers=None, + fail_mode=None): super(OvsUserBridge, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, False, nic_mapping, persist_mapping, @@ -460,7 +474,10 @@ class OvsUserBridge(_BaseOpts): dns_servers) self.members = members or [] self.ovs_options = ovs_options - self.ovs_extra = ovs_extra or [] + ovs_extra = ovs_extra or [] + if fail_mode: + ovs_extra.append('set bridge {name} fail_mode=%s' % fail_mode) + self.ovs_extra = format_ovs_extra(self, ovs_extra) for member in self.members: member.bridge_name = name if not isinstance(member, OvsTunnel) and \ @@ -484,7 +501,8 @@ class OvsUserBridge(_BaseOpts): dhclient_args, dns_servers) = _BaseOpts.base_opts_from_json( json, include_primary=False) ovs_options = json.get('ovs_options') - ovs_extra = json.get('ovs_extra', []) + ovs_extra = json.get('ovs_extra') + fail_mode = json.get('ovs_fail_mode', DEFAULT_OVS_BRIDGE_FAIL_MODE) members = [] # members @@ -503,7 +521,7 @@ class OvsUserBridge(_BaseOpts): ovs_extra=ovs_extra, nic_mapping=nic_mapping, persist_mapping=persist_mapping, defroute=defroute, dhclient_args=dhclient_args, - dns_servers=dns_servers) + dns_servers=dns_servers, fail_mode=fail_mode) class LinuxBridge(_BaseOpts): @@ -808,7 +826,6 @@ class OvsBond(_BaseOpts): addresses = addresses or [] routes = routes or [] members = members or [] - ovs_extra = ovs_extra or [] dns_servers = dns_servers or [] super(OvsBond, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, primary, nic_mapping, @@ -816,7 +833,7 @@ class OvsBond(_BaseOpts): dns_servers) self.members = members self.ovs_options = ovs_options - self.ovs_extra = ovs_extra + self.ovs_extra = format_ovs_extra(self, ovs_extra) for member in self.members: if member.primary: if self.primary_interface_name: @@ -870,7 +887,6 @@ class OvsTunnel(_BaseOpts): ovs_extra=None): addresses = addresses or [] routes = routes or [] - ovs_extra = ovs_extra or [] dns_servers = dns_servers or [] super(OvsTunnel, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, primary, nic_mapping, @@ -878,7 +894,7 @@ class OvsTunnel(_BaseOpts): dhclient_args, dns_servers) self.tunnel_type = tunnel_type self.ovs_options = ovs_options or [] - self.ovs_extra = ovs_extra or [] + self.ovs_extra = format_ovs_extra(self, ovs_extra) @staticmethod def from_json(json): @@ -902,7 +918,6 @@ class OvsPatchPort(_BaseOpts): ovs_options=None, ovs_extra=None): addresses = addresses or [] routes = routes or [] - ovs_extra = ovs_extra or [] dns_servers = dns_servers or [] super(OvsPatchPort, self).__init__(name, use_dhcp, use_dhcpv6, addresses, routes, mtu, primary, @@ -912,7 +927,7 @@ class OvsPatchPort(_BaseOpts): self.bridge_name = bridge_name self.peer = peer self.ovs_options = ovs_options or [] - self.ovs_extra = ovs_extra or [] + self.ovs_extra = format_ovs_extra(self, ovs_extra) @staticmethod def from_json(json): @@ -967,7 +982,7 @@ class OvsDpdkPort(_BaseOpts): dns_servers) self.members = members or [] self.ovs_options = ovs_options or [] - self.ovs_extra = ovs_extra or [] + self.ovs_extra = format_ovs_extra(self, ovs_extra) self.driver = driver @staticmethod @@ -1024,7 +1039,7 @@ class OvsDpdkBond(_BaseOpts): defroute, dhclient_args, dns_servers) self.members = members or [] self.ovs_options = ovs_options - self.ovs_extra = ovs_extra or [] + self.ovs_extra = format_ovs_extra(self, ovs_extra) for member in self.members: if member.primary: diff --git a/os_net_config/tests/test_impl_eni.py b/os_net_config/tests/test_impl_eni.py index 5d3bb8c..e445ed4 100644 --- a/os_net_config/tests/test_impl_eni.py +++ b/os_net_config/tests/test_impl_eni.py @@ -66,13 +66,18 @@ iface br0 inet dhcp pre-up ip addr flush dev eth0 """ +_OVS_BRIDGE_DHCP_STANDALONE = _OVS_BRIDGE_DHCP + \ + " ovs_extra set bridge br0 fail_mode=standalone\n" + +_OVS_BRIDGE_DHCP_SECURE = _OVS_BRIDGE_DHCP + \ + " ovs_extra set bridge br0 fail_mode=secure\n" + _OVS_BRIDGE_DHCP_PRIMARY_INTERFACE = _OVS_BRIDGE_DHCP + \ " ovs_extra set bridge br0 other-config:hwaddr=a1:b2:c3:d4:e5\n" - _OVS_BRIDGE_DHCP_OVS_EXTRA = _OVS_BRIDGE_DHCP + \ " ovs_extra set bridge br0 other-config:hwaddr=a1:b2:c3:d4:e5" + \ - " -- br-set-external-id br-ctlplane bridge-id br-ctlplane\n" + " -- br-set-external-id br0 bridge-id br0\n" _VLAN_NO_IP = """auto vlan5 @@ -188,6 +193,28 @@ 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_standalone_fail_mode(self): + interface = self._default_interface() + bridge = objects.OvsBridge('br0', use_dhcp=True, + members=[interface], + fail_mode='standalone') + self.provider.add_bridge(bridge) + self.provider.add_interface(interface) + self.assertEqual(_OVS_PORT_IFACE, self.get_interface_config()) + self.assertEqual(_OVS_BRIDGE_DHCP_STANDALONE, + self.provider.bridges['br0']) + + def test_network_ovs_bridge_with_secure_fail_mode(self): + interface = self._default_interface() + bridge = objects.OvsBridge('br0', use_dhcp=True, + members=[interface], + fail_mode='secure') + self.provider.add_bridge(bridge) + self.provider.add_interface(interface) + self.assertEqual(_OVS_PORT_IFACE, self.get_interface_config()) + self.assertEqual(_OVS_BRIDGE_DHCP_SECURE, + self.provider.bridges['br0']) + def test_network_ovs_bridge_with_dhcp_and_primary_interface(self): def test_interface_mac(name): @@ -210,7 +237,24 @@ class TestENINetConfig(base.TestCase): self.stubs.Set(utils, 'interface_mac', test_interface_mac) interface = objects.Interface(self.if_name, primary=True) - ovs_extra = "br-set-external-id br-ctlplane bridge-id br-ctlplane" + ovs_extra = "br-set-external-id br0 bridge-id br0" + bridge = objects.OvsBridge('br0', use_dhcp=True, + members=[interface], + ovs_extra=[ovs_extra]) + self.provider.add_bridge(bridge) + self.provider.add_interface(interface) + self.assertEqual(_OVS_PORT_IFACE, self.get_interface_config()) + self.assertEqual(_OVS_BRIDGE_DHCP_OVS_EXTRA, + self.provider.bridges['br0']) + + def test_network_ovs_bridge_with_dhcp_and_primary_with_ovs_format(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) + ovs_extra = "br-set-external-id {name} bridge-id {name}" bridge = objects.OvsBridge('br0', use_dhcp=True, members=[interface], ovs_extra=[ovs_extra]) diff --git a/os_net_config/tests/test_impl_ifcfg.py b/os_net_config/tests/test_impl_ifcfg.py index 2e79aa1..3a5a7db 100644 --- a/os_net_config/tests/test_impl_ifcfg.py +++ b/os_net_config/tests/test_impl_ifcfg.py @@ -143,6 +143,13 @@ OVSBOOTPROTO=dhcp OVSDHCPINTERFACES="em1" """ +_OVS_BRIDGE_DHCP_STANDALONE = _OVS_BRIDGE_DHCP + \ + "OVS_EXTRA=\"set bridge br-ctlplane fail_mode=standalone\"\n" + +_OVS_BRIDGE_DHCP_SECURE = _OVS_BRIDGE_DHCP + \ + "OVS_EXTRA=\"set bridge br-ctlplane fail_mode=secure\"\n" + + _LINUX_BRIDGE_DHCP = """# This file is autogenerated by os-net-config DEVICE=br-ctlplane ONBOOT=yes @@ -499,6 +506,28 @@ class TestIfcfgNetConfig(base.TestCase): self.assertEqual(_OVS_BRIDGE_DHCP, self.provider.bridge_data['br-ctlplane']) + def test_network_ovs_bridge_with_standalone_fail_mode(self): + interface = objects.Interface('em1') + bridge = objects.OvsBridge('br-ctlplane', use_dhcp=True, + members=[interface], + fail_mode='standalone') + self.provider.add_interface(interface) + self.provider.add_bridge(bridge) + self.assertEqual(_OVS_INTERFACE, self.get_interface_config()) + self.assertEqual(_OVS_BRIDGE_DHCP_STANDALONE, + self.provider.bridge_data['br-ctlplane']) + + def test_network_ovs_bridge_with_secure_fail_mode(self): + interface = objects.Interface('em1') + bridge = objects.OvsBridge('br-ctlplane', use_dhcp=True, + members=[interface], + fail_mode='secure') + self.provider.add_interface(interface) + self.provider.add_bridge(bridge) + self.assertEqual(_OVS_INTERFACE, self.get_interface_config()) + self.assertEqual(_OVS_BRIDGE_DHCP_SECURE, + 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, @@ -577,6 +606,22 @@ class TestIfcfgNetConfig(base.TestCase): self.assertEqual(_OVS_BRIDGE_DHCP_OVS_EXTRA, self.provider.bridge_data['br-ctlplane']) + def test_network_ovs_bridge_with_dhcp_primary_interface_with_format(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) + ovs_extra = "br-set-external-id {name} bridge-id {name}" + bridge = objects.OvsBridge('br-ctlplane', use_dhcp=True, + members=[interface], + ovs_extra=[ovs_extra]) + self.provider.add_interface(interface) + self.provider.add_bridge(bridge) + self.assertEqual(_OVS_INTERFACE, self.get_interface_config()) + self.assertEqual(_OVS_BRIDGE_DHCP_OVS_EXTRA, + self.provider.bridge_data['br-ctlplane']) + def test_network_ivs_with_uplink_and_interface(self): interface = objects.Interface('em1') v4_addr = objects.Address('172.16.2.7/24') @@ -870,6 +915,7 @@ class TestIfcfgNetConfigApply(base.TestCase): self.temp_cleanup_file = tempfile.NamedTemporaryFile(delete=False) self.ifup_interface_names = [] self.ovs_appctl_cmds = [] + self.stop_dhclient_interfaces = [] def test_ifcfg_path(name): return self.temp_ifcfg_file.name @@ -891,6 +937,11 @@ class TestIfcfgNetConfigApply(base.TestCase): return self.temp_cleanup_file.name self.stubs.Set(impl_ifcfg, 'cleanup_pattern', test_cleanup_pattern) + def test_stop_dhclient_process(interface): + self.stop_dhclient_interfaces.append(interface) + self.stubs.Set(impl_ifcfg, 'stop_dhclient_process', + test_stop_dhclient_process) + def test_execute(*args, **kwargs): if args[0] == '/sbin/ifup': self.ifup_interface_names.append(args[1]) @@ -943,6 +994,21 @@ class TestIfcfgNetConfigApply(base.TestCase): route_data = utils.get_file_data(self.temp_route_file.name) self.assertEqual("", route_data) + def test_dhclient_stop_on_iface_activate(self): + self.stop_dhclient_interfaces = [] + v4_addr = objects.Address('192.168.1.2/24') + interface = objects.Interface('em1', addresses=[v4_addr]) + interface2 = objects.Interface('em2', use_dhcp=True) + interface3 = objects.Interface('em3', use_dhcp=False) + self.provider.add_interface(interface) + self.provider.add_interface(interface2) + self.provider.add_interface(interface3) + self.provider.apply() + # stop dhclient on em1 due to static IP and em3 due to no IP + self.assertIn('em1', self.stop_dhclient_interfaces) + self.assertIn('em3', self.stop_dhclient_interfaces) + self.assertNotIn('em2', self.stop_dhclient_interfaces) + def test_apply_noactivate(self): interface = objects.Interface('em1') bridge = objects.OvsBridge('br-ctlplane', use_dhcp=True, diff --git a/os_net_config/tests/test_objects.py b/os_net_config/tests/test_objects.py index 3911ef3..ca2dd47 100644 --- a/os_net_config/tests/test_objects.py +++ b/os_net_config/tests/test_objects.py @@ -646,6 +646,44 @@ class TestOvsTunnel(base.TestCase): ["ovs extra"], tun0.ovs_extra) + def test_ovs_extra_formatting(self): + data = """{ +"type": "ovs_bridge", +"name": "br-foo", +"ovs_extra": [ + "set bridge {name} something" +], +"members": [{ + "type": "ovs_tunnel", + "name": "tun0", + "tunnel_type": "gre", + "ovs_options": [ + "remote_ip=192.168.1.1" + ], + "ovs_extra": [ + "ovs extra", + "ovs {name} extra" + ] +}] +} +""" + bridge = objects.object_from_json(json.loads(data)) + self.assertEqual("br-foo", bridge.name) + self.assertEqual(["set bridge br-foo something", + "set bridge br-foo fail_mode=standalone"], + bridge.ovs_extra) + tun0 = bridge.members[0] + self.assertEqual("tun0", tun0.name) + self.assertFalse(tun0.ovs_port) + self.assertEqual("br-foo", tun0.bridge_name) + self.assertEqual("gre", tun0.tunnel_type) + self.assertEqual( + ["options:remote_ip=192.168.1.1"], + tun0.ovs_options) + self.assertEqual( + ["ovs extra", "ovs tun0 extra"], + tun0.ovs_extra) + class TestOvsPatchPort(base.TestCase): @@ -662,6 +700,24 @@ class TestOvsPatchPort(base.TestCase): self.assertEqual("br-ex", patch_port.bridge_name) self.assertEqual("br-ex-patch", patch_port.peer) + def test_from_json_with_extra(self): + data = """{ +"type": "ovs_patch_port", +"name": "br-pub-patch", +"bridge_name": "br-ex", +"peer": "br-ex-patch", +"ovs_extra": [ + "ovs {name} extra" +] +} +""" + patch_port = objects.object_from_json(json.loads(data)) + self.assertEqual(["ovs br-pub-patch extra"], + patch_port.ovs_extra) + self.assertEqual("br-pub-patch", patch_port.name) + self.assertEqual("br-ex", patch_port.bridge_name) + self.assertEqual("br-ex-patch", patch_port.peer) + class TestIbInterface(base.TestCase): |