aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.rst9
-rw-r--r--etc/os-net-config/samples/bridge_fail_mode.json15
-rw-r--r--etc/os-net-config/samples/bridge_fail_mode.yaml10
-rw-r--r--etc/os-net-config/samples/bridge_ovs_extra.json18
-rw-r--r--etc/os-net-config/samples/bridge_ovs_extra.yaml12
-rw-r--r--etc/os-net-config/samples/ib_interface.json2
-rw-r--r--etc/os-net-config/samples/ib_interface.yaml2
-rw-r--r--etc/os-net-config/samples/interface.json14
-rw-r--r--etc/os-net-config/samples/interface.yaml12
-rw-r--r--etc/os-net-config/samples/ovs_patch_port.yaml2
-rw-r--r--os_net_config/__init__.py32
-rw-r--r--os_net_config/cli.py2
-rw-r--r--os_net_config/impl_eni.py22
-rw-r--r--os_net_config/impl_ifcfg.py112
-rw-r--r--os_net_config/objects.py81
-rw-r--r--os_net_config/tests/test_impl_eni.py85
-rw-r--r--os_net_config/tests/test_impl_ifcfg.py128
-rw-r--r--os_net_config/tests/test_objects.py129
-rw-r--r--os_net_config/tests/test_utils.py24
-rw-r--r--os_net_config/utils.py11
20 files changed, 618 insertions, 104 deletions
diff --git a/README.rst b/README.rst
index 54b6080..954b16e 100644
--- a/README.rst
+++ b/README.rst
@@ -1,3 +1,12 @@
+========================
+Team and repository tags
+========================
+
+.. image:: http://governance.openstack.org/badges/os-net-config.svg
+ :target: http://governance.openstack.org/reference/tags/index.html
+
+.. Change things from this point on
+
===============================
os-net-config
===============================
diff --git a/etc/os-net-config/samples/bridge_fail_mode.json b/etc/os-net-config/samples/bridge_fail_mode.json
new file mode 100644
index 0000000..6492da6
--- /dev/null
+++ b/etc/os-net-config/samples/bridge_fail_mode.json
@@ -0,0 +1,15 @@
+{ "network_config": [
+ {
+ "type": "ovs_bridge",
+ "name": "br-ctlplane",
+ "ovs_fail_mode": "secure",
+ "use_dhcp": "true",
+ "members": [
+ {
+ "type": "interface",
+ "name": "em1"
+ }
+ ]
+ }
+ ]
+}
diff --git a/etc/os-net-config/samples/bridge_fail_mode.yaml b/etc/os-net-config/samples/bridge_fail_mode.yaml
new file mode 100644
index 0000000..7589c25
--- /dev/null
+++ b/etc/os-net-config/samples/bridge_fail_mode.yaml
@@ -0,0 +1,10 @@
+network_config:
+ -
+ type: ovs_bridge
+ name: br-ctlplane
+ use_dhcp: true
+ ovs_fail_mode: secure
+ members:
+ -
+ type: interface
+ name: em1
diff --git a/etc/os-net-config/samples/bridge_ovs_extra.json b/etc/os-net-config/samples/bridge_ovs_extra.json
new file mode 100644
index 0000000..d71ead5
--- /dev/null
+++ b/etc/os-net-config/samples/bridge_ovs_extra.json
@@ -0,0 +1,18 @@
+{ "network_config": [
+ {
+ "type": "ovs_bridge",
+ "name": "br-ctlplane",
+ "ovs_extra": [
+ "br-set-external-id br-ctlplane bridge-id br-ctlplane",
+ "set bridge {name} stp_enable=true"
+ ],
+ "use_dhcp": "true",
+ "members": [
+ {
+ "type": "interface",
+ "name": "em1"
+ }
+ ]
+ }
+ ]
+}
diff --git a/etc/os-net-config/samples/bridge_ovs_extra.yaml b/etc/os-net-config/samples/bridge_ovs_extra.yaml
new file mode 100644
index 0000000..e369d62
--- /dev/null
+++ b/etc/os-net-config/samples/bridge_ovs_extra.yaml
@@ -0,0 +1,12 @@
+network_config:
+ -
+ type: ovs_bridge
+ name: br-ctlplane
+ use_dhcp: true
+ ovs_extra:
+ - br-set-external-id br-ctlplane bridge-id br-ctlplane
+ - set bridge {name} stp_enable=true
+ members:
+ -
+ type: interface
+ name: em1
diff --git a/etc/os-net-config/samples/ib_interface.json b/etc/os-net-config/samples/ib_interface.json
index 4e42867..f552af3 100644
--- a/etc/os-net-config/samples/ib_interface.json
+++ b/etc/os-net-config/samples/ib_interface.json
@@ -20,7 +20,7 @@
"type": "ib_interface",
"name": "ib1",
"use_dhcp": true,
- "defroute": no
+ "defroute": false
}
]
}
diff --git a/etc/os-net-config/samples/ib_interface.yaml b/etc/os-net-config/samples/ib_interface.yaml
index f930471..6210f0a 100644
--- a/etc/os-net-config/samples/ib_interface.yaml
+++ b/etc/os-net-config/samples/ib_interface.yaml
@@ -15,4 +15,4 @@ network_config:
type: interface
name: ib1
use_dhcp: true
- defroute: no \ No newline at end of file
+ defroute: false
diff --git a/etc/os-net-config/samples/interface.json b/etc/os-net-config/samples/interface.json
index 8a942b5..7b70e05 100644
--- a/etc/os-net-config/samples/interface.json
+++ b/etc/os-net-config/samples/interface.json
@@ -13,6 +13,11 @@
"ip_netmask": "0.0.0.0/0",
"next_hop": "192.0.2.254",
"default": "true"
+ },
+ {
+ "ip_netmask": "10.1.2.0/24",
+ "next_hop": "192.0.2.5",
+ "route_options": "metric 10"
}
]
},
@@ -20,7 +25,14 @@
"type": "interface",
"name": "em2",
"use_dhcp": true,
- "defroute": no
+ "defroute": false,
+ "ethtool_opts": "speed 1000 duplex full"
+ },
+ {
+ "type": "interface",
+ "name": "em3",
+ "use_dhcp": true,
+ "hotplug": true
}
]
}
diff --git a/etc/os-net-config/samples/interface.yaml b/etc/os-net-config/samples/interface.yaml
index 4f76e07..4c4269e 100644
--- a/etc/os-net-config/samples/interface.yaml
+++ b/etc/os-net-config/samples/interface.yaml
@@ -11,8 +11,18 @@ network_config:
ip_netmask: 0.0.0.0/0
next_hop: 192.0.2.254
default: true
+ -
+ ip_netmask: 10.1.2.0/24
+ next_hop: 192.0.2.5
+ route_options: "metric 10"
-
type: interface
name: em2
use_dhcp: true
- defroute: no \ No newline at end of file
+ defroute: false
+ ethtool_opts: "speed 1000 duplex full"
+ -
+ type: interface
+ name: em3
+ use_dhcp: true
+ hotplug: true
diff --git a/etc/os-net-config/samples/ovs_patch_port.yaml b/etc/os-net-config/samples/ovs_patch_port.yaml
index 91858be..bae8880 100644
--- a/etc/os-net-config/samples/ovs_patch_port.yaml
+++ b/etc/os-net-config/samples/ovs_patch_port.yaml
@@ -10,7 +10,7 @@ network_config:
# force the MAC address of the bridge to this interface
primary: true
mtu: 1500
- ovs_extra: "br-set-external-id br-ctlplane bridge-id br-ctlplane"
+ ovs_extra: ["br-set-external-id br-ctlplane bridge-id br-ctlplane"]
-
type: ovs_patch_port
name: br_pub-patch
diff --git a/os_net_config/__init__.py b/os_net_config/__init__.py
index 19d4a1e..faa5e92 100644
--- a/os_net_config/__init__.py
+++ b/os_net_config/__init__.py
@@ -101,105 +101,105 @@ class NetConfig(object):
:param interface: The Interface object to add.
"""
- raise NotImplemented("add_interface is not implemented.")
+ raise NotImplementedError("add_interface is not implemented.")
def add_vlan(self, vlan):
"""Add a Vlan object to the net config object.
:param vlan: The vlan object to add.
"""
- raise NotImplemented("add_vlan is not implemented.")
+ raise NotImplementedError("add_vlan is not implemented.")
def add_bridge(self, bridge):
"""Add an OvsBridge object to the net config object.
:param bridge: The OvsBridge object to add.
"""
- raise NotImplemented("add_bridge is not implemented.")
+ raise NotImplementedError("add_bridge is not implemented.")
def add_ovs_user_bridge(self, bridge):
"""Add an OvsUserBridge object to the net config object.
:param bridge: The OvsUserBridge object to add.
"""
- raise NotImplemented("add_ovs_user_bridge is not implemented.")
+ raise NotImplementedError("add_ovs_user_bridge is not implemented.")
def add_linux_bridge(self, bridge):
"""Add a LinuxBridge object to the net config object.
:param bridge: The LinuxBridge object to add.
"""
- raise NotImplemented("add_linux_bridge is not implemented.")
+ raise NotImplementedError("add_linux_bridge is not implemented.")
def add_ivs_bridge(self, bridge):
"""Add a IvsBridge object to the net config object.
:param bridge: The IvsBridge object to add.
"""
- raise NotImplemented("add_ivs_bridge is not implemented.")
+ raise NotImplementedError("add_ivs_bridge is not implemented.")
def add_nfvswitch_bridge(self, bridge):
"""Add a NfvswitchBridge object to the net config object.
:param bridge: The NfvswitchBridge object to add.
"""
- raise NotImplemented("add_nfvswitch_bridge is not implemented.")
+ raise NotImplementedError("add_nfvswitch_bridge is not implemented.")
def add_bond(self, bond):
"""Add an OvsBond object to the net config object.
:param bond: The OvsBond object to add.
"""
- raise NotImplemented("add_bond is not implemented.")
+ raise NotImplementedError("add_bond is not implemented.")
def add_linux_bond(self, bond):
"""Add a LinuxBond object to the net config object.
:param bond: The LinuxBond object to add.
"""
- raise NotImplemented("add_linux_bond is not implemented.")
+ raise NotImplementedError("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.")
+ raise NotImplementedError("add_linux_team is not implemented.")
def add_ovs_tunnel(self, tunnel):
"""Add a OvsTunnel object to the net config object.
:param tunnel: The OvsTunnel object to add.
"""
- raise NotImplemented("add_ovs_tunnel is not implemented.")
+ raise NotImplementedError("add_ovs_tunnel is not implemented.")
def add_ovs_patch_port(self, ovs_patch_port):
"""Add a OvsPatchPort object to the net config object.
:param ovs_patch_port: The OvsPatchPort object to add.
"""
- raise NotImplemented("add_ovs_patch_port is not implemented.")
+ raise NotImplementedError("add_ovs_patch_port is not implemented.")
def add_ib_interface(self, ib_interface):
"""Add an InfiniBand Interface object to the net config object.
:param interface: The InfiniBand Interface object to add.
"""
- raise NotImplemented("add_ib_interface is not implemented.")
+ raise NotImplementedError("add_ib_interface is not implemented.")
def add_ovs_dpdk_port(self, ovs_dpdk_port):
"""Add a OvsDpdkPort object to the net config object.
:param ovs_dpdk_port: The OvsDpdkPort object to add.
"""
- raise NotImplemented("add_ovs_dpdk_port is not implemented.")
+ raise NotImplementedError("add_ovs_dpdk_port is not implemented.")
def add_ovs_dpdk_bond(self, ovs_dpdk_bond):
"""Add a OvsDpdkBond object to the net config object.
:param ovs_dpdk_bond: The OvsDpdkBond object to add.
"""
- raise NotImplemented("add_ovs_dpdk_bond is not implemented.")
+ raise NotImplementedError("add_ovs_dpdk_bond is not implemented.")
def apply(self, cleanup=False):
"""Apply the network configuration.
@@ -211,7 +211,7 @@ class NetConfig(object):
for each file that was changed (or would be changed if in --noop
mode).
"""
- raise NotImplemented("apply is not implemented.")
+ raise NotImplementedError("apply is not implemented.")
def execute(self, msg, cmd, *args, **kwargs):
"""Print a message and run a command.
diff --git a/os_net_config/cli.py b/os_net_config/cli.py
index c0ac5c4..479b3a3 100644
--- a/os_net_config/cli.py
+++ b/os_net_config/cli.py
@@ -186,7 +186,7 @@ def main(argv=sys.argv):
files_changed = provider.apply(cleanup=opts.cleanup,
activate=not opts.no_activate)
if opts.noop:
- for location, data in files_changed.iteritems():
+ for location, data in files_changed.items():
print("File: %s\n" % location)
print(data)
print("----")
diff --git a/os_net_config/impl_eni.py b/os_net_config/impl_eni.py
index ae60099..360d8c8 100644
--- a/os_net_config/impl_eni.py
+++ b/os_net_config/impl_eni.py
@@ -127,14 +127,17 @@ class ENINetConfig(os_net_config.NetConfig):
data += address_data
data += " vlan-raw-device %s\n" % interface.device
else:
- data += "auto %s\n" % interface.name
+ if isinstance(interface, objects.Interface) and interface.hotplug:
+ data += "allow-hotplug %s\n" % interface.name
+ else:
+ data += "auto %s\n" % interface.name
data += _iface
data += address_data
if interface.mtu:
data += " mtu %i\n" % interface.mtu
if interface.hwaddr:
- raise NotImplemented("hwaddr is not implemented.")
+ raise NotImplementedError("hwaddr is not implemented.")
if ovs_extra:
data += " ovs_extra %s\n" % " -- ".join(ovs_extra)
@@ -181,14 +184,17 @@ class ENINetConfig(os_net_config.NetConfig):
logger.info('adding custom route for interface: %s' % interface_name)
data = ""
for route in routes:
+ options = ""
+ if route.route_options:
+ options = " %s" % (route.route_options)
if route.default and not route.ip_netmask:
rt = netaddr.IPNetwork("0.0.0.0/0")
else:
rt = netaddr.IPNetwork(route.ip_netmask)
- data += "up route add -net %s netmask %s gw %s\n" % (
- str(rt.ip), str(rt.netmask), route.next_hop)
- data += "down route del -net %s netmask %s gw %s\n" % (
- str(rt.ip), str(rt.netmask), route.next_hop)
+ data += "up route add -net %s netmask %s gw %s%s\n" % (
+ str(rt.ip), str(rt.netmask), route.next_hop, options)
+ data += "down route del -net %s netmask %s gw %s%s\n" % (
+ str(rt.ip), str(rt.netmask), route.next_hop, options)
self.routes[interface_name] = data
logger.debug('route data: %s' % self.routes[interface_name])
@@ -209,12 +215,12 @@ class ENINetConfig(os_net_config.NetConfig):
# write out bridges first. This ensures that an ifup -a
# on reboot brings them up first
- for bridge_name, bridge_data in self.bridges.iteritems():
+ for bridge_name, bridge_data in self.bridges.items():
route_data = self.routes.get(bridge_name)
bridge_data += (route_data or '')
new_config += bridge_data
- for interface_name, iface_data in self.interfaces.iteritems():
+ for interface_name, iface_data in self.interfaces.items():
route_data = self.routes.get(interface_name)
iface_data += (route_data or '')
new_config += iface_data
diff --git a/os_net_config/impl_ifcfg.py b/os_net_config/impl_ifcfg.py
index 56f2b33..6d3c681 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."""
@@ -94,7 +131,10 @@ class IfcfgNetConfig(os_net_config.NetConfig):
data = "# This file is autogenerated by os-net-config\n"
data += "DEVICE=%s\n" % base_opt.name
data += "ONBOOT=yes\n"
- data += "HOTPLUG=no\n"
+ if isinstance(base_opt, objects.Interface) and base_opt.hotplug:
+ data += "HOTPLUG=yes\n"
+ else:
+ data += "HOTPLUG=no\n"
data += "NM_CONTROLLED=no\n"
if not base_opt.dns_servers and not base_opt.use_dhcp:
data += "PEERDNS=no\n"
@@ -113,6 +153,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
data += "TYPE=NFVSWITCHIntPort\n"
elif isinstance(base_opt, objects.IbInterface):
data += "TYPE=Infiniband\n"
+ if base_opt.ethtool_opts:
+ data += "ETHTOOL_OPTS=\"%s\"\n" % base_opt.ethtool_opts
elif re.match('\w+\.\d+$', base_opt.name):
data += "VLAN=yes\n"
if base_opt.linux_bond_name:
@@ -244,15 +286,18 @@ class IfcfgNetConfig(os_net_config.NetConfig):
data += "OVS_BRIDGE=%s\n" % base_opt.bridge_name
elif isinstance(base_opt, objects.OvsDpdkBond):
ovs_extra.extend(base_opt.ovs_extra)
- if base_opt.primary_interface_name:
- primary_name = base_opt.primary_interface_name
- self.bond_primary_ifaces[base_opt.name] = primary_name
+ # Referring to bug:1643026, the below commenting of the interfaces,
+ # is to workaround the error, but is not the long term solution.
+ # The long term solution is to run DPDK options before
+ # os-net-config, which is being tracked at BUG:1654975
+ # if base_opt.primary_interface_name:
+ # primary_name = base_opt.primary_interface_name
+ # self.bond_primary_ifaces[base_opt.name] = primary_name
data += "DEVICETYPE=ovs\n"
data += "TYPE=OVSDPDKBond\n"
data += "OVS_BRIDGE=%s\n" % base_opt.bridge_name
if base_opt.members:
members = [member.name for member in base_opt.members]
- self.member_names[base_opt.name] = members
data += ("BOND_IFACES=\"%s\"\n" % " ".join(members))
if base_opt.ovs_options:
data += "OVS_OPTIONS=\"%s\"\n" % base_opt.ovs_options
@@ -262,6 +307,9 @@ class IfcfgNetConfig(os_net_config.NetConfig):
data += "BOOTPROTO=dhcp\n"
elif not base_opt.addresses:
data += "BOOTPROTO=none\n"
+ if isinstance(base_opt, objects.Interface):
+ if base_opt.ethtool_opts:
+ data += "ETHTOOL_OPTS=\"%s\"\n" % base_opt.ethtool_opts
if base_opt.mtu:
data += "MTU=%i\n" % base_opt.mtu
@@ -313,24 +361,29 @@ class IfcfgNetConfig(os_net_config.NetConfig):
data6 = ""
first_line6 = ""
for route in routes:
+ options = ""
+ if route.route_options:
+ options = " %s" % (route.route_options)
if ":" not in route.next_hop:
# Route is an IPv4 route
if route.default:
- first_line = "default via %s dev %s\n" % (route.next_hop,
- interface_name)
+ first_line = "default via %s dev %s%s\n" % (
+ route.next_hop, interface_name,
+ options)
else:
- data += "%s via %s dev %s\n" % (route.ip_netmask,
- route.next_hop,
- interface_name)
+ data += "%s via %s dev %s%s\n" % (
+ route.ip_netmask, route.next_hop,
+ interface_name, options)
else:
# Route is an IPv6 route
if route.default:
- first_line6 = "default via %s dev %s\n" % (route.next_hop,
- interface_name)
+ first_line6 = "default via %s dev %s%s\n" % (
+ route.next_hop, interface_name,
+ options)
else:
- data6 += "%s via %s dev %s\n" % (route.ip_netmask,
- route.next_hop,
- interface_name)
+ data6 += "%s via %s dev %s%s\n" % (
+ route.ip_netmask, route.next_hop,
+ interface_name, options)
self.route_data[interface_name] = first_line + data
self.route6_data[interface_name] = first_line6 + data6
logger.debug('route data: %s' % self.route_data[interface_name])
@@ -629,8 +682,9 @@ 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():
+ for interface_name, iface_data in self.interface_data.items():
route_data = self.route_data.get(interface_name, '')
route6_data = self.route6_data.get(interface_name, '')
interface_path = self.root_dir + ifcfg_config_path(interface_name)
@@ -652,11 +706,13 @@ 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)
- for interface_name, iface_data in self.ivsinterface_data.iteritems():
+ for interface_name, iface_data in self.ivsinterface_data.items():
route_data = self.route_data.get(interface_name, '')
route6_data = self.route6_data.get(interface_name, '')
interface_path = self.root_dir + ifcfg_config_path(interface_name)
@@ -677,7 +733,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
logger.info('No changes required for ivs interface: %s' %
interface_name)
- for iface_name, iface_data in self.nfvswitch_intiface_data.iteritems():
+ for iface_name, iface_data in self.nfvswitch_intiface_data.items():
route_data = self.route_data.get(iface_name, '')
route6_data = self.route6_data.get(iface_name, '')
iface_path = self.root_dir + ifcfg_config_path(iface_name)
@@ -698,7 +754,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
logger.info('No changes required for nfvswitch interface: %s' %
iface_name)
- for vlan_name, vlan_data in self.vlan_data.iteritems():
+ for vlan_name, vlan_data in self.vlan_data.items():
route_data = self.route_data.get(vlan_name, '')
route6_data = self.route6_data.get(vlan_name, '')
vlan_path = self.root_dir + ifcfg_config_path(vlan_name)
@@ -718,7 +774,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
logger.info('No changes required for vlan interface: %s' %
vlan_name)
- for bridge_name, bridge_data in self.bridge_data.iteritems():
+ for bridge_name, bridge_data in self.bridge_data.items():
route_data = self.route_data.get(bridge_name, '')
route6_data = self.route6_data.get(bridge_name, '')
bridge_path = self.root_dir + bridge_config_path(bridge_name)
@@ -742,7 +798,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
else:
logger.info('No changes required for bridge: %s' % bridge_name)
- for bridge_name, bridge_data in self.linuxbridge_data.iteritems():
+ for bridge_name, bridge_data in self.linuxbridge_data.items():
route_data = self.route_data.get(bridge_name, '')
route6_data = self.route6_data.get(bridge_name, '')
bridge_path = self.root_dir + bridge_config_path(bridge_name)
@@ -762,7 +818,7 @@ 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():
+ for team_name, team_data in self.linuxteam_data.items():
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)
@@ -783,7 +839,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
logger.info('No changes required for linux team: %s' %
team_name)
- for bond_name, bond_data in self.linuxbond_data.iteritems():
+ for bond_name, bond_data in self.linuxbond_data.items():
route_data = self.route_data.get(bond_name, '')
route6_data = self.route6_data.get(bond_name, '')
bond_path = self.root_dir + bridge_config_path(bond_name)
@@ -805,7 +861,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
bond_name)
# Infiniband interfaces are handled similarly to Ethernet interfaces
- for interface_name, iface_data in self.ib_interface_data.iteritems():
+ for interface_name, iface_data in self.ib_interface_data.items():
route_data = self.route_data.get(interface_name, '')
route6_data = self.route6_data.get(interface_name, '')
interface_path = self.root_dir + ifcfg_config_path(interface_name)
@@ -856,10 +912,10 @@ class IfcfgNetConfig(os_net_config.NetConfig):
for bridge in restart_bridges:
self.ifdown(bridge, iftype='bridge')
- for oldname, newname in self.renamed_interfaces.iteritems():
+ for oldname, newname in self.renamed_interfaces.items():
self.ifrename(oldname, newname)
- for location, data in update_files.iteritems():
+ for location, data in update_files.items():
self.write_config(location, data)
if ivs_uplinks or ivs_interfaces:
@@ -880,6 +936,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 3c67ada..3136be0 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,20 +133,29 @@ 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."""
- def __init__(self, next_hop, ip_netmask="", default=False):
+ def __init__(self, next_hop, ip_netmask="", default=False,
+ route_options=""):
self.next_hop = next_hop
self.ip_netmask = ip_netmask
self.default = default
+ self.route_options = route_options
@staticmethod
def from_json(json):
next_hop = _get_required_field(json, 'next_hop', 'Route')
ip_netmask = json.get('ip_netmask', "")
+ route_options = json.get('route_options', "")
default = strutils.bool_from_string(str(json.get('default', False)))
- return Route(next_hop, ip_netmask, default)
+ return Route(next_hop, ip_netmask, default, route_options)
class Address(object):
@@ -276,7 +287,7 @@ class Interface(_BaseOpts):
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
routes=None, mtu=None, primary=False, nic_mapping=None,
persist_mapping=False, defroute=True, dhclient_args=None,
- dns_servers=None):
+ dns_servers=None, ethtool_opts=None, hotplug=False):
addresses = addresses or []
routes = routes or []
dns_servers = dns_servers or []
@@ -284,12 +295,17 @@ class Interface(_BaseOpts):
routes, mtu, primary, nic_mapping,
persist_mapping, defroute,
dhclient_args, dns_servers)
+ self.ethtool_opts = ethtool_opts
+ self.hotplug = hotplug
@staticmethod
def from_json(json):
name = _get_required_field(json, 'name', 'Interface')
+ hotplug = strutils.bool_from_string(str(json.get('hotplug', False)))
opts = _BaseOpts.base_opts_from_json(json)
- return Interface(name, *opts)
+ ethtool_opts = json.get('ethtool_opts', None)
+ return Interface(name, *opts, ethtool_opts=ethtool_opts,
+ hotplug=hotplug)
class Vlan(_BaseOpts):
@@ -386,11 +402,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,
@@ -398,7 +414,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):
@@ -421,6 +440,9 @@ class OvsBridge(_BaseOpts):
json, include_primary=False)
ovs_options = json.get('ovs_options')
ovs_extra = json.get('ovs_extra', [])
+ if not isinstance(ovs_extra, list):
+ ovs_extra = [ovs_extra]
+ fail_mode = json.get('ovs_fail_mode', DEFAULT_OVS_BRIDGE_FAIL_MODE)
members = []
# members
@@ -438,7 +460,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):
@@ -447,7 +470,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,
@@ -455,7 +479,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 \
@@ -480,6 +507,9 @@ class OvsUserBridge(_BaseOpts):
json, include_primary=False)
ovs_options = json.get('ovs_options')
ovs_extra = json.get('ovs_extra', [])
+ if not isinstance(ovs_extra, list):
+ ovs_extra = [ovs_extra]
+ fail_mode = json.get('ovs_fail_mode', DEFAULT_OVS_BRIDGE_FAIL_MODE)
members = []
# members
@@ -498,7 +528,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):
@@ -803,7 +833,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,
@@ -811,7 +840,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:
@@ -835,6 +864,8 @@ class OvsBond(_BaseOpts):
json, include_primary=False)
ovs_options = json.get('ovs_options')
ovs_extra = json.get('ovs_extra', [])
+ if not isinstance(ovs_extra, list):
+ ovs_extra = [ovs_extra]
members = []
# members
@@ -865,7 +896,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,
@@ -873,7 +903,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):
@@ -882,6 +912,8 @@ class OvsTunnel(_BaseOpts):
ovs_options = json.get('ovs_options', [])
ovs_options = ['options:%s' % opt for opt in ovs_options]
ovs_extra = json.get('ovs_extra', [])
+ if not isinstance(ovs_extra, list):
+ ovs_extra = [ovs_extra]
opts = _BaseOpts.base_opts_from_json(json)
return OvsTunnel(name, *opts, tunnel_type=tunnel_type,
ovs_options=ovs_options, ovs_extra=ovs_extra)
@@ -897,7 +929,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,
@@ -907,7 +938,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):
@@ -917,6 +948,8 @@ class OvsPatchPort(_BaseOpts):
ovs_options = json.get('ovs_options', [])
ovs_options = ['options:%s' % opt for opt in ovs_options]
ovs_extra = json.get('ovs_extra', [])
+ if not isinstance(ovs_extra, list):
+ ovs_extra = [ovs_extra]
opts = _BaseOpts.base_opts_from_json(json)
return OvsPatchPort(name, *opts, bridge_name=bridge_name, peer=peer,
ovs_options=ovs_options, ovs_extra=ovs_extra)
@@ -928,7 +961,7 @@ class IbInterface(_BaseOpts):
def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
routes=None, mtu=None, primary=False, nic_mapping=None,
persist_mapping=False, defroute=True, dhclient_args=None,
- dns_servers=None):
+ dns_servers=None, ethtool_opts=None):
addresses = addresses or []
routes = routes or []
dns_servers = dns_servers or []
@@ -936,12 +969,14 @@ class IbInterface(_BaseOpts):
addresses, routes, mtu, primary,
nic_mapping, persist_mapping,
defroute, dhclient_args, dns_servers)
+ self.ethtool_opts = ethtool_opts
@staticmethod
def from_json(json):
name = _get_required_field(json, 'name', 'IbInterface')
+ ethtool_opts = json.get('ethtool_opts', None)
opts = _BaseOpts.base_opts_from_json(json)
- return IbInterface(name, *opts)
+ return IbInterface(name, *opts, ethtool_opts=ethtool_opts)
class OvsDpdkPort(_BaseOpts):
@@ -960,7 +995,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
@@ -998,6 +1033,8 @@ class OvsDpdkPort(_BaseOpts):
ovs_options = json.get('ovs_options', [])
ovs_options = ['options:%s' % opt for opt in ovs_options]
ovs_extra = json.get('ovs_extra', [])
+ if not isinstance(ovs_extra, list):
+ ovs_extra = [ovs_extra]
opts = _BaseOpts.base_opts_from_json(json)
return OvsDpdkPort(name, *opts, members=members, driver=driver,
ovs_options=ovs_options, ovs_extra=ovs_extra)
@@ -1017,7 +1054,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:
@@ -1042,6 +1079,8 @@ class OvsDpdkBond(_BaseOpts):
json, include_primary=False)
ovs_options = json.get('ovs_options')
ovs_extra = json.get('ovs_extra', [])
+ if not isinstance(ovs_extra, list):
+ ovs_extra = [ovs_extra]
members = []
# members
diff --git a/os_net_config/tests/test_impl_eni.py b/os_net_config/tests/test_impl_eni.py
index 7f909ff..4911cb9 100644
--- a/os_net_config/tests/test_impl_eni.py
+++ b/os_net_config/tests/test_impl_eni.py
@@ -32,6 +32,12 @@ _V4_IFACE_STATIC_IP = _AUTO + """iface eth0 inet static
netmask 255.255.255.0
"""
+_IFACE_HOTPLUG = """allow-hotplug eth0
+iface eth0 inet static
+ address 192.168.1.2
+ netmask 255.255.255.0
+"""
+
_V4_IFACE_STATIC_IP_MULTIPLE = (_V4_IFACE_STATIC_IP + _AUTO +
"""iface eth0 inet static
address 10.0.0.2
@@ -66,13 +72,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
@@ -90,6 +101,8 @@ iface vlan5 inet manual
_RTS = """up route add -net 172.19.0.0 netmask 255.255.255.0 gw 192.168.1.1
down route del -net 172.19.0.0 netmask 255.255.255.0 gw 192.168.1.1
+up route add -net 172.20.0.0 netmask 255.255.255.0 gw 192.168.1.5 metric 100
+down route del -net 172.20.0.0 netmask 255.255.255.0 gw 192.168.1.5 metric 100
"""
@@ -110,8 +123,9 @@ class TestENINetConfig(base.TestCase):
def get_route_config(self):
return self.provider.routes[self.if_name]
- def _default_interface(self, addr=[], rts=[]):
- return objects.Interface(self.if_name, addresses=addr, routes=rts)
+ def _default_interface(self, addr=[], rts=[], hotplug=False):
+ return objects.Interface(self.if_name, addresses=addr, routes=rts,
+ hotplug=hotplug)
def test_interface_no_ip(self):
interface = self._default_interface()
@@ -124,6 +138,12 @@ class TestENINetConfig(base.TestCase):
self.provider.add_interface(interface)
self.assertEqual(_V4_IFACE_STATIC_IP, self.get_interface_config())
+ def test_add_interface_with_hotplug(self):
+ v4_addr = objects.Address('192.168.1.2/24')
+ interface = self._default_interface(addr=[v4_addr], hotplug=True)
+ self.provider.add_interface(interface)
+ self.assertEqual(_IFACE_HOTPLUG, self.get_interface_config())
+
def test_add_interface_with_v4_multiple(self):
v4_addresses = [objects.Address('192.168.1.2/24'),
objects.Address('10.0.0.2/8')]
@@ -169,8 +189,10 @@ class TestENINetConfig(base.TestCase):
def test_network_with_routes(self):
route1 = objects.Route('192.168.1.1', '172.19.0.0/24')
+ route2 = objects.Route('192.168.1.5', '172.20.0.0/24',
+ route_options="metric 100")
v4_addr = objects.Address('192.168.1.2/24')
- interface = self._default_interface([v4_addr], [route1])
+ interface = self._default_interface([v4_addr], [route1, route2])
self.provider.add_interface(interface)
self.assertEqual(_V4_IFACE_STATIC_IP, self.get_interface_config())
self.assertEqual(_RTS, self.get_route_config())
@@ -184,6 +206,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):
@@ -206,7 +250,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])
@@ -261,10 +322,12 @@ class TestENINetConfigApply(base.TestCase):
super(TestENINetConfigApply, self).tearDown()
def test_network_apply(self):
- route = objects.Route('192.168.1.1', '172.19.0.0/24')
+ route1 = objects.Route('192.168.1.1', '172.19.0.0/24')
+ route2 = objects.Route('192.168.1.5', '172.20.0.0/24',
+ route_options="metric 100")
v4_addr = objects.Address('192.168.1.2/24')
interface = objects.Interface('eth0', addresses=[v4_addr],
- routes=[route])
+ routes=[route1, route2])
self.provider.add_interface(interface)
self.provider.apply()
@@ -273,10 +336,12 @@ class TestENINetConfigApply(base.TestCase):
self.assertIn('eth0', self.ifup_interface_names)
def test_apply_noactivate(self):
- route = objects.Route('192.168.1.1', '172.19.0.0/24')
+ route1 = objects.Route('192.168.1.1', '172.19.0.0/24')
+ route2 = objects.Route('192.168.1.5', '172.20.0.0/24',
+ route_options="metric 100")
v4_addr = objects.Address('192.168.1.2/24')
interface = objects.Interface('eth0', addresses=[v4_addr],
- routes=[route])
+ routes=[route1, route2])
self.provider.add_interface(interface)
self.provider.apply(activate=False)
diff --git a/os_net_config/tests/test_impl_ifcfg.py b/os_net_config/tests/test_impl_ifcfg.py
index 8586daa..9621b8c 100644
--- a/os_net_config/tests/test_impl_ifcfg.py
+++ b/os_net_config/tests/test_impl_ifcfg.py
@@ -33,6 +33,15 @@ NM_CONTROLLED=no
PEERDNS=no
"""
+_HOTPLUG = """# This file is autogenerated by os-net-config
+DEVICE=em1
+ONBOOT=yes
+HOTPLUG=yes
+NM_CONTROLLED=no
+PEERDNS=no
+BOOTPROTO=none
+"""
+
_NO_IP = _BASE_IFCFG + "BOOTPROTO=none\n"
_V4_IFCFG = _BASE_IFCFG + """BOOTPROTO=static
@@ -115,12 +124,14 @@ _OVS_BRIDGE_IFCFG = _BASE_IFCFG + "DEVICETYPE=ovs\n"
_LINUX_BRIDGE_IFCFG = _BASE_IFCFG + "BRIDGE=br-ctlplane\nBOOTPROTO=none\n"
-_ROUTES = """default via 192.168.1.1 dev em1
+_ROUTES = """default via 192.168.1.1 dev em1 metric 10
172.19.0.0/24 via 192.168.1.1 dev em1
+172.20.0.0/24 via 192.168.1.5 dev em1 metric 100
"""
_ROUTES_V6 = """default via 2001:db8::1 dev em1
2001:db8:dead:beef:cafe::/56 via fd00:fd00:2000::1 dev em1
+2001:db8:dead:beff::/64 via fd00:fd00:2000::1 dev em1 metric 100
"""
@@ -141,6 +152,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
@@ -377,6 +395,11 @@ class TestIfcfgNetConfig(base.TestCase):
self.provider.add_interface(interface)
self.assertEqual(_NO_IP, self.get_interface_config())
+ def test_add_interface_with_hotplug(self):
+ interface = objects.Interface('em1', hotplug=True)
+ self.provider.add_interface(interface)
+ self.assertEqual(_HOTPLUG, self.get_interface_config())
+
def test_add_base_interface_vlan(self):
interface = objects.Interface('em1.120')
self.provider.add_interface(interface)
@@ -454,25 +477,35 @@ class TestIfcfgNetConfig(base.TestCase):
self.assertEqual(_V6_IFCFG_MULTIPLE, self.get_interface_config())
def test_network_with_routes(self):
- route1 = objects.Route('192.168.1.1', default=True)
+ route1 = objects.Route('192.168.1.1', default=True,
+ route_options="metric 10")
route2 = objects.Route('192.168.1.1', '172.19.0.0/24')
+ route3 = objects.Route('192.168.1.5', '172.20.0.0/24',
+ route_options="metric 100")
v4_addr = objects.Address('192.168.1.2/24')
interface = objects.Interface('em1', addresses=[v4_addr],
- routes=[route1, route2])
+ routes=[route1, route2, route3])
self.provider.add_interface(interface)
self.assertEqual(_V4_IFCFG, self.get_interface_config())
self.assertEqual(_ROUTES, self.get_route_config())
def test_network_with_ipv6_routes(self):
- route1 = objects.Route('192.168.1.1', default=True)
+ route1 = objects.Route('192.168.1.1', default=True,
+ route_options="metric 10")
route2 = objects.Route('192.168.1.1', '172.19.0.0/24')
- route3 = objects.Route('2001:db8::1', default=True)
- route4 = objects.Route('fd00:fd00:2000::1',
+ route3 = objects.Route('192.168.1.5', '172.20.0.0/24',
+ route_options="metric 100")
+ route4 = objects.Route('2001:db8::1', default=True)
+ route5 = objects.Route('fd00:fd00:2000::1',
'2001:db8:dead:beef:cafe::/56')
+ route6 = objects.Route('fd00:fd00:2000::1',
+ '2001:db8:dead:beff::/64',
+ route_options="metric 100")
v4_addr = objects.Address('192.168.1.2/24')
v6_addr = objects.Address('2001:abc:a::/64')
interface = objects.Interface('em1', addresses=[v4_addr, v6_addr],
- routes=[route1, route2, route3, route4])
+ routes=[route1, route2, route3,
+ route4, route5, route6])
self.provider.add_interface(interface)
self.assertEqual(_V4_V6_IFCFG, self.get_interface_config())
self.assertEqual(_ROUTES_V6, self.get_route6_config())
@@ -487,6 +520,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,
@@ -565,6 +620,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')
@@ -730,6 +801,21 @@ DHCLIENTARGS=--foobar
"""
self.assertEqual(em1_config, self.get_interface_config('em1'))
+ def test_interface_ethtool_opts(self):
+ interface1 = objects.Interface('em1',
+ ethtool_opts='speed 1000 duplex full')
+ self.provider.add_interface(interface1)
+ em1_config = """# This file is autogenerated by os-net-config
+DEVICE=em1
+ONBOOT=yes
+HOTPLUG=no
+NM_CONTROLLED=no
+PEERDNS=no
+BOOTPROTO=none
+ETHTOOL_OPTS=\"speed 1000 duplex full\"
+"""
+ self.assertEqual(em1_config, self.get_interface_config('em1'))
+
def test_interface_single_dns_server(self):
interface1 = objects.Interface('em1', dns_servers=['1.2.3.4'])
self.provider.add_interface(interface1)
@@ -843,6 +929,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
@@ -864,6 +951,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])
@@ -884,11 +976,14 @@ class TestIfcfgNetConfigApply(base.TestCase):
super(TestIfcfgNetConfigApply, self).tearDown()
def test_network_apply(self):
- route1 = objects.Route('192.168.1.1', default=True)
+ route1 = objects.Route('192.168.1.1', default=True,
+ route_options="metric 10")
route2 = objects.Route('192.168.1.1', '172.19.0.0/24')
+ route3 = objects.Route('192.168.1.5', '172.20.0.0/24',
+ route_options="metric 100")
v4_addr = objects.Address('192.168.1.2/24')
interface = objects.Interface('em1', addresses=[v4_addr],
- routes=[route1, route2])
+ routes=[route1, route2, route3])
self.provider.add_interface(interface)
self.provider.apply()
@@ -913,6 +1008,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 7500392..f5daf31 100644
--- a/os_net_config/tests/test_objects.py
+++ b/os_net_config/tests/test_objects.py
@@ -25,26 +25,30 @@ from os_net_config import utils
class TestRoute(base.TestCase):
def test_from_json(self):
- data = '{"next_hop": "172.19.0.1", "ip_netmask": "172.19.0.0/24"}'
+ data = '{"next_hop": "172.19.0.1", "ip_netmask": "172.19.0.0/24", ' \
+ '"route_options": "metric 10"}'
route = objects.Route.from_json(json.loads(data))
self.assertEqual("172.19.0.1", route.next_hop)
self.assertEqual("172.19.0.0/24", route.ip_netmask)
self.assertFalse(route.default)
+ self.assertEqual("metric 10", route.route_options)
def test_from_json_default_route(self):
data = '{"next_hop": "172.19.0.1", "ip_netmask": "172.19.0.0/24", ' \
- '"default": true}'
+ '"default": true, "route_options": "metric 10"}'
route = objects.Route.from_json(json.loads(data))
self.assertEqual("172.19.0.1", route.next_hop)
self.assertEqual("172.19.0.0/24", route.ip_netmask)
self.assertTrue(route.default)
+ self.assertEqual("metric 10", route.route_options)
data = '{"next_hop": "172.19.0.1", "ip_netmask": "172.19.0.0/24", ' \
- '"default": "true"}'
+ '"default": "true", "route_options": "metric 10"}'
route = objects.Route.from_json(json.loads(data))
self.assertEqual("172.19.0.1", route.next_hop)
self.assertEqual("172.19.0.0/24", route.ip_netmask)
self.assertTrue(route.default)
+ self.assertEqual("metric 10", route.route_options)
class TestAddress(base.TestCase):
@@ -94,6 +98,27 @@ class TestInterface(base.TestCase):
self.assertEqual("em1", interface.name)
self.assertTrue(interface.use_dhcp)
+ def test_from_json_hotplug(self):
+ data = """{
+"type": "interface",
+"name": "em1",
+"hotplug": true
+}
+"""
+ interface = objects.object_from_json(json.loads(data))
+ self.assertEqual("em1", interface.name)
+ self.assertTrue(interface.hotplug)
+
+ def test_from_json_hotplug_off_by_default(self):
+ data = """{
+"type": "interface",
+"name": "em1"
+}
+"""
+ interface = objects.object_from_json(json.loads(data))
+ self.assertEqual("em1", interface.name)
+ self.assertFalse(interface.hotplug)
+
def test_from_json_defroute(self):
data = '{"type": "interface", "name": "em1", "use_dhcp": true}'
interface1 = objects.object_from_json(json.loads(data))
@@ -146,12 +171,14 @@ class TestInterface(base.TestCase):
"name": "em1",
"use_dhcp": false,
"mtu": 1501,
+"ethtool_opts": "speed 1000 duplex full",
"addresses": [{
"ip_netmask": "192.0.2.1/24"
}],
"routes": [{
"next_hop": "192.0.2.1",
- "ip_netmask": "192.0.2.1/24"
+ "ip_netmask": "192.0.2.1/24",
+ "route_options": "metric 10"
}]
}
"""
@@ -160,12 +187,14 @@ class TestInterface(base.TestCase):
self.assertFalse(interface.use_dhcp)
self.assertFalse(interface.use_dhcpv6)
self.assertEqual(1501, interface.mtu)
+ self.assertEqual("speed 1000 duplex full", interface.ethtool_opts)
address1 = interface.v4_addresses()[0]
self.assertEqual("192.0.2.1", address1.ip)
self.assertEqual("255.255.255.0", address1.netmask)
route1 = interface.routes[0]
self.assertEqual("192.0.2.1", route1.next_hop)
self.assertEqual("192.0.2.1/24", route1.ip_netmask)
+ self.assertEqual("metric 10", route1.route_options)
class TestVlan(base.TestCase):
@@ -266,6 +295,34 @@ class TestBridge(base.TestCase):
self.assertTrue(interface2.ovs_port)
self.assertEqual("br-foo", interface2.bridge_name)
+ def test_from_json_ovs_extra(self):
+ data = """{
+"type": "ovs_bridge",
+"name": "br-foo",
+"ovs_extra": ["bar"],
+"ovs_fail_mode": "standalone"
+}
+"""
+ bridge = objects.object_from_json(json.loads(data))
+ self.assertTrue(2 == len(bridge.ovs_extra))
+ self.assertEqual("bar", bridge.ovs_extra[0])
+ self.assertEqual("set bridge br-foo fail_mode=standalone",
+ bridge.ovs_extra[1])
+
+ def test_from_json_ovs_extra_string(self):
+ data = """{
+"type": "ovs_bridge",
+"name": "br-foo",
+"ovs_extra": "bar",
+"ovs_fail_mode": "standalone"
+}
+"""
+ bridge = objects.object_from_json(json.loads(data))
+ self.assertTrue(2 == len(bridge.ovs_extra))
+ self.assertEqual("bar", bridge.ovs_extra[0])
+ self.assertEqual("set bridge br-foo fail_mode=standalone",
+ bridge.ovs_extra[1])
+
class TestLinuxBridge(base.TestCase):
@@ -399,7 +456,7 @@ class TestIvsInterface(base.TestCase):
objects.IvsBridge.from_json,
json.loads(data))
expected = 'IVS does not support bond interfaces.'
- self.assertIn(expected, err)
+ self.assertIn(expected, six.text_type(err))
class TestNfvswitchBridge(base.TestCase):
@@ -464,7 +521,7 @@ class TestNfvswitchInterface(base.TestCase):
objects.NfvswitchBridge.from_json,
json.loads(data))
expected = 'NFVSwitch does not support bond interfaces.'
- self.assertIn(expected, err)
+ self.assertIn(expected, six.text_type(err))
class TestBond(base.TestCase):
@@ -638,6 +695,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):
@@ -654,6 +749,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):
@@ -727,7 +840,8 @@ class TestIbInterface(base.TestCase):
}],
"routes": [{
"next_hop": "192.0.2.1",
- "ip_netmask": "192.0.2.1/24"
+ "ip_netmask": "192.0.2.1/24",
+ "route_options": "metric 10"
}]
}
"""
@@ -742,6 +856,7 @@ class TestIbInterface(base.TestCase):
route1 = ib_interface.routes[0]
self.assertEqual("192.0.2.1", route1.next_hop)
self.assertEqual("192.0.2.1/24", route1.ip_netmask)
+ self.assertEqual("metric 10", route1.route_options)
class TestNicMapping(base.TestCase):
diff --git a/os_net_config/tests/test_utils.py b/os_net_config/tests/test_utils.py
index b766384..1885cbb 100644
--- a/os_net_config/tests/test_utils.py
+++ b/os_net_config/tests/test_utils.py
@@ -217,3 +217,27 @@ class TestUtils(base.TestCase):
def test_interface_mac_raises(self):
self.assertRaises(IOError, utils.interface_mac, 'ens20f2p3')
+
+ def test_is_active_nic_for_sriov_vf(self):
+
+ tmpdir = tempfile.mkdtemp()
+ self.stubs.Set(utils, '_SYS_CLASS_NET', tmpdir)
+
+ # SR-IOV PF = ens802f0
+ # SR-IOV VF = enp129s2
+ for nic in ['ens802f0', 'enp129s2']:
+ nic_path = os.path.join(tmpdir, nic)
+ os.makedirs(nic_path)
+ os.makedirs(os.path.join(nic_path, 'device'))
+ with open(os.path.join(nic_path, 'operstate'), 'w') as f:
+ f.write('up')
+ with open(os.path.join(nic_path, 'address'), 'w') as f:
+ f.write('1.2.3.4')
+
+ nic_path = os.path.join(tmpdir, 'enp129s2', 'device', 'physfn')
+ os.makedirs(nic_path)
+
+ self.assertEqual(utils._is_active_nic('ens802f0'), True)
+ self.assertEqual(utils._is_active_nic('enp129s2'), False)
+
+ shutil.rmtree(tmpdir)
diff --git a/os_net_config/utils.py b/os_net_config/utils.py
index af359d5..98bfe99 100644
--- a/os_net_config/utils.py
+++ b/os_net_config/utils.py
@@ -107,7 +107,16 @@ def _is_active_nic(interface_name):
with open(_SYS_CLASS_NET + '/%s/address' % interface_name, 'r') as f:
address = f.read().rstrip()
- if has_device_dir and operstate == 'up' and address:
+ # If SR-IOV Virtual Functions (VF) are enabled in an interface, there
+ # will be additional nics created for each VF. It has to be ignored in
+ # the nic numbering. All the VFs will have a reference to the PF with
+ # directory name as 'physfn', if this directory is present it should be
+ # ignored.
+ vf_path_check = _SYS_CLASS_NET + '/%s/device/physfn' % interface_name
+ is_sriov_vf = os.path.isdir(vf_path_check)
+
+ if (has_device_dir and operstate == 'up' and address and
+ not is_sriov_vf):
return True
else:
return False