Skip to content

Store xDS resources as YAML instead of JSON#1321

Draft
minwoox wants to merge 1 commit into
line:mainfrom
minwoox:xds_yaml
Draft

Store xDS resources as YAML instead of JSON#1321
minwoox wants to merge 1 commit into
line:mainfrom
minwoox:xds_yaml

Conversation

@minwoox

@minwoox minwoox commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Motivation:

  • xDS resources were stored as JSON files. YAML is more human-readable and is the preferred format for configuration in the Kubernetes/Envoy ecosystem.

Modifications:

  • XdsResourceManager: fileName() now returns .yaml; push() uses Change.ofYamlUpsert(); updateOrDelete() falls back to the legacy .json path when the .yaml file is absent; update() atomically removes the old .json and creates the new .yaml in a single commit.
  • XdsResourceWatchingService: changed handleXdsResource() signature from String contentAsText to JsonNode content; both call sites now pass entry.content() / change.content() directly instead of contentAsText(). Also accepts EntryType.YAML and handles UPSERT_YAML.

Result:

  • Newly created xDS resources are stored as .yaml files.
  • Existing .json files remain readable; they are atomically migrated to .yaml on the first update.

Motivation:
- xDS resources were stored as JSON files. YAML is more
  human-readable and is the preferred format for configuration
  in the Kubernetes/Envoy ecosystem.

Modifications:
- `XdsResourceManager`: `fileName()` now returns `.yaml`;
  `push()` uses `Change.ofYamlUpsert()`; `updateOrDelete()`
  falls back to the legacy `.json` path when the `.yaml` file
  is absent; `update()` atomically removes the old `.json` and
  creates the new `.yaml` in a single commit.
- `XdsResourceWatchingService`: changed `handleXdsResource()`
  signature from `String contentAsText` to `JsonNode content`;
  both call sites now pass `entry.content()` /
  `change.content()` directly instead of `contentAsText()`.
  Also accepts `EntryType.YAML` and handles `UPSERT_YAML`.

Result:
- Newly created xDS resources are stored as `.yaml` files.
- Existing `.json` files remain readable; they are atomically
  migrated to `.yaml` on the first update.
@minwoox minwoox added this to the 0.85.0 milestone Jun 30, 2026
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates XDS resource storage from .json to .yaml files across the entire stack. New resources are written as YAML; legacy JSON files are detected via glob queries and atomically migrated to YAML on update. The resource watching pipeline, read endpoints, Kubernetes endpoint fetching, the webapp API slice, and all tests are updated to handle both formats.

Changes

XDS YAML Migration

