DynamicPathManager.set_controller()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
"""Classes related to paths"""
2
import requests
3
4
from kytos.core import log
5
from kytos.core.common import EntityStatus, GenericEntity
6
from kytos.core.link import Link
7
from napps.kytos.mef_eline import settings
8
from napps.kytos.mef_eline.exceptions import InvalidPath
9
10
11
class Path(list, GenericEntity):
12
    """Class to represent a Path."""
13
14
    def __eq__(self, other=None):
15
        """Compare paths."""
16
        if not other or not isinstance(other, Path):
17
            return False
18
        return super().__eq__(other)
19
20
    def is_affected_by_link(self, link=None):
21
        """Verify if the current path is affected by link."""
22
        if not link:
23
            return False
24
        return link in self
25
26
    def link_affected_by_interface(self, interface=None):
27
        """Return the link using this interface, if any, or None otherwise."""
28
        if not interface:
29
            return None
30
        for link in self:
31
            if interface in (link.endpoint_a, link.endpoint_b):
32
                return link
33
        return None
34
35
    def choose_vlans(self):
36
        """Choose the VLANs to be used for the circuit."""
37
        for link in self:
38
            tag = link.get_next_available_tag()
39
            link.add_metadata("s_vlan", tag)
40
41
    def make_vlans_available(self):
42
        """Make the VLANs used in a path available when undeployed."""
43
        for link in self:
44
            link.make_tag_available(link.get_metadata("s_vlan"))
45
            link.remove_metadata("s_vlan")
46
47
    def is_valid(self, switch_a, switch_z, is_scheduled=False):
48
        """Check if this is a valid path."""
49
        previous = switch_a
50
        for link in self:
51
            if link.endpoint_a.switch != previous:
52
                raise InvalidPath(
53
                    f"{link.endpoint_a} switch is different" f" from previous."
54
                )
55
            if is_scheduled is False and (
56
                link.endpoint_a.link is None
57
                or link.endpoint_a.link != link
58
                or link.endpoint_b.link is None
59
                or link.endpoint_b.link != link
60
            ):
61
                raise InvalidPath(f"Link {link} is not available.")
62
            previous = link.endpoint_b.switch
63
        if previous == switch_z:
64
            return True
65
        raise InvalidPath(f"Last endpoint is different from uni_z")
66
67
    @property
68
    def status(self):
69
        """Check for the  status of a path.
70
71
        If any link in this path is down, the path is considered down.
72
        """
73
        if not self:
74
            return EntityStatus.DISABLED
75
76
        endpoint = "%s/%s" % (settings.TOPOLOGY_URL, "links")
77
        api_reply = requests.get(endpoint)
78
        if api_reply.status_code != getattr(requests.codes, "ok"):
79
            log.error(
80
                "Failed to get links at %s. Returned %s",
81
                endpoint,
82
                api_reply.status_code,
83
            )
84
            return None
85
        links = api_reply.json()["links"]
86
        return_status = EntityStatus.UP
87
        for path_link in self:
88
            try:
89
                link = links[path_link.id]
90
            except KeyError:
91
                return EntityStatus.DISABLED
92
            if link["enabled"] is False:
93
                return EntityStatus.DISABLED
94
            if link["active"] is False:
95
                return_status = EntityStatus.DOWN
96
        return return_status
97
98
    def as_dict(self):
99
        """Return list comprehension of links as_dict."""
100
        return [link.as_dict() for link in self if link]
101
102
103
class DynamicPathManager:
104
    """Class to handle and create paths."""
105
106
    controller = None
107
108
    @classmethod
109
    def set_controller(cls, controller=None):
110
        """Set the controller to discovery news paths."""
111
        cls.controller = controller
112
113
    @staticmethod
114
    def get_paths(circuit):
115
        """Get a valid path for the circuit from the Pathfinder."""
116
        endpoint = settings.PATHFINDER_URL
117
        request_data = {
118
            "source": circuit.uni_a.interface.id,
119
            "destination": circuit.uni_z.interface.id,
120
        }
121
        api_reply = requests.post(endpoint, json=request_data)
122
123
        if api_reply.status_code != getattr(requests.codes, "ok"):
124
            log.error(
125
                "Failed to get paths at %s. Returned %s",
126
                endpoint,
127
                api_reply.status_code,
128
            )
129
            return None
130
        reply_data = api_reply.json()
131
        return reply_data.get("paths")
132
133
    @staticmethod
134
    def _clear_path(path):
135
        """Remove switches from a path, returning only interfaces."""
136
        return [endpoint for endpoint in path if len(endpoint) > 23]
137
138
    @classmethod
139
    def get_best_path(cls, circuit):
140
        """Return the best path available for a circuit, if exists."""
141
        paths = cls.get_paths(circuit)
142
        if paths:
143
            return cls.create_path(cls.get_paths(circuit)[0]["hops"])
144
        return None
145
146
    @classmethod
147
    def get_best_paths(cls, circuit):
148
        """Return the best paths available for a circuit, if they exist."""
149
        for path in cls.get_paths(circuit):
150
            yield cls.create_path(path["hops"])
151
152
    @classmethod
153
    def create_path(cls, path):
154
        """Return the path containing only the interfaces."""
155
        new_path = Path()
156
        clean_path = cls._clear_path(path)
157
158
        if len(clean_path) % 2:
159
            return None
160
161
        for link in zip(clean_path[1:-1:2], clean_path[2::2]):
162
            interface_a = cls.controller.get_interface_by_id(link[0])
163
            interface_b = cls.controller.get_interface_by_id(link[1])
164
            if interface_a is None or interface_b is None:
165
                return None
166
            new_path.append(Link(interface_a, interface_b))
167
168
        return new_path
169