Test Failed
Push — master ( 9ea46a...54e0f4 )
by Beraldo
01:10
created

build.main   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 271
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
eloc 143
dl 0
loc 271
ccs 0
cts 134
cp 0
rs 8.96
c 0
b 0
f 0
wmc 43

14 Methods

Rating   Name   Duplication   Size   Complexity  
A Main.link_from_dict() 0 12 1
A Main.shutdown() 0 6 1
A Main.trigger_evc_reprovisioning() 0 12 3
A Main.list_circuits() 0 8 2
A Main.uni_from_dict() 0 21 5
A Main.is_duplicated_evc() 0 20 4
A Main.handle_link_up() 0 14 4
A Main.get_circuit() 0 13 2
A Main.setup() 0 16 1
A Main.handle_link_down() 0 14 4
A Main.execute() 0 3 1
A Main.create_circuit() 0 49 3
C Main.evc_from_dict() 0 23 9
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
from flask import jsonify, request
6
7
from kytos.core import KytosNApp, log, rest
8
from kytos.core.events import KytosEvent
9
from kytos.core.helpers import listen_to
10
from kytos.core.interface import TAG, UNI
11
from kytos.core.link import Link
12
from napps.kytos.mef_eline.models import EVC, DynamicPathManager
13
from napps.kytos.mef_eline.scheduler import CircuitSchedule, Scheduler
14
from napps.kytos.mef_eline.storehouse import StoreHouse
15
16
17
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
    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
        self.sched = Scheduler()
33
34
        # object to save and load circuits
35
        self.storehouse = StoreHouse(self.controller)
36
37
        # set the controller that will manager the dynamic paths
38
        DynamicPathManager.set_controller(self.controller)
39
40
    def execute(self):
41
        """Execute once when the napp is running."""
42
        pass
43
44
    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
    @rest('/v2/evc/', methods=['GET'])
52
    def list_circuits(self):
53
        """Endpoint to return all circuits stored."""
54
        circuits = self.storehouse.get_data()
55
        if not circuits:
56
            return jsonify({"response": "No circuit stored."}), 200
57
58
        return jsonify(circuits), 200
59
60
    @rest('/v2/evc/<circuit_id>', methods=['GET'])
61
    def get_circuit(self, circuit_id):
62
        """Endpoint to return a circuit based on id."""
63
        circuits = self.storehouse.get_data()
64
65
        if circuit_id in circuits:
66
            result = circuits[circuit_id]
67
            status = 200
68
        else:
69
            result = {'response': f'circuit_id {circuit_id} not found'}
70
            status = 400
71
72
        return jsonify(result), status
73
74
    @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
        data = request.get_json()
101
102
        try:
103
            evc = self.evc_from_dict(data)
104
        except ValueError as exception:
105
            return jsonify("Bad request: {}".format(exception)), 400
106
107
        # verify duplicated evc
108
        if self.is_duplicated_evc(evc):
109
            return jsonify("Not Acceptable: This evc already exists."), 409
110
111
        # save circuit
112
        self.storehouse.save_evc(evc)
113
114
        # Schedule the circuit deploy
115
        self.sched.add(evc)
116
117
        # Notify users
118
        event = KytosEvent(name='kytos.mef_eline.created',
119
                           content=evc.as_dict())
120
        self.controller.buffers.app.put(event)
121
122
        return jsonify({"circuit_id": evc.id}), 201
123
124
    @rest('/v2/evc/<circuit_id>', methods=['PATCH'])
125
    def update(self, circuit_id):
126
        """Update a circuit based on payload.
127
128
        The EVC required attributes can't be updated.
129
        """
130
        data = request.get_json()
131
        circuits = self.storehouse.get_data()
132
133
        if circuit_id not in circuits:
134
            result = {'response': f'circuit_id {circuit_id} not found'}
135
            return jsonify(result), 404
136
137
        try:
138
            evc = self.evc_from_dict(circuits.get(circuit_id))
139
            evc.update(**data)
140
            self.storehouse.save_evc(evc)
141
            result = {evc.id: evc.as_dict()}
142
            status = 200
143
        except ValueError as exception:
144
            result = "Bad request: {}".format(exception)
