crossplane-runtime/pkg/resource/managed_reconciler_test.go

804 lines
33 KiB
Go

/*
Copyright 2019 The Crossplane 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.
*/
package resource
import (
"context"
"testing"
"sigs.k8s.io/controller-runtime/pkg/client"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/google/go-cmp/cmp"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/crossplaneio/crossplane-runtime/apis/core/v1alpha1"
"github.com/crossplaneio/crossplane-runtime/pkg/test"
)
var _ reconcile.Reconciler = &ManagedReconciler{}
func TestManagedReconciler(t *testing.T) {
type args struct {
m manager.Manager
mg ManagedKind
o []ManagedReconcilerOption
}
type want struct {
result reconcile.Result
err error
}
errBoom := errors.New("boom")
errNotReady := &referencesAccessErr{[]ReferenceStatus{{Name: "cool-res", Status: ReferenceNotReady}}}
now := metav1.Now()
cases := map[string]struct {
reason string
args args
want want
}{
"GetManagedError": {
reason: "Any error (except not found) encountered while getting the resource under reconciliation should be returned.",
args: args{
m: &MockManager{
c: &test.MockClient{MockGet: test.NewMockGetFn(errBoom)},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
},
want: want{err: errors.Wrap(errBoom, errGetManaged)},
},
"ManagedNotFound": {
reason: "Not found errors encountered while getting the resource under reconciliation should be ignored.",
args: args{
m: &MockManager{
c: &test.MockClient{MockGet: test.NewMockGetFn(kerrors.NewNotFound(schema.GroupResource{}, ""))},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
},
want: want{result: reconcile.Result{}},
},
"ExternalConnectError": {
reason: "Errors connecting to the provider should trigger a requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, got runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetConditions(v1alpha1.ReconcileError(errBoom))
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
reason := "Errors connecting to the provider should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, mg Managed) (ExternalClient, error) {
return nil, errBoom
})),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"InitializeError": {
reason: "Errors initializing the managed resource should trigger a requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetConditions(v1alpha1.ReconcileError(errBoom))
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "Errors initializing the managed resource should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithExternalConnecter(&NopConnecter{}),
WithManagedInitializers(ManagedInitializerFn(func(_ context.Context, mg Managed) error {
return errBoom
})),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"ResolveReferencesNotReadyError": {
reason: "Dependencies on unready references should trigger a requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetConditions(v1alpha1.ReferenceResolutionBlocked(errNotReady))
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "Dependencies on unready references should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithExternalConnecter(&NopConnecter{}),
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, res CanReference) error {
return errNotReady
})),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"ResolveReferencesError": {
reason: "Errors during reference resolution references should trigger a requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetConditions(v1alpha1.ReconcileError(errBoom))
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "Errors during reference resolution should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithExternalConnecter(&NopConnecter{}),
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, res CanReference) error {
return errBoom
})),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"ExternalObserveError": {
reason: "Errors observing the external resource should trigger a requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetConditions(v1alpha1.ReferenceResolutionSuccess())
want.SetConditions(v1alpha1.ReconcileError(errBoom))
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "Errors observing the managed resource should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, mg Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ Managed) (ExternalObservation, error) {
return ExternalObservation{}, errBoom
},
}
return c, nil
})),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"ExternalDeleteError": {
reason: "Errors deleting the external resource should trigger a requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj runtime.Object) error {
mg := obj.(*MockManaged)
mg.SetDeletionTimestamp(&now)
mg.SetReclaimPolicy(v1alpha1.ReclaimDelete)
return nil
}),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetDeletionTimestamp(&now)
want.SetReclaimPolicy(v1alpha1.ReclaimDelete)
want.SetConditions(v1alpha1.ReferenceResolutionSuccess())
want.SetConditions(v1alpha1.ReconcileError(errBoom))
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "An error deleting an external resource should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, _ CanReference) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, mg Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true}, nil
},
DeleteFn: func(_ context.Context, _ Managed) error {
return errBoom
},
}
return c, nil
})),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"ExternalDeleteSuccessful": {
reason: "A deleted managed resource with the 'delete' reclaim policy should delete its external resource then requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj runtime.Object) error {
mg := obj.(*MockManaged)
mg.SetDeletionTimestamp(&now)
mg.SetReclaimPolicy(v1alpha1.ReclaimDelete)
return nil
}),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetDeletionTimestamp(&now)
want.SetReclaimPolicy(v1alpha1.ReclaimDelete)
want.SetConditions(v1alpha1.ReferenceResolutionSuccess())
want.SetConditions(v1alpha1.ReconcileSuccess())
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "A deleted external resource should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, _ CanReference) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, mg Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true}, nil
},
DeleteFn: func(_ context.Context, _ Managed) error {
return nil
},
}
return c, nil
})),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"UnpublishConnectionDetailsError": {
reason: "Errors unpublishing connection details should trigger a requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj runtime.Object) error {
mg := obj.(*MockManaged)
mg.SetDeletionTimestamp(&now)
return nil
}),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetDeletionTimestamp(&now)
want.SetConditions(v1alpha1.ReferenceResolutionSuccess())
want.SetConditions(v1alpha1.ReconcileError(errBoom))
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "Errors unpublishing connection details should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, _ CanReference) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, mg Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: false}, nil
},
}
return c, nil
})),
WithManagedConnectionPublishers(ManagedConnectionPublisherFns{
UnpublishConnectionFn: func(_ context.Context, _ Managed, _ ConnectionDetails) error { return errBoom },
}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"RemoveFinalizerError": {
reason: "Errors removing the managed resource finalizer should trigger a requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj runtime.Object) error {
mg := obj.(*MockManaged)
mg.SetDeletionTimestamp(&now)
return nil
}),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetDeletionTimestamp(&now)
want.SetConditions(v1alpha1.ReferenceResolutionSuccess())
want.SetConditions(v1alpha1.ReconcileError(errBoom))
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "Errors removing the managed resource finalizer should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, _ CanReference) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, mg Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: false}, nil
},
}
return c, nil
})),
WithManagedConnectionPublishers(),
WithManagedFinalizer(ManagedFinalizerFns{RemoveFinalizerFn: func(_ context.Context, _ Managed) error { return errBoom }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"DeleteSuccessful": {
reason: "Successful managed resource deletion should not trigger a requeue or status update.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj runtime.Object) error {
mg := obj.(*MockManaged)
mg.SetDeletionTimestamp(&now)
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, _ CanReference) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, mg Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: false}, nil
},
}
return c, nil
})),
WithManagedConnectionPublishers(),
WithManagedFinalizer(ManagedFinalizerFns{RemoveFinalizerFn: func(_ context.Context, _ Managed) error { return nil }}),
},
},
want: want{result: reconcile.Result{Requeue: false}},
},
"PublishObservationConnectionDetailsError": {
reason: "Errors publishing connection details after observation should trigger a requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetConditions(v1alpha1.ReferenceResolutionSuccess())
want.SetConditions(v1alpha1.ReconcileError(errBoom))
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "Errors publishing connection details after observation should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, _ CanReference) error { return nil })),
WithExternalConnecter(&NopConnecter{}),
WithManagedConnectionPublishers(ManagedConnectionPublisherFns{
PublishConnectionFn: func(_ context.Context, _ Managed, _ ConnectionDetails) error { return errBoom },
}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"AddFinalizerError": {
reason: "Errors adding a finalizer should trigger a requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetConditions(v1alpha1.ReferenceResolutionSuccess())
want.SetConditions(v1alpha1.ReconcileError(errBoom))
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "Errors adding a finalizer should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, _ CanReference) error { return nil })),
WithExternalConnecter(&NopConnecter{}),
WithManagedConnectionPublishers(),
WithManagedFinalizer(ManagedFinalizerFns{AddFinalizerFn: func(_ context.Context, _ Managed) error { return errBoom }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"CreateExternalError": {
reason: "Errors while creating an external resource should trigger a requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetConditions(v1alpha1.ReferenceResolutionSuccess())
want.SetConditions(v1alpha1.ReconcileError(errBoom))
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "Errors while creating an external resource should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, _ CanReference) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, mg Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: false}, nil
},
CreateFn: func(_ context.Context, _ Managed) (ExternalCreation, error) {
return ExternalCreation{}, errBoom
},
}
return c, nil
})),
WithManagedConnectionPublishers(),
WithManagedFinalizer(ManagedFinalizerFns{AddFinalizerFn: func(_ context.Context, _ Managed) error { return nil }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"PublishCreationConnectionDetailsError": {
reason: "Errors publishing connection details after creation should trigger a requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetConditions(v1alpha1.ReferenceResolutionSuccess())
want.SetConditions(v1alpha1.ReconcileError(errBoom))
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "Errors publishing connection details after creation should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, _ CanReference) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, mg Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: false}, nil
},
CreateFn: func(_ context.Context, _ Managed) (ExternalCreation, error) {
cd := ConnectionDetails{"create": []byte{}}
return ExternalCreation{ConnectionDetails: cd}, nil
},
}
return c, nil
})),
WithManagedConnectionPublishers(ManagedConnectionPublisherFns{
PublishConnectionFn: func(_ context.Context, _ Managed, cd ConnectionDetails) error {
// We're called after observe, create, and update
// but we only want to fail when publishing details
// after a creation.
if _, ok := cd["create"]; ok {
return errBoom
}
return nil
},
}),
WithManagedFinalizer(ManagedFinalizerFns{AddFinalizerFn: func(_ context.Context, _ Managed) error { return nil }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"CreateSuccessful": {
reason: "Successful managed resource creation should trigger a requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetConditions(v1alpha1.ReferenceResolutionSuccess())
want.SetConditions(v1alpha1.ReconcileSuccess())
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "Successful managed resource creation should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, _ CanReference) error { return nil })),
WithExternalConnecter(&NopConnecter{}),
WithManagedConnectionPublishers(),
WithManagedFinalizer(ManagedFinalizerFns{AddFinalizerFn: func(_ context.Context, _ Managed) error { return nil }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"ExternalResourceUpToDate": {
reason: "When the external resource exists and is up to date a requeue should be triggered after a long wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetConditions(v1alpha1.ReferenceResolutionSuccess())
want.SetConditions(v1alpha1.ReconcileSuccess())
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "A successful no-op reconcile should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, _ CanReference) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, mg Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: true}, nil
},
}
return c, nil
})),
WithManagedConnectionPublishers(),
WithManagedFinalizer(ManagedFinalizerFns{AddFinalizerFn: func(_ context.Context, _ Managed) error { return nil }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedLongWait}},
},
"UpdateExternalError": {
reason: "Errors while updating an external resource should trigger a requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetConditions(v1alpha1.ReferenceResolutionSuccess())
want.SetConditions(v1alpha1.ReconcileError(errBoom))
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "Errors while updating an external resource should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, _ CanReference) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, mg Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: false}, nil
},
UpdateFn: func(_ context.Context, _ Managed) (ExternalUpdate, error) {
return ExternalUpdate{}, errBoom
},
}
return c, nil
})),
WithManagedConnectionPublishers(),
WithManagedFinalizer(ManagedFinalizerFns{AddFinalizerFn: func(_ context.Context, _ Managed) error { return nil }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"PublishUpdateConnectionDetailsError": {
reason: "Errors publishing connection details after an update should trigger a requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetConditions(v1alpha1.ReferenceResolutionSuccess())
want.SetConditions(v1alpha1.ReconcileError(errBoom))
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "Errors publishing connection details after an update should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, _ CanReference) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, mg Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: false}, nil
},
UpdateFn: func(_ context.Context, _ Managed) (ExternalUpdate, error) {
cd := ConnectionDetails{"update": []byte{}}
return ExternalUpdate{ConnectionDetails: cd}, nil
},
}
return c, nil
})),
WithManagedConnectionPublishers(ManagedConnectionPublisherFns{
PublishConnectionFn: func(_ context.Context, _ Managed, cd ConnectionDetails) error {
// We're called after observe, create, and update
// but we only want to fail when publishing details
// after an update.
if _, ok := cd["update"]; ok {
return errBoom
}
return nil
},
}),
WithManagedFinalizer(ManagedFinalizerFns{AddFinalizerFn: func(_ context.Context, _ Managed) error { return nil }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"UpdateSuccessful": {
reason: "A successful managed resource update should trigger a requeue after a long wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetConditions(v1alpha1.ReferenceResolutionSuccess())
want.SetConditions(v1alpha1.ReconcileSuccess())
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "A successful managed resource update should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, _ CanReference) error { return nil })),
WithExternalConnecter(ExternalConnectorFn(func(_ context.Context, mg Managed) (ExternalClient, error) {
c := &ExternalClientFns{
ObserveFn: func(_ context.Context, _ Managed) (ExternalObservation, error) {
return ExternalObservation{ResourceExists: true, ResourceUpToDate: false}, nil
},
UpdateFn: func(_ context.Context, _ Managed) (ExternalUpdate, error) {
return ExternalUpdate{}, nil
},
}
return c, nil
})),
WithManagedConnectionPublishers(),
WithManagedFinalizer(ManagedFinalizerFns{AddFinalizerFn: func(_ context.Context, _ Managed) error { return nil }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedLongWait}},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
r := NewManagedReconciler(tc.args.m, tc.args.mg, tc.args.o...)
got, err := r.Reconcile(reconcile.Request{})
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("\nReason: %s\nr.Reconcile(...): -want error, +got error:\n%s", tc.reason, diff)
}
if diff := cmp.Diff(tc.want.result, got); diff != "" {
t.Errorf("\nReason: %s\nr.Reconcile(...): -want, +got:\n%s", tc.reason, diff)
}
})
}
}