Layer / File(s) Summary
Git storage: InsertYaml and ContentTransformer YAML support
server/.../command/ContentTransformer.java, server/.../git/AbstractChangesApplier.java, server/.../git/TransformingChangesApplier.java
ContentTransformer now accepts YAML entry type. AbstractChangesApplier gains InsertYaml to write YAML blobs. TransformingChangesApplier branches on entry type for both parsing and writeback, using InsertYaml or InsertJson accordingly.
XdsResourceManager: YAML-primary filenames and legacy JSON migration
xds/.../internal/XdsResourceManager.java
fileName() returns .yaml; a new toLegacyJsonFileName() derives the legacy path. push, update, and delete use atomic glob lookups (*.{yaml,json}) to detect legacy JSON and migrate it in the same commit. updateOrDelete is changed to a Consumer<String> form.
XDS service create operations: .yaml filenames
xds/.../cluster/v1/XdsClusterService.java, xds/.../endpoint/v1/XdsEndpointService.java, xds/.../listener/v1/XdsListenerService.java, xds/.../route/v1/XdsRouteService.java, xds/.../k8s/v1/XdsKubernetesService.java
All push(...) calls in create paths updated to use .yaml filenames and remove the legacy boolean argument.
XdsResourceWatchingService and ControlPlaneService: JsonNode pipeline
xds/.../internal/XdsResourceWatchingService.java, xds/.../internal/ControlPlaneService.java, xds/.../internal/CentralDogmaXdsResources.java
Abstract handleXdsResource hook changes to JsonNode content. init() broadens to JSON/YAML entries. handleDiff() handles UPSERT_YAML and skips .json removals during atomic migration. ControlPlaneService merges via content.traverse().
XdsEndpointReadService: YAML-first, JSON-fallback reads
xds/.../internal/XdsEndpointReadService.java
listEndpoints includes both JSON and YAML entries with dynamic type names. getEndpoint uses a new readWithFallback helper (YAML-first, retries JSON on EntryNotFoundException).
XdsEndpointUpdateScheduler: atomic YAML flush and legacy JSON migration
xds/.../endpoint/v1/XdsEndpointUpdateScheduler.java
Flush uses a glob find for YAML/JSON. Legacy .json triggers an atomic removal + YAML upsert with conflict-based retry. completeUpdates centralizes observer completion.
XdsKubernetesEndpointFetchingService: YAML push with legacy JSON migration
xds/.../k8s/v1/XdsKubernetesEndpointFetchingService.java
handleXdsResource accepts JsonNode. onFileRemoved locates endpoint via repo glob. KubernetesEndpointsUpdater migrates .json.yaml on first push using legacyJsonMigrated flag. pushYamlChange helper ignores redundant updates.
Webapp: YAML/JSON-aware fetching and resource ID derivation
webapp/src/dogma/features/xds/xdsApiSlice.ts, webapp/src/dogma/features/xds/XdsTypes.ts, webapp/src/dogma/features/xds/K8sAggregatorStatus.tsx
fetchYamlOrJson helper added (YAML-first, JSON fallback on 404). getResource and getK8sAggregator use it. ResourceIdArg gains optional path. RawFileDto.type fixed to 'YAML'. resourceName() strips both extensions. Generated path updated to .yaml.
XdsLegacyJsonCompatibilityTest: create/update/delete compatibility
xds/src/test/.../XdsLegacyJsonCompatibilityTest.java
New test suite: legacy JSON create conflicts (409), update migrates JSON→YAML atomically, delete removes legacy JSON—for clusters, listeners, routes, and endpoints.
Updated existing tests
xds/src/test/.../XdsResourceWatchingServiceTest.java, xds/src/test/.../XdsWritePermissionTest.java, xds/src/test/.../XdsEndpointReadPermissionTest.java, xds/src/test/.../k8s/v1/XdsKubernetesServiceTest.java, xds/src/test/.../k8s/v1/AggregatingMultipleKubernetesTest.java, it/.../XdsKubernetesNodeIpExtractorTest.java
All test queries switched from Query.ofJson/.json to Query.ofYaml/.yaml; handleXdsResource overrides updated to JsonNode signature; YAML upsert scenario added to watching service test.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant XdsService as XdsCluster/Endpoint/ListenerService
  participant XdsResourceManager
  participant Repository

  Client->>XdsService: createResource(group, id, proto)
  XdsService->>XdsResourceManager: push(group, name, .yaml path, proto)
  XdsResourceManager->>Repository: find(*.{yaml,json}) glob
  Repository-->>XdsResourceManager: absent / legacy .json found
  alt absent → create new
    XdsResourceManager->>Repository: upsert id.yaml
  else legacy .json exists → conflict (409)
    XdsResourceManager-->>Client: ALREADY_EXISTS
  end

  Client->>XdsService: updateResource(group, id, proto)
  XdsService->>XdsResourceManager: update(group, name, proto)
  XdsResourceManager->>Repository: find(*.{yaml,json}) glob via updateOrDelete
  Repository-->>XdsResourceManager: foundFileName (.yaml or .json)
  XdsResourceManager->>Repository: upsert id.yaml [+ remove legacy id.json]
  Repository-->>XdsResourceManager: committed
  XdsResourceManager-->>Client: OK

  Note over XdsResourceManager,Repository: Watch pipeline receives diff
  XdsResourceManager->>XdsResourceWatchingService: UPSERT_YAML / UPSERT_JSON diff entry
  XdsResourceWatchingService->>ControlPlaneService: handleXdsResource(path, JsonNode)
  ControlPlaneService->>ControlPlaneService: merge into Envoy proto builder via content.traverse()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • line/centraldogma#1151: Directly modifies XdsEndpointUpdateScheduler introduced by this PR, switching its transformation lookup to YAML-first with JSON fallback and atomic commit behavior.
  • line/centraldogma#1320: Directly updates K8sAggregatorStatus.tsx to use .yaml paths for history and display, the same file changed in this PR.

