diff --git a/controller/internal/reconciler/bucketclaim.go b/controller/internal/reconciler/bucketclaim.go index f4396cf8..dc4adb1b 100644 --- a/controller/internal/reconciler/bucketclaim.go +++ b/controller/internal/reconciler/bucketclaim.go @@ -18,6 +18,7 @@ package reconciler import ( "context" + "errors" "fmt" "time" @@ -33,6 +34,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" cosiapi "sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2" + cosierr "sigs.k8s.io/container-object-storage-interface/internal/errors" cosipredicate "sigs.k8s.io/container-object-storage-interface/internal/predicate" ) @@ -63,18 +65,18 @@ func (r *BucketClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, err } - retryError, err := r.reconcile(ctx, logger, claim) + err := r.reconcile(ctx, logger, claim) if err != nil { // Record any error as a timestamped error in the status. claim.Status.Error = cosiapi.NewTimestampedError(time.Now(), err.Error()) if updErr := r.Status().Update(ctx, claim); updErr != nil { logger.Error(err, "failed to update BucketClaim status after reconcile error", "updateError", updErr) // If status update fails, we must retry the error regardless of the reconcile return. - // The reconcile needs to run again to make sure the status is eventually be updated. + // The reconcile needs to run again to make sure the status is eventually updated. return reconcile.Result{}, err } - if !retryError { + if errors.Is(err, cosierr.NonRetryableError(nil)) { return reconcile.Result{}, reconcile.TerminalError(err) } return reconcile.Result{}, err @@ -112,31 +114,20 @@ func (r *BucketClaimReconciler) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } -// Type definition enforces readability. true/false in code is hard to read/review/maintain. -type retryErrorType bool - -const ( - RetryError retryErrorType = true // DO retry the error. - DoNotRetryError retryErrorType = false // Do NOT retry the error. - NoError retryErrorType = false // No error, don't retry. For readability. -) - -func (r *BucketClaimReconciler) reconcile( - ctx context.Context, logger logr.Logger, claim *cosiapi.BucketClaim, -) (retryErrorType, error) { +func (r *BucketClaimReconciler) reconcile(ctx context.Context, logger logr.Logger, claim *cosiapi.BucketClaim) error { bucketName, err := determineBucketName(claim) if err != nil { // Opinion: It is best to not apply a missing finalizer when boundBucketName is degraded // (err returned here). When degraded, the user needs to delete and re-create the // BucketClaim to fix the degradation, which requires the finalizer be absent. logger.Error(err, "failed to determine Bucket name for claim") - return DoNotRetryError, err + return cosierr.NonRetryableError(err) } logger = logger.WithValues("bucketName", bucketName) if claim.Spec.ExistingBucketName != "" { - return DoNotRetryError, fmt.Errorf("static provisioning is not yet supported") // TODO + return cosierr.NonRetryableError(fmt.Errorf("static provisioning is not yet supported")) // TODO } if !claim.GetDeletionTimestamp().IsZero() { @@ -147,10 +138,10 @@ func (r *BucketClaimReconciler) reconcile( ctrlutil.RemoveFinalizer(claim, cosiapi.ProtectionFinalizer) if err := r.Update(ctx, claim); err != nil { logger.Error(err, "failed to remove finalizer") - return RetryError, fmt.Errorf("failed to remove finalizer: %w", err) + return fmt.Errorf("failed to remove finalizer: %w", err) } - return DoNotRetryError, fmt.Errorf("deletion is not yet implemented") // TODO + return cosierr.NonRetryableError(fmt.Errorf("deletion is not yet implemented")) // TODO } logger.V(1).Info("reconciling BucketClaim") @@ -159,7 +150,7 @@ func (r *BucketClaimReconciler) reconcile( if didAdd { if err := r.Update(ctx, claim); err != nil { logger.Error(err, "failed to add protection finalizer") - return RetryError, fmt.Errorf("failed to add protection finalizer: %w", err) + return fmt.Errorf("failed to add protection finalizer: %w", err) } } @@ -168,7 +159,7 @@ func (r *BucketClaimReconciler) reconcile( claim.Status.BoundBucketName = bucketName if err := r.Status().Update(ctx, claim); err != nil { logger.Error(err, "failed to bind BucketClaim to Bucket") - return RetryError, fmt.Errorf("failed to bind BucketClaim to Bucket: %w", err) + return fmt.Errorf("failed to bind BucketClaim to Bucket: %w", err) } } @@ -180,14 +171,14 @@ func (r *BucketClaimReconciler) reconcile( if err := r.Get(ctx, bucketNsName, bucket); err != nil { if !kerrors.IsNotFound(err) { logger.Error(err, "failed to determine if Bucket exists") - return RetryError, err + return err } // TODO: static provisioning: don't do this logger.Info("creating intermediate Bucket") - _, retryErr, err := createIntermediateBucket(ctx, logger, r.Client, claim, bucketName) + _, err := createIntermediateBucket(ctx, logger, r.Client, claim, bucketName) if err != nil { - return retryErr, err + return err } } @@ -205,7 +196,7 @@ func (r *BucketClaimReconciler) reconcile( // 1. Supported protocols // 2. `ReadyToUse` - return NoError, nil + return nil } // Determine the bucket name that should go with the claim. No errors can be retried. @@ -244,11 +235,11 @@ func createIntermediateBucket( client client.Client, claim *cosiapi.BucketClaim, bucketName string, -) (*cosiapi.Bucket, retryErrorType, error) { +) (*cosiapi.Bucket, error) { className := claim.Spec.BucketClassName if className == "" { logger.Error(nil, "BucketClaim cannot have empty bucketClassName") - return nil, DoNotRetryError, fmt.Errorf("BucketClaim cannot have empty bucketClassName") + return nil, cosierr.NonRetryableError(fmt.Errorf("BucketClaim cannot have empty bucketClassName")) } logger = logger.WithValues("bucketClassName", className) @@ -265,10 +256,10 @@ func createIntermediateBucket( // BucketClass reconciler that enqueues requests for BucketClaims that reference the // class and don't yet have a bound Bucket. logger.Error(err, "BucketClass not found") - return nil, RetryError, err + return nil, err } logger.Error(err, "failed to get BucketClass") - return nil, RetryError, err + return nil, err } logger.V(1).Info("using BucketClass for intermediate Bucket") @@ -279,13 +270,13 @@ func createIntermediateBucket( if kerrors.IsAlreadyExists(err) { // Unlikely race condition. Error to allow the next reconcile to attempt to recover. logger.Error(err, "intermediate Bucket already exists") - return nil, RetryError, err + return nil, err } logger.Error(err, "failed to create intermediate Bucket") - return nil, RetryError, err + return nil, err } - return bucket, NoError, nil + return bucket, nil } func generateIntermediateBucket( diff --git a/controller/internal/reconciler/bucketclaim_test.go b/controller/internal/reconciler/bucketclaim_test.go index dfb78923..777017fe 100644 --- a/controller/internal/reconciler/bucketclaim_test.go +++ b/controller/internal/reconciler/bucketclaim_test.go @@ -33,6 +33,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" cosiapi "sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2" + cosierr "sigs.k8s.io/container-object-storage-interface/internal/errors" ) func Test_determineBucketName(t *testing.T) { @@ -162,7 +163,7 @@ func Test_createIntermediateBucket(t *testing.T) { claim := baseClaim.DeepCopy() client := newClient(baseClass.DeepCopy()) - bucket, _, err := createIntermediateBucket(ctx, nolog, client, claim, "bc-qwerty") + bucket, err := createIntermediateBucket(ctx, nolog, client, claim, "bc-qwerty") assert.NoError(t, err) assert.Empty(t, bucket.Finalizers) // NO finalizers pre-applied @@ -185,10 +186,10 @@ func Test_createIntermediateBucket(t *testing.T) { claim := baseClaim.DeepCopy() client := newClient() // no bucketclass exists - bucket, retryErr, err := createIntermediateBucket(ctx, nolog, client, claim, "bc-qwerty") + bucket, err := createIntermediateBucket(ctx, nolog, client, claim, "bc-qwerty") assert.Error(t, err) assert.ErrorContains(t, err, "s3-class") // the class name - assert.Equal(t, RetryError, retryErr) + assert.NotErrorIs(t, err, cosierr.NonRetryableError(nil)) assert.Nil(t, bucket) }) @@ -197,9 +198,9 @@ func Test_createIntermediateBucket(t *testing.T) { claim.Spec.BucketClassName = "" client := newClient(baseClass.DeepCopy()) - bucket, retryErr, err := createIntermediateBucket(ctx, nolog, client, claim, "bc-qwerty") + bucket, err := createIntermediateBucket(ctx, nolog, client, claim, "bc-qwerty") assert.Error(t, err) - assert.Equal(t, DoNotRetryError, retryErr) + assert.ErrorIs(t, err, cosierr.NonRetryableError(nil)) assert.Nil(t, bucket) }) @@ -213,9 +214,9 @@ func Test_createIntermediateBucket(t *testing.T) { } client := newClient(baseClass.DeepCopy(), raceBucket) - bucket, retryErr, err := createIntermediateBucket(ctx, nolog, client, claim, "bc-qwerty") + bucket, err := createIntermediateBucket(ctx, nolog, client, claim, "bc-qwerty") assert.Error(t, err) - assert.Equal(t, RetryError, retryErr) + assert.NotErrorIs(t, err, cosierr.NonRetryableError(nil)) assert.Nil(t, bucket) }) } diff --git a/internal/errors/errors.go b/internal/errors/errors.go new file mode 100644 index 00000000..0042816f --- /dev/null +++ b/internal/errors/errors.go @@ -0,0 +1,43 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// COSI internal error types. +package errors + +import "errors" + +// NonRetryableError is an error that should not be retried but still treated as a reconcile error. +func NonRetryableError(wrapped error) error { + return &nonRetryableError{err: wrapped} +} + +type nonRetryableError struct { + err error +} + +func (te *nonRetryableError) Error() string { + return te.err.Error() +} + +// This function will return nil if te.err is nil. +func (te *nonRetryableError) Unwrap() error { + return te.err +} + +func (te *nonRetryableError) Is(target error) bool { + tp := &nonRetryableError{} + return errors.As(target, &tp) +} diff --git a/sidecar/internal/reconciler/bucket.go b/sidecar/internal/reconciler/bucket.go index 746f41d3..a75c102f 100644 --- a/sidecar/internal/reconciler/bucket.go +++ b/sidecar/internal/reconciler/bucket.go @@ -18,6 +18,7 @@ package reconciler import ( "context" + "errors" "fmt" "time" @@ -32,6 +33,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" cosiapi "sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2" + cosierr "sigs.k8s.io/container-object-storage-interface/internal/errors" cosipredicate "sigs.k8s.io/container-object-storage-interface/internal/predicate" "sigs.k8s.io/container-object-storage-interface/internal/protocol" cosiproto "sigs.k8s.io/container-object-storage-interface/proto" @@ -64,18 +66,18 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr return ctrl.Result{}, err } - retryError, err := r.reconcile(ctx, logger, bucket) + err := r.reconcile(ctx, logger, bucket) if err != nil { // Record any error as a timestamped error in the status. bucket.Status.Error = cosiapi.NewTimestampedError(time.Now(), err.Error()) if updErr := r.Status().Update(ctx, bucket); updErr != nil { logger.Error(err, "failed to update Bucket status after reconcile error", "updateError", updErr) // If status update fails, we must retry the error regardless of the reconcile return. - // The reconcile needs to run again to make sure the status is eventually be updated. + // The reconcile needs to run again to make sure the status is eventually updated. return reconcile.Result{}, err } - if !retryError { + if errors.Is(err, cosierr.NonRetryableError(nil)) { return reconcile.Result{}, reconcile.TerminalError(err) } return reconcile.Result{}, err @@ -116,23 +118,12 @@ func (r *BucketReconciler) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } -// Type definition enforces readability. true/false in code is hard to read/review/maintain. -type retryErrorType bool - -const ( - RetryError retryErrorType = true // DO retry the error. - DoNotRetryError retryErrorType = false // Do NOT retry the error. - NoError retryErrorType = false // No error, don't retry. For readability. -) - -func (r *BucketReconciler) reconcile( - ctx context.Context, logger logr.Logger, bucket *cosiapi.Bucket, -) (retryErrorType, error) { +func (r *BucketReconciler) reconcile(ctx context.Context, logger logr.Logger, bucket *cosiapi.Bucket) error { if bucket.Spec.DriverName != r.DriverInfo.name { // TODO: configure the predicate to ignore any reconcile with non-matching driver // keep this log to help debug any issues that might arise with predicate logic logger.Info("not reconciling bucket with non-matching driver name %q", bucket.Spec.DriverName) - return NoError, nil + return nil } if !bucket.GetDeletionTimestamp().IsZero() { @@ -143,28 +134,28 @@ func (r *BucketReconciler) reconcile( ctrlutil.RemoveFinalizer(bucket, cosiapi.ProtectionFinalizer) if err := r.Update(ctx, bucket); err != nil { logger.Error(err, "failed to remove finalizer") - return RetryError, fmt.Errorf("failed to remove finalizer: %w", err) + return fmt.Errorf("failed to remove finalizer: %w", err) } - return DoNotRetryError, fmt.Errorf("deletion is not yet implemented") // TODO + return cosierr.NonRetryableError(fmt.Errorf("deletion is not yet implemented")) // TODO } requiredProtos, err := objectProtocolListFromApiList(bucket.Spec.Protocols) if err != nil { logger.Error(err, "failed to parse protocol list") - return DoNotRetryError, err + return cosierr.NonRetryableError(err) } if err := validateDriverSupportsProtocols(r.DriverInfo, requiredProtos); err != nil { logger.Error(err, "protocol(s) are unsupported") - return DoNotRetryError, err + return cosierr.NonRetryableError(err) } isStaticProvisioning := false if bucket.Spec.ExistingBucketID != "" { // isStaticProvisioning = true // logger = logger.WithValues("provisioningStrategy", "static") - return DoNotRetryError, fmt.Errorf("static provisioning is not yet supported") // TODO + return cosierr.NonRetryableError(fmt.Errorf("static provisioning is not yet supported")) // TODO } else { logger = logger.WithValues("provisioningStrategy", "dynamic") } @@ -175,16 +166,15 @@ func (r *BucketReconciler) reconcile( if didAdd { if err := r.Update(ctx, bucket); err != nil { logger.Error(err, "failed to add protection finalizer") - return RetryError, fmt.Errorf("failed to add protection finalizer: %w", err) + return fmt.Errorf("failed to add protection finalizer: %w", err) } } var provisionedBucket *provisionedBucketDetails - var retryErr retryErrorType if isStaticProvisioning { logger.Error(err, "how did we get here?") // TODO: static } else { - provisionedBucket, retryErr, err = r.dynamicProvision(ctx, logger, dynamicProvisionParams{ + provisionedBucket, err = r.dynamicProvision(ctx, logger, dynamicProvisionParams{ bucketName: bucket.Name, requiredProtos: requiredProtos, parameters: bucket.Spec.Parameters, @@ -192,19 +182,19 @@ func (r *BucketReconciler) reconcile( }) } if err != nil { - return retryErr, err + return err } // final validation and status updates are the same for dynamic and static provisioning if len(provisionedBucket.supportedProtos) == 0 { logger.Error(nil, "created bucket supports no protocols") - return DoNotRetryError, fmt.Errorf("created bucket supports no protocols") + return cosierr.NonRetryableError(fmt.Errorf("created bucket supports no protocols")) } if err := validateBucketSupportsProtocols(provisionedBucket.supportedProtos, bucket.Spec.Protocols); err != nil { logger.Error(err, "bucket required protocols missing") - return DoNotRetryError, fmt.Errorf("bucket required protocols missing: %w", err) + return cosierr.NonRetryableError(fmt.Errorf("bucket required protocols missing: %w", err)) } bucket.Status = cosiapi.BucketStatus{ @@ -216,10 +206,10 @@ func (r *BucketReconciler) reconcile( } if err := r.Status().Update(ctx, bucket); err != nil { logger.Error(err, "failed to update Bucket status after successful bucket creation") - return RetryError, fmt.Errorf("failed to update Bucket status after successful bucket creation: %w", err) + return fmt.Errorf("failed to update Bucket status after successful bucket creation: %w", err) } - return NoError, nil + return nil } // Details about provisioned bucket for both dynamic and static provisioning. @@ -248,14 +238,14 @@ func (r *BucketReconciler) dynamicProvision( dynamic dynamicProvisionParams, ) ( details *provisionedBucketDetails, - retry retryErrorType, err error, ) { cr := dynamic.claimRef if cr.Name == "" || cr.Namespace == "" || cr.UID == "" { // likely a malformed bucket intended for static provisioning (possible COSI controller bug) logger.Error(nil, "all bucketClaimRef fields must be set for dynamic provisioning", "bucketClaimRef", cr) - return nil, DoNotRetryError, fmt.Errorf("all bucketClaimRef fields must be set for dynamic provisioning: %#v", cr) + return nil, cosierr.NonRetryableError( + fmt.Errorf("all bucketClaimRef fields must be set for dynamic provisioning: %#v", cr)) } resp, err := r.DriverInfo.provisionerClient.DriverCreateBucket(ctx, @@ -268,21 +258,21 @@ func (r *BucketReconciler) dynamicProvision( if err != nil { logger.Error(err, "DriverCreateBucketRequest error") if rpcErrorIsRetryable(status.Code(err)) { - return nil, RetryError, err + return nil, err } - return nil, DoNotRetryError, err + return nil, cosierr.NonRetryableError(err) } if resp.BucketId == "" { logger.Error(nil, "created bucket ID missing") // driver behavior is unlikely to change if the request is retried - return nil, DoNotRetryError, fmt.Errorf("created bucket ID missing") + return nil, cosierr.NonRetryableError(fmt.Errorf("created bucket ID missing")) } protoResp := resp.Protocols if protoResp == nil { logger.Error(nil, "created bucket protocol response missing") - return nil, DoNotRetryError, fmt.Errorf("created bucket protocol response missing") + return nil, cosierr.NonRetryableError(fmt.Errorf("created bucket protocol response missing")) } supportedProtos, allBucketInfo := parseProtocolBucketInfo(protoResp) @@ -292,7 +282,7 @@ func (r *BucketReconciler) dynamicProvision( supportedProtos: supportedProtos, allProtoBucketInfo: allBucketInfo, } - return details, NoError, nil + return details, nil } // Parse driver's per-protocol bucket info into raw user-facing info. Input must be non-nil. diff --git a/sidecar/internal/reconciler/bucket_test.go b/sidecar/internal/reconciler/bucket_test.go index 08b3462e..0449b708 100644 --- a/sidecar/internal/reconciler/bucket_test.go +++ b/sidecar/internal/reconciler/bucket_test.go @@ -36,6 +36,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" cosiapi "sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2" + cosierr "sigs.k8s.io/container-object-storage-interface/internal/errors" cosiproto "sigs.k8s.io/container-object-storage-interface/proto" "sigs.k8s.io/container-object-storage-interface/sidecar/internal/test" ) @@ -537,7 +538,7 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { "option": "setting", } - details, retryErr, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ + details, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ bucketName: "bc-qwerty", requiredProtos: []*cosiproto.ObjectProtocol{ {Type: cosiproto.ObjectProtocol_S3}, @@ -546,7 +547,6 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { claimRef: validClaimRef, }) assert.NoError(t, err) - assert.False(t, bool(retryErr)) assert.Equal(t, "bc-qwerty", details.bucketId) assert.Equal(t, []cosiapi.ObjectProtocol{cosiapi.ObjectProtocolS3}, details.supportedProtos) // If we check the exact results of details.allProtoBucketInfo, we will tie the unit tests @@ -588,7 +588,7 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { }, } - details, retryErr, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ + details, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ bucketName: "bc-qwerty", requiredProtos: []*cosiproto.ObjectProtocol{ {Type: cosiproto.ObjectProtocol_S3}, @@ -598,7 +598,7 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { }) assert.Error(t, err) assert.ErrorContains(t, err, "fake unknown err") - assert.True(t, bool(retryErr)) + assert.NotErrorIs(t, err, cosierr.NonRetryableError(nil)) assert.Nil(t, details) }) @@ -626,7 +626,7 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { }, } - details, retryErr, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ + details, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ bucketName: "bc-qwerty", requiredProtos: []*cosiproto.ObjectProtocol{ {Type: cosiproto.ObjectProtocol_S3}, @@ -636,7 +636,7 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { }) assert.Error(t, err) assert.ErrorContains(t, err, "fake invalid arg err") - assert.False(t, bool(retryErr)) + assert.ErrorIs(t, err, cosierr.NonRetryableError(nil)) assert.Nil(t, details) }) @@ -674,7 +674,7 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { }, } - details, retryErr, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ + details, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ bucketName: "bc-qwerty", requiredProtos: []*cosiproto.ObjectProtocol{ {Type: cosiproto.ObjectProtocol_S3}, @@ -684,7 +684,7 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { }) assert.Error(t, err) assert.ErrorContains(t, err, "all bucketClaimRef fields must be set") - assert.False(t, bool(retryErr)) + assert.ErrorIs(t, err, cosierr.NonRetryableError(nil)) assert.Nil(t, details) }) @@ -722,7 +722,7 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { }, } - details, retryErr, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ + details, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ bucketName: "bc-qwerty", requiredProtos: []*cosiproto.ObjectProtocol{ {Type: cosiproto.ObjectProtocol_S3}, @@ -732,7 +732,7 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { }) assert.Error(t, err) assert.ErrorContains(t, err, "bucket ID missing") - assert.False(t, bool(retryErr)) + assert.ErrorIs(t, err, cosierr.NonRetryableError(nil)) assert.Nil(t, details) }) @@ -763,7 +763,7 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { }, } - details, retryErr, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ + details, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ bucketName: "bc-qwerty", requiredProtos: []*cosiproto.ObjectProtocol{ {Type: cosiproto.ObjectProtocol_S3}, @@ -773,7 +773,7 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { }) assert.Error(t, err) assert.ErrorContains(t, err, "protocol response missing") - assert.False(t, bool(retryErr)) + assert.ErrorIs(t, err, cosierr.NonRetryableError(nil)) assert.Nil(t, details) }) @@ -806,7 +806,7 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { }, } - details, retryErr, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ + details, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ bucketName: "bc-qwerty", requiredProtos: []*cosiproto.ObjectProtocol{ {Type: cosiproto.ObjectProtocol_S3}, @@ -815,7 +815,6 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { claimRef: validClaimRef, }) assert.NoError(t, err) - assert.False(t, bool(retryErr)) assert.Equal(t, "bc-qwerty", details.bucketId) assert.Equal(t, []cosiapi.ObjectProtocol{cosiapi.ObjectProtocolS3}, details.supportedProtos) assert.NotEmpty(t, details.allProtoBucketInfo) // bucket info should be present @@ -854,7 +853,7 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { }, } - details, retryErr, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ + details, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ bucketName: "bc-qwerty", requiredProtos: []*cosiproto.ObjectProtocol{ {Type: cosiproto.ObjectProtocol_AZURE}, @@ -863,7 +862,6 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { claimRef: validClaimRef, }) assert.NoError(t, err) - assert.False(t, bool(retryErr)) assert.Equal(t, "bc-qwerty", details.bucketId) assert.Equal(t, []cosiapi.ObjectProtocol{cosiapi.ObjectProtocolAzure}, details.supportedProtos) assert.NotEmpty(t, details.allProtoBucketInfo) // bucket info should be present @@ -902,7 +900,7 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { }, } - details, retryErr, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ + details, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ bucketName: "bc-qwerty", requiredProtos: []*cosiproto.ObjectProtocol{ {Type: cosiproto.ObjectProtocol_GCS}, @@ -911,7 +909,6 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { claimRef: validClaimRef, }) assert.NoError(t, err) - assert.False(t, bool(retryErr)) assert.Equal(t, "bc-qwerty", details.bucketId) assert.Equal(t, []cosiapi.ObjectProtocol{cosiapi.ObjectProtocolGcs}, details.supportedProtos) assert.NotEmpty(t, details.allProtoBucketInfo) // bucket info should be present @@ -954,7 +951,7 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { }, } - details, retryErr, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ + details, err := r.dynamicProvision(context.Background(), logr.Discard(), dynamicProvisionParams{ bucketName: "bc-qwerty", requiredProtos: []*cosiproto.ObjectProtocol{ {Type: cosiproto.ObjectProtocol_S3}, // example of request for S3, returned support for S3+Azure @@ -963,7 +960,6 @@ func TestBucketReconciler_dynamicProvision(t *testing.T) { claimRef: validClaimRef, }) assert.NoError(t, err) - assert.False(t, bool(retryErr)) assert.Equal(t, "bc-qwerty", details.bucketId) assert.ElementsMatch(t, []cosiapi.ObjectProtocol{