Skip to content

Commit 43882e8

Browse files
committed
Support IPv6 for SubnetPort
Signed-off-by: Yanjun Zhou <yanjun.zhou@broadcom.com>
1 parent 2fd173c commit 43882e8

19 files changed

Lines changed: 839 additions & 441 deletions

File tree

build/yaml/crd/vpc/crd.nsx.vmware.com_subnetports.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ spec:
146146
description: DHCPDeactivatedOnSubnet indicates whether DHCP is
147147
deactivated on the Subnet.
148148
type: boolean
149+
dhcpv6DeactivatedOnSubnet:
150+
description: DHCPv6DeactivatedOnSubnet indicates whether DHCPv6
151+
is deactivated on the Subnet.
152+
type: boolean
149153
ipAddresses:
150154
items:
151155
properties:

docs/ref/apis/vpc.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,7 @@ _Appears in:_
421421
| `ipAddresses` _[NetworkInterfaceIPAddress](#networkinterfaceipaddress) array_ | | | |
422422
| `macAddress` _string_ | The MAC address. | | |
423423
| `dhcpDeactivatedOnSubnet` _boolean_ | DHCPDeactivatedOnSubnet indicates whether DHCP is deactivated on the Subnet. | | |
424+
| `dhcpv6DeactivatedOnSubnet` _boolean_ | DHCPv6DeactivatedOnSubnet indicates whether DHCPv6 is deactivated on the Subnet. | | |
424425

425426

426427
#### NetworkInterfaceIPAddress

pkg/apis/vpc/v1alpha1/subnetport_types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ type NetworkInterfaceConfig struct {
7070
MACAddress string `json:"macAddress,omitempty"`
7171
// DHCPDeactivatedOnSubnet indicates whether DHCP is deactivated on the Subnet.
7272
DHCPDeactivatedOnSubnet bool `json:"dhcpDeactivatedOnSubnet,omitempty"`
73+
// DHCPv6DeactivatedOnSubnet indicates whether DHCPv6 is deactivated on the Subnet.
74+
DHCPv6DeactivatedOnSubnet bool `json:"dhcpv6DeactivatedOnSubnet,omitempty"`
7375
}
7476

7577
type NetworkInterfaceIPAddress struct {

pkg/controllers/common/utils.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const (
3030
NSXIPAddressTypeIPv4 = "IPV4"
3131
NSXIPAddressTypeIPv6 = "IPV6"
3232
NSXIPAddressTypeIPv4IPv6 = "IPV4_IPV6"
33+
NSXIPAddressTypeNone = "NONE"
3334
)
3435

3536
var (
@@ -641,6 +642,25 @@ func ConvertCRIPAddressTypeToNSX(crType v1alpha1.IPAddressType) string {
641642
}
642643
}
643644

645+
// ConvertCRStaticIPAddressTypeToNSX converts CR StaticIPAddressType to NSX API format
646+
// v1alpha1 format: IPv4, IPv6, IPv4IPv6, None
647+
// NSX format: IPV4, IPV6, IPV4_IPV6, NONE
648+
func ConvertCRStaticIPAddressTypeToNSX(crType v1alpha1.StaticIPAllocationType) string {
649+
switch crType {
650+
case v1alpha1.StaticIPAllocationTypeIPv4:
651+
return NSXIPAddressTypeIPv4
652+
case v1alpha1.StaticIPAllocationTypeIPv6:
653+
return NSXIPAddressTypeIPv6
654+
case v1alpha1.StaticIPAllocationTypeIPv4IPv6:
655+
return NSXIPAddressTypeIPv4IPv6
656+
case v1alpha1.StaticIPAllocationTypeNone:
657+
return NSXIPAddressTypeNone
658+
default:
659+
log.Warn("Unknown IP address type, defaulting to IPv4", "unknownType", string(crType))
660+
return NSXIPAddressTypeIPv4
661+
}
662+
}
663+
644664
// ConvertNSXIPAddressTypeToCR converts NSX API IPAddressType to CR format
645665
// NSX format: IPV4, IPV6, IPV4_IPV6
646666
// v1alpha1 format: IPV4, IPV6, IPV4IPV6

pkg/controllers/pod/pod_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R
116116
r.StatusUpdater.UpdateFail(ctx, pod, err, "", nil)
117117
return common.ResultRequeue, err
118118
}
119-
nsxSubnetPortState, _, err := r.SubnetPortService.CreateOrUpdateSubnetPort(pod, nsxSubnet, contextID, &pod.ObjectMeta.Labels, false, r.restoreMode)
119+
nsxSubnetPortState, err := r.SubnetPortService.CreateOrUpdateSubnetPort(pod, nsxSubnet, contextID, &pod.ObjectMeta.Labels, false, r.restoreMode)
120120
if err != nil {
121121
r.StatusUpdater.UpdateFail(ctx, pod, err, "", nil)
122122
return common.ResultRequeue, err

pkg/controllers/pod/pod_controller_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,8 @@ func TestPodReconciler_Reconcile(t *testing.T) {
223223
return &model.VpcSubnet{}, nil
224224
})
225225
patches.ApplyFunc((*subnetport.SubnetPortService).CreateOrUpdateSubnetPort,
226-
func(r *subnetport.SubnetPortService, obj interface{}, nsxSubnet *model.VpcSubnet, contextID string, tags *map[string]string, isVmSubnetPort bool, restoreMode bool) (*model.SegmentPortState, bool, error) {
227-
return nil, false, errors.New("failed to create subnetport")
226+
func(r *subnetport.SubnetPortService, obj interface{}, nsxSubnet *model.VpcSubnet, contextID string, tags *map[string]string, isVmSubnetPort bool, restoreMode bool) (*model.SegmentPortState, error) {
227+
return nil, errors.New("failed to create subnetport")
228228
})
229229
return patches
230230
},
@@ -256,7 +256,7 @@ func TestPodReconciler_Reconcile(t *testing.T) {
256256
return &model.VpcSubnet{}, nil
257257
})
258258
patches.ApplyFunc((*subnetport.SubnetPortService).CreateOrUpdateSubnetPort,
259-
func(s *subnetport.SubnetPortService, obj interface{}, nsxSubnet *model.VpcSubnet, contextID string, tags *map[string]string) (*model.SegmentPortState, bool, error) {
259+
func(s *subnetport.SubnetPortService, obj interface{}, nsxSubnet *model.VpcSubnet, contextID string, tags *map[string]string) (*model.SegmentPortState, error) {
260260
return &model.SegmentPortState{
261261
RealizedBindings: []model.AddressBindingEntry{
262262
{
@@ -268,7 +268,7 @@ func TestPodReconciler_Reconcile(t *testing.T) {
268268
Attachment: &model.SegmentPortAttachmentState{
269269
Id: servicecommon.String("attachment-id"),
270270
},
271-
}, false, nil
271+
}, nil
272272
})
273273

274274
k8sClient.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil).Do(func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
@@ -306,7 +306,7 @@ func TestPodReconciler_Reconcile(t *testing.T) {
306306
return &model.VpcSubnet{}, nil
307307
})
308308
patches.ApplyFunc((*subnetport.SubnetPortService).CreateOrUpdateSubnetPort,
309-
func(s *subnetport.SubnetPortService, obj interface{}, nsxSubnet *model.VpcSubnet, contextID string, tags *map[string]string) (*model.SegmentPortState, bool, error) {
309+
func(s *subnetport.SubnetPortService, obj interface{}, nsxSubnet *model.VpcSubnet, contextID string, tags *map[string]string) (*model.SegmentPortState, error) {
310310
return &model.SegmentPortState{
311311
RealizedBindings: []model.AddressBindingEntry{
312312
{
@@ -315,7 +315,7 @@ func TestPodReconciler_Reconcile(t *testing.T) {
315315
},
316316
},
317317
},
318-
}, false, nil
318+
}, nil
319319
})
320320
patches.ApplyMethod(reflect.TypeOf(r.SubnetPortService.NSXClient), "NSXCheckVersion", func(_ *nsx.Client, _ int) bool {
321321
return false

pkg/controllers/subnetport/subnetport_controller.go

Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ func (r *SubnetPortReconciler) Reconcile(ctx context.Context, req ctrl.Request)
167167
r.StatusUpdater.UpdateFail(ctx, subnetPort, err, "Failed to create NSX IPAddressAllocation for AddressBinding restore", setSubnetPortReadyStatusFalse, r.SubnetPortService, r.restoreMode)
168168
return common.ResultRequeue, err
169169
}
170-
nsxSubnetPortState, enableDHCP, err := r.SubnetPortService.CreateOrUpdateSubnetPort(subnetPort, nsxSubnet, "", labels, isVmSubnetPort, r.restoreMode)
170+
nsxSubnetPortState, err := r.SubnetPortService.CreateOrUpdateSubnetPort(subnetPort, nsxSubnet, "", labels, isVmSubnetPort, r.restoreMode)
171171
if err != nil {
172172
r.StatusUpdater.UpdateFail(ctx, subnetPort, err, "", setSubnetPortReadyStatusFalse, r.SubnetPortService, r.restoreMode)
173173
if nsxutil.IsRealizeStateError(err) {
@@ -191,13 +191,35 @@ func (r *SubnetPortReconciler) Reconcile(ctx context.Context, req ctrl.Request)
191191
Gateway: "",
192192
},
193193
},
194-
DHCPDeactivatedOnSubnet: !enableDHCP,
194+
DHCPDeactivatedOnSubnet: !util.NSXSubnetDHCPEnabled(nsxSubnet),
195+
DHCPv6DeactivatedOnSubnet: !util.NSXSubnetDHCPv6Enabled(nsxSubnet),
196+
}
197+
// Append one more ipaddress for dual stack subnet
198+
// TODO: check SubnetPort interfacetype when it is supported
199+
if nsxSubnet.IpAddressType != nil && *nsxSubnet.IpAddressType == model.VpcSubnet_IP_ADDRESS_TYPE_IPV4_IPV6 {
200+
subnetPort.Status.NetworkInterfaceConfig.IPAddresses = append(
201+
subnetPort.Status.NetworkInterfaceConfig.IPAddresses,
202+
v1alpha1.NetworkInterfaceIPAddress{Gateway: ""},
203+
)
195204
}
196205
if util.NSXSubnetStaticIPAllocationEnabled(nsxSubnet) || len(subnetPort.Spec.AddressBindings) > 0 {
197206
if len(nsxSubnetPortState.RealizedBindings) > 0 {
198-
subnetPort.Status.NetworkInterfaceConfig.IPAddresses[0].IPAddress = *nsxSubnetPortState.RealizedBindings[0].Binding.IpAddress
199-
// The MAC address is updated here when the SubnetPort's StaticIPAllocation is enabled or spec.AddressBindings is specific. For the other cases, the MAC address will be updated in the VIF polling.
200-
subnetPort.Status.NetworkInterfaceConfig.MACAddress = strings.Trim(*nsxSubnetPortState.RealizedBindings[0].Binding.MacAddress, "\"")
207+
// Process all realized bindings and populate IPAddresses array
208+
// RealizedBindings can contain up to 2 entries (IPv4 and IPv6)
209+
// The MAC address is updated here when the SubnetPort's StaticIPAllocation
210+
// is enabled or spec.AddressBindings is specific. For the other cases, the MAC
211+
// address will be updated in the VIF polling.
212+
macAddress := ""
213+
for i, binding := range nsxSubnetPortState.RealizedBindings {
214+
if binding.Binding != nil && binding.Binding.IpAddress != nil {
215+
if macAddress == "" && binding.Binding.MacAddress != nil {
216+
macAddress = strings.Trim(*binding.Binding.MacAddress, "\"")
217+
}
218+
subnetPort.Status.NetworkInterfaceConfig.IPAddresses[i].IPAddress = *binding.Binding.IpAddress
219+
}
220+
}
221+
// MAC address is consistent across all bindings, set it once
222+
subnetPort.Status.NetworkInterfaceConfig.MACAddress = macAddress
201223
} else if !util.NSXSubnetStaticIPAllocationEnabled(nsxSubnet) && len(subnetPort.Spec.AddressBindings) > 0 {
202224
// If StaticIPAllocation is disabled, propagate the MAC from spec.addressBinding to status
203225
subnetPort.Status.NetworkInterfaceConfig.MACAddress = subnetPort.Spec.AddressBindings[0].MACAddress
@@ -211,9 +233,7 @@ func (r *SubnetPortReconciler) Reconcile(ctx context.Context, req ctrl.Request)
211233
if subnetPort.Status.NetworkInterfaceConfig.MACAddress == "" && old_status.NetworkInterfaceConfig.MACAddress != "" {
212234
subnetPort.Status.NetworkInterfaceConfig.MACAddress = old_status.NetworkInterfaceConfig.MACAddress
213235
}
214-
if subnetPort.Status.NetworkInterfaceConfig.IPAddresses[0].IPAddress == "" && old_status.NetworkInterfaceConfig.IPAddresses[0].IPAddress != "" {
215-
subnetPort.Status.NetworkInterfaceConfig.IPAddresses[0].IPAddress = old_status.NetworkInterfaceConfig.IPAddresses[0].IPAddress
216-
}
236+
subnetPort.Status.NetworkInterfaceConfig.IPAddresses = old_status.NetworkInterfaceConfig.IPAddresses
217237
}
218238
err = r.updateSubnetStatusOnSubnetPort(subnetPort, nsxSubnet)
219239
if err != nil {
@@ -924,19 +944,50 @@ func (r *SubnetPortReconciler) CheckAndGetSubnetPathForSubnetPort(ctx context.Co
924944
}
925945

926946
func (r *SubnetPortReconciler) updateSubnetStatusOnSubnetPort(subnetPort *v1alpha1.SubnetPort, nsxSubnet *model.VpcSubnet) error {
927-
gateway, prefix, err := r.SubnetService.GetGatewayPrefixOfSubnet(nsxSubnet)
947+
subnetPort.Status.NetworkInterfaceConfig.LogicalSwitchUUID = *nsxSubnet.RealizationId
948+
// Get all gateways from the subnet (may be IPv4, IPv6, or both for dual-stack)
949+
gatewaysWithPrefixes, err := r.SubnetService.GetAllGatewayPrefixesOfSubnet(nsxSubnet)
928950
if err != nil {
929951
return err
930952
}
931-
// For now, we have an assumption that one subnetport only have one IP address
932-
if len(subnetPort.Status.NetworkInterfaceConfig.IPAddresses[0].IPAddress) > 0 && prefix > 0 {
933-
subnetPort.Status.NetworkInterfaceConfig.IPAddresses[0].IPAddress += fmt.Sprintf("/%d", prefix)
934-
}
935953
// The gateway can be empty for L2_Only Subnet which has the vlan_connection without gateway
936-
if len(gateway) > 0 {
937-
subnetPort.Status.NetworkInterfaceConfig.IPAddresses[0].Gateway = gateway
954+
if len(gatewaysWithPrefixes) == 0 {
955+
return nil
956+
}
957+
958+
// Process each IP address entry
959+
for i, ipConfig := range subnetPort.Status.NetworkInterfaceConfig.IPAddresses {
960+
var isIPv6 bool
961+
962+
if len(ipConfig.IPAddress) > 0 {
963+
// If IP address is present, determine family dynamically
964+
isIPv6 = util.IsIPv6(ipConfig.IPAddress)
965+
} else {
966+
// For empty IP configurations (e.g., DHCP), fall back to index-based positional inference:
967+
// Index 0 matches the first gateway family, Index 1 matches the second gateway family
968+
if i < len(gatewaysWithPrefixes) {
969+
isIPv6 = util.IsIPv6(gatewaysWithPrefixes[i].Gateway)
970+
} else {
971+
continue
972+
}
973+
}
974+
975+
// Find matching gateway by IP family
976+
for _, gwInfo := range gatewaysWithPrefixes {
977+
isGatewayIPv6 := util.IsIPv6(gwInfo.Gateway)
978+
if isIPv6 != isGatewayIPv6 {
979+
continue
980+
}
981+
982+
// Only append the prefix notation if the IP address is actually present
983+
if len(subnetPort.Status.NetworkInterfaceConfig.IPAddresses[i].IPAddress) > 0 {
984+
subnetPort.Status.NetworkInterfaceConfig.IPAddresses[i].IPAddress += fmt.Sprintf("/%d", gwInfo.Prefix)
985+
}
986+
987+
subnetPort.Status.NetworkInterfaceConfig.IPAddresses[i].Gateway = gwInfo.Gateway
988+
break
989+
}
938990
}
939-
subnetPort.Status.NetworkInterfaceConfig.LogicalSwitchUUID = *nsxSubnet.RealizationId
940991
return nil
941992
}
942993

0 commit comments

Comments
 (0)