Suggested labels

new feature

Suggested reviewers

  • trustin
  • jrhee17
  • ikhoon

Poem

🐇 Hoppy news from the warren below,
JSON files had to go!
YAML blobs now bloom in Git,
Legacy JSON? Migrated — bit by bit.
The watchers watch, the mergers merge,
On .yaml paths we all converge! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: xDS resources are now stored as YAML rather than JSON.
Description check ✅ Passed The description matches the changeset and describes the YAML migration, compatibility fallback, and watcher updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
xds/src/main/java/com/linecorp/centraldogma/xds/endpoint/v1/XdsEndpointUpdateScheduler.java (1)

180-198: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Make the YAML-vs-JSON choice deterministic.

FIND_ONE_WITHOUT_CONTENT over base.* can return the legacy .json even when the .yaml counterpart also exists, so this path may transform stale JSON and upsert it over the YAML content. Fetch both matches without content and prefer .yaml; only migrate .json when no YAML entry exists.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xds/src/main/java/com/linecorp/centraldogma/xds/endpoint/v1/XdsEndpointUpdateScheduler.java`
around lines 180 - 198, The selection logic in XdsEndpointUpdateScheduler.handle
currently relies on FIND_ONE_WITHOUT_CONTENT over baseFileName + ".*", which can
nondeterministically pick the legacy .json entry even when a .yaml counterpart
exists. Update the lookup to fetch both matching entries without content, then
explicitly prefer the .yaml path in the branch that calls executeTransform; only
fall back to executeTransformWithMigration when no .yaml entry is present and
the matched file is .json. Ensure the decision is based on the resolved entry
name, not the arbitrary first result from the map.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@xds/src/main/java/com/linecorp/centraldogma/xds/endpoint/v1/XdsEndpointUpdateScheduler.java`:
- Around line 248-249: The migration path in XdsEndpointUpdateScheduler
currently lets a failure from BatchUpdateTransformer.apply() abort the returned
stage without closing the copied gRPC observers. Update the callback around
entry.revision() / entry.content() so any exception from apply is caught and all
pending observers are completed or failed before propagating the error, similar
to how Command.transform handles cleanup.

In
`@xds/src/main/java/com/linecorp/centraldogma/xds/internal/XdsResourceManager.java`:
- Around line 201-208: The resource lookup in XdsResourceManager is too broad
because baseFileName + ".*" can match unrelated sibling files and cause false
conflicts or wrong update/delete targets. Update the lookup in the
create/update/delete flow around the repository.find call to check only the
exact .yaml resource path and the legacy .json path, using the existing
fileName/baseFileName handling in XdsResourceManager so FIND_ONE_WITHOUT_CONTENT
only considers those two resource candidates.
- Around line 287-288: Retry the push when a legacy-file removal races with
migration: XdsResourceManager.updateOrDelete() can resolve a .json path that is
deleted and replaced by .yaml before executePush() runs, causing
Change.ofRemoval(legacyFileToRemove) to fail. Update the push flow around
executePush(), createLegacyChange, and the legacyFileToRemove handling so it
re-resolves the current resource path and retries with the YAML target instead
of surfacing the stale removal error.

In
`@xds/src/main/java/com/linecorp/centraldogma/xds/k8s/v1/XdsKubernetesEndpointFetchingService.java`:
- Around line 201-213: In XdsKubernetesEndpointFetchingService’s endpoint
cleanup flow, the current find/delete path only removes the first matching
endpoint file, so a leftover .json or .yaml counterpart can remain. Update the
repository.find(...).handle(...) logic to collect all matching endpoint paths
returned for the endpointBase prefix instead of using FIND_ONE_WITHOUT_CONTENT
and findFirst(), then call removeEndpointFile for each matched YAML/JSON path so
both legacy formats are cleaned up together.
- Around line 188-193: When an aggregator file is removed, the code in
XdsKubernetesEndpointFetchingService currently closes the corresponding
KubernetesEndpointsUpdater but leaves it in kubernetesEndpointsUpdaters. Update
the removal path to also delete the entry from the map after calling
updater.close(), using the existing aggregatorName key so stale updaters do not
accumulate.

