Test Failed
Pull Request — master (#64)
by
unknown
05:23
created

build.main   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 179
Duplicated Lines 0 %

Test Coverage

Coverage 96.26%

Importance

Changes 0
Metric Value
eloc 121
dl 0
loc 179
rs 9.6
c 0
b 0
f 0
ccs 103
cts 107
cp 0.9626
wmc 35

10 Methods

Rating   Name   Duplication   Size   Complexity  
A Main.get_all_mw() 0 5 1
A Main.update_mw() 0 19 5
A Main.get_mw() 0 7 2
A Main.create_mw() 0 13 3
A Main.shutdown() 0 6 1
A Main.setup() 0 10 1
A Main.execute() 0 2 1
A Main.end_mw() 0 17 4
B Main.extend_mw() 0 31 7
A Main.remove_mw() 0 11 3

1 Function

Rating   Name   Duplication   Size   Complexity  
B validate_mw() 0 14 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
TIME_FMT = "%Y-%m-%dT%H:%M:%S%z"
19
20
21
def validate_mw(maintenance: MW):
22
    """Validates received maintenance windows
23 1
    """
24
    now = datetime.now(pytz.utc)
25
    if maintenance is None:
26
        raise BadRequest('One or more items are invalid')
27
    if maintenance.start < now:
28
        raise BadRequest('Start in the past not allowed')
29
    if maintenance.end <= maintenance.start:
30
        raise BadRequest('End before start not allowed')
31 1
    if not maintenance.switches\
32 1
        and not maintenance.interfaces\
33
        and not maintenance.links:
34 1
        raise BadRequest('At least one item must be provided')
35
36
37
class Main(KytosNApp):
38
    """Main class of kytos/maintenance NApp.
39
40
    This class is the entry point for this napp.
41
    """
42
43 1
    def setup(self):
44
        """Replace the '__init__' method for the KytosNApp subclass.
45
46
        The setup method is automatically called by the controller when your
47
        application is loaded.
48
49 1
        So, if you have any setup routine, insert it here.
50 1
        """
51 1
        self.scheduler = Scheduler.new_scheduler(self.controller)
52
        self.scheduler.start()
53 1
54 1
    def execute(self):
55
        """Run after the setup method execution.
56
57 1
        You can also use this method in loop mode if you add to the above setup
58 1
        method a line like the following example:
59 1
60 1
            self.execute_as_loop(30)  # 30-second interval.
61
        """
62 1
63 1
    def shutdown(self):
64
        """Run when your napp is unloaded.
65 1
66 1
        If you have some cleanup procedure, insert it here.
67 1
        """
68
        self.scheduler.shutdown()
69 1
70 1
    @rest('/v1', methods=['GET'])
71
    def get_all_mw(self):
72
        """Return all maintenance windows."""
73 1
        maintenances = self.scheduler.list_maintenances()
74 1
        return jsonify(maintenances), 200
75 1
76 1
    @rest('/v1/<mw_id>', methods=['GET'])
77 1
    def get_mw(self, mw_id: MaintenanceID):
78 1
        """Return one maintenance window."""
79 1
        window = self.scheduler.get_maintenance(mw_id)
80 1
        if window:
81 1
            return jsonify(window.dict()), 200
82
        raise NotFound(f'Maintenance with id {mw_id} not found')
83 1
84 1
    @rest('/v1', methods=['POST'])
85
    def create_mw(self):
86 1
        """Create a new maintenance window."""
87 1
        data = request.get_json()
88 1
        if not data:
89 1
            raise UnsupportedMediaType('The request does not have a json')
90 1
        try:
91 1
            maintenance = MW.parse_obj(data)
92 1
        except ValidationError as err:
93 1
            raise BadRequest('Failed to create window') from err
94
        validate_mw(maintenance)
95 1
        self.scheduler.add(maintenance)
96 1
        return jsonify({'mw_id': maintenance.id}), 201
97 1
98 1
    @rest('/v1/<mw_id>', methods=['PATCH'])
99 1
    def update_mw(self, mw_id: MaintenanceID):
100 1
        """Update a maintenance window."""
101 1
        data = request.get_json()
102
        if not data:
103 1
            raise UnsupportedMediaType('The request does not have a json')
104 1
        old_maintenance = self.scheduler.get_maintenance(mw_id)
105
        if old_maintenance is None:
106 1
            raise NotFound(f'Maintenance with id {mw_id} not found')
107 1
        if old_maintenance.status == Status.RUNNING:
108 1
            raise BadRequest('Updating a running maintenance is not allowed')
109 1
        try:
110 1
            new_maintenance = MW.parse_obj({**old_maintenance.dict(), **data})
111 1
        except ValidationError as err:
112 1
            raise BadRequest('Failed to create window') from err
113 1
        validate_mw(new_maintenance)
114 1
        self.scheduler.remove(mw_id)
115
        self.scheduler.add(new_maintenance)
116
        return jsonify({'response': f'Maintenance {mw_id} updated'}), 200
117 1
118 1
    @rest('/v1/<mw_id>', methods=['DELETE'])
119
    def remove_mw(self, mw_id: MaintenanceID):
120 1
        """Delete a maintenance window."""
121 1
        maintenance = self.scheduler.get_maintenance(mw_id)
122 1
        if maintenance is None:
123 1
            raise NotFound(f'Maintenance with id {mw_id} not found')
124 1
        if maintenance.status == Status.RUNNING:
125 1
            raise BadRequest('Deleting a running maintenance is not allowed')
126 1
        self.scheduler.remove(mw_id)
127
        return jsonify({'response': f'Maintenance with id {mw_id} '
128 1
                                    f'successfully removed'}), 200
129 1
130
    @rest('/v1/<mw_id>/end', methods=['PATCH'])
131 1
    def end_mw(self, mw_id: MaintenanceID):
132 1
        """Finish a maintenance window right now."""
133 1
        maintenance = self.scheduler.get_maintenance(mw_id)
134
        if maintenance is None:
135
            raise NotFound(f'Maintenance with id {mw_id} not found')
136 1
        if maintenance.status == Status.PENDING:
137 1
            raise BadRequest(
138
                f'Maintenance window {mw_id} has not yet started'
139 1
            )
140 1
        if maintenance.status == Status.FINISHED:
141 1
            raise BadRequest(
142 1
                f'Maintenance window {mw_id} has already finished'
143 1
            )
144 1
        self.scheduler.end_maintenance_early(mw_id)
145 1
        return jsonify({'response': f'Maintenance window {mw_id} '
146 1
                                    f'finished'}), 200
147 1
148 1
    @rest('/v1/<mw_id>/extend', methods=['PATCH'])
149 1
    def extend_mw(self, mw_id):
150 1
        """Extend a running maintenance window."""
151
        data = request.get_json()
152 1
        if not data:
153 1
            raise UnsupportedMediaType('The request does not have a json')
154
        maintenance = self.scheduler.get_maintenance(mw_id)
155 1
        if maintenance is None:
156 1
            raise NotFound(f'Maintenance with id {mw_id} not found')
157
        if 'minutes' not in data:
158 1
            raise BadRequest('Minutes of extension must be sent')
159 1
        if maintenance.status == Status.PENDING:
160 1
            raise BadRequest(
161 1
                f'Maintenance window {mw_id} has not yet started'
162 1
            )
163
        if maintenance.status == Status.FINISHED:
164
            raise BadRequest(
165
                f'Maintenance window {mw_id} has already finished'
166
            )
167
        try:
168
            maintenance_end = maintenance.end + \
169
                timedelta(minutes=data['minutes'])
170
            new_maintenance = maintenance.copy(
171
                update = {'end': maintenance_end}
172
            )
173
        except TypeError as exc:
174
            raise BadRequest('Minutes of extension must be integer') from exc
175
176
        self.scheduler.remove(maintenance.id)
177
        self.scheduler.add(new_maintenance)
178
        return jsonify({'response': f'Maintenance {mw_id} extended'}), 200
179