Skip to content

Commit edc06d8

Browse files
committed
Drop hash computation of ClusterExtensionRevision phases
Applier can decide if a new `ClusterExtensionRevision` needs to be created without computing the digest all objects in phases: * try to patch the current revision * if the operation fails due to invalid payload, it is a signal that we tried to update an immutable field (phases included) * in that case, create a new revision Benefits: * No need to keep the computed digest attached to `ClusterExtensionRevision` as annotation * Revisions are created using SSA, passing the right field owner * Simpler applier logic Changes: * Unit tests updated, rephrasing their names to better reflect the use case scenario under test * Added test-operator 1.2.0 bundle to be able to assert creation of new revision in added e2e `TestClusterExtensionForceInstallNonSuccessorVersion` test * Helper function previously living in `test/e2e/cluster_extension_install_test.go` extracted into `test/helpers/helpers.go` so that it could be used in `test/experimental-e2e/experimental_e2e_test.go` as well
1 parent 029484d commit edc06d8

File tree

13 files changed

+806
-444
lines changed

13 files changed

+806
-444
lines changed

cmd/operator-controller/main.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,10 @@ type config struct {
107107
globalPullSecret string
108108
}
109109

110-
const authFilePrefix = "operator-controller-global-pull-secrets"
110+
const (
111+
authFilePrefix = "operator-controller-global-pull-secrets"
112+
fieldOwnerPrefix = "olm.operatorframework.io"
113+
)
111114

