19
19
from __future__ import annotations
20
20
21
21
import os
22
+ import time
22
23
import typing
23
24
24
25
import click
25
26
import django .core
26
27
import django .core .wsgi
28
+ import django_rq
29
+ import redis .exceptions
27
30
28
31
from django .conf import settings
32
+ from django .db import connections , OperationalError
29
33
30
34
if typing .TYPE_CHECKING :
31
35
from click import Context
32
36
33
37
38
+ DEFAULT_BACKOFF_MAX = 120
39
+ DEFAULT_MAX_RETRIES = 10
40
+
41
+
34
42
@click .group ()
35
43
@click .pass_context
36
44
def run (ctx : Context ):
@@ -60,6 +68,8 @@ def server(ctx: Context, devel: bool, clear_tasks: bool):
60
68
should be run with a reverse proxy. If you activate the '--dev' flag,
61
69
a HTTP server will be run instead.
62
70
"""
71
+ wait_for_services (wait_archivist_storage = False )
72
+
63
73
env = os .environ
64
74
65
75
env ["UWSGI_ENV" ] = f"DJANGO_SETTINGS_MODULE={ ctx .obj ['cfg' ]} "
@@ -114,6 +124,8 @@ def eventizers(workers: int):
114
124
Workers get jobs from the GRIMOIRELAB_Q_EVENTIZER_JOBS queue defined
115
125
in the configuration file.
116
126
"""
127
+ wait_for_services (wait_archivist_storage = False )
128
+
117
129
django .core .management .call_command (
118
130
'rqworker-pool' , settings .GRIMOIRELAB_Q_EVENTIZER_JOBS ,
119
131
num_workers = workers
@@ -137,6 +149,8 @@ def archivists(workers: int):
137
149
Workers get jobs from the GRIMOIRELAB_Q_ARCHIVIST_JOBS queue defined
138
150
in the configuration file.
139
151
"""
152
+ wait_for_services ()
153
+
140
154
django .core .management .call_command (
141
155
'rqworker-pool' , settings .GRIMOIRELAB_Q_ARCHIVIST_JOBS ,
142
156
num_workers = workers
@@ -191,3 +205,80 @@ def create_background_tasks(clear_tasks: bool):
191
205
elif workers < current :
192
206
tasks = StorageTask .objects .all ()[workers :]
193
207
tasks .update (burst = True )
208
+
209
+
210
+ def wait_for_services (
211
+ wait_database : bool = True ,
212
+ wait_redis : bool = True ,
213
+ wait_archivist_storage : bool = True
214
+ ):
215
+ """Wait for services to be available before starting"""
216
+
217
+ if wait_database :
218
+ wait_database_ready ()
219
+
220
+ if wait_redis :
221
+ wait_redis_ready ()
222
+
223
+ if wait_archivist_storage :
224
+ wait_archivist_storage_ready ()
225
+
226
+
227
+ def _sleep_backoff (attempt : int ) -> None :
228
+ """Sleep with exponential backoff"""
229
+
230
+ backoff = min (DEFAULT_BACKOFF_MAX , 2 ** attempt )
231
+ time .sleep (backoff )
232
+
233
+
234
+ def wait_database_ready ():
235
+ """Wait for the database to be available before starting"""
236
+
237
+ for attempt in range (DEFAULT_MAX_RETRIES ):
238
+ try :
239
+ db_conn = connections ['default' ]
240
+ if db_conn :
241
+ db_conn .cursor ()
242
+ db_conn .close ()
243
+ break
244
+ except OperationalError as e :
245
+ click .echo (f"[{ attempt } /{ DEFAULT_MAX_RETRIES } ] Database connection not ready { e .__cause__ } " )
246
+ _sleep_backoff (attempt )
247
+ else :
248
+ click .echo ("Failed to connect to the database" )
249
+ exit (1 )
250
+
251
+
252
+ def wait_redis_ready ():
253
+
254
+ for attempt in range (DEFAULT_MAX_RETRIES ):
255
+ try :
256
+ redis_conn = django_rq .get_connection (settings .GRIMOIRELAB_Q_EVENTIZER_JOBS )
257
+ redis_conn .ping ()
258
+ break
259
+ except redis .exceptions .ConnectionError as e :
260
+ click .echo (f"[{ attempt } /{ DEFAULT_MAX_RETRIES } ] Redis connection not ready { e .__cause__ } " )
261
+ _sleep_backoff (attempt )
262
+ else :
263
+ click .echo ("Failed to connect to Redis server" )
264
+ exit (1 )
265
+
266
+
267
+ def wait_archivist_storage_ready ():
268
+ """Wait for the storage to be available before starting"""
269
+
270
+ for attempt in range (DEFAULT_MAX_RETRIES ):
271
+ from grimoirelab .core .scheduler .tasks .archivist import get_storage_backend
272
+ Storage = get_storage_backend (settings .GRIMOIRELAB_ARCHIVIST ['STORAGE_TYPE' ])
273
+ storage = Storage (url = settings .GRIMOIRELAB_ARCHIVIST ['STORAGE_URL' ],
274
+ db_name = settings .GRIMOIRELAB_ARCHIVIST ['STORAGE_INDEX' ],
275
+ verify_certs = settings .GRIMOIRELAB_ARCHIVIST ['STORAGE_VERIFY_CERT' ])
276
+
277
+ if storage .ping ():
278
+ break
279
+ else :
280
+ click .echo (f"[{ attempt } /{ DEFAULT_MAX_RETRIES } ] Storage connection not ready" )
281
+ _sleep_backoff (attempt )
282
+ else :
283
+ click .echo ("Failed to connect to archivist storage" )
284
+ exit (1 )
0 commit comments