build.models.path   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 169
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 119
dl 0
loc 169
rs 8.96
c 0
b 0
f 0
wmc 43

14 Methods

Rating   Name   Duplication   Size   Complexity  
A Path.is_affected_by_link() 0 5 2
A Path.__eq__() 0 5 3
A Path.make_vlans_available() 0 5 2
A Path.choose_vlans() 0 5 2
A Path.link_affected_by_interface() 0 8 4
A DynamicPathManager.get_best_path() 0 7 2
A DynamicPathManager.get_best_paths() 0 5 2
B Path.status() 0 30 7
A DynamicPathManager.create_path() 0 17 5
A DynamicPathManager.set_controller() 0 4 1
C Path.is_valid() 0 19 9
A DynamicPathManager.get_paths() 0 19 2
A DynamicPathManager._clear_path() 0 4 1
A Path.as_dict() 0 3 1

How to fix   Complexity   

Complexity

Complex classes like build.models.path often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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