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 })