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