Skip to content
Merged
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
5 changes: 5 additions & 0 deletions pkg/providers/vsphere/network/gosc.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ func GuestOSCustomization(results NetworkInterfaceResults) ([]vimtypes.Customiza
break
}

// When adapter.Ip is nil, the vSphere API requires it to be set.
// Set it to disable IPv4, which handles both IPv6-only and completely unconfigured cases.
if adapter.Ip == nil {
adapter.Ip = &vimtypes.CustomizationDisableIpV4{}
}
}

switch {
Expand Down
74 changes: 74 additions & 0 deletions pkg/providers/vsphere/network/gosc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,5 +185,79 @@ var _ = Describe("GOSC", func() {
Expect(adapter.IpV6Spec).To(BeNil())
})
})

Context("Unconfigured interface", func() {
BeforeEach(func() {
results.Results = []network.NetworkInterfaceResult{
{
MacAddress: macAddr1,
Name: "eth0",
IPConfigs: []network.NetworkInterfaceIPConfig{},
DHCP4: false,
DHCP6: false,
NoIPAM: false,
MTU: 1500,
Nameservers: []string{dnsServer1},
},
}
})

It("returns success", func() {
Expect(err).ToNot(HaveOccurred())
Expect(adapterMappings).To(HaveLen(1))
mapping := adapterMappings[0]

adapter := mapping.Adapter
Expect(mapping.MacAddress).To(Equal(macAddr1))
// Unconfigured interface fix: adapter.Ip should be set to disable IPv4
// This matches Linux behavior where an interface can exist without an IP address
Expect(adapter.Ip).To(BeAssignableToTypeOf(&vimtypes.CustomizationDisableIpV4{}))
Expect(adapter.IpV6Spec).To(BeNil())
Expect(adapter.Gateway).To(BeEmpty())
Expect(adapter.SubnetMask).To(BeEmpty())
Expect(adapter.DnsServerList).To(Equal([]string{dnsServer1}))
})
})

Context("IPv6-Only Static", func() {
BeforeEach(func() {
results.Results = []network.NetworkInterfaceResult{
{
IPConfigs: []network.NetworkInterfaceIPConfig{
{
IPCIDR: "2001:db8::100/64",
IsIPv4: false,
Gateway: "2001:db8::1",
},
},
MacAddress: macAddr1,
Name: "eth0",
DHCP4: false,
DHCP6: false,
MTU: 1500,
Nameservers: []string{"2001:4860:4860::8888"},
},
}
})

It("returns success", func() {
Expect(err).ToNot(HaveOccurred())
Expect(adapterMappings).To(HaveLen(1))
mapping := adapterMappings[0]

adapter := mapping.Adapter
Expect(mapping.MacAddress).To(Equal(macAddr1))
// IPv6-only fix: adapter.Ip should be set to disable IPv4
Expect(adapter.Ip).To(BeAssignableToTypeOf(&vimtypes.CustomizationDisableIpV4{}))
Expect(adapter.IpV6Spec).ToNot(BeNil())
Expect(adapter.IpV6Spec.Gateway).To(Equal([]string{"2001:db8::1"}))
Expect(adapter.IpV6Spec.Ip).To(HaveLen(1))
Expect(adapter.IpV6Spec.Ip[0]).To(BeAssignableToTypeOf(&vimtypes.CustomizationFixedIpV6{}))
addressSpec := adapter.IpV6Spec.Ip[0].(*vimtypes.CustomizationFixedIpV6)
Expect(addressSpec.IpAddress).To(Equal("2001:db8::100"))
Expect(addressSpec.SubnetMask).To(BeEquivalentTo(64))
})
})

})
})
159 changes: 159 additions & 0 deletions pkg/providers/vsphere/network/netplan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,5 +245,164 @@ var _ = Describe("Netplan", func() {
Expect(np.AcceptRa).To(HaveValue(BeFalse()))
})
})

Context("IPv4-Only Static", func() {
BeforeEach(func() {
results.Results = []network.NetworkInterfaceResult{
{
IPConfigs: []network.NetworkInterfaceIPConfig{
{
IPCIDR: "192.168.1.100/24",
IsIPv4: true,
Gateway: "192.168.1.1",
},
},
MacAddress: macAddr1,
Name: ifName,
GuestDeviceName: guestDevName,
DHCP4: false,
DHCP6: false,
MTU: 1500,
Nameservers: []string{dnsServer1},
},
}
})

It("returns success with IPv4-only configuration", func() {
Expect(err).ToNot(HaveOccurred())
Expect(config).ToNot(BeNil())

np := config.Ethernets[ifName]
Expect(np.Addresses).To(HaveLen(1))
Expect(np.Addresses[0]).To(Equal(netplan.Address{String: ptr.To("192.168.1.100/24")}))
Expect(np.Gateway4).To(HaveValue(Equal("192.168.1.1")))
Expect(np.Gateway6).To(BeNil())
Expect(np.Dhcp4).To(HaveValue(BeFalse()))
Expect(np.Dhcp6).To(HaveValue(BeFalse()))
})
})

