diff options
Diffstat (limited to 'os_net_config')
-rw-r--r-- | os_net_config/__init__.py | 14 | ||||
-rw-r--r-- | os_net_config/impl_ifcfg.py | 230 | ||||
-rw-r--r-- | os_net_config/objects.py | 94 | ||||
-rw-r--r-- | os_net_config/tests/test_impl_ifcfg.py | 136 | ||||
-rw-r--r-- | os_net_config/tests/test_objects.py | 121 |
5 files changed, 528 insertions, 67 deletions
diff --git a/os_net_config/__init__.py b/os_net_config/__init__.py index 42131bb..a8fdf43 100644 --- a/os_net_config/__init__.py +++ b/os_net_config/__init__.py @@ -40,6 +40,7 @@ class NetConfig(object): def add_object(self, obj): """Convenience method to add any type of object to the network config. + See objects.py. :param obj: The object to add. @@ -48,6 +49,8 @@ class NetConfig(object): self.add_interface(obj) elif isinstance(obj, objects.Vlan): self.add_vlan(obj) + elif isinstance(obj, objects.IvsInterface): + self.add_ivs_interface(obj) elif isinstance(obj, objects.OvsBridge): self.add_bridge(obj) for member in obj.members: @@ -56,6 +59,10 @@ class NetConfig(object): self.add_linux_bridge(obj) for member in obj.members: self.add_object(member) + elif isinstance(obj, objects.IvsBridge): + self.add_ivs_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: @@ -93,6 +100,13 @@ class NetConfig(object): """ raise NotImplemented("add_linux_bridge is not implemented.") + def add_ivs_bridge(self, bridge): + """Add a IvsBridge object to the net config object. + + :param bridge: The IvsBridge object to add. + """ + raise NotImplemented("add_ivs_bridge is not implemented.") + def add_bond(self, bond): """Add an OvsBond object to the net config object. diff --git a/os_net_config/impl_ifcfg.py b/os_net_config/impl_ifcfg.py index 2e99f98..6459bcc 100644 --- a/os_net_config/impl_ifcfg.py +++ b/os_net_config/impl_ifcfg.py @@ -30,15 +30,23 @@ def ifcfg_config_path(name): return "/etc/sysconfig/network-scripts/ifcfg-%s" % name -#NOTE(dprince): added here for testability +# NOTE(dprince): added here for testability def bridge_config_path(name): return ifcfg_config_path(name) +def ivs_config_path(): + return "/etc/sysconfig/ivs" + + def route_config_path(name): return "/etc/sysconfig/network-scripts/route-%s" % name +def route6_config_path(name): + return "/etc/sysconfig/network-scripts/route6-%s" % name + + def cleanup_pattern(): return "/etc/sysconfig/network-scripts/ifcfg-*" @@ -49,12 +57,14 @@ class IfcfgNetConfig(os_net_config.NetConfig): def __init__(self, noop=False, root_dir=''): super(IfcfgNetConfig, self).__init__(noop, root_dir) self.interface_data = {} + self.ivsinterface_data = {} + self.vlan_data = {} self.route_data = {} + self.route6_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.') @@ -78,14 +88,27 @@ class IfcfgNetConfig(os_net_config.NetConfig): data += "ONBOOT=yes\n" data += "HOTPLUG=no\n" data += "NM_CONTROLLED=no\n" + if not base_opt.dns_servers and not base_opt.use_dhcp: + data += "PEERDNS=no\n" if isinstance(base_opt, objects.Vlan): if not base_opt.ovs_port: # vlans on OVS bridges are internal ports (no device, etc) data += "VLAN=yes\n" if base_opt.device: data += "PHYSDEV=%s\n" % base_opt.device + else: + if base_opt.linux_bond_name: + data += "PHYSDEV=%s\n" % base_opt.linux_bond_name + elif isinstance(base_opt, objects.IvsInterface): + data += "TYPE=IVSIntPort\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.ivs_bridge_name: + data += "DEVICETYPE=ivs\n" + data += "IVS_BRIDGE=%s\n" % base_opt.ivs_bridge_name if base_opt.ovs_port: data += "DEVICETYPE=ovs\n" if base_opt.bridge_name: @@ -152,14 +175,9 @@ class IfcfgNetConfig(os_net_config.NetConfig): 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: @@ -211,16 +229,31 @@ class IfcfgNetConfig(os_net_config.NetConfig): logger.info('adding custom route for interface: %s' % interface_name) data = "" first_line = "" + data6 = "" + first_line6 = "" for route in routes: - if route.default: - first_line = "default via %s dev %s\n" % (route.next_hop, - interface_name) + if ":" not in route.next_hop: + # Route is an IPv4 route + if route.default: + first_line = "default via %s dev %s\n" % (route.next_hop, + interface_name) + else: + data += "%s via %s dev %s\n" % (route.ip_netmask, + route.next_hop, + interface_name) else: - data += "%s via %s dev %s\n" % (route.ip_netmask, - route.next_hop, - interface_name) + # Route is an IPv6 route + if route.default: + first_line6 = "default via %s dev %s\n" % (route.next_hop, + interface_name) + else: + data6 += "%s via %s dev %s\n" % (route.ip_netmask, + route.next_hop, + interface_name) self.route_data[interface_name] = first_line + data + self.route6_data[interface_name] = first_line6 + data6 logger.debug('route data: %s' % self.route_data[interface_name]) + logger.debug('ipv6 route data: %s' % self.route6_data[interface_name]) def add_interface(self, interface): """Add an Interface object to the net config object. @@ -247,10 +280,22 @@ class IfcfgNetConfig(os_net_config.NetConfig): logger.info('adding vlan: %s' % vlan.name) data = self._add_common(vlan) logger.debug('vlan data: %s' % data) - self.interface_data[vlan.name] = data + self.vlan_data[vlan.name] = data if vlan.routes: self._add_routes(vlan.name, vlan.routes) + def add_ivs_interface(self, ivs_interface): + """Add a ivs_interface object to the net config object. + + :param ivs_interface: The ivs_interface object to add. + """ + logger.info('adding ivs_interface: %s' % ivs_interface.name) + data = self._add_common(ivs_interface) + logger.debug('ivs_interface data: %s' % data) + self.ivsinterface_data[ivs_interface.name] = data + if ivs_interface.routes: + self._add_routes(ivs_interface.name, ivs_interface.routes) + def add_bridge(self, bridge): """Add an OvsBridge object to the net config object. @@ -275,6 +320,18 @@ class IfcfgNetConfig(os_net_config.NetConfig): if bridge.routes: self._add_routes(bridge.name, bridge.routes) + def add_ivs_bridge(self, bridge): + """Add a IvsBridge object to the net config object. + + IVS can only support one virtual switch per node, + using "ivs" as its name. As long as the ivs service + is running, the ivs virtual switch will be there. + It is impossible to add multiple ivs virtual switches + per node. + :param bridge: The IvsBridge object to add. + """ + pass + def add_bond(self, bond): """Add an OvsBond object to the net config object. @@ -295,11 +352,30 @@ class IfcfgNetConfig(os_net_config.NetConfig): 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 generate_ivs_config(self, ivs_uplinks, ivs_interfaces): + """Generate configuration content for ivs.""" + + intfs = [] + for intf in ivs_uplinks: + intfs.append(' -u ') + intfs.append(intf) + uplink_str = ''.join(intfs) + + intfs = [] + for intf in ivs_interfaces: + intfs.append(' --internal-port=') + intfs.append(intf) + intf_str = ''.join(intfs) + + data = ("DAEMON_ARGS=\"--hitless --certificate /etc/ivs " + "--inband-vlan 4092%s%s\"" + % (uplink_str, intf_str)) + return data + def apply(self, cleanup=False, activate=True): """Apply the network configuration. @@ -317,65 +393,137 @@ class IfcfgNetConfig(os_net_config.NetConfig): """ logger.info('applying network configs...') restart_interfaces = [] + restart_vlans = [] restart_bridges = [] + restart_linux_bonds = [] update_files = {} all_file_names = [] + ivs_uplinks = [] # ivs physical uplinks + ivs_interfaces = [] # ivs internal ports for interface_name, iface_data in self.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) + 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)): + 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 interface: %s' % interface_name) + for interface_name, iface_data in self.ivsinterface_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) + ivs_interfaces.append(interface_name) + if (utils.diff(interface_path, iface_data) or + utils.diff(route_path, route_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 ivs interface: %s' % + interface_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, '') + vlan_path = self.root_dir + ifcfg_config_path(vlan_name) + vlan_route_path = self.root_dir + route_config_path(vlan_name) + vlan_route6_path = self.root_dir + route6_config_path(vlan_name) + all_file_names.append(vlan_path) + all_file_names.append(vlan_route_path) + all_file_names.append(vlan_route6_path) + if (utils.diff(vlan_path, vlan_data) or + utils.diff(vlan_route_path, route_data)): + restart_vlans.append(vlan_name) + restart_vlans.extend(self.child_members(vlan_name)) + update_files[vlan_path] = vlan_data + update_files[vlan_route_path] = route_data + update_files[vlan_route6_path] = route6_data + else: + logger.info('No changes required for vlan interface: %s' % + vlan_name) + for bridge_name, bridge_data in self.bridge_data.iteritems(): route_data = self.route_data.get(bridge_name, '') + route6_data = self.route6_data.get(bridge_name, '') bridge_path = self.root_dir + bridge_config_path(bridge_name) - bridge_route_path = self.root_dir + route_config_path(bridge_name) + br_route_path = self.root_dir + route_config_path(bridge_name) + br_route6_path = self.root_dir + route6_config_path(bridge_name) all_file_names.append(bridge_path) - all_file_names.append(bridge_route_path) + all_file_names.append(br_route_path) + all_file_names.append(br_route6_path) if (utils.diff(bridge_path, bridge_data) or - utils.diff(bridge_route_path, route_data)): + utils.diff(br_route_path, route_data) or + utils.diff(br_route6_path, route6_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 + update_files[br_route_path] = route_data + update_files[br_route6_path] = route6_data + else: 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, '') + route6_data = self.route6_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) + br_route_path = self.root_dir + route_config_path(bridge_name) + br_route6_path = self.root_dir + route6_config_path(bridge_name) all_file_names.append(bridge_path) - all_file_names.append(bridge_route_path) + all_file_names.append(br_route_path) + all_file_names.append(br_route6_path) if (utils.diff(bridge_path, bridge_data) or - utils.diff(bridge_route_path, route_data)): + utils.diff(br_route_path, route_data) or + utils.diff(br_route6_path, route6_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 + update_files[br_route_path] = route_data + update_files[br_route6_path] = route6_data + else: 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, '') + route6_data = self.route6_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) + bond_route6_path = self.root_dir + route6_config_path(bond_name) all_file_names.append(bond_path) all_file_names.append(bond_route_path) + all_file_names.append(bond_route6_path) if (utils.diff(bond_path, bond_data) or - utils.diff(bond_route_path, route_data)): - restart_interfaces.append(bond_name) + utils.diff(bond_route_path, route_data) or + utils.diff(bond_route6_path, route6_data)): + restart_linux_bonds.append(bond_name) restart_interfaces.extend(self.child_members(bond_name)) update_files[bond_path] = bond_data update_files[bond_route_path] = route_data + update_files[bond_route6_path] = route6_data + else: logger.info('No changes required for linux bond: %s' % bond_name) @@ -390,9 +538,15 @@ class IfcfgNetConfig(os_net_config.NetConfig): self.remove_config(ifcfg_file) if activate: + for vlan in restart_vlans: + self.ifdown(vlan) + for interface in restart_interfaces: self.ifdown(interface) + for linux_bond in restart_linux_bonds: + self.ifdown(linux_bond) + for bridge in restart_bridges: self.ifdown(bridge, iftype='bridge') @@ -402,7 +556,15 @@ class IfcfgNetConfig(os_net_config.NetConfig): for location, data in update_files.iteritems(): self.write_config(location, data) + if ivs_uplinks or ivs_interfaces: + location = ivs_config_path() + data = self.generate_ivs_config(ivs_uplinks, ivs_interfaces) + self.write_config(location, data) + if activate: + for linux_bond in restart_linux_bonds: + self.ifup(linux_bond) + for bridge in restart_bridges: self.ifup(bridge, iftype='bridge') @@ -413,4 +575,20 @@ class IfcfgNetConfig(os_net_config.NetConfig): self.ovs_appctl('bond/set-active-slave', bond, self.bond_primary_ifaces[bond]) + if ivs_uplinks or ivs_interfaces: + logger.info("Attach to ivs with " + "uplinks: %s, " + "interfaces: %s" % + (ivs_uplinks, ivs_interfaces)) + for ivs_uplink in ivs_uplinks: + self.ifup(ivs_uplink) + for ivs_interface in ivs_interfaces: + self.ifup(ivs_interface) + msg = "Restart ivs" + self.execute(msg, '/usr/bin/systemctl', + 'restart', 'ivs') + + for vlan in restart_vlans: + self.ifup(vlan) + return update_files diff --git a/os_net_config/objects.py b/os_net_config/objects.py index b08dc46..4f3e0d5 100644 --- a/os_net_config/objects.py +++ b/os_net_config/objects.py @@ -44,6 +44,10 @@ def object_from_json(json): return LinuxBond.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) def _get_required_field(json, name, object_name): @@ -166,6 +170,8 @@ class _BaseOpts(object): self.dns_servers = dns_servers self.bridge_name = None # internal self.linux_bridge_name = None # internal + self.ivs_bridge_name = None # internal + self.linux_bond_name = None # internal self.ovs_port = False # internal self.primary_interface_name = None # internal @@ -290,6 +296,32 @@ class Vlan(_BaseOpts): return Vlan(device, vlan_id, *opts) +class IvsInterface(_BaseOpts): + """Base class for ivs interfaces.""" + + def __init__(self, vlan_id, name='ivs', 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(IvsInterface, 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', 'IvsInterface') + opts = _BaseOpts.base_opts_from_json(json) + return IvsInterface(vlan_id, name, *opts) + + class OvsBridge(_BaseOpts): """Base class for OVS bridges.""" @@ -405,6 +437,67 @@ class LinuxBridge(_BaseOpts): dns_servers=dns_servers) +class IvsBridge(_BaseOpts): + """Base class for IVS bridges. + + Indigo Virtual Switch (IVS) is a virtual switch for Linux. + It is compatible with the KVM hypervisor and leveraging the + Open vSwitch kernel module for packet forwarding. There are + three major differences between IVS and OVS: + 1. Each node can have at most one ivs, no name required. + 2. Bond is not allowed to attach to an ivs. It is the SDN + controller's job to dynamically form bonds on ivs. + 3. IP address can only be statically assigned. + """ + + def __init__(self, name='ivs', 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(IvsBridge, 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: + if isinstance(member, OvsBond) or isinstance(member, LinuxBond): + msg = 'IVS does not support bond interfaces.' + raise InvalidConfigException(msg) + member.ivs_bridge_name = name + member.ovs_port = False + self.primary_interface_name = None # ivs doesn't use primary intf + + @staticmethod + def from_json(json): + name = 'ivs' + (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 IvsBridge(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.""" @@ -423,6 +516,7 @@ class LinuxBond(_BaseOpts): self.members = members self.bonding_options = bonding_options for member in self.members: + member.linux_bond_name = name if member.primary: if self.primary_interface_name: msg = 'Only one primary interface allowed per bond.' diff --git a/os_net_config/tests/test_impl_ifcfg.py b/os_net_config/tests/test_impl_ifcfg.py index 8a60a8d..d68a96e 100644 --- a/os_net_config/tests/test_impl_ifcfg.py +++ b/os_net_config/tests/test_impl_ifcfg.py @@ -30,6 +30,7 @@ DEVICE=em1 ONBOOT=yes HOTPLUG=no NM_CONTROLLED=no +PEERDNS=no """ _NO_IP = _BASE_IFCFG + "BOOTPROTO=none\n" @@ -39,11 +40,20 @@ IPADDR=192.168.1.2 NETMASK=255.255.255.0 """ +_V4_V6_IFCFG = _BASE_IFCFG + """IPV6INIT=yes +BOOTPROTO=static +IPADDR=192.168.1.2 +NETMASK=255.255.255.0 +IPV6_AUTOCONF=no +IPV6ADDR=2001:abc:a::/64 +""" + _IFCFG_VLAN = """# This file is autogenerated by os-net-config DEVICE=em1.120 ONBOOT=yes HOTPLUG=no NM_CONTROLLED=no +PEERDNS=no VLAN=yes BOOTPROTO=none """ @@ -75,6 +85,11 @@ _ROUTES = """default via 192.168.1.1 dev em1 172.19.0.0/24 via 192.168.1.1 dev em1 """ +_ROUTES_V6 = """default via 2001:db8::1 dev em1 +2001:db8:dead:beef:cafe::/56 via fd00:fd00:2000::1 dev em1 +""" + + _OVS_INTERFACE = _BASE_IFCFG + """DEVICETYPE=ovs TYPE=OVSPort OVS_BRIDGE=br-ctlplane @@ -107,6 +122,7 @@ DEVICE=br-ctlplane ONBOOT=yes HOTPLUG=no NM_CONTROLLED=no +PEERDNS=no DEVICETYPE=ovs TYPE=OVSBridge BOOTPROTO=static @@ -119,6 +135,7 @@ DEVICE=br-ctlplane ONBOOT=yes HOTPLUG=no NM_CONTROLLED=no +PEERDNS=no TYPE=Bridge DELAY=0 BOOTPROTO=static @@ -140,6 +157,7 @@ DEVICE=vlan5 ONBOOT=yes HOTPLUG=no NM_CONTROLLED=no +PEERDNS=no VLAN=yes PHYSDEV=em1 """ @@ -150,6 +168,7 @@ DEVICE=vlan5 ONBOOT=yes HOTPLUG=no NM_CONTROLLED=no +PEERDNS=no """ _VLAN_NO_IP = _BASE_VLAN + "BOOTPROTO=none\n" @@ -165,6 +184,11 @@ OVS_OPTIONS="tag=5" BOOTPROTO=none """ +_VLAN_LINUX_BRIDGE = _BASE_VLAN_OVS + """VLAN=yes +PHYSDEV=em1 +BRIDGE=br-ctlplane +BOOTPROTO=none +""" _OVS_BOND_DHCP = """# This file is autogenerated by os-net-config DEVICE=bond0 @@ -177,7 +201,6 @@ OVSBOOTPROTO=dhcp BOND_IFACES="em1 em2" """ - _LINUX_BOND_DHCP = """# This file is autogenerated by os-net-config DEVICE=bond0 ONBOOT=yes @@ -187,6 +210,42 @@ BOOTPROTO=dhcp """ +_LINUX_BOND_INTERFACE = _BASE_IFCFG + """MASTER=bond0 +SLAVE=yes +BOOTPROTO=none +""" + + +_IVS_UPLINK = """# This file is autogenerated by os-net-config +DEVICE=em1 +ONBOOT=yes +HOTPLUG=no +NM_CONTROLLED=no +PEERDNS=no +DEVICETYPE=ivs +IVS_BRIDGE=ivs +BOOTPROTO=none +""" + +_IVS_INTERFACE = """# This file is autogenerated by os-net-config +DEVICE=storage5 +ONBOOT=yes +HOTPLUG=no +NM_CONTROLLED=no +PEERDNS=no +TYPE=IVSIntPort +DEVICETYPE=ivs +IVS_BRIDGE=ivs +MTU=1500 +BOOTPROTO=static +IPADDR=172.16.2.7 +NETMASK=255.255.255.0 +""" + +_IVS_CONFIG = ('DAEMON_ARGS=\"--hitless --certificate /etc/ivs ' + '--inband-vlan 4092 -u em1 --internal-port=storage5\"') + + class TestIfcfgNetConfig(base.TestCase): def setUp(self): @@ -200,9 +259,18 @@ class TestIfcfgNetConfig(base.TestCase): def get_interface_config(self, name='em1'): return self.provider.interface_data[name] + def get_vlan_config(self, name='vlan1'): + return self.provider.vlan_data[name] + + def get_linux_bond_config(self, name='bond0'): + return self.provider.linuxbond_data[name] + def get_route_config(self, name='em1'): return self.provider.route_data.get(name, '') + def get_route6_config(self, name='em1'): + return self.provider.route6_data.get(name, '') + def test_add_base_interface(self): interface = objects.Interface('em1') self.provider.add_interface(interface) @@ -276,6 +344,20 @@ class TestIfcfgNetConfig(base.TestCase): self.assertEqual(_V4_IFCFG, self.get_interface_config()) self.assertEqual(_ROUTES, self.get_route_config()) + def test_network_with_ipv6_routes(self): + route1 = objects.Route('192.168.1.1', default=True) + route2 = objects.Route('192.168.1.1', '172.19.0.0/24') + route3 = objects.Route('2001:db8::1', default=True) + route4 = objects.Route('fd00:fd00:2000::1', + '2001:db8:dead:beef:cafe::/56') + v4_addr = objects.Address('192.168.1.2/24') + v6_addr = objects.Address('2001:abc:a::/64') + interface = objects.Interface('em1', addresses=[v4_addr, v6_addr], + routes=[route1, route2, route3, route4]) + self.provider.add_interface(interface) + self.assertEqual(_V4_V6_IFCFG, self.get_interface_config()) + self.assertEqual(_ROUTES_V6, self.get_route6_config()) + def test_network_ovs_bridge_with_dhcp(self): interface = objects.Interface('em1') bridge = objects.OvsBridge('br-ctlplane', use_dhcp=True, @@ -348,22 +430,38 @@ class TestIfcfgNetConfig(base.TestCase): self.assertEqual(_OVS_BRIDGE_DHCP_OVS_EXTRA, self.provider.bridge_data['br-ctlplane']) + def test_network_ivs_with_uplink_and_interface(self): + interface = objects.Interface('em1') + v4_addr = objects.Address('172.16.2.7/24') + ivs_interface = objects.IvsInterface(vlan_id=5, + name='storage', + addresses=[v4_addr]) + bridge = objects.IvsBridge(members=[interface, ivs_interface]) + self.provider.add_interface(interface) + self.provider.add_ivs_interface(ivs_interface) + self.provider.add_bridge(bridge) + self.assertEqual(_IVS_UPLINK, self.get_interface_config()) + self.assertEqual(_IVS_INTERFACE, + self.provider.ivsinterface_data[ivs_interface.name]) + data = self.provider.generate_ivs_config(['em1'], ['storage5']) + self.assertEqual(_IVS_CONFIG, data) + def test_add_vlan(self): vlan = objects.Vlan('em1', 5) self.provider.add_vlan(vlan) - self.assertEqual(_VLAN_NO_IP, self.get_interface_config('vlan5')) + self.assertEqual(_VLAN_NO_IP, self.get_vlan_config('vlan5')) def test_add_vlan_ovs(self): vlan = objects.Vlan('em1', 5) vlan.ovs_port = True self.provider.add_vlan(vlan) - self.assertEqual(_VLAN_OVS, self.get_interface_config('vlan5')) + self.assertEqual(_VLAN_OVS, self.get_vlan_config('vlan5')) def test_add_vlan_mtu_1500(self): vlan = objects.Vlan('em1', 5, mtu=1500) self.provider.add_vlan(vlan) expected = _VLAN_NO_IP + 'MTU=1500\n' - self.assertEqual(expected, self.get_interface_config('vlan5')) + self.assertEqual(expected, self.get_vlan_config('vlan5')) def test_add_ovs_bridge_with_vlan(self): vlan = objects.Vlan('em1', 5) @@ -371,7 +469,15 @@ class TestIfcfgNetConfig(base.TestCase): members=[vlan]) self.provider.add_vlan(vlan) self.provider.add_bridge(bridge) - self.assertEqual(_VLAN_OVS_BRIDGE, self.get_interface_config('vlan5')) + self.assertEqual(_VLAN_OVS_BRIDGE, self.get_vlan_config('vlan5')) + + def test_add_linux_bridge_with_vlan(self): + vlan = objects.Vlan('em1', 5) + bridge = objects.LinuxBridge('br-ctlplane', use_dhcp=True, + members=[vlan]) + self.provider.add_vlan(vlan) + self.provider.add_bridge(bridge) + self.assertEqual(_VLAN_LINUX_BRIDGE, self.get_vlan_config('vlan5')) def test_ovs_bond(self): interface1 = objects.Interface('em1') @@ -388,6 +494,7 @@ DEVICE=em2 ONBOOT=yes HOTPLUG=no NM_CONTROLLED=no +PEERDNS=no BOOTPROTO=none """ self.assertEqual(em2_config, self.get_interface_config('em2')) @@ -400,8 +507,12 @@ BOOTPROTO=none bond = objects.LinuxBond('bond0', use_dhcp=True, members=[interface1, interface2]) self.provider.add_linux_bond(bond) + self.provider.add_interface(interface1) + self.provider.add_interface(interface2) self.assertEqual(_LINUX_BOND_DHCP, - self.get_interface_config('bond0')) + self.get_linux_bond_config('bond0')) + self.assertEqual(_LINUX_BOND_INTERFACE, + self.get_interface_config('em1')) def test_interface_defroute(self): interface1 = objects.Interface('em1') @@ -413,6 +524,7 @@ DEVICE=em1 ONBOOT=yes HOTPLUG=no NM_CONTROLLED=no +PEERDNS=no BOOTPROTO=none """ em2_config = """# This file is autogenerated by os-net-config @@ -420,6 +532,7 @@ DEVICE=em2 ONBOOT=yes HOTPLUG=no NM_CONTROLLED=no +PEERDNS=no BOOTPROTO=none DEFROUTE=no """ @@ -434,6 +547,7 @@ DEVICE=em1 ONBOOT=yes HOTPLUG=no NM_CONTROLLED=no +PEERDNS=no BOOTPROTO=none DHCLIENTARGS=--foobar """ @@ -474,6 +588,7 @@ class TestIfcfgNetConfigApply(base.TestCase): super(TestIfcfgNetConfigApply, self).setUp() self.temp_ifcfg_file = tempfile.NamedTemporaryFile() self.temp_route_file = tempfile.NamedTemporaryFile() + self.temp_route6_file = tempfile.NamedTemporaryFile() self.temp_bridge_file = tempfile.NamedTemporaryFile() self.temp_cleanup_file = tempfile.NamedTemporaryFile(delete=False) self.ifup_interface_names = [] @@ -487,6 +602,10 @@ class TestIfcfgNetConfigApply(base.TestCase): return self.temp_route_file.name self.stubs.Set(impl_ifcfg, 'route_config_path', test_routes_path) + def test_routes6_path(name): + return self.temp_route6_file.name + self.stubs.Set(impl_ifcfg, 'route6_config_path', test_routes6_path) + def test_bridge_path(name): return self.temp_bridge_file.name self.stubs.Set(impl_ifcfg, 'bridge_config_path', test_bridge_path) @@ -508,6 +627,7 @@ class TestIfcfgNetConfigApply(base.TestCase): def tearDown(self): self.temp_ifcfg_file.close() self.temp_route_file.close() + self.temp_route6_file.close() self.temp_bridge_file.close() if os.path.exists(self.temp_cleanup_file.name): self.temp_cleanup_file.close() @@ -553,7 +673,7 @@ class TestIfcfgNetConfigApply(base.TestCase): self.assertEqual([], self.ifup_interface_names) def test_bond_active_slave(self): - #setup and apply a bond + # setup and apply a bond interface1 = objects.Interface('em1') interface2 = objects.Interface('em2', primary=True) bond = objects.OvsBond('bond1', use_dhcp=True, @@ -566,7 +686,7 @@ class TestIfcfgNetConfigApply(base.TestCase): self.assertIn(ovs_appctl_cmds, self.ovs_appctl_cmds) def test_bond_active_ordering(self): - #setup and apply a bond + # setup and apply a bond interface1 = objects.Interface('em1') interface2 = objects.Interface('em2') bond = objects.OvsBond('bond1', use_dhcp=True, diff --git a/os_net_config/tests/test_objects.py b/os_net_config/tests/test_objects.py index a2e021b..d3e41df 100644 --- a/os_net_config/tests/test_objects.py +++ b/os_net_config/tests/test_objects.py @@ -29,7 +29,7 @@ class TestRoute(base.TestCase): route = objects.Route.from_json(json.loads(data)) self.assertEqual("172.19.0.1", route.next_hop) self.assertEqual("172.19.0.0/24", route.ip_netmask) - self.assertEqual(False, route.default) + self.assertFalse(route.default) def test_from_json_default_route(self): data = '{"next_hop": "172.19.0.1", "ip_netmask": "172.19.0.0/24", ' \ @@ -37,14 +37,14 @@ class TestRoute(base.TestCase): route = objects.Route.from_json(json.loads(data)) self.assertEqual("172.19.0.1", route.next_hop) self.assertEqual("172.19.0.0/24", route.ip_netmask) - self.assertEqual(True, route.default) + self.assertTrue(route.default) data = '{"next_hop": "172.19.0.1", "ip_netmask": "172.19.0.0/24", ' \ '"default": "true"}' route = objects.Route.from_json(json.loads(data)) self.assertEqual("172.19.0.1", route.next_hop) self.assertEqual("172.19.0.0/24", route.ip_netmask) - self.assertEqual(True, route.default) + self.assertTrue(route.default) class TestAddress(base.TestCase): @@ -85,14 +85,14 @@ class TestInterface(base.TestCase): v4_addr = objects.Address('192.168.1.1/24') v6_addr = objects.Address('2001:abc:a::/64') interface = objects.Interface('foo', addresses=[v4_addr, v6_addr]) - self.assertEquals("192.168.1.1", interface.v4_addresses()[0].ip) - self.assertEquals("2001:abc:a::", interface.v6_addresses()[0].ip) + self.assertEqual("192.168.1.1", interface.v4_addresses()[0].ip) + self.assertEqual("2001:abc:a::", interface.v6_addresses()[0].ip) def test_from_json_dhcp(self): data = '{"type": "interface", "name": "em1", "use_dhcp": true}' interface = objects.object_from_json(json.loads(data)) self.assertEqual("em1", interface.name) - self.assertEqual(True, interface.use_dhcp) + self.assertTrue(interface.use_dhcp) def test_from_json_defroute(self): data = '{"type": "interface", "name": "em1", "use_dhcp": true}' @@ -105,8 +105,8 @@ class TestInterface(base.TestCase): } """ interface2 = objects.object_from_json(json.loads(data)) - self.assertEqual(True, interface1.defroute) - self.assertEqual(False, interface2.defroute) + self.assertTrue(interface1.defroute) + self.assertFalse(interface2.defroute) def test_from_json_dhclient_args(self): data = """{ @@ -138,7 +138,7 @@ class TestInterface(base.TestCase): data = '{"type": "interface", "name": "nic1", "use_dhcp": true}' interface = objects.object_from_json(json.loads(data)) self.assertEqual("em3", interface.name) - self.assertEqual(True, interface.use_dhcp) + self.assertTrue(interface.use_dhcp) def test_from_json_with_addresses(self): data = """{ @@ -157,8 +157,8 @@ class TestInterface(base.TestCase): """ interface = objects.object_from_json(json.loads(data)) self.assertEqual("em1", interface.name) - self.assertEqual(False, interface.use_dhcp) - self.assertEqual(False, interface.use_dhcpv6) + self.assertFalse(interface.use_dhcp) + self.assertFalse(interface.use_dhcpv6) self.assertEqual(1501, interface.mtu) address1 = interface.v4_addresses()[0] self.assertEqual("192.0.2.1", address1.ip) @@ -176,7 +176,7 @@ class TestVlan(base.TestCase): vlan = objects.object_from_json(json.loads(data)) self.assertEqual("em1", vlan.device) self.assertEqual(16, vlan.vlan_id) - self.assertEqual(True, vlan.use_dhcp) + self.assertTrue(vlan.use_dhcp) def test_from_json_dhcp_nic1(self): def dummy_numbered_nics(nic_mapping=None): @@ -188,7 +188,7 @@ class TestVlan(base.TestCase): vlan = objects.object_from_json(json.loads(data)) self.assertEqual("em4", vlan.device) self.assertEqual(16, vlan.vlan_id) - self.assertEqual(True, vlan.use_dhcp) + self.assertTrue(vlan.use_dhcp) class TestBridge(base.TestCase): @@ -206,10 +206,10 @@ class TestBridge(base.TestCase): """ bridge = objects.object_from_json(json.loads(data)) self.assertEqual("br-foo", bridge.name) - self.assertEqual(True, bridge.use_dhcp) + self.assertTrue(bridge.use_dhcp) interface1 = bridge.members[0] self.assertEqual("em1", interface1.name) - self.assertEqual(True, interface1.ovs_port) + self.assertTrue(interface1.ovs_port) self.assertEqual("br-foo", interface1.bridge_name) def test_from_json_dhcp_with_nic1(self): @@ -229,10 +229,10 @@ class TestBridge(base.TestCase): """ bridge = objects.object_from_json(json.loads(data)) self.assertEqual("br-foo", bridge.name) - self.assertEqual(True, bridge.use_dhcp) + self.assertTrue(bridge.use_dhcp) interface1 = bridge.members[0] self.assertEqual("em5", interface1.name) - self.assertEqual(True, interface1.ovs_port) + self.assertTrue(interface1.ovs_port) self.assertEqual("br-foo", interface1.bridge_name) def test_from_json_primary_interface(self): @@ -254,16 +254,16 @@ class TestBridge(base.TestCase): """ bridge = objects.object_from_json(json.loads(data)) self.assertEqual("br-foo", bridge.name) - self.assertEqual(True, bridge.use_dhcp) + self.assertTrue(bridge.use_dhcp) self.assertEqual("em1", bridge.primary_interface_name) interface1 = bridge.members[0] self.assertEqual("em1", interface1.name) - self.assertEqual(True, interface1.ovs_port) - self.assertEqual(True, interface1.primary) + self.assertTrue(interface1.ovs_port) + self.assertTrue(interface1.primary) self.assertEqual("br-foo", interface1.bridge_name) interface2 = bridge.members[1] self.assertEqual("em2", interface2.name) - self.assertEqual(True, interface2.ovs_port) + self.assertTrue(interface2.ovs_port) self.assertEqual("br-foo", interface2.bridge_name) @@ -282,10 +282,10 @@ class TestLinuxBridge(base.TestCase): """ bridge = objects.object_from_json(json.loads(data)) self.assertEqual("br-foo", bridge.name) - self.assertEqual(True, bridge.use_dhcp) + self.assertTrue(bridge.use_dhcp) interface1 = bridge.members[0] self.assertEqual("em1", interface1.name) - self.assertEqual(False, interface1.ovs_port) + self.assertFalse(interface1.ovs_port) self.assertEqual("br-foo", interface1.linux_bridge_name) def test_from_json_dhcp_with_nic1(self): @@ -305,10 +305,10 @@ class TestLinuxBridge(base.TestCase): """ bridge = objects.object_from_json(json.loads(data)) self.assertEqual("br-foo", bridge.name) - self.assertEqual(True, bridge.use_dhcp) + self.assertTrue(bridge.use_dhcp) interface1 = bridge.members[0] self.assertEqual("em5", interface1.name) - self.assertEqual(False, interface1.ovs_port) + self.assertFalse(interface1.ovs_port) self.assertEqual("br-foo", interface1.linux_bridge_name) def test_from_json_primary_interface(self): @@ -330,19 +330,74 @@ class TestLinuxBridge(base.TestCase): """ bridge = objects.object_from_json(json.loads(data)) self.assertEqual("br-foo", bridge.name) - self.assertEqual(True, bridge.use_dhcp) + self.assertTrue(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.assertFalse(interface1.ovs_port) + self.assertTrue(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.assertFalse(interface2.ovs_port) self.assertEqual("br-foo", interface2.linux_bridge_name) +class TestIvsBridge(base.TestCase): + + def test_interface_from_json(self): + data = """{ +"type": "ivs_bridge", +"members": [{ + "type": "interface", + "name": "nic2" +}] +} +""" + bridge = objects.object_from_json(json.loads(data)) + self.assertEqual("ivs", bridge.name) + interface1 = bridge.members[0] + self.assertEqual("nic2", interface1.name) + self.assertEqual(False, interface1.ovs_port) + self.assertEqual("ivs", interface1.ivs_bridge_name) + + def test_ivs_interface_from_json(self): + data = """{ +"type": "ivs_bridge", +"members": [{ + "type": "ivs_interface", + "name": "storage", + "vlan_id": 202 +}] +} +""" + bridge = objects.object_from_json(json.loads(data)) + self.assertEqual("ivs", bridge.name) + interface1 = bridge.members[0] + self.assertEqual("storage202", interface1.name) + self.assertEqual(False, interface1.ovs_port) + self.assertEqual("ivs", interface1.ivs_bridge_name) + + def test_bond_interface_from_json(self): + data = """{ +"type": "ivs_bridge", +"members": [{ + "type": "linux_bond", + "name": "bond1", + "members": [ + {"type": "interface", "name": "nic2"}, + {"type": "interface", "name": "nic3"} + ] +}] +} +""" + err = self.assertRaises(objects.InvalidConfigException, + objects.IvsBridge.from_json, + json.loads(data)) + expected = 'IVS does not support bond interfaces.' + self.assertIn(expected, err) + + class TestBond(base.TestCase): def test_from_json_dhcp(self): @@ -364,7 +419,7 @@ class TestBond(base.TestCase): """ bridge = objects.object_from_json(json.loads(data)) self.assertEqual("bond1", bridge.name) - self.assertEqual(True, bridge.use_dhcp) + self.assertTrue(bridge.use_dhcp) interface1 = bridge.members[0] self.assertEqual("em1", interface1.name) interface2 = bridge.members[1] @@ -394,7 +449,7 @@ class TestBond(base.TestCase): """ bridge = objects.object_from_json(json.loads(data)) self.assertEqual("bond1", bridge.name) - self.assertEqual(True, bridge.use_dhcp) + self.assertTrue(bridge.use_dhcp) interface1 = bridge.members[0] self.assertEqual("em1", interface1.name) interface2 = bridge.members[1] @@ -422,7 +477,7 @@ class TestLinuxBond(base.TestCase): """ bridge = objects.object_from_json(json.loads(data)) self.assertEqual("bond1", bridge.name) - self.assertEqual(True, bridge.use_dhcp) + self.assertTrue(bridge.use_dhcp) interface1 = bridge.members[0] self.assertEqual("em1", interface1.name) interface2 = bridge.members[1] @@ -452,7 +507,7 @@ class TestLinuxBond(base.TestCase): """ bridge = objects.object_from_json(json.loads(data)) self.assertEqual("bond1", bridge.name) - self.assertEqual(True, bridge.use_dhcp) + self.assertTrue(bridge.use_dhcp) interface1 = bridge.members[0] self.assertEqual("em1", interface1.name) interface2 = bridge.members[1] |