cornflow 1.2.4__py3-none-any.whl → 1.3.0rc1__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.
- cornflow/cli/service.py +91 -42
- cornflow/commands/dag.py +7 -7
- cornflow/commands/permissions.py +9 -5
- cornflow/config.py +23 -3
- cornflow/endpoints/case.py +37 -21
- cornflow/endpoints/dag.py +5 -5
- cornflow/endpoints/data_check.py +8 -7
- cornflow/endpoints/example_data.py +4 -2
- cornflow/endpoints/execution.py +215 -127
- cornflow/endpoints/health.py +30 -11
- cornflow/endpoints/instance.py +3 -3
- cornflow/endpoints/login.py +9 -2
- cornflow/endpoints/schemas.py +3 -3
- cornflow/migrations/versions/999b98e24225.py +34 -0
- cornflow/migrations/versions/cef1df240b27_.py +34 -0
- cornflow/models/__init__.py +2 -1
- cornflow/models/dag.py +8 -9
- cornflow/models/dag_permissions.py +3 -3
- cornflow/models/execution.py +2 -3
- cornflow/models/permissions.py +1 -0
- cornflow/models/user.py +1 -1
- cornflow/schemas/execution.py +14 -1
- cornflow/schemas/health.py +1 -1
- cornflow/shared/authentication/auth.py +14 -1
- cornflow/shared/authentication/decorators.py +0 -1
- cornflow/shared/const.py +44 -1
- cornflow/shared/exceptions.py +2 -1
- cornflow/tests/base_test_execution.py +798 -0
- cornflow/tests/const.py +1 -0
- cornflow/tests/integration/test_commands.py +2 -2
- cornflow/tests/integration/test_cornflowclient.py +2 -1
- cornflow/tests/unit/test_cases.py +1 -1
- cornflow/tests/unit/test_commands.py +5 -5
- cornflow/tests/unit/test_dags.py +3 -3
- cornflow/tests/unit/test_example_data.py +1 -1
- cornflow/tests/unit/test_executions.py +115 -535
- cornflow/tests/unit/test_health.py +84 -3
- cornflow/tests/unit/test_main_alarms.py +1 -1
- cornflow/tests/unit/test_roles.py +2 -1
- cornflow/tests/unit/test_schema_from_models.py +1 -1
- cornflow/tests/unit/test_schemas.py +1 -1
- cornflow/tests/unit/tools.py +93 -10
- {cornflow-1.2.4.dist-info → cornflow-1.3.0rc1.dist-info}/METADATA +2 -2
- {cornflow-1.2.4.dist-info → cornflow-1.3.0rc1.dist-info}/RECORD +47 -44
- {cornflow-1.2.4.dist-info → cornflow-1.3.0rc1.dist-info}/WHEEL +0 -0
- {cornflow-1.2.4.dist-info → cornflow-1.3.0rc1.dist-info}/entry_points.txt +0 -0
- {cornflow-1.2.4.dist-info → cornflow-1.3.0rc1.dist-info}/top_level.txt +0 -0
@@ -26,6 +26,8 @@ class ExampleDataListEndpoint(BaseMetaResource):
|
|
26
26
|
Endpoint used to obtain schemas for one app
|
27
27
|
"""
|
28
28
|
|
29
|
+
# TODO: DATABRICKS NOT IMPLEMENTED YET
|
30
|
+
|
29
31
|
ROLES_WITH_ACCESS = [VIEWER_ROLE, PLANNER_ROLE, ADMIN_ROLE]
|
30
32
|
|
31
33
|
@doc(description="Get lsit of example data from DAG", tags=["DAG"])
|
@@ -53,7 +55,7 @@ class ExampleDataListEndpoint(BaseMetaResource):
|
|
53
55
|
raise AirflowError(error=f"{AIRFLOW_NOT_REACHABLE_MSG}")
|
54
56
|
|
55
57
|
# try airflow and see if dag_name exists
|
56
|
-
af_client.
|
58
|
+
af_client.get_workflow_info(workflow_name=dag_name)
|
57
59
|
|
58
60
|
current_app.logger.info("User gets example data from {}".format(dag_name))
|
59
61
|
|
@@ -96,7 +98,7 @@ class ExampleDataDetailEndpoint(BaseMetaResource):
|
|
96
98
|
raise AirflowError(error=f"{AIRFLOW_NOT_REACHABLE_MSG}")
|
97
99
|
|
98
100
|
# try airflow and see if dag_name exists
|
99
|
-
af_client.
|
101
|
+
af_client.get_workflow_info(workflow_name=dag_name)
|
100
102
|
|
101
103
|
current_app.logger.info("User gets example data from {}".format(dag_name))
|
102
104
|
|
cornflow/endpoints/execution.py
CHANGED
@@ -6,13 +6,16 @@ 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
|
+
|
10
|
+
from cornflow_client.databricks.api import Databricks
|
9
11
|
from cornflow_client.constants import INSTANCE_SCHEMA, CONFIG_SCHEMA, SOLUTION_SCHEMA
|
10
12
|
from flask import request, current_app
|
11
13
|
from flask_apispec import marshal_with, use_kwargs, doc
|
12
14
|
|
13
15
|
# Import from internal modules
|
14
16
|
from cornflow.endpoints.meta_resource import BaseMetaResource
|
15
|
-
from cornflow.models import InstanceModel,
|
17
|
+
from cornflow.models import InstanceModel, DeployedWorkflow, ExecutionModel
|
18
|
+
from cornflow.shared.const import config_orchestrator
|
16
19
|
from cornflow.schemas.execution import (
|
17
20
|
ExecutionDetailsEndpointResponse,
|
18
21
|
ExecutionDetailsEndpointWithIndicatorsResponse,
|
@@ -28,6 +31,10 @@ from cornflow.schemas.execution import (
|
|
28
31
|
)
|
29
32
|
from cornflow.shared.authentication import Auth, authenticate
|
30
33
|
from cornflow.shared.compress import compressed
|
34
|
+
from cornflow.shared.const import (
|
35
|
+
AIRFLOW_BACKEND,
|
36
|
+
DATABRICKS_BACKEND,
|
37
|
+
)
|
31
38
|
from cornflow.shared.const import (
|
32
39
|
AIRFLOW_ERROR_MSG,
|
33
40
|
AIRFLOW_NOT_REACHABLE_MSG,
|
@@ -39,24 +46,85 @@ from cornflow.shared.const import (
|
|
39
46
|
EXEC_STATE_UNKNOWN,
|
40
47
|
EXECUTION_STATE_MESSAGE_DICT,
|
41
48
|
AIRFLOW_TO_STATE_MAP,
|
49
|
+
DATABRICKS_TO_STATE_MAP,
|
42
50
|
EXEC_STATE_STOPPED,
|
43
51
|
EXEC_STATE_QUEUED,
|
44
52
|
)
|
45
|
-
|
53
|
+
|
54
|
+
from cornflow.shared.exceptions import (
|
55
|
+
AirflowError,
|
56
|
+
DatabricksError,
|
57
|
+
ObjectDoesNotExist,
|
58
|
+
InvalidData,
|
59
|
+
EndpointNotImplemented,
|
60
|
+
)
|
46
61
|
from cornflow.shared.validators import (
|
47
62
|
json_schema_validate_as_string,
|
48
63
|
json_schema_extend_and_validate_as_string,
|
49
64
|
)
|
50
65
|
|
51
66
|
|
52
|
-
class
|
67
|
+
class OrchestratorMixin(BaseMetaResource):
|
68
|
+
"""
|
69
|
+
Base class that provides orchestrator-related functionality for execution endpoints.
|
70
|
+
This mixin handles the initialization and properties for orchestrator clients (Airflow/Databricks).
|
71
|
+
"""
|
72
|
+
|
73
|
+
def __init__(self):
|
74
|
+
super().__init__()
|
75
|
+
self._orch_type = None
|
76
|
+
self._orch_client = None
|
77
|
+
self._orch_error = None
|
78
|
+
self._orch_to_state_map = None
|
79
|
+
self._orch_const = None
|
80
|
+
|
81
|
+
def _init_orch(self):
|
82
|
+
if self._orch_type is None:
|
83
|
+
self._orch_type = current_app.config["CORNFLOW_BACKEND"]
|
84
|
+
if self._orch_type == AIRFLOW_BACKEND:
|
85
|
+
self._orch_client = Airflow.from_config(current_app.config)
|
86
|
+
self._orch_error = AirflowError
|
87
|
+
self._orch_to_state_map = AIRFLOW_TO_STATE_MAP
|
88
|
+
self._orch_const = config_orchestrator["airflow"]
|
89
|
+
elif self._orch_type == DATABRICKS_BACKEND:
|
90
|
+
self._orch_client = Databricks.from_config(current_app.config)
|
91
|
+
self._orch_error = DatabricksError
|
92
|
+
self._orch_to_state_map = DATABRICKS_TO_STATE_MAP
|
93
|
+
self._orch_const = config_orchestrator["databricks"]
|
94
|
+
|
95
|
+
@property
|
96
|
+
def orch_type(self):
|
97
|
+
self._init_orch()
|
98
|
+
return self._orch_type
|
99
|
+
|
100
|
+
@property
|
101
|
+
def orch_client(self):
|
102
|
+
self._init_orch()
|
103
|
+
return self._orch_client
|
104
|
+
|
105
|
+
@property
|
106
|
+
def orch_error(self):
|
107
|
+
self._init_orch()
|
108
|
+
return self._orch_error
|
109
|
+
|
110
|
+
@property
|
111
|
+
def orch_to_state_map(self):
|
112
|
+
self._init_orch()
|
113
|
+
return self._orch_to_state_map
|
114
|
+
|
115
|
+
@property
|
116
|
+
def orch_const(self):
|
117
|
+
self._init_orch()
|
118
|
+
return self._orch_const
|
119
|
+
|
120
|
+
|
121
|
+
class ExecutionEndpoint(OrchestratorMixin):
|
53
122
|
"""
|
54
123
|
Endpoint used to create a new execution or get all the executions and their information back
|
55
124
|
"""
|
56
125
|
|
57
126
|
def __init__(self):
|
58
127
|
super().__init__()
|
59
|
-
self.model = ExecutionModel
|
60
128
|
self.data_model = ExecutionModel
|
61
129
|
self.foreign_data = {"instance_id": InstanceModel}
|
62
130
|
|
@@ -91,36 +159,37 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
91
159
|
]
|
92
160
|
|
93
161
|
for execution in running_executions:
|
94
|
-
|
95
|
-
|
96
|
-
|
162
|
+
run_id = execution.run_id
|
163
|
+
|
164
|
+
if not run_id:
|
165
|
+
# it's safe to say we will never get anything if we did not store the run_id
|
97
166
|
current_app.logger.warning(
|
98
167
|
"Error while the app tried to update the status of all running executions."
|
99
168
|
f"Execution {execution.id} has status {execution.state} but has no dag run associated."
|
100
169
|
)
|
101
170
|
continue
|
102
171
|
|
103
|
-
|
104
|
-
if not af_client.is_alive():
|
172
|
+
if not self.orch_client.is_alive(config=current_app.config):
|
105
173
|
current_app.logger.warning(
|
106
174
|
f"Error while the app tried to update the status of all running executions."
|
107
175
|
f"{AIRFLOW_NOT_REACHABLE_MSG}"
|
108
176
|
)
|
109
177
|
continue
|
110
|
-
|
111
178
|
try:
|
112
|
-
response =
|
113
|
-
|
179
|
+
response = self.orch_client.get_run_status(
|
180
|
+
schema=execution.schema, run_id=run_id
|
114
181
|
)
|
115
|
-
except
|
182
|
+
except self.orch_error as err:
|
116
183
|
current_app.logger.warning(
|
117
184
|
"Error while the app tried to update the status of all running executions."
|
118
185
|
f"{AIRFLOW_ERROR_MSG} {err}"
|
119
186
|
)
|
120
187
|
continue
|
121
|
-
|
122
|
-
|
123
|
-
|
188
|
+
if self.orch_type == DATABRICKS_BACKEND:
|
189
|
+
state = self.orch_to_state_map.get(response, EXEC_STATE_UNKNOWN)
|
190
|
+
else:
|
191
|
+
data = response.json()
|
192
|
+
state = self.orch_to_state_map.get(data["state"], EXEC_STATE_UNKNOWN)
|
124
193
|
execution.update_state(state)
|
125
194
|
|
126
195
|
return executions
|
@@ -141,12 +210,11 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
141
210
|
:rtype: Tuple(dict, integer)
|
142
211
|
"""
|
143
212
|
|
144
|
-
config = current_app.config
|
145
|
-
|
146
213
|
if "schema" not in kwargs:
|
147
|
-
kwargs["schema"] = "
|
148
|
-
|
149
|
-
|
214
|
+
kwargs["schema"] = self.orch_const["def_schema"]
|
215
|
+
# region INDEPENDIENTE A AIRFLOW
|
216
|
+
config = current_app.config
|
217
|
+
execution, status_code = self.post_list(data=kwargs)
|
150
218
|
instance = InstanceModel.get_one_object(
|
151
219
|
user=self.get_user(), idx=execution.instance_id
|
152
220
|
)
|
@@ -156,7 +224,7 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
156
224
|
raise InvalidData(error="Instance and execution schema mismatch")
|
157
225
|
|
158
226
|
current_app.logger.debug(f"The request is: {request.args.get('run')}")
|
159
|
-
# this allows testing without
|
227
|
+
# this allows testing without orchestrator interaction:
|
160
228
|
if request.args.get("run", "1") == "0":
|
161
229
|
current_app.logger.info(
|
162
230
|
f"User {self.get_user_id()} creates execution {execution.id} but does not run it."
|
@@ -164,27 +232,30 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
164
232
|
execution.update_state(EXEC_STATE_NOT_RUN)
|
165
233
|
return execution, 201
|
166
234
|
|
167
|
-
# We now try to launch the task in
|
168
|
-
|
169
|
-
|
235
|
+
# We now try to launch the task in the orchestrator
|
236
|
+
# Note schema is a string with the name of the job/dag
|
237
|
+
schema = execution.schema
|
238
|
+
# endregion
|
170
239
|
|
171
|
-
|
240
|
+
# region VALIDACIONES
|
241
|
+
# We check if the job/dag exists and orchestrator is alive
|
242
|
+
if not self.orch_client.is_alive(config=current_app.config):
|
243
|
+
error = f"{self.orch_const['name']} is not accessible"
|
244
|
+
current_app.logger.error(error)
|
172
245
|
execution.update_state(EXEC_STATE_ERROR_START)
|
173
|
-
raise
|
174
|
-
error=
|
246
|
+
raise self.orch_error(
|
247
|
+
error=error,
|
175
248
|
payload=dict(
|
176
249
|
message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
|
177
250
|
state=EXEC_STATE_ERROR_START,
|
178
251
|
),
|
179
|
-
log_txt=f"Error while user {self.get_user()} tries to create an execution "
|
180
|
-
+
|
252
|
+
log_txt=f"Error while user {self.get_user()} tries to create an execution. "
|
253
|
+
+ error,
|
181
254
|
)
|
182
|
-
# ask airflow if dag_name exists
|
183
|
-
schema = execution.schema
|
184
|
-
schema_info = af_client.get_dag_info(schema)
|
185
255
|
|
186
|
-
|
187
|
-
|
256
|
+
schema_info = self.orch_client.get_workflow_info(workflow_name=schema)
|
257
|
+
# Validate config before running the run
|
258
|
+
config_schema = DeployedWorkflow.get_one_schema(config, schema, CONFIG_SCHEMA)
|
188
259
|
new_config, config_errors = json_schema_extend_and_validate_as_string(
|
189
260
|
config_schema, kwargs["config"]
|
190
261
|
)
|
@@ -204,7 +275,9 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
204
275
|
execution.update_config(new_config)
|
205
276
|
|
206
277
|
# Validate instance data before running the dag
|
207
|
-
instance_schema =
|
278
|
+
instance_schema = DeployedWorkflow.get_one_schema(
|
279
|
+
config, schema, INSTANCE_SCHEMA
|
280
|
+
)
|
208
281
|
instance_errors = json_schema_validate_as_string(instance_schema, instance.data)
|
209
282
|
if instance_errors:
|
210
283
|
execution.update_state(
|
@@ -220,7 +293,7 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
220
293
|
)
|
221
294
|
# Validate solution data before running the dag (if it exists)
|
222
295
|
if kwargs.get("data") is not None:
|
223
|
-
solution_schema =
|
296
|
+
solution_schema = DeployedWorkflow.get_one_schema(
|
224
297
|
config, schema, SOLUTION_SCHEMA
|
225
298
|
)
|
226
299
|
solution_errors = json_schema_validate_as_string(
|
@@ -234,29 +307,30 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
234
307
|
)
|
235
308
|
execution.update_log_txt(f"{solution_errors}")
|
236
309
|
raise InvalidData(payload=dict(jsonschema_errors=solution_errors))
|
237
|
-
|
238
|
-
|
239
|
-
if
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
310
|
+
# endregion
|
311
|
+
# TODO: Consider adding similar checks for databricks
|
312
|
+
if self.orch_type == AIRFLOW_BACKEND:
|
313
|
+
info = schema_info.json()
|
314
|
+
if info["is_paused"]:
|
315
|
+
current_app.logger.error(DAG_PAUSED_MSG)
|
316
|
+
execution.update_state(EXEC_STATE_ERROR_START)
|
317
|
+
raise self.orch_error(
|
318
|
+
error=DAG_PAUSED_MSG,
|
319
|
+
payload=dict(
|
320
|
+
message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
|
321
|
+
state=EXEC_STATE_ERROR_START,
|
322
|
+
),
|
323
|
+
log_txt=f"Error while user {self.get_user()} tries to create an execution. "
|
324
|
+
+ DAG_PAUSED_MSG,
|
325
|
+
)
|
252
326
|
|
253
327
|
try:
|
254
|
-
response =
|
255
|
-
except
|
256
|
-
error =
|
328
|
+
response = self.orch_client.run_workflow(execution.id, workflow_name=schema)
|
329
|
+
except self.orch_error as err:
|
330
|
+
error = self.orch_const["name"] + " responded with an error: {}".format(err)
|
257
331
|
current_app.logger.error(error)
|
258
332
|
execution.update_state(EXEC_STATE_ERROR)
|
259
|
-
raise
|
333
|
+
raise self.orch_error(
|
260
334
|
error=error,
|
261
335
|
payload=dict(
|
262
336
|
message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR],
|
@@ -266,9 +340,11 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
266
340
|
+ error,
|
267
341
|
)
|
268
342
|
|
269
|
-
# if we succeed, we register the
|
270
|
-
|
271
|
-
|
343
|
+
# if we succeed, we register the run_id in the execution table:
|
344
|
+
orch_data = response.json()
|
345
|
+
info = "orch data is " + str(orch_data)
|
346
|
+
current_app.logger.info(info)
|
347
|
+
execution.run_id = orch_data[self.orch_const["run_id"]]
|
272
348
|
execution.update_state(EXEC_STATE_QUEUED)
|
273
349
|
current_app.logger.info(
|
274
350
|
"User {} creates execution {}".format(self.get_user_id(), execution.id)
|
@@ -276,7 +352,7 @@ class ExecutionEndpoint(BaseMetaResource):
|
|
276
352
|
return execution, 201
|
277
353
|
|
278
354
|
|
279
|
-
class ExecutionRelaunchEndpoint(
|
355
|
+
class ExecutionRelaunchEndpoint(OrchestratorMixin):
|
280
356
|
def __init__(self):
|
281
357
|
super().__init__()
|
282
358
|
self.model = ExecutionModel
|
@@ -298,9 +374,8 @@ class ExecutionRelaunchEndpoint(BaseMetaResource):
|
|
298
374
|
:rtype: Tuple(dict, integer)
|
299
375
|
"""
|
300
376
|
config = current_app.config
|
301
|
-
|
302
377
|
if "schema" not in kwargs:
|
303
|
-
kwargs["schema"] = "
|
378
|
+
kwargs["schema"] = self.orch_const["def_schema"]
|
304
379
|
|
305
380
|
self.put_detail(
|
306
381
|
data=dict(config=kwargs["config"]), user=self.get_user(), idx=idx
|
@@ -331,7 +406,7 @@ class ExecutionRelaunchEndpoint(BaseMetaResource):
|
|
331
406
|
}, 201
|
332
407
|
|
333
408
|
# Validate config before running the dag
|
334
|
-
config_schema =
|
409
|
+
config_schema = DeployedWorkflow.get_one_schema(
|
335
410
|
config, kwargs["schema"], CONFIG_SCHEMA
|
336
411
|
)
|
337
412
|
config_errors = json_schema_validate_as_string(config_schema, kwargs["config"])
|
@@ -341,48 +416,46 @@ class ExecutionRelaunchEndpoint(BaseMetaResource):
|
|
341
416
|
log_txt=f"Error while user {self.get_user()} tries to relaunch execution {idx}. "
|
342
417
|
f"Configuration data does not match the jsonschema.",
|
343
418
|
)
|
344
|
-
|
345
|
-
# We now try to launch the task in airflow
|
346
|
-
af_client = Airflow.from_config(config)
|
347
|
-
if not af_client.is_alive():
|
348
|
-
|
349
|
-
current_app.logger.error(AIRFLOW_NOT_REACHABLE_MSG)
|
350
|
-
execution.update_state(EXEC_STATE_ERROR_START)
|
351
|
-
raise AirflowError(
|
352
|
-
error=AIRFLOW_NOT_REACHABLE_MSG,
|
353
|
-
payload=dict(
|
354
|
-
message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
|
355
|
-
state=EXEC_STATE_ERROR_START,
|
356
|
-
),
|
357
|
-
log_txt=f"Error while user {self.get_user()} tries to relaunch execution {idx}. "
|
358
|
-
+ AIRFLOW_NOT_REACHABLE_MSG,
|
359
|
-
)
|
360
|
-
# ask airflow if dag_name exists
|
361
419
|
schema = execution.schema
|
362
|
-
schema_info = af_client.get_dag_info(schema)
|
363
|
-
|
364
|
-
info = schema_info.json()
|
365
|
-
if info["is_paused"]:
|
366
420
|
|
367
|
-
|
421
|
+
# Check if orchestrator is alive
|
422
|
+
if not self.orch_client.is_alive(config=current_app.config):
|
423
|
+
error = f"{self.orch_const['name']} is not accessible"
|
424
|
+
current_app.logger.error(error)
|
368
425
|
execution.update_state(EXEC_STATE_ERROR_START)
|
369
|
-
raise
|
370
|
-
error=
|
426
|
+
raise self.orch_error(
|
427
|
+
error=error,
|
371
428
|
payload=dict(
|
372
429
|
message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
|
373
430
|
state=EXEC_STATE_ERROR_START,
|
374
431
|
),
|
375
432
|
log_txt=f"Error while user {self.get_user()} tries to relaunch execution {idx}. "
|
376
|
-
+
|
433
|
+
+ error,
|
377
434
|
)
|
378
435
|
|
436
|
+
schema_info = self.orch_client.get_workflow_info(workflow_name=schema)
|
437
|
+
info = schema_info.json()
|
438
|
+
if self.orch_type == AIRFLOW_BACKEND:
|
439
|
+
if info["is_paused"]:
|
440
|
+
current_app.logger.error(AIRFLOW_NOT_REACHABLE_MSG)
|
441
|
+
execution.update_state(EXEC_STATE_ERROR_START)
|
442
|
+
raise self.orch_error(
|
443
|
+
error=AIRFLOW_NOT_REACHABLE_MSG,
|
444
|
+
payload=dict(
|
445
|
+
message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
|
446
|
+
state=EXEC_STATE_ERROR_START,
|
447
|
+
),
|
448
|
+
log_txt=f"Error while user {self.get_user()} tries to relaunch execution {idx}. "
|
449
|
+
+ AIRFLOW_NOT_REACHABLE_MSG,
|
450
|
+
)
|
451
|
+
# TODO: Consider adding similar checks for databricks
|
379
452
|
try:
|
380
|
-
response =
|
381
|
-
except
|
382
|
-
error =
|
453
|
+
response = self.orch_client.run_workflow(execution.id, workflow_name=schema)
|
454
|
+
except self.orch_error as err:
|
455
|
+
error = self.orch_const["name"] + " responded with an error: {}".format(err)
|
383
456
|
current_app.logger.error(error)
|
384
457
|
execution.update_state(EXEC_STATE_ERROR)
|
385
|
-
raise
|
458
|
+
raise self.orch_error(
|
386
459
|
error=error,
|
387
460
|
payload=dict(
|
388
461
|
message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR],
|
@@ -392,9 +465,9 @@ class ExecutionRelaunchEndpoint(BaseMetaResource):
|
|
392
465
|
+ error,
|
393
466
|
)
|
394
467
|
|
395
|
-
# if we succeed, we register the
|
396
|
-
|
397
|
-
execution.
|
468
|
+
# if we succeed, we register the run_id in the execution table:
|
469
|
+
orch_data = response.json()
|
470
|
+
execution.run_id = orch_data[self.orch_const["run_id"]]
|
398
471
|
execution.update_state(EXEC_STATE_QUEUED)
|
399
472
|
current_app.logger.info(
|
400
473
|
"User {} relaunches execution {}".format(self.get_user_id(), execution.id)
|
@@ -402,7 +475,7 @@ class ExecutionRelaunchEndpoint(BaseMetaResource):
|
|
402
475
|
return {"message": "The execution was relaunched correctly"}, 201
|
403
476
|
|
404
477
|
|
405
|
-
class ExecutionDetailsEndpointBase(
|
478
|
+
class ExecutionDetailsEndpointBase(OrchestratorMixin):
|
406
479
|
"""
|
407
480
|
Endpoint used to get the information of a certain execution. But not the data!
|
408
481
|
"""
|
@@ -451,7 +524,7 @@ class ExecutionDetailsEndpoint(ExecutionDetailsEndpointBase):
|
|
451
524
|
schema = ExecutionModel.get_one_object(user=self.get_user(), idx=idx).schema
|
452
525
|
|
453
526
|
if data.get("data") is not None and schema is not None:
|
454
|
-
data_jsonschema =
|
527
|
+
data_jsonschema = DeployedWorkflow.get_one_schema(
|
455
528
|
config, schema, SOLUTION_SCHEMA
|
456
529
|
)
|
457
530
|
validation_errors = json_schema_validate_as_string(
|
@@ -488,29 +561,33 @@ class ExecutionDetailsEndpoint(ExecutionDetailsEndpointBase):
|
|
488
561
|
@authenticate(auth_class=Auth())
|
489
562
|
@Auth.dag_permission_required
|
490
563
|
def post(self, idx):
|
564
|
+
if self.orch_type != AIRFLOW_BACKEND:
|
565
|
+
return {
|
566
|
+
"message": f"This feature is not available for {self.orch_const['name']}"
|
567
|
+
}, 501
|
491
568
|
execution = ExecutionModel.get_one_object(user=self.get_user(), idx=idx)
|
492
569
|
if execution is None:
|
493
570
|
raise ObjectDoesNotExist(
|
494
571
|
log_txt=f"Error while user {self.get_user()} tries to stop execution {idx}. "
|
495
572
|
f"The execution does not exist."
|
496
573
|
)
|
497
|
-
af_client = Airflow.from_config(current_app.config)
|
498
|
-
if not af_client.is_alive():
|
499
574
|
|
500
|
-
|
575
|
+
if not self.orch_client.is_alive(config=current_app.config):
|
576
|
+
raise self.orch_error(
|
501
577
|
error=AIRFLOW_NOT_REACHABLE_MSG,
|
502
|
-
log_txt=f"Error while user {self.get_user()} tries to stop execution {idx}. "
|
503
|
-
+ AIRFLOW_NOT_REACHABLE_MSG,
|
578
|
+
log_txt=f"Error while user {self.get_user()} tries to stop execution {idx}. {AIRFLOW_NOT_REACHABLE_MSG}",
|
504
579
|
)
|
505
|
-
|
506
|
-
|
580
|
+
|
581
|
+
self.orch_client.set_dag_run_to_fail(
|
582
|
+
dag_name=execution.schema, run_id=execution.run_id
|
507
583
|
)
|
584
|
+
# We should check if the execution has been stopped
|
508
585
|
execution.update_state(EXEC_STATE_STOPPED)
|
509
586
|
current_app.logger.info(f"User {self.get_user()} stopped execution {idx}")
|
510
587
|
return {"message": "The execution has been stopped"}, 200
|
511
588
|
|
512
589
|
|
513
|
-
class ExecutionStatusEndpoint(
|
590
|
+
class ExecutionStatusEndpoint(OrchestratorMixin):
|
514
591
|
"""
|
515
592
|
Endpoint used to get the status of a certain execution that is running in the airflow webserver
|
516
593
|
"""
|
@@ -544,7 +621,7 @@ class ExecutionStatusEndpoint(BaseMetaResource):
|
|
544
621
|
EXEC_STATE_QUEUED,
|
545
622
|
EXEC_STATE_UNKNOWN,
|
546
623
|
]:
|
547
|
-
# we only care on asking
|
624
|
+
# we only care on asking orchestrator if the status is unknown, queued or running.
|
548
625
|
return execution, 200
|
549
626
|
|
550
627
|
def _raise_af_error(execution, error, state=EXEC_STATE_UNKNOWN, log_txt=None):
|
@@ -552,38 +629,35 @@ class ExecutionStatusEndpoint(BaseMetaResource):
|
|
552
629
|
log_txt = error
|
553
630
|
message = EXECUTION_STATE_MESSAGE_DICT[state]
|
554
631
|
execution.update_state(state)
|
555
|
-
raise
|
632
|
+
raise self.orch_error(
|
556
633
|
error=error, payload=dict(message=message, state=state), log_txt=log_txt
|
557
634
|
)
|
558
635
|
|
559
|
-
|
560
|
-
if not
|
561
|
-
# it's safe to say we will never get anything if we did not store the
|
636
|
+
run_id = execution.run_id
|
637
|
+
if not run_id:
|
638
|
+
# it's safe to say we will never get anything if we did not store the run_id
|
562
639
|
_raise_af_error(
|
563
640
|
execution,
|
564
641
|
state=EXEC_STATE_ERROR,
|
565
|
-
error="The execution has no
|
642
|
+
error="The execution has no run_id associated",
|
566
643
|
log_txt=f"Error while user {self.get_user()} tries to get the status of execution {idx}. "
|
567
|
-
f"The execution has no associated
|
644
|
+
f"The execution has no associated run id.",
|
568
645
|
)
|
569
|
-
|
570
|
-
|
571
|
-
if not
|
572
|
-
|
646
|
+
schema = execution.schema
|
647
|
+
# We check if the orchestrator is alive
|
648
|
+
if not self.orch_client.is_alive(config=current_app.config):
|
649
|
+
error = f"{self.orch_const['name']} is not accessible"
|
573
650
|
_raise_af_error(
|
574
651
|
execution,
|
575
|
-
|
652
|
+
error,
|
653
|
+
state=EXEC_STATE_ERROR_START,
|
576
654
|
log_txt=f"Error while user {self.get_user()} tries to get the status of execution {idx}. "
|
577
|
-
+
|
655
|
+
+ error,
|
578
656
|
)
|
579
|
-
|
580
657
|
try:
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
)
|
585
|
-
except AirflowError as err:
|
586
|
-
error = f"{AIRFLOW_ERROR_MSG} {err}"
|
658
|
+
state = self.orch_client.get_run_status(schema, run_id)
|
659
|
+
except self.orch_error as err:
|
660
|
+
error = self.orch_const["name"] + f" responded with an error: {err}"
|
587
661
|
_raise_af_error(
|
588
662
|
execution,
|
589
663
|
error,
|
@@ -591,8 +665,7 @@ class ExecutionStatusEndpoint(BaseMetaResource):
|
|
591
665
|
+ str(err),
|
592
666
|
)
|
593
667
|
|
594
|
-
|
595
|
-
state = AIRFLOW_TO_STATE_MAP.get(data["state"], EXEC_STATE_UNKNOWN)
|
668
|
+
state = map_run_state(state, self.orch_type)
|
596
669
|
execution.update_state(state)
|
597
670
|
current_app.logger.info(
|
598
671
|
f"User {self.get_user()} gets status of execution {idx}"
|
@@ -677,3 +750,18 @@ class ExecutionLogEndpoint(ExecutionDetailsEndpointBase):
|
|
677
750
|
"""
|
678
751
|
current_app.logger.info(f"User {self.get_user()} gets log of execution {idx}")
|
679
752
|
return self.get_detail(user=self.get_user(), idx=idx)
|
753
|
+
|
754
|
+
|
755
|
+
# region aux_functions
|
756
|
+
|
757
|
+
|
758
|
+
def map_run_state(state, orch_TYPE):
|
759
|
+
"""
|
760
|
+
Maps the state of the execution in the orchestrator to the state of the execution in cornflow
|
761
|
+
"""
|
762
|
+
if orch_TYPE == AIRFLOW_BACKEND:
|
763
|
+
state = state.json()["state"]
|
764
|
+
return AIRFLOW_TO_STATE_MAP.get(state, EXEC_STATE_UNKNOWN)
|
765
|
+
elif orch_TYPE == DATABRICKS_BACKEND:
|
766
|
+
preliminar_state = DATABRICKS_TO_STATE_MAP.get(state, EXEC_STATE_UNKNOWN)
|
767
|
+
return preliminar_state
|