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

build.main.Main.extend_mw()   B

Complexity

Conditions 7

Size

Total Lines 30
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 7

Importance

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