Skip to content

MTV-5674 | Add per-network guestIPConfig to NetworkMap#6916

Open
Hazanel wants to merge 1 commit into
kubev2v:mainfrom
Hazanel:mtv-5268-guest-ip-config
Open

MTV-5674 | Add per-network guestIPConfig to NetworkMap#6916
Hazanel wants to merge 1 commit into
kubev2v:mainfrom
Hazanel:mtv-5268-guest-ip-config

Conversation

@Hazanel

@Hazanel Hazanel commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Introduce a guestIPConfig field (preserve/dhcp/none) on NetworkPair entries in the NetworkMap CRD, allowing per-NIC control over whether static IPs are preserved during migration. This overrides the plan-level preserveStaticIPs flag on a per-network basis.

Changes:

  • Add GuestIPConfig type and field to NetworkPair in mapping.go
  • Add validation rejecting guestIPConfig on ignored destinations
  • Add ResolveNICModes() and HasPreserveMode() helpers in base package
  • Modify vSphere and HyperV builders to filter mapMacStaticIps by mode

Ref: https://redhat.atlassian.net/browse/MTV-5674
Resolves: MTV-5674

@Hazanel

Hazanel commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Hey @coderabbitai, please take a look

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get PR details
gh pr view 6916 --repo kubev2v/forklift --json title,body,files,additions,deletions,commits

echo "=== PR Files Changed ==="
gh pr view 6916 --repo kubev2v/forklift --json files --jq '.files[].path'

Length of output: 4061


🏁 Script executed:

#!/bin/bash
# Get the PR diff
gh pr diff 6916 --repo kubev2v/forklift

Length of output: 49452



@Hazanel Sure, happy to review! Here's my analysis of PR #6916.


Review: MTV-5674 | Add per-network guestIPConfig to NetworkMap

The overall design is clean and well-tested. A few things are worth addressing:


🔴 High: nicRefsFromVM is duplicated in two adapters

The function is defined twice with identical logic, only operating on different model types:

pkg/controller/plan/adapter/hyperv/builder.go:

func nicRefsFromVM(vm *model.VM) []planbase.NICRef {
    nics := make([]planbase.NICRef, len(vm.NICs))
    for i, nic := range vm.NICs {
        nics[i] = planbase.NICRef{MAC: nic.MAC, NetworkID: nic.Network.ID}
    }
    return nics
}

pkg/controller/plan/adapter/vsphere/builder.go:

func nicRefsFromVM(vm *model.VM) []planbase.NICRef {
    nics := make([]planbase.NICRef, len(vm.NICs))
    for i, nic := range vm.NICs {
        nics[i] = planbase.NICRef{MAC: nic.MAC, NetworkID: nic.Network.ID}
    }
    return nics
}

Since both source model types expose .NICs[i].MAC and .NICs[i].Network.ID, these could be unified via an interface or generics in the base package. At minimum, the duplication should be noted so it's tracked for a follow-up.


🟡 Medium: Validation tests duplicate the validator logic instead of testing it

In pkg/controller/map/network/network_test.go, the GuestIPConfig validation Describe block manually re-implements the for-loop from validateDestination rather than calling the actual function:

// Simulate what validateDestination does for the guestIPConfig check
var guestIPConfigOnIgnored []string
for _, entry := range nm.Spec.Map {
    if entry.Destination.Type == Ignored && entry.GuestIPConfig != "" {
        guestIPConfigOnIgnored = append(guestIPConfigOnIgnored, entry.Source.String())
    }
}

If validateDestination changes its logic, these tests won't catch it. The existing tests in the file (e.g. DestinationNetworkNotValid tests) seem to call the reconciler/handler directly. Could these tests be restructured to call the actual validator?


🟡 Medium: dhcp mode has no additional wiring

The PR introduces three modes — preserve, dhcp, and none — but dhcp and none are both treated the same way (excluded from the static-IP preserve path). There's no code that emits a V2V_dhcp environment variable or similar signal to the migration pod for NICs in dhcp mode.

Is dhcp intentionally just "don't preserve, let the guest figure it out" for now, with active DHCP configuration wiring to follow in a later PR? If so, a comment in the code (and/or in the enum godoc) would help future readers understand that dhcpnone semantically even though they behave identically today.


🟡 Medium: guestIPConfig: preserve on a pod destination type

The validator only rejects guestIPConfig on ignored destinations. However, pod (the default pod network) also seems like an unusual candidate for preserve — pod-network IPs are typically managed by CNI and the guest IP doesn't map directly. Is this intentional, or should guestIPConfig: preserve on a pod destination also be warned about or rejected?


🟢 Low: Duplicate test coverage across packages

ResolveNICModes and HasPreserveMode are tested identically in:

  • pkg/controller/plan/adapter/base/network_test.go
  • pkg/controller/plan/adapter/vsphere/builder_test.go
  • pkg/controller/plan/adapter/hyperv/builder_test.go

The vsphere/hyperv copies add no additional coverage over the base tests. Consider removing them from the adapter-specific test files to reduce maintenance overhead.


✅ Things that look good

  • nil-safety: reading from a nil modeByMAC map in Go is safe (returns "", false), so the backward-compatibility path when modeByMAC == nil (include all NICs) works correctly without an explicit nil check.
  • CRD enum constraint (+kubebuilder:validation:Enum=preserve;dhcp;none) is correctly placed on the type.
  • guestIPConfig,omitempty ensures the field doesn't appear in serialized objects when unset — correct.
  • AllocateNetwork / pool reuse properly handles 1:N network mappings in ResolveNICModes.
  • r.Context.Map.Network nil is safely handled in ResolveNICModes.
  • Test coverage for base helpers is thorough.

🧠 Learnings used
Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-30T06:35:57.763Z
Learning: Repo: kubev2v/forklift PR: 5655 — In `pkg/controller/plan/adapter/vsphere/validator.go`, `StaticIPs()` intentionally returns `(false, nil)` when `vm.GuestNetworks` is empty (instead of the old `(true, nil)`). This is a deliberate behavioral change to align with HyperV adapter behavior and surface the real problem: if preserveStaticIPs is enabled but VMware Tools isn't reporting guest networks, the user should be warned.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-30T06:35:57.763Z
Learning: Repo: kubev2v/forklift PR: 5655 — `ExcludeNICsFromStaticIPs` (on `plan.VM`) is silently ignored for oVirt, OVF-base, and OpenStack providers by design. This is consistent with `PreserveStaticIPs` itself being a no-op for those providers: `SupportsPreserveStaticIps()` gates the Pod Network warning block, and `StaticIPs()` always returns `(true, nil)` for those adapters, so no `VMMissingGuestIPs` warning is ever generated. No validation warning for non-supporting providers is intentional.

