Repository: keikoproj/instance-manager (go)

Issue:
SubFamilyFlexible calculations should ensure that instance types have the same supported architectures.
**Is this a BUG REPORT or FEATURE REQUEST?**:
BUG REPORT
**What happened**:
I configured an InstanceGroup to use spot instances with the SubFamilyFlexible option, with the main instance type being set to m6i.2xlarge. Instance Manager also included m6g.2xlarges in the ASG, which have a different supported architecture (arm64) then the main instance type (amd64)
**What you expected to happen**:
Only instances with the same architecture should be used. 
**How to reproduce it (as minimally and precisely as possible)**:

**Anything else we need to know?**:

**Environment**:

- Kubernetes version:

```bash
kubectl version -o yaml
```

**Other debugging information (if applicable)**:

- InstanceGroup status:

```bash
kubectl describe instancegroup <ig-name>
```

- controller logs:

```bash
kubectl logs <instance-manager pod>
```


Pull request:
fix: check architecture when considering similar instance types in the same family
Closes #331 

Signed-off-by: Jonah Back <jonah@jonahback.com>

Patch:
diff --git a/controllers/providers/aws/aws.go b/controllers/providers/aws/aws.go
index b558cb4..53d7056 100644
--- a/controllers/providers/aws/aws.go
+++ b/controllers/providers/aws/aws.go
@@ -18,6 +18,7 @@ package aws
 import (
 	"os"
 	"regexp"
+	"sort"
 	"strings"
 	"time"
 
@@ -218,6 +219,20 @@ func GetInstanceFamily(instanceType string) string {
 	return ""
 }
 
+func GetInstanceArchitectures(typeInfo []*ec2.InstanceTypeInfo, instanceType string) []string {
+
+	instanceTypeInfo := GetInstanceTypeInfo(typeInfo, instanceType)
+	if instanceTypeInfo == nil || instanceTypeInfo.ProcessorInfo == nil {
+		return []string{}
+	}
+	var archs = []string{}
+	for _, s := range instanceTypeInfo.ProcessorInfo.SupportedArchitectures {
+		archs = append(archs, aws.StringValue(s))
+	}
+	sort.Strings(archs)
+	return archs
+}
+
 func GetScalingConfigName(group *autoscaling.Group) string {
 	var configName string
 	if IsUsingLaunchConfiguration(group) {
@@ -238,3 +253,12 @@ func GetInstanceTypeNetworkInfo(instanceTypes []*ec2.InstanceTypeInfo, instanceT
 	}
 	return nil
 }
+
+func GetInstanceTypeInfo(instanceTypes []*ec2.InstanceTypeInfo, instanceType string) *ec2.InstanceTypeInfo {
+	for _, instanceTypeInfo := range instanceTypes {
+		if aws.StringValue(instanceTypeInfo.InstanceType) == instanceType {
+			return instanceTypeInfo
+		}
+	}
+	return nil
+}
diff --git a/controllers/provisioners/eks/cloud.go b/controllers/provisioners/eks/cloud.go
index 1a23fe6..b4357db 100644
--- a/controllers/provisioners/eks/cloud.go
+++ b/controllers/provisioners/eks/cloud.go
@@ -412,6 +412,7 @@ func subFamilyFlexiblePool(offerings []*ec2.InstanceTypeOffering, typeInfo []*ec
 	for _, t := range offerings {
 		var (
 			offeringType      = aws.StringValue(t.InstanceType)
+			desiredArchs      = awsprovider.GetInstanceArchitectures(typeInfo, offeringType)
 			desiredFamily     = awsprovider.GetInstanceFamily(offeringType)
 			desiredGeneration = awsprovider.GetInstanceGeneration(offeringType)
 			cpu               = awsprovider.GetOfferingVCPU(typeInfo, offeringType)
@@ -436,8 +437,18 @@ func subFamilyFlexiblePool(offerings []*ec2.InstanceTypeOffering, typeInfo []*ec
 					Type:   instanceType,
 					Weight: DefaultOfferingWeight,
 				}
+				supportedArchs = awsprovider.GetInstanceArchitectures(typeInfo, instanceType)
 			)
 
+			if len(desiredArchs) != len(supportedArchs) {
+				continue
+			}
+
+
+			if !common.StringSliceContains(desiredArchs, supportedArchs){
+				continue
+			}
+
 			if !strings.EqualFold(family, desiredFamily) {
 				continue
 			}


Test patch:
diff --git a/controllers/provisioners/eks/cloud_test.go b/controllers/provisioners/eks/cloud_test.go
index aceaed8..02bdbaf 100644
--- a/controllers/provisioners/eks/cloud_test.go
+++ b/controllers/provisioners/eks/cloud_test.go
@@ -210,16 +210,20 @@ func TestDeriveSubFamilyFlexiblePool(t *testing.T) {
 		g = gomega.NewGomegaWithT(t)
 	)
 
-	mockOfferings := MockTypeOffering("us-west-2", "z5.large", "z5.xlarge", "z5.2xlarge", "x4.large", "x4a.large", "x4.xlarge", "x3.2xlarge")
+	mockOfferings := MockTypeOffering("us-west-2", "z5.large", "z5.xlarge", "z5.2xlarge", "x4.large", "x4a.large", "x4.xlarge", "x3.2xlarge", "a5i.large", "a5g.large", "a5a.large")
 
 	mockInfo := MockTypeInfo(
-		MockInstanceTypeInfo{"z5.large", 1, 100},
-		MockInstanceTypeInfo{"z5.xlarge", 1, 100},
-		MockInstanceTypeInfo{"z5.2xlarge", 1, 100},
-		MockInstanceTypeInfo{"x4.large", 2, 100},
-		MockInstanceTypeInfo{"x4a.large", 2, 100},
-		MockInstanceTypeInfo{"x4.xlarge", 4, 200},
-		MockInstanceTypeInfo{"x3.2xlarge", 6, 400},
+		MockInstanceTypeInfo{"z5.large", 1, 100, "amd64"},
+		MockInstanceTypeInfo{"z5.xlarge", 1, 100, "amd64"},
+		MockInstanceTypeInfo{"z5.2xlarge", 1, 100, "amd64"},
+		MockInstanceTypeInfo{"x4.large", 2, 100, "amd64"},
+		MockInstanceTypeInfo{"x4a.large", 2, 100, "amd64"},
+		MockInstanceTypeInfo{"x4.xlarge", 4, 200, "amd64"},
+		MockInstanceTypeInfo{"x3.2xlarge", 6, 400, "amd64"},
+		MockInstanceTypeInfo{"a5i.large", 1, 100, "amd64"},
+		MockInstanceTypeInfo{"a5g.large", 1, 100, "arm64"},
+		MockInstanceTypeInfo{"a5a.large", 1, 100, "amd64"},
+
 	)
 
 	expectedPool := make(map[string][]InstanceSpec, 0)
@@ -297,6 +301,32 @@ func TestDeriveSubFamilyFlexiblePool(t *testing.T) {
 			Weight: "1",
 		},
 	}
+	expectedPool["a5g.large"] = []InstanceSpec{
+		{
+			Type:   "a5g.large",
+			Weight: "1",
+		},
+	}
+	expectedPool["a5a.large"] = []InstanceSpec{
+		{
+			Type:   "a5a.large",
+			Weight: "1",
+		},
+		{
+			Type:   "a5i.large",
+			Weight: "1",
+		},
+	}
+	expectedPool["a5i.large"] = []InstanceSpec{
+		{
+			Type:   "a5i.large",
+			Weight: "1",
+		},
+		{
+			Type:   "a5a.large",
+			Weight: "1",
+		},
+	}
 
 	p := subFamilyFlexiblePool(mockOfferings, mockInfo)
 	g.Expect(p).To(gomega.Equal(expectedPool))
diff --git a/controllers/provisioners/eks/eks_test.go b/controllers/provisioners/eks/eks_test.go
index e51c059..47d693a 100644
--- a/controllers/provisioners/eks/eks_test.go
+++ b/controllers/provisioners/eks/eks_test.go
@@ -367,6 +367,7 @@ type MockInstanceTypeInfo struct {
 	InstanceType string
 	VCpus        int64
 	MemoryMib    int64
+	Arch         string
 }
 
 func MockTypeInfo(types ...MockInstanceTypeInfo) []*ec2.InstanceTypeInfo {
@@ -377,6 +378,9 @@ func MockTypeInfo(types ...MockInstanceTypeInfo) []*ec2.InstanceTypeInfo {
 			VCpuInfo: &ec2.VCpuInfo{
 				DefaultVCpus: aws.Int64(t.VCpus),
 			},
+			ProcessorInfo: &ec2.ProcessorInfo{
+				SupportedArchitectures: []*string{aws.String(t.Arch)},
+			},
 			MemoryInfo: &ec2.MemoryInfo{
 				SizeInMiB: aws.Int64(t.MemoryMib),
 			},