@@ -4,26 +4,34 @@ import (
4
4
"context"
5
5
"encoding/json"
6
6
"fmt"
7
+ "path/filepath"
8
+ "testing"
7
9
8
10
. "github.com/onsi/ginkgo/v2"
9
11
. "github.com/onsi/gomega"
10
12
"github.com/operator-framework/deppy/pkg/deppy/solver"
11
13
"github.com/operator-framework/operator-registry/alpha/declcfg"
12
14
"github.com/operator-framework/operator-registry/alpha/property"
13
15
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
16
+ "github.com/stretchr/testify/assert"
17
+ "github.com/stretchr/testify/require"
14
18
apimeta "k8s.io/apimachinery/pkg/api/meta"
15
19
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20
+ "k8s.io/apimachinery/pkg/runtime"
16
21
"k8s.io/apimachinery/pkg/types"
17
22
"k8s.io/apimachinery/pkg/util/rand"
23
+ featuregatetesting "k8s.io/component-base/featuregate/testing"
18
24
"k8s.io/utils/pointer"
19
25
ctrl "sigs.k8s.io/controller-runtime"
20
26
"sigs.k8s.io/controller-runtime/pkg/client"
21
27
"sigs.k8s.io/controller-runtime/pkg/client/fake"
28
+ "sigs.k8s.io/controller-runtime/pkg/envtest"
22
29
23
30
operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
24
31
"github.com/operator-framework/operator-controller/internal/catalogmetadata"
25
32
"github.com/operator-framework/operator-controller/internal/conditionsets"
26
33
"github.com/operator-framework/operator-controller/internal/controllers"
34
+ "github.com/operator-framework/operator-controller/pkg/features"
27
35
testutil "github.com/operator-framework/operator-controller/test/util"
28
36
)
29
37
@@ -1048,6 +1056,223 @@ func verifyConditionsInvariants(op *operatorsv1alpha1.Operator) {
1048
1056
}
1049
1057
}
1050
1058
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
+
1051
1276
var (
1052
1277
prometheusAlphaChannel = catalogmetadata.Channel {
1053
1278
Channel : declcfg.Channel {
0 commit comments