Skip to content

Commit 4028ae4

Browse files
author
Mikalai Radchuk
committed
Add operator upgrade tests
Signed-off-by: Mikalai Radchuk <[email protected]>
1 parent 1e7b9b1 commit 4028ae4

File tree

1 file changed

+201
-0
lines changed

1 file changed

+201
-0
lines changed

internal/controllers/operator_controller_test.go

+201
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,21 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"testing"
78

89
. "github.com/onsi/ginkgo/v2"
910
. "github.com/onsi/gomega"
1011
"github.com/operator-framework/deppy/pkg/deppy/solver"
1112
"github.com/operator-framework/operator-registry/alpha/declcfg"
1213
"github.com/operator-framework/operator-registry/alpha/property"
1314
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
15+
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
1417
apimeta "k8s.io/apimachinery/pkg/api/meta"
1518
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1619
"k8s.io/apimachinery/pkg/types"
1720
"k8s.io/apimachinery/pkg/util/rand"
21+
featuregatetesting "k8s.io/component-base/featuregate/testing"
1822
"k8s.io/utils/pointer"
1923
ctrl "sigs.k8s.io/controller-runtime"
2024
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -24,6 +28,7 @@ import (
2428
"github.com/operator-framework/operator-controller/internal/catalogmetadata"
2529
"github.com/operator-framework/operator-controller/internal/conditionsets"
2630
"github.com/operator-framework/operator-controller/internal/controllers"
31+
"github.com/operator-framework/operator-controller/pkg/features"
2732
testutil "github.com/operator-framework/operator-controller/test/util"
2833
)
2934

@@ -1048,6 +1053,202 @@ func verifyConditionsInvariants(op *operatorsv1alpha1.Operator) {
10481053
}
10491054
}
10501055

