Skip to content

Commit

Permalink
Merge pull request #207 from fabric-testbed/204-subinterface-support
Browse files Browse the repository at this point in the history
204 subinterface support
  • Loading branch information
kthare10 authored Jul 1, 2024
2 parents e6e033b + f153df1 commit 3691413
Show file tree
Hide file tree
Showing 17 changed files with 486 additions and 21 deletions.
2 changes: 1 addition & 1 deletion fim/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
FABRIC Information Model library and utilities
"""
__VERSION__ = "1.7.0b10"
__VERSION__ = "1.7.0b12"
__version__ = __VERSION__
76 changes: 72 additions & 4 deletions fim/graph/abc_property_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
from fim.slivers.capacities_labels import Capacities, Labels, ReservationInfo, \
StructuralInfo, CapacityHints, Location, Flags
from fim.slivers.delegations import Delegations, DelegationType
from fim.slivers.interface_info import InterfaceSliver, InterfaceInfo
from fim.slivers.interface_info import InterfaceSliver, InterfaceInfo, InterfaceType
from fim.slivers.base_sliver import BaseSliver
from fim.slivers.network_node import NodeSliver, CompositeNodeSliver
from fim.slivers.network_link import NetworkLinkSliver
Expand Down Expand Up @@ -336,6 +336,18 @@ def get_nodes_on_shortest_path(self, *, node_a: str, node_z: str, rel: str = Non
:return:
"""

def get_nodes_on_path_with_hops(self, *, node_a: str, node_z: str, hops: List[str], cut_off: int = 100) -> List:
"""
Get a list of node ids that lie on a path between two nodes with the specified hops. Return empty
list if no path can be found. Optionally specify the type of relationship that path
should consist of.
:param node_a: Starting node ID.
:param node_z: Ending node ID.
:param hops: List of hops that must be present in the path.
:param cut_off: Optional Depth to stop the search. Only paths of length <= cutoff are returned.
:return: Path with specified hops and no loops exists, empty list otherwise.
"""

