mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-06 02:34:03 +00:00
additional subnet configuration for AWS ELB (#97431)
* support subnets without cluster tag for auto-discovery * add support for manual subnet configuration via service annotation
This commit is contained in:
parent
11a05eb9af
commit
f48972e671
@ -233,6 +233,16 @@ const ServiceAnnotationLoadBalancerEIPAllocations = "service.beta.kubernetes.io/
|
|||||||
// For example: "Key1=Val1,Key2=Val2,KeyNoVal1=,KeyNoVal2"
|
// For example: "Key1=Val1,Key2=Val2,KeyNoVal1=,KeyNoVal2"
|
||||||
const ServiceAnnotationLoadBalancerTargetNodeLabels = "service.beta.kubernetes.io/aws-load-balancer-target-node-labels"
|
const ServiceAnnotationLoadBalancerTargetNodeLabels = "service.beta.kubernetes.io/aws-load-balancer-target-node-labels"
|
||||||
|
|
||||||
|
// ServiceAnnotationLoadBalancerSubnets is the annotation used on the service to specify the
|
||||||
|
// Availability Zone configuration for the load balancer. The values are comma separated list of
|
||||||
|
// subnetID or subnetName from different AZs
|
||||||
|
// By default, the controller will auto-discover the subnets. If there are multiple subnets per AZ, auto-discovery
|
||||||
|
// will break the tie in the following order -
|
||||||
|
// 1. prefer the subnet with the correct role tag. kubernetes.io/role/elb for public and kubernetes.io/role/internal-elb for private access
|
||||||
|
// 2. prefer the subnet with the cluster tag kubernetes.io/cluster/<Cluster Name>
|
||||||
|
// 3. prefer the subnet that is first in lexicographic order
|
||||||
|
const ServiceAnnotationLoadBalancerSubnets = "service.beta.kubernetes.io/aws-load-balancer-subnets"
|
||||||
|
|
||||||
// Event key when a volume is stuck on attaching state when being attached to a volume
|
// Event key when a volume is stuck on attaching state when being attached to a volume
|
||||||
const volumeAttachmentStuck = "VolumeAttachmentStuck"
|
const volumeAttachmentStuck = "VolumeAttachmentStuck"
|
||||||
|
|
||||||
@ -3368,7 +3378,7 @@ func findTag(tags []*ec2.Tag, key string) (string, bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finds the subnets associated with the cluster, by matching tags.
|
// Finds the subnets associated with the cluster, by matching cluster tags if present.
|
||||||
// For maximal backwards compatibility, if no subnets are tagged, it will fall-back to the current subnet.
|
// For maximal backwards compatibility, if no subnets are tagged, it will fall-back to the current subnet.
|
||||||
// However, in future this will likely be treated as an error.
|
// However, in future this will likely be treated as an error.
|
||||||
func (c *Cloud) findSubnets() ([]*ec2.Subnet, error) {
|
func (c *Cloud) findSubnets() ([]*ec2.Subnet, error) {
|
||||||
@ -3384,6 +3394,8 @@ func (c *Cloud) findSubnets() ([]*ec2.Subnet, error) {
|
|||||||
for _, subnet := range subnets {
|
for _, subnet := range subnets {
|
||||||
if c.tagging.hasClusterTag(subnet.Tags) {
|
if c.tagging.hasClusterTag(subnet.Tags) {
|
||||||
matches = append(matches, subnet)
|
matches = append(matches, subnet)
|
||||||
|
} else if c.tagging.hasNoClusterPrefixTag(subnet.Tags) {
|
||||||
|
matches = append(matches, subnet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3447,7 +3459,7 @@ func (c *Cloud) findELBSubnets(internalELB bool) ([]string, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to break the tie using a tag
|
// Try to break the tie using the role tag
|
||||||
var tagName string
|
var tagName string
|
||||||
if internalELB {
|
if internalELB {
|
||||||
tagName = TagNameSubnetInternalELB
|
tagName = TagNameSubnetInternalELB
|
||||||
@ -3465,8 +3477,17 @@ func (c *Cloud) findELBSubnets(internalELB bool) ([]string, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prefer the one with the cluster Tag
|
||||||
|
existingHasClusterTag := c.tagging.hasClusterTag(existing.Tags)
|
||||||
|
subnetHasClusterTag := c.tagging.hasClusterTag(subnet.Tags)
|
||||||
|
if existingHasClusterTag != subnetHasClusterTag {
|
||||||
|
if subnetHasClusterTag {
|
||||||
|
subnetsByAZ[az] = subnet
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// If we have two subnets for the same AZ we arbitrarily choose the one that is first lexicographically.
|
// If we have two subnets for the same AZ we arbitrarily choose the one that is first lexicographically.
|
||||||
// TODO: Should this be an error.
|
|
||||||
if strings.Compare(*existing.SubnetId, *subnet.SubnetId) > 0 {
|
if strings.Compare(*existing.SubnetId, *subnet.SubnetId) > 0 {
|
||||||
klog.Warningf("Found multiple subnets in AZ %q; choosing %q between subnets %q and %q", az, *subnet.SubnetId, *existing.SubnetId, *subnet.SubnetId)
|
klog.Warningf("Found multiple subnets in AZ %q; choosing %q between subnets %q and %q", az, *subnet.SubnetId, *existing.SubnetId, *subnet.SubnetId)
|
||||||
subnetsByAZ[az] = subnet
|
subnetsByAZ[az] = subnet
|
||||||
@ -3492,6 +3513,90 @@ func (c *Cloud) findELBSubnets(internalELB bool) ([]string, error) {
|
|||||||
return subnetIDs, nil
|
return subnetIDs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func splitCommaSeparatedString(commaSeparatedString string) []string {
|
||||||
|
var result []string
|
||||||
|
parts := strings.Split(commaSeparatedString, ",")
|
||||||
|
for _, part := range parts {
|
||||||
|
part = strings.TrimSpace(part)
|
||||||
|
if len(part) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, part)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// parses comma separated values from annotation into string slice, returns true if annotation exists
|
||||||
|
func parseStringSliceAnnotation(annotations map[string]string, annotation string, value *[]string) bool {
|
||||||
|
rawValue := ""
|
||||||
|
if exists := parseStringAnnotation(annotations, annotation, &rawValue); !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*value = splitCommaSeparatedString(rawValue)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cloud) getLoadBalancerSubnets(service *v1.Service, internalELB bool) ([]string, error) {
|
||||||
|
var rawSubnetNameOrIDs []string
|
||||||
|
if exists := parseStringSliceAnnotation(service.Annotations, ServiceAnnotationLoadBalancerSubnets, &rawSubnetNameOrIDs); exists {
|
||||||
|
return c.resolveSubnetNameOrIDs(rawSubnetNameOrIDs)
|
||||||
|
}
|
||||||
|
return c.findELBSubnets(internalELB)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cloud) resolveSubnetNameOrIDs(subnetNameOrIDs []string) ([]string, error) {
|
||||||
|
var subnetIDs []string
|
||||||
|
var subnetNames []string
|
||||||
|
if len(subnetNameOrIDs) == 0 {
|
||||||
|
return []string{}, fmt.Errorf("unable to resolve empty subnet slice")
|
||||||
|
}
|
||||||
|
for _, nameOrID := range subnetNameOrIDs {
|
||||||
|
if strings.HasPrefix(nameOrID, "subnet-") {
|
||||||
|
subnetIDs = append(subnetIDs, nameOrID)
|
||||||
|
} else {
|
||||||
|
subnetNames = append(subnetNames, nameOrID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var resolvedSubnets []*ec2.Subnet
|
||||||
|
if len(subnetIDs) > 0 {
|
||||||
|
req := &ec2.DescribeSubnetsInput{
|
||||||
|
SubnetIds: aws.StringSlice(subnetIDs),
|
||||||
|
}
|
||||||
|
subnets, err := c.ec2.DescribeSubnets(req)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
resolvedSubnets = append(resolvedSubnets, subnets...)
|
||||||
|
}
|
||||||
|
if len(subnetNames) > 0 {
|
||||||
|
req := &ec2.DescribeSubnetsInput{
|
||||||
|
Filters: []*ec2.Filter{
|
||||||
|
{
|
||||||
|
Name: aws.String("tag:Name"),
|
||||||
|
Values: aws.StringSlice(subnetNames),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: aws.String("vpc-id"),
|
||||||
|
Values: aws.StringSlice([]string{c.vpcID}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
subnets, err := c.ec2.DescribeSubnets(req)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
resolvedSubnets = append(resolvedSubnets, subnets...)
|
||||||
|
}
|
||||||
|
if len(resolvedSubnets) != len(subnetNameOrIDs) {
|
||||||
|
return []string{}, fmt.Errorf("expected to find %v, but found %v subnets", len(subnetNameOrIDs), len(resolvedSubnets))
|
||||||
|
}
|
||||||
|
var subnets []string
|
||||||
|
for _, subnet := range resolvedSubnets {
|
||||||
|
subnets = append(subnets, aws.StringValue(subnet.SubnetId))
|
||||||
|
}
|
||||||
|
return subnets, nil
|
||||||
|
}
|
||||||
|
|
||||||
func isSubnetPublic(rt []*ec2.RouteTable, subnetID string) (bool, error) {
|
func isSubnetPublic(rt []*ec2.RouteTable, subnetID string) (bool, error) {
|
||||||
var subnetTable *ec2.RouteTable
|
var subnetTable *ec2.RouteTable
|
||||||
for _, table := range rt {
|
for _, table := range rt {
|
||||||
@ -3869,7 +3974,7 @@ func (c *Cloud) EnsureLoadBalancer(ctx context.Context, clusterName string, apiS
|
|||||||
|
|
||||||
if isNLB(annotations) {
|
if isNLB(annotations) {
|
||||||
// Find the subnets that the ELB will live in
|
// Find the subnets that the ELB will live in
|
||||||
subnetIDs, err := c.findELBSubnets(internalELB)
|
subnetIDs, err := c.getLoadBalancerSubnets(apiService, internalELB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("Error listing subnets in VPC: %q", err)
|
klog.Errorf("Error listing subnets in VPC: %q", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -4036,7 +4141,7 @@ func (c *Cloud) EnsureLoadBalancer(ctx context.Context, clusterName string, apiS
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find the subnets that the ELB will live in
|
// Find the subnets that the ELB will live in
|
||||||
subnetIDs, err := c.findELBSubnets(internalELB)
|
subnetIDs, err := c.getLoadBalancerSubnets(apiService, internalELB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("Error listing subnets in VPC: %q", err)
|
klog.Errorf("Error listing subnets in VPC: %q", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -20,6 +20,7 @@ package aws
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
@ -808,6 +809,356 @@ func constructRouteTable(subnetID string, public bool) *ec2.RouteTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_findELBSubnets(t *testing.T) {
|
||||||
|
awsServices := newMockedFakeAWSServices(TestClusterID)
|
||||||
|
c, err := newAWSCloud(CloudConfig{}, awsServices)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error building aws cloud: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subnetA0000001 := &ec2.Subnet{
|
||||||
|
AvailabilityZone: aws.String("us-west-2a"),
|
||||||
|
SubnetId: aws.String("subnet-a0000001"),
|
||||||
|
Tags: []*ec2.Tag{
|
||||||
|
{
|
||||||
|
Key: aws.String(TagNameSubnetPublicELB),
|
||||||
|
Value: aws.String("1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
subnetA0000002 := &ec2.Subnet{
|
||||||
|
AvailabilityZone: aws.String("us-west-2a"),
|
||||||
|
SubnetId: aws.String("subnet-a0000002"),
|
||||||
|
Tags: []*ec2.Tag{
|
||||||
|
{
|
||||||
|
Key: aws.String(TagNameSubnetPublicELB),
|
||||||
|
Value: aws.String("1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
subnetA0000003 := &ec2.Subnet{
|
||||||
|
AvailabilityZone: aws.String("us-west-2a"),
|
||||||
|
SubnetId: aws.String("subnet-a0000003"),
|
||||||
|
Tags: []*ec2.Tag{
|
||||||
|
{
|
||||||
|
Key: aws.String(c.tagging.clusterTagKey()),
|
||||||
|
Value: aws.String("owned"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: aws.String(TagNameSubnetInternalELB),
|
||||||
|
Value: aws.String("1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
subnetB0000001 := &ec2.Subnet{
|
||||||
|
AvailabilityZone: aws.String("us-west-2b"),
|
||||||
|
SubnetId: aws.String("subnet-b0000001"),
|
||||||
|
Tags: []*ec2.Tag{
|
||||||
|
{
|
||||||
|
Key: aws.String(c.tagging.clusterTagKey()),
|
||||||
|
Value: aws.String("owned"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: aws.String(TagNameSubnetPublicELB),
|
||||||
|
Value: aws.String("1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
subnetB0000002 := &ec2.Subnet{
|
||||||
|
AvailabilityZone: aws.String("us-west-2b"),
|
||||||
|
SubnetId: aws.String("subnet-b0000002"),
|
||||||
|
Tags: []*ec2.Tag{
|
||||||
|
{
|
||||||
|
Key: aws.String(c.tagging.clusterTagKey()),
|
||||||
|
Value: aws.String("owned"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: aws.String(TagNameSubnetInternalELB),
|
||||||
|
Value: aws.String("1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
subnetC0000001 := &ec2.Subnet{
|
||||||
|
AvailabilityZone: aws.String("us-west-2c"),
|
||||||
|
SubnetId: aws.String("subnet-c0000001"),
|
||||||
|
Tags: []*ec2.Tag{
|
||||||
|
{
|
||||||
|
Key: aws.String(c.tagging.clusterTagKey()),
|
||||||
|
Value: aws.String("owned"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: aws.String(TagNameSubnetInternalELB),
|
||||||
|
Value: aws.String("1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
subnetOther := &ec2.Subnet{
|
||||||
|
AvailabilityZone: aws.String("us-west-2c"),
|
||||||
|
SubnetId: aws.String("subnet-other"),
|
||||||
|
Tags: []*ec2.Tag{
|
||||||
|
{
|
||||||
|
Key: aws.String(TagNameKubernetesClusterPrefix + "clusterid.other"),
|
||||||
|
Value: aws.String("owned"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: aws.String(TagNameSubnetInternalELB),
|
||||||
|
Value: aws.String("1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
subnetNoTag := &ec2.Subnet{
|
||||||
|
AvailabilityZone: aws.String("us-west-2c"),
|
||||||
|
SubnetId: aws.String("subnet-notag"),
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
subnets []*ec2.Subnet
|
||||||
|
routeTables map[string]bool
|
||||||
|
internal bool
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no subnets",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single tagged subnet",
|
||||||
|
subnets: []*ec2.Subnet{
|
||||||
|
subnetA0000001,
|
||||||
|
},
|
||||||
|
routeTables: map[string]bool{
|
||||||
|
"subnet-a0000001": true,
|
||||||
|
},
|
||||||
|
internal: false,
|
||||||
|
want: []string{"subnet-a0000001"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no matching public subnet",
|
||||||
|
subnets: []*ec2.Subnet{
|
||||||
|
subnetA0000002,
|
||||||
|
},
|
||||||
|
routeTables: map[string]bool{
|
||||||
|
"subnet-a0000002": false,
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "prefer role over cluster tag",
|
||||||
|
subnets: []*ec2.Subnet{
|
||||||
|
subnetA0000001,
|
||||||
|
subnetA0000003,
|
||||||
|
},
|
||||||
|
routeTables: map[string]bool{
|
||||||
|
"subnet-a0000001": true,
|
||||||
|
"subnet-a0000003": true,
|
||||||
|
},
|
||||||
|
want: []string{"subnet-a0000001"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "prefer cluster tag",
|
||||||
|
subnets: []*ec2.Subnet{
|
||||||
|
subnetC0000001,
|
||||||
|
subnetNoTag,
|
||||||
|
},
|
||||||
|
want: []string{"subnet-c0000001"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "include untagged",
|
||||||
|
subnets: []*ec2.Subnet{
|
||||||
|
subnetA0000001,
|
||||||
|
subnetNoTag,
|
||||||
|
},
|
||||||
|
routeTables: map[string]bool{
|
||||||
|
"subnet-a0000001": true,
|
||||||
|
"subnet-notag": true,
|
||||||
|
},
|
||||||
|
want: []string{"subnet-a0000001", "subnet-notag"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ignore some other cluster owned subnet",
|
||||||
|
subnets: []*ec2.Subnet{
|
||||||
|
subnetB0000001,
|
||||||
|
subnetOther,
|
||||||
|
},
|
||||||
|
routeTables: map[string]bool{
|
||||||
|
"subnet-b0000001": true,
|
||||||
|
"subnet-other": true,
|
||||||
|
},
|
||||||
|
want: []string{"subnet-b0000001"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "prefer matching role",
|
||||||
|
subnets: []*ec2.Subnet{
|
||||||
|
subnetB0000001,
|
||||||
|
subnetB0000002,
|
||||||
|
},
|
||||||
|
routeTables: map[string]bool{
|
||||||
|
"subnet-b0000001": false,
|
||||||
|
"subnet-b0000002": false,
|
||||||
|
},
|
||||||
|
want: []string{"subnet-b0000002"},
|
||||||
|
internal: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "choose lexicographic order",
|
||||||
|
subnets: []*ec2.Subnet{
|
||||||
|
subnetA0000001,
|
||||||
|
subnetA0000002,
|
||||||
|
},
|
||||||
|
routeTables: map[string]bool{
|
||||||
|
"subnet-a0000001": true,
|
||||||
|
"subnet-a0000002": true,
|
||||||
|
},
|
||||||
|
want: []string{"subnet-a0000001"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "everything",
|
||||||
|
subnets: []*ec2.Subnet{
|
||||||
|
subnetA0000001,
|
||||||
|
subnetA0000002,
|
||||||
|
subnetB0000001,
|
||||||
|
subnetB0000002,
|
||||||
|
subnetC0000001,
|
||||||
|
subnetNoTag,
|
||||||
|
subnetOther,
|
||||||
|
},
|
||||||
|
routeTables: map[string]bool{
|
||||||
|
"subnet-a0000001": true,
|
||||||
|
"subnet-a0000002": true,
|
||||||
|
"subnet-b0000001": true,
|
||||||
|
"subnet-b0000002": true,
|
||||||
|
"subnet-c0000001": true,
|
||||||
|
"subnet-notag": true,
|
||||||
|
"subnet-other": true,
|
||||||
|
},
|
||||||
|
want: []string{"subnet-a0000001", "subnet-b0000001", "subnet-c0000001"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
awsServices.ec2.RemoveSubnets()
|
||||||
|
awsServices.ec2.RemoveRouteTables()
|
||||||
|
for _, subnet := range tt.subnets {
|
||||||
|
awsServices.ec2.CreateSubnet(subnet)
|
||||||
|
}
|
||||||
|
routeTables := constructRouteTables(tt.routeTables)
|
||||||
|
for _, rt := range routeTables {
|
||||||
|
awsServices.ec2.CreateRouteTable(rt)
|
||||||
|
}
|
||||||
|
got, _ := c.findELBSubnets(tt.internal)
|
||||||
|
sort.Strings(tt.want)
|
||||||
|
sort.Strings(got)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_getLoadBalancerSubnets(t *testing.T) {
|
||||||
|
awsServices := newMockedFakeAWSServices(TestClusterID)
|
||||||
|
c, err := newAWSCloud(CloudConfig{}, awsServices)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error building aws cloud: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
service *v1.Service
|
||||||
|
subnets []*ec2.Subnet
|
||||||
|
internalELB bool
|
||||||
|
want []string
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no annotation",
|
||||||
|
service: &v1.Service{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "annotation with no subnets",
|
||||||
|
service: &v1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"service.beta.kubernetes.io/aws-load-balancer-subnets": "\t",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: errors.New("unable to resolve empty subnet slice"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "subnet ids",
|
||||||
|
subnets: []*ec2.Subnet{
|
||||||
|
{
|
||||||
|
AvailabilityZone: aws.String("us-west-2c"),
|
||||||
|
SubnetId: aws.String("subnet-a000001"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AvailabilityZone: aws.String("us-west-2b"),
|
||||||
|
SubnetId: aws.String("subnet-a000002"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
service: &v1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"service.beta.kubernetes.io/aws-load-balancer-subnets": "subnet-a000001, subnet-a000002",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []string{"subnet-a000001", "subnet-a000002"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "subnet names",
|
||||||
|
subnets: []*ec2.Subnet{
|
||||||
|
{
|
||||||
|
AvailabilityZone: aws.String("us-west-2c"),
|
||||||
|
SubnetId: aws.String("subnet-a000001"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AvailabilityZone: aws.String("us-west-2b"),
|
||||||
|
SubnetId: aws.String("subnet-a000002"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
service: &v1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"service.beta.kubernetes.io/aws-load-balancer-subnets": "My Subnet 1, My Subnet 2 ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []string{"subnet-a000001", "subnet-a000002"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unable to find all subnets",
|
||||||
|
subnets: []*ec2.Subnet{
|
||||||
|
{
|
||||||
|
AvailabilityZone: aws.String("us-west-2c"),
|
||||||
|
SubnetId: aws.String("subnet-a000001"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
service: &v1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"service.beta.kubernetes.io/aws-load-balancer-subnets": "My Subnet 1, My Subnet 2, Test Subnet ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: errors.New("expected to find 3, but found 1 subnets"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
awsServices.ec2.RemoveSubnets()
|
||||||
|
for _, subnet := range tt.subnets {
|
||||||
|
awsServices.ec2.CreateSubnet(subnet)
|
||||||
|
}
|
||||||
|
got, err := c.getLoadBalancerSubnets(tt.service, tt.internalELB)
|
||||||
|
if tt.wantErr != nil {
|
||||||
|
assert.EqualError(t, err, tt.wantErr.Error())
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSubnetIDsinVPC(t *testing.T) {
|
func TestSubnetIDsinVPC(t *testing.T) {
|
||||||
awsServices := newMockedFakeAWSServices(TestClusterID)
|
awsServices := newMockedFakeAWSServices(TestClusterID)
|
||||||
c, err := newAWSCloud(CloudConfig{}, awsServices)
|
c, err := newAWSCloud(CloudConfig{}, awsServices)
|
||||||
@ -3064,3 +3415,54 @@ func TestCloud_buildNLBHealthCheckConfiguration(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_parseStringSliceAnnotation(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
annotation string
|
||||||
|
annotations map[string]string
|
||||||
|
want []string
|
||||||
|
wantExist bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty annotation",
|
||||||
|
annotation: "test.annotation",
|
||||||
|
wantExist: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty value",
|
||||||
|
annotation: "a1",
|
||||||
|
annotations: map[string]string{
|
||||||
|
"a1": "\t, ,,",
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
wantExist: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single value",
|
||||||
|
annotation: "a1",
|
||||||
|
annotations: map[string]string{
|
||||||
|
"a1": " value 1 ",
|
||||||
|
},
|
||||||
|
want: []string{"value 1"},
|
||||||
|
wantExist: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple values",
|
||||||
|
annotation: "a1",
|
||||||
|
annotations: map[string]string{
|
||||||
|
"a1": "subnet-1, subnet-2, My Subnet ",
|
||||||
|
},
|
||||||
|
want: []string{"subnet-1", "subnet-2", "My Subnet"},
|
||||||
|
wantExist: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var gotValue []string
|
||||||
|
gotExist := parseStringSliceAnnotation(tt.annotations, tt.annotation, &gotValue)
|
||||||
|
assert.Equal(t, tt.wantExist, gotExist)
|
||||||
|
assert.Equal(t, tt.want, gotValue)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -152,6 +152,15 @@ func (t *awsTagging) hasClusterTag(tags []*ec2.Tag) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *awsTagging) hasNoClusterPrefixTag(tags []*ec2.Tag) bool {
|
||||||
|
for _, tag := range tags {
|
||||||
|
if strings.HasPrefix(aws.StringValue(tag.Key), TagNameKubernetesClusterPrefix) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure that a resource has the correct tags
|
// Ensure that a resource has the correct tags
|
||||||
// If it has no tags, we assume that this was a problem caused by an error in between creation and tagging,
|
// If it has no tags, we assume that this was a problem caused by an error in between creation and tagging,
|
||||||
// and we add the tags. If it has a different cluster's tags, that is an error.
|
// and we add the tags. If it has a different cluster's tags, that is an error.
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFilterTags(t *testing.T) {
|
func TestFilterTags(t *testing.T) {
|
||||||
@ -182,3 +183,52 @@ func TestHasClusterTag(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHasNoClusterPrefixTag(t *testing.T) {
|
||||||
|
awsServices := NewFakeAWSServices(TestClusterID)
|
||||||
|
c, err := newAWSCloud(CloudConfig{}, awsServices)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error building aws cloud: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
tags []*ec2.Tag
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no tags",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no cluster tags",
|
||||||
|
tags: []*ec2.Tag{
|
||||||
|
{
|
||||||
|
Key: aws.String("not a cluster tag"),
|
||||||
|
Value: aws.String("true"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contains cluster tags",
|
||||||
|
tags: []*ec2.Tag{
|
||||||
|
{
|
||||||
|
Key: aws.String("tag1"),
|
||||||
|
Value: aws.String("value1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: aws.String("kubernetes.io/cluster/test.cluster"),
|
||||||
|
Value: aws.String("owned"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.want, c.tagging.hasNoClusterPrefixTag(tt.tags))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user