Skip to content

Commit 1d1c322

Browse files
authored
Add upgrade E2E (#1003)
Testing upgrade from the latest release to the current commit Signed-off-by: Mikalai Radchuk <[email protected]>
1 parent e5470e2 commit 1d1c322

File tree

5 files changed

+274
-2
lines changed

5 files changed

+274
-2
lines changed

.github/workflows/e2e.yaml

+13-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ jobs:
1919
go-version-file: go.mod
2020

2121
- name: Run the extension developer e2e test
22-
run: |
23-
make extension-developer-e2e
22+
run: make extension-developer-e2e
2423

2524
e2e-kind:
2625
runs-on: ubuntu-latest
@@ -48,3 +47,15 @@ jobs:
4847
files: e2e-cover.out
4948
flags: e2e
5049
token: ${{ secrets.CODECOV_TOKEN }}
50+
51+
upgrade-e2e:
52+
runs-on: ubuntu-latest
53+
steps:
54+
- uses: actions/checkout@v4
55+
56+
- uses: actions/setup-go@v5
57+
with:
58+
go-version-file: go.mod
59+
60+
- name: Run the upgrade e2e test
61+
run: make test-upgrade-e2e

Makefile

+18
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,24 @@ extension-developer-e2e: KUSTOMIZE_BUILD_DIR := config/overlays/cert-manager
159159
extension-developer-e2e: KIND_CLUSTER_NAME := operator-controller-ext-dev-e2e #EXHELP Run extension-developer e2e on local kind cluster
160160
extension-developer-e2e: run image-registry test-ext-dev-e2e kind-clean
161161

162+
.PHONY: run-latest-release
163+
run-latest-release:
164+
curl -L -s https://github.com/operator-framework/operator-controller/releases/latest/download/install.sh | bash -s
165+
166+
.PHONY: pre-upgrade-setup
167+
pre-upgrade-setup:
168+
./hack/pre-upgrade-setup.sh $(CATALOG_IMG) $(TEST_CLUSTER_CATALOG_NAME) $(TEST_CLUSTER_EXTENSION_NAME)
169+
170+
.PHONY: post-upgrade-checks
171+
post-upgrade-checks:
172+
go test -count=1 -v ./test/upgrade-e2e/...
173+
174+
.PHONY: test-upgrade-e2e
175+
test-upgrade-e2e: KIND_CLUSTER_NAME := operator-controller-upgrade-e2e
176+
test-upgrade-e2e: export TEST_CLUSTER_CATALOG_NAME := test-catalog
177+
test-upgrade-e2e: export TEST_CLUSTER_EXTENSION_NAME := test-package
178+
test-upgrade-e2e: kind-cluster run-latest-release image-registry build-push-e2e-catalog registry-load-bundles pre-upgrade-setup docker-build kind-load kind-deploy post-upgrade-checks kind-clean #HELP Run upgrade e2e tests on a local kind cluster
179+
162180
.PHONY: e2e-coverage
163181
e2e-coverage:
164182
COVERAGE_OUTPUT=./e2e-cover.out ./hack/e2e-coverage.sh

hack/pre-upgrade-setup.sh

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
help="pre-upgrade-setup.sh is used to create some basic resources
6+
which will later be used in upgrade testing.
7+
8+
Usage:
9+
post-upgrade-checks.sh [TEST_CATALOG_IMG] [TEST_CATALOG_NAME] [TEST_CLUSTER_EXTENSION_NAME]
10+
"
11+
12+
if [[ "$#" -ne 3 ]]; then
13+
echo "Illegal number of arguments passed"
14+
echo "${help}"
15+
exit 1
16+
fi
17+
18+
TEST_CATALOG_IMG=$1
19+
TEST_CLUSTER_CATALOG_NAME=$2
20+
TEST_CLUSTER_EXTENSION_NAME=$3
21+
22+
kubectl apply -f - << EOF
23+
apiVersion: catalogd.operatorframework.io/v1alpha1
24+
kind: ClusterCatalog
25+
metadata:
26+
name: ${TEST_CLUSTER_CATALOG_NAME}
27+
spec:
28+
source:
29+
type: image
30+
image:
31+
ref: ${TEST_CATALOG_IMG}
32+
pollInterval: 24h
33+
insecureSkipTLSVerify: true
34+
EOF
35+
36+
37+
kubectl apply -f - << EOF
38+
apiVersion: olm.operatorframework.io/v1alpha1
39+
kind: ClusterExtension
40+
metadata:
41+
name: ${TEST_CLUSTER_EXTENSION_NAME}
42+
spec:
43+
installNamespace: default
44+
packageName: prometheus
45+
version: 1.0.0
46+
serviceAccount:
47+
name: default
48+
EOF
49+
50+
kubectl wait --for=condition=Unpacked --timeout=60s ClusterCatalog $TEST_CLUSTER_CATALOG_NAME
51+
kubectl wait --for=condition=Installed --timeout=60s ClusterExtension $TEST_CLUSTER_EXTENSION_NAME

test/upgrade-e2e/post_upgrade_test.go

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package upgradee2e
2+
3+
import (
4+
"bufio"
5+
"context"
6+
"fmt"
7+
"strings"
8+
"testing"
9+
"time"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
appsv1 "k8s.io/api/apps/v1"
14+
corev1 "k8s.io/api/core/v1"
15+
apimeta "k8s.io/apimachinery/pkg/api/meta"
16+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17+
"k8s.io/apimachinery/pkg/labels"
18+
"k8s.io/apimachinery/pkg/types"
19+
"sigs.k8s.io/controller-runtime/pkg/client"
20+
21+
catalogdv1alpha1 "github.com/operator-framework/catalogd/api/core/v1alpha1"
22+
23+
ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
24+
)
25+
26+
func TestClusterExtensionAfterOLMUpgrade(t *testing.T) {
27+
t.Log("Starting checks after OLM upgrade")
28+
ctx := context.Background()
29+
30+
managerLabelSelector := labels.Set{"control-plane": "controller-manager"}
31+
32+
t.Log("Checking that the controller-manager deployment is updated")
33+
require.EventuallyWithT(t, func(ct *assert.CollectT) {
34+
var managerDeployments appsv1.DeploymentList
35+
assert.NoError(ct, c.List(ctx, &managerDeployments, client.MatchingLabelsSelector{Selector: managerLabelSelector.AsSelector()}))
36+
assert.Len(ct, managerDeployments.Items, 1)
37+
managerDeployment := managerDeployments.Items[0]
38+
39+
assert.True(ct,
40+
managerDeployment.Status.UpdatedReplicas == *managerDeployment.Spec.Replicas &&
41+
managerDeployment.Status.Replicas == *managerDeployment.Spec.Replicas &&
42+
managerDeployment.Status.AvailableReplicas == *managerDeployment.Spec.Replicas &&
43+
managerDeployment.Status.ReadyReplicas == *managerDeployment.Spec.Replicas,
44+
)
45+
}, time.Minute, time.Second)
46+
47+
var managerPods corev1.PodList
48+
t.Log("Waiting for only one controller-manager Pod to remain")
49+
require.EventuallyWithT(t, func(ct *assert.CollectT) {
50+
assert.NoError(ct, c.List(ctx, &managerPods, client.MatchingLabelsSelector{Selector: managerLabelSelector.AsSelector()}))
51+
assert.Len(ct, managerPods.Items, 1)
52+
}, time.Minute, time.Second)
53+
54+
t.Log("Reading logs to make sure that ClusterExtension was reconciled by operator-controller before we update it")
55+
// Make sure that after we upgrade OLM itself we can still reconcile old objects without any changes
56+
logCtx, cancel := context.WithTimeout(ctx, time.Minute)
57+
defer cancel()
58+
substring := fmt.Sprintf(`"ClusterExtension": {"name":"%s"}`, testClusterExtensionName)
59+
found, err := watchPodLogsForSubstring(logCtx, &managerPods.Items[0], "manager", substring)
60+
require.NoError(t, err)
61+
require.True(t, found)
62+
63+
t.Log("Checking that the ClusterCatalog is unpacked")
64+
require.EventuallyWithT(t, func(ct *assert.CollectT) {
65+
var clusterCatalog catalogdv1alpha1.ClusterCatalog
66+
assert.NoError(ct, c.Get(ctx, types.NamespacedName{Name: testClusterCatalogName}, &clusterCatalog))
67+
cond := apimeta.FindStatusCondition(clusterCatalog.Status.Conditions, catalogdv1alpha1.TypeUnpacked)
68+
if !assert.NotNil(ct, cond) {
69+
return
70+
}
71+
assert.Equal(ct, metav1.ConditionTrue, cond.Status)
72+
assert.Equal(ct, catalogdv1alpha1.ReasonUnpackSuccessful, cond.Reason)
73+
}, time.Minute, time.Second)
74+
75+
t.Log("Checking that the ClusterExtension is installed")
76+
var clusterExtension ocv1alpha1.ClusterExtension
77+
require.EventuallyWithT(t, func(ct *assert.CollectT) {
78+
assert.NoError(ct, c.Get(ctx, types.NamespacedName{Name: testClusterExtensionName}, &clusterExtension))
79+
cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled)
80+
if !assert.NotNil(ct, cond) {
81+
return
82+
}
83+
assert.Equal(ct, metav1.ConditionTrue, cond.Status)
84+
assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason)
85+
assert.Contains(ct, cond.Message, "Instantiated bundle")
86+
assert.NotEmpty(ct, clusterExtension.Status.InstalledBundle)
87+
assert.NotEmpty(ct, clusterExtension.Status.InstalledBundle.Version)
88+
}, time.Minute, time.Second)
89+
90+
previousVersion := clusterExtension.Status.InstalledBundle.Version
91+
92+
t.Log("Updating the ClusterExtension to change version")
93+
// Make sure that after we upgrade OLM itself we can still reconcile old objects if we change them
94+
clusterExtension.Spec.Version = "1.0.1"
95+
require.NoError(t, c.Update(ctx, &clusterExtension))
96+
97+
t.Log("Checking that the ClusterExtension installs successfully")
98+
require.EventuallyWithT(t, func(ct *assert.CollectT) {
99+
assert.NoError(ct, c.Get(ctx, types.NamespacedName{Name: testClusterExtensionName}, &clusterExtension))
100+
cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeInstalled)
101+
if !assert.NotNil(ct, cond) {
102+
return
103+
}
104+
assert.Equal(ct, ocv1alpha1.ReasonSuccess, cond.Reason)
105+
assert.Contains(ct, cond.Message, "Instantiated bundle")
106+
assert.Equal(ct, &ocv1alpha1.BundleMetadata{Name: "prometheus-operator.1.0.1", Version: "1.0.1"}, clusterExtension.Status.ResolvedBundle)
107+
assert.Equal(ct, &ocv1alpha1.BundleMetadata{Name: "prometheus-operator.1.0.1", Version: "1.0.1"}, clusterExtension.Status.InstalledBundle)
108+
assert.NotEqual(ct, previousVersion, clusterExtension.Status.InstalledBundle.Version)
109+
}, time.Minute, time.Second)
110+
}
111+
112+
func watchPodLogsForSubstring(ctx context.Context, pod *corev1.Pod, container, substring string) (bool, error) {
113+
podLogOpts := corev1.PodLogOptions{
114+
Follow: true,
115+
Container: container,
116+
}
117+
118+
req := kclientset.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &podLogOpts)
119+
podLogs, err := req.Stream(ctx)
120+
if err != nil {
121+
return false, err
122+
}
123+
defer podLogs.Close()
124+
125+
scanner := bufio.NewScanner(podLogs)
126+
for scanner.Scan() {
127+
line := scanner.Text()
128+
129+
if strings.Contains(line, substring) {
130+
return true, nil
131+
}
132+
}
133+
134+
return false, scanner.Err()
135+
}
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package upgradee2e
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"testing"
7+
8+
"k8s.io/client-go/kubernetes"
9+
ctrl "sigs.k8s.io/controller-runtime"
10+
"sigs.k8s.io/controller-runtime/pkg/client"
11+
12+
"github.com/operator-framework/operator-controller/pkg/scheme"
13+
)
14+
15+
const (
16+
testClusterCatalogNameEnv = "TEST_CLUSTER_CATALOG_NAME"
17+
testClusterExtensionNameEnv = "TEST_CLUSTER_EXTENSION_NAME"
18+
)
19+
20+
var (
21+
c client.Client
22+
kclientset kubernetes.Interface
23+
24+
testClusterCatalogName string
25+
testClusterExtensionName string
26+
)
27+
28+
func TestMain(m *testing.M) {
29+
var ok bool
30+
testClusterCatalogName, ok = os.LookupEnv(testClusterCatalogNameEnv)
31+
if !ok {
32+
fmt.Printf("%q is not set", testClusterCatalogNameEnv)
33+
os.Exit(1)
34+
}
35+
testClusterExtensionName, ok = os.LookupEnv(testClusterExtensionNameEnv)
36+
if !ok {
37+
fmt.Printf("%q is not set", testClusterExtensionNameEnv)
38+
os.Exit(1)
39+
}
40+
41+
cfg := ctrl.GetConfigOrDie()
42+
43+
var err error
44+
c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
45+
if err != nil {
46+
fmt.Printf("failed to create client: %s\n", err)
47+
os.Exit(1)
48+
}
49+
50+
kclientset, err = kubernetes.NewForConfig(cfg)
51+
if err != nil {
52+
fmt.Printf("failed to create kubernetes clientset: %s\n", err)
53+
os.Exit(1)
54+
}
55+
56+
os.Exit(m.Run())
57+
}

0 commit comments

Comments
 (0)