@abstractmethod
def get_first_neighbor(self, *, node_id: str, rel: str, node_label: str) -> List:
"""
Expand Down Expand Up @@ -668,7 +680,7 @@ def sliver_to_dict(sliver) -> Dict[str, Any]:

if sliver.network_service_info is not None:
nss = list()
for ns in sliver.network_service_info.list_network_services():
for ns in sliver.network_service_info.list_services():
nss.append(ABCPropertyGraph.sliver_to_dict(ns))
d['network_services'] = nss

Expand Down Expand Up @@ -696,6 +708,13 @@ def sliver_to_dict(sliver) -> Dict[str, Any]:
d = ABCPropertyGraph.link_sliver_to_graph_properties_dict(sliver)
elif type(sliver) == InterfaceSliver:
d = ABCPropertyGraph.interface_sliver_to_graph_properties_dict(sliver)
# now add deep sliver stuff
# interfaces
if sliver.interface_info is not None:
ii = list()
for i in sliver.interface_info.list_interfaces():
ii.append(ABCPropertyGraph.sliver_to_dict(i))
d['interfaces'] = ii
else:
raise PropertyGraphQueryException(msg=f'JSON Conversion for type {type(sliver)} is not supported.',
graph_id=None, node_id=None)
Expand Down Expand Up @@ -829,6 +848,14 @@ def interface_sliver_from_graph_properties_dict(d: Dict[str, str]) -> InterfaceS
isl = InterfaceSliver()
ABCPropertyGraph.set_base_sliver_properties_from_graph_properties_dict(isl, d)
isl.set_properties(peer_labels=Labels.from_json(d.get(ABCPropertyGraph.PROP_PEER_LABELS, None)))
# find interfaces and attach
ifs = d.get('interfaces', None)
if ifs is not None and len(ifs) > 0:
ifi = InterfaceInfo()
for i in ifs:
ifsl = ABCPropertyGraph.interface_sliver_from_graph_properties_dict(i)
ifi.add_interface(ifsl)
isl.interface_info = ifi
return isl

def build_deep_node_sliver(self, *, node_id: str) -> NodeSliver:
Expand Down Expand Up @@ -857,6 +884,7 @@ def build_deep_node_sliver(self, *, node_id: str) -> NodeSliver:

nss = self.get_first_neighbor(node_id=node_id, rel=ABCPropertyGraph.REL_HAS,
node_label=ABCPropertyGraph.CLASS_NetworkService)

if nss is not None and len(nss) > 0:
nsi = NetworkServiceInfo()
for s in nss:
Expand Down Expand Up @@ -910,11 +938,14 @@ def build_deep_ns_sliver(self, *, node_id: str) -> NetworkServiceSliver:
# find interfaces and attach
ifs = self.get_first_neighbor(node_id=node_id, rel=ABCPropertyGraph.REL_CONNECTS,
node_label=ABCPropertyGraph.CLASS_ConnectionPoint)

if ifs is not None and len(ifs) > 0:
ifi = InterfaceInfo()
for i in ifs:
_, iprops = self.get_node_properties(node_id=i)
ifsl = self.interface_sliver_from_graph_properties_dict(iprops)
# Take child interfaces into account
ifsl = self.build_deep_interface_sliver(node_id=i)
#_, iprops = self.get_node_properties(node_id=i)
#ifsl = self.interface_sliver_from_graph_properties_dict(iprops)
ifi.add_interface(ifsl)
nss.interface_info = ifi
return nss
Expand Down Expand Up @@ -992,6 +1023,18 @@ def build_deep_interface_sliver(self, *, node_id: str) -> InterfaceSliver:
msg="Node is not of class Interface")
# create top-level sliver
isl = ABCPropertyGraph.interface_sliver_from_graph_properties_dict(props)

# find interfaces and attach
if isl.get_type() == InterfaceType.DedicatedPort and not isl.interface_info:
ifs = self.get_first_neighbor(node_id=node_id, rel=ABCPropertyGraph.REL_CONNECTS,
node_label=ABCPropertyGraph.CLASS_ConnectionPoint)
if ifs is not None and len(ifs) > 0:
ifi = InterfaceInfo()
for i in ifs:
_, iprops = self.get_node_properties(node_id=i)
ifsl = self.interface_sliver_from_graph_properties_dict(iprops)
ifi.add_interface(ifsl)
isl.interface_info = ifi
return isl

@staticmethod
Expand All @@ -1003,6 +1046,15 @@ def build_deep_interface_sliver_from_dict(*, props: Dict[str, Any]) -> Interface
"""
# create top-level sliver
isl = ABCPropertyGraph.interface_sliver_from_graph_properties_dict(props)

# find interfaces and attach
ifs = props.get('interfaces', None)
if ifs is not None and len(ifs) > 0:
ifi = InterfaceInfo()
for i in ifs:
ifsl = ABCPropertyGraph.interface_sliver_from_graph_properties_dict(i)
ifi.add_interface(ifsl)
isl.interface_info = ifi
return isl

def build_deep_link_sliver(self, *, node_id: str) -> NetworkLinkSliver:
Expand Down Expand Up @@ -1123,6 +1175,7 @@ def remove_cp_and_links(self, node_id: str):
parents = self.get_first_neighbor(node_id=node_id,
rel=ABCPropertyGraph.REL_CONNECTS,
node_label=ABCPropertyGraph.CLASS_ConnectionPoint)

