Skip to content

Commit a57f1b8

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 687401c commit a57f1b8

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
@@ -560,6 +563,7 @@ func setupBoxcutter(
560563
Scheme: mgr.GetScheme(),
561564
RevisionGenerator: rg,
562565
Preflights: preflights,
566+
FieldOwner: fmt.Sprintf("%s/clusterextension-controller", fieldOwnerPrefix),
563567
}
564568
ceReconciler.RevisionStatesGetter = &controllers.BoxcutterRevisionStatesGetter{Reader: mgr.GetClient()}
565569
ceReconciler.StorageMigrator = &applier.BoxcutterStorageMigrator{
@@ -568,11 +572,6 @@ func setupBoxcutter(
568572
RevisionGenerator: rg,
569573
}
570574

571-
// Boxcutter
572-
const (
573-
boxcutterSystemPrefixFieldOwner = "olm.operatorframework.io"
574-
)
575-
576575
discoveryClient, err := discovery.NewDiscoveryClientForConfig(mgr.GetConfig())
577576
if err != nil {
578577
return fmt.Errorf("unable to create discovery client: %w", err)
@@ -599,8 +598,8 @@ func setupBoxcutter(
599598
machinery.NewObjectEngine(
600599
mgr.GetScheme(), trackingCache, mgr.GetClient(),
601600
ownerhandling.NewNative(mgr.GetScheme()),
602-
machinery.NewComparator(ownerhandling.NewNative(mgr.GetScheme()), discoveryClient, mgr.GetScheme(), boxcutterSystemPrefixFieldOwner),
603-
boxcutterSystemPrefixFieldOwner, boxcutterSystemPrefixFieldOwner,
601+
machinery.NewComparator(ownerhandling.NewNative(mgr.GetScheme()), discoveryClient, mgr.GetScheme(), fieldOwnerPrefix),
602+
fieldOwnerPrefix, fieldOwnerPrefix,
604603
),
605604
validation.NewClusterPhaseValidator(mgr.GetRESTMapper(), mgr.GetClient()),
606605
),

internal/operator-controller/applier/boxcutter.go

Lines changed: 70 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import (
2727
ocv1 "github.com/operator-framework/operator-controller/api/v1"
2828
"github.com/operator-framework/operator-controller/internal/operator-controller/controllers"
2929
"github.com/operator-framework/operator-controller/internal/operator-controller/labels"
30-
hashutil "github.com/operator-framework/operator-controller/internal/shared/util/hash"
3130
)
3231

3332
const (
@@ -200,6 +199,7 @@ type Boxcutter struct {
200199
Scheme *runtime.Scheme
201200
RevisionGenerator ClusterExtensionRevisionGenerator
202201
Preflights []Preflight
202+
FieldOwner string
203203
}
204204

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

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

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

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

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

273-
if currentRevision == nil {
274-
// all Revisions are outdated => create a new one.
302+
// need to create new revision
275303
prevRevisions := existingRevisions
276304
revisionNumber := latestRevisionNumber(prevRevisions) + 1
277305

278-
newRevision := desiredRevision
279-
newRevision.Name = fmt.Sprintf("%s-%d", ext.Name, revisionNumber)
280-
if newRevision.GetAnnotations() == nil {
281-
newRevision.Annotations = map[string]string{}
282-
}
283-
newRevision.Annotations[RevisionHashAnnotation] = desiredHash
284-
newRevision.Spec.Revision = revisionNumber
306+
desiredRevision.Name = fmt.Sprintf("%s-%d", ext.Name, revisionNumber)
307+
desiredRevision.Spec.Revision = revisionNumber
285308

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

290-
if err := controllerutil.SetControllerReference(ext, newRevision, bc.Scheme); err != nil {
291-
return false, "", fmt.Errorf("set ownerref: %w", err)
313+
if err := bc.ensureGVKIsSet(desiredRevision); err != nil {
314+
return false, "", fmt.Errorf("setting gvk failed: %w", err)
292315
}
293-
if err := bc.Client.Create(ctx, newRevision); err != nil {
316+
if err := bc.Client.Patch(ctx, desiredRevision, client.Apply, client.FieldOwner(bc.FieldOwner), client.ForceOwnership); err != nil {
294317
return false, "", fmt.Errorf("creating new Revision: %w", err)
295318
}
296-
currentRevision = newRevision
319+
currentRevision = desiredRevision
297320
}
298321

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

0 commit comments

Comments
 (0)