cornflow 2.0.0a11__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.
Files changed (52) hide show
  1. airflow_config/airflow_local_settings.py +1 -1
  2. cornflow/app.py +8 -3
  3. cornflow/cli/migrations.py +23 -3
  4. cornflow/cli/service.py +18 -18
  5. cornflow/cli/utils.py +16 -1
  6. cornflow/commands/dag.py +1 -1
  7. cornflow/config.py +13 -8
  8. cornflow/endpoints/__init__.py +8 -2
  9. cornflow/endpoints/alarms.py +66 -2
  10. cornflow/endpoints/data_check.py +53 -26
  11. cornflow/endpoints/execution.py +387 -132
  12. cornflow/endpoints/login.py +81 -63
  13. cornflow/endpoints/meta_resource.py +11 -3
  14. cornflow/migrations/versions/999b98e24225.py +34 -0
  15. cornflow/models/base_data_model.py +4 -32
  16. cornflow/models/execution.py +2 -3
  17. cornflow/models/meta_models.py +28 -22
  18. cornflow/models/user.py +7 -10
  19. cornflow/schemas/alarms.py +8 -0
  20. cornflow/schemas/execution.py +1 -1
  21. cornflow/schemas/query.py +2 -1
  22. cornflow/schemas/user.py +5 -20
  23. cornflow/shared/authentication/auth.py +201 -264
  24. cornflow/shared/const.py +3 -14
  25. cornflow/shared/databricks.py +5 -1
  26. cornflow/tests/const.py +1 -0
  27. cornflow/tests/custom_test_case.py +77 -26
  28. cornflow/tests/unit/test_actions.py +2 -2
  29. cornflow/tests/unit/test_alarms.py +55 -1
  30. cornflow/tests/unit/test_apiview.py +108 -3
  31. cornflow/tests/unit/test_cases.py +20 -29
  32. cornflow/tests/unit/test_cli.py +6 -5
  33. cornflow/tests/unit/test_commands.py +3 -3
  34. cornflow/tests/unit/test_dags.py +5 -6
  35. cornflow/tests/unit/test_executions.py +443 -123
  36. cornflow/tests/unit/test_instances.py +14 -2
  37. cornflow/tests/unit/test_instances_file.py +1 -1
  38. cornflow/tests/unit/test_licenses.py +1 -1
  39. cornflow/tests/unit/test_log_in.py +230 -207
  40. cornflow/tests/unit/test_permissions.py +8 -8
  41. cornflow/tests/unit/test_roles.py +48 -10
  42. cornflow/tests/unit/test_schemas.py +1 -1
  43. cornflow/tests/unit/test_tables.py +7 -7
  44. cornflow/tests/unit/test_token.py +19 -5
  45. cornflow/tests/unit/test_users.py +22 -6
  46. cornflow/tests/unit/tools.py +75 -10
  47. {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/METADATA +16 -15
  48. {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/RECORD +51 -51
  49. {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/WHEEL +1 -1
  50. cornflow/endpoints/execution_databricks.py +0 -808
  51. {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/entry_points.txt +0 -0
  52. {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/top_level.txt +0 -0
@@ -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
- from cornflow.shared.exceptions import AirflowError, ObjectDoesNotExist, InvalidData
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 a new execution or get all the executions and their information back
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
- dag_run_id = execution.dag_run_id
92
- if not dag_run_id:
93
- # it's safe to say we will never get anything if we did not store the dag_run_id
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
- af_client = Airflow.from_config(current_app.config)
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 = af_client.get_run_status(
110
- dag_name=execution.schema, dag_run_id=dag_run_id
167
+ response = self.orch_client.get_run_status(
168
+ dag_name=execution.schema, run_id=run_id
111
169
  )
112
- except AirflowError as err:
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"Airflow responded with an error: {err}"
173
+ f"Orchestrator responded with an error: {err}"
116
174
  )
117
175
  continue
118
176
 
119
177
  data = response.json()
120
- state = AIRFLOW_TO_STATE_MAP.get(data["state"], EXEC_STATE_UNKNOWN)
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"] = "solve_model_dag"
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 airflow interaction:
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 airflow
162
- af_client = Airflow.from_config(config)
163
- if not af_client.is_alive():
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
- schema_info = af_client.get_orch_info(schema)
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
- # Validate config before running the dag
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
- info = schema_info.json()
233
- if info["is_paused"]:
234
- err = "The dag exists but it is paused in airflow"
235
- current_app.logger.error(err)
236
- execution.update_state(EXEC_STATE_ERROR_START)
237
- raise AirflowError(
238
- error=err,
239
- payload=dict(
240
- message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
241
- state=EXEC_STATE_ERROR_START,
242
- ),
243
- log_txt=f"Error while user {self.get_user()} tries to create an execution. "
244
- + err,
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
- response = af_client.run_workflow(execution.id, orch_name=schema)
249
- except AirflowError as err:
250
- error = "Airflow responded with an error: {}".format(err)
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 AirflowError(
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 dag_run_id in the execution table:
264
- af_data = response.json()
265
- execution.dag_run_id = af_data["dag_run_id"]
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"] = "solve_model_dag"
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
- # We now try to launch the task in airflow
340
- af_client = Airflow.from_config(config)
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 info["is_paused"]:
360
- err = "The dag exists but it is paused in airflow"
361
- current_app.logger.error(err)
362
- execution.update_state(EXEC_STATE_ERROR_START)
363
- raise AirflowError(
364
- error=err,
365
- payload=dict(
366
- message=EXECUTION_STATE_MESSAGE_DICT[EXEC_STATE_ERROR_START],
367
- state=EXEC_STATE_ERROR_START,
368
- ),
369
- log_txt=f"Error while user {self.get_user()} tries to relaunch execution {idx}. "
370
- + err,
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 = af_client.run_workflow(execution.id, orch_name=schema)
375
- except AirflowError as err:
376
- error = "Airflow responded with an error: {}".format(err)
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 AirflowError(
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 dag_run_id in the execution table:
390
- af_data = response.json()
391
- execution.dag_run_id = af_data["dag_run_id"]
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
- af_client = Airflow.from_config(current_app.config)
492
- if not af_client.is_alive():
493
- err = "Airflow is not accessible"
494
- raise AirflowError(
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
- response = af_client.set_dag_run_to_fail(
500
- dag_name=execution.schema, dag_run_id=execution.dag_run_id
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 airflow if the status is unknown, queued or running.
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 AirflowError(
734
+ raise self.orch_error(
550
735
  error=error, payload=dict(message=message, state=state), log_txt=log_txt
551
736
  )
552
737
 
553
- dag_run_id = execution.dag_run_id
554
- if not dag_run_id:
555
- # it's safe to say we will never get anything if we did not store the dag_run_id
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
- af_client = Airflow.from_config(current_app.config)
565
- if not af_client.is_alive():
566
- err = "Airflow is not accessible"
567
- _raise_af_error(
568
- execution,
569
- err,
570
- log_txt=f"Error while user {self.get_user()} tries to get the status of execution {idx}. "
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
- # TODO: get the dag_name from somewhere!
576
- response = af_client.get_run_status(
577
- dag_name=execution.schema, dag_run_id=dag_run_id
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
- data = response.json()
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