aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.coveragerc2
-rw-r--r--etc/os-net-config/samples/linux_bond.yaml13
-rw-r--r--etc/os-net-config/samples/linux_bridge.yaml9
-rw-r--r--os_net_config/__init__.py24
-rw-r--r--os_net_config/cli.py11
-rw-r--r--os_net_config/impl_ifcfg.py96
-rw-r--r--os_net_config/objects.py197
-rw-r--r--os_net_config/tests/test_cli.py25
-rw-r--r--os_net_config/tests/test_impl_ifcfg.py99
-rw-r--r--os_net_config/tests/test_objects.py145
10 files changed, 591 insertions, 30 deletions
diff --git a/.coveragerc b/.coveragerc
index 13fd272..96a0e8d 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -4,4 +4,4 @@ source = os_net_config
omit = os_net_config/tests/*,os_net_config/openstack/*
[report]
-ignore-errors = True \ No newline at end of file
+ignore_errors = True
diff --git a/etc/os-net-config/samples/linux_bond.yaml b/etc/os-net-config/samples/linux_bond.yaml
new file mode 100644
index 0000000..566c747
--- /dev/null
+++ b/etc/os-net-config/samples/linux_bond.yaml
@@ -0,0 +1,13 @@
+network_config:
+ -
+ type: linux_bond
+ name: bond1
+ use_dhcp: true
+ bonding_options: "mode=active-backup"
+ members:
+ -
+ type: interface
+ name: em1
+ -
+ type: interface
+ name: em2
diff --git a/etc/os-net-config/samples/linux_bridge.yaml b/etc/os-net-config/samples/linux_bridge.yaml
new file mode 100644
index 0000000..051f6f3
--- /dev/null
+++ b/etc/os-net-config/samples/linux_bridge.yaml
@@ -0,0 +1,9 @@
+network_config:
+ -
+ type: linux_bridge
+ name: br-ctlplane
+ use_dhcp: true
+ members:
+ -
+ type: interface
+ name: em1
diff --git a/os_net_config/__init__.py b/os_net_config/__init__.py
index 641ee3b..42131bb 100644
--- a/os_net_config/__init__.py
+++ b/os_net_config/__init__.py
@@ -31,7 +31,7 @@ class NotImplemented(Exception):
class NetConfig(object):
- """Configure network interfaces using the ifcfg format."""
+ """Common network config methods class."""
def __init__(self, noop=False, root_dir=''):
self.noop = noop
@@ -52,10 +52,18 @@ class NetConfig(object):
self.add_bridge(obj)
for member in obj.members:
self.add_object(member)
+ elif isinstance(obj, objects.LinuxBridge):
+ self.add_linux_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:
self.add_object(member)
+ elif isinstance(obj, objects.LinuxBond):
+ self.add_linux_bond(obj)
+ for member in obj.members:
+ self.add_object(member)
def add_interface(self, interface):
"""Add an Interface object to the net config object.
@@ -78,6 +86,13 @@ class NetConfig(object):
"""
raise NotImplemented("add_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.")
+
def add_bond(self, bond):
"""Add an OvsBond object to the net config object.
@@ -85,6 +100,13 @@ class NetConfig(object):
"""
raise NotImplemented("add_bond is not implemented.")
+ def add_linux_bond(self, bond):
+ """Add a LinuxBond object to the net config object.
+
+ :param bridge: The LinuxBond object to add.
+ """
+ raise NotImplemented("add_linuxbond is not implemented.")
+
def apply(self, cleanup=False):
"""Apply the network configuration.
diff --git a/os_net_config/cli.py b/os_net_config/cli.py
index c2491a2..c0ac5c4 100644
--- a/os_net_config/cli.py
+++ b/os_net_config/cli.py
@@ -48,6 +48,13 @@ def parse_opts(argv):
parser.add_argument('-r', '--root-dir', metavar='ROOT_DIR',
help="""The root directory of the filesystem.""",
default='')
+ parser.add_argument('--detailed-exit-codes',
+ action='store_true',
+ help="""Enable detailed exit codes. """
+ """If enabled an exit code of '2' means """
+ """that files were modified."""
+ """Disabled by default.""",
+ default=False)
parser.add_argument(
'-d', '--debug',
dest="debug",
@@ -183,6 +190,10 @@ def main(argv=sys.argv):
print("File: %s\n" % location)
print(data)
print("----")
+
+ if opts.detailed_exit_codes and len(files_changed) > 0:
+ return 2
+
return 0
diff --git a/os_net_config/impl_ifcfg.py b/os_net_config/impl_ifcfg.py
index b964819..0deb61e 100644
--- a/os_net_config/impl_ifcfg.py
+++ b/os_net_config/impl_ifcfg.py
@@ -50,7 +50,10 @@ class IfcfgNetConfig(os_net_config.NetConfig):
self.interface_data = {}
self.route_data = {}
self.bridge_data = {}
+ self.linuxbridge_data = {}
+ self.linuxbond_data = {}
self.member_names = {}
+ self.bond_slaves = {}
self.renamed_interfaces = {}
self.bond_primary_ifaces = {}
logger.info('Ifcfg net config provider created.')
@@ -62,7 +65,7 @@ class IfcfgNetConfig(os_net_config.NetConfig):
children.add(member)
children.update(self.child_members(member))
except KeyError:
- children.add(name)
+ pass
return children
def _add_common(self, base_opt):
@@ -90,6 +93,8 @@ class IfcfgNetConfig(os_net_config.NetConfig):
else:
data += "TYPE=OVSPort\n"
data += "OVS_BRIDGE=%s\n" % base_opt.bridge_name
+ if base_opt.linux_bridge_name:
+ data += "BRIDGE=%s\n" % base_opt.linux_bridge_name
if isinstance(base_opt, objects.OvsBridge):
data += "DEVICETYPE=ovs\n"
data += "TYPE=OVSBridge\n"
@@ -122,7 +127,36 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if base_opt.ovs_options:
data += "OVS_OPTIONS=\"%s\"\n" % base_opt.ovs_options
ovs_extra.extend(base_opt.ovs_extra)
+ elif isinstance(base_opt, objects.LinuxBridge):
+ data += "TYPE=Bridge\n"
+ data += "DELAY=0\n"
+ 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
+ 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
+ elif isinstance(base_opt, objects.LinuxBond):
+ 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
+ for member in members:
+ self.bond_slaves[member] = base_opt.name
+ if base_opt.bonding_options:
+ data += "BONDING_OPTS=\"%s\"\n" % base_opt.bonding_options
else:
+ if base_opt.name in self.bond_slaves:
+ data += "MASTER=%s\n" % self.bond_slaves[base_opt.name]
+ data += "SLAVE=yes\n"
if base_opt.use_dhcp:
data += "BOOTPROTO=dhcp\n"
elif not base_opt.addresses:
@@ -162,6 +196,12 @@ class IfcfgNetConfig(os_net_config.NetConfig):
data += "DEFROUTE=no\n"
if base_opt.dhclient_args:
data += "DHCLIENTARGS=%s\n" % base_opt.dhclient_args
+ if base_opt.dns_servers:
+ data += "DNS1=%s\n" % base_opt.dns_servers[0]
+ if len(base_opt.dns_servers) == 2:
+ data += "DNS2=%s\n" % base_opt.dns_servers[1]
+ elif len(base_opt.dns_servers) > 2:
+ logger.warning('ifcfg format supports a max of 2 dns servers.')
return data
def _add_routes(self, interface_name, routes=[]):
@@ -220,6 +260,18 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if bridge.routes:
self._add_routes(bridge.name, bridge.routes)
+ def add_linux_bridge(self, bridge):
+ """Add a LinuxBridge object to the net config object.
+
+ :param bridge: The LinuxBridge object to add.
+ """
+ logger.info('adding linux bridge: %s' % bridge.name)
+ data = self._add_common(bridge)
+ logger.debug('bridge data: %s' % data)
+ self.linuxbridge_data[bridge.name] = data
+ if bridge.routes:
+ self._add_routes(bridge.name, bridge.routes)
+
def add_bond(self, bond):
"""Add an OvsBond object to the net config object.
@@ -232,6 +284,19 @@ class IfcfgNetConfig(os_net_config.NetConfig):
if bond.routes:
self._add_routes(bond.name, bond.routes)
+ def add_linux_bond(self, bond):
+ """Add a LinuxBond object to the net config object.
+
+ :param bond: The LinuxBond object to add.
+ """
+ logger.info('adding linux bond: %s' % bond.name)
+ data = self._add_common(bond)
+ logger.debug('bond data: %s' % data)
+ self.interface_data[bond.name] = data
+ self.linuxbond_data[bond.name] = data
+ if bond.routes:
+ self._add_routes(bond.name, bond.routes)
+
def apply(self, cleanup=False, activate=True):
"""Apply the network configuration.
@@ -282,6 +347,35 @@ class IfcfgNetConfig(os_net_config.NetConfig):
update_files[bridge_route_path] = route_data
logger.info('No changes required for bridge: %s' % bridge_name)
+ for bridge_name, bridge_data in self.linuxbridge_data.iteritems():
+ route_data = self.route_data.get(bridge_name, '')
+ bridge_path = self.root_dir + bridge_config_path(bridge_name)
+ bridge_route_path = self.root_dir + route_config_path(bridge_name)
+ all_file_names.append(bridge_path)
+ all_file_names.append(bridge_route_path)
+ if (utils.diff(bridge_path, bridge_data) or
+ utils.diff(bridge_route_path, route_data)):
+ restart_bridges.append(bridge_name)
+ restart_interfaces.extend(self.child_members(bridge_name))
+ update_files[bridge_path] = bridge_data
+ update_files[bridge_route_path] = route_data
+ logger.info('No changes required for bridge: %s' % bridge_name)
+
+ for bond_name, bond_data in self.linuxbond_data.iteritems():
+ route_data = self.route_data.get(bond_name, '')
+ bond_path = self.root_dir + bridge_config_path(bond_name)
+ bond_route_path = self.root_dir + route_config_path(bond_name)
+ all_file_names.append(bond_path)
+ all_file_names.append(bond_route_path)
+ if (utils.diff(bond_path, bond_data) or
+ utils.diff(bond_route_path, route_data)):
+ restart_interfaces.append(bond_name)
+ restart_interfaces.extend(self.child_members(bond_name))
+ update_files[bond_path] = bond_data
+ update_files[bond_route_path] = route_data
+ logger.info('No changes required for linux bond: %s' %
+ bond_name)
+
if cleanup:
for ifcfg_file in glob.iglob(cleanup_pattern()):
if ifcfg_file not in all_file_names:
diff --git a/os_net_config/objects.py b/os_net_config/objects.py
index 6d0921f..dd8489e 100644
--- a/os_net_config/objects.py
+++ b/os_net_config/objects.py
@@ -40,6 +40,10 @@ def object_from_json(json):
return OvsBridge.from_json(json)
elif obj_type == "ovs_bond":
return OvsBond.from_json(json)
+ elif obj_type == "linux_bond":
+ return LinuxBond.from_json(json)
+ elif obj_type == "linux_bridge":
+ return LinuxBridge.from_json(json)
def _get_required_field(json, name, object_name):
@@ -129,9 +133,13 @@ class Address(object):
class _BaseOpts(object):
"""Base abstraction for logical port options."""
- def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=[],
- routes=[], mtu=1500, primary=False, nic_mapping=None,
- persist_mapping=False, defroute=True, dhclient_args=None):
+ def __init__(self, name, 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 []
numbered_nic_names = _numbered_nics(nic_mapping)
self.hwaddr = None
self.hwname = None
@@ -155,7 +163,9 @@ class _BaseOpts(object):
self.primary = primary
self.defroute = defroute
self.dhclient_args = dhclient_args
+ self.dns_servers = dns_servers
self.bridge_name = None # internal
+ self.linux_bridge_name = None # internal
self.ovs_port = False # internal
self.primary_interface_name = None # internal
@@ -184,6 +194,7 @@ class _BaseOpts(object):
True)))
mtu = json.get('mtu', 1500)
dhclient_args = json.get('dhclient_args')
+ dns_servers = json.get('dns_servers')
primary = strutils.bool_from_string(str(json.get('primary', False)))
addresses = []
routes = []
@@ -213,22 +224,28 @@ class _BaseOpts(object):
if include_primary:
return (use_dhcp, use_dhcpv6, addresses, routes, mtu, primary,
- nic_mapping, persist_mapping, defroute, dhclient_args)
+ nic_mapping, persist_mapping, defroute, dhclient_args,
+ dns_servers)
else:
return (use_dhcp, use_dhcpv6, addresses, routes, mtu,
- nic_mapping, persist_mapping, defroute, dhclient_args)
+ nic_mapping, persist_mapping, defroute, dhclient_args,
+ dns_servers)
class Interface(_BaseOpts):
"""Base class for network interfaces."""
- def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=[],
- routes=[], mtu=1500, primary=False, nic_mapping=None,
- persist_mapping=False, defroute=True, dhclient_args=None):
+ def __init__(self, name, 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 []
super(Interface, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
routes, mtu, primary, nic_mapping,
persist_mapping, defroute,
- dhclient_args)
+ dhclient_args, dns_servers)
@staticmethod
def from_json(json):
@@ -245,13 +262,17 @@ class Vlan(_BaseOpts):
"""
def __init__(self, device, vlan_id, use_dhcp=False, use_dhcpv6=False,
- addresses=[], routes=[], mtu=1500, primary=False,
+ addresses=None, routes=None, mtu=1500, primary=False,
nic_mapping=None, persist_mapping=False, defroute=True,
- dhclient_args=None):
+ dhclient_args=None, dns_servers=None):
+ addresses = addresses or []
+ routes = routes or []
+ dns_servers = dns_servers or []
name = 'vlan%i' % vlan_id
super(Vlan, self).__init__(name, use_dhcp, use_dhcpv6, addresses,
routes, mtu, primary, nic_mapping,
- persist_mapping, defroute, dhclient_args)
+ persist_mapping, defroute, dhclient_args,
+ dns_servers)
self.vlan_id = int(vlan_id)
numbered_nic_names = _numbered_nics(nic_mapping)
@@ -272,14 +293,19 @@ class Vlan(_BaseOpts):
class OvsBridge(_BaseOpts):
"""Base class for OVS bridges."""
- def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=[],
- routes=[], mtu=1500, members=[], ovs_options=None,
- ovs_extra=[], nic_mapping=None, persist_mapping=False,
- defroute=True, dhclient_args=None):
+ def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
+ routes=None, mtu=1500, members=None, ovs_options=None,
+ ovs_extra=None, nic_mapping=None, persist_mapping=False,
+ defroute=True, dhclient_args=None, dns_servers=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,
persist_mapping, defroute,
- dhclient_args)
+ dhclient_args, dns_servers)
self.members = members
self.ovs_options = ovs_options
self.ovs_extra = ovs_extra
@@ -300,7 +326,7 @@ class OvsBridge(_BaseOpts):
name = _get_required_field(json, 'name', 'OvsBridge')
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
persist_mapping, defroute,
- dhclient_args) = _BaseOpts.base_opts_from_json(
+ dhclient_args, dns_servers) = _BaseOpts.base_opts_from_json(
json, include_primary=False)
ovs_options = json.get('ovs_options')
ovs_extra = json.get('ovs_extra', [])
@@ -321,19 +347,136 @@ 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)
+ dhclient_args=dhclient_args, dns_servers=dns_servers)
+
+
+class LinuxBridge(_BaseOpts):
+ """Base class for Linux bridges."""
+
+ def __init__(self, name, 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):
+ addresses = addresses or []
+ routes = routes or []
+ members = members or []
+ dns_servers = dns_servers or []
+ super(LinuxBridge, self).__init__(name, use_dhcp, use_dhcpv6,
+ addresses, routes, mtu, False,
+ nic_mapping, persist_mapping,
+ defroute, dhclient_args, dns_servers)
+ self.members = members
+ for member in self.members:
+ member.linux_bridge_name = name
+ member.ovs_port = False
+ if member.primary:
+ if self.primary_interface_name:
+ msg = 'Only one primary interface allowed per bridge.'
+ raise InvalidConfigException(msg)
+ if member.primary_interface_name:
+ self.primary_interface_name = member.primary_interface_name
+ else:
+ self.primary_interface_name = member.name
+
+ @staticmethod
+ def from_json(json):
+ name = _get_required_field(json, 'name', 'LinuxBridge')
+ (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)
+
+ return LinuxBridge(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)
+
+
+class LinuxBond(_BaseOpts):
+ """Base class for Linux bonds."""
+
+ def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
+ routes=None, mtu=1500, 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(LinuxBond, 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:
+ if member.primary:
+ if self.primary_interface_name:
+ msg = 'Only one primary interface allowed per bond.'
+ raise InvalidConfigException(msg)
+ if member.primary_interface_name:
+ self.primary_interface_name = member.primary_interface_name
+ else:
+ self.primary_interface_name = member.name
+
+ @staticmethod
+ def from_json(json):
+ name = _get_required_field(json, 'name', 'LinuxBond')
+ (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 LinuxBond(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 OvsBond(_BaseOpts):
"""Base class for OVS bonds."""
- def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=[],
- routes=[], mtu=1500, primary=False, members=[],
- ovs_options=None, ovs_extra=[], nic_mapping=None,
- persist_mapping=False, defroute=True, dhclient_args=None):
+ def __init__(self, name, use_dhcp=False, use_dhcpv6=False, addresses=None,
+ routes=None, mtu=1500, primary=False, members=None,
+ ovs_options=None, ovs_extra=None, nic_mapping=None,
+ persist_mapping=False, defroute=True, dhclient_args=None,
+ dns_servers=None):
+ 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,
- persist_mapping, defroute, dhclient_args)
+ persist_mapping, defroute, dhclient_args,
+ dns_servers)
self.members = members
self.ovs_options = ovs_options
self.ovs_extra = ovs_extra
@@ -355,8 +498,8 @@ class OvsBond(_BaseOpts):
def from_json(json):
name = _get_required_field(json, 'name', 'OvsBond')
(use_dhcp, use_dhcpv6, addresses, routes, mtu, nic_mapping,
- persist_mapping, defroute,
- dhclient_args) = _BaseOpts.base_opts_from_json(
+ persist_mapping, defroute, dhclient_args,
+ dns_servers) = _BaseOpts.base_opts_from_json(
json, include_primary=False)
ovs_options = json.get('ovs_options')
ovs_extra = json.get('ovs_extra', [])
@@ -377,4 +520,4 @@ class OvsBond(_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)
+ dhclient_args=dhclient_args, dns_servers=dns_servers)
diff --git a/os_net_config/tests/test_cli.py b/os_net_config/tests/test_cli.py
index 310a527..98477fc 100644
--- a/os_net_config/tests/test_cli.py
+++ b/os_net_config/tests/test_cli.py
@@ -17,7 +17,9 @@
import os.path
import sys
+import os_net_config
from os_net_config import cli
+from os_net_config import impl_ifcfg
from os_net_config.tests import base
import six
@@ -121,3 +123,26 @@ class TestCli(base.TestCase):
'-c %s' % (provider, bond_yaml))
self.assertEqual('', stderr)
self.assertIn('File: /rootfs/', stdout_yaml)
+
+ def test_interface_noop_detailed_exit_codes(self):
+ interface_yaml = os.path.join(SAMPLE_BASE, 'interface.yaml')
+ stdout_yaml, stderr = self.run_cli('ARG0 --provider=ifcfg --noop '
+ '-c %s --detailed-exit-codes'
+ % interface_yaml, exitcodes=(2,))
+
+ def test_interface_noop_detailed_exit_codes_no_changes(self):
+ interface_yaml = os.path.join(SAMPLE_BASE, 'interface.yaml')
+
+ class TestImpl(os_net_config.NetConfig):
+
+ def add_interface(self, interface):
+ pass
+
+ def apply(self, cleanup=False, activate=True):
+ # this fake implementation returns no changes
+ return {}
+
+ self.stubs.Set(impl_ifcfg, 'IfcfgNetConfig', TestImpl)
+ stdout_yaml, stderr = self.run_cli('ARG0 --provider=ifcfg --noop '
+ '-c %s --detailed-exit-codes'
+ % interface_yaml, exitcodes=(0,))
diff --git a/os_net_config/tests/test_impl_ifcfg.py b/os_net_config/tests/test_impl_ifcfg.py
index 73271e8..db61795 100644
--- a/os_net_config/tests/test_impl_ifcfg.py
+++ b/os_net_config/tests/test_impl_ifcfg.py
@@ -60,6 +60,7 @@ _OVS_IFCFG = _BASE_IFCFG + "DEVICETYPE=ovs\nBOOTPROTO=none\n"
_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
172.19.0.0/24 via 192.168.1.1 dev em1
@@ -82,6 +83,16 @@ OVSBOOTPROTO=dhcp
OVSDHCPINTERFACES="em1"
"""
+_LINUX_BRIDGE_DHCP = """# This file is autogenerated by os-net-config
+DEVICE=br-ctlplane
+ONBOOT=yes
+HOTPLUG=no
+NM_CONTROLLED=no
+TYPE=Bridge
+DELAY=0
+BOOTPROTO=dhcp
+"""
+
_OVS_BRIDGE_STATIC = """# This file is autogenerated by os-net-config
DEVICE=br-ctlplane
ONBOOT=yes
@@ -94,6 +105,18 @@ IPADDR=192.168.1.2
NETMASK=255.255.255.0
"""
+_LINUX_BRIDGE_STATIC = """# This file is autogenerated by os-net-config
+DEVICE=br-ctlplane
+ONBOOT=yes
+HOTPLUG=no
+NM_CONTROLLED=no
+TYPE=Bridge
+DELAY=0
+BOOTPROTO=static
+IPADDR=192.168.1.2
+NETMASK=255.255.255.0
+"""
+
_OVS_BRIDGE_DHCP_PRIMARY_INTERFACE = _OVS_BRIDGE_DHCP + \
"OVS_EXTRA=\"set bridge br-ctlplane other-config:hwaddr=a1:b2:c3:d4:e5\"\n"
@@ -146,6 +169,15 @@ BOND_IFACES="em1 em2"
"""
+_LINUX_BOND_DHCP = """# This file is autogenerated by os-net-config
+DEVICE=bond0
+ONBOOT=yes
+HOTPLUG=no
+NM_CONTROLLED=no
+BOOTPROTO=dhcp
+"""
+
+
class TestIfcfgNetConfig(base.TestCase):
def setUp(self):
@@ -240,6 +272,16 @@ class TestIfcfgNetConfig(base.TestCase):
self.assertEqual(_OVS_BRIDGE_DHCP,
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,
+ members=[interface])
+ self.provider.add_linux_bridge(bridge)
+ self.provider.add_interface(interface)
+ self.assertEqual(_LINUX_BRIDGE_IFCFG, self.get_interface_config())
+ self.assertEqual(_LINUX_BRIDGE_DHCP,
+ self.provider.linuxbridge_data['br-ctlplane'])
+
def test_network_ovs_bridge_static(self):
v4_addr = objects.Address('192.168.1.2/24')
interface = objects.Interface('em1')
@@ -251,6 +293,17 @@ class TestIfcfgNetConfig(base.TestCase):
self.assertEqual(_OVS_BRIDGE_STATIC,
self.provider.bridge_data['br-ctlplane'])
+ def test_network_linux_bridge_static(self):
+ v4_addr = objects.Address('192.168.1.2/24')
+ interface = objects.Interface('em1')
+ bridge = objects.LinuxBridge('br-ctlplane', members=[interface],
+ addresses=[v4_addr])
+ self.provider.add_interface(interface)
+ self.provider.add_bridge(bridge)
+ self.assertEqual(_LINUX_BRIDGE_IFCFG, self.get_interface_config())
+ self.assertEqual(_LINUX_BRIDGE_STATIC,
+ self.provider.bridge_data['br-ctlplane'])
+
def test_network_ovs_bridge_with_dhcp_primary_interface(self):
def test_interface_mac(name):
return "a1:b2:c3:d4:e5"
@@ -321,6 +374,15 @@ BOOTPROTO=none
self.assertEqual(_OVS_BOND_DHCP,
self.get_interface_config('bond0'))
+ def test_linux_bond(self):
+ interface1 = objects.Interface('em1')
+ interface2 = objects.Interface('em2')
+ bond = objects.LinuxBond('bond0', use_dhcp=True,
+ members=[interface1, interface2])
+ self.provider.add_linux_bond(bond)
+ self.assertEqual(_LINUX_BOND_DHCP,
+ self.get_interface_config('bond0'))
+
def test_interface_defroute(self):
interface1 = objects.Interface('em1')
interface2 = objects.Interface('em2', defroute=False)
@@ -357,6 +419,34 @@ DHCLIENTARGS=--foobar
"""
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)
+ em1_config = """# This file is autogenerated by os-net-config
+DEVICE=em1
+ONBOOT=yes
+HOTPLUG=no
+NM_CONTROLLED=no
+BOOTPROTO=none
+DNS1=1.2.3.4
+"""
+ self.assertEqual(em1_config, self.get_interface_config('em1'))
+
+ def test_interface_dns_servers(self):
+ interface1 = objects.Interface('em1', dns_servers=['1.2.3.4',
+ '5.6.7.8'])
+ self.provider.add_interface(interface1)
+ em1_config = """# This file is autogenerated by os-net-config
+DEVICE=em1
+ONBOOT=yes
+HOTPLUG=no
+NM_CONTROLLED=no
+BOOTPROTO=none
+DNS1=1.2.3.4
+DNS2=5.6.7.8
+"""
+ self.assertEqual(em1_config, self.get_interface_config('em1'))
+
class TestIfcfgNetConfigApply(base.TestCase):
@@ -515,6 +605,15 @@ class TestIfcfgNetConfigApply(base.TestCase):
self.assertIn('em1', self.ifup_interface_names)
self.assertIn('em2', self.ifup_interface_names)
+ def test_restart_interface_counts(self):
+ interface = objects.Interface('em1')
+ self.provider.add_interface(interface)
+ interface2 = objects.Interface('em2')
+ self.provider.add_interface(interface2)
+ self.provider.apply()
+ self.assertEqual(1, self.ifup_interface_names.count("em1"))
+ self.assertEqual(1, self.ifup_interface_names.count("em2"))
+
def test_vlan_apply(self):
vlan = objects.Vlan('em1', 5)
self.provider.add_vlan(vlan)
diff --git a/os_net_config/tests/test_objects.py b/os_net_config/tests/test_objects.py
index c824ba8..a2e021b 100644
--- a/os_net_config/tests/test_objects.py
+++ b/os_net_config/tests/test_objects.py
@@ -119,6 +119,17 @@ class TestInterface(base.TestCase):
interface1 = objects.object_from_json(json.loads(data))
self.assertEqual("--foobar", interface1.dhclient_args)
+ def test_from_json_dns_servers(self):
+ data = """{
+"type": "interface",
+"name": "em1",
+"use_dhcp": true,
+"dns_servers": ["1.2.3.4"]
+}
+"""
+ interface1 = objects.object_from_json(json.loads(data))
+ self.assertEqual(["1.2.3.4"], interface1.dns_servers)
+
def test_from_json_dhcp_nic1(self):
def dummy_numbered_nics(nic_mapping=None):
return {"nic1": "em3"}
@@ -256,6 +267,82 @@ class TestBridge(base.TestCase):
self.assertEqual("br-foo", interface2.bridge_name)
+class TestLinuxBridge(base.TestCase):
+
+ def test_from_json_dhcp(self):
+ data = """{
+"type": "linux_bridge",
+"name": "br-foo",
+"use_dhcp": true,
+"members": [{
+ "type": "interface",
+ "name": "em1"
+}]
+}
+"""
+ bridge = objects.object_from_json(json.loads(data))
+ self.assertEqual("br-foo", bridge.name)
+ self.assertEqual(True, bridge.use_dhcp)
+ interface1 = bridge.members[0]
+ self.assertEqual("em1", interface1.name)
+ self.assertEqual(False, interface1.ovs_port)
+ self.assertEqual("br-foo", interface1.linux_bridge_name)
+
+ def test_from_json_dhcp_with_nic1(self):
+ def dummy_numbered_nics(nic_mapping=None):
+ return {"nic1": "em5"}
+ self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics)
+
+ data = """{
+"type": "linux_bridge",
+"name": "br-foo",
+"use_dhcp": true,
+"members": [{
+ "type": "interface",
+ "name": "nic1"
+}]
+}
+"""
+ bridge = objects.object_from_json(json.loads(data))
+ self.assertEqual("br-foo", bridge.name)
+ self.assertEqual(True, bridge.use_dhcp)
+ interface1 = bridge.members[0]
+ self.assertEqual("em5", interface1.name)
+ self.assertEqual(False, interface1.ovs_port)
+ self.assertEqual("br-foo", interface1.linux_bridge_name)
+
+ def test_from_json_primary_interface(self):
+ data = """{
+"type": "linux_bridge",
+"name": "br-foo",
+"use_dhcp": true,
+"members": [
+ {
+ "type": "interface",
+ "name": "em1",
+ "primary": "true"
+ },
+ {
+ "type": "interface",
+ "name": "em2"
+ }]
+}
+"""
+ bridge = objects.object_from_json(json.loads(data))
+ self.assertEqual("br-foo", bridge.name)
+ self.assertEqual(True, bridge.use_dhcp)
+ self.assertEqual("em1", bridge.primary_interface_name)
+ interface1 = bridge.members[0]
+ self.assertEqual("em1", interface1.name)
+ self.assertEqual(False, interface1.ovs_port)
+ self.assertEqual(True, interface1.primary)
+ self.assertEqual("br-foo", interface1.linux_bridge_name)
+ interface2 = bridge.members[1]
+ self.assertEqual("em2", interface2.name)
+ self.assertEqual(False, interface2.ovs_port)
+ self.assertEqual("br-foo", interface2.linux_bridge_name)
+
+
class TestBond(base.TestCase):
def test_from_json_dhcp(self):
@@ -314,6 +401,64 @@ class TestBond(base.TestCase):
self.assertEqual("em2", interface2.name)
+class TestLinuxBond(base.TestCase):
+
+ def test_from_json_dhcp(self):
+ data = """{
+"type": "linux_bond",
+"name": "bond1",
+"use_dhcp": true,
+"members": [
+ {
+ "type": "interface",
+ "name": "em1"
+ },
+ {
+ "type": "interface",
+ "name": "em2"
+ }
+]
+}
+"""
+ bridge = objects.object_from_json(json.loads(data))
+ self.assertEqual("bond1", bridge.name)
+ self.assertEqual(True, bridge.use_dhcp)
+ interface1 = bridge.members[0]
+ self.assertEqual("em1", interface1.name)
+ interface2 = bridge.members[1]
+ self.assertEqual("em2", interface2.name)
+
+ def test_from_json_dhcp_with_nic1_nic2(self):
+
+ def dummy_numbered_nics(nic_mapping=None):
+ return {"nic1": "em1", "nic2": "em2"}
+ self.stubs.Set(objects, '_numbered_nics', dummy_numbered_nics)
+
+ data = """{
+"type": "ovs_bond",
+"name": "bond1",
+"use_dhcp": true,
+"members": [
+ {
+ "type": "interface",
+ "name": "nic1"
+ },
+ {
+ "type": "interface",
+ "name": "nic2"
+ }
+]
+}
+"""
+ bridge = objects.object_from_json(json.loads(data))
+ self.assertEqual("bond1", bridge.name)
+ self.assertEqual(True, bridge.use_dhcp)
+ interface1 = bridge.members[0]
+ self.assertEqual("em1", interface1.name)
+ interface2 = bridge.members[1]
+ self.assertEqual("em2", interface2.name)
+
+
class TestNumberedNicsMapping(base.TestCase):
# We want to test the function, not the dummy..