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
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ spec:
vpc:
description: |-
NSX path of the VPC the Namespace is associated with.
If vpc is set, only defaultSubnetSize takes effect, other fields are ignored.
If vpc is set, only defaultSubnetSize and defaultIPv6PrefixLength take effect, other fields are ignored.
type: string
vpcConnectivityProfile:
description: VPCConnectivityProfile Path. This profile has configuration
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/vpc/v1alpha1/vpcnetworkconfiguration_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
// in the default VPCNetworkConfiguration.
type VPCNetworkConfigurationSpec struct {
// NSX path of the VPC the Namespace is associated with.
// If vpc is set, only defaultSubnetSize takes effect, other fields are ignored.
// If vpc is set, only defaultSubnetSize and defaultIPv6PrefixLength take effect, other fields are ignored.
// +optional
VPC string `json:"vpc,omitempty"`

Expand Down
30 changes: 29 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import (
"flag"
"fmt"
"os"
"strings"

"github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model"
"go.uber.org/zap"
ini "gopkg.in/ini.v1"
"gopkg.in/ini.v1"

"github.com/vmware-tanzu/nsx-operator/pkg/apis/vpc/v1alpha1"
"github.com/vmware-tanzu/nsx-operator/pkg/nsx/auth"
"github.com/vmware-tanzu/nsx-operator/pkg/nsx/auth/jwt"
)
Expand Down Expand Up @@ -142,6 +144,32 @@ type K8sConfig struct {
KubeConfigFile string `ini:"kubeconfig"`
// Controlled by FSS
EnableAntreaNSXInterworking bool `ini:"enable_antrea_nsx_interworking"`
// IPFamily is the raw ip_family value from the [k8s] ini section.
// Expected values: "IPv4" (default), "IPv6", or "DualStack".
// Use GetIPAddressType() to obtain the canonical v1alpha1.IPAddressType.
IPFamily string `ini:"ip_family"`
}

// GetIPAddressType parses the raw IPFamily string and returns the canonical
// v1alpha1.IPAddressType value. "IPv4" → IPV4, "IPv6" → IPV6,
// "DualStack" → IPV4IPV6. Empty or unrecognised values default to IPv4.
func (k *K8sConfig) GetIPAddressType() v1alpha1.IPAddressType {
return ParseIPFamily(k.IPFamily)
}

// ParseIPFamily converts a raw ip_family config value to the canonical
// v1alpha1.IPAddressType constant. Recognised values (case-insensitive):
// "IPv4" / "ipv4", "IPv6" / "ipv6", "DualStack" / "dualstack".
// Empty string and any unrecognised value default to IPv4-only.
func ParseIPFamily(s string) v1alpha1.IPAddressType {
switch strings.ToLower(strings.TrimSpace(s)) {
case "ipv6":
return v1alpha1.IPAddressTypeIPv6
case "dualstack":
return v1alpha1.IPAddressTypeIPv4IPv6
default:
return v1alpha1.IPAddressTypeIPv4
}
}

type VCConfig struct {
Expand Down
14 changes: 14 additions & 0 deletions pkg/controllers/common/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,20 @@ func listSubnetSet(ctx context.Context, client k8sclient.Client, ns string, labe
return oldObj, nil
}

// IsDefaultSubnetSet reports whether the SubnetSet is a namespace default VM/Pod network SubnetSet managed by the operator.
func IsDefaultSubnetSet(s *v1alpha1.SubnetSet) bool {
if s == nil {
return false
}
if _, ok := s.Labels[servicecommon.LabelDefaultNetwork]; ok {
return true
}
if _, ok := s.Labels[servicecommon.LabelDefaultSubnetSet]; ok {
return true
}
return s.Name == servicecommon.DefaultVMSubnetSet || s.Name == servicecommon.DefaultPodSubnetSet
}

func ListDefaultSubnetSet(ctx context.Context, client k8sclient.Client, ns string, subnetSetType string) (*v1alpha1.SubnetSet, error) {
label := k8sclient.MatchingLabels{
servicecommon.LabelDefaultNetwork: subnetSetType,
Expand Down
22 changes: 14 additions & 8 deletions pkg/controllers/namespace/namespace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,9 @@ func getDefaultSubnetsets(namespaceType common.NameSpaceType) map[string]string
}

// createDefaultSubnetSet only create default subnetset for system Namespace or SVService Namespace
func (r *NamespaceReconciler) createDefaultSubnetSet(ctx context.Context, ns string, defaultSubnetSize int, namespaceType common.NameSpaceType, networkStack v1alpha1.NetworkStackType) error {
func (r *NamespaceReconciler) createDefaultSubnetSet(ctx context.Context, ns string, nc *v1alpha1.VPCNetworkConfiguration, namespaceType common.NameSpaceType, networkStack v1alpha1.NetworkStackType) error {
defaultSubnetSets := getDefaultSubnetsets(namespaceType)
ipFamily := r.NSXConfig.K8sConfig.GetIPAddressType()
for name, subnetSetType := range defaultSubnetSets {
if err := retry.OnError(retry.DefaultRetry, func(err error) bool {
return err != nil
Expand All @@ -168,19 +169,24 @@ func (r *NamespaceReconciler) createDefaultSubnetSet(ctx context.Context, ns str
if networkStack == v1alpha1.FullStackVPC {
accessMode = getDefaultAccessMode(name)
}
if namespaceType == common.SystemNs {
defaultSubnetSize = r.getSystemNsDefaultSize()
spec := v1alpha1.SubnetSetSpec{AccessMode: accessMode}
if util.IPAddressTypeIncludesIPv4(ipFamily) {
spec.IPv4SubnetSize = nc.Spec.DefaultSubnetSize
if namespaceType == common.SystemNs {
spec.IPv4SubnetSize = r.getSystemNsDefaultSize()
}
}
if util.IPAddressTypeIncludesIPv6(ipFamily) {
spec.IPv6PrefixLength = nc.Spec.DefaultIPv6PrefixLength
}
spec.IPAddressType = ipFamily
obj := &v1alpha1.SubnetSet{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
Name: name,
Labels: map[string]string{types.LabelDefaultNetwork: subnetSetType},
},
Spec: v1alpha1.SubnetSetSpec{
AccessMode: accessMode,
IPv4SubnetSize: defaultSubnetSize,
},
Spec: spec,
}
// set the label for backward compatibility
switch subnetSetType {
Expand Down Expand Up @@ -294,7 +300,7 @@ func (r *NamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
namespaceType := common.GetNamespaceType(obj, nc)

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

Expand Down
81 changes: 50 additions & 31 deletions pkg/controllers/namespace/namespace_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ func createNameSpaceReconciler(objs []client.Object) *NamespaceReconciler {
EnforcementPoint: "vmc-enforcementpoint",
UseAVILoadBalancer: false,
},
K8sConfig: &config.K8sConfig{},
}

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

func TestCreateDefaultSubnetSet(t *testing.T) {
tests := []struct {
name string
namespace string
defaultSubnetSize int
existingResources []client.Object
expectedError bool
networkStack v1alpha1.NetworkStackType
nameSpaceType ctlcommon.NameSpaceType
expectedSubnetSets int
setupMocks func(r *NamespaceReconciler) *gomonkey.Patches
name string
namespace string
defaultSubnetSize int
defaultIPv6PrefixLength int
existingResources []client.Object
ipFamily string
expectedError bool
networkStack v1alpha1.NetworkStackType
nameSpaceType ctlcommon.NameSpaceType
expectedSubnetSets int
expectedIPAddressType v1alpha1.IPAddressType
setupMocks func(r *NamespaceReconciler) *gomonkey.Patches
}{
{
name: "Skip case - not create SubnetSet for NormalNs",
Expand All @@ -366,37 +370,43 @@ func TestCreateDefaultSubnetSet(t *testing.T) {
setupMocks: func(r *NamespaceReconciler) *gomonkey.Patches { return nil },
},
{
name: "Success case - create new SubnetSets for cpvm namespace",
namespace: "test-ns",
defaultSubnetSize: 8,
existingResources: []client.Object{},
expectedError: false,
expectedSubnetSets: 1, // VM
networkStack: v1alpha1.FullStackVPC,
nameSpaceType: ctlcommon.SystemNs,
name: "Success case - create new SubnetSets for cpvm namespace (dual-stack)",
namespace: "test-ns",
defaultSubnetSize: 8,
defaultIPv6PrefixLength: 72,
existingResources: []client.Object{},
ipFamily: "DualStack",
expectedError: false,
expectedSubnetSets: 1, // VM
networkStack: v1alpha1.FullStackVPC,
nameSpaceType: ctlcommon.SystemNs,
expectedIPAddressType: v1alpha1.IPAddressTypeIPv4IPv6,
setupMocks: func(r *NamespaceReconciler) *gomonkey.Patches {
patches := gomonkey.ApplyPrivateMethod(reflect.TypeOf(r), "getSystemNsDefaultSize", func(_ *NamespaceReconciler) int {
return 8
// Avoid NSXClient version checks that require a populated Cluster in unit tests.
return gomonkey.ApplyMethod(reflect.TypeOf(r.SubnetService.NSXClient), "NSXCheckVersion", func(_ *nsx.Client, _ int) bool {
return true
})
return patches
},
},
{
name: "Success case - create new SubnetSets for system svc namespace",
namespace: "test-ns",
defaultSubnetSize: 24,
existingResources: []client.Object{},
expectedError: false,
expectedSubnetSets: 1, // Pod
networkStack: v1alpha1.FullStackVPC,
nameSpaceType: ctlcommon.SVServiceNs,
setupMocks: func(r *NamespaceReconciler) *gomonkey.Patches { return nil },
name: "Success case - create new SubnetSets for system svc namespace (IPv4-only)",
namespace: "test-ns",
defaultSubnetSize: 24,
existingResources: []client.Object{},
ipFamily: "IPv4",
expectedError: false,
expectedSubnetSets: 1, // Pod
networkStack: v1alpha1.FullStackVPC,
nameSpaceType: ctlcommon.SVServiceNs,
expectedIPAddressType: v1alpha1.IPAddressTypeIPv4,
setupMocks: func(r *NamespaceReconciler) *gomonkey.Patches { return nil },
},
}

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

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

nc := &v1alpha1.VPCNetworkConfiguration{
Spec: v1alpha1.VPCNetworkConfigurationSpec{
DefaultSubnetSize: tt.defaultSubnetSize,
DefaultIPv6PrefixLength: tt.defaultIPv6PrefixLength,
},
}
// Call the function being tested
err := r.createDefaultSubnetSet(context.Background(), tt.namespace, tt.defaultSubnetSize, tt.nameSpaceType, tt.networkStack)
err := r.createDefaultSubnetSet(context.Background(), tt.namespace, nc, tt.nameSpaceType, tt.networkStack)

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

// Check that the SubnetSets have the correct properties
if len(subnetSetList.Items) > 0 {
wantIPv6 := nc.Spec.DefaultIPv6PrefixLength
for _, subnetSet := range subnetSetList.Items {
switch subnetSet.Name {
case servicetypes.DefaultVMSubnetSet:
Expand All @@ -430,6 +447,8 @@ func TestCreateDefaultSubnetSet(t *testing.T) {
assert.Equal(t, v1alpha1.AccessMode(v1alpha1.AccessModeProject), subnetSet.Spec.AccessMode)
}
assert.Equal(t, tt.defaultSubnetSize, subnetSet.Spec.IPv4SubnetSize)
assert.Equal(t, wantIPv6, subnetSet.Spec.IPv6PrefixLength)
assert.Equal(t, tt.expectedIPAddressType, subnetSet.Spec.IPAddressType)
}
}
}
Expand Down
25 changes: 16 additions & 9 deletions pkg/controllers/networkinfo/networkinfo_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func (r *NetworkInfoReconciler) GetVpcConnectivityProfilePathByVpcPath(vpcPath s
return r.Service.GetVpcConnectivityProfilePathByVpcPath(vpcPath)
}

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

ipFamily := r.Service.NSXConfig.K8sConfig.GetIPAddressType()

if err := retry.OnError(retry.DefaultRetry, func(err error) bool {
return err != nil
}, func() error {
Expand All @@ -140,13 +142,21 @@ func (r *NetworkInfoReconciler) updateDefaultSubnetSet(ctx context.Context, subn
log.Debug("Delete default SubnetSet", commonservice.LabelDefaultNetwork, subnetSetType, "Namespace", ns)
return r.Client.Delete(ctx, subnetSetCR)
}
// Do nothing if the default SubnetSet uses the pre-created Subnets
// or there is CIDR for auto-created SubnetSet
// Do nothing if the default SubnetSet already exists. IPv6PrefixLength is immutable in NSX
// once subnets are created, so changes in VPCNetworkConfiguration must not be propagated
// to existing SubnetSets.
log.Debug("Default SubnetSet already exists", commonservice.LabelDefaultNetwork, subnetSetType, "Namespace", ns, "auto-created", subnetSetCR.Spec.SubnetNames == nil)
return nil
}
// Only create the auto-created SubnetSet if there is no pre-created Subnet for default network
if !hasPrecreatedSubnet && hasCIDR {
spec := v1alpha1.SubnetSetSpec{AccessMode: accessMode}
if util.IPAddressTypeIncludesIPv4(ipFamily) {
spec.IPv4SubnetSize = nc.Spec.DefaultSubnetSize
}
if util.IPAddressTypeIncludesIPv6(ipFamily) {
spec.IPv6PrefixLength = nc.Spec.DefaultIPv6PrefixLength
}
obj := &v1alpha1.SubnetSet{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
Expand All @@ -157,10 +167,7 @@ func (r *NetworkInfoReconciler) updateDefaultSubnetSet(ctx context.Context, subn
commonservice.LabelDefaultSubnetSet: common.NetworkSubnetSetNameMap[subnetSetType],
},
},
Spec: v1alpha1.SubnetSetSpec{
AccessMode: accessMode,
IPv4SubnetSize: defaultSubnetSize,
},
Spec: spec,
}
log.Debug("Create default SubnetSet with auto-created Subnet", commonservice.LabelDefaultNetwork, subnetSetType, "Namespace", ns)
return r.Client.Create(ctx, obj)
Expand Down Expand Up @@ -312,7 +319,7 @@ func (r *NetworkInfoReconciler) Reconcile(ctx context.Context, req ctrl.Request)
// Check private cidr to determine if create default SubnetSet for VM
if namespaceType == common.NormalNs {
hasPrivateCidr := len(privateIPs) > 0
if err := r.updateDefaultSubnetSet(ctx, commonservice.DefaultVMNetwork, req.Namespace, nc.Spec.DefaultSubnetSize, hasPrivateCidr, hasVMDefaultSubnets(nc.Spec.Subnets)); err != nil {
if err := r.updateDefaultSubnetSet(ctx, commonservice.DefaultVMNetwork, req.Namespace, nc, hasPrivateCidr, hasVMDefaultSubnets(nc.Spec.Subnets)); err != nil {
r.StatusUpdater.UpdateFail(ctx, networkInfoCR, err, "Failed to create or update default SubnetSet for VM", setNetworkInfoVPCStatusWithError, nil)
setNSNetworkReadyCondition(ctx, r.Client, req.Namespace, nsMsgVPCCreateUpdateError.getNSNetworkCondition(err))
return common.ResultNormal, err
Expand All @@ -339,7 +346,7 @@ func (r *NetworkInfoReconciler) Reconcile(ctx context.Context, req ctrl.Request)
// Check privatetgw IP blocks to determine if create default SubnetSet for Pod
if namespaceType == common.NormalNs {
hasPrivateTgwCidr := len(vpcConnectivityProfile.PrivateTgwIpBlocks) > 0
if err := r.updateDefaultSubnetSet(ctx, commonservice.DefaultPodNetwork, req.Namespace, nc.Spec.DefaultSubnetSize, hasPrivateTgwCidr, hasPodDefaultSubnets(nc.Spec.Subnets)); err != nil {
if err := r.updateDefaultSubnetSet(ctx, commonservice.DefaultPodNetwork, req.Namespace, nc, hasPrivateTgwCidr, hasPodDefaultSubnets(nc.Spec.Subnets)); err != nil {
r.StatusUpdater.UpdateFail(ctx, networkInfoCR, err, "Failed to create or update default SubnetSet for Pod", setNetworkInfoVPCStatusWithError, nil)
setNSNetworkReadyCondition(ctx, r.Client, req.Namespace, nsMsgVPCCreateUpdateError.getNSNetworkCondition(err))
return common.ResultNormal, err
Expand Down
16 changes: 15 additions & 1 deletion pkg/controllers/networkinfo/networkinfo_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ func createNetworkInfoReconciler(objs []client.Object) *NetworkInfoReconciler {
EnforcementPoint: "vmc-enforcementpoint",
UseAVILoadBalancer: false,
},
K8sConfig: &config.K8sConfig{},
},
},
}
Expand Down Expand Up @@ -2263,12 +2264,25 @@ func TestNetworkInfoReconciler_UpdateDefaultSubnetSet(t *testing.T) {
r := createNetworkInfoReconciler(tc.subnetSetList)
ctx := context.TODO()

err := r.updateDefaultSubnetSet(ctx, "pod", "ns-1", 32, tc.hasCIDR, tc.hasPrecreatedSubnet)
nc := &v1alpha1.VPCNetworkConfiguration{
Spec: v1alpha1.VPCNetworkConfigurationSpec{
DefaultSubnetSize: 32,
DefaultIPv6PrefixLength: 80,
},
}
err := r.updateDefaultSubnetSet(ctx, "pod", "ns-1", nc, tc.hasCIDR, tc.hasPrecreatedSubnet)
if tc.expectErrStr != "" {
assert.ErrorContains(t, err, tc.expectErrStr)
} else {
require.NoError(t, err)
}
if tc.name == "Existing default SubnetSet" {
updated := &v1alpha1.SubnetSet{}
require.NoError(t, r.Client.Get(ctx, types.NamespacedName{Namespace: "ns-1", Name: "pod-default"}, updated))
// IPv6PrefixLength must not be propagated to an existing SubnetSet because
// NSX subnets have an immutable IPv6PrefixLength.
assert.Equal(t, subnetSet.Spec.IPv6PrefixLength, updated.Spec.IPv6PrefixLength)
}
})
}
}
Expand Down
Loading
Loading