Skip to content

OCPBUGS-78085: Fix dual-stack service update by preserving IP families - #467

Merged
openshift-merge-bot[bot] merged 1 commit into
openshift:masterfrom
bentito:fix-dns-service-dual-stack
Mar 20, 2026
Merged

OCPBUGS-78085: Fix dual-stack service update by preserving IP families#467
openshift-merge-bot[bot] merged 1 commit into
openshift:masterfrom
bentito:fix-dns-service-dual-stack

Conversation

@bentito

@bentito bentito commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

The failure in the DNS should answer A and AAAA queries for a dual-stack service test on metal jobs was triggered by the recent merge of PR #457 (NE-2414: Use trafficDistribution: PreferSameNode for openshift-dns Service) in commit e2b909a5.

The Root Cause

In pkg/operator/controller/controller_dns_service.go, the operator compares the current DNS Service against the expected (static) asset using a function called serviceChanged().

To prevent unnecessary updates, the comparison correctly ignores dynamic dual-stack fields:

		cmpopts.IgnoreFields(
			corev1.ServiceSpec{},
			"ClusterIP", "ClusterIPs",
			"IPFamilies", "IPFamilyPolicy",
		),

However, when PR #457 added trafficDistribution: PreferSameNode to the static asset, it caused expected and current to differ on existing clusters, legitimately triggering a Service update.

When an update is triggered, the operator blindly replaces the spec (updated.Spec = expected.Spec) and then tries to manually preserve dynamic fields that the API server or other controllers manage. It only preserved ClusterIP, dropping ClusterIPs, IPFamilies, and IPFamilyPolicy:

	// Preserve fields that the API, other controllers, or user may have
	// modified.
	updated.Spec.ClusterIP = current.Spec.ClusterIP

The Impact

Because expected.Spec (from the static YAML) has no IP families defined, the operator pushes a Service update that wipes out the secondary IPv6 Cluster IP. The openshift-dns service is abruptly converted back to a single-stack IPv4 service, which permanently breaks the DNS should answer A and AAAA queries for a dual-stack service test on metal (dual-stack) jobs.

The Fix

This explicitly preserves the dual-stack fields during an update:

	// Preserve fields that the API, other controllers, or user may have
	// modified.
	updated.Spec.ClusterIP = current.Spec.ClusterIP
	updated.Spec.ClusterIPs = current.Spec.ClusterIPs
	updated.Spec.IPFamilies = current.Spec.IPFamilies
	updated.Spec.IPFamilyPolicy = current.Spec.IPFamilyPolicy

Summary by CodeRabbit

  • Bug Fixes

    • DNS service updates no longer overwrite existing multi‑IP configuration: ClusterIPs, IP families, and IP family policy are preserved so dual‑stack addresses remain intact during updates.
  • Tests

    • Service-change tests expanded to allow pre‑mutation setup, apply mutations, and verify post‑update preservation of multi‑IP fields, including a case where dual‑stack fields are removed from the desired spec.

@coderabbitai

coderabbitai Bot commented Mar 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are limited based on label configuration.

🚫 Review skipped — only excluded labels are configured. (1)
  • do-not-merge/work-in-progress

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 952b15b4-521b-460c-8e7e-ea8871f7d0b3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

serviceChanged now also preserves ClusterIPs, IPFamilies, and IPFamilyPolicy from the current Service when constructing the updated Service. Tests were extended with mutateOriginal and verify callbacks and include a case validating preservation of dual-stack fields.

Changes

Cohort / File(s) Summary
Service Field Preservation
pkg/operator/controller/controller_dns_service.go
serviceChanged now copies ClusterIPs, IPFamilies, and IPFamilyPolicy from the current Service into the updated Service, in addition to preserving ClusterIP.
Tests: extended verification
pkg/operator/controller/controller_dns_service_test.go
Test table gains mutateOriginal, mutate, and verify callbacks; test flow applies an optional original mutation, calls serviceChanged, runs verify when a change occurs, and re-runs serviceChanged to assert fixed-point. Added test for dual-stack field preservation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Structure And Quality ⚠️ Warning Test assertions lack meaningful failure messages and test is not fully load-bearing for ClusterIP preservation because mutate function doesn't clear ClusterIP in expected fixture. Add descriptive failure messages to assertions and implement test tightening by adding service.Spec.ClusterIP = "" in mutate function to ensure regression test properly validates dual-stack field preservation.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Stable And Deterministic Test Names ✅ Passed All test case descriptions are stable and deterministic, using only static hardcoded strings without dynamic information.
Title check ✅ Passed The PR title accurately describes the main change: preserving dual-stack IP family fields (ClusterIPs, IPFamilies, IPFamilyPolicy) during service updates, which directly addresses the regression described in the PR objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