---

Outside diff comments:
In
`@xds/src/main/java/com/linecorp/centraldogma/xds/endpoint/v1/XdsEndpointUpdateScheduler.java`:
- Around line 180-198: The selection logic in XdsEndpointUpdateScheduler.handle
currently relies on FIND_ONE_WITHOUT_CONTENT over baseFileName + ".*", which can
nondeterministically pick the legacy .json entry even when a .yaml counterpart
exists. Update the lookup to fetch both matching entries without content, then
explicitly prefer the .yaml path in the branch that calls executeTransform; only
fall back to executeTransformWithMigration when no .yaml entry is present and
the matched file is .json. Ensure the decision is based on the resolved entry
name, not the arbitrary first result from the map.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bc455d39-0640-4f89-8c2e-49525ea44751

📥 Commits

Reviewing files that changed from the base of the PR and between b0011fd and fdef095.

📒 Files selected for processing (25)
  • it/xds-k8s-node-ip-extractor/src/test/java/com/linecorp/centraldogma/it/xds/k8s/XdsKubernetesNodeIpExtractorTest.java
  • server/src/main/java/com/linecorp/centraldogma/server/command/ContentTransformer.java
  • server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/git/AbstractChangesApplier.java
  • server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/git/TransformingChangesApplier.java
  • webapp/src/dogma/features/xds/K8sAggregatorStatus.tsx
  • webapp/src/dogma/features/xds/XdsTypes.ts
  • webapp/src/dogma/features/xds/xdsApiSlice.ts
  • xds/src/main/java/com/linecorp/centraldogma/xds/cluster/v1/XdsClusterService.java
  • xds/src/main/java/com/linecorp/centraldogma/xds/endpoint/v1/XdsEndpointService.java
  • xds/src/main/java/com/linecorp/centraldogma/xds/endpoint/v1/XdsEndpointUpdateScheduler.java
  • xds/src/main/java/com/linecorp/centraldogma/xds/internal/CentralDogmaXdsResources.java
  • xds/src/main/java/com/linecorp/centraldogma/xds/internal/ControlPlaneService.java
  • xds/src/main/java/com/linecorp/centraldogma/xds/internal/XdsEndpointReadService.java
  • xds/src/main/java/com/linecorp/centraldogma/xds/internal/XdsResourceManager.java
  • xds/src/main/java/com/linecorp/centraldogma/xds/internal/XdsResourceWatchingService.java
  • xds/src/main/java/com/linecorp/centraldogma/xds/k8s/v1/XdsKubernetesEndpointFetchingService.java
  • xds/src/main/java/com/linecorp/centraldogma/xds/k8s/v1/XdsKubernetesService.java
  • xds/src/main/java/com/linecorp/centraldogma/xds/listener/v1/XdsListenerService.java
  • xds/src/main/java/com/linecorp/centraldogma/xds/route/v1/XdsRouteService.java
  • xds/src/test/java/com/linecorp/centraldogma/xds/internal/XdsEndpointReadPermissionTest.java
  • xds/src/test/java/com/linecorp/centraldogma/xds/internal/XdsLegacyJsonCompatibilityTest.java
  • xds/src/test/java/com/linecorp/centraldogma/xds/internal/XdsResourceWatchingServiceTest.java
  • xds/src/test/java/com/linecorp/centraldogma/xds/internal/XdsWritePermissionTest.java
  • xds/src/test/java/com/linecorp/centraldogma/xds/k8s/v1/AggregatingMultipleKubernetesTest.java
  • xds/src/test/java/com/linecorp/centraldogma/xds/k8s/v1/XdsKubernetesServiceTest.java

Comment on lines +248 to +249
final JsonNode newContent = new BatchUpdateTransformer(toRegister, toDeregister)
.apply(entry.revision(), entry.content());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Complete pending observers if the local migration transform fails.

Unlike Command.transform, this migration path runs BatchUpdateTransformer.apply() inside the callback before installing a command handler. If it throws, the returned stage fails and the copied gRPC observers are left open.

