Skip to content

Commit

Permalink
implement the --flexible suite filter (#20)
Browse files Browse the repository at this point in the history
* implement the --flexible suite filter

* fix readme usage
  • Loading branch information
bwagner5 authored Jun 23, 2020
1 parent 3e75148 commit a7e05ab
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 45 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ Filter Flags:
Suite Flags:
--base-instance-type string Instance Type used to retrieve similarly spec'd instance types
--flexible Retrieves a group of instance types spanning multiple generations based on opinionated defaults and user overridden resource filters
Global Flags:
Expand Down
5 changes: 4 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const (
// Aggregate Filter Flags
const (
instanceTypeBase = "base-instance-type"
flexible = "flexible"
)

// Configuration Flag Constants
Expand Down Expand Up @@ -140,6 +141,7 @@ Full docs can be found at github.com/aws/amazon-` + binName
// Suite Flags - higher level aggregate filters that return opinionated result

cli.SuiteStringFlag(instanceTypeBase, nil, nil, "Instance Type used to retrieve similarly spec'd instance types", nil)
cli.SuiteBoolFlag(flexible, nil, nil, "Retrieves a group of instance types spanning multiple generations based on opinionated defaults and user overridden resource filters")

// Configuration Flags - These will be grouped at the bottom of the help flags

Expand Down Expand Up @@ -210,11 +212,12 @@ Full docs can be found at github.com/aws/amazon-` + binName
AllowList: cli.RegexMe(flags[allowList]),
DenyList: cli.RegexMe(flags[denyList]),
InstanceTypeBase: cli.StringMe(flags[instanceTypeBase]),
Flexible: cli.BoolMe(flags[flexible]),
}

if flags[verbose] != nil {
resultsOutputFn = outputs.VerboseInstanceTypeOutput
transformedFilters, err := instanceSelector.AggregateFilterTransform(filters, selector.AggregateLowPercentile, selector.AggregateHighPercentile)
transformedFilters, err := instanceSelector.AggregateFilterTransform(filters)
if err != nil {
fmt.Printf("An error occurred while transforming the aggregate filters")
os.Exit(1)
Expand Down
107 changes: 107 additions & 0 deletions pkg/selector/aggregates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package selector

import (
"fmt"
"regexp"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
)

const (
// AggregateLowPercentile is the default lower percentile for resource ranges on similar instance type comparisons
AggregateLowPercentile = 0.8
// AggregateHighPercentile is the default upper percentile for resource ranges on similar instance type comparisons
AggregateHighPercentile = 1.2
)

// FiltersTransform can be implemented to provide custom transforms
type FiltersTransform interface {
Transform(Filters) (Filters, error)
}

// TransformFn is the func type definition for a FiltersTransform
type TransformFn func(Filters) (Filters, error)

// Transform implements FiltersTransform interface on TransformFn
// This allows any TransformFn to be passed into funcs accepting FiltersTransform interface
func (fn TransformFn) Transform(filters Filters) (Filters, error) {
return fn(filters)
}

// TransformBaseInstanceType transforms lower level filters based on the instanceTypeBase specs
func (itf Selector) TransformBaseInstanceType(filters Filters) (Filters, error) {
if filters.InstanceTypeBase == nil {
return filters, nil
}
instanceTypesOutput, err := itf.EC2.DescribeInstanceTypes(&ec2.DescribeInstanceTypesInput{
InstanceTypes: []*string{filters.InstanceTypeBase},
})
if err != nil {
return filters, err
}
if len(instanceTypesOutput.InstanceTypes) == 0 {
return filters, fmt.Errorf("error instance type %s is not a valid instance type", *filters.InstanceTypeBase)
}
instanceTypeInfo := instanceTypesOutput.InstanceTypes[0]
if filters.BareMetal == nil {
filters.BareMetal = instanceTypeInfo.BareMetal
}
if filters.CPUArchitecture == nil {
filters.CPUArchitecture = instanceTypeInfo.ProcessorInfo.SupportedArchitectures[0]
}
if filters.Fpga == nil {
isFpgaSupported := instanceTypeInfo.FpgaInfo != nil
filters.Fpga = &isFpgaSupported
}
if filters.GpusRange == nil {
if instanceTypeInfo.GpuInfo != nil {
gpuCount := int(*getTotalGpusCount(instanceTypeInfo.GpuInfo))
filters.GpusRange = &IntRangeFilter{LowerBound: gpuCount, UpperBound: gpuCount}
}
}
if filters.MemoryRange == nil {
lowerBound := int(float64(*instanceTypeInfo.MemoryInfo.SizeInMiB) * AggregateLowPercentile)
upperBound := int(float64(*instanceTypeInfo.MemoryInfo.SizeInMiB) * AggregateHighPercentile)
filters.MemoryRange = &IntRangeFilter{LowerBound: lowerBound, UpperBound: upperBound}
}
if filters.VCpusRange == nil {
lowerBound := int(float64(*instanceTypeInfo.VCpuInfo.DefaultVCpus) * AggregateLowPercentile)
upperBound := int(float64(*instanceTypeInfo.VCpuInfo.DefaultVCpus) * AggregateHighPercentile)
filters.VCpusRange = &IntRangeFilter{LowerBound: lowerBound, UpperBound: upperBound}
}
filters.InstanceTypeBase = nil

return filters, nil
}

// TransformFlexible transforms lower level filters based on a set of opinions
func (itf Selector) TransformFlexible(filters Filters) (Filters, error) {
if filters.Flexible == nil {
return filters, nil
}
if filters.CPUArchitecture == nil {
filters.CPUArchitecture = aws.String("x86_64")
}
if filters.BareMetal == nil {
filters.BareMetal = aws.Bool(false)
}
if filters.Fpga == nil {
filters.Fpga = aws.Bool(false)
}

if filters.AllowList == nil {
baseAllowedInstanceTypes, err := regexp.Compile("^[cmr][3-9][ag]?\\..*$|^a[1-9]\\..*$|^t[2-9]\\..*$")
if err != nil {
return filters, err
}
filters.AllowList = baseAllowedInstanceTypes
}

if filters.VCpusRange == nil && filters.MemoryRange == nil {
defaultVcpus := 4
filters.VCpusRange = &IntRangeFilter{LowerBound: defaultVcpus, UpperBound: defaultVcpus}
}

return filters, nil
}
56 changes: 56 additions & 0 deletions pkg/selector/aggregates_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file 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 selector_test

import (
"testing"

"github.com/aws/amazon-ec2-instance-selector/pkg/selector"
h "github.com/aws/amazon-ec2-instance-selector/pkg/test"
)

// Tests

func TestTransformBaseInstanceType(t *testing.T) {
ec2Mock := mockedEC2{
DescribeInstanceTypesResp: setupMock(t, describeInstanceTypes, "c4_large.json").DescribeInstanceTypesResp,
DescribeInstanceTypesPagesResp: setupMock(t, describeInstanceTypesPages, "25_instances.json").DescribeInstanceTypesPagesResp,
DescribeInstanceTypeOfferingsResp: setupMock(t, describeInstanceTypeOfferings, "us-east-2a.json").DescribeInstanceTypeOfferingsResp,
}
itf := selector.Selector{
EC2: ec2Mock,
}
instanceTypeBase := "c4.large"
filters := selector.Filters{
InstanceTypeBase: &instanceTypeBase,
}
filters, err := itf.TransformBaseInstanceType(filters)
h.Ok(t, err)
h.Assert(t, *filters.BareMetal == false, " should filter out bare metal instances")
h.Assert(t, *filters.Fpga == false, "should filter out FPGA instances")
h.Assert(t, *filters.CPUArchitecture == "x86_64", "should only return x86_64 instance types")
}

func TestTransformFamilyFlexibile(t *testing.T) {
itf := selector.Selector{}
flexible := true
filters := selector.Filters{
Flexible: &flexible,
}
filters, err := itf.TransformFlexible(filters)
h.Ok(t, err)
h.Assert(t, *filters.BareMetal == false, " should filter out bare metal instances")
h.Assert(t, *filters.Fpga == false, "should filter out FPGA instances")
h.Assert(t, *filters.CPUArchitecture == "x86_64", "should only return x86_64 instance types")
}
51 changes: 9 additions & 42 deletions pkg/selector/selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,6 @@ const (
networkPerformance = "networkPerformance"
allowList = "allowList"
denyList = "denyList"

// AggregateLowPercentile is the default lower percentile for resource ranges on similar instance type comparisons
AggregateLowPercentile = 0.8
// AggregateHighPercentile is the default upper percentile for resource ranges on similar instance type comparisons
AggregateHighPercentile = 1.2
)

// New creates an instance of Selector provided an aws session
Expand Down Expand Up @@ -121,53 +116,25 @@ func (itf Selector) truncateResults(maxResults *int, instanceTypeInfoSlice []*ec
}

// AggregateFilterTransform takes higher level filters which are used to affect multiple raw filters in an opinionated way.
func (itf Selector) AggregateFilterTransform(filters Filters, lowerPercentile float64, upperPercentile float64) (Filters, error) {
if filters.InstanceTypeBase != nil {
instanceTypesOutput, err := itf.EC2.DescribeInstanceTypes(&ec2.DescribeInstanceTypesInput{
InstanceTypes: []*string{filters.InstanceTypeBase},
})
func (itf Selector) AggregateFilterTransform(filters Filters) (Filters, error) {
transforms := []FiltersTransform{
TransformFn(itf.TransformBaseInstanceType),
TransformFn(itf.TransformFlexible),
}
var err error
for _, transform := range transforms {
filters, err = transform.Transform(filters)
if err != nil {
return filters, err
}
if len(instanceTypesOutput.InstanceTypes) == 0 {
return filters, fmt.Errorf("error instance type %s is not a valid instance type", *filters.InstanceTypeBase)
}
instanceTypeInfo := instanceTypesOutput.InstanceTypes[0]
if filters.BareMetal == nil {
filters.BareMetal = instanceTypeInfo.BareMetal
}
if filters.CPUArchitecture == nil {
filters.CPUArchitecture = instanceTypeInfo.ProcessorInfo.SupportedArchitectures[0]
}
if filters.Fpga == nil {
isFpgaSupported := instanceTypeInfo.FpgaInfo != nil
filters.Fpga = &isFpgaSupported
}
if filters.GpusRange == nil {
if instanceTypeInfo.GpuInfo != nil {
gpuCount := int(*getTotalGpusCount(instanceTypeInfo.GpuInfo))
filters.GpusRange = &IntRangeFilter{LowerBound: gpuCount, UpperBound: gpuCount}
}
}
if filters.MemoryRange == nil {
lowerBound := int(float64(*instanceTypeInfo.MemoryInfo.SizeInMiB) * lowerPercentile)
upperBound := int(float64(*instanceTypeInfo.MemoryInfo.SizeInMiB) * upperPercentile)
filters.MemoryRange = &IntRangeFilter{LowerBound: lowerBound, UpperBound: upperBound}
}
if filters.VCpusRange == nil {
lowerBound := int(float64(*instanceTypeInfo.VCpuInfo.DefaultVCpus) * lowerPercentile)
upperBound := int(float64(*instanceTypeInfo.VCpuInfo.DefaultVCpus) * upperPercentile)
filters.VCpusRange = &IntRangeFilter{LowerBound: lowerBound, UpperBound: upperBound}
}
filters.InstanceTypeBase = nil
}
return filters, nil
}

// rawFilter accepts a Filters struct which is used to select the available instance types
// matching the criteria within Filters and returns the detailed specs of matching instance types
func (itf Selector) rawFilter(filters Filters) ([]*ec2.InstanceTypeInfo, error) {
filters, err := itf.AggregateFilterTransform(filters, AggregateLowPercentile, AggregateHighPercentile)
filters, err := itf.AggregateFilterTransform(filters)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/selector/selector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ func TestAggregateFilterTransform(t *testing.T) {
filters := selector.Filters{
InstanceTypeBase: &g22Xlarge,
}
filters, err := itf.AggregateFilterTransform(filters, 0.8, 1.2)
filters, err := itf.AggregateFilterTransform(filters)
h.Ok(t, err)
h.Assert(t, filters.GpusRange != nil, "g2.2Xlarge as a base instance type should filter out non-GPU instances")
h.Assert(t, *filters.BareMetal == false, "g2.2Xlarge as a base instance type should filter out bare metal instances")
Expand All @@ -417,7 +417,7 @@ func TestAggregateFilterTransform_InvalidInstanceType(t *testing.T) {
filters := selector.Filters{
InstanceTypeBase: &t3Micro,
}
_, err := itf.AggregateFilterTransform(filters, 0.8, 1.2)
_, err := itf.AggregateFilterTransform(filters)
h.Nok(t, err)
}

Expand Down
4 changes: 4 additions & 0 deletions pkg/selector/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,8 @@ type Filters struct {

// InstanceTypeBase is a base instance type which is used to retrieve similarly spec'd instance types
InstanceTypeBase *string

// Flexible finds an opinionated set of general (c, m, r, t, a, etc.) instance types that match a criteria specified
// or defaults to 4 vcpus
Flexible *bool
}

0 comments on commit a7e05ab

Please sign in to comment.