This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/dev/pr-12722-74905675806fb615559f84252b5f75cbd8797c77 in repository https://gitbox.apache.org/repos/asf/seatunnel.git
commit a9db0e65428bd95dfd00ee21484f10dc6908db27 Author: SEZ <[email protected]> AuthorDate: Wed Oct 14 12:52:22 2026 +0000 [Fix][seatunnel-cli] Default the engine REST base URL to the Jetty HTTP port (#12722) --- seatunnel-cli/README.md | 4 +- seatunnel-cli/README.zh-CN.md | 4 +- seatunnel-cli/env.example.sh | 2 +- seatunnel-cli/seatunnel_cli/connectors.py | 18 ++++++-- seatunnel-cli/tests/test_engine_api_base.py | 68 +++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 9 deletions(-) diff --git a/seatunnel-cli/README.md b/seatunnel-cli/README.md index 677402e626..58f6888e0c 100644 --- a/seatunnel-cli/README.md +++ b/seatunnel-cli/README.md @@ -224,7 +224,7 @@ For source installs (`setup.sh` / `pip install`), the directory structure does n For live connector metadata and REST API job submission, start the SeaTunnel server: ```bash -export SEATUNNEL_API_BASE=http://localhost:5801 # Default +export SEATUNNEL_API_BASE=http://localhost:8080 # Default: the engine's Jetty HTTP port ``` When the engine is running, the CLI operates in **cluster mode** with live connector metadata, engine-level validation, and direct job submission via REST API. When unavailable, it falls back to **offline mode** using the bundled connector metadata. @@ -248,7 +248,7 @@ When the engine is running, the CLI operates in **cluster mode** with live conne | `ORCAROUTER_SMALL_FAST_MODEL` | No | `orcarouter/auto` | Fast model for OrcaRouter provider | | `ORCAROUTER_ECHO_REASONING_CONTENT` | No | `true` | Preserve and replay `reasoning_content` for OpenAI-compatible reasoning models (parity with `OPENAI_ECHO_REASONING_CONTENT`) | | `SEATUNNEL_HOME` | No | Auto-detect | SeaTunnel installation directory. Auto-detected in distribution tarball; set manually for source install | -| `SEATUNNEL_API_BASE` | No | `http://localhost:5801` | SeaTunnel REST API endpoint | +| `SEATUNNEL_API_BASE` | No | `http://localhost:8080` | SeaTunnel REST API endpoint (the engine's Jetty HTTP port, not the 5801 member port) | | `SEATUNNEL_CLI_DATA` | No | `<cli-package>/.data/` | Override CLI data directory (sessions, memory, config) | ## Usage diff --git a/seatunnel-cli/README.zh-CN.md b/seatunnel-cli/README.zh-CN.md index 42945f8121..f57c82fc23 100644 --- a/seatunnel-cli/README.zh-CN.md +++ b/seatunnel-cli/README.zh-CN.md @@ -192,7 +192,7 @@ apache-seatunnel-3.0.1/ <-- 自动检测为 SEATUNNEL_HOME 如需实时连接器元数据和 REST API 作业提交,请启动 SeaTunnel 服务: ```bash -export SEATUNNEL_API_BASE=http://localhost:5801 # 默认值 +export SEATUNNEL_API_BASE=http://localhost:8080 # 默认值:引擎的 Jetty HTTP 端口 ``` 引擎运行时,CLI 以**集群模式**运行,支持实时连接器元数据、引擎级校验和通过 REST API 直接提交作业。引擎不可用时,回退到**离线模式**,使用内置的连接器元数据。 @@ -216,7 +216,7 @@ export SEATUNNEL_API_BASE=http://localhost:5801 # 默认值 | `ORCAROUTER_SMALL_FAST_MODEL` | 否 | `orcarouter/auto` | OrcaRouter 提供商的快速模型 | | `ORCAROUTER_ECHO_REASONING_CONTENT` | 否 | `true` | 保留并回传 `reasoning_content`(与 `OPENAI_ECHO_REASONING_CONTENT` 对齐) | | `SEATUNNEL_HOME` | 否 | 自动检测 | SeaTunnel 安装目录。发行版压缩包中自动检测;源码安装需手动设置 | -| `SEATUNNEL_API_BASE` | 否 | `http://localhost:5801` | SeaTunnel REST API 端点 | +| `SEATUNNEL_API_BASE` | 否 | `http://localhost:8080` | SeaTunnel REST API 端点(引擎的 Jetty HTTP 端口,不是 5801 成员端口) | | `SEATUNNEL_CLI_DATA` | 否 | `<cli-package>/.data/` | 覆盖 CLI 数据目录(会话、记忆、配置) | ## 使用方法 diff --git a/seatunnel-cli/env.example.sh b/seatunnel-cli/env.example.sh index c53c4e5f57..8556126476 100755 --- a/seatunnel-cli/env.example.sh +++ b/seatunnel-cli/env.example.sh @@ -62,4 +62,4 @@ # ─── SeaTunnel Engine (optional) ─── # export SEATUNNEL_HOME=/path/to/seatunnel -# export SEATUNNEL_API_BASE=http://localhost:5801 +# export SEATUNNEL_API_BASE=http://localhost:8080 diff --git a/seatunnel-cli/seatunnel_cli/connectors.py b/seatunnel-cli/seatunnel_cli/connectors.py index ecce50c197..ade13972ef 100644 --- a/seatunnel-cli/seatunnel_cli/connectors.py +++ b/seatunnel-cli/seatunnel_cli/connectors.py @@ -48,11 +48,21 @@ logger = logging.getLogger(__name__) # ─── Runtime API client ─── -# Default REST API base — SeaTunnel engine HTTP port. -# In cluster mode (seatunnel-server.sh): REST API on port 5801 by default. +# Default REST API base — the engine's Jetty HTTP port, 8080. +# +# Not 5801. Every endpoint this CLI calls (/running-jobs, /option-rules, +# /submit-job, /job-info) is registered as a Jetty servlet in JettyService and +# served on `seatunnel.engine.http.port`, which is 8080 both as the code +# default and in the packaged config/seatunnel.yaml. Port 5801 is the +# Hazelcast member port: there the same handlers answer only under +# RestConstant.CONTEXT_PATH ("/hazelcast/rest/maps") and only when Hazelcast's +# own REST API is enabled, which the packaged config does not do. A base URL +# of localhost:5801 therefore returns 404 for every call the CLI makes. +# # In local mode (-m local): REST API is NOT available. -# Users can override with SEATUNNEL_API_BASE env var. -_ENGINE_API_BASE = os.environ.get("SEATUNNEL_API_BASE", "http://localhost:5801") +# Users can override with SEATUNNEL_API_BASE env var — needed when +# enable-dynamic-port moves the listener off 8080, or for a remote cluster. +_ENGINE_API_BASE = os.environ.get("SEATUNNEL_API_BASE", "http://localhost:8080") _API_CACHE: dict[str, dict] = {} # memory cache: "source:Jdbc" → parsed response _ENGINE_AVAILABLE: bool | None = None # None = not checked yet _ENGINE_CHECK_TS: float = 0 # timestamp of last check — recheck after 60s diff --git a/seatunnel-cli/tests/test_engine_api_base.py b/seatunnel-cli/tests/test_engine_api_base.py new file mode 100644 index 0000000000..af16d3817a --- /dev/null +++ b/seatunnel-cli/tests/test_engine_api_base.py @@ -0,0 +1,68 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Tests for the engine REST base URL the CLI talks to by default. + +The default is load-bearing and easy to get wrong, because SeaTunnel exposes +two HTTP ports that answer on different path prefixes: the Jetty HTTP port +(`seatunnel.engine.http.port`, 8080 in the packaged config) serves the v2 REST +API at `/running-jobs`, `/option-rules`, `/submit-job` and `/job-info`, while +the Hazelcast member port 5801 serves the same handlers only under +`/hazelcast/rest/maps` and only when Hazelcast's own REST API is enabled. + +A wrong default is silent: every call 404s, the engine reads as unreachable, +and the CLI quietly falls back to offline metadata. These tests pin the port +and the override so that cannot regress unnoticed. +""" + +import importlib + +from seatunnel_cli import connectors + + +def _reload_with_env(monkeypatch, value): + """Reload `connectors` so it re-resolves the base URL from the environment. + + The module reads SEATUNNEL_API_BASE once at import time, which is why the + production code caches it in a module attribute and why a test has to + reload rather than just set the variable. + """ + if value is None: + monkeypatch.delenv("SEATUNNEL_API_BASE", raising=False) + else: + monkeypatch.setenv("SEATUNNEL_API_BASE", value) + return importlib.reload(connectors) + + +def test_the_default_is_the_jetty_http_port(monkeypatch): + try: + assert _reload_with_env(monkeypatch, None)._ENGINE_API_BASE == "http://localhost:8080" + finally: + # Leave the module resolved the way the rest of the session expects. + monkeypatch.undo() + importlib.reload(connectors) + + +def test_the_environment_override_still_wins(monkeypatch): + # The override is the documented escape hatch for a remote cluster, and for + # a cluster whose enable-dynamic-port moved the listener off 8080. + try: + reloaded = _reload_with_env(monkeypatch, "http://zeta-master:18080") + assert reloaded._ENGINE_API_BASE == "http://zeta-master:18080" + finally: + monkeypatch.undo() + importlib.reload(connectors)