Learnt from: mnecas
Repo: kubev2v/forklift PR: 6015
File: pkg/controller/plan/kubevirt.go:747-749
Timestamp: 2026-04-28T02:28:15.946Z
Learning: In `pkg/controller/plan/kubevirt.go` (kubev2v/forklift PR `#6015`), `NetAppShiftPVCs` intentionally uses migration-scoped labels (`r.vmLabels(vm.Ref)` which includes `kMigration`) when building PVCs for NetApp Shift StorageClasses. This means a new PVC is provisioned on each migration retry rather than reusing an existing one. This is a deliberate design decision by maintainer mnecas and should not be flagged as an idempotency issue.

@codecov-commenter

codecov-commenter commented Jun 4, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 41.25000% with 47 lines in your changes missing coverage. Please review.
✅ Project coverage is 10.75%. Comparing base (f1fe5d0) to head (bbea2f2).
⚠️ Report is 2589 commits behind head on main.

Files with missing lines Patch % Lines
pkg/controller/map/network/validation.go 0.00% 19 Missing ⚠️
pkg/controller/plan/adapter/hyperv/builder.go 21.42% 11 Missing ⚠️
pkg/controller/plan/adapter/vsphere/builder.go 25.00% 9 Missing ⚠️
pkg/controller/plan/adapter/base/network.go 77.14% 6 Missing and 2 partials ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6916      +/-   ##
==========================================
- Coverage   15.45%   10.75%   -4.70%     
==========================================
  Files         112      521     +409     
  Lines       23377    62120   +38743     
