Passed
Pull Request — master (#150)
by Antonio
03:28
created

build.main   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 281
Duplicated Lines 0 %

Test Coverage

Coverage 47.89%

Importance

Changes 0
Metric Value
eloc 153
dl 0
loc 281
rs 8.64
c 0
b 0
f 0
ccs 68
cts 142
cp 0.4789
wmc 47

14 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 14 4
A Main.handle_link_up() 0 6 4
A Main.link_from_dict() 0 20 4
A Main.get_circuit() 0 12 2
A Main.shutdown() 0 2 1
A Main.setup() 0 19 1
A Main.handle_link_down() 0 7 3
A Main.execute() 0 2 1
B Main.create_circuit() 0 59 5
C Main.evc_from_dict() 0 30 11
A Main.update() 0 22 3
A Main.delete_circuit() 0 23 2

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
        # dictionary of EVCs created
41 2
        self.circuits = {}
42
43 2
    def execute(self):
44
        """Execute once when the napp is running."""
45
46 2
    def shutdown(self):
47
        """Execute when your napp is unloaded.
48
49
        If you have some cleanup procedure, insert it here.
50
        """
51
52 2
    @rest('/v2/evc/', methods=['GET'])
53
    def list_circuits(self):
54
        """Endpoint to return all circuits stored."""
55 2
        circuits = self.storehouse.get_data()
56 2
        if not circuits:
57 2
            return jsonify({}), 200
58
59 2
        return jsonify(circuits), 200
60
61 2
    @rest('/v2/evc/<circuit_id>', methods=['GET'])
62
    def get_circuit(self, circuit_id):
63
        """Endpoint to return a circuit based on id."""
64 2
        circuits = self.storehouse.get_data()
65 2
        try:
66 2
            result = circuits[circuit_id]
67 2
            status = 200
68 2
        except KeyError:
69 2
            result = {'response': f'circuit_id {circuit_id} not found'}
70 2
            status = 400
71
72 2
        return jsonify(result), status
73
74 2
    @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 2
        data = request.get_json()
101
102 2
        if not data:
103 2
            return jsonify("Bad request: The request do not have a json."), 400
104
105 2
        try:
106 2
            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 2
        if self.is_duplicated_evc(evc):
112 2
            return jsonify("Not Acceptable: This evc already exists."), 409
113
114
        # store circuit in dictionary
115 2
        self.circuits[evc.id] = evc
116
117
        # save circuit
118 2
        self.storehouse.save_evc(evc)
119
120
        # Schedule the circuit deploy
121 2
        self.sched.add(evc)
122
123
        # Circuit has no schedule, deploy now
124 2
        if not evc.circuit_scheduler:
125 2
            evc.deploy()
126
127
        # Notify users
128 2
        event = KytosEvent(name='kytos.mef_eline.created',
129
                           content=evc.as_dict())
130 2
        self.controller.buffers.app.put(event)
131
132 2
        return jsonify({"circuit_id": evc.id}), 201
133
134 2
    @rest('/v2/evc/<circuit_id>', methods=['PATCH'])
135
    def update(self, circuit_id):
136
        """Update a circuit based on payload.
137
138
        The EVC required attributes (name, uni_a, uni_z) can't be updated.
139
        """
140
        data = request.get_json()
141
142
        try:
143
            evc = self.circuits[circuit_id]
144
            evc.update(**data)
145
            evc.sync()
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
        except KeyError:
152
            result = {'response': f'circuit_id {circuit_id} not found'}
153
            status = 404
154
155
        return jsonify(result), status
156
157 2
    @rest('/v2/evc/<circuit_id>', methods=['DELETE'])
158
    def delete_circuit(self, circuit_id):
159
        """Remove a circuit.
160
161
        First, the flows are removed from the switches, and then the EVC is
162
        disabled.
163
        """
164
        try:
165
            evc = self.circuits[circuit_id]
166
            log.info(f'Removing {circuit_id}')
167
            evc.remove_current_flows()
168
            evc.deactivate()
169
            evc.disable()
170
            self.sched.remove(evc)
171
            evc.archive()
172
            evc.sync()
173
            result = {'response': f'Circuit {circuit_id} removed'}
174
            status = 200
175
        except KeyError:
176
            result = {'response': f'circuit_id {circuit_id} not found'}
177
            status = 404
178
179
        return jsonify(result), status
180
181 2
    def is_duplicated_evc(self, evc):
182
        """Verify if the circuit given is duplicated with the stored evcs.
183
184
        Args:
185
            evc (EVC): circuit to be analysed.
186
187
        Returns:
188
            boolean: True if the circuit is duplicated, otherwise False.
189
190
        """
191 2
        for circuit in self.circuits.values():
192 2
            if not circuit.archived and circuit == evc:
193 2
                return True
194 2
        return False
195
196 2
    @listen_to('kytos/topology.link_up')
197
    def handle_link_up(self, event):
198
        """Change circuit when link is up or end_maintenance."""
199
        for evc in self.circuits.values():
200
            if evc.is_enabled() and not evc.archived:
201
                evc.handle_link_up(event.content['link'])
202
203 2
    @listen_to('kytos/topology.link_down')
204
    def handle_link_down(self, event):
205
        """Change circuit when link is down or under_mantenance."""
206
        for evc in self.circuits.values():
207
            if evc.is_affected_by_link(event.content['link']):
208
                log.info('handling evc %s' % evc)
209
                evc.handle_link_down()
210
211 2
    def evc_from_dict(self, evc_dict):
212
        """Convert some dict values to instance of EVC classes.
213
214
        This method will convert: [UNI, Link]
215
        """
216 2
        data = evc_dict.copy()  # Do not modify the original dict
217
218 2
        for attribute, value in data.items():
219
220 2
            if 'uni' in attribute:
221 2
                try:
222 2
                    data[attribute] = self.uni_from_dict(value)
223
                except ValueError as exc:
224
                    raise ValueError(f'Error creating UNI: {exc}')
225
226 2
            if attribute == 'circuit_scheduler':
227
                data[attribute] = []
228
                for schedule in value:
229
                    data[attribute].append(CircuitSchedule.from_dict(schedule))
230
231 2
            if 'link' in attribute:
232
                if value:
233
                    data[attribute] = self.link_from_dict(value)
234
235 2
            if 'path' in attribute and attribute != 'dynamic_backup_path':
236
                if value:
237
                    data[attribute] = [self.link_from_dict(link)
238
                                       for link in value]
239
240 2
        return EVC(self.controller, **data)
241
242 2
    def uni_from_dict(self, uni_dict):
243
        """Return a UNI object from python dict."""
244
        if uni_dict is None:
245
            return False
246
247
        interface_id = uni_dict.get("interface_id")
248
        interface = self.controller.get_interface_by_id(interface_id)
249
        if interface is None:
250
            raise ValueError(f'Could not instantiate interface {interface_id}')
251
252
        tag_dict = uni_dict.get("tag")
253
        tag = TAG.from_dict(tag_dict)
254
        if tag is False:
255
            raise ValueError(f'Could not instantiate tag from dict {tag_dict}')
256
257
        uni = UNI(interface, tag)
258
259
        return uni
260
261 2
    def link_from_dict(self, link_dict):
262
        """Return a Link object from python dict."""
263
        id_a = link_dict.get('endpoint_a').get('id')
264
        id_b = link_dict.get('endpoint_b').get('id')
265
266
        endpoint_a = self.controller.get_interface_by_id(id_a)
267
        endpoint_b = self.controller.get_interface_by_id(id_b)
268
269
        link = Link(endpoint_a, endpoint_b)
270
        if 'metadata' in link_dict:
271
            link.extend_metadata(link_dict.get('metadata'))
272
273
        s_vlan = link.get_metadata('s_vlan')
274
        if s_vlan:
275
            tag = TAG.from_dict(s_vlan)
276
            if tag is False:
277
                error_msg = f'Could not instantiate tag from dict {s_vlan}'
278
                raise ValueError(error_msg)
279
            link.update_metadata('s_vlan', tag)
280
        return link
281