Test Failed
Push — master ( 087782...9ad0e5 )
by Antonio
03:03 queued 11s
created

build.main   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 295
Duplicated Lines 0 %

Test Coverage

Coverage 44.37%

Importance

Changes 0
Metric Value
eloc 162
dl 0
loc 295
ccs 67
cts 151
cp 0.4437
rs 8.48
c 0
b 0
f 0
wmc 49

14 Methods

Rating   Name   Duplication   Size   Complexity  
A Main.setup() 0 16 1
A Main.get_circuit() 0 13 2
A Main.shutdown() 0 2 1
A Main.execute() 0 2 1
A Main.list_circuits() 0 8 2
A Main.update() 0 24 3
B Main.create_circuit() 0 56 5
A Main.uni_from_dict() 0 18 4
A Main.is_duplicated_evc() 0 20 5
A Main.handle_link_up() 0 14 5
A Main.link_from_dict() 0 20 4
A Main.handle_link_down() 0 15 4
C Main.evc_from_dict() 0 30 11
A Main.delete_circuit() 0 18 1

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