Test Failed
Pull Request — master (#155)
by Antonio
04:13
created

build.main   F

Complexity

Total Complexity 66

Size/Duplication

Total Lines 344
Duplicated Lines 0 %

Test Coverage

Coverage 45.1%

Importance

Changes 0
Metric Value
eloc 204
dl 0
loc 344
ccs 69
cts 153
cp 0.451
rs 3.12
c 0
b 0
f 0
wmc 66

17 Methods

Rating   Name   Duplication   Size   Complexity  
A Main.list_circuits() 0 8 2
A Main.uni_from_dict() 0 18 4
B Main.load_evcs() 0 26 8
A Main.is_duplicated_evc() 0 14 4
A Main.load_circuits_by_interface() 0 13 4
A Main.handle_link_up() 0 6 4
A Main.link_from_dict() 0 20 4
A Main.get_circuit() 0 12 2
A Main.shutdown() 0 2 1
A Main.handle_link_down() 0 7 3
A Main.setup() 0 22 1
A Main.execute() 0 2 1
B Main.create_circuit() 0 59 5
C Main.evc_from_dict() 0 30 11
B Main.update() 0 29 6
A Main.add_to_circuits_by_interface() 0 5 2
A Main.delete_circuit() 0 28 4

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 2
from flask import jsonify, request
6 2
from werkzeug.exceptions import BadRequest
7
8 2
from kytos.core import KytosNApp, log, rest
9 2
from kytos.core.events import KytosEvent
10 2
from kytos.core.helpers import listen_to
11 2
from kytos.core.interface import TAG, UNI
12 2
from kytos.core.link import Link
13 2
from napps.kytos.mef_eline.models import EVC, DynamicPathManager
14 2
from napps.kytos.mef_eline.scheduler import CircuitSchedule, Scheduler
15 2
from napps.kytos.mef_eline.storehouse import StoreHouse
16
17
18 2
class Main(KytosNApp):
19
    """Main class of amlight/mef_eline NApp.
20
21
    This class is the entry point for this napp.
22
    """
23
24 2
    def setup(self):
25
        """Replace the '__init__' method for the KytosNApp subclass.
26
27
        The setup method is automatically called by the controller when your
28
        application is loaded.
29
30
        So, if you have any setup routine, insert it here.
31
        """
32
        # object used to scheduler circuit events
33 2
        self.sched = Scheduler()
34
35
        # object to save and load circuits
36 2
        self.storehouse = StoreHouse(self.controller)
37
38
        # set the controller that will manager the dynamic paths
39 2
        DynamicPathManager.set_controller(self.controller)
40
41
        # dictionary of EVCs created
42 2
        self.circuits = {}
43
44 2
        # dictionary of EVCs by interface
45
        self._circuits_by_interface = {}
46
47 2
    def execute(self):
48
        """Execute once when the napp is running."""
49
50
    def shutdown(self):
51
        """Execute when your napp is unloaded.
52
53 2
        If you have some cleanup procedure, insert it here.
54
        """
55
56 2
    @rest('/v2/evc/', methods=['GET'])
57 2
    def list_circuits(self):
58 2
        """Endpoint to return all circuits stored."""
59
        circuits = self.storehouse.get_data()
60 2
        if not circuits:
61
            return jsonify({}), 200
62 2
63
        return jsonify(circuits), 200
64
65 2
    @rest('/v2/evc/<circuit_id>', methods=['GET'])
66 2
    def get_circuit(self, circuit_id):
67 2
        """Endpoint to return a circuit based on id."""
68 2
        circuits = self.storehouse.get_data()
69 2
        try:
70 2
            result = circuits[circuit_id]
71 2
            status = 200
72
        except KeyError:
73 2
            result = {'response': f'circuit_id {circuit_id} not found'}
74
            status = 404
75 2
76
        return jsonify(result), status
77
78
    @rest('/v2/evc/', methods=['POST'])
79
    def create_circuit(self):
80
        """Try to create a new circuit.
81
82
        Firstly, for EVPL: E-Line NApp verifies if UNI_A's requested C-VID and
83
        UNI_Z's requested C-VID are available from the interfaces' pools. This
84
        is checked when creating the UNI object.
85
86
        Then, E-Line NApp requests a primary and a backup path to the
87
        Pathfinder NApp using the attributes primary_links and backup_links
88
        submitted via REST
89
90
        # For each link composing paths in #3:
91
        #  - E-Line NApp requests a S-VID available from the link VLAN pool.
92
        #  - Using the S-VID obtained, generate abstract flow entries to be
93
        #    sent to FlowManager
94
95
        Push abstract flow entries to FlowManager and FlowManager pushes
96
        OpenFlow entries to datapaths
97
98
        E-Line NApp generates an event to notify all Kytos NApps of a new EVC
99
        creation
100
101 2
        Finnaly, notify user of the status of its request.
102
        """
103 2
        # Try to create the circuit object
104 2
        data = request.get_json()
105
106 2
        if not data:
107 2
            return jsonify("Bad request: The request do not have a json."), 400
108
109
        try:
110
            evc = self.evc_from_dict(data)
111
        except ValueError as exception:
112 2
            return jsonify("Bad request: {}".format(exception)), 400
113 2
114
        # verify duplicated evc
115
        if self.is_duplicated_evc(evc):
116 2
            return jsonify("Not Acceptable: This evc already exists."), 409
117
118
        # store circuit in dictionary
119 2
        self.circuits[evc.id] = evc
120
121
        # save circuit
122 2
        self.storehouse.save_evc(evc)
123
124
        # Schedule the circuit deploy
125 2
        self.sched.add(evc)
126 2
127
        # Circuit has no schedule, deploy now
128
        if not evc.circuit_scheduler:
129 2
            evc.deploy()
130
131 2
        # Notify users
132
        event = KytosEvent(name='kytos.mef_eline.created',
133 2
                           content=evc.as_dict())
134
        self.controller.buffers.app.put(event)
135 2
136
        return jsonify({"circuit_id": evc.id}), 201
137
138
    @rest('/v2/evc/<circuit_id>', methods=['PATCH'])
139
    def update(self, circuit_id):
140
        """Update a circuit based on payload.
141
142
        The EVC required attributes (name, uni_a, uni_z) can't be updated.
143
        """
144
        try:
145
            evc = self.circuits[circuit_id]
146
            data = request.get_json()
147
            evc.update(**data)
148
        except ValueError as exception:
149
            result = {'response': 'Bad Request: {}'.format(exception)}
150
            status = 400
151
        except TypeError:
152
            result = {'response': 'Content-Type must be application/json'}
153
            status = 415
154
        except BadRequest:
155
            response = 'Bad Request: The request is not a valid JSON.'
156
            result = {'response': response}
157
            status = 400
158
        except KeyError:
159
            result = {'response': f'circuit_id {circuit_id} not found'}
160
            status = 404
161
        else:
162
            evc.sync()
163
            result = {evc.id: evc.as_dict()}
164
            status = 200
165 2
166
        return jsonify(result), status
167
168
    @rest('/v2/evc/<circuit_id>', methods=['DELETE'])
169
    def delete_circuit(self, circuit_id):
170
        """Remove a circuit.
171
172
        First, the flows are removed from the switches, and then the EVC is
173
        disabled.
174
        """
175
        try:
176
            evc = self.circuits[circuit_id]
177
        except KeyError:
178
            result = {'response': f'circuit_id {circuit_id} not found'}
179
            status = 404
180
        else:
181
            log.info(f'Removing {circuit_id}')
182
            if evc.archived:
183
                result = {'response': f'Circuit {circuit_id} already removed'}
184
                status = 404
185
            else:
186
                evc.remove_current_flows()
187
                evc.deactivate()
188
                evc.disable()
189
                self.sched.remove(evc)
190
                evc.archive()
191
                evc.sync()
192
                result = {'response': f'Circuit {circuit_id} removed'}
193
                status = 200
194 2
195
        return jsonify(result), status
196
197
    def is_duplicated_evc(self, evc):
198
        """Verify if the circuit given is duplicated with the stored evcs.
199
200
        Args:
201
            evc (EVC): circuit to be analysed.
202
203
        Returns:
204 2
            boolean: True if the circuit is duplicated, otherwise False.
205 2
206 2
        """
207 2
        for circuit in self.circuits.values():
208
            if not circuit.archived and circuit == evc:
209 2
                return True
210
        return False
211
212
    @listen_to('kytos/topology.link_up')
213
    def handle_link_up(self, event):
214
        """Change circuit when link is up or end_maintenance."""
215
        for evc in self.circuits.values():
216 2
            if evc.is_enabled() and not evc.archived:
217
                evc.handle_link_up(event.content['link'])
218
219
    @listen_to('kytos/topology.link_down')
220
    def handle_link_down(self, event):
221
        """Change circuit when link is down or under_mantenance."""
222
        for evc in self.circuits.values():
223
            if evc.is_affected_by_link(event.content['link']):
224 2
                log.info('handling evc %s' % evc)
225
                evc.handle_link_down()
226
227
    def load_circuits_by_interface(self, circuits):
228
        """Load circuits in storehouse for in-memory dictionary."""
229 2
        for circuit_id, circuit  in circuits.items():
230
            intf_a = circuit['uni_a']['interface_id']
231 2
            self.add_to_circuits_by_interface(intf_a, circuit_id)
232
            intf_z = circuit['uni_z']['interface_id']
233 2
            self.add_to_circuits_by_interface(intf_z, circuit_id)
234 2
            for path in ('current_path', 'primary_path', 'backup_path'):
235 2
                for link in circuit[path]:
236
                    intf_a = link['endpoint_a']['id']
237
                    self.add_to_circuits_by_interface(intf_a, circuit_id)
238
                    intf_b = link['endpoint_b']['id']
239 2
                    self.add_to_circuits_by_interface(intf_b, circuit_id)
240
241
    def add_to_circuits_by_interface(self, intf, circuit_id):
242
        """Add a single item to the dictionary of circuits by interface."""
243
        if intf not in self._circuits_by_interface:
244 2
            self._circuits_by_interface[intf] = set()
245
        self._circuits_by_interface[intf].add(circuit_id)
246
247
    @listen_to('kytos/of_core.switch.port.created')
248 2
    def load_evcs(self, event):
249
        """Try to load the unloaded EVCs from storehouse."""
250
        circuits = self.storehouse.get_data()
251
        if not self._circuits_by_interface:
252
            self.load_circuits_by_interface(circuits)
253 2
254
        interface_id = '{}:{}'.format(event.content['switch'],
255 2
                                      event.content['port'])
256
257
        for circuit_id in self._circuits_by_interface.get(interface_id, []):
258
            if circuit_id in circuits and circuit_id not in self.circuits:
259
                try:
260
                    evc = self.evc_from_dict(circuits[circuit_id])
261
                except ValueError as exception:
262
                    log.info(
263
                        f'Could not load EVC {circuit_id} because {exception}')
264
                    continue
265
                log.info(f'Loading EVC {circuit_id}')
266
                if evc.archived:
267
                    continue
268
                if evc.is_enabled():
269
                    log.info(f'Trying to deploy EVC {circuit_id}')
270
                    evc.deploy()
271
                self.circuits[circuit_id] = evc
272
                self.sched.add(evc)
273
274 2
    def evc_from_dict(self, evc_dict):
275
        """Convert some dict values to instance of EVC classes.
276
277
        This method will convert: [UNI, Link]
278
        """
279
        data = evc_dict.copy()  # Do not modify the original dict
280
281
        for attribute, value in data.items():
282
283
            if 'uni' in attribute:
284
                try:
285
                    data[attribute] = self.uni_from_dict(value)
286
                except ValueError as exc:
287
                    raise ValueError(f'Error creating UNI: {exc}')
288
289
            if attribute == 'circuit_scheduler':
290
                data[attribute] = []
291
                for schedule in value:
292
                    data[attribute].append(CircuitSchedule.from_dict(schedule))
293
294
            if 'link' in attribute:
295
                if value:
296
                    data[attribute] = self.link_from_dict(value)
297
298
            if 'path' in attribute and attribute != 'dynamic_backup_path':
299
                if value:
300
                    data[attribute] = [self.link_from_dict(link)
301
                                       for link in value]
302
303
        return EVC(self.controller, **data)
304
305
    def uni_from_dict(self, uni_dict):
306
        """Return a UNI object from python dict."""
307
        if uni_dict is None:
308
            return False
309
310
        interface_id = uni_dict.get("interface_id")
311
        interface = self.controller.get_interface_by_id(interface_id)
312
        if interface is None:
313
            raise ValueError(f'Could not instantiate interface {interface_id}')
314
315
        tag_dict = uni_dict.get("tag")
316
        tag = TAG.from_dict(tag_dict)
317
        if tag is False:
318
            raise ValueError(f'Could not instantiate tag from dict {tag_dict}')
319
320
        uni = UNI(interface, tag)
321
322
        return uni
323
324
    def link_from_dict(self, link_dict):
325
        """Return a Link object from python dict."""
326
        id_a = link_dict.get('endpoint_a').get('id')
327
        id_b = link_dict.get('endpoint_b').get('id')
328
329
        endpoint_a = self.controller.get_interface_by_id(id_a)
330
        endpoint_b = self.controller.get_interface_by_id(id_b)
331
332
        link = Link(endpoint_a, endpoint_b)
333
        if 'metadata' in link_dict:
334
            link.extend_metadata(link_dict.get('metadata'))
335
336
        s_vlan = link.get_metadata('s_vlan')
337
        if s_vlan:
338
            tag = TAG.from_dict(s_vlan)
339
            if tag is False:
340
                error_msg = f'Could not instantiate tag from dict {s_vlan}'
341
                raise ValueError(error_msg)
342
            link.update_metadata('s_vlan', tag)
343
        return link
344