Skip to content

Commit

Permalink
Add Namespace Scoped Zone Discovery
Browse files Browse the repository at this point in the history
- Introduce a feature flag to enable Namespace Scoped Zone.
- Enhance zone discovery to support Namespace Scoped Zones.
- Filter out zones marked for deletion during the discovery process.

Signed-off-by: Gong Zhang <[email protected]>
  • Loading branch information
zhanggbj committed Aug 9, 2024
1 parent 98f6510 commit e20a2e7
Show file tree
Hide file tree
Showing 15 changed files with 765 additions and 556 deletions.
2 changes: 1 addition & 1 deletion config/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ spec:
- "--diagnostics-address=${CAPI_DIAGNOSTICS_ADDRESS:=:8443}"
- "--insecure-diagnostics=${CAPI_INSECURE_DIAGNOSTICS:=false}"
- --v=4
- "--feature-gates=NodeAntiAffinity=${EXP_NODE_ANTI_AFFINITY:=false}"
- "--feature-gates=NodeAntiAffinity=${EXP_NODE_ANTI_AFFINITY:=false},NamespaceScopedZone=${EXP_NAMESPACE_SCOPED_ZONE:=false}"
image: controller:latest
imagePullPolicy: IfNotPresent
name: manager
Expand Down
34 changes: 29 additions & 5 deletions controllers/vmware/vspherecluster_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"fmt"

"github.com/pkg/errors"
topologyv1 "github.com/vmware-tanzu/vm-operator/external/tanzu-topology/api/v1alpha1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
kerrors "k8s.io/apimachinery/pkg/util/errors"
Expand All @@ -40,6 +39,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"

