github.com/IBM-Blockchain/fabric-operator@v1.0.4/controllers/ibpca/ibpca_controller_test.go (about)

     1  /*
     2   * Copyright contributors to the Hyperledger Fabric Operator project
     3   *
     4   * SPDX-License-Identifier: Apache-2.0
     5   *
     6   * Licensed under the Apache License, Version 2.0 (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at:
     9   *
    10   * 	  http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing, software
    13   * distributed under the License is distributed on an "AS IS" BASIS,
    14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15   * See the License for the specific language governing permissions and
    16   * limitations under the License.
    17   */
    18  
    19  package ibpca
    20  
    21  import (
    22  	"context"
    23  	"errors"
    24  	"fmt"
    25  	"sync"
    26  
    27  	current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1"
    28  	v1 "github.com/IBM-Blockchain/fabric-operator/pkg/apis/ca/v1"
    29  	"github.com/IBM-Blockchain/fabric-operator/pkg/offering/common"
    30  	"github.com/IBM-Blockchain/fabric-operator/pkg/util"
    31  
    32  	. "github.com/onsi/ginkgo/v2"
    33  	. "github.com/onsi/gomega"
    34  
    35  	camocks "github.com/IBM-Blockchain/fabric-operator/controllers/ibpca/mocks"
    36  	"github.com/IBM-Blockchain/fabric-operator/controllers/mocks"
    37  	"github.com/IBM-Blockchain/fabric-operator/pkg/operatorerrors"
    38  	corev1 "k8s.io/api/core/v1"
    39  	k8serror "k8s.io/apimachinery/pkg/api/errors"
    40  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    41  	"k8s.io/apimachinery/pkg/runtime"
    42  	"k8s.io/apimachinery/pkg/types"
    43  	"sigs.k8s.io/controller-runtime/pkg/client"
    44  	"sigs.k8s.io/controller-runtime/pkg/event"
    45  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    46  )
    47  
    48  var _ = Describe("ReconcileIBPCA", func() {
    49  	var (
    50  		reconciler      *ReconcileIBPCA
    51  		request         reconcile.Request
    52  		mockKubeClient  *mocks.Client
    53  		mockCAReconcile *camocks.CAReconcile
    54  		instance        *current.IBPCA
    55  	)
    56  
    57  	BeforeEach(func() {
    58  		mockKubeClient = &mocks.Client{}
    59  		mockCAReconcile = &camocks.CAReconcile{}
    60  		instance = &current.IBPCA{
    61  			Spec: current.IBPCASpec{},
    62  		}
    63  		instance.Name = "test-ca"
    64  
    65  		mockKubeClient.GetStub = func(ctx context.Context, types types.NamespacedName, obj client.Object) error {
    66  			switch obj.(type) {
    67  			case *current.IBPCA:
    68  				o := obj.(*current.IBPCA)
    69  				o.Kind = "IBPCA"
    70  				o.Name = instance.Name
    71  
    72  				instance.Status = o.Status
    73  			}
    74  			return nil
    75  		}
    76  
    77  		mockKubeClient.UpdateStatusStub = func(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
    78  			switch obj.(type) {
    79  			case *current.IBPCA:
    80  				o := obj.(*current.IBPCA)
    81  				instance.Status = o.Status
    82  			}
    83  			return nil
    84  		}
    85  
    86  		mockKubeClient.ListStub = func(ctx context.Context, obj client.ObjectList, opts ...client.ListOption) error {
    87  			switch obj.(type) {
    88  			case *corev1.NodeList:
    89  				nodeList := obj.(*corev1.NodeList)
    90  				node := corev1.Node{}
    91  				node.Labels = map[string]string{}
    92  				node.Labels["topology.kubernetes.io/zone"] = "dal"
    93  				node.Labels["topology.kubernetes.io/region"] = "us-south"
    94  				nodeList.Items = append(nodeList.Items, node)
    95  			case *current.IBPCAList:
    96  				caList := obj.(*current.IBPCAList)
    97  				ca1 := current.IBPCA{}
    98  				ca1.Name = "test-ca1"
    99  				ca2 := current.IBPCA{}
   100  				ca2.Name = "test-ca2"
   101  				ca3 := current.IBPCA{}
   102  				ca3.Name = "test-ca2"
   103  				caList.Items = []current.IBPCA{ca1, ca2, ca3}
   104  			case *current.IBPPeerList:
   105  				caList := obj.(*current.IBPPeerList)
   106  				p1 := current.IBPPeer{}
   107  				p1.Name = "test-peer"
   108  				caList.Items = []current.IBPPeer{p1}
   109  			}
   110  			return nil
   111  		}
   112  
   113  		reconciler = &ReconcileIBPCA{
   114  			Offering: mockCAReconcile,
   115  			client:   mockKubeClient,
   116  			scheme:   &runtime.Scheme{},
   117  			update:   map[string][]Update{},
   118  			mutex:    &sync.Mutex{},
   119  		}
   120  		request = reconcile.Request{
   121  			NamespacedName: types.NamespacedName{
   122  				Namespace: "test-namespace",
   123  				Name:      "test",
   124  			},
   125  		}
   126  	})
   127  
   128  	Context("Reconciles", func() {
   129  		It("does not return an error if the custom resource is 'not found'", func() {
   130  			notFoundErr := &k8serror.StatusError{
   131  				ErrStatus: metav1.Status{
   132  					Reason: metav1.StatusReasonNotFound,
   133  				},
   134  			}
   135  			mockKubeClient.GetReturns(notFoundErr)
   136  			_, err := reconciler.Reconcile(context.TODO(), request)
   137  			Expect(err).NotTo(HaveOccurred())
   138  		})
   139  
   140  		It("returns an error if the request to get custom resource return any other error besides 'not found'", func() {
   141  			alreadyExistsErr := &k8serror.StatusError{
   142  				ErrStatus: metav1.Status{
   143  					Message: "already exists",
   144  					Reason:  metav1.StatusReasonAlreadyExists,
   145  				},
   146  			}
   147  			mockKubeClient.GetReturns(alreadyExistsErr)
   148  			_, err := reconciler.Reconcile(context.TODO(), request)
   149  			Expect(err).To(HaveOccurred())
   150  			Expect(err.Error()).To(Equal("already exists"))
   151  		})
   152  
   153  		It("returns an error if it encountered a non-breaking error", func() {
   154  			errMsg := "failed to reconcile deployment encountered breaking error"
   155  			mockCAReconcile.ReconcileReturns(common.Result{}, errors.New(errMsg))
   156  			_, err := reconciler.Reconcile(context.TODO(), request)
   157  			Expect(err).To(HaveOccurred())
   158  			Expect(err.Error()).To(Equal(fmt.Sprintf("CA instance '%s' encountered error: %s", instance.Name, errMsg)))
   159  		})
   160  
   161  		It("does not return an error if it encountered a breaking error", func() {
   162  			mockCAReconcile.ReconcileReturns(common.Result{}, operatorerrors.New(operatorerrors.InvalidDeploymentCreateRequest, "failed to reconcile deployment encountered breaking error"))
   163  			_, err := reconciler.Reconcile(context.TODO(), request)
   164  			Expect(err).NotTo(HaveOccurred())
   165  		})
   166  	})
   167  
   168  	Context("update reconcile", func() {
   169  		var (
   170  			oldCA *current.IBPCA
   171  			newCA *current.IBPCA
   172  			e     event.UpdateEvent
   173  		)
   174  
   175  		BeforeEach(func() {
   176  			caConfig := &v1.ServerConfig{
   177  				CAConfig: v1.CAConfig{
   178  					CA: v1.CAInfo{
   179  						Name: "old-ca-name",
   180  					},
   181  				},
   182  			}
   183  			caJson, err := util.ConvertToJsonMessage(caConfig)
   184  			Expect(err).NotTo(HaveOccurred())
   185  
   186  			oldCA = &current.IBPCA{
   187  				ObjectMeta: metav1.ObjectMeta{
   188  					Name: instance.Name,
   189  				},
   190  				Spec: current.IBPCASpec{
   191  					ConfigOverride: &current.ConfigOverride{
   192  						CA: &runtime.RawExtension{Raw: *caJson},
   193  					},
   194  				},
   195  			}
   196  
   197  			newcaConfig := &v1.ServerConfig{
   198  				CAConfig: v1.CAConfig{
   199  					CA: v1.CAInfo{
   200  						Name: "new-ca-name",
   201  					},
   202  				},
   203  			}
   204  			newcaJson, err := util.ConvertToJsonMessage(newcaConfig)
   205  			Expect(err).NotTo(HaveOccurred())
   206  
   207  			newCA = &current.IBPCA{
   208  				ObjectMeta: metav1.ObjectMeta{
   209  					Name: instance.Name,
   210  				},
   211  				Spec: current.IBPCASpec{
   212  					ConfigOverride: &current.ConfigOverride{
   213  						CA: &runtime.RawExtension{Raw: *newcaJson},
   214  					},
   215  				},
   216  			}
   217  
   218  			e = event.UpdateEvent{
   219  				ObjectOld: oldCA,
   220  				ObjectNew: newCA,
   221  			}
   222  
   223  			Expect(reconciler.UpdateFunc(e)).To(Equal(true))
   224  
   225  			oldCA = &current.IBPCA{
   226  				ObjectMeta: metav1.ObjectMeta{
   227  					Name: instance.Name,
   228  				},
   229  				Spec: current.IBPCASpec{
   230  					ImagePullSecrets: []string{"old-secret"},
   231  				},
   232  			}
   233  
   234  			newCA = &current.IBPCA{
   235  				ObjectMeta: metav1.ObjectMeta{
   236  					Name: instance.Name,
   237  				},
   238  				Spec: current.IBPCASpec{
   239  					ImagePullSecrets: []string{"new-secret"},
   240  				},
   241  			}
   242  
   243  			e = event.UpdateEvent{
   244  				ObjectOld: oldCA,
   245  				ObjectNew: newCA,
   246  			}
   247  
   248  			Expect(reconciler.UpdateFunc(e)).To(Equal(true))
   249  		})
   250  
   251  		It("properly pops update flags from stack", func() {
   252  			Expect(reconciler.GetUpdateStatus(instance).CAOverridesUpdated()).To(Equal(true))
   253  
   254  			_, err := reconciler.Reconcile(context.TODO(), request)
   255  			Expect(err).NotTo(HaveOccurred())
   256  
   257  			Expect(reconciler.GetUpdateStatus(instance).CAOverridesUpdated()).To(Equal(false))
   258  			Expect(reconciler.GetUpdateStatus(instance).SpecUpdated()).To(Equal(true))
   259  
   260  			_, err = reconciler.Reconcile(context.TODO(), request)
   261  			Expect(err).NotTo(HaveOccurred())
   262  
   263  			Expect(reconciler.GetUpdateStatus(instance).CAOverridesUpdated()).To(Equal(false))
   264  			Expect(reconciler.GetUpdateStatus(instance).TLSCAOverridesUpdated()).To(Equal(false))
   265  			Expect(reconciler.GetUpdateStatus(instance).SpecUpdated()).To(Equal(false))
   266  		})
   267  	})
   268  
   269  	Context("set status", func() {
   270  		It("sets the status to error if error occured during IBPCA reconciliation", func() {
   271  			reconciler.SetStatus(instance, nil, errors.New("ibpca error"))
   272  			Expect(instance.Status.Type).To(Equal(current.Error))
   273  			Expect(instance.Status.Message).To(Equal("ibpca error"))
   274  		})
   275  
   276  		It("sets the status to deploying if pod is not yet running", func() {
   277  			mockKubeClient.ListStub = func(ctx context.Context, obj client.ObjectList, opts ...client.ListOption) error {
   278  				podList := obj.(*corev1.PodList)
   279  				pod := corev1.Pod{}
   280  				podList.Items = append(podList.Items, pod)
   281  				return nil
   282  			}
   283  			reconciler.SetStatus(instance, nil, nil)
   284  			Expect(instance.Status.Type).To(Equal(current.Deploying))
   285  		})
   286  
   287  		It("sets the status to deployed if pod is running", func() {
   288  			mockKubeClient.ListStub = func(ctx context.Context, obj client.ObjectList, opts ...client.ListOption) error {
   289  				podList := obj.(*corev1.PodList)
   290  				pod := corev1.Pod{
   291  					Status: corev1.PodStatus{
   292  						Phase: corev1.PodRunning,
   293  					},
   294  				}
   295  				podList.Items = append(podList.Items, pod)
   296  				return nil
   297  			}
   298  
   299  			reconciler.SetStatus(instance, nil, nil)
   300  			Expect(instance.Status.Type).To(Equal(current.Deployed))
   301  		})
   302  	})
   303  
   304  	Context("add owner reference to secret", func() {
   305  		var (
   306  			secret *corev1.Secret
   307  		)
   308  
   309  		BeforeEach(func() {
   310  			secret = &corev1.Secret{}
   311  			secret.Name = "test-ca1-ca-crypto"
   312  		})
   313  
   314  		It("returns error if fails to get list of CAs", func() {
   315  			mockKubeClient.ListReturns(errors.New("list error"))
   316  			_, err := reconciler.AddOwnerReferenceToSecret(secret)
   317  			Expect(err).To(HaveOccurred())
   318  			Expect(err.Error()).To(ContainSubstring("list error"))
   319  		})
   320  
   321  		It("returns false if secret doesn't belong to any CAs in list", func() {
   322  			secret.Name = "invalidca-ca-crypto"
   323  			added, err := reconciler.AddOwnerReferenceToSecret(secret)
   324  			Expect(err).NotTo(HaveOccurred())
   325  			Expect(added).To(Equal(false))
   326  		})
   327  
   328  		It("returns true if owner references added to ca crypto secret", func() {
   329  			added, err := reconciler.AddOwnerReferenceToSecret(secret)
   330  			Expect(err).NotTo(HaveOccurred())
   331  			Expect(added).To(Equal(true))
   332  		})
   333  
   334  		It("returns true if owner references added to tlsca crypto secret", func() {
   335  			secret.Name = "test-ca2-tlsca-crypto"
   336  			added, err := reconciler.AddOwnerReferenceToSecret(secret)
   337  			Expect(err).NotTo(HaveOccurred())
   338  			Expect(added).To(Equal(true))
   339  		})
   340  
   341  		It("returns true if owner references added to ca secret", func() {
   342  			secret.Name = "test-ca2-ca"
   343  			added, err := reconciler.AddOwnerReferenceToSecret(secret)
   344  			Expect(err).NotTo(HaveOccurred())
   345  			Expect(added).To(Equal(true))
   346  		})
   347  
   348  		It("returns true if owner references added to tlsca secret", func() {
   349  			secret.Name = "test-ca2-tlsca"
   350  			added, err := reconciler.AddOwnerReferenceToSecret(secret)
   351  			Expect(err).NotTo(HaveOccurred())
   352  			Expect(added).To(Equal(true))
   353  		})
   354  	})
   355  })