autoscaler/cluster-autoscaler/cloudprovider/aws/aws_manager_test.go

857 lines
25 KiB
Go

/*
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 (
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"reflect"
"sort"
"strconv"
"strings"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
provider_aws "k8s.io/legacy-cloud-providers/aws"
)
// resetAWSRegion resets AWS_REGION environment variable key to its pre-test
// value, but only if it was originally present among environment variables.
func resetAWSRegion(value string, present bool) {
os.Unsetenv("AWS_REGION")
if present {
os.Setenv("AWS_REGION", value)
}
}
// TestGetRegion ensures correct source supplies AWS Region.
func TestGetRegion(t *testing.T) {
key := "AWS_REGION"
defer resetAWSRegion(os.LookupEnv(key))
// Ensure environment variable retains precedence.
expected1 := "the-shire-1"
os.Setenv(key, expected1)
assert.Equal(t, expected1, getRegion())
// Ensure without environment variable, EC2 Metadata is used.
expected2 := "mordor-2"
expectedjson := ec2metadata.EC2InstanceIdentityDocument{Region: expected2}
js, _ := json.Marshal(expectedjson)
os.Unsetenv(key)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(js)
}))
cfg := aws.NewConfig().WithEndpoint(server.URL)
assert.Equal(t, expected2, getRegion(cfg))
}
func TestBuildGenericLabels(t *testing.T) {
labels := buildGenericLabels(&asgTemplate{
InstanceType: &InstanceType{
InstanceType: "c4.large",
VCPU: 2,
MemoryMb: 3840,
Architecture: cloudprovider.DefaultArch,
},
Region: "us-east-1",
}, "sillyname")
assert.Equal(t, "us-east-1", labels[apiv1.LabelZoneRegionStable])
assert.Equal(t, "sillyname", labels[apiv1.LabelHostname])
assert.Equal(t, "c4.large", labels[apiv1.LabelInstanceTypeStable])
assert.Equal(t, cloudprovider.DefaultArch, labels[apiv1.LabelArchStable])
assert.Equal(t, cloudprovider.DefaultOS, labels[apiv1.LabelOSStable])
}
func TestExtractAllocatableResourcesFromAsg(t *testing.T) {
tags := []*autoscaling.TagDescription{
{
Key: aws.String("k8s.io/cluster-autoscaler/node-template/resources/cpu"),
Value: aws.String("100m"),
},
{
Key: aws.String("k8s.io/cluster-autoscaler/node-template/resources/memory"),
Value: aws.String("100M"),
},
{
Key: aws.String("k8s.io/cluster-autoscaler/node-template/resources/ephemeral-storage"),
Value: aws.String("20G"),
},
}
labels := extractAllocatableResourcesFromAsg(tags)
assert.Equal(t, resource.NewMilliQuantity(100, resource.DecimalSI).String(), labels["cpu"].String())
expectedMemory := resource.MustParse("100M")
assert.Equal(t, (&expectedMemory).String(), labels["memory"].String())
expectedEphemeralStorage := resource.MustParse("20G")
assert.Equal(t, (&expectedEphemeralStorage).String(), labels["ephemeral-storage"].String())
}
func TestBuildNodeFromTemplate(t *testing.T) {
awsManager := &AwsManager{}
asg := &asg{AwsRef: AwsRef{Name: "test-auto-scaling-group"}}
c5Instance := &InstanceType{
InstanceType: "c5.xlarge",
VCPU: 4,
MemoryMb: 8192,
GPU: 0,
}
// Node with custom resource
ephemeralStorageKey := "ephemeral-storage"
ephemeralStorageValue := int64(20)
vpcIPKey := "vpc.amazonaws.com/PrivateIPv4Address"
observedNode, observedErr := awsManager.buildNodeFromTemplate(asg, &asgTemplate{
InstanceType: c5Instance,
Tags: []*autoscaling.TagDescription{
{
Key: aws.String(fmt.Sprintf("k8s.io/cluster-autoscaler/node-template/resources/%s", ephemeralStorageKey)),
Value: aws.String(strconv.FormatInt(ephemeralStorageValue, 10)),
},
},
})
assert.NoError(t, observedErr)
esValue, esExist := observedNode.Status.Capacity[apiv1.ResourceName(ephemeralStorageKey)]
assert.True(t, esExist)
assert.Equal(t, int64(20), esValue.Value())
_, ipExist := observedNode.Status.Capacity[apiv1.ResourceName(vpcIPKey)]
assert.False(t, ipExist)
// Node with labels
GPULabelValue := "nvidia-telsa-v100"
observedNode, observedErr = awsManager.buildNodeFromTemplate(asg, &asgTemplate{
InstanceType: c5Instance,
Tags: []*autoscaling.TagDescription{
{
Key: aws.String(fmt.Sprintf("k8s.io/cluster-autoscaler/node-template/label/%s", GPULabel)),
Value: aws.String(GPULabelValue),
},
},
})
assert.NoError(t, observedErr)
gpuValue, gpuLabelExist := observedNode.Labels[GPULabel]
assert.True(t, gpuLabelExist)
assert.Equal(t, GPULabelValue, gpuValue)
// Node with EKS labels
ngNameLabelValue := "nodegroup-1"
observedNode, observedErr = awsManager.buildNodeFromTemplate(asg, &asgTemplate{
InstanceType: c5Instance,
Tags: []*autoscaling.TagDescription{
{
Key: aws.String("eks:nodegroup-name"),
Value: aws.String(ngNameLabelValue),
},
},
})
assert.NoError(t, observedErr)
ngNameValue, ngLabelExist := observedNode.Labels["nodegroup-name"]
assert.True(t, ngLabelExist)
assert.Equal(t, ngNameLabelValue, ngNameValue)
// Node with taints
gpuTaint := apiv1.Taint{
Key: "nvidia.com/gpu",
Value: "present",
Effect: "NoSchedule",
}
observedNode, observedErr = awsManager.buildNodeFromTemplate(asg, &asgTemplate{
InstanceType: c5Instance,
Tags: []*autoscaling.TagDescription{
{
Key: aws.String(fmt.Sprintf("k8s.io/cluster-autoscaler/node-template/taint/%s", gpuTaint.Key)),
Value: aws.String(fmt.Sprintf("%s:%s", gpuTaint.Value, gpuTaint.Effect)),
},
},
})
assert.NoError(t, observedErr)
observedTaints := observedNode.Spec.Taints
assert.Equal(t, 1, len(observedTaints))
assert.Equal(t, gpuTaint, observedTaints[0])
}
func TestExtractLabelsFromAsg(t *testing.T) {
tags := []*autoscaling.TagDescription{
{
Key: aws.String("k8s.io/cluster-autoscaler/node-template/label/foo"),
Value: aws.String("bar"),
},
{
Key: aws.String("eks:nodegroup-name"),
Value: aws.String("bar2"),
},
{
Key: aws.String("eks:cluster-name"),
Value: aws.String("bar4"),
},
{
Key: aws.String("bar"),
Value: aws.String("baz"),
},
}
labels := extractLabelsFromAsg(tags)
assert.Equal(t, 3, len(labels))
assert.Equal(t, "bar", labels["foo"])
assert.Equal(t, "bar2", labels["nodegroup-name"])
assert.Equal(t, "bar4", labels["cluster-name"])
}
func TestExtractTaintsFromAsg(t *testing.T) {
tags := []*autoscaling.TagDescription{
{
Key: aws.String("k8s.io/cluster-autoscaler/node-template/taint/dedicated"),
Value: aws.String("foo:NoSchedule"),
},
{
Key: aws.String("k8s.io/cluster-autoscaler/node-template/taint/group"),
Value: aws.String("bar:NoExecute"),
},
{
Key: aws.String("k8s.io/cluster-autoscaler/node-template/taint/app"),
Value: aws.String("fizz:PreferNoSchedule"),
},
{
Key: aws.String("bar"),
Value: aws.String("baz"),
},
{
Key: aws.String("k8s.io/cluster-autoscaler/node-template/taint/blank"),
Value: aws.String(""),
},
{
Key: aws.String("k8s.io/cluster-autoscaler/node-template/taint/nosplit"),
Value: aws.String("some_value"),
},
}
expectedTaints := []apiv1.Taint{
{
Key: "dedicated",
Value: "foo",
Effect: apiv1.TaintEffectNoSchedule,
},
{
Key: "group",
Value: "bar",
Effect: apiv1.TaintEffectNoExecute,
},
{
Key: "app",
Value: "fizz",
Effect: apiv1.TaintEffectPreferNoSchedule,
},
}
taints := extractTaintsFromAsg(tags)
assert.Equal(t, 3, len(taints))
assert.Equal(t, makeTaintSet(expectedTaints), makeTaintSet(taints))
}
func makeTaintSet(taints []apiv1.Taint) map[apiv1.Taint]bool {
set := make(map[apiv1.Taint]bool)
for _, taint := range taints {
set[taint] = true
}
return set
}
func TestFetchExplicitAsgs(t *testing.T) {
min, max, groupname := 1, 10, "coolasg"
a := &autoScalingMock{}
a.On("DescribeAutoScalingGroups", &autoscaling.DescribeAutoScalingGroupsInput{
AutoScalingGroupNames: []*string{aws.String(groupname)},
MaxRecords: aws.Int64(1),
}).Return(&autoscaling.DescribeAutoScalingGroupsOutput{
AutoScalingGroups: []*autoscaling.Group{
{AutoScalingGroupName: aws.String(groupname)},
},
})
a.On("DescribeAutoScalingGroupsPages",
&autoscaling.DescribeAutoScalingGroupsInput{
AutoScalingGroupNames: aws.StringSlice([]string{groupname}),
MaxRecords: aws.Int64(maxRecordsReturnedByAPI),
},
mock.AnythingOfType("func(*autoscaling.DescribeAutoScalingGroupsOutput, bool) bool"),
).Run(func(args mock.Arguments) {
fn := args.Get(1).(func(*autoscaling.DescribeAutoScalingGroupsOutput, bool) bool)
zone := "test-1a"
fn(&autoscaling.DescribeAutoScalingGroupsOutput{
AutoScalingGroups: []*autoscaling.Group{
{
AvailabilityZones: []*string{&zone},
AutoScalingGroupName: aws.String(groupname),
MinSize: aws.Int64(int64(min)),
MaxSize: aws.Int64(int64(max)),
DesiredCapacity: aws.Int64(int64(min)),
},
}}, false)
}).Return(nil)
do := cloudprovider.NodeGroupDiscoveryOptions{
// Register the same node group twice with different max nodes.
// The intention is to test that the asgs.Register method will update
// the node group instead of registering it twice.
NodeGroupSpecs: []string{
fmt.Sprintf("%d:%d:%s", min, max, groupname),
fmt.Sprintf("%d:%d:%s", min, max-1, groupname),
},
}
// #1449 Without AWS_REGION getRegion() lookup runs till timeout during tests.
defer resetAWSRegion(os.LookupEnv("AWS_REGION"))
os.Setenv("AWS_REGION", "fanghorn")
instanceTypes, _ := GetStaticEC2InstanceTypes()
m, err := createAWSManagerInternal(nil, do, &awsWrapper{a, nil, nil}, instanceTypes)
assert.NoError(t, err)
asgs := m.asgCache.Get()
assert.Equal(t, 1, len(asgs))
validateAsg(t, asgs[0], groupname, min, max)
}
func TestGetASGTemplate(t *testing.T) {
const (
asgName = "sample"
knownInstanceType = "t3.micro"
region = "us-east-1"
az = region + "a"
ltName = "launcher"
ltVersion = "1"
)
asgRef := AwsRef{Name: asgName}
tags := []*autoscaling.TagDescription{
{
Key: aws.String("k8s.io/cluster-autoscaler/node-template/taint/dedicated"),
Value: aws.String("foo:NoSchedule"),
},
}
tests := []struct {
description string
instanceType string
availabilityZones []string
error bool
}{
{"insufficient availability zones",
knownInstanceType, []string{}, true},
{"single availability zone",
knownInstanceType, []string{az}, false},
{"multiple availability zones",
knownInstanceType, []string{az, "us-west-1b"}, false},
{"unknown instance type",
"nonexistent.xlarge", []string{az}, true},
}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
e := &ec2Mock{}
e.On("DescribeLaunchTemplateVersions", &ec2.DescribeLaunchTemplateVersionsInput{
LaunchTemplateName: aws.String(ltName),
Versions: []*string{aws.String(ltVersion)},
}).Return(&ec2.DescribeLaunchTemplateVersionsOutput{
LaunchTemplateVersions: []*ec2.LaunchTemplateVersion{
{
LaunchTemplateData: &ec2.ResponseLaunchTemplateData{
InstanceType: aws.String(test.instanceType),
},
},
},
})
// #1449 Without AWS_REGION getRegion() lookup runs till timeout during tests.
defer resetAWSRegion(os.LookupEnv("AWS_REGION"))
os.Setenv("AWS_REGION", "fanghorn")
instanceTypes, _ := GetStaticEC2InstanceTypes()
do := cloudprovider.NodeGroupDiscoveryOptions{}
m, err := createAWSManagerInternal(nil, do, &awsWrapper{nil, e, nil}, instanceTypes)
origGetInstanceTypeFunc := getInstanceTypeForAsg
defer func() { getInstanceTypeForAsg = origGetInstanceTypeFunc }()
getInstanceTypeForAsg = func(m *asgCache, asg *asg) (string, error) {
return test.instanceType, nil
}
assert.NoError(t, err)
asg := &asg{
AwsRef: asgRef,
AvailabilityZones: test.availabilityZones,
LaunchTemplate: &launchTemplate{
name: ltName,
version: ltVersion},
Tags: tags,
}
template, err := m.getAsgTemplate(asg)
if test.error {
assert.Error(t, err)
} else {
assert.NoError(t, err)
if assert.NotNil(t, template) {
assert.Equal(t, test.instanceType, template.InstanceType.InstanceType)
assert.Equal(t, region, template.Region)
assert.Equal(t, test.availabilityZones[0], template.Zone)
assert.Equal(t, tags, template.Tags)
}
}
})
}
}
func TestFetchAutoAsgs(t *testing.T) {
min, max := 1, 10
groupname, tags := "coolasg", []string{"tag", "anothertag"}
a := &autoScalingMock{}
// Lookup groups associated with tags
expectedTagsInput := &autoscaling.DescribeTagsInput{
Filters: []*autoscaling.Filter{
{Name: aws.String("key"), Values: aws.StringSlice([]string{tags[0]})},
{Name: aws.String("key"), Values: aws.StringSlice([]string{tags[1]})},
},
MaxRecords: aws.Int64(maxRecordsReturnedByAPI),
}
// Use MatchedBy pattern to avoid list order issue https://github.com/kubernetes/autoscaler/issues/1346
a.On("DescribeTagsPages", mock.MatchedBy(tagsMatcher(expectedTagsInput)),
mock.AnythingOfType("func(*autoscaling.DescribeTagsOutput, bool) bool"),
).Run(func(args mock.Arguments) {
fn := args.Get(1).(func(*autoscaling.DescribeTagsOutput, bool) bool)
fn(&autoscaling.DescribeTagsOutput{
Tags: []*autoscaling.TagDescription{
{ResourceId: aws.String(groupname)},
{ResourceId: aws.String(groupname)},
}}, false)
}).Return(nil).Once()
// Describe the group to register it, then again to generate the instance
// cache.
a.On("DescribeAutoScalingGroupsPages",
&autoscaling.DescribeAutoScalingGroupsInput{
AutoScalingGroupNames: aws.StringSlice([]string{groupname}),
MaxRecords: aws.Int64(maxRecordsReturnedByAPI),
},
mock.AnythingOfType("func(*autoscaling.DescribeAutoScalingGroupsOutput, bool) bool"),
).Run(func(args mock.Arguments) {
fn := args.Get(1).(func(*autoscaling.DescribeAutoScalingGroupsOutput, bool) bool)
zone := "test-1a"
fn(&autoscaling.DescribeAutoScalingGroupsOutput{
AutoScalingGroups: []*autoscaling.Group{{
AvailabilityZones: []*string{&zone},
AutoScalingGroupName: aws.String(groupname),
MinSize: aws.Int64(int64(min)),
MaxSize: aws.Int64(int64(max)),
DesiredCapacity: aws.Int64(int64(min)),
}}}, false)
}).Return(nil).Twice()
do := cloudprovider.NodeGroupDiscoveryOptions{
NodeGroupAutoDiscoverySpecs: []string{fmt.Sprintf("asg:tag=%s", strings.Join(tags, ","))},
}
// #1449 Without AWS_REGION getRegion() lookup runs till timeout during tests.
defer resetAWSRegion(os.LookupEnv("AWS_REGION"))
os.Setenv("AWS_REGION", "fanghorn")
// fetchAutoASGs is called at manager creation time, via forceRefresh
instanceTypes, _ := GetStaticEC2InstanceTypes()
m, err := createAWSManagerInternal(nil, do, &awsWrapper{a, nil, nil}, instanceTypes)
assert.NoError(t, err)
asgs := m.asgCache.Get()
assert.Equal(t, 1, len(asgs))
validateAsg(t, asgs[0], groupname, min, max)
// Simulate the previously discovered ASG disappearing
a.On("DescribeTagsPages", mock.MatchedBy(tagsMatcher(expectedTagsInput)),
mock.AnythingOfType("func(*autoscaling.DescribeTagsOutput, bool) bool"),
).Run(func(args mock.Arguments) {
fn := args.Get(1).(func(*autoscaling.DescribeTagsOutput, bool) bool)
fn(&autoscaling.DescribeTagsOutput{Tags: []*autoscaling.TagDescription{}}, false)
}).Return(nil).Once()
err = m.asgCache.regenerate()
assert.NoError(t, err)
assert.Empty(t, m.asgCache.Get())
}
type ServiceDescriptor struct {
name string
region string
signingRegion, signingMethod string
signingName string
}
func TestOverridesActiveConfig(t *testing.T) {
tests := []struct {
name string
reader io.Reader
aws provider_aws.Services
expectError bool
active bool
servicesOverridden []ServiceDescriptor
}{
{
"No overrides",
strings.NewReader(`
[global]
`),
nil,
false, false,
[]ServiceDescriptor{},
},
{
"Missing Service Name",
strings.NewReader(`
[global]
[ServiceOverride "1"]
Region=sregion
URL=https://s3.foo.bar
SigningRegion=sregion
SigningMethod = sign
`),
nil,
true, false,
[]ServiceDescriptor{},
},
{
"Missing Service Region",
strings.NewReader(`
[global]
[ServiceOverride "1"]
Service=s3
URL=https://s3.foo.bar
SigningRegion=sregion
SigningMethod = sign
`),
nil,
true, false,
[]ServiceDescriptor{},
},
{
"Missing URL",
strings.NewReader(`
[global]
[ServiceOverride "1"]
Service="s3"
Region=sregion
SigningRegion=sregion
SigningMethod = sign
`),
nil,
true, false,
[]ServiceDescriptor{},
},
{
"Missing Signing Region",
strings.NewReader(`
[global]
[ServiceOverride "1"]
Service=s3
Region=sregion
URL=https://s3.foo.bar
SigningMethod = sign
`),
nil,
true, false,
[]ServiceDescriptor{},
},
{
"Active Overrides",
strings.NewReader(`
[Global]
[ServiceOverride "1"]
Service = "s3 "
Region = sregion
URL = https://s3.foo.bar
SigningRegion = sregion
SigningMethod = v4
`),
nil,
false, true,
[]ServiceDescriptor{{name: "s3", region: "sregion", signingRegion: "sregion", signingMethod: "v4"}},
},
{
"Multiple Overridden Services",
strings.NewReader(`
[Global]
vpc = vpc-abc1234567
[ServiceOverride "1"]
Service=s3
Region=sregion1
URL=https://s3.foo.bar
SigningRegion=sregion1
SigningMethod = v4
[ServiceOverride "2"]
Service=ec2
Region=sregion2
URL=https://ec2.foo.bar
SigningRegion=sregion2
SigningMethod = v4
`),
nil,
false, true,
[]ServiceDescriptor{{name: "s3", region: "sregion1", signingRegion: "sregion1", signingMethod: "v4"},
{name: "ec2", region: "sregion2", signingRegion: "sregion2", signingMethod: "v4"}},
},
{
"Duplicate Services",
strings.NewReader(`
[Global]
vpc = vpc-abc1234567
[ServiceOverride "1"]
Service=s3
Region=sregion1
URL=https://s3.foo.bar
SigningRegion=sregion
SigningMethod = sign
[ServiceOverride "2"]
Service=s3
Region=sregion1
URL=https://s3.foo.bar
SigningRegion=sregion
SigningMethod = sign
`),
nil,
true, false,
[]ServiceDescriptor{},
},
{
"Multiple Overridden Services in Multiple regions",
strings.NewReader(`
[global]
[ServiceOverride "1"]
Service=s3
Region=region1
URL=https://s3.foo.bar
SigningRegion=sregion1
[ServiceOverride "2"]
Service=ec2
Region=region2
URL=https://ec2.foo.bar
SigningRegion=sregion
SigningMethod = v4
`),
nil,
false, true,
[]ServiceDescriptor{{name: "s3", region: "region1", signingRegion: "sregion1", signingMethod: ""},
{name: "ec2", region: "region2", signingRegion: "sregion", signingMethod: "v4"}},
},
{
"Multiple regions, Same Service",
strings.NewReader(`
[global]
[ServiceOverride "1"]
Service=s3
Region=region1
URL=https://s3.foo.bar
SigningRegion=sregion1
SigningMethod = v3
[ServiceOverride "2"]
Service=s3
Region=region2
URL=https://s3.foo.bar
SigningRegion=sregion1
SigningMethod = v4
SigningName = "name"
`),
nil,
false, true,
[]ServiceDescriptor{{name: "s3", region: "region1", signingRegion: "sregion1", signingMethod: "v3"},
{name: "s3", region: "region2", signingRegion: "sregion1", signingMethod: "v4", signingName: "name"}},
},
}
for _, test := range tests {
t.Logf("Running test case %s", test.name)
cfg, err := readAWSCloudConfig(test.reader)
if err == nil {
err = validateOverrides(cfg)
}
if test.expectError {
if err == nil {
t.Errorf("Should error for case %s (cfg=%v)", test.name, cfg)
}
} else {
if err != nil {
t.Errorf("Should succeed for case: %s, got %v", test.name, err)
}
if len(cfg.ServiceOverride) != len(test.servicesOverridden) {
t.Errorf("Expected %d overridden services, received %d for case %s",
len(test.servicesOverridden), len(cfg.ServiceOverride), test.name)
} else {
for _, sd := range test.servicesOverridden {
var found *struct {
Service string
Region string
URL string
SigningRegion string
SigningMethod string
SigningName string
}
for _, v := range cfg.ServiceOverride {
if v.Service == sd.name && v.Region == sd.region {
found = v
break
}
}
if found == nil {
t.Errorf("Missing override for service %s in case %s",
sd.name, test.name)
} else {
if found.SigningRegion != sd.signingRegion {
t.Errorf("Expected signing region '%s', received '%s' for case %s",
sd.signingRegion, found.SigningRegion, test.name)
}
if found.SigningMethod != sd.signingMethod {
t.Errorf("Expected signing method '%s', received '%s' for case %s",
sd.signingMethod, found.SigningRegion, test.name)
}
targetName := fmt.Sprintf("https://%s.foo.bar", sd.name)
if found.URL != targetName {
t.Errorf("Expected Endpoint '%s', received '%s' for case %s",
targetName, found.URL, test.name)
}
if found.SigningName != sd.signingName {
t.Errorf("Expected signing name '%s', received '%s' for case %s",
sd.signingName, found.SigningName, test.name)
}
fn := getResolver(cfg)
ep1, e := fn(sd.name, sd.region, nil)
if e != nil {
t.Errorf("Expected a valid endpoint for %s in case %s",
sd.name, test.name)
} else {
targetName := fmt.Sprintf("https://%s.foo.bar", sd.name)
if ep1.URL != targetName {
t.Errorf("Expected endpoint url: %s, received %s in case %s",
targetName, ep1.URL, test.name)
}
if ep1.SigningRegion != sd.signingRegion {
t.Errorf("Expected signing region '%s', received '%s' in case %s",
sd.signingRegion, ep1.SigningRegion, test.name)
}
if ep1.SigningMethod != sd.signingMethod {
t.Errorf("Expected signing method '%s', received '%s' in case %s",
sd.signingMethod, ep1.SigningRegion, test.name)
}
}
}
}
}
}
}
}
func tagsMatcher(expected *autoscaling.DescribeTagsInput) func(*autoscaling.DescribeTagsInput) bool {
return func(actual *autoscaling.DescribeTagsInput) bool {
expectedTags := flatTagSlice(expected.Filters)
actualTags := flatTagSlice(actual.Filters)
return *expected.MaxRecords == *actual.MaxRecords && reflect.DeepEqual(expectedTags, actualTags)
}
}
func flatTagSlice(filters []*autoscaling.Filter) []string {
tags := []string{}
for _, filter := range filters {
tags = append(tags, aws.StringValueSlice(filter.Values)...)
}
// Sort slice for compare
sort.Strings(tags)
return tags
}
func TestParseASGAutoDiscoverySpecs(t *testing.T) {
cases := []struct {
name string
specs []string
want []asgAutoDiscoveryConfig
wantErr bool
}{
{
name: "GoodSpecs",
specs: []string{
"asg:tag=tag,anothertag",
"asg:tag=cooltag,anothertag",
"asg:tag=label=value,anothertag",
},
want: []asgAutoDiscoveryConfig{
{Tags: map[string]string{"tag": "", "anothertag": ""}},
{Tags: map[string]string{"cooltag": "", "anothertag": ""}},
{Tags: map[string]string{"label": "value", "anothertag": ""}},
},
},
{
name: "MissingASGType",
specs: []string{"tag=tag,anothertag"},
wantErr: true,
},
{
name: "WrongType",
specs: []string{"mig:tag=tag,anothertag"},
wantErr: true,
},
{
name: "KeyMissingValue",
specs: []string{"asg:tag="},
wantErr: true,
},
{
name: "ValueMissingKey",
specs: []string{"asg:=tag"},
wantErr: true,
},
{
name: "KeyMissingSeparator",
specs: []string{"asg:tag"},
wantErr: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
do := cloudprovider.NodeGroupDiscoveryOptions{NodeGroupAutoDiscoverySpecs: tc.specs}
got, err := parseASGAutoDiscoverySpecs(do)
if tc.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.True(t, assert.ObjectsAreEqualValues(tc.want, got), "\ngot: %#v\nwant: %#v", got, tc.want)
})
}
}