Issues (14)

controllers/__init__.py (1 issue)

1
"""MaintenanceController."""
2
3
# pylint: disable=invalid-name
4 1
from datetime import datetime
5 1
import os
6 1
import pytz
7 1
from typing import Optional
8
9 1
from bson.codec_options import CodecOptions
10 1
import pymongo
11 1
from pymongo.errors import ConnectionFailure, ExecutionTimeout
12 1
from tenacity import retry_if_exception_type, stop_after_attempt, wait_random
13
14 1
from kytos.core import log
15 1
from kytos.core.db import Mongo
16 1
from kytos.core.retry import before_sleep, for_all_methods, retries
17 1
from napps.kytos.maintenance.models import (
18
    MaintenanceWindow,
19
    MaintenanceWindows,
20
    MaintenanceID,
21
    Status,
22
)
23
24
25 1
@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((ConnectionFailure, ExecutionTimeout)),
36
)
37 1
class MaintenanceController:
38
    """MaintenanceController."""
39
40 1
    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 1
        self.mongo = get_mongo()
43 1
        self.db_client = self.mongo.client
44 1
        self.db = self.db_client[self.mongo.db_name]
45 1
        self.windows = self.db['maintenance.windows'].with_options(
46
            codec_options=CodecOptions(
47
                tz_aware=True,
48
            )
49
        )
50
51 1
    def bootstrap_indexes(self) -> None:
52
        """Bootstrap all maintenance related indexes."""
53 1
        unique_index_tuples = [
54
            ("maintenance.windows", [("id", pymongo.ASCENDING)]),
55
        ]
56 1
        for collection, keys in unique_index_tuples:
57 1
            if self.mongo.bootstrap_index(collection, keys, unique=True):
58 1
                log.info(
59
                    f"Created DB unique index {keys}, collection: {collection})"
60
                )
61
62 1
    def insert_window(self, window: MaintenanceWindow):
63 1
        now = datetime.now(pytz.utc)
64 1
        self.windows.insert_one({
65
                    **window.model_dump(exclude={'inserted_at', 'updated_at'}),
66
                    'inserted_at': now,
67
                    'updated_at': now,
68
        })
69
70 1
    def update_window(self, window: MaintenanceWindow):
71 1
        self.windows.update_one(
72
            {'id': window.id},
73
            [{
74
                '$set': {
75
                    **window.model_dump(exclude={'inserted_at', 'updated_at'}),
76
                    'updated_at': '$$NOW',
77
                },
78
            }],
79
        )
80
81 1
    def get_window(self, mw_id: MaintenanceID) -> Optional[MaintenanceWindow]:
82 1
        window = self.windows.find_one(
83
            {'id': mw_id},
84
            {'_id': False},
85
        )
86 1
        if window is None:
87 1
            return None
88
        else:
89 1
            return MaintenanceWindow.model_construct(**window)
90
91 1
    def start_window(self, mw_id: MaintenanceID) -> MaintenanceWindow:
92
        window = self.windows.find_one_and_update(
93
            {'id': mw_id},
94
            [{
95
                '$set': {
96
                    'status': Status.RUNNING,
97
                    'last_modified': '$$NOW',
98
                },
99
            }],
100
            {'_id': False},
101
            return_document=pymongo.ReturnDocument.AFTER,
102
        )
103
        return MaintenanceWindow.model_construct(**window)
104
105 1
    def end_window(self, mw_id: MaintenanceID) -> MaintenanceWindow:
106
        window = self.windows.find_one_and_update(
107
            {'id': mw_id},
108
            [{
109
                '$set': {
110
                    'status': Status.FINISHED,
111
                    'last_modified': '$$NOW',
112
                },
113
            }],
114
            {'_id': False},
115
            return_document=pymongo.ReturnDocument.AFTER,
116
        )
117
        return MaintenanceWindow.model_construct(**window)
118
119 1
    def check_overlap(self, window):
120
        # If two time periods are overlapping,
121
        # then the start of one time period must occur in the other time period
122
        windows = self.windows.find(
123
            {
124
                '$and': [
125
                    {'status': {'$ne': Status.FINISHED}},
126
                    {'$or': [
127
                        {'$and': [
128
                            {'start': {'$lte': window.start}},
129
                            {'end': {'$gt': window.start}},
130
                        ]},
131
                        {'$and': [
132
                            {'start': {'$gte': window.start}},
133
                            {'start': {'$lt': window.end}},
134
                        ]},
135
                    ]},
136
                ],
137
            },
138
            {'_id': False}
139
        )
140
        return MaintenanceWindows.model_construct(
141
            root = [MaintenanceWindow.model_construct(**window) for window in windows]
142
        )
143
144 1
    def get_windows(self) -> MaintenanceWindows:
145 1
        windows = self.windows.find(projection={'_id': False})
146 1
        return MaintenanceWindows.model_construct(
147
            root = [MaintenanceWindow.model_construct(**window) for window in windows]
148
        )
149
150 1
    def get_unfinished_windows(self) -> MaintenanceWindows:
151
        windows = self.windows.find(
152
            {'status': {'$ne': Status.FINISHED}},
153
            projection={'_id': False}
154
        )
155
        return MaintenanceWindows.model_construct(
156
            root = [MaintenanceWindow.model_construct(**window) for window in windows]
157
        )
158
159 1
    def remove_window(self, mw_id: MaintenanceID):
160
        self.windows.delete_one({'id': mw_id})
161
162 1
    def prepare_start(self):
163
        now = datetime.now(pytz.utc)
164
        self.windows.update_many(
165
            {'$and': [
166
                {'status': {'$eq': Status.PENDING}},
167
                {'start': {'$lte': now}},
168
            ]},
169
            {
170
                '$set': {
171
                    'status': Status.RUNNING,
172
                    'last_modified': now,
173
                },
174
            }
175
        )
176
        self.windows.update_many(
177
            {'$and': [
178
                {'status': {'$eq': Status.RUNNING}},
179
                {'end': {'$lte': now}},
180
            ]},
181
            {
182
                '$set': {
183
                    'status': Status.FINISHED,
184
                    'last_modified': now,
185
                },
186
            }
187
        )
188