github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/experiment/aws-stockout/main.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"encoding/json"
    21  	"flag"
    22  	"fmt"
    23  	"os"
    24  
    25  	"github.com/aws/aws-sdk-go/aws"
    26  	"github.com/aws/aws-sdk-go/aws/awserr"
    27  	"github.com/aws/aws-sdk-go/aws/session"
    28  	"github.com/aws/aws-sdk-go/service/ec2"
    29  	"github.com/golang/glog"
    30  )
    31  
    32  var imageName = flag.String("image-name", "amzn-ami-hvm-2017.09.1.20180115-x86_64-gp2", "name of AMI to launch")
    33  var imageOwner = flag.String("image-owner", "137112412989", "owner of AMI to launch")
    34  
    35  var instanceType = flag.String("instance-type", "c4.large", "instance type to check")
    36  
    37  var tagKey = "k8s.io/test-infra/check-stockout"
    38  
    39  func main() {
    40  	flag.Set("alsologtostderr", "true")
    41  
    42  	flag.Parse()
    43  
    44  	err := run()
    45  	if err != nil {
    46  		fmt.Fprintf(os.Stderr, "%v\n", err)
    47  		os.Exit(1)
    48  	}
    49  }
    50  
    51  func run() error {
    52  	regions, err := findRegions()
    53  	if err != nil {
    54  		return err
    55  	}
    56  
    57  	// For faster testing:
    58  	//regions = []string{"us-east-1"}
    59  
    60  	defer func() {
    61  		for _, r := range regions {
    62  			if err := cleanupInstances(r); err != nil {
    63  				glog.Warningf("failed to cleanup region %s: %v", r, err)
    64  			}
    65  		}
    66  	}()
    67  
    68  	avail, err := checkInstanceTypeAvailability(regions, *instanceType)
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	o, err := json.MarshalIndent(avail, "", "  ")
    74  	if err != nil {
    75  		return fmt.Errorf("error marshaling json: %v", err)
    76  	}
    77  
    78  	fmt.Printf("\n%s\n", string(o))
    79  
    80  	return nil
    81  }
    82  
    83  func findRegions() ([]string, error) {
    84  	config := &aws.Config{}
    85  	sess, err := session.NewSession(config)
    86  	if err != nil {
    87  		return nil, fmt.Errorf("error creating aws session: %v", err)
    88  	}
    89  
    90  	svc := ec2.New(sess)
    91  
    92  	var regions []string
    93  	{
    94  		response, err := svc.DescribeRegions(&ec2.DescribeRegionsInput{})
    95  		if err != nil {
    96  			return nil, fmt.Errorf("error from DescribeRegions: %v", err)
    97  		}
    98  		for _, r := range response.Regions {
    99  			regions = append(regions, aws.StringValue(r.RegionName))
   100  		}
   101  	}
   102  	glog.Infof("regions: %v", regions)
   103  	return regions, nil
   104  }
   105  
   106  func checkInstanceTypeAvailability(regions []string, instanceType string) (map[string]bool, error) {
   107  	avail := make(map[string]bool)
   108  
   109  	var errorRegions []string
   110  	for _, r := range regions {
   111  		zone, err := launchInstance(r, instanceType)
   112  		if err != nil {
   113  			glog.Warningf("error from region %s: %v", r, err)
   114  			errorRegions = append(errorRegions, r)
   115  		} else {
   116  			avail[r] = zone != ""
   117  		}
   118  	}
   119  
   120  	if len(errorRegions) != 0 {
   121  		return nil, fmt.Errorf("some regions had errors: %v", errorRegions)
   122  	}
   123  
   124  	return avail, nil
   125  }
   126  
   127  func awsErrorCode(err error) string {
   128  	if awsErr, ok := err.(awserr.Error); ok {
   129  		return awsErr.Code()
   130  	}
   131  	return ""
   132  }
   133  
   134  func buildFilter(name, value string) *ec2.Filter {
   135  	return &ec2.Filter{Name: aws.String(name), Values: []*string{&value}}
   136  }
   137  
   138  func cleanupInstances(region string) error {
   139  	config := &aws.Config{Region: aws.String(region)}
   140  	sess, err := session.NewSession(config)
   141  	if err != nil {
   142  		return fmt.Errorf("error creating aws session: %v", err)
   143  	}
   144  
   145  	svc := ec2.New(sess)
   146  
   147  	instancesResponse, err := svc.DescribeInstances(&ec2.DescribeInstancesInput{
   148  		Filters: []*ec2.Filter{
   149  			buildFilter("tag-key", tagKey),
   150  		},
   151  	})
   152  	if err != nil {
   153  		return fmt.Errorf("error from DescribeInstances: %v", err)
   154  	}
   155  
   156  	for _, r := range instancesResponse.Reservations {
   157  		for _, i := range r.Instances {
   158  			id := aws.StringValue(i.InstanceId)
   159  			stateName := ""
   160  			if i.State != nil {
   161  				stateName = aws.StringValue(i.State.Name)
   162  			}
   163  
   164  			if stateName == "terminated" {
   165  				continue
   166  			}
   167  
   168  			glog.Infof("terminating existing instance %s in state %s", id, stateName)
   169  
   170  			_, err := svc.TerminateInstances(&ec2.TerminateInstancesInput{
   171  				InstanceIds: []*string{i.InstanceId},
   172  			})
   173  			if err != nil {
   174  				glog.Warningf("failed to terminate instance %s", aws.StringValue(i.InstanceId))
   175  			}
   176  
   177  		}
   178  	}
   179  
   180  	return nil
   181  }
   182  
   183  func launchInstance(region string, instanceType string) (string, error) {
   184  	config := &aws.Config{Region: aws.String(region)}
   185  	sess, err := session.NewSession(config)
   186  	if err != nil {
   187  		return "", fmt.Errorf("error creating aws session: %v", err)
   188  	}
   189  
   190  	svc := ec2.New(sess)
   191  
   192  	images, err := svc.DescribeImages(&ec2.DescribeImagesInput{
   193  		Filters: []*ec2.Filter{
   194  			buildFilter("owner-id", *imageOwner),
   195  			buildFilter("name", *imageName),
   196  		},
   197  	})
   198  	if err != nil {
   199  		return "", fmt.Errorf("error describing images: %v", err)
   200  	}
   201  
   202  	if len(images.Images) == 0 {
   203  		return "", fmt.Errorf("did not find AMI with owner=%s, name=%s", *imageOwner, *imageName)
   204  	}
   205  
   206  	if len(images.Images) != 1 {
   207  		return "", fmt.Errorf("found multiple AMIs with owner=%s, name=%s", *imageOwner, *imageName)
   208  	}
   209  
   210  	ami := aws.StringValue(images.Images[0].ImageId)
   211  	glog.Infof("%s: found AMI %s", region, ami)
   212  
   213  	createResponse, err := svc.RunInstances(&ec2.RunInstancesInput{
   214  		MinCount:     aws.Int64(1),
   215  		MaxCount:     aws.Int64(1),
   216  		ImageId:      aws.String(ami),
   217  		InstanceType: aws.String(instanceType),
   218  		TagSpecifications: []*ec2.TagSpecification{
   219  			{
   220  				ResourceType: aws.String("instance"),
   221  				Tags:         []*ec2.Tag{{Key: aws.String(tagKey), Value: aws.String("1")}},
   222  			},
   223  		},
   224  	})
   225  	if err != nil {
   226  		if awsErrorCode(err) == "InsufficientInstanceCapacity" {
   227  			glog.Infof("region %s has InsufficientInstanceCapacity for %s", region, instanceType)
   228  			return "", nil
   229  		}
   230  
   231  		if awsErrorCode(err) == "Unsupported" {
   232  			glog.Infof("region %s does not support instanceType %s", region, instanceType)
   233  			return "", nil
   234  		}
   235  
   236  		return "", fmt.Errorf("error from RunInstances: %v", err)
   237  	}
   238  
   239  	zone := aws.StringValue(createResponse.Instances[0].Placement.AvailabilityZone)
   240  	glog.Infof("created instance in region %s, zone %s: %s", region, zone, aws.StringValue(createResponse.Instances[0].InstanceId))
   241  
   242  	return zone, nil
   243  }