This is an automated email from the ASF dual-hosted git repository.
kaxil pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 0a913f21aa8 Use the configured API port in the default execution API
URL (#74478)
0a913f21aa8 is described below
commit 0a913f21aa8138544da3b60e20e4f35092825500
Author: Kaxil Naik <[email protected]>
AuthorDate: Fri Oct 9 16:22:57 2026 +0100
Use the configured API port in the default execution API URL (#74478)
When [api] base_url is unset or relative, the execution API URL fell
back to http://localhost:8080, ignoring [api] port. A single-host setup
that changed [api] port without setting base_url failed every task with
"Connection refused". The fallback now uses [api] port.
Also document how tasks reach an execution API server that runs
separately from the core API server.
---
.../administration-and-deployment/web-stack.rst | 13 +++++++--
.../src/airflow/config_templates/config.yml | 5 ++--
.../src/airflow/executors/base_executor.py | 14 +++++-----
.../tests/unit/executors/test_local_executor.py | 32 ++++++++++++++++++++++
4 files changed, 52 insertions(+), 12 deletions(-)
diff --git a/airflow-core/docs/administration-and-deployment/web-stack.rst
b/airflow-core/docs/administration-and-deployment/web-stack.rst
index ed8264efc4b..7a61d27c59d 100644
--- a/airflow-core/docs/administration-and-deployment/web-stack.rst
+++ b/airflow-core/docs/administration-and-deployment/web-stack.rst
@@ -31,9 +31,9 @@ now be available through that additional ``d12345`` prefix.
Without rebuilding
the frontend, XHR requests and static file queries should be directed to the
prefixed url
and served successfully.
-You will also need to update the execution API server url
-:ref:`config:core__execution_api_server_url` for tasks to be able to reach the
API
-with the new prefix.
+The default execution API server url
:ref:`config:core__execution_api_server_url` follows
+``base_url``, so tasks reach the API with the new prefix. If you set
``execution_api_server_url``
+explicitly, add the prefix to it as well.
Separating API Servers
-----------------------
@@ -58,6 +58,13 @@ separately. This might be useful for scaling them
independently or for deploying
# serve only the Execution API Server
airflow api-server --apps execution
+Tasks call the Execution API at :ref:`config:core__execution_api_server_url`,
which defaults to
+``{base_url}/execution/`` (see :ref:`config:api__base_url`). When you separate
the servers, either
+have your proxy route that path to the Execution API Server, or point
``execution_api_server_url``
+at it wherever tasks run, for example
``http://airflow-execution-api:8080/execution/``. If
+``base_url`` has a path such as ``/d12345``, the Execution API Server serves
under that path too,
+so include it: ``http://airflow-execution-api:8080/d12345/execution/``.
+
Known Issues
------------
diff --git a/airflow-core/src/airflow/config_templates/config.yml
b/airflow-core/src/airflow/config_templates/config.yml
index 7581497d0c5..0de787a4c59 100644
--- a/airflow-core/src/airflow/config_templates/config.yml
+++ b/airflow-core/src/airflow/config_templates/config.yml
@@ -557,8 +557,9 @@ core:
execution_api_server_url:
description: |
The url of the execution api server. Default is
``{BASE_URL}/execution/``
- where ``{BASE_URL}`` is the base url of the API Server. If
``{BASE_URL}`` is not set,
- it will use ``http://localhost:8080`` as the default base url.
+ where ``{BASE_URL}`` is the base url of the API Server. If
``{BASE_URL}`` is not set
+ or is a relative path such as ``/airflow``, it is prefixed with
``http://localhost:{PORT}``,
+ where ``{PORT}`` is ``[api] port``.
version_added: 3.0.0
type: string
example: ~
diff --git a/airflow-core/src/airflow/executors/base_executor.py
b/airflow-core/src/airflow/executors/base_executor.py
index b99ad38a4d4..c7898c347ed 100644
--- a/airflow-core/src/airflow/executors/base_executor.py
+++ b/airflow-core/src/airflow/executors/base_executor.py
@@ -25,7 +25,7 @@ from collections import defaultdict, deque
from collections.abc import Sequence
from dataclasses import dataclass, field
from functools import cached_property, wraps
-from typing import TYPE_CHECKING, Any, ClassVar, cast
+from typing import TYPE_CHECKING, Any, ClassVar
import pendulum
@@ -58,15 +58,15 @@ def get_execution_api_server_url(conf_source:
AirflowConfigParser | ExecutorConf
Team-specific executors can pass their own config (e.g.
``ExecutorConf``) to resolve
a team-specific URL.
"""
+ execution_api_server_url = conf_source.get("core",
"execution_api_server_url", fallback=None)
+ if execution_api_server_url is not None:
+ return execution_api_server_url
base_url = conf_source.get("api", "base_url", fallback="/")
# ExecutorConf.get() is typed as str | None even when fallback= guarantees
a str,
- # so the `not base_url` guard and the cast() below keep mypy happy.
+ # so the `not base_url` guard keeps mypy happy.
if not base_url or base_url.startswith("/"):
- base_url = f"http://localhost:8080{base_url}"
- default_execution_api_server = f"{base_url.rstrip('/')}/execution/"
- return cast(
- "str", conf_source.get("core", "execution_api_server_url",
fallback=default_execution_api_server)
- )
+ base_url = f"http://localhost:{conf_source.getint('api',
'port')}{base_url}"
+ return f"{base_url.rstrip('/')}/execution/"
if TYPE_CHECKING:
diff --git a/airflow-core/tests/unit/executors/test_local_executor.py
b/airflow-core/tests/unit/executors/test_local_executor.py
index 2c6e1d15484..9f5d196d6cd 100644
--- a/airflow-core/tests/unit/executors/test_local_executor.py
+++ b/airflow-core/tests/unit/executors/test_local_executor.py
@@ -463,6 +463,22 @@ class TestLocalExecutor:
({}, "http://localhost:8080/execution/"),
({("api", "base_url"): "/"}, "http://localhost:8080/execution/"),
({("api", "base_url"): "/airflow/"},
"http://localhost:8080/airflow/execution/"),
+ ({("api", "port"): "9091"}, "http://localhost:9091/execution/"),
+ (
+ {("api", "base_url"): "/airflow/", ("api", "port"): "9091"},
+ "http://localhost:9091/airflow/execution/",
+ ),
+ (
+ {("api", "base_url"): "http://test-server", ("api", "port"):
"9091"},
+ "http://test-server/execution/",
+ ),
+ (
+ {
+ ("core", "execution_api_server_url"):
"http://custom-server/execution/",
+ ("api", "port"): "not-a-port",
+ },
+ "http://custom-server/execution/",
+ ),
],
ids=[
"base_url_fallback",
@@ -470,6 +486,10 @@ class TestLocalExecutor:
"no_base_url_no_custom",
"base_url_no_custom",
"relative_base_url",
+ "no_base_url_custom_port",
+ "relative_base_url_custom_port",
+ "absolute_base_url_ignores_port",
+ "custom_server_ignores_port",
],
)
@mock.patch("airflow.executors.base_executor.BaseExecutor.run_workload")
@@ -526,6 +546,18 @@ class TestLocalExecutor:
assert mock_run_workload.call_count == 1
assert mock_run_workload.call_args.kwargs["server"] ==
default_server
+ def test_execution_api_server_url_fallback_uses_team_api_port(self):
+ """A team executor builds the fallback URL from its own ``[api]
port``, not the global one."""
+ with (
+ mock.patch.dict(os.environ, {"AIRFLOW__TEAM_A___API__PORT":
"9092"}),
+ conf_vars({("api", "port"): "9091"}),
+ ):
+ team_url =
get_execution_api_server_url(ExecutorConf(team_name="team_a"))
+ global_url =
get_execution_api_server_url(ExecutorConf(team_name=None))
+
+ assert team_url == "http://localhost:9092/execution/"
+ assert global_url == "http://localhost:9091/execution/"
+
def test_multiple_team_executors_isolation(self):
"""Test that multiple team executors can coexist with isolated
resources"""
team_a_executor = LocalExecutor(parallelism=2, team_name="team_a")