apache-airflow-providers-edge3 1.4.0__py3-none-any.whl → 1.4.1__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 +9 -3
- airflow/providers/edge3/cli/worker.py +15 -9
- airflow/providers/edge3/executors/edge_executor.py +6 -4
- airflow/providers/edge3/models/edge_job.py +1 -1
- airflow/providers/edge3/models/edge_worker.py +21 -7
- airflow/providers/edge3/plugins/edge_executor_plugin.py +3 -12
- airflow/providers/edge3/plugins/www/dist/main.umd.cjs +14 -50
- airflow/providers/edge3/plugins/www/package.json +22 -22
- airflow/providers/edge3/plugins/www/pnpm-lock.yaml +1502 -1509
- airflow/providers/edge3/plugins/www/src/layouts/EdgeLayout.tsx +9 -14
- airflow/providers/edge3/plugins/www/src/layouts/NavTabs.tsx +1 -8
- airflow/providers/edge3/plugins/www/src/main.tsx +0 -5
- airflow/providers/edge3/plugins/www/src/pages/JobsPage.tsx +7 -8
- airflow/providers/edge3/plugins/www/src/utils/index.ts +0 -1
- airflow/providers/edge3/plugins/www/vite.config.ts +2 -1
- airflow/providers/edge3/worker_api/routes/_v2_compat.py +1 -0
- airflow/providers/edge3/worker_api/routes/jobs.py +4 -5
- airflow/providers/edge3/worker_api/routes/ui.py +8 -3
- airflow/providers/edge3/worker_api/routes/worker.py +8 -4
- {apache_airflow_providers_edge3-1.4.0.dist-info → apache_airflow_providers_edge3-1.4.1.dist-info}/METADATA +11 -11
- {apache_airflow_providers_edge3-1.4.0.dist-info → apache_airflow_providers_edge3-1.4.1.dist-info}/RECORD +24 -25
- airflow/providers/edge3/plugins/www/src/utils/tokenHandler.ts +0 -51
- {apache_airflow_providers_edge3-1.4.0.dist-info → apache_airflow_providers_edge3-1.4.1.dist-info}/WHEEL +0 -0
- {apache_airflow_providers_edge3-1.4.0.dist-info → apache_airflow_providers_edge3-1.4.1.dist-info}/entry_points.txt +0 -0
|
@@ -29,7 +29,7 @@ from airflow import __version__ as airflow_version
|
|
|
29
29
|
|
|
30
30
|
__all__ = ["__version__"]
|
|
31
31
|
|
|
32
|
-
__version__ = "1.4.
|
|
32
|
+
__version__ = "1.4.1"
|
|
33
33
|
|
|
34
34
|
if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
|
|
35
35
|
"2.10.0"
|
|
@@ -53,12 +53,18 @@ logger = logging.getLogger(__name__)
|
|
|
53
53
|
# Note: Given defaults make attempts after 1, 3, 7, 15, 31seconds, 1:03, 2:07, 3:37 and fails after 5:07min
|
|
54
54
|
# So far there is no other config facility in Task SDK we use ENV for the moment
|
|
55
55
|
# TODO: Consider these env variables jointly in task sdk together with task_sdk/src/airflow/sdk/api/client.py
|
|
56
|
-
API_RETRIES = int(
|
|
56
|
+
API_RETRIES = int(
|
|
57
|
+
os.getenv("AIRFLOW__EDGE__API_RETRIES", os.getenv("AIRFLOW__WORKERS__API_RETRIES", str(10)))
|
|
58
|
+
)
|
|
57
59
|
API_RETRY_WAIT_MIN = float(
|
|
58
|
-
os.getenv(
|
|
60
|
+
os.getenv(
|
|
61
|
+
"AIRFLOW__EDGE__API_RETRY_WAIT_MIN", os.getenv("AIRFLOW__WORKERS__API_RETRY_WAIT_MIN", str(1.0))
|
|
62
|
+
)
|
|
59
63
|
)
|
|
60
64
|
API_RETRY_WAIT_MAX = float(
|
|
61
|
-
os.getenv(
|
|
65
|
+
os.getenv(
|
|
66
|
+
"AIRFLOW__EDGE__API_RETRY_WAIT_MAX", os.getenv("AIRFLOW__WORKERS__API_RETRY_WAIT_MAX", str(90.0))
|
|
67
|
+
)
|
|
62
68
|
)
|
|
63
69
|
|
|
64
70
|
|
|
@@ -21,13 +21,13 @@ import os
|
|
|
21
21
|
import signal
|
|
22
22
|
import sys
|
|
23
23
|
from datetime import datetime
|
|
24
|
+
from functools import cache
|
|
24
25
|
from http import HTTPStatus
|
|
25
26
|
from multiprocessing import Process
|
|
26
27
|
from pathlib import Path
|
|
27
28
|
from subprocess import Popen
|
|
28
29
|
from time import sleep
|
|
29
30
|
from typing import TYPE_CHECKING
|
|
30
|
-
from urllib.parse import urlparse
|
|
31
31
|
|
|
32
32
|
from lockfile.pidlockfile import remove_existing_pidfile
|
|
33
33
|
from requests import HTTPError
|
|
@@ -176,7 +176,19 @@ class EdgeWorker:
|
|
|
176
176
|
return EdgeWorkerState.IDLE
|
|
177
177
|
|
|
178
178
|
@staticmethod
|
|
179
|
-
|
|
179
|
+
@cache
|
|
180
|
+
def _execution_api_server_url() -> str:
|
|
181
|
+
"""Get the execution api server url from config or environment."""
|
|
182
|
+
api_url = conf.get("edge", "api_url")
|
|
183
|
+
execution_api_server_url = conf.get("core", "execution_api_server_url", fallback="")
|
|
184
|
+
if not execution_api_server_url and api_url:
|
|
185
|
+
# Derive execution api url from edge api url as fallback
|
|
186
|
+
execution_api_server_url = api_url.replace("edge_worker/v1/rpcapi", "execution")
|
|
187
|
+
logger.info("Using execution api server url: %s", execution_api_server_url)
|
|
188
|
+
return execution_api_server_url
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def _run_job_via_supervisor(workload, execution_api_server_url) -> int:
|
|
180
192
|
from airflow.sdk.execution_time.supervisor import supervise
|
|
181
193
|
|
|
182
194
|
# Ignore ctrl-c in this process -- we don't want to kill _this_ one. we let tasks run to completion
|
|
@@ -186,12 +198,6 @@ class EdgeWorker:
|
|
|
186
198
|
setproctitle(f"airflow edge worker: {workload.ti.key}")
|
|
187
199
|
|
|
188
200
|
try:
|
|
189
|
-
api_url = conf.get("edge", "api_url")
|
|
190
|
-
execution_api_server_url = conf.get("core", "execution_api_server_url", fallback="")
|
|
191
|
-
if not execution_api_server_url:
|
|
192
|
-
parsed = urlparse(api_url)
|
|
193
|
-
execution_api_server_url = f"{parsed.scheme}://{parsed.netloc}/execution/"
|
|
194
|
-
|
|
195
201
|
supervise(
|
|
196
202
|
# This is the "wrong" ti type, but it duck types the same. TODO: Create a protocol for this.
|
|
197
203
|
# Same like in airflow/executors/local_executor.py:_execute_work()
|
|
@@ -215,7 +221,7 @@ class EdgeWorker:
|
|
|
215
221
|
workload: ExecuteTask = edge_job.command
|
|
216
222
|
process = Process(
|
|
217
223
|
target=EdgeWorker._run_job_via_supervisor,
|
|
218
|
-
kwargs={"workload": workload},
|
|
224
|
+
kwargs={"workload": workload, "execution_api_server_url": EdgeWorker._execution_api_server_url()},
|
|
219
225
|
)
|
|
220
226
|
process.start()
|
|
221
227
|
base_log_folder = conf.get("logging", "base_log_folder", fallback="NOT AVAILABLE")
|
|
@@ -30,7 +30,7 @@ from sqlalchemy.orm import Session
|
|
|
30
30
|
from airflow.cli.cli_config import GroupCommand
|
|
31
31
|
from airflow.configuration import conf
|
|
32
32
|
from airflow.executors.base_executor import BaseExecutor
|
|
33
|
-
from airflow.models.taskinstance import TaskInstance
|
|
33
|
+
from airflow.models.taskinstance import TaskInstance
|
|
34
34
|
from airflow.providers.common.compat.sdk import timezone
|
|
35
35
|
from airflow.providers.edge3.cli.edge_command import EDGE_COMMANDS
|
|
36
36
|
from airflow.providers.edge3.models.edge_job import EdgeJobModel
|
|
@@ -40,6 +40,7 @@ from airflow.providers.edge3.version_compat import AIRFLOW_V_3_0_PLUS
|
|
|
40
40
|
from airflow.stats import Stats
|
|
41
41
|
from airflow.utils.db import DBLocks, create_global_lock
|
|
42
42
|
from airflow.utils.session import NEW_SESSION, provide_session
|
|
43
|
+
from airflow.utils.state import TaskInstanceState
|
|
43
44
|
|
|
44
45
|
if TYPE_CHECKING:
|
|
45
46
|
import argparse
|
|
@@ -68,7 +69,8 @@ class EdgeExecutor(BaseExecutor):
|
|
|
68
69
|
"""
|
|
69
70
|
Check if already existing table matches the newest table schema.
|
|
70
71
|
|
|
71
|
-
workaround till Airflow
|
|
72
|
+
workaround till support for Airflow 2.x is dropped,
|
|
73
|
+
then it is possible to use alembic also for provider distributions.
|
|
72
74
|
"""
|
|
73
75
|
inspector = inspect(engine)
|
|
74
76
|
edge_job_columns = None
|
|
@@ -78,7 +80,7 @@ class EdgeExecutor(BaseExecutor):
|
|
|
78
80
|
edge_job_columns = [column["name"] for column in edge_job_schema]
|
|
79
81
|
for column in edge_job_schema:
|
|
80
82
|
if column["name"] == "command":
|
|
81
|
-
edge_job_command_len = column["type"].length
|
|
83
|
+
edge_job_command_len = column["type"].length # type: ignore[attr-defined]
|
|
82
84
|
|
|
83
85
|
# version 0.6.0rc1 added new column concurrency_slots
|
|
84
86
|
if edge_job_columns and "concurrency_slots" not in edge_job_columns:
|
|
@@ -284,7 +286,7 @@ class EdgeExecutor(BaseExecutor):
|
|
|
284
286
|
map_index=job.map_index,
|
|
285
287
|
session=session,
|
|
286
288
|
)
|
|
287
|
-
job.state = ti.state if ti else TaskInstanceState.REMOVED
|
|
289
|
+
job.state = ti.state if ti and ti.state else TaskInstanceState.REMOVED
|
|
288
290
|
|
|
289
291
|
if job.state != TaskInstanceState.RUNNING:
|
|
290
292
|
# Edge worker does not backport emitted Airflow metrics, so export some metrics
|
|
@@ -230,7 +230,9 @@ def request_maintenance(
|
|
|
230
230
|
) -> None:
|
|
231
231
|
"""Write maintenance request to the db."""
|
|
232
232
|
query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name)
|
|
233
|
-
worker: EdgeWorkerModel = session.scalar(query)
|
|
233
|
+
worker: EdgeWorkerModel | None = session.scalar(query)
|
|
234
|
+
if not worker:
|
|
235
|
+
raise ValueError(f"Edge Worker {worker_name} not found in list of registered workers")
|
|
234
236
|
worker.state = EdgeWorkerState.MAINTENANCE_REQUEST
|
|
235
237
|
worker.maintenance_comment = maintenance_comment
|
|
236
238
|
|
|
@@ -239,7 +241,9 @@ def request_maintenance(
|
|
|
239
241
|
def exit_maintenance(worker_name: str, session: Session = NEW_SESSION) -> None:
|
|
240
242
|
"""Write maintenance exit to the db."""
|
|
241
243
|
query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name)
|
|
242
|
-
worker: EdgeWorkerModel = session.scalar(query)
|
|
244
|
+
worker: EdgeWorkerModel | None = session.scalar(query)
|
|
245
|
+
if not worker:
|
|
246
|
+
raise ValueError(f"Edge Worker {worker_name} not found in list of registered workers")
|
|
243
247
|
worker.state = EdgeWorkerState.MAINTENANCE_EXIT
|
|
244
248
|
worker.maintenance_comment = None
|
|
245
249
|
|
|
@@ -248,7 +252,9 @@ def exit_maintenance(worker_name: str, session: Session = NEW_SESSION) -> None:
|
|
|
248
252
|
def remove_worker(worker_name: str, session: Session = NEW_SESSION) -> None:
|
|
249
253
|
"""Remove a worker that is offline or just gone from DB."""
|
|
250
254
|
query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name)
|
|
251
|
-
worker: EdgeWorkerModel = session.scalar(query)
|
|
255
|
+
worker: EdgeWorkerModel | None = session.scalar(query)
|
|
256
|
+
if not worker:
|
|
257
|
+
raise ValueError(f"Edge Worker {worker_name} not found in list of registered workers")
|
|
252
258
|
if worker.state in (
|
|
253
259
|
EdgeWorkerState.OFFLINE,
|
|
254
260
|
EdgeWorkerState.OFFLINE_MAINTENANCE,
|
|
@@ -267,7 +273,9 @@ def change_maintenance_comment(
|
|
|
267
273
|
) -> None:
|
|
268
274
|
"""Write maintenance comment in the db."""
|
|
269
275
|
query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name)
|
|
270
|
-
worker: EdgeWorkerModel = session.scalar(query)
|
|
276
|
+
worker: EdgeWorkerModel | None = session.scalar(query)
|
|
277
|
+
if not worker:
|
|
278
|
+
raise ValueError(f"Edge Worker {worker_name} not found in list of registered workers")
|
|
271
279
|
if worker.state in (
|
|
272
280
|
EdgeWorkerState.MAINTENANCE_MODE,
|
|
273
281
|
EdgeWorkerState.MAINTENANCE_PENDING,
|
|
@@ -285,7 +293,9 @@ def change_maintenance_comment(
|
|
|
285
293
|
def request_shutdown(worker_name: str, session: Session = NEW_SESSION) -> None:
|
|
286
294
|
"""Request to shutdown the edge worker."""
|
|
287
295
|
query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name)
|
|
288
|
-
worker: EdgeWorkerModel = session.scalar(query)
|
|
296
|
+
worker: EdgeWorkerModel | None = session.scalar(query)
|
|
297
|
+
if not worker:
|
|
298
|
+
raise ValueError(f"Edge Worker {worker_name} not found in list of registered workers")
|
|
289
299
|
if worker.state not in (
|
|
290
300
|
EdgeWorkerState.OFFLINE,
|
|
291
301
|
EdgeWorkerState.OFFLINE_MAINTENANCE,
|
|
@@ -298,7 +308,9 @@ def request_shutdown(worker_name: str, session: Session = NEW_SESSION) -> None:
|
|
|
298
308
|
def add_worker_queues(worker_name: str, queues: list[str], session: Session = NEW_SESSION) -> None:
|
|
299
309
|
"""Add queues to an edge worker."""
|
|
300
310
|
query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name)
|
|
301
|
-
worker: EdgeWorkerModel = session.scalar(query)
|
|
311
|
+
worker: EdgeWorkerModel | None = session.scalar(query)
|
|
312
|
+
if not worker:
|
|
313
|
+
raise ValueError(f"Edge Worker {worker_name} not found in list of registered workers")
|
|
302
314
|
if worker.state in (
|
|
303
315
|
EdgeWorkerState.OFFLINE,
|
|
304
316
|
EdgeWorkerState.OFFLINE_MAINTENANCE,
|
|
@@ -314,7 +326,9 @@ def add_worker_queues(worker_name: str, queues: list[str], session: Session = NE
|
|
|
314
326
|
def remove_worker_queues(worker_name: str, queues: list[str], session: Session = NEW_SESSION) -> None:
|
|
315
327
|
"""Remove queues from an edge worker."""
|
|
316
328
|
query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name)
|
|
317
|
-
worker: EdgeWorkerModel = session.scalar(query)
|
|
329
|
+
worker: EdgeWorkerModel | None = session.scalar(query)
|
|
330
|
+
if not worker:
|
|
331
|
+
raise ValueError(f"Edge Worker {worker_name} not found in list of registered workers")
|
|
318
332
|
if worker.state in (
|
|
319
333
|
EdgeWorkerState.OFFLINE,
|
|
320
334
|
EdgeWorkerState.OFFLINE_MAINTENANCE,
|
|
@@ -66,8 +66,7 @@ else:
|
|
|
66
66
|
from sqlalchemy import select
|
|
67
67
|
|
|
68
68
|
from airflow.auth.managers.models.resource_details import AccessView
|
|
69
|
-
from airflow.
|
|
70
|
-
from airflow.utils.state import State
|
|
69
|
+
from airflow.utils.state import State, TaskInstanceState
|
|
71
70
|
from airflow.utils.yaml import safe_load
|
|
72
71
|
from airflow.www.auth import has_access_view
|
|
73
72
|
|
|
@@ -252,18 +251,10 @@ class EdgeExecutorPlugin(AirflowPlugin):
|
|
|
252
251
|
fastapi_apps = [_get_api_endpoint()]
|
|
253
252
|
react_apps = [
|
|
254
253
|
{
|
|
255
|
-
"name": "Edge
|
|
254
|
+
"name": "Edge Executor",
|
|
256
255
|
"bundle_url": _get_base_url_path("/edge_worker/static/main.umd.cjs"),
|
|
257
256
|
"destination": "nav",
|
|
258
|
-
"url_route": "
|
|
259
|
-
"category": "admin",
|
|
260
|
-
"icon": _get_base_url_path("/edge_worker/res/cloud-computer.svg"),
|
|
261
|
-
"icon_dark_mode": _get_base_url_path("/edge_worker/res/cloud-computer-dark.svg"),
|
|
262
|
-
},
|
|
263
|
-
{
|
|
264
|
-
"name": "Edge Worker Jobs",
|
|
265
|
-
"bundle_url": _get_base_url_path("/edge_worker/static/main.umd.cjs"),
|
|
266
|
-
"url_route": "edge_jobs",
|
|
257
|
+
"url_route": "edge_executor",
|
|
267
258
|
"category": "admin",
|
|
268
259
|
"icon": _get_base_url_path("/edge_worker/res/cloud-computer.svg"),
|
|
269
260
|
"icon_dark_mode": _get_base_url_path("/edge_worker/res/cloud-computer-dark.svg"),
|