Skip to content

Instantly share code, notes, and snippets.

@akutz
Created October 1, 2019 16:30
package controllers_test
import (
"reflect"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha2"
"sigs.k8s.io/controller-runtime/pkg/client"
infrav1 "sigs.k8s.io/cluster-api-provider-vsphere/api/v1alpha2"
)
const (
namespace = "default"
)
type runtimeObject interface {
runtime.Object
GetName() string
GetNamespace() string
SetFinalizers([]string)
GetFinalizers() []string
GetOwnerReferences() []metav1.OwnerReference
SetOwnerReferences([]metav1.OwnerReference)
}
// newInfraCluster should return a new instance of the infrastructure cluster.
func newInfraCluster() runtimeObject {
return &infrav1.VSphereCluster{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
Namespace: namespace,
},
}
}
// newInfraMachine should return a new instance of the infrastructure machine.
func newInfraMachine() runtimeObject {
return &infrav1.VSphereMachine{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
Namespace: namespace,
},
}
}
// Cache the type names of the infrastructure cluster and machine.
var (
infraClusterTypeName = reflect.TypeOf(newInfraCluster()).Elem().Name()
infraMachineTypeName = reflect.TypeOf(newInfraMachine()).Elem().Name()
)
// The spec conformance tests assert that the infrastructure types
// can be submitted to the API server without any errors.
var _ = Describe("Spec conformance tests", func() {
var (
obj runtimeObject
key *client.ObjectKey
)
// assertObjEventuallyExists is used to assert that eventually obj can be
// retrieved from the API server.
assertObjEventuallyExists := func() {
EventuallyWithOffset(1, func() error {
return k8sClient.Get(ctx, *key, obj)
}, time.Second*30).Should(Succeed())
}
JustBeforeEach(func() {
Expect(k8sClient.Create(ctx, obj)).To(Succeed())
key = &client.ObjectKey{
Namespace: obj.GetNamespace(),
Name: obj.GetName(),
}
})
JustAfterEach(func() {
Expect(k8sClient.Delete(ctx, obj)).To(Succeed())
})
AfterEach(func() {
obj = nil
key = nil
})
Context(infraClusterTypeName, func() {
BeforeEach(func() {
obj = newInfraCluster()
})
It("Will be created and wait on an OwnerRef", func() {
assertObjEventuallyExists()
})
})
Context(infraMachineTypeName, func() {
BeforeEach(func() {
obj = newInfraMachine()
})
It("Will be created and wait on an OwnerRef", func() {
assertObjEventuallyExists()
})
})
})
// Verifies that the infrastructure types have finalizers set when an OwnerRef
// is set that points to the corresponding CAPI resource.
var _ = Describe("Reconciler tests", func() {
Specify("Infrastructure resources should have finalizers after reconciliation", func() {
// assertEventuallyFinalizers is used to assert an object eventually has one or more
// finalizers.
assertEventuallyFinalizers := func(key client.ObjectKey, obj runtimeObject) {
EventuallyWithOffset(1, func() (int, error) {
if err := k8sClient.Get(ctx, key, obj); err != nil {
return 0, err
}
return len(obj.GetFinalizers()), nil
}, time.Second*30).Should(BeNumerically(">", 0))
}
By("Create the CAPI Cluster and wait for it to exist")
// A finalizer is added to prevent it from being deleted until its
// dependents are removed.
cluster := &clusterv1.Cluster{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
Namespace: namespace,
Finalizers: []string{"test"},
},
Spec: clusterv1.ClusterSpec{},
}
Expect(k8sClient.Create(ctx, cluster)).To(Succeed())
clusterKey := client.ObjectKey{Namespace: cluster.Namespace, Name: cluster.Name}
Eventually(func() error {
return k8sClient.Get(ctx, clusterKey, cluster)
}, time.Second*30).Should(Succeed())
By("Create the infrastructure cluster and wait for it to have a finalizer")
infraCluster := newInfraCluster()
infraCluster.SetOwnerReferences([]metav1.OwnerReference{
metav1.OwnerReference{
APIVersion: cluster.APIVersion,
Kind: cluster.Kind,
Name: cluster.Name,
UID: cluster.UID,
},
})
Expect(k8sClient.Create(ctx, infraCluster)).To(Succeed())
// Assert that eventually the infrastructure cluster will have a
// finalizer.
infraClusterKey := client.ObjectKey{Namespace: infraCluster.GetNamespace(), Name: infraCluster.GetName()}
assertEventuallyFinalizers(infraClusterKey, infraCluster)
By("Update the CAPI Cluster's InfrastructureRef")
cluster.Spec.InfrastructureRef = &corev1.ObjectReference{
APIVersion: infraCluster.GetObjectKind().GroupVersionKind().GroupVersion().String(),
Kind: infraCluster.GetObjectKind().GroupVersionKind().Kind,
Name: infraCluster.GetName(),
}
Expect(k8sClient.Update(ctx, cluster)).To(Succeed())
By("Create the CAPI Machine and wait for it to exist")
// A finalizer is added to prevent it from being deleted until its
// dependents are removed.
machine := &clusterv1.Machine{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
Namespace: namespace,
Finalizers: []string{"test"},
Labels: map[string]string{
clusterv1.MachineClusterLabelName: cluster.Name,
},
OwnerReferences: []metav1.OwnerReference{
metav1.OwnerReference{
APIVersion: cluster.APIVersion,
Kind: cluster.Kind,
Name: cluster.Name,
UID: cluster.UID,
},
},
},
Spec: clusterv1.MachineSpec{},
}
Expect(k8sClient.Create(ctx, machine)).To(Succeed())
machineKey := client.ObjectKey{Namespace: machine.Namespace, Name: machine.Name}
Eventually(func() error {
return k8sClient.Get(ctx, machineKey, machine)
}, time.Second*30).Should(Succeed())
By("Create the infrastructure machine and wait for it to have a finalizer")
infraMachine := newInfraMachine()
infraMachine.SetOwnerReferences([]metav1.OwnerReference{
metav1.OwnerReference{
APIVersion: machine.APIVersion,
Kind: machine.Kind,
Name: machine.Name,
UID: machine.UID,
},
})
Expect(k8sClient.Create(ctx, infraMachine)).To(Succeed())
infraMachineKey := client.ObjectKey{Namespace: infraMachine.GetNamespace(), Name: infraMachine.GetName()}
assertEventuallyFinalizers(infraMachineKey, infraMachine)
deleteAndWait := func(key client.ObjectKey, obj runtimeObject, removeFinalizers bool) {
// Delete the object.
Expect(k8sClient.Delete(ctx, obj)).To(Succeed())
// Issues updates until the patch to remove the finalizers is
// successful.
if removeFinalizers {
EventuallyWithOffset(1, func() error {
if err := k8sClient.Get(ctx, key, obj); err != nil {
return err
}
obj.SetFinalizers([]string{})
return k8sClient.Update(ctx, obj)
}, time.Second*30).Should(Succeed())
}
// Wait for the object to no longer be available.
EventuallyWithOffset(1, func() error {
return k8sClient.Get(ctx, key, obj)
}, time.Second*30).ShouldNot(Succeed())
}
// Delete the CAPI Cluster. To simulate the CAPI components we must:
//
// 1. Delete a resource.
// 2. Remove its finalizers (if its a CAPI object).
// 3. Update the resource.
// 4. Wait for the resource to be deleted.
By("Delete the infrastructure machine and wait for it to be removed")
deleteAndWait(infraMachineKey, infraMachine, false)
By("Delete the CAPI machine and wait for it to be removed")
deleteAndWait(machineKey, machine, true)
By("Delete the infrastructure cluster and wait for it to be removed")
deleteAndWait(infraClusterKey, infraCluster, false)
By("Delete the CAPI cluster and wait for it to be removed")
deleteAndWait(clusterKey, cluster, true)
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment