cornflow 2.0.0a10__py3-none-any.whl → 2.0.0a12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- airflow_config/airflow_local_settings.py +1 -1
- cornflow/app.py +8 -3
- cornflow/cli/migrations.py +23 -3
- cornflow/cli/service.py +18 -18
- cornflow/cli/utils.py +16 -1
- cornflow/commands/dag.py +1 -1
- cornflow/config.py +13 -8
- cornflow/endpoints/__init__.py +8 -2
- cornflow/endpoints/alarms.py +66 -2
- cornflow/endpoints/data_check.py +53 -26
- cornflow/endpoints/execution.py +387 -132
- cornflow/endpoints/login.py +81 -63
- cornflow/endpoints/meta_resource.py +11 -3
- cornflow/migrations/versions/999b98e24225.py +34 -0
- cornflow/models/base_data_model.py +4 -32
- cornflow/models/execution.py +2 -3
- cornflow/models/meta_models.py +28 -22
- cornflow/models/user.py +7 -10
- cornflow/schemas/alarms.py +8 -0
- cornflow/schemas/execution.py +1 -1
- cornflow/schemas/query.py +2 -1
- cornflow/schemas/user.py +5 -20
- cornflow/shared/authentication/auth.py +201 -264
- cornflow/shared/const.py +3 -14
- cornflow/shared/databricks.py +5 -1
- cornflow/tests/const.py +1 -0
- cornflow/tests/custom_test_case.py +77 -26
- cornflow/tests/unit/test_actions.py +2 -2
- cornflow/tests/unit/test_alarms.py +55 -1
- cornflow/tests/unit/test_apiview.py +108 -3
- cornflow/tests/unit/test_cases.py +20 -29
- cornflow/tests/unit/test_cli.py +6 -5
- cornflow/tests/unit/test_commands.py +3 -3
- cornflow/tests/unit/test_dags.py +5 -6
- cornflow/tests/unit/test_executions.py +443 -123
- cornflow/tests/unit/test_instances.py +14 -2
- cornflow/tests/unit/test_instances_file.py +1 -1
- cornflow/tests/unit/test_licenses.py +1 -1
- cornflow/tests/unit/test_log_in.py +230 -207
- cornflow/tests/unit/test_permissions.py +8 -8
- cornflow/tests/unit/test_roles.py +48 -10
- cornflow/tests/unit/test_schemas.py +1 -1
- cornflow/tests/unit/test_tables.py +7 -7
- cornflow/tests/unit/test_token.py +19 -5
- cornflow/tests/unit/test_users.py +22 -6
- cornflow/tests/unit/tools.py +75 -10
- {cornflow-2.0.0a10.dist-info → cornflow-2.0.0a12.dist-info}/METADATA +16 -15
- {cornflow-2.0.0a10.dist-info → cornflow-2.0.0a12.dist-info}/RECORD +51 -51
- {cornflow-2.0.0a10.dist-info → cornflow-2.0.0a12.dist-info}/WHEEL +1 -1
- cornflow/endpoints/execution_databricks.py +0 -808
- {cornflow-2.0.0a10.dist-info → cornflow-2.0.0a12.dist-info}/entry_points.txt +0 -0
- {cornflow-2.0.0a10.dist-info → cornflow-2.0.0a12.dist-info}/top_level.txt +0 -0
cornflow/endpoints/execution.py
CHANGED
@@ -6,13 +6,17 @@ These endpoints hve different access url, but manage the same data entities
|
|
6
6
|
|
7
7
|
# Import from libraries
|
8
8
|
from cornflow_client.airflow.api import Airflow
|
9
|
+
from cornflow_client.databricks.api import Databricks
|
9
10
|
from cornflow_client.constants import INSTANCE_SCHEMA, CONFIG_SCHEMA, SOLUTION_SCHEMA
|
11
|
+
|
12
|
+
# TODO AGA: Porqué el import no funcina correctamente
|
10
13
|
from flask import request, current_app
|
11
14
|
from flask_apispec import marshal_with, use_kwargs, doc
|
12
15
|
|
13
16
|
# Import from internal modules
|
14
17
|
from cornflow.endpoints.meta_resource import BaseMetaResource
|
15
18
|
from cornflow.models import InstanceModel, DeployedOrch, ExecutionModel
|
19
|
+
from cornflow.orchestrator_constants import config_orchestrator
|
16
20
|
from cornflow.schemas.execution import (
|
17
21
|
ExecutionDetailsEndpointResponse,
|
18
22
|
ExecutionDetailsEndpointWithIndicatorsResponse,
|
@@ -24,10 +28,14 @@ from cornflow.schemas.execution import (
|
|
24
28
|
ExecutionEditRequest,
|
25
29
|
QueryFiltersExecution,
|
26
30
|
ReLaunchExecutionRequest,
|
27
|
-
ExecutionDetailsWithIndicatorsAndLogResponse
|
31
|
+
ExecutionDetailsWithIndicatorsAndLogResponse,
|
28
32
|
)
|
29
33
|
from cornflow.shared.authentication import Auth, authenticate
|
30
34
|
from cornflow.shared.compress import compressed
|
35
|
+
from cornflow.shared.const import (
|
36
|
+
AIRFLOW_BACKEND,
|
37
|
+
DATABRICKS_BACKEND,
|
38
|
+
)
|
31
39
|
from cornflow.shared.const import (
|
32
40
|
EXEC_STATE_RUNNING,
|
33
41
|
EXEC_STATE_ERROR,
|
@@ -36,10 +44,18 @@ from cornflow.shared.const import (
|
|
36
44
|
EXEC_STATE_UNKNOWN,
|
37
45
|
EXECUTION_STATE_MESSAGE_DICT,
|
38
46
|
AIRFLOW_TO_STATE_MAP,
|
47
|
+
DATABRICKS_TO_STATE_MAP,
|
39
48
|
EXEC_STATE_STOPPED,
|
40
49
|
EXEC_STATE_QUEUED,
|
41
50
|
)
|
42
|
-
|
51
|
+
|
52
|
+
from cornflow.shared.exceptions import (
|
53
|
+
AirflowError,
|
54
|
+
DatabricksError,
|
55
|
+
ObjectDoesNotExist,
|
56
|
+
InvalidData,
|
57
|
+
EndpointNotImplemented,
|
58
|
+
)
|
43
59
|
from cornflow.shared.validators import (
|
44
60
|
json_schema_validate_as_string,
|
45
61
|
json_schema_extend_and_validate_as_string,
|
@@ -48,14 +64,57 @@ from cornflow.shared.validators import (
|
|
48
64
|
|
49
65
|
class ExecutionEndpoint(BaseMetaResource):
|
50
66
|
"""
|
51
|
-
Endpoint used to create
|
67
|
+
Endpoint used to create and get executions
|
52
68
|
"""
|
53
69
|
|
54
70
|
def __init__(self):
|
55
71
|
super().__init__()
|
56
|
-
self.model = ExecutionModel
|
57
72
|
self.data_model = ExecutionModel
|
58
73
|
self.foreign_data = {"instance_id": InstanceModel}
|
74
|
+
self._orch_type = None
|
75
|
+
self._orch_client = None
|
76
|
+
self._orch_error = None
|
77
|
+
self._orch_to_state_map = None
|
78
|
+
self._orch_const = None
|
79
|
+
|
80
|
+
def _init_orch(self):
|
81
|
+
if self._orch_type is None:
|
82
|
+
self._orch_type = current_app.config["CORNFLOW_BACKEND"]
|
83
|
+
if self._orch_type == AIRFLOW_BACKEND:
|
84
|
+
self._orch_client = Airflow.from_config(current_app.config)
|
85
|
+
self._orch_error = AirflowError
|
86
|
+
self._orch_to_state_map = AIRFLOW_TO_STATE_MAP
|
87
|
+
self._orch_const = config_orchestrator["airflow"]
|
88
|
+
elif self._orch_type == DATABRICKS_BACKEND:
|
89
|
+
self._orch_client = Databricks.from_config(current_app.config)
|
90
|
+
self._orch_error = DatabricksError
|
91
|
+
self._orch_to_state_map = DATABRICKS_TO_STATE_MAP
|
92
|
+
self._orch_const = config_orchestrator["databricks"]
|
93
|
+
|
94
|
+
@property
|
95
|
+
def orch_type(self):
|
96
|
+
self._init_orch()
|
97
|
+
return self._orch_type
|
98
|
+
|
99
|
+
@property
|
100
|
+
def orch_client(self):
|
101
|
+
self._init_orch()
|
102
|
+
return self._orch_client
|
103
|
+
|
104
|
+
@property
|
105
|
+
def orch_error(self):
|
106
|
+
self._init_orch()
|
107
|
+
return self._orch_error
|
108
|
+
|
109
|
+
@property
|
110
|
+
def orch_to_state_map(self):
|
111
|
+
self._init_orch()
|
112
|
+
return self._orch_to_state_map
|
113
|
+
|
114
|
+
@property
|
115
|
+
def orch_const(self):
|
116
|
+
self._init_orch()
|
117
|
+
return self._orch_const
|
59
118
|
|
60
119
|
@doc(description="Get all executions", tags=["Executions"])
|
61
120
|
@authenticate(auth_class=Auth())
|
@@ -88,36 +147,35 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
88
147
|
]
|
89
148
|
|
90
149
|
for execution in running_executions:
|
91
|
-
|
92
|
-
|
93
|
-
|
150
|
+
run_id = execution.run_id
|
151
|
+
|
152
|
+
if not run_id:
|
153
|
+
# it's safe to say we will never get anything if we did not store the run_id
|
94
154
|
current_app.logger.warning(
|
95
155
|
"Error while the app tried to update the status of all running executions."
|
96
156
|
f"Execution {execution.id} has status {execution.state} but has no dag run associated."
|
97
157
|
)
|
98
158
|
continue
|
99
159
|
|
100
|
-
|
101
|
-
if not af_client.is_alive():
|
160
|
+
if not self.orch_client.is_alive():
|
102
161
|
current_app.logger.warning(
|
103
162
|
"Error while the app tried to update the status of all running executions."
|
104
163
|
"Airflow is not accessible."
|
105
164
|
)
|
106
165
|
continue
|
107
|
-
|
108
166
|
try:
|
109
|
-
response =
|
110
|
-
dag_name=execution.schema,
|
167
|
+
response = self.orch_client.get_run_status(
|
168
|
+
dag_name=execution.schema, run_id=run_id
|
111
169
|
)
|
112
|
-
except
|
170
|
+
except self.orch_error as err:
|
113
171
|
current_app.logger.warning(
|
114
172
|
"Error while the app tried to update the status of all running executions."
|
115
|
-
f"
|
173
|
+
f"Orchestrator responded with an error: {err}"
|
116
174
|
)
|
117
175
|
continue
|
118
176
|
|
119
177
|
data = response.json()
|
120
|
-
state =
|
178
|
+
state = self.orch_to_state_map.get(data["state"], EXEC_STATE_UNKNOWN)
|
121
179
|
execution.update_state(state)
|
122
180
|
|
123
181
|
return executions
|
@@ -139,18 +197,18 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
139
197
|
"""
|
140
198
|
# TODO: should validation should be done even if the execution is not going to be run?
|
141
199
|
# TODO: should the schema field be cross validated with the instance schema field?
|
142
|
-
config = current_app.config
|
143
200
|
|
144
201
|
if "schema" not in kwargs:
|
145
|
-
kwargs["schema"] = "
|
146
|
-
|
202
|
+
kwargs["schema"] = self.orch_const["def_schema"]
|
203
|
+
# region INDEPENDIENTE A AIRFLOW
|
204
|
+
config = current_app.config
|
147
205
|
execution, status_code = self.post_list(data=kwargs)
|
148
206
|
instance = InstanceModel.get_one_object(
|
149
207
|
user=self.get_user(), idx=execution.instance_id
|
150
208
|
)
|
151
209
|
|
152
210
|
current_app.logger.debug(f"The request is: {request.args.get('run')}")
|
153
|
-
# this allows testing without
|
211
|
+
# this allows testing without orchestrator interaction:
|
154
212
|
if request.args.get("run", "1") == "0":
|
155
213
|
current_app.logger.info(
|
156
214
|
f"User {self.get_user_id()} creates execution {execution.id} but does not run it."
|
@@ -158,26 +216,21 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
158
216
|
execution.update_state(EXEC_STATE_NOT_RUN)
|
159
217
|
return execution, 201
|
160
218
|
|
161
|
-
# We now try to launch the task in
|
162
|
-
|
163
|
-
|
164
|
-
err = "Airflow is not accessible"
|
165
|
-
current_app.logger.error(err)
|
166
|
-
execution.update_state(EXEC_STATE_ERROR_START)
|
167
|
-
raise AirflowError(
|
168
|
-
error=err,
|
169
|
-
payload=dict(
|
170
|
-
message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
|
171
|
-
state=EXEC_STATE_ERROR_START,
|
172
|
-
),
|
173
|
-
log_txt=f"Error while user {self.get_user()} tries to create an execution "
|
174
|
-
+ err,
|
175
|
-
)
|
176
|
-
# ask airflow if dag_name exists
|
219
|
+
# We now try to launch the task in the orchestrator
|
220
|
+
# We try to create an orch client
|
221
|
+
# Note schema is a string with the name of the job/dag
|
177
222
|
schema = execution.schema
|
178
|
-
|
223
|
+
# If we are dealing with DataBricks, the schema will
|
224
|
+
# be the job id
|
225
|
+
orch_client, schema_info, execution = get_orch_client(
|
226
|
+
schema, self.orch_type, execution
|
227
|
+
)
|
228
|
+
# endregion
|
179
229
|
|
180
|
-
#
|
230
|
+
# region VALIDACIONES
|
231
|
+
# We check if the job/dag exists
|
232
|
+
orch_client.get_orch_info(schema)
|
233
|
+
# Validate config before running the run
|
181
234
|
config_schema = DeployedOrch.get_one_schema(config, schema, CONFIG_SCHEMA)
|
182
235
|
new_config, config_errors = json_schema_extend_and_validate_as_string(
|
183
236
|
config_schema, kwargs["config"]
|
@@ -228,29 +281,35 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
228
281
|
)
|
229
282
|
execution.update_log_txt(f"{solution_errors}")
|
230
283
|
raise InvalidData(payload=dict(jsonschema_errors=solution_errors))
|
231
|
-
|
232
|
-
|
233
|
-
if
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
284
|
+
# endregion
|
285
|
+
# TODO GG: Duda, de los estados definidos en const.py, hay alguno de databricks que cuadre aquí?
|
286
|
+
if self.orch_type == AIRFLOW_BACKEND:
|
287
|
+
info = schema_info.json()
|
288
|
+
if info["is_paused"]:
|
289
|
+
err = "The dag exists but it is paused in airflow"
|
290
|
+
current_app.logger.error(err)
|
291
|
+
execution.update_state(EXEC_STATE_ERROR_START)
|
292
|
+
raise self.orch_error(
|
293
|
+
error=err,
|
294
|
+
payload=dict(
|
295
|
+
message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
|
296
|
+
state=EXEC_STATE_ERROR_START,
|
297
|
+
),
|
298
|
+
log_txt=f"Error while user {self.get_user()} tries to create an execution. "
|
299
|
+
+ err,
|
300
|
+
)
|
301
|
+
# TODO AGA: revisar si hay que hacer alguna verificación a los JOBS
|
246
302
|
|
247
303
|
try:
|
248
|
-
|
249
|
-
|
250
|
-
|
304
|
+
# TODO AGA: Hay que genestionar la posible eliminación de execution.id como
|
305
|
+
# parámetro, ya que no se puede seleccionar el id en databricks
|
306
|
+
# revisar las consecuencias que puede tener
|
307
|
+
response = self.orch_client.run_workflow(execution.id, orch_name=schema)
|
308
|
+
except self.orch_error as err:
|
309
|
+
error = self.orch_const["name"] + " responded with an error: {}".format(err)
|
251
310
|
current_app.logger.error(error)
|
252
311
|
execution.update_state(EXEC_STATE_ERROR)
|
253
|
-
raise
|
312
|
+
raise self.orch_error(
|
254
313
|
error=error,
|
255
314
|
payload=dict(
|
256
315
|
message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR],
|
@@ -260,9 +319,10 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
260
319
|
+ error,
|
261
320
|
)
|
262
321
|
|
263
|
-
# if we succeed, we register the
|
264
|
-
|
265
|
-
|
322
|
+
# if we succeed, we register the run_id in the execution table:
|
323
|
+
orch_data = response.json()
|
324
|
+
print("orch data is ", orch_data)
|
325
|
+
execution.run_id = orch_data[self.orch_const["run_id"]]
|
266
326
|
execution.update_state(EXEC_STATE_QUEUED)
|
267
327
|
current_app.logger.info(
|
268
328
|
"User {} creates execution {}".format(self.get_user_id(), execution.id)
|
@@ -276,6 +336,50 @@ class ExecutionRelaunchEndpoint(BaseMetaResource):
|
|
276
336
|
self.model = ExecutionModel
|
277
337
|
self.data_model = ExecutionModel
|
278
338
|
self.foreign_data = {"instance_id": InstanceModel}
|
339
|
+
self._orch_type = None
|
340
|
+
self._orch_client = None
|
341
|
+
self._orch_error = None
|
342
|
+
self._orch_to_state_map = None
|
343
|
+
self._orch_const = None
|
344
|
+
|
345
|
+
def _init_orch(self):
|
346
|
+
if self._orch_type is None:
|
347
|
+
self._orch_type = current_app.config["CORNFLOW_BACKEND"]
|
348
|
+
if self._orch_type == AIRFLOW_BACKEND:
|
349
|
+
self._orch_client = Airflow.from_config(current_app.config)
|
350
|
+
self._orch_error = AirflowError
|
351
|
+
self._orch_to_state_map = AIRFLOW_TO_STATE_MAP
|
352
|
+
self._orch_const = config_orchestrator["airflow"]
|
353
|
+
elif self._orch_type == DATABRICKS_BACKEND:
|
354
|
+
self._orch_client = Databricks.from_config(current_app.config)
|
355
|
+
self._orch_error = DatabricksError
|
356
|
+
self._orch_to_state_map = DATABRICKS_TO_STATE_MAP
|
357
|
+
self._orch_const = config_orchestrator["databricks"]
|
358
|
+
|
359
|
+
@property
|
360
|
+
def orch_type(self):
|
361
|
+
self._init_orch()
|
362
|
+
return self._orch_type
|
363
|
+
|
364
|
+
@property
|
365
|
+
def orch_client(self):
|
366
|
+
self._init_orch()
|
367
|
+
return self._orch_client
|
368
|
+
|
369
|
+
@property
|
370
|
+
def orch_error(self):
|
371
|
+
self._init_orch()
|
372
|
+
return self._orch_error
|
373
|
+
|
374
|
+
@property
|
375
|
+
def orch_to_state_map(self):
|
376
|
+
self._init_orch()
|
377
|
+
return self._orch_to_state_map
|
378
|
+
|
379
|
+
@property
|
380
|
+
def orch_const(self):
|
381
|
+
self._init_orch()
|
382
|
+
return self._orch_const
|
279
383
|
|
280
384
|
@doc(description="Re-launch an execution", tags=["Executions"])
|
281
385
|
@authenticate(auth_class=Auth())
|
@@ -292,9 +396,8 @@ class ExecutionRelaunchEndpoint(BaseMetaResource):
|
|
292
396
|
:rtype: Tuple(dict, integer)
|
293
397
|
"""
|
294
398
|
config = current_app.config
|
295
|
-
|
296
399
|
if "schema" not in kwargs:
|
297
|
-
kwargs["schema"] = "
|
400
|
+
kwargs["schema"] = self.orch_const["def_schema"]
|
298
401
|
|
299
402
|
self.put_detail(
|
300
403
|
data=dict(config=kwargs["config"]), user=self.get_user(), idx=idx
|
@@ -335,48 +438,34 @@ class ExecutionRelaunchEndpoint(BaseMetaResource):
|
|
335
438
|
log_txt=f"Error while user {self.get_user()} tries to relaunch execution {idx}. "
|
336
439
|
f"Configuration data does not match the jsonschema.",
|
337
440
|
)
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
if not af_client.is_alive():
|
342
|
-
err = "Airflow is not accessible"
|
343
|
-
current_app.logger.error(err)
|
344
|
-
execution.update_state(EXEC_STATE_ERROR_START)
|
345
|
-
raise AirflowError(
|
346
|
-
error=err,
|
347
|
-
payload=dict(
|
348
|
-
message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
|
349
|
-
state=EXEC_STATE_ERROR_START,
|
350
|
-
),
|
351
|
-
log_txt=f"Error while user {self.get_user()} tries to relaunch execution {idx}. "
|
352
|
-
+ err,
|
353
|
-
)
|
354
|
-
# ask airflow if dag_name exists
|
441
|
+
orch_client, schema_info, execution = get_orch_client(
|
442
|
+
kwargs["schema"], self.orch_type, execution
|
443
|
+
)
|
355
444
|
schema = execution.schema
|
356
|
-
schema_info = af_client.get_orch_info(schema)
|
357
445
|
|
358
446
|
info = schema_info.json()
|
359
|
-
if
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
447
|
+
if self.orch_type == AIRFLOW_BACKEND:
|
448
|
+
if info["is_paused"]:
|
449
|
+
err = "The dag exists but it is paused in airflow"
|
450
|
+
current_app.logger.error(err)
|
451
|
+
execution.update_state(EXEC_STATE_ERROR_START)
|
452
|
+
raise self.orch_error(
|
453
|
+
error=err,
|
454
|
+
payload=dict(
|
455
|
+
message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
|
456
|
+
state=EXEC_STATE_ERROR_START,
|
457
|
+
),
|
458
|
+
log_txt=f"Error while user {self.get_user()} tries to relaunch execution {idx}. "
|
459
|
+
+ err,
|
460
|
+
)
|
461
|
+
# TODO GG: revisar si hay que hacer alguna comprobación del estilo a databricks
|
373
462
|
try:
|
374
|
-
response =
|
375
|
-
except
|
376
|
-
error = "
|
463
|
+
response = self.orch_client.run_workflow(execution.id, orch_name=schema)
|
464
|
+
except self.orch_error as err:
|
465
|
+
error = self.orch_const["name"] + " responded with an error: {}".format(err)
|
377
466
|
current_app.logger.error(error)
|
378
467
|
execution.update_state(EXEC_STATE_ERROR)
|
379
|
-
raise
|
468
|
+
raise self.orch_error(
|
380
469
|
error=error,
|
381
470
|
payload=dict(
|
382
471
|
message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR],
|
@@ -386,9 +475,9 @@ class ExecutionRelaunchEndpoint(BaseMetaResource):
|
|
386
475
|
+ error,
|
387
476
|
)
|
388
477
|
|
389
|
-
# if we succeed, we register the
|
390
|
-
|
391
|
-
execution.
|
478
|
+
# if we succeed, we register the run_id in the execution table:
|
479
|
+
orch_data = response.json()
|
480
|
+
execution.run_id = orch_data[self.orch_const["run_id"]]
|
392
481
|
execution.update_state(EXEC_STATE_QUEUED)
|
393
482
|
current_app.logger.info(
|
394
483
|
"User {} relaunches execution {}".format(self.get_user_id(), execution.id)
|
@@ -405,6 +494,50 @@ class ExecutionDetailsEndpointBase(BaseMetaResource):
|
|
405
494
|
super().__init__()
|
406
495
|
self.data_model = ExecutionModel
|
407
496
|
self.foreign_data = {"instance_id": InstanceModel}
|
497
|
+
self._orch_type = None
|
498
|
+
self._orch_client = None
|
499
|
+
self._orch_error = None
|
500
|
+
self._orch_to_state_map = None
|
501
|
+
self._orch_const = None
|
502
|
+
|
503
|
+
def _init_orch(self):
|
504
|
+
if self._orch_type is None:
|
505
|
+
self._orch_type = current_app.config["CORNFLOW_BACKEND"]
|
506
|
+
if self._orch_type == AIRFLOW_BACKEND:
|
507
|
+
self._orch_client = Airflow.from_config(current_app.config)
|
508
|
+
self._orch_error = AirflowError
|
509
|
+
self._orch_to_state_map = AIRFLOW_TO_STATE_MAP
|
510
|
+
self._orch_const = config_orchestrator["airflow"]
|
511
|
+
elif self._orch_type == DATABRICKS_BACKEND:
|
512
|
+
self._orch_client = Databricks.from_config(current_app.config)
|
513
|
+
self._orch_error = DatabricksError
|
514
|
+
self._orch_to_state_map = DATABRICKS_TO_STATE_MAP
|
515
|
+
self._orch_const = config_orchestrator["databricks"]
|
516
|
+
|
517
|
+
@property
|
518
|
+
def orch_type(self):
|
519
|
+
self._init_orch()
|
520
|
+
return self._orch_type
|
521
|
+
|
522
|
+
@property
|
523
|
+
def orch_client(self):
|
524
|
+
self._init_orch()
|
525
|
+
return self._orch_client
|
526
|
+
|
527
|
+
@property
|
528
|
+
def orch_error(self):
|
529
|
+
self._init_orch()
|
530
|
+
return self._orch_error
|
531
|
+
|
532
|
+
@property
|
533
|
+
def orch_to_state_map(self):
|
534
|
+
self._init_orch()
|
535
|
+
return self._orch_to_state_map
|
536
|
+
|
537
|
+
@property
|
538
|
+
def orch_const(self):
|
539
|
+
self._init_orch()
|
540
|
+
return self._orch_const
|
408
541
|
|
409
542
|
|
410
543
|
class ExecutionDetailsEndpoint(ExecutionDetailsEndpointBase):
|
@@ -482,23 +615,28 @@ class ExecutionDetailsEndpoint(ExecutionDetailsEndpointBase):
|
|
482
615
|
@authenticate(auth_class=Auth())
|
483
616
|
@Auth.dag_permission_required
|
484
617
|
def post(self, idx):
|
618
|
+
if self.orch_type != AIRFLOW_BACKEND:
|
619
|
+
return {
|
620
|
+
"message": f"This feature is not available for {self.orch_const['name']}"
|
621
|
+
}, 200
|
485
622
|
execution = ExecutionModel.get_one_object(user=self.get_user(), idx=idx)
|
486
623
|
if execution is None:
|
487
624
|
raise ObjectDoesNotExist(
|
488
625
|
log_txt=f"Error while user {self.get_user()} tries to stop execution {idx}. "
|
489
626
|
f"The execution does not exist."
|
490
627
|
)
|
491
|
-
|
492
|
-
if not
|
493
|
-
err = "
|
494
|
-
raise
|
628
|
+
|
629
|
+
if not self.orch_client.is_alive():
|
630
|
+
err = self.orch_const["name"] + " is not accessible"
|
631
|
+
raise self.orch_error(
|
495
632
|
error=err,
|
496
|
-
log_txt=f"Error while user {self.get_user()} tries to stop execution {idx}. "
|
497
|
-
+ err,
|
633
|
+
log_txt=f"Error while user {self.get_user()} tries to stop execution {idx}. {err}",
|
498
634
|
)
|
499
|
-
|
500
|
-
|
635
|
+
|
636
|
+
response = self.orch_client.set_dag_run_to_fail(
|
637
|
+
dag_name=execution.schema, run_id=execution.run_id
|
501
638
|
)
|
639
|
+
# We should check if the execution has been stopped
|
502
640
|
execution.update_state(EXEC_STATE_STOPPED)
|
503
641
|
current_app.logger.info(f"User {self.get_user()} stopped execution {idx}")
|
504
642
|
return {"message": "The execution has been stopped"}, 200
|
@@ -512,6 +650,53 @@ class ExecutionStatusEndpoint(BaseMetaResource):
|
|
512
650
|
def __init__(self):
|
513
651
|
super().__init__()
|
514
652
|
self.data_model = ExecutionModel
|
653
|
+
self._orch_type = None
|
654
|
+
self._orch_client = None
|
655
|
+
self._orch_error = None
|
656
|
+
self._orch_to_state_map = None
|
657
|
+
self._orch_const = None
|
658
|
+
|
659
|
+
@property
|
660
|
+
def orch_type(self):
|
661
|
+
if self._orch_type is None:
|
662
|
+
self._orch_type = current_app.config["CORNFLOW_BACKEND"]
|
663
|
+
return self._orch_type
|
664
|
+
|
665
|
+
@property
|
666
|
+
def orch_client(self):
|
667
|
+
if self._orch_client is None:
|
668
|
+
if self.orch_type == AIRFLOW_BACKEND:
|
669
|
+
self._orch_client = Airflow.from_config(current_app.config)
|
670
|
+
elif self.orch_type == DATABRICKS_BACKEND:
|
671
|
+
self._orch_client = Databricks.from_config(current_app.config)
|
672
|
+
return self._orch_client
|
673
|
+
|
674
|
+
@property
|
675
|
+
def orch_error(self):
|
676
|
+
if self._orch_error is None:
|
677
|
+
if self.orch_type == AIRFLOW_BACKEND:
|
678
|
+
self._orch_error = AirflowError
|
679
|
+
elif self.orch_type == DATABRICKS_BACKEND:
|
680
|
+
self._orch_error = DatabricksError
|
681
|
+
return self._orch_error
|
682
|
+
|
683
|
+
@property
|
684
|
+
def orch_to_state_map(self):
|
685
|
+
if self._orch_to_state_map is None:
|
686
|
+
if self.orch_type == AIRFLOW_BACKEND:
|
687
|
+
self._orch_to_state_map = AIRFLOW_TO_STATE_MAP
|
688
|
+
elif self.orch_type == DATABRICKS_BACKEND:
|
689
|
+
self._orch_to_state_map = DATABRICKS_TO_STATE_MAP
|
690
|
+
return self._orch_to_state_map
|
691
|
+
|
692
|
+
@property
|
693
|
+
def orch_const(self):
|
694
|
+
if self._orch_const is None:
|
695
|
+
if self.orch_type == AIRFLOW_BACKEND:
|
696
|
+
self._orch_const = config_orchestrator["airflow"]
|
697
|
+
elif self.orch_type == DATABRICKS_BACKEND:
|
698
|
+
self._orch_const = config_orchestrator["databricks"]
|
699
|
+
return self._orch_const
|
515
700
|
|
516
701
|
@doc(description="Get status of an execution", tags=["Executions"])
|
517
702
|
@authenticate(auth_class=Auth())
|
@@ -538,7 +723,7 @@ class ExecutionStatusEndpoint(BaseMetaResource):
|
|
538
723
|
EXEC_STATE_QUEUED,
|
539
724
|
EXEC_STATE_UNKNOWN,
|
540
725
|
]:
|
541
|
-
# we only care on asking
|
726
|
+
# we only care on asking orchestrator if the status is unknown, queued or running.
|
542
727
|
return execution, 200
|
543
728
|
|
544
729
|
def _raise_af_error(execution, error, state=EXEC_STATE_UNKNOWN, log_txt=None):
|
@@ -546,13 +731,13 @@ class ExecutionStatusEndpoint(BaseMetaResource):
|
|
546
731
|
log_txt = error
|
547
732
|
message = EXECUTION_STATE_MESSAGE_DICT[state]
|
548
733
|
execution.update_state(state)
|
549
|
-
raise
|
734
|
+
raise self.orch_error(
|
550
735
|
error=error, payload=dict(message=message, state=state), log_txt=log_txt
|
551
736
|
)
|
552
737
|
|
553
|
-
|
554
|
-
if not
|
555
|
-
# it's safe to say we will never get anything if we did not store the
|
738
|
+
run_id = execution.run_id
|
739
|
+
if not run_id:
|
740
|
+
# it's safe to say we will never get anything if we did not store the run_id
|
556
741
|
_raise_af_error(
|
557
742
|
execution,
|
558
743
|
state=EXEC_STATE_ERROR,
|
@@ -560,24 +745,18 @@ class ExecutionStatusEndpoint(BaseMetaResource):
|
|
560
745
|
log_txt=f"Error while user {self.get_user()} tries to get the status of execution {idx}. "
|
561
746
|
f"The execution has no associated dag run id.",
|
562
747
|
)
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
+ err,
|
572
|
-
)
|
573
|
-
|
748
|
+
schema = execution.schema
|
749
|
+
# We use it only to check if the orchestrator is alive
|
750
|
+
orch_client, schema_info, execution = get_orch_client(
|
751
|
+
schema,
|
752
|
+
self.orch_type,
|
753
|
+
execution,
|
754
|
+
message="tries to get the status of an execution",
|
755
|
+
)
|
574
756
|
try:
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
)
|
579
|
-
except AirflowError as err:
|
580
|
-
error = f"Airflow responded with an error: {err}"
|
757
|
+
state = self.orch_client.get_run_status(schema, run_id)
|
758
|
+
except self.orch_error as err:
|
759
|
+
error = self.orch_const["name"] + f" responded with an error: {err}"
|
581
760
|
_raise_af_error(
|
582
761
|
execution,
|
583
762
|
error,
|
@@ -585,8 +764,7 @@ class ExecutionStatusEndpoint(BaseMetaResource):
|
|
585
764
|
+ str(err),
|
586
765
|
)
|
587
766
|
|
588
|
-
|
589
|
-
state = AIRFLOW_TO_STATE_MAP.get(data["state"], EXEC_STATE_UNKNOWN)
|
767
|
+
state = map_run_state(state, self.orch_type)
|
590
768
|
execution.update_state(state)
|
591
769
|
current_app.logger.info(
|
592
770
|
f"User {self.get_user()} gets status of execution {idx}"
|
@@ -671,3 +849,80 @@ class ExecutionLogEndpoint(ExecutionDetailsEndpointBase):
|
|
671
849
|
"""
|
672
850
|
current_app.logger.info(f"User {self.get_user()} gets log of execution {idx}")
|
673
851
|
return self.get_detail(user=self.get_user(), idx=idx)
|
852
|
+
|
853
|
+
|
854
|
+
# region aux_functions
|
855
|
+
|
856
|
+
|
857
|
+
def get_orch_client(
|
858
|
+
schema, orch_type, execution, message="tries to create an execution"
|
859
|
+
):
|
860
|
+
"""
|
861
|
+
Get the orchestrator client and the schema info
|
862
|
+
"""
|
863
|
+
if orch_type == AIRFLOW_BACKEND:
|
864
|
+
return get_airflow(schema, execution=execution, message=message)
|
865
|
+
elif orch_type == DATABRICKS_BACKEND:
|
866
|
+
return get_databricks(schema, execution=execution, message=message)
|
867
|
+
else:
|
868
|
+
raise EndpointNotImplemented()
|
869
|
+
|
870
|
+
|
871
|
+
def get_airflow(schema, execution, message="tries to create an execution"):
|
872
|
+
"""
|
873
|
+
Get the Airflow client and the schema info
|
874
|
+
"""
|
875
|
+
af_client = Airflow.from_config(current_app.config)
|
876
|
+
schema_info = af_client.get_orch_info(schema)
|
877
|
+
if not af_client.is_alive():
|
878
|
+
err = "Airflow is not accessible"
|
879
|
+
current_app.logger.error(err)
|
880
|
+
execution.update_state(EXEC_STATE_ERROR_START)
|
881
|
+
raise AirflowError(
|
882
|
+
error=err,
|
883
|
+
payload=dict(
|
884
|
+
message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
|
885
|
+
state=EXEC_STATE_ERROR_START,
|
886
|
+
),
|
887
|
+
log_txt=f"Error while user {execution.user_id} {message} " + err,
|
888
|
+
)
|
889
|
+
# TODO AGA: revisar si tiene sentido que se devuelva execution o si
|
890
|
+
# es un puntero
|
891
|
+
return af_client, schema_info, execution
|
892
|
+
|
893
|
+
|
894
|
+
def get_databricks(schema, execution, message="tries to create an execution"):
|
895
|
+
"""
|
896
|
+
Get the Databricks client and the schema info
|
897
|
+
"""
|
898
|
+
db_client = Databricks.from_config(current_app.config)
|
899
|
+
schema_info = db_client.get_orch_info(schema)
|
900
|
+
if not db_client.is_alive():
|
901
|
+
err = "Databricks is not accessible"
|
902
|
+
current_app.logger.error(err)
|
903
|
+
execution.update_state(EXEC_STATE_ERROR_START)
|
904
|
+
raise DatabricksError(
|
905
|
+
error=err,
|
906
|
+
payload=dict(
|
907
|
+
message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
|
908
|
+
state=EXEC_STATE_ERROR_START,
|
909
|
+
),
|
910
|
+
log_txt=f"Error while user {execution.user_id} {message} " + err,
|
911
|
+
)
|
912
|
+
return db_client, schema_info, execution
|
913
|
+
# endregion
|
914
|
+
|
915
|
+
|
916
|
+
def map_run_state(state, orch_TYPE):
|
917
|
+
"""
|
918
|
+
Maps the state of the execution in the orchestrator to the state of the execution in cornflow
|
919
|
+
"""
|
920
|
+
if orch_TYPE == AIRFLOW_BACKEND:
|
921
|
+
state = state.json()["state"]
|
922
|
+
return AIRFLOW_TO_STATE_MAP.get(state, EXEC_STATE_UNKNOWN)
|
923
|
+
elif orch_TYPE == DATABRICKS_BACKEND:
|
924
|
+
preliminar_state = DATABRICKS_TO_STATE_MAP.get(state, EXEC_STATE_UNKNOWN)
|
925
|
+
# if preliminar_state =="TERMINATED":
|
926
|
+
# # TODO GG DUDA: Revisar si es correcto el error predeterminado
|
927
|
+
# return DATABRICKS_FINISH_TO_STATE_MAP.get(state,EXEC_STATE_ERROR)
|
928
|
+
return preliminar_state
|