Skip to content

Commit

Permalink
Merge pull request #203 from fabric-testbed/202-add-support-to-query-…
Browse files Browse the repository at this point in the history
…path-between-two-nodes-containing-given-hops

202 add support to query path between two nodes containing given hops
  • Loading branch information
kthare10 authored Jun 4, 2024
2 parents 21d40ac + 20c8cfa commit 959cf32
Show file tree
Hide file tree
Showing 9 changed files with 1,270 additions and 4 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.0b4"
__VERSION__ = "1.7.0b6"
__version__ = __VERSION__
12 changes: 12 additions & 0 deletions fim/graph/abc_property_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,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
41 changes: 41 additions & 0 deletions fim/graph/neo4j_property_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"""
Neo4j-specific implementation of property graph abstraction
"""
from collections import deque
from typing import Dict, Any, Tuple, List, Set

import logging
Expand Down Expand Up @@ -416,6 +417,46 @@ def get_nodes_on_shortest_path(self, *, node_a: str, node_z: str, rel: str = Non
return list()
return val.data()['nodeids']

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.
"""
assert node_a is not None
assert node_z is not None

query = "MATCH (a:GraphNode {GraphID: $graphId, NodeID: $nodeA}), " \
"(z:GraphNode {GraphID: $graphId, NodeID: $nodeZ}) " \
"CALL apoc.algo.allSimplePaths(a, z, 'connects|has', $cut_off) " \
"YIELD path AS path WITH path, relationships(path) AS rels " \
"WHERE size(rels) = size(apoc.coll.toSet(rels)) " \
"RETURN [node in nodes(path) | node.NodeID] AS nodeids"

path_nodes = []
with self.driver.session() as session:
result = session.run(query, graphId=self.graph_id, nodeA=node_a, nodeZ=node_z, cut_off=cut_off)
for record in result:
path_nodes.append(record["nodeids"])

if not len(path_nodes):
return []

result = []
for path in path_nodes:
# Check all hops are in path
if all(hop in path for hop in hops):
# Update shortest path
if not len(result) or len(result) > len(path):
result = path

return result

def get_first_neighbor(self, *, node_id: str, rel: str, node_label: str) -> List[str]:
"""
Return a list of ids of nodes of this label related via relationship. List may be empty.
Expand Down
40 changes: 40 additions & 0 deletions fim/graph/networkx_property_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,46 @@ def get_nodes_on_shortest_path(self, *, node_a: str, node_z: str, rel: str = Non
return list()
return self._get_node_ids_for_list(graph, sp)

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.
"""
# extract a graph
graph = self.storage.extract_graph(self.graph_id)
if graph is None:
raise PropertyGraphQueryException(graph_id=self.graph_id,
msg="Unable to find graph")
real_node_a = self._find_node(node_id=node_a)
real_node_z = self._find_node(node_id=node_z)

try:
all_paths = nx.all_simple_paths(graph, real_node_a, real_node_z, cutoff=cut_off)
except nx.exception.NetworkXNoPath:
return list()

result = []
for path in all_paths:
subgraph = graph.subgraph(path)
# check no cycles
cycles = nx.cycle_basis(subgraph)
if len(cycles):
continue
# Extract Node Id for all nodes in Path
path_node_ids = self._get_node_ids_for_list(graph, path)
# Check all hops are in path
if all(hop in path_node_ids for hop in hops):
# Update the result if this path is shortest
if not len(result) or len(result) > len(path_node_ids):
result = path_node_ids
return result

def get_first_neighbor(self, *, node_id: str, rel: str, node_label: str) -> List:
"""
Return a list of ids of nodes of this label related via relationship. List may be empty.
Expand Down
3 changes: 2 additions & 1 deletion fim/slivers/network_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ class NetworkServiceSliver(BaseSliver):
required_properties=[],
forbidden_properties=['mirror_port',
'mirror_direction',
'controller_url'],
'controller_url',
"ero"],
required_interface_types=[]),
ServiceType.L2PTP: ServiceConstraintRecord(layer=NSLayer.L2, min_interfaces=2,
num_interfaces=2, num_sites=2,
Expand Down
Loading

0 comments on commit 959cf32

Please sign in to comment.