Skip to content

Commit

Permalink
Merge pull request #290 from fabric-testbed/289-add-facility-port-to-…
Browse files Browse the repository at this point in the history
…allow-adding-multiple-interfaces

289 add facility port to allow adding multiple interfaces
  • Loading branch information
kthare10 authored Mar 11, 2024
2 parents 997ceed + a0c79df commit 5564af6
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 44 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

# Unreleased

### Fixed
- Add Facility Port to allow adding multiple interfaces (Issue [#289](https://github.com/fabric-testbed/fabrictestbed-extensions/issues/289))

## [1.6.4] - 2024-03-05

### Fixed
Expand Down
42 changes: 29 additions & 13 deletions fabrictestbed_extensions/fablib/facility_port.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from __future__ import annotations

import json
from typing import TYPE_CHECKING, List
from typing import TYPE_CHECKING, List, Union

import jinja2
from fabrictestbed.slice_editor import Capacities, Labels
Expand All @@ -41,7 +41,7 @@
from fabrictestbed_extensions.fablib.interface import Interface

if TYPE_CHECKING:
from fim.user.interface import Interface as FimInterface
from fim.user.node import Node as FimNode

from fabrictestbed_extensions.fablib.slice import Slice

Expand All @@ -50,7 +50,7 @@ class FacilityPort:
fim_interface = None
slice = None

def __init__(self, slice: Slice, fim_interface: FimInterface):
def __init__(self, slice: Slice, fim_interface: FimNode):
"""
:param slice: the fablib slice to have this node on
:type slice: Slice
Expand Down Expand Up @@ -119,7 +119,7 @@ def show(

return table

def get_fim_interface(self) -> FimInterface:
def get_fim_interface(self) -> FimNode:
return self.fim_interface

def get_model(self) -> str:
Expand All @@ -141,19 +141,35 @@ def new_facility_port(
slice: Slice = None,
name: str = None,
site: str = None,
vlan: str = None,
vlan: Union[str, list] = None,
bandwidth: int = 10,
):
fim_facility_port = slice.get_fim_topology().add_facility(
name=name,
site=site,
capacities=Capacities(bw=bandwidth),
labels=Labels(vlan=vlan),
)
if isinstance(vlan, list):
interfaces = []
index = 1
for v in vlan:
iface_tuple = (
f"iface-{index}",
Labels(vlan=v),
Capacities(bw=bandwidth),
)
interfaces.append(iface_tuple)
fim_facility_port = slice.get_fim_topology().add_facility(
name=name,
site=site,
interfaces=interfaces,
)
else:
fim_facility_port = slice.get_fim_topology().add_facility(
name=name,
site=site,
capacities=Capacities(bw=bandwidth),
labels=Labels(vlan=vlan),
)
return FacilityPort(slice, fim_facility_port)

@staticmethod
def get_facility_port(slice: Slice = None, facility_port: FimInterface = None):
def get_facility_port(slice: Slice = None, facility_port: FimNode = None):
"""
:param slice:
Expand Down Expand Up @@ -195,6 +211,6 @@ def get_interfaces(self) -> List[Interface]:
"""
ifaces = []
for fim_interface in self.get_fim_interface().interface_list:
ifaces.append(Interface(component=self, fim_interface=fim_interface))
ifaces.append(Interface(node=self, fim_interface=fim_interface))

return ifaces
40 changes: 26 additions & 14 deletions fabrictestbed_extensions/fablib/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
import json
import logging
from ipaddress import IPv4Address
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Union

import jinja2
from fabrictestbed.slice_editor import Flags
Expand All @@ -44,6 +44,7 @@
from fabrictestbed_extensions.fablib.node import Node
from fabrictestbed_extensions.fablib.network_service import NetworkService
from fabrictestbed_extensions.fablib.component import Component
from fabrictestbed_extensions.fablib.facility_port import FacilityPort

from fabrictestbed.slice_editor import UserData
from fim.user.interface import Interface as FimInterface
Expand All @@ -57,7 +58,12 @@ class Interface:
ADDR = "addr"
CONFIG = "config"

def __init__(self, component: Component = None, fim_interface: FimInterface = None):
def __init__(
self,
component: Component = None,
fim_interface: FimInterface = None,
node: FacilityPort = None,
):
"""
.. note::
Expand All @@ -69,12 +75,16 @@ def __init__(self, component: Component = None, fim_interface: FimInterface = No
:param fim_interface: the FABRIC information model interface
to set on this fablib interface
:type fim_interface: FimInterface
:param node: the facility Port to which interface is assoicated with
:type node: FacilityPort
"""
super().__init__()
self.fim_interface = fim_interface
self.component = component
self.network = None
self.dev = None
self.node = node

def get_fablib_manager(self):
return self.get_slice().get_fablib_manager()
Expand Down Expand Up @@ -532,15 +542,15 @@ def get_fim_interface(self) -> FimInterface:
"""
return self.fim_interface

def get_bandwidth(self) -> str:
def get_bandwidth(self) -> int:
"""
Gets the bandwidth of an interface. Basic NICs claim 0 bandwidth but
are 100 Gbps shared by all Basic NICs on the host.
:return: bandwith
:rtype: String
"""
if self.get_component().get_model() == "NIC_Basic":
if self.get_component() and self.get_component().get_model() == "NIC_Basic":
return 100
else:
return self.get_fim_interface().capacities.bw
Expand Down Expand Up @@ -635,7 +645,10 @@ def get_model(self) -> str:
:return: the model of this interface's component
:rtype: str
"""
return self.get_component().get_model()
if self.node:
return self.node.get_model()
else:
return self.get_component().get_model()

def get_site(self) -> str:
"""
Expand All @@ -644,7 +657,7 @@ def get_site(self) -> str:
:return: the site this interface is on
:rtype: str
"""
return self.get_component().get_site()
return self.get_node().get_site()

def get_slice(self) -> Slice:
"""
Expand All @@ -655,14 +668,17 @@ def get_slice(self) -> Slice:
"""
return self.get_node().get_slice()

def get_node(self) -> Node:
def get_node(self) -> Union[Node, FacilityPort]:
"""
Gets the node this interface's component is on.
:return: the node this interface is attached to
:rtype: Node
"""
return self.get_component().get_node()
if self.node:
return self.node
else:
return self.get_component().get_node()

def get_network(self) -> NetworkService:
"""
Expand Down Expand Up @@ -690,8 +706,6 @@ def get_network(self) -> NetworkService:
)
return self.network

return None

# fablib.Interface.get_ip_link()
def get_ip_link(self):
"""
Expand Down Expand Up @@ -724,14 +738,12 @@ def get_ip_addr_show(self, dev=None):
stdout, stderr = self.get_node().execute(
f"ip -j addr show {dev}", quiet=True
)
return stdout
except Exception as e:
(f"Exception: {e}")
logging.error(
f"Failed to get ip addr show info for interface {self.get_name()}"
f"Failed to get ip addr show info for interface {self.get_name()} Exception: {e}"
)

return stdout

# fablib.Interface.get_ip_addr()
def get_ip_addr_ssh(self, dev=None):
"""
Expand Down
48 changes: 34 additions & 14 deletions fabrictestbed_extensions/fablib/network_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network

import jinja2
from fabrictestbed.slice_editor import Flags, Labels
from fabrictestbed.slice_editor import Labels
from fabrictestbed.slice_editor import NetworkService as FimNetworkService
from fabrictestbed.slice_editor import ServiceType, UserData
from fim.slivers.network_service import NSLayer, ServiceType
Expand Down Expand Up @@ -134,14 +134,14 @@ def calculate_l2_nstype(interfaces: List[Interface] = None) -> ServiceType:
facility_port_interfaces = 0
for interface in interfaces:
sites.add(interface.get_site())
if isinstance(interface.get_component(), FacilityPort):
if isinstance(interface.get_node(), FacilityPort):
includes_facility_port = True
facility_port_interfaces += 1
if interface.get_model() == "NIC_Basic":
basic_nic_count += 1

rtn_nstype = None
if len(sites) <= 1 and len(sites) >= 0:
if 1 >= len(sites) >= 0:
rtn_nstype = NetworkService.network_service_map["L2Bridge"]
# elif basic_nic_count == 0 and len(sites) == 2 and len(interfaces) == 2:
# #TODO: remove this when STS works on all links.
Expand All @@ -166,7 +166,7 @@ def calculate_l2_nstype(interfaces: List[Interface] = None) -> ServiceType:
@staticmethod
def validate_nstype(type, interfaces):
"""
Not inteded for API use
Not intended for API use
Verifies the network service type against the number of interfaces.
Expand All @@ -180,6 +180,8 @@ def validate_nstype(type, interfaces):
:rtype: bool
"""

from fabrictestbed_extensions.fablib.facility_port import FacilityPort

sites = set([])
nics = set([])
nodes = set([])
Expand All @@ -197,13 +199,15 @@ def validate_nstype(type, interfaces):
if type == NetworkService.network_service_map["L2Bridge"]:
if len(sites) > 1:
raise Exception(
f"Network type {type} must be empty or include interfaces from exactly one site. {len(sites)} sites requested: {sites}"
f"Network type {type} must be empty or include interfaces from exactly one site. {len(sites)} "
f"sites requested: {sites}"
)

elif type == NetworkService.network_service_map["L2PTP"]:
if not len(sites) == 2:
raise Exception(
f"Network type {type} must include interfaces from exactly two sites. {len(sites)} sites requested: {sites}"
f"Network type {type} must include interfaces from exactly two sites. {len(sites)} sites "
f"requested: {sites}"
)
if "NIC_Basic" in nics:
raise Exception(
Expand All @@ -214,20 +218,36 @@ def validate_nstype(type, interfaces):
exception_list = []
if len(sites) != 2:
exception_list.append(
f"Network type {type} must include interfaces from exactly two sites. {len(sites)} sites requested: {sites}"
f"Network type {type} must include interfaces from exactly two sites. {len(sites)} sites "
f"requested: {sites}"
)
if len(interfaces) > 2:
hosts = set([])
nodes_per_site = {}
for interface in interfaces:
node = interface.get_node()
if node.get_site() not in nodes_per_site:
nodes_per_site[node.get_site()] = 0
if isinstance(node, FacilityPort):
continue
nodes_per_site[node.get_site()] += 1
for interface in interfaces:
node = interface.get_node()
if interface.get_model() == "NIC_Basic":
if node.get_host() == None:
if (
interface.get_model() == "NIC_Basic"
and nodes_per_site[node.get_site()] > 1
):
if node.get_host() is None:
exception_list.append(
f"Network type {type} does not support multiple NIC_Basic interfaces on VMs residing on the same host. Please see Node.set_host(host_nane) to explicitily bind a nodes to a specific host. Node {node.get_name()} is unbound."
f"Network type {type} does not support multiple NIC_Basic interfaces on VMs "
f"residing on the same host. Please see Node.set_host(host_name) to explicitly "
f"bind a nodes to a specific host. Node {node.get_name()} is unbound."
)
elif node.get_host() in hosts:
exception_list.append(
f"Network type {type} does not support multiple NIC_Basic interfaces on VMs residing on the same host. Please see Node.set_host(host_nane) to explicitily bind a nodes to a specific host. Multiple nodes bound to {node.get_host()}."
f"Network type {type} does not support multiple NIC_Basic interfaces on VMs residing "
f"on the same host. Please see Node.set_host(host_name) to explicitly bind a nodes "
f"to a specific host. Multiple nodes bound to {node.get_host()}."
)
else:
hosts.add(node.get_host())
Expand Down Expand Up @@ -389,15 +409,15 @@ def new_l2network(
vlan1 = interfaces[0].get_vlan()
vlan2 = interfaces[1].get_vlan()

if vlan1 == None and vlan2 == None:
if vlan1 is None and vlan2 is None:
# TODO: Long term we might have multiple vlan on one property
# and will need to make sure they are unique. For now this okay
interfaces[0].set_vlan("100")
interfaces[1].set_vlan("100")
elif vlan1 == None and vlan2 != None:
elif vlan1 is None and vlan2 is not None:
# Match VLANs if one is set.
interfaces[0].set_vlan(vlan2)
elif vlan1 != None and vlan2 == None:
elif vlan1 is not None and vlan2 is None:
# Match VLANs if one is set.
interfaces[1].set_vlan(vlan1)

Expand Down
2 changes: 1 addition & 1 deletion fabrictestbed_extensions/fablib/slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -1032,7 +1032,7 @@ def add_l3network(
)

def add_facility_port(
self, name: str = None, site: str = None, vlan: str = None
self, name: str = None, site: str = None, vlan: Union[str, list] = None
) -> NetworkService:
"""
Adds a new L2 facility port to this slice
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"

[project]
name = "fabrictestbed-extensions"
version = "1.6.4"
version = "1.6.5"
description = "FABRIC Python Client Library and CLI Extensions"
authors = [
{ name = "Paul Ruth", email = "[email protected]" },
Expand All @@ -20,7 +20,7 @@ dependencies = [
"ipyleaflet",
"ipycytoscape",
"tabulate",
"fabrictestbed==1.6.8",
"fabrictestbed==1.6.9",
"paramiko",
"jinja2>=3.0.0",
"pandas",
Expand Down

0 comments on commit 5564af6

Please sign in to comment.