apache-airflow-providers-edge3 2.0.0rc1__py3-none-any.whl → 3.0.1rc1__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/providers/edge3/__init__.py +1 -1
- airflow/providers/edge3/cli/api_client.py +30 -28
- airflow/providers/edge3/cli/dataclasses.py +3 -10
- airflow/providers/edge3/cli/definition.py +261 -0
- airflow/providers/edge3/cli/edge_command.py +8 -206
- airflow/providers/edge3/cli/worker.py +226 -198
- airflow/providers/edge3/example_dags/win_notepad.py +1 -1
- airflow/providers/edge3/executors/edge_executor.py +24 -49
- airflow/providers/edge3/get_provider_info.py +1 -0
- airflow/providers/edge3/models/edge_job.py +1 -2
- airflow/providers/edge3/models/edge_worker.py +61 -16
- airflow/providers/edge3/plugins/edge_executor_plugin.py +1 -1
- airflow/providers/edge3/plugins/www/dist/main.umd.cjs +8 -8
- airflow/providers/edge3/plugins/www/package.json +32 -27
- airflow/providers/edge3/plugins/www/pnpm-lock.yaml +1625 -1716
- airflow/providers/edge3/plugins/www/src/global.d.ts +24 -0
- airflow/providers/edge3/plugins/www/src/layouts/NavTabs.tsx +25 -3
- airflow/providers/edge3/plugins/www/src/main.tsx +6 -1
- airflow/providers/edge3/plugins/www/src/theme.ts +1 -1
- airflow/providers/edge3/worker_api/datamodels.py +12 -1
- airflow/providers/edge3/worker_api/routes/jobs.py +21 -8
- airflow/providers/edge3/worker_api/routes/logs.py +1 -1
- airflow/providers/edge3/worker_api/routes/worker.py +16 -3
- {apache_airflow_providers_edge3-2.0.0rc1.dist-info → apache_airflow_providers_edge3-3.0.1rc1.dist-info}/METADATA +14 -10
- {apache_airflow_providers_edge3-2.0.0rc1.dist-info → apache_airflow_providers_edge3-3.0.1rc1.dist-info}/RECORD +29 -29
- {apache_airflow_providers_edge3-2.0.0rc1.dist-info → apache_airflow_providers_edge3-3.0.1rc1.dist-info}/licenses/NOTICE +1 -1
- airflow/providers/edge3/plugins/templates/edge_worker_hosts.html +0 -175
- airflow/providers/edge3/plugins/templates/edge_worker_jobs.html +0 -69
- {apache_airflow_providers_edge3-2.0.0rc1.dist-info → apache_airflow_providers_edge3-3.0.1rc1.dist-info}/WHEEL +0 -0
- {apache_airflow_providers_edge3-2.0.0rc1.dist-info → apache_airflow_providers_edge3-3.0.1rc1.dist-info}/entry_points.txt +0 -0
- {apache_airflow_providers_edge3-2.0.0rc1.dist-info → apache_airflow_providers_edge3-3.0.1rc1.dist-info}/licenses/LICENSE +0 -0
|
@@ -23,17 +23,15 @@ from copy import deepcopy
|
|
|
23
23
|
from datetime import datetime, timedelta
|
|
24
24
|
from typing import TYPE_CHECKING, Any
|
|
25
25
|
|
|
26
|
-
from sqlalchemy import delete, inspect, text
|
|
26
|
+
from sqlalchemy import delete, inspect, select, text
|
|
27
27
|
from sqlalchemy.exc import NoSuchTableError
|
|
28
28
|
from sqlalchemy.orm import Session
|
|
29
29
|
|
|
30
|
-
from airflow.cli.cli_config import GroupCommand
|
|
31
30
|
from airflow.configuration import conf
|
|
32
31
|
from airflow.executors import workloads
|
|
33
32
|
from airflow.executors.base_executor import BaseExecutor
|
|
34
33
|
from airflow.models.taskinstance import TaskInstance
|
|
35
34
|
from airflow.providers.common.compat.sdk import Stats, timezone
|
|
36
|
-
from airflow.providers.edge3.cli.edge_command import EDGE_COMMANDS
|
|
37
35
|
from airflow.providers.edge3.models.edge_job import EdgeJobModel
|
|
38
36
|
from airflow.providers.edge3.models.edge_logs import EdgeLogsModel
|
|
39
37
|
from airflow.providers.edge3.models.edge_worker import EdgeWorkerModel, EdgeWorkerState, reset_metrics
|
|
@@ -42,10 +40,9 @@ from airflow.utils.session import NEW_SESSION, provide_session
|
|
|
42
40
|
from airflow.utils.state import TaskInstanceState
|
|
43
41
|
|
|
44
42
|
if TYPE_CHECKING:
|
|
45
|
-
import argparse
|
|
46
|
-
|
|
47
43
|
from sqlalchemy.engine.base import Engine
|
|
48
44
|
|
|
45
|
+
from airflow.cli.cli_config import GroupCommand
|
|
49
46
|
from airflow.models.taskinstancekey import TaskInstanceKey
|
|
50
47
|
|
|
51
48
|
# TODO: Airflow 2 type hints; remove when Airflow 2 support is removed
|
|
@@ -140,17 +137,15 @@ class EdgeExecutor(BaseExecutor):
|
|
|
140
137
|
key = task_instance.key
|
|
141
138
|
|
|
142
139
|
# Check if job already exists with same dag_id, task_id, run_id, map_index, try_number
|
|
143
|
-
existing_job = (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
try_number=key.try_number,
|
|
140
|
+
existing_job = session.scalars(
|
|
141
|
+
select(EdgeJobModel).where(
|
|
142
|
+
EdgeJobModel.dag_id == key.dag_id,
|
|
143
|
+
EdgeJobModel.task_id == key.task_id,
|
|
144
|
+
EdgeJobModel.run_id == key.run_id,
|
|
145
|
+
EdgeJobModel.map_index == key.map_index,
|
|
146
|
+
EdgeJobModel.try_number == key.try_number,
|
|
151
147
|
)
|
|
152
|
-
|
|
153
|
-
)
|
|
148
|
+
).first()
|
|
154
149
|
|
|
155
150
|
if existing_job:
|
|
156
151
|
existing_job.state = TaskInstanceState.QUEUED
|
|
@@ -176,10 +171,10 @@ class EdgeExecutor(BaseExecutor):
|
|
|
176
171
|
"""Reset worker state if heartbeat timed out."""
|
|
177
172
|
changed = False
|
|
178
173
|
heartbeat_interval: int = conf.getint("edge", "heartbeat_interval")
|
|
179
|
-
lifeless_workers:
|
|
180
|
-
|
|
174
|
+
lifeless_workers: Sequence[EdgeWorkerModel] = session.scalars(
|
|
175
|
+
select(EdgeWorkerModel)
|
|
181
176
|
.with_for_update(skip_locked=True)
|
|
182
|
-
.
|
|
177
|
+
.where(
|
|
183
178
|
EdgeWorkerModel.state.not_in(
|
|
184
179
|
[
|
|
185
180
|
EdgeWorkerState.UNKNOWN,
|
|
@@ -189,8 +184,7 @@ class EdgeExecutor(BaseExecutor):
|
|
|
189
184
|
),
|
|
190
185
|
EdgeWorkerModel.last_update < (timezone.utcnow() - timedelta(seconds=heartbeat_interval * 5)),
|
|
191
186
|
)
|
|
192
|
-
|
|
193
|
-
)
|
|
187
|
+
).all()
|
|
194
188
|
|
|
195
189
|
for worker in lifeless_workers:
|
|
196
190
|
changed = True
|
|
@@ -212,15 +206,14 @@ class EdgeExecutor(BaseExecutor):
|
|
|
212
206
|
def _update_orphaned_jobs(self, session: Session) -> bool:
|
|
213
207
|
"""Update status ob jobs when workers die and don't update anymore."""
|
|
214
208
|
heartbeat_interval: int = conf.getint("scheduler", "task_instance_heartbeat_timeout")
|
|
215
|
-
lifeless_jobs:
|
|
216
|
-
|
|
209
|
+
lifeless_jobs: Sequence[EdgeJobModel] = session.scalars(
|
|
210
|
+
select(EdgeJobModel)
|
|
217
211
|
.with_for_update(skip_locked=True)
|
|
218
|
-
.
|
|
212
|
+
.where(
|
|
219
213
|
EdgeJobModel.state == TaskInstanceState.RUNNING,
|
|
220
214
|
EdgeJobModel.last_update < (timezone.utcnow() - timedelta(seconds=heartbeat_interval)),
|
|
221
215
|
)
|
|
222
|
-
|
|
223
|
-
)
|
|
216
|
+
).all()
|
|
224
217
|
|
|
225
218
|
for job in lifeless_jobs:
|
|
226
219
|
ti = TaskInstance.get_task_instance(
|
|
@@ -254,10 +247,10 @@ class EdgeExecutor(BaseExecutor):
|
|
|
254
247
|
purged_marker = False
|
|
255
248
|
job_success_purge = conf.getint("edge", "job_success_purge")
|
|
256
249
|
job_fail_purge = conf.getint("edge", "job_fail_purge")
|
|
257
|
-
jobs:
|
|
258
|
-
|
|
250
|
+
jobs: Sequence[EdgeJobModel] = session.scalars(
|
|
251
|
+
select(EdgeJobModel)
|
|
259
252
|
.with_for_update(skip_locked=True)
|
|
260
|
-
.
|
|
253
|
+
.where(
|
|
261
254
|
EdgeJobModel.state.in_(
|
|
262
255
|
[
|
|
263
256
|
TaskInstanceState.RUNNING,
|
|
@@ -269,8 +262,7 @@ class EdgeExecutor(BaseExecutor):
|
|
|
269
262
|
]
|
|
270
263
|
)
|
|
271
264
|
)
|
|
272
|
-
|
|
273
|
-
)
|
|
265
|
+
).all()
|
|
274
266
|
|
|
275
267
|
# Sync DB with executor otherwise runs out of sync in multi scheduler deployment
|
|
276
268
|
already_removed = self.running - set(job.key for job in jobs)
|
|
@@ -388,23 +380,6 @@ class EdgeExecutor(BaseExecutor):
|
|
|
388
380
|
|
|
389
381
|
@staticmethod
|
|
390
382
|
def get_cli_commands() -> list[GroupCommand]:
|
|
391
|
-
|
|
392
|
-
GroupCommand(
|
|
393
|
-
name="edge",
|
|
394
|
-
help="Edge Worker components",
|
|
395
|
-
description=(
|
|
396
|
-
"Start and manage Edge Worker. Works only when using EdgeExecutor. For more information, "
|
|
397
|
-
"see https://airflow.apache.org/docs/apache-airflow-providers-edge3/stable/edge_executor.html"
|
|
398
|
-
),
|
|
399
|
-
subcommands=EDGE_COMMANDS,
|
|
400
|
-
),
|
|
401
|
-
]
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
def _get_parser() -> argparse.ArgumentParser:
|
|
405
|
-
"""
|
|
406
|
-
Generate documentation; used by Sphinx.
|
|
383
|
+
from airflow.providers.edge3.cli.definition import get_edge_cli_commands
|
|
407
384
|
|
|
408
|
-
|
|
409
|
-
"""
|
|
410
|
-
return EdgeExecutor._get_parser()
|
|
385
|
+
return get_edge_cli_commands()
|
|
@@ -32,6 +32,7 @@ def get_provider_info():
|
|
|
32
32
|
"plugin-class": "airflow.providers.edge3.plugins.edge_executor_plugin.EdgeExecutorPlugin",
|
|
33
33
|
}
|
|
34
34
|
],
|
|
35
|
+
"cli": ["airflow.providers.edge3.cli.definition.get_edge_cli_commands"],
|
|
35
36
|
"executors": ["airflow.providers.edge3.executors.EdgeExecutor"],
|
|
36
37
|
"config": {
|
|
37
38
|
"edge": {
|
|
@@ -27,8 +27,7 @@ from sqlalchemy import (
|
|
|
27
27
|
from sqlalchemy.orm import Mapped
|
|
28
28
|
|
|
29
29
|
from airflow.models.base import Base, StringID
|
|
30
|
-
from airflow.
|
|
31
|
-
from airflow.providers.common.compat.sdk import timezone
|
|
30
|
+
from airflow.providers.common.compat.sdk import TaskInstanceKey, timezone
|
|
32
31
|
from airflow.providers.common.compat.sqlalchemy.orm import mapped_column
|
|
33
32
|
from airflow.utils.log.logging_mixin import LoggingMixin
|
|
34
33
|
from airflow.utils.sqlalchemy import UtcDateTime
|
|
@@ -173,27 +173,72 @@ def set_metrics(
|
|
|
173
173
|
EdgeWorkerState.OFFLINE_MAINTENANCE,
|
|
174
174
|
)
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
try:
|
|
177
|
+
from airflow.sdk._shared.observability.metrics.dual_stats_manager import DualStatsManager
|
|
178
|
+
|
|
179
|
+
DualStatsManager.gauge(
|
|
180
|
+
"edge_worker.connected",
|
|
181
|
+
int(connected),
|
|
182
|
+
tags={},
|
|
183
|
+
extra_tags={"worker_name": worker_name},
|
|
184
|
+
)
|
|
178
185
|
|
|
179
|
-
|
|
180
|
-
|
|
186
|
+
DualStatsManager.gauge(
|
|
187
|
+
"edge_worker.maintenance",
|
|
188
|
+
int(maintenance),
|
|
189
|
+
tags={},
|
|
190
|
+
extra_tags={"worker_name": worker_name},
|
|
191
|
+
)
|
|
181
192
|
|
|
182
|
-
|
|
183
|
-
|
|
193
|
+
DualStatsManager.gauge(
|
|
194
|
+
"edge_worker.jobs_active",
|
|
195
|
+
jobs_active,
|
|
196
|
+
tags={},
|
|
197
|
+
extra_tags={"worker_name": worker_name},
|
|
198
|
+
)
|
|
184
199
|
|
|
185
|
-
|
|
186
|
-
|
|
200
|
+
DualStatsManager.gauge(
|
|
201
|
+
"edge_worker.concurrency",
|
|
202
|
+
concurrency,
|
|
203
|
+
tags={},
|
|
204
|
+
extra_tags={"worker_name": worker_name},
|
|
205
|
+
)
|
|
187
206
|
|
|
188
|
-
|
|
189
|
-
|
|
207
|
+
DualStatsManager.gauge(
|
|
208
|
+
"edge_worker.free_concurrency",
|
|
209
|
+
free_concurrency,
|
|
210
|
+
tags={},
|
|
211
|
+
extra_tags={"worker_name": worker_name},
|
|
212
|
+
)
|
|
190
213
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
214
|
+
DualStatsManager.gauge(
|
|
215
|
+
"edge_worker.num_queues",
|
|
216
|
+
len(queues),
|
|
217
|
+
tags={},
|
|
218
|
+
extra_tags={"worker_name": worker_name, "queues": ",".join(queues)},
|
|
219
|
+
)
|
|
220
|
+
except ImportError:
|
|
221
|
+
Stats.gauge(f"edge_worker.connected.{worker_name}", int(connected))
|
|
222
|
+
Stats.gauge("edge_worker.connected", int(connected), tags={"worker_name": worker_name})
|
|
223
|
+
|
|
224
|
+
Stats.gauge(f"edge_worker.maintenance.{worker_name}", int(maintenance))
|
|
225
|
+
Stats.gauge("edge_worker.maintenance", int(maintenance), tags={"worker_name": worker_name})
|
|
226
|
+
|
|
227
|
+
Stats.gauge(f"edge_worker.jobs_active.{worker_name}", jobs_active)
|
|
228
|
+
Stats.gauge("edge_worker.jobs_active", jobs_active, tags={"worker_name": worker_name})
|
|
229
|
+
|
|
230
|
+
Stats.gauge(f"edge_worker.concurrency.{worker_name}", concurrency)
|
|
231
|
+
Stats.gauge("edge_worker.concurrency", concurrency, tags={"worker_name": worker_name})
|
|
232
|
+
|
|
233
|
+
Stats.gauge(f"edge_worker.free_concurrency.{worker_name}", free_concurrency)
|
|
234
|
+
Stats.gauge("edge_worker.free_concurrency", free_concurrency, tags={"worker_name": worker_name})
|
|
235
|
+
|
|
236
|
+
Stats.gauge(f"edge_worker.num_queues.{worker_name}", len(queues))
|
|
237
|
+
Stats.gauge(
|
|
238
|
+
"edge_worker.num_queues",
|
|
239
|
+
len(queues),
|
|
240
|
+
tags={"worker_name": worker_name, "queues": ",".join(queues)},
|
|
241
|
+
)
|
|
197
242
|
|
|
198
243
|
|
|
199
244
|
def reset_metrics(worker_name: str) -> None:
|
|
@@ -22,7 +22,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
22
22
|
|
|
23
23
|
from airflow.configuration import conf
|
|
24
24
|
from airflow.exceptions import AirflowConfigException
|
|
25
|
-
from airflow.
|
|
25
|
+
from airflow.providers.common.compat.sdk import AirflowPlugin
|
|
26
26
|
from airflow.providers.edge3.version_compat import AIRFLOW_V_3_1_PLUS
|
|
27
27
|
from airflow.utils.session import NEW_SESSION, provide_session
|
|
28
28
|
|