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

MaintenanceController.bootstrap_indexes()   A

Complexity

Conditions 3

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nop 1
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
"""MaintenanceController."""
2
3
# pylint: disable=invalid-name
4
from datetime import datetime
5
import os
6
import pytz
7
from typing import Optional
8
9
from bson.codec_options import CodecOptions
10
import pymongo
11
from pymongo.errors import AutoReconnect
12
from tenacity import retry_if_exception_type, stop_after_attempt, wait_random
13
14
from kytos.core import log
15
from kytos.core.db import Mongo
16
from kytos.core.retry import before_sleep, for_all_methods, retries
17
from napps.kytos.maintenance.models import (
18
    MaintenanceWindow,
19
    MaintenanceWindows,
20
    MaintenanceID,
21
    Status,
22
)
23
24
25
@for_all_methods(
26
    retries,
27
    stop=stop_after_attempt(
28
        int(os.environ.get("MONGO_AUTO_RETRY_STOP_AFTER_ATTEMPT", 3))
29
    ),
30
    wait=wait_random(
31
        min=int(os.environ.get("MONGO_AUTO_RETRY_WAIT_RANDOM_MIN", 0.1)),
32
        max=int(os.environ.get("MONGO_AUTO_RETRY_WAIT_RANDOM_MAX", 1)),
33
    ),
34
    before_sleep=before_sleep,
35
    retry=retry_if_exception_type((AutoReconnect,)),
36
)
37
class MaintenanceController:
38
    """MaintenanceController."""
39
40
    def __init__(self, get_mongo=lambda: Mongo()) -> None:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable Mongo does not seem to be defined.
Loading history...
41
        """Constructor of MaintenanceController."""
42
        self.mongo = get_mongo()
43
        self.db_client = self.mongo.client
44
        self.db = self.db_client[self.mongo.db_name]
45
        self.windows = self.db['maintenance.windows'].with_options(
46
            codec_options=CodecOptions(
47
                tz_aware=True,
48
            )
49
        )
50
51
    def bootstrap_indexes(self) -> None:
52
        """Bootstrap all maintenance related indexes."""
53
        unique_index_tuples = [
54
            ("maintenance.windows", [("id", pymongo.ASCENDING)]),
55
        ]
56
        for collection, keys in unique_index_tuples:
57
            if self.mongo.bootstrap_index(collection, keys, unique=True):
58
                log.info(
59
                    f"Created DB unique index {keys}, collection: {collection})"
60
                )
61
62
    def insert_window(self, window: MaintenanceWindow):
63
        now = datetime.now(pytz.utc)
64
        self.windows.insert_one({
65
                    **window.dict(exclude={'inserted_at', 'updated_at'}),
66
                    'inserted_at': now,
67
                    'updated_at': now,
68
        })
69
70
71
    def update_window(self, window: MaintenanceWindow):
72
        self.windows.update_one(
73
            {'id': window.id},
74
            [{
75
                '$set': {
76
                    **window.dict(exclude={'inserted_at', 'updated_at'}),
77
                    'updated_at': '$$NOW',
78
                },
79
            }],
80
        )
81
82
    def get_window(self, mw_id: MaintenanceID) -> Optional[MaintenanceWindow]:
83
        window = self.windows.find_one(
84
            {'id': mw_id},
85
            {'_id': False},
86
        )
87
        if window is None:
88
            return None
89
        else:
90
            return MaintenanceWindow.construct(**window)
91
92
    def start_window(self, mw_id: MaintenanceID) -> MaintenanceWindow:
93
        window = self.windows.find_one_and_update(
94
            {'id': mw_id},
95
            [{
96
                '$set': {
97
                    'status': Status.RUNNING,
98
                    'last_modified': '$$NOW',
99
                },
100
            }],
101
            {'_id': False},
102
            return_document=pymongo.ReturnDocument.AFTER,
103
        )
104
        return MaintenanceWindow.construct(**window)
105
106
    def end_window(self, mw_id: MaintenanceID) -> MaintenanceWindow:
107
        window = self.windows.find_one_and_update(
108
            {'id': mw_id},
109
            [{
110
                '$set': {
111
                    'status': Status.FINISHED,
112
                    'last_modified': '$$NOW',
113
                },
114
            }],
115
            {'_id': False},
116
            return_document=pymongo.ReturnDocument.AFTER,
117
        )
118
        return MaintenanceWindow.construct(**window)
119
120
    def check_overlap(self, window):
121
        # If two time periods are overlapping,
122
        # then the start of one time period must occur in the other time period
123
        windows = self.windows.find(
124
            {
125
                '$or': [
126
                    {'$and': [
127
                        {'start': {'$lte': window.start}},
128
                        {'end': {'$gt': window.start}},
129
                    ]},
130
                    {'$and': [
131
                        {'start': {'$gte': window.start}},
132
                        {'start': {'$lt': window.end}},
133
                    ]}
134
                ]
135
            },
136
            {'_id': False}
137
        )
138
        return MaintenanceWindows.construct(
139
            __root__ = [MaintenanceWindow.construct(**window) for window in windows]
140
        )
141
142
    def get_windows(self) -> MaintenanceWindows:
143
        windows = self.windows.find(projection={'_id': False})
144
        return MaintenanceWindows.construct(
145
            __root__ = [MaintenanceWindow.construct(**window) for window in windows]
146
        )
147
148
    def remove_window(self, mw_id: MaintenanceID):
149
        self.windows.delete_one({'id': mw_id})
150
151
    def prepare_start(self):
152
        now = datetime.now(pytz.utc)
153
        self.windows.update_many(
154
            {'$and': [
155
                {'status': {'$eq': Status.PENDING}},
156
                {'start': {'$lte': now}},
157
            ]},
158
            {
159
                '$set': {
160
                    'status': Status.RUNNING,
161
                    'last_modified': now,
162
                },
163
            }
164
        )
165
        self.windows.update_many(
166
            {'$and': [
167
                {'status': {'$eq': Status.RUNNING}},
168
                {'end': {'$lte': now}},
169
            ]},
170
            {
171
                '$set': {
172
                    'status': Status.FINISHED,
173
                    'last_modified': now,
174
                },
175
            }
176
        )
177