sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/services/eks/cluster_test.go (about) 1 /* 2 Copyright 2020 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 eks 18 19 import ( 20 "testing" 21 22 "github.com/aws/aws-sdk-go/aws" 23 "github.com/aws/aws-sdk-go/service/eks" 24 "github.com/aws/aws-sdk-go/service/iam" 25 "github.com/golang/mock/gomock" 26 . "github.com/onsi/gomega" 27 "github.com/pkg/errors" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/apimachinery/pkg/util/version" 31 "k8s.io/utils/pointer" 32 "sigs.k8s.io/controller-runtime/pkg/client/fake" 33 34 infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1" 35 ekscontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/controlplane/eks/api/v1beta1" 36 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/scope" 37 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/eks/mock_eksiface" 38 "sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/services/iamauth/mock_iamauth" 39 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 40 ) 41 42 func TestMakeEksEncryptionConfigs(t *testing.T) { 43 providerOne := "provider" 44 resourceOne := "resourceOne" 45 resourceTwo := "resourceTwo" 46 testCases := []struct { 47 name string 48 input *ekscontrolplanev1.EncryptionConfig 49 expect []*eks.EncryptionConfig 50 }{ 51 { 52 name: "nil input", 53 input: nil, 54 expect: []*eks.EncryptionConfig{}, 55 }, 56 { 57 name: "nil input", 58 input: &ekscontrolplanev1.EncryptionConfig{ 59 Provider: &providerOne, 60 Resources: []*string{&resourceOne, &resourceTwo}, 61 }, 62 expect: []*eks.EncryptionConfig{{ 63 Provider: &eks.Provider{KeyArn: &providerOne}, 64 Resources: []*string{&resourceOne, &resourceTwo}, 65 }}, 66 }, 67 } 68 for _, tc := range testCases { 69 t.Run(tc.name, func(t *testing.T) { 70 g := NewWithT(t) 71 g.Expect(makeEksEncryptionConfigs(tc.input)).To(Equal(tc.expect)) 72 }) 73 } 74 } 75 76 func TestParseEKSVersion(t *testing.T) { 77 testCases := []struct { 78 name string 79 input string 80 expect version.Version 81 }{ 82 { 83 name: "with patch", 84 input: "1.17.8", 85 expect: *version.MustParseGeneric("1.17"), 86 }, 87 { 88 name: "with v", 89 input: "v1.17.8", 90 expect: *version.MustParseGeneric("1.17"), 91 }, 92 { 93 name: "no patch", 94 input: "1.17", 95 expect: *version.MustParseGeneric("1.17"), 96 }, 97 } 98 for _, tc := range testCases { 99 t.Run(tc.name, func(t *testing.T) { 100 g := NewWithT(t) 101 g.Expect(*parseEKSVersion(tc.input)).To(Equal(tc.expect)) 102 }) 103 } 104 } 105 func TestVersionToEKS(t *testing.T) { 106 testCases := []struct { 107 name string 108 input *version.Version 109 expect string 110 }{ 111 { 112 name: "with patch", 113 input: version.MustParseGeneric("1.17.8"), 114 expect: "1.17", 115 }, 116 { 117 name: "no patch", 118 input: version.MustParseGeneric("1.17"), 119 expect: "1.17", 120 }, 121 { 122 name: "with extra data", 123 input: version.MustParseGeneric("1.17-alpha"), 124 expect: "1.17", 125 }, 126 } 127 for _, tc := range testCases { 128 t.Run(tc.name, func(t *testing.T) { 129 g := NewWithT(t) 130 g.Expect(versionToEKS(tc.input)).To(Equal(tc.expect)) 131 }) 132 } 133 } 134 135 func TestMakeVPCConfig(t *testing.T) { 136 type input struct { 137 subnets infrav1.Subnets 138 endpointAccess ekscontrolplanev1.EndpointAccess 139 securityGroups map[infrav1.SecurityGroupRole]infrav1.SecurityGroup 140 } 141 142 idOne := "one" 143 idTwo := "two" 144 testCases := []struct { 145 name string 146 input input 147 err bool 148 expect *eks.VpcConfigRequest 149 }{ 150 { 151 name: "no subnets", 152 input: input{ 153 subnets: nil, 154 endpointAccess: ekscontrolplanev1.EndpointAccess{}, 155 }, 156 err: true, 157 expect: nil, 158 }, 159 { 160 name: "enough subnets", 161 input: input{ 162 subnets: []infrav1.SubnetSpec{ 163 { 164 ID: idOne, 165 CidrBlock: "10.0.10.0/24", 166 AvailabilityZone: "us-west-2a", 167 IsPublic: true, 168 }, 169 { 170 ID: idTwo, 171 CidrBlock: "10.0.10.0/24", 172 AvailabilityZone: "us-west-2b", 173 IsPublic: false, 174 }, 175 }, 176 endpointAccess: ekscontrolplanev1.EndpointAccess{}, 177 }, 178 expect: &eks.VpcConfigRequest{ 179 SubnetIds: []*string{&idOne, &idTwo}, 180 }, 181 }, 182 { 183 name: "security groups", 184 input: input{ 185 subnets: []infrav1.SubnetSpec{ 186 { 187 ID: idOne, 188 CidrBlock: "10.0.10.0/24", 189 AvailabilityZone: "us-west-2a", 190 IsPublic: true, 191 }, 192 { 193 ID: idTwo, 194 CidrBlock: "10.0.10.0/24", 195 AvailabilityZone: "us-west-2b", 196 IsPublic: false, 197 }, 198 }, 199 endpointAccess: ekscontrolplanev1.EndpointAccess{}, 200 securityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ 201 infrav1.SecurityGroupEKSNodeAdditional: { 202 ID: idOne, 203 }, 204 }, 205 }, 206 expect: &eks.VpcConfigRequest{ 207 SubnetIds: []*string{&idOne, &idTwo}, 208 SecurityGroupIds: []*string{&idOne}, 209 }, 210 }, 211 { 212 name: "non canonical public access CIDR", 213 input: input{ 214 subnets: []infrav1.SubnetSpec{ 215 { 216 ID: idOne, 217 CidrBlock: "10.0.10.0/24", 218 AvailabilityZone: "us-west-2a", 219 IsPublic: true, 220 }, 221 { 222 ID: idTwo, 223 CidrBlock: "10.0.10.1/24", 224 AvailabilityZone: "us-west-2b", 225 IsPublic: false, 226 }, 227 }, 228 endpointAccess: ekscontrolplanev1.EndpointAccess{ 229 PublicCIDRs: []*string{aws.String("10.0.0.1/24")}, 230 }, 231 }, 232 expect: &eks.VpcConfigRequest{ 233 SubnetIds: []*string{&idOne, &idTwo}, 234 PublicAccessCidrs: []*string{aws.String("10.0.0.0/24")}, 235 }, 236 }, 237 } 238 for _, tc := range testCases { 239 t.Run(tc.name, func(t *testing.T) { 240 g := NewWithT(t) 241 config, err := makeVpcConfig(tc.input.subnets, tc.input.endpointAccess, tc.input.securityGroups) 242 if tc.err { 243 g.Expect(err).To(HaveOccurred()) 244 } else { 245 g.Expect(config).To(Equal(tc.expect)) 246 } 247 }) 248 } 249 } 250 251 func TestPublicAccessCIDRsEqual(t *testing.T) { 252 testCases := []struct { 253 name string 254 a []*string 255 b []*string 256 expect bool 257 }{ 258 { 259 name: "no CIDRs", 260 a: nil, 261 b: nil, 262 expect: true, 263 }, 264 { 265 name: "every address", 266 a: []*string{aws.String("0.0.0.0/0")}, 267 b: nil, 268 expect: true, 269 }, 270 { 271 name: "every address", 272 a: []*string{aws.String("1.1.1.0/24")}, 273 b: []*string{aws.String("1.1.1.0/24")}, 274 expect: true, 275 }, 276 } 277 for _, tc := range testCases { 278 t.Run(tc.name, func(t *testing.T) { 279 g := NewWithT(t) 280 g.Expect(publicAccessCIDRsEqual(tc.a, tc.b)).To(Equal(tc.expect)) 281 }) 282 } 283 } 284 285 func TestMakeEKSLogging(t *testing.T) { 286 testCases := []struct { 287 name string 288 input *ekscontrolplanev1.ControlPlaneLoggingSpec 289 expect *eks.Logging 290 }{ 291 { 292 name: "no subnets", 293 input: nil, 294 expect: nil, 295 }, 296 { 297 name: "some enabled, some disabled", 298 input: &ekscontrolplanev1.ControlPlaneLoggingSpec{ 299 APIServer: true, 300 Audit: false, 301 }, 302 expect: &eks.Logging{ 303 ClusterLogging: []*eks.LogSetup{ 304 { 305 Enabled: aws.Bool(true), 306 Types: []*string{aws.String(eks.LogTypeApi)}, 307 }, 308 { 309 Enabled: aws.Bool(false), 310 Types: []*string{ 311 aws.String(eks.LogTypeAudit), 312 aws.String(eks.LogTypeAuthenticator), 313 aws.String(eks.LogTypeControllerManager), 314 aws.String(eks.LogTypeScheduler), 315 }, 316 }, 317 }, 318 }, 319 }, 320 } 321 for _, tc := range testCases { 322 t.Run(tc.name, func(t *testing.T) { 323 g := NewWithT(t) 324 logging := makeEksLogging(tc.input) 325 g.Expect(logging).To(Equal(tc.expect)) 326 }) 327 } 328 } 329 330 func TestReconcileClusterVersion(t *testing.T) { 331 clusterName := "default.cluster" 332 tests := []struct { 333 name string 334 expect func(m *mock_eksiface.MockEKSAPIMockRecorder) 335 expectError bool 336 }{ 337 { 338 name: "no upgrade necessary", 339 expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) { 340 m. 341 DescribeCluster(gomock.AssignableToTypeOf(&eks.DescribeClusterInput{})). 342 Return(&eks.DescribeClusterOutput{ 343 Cluster: &eks.Cluster{ 344 Name: aws.String("default.cluster"), 345 Version: aws.String("1.16"), 346 }, 347 }, nil) 348 }, 349 expectError: false, 350 }, 351 { 352 name: "needs upgrade", 353 expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) { 354 m. 355 DescribeCluster(gomock.AssignableToTypeOf(&eks.DescribeClusterInput{})). 356 Return(&eks.DescribeClusterOutput{ 357 Cluster: &eks.Cluster{ 358 Name: aws.String("default.cluster"), 359 Version: aws.String("1.14"), 360 }, 361 }, nil) 362 m.WaitUntilClusterUpdating( 363 gomock.AssignableToTypeOf(&eks.DescribeClusterInput{}), gomock.Any(), 364 ).Return(nil) 365 m. 366 UpdateClusterVersion(gomock.AssignableToTypeOf(&eks.UpdateClusterVersionInput{})). 367 Return(&eks.UpdateClusterVersionOutput{}, nil) 368 }, 369 expectError: false, 370 }, 371 { 372 name: "api error", 373 expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) { 374 m. 375 DescribeCluster(gomock.AssignableToTypeOf(&eks.DescribeClusterInput{})). 376 Return(&eks.DescribeClusterOutput{ 377 Cluster: &eks.Cluster{ 378 Name: aws.String("default.cluster"), 379 Version: aws.String("1.14"), 380 }, 381 }, nil) 382 m. 383 UpdateClusterVersion(gomock.AssignableToTypeOf(&eks.UpdateClusterVersionInput{})). 384 Return(&eks.UpdateClusterVersionOutput{}, errors.New("")) 385 }, 386 expectError: true, 387 }, 388 } 389 390 for _, tc := range tests { 391 t.Run(tc.name, func(t *testing.T) { 392 g := NewWithT(t) 393 394 mockControl := gomock.NewController(t) 395 defer mockControl.Finish() 396 397 eksMock := mock_eksiface.NewMockEKSAPI(mockControl) 398 399 scheme := runtime.NewScheme() 400 _ = infrav1.AddToScheme(scheme) 401 _ = ekscontrolplanev1.AddToScheme(scheme) 402 client := fake.NewClientBuilder().WithScheme(scheme).Build() 403 scope, err := scope.NewManagedControlPlaneScope(scope.ManagedControlPlaneScopeParams{ 404 Client: client, 405 Cluster: &clusterv1.Cluster{ 406 ObjectMeta: metav1.ObjectMeta{ 407 Namespace: "ns", 408 Name: clusterName, 409 }, 410 }, 411 ControlPlane: &ekscontrolplanev1.AWSManagedControlPlane{ 412 Spec: ekscontrolplanev1.AWSManagedControlPlaneSpec{ 413 Version: aws.String("1.16"), 414 }, 415 }, 416 }) 417 g.Expect(err).To(BeNil()) 418 419 tc.expect(eksMock.EXPECT()) 420 s := NewService(scope) 421 s.EKSClient = eksMock 422 423 cluster, err := s.describeEKSCluster(clusterName) 424 g.Expect(err).To(BeNil()) 425 426 err = s.reconcileClusterVersion(cluster) 427 if tc.expectError { 428 g.Expect(err).To(HaveOccurred()) 429 return 430 } 431 g.Expect(err).To(BeNil()) 432 }) 433 } 434 } 435 436 func TestCreateCluster(t *testing.T) { 437 clusterName := "cluster.default" 438 version := aws.String("1.24") 439 tests := []struct { 440 name string 441 expectEKS func(m *mock_eksiface.MockEKSAPIMockRecorder) 442 expectError bool 443 role *string 444 tags map[string]*string 445 subnets []infrav1.SubnetSpec 446 }{ 447 { 448 name: "cluster create with 2 subnets", 449 expectEKS: func(m *mock_eksiface.MockEKSAPIMockRecorder) {}, 450 expectError: false, 451 role: aws.String("arn:role"), 452 tags: map[string]*string{ 453 "kubernetes.io/cluster/" + clusterName: aws.String("owned"), 454 }, 455 subnets: []infrav1.SubnetSpec{ 456 {ID: "1", AvailabilityZone: "us-west-2a"}, {ID: "2", AvailabilityZone: "us-west-2b"}, 457 }, 458 }, 459 { 460 name: "cluster create without subnets", 461 expectEKS: func(m *mock_eksiface.MockEKSAPIMockRecorder) {}, 462 expectError: true, 463 role: aws.String("arn:role"), 464 subnets: []infrav1.SubnetSpec{}, 465 }, 466 } 467 468 for _, tc := range tests { 469 t.Run(tc.name, func(t *testing.T) { 470 g := NewWithT(t) 471 472 mockControl := gomock.NewController(t) 473 defer mockControl.Finish() 474 475 iamMock := mock_iamauth.NewMockIAMAPI(mockControl) 476 eksMock := mock_eksiface.NewMockEKSAPI(mockControl) 477 478 scheme := runtime.NewScheme() 479 _ = infrav1.AddToScheme(scheme) 480 _ = ekscontrolplanev1.AddToScheme(scheme) 481 client := fake.NewClientBuilder().WithScheme(scheme).Build() 482 scope, _ := scope.NewManagedControlPlaneScope(scope.ManagedControlPlaneScopeParams{ 483 Client: client, 484 Cluster: &clusterv1.Cluster{ 485 ObjectMeta: metav1.ObjectMeta{ 486 Namespace: "ns", 487 Name: "capi-name", 488 }, 489 }, 490 ControlPlane: &ekscontrolplanev1.AWSManagedControlPlane{ 491 Spec: ekscontrolplanev1.AWSManagedControlPlaneSpec{ 492 EKSClusterName: clusterName, 493 Version: version, 494 RoleName: tc.role, 495 NetworkSpec: infrav1.NetworkSpec{Subnets: tc.subnets}, 496 }, 497 }, 498 }) 499 subnetIds := make([]*string, 0) 500 for i := range tc.subnets { 501 subnet := tc.subnets[i] 502 subnetIds = append(subnetIds, &subnet.ID) 503 } 504 505 if !tc.expectError { 506 roleOutput := iam.GetRoleOutput{Role: &iam.Role{Arn: tc.role}} 507 iamMock.EXPECT().GetRole(gomock.Any()).Return(&roleOutput, nil) 508 eksMock.EXPECT().CreateCluster(&eks.CreateClusterInput{ 509 Name: aws.String(clusterName), 510 EncryptionConfig: []*eks.EncryptionConfig{}, 511 ResourcesVpcConfig: &eks.VpcConfigRequest{ 512 SubnetIds: subnetIds, 513 }, 514 RoleArn: tc.role, 515 Tags: tc.tags, 516 Version: version, 517 }).Return(&eks.CreateClusterOutput{}, nil) 518 } 519 s := NewService(scope) 520 s.IAMClient = iamMock 521 s.EKSClient = eksMock 522 523 _, err := s.createCluster(clusterName) 524 if tc.expectError { 525 g.Expect(err).To(HaveOccurred()) 526 return 527 } 528 g.Expect(err).To(BeNil()) 529 }) 530 } 531 } 532 533 func TestReconcileEKSEncryptionConfig(t *testing.T) { 534 clusterName := "default.cluster" 535 tests := []struct { 536 name string 537 oldEncryptionConfig *ekscontrolplanev1.EncryptionConfig 538 newEncryptionConfig *ekscontrolplanev1.EncryptionConfig 539 expect func(m *mock_eksiface.MockEKSAPIMockRecorder) 540 expectError bool 541 }{ 542 { 543 name: "no upgrade necessary - encryption disabled", 544 oldEncryptionConfig: &ekscontrolplanev1.EncryptionConfig{}, 545 newEncryptionConfig: &ekscontrolplanev1.EncryptionConfig{}, 546 expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {}, 547 expectError: false, 548 }, 549 { 550 name: "no upgrade necessary - encryption config unchanged", 551 oldEncryptionConfig: &ekscontrolplanev1.EncryptionConfig{ 552 Provider: pointer.String("provider"), 553 Resources: []*string{pointer.String("foo"), pointer.String("bar")}, 554 }, 555 newEncryptionConfig: &ekscontrolplanev1.EncryptionConfig{ 556 Provider: pointer.String("provider"), 557 Resources: []*string{pointer.String("foo"), pointer.String("bar")}, 558 }, 559 expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {}, 560 expectError: false, 561 }, 562 { 563 name: "needs upgrade", 564 oldEncryptionConfig: nil, 565 newEncryptionConfig: &ekscontrolplanev1.EncryptionConfig{ 566 Provider: pointer.String("provider"), 567 Resources: []*string{pointer.String("foo"), pointer.String("bar")}, 568 }, 569 expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) { 570 m.WaitUntilClusterUpdating( 571 gomock.AssignableToTypeOf(&eks.DescribeClusterInput{}), gomock.Any(), 572 ).Return(nil) 573 m.AssociateEncryptionConfig(gomock.AssignableToTypeOf(&eks.AssociateEncryptionConfigInput{})).Return(&eks.AssociateEncryptionConfigOutput{}, nil) 574 }, 575 expectError: false, 576 }, 577 { 578 name: "upgrade not allowed if encryption config updated as nil", 579 oldEncryptionConfig: &ekscontrolplanev1.EncryptionConfig{ 580 Provider: pointer.String("provider"), 581 Resources: []*string{pointer.String("foo"), pointer.String("bar")}, 582 }, 583 newEncryptionConfig: nil, 584 expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {}, 585 expectError: true, 586 }, 587 { 588 name: "upgrade not allowed if encryption config exists", 589 oldEncryptionConfig: &ekscontrolplanev1.EncryptionConfig{ 590 Provider: pointer.String("provider"), 591 Resources: []*string{pointer.String("foo"), pointer.String("bar")}, 592 }, 593 newEncryptionConfig: &ekscontrolplanev1.EncryptionConfig{ 594 Provider: pointer.String("new-provider"), 595 Resources: []*string{pointer.String("foo"), pointer.String("bar")}, 596 }, 597 expect: func(m *mock_eksiface.MockEKSAPIMockRecorder) {}, 598 expectError: true, 599 }, 600 } 601 602 for _, tc := range tests { 603 t.Run(tc.name, func(t *testing.T) { 604 g := NewWithT(t) 605 606 mockControl := gomock.NewController(t) 607 defer mockControl.Finish() 608 609 eksMock := mock_eksiface.NewMockEKSAPI(mockControl) 610 611 scheme := runtime.NewScheme() 612 _ = infrav1.AddToScheme(scheme) 613 _ = ekscontrolplanev1.AddToScheme(scheme) 614 client := fake.NewClientBuilder().WithScheme(scheme).Build() 615 scope, err := scope.NewManagedControlPlaneScope(scope.ManagedControlPlaneScopeParams{ 616 Client: client, 617 Cluster: &clusterv1.Cluster{ 618 ObjectMeta: metav1.ObjectMeta{ 619 Namespace: "ns", 620 Name: clusterName, 621 }, 622 }, 623 ControlPlane: &ekscontrolplanev1.AWSManagedControlPlane{ 624 Spec: ekscontrolplanev1.AWSManagedControlPlaneSpec{ 625 Version: aws.String("1.16"), 626 EncryptionConfig: tc.newEncryptionConfig, 627 }, 628 }, 629 }) 630 g.Expect(err).To(BeNil()) 631 632 tc.expect(eksMock.EXPECT()) 633 s := NewService(scope) 634 s.EKSClient = eksMock 635 636 err = s.reconcileEKSEncryptionConfig(makeEksEncryptionConfigs(tc.oldEncryptionConfig)) 637 if tc.expectError { 638 g.Expect(err).To(HaveOccurred()) 639 return 640 } 641 g.Expect(err).To(BeNil()) 642 }) 643 } 644 }