Files
Tesshu FlowerandClaude Sonnet 4.5 12c257987e 🌱 add v1beta1 e2e tests for addon API - preserve v1alpha1 tests (#1553)
* test: add v1beta1 e2e tests for addon lifecycle and install strategy

Add v1beta1 e2e test coverage following the alpha/beta split pattern from
integration tests. Rename framework helper functions to use explicit
V1Alpha1/V1Beta1 suffixes for consistency.

Framework changes (test/framework/managedclusteraddon.go):
- Rename CreateManagedClusterAddOn to CreateManagedClusterAddOnV1Alpha1
- Rename CheckManagedClusterAddOnStatus to CheckManagedClusterAddOnStatusV1Alpha1
- Add CheckManagedClusterAddOnStatusV1Beta1 helper
- Organize functions with Alpha version followed by Beta counterpart

E2E test changes:
- Rename addon_test.go to addon_alpha_test.go (v1alpha1 API)
- Create new addon_test.go with v1beta1 API using V1Beta1 helpers
- Rename addon_install_test.go to addon_install_alpha_test.go (v1alpha1 API)
- Create new addon_install_test.go with v1beta1 API
- Update addon_lease_test.go, addon_token_auth_test.go, addonmanagement_test.go
  to use V1Alpha1 helpers

Test coverage (both alpha and v1beta1):
- addon_test.go: Basic ManagedClusterAddOn lifecycle and availability
- addon_install_test.go: ClusterManagementAddOn with placement-based install
  strategy, addon annotation syncing

Naming pattern matches integration tests: *_alpha_test.go for v1alpha1,
*_test.go (no suffix) for v1beta1.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Tesshu Flower <tflower@redhat.com>

* test: add v1beta1 e2e tests for addon lease health check

Add v1beta1 e2e test coverage for addon lease-based health checking
following the alpha/beta split pattern.

Changes:
- Rename addon_lease_test.go to addon_lease_alpha_test.go (v1alpha1 API)
- Create new addon_lease_test.go with v1beta1 API using V1Beta1 helpers
- Update all AddonClient.AddonV1alpha1() calls to AddonV1beta1() in beta test

Both alpha and v1beta1 tests cover:
- Addon status remains available while lease is updated
- Addon status changes to unavailable when lease stops updating
- Addon status changes to unknown when no lease exists
- Addon status changes to unknown when managed cluster lease stops updating

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Tesshu Flower <tflower@redhat.com>

* test: migrate addon_token_auth test to alpha/beta versions

- Rename addon_token_auth_test.go to addon_token_auth_alpha_test.go
- Update alpha Describe to include (v1alpha1)
- Create new addon_token_auth_test.go for v1beta1
- Update imports and client calls to use v1beta1 API

Signed-off-by: Tesshu Flower <tflower@redhat.com>

---------

Signed-off-by: Tesshu Flower <tflower@redhat.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-04 02:06:45 +00:00

338 lines
14 KiB
Go

package e2e
import (
"context"
"fmt"
"time"
"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
coordv1 "k8s.io/api/coordination/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/rand"
"k8s.io/klog/v2"
clusterv1 "open-cluster-management.io/api/cluster/v1"
operatorapiv1 "open-cluster-management.io/api/operator/v1"
"open-cluster-management.io/ocm/test/framework"
)
var _ = ginkgo.Describe("Addon Health Check (v1alpha1)", ginkgo.Label("addon-lease"), func() {
const availableLabelValue = "available"
ginkgo.Context("Checking addon lease on managed cluster to update addon status", func() {
var addOnName string
ginkgo.BeforeEach(func() {
// create an addon on created managed cluster
addOnName = fmt.Sprintf("addon-%s", rand.String(6))
ginkgo.By(fmt.Sprintf("Creating managed cluster addon %q", addOnName))
err := hub.CreateManagedClusterAddOnV1Alpha1(universalClusterName, addOnName, addOnName)
gomega.Expect(err).ToNot(gomega.HaveOccurred())
// create addon installation namespace
ginkgo.By(fmt.Sprintf("Creating managed cluster addon installation namespace %q", addOnName))
_, err = spoke.KubeClient.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: addOnName,
},
}, metav1.CreateOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
})
ginkgo.AfterEach(func() {
ginkgo.By(fmt.Sprintf("Cleaning managed cluster addon installation namespace %q", addOnName))
err := spoke.KubeClient.CoreV1().Namespaces().Delete(context.TODO(), addOnName, metav1.DeleteOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
})
ginkgo.It("Should keep addon status to available", func() {
ginkgo.By(fmt.Sprintf("Creating lease %q for managed cluster addon %q", addOnName, addOnName))
_, err := spoke.KubeClient.CoordinationV1().Leases(addOnName).Create(context.TODO(), &coordv1.Lease{
ObjectMeta: metav1.ObjectMeta{
Name: addOnName,
Namespace: addOnName,
},
Spec: coordv1.LeaseSpec{
RenewTime: &metav1.MicroTime{Time: time.Now()},
},
}, metav1.CreateOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
gomega.Eventually(func() error {
found, err := hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Get(context.TODO(), addOnName, metav1.GetOptions{})
if err != nil {
return err
}
if !meta.IsStatusConditionTrue(found.Status.Conditions, "Available") {
return fmt.Errorf("condition should be available, got %v", found.Status.Conditions)
}
return nil
}).Should(gomega.Succeed())
// check if the cluster has a label for addon with expected value
gomega.Eventually(func() bool {
cluster, err := hub.ClusterClient.ClusterV1().ManagedClusters().Get(context.TODO(), universalClusterName, metav1.GetOptions{})
if err != nil {
return false
}
if len(cluster.Labels) == 0 {
return false
}
key := fmt.Sprintf("feature.open-cluster-management.io/addon-%s", addOnName)
return cluster.Labels[key] == availableLabelValue
}).Should(gomega.BeTrue())
})
ginkgo.It("Should update addon status to unavailable if addon stops to update its lease", func() {
ginkgo.By(fmt.Sprintf("Creating lease %q for managed cluster addon %q", addOnName, addOnName))
_, err := spoke.KubeClient.CoordinationV1().Leases(addOnName).Create(context.TODO(), &coordv1.Lease{
ObjectMeta: metav1.ObjectMeta{
Name: addOnName,
Namespace: addOnName,
},
Spec: coordv1.LeaseSpec{
RenewTime: &metav1.MicroTime{Time: time.Now()},
},
}, metav1.CreateOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
gomega.Eventually(func() error {
found, err := hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Get(context.TODO(), addOnName, metav1.GetOptions{})
if err != nil {
return err
}
if !meta.IsStatusConditionTrue(found.Status.Conditions, "Available") {
return fmt.Errorf("condition should be available")
}
return nil
}).Should(gomega.Succeed())
// check if the cluster has a label for addon with expected value
gomega.Eventually(func() bool {
cluster, err := hub.ClusterClient.ClusterV1().ManagedClusters().Get(context.TODO(), universalClusterName, metav1.GetOptions{})
if err != nil {
return false
}
if len(cluster.Labels) == 0 {
return false
}
key := fmt.Sprintf("feature.open-cluster-management.io/addon-%s", addOnName)
return cluster.Labels[key] == availableLabelValue
}).Should(gomega.BeTrue())
ginkgo.By(fmt.Sprintf("Updating lease %q with a past time", addOnName))
lease, err := spoke.KubeClient.CoordinationV1().Leases(addOnName).Get(context.TODO(), addOnName, metav1.GetOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
lease.Spec.RenewTime = &metav1.MicroTime{Time: time.Now().Add(-10 * time.Minute)}
_, err = spoke.KubeClient.CoordinationV1().Leases(addOnName).Update(context.TODO(), lease, metav1.UpdateOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
gomega.Eventually(func() error {
found, err := hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Get(context.TODO(), addOnName, metav1.GetOptions{})
if err != nil {
return err
}
if !meta.IsStatusConditionFalse(found.Status.Conditions, "Available") {
return fmt.Errorf("condition should be available")
}
return nil
}).Should(gomega.Succeed())
// check if the cluster has a label for addon with expected value
gomega.Eventually(func() bool {
cluster, err := hub.ClusterClient.ClusterV1().ManagedClusters().Get(context.TODO(), universalClusterName, metav1.GetOptions{})
if err != nil {
return false
}
if len(cluster.Labels) == 0 {
return false
}
key := fmt.Sprintf("feature.open-cluster-management.io/addon-%s", addOnName)
return cluster.Labels[key] == "unhealthy"
}).Should(gomega.BeTrue())
})
ginkgo.It("Should update addon status to unknown if there is no lease for this addon", func() {
ginkgo.By(fmt.Sprintf("Creating lease %q for managed cluster addon %q", addOnName, addOnName))
_, err := spoke.KubeClient.CoordinationV1().Leases(addOnName).Create(context.TODO(), &coordv1.Lease{
ObjectMeta: metav1.ObjectMeta{
Name: addOnName,
Namespace: addOnName,
},
Spec: coordv1.LeaseSpec{
RenewTime: &metav1.MicroTime{Time: time.Now()},
},
}, metav1.CreateOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
gomega.Eventually(func() error {
found, err := hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Get(context.TODO(), addOnName, metav1.GetOptions{})
if err != nil {
return err
}
if !meta.IsStatusConditionTrue(found.Status.Conditions, "Available") {
return fmt.Errorf("condition should be available")
}
return nil
}).Should(gomega.Succeed())
// check if the cluster has a label for addon with expected value
gomega.Eventually(func() bool {
cluster, err := hub.ClusterClient.ClusterV1().ManagedClusters().Get(context.TODO(), universalClusterName, metav1.GetOptions{})
if err != nil {
return false
}
if len(cluster.Labels) == 0 {
return false
}
key := fmt.Sprintf("feature.open-cluster-management.io/addon-%s", addOnName)
return cluster.Labels[key] == availableLabelValue
}).Should(gomega.BeTrue())
ginkgo.By(fmt.Sprintf("Deleting lease %q", addOnName))
err = spoke.KubeClient.CoordinationV1().Leases(addOnName).Delete(context.TODO(), addOnName, metav1.DeleteOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
gomega.Eventually(func() error {
found, err := hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Get(context.TODO(), addOnName, metav1.GetOptions{})
if err != nil {
return err
}
if !meta.IsStatusConditionTrue(found.Status.Conditions, "Available") {
return fmt.Errorf("condition should be available")
}
return nil
}).Should(gomega.Succeed())
// check if the cluster has a label for addon with expected value
gomega.Eventually(func() bool {
cluster, err := hub.ClusterClient.ClusterV1().ManagedClusters().Get(context.TODO(), universalClusterName, metav1.GetOptions{})
if err != nil {
return false
}
if len(cluster.Labels) == 0 {
return false
}
key := fmt.Sprintf("feature.open-cluster-management.io/addon-%s", addOnName)
return cluster.Labels[key] == "unreachable"
}).Should(gomega.BeTrue())
})
})
ginkgo.Context("Checking managed cluster status to update addon status", func() {
var klusterletName, clusterName, addOnName string
ginkgo.BeforeEach(func() {
klusterletName = fmt.Sprintf("e2e-klusterlet-%s", rand.String(6))
clusterName = fmt.Sprintf("e2e-managedcluster-%s", rand.String(6))
agentNamespace := fmt.Sprintf("open-cluster-management-agent-%s", rand.String(6))
framework.CreateAndApproveKlusterlet(
hub, spoke,
klusterletName, clusterName, agentNamespace, operatorapiv1.InstallMode(klusterletDeployMode), bootstrapHubKubeConfigSecret, images, registrationDriver)
// create an addon on created managed cluster
addOnName = fmt.Sprintf("addon-%s", rand.String(6))
ginkgo.By(fmt.Sprintf("Creating managed cluster addon %q", addOnName))
gomega.Expect(hub.CreateManagedClusterAddOnV1Alpha1(clusterName, addOnName, addOnName)).ToNot(gomega.HaveOccurred())
// create addon installation namespace
ginkgo.By(fmt.Sprintf("Creating managed cluster addon installation namespace %q", addOnName))
_, err := spoke.KubeClient.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: addOnName,
},
}, metav1.CreateOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
})
ginkgo.AfterEach(func() {
ginkgo.By(fmt.Sprintf("Cleaning managed cluster addon installation namespace %q", addOnName))
err := hub.KubeClient.CoreV1().Namespaces().Delete(context.TODO(), addOnName, metav1.DeleteOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
ginkgo.By(fmt.Sprintf("clean klusterlet %v resources after the test case", klusterletName))
framework.CleanKlusterletRelatedResources(hub, spoke, klusterletName, clusterName)
})
ginkgo.It("Should update addon status to unknown if managed cluster stops to update its lease", func() {
ginkgo.By(fmt.Sprintf("Creating lease %q for managed cluster addon %q", addOnName, addOnName))
_, err := spoke.KubeClient.CoordinationV1().Leases(addOnName).Create(context.TODO(), &coordv1.Lease{
ObjectMeta: metav1.ObjectMeta{
Name: addOnName,
Namespace: addOnName,
},
Spec: coordv1.LeaseSpec{
RenewTime: &metav1.MicroTime{Time: time.Now()},
},
}, metav1.CreateOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
gomega.Eventually(func() error {
found, err := hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(clusterName).Get(context.TODO(), addOnName, metav1.GetOptions{})
if err != nil {
return err
}
if found.Status.Conditions == nil {
return fmt.Errorf("condition should not be nil")
}
cond := meta.FindStatusCondition(found.Status.Conditions, "Available")
if cond.Status != metav1.ConditionTrue {
return fmt.Errorf("available status should be true")
}
return nil
}).Should(gomega.Succeed())
// delete registration agent to stop agent update its status
ginkgo.By("Stoping klusterlet")
err = spoke.OperatorClient.OperatorV1().Klusterlets().Delete(context.TODO(), klusterletName, metav1.DeleteOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
gomega.Eventually(func() error {
_, err := spoke.OperatorClient.OperatorV1().Klusterlets().Get(context.TODO(), klusterletName, metav1.GetOptions{})
if errors.IsNotFound(err) {
klog.Infof("klusterlet %s deleted successfully", klusterletName)
return nil
}
if err != nil {
klog.Infof("get klusterlet %s error: %v", klusterletName, err)
return err
}
return fmt.Errorf("klusterlet is still deleting")
}).Should(gomega.Succeed())
// for speeding up test, update managed cluster status to unknown manually
ginkgo.By(fmt.Sprintf("Updating managed cluster %s status to unknown", clusterName))
found, err := hub.ClusterClient.ClusterV1().ManagedClusters().Get(context.TODO(), clusterName, metav1.GetOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
found.Status = clusterv1.ManagedClusterStatus{
Conditions: []metav1.Condition{
{
Type: clusterv1.ManagedClusterConditionAvailable,
Status: metav1.ConditionUnknown,
Reason: "ManagedClusterLeaseUpdateStopped",
Message: "Registration agent stopped updating its lease.",
LastTransitionTime: metav1.Now(),
},
},
}
_, err = hub.ClusterClient.ClusterV1().ManagedClusters().UpdateStatus(context.TODO(), found, metav1.UpdateOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
gomega.Eventually(func() error {
found, err := hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(clusterName).Get(context.TODO(), addOnName, metav1.GetOptions{})
if err != nil {
return err
}
if found.Status.Conditions == nil {
return fmt.Errorf("condition should not be nil")
}
cond := meta.FindStatusCondition(found.Status.Conditions, "Available")
if cond.Status != metav1.ConditionUnknown {
return fmt.Errorf("available status should be unknown")
}
return nil
}).Should(gomega.Succeed())
})
})
})