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

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