sigs.k8s.io/cluster-api-provider-aws@v1.5.5/bootstrap/eks/controllers/eksconfig_controller_reconciler_test.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package controllers
    18  
    19  import (
    20  	"fmt"
    21  	"testing"
    22  
    23  	. "github.com/onsi/gomega"
    24  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/types"
    27  	"sigs.k8s.io/controller-runtime/pkg/client"
    28  	"sigs.k8s.io/yaml"
    29  
    30  	eksbootstrapv1 "sigs.k8s.io/cluster-api-provider-aws/bootstrap/eks/api/v1beta1"
    31  	"sigs.k8s.io/cluster-api-provider-aws/bootstrap/eks/internal/userdata"
    32  	ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/controlplane/eks/api/v1beta1"
    33  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    34  	"sigs.k8s.io/cluster-api/util"
    35  	"sigs.k8s.io/cluster-api/util/conditions"
    36  )
    37  
    38  func TestEKSConfigReconciler(t *testing.T) {
    39  	t.Run("Should reconcile an EKSConfig and create data Secret", func(t *testing.T) {
    40  		g := NewWithT(t)
    41  		amcp := newAMCP("test-cluster")
    42  		cluster := newCluster(amcp.Name)
    43  		machine := newMachine(cluster, "test-machine")
    44  		config := newEKSConfig(machine)
    45  		t.Log(dump("amcp", amcp))
    46  		t.Log(dump("config", config))
    47  		t.Log(dump("machine", machine))
    48  		t.Log(dump("cluster", cluster))
    49  		expectedUserData, err := newUserData(cluster.Name, map[string]string{"test-arg": "test-value"})
    50  		g.Expect(err).To(BeNil())
    51  		g.Expect(testEnv.Client.Create(ctx, amcp)).To(Succeed())
    52  
    53  		reconciler := EKSConfigReconciler{
    54  			Client: testEnv.Client,
    55  		}
    56  		t.Logf(fmt.Sprintf("Calling reconcile on cluster '%s' and config '%s' should requeue", cluster.Name, config.Name))
    57  		g.Eventually(func(gomega Gomega) {
    58  			result, err := reconciler.joinWorker(ctx, cluster, config)
    59  			gomega.Expect(err).NotTo(HaveOccurred())
    60  			gomega.Expect(result.Requeue).To(BeFalse())
    61  		}).Should(Succeed())
    62  
    63  		t.Logf(fmt.Sprintf("Secret '%s' should exist and be correct", config.Name))
    64  		secretList := &corev1.SecretList{}
    65  		testEnv.Client.List(ctx, secretList)
    66  		t.Logf(dump("secrets", secretList))
    67  		secret := &corev1.Secret{}
    68  		g.Eventually(func(gomega Gomega) {
    69  			gomega.Expect(testEnv.Client.Get(ctx, client.ObjectKey{
    70  				Name:      config.Name,
    71  				Namespace: "default",
    72  			}, secret)).To(Succeed())
    73  		}).Should(Succeed())
    74  
    75  		g.Expect(string(secret.Data["value"])).To(Equal(string(expectedUserData)))
    76  	})
    77  
    78  	t.Run("Should reconcile an EKSConfig and update data Secret", func(t *testing.T) {
    79  		g := NewWithT(t)
    80  		amcp := newAMCP("test-cluster")
    81  		cluster := newCluster(amcp.Name)
    82  		machine := newMachine(cluster, "test-machine")
    83  		config := newEKSConfig(machine)
    84  		t.Logf(dump("amcp", amcp))
    85  		t.Logf(dump("config", config))
    86  		t.Logf(dump("machine", machine))
    87  		t.Logf(dump("cluster", cluster))
    88  		oldUserData, err := newUserData(cluster.Name, map[string]string{"test-arg": "test-value"})
    89  		g.Expect(err).To(BeNil())
    90  		expectedUserData, err := newUserData(cluster.Name, map[string]string{"test-arg": "updated-test-value"})
    91  		g.Expect(err).To(BeNil())
    92  		g.Expect(testEnv.Client.Create(ctx, amcp)).To(Succeed())
    93  
    94  		amcpList := &ekscontrolplanev1.AWSManagedControlPlaneList{}
    95  		testEnv.Client.List(ctx, amcpList)
    96  		t.Logf(dump("stored-amcps", amcpList))
    97  
    98  		reconciler := EKSConfigReconciler{
    99  			Client: testEnv.Client,
   100  		}
   101  		t.Logf(fmt.Sprintf("Calling reconcile on cluster '%s' and config '%s' should requeue", cluster.Name, config.Name))
   102  		g.Eventually(func(gomega Gomega) {
   103  			result, err := reconciler.joinWorker(ctx, cluster, config)
   104  			gomega.Expect(err).NotTo(HaveOccurred())
   105  			gomega.Expect(result.Requeue).To(BeFalse())
   106  		}).Should(Succeed())
   107  
   108  		t.Logf(fmt.Sprintf("Secret '%s' should exist and be correct", config.Name))
   109  		secretList := &corev1.SecretList{}
   110  		testEnv.Client.List(ctx, secretList)
   111  		t.Logf(dump("secrets", secretList))
   112  
   113  		secret := &corev1.Secret{}
   114  		g.Eventually(func(gomega Gomega) {
   115  			gomega.Expect(testEnv.Client.Get(ctx, client.ObjectKey{
   116  				Name:      config.Name,
   117  				Namespace: "default",
   118  			}, secret)).To(Succeed())
   119  			gomega.Expect(string(secret.Data["value"])).To(Equal(string(oldUserData)))
   120  		}).Should(Succeed())
   121  
   122  		// Secret already exists in testEnv so we update it
   123  		config.Spec.KubeletExtraArgs = map[string]string{
   124  			"test-arg": "updated-test-value",
   125  		}
   126  		t.Logf(dump("config", config))
   127  		g.Eventually(func(gomega Gomega) {
   128  			result, err := reconciler.joinWorker(ctx, cluster, config)
   129  			gomega.Expect(err).NotTo(HaveOccurred())
   130  			gomega.Expect(result.Requeue).To(BeFalse())
   131  		}).Should(Succeed())
   132  		t.Logf(fmt.Sprintf("Secret '%s' should exist and be up to date", config.Name))
   133  
   134  		testEnv.Client.List(ctx, secretList)
   135  		t.Logf(dump("secrets", secretList))
   136  		g.Eventually(func(gomega Gomega) {
   137  			gomega.Expect(testEnv.Client.Get(ctx, client.ObjectKey{
   138  				Name:      config.Name,
   139  				Namespace: "default",
   140  			}, secret)).To(Succeed())
   141  			gomega.Expect(string(secret.Data["value"])).To(Equal(string(expectedUserData)))
   142  		}).Should(Succeed())
   143  	})
   144  }
   145  
   146  // newCluster return a CAPI cluster object.
   147  func newCluster(name string) *clusterv1.Cluster {
   148  	cluster := &clusterv1.Cluster{
   149  		TypeMeta: metav1.TypeMeta{
   150  			Kind:       "Cluster",
   151  			APIVersion: clusterv1.GroupVersion.String(),
   152  		},
   153  		ObjectMeta: metav1.ObjectMeta{
   154  			Namespace: "default",
   155  			Name:      name,
   156  		},
   157  		Spec: clusterv1.ClusterSpec{
   158  			ControlPlaneRef: &corev1.ObjectReference{
   159  				Name:      name,
   160  				Kind:      "AWSManagedControlPlane",
   161  				Namespace: "default",
   162  			},
   163  		},
   164  		Status: clusterv1.ClusterStatus{
   165  			InfrastructureReady: true,
   166  		},
   167  	}
   168  	conditions.MarkTrue(cluster, clusterv1.ControlPlaneInitializedCondition)
   169  	return cluster
   170  }
   171  
   172  func dump(desc string, o interface{}) string {
   173  	dat, _ := yaml.Marshal(o)
   174  	return fmt.Sprintf("%s:\n%s", desc, string(dat))
   175  }
   176  
   177  // newMachine return a CAPI machine object; if cluster is not nil, the machine is linked to the cluster as well.
   178  func newMachine(cluster *clusterv1.Cluster, name string) *clusterv1.Machine {
   179  	generatedName := fmt.Sprintf("%s-%s", name, util.RandomString(5))
   180  	machine := &clusterv1.Machine{
   181  		TypeMeta: metav1.TypeMeta{
   182  			Kind:       "Machine",
   183  			APIVersion: clusterv1.GroupVersion.String(),
   184  		},
   185  		ObjectMeta: metav1.ObjectMeta{
   186  			Namespace: "default",
   187  			Name:      generatedName,
   188  		},
   189  		Spec: clusterv1.MachineSpec{
   190  			Bootstrap: clusterv1.Bootstrap{
   191  				ConfigRef: &corev1.ObjectReference{
   192  					Kind:       "EKSConfig",
   193  					APIVersion: eksbootstrapv1.GroupVersion.String(),
   194  				},
   195  			},
   196  		},
   197  	}
   198  	if cluster != nil {
   199  		machine.Spec.ClusterName = cluster.Name
   200  		machine.ObjectMeta.Labels = map[string]string{
   201  			clusterv1.ClusterLabelName: cluster.Name,
   202  		}
   203  	}
   204  	return machine
   205  }
   206  
   207  // newEKSConfig return an EKSConfig object; if machine is not nil, the EKSConfig is linked to the machine as well.
   208  func newEKSConfig(machine *clusterv1.Machine) *eksbootstrapv1.EKSConfig {
   209  	config := &eksbootstrapv1.EKSConfig{
   210  		TypeMeta: metav1.TypeMeta{
   211  			Kind:       "EKSConfig",
   212  			APIVersion: eksbootstrapv1.GroupVersion.String(),
   213  		},
   214  		ObjectMeta: metav1.ObjectMeta{
   215  			Namespace: "default",
   216  		},
   217  		Spec: eksbootstrapv1.EKSConfigSpec{
   218  			KubeletExtraArgs: map[string]string{
   219  				"test-arg": "test-value",
   220  			},
   221  		},
   222  	}
   223  	if machine != nil {
   224  		config.ObjectMeta.Name = machine.Name
   225  		config.ObjectMeta.UID = types.UID(fmt.Sprintf("%s uid", machine.Name))
   226  		config.ObjectMeta.OwnerReferences = []metav1.OwnerReference{
   227  			{
   228  				Kind:       "Machine",
   229  				APIVersion: clusterv1.GroupVersion.String(),
   230  				Name:       machine.Name,
   231  				UID:        types.UID(fmt.Sprintf("%s uid", machine.Name)),
   232  			},
   233  		}
   234  		machine.Spec.Bootstrap.ConfigRef.Name = config.Name
   235  		machine.Spec.Bootstrap.ConfigRef.Namespace = config.Namespace
   236  	}
   237  	return config
   238  }
   239  
   240  // newUserData generates user data ready to be passed to r.storeBootstrapData.
   241  func newUserData(clusterName string, kubeletExtraArgs map[string]string) ([]byte, error) {
   242  	return userdata.NewNode(&userdata.NodeInput{
   243  		ClusterName:      clusterName,
   244  		KubeletExtraArgs: kubeletExtraArgs,
   245  	})
   246  }
   247  
   248  // newAMCP returns an EKS AWSManagedControlPlane object.
   249  func newAMCP(name string) *ekscontrolplanev1.AWSManagedControlPlane {
   250  	generatedName := fmt.Sprintf("%s-%s", name, util.RandomString(5))
   251  	return &ekscontrolplanev1.AWSManagedControlPlane{
   252  		TypeMeta: metav1.TypeMeta{
   253  			Kind:       "AWSManagedControlPlane",
   254  			APIVersion: ekscontrolplanev1.GroupVersion.String(),
   255  		},
   256  		ObjectMeta: metav1.ObjectMeta{
   257  			Name:      generatedName,
   258  			Namespace: "default",
   259  		},
   260  		Spec: ekscontrolplanev1.AWSManagedControlPlaneSpec{
   261  			EKSClusterName: generatedName,
   262  		},
   263  	}
   264  }