Completed
Pull Request — master (#58)
by
unknown
02:30
created

build.graph.KytosGraph.__init__()   B

Complexity

Conditions 7

Size

Total Lines 26
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 7.0671

Importance

Changes 0
Metric Value
cc 7
eloc 22
nop 1
dl 0
loc 26
rs 7.952
c 0
b 0
f 0
ccs 16
cts 18
cp 0.8889
crap 7.0671
1
"""Module Graph of kytos/pathfinder Kytos Network Application."""
2
3 1
from itertools import combinations
4
5 1
from kytos.core import log
6 1
7 1
try:
8
    import networkx as nx
9
    from networkx.exception import NodeNotFound, NetworkXNoPath
10
except ImportError:
11
    PACKAGE = 'networkx>=2.2'
12
    log.error(f"Package {PACKAGE} not found. Please 'pip install {PACKAGE}'")
13 1
14
15
class Filter:
16 1
    """Class responsible for removing items with disqualifying values."""
17 1
18
    def __init__(self, filter_type, filter_function):
19 1
        self._filter_type = filter_type
20
        self._filter_function = filter_function
21 1
22
    def run(self, value, items):
23 1
        """Filter out items. Filter chosen is picked at runtime."""
24
        if isinstance(value, self._filter_type):
25 1
            return filter(self._filter_function(value), items)
26 1
27 1
        raise TypeError(f"Expected type: {self._filter_type}")
28
29 1
30
class KytosGraph:
31 1
    """Class responsible for the graph generation."""
32 1
33 1
    def __init__(self):
34
        self.graph = nx.Graph()
35 1
        self._filter_functions = {}
36 1
37 1
        def filter_leq(metric):  # Lower values are better
38
            return lambda x: (lambda y: y[2].get(metric, x) <= x)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable x does not seem to be defined.
Loading history...
39
40
        def filter_geq(metric):  # Higher values are better
41
            return lambda x: (lambda y: y[2].get(metric, x) >= x)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable x does not seem to be defined.
Loading history...
42 1
43
        def filter_eeq(metric):  # Equivalence
44 1
            return lambda x: (lambda y: y[2].get(metric, x) == x)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable x does not seem to be defined.
Loading history...
45 1
46 1
        self._filter_functions["ownership"] = Filter(
47 1
            str, filter_eeq("ownership"))
48 1
        self._filter_functions["bandwidth"] = Filter(
49 1
            (int, float), filter_geq("bandwidth"))
50 1
        self._filter_functions["priority"] = Filter(
51 1
            (int, float), filter_geq("priority"))
52 1
        self._filter_functions["reliability"] = Filter(
53
            (int, float), filter_geq("reliability"))
54 1
        self._filter_functions["utilization"] = Filter(
55
            (int, float), filter_leq("utilization"))
56 1
        self._filter_functions["delay"] = Filter(
57
            (int, float), filter_leq("delay"))
58
        self._path_function = nx.all_shortest_paths
59
60
    def set_path_function(self, path_function):
61
        """Set the shortest path function."""
62 1
        self._path_function = path_function
63 1
64 1
    def clear(self):
65 1
        """Remove all nodes and links registered."""
66
        self.graph.clear()
67 1
68
    def update_topology(self, topology):
69
        """Update all nodes and links inside the graph."""
70 1
        self.graph.clear()
71 1
        self.update_nodes(topology.switches)
72 1
        self.update_links(topology.links)
73
74 1
    def update_nodes(self, nodes):
75
        """Update all nodes inside the graph."""
76 1
        for node in nodes.values():
77 1
            try:
78
                self.graph.add_node(node.id)
79
80
                for interface in node.interfaces.values():
81
                    self.graph.add_node(interface.id)
82 1
                    self.graph.add_edge(node.id, interface.id)
83
84
            except AttributeError:
85
                pass
86
87
    def update_links(self, links):
88
        """Update all links inside the graph."""
89
        keys = []
90
        for link in links.values():
91
            if link.is_active():
92
                self.graph.add_edge(link.endpoint_a.id, link.endpoint_b.id)
93
                for key, value in link.metadata.items():
94
                    keys.append(key)
95
                    endpoint_a = link.endpoint_a.id
96
                    endpoint_b = link.endpoint_b.id
97
                    self.graph[endpoint_a][endpoint_b][key] = value
98
99
    def get_metadata_from_link(self, endpoint_a, endpoint_b):
100
        """Return the metadata of a link."""
101
        return self.graph.edges[endpoint_a, endpoint_b]
102
103
    @staticmethod
104
    def _remove_switch_hops(circuit):
105
        """Remove switch hops from a circuit hops list."""
106
        for hop in circuit['hops']:
107
            if len(hop.split(':')) == 8:
108
                circuit['hops'].remove(hop)
109
110
    def shortest_paths(self, source, destination, parameter=None):
111
        """Calculate the shortest paths and return them."""
112
        try:
113
            paths = list(self._path_function(self.graph,
114
                                             source, destination, parameter))
115
        except (NodeNotFound, NetworkXNoPath):
116
            return []
117
        return paths
118
119
    def constrained_flexible_paths(self, source, destination,
120
                                   maximum_misses=None, **metrics):
121
        """Calculate the constrained shortest paths with flexibility."""
122
        base = metrics.get("base", {})
123
        flexible = metrics.get("flexible", {})
124
        # Retrieve subgraph with edges that meet base requirements.
125
        default_edge_list = list(self._filter_edges(
126
            self.graph.edges(data=True), **base))
127
        length = len(flexible)
128
        if maximum_misses is None:
129
            maximum_misses = length
130
        maximum_misses = min(length, max(0, maximum_misses))
131
        results = []
132
        paths = []
133
        i = 0
134
        # Create "sub-subgraphs" from original subgraph by trimming edges
135
        # that miss flexible requirement combinations. Search for a shortest
136
        # path in each of these graphs, until at least one is found.
137
        while (paths == [] and i in range(0, maximum_misses+1)):
138
            for combo in combinations(flexible.items(), length-i):
139
                additional = dict(combo)
140
                paths = self._constrained_shortest_paths(
141
                    source, destination, ((u, v) for u, v, d in
0 ignored issues
show
introduced by
The variable v does not seem to be defined for all execution paths.
Loading history...
introduced by
The variable u does not seem to be defined for all execution paths.
Loading history...
142
                                          self._filter_edges(default_edge_list,
143
                                                             **additional)))
144
                if paths != []:
145
                    results.append(
146
                        {"paths": paths, "metrics": {**base, **additional}})
147
            i = i + 1
148
        return results
149
150
    def _constrained_shortest_paths(self, source, destination, edges):
151
        paths = []
152
        try:
153
            paths = list(self._path_function(self.graph.edge_subgraph(edges),
154
                                             source, destination))
155
        except NetworkXNoPath:
156
            pass
157
        except NodeNotFound:
158
            if source == destination:
159
                if source in self.graph.nodes:
160
                    paths = [[source]]
161
        return paths
162
163
    def _filter_edges(self, edges, **metrics):
164
        for metric, value in metrics.items():
165
            filter_ = self._filter_functions.get(metric, None)
166
            if filter_ is not None:
167
                try:
168
                    edges = filter_.run(value, edges)
169
                except TypeError as err:
170
                    raise TypeError(f"Error in {metric} filter: {err}")
171
        return edges
172