Skip to content

Commit a7e05ab

Browse files
authored
implement the --flexible suite filter (aws#20)
* implement the --flexible suite filter * fix readme usage
1 parent 3e75148 commit a7e05ab

File tree

7 files changed

+183
-45
lines changed

7 files changed

+183
-45
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ Filter Flags:
172172
173173
Suite Flags:
174174
--base-instance-type string Instance Type used to retrieve similarly spec'd instance types
175+
--flexible Retrieves a group of instance types spanning multiple generations based on opinionated defaults and user overridden resource filters
175176
176177
177178
Global Flags:

cmd/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const (
7373
// Aggregate Filter Flags
7474
const (
7575
instanceTypeBase = "base-instance-type"
76+
flexible = "flexible"
7677
)
7778

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

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

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

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

215218
if flags[verbose] != nil {
216219
resultsOutputFn = outputs.VerboseInstanceTypeOutput
217-
transformedFilters, err := instanceSelector.AggregateFilterTransform(filters, selector.AggregateLowPercentile, selector.AggregateHighPercentile)
220+
transformedFilters, err := instanceSelector.AggregateFilterTransform(filters)
218221
if err != nil {
219222
fmt.Printf("An error occurred while transforming the aggregate filters")
220223
os.Exit(1)

pkg/selector/aggregates.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package selector
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
7+
"github.com/aws/aws-sdk-go/aws"
8+
"github.com/aws/aws-sdk-go/service/ec2"
9+
)
10+
11+
const (
12+
// AggregateLowPercentile is the default lower percentile for resource ranges on similar instance type comparisons
13+
AggregateLowPercentile = 0.8
14+
// AggregateHighPercentile is the default upper percentile for resource ranges on similar instance type comparisons
15+
AggregateHighPercentile = 1.2
16+
)
17+
18+
// FiltersTransform can be implemented to provide custom transforms
19+
type FiltersTransform interface {
20+
Transform(Filters) (Filters, error)
21+
}
22+
23+
// TransformFn is the func type definition for a FiltersTransform
24+
type TransformFn func(Filters) (Filters, error)
25+
26+
// Transform implements FiltersTransform interface on TransformFn
27+
// This allows any TransformFn to be passed into funcs accepting FiltersTransform interface
28+
func (fn TransformFn) Transform(filters Filters) (Filters, error) {
29+
return fn(filters)
30+
}
31+
32+
// TransformBaseInstanceType transforms lower level filters based on the instanceTypeBase specs
33+
func (itf Selector) TransformBaseInstanceType(filters Filters) (Filters, error) {
34+
if filters.InstanceTypeBase == nil {
35+
return filters, nil
36+
}
37+
instanceTypesOutput, err := itf.EC2.DescribeInstanceTypes(&ec2.DescribeInstanceTypesInput{
38+
InstanceTypes: []*string{filters.InstanceTypeBase},
39+
})
40+
if err != nil {
41+
return filters, err
42+
}
43+
if len(instanceTypesOutput.InstanceTypes) == 0 {
44+
return filters, fmt.Errorf("error instance type %s is not a valid instance type", *filters.InstanceTypeBase)
45+
}
46+
instanceTypeInfo := instanceTypesOutput.InstanceTypes[0]
47+
if filters.BareMetal == nil {
48+
filters.BareMetal = instanceTypeInfo.BareMetal
49+
}
50+
if filters.CPUArchitecture == nil {
51+
filters.CPUArchitecture = instanceTypeInfo.ProcessorInfo.SupportedArchitectures[0]
52+
}
53+
if filters.Fpga == nil {
54+
isFpgaSupported := instanceTypeInfo.FpgaInfo != nil
55+
filters.Fpga = &isFpgaSupported
56+
}
57+
if filters.GpusRange == nil {
58+
if instanceTypeInfo.GpuInfo != nil {
59+
gpuCount := int(*getTotalGpusCount(instanceTypeInfo.GpuInfo))
60+
filters.GpusRange = &IntRangeFilter{LowerBound: gpuCount, UpperBound: gpuCount}
61+
}
62+
}
63+
if filters.MemoryRange == nil {
64+
lowerBound := int(float64(*instanceTypeInfo.MemoryInfo.SizeInMiB) * AggregateLowPercentile)
65+
upperBound := int(float64(*instanceTypeInfo.MemoryInfo.SizeInMiB) * AggregateHighPercentile)
66+
filters.MemoryRange = &IntRangeFilter{LowerBound: lowerBound, UpperBound: upperBound}
67+
}
68+
if filters.VCpusRange == nil {
69+
lowerBound := int(float64(*instanceTypeInfo.VCpuInfo.DefaultVCpus) * AggregateLowPercentile)
70+
upperBound := int(float64(*instanceTypeInfo.VCpuInfo.DefaultVCpus) * AggregateHighPercentile)
71+
filters.VCpusRange = &IntRangeFilter{LowerBound: lowerBound, UpperBound: upperBound}
72+
}
73+
filters.InstanceTypeBase = nil
74+
75+
return filters, nil
76+
}
77+
78+
// TransformFlexible transforms lower level filters based on a set of opinions
79+
func (itf Selector) TransformFlexible(filters Filters) (Filters, error) {
80+
if filters.Flexible == nil {
81+
return filters, nil
82+
}
83+
if filters.CPUArchitecture == nil {
84+
filters.CPUArchitecture = aws.String("x86_64")
85+
}
86+
if filters.BareMetal == nil {
87+
filters.BareMetal = aws.Bool(false)
88+
}
89+
if filters.Fpga == nil {
90+
filters.Fpga = aws.Bool(false)
91+
}
92+
93+
if filters.AllowList == nil {
94+
baseAllowedInstanceTypes, err := regexp.Compile("^[cmr][3-9][ag]?\\..*$|^a[1-9]\\..*$|^t[2-9]\\..*$")
95+
if err != nil {
96+
return filters, err
97+
}
98+
filters.AllowList = baseAllowedInstanceTypes
99+
}
100+
101+
if filters.VCpusRange == nil && filters.MemoryRange == nil {
102+
defaultVcpus := 4
103+
filters.VCpusRange = &IntRangeFilter{LowerBound: defaultVcpus, UpperBound: defaultVcpus}
104+
}
105+
106+
return filters, nil
107+
}

pkg/selector/aggregates_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package selector_test
15+
16+
import (
17+
"testing"
18+
19+
"github.com/aws/amazon-ec2-instance-selector/pkg/selector"
20+
h "github.com/aws/amazon-ec2-instance-selector/pkg/test"
21+
)
22+
23+
// Tests
24+
25+
func TestTransformBaseInstanceType(t *testing.T) {
26+
ec2Mock := mockedEC2{
27+
DescribeInstanceTypesResp: setupMock(t, describeInstanceTypes, "c4_large.json").DescribeInstanceTypesResp,
28+
DescribeInstanceTypesPagesResp: setupMock(t, describeInstanceTypesPages, "25_instances.json").DescribeInstanceTypesPagesResp,
29+
DescribeInstanceTypeOfferingsResp: setupMock(t, describeInstanceTypeOfferings, "us-east-2a.json").DescribeInstanceTypeOfferingsResp,
30+
}
31+
itf := selector.Selector{
32+
EC2: ec2Mock,
33+
}
34+
instanceTypeBase := "c4.large"
35+
filters := selector.Filters{
36+
InstanceTypeBase: &instanceTypeBase,
37+
}
38+
filters, err := itf.TransformBaseInstanceType(filters)
39+
h.Ok(t, err)
40+
h.Assert(t, *filters.BareMetal == false, " should filter out bare metal instances")
41+
h.Assert(t, *filters.Fpga == false, "should filter out FPGA instances")
42+
h.Assert(t, *filters.CPUArchitecture == "x86_64", "should only return x86_64 instance types")
43+
}
44+
45+
func TestTransformFamilyFlexibile(t *testing.T) {
46+
itf := selector.Selector{}
47+
flexible := true
48+
filters := selector.Filters{
49+
Flexible: &flexible,
50+
}
51+
filters, err := itf.TransformFlexible(filters)
52+
h.Ok(t, err)
53+
h.Assert(t, *filters.BareMetal == false, " should filter out bare metal instances")
54+
h.Assert(t, *filters.Fpga == false, "should filter out FPGA instances")
55+
h.Assert(t, *filters.CPUArchitecture == "x86_64", "should only return x86_64 instance types")
56+
}

pkg/selector/selector.go

Lines changed: 9 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,6 @@ const (
6262
networkPerformance = "networkPerformance"
6363
allowList = "allowList"
6464
denyList = "denyList"
65-
66-
// AggregateLowPercentile is the default lower percentile for resource ranges on similar instance type comparisons
67-
AggregateLowPercentile = 0.8
68-
// AggregateHighPercentile is the default upper percentile for resource ranges on similar instance type comparisons
69-
AggregateHighPercentile = 1.2
7065
)
7166

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

123118
// AggregateFilterTransform takes higher level filters which are used to affect multiple raw filters in an opinionated way.
124-
func (itf Selector) AggregateFilterTransform(filters Filters, lowerPercentile float64, upperPercentile float64) (Filters, error) {
125-
if filters.InstanceTypeBase != nil {
126-
instanceTypesOutput, err := itf.EC2.DescribeInstanceTypes(&ec2.DescribeInstanceTypesInput{
127-
InstanceTypes: []*string{filters.InstanceTypeBase},
128-
})
119+
func (itf Selector) AggregateFilterTransform(filters Filters) (Filters, error) {
120+
transforms := []FiltersTransform{
121+
TransformFn(itf.TransformBaseInstanceType),
122+
TransformFn(itf.TransformFlexible),
123+
}
124+
var err error
125+
for _, transform := range transforms {
126+
filters, err = transform.Transform(filters)
129127
if err != nil {
130128
return filters, err
131129
}
132-
if len(instanceTypesOutput.InstanceTypes) == 0 {
133-
return filters, fmt.Errorf("error instance type %s is not a valid instance type", *filters.InstanceTypeBase)
134-
}
135-
instanceTypeInfo := instanceTypesOutput.InstanceTypes[0]
136-
if filters.BareMetal == nil {
137-
filters.BareMetal = instanceTypeInfo.BareMetal
138-
}
139-
if filters.CPUArchitecture == nil {
140-
filters.CPUArchitecture = instanceTypeInfo.ProcessorInfo.SupportedArchitectures[0]
141-
}
142-
if filters.Fpga == nil {
143-
isFpgaSupported := instanceTypeInfo.FpgaInfo != nil
144-
filters.Fpga = &isFpgaSupported
145-
}
146-
if filters.GpusRange == nil {
147-
if instanceTypeInfo.GpuInfo != nil {
148-
gpuCount := int(*getTotalGpusCount(instanceTypeInfo.GpuInfo))
149-
filters.GpusRange = &IntRangeFilter{LowerBound: gpuCount, UpperBound: gpuCount}
150-
}
151-
}
152-
if filters.MemoryRange == nil {
153-
lowerBound := int(float64(*instanceTypeInfo.MemoryInfo.SizeInMiB) * lowerPercentile)
154-
upperBound := int(float64(*instanceTypeInfo.MemoryInfo.SizeInMiB) * upperPercentile)
155-
filters.MemoryRange = &IntRangeFilter{LowerBound: lowerBound, UpperBound: upperBound}
156-
}
157-
if filters.VCpusRange == nil {
158-
lowerBound := int(float64(*instanceTypeInfo.VCpuInfo.DefaultVCpus) * lowerPercentile)
159-
upperBound := int(float64(*instanceTypeInfo.VCpuInfo.DefaultVCpus) * upperPercentile)
160-
filters.VCpusRange = &IntRangeFilter{LowerBound: lowerBound, UpperBound: upperBound}
161-
}
162-
filters.InstanceTypeBase = nil
163130
}
164131
return filters, nil
165132
}
166133

167134
// rawFilter accepts a Filters struct which is used to select the available instance types
168135
// matching the criteria within Filters and returns the detailed specs of matching instance types
169136
func (itf Selector) rawFilter(filters Filters) ([]*ec2.InstanceTypeInfo, error) {
170-
filters, err := itf.AggregateFilterTransform(filters, AggregateLowPercentile, AggregateHighPercentile)
137+
filters, err := itf.AggregateFilterTransform(filters)
171138
if err != nil {
172139
return nil, err
173140
}

pkg/selector/selector_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ func TestAggregateFilterTransform(t *testing.T) {
400400
filters := selector.Filters{
401401
InstanceTypeBase: &g22Xlarge,
402402
}
403-
filters, err := itf.AggregateFilterTransform(filters, 0.8, 1.2)
403+
filters, err := itf.AggregateFilterTransform(filters)
404404
h.Ok(t, err)
405405
h.Assert(t, filters.GpusRange != nil, "g2.2Xlarge as a base instance type should filter out non-GPU instances")
406406
h.Assert(t, *filters.BareMetal == false, "g2.2Xlarge as a base instance type should filter out bare metal instances")
@@ -417,7 +417,7 @@ func TestAggregateFilterTransform_InvalidInstanceType(t *testing.T) {
417417
filters := selector.Filters{
418418
InstanceTypeBase: &t3Micro,
419419
}
420-
_, err := itf.AggregateFilterTransform(filters, 0.8, 1.2)
420+
_, err := itf.AggregateFilterTransform(filters)
421421
h.Nok(t, err)
422422
}
423423

pkg/selector/types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,8 @@ type Filters struct {
166166

167167
// InstanceTypeBase is a base instance type which is used to retrieve similarly spec'd instance types
168168
InstanceTypeBase *string
169+
170+
// Flexible finds an opinionated set of general (c, m, r, t, a, etc.) instance types that match a criteria specified
171+
// or defaults to 4 vcpus
172+
Flexible *bool
169173
}

0 commit comments

Comments
 (0)