Skip to content

Commit 8a492cb

Browse files
author
Per Goncalves da Silva
committed
Add ClusterExtensionRevision e2e
Signed-off-by: Per Goncalves da Silva <pegoncal@redhat.com>
1 parent 511d4be commit 8a492cb

File tree

2 files changed

+250
-0
lines changed

2 files changed

+250
-0
lines changed
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
package e2e
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"slices"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
corev1 "k8s.io/api/core/v1"
13+
apimeta "k8s.io/apimachinery/pkg/api/meta"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
"k8s.io/apimachinery/pkg/types"
16+
"k8s.io/client-go/kubernetes/scheme"
17+
"k8s.io/client-go/tools/remotecommand"
18+
ctrl "sigs.k8s.io/controller-runtime"
19+
"sigs.k8s.io/controller-runtime/pkg/client"
20+
21+
ocv1 "github.com/operator-framework/operator-controller/api/v1"
22+
"github.com/operator-framework/operator-controller/internal/operator-controller/features"
23+
. "github.com/operator-framework/operator-controller/internal/shared/util/testutils"
24+
. "github.com/operator-framework/operator-controller/test/helpers"
25+
)
26+
27+
func TestClusterExtensionRevision(t *testing.T) {
28+
SkipIfFeatureGateDisabled(t, string(features.BoxcutterRuntime))
29+
t.Log("When a cluster extension is installed from a catalog")
30+
t.Log("When the extension bundle format is registry+v1")
31+
32+
clusterExtension, extensionCatalog, sa, ns := TestInit(t)
33+
defer TestCleanup(t, extensionCatalog, clusterExtension, sa, ns)
34+
defer CollectTestArtifacts(t, artifactName, c, cfg)
35+
36+
clusterExtension.Spec = ocv1.ClusterExtensionSpec{
37+
Source: ocv1.SourceConfig{
38+
SourceType: "Catalog",
39+
Catalog: &ocv1.CatalogFilter{
40+
PackageName: "test",
41+
Version: "1.0.1",
42+
Selector: &metav1.LabelSelector{
43+
MatchLabels: map[string]string{"olm.operatorframework.io/metadata.name": extensionCatalog.Name},
44+
},
45+
},
46+
},
47+
Namespace: ns.Name,
48+
ServiceAccount: ocv1.ServiceAccountReference{
49+
Name: sa.Name,
50+
},
51+
}
52+
t.Log("It resolves the specified package with correct bundle path")
53+
t.Log("By creating the ClusterExtension resource")
54+
require.NoError(t, c.Create(context.Background(), clusterExtension))
55+
56+
t.Log("By eventually reporting a successful resolution and bundle path")
57+
require.EventuallyWithT(t, func(ct *assert.CollectT) {
58+
require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension))
59+
}, pollDuration, pollInterval)
60+
61+
t.Log("By revision-1 eventually reporting Progressing:False:RolledOut and Available:True:ProbesSucceeded conditions")
62+
var clusterExtensionRevision ocv1.ClusterExtensionRevision
63+
require.EventuallyWithT(t, func(ct *assert.CollectT) {
64+
require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: fmt.Sprintf("%s-1", clusterExtension.Name)}, &clusterExtensionRevision))
65+
cond := apimeta.FindStatusCondition(clusterExtensionRevision.Status.Conditions, ocv1.ClusterExtensionRevisionTypeProgressing)
66+
require.NotNil(ct, cond)
67+
require.Equal(ct, metav1.ConditionFalse, cond.Status)
68+
require.Equal(ct, ocv1.ClusterExtensionRevisionReasonRolledOut, cond.Reason)
69+
70+
cond = apimeta.FindStatusCondition(clusterExtensionRevision.Status.Conditions, ocv1.ClusterExtensionRevisionTypeAvailable)
71+
require.NotNil(ct, cond)
72+
require.Equal(ct, metav1.ConditionTrue, cond.Status)
73+
require.Equal(ct, ocv1.ClusterExtensionRevisionReasonProbesSucceeded, cond.Reason)
74+
}, pollDuration, pollInterval)
75+
76+
t.Log("By eventually reporting progressing as True")
77+
require.EventuallyWithT(t, func(ct *assert.CollectT) {
78+
require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension))
79+
cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing)
80+
require.NotNil(ct, cond)
81+
require.Equal(ct, metav1.ConditionTrue, cond.Status)
82+
require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason)
83+
}, pollDuration, pollInterval)
84+
85+
// TODO: re-enable this once the available condition is mirrored to the ClusterExtension conditions
86+
//t.Log("By eventually reporting available as True")
87+
//require.EventuallyWithT(t, func(ct *assert.CollectT) {
88+
// require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension))
89+
// cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.ClusterExtensionRevisionTypeAvailable)
90+
// require.NotNil(ct, cond)
91+
// require.Equal(ct, metav1.ConditionTrue, cond.Status)
92+
// require.Equal(ct, ocv1.ClusterExtensionRevisionReasonProbesSucceeded, cond.Reason)
93+
//}, pollDuration, pollInterval)
94+
95+
t.Log("By eventually installing the package successfully")
96+
require.EventuallyWithT(t, func(ct *assert.CollectT) {
97+
require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension))
98+
cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled)
99+
require.NotNil(ct, cond)
100+
require.Equal(ct, metav1.ConditionTrue, cond.Status)
101+
require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason)
102+
require.Contains(ct, cond.Message, "Installed bundle")
103+
require.NotEmpty(ct, clusterExtension.Status.Install.Bundle)
104+
}, pollDuration, pollInterval)
105+
106+
t.Log("Check Deployment Availability Probe")
107+
t.Log("By making the operator pod not ready")
108+
podName := getPodName(t, clusterExtension.Spec.Namespace, client.MatchingLabels{"app": "olme2etest"})
109+
podExec(t, clusterExtension.Spec.Namespace, podName, []string{"rm", "/var/www/ready"})
110+
111+
t.Log("By revision-1 eventually reporting Progressing:False:RolledOut and Available:False:ProbeFailure conditions")
112+
require.EventuallyWithT(t, func(ct *assert.CollectT) {
113+
require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: fmt.Sprintf("%s-1", clusterExtension.Name)}, &clusterExtensionRevision))
114+
cond := apimeta.FindStatusCondition(clusterExtensionRevision.Status.Conditions, ocv1.ClusterExtensionRevisionTypeProgressing)
115+
require.NotNil(ct, cond)
116+
require.Equal(ct, metav1.ConditionFalse, cond.Status)
117+
require.Equal(ct, ocv1.ClusterExtensionRevisionReasonRolledOut, cond.Reason)
118+
119+
cond = apimeta.FindStatusCondition(clusterExtensionRevision.Status.Conditions, ocv1.ClusterExtensionRevisionTypeAvailable)
120+
require.NotNil(ct, cond)
121+
require.Equal(ct, metav1.ConditionFalse, cond.Status)
122+
require.Equal(ct, ocv1.ClusterExtensionRevisionReasonProbeFailure, cond.Reason)
123+
}, pollDuration, pollInterval)
124+
125+
// TODO: re-enable this once the available condition is mirrored to the ClusterExtension conditions
126+
//t.Log("By extension eventually reporting available as False")
127+
//require.EventuallyWithT(t, func(ct *assert.CollectT) {
128+
// require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension))
129+
// cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.ClusterExtensionRevisionTypeAvailable)
130+
// require.NotNil(ct, cond)
131+
// require.Equal(ct, metav1.ConditionFalse, cond.Status)
132+
// require.Equal(ct, ocv1.ClusterExtensionRevisionReasonProbeFailure, cond.Reason)
133+
//}, pollDuration, pollInterval)
134+
135+
t.Log("By making the operator pod ready")
136+
podName = getPodName(t, clusterExtension.Spec.Namespace, client.MatchingLabels{"app": "olme2etest"})
137+
podExec(t, clusterExtension.Spec.Namespace, podName, []string{"touch", "/var/www/ready"})
138+
139+
t.Log("By revision-1 eventually reporting Progressing:False:RolledOut and Available:True:ProbesSucceeded conditions")
140+
require.EventuallyWithT(t, func(ct *assert.CollectT) {
141+
require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: fmt.Sprintf("%s-1", clusterExtension.Name)}, &clusterExtensionRevision))
142+
cond := apimeta.FindStatusCondition(clusterExtensionRevision.Status.Conditions, ocv1.ClusterExtensionRevisionTypeProgressing)
143+
require.NotNil(ct, cond)
144+
require.Equal(ct, metav1.ConditionFalse, cond.Status)
145+
require.Equal(ct, ocv1.ClusterExtensionRevisionReasonRolledOut, cond.Reason)
146+
147+
cond = apimeta.FindStatusCondition(clusterExtensionRevision.Status.Conditions, ocv1.ClusterExtensionRevisionTypeAvailable)
148+
require.NotNil(ct, cond)
149+
require.Equal(ct, metav1.ConditionTrue, cond.Status)
150+
require.Equal(ct, ocv1.ClusterExtensionRevisionReasonProbesSucceeded, cond.Reason)
151+
}, pollDuration, pollInterval)
152+
153+
// TODO: re-enable this once the available condition is mirrored to the ClusterExtension conditions
154+
//t.Log("By extension eventually reporting available as True")
155+
//require.EventuallyWithT(t, func(ct *assert.CollectT) {
156+
// require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension))
157+
// cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.ClusterExtensionRevisionTypeAvailable)
158+
// require.NotNil(ct, cond)
159+
// require.Equal(ct, metav1.ConditionTrue, cond.Status)
160+
// require.Equal(ct, ocv1.ClusterExtensionRevisionReasonProbesSucceeded, cond.Reason)
161+
//}, pollDuration, pollInterval)
162+
163+
t.Log("Check archiving")
164+
t.Log("By upgrading the cluster extension to v1.2.0")
165+
require.EventuallyWithT(t, func(ct *assert.CollectT) {
166+
require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension))
167+
clusterExtension.Spec.Source.Catalog.Version = "1.2.0"
168+
require.NoError(t, c.Update(context.Background(), clusterExtension))
169+
}, pollDuration, pollInterval)
170+
171+
t.Log("By revision-2 eventually reporting Progressing:False:RolledOut and Available:True:ProbesSucceeded conditions")
172+
require.EventuallyWithT(t, func(ct *assert.CollectT) {
173+
require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: fmt.Sprintf("%s-2", clusterExtension.Name)}, &clusterExtensionRevision))
174+
cond := apimeta.FindStatusCondition(clusterExtensionRevision.Status.Conditions, ocv1.ClusterExtensionRevisionTypeProgressing)
175+
require.NotNil(ct, cond)
176+
require.Equal(ct, metav1.ConditionFalse, cond.Status)
177+
require.Equal(ct, ocv1.ClusterExtensionRevisionReasonRolledOut, cond.Reason)
178+
179+
cond = apimeta.FindStatusCondition(clusterExtensionRevision.Status.Conditions, ocv1.ClusterExtensionRevisionTypeAvailable)
180+
require.NotNil(ct, cond)
181+
require.Equal(ct, metav1.ConditionTrue, cond.Status)
182+
require.Equal(ct, ocv1.ClusterExtensionRevisionReasonProbesSucceeded, cond.Reason)
183+
}, pollDuration, pollInterval)
184+
185+
t.Log("By eventually reporting progressing, available, and installed as True")
186+
require.EventuallyWithT(t, func(ct *assert.CollectT) {
187+
require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension))
188+
cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeProgressing)
189+
require.NotNil(ct, cond)
190+
require.Equal(ct, metav1.ConditionTrue, cond.Status)
191+
require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason)
192+
193+
// TODO: re-enable this once the available condition is mirrored to the ClusterExtension conditions
194+
//cond = apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.ClusterExtensionRevisionTypeAvailable)
195+
//require.NotNil(ct, cond)
196+
//require.Equal(ct, metav1.ConditionTrue, cond.Status)
197+
//require.Equal(ct, ocv1.ClusterExtensionRevisionReasonProbesSucceeded, cond.Reason)
198+
199+
cond = apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled)
200+
require.NotNil(ct, cond)
201+
require.Equal(ct, metav1.ConditionTrue, cond.Status)
202+
require.Equal(ct, ocv1.ReasonSucceeded, cond.Reason)
203+
require.Contains(ct, cond.Message, "Installed bundle")
204+
require.NotEmpty(ct, clusterExtension.Status.Install.Bundle)
205+
}, pollDuration, pollInterval)
206+
207+
t.Log("By revision-1 eventually reporting Progressing:False:Archived and Available:Unknown:Archived conditions")
208+
require.EventuallyWithT(t, func(ct *assert.CollectT) {
209+
require.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: fmt.Sprintf("%s-1", clusterExtension.Name)}, &clusterExtensionRevision))
210+
cond := apimeta.FindStatusCondition(clusterExtensionRevision.Status.Conditions, ocv1.ClusterExtensionRevisionTypeProgressing)
211+
require.NotNil(ct, cond)
212+
require.Equal(ct, metav1.ConditionFalse, cond.Status)
213+
require.Equal(ct, ocv1.ClusterExtensionRevisionReasonArchived, cond.Reason)
214+
215+
cond = apimeta.FindStatusCondition(clusterExtensionRevision.Status.Conditions, ocv1.ClusterExtensionRevisionTypeAvailable)
216+
require.NotNil(ct, cond)
217+
require.Equal(ct, metav1.ConditionUnknown, cond.Status)
218+
require.Equal(ct, ocv1.ClusterExtensionRevisionReasonArchived, cond.Reason)
219+
}, pollDuration, pollInterval)
220+
}
221+
222+
func getPodName(t *testing.T, podNamespace string, matchingLabels client.MatchingLabels) string {
223+
var podList corev1.PodList
224+
require.EventuallyWithT(t, func(ct *assert.CollectT) {
225+
require.NoError(ct, c.List(context.Background(), &podList, client.InNamespace(podNamespace), matchingLabels))
226+
podList.Items = slices.DeleteFunc(podList.Items, func(pod corev1.Pod) bool {
227+
// Ignore terminating pods
228+
return pod.DeletionTimestamp != nil
229+
})
230+
require.Len(ct, podList.Items, 1)
231+
}, pollDuration, pollInterval)
232+
return podList.Items[0].Name
233+
}
234+
235+
func podExec(t *testing.T, podNamespace string, podName string, cmd []string) {
236+
req := cs.CoreV1().RESTClient().Post().Resource("pods").Name(podName).Namespace(podNamespace).SubResource("exec")
237+
req.VersionedParams(&corev1.PodExecOptions{
238+
Command: cmd,
239+
Stdout: true,
240+
}, scheme.ParameterCodec)
241+
exec, err := remotecommand.NewSPDYExecutor(ctrl.GetConfigOrDie(), "POST", req.URL())
242+
require.NoError(t, err)
243+
err = exec.StreamWithContext(context.Background(), remotecommand.StreamOptions{Stdout: os.Stdout})
244+
require.NoError(t, err)
245+
}

test/e2e/e2e_suite_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1010
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
11+
"k8s.io/client-go/kubernetes"
1112
"k8s.io/client-go/rest"
1213
ctrl "sigs.k8s.io/controller-runtime"
1314
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -20,6 +21,7 @@ import (
2021
var (
2122
cfg *rest.Config
2223
c client.Client
24+
cs *kubernetes.Clientset
2325
)
2426

2527
const (
@@ -35,6 +37,9 @@ func TestMain(m *testing.M) {
3537
c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
3638
utilruntime.Must(err)
3739

40+
cs, err = kubernetes.NewForConfig(cfg)
41+
utilruntime.Must(err)
42+
3843
res := m.Run()
3944
path := os.Getenv(testSummaryOutputEnvVar)
4045
if path == "" {

0 commit comments

Comments
 (0)