Context("IPv6-Only Static", func() {
BeforeEach(func() {
results.Results = []network.NetworkInterfaceResult{
{
IPConfigs: []network.NetworkInterfaceIPConfig{
{
IPCIDR: "2001:db8::100/64",
IsIPv4: false,
Gateway: "2001:db8::1",
},
},
MacAddress: macAddr1,
Name: ifName,
GuestDeviceName: guestDevName,
DHCP4: false,
DHCP6: false,
MTU: 1500,
Nameservers: []string{"2001:4860:4860::8888"},
},
}
})

It("returns success with IPv6-only configuration", func() {
Expect(err).ToNot(HaveOccurred())
Expect(config).ToNot(BeNil())

np := config.Ethernets[ifName]
Expect(np.Addresses).To(HaveLen(1))
Expect(np.Addresses[0]).To(Equal(netplan.Address{String: ptr.To("2001:db8::100/64")}))
Expect(np.Gateway4).To(BeNil())
Expect(np.Gateway6).To(HaveValue(Equal("2001:db8::1")))
Expect(np.Dhcp4).To(HaveValue(BeFalse()))
Expect(np.Dhcp6).To(HaveValue(BeFalse()))
})
})

Context("Multiple IPv6 Addresses", func() {
BeforeEach(func() {
results.Results = []network.NetworkInterfaceResult{
{
IPConfigs: []network.NetworkInterfaceIPConfig{
{
IPCIDR: "2001:db8::100/64",
IsIPv4: false,
Gateway: "2001:db8::1",
},
{
IPCIDR: "2001:db8::101/64",
IsIPv4: false,
Gateway: "2001:db8::1",
},
},
MacAddress: macAddr1,
Name: ifName,
GuestDeviceName: guestDevName,
DHCP4: false,
DHCP6: false,
MTU: 1500,
Nameservers: []string{"2001:4860:4860::8888"},
},
}
})

It("returns success with multiple IPv6 addresses", func() {
Expect(err).ToNot(HaveOccurred())
Expect(config).ToNot(BeNil())
Expect(config.Version).To(Equal(constants.NetPlanVersion))

Expect(config.Ethernets).To(HaveLen(1))
Expect(config.Ethernets).To(HaveKey(ifName))

np := config.Ethernets[ifName]
Expect(np.Addresses).To(HaveLen(2))
Expect(np.Addresses[0]).To(Equal(netplan.Address{String: ptr.To("2001:db8::100/64")}))
Expect(np.Addresses[1]).To(Equal(netplan.Address{String: ptr.To("2001:db8::101/64")}))
Expect(np.Gateway6).To(HaveValue(Equal("2001:db8::1")))
})
})

Context("Multiple IPv4 Addresses", func() {
BeforeEach(func() {
results.Results = []network.NetworkInterfaceResult{
{
IPConfigs: []network.NetworkInterfaceIPConfig{
{
IPCIDR: "192.168.1.100/24",
IsIPv4: true,
Gateway: "192.168.1.1",
},
{
IPCIDR: "192.168.1.101/24",
IsIPv4: true,
Gateway: "192.168.1.1",
},
},
MacAddress: macAddr1,
Name: ifName,
GuestDeviceName: guestDevName,
DHCP4: false,
DHCP6: false,
MTU: 1500,
Nameservers: []string{dnsServer1},
},
}
})

It("returns success with multiple IPv4 addresses", func() {
Expect(err).ToNot(HaveOccurred())
Expect(config).ToNot(BeNil())
Expect(config.Version).To(Equal(constants.NetPlanVersion))

Expect(config.Ethernets).To(HaveLen(1))
Expect(config.Ethernets).To(HaveKey(ifName))

np := config.Ethernets[ifName]
Expect(np.Addresses).To(HaveLen(2))
Expect(np.Addresses[0]).To(Equal(netplan.Address{String: ptr.To("192.168.1.100/24")}))
Expect(np.Addresses[1]).To(Equal(netplan.Address{String: ptr.To("192.168.1.101/24")}))
Expect(np.Gateway4).To(HaveValue(Equal("192.168.1.1")))
})
})

})
})
15 changes: 13 additions & 2 deletions pkg/providers/vsphere/network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,14 @@ func applyInterfaceSpecToResult(
result.MTU = *interfaceSpec.MTU
}

// TODO: There is currently no way to unset DHCP flags if NetOP has set them.
// For example, if NetOP sets both DHCP4 and DHCP6, but user only wants DHCP4,
// they cannot disable DHCP6. Consider adding explicit unset mechanism (e.g.,
// using pointer types or separate "disable" flags) in the future.
if interfaceSpec.DHCP4 {
result.DHCP4 = true
}
if interfaceSpec.DHCP6 {
// We don't really support IPv6 yet so this is only enabled when specified in
// the interface spec.
result.DHCP6 = true
}

Expand Down Expand Up @@ -463,10 +465,19 @@ func netOpNetIfToResult(

switch netIf.Status.IPAssignmentMode {
case netopv1alpha1.NetworkInterfaceIPAssignmentModeDHCP:
// When NetOP indicates DHCP, IPConfigs will be empty.
// Since NetOP doesn't distinguish between IPv4 and IPv6, set both.
// User's interface spec can override either or both flags.
result.DHCP4 = true
result.DHCP6 = true
case netopv1alpha1.NetworkInterfaceIPAssignmentModeNone:
result.NoIPAM = true
default: // netopv1alpha1.NetworkInterfaceIPAssignmentModeStaticPool
// Process all IPConfigs (both IPv4 and IPv6). This correctly handles:
// - IPv4-only scenarios (only IPv4 IPConfigs)
// - IPv6-only scenarios (only IPv6 IPConfigs)
// - Dual-stack scenarios (both IPv4 and IPv6 IPConfigs)
// DHCP4/DHCP6 are not set in StaticPool mode, only IPConfigs are populated.
for _, ip := range netIf.Status.IPConfigs {
ipConfig := NetworkInterfaceIPConfig{
IPCIDR: ipCIDRNotation(ip.IP, ip.SubnetMask, ip.IPFamily == corev1.IPv4Protocol),
Expand Down
Loading