Passed
Branch master (98eee7)
by Beraldo
04:41
created

build.main   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 294
Duplicated Lines 0 %

Test Coverage

Coverage 45.1%

Importance

Changes 0
Metric Value
eloc 164
dl 0
loc 294
ccs 69
cts 153
cp 0.451
rs 7.44
c 0
b 0
f 0
wmc 52

14 Methods

Rating   Name   Duplication   Size   Complexity  
A Main.list_circuits() 0 8 2
A Main.get_circuit() 0 12 2
A Main.shutdown() 0 2 1
A Main.setup() 0 19 1
A Main.execute() 0 2 1
B Main.create_circuit() 0 59 5
A Main.uni_from_dict() 0 18 4
A Main.is_duplicated_evc() 0 14 4
A Main.handle_link_up() 0 6 4
A Main.link_from_dict() 0 20 4
A Main.handle_link_down() 0 7 3
C Main.evc_from_dict() 0 30 11
B Main.update() 0 29 6
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
    def execute(self):
45
        """Execute once when the napp is running."""
46
47 2
    def shutdown(self):
48
        """Execute when your napp is unloaded.
49
50
        If you have some cleanup procedure, insert it here.
51
        """
52
53 2
    @rest('/v2/evc/', methods=['GET'])
54
    def list_circuits(self):
55
        """Endpoint to return all circuits stored."""
56 2
        circuits = self.storehouse.get_data()
57 2
        if not circuits:
58 2
            return jsonify({}), 200
59
60 2
        return jsonify(circuits), 200
61
62 2
    @rest('/v2/evc/<circuit_id>', methods=['GET'])
63
    def get_circuit(self, circuit_id):
64
        """Endpoint to return a circuit based on id."""
65 2
        circuits = self.storehouse.get_data()
66 2
        try:
67 2
            result = circuits[circuit_id]
68 2
            status = 200
69 2
        except KeyError:
70 2
            result = {'response': f'circuit_id {circuit_id} not found'}
71 2
            status = 404
72
73 2
        return jsonify(result), status
74
75 2
    @rest('/v2/evc/', methods=['POST'])
76
    def create_circuit(self):
77
        """Try to create a new circuit.
78
79
        Firstly, for EVPL: E-Line NApp verifies if UNI_A's requested C-VID and
80
        UNI_Z's requested C-VID are available from the interfaces' pools. This
81
        is checked when creating the UNI object.
82
83
        Then, E-Line NApp requests a primary and a backup path to the
84
        Pathfinder NApp using the attributes primary_links and backup_links
85
        submitted via REST
86
87
        # For each link composing paths in #3:
88
        #  - E-Line NApp requests a S-VID available from the link VLAN pool.
89
        #  - Using the S-VID obtained, generate abstract flow entries to be
90
        #    sent to FlowManager
91
92
        Push abstract flow entries to FlowManager and FlowManager pushes
93
        OpenFlow entries to datapaths
94
95
        E-Line NApp generates an event to notify all Kytos NApps of a new EVC
96
        creation
97
98
        Finnaly, notify user of the status of its request.
99
        """
100
        # Try to create the circuit object
101 2
        data = request.get_json()
102
103 2
        if not data:
104 2
            return jsonify("Bad request: The request do not have a json."), 400
105
106 2
        try:
107 2
            evc = self.evc_from_dict(data)
108
        except ValueError as exception:
109
            return jsonify("Bad request: {}".format(exception)), 400
110
111
        # verify duplicated evc
112 2
        if self.is_duplicated_evc(evc):
113 2
            return jsonify("Not Acceptable: This evc already exists."), 409
114
115
        # store circuit in dictionary
116 2
        self.circuits[evc.id] = evc
117
118
        # save circuit
119 2
        self.storehouse.save_evc(evc)
120
121
        # Schedule the circuit deploy
122 2
        self.sched.add(evc)
123
124
        # Circuit has no schedule, deploy now
125 2
        if not evc.circuit_scheduler:
126 2
            evc.deploy()
127
128
        # Notify users
129 2
        event = KytosEvent(name='kytos.mef_eline.created',
130
                           content=evc.as_dict())
131 2
        self.controller.buffers.app.put(event)
132
133 2
        return jsonify({"circuit_id": evc.id}), 201
134
135 2
    @rest('/v2/evc/<circuit_id>', methods=['PATCH'])
136
    def update(self, circuit_id):
137
        """Update a circuit based on payload.
138
139
        The EVC required attributes (name, uni_a, uni_z) can't be updated.
140
        """
141
        try:
142
            evc = self.circuits[circuit_id]
143
            data = request.get_json()
144
            evc.update(**data)
145
        except ValueError as exception:
146
            result = {'response': 'Bad Request: {}'.format(exception)}
147
            status = 400
148
        except TypeError:
149
            result = {'response': 'Content-Type must be application/json'}
150
            status = 415
151
        except BadRequest:
152
            response = 'Bad Request: The request is not a valid JSON.'
153
            result = {'response': response}
154
            status = 400