112115
// podNamespace checks whether the controller is running in a Pod vs.
113116
// being run locally by inspecting the namespace file that gets mounted
@@ -552,6 +555,7 @@ func setupBoxcutter(mgr manager.Manager, ceReconciler *controllers.ClusterExtens
552555
Scheme: mgr.GetScheme(),
553556
RevisionGenerator: rg,
554557
Preflights: preflights,
558+
FieldOwner: fmt.Sprintf("%s/clusterextension-controller", fieldOwnerPrefix),
555559
}
556560
ceReconciler.RevisionStatesGetter = &controllers.BoxcutterRevisionStatesGetter{Reader: mgr.GetClient()}
557561
ceReconciler.StorageMigrator = &applier.BoxcutterStorageMigrator{
@@ -560,11 +564,6 @@ func setupBoxcutter(mgr manager.Manager, ceReconciler *controllers.ClusterExtens
560564
RevisionGenerator: rg,
561565
}
562566

563-
// Boxcutter
564-
const (
565-
boxcutterSystemPrefixFieldOwner = "olm.operatorframework.io"
566-
)
567-
568567
discoveryClient, err := discovery.NewDiscoveryClientForConfig(mgr.GetConfig())
569568
if err != nil {
570569
return fmt.Errorf("unable to create discovery client: %w", err)
@@ -591,8 +590,8 @@ func setupBoxcutter(mgr manager.Manager, ceReconciler *controllers.ClusterExtens
591590
machinery.NewObjectEngine(
592591
mgr.GetScheme(), trackingCache, mgr.GetClient(),
593592
ownerhandling.NewNative(mgr.GetScheme()),
594-
machinery.NewComparator(ownerhandling.NewNative(mgr.GetScheme()), discoveryClient, mgr.GetScheme(), boxcutterSystemPrefixFieldOwner),
595-
boxcutterSystemPrefixFieldOwner, boxcutterSystemPrefixFieldOwner,
593+
machinery.NewComparator(ownerhandling.NewNative(mgr.GetScheme()), discoveryClient, mgr.GetScheme(), fieldOwnerPrefix),
594+
fieldOwnerPrefix, fieldOwnerPrefix,
596595
),
597596
validation.NewClusterPhaseValidator(mgr.GetRESTMapper(), mgr.GetClient()),
598597
),

internal/operator-controller/applier/boxcutter.go

Lines changed: 70 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import (
2929
"github.com/operator-framework/operator-controller/internal/operator-controller/labels"
3030
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle/source"
3131
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render"
32-
hashutil "github.com/operator-framework/operator-controller/internal/shared/util/hash"
3332
)
3433

3534
const (
@@ -202,6 +201,7 @@ type Boxcutter struct {
202201
Scheme *runtime.Scheme
203202
RevisionGenerator ClusterExtensionRevisionGenerator
204203
Preflights []Preflight
204+
FieldOwner string
205205
}
206206

207207
func (bc *Boxcutter) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (bool, string, error) {
@@ -218,84 +218,107 @@ func (bc *Boxcutter) getObjects(rev *ocv1.ClusterExtensionRevision) []client.Obj
218218
return objs
219219
}
220220

221+
func (bc *Boxcutter) ensureGVKIsSet(obj client.Object) error {
222+
if !obj.GetObjectKind().GroupVersionKind().Empty() {
223+
return nil
224+
}
225+
226+
gvk, err := apiutil.GVKForObject(obj, bc.Scheme)
227+
if err != nil {
228+
return err
229+
}
230+
231+
obj.GetObjectKind().SetGroupVersionKind(gvk)
232+
233+
return nil
234+
}
235+
221236
func (bc *Boxcutter) apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExtension, objectLabels, revisionAnnotations map[string]string) (bool, string, error) {
222237
// Generate desired revision
223238
desiredRevision, err := bc.RevisionGenerator.GenerateRevision(contentFS, ext, objectLabels, revisionAnnotations)
224239
if err != nil {
225240
return false, "", err
226241
}
227242

243+
if err := controllerutil.SetControllerReference(ext, desiredRevision, bc.Scheme); err != nil {
244+
return false, "", fmt.Errorf("set ownerref: %w", err)
245+
}
246+
228247
// List all existing revisions
229248
existingRevisions, err := bc.getExistingRevisions(ctx, ext.GetName())
230249
if err != nil {
231250
return false, "", err
232251
}
233-
desiredHash := hashutil.DeepHashObject(desiredRevision.Spec.Phases)
234252

235-
// Sort into current and previous revisions.
236-
var (
237-
currentRevision *ocv1.ClusterExtensionRevision
238-
)
253+
currentRevision := &ocv1.ClusterExtensionRevision{}
239254
state := StateNeedsInstall
240-
if len(existingRevisions) > 0 {
241-
maybeCurrentRevision := existingRevisions[len(existingRevisions)-1]
242-
annotations := maybeCurrentRevision.GetAnnotations()
243-
if annotations != nil {
244-
if revisionHash, ok := annotations[RevisionHashAnnotation]; ok && revisionHash == desiredHash {
245-
currentRevision = &maybeCurrentRevision
255+
// check if we can update the current revision.
256+
if len(existingRevisions) > 0 { // nolint:nestif
257+
// try first to update the current revision.
258+
currentRevision = &existingRevisions[len(existingRevisions)-1]
259+
desiredRevision.Spec.Previous = currentRevision.Spec.Previous
260+
desiredRevision.Spec.Revision = currentRevision.Spec.Revision
261+
desiredRevision.Name = currentRevision.Name
262+
263+
if err := bc.ensureGVKIsSet(desiredRevision); err != nil {
264+
return false, "", fmt.Errorf("setting gvk failed: %w", err)
265+
}
266+
if err = bc.Client.Patch(ctx, desiredRevision, client.Apply, client.FieldOwner(bc.FieldOwner), client.ForceOwnership); err != nil {
267+
if !apierrors.IsInvalid(err) {
268+
return false, "", fmt.Errorf("patching %s Revision: %w", desiredRevision.Name, err)
246269
}
270+
// We could not update the current revision due to trying to update an immutable field.
271+
// Therefore, we need to create a new revision.
272+
state = StateNeedsUpgrade
273+
} else {
274+
// inplace patch was successful, no changes in phases
275+
state = StateUnchanged
247276
}
248-
state = StateNeedsUpgrade
249277
}
250278

251-
// Preflights
252-
plainObjs := bc.getObjects(desiredRevision)
253-
for _, preflight := range bc.Preflights {
254-
if shouldSkipPreflight(ctx, preflight, ext, state) {
255-
continue
256-
}
257-
switch state {
258-
case StateNeedsInstall:
259-
err := preflight.Install(ctx, plainObjs)
260-
if err != nil {
261-
return false, "", err
279+
if state != StateUnchanged { // nolint:nestif
280+
// Preflights
281+
plainObjs := bc.getObjects(desiredRevision)
282+
for _, preflight := range bc.Preflights {
283+
if shouldSkipPreflight(ctx, preflight, ext, state) {
284+
continue
262285
}
263-
// TODO: jlanford's IDE says that "StateNeedsUpgrade" condition is always true, but
264-
// it isn't immediately obvious why that is. Perhaps len(existingRevisions) is
265-
// always greater than 0 (seems unlikely), or shouldSkipPreflight always returns
266-
// true (and we continue) when state == StateNeedsInstall?
267-
case StateNeedsUpgrade:
268-
err := preflight.Upgrade(ctx, plainObjs)
269-
if err != nil {
270-
return false, "", err
286+
switch state {
287+
case StateNeedsInstall:
288+
err := preflight.Install(ctx, plainObjs)
289+
if err != nil {
290+
return false, "", err
291+
}
292+
// TODO: jlanford's IDE says that "StateNeedsUpgrade" condition is always true, but
293+
// it isn't immediately obvious why that is. Perhaps len(existingRevisions) is
294+
// always greater than 0 (seems unlikely), or shouldSkipPreflight always returns
295+
// true (and we continue) when state == StateNeedsInstall?
296+
case StateNeedsUpgrade:
297+
err := preflight.Upgrade(ctx, plainObjs)
298+
if err != nil {
299+
return false, "", err
300+
}
271301
}
272302
}
273-
}
274303

275-
if currentRevision == nil {
276-
// all Revisions are outdated => create a new one.
304+
// need to create new revision
277305
prevRevisions := existingRevisions
278306
revisionNumber := latestRevisionNumber(prevRevisions) + 1
279307

280-
newRevision := desiredRevision
281-
newRevision.Name = fmt.Sprintf("%s-%d", ext.Name, revisionNumber)
282-
if newRevision.GetAnnotations() == nil {
283-
newRevision.Annotations = map[string]string{}
284-
}
285-
newRevision.Annotations[RevisionHashAnnotation] = desiredHash
286-
newRevision.Spec.Revision = revisionNumber
308+
desiredRevision.Name = fmt.Sprintf("%s-%d", ext.Name, revisionNumber)
309+
desiredRevision.Spec.Revision = revisionNumber
287310

288-
if err = bc.setPreviousRevisions(ctx, newRevision, prevRevisions); err != nil {
311+
if err = bc.setPreviousRevisions(ctx, desiredRevision, prevRevisions); err != nil {
289312
return false, "", fmt.Errorf("garbage collecting old Revisions: %w", err)
290313
}
291314

292-
if err := controllerutil.SetControllerReference(ext, newRevision, bc.Scheme); err != nil {
293-
return false, "", fmt.Errorf("set ownerref: %w", err)
315+
if err := bc.ensureGVKIsSet(desiredRevision); err != nil {
316+
return false, "", fmt.Errorf("setting gvk failed: %w", err)
294317
}
295-
if err := bc.Client.Create(ctx, newRevision); err != nil {
318+
if err := bc.Client.Patch(ctx, desiredRevision, client.Apply, client.FieldOwner(bc.FieldOwner), client.ForceOwnership); err != nil {
296319
return false, "", fmt.Errorf("creating new Revision: %w", err)
297320
}
298-
currentRevision = newRevision
321+
currentRevision = desiredRevision
299322
}
300323

301324
progressingCondition := meta.FindStatusCondition(currentRevision.Status.Conditions, ocv1.TypeProgressing)

0 commit comments

Comments
 (0)