Repository: kubernetes-sigs/cluster-api-provider-aws
Pull request #2961 (2021-11-16 14:54:26)

[backport 0.7] Fixes an issue with using multiple EKS addons.
**What type of PR is this?**

/kind bug

**What this PR does / why we need it**:

When the EKS addons functionality was added to CAPA there was only 1
addon available (vpc) and so it was never tested with multiple addons.
Now there are multiple addons there are issues.

The unit and e2e tests only tested 1 addons. On investigation the issue
was caused by implicit memory aliasing in a loop. This change fixes the
loop and also expands the unit and e2e tests to include multiple eks
addons

Backport of #2914 

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Relates #2911 

**Special notes for your reviewer**:

**Checklist**:
<!-- Put an "X" character inside the brackets of each completed task. Some may be optional depending on the PR in which case these can be deleted -->

- [x] squashed commits
- [x] adds unit tests
- [x] adds or updates e2e tests

**Release note**:
<!--  Write your release note:
1. Enter your extended release note in the below block. 
2. If the PR requires additional action from users switching to the new release, include the string "action required".
2. If no release note is required, just write "NONE"....however we encourage contributors to never use this as release notes are incredible useful.
-->
```release-note
Fixes an issue with using multiple EKS addons.
```


k8s-ci-robot: @richardcase: This issue is currently awaiting triage.

If CAPA/CAPI contributors determines this is a relevant issue, they will accept it by applying the `triage/accepted` label and provide further guidance.

The `triage/accepted` label can be added by org members by writing `/triage accepted` in a comment.


<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
k8s-ci-robot: @richardcase: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-aws-verify-release-0-7 | 7f427ffd63b6db1e313d12bff4378e4eb62127c9 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/2961/pull-cluster-api-provider-aws-verify-release-0-7/1460622298775031808) | true | `/test pull-cluster-api-provider-aws-verify-release-0-7`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-aws&pr=2961). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Arichardcase). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please ask for approval from **richardcase** after the PR has been reviewed.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcluster-api-provider-aws).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/cluster-api-provider-aws/blob/release-0.7/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["richardcase"]} -->
richardcase: /retitle [backport] fix: support more than 1 eks addon

Patch:
diff --git a/.gitignore b/.gitignore
index 24ec630d9..84740d8e7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@ bin
 # envfiles
 .env
 envfile
+.envrc
 
 # kubeconfigs
 kind.kubeconfig
