Skip to content

Commit

Permalink
Renamed and moved tests
Browse files Browse the repository at this point in the history
 - test_graph now test_search_results
 - test_graph1, 2, 3 now test_search1_results1, 2, 3
 - fixed test_constrained_path9 in test_search_results1
 - class names changed to reflect new file names
 - moved files to unit folder

Fixed padding in graph.py

Cleaned up constrained_flexible_paths and added comments

 - compacted default_edge_list and flexible assignments
 - renamed some variables
 - changed loop from for to while

Removed failing tests for checking

Fixed formatting issues

Fixed more formatting issues

Minimized local variable count

 - 5 parameters instead of 6
 - 17 local vars instead of 18

Fixed all formatting errors

Fixed minor formatting error

Changed logic to support code reuse

Revamped parameter passing.

 - calling constrained flexible paths is now more user-friendly
 - test updated to reflect changes.

Changed REST API parameters

Fixed KytosGraph import in Main

Implement Humberto's fixes

Updated comments and changed endpoints for new UI

Added missing endpoint changes

Updated KytosGraph, TestResults and subclasses
 - KytosGraph
   - set_path_function removed.
 - TestResults
   - test_setup method removed.
   - setup method renamed to setUp to run at the start of every test
 - TestResultsSimple, TestResultsMetaData, TestResultsEdges
   - Removed explicit setup call

Gave better descriptions to test subclasses.

Undid KytosGraph import change in Main.

Linting fixes
 - TestResults
   - removed unused import.
 - TestResultsMetadata
   - fixed two methods with same name.

Fixed JSON serialization of error message

Re-added methods and reverted update_links

Fixed linting issue

changed extend back to append

Update tests/unit/test_results.py

Standardized decorators

Co-authored-by: Gleyberson Andrade <[email protected]>
Updated return values of shortest_constrained_path
   - Returns 400 BAD REQUEST if user provides an illegal attribute value

Remove space from decorator

Changed maximum misses variable to minimum hits
   - This is to reflect changes from kytos#62

Co-authored-by: Humberto Diógenes <[email protected]>
  • Loading branch information
