github.com/kotalco/kotal@v0.3.0/controllers/ethereum2/validator_controller_test.go (about) 1 package controllers 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "time" 8 9 appsv1 "k8s.io/api/apps/v1" 10 corev1 "k8s.io/api/core/v1" 11 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 13 "k8s.io/apimachinery/pkg/api/resource" 14 "k8s.io/apimachinery/pkg/types" 15 16 ethereum2v1alpha1 "github.com/kotalco/kotal/apis/ethereum2/v1alpha1" 17 ethereum2Clients "github.com/kotalco/kotal/clients/ethereum2" 18 "github.com/kotalco/kotal/controllers/shared" 19 20 . "github.com/onsi/ginkgo/v2" 21 . "github.com/onsi/gomega" 22 "github.com/onsi/gomega/gstruct" 23 ) 24 25 var _ = Describe("Ethereum 2.0 validator client", func() { 26 27 Context("Teku validator client", func() { 28 ns := &corev1.Namespace{ 29 ObjectMeta: metav1.ObjectMeta{ 30 Name: "teku", 31 }, 32 } 33 34 key := types.NamespacedName{ 35 Name: "teku-validator", 36 Namespace: ns.Name, 37 } 38 39 testImage := "kotalco/teku:test" 40 41 spec := ethereum2v1alpha1.ValidatorSpec{ 42 Image: testImage, 43 Network: "mainnet", 44 Client: ethereum2v1alpha1.TekuClient, 45 BeaconEndpoints: []string{"http://10.96.130.88:9999"}, 46 Graffiti: "testing Kotal validator controller", 47 Keystores: []ethereum2v1alpha1.Keystore{ 48 { 49 SecretName: "my-validator", 50 }, 51 }, 52 } 53 54 toCreate := ðereum2v1alpha1.Validator{ 55 ObjectMeta: metav1.ObjectMeta{ 56 Name: key.Name, 57 Namespace: key.Namespace, 58 }, 59 Spec: spec, 60 } 61 62 t := true 63 64 validatorOwnerReference := metav1.OwnerReference{ 65 APIVersion: "ethereum2.kotal.io/v1alpha1", 66 Kind: "Validator", 67 Name: toCreate.Name, 68 Controller: &t, 69 BlockOwnerDeletion: &t, 70 } 71 72 It(fmt.Sprintf("Should create %s namespace", ns.Name), func() { 73 Expect(k8sClient.Create(context.TODO(), ns)) 74 }) 75 76 It("Should create validator client", func() { 77 if os.Getenv(shared.EnvUseExistingCluster) != "true" { 78 toCreate.Default() 79 } 80 Expect(k8sClient.Create(context.Background(), toCreate)).Should(Succeed()) 81 }) 82 83 It("should get validator client", func() { 84 fetched := ðereum2v1alpha1.Validator{} 85 Expect(k8sClient.Get(context.Background(), key, fetched)).To(Succeed()) 86 Expect(fetched.Spec).To(Equal(toCreate.Spec)) 87 validatorOwnerReference.UID = fetched.GetUID() 88 time.Sleep(5 * time.Second) 89 }) 90 91 It("Should create statefulset", func() { 92 validatorSts := &appsv1.StatefulSet{} 93 94 Expect(k8sClient.Get(context.Background(), key, validatorSts)).To(Succeed()) 95 Expect(validatorSts.GetOwnerReferences()).To(ContainElement(validatorOwnerReference)) 96 Expect(*validatorSts.Spec.Template.Spec.SecurityContext).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{ 97 "RunAsUser": gstruct.PointTo(Equal(int64(1000))), 98 "RunAsGroup": gstruct.PointTo(Equal(int64(3000))), 99 "FSGroup": gstruct.PointTo(Equal(int64(2000))), 100 "RunAsNonRoot": gstruct.PointTo(Equal(true)), 101 })) 102 Expect(validatorSts.Spec.Template.Spec.Containers[0].Image).To(Equal(testImage)) 103 // container volume mounts 104 Expect(validatorSts.Spec.Template.Spec.Containers[0].VolumeMounts).To(ContainElements( 105 corev1.VolumeMount{ 106 Name: "data", 107 MountPath: shared.PathData(ethereum2Clients.TekuHomeDir), 108 }, 109 corev1.VolumeMount{ 110 Name: "config", 111 MountPath: shared.PathConfig(ethereum2Clients.TekuHomeDir), 112 }, 113 corev1.VolumeMount{ 114 Name: "my-validator", 115 MountPath: fmt.Sprintf("%s/validator-keys/%s", shared.PathSecrets(ethereum2Clients.TekuHomeDir), "my-validator"), 116 }, 117 )) 118 // container volume 119 mode := corev1.ConfigMapVolumeSourceDefaultMode 120 Expect(validatorSts.Spec.Template.Spec.Volumes).To(ContainElements( 121 corev1.Volume{ 122 Name: "data", 123 VolumeSource: corev1.VolumeSource{ 124 PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ 125 ClaimName: validatorSts.Name, 126 }, 127 }, 128 }, 129 corev1.Volume{ 130 Name: "config", 131 VolumeSource: corev1.VolumeSource{ 132 ConfigMap: &corev1.ConfigMapVolumeSource{ 133 LocalObjectReference: corev1.LocalObjectReference{Name: validatorSts.Name}, 134 DefaultMode: &mode, 135 }, 136 }, 137 }, 138 corev1.Volume{ 139 Name: "my-validator", 140 VolumeSource: corev1.VolumeSource{ 141 Secret: &corev1.SecretVolumeSource{ 142 SecretName: "my-validator", 143 Items: []corev1.KeyToPath{ 144 { 145 Key: "keystore", 146 Path: "keystore-0.json", 147 }, 148 { 149 Key: "password", 150 Path: "password.txt", 151 }, 152 }, 153 DefaultMode: &mode, 154 }, 155 }, 156 }, 157 )) 158 // teku doesn't require init containers 159 }) 160 161 It("Should allocate correct resources to validator statefulset", func() { 162 validatorSts := &appsv1.StatefulSet{} 163 expectedResources := corev1.ResourceRequirements{ 164 Requests: corev1.ResourceList{ 165 corev1.ResourceCPU: resource.MustParse(ethereum2v1alpha1.DefaultCPURequest), 166 corev1.ResourceMemory: resource.MustParse(ethereum2v1alpha1.DefaultMemoryRequest), 167 }, 168 Limits: corev1.ResourceList{ 169 corev1.ResourceCPU: resource.MustParse(ethereum2v1alpha1.DefaultCPULimit), 170 corev1.ResourceMemory: resource.MustParse(ethereum2v1alpha1.DefaultMemoryLimit), 171 }, 172 } 173 Expect(k8sClient.Get(context.Background(), key, validatorSts)).To(Succeed()) 174 Expect(validatorSts.Spec.Template.Spec.Containers[0].Resources).To(Equal(expectedResources)) 175 }) 176 177 It("Should create validator configmap", func() { 178 configmap := &corev1.ConfigMap{} 179 Expect(k8sClient.Get(context.Background(), key, configmap)).To(Succeed()) 180 Expect(configmap.GetOwnerReferences()).To(ContainElement(validatorOwnerReference)) 181 }) 182 183 It("Should create data persistent volume with correct resources", func() { 184 validatorPVC := &corev1.PersistentVolumeClaim{} 185 expectedResources := corev1.VolumeResourceRequirements{ 186 Requests: corev1.ResourceList{ 187 corev1.ResourceStorage: resource.MustParse(ethereum2v1alpha1.DefaultStorage), 188 }, 189 } 190 Expect(k8sClient.Get(context.Background(), key, validatorPVC)).To(Succeed()) 191 Expect(validatorPVC.GetOwnerReferences()).To(ContainElement(validatorOwnerReference)) 192 Expect(validatorPVC.Spec.Resources).To(Equal(expectedResources)) 193 }) 194 195 It(fmt.Sprintf("Should delete %s namespace", ns.Name), func() { 196 Expect(k8sClient.Delete(context.Background(), ns)).To(Succeed()) 197 }) 198 199 }) 200 201 Context("Prysm validator client", func() { 202 ns := &corev1.Namespace{ 203 ObjectMeta: metav1.ObjectMeta{ 204 Name: "prysm", 205 }, 206 } 207 208 key := types.NamespacedName{ 209 Name: "prysm-validator", 210 Namespace: ns.Name, 211 } 212 213 testImage := "kotalco/prysm:test" 214 215 spec := ethereum2v1alpha1.ValidatorSpec{ 216 Image: testImage, 217 Network: "mainnet", 218 Client: ethereum2v1alpha1.PrysmClient, 219 BeaconEndpoints: []string{"http://10.96.130.88:9999"}, 220 Graffiti: "testing Kotal validator controller", 221 WalletPasswordSecret: "my-wallet-password", 222 Keystores: []ethereum2v1alpha1.Keystore{ 223 { 224 SecretName: "my-validator", 225 }, 226 }, 227 CertSecretName: "my-cert", 228 } 229 230 toCreate := ðereum2v1alpha1.Validator{ 231 ObjectMeta: metav1.ObjectMeta{ 232 Name: key.Name, 233 Namespace: key.Namespace, 234 }, 235 Spec: spec, 236 } 237 238 t := true 239 240 validatorOwnerReference := metav1.OwnerReference{ 241 APIVersion: "ethereum2.kotal.io/v1alpha1", 242 Kind: "Validator", 243 Name: toCreate.Name, 244 Controller: &t, 245 BlockOwnerDeletion: &t, 246 } 247 248 It(fmt.Sprintf("Should create %s namespace", ns.Name), func() { 249 Expect(k8sClient.Create(context.TODO(), ns)) 250 }) 251 252 It("Should create validator client", func() { 253 if os.Getenv(shared.EnvUseExistingCluster) != "true" { 254 toCreate.Default() 255 } 256 Expect(k8sClient.Create(context.Background(), toCreate)).Should(Succeed()) 257 }) 258 259 It("should get validator client", func() { 260 fetched := ðereum2v1alpha1.Validator{} 261 Expect(k8sClient.Get(context.Background(), key, fetched)).To(Succeed()) 262 Expect(fetched.Spec).To(Equal(toCreate.Spec)) 263 validatorOwnerReference.UID = fetched.GetUID() 264 time.Sleep(5 * time.Second) 265 }) 266 267 It("Should create statefulset", func() { 268 validatorSts := &appsv1.StatefulSet{} 269 270 Expect(k8sClient.Get(context.Background(), key, validatorSts)).To(Succeed()) 271 Expect(validatorSts.GetOwnerReferences()).To(ContainElement(validatorOwnerReference)) 272 Expect(*validatorSts.Spec.Template.Spec.SecurityContext).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{ 273 "RunAsUser": gstruct.PointTo(Equal(int64(1000))), 274 "RunAsGroup": gstruct.PointTo(Equal(int64(3000))), 275 "FSGroup": gstruct.PointTo(Equal(int64(2000))), 276 "RunAsNonRoot": gstruct.PointTo(Equal(true)), 277 })) 278 Expect(validatorSts.Spec.Template.Spec.Containers[0].Image).To(Equal(testImage)) 279 // container volume mounts 280 Expect(validatorSts.Spec.Template.Spec.Containers[0].VolumeMounts).To(ContainElements( 281 corev1.VolumeMount{ 282 Name: "data", 283 MountPath: shared.PathData(ethereum2Clients.PrysmHomeDir), 284 }, 285 corev1.VolumeMount{ 286 Name: "config", 287 MountPath: shared.PathConfig(ethereum2Clients.PrysmHomeDir), 288 }, 289 corev1.VolumeMount{ 290 Name: "my-validator", 291 MountPath: fmt.Sprintf("%s/validator-keys/%s", shared.PathSecrets(ethereum2Clients.PrysmHomeDir), "my-validator"), 292 }, 293 corev1.VolumeMount{ 294 Name: "my-wallet-password", 295 ReadOnly: true, 296 MountPath: fmt.Sprintf("%s/prysm-wallet", shared.PathSecrets(ethereum2Clients.PrysmHomeDir)), 297 }, 298 corev1.VolumeMount{ 299 Name: "cert", 300 ReadOnly: true, 301 MountPath: fmt.Sprintf("%s/cert", shared.PathSecrets(ethereum2Clients.PrysmHomeDir)), 302 }, 303 )) 304 // container volume 305 mode := corev1.ConfigMapVolumeSourceDefaultMode 306 Expect(validatorSts.Spec.Template.Spec.Volumes).To(ContainElements( 307 corev1.Volume{ 308 Name: "data", 309 VolumeSource: corev1.VolumeSource{ 310 PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ 311 ClaimName: validatorSts.Name, 312 }, 313 }, 314 }, 315 corev1.Volume{ 316 Name: "config", 317 VolumeSource: corev1.VolumeSource{ 318 ConfigMap: &corev1.ConfigMapVolumeSource{ 319 LocalObjectReference: corev1.LocalObjectReference{Name: validatorSts.Name}, 320 DefaultMode: &mode, 321 }, 322 }, 323 }, 324 corev1.Volume{ 325 Name: "my-validator", 326 VolumeSource: corev1.VolumeSource{ 327 Secret: &corev1.SecretVolumeSource{ 328 SecretName: "my-validator", 329 Items: []corev1.KeyToPath{ 330 { 331 Key: "keystore", 332 Path: "keystore-0.json", 333 }, 334 { 335 Key: "password", 336 Path: "password.txt", 337 }, 338 }, 339 DefaultMode: &mode, 340 }, 341 }, 342 }, 343 corev1.Volume{ 344 Name: "my-wallet-password", 345 VolumeSource: corev1.VolumeSource{ 346 Secret: &corev1.SecretVolumeSource{ 347 SecretName: "my-wallet-password", 348 Items: []corev1.KeyToPath{ 349 { 350 Key: "password", 351 Path: "prysm-wallet-password.txt", 352 }, 353 }, 354 DefaultMode: &mode, 355 }, 356 }, 357 }, 358 corev1.Volume{ 359 Name: "cert", 360 VolumeSource: corev1.VolumeSource{ 361 Secret: &corev1.SecretVolumeSource{ 362 SecretName: toCreate.Spec.CertSecretName, 363 DefaultMode: &mode, 364 }, 365 }, 366 }, 367 )) 368 // init containers 369 Expect(validatorSts.Spec.Template.Spec.InitContainers[0].Image).To(Equal(testImage)) 370 Expect(validatorSts.Spec.Template.Spec.InitContainers[0].Env).To(ContainElements( 371 corev1.EnvVar{ 372 Name: envNetwork, 373 Value: "mainnet", 374 }, 375 corev1.EnvVar{ 376 Name: shared.EnvDataPath, 377 Value: shared.PathData(ethereum2Clients.PrysmHomeDir), 378 }, 379 corev1.EnvVar{ 380 Name: envKeyDir, 381 Value: fmt.Sprintf("%s/validator-keys/%s", shared.PathSecrets(ethereum2Clients.PrysmHomeDir), "my-validator"), 382 }, 383 corev1.EnvVar{ 384 Name: envKeystoreIndex, 385 Value: "0", 386 }, 387 corev1.EnvVar{ 388 Name: shared.EnvSecretsPath, 389 Value: shared.PathSecrets(ethereum2Clients.PrysmHomeDir), 390 }, 391 )) 392 Expect(validatorSts.Spec.Template.Spec.InitContainers[0].Command).To(ConsistOf("/bin/sh")) 393 Expect(validatorSts.Spec.Template.Spec.InitContainers[0].Args).To(ConsistOf( 394 fmt.Sprintf("%s/prysm_import_keystore.sh", shared.PathConfig(ethereum2Clients.PrysmHomeDir))), 395 ) 396 Expect(validatorSts.Spec.Template.Spec.InitContainers[0].VolumeMounts).To(ContainElements( 397 corev1.VolumeMount{ 398 Name: "data", 399 MountPath: shared.PathData(ethereum2Clients.PrysmHomeDir), 400 }, 401 corev1.VolumeMount{ 402 Name: "config", 403 MountPath: shared.PathConfig(ethereum2Clients.PrysmHomeDir), 404 }, 405 corev1.VolumeMount{ 406 Name: "my-validator", 407 MountPath: fmt.Sprintf("%s/validator-keys/%s", shared.PathSecrets(ethereum2Clients.PrysmHomeDir), "my-validator"), 408 }, 409 corev1.VolumeMount{ 410 Name: "my-wallet-password", 411 ReadOnly: true, 412 MountPath: fmt.Sprintf("%s/prysm-wallet", shared.PathSecrets(ethereum2Clients.PrysmHomeDir)), 413 }, 414 )) 415 416 }) 417 418 It("Should allocate correct resources to validator statefulset", func() { 419 validatorSts := &appsv1.StatefulSet{} 420 expectedResources := corev1.ResourceRequirements{ 421 Requests: corev1.ResourceList{ 422 corev1.ResourceCPU: resource.MustParse(ethereum2v1alpha1.DefaultCPURequest), 423 corev1.ResourceMemory: resource.MustParse(ethereum2v1alpha1.DefaultMemoryRequest), 424 }, 425 Limits: corev1.ResourceList{ 426 corev1.ResourceCPU: resource.MustParse(ethereum2v1alpha1.DefaultCPULimit), 427 corev1.ResourceMemory: resource.MustParse(ethereum2v1alpha1.DefaultMemoryLimit), 428 }, 429 } 430 Expect(k8sClient.Get(context.Background(), key, validatorSts)).To(Succeed()) 431 Expect(validatorSts.Spec.Template.Spec.Containers[0].Resources).To(Equal(expectedResources)) 432 }) 433 434 It("Should create validator configmap", func() { 435 configmap := &corev1.ConfigMap{} 436 Expect(k8sClient.Get(context.Background(), key, configmap)).To(Succeed()) 437 Expect(configmap.GetOwnerReferences()).To(ContainElement(validatorOwnerReference)) 438 Expect(configmap.Data).To(HaveKey("prysm_import_keystore.sh")) 439 }) 440 441 It("Should create data persistent volume with correct resources", func() { 442 validatorPVC := &corev1.PersistentVolumeClaim{} 443 expectedResources := corev1.VolumeResourceRequirements{ 444 Requests: corev1.ResourceList{ 445 corev1.ResourceStorage: resource.MustParse(ethereum2v1alpha1.DefaultStorage), 446 }, 447 } 448 Expect(k8sClient.Get(context.Background(), key, validatorPVC)).To(Succeed()) 449 Expect(validatorPVC.GetOwnerReferences()).To(ContainElement(validatorOwnerReference)) 450 Expect(validatorPVC.Spec.Resources).To(Equal(expectedResources)) 451 }) 452 453 It(fmt.Sprintf("Should delete %s namespace", ns.Name), func() { 454 Expect(k8sClient.Delete(context.Background(), ns)).To(Succeed()) 455 }) 456 457 }) 458 459 Context("Lighthouse validator client", func() { 460 ns := &corev1.Namespace{ 461 ObjectMeta: metav1.ObjectMeta{ 462 Name: "lighthouse", 463 }, 464 } 465 466 key := types.NamespacedName{ 467 Name: "lighthouse-validator", 468 Namespace: ns.Name, 469 } 470 471 testImage := "kotalco/lighthouse:test" 472 473 spec := ethereum2v1alpha1.ValidatorSpec{ 474 Image: testImage, 475 Network: "mainnet", 476 Client: ethereum2v1alpha1.LighthouseClient, 477 BeaconEndpoints: []string{"http://10.96.130.88:9999"}, 478 Graffiti: "testing Kotal validator controller", 479 WalletPasswordSecret: "my-wallet-password", 480 Keystores: []ethereum2v1alpha1.Keystore{ 481 { 482 SecretName: "my-validator", 483 PublicKey: "0x83dbb18e088cb16a07fca598db2ac24da3e8549601eedd75eb28d8a9d4be405f49f7dbdcad5c9d7df54a8a40a143e852", 484 }, 485 }, 486 } 487 488 toCreate := ðereum2v1alpha1.Validator{ 489 ObjectMeta: metav1.ObjectMeta{ 490 Name: key.Name, 491 Namespace: key.Namespace, 492 }, 493 Spec: spec, 494 } 495 496 t := true 497 498 validatorOwnerReference := metav1.OwnerReference{ 499 APIVersion: "ethereum2.kotal.io/v1alpha1", 500 Kind: "Validator", 501 Name: toCreate.Name, 502 Controller: &t, 503 BlockOwnerDeletion: &t, 504 } 505 506 It(fmt.Sprintf("Should create %s namespace", ns.Name), func() { 507 Expect(k8sClient.Create(context.TODO(), ns)) 508 }) 509 510 It("Should create validator client", func() { 511 if os.Getenv(shared.EnvUseExistingCluster) != "true" { 512 toCreate.Default() 513 } 514 Expect(k8sClient.Create(context.Background(), toCreate)).Should(Succeed()) 515 }) 516 517 It("should get validator client", func() { 518 fetched := ðereum2v1alpha1.Validator{} 519 Expect(k8sClient.Get(context.Background(), key, fetched)).To(Succeed()) 520 Expect(fetched.Spec).To(Equal(toCreate.Spec)) 521 validatorOwnerReference.UID = fetched.GetUID() 522 time.Sleep(5 * time.Second) 523 }) 524 525 It("Should create statefulset", func() { 526 validatorSts := &appsv1.StatefulSet{} 527 528 Expect(k8sClient.Get(context.Background(), key, validatorSts)).To(Succeed()) 529 Expect(validatorSts.GetOwnerReferences()).To(ContainElement(validatorOwnerReference)) 530 Expect(*validatorSts.Spec.Template.Spec.SecurityContext).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{ 531 "RunAsUser": gstruct.PointTo(Equal(int64(1000))), 532 "RunAsGroup": gstruct.PointTo(Equal(int64(3000))), 533 "FSGroup": gstruct.PointTo(Equal(int64(2000))), 534 "RunAsNonRoot": gstruct.PointTo(Equal(true)), 535 })) 536 Expect(validatorSts.Spec.Template.Spec.Containers[0].Image).To(Equal(testImage)) 537 // container volume mounts 538 Expect(validatorSts.Spec.Template.Spec.Containers[0].VolumeMounts).To(ContainElements( 539 corev1.VolumeMount{ 540 Name: "data", 541 MountPath: shared.PathData(ethereum2Clients.LighthouseHomeDir), 542 }, 543 corev1.VolumeMount{ 544 Name: "config", 545 MountPath: shared.PathConfig(ethereum2Clients.LighthouseHomeDir), 546 }, 547 corev1.VolumeMount{ 548 Name: "my-validator", 549 MountPath: fmt.Sprintf("%s/validator-keys/%s", shared.PathSecrets(ethereum2Clients.LighthouseHomeDir), "my-validator"), 550 }, 551 )) 552 // container volume 553 mode := corev1.ConfigMapVolumeSourceDefaultMode 554 Expect(validatorSts.Spec.Template.Spec.Volumes).To(ContainElements( 555 corev1.Volume{ 556 Name: "data", 557 VolumeSource: corev1.VolumeSource{ 558 PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ 559 ClaimName: validatorSts.Name, 560 }, 561 }, 562 }, 563 corev1.Volume{ 564 Name: "config", 565 VolumeSource: corev1.VolumeSource{ 566 ConfigMap: &corev1.ConfigMapVolumeSource{ 567 LocalObjectReference: corev1.LocalObjectReference{Name: validatorSts.Name}, 568 DefaultMode: &mode, 569 }, 570 }, 571 }, 572 corev1.Volume{ 573 Name: "my-validator", 574 VolumeSource: corev1.VolumeSource{ 575 Secret: &corev1.SecretVolumeSource{ 576 SecretName: "my-validator", 577 Items: []corev1.KeyToPath{ 578 { 579 Key: "keystore", 580 Path: "keystore-0.json", 581 }, 582 { 583 Key: "password", 584 Path: "password.txt", 585 }, 586 }, 587 DefaultMode: &mode, 588 }, 589 }, 590 }, 591 )) 592 // init containers 593 Expect(validatorSts.Spec.Template.Spec.InitContainers[0].Image).To(Equal(testImage)) 594 Expect(validatorSts.Spec.Template.Spec.InitContainers[0].Env).To(ContainElements( 595 corev1.EnvVar{ 596 Name: envNetwork, 597 Value: "mainnet", 598 }, 599 corev1.EnvVar{ 600 Name: shared.EnvDataPath, 601 Value: shared.PathData(ethereum2Clients.LighthouseHomeDir), 602 }, 603 corev1.EnvVar{ 604 Name: envKeyDir, 605 Value: fmt.Sprintf("%s/validator-keys/%s", shared.PathSecrets(ethereum2Clients.LighthouseHomeDir), "my-validator"), 606 }, 607 corev1.EnvVar{ 608 Name: envKeystoreIndex, 609 Value: "0", 610 }, 611 )) 612 Expect(validatorSts.Spec.Template.Spec.InitContainers[0].Command).To(ConsistOf("/bin/sh")) 613 Expect(validatorSts.Spec.Template.Spec.InitContainers[0].Args).To(ConsistOf( 614 fmt.Sprintf("%s/lighthouse_import_keystore.sh", shared.PathConfig(ethereum2Clients.LighthouseHomeDir))), 615 ) 616 Expect(validatorSts.Spec.Template.Spec.InitContainers[0].VolumeMounts).To(ContainElements( 617 corev1.VolumeMount{ 618 Name: "data", 619 MountPath: shared.PathData(ethereum2Clients.LighthouseHomeDir), 620 }, 621 corev1.VolumeMount{ 622 Name: "config", 623 MountPath: shared.PathConfig(ethereum2Clients.LighthouseHomeDir), 624 }, 625 corev1.VolumeMount{ 626 Name: "my-validator", 627 MountPath: fmt.Sprintf("%s/validator-keys/%s", shared.PathSecrets(ethereum2Clients.LighthouseHomeDir), "my-validator"), 628 }, 629 )) 630 631 }) 632 633 It("Should allocate correct resources to validator statefulset", func() { 634 validatorSts := &appsv1.StatefulSet{} 635 expectedResources := corev1.ResourceRequirements{ 636 Requests: corev1.ResourceList{ 637 corev1.ResourceCPU: resource.MustParse(ethereum2v1alpha1.DefaultCPURequest), 638 corev1.ResourceMemory: resource.MustParse(ethereum2v1alpha1.DefaultMemoryRequest), 639 }, 640 Limits: corev1.ResourceList{ 641 corev1.ResourceCPU: resource.MustParse(ethereum2v1alpha1.DefaultCPULimit), 642 corev1.ResourceMemory: resource.MustParse(ethereum2v1alpha1.DefaultMemoryLimit), 643 }, 644 } 645 Expect(k8sClient.Get(context.Background(), key, validatorSts)).To(Succeed()) 646 Expect(validatorSts.Spec.Template.Spec.Containers[0].Resources).To(Equal(expectedResources)) 647 }) 648 649 It("Should create validator configmap", func() { 650 configmap := &corev1.ConfigMap{} 651 Expect(k8sClient.Get(context.Background(), key, configmap)).To(Succeed()) 652 Expect(configmap.GetOwnerReferences()).To(ContainElement(validatorOwnerReference)) 653 Expect(configmap.Data).To(HaveKey("lighthouse_import_keystore.sh")) 654 }) 655 656 It("Should create data persistent volume with correct resources", func() { 657 validatorPVC := &corev1.PersistentVolumeClaim{} 658 expectedResources := corev1.VolumeResourceRequirements{ 659 Requests: corev1.ResourceList{ 660 corev1.ResourceStorage: resource.MustParse(ethereum2v1alpha1.DefaultStorage), 661 }, 662 } 663 Expect(k8sClient.Get(context.Background(), key, validatorPVC)).To(Succeed()) 664 Expect(validatorPVC.GetOwnerReferences()).To(ContainElement(validatorOwnerReference)) 665 Expect(validatorPVC.Spec.Resources).To(Equal(expectedResources)) 666 }) 667 668 It(fmt.Sprintf("Should delete %s namespace", ns.Name), func() { 669 Expect(k8sClient.Delete(context.Background(), ns)).To(Succeed()) 670 }) 671 672 }) 673 674 Context("Nimbus validator client", func() { 675 ns := &corev1.Namespace{ 676 ObjectMeta: metav1.ObjectMeta{ 677 Name: "nimbus", 678 }, 679 } 680 681 key := types.NamespacedName{ 682 Name: "nimbus-validator", 683 Namespace: ns.Name, 684 } 685 686 testImage := "kotalco/nimbus:test" 687 688 spec := ethereum2v1alpha1.ValidatorSpec{ 689 Image: testImage, 690 Network: "mainnet", 691 Client: ethereum2v1alpha1.NimbusClient, 692 BeaconEndpoints: []string{"http://10.96.130.88:9999"}, 693 Graffiti: "testing Kotal validator controller", 694 WalletPasswordSecret: "my-wallet-password", 695 Keystores: []ethereum2v1alpha1.Keystore{ 696 { 697 SecretName: "my-validator", 698 }, 699 }, 700 } 701 702 toCreate := ðereum2v1alpha1.Validator{ 703 ObjectMeta: metav1.ObjectMeta{ 704 Name: key.Name, 705 Namespace: key.Namespace, 706 }, 707 Spec: spec, 708 } 709 710 t := true 711 712 validatorOwnerReference := metav1.OwnerReference{ 713 APIVersion: "ethereum2.kotal.io/v1alpha1", 714 Kind: "Validator", 715 Name: toCreate.Name, 716 Controller: &t, 717 BlockOwnerDeletion: &t, 718 } 719 720 It(fmt.Sprintf("Should create %s namespace", ns.Name), func() { 721 Expect(k8sClient.Create(context.TODO(), ns)) 722 }) 723 724 It("Should create validator client", func() { 725 if os.Getenv(shared.EnvUseExistingCluster) != "true" { 726 toCreate.Default() 727 } 728 Expect(k8sClient.Create(context.Background(), toCreate)).Should(Succeed()) 729 }) 730 731 It("should get validator client", func() { 732 fetched := ðereum2v1alpha1.Validator{} 733 Expect(k8sClient.Get(context.Background(), key, fetched)).To(Succeed()) 734 Expect(fetched.Spec).To(Equal(toCreate.Spec)) 735 validatorOwnerReference.UID = fetched.GetUID() 736 time.Sleep(5 * time.Second) 737 }) 738 739 It("Should create statefulset", func() { 740 validatorSts := &appsv1.StatefulSet{} 741 742 Expect(k8sClient.Get(context.Background(), key, validatorSts)).To(Succeed()) 743 Expect(validatorSts.GetOwnerReferences()).To(ContainElement(validatorOwnerReference)) 744 Expect(*validatorSts.Spec.Template.Spec.SecurityContext).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{ 745 "RunAsUser": gstruct.PointTo(Equal(int64(1000))), 746 "RunAsGroup": gstruct.PointTo(Equal(int64(3000))), 747 "FSGroup": gstruct.PointTo(Equal(int64(2000))), 748 "RunAsNonRoot": gstruct.PointTo(Equal(true)), 749 })) 750 Expect(validatorSts.Spec.Template.Spec.Containers[0].Image).To(Equal(testImage)) 751 // container volume mounts 752 Expect(validatorSts.Spec.Template.Spec.Containers[0].VolumeMounts).To(ContainElements( 753 corev1.VolumeMount{ 754 Name: "data", 755 MountPath: shared.PathData(ethereum2Clients.NimbusHomeDir), 756 }, 757 corev1.VolumeMount{ 758 Name: "config", 759 MountPath: shared.PathConfig(ethereum2Clients.NimbusHomeDir), 760 }, 761 corev1.VolumeMount{ 762 Name: "my-validator", 763 MountPath: fmt.Sprintf("%s/validator-keys/%s", shared.PathSecrets(ethereum2Clients.NimbusHomeDir), "my-validator"), 764 }, 765 corev1.VolumeMount{ 766 Name: "validator-secrets", 767 MountPath: fmt.Sprintf("%s/validator-secrets", shared.PathSecrets(ethereum2Clients.NimbusHomeDir)), 768 }, 769 )) 770 // container volume 771 mode := corev1.ConfigMapVolumeSourceDefaultMode 772 fmt.Sprintln(validatorSts.Spec.Template.Spec.Volumes) 773 Expect(validatorSts.Spec.Template.Spec.Volumes).To(ContainElements( 774 corev1.Volume{ 775 Name: "data", 776 VolumeSource: corev1.VolumeSource{ 777 PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ 778 ClaimName: validatorSts.Name, 779 }, 780 }, 781 }, 782 corev1.Volume{ 783 Name: "config", 784 VolumeSource: corev1.VolumeSource{ 785 ConfigMap: &corev1.ConfigMapVolumeSource{ 786 LocalObjectReference: corev1.LocalObjectReference{Name: validatorSts.Name}, 787 DefaultMode: &mode, 788 }, 789 }, 790 }, 791 corev1.Volume{ 792 Name: "my-validator", 793 VolumeSource: corev1.VolumeSource{ 794 Secret: &corev1.SecretVolumeSource{ 795 SecretName: "my-validator", 796 Items: []corev1.KeyToPath{ 797 { 798 Key: "keystore", 799 Path: "keystore.json", 800 }, 801 }, 802 DefaultMode: &mode, 803 }, 804 }, 805 }, 806 corev1.Volume{ 807 Name: "validator-secrets", 808 VolumeSource: corev1.VolumeSource{ 809 Projected: &corev1.ProjectedVolumeSource{ 810 Sources: []corev1.VolumeProjection{ 811 { 812 Secret: &corev1.SecretProjection{ 813 LocalObjectReference: corev1.LocalObjectReference{ 814 Name: "my-validator", 815 }, 816 Items: []corev1.KeyToPath{ 817 { 818 Key: "password", 819 Path: "my-validator", 820 }, 821 }, 822 }, 823 }, 824 }, 825 DefaultMode: &mode, 826 }, 827 }, 828 }, 829 )) 830 // init containers 831 Expect(validatorSts.Spec.Template.Spec.InitContainers[0].Image).To(Equal(testImage)) 832 Expect(validatorSts.Spec.Template.Spec.InitContainers[0].Env).To(ContainElements( 833 corev1.EnvVar{ 834 Name: shared.EnvSecretsPath, 835 Value: shared.PathSecrets(ethereum2Clients.NimbusHomeDir), 836 }, 837 corev1.EnvVar{ 838 Name: envValidatorsPath, 839 Value: fmt.Sprintf("%s/kotal-validators", shared.PathData(ethereum2Clients.NimbusHomeDir)), 840 }, 841 )) 842 Expect(validatorSts.Spec.Template.Spec.InitContainers[0].Command).To(ConsistOf("/bin/sh")) 843 Expect(validatorSts.Spec.Template.Spec.InitContainers[0].Args).To(ConsistOf( 844 fmt.Sprintf("%s/nimbus_copy_validators.sh", shared.PathConfig(ethereum2Clients.NimbusHomeDir))), 845 ) 846 Expect(validatorSts.Spec.Template.Spec.InitContainers[0].VolumeMounts).To(ContainElements( 847 corev1.VolumeMount{ 848 Name: "data", 849 MountPath: shared.PathData(ethereum2Clients.NimbusHomeDir), 850 }, 851 corev1.VolumeMount{ 852 Name: "config", 853 MountPath: shared.PathConfig(ethereum2Clients.NimbusHomeDir), 854 }, 855 corev1.VolumeMount{ 856 Name: "my-validator", 857 MountPath: fmt.Sprintf("%s/validator-keys/%s", shared.PathSecrets(ethereum2Clients.NimbusHomeDir), "my-validator"), 858 }, 859 )) 860 861 }) 862 863 It("Should allocate correct resources to validator statefulset", func() { 864 validatorSts := &appsv1.StatefulSet{} 865 expectedResources := corev1.ResourceRequirements{ 866 Requests: corev1.ResourceList{ 867 corev1.ResourceCPU: resource.MustParse(ethereum2v1alpha1.DefaultCPURequest), 868 corev1.ResourceMemory: resource.MustParse(ethereum2v1alpha1.DefaultMemoryRequest), 869 }, 870 Limits: corev1.ResourceList{ 871 corev1.ResourceCPU: resource.MustParse(ethereum2v1alpha1.DefaultCPULimit), 872 corev1.ResourceMemory: resource.MustParse(ethereum2v1alpha1.DefaultMemoryLimit), 873 }, 874 } 875 Expect(k8sClient.Get(context.Background(), key, validatorSts)).To(Succeed()) 876 Expect(validatorSts.Spec.Template.Spec.Containers[0].Resources).To(Equal(expectedResources)) 877 }) 878 879 It("Should create validator configmap", func() { 880 configmap := &corev1.ConfigMap{} 881 Expect(k8sClient.Get(context.Background(), key, configmap)).To(Succeed()) 882 Expect(configmap.GetOwnerReferences()).To(ContainElement(validatorOwnerReference)) 883 Expect(configmap.Data).To(HaveKey("nimbus_copy_validators.sh")) 884 }) 885 886 It("Should create data persistent volume with correct resources", func() { 887 validatorPVC := &corev1.PersistentVolumeClaim{} 888 expectedResources := corev1.VolumeResourceRequirements{ 889 Requests: corev1.ResourceList{ 890 corev1.ResourceStorage: resource.MustParse(ethereum2v1alpha1.DefaultStorage), 891 }, 892 } 893 Expect(k8sClient.Get(context.Background(), key, validatorPVC)).To(Succeed()) 894 Expect(validatorPVC.GetOwnerReferences()).To(ContainElement(validatorOwnerReference)) 895 Expect(validatorPVC.Spec.Resources).To(Equal(expectedResources)) 896 }) 897 898 It(fmt.Sprintf("Should delete %s namespace", ns.Name), func() { 899 Expect(k8sClient.Delete(context.Background(), ns)).To(Succeed()) 900 }) 901 902 }) 903 904 })