...

Source file src/sigs.k8s.io/controller-runtime/pkg/reconcile/reconcile_test.go

Documentation: sigs.k8s.io/controller-runtime/pkg/reconcile

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package reconcile_test
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	. "github.com/onsi/ginkgo/v2"
    25  	. "github.com/onsi/gomega"
    26  	corev1 "k8s.io/api/core/v1"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  	"sigs.k8s.io/controller-runtime/pkg/envtest"
    32  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    33  )
    34  
    35  type mockObjectReconciler struct {
    36  	reconcileFunc func(context.Context, *corev1.ConfigMap) (reconcile.Result, error)
    37  }
    38  
    39  func (r *mockObjectReconciler) Reconcile(ctx context.Context, cm *corev1.ConfigMap) (reconcile.Result, error) {
    40  	return r.reconcileFunc(ctx, cm)
    41  }
    42  
    43  var _ = Describe("reconcile", func() {
    44  	Describe("Result", func() {
    45  		It("IsZero should return true if empty", func() {
    46  			var res *reconcile.Result
    47  			Expect(res.IsZero()).To(BeTrue())
    48  			res2 := &reconcile.Result{}
    49  			Expect(res2.IsZero()).To(BeTrue())
    50  			res3 := reconcile.Result{}
    51  			Expect(res3.IsZero()).To(BeTrue())
    52  		})
    53  
    54  		It("IsZero should return false if Requeue is set to true", func() {
    55  			res := reconcile.Result{Requeue: true}
    56  			Expect(res.IsZero()).To(BeFalse())
    57  		})
    58  
    59  		It("IsZero should return false if RequeueAfter is set to true", func() {
    60  			res := reconcile.Result{RequeueAfter: 1 * time.Second}
    61  			Expect(res.IsZero()).To(BeFalse())
    62  		})
    63  	})
    64  
    65  	Describe("Func", func() {
    66  		It("should call the function with the request and return a nil error.", func() {
    67  			request := reconcile.Request{
    68  				NamespacedName: types.NamespacedName{Name: "foo", Namespace: "bar"},
    69  			}
    70  			result := reconcile.Result{
    71  				Requeue: true,
    72  			}
    73  
    74  			instance := reconcile.Func(func(_ context.Context, r reconcile.Request) (reconcile.Result, error) {
    75  				defer GinkgoRecover()
    76  				Expect(r).To(Equal(request))
    77  
    78  				return result, nil
    79  			})
    80  			actualResult, actualErr := instance.Reconcile(context.Background(), request)
    81  			Expect(actualResult).To(Equal(result))
    82  			Expect(actualErr).NotTo(HaveOccurred())
    83  		})
    84  
    85  		It("should call the function with the request and return an error.", func() {
    86  			request := reconcile.Request{
    87  				NamespacedName: types.NamespacedName{Name: "foo", Namespace: "bar"},
    88  			}
    89  			result := reconcile.Result{
    90  				Requeue: false,
    91  			}
    92  			err := fmt.Errorf("hello world")
    93  
    94  			instance := reconcile.Func(func(_ context.Context, r reconcile.Request) (reconcile.Result, error) {
    95  				defer GinkgoRecover()
    96  				Expect(r).To(Equal(request))
    97  
    98  				return result, err
    99  			})
   100  			actualResult, actualErr := instance.Reconcile(context.Background(), request)
   101  			Expect(actualResult).To(Equal(result))
   102  			Expect(actualErr).To(Equal(err))
   103  		})
   104  
   105  		It("should allow unwrapping inner error from terminal error", func() {
   106  			inner := apierrors.NewGone("")
   107  			terminalError := reconcile.TerminalError(inner)
   108  
   109  			Expect(apierrors.IsGone(terminalError)).To(BeTrue())
   110  		})
   111  
   112  		It("should handle nil terminal errors properly", func() {
   113  			err := reconcile.TerminalError(nil)
   114  			Expect(err.Error()).To(Equal("nil terminal error"))
   115  		})
   116  	})
   117  
   118  	Describe("AsReconciler", func() {
   119  		var testenv *envtest.Environment
   120  		var testClient client.Client
   121  
   122  		BeforeEach(func() {
   123  			testenv = &envtest.Environment{}
   124  
   125  			cfg, err := testenv.Start()
   126  			Expect(err).NotTo(HaveOccurred())
   127  
   128  			testClient, err = client.New(cfg, client.Options{})
   129  			Expect(err).NotTo(HaveOccurred())
   130  		})
   131  
   132  		AfterEach(func() {
   133  			Expect(testenv.Stop()).NotTo(HaveOccurred())
   134  		})
   135  
   136  		Context("with an existing object", func() {
   137  			var key client.ObjectKey
   138  
   139  			BeforeEach(func() {
   140  				cm := &corev1.ConfigMap{
   141  					ObjectMeta: metav1.ObjectMeta{
   142  						Namespace: "default",
   143  						Name:      "test",
   144  					},
   145  				}
   146  				key = client.ObjectKeyFromObject(cm)
   147  
   148  				err := testClient.Create(context.Background(), cm)
   149  				Expect(err).NotTo(HaveOccurred())
   150  			})
   151  
   152  			It("should Get the object and call the ObjectReconciler", func() {
   153  				var actual *corev1.ConfigMap
   154  				reconciler := reconcile.AsReconciler(testClient, &mockObjectReconciler{
   155  					reconcileFunc: func(ctx context.Context, cm *corev1.ConfigMap) (reconcile.Result, error) {
   156  						actual = cm
   157  						return reconcile.Result{}, nil
   158  					},
   159  				})
   160  
   161  				res, err := reconciler.Reconcile(context.Background(), reconcile.Request{NamespacedName: key})
   162  				Expect(err).NotTo(HaveOccurred())
   163  				Expect(res).To(BeZero())
   164  				Expect(actual).NotTo(BeNil())
   165  				Expect(actual.ObjectMeta.Name).To(Equal(key.Name))
   166  				Expect(actual.ObjectMeta.Namespace).To(Equal(key.Namespace))
   167  			})
   168  		})
   169  
   170  		Context("with an object that doesn't exist", func() {
   171  			It("should not call the ObjectReconciler", func() {
   172  				called := false
   173  				reconciler := reconcile.AsReconciler(testClient, &mockObjectReconciler{
   174  					reconcileFunc: func(ctx context.Context, cm *corev1.ConfigMap) (reconcile.Result, error) {
   175  						called = true
   176  						return reconcile.Result{}, nil
   177  					},
   178  				})
   179  
   180  				key := types.NamespacedName{Namespace: "default", Name: "fake-obj"}
   181  				res, err := reconciler.Reconcile(context.Background(), reconcile.Request{NamespacedName: key})
   182  				Expect(err).NotTo(HaveOccurred())
   183  				Expect(res).To(BeZero())
   184  				Expect(called).To(BeFalse())
   185  			})
   186  		})
   187  	})
   188  })
   189  

View as plain text