diff options
-rw-r--r-- | .coveragerc | 2 | ||||
-rw-r--r-- | etc/os-net-config/samples/linux_bond.yaml | 13 | ||||
-rw-r--r-- | etc/os-net-config/samples/linux_bridge.yaml | 9 | ||||
-rw-r--r-- | os_net_config/__init__.py | 24 | ||||
-rw-r--r-- | os_net_config/cli.py | 11 | ||||
-rw-r--r-- | os_net_config/impl_ifcfg.py | 96 | ||||
-rw-r--r-- | os_net_config/objects.py | 197 | ||||
-rw-r--r-- | os_net_config/tests/test_cli.py | 25 | ||||
-rw-r--r-- | os_net_config/tests/test_impl_ifcfg.py | 99 | ||||
-rw-r--r-- | os_net_config/tests/test_objects.py | 145 |
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.. |