aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--etc/os-net-config/samples/ib_interface.json26
-rw-r--r--etc/os-net-config/samples/ib_interface.yaml18
-rw-r--r--etc/os-net-config/samples/mapping_mnemonic.yaml8
-rw-r--r--etc/os-net-config/samples/nfvswitch.json38
-rw-r--r--etc/os-net-config/samples/nfvswitch.yaml25
-rw-r--r--etc/os-net-config/samples/team.json13
-rw-r--r--etc/os-net-config/samples/team.yaml18
-rw-r--r--os_net_config/__init__.py33
-rw-r--r--os_net_config/impl_ifcfg.py212
-rw-r--r--os_net_config/objects.py246
-rw-r--r--os_net_config/tests/base.py12
-rw-r--r--os_net_config/tests/test_cli.py18
-rw-r--r--os_net_config/tests/test_impl_ifcfg.py140
-rw-r--r--os_net_config/tests/test_objects.py265
14 files changed, 1006 insertions, 66 deletions
diff --git a/etc/os-net-config/samples/ib_interface.json b/etc/os-net-config/samples/ib_interface.json
new file mode 100644
index 0000000..4e42867
--- /dev/null
+++ b/etc/os-net-config/samples/ib_interface.json
@@ -0,0 +1,26 @@
+{"network_config": [
+ {
+ "type": "ib_interface",
+ "name": "ib0",
+ "use_dhcp": false,
+ "addresses": [
+ {
+ "ip_netmask": "192.0.2.1/24"
+ }
+ ],
+ "routes": [
+ {
+ "ip_netmask": "0.0.0.0/0",
+ "next_hop": "192.0.2.254",
+ "default": "true"
+ }
+ ]
+ },
+ {
+ "type": "ib_interface",
+ "name": "ib1",
+ "use_dhcp": true,
+ "defroute": no
+ }
+ ]
+}
diff --git a/etc/os-net-config/samples/ib_interface.yaml b/etc/os-net-config/samples/ib_interface.yaml
new file mode 100644
index 0000000..f930471
--- /dev/null
+++ b/etc/os-net-config/samples/ib_interface.yaml
@@ -0,0 +1,18 @@
+network_config:
+ -
+ type: ib_interface
+ name: ib0
+ use_dhcp: false
+ addresses:
+ -
+ ip_netmask: 192.0.2.1/24
+ routes:
+ -
+ ip_netmask: 0.0.0.0/0
+ next_hop: 192.0.2.254
+ default: true
+ -
+ type: interface
+ name: ib1
+ use_dhcp: true
+ defroute: no \ No newline at end of file
diff --git a/etc/os-net-config/samples/mapping_mnemonic.yaml b/etc/os-net-config/samples/mapping_mnemonic.yaml
new file mode 100644
index 0000000..10f31de
--- /dev/null
+++ b/etc/os-net-config/samples/mapping_mnemonic.yaml
@@ -0,0 +1,8 @@
+# See the mapping.yaml sample for an overview of the mapping functionality.
+# This shows the use of mnemonic aliases instead of the default nicN aliases
+# in configs. It can be used with the -m option to override the default
+# mapping of the nicN aliases.
+interface_mapping:
+ provision: em1
+ bond0p1: em2
+ bond0p2: 12:34:56:de:f0:12
diff --git a/etc/os-net-config/samples/nfvswitch.json b/etc/os-net-config/samples/nfvswitch.json
new file mode 100644
index 0000000..2d8af8a
--- /dev/null
+++ b/etc/os-net-config/samples/nfvswitch.json
@@ -0,0 +1,38 @@
+{
+ "network_config": [
+ {
+ "type": "nfvswitch_bridge",
+ "cpus": "2,3,4,5",
+ "members": [
+ {
+ "type": "interface",
+ "name": "nic2",
+ },
+ {
+ "type": "interface",
+ "name": "nic3"
+ },
+ {
+ "type": "nfvswitch_internal",
+ "name": "api",
+ "addresses": [
+ {
+ "ip_netmask": "172.16.2.7/24"
+ }
+ ],
+ "vlan_id": 201
+ },
+ {
+ "type": "nfvswitch_internal",
+ "name": "storage",
+ "addresses": [
+ {
+ "ip_netmask": "172.16.1.6/24"
+ }
+ ],
+ "vlan_id": 202
+ }
+ ]
+ }
+ ]
+}
diff --git a/etc/os-net-config/samples/nfvswitch.yaml b/etc/os-net-config/samples/nfvswitch.yaml
new file mode 100644
index 0000000..5af3f70
--- /dev/null
+++ b/etc/os-net-config/samples/nfvswitch.yaml
@@ -0,0 +1,25 @@
+network_config:
+ -
+ type: nfvswitch_bridge
+ cpus: "2,3,4,5"
+ members:
+ -
+ type: interface
+ name: nic2
+ -
+ type: interface
+ name: nic3
+ -
+ type: nfvswitch_internal
+ name: api
+ vlan_id: 201
+ addresses:
+ -
+ ip_netmask: 172.16.2.7/24
+ -
+ type: nfvswitch_internal
+ name: storage
+ vlan_id: 202
+ addresses:
+ -
+ ip_netmask: 172.16.1.6/24 \ No newline at end of file
diff --git a/etc/os-net-config/samples/team.json b/etc/os-net-config/samples/team.json
new file mode 100644
index 0000000..4524b3c
--- /dev/null
+++ b/etc/os-net-config/samples/team.json
@@ -0,0 +1,13 @@
+{ "network_config": [
+ {
+ "type": "team",
+ "name": "team1",
+ "use_dhcp": true,
+ "bonding_options": "{\"runner\": {\"name\": \"activebackup\"}}",
+ "members": [
+ { "type": "interface", "name": "em1", "primary": true },
+ { "type": "interface", "name": "em2" }
+ ]
+ }
+ ]
+}
diff --git a/etc/os-net-config/samples/team.yaml b/etc/os-net-config/samples/team.yaml
new file mode 100644
index 0000000..3770de0
--- /dev/null
+++ b/etc/os-net-config/samples/team.yaml
@@ -0,0 +1,18 @@
+# Config for bonding with teamd. Bonding options are provided as a JSON
+# string. The following runners are available in teamd: broadcast,
+# roundrobin, activebackup, loadbalance, and lacp.
+# Please see the teamd.conf(5) man page for more information.
+network_config:
+ -
+ type: team
+ name: team1
+ use_dhcp: true
+ bonding_options: '{"runner": {"name": "activebackup"}}'
+ members:
+ -
+ type: interface
+ name: em1
+ primary: true
+ -
+ type: interface
+ name: em2
diff --git a/os_net_config/__init__.py b/os_net_config/__init__.py
index 88e8900..2aafc23 100644
--- a/os_net_config/__init__.py
+++ b/os_net_config/__init__.py
@@ -51,6 +51,8 @@ class NetConfig(object):
self.add_vlan(obj)
elif isinstance(obj, objects.IvsInterface):
self.add_ivs_interface(obj)
+ elif isinstance(obj, objects.NfvswitchInternal):
+ self.add_nfvswitch_internal(obj)
elif isinstance(obj, objects.OvsBridge):
self.add_bridge(obj)
for member in obj.members:
@@ -63,6 +65,10 @@ class NetConfig(object):
self.add_ivs_bridge(obj)
for member in obj.members:
self.add_object(member)
+ elif isinstance(obj, objects.NfvswitchBridge):
+ self.add_nfvswitch_bridge(obj)
+ for member in obj.members:
+ self.add_object(member)
elif isinstance(obj, objects.OvsBond):
self.add_bond(obj)
for member in obj.members:
@@ -71,10 +77,16 @@ class NetConfig(object):
self.add_linux_bond(obj)
for member in obj.members:
self.add_object(member)
+ elif isinstance(obj, objects.LinuxTeam):
+ self.add_linux_team(obj)
+ for member in obj.members:
+ self.add_object(member)
elif isinstance(obj, objects.OvsTunnel):
self.add_ovs_tunnel(obj)
elif isinstance(obj, objects.OvsPatchPort):
self.add_ovs_patch_port(obj)
+ elif isinstance(obj, objects.IbInterface):
+ self.add_ib_interface(obj)
def add_interface(self, interface):
"""Add an Interface object to the net config object.
@@ -111,6 +123,13 @@ class NetConfig(object):
"""
raise NotImplemented("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.")
+
def add_bond(self, bond):
"""Add an OvsBond object to the net config object.
@@ -125,6 +144,13 @@ class NetConfig(object):
"""
raise NotImplemented("add_linux_bond is not implemented.")
+ def add_linux_team(self, team):
+ """Add a LinuxTeam object to the net config object.
+
+ :param team: The LinuxTeam object to add.
+ """
+ raise NotImplemented("add_linux_team is not implemented.")
+
def add_ovs_tunnel(self, tunnel):
"""Add a OvsTunnel object to the net config object.
@@ -139,6 +165,13 @@ class NetConfig(object):
"""
raise NotImplemented("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.")
+
def apply(self, cleanup=False):
"""Apply the network configuration.
diff --git a/os_net_config/impl_ifcfg.py b/os_net_config/impl_ifcfg.py
index fbd1c3a..d49b57b 100644
--- a/os_net_config/impl_ifcfg.py
+++ b/os_net_config/impl_ifcfg.py
@@ -39,6 +39,10 @@ def ivs_config_path():
return "/etc/sysconfig/ivs"
+def nfvswitch_config_path():
+ return "/etc/sysconfig/nfvswitch"
+
+
def route_config_path(name):
return "/etc/sysconfig/network-scripts/route-%s" % name
@@ -58,12 +62,16 @@ class IfcfgNetConfig(os_net_config.NetConfig):
super(IfcfgNetConfig, self).__init__(noop, root_dir)
self.interface_data = {}
self.ivsinterface_data = {}
+ self.nfvswitch_intiface_data = {}
+ self.nfvswitch_cpus = None
self.vlan_data = {}
self.route_data = {}
self.route6_data = {}
self.bridge_data = {}
self.linuxbridge_data = {}
self.linuxbond_data = {}
+ self.ib_interface_data = {}
+ self.linuxteam_data = {}
self.member_names = {}
self.renamed_interfaces = {}
self.bond_primary_ifaces = {}
@@ -101,16 +109,28 @@ class IfcfgNetConfig(os_net_config.NetConfig):
data += "PHYSDEV=%s\n" % base_opt.linux_bond_name
elif isinstance(base_opt, objects.IvsInterface):
data += "TYPE=IVSIntPort\n"
+ elif isinstance(base_opt, objects.NfvswitchInternal):
+ data += "TYPE=NFVSWITCHIntPort\n"
+ elif isinstance(base_opt, objects.IbInterface):
+ data += "TYPE=Infiniband\n"
elif re.match('\w+\.\d+$', base_opt.name):
data += "VLAN=yes\n"
if base_opt.linux_bond_name:
data += "MASTER=%s\n" % base_opt.linux_bond_name
data += "SLAVE=yes\n"
+ if base_opt.linux_team_name:
+ data += "TEAM_MASTER=%s\n" % base_opt.linux_team_name
+ if base_opt.primary:
+ data += "TEAM_PORT_CONFIG='{\"prio\": 100}'\n"
if base_opt.ivs_bridge_name:
data += "DEVICETYPE=ivs\n"
data += "IVS_BRIDGE=%s\n" % base_opt.ivs_bridge_name
+ if base_opt.nfvswitch_bridge_name:
+ data += "DEVICETYPE=nfvswitch\n"
+ data += "NFVSWITCH_BRIDGE=%s\n" % base_opt.nfvswitch_bridge_name
if base_opt.ovs_port:
- data += "DEVICETYPE=ovs\n"
+ if not isinstance(base_opt, objects.LinuxTeam):
+ data += "DEVICETYPE=ovs\n"
if base_opt.bridge_name:
if isinstance(base_opt, objects.Vlan):
data += "TYPE=OVSIntPort\n"
@@ -177,6 +197,19 @@ class IfcfgNetConfig(os_net_config.NetConfig):
self.member_names[base_opt.name] = members
if base_opt.bonding_options:
data += "BONDING_OPTS=\"%s\"\n" % base_opt.bonding_options
+ elif isinstance(base_opt, objects.LinuxTeam):
+ if base_opt.primary_interface_name:
+ primary_name = base_opt.primary_interface_name
+ primary_mac = utils.interface_mac(primary_name)
+ data += "MACADDR=\"%s\"\n" % primary_mac
+ if base_opt.use_dhcp:
+ data += "BOOTPROTO=dhcp\n"
+ if base_opt.members:
+ members = [member.name for member in base_opt.members]
+ self.member_names[base_opt.name] = members
+ data += "DEVICETYPE=Team\n"
+ if base_opt.bonding_options:
+ data += "TEAM_CONFIG='%s'\n" % base_opt.bonding_options
elif isinstance(base_opt, objects.OvsTunnel):
ovs_extra.extend(base_opt.ovs_extra)
data += "DEVICETYPE=ovs\n"
@@ -311,6 +344,19 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if ivs_interface.routes:
self._add_routes(ivs_interface.name, ivs_interface.routes)
+ def add_nfvswitch_internal(self, nfvswitch_internal):
+ """Add a nfvswitch_internal interface object to the net config object.
+
+ :param nfvswitch_internal: The nfvswitch_internal object to add.
+ """
+ iface_name = nfvswitch_internal.name
+ logger.info('adding nfvswitch_internal interface: %s' % iface_name)
+ data = self._add_common(nfvswitch_internal)
+ logger.debug('nfvswitch_internal interface data: %s' % data)
+ self.nfvswitch_intiface_data[iface_name] = data
+ if nfvswitch_internal.routes:
+ self._add_routes(iface_name, nfvswitch_internal.routes)
+
def add_bridge(self, bridge):
"""Add an OvsBridge object to the net config object.
@@ -347,6 +393,16 @@ class IfcfgNetConfig(os_net_config.NetConfig):
"""
pass
+ def add_nfvswitch_bridge(self, bridge):
+ """Add a NFVSwitchBridge object to the net config object.
+
+ NFVSwitch can only support one virtual switch per node,
+ using "nfvswitch" as its name. As long as the nfvswitch service
+ is running, the nfvswitch virtual switch will be available.
+ :param bridge: The NfvswitchBridge object to add.
+ """
+ self.nfvswitch_cpus = bridge.cpus
+
def add_bond(self, bond):
"""Add an OvsBond object to the net config object.
@@ -371,6 +427,18 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if bond.routes:
self._add_routes(bond.name, bond.routes)
+ def add_linux_team(self, team):
+ """Add a LinuxTeam object to the net config object.
+
+ :param team: The LinuxTeam object to add.
+ """
+ logger.info('adding linux team: %s' % team.name)
+ data = self._add_common(team)
+ logger.debug('team data: %s' % data)
+ self.linuxteam_data[team.name] = data
+ if team.routes:
+ self._add_routes(team.name, team.routes)
+
def add_ovs_tunnel(self, tunnel):
"""Add a OvsTunnel object to the net config object.
@@ -391,6 +459,23 @@ class IfcfgNetConfig(os_net_config.NetConfig):
logger.debug('ovs patch port data: %s' % data)
self.interface_data[ovs_patch_port.name] = data
+ def add_ib_interface(self, ib_interface):
+ """Add an InfiniBand interface object to the net config object.
+
+ :param ib_interface: The InfiniBand interface object to add.
+ """
+ logger.info('adding ib_interface: %s' % ib_interface.name)
+ data = self._add_common(ib_interface)
+ logger.debug('ib_interface data: %s' % data)
+ self.ib_interface_data[ib_interface.name] = data
+ if ib_interface.routes:
+ self._add_routes(ib_interface.name, ib_interface.routes)
+
+ if ib_interface.renamed:
+ logger.info("InfiniBand interface %s being renamed to %s"
+ % (ib_interface.hwname, ib_interface.name))
+ self.renamed_interfaces[ib_interface.hwname] = ib_interface.name
+
def generate_ivs_config(self, ivs_uplinks, ivs_interfaces):
"""Generate configuration content for ivs."""
@@ -411,6 +496,29 @@ class IfcfgNetConfig(os_net_config.NetConfig):
% (uplink_str, intf_str))
return data
+ def generate_nfvswitch_config(self, nfvswitch_ifaces,
+ nfvswitch_internal_ifaces):
+ """Generate configuration content for nfvswitch."""
+
+ cpu_str = ""
+ if self.nfvswitch_cpus:
+ cpu_str = " -c " + self.nfvswitch_cpus
+
+ ifaces = []
+ for iface in nfvswitch_ifaces:
+ ifaces.append(' -u ')
+ ifaces.append(iface)
+ iface_str = ''.join(ifaces)
+
+ ifaces = []
+ for iface in nfvswitch_internal_ifaces:
+ ifaces.append(' -m ')
+ ifaces.append(iface)
+ internal_str = ''.join(ifaces)
+
+ data = ("SETUP_ARGS=\"%s%s%s\"" % (cpu_str, iface_str, internal_str))
+ return data
+
def apply(self, cleanup=False, activate=True):
"""Apply the network configuration.
@@ -431,10 +539,13 @@ class IfcfgNetConfig(os_net_config.NetConfig):
restart_vlans = []
restart_bridges = []
restart_linux_bonds = []
+ restart_linux_teams = []
update_files = {}
all_file_names = []
ivs_uplinks = [] # ivs physical uplinks
ivs_interfaces = [] # ivs internal ports
+ nfvswitch_interfaces = [] # nfvswitch physical interfaces
+ nfvswitch_internal_ifaces = [] # nfvswitch internal/management ports
for interface_name, iface_data in self.interface_data.iteritems():
route_data = self.route_data.get(interface_name, '')
@@ -447,6 +558,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
all_file_names.append(route6_path)
if "IVS_BRIDGE" in iface_data:
ivs_uplinks.append(interface_name)
+ if "NFVSWITCH_BRIDGE" in iface_data:
+ nfvswitch_interfaces.append(interface_name)
all_file_names.append(route6_path)
if (utils.diff(interface_path, iface_data) or
utils.diff(route_path, route_data) or
@@ -481,6 +594,27 @@ 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():
+ 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)
+ route_path = self.root_dir + route_config_path(iface_name)
+ route6_path = self.root_dir + route6_config_path(iface_name)
+ all_file_names.append(iface_path)
+ all_file_names.append(route_path)
+ all_file_names.append(route6_path)
+ nfvswitch_internal_ifaces.append(iface_name)
+ if (utils.diff(iface_path, iface_data) or
+ utils.diff(route_path, route_data)):
+ restart_interfaces.append(iface_name)
+ restart_interfaces.extend(self.child_members(iface_name))
+ update_files[iface_path] = iface_data
+ update_files[route_path] = route_data
+ update_files[route6_path] = route6_data
+ else:
+ logger.info('No changes required for nfvswitch interface: %s' %
+ iface_name)
+
for vlan_name, vlan_data in self.vlan_data.iteritems():
route_data = self.route_data.get(vlan_name, '')
route6_data = self.route6_data.get(vlan_name, '')
@@ -541,6 +675,27 @@ class IfcfgNetConfig(os_net_config.NetConfig):
else:
logger.info('No changes required for bridge: %s' % bridge_name)
+ for team_name, team_data in self.linuxteam_data.iteritems():
+ route_data = self.route_data.get(team_name, '')
+ route6_data = self.route6_data.get(team_name, '')
+ team_path = self.root_dir + bridge_config_path(team_name)
+ team_route_path = self.root_dir + route_config_path(team_name)
+ team_route6_path = self.root_dir + route6_config_path(team_name)
+ all_file_names.append(team_path)
+ all_file_names.append(team_route_path)
+ all_file_names.append(team_route6_path)
+ if (utils.diff(team_path, team_data) or
+ utils.diff(team_route_path, route_data) or
+ utils.diff(team_route6_path, route6_data)):
+ restart_linux_teams.append(team_name)
+ restart_interfaces.extend(self.child_members(team_name))
+ update_files[team_path] = team_data
+ update_files[team_route_path] = route_data
+ update_files[team_route6_path] = route6_data
+ else:
+ logger.info('No changes required for linux team: %s' %
+ team_name)
+
for bond_name, bond_data in self.linuxbond_data.iteritems():
route_data = self.route_data.get(bond_name, '')
route6_data = self.route6_data.get(bond_name, '')
@@ -562,6 +717,32 @@ class IfcfgNetConfig(os_net_config.NetConfig):
logger.info('No changes required for linux bond: %s' %
bond_name)
+ # Infiniband interfaces are handled similarly to Ethernet interfaces
+ for interface_name, iface_data in self.ib_interface_data.iteritems():
+ 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)
+ route_path = self.root_dir + route_config_path(interface_name)
+ route6_path = self.root_dir + route6_config_path(interface_name)
+ all_file_names.append(interface_path)
+ all_file_names.append(route_path)
+ all_file_names.append(route6_path)
+ # TODO(dsneddon) determine if InfiniBand can be used with IVS
+ if "IVS_BRIDGE" in iface_data:
+ ivs_uplinks.append(interface_name)
+ all_file_names.append(route6_path)
+ if (utils.diff(interface_path, iface_data) or
+ utils.diff(route_path, route_data) or
+ utils.diff(route6_path, route6_data)):
+ restart_interfaces.append(interface_name)
+ restart_interfaces.extend(self.child_members(interface_name))
+ update_files[interface_path] = iface_data
+ update_files[route_path] = route_data
+ update_files[route6_path] = route6_data
+ else:
+ logger.info('No changes required for InfiniBand iface: %s' %
+ interface_name)
+
if cleanup:
for ifcfg_file in glob.iglob(cleanup_pattern()):
if ifcfg_file not in all_file_names:
@@ -582,6 +763,9 @@ class IfcfgNetConfig(os_net_config.NetConfig):
for linux_bond in restart_linux_bonds:
self.ifdown(linux_bond)
+ for linux_team in restart_linux_teams:
+ self.ifdown(linux_team)
+
for bridge in restart_bridges:
self.ifdown(bridge, iftype='bridge')
@@ -596,9 +780,15 @@ class IfcfgNetConfig(os_net_config.NetConfig):
data = self.generate_ivs_config(ivs_uplinks, ivs_interfaces)
self.write_config(location, data)
+ if nfvswitch_interfaces or nfvswitch_internal_ifaces:
+ location = nfvswitch_config_path()
+ data = self.generate_nfvswitch_config(nfvswitch_interfaces,
+ nfvswitch_internal_ifaces)
+ self.write_config(location, data)
+
if activate:
- for linux_bond in restart_linux_bonds:
- self.ifup(linux_bond)
+ for linux_team in restart_linux_teams:
+ self.ifup(linux_team)
for bridge in restart_bridges:
self.ifup(bridge, iftype='bridge')
@@ -606,6 +796,9 @@ class IfcfgNetConfig(os_net_config.NetConfig):
for interface in restart_interfaces:
self.ifup(interface)
+ for linux_bond in restart_linux_bonds:
+ self.ifup(linux_bond)
+
for bond in self.bond_primary_ifaces:
self.ovs_appctl('bond/set-active-slave', bond,
self.bond_primary_ifaces[bond])
@@ -623,6 +816,19 @@ class IfcfgNetConfig(os_net_config.NetConfig):
self.execute(msg, '/usr/bin/systemctl',
'restart', 'ivs')
+ if nfvswitch_interfaces or nfvswitch_internal_ifaces:
+ logger.info("Attach to nfvswitch with "
+ "interfaces: %s, "
+ "internal interfaces: %s" %
+ (nfvswitch_interfaces, nfvswitch_internal_ifaces))
+ for nfvswitch_interface in nfvswitch_interfaces:
+ self.ifup(nfvswitch_interface)
+ for nfvswitch_internal in nfvswitch_internal_ifaces:
+ self.ifup(nfvswitch_internal)
+ msg = "Restart nfvswitch"
+ self.execute(msg, '/usr/bin/systemctl',
+ 'restart', 'nfvswitch')
+
for vlan in restart_vlans:
self.ifup(vlan)
diff --git a/os_net_config/objects.py b/os_net_config/objects.py
index 9815832..9b5523f 100644
--- a/os_net_config/objects.py
+++ b/os_net_config/objects.py
@@ -23,7 +23,7 @@ from os_net_config import utils
logger = logging.getLogger(__name__)
-_NUMBERED_NICS = None
+_MAPPED_NICS = None
class InvalidConfigException(ValueError):
@@ -42,16 +42,24 @@ def object_from_json(json):
return OvsBond.from_json(json)
elif obj_type == "linux_bond":
return LinuxBond.from_json(json)
+ elif obj_type == "team":
+ return LinuxTeam.from_json(json)
elif obj_type == "linux_bridge":
return LinuxBridge.from_json(json)
elif obj_type == "ivs_bridge":
return IvsBridge.from_json(json)
elif obj_type == "ivs_interface":
return IvsInterface.from_json(json)
+ elif obj_type == "nfvswitch_bridge":
+ return NfvswitchBridge.from_json(json)
+ elif obj_type == "nfvswitch_internal":
+ return NfvswitchInternal.from_json(json)
elif obj_type == "ovs_tunnel":
return OvsTunnel.from_json(json)
elif obj_type == "ovs_patch_port":
return OvsPatchPort.from_json(json)
+ elif obj_type == "ib_interface":
+ return IbInterface.from_json(json)
def _get_required_field(json, name, object_name):
@@ -63,21 +71,17 @@ def _get_required_field(json, name, object_name):
return field
-def _numbered_nics(nic_mapping=None):
+def _mapped_nics(nic_mapping=None):
mapping = nic_mapping or {}
- global _NUMBERED_NICS
- if _NUMBERED_NICS:
- return _NUMBERED_NICS
- _NUMBERED_NICS = {}
- count = 0
+ global _MAPPED_NICS
+ if _MAPPED_NICS:
+ return _MAPPED_NICS
+ _MAPPED_NICS = {}
active_nics = utils.ordered_active_nics()
- for nic in active_nics:
- count += 1
- nic_alias = "nic%i" % count
- nic_mapped = mapping.get(nic_alias, nic)
-
- # The mapping is either invalid, or specifies a mac
+ for nic_alias, nic_mapped in mapping.items():
if nic_mapped not in active_nics:
+ # The mapping is either invalid, or specifies a mac
+ is_mapping_valid = False
for active in active_nics:
try:
active_mac = utils.interface_mac(active)
@@ -86,25 +90,39 @@ def _numbered_nics(nic_mapping=None):
if nic_mapped == active_mac:
logger.debug("%s matches device %s" % (nic_mapped, active))
nic_mapped = active
+ is_mapping_valid = True
break
- else:
+
+ if not is_mapping_valid:
# The mapping can't specify a non-active or non-existent nic
- logger.warning('interface %s is not in an active nic (%s)'
+ logger.warning('interface %s is not an active nic (%s)'
% (nic_mapped, ', '.join(active_nics)))
continue
# Duplicate mappings are not allowed
- if nic_mapped in _NUMBERED_NICS.values():
+ if nic_mapped in _MAPPED_NICS.values():
msg = ('interface %s already mapped, '
'check mapping file for duplicates'
% nic_mapped)
raise InvalidConfigException(msg)
- _NUMBERED_NICS[nic_alias] = nic_mapped
+ _MAPPED_NICS[nic_alias] = nic_mapped
logger.info("%s mapped to: %s" % (nic_alias, nic_mapped))
- if not _NUMBERED_NICS:
+
+ # Add default numbered mappings, but do not overwrite existing entries
+ for nic_mapped in set(active_nics).difference(set(_MAPPED_NICS.values())):
+ nic_alias = "nic%i" % (active_nics.index(nic_mapped) + 1)
+ if nic_alias in _MAPPED_NICS:
+ logger.warning("no mapping for interface %s because "
+ "%s is mapped to %s"
+ % (nic_mapped, nic_alias, _MAPPED_NICS[nic_alias]))
+ else:
+ _MAPPED_NICS[nic_alias] = nic_mapped
+ logger.info("%s mapped to: %s" % (nic_alias, nic_mapped))
+
+ if not _MAPPED_NICS:
logger.warning('No active nics found.')
- return _NUMBERED_NICS
+ return _MAPPED_NICS
class Route(object):
@@ -150,18 +168,18 @@ class _BaseOpts(object):
addresses = addresses or []
routes = routes or []
dns_servers = dns_servers or []
- numbered_nic_names = _numbered_nics(nic_mapping)
+ mapped_nic_names = _mapped_nics(nic_mapping)
self.hwaddr = None
self.hwname = None
self.renamed = False
- if name in numbered_nic_names:
+ if name in mapped_nic_names:
if persist_mapping:
self.name = name
- self.hwname = numbered_nic_names[name]
+ self.hwname = mapped_nic_names[name]
self.hwaddr = utils.interface_mac(self.hwname)
self.renamed = True
else:
- self.name = numbered_nic_names[name]
+ self.name = mapped_nic_names[name]
else:
self.name = name
@@ -177,7 +195,9 @@ class _BaseOpts(object):
self.bridge_name = None # internal
self.linux_bridge_name = None # internal
self.ivs_bridge_name = None # internal
+ self.nfvswitch_bridge_name = None # internal
self.linux_bond_name = None # internal
+ self.linux_team_name = None # internal
self.ovs_port = False # internal
self.primary_interface_name = None # internal
@@ -287,9 +307,9 @@ class Vlan(_BaseOpts):
dns_servers)
self.vlan_id = int(vlan_id)
- numbered_nic_names = _numbered_nics(nic_mapping)
- if device in numbered_nic_names:
- self.device = numbered_nic_names[device]
+ mapped_nic_names = _mapped_nics(nic_mapping)
+ if device in mapped_nic_names:
+ self.device = mapped_nic_names[device]
else:
self.device = device
@@ -328,6 +348,32 @@ class IvsInterface(_BaseOpts):
return IvsInterface(vlan_id, name, *opts)
+class NfvswitchInternal(_BaseOpts):
+ """Base class for nfvswitch internal interfaces."""
+
+ def __init__(self, vlan_id, name='nfvswitch', use_dhcp=False,
+ use_dhcpv6=False, addresses=None, routes=None, mtu=1500,
+ primary=False, nic_mapping=None, persist_mapping=False,
+ defroute=True, dhclient_args=None, dns_servers=None):
+ addresses = addresses or []
+ routes = routes or []
+ dns_servers = dns_servers or []
+ name_vlan = '%s%i' % (name, vlan_id)
+ super(NfvswitchInternal, self).__init__(name_vlan, use_dhcp,
+ use_dhcpv6, addresses, routes,
+ mtu, primary, nic_mapping,
+ persist_mapping, defroute,
+ dhclient_args, dns_servers)
+ self.vlan_id = int(vlan_id)
+
+ @staticmethod
+ def from_json(json):
+ name = json.get('name')
+ vlan_id = _get_required_field(json, 'vlan_id', 'NfvswitchInternal')
+ opts = _BaseOpts.base_opts_from_json(json)
+ return NfvswitchInternal(vlan_id, name, *opts)
+
+
class OvsBridge(_BaseOpts):
"""Base class for OVS bridges."""
@@ -505,6 +551,132 @@ class IvsBridge(_BaseOpts):
dns_servers=dns_servers)
+class NfvswitchBridge(_BaseOpts):
+ """Base class for NFVSwitch bridges.
+
+ NFVSwitch is a virtual switch for Linux.
+ It is compatible with the KVM hypervisor and uses DPDK for packet
+ forwarding.
+ """
+
+ def __init__(self, name='nfvswitch', use_dhcp=False, use_dhcpv6=False,
+ addresses=None, routes=None, mtu=1500, members=None,
+ nic_mapping=None, persist_mapping=False, defroute=True,
+ dhclient_args=None, dns_servers=None, cpus=""):
+ addresses = addresses or []
+ routes = routes or []
+ members = members or []
+ dns_servers = dns_servers or []
+ super(NfvswitchBridge, self).__init__(name, use_dhcp, use_dhcpv6,
+ addresses, routes, mtu, False,
+ nic_mapping, persist_mapping,
+ defroute, dhclient_args,
+ dns_servers)
+ self.cpus = cpus
+ self.members = members
+ for member in self.members:
+ if isinstance(member, OvsBond) or isinstance(member, LinuxBond):
+ msg = 'NFVSwitch does not support bond interfaces.'
+ raise InvalidConfigException(msg)
+ member.nfvswitch_bridge_name = name
+ member.ovs_port = False
+ self.primary_interface_name = None
+
+ @staticmethod
+ def from_json(json):
+ name = 'nfvswitch'
+ (use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
+ persist_mapping, defroute, dhclient_args,
+ dns_servers) = _BaseOpts.base_opts_from_json(
+ json, include_primary=False)
+
+ # members
+ members = []
+ members_json = json.get('members')
+ if members_json:
+ if isinstance(members_json, list):
+ for member in members_json:
+ members.append(object_from_json(member))
+ else:
+ msg = 'Members must be a list.'
+ raise InvalidConfigException(msg)
+
+ cpus = ''
+ cpus_json = json.get('cpus')
+ if cpus_json:
+ if isinstance(cpus_json, basestring):
+ cpus = cpus_json
+ else:
+ msg = '"cpus" must be a string of numbers separated by commas.'
+ raise InvalidConfigException(msg)
+ else:
+ msg = 'Config "cpus" is mandatory.'
+ raise InvalidConfigException(msg)
+
+ return NfvswitchBridge(name, use_dhcp=use_dhcp, use_dhcpv6=use_dhcpv6,
+ addresses=addresses, routes=routes, mtu=mtu,
+ members=members, nic_mapping=nic_mapping,
+ persist_mapping=persist_mapping,
+ defroute=defroute, dhclient_args=dhclient_args,
+ dns_servers=dns_servers, cpus=cpus)
+
+
+class LinuxTeam(_BaseOpts):
+ """Base class for Linux bonds using teamd."""
+
+ def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
+ routes=None, mtu=None, primary=False, members=None,
+ bonding_options=None, nic_mapping=None, persist_mapping=False,
+ defroute=True, dhclient_args=None, dns_servers=None):
+ addresses = addresses or []
+ routes = routes or []
+ members = members or []
+ dns_servers = dns_servers or []
+ super(LinuxTeam, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
+ routes, mtu, primary, nic_mapping,
+ persist_mapping, defroute,
+ dhclient_args, dns_servers)
+ self.members = members
+ self.bonding_options = bonding_options
+ for member in self.members:
+ member.linux_team_name = name
+ if member.primary:
+ if self.primary_interface_name:
+ msg = 'Only one primary interface allowed per team.'
+ raise InvalidConfigException(msg)
+ if member.primary_interface_name:
+ self.primary_interface_name = member.primary_interface_name
+ else:
+ self.primary_interface_name = member.name
+
+ @staticmethod
+ def from_json(json):
+ name = _get_required_field(json, 'name', 'LinuxTeam')
+ (use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
+ persist_mapping, defroute, dhclient_args,
+ dns_servers) = _BaseOpts.base_opts_from_json(
+ json, include_primary=False)
+ bonding_options = json.get('bonding_options')
+ members = []
+
+ # members
+ members_json = json.get('members')
+ if members_json:
+ if isinstance(members_json, list):
+ for member in members_json:
+ members.append(object_from_json(member))
+ else:
+ msg = 'Members must be a list.'
+ raise InvalidConfigException(msg)
+
+ return LinuxTeam(name, use_dhcp=use_dhcp, use_dhcpv6=use_dhcpv6,
+ addresses=addresses, routes=routes, mtu=mtu,
+ members=members, bonding_options=bonding_options,
+ nic_mapping=nic_mapping,
+ persist_mapping=persist_mapping, defroute=defroute,
+ dhclient_args=dhclient_args, dns_servers=dns_servers)
+
+
class LinuxBond(_BaseOpts):
"""Base class for Linux bonds."""
@@ -689,3 +861,25 @@ class OvsPatchPort(_BaseOpts):
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)
+
+
+class IbInterface(_BaseOpts):
+ """Base class for InfiniBand network interfaces."""
+
+ 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):
+ addresses = addresses or []
+ routes = routes or []
+ dns_servers = dns_servers or []
+ super(IbInterface, self).__init__(name, use_dhcp, use_dhcpv6,
+ addresses, routes, mtu, primary,
+ nic_mapping, persist_mapping,
+ defroute, dhclient_args, dns_servers)
+
+ @staticmethod
+ def from_json(json):
+ name = _get_required_field(json, 'name', 'IbInterface')
+ opts = _BaseOpts.base_opts_from_json(json)
+ return IbInterface(name, *opts)
diff --git a/os_net_config/tests/base.py b/os_net_config/tests/base.py
index d5adc66..55e0b61 100644
--- a/os_net_config/tests/base.py
+++ b/os_net_config/tests/base.py
@@ -29,19 +29,19 @@ _TRUE_VALUES = ('True', 'true', '1', 'yes')
class TestCase(testtools.TestCase):
"""Test case base class for all unit tests."""
- stub_numbered_nics = True
+ stub_mapped_nics = True
def setUp(self):
"""Run before each test method to initialize test environment."""
super(TestCase, self).setUp()
self.stubs = stubout.StubOutForTesting()
- self.stubbed_numbered_nics = {}
+ self.stubbed_mapped_nics = {}
- def dummy_numbered_nics(nic_mapping=None):
- return self.stubbed_numbered_nics
- if self.stub_numbered_nics:
- self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics)
+ def dummy_mapped_nics(nic_mapping=None):
+ return self.stubbed_mapped_nics
+ if self.stub_mapped_nics:
+ self.stubs.Set(objects, '_mapped_nics', dummy_mapped_nics)
test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
try:
diff --git a/os_net_config/tests/test_cli.py b/os_net_config/tests/test_cli.py
index 98477fc..e598dcb 100644
--- a/os_net_config/tests/test_cli.py
+++ b/os_net_config/tests/test_cli.py
@@ -146,3 +146,21 @@ class TestCli(base.TestCase):
stdout_yaml, stderr = self.run_cli('ARG0 --provider=ifcfg --noop '
'-c %s --detailed-exit-codes'
% interface_yaml, exitcodes=(0,))
+
+ def test_nfvswitch_noop_output(self):
+ nfvswitch_yaml = os.path.join(SAMPLE_BASE, 'nfvswitch.yaml')
+ nfvswitch_json = os.path.join(SAMPLE_BASE, 'nfvswitch.json')
+ stdout_yaml, stderr = self.run_cli('ARG0 --provider=ifcfg --noop '
+ '-c %s' % nfvswitch_yaml)
+ self.assertEqual('', stderr)
+ stdout_json, stderr = self.run_cli('ARG0 --provider=ifcfg --noop '
+ '-c %s' % nfvswitch_json)
+ self.assertEqual('', stderr)
+ sanity_devices = ['DEVICE=nic2',
+ 'DEVICE=nic3',
+ 'DEVICE=api201',
+ 'DEVICE=storage202',
+ 'DEVICETYPE=nfvswitch']
+ for dev in sanity_devices:
+ self.assertIn(dev, stdout_yaml)
+ self.assertEqual(stdout_yaml, stdout_json)
diff --git a/os_net_config/tests/test_impl_ifcfg.py b/os_net_config/tests/test_impl_ifcfg.py
index 71ad9ae..9b24717 100644
--- a/os_net_config/tests/test_impl_ifcfg.py
+++ b/os_net_config/tests/test_impl_ifcfg.py
@@ -60,12 +60,33 @@ BOOTPROTO=none
_V4_IFCFG_MAPPED = _V4_IFCFG.replace('em1', 'nic1') + "HWADDR=a1:b2:c3:d4:e5\n"
+
+_BASE_IB_IFCFG = """# This file is autogenerated by os-net-config
+DEVICE=ib0
+ONBOOT=yes
+HOTPLUG=no
+NM_CONTROLLED=no
+PEERDNS=no
+TYPE=Infiniband
+"""
+
+_V4_IB_IFCFG = _BASE_IB_IFCFG + """BOOTPROTO=static
+IPADDR=192.168.1.2
+NETMASK=255.255.255.0
+"""
+
_V4_IFCFG_MULTIPLE = _V4_IFCFG + """IPADDR1=192.168.1.3
NETMASK1=255.255.255.255
IPADDR2=10.0.0.2
NETMASK2=255.0.0.0
"""
+_IB_V4_IFCFG_MULTIPLE = _V4_IB_IFCFG + """IPADDR1=192.168.1.3
+NETMASK1=255.255.255.255
+IPADDR2=10.0.0.2
+NETMASK2=255.0.0.0
+"""
+
_V6_IFCFG = _BASE_IFCFG + """IPV6INIT=yes
IPV6_AUTOCONF=no
IPV6ADDR=2001:abc:a::/64
@@ -223,11 +244,24 @@ BOOTPROTO=dhcp
"""
+_LINUX_TEAM_DHCP = """# This file is autogenerated by os-net-config
+DEVICE=team0
+ONBOOT=yes
+HOTPLUG=no
+NM_CONTROLLED=no
+BOOTPROTO=dhcp
+DEVICETYPE=Team
+"""
+
+
_LINUX_BOND_INTERFACE = _BASE_IFCFG + """MASTER=bond0
SLAVE=yes
BOOTPROTO=none
"""
+_LINUX_TEAM_INTERFACE = _BASE_IFCFG + """TEAM_MASTER=team0
+BOOTPROTO=none
+"""
_IVS_UPLINK = """# This file is autogenerated by os-net-config
DEVICE=em1
@@ -258,6 +292,34 @@ NETMASK=255.255.255.0
_IVS_CONFIG = ('DAEMON_ARGS=\"--hitless --certificate /etc/ivs '
'--inband-vlan 4092 -u em1 --internal-port=storage5\"')
+_NFVSWITCH_INTERFACE = """# This file is autogenerated by os-net-config
+DEVICE=em1
+ONBOOT=yes
+HOTPLUG=no
+NM_CONTROLLED=no
+PEERDNS=no
+DEVICETYPE=nfvswitch
+NFVSWITCH_BRIDGE=nfvswitch
+BOOTPROTO=none
+"""
+
+_NFVSWITCH_INTERNAL = """# This file is autogenerated by os-net-config
+DEVICE=storage5
+ONBOOT=yes
+HOTPLUG=no
+NM_CONTROLLED=no
+PEERDNS=no
+TYPE=NFVSWITCHIntPort
+DEVICETYPE=nfvswitch
+NFVSWITCH_BRIDGE=nfvswitch
+MTU=1500
+BOOTPROTO=static
+IPADDR=172.16.2.7
+NETMASK=255.255.255.0
+"""
+
+_NFVSWITCH_CONFIG = ('SETUP_ARGS=\" -c 2,3,4,5 -u em1 -m storage5\"')
+
_OVS_IFCFG_PATCH_PORT = """# This file is autogenerated by os-net-config
DEVICE=br-pub-patch
ONBOOT=yes
@@ -270,6 +332,17 @@ OVS_BRIDGE=br-ex
OVS_PATCH_PEER=br-ex-patch
"""
+_LINUX_TEAM_PRIMARY_IFACE = """# This file is autogenerated by os-net-config
+DEVICE=em1
+ONBOOT=yes
+HOTPLUG=no
+NM_CONTROLLED=no
+PEERDNS=no
+TEAM_MASTER=team1
+TEAM_PORT_CONFIG='{"prio": 100}'
+BOOTPROTO=none
+"""
+
class TestIfcfgNetConfig(base.TestCase):
@@ -290,6 +363,9 @@ class TestIfcfgNetConfig(base.TestCase):
def get_linux_bond_config(self, name='bond0'):
return self.provider.linuxbond_data[name]
+ def get_linux_team_config(self, name='team0'):
+ return self.provider.linuxteam_data[name]
+
def get_route_config(self, name='em1'):
return self.provider.route_data.get(name, '')
@@ -353,7 +429,7 @@ class TestIfcfgNetConfig(base.TestCase):
self.stubs.Set(utils, 'interface_mac', test_interface_mac)
nic_mapping = {'nic1': 'em1'}
- self.stubbed_numbered_nics = nic_mapping
+ self.stubbed_mapped_nics = nic_mapping
v4_addr = objects.Address('192.168.1.2/24')
interface = objects.Interface('nic1', addresses=[v4_addr],
nic_mapping=nic_mapping,
@@ -505,6 +581,35 @@ class TestIfcfgNetConfig(base.TestCase):
data = self.provider.generate_ivs_config(['em1'], ['storage5'])
self.assertEqual(_IVS_CONFIG, data)
+ def test_network_nfvswitch_with_interfaces_and_internal_interfaces(self):
+ interface = objects.Interface('em1')
+ v4_addr = objects.Address('172.16.2.7/24')
+ nfvswitch_internal = objects.NfvswitchInternal(vlan_id=5,
+ name='storage',
+ addresses=[v4_addr])
+ iface_name = nfvswitch_internal.name
+ bridge = objects.NfvswitchBridge(members=[interface,
+ nfvswitch_internal],
+ cpus="2,3,4,5")
+ self.provider.add_interface(interface)
+ self.provider.add_nfvswitch_internal(nfvswitch_internal)
+ self.provider.add_nfvswitch_bridge(bridge)
+ self.assertEqual(_NFVSWITCH_INTERFACE, self.get_interface_config())
+ self.assertEqual(_NFVSWITCH_INTERNAL,
+ self.provider.nfvswitch_intiface_data[iface_name])
+ data = self.provider.generate_nfvswitch_config(['em1'], ['storage5'])
+ self.assertEqual(_NFVSWITCH_CONFIG, data)
+
+ def test_add_ib_interface_with_v4_multiple(self):
+ addresses = [objects.Address('192.168.1.2/24'),
+ objects.Address('192.168.1.3/32'),
+ objects.Address('10.0.0.2/8')]
+ ib_interface = objects.IbInterface('ib0', addresses=addresses)
+ self.provider.add_interface(ib_interface)
+ self.assertEqual(_IB_V4_IFCFG_MULTIPLE,
+ self.get_interface_config('ib0'))
+ self.assertEqual('', self.get_route_config())
+
def test_add_vlan(self):
vlan = objects.Vlan('em1', 5)
self.provider.add_vlan(vlan)
@@ -573,6 +678,19 @@ BOOTPROTO=none
self.assertEqual(_LINUX_BOND_INTERFACE,
self.get_interface_config('em1'))
+ def test_linux_team(self):
+ interface1 = objects.Interface('em1')
+ interface2 = objects.Interface('em2')
+ team = objects.LinuxTeam('team0', use_dhcp=True,
+ members=[interface1, interface2])
+ self.provider.add_linux_team(team)
+ self.provider.add_interface(interface1)
+ self.provider.add_interface(interface2)
+ self.assertEqual(_LINUX_TEAM_DHCP,
+ self.get_linux_team_config('team0'))
+ self.assertEqual(_LINUX_TEAM_INTERFACE,
+ self.get_interface_config('em1'))
+
def test_interface_defroute(self):
interface1 = objects.Interface('em1')
interface2 = objects.Interface('em2', defroute=False)
@@ -775,6 +893,26 @@ class TestIfcfgNetConfigApply(base.TestCase):
self.provider.add_interface(interface)
self.provider.add_bridge(bridge)
self.provider.apply()
+ self.assertIn('em1', self.ifup_interface_names)
+
+ # test infiniband interfaces act as proper bridge members
+ ib_interface = objects.IbInterface('ib0')
+ bridge = objects.OvsBridge('br-ctlplane', use_dhcp=True,
+ members=[ib_interface])
+ self.provider.add_interface(ib_interface)
+ self.provider.add_bridge(bridge)
+ self.provider.apply()
+ self.assertIn('ib0', self.ifup_interface_names)
+ self.assertIn('br-ctlplane', self.ifup_interface_names)
+
+ # changing the bridge should restart the interface too
+ self.ifup_interface_names = []
+ bridge = objects.OvsBridge('br-ctlplane', use_dhcp=False,
+ members=[ib_interface])
+ self.provider.add_interface(interface)
+ self.provider.add_bridge(bridge)
+ self.provider.apply()
+ self.assertIn('ib0', self.ifup_interface_names)
# setup and apply a bond on a bridge
self.ifup_interface_names = []
diff --git a/os_net_config/tests/test_objects.py b/os_net_config/tests/test_objects.py
index 7d8d3b3..7e8441c 100644
--- a/os_net_config/tests/test_objects.py
+++ b/os_net_config/tests/test_objects.py
@@ -131,9 +131,9 @@ class TestInterface(base.TestCase):
self.assertEqual(["1.2.3.4"], interface1.dns_servers)
def test_from_json_dhcp_nic1(self):
- def dummy_numbered_nics(nic_mapping=None):
+ def dummy_mapped_nics(nic_mapping=None):
return {"nic1": "em3"}
- self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics)
+ self.stubs.Set(objects, '_mapped_nics', dummy_mapped_nics)
data = '{"type": "interface", "name": "nic1", "use_dhcp": true}'
interface = objects.object_from_json(json.loads(data))
@@ -179,9 +179,9 @@ class TestVlan(base.TestCase):
self.assertTrue(vlan.use_dhcp)
def test_from_json_dhcp_nic1(self):
- def dummy_numbered_nics(nic_mapping=None):
+ def dummy_mapped_nics(nic_mapping=None):
return {"nic1": "em4"}
- self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics)
+ self.stubs.Set(objects, '_mapped_nics', dummy_mapped_nics)
data = '{"type": "vlan", "device": "nic1", "vlan_id": 16,' \
'"use_dhcp": true}'
@@ -213,9 +213,9 @@ class TestBridge(base.TestCase):
self.assertEqual("br-foo", interface1.bridge_name)
def test_from_json_dhcp_with_nic1(self):
- def dummy_numbered_nics(nic_mapping=None):
+ def dummy_mapped_nics(nic_mapping=None):
return {"nic1": "em5"}
- self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics)
+ self.stubs.Set(objects, '_mapped_nics', dummy_mapped_nics)
data = """{
"type": "ovs_bridge",
@@ -289,9 +289,9 @@ class TestLinuxBridge(base.TestCase):
self.assertEqual("br-foo", interface1.linux_bridge_name)
def test_from_json_dhcp_with_nic1(self):
- def dummy_numbered_nics(nic_mapping=None):
+ def dummy_mapped_nics(nic_mapping=None):
return {"nic1": "em5"}
- self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics)
+ self.stubs.Set(objects, '_mapped_nics', dummy_mapped_nics)
data = """{
"type": "linux_bridge",
@@ -398,6 +398,87 @@ class TestIvsBridge(base.TestCase):
self.assertIn(expected, err)
+class TestNfvswitchBridge(base.TestCase):
+
+ def test_from_json(self):
+ data = """{
+"type": "nfvswitch_bridge",
+"cpus": "2,3,4,5",
+"members": [
+ {"type": "interface", "name": "nic2"}
+ ]
+}
+"""
+ bridge = objects.object_from_json(json.loads(data))
+ self.assertEqual("nfvswitch", bridge.name)
+ self.assertEqual("2,3,4,5", bridge.cpus)
+ interface1 = bridge.members[0]
+ self.assertEqual("nic2", interface1.name)
+ self.assertEqual(False, interface1.ovs_port)
+ self.assertEqual("nfvswitch", interface1.nfvswitch_bridge_name)
+
+
+class TestNfvswitchInterface(base.TestCase):
+
+ def test_interface_from_json(self):
+ data = """{
+"type": "nfvswitch_bridge",
+"cpus": "2,3,4,5",
+"members": [
+ {"type": "interface","name": "nic1"},
+ {"type": "interface","name": "nic2"}
+ ]
+}
+"""
+ bridge = objects.object_from_json(json.loads(data))
+ self.assertEqual("nfvswitch", bridge.name)
+ self.assertEqual("2,3,4,5", bridge.cpus)
+ interface1 = bridge.members[0]
+ self.assertEqual("nic1", interface1.name)
+ interface2 = bridge.members[1]
+ self.assertEqual("nic2", interface2.name)
+ self.assertEqual(False, interface2.ovs_port)
+ self.assertEqual("nfvswitch", interface1.nfvswitch_bridge_name)
+
+ def test_nfvswitch_internal_from_json(self):
+ data = """{
+"type": "nfvswitch_bridge",
+"cpus": "2,3,4,5",
+"members": [
+ {"type": "nfvswitch_internal", "name": "storage", "vlan_id": 202},
+ {"type": "nfvswitch_internal", "name": "api", "vlan_id": 201}
+ ]
+}
+"""
+ bridge = objects.object_from_json(json.loads(data))
+ self.assertEqual("nfvswitch", bridge.name)
+ self.assertEqual("2,3,4,5", bridge.cpus)
+ interface1 = bridge.members[0]
+ self.assertEqual("storage202", interface1.name)
+ interface2 = bridge.members[1]
+ self.assertEqual("api201", interface2.name)
+ self.assertEqual(False, interface1.ovs_port)
+ self.assertEqual("nfvswitch", interface1.nfvswitch_bridge_name)
+
+ def test_bond_interface_from_json(self):
+ data = """{
+"type": "nfvswitch_bridge",
+"cpus": "2,3,4,5",
+"members": [{
+ "type": "linux_bond", "name": "bond1", "members":
+ [{"type": "interface", "name": "nic2"},
+ {"type": "interface", "name": "nic3"}]
+ }
+ ]
+}
+"""
+ err = self.assertRaises(objects.InvalidConfigException,
+ objects.NfvswitchBridge.from_json,
+ json.loads(data))
+ expected = 'NFVSwitch does not support bond interfaces.'
+ self.assertIn(expected, err)
+
+
class TestBond(base.TestCase):
def test_from_json_dhcp(self):
@@ -427,9 +508,9 @@ class TestBond(base.TestCase):
def test_from_json_dhcp_with_nic1_nic2(self):
- def dummy_numbered_nics(nic_mapping=None):
+ def dummy_mapped_nics(nic_mapping=None):
return {"nic1": "em1", "nic2": "em2"}
- self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics)
+ self.stubs.Set(objects, '_mapped_nics', dummy_mapped_nics)
data = """{
"type": "ovs_bond",
@@ -456,6 +537,28 @@ class TestBond(base.TestCase):
self.assertEqual("em2", interface2.name)
+class TestLinuxTeam(base.TestCase):
+
+ def test_from_json_dhcp(self):
+ data = """{
+"type": "team",
+"name": "team1",
+"use_dhcp": true,
+"members": [
+ { "type": "interface", "name": "em1", "primary": true },
+ { "type": "interface", "name": "em2" }
+]
+}
+"""
+ team = objects.object_from_json(json.loads(data))
+ self.assertEqual("team1", team.name)
+ self.assertTrue(team.use_dhcp)
+ interface1 = team.members[0]
+ self.assertEqual("em1", interface1.name)
+ interface2 = team.members[1]
+ self.assertEqual("em2", interface2.name)
+
+
class TestLinuxBond(base.TestCase):
def test_from_json_dhcp(self):
@@ -485,9 +588,9 @@ class TestLinuxBond(base.TestCase):
def test_from_json_dhcp_with_nic1_nic2(self):
- def dummy_numbered_nics(nic_mapping=None):
+ def dummy_mapped_nics(nic_mapping=None):
return {"nic1": "em1", "nic2": "em2"}
- self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics)
+ self.stubs.Set(objects, '_mapped_nics', dummy_mapped_nics)
data = """{
"type": "ovs_bond",
@@ -564,52 +667,154 @@ class TestOvsPatchPort(base.TestCase):
self.assertEqual("br-ex-patch", patch_port.peer)
-class TestNumberedNicsMapping(base.TestCase):
+class TestIbInterface(base.TestCase):
+
+ def test_ib_interface_addresses(self):
+ v4_addr = objects.Address('192.168.1.1/24')
+ v6_addr = objects.Address('2001:abc:a::/64')
+ ib_interface = objects.IbInterface('foo', addresses=[v4_addr, v6_addr])
+ self.assertEqual("192.168.1.1", ib_interface.v4_addresses()[0].ip)
+ self.assertEqual("2001:abc:a::", ib_interface.v6_addresses()[0].ip)
+
+ def test_from_json_dhcp(self):
+ data = '{"type": "ib_interface", "name": "ib0", "use_dhcp": true}'
+ ib_interface = objects.object_from_json(json.loads(data))
+ self.assertEqual("ib0", ib_interface.name)
+ self.assertTrue(ib_interface.use_dhcp)
+
+ def test_from_json_defroute(self):
+ data = '{"type": "ib_interface", "name": "ib0", "use_dhcp": true}'
+ ib_interface1 = objects.object_from_json(json.loads(data))
+ data = """{
+"type": "ib_interface",
+"name": "ib0",
+"use_dhcp": true,
+"defroute": false
+}
+"""
+ ib_interface2 = objects.object_from_json(json.loads(data))
+ self.assertTrue(ib_interface1.defroute)
+ self.assertFalse(ib_interface2.defroute)
+
+ def test_from_json_dhclient_args(self):
+ data = """{
+"type": "ib_interface",
+"name": "ib0",
+"use_dhcp": true,
+"dhclient_args": "--foobar"
+}
+"""
+ ib_interface1 = objects.object_from_json(json.loads(data))
+ self.assertEqual("--foobar", ib_interface1.dhclient_args)
+
+ def test_from_json_dns_servers(self):
+ data = """{
+"type": "ib_interface",
+"name": "ib0",
+"use_dhcp": true,
+"dns_servers": ["1.2.3.4"]
+}
+"""
+ ib_interface1 = objects.object_from_json(json.loads(data))
+ self.assertEqual(["1.2.3.4"], ib_interface1.dns_servers)
+
+ def test_from_json_dhcp_nic1(self):
+ def dummy_mapped_nics(nic_mapping=None):
+ return {"nic1": "ib0"}
+ self.stubs.Set(objects, '_mapped_nics', dummy_mapped_nics)
+
+ data = '{"type": "ib_interface", "name": "nic1", "use_dhcp": true}'
+ ib_interface = objects.object_from_json(json.loads(data))
+ self.assertEqual("ib0", ib_interface.name)
+ self.assertTrue(ib_interface.use_dhcp)
+
+ def test_from_json_with_addresses(self):
+ data = """{
+"type": "ib_interface",
+"name": "ib0",
+"use_dhcp": false,
+"mtu": 1501,
+"addresses": [{
+ "ip_netmask": "192.0.2.1/24"
+}],
+"routes": [{
+ "next_hop": "192.0.2.1",
+ "ip_netmask": "192.0.2.1/24"
+}]
+}
+"""
+ ib_interface = objects.object_from_json(json.loads(data))
+ self.assertEqual("ib0", ib_interface.name)
+ self.assertFalse(ib_interface.use_dhcp)
+ self.assertFalse(ib_interface.use_dhcpv6)
+ self.assertEqual(1501, ib_interface.mtu)
+ address1 = ib_interface.v4_addresses()[0]
+ self.assertEqual("192.0.2.1", address1.ip)
+ self.assertEqual("255.255.255.0", address1.netmask)
+ route1 = ib_interface.routes[0]
+ self.assertEqual("192.0.2.1", route1.next_hop)
+ self.assertEqual("192.0.2.1/24", route1.ip_netmask)
+
+
+class TestNicMapping(base.TestCase):
# We want to test the function, not the dummy..
- stub_numbered_nics = False
+ stub_mapped_nics = False
def tearDown(self):
- super(TestNumberedNicsMapping, self).tearDown()
- objects._NUMBERED_NICS = None
+ super(TestNicMapping, self).tearDown()
+ objects._MAPPED_NICS = None
def _stub_active_nics(self, nics):
def dummy_ordered_active_nics():
return nics
self.stubs.Set(utils, 'ordered_active_nics', dummy_ordered_active_nics)
- def test_numbered_nics_default(self):
+ def test_mapped_nics_default(self):
self._stub_active_nics(['em1', 'em2'])
expected = {'nic1': 'em1', 'nic2': 'em2'}
- self.assertEqual(expected, objects._numbered_nics())
+ self.assertEqual(expected, objects._mapped_nics())
- def test_numbered_nics_mapped(self):
+ def test_mapped_nics_mapped(self):
self._stub_active_nics(['em1', 'em2'])
mapping = {'nic1': 'em2', 'nic2': 'em1'}
expected = {'nic1': 'em2', 'nic2': 'em1'}
- self.assertEqual(expected, objects._numbered_nics(nic_mapping=mapping))
+ self.assertEqual(expected, objects._mapped_nics(nic_mapping=mapping))
- def test_numbered_nics_mapped_partial(self):
+ def test_mapped_nics_mapped_partial(self):
self._stub_active_nics(['em1', 'em2', 'em3', 'em4'])
mapping = {'nic1': 'em2', 'nic2': 'em1'}
expected = {'nic1': 'em2', 'nic2': 'em1', 'nic3': 'em3', 'nic4': 'em4'}
- self.assertEqual(expected, objects._numbered_nics(nic_mapping=mapping))
+ self.assertEqual(expected, objects._mapped_nics(nic_mapping=mapping))
+
+ def test_mapped_nics_mapped_partial_reordered(self):
+ self._stub_active_nics(['em1', 'em2', 'em3', 'em4'])
+ mapping = {'nic1': 'em1', 'nic2': 'em3'}
+ expected = {'nic1': 'em1', 'nic2': 'em3', 'nic4': 'em4'}
+ self.assertEqual(expected, objects._mapped_nics(nic_mapping=mapping))
+
+ def test_mapped_nics_mapped_unnumbered(self):
+ self._stub_active_nics(['em1', 'em2', 'em3', 'em4'])
+ mapping = {'John': 'em1', 'Paul': 'em2', 'George': 'em3'}
+ expected = {'John': 'em1', 'Paul': 'em2', 'George': 'em3',
+ 'nic4': 'em4'}
+ self.assertEqual(expected, objects._mapped_nics(nic_mapping=mapping))
- def test_numbered_nics_map_error_notactive(self):
+ def test_mapped_nics_map_error_notactive(self):
self._stub_active_nics(['em1', 'em2'])
mapping = {'nic1': 'em3', 'nic2': 'em1'}
expected = {'nic2': 'em1'}
- self.assertEqual(expected, objects._numbered_nics(nic_mapping=mapping))
+ self.assertEqual(expected, objects._mapped_nics(nic_mapping=mapping))
- def test_numbered_nics_map_error_duplicate(self):
+ def test_mapped_nics_map_error_duplicate(self):
self._stub_active_nics(['em1', 'em2'])
mapping = {'nic1': 'em1', 'nic2': 'em1'}
err = self.assertRaises(objects.InvalidConfigException,
- objects._numbered_nics, nic_mapping=mapping)
+ objects._mapped_nics, nic_mapping=mapping)
expected = 'em1 already mapped, check mapping file for duplicates'
self.assertIn(expected, six.text_type(err))
- def test_numbered_nics_map_mac(self):
+ def test_mapped_nics_map_mac(self):
def dummy_interface_mac(name):
mac_map = {'em1': '12:34:56:78:9a:bc',
'em2': '12:34:56:de:f0:12'}
@@ -618,10 +823,10 @@ class TestNumberedNicsMapping(base.TestCase):
self._stub_active_nics(['em1', 'em2'])
mapping = {'nic1': '12:34:56:de:f0:12', 'nic2': '12:34:56:78:9a:bc'}
expected = {'nic1': 'em2', 'nic2': 'em1'}
- self.assertEqual(expected, objects._numbered_nics(nic_mapping=mapping))
+ self.assertEqual(expected, objects._mapped_nics(nic_mapping=mapping))
- def test_numbered_nics_no_active(self):
+ def test_mapped_nics_no_active(self):
self._stub_active_nics([])
expected = {}
# This only emits a warning, so it should still work
- self.assertEqual(expected, objects._numbered_nics())
+ self.assertEqual(expected, objects._mapped_nics())