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
25 changes: 14 additions & 11 deletions pkg/cvo/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ const (
// cannot reach the desired state. It is considered more serious than Degraded
// and indicates the cluster is not healthy.
ClusterStatusFailing = configv1.ClusterStatusConditionType("Failing")

// MaxHistory is the maximum size of ClusterVersion history. Once exceeded
// ClusterVersion history will be pruned.
MaxHistory = 50
)

func mergeEqualVersions(current *configv1.UpdateHistory, desired configv1.Release) bool {
Expand Down Expand Up @@ -128,26 +132,25 @@ func mergeOperatorHistory(config *configv1.ClusterVersion, desired configv1.Rele
}

// TODO: prune Z versions over transitions to Y versions, keep initial installed version
pruneStatusHistory(config, 50)
pruneStatusHistory(config, MaxHistory)

config.Status.Desired = desired
}

// pruneStatusHistory maintains history size at MaxHistory by removing entry at index MaxHistory
// unless that entry is a completed update in which case entry at MaxHistory-1 is removed thereby
// retaining the initial completed version.
func pruneStatusHistory(config *configv1.ClusterVersion, maxHistory int) {
if len(config.Status.History) <= maxHistory {
return
}
for i, item := range config.Status.History {
if item.State != configv1.CompletedUpdate {
continue
}
// guarantee the last position in the history is always a completed item
if i >= maxHistory {
config.Status.History[maxHistory-1] = item
}
break
if config.Status.History[maxHistory].State == configv1.CompletedUpdate {
item := config.Status.History[maxHistory]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I doubt we will drop MaxHistory below the current 50, but if we do, is it worth teaching this to prefer the oldest >=maxHistory index that's completed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, because pruneStatusHistory gets called each time a new entry is added the history index will never exceed Maxhistory. This could change in the future but most likely if it does it will be part of a more robust history pruning re-do, e.g. implementing our enhancement, which will change things significantly anyway.

config.Status.History = config.Status.History[0 : maxHistory-1]
config.Status.History = append(config.Status.History, item)
} else {
config.Status.History = config.Status.History[:maxHistory]
}
config.Status.History = config.Status.History[:maxHistory]
}

// ClusterVersionInvalid indicates that the cluster version has an error that prevents the server from
Expand Down
71 changes: 70 additions & 1 deletion pkg/cvo/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"reflect"
"testing"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/diff"
Expand Down Expand Up @@ -80,14 +81,16 @@ func Test_pruneStatusHistory(t *testing.T) {
want []configv1.UpdateHistory
}{
{
name: "max history 2",
config: obj.DeepCopy(),
maxHistory: 2,
want: []configv1.UpdateHistory{
{State: configv1.PartialUpdate, Version: "0.0.10"},
{State: configv1.CompletedUpdate, Version: "0.0.7"},
{State: configv1.PartialUpdate, Version: "0.0.9"},
},
},
{
name: "max history 3",
config: obj.DeepCopy(),
maxHistory: 3,
want: []configv1.UpdateHistory{
Expand All @@ -97,6 +100,7 @@ func Test_pruneStatusHistory(t *testing.T) {
},
},
{
name: "max history 4",
config: obj.DeepCopy(),
maxHistory: 4,
want: []configv1.UpdateHistory{
Expand All @@ -107,6 +111,7 @@ func Test_pruneStatusHistory(t *testing.T) {
},
},
{
name: "max history 5",
config: obj.DeepCopy(),
maxHistory: 5,
want: []configv1.UpdateHistory{
Expand All @@ -118,6 +123,7 @@ func Test_pruneStatusHistory(t *testing.T) {
},
},
{
name: "max history 6",
config: obj.DeepCopy(),
maxHistory: 6,
want: []configv1.UpdateHistory{
Expand All @@ -134,12 +140,75 @@ func Test_pruneStatusHistory(t *testing.T) {
config := tt.config.DeepCopy()
pruneStatusHistory(config, tt.maxHistory)
if !reflect.DeepEqual(tt.want, config.Status.History) {
t.Logf("%v", config.Status.History)
t.Fatalf("%s", diff.ObjectReflectDiff(tt.want, config.Status.History))
}
})
}
}

func Test_mergeOperatorHistory(t *testing.T) {
obj := &configv1.ClusterVersion{}
var nowT time.Time
now := metav1.NewTime(nowT)
tests := []struct {
name string
config *configv1.ClusterVersion
first configv1.UpdateHistory
second configv1.UpdateHistory
want configv1.UpdateHistory
}{
{
name: "drop partial",
config: obj.DeepCopy(),
first: configv1.UpdateHistory{State: configv1.PartialUpdate, CompletionTime: &now, Version: "0.0.7", Image: "test:0"},
second: configv1.UpdateHistory{State: configv1.CompletedUpdate, CompletionTime: &now, Version: "0.0.6", Image: "test:1"},
want: configv1.UpdateHistory{State: configv1.CompletedUpdate, CompletionTime: &now, Version: "0.0.6", Image: "test:1"},
},
{
name: "keep completed",
config: obj.DeepCopy(),
first: configv1.UpdateHistory{State: configv1.CompletedUpdate, CompletionTime: &now, Version: "0.0.7", Image: "test:0"},
second: configv1.UpdateHistory{State: configv1.PartialUpdate, CompletionTime: &now, Version: "0.0.6", Image: "test:1"},
want: configv1.UpdateHistory{State: configv1.CompletedUpdate, CompletionTime: &now, Version: "0.0.7", Image: "test:0"},
},
{
name: "neither completed",
config: obj.DeepCopy(),
first: configv1.UpdateHistory{State: configv1.PartialUpdate, CompletionTime: &now, Version: "0.0.7", Image: "test:0"},
second: configv1.UpdateHistory{State: configv1.PartialUpdate, CompletionTime: &now, Version: "0.0.6", Image: "test:1"},
want: configv1.UpdateHistory{State: configv1.PartialUpdate, CompletionTime: &now, Version: "0.0.6", Image: "test:1"},
},
{
name: "keep first completed",
config: obj.DeepCopy(),
first: configv1.UpdateHistory{State: configv1.CompletedUpdate, CompletionTime: &now, Version: "0.0.7", Image: "test:0"},
second: configv1.UpdateHistory{State: configv1.CompletedUpdate, CompletionTime: &now, Version: "0.0.6", Image: "test:1"},
want: configv1.UpdateHistory{State: configv1.CompletedUpdate, CompletionTime: &now, Version: "0.0.7", Image: "test:0"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for i := 0; i <= MaxHistory; i++ {
image := fmt.Sprintf("test:%d", i)
if i == 0 {
mergeOperatorHistory(tt.config, configv1.Release{Image: image, Version: tt.first.Version}, false, now,
(tt.first.State == configv1.CompletedUpdate))
} else if i == 1 {
mergeOperatorHistory(tt.config, configv1.Release{Image: image, Version: tt.second.Version}, false, now,
(tt.second.State == configv1.CompletedUpdate))
} else {
version := fmt.Sprintf("%d", i)
mergeOperatorHistory(tt.config, configv1.Release{Image: image, Version: version}, false, now, true)
}
}
if !reflect.DeepEqual(tt.want, tt.config.Status.History[MaxHistory-1]) {
t.Fatalf("%s", diff.ObjectReflectDiff(tt.want, tt.config.Status.History[MaxHistory-1]))
}
})
}
}

func TestOperator_syncFailingStatus(t *testing.T) {
ctx := context.Background()
tests := []struct {
Expand Down