github.com/IBM-Blockchain/fabric-operator@v1.0.4/pkg/initializer/common/enroller/hsmenroller_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 enroller_test
    20  
    21  import (
    22  	"context"
    23  
    24  	. "github.com/onsi/ginkgo/v2"
    25  	. "github.com/onsi/gomega"
    26  	"github.com/pkg/errors"
    27  
    28  	batchv1 "k8s.io/api/batch/v1"
    29  	corev1 "k8s.io/api/core/v1"
    30  	"k8s.io/apimachinery/pkg/types"
    31  
    32  	current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1"
    33  	ccmocks "github.com/IBM-Blockchain/fabric-operator/controllers/mocks"
    34  	"github.com/IBM-Blockchain/fabric-operator/pkg/apis/common"
    35  	"github.com/IBM-Blockchain/fabric-operator/pkg/initializer/common/config"
    36  	"github.com/IBM-Blockchain/fabric-operator/pkg/initializer/common/enroller"
    37  	"github.com/IBM-Blockchain/fabric-operator/pkg/initializer/common/enroller/mocks"
    38  
    39  	k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
    40  )
    41  
    42  var _ = Describe("HSM sidecar enroller", func() {
    43  	var (
    44  		e           *enroller.HSMEnroller
    45  		ccClient    *ccmocks.Client
    46  		hsmcaClient *mocks.HSMCAClient
    47  		instance    *mocks.Instance
    48  	)
    49  
    50  	BeforeEach(func() {
    51  		instance = &mocks.Instance{}
    52  		instance.GetNameReturns("test")
    53  
    54  		ccClient = &ccmocks.Client{
    55  			GetStub: func(ctx context.Context, nn types.NamespacedName, obj k8sclient.Object) error {
    56  				switch obj.(type) {
    57  				case *batchv1.Job:
    58  					j := obj.(*batchv1.Job)
    59  					j.Status.Active = int32(1)
    60  					j.Name = "test-job"
    61  				}
    62  				return nil
    63  			},
    64  			ListStub: func(ctx context.Context, obj k8sclient.ObjectList, opts ...k8sclient.ListOption) error {
    65  				switch obj.(type) {
    66  				case *corev1.PodList:
    67  					p := obj.(*corev1.PodList)
    68  					p.Items = []corev1.Pod{{
    69  						Status: corev1.PodStatus{
    70  							ContainerStatuses: []corev1.ContainerStatus{
    71  								{
    72  									State: corev1.ContainerState{
    73  										Terminated: &corev1.ContainerStateTerminated{},
    74  									},
    75  								},
    76  							},
    77  							Phase: corev1.PodSucceeded,
    78  						},
    79  					}}
    80  				}
    81  				return nil
    82  			},
    83  		}
    84  
    85  		hsmcaClient = &mocks.HSMCAClient{}
    86  		hsmcaClient.GetEnrollmentRequestReturns(&current.Enrollment{
    87  			CATLS: &current.CATLS{
    88  				CACert: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNGakNDQWIyZ0F3SUJBZ0lVZi84bk94M2NqM1htVzNDSUo1L0Q1ejRRcUVvd0NnWUlLb1pJemowRUF3SXcKYURFTE1Ba0dBMVVFQmhNQ1ZWTXhGekFWQmdOVkJBZ1REazV2Y25Sb0lFTmhjbTlzYVc1aE1SUXdFZ1lEVlFRSwpFd3RJZVhCbGNteGxaR2RsY2pFUE1BMEdBMVVFQ3hNR1JtRmljbWxqTVJrd0Z3WURWUVFERXhCbVlXSnlhV010ClkyRXRjMlZ5ZG1WeU1CNFhEVEU1TVRBek1ERTNNamd3TUZvWERUTTBNVEF5TmpFM01qZ3dNRm93YURFTE1Ba0cKQTFVRUJoTUNWVk14RnpBVkJnTlZCQWdURGs1dmNuUm9JRU5oY205c2FXNWhNUlF3RWdZRFZRUUtFd3RJZVhCbApjbXhsWkdkbGNqRVBNQTBHQTFVRUN4TUdSbUZpY21sak1Sa3dGd1lEVlFRREV4Qm1ZV0p5YVdNdFkyRXRjMlZ5CmRtVnlNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVSbzNmbUc2UHkyUHd6cUMwNnFWZDlFOFgKZ044eldqZzFMb3lnMmsxdkQ4MXY1dENRRytCTVozSUJGQnI2VTRhc0tZTUREakd6TElERmdUUTRjVDd1VktORgpNRU13RGdZRFZSMFBBUUgvQkFRREFnRUdNQklHQTFVZEV3RUIvd1FJTUFZQkFmOENBUUV3SFFZRFZSME9CQllFCkZFa0RtUHhjbTdGcXZSMXllN0tNNGdLLy9KZ1JNQW9HQ0NxR1NNNDlCQU1DQTBjQU1FUUNJRC92QVFVSEh2SWwKQWZZLzM5UWdEU2ltTWpMZnhPTG44NllyR1EvWHpkQVpBaUFpUmlyZmlMdzVGbXBpRDhtYmlmRjV4bzdFUzdqNApaUWQyT0FUNCt5OWE0Zz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
    89  			},
    90  		})
    91  
    92  		hsmConfig := &config.HSMConfig{
    93  			Type:    "hsm",
    94  			Version: "v1",
    95  			Library: config.Library{
    96  				FilePath: "/usr/lib/libCryptoki2_64.so",
    97  				Image:    "ghcr.io/ibm-blockchain/ibp-pkcs11-proxy/gemalto-client:skarim-amd64",
    98  				Auth: &config.Auth{
    99  					ImagePullSecret: "hsmpullsecret",
   100  				},
   101  			},
   102  			Envs: []corev1.EnvVar{
   103  				{
   104  					Name:  "DUMMY_ENV_NAME",
   105  					Value: "DUMMY_ENV_VALUE",
   106  				},
   107  			},
   108  			MountPaths: []config.MountPath{
   109  				{
   110  					Name:      "hsmcrypto",
   111  					Secret:    "hsmcrypto",
   112  					MountPath: "/hsm",
   113  					Paths: []config.Path{
   114  						{
   115  							Key:  "cafile.pem",
   116  							Path: "cafile.pem",
   117  						},
   118  					},
   119  				},
   120  				{
   121  					Name:      "hsmconfig",
   122  					Secret:    "hsmcrypto",
   123  					MountPath: "/etc/Chrystoki.conf",
   124  					SubPath:   "Chrystoki.conf",
   125  				},
   126  			},
   127  		}
   128  
   129  		e = &enroller.HSMEnroller{
   130  			Config:   hsmConfig,
   131  			Client:   ccClient,
   132  			Instance: instance,
   133  			CAClient: hsmcaClient,
   134  			Timeouts: enroller.HSMEnrollJobTimeouts{
   135  				JobStart:      common.MustParseDuration("1s"),
   136  				JobCompletion: common.MustParseDuration("1s"),
   137  			},
   138  		}
   139  	})
   140  
   141  	Context("enroll", func() {
   142  		It("returns error if creating ca crypto secret fails", func() {
   143  			ccClient.CreateReturnsOnCall(0, errors.New("failed to create root TLS secret"))
   144  			_, err := e.Enroll()
   145  			Expect(err).To(MatchError(ContainSubstring("failed to create root TLS secret")))
   146  		})
   147  
   148  		It("returns error if creating ca config map fails", func() {
   149  			ccClient.CreateReturnsOnCall(1, errors.New("failed to create ca config map"))
   150  			_, err := e.Enroll()
   151  			Expect(err).To(MatchError(ContainSubstring("failed to create ca config map")))
   152  		})
   153  
   154  		It("returns error if creating job fails", func() {
   155  			ccClient.CreateReturnsOnCall(2, errors.New("failed to create job"))
   156  			_, err := e.Enroll()
   157  			Expect(err).To(MatchError(ContainSubstring("failed to create job")))
   158  		})
   159  
   160  		Context("job start timeout", func() {
   161  			BeforeEach(func() {
   162  				ccClient.GetStub = func(ctx context.Context, nn types.NamespacedName, obj k8sclient.Object) error {
   163  					switch obj.(type) {
   164  					case *batchv1.Job:
   165  						j := obj.(*batchv1.Job)
   166  						j.Status.Active = int32(0)
   167  						j.Name = "test-job"
   168  
   169  					}
   170  					return nil
   171  				}
   172  			})
   173  
   174  			It("returns error if job doesn't start before timeout", func() {
   175  				_, err := e.Enroll()
   176  				Expect(err).To(MatchError(ContainSubstring("job failed to start")))
   177  			})
   178  		})
   179  
   180  		Context("job fails", func() {
   181  			When("job timesout", func() {
   182  				BeforeEach(func() {
   183  					ccClient.ListStub = func(ctx context.Context, obj k8sclient.ObjectList, opts ...k8sclient.ListOption) error {
   184  						switch obj.(type) {
   185  						case *corev1.PodList:
   186  							p := obj.(*corev1.PodList)
   187  							p.Items = []corev1.Pod{}
   188  						}
   189  						return nil
   190  					}
   191  				})
   192  
   193  				It("returns error", func() {
   194  					_, err := e.Enroll()
   195  					Expect(err).To(MatchError(ContainSubstring("failed to finish")))
   196  				})
   197  			})
   198  
   199  			When("pod enters failed state", func() {
   200  				BeforeEach(func() {
   201  					ccClient.ListStub = func(ctx context.Context, obj k8sclient.ObjectList, opts ...k8sclient.ListOption) error {
   202  						switch obj.(type) {
   203  						case *corev1.PodList:
   204  							p := obj.(*corev1.PodList)
   205  							p.Items = []corev1.Pod{{
   206  								Status: corev1.PodStatus{
   207  									Phase: corev1.PodFailed,
   208  								},
   209  							}}
   210  						}
   211  						return nil
   212  					}
   213  				})
   214  
   215  				It("returns error", func() {
   216  					_, err := e.Enroll()
   217  					Expect(err).To(MatchError(ContainSubstring("finished unsuccessfully, not cleaning up pods to allow for error")))
   218  				})
   219  			})
   220  		})
   221  
   222  		It("returns no error on successfull enroll", func() {
   223  			resp, err := e.Enroll()
   224  			Expect(err).NotTo(HaveOccurred())
   225  			Expect(resp).NotTo(BeNil())
   226  
   227  			By("creating a job resource", func() {
   228  				_, obj, _ := ccClient.CreateArgsForCall(2)
   229  				Expect(obj).NotTo(BeNil())
   230  
   231  				job := obj.(*batchv1.Job)
   232  				Expect(job.Spec.Template.Spec.Containers[0].Env).To(Equal([]corev1.EnvVar{
   233  					{
   234  						Name:  "DUMMY_ENV_NAME",
   235  						Value: "DUMMY_ENV_VALUE",
   236  					},
   237  				}))
   238  
   239  				Expect(job.Spec.Template.Spec.Containers[0].VolumeMounts).To(ContainElements([]corev1.VolumeMount{
   240  					{
   241  						Name:      "hsmcrypto",
   242  						MountPath: "/hsm",
   243  					},
   244  					{
   245  						Name:      "hsmconfig",
   246  						MountPath: "/etc/Chrystoki.conf",
   247  						SubPath:   "Chrystoki.conf",
   248  					},
   249  				}))
   250  
   251  				Expect(job.Spec.Template.Spec.Volumes).To(ContainElements([]corev1.Volume{
   252  					{
   253  						Name: "hsmconfig",
   254  						VolumeSource: corev1.VolumeSource{
   255  							Secret: &corev1.SecretVolumeSource{
   256  								SecretName: "hsmcrypto",
   257  							},
   258  						},
   259  					},
   260  					{
   261  						Name: "hsmcrypto",
   262  						VolumeSource: corev1.VolumeSource{
   263  							Secret: &corev1.SecretVolumeSource{
   264  								SecretName: "hsmcrypto",
   265  								Items: []corev1.KeyToPath{
   266  									{
   267  										Key:  "cafile.pem",
   268  										Path: "cafile.pem",
   269  									},
   270  								},
   271  							},
   272  						},
   273  					},
   274  				}))
   275  			})
   276  
   277  			By("deleting completed job", func() {
   278  				// One delete to clean up ca config map before starting job
   279  				// Second delete to delete job
   280  				// Third delete to delete associated pod
   281  				// Fourth delete to delete root tls secret
   282  				// Fifth delete to delete ca config map
   283  				Expect(ccClient.DeleteCallCount()).To(Equal(5))
   284  			})
   285  
   286  			By("setting controller reference on resources created by enroll job", func() {
   287  				Expect(ccClient.UpdateCallCount()).To(Equal(4))
   288  			})
   289  		})
   290  	})
   291  
   292  	// TODO: Add more tests for error path testing
   293  })