Skip to content

Commit 8c59a33

Browse files
authored
Respect .spec.upgradeConstraintPolicy (#520)
This commit makes OLM respect `.spec.upgradeConstraintPolicy` set on an `Operator` CR when chosing upgrade edges. Signed-off-by: Mikalai Radchuk <[email protected]>
1 parent 774ab74 commit 8c59a33

File tree

6 files changed

+404
-20
lines changed

6 files changed

+404
-20
lines changed

internal/controllers/operator_controller_test.go

Lines changed: 258 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,7 +1062,7 @@ func TestOperatorUpgrade(t *testing.T) {
10621062
Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)),
10631063
}
10641064

1065-
t.Run("semver upgrade constraints", func(t *testing.T) {
1065+
t.Run("semver upgrade constraints enforcement of upgrades within major version", func(t *testing.T) {
10661066
defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, true)()
10671067
defer func() {
10681068
require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{}))
@@ -1155,7 +1155,7 @@ func TestOperatorUpgrade(t *testing.T) {
11551155
assert.Equal(t, `resolved to "quay.io/operatorhubio/[email protected]"`, cond.Message)
11561156
})
11571157

1158-
t.Run("legacy semantics upgrade constraints", func(t *testing.T) {
1158+
t.Run("legacy semantics upgrade constraints enforcement", func(t *testing.T) {
11591159
defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, false)()
11601160
defer func() {
11611161
require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{}))
@@ -1247,6 +1247,262 @@ func TestOperatorUpgrade(t *testing.T) {
12471247
assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason)
12481248
assert.Equal(t, `resolved to "quay.io/operatorhubio/[email protected]"`, cond.Message)
12491249
})
1250+
1251+
t.Run("ignore upgrade constraints", func(t *testing.T) {
1252+
for _, tt := range []struct {
1253+
name string
1254+
flagState bool
1255+
}{
1256+
{
1257+
name: "ForceSemverUpgradeConstraints feature gate enabled",
1258+
flagState: true,
1259+
},
1260+
{
1261+
name: "ForceSemverUpgradeConstraints feature gate disabled",
1262+
flagState: false,
1263+
},
1264+
} {
1265+
t.Run(tt.name, func(t *testing.T) {
1266+
defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, tt.flagState)()
1267+
defer func() {
1268+
require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{}))
1269+
require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{}))
1270+
}()
1271+
1272+
opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))}
1273+
operator := &operatorsv1alpha1.Operator{
1274+
ObjectMeta: metav1.ObjectMeta{Name: opKey.Name},
1275+
Spec: operatorsv1alpha1.OperatorSpec{
1276+
PackageName: "prometheus",
1277+
Version: "1.0.0",
1278+
Channel: "beta",
1279+
UpgradeConstraintPolicy: operatorsv1alpha1.UpgradeConstraintPolicyIgnore,
1280+
},
1281+
}
1282+
// Create an operator
1283+
err := cl.Create(ctx, operator)
1284+
require.NoError(t, err)
1285+
1286+
// Run reconcile
1287+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1288+
require.NoError(t, err)
1289+
assert.Equal(t, ctrl.Result{}, res)
1290+
1291+
// Refresh the operator after reconcile
1292+
err = cl.Get(ctx, opKey, operator)
1293+
require.NoError(t, err)
1294+
1295+
// Checking the status fields
1296+
assert.Equal(t, "quay.io/operatorhubio/[email protected]", operator.Status.ResolvedBundleResource)
1297+
1298+
// checking the expected conditions
1299+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1300+
require.NotNil(t, cond)
1301+
assert.Equal(t, metav1.ConditionTrue, cond.Status)
1302+
assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason)
1303+
assert.Equal(t, `resolved to "quay.io/operatorhubio/[email protected]"`, cond.Message)
1304+
1305+
// We can go to the next major version when using semver
1306+
// as well as to the version which is not next in the channel
1307+
// when using legacy constraints
1308+
operator.Spec.Version = "2.0.0"
1309+
err = cl.Update(ctx, operator)
1310+
require.NoError(t, err)
1311+
1312+
// Run reconcile again
1313+
res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1314+
require.NoError(t, err)
1315+
assert.Equal(t, ctrl.Result{}, res)
1316+
1317+
// Refresh the operator after reconcile
1318+
err = cl.Get(ctx, opKey, operator)
1319+
require.NoError(t, err)
1320+
1321+
// Checking the status fields
1322+
assert.Equal(t, "quay.io/operatorhubio/[email protected]", operator.Status.ResolvedBundleResource)
1323+
1324+
// checking the expected conditions
1325+
cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1326+
require.NotNil(t, cond)
1327+
assert.Equal(t, metav1.ConditionTrue, cond.Status)
1328+
assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason)
1329+
assert.Equal(t, `resolved to "quay.io/operatorhubio/[email protected]"`, cond.Message)
1330+
})
1331+
}
1332+
})
1333+
}
1334+
1335+
func TestOperatorDowngrade(t *testing.T) {
1336+
ctx := context.Background()
1337+
fakeCatalogClient := testutil.NewFakeCatalogClient(testBundleList)
1338+
reconciler := &controllers.OperatorReconciler{
1339+
Client: cl,
1340+
Scheme: sch,
1341+
Resolver: solver.NewDeppySolver(controllers.NewVariableSource(cl, &fakeCatalogClient)),
1342+
}
1343+
1344+
t.Run("enforce upgrade constraints", func(t *testing.T) {
1345+
for _, tt := range []struct {
1346+
name string
1347+
flagState bool
1348+
}{
1349+
{
1350+
name: "ForceSemverUpgradeConstraints feature gate enabled",
1351+
flagState: true,
1352+
},
1353+
{
1354+
name: "ForceSemverUpgradeConstraints feature gate disabled",
1355+
flagState: false,
1356+
},
1357+
} {
1358+
t.Run(tt.name, func(t *testing.T) {
1359+
defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, tt.flagState)()
1360+
defer func() {
1361+
require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{}))
1362+
require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{}))
1363+
}()
1364+
1365+
opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))}
1366+
operator := &operatorsv1alpha1.Operator{
1367+
ObjectMeta: metav1.ObjectMeta{Name: opKey.Name},
1368+
Spec: operatorsv1alpha1.OperatorSpec{
1369+
PackageName: "prometheus",
1370+
Version: "1.0.1",
1371+
Channel: "beta",
1372+
},
1373+
}
1374+
// Create an operator
1375+
err := cl.Create(ctx, operator)
1376+
require.NoError(t, err)
1377+
1378+
// Run reconcile
1379+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1380+
require.NoError(t, err)
1381+
assert.Equal(t, ctrl.Result{}, res)
1382+
1383+
// Refresh the operator after reconcile
1384+
err = cl.Get(ctx, opKey, operator)
1385+
require.NoError(t, err)
1386+
1387+
// Checking the status fields
1388+
assert.Equal(t, "quay.io/operatorhubio/[email protected]", operator.Status.ResolvedBundleResource)
1389+
1390+
// checking the expected conditions
1391+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1392+
require.NotNil(t, cond)
1393+
assert.Equal(t, metav1.ConditionTrue, cond.Status)
1394+
assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason)
1395+
assert.Equal(t, `resolved to "quay.io/operatorhubio/[email protected]"`, cond.Message)
1396+
1397+
// Invalid operation: can not downgrade
1398+
operator.Spec.Version = "1.0.0"
1399+
err = cl.Update(ctx, operator)
1400+
require.NoError(t, err)
1401+
1402+
// Run reconcile again
1403+
res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1404+
require.Error(t, err)
1405+
assert.Equal(t, ctrl.Result{}, res)
1406+
1407+
// Refresh the operator after reconcile
1408+
err = cl.Get(ctx, opKey, operator)
1409+
require.NoError(t, err)
1410+
1411+
// Checking the status fields
1412+
// TODO: https://github.com/operator-framework/operator-controller/issues/320
1413+
assert.Equal(t, "", operator.Status.ResolvedBundleResource)
1414+
1415+
// checking the expected conditions
1416+
cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1417+
require.NotNil(t, cond)
1418+
assert.Equal(t, metav1.ConditionFalse, cond.Status)
1419+
assert.Equal(t, operatorsv1alpha1.ReasonResolutionFailed, cond.Reason)
1420+
assert.Contains(t, cond.Message, "constraints not satisfiable")
1421+
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;")
1422+
})
1423+
}
1424+
})
1425+
1426+
t.Run("ignore upgrade constraints", func(t *testing.T) {
1427+
for _, tt := range []struct {
1428+
name string
1429+
flagState bool
1430+
}{
1431+
{
1432+
name: "ForceSemverUpgradeConstraints feature gate enabled",
1433+
flagState: true,
1434+
},
1435+
{
1436+
name: "ForceSemverUpgradeConstraints feature gate disabled",
1437+
flagState: false,
1438+
},
1439+
} {
1440+
t.Run(tt.name, func(t *testing.T) {
1441+
defer featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.ForceSemverUpgradeConstraints, tt.flagState)()
1442+
defer func() {
1443+
require.NoError(t, cl.DeleteAllOf(ctx, &operatorsv1alpha1.Operator{}))
1444+
require.NoError(t, cl.DeleteAllOf(ctx, &rukpakv1alpha1.BundleDeployment{}))
1445+
}()
1446+
1447+
opKey := types.NamespacedName{Name: fmt.Sprintf("operator-test-%s", rand.String(8))}
1448+
operator := &operatorsv1alpha1.Operator{
1449+
ObjectMeta: metav1.ObjectMeta{Name: opKey.Name},
1450+
Spec: operatorsv1alpha1.OperatorSpec{
1451+
PackageName: "prometheus",
1452+
Version: "2.0.0",
1453+
Channel: "beta",
1454+
UpgradeConstraintPolicy: operatorsv1alpha1.UpgradeConstraintPolicyIgnore,
1455+
},
1456+
}
1457+
// Create an operator
1458+
err := cl.Create(ctx, operator)
1459+
require.NoError(t, err)
1460+
1461+
// Run reconcile
1462+
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1463+
require.NoError(t, err)
1464+
assert.Equal(t, ctrl.Result{}, res)
1465+
1466+
// Refresh the operator after reconcile
1467+
err = cl.Get(ctx, opKey, operator)
1468+
require.NoError(t, err)
1469+
1470+
// Checking the status fields
1471+
assert.Equal(t, "quay.io/operatorhubio/[email protected]", operator.Status.ResolvedBundleResource)
1472+
1473+
// checking the expected conditions
1474+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1475+
require.NotNil(t, cond)
1476+
assert.Equal(t, metav1.ConditionTrue, cond.Status)
1477+
assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason)
1478+
assert.Equal(t, `resolved to "quay.io/operatorhubio/[email protected]"`, cond.Message)
1479+
1480+
// We downgrade
1481+
operator.Spec.Version = "1.0.0"
1482+
err = cl.Update(ctx, operator)
1483+
require.NoError(t, err)
1484+
1485+
// Run reconcile again
1486+
res, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
1487+
require.NoError(t, err)
1488+
assert.Equal(t, ctrl.Result{}, res)
1489+
1490+
// Refresh the operator after reconcile
1491+
err = cl.Get(ctx, opKey, operator)
1492+
require.NoError(t, err)
1493+
1494+
// Checking the status fields
1495+
assert.Equal(t, "quay.io/operatorhubio/[email protected]", operator.Status.ResolvedBundleResource)
1496+
1497+
// checking the expected conditions
1498+
cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
1499+
require.NotNil(t, cond)
1500+
assert.Equal(t, metav1.ConditionTrue, cond.Status)
1501+
assert.Equal(t, operatorsv1alpha1.ReasonSuccess, cond.Reason)
1502+
assert.Equal(t, `resolved to "quay.io/operatorhubio/[email protected]"`, cond.Message)
1503+
})
1504+
}
1505+
})
12501506
}
12511507

