diff --git a/CHANGELOG.md b/CHANGELOG.md index 2174af84..e3ec4799 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # Unreleased ### Fixed +- Sub Interface Support (Issue [#350](https://github.com/fabric-testbed/fabrictestbed-extensions/issues/350)) - Advanced reservations (Issue [#345](https://github.com/fabric-testbed/fabrictestbed-extensions/issues/345)) - Port Mirroring with Basic NICs (Issue [#343](https://github.com/fabric-testbed/fabrictestbed-extensions/issues/343)) - P4 support (Issue [#340](https://github.com/fabric-testbed/fabrictestbed-extensions/issues/340)) diff --git a/README.md b/README.md index 10793291..e5877eb2 100644 --- a/README.md +++ b/README.md @@ -90,3 +90,4 @@ help FABlib, please review the [guidelines] first. [guidelines]: ./CONTRIBUTING.md + diff --git a/fabrictestbed_extensions/fablib/component.py b/fabrictestbed_extensions/fablib/component.py index eb7c383a..c9eea51b 100644 --- a/fabrictestbed_extensions/fablib/component.py +++ b/fabrictestbed_extensions/fablib/component.py @@ -44,6 +44,8 @@ import jinja2 +from fabrictestbed_extensions.fablib.constants import Constants + if TYPE_CHECKING: from fabrictestbed_extensions.fablib.slice import Slice from fabrictestbed_extensions.fablib.node import Node @@ -60,17 +62,17 @@ class Component: component_model_map = { - "NIC_Basic": ComponentModelType.SharedNIC_ConnectX_6, - "NIC_ConnectX_6": ComponentModelType.SmartNIC_ConnectX_6, - "NIC_ConnectX_5": ComponentModelType.SmartNIC_ConnectX_5, - "NIC_P4": "P4_DedicatedPort", - "NVME_P4510": ComponentModelType.NVME_P4510, - "GPU_TeslaT4": ComponentModelType.GPU_Tesla_T4, - "GPU_RTX6000": ComponentModelType.GPU_RTX6000, - "GPU_A40": ComponentModelType.GPU_A40, - "GPU_A30": ComponentModelType.GPU_A30, - "NIC_OpenStack": ComponentModelType.SharedNIC_OpenStack_vNIC, - "FPGA_Xilinx_U280": ComponentModelType.FPGA_Xilinx_U280, + Constants.CMP_NIC_Basic: ComponentModelType.SharedNIC_ConnectX_6, + Constants.CMP_NIC_ConnectX_6: ComponentModelType.SmartNIC_ConnectX_6, + Constants.CMP_NIC_ConnectX_5: ComponentModelType.SmartNIC_ConnectX_5, + Constants.CMP_NIC_P4: Constants.P4_DedicatedPort, + Constants.CMP_NVME_P4510: ComponentModelType.NVME_P4510, + Constants.CMP_GPU_TeslaT4: ComponentModelType.GPU_Tesla_T4, + Constants.CMP_GPU_RTX6000: ComponentModelType.GPU_RTX6000, + Constants.CMP_GPU_A40: ComponentModelType.GPU_A40, + Constants.CMP_GPU_A30: ComponentModelType.GPU_A30, + Constants.CMP_NIC_OpenStack: ComponentModelType.SharedNIC_OpenStack_vNIC, + Constants.CMP_FPGA_Xilinx_U280: ComponentModelType.FPGA_Xilinx_U280, } def __str__(self): @@ -317,10 +319,13 @@ def __init__(self, node: Node = None, fim_component: FimComponent = None): self.node = node self.interfaces = None - def get_interfaces(self) -> List[Interface]: + def get_interfaces(self, include_subs: bool = True) -> List[Interface]: """ Gets the interfaces attached to this fablib component's FABRIC component. + :param include_subs: Flag indicating if sub interfaces should be included + :type include_subs: bool + :return: a list of the interfaces on this component. :rtype: List[Interface] """ @@ -330,9 +335,12 @@ def get_interfaces(self) -> List[Interface]: if not self.interfaces: self.interfaces = [] for fim_interface in self.get_fim_component().interface_list: - self.interfaces.append( - Interface(component=self, fim_interface=fim_interface) - ) + iface = Interface(component=self, fim_interface=fim_interface) + self.interfaces.append(iface) + if include_subs: + child_interfaces = iface.get_interfaces() + if child_interfaces and len(child_interfaces): + self.interfaces.extend(child_interfaces) return self.interfaces @@ -448,25 +456,23 @@ def get_model(self) -> str: str(self.get_type()) == "SmartNIC" and str(self.get_fim_model()) == "ConnectX-6" ): - return "NIC_ConnectX_6" + return Constants.CMP_NIC_ConnectX_6 elif ( str(self.get_type()) == "SmartNIC" and str(self.get_fim_model()) == "ConnectX-5" ): - return "NIC_ConnectX_5" + return Constants.CMP_NIC_ConnectX_5 elif str(self.get_type()) == "NVME" and str(self.get_fim_model()) == "P4510": - return "NVME_P4510" + return Constants.CMP_NVME_P4510 elif str(self.get_type()) == "GPU" and str(self.get_fim_model()) == "Tesla T4": - return "GPU_TeslaT4" + return Constants.CMP_GPU_TeslaT4 elif str(self.get_type()) == "GPU" and str(self.get_fim_model()) == "RTX6000": - return "GPU_RTX6000" + return Constants.CMP_GPU_RTX6000 elif ( str(self.get_type()) == "SharedNIC" and str(self.get_fim_model()) == "ConnectX-6" ): - return "NIC_Basic" - else: - return None + return Constants.CMP_NIC_Basic def get_reservation_id(self) -> str or None: """ diff --git a/fabrictestbed_extensions/fablib/constants.py b/fabrictestbed_extensions/fablib/constants.py index f53e98ef..7d8df3eb 100644 --- a/fabrictestbed_extensions/fablib/constants.py +++ b/fabrictestbed_extensions/fablib/constants.py @@ -183,3 +183,16 @@ class Constants: CPUS = "CPUs" HOSTS = "Hosts" P4_SWITCH = "P4-Switch" + + CMP_NIC_Basic = "NIC_Basic" + CMP_NIC_ConnectX_6 = "NIC_ConnectX_6" + CMP_NIC_ConnectX_5 = "NIC_ConnectX_5" + CMP_NIC_P4 = "NIC_P4" + CMP_NVME_P4510 = "NVME_P4510" + CMP_GPU_TeslaT4 = "GPU_TeslaT4" + CMP_GPU_RTX6000 = "GPU_RTX6000" + CMP_GPU_A40 = "GPU_A40" + CMP_GPU_A30 = "GPU_A30" + CMP_NIC_OpenStack = "NIC_OpenStack" + CMP_FPGA_Xilinx_U280 = "FPGA_Xilinx_U280" + P4_DedicatedPort = "P4_DedicatedPort" diff --git a/fabrictestbed_extensions/fablib/interface.py b/fabrictestbed_extensions/fablib/interface.py index dd825474..3174c0e4 100644 --- a/fabrictestbed_extensions/fablib/interface.py +++ b/fabrictestbed_extensions/fablib/interface.py @@ -33,13 +33,15 @@ import json import logging from ipaddress import IPv4Address -from typing import TYPE_CHECKING, Any, Union +from typing import TYPE_CHECKING, Any, List, Union import jinja2 from fabrictestbed.slice_editor import Flags -from fim.user import Labels +from fim.user import Capacities, InterfaceType, Labels from tabulate import tabulate +from fabrictestbed_extensions.fablib.constants import Constants + if TYPE_CHECKING: from fabrictestbed_extensions.fablib.slice import Slice from fabrictestbed_extensions.fablib.node import Node @@ -66,6 +68,7 @@ def __init__( fim_interface: FimInterface = None, node: Union[Switch, FacilityPort] = None, model: str = None, + parent: Interface = None, ): """ .. note:: @@ -89,6 +92,8 @@ def __init__( self.dev = None self.node = node self.model = model + self.interfaces = None + self.parent = parent def get_fablib_manager(self): return self.get_slice().get_fablib_manager() @@ -448,7 +453,12 @@ def get_mac(self) -> str: :rtype: String """ try: - mac = self.get_fim_interface().get_property(pname="label_allocations").mac + if self.parent: + mac = self.parent.get_mac() + else: + mac = ( + self.get_fim_interface().get_property(pname="label_allocations").mac + ) except: mac = None @@ -719,6 +729,9 @@ def get_short_name(self): :return: Shortened name of the interface. :rtype: str """ + if self.parent: + return self.get_name() + # Strip off the extra parts of the name added by FIM prefix_length = len( f"{self.get_node().get_name()}-{self.get_component().get_short_name()}-" @@ -1193,6 +1206,8 @@ def delete(self): net = self.get_network() if net: net.remove_interface(self) + if self.parent and self.parent.get_fim(): + self.parent.get_fim().remove_child_interface(name=self.get_name()) def set_subnet(self, ipv4_subnet: str = None, ipv6_subnet: str = None): """ @@ -1278,3 +1293,82 @@ def get_peer_account_id(self): """ if self.get_fim() and self.get_fim().peer_labels: return self.get_fim().peer_labels.account_id + + def get_interfaces(self) -> List[Interface]: + """ + Gets the interfaces attached to this fablib component's FABRIC component. + + :return: a list of the interfaces on this component. + :rtype: List[Interface] + """ + + if not self.interfaces: + self.interfaces = [] + for fim_interface in self.get_fim().interface_list: + self.interfaces.append( + Interface( + component=self.get_component(), + fim_interface=fim_interface, + model=str(InterfaceType.SubInterface), + parent=self, + ) + ) + + return self.interfaces + + def add_sub_interface(self, name: str, vlan: str, bw: int = 10): + """ + Add a sub-interface to a dedicated NIC. + + This method adds a sub-interface to a NIC (Network Interface Card) with the specified + name, VLAN (Virtual Local Area Network) ID, and bandwidth. It supports only specific + NIC models. + + :param name: The name of the sub-interface. + :type name: str + + :param vlan: The VLAN ID for the sub-interface. + :type vlan: str + + :param bw: The bandwidth allocated to the sub-interface, in Gbps. Default is 10 Gbps. + :type bw: int + + :raises Exception: If the NIC model does not support sub-interfaces. + """ + if self.get_model() not in [ + Constants.CMP_NIC_ConnectX_5, + Constants.CMP_NIC_ConnectX_6, + ]: + raise Exception( + f"Sub interfaces are only supported for the following NIC models: " + f"{Constants.CMP_NIC_ConnectX_5}, {Constants.CMP_NIC_ConnectX_6}" + ) + + if self.get_fim(): + child_interface = self.get_fim().add_child_interface( + name=name, labels=Labels(vlan=vlan) + ) + child_if_capacities = child_interface.get_property(pname="capacities") + if not child_if_capacities: + child_if_capacities = Capacities() + child_if_capacities.bw = int(bw) + child_interface.set_properties(capacities=child_if_capacities) + if not self.interfaces: + self.interfaces = [] + + ch_iface = Interface( + component=self.get_component(), + fim_interface=child_interface, + model=str(InterfaceType.SubInterface), + ) + self.interfaces.append(ch_iface) + return ch_iface + + def get_type(self) -> str: + """ + Get Interface type + :return: get interface type + :rtype: String + """ + if self.get_fim(): + return self.get_fim().type diff --git a/fabrictestbed_extensions/fablib/node.py b/fabrictestbed_extensions/fablib/node.py index 851bfd50..530a00be 100644 --- a/fabrictestbed_extensions/fablib/node.py +++ b/fabrictestbed_extensions/fablib/node.py @@ -1024,16 +1024,19 @@ def get_error_message(self) -> str or None: except: return "" - def get_interfaces(self) -> List[Interface] or None: + def get_interfaces(self, include_subs: bool = True) -> List[Interface] or None: """ Gets a list of the interfaces associated with the FABRIC node. + :param include_subs: Flag indicating if sub interfaces should be included + :type include_subs: bool + :return: a list of interfaces on the node :rtype: List[Interface] """ interfaces = [] for component in self.get_components(): - for interface in component.get_interfaces(): + for interface in component.get_interfaces(include_subs=include_subs): interfaces.append(interface) return interfaces diff --git a/pyproject.toml b/pyproject.toml index f8d6b00f..9db3b3d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ "ipyleaflet", "ipycytoscape", "tabulate", - "fabrictestbed==1.7.0b11", + "fabrictestbed==1.7.0b12", "paramiko", "jinja2>=3.0.0", "pandas",