MarvinTorres and hdiogenes committed Jul 29, 2020
1 parent 982c7a4 commit 2b8929e
Show file tree
Hide file tree
Showing 9 changed files with 572 additions and 604 deletions.
138 changes: 79 additions & 59 deletions graph.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Module Graph of kytos/pathfinder Kytos Network Application."""

from itertools import combinations

from kytos.core import log

try:
Expand All @@ -9,47 +11,51 @@
PACKAGE = 'networkx>=2.2'
log.error(f"Package {PACKAGE} not found. Please 'pip install {PACKAGE}'")

from itertools import combinations

class Filter:
"""Class responsible for removing items with disqualifying values."""

def __init__(self, filter_type, filter_function):
self._filter_type = filter_type
self._filter_fun = filter_function
self._filter_function = filter_function

def run(self,value, items):
def run(self, value, items):
"""Filter out items. Filter chosen is picked at runtime."""
if isinstance(value, self._filter_type):
fun0 = self._filter_fun(value)
return filter(fun0, items)
else:
raise TypeError(f"Expected type: {self._filter_type}")
return filter(self._filter_function(value), items)

raise TypeError(f"Expected type: {self._filter_type}")


class KytosGraph:
"""Class responsible for the graph generation."""

def __init__(self):
self.graph = nx.Graph()
self._filter_fun_dict = {}
def filterLEQ(metric):# Lower values are better
return lambda x: (lambda y: y[2].get(metric,x) <= x)
def filterGEQ(metric):# Higher values are better
return lambda x: (lambda y: y[2].get(metric,x) >= x)
def filterEEQ(metric):# Equivalence
return lambda x: (lambda y: y[2].get(metric,x) == x)


self._filter_fun_dict["ownership"] = Filter(str,filterEEQ("ownership"))
self._filter_fun_dict["bandwidth"] = Filter((int,float),filterGEQ("bandwidth"))
self._filter_fun_dict["priority"] = Filter((int,float),filterGEQ("priority"))
self._filter_fun_dict["reliability"] = Filter((int,float),filterGEQ("reliability"))
self._filter_fun_dict["utilization"] = Filter((int,float),filterLEQ("utilization"))
self._filter_fun_dict["delay"] = Filter((int,float),filterLEQ("delay"))
self._path_fun = nx.all_shortest_paths


def set_path_fun(self, path_fun):
self._path_fun = path_fun
self._filter_functions = {}

def filter_leq(metric): # Lower values are better
return lambda x: (lambda y: y[2].get(metric, x) <= x)

def filter_geq(metric): # Higher values are better
return lambda x: (lambda y: y[2].get(metric, x) >= x)

def filter_eeq(metric): # Equivalence
return lambda x: (lambda y: y[2].get(metric, x) == x)

self._filter_functions["ownership"] = Filter(
str, filter_eeq("ownership"))
self._filter_functions["bandwidth"] = Filter(
(int, float), filter_geq("bandwidth"))
self._filter_functions["priority"] = Filter(
(int, float), filter_geq("priority"))
self._filter_functions["reliability"] = Filter(
(int, float), filter_geq("reliability"))
self._filter_functions["utilization"] = Filter(
(int, float), filter_leq("utilization"))
self._filter_functions["delay"] = Filter(
(int, float), filter_leq("delay"))
self._path_function = nx.all_shortest_paths

def clear(self):
"""Remove all nodes and links registered."""
Expand Down Expand Up @@ -86,6 +92,19 @@ def update_links(self, links):
endpoint_b = link.endpoint_b.id
self.graph[endpoint_a][endpoint_b][key] = value

self._set_default_metadata(keys)

def _set_default_metadata(self, keys):
"""Set metadata to all links.
Set the value to zero for inexistent metadata in a link to make those
irrelevant in pathfinding.
"""
for key in keys:
for endpoint_a, endpoint_b in self.graph.edges:
if key not in self.graph[endpoint_a][endpoint_b]:
self.graph[endpoint_a][endpoint_b][key] = 0

def get_metadata_from_link(self, endpoint_a, endpoint_b):
"""Return the metadata of a link."""
return self.graph.edges[endpoint_a, endpoint_b]
Expand All @@ -100,44 +119,45 @@ def _remove_switch_hops(circuit):
def shortest_paths(self, source, destination, parameter=None):
"""Calculate the shortest paths and return them."""
try:
paths = list(self._path_fun(self.graph,
source, destination, parameter))
paths = list(nx.shortest_simple_paths(self.graph,
source, destination,
parameter))
except (NodeNotFound, NetworkXNoPath):
return []
return paths

def constrained_flexible_paths(self, source, destination, metrics, flexible_metrics, flexible = None):
default_edge_list = self.graph.edges(data=True)
default_edge_list = self._filter_edges(default_edge_list,**metrics)
default_edge_list = list(default_edge_list)
length = len(flexible_metrics)
if flexible is None:
flexible = length
flexible = max(0,flexible)
flexible = min(length,flexible)
def constrained_flexible_paths(self, source, destination,
minimum_hits=None, **metrics):
"""Calculate the constrained shortest paths with flexibility."""
base = metrics.get("base", {})
flexible = metrics.get("flexible", {})
default_edge_list = list(self._filter_edges(
self.graph.edges(data=True), **base))
length = len(flexible)
if minimum_hits is None:
minimum_hits = length
minimum_hits = min(length, max(0, minimum_hits))
results = []
stop = False
for i in range(0,flexible+1):
if stop:
break
y = combinations(flexible_metrics.items(),length-i)
for x in y:
tempDict = {}
for k,v in x:
tempDict[k] = v
edges = self._filter_edges(default_edge_list,**tempDict)
edges = ((u,v) for u,v,d in edges)
res0 = self._constrained_shortest_paths(source,destination,edges)
if res0 != []:
results.append({"paths":res0, "metrics":{**metrics, **tempDict}})
stop = True
paths = []
i = 0
while (paths == [] and i in range(0, minimum_hits+1)):
for combo in combinations(flexible.items(), length-i):
additional = dict(combo)
paths = self._constrained_shortest_paths(
source, destination, ((u, v) for u, v, d in
self._filter_edges(default_edge_list,
**additional)))
if paths != []:
results.append(
{"paths": paths, "metrics": {**base, **additional}})
i = i + 1
return results

def _constrained_shortest_paths(self, source, destination, edges):
paths = []
try:
paths = list(self._path_fun(self.graph.edge_subgraph(edges),
source, destination))
paths = list(self._path_function(self.graph.edge_subgraph(edges),
source, destination))
except NetworkXNoPath:
pass
except NodeNotFound:
Expand All @@ -148,10 +168,10 @@ def _constrained_shortest_paths(self, source, destination, edges):

def _filter_edges(self, edges, **metrics):
for metric, value in metrics.items():
fil = self._filter_fun_dict.get(metric, None)
if fil != None:
filter_ = self._filter_functions.get(metric, None)
if filter_ is not None:
try:
edges = fil.run(value,edges)
edges = filter_.run(value, edges)
except TypeError as err:
raise TypeError(f"Error in {metric} filter: {err}")
raise TypeError(f"Error in {metric} value: {err}")
return edges
30 changes: 8 additions & 22 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,38 +89,24 @@ def shortest_path(self):
paths = self._filter_paths(paths, desired, undesired)
return jsonify({'paths': paths})

@rest('v3/', methods=['POST'])
@rest('v2/best-constrained-paths', methods=['POST'])
def shortest_constrained_path(self):
"""Get the set of shortest paths between the source and destination."""
data = request.get_json()

source = data.get('source')
destination = data.get('destination')
flexible = data.get('flexible', 0)
metrics = data.get('metrics',{})
try:
paths = self.graph.constrained_flexible_paths(source, destination,{},metrics,flexible)
return jsonify(paths)
except TypeError as err:
return jsonify({"error":err})


@rest('v4/', methods=['POST'])
def shortest_constrained_path2(self):
"""Get the set of shortest paths between the source and destination."""
data = request.get_json()

source = data.get('source')
destination = data.get('destination')
metrics = data.get('metrics',{})
flexible_metrics = data.get('flexibleMetrics', {})
base_metrics = data.get('base_metrics', {})
fle_metrics = data.get('flexible_metrics', {})
minimum_hits = data.get('minimum_flexible_hits')
try:
paths = self.graph.constrained_flexible_paths(source, destination,
metrics, flexible_metrics)
minimum_hits,
base=base_metrics,
flexible=fle_metrics)
return jsonify(paths)
except TypeError as err:
return jsonify({"error":err})

return jsonify({"error": str(err)}), 400

@listen_to('kytos.topology.updated')
def update_topology(self, event):
Expand Down
74 changes: 0 additions & 74 deletions tests/test_graph.py

This file was deleted.

Loading

0 comments on commit 2b8929e

Please sign in to comment.