Test Failed
Push — master ( dba1ae...723a1d )
by Antonio
04:50 queued 13s
created

build.models.path.Path.is_valid()   C

Complexity

Conditions 9

Size

Total Lines 18
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 15
nop 4
dl 0
loc 18
rs 6.6666
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(f'{link.endpoint_a} switch is different'
53
                                  f' from previous.')
54
            if is_scheduled is False and (
55
                link.endpoint_a.link is None or
56
                link.endpoint_a.link != link or
57
                link.endpoint_b.link is None or
58
                link.endpoint_b.link != link
59
            ):
60
                raise InvalidPath(f'Link {link} is not available.')
61
            previous = link.endpoint_b.switch
62
        if previous == switch_z:
63
            return True
64
        raise InvalidPath(f'Last endpoint is different from uni_z')
65
66
    @property
67
    def status(self):
68
        """Check for the  status of a path.
69
70
        If any link in this path is down, the path is considered down.
71
        """
72
        if not self:
73
            return EntityStatus.DISABLED
74
75
        endpoint = '%s/%s' % (settings.TOPOLOGY_URL, 'links')
76
        api_reply = requests.get(endpoint)
77
        if api_reply.status_code != getattr(requests.codes, 'ok'):
78
            log.error('Failed to get links at %s. Returned %s',
79
                      endpoint, api_reply.status_code)
80
            return None
81
        links = api_reply.json()['links']
82
        return_status = EntityStatus.UP
83
        for path_link in self:
84
            try:
85
                link = links[path_link.id]
86
            except KeyError:
87
                return EntityStatus.DISABLED
88
            if link['enabled'] is False:
89
                return EntityStatus.DISABLED
90
            if link['active'] is False:
91
                return_status = EntityStatus.DOWN
92
        return return_status
93
94
    def as_dict(self):
95
        """Return list comprehension of links as_dict."""
96
        return [link.as_dict() for link in self if link]
97
98
99
class DynamicPathManager:
100
    """Class to handle and create paths."""
101
102
    controller = None
103
104
    @classmethod
105
    def set_controller(cls, controller=None):
106
        """Set the controller to discovery news paths."""
107
        cls.controller = controller
108
109
    @staticmethod
110
    def get_paths(circuit):
111
        """Get a valid path for the circuit from the Pathfinder."""
112
        endpoint = settings.PATHFINDER_URL
113
        request_data = {"source": circuit.uni_a.interface.id,
114
                        "destination": circuit.uni_z.interface.id}
115
        api_reply = requests.post(endpoint, json=request_data)
116
117
        if api_reply.status_code != getattr(requests.codes, 'ok'):
118
            log.error("Failed to get paths at %s. Returned %s",
119
                      endpoint, api_reply.status_code)
120
            return None
121
        reply_data = api_reply.json()
122
        return reply_data.get('paths')
123
124
    @staticmethod
125
    def _clear_path(path):
126
        """Remove switches from a path, returning only interfaces."""
127
        return [endpoint for endpoint in path if len(endpoint) > 23]
128
129
    @classmethod
130
    def get_best_path(cls, circuit):
131
        """Return the best path available for a circuit, if exists."""
132
        paths = cls.get_paths(circuit)
133
        if paths:
134
            return cls.create_path(cls.get_paths(circuit)[0]['hops'])
135
        return None
136
137
    @classmethod
138
    def get_best_paths(cls, circuit):
139
        """Return the best paths available for a circuit, if they exist."""
140
        for path in cls.get_paths(circuit):
141
            yield cls.create_path(path['hops'])
142
143
    @classmethod
144
    def create_path(cls, path):
145
        """Return the path containing only the interfaces."""
146
        new_path = Path()
147
        clean_path = cls._clear_path(path)
148
149
        if len(clean_path) % 2:
150
            return None
151
152
        for link in zip(clean_path[1:-1:2], clean_path[2::2]):
153
            interface_a = cls.controller.get_interface_by_id(link[0])
154
            interface_b = cls.controller.get_interface_by_id(link[1])
155
            if interface_a is None or interface_b is None:
156
                return None
157
            new_path.append(Link(interface_a, interface_b))
158
159
        return new_path
160