vmwarev1 "sigs.k8s.io/cluster-api-provider-vsphere/apis/vmware/v1beta1"
"sigs.k8s.io/cluster-api-provider-vsphere/feature"
topologyv1 "sigs.k8s.io/cluster-api-provider-vsphere/internal/apis/topology/v1alpha1"
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/context/vmware"
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/services"
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/util"
Expand Down Expand Up @@ -160,7 +161,7 @@ func (r *ClusterReconciler) reconcileDelete(clusterCtx *vmware.ClusterContext) {

func (r *ClusterReconciler) reconcileNormal(ctx context.Context, clusterCtx *vmware.ClusterContext) error {
// Get any failure domains to report back to the CAPI core controller.
failureDomains, err := r.getFailureDomains(ctx)
failureDomains, err := r.getFailureDomains(ctx, clusterCtx.VSphereCluster.Namespace)
if err != nil {
return errors.Wrapf(
err,
Expand Down Expand Up @@ -371,7 +372,32 @@ func (r *ClusterReconciler) VSphereMachineToCluster(ctx context.Context, o clien

// Returns the failure domain information discovered on the cluster
// hosting this controller.
func (r *ClusterReconciler) getFailureDomains(ctx context.Context) (clusterv1.FailureDomains, error) {
func (r *ClusterReconciler) getFailureDomains(ctx context.Context, namespace string) (clusterv1.FailureDomains, error) {
// Determine the source of failure domain based on feature gates NamespaceScopedZone.
// If NamespaceScopedZone is enabled, use Zone which is Namespace scoped,otherwise use
// Availability Zone which is Cluster scoped.
failureDomains := clusterv1.FailureDomains{}
if feature.Gates.Enabled(feature.NamespaceScopedZone) {
zoneList := &topologyv1.ZoneList{}
listOptions := &client.ListOptions{Namespace: namespace}
if err := r.Client.List(ctx, zoneList, listOptions); err != nil {
return nil, err
}

for _, zone := range zoneList.Items {
// Skip zones which are in deletion
if !zone.DeletionTimestamp.IsZero() {
continue
}
failureDomains[zone.Name] = clusterv1.FailureDomainSpec{ControlPlane: true}
}

if len(failureDomains) == 0 {
return nil, nil
}

return failureDomains, nil
}
availabilityZoneList := &topologyv1.AvailabilityZoneList{}
if err := r.Client.List(ctx, availabilityZoneList); err != nil {
return nil, err
Expand All @@ -380,8 +406,6 @@ func (r *ClusterReconciler) getFailureDomains(ctx context.Context) (clusterv1.Fa
if len(availabilityZoneList.Items) == 0 {
return nil, nil
}

failureDomains := clusterv1.FailureDomains{}
for _, az := range availabilityZoneList.Items {
failureDomains[az.Name] = clusterv1.FailureDomainSpec{
ControlPlane: true,
Expand Down
148 changes: 128 additions & 20 deletions controllers/vmware/vspherecluster_reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,18 @@ import (
. "github.com/onsi/ginkgo/v2"
"github.com/onsi/ginkgo/v2/types"
. "github.com/onsi/gomega"
topologyv1 "github.com/vmware-tanzu/vm-operator/external/tanzu-topology/api/v1alpha1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apirecord "k8s.io/client-go/tools/record"
utilfeature "k8s.io/component-base/featuregate/testing"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/cluster-api/util/conditions"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

vmwarev1 "sigs.k8s.io/cluster-api-provider-vsphere/apis/vmware/v1beta1"
"sigs.k8s.io/cluster-api-provider-vsphere/feature"
topologyv1 "sigs.k8s.io/cluster-api-provider-vsphere/internal/apis/topology/v1alpha1"
capvcontext "sigs.k8s.io/cluster-api-provider-vsphere/pkg/context"
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/context/vmware"
"sigs.k8s.io/cluster-api-provider-vsphere/pkg/services/network"
Expand Down Expand Up @@ -130,32 +133,137 @@ var _ = Describe("Cluster Controller Tests", func() {
})

Context("Test getFailureDomains", func() {
It("should not find FailureDomains", func() {
fds, err := reconciler.getFailureDomains(ctx)
It("should not find any FailureDomains if neither AvailabilityZone nor Zone exists", func() {
fds, err := reconciler.getFailureDomains(ctx, clusterCtx.VSphereCluster.Namespace)
Expect(err).ToNot(HaveOccurred())
Expect(fds).Should(BeEmpty())
})

It("should find FailureDomains", func() {
zoneNames := []string{"homer", "marge", "bart"}
for _, name := range zoneNames {
zone := &topologyv1.AvailabilityZone{
TypeMeta: metav1.TypeMeta{
APIVersion: topologyv1.GroupVersion.String(),
Kind: "AvailabilityZone",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Context("when only AvailabilityZone exists", func() {
BeforeEach(func() {
azNames := []string{"az-1", "az-2", "az-3"}
for _, name := range azNames {
az := &topologyv1.AvailabilityZone{
TypeMeta: metav1.TypeMeta{
APIVersion: topologyv1.GroupVersion.String(),
Kind: "AvailabilityZone",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}

Expect(controllerManagerContext.Client.Create(ctx, az)).To(Succeed())
}
})

Expect(controllerManagerContext.Client.Create(ctx, zone)).To(Succeed())
}
It("should discover FailureDomains using AvailabilityZone by default", func() {
fds, err := reconciler.getFailureDomains(ctx, clusterCtx.VSphereCluster.Namespace)
Expect(err).ToNot(HaveOccurred())
Expect(fds).NotTo(BeNil())
Expect(fds).Should(HaveLen(3))
})

fds, err := reconciler.getFailureDomains(ctx)
Expect(err).ToNot(HaveOccurred())
Expect(fds).NotTo(BeNil())
Expect(fds).Should(HaveLen(3))
It("should return nil when NamespaceScopedZone is enabled", func() {
defer utilfeature.SetFeatureGateDuringTest(GinkgoTB(), feature.Gates, feature.NamespaceScopedZone, true)()
fds, err := reconciler.getFailureDomains(ctx, clusterCtx.VSphereCluster.Namespace)
Expect(err).ToNot(HaveOccurred())
Expect(fds).To(BeNil())
})
})

Context("when AvailabilityZone and Zone co-exists", func() {
BeforeEach(func() {
azNames := []string{"az-1", "az-2"}
for _, name := range azNames {
az := &topologyv1.AvailabilityZone{
TypeMeta: metav1.TypeMeta{
APIVersion: topologyv1.GroupVersion.String(),
Kind: "AvailabilityZone",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}
Expect(controllerManagerContext.Client.Create(ctx, az)).To(Succeed())

}
zoneNames := []string{"zone-1", "zone-2", "zone-3"}
for _, name := range zoneNames {
zone := &topologyv1.Zone{
TypeMeta: metav1.TypeMeta{
APIVersion: topologyv1.GroupVersion.String(),
Kind: "Zone",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: clusterCtx.VSphereCluster.Namespace,
},
}

Expect(controllerManagerContext.Client.Create(ctx, zone)).To(Succeed())
}
})

It("should discover FailureDomains using AvailabilityZone by default", func() {
fds, err := reconciler.getFailureDomains(ctx, clusterCtx.VSphereCluster.Namespace)
Expect(err).ToNot(HaveOccurred())
Expect(fds).NotTo(BeNil())
Expect(fds).Should(HaveLen(2))
})

It("should discover FailureDomains using Zone when NamespaceScopedZone is enabled", func() {
defer utilfeature.SetFeatureGateDuringTest(GinkgoTB(), feature.Gates, feature.NamespaceScopedZone, true)()

fds, err := reconciler.getFailureDomains(ctx, clusterCtx.VSphereCluster.Namespace)
Expect(err).ToNot(HaveOccurred())
Expect(fds).NotTo(BeNil())
Expect(fds).Should(HaveLen(3))
})
})

Context("when Zone is marked for deleteion", func() {
BeforeEach(func() {
zoneNames := []string{"zone-1", "zone-2", "zone-3"}
zoneNamespace := clusterCtx.VSphereCluster.Namespace
for _, name := range zoneNames {
zone := &topologyv1.Zone{
TypeMeta: metav1.TypeMeta{
APIVersion: topologyv1.GroupVersion.String(),
Kind: "Zone",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: zoneNamespace,
Finalizers: []string{"zone.test.finalizer"},
},
}

Expect(controllerManagerContext.Client.Create(ctx, zone)).To(Succeed())

if name == "zone-3" {
// Delete the zone to set the deletion timestamp
Expect(controllerManagerContext.Client.Delete(ctx, zone)).To(Succeed())
Zone3 := &topologyv1.Zone{}
Expect(controllerManagerContext.Client.Get(ctx, client.ObjectKey{Namespace: zoneNamespace, Name: name}, Zone3)).To(Succeed())

// Validate the deletion timestamp
Expect(Zone3.DeletionTimestamp.IsZero()).To(BeFalse())
}
}

})

It("should discover FailureDomains using Zone and filter out Zone marked for deletion", func() {
defer utilfeature.SetFeatureGateDuringTest(GinkgoTB(), feature.Gates, feature.NamespaceScopedZone, true)()

fds, err := reconciler.getFailureDomains(ctx, clusterCtx.VSphereCluster.Namespace)
Expect(err).ToNot(HaveOccurred())
Expect(fds).NotTo(BeNil())
Expect(fds).Should(HaveLen(2))
})

})

})
})
8 changes: 7 additions & 1 deletion feature/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ const (
//
// alpha: v1.4
NodeAntiAffinity featuregate.Feature = "NodeAntiAffinity"

// NamespaceScopedZone is a feature gate for the NamespaceScopedZone functionality for supervisor.
//
// alpha: v1.11
NamespaceScopedZone featuregate.Feature = "NamespaceScopedZone"
)

func init() {
Expand All @@ -43,5 +48,6 @@ func init() {
// To add a new feature, define a key for it above and add it here.
var defaultCAPVFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
// Every feature should be initiated here:
NodeAntiAffinity: {Default: false, PreRelease: featuregate.Alpha},
NodeAntiAffinity: {Default: false, PreRelease: featuregate.Alpha},
NamespaceScopedZone: {Default: false, PreRelease: featuregate.Alpha},
}
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ require (
// The version of vm-operator should be kept in sync with the manifests at: config/deployments/integration-tests
github.com/vmware-tanzu/vm-operator/api v1.8.6
github.com/vmware-tanzu/vm-operator/external/ncp v0.0.0-20240404200847-de75746a9505
github.com/vmware-tanzu/vm-operator/external/tanzu-topology v0.0.0-20240404200847-de75746a9505
github.com/vmware/govmomi v0.39.0
)

Expand Down
Loading

0 comments on commit e20a2e7

Please sign in to comment.