Passed
Push — master ( 61cfcd...d7abc2 )
by Vinicius
15:29 queued 13:28
created
Severity
1
"""Module Graph of kytos/pathfinder Kytos Network Application."""
2
3
# pylint: disable=too-many-arguments,too-many-locals
4 1
from itertools import combinations, islice
5
6 1
from kytos.core import log
7 1
from kytos.core.common import EntityStatus
8 1
from napps.kytos.pathfinder.utils import (filter_ge, filter_in, filter_le,
9
                                          lazy_filter, nx_edge_data_delay,
10
                                          nx_edge_data_priority,
11
                                          nx_edge_data_weight)
12
13 1
try:
14 1
    import networkx as nx
15 1
    from networkx.exception import NetworkXNoPath, NodeNotFound
16
except ImportError:
17
    PACKAGE = "networkx==2.5.1"
18
    log.error(f"Package {PACKAGE} not found. Please 'pip install {PACKAGE}'")
19
20
21 1
class KytosGraph:
22
    """Class responsible for the graph generation."""
23
24 1
    def __init__(self):
25 1
        self.graph = nx.Graph()
26 1
        self._filter_functions = {
27
            "ownership": lazy_filter(str, filter_in("ownership")),
28
            "bandwidth": lazy_filter((int, float), filter_ge("bandwidth")),
29
            "reliability": lazy_filter((int, float), filter_ge("reliability")),
30
            "priority": lazy_filter((int, float), filter_le("priority")),
31
            "utilization": lazy_filter((int, float), filter_le("utilization")),
32
            "delay": lazy_filter((int, float), filter_le("delay")),
33
        }
34 1
        self.spf_edge_data_cbs = {
35
            "hop": nx_edge_data_weight,
36
            "delay": nx_edge_data_delay,
37
            "priority": nx_edge_data_priority,
38
        }
39
40 1
    def clear(self):
41
        """Remove all nodes and links registered."""
42 1
        self.graph.clear()
43
44 1
    def update_topology(self, topology):
45
        """Update all nodes and links inside the graph."""
46 1
        self.graph.clear()
47 1
        self.update_nodes(topology.switches)
48 1
        self.update_links(topology.links)
49
50 1
    def update_nodes(self, nodes):
51
        """Update all nodes inside the graph."""
52 1
        for node in nodes.values():
53 1
            try:
54 1
                if node.status != EntityStatus.UP:
55 1
                    continue
56 1
                self.graph.add_node(node.id)
57
58 1
                for interface in node.interfaces.values():
59 1
                    if interface.status == EntityStatus.UP:
60 1
                        self.graph.add_node(interface.id)
61 1
                        self.graph.add_edge(node.id, interface.id)
62
63 1
            except AttributeError as err:
64 1
                raise TypeError(
65
                    f"Error when updating nodes inside the graph: {str(err)}"
66
                )
67
68 1
    def update_links(self, links):
69
        """Update all links inside the graph."""
70 1
        for link in links.values():
71 1
            if link.status == EntityStatus.UP:
72 1
                self.graph.add_edge(link.endpoint_a.id, link.endpoint_b.id)
73 1
                self.update_link_metadata(link)
74
75 1
    def update_link_metadata(self, link):
76
        """Update link metadata."""
77 1
        for key, value in link.metadata.items():
78 1
            if key not in self._filter_functions:
79 1
                continue
80 1
            endpoint_a = link.endpoint_a.id
81 1
            endpoint_b = link.endpoint_b.id
82 1
            self.graph[endpoint_a][endpoint_b][key] = value
83
84 1
    def get_link_metadata(self, endpoint_a, endpoint_b):
85
        """Return the metadata of a link."""
86 1
        return self.graph.get_edge_data(endpoint_a, endpoint_b)
87
88 1
    @staticmethod
89 1
    def _remove_switch_hops(circuit):
90
        """Remove switch hops from a circuit hops list."""
91 1
        for hop in circuit["hops"]:
92 1
            if len(hop.split(":")) == 8:
93 1
                circuit["hops"].remove(hop)
94
95 1
    def _path_cost(self, path, weight="hop", default_cost=1):
96
        """Compute the path cost given an attribute."""
97 1
        cost = 0
98 1
        for node, nbr in nx.utils.pairwise(path):
99 1
            cost += self.graph[node][nbr].get(weight, default_cost)