==========================================
+ Hits         3613     6683    +3070     
- Misses      19479    54868   +35389     
- Partials      285      569     +284     
Flag Coverage Δ
unittests 10.75% <41.25%> (-4.70%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR extends the NetworkMap CRD to support per-network guest IP configuration, allowing fine-grained control over static IP preservation for individual mapped networks. The implementation adds guestIPConfig enum field (preserve/dhcp/none) to network destinations, introduces mode-resolution logic, and integrates the feature into Hyper-V and vSphere migration builders.

Changes

Guest IP Configuration Support for Network Mappings

Layer / File(s) Summary
CRD schema and API type definition
operator/.downstream_manifests, operator/.upstream_manifests, operator/config/crd/bases/forklift.konveyor.io_networkmaps.yaml, pkg/apis/forklift/v1beta1/mapping.go
CRD manifests and base YAML schema extended with guestIPConfig field (enum: preserve, dhcp, none) under network destination. Go API introduces GuestIPConfig string enum with three constants and updates NetworkPair struct with optional guestIPConfig field.
Network mapping validation and condition handling
pkg/controller/map/network/validation.go, pkg/controller/map/network/network_test.go
Validation rejects guestIPConfig on ignored destinations, emits GuestIPConfigNotValid condition with offending source entries. Tests verify condition setting for ignored vs allowed destination types and FindAllNetworks field preservation.
NIC mode resolution and preserve detection
pkg/controller/plan/adapter/base/network.go, pkg/controller/plan/adapter/base/network_test.go
NICRef struct and ResolveNICModes function build MAC-to-mode map by matching NICs against NetworkMap with NADPool allocation, using guestIPConfig or plan-level preserveStaticIPs fallback. HasPreserveMode detects preserve mode presence. Tests cover nil/empty maps, overrides, allocation, and mixed destination types.
Hyper-V builder integration with NIC mode resolution
pkg/controller/plan/adapter/hyperv/builder.go, pkg/controller/plan/adapter/hyperv/builder_test.go, pkg/controller/plan/adapter/hyperv/hyperv_suite_test.go
PodEnvironment computes mode map via ResolveNICModes, gates static IP configuration on HasPreserveMode, and uses mode-aware mapMacStaticIps and hasMultipleStaticIPsPerNIC. nicRefsFromVM converts VM NICs to NICRef inputs. Test suite covers all mode scenarios, allocation, and backward compatibility.
vSphere builder integration with NIC mode resolution
pkg/controller/plan/adapter/vsphere/builder.go, pkg/controller/plan/adapter/vsphere/builder_test.go
PodEnvironment mirrors Hyper-V integration: computes modeByMAC, gates on HasPreserveMode, filters static IP generation and multi-IP counting by resolved mode. nicRefsFromVM extracts identities. Tests validate filtering, fallback rules, overrides, and mixed scenarios.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • kubev2v/forklift#6252: The main PR's new per-NIC ResolveNICModes logic (used by Hyper-V/vSphere static IP handling) builds on the same NIC-to-network mapping resolution layer that the retrieved PR refactors/extends with NADPool and multi-match lookup helpers for duplicate source networks.

Suggested reviewers

  • yaacov
  • mnecas
  • mrnold
  • solenoci

Poem

🐰 A rabbit hops through networks with pride,
Per-NIC IP modes now guide the tide!
Preserve, DHCP, none—choices galore,
For vSphere and Hyper-V, features to adore,
The mapping flows smooth, both swift and wide. 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% 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 clearly and concisely summarizes the main change: adding a per-network guestIPConfig field to NetworkMap.
Description check ✅ Passed The description is directly related to the changeset, providing context and listing the key changes across multiple files.
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.

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

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

@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

🧹 Nitpick comments (3)
pkg/controller/plan/adapter/vsphere/builder.go (1)

327-365: ⚡ Quick win

Refactor mapMacStaticIps to reduce complexity in this hot path.

Line 327 is now above the configured complexity threshold (25 vs 15). Extract NIC eligibility and gateway resolution into small helpers to keep this path maintainable and avoid recurring quality-gate noise.

Refactor sketch
 func (r *Builder) mapMacStaticIps(vm *model.VM, modeByMAC map[string]string) (ipMap string, err error) {
-  // existing complex loop...
+  isWindowsFlag := isWindows(vm)
+  var configurations []string
+  for _, guestNetwork := range vm.GuestNetworks {
+    if !shouldIncludeNICForPreserve(guestNetwork.MAC, modeByMAC) {
+      continue
+    }
+    if isWindowsFlag && guestNetwork.Origin != string(types.NetIpConfigInfoIpAddressOriginManual) {
+      continue
+    }
+    gateway := findMatchingGateway(vm.GuestIpStacks, guestNetwork)
+    configurations = append(configurations, formatStaticIPConfig(guestNetwork, gateway))
+  }
+  return strings.Join(configurations, "_"), nil
 }

As per coding guidelines "Clarity and Simplicity: Write idiomatic Go. Code should be self-documenting. Complexity should be justified."

🤖 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 `@pkg/controller/plan/adapter/vsphere/builder.go` around lines 327 - 365, The
mapMacStaticIps function is too complex; extract the NIC eligibility checks and
gateway resolution into small helpers to simplify the hot path. Create a helper
like shouldIncludeNIC(guestNetwork *model.GuestNetwork, modeByMAC
map[string]string, isWindows bool) that encapsulates the preserve-mode check and
the Windows-origin logic, and another helper resolveGateway(vm *model.VM,
guestNetwork *model.GuestNetwork) string that iterates vm.GuestIpStacks and
returns the matching gateway (or ""), then simplify mapMacStaticIps to call
these helpers, build the dns/configuration string as before, and append trimmed
configurations; keep function signature of mapMacStaticIps unchanged.
pkg/controller/plan/adapter/hyperv/builder_test.go (1)

102-270: ⚡ Quick win

Add direct tests for hasMultipleStaticIPsPerNIC mode filtering.

This suite validates ResolveNICModes/mapMacStaticIps, but the changed counting path in hasMultipleStaticIPsPerNIC is still untested. Please add a focused case proving that Windows multi-IP detection ignores NICs resolved as dhcp/none.

Proposed test addition
+	Context("hasMultipleStaticIPsPerNIC with guestIPConfig filtering", func() {
+		It("ignores multiple IPs on non-preserve NICs", func() {
+			vm := &model.VM{
+				GuestOS: "Windows Server 2019",
+				GuestNetworks: []hyperv.GuestNetwork{
+					{MAC: "00:15:5D:01:02:03", IP: "172.29.3.193", Origin: hyperv.OriginManual},
+					{MAC: "00:15:5D:01:02:03", IP: "172.29.3.194", Origin: hyperv.OriginManual},
+				},
+			}
+
+			Expect(hasMultipleStaticIPsPerNIC(vm, map[string]string{
+				"00:15:5D:01:02:03": "dhcp",
+			})).To(BeFalse())
+
+			Expect(hasMultipleStaticIPsPerNIC(vm, map[string]string{
+				"00:15:5D:01:02:03": "preserve",
+			})).To(BeTrue())
+		})
+	})

As per coding guidelines: "Coverage: Strive for high test coverage on critical logic paths."

🤖 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 `@pkg/controller/plan/adapter/hyperv/builder_test.go` around lines 102 - 270,
Add a unit test that directly exercises hasMultipleStaticIPsPerNIC to cover the
branch where NICs resolved as "dhcp" or "none" are ignored: create a VM with
multiple NICs and guest/static IP setups, call planbase.ResolveNICModes (or
mapMacStaticIps) to produce modes and the mac->static IP mapping, then assert
hasMultipleStaticIPsPerNIC returns false when only DHCP/none NICs would
otherwise contribute and true when a NIC resolved as "preserve" has multiple
static IPs; refer to hasMultipleStaticIPsPerNIC, ResolveNICModes,
mapMacStaticIps and nicRefsFromVM to locate the logic to test.
pkg/controller/map/network/network_test.go (1)

257-407: 🏗️ Heavy lift

These tests re-implement validation logic instead of testing it.

The new GuestIPConfig validation specs manually loop through nm.Spec.Map and manually call SetCondition, so they can pass even if validateDestination() wiring regresses. Please test the production path (or extract a shared helper used by both validation and tests) to avoid self-fulfilling tests.

As per coding guidelines: "Coverage: Strive for high test coverage on critical logic paths."

🤖 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 `@pkg/controller/map/network/network_test.go` around lines 257 - 407, Tests are
re-implementing validation logic instead of exercising the production validator;
replace the manual loop and direct SetCondition calls with a call to the real
validation path (e.g. invoke validateDestination/ValidateDestination or the
exported validation helper used by the controller against the NetworkMap
instance) so the test uses the same code under test, then assert on
nm.Status.HasCondition(GuestIPConfigNotValid) and HasBlockerCondition as before;
if no exported validator exists, extract the guestIPConfig-checking logic into a
shared helper (used by validateDestination and tests) and call that helper here
instead.
🤖 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 `@pkg/controller/plan/adapter/base/network.go`:
- Around line 35-43: When resolving mode := string(pair.GuestIPConfig) for each
nic, do not let plan-level preserveStaticIPs force "preserve" for destinations
with type "ignored": if pair.GuestIPConfig is empty and the destination type is
"ignored" set mode to api.GuestIPConfigNone explicitly (instead of using
preserveStaticIPs -> api.GuestIPConfigPreserve). Locate the branch that sets
mode when pair.GuestIPConfig == "" and add a check for destination.type ==
"ignored" (or the corresponding enum/constant) to assign api.GuestIPConfigNone
before considering preserveStaticIPs; keep using nic.MAC and modes[nic.MAC] =
mode unchanged.

---

Nitpick comments:
In `@pkg/controller/map/network/network_test.go`:
- Around line 257-407: Tests are re-implementing validation logic instead of
exercising the production validator; replace the manual loop and direct
SetCondition calls with a call to the real validation path (e.g. invoke
validateDestination/ValidateDestination or the exported validation helper used
by the controller against the NetworkMap instance) so the test uses the same
code under test, then assert on nm.Status.HasCondition(GuestIPConfigNotValid)
and HasBlockerCondition as before; if no exported validator exists, extract the
guestIPConfig-checking logic into a shared helper (used by validateDestination
and tests) and call that helper here instead.

In `@pkg/controller/plan/adapter/hyperv/builder_test.go`:
- Around line 102-270: Add a unit test that directly exercises
hasMultipleStaticIPsPerNIC to cover the branch where NICs resolved as "dhcp" or
"none" are ignored: create a VM with multiple NICs and guest/static IP setups,
call planbase.ResolveNICModes (or mapMacStaticIps) to produce modes and the
mac->static IP mapping, then assert hasMultipleStaticIPsPerNIC returns false
when only DHCP/none NICs would otherwise contribute and true when a NIC resolved
as "preserve" has multiple static IPs; refer to hasMultipleStaticIPsPerNIC,
ResolveNICModes, mapMacStaticIps and nicRefsFromVM to locate the logic to test.

In `@pkg/controller/plan/adapter/vsphere/builder.go`:
- Around line 327-365: The mapMacStaticIps function is too complex; extract the
NIC eligibility checks and gateway resolution into small helpers to simplify the
hot path. Create a helper like shouldIncludeNIC(guestNetwork
*model.GuestNetwork, modeByMAC map[string]string, isWindows bool) that
encapsulates the preserve-mode check and the Windows-origin logic, and another
helper resolveGateway(vm *model.VM, guestNetwork *model.GuestNetwork) string
that iterates vm.GuestIpStacks and returns the matching gateway (or ""), then
simplify mapMacStaticIps to call these helpers, build the dns/configuration
string as before, and append trimmed configurations; keep function signature of
mapMacStaticIps unchanged.
🪄 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: 1c02b41a-ee33-4700-80f1-8d2c76dfca17

📥 Commits

Reviewing files that changed from the base of the PR and between a730bf0 and 12eea74.

📒 Files selected for processing (13)
  • operator/.downstream_manifests
  • operator/.upstream_manifests
  • operator/config/crd/bases/forklift.konveyor.io_networkmaps.yaml
  • pkg/apis/forklift/v1beta1/mapping.go
  • pkg/controller/map/network/network_test.go
  • pkg/controller/map/network/validation.go
  • pkg/controller/plan/adapter/base/network.go
  • pkg/controller/plan/adapter/base/network_test.go
  • pkg/controller/plan/adapter/hyperv/builder.go
  • pkg/controller/plan/adapter/hyperv/builder_test.go
  • pkg/controller/plan/adapter/hyperv/hyperv_suite_test.go
  • pkg/controller/plan/adapter/vsphere/builder.go
  • pkg/controller/plan/adapter/vsphere/builder_test.go

Comment thread pkg/controller/plan/adapter/base/network.go
@Hazanel

Hazanel commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

thanks @coderabbitai, what about the nadpool logic, wdyt?

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
cat pkg/controller/plan/adapter/base/network.go
echo "=== TESTS ==="
cat pkg/controller/plan/adapter/base/network_test.go

Length of output: 24801



@Hazanel The NAD pool logic is well-structured overall. Here's a detailed breakdown:


✅ What works well

Pool-per-VM isolation: ResolveNICModes creates a fresh NADPool for each call, so each VM gets independent allocation. Correct.

Cross-network NAD deduplication: The pool is shared across all source networks within one VM invocation. So if net-1 → nad-a and net-2 → nad-a are both in the map, whichever NIC is processed first claims nad-a, and the second is correctly blocked. This is symmetric with how ValidateNetworkDuplicates works, which is a nice consistency property.

Deterministic ordering: nicRefsFromVM iterates vm.NICs (a slice), so NIC→NAD assignment is stable across reconcile cycles — no flapping.

Pool key safety: The key is namespace/name (or just name when namespace is empty). Since / is not a valid character in Kubernetes names, there's no collision risk between e.g. {ns: "foo", name: "bar"} and {ns: "", name: "foo/bar"}.

AllocateNetwork routing: Non-Multus destinations (Pod, Ignored) bypass the pool entirely and return immediately — correct, since only Multus NAD names need deduplication.


🟡 One subtle edge case: pool-exhausted NIC gets "preserve" treatment

When there are more NICs than NAD rows for a source network (pool exhausted), the NIC is skipped — it doesn't appear in modeByMAC. In mapMacStaticIps, the pattern is:

if mode, found := modeByMAC[mac]; found && mode != string(api.GuestIPConfigPreserve) {
    continue // skip this NIC
}
// fallthrough → include this NIC in static IP config

So a pool-exhausted NIC falls into the found=false branch and gets included in the static IP environment variable, as if it were in preserve mode — even if the user had configured the network with guestIPConfig: dhcp or guestIPConfig: none.

In practice this is rare (it requires more NICs than NAD rows, which ValidateNetworkDuplicates would flag), but the behavior is non-obvious. A comment explaining the intent (safe fallback: include rather than silently drop) would help future readers.


🟢 Minor: AllocateNetwork mixed-type silently prioritizes Pod

If a source network is ever mapped to both a Pod destination and a Multus NAD in the same map (an unusual configuration), AllocateNetwork returns the Pod immediately and the Multus rows are never reached:

for _, pair := range pairsForSource {
    if pair.Destination.Type != Multus {
        return pair, true  // Pod always wins
    }
    nadPairs = append(nadPairs, pair)
}

This is correct behavior (and likely unreachable in valid configs since the validator would probably reject it), but a one-line comment on the loop would clarify the intent.


TL;DR: The NAD pool design is solid. The only thing worth a comment is the "pool-exhausted NIC → silent preserve-like treatment" path, since it's a non-obvious interaction between ResolveNICModes and mapMacStaticIps.

(゚◡゚)🐇


🧠 Learnings used
Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-30T06:35:57.763Z
Learning: Repo: kubev2v/forklift PR: 5655 — In `pkg/controller/plan/adapter/vsphere/validator.go`, `StaticIPs()` intentionally returns `(false, nil)` when `vm.GuestNetworks` is empty (instead of the old `(true, nil)`). This is a deliberate behavioral change to align with HyperV adapter behavior and surface the real problem: if preserveStaticIPs is enabled but VMware Tools isn't reporting guest networks, the user should be warned.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-30T06:35:57.763Z
Learning: Repo: kubev2v/forklift PR: 5655 — `ExcludeNICsFromStaticIPs` (on `plan.VM`) is silently ignored for oVirt, OVF-base, and OpenStack providers by design. This is consistent with `PreserveStaticIPs` itself being a no-op for those providers: `SupportsPreserveStaticIps()` gates the Pod Network warning block, and `StaticIPs()` always returns `(true, nil)` for those adapters, so no `VMMissingGuestIPs` warning is ever generated. No validation warning for non-supporting providers is intentional.

Learnt from: yaacov
Repo: kubev2v/forklift PR: 3204
File: operator/.upstream_manifests:156-181
Timestamp: 2025-10-16T14:11:57.112Z
Learning: Repo: kubev2v/forklift — Maintainer yaacov prefers not to create GitHub follow-up issues (e.g., for re-tightening ForkliftController CRD types after temporary relaxation); such work is tracked in a different system. In future reviews, acknowledge and proceed without proposing GitHub issue creation.

Learnt from: mnecas
Repo: kubev2v/forklift PR: 6015
File: pkg/controller/plan/kubevirt.go:747-749
Timestamp: 2026-04-28T02:28:15.946Z
Learning: In `pkg/controller/plan/kubevirt.go` (kubev2v/forklift PR `#6015`), `NetAppShiftPVCs` intentionally uses migration-scoped labels (`r.vmLabels(vm.Ref)` which includes `kMigration`) when building PVCs for NetApp Shift StorageClasses. This means a new PVC is provisioned on each migration retry rather than reusing an existing one. This is a deliberate design decision by maintainer mnecas and should not be flagged as an idempotency issue.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-08T15:00:17.774Z
Learning: Repo: kubev2v/forklift PR: 5243 — In `pkg/controller/plan/migration.go` `PhasePreflightInspection` case, `ValidatePreflightInspection` is guarded by `!step.MarkedCompleted()` to prevent repeated HTTP calls to the inspection server across reconcile cycles when the pod is still in Running phase after shutdown is requested.

Learnt from: yaacov
Repo: kubev2v/forklift PR: 5727
File: .coderabbit.yaml:3-3
Timestamp: 2026-04-06T09:43:33.951Z
Learning: In kubev2v/forklift, yaacov prefers the auto review to use `profile: chill` (light pass to catch disasters only), with `auto_incremental_review: false` so CodeRabbit does not re-review on each new push. Manual follow-up questions via coderabbitai mentions are the preferred way to do deeper analysis.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-22T12:26:00.736Z
Learning: Repo: kubev2v/forklift PR: 5599 — In the oVirt adapter (builder.go), the `da.Disk.Lun.LogicalUnits.LogicalUnit[0]` access in `LunPersistentVolumes` and `LunPersistentVolumeClaims` has no explicit bounds check, but this is intentional and safe: the upstream `isLunMissingSize` validator guarantees at least one LogicalUnit is present before these functions are called.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-05T20:28:29.968Z
Learning: Repo: kubev2v/forklift PR: 5133 — DetectedBootDisk (stored in VMStatus) is intentionally consumed only by the vSphere builder (pkg/controller/plan/adapter/vsphere/builder.go). OVA and HyperV adapters do not consume it by design. Coverage for those providers is deferred to a future PR.

Learnt from: yaacov
Repo: kubev2v/forklift PR: 4509
File: operator/.upstream_manifests:249-268
Timestamp: 2026-02-01T15:17:00.752Z
Learning: Repo: kubev2v/forklift — During reviews, do not request edits or PR-splitting based on diffs in auto-generated manifest files (e.g., operator/.upstream_manifests, operator/.kustomized_manifests, operator/.downstream_manifests). Treat them as outputs; only suggest scope changes if the underlying source templates or code introduced the unrelated changes.

Learnt from: yaacov
Repo: kubev2v/forklift PR: 3204
File: operator/.downstream_manifests:138-138
Timestamp: 2025-10-16T13:44:49.727Z
Learning: Repo: kubev2v/forklift — For PRs that relax ForkliftController CRD types (e.g., boolean-like and counting fields), maintainers prefer no field-name changes. Typos like controller_snapshot_removal_timeout_minuts should be tracked in a separate follow-up issue rather than addressed in the same PR.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-05T09:28:44.440Z
Learning: In kubev2v/forklift, the `9999-network-config.ps1.tmpl` script is part of virt-v2v's upstream firstboot chain and uses `Get-WmiObject Win32_NetworkAdapter`, which the forklift team does not control. Ghost adapter mitigation via `9999-verify-network-config.ps1` is intentionally designed as a downstream safety-net that runs after the upstream script.

@Hazanel Hazanel force-pushed the mtv-5268-guest-ip-config branch from 12eea74 to 9d133eb Compare June 4, 2026 07:54
@Hazanel

Hazanel commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai, please take a look

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Recent commits ==="
gh pr view 6916 --repo kubev2v/forklift --json commits --jq '.commits[-5:] | .[] | {oid: .oid[0:8], message: .messageHeadline, date: .committedDate}'

echo ""
echo "=== PR status ==="
gh pr view 6916 --repo kubev2v/forklift --json state,reviews,reviewDecision,mergeable,statusCheckRollup --jq '{state, reviewDecision, mergeable, checks: [.statusCheckRollup[]? | {name:.name, state:.state}]}'

echo ""
echo "=== Files changed ==="
gh pr view 6916 --repo kubev2v/forklift --json files --jq '.files[] | {path: .path, additions, deletions}'

Length of output: 3143



@Hazanel The PR hasn't changed since my earlier reviews (still on the single commit from June 4). Here's a consolidated summary of what's outstanding:


Open items from previous reviews

Priority Item
🔴 nicRefsFromVM duplicated in both hyperv/builder.go and vsphere/builder.go with identical logic — worth consolidating into the base package or tracking as a follow-up
🟡 dhcp mode has no active wiring — it behaves identically to none today; a comment in the enum godoc explaining that dhcpnone semantically (wiring deferred) would help future readers
🟡 guestIPConfig: preserve on a pod destination is not validated — is this intentional? Pod-network IPs are CNI-managed
🟡 Pool-exhausted NIC gets silent preserve-like treatment — a comment in mapMacStaticIps explaining the intent (safe fallback: include rather than silently drop) would help
🟢 ResolveNICModes/HasPreserveMode tests duplicated across base, vsphere, and hyperv test files — the adapter-specific copies add no unique coverage

CI is still pending so I can't report on that yet. Let me know if you want me to dive deeper into any of the items above, or if any of them have been addressed outside the current commit!


🧠 Learnings used
Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-30T06:35:57.763Z
Learning: Repo: kubev2v/forklift PR: 5655 — In `pkg/controller/plan/adapter/vsphere/validator.go`, `StaticIPs()` intentionally returns `(false, nil)` when `vm.GuestNetworks` is empty (instead of the old `(true, nil)`). This is a deliberate behavioral change to align with HyperV adapter behavior and surface the real problem: if preserveStaticIPs is enabled but VMware Tools isn't reporting guest networks, the user should be warned.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-05T09:28:44.440Z
Learning: In kubev2v/forklift, the `9999-network-config.ps1.tmpl` script is part of virt-v2v's upstream firstboot chain and uses `Get-WmiObject Win32_NetworkAdapter`, which the forklift team does not control. Ghost adapter mitigation via `9999-verify-network-config.ps1` is intentionally designed as a downstream safety-net that runs after the upstream script.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-05T09:28:44.440Z
Learning: In kubev2v/forklift, the 60-second per-NIC polling timeout in `9999-verify-network-config.ps1` is a known limitation. In practice it only affects ghost-only MACs with no valid adapter (uncommon). A shared deadline across all NICs is a tracked future improvement but not a regression.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-30T06:35:57.763Z
Learning: Repo: kubev2v/forklift PR: 5655 — `ExcludeNICsFromStaticIPs` (on `plan.VM`) is silently ignored for oVirt, OVF-base, and OpenStack providers by design. This is consistent with `PreserveStaticIPs` itself being a no-op for those providers: `SupportsPreserveStaticIps()` gates the Pod Network warning block, and `StaticIPs()` always returns `(true, nil)` for those adapters, so no `VMMissingGuestIPs` warning is ever generated. No validation warning for non-supporting providers is intentional.

Learnt from: yaacov
Repo: kubev2v/forklift PR: 3204
File: operator/.upstream_manifests:156-181
Timestamp: 2025-10-16T14:11:57.112Z
Learning: Repo: kubev2v/forklift — Maintainer yaacov prefers not to create GitHub follow-up issues (e.g., for re-tightening ForkliftController CRD types after temporary relaxation); such work is tracked in a different system. In future reviews, acknowledge and proceed without proposing GitHub issue creation.

Learnt from: yaacov
Repo: kubev2v/forklift PR: 5727
File: .coderabbit.yaml:3-3
Timestamp: 2026-04-06T09:43:33.951Z
Learning: In kubev2v/forklift, yaacov prefers the auto review to use `profile: chill` (light pass to catch disasters only), with `auto_incremental_review: false` so CodeRabbit does not re-review on each new push. Manual follow-up questions via coderabbitai mentions are the preferred way to do deeper analysis.

Learnt from: mnecas
Repo: kubev2v/forklift PR: 6015
File: pkg/controller/plan/kubevirt.go:747-749
Timestamp: 2026-04-28T02:28:15.946Z
Learning: In `pkg/controller/plan/kubevirt.go` (kubev2v/forklift PR `#6015`), `NetAppShiftPVCs` intentionally uses migration-scoped labels (`r.vmLabels(vm.Ref)` which includes `kMigration`) when building PVCs for NetApp Shift StorageClasses. This means a new PVC is provisioned on each migration retry rather than reusing an existing one. This is a deliberate design decision by maintainer mnecas and should not be flagged as an idempotency issue.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-22T12:26:00.736Z
Learning: Repo: kubev2v/forklift PR: 5599 — In the oVirt adapter (builder.go), the `da.Disk.Lun.LogicalUnits.LogicalUnit[0]` access in `LunPersistentVolumes` and `LunPersistentVolumeClaims` has no explicit bounds check, but this is intentional and safe: the upstream `isLunMissingSize` validator guarantees at least one LogicalUnit is present before these functions are called.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-26T18:09:51.763Z
Learning: In kubev2v/forklift, the DHCP companion heuristic in `resolve_device_from_siblings()` (udev/network-config firstboot script) is a last-resort fallback reached only when no sibling ifcfg file has a matching HWADDR. Ambiguous multi-NIC picks are mitigated downstream by `check_dupe_hws()`, which discards udev rules with duplicate MAC addresses.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-08T15:00:17.774Z
Learning: Repo: kubev2v/forklift PR: 5243 — In `pkg/controller/plan/migration.go` `PhasePreflightInspection` case, `ValidatePreflightInspection` is guarded by `!step.MarkedCompleted()` to prevent repeated HTTP calls to the inspection server across reconcile cycles when the pod is still in Running phase after shutdown is requested.

@Hazanel

Hazanel commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai, the PR has changed , changes were amended to the PR

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Recent commits ==="
gh pr view 6916 --repo kubev2v/forklift --json commits --jq '.commits[-8:] | .[] | {oid: .oid[0:8], message: .messageHeadline, date: .committedDate}'

echo ""
echo "=== Files changed ==="
gh pr view 6916 --repo kubev2v/forklift --json files --jq '.files[] | {path: .path, additions, deletions}'

echo ""
echo "=== PR diff ==="
gh pr diff 6916 --repo kubev2v/forklift

Length of output: 41179



@Hazanel Thanks for the update! Here's the delta from my previous reviews:

✅ All previous concerns addressed

Item Status
nicRefsFromVM duplication ✅ Resolved — NICRefsFrom[N] generic in base package, adapters just provide the accessor lambda
dhcp mode godoc ✅ Resolved — godocs now distinguish dhcp ("use DHCP on the target") from none ("left untouched")
guestIPConfig: preserve on pod destination ✅ Resolved — now validated at Warn severity in validateDestination
Pool-exhausted NIC comment ✅ Resolved — clear comment in network.go and both builder mapMacStaticIps functions
Test duplication (ResolveNICModes/HasPreserveMode) ✅ Resolved — adapter tests now only cover adapter-specific logic
AllocateNetwork Pod-priority comment ✅ Resolved — "Only Multus NADs need deduplication via the pool" comment added

🟡 One lingering minor note

The GuestIPConfig validation tests in network_test.go still manually re-implement the validateDestination for-loop rather than exercising the actual reconciler. This means if the validation logic changes, those tests won't catch it. It was flagged in the first review — if it's intentional (e.g., unit-testing the condition-setting logic in isolation), a comment to that effect would suffice. Not a blocker.

🟢 Notable improvement

The rename from NotValidForIgnoredNotValidForDestination is a nice touch given that pod+preserve is now also covered.

Overall the PR is in great shape! 🐇


🧠 Learnings used
Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-05T09:28:44.440Z
Learning: In kubev2v/forklift, the 60-second per-NIC polling timeout in `9999-verify-network-config.ps1` is a known limitation. In practice it only affects ghost-only MACs with no valid adapter (uncommon). A shared deadline across all NICs is a tracked future improvement but not a regression.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-30T06:35:57.763Z
Learning: Repo: kubev2v/forklift PR: 5655 — In `pkg/controller/plan/adapter/vsphere/validator.go`, `StaticIPs()` intentionally returns `(false, nil)` when `vm.GuestNetworks` is empty (instead of the old `(true, nil)`). This is a deliberate behavioral change to align with HyperV adapter behavior and surface the real problem: if preserveStaticIPs is enabled but VMware Tools isn't reporting guest networks, the user should be warned.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-05T09:28:44.440Z
Learning: In kubev2v/forklift, the `9999-network-config.ps1.tmpl` script is part of virt-v2v's upstream firstboot chain and uses `Get-WmiObject Win32_NetworkAdapter`, which the forklift team does not control. Ghost adapter mitigation via `9999-verify-network-config.ps1` is intentionally designed as a downstream safety-net that runs after the upstream script.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-30T06:35:57.763Z
Learning: Repo: kubev2v/forklift PR: 5655 — `ExcludeNICsFromStaticIPs` (on `plan.VM`) is silently ignored for oVirt, OVF-base, and OpenStack providers by design. This is consistent with `PreserveStaticIPs` itself being a no-op for those providers: `SupportsPreserveStaticIps()` gates the Pod Network warning block, and `StaticIPs()` always returns `(true, nil)` for those adapters, so no `VMMissingGuestIPs` warning is ever generated. No validation warning for non-supporting providers is intentional.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-26T18:09:51.763Z
Learning: In kubev2v/forklift, the DHCP companion heuristic in `resolve_device_from_siblings()` (udev/network-config firstboot script) is a last-resort fallback reached only when no sibling ifcfg file has a matching HWADDR. Ambiguous multi-NIC picks are mitigated downstream by `check_dupe_hws()`, which discards udev rules with duplicate MAC addresses.

Learnt from: mnecas
Repo: kubev2v/forklift PR: 6015
File: pkg/controller/plan/kubevirt.go:747-749
Timestamp: 2026-04-28T02:28:15.946Z
Learning: In `pkg/controller/plan/kubevirt.go` (kubev2v/forklift PR `#6015`), `NetAppShiftPVCs` intentionally uses migration-scoped labels (`r.vmLabels(vm.Ref)` which includes `kMigration`) when building PVCs for NetApp Shift StorageClasses. This means a new PVC is provisioned on each migration retry rather than reusing an existing one. This is a deliberate design decision by maintainer mnecas and should not be flagged as an idempotency issue.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-26T18:09:51.763Z
Learning: In kubev2v/forklift, the interface prefix allowlist in `device_name_from_ifcfg_filename()` (in the udev/network-config firstboot script) is intentionally narrow (eth, eno, ens, enp, em, bond, team, br, vlan). Migrated VMs originate from VMware, HyperV, and oVirt and never have wlan, tun, tap, macvlan, or similar interface types; widening the list is not desired.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 4830
File: pkg/controller/provider/container/vsphere/collector.go:1004-1021
Timestamp: 2026-04-30T08:23:30.679Z
Learning: In kubev2v/forklift `pkg/controller/provider/container/vsphere/collector.go`, the tag reconciliation approach in `apply()` is intentionally lazy: `getVMsWithTags()` (vAPI REST call) is fetched at most once per update batch, only when a VirtualMachine update is present, and tags are written only for VMs in that batch. This is a deliberate design decision — tag-only changes on quiet VMs (no PropertyCollector update) will be missed until the next VM property change arrives. Maintainer Hazanel accepted this tradeoff (simpler code, minor staleness risk) over adding an independent tag polling loop.

Learnt from: yaacov
Repo: kubev2v/forklift PR: 3204
File: operator/.upstream_manifests:156-181
Timestamp: 2025-10-16T14:11:57.112Z
Learning: Repo: kubev2v/forklift — Maintainer yaacov prefers not to create GitHub follow-up issues (e.g., for re-tightening ForkliftController CRD types after temporary relaxation); such work is tracked in a different system. In future reviews, acknowledge and proceed without proposing GitHub issue creation.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-06T08:35:03.716Z
Learning: Repo: kubev2v/forklift PR: 4756 — In the Hyper-V provider implementation, `pkg/lib/hyperv/driver/winrm.go` and `pkg/lib/hyperv/powershell/scripts.go` exist and are actively used. The `HyperVDriver` interface abstraction was removed; the plan adapter client (`pkg/controller/plan/adapter/hyperv/client.go`) calls `driver.NewWinRMDriver` directly. The inventory collector (`pkg/controller/provider/container/hyperv/collector.go`) is a standalone custom Collector struct (not delegating to any shared ovfbase.Collector).

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-08T15:00:17.774Z
Learning: Repo: kubev2v/forklift PR: 5243 — In `pkg/controller/plan/migration.go` `PhasePreflightInspection` case, `ValidatePreflightInspection` is guarded by `!step.MarkedCompleted()` to prevent repeated HTTP calls to the inspection server across reconcile cycles when the pod is still in Running phase after shutdown is requested.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-22T12:26:00.736Z
Learning: Repo: kubev2v/forklift PR: 5599 — In the oVirt adapter (builder.go), the `da.Disk.Lun.LogicalUnits.LogicalUnit[0]` access in `LunPersistentVolumes` and `LunPersistentVolumeClaims` has no explicit bounds check, but this is intentional and safe: the upstream `isLunMissingSize` validator guarantees at least one LogicalUnit is present before these functions are called.

Learnt from: yaacov
Repo: kubev2v/forklift PR: 4509
File: operator/.upstream_manifests:249-268
Timestamp: 2026-02-01T15:17:00.752Z
Learning: Repo: kubev2v/forklift — During reviews, do not request edits or PR-splitting based on diffs in auto-generated manifest files (e.g., operator/.upstream_manifests, operator/.kustomized_manifests, operator/.downstream_manifests). Treat them as outputs; only suggest scope changes if the underlying source templates or code introduced the unrelated changes.

Learnt from: yaacov
Repo: kubev2v/forklift PR: 3204
File: operator/.upstream_manifests:6770-6772
Timestamp: 2025-10-16T18:19:40.853Z
Learning: Repo: kubev2v/forklift — For PRs relaxing ForkliftController CRD types (e.g., accepting booleans/ints/strings), do not modify or expand the example ForkliftController resource in operator/.upstream_manifests within that PR. Keep examples as-is and handle any exposure/consistency adjustments (e.g., listing feature_* flags in samples/CSV) in a separate, dedicated follow-up. Avoid creating GitHub issues for such follow-ups as they’re tracked elsewhere.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-02-24T08:36:41.996Z
Learning: Repo: kubev2v/forklift — In pkg/controller/plan/kubevirt.go, EnsureProviderStoragePVC lists by labels that currently include migration UID (via nfsPVCLabels/smbPVCLabels). Because migration UID changes on retries, ensures won’t find prior provider-storage PVCs across retries, leading to new PVCs. Deterministic naming by Plan UID + VM ID is preferred to guarantee idempotency.

Introduce a guestIPConfig field (preserve/dhcp/none) on NetworkPair
entries in the NetworkMap CRD, allowing per-NIC control over whether
static IPs are preserved during migration. This overrides the
plan-level preserveStaticIPs flag on a per-network basis.

Changes:
- Add GuestIPConfig type and field to NetworkPair in mapping.go
- Add validation rejecting guestIPConfig on ignored destinations
- Add ResolveNICModes() and HasPreserveMode() helpers in base package
- Modify vSphere and HyperV builders to filter mapMacStaticIps by mode

Ref: https://redhat.atlassian.net/browse/MTV-5674
Resolves: MTV-5674
Signed-off-by: Elad Hazan <ehazan@redhat.com>
@Hazanel Hazanel force-pushed the mtv-5268-guest-ip-config branch from 9d133eb to bbea2f2 Compare June 4, 2026 08:04
@Hazanel

Hazanel commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai, I just addressed your final minor note

@sonarqubecloud

sonarqubecloud Bot commented Jun 4, 2026

Copy link
Copy Markdown

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@Hazanel Perfect — the PR is fully addressed then! All the flagged items are resolved. Thanks for iterating on the feedback so thoroughly. 🐇✨


🧠 Learnings used
Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-05T09:28:44.440Z
Learning: In kubev2v/forklift, the 60-second per-NIC polling timeout in `9999-verify-network-config.ps1` is a known limitation. In practice it only affects ghost-only MACs with no valid adapter (uncommon). A shared deadline across all NICs is a tracked future improvement but not a regression.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-30T06:35:57.763Z
Learning: Repo: kubev2v/forklift PR: 5655 — In `pkg/controller/plan/adapter/vsphere/validator.go`, `StaticIPs()` intentionally returns `(false, nil)` when `vm.GuestNetworks` is empty (instead of the old `(true, nil)`). This is a deliberate behavioral change to align with HyperV adapter behavior and surface the real problem: if preserveStaticIPs is enabled but VMware Tools isn't reporting guest networks, the user should be warned.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-05T09:28:44.440Z
Learning: In kubev2v/forklift, the `9999-network-config.ps1.tmpl` script is part of virt-v2v's upstream firstboot chain and uses `Get-WmiObject Win32_NetworkAdapter`, which the forklift team does not control. Ghost adapter mitigation via `9999-verify-network-config.ps1` is intentionally designed as a downstream safety-net that runs after the upstream script.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-30T06:35:57.763Z
Learning: Repo: kubev2v/forklift PR: 5655 — `ExcludeNICsFromStaticIPs` (on `plan.VM`) is silently ignored for oVirt, OVF-base, and OpenStack providers by design. This is consistent with `PreserveStaticIPs` itself being a no-op for those providers: `SupportsPreserveStaticIps()` gates the Pod Network warning block, and `StaticIPs()` always returns `(true, nil)` for those adapters, so no `VMMissingGuestIPs` warning is ever generated. No validation warning for non-supporting providers is intentional.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-26T18:09:51.763Z
Learning: In kubev2v/forklift, the DHCP companion heuristic in `resolve_device_from_siblings()` (udev/network-config firstboot script) is a last-resort fallback reached only when no sibling ifcfg file has a matching HWADDR. Ambiguous multi-NIC picks are mitigated downstream by `check_dupe_hws()`, which discards udev rules with duplicate MAC addresses.

Learnt from: mnecas
Repo: kubev2v/forklift PR: 6015
File: pkg/controller/plan/kubevirt.go:747-749
Timestamp: 2026-04-28T02:28:15.946Z
Learning: In `pkg/controller/plan/kubevirt.go` (kubev2v/forklift PR `#6015`), `NetAppShiftPVCs` intentionally uses migration-scoped labels (`r.vmLabels(vm.Ref)` which includes `kMigration`) when building PVCs for NetApp Shift StorageClasses. This means a new PVC is provisioned on each migration retry rather than reusing an existing one. This is a deliberate design decision by maintainer mnecas and should not be flagged as an idempotency issue.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-26T18:09:51.763Z
Learning: In kubev2v/forklift, the interface prefix allowlist in `device_name_from_ifcfg_filename()` (in the udev/network-config firstboot script) is intentionally narrow (eth, eno, ens, enp, em, bond, team, br, vlan). Migrated VMs originate from VMware, HyperV, and oVirt and never have wlan, tun, tap, macvlan, or similar interface types; widening the list is not desired.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 4830
File: pkg/controller/provider/container/vsphere/collector.go:1004-1021
Timestamp: 2026-04-30T08:23:30.679Z
Learning: In kubev2v/forklift `pkg/controller/provider/container/vsphere/collector.go`, the tag reconciliation approach in `apply()` is intentionally lazy: `getVMsWithTags()` (vAPI REST call) is fetched at most once per update batch, only when a VirtualMachine update is present, and tags are written only for VMs in that batch. This is a deliberate design decision — tag-only changes on quiet VMs (no PropertyCollector update) will be missed until the next VM property change arrives. Maintainer Hazanel accepted this tradeoff (simpler code, minor staleness risk) over adding an independent tag polling loop.

Learnt from: yaacov
Repo: kubev2v/forklift PR: 3204
File: operator/.upstream_manifests:156-181
Timestamp: 2025-10-16T14:11:57.112Z
Learning: Repo: kubev2v/forklift — Maintainer yaacov prefers not to create GitHub follow-up issues (e.g., for re-tightening ForkliftController CRD types after temporary relaxation); such work is tracked in a different system. In future reviews, acknowledge and proceed without proposing GitHub issue creation.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-06T08:35:03.716Z
Learning: Repo: kubev2v/forklift PR: 4756 — In the Hyper-V provider implementation, `pkg/lib/hyperv/driver/winrm.go` and `pkg/lib/hyperv/powershell/scripts.go` exist and are actively used. The `HyperVDriver` interface abstraction was removed; the plan adapter client (`pkg/controller/plan/adapter/hyperv/client.go`) calls `driver.NewWinRMDriver` directly. The inventory collector (`pkg/controller/provider/container/hyperv/collector.go`) is a standalone custom Collector struct (not delegating to any shared ovfbase.Collector).

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-02-24T08:36:41.996Z
Learning: Repo: kubev2v/forklift — In pkg/controller/plan/kubevirt.go, EnsureProviderStoragePVC lists by labels that currently include migration UID (via nfsPVCLabels/smbPVCLabels). Because migration UID changes on retries, ensures won’t find prior provider-storage PVCs across retries, leading to new PVCs. Deterministic naming by Plan UID + VM ID is preferred to guarantee idempotency.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-22T12:26:00.736Z
Learning: Repo: kubev2v/forklift PR: 5599 — In the oVirt adapter (builder.go), the `da.Disk.Lun.LogicalUnits.LogicalUnit[0]` access in `LunPersistentVolumes` and `LunPersistentVolumeClaims` has no explicit bounds check, but this is intentional and safe: the upstream `isLunMissingSize` validator guarantees at least one LogicalUnit is present before these functions are called.

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-08T15:00:17.774Z
Learning: Repo: kubev2v/forklift PR: 5243 — In `pkg/controller/plan/migration.go` `PhasePreflightInspection` case, `ValidatePreflightInspection` is guarded by `!step.MarkedCompleted()` to prevent repeated HTTP calls to the inspection server across reconcile cycles when the pod is still in Running phase after shutdown is requested.

Learnt from: yaacov
Repo: kubev2v/forklift PR: 4348
File: pkg/provider/ec2/testutil/fixtures.go:225-233
Timestamp: 2026-01-19T08:37:53.183Z
Learning: In the kubev2v/forklift EC2 provider tests, maintainer yaacov intentionally uses hard-coded tag key strings (e.g., "forklift.konveyor.io/vmID", "forklift.konveyor.io/vm-name", "forklift.konveyor.io/volume") in test utilities rather than importing production constants. This is a deliberate testing strategy to catch changes to production tag keys - tests should fail if production strings change, which wouldn't happen if tests shared the same constants.
<!-- </add_learning>

Learnt from: Hazanel
Repo: kubev2v/forklift PR: 0
File: :0-0
Timestamp: 2026-03-22T12:26:00.736Z
Learning: Repo: kubev2v/forklift PR: 5599 — In the oVirt adapter, an empty `storageClassName` in the storage map is intentional/consistent behaviour: it signals Kubernetes to use the cluster default StorageClass. This is not a bug.

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@Hazanel

Hazanel commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

Test plan and conversion pod's log are attached to the ticket

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants