Skip to content

Commit f21b666

Browse files
wenqiqyanjunz97
authored andcommitted
Sync defaultIPv6PrefixLength from VPCNetworkConfiguration to Subnet(Set)
Signed-off-by: Wenqi Qiu <wenqi.qiu@broadcom.com>
1 parent ff79596 commit f21b666

17 files changed

Lines changed: 283 additions & 93 deletions

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ spec:
109109
vpc:
110110
description: |-
111111
NSX path of the VPC the Namespace is associated with.
112-
If vpc is set, only defaultSubnetSize takes effect, other fields are ignored.
112+
If vpc is set, only defaultSubnetSize and defaultIPv6PrefixLength take effect, other fields are ignored.
113113
type: string
114114
vpcConnectivityProfile:
115115
description: VPCConnectivityProfile Path. This profile has configuration

pkg/apis/vpc/v1alpha1/vpcnetworkconfiguration_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
// in the default VPCNetworkConfiguration.
1616
type VPCNetworkConfigurationSpec struct {
1717
// NSX path of the VPC the Namespace is associated with.
18-
// If vpc is set, only defaultSubnetSize takes effect, other fields are ignored.
18+
// If vpc is set, only defaultSubnetSize and defaultIPv6PrefixLength take effect, other fields are ignored.
1919
// +optional
2020
VPC string `json:"vpc,omitempty"`
2121

pkg/config/config.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import (
1010
"flag"
1111
"fmt"
1212
"os"
13+
"strings"
1314

1415
"github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model"
1516
"go.uber.org/zap"
16-
ini "gopkg.in/ini.v1"
17+
"gopkg.in/ini.v1"
1718

19+
"github.com/vmware-tanzu/nsx-operator/pkg/apis/vpc/v1alpha1"
1820
"github.com/vmware-tanzu/nsx-operator/pkg/nsx/auth"
1921
"github.com/vmware-tanzu/nsx-operator/pkg/nsx/auth/jwt"
2022
)
@@ -142,6 +144,32 @@ type K8sConfig struct {
142144
KubeConfigFile string `ini:"kubeconfig"`
143145
// Controlled by FSS
144146
EnableAntreaNSXInterworking bool `ini:"enable_antrea_nsx_interworking"`
147+
// IPFamily is the raw ip_family value from the [k8s] ini section.
148+
// Expected values: "IPv4" (default), "IPv6", or "DualStack".
149+
// Use GetIPAddressType() to obtain the canonical v1alpha1.IPAddressType.
150+
IPFamily string `ini:"ip_family"`
151+
}
152+
153+
// GetIPAddressType parses the raw IPFamily string and returns the canonical
154+
// v1alpha1.IPAddressType value. "IPv4" → IPV4, "IPv6" → IPV6,
155+
// "DualStack" → IPV4IPV6. Empty or unrecognised values default to IPv4.
156+
func (k *K8sConfig) GetIPAddressType() v1alpha1.IPAddressType {
157+
return ParseIPFamily(k.IPFamily)
158+
}
159+
160+
// ParseIPFamily converts a raw ip_family config value to the canonical
161+
// v1alpha1.IPAddressType constant. Recognised values (case-insensitive):
162+
// "IPv4" / "ipv4", "IPv6" / "ipv6", "DualStack" / "dualstack".
163+
// Empty string and any unrecognised value default to IPv4-only.
164+
func ParseIPFamily(s string) v1alpha1.IPAddressType {
165+
switch strings.ToLower(strings.TrimSpace(s)) {
166+
case "ipv6":
167+
return v1alpha1.IPAddressTypeIPv6
168+
case "dualstack":
169+
return v1alpha1.IPAddressTypeIPv4IPv6
170+
default:
171+
return v1alpha1.IPAddressTypeIPv4
172+
}
145173
}
146174

147175
type VCConfig struct {

pkg/controllers/common/utils.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,20 @@ func listSubnetSet(ctx context.Context, client k8sclient.Client, ns string, labe
470470
return oldObj, nil
471471
}
472472

473+
// IsDefaultSubnetSet reports whether the SubnetSet is a namespace default VM/Pod network SubnetSet managed by the operator.
474+
func IsDefaultSubnetSet(s *v1alpha1.SubnetSet) bool {
475+
if s == nil {
476+
return false
477+
}
478+
if _, ok := s.Labels[servicecommon.LabelDefaultNetwork]; ok {
479+
return true
480+
}
481+
if _, ok := s.Labels[servicecommon.LabelDefaultSubnetSet]; ok {
482+
return true
483+
}
484+
return s.Name == servicecommon.DefaultVMSubnetSet || s.Name == servicecommon.DefaultPodSubnetSet
485+
}
486+
473487
func ListDefaultSubnetSet(ctx context.Context, client k8sclient.Client, ns string, subnetSetType string) (*v1alpha1.SubnetSet, error) {
474488
label := k8sclient.MatchingLabels{
475489
servicecommon.LabelDefaultNetwork: subnetSetType,

pkg/controllers/namespace/namespace_controller.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,9 @@ func getDefaultSubnetsets(namespaceType common.NameSpaceType) map[string]string
150150
}
151151

152152
// createDefaultSubnetSet only create default subnetset for system Namespace or SVService Namespace
153-
func (r *NamespaceReconciler) createDefaultSubnetSet(ctx context.Context, ns string, defaultSubnetSize int, namespaceType common.NameSpaceType, networkStack v1alpha1.NetworkStackType) error {
153+
func (r *NamespaceReconciler) createDefaultSubnetSet(ctx context.Context, ns string, nc *v1alpha1.VPCNetworkConfiguration, namespaceType common.NameSpaceType, networkStack v1alpha1.NetworkStackType) error {
154154
defaultSubnetSets := getDefaultSubnetsets(namespaceType)
155+
ipFamily := r.NSXConfig.K8sConfig.GetIPAddressType()
155156
for name, subnetSetType := range defaultSubnetSets {
156157
if err := retry.OnError(retry.DefaultRetry, func(err error) bool {
157158
return err != nil
@@ -168,19 +169,24 @@ func (r *NamespaceReconciler) createDefaultSubnetSet(ctx context.Context, ns str
168169
if networkStack == v1alpha1.FullStackVPC {
169170
accessMode = getDefaultAccessMode(name)
170171
}
171-
if namespaceType == common.SystemNs {
172-
defaultSubnetSize = r.getSystemNsDefaultSize()
172+
spec := v1alpha1.SubnetSetSpec{AccessMode: accessMode}
173+
if util.IPAddressTypeIncludesIPv4(ipFamily) {
174+
spec.IPv4SubnetSize = nc.Spec.DefaultSubnetSize
175+
if namespaceType == common.SystemNs {
176+
spec.IPv4SubnetSize = r.getSystemNsDefaultSize()
177+
}
173178
}
179+
if util.IPAddressTypeIncludesIPv6(ipFamily) {
180+
spec.IPv6PrefixLength = nc.Spec.DefaultIPv6PrefixLength
181+
}
182+
spec.IPAddressType = ipFamily
174183
obj := &v1alpha1.SubnetSet{
175184
ObjectMeta: metav1.ObjectMeta{
176185
Namespace: ns,
177186
Name: name,
178187
Labels: map[string]string{types.LabelDefaultNetwork: subnetSetType},
179188
},
180-
Spec: v1alpha1.SubnetSetSpec{
181-
AccessMode: accessMode,
182-
IPv4SubnetSize: defaultSubnetSize,
183-
},
189+
Spec: spec,
184190
}
185191
// set the label for backward compatibility
186192
switch subnetSetType {
@@ -294,7 +300,7 @@ func (r *NamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
294300
namespaceType := common.GetNamespaceType(obj, nc)
295301

296302
// create default SubnetSet for system Namespace or SVService Namespace
297-
if err := r.createDefaultSubnetSet(ctx, ns, nc.Spec.DefaultSubnetSize, namespaceType, networkStack); err != nil {
303+
if err := r.createDefaultSubnetSet(ctx, ns, nc, namespaceType, networkStack); err != nil {
298304
return common.ResultNormal, err
299305
}
300306

pkg/controllers/namespace/namespace_controller_test.go

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ func createNameSpaceReconciler(objs []client.Object) *NamespaceReconciler {
5959
EnforcementPoint: "vmc-enforcementpoint",
6060
UseAVILoadBalancer: false,
6161
},
62+
K8sConfig: &config.K8sConfig{},
6263
}
6364

6465
service := &vpc.VPCService{
@@ -236,7 +237,7 @@ func TestNamespaceReconciler_Reconcile(t *testing.T) {
236237
})
237238
// Mock createDefaultSubnetSet to return nil (no error)
238239
patches.ApplyPrivateMethod(reflect.TypeOf(r), "createDefaultSubnetSet", func(_ *NamespaceReconciler, _ context.Context, _ string,
239-
_ int, _ []v1alpha1.SharedSubnet, _ ctlcommon.NameSpaceType, _ bool) error {
240+
_ *v1alpha1.VPCNetworkConfiguration, _ ctlcommon.NameSpaceType, _ v1alpha1.NetworkStackType) error {
240241
return nil
241242
})
242243
patches.ApplyMethod(reflect.TypeOf(&vpc.VPCService{}), "GetNetworkStackFromNC", func(_ *vpc.VPCService, _ *v1alpha1.VPCNetworkConfiguration) (v1alpha1.NetworkStackType, error) {
@@ -345,15 +346,18 @@ func TestGetAccessMode(t *testing.T) {
345346

346347
func TestCreateDefaultSubnetSet(t *testing.T) {
347348
tests := []struct {
348-
name string
349-
namespace string
350-
defaultSubnetSize int
351-
existingResources []client.Object
352-
expectedError bool
353-
networkStack v1alpha1.NetworkStackType
354-
nameSpaceType ctlcommon.NameSpaceType
355-
expectedSubnetSets int
356-
setupMocks func(r *NamespaceReconciler) *gomonkey.Patches
349+
name string
350+
namespace string
351+
defaultSubnetSize int
352+
defaultIPv6PrefixLength int
353+
existingResources []client.Object
354+
ipFamily string
355+
expectedError bool
356+
networkStack v1alpha1.NetworkStackType
357+
nameSpaceType ctlcommon.NameSpaceType
358+
expectedSubnetSets int
359+
expectedIPAddressType v1alpha1.IPAddressType
360+
setupMocks func(r *NamespaceReconciler) *gomonkey.Patches
357361
}{
358362
{
359363
name: "Skip case - not create SubnetSet for NormalNs",
@@ -366,37 +370,43 @@ func TestCreateDefaultSubnetSet(t *testing.T) {
366370
setupMocks: func(r *NamespaceReconciler) *gomonkey.Patches { return nil },
367371
},
368372
{
369-
name: "Success case - create new SubnetSets for cpvm namespace",
370-
namespace: "test-ns",
371-
defaultSubnetSize: 8,
372-
existingResources: []client.Object{},
373-
expectedError: false,
374-
expectedSubnetSets: 1, // VM
375-
networkStack: v1alpha1.FullStackVPC,
376-
nameSpaceType: ctlcommon.SystemNs,
373+
name: "Success case - create new SubnetSets for cpvm namespace (dual-stack)",
374+
namespace: "test-ns",
375+
defaultSubnetSize: 8,
376+
defaultIPv6PrefixLength: 72,
377+
existingResources: []client.Object{},
378+
ipFamily: "DualStack",
379+
expectedError: false,
380+
expectedSubnetSets: 1, // VM
381+
networkStack: v1alpha1.FullStackVPC,
382+
nameSpaceType: ctlcommon.SystemNs,
383+
expectedIPAddressType: v1alpha1.IPAddressTypeIPv4IPv6,
377384
setupMocks: func(r *NamespaceReconciler) *gomonkey.Patches {
378-
patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(r), "getSystemNsDefaultSize", func(_ *NamespaceReconciler) int {
379-
return 8
385+
// Avoid NSXClient version checks that require a populated Cluster in unit tests.
386+
return gomonkey.ApplyMethod(reflect.TypeOf(r.SubnetService.NSXClient), "NSXCheckVersion", func(_ *nsx.Client, _ int) bool {
387+
return true
380388
})
381-
return patches
382389
},
383390
},
384391
{
385-
name: "Success case - create new SubnetSets for system svc namespace",
386-
namespace: "test-ns",
387-
defaultSubnetSize: 24,
388-
existingResources: []client.Object{},
389-
expectedError: false,
390-
expectedSubnetSets: 1, // Pod
391-
networkStack: v1alpha1.FullStackVPC,
392-
nameSpaceType: ctlcommon.SVServiceNs,
393-
setupMocks: func(r *NamespaceReconciler) *gomonkey.Patches { return nil },
392+
name: "Success case - create new SubnetSets for system svc namespace (IPv4-only)",
393+
namespace: "test-ns",
394+
defaultSubnetSize: 24,
395+
existingResources: []client.Object{},
396+
ipFamily: "IPv4",
397+
expectedError: false,
398+
expectedSubnetSets: 1, // Pod
399+
networkStack: v1alpha1.FullStackVPC,
400+
nameSpaceType: ctlcommon.SVServiceNs,
401+
expectedIPAddressType: v1alpha1.IPAddressTypeIPv4,
402+
setupMocks: func(r *NamespaceReconciler) *gomonkey.Patches { return nil },
394403
},
395404
}
396405

397406
for _, tt := range tests {
398407
t.Run(tt.name, func(t *testing.T) {
399408
r := createNameSpaceReconciler(tt.existingResources)
409+
r.NSXConfig.K8sConfig.IPFamily = tt.ipFamily
400410

401411
if tt.setupMocks != nil {
402412
patches := tt.setupMocks(r)
@@ -405,8 +415,14 @@ func TestCreateDefaultSubnetSet(t *testing.T) {
405415
}
406416
}
407417

418+
nc := &v1alpha1.VPCNetworkConfiguration{
419+
Spec: v1alpha1.VPCNetworkConfigurationSpec{
420+
DefaultSubnetSize: tt.defaultSubnetSize,
421+
DefaultIPv6PrefixLength: tt.defaultIPv6PrefixLength,
422+
},
423+
}
408424
// Call the function being tested
409-
err := r.createDefaultSubnetSet(context.Background(), tt.namespace, tt.defaultSubnetSize, tt.nameSpaceType, tt.networkStack)
425+
err := r.createDefaultSubnetSet(context.Background(), tt.namespace, nc, tt.nameSpaceType, tt.networkStack)
410426

411427
// Check the result
412428
if tt.expectedError {
@@ -422,6 +438,7 @@ func TestCreateDefaultSubnetSet(t *testing.T) {
422438

423439
// Check that the SubnetSets have the correct properties
424440
if len(subnetSetList.Items) > 0 {
441+
wantIPv6 := nc.Spec.DefaultIPv6PrefixLength
425442
for _, subnetSet := range subnetSetList.Items {
426443
switch subnetSet.Name {
427444
case servicetypes.DefaultVMSubnetSet:
@@ -430,6 +447,8 @@ func TestCreateDefaultSubnetSet(t *testing.T) {
430447
assert.Equal(t, v1alpha1.AccessMode(v1alpha1.AccessModeProject), subnetSet.Spec.AccessMode)
431448
}
432449
assert.Equal(t, tt.defaultSubnetSize, subnetSet.Spec.IPv4SubnetSize)
450+
assert.Equal(t, wantIPv6, subnetSet.Spec.IPv6PrefixLength)
451+
assert.Equal(t, tt.expectedIPAddressType, subnetSet.Spec.IPAddressType)
433452
}
434453
}
435454
}

pkg/controllers/networkinfo/networkinfo_controller.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func (r *NetworkInfoReconciler) GetVpcConnectivityProfilePathByVpcPath(vpcPath s
114114
return r.Service.GetVpcConnectivityProfilePathByVpcPath(vpcPath)
115115
}
116116

117-
func (r *NetworkInfoReconciler) updateDefaultSubnetSet(ctx context.Context, subnetSetType string, ns string, defaultSubnetSize int, hasCIDR bool, hasPrecreatedSubnet bool) error {
117+
func (r *NetworkInfoReconciler) updateDefaultSubnetSet(ctx context.Context, subnetSetType string, ns string, nc *v1alpha1.VPCNetworkConfiguration, hasCIDR bool, hasPrecreatedSubnet bool) error {
118118
var name string
119119
var accessMode v1alpha1.AccessMode
120120
// TODO: remove this old Label when all other dependencies consume the new label
@@ -127,6 +127,8 @@ func (r *NetworkInfoReconciler) updateDefaultSubnetSet(ctx context.Context, subn
127127
accessMode = v1alpha1.AccessMode(v1alpha1.AccessModePrivate)
128128
}
129129

130+
ipFamily := r.Service.NSXConfig.K8sConfig.GetIPAddressType()
131+
130132
if err := retry.OnError(retry.DefaultRetry, func(err error) bool {
131133
return err != nil
132134
}, func() error {
@@ -140,13 +142,21 @@ func (r *NetworkInfoReconciler) updateDefaultSubnetSet(ctx context.Context, subn
140142
log.Debug("Delete default SubnetSet", commonservice.LabelDefaultNetwork, subnetSetType, "Namespace", ns)
141143
return r.Client.Delete(ctx, subnetSetCR)
142144
}
143-
// Do nothing if the default SubnetSet uses the pre-created Subnets
144-
// or there is CIDR for auto-created SubnetSet
145+
// Do nothing if the default SubnetSet already exists. IPv6PrefixLength is immutable in NSX
146+
// once subnets are created, so changes in VPCNetworkConfiguration must not be propagated
147+
// to existing SubnetSets.
145148
log.Debug("Default SubnetSet already exists", commonservice.LabelDefaultNetwork, subnetSetType, "Namespace", ns, "auto-created", subnetSetCR.Spec.SubnetNames == nil)
146149
return nil
147150
}
148151
// Only create the auto-created SubnetSet if there is no pre-created Subnet for default network
149152
if !hasPrecreatedSubnet && hasCIDR {
153+
spec := v1alpha1.SubnetSetSpec{AccessMode: accessMode}
154+
if util.IPAddressTypeIncludesIPv4(ipFamily) {
155+
spec.IPv4SubnetSize = nc.Spec.DefaultSubnetSize
156+
}
157+
if util.IPAddressTypeIncludesIPv6(ipFamily) {
158+
spec.IPv6PrefixLength = nc.Spec.DefaultIPv6PrefixLength
159+
}
150160
obj := &v1alpha1.SubnetSet{
151161
ObjectMeta: metav1.ObjectMeta{
152162
Namespace: ns,
@@ -157,10 +167,7 @@ func (r *NetworkInfoReconciler) updateDefaultSubnetSet(ctx context.Context, subn
157167
commonservice.LabelDefaultSubnetSet: common.NetworkSubnetSetNameMap[subnetSetType],
158168
},
159169
},
160-
Spec: v1alpha1.SubnetSetSpec{
161-
AccessMode: accessMode,
162-
IPv4SubnetSize: defaultSubnetSize,
163-
},
170+
Spec: spec,
164171
}
165172
log.Debug("Create default SubnetSet with auto-created Subnet", commonservice.LabelDefaultNetwork, subnetSetType, "Namespace", ns)
166173
return r.Client.Create(ctx, obj)
@@ -312,7 +319,7 @@ func (r *NetworkInfoReconciler) Reconcile(ctx context.Context, req ctrl.Request)
312319
// Check private cidr to determine if create default SubnetSet for VM
313320
if namespaceType == common.NormalNs {
314321
hasPrivateCidr := len(privateIPs) > 0
315-
if err := r.updateDefaultSubnetSet(ctx, commonservice.DefaultVMNetwork, req.Namespace, nc.Spec.DefaultSubnetSize, hasPrivateCidr, hasVMDefaultSubnets(nc.Spec.Subnets)); err != nil {
322+
if err := r.updateDefaultSubnetSet(ctx, commonservice.DefaultVMNetwork, req.Namespace, nc, hasPrivateCidr, hasVMDefaultSubnets(nc.Spec.Subnets)); err != nil {
316323
r.StatusUpdater.UpdateFail(ctx, networkInfoCR, err, "Failed to create or update default SubnetSet for VM", setNetworkInfoVPCStatusWithError, nil)
317324
setNSNetworkReadyCondition(ctx, r.Client, req.Namespace, nsMsgVPCCreateUpdateError.getNSNetworkCondition(err))
318325
return common.ResultNormal, err
@@ -339,7 +346,7 @@ func (r *NetworkInfoReconciler) Reconcile(ctx context.Context, req ctrl.Request)
339346
// Check privatetgw IP blocks to determine if create default SubnetSet for Pod
340347
if namespaceType == common.NormalNs {
341348
hasPrivateTgwCidr := len(vpcConnectivityProfile.PrivateTgwIpBlocks) > 0
342-
if err := r.updateDefaultSubnetSet(ctx, commonservice.DefaultPodNetwork, req.Namespace, nc.Spec.DefaultSubnetSize, hasPrivateTgwCidr, hasPodDefaultSubnets(nc.Spec.Subnets)); err != nil {
349+
if err := r.updateDefaultSubnetSet(ctx, commonservice.DefaultPodNetwork, req.Namespace, nc, hasPrivateTgwCidr, hasPodDefaultSubnets(nc.Spec.Subnets)); err != nil {
343350
r.StatusUpdater.UpdateFail(ctx, networkInfoCR, err, "Failed to create or update default SubnetSet for Pod", setNetworkInfoVPCStatusWithError, nil)
344351
setNSNetworkReadyCondition(ctx, r.Client, req.Namespace, nsMsgVPCCreateUpdateError.getNSNetworkCondition(err))
345352
return common.ResultNormal, err

pkg/controllers/networkinfo/networkinfo_controller_test.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ func createNetworkInfoReconciler(objs []client.Object) *NetworkInfoReconciler {
124124
EnforcementPoint: "vmc-enforcementpoint",
125125
UseAVILoadBalancer: false,
126126
},
127+
K8sConfig: &config.K8sConfig{},
127128
},
128129
},
129130
}
@@ -2263,12 +2264,25 @@ func TestNetworkInfoReconciler_UpdateDefaultSubnetSet(t *testing.T) {
22632264
r := createNetworkInfoReconciler(tc.subnetSetList)
22642265
ctx := context.TODO()
22652266

2266-
err := r.updateDefaultSubnetSet(ctx, "pod", "ns-1", 32, tc.hasCIDR, tc.hasPrecreatedSubnet)
2267+
nc := &v1alpha1.VPCNetworkConfiguration{
2268+
Spec: v1alpha1.VPCNetworkConfigurationSpec{
2269+
DefaultSubnetSize: 32,
2270+
DefaultIPv6PrefixLength: 80,
2271+
},
2272+
}
2273+
err := r.updateDefaultSubnetSet(ctx, "pod", "ns-1", nc, tc.hasCIDR, tc.hasPrecreatedSubnet)
22672274
if tc.expectErrStr != "" {
22682275
assert.ErrorContains(t, err, tc.expectErrStr)
22692276
} else {
22702277
require.NoError(t, err)
22712278
}
2279+
if tc.name == "Existing default SubnetSet" {
2280+
updated := &v1alpha1.SubnetSet{}
2281+
require.NoError(t, r.Client.Get(ctx, types.NamespacedName{Namespace: "ns-1", Name: "pod-default"}, updated))
2282+
// IPv6PrefixLength must not be propagated to an existing SubnetSet because
2283+
// NSX subnets have an immutable IPv6PrefixLength.
2284+
assert.Equal(t, subnetSet.Spec.IPv6PrefixLength, updated.Spec.IPv6PrefixLength)
2285+
}
22722286
})
22732287
}
22742288
}

0 commit comments

Comments
 (0)