Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/cvo/cvo.go
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,7 @@ func hasReachedLevel(cv *configv1.ClusterVersion, desired configv1.Update) bool

func (optr *Operator) defaultPreconditionChecks() precondition.List {
return []precondition.Precondition{
preconditioncv.NewRollback(optr.currentVersion),
preconditioncv.NewUpgradeable(optr.cvLister),
preconditioncv.NewRecentEtcdBackup(optr.cvLister, optr.coLister),
preconditioncv.NewRecommendedUpdate(optr.cvLister),
Expand Down
65 changes: 65 additions & 0 deletions pkg/payload/precondition/clusterversion/rollback.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package clusterversion

import (
"context"
"fmt"

"github.com/blang/semver/v4"
configv1 "github.com/openshift/api/config/v1"

precondition "github.com/openshift/cluster-version-operator/pkg/payload/precondition"
)

// currentRelease is a callback for returning the version that is currently being reconciled.
type currentRelease func() configv1.Release

// Rollback blocks rollbacks from the version that is currently being reconciled.
type Rollback struct {
currentRelease
}

// NewRollback returns a new Rollback precondition check.
func NewRollback(fn currentRelease) *Rollback {
return &Rollback{
currentRelease: fn,
}
}

// Name returns Name for the precondition.
func (pf *Rollback) Name() string { return "ClusterVersionRollback" }

// Run runs the Rollback precondition, blocking rollbacks from the
// version that is currently being reconciled. It returns a
// PreconditionError when possible.
func (p *Rollback) Run(ctx context.Context, releaseContext precondition.ReleaseContext) error {
currentRelease := p.currentRelease()
currentVersion, err := semver.Parse(currentRelease.Version)
if err != nil {
return &precondition.Error{
Nested: err,
Reason: "InvalidCurrentVersion",
Message: err.Error(),
Name: p.Name(),
}
}

targetVersion, err := semver.Parse(releaseContext.DesiredVersion)
if err != nil {
return &precondition.Error{
Nested: err,
Reason: "InvalidDesiredVersion",
Message: err.Error(),
Name: p.Name(),
}
}

if targetVersion.LT(currentVersion) {
return &precondition.Error{
Reason: "LowDesiredVersion",
Message: fmt.Sprintf("%s is less than the current target %s, but rollbacks and downgrades are not recommended", targetVersion, currentVersion),
Name: p.Name(),
}
}

return nil
}
64 changes: 64 additions & 0 deletions pkg/payload/precondition/clusterversion/rollback_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package clusterversion

import (
"context"
"testing"

configv1 "github.com/openshift/api/config/v1"
"github.com/openshift/cluster-version-operator/pkg/payload/precondition"
)

func TestRollbackRun(t *testing.T) {
ctx := context.Background()

tests := []struct {
name string
currVersion string
desiredVersion string
expected string
}{
{
name: "update",
currVersion: "1.0.0",
desiredVersion: "1.0.1",
expected: "",
},
{
name: "no change",
currVersion: "1.0.0",
desiredVersion: "1.0.0",
expected: "",
},
{
name: "rollback",
currVersion: "1.0.1",
desiredVersion: "1.0.0",
expected: "1.0.0 is less than the current target 1.0.1, but rollbacks and downgrades are not recommended",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
instance := NewRollback(func() configv1.Release {
return configv1.Release{
Version: tc.currVersion,
}
})

err := instance.Run(ctx, precondition.ReleaseContext{
DesiredVersion: tc.desiredVersion,
})
switch {
case err != nil && len(tc.expected) == 0:
t.Error(err)
case err != nil && err.Error() == tc.expected:
case err != nil && err.Error() != tc.expected:
t.Error(err)
case err == nil && len(tc.expected) == 0:
case err == nil && len(tc.expected) != 0:
t.Error(err)
}

})
}
}