1056+
func TestOperatorUpgrade(t *testing.T) {
1057+
ctx := context.Background()
1058+
fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList)
1059+
reconciler := &controllers.OperatorReconciler{
1060+
Client: cl,
1061+
Scheme: sch,
1062+
Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)),
1063+
}
1064+
1065+
t.Run("semver upgrade constraints", func(t *testing.T) {
1066+
defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, true)()
1067+
defer func() {
1068+
require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{}))
1069+
require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{}))
1070+
}()
1071+
1072+
pkgName := "prometheus"
1073+
pkgVer := "1.0.0"
1074+
pkgChan := "beta"
1075+
opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))}
1076+
operator := &operatorsv1alpha1.Operator{
1077+
ObjectMeta: metav1.ObjectMeta{Name: opKey.Name},
1078+
Spec: operatorsv1alpha1.OperatorSpec{
1079+
PackageName: pkgName,
1080+
Version: pkgVer,
1081+
Channel: pkgChan,
1082+
},
1083+
}
1084+
// Create an operator
1085+
err := cl.Create(ctx, operator)
1086+
require.NoError(t, err)
1087+
1088+
// Run reconcile
1089+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1090+
require.NoError(t, err)
1091+
assert.Equal(t, ctrl.Result{}, res)
1092+
1093+
// Refresh the operator after reconcile
1094+
err = cl.Get(ctx, opKey, operator)
1095+
require.NoError(t, err)
1096+
1097+
// Checking the status fields
1098+
assert.Equal(t, "quay.io/operatorhubio/[email protected]", operator.Status.ResolvedBundleResource)
1099+
1100+
// checking the expected conditions
1101+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1102+
require.NotNil(t, cond)
1103+
assert.Equal(t, metav1.ConditionTrue, cond.Status)
1104+
assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason)
1105+
assert.Equal(t, `resolved to "quay.io/operatorhubio/[email protected]"`, cond.Message)
1106+
1107+
// Invalid update: can not go to the next major version
1108+
operator.Spec.Version = "2.0.0"
1109+
err = cl.Update(ctx, operator)
1110+
require.NoError(t, err)
1111+
1112+
// Run reconcile again
1113+
res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1114+
require.Error(t, err)
1115+
assert.Equal(t, ctrl.Result{}, res)
1116+
1117+
// Refresh the operator after reconcile
1118+
err = cl.Get(ctx, opKey, operator)
1119+
require.NoError(t, err)
1120+
1121+
// Checking the status fields
1122+
// TODO: https://github.com/operator-framework/operator-controller/issues/320
1123+
assert.Equal(t, "", operator.Status.ResolvedBundleResource)
1124+
1125+
// checking the expected conditions
1126+
cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1127+
require.NotNil(t, cond)
1128+
assert.Equal(t, metav1.ConditionFalse, cond.Status)
1129+
assert.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason)
1130+
assert.Contains(t, cond.Message, "constraints not satisfiable")
1131+
assert.Contains(t, cond.Message, "installed package prometheus requires at least one of fake-catalog-prometheus-operatorhub/prometheus/beta/1.2.0, fake-catalog-prometheus-operatorhub/prometheus/beta/1.0.1, fake-catalog-prometheus-operatorhub/prometheus/beta/1.0.0;")
1132+
1133+
// Valid update skipping one version
1134+
operator.Spec.Version = "1.2.0"
1135+
err = cl.Update(ctx, operator)
1136+
require.NoError(t, err)
1137+
1138+
// Run reconcile again
1139+
res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1140+
require.NoError(t, err)
1141+
assert.Equal(t, ctrl.Result{}, res)
1142+
1143+
// Refresh the operator after reconcile
1144+
err = cl.Get(ctx, opKey, operator)
1145+
require.NoError(t, err)
1146+
1147+
// Checking the status fields
1148+
assert.Equal(t, "quay.io/operatorhubio/[email protected]", operator.Status.ResolvedBundleResource)
1149+
1150+
// checking the expected conditions
1151+
cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1152+
require.NotNil(t, cond)
1153+
assert.Equal(t, metav1.ConditionTrue, cond.Status)
1154+
assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason)
1155+
assert.Equal(t, `resolved to "quay.io/operatorhubio/[email protected]"`, cond.Message)
1156+
})
1157+
1158+
t.Run("legacy semantics upgrade constraints", func(t *testing.T) {
1159+
defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, false)()
1160+
defer func() {
1161+
require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{}))
1162+
require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{}))
1163+
}()
1164+
1165+
pkgName := "prometheus"
1166+
pkgVer := "1.0.0"
1167+
pkgChan := "beta"
1168+
opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))}
1169+
operator := &operatorsv1alpha1.Operator{
1170+
ObjectMeta: metav1.ObjectMeta{Name: opKey.Name},
1171+
Spec: operatorsv1alpha1.OperatorSpec{
1172+
PackageName: pkgName,
1173+
Version: pkgVer,
1174+
Channel: pkgChan,
1175+
},
1176+
}
1177+
// Create an operator
1178+
err := cl.Create(ctx, operator)
1179+
require.NoError(t, err)
1180+
1181+
// Run reconcile
1182+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1183+
require.NoError(t, err)
1184+
assert.Equal(t, ctrl.Result{}, res)
1185+
1186+
// Refresh the operator after reconcile
1187+
err = cl.Get(ctx, opKey, operator)
1188+
require.NoError(t, err)
1189+
1190+
// Checking the status fields
1191+
assert.Equal(t, "quay.io/operatorhubio/[email protected]", operator.Status.ResolvedBundleResource)
1192+
1193+
// checking the expected conditions
1194+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1195+
require.NotNil(t, cond)
1196+
assert.Equal(t, metav1.ConditionTrue, cond.Status)
1197+
assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason)
1198+
assert.Equal(t, `resolved to "quay.io/operatorhubio/[email protected]"`, cond.Message)
1199+
1200+
// Invalid update: can not upgrade by skipping a version in the replaces chain
1201+
operator.Spec.Version = "1.2.0"
1202+
err = cl.Update(ctx, operator)
1203+
require.NoError(t, err)
1204+
1205+
// Run reconcile again
1206+
res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1207+
require.Error(t, err)
1208+
assert.Equal(t, ctrl.Result{}, res)
1209+
1210+
// Refresh the operator after reconcile
1211+
err = cl.Get(ctx, opKey, operator)
1212+
require.NoError(t, err)
1213+
1214+
// Checking the status fields
1215+
// TODO: https://github.com/operator-framework/operator-controller/issues/320
1216+
assert.Equal(t, "", operator.Status.ResolvedBundleResource)
1217+
1218+
// checking the expected conditions
1219+
cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1220+
require.NotNil(t, cond)
1221+
assert.Equal(t, metav1.ConditionFalse, cond.Status)
1222+
assert.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason)
1223+
assert.Contains(t, cond.Message, "constraints not satisfiable")
1224+
assert.Contains(t, cond.Message, "installed package prometheus requires at least one of fake-catalog-prometheus-operatorhub/prometheus/beta/1.0.1, fake-catalog-prometheus-operatorhub/prometheus/beta/1.0.0;")
1225+
1226+
// Valid update skipping one version
1227+
operator.Spec.Version = "1.0.1"
1228+
err = cl.Update(ctx, operator)
1229+
require.NoError(t, err)
1230+
1231+
// Run reconcile again
1232+
res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1233+
require.NoError(t, err)
1234+
assert.Equal(t, ctrl.Result{}, res)
1235+
1236+
// Refresh the operator after reconcile
1237+
err = cl.Get(ctx, opKey, operator)
1238+
require.NoError(t, err)
1239+
1240+
// Checking the status fields
1241+
assert.Equal(t, "quay.io/operatorhubio/[email protected]", operator.Status.ResolvedBundleResource)
1242+
1243+
// checking the expected conditions
1244+
cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1245+
require.NotNil(t, cond)
1246+
assert.Equal(t, metav1.ConditionTrue, cond.Status)
1247+
assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason)
1248+
assert.Equal(t, `resolved to "quay.io/operatorhubio/[email protected]"`, cond.Message)
1249+
})
1250+
}
1251+
10511252
var (
10521253
prometheusAlphaChannel = catalogmetadata.Channel{
10531254
Channel: declcfg.Channel{

0 commit comments

Comments
 (0)