OCPBUGS-77307: Generate KubeVirt nmstate network config conditionally - #8365
OCPBUGS-77307: Generate KubeVirt nmstate network config conditionally#8365qinqon wants to merge 3 commits into
Conversation
|
Pipeline controller notification For optional jobs, comment This repository is configured in: LGTM mode |
|
@qinqon: This pull request references Jira Issue OCPBUGS-77307, which is invalid:
Comment The bug has been updated to refer to the pull request using the external bug tracker. DetailsIn response to this:
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. |
|
Skipping CI for Draft Pull Request. |
📝 WalkthroughWalkthroughThe PR adds platform-specific MachineConfig generation for KubeVirt to the NodePool config assembly. Sequence Diagram(s)sequenceDiagram
participant NodePool
participant ConfigGen as generateMCORawConfig
participant PlatformGen as kubevirtPlatformConfig
participant MachineConfig
participant MCO
NodePool->>ConfigGen: request raw MCO config
ConfigGen->>PlatformGen: getPlatformConfigs(nodePool)
alt Platform is KubeVirt and AttachDefaultNetwork true/nil
PlatformGen->>PlatformGen: build Ignition with nmstate files
PlatformGen->>MachineConfig: serialize MachineConfig (network)
else Platform is KubeVirt and multus primary
PlatformGen->>PlatformGen: build no-op nmstate override
PlatformGen->>MachineConfig: serialize MachineConfig (override)
else Other platform
PlatformGen-->>ConfigGen: return empty
end
PlatformGen-->>ConfigGen: return ConfigMap/MachineConfig YAML
ConfigGen->>MachineConfig: append platform config
ConfigGen-->>NodePool: return combined raw config
NodePool->>MCO: apply MachineConfig YAML
🚥 Pre-merge checks | ✅ 11 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (11 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #8365 +/- ##
==========================================
+ Coverage 44.24% 44.29% +0.05%
==========================================
Files 773 774 +1
Lines 96512 96624 +112
==========================================
+ Hits 42702 42802 +100
- Misses 50861 50869 +8
- Partials 2949 2953 +4
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
10f4aad to
4cdc740
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
hypershift-operator/controllers/nodepool/kubevirt/network.go (1)
225-230: Inconsistent YAML serialization approach.
GenerateNetworkMachineConfigusesapi.CompatibleYAMLEncode(line 111) while this function usesapi.YamlSerializer.Encodedirectly. This inconsistency could lead to subtle differences in output format and potentially affect hash stability.♻️ Suggested fix to use consistent serialization
- buf := &bytes.Buffer{} - if err := api.YamlSerializer.Encode(mc, buf); err != nil { + encoded, err := api.CompatibleYAMLEncode(mc, api.YamlSerializer) + if err != nil { return "", fmt.Errorf("failed to serialize kubevirt network override machine config: %w", err) } - return buf.String(), nil + return string(encoded), nil🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hypershift-operator/controllers/nodepool/kubevirt/network.go` around lines 225 - 230, The serialization in this function uses api.YamlSerializer.Encode directly which is inconsistent with GenerateNetworkMachineConfig that uses api.CompatibleYAMLEncode; update this function to call api.CompatibleYAMLEncode when encoding the machine config (mc) into the buffer (buf) so the output format and hash stability match the other code path, and propagate any returned error in the same manner as the existing fmt.Errorf wrapping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@hypershift-operator/controllers/nodepool/kubevirt/network.go`:
- Around line 225-230: The serialization in this function uses
api.YamlSerializer.Encode directly which is inconsistent with
GenerateNetworkMachineConfig that uses api.CompatibleYAMLEncode; update this
function to call api.CompatibleYAMLEncode when encoding the machine config (mc)
into the buffer (buf) so the output format and hash stability match the other
code path, and propagate any returned error in the same manner as the existing
fmt.Errorf wrapping.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Central YAML (inherited)
Review profile: CHILL
Plan: Enterprise
Run ID: 39e42667-f313-4562-94f9-4339d98c2375
📒 Files selected for processing (3)
hypershift-operator/controllers/nodepool/config.gohypershift-operator/controllers/nodepool/kubevirt/network.gohypershift-operator/controllers/nodepool/kubevirt/network_test.go
There was a problem hiding this comment.
🧹 Nitpick comments (1)
hypershift-operator/controllers/nodepool/kubevirt/network.go (1)
225-228: Inconsistent YAML serialization method.
GenerateNetworkMachineConfig(line 111) usesapi.CompatibleYAMLEncode(mc, api.YamlSerializer)while this function usesapi.YamlSerializer.Encode(mc, buf)directly. Both functions generate the same object type (MachineConfig) and should use consistent serialization to ensure identical YAML formatting behavior.♻️ Proposed fix for consistency
- buf := &bytes.Buffer{} - if err := api.YamlSerializer.Encode(mc, buf); err != nil { + encoded, err := api.CompatibleYAMLEncode(mc, api.YamlSerializer) + if err != nil { return "", fmt.Errorf("failed to serialize kubevirt network override machine config: %w", err) } - return buf.String(), nil + return string(encoded), nilAfter applying this change, the
bytesimport on line 4 can be removed if it's no longer used elsewhere in the file.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hypershift-operator/controllers/nodepool/kubevirt/network.go` around lines 225 - 228, The YAML serialization in this function is inconsistent with GenerateNetworkMachineConfig: replace the manual bytes.Buffer + api.YamlSerializer.Encode(mc, buf) pattern with the same helper call used elsewhere — api.CompatibleYAMLEncode(mc, api.YamlSerializer) — so mc (the MachineConfig) is encoded with the same formatting behavior; remove the now-unused bytes import if it is no longer referenced after the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@hypershift-operator/controllers/nodepool/kubevirt/network.go`:
- Around line 225-228: The YAML serialization in this function is inconsistent
with GenerateNetworkMachineConfig: replace the manual bytes.Buffer +
api.YamlSerializer.Encode(mc, buf) pattern with the same helper call used
elsewhere — api.CompatibleYAMLEncode(mc, api.YamlSerializer) — so mc (the
MachineConfig) is encoded with the same formatting behavior; remove the
now-unused bytes import if it is no longer referenced after the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Central YAML (inherited)
Review profile: CHILL
Plan: Enterprise
Run ID: 645f2beb-0f31-4789-bd8f-fe3ec5e1aa9e
📒 Files selected for processing (3)
hypershift-operator/controllers/nodepool/config.gohypershift-operator/controllers/nodepool/kubevirt/network.gohypershift-operator/controllers/nodepool/kubevirt/network_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
- hypershift-operator/controllers/nodepool/kubevirt/network_test.go
4cdc740 to
efa02a6
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
hypershift-operator/controllers/nodepool/kubevirt/network_test.go (1)
13-26: Decode the generated object structurally instead of scanning YAML lines.This helper is tied to the current YAML/data-URL formatting, so harmless quoting or wrapping changes can fail the tests even when the
MachineConfigis still valid. Parsing the YAML intoMachineConfig, then decodingSpec.Config.Raw, would make these assertions much less brittle.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hypershift-operator/controllers/nodepool/kubevirt/network_test.go` around lines 13 - 26, The test helper decodeBase64Content is brittle because it scans YAML lines; replace its implementation to parse the YAML into a MachineConfig object and return the config payload from Spec.Config.Raw instead of string-scanning. Specifically, in decodeBase64Content: unmarshal the config YAML into the machineconfigv1.MachineConfig type (or a minimal struct with Spec.Config as a runtime.RawExtension), then return string(mc.Spec.Config.Raw) (or the Raw field) so the test reads the structured Spec.Config.Raw payload; add the necessary imports for the MachineConfig type and YAML unmarshalling.hypershift-operator/controllers/nodepool/kubevirt/network.go (1)
83-116: Extract the shared MachineConfig assembly path.Lines 83-116 and Lines 198-231 duplicate the same ignition serialization,
MachineConfigconstruction, label defaulting, and YAML encoding. Pulling that into one helper will keep the default and override branches from drifting.Also applies to: 198-231
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hypershift-operator/controllers/nodepool/kubevirt/network.go` around lines 83 - 116, Duplicate logic that serializes an ignition config, constructs a MachineConfig (including setting Name via kubevirtNetworkMachineConfigName), calls ignition.SetMachineConfigLabels, sets Spec.Config.Raw, APIVersion and Kind, and YAML-encodes it should be extracted into a single helper (e.g., buildKubevirtNetworkMachineConfig or encodeMachineConfigFromIgnition) that accepts the ignition.Config or the serialized bytes and returns the encoded YAML string (or error). Replace the duplicated blocks (the block using serializeIgnitionConfig, mcfgv1.MachineConfig, ignition.SetMachineConfigLabels, and api.CompatibleYAMLEncode) with calls to that helper in both places; ensure the helper preserves setting mc.Spec.Config.Raw = serializedConfig, mc.ObjectMeta.Name = kubevirtNetworkMachineConfigName, mc.APIVersion = mcfgv1.SchemeGroupVersion.String(), mc.Kind = "MachineConfig", and forwards errors from serializeIgnitionConfig and api.CompatibleYAMLEncode.hypershift-operator/controllers/nodepool/config.go (1)
162-167: This also changes the rollout hash for default-network KubeVirt pools.Because Line 121 and Line 129 hash
cg.mcoRawConfig, appending a platformMachineConfighere will force a rollout for every KubeVirt NodePool, not just the multus-primary ones. If that churn is expected, it would be good to call it out in the upgrade plan/release notes; otherwise this needs version gating around the paired MCO change.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hypershift-operator/controllers/nodepool/config.go` around lines 162 - 167, Appending platform-specific MachineConfigs unconditionally causes cg.mcoRawConfig-based rollout hashes (see uses at cg.mcoRawConfig) to change for all KubeVirt NodePools; restrict this so only multus-primary pools cause the append or add version gating around the paired MCO change. Modify the code around cg.getPlatformConfigs() and the call site where configs are appended so you either (a) early-return or skip calling cg.getPlatformConfigs()/appending platformConfigs unless the NodePool is the multus-primary type (check the NodePool spec/labels), or (b) guard the append behind a feature/version flag tied to the MCO rollout change, ensuring cg.mcoRawConfig is not mutated or included in the rollout hash for default-network pools.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@hypershift-operator/controllers/nodepool/kubevirt/network.go`:
- Around line 66-73: Add a nil guard at the start of exported helpers so they
return the neutral result instead of panicking; for example, in
GenerateNetworkMachineConfig check if nodePool == nil and immediately return "",
nil, and apply the same pattern to the other exported helper functions in this
file (the ones around lines 156-164 and 181-189) so they return their respective
neutral values (empty string or false) when nodePool is nil before dereferencing
nodePool.Spec.
---
Nitpick comments:
In `@hypershift-operator/controllers/nodepool/config.go`:
- Around line 162-167: Appending platform-specific MachineConfigs
unconditionally causes cg.mcoRawConfig-based rollout hashes (see uses at
cg.mcoRawConfig) to change for all KubeVirt NodePools; restrict this so only
multus-primary pools cause the append or add version gating around the paired
MCO change. Modify the code around cg.getPlatformConfigs() and the call site
where configs are appended so you either (a) early-return or skip calling
cg.getPlatformConfigs()/appending platformConfigs unless the NodePool is the
multus-primary type (check the NodePool spec/labels), or (b) guard the append
behind a feature/version flag tied to the MCO rollout change, ensuring
cg.mcoRawConfig is not mutated or included in the rollout hash for
default-network pools.
In `@hypershift-operator/controllers/nodepool/kubevirt/network_test.go`:
- Around line 13-26: The test helper decodeBase64Content is brittle because it
scans YAML lines; replace its implementation to parse the YAML into a
MachineConfig object and return the config payload from Spec.Config.Raw instead
of string-scanning. Specifically, in decodeBase64Content: unmarshal the config
YAML into the machineconfigv1.MachineConfig type (or a minimal struct with
Spec.Config as a runtime.RawExtension), then return string(mc.Spec.Config.Raw)
(or the Raw field) so the test reads the structured Spec.Config.Raw payload; add
the necessary imports for the MachineConfig type and YAML unmarshalling.
In `@hypershift-operator/controllers/nodepool/kubevirt/network.go`:
- Around line 83-116: Duplicate logic that serializes an ignition config,
constructs a MachineConfig (including setting Name via
kubevirtNetworkMachineConfigName), calls ignition.SetMachineConfigLabels, sets
Spec.Config.Raw, APIVersion and Kind, and YAML-encodes it should be extracted
into a single helper (e.g., buildKubevirtNetworkMachineConfig or
encodeMachineConfigFromIgnition) that accepts the ignition.Config or the
serialized bytes and returns the encoded YAML string (or error). Replace the
duplicated blocks (the block using serializeIgnitionConfig,
mcfgv1.MachineConfig, ignition.SetMachineConfigLabels, and
api.CompatibleYAMLEncode) with calls to that helper in both places; ensure the
helper preserves setting mc.Spec.Config.Raw = serializedConfig,
mc.ObjectMeta.Name = kubevirtNetworkMachineConfigName, mc.APIVersion =
mcfgv1.SchemeGroupVersion.String(), mc.Kind = "MachineConfig", and forwards
errors from serializeIgnitionConfig and api.CompatibleYAMLEncode.
🪄 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: Repository YAML (base), Central YAML (inherited)
Review profile: CHILL
Plan: Enterprise
Run ID: 79da125b-65ec-4797-9598-6989684bd0d1
📒 Files selected for processing (3)
hypershift-operator/controllers/nodepool/config.gohypershift-operator/controllers/nodepool/kubevirt/network.gohypershift-operator/controllers/nodepool/kubevirt/network_test.go
f46757b to
f3f31bf
Compare
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: qinqon The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
d40cfff to
aa28671
Compare
b82d83d to
e103f0e
Compare
65c4090 to
2885536
Compare
|
/test e2e-kubevirt-aws-ovn |
|
/retest |
|
/test e2e-kubevirt-aws-ovn |
|
@qinqon: The following test failed, say
Full PR test history. Your PR dashboard. DetailsInstructions 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. |
|
The PR's changes to Now I have enough evidence to produce the final report: Test Failure Analysis CompleteJob Information
Test Failure AnalysisErrorSummaryTwo pre-existing KubeVirt platform flaky tests failed due to infrastructure resource constraints — neither failure is related to the PR #8365 changes. The PR modifies KubeVirt nmstate network config generation ( Root CauseBoth failures stem from KubeVirt VM scheduling resource exhaustion on the management cluster, not from any code change in PR #8365: Failure 1 — TestNodePoolReplaceUpgrade: During a replace upgrade, the test creates a new NodePool ( Failure 2 — TestAdditionalTrustBundlePropagation: After updating the hosted cluster with an additional trust bundle, the NodePool entered Why these are unrelated to PR #8365:
Recommendations
Evidence
|
15fdd7a to
98cc0b9
Compare
| } | ||
|
|
||
| // Generate platform-specific MachineConfigs. | ||
| platformConfigs, err := cg.getPlatformConfigs() |
There was a problem hiding this comment.
this would cause a fleet wide nodepool rollout as you upgrade the HO
There was a problem hiding this comment.
We are going to need to change MC to fix the bug, maybe we can make it opt-in somehow or detect the specific scenario (hosted cluster with non cluster default network and ipv6)
There was a problem hiding this comment.
Reworked to address this — the HyperShift operator no longer generates anything for default-network NodePools: the MCO templates remain the source of truth, so the raw config and hash are byte-identical to before and upgrading the HO does not trigger any rollout. TestGetPlatformConfigs now asserts this hash-neutrality explicitly.
The override MachineConfig is only emitted for NodePools using multus as primary network and whose HostedCluster networking includes IPv6 — exactly the broken population, where the rollout is the fix itself. IPv4-only multus clusters are also left untouched (asymptomatic, and since networking CIDRs are immutable they can never become affected).
There was a problem hiding this comment.
@enxebre I have limit it to add-default-network=false and ipv6 do make sense now ?
An alternative is to just document this so customers change their nodepoos with machine config deleting those files.
98cc0b9 to
de2d742
Compare
|
@qinqon: This pull request references Jira Issue OCPBUGS-77307, which is valid. The bug has been moved to the POST state. 3 validation(s) were run on this bug
No GitHub users were found matching the public email listed for the QA contact in Jira (yli2@redhat.com), skipping review request. DetailsIn response to this:
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. |
|
@qinqon: This pull request references Jira Issue OCPBUGS-77307, which is valid. 3 validation(s) were run on this bug
No GitHub users were found matching the public email listed for the QA contact in Jira (yli2@redhat.com), skipping review request. DetailsIn response to this:
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. |
… IPv6 The MCO templates unconditionally render nmstate files that disable IPv6 autoconf and route IPv6 through KubeVirt's ARP proxy gateway (fe80::1). That configuration is only correct for the default pod network, where OVN-Kubernetes assigns IPv6 via DHCPv6 stateful. When a NodePool uses multus as its primary network (AttachDefaultNetwork=false), it breaks SLAAC and nodes never get IPv6 addresses on dual-stack clusters. Generate an override MachineConfig that replaces the MCO-rendered nmstate files with no-op content, restoring standard IPv6 auto-configuration. The override is scoped to NodePools using multus as primary network on clusters whose networking includes IPv6: - Default-network NodePools get nothing: the MCO templates remain the source of truth, the NodePool config hash is unchanged and upgrading the HyperShift operator does not trigger a fleet-wide rollout. - IPv4-only multus NodePools get nothing either: the stale files are asymptomatic there, and since cluster networking CIDRs are immutable those clusters can never become affected. - Multus NodePools on IPv6-enabled clusters get the override; the resulting NodePool rollout is the bug fix itself. Co-Authored-By: Claude Opus 4 (claude-opus-4-6) <noreply@anthropic.com> Assisted-By: Claude Opus 4.8 <noreply@anthropic.com> Assisted-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Enrique Llorente <ellorent@redhat.com>
…tack On KubeVirt, CNO requires worker nodes to probe the network MTU before deploying its operands (ovnkube-control-plane, network-node-identity, multus-admission-controller). Without at least one worker node, these deployments are never created, causing the CNO RolloutComplete condition to stay False and controlPlaneVersion to remain Partial indefinitely. This is the same issue OpenStack already works around by setting NodePoolReplicas=1. Apply the same workaround for KubeVirt. Co-Authored-By: Claude Opus 4 (claude-opus-4-6) <noreply@anthropic.com> Signed-off-by: Enrique Llorente <ellorent@redhat.com> Assisted-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Enrique Llorente <ellorent@redhat.com>
…net tests Extend KubeVirtAdvancedMultinetTest and KubeVirtMultinetTest to verify the nmstate network configuration nodes end up with, depending on the AttachDefaultNetwork setting and the cluster IP family. When the default network is attached (KubeVirtMultinetTest), a privileged DaemonSet checks via nmstatectl that autoconf: false IS present, confirming the MCO-rendered nmstate configuration is applied. When AttachDefaultNetwork=false (KubeVirtAdvancedMultinetTest) the assertion depends on the HostedCluster networking: on clusters with IPv6 the override MachineConfig must neutralize the MCO-rendered config, so autoconf: false must NOT be present; on IPv4-only clusters no override is generated on purpose (to avoid NodePool rollouts on operator upgrades), so the MCO-rendered config must still be applied. The negative (multus+IPv6) probe captures nmstatectl output before grepping so a transient command failure fails the probe (the pod stays NotReady and the test keeps waiting) instead of being misread as "config absent". Both tests reuse existing e2e infrastructure: CorrelateDaemonSet for node targeting and eventuallyDaemonSetRollsOut for readiness waiting. Co-Authored-By: Claude Opus 4 (claude-opus-4-6) <noreply@anthropic.com> Assisted-By: Claude Opus 4.8 <noreply@anthropic.com> Assisted-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Enrique Llorente <ellorent@redhat.com>
de2d742 to
74d845f
Compare
|
Stale PRs are closed after 21d of inactivity. If this PR is still relevant, comment to refresh it or remove the stale label. If this PR is safe to close now please do so with /lifecycle stale |
|
superseed by #9381 |
|
@qinqon: This pull request references Jira Issue OCPBUGS-77307. The bug has been updated to no longer refer to the pull request using the external bug tracker. DetailsIn response to this:
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. |
What this PR does / why we need it:
The MCO templates unconditionally render nmstate configuration files that disable IPv6 autoconf and set up the
fe80::1ARP proxy gateway route. This is correct for the default pod network, where OVN-Kubernetes assigns IPv6 via DHCPv6 stateful. However, when a KubeVirt NodePool uses multus as the primary network (AttachDefaultNetwork=false), these configurations break SLAAC and prevent nodes from getting IPv6 addresses in dual-stack setups.This PR makes the HyperShift nodepool controller generate an override MachineConfig that replaces the MCO-rendered nmstate files with no-op content, restoring standard IPv6 auto-configuration (SLAAC). The override is scoped to exactly the broken population — NodePools using multus as primary network on clusters whose networking includes IPv6:
Which issue(s) this PR fixes:
Fixes https://issues.redhat.com/browse/OCPBUGS-77307
Special notes for your reviewer:
No MCO changes are required: instead of moving ownership of the nmstate configuration into HyperShift (which would have changed the NodePool config hash for every KubeVirt NodePool and caused a fleet-wide rollout on operator upgrade), HyperShift only neutralizes the MCO-rendered files where they are wrong.
Unit tests in
config_test.go(TestGetPlatformConfigs) assert that no platform config is generated for default-network and IPv4-only multus NodePools, guaranteeing the config hash — and therefore the fleet — is untouched by an operator upgrade.The e2e
KubeVirtAdvancedMultinetTestassertion is IP-family aware: on IPv4-only CI lanes it verifies the override is correctly not generated (MCO config still applied); on IPv6-enabled clusters it verifies the override neutralizes the MCO config.Test coverage note: the KubeVirt CI lane (
e2e-kubevirt-aws-ovn-reduced) is IPv4-only, so CI exercises the gating logic and the no-rollout guarantees (unit + e2e), but not the positive path end-to-end (override applied on nodes, SLAAC working). The positive path is covered by unit tests on the generated MachineConfig content and will be verified on a dual-stack environment as part of the OCPBUGS-77307 QE verification.Checklist: