diff --git a/etc/os-net-config/samples/ib_child_interface.json b/etc/os-net-config/samples/ib_child_interface.json new file mode 100644 index 00000000..dadef460 --- /dev/null +++ b/etc/os-net-config/samples/ib_child_interface.json @@ -0,0 +1,20 @@ +{ + "network_config": [ + { + "type": "ib_interface", + "name": "ib0", + "use_dhcp": false + }, + { + "type": "ib_child_interface", + "parent": "ib0", + "pkey_id": "100", + "use_dhcp": false, + "addresses": [ + { + "ip_netmask": "10.20.30.40/24" + } + ] + } + ] +} diff --git a/etc/os-net-config/samples/ib_child_interface.yaml b/etc/os-net-config/samples/ib_child_interface.yaml new file mode 100644 index 00000000..2aa0a9a2 --- /dev/null +++ b/etc/os-net-config/samples/ib_child_interface.yaml @@ -0,0 +1,18 @@ +network_config: + # Note(abdallahyas): a parent InfiniBand interface is needed to be up for + # the IPoIB pkey interface to work. The ib0 interface here is just there + # to make sure that it is up, it can be configured separately. + + - + type: ib_interface + name: ib0 + use_dhcp: false + + - + type: ib_child_interface + parent: ib0 + pkey_id: "100" + use_dhcp: false + addresses: + - + ip_netmask: 10.20.30.40/24 diff --git a/os_net_config/__init__.py b/os_net_config/__init__.py index 7a00ee09..ae6dde3c 100644 --- a/os_net_config/__init__.py +++ b/os_net_config/__init__.py @@ -98,6 +98,8 @@ class NetConfig(object): self.add_ovs_patch_port(obj) elif isinstance(obj, objects.IbInterface): self.add_ib_interface(obj) + elif isinstance(obj, objects.IbChildInterface): + self.add_ib_child_interface(obj) elif isinstance(obj, objects.OvsDpdkPort): self.add_ovs_dpdk_port(obj) elif isinstance(obj, objects.OvsDpdkBond): @@ -215,6 +217,14 @@ class NetConfig(object): """ raise NotImplementedError("add_ib_interface is not implemented.") + def add_ib_child_interface(self, ib_child_interface): + """Add an InfiniBand child interface object to the net config object. + + :param ib_child_interface: The InfiniBand child + interface object to add. + """ + raise NotImplementedError("add_ib_child_interface is not implemented.") + def add_ovs_dpdk_port(self, ovs_dpdk_port): """Add a OvsDpdkPort object to the net config object. diff --git a/os_net_config/impl_ifcfg.py b/os_net_config/impl_ifcfg.py index f9ef7bb5..d6634811 100644 --- a/os_net_config/impl_ifcfg.py +++ b/os_net_config/impl_ifcfg.py @@ -25,7 +25,6 @@ import os_net_config from os_net_config import objects from os_net_config import utils - logger = logging.getLogger(__name__) # Import the raw NetConfig object so we can call its methods @@ -134,6 +133,7 @@ class IfcfgNetConfig(os_net_config.NetConfig): self.nfvswitch_intiface_data = {} self.nfvswitch_options = None self.vlan_data = {} + self.ib_childs_data = {} self.route_data = {} self.route6_data = {} self.route_table_data = {} @@ -374,6 +374,11 @@ class IfcfgNetConfig(os_net_config.NetConfig): data += "TYPE=NFVSWITCHIntPort\n" elif isinstance(base_opt, objects.IbInterface): data += "TYPE=Infiniband\n" + elif isinstance(base_opt, objects.IbChildInterface): + data += "TYPE=Infiniband\n" + data += "PKEY=yes\n" + data += "PHYSDEV=%s\n" % base_opt.parent + data += "PKEY_ID=%s\n" % base_opt.pkey_id elif re.match('\w+\.\d+$', base_opt.name): data += "VLAN=yes\n" elif isinstance(base_opt, objects.Interface): @@ -887,6 +892,22 @@ class IfcfgNetConfig(os_net_config.NetConfig): % (ib_interface.hwname, ib_interface.name)) self.renamed_interfaces[ib_interface.hwname] = ib_interface.name + def add_ib_child_interface(self, ib_child_interface): + """Add an InfiniBand child interface object to the net config object. + + :param ib_child_interface: The InfiniBand child + interface object to add. + """ + logger.info('adding ib_child_interface: %s' % ib_child_interface.name) + data = self._add_common(ib_child_interface) + logger.debug('ib_child_interface data: %s' % data) + self.ib_childs_data[ib_child_interface.name] = data + if ib_child_interface.routes: + self._add_routes(ib_child_interface.name, + ib_child_interface.routes) + if ib_child_interface.rules: + self._add_rules(ib_child_interface.name, ib_child_interface.rules) + def add_ovs_dpdk_port(self, ovs_dpdk_port): """Add a OvsDpdkPort object to the net config object. @@ -1153,6 +1174,7 @@ class IfcfgNetConfig(os_net_config.NetConfig): logger.info('applying network configs...') restart_interfaces = [] restart_vlans = [] + restart_ib_childs = [] restart_bridges = [] restart_linux_bonds = [] restart_linux_teams = [] @@ -1514,6 +1536,51 @@ class IfcfgNetConfig(os_net_config.NetConfig): if utils.diff(vlan_rule_path, rule_data): update_files[vlan_rule_path] = rule_data + for ib_child_name, ib_child_data in self.ib_childs_data.items(): + route_data = self.route_data.get(ib_child_name, '') + route6_data = self.route6_data.get(ib_child_name, '') + rule_data = self.rule_data.get(ib_child_name, '') + ib_child_path = self.root_dir + ifcfg_config_path(ib_child_name) + ib_child_route_path = \ + self.root_dir + route_config_path(ib_child_name) + ib_child_route6_path = \ + self.root_dir + route6_config_path(ib_child_name) + ib_child_rule_path = \ + self.root_dir + route_rule_config_path(ib_child_name) + all_file_names.append(ib_child_path) + all_file_names.append(ib_child_route_path) + all_file_names.append(ib_child_route6_path) + all_file_names.append(ib_child_rule_path) + restarts_concatenated = itertools.chain(restart_interfaces, + restart_bridges, + restart_linux_bonds, + restart_linux_teams) + if (self.parse_ifcfg(ib_child_data).get('PHYSDEV') in + restarts_concatenated): + if ib_child_name not in restart_ib_childs: + restart_ib_childs.append(ib_child_name) + update_files[ib_child_path] = ib_child_data + elif utils.diff(ib_child_path, ib_child_data): + if self.ifcfg_requires_restart(ib_child_path, ib_child_data): + restart_ib_childs.append(ib_child_name) + else: + apply_interfaces.append( + (ib_child_name, ib_child_path, ib_child_data)) + update_files[ib_child_path] = ib_child_data + else: + logger.info('No changes required for the ib child interface: ' + '%s' % ib_child_name) + if utils.diff(ib_child_route_path, route_data): + update_files[ib_child_route_path] = route_data + if ib_child_name not in restart_ib_childs: + apply_routes.append((ib_child_name, route_data)) + if utils.diff(ib_child_route6_path, route6_data): + update_files[ib_child_route6_path] = route6_data + if ib_child_name not in restart_ib_childs: + apply_routes.append((ib_child_name, route6_data)) + if utils.diff(ib_child_rule_path, rule_data): + update_files[ib_child_rule_path] = rule_data + if self.vpp_interface_data or self.vpp_bond_data: vpp_path = self.root_dir + vpp_config_path() vpp_config = utils.generate_vpp_config(vpp_path, vpp_interfaces, @@ -1592,6 +1659,9 @@ class IfcfgNetConfig(os_net_config.NetConfig): for vlan in restart_vlans: self.ifdown(vlan) + for ib_child in restart_ib_childs: + self.ifdown(ib_child) + for interface in restart_interfaces: self.ifdown(interface) @@ -1691,6 +1761,9 @@ class IfcfgNetConfig(os_net_config.NetConfig): for nfvswitch_internal in nfvswitch_internal_ifaces: self.ifup(nfvswitch_internal) + for ib_child in restart_ib_childs: + self.ifup(ib_child) + for vlan in restart_vlans: self.ifup(vlan) diff --git a/os_net_config/objects.py b/os_net_config/objects.py index be4aa3f5..9a4cad52 100644 --- a/os_net_config/objects.py +++ b/os_net_config/objects.py @@ -73,6 +73,8 @@ def object_from_json(json): return OvsPatchPort.from_json(json) elif obj_type == "ib_interface": return IbInterface.from_json(json) + elif obj_type == "ib_child_interface": + return IbChildInterface.from_json(json) elif obj_type == "ovs_dpdk_port": return OvsDpdkPort.from_json(json) elif obj_type == "ovs_dpdk_bond": @@ -1244,6 +1246,55 @@ class IbInterface(_BaseOpts): return IbInterface(name, *opts, ethtool_opts=ethtool_opts) +class IbChildInterface(_BaseOpts): + """Base class for InfiniBand child network interfaces.""" + + def __init__(self, parent, pkey_id, use_dhcp=False, use_dhcpv6=False, + addresses=None, routes=None, rules=None, mtu=None, + primary=False, nic_mapping=None, persist_mapping=False, + defroute=True, dhclient_args=None, dns_servers=None, + nm_controlled=True, onboot=True, domain=None): + addresses = addresses or [] + routes = routes or [] + rules = rules or [] + dns_servers = dns_servers or [] + self.pkey_id = pkey_id + self.parent = parent + full_pkey_id = 0x8000 | pkey_id + name = "%s.%04x" % (parent, full_pkey_id) + nm_controlled = True + super(IbChildInterface, self).__init__(name, use_dhcp, use_dhcpv6, + addresses, routes, rules, mtu, + primary, nic_mapping, + persist_mapping, defroute, + dhclient_args, dns_servers, + nm_controlled, onboot, domain) + + @staticmethod + def from_json(json): + parent = _get_required_field(json, 'parent', 'IbChildInterface') + pkey_id = _get_required_field(json, 'pkey_id', 'IbChildInterface') + if type(pkey_id) == str: + try: + pkey_id = int(pkey_id) + except ValueError: + try: + pkey_id = int(pkey_id, base=16) + except Exception: + # Note (Abdallahyas): We do not care for other + # bases other than decimal and hexa + msg = "pkey only support decimal and hex bases, not int" + raise InvalidConfigException(msg) + else: + msg = "Invalid pkey type: got %s" % type(pkey_id) + raise InvalidConfigException(msg) + if (pkey_id < 0x0001 or pkey_id >= 0x7fff): + msg = "Invalid pkey value 0x%X" % pkey_id + raise InvalidConfigException(msg) + opts = _BaseOpts.base_opts_from_json(json) + return IbChildInterface(parent, pkey_id, *opts) + + class OvsDpdkPort(_BaseOpts): """Base class for OVS Dpdk Ports.""" diff --git a/os_net_config/schema.yaml b/os_net_config/schema.yaml index 4ad1ccbe..323b2af0 100644 --- a/os_net_config/schema.yaml +++ b/os_net_config/schema.yaml @@ -1455,6 +1455,52 @@ definitions: - name additionalProperties: False + ib_child_interface: + type: object + properties: + type: + enum: ["ib_child_interface"] + parent: + $ref: "#/definitions/string_or_param" + pkey_id: + $ref: "#/definitions/string_or_param" + primary: + $ref: "#/definitions/bool_or_param" + # common options: + use_dhcp: + $ref: "#/definitions/bool_or_param" + use_dhcpv6: + $ref: "#/definitions/bool_or_param" + addresses: + $ref: "#/definitions/list_of_address" + routes: + $ref: "#/definitions/list_of_route" + rules: + $ref: "#/definitions/list_of_rule" + mtu: + $ref: "#/definitions/int_or_param" + nic_mapping: + $ref: "#/definitions/nic_mapping" + persist_mapping: + $ref: "#/definitions/bool_or_param" + defroute: + $ref: "#/definitions/bool_or_param" + dhclient_args: + $ref: "#/definitions/string_or_param" + dns_servers: + $ref: "#/definitions/list_of_ip_address_string_or_param" + nm_controlled: + $ref: "#/definitions/bool_or_param" + onboot: + $ref: "#/definitions/bool_or_param" + domain: + $ref: "#/definitions/list_of_domain_name_string_or_domain_name_string" + required: + - type + - parent + - pkey_id + additionalProperties: False + type: array items: oneOf: @@ -1478,6 +1524,7 @@ items: - $ref: "#/definitions/nfvswitch_bridge" - $ref: "#/definitions/nfvswitch_internal" - $ref: "#/definitions/ib_interface" + - $ref: "#/definitions/ib_child_interface" - $ref: "#/definitions/vpp_interface" - $ref: "#/definitions/vpp_bond" - $ref: "#/definitions/contrail_vrouter" diff --git a/os_net_config/tests/test_impl_ifcfg.py b/os_net_config/tests/test_impl_ifcfg.py index 3f09e081..7abd2a4e 100644 --- a/os_net_config/tests/test_impl_ifcfg.py +++ b/os_net_config/tests/test_impl_ifcfg.py @@ -174,6 +174,19 @@ IPADDR2=10.0.0.2 NETMASK2=255.0.0.0 """ +_BASE_IB_CHILD_IFCFG = """# This file is autogenerated by os-net-config +DEVICE=ib0.8001 +ONBOOT=yes +HOTPLUG=no +NM_CONTROLLED=yes +PEERDNS=no +TYPE=Infiniband +PKEY=yes +PHYSDEV=ib0 +PKEY_ID=1 +BOOTPROTO=none +""" + _V6_IFCFG = _BASE_IFCFG + """IPV6INIT=yes IPV6_AUTOCONF=no IPV6ADDR=2001:abc:a::/64 @@ -974,6 +987,13 @@ class TestIfcfgNetConfig(base.TestCase): self.get_interface_config('ib0')) self.assertEqual('', self.get_route_config()) + def test_add_ib_child_interface(self): + ib_child_interface = objects.IbChildInterface('ib0', 1) + self.provider.add_interface(ib_child_interface) + self.assertEqual(_BASE_IB_CHILD_IFCFG, + self.get_interface_config('ib0.8001')) + self.assertEqual('', self.get_route_config()) + def test_add_contrail_vrouter(self): addresses = [objects.Address('10.0.0.30/24')] interface1 = objects.Interface('em3') diff --git a/os_net_config/tests/test_objects.py b/os_net_config/tests/test_objects.py index 0d427213..82137106 100644 --- a/os_net_config/tests/test_objects.py +++ b/os_net_config/tests/test_objects.py @@ -1597,6 +1597,67 @@ class TestIbInterface(base.TestCase): self.assertEqual("metric 10", route1.route_options) +class TestIbChildInterface(base.TestCase): + + def test_ib_child_interface_name(self): + ib_child_interface = objects.IbChildInterface('foo', pkey_id=100) + self.assertEqual("foo.8064", ib_child_interface.name) + self.assertEqual("foo", ib_child_interface.parent) + + def test_ib_child_interface_pkey_id_valid(self): + ib_child_interface = objects.IbChildInterface('foo', pkey_id=100) + self.assertEqual(100, ib_child_interface.pkey_id) + + def test_ib_child_interface_pkek_id_str(self): + data = '{"type": "ib_child_interface", ' \ + '"parent": "foo", "pkey_id": "100"}' + ib_child_interface = \ + objects.IbChildInterface.from_json(json.loads(data)) + self.assertEqual(100, ib_child_interface.pkey_id) + + def test_ib_child_interface_pkey_id_str_hexa(self): + data = '{"type": "ib_child_interface", ' \ + '"parent": "foo", "pkey_id": "0x64"}' + ib_child_interface = \ + objects.IbChildInterface.from_json(json.loads(data)) + self.assertEqual(100, ib_child_interface.pkey_id) + + def test_ib_child_interface_pkey_id_str_invalid_base(self): + data = '{"type": "ib_child_interface", ' \ + '"parent": "foo", "pkey_id": "0b01100100"}' + self.assertRaises(objects.InvalidConfigException, + objects.object_from_json, + json.loads(data)) + + def test_ib_child_interface_nm_controlled_true(self): + data = '{"type": "ib_child_interface", ' \ + '"parent": "foo", "pkey_id": "100"}' + ib_child_interface = \ + objects.IbChildInterface.from_json(json.loads(data)) + self.assertEqual(True, ib_child_interface.nm_controlled) + + def test_ib_child_interface_pkey_id_zero(self): + data = '{"type": "ib_child_interface", ' \ + '"parent": "foo", "pkey_id": "0"}' + self.assertRaises(objects.InvalidConfigException, + objects.object_from_json, + json.loads(data)) + + def test_ib_child_interface_pkey_id_managment(self): + data = '{"type": "ib_child_interface", ' \ + '"parent": "foo", "pkey_id": "0x7fff"}' + self.assertRaises(objects.InvalidConfigException, + objects.object_from_json, + json.loads(data)) + + def test_ib_child_interface_pkey_id_max(self): + data = '{"type": "ib_child_interface", ' \ + '"parent": "foo", "pkey_id": "0x8001"}' + self.assertRaises(objects.InvalidConfigException, + objects.object_from_json, + json.loads(data)) + + class TestNicMapping(base.TestCase): # We want to test the function, not the dummy..