github.com/kotalco/kotal@v0.3.0/controllers/near/node_controller_test.go (about)

     1  package controllers
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"time"
     8  
     9  	nearv1alpha1 "github.com/kotalco/kotal/apis/near/v1alpha1"
    10  	nearClients "github.com/kotalco/kotal/clients/near"
    11  	"github.com/kotalco/kotal/controllers/shared"
    12  	. "github.com/onsi/ginkgo/v2"
    13  	. "github.com/onsi/gomega"
    14  	"github.com/onsi/gomega/gstruct"
    15  	appsv1 "k8s.io/api/apps/v1"
    16  	corev1 "k8s.io/api/core/v1"
    17  	"k8s.io/apimachinery/pkg/api/resource"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/apimachinery/pkg/types"
    20  	"k8s.io/apimachinery/pkg/util/intstr"
    21  )
    22  
    23  var _ = Describe("NEAR node controller", func() {
    24  	ns := &corev1.Namespace{
    25  		ObjectMeta: metav1.ObjectMeta{
    26  			Name: "near",
    27  		},
    28  	}
    29  
    30  	key := types.NamespacedName{
    31  		Name:      "near-node",
    32  		Namespace: ns.Name,
    33  	}
    34  
    35  	testImage := "kotalco/nearcore:controller-test"
    36  
    37  	spec := nearv1alpha1.NodeSpec{
    38  		Image:                    testImage,
    39  		Network:                  "mainnet",
    40  		RPC:                      true,
    41  		Archive:                  true, // test volume storage size
    42  		NodePrivateKeySecretName: "my-node-key",
    43  		ValidatorSecretName:      "validator-key",
    44  	}
    45  
    46  	toCreate := &nearv1alpha1.Node{
    47  		ObjectMeta: metav1.ObjectMeta{
    48  			Name:      key.Name,
    49  			Namespace: key.Namespace,
    50  		},
    51  		Spec: spec,
    52  	}
    53  
    54  	client := nearClients.NewClient(toCreate)
    55  
    56  	t := true
    57  
    58  	nodeOwnerReference := metav1.OwnerReference{
    59  		APIVersion:         "near.kotal.io/v1alpha1",
    60  		Kind:               "Node",
    61  		Name:               toCreate.Name,
    62  		Controller:         &t,
    63  		BlockOwnerDeletion: &t,
    64  	}
    65  
    66  	It(fmt.Sprintf("Should create %s namespace", ns.Name), func() {
    67  		Expect(k8sClient.Create(context.TODO(), ns)).To(Succeed())
    68  	})
    69  
    70  	It("should create NEAR node", func() {
    71  		if os.Getenv(shared.EnvUseExistingCluster) != "true" {
    72  			toCreate.Default()
    73  		}
    74  		Expect(k8sClient.Create(context.Background(), toCreate)).Should(Succeed())
    75  	})
    76  
    77  	It("Should get NEAR node", func() {
    78  		fetched := &nearv1alpha1.Node{}
    79  		Expect(k8sClient.Get(context.Background(), key, fetched)).To(Succeed())
    80  		Expect(fetched.Spec).To(Equal(toCreate.Spec))
    81  		nodeOwnerReference.UID = fetched.UID
    82  		time.Sleep(5 * time.Second)
    83  	})
    84  
    85  	It("Should create node statefulset", func() {
    86  		fetched := &appsv1.StatefulSet{}
    87  		Expect(k8sClient.Get(context.Background(), key, fetched)).To(Succeed())
    88  		Expect(fetched.OwnerReferences).To(ContainElements(nodeOwnerReference))
    89  		Expect(*fetched.Spec.Template.Spec.SecurityContext).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
    90  			"RunAsUser":    gstruct.PointTo(Equal(int64(1000))),
    91  			"RunAsGroup":   gstruct.PointTo(Equal(int64(3000))),
    92  			"FSGroup":      gstruct.PointTo(Equal(int64(2000))),
    93  			"RunAsNonRoot": gstruct.PointTo(Equal(true)),
    94  		}))
    95  		// init near node
    96  		Expect(fetched.Spec.Template.Spec.InitContainers[0].Name).To(Equal("init-near-node"))
    97  		Expect(fetched.Spec.Template.Spec.InitContainers[0].Image).To(Equal(testImage))
    98  		Expect(fetched.Spec.Template.Spec.InitContainers[0].Command).To(Equal([]string{"/bin/sh"}))
    99  		Expect(fetched.Spec.Template.Spec.InitContainers[0].Args).To(ContainElements(
   100  			fmt.Sprintf("%s/init_near_node.sh", shared.PathConfig(client.HomeDir())),
   101  		))
   102  		Expect(fetched.Spec.Template.Spec.InitContainers[0].Env).To(ContainElements(
   103  			corev1.EnvVar{
   104  				Name:  shared.EnvDataPath,
   105  				Value: shared.PathData(client.HomeDir()),
   106  			},
   107  			corev1.EnvVar{
   108  				Name:  envNetwork,
   109  				Value: toCreate.Spec.Network,
   110  			},
   111  		))
   112  		Expect(fetched.Spec.Template.Spec.InitContainers[0].VolumeMounts).To(ContainElements(
   113  			corev1.VolumeMount{
   114  				Name:      "data",
   115  				MountPath: shared.PathData(client.HomeDir()),
   116  			},
   117  			corev1.VolumeMount{
   118  				Name:      "config",
   119  				MountPath: shared.PathConfig(client.HomeDir()),
   120  			},
   121  			corev1.VolumeMount{
   122  				Name:      "secrets",
   123  				MountPath: shared.PathSecrets(client.HomeDir()),
   124  			},
   125  		))
   126  		// copy node key
   127  		Expect(fetched.Spec.Template.Spec.InitContainers[1].Name).To(Equal("copy-node-key"))
   128  		Expect(fetched.Spec.Template.Spec.InitContainers[1].Image).To(Equal(shared.BusyboxImage))
   129  		Expect(fetched.Spec.Template.Spec.InitContainers[1].Command).To(Equal([]string{"/bin/sh"}))
   130  		Expect(fetched.Spec.Template.Spec.InitContainers[1].Args).To(ContainElements(
   131  			fmt.Sprintf("%s/copy_node_key.sh", shared.PathConfig(client.HomeDir())),
   132  		))
   133  		Expect(fetched.Spec.Template.Spec.InitContainers[1].Env).To(ContainElements(
   134  			corev1.EnvVar{
   135  				Name:  shared.EnvDataPath,
   136  				Value: shared.PathData(client.HomeDir()),
   137  			},
   138  			corev1.EnvVar{
   139  				Name:  shared.EnvSecretsPath,
   140  				Value: shared.PathSecrets(client.HomeDir()),
   141  			},
   142  		))
   143  		Expect(fetched.Spec.Template.Spec.InitContainers[1].VolumeMounts).To(ContainElements(
   144  			corev1.VolumeMount{
   145  				Name:      "data",
   146  				MountPath: shared.PathData(client.HomeDir()),
   147  			},
   148  			corev1.VolumeMount{
   149  				Name:      "config",
   150  				MountPath: shared.PathConfig(client.HomeDir()),
   151  			},
   152  			corev1.VolumeMount{
   153  				Name:      "secrets",
   154  				MountPath: shared.PathSecrets(client.HomeDir()),
   155  			},
   156  		))
   157  		// copy validator key
   158  		Expect(fetched.Spec.Template.Spec.InitContainers[2].Name).To(Equal("copy-validator-key"))
   159  		Expect(fetched.Spec.Template.Spec.InitContainers[2].Image).To(Equal(shared.BusyboxImage))
   160  		Expect(fetched.Spec.Template.Spec.InitContainers[2].Command).To(Equal([]string{"/bin/sh"}))
   161  		Expect(fetched.Spec.Template.Spec.InitContainers[2].Args).To(ContainElements(
   162  			fmt.Sprintf("%s/copy_validator_key.sh", shared.PathConfig(client.HomeDir())),
   163  		))
   164  		Expect(fetched.Spec.Template.Spec.InitContainers[2].Env).To(ContainElements(
   165  			corev1.EnvVar{
   166  				Name:  shared.EnvDataPath,
   167  				Value: shared.PathData(client.HomeDir()),
   168  			},
   169  			corev1.EnvVar{
   170  				Name:  shared.EnvSecretsPath,
   171  				Value: shared.PathSecrets(client.HomeDir()),
   172  			},
   173  		))
   174  		Expect(fetched.Spec.Template.Spec.InitContainers[2].VolumeMounts).To(ContainElements(
   175  			corev1.VolumeMount{
   176  				Name:      "data",
   177  				MountPath: shared.PathData(client.HomeDir()),
   178  			},
   179  			corev1.VolumeMount{
   180  				Name:      "config",
   181  				MountPath: shared.PathConfig(client.HomeDir()),
   182  			},
   183  			corev1.VolumeMount{
   184  				Name:      "secrets",
   185  				MountPath: shared.PathSecrets(client.HomeDir()),
   186  			},
   187  		))
   188  		// node
   189  		Expect(fetched.Spec.Template.Spec.Containers[0].Name).To(Equal("node"))
   190  		Expect(fetched.Spec.Template.Spec.Containers[0].Image).To(Equal(testImage))
   191  		Expect(fetched.Spec.Template.Spec.Containers[0].Command).To(Equal(client.Command()))
   192  		Expect(fetched.Spec.Template.Spec.Containers[0].Args).To(Equal(client.Args()))
   193  		Expect(fetched.Spec.Template.Spec.Containers[0].VolumeMounts).To(ContainElements(
   194  			corev1.VolumeMount{
   195  				Name:      "data",
   196  				MountPath: shared.PathData(client.HomeDir()),
   197  			},
   198  			corev1.VolumeMount{
   199  				Name:      "config",
   200  				MountPath: shared.PathConfig(client.HomeDir()),
   201  			},
   202  			corev1.VolumeMount{
   203  				Name:      "secrets",
   204  				MountPath: shared.PathSecrets(client.HomeDir()),
   205  			},
   206  		))
   207  		// volumes
   208  		mode := corev1.ConfigMapVolumeSourceDefaultMode
   209  		Expect(fetched.Spec.Template.Spec.Volumes).To(ContainElements(
   210  			[]corev1.Volume{
   211  				{
   212  					Name: "data",
   213  					VolumeSource: corev1.VolumeSource{
   214  						PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
   215  							ClaimName: toCreate.Name,
   216  						},
   217  					},
   218  				},
   219  				{
   220  					Name: "config",
   221  					VolumeSource: corev1.VolumeSource{
   222  						ConfigMap: &corev1.ConfigMapVolumeSource{
   223  							LocalObjectReference: corev1.LocalObjectReference{
   224  								Name: toCreate.Name,
   225  							},
   226  							DefaultMode: &mode,
   227  						},
   228  					},
   229  				},
   230  				{
   231  					Name: "secrets",
   232  					VolumeSource: corev1.VolumeSource{
   233  						Projected: &corev1.ProjectedVolumeSource{
   234  							Sources: []corev1.VolumeProjection{
   235  								{
   236  									Secret: &corev1.SecretProjection{
   237  										LocalObjectReference: corev1.LocalObjectReference{
   238  											Name: toCreate.Spec.NodePrivateKeySecretName,
   239  										},
   240  										Items: []corev1.KeyToPath{
   241  											{
   242  												Key:  "key",
   243  												Path: "node_key.json",
   244  											},
   245  										},
   246  									},
   247  								},
   248  								{
   249  									Secret: &corev1.SecretProjection{
   250  										LocalObjectReference: corev1.LocalObjectReference{
   251  											Name: toCreate.Spec.ValidatorSecretName,
   252  										},
   253  										Items: []corev1.KeyToPath{
   254  											{
   255  												Key:  "key",
   256  												Path: "validator_key.json",
   257  											},
   258  										},
   259  									},
   260  								},
   261  							},
   262  							DefaultMode: &mode,
   263  						},
   264  					},
   265  				},
   266  			},
   267  		))
   268  	})
   269  
   270  	It("Should create allocate correct resources to peer statefulset", func() {
   271  		fetched := &appsv1.StatefulSet{}
   272  		expectedResources := corev1.ResourceRequirements{
   273  			Requests: corev1.ResourceList{
   274  				corev1.ResourceCPU:    resource.MustParse(nearv1alpha1.DefaultNodeCPURequest),
   275  				corev1.ResourceMemory: resource.MustParse(nearv1alpha1.DefaultNodeMemoryRequest),
   276  			},
   277  			Limits: corev1.ResourceList{
   278  				corev1.ResourceCPU:    resource.MustParse(nearv1alpha1.DefaultNodeCPULimit),
   279  				corev1.ResourceMemory: resource.MustParse(nearv1alpha1.DefaultNodeMemoryLimit),
   280  			},
   281  		}
   282  		Expect(k8sClient.Get(context.Background(), key, fetched)).To(Succeed())
   283  		Expect(fetched.Spec.Template.Spec.Containers[0].Resources).To(Equal(expectedResources))
   284  	})
   285  
   286  	It("Should create node configmap", func() {
   287  		fetched := &corev1.ConfigMap{}
   288  		Expect(k8sClient.Get(context.Background(), key, fetched)).To(Succeed())
   289  		Expect(fetched.OwnerReferences).To(ContainElements(nodeOwnerReference))
   290  		Expect(fetched.Data).To(HaveKey("init_near_node.sh"))
   291  		Expect(fetched.Data).To(HaveKey("copy_node_key.sh"))
   292  
   293  	})
   294  
   295  	It("Should create node data persistent volume with correct resources", func() {
   296  		fetched := &corev1.PersistentVolumeClaim{}
   297  		Expect(k8sClient.Get(context.Background(), key, fetched)).To(Succeed())
   298  		Expect(fetched.OwnerReferences).To(ContainElements(nodeOwnerReference))
   299  		expectedResources := corev1.VolumeResourceRequirements{
   300  			Requests: corev1.ResourceList{
   301  				corev1.ResourceStorage: resource.MustParse(nearv1alpha1.DefaultArchivalNodeStorageRequest),
   302  			},
   303  		}
   304  		Expect(fetched.Spec.Resources).To(Equal(expectedResources))
   305  	})
   306  
   307  	It("Should create node service", func() {
   308  		fetched := &corev1.Service{}
   309  		Expect(k8sClient.Get(context.Background(), key, fetched)).To(Succeed())
   310  		Expect(fetched.OwnerReferences).To(ContainElements(nodeOwnerReference))
   311  		Expect(fetched.Spec.Ports).To(ContainElements(
   312  			[]corev1.ServicePort{
   313  				{
   314  					Name:       "p2p",
   315  					Port:       int32(nearv1alpha1.DefaultP2PPort),
   316  					TargetPort: intstr.FromString("p2p"),
   317  					Protocol:   corev1.ProtocolTCP,
   318  				},
   319  				{
   320  					Name:       "discovery",
   321  					Port:       int32(nearv1alpha1.DefaultP2PPort),
   322  					TargetPort: intstr.FromString("discovery"),
   323  					Protocol:   corev1.ProtocolUDP,
   324  				},
   325  				{
   326  					Name:       "prometheus",
   327  					Port:       int32(nearv1alpha1.DefaultPrometheusPort),
   328  					TargetPort: intstr.FromString("prometheus"),
   329  					Protocol:   corev1.ProtocolTCP,
   330  				},
   331  				{
   332  					Name:       "rpc",
   333  					Port:       int32(nearv1alpha1.DefaultRPCPort),
   334  					TargetPort: intstr.FromString("rpc"),
   335  					Protocol:   corev1.ProtocolTCP,
   336  				},
   337  			},
   338  		))
   339  	})
   340  
   341  	It(fmt.Sprintf("Should delete %s namespace", ns.Name), func() {
   342  		Expect(k8sClient.Delete(context.Background(), ns)).To(Succeed())
   343  	})
   344  
   345  })