Skip to content

Commit dd5096b

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

File tree

1 file changed

+225
-0
lines changed

1 file changed

+225
-0
lines changed

internal/controllers/operator_controller_test.go

+225
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,34 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"path/filepath"
8+
"testing"
79

810
. "github.com/onsi/ginkgo/v2"
911
. "github.com/onsi/gomega"
1012
"github.com/operator-framework/deppy/pkg/deppy/solver"
1113
"github.com/operator-framework/operator-registry/alpha/declcfg"
1214
"github.com/operator-framework/operator-registry/alpha/property"
1315
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
16+
"github.com/stretchr/testify/assert"
17+
"github.com/stretchr/testify/require"
1418
apimeta "k8s.io/apimachinery/pkg/api/meta"
1519
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20+
"k8s.io/apimachinery/pkg/runtime"
1621
"k8s.io/apimachinery/pkg/types"
1722
"k8s.io/apimachinery/pkg/util/rand"
23+
featuregatetesting "k8s.io/component-base/featuregate/testing"
1824
"k8s.io/utils/pointer"
1925
ctrl "sigs.k8s.io/controller-runtime"
2026
"sigs.k8s.io/controller-runtime/pkg/client"
2127
"sigs.k8s.io/controller-runtime/pkg/client/fake"
28+
"sigs.k8s.io/controller-runtime/pkg/envtest"
2229

2330
operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
2431
"github.com/operator-framework/operator-controller/internal/catalogmetadata"
2532
"github.com/operator-framework/operator-controller/internal/conditionsets"
2633
"github.com/operator-framework/operator-controller/internal/controllers"
34+
"github.com/operator-framework/operator-controller/pkg/features"
2735
testutil "github.com/operator-framework/operator-controller/test/util"
2836
)
2937

@@ -1048,6 +1056,223 @@ func verifyConditionsInvariants(op *operatorsv1alpha1.Operator) {
10481056
}
10491057
}
10501058

