Passed
Pull Request — master (#114)
by Rogerio
03:44
created

build.main   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 301
Duplicated Lines 0 %

Test Coverage

Coverage 40.52%

Importance

Changes 0
Metric Value
eloc 165
dl 0
loc 301
rs 8.5599
c 0
b 0
f 0
ccs 62
cts 153
cp 0.4052
wmc 48

15 Methods

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

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
            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 2
        evc.deploy()
118
119
        # Notify users
120 2
        event = KytosEvent(name='kytos.mef_eline.created',
121
                           content=evc.as_dict())
122 2
        self.controller.buffers.app.put(event)
123
124 2
        return jsonify({"circuit_id": evc.id}), 201
125
126 2
    @rest('/v2/evc/<circuit_id>', methods=['PATCH'])
127
    def update(self, circuit_id):
128
        """Update a circuit based on payload.
129
130
        The EVC required attributes can't be updated.
131
        """
132
        data = request.get_json()
133
        circuits = self.storehouse.get_data()
134
135
        if circuit_id not in circuits:
136
            result = {'response': f'circuit_id {circuit_id} not found'}
137
            return jsonify(result), 404
138
139
        try:
140
            evc = self.evc_from_dict(circuits.get(circuit_id))
141
            evc.update(**data)
142
            self.storehouse.save_evc(evc)
143
            result = {evc.id: evc.as_dict()}
144
            status = 200
145
        except ValueError as exception:
146
            result = "Bad request: {}".format(exception)
147
            status = 400
148
149
        return jsonify(result), status
150
151 2
    @rest('/v2/evc/<circuit_id>', methods=['DELETE'])
152
    def delete_circuit(self, circuit_id):
153
        """Remove a circuit.
154
155
        First, flows are removed from the switches, then the EVC is
156
        disabled.
157
        """
158
        circuits = self.storehouse.get_data()
159
        log.info("Removing %s" % circuit_id)
160
        evc = self.evc_from_dict(circuits.get(circuit_id))
161
        evc.remove_current_flows()
162
        evc.disable()
163
        self.storehouse.save_evc(evc)
164
165
        return jsonify("Circuit removed"), 200
166
167 2
    def is_duplicated_evc(self, evc):
168
        """Verify if the circuit given is duplicated with the stored evcs.
169
170
        Args:
171
            evc (EVC): circuit to be analysed.
172
173
        Returns:
174
            boolean: True if the circuit is duplicated, otherwise False.
175
176
        """
177 2
        for circuit_dict in self.storehouse.get_data().values():
178
            try:
179
                circuit = self.evc_from_dict(circuit_dict)
180
            except ValueError:
181
                continue
182
183
            if circuit == evc:
184
                return True
185
186 2
        return False
187
188 2
    @listen_to('kytos/topology.updated')
189
    def trigger_evc_reprovisioning(self, *_):
190
        """Listen to topology update to trigger EVCs (re)provisioning.
191
192
        Schedule all Circuits with valid UNIs.
193
        """
194
        for data in self.storehouse.get_data().values():
195
            try:
196
                evc = self.evc_from_dict(data)
197
                self.sched.add(evc)
198
            except ValueError as _exception:
199
                log.debug(f'{data.get("id")} can not be provisioning yet.')
200
201 2
    @listen_to('kytos/topology.link_up')
202
    def handle_link_up(self, event):
203
        """Change circuit when link is up or end_maintenance."""
204
        evc = None
205
206
        for data in self.storehouse.get_data().values():
207
            try:
208
                evc = self.evc_from_dict(data)
209
            except ValueError as _exception:
210
                log.debug(f'{data.get("id")} can not be provisioning yet.')
211
                continue
212
213
            evc.handle_link_up(event.content['link'])
214
215 2
    @listen_to('kytos/topology.link_down')
216
    def handle_link_down(self, event):
217
        """Change circuit when link is down or under_mantenance."""
218
        evc = None
219
220
        for data in self.storehouse.get_data().values():
221
            try:
222
                evc = self.evc_from_dict(data)
223
            except ValueError as _exception:
224
                log.debug(f'{data.get("id")} can not be provisioned yet.')
225
                continue
226
227
            if evc.is_affected_by_link(event.content['link']):
228
                log.info('handling evc %s' % evc)
229
                evc.handle_link_down()
230
231 2
    def evc_from_dict(self, evc_dict):
232
        """Convert some dict values to instance of EVC classes.
233
234
        This method will convert: [UNI, Link]
235
        """
236 2
        data = evc_dict.copy()  # Do not modify the original dict
237
238 2
        for attribute, value in data.items():
239
240 2
            if 'uni' in attribute:
241 2
                try:
242 2
                    data[attribute] = self.uni_from_dict(value)
243
                except ValueError as exc:
244
                    raise ValueError(f'Error creating UNI: {exc}')
245
246 2
            if attribute == 'circuit_scheduler':
247
                data[attribute] = []
248
                for schedule in value:
249
                    data[attribute].append(CircuitSchedule.from_dict(schedule))
250
251 2
            if 'link' in attribute:
252
                if value:
253
                    data[attribute] = self.link_from_dict(value)
254
255 2
            if 'path' in attribute and attribute != 'dynamic_backup_path':
256
                if value:
257
                    data[attribute] = [self.link_from_dict(link)
258
                                       for link in value]
259
260 2
        return EVC(self.controller, **data)
261
262 2
    def uni_from_dict(self, uni_dict):
263
        """Return a UNI object from python dict."""
264
        if uni_dict is None:
265
            return False
266
267
        interface_id = uni_dict.get("interface_id")
268
        interface = self.controller.get_interface_by_id(interface_id)
269
        if interface is None:
270
            raise ValueError(f'Could not instantiate interface {interface_id}')
271
272
        tag_dict = uni_dict.get("tag")
273
        tag = TAG.from_dict(tag_dict)
274
        if tag is False:
275
            raise ValueError(f'Could not instantiate tag from dict {tag_dict}')
276
277
        uni = UNI(interface, tag)
278
279
        return uni
280
281 2
    def link_from_dict(self, link_dict):
282
        """Return a Link object from python dict."""
283
        id_a = link_dict.get('endpoint_a').get('id')
284
        id_b = link_dict.get('endpoint_b').get('id')
285
286
        endpoint_a = self.controller.get_interface_by_id(id_a)
287
        endpoint_b = self.controller.get_interface_by_id(id_b)
288
289
        link = Link(endpoint_a, endpoint_b)
290
        if 'metadata' in link_dict:
291
            link.extend_metadata(link_dict.get('metadata'))
292
293
        s_vlan = link.get_metadata('s_vlan')
294
        if s_vlan:
295
            tag = TAG.from_dict(s_vlan)
296
            if tag is False:
297
                error_msg = f'Could not instantiate tag from dict {s_vlan}'
298
                raise ValueError(error_msg)
299
            link.update_metadata('s_vlan', tag)
300
        return link
301