155
        except KeyError:
156
            result = {'response': f'circuit_id {circuit_id} not found'}
157
            status = 404
158
        else:
159
            evc.sync()
160
            result = {evc.id: evc.as_dict()}
161
            status = 200
162
163
        return jsonify(result), status
164
165 2
    @rest('/v2/evc/<circuit_id>', methods=['DELETE'])
166
    def delete_circuit(self, circuit_id):
167
        """Remove a circuit.
168
169
        First, the flows are removed from the switches, and then the EVC is
170
        disabled.
171
        """
172
        try:
173
            evc = self.circuits[circuit_id]
174
        except KeyError:
175
            result = {'response': f'circuit_id {circuit_id} not found'}
176
            status = 404
177
        else:
178
            log.info(f'Removing {circuit_id}')
179
            if evc.archived:
180
                result = {'response': f'Circuit {circuit_id} already removed'}
181
                status = 404
182
            else:
183
                evc.remove_current_flows()
184
                evc.deactivate()
185
                evc.disable()
186
                self.sched.remove(evc)
187
                evc.archive()
188
                evc.sync()
189
                result = {'response': f'Circuit {circuit_id} removed'}
190
                status = 200
191
192
        return jsonify(result), status
193
194 2
    def is_duplicated_evc(self, evc):
195
        """Verify if the circuit given is duplicated with the stored evcs.
196
197
        Args:
198
            evc (EVC): circuit to be analysed.
199
200
        Returns:
201
            boolean: True if the circuit is duplicated, otherwise False.
202
203
        """
204 2
        for circuit in self.circuits.values():
205 2
            if not circuit.archived and circuit == evc:
206 2
                return True
207 2
        return False
208
209 2
    @listen_to('kytos/topology.link_up')
210
    def handle_link_up(self, event):
211
        """Change circuit when link is up or end_maintenance."""
212
        for evc in self.circuits.values():
213
            if evc.is_enabled() and not evc.archived:
214
                evc.handle_link_up(event.content['link'])
215
216 2
    @listen_to('kytos/topology.link_down')
217
    def handle_link_down(self, event):
218
        """Change circuit when link is down or under_mantenance."""
219
        for evc in self.circuits.values():
220
            if evc.is_affected_by_link(event.content['link']):
221
                log.info('handling evc %s' % evc)
222
                evc.handle_link_down()
223
224 2
    def evc_from_dict(self, evc_dict):
225
        """Convert some dict values to instance of EVC classes.
226
227
        This method will convert: [UNI, Link]
228
        """
229 2
        data = evc_dict.copy()  # Do not modify the original dict
230
231 2
        for attribute, value in data.items():
232
233 2
            if 'uni' in attribute:
234 2
                try:
235 2
                    data[attribute] = self.uni_from_dict(value)
236
                except ValueError as exc:
237
                    raise ValueError(f'Error creating UNI: {exc}')
238
239 2
            if attribute == 'circuit_scheduler':
240
                data[attribute] = []
241
                for schedule in value:
242
                    data[attribute].append(CircuitSchedule.from_dict(schedule))
243
244 2
            if 'link' in attribute:
245
                if value:
246
                    data[attribute] = self.link_from_dict(value)
247
248 2
            if 'path' in attribute and attribute != 'dynamic_backup_path':
249
                if value:
250
                    data[attribute] = [self.link_from_dict(link)
251
                                       for link in value]
252
253 2
        return EVC(self.controller, **data)
254
255 2
    def uni_from_dict(self, uni_dict):
256
        """Return a UNI object from python dict."""
257
        if uni_dict is None:
258
            return False
259
260
        interface_id = uni_dict.get("interface_id")
261
        interface = self.controller.get_interface_by_id(interface_id)
262
        if interface is None:
263
            raise ValueError(f'Could not instantiate interface {interface_id}')
264
265
        tag_dict = uni_dict.get("tag")
266
        tag = TAG.from_dict(tag_dict)
267
        if tag is False:
268
            raise ValueError(f'Could not instantiate tag from dict {tag_dict}')
269
270
        uni = UNI(interface, tag)
271
272
        return uni
273
274 2
    def link_from_dict(self, link_dict):
275
        """Return a Link object from python dict."""
276
        id_a = link_dict.get('endpoint_a').get('id')
277
        id_b = link_dict.get('endpoint_b').get('id')
278
279
        endpoint_a = self.controller.get_interface_by_id(id_a)
280
        endpoint_b = self.controller.get_interface_by_id(id_b)
281
282
        link = Link(endpoint_a, endpoint_b)
283
        if 'metadata' in link_dict:
284
            link.extend_metadata(link_dict.get('metadata'))
285
286
        s_vlan = link.get_metadata('s_vlan')
287
        if s_vlan:
288
            tag = TAG.from_dict(s_vlan)
289
            if tag is False:
290
                error_msg = f'Could not instantiate tag from dict {s_vlan}'
291
                raise ValueError(error_msg)
292
            link.update_metadata('s_vlan', tag)
293
        return link
294