github.com/IBM-Blockchain/fabric-operator@v1.0.4/pkg/initializer/ca/hsm_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 initializer_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  	current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1"
    30  	v1 "github.com/IBM-Blockchain/fabric-operator/pkg/apis/ca/v1"
    31  	"github.com/IBM-Blockchain/fabric-operator/pkg/apis/common"
    32  	initializer "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/ca"
    33  	caconfig "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/ca/config"
    34  	"github.com/IBM-Blockchain/fabric-operator/pkg/initializer/ca/mocks"
    35  	"github.com/IBM-Blockchain/fabric-operator/pkg/initializer/common/config"
    36  
    37  	batchv1 "k8s.io/api/batch/v1"
    38  	corev1 "k8s.io/api/core/v1"
    39  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    40  	"k8s.io/apimachinery/pkg/types"
    41  
    42  	k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
    43  )
    44  
    45  var _ = Describe("HSM CA initializer", func() {
    46  	var (
    47  		client *mocks.Client
    48  		ca     *mocks.IBPCA
    49  		// defaultConfig *mocks.CAConfig
    50  
    51  		hsmca    *initializer.HSM
    52  		instance *current.IBPCA
    53  	)
    54  
    55  	BeforeEach(func() {
    56  		client = &mocks.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  					pods := obj.(*corev1.PodList)
    70  					pods.Items = []corev1.Pod{
    71  						{
    72  							Status: corev1.PodStatus{
    73  								Phase: corev1.PodSucceeded,
    74  								ContainerStatuses: []corev1.ContainerStatus{
    75  									{
    76  										State: corev1.ContainerState{
    77  											Terminated: &corev1.ContainerStateTerminated{},
    78  										},
    79  									},
    80  								},
    81  							},
    82  						},
    83  					}
    84  				}
    85  				return nil
    86  			},
    87  		}
    88  
    89  		hsmConfig := &config.HSMConfig{
    90  			Type:    "hsm",
    91  			Version: "v1",
    92  			Library: config.Library{
    93  				FilePath: "/usr/lib/libCryptoki2_64.so",
    94  				Image:    "ghcr.io/ibm-blockchain/gemalto-client:skarim-amd64",
    95  				Auth: &config.Auth{
    96  					ImagePullSecret: "hsmpullsecret",
    97  				},
    98  			},
    99  			Envs: []corev1.EnvVar{
   100  				{
   101  					Name:  "DUMMY_ENV_NAME",
   102  					Value: "DUMMY_ENV_VALUE",
   103  				},
   104  			},
   105  			MountPaths: []config.MountPath{
   106  				{
   107  					Name:      "hsmcrypto",
   108  					Secret:    "hsmcrypto",
   109  					MountPath: "/hsm",
   110  					Paths: []config.Path{
   111  						{
   112  							Key:  "cafile.pem",
   113  							Path: "cafile.pem",
   114  						},
   115  					},
   116  				},
   117  				{
   118  					Name:      "hsmconfig",
   119  					Secret:    "hsmcrypto",
   120  					MountPath: "/etc/Chrystoki.conf",
   121  					SubPath:   "Chrystoki.conf",
   122  				},
   123  			},
   124  		}
   125  
   126  		ca = &mocks.IBPCA{}
   127  		ca.GetServerConfigReturns(&v1.ServerConfig{
   128  			CAConfig: v1.CAConfig{
   129  				CSP: &v1.BCCSP{
   130  					PKCS11: &v1.PKCS11Opts{},
   131  				},
   132  			},
   133  		})
   134  		ca.GetTypeReturns(caconfig.EnrollmentCA)
   135  
   136  		instance = &current.IBPCA{
   137  			ObjectMeta: metav1.ObjectMeta{
   138  				Name: "test-ibpca",
   139  			},
   140  			Spec: current.IBPCASpec{
   141  				Resources: &current.CAResources{
   142  					CA:   &corev1.ResourceRequirements{},
   143  					Init: &corev1.ResourceRequirements{},
   144  				},
   145  				Images: &current.CAImages{},
   146  			},
   147  		}
   148  
   149  		hsmca = &initializer.HSM{
   150  			Config: hsmConfig,
   151  			Timeouts: initializer.HSMInitJobTimeouts{
   152  				JobStart:      common.MustParseDuration("1s"),
   153  				JobCompletion: common.MustParseDuration("1s"),
   154  			},
   155  			Client: client,
   156  		}
   157  	})
   158  
   159  	Context("creates", func() {
   160  		It("returns error if overriding server config fails", func() {
   161  			ca.OverrideServerConfigReturns(errors.New("override failed"))
   162  			_, err := hsmca.Create(instance, &v1.ServerConfig{}, ca)
   163  			Expect(err).To(MatchError("override failed"))
   164  		})
   165  
   166  		It("returns error if creating ca crypto secret fails", func() {
   167  			client.CreateReturnsOnCall(0, errors.New("failed to create crypto secret"))
   168  			_, err := hsmca.Create(instance, &v1.ServerConfig{}, ca)
   169  			Expect(err).To(MatchError(ContainSubstring("failed to create crypto secret")))
   170  		})
   171  
   172  		It("returns error if creating ca config map fails", func() {
   173  			client.CreateReturnsOnCall(1, errors.New("failed to create config map"))
   174  			_, err := hsmca.Create(instance, &v1.ServerConfig{}, ca)
   175  			Expect(err).To(MatchError(ContainSubstring("failed to create config map")))
   176  		})
   177  
   178  		It("returns error if creating job fails", func() {
   179  			client.CreateReturnsOnCall(2, errors.New("failed to create job"))
   180  			_, err := hsmca.Create(instance, &v1.ServerConfig{}, ca)
   181  			Expect(err).To(MatchError(ContainSubstring("failed to create job")))
   182  		})
   183  
   184  		Context("job start timeout", func() {
   185  			BeforeEach(func() {
   186  				client.GetStub = func(ctx context.Context, nn types.NamespacedName, obj k8sclient.Object) error {
   187  					switch obj.(type) {
   188  					case *batchv1.Job:
   189  						j := obj.(*batchv1.Job)
   190  						j.Status.Active = int32(0)
   191  						j.Name = "test-job"
   192  
   193  					}
   194  					return nil
   195  				}
   196  			})
   197  
   198  			It("returns error if job doesn't start before timeout", func() {
   199  				_, err := hsmca.Create(instance, &v1.ServerConfig{}, ca)
   200  				Expect(err).To(MatchError(ContainSubstring("job failed to start")))
   201  			})
   202  		})
   203  
   204  		Context("job fails", func() {
   205  			When("job timesout", func() {
   206  				BeforeEach(func() {
   207  					client.ListStub = func(ctx context.Context, obj k8sclient.ObjectList, opts ...k8sclient.ListOption) error {
   208  						switch obj.(type) {
   209  						case *corev1.PodList:
   210  							p := obj.(*corev1.PodList)
   211  							p.Items = []corev1.Pod{}
   212  						}
   213  						return nil
   214  					}
   215  				})
   216  
   217  				It("returns error", func() {
   218  					_, err := hsmca.Create(instance, &v1.ServerConfig{}, ca)
   219  					Expect(err).To(MatchError(ContainSubstring("failed to finish")))
   220  				})
   221  			})
   222  
   223  			When("pod enters failed state", func() {
   224  				BeforeEach(func() {
   225  					client.ListStub = func(ctx context.Context, obj k8sclient.ObjectList, opts ...k8sclient.ListOption) error {
   226  						switch obj.(type) {
   227  						case *corev1.PodList:
   228  							p := obj.(*corev1.PodList)
   229  							p.Items = []corev1.Pod{{
   230  								Status: corev1.PodStatus{
   231  									Phase: corev1.PodFailed,
   232  								},
   233  							}}
   234  						}
   235  						return nil
   236  					}
   237  				})
   238  
   239  				It("returns error", func() {
   240  					_, err := hsmca.Create(instance, &v1.ServerConfig{}, ca)
   241  					Expect(err).To(MatchError(ContainSubstring(fmt.Sprintf("check job '%s' pods for errors", instance.GetName()+"-ca-init"))))
   242  				})
   243  			})
   244  		})
   245  
   246  		It("returns error if unable to delete job after success", func() {
   247  			client.DeleteReturns(errors.New("failed to delete job"))
   248  			_, err := hsmca.Create(instance, &v1.ServerConfig{}, ca)
   249  			Expect(err).To(MatchError(ContainSubstring("failed to delete job")))
   250  		})
   251  
   252  		It("returns error if unable to update ca config map", func() {
   253  			client.UpdateReturns(errors.New("failed to update ca config map"))
   254  			_, err := hsmca.Create(instance, &v1.ServerConfig{}, ca)
   255  			Expect(err).To(MatchError(ContainSubstring("failed to update ca config map")))
   256  		})
   257  
   258  		It("returns sucessfully with no error and nil response", func() {
   259  			_, err := hsmca.Create(instance, &v1.ServerConfig{}, ca)
   260  			Expect(err).NotTo(HaveOccurred())
   261  
   262  			By("creating a job resource", func() {
   263  				_, obj, _ := client.CreateArgsForCall(2)
   264  				Expect(obj).NotTo(BeNil())
   265  
   266  				job := obj.(*batchv1.Job)
   267  				Expect(job.Spec.Template.Spec.Containers[0].Env).To(ContainElements(
   268  					corev1.EnvVar{
   269  						Name:  "DUMMY_ENV_NAME",
   270  						Value: "DUMMY_ENV_VALUE",
   271  					},
   272  				))
   273  
   274  				Expect(job.Spec.Template.Spec.Containers[0].VolumeMounts).To(ContainElements([]corev1.VolumeMount{
   275  					{
   276  						Name:      "hsmcrypto",
   277  						MountPath: "/hsm",
   278  					},
   279  					{
   280  						Name:      "hsmconfig",
   281  						MountPath: "/etc/Chrystoki.conf",
   282  						SubPath:   "Chrystoki.conf",
   283  					},
   284  				}))
   285  
   286  				Expect(job.Spec.Template.Spec.Volumes).To(ContainElements([]corev1.Volume{
   287  					{
   288  						Name: "hsmconfig",
   289  						VolumeSource: corev1.VolumeSource{
   290  							Secret: &corev1.SecretVolumeSource{
   291  								SecretName: "hsmcrypto",
   292  							},
   293  						},
   294  					},
   295  					{
   296  						Name: "hsmcrypto",
   297  						VolumeSource: corev1.VolumeSource{
   298  							Secret: &corev1.SecretVolumeSource{
   299  								SecretName: "hsmcrypto",
   300  								Items: []corev1.KeyToPath{
   301  									{
   302  										Key:  "cafile.pem",
   303  										Path: "cafile.pem",
   304  									},
   305  								},
   306  							},
   307  						},
   308  					},
   309  				}))
   310  			})
   311  
   312  			By("deleting completed job", func() {
   313  				// One delete count to delete job and second delete count to delete associated pod
   314  				Expect(client.DeleteCallCount()).To(Equal(2))
   315  			})
   316  
   317  			By("updating config map if enrollment CA", func() {
   318  				Expect(client.UpdateCallCount()).To(Equal(1))
   319  			})
   320  		})
   321  	})
   322  })