diff --git a/test/e2e/parallel/nstemplatetier_test.go b/test/e2e/parallel/nstemplatetier_test.go index c2a3fc983..581265d03 100644 --- a/test/e2e/parallel/nstemplatetier_test.go +++ b/test/e2e/parallel/nstemplatetier_test.go @@ -20,6 +20,7 @@ import ( . "github.com/codeready-toolchain/toolchain-e2e/testsupport/space" "github.com/codeready-toolchain/toolchain-e2e/testsupport/tiers" "github.com/codeready-toolchain/toolchain-e2e/testsupport/wait" + apiwait "k8s.io/apimachinery/pkg/util/wait" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -378,6 +379,41 @@ func TestFeatureToggles(t *testing.T) { }) } +func TestTierTemplateRevision(t *testing.T) { + t.Parallel() + + awaitilities := WaitForDeployments(t) + hostAwait := awaitilities.Host() + + baseTier, err := hostAwait.WaitForNSTemplateTier(t, "base1ns") + require.NoError(t, err) + + // create new NSTemplateTiers (derived from `base`) + // for the tiertemplaterevisions to be created the tiertemplates need to have template objects populated + updateTierTemplateObjects := func(template *toolchainv1alpha1.TierTemplate) error { + template.Spec.TemplateObjects = template.Spec.Template.Objects + return nil + } + namespaceResourcesWithTemplateObjects := tiers.WithNamespaceResources(t, baseTier, updateTierTemplateObjects) + clusterResourcesWithTemplateObjects := tiers.WithClusterResources(t, baseTier, updateTierTemplateObjects) + spaceRolesWithTemplateObjects := tiers.WithSpaceRoles(t, baseTier, updateTierTemplateObjects) + tiers.CreateCustomNSTemplateTier(t, hostAwait, "ttr", baseTier, namespaceResourcesWithTemplateObjects, clusterResourcesWithTemplateObjects, spaceRolesWithTemplateObjects) + + // verify the counters in the status.history for 'tierUsingTierTemplateRevisions' tier + // and verify that TierTemplateRevision CRs were created, since all the tiertemplates now have templateObjects field populated + verifyStatus(t, hostAwait, "ttr", 1) + // check the expected total number of ttr matches + err = apiwait.Poll(hostAwait.RetryInterval, hostAwait.Timeout, func() (done bool, err error) { + objs := &toolchainv1alpha1.TierTemplateRevisionList{} + if err := hostAwait.Client.List(context.TODO(), objs, client.InNamespace(hostAwait.Namespace)); err != nil { + return false, err + } + require.Len(t, objs.Items, 3) // expect one TTR per each tiertemplate ( clusterresource, namespace and spacerole ) + return true, nil + }) + require.NoError(t, err) +} + func withClusterRoleBindings(t *testing.T, otherTier *toolchainv1alpha1.NSTemplateTier, feature string) tiers.CustomNSTemplateTierModifier { clusterRB := getCRBforFeature(t, feature) // This is the ClusterRoleBinding for the desired feature noiseCRB := getCRBforFeature(t, unknownFeature) // This is a noise CRB for unknown/disabled feature. To be used to check that this CRB is never created. diff --git a/testsupport/init.go b/testsupport/init.go index a36bfb0d9..8b8a7c0a9 100644 --- a/testsupport/init.go +++ b/testsupport/init.go @@ -214,8 +214,8 @@ func WaitForDeployments(t *testing.T) wait.Awaitilities { initMemberAwait.WaitForMemberWebhooks(t, webhookImage) // wait for autoscaler buffer apps - initMemberAwait.WaitForAutoscalingBufferApp(t) - initMember2Await.WaitForAutoscalingBufferApp(t) + //initMemberAwait.WaitForAutoscalingBufferApp(t) + //initMember2Await.WaitForAutoscalingBufferApp(t) // check that the tier exists, and all its namespace other cluster-scoped resource revisions // are different from `000000a` which is the value specified in the initial manifest (used for base tier) diff --git a/testsupport/tiers/tier_setup.go b/testsupport/tiers/tier_setup.go index e56676104..3e040e643 100644 --- a/testsupport/tiers/tier_setup.go +++ b/testsupport/tiers/tier_setup.go @@ -47,13 +47,13 @@ func WithClusterResources(t *testing.T, otherTier *toolchainv1alpha1.NSTemplateT } } -func WithNamespaceResources(t *testing.T, otherTier *toolchainv1alpha1.NSTemplateTier) CustomNSTemplateTierModifier { +func WithNamespaceResources(t *testing.T, otherTier *toolchainv1alpha1.NSTemplateTier, modifiers ...TierTemplateModifier) CustomNSTemplateTierModifier { return func(hostAwait *HostAwaitility, tier *CustomNSTemplateTier) error { tier.NamespaceResourcesTier = otherTier // configure the "wrapped" NSTemplateTier tier.Spec.Namespaces = make([]toolchainv1alpha1.NSTemplateTierNamespace, len(otherTier.Spec.Namespaces)) for i, def := range otherTier.Spec.Namespaces { - tmplRef, err := duplicateTierTemplate(t, hostAwait, otherTier.Namespace, tier.Name, def.TemplateRef) + tmplRef, err := duplicateTierTemplate(t, hostAwait, otherTier.Namespace, tier.Name, def.TemplateRef, modifiers...) if err != nil { return err } @@ -63,13 +63,13 @@ func WithNamespaceResources(t *testing.T, otherTier *toolchainv1alpha1.NSTemplat } } -func WithSpaceRoles(t *testing.T, otherTier *toolchainv1alpha1.NSTemplateTier) CustomNSTemplateTierModifier { +func WithSpaceRoles(t *testing.T, otherTier *toolchainv1alpha1.NSTemplateTier, modifiers ...TierTemplateModifier) CustomNSTemplateTierModifier { return func(hostAwait *HostAwaitility, tier *CustomNSTemplateTier) error { tier.SpaceRolesTier = otherTier // configure the "wrapped" NSTemplateTier tier.Spec.SpaceRoles = make(map[string]toolchainv1alpha1.NSTemplateTierSpaceRole, len(otherTier.Spec.SpaceRoles)) for name, def := range otherTier.Spec.SpaceRoles { - tmplRef, err := duplicateTierTemplate(t, hostAwait, otherTier.Namespace, tier.Name, def.TemplateRef) + tmplRef, err := duplicateTierTemplate(t, hostAwait, otherTier.Namespace, tier.Name, def.TemplateRef, modifiers...) if err != nil { return err } diff --git a/testsupport/wait/host.go b/testsupport/wait/host.go index d0634e017..65afdeccc 100644 --- a/testsupport/wait/host.go +++ b/testsupport/wait/host.go @@ -1023,18 +1023,67 @@ func (a *HostAwaitility) WaitForNSTemplateTierAndCheckTemplates(t *testing.T, na if ns.TemplateRef == "" { return nil, fmt.Errorf("missing 'templateRef' in namespace #%d in NSTemplateTier '%s'", i, tier.Name) } - if _, err := a.WaitForTierTemplate(t, ns.TemplateRef); err != nil { + tierTemplateNamespaces, err := a.WaitForTierTemplate(t, ns.TemplateRef) + if err != nil { return nil, err } + // if the tier template supports Tier Template Revisions then let's check those + if tierTemplateNamespaces.Spec.TemplateObjects != nil { + if _, err := a.WaitForTierTemplateRevision(t, tierTemplateNamespaces.GetName(), UntilTierTemplateRevisionsHasOwnerReferences([]metav1.OwnerReference{ + { + APIVersion: "toolchain.dev.openshift.com/v1alpha1", // for some reason, the apiversion and kind are empty on the CR + Kind: "TierTemplate", + Name: tierTemplateNamespaces.Name, + }, + })); err != nil { + return nil, err + } + } } if tier.Spec.ClusterResources != nil { if tier.Spec.ClusterResources.TemplateRef == "" { return nil, fmt.Errorf("missing 'templateRef' for the cluster resources in NSTemplateTier '%s'", tier.Name) } - if _, err := a.WaitForTierTemplate(t, tier.Spec.ClusterResources.TemplateRef); err != nil { + tierTemplateClusterResources, err := a.WaitForTierTemplate(t, tier.Spec.ClusterResources.TemplateRef) + if err != nil { + return nil, err + } + // if the tier template supports Tier Template Revisions then let's check those + if tierTemplateClusterResources.Spec.TemplateObjects != nil { + if _, err := a.WaitForTierTemplateRevision(t, tierTemplateClusterResources.GetName(), UntilTierTemplateRevisionsHasOwnerReferences([]metav1.OwnerReference{ + { + APIVersion: "toolchain.dev.openshift.com/v1alpha1", // for some reason, the apiversion and kind are empty on the CR + Kind: "TierTemplate", + Name: tierTemplateClusterResources.Name, + }, + })); err != nil { + return nil, err + } + } + } + + for _, r := range tier.Spec.SpaceRoles { + if r.TemplateRef == "" { + return nil, fmt.Errorf("missing 'templateRef' in spaceRole %s in NSTemplateTier '%s'", r.TemplateRef, tier.Name) + } + tierTemplateSpaceRoles, err := a.WaitForTierTemplate(t, r.TemplateRef) + if err != nil { return nil, err } + // if the tier template supports Tier Template Revisions then let's check those + if tierTemplateSpaceRoles.Spec.TemplateObjects != nil { + if _, err := a.WaitForTierTemplateRevision(t, r.TemplateRef, UntilTierTemplateRevisionsHasOwnerReferences([]metav1.OwnerReference{ + { + APIVersion: "toolchain.dev.openshift.com/v1alpha1", // for some reason, the apiversion and kind are empty on the CR + Kind: "TierTemplate", + Name: tierTemplateSpaceRoles.Name, + }, + })); err != nil { + return nil, err + } + } } + return tier, err } @@ -1061,6 +1110,98 @@ func (a *HostAwaitility) WaitForTierTemplate(t *testing.T, name string) (*toolch return tierTemplate, err } +// TierTemplateRevisionWaitCriterion a struct to compare with an expected TierTemplateRevision +type TierTemplateRevisionWaitCriterion struct { + Match func(*toolchainv1alpha1.TierTemplateRevision) bool + Diff func(*toolchainv1alpha1.TierTemplateRevision) string +} + +func matchTierTemplateRevisionWaitCriterion(actual *toolchainv1alpha1.TierTemplateRevision, criteria ...TierTemplateRevisionWaitCriterion) bool { + for _, c := range criteria { + // if at least one criteria does not match, keep waiting + if !c.Match(actual) { + return false + } + } + return true +} + +func (a *HostAwaitility) printTierTemplateRevisionWaitCriterionDiffs(t *testing.T, actual *toolchainv1alpha1.TierTemplateRevision, criteria ...TierTemplateRevisionWaitCriterion) { + buf := &strings.Builder{} + if actual == nil { + buf.WriteString("failed to find TierTemplateRevision\n") + } else { + buf.WriteString("failed to find TierTemplateRevision with matching criteria:\n") + buf.WriteString("actual:\n") + y, _ := StringifyObject(actual) + buf.Write(y) + buf.WriteString("\n----\n") + buf.WriteString("diffs:\n") + for _, c := range criteria { + if !c.Match(actual) { + buf.WriteString(c.Diff(actual)) + buf.WriteString("\n") + } + } + } + // include also all TierTemplateRevisions in the host namespace, to help troubleshooting + a.listAndPrint(t, "TierTemplateRevisions", a.Namespace, &toolchainv1alpha1.TierTemplateRevisionList{}) + + t.Log(buf.String()) +} + +// WaitForTierTemplateRevision waits until a TierTemplateRevision with the given labels to exists +// Returns an error if the resource did not exist (or something wrong happened) +func (a *HostAwaitility) WaitForTierTemplateRevision(t *testing.T, templateRef string, criteria ...TierTemplateRevisionWaitCriterion) (*toolchainv1alpha1.TierTemplateRevision, error) { // nolint:unparam + ttr := &toolchainv1alpha1.TierTemplateRevision{} + t.Logf("waiting until TierTemplateRevision for templateRef '%s' exists in namespace '%s'...", templateRef, a.Namespace) + err := wait.Poll(a.RetryInterval, a.Timeout, func() (done bool, err error) { + objs := &toolchainv1alpha1.TierTemplateRevisionList{} + err = a.Client.List(context.TODO(), objs, client.InNamespace(a.Namespace), client.MatchingLabels(map[string]string{ + toolchainv1alpha1.TemplateRefLabelKey: templateRef, + })) + // no match found, print the diffs + if err != nil { + return false, err + } + require.Len(t, objs.Items, 1) + ttr = &objs.Items[0] + return matchTierTemplateRevisionWaitCriterion(ttr, criteria...), nil + }) + // log message if an error occurred + if err != nil { + a.printTierTemplateRevisionWaitCriterionDiffs(t, ttr, criteria...) + } + return ttr, err +} + +// UntilTierTemplateRevisionsHasOwnerReferences verify that the TierTemplateRevision has the specified owner references +func UntilTierTemplateRevisionsHasOwnerReferences(references []metav1.OwnerReference) TierTemplateRevisionWaitCriterion { + return TierTemplateRevisionWaitCriterion{ + Match: func(actual *toolchainv1alpha1.TierTemplateRevision) bool { + actualReferences := actual.GetOwnerReferences() + for _, actualRef := range actualReferences { + found := false + for _, expectedRef := range references { + if expectedRef.Name == actualRef.Name && + expectedRef.APIVersion == actualRef.APIVersion && + expectedRef.Kind == actualRef.Kind { + found = true + break + } + } + if !found { + return false + } + } + return len(references) == len(actualReferences) + }, + Diff: func(actual *toolchainv1alpha1.TierTemplateRevision) string { + return fmt.Sprintf("unable to find expected owner references: %v", references) + }, + } +} + // NSTemplateTierWaitCriterion a struct to compare with an expected NSTemplateTier type NSTemplateTierWaitCriterion struct { Match func(*toolchainv1alpha1.NSTemplateTier) bool