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

build.main   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 176
Duplicated Lines 0 %

Test Coverage

Coverage 96.26%

Importance

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