12521508
var (

internal/controllers/variable_source.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func (v *VariableSource) GetVariables(ctx context.Context) ([]deppy.Variable, er
7171
return variablesources.NewOperatorVariableSource(operatorList.Items, allBundles, inputVariableSource), nil
7272
},
7373
func(inputVariableSource input.VariableSource) (input.VariableSource, error) {
74-
return variablesources.NewBundleDeploymentVariableSource(bundleDeploymentList.Items, allBundles, inputVariableSource), nil
74+
return variablesources.NewBundleDeploymentVariableSource(operatorList.Items, bundleDeploymentList.Items, allBundles, inputVariableSource), nil
7575
},
7676
func(inputVariableSource input.VariableSource) (input.VariableSource, error) {
7777
return variablesources.NewBundlesAndDepsVariableSource(allBundles, inputVariableSource), nil

internal/resolution/variablesources/bundle_deployment.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,22 @@ import (
77
"github.com/operator-framework/deppy/pkg/deppy/input"
88
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
99

10+
operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
1011
"github.com/operator-framework/operator-controller/internal/catalogmetadata"
1112
)
1213

1314
var _ input.VariableSource = &BundleDeploymentVariableSource{}
1415

1516
type BundleDeploymentVariableSource struct {
17+
operators []operatorsv1alpha1.Operator
1618
bundleDeployments []rukpakv1alpha1.BundleDeployment
1719
allBundles []*catalogmetadata.Bundle
1820
inputVariableSource input.VariableSource
1921
}
2022

21-
func NewBundleDeploymentVariableSource(bundleDeployments []rukpakv1alpha1.BundleDeployment, allBundles []*catalogmetadata.Bundle, inputVariableSource input.VariableSource) *BundleDeploymentVariableSource {
23+
func NewBundleDeploymentVariableSource(operators []operatorsv1alpha1.Operator, bundleDeployments []rukpakv1alpha1.BundleDeployment, allBundles []*catalogmetadata.Bundle, inputVariableSource input.VariableSource) *BundleDeploymentVariableSource {
2224
return &BundleDeploymentVariableSource{
25+
operators: operators,
2326
bundleDeployments: bundleDeployments,
2427
allBundles: allBundles,
2528
inputVariableSource: inputVariableSource,
@@ -37,7 +40,7 @@ func (o *BundleDeploymentVariableSource) GetVariables(ctx context.Context) ([]de
3740
return nil, err
3841
}
3942

40-
installedPackages, err := MakeInstalledPackageVariables(o.allBundles, o.bundleDeployments)
43+
installedPackages, err := MakeInstalledPackageVariables(o.allBundles, o.operators, o.bundleDeployments)
4144
if err != nil {
4245
return nil, err
4346
}

0 commit comments

Comments
 (0)