@openshift-ci
openshift-ci Bot requested review from davidesalerno and frobware March 9, 2026 20:49
@gcs278

gcs278 commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

/assign

@bentito
bentito force-pushed the fix-dns-service-dual-stack branch from baf4270 to 60f82c6 Compare March 9, 2026 21:35
@bentito

bentito commented Mar 9, 2026

Copy link
Copy Markdown
Contributor Author

I amended the last commit with an update to TestDNSServiceChanged to include a dedicated unit test for this regression.

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/operator/controller/controller_dns_service_test.go`:
- Around line 145-166: The test case uses mutateOriginal/mutate/verify but only
asserts lengths of updated.Spec.IPFamilies and ClusterIPs, which can miss
loss/reordering of the primary ClusterIP or family order; update the fixture in
mutateOriginal to set a realistic dual-stack Service including
Service.Spec.ClusterIP (primary IPv4 e.g. "1.2.3.4") and ClusterIPs in the
proper order (["1.2.3.4", "fd00::1"]) and in verify assert equality of
updated.Spec.ClusterIP, updated.Spec.ClusterIPs, and updated.Spec.IPFamilies
(and IPFamilyPolicy) against the original Service values rather than just
checking slice lengths so the preserved dual-stack state is validated
field-for-field.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 189a0b21-d750-4e52-aca4-8620ff530fd7

📥 Commits

Reviewing files that changed from the base of the PR and between baf4270 and 60f82c6.

📒 Files selected for processing (2)
  • pkg/operator/controller/controller_dns_service.go
  • pkg/operator/controller/controller_dns_service_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/operator/controller/controller_dns_service.go

Comment thread pkg/operator/controller/controller_dns_service_test.go
@bentito bentito changed the title Fix dual-stack service update by preserving IP families OCPBUGS-78085: Fix dual-stack service update by preserving IP families Mar 9, 2026
@openshift-ci-robot openshift-ci-robot added jira/severity-important Referenced Jira bug's severity is important for the branch this PR is targeting. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. labels Mar 9, 2026
@openshift-ci-robot

Copy link
Copy Markdown
Contributor

@bentito: This pull request references Jira Issue OCPBUGS-78085, which is invalid:

  • expected the bug to target the "4.22.0" version, but no target version was set

Comment /jira refresh to re-evaluate validity if changes to the Jira bug are made, or edit the title of this pull request to link to a different bug.

The bug has been updated to refer to the pull request using the external bug tracker.

Details

In response to this:

The failure in the DNS should answer A and AAAA queries for a dual-stack service test on metal jobs was triggered by the recent merge of PR #457 (NE-2414: Use trafficDistribution: PreferSameNode for openshift-dns Service) in commit e2b909a5.

The Root Cause

In pkg/operator/controller/controller_dns_service.go, the operator compares the current DNS Service against the expected (static) asset using a function called serviceChanged().

To prevent unnecessary updates, the comparison correctly ignores dynamic dual-stack fields:

  	cmpopts.IgnoreFields(
  		corev1.ServiceSpec{},
  		"ClusterIP", "ClusterIPs",
  		"IPFamilies", "IPFamilyPolicy",
  	),

However, when PR #457 added trafficDistribution: PreferSameNode to the static asset, it caused expected and current to differ on existing clusters, legitimately triggering a Service update.

When an update is triggered, the operator blindly replaces the spec (updated.Spec = expected.Spec) and then tries to manually preserve dynamic fields that the API server or other controllers manage. It only preserved ClusterIP, dropping ClusterIPs, IPFamilies, and IPFamilyPolicy:

  // Preserve fields that the API, other controllers, or user may have
  // modified.
  updated.Spec.ClusterIP = current.Spec.ClusterIP

The Impact

Because expected.Spec (from the static YAML) has no IP families defined, the operator pushes a Service update that wipes out the secondary IPv6 Cluster IP. The openshift-dns service is abruptly converted back to a single-stack IPv4 service, which permanently breaks the DNS should answer A and AAAA queries for a dual-stack service test on metal (dual-stack) jobs.

The Fix

This explicitly preserves the dual-stack fields during an update:

  // Preserve fields that the API, other controllers, or user may have
  // modified.
  updated.Spec.ClusterIP = current.Spec.ClusterIP
  updated.Spec.ClusterIPs = current.Spec.ClusterIPs
  updated.Spec.IPFamilies = current.Spec.IPFamilies
  updated.Spec.IPFamilyPolicy = current.Spec.IPFamilyPolicy

Summary by CodeRabbit

  • Bug Fixes
  • Prevented DNS service updates from overwriting existing multi-IP settings (ClusterIPs, IP families and policy) so dual-stack addresses are preserved during updates.
  • Tests
  • Expanded service-change tests to cover preservation of multi-IP fields and added verification steps to assert correct behavior.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@bentito

bentito commented Mar 9, 2026

Copy link
Copy Markdown
Contributor Author

/jira refresh

@openshift-ci-robot openshift-ci-robot added the jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. label Mar 9, 2026
@openshift-ci-robot

Copy link
Copy Markdown
Contributor

@bentito: This pull request references Jira Issue OCPBUGS-78085, which is valid. The bug has been moved to the POST state.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (4.22.0) matches configured target version for branch (4.22.0)
  • bug is in the state New, which is one of the valid states (NEW, ASSIGNED, POST)

Requesting review from QA contact:
/cc @lihongan

Details

In response to this:

/jira refresh

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot openshift-ci-robot removed the jira/invalid-bug Indicates that a referenced Jira bug is invalid for the branch this PR is targeting. label Mar 9, 2026
@openshift-ci
openshift-ci Bot requested a review from lihongan March 9, 2026 21:49
@lihongan

Copy link
Copy Markdown

/jira refresh

@openshift-ci-robot

Copy link
Copy Markdown
Contributor

@lihongan: This pull request references Jira Issue OCPBUGS-78085, which is valid.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (4.22.0) matches configured target version for branch (4.22.0)
  • bug is in the state POST, which is one of the valid states (NEW, ASSIGNED, POST)

Requesting review from QA contact:
/cc @melvinjoseph86

Details

In response to this:

/jira refresh

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci
openshift-ci Bot requested a review from melvinjoseph86 March 10, 2026 01:43
@lihongan

Copy link
Copy Markdown

/retest-required

@bentito bentito changed the title OCPBUGS-78085: Fix dual-stack service update by preserving IP families OCPBUGS-78053: Fix dual-stack service update by preserving IP families Mar 10, 2026
@openshift-ci-robot openshift-ci-robot removed the jira/severity-important Referenced Jira bug's severity is important for the branch this PR is targeting. label Mar 10, 2026
@openshift-ci-robot

Copy link
Copy Markdown
Contributor

@bentito: This pull request references Jira Issue OCPBUGS-78053, which is valid. The bug has been moved to the POST state.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (4.22.0) matches configured target version for branch (4.22.0)
  • bug is in the state ASSIGNED, which is one of the valid states (NEW, ASSIGNED, POST)

Requesting review from QA contact:
/cc @lihongan

The bug has been updated to refer to the pull request using the external bug tracker.

Details

In response to this:

The failure in the DNS should answer A and AAAA queries for a dual-stack service test on metal jobs was triggered by the recent merge of PR #457 (NE-2414: Use trafficDistribution: PreferSameNode for openshift-dns Service) in commit e2b909a5.

The Root Cause

In pkg/operator/controller/controller_dns_service.go, the operator compares the current DNS Service against the expected (static) asset using a function called serviceChanged().

To prevent unnecessary updates, the comparison correctly ignores dynamic dual-stack fields:

  	cmpopts.IgnoreFields(
  		corev1.ServiceSpec{},
  		"ClusterIP", "ClusterIPs",
  		"IPFamilies", "IPFamilyPolicy",
  	),

However, when PR #457 added trafficDistribution: PreferSameNode to the static asset, it caused expected and current to differ on existing clusters, legitimately triggering a Service update.

When an update is triggered, the operator blindly replaces the spec (updated.Spec = expected.Spec) and then tries to manually preserve dynamic fields that the API server or other controllers manage. It only preserved ClusterIP, dropping ClusterIPs, IPFamilies, and IPFamilyPolicy:

  // Preserve fields that the API, other controllers, or user may have
  // modified.
  updated.Spec.ClusterIP = current.Spec.ClusterIP

The Impact

Because expected.Spec (from the static YAML) has no IP families defined, the operator pushes a Service update that wipes out the secondary IPv6 Cluster IP. The openshift-dns service is abruptly converted back to a single-stack IPv4 service, which permanently breaks the DNS should answer A and AAAA queries for a dual-stack service test on metal (dual-stack) jobs.

The Fix

This explicitly preserves the dual-stack fields during an update:

  // Preserve fields that the API, other controllers, or user may have
  // modified.
  updated.Spec.ClusterIP = current.Spec.ClusterIP
  updated.Spec.ClusterIPs = current.Spec.ClusterIPs
  updated.Spec.IPFamilies = current.Spec.IPFamilies
  updated.Spec.IPFamilyPolicy = current.Spec.IPFamilyPolicy

Summary by CodeRabbit

  • Bug Fixes
  • Prevented DNS service updates from overwriting existing multi-IP settings (ClusterIPs, IP families and policy) so dual-stack addresses are preserved during updates.
  • Tests
  • Expanded service-change tests to cover preservation of multi-IP fields and added verification steps to assert correct behavior.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

Comment on lines +242 to +244
updated.Spec.ClusterIPs = current.Spec.ClusterIPs
updated.Spec.IPFamilies = current.Spec.IPFamilies
updated.Spec.IPFamilyPolicy = current.Spec.IPFamilyPolicy

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You should also update serviceCmpOpts not to ignore these fields.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@Miciah I'm not sure if I understand. We want to preserve these fields, so if they change, we shouldn't need to reconcile/change anything right? I think the current implementation is correct.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think Miciah might be suggesting we should strictly enforce the dual-stack state in the desired Service. If that's the case, I'm concerned that removing those fields from serviceCmpOpts would cause a reconciliation loop unless we also update desiredDNSService to dynamically resolve the full ClusterIPs list and IPFamilyPolicy from the cluster network config. Does that sound like what you had in mind, @Miciah?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah, I think you are right—we want the operator to ignore clusterIP, clusterIPs, ipFamilies, and ipFamilyPolicy and let the API server set them. It is only trafficDistribution where the operator has a preference.

@Miciah

Miciah commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Please make sure to include details and a Jira link in the commit message.

@openshift-ci-robot

Copy link
Copy Markdown
Contributor

@bentito: This pull request references Jira Issue OCPBUGS-78053, which is valid.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (4.22.0) matches configured target version for branch (4.22.0)
  • bug is in the state POST, which is one of the valid states (NEW, ASSIGNED, POST)

Requesting review from QA contact:
/cc @lihongan

Details

In response to this:

The failure in the DNS should answer A and AAAA queries for a dual-stack service test on metal jobs was triggered by the recent merge of PR #457 (NE-2414: Use trafficDistribution: PreferSameNode for openshift-dns Service) in commit e2b909a5.

The Root Cause

In pkg/operator/controller/controller_dns_service.go, the operator compares the current DNS Service against the expected (static) asset using a function called serviceChanged().

To prevent unnecessary updates, the comparison correctly ignores dynamic dual-stack fields:

  	cmpopts.IgnoreFields(
  		corev1.ServiceSpec{},
  		"ClusterIP", "ClusterIPs",
  		"IPFamilies", "IPFamilyPolicy",
  	),

However, when PR #457 added trafficDistribution: PreferSameNode to the static asset, it caused expected and current to differ on existing clusters, legitimately triggering a Service update.

When an update is triggered, the operator blindly replaces the spec (updated.Spec = expected.Spec) and then tries to manually preserve dynamic fields that the API server or other controllers manage. It only preserved ClusterIP, dropping ClusterIPs, IPFamilies, and IPFamilyPolicy:

  // Preserve fields that the API, other controllers, or user may have
  // modified.
  updated.Spec.ClusterIP = current.Spec.ClusterIP

The Impact

Because expected.Spec (from the static YAML) has no IP families defined, the operator pushes a Service update that wipes out the secondary IPv6 Cluster IP. The openshift-dns service is abruptly converted back to a single-stack IPv4 service, which permanently breaks the DNS should answer A and AAAA queries for a dual-stack service test on metal (dual-stack) jobs.

The Fix

This explicitly preserves the dual-stack fields during an update:

  // Preserve fields that the API, other controllers, or user may have
  // modified.
  updated.Spec.ClusterIP = current.Spec.ClusterIP
  updated.Spec.ClusterIPs = current.Spec.ClusterIPs
  updated.Spec.IPFamilies = current.Spec.IPFamilies
  updated.Spec.IPFamilyPolicy = current.Spec.IPFamilyPolicy

Summary by CodeRabbit

  • Bug Fixes

  • DNS service updates no longer overwrite existing multi-IP configuration: ClusterIPs, IP families, and IP family policy are preserved so dual‑stack addresses remain intact during updates.

  • Tests

  • Service-change tests expanded to initialize original state, apply mutations, and verify post‑update preservation of multi‑IP fields, adding a case for removing dual‑stack fields from the desired spec.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@bentito
bentito force-pushed the fix-dns-service-dual-stack branch from cba2295 to 04c726d Compare March 10, 2026 19:32
@bentito

bentito commented Mar 10, 2026

Copy link
Copy Markdown
Contributor Author

Please make sure to include details and a Jira link in the commit message.

Done, squashed to one commit with (mostly) the contents of the PR description.

@lihongan

Copy link
Copy Markdown

The test is still failing in the payload jobs

@gcs278

gcs278 commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

@lihongan @bentito

I see failures like this (before and after this PR):

host.ostest.test.metalkube.org:5000/localimages/local-test-image:e2e-14-registry-k8s-io-e2e-test-images-jessie-dnsutils-1-7-bJ-yvCS2MUBlnXm1: reading manifest e2e-14-registry-k8s-io-e2e-test-images-jessie-dnsutils-1-7-bJ-yvCS2MUBlnXm1 in virthost.ostest.test.metalkube.org:5000/localimages/local-test-image: manifest unknown; artifact err: get manifest: build image source: reading manifest e2e-14-registry-k8s-io-e2e-test-images-jessie-dnsutils-1-7-bJ-yvCS2MUBlnXm1 in virthost.ostest.test.metalkube.org:5000/localimages/local-test-image: manifest unknown

I don't doubt that Brett found a bug, but looking at the sippy failures, I think the issue causing the test failures is it that the dnsutil image is not found?

@alebedev87

Copy link
Copy Markdown
Contributor

/assign @rikatz

@rikatz

rikatz commented Mar 11, 2026

Copy link
Copy Markdown
Member

/retest-required

@rikatz

rikatz commented Mar 11, 2026

Copy link
Copy Markdown
Member

/test e2e-hypershift

1 similar comment
@rikatz

rikatz commented Mar 11, 2026

Copy link
Copy Markdown
Member

/test e2e-hypershift

@gcs278

gcs278 commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

@bentito Based on our finding that this isn't causing the CI failure, I re-opened https://issues.redhat.com/browse/OCPBUGS-78085, mind updating your commit message link and the title of this PR?

The failure in the `DNS should answer A and AAAA queries for a dual-stack service` test on metal jobs was triggered by the recent merge of PR openshift#457 (`NE-2414: Use trafficDistribution: PreferSameNode for openshift-dns Service`).

### The Root Cause
In `pkg/operator/controller/controller_dns_service.go`, the operator compares the `current` DNS Service against the `expected` (static) asset using a function called `serviceChanged()`.
To prevent unnecessary updates, the comparison correctly ignores dynamic dual-stack fields.
However, when PR openshift#457 added `trafficDistribution: PreferSameNode` to the static asset, it caused `expected` and `current` to differ on existing clusters, legitimately triggering a Service update.
When an update is triggered, the operator blindly replaces the spec (`updated.Spec = expected.Spec`) and then tries to manually preserve dynamic fields that the API server or other controllers manage. It only preserved `ClusterIP`, dropping `ClusterIPs`, `IPFamilies`, and `IPFamilyPolicy`.

### The Impact
Because `expected.Spec` (from the static YAML) has no IP families defined, the operator pushes a Service update that wipes out the secondary IPv6 Cluster IP. The `openshift-dns` service is abruptly converted back to a single-stack IPv4 service, which permanently breaks the `DNS should answer A and AAAA queries for a dual-stack service` test on metal (dual-stack) jobs.

### The Fix
This explicitly preserves the dual-stack fields (`ClusterIPs`, `IPFamilies`, `IPFamilyPolicy`) during an update. It also improves the dual-stack test assertions to verify these fields are preserved field-for-field.

Jira: https://issues.redhat.com/browse/OCPBUGS-78085
@bentito
bentito force-pushed the fix-dns-service-dual-stack branch from 04c726d to 6178e04 Compare March 11, 2026 21:39
@bentito

bentito commented Mar 11, 2026

Copy link
Copy Markdown
Contributor Author

@bentito Based on our finding that this isn't causing the CI failure, I re-opened https://issues.redhat.com/browse/OCPBUGS-78085, mind updating your commit message link and the title of this PR?

thanks! yep, done now

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/operator/controller/controller_dns_service_test.go`:
- Around line 154-158: The test's mutate closure clears
IPFamilies/IPFamilyPolicy/ClusterIPs but the expected fixture still retains
ClusterIP from the original, so update the test to also clear
expected.Spec.ClusterIP (and expected.Spec.ClusterIPs if present) so the
assertion truly validates preservation behavior; locate the mutate func and the
test's expected variable in controller_dns_service_test.go and set
expected.Spec.ClusterIP = "" (and clear expected.Spec.ClusterIPs) to match the
mutated service.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 385b1096-909c-4aac-b71f-174c9b8f3a2d

