Passed
Pull Request — master (#112)
by Rogerio
01:44
created

build.main   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 303
Duplicated Lines 0 %

Test Coverage

Coverage 40%

Importance

Changes 0
Metric Value
eloc 167
dl 0
loc 303
ccs 62
cts 155
cp 0.4
rs 8.5599
c 0
b 0
f 0
wmc 48

15 Methods

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