Skip to content

[fix][admin] Reject empty cluster migration URL: fix inverted ClusterUrl.isEmpty() - #26473

Open
SongOf wants to merge 1 commit into
apache:masterfrom
SongOf:fix/cluster-url-is-empty
Open

[fix][admin] Reject empty cluster migration URL: fix inverted ClusterUrl.isEmpty()#26473
SongOf wants to merge 1 commit into
apache:masterfrom
SongOf:fix/cluster-url-is-empty

Conversation

@SongOf

@SongOf SongOf commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Motivation

ClusterPolicies.ClusterUrl#isEmpty() has its condition inverted:

return serviceUrl != null && serviceUrlTls != null && brokerServiceUrl == null && brokerServiceUrlTls == null;

It only returns true when both HTTP URLs are set and both broker URLs are unset, and returns false for a ClusterUrl whose four fields are all null.

Its only caller is the cluster-migration admin endpoint (ClustersBase#updateClusterMigration), which uses it to reject migrated=true without a target:

if (isMigrated && clusterUrl.isEmpty()) {
    asyncResponse.resume(new RestException(Status.BAD_REQUEST, "Cluster url must not be empty"));

Because of the inverted predicate this guard never fires for the case it exists for. pulsar-admin clusters update-cluster-migration --migrated with no --service-url/--broker-url options is accepted and persisted; later, when the broker redirects producers/consumers with CommandTopicMigrated, the client receives null URLs and fails in HandlerState#setRedirectedClusterURI (logged, not surfaced), so the affected producers/consumers reconnect in a loop with no clear error. Conversely, a legitimate ClusterUrl carrying only the HTTP endpoints is rejected as "empty".

Modifications

  • ClusterPolicies.ClusterUrl#isEmpty() now returns true only when none of the four URLs is set (null or blank). Any single configured URL — HTTP or binary, plain or TLS — makes it non-empty. No signature, wire, or REST change; the guard in ClustersBase#updateClusterMigration is unchanged and now behaves as its error message says.

Verifying this change

This change added tests and can be verified as follows:

  • ClusterUrlTest (pulsar-client-admin-api) — pins the predicate: all-null / all-blank is empty; HTTP-only, broker-only, and fully populated URLs are not. Fails on the unpatched code (testNoUrlIsEmpty, testBlankUrlsAreEmpty, testHttpOnlyUrlIsNotEmpty).
  • AdminTest#clusters (pulsar-broker) — adds the end-to-end assertion at the admin resource: updateClusterMigration(..., migrated=true, new ClusterUrl()) returns 400 BAD_REQUEST, and a ClusterUrl with only a broker service URL is accepted and read back via getClusterMigration. On the unpatched code the empty-URL call is accepted and the test fails at fail("should have failed"). The validateSuperUserAccessAsync invocation count in this test is bumped 24 → 26 for the two accepted calls (the rejected call fails at the guard before any authorization check).

Does this pull request potentially affect one of the following parts:

If the box was checked, please highlight the changes

  • Dependencies (add or upgrade a dependency)
  • The public API
  • The schema
  • The default values of configurations
  • The threading model
  • The binary protocol
  • The REST endpoints
  • The admin CLI options
  • The metrics
  • Anything that affects deployment

This is an internal behavior change: the only user-visible effect is that POST /admin/v2/clusters/{cluster}/migrate?migrated=true with an empty body/URL now returns 400 as documented, instead of being accepted. No public API, schema, configuration, wire protocol, REST endpoint shape, CLI option, or metric changes.

Documentation

  • doc
  • doc-required
  • doc-not-needed
  • doc-complete

@SongOf
SongOf force-pushed the fix/cluster-url-is-empty branch from f15f59b to 85b9c0f Compare September 6, 2026 09:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant