AWS: Support shared tag
We recognize an additional cluster tag: kubernetes.io/cluster/<clusterid> This now allows us to share resources, in particular subnets. In addition, the value is used to track ownership/lifecycle. When we create objects, we record the value as "owned". We also refactor out tags into its own file & class, as we are touching most of these functions anyway.
This commit is contained in:
		@@ -21,6 +21,7 @@ go_library(
 | 
				
			|||||||
        "regions.go",
 | 
					        "regions.go",
 | 
				
			||||||
        "retry_handler.go",
 | 
					        "retry_handler.go",
 | 
				
			||||||
        "sets_ippermissions.go",
 | 
					        "sets_ippermissions.go",
 | 
				
			||||||
 | 
					        "tags.go",
 | 
				
			||||||
        "volumes.go",
 | 
					        "volumes.go",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    tags = ["automanaged"],
 | 
					    tags = ["automanaged"],
 | 
				
			||||||
@@ -56,6 +57,7 @@ go_test(
 | 
				
			|||||||
        "device_allocator_test.go",
 | 
					        "device_allocator_test.go",
 | 
				
			||||||
        "regions_test.go",
 | 
					        "regions_test.go",
 | 
				
			||||||
        "retry_handler_test.go",
 | 
					        "retry_handler_test.go",
 | 
				
			||||||
 | 
					        "tags_test.go",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    library = ":go_default_library",
 | 
					    library = ":go_default_library",
 | 
				
			||||||
    tags = ["automanaged"],
 | 
					    tags = ["automanaged"],
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,10 +54,6 @@ import (
 | 
				
			|||||||
// ProviderName is the name of this cloud provider.
 | 
					// ProviderName is the name of this cloud provider.
 | 
				
			||||||
const ProviderName = "aws"
 | 
					const ProviderName = "aws"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TagNameKubernetesCluster is the tag name we use to differentiate multiple
 | 
					 | 
				
			||||||
// logically independent clusters running in the same AZ
 | 
					 | 
				
			||||||
const TagNameKubernetesCluster = "KubernetesCluster"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// TagNameKubernetesService is the tag name we use to differentiate multiple
 | 
					// TagNameKubernetesService is the tag name we use to differentiate multiple
 | 
				
			||||||
// services. Used currently for ELBs only.
 | 
					// services. Used currently for ELBs only.
 | 
				
			||||||
const TagNameKubernetesService = "kubernetes.io/service-name"
 | 
					const TagNameKubernetesService = "kubernetes.io/service-name"
 | 
				
			||||||
@@ -359,7 +355,7 @@ type Cloud struct {
 | 
				
			|||||||
	region   string
 | 
						region   string
 | 
				
			||||||
	vpcID    string
 | 
						vpcID    string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	filterTags map[string]string
 | 
						tagging awsTagging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// The AWS instance that we are running on
 | 
						// The AWS instance that we are running on
 | 
				
			||||||
	// Note that we cache some state in awsInstance (mountpoints), so we must preserve the instance
 | 
						// Note that we cache some state in awsInstance (mountpoints), so we must preserve the instance
 | 
				
			||||||
@@ -388,7 +384,10 @@ type CloudConfig struct {
 | 
				
			|||||||
		// Maybe if we're not running on AWS, e.g. bootstrap; for now it is not very useful
 | 
							// Maybe if we're not running on AWS, e.g. bootstrap; for now it is not very useful
 | 
				
			||||||
		Zone string
 | 
							Zone string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// KubernetesClusterTag is the legacy cluster id we'll use to identify our cluster resources
 | 
				
			||||||
		KubernetesClusterTag string
 | 
							KubernetesClusterTag string
 | 
				
			||||||
 | 
							// KubernetesClusterTag is the cluster id we'll use to identify our cluster resources
 | 
				
			||||||
 | 
							KubernetesClusterID string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		//The aws provider creates an inbound rule per load balancer on the node security
 | 
							//The aws provider creates an inbound rule per load balancer on the node security
 | 
				
			||||||
		//group. However, this can run into the AWS security group rule limit of 50 if
 | 
							//group. However, this can run into the AWS security group rule limit of 50 if
 | 
				
			||||||
@@ -534,12 +533,12 @@ func orEmpty(s *string) string {
 | 
				
			|||||||
	return aws.StringValue(s)
 | 
						return aws.StringValue(s)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func newEc2Filter(name string, value string) *ec2.Filter {
 | 
					func newEc2Filter(name string, values ...string) *ec2.Filter {
 | 
				
			||||||
	filter := &ec2.Filter{
 | 
						filter := &ec2.Filter{
 | 
				
			||||||
		Name: aws.String(name),
 | 
							Name: aws.String(name),
 | 
				
			||||||
		Values: []*string{
 | 
						}
 | 
				
			||||||
			aws.String(value),
 | 
						for _, value := range values {
 | 
				
			||||||
		},
 | 
							filter.Values = append(filter.Values, aws.String(value))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return filter
 | 
						return filter
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -817,32 +816,20 @@ func newAWSCloud(config io.Reader, awsServices Services) (*Cloud, error) {
 | 
				
			|||||||
	awsCloud.selfAWSInstance = selfAWSInstance
 | 
						awsCloud.selfAWSInstance = selfAWSInstance
 | 
				
			||||||
	awsCloud.vpcID = selfAWSInstance.vpcID
 | 
						awsCloud.vpcID = selfAWSInstance.vpcID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	filterTags := map[string]string{}
 | 
						if cfg.Global.KubernetesClusterTag != "" || cfg.Global.KubernetesClusterID != "" {
 | 
				
			||||||
	if cfg.Global.KubernetesClusterTag != "" {
 | 
							if err := awsCloud.tagging.init(cfg.Global.KubernetesClusterTag, cfg.Global.KubernetesClusterID); err != nil {
 | 
				
			||||||
		filterTags[TagNameKubernetesCluster] = cfg.Global.KubernetesClusterTag
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		// TODO: Clean up double-API query
 | 
							// TODO: Clean up double-API query
 | 
				
			||||||
		info, err := selfAWSInstance.describeInstance()
 | 
							info, err := selfAWSInstance.describeInstance()
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		for _, tag := range info.Tags {
 | 
							if err := awsCloud.tagging.initFromTags(info.Tags); err != nil {
 | 
				
			||||||
			if orEmpty(tag.Key) == TagNameKubernetesCluster {
 | 
								return nil, err
 | 
				
			||||||
				filterTags[TagNameKubernetesCluster] = orEmpty(tag.Value)
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if filterTags[TagNameKubernetesCluster] == "" {
 | 
					 | 
				
			||||||
		glog.Errorf("Tag %q not found; Kubernetes may behave unexpectedly.", TagNameKubernetesCluster)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	awsCloud.filterTags = filterTags
 | 
					 | 
				
			||||||
	if len(filterTags) > 0 {
 | 
					 | 
				
			||||||
		glog.Infof("AWS cloud filtering on tags: %v", filterTags)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		glog.Infof("AWS cloud - no tag filtering")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Register regions, in particular for ECR credentials
 | 
						// Register regions, in particular for ECR credentials
 | 
				
			||||||
	once.Do(func() {
 | 
						once.Do(func() {
 | 
				
			||||||
@@ -990,15 +977,12 @@ func (c *Cloud) InstanceType(nodeName types.NodeName) (string, error) {
 | 
				
			|||||||
// Return a list of instances matching regex string.
 | 
					// Return a list of instances matching regex string.
 | 
				
			||||||
func (c *Cloud) getInstancesByRegex(regex string) ([]types.NodeName, error) {
 | 
					func (c *Cloud) getInstancesByRegex(regex string) ([]types.NodeName, error) {
 | 
				
			||||||
	filters := []*ec2.Filter{newEc2Filter("instance-state-name", "running")}
 | 
						filters := []*ec2.Filter{newEc2Filter("instance-state-name", "running")}
 | 
				
			||||||
	filters = c.addFilters(filters)
 | 
					 | 
				
			||||||
	request := &ec2.DescribeInstancesInput{
 | 
					 | 
				
			||||||
		Filters: filters,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	instances, err := c.ec2.DescribeInstances(request)
 | 
						instances, err := c.describeInstances(filters)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return []types.NodeName{}, err
 | 
							return []types.NodeName{}, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(instances) == 0 {
 | 
						if len(instances) == 0 {
 | 
				
			||||||
		return []types.NodeName{}, fmt.Errorf("no instances returned")
 | 
							return []types.NodeName{}, fmt.Errorf("no instances returned")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -1050,15 +1034,12 @@ func (c *Cloud) getAllZones() (sets.String, error) {
 | 
				
			|||||||
	// TODO: We could also query for subnets, I think
 | 
						// TODO: We could also query for subnets, I think
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	filters := []*ec2.Filter{newEc2Filter("instance-state-name", "running")}
 | 
						filters := []*ec2.Filter{newEc2Filter("instance-state-name", "running")}
 | 
				
			||||||
	filters = c.addFilters(filters)
 | 
					 | 
				
			||||||
	request := &ec2.DescribeInstancesInput{
 | 
					 | 
				
			||||||
		Filters: filters,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	instances, err := c.ec2.DescribeInstances(request)
 | 
						instances, err := c.describeInstances(filters)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(instances) == 0 {
 | 
						if len(instances) == 0 {
 | 
				
			||||||
		return nil, fmt.Errorf("no instances returned")
 | 
							return nil, fmt.Errorf("no instances returned")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -1647,17 +1628,7 @@ func (c *Cloud) CreateDisk(volumeOptions *VolumeOptions) (KubernetesVolumeID, er
 | 
				
			|||||||
	volumeName := KubernetesVolumeID("aws://" + aws.StringValue(response.AvailabilityZone) + "/" + string(awsID))
 | 
						volumeName := KubernetesVolumeID("aws://" + aws.StringValue(response.AvailabilityZone) + "/" + string(awsID))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// apply tags
 | 
						// apply tags
 | 
				
			||||||
	tags := make(map[string]string)
 | 
						if err := c.tagging.createTags(c.ec2, string(awsID), ResourceLifecycleOwned, volumeOptions.Tags); err != nil {
 | 
				
			||||||
	for k, v := range volumeOptions.Tags {
 | 
					 | 
				
			||||||
		tags[k] = v
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if c.getClusterName() != "" {
 | 
					 | 
				
			||||||
		tags[TagNameKubernetesCluster] = c.getClusterName()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if len(tags) != 0 {
 | 
					 | 
				
			||||||
		if err := c.createTags(string(awsID), tags); err != nil {
 | 
					 | 
				
			||||||
		// delete the volume and hope it succeeds
 | 
							// delete the volume and hope it succeeds
 | 
				
			||||||
		_, delerr := c.DeleteDisk(volumeName)
 | 
							_, delerr := c.DeleteDisk(volumeName)
 | 
				
			||||||
		if delerr != nil {
 | 
							if delerr != nil {
 | 
				
			||||||
@@ -1666,7 +1637,7 @@ func (c *Cloud) CreateDisk(volumeOptions *VolumeOptions) (KubernetesVolumeID, er
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		return "", fmt.Errorf("error tagging volume %s: %v", volumeName, err)
 | 
							return "", fmt.Errorf("error tagging volume %s: %v", volumeName, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	return volumeName, nil
 | 
						return volumeName, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2115,36 +2086,6 @@ func (c *Cloud) removeSecurityGroupIngress(securityGroupID string, removePermiss
 | 
				
			|||||||
	return true, nil
 | 
						return true, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 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,
 | 
					 | 
				
			||||||
// and we add the tags.  If it has a different cluster's tags, that is an error.
 | 
					 | 
				
			||||||
func (c *Cloud) ensureClusterTags(resourceID string, tags []*ec2.Tag) error {
 | 
					 | 
				
			||||||
	actualTags := make(map[string]string)
 | 
					 | 
				
			||||||
	for _, tag := range tags {
 | 
					 | 
				
			||||||
		actualTags[aws.StringValue(tag.Key)] = aws.StringValue(tag.Value)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	addTags := make(map[string]string)
 | 
					 | 
				
			||||||
	for k, expected := range c.filterTags {
 | 
					 | 
				
			||||||
		actual := actualTags[k]
 | 
					 | 
				
			||||||
		if actual == expected {
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if actual == "" {
 | 
					 | 
				
			||||||
			glog.Warningf("Resource %q was missing expected cluster tag %q.  Will add (with value %q)", resourceID, k, expected)
 | 
					 | 
				
			||||||
			addTags[k] = expected
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			return fmt.Errorf("resource %q has tag belonging to another cluster: %q=%q (expected %q)", resourceID, k, actual, expected)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err := c.createTags(resourceID, addTags); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("error adding missing tags to resource %q: %v", resourceID, err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Makes sure the security group exists.
 | 
					// Makes sure the security group exists.
 | 
				
			||||||
// For multi-cluster isolation, name must be globally unique, for example derived from the service UUID.
 | 
					// For multi-cluster isolation, name must be globally unique, for example derived from the service UUID.
 | 
				
			||||||
// Returns the security group id or error
 | 
					// Returns the security group id or error
 | 
				
			||||||
@@ -2175,7 +2116,9 @@ func (c *Cloud) ensureSecurityGroup(name string, description string) (string, er
 | 
				
			|||||||
			if len(securityGroups) > 1 {
 | 
								if len(securityGroups) > 1 {
 | 
				
			||||||
				glog.Warningf("Found multiple security groups with name: %q", name)
 | 
									glog.Warningf("Found multiple security groups with name: %q", name)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			err := c.ensureClusterTags(aws.StringValue(securityGroups[0].GroupId), securityGroups[0].Tags)
 | 
								err := c.tagging.readRepairClusterTags(
 | 
				
			||||||
 | 
									c.ec2, aws.StringValue(securityGroups[0].GroupId),
 | 
				
			||||||
 | 
									ResourceLifecycleOwned, nil, securityGroups[0].Tags)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				return "", err
 | 
									return "", err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@@ -2212,7 +2155,7 @@ func (c *Cloud) ensureSecurityGroup(name string, description string) (string, er
 | 
				
			|||||||
		return "", fmt.Errorf("created security group, but id was not returned: %s", name)
 | 
							return "", fmt.Errorf("created security group, but id was not returned: %s", name)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err := c.createTags(groupID, c.filterTags)
 | 
						err := c.tagging.createTags(c.ec2, groupID, ResourceLifecycleOwned, nil)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		// If we retry, ensureClusterTags will recover from this - it
 | 
							// If we retry, ensureClusterTags will recover from this - it
 | 
				
			||||||
		// will add the missing tags.  We could delete the security
 | 
							// will add the missing tags.  We could delete the security
 | 
				
			||||||
@@ -2223,52 +2166,6 @@ func (c *Cloud) ensureSecurityGroup(name string, description string) (string, er
 | 
				
			|||||||
	return groupID, nil
 | 
						return groupID, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// createTags calls EC2 CreateTags, but adds retry-on-failure logic
 | 
					 | 
				
			||||||
// We retry mainly because if we create an object, we cannot tag it until it is "fully created" (eventual consistency)
 | 
					 | 
				
			||||||
// The error code varies though (depending on what we are tagging), so we simply retry on all errors
 | 
					 | 
				
			||||||
func (c *Cloud) createTags(resourceID string, tags map[string]string) error {
 | 
					 | 
				
			||||||
	if tags == nil || len(tags) == 0 {
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var awsTags []*ec2.Tag
 | 
					 | 
				
			||||||
	for k, v := range tags {
 | 
					 | 
				
			||||||
		tag := &ec2.Tag{
 | 
					 | 
				
			||||||
			Key:   aws.String(k),
 | 
					 | 
				
			||||||
			Value: aws.String(v),
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		awsTags = append(awsTags, tag)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	backoff := wait.Backoff{
 | 
					 | 
				
			||||||
		Duration: createTagInitialDelay,
 | 
					 | 
				
			||||||
		Factor:   createTagFactor,
 | 
					 | 
				
			||||||
		Steps:    createTagSteps,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	request := &ec2.CreateTagsInput{}
 | 
					 | 
				
			||||||
	request.Resources = []*string{&resourceID}
 | 
					 | 
				
			||||||
	request.Tags = awsTags
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var lastErr error
 | 
					 | 
				
			||||||
	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
 | 
					 | 
				
			||||||
		_, err := c.ec2.CreateTags(request)
 | 
					 | 
				
			||||||
		if err == nil {
 | 
					 | 
				
			||||||
			return true, nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// We could check that the error is retryable, but the error code changes based on what we are tagging
 | 
					 | 
				
			||||||
		// SecurityGroup: InvalidGroup.NotFound
 | 
					 | 
				
			||||||
		glog.V(2).Infof("Failed to create tags; will retry.  Error was %v", err)
 | 
					 | 
				
			||||||
		lastErr = err
 | 
					 | 
				
			||||||
		return false, nil
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	if err == wait.ErrWaitTimeout {
 | 
					 | 
				
			||||||
		// return real CreateTags error instead of timeout
 | 
					 | 
				
			||||||
		err = lastErr
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return err
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Finds the value for a given tag.
 | 
					// Finds the value for a given tag.
 | 
				
			||||||
func findTag(tags []*ec2.Tag, key string) (string, bool) {
 | 
					func findTag(tags []*ec2.Tag, key string) (string, bool) {
 | 
				
			||||||
	for _, tag := range tags {
 | 
						for _, tag := range tags {
 | 
				
			||||||
@@ -2284,18 +2181,23 @@ func findTag(tags []*ec2.Tag, key string) (string, bool) {
 | 
				
			|||||||
// 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) {
 | 
				
			||||||
	request := &ec2.DescribeSubnetsInput{}
 | 
						request := &ec2.DescribeSubnetsInput{}
 | 
				
			||||||
	vpcIDFilter := newEc2Filter("vpc-id", c.vpcID)
 | 
						filters := []*ec2.Filter{newEc2Filter("vpc-id", c.vpcID)}
 | 
				
			||||||
	filters := []*ec2.Filter{vpcIDFilter}
 | 
						request.Filters = c.tagging.addFilters(filters)
 | 
				
			||||||
	filters = c.addFilters(filters)
 | 
					 | 
				
			||||||
	request.Filters = filters
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	subnets, err := c.ec2.DescribeSubnets(request)
 | 
						subnets, err := c.ec2.DescribeSubnets(request)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, fmt.Errorf("error describing subnets: %v", err)
 | 
							return nil, fmt.Errorf("error describing subnets: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(subnets) != 0 {
 | 
						var matches []*ec2.Subnet
 | 
				
			||||||
		return subnets, nil
 | 
						for _, subnet := range subnets {
 | 
				
			||||||
 | 
							if c.tagging.hasClusterTag(subnet.Tags) {
 | 
				
			||||||
 | 
								matches = append(matches, subnet)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(matches) != 0 {
 | 
				
			||||||
 | 
							return matches, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Fall back to the current instance subnets, if nothing is tagged
 | 
						// Fall back to the current instance subnets, if nothing is tagged
 | 
				
			||||||
@@ -2850,7 +2752,7 @@ func findSecurityGroupForInstance(instance *ec2.Instance, taggedSecurityGroups m
 | 
				
			|||||||
// Return all the security groups that are tagged as being part of our cluster
 | 
					// Return all the security groups that are tagged as being part of our cluster
 | 
				
			||||||
func (c *Cloud) getTaggedSecurityGroups() (map[string]*ec2.SecurityGroup, error) {
 | 
					func (c *Cloud) getTaggedSecurityGroups() (map[string]*ec2.SecurityGroup, error) {
 | 
				
			||||||
	request := &ec2.DescribeSecurityGroupsInput{}
 | 
						request := &ec2.DescribeSecurityGroupsInput{}
 | 
				
			||||||
	request.Filters = c.addFilters(nil)
 | 
						request.Filters = c.tagging.addFilters(nil)
 | 
				
			||||||
	groups, err := c.ec2.DescribeSecurityGroups(request)
 | 
						groups, err := c.ec2.DescribeSecurityGroups(request)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, fmt.Errorf("error querying security groups: %v", err)
 | 
							return nil, fmt.Errorf("error querying security groups: %v", err)
 | 
				
			||||||
@@ -2858,6 +2760,10 @@ func (c *Cloud) getTaggedSecurityGroups() (map[string]*ec2.SecurityGroup, error)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	m := make(map[string]*ec2.SecurityGroup)
 | 
						m := make(map[string]*ec2.SecurityGroup)
 | 
				
			||||||
	for _, group := range groups {
 | 
						for _, group := range groups {
 | 
				
			||||||
 | 
							if !c.tagging.hasClusterTag(group.Tags) {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		id := aws.StringValue(group.GroupId)
 | 
							id := aws.StringValue(group.GroupId)
 | 
				
			||||||
		if id == "" {
 | 
							if id == "" {
 | 
				
			||||||
			glog.Warningf("Ignoring group without id: %v", group)
 | 
								glog.Warningf("Ignoring group without id: %v", group)
 | 
				
			||||||
@@ -2892,14 +2798,24 @@ func (c *Cloud) updateInstanceSecurityGroupsForLoadBalancer(lb *elb.LoadBalancer
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Get the actual list of groups that allow ingress from the load-balancer
 | 
						// Get the actual list of groups that allow ingress from the load-balancer
 | 
				
			||||||
 | 
						var actualGroups []*ec2.SecurityGroup
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
		describeRequest := &ec2.DescribeSecurityGroupsInput{}
 | 
							describeRequest := &ec2.DescribeSecurityGroupsInput{}
 | 
				
			||||||
	filters := []*ec2.Filter{}
 | 
							filters := []*ec2.Filter{
 | 
				
			||||||
	filters = append(filters, newEc2Filter("ip-permission.group-id", loadBalancerSecurityGroupID))
 | 
								newEc2Filter("ip-permission.group-id", loadBalancerSecurityGroupID),
 | 
				
			||||||
	describeRequest.Filters = c.addFilters(filters)
 | 
							}
 | 
				
			||||||
	actualGroups, err := c.ec2.DescribeSecurityGroups(describeRequest)
 | 
							describeRequest.Filters = c.tagging.addFilters(filters)
 | 
				
			||||||
 | 
							response, err := c.ec2.DescribeSecurityGroups(describeRequest)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return fmt.Errorf("error querying security groups for ELB: %v", err)
 | 
								return fmt.Errorf("error querying security groups for ELB: %v", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							for _, sg := range response {
 | 
				
			||||||
 | 
								if !c.tagging.hasClusterTag(sg.Tags) {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								actualGroups = append(actualGroups, sg)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	taggedSecurityGroups, err := c.getTaggedSecurityGroups()
 | 
						taggedSecurityGroups, err := c.getTaggedSecurityGroups()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -3187,12 +3103,7 @@ func (c *Cloud) getInstancesByNodeNamesCached(nodeNames sets.String) ([]*ec2.Ins
 | 
				
			|||||||
		newEc2Filter("instance-state-name", "running"),
 | 
							newEc2Filter("instance-state-name", "running"),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	filters = c.addFilters(filters)
 | 
						instances, err := c.describeInstances(filters)
 | 
				
			||||||
	request := &ec2.DescribeInstancesInput{
 | 
					 | 
				
			||||||
		Filters: filters,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	instances, err := c.ec2.DescribeInstances(request)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		glog.V(2).Infof("Failed to describe instances %v", nodeNames)
 | 
							glog.V(2).Infof("Failed to describe instances %v", nodeNames)
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
@@ -3209,6 +3120,26 @@ func (c *Cloud) getInstancesByNodeNamesCached(nodeNames sets.String) ([]*ec2.Ins
 | 
				
			|||||||
	return instances, nil
 | 
						return instances, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Cloud) describeInstances(filters []*ec2.Filter) ([]*ec2.Instance, error) {
 | 
				
			||||||
 | 
						filters = c.tagging.addFilters(filters)
 | 
				
			||||||
 | 
						request := &ec2.DescribeInstancesInput{
 | 
				
			||||||
 | 
							Filters: filters,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						response, err := c.ec2.DescribeInstances(request)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var matches []*ec2.Instance
 | 
				
			||||||
 | 
						for _, instance := range response {
 | 
				
			||||||
 | 
							if c.tagging.hasClusterTag(instance.Tags) {
 | 
				
			||||||
 | 
								matches = append(matches, instance)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return matches, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// mapNodeNameToPrivateDNSName maps a k8s NodeName to an AWS Instance PrivateDNSName
 | 
					// mapNodeNameToPrivateDNSName maps a k8s NodeName to an AWS Instance PrivateDNSName
 | 
				
			||||||
// This is a simple string cast
 | 
					// This is a simple string cast
 | 
				
			||||||
func mapNodeNameToPrivateDNSName(nodeName types.NodeName) string {
 | 
					func mapNodeNameToPrivateDNSName(nodeName types.NodeName) string {
 | 
				
			||||||
@@ -3228,15 +3159,12 @@ func (c *Cloud) findInstanceByNodeName(nodeName types.NodeName) (*ec2.Instance,
 | 
				
			|||||||
		newEc2Filter("private-dns-name", privateDNSName),
 | 
							newEc2Filter("private-dns-name", privateDNSName),
 | 
				
			||||||
		newEc2Filter("instance-state-name", "running"),
 | 
							newEc2Filter("instance-state-name", "running"),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	filters = c.addFilters(filters)
 | 
					 | 
				
			||||||
	request := &ec2.DescribeInstancesInput{
 | 
					 | 
				
			||||||
		Filters: filters,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	instances, err := c.ec2.DescribeInstances(request)
 | 
						instances, err := c.describeInstances(filters)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(instances) == 0 {
 | 
						if len(instances) == 0 {
 | 
				
			||||||
		return nil, nil
 | 
							return nil, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -3268,23 +3196,3 @@ func (c *Cloud) getFullInstance(nodeName types.NodeName) (*awsInstance, *ec2.Ins
 | 
				
			|||||||
	awsInstance := newAWSInstance(c.ec2, instance)
 | 
						awsInstance := newAWSInstance(c.ec2, instance)
 | 
				
			||||||
	return awsInstance, instance, err
 | 
						return awsInstance, instance, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Add additional filters, to match on our tags
 | 
					 | 
				
			||||||
// This lets us run multiple k8s clusters in a single EC2 AZ
 | 
					 | 
				
			||||||
func (c *Cloud) addFilters(filters []*ec2.Filter) []*ec2.Filter {
 | 
					 | 
				
			||||||
	for k, v := range c.filterTags {
 | 
					 | 
				
			||||||
		filters = append(filters, newEc2Filter("tag:"+k, v))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if len(filters) == 0 {
 | 
					 | 
				
			||||||
		// We can't pass a zero-length Filters to AWS (it's an error)
 | 
					 | 
				
			||||||
		// So if we end up with no filters; just return nil
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return filters
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Returns the cluster name or an empty string
 | 
					 | 
				
			||||||
func (c *Cloud) getClusterName() string {
 | 
					 | 
				
			||||||
	return c.filterTags[TagNameKubernetesCluster]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -55,9 +55,14 @@ func (c *Cloud) ensureLoadBalancer(namespacedName types.NamespacedName, loadBala
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		createRequest.SecurityGroups = stringPointerArray(securityGroupIDs)
 | 
							createRequest.SecurityGroups = stringPointerArray(securityGroupIDs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		createRequest.Tags = []*elb.Tag{
 | 
							tags := c.tagging.buildTags(ResourceLifecycleOwned, map[string]string{
 | 
				
			||||||
			{Key: aws.String(TagNameKubernetesCluster), Value: aws.String(c.getClusterName())},
 | 
								TagNameKubernetesService: namespacedName.String(),
 | 
				
			||||||
			{Key: aws.String(TagNameKubernetesService), Value: aws.String(namespacedName.String())},
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for k, v := range tags {
 | 
				
			||||||
 | 
								createRequest.Tags = append(createRequest.Tags, &elb.Tag{
 | 
				
			||||||
 | 
									Key: aws.String(k), Value: aws.String(v),
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		glog.Infof("Creating load balancer for %v with name: %s", namespacedName, loadBalancerName)
 | 
							glog.Infof("Creating load balancer for %v with name: %s", namespacedName, loadBalancerName)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,14 +29,20 @@ func (c *Cloud) findRouteTable(clusterName string) (*ec2.RouteTable, error) {
 | 
				
			|||||||
	// This should be unnecessary (we already filter on TagNameKubernetesCluster,
 | 
						// This should be unnecessary (we already filter on TagNameKubernetesCluster,
 | 
				
			||||||
	// and something is broken if cluster name doesn't match, but anyway...
 | 
						// and something is broken if cluster name doesn't match, but anyway...
 | 
				
			||||||
	// TODO: All clouds should be cluster-aware by default
 | 
						// TODO: All clouds should be cluster-aware by default
 | 
				
			||||||
	filters := []*ec2.Filter{newEc2Filter("tag:"+TagNameKubernetesCluster, clusterName)}
 | 
						request := &ec2.DescribeRouteTablesInput{Filters: c.tagging.addFilters(nil)}
 | 
				
			||||||
	request := &ec2.DescribeRouteTablesInput{Filters: c.addFilters(filters)}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tables, err := c.ec2.DescribeRouteTables(request)
 | 
						response, err := c.ec2.DescribeRouteTables(request)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var tables []*ec2.RouteTable
 | 
				
			||||||
 | 
						for _, table := range response {
 | 
				
			||||||
 | 
							if c.tagging.hasClusterTag(table.Tags) {
 | 
				
			||||||
 | 
								tables = append(tables, table)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(tables) == 0 {
 | 
						if len(tables) == 0 {
 | 
				
			||||||
		return nil, fmt.Errorf("unable to find route table for AWS cluster: %s", clusterName)
 | 
							return nil, fmt.Errorf("unable to find route table for AWS cluster: %s", clusterName)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -146,7 +146,7 @@ func NewFakeAWSServices() *FakeAWSServices {
 | 
				
			|||||||
	s.instances = []*ec2.Instance{selfInstance}
 | 
						s.instances = []*ec2.Instance{selfInstance}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var tag ec2.Tag
 | 
						var tag ec2.Tag
 | 
				
			||||||
	tag.Key = aws.String(TagNameKubernetesCluster)
 | 
						tag.Key = aws.String(TagNameKubernetesClusterLegacy)
 | 
				
			||||||
	tag.Value = aws.String(TestClusterId)
 | 
						tag.Value = aws.String(TestClusterId)
 | 
				
			||||||
	selfInstance.Tags = []*ec2.Tag{&tag}
 | 
						selfInstance.Tags = []*ec2.Tag{&tag}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -177,24 +177,6 @@ func (s *FakeAWSServices) Metadata() (EC2Metadata, error) {
 | 
				
			|||||||
	return s.metadata, nil
 | 
						return s.metadata, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestFilterTags(t *testing.T) {
 | 
					 | 
				
			||||||
	awsServices := NewFakeAWSServices()
 | 
					 | 
				
			||||||
	c, err := newAWSCloud(strings.NewReader("[global]"), awsServices)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		t.Errorf("Error building aws cloud: %v", err)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if len(c.filterTags) != 1 {
 | 
					 | 
				
			||||||
		t.Errorf("unexpected filter tags: %v", c.filterTags)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if c.filterTags[TagNameKubernetesCluster] != TestClusterId {
 | 
					 | 
				
			||||||
		t.Errorf("unexpected filter tags: %v", c.filterTags)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestNewAWSCloud(t *testing.T) {
 | 
					func TestNewAWSCloud(t *testing.T) {
 | 
				
			||||||
	tests := []struct {
 | 
						tests := []struct {
 | 
				
			||||||
		name string
 | 
							name string
 | 
				
			||||||
@@ -279,6 +261,15 @@ func instanceMatchesFilter(instance *ec2.Instance, filter *ec2.Filter) bool {
 | 
				
			|||||||
		return contains(filter.Values, *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:") {
 | 
						if strings.HasPrefix(name, "tag:") {
 | 
				
			||||||
		tagName := name[4:]
 | 
							tagName := name[4:]
 | 
				
			||||||
		for _, instanceTag := range instance.Tags {
 | 
							for _, instanceTag := range instance.Tags {
 | 
				
			||||||
@@ -286,7 +277,9 @@ func instanceMatchesFilter(instance *ec2.Instance, filter *ec2.Filter) bool {
 | 
				
			|||||||
				return true
 | 
									return true
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	panic("Unknown filter name: " + name)
 | 
						panic("Unknown filter name: " + name)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -969,13 +962,14 @@ func TestIpPermissionExistsHandlesMultipleGroupIdsWithUserIds(t *testing.T) {
 | 
				
			|||||||
		t.Errorf("Should have not been considered equal since first is not in the second array of groups")
 | 
							t.Errorf("Should have not been considered equal since first is not in the second array of groups")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestFindInstanceByNodeNameExcludesTerminatedInstances(t *testing.T) {
 | 
					func TestFindInstanceByNodeNameExcludesTerminatedInstances(t *testing.T) {
 | 
				
			||||||
	awsServices := NewFakeAWSServices()
 | 
						awsServices := NewFakeAWSServices()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	nodeName := types.NodeName("my-dns.internal")
 | 
						nodeName := types.NodeName("my-dns.internal")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var tag ec2.Tag
 | 
						var tag ec2.Tag
 | 
				
			||||||
	tag.Key = aws.String(TagNameKubernetesCluster)
 | 
						tag.Key = aws.String(TagNameKubernetesClusterLegacy)
 | 
				
			||||||
	tag.Value = aws.String(TestClusterId)
 | 
						tag.Value = aws.String(TestClusterId)
 | 
				
			||||||
	tags := []*ec2.Tag{&tag}
 | 
						tags := []*ec2.Tag{&tag}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1019,8 +1013,8 @@ func TestFindInstancesByNodeNameCached(t *testing.T) {
 | 
				
			|||||||
	nodeNameTwo := "my-dns-two.internal"
 | 
						nodeNameTwo := "my-dns-two.internal"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var tag ec2.Tag
 | 
						var tag ec2.Tag
 | 
				
			||||||
	tag.Key = aws.String(TagNameKubernetesCluster)
 | 
						tag.Key = aws.String(TagNameKubernetesClusterPrefix + TestClusterId)
 | 
				
			||||||
	tag.Value = aws.String(TestClusterId)
 | 
						tag.Value = aws.String("")
 | 
				
			||||||
	tags := []*ec2.Tag{&tag}
 | 
						tags := []*ec2.Tag{&tag}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var runningInstance ec2.Instance
 | 
						var runningInstance ec2.Instance
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										256
									
								
								pkg/cloudprovider/providers/aws/tags.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								pkg/cloudprovider/providers/aws/tags.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,256 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					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 (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/aws/aws-sdk-go/aws"
 | 
				
			||||||
 | 
						"github.com/aws/aws-sdk-go/service/ec2"
 | 
				
			||||||
 | 
						"github.com/golang/glog"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/util/wait"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TagNameKubernetesClusterPrefix is the tag name we use to differentiate multiple
 | 
				
			||||||
 | 
					// logically independent clusters running in the same AZ.
 | 
				
			||||||
 | 
					// The tag key = TagNameKubernetesClusterPrefix + clusterID
 | 
				
			||||||
 | 
					// The tag value is an ownership value
 | 
				
			||||||
 | 
					const TagNameKubernetesClusterPrefix = "kubernetes.io/cluster/"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TagNameKubernetesClusterLegacy is the legacy tag name we use to differentiate multiple
 | 
				
			||||||
 | 
					// logically independent clusters running in the same AZ.  The problem with it was that it
 | 
				
			||||||
 | 
					// did not allow shared resources.
 | 
				
			||||||
 | 
					const TagNameKubernetesClusterLegacy = "KubernetesCluster"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ResourceLifecycle string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// ResourceLifecycleOwned is the value we use when tagging resources to indicate
 | 
				
			||||||
 | 
						// that the resource is considered owned and managed by the cluster,
 | 
				
			||||||
 | 
						// and in particular that the lifecycle is tied to the lifecycle of the cluster.
 | 
				
			||||||
 | 
						ResourceLifecycleOwned = "owned"
 | 
				
			||||||
 | 
						// ResourceLifecycleShared is the value we use when tagging resources to indicate
 | 
				
			||||||
 | 
						// that the resource is shared between multiple clusters, and should not be destroyed
 | 
				
			||||||
 | 
						// if the cluster is destroyed.
 | 
				
			||||||
 | 
						ResourceLifecycleShared = "shared"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type awsTagging struct {
 | 
				
			||||||
 | 
						// ClusterID is our cluster identifier: we tag AWS resources with this value,
 | 
				
			||||||
 | 
						// and thus we can run two independent clusters in the same VPC or subnets.
 | 
				
			||||||
 | 
						// This gives us similar functionality to GCE projects.
 | 
				
			||||||
 | 
						ClusterID string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// usesLegacyTags is true if we are using the legacy TagNameKubernetesClusterLegacy tags
 | 
				
			||||||
 | 
						usesLegacyTags bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *awsTagging) init(legacyClusterID string, clusterID string) error {
 | 
				
			||||||
 | 
						if legacyClusterID != "" {
 | 
				
			||||||
 | 
							if clusterID != "" && legacyClusterID != clusterID {
 | 
				
			||||||
 | 
								return fmt.Errorf("ClusterID tags did not match: %q vs %q", clusterID, legacyClusterID)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							t.usesLegacyTags = true
 | 
				
			||||||
 | 
							clusterID = legacyClusterID
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.ClusterID = clusterID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if clusterID != "" {
 | 
				
			||||||
 | 
							glog.Infof("AWS cloud filtering on ClusterID: %v", clusterID)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							glog.Infof("AWS cloud - no clusterID filtering")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Extracts a clusterID from the given tags, if one is present
 | 
				
			||||||
 | 
					// If no clusterID is found, returns "", nil
 | 
				
			||||||
 | 
					// If multiple (different) clusterIDs are found, returns an error
 | 
				
			||||||
 | 
					func (t *awsTagging) initFromTags(tags []*ec2.Tag) error {
 | 
				
			||||||
 | 
						legacyClusterID, newClusterID, err := findClusterIDs(tags)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if legacyClusterID == "" && newClusterID == "" {
 | 
				
			||||||
 | 
							glog.Errorf("Tag %q nor %q not found; Kubernetes may behave unexpectedly.", TagNameKubernetesClusterLegacy, TagNameKubernetesClusterPrefix+"...")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return t.init(legacyClusterID, newClusterID)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Extracts the legacy & new cluster ids from the given tags, if they are present
 | 
				
			||||||
 | 
					// If duplicate tags are found, returns an error
 | 
				
			||||||
 | 
					func findClusterIDs(tags []*ec2.Tag) (string, string, error) {
 | 
				
			||||||
 | 
						legacyClusterID := ""
 | 
				
			||||||
 | 
						newClusterID := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tag := range tags {
 | 
				
			||||||
 | 
							tagKey := aws.StringValue(tag.Key)
 | 
				
			||||||
 | 
							if strings.HasPrefix(tagKey, TagNameKubernetesClusterPrefix) {
 | 
				
			||||||
 | 
								id := strings.TrimPrefix(tagKey, TagNameKubernetesClusterPrefix)
 | 
				
			||||||
 | 
								if newClusterID != "" {
 | 
				
			||||||
 | 
									return "", "", fmt.Errorf("Found multiple cluster tags with prefix %s (%q and %q)", TagNameKubernetesClusterPrefix, newClusterID, id)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								newClusterID = id
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if tagKey == TagNameKubernetesClusterLegacy {
 | 
				
			||||||
 | 
								id := aws.StringValue(tag.Value)
 | 
				
			||||||
 | 
								if legacyClusterID != "" {
 | 
				
			||||||
 | 
									return "", "", fmt.Errorf("Found multiple %s tags (%q and %q)", TagNameKubernetesClusterLegacy, legacyClusterID, id)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								legacyClusterID = id
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return legacyClusterID, newClusterID, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *awsTagging) clusterTagKey() string {
 | 
				
			||||||
 | 
						return TagNameKubernetesClusterPrefix + t.ClusterID
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *awsTagging) hasClusterTag(tags []*ec2.Tag) bool {
 | 
				
			||||||
 | 
						clusterTagKey := t.clusterTagKey()
 | 
				
			||||||
 | 
						for _, tag := range tags {
 | 
				
			||||||
 | 
							tagKey := aws.StringValue(tag.Key)
 | 
				
			||||||
 | 
							// For 1.6, we continue to recognize the legacy tags, for the 1.5 -> 1.6 upgrade
 | 
				
			||||||
 | 
							if tagKey == TagNameKubernetesClusterLegacy {
 | 
				
			||||||
 | 
								return aws.StringValue(tag.Value) == t.ClusterID
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if tagKey == clusterTagKey {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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,
 | 
				
			||||||
 | 
					// and we add the tags.  If it has a different cluster's tags, that is an error.
 | 
				
			||||||
 | 
					func (c *awsTagging) readRepairClusterTags(client EC2, resourceID string, lifecycle ResourceLifecycle, additionalTags map[string]string, observedTags []*ec2.Tag) error {
 | 
				
			||||||
 | 
						actualTagMap := make(map[string]string)
 | 
				
			||||||
 | 
						for _, tag := range observedTags {
 | 
				
			||||||
 | 
							actualTagMap[aws.StringValue(tag.Key)] = aws.StringValue(tag.Value)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						expectedTags := c.buildTags(lifecycle, additionalTags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						addTags := make(map[string]string)
 | 
				
			||||||
 | 
						for k, expected := range expectedTags {
 | 
				
			||||||
 | 
							actual := actualTagMap[k]
 | 
				
			||||||
 | 
							if actual == expected {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if actual == "" {
 | 
				
			||||||
 | 
								glog.Warningf("Resource %q was missing expected cluster tag %q.  Will add (with value %q)", resourceID, k, expected)
 | 
				
			||||||
 | 
								addTags[k] = expected
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return fmt.Errorf("resource %q has tag belonging to another cluster: %q=%q (expected %q)", resourceID, k, actual, expected)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := c.createTags(client, resourceID, lifecycle, additionalTags); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("error adding missing tags to resource %q: %v", resourceID, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// createTags calls EC2 CreateTags, but adds retry-on-failure logic
 | 
				
			||||||
 | 
					// We retry mainly because if we create an object, we cannot tag it until it is "fully created" (eventual consistency)
 | 
				
			||||||
 | 
					// The error code varies though (depending on what we are tagging), so we simply retry on all errors
 | 
				
			||||||
 | 
					func (t *awsTagging) createTags(client EC2, resourceID string, lifecycle ResourceLifecycle, additionalTags map[string]string) error {
 | 
				
			||||||
 | 
						tags := t.buildTags(lifecycle, additionalTags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if tags == nil || len(tags) == 0 {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var awsTags []*ec2.Tag
 | 
				
			||||||
 | 
						for k, v := range tags {
 | 
				
			||||||
 | 
							tag := &ec2.Tag{
 | 
				
			||||||
 | 
								Key:   aws.String(k),
 | 
				
			||||||
 | 
								Value: aws.String(v),
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							awsTags = append(awsTags, tag)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						backoff := wait.Backoff{
 | 
				
			||||||
 | 
							Duration: createTagInitialDelay,
 | 
				
			||||||
 | 
							Factor:   createTagFactor,
 | 
				
			||||||
 | 
							Steps:    createTagSteps,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						request := &ec2.CreateTagsInput{}
 | 
				
			||||||
 | 
						request.Resources = []*string{&resourceID}
 | 
				
			||||||
 | 
						request.Tags = awsTags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var lastErr error
 | 
				
			||||||
 | 
						err := wait.ExponentialBackoff(backoff, func() (bool, error) {
 | 
				
			||||||
 | 
							_, err := client.CreateTags(request)
 | 
				
			||||||
 | 
							if err == nil {
 | 
				
			||||||
 | 
								return true, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// We could check that the error is retryable, but the error code changes based on what we are tagging
 | 
				
			||||||
 | 
							// SecurityGroup: InvalidGroup.NotFound
 | 
				
			||||||
 | 
							glog.V(2).Infof("Failed to create tags; will retry.  Error was %v", err)
 | 
				
			||||||
 | 
							lastErr = err
 | 
				
			||||||
 | 
							return false, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err == wait.ErrWaitTimeout {
 | 
				
			||||||
 | 
							// return real CreateTags error instead of timeout
 | 
				
			||||||
 | 
							err = lastErr
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Add additional filters, to match on our tags
 | 
				
			||||||
 | 
					// This lets us run multiple k8s clusters in a single EC2 AZ
 | 
				
			||||||
 | 
					func (t *awsTagging) addFilters(filters []*ec2.Filter) []*ec2.Filter {
 | 
				
			||||||
 | 
						// For 1.6, we always recognize the legacy tag, for the 1.5 -> 1.6 upgrade
 | 
				
			||||||
 | 
						// There are no "or" filters by key, so we look for both the legacy and new key, and then we have to post-filter
 | 
				
			||||||
 | 
						f := newEc2Filter("tag-key", TagNameKubernetesClusterLegacy, t.clusterTagKey())
 | 
				
			||||||
 | 
						filters = append(filters, f)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(filters) == 0 {
 | 
				
			||||||
 | 
							// We can't pass a zero-length Filters to AWS (it's an error)
 | 
				
			||||||
 | 
							// So if we end up with no filters; just return nil
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return filters
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *awsTagging) buildTags(lifecycle ResourceLifecycle, additionalTags map[string]string) map[string]string {
 | 
				
			||||||
 | 
						tags := make(map[string]string)
 | 
				
			||||||
 | 
						for k, v := range additionalTags {
 | 
				
			||||||
 | 
							tags[k] = v
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// We only create legacy tags if we are using legacy tags, i.e. if we have seen a legacy tag on our instance
 | 
				
			||||||
 | 
						if t.usesLegacyTags {
 | 
				
			||||||
 | 
							tags[TagNameKubernetesClusterLegacy] = t.ClusterID
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tags[t.clusterTagKey()] = string(lifecycle)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return tags
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										111
									
								
								pkg/cloudprovider/providers/aws/tags_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								pkg/cloudprovider/providers/aws/tags_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2014 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 (
 | 
				
			||||||
 | 
						"github.com/aws/aws-sdk-go/aws"
 | 
				
			||||||
 | 
						"github.com/aws/aws-sdk-go/service/ec2"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestFilterTags(t *testing.T) {
 | 
				
			||||||
 | 
						awsServices := NewFakeAWSServices()
 | 
				
			||||||
 | 
						c, err := newAWSCloud(strings.NewReader("[global]"), awsServices)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Errorf("Error building aws cloud: %v", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.tagging.ClusterID != TestClusterId {
 | 
				
			||||||
 | 
							t.Errorf("unexpected ClusterID: %v", c.tagging.ClusterID)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestFindClusterID(t *testing.T) {
 | 
				
			||||||
 | 
						grid := []struct {
 | 
				
			||||||
 | 
							Tags           map[string]string
 | 
				
			||||||
 | 
							ExpectedNew    string
 | 
				
			||||||
 | 
							ExpectedLegacy string
 | 
				
			||||||
 | 
							ExpectError    bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Tags: map[string]string{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Tags: map[string]string{
 | 
				
			||||||
 | 
									TagNameKubernetesClusterLegacy: "a",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								ExpectedLegacy: "a",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Tags: map[string]string{
 | 
				
			||||||
 | 
									TagNameKubernetesClusterPrefix + "a": "owned",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								ExpectedNew: "a",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Tags: map[string]string{
 | 
				
			||||||
 | 
									TagNameKubernetesClusterPrefix + "a": "",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								ExpectedNew: "a",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Tags: map[string]string{
 | 
				
			||||||
 | 
									TagNameKubernetesClusterLegacy:       "a",
 | 
				
			||||||
 | 
									TagNameKubernetesClusterPrefix + "a": "",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								ExpectedLegacy: "a",
 | 
				
			||||||
 | 
								ExpectedNew:    "a",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Tags: map[string]string{
 | 
				
			||||||
 | 
									TagNameKubernetesClusterPrefix + "a": "",
 | 
				
			||||||
 | 
									TagNameKubernetesClusterPrefix + "b": "",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								ExpectError: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, g := range grid {
 | 
				
			||||||
 | 
							var ec2Tags []*ec2.Tag
 | 
				
			||||||
 | 
							for k, v := range g.Tags {
 | 
				
			||||||
 | 
								ec2Tags = append(ec2Tags, &ec2.Tag{Key: aws.String(k), Value: aws.String(v)})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							actualLegacy, actualNew, err := findClusterIDs(ec2Tags)
 | 
				
			||||||
 | 
							if g.ExpectError {
 | 
				
			||||||
 | 
								if err == nil {
 | 
				
			||||||
 | 
									t.Errorf("expected error for tags %v", g.Tags)
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									t.Errorf("unexpected error for tags %v: %v", g.Tags, err)
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if g.ExpectedNew != actualNew {
 | 
				
			||||||
 | 
									t.Errorf("unexpected new clusterid for tags %v: %s vs %s", g.Tags, g.ExpectedNew, actualNew)
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if g.ExpectedLegacy != actualLegacy {
 | 
				
			||||||
 | 
									t.Errorf("unexpected new clusterid for tags %v: %s vs %s", g.Tags, g.ExpectedLegacy, actualLegacy)
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user