100 1
        return cost
101
102 1
    def path_cost_builder(self, paths, weight="hop", default_weight=1):
103
        """Build the cost of a path given a list of paths."""
104 1
        paths_acc = []
105 1
        for path in paths:
106 1
            if isinstance(path, list):
107 1
                paths_acc.append(
108
                    {
109
                        "hops": path,
110
                        "cost": self._path_cost(
111
                            path, weight=weight, default_cost=default_weight
112
                        ),
113
                    }
114
                )
115 1
            elif isinstance(path, dict):
116 1
                path["cost"] = self._path_cost(
117
                    path["hops"], weight=weight, default_cost=default_weight
118
                )
119 1
                paths_acc.append(path)
120
            else:
121
                raise TypeError(
122
                    f"type: '{type(path)}' must be be either list or dict. "
123
                    f"path: {path}"
124
                )
125 1
        return paths_acc
126
127 1
    def k_shortest_paths(
128
        self, source, destination, weight=None, k=1, graph=None
129
    ):
130
        """
131
        Compute up to k shortest paths and return them.
132
133
        This procedure is based on algorithm by Jin Y. Yen [1].
134
        Since Yen's algorithm calls Dijkstra's up to k times, the time
135
        complexity will be proportional to K * Dijkstra's, average
136
        O(K(|V| + |E|)logV), assuming it's using a heap, where V is the
137
        number of vertices and E number of egdes.
138
139
        References
140
        ----------
141
        .. [1] Jin Y. Yen, "Finding the K Shortest Loopless Paths in a
142
           Network", Management Science, Vol. 17, No. 11, Theory Series
143
           (Jul., 1971), pp. 712-716.
144
        """
145 1
        try:
146 1
            return list(
147
                islice(
148
                    nx.shortest_simple_paths(
149
                        graph or self.graph,
150
                        source,
151
                        destination,
152
                        weight=weight,
153
                    ),
154
                    k,
155
                )
156
            )
157 1
        except (NodeNotFound, NetworkXNoPath):
158 1
            return []
159
160 1
    def constrained_k_shortest_paths(
161
        self,
162
        source,
163
        destination,
164
        weight=None,
165
        k=1,
166
        minimum_hits=None,
167
        **metrics,
168
    ):
169
        """Calculate the constrained shortest paths with flexibility."""
170 1
        mandatory_metrics = metrics.get("mandatory_metrics", {})
171 1
        flexible_metrics = metrics.get("flexible_metrics", {})
172 1
        first_pass_links = list(
173
            self._filter_links(
174
                self.graph.edges(data=True), **mandatory_metrics
175
            )
176
        )
177 1
        length = len(flexible_metrics)
178 1
        if minimum_hits is None:
179 1
            minimum_hits = 0
180 1
        minimum_hits = min(length, max(0, minimum_hits))
181
182 1
        paths = []
183 1
        for i in range(length, minimum_hits - 1, -1):
184 1
            for combo in combinations(flexible_metrics.items(), i):
185 1
                additional = dict(combo)
186 1
                filtered_links = self._filter_links(
187
                    first_pass_links, **additional
188
                )
189 1
                filtered_links = ((u, v) for u, v, d in filtered_links)
0 ignored issues
show
The variable u does not seem to be defined for all execution paths.
Loading history...
The variable v does not seem to be defined for all execution paths.
Loading history...
190 1
                for path in self.k_shortest_paths(
191
                    source,
192
                    destination,
193
                    weight=weight,
194
                    k=k,
195
                    graph=self.graph.edge_subgraph(filtered_links),
196
                ):
197 1
                    paths.append(
198
                        {
199
                            "hops": path,
200
                            "metrics": {**mandatory_metrics, **additional},
201
                        }
202
                    )
203 1
                if len(paths) == k:
204 1
                    return paths
205 1
            if paths:
206 1
                return paths
207 1
        return paths
208
209 1
    def _filter_links(self, links, **metrics):
210 1
        for metric, value in metrics.items():
211 1
            filter_func = self._filter_functions.get(metric, None)
212 1
            if filter_func is not None:
213 1
                try:
214 1
                    links = filter_func(value, links)
215 1
                except TypeError as err:
216 1
                    raise TypeError(
217
                        f"Error in {metric} value: {value} err: {err}"
218
                    )
219
        return links
220