Suggested fix
-                final JsonNode newContent = new BatchUpdateTransformer(toRegister, toDeregister)
-                        .apply(entry.revision(), entry.content());
+                final JsonNode newContent;
+                try {
+                    newContent = new BatchUpdateTransformer(toRegister, toDeregister)
+                            .apply(entry.revision(), entry.content());
+                } catch (Throwable t) {
+                    copied.forEach(p -> p.streamObserver.onError(t));
+                    return null;
+                }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
final JsonNode newContent = new BatchUpdateTransformer(toRegister, toDeregister)
.apply(entry.revision(), entry.content());
final JsonNode newContent;
try {
newContent = new BatchUpdateTransformer(toRegister, toDeregister)
.apply(entry.revision(), entry.content());
} catch (Throwable t) {
copied.forEach(p -> p.streamObserver.onError(t));
return null;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xds/src/main/java/com/linecorp/centraldogma/xds/endpoint/v1/XdsEndpointUpdateScheduler.java`
around lines 248 - 249, The migration path in XdsEndpointUpdateScheduler
currently lets a failure from BatchUpdateTransformer.apply() abort the returned
stage without closing the copied gRPC observers. Update the callback around
entry.revision() / entry.content() so any exception from apply is caught and all
pending observers are completed or failed before propagating the error, similar
to how Command.transform handles cleanup.

Comment on lines +201 to +208
final String baseFileName = fileName.substring(0, fileName.length() - 5);
repository.find(Revision.HEAD, baseFileName + ".*", FIND_ONE_WITHOUT_CONTENT)
.handle((entries, cause) -> {
if (cause != null) {
responseObserver.onError(cause);
return null;
}
if (!entries.isEmpty()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Restrict the lookup to the exact .yaml and legacy .json paths.

baseFileName + ".*" can match unrelated siblings, so creates may falsely conflict and update/delete may operate on a non-resource file chosen by FIND_ONE_WITHOUT_CONTENT.

Proposed fix
-        final String baseFileName = fileName.substring(0, fileName.length() - 5);
-        repository.find(Revision.HEAD, baseFileName + ".*", FIND_ONE_WITHOUT_CONTENT)
+        final String legacyJsonFileName = toLegacyJsonFileName(fileName);
+        repository.find(Revision.HEAD, fileName + ',' + legacyJsonFileName, FIND_ONE_WITHOUT_CONTENT)
                   .handle((entries, cause) -> {
-        final String baseFileName = fileName.substring(0, fileName.length() - 5);
-        repository.find(Revision.HEAD, baseFileName + ".*", FIND_ONE_WITHOUT_CONTENT)
+        final String legacyJsonFileName = toLegacyJsonFileName(fileName);
+        repository.find(Revision.HEAD, fileName + ',' + legacyJsonFileName, FIND_ONE_WITHOUT_CONTENT)
                   .handle((entries, cause) -> {

Also applies to: 321-334

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xds/src/main/java/com/linecorp/centraldogma/xds/internal/XdsResourceManager.java`
around lines 201 - 208, The resource lookup in XdsResourceManager is too broad
because baseFileName + ".*" can match unrelated sibling files and cause false
conflicts or wrong update/delete targets. Update the lookup in the
create/update/delete flow around the repository.find call to check only the
exact .yaml resource path and the legacy .json path, using the existing
fileName/baseFileName handling in XdsResourceManager so FIND_ONE_WITHOUT_CONTENT
only considers those two resource candidates.

Comment on lines +287 to +288
executePush(responseObserver, group, resourceName, summary, author, resource,
change, false, legacyFileToRemove);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Retry the lookup when legacy removal races with migration.

After updateOrDelete() finds .json, another update can remove it and create .yaml before this push runs. That makes Change.ofRemoval(legacyFileToRemove) fail and currently surfaces an error instead of retrying against the now-current YAML path.

Suggested direction
-                               responseObserver.onError(cause);
+                               if (!create && legacyFileToRemove != null &&
+                                   peeled instanceof ChangeConflictException) {
+                                   // Re-run the YAML/JSON lookup so a concurrent migration is handled as YAML,
+                                   // while a concurrent delete still reports NOT_FOUND.
+                                   // Implement this at the update/delete call site to avoid blindly recreating
+                                   // a resource that was deleted.
+                               }
+                               responseObserver.onError(cause);
                                return null;

Also applies to: 299-302, 232-248

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xds/src/main/java/com/linecorp/centraldogma/xds/internal/XdsResourceManager.java`
around lines 287 - 288, Retry the push when a legacy-file removal races with
migration: XdsResourceManager.updateOrDelete() can resolve a .json path that is
deleted and replaced by .yaml before executePush() runs, causing
Change.ofRemoval(legacyFileToRemove) to fail. Update the push flow around
executePush(), createLegacyChange, and the legacyFileToRemove handling so it
re-resolves the current resource path and retries with the YAML target instead
of surfacing the stale removal error.

Comment on lines 188 to 193
if (updaters != null) {
final KubernetesEndpointsUpdater updater = updaters.get(aggregatorName);
if (updater != null) {
updater.close();
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Remove the updater from the map when the aggregator file is removed.

This currently closes the updater but leaves a stale entry in kubernetesEndpointsUpdaters, which can leak one entry per deleted aggregator.

Suggested fix
         if (updaters != null) {
-            final KubernetesEndpointsUpdater updater = updaters.get(aggregatorName);
+            final KubernetesEndpointsUpdater updater = updaters.remove(aggregatorName);
             if (updater != null) {
                 updater.close();
             }
+            if (updaters.isEmpty()) {
+                kubernetesEndpointsUpdaters.remove(groupName);
+            }
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (updaters != null) {
final KubernetesEndpointsUpdater updater = updaters.get(aggregatorName);
if (updater != null) {
updater.close();
}
}
if (updaters != null) {
final KubernetesEndpointsUpdater updater = updaters.remove(aggregatorName);
if (updater != null) {
updater.close();
}
if (updaters.isEmpty()) {
kubernetesEndpointsUpdaters.remove(groupName);
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xds/src/main/java/com/linecorp/centraldogma/xds/k8s/v1/XdsKubernetesEndpointFetchingService.java`
around lines 188 - 193, When an aggregator file is removed, the code in
XdsKubernetesEndpointFetchingService currently closes the corresponding
KubernetesEndpointsUpdater but leaves it in kubernetesEndpointsUpdaters. Update
the removal path to also delete the entry from the map after calling
updater.close(), using the existing aggregatorName key so stale updaters do not
accumulate.

Comment on lines +201 to +213
repository.find(Revision.HEAD, endpointBase + ".*", FIND_ONE_WITHOUT_CONTENT)
.handle((entries, findCause) -> {
if (findCause != null) {
logger.warn("Failed to find endpoint file for {} in {}", endpointBase, groupName, findCause);
return null;
}
final String actualPath = entries.keySet().stream()
.filter(p -> p.endsWith(".yaml") || p.endsWith(".json"))
.findFirst().orElse(null);
if (actualPath == null) {
return null; // Already removed or never created.
}
removeEndpointFile(groupName, actualPath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Remove both endpoint counterparts when both formats exist.

Using FIND_ONE_WITHOUT_CONTENT deletes only one matched endpoint path. If a legacy .json and .yaml counterpart both exist, deleting the aggregator can leave a stale endpoint resource behind. Fetch all matching YAML/JSON paths and remove them in the same cleanup command.

🧰 Tools
🪛 PMD (7.25.0)

[Low] 204-204: InvalidLogMessageFormat (Error Prone): Too many arguments, expected 2 arguments but found 3

(InvalidLogMessageFormat (Error Prone))

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xds/src/main/java/com/linecorp/centraldogma/xds/k8s/v1/XdsKubernetesEndpointFetchingService.java`
around lines 201 - 213, In XdsKubernetesEndpointFetchingService’s endpoint
cleanup flow, the current find/delete path only removes the first matching
endpoint file, so a leftover .json or .yaml counterpart can remain. Update the
repository.find(...).handle(...) logic to collect all matching endpoint paths
returned for the endpointBase prefix instead of using FIND_ONE_WITHOUT_CONTENT
and findFirst(), then call removeEndpointFile for each matched YAML/JSON path so
both legacy formats are cleaned up together.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant