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 := &ethereum2v1alpha1.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 := &ethereum2v1alpha1.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 := &ethereum2v1alpha1.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 := &ethereum2v1alpha1.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 := &ethereum2v1alpha1.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 := &ethereum2v1alpha1.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 := &ethereum2v1alpha1.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 := &ethereum2v1alpha1.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  })