for parent in parents: # really should only be one parent interface
children = self.get_first_neighbor(node_id=parent,
rel=ABCPropertyGraph.REL_CONNECTS,
Expand Down Expand Up @@ -1261,6 +1314,21 @@ def get_all_ns_or_link_connection_points(self, link_id: str) -> List[str]:
return self.get_first_neighbor(node_id=link_id, rel=ABCPropertyGraph.REL_CONNECTS,
node_label=ABCPropertyGraph.CLASS_ConnectionPoint)

def get_all_child_connection_points(self, interface_id: str) -> List[str]:
"""
Get child interfaces attached to a Dedicated Interface
:param interface_id:
:return:
"""
assert interface_id is not None
# check this is a link
labels, parent_props = self.get_node_properties(node_id=interface_id)
if ABCPropertyGraph.CLASS_ConnectionPoint not in labels:
raise PropertyGraphQueryException(graph_id=self.graph_id, node_id=interface_id,
msg="Node type is not ConnectionPoint")
return self.get_first_neighbor(node_id=interface_id, rel=ABCPropertyGraph.REL_CONNECTS,
node_label=ABCPropertyGraph.CLASS_ConnectionPoint)

def get_all_node_or_component_connection_points(self, parent_node_id: str) -> List[str]:
"""
Get a list of interfaces attached via network services
Expand Down
4 changes: 2 additions & 2 deletions fim/graph/data/graph_validation_rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
"msg": "All Components must be of type SharedNIC, SmartNIC, GPU, FPGA, Storage or NVME"
},
{
"rule": "MATCH (n:ConnectionPoint {GraphID: $graphId}) RETURN ALL(r IN collect(n) WHERE r.Type IN [\"AccessPort\", \"TrunkPort\", \"ServicePort\", \"DedicatedPort\", \"SharedPort\", \"vInt\", \"FacilityPort\", \"StitchPort\"])",
"msg": "All ConnectionPoints must be of type AccessPort, TrunkPort, ServicePort, DedicatedPort, SharedPort, vInt, FacilityPort or StitchPort"
"rule": "MATCH (n:ConnectionPoint {GraphID: $graphId}) RETURN ALL(r IN collect(n) WHERE r.Type IN [\"AccessPort\", \"TrunkPort\", \"ServicePort\", \"DedicatedPort\", \"SharedPort\", \"vInt\", \"FacilityPort\", \"SubInterface\", \"StitchPort\"])",
"msg": "All ConnectionPoints must be of type AccessPort, TrunkPort, ServicePort, DedicatedPort, SharedPort, vInt, FacilityPort, SubInterface or StitchPort"
},
{
"rule": "MATCH (n:NetworkService {GraphID: $graphId}) RETURN ALL(r IN collect(n) WHERE r.Type IN [ \"P4\", \"OVS\", \"MPLS\", \"VLAN\", \"L2Path\", \"L2Bridge\", \"L2PTP\", \"L2STS\", \"FABNetv4\", \"FABNetv6\", \"FABNetv4Ext\", \"FABNetv6Ext\", \"L3VPN\", \"PortMirror\"])",
Expand Down
11 changes: 11 additions & 0 deletions fim/graph/slices/abc_asm.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,14 @@ def find_connection_point_by_name(self, *, parent_node_id: str, iname: str) -> s
raise PropertyGraphQueryException(graph_id=self.graph_id, node_id=None,
msg=f"Unable to find ConnectionPoint with name {iname}")

def find_child_connection_point_by_name(self, *, parent_node_id: str, iname: str) -> str:

assert iname is not None

if_id_list = self.get_all_child_connection_points(interface_id=parent_node_id)
for cid in if_id_list:
_, cprops = self.get_node_properties(node_id=cid)
if cprops[ABCPropertyGraph.PROP_NAME] == iname:
return cid
raise PropertyGraphQueryException(graph_id=self.graph_id, node_id=None,
msg=f"Unable to find ConnectionPoint with name {iname}")
2 changes: 1 addition & 1 deletion fim/slivers/capacities_labels.py
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ def to_latlon(self) -> Tuple[float, float]:
raise LocationException(f"Unable to interpret response from OpenStreetmaps for address {self.postal}")

self.lat = float(response_json[0]['lat'])
self.lon = (response_json[0]['lon'])
self.lon = float(response_json[0]['lon'])
return self.lat, self.lon


Expand Down
54 changes: 52 additions & 2 deletions fim/slivers/interface_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import enum

from .base_sliver import BaseSliver
from .topology_diff import TopologyDiff
from .topology_diff import TopologyDiff, WhatsModifiedFlag, TopologyDiffTuple, TopologyDiffModifiedTuple
from .capacities_labels import Labels


Expand All @@ -44,6 +44,7 @@ class InterfaceType(enum.Enum):
vInt = enum.auto()
StitchPort = enum.auto()
FacilityPort = enum.auto()
SubInterface = enum.auto()

def help(self) -> str:
return 'An ' + self.name
Expand All @@ -64,6 +65,7 @@ def __init__(self):
super().__init__()
# note that these are used in ASMs, not delegateable
self.peer_labels = None
self.interface_info = None

def set_peer_labels(self, lab: Labels) -> None:
assert(lab is None or isinstance(lab, Labels))
Expand All @@ -81,7 +83,55 @@ def type_from_str(ntype: str) -> InterfaceType:
return t

def diff(self, other_sliver) -> TopologyDiff or None:
raise RuntimeError('Not implemented')
if not other_sliver:
return None

super().diff(other_sliver)

ifs_added = set()
ifs_removed = set()
ifs_modified = list()

# see if we ourselves have modified properties
self_modified = list()
self_modified_flags = self.prop_diff(other_sliver)
if self.prop_diff(other_sliver) != WhatsModifiedFlag.NONE:
self_modified.append((self, self_modified_flags))

if self.interface_info and other_sliver.interface_info:
diff_comps = self._dict_diff(self.interface_info.interfaces,
other_sliver.interface_info.interfaces)
ifs_added = set(diff_comps['added'].values())
ifs_removed = set(diff_comps['removed'].values())
# there are interfaces in common, so we check if they have been modified
ifs_common = self._dict_common(self.interface_info.interfaces,
other_sliver.interface_info.interfaces)
for iA in ifs_common.values():
iB = other_sliver.interface_info.get_interface(iA.resource_name)
# compare properties
flag = iA.prop_diff(iB)
if flag != WhatsModifiedFlag.NONE:
ifs_modified.append((iA, flag))

if not self.interface_info and other_sliver.interface_info:
ifs_added = set(other_sliver.interface_info.interfaces.values())

if self.interface_info and not other_sliver.interface_info:
ifs_removed = set(self.interface_info.interfaces.values())

if len(self_modified) > 0 or len(ifs_added) > 0 or len(ifs_removed) > 0 or len(ifs_modified) > 0:
return TopologyDiff(added=TopologyDiffTuple(components=set(), services=set(), interfaces=ifs_added,
nodes=set()),
removed=TopologyDiffTuple(components=set(), services=set(),
interfaces=ifs_removed, nodes=set()),
modified=TopologyDiffModifiedTuple(
nodes=list(),
components=list(),
services=self_modified,
interfaces=ifs_modified)
)
else:
return None


class InterfaceInfo:
Expand Down
9 changes: 9 additions & 0 deletions fim/slivers/network_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from recordclass import recordclass

from fim.slivers.capacities_labels import Location
from .attached_components import ComponentType
from .base_sliver import BaseSliver
from .topology_diff import TopologyDiff, TopologyDiffTuple, TopologyDiffModifiedTuple, WhatsModifiedFlag
from fim.slivers.maintenance_mode import MaintenanceInfo
Expand Down Expand Up @@ -191,6 +192,14 @@ def diff(self, other_sliver) -> TopologyDiff or None:
cB = other_sliver.attached_components_info.get_device(cA.resource_name)
# compare properties
flag = cA.prop_diff(cB)

# compare child interfaces
if cA.get_type() == ComponentType.SmartNIC:
cAns = list(cA.network_service_info.network_services.values())[0]
cBns = list(cB.network_service_info.network_services.values())[0]
if cAns.diff(cBns):
flag |= WhatsModifiedFlag.SUB_INTERFACES

if flag != WhatsModifiedFlag.NONE:
comp_modified.append((cA, flag))

Expand Down
8 changes: 7 additions & 1 deletion fim/slivers/network_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ class NetworkServiceSliver(BaseSliver):
num_interfaces=1, num_sites=1,
num_instances=NO_LIMIT,
desc='A port mirroring service in a FABRIC site.',
required_properties=['mirror_port', 'mirror_vlan',
required_properties=['mirror_port',
'mirror_direction', 'site'],
forbidden_properties=['controller_url'],
required_interface_types=[]),
Expand Down Expand Up @@ -390,6 +390,12 @@ def diff(self, other_sliver) -> TopologyDiff or None:
iB = other_sliver.interface_info.get_interface(iA.resource_name)
# compare properties
flag = iA.prop_diff(iB)

if iA.get_type() == InterfaceType.DedicatedPort:
if iA.diff(iB):
print("Added the child interfaces")
flag |= WhatsModifiedFlag.SUB_INTERFACES

if flag != WhatsModifiedFlag.NONE:
ifs_modified.append((iA, flag))

Expand Down
1 change: 1 addition & 0 deletions fim/slivers/topology_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class WhatsModifiedFlag(Flag):
LABELS = auto()
CAPACITIES = auto()
USER_DATA = auto()
SUB_INTERFACES = auto()


@dataclasses.dataclass
Expand Down
Loading

0 comments on commit 3691413

Please sign in to comment.