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

build.models.path   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 160
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 113
dl 0
loc 160
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 DynamicPathManager.get_best_path() 0 7 2
A DynamicPathManager.get_best_paths() 0 5 2
B Path.status() 0 27 7
A DynamicPathManager.create_path() 0 17 5
A DynamicPathManager.set_controller() 0 4 1
A Path.choose_vlans() 0 5 2
A Path.link_affected_by_interface() 0 8 4
C Path.is_valid() 0 18 9
A DynamicPathManager.get_paths() 0 14 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(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