Skip to content

Commit

Permalink
pkg/types/aws/defaults/platform: Default us-west-2 to m5
Browse files Browse the repository at this point in the history
Some regions have per-zone support differences for available instance
classes.  For example, us-west-2 has m4 instances in zones a through
c, but not in zone d.  It supports m5 instances in all four zones [1].

Unfortunately, we can't switch to just using the reserved-instance
offerings, because Paris claims those for m4.large in all three of its
zones:

  $ AWS_PROFILE=ci aws --region eu-west-3 ec2 describe-reserved-instances-offerings --instance-tenancy default --instance-type m4.large --product-description 'Linux/UNIX' --filters Name=scope,Values='Availability Zone' | jq -r '[.ReservedInstancesOfferings[].AvailabilityZone] | sort | unique[]'
  eu-west-3a
  eu-west-3b
  eu-west-3c

but still does not price on-demand compute instances for that region:

  $ AWS_PROFILE=ci aws --region us-east-1 pricing get-products --service-code AmazonEC2 --filters Field=tenancy,Type=TERM_MATCH,Value=Shared Field=productFamily,Type=TERM_MATCH,Value='Compute Instance' Field=operatingSystem,Type=TERM_MATCH,Value=Linux Field=instanceFamily,Type=TERM_MATCH,Value='General purpose' | jq -r '[.PriceList[] | fromjson | .product.attributes | select(.instanceType == "m4.large").location] | sort | unique[]'
  AWS GovCloud (US)
  Asia Pacific (Mumbai)
  Asia Pacific (Osaka-Local)
  Asia Pacific (Seoul)
  Asia Pacific (Singapore)
  Asia Pacific (Sydney)
  Asia Pacific (Tokyo)
  Canada (Central)
  EU (Frankfurt)
  EU (Ireland)
  EU (London)
  South America (Sao Paulo)
  US East (N. Virginia)
  US East (Ohio)
  US West (N. California)
  US West (Oregon)

So the new test here only assumes a match when both APIs claim support
for the instance class in a given region to avoid re-breaking Paris
[2].

After rerolling the test to account for that sort of thing, I made the
default change to address:

  $ AWS_PROFILE=ci go test -count 1 .
  --- FAIL: TestGetDefaultInstanceClass (57.36s)
      --- FAIL: TestGetDefaultInstanceClass/US_West_(Oregon) (1.22s)
        default_instance_class_test.go:176: map[m4:map[us-west-2c:{} us-west-2a:{} us-west-2b:{}] m5:map[us-west-2b:{} us-west-2c:{} us-west-2d:{} us-west-2a:{}]]
        default_instance_class_test.go:177:
            Error Trace:  default_instance_class_test.go:177
            Error:        Not equal:
                          expected: "m4"
                          actual  : "m5"

                          Diff:
                          --- Expected
                          +++ Actual
                          @@ -1 +1 @@
                          -m4
                          +m5
            Test:         TestGetDefaultInstanceClass/US_West_(Oregon)
  FAIL
  FAIL  github.com/openshift/installer/platformtests/aws  57.374s

The zone-error fallback covers regions that I don't have access too,
where requesting zones returns errors like (us-gov-east-1):

  AuthFailure: AWS was not able to validate the provided access credentials

and (ap-northeast-3):

  OptInRequired: You are not subscribed to this service. Please go to http://aws.amazon.com to subscribe.

[1]: https://bugzilla.redhat.com/show_bug.cgi?id=1713157#c1
[2]: #1127
  • Loading branch information
wking authored and JacobTanenbaum committed Jun 4, 2019
1 parent 3d88fa0 commit 06e58e0
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 17 deletions.
1 change: 1 addition & 0 deletions pkg/types/aws/defaults/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var (
"eu-north-1": "m5",
"eu-west-3": "m5",
"us-gov-east-1": "m5",
"us-west-2": "m5",
}
)

Expand Down
100 changes: 83 additions & 17 deletions platformtests/aws/default_instance_class_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package aws

