796 lines
27 KiB
Go
796 lines
27 KiB
Go
/*
|
|
Copyright © 2022 - 2025 SUSE LLC
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package controllers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"time"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/utils/ptr"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
|
|
elementalv1 "github.com/rancher/elemental-operator/api/v1beta1"
|
|
systemagent "github.com/rancher/elemental-operator/internal/system-agent"
|
|
"github.com/rancher/elemental-operator/pkg/network"
|
|
"github.com/rancher/elemental-operator/pkg/test"
|
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
)
|
|
|
|
var _ = Describe("reconcile machine inventory", func() {
|
|
var r *MachineInventoryReconciler
|
|
var mInventory *elementalv1.MachineInventory
|
|
var planSecret *corev1.Secret
|
|
|
|
BeforeEach(func() {
|
|
r = &MachineInventoryReconciler{
|
|
Client: cl,
|
|
}
|
|
|
|
mInventory = &elementalv1.MachineInventory{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Finalizers: []string{elementalv1.MachineInventoryFinalizer},
|
|
Name: "machine-inventory-suite",
|
|
Namespace: "default",
|
|
},
|
|
}
|
|
|
|
planSecret = &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: mInventory.Namespace,
|
|
Name: mInventory.Name,
|
|
},
|
|
Data: map[string][]byte{
|
|
"applied-checksum": []byte("appliedchecksum"),
|
|
},
|
|
}
|
|
|
|
Expect(cl.Create(ctx, mInventory)).To(Succeed())
|
|
})
|
|
|
|
AfterEach(func() {
|
|
Expect(test.CleanupAndWait(ctx, cl, mInventory, planSecret)).To(Succeed())
|
|
})
|
|
|
|
It("should reconcile machine inventory object when plan secret doesn't exist", func() {
|
|
_, err := r.Reconcile(ctx, reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: mInventory.Namespace,
|
|
Name: mInventory.Name,
|
|
},
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Expect(cl.Get(ctx, client.ObjectKey{
|
|
Name: mInventory.Name,
|
|
Namespace: mInventory.Namespace,
|
|
}, mInventory)).To(Succeed())
|
|
|
|
cond := meta.FindStatusCondition(mInventory.Status.Conditions, elementalv1.ReadyCondition)
|
|
Expect(cond).NotTo(BeNil())
|
|
|
|
Expect(cond.Reason).To(Equal(elementalv1.WaitingForPlanReason))
|
|
Expect(cond.Status).To(Equal(metav1.ConditionFalse))
|
|
Expect(cond.Message).To(Equal("waiting for plan to be applied"))
|
|
})
|
|
|
|
It("should reconcile machine inventory object when plan secret already exists", func() {
|
|
Expect(cl.Create(ctx, planSecret)).To(Succeed())
|
|
|
|
_, err := r.Reconcile(ctx, reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: mInventory.Namespace,
|
|
Name: mInventory.Name,
|
|
},
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Expect(cl.Get(ctx, client.ObjectKey{
|
|
Name: mInventory.Name,
|
|
Namespace: mInventory.Namespace,
|
|
}, mInventory)).To(Succeed())
|
|
|
|
Expect(mInventory.Status.Plan.Checksum).To(Equal(string(planSecret.Data["applied-checksum"])))
|
|
Expect(mInventory.Status.Plan.State).To(Equal(elementalv1.PlanApplied))
|
|
|
|
cond := meta.FindStatusCondition(mInventory.Status.Conditions, elementalv1.ReadyCondition)
|
|
Expect(cond).NotTo(BeNil())
|
|
|
|
Expect(cond.Reason).To(Equal(elementalv1.PlanSuccessfullyAppliedReason))
|
|
Expect(cond.Status).To(Equal(metav1.ConditionTrue))
|
|
Expect(cond.Message).To(Equal("plan successfully applied"))
|
|
})
|
|
|
|
It("should add finalizer if not exist", func() {
|
|
noFinalizerMI := &elementalv1.MachineInventory{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "machine-inventory-no-finalizer",
|
|
Namespace: "default",
|
|
},
|
|
}
|
|
Expect(cl.Create(ctx, noFinalizerMI)).To(Succeed())
|
|
|
|
_, err := r.Reconcile(ctx, reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: mInventory.Namespace,
|
|
Name: mInventory.Name,
|
|
},
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Expect(cl.Get(ctx, client.ObjectKey{
|
|
Name: mInventory.Name,
|
|
Namespace: mInventory.Namespace,
|
|
}, noFinalizerMI)).To(Succeed())
|
|
|
|
Expect(controllerutil.ContainsFinalizer(noFinalizerMI, elementalv1.MachineInventoryFinalizer)).To(BeTrue())
|
|
Expect(test.CleanupAndWait(ctx, cl, noFinalizerMI)).To(Succeed())
|
|
})
|
|
})
|
|
|
|
var _ = Describe("createPlanSecret", func() {
|
|
var r *MachineInventoryReconciler
|
|
var mInventory *elementalv1.MachineInventory
|
|
var planSecret *corev1.Secret
|
|
|
|
BeforeEach(func() {
|
|
r = &MachineInventoryReconciler{
|
|
Client: cl,
|
|
}
|
|
|
|
mInventory = &elementalv1.MachineInventory{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "machine-inventory-suite",
|
|
Namespace: "default",
|
|
},
|
|
}
|
|
|
|
planSecret = &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: mInventory.Namespace,
|
|
Name: mInventory.Name,
|
|
},
|
|
}
|
|
|
|
Expect(cl.Create(ctx, mInventory)).To(Succeed())
|
|
})
|
|
|
|
AfterEach(func() {
|
|
Expect(test.CleanupAndWait(ctx, cl, mInventory, planSecret)).To(Succeed())
|
|
})
|
|
|
|
It("should succesfully create plan secret", func() {
|
|
Expect(r.createPlanSecret(ctx, mInventory)).To(Succeed())
|
|
|
|
Expect(r.Get(ctx, types.NamespacedName{Namespace: mInventory.Namespace, Name: mInventory.Name}, planSecret)).To(Succeed())
|
|
|
|
Expect(planSecret.OwnerReferences).To(HaveLen(1))
|
|
Expect(planSecret.OwnerReferences[0].APIVersion).To(Equal(elementalv1.GroupVersion.String()))
|
|
Expect(planSecret.OwnerReferences[0].Kind).To(Equal("MachineInventory"))
|
|
Expect(planSecret.OwnerReferences[0].Name).To(Equal(mInventory.Name))
|
|
Expect(planSecret.OwnerReferences[0].UID).To(Equal(mInventory.UID))
|
|
Expect(planSecret.OwnerReferences[0].Controller).To(Equal(ptr.To(true)))
|
|
Expect(planSecret.Labels).To(HaveKey(elementalv1.ElementalManagedLabel))
|
|
Expect(planSecret.Type).To(Equal(elementalv1.PlanSecretType))
|
|
Expect(planSecret.Data).To(HaveKey("plan"))
|
|
|
|
Expect(mInventory.Status.Plan.PlanSecretRef.Name).To(Equal(mInventory.Name))
|
|
Expect(mInventory.Status.Plan.PlanSecretRef.Namespace).To(Equal(mInventory.Namespace))
|
|
|
|
cond := meta.FindStatusCondition(mInventory.Status.Conditions, elementalv1.ReadyCondition)
|
|
Expect(cond).NotTo(BeNil())
|
|
|
|
Expect(cond.Reason).To(Equal(elementalv1.WaitingForPlanReason))
|
|
Expect(cond.Status).To(Equal(metav1.ConditionFalse))
|
|
Expect(cond.Message).To(Equal("waiting for plan to be applied"))
|
|
})
|
|
|
|
It("shouldn't return error is secret already exists", func() {
|
|
Expect(r.createPlanSecret(ctx, mInventory)).To(Succeed())
|
|
Expect(r.createPlanSecret(ctx, mInventory)).To(Succeed())
|
|
})
|
|
|
|
It("should return error is secret fails to be created", func() {
|
|
r.Client = machineInventoryFailingClient{}
|
|
err := r.createPlanSecret(ctx, mInventory)
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err.Error()).To(ContainSubstring("failed to create"))
|
|
})
|
|
|
|
It("should do nothing if ready condition is present", func() {
|
|
meta.SetStatusCondition(&mInventory.Status.Conditions, metav1.Condition{
|
|
Type: elementalv1.ReadyCondition,
|
|
Reason: elementalv1.PlanSuccessfullyAppliedReason,
|
|
Status: metav1.ConditionTrue,
|
|
Message: "plan successfully applied",
|
|
})
|
|
Expect(r.createPlanSecret(ctx, mInventory)).To(Succeed())
|
|
})
|
|
|
|
It("should not reset network if configurator none", func() {
|
|
inventoryWithNoneNetwork := &elementalv1.MachineInventory{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "machine-inventory-network-none",
|
|
Namespace: "default",
|
|
},
|
|
Spec: elementalv1.MachineInventorySpec{
|
|
Network: elementalv1.NetworkConfig{
|
|
Configurator: network.ConfiguratorNone,
|
|
},
|
|
},
|
|
}
|
|
Expect(r.Create(ctx, inventoryWithNoneNetwork)).Should(Succeed())
|
|
Expect(r.createPlanSecret(ctx, inventoryWithNoneNetwork)).To(Succeed())
|
|
Expect(r.Get(ctx, types.NamespacedName{Namespace: inventoryWithNoneNetwork.Namespace, Name: inventoryWithNoneNetwork.Name}, planSecret)).To(Succeed())
|
|
Expect(r.updatePlanSecretWithReset(ctx, inventoryWithNoneNetwork, planSecret)).Should(Succeed())
|
|
Expect(r.Get(ctx, types.NamespacedName{Namespace: inventoryWithNoneNetwork.Namespace, Name: inventoryWithNoneNetwork.Name}, planSecret)).To(Succeed())
|
|
planData, found := planSecret.Data["plan"]
|
|
Expect(found).To(BeTrue(), "Secret should contain reset plan data")
|
|
|
|
plan := &systemagent.Plan{}
|
|
Expect(json.Unmarshal(planData, plan)).Should(Succeed())
|
|
Expect(len(plan.OneTimeInstructions)).Should(Equal(2), "Should contain 2 reset instructions")
|
|
Expect(plan.OneTimeInstructions).ShouldNot(ContainElement(systemagent.OneTimeInstruction{
|
|
CommonInstruction: systemagent.CommonInstruction{
|
|
Name: "restore first boot config",
|
|
Command: "elemental-register",
|
|
Args: []string{"--debug", "--reset-network"},
|
|
}}))
|
|
Expect(test.CleanupAndWait(ctx, cl, inventoryWithNoneNetwork)).To(Succeed())
|
|
})
|
|
|
|
It("should reset network if configurator not none", func() {
|
|
inventoryWithNetwork := &elementalv1.MachineInventory{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "machine-inventory-network-nmc",
|
|
Namespace: "default",
|
|
},
|
|
Spec: elementalv1.MachineInventorySpec{
|
|
Network: elementalv1.NetworkConfig{
|
|
Configurator: network.ConfiguratorNmc,
|
|
},
|
|
},
|
|
}
|
|
Expect(r.Create(ctx, inventoryWithNetwork)).Should(Succeed())
|
|
Expect(r.createPlanSecret(ctx, inventoryWithNetwork)).To(Succeed())
|
|
Expect(r.Get(ctx, types.NamespacedName{Namespace: inventoryWithNetwork.Namespace, Name: inventoryWithNetwork.Name}, planSecret)).To(Succeed())
|
|
Expect(r.updatePlanSecretWithReset(ctx, inventoryWithNetwork, planSecret)).Should(Succeed())
|
|
Expect(r.Get(ctx, types.NamespacedName{Namespace: inventoryWithNetwork.Namespace, Name: inventoryWithNetwork.Name}, planSecret)).To(Succeed())
|
|
planData, found := planSecret.Data["plan"]
|
|
Expect(found).To(BeTrue(), "Secret should contain reset plan data")
|
|
|
|
plan := &systemagent.Plan{}
|
|
Expect(json.Unmarshal(planData, plan)).Should(Succeed())
|
|
Expect(plan.OneTimeInstructions).Should(ContainElement(systemagent.OneTimeInstruction{
|
|
CommonInstruction: systemagent.CommonInstruction{
|
|
Name: "restore first boot config",
|
|
Command: "elemental-register",
|
|
Args: []string{"--debug", "--reset-network"},
|
|
}}))
|
|
Expect(test.CleanupAndWait(ctx, cl, inventoryWithNetwork)).To(Succeed())
|
|
})
|
|
})
|
|
|
|
var _ = Describe("updateInventoryWithAdoptionStatus", func() {
|
|
var r *MachineInventoryReconciler
|
|
var mInventory *elementalv1.MachineInventory
|
|
var miSelector *elementalv1.MachineInventorySelector
|
|
|
|
BeforeEach(func() {
|
|
r = &MachineInventoryReconciler{
|
|
Client: cl,
|
|
}
|
|
|
|
miSelector = &elementalv1.MachineInventorySelector{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "machine-selector",
|
|
Namespace: "default",
|
|
UID: "test",
|
|
},
|
|
}
|
|
mInventory = &elementalv1.MachineInventory{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "machine-inventory-suite",
|
|
Namespace: "default",
|
|
OwnerReferences: []metav1.OwnerReference{
|
|
{
|
|
APIVersion: elementalv1.GroupVersion.String(),
|
|
Kind: "MachineInventorySelector",
|
|
Name: miSelector.ObjectMeta.Name,
|
|
UID: miSelector.ObjectMeta.UID,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
Expect(cl.Create(ctx, mInventory)).To(Succeed())
|
|
Expect(cl.Create(ctx, miSelector)).To(Succeed())
|
|
})
|
|
|
|
AfterEach(func() {
|
|
Expect(test.CleanupAndWait(ctx, cl, mInventory, miSelector)).To(Succeed())
|
|
})
|
|
|
|
It("successfully verifies owner reference in status", func() {
|
|
// Set selector reference
|
|
Expect(cl.Get(ctx, types.NamespacedName{
|
|
Namespace: miSelector.Namespace,
|
|
Name: miSelector.Name,
|
|
}, miSelector)).To(Succeed())
|
|
miSelector.Status.MachineInventoryRef = &corev1.LocalObjectReference{
|
|
Name: mInventory.Name,
|
|
}
|
|
Expect(cl.Status().Update(ctx, miSelector)).To(Succeed())
|
|
|
|
requeue, err := r.updateInventoryWithAdoptionStatus(ctx, mInventory)
|
|
Expect(err).To(Succeed())
|
|
Expect(requeue).To(BeFalse())
|
|
|
|
cond := meta.FindStatusCondition(mInventory.Status.Conditions, elementalv1.AdoptionReadyCondition)
|
|
Expect(cond).NotTo(BeNil())
|
|
Expect(cond.Status).To(Equal(metav1.ConditionTrue))
|
|
Expect(cond.Reason).To(Equal(elementalv1.SuccessfullyAdoptedReason))
|
|
})
|
|
|
|
It("reaches adoption validation timeout", func() {
|
|
|
|
testTimeout := time.Now().Add(adoptionTimeout * 2 * time.Second)
|
|
requeue, err := r.updateInventoryWithAdoptionStatus(ctx, mInventory)
|
|
for err == nil {
|
|
Expect(err).To(Succeed())
|
|
Expect(requeue).To(BeTrue())
|
|
Expect(time.Now().Before(testTimeout)).To(BeTrue())
|
|
time.Sleep(time.Second)
|
|
requeue, err = r.updateInventoryWithAdoptionStatus(ctx, mInventory)
|
|
}
|
|
Expect(err.Error()).To(ContainSubstring("Adoption timeout"))
|
|
|
|
cond := meta.FindStatusCondition(mInventory.Status.Conditions, elementalv1.AdoptionReadyCondition)
|
|
Expect(cond).NotTo(BeNil())
|
|
Expect(cond.Status).To(Equal(metav1.ConditionUnknown))
|
|
Expect(cond.Reason).To(Equal(elementalv1.ValidatingAdoptionReason))
|
|
})
|
|
|
|
It("detects an adoption mismatch", func() {
|
|
// Set selector reference
|
|
Expect(cl.Get(ctx, types.NamespacedName{
|
|
Namespace: miSelector.Namespace,
|
|
Name: miSelector.Name,
|
|
}, miSelector)).To(Succeed())
|
|
miSelector.Status.MachineInventoryRef = &corev1.LocalObjectReference{
|
|
Name: "unexpectedName",
|
|
}
|
|
Expect(cl.Status().Update(ctx, miSelector)).To(Succeed())
|
|
|
|
requeue, err := r.updateInventoryWithAdoptionStatus(ctx, mInventory)
|
|
Expect(err).NotTo(Succeed())
|
|
Expect(err.Error()).To(ContainSubstring("Ownership mismatch"))
|
|
Expect(requeue).To(BeFalse())
|
|
})
|
|
|
|
It("has nothing if no owner is set", func() {
|
|
mInventory.ObjectMeta.OwnerReferences = []metav1.OwnerReference{}
|
|
|
|
requeue, err := r.updateInventoryWithAdoptionStatus(ctx, mInventory)
|
|
Expect(err).To(Succeed())
|
|
Expect(requeue).To(BeFalse())
|
|
|
|
cond := meta.FindStatusCondition(mInventory.Status.Conditions, elementalv1.AdoptionReadyCondition)
|
|
Expect(cond).NotTo(BeNil())
|
|
Expect(cond.Status).To(Equal(metav1.ConditionFalse))
|
|
Expect(cond.Reason).To(Equal(elementalv1.WaitingToBeAdoptedReason))
|
|
})
|
|
|
|
})
|
|
|
|
var _ = Describe("updateInventoryWithPlanStatus", func() {
|
|
var r *MachineInventoryReconciler
|
|
var mInventory *elementalv1.MachineInventory
|
|
var planSecret *corev1.Secret
|
|
|
|
BeforeEach(func() {
|
|
r = &MachineInventoryReconciler{
|
|
Client: cl,
|
|
}
|
|
|
|
mInventory = &elementalv1.MachineInventory{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "machine-inventory-suite",
|
|
Namespace: "default",
|
|
},
|
|
}
|
|
|
|
planSecret = &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: mInventory.Namespace,
|
|
Name: mInventory.Name,
|
|
},
|
|
}
|
|
|
|
Expect(cl.Create(ctx, mInventory)).To(Succeed())
|
|
})
|
|
|
|
AfterEach(func() {
|
|
Expect(test.CleanupAndWait(ctx, cl, mInventory, planSecret)).To(Succeed())
|
|
})
|
|
|
|
It("should succesfully update when plan is applied", func() {
|
|
planSecret.Data = map[string][]byte{
|
|
"applied-checksum": []byte("applied-checksum"),
|
|
}
|
|
Expect(cl.Create(ctx, planSecret)).To(Succeed())
|
|
|
|
mInventory.Status.Plan = &elementalv1.PlanStatus{}
|
|
Expect(r.updateInventoryWithPlanStatus(ctx, mInventory)).To(Succeed())
|
|
|
|
Expect(mInventory.Status.Plan.State).To(Equal(elementalv1.PlanApplied))
|
|
|
|
Expect(mInventory.Status.Conditions).To(HaveLen(1))
|
|
Expect(mInventory.Status.Conditions[0].Type).To(Equal(elementalv1.ReadyCondition))
|
|
Expect(mInventory.Status.Conditions[0].Reason).To(Equal(elementalv1.PlanSuccessfullyAppliedReason))
|
|
Expect(mInventory.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue))
|
|
Expect(mInventory.Status.Conditions[0].Message).To(Equal("plan successfully applied"))
|
|
})
|
|
|
|
It("should return error when plan failed to be applied", func() {
|
|
planSecret.Data = map[string][]byte{
|
|
"failed-checksum": []byte("failed-checksum"),
|
|
}
|
|
Expect(cl.Create(ctx, planSecret)).To(Succeed())
|
|
|
|
mInventory.Status.Plan = &elementalv1.PlanStatus{}
|
|
err := r.updateInventoryWithPlanStatus(ctx, mInventory)
|
|
Expect(err).To(HaveOccurred())
|
|
Expect(err.Error()).To(ContainSubstring("failed to apply plan"))
|
|
})
|
|
|
|
AfterEach(func() {
|
|
Expect(test.CleanupAndWait(ctx, cl, mInventory, planSecret)).To(Succeed())
|
|
})
|
|
|
|
})
|
|
|
|
var _ = Describe("handle finalizer", func() {
|
|
var r *MachineInventoryReconciler
|
|
var mInventory *elementalv1.MachineInventory
|
|
var planSecret *corev1.Secret
|
|
|
|
BeforeEach(func() {
|
|
r = &MachineInventoryReconciler{
|
|
Client: cl,
|
|
}
|
|
|
|
mInventory = &elementalv1.MachineInventory{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
DeletionTimestamp: &metav1.Time{Time: time.Now()},
|
|
Annotations: map[string]string{elementalv1.MachineInventoryResettableAnnotation: "true"},
|
|
Finalizers: []string{elementalv1.MachineInventoryFinalizer},
|
|
Name: "machine-inventory-suite-finalizer",
|
|
Namespace: "default",
|
|
},
|
|
}
|
|
|
|
planSecret = &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: mInventory.Name,
|
|
Namespace: mInventory.Namespace,
|
|
},
|
|
}
|
|
|
|
// 1. Create initial MachineInventory
|
|
Expect(cl.Create(ctx, mInventory)).To(Succeed())
|
|
// 2. Create initial plan Secret
|
|
_, err := r.Reconcile(ctx, reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: mInventory.Namespace,
|
|
Name: mInventory.Name,
|
|
},
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
// 3. Update meta.DeletionTimestamp
|
|
Expect(cl.Delete(ctx, mInventory)).To(Succeed())
|
|
// 4. Update secret with reset plan
|
|
_, err = r.Reconcile(ctx, reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: mInventory.Namespace,
|
|
Name: mInventory.Name,
|
|
},
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
// 5. Update MachineInventory plan status
|
|
_, err = r.Reconcile(ctx, reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: mInventory.Namespace,
|
|
Name: mInventory.Name,
|
|
},
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("should update secret with reset plan", func() {
|
|
Expect(cl.Get(ctx, client.ObjectKey{
|
|
Name: planSecret.Name,
|
|
Namespace: planSecret.Namespace,
|
|
}, planSecret)).To(Succeed())
|
|
Expect(cl.Get(ctx, client.ObjectKey{
|
|
Name: mInventory.Name,
|
|
Namespace: mInventory.Namespace,
|
|
}, mInventory)).To(Succeed())
|
|
|
|
wantChecksum, wantPlan, err := r.newResetPlan(ctx, false)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Check Plan status
|
|
Expect(mInventory.Status.Plan.Checksum).To(Equal(wantChecksum))
|
|
Expect(mInventory.Status.Plan.PlanSecretRef.Name).To(Equal(planSecret.Name))
|
|
Expect(mInventory.Status.Plan.PlanSecretRef.Namespace).To(Equal(planSecret.Namespace))
|
|
Expect(mInventory.Status.Plan.State).To(Equal(elementalv1.PlanState("")))
|
|
|
|
// Check MachineInventory status
|
|
Expect(mInventory.Status.Conditions[0].Type).To(Equal(elementalv1.ReadyCondition))
|
|
Expect(mInventory.Status.Conditions[0].Reason).To(Equal(elementalv1.WaitingForPlanReason))
|
|
Expect(mInventory.Status.Conditions[0].Status).To(Equal(metav1.ConditionFalse))
|
|
Expect(mInventory.Status.Conditions[0].Message).To(Equal("waiting for plan to be applied"))
|
|
|
|
// Check plan secret was updated
|
|
Expect(planSecret.Annotations[elementalv1.PlanTypeAnnotation]).To(Equal(elementalv1.PlanTypeReset))
|
|
Expect(planSecret.Data["plan"]).To(Equal(wantPlan))
|
|
Expect(planSecret.Data["applied-checksum"]).To(Equal([]byte("")))
|
|
Expect(planSecret.Data["failed-checksum"]).To(Equal([]byte("")))
|
|
|
|
// Check we are holding on the MachineInventory (preventing actual deletion)
|
|
_, err = r.Reconcile(ctx, reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: mInventory.Namespace,
|
|
Name: mInventory.Name,
|
|
},
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(cl.Get(ctx, client.ObjectKey{
|
|
Name: mInventory.Name,
|
|
Namespace: mInventory.Namespace,
|
|
}, mInventory)).To(Succeed())
|
|
})
|
|
|
|
It("should remove finalizer on reset plan applied", func() {
|
|
// 6. Mark the reset plan as applied
|
|
Expect(cl.Get(ctx, client.ObjectKey{
|
|
Name: mInventory.Name,
|
|
Namespace: mInventory.Namespace,
|
|
}, planSecret)).To(Succeed())
|
|
planSecret.Data["applied-checksum"] = []byte("applied")
|
|
Expect(cl.Update(ctx, planSecret)).To(Succeed())
|
|
|
|
// 7. Trigger finalizer removal
|
|
_, err := r.Reconcile(ctx, reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: mInventory.Namespace,
|
|
Name: mInventory.Name,
|
|
},
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Check MachineInventory was actually deleted
|
|
err = cl.Get(ctx, client.ObjectKey{
|
|
Name: mInventory.Name,
|
|
Namespace: mInventory.Namespace,
|
|
}, mInventory)
|
|
Expect(apierrors.IsNotFound(err)).To(BeTrue())
|
|
})
|
|
|
|
It("should delete by removing finalizer when resettable annotation is false", func() {
|
|
// 6. Manually disable resettable annotation
|
|
Expect(cl.Get(ctx, client.ObjectKey{
|
|
Name: mInventory.Name,
|
|
Namespace: mInventory.Namespace,
|
|
}, mInventory)).To(Succeed())
|
|
mInventory.Annotations[elementalv1.MachineInventoryResettableAnnotation] = "false"
|
|
Expect(cl.Update(ctx, mInventory)).To(Succeed())
|
|
|
|
// 7. Trigger finalizer removal
|
|
_, err := r.Reconcile(ctx, reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: mInventory.Namespace,
|
|
Name: mInventory.Name,
|
|
},
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Check MachineInventory was actually deleted
|
|
err = cl.Get(ctx, client.ObjectKey{
|
|
Name: mInventory.Name,
|
|
Namespace: mInventory.Namespace,
|
|
}, mInventory)
|
|
Expect(apierrors.IsNotFound(err)).To(BeTrue())
|
|
})
|
|
|
|
It("should delete by removing finalizer when up for deletion", func() {
|
|
// 6. Manually remove finalizer
|
|
Expect(cl.Get(ctx, client.ObjectKey{
|
|
Name: mInventory.Name,
|
|
Namespace: mInventory.Namespace,
|
|
}, mInventory)).To(Succeed())
|
|
controllerutil.RemoveFinalizer(mInventory, elementalv1.MachineInventoryFinalizer)
|
|
Expect(cl.Update(ctx, mInventory)).To(Succeed())
|
|
|
|
// Check MachineInventory was actually deleted
|
|
err := cl.Get(ctx, client.ObjectKey{
|
|
Name: mInventory.Name,
|
|
Namespace: mInventory.Namespace,
|
|
}, mInventory)
|
|
Expect(apierrors.IsNotFound(err)).To(BeTrue())
|
|
})
|
|
|
|
AfterEach(func() {
|
|
Expect(test.CleanupAndWait(ctx, cl, mInventory, planSecret)).To(Succeed())
|
|
})
|
|
})
|
|
|
|
var _ = Describe("handle unmanaged finalizer", func() {
|
|
var r *MachineInventoryReconciler
|
|
var mInventory *elementalv1.MachineInventory
|
|
var planSecret *corev1.Secret
|
|
|
|
BeforeEach(func() {
|
|
r = &MachineInventoryReconciler{
|
|
Client: cl,
|
|
}
|
|
|
|
mInventory = &elementalv1.MachineInventory{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
DeletionTimestamp: &metav1.Time{Time: time.Now()},
|
|
Annotations: map[string]string{elementalv1.MachineInventoryResettableAnnotation: "true", elementalv1.MachineInventoryOSUnmanagedAnnotation: "true"},
|
|
Finalizers: []string{elementalv1.MachineInventoryFinalizer},
|
|
Name: "machine-inventory-suite-finalizer",
|
|
Namespace: "default",
|
|
},
|
|
}
|
|
|
|
planSecret = &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: mInventory.Name,
|
|
Namespace: mInventory.Namespace,
|
|
},
|
|
}
|
|
|
|
// 1. Create initial MachineInventory
|
|
Expect(cl.Create(ctx, mInventory)).To(Succeed())
|
|
// 2. Create initial plan Secret
|
|
_, err := r.Reconcile(ctx, reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: mInventory.Namespace,
|
|
Name: mInventory.Name,
|
|
},
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
// 3. Update meta.DeletionTimestamp
|
|
Expect(cl.Delete(ctx, mInventory)).To(Succeed())
|
|
// 4. Update secret with reset plan
|
|
_, err = r.Reconcile(ctx, reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: mInventory.Namespace,
|
|
Name: mInventory.Name,
|
|
},
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
// 5. Update MachineInventory plan status
|
|
_, err = r.Reconcile(ctx, reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: mInventory.Namespace,
|
|
Name: mInventory.Name,
|
|
},
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("should update secret with reset plan when unmanaged annotation is true", func() {
|
|
// Manually enable os.unmanaged annotation
|
|
Expect(cl.Get(ctx, client.ObjectKey{
|
|
Name: planSecret.Name,
|
|
Namespace: planSecret.Namespace,
|
|
}, planSecret)).To(Succeed())
|
|
Expect(cl.Get(ctx, client.ObjectKey{
|
|
Name: mInventory.Name,
|
|
Namespace: mInventory.Namespace,
|
|
}, mInventory)).To(Succeed())
|
|
mInventory.Annotations[elementalv1.MachineInventoryOSUnmanagedAnnotation] = "true"
|
|
Expect(cl.Update(ctx, mInventory)).To(Succeed())
|
|
|
|
_, wantPlan, err := r.newUnmanagedResetPlan(ctx)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Check Plan status
|
|
Expect(mInventory.Status.Plan.PlanSecretRef.Name).To(Equal(planSecret.Name))
|
|
Expect(mInventory.Status.Plan.PlanSecretRef.Namespace).To(Equal(planSecret.Namespace))
|
|
Expect(mInventory.Status.Plan.State).To(Equal(elementalv1.PlanState("")))
|
|
|
|
// Check MachineInventory status
|
|
Expect(mInventory.Status.Conditions[0].Type).To(Equal(elementalv1.ReadyCondition))
|
|
Expect(mInventory.Status.Conditions[0].Reason).To(Equal(elementalv1.WaitingForPlanReason))
|
|
Expect(mInventory.Status.Conditions[0].Status).To(Equal(metav1.ConditionFalse))
|
|
Expect(mInventory.Status.Conditions[0].Message).To(Equal("waiting for plan to be applied"))
|
|
|
|
// Check plan secret was updated
|
|
Expect(planSecret.Annotations[elementalv1.PlanTypeAnnotation]).To(Equal(elementalv1.PlanTypeReset))
|
|
Expect(string(planSecret.Data["plan"])).To(Equal(string(wantPlan)))
|
|
Expect(string(planSecret.Data["applied-checksum"])).To(Equal(""))
|
|
Expect(string(planSecret.Data["failed-checksum"])).To(Equal(""))
|
|
|
|
// Check we are holding on the MachineInventory (preventing actual deletion)
|
|
_, err = r.Reconcile(ctx, reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: mInventory.Namespace,
|
|
Name: mInventory.Name,
|
|
},
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(cl.Get(ctx, client.ObjectKey{
|
|
Name: mInventory.Name,
|
|
Namespace: mInventory.Namespace,
|
|
}, mInventory)).To(Succeed())
|
|
})
|
|
|
|
It("should remove finalizer on unmanaged reset plan applied", func() {
|
|
// 6. Mark the reset plan as applied
|
|
Expect(cl.Get(ctx, client.ObjectKey{
|
|
Name: mInventory.Name,
|
|
Namespace: mInventory.Namespace,
|
|
}, planSecret)).To(Succeed())
|
|
mInventory.Annotations[elementalv1.MachineInventoryOSUnmanagedAnnotation] = "true"
|
|
planSecret.Data["applied-checksum"] = []byte("applied")
|
|
Expect(cl.Update(ctx, planSecret)).To(Succeed())
|
|
|
|
// 7. Trigger finalizer removal
|
|
_, err := r.Reconcile(ctx, reconcile.Request{
|
|
NamespacedName: types.NamespacedName{
|
|
Namespace: mInventory.Namespace,
|
|
Name: mInventory.Name,
|
|
},
|
|
})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// Check MachineInventory was actually deleted
|
|
err = cl.Get(ctx, client.ObjectKey{
|
|
Name: mInventory.Name,
|
|
Namespace: mInventory.Namespace,
|
|
}, mInventory)
|
|
Expect(apierrors.IsNotFound(err)).To(BeTrue())
|
|
})
|
|
|
|
AfterEach(func() {
|
|
Expect(test.CleanupAndWait(ctx, cl, mInventory, planSecret)).To(Succeed())
|
|
})
|
|
})
|
|
|
|
type machineInventoryFailingClient struct {
|
|
client.Client
|
|
}
|
|
|
|
func (cl machineInventoryFailingClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
|
|
return errors.New("failed to create")
|
|
}
|