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 }