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

build.main.Main.create_mw()   A

Complexity

Conditions 4

Size

Total Lines 16
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4.0466

Importance

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