1059+
func TestOperatorUpgrade(t *testing.T) {
1060+
// bootstrapping test environment
1061+
var err error
1062+
testEnv := &envtest.Environment{
1063+
CRDDirectoryPaths: []string{
1064+
filepath.Join("..", "..", "config", "crd", "bases"),
1065+
filepath.Join("..", "..", "testdata", "crds")},
1066+
ErrorIfCRDPathMissing: true,
1067+
}
1068+
1069+
cfg, err = testEnv.Start()
1070+
require.NoError(t, err)
1071+
assert.NotNil(t, cfg)
1072+
1073+
sch = runtime.NewScheme()
1074+
require.NoError(t, operatorsv1alpha1.AddToScheme(sch))
1075+
require.NoError(t, rukpakv1alpha1.AddToScheme(sch))
1076+
1077+
cl, err = client.New(cfg, client.Options{Scheme: sch})
1078+
require.NoError(t, err)
1079+
assert.NotNil(t, cl)
1080+
1081+
ctx := context.Background()
1082+
fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList)
1083+
reconciler := &controllers.OperatorReconciler{
1084+
Client: cl,
1085+
Scheme: sch,
1086+
Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)),
1087+
}
1088+
1089+
t.Run("semver upgrade constraints", func(t *testing.T) {
1090+
defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, true)()
1091+
defer func() {
1092+
require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{}))
1093+
require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{}))
1094+
}()
1095+
1096+
pkgName := "prometheus"
1097+
pkgVer := "1.0.0"
1098+
pkgChan := "beta"
1099+
opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))}
1100+
operator := &operatorsv1alpha1.Operator{
1101+
ObjectMeta: metav1.ObjectMeta{Name: opKey.Name},
1102+
Spec: operatorsv1alpha1.OperatorSpec{
1103+
PackageName: pkgName,
1104+
Version: pkgVer,
1105+
Channel: pkgChan,
1106+
},
1107+
}
1108+
// Create an operator
1109+
err = cl.Create(ctx, operator)
1110+
require.NoError(t, err)
1111+
1112+
// Run reconcile
1113+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1114+
require.NoError(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+
assert.Equal(t, "quay.io/operatorhubio/[email protected]", operator.Status.ResolvedBundleResource)
1123+
1124+
// checking the expected conditions
1125+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1126+
require.NotNil(t, cond)
1127+
assert.Equal(t, metav1.ConditionTrue, cond.Status)
1128+
assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason)
1129+
assert.Equal(t, `resolved to "quay.io/operatorhubio/[email protected]"`, cond.Message)
1130+
1131+
// Invalid update: can not go to the next major version
1132+
operator.Spec.Version = "2.0.0"
1133+
err = cl.Update(ctx, operator)
1134+
require.NoError(t, err)
1135+
1136+
// Run reconcile again
1137+
res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1138+
require.Error(t, err)
1139+
assert.Equal(t, ctrl.Result{}, res)
1140+
1141+
// Refresh the operator after reconcile
1142+
err = cl.Get(ctx, opKey, operator)
1143+
require.NoError(t, err)
1144+
1145+
// Checking the status fields
1146+
// TODO: https://github.com/operator-framework/operator-controller/issues/320
1147+
assert.Equal(t, "", operator.Status.ResolvedBundleResource)
1148+
1149+
// checking the expected conditions
1150+
cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1151+
require.NotNil(t, cond)
1152+
assert.Equal(t, metav1.ConditionFalse, cond.Status)
1153+
assert.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason)
1154+
assert.Contains(t, cond.Message, "constraints not satisfiable")
1155+
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;")
1156+
1157+
// Valid update skipping one version
1158+
operator.Spec.Version = "1.2.0"
1159+
err = cl.Update(ctx, operator)
1160+
require.NoError(t, err)
1161+
1162+
// Run reconcile again
1163+
res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1164+
require.NoError(t, err)
1165+
assert.Equal(t, ctrl.Result{}, res)
1166+
1167+
// Refresh the operator after reconcile
1168+
err = cl.Get(ctx, opKey, operator)
1169+
require.NoError(t, err)
1170+
1171+
// Checking the status fields
1172+
assert.Equal(t, "quay.io/operatorhubio/[email protected]", operator.Status.ResolvedBundleResource)
1173+
1174+
// checking the expected conditions
1175+
cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1176+
require.NotNil(t, cond)
1177+
assert.Equal(t, metav1.ConditionTrue, cond.Status)
1178+
assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason)
1179+
assert.Equal(t, `resolved to "quay.io/operatorhubio/[email protected]"`, cond.Message)
1180+
})
1181+
1182+
t.Run("legacy semantics upgrade constraints", func(t *testing.T) {
1183+
defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, false)()
1184+
defer func() {
1185+
require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{}))
1186+
require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{}))
1187+
}()
1188+
1189+
pkgName := "prometheus"
1190+
pkgVer := "1.0.0"
1191+
pkgChan := "beta"
1192+
opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))}
1193+
operator := &operatorsv1alpha1.Operator{
1194+
ObjectMeta: metav1.ObjectMeta{Name: opKey.Name},
1195+
Spec: operatorsv1alpha1.OperatorSpec{
1196+
PackageName: pkgName,
1197+
Version: pkgVer,
1198+
Channel: pkgChan,
1199+
},
1200+
}
1201+
// Create an operator
1202+
err = cl.Create(ctx, operator)
1203+
require.NoError(t, err)
1204+
1205+
// Run reconcile
1206+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1207+
require.NoError(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+
assert.Equal(t, "quay.io/operatorhubio/[email protected]", operator.Status.ResolvedBundleResource)
1216+
1217+
// checking the expected conditions
1218+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1219+
require.NotNil(t, cond)
1220+
assert.Equal(t, metav1.ConditionTrue, cond.Status)
1221+
assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason)
1222+
assert.Equal(t, `resolved to "quay.io/operatorhubio/[email protected]"`, cond.Message)
1223+
1224+
// Invalid update: can not upgrade by skipping a version in the replaces chain
1225+
operator.Spec.Version = "1.2.0"
1226+
err = cl.Update(ctx, operator)
1227+
require.NoError(t, err)
1228+
1229+
// Run reconcile again
1230+
res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1231+
require.Error(t, err)
1232+
assert.Equal(t, ctrl.Result{}, res)
1233+
1234+
// Refresh the operator after reconcile
1235+
err = cl.Get(ctx, opKey, operator)
1236+
require.NoError(t, err)
1237+
1238+
// Checking the status fields
1239+
// TODO: https://github.com/operator-framework/operator-controller/issues/320
1240+
assert.Equal(t, "", operator.Status.ResolvedBundleResource)
1241+
1242+
// checking the expected conditions
1243+
cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1244+
require.NotNil(t, cond)
1245+
assert.Equal(t, metav1.ConditionFalse, cond.Status)
1246+
assert.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason)
1247+
assert.Contains(t, cond.Message, "constraints not satisfiable")
1248+
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;")
1249+
1250+
// Valid update skipping one version
1251+
operator.Spec.Version = "1.0.1"
1252+
err = cl.Update(ctx, operator)
1253+
require.NoError(t, err)
1254+
1255+
// Run reconcile again
1256+
res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1257+
require.NoError(t, err)
1258+
assert.Equal(t, ctrl.Result{}, res)
1259+
1260+
// Refresh the operator after reconcile
1261+
err = cl.Get(ctx, opKey, operator)
1262+
require.NoError(t, err)
1263+
1264+
// Checking the status fields
1265+
assert.Equal(t, "quay.io/operatorhubio/[email protected]", operator.Status.ResolvedBundleResource)
1266+
1267+
// checking the expected conditions
1268+
cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1269+
require.NotNil(t, cond)
1270+
assert.Equal(t, metav1.ConditionTrue, cond.Status)
1271+
assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason)
1272+
assert.Equal(t, `resolved to "quay.io/operatorhubio/[email protected]"`, cond.Message)
1273+
})
1274+
}
1275+
10511276
var (
10521277
prometheusAlphaChannel = catalogmetadata.Channel{
10531278
Channel: declcfg.Channel{

0 commit comments

Comments
 (0)