import (
"fmt"
"reflect"
"strings"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/pricing"
awsutil "github.com/openshift/installer/pkg/asset/installconfig/aws"
"github.com/openshift/installer/pkg/types/aws/defaults"
Expand All @@ -13,17 +16,18 @@ import (
)

func TestGetDefaultInstanceClass(t *testing.T) {
preferredInstanceClasses := []string{"m4", "m5"} // decreasing precedence

ssn, err := awsutil.GetSession()
if err != nil {
t.Fatal(err)
}

exists := struct{}{}
instanceClasses := map[string]map[string]struct{}{}

client := pricing.New(ssn, aws.NewConfig().WithRegion("us-east-1"))
pricingInstanceClasses := map[string]map[string]struct{}{}

err = client.GetProductsPages(
pricingClient := pricing.New(ssn, aws.NewConfig().WithRegion("us-east-1"))
err = pricingClient.GetProductsPages(
&pricing.GetProductsInput{
ServiceCode: aws.String("AmazonEC2"),
Filters: []*pricing.Filter{
Expand Down Expand Up @@ -57,11 +61,11 @@ func TestGetDefaultInstanceClass(t *testing.T) {
instanceType := attr["instanceType"].(string)
instanceClassSlice := strings.Split(instanceType, ".")
instanceClass := instanceClassSlice[0]
_, ok := instanceClasses[location]
_, ok := pricingInstanceClasses[location]
if ok {
instanceClasses[location][instanceClass] = exists
pricingInstanceClasses[location][instanceClass] = exists
} else {
instanceClasses[location] = map[string]struct{}{instanceClass: exists}
pricingInstanceClasses[location] = map[string]struct{}{instanceClass: exists}
}
}
return !lastPage
Expand All @@ -80,7 +84,7 @@ func TestGetDefaultInstanceClass(t *testing.T) {
"South America (Sao Paulo)": "sa-east-1",
}

for location, classes := range instanceClasses {
for location, classes := range pricingInstanceClasses {
t.Run(location, func(t *testing.T) {
region, ok := regions[location]
if !ok {
Expand All @@ -96,19 +100,81 @@ func TestGetDefaultInstanceClass(t *testing.T) {
}
}

class := ""
// ordered list of prefered instance classes
for _, preferredClass := range []string{"m4", "m5"} {
if _, ok := classes[preferredClass]; ok {
class = preferredClass
ec2Client := ec2.New(ssn, aws.NewConfig().WithRegion(region))
zonesResponse, err := ec2Client.DescribeAvailabilityZones(nil)
if err != nil {
t.Logf("no direct access to region, assuming full support: %v", err)

var match string
for _, instanceClass := range preferredInstanceClasses {
if _, ok := classes[instanceClass]; ok {
match = instanceClass
break
}
}

if match == "" {
t.Fatalf("none of the preferred instance classes are priced: %v", classes)
}

t.Log(classes)
assert.Equal(t, defaults.InstanceClass(region), match)
return
}

zones := make(map[string]struct{}, len(zonesResponse.AvailabilityZones))
for _, zone := range zonesResponse.AvailabilityZones {
zones[*zone.ZoneName] = exists
}

available := make(map[string]map[string]struct{}, len(preferredInstanceClasses))
var match string

for _, instanceClass := range preferredInstanceClasses {
if _, ok := classes[instanceClass]; !ok {
t.Logf("skip the unpriced %s", instanceClass)
continue
}

available[instanceClass] = make(map[string]struct{}, len(zones))
exampleInstanceType := fmt.Sprintf("%s.large", instanceClass)
err := ec2Client.DescribeReservedInstancesOfferingsPages(
&ec2.DescribeReservedInstancesOfferingsInput{
Filters: []*ec2.Filter{
{Name: aws.String("scope"), Values: []*string{aws.String("Availability Zone")}},
},
InstanceTenancy: aws.String("default"),
InstanceType: &exampleInstanceType,
ProductDescription: aws.String("Linux/UNIX"),
},
func(results *ec2.DescribeReservedInstancesOfferingsOutput, lastPage bool) bool {
for _, offering := range results.ReservedInstancesOfferings {
if offering.AvailabilityZone == nil {
continue
}

available[instanceClass][*offering.AvailabilityZone] = exists
}

return !lastPage
},
)
if err != nil {
t.Fatal(err)
}

if reflect.DeepEqual(available[instanceClass], zones) {
match = instanceClass
break
}
}
if class == "" {
t.Fatalf("does not support any preferred classes: %v", classes)

if match == "" {
t.Fatalf("none of the preferred instance classes are fully supported: %v", available)
}
defaultClass := defaults.InstanceClass(region)
assert.Equal(t, defaultClass, class)

t.Log(available)
assert.Equal(t, defaults.InstanceClass(region), match)
})
}
}

0 comments on commit 06e58e0

Please sign in to comment.