Skip to content

Commit 4cfd94b

Browse files
Merge pull request #1381 from hongkailiu/OTA-1966
OTA-1966: Init the Proprosal Lifecycle Controller
2 parents ef11816 + 1a2c790 commit 4cfd94b

71 files changed

Lines changed: 19236 additions & 8 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.openshift-tests-extension/openshift_payload_cluster-version-operator.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,18 @@
9696
"source": "openshift:payload:cluster-version-operator",
9797
"lifecycle": "blocking",
9898
"environmentSelector": {}
99+
},
100+
{
101+
"name": "[Jira:\"Cluster Version Operator\"] cluster-version-operator should create proposals",
102+
"labels": {
103+
"Lifecycle:informing": {},
104+
"Serial": {}
105+
},
106+
"resources": {
107+
"isolation": {}
108+
},
109+
"source": "openshift:payload:cluster-version-operator",
110+
"lifecycle": "informing",
111+
"environmentSelector": {}
99112
}
100113
]

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ require (
3434
k8s.io/klog/v2 v2.130.1
3535
k8s.io/kube-aggregator v0.35.1
3636
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
37+
sigs.k8s.io/controller-runtime v0.22.2
3738
sigs.k8s.io/kustomize/kyaml v0.21.1
3839
sigs.k8s.io/yaml v1.6.0
3940
)
@@ -44,6 +45,7 @@ require (
4445
github.com/beorn7/perks v1.0.1 // indirect
4546
github.com/cespare/xxhash/v2 v2.3.0 // indirect
4647
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
48+
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
4749
github.com/fsnotify/fsnotify v1.9.0 // indirect
4850
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
4951
github.com/go-errors/errors v1.4.2 // indirect
@@ -100,7 +102,6 @@ require (
100102
gopkg.in/inf.v0 v0.9.1 // indirect
101103
k8s.io/component-base v0.35.1 // indirect
102104
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
103-
sigs.k8s.io/controller-runtime v0.22.2 // indirect
104105
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
105106
sigs.k8s.io/kube-storage-version-migrator v0.0.6-0.20230721195810-5c8923c5ff96 // indirect
106107
sigs.k8s.io/randfill v1.0.0 // indirect

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
1717
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1818
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
1919
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
20+
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
21+
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
2022
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
2123
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
2224
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
@@ -25,6 +27,8 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI
2527
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
2628
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
2729
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
30+
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
31+
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
2832
github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk=
2933
github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM=
3034
github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU=
@@ -167,6 +171,10 @@ go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKr
167171
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
168172
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
169173
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
174+
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
175+
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
176+
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
177+
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
170178
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
171179
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
172180
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=

pkg/cvo/availableupdates.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ func (optr *Operator) syncAvailableUpdates(ctx context.Context, config *configv1
182182

183183
// queue optr.sync() to update ClusterVersion status
184184
optr.queue.Add(queueKey)
185+
if optr.shouldEnableProposalController() {
186+
// queue optr.proposalController.Sync() to manage proposals
187+
optr.proposalController.Queue().Add(optr.proposalController.QueueKey())
188+
}
185189
return nil
186190
}
187191

pkg/cvo/availableupdates_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/blang/semver/v4"
1515
"github.com/google/go-cmp/cmp"
1616
"github.com/google/go-cmp/cmp/cmpopts"
17+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
1718

1819
corev1 "k8s.io/api/core/v1"
1920
k8serrors "k8s.io/apimachinery/pkg/api/errors"
@@ -29,6 +30,7 @@ import (
2930
"github.com/openshift/cluster-version-operator/pkg/clusterconditions/always"
3031
"github.com/openshift/cluster-version-operator/pkg/clusterconditions/mock"
3132
"github.com/openshift/cluster-version-operator/pkg/featuregates"
33+
"github.com/openshift/cluster-version-operator/pkg/proposal"
3234
"github.com/openshift/cluster-version-operator/pkg/risk"
3335
riskmock "github.com/openshift/cluster-version-operator/pkg/risk/mock"
3436
)
@@ -202,6 +204,14 @@ func newOperator(url string, cluster release, promqlMock clusterconditions.Condi
202204
availableUpdates := &availableUpdates{
203205
Current: configv1.Release{Version: cluster.version, Image: cluster.image},
204206
}
207+
operator.proposalController = proposal.NewController(
208+
func() ([]configv1.Release, []configv1.ConditionalUpdate, error) {
209+
return nil, nil, nil
210+
},
211+
fake.NewClientBuilder().Build(), func(_ string) (*configv1.ClusterVersion, error) {
212+
return &configv1.ClusterVersion{}, nil
213+
},
214+
)
205215
return availableUpdates, operator
206216
}
207217

pkg/cvo/cvo.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"sync"
99
"time"
1010

11+
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
12+
1113
corev1 "k8s.io/api/core/v1"
1214
apierrors "k8s.io/apimachinery/pkg/api/errors"
1315
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -53,6 +55,7 @@ import (
5355
"github.com/openshift/cluster-version-operator/pkg/payload"
5456
"github.com/openshift/cluster-version-operator/pkg/payload/precondition"
5557
preconditioncv "github.com/openshift/cluster-version-operator/pkg/payload/precondition/clusterversion"
58+
"github.com/openshift/cluster-version-operator/pkg/proposal"
5659
"github.com/openshift/cluster-version-operator/pkg/risk"
5760
"github.com/openshift/cluster-version-operator/pkg/risk/alert"
5861
)
@@ -213,6 +216,9 @@ type Operator struct {
213216
// risks holds update-risk source (in-cluster alerts, etc.)
214217
// that will be aggregated into conditional update risks.
215218
risks risk.Source
219+
220+
// proposalController, if enabled, watches available and conditionals updates and manage proposals for them
221+
proposalController *proposal.Controller
216222
}
217223

218224
// New returns a new cluster version operator.
@@ -242,6 +248,7 @@ func New(
242248
featureSet configv1.FeatureSet,
243249
cvoGates featuregates.CvoGateChecker,
244250
startingEnabledManifestFeatureGates sets.Set[string],
251+
rtClient runtimeclient.Client,
245252
) (*Operator, error) {
246253
eventBroadcaster := record.NewBroadcaster()
247254
eventBroadcaster.StartLogging(klog.Infof)
@@ -317,6 +324,14 @@ func New(
317324

318325
optr.configuration = configuration.NewClusterVersionOperatorConfiguration(operatorClient, operatorInformerFactory)
319326

327+
optr.proposalController = proposal.NewController(func() ([]configv1.Release, []configv1.ConditionalUpdate, error) {
328+
availableUpdates := optr.getAvailableUpdates()
329+
if availableUpdates == nil {
330+
return nil, nil, nil
331+
}
332+
return availableUpdates.Updates, availableUpdates.ConditionalUpdates, nil
333+
}, rtClient, cvInformer.Lister().Get)
334+
320335
return optr, nil
321336
}
322337

@@ -470,6 +485,7 @@ func (optr *Operator) Run(runContext context.Context, shutdownContext context.Co
470485
defer optr.availableUpdatesQueue.ShutDown()
471486
defer optr.upgradeableQueue.ShutDown()
472487
defer optr.configuration.Queue().ShutDown()
488+
defer optr.proposalController.Queue().ShutDown()
473489
stopCh := runContext.Done()
474490

475491
klog.Infof("Starting ClusterVersionOperator with minimum reconcile period %s", optr.minimumUpdateCheckInterval)
@@ -525,6 +541,19 @@ func (optr *Operator) Run(runContext context.Context, shutdownContext context.Co
525541
klog.Infof("The ClusterVersionOperatorConfiguration feature gate is disabled or HyperShift is detected; the configuration sync routine will not run.")
526542
}
527543

544+
if optr.shouldEnableProposalController() {
545+
resultChannelCount++
546+
go func() {
547+
defer utilruntime.HandleCrash()
548+
wait.UntilWithContext(runContext, func(runContext context.Context) {
549+
optr.worker(runContext, optr.proposalController.Queue(), optr.proposalController.Sync)
550+
}, time.Second)
551+
resultChannel <- asyncResult{name: "proposal controller"}
552+
}()
553+
} else {
554+
klog.Infof("The proposal controller is disabled.")
555+
}
556+
528557
resultChannelCount++
529558
go func() {
530559
defer utilruntime.HandleCrash()
@@ -595,6 +624,7 @@ func (optr *Operator) Run(runContext context.Context, shutdownContext context.Co
595624
optr.availableUpdatesQueue.ShutDown()
596625
optr.upgradeableQueue.ShutDown()
597626
optr.configuration.Queue().ShutDown()
627+
optr.proposalController.Queue().ShutDown()
598628
}
599629
}
600630

@@ -1191,3 +1221,10 @@ func (optr *Operator) shouldReconcileAcceptRisks() bool {
11911221
// HyperShift will be supported later if needed
11921222
return optr.enabledCVOFeatureGates.AcceptRisks() && !optr.hypershift
11931223
}
1224+
1225+
// shouldEnableProposalController returns whether the CVO should enable the proposal controller
1226+
func (optr *Operator) shouldEnableProposalController() bool {
1227+
// We do not have a specific gate for the Proposal feature and use the TechPreviewNoUpgrade instead.
1228+
// It can ensure that featuregates.ChangeStopper restarts CVO when the returns of this function flips.
1229+
return optr.requiredFeatureSet == configv1.TechPreviewNoUpgrade
1230+
}

pkg/cvo/cvo_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/davecgh/go-spew/spew"
1818
"github.com/google/go-cmp/cmp"
1919
"github.com/google/uuid"
20+
ctrlruntimefake "sigs.k8s.io/controller-runtime/pkg/client/fake"
2021

2122
appsv1 "k8s.io/api/apps/v1"
2223
corev1 "k8s.io/api/core/v1"
@@ -46,6 +47,7 @@ import (
4647
"github.com/openshift/cluster-version-operator/pkg/featuregates"
4748
"github.com/openshift/cluster-version-operator/pkg/internal"
4849
"github.com/openshift/cluster-version-operator/pkg/payload"
50+
"github.com/openshift/cluster-version-operator/pkg/proposal"
4951
)
5052

5153
var (
@@ -2758,6 +2760,11 @@ func TestOperator_availableUpdatesSync(t *testing.T) {
27582760
old := optr.availableUpdates
27592761

27602762
ctx := context.Background()
2763+
optr.proposalController = proposal.NewController(func() ([]configv1.Release, []configv1.ConditionalUpdate, error) {
2764+
return nil, nil, nil
2765+
}, ctrlruntimefake.NewClientBuilder().Build(), func(_ string) (*configv1.ClusterVersion, error) {
2766+
return &configv1.ClusterVersion{}, nil
2767+
})
27612768
err := optr.availableUpdatesSync(ctx, optr.queueKey())
27622769
if err != nil && tt.wantErr == nil {
27632770
t.Fatalf("Operator.sync() unexpected error: %v", err)

pkg/cvo/status_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func TestOperator_syncFailingStatus(t *testing.T) {
108108
Desired: configv1.Release{
109109
Version: "4.0.1",
110110
Image: "image/image:v4.0.1",
111-
URL: configv1.URL("https://example.com/v4.0.1"),
111+
URL: "https://example.com/v4.0.1",
112112
},
113113
VersionHash: "",
114114
Conditions: []configv1.ClusterOperatorStatusCondition{
@@ -149,7 +149,7 @@ func TestOperator_syncFailingStatus(t *testing.T) {
149149
Desired: configv1.Release{
150150
Version: "4.0.1",
151151
Image: "image/image:v4.0.1",
152-
URL: configv1.URL("https://example.com/v4.0.1"),
152+
URL: "https://example.com/v4.0.1",
153153
},
154154
VersionHash: "",
155155
Conditions: []configv1.ClusterOperatorStatusCondition{
@@ -1084,7 +1084,7 @@ func Test_conditionalUpdateWithRiskNamesAndRiskConditions(t *testing.T) {
10841084
desiredImage: "not-important",
10851085
availableUpdates: &availableUpdates{
10861086
RiskConditions: map[string][]metav1.Condition{
1087-
"TestAlert": []metav1.Condition{{
1087+
"TestAlert": {{
10881088
Type: "Applies",
10891089
Status: "True",
10901090
Reason: "Alert:firing",

0 commit comments

Comments
 (0)