📥 Commits

Reviewing files that changed from the base of the PR and between 04c726d and 6178e04.

📒 Files selected for processing (2)
  • pkg/operator/controller/controller_dns_service.go
  • pkg/operator/controller/controller_dns_service_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/operator/controller/controller_dns_service.go

Comment on lines +154 to +158
mutate: func(service *corev1.Service) {
service.Spec.IPFamilies = nil
service.Spec.IPFamilyPolicy = nil
service.Spec.ClusterIPs = nil
service.Spec.Selector = map[string]string{"foo": "bar"}

@coderabbitai coderabbitai Bot Mar 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clear ClusterIP in the expected fixture too.

This case says the dynamic dual-stack fields are absent from expected, but Line 154-158 still leaves service.Spec.ClusterIP copied from original. That makes the ClusterIP assertion pass even if serviceChanged stops preserving that field, so the regression test is not fully load-bearing for ClusterIP.

💡 Proposed test tightening
 			mutate: func(service *corev1.Service) {
+				service.Spec.ClusterIP = ""
 				service.Spec.IPFamilies = nil
 				service.Spec.IPFamilyPolicy = nil
 				service.Spec.ClusterIPs = nil
 				service.Spec.Selector = map[string]string{"foo": "bar"}
 			},
📝 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
mutate: func(service *corev1.Service) {
service.Spec.IPFamilies = nil
service.Spec.IPFamilyPolicy = nil
service.Spec.ClusterIPs = nil
service.Spec.Selector = map[string]string{"foo": "bar"}
mutate: func(service *corev1.Service) {
service.Spec.ClusterIP = ""
service.Spec.IPFamilies = nil
service.Spec.IPFamilyPolicy = nil
service.Spec.ClusterIPs = nil
service.Spec.Selector = map[string]string{"foo": "bar"}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/operator/controller/controller_dns_service_test.go` around lines 154 -
158, The test's mutate closure clears IPFamilies/IPFamilyPolicy/ClusterIPs but
the expected fixture still retains ClusterIP from the original, so update the
test to also clear expected.Spec.ClusterIP (and expected.Spec.ClusterIPs if
present) so the assertion truly validates preservation behavior; locate the
mutate func and the test's expected variable in controller_dns_service_test.go
and set expected.Spec.ClusterIP = "" (and clear expected.Spec.ClusterIPs) to
match the mutated service.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@bentito I think code rabbit has a point here too - adding a validation for the existing ClusterIP behavior is probably worth it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@lihongan

Copy link
Copy Markdown

/test e2e-hypershift

@bentito bentito changed the title OCPBUGS-78053: Fix dual-stack service update by preserving IP families OCPBUGS-78085: Fix dual-stack service update by preserving IP families Mar 12, 2026
@openshift-ci-robot openshift-ci-robot added the jira/severity-important Referenced Jira bug's severity is important for the branch this PR is targeting. label Mar 12, 2026
@openshift-ci-robot

Copy link
Copy Markdown
Contributor

@bentito: This pull request references Jira Issue OCPBUGS-78085, which is valid.

3 validation(s) were run on this bug
  • bug is open, matching expected state (open)
  • bug target version (4.22.0) matches configured target version for branch (4.22.0)
  • bug is in the state POST, which is one of the valid states (NEW, ASSIGNED, POST)

Requesting review from QA contact:
/cc @lihongan

Details

In response to this:

The failure in the DNS should answer A and AAAA queries for a dual-stack service test on metal jobs was triggered by the recent merge of PR #457 (NE-2414: Use trafficDistribution: PreferSameNode for openshift-dns Service) in commit e2b909a5.

The Root Cause

In pkg/operator/controller/controller_dns_service.go, the operator compares the current DNS Service against the expected (static) asset using a function called serviceChanged().

To prevent unnecessary updates, the comparison correctly ignores dynamic dual-stack fields:

  	cmpopts.IgnoreFields(
  		corev1.ServiceSpec{},
  		"ClusterIP", "ClusterIPs",
  		"IPFamilies", "IPFamilyPolicy",
  	),

However, when PR #457 added trafficDistribution: PreferSameNode to the static asset, it caused expected and current to differ on existing clusters, legitimately triggering a Service update.

When an update is triggered, the operator blindly replaces the spec (updated.Spec = expected.Spec) and then tries to manually preserve dynamic fields that the API server or other controllers manage. It only preserved ClusterIP, dropping ClusterIPs, IPFamilies, and IPFamilyPolicy:

  // Preserve fields that the API, other controllers, or user may have
  // modified.
  updated.Spec.ClusterIP = current.Spec.ClusterIP

The Impact

Because expected.Spec (from the static YAML) has no IP families defined, the operator pushes a Service update that wipes out the secondary IPv6 Cluster IP. The openshift-dns service is abruptly converted back to a single-stack IPv4 service, which permanently breaks the DNS should answer A and AAAA queries for a dual-stack service test on metal (dual-stack) jobs.

The Fix

This explicitly preserves the dual-stack fields during an update:

  // Preserve fields that the API, other controllers, or user may have
  // modified.
  updated.Spec.ClusterIP = current.Spec.ClusterIP
  updated.Spec.ClusterIPs = current.Spec.ClusterIPs
  updated.Spec.IPFamilies = current.Spec.IPFamilies
  updated.Spec.IPFamilyPolicy = current.Spec.IPFamilyPolicy

Summary by CodeRabbit

  • Bug Fixes

  • DNS service updates no longer overwrite existing multi‑IP configuration: ClusterIPs, IP families, and IP family policy are preserved so dual‑stack addresses remain intact during updates.

  • Tests

  • Service-change tests expanded to allow pre‑mutation setup, apply mutations, and verify post‑update preservation of multi‑IP fields, including a case where dual‑stack fields are removed from the desired spec.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@rikatz

rikatz commented Mar 18, 2026

Copy link
Copy Markdown
Member

/test e2e-hypershift

@rikatz

rikatz commented Mar 18, 2026

Copy link
Copy Markdown
Member

/lgtm
/approve

thanks @bentito
/cc @lihongan @melvinjoseph86 @rhamini3 @ShudiLi
for QE

@openshift-ci
openshift-ci Bot requested review from ShudiLi and rhamini3 March 18, 2026 22:06
@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Mar 18, 2026
@openshift-ci

openshift-ci Bot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: rikatz

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Mar 18, 2026
@lihongan

Copy link
Copy Markdown

/verified by @lihongan

tested on dualstack cluster and looks good

$ oc get clusterversion
NAME      VERSION                                                AVAILABLE   PROGRESSING   SINCE   STATUS
version   4.22.0-0-2026-03-20-063152-test-ci-ln-p9dl2it-latest   True        False         155m    Cluster version is 4.22.0-0-2026-03-20-063152-test-ci-ln-p9dl2it-latest

# ensure service is dualstack
$ oc -n openshift-dns get svc dns-default -oyaml
<......>
spec:
  clusterIP: 172.30.0.10
  clusterIPs:
  - 172.30.0.10
  - fd65:172:16::98c0
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  - IPv6
  ipFamilyPolicy: PreferDualStack

## edit svc and remove "trafficDistribution: PreferSameNode" and it is added back and ipFamilies no change.

@openshift-ci-robot openshift-ci-robot added the verified Signifies that the PR passed pre-merge verification criteria label Mar 20, 2026
@openshift-ci-robot

Copy link
Copy Markdown
Contributor

@lihongan: This PR has been marked as verified by @lihongan.

Details

In response to this:

/verified by @lihongan

tested on dualstack cluster and looks good

$ oc get clusterversion
NAME      VERSION                                                AVAILABLE   PROGRESSING   SINCE   STATUS
version   4.22.0-0-2026-03-20-063152-test-ci-ln-p9dl2it-latest   True        False         155m    Cluster version is 4.22.0-0-2026-03-20-063152-test-ci-ln-p9dl2it-latest

# ensure service is dualstack
$ oc -n openshift-dns get svc dns-default -oyaml
<......>
spec:
 clusterIP: 172.30.0.10
 clusterIPs:
 - 172.30.0.10
 - fd65:172:16::98c0
 internalTrafficPolicy: Cluster
 ipFamilies:
 - IPv4
 - IPv6
 ipFamilyPolicy: PreferDualStack

## edit svc and remove "trafficDistribution: PreferSameNode" and it is added back and ipFamilies no change.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci

openshift-ci Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

@bentito: all tests passed!

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@openshift-merge-bot
openshift-merge-bot Bot merged commit 3d21411 into openshift:master Mar 20, 2026
13 checks passed
@openshift-ci-robot

Copy link
Copy Markdown
Contributor

@bentito: Jira Issue Verification Checks: Jira Issue OCPBUGS-78085
✔️ This pull request was pre-merge verified.
✔️ All associated pull requests have merged.
✔️ All associated, merged pull requests were pre-merge verified.

Jira Issue OCPBUGS-78085 has been moved to the MODIFIED state and will move to the VERIFIED state when the change is available in an accepted nightly payload. 🕓

Details

In response to this:

The failure in the DNS should answer A and AAAA queries for a dual-stack service test on metal jobs was triggered by the recent merge of PR #457 (NE-2414: Use trafficDistribution: PreferSameNode for openshift-dns Service) in commit e2b909a5.

The Root Cause

In pkg/operator/controller/controller_dns_service.go, the operator compares the current DNS Service against the expected (static) asset using a function called serviceChanged().

To prevent unnecessary updates, the comparison correctly ignores dynamic dual-stack fields:

  	cmpopts.IgnoreFields(
  		corev1.ServiceSpec{},
  		"ClusterIP", "ClusterIPs",
  		"IPFamilies", "IPFamilyPolicy",
  	),

However, when PR #457 added trafficDistribution: PreferSameNode to the static asset, it caused expected and current to differ on existing clusters, legitimately triggering a Service update.

When an update is triggered, the operator blindly replaces the spec (updated.Spec = expected.Spec) and then tries to manually preserve dynamic fields that the API server or other controllers manage. It only preserved ClusterIP, dropping ClusterIPs, IPFamilies, and IPFamilyPolicy:

  // Preserve fields that the API, other controllers, or user may have
  // modified.
  updated.Spec.ClusterIP = current.Spec.ClusterIP

The Impact

Because expected.Spec (from the static YAML) has no IP families defined, the operator pushes a Service update that wipes out the secondary IPv6 Cluster IP. The openshift-dns service is abruptly converted back to a single-stack IPv4 service, which permanently breaks the DNS should answer A and AAAA queries for a dual-stack service test on metal (dual-stack) jobs.

The Fix

This explicitly preserves the dual-stack fields during an update:

  // Preserve fields that the API, other controllers, or user may have
  // modified.
  updated.Spec.ClusterIP = current.Spec.ClusterIP
  updated.Spec.ClusterIPs = current.Spec.ClusterIPs
  updated.Spec.IPFamilies = current.Spec.IPFamilies
  updated.Spec.IPFamilyPolicy = current.Spec.IPFamilyPolicy

Summary by CodeRabbit

  • Bug Fixes

  • DNS service updates no longer overwrite existing multi‑IP configuration: ClusterIPs, IP families, and IP family policy are preserved so dual‑stack addresses remain intact during updates.

  • Tests

  • Service-change tests expanded to allow pre‑mutation setup, apply mutations, and verify post‑update preservation of multi‑IP fields, including a case where dual‑stack fields are removed from the desired spec.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@lihongan

Copy link
Copy Markdown

Fix included in accepted release 4.22.0-0.nightly-2026-03-23-022245

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

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. jira/severity-important Referenced Jira bug's severity is important for the branch this PR is targeting. jira/valid-bug Indicates that a referenced Jira bug is valid for the branch this PR is targeting. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. lgtm Indicates that a PR is ready to be merged. verified Signifies that the PR passed pre-merge verification criteria

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants