Test Failed
Pull Request — master (#69)
by macartur
02:22
created

build.main   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 314
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
eloc 177
dl 0
loc 314
rs 6.96
c 0
b 0
f 0
ccs 0
cts 143
cp 0
wmc 53

18 Methods

Rating   Name   Duplication   Size   Complexity  
A Main.list_circuits() 0 8 2
A Main.get_paths() 0 14 2
A Main._clear_path() 0 4 1
A Main.is_duplicated_evc() 0 20 4
A Main.get_best_path() 0 6 2
A Main.get_circuit() 0 13 2
A Main.shutdown() 0 6 1
A Main.setup() 0 10 1
A Main.execute() 0 3 1
A Main.create_path() 0 16 5
A Main.create_circuit() 0 52 3
A Main.update() 0 24 3
A Main.uni_from_dict() 0 21 5
A Main.handle_link_up() 0 14 4
A Main.link_from_dict() 0 12 1
A Main.handle_link_down() 0 14 4
A Main.trigger_evc_reprovisioning() 0 12 3
C Main.evc_from_dict() 0 23 9

How to fix   Complexity   

Complexity

Complex classes like build.main 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
"""Main module of kytos/mef_eline Kytos Network Application.
2
3
NApp to provision circuits from user request.
4
"""
5
import requests
6
from flask import jsonify, request
7
8
from kytos.core import KytosNApp, log, rest
9
from kytos.core.events import KytosEvent
10
from kytos.core.helpers import listen_to
11
from kytos.core.interface import TAG, UNI
12
from kytos.core.link import Link
13
from napps.kytos.mef_eline import settings
14
from napps.kytos.mef_eline.models import EVC
15
from napps.kytos.mef_eline.scheduler import CircuitSchedule, Scheduler
16
from napps.kytos.mef_eline.storehouse import StoreHouse
17
18
19
class Main(KytosNApp):
20
    """Main class of amlight/mef_eline NApp.
21
22
    This class is the entry point for this napp.
23
    """
24
25
    def setup(self):
26
        """Replace the '__init__' method for the KytosNApp subclass.
27
28
        The setup method is automatically called by the controller when your
29
        application is loaded.
30
31
        So, if you have any setup routine, insert it here.
32
        """
33
        self.sched = Scheduler()
34
        self.storehouse = StoreHouse(self.controller)
35
36
    def execute(self):
37
        """Execute once when the napp is running."""
38
        pass
39
40
    def shutdown(self):
41
        """Execute when your napp is unloaded.
42
43
        If you have some cleanup procedure, insert it here.
44
        """
45
        pass
46
47
    @staticmethod
48
    def _clear_path(path):
49
        """Remove switches from a path, returning only interfaeces."""
50
        return [endpoint for endpoint in path if len(endpoint) > 23]
51
52
    @staticmethod
53
    def get_paths(circuit):
54
        """Get a valid path for the circuit from the Pathfinder."""
55
        endpoint = settings.PATHFINDER_URL
56
        request_data = {"source": circuit.uni_a.interface.id,
57
                        "destination": circuit.uni_z.interface.id}
58
        api_reply = requests.post(endpoint, json=request_data)
59
60
        if api_reply.status_code != getattr(requests.codes, 'ok'):
61
            log.error("Failed to get paths at %s. Returned %s",
62
                      endpoint, api_reply.status_code)
63
            return None
64
        reply_data = api_reply.json()
65
        return reply_data.get('paths')
66
67
    def get_best_path(self, circuit):
68
        """Return the best path available for a circuit, if exists."""
69
        paths = self.get_paths(circuit)
70
        if paths:
71
            return self.create_path(self.get_paths(circuit)[0]['hops'])
72
        return None
73
74
    def create_path(self, path):
75
        """Return the path containing only the interfaces."""
76
        new_path = []
77
        clean_path = self._clear_path(path)
78
79
        if len(clean_path) % 2:
80
            return None
81
82
        for link in zip(clean_path[1:-1:2], clean_path[2::2]):
83
            interface_a = self.controller.get_interface_by_id(link[0])
84
            interface_b = self.controller.get_interface_by_id(link[1])
85
            if interface_a is None or interface_b is None:
86
                return None
87
            new_path.append(Link(interface_a, interface_b))
88
89
        return new_path
90
91
    @rest('/v2/evc/', methods=['GET'])
92
    def list_circuits(self):
93
        """Endpoint to return all circuits stored."""
94
        circuits = self.storehouse.get_data()
95
        if not circuits:
96
            return jsonify({"response": "No circuit stored."}), 200
97
98
        return jsonify(circuits), 200
99
100
    @rest('/v2/evc/<circuit_id>', methods=['GET'])
101
    def get_circuit(self, circuit_id):
102
        """Endpoint to return a circuit based on id."""
103
        circuits = self.storehouse.get_data()
104
105
        if circuit_id in circuits:
106
            result = circuits[circuit_id]
107
            status = 200
108
        else:
109
            result = {'response': f'circuit_id {circuit_id} not found'}
110
            status = 400
111
112
        return jsonify(result), status
113
114
    @rest('/v2/evc/', methods=['POST'])
115
    def create_circuit(self):
116
        """Try to create a new circuit.
117
118
        Firstly, for EVPL: E-Line NApp verifies if UNI_A's requested C-VID and
119
        UNI_Z's requested C-VID are available from the interfaces' pools. This
120
        is checked when creating the UNI object.
121
122
        Then, E-Line NApp requests a primary and a backup path to the
123
        Pathfinder NApp using the attributes primary_links and backup_links
124
        submitted via REST
125
126
        # For each link composing paths in #3:
127
        #  - E-Line NApp requests a S-VID available from the link VLAN pool.
128
        #  - Using the S-VID obtained, generate abstract flow entries to be
129
        #    sent to FlowManager
130
131
        Push abstract flow entries to FlowManager and FlowManager pushes
132
        OpenFlow entries to datapaths
133
134
        E-Line NApp generates an event to notify all Kytos NApps of a new EVC
135
        creation
136
137
        Finnaly, notify user of the status of its request.
138
        """
139
        # Try to create the circuit object
140
        data = request.get_json()
141
142
        try:
143
            evc = self.evc_from_dict(data)
144
        except ValueError as exception:
145
            return jsonify("Bad request: {}".format(exception)), 400
146
147
        # verify duplicated evc
148
        if self.is_duplicated_evc(evc):
149
            return jsonify("Not Acceptable: This evc already exists."), 409
150
151
        # save circuit
152
        self.storehouse.save_evc(evc)
153
154
        # Request paths to Pathfinder
155
        evc.primary_links = self.get_best_path(evc) or []
156
157
        # Schedule the circuit deploy
158
        self.sched.add(evc)
159
160
        # Notify users
161
        event = KytosEvent(name='kytos.mef_eline.created',
162
                           content=evc.as_dict())
163
        self.controller.buffers.app.put(event)
164
165
        return jsonify({"circuit_id": evc.id}), 201
166
167
    @rest('/v2/evc/<circuit_id>', methods=['PATCH'])
168
    def update(self, circuit_id):
169
        """Update a circuit based on payload.
170
171
        The EVC required attributes can't be updated.
172
        """
173
        data = request.get_json()
174
        circuits = self.storehouse.get_data()
175
176
        if circuit_id not in circuits:
177
            result = {'response': f'circuit_id {circuit_id} not found'}
178
            return jsonify(result), 404
179
180
        try:
181
            evc = self.evc_from_dict(circuits.get(circuit_id))
182
            evc.update(**data)
183
            self.storehouse.save_evc(evc)
184
            result = {evc.id: evc.as_dict()}
185
            status = 200
186
        except ValueError as exception:
187
            result = "Bad request: {}".format(exception)
188
            status = 400
189
190
        return jsonify(result), status
191
192
    def is_duplicated_evc(self, evc):
193
        """Verify if the circuit given is duplicated with the stored evcs.
194
195
        Args:
196
            evc (EVC): circuit to be analysed.
197
198
        Returns:
199
            boolean: True if the circuit is duplicated, otherwise False.
200
201
        """
202
        for circuit_dict in self.storehouse.get_data().values():
203
            try:
204
                circuit = self.evc_from_dict(circuit_dict)
205
            except ValueError:
206
                continue
207
208
            if circuit == evc:
209
                return True
210
211
        return False
212
213
    @listen_to('kytos/topology.updated')
214
    def trigger_evc_reprovisioning(self, *_):
215
        """Listen to topology update to trigger EVCs (re)provisioning.
216
217
        Schedule all Circuits with valid UNIs.
218
        """
219
        for data in self.storehouse.get_data().values():
220
            try:
221
                evc = self.evc_from_dict(data)
222
                self.sched.add(evc)
223
            except ValueError as _exception:
224
                log.debug(f'{data.get("id")} can not be provisioning yet.')
225
226
    @listen_to('kytos.*.link.up', 'kytos.*.link.end_maintenance')
227
    def handle_link_up(self, event):
228
        """Change circuit when link is up or end_maintenance."""
229
        evc = None
230
231
        for data in self.storehouse.get_data().values():
232
            try:
233
                evc = self.evc_from_dict(data)
234
            except ValueError as _exception:
235
                log.debug(f'{data.get("id")} can not be provisioning yet.')
236
                continue
237
238
            if not evc.is_affected_by_link(event.link):
239
                evc.handle_link_up(event.link)
240
241
    @listen_to('kytos.*.link.down', 'kytos.*.link.under_maintenance')
242
    def handle_link_down(self, event):
243
        """Change circuit when link is down or under_mantenance."""
244
        evc = None
245
246
        for data in self.storehouse.get_data().values():
247
            try:
248
                evc = self.evc_from_dict(data)
249
            except ValueError as _exception:
250
                log.debug(f'{data.get("id")} can not be provisioning yet.')
251
                continue
252
253
            if not evc.is_affected_by_link(event.link):
254
                evc.handle_link_down()
255
256
    def evc_from_dict(self, evc_dict):
257
        """Convert some dict values to instance of EVC classes.
258
259
        This method will convert: [UNI, Link]
260
        """
261
        data = evc_dict.copy()  # Do not modify the original dict
262
263
        for attribute, value in data.items():
264
265
            if 'uni' in attribute:
266
                data[attribute] = self.uni_from_dict(value)
267
268
            if attribute == 'circuit_schedule':
269
                data[attribute] = []
270
                for schedule in value:
271
                    data[attribute].append(CircuitSchedule.from_dict(schedule))
272
273
            if ('path' in attribute or 'link' in attribute) and \
274
               (attribute != 'dynamic_backup_path'):
275
                if value:
276
                    data[attribute] = self.link_from_dict(value)
277
278
        return EVC(**data)
279
280
    def uni_from_dict(self, uni_dict):
281
        """Return a UNI object from python dict."""
282
        if uni_dict is None:
283
            return False
284
285
        interface_id = uni_dict.get("interface_id")
286
        interface = self.controller.get_interface_by_id(interface_id)
287
        if interface is None:
288
            return False
289
290
        tag = TAG.from_dict(uni_dict.get("tag"))
291
292
        if tag is False:
293
            return False
294
295
        try:
296
            uni = UNI(interface, tag)
297
        except TypeError:
298
            return False
299
300
        return uni
301
302
    def link_from_dict(self, link_dict):
303
        """Return a Link object from python dict."""
304
        id_a = link_dict.get('endpoint_a')
305
        id_b = link_dict.get('endpoint_b')
306
307
        endpoint_a = self.controller.get_interface_by_id(id_b)
308
        endpoint_b = self.controller.get_interface_by_id(id_a)
309
310
        link = Link(endpoint_a, endpoint_b)
311
        link.extend_metadata(link_dict.get('metadata'))
312
313
        return link
314