sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/eks/addons/plan_test.go (about)

     1  /*
     2  Copyright 2021 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 addons
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/aws/aws-sdk-go/aws"
    25  	"github.com/aws/aws-sdk-go/service/eks"
    26  	"github.com/golang/mock/gomock"
    27  	. "github.com/onsi/gomega"
    28  
    29  	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    30  	"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/eks/mock_eksiface"
    31  )
    32  
    33  func TestEKSAddonPlan(t *testing.T) {
    34  	clusterName := "default.cluster"
    35  	addonARN := "aws://someaddonarn"
    36  	addon1Name := "addon1"
    37  	addon1version := "1.0.0"
    38  	addon1Upgrade := "2.0.0"
    39  	addonStatusActive := string(eks.AddonStatusActive)
    40  	addonStatusUpdating := string(eks.AddonStatusUpdating)
    41  	addonStatusDeleting := string(eks.AddonStatusDeleting)
    42  	addonStatusCreating := string(eks.AddonStatusCreating)
    43  	created := time.Now()
    44  
    45  	testCases := []struct {
    46  		name              string
    47  		desiredAddons     []*EKSAddon
    48  		installedAddons   []*EKSAddon
    49  		expect            func(m *mock_eksiface.MockEKSAPIMockRecorder)
    50  		expectCreateError bool
    51  		expectDoError     bool
    52  	}{
    53  		{
    54  			name: "no desired and no installed",
    55  			expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {
    56  				// Do nothing
    57  			},
    58  			expectCreateError: false,
    59  			expectDoError:     false,
    60  		},
    61  		{
    62  			name: "no installed and 1 desired",
    63  			expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {
    64  				m.
    65  					CreateAddon(gomock.Eq(&eks.CreateAddonInput{
    66  						AddonName:        aws.String(addon1Name),
    67  						AddonVersion:     aws.String(addon1version),
    68  						ClusterName:      aws.String(clusterName),
    69  						ResolveConflicts: aws.String(eks.ResolveConflictsOverwrite),
    70  						Tags:             convertTags(createTags()),
    71  					})).
    72  					Return(&eks.CreateAddonOutput{
    73  						Addon: &eks.Addon{
    74  							AddonArn:     aws.String(addonARN),
    75  							AddonName:    aws.String(addon1Name),
    76  							AddonVersion: aws.String(addon1version),
    77  							ClusterName:  aws.String(clusterName),
    78  							CreatedAt:    &created,
    79  							ModifiedAt:   &created,
    80  							Status:       aws.String(addonStatusCreating),
    81  							Tags:         convertTags(createTags()),
    82  						},
    83  					}, nil)
    84  
    85  				out := &eks.DescribeAddonOutput{
    86  					Addon: &eks.Addon{
    87  						Status: aws.String(eks.AddonStatusActive),
    88  					},
    89  				}
    90  				m.DescribeAddon(gomock.Eq(&eks.DescribeAddonInput{
    91  					AddonName:   aws.String(addon1Name),
    92  					ClusterName: aws.String(clusterName),
    93  				})).Return(out, nil)
    94  			},
    95  			desiredAddons: []*EKSAddon{
    96  				createDesiredAddon(addon1Name, addon1version),
    97  			},
    98  			expectCreateError: false,
    99  			expectDoError:     false,
   100  		},
   101  		{
   102  			name: "no installed and 2 desired",
   103  			expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {
   104  				m.
   105  					CreateAddon(gomock.Eq(&eks.CreateAddonInput{
   106  						AddonName:        aws.String(addon1Name),
   107  						AddonVersion:     aws.String(addon1version),
   108  						ClusterName:      aws.String(clusterName),
   109  						ResolveConflicts: aws.String(eks.ResolveConflictsOverwrite),
   110  						Tags:             convertTags(createTags()),
   111  					})).
   112  					Return(&eks.CreateAddonOutput{
   113  						Addon: &eks.Addon{
   114  							AddonArn:     aws.String(addonARN),
   115  							AddonName:    aws.String(addon1Name),
   116  							AddonVersion: aws.String(addon1version),
   117  							ClusterName:  aws.String(clusterName),
   118  							CreatedAt:    &created,
   119  							ModifiedAt:   &created,
   120  							Status:       aws.String(addonStatusCreating),
   121  							Tags:         convertTags(createTags()),
   122  						},
   123  					}, nil)
   124  
   125  				out := &eks.DescribeAddonOutput{
   126  					Addon: &eks.Addon{
   127  						Status: aws.String(eks.AddonStatusActive),
   128  					},
   129  				}
   130  				m.DescribeAddon(gomock.Eq(&eks.DescribeAddonInput{
   131  					AddonName:   aws.String(addon1Name),
   132  					ClusterName: aws.String(clusterName),
   133  				})).Return(out, nil)
   134  
   135  				m.
   136  					CreateAddon(gomock.Eq(&eks.CreateAddonInput{
   137  						AddonName:        aws.String("addon2"),
   138  						AddonVersion:     aws.String(addon1version),
   139  						ClusterName:      aws.String(clusterName),
   140  						ResolveConflicts: aws.String(eks.ResolveConflictsOverwrite),
   141  						Tags:             convertTags(createTags()),
   142  					})).
   143  					Return(&eks.CreateAddonOutput{
   144  						Addon: &eks.Addon{
   145  							AddonArn:     aws.String(addonARN),
   146  							AddonName:    aws.String("addon2"),
   147  							AddonVersion: aws.String(addon1version),
   148  							ClusterName:  aws.String(clusterName),
   149  							CreatedAt:    &created,
   150  							ModifiedAt:   &created,
   151  							Status:       aws.String(addonStatusCreating),
   152  							Tags:         convertTags(createTags()),
   153  						},
   154  					}, nil)
   155  
   156  				m.DescribeAddon(gomock.Eq(&eks.DescribeAddonInput{
   157  					AddonName:   aws.String("addon2"),
   158  					ClusterName: aws.String(clusterName),
   159  				})).Return(out, nil)
   160  			},
   161  			desiredAddons: []*EKSAddon{
   162  				createDesiredAddon(addon1Name, addon1version),
   163  				createDesiredAddon("addon2", addon1version),
   164  			},
   165  			expectCreateError: false,
   166  			expectDoError:     false,
   167  		},
   168  		{
   169  			name: "1 installed and 1 desired - both same and installed active",
   170  			expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {
   171  				// No Action expected
   172  			},
   173  			desiredAddons: []*EKSAddon{
   174  				createDesiredAddon(addon1Name, addon1version),
   175  			},
   176  			installedAddons: []*EKSAddon{
   177  				createInstalledAddon(addon1Name, addon1version, addonARN, addonStatusActive),
   178  			},
   179  			expectCreateError: false,
   180  			expectDoError:     false,
   181  		},
   182  		{
   183  			name: "1 installed and 1 desired - both same and installed is creating",
   184  			expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {
   185  				out := &eks.DescribeAddonOutput{
   186  					Addon: &eks.Addon{
   187  						Status: aws.String(eks.AddonStatusActive),
   188  					},
   189  				}
   190  				m.DescribeAddon(gomock.Eq(&eks.DescribeAddonInput{
   191  					AddonName:   aws.String(addon1Name),
   192  					ClusterName: aws.String(clusterName),
   193  				})).Return(out, nil)
   194  			},
   195  			desiredAddons: []*EKSAddon{
   196  				createDesiredAddon(addon1Name, addon1version),
   197  			},
   198  			installedAddons: []*EKSAddon{
   199  				createInstalledAddon(addon1Name, addon1version, addonARN, addonStatusCreating),
   200  			},
   201  			expectCreateError: false,
   202  			expectDoError:     false,
   203  		},
   204  		{
   205  			name: "1 installed and 1 desired - version upgrade",
   206  			expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {
   207  				m.
   208  					UpdateAddon(gomock.Eq(&eks.UpdateAddonInput{
   209  						AddonName:        aws.String(addon1Name),
   210  						AddonVersion:     aws.String(addon1Upgrade),
   211  						ClusterName:      aws.String(clusterName),
   212  						ResolveConflicts: aws.String(eks.ResolveConflictsOverwrite),
   213  					})).
   214  					Return(&eks.UpdateAddonOutput{
   215  						Update: &eks.Update{
   216  							CreatedAt: &created,
   217  							Id:        aws.String("someid"),
   218  							Status:    aws.String(addonStatusUpdating),
   219  							Type:      aws.String(eks.UpdateTypeVersionUpdate),
   220  						},
   221  					}, nil)
   222  
   223  				out := &eks.DescribeAddonOutput{
   224  					Addon: &eks.Addon{
   225  						Status: aws.String(eks.AddonStatusActive),
   226  					},
   227  				}
   228  				m.DescribeAddon(gomock.Eq(&eks.DescribeAddonInput{
   229  					AddonName:   aws.String(addon1Name),
   230  					ClusterName: aws.String(clusterName),
   231  				})).Return(out, nil)
   232  			},
   233  			desiredAddons: []*EKSAddon{
   234  				createDesiredAddon(addon1Name, addon1Upgrade),
   235  			},
   236  			installedAddons: []*EKSAddon{
   237  				createInstalledAddon(addon1Name, addon1version, addonARN, addonStatusActive),
   238  			},
   239  			expectCreateError: false,
   240  			expectDoError:     false,
   241  		},
   242  		{
   243  			name: "1 installed and 1 desired - version upgrade in progress",
   244  			expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {
   245  				out := &eks.DescribeAddonOutput{
   246  					Addon: &eks.Addon{
   247  						Status: aws.String(eks.AddonStatusActive),
   248  					},
   249  				}
   250  				m.DescribeAddon(gomock.Eq(&eks.DescribeAddonInput{
   251  					AddonName:   aws.String(addon1Name),
   252  					ClusterName: aws.String(clusterName),
   253  				})).Return(out, nil)
   254  			},
   255  			desiredAddons: []*EKSAddon{
   256  				createDesiredAddon(addon1Name, addon1Upgrade),
   257  			},
   258  			installedAddons: []*EKSAddon{
   259  				createInstalledAddon(addon1Name, addon1Upgrade, addonARN, addonStatusUpdating),
   260  			},
   261  			expectCreateError: false,
   262  			expectDoError:     false,
   263  		},
   264  		{
   265  			name: "1 installed and 1 desired - tags upgrade",
   266  			expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {
   267  				m.
   268  					TagResource(gomock.Eq(&eks.TagResourceInput{
   269  						ResourceArn: &addonARN,
   270  						Tags:        convertTags(createTagsAdditional()),
   271  					})).
   272  					Return(&eks.TagResourceOutput{}, nil)
   273  			},
   274  			desiredAddons: []*EKSAddon{
   275  				createDesiredAddonExtraTag(addon1Name, addon1version),
   276  			},
   277  			installedAddons: []*EKSAddon{
   278  				createInstalledAddon(addon1Name, addon1version, addonARN, addonStatusActive),
   279  			},
   280  			expectCreateError: false,
   281  			expectDoError:     false,
   282  		},
   283  		{
   284  			name: "1 installed and 1 desired - version & tags upgrade",
   285  			expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {
   286  				m.
   287  					TagResource(gomock.Eq(&eks.TagResourceInput{
   288  						ResourceArn: &addonARN,
   289  						Tags:        convertTags(createTagsAdditional()),
   290  					})).
   291  					Return(&eks.TagResourceOutput{}, nil)
   292  				m.
   293  					UpdateAddon(gomock.Eq(&eks.UpdateAddonInput{
   294  						AddonName:        aws.String(addon1Name),
   295  						AddonVersion:     aws.String(addon1Upgrade),
   296  						ClusterName:      aws.String(clusterName),
   297  						ResolveConflicts: aws.String(eks.ResolveConflictsOverwrite),
   298  					})).
   299  					Return(&eks.UpdateAddonOutput{
   300  						Update: &eks.Update{
   301  							CreatedAt: &created,
   302  							Id:        aws.String("someid"),
   303  							Status:    aws.String(addonStatusUpdating),
   304  							Type:      aws.String(eks.UpdateTypeVersionUpdate),
   305  						},
   306  					}, nil)
   307  
   308  				out := &eks.DescribeAddonOutput{
   309  					Addon: &eks.Addon{
   310  						Status: aws.String(eks.AddonStatusActive),
   311  					},
   312  				}
   313  				m.DescribeAddon(gomock.Eq(&eks.DescribeAddonInput{
   314  					AddonName:   aws.String(addon1Name),
   315  					ClusterName: aws.String(clusterName),
   316  				})).Return(out, nil)
   317  			},
   318  			desiredAddons: []*EKSAddon{
   319  				createDesiredAddonExtraTag(addon1Name, addon1Upgrade),
   320  			},
   321  			installedAddons: []*EKSAddon{
   322  				createInstalledAddon(addon1Name, addon1version, addonARN, addonStatusActive),
   323  			},
   324  			expectCreateError: false,
   325  			expectDoError:     false,
   326  		},
   327  		{
   328  			name: "1 installed and 0 desired - delete addon",
   329  			expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {
   330  				m.
   331  					DeleteAddon(gomock.Eq(&eks.DeleteAddonInput{
   332  						AddonName:   &addon1Name,
   333  						ClusterName: &clusterName,
   334  					})).
   335  					Return(&eks.DeleteAddonOutput{
   336  						Addon: &eks.Addon{
   337  							AddonArn:     aws.String(addonARN),
   338  							AddonName:    aws.String(addon1Name),
   339  							AddonVersion: aws.String(addon1version),
   340  							ClusterName:  aws.String(clusterName),
   341  							CreatedAt:    &created,
   342  							ModifiedAt:   &created,
   343  							Status:       aws.String(addonStatusDeleting),
   344  							Tags:         convertTags(createTags()),
   345  						},
   346  					}, nil)
   347  				m.WaitUntilAddonDeleted(gomock.Eq(&eks.DescribeAddonInput{
   348  					AddonName:   aws.String(addon1Name),
   349  					ClusterName: aws.String(clusterName),
   350  				})).Return(nil)
   351  			},
   352  			installedAddons: []*EKSAddon{
   353  				createInstalledAddon(addon1Name, addon1version, addonARN, addonStatusActive),
   354  			},
   355  			expectCreateError: false,
   356  			expectDoError:     false,
   357  		},
   358  		{
   359  			name: "1 installed and 0 desired - addon has status of deleting",
   360  			expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {
   361  				m.WaitUntilAddonDeleted(gomock.Eq(&eks.DescribeAddonInput{
   362  					AddonName:   aws.String(addon1Name),
   363  					ClusterName: aws.String(clusterName),
   364  				})).Return(nil)
   365  			},
   366  			installedAddons: []*EKSAddon{
   367  				createInstalledAddon(addon1Name, addon1version, addonARN, addonStatusDeleting),
   368  			},
   369  			expectCreateError: false,
   370  			expectDoError:     false,
   371  		},
   372  	}
   373  
   374  	for _, tc := range testCases {
   375  		t.Run(tc.name, func(t *testing.T) {
   376  			g := NewWithT(t)
   377  
   378  			mockControl := gomock.NewController(t)
   379  			defer mockControl.Finish()
   380  
   381  			eksMock := mock_eksiface.NewMockEKSAPI(mockControl)
   382  			tc.expect(eksMock.EXPECT())
   383  
   384  			ctx := context.TODO()
   385  
   386  			planner := NewPlan(clusterName, tc.desiredAddons, tc.installedAddons, eksMock)
   387  			procedures, err := planner.Create(ctx)
   388  			if tc.expectCreateError {
   389  				g.Expect(err).To(HaveOccurred())
   390  				return
   391  			}
   392  			g.Expect(err).To(BeNil())
   393  			g.Expect(procedures).NotTo(BeNil())
   394  
   395  			for _, proc := range procedures {
   396  				procErr := proc.Do(ctx)
   397  				if tc.expectDoError {
   398  					g.Expect(procErr).To(HaveOccurred())
   399  					return
   400  				}
   401  				g.Expect(procErr).To(BeNil())
   402  			}
   403  		})
   404  	}
   405  }
   406  
   407  func createTags() infrav1.Tags {
   408  	tags := infrav1.Tags{}
   409  	tags["tag1"] = "val1"
   410  
   411  	return tags
   412  }
   413  
   414  func createTagsAdditional() infrav1.Tags {
   415  	tags := createTags()
   416  	tags["tag2"] = "val2"
   417  
   418  	return tags
   419  }
   420  
   421  func createDesiredAddon(name, version string) *EKSAddon {
   422  	tags := createTags()
   423  
   424  	return &EKSAddon{
   425  		Name:            &name,
   426  		Version:         &version,
   427  		Tags:            tags,
   428  		ResolveConflict: aws.String(eks.ResolveConflictsOverwrite),
   429  	}
   430  }
   431  
   432  func createDesiredAddonExtraTag(name, version string) *EKSAddon {
   433  	tags := createTagsAdditional()
   434  
   435  	return &EKSAddon{
   436  		Name:            &name,
   437  		Version:         &version,
   438  		Tags:            tags,
   439  		ResolveConflict: aws.String(eks.ResolveConflictsOverwrite),
   440  	}
   441  }
   442  
   443  func createInstalledAddon(name, version, arn, status string) *EKSAddon {
   444  	desired := createDesiredAddon(name, version)
   445  	desired.ARN = &arn
   446  	desired.Status = &status
   447  
   448  	return desired
   449  }