Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion pkg/asset/machines/vsphere/capimachines.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,38 @@ const (
masterRole = "master"
)

// detectMachineNetworkIPFamilies checks the InstallConfig's machine network configuration
// and returns whether IPv4 and/or IPv6 CIDRs are present. This is used to determine
// whether DHCPv4 and/or DHCPv6 should be enabled for VSphereMachine network devices
// to match the machineconfigs and avoid WaitingForIPAllocation failures.
// Supports IPv4-only, IPv6-only, and dual-stack configurations.
// Generated by GPT-5.1
func detectMachineNetworkIPFamilies(config *types.InstallConfig) (hasIPv4, hasIPv6 bool) {
if config.Networking == nil {
return false, false
}

// Check MachineNetwork entries
for _, machineNet := range config.Networking.MachineNetwork {
if machineNet.CIDR.IP.To4() != nil {
hasIPv4 = true
} else {
hasIPv6 = true
}
}

// Check deprecated MachineCIDR for backward compatibility
if config.Networking.DeprecatedMachineCIDR != nil {
if config.Networking.DeprecatedMachineCIDR.IP.To4() != nil {
hasIPv4 = true
} else {
hasIPv6 = true
}
}

return hasIPv4, hasIPv6
}

// ProviderSpecFromRawExtension unmarshals the JSON-encoded spec.
func ProviderSpecFromRawExtension(rawExtension *runtime.RawExtension) (*machinev1.VSphereMachineProviderSpec, error) {
if rawExtension == nil {
Expand Down Expand Up @@ -97,6 +129,14 @@ func GenerateMachines(ctx context.Context, clusterID string, config *types.Insta
"stealclock.enable": "TRUE",
}

// Determine if DHCPv4 and/or DHCPv6 should be enabled based on the InstallConfig's machine network.
// This ensures VSphereMachine network devices match the machineconfigs (ip=dhcp,dhcp6 for dual-stack,
// ip=dhcp for IPv4-only, ip=dhcp6 for IPv6-only) and prevents WaitingForIPAllocation failures
// for control-plane VMs in IPv4-only, IPv6-only, and dual-stack configurations.
hasIPv4, hasIPv6 := detectMachineNetworkIPFamilies(config)
useDHCP4 := hasIPv4
useDHCP6 := hasIPv6

capvNetworkDevices := []capv.NetworkDeviceSpec{}
for _, networkDevice := range providerSpec.Network.Devices {
networkName, err := getNetworkInventoryPath(vcenterContext, networkDevice.NetworkName, providerSpec)
Expand All @@ -105,7 +145,8 @@ func GenerateMachines(ctx context.Context, clusterID string, config *types.Insta
}
deviceSpec := capv.NetworkDeviceSpec{
NetworkName: networkName,
DHCP4: true,
DHCP4: useDHCP4,
DHCP6: useDHCP6,
}

// Static IP configured. Add kargs.
Expand Down
156 changes: 156 additions & 0 deletions pkg/asset/machines/vsphere/capimachines_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package vsphere

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/openshift/installer/pkg/ipnet"
"github.com/openshift/installer/pkg/types"
)

// Generated by GPT-5.1

func TestDetectMachineNetworkIPFamilies(t *testing.T) {
testCases := []struct {
name string
config *types.InstallConfig
hasIPv4 bool
hasIPv6 bool
}{
{
name: "Networking is nil",
config: &types.InstallConfig{
Networking: nil,
},
hasIPv4: false,
hasIPv6: false,
},
{
name: "IPv4-only MachineNetwork",
config: &types.InstallConfig{
Networking: &types.Networking{
MachineNetwork: []types.MachineNetworkEntry{
{CIDR: *ipnet.MustParseCIDR("192.0.2.0/24")},
},
},
},
hasIPv4: true,
hasIPv6: false,
},
{
name: "IPv6-only MachineNetwork",
config: &types.InstallConfig{
Networking: &types.Networking{
MachineNetwork: []types.MachineNetworkEntry{
{CIDR: *ipnet.MustParseCIDR("2001:db8::/64")},
},
},
},
hasIPv4: false,
hasIPv6: true,
},
{
name: "Dual-stack MachineNetwork with IPv4 first",
config: &types.InstallConfig{
Networking: &types.Networking{
MachineNetwork: []types.MachineNetworkEntry{
{CIDR: *ipnet.MustParseCIDR("192.0.2.0/24")},
{CIDR: *ipnet.MustParseCIDR("2001:db8::/64")},
},
},
},
hasIPv4: true,
hasIPv6: true,
},
{
name: "Dual-stack MachineNetwork with IPv6 first",
config: &types.InstallConfig{
Networking: &types.Networking{
MachineNetwork: []types.MachineNetworkEntry{
{CIDR: *ipnet.MustParseCIDR("2001:db8::/64")},
{CIDR: *ipnet.MustParseCIDR("192.0.2.0/24")},
},
},
},
hasIPv4: true,
hasIPv6: true,
},
{
name: "IPv4-only DeprecatedMachineCIDR",
config: &types.InstallConfig{
Networking: &types.Networking{
DeprecatedMachineCIDR: ipnet.MustParseCIDR("192.0.2.0/24"),
},
},
hasIPv4: true,
hasIPv6: false,
},
{
name: "IPv6-only DeprecatedMachineCIDR",
config: &types.InstallConfig{
Networking: &types.Networking{
DeprecatedMachineCIDR: ipnet.MustParseCIDR("2001:db8::/32"),
},
},
hasIPv4: false,
hasIPv6: true,
},
{
name: "MachineNetwork IPv4 with DeprecatedMachineCIDR IPv6",
config: &types.InstallConfig{
Networking: &types.Networking{
MachineNetwork: []types.MachineNetworkEntry{
{CIDR: *ipnet.MustParseCIDR("192.0.2.0/24")},
},
DeprecatedMachineCIDR: ipnet.MustParseCIDR("2001:db8::/32"),
},
},
hasIPv4: true,
hasIPv6: true,
},
{
name: "MachineNetwork IPv6 with DeprecatedMachineCIDR IPv4",
config: &types.InstallConfig{
Networking: &types.Networking{
MachineNetwork: []types.MachineNetworkEntry{
{CIDR: *ipnet.MustParseCIDR("2001:db8::/64")},
},
DeprecatedMachineCIDR: ipnet.MustParseCIDR("192.0.2.0/24"),
},
},
hasIPv4: true,
hasIPv6: true,
},
{
name: "Empty MachineNetwork with DeprecatedMachineCIDR IPv4",
config: &types.InstallConfig{
Networking: &types.Networking{
MachineNetwork: []types.MachineNetworkEntry{},
DeprecatedMachineCIDR: ipnet.MustParseCIDR("192.0.2.0/24"),
},
},
hasIPv4: true,
hasIPv6: false,
},
{
name: "Empty MachineNetwork with DeprecatedMachineCIDR IPv6",
config: &types.InstallConfig{
Networking: &types.Networking{
MachineNetwork: []types.MachineNetworkEntry{},
DeprecatedMachineCIDR: ipnet.MustParseCIDR("2001:db8::/32"),
},
},
hasIPv4: false,
hasIPv6: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
hasIPv4, hasIPv6 := detectMachineNetworkIPFamilies(tc.config)
assert.Equal(t, tc.hasIPv4, hasIPv4, "hasIPv4 mismatch")
assert.Equal(t, tc.hasIPv6, hasIPv6, "hasIPv6 mismatch")
})
}
}