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

     1  package controllers
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"time"
     8  
     9  	chainlinkv1alpha1 "github.com/kotalco/kotal/apis/chainlink/v1alpha1"
    10  	chainlinkClients "github.com/kotalco/kotal/clients/chainlink"
    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("Chainlink node controller", func() {
    24  	ns := &corev1.Namespace{
    25  		ObjectMeta: metav1.ObjectMeta{
    26  			Name: "chainlink",
    27  		},
    28  	}
    29  
    30  	key := types.NamespacedName{
    31  		Name:      "chainlink-node",
    32  		Namespace: ns.Name,
    33  	}
    34  
    35  	testImage := "kotalco/chainlink:controller-test"
    36  
    37  	spec := chainlinkv1alpha1.NodeSpec{
    38  		Image:                      testImage,
    39  		EthereumChainId:            1,
    40  		EthereumWSEndpoint:         "wss://my-eth-node:8546",
    41  		LinkContractAddress:        "0x01BE23585060835E02B77ef475b0Cc51aA1e0709",
    42  		DatabaseURL:                "postgresql://postgres:password@postgres:5432/postgres",
    43  		KeystorePasswordSecretName: "keystore-password",
    44  		API:                        true,
    45  		APICredentials: chainlinkv1alpha1.APICredentials{
    46  			Email:              "mostafa@kotal.co",
    47  			PasswordSecretName: "api-password",
    48  		},
    49  	}
    50  
    51  	toCreate := &chainlinkv1alpha1.Node{
    52  		ObjectMeta: metav1.ObjectMeta{
    53  			Name:      key.Name,
    54  			Namespace: key.Namespace,
    55  		},
    56  		Spec: spec,
    57  	}
    58  
    59  	client := chainlinkClients.NewClient(toCreate)
    60  
    61  	t := true
    62  
    63  	nodeOwnerReference := metav1.OwnerReference{
    64  		APIVersion:         "chainlink.kotal.io/v1alpha1",
    65  		Kind:               "Node",
    66  		Name:               toCreate.Name,
    67  		Controller:         &t,
    68  		BlockOwnerDeletion: &t,
    69  	}
    70  
    71  	It(fmt.Sprintf("Should create %s namespace", ns.Name), func() {
    72  		Expect(k8sClient.Create(context.TODO(), ns)).To(Succeed())
    73  	})
    74  
    75  	It("Should create keystore password secret", func() {
    76  		secret := corev1.Secret{
    77  			ObjectMeta: metav1.ObjectMeta{
    78  				Name:      "keystore-password",
    79  				Namespace: ns.Name,
    80  			},
    81  			StringData: map[string]string{
    82  				"password": "99%OfBlockchainNodesRunOnAWS",
    83  			},
    84  		}
    85  		Expect(k8sClient.Create(context.Background(), &secret)).To(Succeed())
    86  	})
    87  
    88  	It("should create chainlink node", func() {
    89  		if os.Getenv(shared.EnvUseExistingCluster) != "true" {
    90  			toCreate.Default()
    91  		}
    92  		Expect(k8sClient.Create(context.Background(), toCreate)).Should(Succeed())
    93  	})
    94  
    95  	It("Should get chainlink node", func() {
    96  		fetched := &chainlinkv1alpha1.Node{}
    97  		Expect(k8sClient.Get(context.Background(), key, fetched)).To(Succeed())
    98  		Expect(fetched.Spec).To(Equal(toCreate.Spec))
    99  		nodeOwnerReference.UID = fetched.UID
   100  		time.Sleep(5 * time.Second)
   101  	})
   102  
   103  	It("Should create node statefulset", func() {
   104  		fetched := &appsv1.StatefulSet{}
   105  		Expect(k8sClient.Get(context.Background(), key, fetched)).To(Succeed())
   106  		Expect(fetched.OwnerReferences).To(ContainElements(nodeOwnerReference))
   107  		Expect(*fetched.Spec.Template.Spec.SecurityContext).To(gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
   108  			"RunAsUser":    gstruct.PointTo(Equal(int64(1000))),
   109  			"RunAsGroup":   gstruct.PointTo(Equal(int64(3000))),
   110  			"FSGroup":      gstruct.PointTo(Equal(int64(2000))),
   111  			"RunAsNonRoot": gstruct.PointTo(Equal(true)),
   112  		}))
   113  		// init container
   114  		Expect(fetched.Spec.Template.Spec.InitContainers[0].Image).To(Equal(shared.BusyboxImage))
   115  		Expect(fetched.Spec.Template.Spec.InitContainers[0].Command).To(ConsistOf("/bin/sh"))
   116  		Expect(fetched.Spec.Template.Spec.InitContainers[0].Args).To(ConsistOf(
   117  			fmt.Sprintf("%s/copy_api_credentials.sh", shared.PathConfig(client.HomeDir())),
   118  		))
   119  		Expect(fetched.Spec.Template.Spec.InitContainers[0].Env).To(ContainElements(
   120  			corev1.EnvVar{
   121  				Name:  shared.EnvDataPath,
   122  				Value: shared.PathData(client.HomeDir()),
   123  			},
   124  			corev1.EnvVar{
   125  				Name:  envApiEmail,
   126  				Value: toCreate.Spec.APICredentials.Email,
   127  			},
   128  			corev1.EnvVar{
   129  				Name:  shared.EnvSecretsPath,
   130  				Value: shared.PathSecrets(client.HomeDir()),
   131  			},
   132  		))
   133  		Expect(fetched.Spec.Template.Spec.InitContainers[0].VolumeMounts).To(ContainElements(
   134  			corev1.VolumeMount{
   135  				Name:      "data",
   136  				MountPath: client.HomeDir(),
   137  			},
   138  			corev1.VolumeMount{
   139  				Name:      "config",
   140  				MountPath: shared.PathConfig(client.HomeDir()),
   141  			},
   142  			corev1.VolumeMount{
   143  				Name:      "secrets",
   144  				MountPath: shared.PathSecrets(client.HomeDir()),
   145  			},
   146  		))
   147  		// node container
   148  		Expect(fetched.Spec.Template.Spec.Containers[0].Image).To(Equal(testImage))
   149  		Expect(fetched.Spec.Template.Spec.Containers[0].Command).To(Equal(client.Command()))
   150  		Expect(fetched.Spec.Template.Spec.Containers[0].Args).To(Equal(client.Args()))
   151  		Expect(fetched.Spec.Template.Spec.Containers[0].Env).To(Equal(client.Env()))
   152  		Expect(fetched.Spec.Template.Spec.InitContainers[0].VolumeMounts).To(ContainElements(
   153  			corev1.VolumeMount{
   154  				Name:      "data",
   155  				MountPath: client.HomeDir(),
   156  			},
   157  			corev1.VolumeMount{
   158  				Name:      "secrets",
   159  				MountPath: shared.PathSecrets(client.HomeDir()),
   160  			},
   161  		))
   162  	})
   163  
   164  	It("Should create allocate correct resources to peer statefulset", func() {
   165  		fetched := &appsv1.StatefulSet{}
   166  		expectedResources := corev1.ResourceRequirements{
   167  			Requests: corev1.ResourceList{
   168  				corev1.ResourceCPU:    resource.MustParse(chainlinkv1alpha1.DefaultNodeCPURequest),
   169  				corev1.ResourceMemory: resource.MustParse(chainlinkv1alpha1.DefaultNodeMemoryRequest),
   170  			},
   171  			Limits: corev1.ResourceList{
   172  				corev1.ResourceCPU:    resource.MustParse(chainlinkv1alpha1.DefaultNodeCPULimit),
   173  				corev1.ResourceMemory: resource.MustParse(chainlinkv1alpha1.DefaultNodeMemoryLimit),
   174  			},
   175  		}
   176  		Expect(k8sClient.Get(context.Background(), key, fetched)).To(Succeed())
   177  		Expect(fetched.Spec.Template.Spec.Containers[0].Resources).To(Equal(expectedResources))
   178  	})
   179  
   180  	It("Should create node configmap", func() {
   181  		fetched := &corev1.ConfigMap{}
   182  		Expect(k8sClient.Get(context.Background(), key, fetched)).To(Succeed())
   183  		Expect(fetched.OwnerReferences).To(ContainElements(nodeOwnerReference))
   184  		Expect(fetched.Data).To(HaveKey("copy_api_credentials.sh"))
   185  
   186  	})
   187  
   188  	It("Should create node service", func() {
   189  		fetched := &corev1.Service{}
   190  		Expect(k8sClient.Get(context.Background(), key, fetched)).To(Succeed())
   191  		Expect(fetched.OwnerReferences).To(ContainElements(nodeOwnerReference))
   192  		Expect(fetched.Spec.Ports).To(ContainElements(
   193  			[]corev1.ServicePort{
   194  				{
   195  					Name:       "p2p",
   196  					Port:       int32(toCreate.Spec.P2PPort),
   197  					TargetPort: intstr.FromString("p2p"),
   198  					Protocol:   corev1.ProtocolTCP,
   199  				},
   200  				{
   201  					Name:       "api",
   202  					Port:       int32(toCreate.Spec.APIPort),
   203  					TargetPort: intstr.FromString("api"),
   204  					Protocol:   corev1.ProtocolTCP,
   205  				},
   206  			},
   207  		))
   208  
   209  	})
   210  
   211  	It("Should create node data persistent volume with correct resources", func() {
   212  		fetched := &corev1.PersistentVolumeClaim{}
   213  		Expect(k8sClient.Get(context.Background(), key, fetched)).To(Succeed())
   214  		Expect(fetched.OwnerReferences).To(ContainElements(nodeOwnerReference))
   215  		expectedResources := corev1.VolumeResourceRequirements{
   216  			Requests: corev1.ResourceList{
   217  				corev1.ResourceStorage: resource.MustParse(chainlinkv1alpha1.DefaultNodeStorageRequest),
   218  			},
   219  		}
   220  		Expect(fetched.Spec.Resources).To(Equal(expectedResources))
   221  	})
   222  
   223  	It(fmt.Sprintf("Should delete %s namespace", ns.Name), func() {
   224  		Expect(k8sClient.Delete(context.Background(), ns)).To(Succeed())
   225  	})
   226  
   227  })