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 }