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