apache / apache/seatunnel

[Bug] [Zeta] Dynamic REST port is not written back to HttpConfig, so cluster fan-out addresses the wrong node

Open
#12,294 1 comment 0 reactions 1 assignee Claimed by @SEZ9 View on GitHub
Dominant language
Java
Stars
9.7k
Forks
2.4k
Avg merge
3d 9h
Merged PRs (30d)
204

Description

### Search before asking

- [X] I had searched in the [issues](https://github.com/apache/seatunnel/issues?q=is%3Aissue+label%3A%22bug%22) and found no similar issues.

### What happened

When `seatunnel.engine.http.enable-dynamic-port` is `true` and the configured port is already in use, `JettyService` picks a free port and binds to it, but the chosen port is never written back to `HttpConfig`. `HttpConfig.getPort()` keeps returning the *configured* port rather than the *bound* port.

Every cluster-wide REST fan-out resolves a peer's HTTP port through `GetNodeHttpPortOperation`, which simply reads that stale value:

```java
// GetNodeHttpPortOperation#run
SeaTunnelServer service = getService();
response = service.getSeaTunnelConfig().getEngineConfig().getHttpConfig().getPort();
```

So on a node that took a dynamic port, peers keep addressing the configured port instead. The most visible consequence is in `LogService#allLogNameList`, which iterates every member and issues an HTTP GET to each member's reported port:

```java
String url = "http://" + host + ":" + nodeHttpPort + contextPath;
...
final String nodeId = host + ":" + nodeHttpPort;
```

On a single-host cluster where node A binds the configured port and node B falls back to a dynamic one, node B reports node A's port. Therefore:

- `GET /logs` enumerates node A twice and **never lists node B's local log files**;
- both entries carry an identical `nodeId` (`host:`);
- the returned `href` links for node B's files point at node A.

`LoggerLevelService` resolves peer ports the same way, so `GET /loggers?scope=cluster` is affected as well.

### Why existing tests do not catch it

`RestApiIT` already builds exactly this topology, and the stale value silently degrades it:

- node1 does `setPort(8080)` and starts first, binding 8080;
- node2 sets no port, so it inherits `port: 8080` from `seatunnel-e2e/.../test/resources/seatunnel.yaml`, and sets `setEnableDynamicPort(true)`. It therefore always falls back to another port;
- `beforeClass` then records `ports.put(node2HzPort, node2Config...getHttpConfig().getPort())`, which is the stale `8080`.

`ports` maps the Hazelcast member port to the HTTP port. REST v1 assertions use the key (the member port) and are unaffected, but every REST v2 assertion uses the value — so all of them address node1 on both iterations, and **node2's Jetty endpoints are never actually exercised**.

`verifyLogLink` passes for the wrong reason: node2's `href` points at node1, and node1 is the master, so the expected `Init JobMaster for Job fake_to_file` content is present.

### SeaTunnel Version

dev

### SeaTunnel Config

```conf
seatunnel:
engine:
http:
enable-http: true
port: 8080
enable-dynamic-port: true
```

Two nodes on the same host, so the second one falls back to a dynamic port.

### Running Command

```shell
curl http://:/logs
```

### Error Exception

```log
No exception is thrown; the response is silently wrong.
```

### Zeta or Flink or Spark Version

Zeta

### Java or Scala Version

_No response_

### Screenshots

_No response_

### Are you willing to submit PR?

- [X] Yes I am willing to submit a PR!

### Code of Conduct

- [X] I agree to follow this project's [Code of Conduct](https://www.apache.org/foundation/policies/conduct)

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.