145
            status = 400
146
147
        return jsonify(result), status
148
149
    def is_duplicated_evc(self, evc):
150
        """Verify if the circuit given is duplicated with the stored evcs.
151
152
        Args:
153
            evc (EVC): circuit to be analysed.
154
155
        Returns:
156
            boolean: True if the circuit is duplicated, otherwise False.
157
158
        """
159
        for circuit_dict in self.storehouse.get_data().values():
160
            try:
161
                circuit = self.evc_from_dict(circuit_dict)
162
            except ValueError:
163
                continue
164
165
            if circuit == evc:
166
                return True
167
168
        return False
169
170
    @listen_to('kytos/topology.updated')
171
    def trigger_evc_reprovisioning(self, *_):
172
        """Listen to topology update to trigger EVCs (re)provisioning.
173
174
        Schedule all Circuits with valid UNIs.
175
        """
176
        for data in self.storehouse.get_data().values():
177
            try:
178
                evc = self.evc_from_dict(data)
179
                self.sched.add(evc)
180
            except ValueError as _exception:
181
                log.debug(f'{data.get("id")} can not be provisioning yet.')
182
183
    @listen_to('kytos.*.link.up', 'kytos.*.link.end_maintenance')
184
    def handle_link_up(self, event):
185
        """Change circuit when link is up or end_maintenance."""
186
        evc = None
187
188
        for data in self.storehouse.get_data().values():
189
            try:
190
                evc = self.evc_from_dict(data)
191
            except ValueError as _exception:
192
                log.debug(f'{data.get("id")} can not be provisioning yet.')
193
                continue
194
195
            if not evc.is_affected_by_link(event.link):
196
                evc.handle_link_up(event.link)
197
198
    @listen_to('kytos.*.link.down', 'kytos.*.link.under_maintenance')
199
    def handle_link_down(self, event):
200
        """Change circuit when link is down or under_mantenance."""
201
        evc = None
202
203
        for data in self.storehouse.get_data().values():
204
            try:
205
                evc = self.evc_from_dict(data)
206
            except ValueError as _exception:
207
                log.debug(f'{data.get("id")} can not be provisioning yet.')
208
                continue
209
210
            if not evc.is_affected_by_link(event.link):
211
                evc.handle_link_down()
212
213
    def evc_from_dict(self, evc_dict):
214
        """Convert some dict values to instance of EVC classes.
215
216
        This method will convert: [UNI, Link]
217
        """
218
        data = evc_dict.copy()  # Do not modify the original dict
219
220
        for attribute, value in data.items():
221
222
            if 'uni' in attribute:
223
                data[attribute] = self.uni_from_dict(value)
224
225
            if attribute == 'circuit_schedule':
226
                data[attribute] = []
227
                for schedule in value:
228
                    data[attribute].append(CircuitSchedule.from_dict(schedule))
229
230
            if ('path' in attribute or 'link' in attribute) and \
231
               (attribute != 'dynamic_backup_path'):
232
                if value:
233
                    data[attribute] = self.link_from_dict(value)
234
235
        return EVC(**data)
236
237
    def uni_from_dict(self, uni_dict):
238
        """Return a UNI object from python dict."""
239
        if uni_dict is None:
240
            return False
241
242
        interface_id = uni_dict.get("interface_id")
243
        interface = self.controller.get_interface_by_id(interface_id)
244
        if interface is None:
245
            return False
246
247
        tag = TAG.from_dict(uni_dict.get("tag"))
248
249
        if tag is False:
250
            return False
251
252
        try:
253
            uni = UNI(interface, tag)
254
        except TypeError:
255
            return False
256
257
        return uni
258
259
    def link_from_dict(self, link_dict):
260
        """Return a Link object from python dict."""
261
        id_a = link_dict.get('endpoint_a')
262
        id_b = link_dict.get('endpoint_b')
263
264
        endpoint_a = self.controller.get_interface_by_id(id_b)
265
        endpoint_b = self.controller.get_interface_by_id(id_a)
266
267
        link = Link(endpoint_a, endpoint_b)
268
        link.extend_metadata(link_dict.get('metadata'))
269
270
        return link
271