Test Failed
Pull Request — master (#64)
by
unknown
02:34
created

build.main.Main.update_mw()   B

Complexity

Conditions 7

Size

Total Lines 21
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 20
nop 2
dl 0
loc 21
rs 8
c 0
b 0
f 0
ccs 18
cts 18
cp 1
crap 7
1
"""Main module of kytos/maintenance Kytos Network Application.
2
3
This NApp creates maintenance windows, allowing the maintenance of network
4
devices (switch, link, and interface) without receiving alerts.
5
"""
6 1
from datetime import timedelta
7
8 1
from flask import current_app, jsonify, request
9 1
from napps.kytos.maintenance.models import MaintenanceDeployer, MaintenanceID
10 1
from napps.kytos.maintenance.models import MaintenanceWindow as MW
11 1
from napps.kytos.maintenance.models import OverlapError, Scheduler, Status
12 1
from pydantic import ValidationError
13
from werkzeug.exceptions import BadRequest, NotFound, UnsupportedMediaType
14 1
15
from kytos.core import KytosNApp, rest
16
from kytos.core.interface import Interface
17 1
from kytos.core.link import Link
18
from kytos.core.switch import Switch
19
20
21
class Main(KytosNApp):
22
    """Main class of kytos/maintenance NApp.
23 1
24
    This class is the entry point for this napp.
25
    """
26
27
    def setup(self):
28
        """Replace the '__init__' method for the KytosNApp subclass.
29
30
        The setup method is automatically called by the controller when your
31 1
        application is loaded.
32 1
33
        So, if you have any setup routine, insert it here.
34 1
        """
35
        self.maintenance_deployer = \
36
            MaintenanceDeployer.new_deployer(self.controller)
37
38
        # Switch.register_status_func(
39
        #     'maintenance_status',
40
        #     self.maintenance_deployer.dev_in_maintenance
41
        # )
42
        # Interface.register_status_func(
43 1
        #     'maintenance_status',
44
        #     self.maintenance_deployer.dev_in_maintenance
45
        # )
46
        # Link.register_status_func(
47
        #     'maintenance_status',
48
        #     self.maintenance_deployer.dev_in_maintenance
49 1
        # )
50 1
        self.scheduler = Scheduler.new_scheduler(self.maintenance_deployer)
51 1
        self.scheduler.start()
52
53 1
    def execute(self):
54 1
        """Run after the setup method execution.
55
56
        You can also use this method in loop mode if you add to the above setup
57 1
        method a line like the following example:
58 1
59 1
            self.execute_as_loop(30)  # 30-second interval.
60 1
        """
61
62 1
    def shutdown(self):
63 1
        """Run when your napp is unloaded.
64
65 1
        If you have some cleanup procedure, insert it here.
66 1
        """
67 1
        self.scheduler.shutdown()
68
69 1
    @rest('/v1', methods=['GET'])
70 1
    def get_all_mw(self):
71
        """Return all maintenance windows."""
72
        maintenances = self.scheduler.list_maintenances()
73 1
        return current_app.response_class(
74 1
            f"{maintenances.json()}\n",
75 1
            mimetype=current_app.config["JSONIFY_MIMETYPE"],
76 1
        ), 200
77 1
78 1
    @rest('/v1/<mw_id>', methods=['GET'])
79 1
    def get_mw(self, mw_id: MaintenanceID):
80 1
        """Return one maintenance window."""
81 1
        window = self.scheduler.get_maintenance(mw_id)
82
        if window:
83 1
            return current_app.response_class(
84 1
                f"{window.json()}\n",
85
                mimetype=current_app.config["JSONIFY_MIMETYPE"],
86 1
            ), 200
87 1
        raise NotFound(f'Maintenance with id {mw_id} not found')
88 1
89 1
    @rest('/v1', methods=['POST'])
90 1
    def create_mw(self):
91 1
        """Create a new maintenance window."""
92 1
        data: dict = request.get_json()
93 1
        if not data:
94
            raise UnsupportedMediaType('The request does not have a json')
95 1
        try:
96 1
            maintenance = MW.parse_obj(data)
97 1
            force = data.get('force', False)
98 1
        except ValidationError as err:
99 1
            raise BadRequest(f'{err.errors()[0]["msg"]}') from err
100 1
        try:
101 1
            self.scheduler.add(maintenance, force=force)
102
        except OverlapError as err:
103 1
            raise BadRequest(f'{err}') from err
104 1
        return jsonify({'mw_id': maintenance.id}), 201
105
106 1
    @rest('/v1/<mw_id>', methods=['PATCH'])
107 1
    def update_mw(self, mw_id: MaintenanceID):
108 1
        """Update a maintenance window."""
109 1
        data = request.get_json()
110 1
        if not data:
111 1
            raise UnsupportedMediaType('The request does not have a json')
112 1
        old_maintenance = self.scheduler.get_maintenance(mw_id)
113 1
        if old_maintenance is None:
114 1
            raise NotFound(f'Maintenance with id {mw_id} not found')
115
        if old_maintenance.status == Status.RUNNING:
116
            raise BadRequest('Updating a running maintenance is not allowed')
117 1
        if 'status' in data:
118 1
            raise BadRequest('Updating a maintenance status is not allowed')
119
        try:
120 1
            new_maintenance = MW.parse_obj({**old_maintenance.dict(), **data})
121 1
        except ValidationError as err:
122 1
            raise BadRequest(f'{err.errors()[0]["msg"]}') from err
123 1
        if new_maintenance.id != old_maintenance.id:
124 1
            raise BadRequest('Updated id must match old id')
125 1
        self.scheduler.update(new_maintenance)
126 1
        return jsonify({'response': f'Maintenance {mw_id} updated'}), 200
127
128 1
    @rest('/v1/<mw_id>', methods=['DELETE'])
129 1
    def remove_mw(self, mw_id: MaintenanceID):
130
        """Delete a maintenance window."""
131 1
        maintenance = self.scheduler.get_maintenance(mw_id)
132 1
        if maintenance is None:
133 1
            raise NotFound(f'Maintenance with id {mw_id} not found')
134
        if maintenance.status == Status.RUNNING:
135
            raise BadRequest('Deleting a running maintenance is not allowed')
136 1
        self.scheduler.remove(mw_id)
137 1
        return jsonify({'response': f'Maintenance with id {mw_id} '
138
                                    f'successfully removed'}), 200
139 1
140 1
    @rest('/v1/<mw_id>/end', methods=['PATCH'])
141 1
    def end_mw(self, mw_id: MaintenanceID):
142 1
        """Finish a maintenance window right now."""
143 1
        maintenance = self.scheduler.get_maintenance(mw_id)
144 1
        if maintenance is None:
145 1
            raise NotFound(f'Maintenance with id {mw_id} not found')
146 1
        if maintenance.status == Status.PENDING:
147 1
            raise BadRequest(
148 1
                f'Maintenance window {mw_id} has not yet started'
149 1
            )
150 1
        if maintenance.status == Status.FINISHED:
151
            raise BadRequest(
152 1
                f'Maintenance window {mw_id} has already finished'
153 1
            )
154
        self.scheduler.end_maintenance_early(mw_id)
155 1
        return jsonify({'response': f'Maintenance window {mw_id} '
156 1
                                    f'finished'}), 200
157
158 1
    @rest('/v1/<mw_id>/extend', methods=['PATCH'])
159 1
    def extend_mw(self, mw_id):
160 1
        """Extend a running maintenance window."""
161 1
        data = request.get_json()
162 1
        if not data:
163
            raise UnsupportedMediaType('The request does not have a json')
164
        maintenance = self.scheduler.get_maintenance(mw_id)
165
        if maintenance is None:
166
            raise NotFound(f'Maintenance with id {mw_id} not found')
167
        if 'minutes' not in data:
168
            raise BadRequest('Minutes of extension must be sent')
169
        if maintenance.status == Status.PENDING:
170
            raise BadRequest(
171
                f'Maintenance window {mw_id} has not yet started'
172
            )
173
        if maintenance.status == Status.FINISHED:
174
            raise BadRequest(
175
                f'Maintenance window {mw_id} has already finished'
176
            )
177
        try:
178
            maintenance_end = maintenance.end + \
179
                timedelta(minutes=data['minutes'])
180
            new_maintenance = maintenance.copy(
181
                update={'end': maintenance_end}
182
            )
183
        except TypeError as exc:
184
            raise BadRequest('Minutes of extension must be integer') from exc
185
186
        self.scheduler.update(new_maintenance)
187
        return jsonify({'response': f'Maintenance {mw_id} extended'}), 200
188