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(¤t.Enrollment{ 92 CATLS: ¤t.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 })