Skip to content

Commit

Permalink
Implement Detached/Status Annotation E2E Test
Browse files Browse the repository at this point in the history
Validate BMH status retention across delete & recreate with annotations.
  • Loading branch information
maxrantil committed Nov 8, 2023
1 parent 74bfd27 commit 24269c8
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 2 deletions.
146 changes: 144 additions & 2 deletions test/e2e/basic_provisioning_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@ package e2e

import (
"context"
"encoding/json"
"fmt"
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/cluster-api/test/framework"
"sigs.k8s.io/cluster-api/util"
"sigs.k8s.io/cluster-api/util/patch"
"sigs.k8s.io/controller-runtime/pkg/client"

metal3api "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
)

var _ = Describe("Provisioning", func() {
var _ = Describe("BMH Provisioning and Annotation Management", func() {
var (
specName = "provisioning-ops"
namespace *corev1.Namespace
Expand All @@ -40,7 +45,7 @@ var _ = Describe("Provisioning", func() {
})
})

It("should provision and then deprovision a BMH", func() {
It("provisions a BMH, applies detached and status annotations, then deprovisions", func() {
By("Creating a secret with BMH credentials")
bmcCredentials := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -118,6 +123,143 @@ var _ = Describe("Provisioning", func() {
State: metal3api.StateProvisioned,
}, e2eConfig.GetIntervals(specName, "wait-provisioned")...)

By("Retrieving the latest BMH object")
err = clusterProxy.GetClient().Get(ctx, client.ObjectKey{
Name: bmh.Name,
Namespace: bmh.Namespace,
}, &bmh)
Expect(err).NotTo(HaveOccurred())

By("Adding the detached annotation")
helper, err = patch.NewHelper(&bmh, clusterProxy.GetClient())
Expect(err).NotTo(HaveOccurred())

// Add the detached annotation; "true" is used explicitly to clarify intent.
bmh.ObjectMeta.Annotations["baremetalhost.metal3.io/detached"] = "true"

Expect(helper.Patch(ctx, &bmh)).To(Succeed())

By("Saving the status to a JSON string")
savedStatus := bmh.Status
statusJSON, err := json.Marshal(savedStatus)
Expect(err).NotTo(HaveOccurred())

By("Deleting the BMH")
//Wait for 2 seconds to allow time to confirm annotation is set
time.Sleep(2 * time.Second)

err = clusterProxy.GetClient().Delete(ctx, &bmh)
Expect(err).NotTo(HaveOccurred())

By("Waiting for the deleting state")
Eventually(func() (string, error) {
var currentBmh metal3api.BareMetalHost
err := clusterProxy.GetClient().Get(ctx, types.NamespacedName{Name: bmh.Name, Namespace: bmh.Namespace}, &currentBmh)
if err != nil {
if apierrors.IsNotFound(err) {
// If the BMH is not found, we assume it has been successfully deleted.
return "deleting", nil
}
// Any other error should be returned.
return "", err
}

currentStatus := currentBmh.Status.Provisioning.State

// If the state is 'deleting', we return this state to finish the polling.
if currentStatus == "deleting" {
return "deleting", nil
}

// If the state is empty or 'provisioned', we continue polling.
if currentStatus == "" || currentStatus == "provisioned" {
return "", nil
}

// Any other state is unexpected, and we stop the polling.
return "", StopTrying(fmt.Sprintf("BMH is in an unexpected state: %s", currentStatus))
}, e2eConfig.GetIntervals(specName, "wait-deleting")...).Should(Equal("deleting"))

By("Waiting for the secret to be deleted")
Eventually(func() bool {
err := clusterProxy.GetClient().Get(ctx, client.ObjectKey{
Name: "bmc-credentials",
Namespace: namespace.Name,
}, &corev1.Secret{})
return apierrors.IsNotFound(err)
}, e2eConfig.GetIntervals(specName, "wait-secret-deletion")...).Should(BeTrue())

By("Creating a secret with BMH credentials")
secretName := "bmc-credentials"
bmcCredentials = corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: namespace.Name,
},
StringData: map[string]string{
"username": bmcUser,
"password": bmcPassword,
},
}

err = clusterProxy.GetClient().Create(ctx, &bmcCredentials)
Expect(err).NotTo(HaveOccurred())

By("Recreating the BMH with the previously saved status in the status annotation")
bmh = metal3api.BareMetalHost{
ObjectMeta: metav1.ObjectMeta{
Name: specName,
Namespace: namespace.Name,
Annotations: map[string]string{
metal3api.StatusAnnotation: string(statusJSON),
},
},
Spec: metal3api.BareMetalHostSpec{
Online: true,
BMC: metal3api.BMCDetails{
Address: bmcAddress,
CredentialsName: "bmc-credentials",
},
BootMode: metal3api.Legacy,
BootMACAddress: bootMacAddress,
Image: &metal3api.Image{
URL: e2eConfig.GetVariable("IMAGE_URL"),
Checksum: e2eConfig.GetVariable("IMAGE_CHECKSUM"),
},
RootDeviceHints: &metal3api.RootDeviceHints{
DeviceName: "/dev/vda",
},
},
}

err = clusterProxy.GetClient().Create(ctx, &bmh)
Expect(err).NotTo(HaveOccurred())

By("Waiting for the 'provisioned' state")
Eventually(func() (string, error) {
var currentBmh metal3api.BareMetalHost
err := clusterProxy.GetClient().Get(ctx, types.NamespacedName{Name: bmh.Name, Namespace: bmh.Namespace}, &currentBmh)
if err != nil {
// Handle errors that may occur while fetching the BMH.
return "", err
}

currentStatus := currentBmh.Status.Provisioning.State

// If the state is 'provisioned', we return this state to finish the polling.
if currentStatus == "provisioned" {
return "provisioned", nil
}

// If the state is empty, we continue polling.
if currentStatus == "" {
return "", nil
}

// Any other state is unexpected, and we stop the polling.
return "", StopTrying(fmt.Sprintf("BMH should not be in '%s' state", currentStatus))
}, e2eConfig.GetIntervals(specName, "wait-provisioned")...).Should(Equal("provisioned"))

By("Triggering the deprovisioning of the BMH")
helper, err = patch.NewHelper(&bmh, clusterProxy.GetClient())
Expect(err).NotTo(HaveOccurred())
Expand Down
2 changes: 2 additions & 0 deletions test/e2e/config/fixture.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,5 @@ intervals:
default/wait-provisioned: ["20s", "1s"]
default/wait-deprovisioning: ["5s", "10ms"]
default/wait-available: ["20s", "1s"]
default/wait-deleting: ["5s", "10ms"]
default/wait-secret-deletion: ["5s", "10ms"]
2 changes: 2 additions & 0 deletions test/e2e/config/ironic.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ intervals:
default/wait-provisioning: ["20s", "1s"]
default/wait-provisioned: ["5m", "1s"]
default/wait-deprovisioning: ["1m", "10ms"]
default/wait-deleting: ["1m", "1s"]
default/wait-secret-deletion: ["1m", "1s"]

0 comments on commit 24269c8

Please sign in to comment.