diff --git a/pkg/cloudprovider/providers/aws/BUILD b/pkg/cloudprovider/providers/aws/BUILD index eb41175c317..f029c689234 100644 --- a/pkg/cloudprovider/providers/aws/BUILD +++ b/pkg/cloudprovider/providers/aws/BUILD @@ -10,6 +10,7 @@ go_library( name = "go_default_library", srcs = [ "aws.go", + "aws_fakes.go", "aws_instancegroups.go", "aws_loadbalancer.go", "aws_metrics.go", @@ -68,11 +69,8 @@ go_test( deps = [ "//pkg/kubelet/apis:go_default_library", "//vendor/github.com/aws/aws-sdk-go/aws:go_default_library", - "//vendor/github.com/aws/aws-sdk-go/service/autoscaling:go_default_library", "//vendor/github.com/aws/aws-sdk-go/service/ec2:go_default_library", "//vendor/github.com/aws/aws-sdk-go/service/elb:go_default_library", - "//vendor/github.com/aws/aws-sdk-go/service/kms:go_default_library", - "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/github.com/stretchr/testify/mock:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/cloudprovider/providers/aws/aws_fakes.go b/pkg/cloudprovider/providers/aws/aws_fakes.go new file mode 100644 index 00000000000..069da837db4 --- /dev/null +++ b/pkg/cloudprovider/providers/aws/aws_fakes.go @@ -0,0 +1,430 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package aws + +import ( + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/autoscaling" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/elb" + "github.com/aws/aws-sdk-go/service/kms" + + "github.com/golang/glog" +) + +type FakeAWSServices struct { + region string + instances []*ec2.Instance + selfInstance *ec2.Instance + networkInterfacesMacs []string + networkInterfacesPrivateIPs [][]string + networkInterfacesVpcIDs []string + + ec2 FakeEC2 + elb ELB + asg *FakeASG + metadata *FakeMetadata + kms *FakeKMS +} + +func NewFakeAWSServices(clusterId string) *FakeAWSServices { + s := &FakeAWSServices{} + s.region = "us-east-1" + s.ec2 = &FakeEC2Impl{aws: s} + s.elb = &FakeELB{aws: s} + s.asg = &FakeASG{aws: s} + s.metadata = &FakeMetadata{aws: s} + s.kms = &FakeKMS{aws: s} + + s.networkInterfacesMacs = []string{"aa:bb:cc:dd:ee:00", "aa:bb:cc:dd:ee:01"} + s.networkInterfacesVpcIDs = []string{"vpc-mac0", "vpc-mac1"} + + selfInstance := &ec2.Instance{} + selfInstance.InstanceId = aws.String("i-self") + selfInstance.Placement = &ec2.Placement{ + AvailabilityZone: aws.String("us-east-1a"), + } + selfInstance.PrivateDnsName = aws.String("ip-172-20-0-100.ec2.internal") + selfInstance.PrivateIpAddress = aws.String("192.168.0.1") + selfInstance.PublicIpAddress = aws.String("1.2.3.4") + s.selfInstance = selfInstance + s.instances = []*ec2.Instance{selfInstance} + + var tag ec2.Tag + tag.Key = aws.String(TagNameKubernetesClusterLegacy) + tag.Value = aws.String(clusterId) + selfInstance.Tags = []*ec2.Tag{&tag} + + return s +} + +func (s *FakeAWSServices) WithAz(az string) *FakeAWSServices { + if s.selfInstance.Placement == nil { + s.selfInstance.Placement = &ec2.Placement{} + } + s.selfInstance.Placement.AvailabilityZone = aws.String(az) + return s +} + +func (s *FakeAWSServices) Compute(region string) (EC2, error) { + return s.ec2, nil +} + +func (s *FakeAWSServices) LoadBalancing(region string) (ELB, error) { + return s.elb, nil +} + +func (s *FakeAWSServices) Autoscaling(region string) (ASG, error) { + return s.asg, nil +} + +func (s *FakeAWSServices) Metadata() (EC2Metadata, error) { + return s.metadata, nil +} + +func (s *FakeAWSServices) KeyManagement(region string) (KMS, error) { + return s.kms, nil +} + +type FakeEC2 interface { + EC2 + CreateSubnet(*ec2.Subnet) (*ec2.CreateSubnetOutput, error) + RemoveSubnets() + CreateRouteTable(*ec2.RouteTable) (*ec2.CreateRouteTableOutput, error) + RemoveRouteTables() +} + +type FakeEC2Impl struct { + aws *FakeAWSServices + Subnets []*ec2.Subnet + DescribeSubnetsInput *ec2.DescribeSubnetsInput + RouteTables []*ec2.RouteTable + DescribeRouteTablesInput *ec2.DescribeRouteTablesInput +} + +func (ec2i *FakeEC2Impl) DescribeInstances(request *ec2.DescribeInstancesInput) ([]*ec2.Instance, error) { + matches := []*ec2.Instance{} + for _, instance := range ec2i.aws.instances { + if request.InstanceIds != nil { + if instance.InstanceId == nil { + glog.Warning("Instance with no instance id: ", instance) + continue + } + + found := false + for _, instanceID := range request.InstanceIds { + if *instanceID == *instance.InstanceId { + found = true + break + } + } + if !found { + continue + } + } + if request.Filters != nil { + allMatch := true + for _, filter := range request.Filters { + if !instanceMatchesFilter(instance, filter) { + allMatch = false + break + } + } + if !allMatch { + continue + } + } + matches = append(matches, instance) + } + + return matches, nil +} + +func (ec2i *FakeEC2Impl) AttachVolume(request *ec2.AttachVolumeInput) (resp *ec2.VolumeAttachment, err error) { + panic("Not implemented") +} + +func (ec2i *FakeEC2Impl) DetachVolume(request *ec2.DetachVolumeInput) (resp *ec2.VolumeAttachment, err error) { + panic("Not implemented") +} + +func (ec2i *FakeEC2Impl) DescribeVolumes(request *ec2.DescribeVolumesInput) ([]*ec2.Volume, error) { + panic("Not implemented") +} + +func (ec2i *FakeEC2Impl) CreateVolume(request *ec2.CreateVolumeInput) (resp *ec2.Volume, err error) { + panic("Not implemented") +} + +func (ec2i *FakeEC2Impl) DeleteVolume(request *ec2.DeleteVolumeInput) (resp *ec2.DeleteVolumeOutput, err error) { + panic("Not implemented") +} + +func (ec2i *FakeEC2Impl) DescribeSecurityGroups(request *ec2.DescribeSecurityGroupsInput) ([]*ec2.SecurityGroup, error) { + panic("Not implemented") +} + +func (ec2i *FakeEC2Impl) CreateSecurityGroup(*ec2.CreateSecurityGroupInput) (*ec2.CreateSecurityGroupOutput, error) { + panic("Not implemented") +} + +func (ec2i *FakeEC2Impl) DeleteSecurityGroup(*ec2.DeleteSecurityGroupInput) (*ec2.DeleteSecurityGroupOutput, error) { + panic("Not implemented") +} + +func (ec2i *FakeEC2Impl) AuthorizeSecurityGroupIngress(*ec2.AuthorizeSecurityGroupIngressInput) (*ec2.AuthorizeSecurityGroupIngressOutput, error) { + panic("Not implemented") +} + +func (ec2i *FakeEC2Impl) RevokeSecurityGroupIngress(*ec2.RevokeSecurityGroupIngressInput) (*ec2.RevokeSecurityGroupIngressOutput, error) { + panic("Not implemented") +} + +func (ec2i *FakeEC2Impl) CreateSubnet(request *ec2.Subnet) (*ec2.CreateSubnetOutput, error) { + ec2i.Subnets = append(ec2i.Subnets, request) + response := &ec2.CreateSubnetOutput{ + Subnet: request, + } + return response, nil +} + +func (ec2i *FakeEC2Impl) DescribeSubnets(request *ec2.DescribeSubnetsInput) ([]*ec2.Subnet, error) { + ec2i.DescribeSubnetsInput = request + return ec2i.Subnets, nil +} + +func (ec2i *FakeEC2Impl) RemoveSubnets() { + ec2i.Subnets = ec2i.Subnets[:0] +} + +func (ec2i *FakeEC2Impl) CreateTags(*ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error) { + panic("Not implemented") +} + +func (ec2i *FakeEC2Impl) DescribeRouteTables(request *ec2.DescribeRouteTablesInput) ([]*ec2.RouteTable, error) { + ec2i.DescribeRouteTablesInput = request + return ec2i.RouteTables, nil +} + +func (ec2i *FakeEC2Impl) CreateRouteTable(request *ec2.RouteTable) (*ec2.CreateRouteTableOutput, error) { + ec2i.RouteTables = append(ec2i.RouteTables, request) + response := &ec2.CreateRouteTableOutput{ + RouteTable: request, + } + return response, nil +} + +func (ec2i *FakeEC2Impl) RemoveRouteTables() { + ec2i.RouteTables = ec2i.RouteTables[:0] +} + +func (ec2i *FakeEC2Impl) CreateRoute(request *ec2.CreateRouteInput) (*ec2.CreateRouteOutput, error) { + panic("Not implemented") +} + +func (ec2i *FakeEC2Impl) DeleteRoute(request *ec2.DeleteRouteInput) (*ec2.DeleteRouteOutput, error) { + panic("Not implemented") +} + +func (ec2i *FakeEC2Impl) ModifyInstanceAttribute(request *ec2.ModifyInstanceAttributeInput) (*ec2.ModifyInstanceAttributeOutput, error) { + panic("Not implemented") +} + +type FakeMetadata struct { + aws *FakeAWSServices +} + +func (m *FakeMetadata) GetMetadata(key string) (string, error) { + networkInterfacesPrefix := "network/interfaces/macs/" + i := m.aws.selfInstance + if key == "placement/availability-zone" { + az := "" + if i.Placement != nil { + az = aws.StringValue(i.Placement.AvailabilityZone) + } + return az, nil + } else if key == "instance-id" { + return aws.StringValue(i.InstanceId), nil + } else if key == "local-hostname" { + return aws.StringValue(i.PrivateDnsName), nil + } else if key == "public-hostname" { + return aws.StringValue(i.PublicDnsName), nil + } else if key == "local-ipv4" { + return aws.StringValue(i.PrivateIpAddress), nil + } else if key == "public-ipv4" { + return aws.StringValue(i.PublicIpAddress), nil + } else if strings.HasPrefix(key, networkInterfacesPrefix) { + if key == networkInterfacesPrefix { + return strings.Join(m.aws.networkInterfacesMacs, "/\n") + "/\n", nil + } else { + keySplit := strings.Split(key, "/") + macParam := keySplit[3] + if len(keySplit) == 5 && keySplit[4] == "vpc-id" { + for i, macElem := range m.aws.networkInterfacesMacs { + if macParam == macElem { + return m.aws.networkInterfacesVpcIDs[i], nil + } + } + } + if len(keySplit) == 5 && keySplit[4] == "local-ipv4s" { + for i, macElem := range m.aws.networkInterfacesMacs { + if macParam == macElem { + return strings.Join(m.aws.networkInterfacesPrivateIPs[i], "/\n"), nil + } + } + } + return "", nil + } + } else { + return "", nil + } +} + +type FakeELB struct { + aws *FakeAWSServices +} + +func (elb *FakeELB) CreateLoadBalancer(*elb.CreateLoadBalancerInput) (*elb.CreateLoadBalancerOutput, error) { + panic("Not implemented") +} + +func (elb *FakeELB) DeleteLoadBalancer(input *elb.DeleteLoadBalancerInput) (*elb.DeleteLoadBalancerOutput, error) { + panic("Not implemented") +} + +func (elb *FakeELB) DescribeLoadBalancers(input *elb.DescribeLoadBalancersInput) (*elb.DescribeLoadBalancersOutput, error) { + panic("Not implemented") +} + +func (elb *FakeELB) RegisterInstancesWithLoadBalancer(*elb.RegisterInstancesWithLoadBalancerInput) (*elb.RegisterInstancesWithLoadBalancerOutput, error) { + panic("Not implemented") +} + +func (elb *FakeELB) DeregisterInstancesFromLoadBalancer(*elb.DeregisterInstancesFromLoadBalancerInput) (*elb.DeregisterInstancesFromLoadBalancerOutput, error) { + panic("Not implemented") +} + +func (elb *FakeELB) DetachLoadBalancerFromSubnets(*elb.DetachLoadBalancerFromSubnetsInput) (*elb.DetachLoadBalancerFromSubnetsOutput, error) { + panic("Not implemented") +} + +func (elb *FakeELB) AttachLoadBalancerToSubnets(*elb.AttachLoadBalancerToSubnetsInput) (*elb.AttachLoadBalancerToSubnetsOutput, error) { + panic("Not implemented") +} + +func (elb *FakeELB) CreateLoadBalancerListeners(*elb.CreateLoadBalancerListenersInput) (*elb.CreateLoadBalancerListenersOutput, error) { + panic("Not implemented") +} + +func (elb *FakeELB) DeleteLoadBalancerListeners(*elb.DeleteLoadBalancerListenersInput) (*elb.DeleteLoadBalancerListenersOutput, error) { + panic("Not implemented") +} + +func (elb *FakeELB) ApplySecurityGroupsToLoadBalancer(*elb.ApplySecurityGroupsToLoadBalancerInput) (*elb.ApplySecurityGroupsToLoadBalancerOutput, error) { + panic("Not implemented") +} + +func (elb *FakeELB) ConfigureHealthCheck(*elb.ConfigureHealthCheckInput) (*elb.ConfigureHealthCheckOutput, error) { + panic("Not implemented") +} + +func (elb *FakeELB) CreateLoadBalancerPolicy(*elb.CreateLoadBalancerPolicyInput) (*elb.CreateLoadBalancerPolicyOutput, error) { + panic("Not implemented") +} + +func (elb *FakeELB) SetLoadBalancerPoliciesForBackendServer(*elb.SetLoadBalancerPoliciesForBackendServerInput) (*elb.SetLoadBalancerPoliciesForBackendServerOutput, error) { + panic("Not implemented") +} + +func (elb *FakeELB) DescribeLoadBalancerAttributes(*elb.DescribeLoadBalancerAttributesInput) (*elb.DescribeLoadBalancerAttributesOutput, error) { + panic("Not implemented") +} + +func (elb *FakeELB) ModifyLoadBalancerAttributes(*elb.ModifyLoadBalancerAttributesInput) (*elb.ModifyLoadBalancerAttributesOutput, error) { + panic("Not implemented") +} + +func (self *FakeELB) expectDescribeLoadBalancers(loadBalancerName string) { + panic("Not implemented") +} + +type FakeASG struct { + aws *FakeAWSServices +} + +func (a *FakeASG) UpdateAutoScalingGroup(*autoscaling.UpdateAutoScalingGroupInput) (*autoscaling.UpdateAutoScalingGroupOutput, error) { + panic("Not implemented") +} + +func (a *FakeASG) DescribeAutoScalingGroups(*autoscaling.DescribeAutoScalingGroupsInput) (*autoscaling.DescribeAutoScalingGroupsOutput, error) { + panic("Not implemented") +} + +type FakeKMS struct { + aws *FakeAWSServices +} + +func (kms *FakeKMS) DescribeKey(*kms.DescribeKeyInput) (*kms.DescribeKeyOutput, error) { + panic("Not implemented") +} + +func instanceMatchesFilter(instance *ec2.Instance, filter *ec2.Filter) bool { + name := *filter.Name + if name == "private-dns-name" { + if instance.PrivateDnsName == nil { + return false + } + return contains(filter.Values, *instance.PrivateDnsName) + } + + if name == "instance-state-name" { + return contains(filter.Values, *instance.State.Name) + } + + if name == "tag-key" { + for _, instanceTag := range instance.Tags { + if contains(filter.Values, aws.StringValue(instanceTag.Key)) { + return true + } + } + return false + } + + if strings.HasPrefix(name, "tag:") { + tagName := name[4:] + for _, instanceTag := range instance.Tags { + if aws.StringValue(instanceTag.Key) == tagName && contains(filter.Values, aws.StringValue(instanceTag.Value)) { + return true + } + } + return false + } + + panic("Unknown filter name: " + name) +} + +func contains(haystack []*string, needle string) bool { + for _, s := range haystack { + // (deliberately panic if s == nil) + if needle == *s { + return true + } + } + return false +} diff --git a/pkg/cloudprovider/providers/aws/aws_test.go b/pkg/cloudprovider/providers/aws/aws_test.go index c922ededaf4..6609638e8b2 100644 --- a/pkg/cloudprovider/providers/aws/aws_test.go +++ b/pkg/cloudprovider/providers/aws/aws_test.go @@ -26,10 +26,6 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/elb" - "github.com/aws/aws-sdk-go/service/kms" - - "github.com/aws/aws-sdk-go/service/autoscaling" - "github.com/golang/glog" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" @@ -43,6 +39,49 @@ import ( const TestClusterId = "clusterid.test" const TestClusterName = "testCluster" +type MockedFakeEC2 struct { + *FakeEC2Impl + mock.Mock +} + +func (m *MockedFakeEC2) expectDescribeSecurityGroups(clusterId, groupName, clusterID string) { + tags := []*ec2.Tag{ + {Key: aws.String(TagNameKubernetesClusterLegacy), Value: aws.String(clusterId)}, + {Key: aws.String(fmt.Sprintf("%s%s", TagNameKubernetesClusterPrefix, clusterId)), Value: aws.String(ResourceLifecycleOwned)}, + } + + m.On("DescribeSecurityGroups", &ec2.DescribeSecurityGroupsInput{Filters: []*ec2.Filter{ + newEc2Filter("group-name", groupName), + newEc2Filter("vpc-id", ""), + }}).Return([]*ec2.SecurityGroup{{Tags: tags}}) +} + +func (m *MockedFakeEC2) DescribeVolumes(request *ec2.DescribeVolumesInput) ([]*ec2.Volume, error) { + args := m.Called(request) + return args.Get(0).([]*ec2.Volume), nil +} + +func (m *MockedFakeEC2) DescribeSecurityGroups(request *ec2.DescribeSecurityGroupsInput) ([]*ec2.SecurityGroup, error) { + args := m.Called(request) + return args.Get(0).([]*ec2.SecurityGroup), nil +} + +type MockedFakeELB struct { + *FakeELB + mock.Mock +} + +func (m *MockedFakeELB) DescribeLoadBalancers(input *elb.DescribeLoadBalancersInput) (*elb.DescribeLoadBalancersOutput, error) { + args := m.Called(input) + return args.Get(0).(*elb.DescribeLoadBalancersOutput), nil +} + +func (m *MockedFakeELB) expectDescribeLoadBalancers(loadBalancerName string) { + m.On("DescribeLoadBalancers", &elb.DescribeLoadBalancersInput{LoadBalancerNames: []*string{aws.String(loadBalancerName)}}).Return(&elb.DescribeLoadBalancersOutput{ + LoadBalancerDescriptions: []*elb.LoadBalancerDescription{{}}, + }) +} + func TestReadAWSCloudConfig(t *testing.T) { tests := []struct { name string @@ -75,17 +114,17 @@ func TestReadAWSCloudConfig(t *testing.T) { }, { "No zone in config, metadata does not have zone", - strings.NewReader("[global]\n"), NewFakeAWSServices().withAz(""), + strings.NewReader("[global]\n"), newMockedFakeAWSServices(TestClusterId).WithAz(""), true, "", }, { "No zone in config, metadata has zone", - strings.NewReader("[global]\n"), NewFakeAWSServices(), + strings.NewReader("[global]\n"), newMockedFakeAWSServices(TestClusterId), false, "us-east-1a", }, { "Zone in config should take precedence over metadata", - strings.NewReader("[global]\nzone = eu-west-1a"), NewFakeAWSServices(), + strings.NewReader("[global]\nzone = eu-west-1a"), newMockedFakeAWSServices(TestClusterId), false, "eu-west-1a", }, } @@ -113,80 +152,6 @@ func TestReadAWSCloudConfig(t *testing.T) { } } -type FakeAWSServices struct { - region string - instances []*ec2.Instance - selfInstance *ec2.Instance - networkInterfacesMacs []string - networkInterfacesPrivateIPs [][]string - networkInterfacesVpcIDs []string - - ec2 *FakeEC2 - elb *FakeELB - asg *FakeASG - metadata *FakeMetadata - kms *FakeKMS -} - -func NewFakeAWSServices() *FakeAWSServices { - s := &FakeAWSServices{} - s.region = "us-east-1" - s.ec2 = &FakeEC2{aws: s} - s.elb = &FakeELB{aws: s} - s.asg = &FakeASG{aws: s} - s.metadata = &FakeMetadata{aws: s} - s.kms = &FakeKMS{aws: s} - - s.networkInterfacesMacs = []string{"aa:bb:cc:dd:ee:00", "aa:bb:cc:dd:ee:01"} - s.networkInterfacesVpcIDs = []string{"vpc-mac0", "vpc-mac1"} - - selfInstance := &ec2.Instance{} - selfInstance.InstanceId = aws.String("i-self") - selfInstance.Placement = &ec2.Placement{ - AvailabilityZone: aws.String("us-east-1a"), - } - selfInstance.PrivateDnsName = aws.String("ip-172-20-0-100.ec2.internal") - selfInstance.PrivateIpAddress = aws.String("192.168.0.1") - selfInstance.PublicIpAddress = aws.String("1.2.3.4") - s.selfInstance = selfInstance - s.instances = []*ec2.Instance{selfInstance} - - var tag ec2.Tag - tag.Key = aws.String(TagNameKubernetesClusterLegacy) - tag.Value = aws.String(TestClusterId) - selfInstance.Tags = []*ec2.Tag{&tag} - - return s -} - -func (s *FakeAWSServices) withAz(az string) *FakeAWSServices { - if s.selfInstance.Placement == nil { - s.selfInstance.Placement = &ec2.Placement{} - } - s.selfInstance.Placement.AvailabilityZone = aws.String(az) - return s -} - -func (s *FakeAWSServices) Compute(region string) (EC2, error) { - return s.ec2, nil -} - -func (s *FakeAWSServices) LoadBalancing(region string) (ELB, error) { - return s.elb, nil -} - -func (s *FakeAWSServices) Autoscaling(region string) (ASG, error) { - return s.asg, nil -} - -func (s *FakeAWSServices) Metadata() (EC2Metadata, error) { - return s.metadata, nil -} - -func (s *FakeAWSServices) KeyManagement(region string) (KMS, error) { - return s.kms, nil -} - func TestNewAWSCloud(t *testing.T) { tests := []struct { name string @@ -199,24 +164,24 @@ func TestNewAWSCloud(t *testing.T) { }{ { "No config reader", - nil, NewFakeAWSServices().withAz(""), + nil, newMockedFakeAWSServices(TestClusterId).WithAz(""), true, "", }, { "Config specifies valid zone", - strings.NewReader("[global]\nzone = eu-west-1a"), NewFakeAWSServices(), + strings.NewReader("[global]\nzone = eu-west-1a"), newMockedFakeAWSServices(TestClusterId), false, "eu-west-1", }, { "Gets zone from metadata when not in config", strings.NewReader("[global]\n"), - NewFakeAWSServices(), + newMockedFakeAWSServices(TestClusterId), false, "us-east-1", }, { "No zone in config or metadata", strings.NewReader("[global]\n"), - NewFakeAWSServices().withAz(""), + newMockedFakeAWSServices(TestClusterId).WithAz(""), true, "", }, } @@ -239,304 +204,8 @@ func TestNewAWSCloud(t *testing.T) { } } -type FakeEC2 struct { - aws *FakeAWSServices - Subnets []*ec2.Subnet - DescribeSubnetsInput *ec2.DescribeSubnetsInput - RouteTables []*ec2.RouteTable - DescribeRouteTablesInput *ec2.DescribeRouteTablesInput - mock.Mock -} - -func contains(haystack []*string, needle string) bool { - for _, s := range haystack { - // (deliberately panic if s == nil) - if needle == *s { - return true - } - } - return false -} - -func instanceMatchesFilter(instance *ec2.Instance, filter *ec2.Filter) bool { - name := *filter.Name - if name == "private-dns-name" { - if instance.PrivateDnsName == nil { - return false - } - return contains(filter.Values, *instance.PrivateDnsName) - } - - if name == "instance-state-name" { - return contains(filter.Values, *instance.State.Name) - } - - if name == "tag-key" { - for _, instanceTag := range instance.Tags { - if contains(filter.Values, aws.StringValue(instanceTag.Key)) { - return true - } - } - return false - } - - if strings.HasPrefix(name, "tag:") { - tagName := name[4:] - for _, instanceTag := range instance.Tags { - if aws.StringValue(instanceTag.Key) == tagName && contains(filter.Values, aws.StringValue(instanceTag.Value)) { - return true - } - } - return false - } - - panic("Unknown filter name: " + name) -} - -func (self *FakeEC2) DescribeInstances(request *ec2.DescribeInstancesInput) ([]*ec2.Instance, error) { - matches := []*ec2.Instance{} - for _, instance := range self.aws.instances { - if request.InstanceIds != nil { - if instance.InstanceId == nil { - glog.Warning("Instance with no instance id: ", instance) - continue - } - - found := false - for _, instanceID := range request.InstanceIds { - if *instanceID == *instance.InstanceId { - found = true - break - } - } - if !found { - continue - } - } - if request.Filters != nil { - allMatch := true - for _, filter := range request.Filters { - if !instanceMatchesFilter(instance, filter) { - allMatch = false - break - } - } - if !allMatch { - continue - } - } - matches = append(matches, instance) - } - - return matches, nil -} - -type FakeMetadata struct { - aws *FakeAWSServices -} - -func (self *FakeMetadata) GetMetadata(key string) (string, error) { - networkInterfacesPrefix := "network/interfaces/macs/" - i := self.aws.selfInstance - if key == "placement/availability-zone" { - az := "" - if i.Placement != nil { - az = aws.StringValue(i.Placement.AvailabilityZone) - } - return az, nil - } else if key == "instance-id" { - return aws.StringValue(i.InstanceId), nil - } else if key == "local-hostname" { - return aws.StringValue(i.PrivateDnsName), nil - } else if key == "public-hostname" { - return aws.StringValue(i.PublicDnsName), nil - } else if key == "local-ipv4" { - return aws.StringValue(i.PrivateIpAddress), nil - } else if key == "public-ipv4" { - return aws.StringValue(i.PublicIpAddress), nil - } else if strings.HasPrefix(key, networkInterfacesPrefix) { - if key == networkInterfacesPrefix { - return strings.Join(self.aws.networkInterfacesMacs, "/\n") + "/\n", nil - } else { - keySplit := strings.Split(key, "/") - macParam := keySplit[3] - if len(keySplit) == 5 && keySplit[4] == "vpc-id" { - for i, macElem := range self.aws.networkInterfacesMacs { - if macParam == macElem { - return self.aws.networkInterfacesVpcIDs[i], nil - } - } - } - if len(keySplit) == 5 && keySplit[4] == "local-ipv4s" { - for i, macElem := range self.aws.networkInterfacesMacs { - if macParam == macElem { - return strings.Join(self.aws.networkInterfacesPrivateIPs[i], "/\n"), nil - } - } - } - return "", nil - } - } else { - return "", nil - } -} - -func (ec2 *FakeEC2) AttachVolume(request *ec2.AttachVolumeInput) (resp *ec2.VolumeAttachment, err error) { - panic("Not implemented") -} - -func (ec2 *FakeEC2) DetachVolume(request *ec2.DetachVolumeInput) (resp *ec2.VolumeAttachment, err error) { - panic("Not implemented") -} - -func (e *FakeEC2) DescribeVolumes(request *ec2.DescribeVolumesInput) ([]*ec2.Volume, error) { - args := e.Called(request) - return args.Get(0).([]*ec2.Volume), nil -} - -func (ec2 *FakeEC2) CreateVolume(request *ec2.CreateVolumeInput) (resp *ec2.Volume, err error) { - panic("Not implemented") -} - -func (ec2 *FakeEC2) DeleteVolume(request *ec2.DeleteVolumeInput) (resp *ec2.DeleteVolumeOutput, err error) { - panic("Not implemented") -} - -func (e *FakeEC2) DescribeSecurityGroups(request *ec2.DescribeSecurityGroupsInput) ([]*ec2.SecurityGroup, error) { - args := e.Called(request) - return args.Get(0).([]*ec2.SecurityGroup), nil -} - -func (ec2 *FakeEC2) CreateSecurityGroup(*ec2.CreateSecurityGroupInput) (*ec2.CreateSecurityGroupOutput, error) { - panic("Not implemented") -} - -func (ec2 *FakeEC2) DeleteSecurityGroup(*ec2.DeleteSecurityGroupInput) (*ec2.DeleteSecurityGroupOutput, error) { - panic("Not implemented") -} - -func (ec2 *FakeEC2) AuthorizeSecurityGroupIngress(*ec2.AuthorizeSecurityGroupIngressInput) (*ec2.AuthorizeSecurityGroupIngressOutput, error) { - panic("Not implemented") -} - -func (ec2 *FakeEC2) RevokeSecurityGroupIngress(*ec2.RevokeSecurityGroupIngressInput) (*ec2.RevokeSecurityGroupIngressOutput, error) { - panic("Not implemented") -} - -func (ec2 *FakeEC2) DescribeSubnets(request *ec2.DescribeSubnetsInput) ([]*ec2.Subnet, error) { - ec2.DescribeSubnetsInput = request - return ec2.Subnets, nil -} - -func (ec2 *FakeEC2) CreateTags(*ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error) { - panic("Not implemented") -} - -func (ec2 *FakeEC2) DescribeRouteTables(request *ec2.DescribeRouteTablesInput) ([]*ec2.RouteTable, error) { - ec2.DescribeRouteTablesInput = request - return ec2.RouteTables, nil -} - -func (s *FakeEC2) CreateRoute(request *ec2.CreateRouteInput) (*ec2.CreateRouteOutput, error) { - panic("Not implemented") -} - -func (s *FakeEC2) DeleteRoute(request *ec2.DeleteRouteInput) (*ec2.DeleteRouteOutput, error) { - panic("Not implemented") -} - -func (s *FakeEC2) ModifyInstanceAttribute(request *ec2.ModifyInstanceAttributeInput) (*ec2.ModifyInstanceAttributeOutput, error) { - panic("Not implemented") -} - -type FakeELB struct { - aws *FakeAWSServices - mock.Mock -} - -func (ec2 *FakeELB) CreateLoadBalancer(*elb.CreateLoadBalancerInput) (*elb.CreateLoadBalancerOutput, error) { - panic("Not implemented") -} - -func (ec2 *FakeELB) DeleteLoadBalancer(input *elb.DeleteLoadBalancerInput) (*elb.DeleteLoadBalancerOutput, error) { - panic("Not implemented") -} - -func (ec2 *FakeELB) DescribeLoadBalancers(input *elb.DescribeLoadBalancersInput) (*elb.DescribeLoadBalancersOutput, error) { - args := ec2.Called(input) - return args.Get(0).(*elb.DescribeLoadBalancersOutput), nil -} -func (ec2 *FakeELB) RegisterInstancesWithLoadBalancer(*elb.RegisterInstancesWithLoadBalancerInput) (*elb.RegisterInstancesWithLoadBalancerOutput, error) { - panic("Not implemented") -} - -func (ec2 *FakeELB) DeregisterInstancesFromLoadBalancer(*elb.DeregisterInstancesFromLoadBalancerInput) (*elb.DeregisterInstancesFromLoadBalancerOutput, error) { - panic("Not implemented") -} - -func (ec2 *FakeELB) DetachLoadBalancerFromSubnets(*elb.DetachLoadBalancerFromSubnetsInput) (*elb.DetachLoadBalancerFromSubnetsOutput, error) { - panic("Not implemented") -} - -func (ec2 *FakeELB) AttachLoadBalancerToSubnets(*elb.AttachLoadBalancerToSubnetsInput) (*elb.AttachLoadBalancerToSubnetsOutput, error) { - panic("Not implemented") -} - -func (ec2 *FakeELB) CreateLoadBalancerListeners(*elb.CreateLoadBalancerListenersInput) (*elb.CreateLoadBalancerListenersOutput, error) { - panic("Not implemented") -} - -func (ec2 *FakeELB) DeleteLoadBalancerListeners(*elb.DeleteLoadBalancerListenersInput) (*elb.DeleteLoadBalancerListenersOutput, error) { - panic("Not implemented") -} - -func (ec2 *FakeELB) ApplySecurityGroupsToLoadBalancer(*elb.ApplySecurityGroupsToLoadBalancerInput) (*elb.ApplySecurityGroupsToLoadBalancerOutput, error) { - panic("Not implemented") -} - -func (elb *FakeELB) ConfigureHealthCheck(*elb.ConfigureHealthCheckInput) (*elb.ConfigureHealthCheckOutput, error) { - panic("Not implemented") -} - -func (elb *FakeELB) CreateLoadBalancerPolicy(*elb.CreateLoadBalancerPolicyInput) (*elb.CreateLoadBalancerPolicyOutput, error) { - panic("Not implemented") -} - -func (elb *FakeELB) SetLoadBalancerPoliciesForBackendServer(*elb.SetLoadBalancerPoliciesForBackendServerInput) (*elb.SetLoadBalancerPoliciesForBackendServerOutput, error) { - panic("Not implemented") -} - -func (elb *FakeELB) DescribeLoadBalancerAttributes(*elb.DescribeLoadBalancerAttributesInput) (*elb.DescribeLoadBalancerAttributesOutput, error) { - panic("Not implemented") -} - -func (elb *FakeELB) ModifyLoadBalancerAttributes(*elb.ModifyLoadBalancerAttributesInput) (*elb.ModifyLoadBalancerAttributesOutput, error) { - panic("Not implemented") -} - -type FakeASG struct { - aws *FakeAWSServices -} - -func (a *FakeASG) UpdateAutoScalingGroup(*autoscaling.UpdateAutoScalingGroupInput) (*autoscaling.UpdateAutoScalingGroupOutput, error) { - panic("Not implemented") -} - -func (a *FakeASG) DescribeAutoScalingGroups(*autoscaling.DescribeAutoScalingGroupsInput) (*autoscaling.DescribeAutoScalingGroupsOutput, error) { - panic("Not implemented") -} - -type FakeKMS struct { - aws *FakeAWSServices - mock.Mock -} - -func (kms *FakeKMS) DescribeKey(*kms.DescribeKeyInput) (*kms.DescribeKeyOutput, error) { - panic("Not implemented") -} - func mockInstancesResp(selfInstance *ec2.Instance, instances []*ec2.Instance) (*Cloud, *FakeAWSServices) { - awsServices := NewFakeAWSServices() + awsServices := newMockedFakeAWSServices(TestClusterId) awsServices.instances = instances awsServices.selfInstance = selfInstance awsCloud, err := newAWSCloud(nil, awsServices) @@ -547,7 +216,7 @@ func mockInstancesResp(selfInstance *ec2.Instance, instances []*ec2.Instance) (* } func mockAvailabilityZone(availabilityZone string) *Cloud { - awsServices := NewFakeAWSServices().withAz(availabilityZone) + awsServices := newMockedFakeAWSServices(TestClusterId).WithAz(availabilityZone) awsCloud, err := newAWSCloud(nil, awsServices) if err != nil { panic(err) @@ -694,7 +363,7 @@ func TestGetRegion(t *testing.T) { } func TestFindVPCID(t *testing.T) { - awsServices := NewFakeAWSServices() + awsServices := newMockedFakeAWSServices(TestClusterId) c, err := newAWSCloud(strings.NewReader("[global]"), awsServices) if err != nil { t.Errorf("Error building aws cloud: %v", err) @@ -768,7 +437,7 @@ func constructRouteTable(subnetID string, public bool) *ec2.RouteTable { } func TestSubnetIDsinVPC(t *testing.T) { - awsServices := NewFakeAWSServices() + awsServices := newMockedFakeAWSServices(TestClusterId) c, err := newAWSCloud(strings.NewReader("[global]"), awsServices) if err != nil { t.Errorf("Error building aws cloud: %v", err) @@ -786,14 +455,22 @@ func TestSubnetIDsinVPC(t *testing.T) { subnets[2] = make(map[string]string) subnets[2]["id"] = "subnet-c0000001" subnets[2]["az"] = "af-south-1c" - awsServices.ec2.Subnets = constructSubnets(subnets) + constructedSubnets := constructSubnets(subnets) + awsServices.ec2.RemoveSubnets() + for _, subnet := range constructedSubnets { + awsServices.ec2.CreateSubnet(subnet) + } routeTables := map[string]bool{ "subnet-a0000001": true, "subnet-b0000001": true, "subnet-c0000001": true, } - awsServices.ec2.RouteTables = constructRouteTables(routeTables) + constructedRouteTables := constructRouteTables(routeTables) + awsServices.ec2.RemoveRouteTables() + for _, rt := range constructedRouteTables { + awsServices.ec2.CreateRouteTable(rt) + } result, err := c.findELBSubnets(false) if err != nil { @@ -819,7 +496,11 @@ func TestSubnetIDsinVPC(t *testing.T) { } // test implicit routing table - when subnets are not explicitly linked to a table they should use main - awsServices.ec2.RouteTables = constructRouteTables(map[string]bool{}) + constructedRouteTables = constructRouteTables(map[string]bool{}) + awsServices.ec2.RemoveRouteTables() + for _, rt := range constructedRouteTables { + awsServices.ec2.CreateRouteTable(rt) + } result, err = c.findELBSubnets(false) if err != nil { @@ -854,10 +535,18 @@ func TestSubnetIDsinVPC(t *testing.T) { subnets[4] = make(map[string]string) subnets[4]["id"] = "subnet-c0000002" subnets[4]["az"] = "af-south-1c" - awsServices.ec2.Subnets = constructSubnets(subnets) + constructedSubnets = constructSubnets(subnets) + awsServices.ec2.RemoveSubnets() + for _, subnet := range constructedSubnets { + awsServices.ec2.CreateSubnet(subnet) + } routeTables["subnet-c0000000"] = true routeTables["subnet-c0000002"] = true - awsServices.ec2.RouteTables = constructRouteTables(routeTables) + constructedRouteTables = constructRouteTables(routeTables) + awsServices.ec2.RemoveRouteTables() + for _, rt := range constructedRouteTables { + awsServices.ec2.CreateRouteTable(rt) + } result, err = c.findELBSubnets(false) if err != nil { @@ -889,14 +578,23 @@ func TestSubnetIDsinVPC(t *testing.T) { subnets[5]["id"] = "subnet-d0000002" subnets[5]["az"] = "af-south-1b" - awsServices.ec2.Subnets = constructSubnets(subnets) + constructedSubnets = constructSubnets(subnets) + awsServices.ec2.RemoveSubnets() + for _, subnet := range constructedSubnets { + awsServices.ec2.CreateSubnet(subnet) + } + routeTables["subnet-a0000001"] = false routeTables["subnet-b0000001"] = false routeTables["subnet-c0000001"] = false routeTables["subnet-c0000000"] = true routeTables["subnet-d0000001"] = true routeTables["subnet-d0000002"] = true - awsServices.ec2.RouteTables = constructRouteTables(routeTables) + constructedRouteTables = constructRouteTables(routeTables) + awsServices.ec2.RemoveRouteTables() + for _, rt := range constructedRouteTables { + awsServices.ec2.CreateRouteTable(rt) + } result, err = c.findELBSubnets(false) if err != nil { t.Errorf("Error listing subnets: %v", err) @@ -1051,7 +749,7 @@ func TestIpPermissionExistsHandlesMultipleGroupIdsWithUserIds(t *testing.T) { } func TestFindInstanceByNodeNameExcludesTerminatedInstances(t *testing.T) { - awsServices := NewFakeAWSServices() + awsServices := newMockedFakeAWSServices(TestClusterId) nodeName := types.NodeName("my-dns.internal") @@ -1094,7 +792,7 @@ func TestFindInstanceByNodeNameExcludesTerminatedInstances(t *testing.T) { } func TestGetInstanceByNodeNameBatching(t *testing.T) { - awsServices := NewFakeAWSServices() + awsServices := newMockedFakeAWSServices(TestClusterId) c, err := newAWSCloud(strings.NewReader("[global]"), awsServices) assert.Nil(t, err, "Error building aws cloud: %v", err) var tag ec2.Tag @@ -1121,12 +819,12 @@ func TestGetInstanceByNodeNameBatching(t *testing.T) { } func TestGetVolumeLabels(t *testing.T) { - awsServices := NewFakeAWSServices() + awsServices := newMockedFakeAWSServices(TestClusterId) c, err := newAWSCloud(strings.NewReader("[global]"), awsServices) assert.Nil(t, err, "Error building aws cloud: %v", err) volumeId := awsVolumeID("vol-VolumeId") expectedVolumeRequest := &ec2.DescribeVolumesInput{VolumeIds: []*string{volumeId.awsString()}} - awsServices.ec2.On("DescribeVolumes", expectedVolumeRequest).Return([]*ec2.Volume{ + awsServices.ec2.(*MockedFakeEC2).On("DescribeVolumes", expectedVolumeRequest).Return([]*ec2.Volume{ { VolumeId: volumeId.awsString(), AvailabilityZone: aws.String("us-east-1a"), @@ -1139,55 +837,37 @@ func TestGetVolumeLabels(t *testing.T) { assert.Equal(t, map[string]string{ kubeletapis.LabelZoneFailureDomain: "us-east-1a", kubeletapis.LabelZoneRegion: "us-east-1"}, labels) - awsServices.ec2.AssertExpectations(t) -} - -func (self *FakeELB) expectDescribeLoadBalancers(loadBalancerName string) { - self.On("DescribeLoadBalancers", &elb.DescribeLoadBalancersInput{LoadBalancerNames: []*string{aws.String(loadBalancerName)}}).Return(&elb.DescribeLoadBalancersOutput{ - LoadBalancerDescriptions: []*elb.LoadBalancerDescription{{}}, - }) -} - -func (self *FakeEC2) expectDescribeSecurityGroups(groupName, clusterID string) { - tags := []*ec2.Tag{ - {Key: aws.String(TagNameKubernetesClusterLegacy), Value: aws.String(TestClusterId)}, - {Key: aws.String(fmt.Sprintf("%s%s", TagNameKubernetesClusterPrefix, TestClusterId)), Value: aws.String(ResourceLifecycleOwned)}, - } - - self.On("DescribeSecurityGroups", &ec2.DescribeSecurityGroupsInput{Filters: []*ec2.Filter{ - newEc2Filter("group-name", groupName), - newEc2Filter("vpc-id", ""), - }}).Return([]*ec2.SecurityGroup{{Tags: tags}}) + awsServices.ec2.(*MockedFakeEC2).AssertExpectations(t) } func TestDescribeLoadBalancerOnDelete(t *testing.T) { - awsServices := NewFakeAWSServices() + awsServices := newMockedFakeAWSServices(TestClusterId) c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices) - awsServices.elb.expectDescribeLoadBalancers("aid") + awsServices.elb.(*MockedFakeELB).expectDescribeLoadBalancers("aid") c.EnsureLoadBalancerDeleted(TestClusterName, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "myservice", UID: "id"}}) } func TestDescribeLoadBalancerOnUpdate(t *testing.T) { - awsServices := NewFakeAWSServices() + awsServices := newMockedFakeAWSServices(TestClusterId) c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices) - awsServices.elb.expectDescribeLoadBalancers("aid") + awsServices.elb.(*MockedFakeELB).expectDescribeLoadBalancers("aid") c.UpdateLoadBalancer(TestClusterName, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "myservice", UID: "id"}}, []*v1.Node{}) } func TestDescribeLoadBalancerOnGet(t *testing.T) { - awsServices := NewFakeAWSServices() + awsServices := newMockedFakeAWSServices(TestClusterId) c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices) - awsServices.elb.expectDescribeLoadBalancers("aid") + awsServices.elb.(*MockedFakeELB).expectDescribeLoadBalancers("aid") c.GetLoadBalancer(TestClusterName, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "myservice", UID: "id"}}) } func TestDescribeLoadBalancerOnEnsure(t *testing.T) { - awsServices := NewFakeAWSServices() + awsServices := newMockedFakeAWSServices(TestClusterId) c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices) - awsServices.elb.expectDescribeLoadBalancers("aid") + awsServices.elb.(*MockedFakeELB).expectDescribeLoadBalancers("aid") c.EnsureLoadBalancer(TestClusterName, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "myservice", UID: "id"}}, []*v1.Node{}) } @@ -1417,7 +1097,7 @@ func TestGetLoadBalancerAdditionalTags(t *testing.T) { } func TestLBExtraSecurityGroupsAnnotation(t *testing.T) { - awsServices := NewFakeAWSServices() + awsServices := newMockedFakeAWSServices(TestClusterId) c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices) sg1 := "sg-000001" @@ -1435,7 +1115,7 @@ func TestLBExtraSecurityGroupsAnnotation(t *testing.T) { {"Multiple SGs specified", fmt.Sprintf("%s, %s", sg1, sg2), []string{sg1, sg2}}, } - awsServices.ec2.expectDescribeSecurityGroups("k8s-elb-aid", "cluster.test") + awsServices.ec2.(*MockedFakeEC2).expectDescribeSecurityGroups(TestClusterId, "k8s-elb-aid", "cluster.test") for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -1449,3 +1129,10 @@ func TestLBExtraSecurityGroupsAnnotation(t *testing.T) { }) } } + +func newMockedFakeAWSServices(id string) *FakeAWSServices { + s := NewFakeAWSServices(id) + s.ec2 = &MockedFakeEC2{FakeEC2Impl: s.ec2.(*FakeEC2Impl)} + s.elb = &MockedFakeELB{FakeELB: s.elb.(*FakeELB)} + return s +} diff --git a/pkg/cloudprovider/providers/aws/regions_test.go b/pkg/cloudprovider/providers/aws/regions_test.go index 74f9a7bd3e5..50352f754f9 100644 --- a/pkg/cloudprovider/providers/aws/regions_test.go +++ b/pkg/cloudprovider/providers/aws/regions_test.go @@ -73,7 +73,7 @@ func TestRecognizesNewRegion(t *testing.T) { t.Fatalf("region already valid: %q", region) } - awsServices := NewFakeAWSServices().withAz(region + "a") + awsServices := NewFakeAWSServices(TestClusterId).WithAz(region + "a") _, err := newAWSCloud(nil, awsServices) if err != nil { t.Errorf("error building AWS cloud: %v", err) diff --git a/pkg/cloudprovider/providers/aws/tags_test.go b/pkg/cloudprovider/providers/aws/tags_test.go index 5721a54bf31..42185a4f941 100644 --- a/pkg/cloudprovider/providers/aws/tags_test.go +++ b/pkg/cloudprovider/providers/aws/tags_test.go @@ -24,7 +24,7 @@ import ( ) func TestFilterTags(t *testing.T) { - awsServices := NewFakeAWSServices() + awsServices := NewFakeAWSServices(TestClusterId) c, err := newAWSCloud(strings.NewReader("[global]"), awsServices) if err != nil { t.Errorf("Error building aws cloud: %v", err)