diff --git a/pkg/cloud/services/eks/addons.go b/pkg/cloud/services/eks/addons.go
index 2d276c152..11867f61b 100644
--- a/pkg/cloud/services/eks/addons.go
+++ b/pkg/cloud/services/eks/addons.go
@@ -191,7 +191,8 @@ func (s *Service) listAddons(eksClusterName string) ([]*string, error) {
 func (s *Service) translateAPIToAddon(addons []ekscontrolplanev1.Addon) []*eksaddons.EKSAddon {
 	converted := []*eksaddons.EKSAddon{}
 
-	for _, addon := range addons {
+	for i := range addons {
+		addon := addons[i]
 		convertedAddon := &eksaddons.EKSAddon{
 			Name:                  &addon.Name,
 			Version:               &addon.Version,
diff --git a/pkg/cloud/services/ssm/secret.go b/pkg/cloud/services/ssm/secret.go
index d86687362..03ad741bc 100644
--- a/pkg/cloud/services/ssm/secret.go
+++ b/pkg/cloud/services/ssm/secret.go
@@ -93,10 +93,11 @@ func (s *Service) Create(m *scope.MachineScope, data []byte) (string, int32, err
 // retryableCreateSecret is a function to be passed into a waiter. In a separate function for ease of reading.
 func (s *Service) retryableCreateSecret(name string, chunk []byte, tags infrav1.Tags) (bool, error) {
 	_, err := s.SSMClient.PutParameter(&ssm.PutParameterInput{
-		Name:  aws.String(name),
-		Value: aws.String(string(chunk)),
-		Tags:  converters.MapToSSMTags(tags),
-		Type:  aws.String("SecureString"),
+		Name:     aws.String(name),
+		DataType: aws.String("text"),
+		Value:    aws.String(string(chunk)),
+		Tags:     converters.MapToSSMTags(tags),
+		Type:     aws.String("SecureString"),
 	})
 	if err != nil {
 		return false, err
diff --git a/pkg/eks/addons/plan.go b/pkg/eks/addons/plan.go
index 793b55481..e3c109aca 100644
--- a/pkg/eks/addons/plan.go
+++ b/pkg/eks/addons/plan.go
@@ -49,12 +49,13 @@ func (a *plan) Create(ctx context.Context) ([]planner.Procedure, error) {
 	procedures := []planner.Procedure{}
 
 	// Handle create and update
-	for _, desired := range a.desiredAddons {
+	for i := range a.desiredAddons {
+		desired := a.desiredAddons[i]
 		installed := a.getInstalled(*desired.Name)
 		if installed == nil {
 			// Need to add the addon
 			procedures = append(procedures, &CreateAddonProcedure{plan: a, name: *desired.Name})
-			procedures = append(procedures, &WaitAddonActiveProcedure{plan: a, name: *desired.Name})
+			procedures = append(procedures, &WaitAddonActiveProcedure{plan: a, name: *desired.Name, includeDegraded: true})
 		} else {
 			// Check if its just the tags that need updating
 			diffTags := desired.Tags.Difference(installed.Tags)
@@ -64,16 +65,17 @@ func (a *plan) Create(ctx context.Context) ([]planner.Procedure, error) {
 			// Check if we also need to update the addon
 			if !desired.IsEqual(installed, false) {
 				procedures = append(procedures, &UpdateAddonProcedure{plan: a, name: *installed.Name})
-				procedures = append(procedures, &WaitAddonActiveProcedure{plan: a, name: *desired.Name})
+				procedures = append(procedures, &WaitAddonActiveProcedure{plan: a, name: *desired.Name, includeDegraded: true})
 			} else if *installed.Status != eks.AddonStatusActive {
 				// If the desired and installed are the same make sure its active
-				procedures = append(procedures, &WaitAddonActiveProcedure{plan: a, name: *desired.Name})
+				procedures = append(procedures, &WaitAddonActiveProcedure{plan: a, name: *desired.Name, includeDegraded: true})
 			}
 		}
 	}
 
 	// look for deletions & unchanged
-	for _, installed := range a.installedAddons {
+	for i := range a.installedAddons {
+		installed := a.installedAddons[i]
 		desired := a.getDesired(*installed.Name)
 		if desired == nil {
 			if *installed.Status != eks.AddonStatusDeleting {
@@ -87,7 +89,8 @@ func (a *plan) Create(ctx context.Context) ([]planner.Procedure, error) {
 }
 
 func (a *plan) getInstalled(name string) *EKSAddon {
-	for _, installed := range a.installedAddons {
+	for i := range a.installedAddons {
+		installed := a.installedAddons[i]
 		if *installed.Name == name {
 			return installed
 		}
@@ -97,7 +100,8 @@ func (a *plan) getInstalled(name string) *EKSAddon {
 }
 
 func (a *plan) getDesired(name string) *EKSAddon {
-	for _, desired := range a.desiredAddons {
+	for i := range a.desiredAddons {
+		desired := a.desiredAddons[i]
 		if *desired.Name == name {
 			return desired
 		}
diff --git a/pkg/eks/addons/procedures.go b/pkg/eks/addons/procedures.go
index 10074223d..b2672539e 100644
--- a/pkg/eks/addons/procedures.go
+++ b/pkg/eks/addons/procedures.go
@@ -23,6 +23,7 @@ import (
 
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/service/eks"
+	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/wait"
 )
 
 var (
@@ -168,10 +169,12 @@ func (p *CreateAddonProcedure) Name() string {
 }
 
 // WaitAddonActiveProcedure is a procedure that will wait for an EKS addon
-// to be active in a cluster.
+// to be active in a cluster. Abd optionally include the degraded state.
+// Note: addons may be degraded until there are worker nodes.
 type WaitAddonActiveProcedure struct {
-	plan *plan
-	name string
+	plan            *plan
+	name            string
+	includeDegraded bool
 }
 
 // Do implements the logic for the procedure.
@@ -181,8 +184,23 @@ func (p *WaitAddonActiveProcedure) Do(ctx context.Context) error {
 		ClusterName: aws.String(p.plan.clusterName),
 	}
 
-	if err := p.plan.eksClient.WaitUntilAddonActive(input); err != nil {
-		return fmt.Errorf("waiting for addon %s to be active: %w", p.name, err)
+	if err := wait.WaitForWithRetryable(wait.NewBackoff(), func() (bool, error) {
+		out, describeErr := p.plan.eksClient.DescribeAddon(input)
+		if describeErr != nil {
+			return false, describeErr
+		}
+
+		if *out.Addon.Status == eks.AddonStatusActive {
+			return true, nil
+		}
+
+		if p.includeDegraded && *out.Addon.Status == eks.AddonStatusDegraded {
+			return true, nil
+		}
+
+		return false, nil
+	}); err != nil {
+		return fmt.Errorf("failed waiting for addon %s to be ready: %w", p.name, err)
 	}
 
 	return nil


Test patch:
diff --git a/pkg/eks/addons/plan_test.go b/pkg/eks/addons/plan_test.go
index 33a70c98b..3fe7e9df5 100644
--- a/pkg/eks/addons/plan_test.go
+++ b/pkg/eks/addons/plan_test.go
@@ -82,13 +82,85 @@ func TestEKSAddonPlan(t *testing.T) {
 						},
 					}, nil)
 
-				m.WaitUntilAddonActive(gomock.Eq(&eks.DescribeAddonInput{
+				out := &eks.DescribeAddonOutput{
+					Addon: &eks.Addon{
+						Status: aws.String(eks.AddonStatusActive),
+					},
+				}
+				m.DescribeAddon(gomock.Eq(&eks.DescribeAddonInput{
 					AddonName:   aws.String(addon1Name),
 					ClusterName: aws.String(clusterName),
-				})).Return(nil)
+				})).Return(out, nil)
+			},
+			desiredAddons: []*EKSAddon{
+				createDesiredAddon(addon1Name, addon1version),
+			},
+			expectCreateError: false,
+			expectDoError:     false,
+		},
+		{
+			name: "no installed and 2 desired",
+			expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {
+				m.
+					CreateAddon(gomock.Eq(&eks.CreateAddonInput{
+						AddonName:        aws.String(addon1Name),
+						AddonVersion:     aws.String(addon1version),
+						ClusterName:      aws.String(clusterName),
+						ResolveConflicts: aws.String(eks.ResolveConflictsOverwrite),
+						Tags:             convertTags(createTags()),
+					})).
+					Return(&eks.CreateAddonOutput{
+						Addon: &eks.Addon{
+							AddonArn:     aws.String(addonARN),
+							AddonName:    aws.String(addon1Name),
+							AddonVersion: aws.String(addon1version),
+							ClusterName:  aws.String(clusterName),
+							CreatedAt:    &created,
+							ModifiedAt:   &created,
+							Status:       aws.String(addonStatusCreating),
+							Tags:         convertTags(createTags()),
+						},
+					}, nil)
+
+				out := &eks.DescribeAddonOutput{
+					Addon: &eks.Addon{
+						Status: aws.String(eks.AddonStatusActive),
+					},
+				}
+				m.DescribeAddon(gomock.Eq(&eks.DescribeAddonInput{
+					AddonName:   aws.String(addon1Name),
+					ClusterName: aws.String(clusterName),
+				})).Return(out, nil)
+
+				m.
+					CreateAddon(gomock.Eq(&eks.CreateAddonInput{
+						AddonName:        aws.String("addon2"),
+						AddonVersion:     aws.String(addon1version),
+						ClusterName:      aws.String(clusterName),
+						ResolveConflicts: aws.String(eks.ResolveConflictsOverwrite),
+						Tags:             convertTags(createTags()),
+					})).
+					Return(&eks.CreateAddonOutput{
+						Addon: &eks.Addon{
+							AddonArn:     aws.String(addonARN),
+							AddonName:    aws.String("addon2"),
+							AddonVersion: aws.String(addon1version),
+							ClusterName:  aws.String(clusterName),
+							CreatedAt:    &created,
+							ModifiedAt:   &created,
+							Status:       aws.String(addonStatusCreating),
+							Tags:         convertTags(createTags()),
+						},
+					}, nil)
+
+				m.DescribeAddon(gomock.Eq(&eks.DescribeAddonInput{
+					AddonName:   aws.String("addon2"),
+					ClusterName: aws.String(clusterName),
+				})).Return(out, nil)
 			},
 			desiredAddons: []*EKSAddon{
 				createDesiredAddon(addon1Name, addon1version),
+				createDesiredAddon("addon2", addon1version),
 			},
 			expectCreateError: false,
 			expectDoError:     false,
@@ -110,10 +182,15 @@ func TestEKSAddonPlan(t *testing.T) {
 		{
 			name: "1 installed and 1 desired - both same and installed is creating",
 			expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {
-				m.WaitUntilAddonActive(gomock.Eq(&eks.DescribeAddonInput{
+				out := &eks.DescribeAddonOutput{
+					Addon: &eks.Addon{
+						Status: aws.String(eks.AddonStatusActive),
+					},
+				}
+				m.DescribeAddon(gomock.Eq(&eks.DescribeAddonInput{
 					AddonName:   aws.String(addon1Name),
 					ClusterName: aws.String(clusterName),
-				})).Return(nil)
+				})).Return(out, nil)
 			},
 			desiredAddons: []*EKSAddon{
 				createDesiredAddon(addon1Name, addon1version),
@@ -142,10 +219,16 @@ func TestEKSAddonPlan(t *testing.T) {
 							Type:      aws.String(eks.UpdateTypeVersionUpdate),
 						},
 					}, nil)
-				m.WaitUntilAddonActive(gomock.Eq(&eks.DescribeAddonInput{
+
+				out := &eks.DescribeAddonOutput{
+					Addon: &eks.Addon{
+						Status: aws.String(eks.AddonStatusActive),
+					},
+				}
+				m.DescribeAddon(gomock.Eq(&eks.DescribeAddonInput{
 					AddonName:   aws.String(addon1Name),
 					ClusterName: aws.String(clusterName),
-				})).Return(nil)
+				})).Return(out, nil)
 			},
 			desiredAddons: []*EKSAddon{
 				createDesiredAddon(addon1Name, addon1Upgrade),
@@ -159,10 +242,15 @@ func TestEKSAddonPlan(t *testing.T) {
 		{
 			name: "1 installed and 1 desired - version upgrade in progress",
 			expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {
-				m.WaitUntilAddonActive(gomock.Eq(&eks.DescribeAddonInput{
+				out := &eks.DescribeAddonOutput{
+					Addon: &eks.Addon{
+						Status: aws.String(eks.AddonStatusActive),
+					},
+				}
+				m.DescribeAddon(gomock.Eq(&eks.DescribeAddonInput{
 					AddonName:   aws.String(addon1Name),
 					ClusterName: aws.String(clusterName),
-				})).Return(nil)
+				})).Return(out, nil)
 			},
 			desiredAddons: []*EKSAddon{
 				createDesiredAddon(addon1Name, addon1Upgrade),
@@ -217,10 +305,15 @@ func TestEKSAddonPlan(t *testing.T) {
 						},
 					}, nil)
 
-				m.WaitUntilAddonActive(gomock.Eq(&eks.DescribeAddonInput{
+				out := &eks.DescribeAddonOutput{
+					Addon: &eks.Addon{
+						Status: aws.String(eks.AddonStatusActive),
+					},
+				}
+				m.DescribeAddon(gomock.Eq(&eks.DescribeAddonInput{
 					AddonName:   aws.String(addon1Name),
 					ClusterName: aws.String(clusterName),
-				})).Return(nil)
+				})).Return(out, nil)
 			},
 			desiredAddons: []*EKSAddon{
 				createDesiredAddonExtraTag(addon1Name, addon1Upgrade),
@@ -251,7 +344,6 @@ func TestEKSAddonPlan(t *testing.T) {
 							Tags:         convertTags(createTags()),
 						},
 					}, nil)
-
 				m.WaitUntilAddonDeleted(gomock.Eq(&eks.DescribeAddonInput{
 					AddonName:   aws.String(addon1Name),
 					ClusterName: aws.String(clusterName),
diff --git a/test/e2e/data/e2e_eks_conf.yaml b/test/e2e/data/e2e_eks_conf.yaml
index e214f7981..b54cea67a 100644
--- a/test/e2e/data/e2e_eks_conf.yaml
+++ b/test/e2e/data/e2e_eks_conf.yaml
@@ -87,7 +87,7 @@ providers:
 
 
 variables:
-  KUBERNETES_VERSION: "v1.19.8"
+  KUBERNETES_VERSION: "1.21.2"
   CNI: "../../data/cni/calico_eks.yaml"
   EXP_MACHINE_POOL: "true"
   EXP_CLUSTER_RESOURCE_SET: "true"
@@ -95,8 +95,9 @@ variables:
   AWS_SSH_KEY_NAME: "cluster-api-provider-aws-sigs-k8s-io"
   EXP_EKS_IAM: "false"
   EXP_EKS_ADD_ROLES: "false"
-  VPC_ADDON_VERSION: "v1.6.3-eksbuild.1"
-  CONFORMANCE_CI_ARTIFACTS_KUBERNETES_VERSION: "v1.19.8"
+  VPC_ADDON_VERSION: "v1.8.0-eksbuild.1"
+  COREDNS_ADDON_VERSION: "v1.8.3-eksbuild.1"
+  CONFORMANCE_CI_ARTIFACTS_KUBERNETES_VERSION: "1.21.2"
   AUTO_CONTROLLER_IDENTITY_CREATOR: "false"
 
 intervals:
diff --git a/test/e2e/data/eks/cluster-template-eks-control-plane-only-withaddon.yaml b/test/e2e/data/eks/cluster-template-eks-control-plane-only-withaddon.yaml
index 32065a2fa..a0ff4b747 100644
--- a/test/e2e/data/eks/cluster-template-eks-control-plane-only-withaddon.yaml
+++ b/test/e2e/data/eks/cluster-template-eks-control-plane-only-withaddon.yaml
@@ -28,6 +28,9 @@ spec:
     - name: "vpc-cni"
       version: "${VPC_ADDON_VERSION}"
       conflictResolution: "overwrite"
+    - name: "coredns"
+      version: "${COREDNS_ADDON_VERSION}"
+      conflictResolution: "overwrite"
   identityRef:
     kind: AWSClusterStaticIdentity
     name: e2e-account
diff --git a/test/e2e/suites/managed/addon.go b/test/e2e/suites/managed/addon.go
index 1ac57f5f2..081fcd169 100644
--- a/test/e2e/suites/managed/addon.go
+++ b/test/e2e/suites/managed/addon.go
@@ -75,6 +75,6 @@ func CheckAddonExistsSpec(ctx context.Context, inputGetter func() CheckAddonExis
 		AWSSession:   input.AWSSession,
 		AddonName:    input.AddonName,
 		AddonVersion: input.AddonVersion,
-		AddonStatus:  eks.AddonStatusActive,
+		AddonStatus:  []string{eks.AddonStatusActive, eks.AddonStatusDegraded},
 	}, input.E2EConfig.GetIntervals("", "wait-addon-status")...)
 }
diff --git a/test/e2e/suites/managed/addon_helpers.go b/test/e2e/suites/managed/addon_helpers.go
index 226a2151e..5e7bee63b 100644
--- a/test/e2e/suites/managed/addon_helpers.go
+++ b/test/e2e/suites/managed/addon_helpers.go
@@ -35,7 +35,7 @@ type waitForEKSAddonToHaveStatusInput struct {
 	AWSSession   client.ConfigProvider
 	AddonName    string
 	AddonVersion string
-	AddonStatus  string
+	AddonStatus  []string
 }
 
 func waitForEKSAddonToHaveStatus(ctx context.Context, input waitForEKSAddonToHaveStatusInput, intervals ...interface{}) {
@@ -45,7 +45,7 @@ func waitForEKSAddonToHaveStatus(ctx context.Context, input waitForEKSAddonToHav
 	Expect(input.AddonVersion).ShouldNot(HaveLen(0), "Invalid argument. input.AddonVersion can't be empty")
 	Expect(input.AddonStatus).ShouldNot(HaveLen(0), "Invalid argument. input.AddonStatus can't be empty")
 
-	ginkgo.By(fmt.Sprintf("Ensuring EKS addon %s has status %s for EKS cluster %s", input.AddonName, input.AddonStatus, input.ControlPlane.Spec.EKSClusterName))
+	ginkgo.By(fmt.Sprintf("Ensuring EKS addon %s has status in %q for EKS cluster %s", input.AddonName, input.AddonStatus, input.ControlPlane.Spec.EKSClusterName))
 
 	Eventually(func() (bool, error) {
 		installedAddon, err := getEKSClusterAddon(input.ControlPlane.Spec.EKSClusterName, input.AddonName, input.AWSSession)
@@ -57,11 +57,15 @@ func waitForEKSAddonToHaveStatus(ctx context.Context, input waitForEKSAddonToHav
 			return false, err
 		}
 
-		if *installedAddon.Status != input.AddonStatus {
-			return false, nil
+		for i := range input.AddonStatus {
+			wantedStatus := input.AddonStatus[i]
+
+			if wantedStatus == *installedAddon.Status {
+				return true, nil
+			}
 		}
 
-		return true, nil
+		return false, nil
 
 	}, intervals...).Should(BeTrue())
 }
diff --git a/test/e2e/suites/managed/eks_test.go b/test/e2e/suites/managed/eks_test.go
index fa5d11c56..a34681ef2 100644
--- a/test/e2e/suites/managed/eks_test.go
+++ b/test/e2e/suites/managed/eks_test.go
@@ -36,12 +36,14 @@ import (
 // General EKS e2e test
 var _ = ginkgo.Describe("[managed] [general] EKS cluster tests", func() {
 	var (
-		namespace       *corev1.Namespace
-		ctx             context.Context
-		specName        = "eks-nodes"
-		clusterName     string
-		cniAddonName    = "vpc-cni"
-		cniAddonVersion = "v1.6.3-eksbuild.1"
+		namespace           *corev1.Namespace
+		ctx                 context.Context
+		specName            = "eks-nodes"
+		clusterName         string
+		cniAddonName        = "vpc-cni"
+		cniAddonVersion     = "v1.8.0-eksbuild.1"
+		corednsAddonName    = "coredns"
+		corednsAddonVersion = "v1.8.3-eksbuild.1"
 	)
 
 	shared.ConditionalIt(runGeneralTests, "should create a cluster and add nodes", func() {
@@ -89,6 +91,19 @@ var _ = ginkgo.Describe("[managed] [general] EKS cluster tests", func() {
 			}
 		})
 
+		ginkgo.By("should have the Coredns addon installed")
+		CheckAddonExistsSpec(ctx, func() CheckAddonExistsSpecInput {
+			return CheckAddonExistsSpecInput{
+				E2EConfig:             e2eCtx.E2EConfig,
+				BootstrapClusterProxy: e2eCtx.Environment.BootstrapClusterProxy,
+				AWSSession:            e2eCtx.BootstrapUserAWSSession,
+				Namespace:             namespace,
+				ClusterName:           clusterName,
+				AddonName:             corednsAddonName,
+				AddonVersion:          corednsAddonVersion,
+			}
+		})
+
 		ginkgo.By("should create a MachineDeployment")
 		MachineDeploymentSpec(ctx, func() MachineDeploymentSpecInput {
 			return MachineDeploymentSpecInput{