Merge pull request #8856 from rifelpet/machine-types-2

Use ec2.DescribeInstanceTypes for machine type info
This commit is contained in:
Kubernetes Prow Robot 2020-06-09 15:56:05 -07:00 committed by GitHub
commit d5b8e6c90f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 236 additions and 2958 deletions

View File

@ -829,13 +829,6 @@ build-docs-netlify:
pip install -r ${KOPS_ROOT}/images/mkdocs/requirements.txt
mkdocs build
# Update machine_types.go
.PHONY: update-machine-types
update-machine-types:
go build -o hack/machine_types/machine_types ${KOPS_ROOT}/hack/machine_types/
hack/machine_types/machine_types --out upup/pkg/fi/cloudup/awsup/machine_types.go
go fmt upup/pkg/fi/cloudup/awsup/machine_types.go
#-----------------------------------------------------------
# development targets

View File

@ -49,3 +49,8 @@ func (m *MockEC2) DescribeInstancesPages(request *ec2.DescribeInstancesInput, ca
func (m *MockEC2) DescribeInstancesPagesWithContext(aws.Context, *ec2.DescribeInstancesInput, func(*ec2.DescribeInstancesOutput, bool) bool, ...request.Option) error {
panic("Not implemented")
}
func (m *MockEC2) DescribeInstanceTypes(*ec2.DescribeInstanceTypesInput) (*ec2.DescribeInstanceTypesOutput, error) {
klog.Warningf("MockEc2::DescribeInstanceTypes is stub-implemented")
return &ec2.DescribeInstanceTypesOutput{}, nil
}

View File

@ -1231,7 +1231,7 @@ func RunCreateCluster(ctx context.Context, f *util.Factory, out io.Writer, c *Cr
}
strict := false
err = validation.DeepValidate(cluster, instanceGroups, strict)
err = validation.DeepValidate(cluster, instanceGroups, strict, nil)
if err != nil {
return err
}
@ -1255,7 +1255,7 @@ func RunCreateCluster(ctx context.Context, f *util.Factory, out io.Writer, c *Cr
fullInstanceGroups = append(fullInstanceGroups, fullGroup)
}
err = validation.DeepValidate(fullCluster, fullInstanceGroups, true)
err = validation.DeepValidate(fullCluster, fullInstanceGroups, true, nil)
if err != nil {
return err
}

View File

@ -228,7 +228,12 @@ func RunCreateInstanceGroup(ctx context.Context, f *util.Factory, cmd *cobra.Com
return fmt.Errorf("unexpected object type: %T", obj)
}
err = validation.CrossValidateInstanceGroup(group, cluster).ToAggregate()
cloud, err := cloudup.BuildCloud(cluster)
if err != nil {
return err
}
err = validation.CrossValidateInstanceGroup(group, cluster, cloud).ToAggregate()
if err != nil {
return err
}

View File

@ -222,7 +222,12 @@ func RunEditCluster(ctx context.Context, f *util.Factory, cmd *cobra.Command, ar
continue
}
err = validation.DeepValidate(fullCluster, instanceGroups, true)
cloud, err := cloudup.BuildCloud(fullCluster)
if err != nil {
return err
}
err = validation.DeepValidate(fullCluster, instanceGroups, true, cloud)
if err != nil {
results = editResults{
file: file,

View File

@ -173,7 +173,12 @@ func RunEditInstanceGroup(ctx context.Context, f *util.Factory, cmd *cobra.Comma
return err
}
err = validation.CrossValidateInstanceGroup(fullGroup, fullCluster).ToAggregate()
cloud, err := cloudup.BuildCloud(fullCluster)
if err != nil {
return err
}
err = validation.CrossValidateInstanceGroup(fullGroup, fullCluster, cloud).ToAggregate()
if err != nil {
return err
}

View File

@ -4,6 +4,8 @@
# Significant changes
* Clusters using the Amazon VPC CNI provider now perform an `ec2.DescribeInstanceTypes` call at instance launch time. In large clusters or AWS accounts this may lead to API throttling which could delay node readiness. If this becomes a problem please open a GitHub issue.
# Breaking changes
* Support for Kubernetes 1.9 and 1.10 has been removed.

View File

@ -33,7 +33,6 @@ k8s.io/kops/dnsprovider/pkg/dnsprovider/providers/openstack/designate
k8s.io/kops/dnsprovider/pkg/dnsprovider/rrstype
k8s.io/kops/dnsprovider/pkg/dnsprovider/tests
k8s.io/kops/examples/kops-api-example
k8s.io/kops/hack/machine_types
k8s.io/kops/kube-discovery/cmd/kube-discovery
k8s.io/kops/node-authorizer/cmd/node-authorizer
k8s.io/kops/node-authorizer/pkg/authorizers/alwaysallow

View File

@ -1 +0,0 @@
machine_types

View File

@ -1,21 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
go_library(
name = "go_default_library",
srcs = ["machine_types.go"],
importpath = "k8s.io/kops/hack/machine_types",
visibility = ["//visibility:private"],
deps = [
"//upup/pkg/fi/cloudup/awsup:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/aws:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/aws/session:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/service/ec2:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
go_binary(
name = "machinetypes",
embed = [":go_default_library"],
visibility = ["//visibility:public"],
)

View File

@ -1,19 +0,0 @@
Machine Types Generator
=======================
To prevent errors or lagging updates, we use this generator to update the known aws machine types
that are [hard coded in kops](https://github.com/kubernetes/kops/blob/7d7112c1e9a52d4f677db6bd98943d308ec9f581/upup/pkg/fi/cloudup/awsup/machine_types.go#L76).
This generator uses the AWS Pricing API to get most of it's info on what instance types are supported.
Usage
-----
```
make update-machine-types
git add .
git commit -am "Updated machine types"
```
TODO:
-----
* Cross reference other regions besides us-east-1. Currently we just look at one region to determine instance types.

View File

@ -1,328 +0,0 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License 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 main
import (
"bufio"
"bytes"
"flag"
"fmt"
"io/ioutil"
"math"
"os"
"sort"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"k8s.io/klog"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
)
var outputPath = ""
func main() {
flag.StringVar(&outputPath, "out", outputPath, "file to write")
flag.Parse()
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}
func run() error {
if outputPath == "" {
return fmt.Errorf("must specify output file with --out")
}
klog.Info("Beginning AWS Machine Refresh")
// These are instance types not available in every account
// If they're not available, they wont be in the ec2.DescribeInstanceTypes response
// so they are hardcoded here for reference.
// Note that the m6g instances do not have ENI or IP information
machines := []awsup.AWSMachineTypeInfo{
{
Name: "cr1.8xlarge",
MemoryGB: 244,
Cores: 32,
InstanceENIs: 8,
InstanceIPsPerENI: 30,
EphemeralDisks: []int{120, 120},
},
{
Name: "hs1.8xlarge",
MemoryGB: 117,
Cores: 16,
InstanceENIs: 8,
InstanceIPsPerENI: 30,
EphemeralDisks: []int{2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000},
},
{
Name: "m6g.medium",
MemoryGB: 4,
Cores: 1,
InstanceENIs: 0,
InstanceIPsPerENI: 0,
EphemeralDisks: nil,
},
{
Name: "m6g.large",
MemoryGB: 8,
Cores: 2,
InstanceENIs: 0,
InstanceIPsPerENI: 0,
EphemeralDisks: nil,
},
{
Name: "m6g.xlarge",
MemoryGB: 16,
Cores: 4,
InstanceENIs: 0,
InstanceIPsPerENI: 0,
EphemeralDisks: nil,
},
{
Name: "m6g.2xlarge",
MemoryGB: 32,
Cores: 8,
InstanceENIs: 0,
InstanceIPsPerENI: 0,
EphemeralDisks: nil,
},
{
Name: "m6g.4xlarge",
MemoryGB: 64,
Cores: 16,
InstanceENIs: 0,
InstanceIPsPerENI: 0,
EphemeralDisks: nil,
},
{
Name: "m6g.8xlarge",
MemoryGB: 128,
Cores: 32,
InstanceENIs: 0,
InstanceIPsPerENI: 0,
EphemeralDisks: nil,
},
{
Name: "m6g.12xlarge",
MemoryGB: 192,
Cores: 48,
InstanceENIs: 0,
InstanceIPsPerENI: 0,
EphemeralDisks: nil,
},
{
Name: "m6g.16xlarge",
MemoryGB: 256,
Cores: 64,
InstanceENIs: 0,
InstanceIPsPerENI: 0,
EphemeralDisks: nil,
},
}
families := map[string]struct{}{
"cr1": {},
"hs1": {},
"m6g": {},
}
config := aws.NewConfig()
// Give verbose errors on auth problems
config = config.WithCredentialsChainVerboseErrors(true)
// Default to us-east-1
config = config.WithRegion("us-east-1")
sess, err := session.NewSession()
if err != nil {
return err
}
client := ec2.New(sess, config)
instanceTypes := make([]*ec2.InstanceTypeInfo, 0)
err = client.DescribeInstanceTypesPages(&ec2.DescribeInstanceTypesInput{},
func(page *ec2.DescribeInstanceTypesOutput, lastPage bool) bool {
instanceTypes = append(instanceTypes, page.InstanceTypes...)
return true
})
if err != nil {
return err
}
var warnings []string
seen := map[string]bool{}
for _, typeInfo := range instanceTypes {
instanceType := *typeInfo.InstanceType
if _, ok := seen[instanceType]; ok {
continue
}
seen[instanceType] = true
machine := awsup.AWSMachineTypeInfo{
Name: instanceType,
GPU: typeInfo.GpuInfo != nil,
InstanceENIs: intValue(typeInfo.NetworkInfo.MaximumNetworkInterfaces),
InstanceIPsPerENI: intValue(typeInfo.NetworkInfo.Ipv4AddressesPerInterface),
}
memoryGB := float64(intValue(typeInfo.MemoryInfo.SizeInMiB)) / 1024
machine.MemoryGB = float32(math.Round(memoryGB*100) / 100)
if typeInfo.VCpuInfo != nil && typeInfo.VCpuInfo.DefaultVCpus != nil {
machine.Cores = intValue(typeInfo.VCpuInfo.DefaultVCpus)
}
if typeInfo.InstanceStorageInfo != nil && len(typeInfo.InstanceStorageInfo.Disks) > 0 {
disks := make([]int, 0)
for _, disk := range typeInfo.InstanceStorageInfo.Disks {
for i := 0; i < intValue(disk.Count); i++ {
disks = append(disks, intValue(disk.SizeInGB))
}
}
machine.EphemeralDisks = disks
}
machines = append(machines, machine)
family := strings.Split(instanceType, ".")[0]
families[family] = struct{}{}
}
sortedFamilies := []string{}
for f := range families {
sortedFamilies = append(sortedFamilies, f)
}
sort.Strings(sortedFamilies)
sort.Slice(machines, func(i, j int) bool {
// Sort first by family
tokensI := strings.Split(machines[i].Name, ".")
tokensJ := strings.Split(machines[j].Name, ".")
if tokensI[0] != tokensJ[0] {
return tokensI[0] < tokensJ[0]
}
// Then sort by size within the family
if machines[i].MemoryGB != machines[j].MemoryGB {
return machines[i].MemoryGB < machines[j].MemoryGB
}
// Fallback: sort by name
return machines[i].Name < machines[j].Name
})
var output string
if len(warnings) != 0 {
output = output + "\n"
for _, warning := range warnings {
output = output + "// WARNING: " + warning + "\n"
}
output = output + "\n"
}
for _, f := range sortedFamilies {
output = output + fmt.Sprintf("\n// %s family", f)
for _, m := range machines {
if family := strings.Split(m.Name, ".")[0]; family == f {
body := fmt.Sprintf(`
{
Name: "%s",
MemoryGB: %v,
Cores: %v,
InstanceENIs: %v,
InstanceIPsPerENI: %v,
`, m.Name, m.MemoryGB, m.Cores, m.InstanceENIs, m.InstanceIPsPerENI)
output = output + body
// Avoid awkward []int(nil) syntax
if len(m.EphemeralDisks) == 0 {
output = output + "EphemeralDisks: nil,\n"
} else {
output = output + fmt.Sprintf("EphemeralDisks: %#v,\n", m.EphemeralDisks)
}
if m.GPU {
output = output + "GPU: true,\n"
}
output = output + "},\n"
}
}
output = output + "\n"
}
klog.Infof("Writing changes to %v", outputPath)
fileInput, err := ioutil.ReadFile(outputPath)
if err != nil {
return fmt.Errorf("error reading %s: %v", outputPath, err)
}
scanner := bufio.NewScanner(bytes.NewReader(fileInput))
scanner.Split(bufio.ScanLines)
var newfile string
flag := false
done := false
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "END GENERATED CONTENT") {
flag = false
done = true
}
if !flag {
newfile = newfile + line + "\n"
}
if strings.Contains(line, "BEGIN GENERATED CONTENT") {
flag = true
newfile = newfile + output
}
}
if !done {
return fmt.Errorf("BEGIN GENERATED CONTENT / END GENERATED CONTENT markers not found")
}
err = ioutil.WriteFile(outputPath, []byte(newfile), 0644)
if err != nil {
return fmt.Errorf("error writing %s: %v", outputPath, err)
}
klog.Info("Done.")
klog.Flush()
return nil
}
func intValue(v *int64) int {
return int(aws.Int64Value(v))
}

View File

@ -452,8 +452,16 @@ func (b *KubeletBuilder) buildKubeletConfigSpec() (*kops.KubeletConfigSpec, erro
instanceTypeName = strings.Split(b.InstanceGroup.Spec.MachineType, ",")[0]
}
region, err := awsup.FindRegion(b.Cluster)
if err != nil {
return nil, err
}
awsCloud, err := awsup.NewAWSCloud(region, nil)
if err != nil {
return nil, err
}
// Get the instance type's detailed information.
instanceType, err := awsup.GetMachineTypeInfo(instanceTypeName)
instanceType, err := awsup.GetMachineTypeInfo(awsCloud, instanceTypeName)
if err != nil {
return nil, err
}

View File

@ -47,6 +47,7 @@ go_test(
deps = [
"//pkg/apis/kops:go_default_library",
"//upup/pkg/fi:go_default_library",
"//upup/pkg/fi/cloudup/awsup:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",

View File

@ -23,6 +23,7 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
)
@ -38,17 +39,21 @@ func awsValidateCluster(c *kops.Cluster) field.ErrorList {
return allErrs
}
func awsValidateInstanceGroup(ig *kops.InstanceGroup) field.ErrorList {
func awsValidateInstanceGroup(ig *kops.InstanceGroup, cloud awsup.AWSCloud) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, awsValidateAdditionalSecurityGroups(field.NewPath("spec", "additionalSecurityGroups"), ig.Spec.AdditionalSecurityGroups)...)
allErrs = append(allErrs, awsValidateMachineType(field.NewPath(ig.GetName(), "spec", "machineType"), ig.Spec.MachineType)...)
allErrs = append(allErrs, awsValidateInstanceType(field.NewPath(ig.GetName(), "spec", "machineType"), ig.Spec.MachineType, cloud)...)
allErrs = append(allErrs, awsValidateSpotDurationInMinute(field.NewPath(ig.GetName(), "spec", "spotDurationInMinutes"), ig)...)
allErrs = append(allErrs, awsValidateInstanceInterruptionBehavior(field.NewPath(ig.GetName(), "spec", "instanceInterruptionBehavior"), ig)...)
if ig.Spec.MixedInstancesPolicy != nil {
allErrs = append(allErrs, awsValidateMixedInstancesPolicy(field.NewPath("spec", "mixedInstancesPolicy"), ig.Spec.MixedInstancesPolicy, ig, cloud)...)
}
return allErrs
}
@ -73,12 +78,11 @@ func awsValidateAdditionalSecurityGroups(fieldPath *field.Path, groups []string)
return allErrs
}
func awsValidateMachineType(fieldPath *field.Path, machineType string) field.ErrorList {
func awsValidateInstanceType(fieldPath *field.Path, instanceType string, cloud awsup.AWSCloud) field.ErrorList {
allErrs := field.ErrorList{}
if machineType != "" {
for _, typ := range strings.Split(machineType, ",") {
if _, err := awsup.GetMachineTypeInfo(typ); err != nil {
if instanceType != "" && cloud != nil {
for _, typ := range strings.Split(instanceType, ",") {
if _, err := cloud.DescribeInstanceType(typ); err != nil {
allErrs = append(allErrs, field.Invalid(fieldPath, typ, "machine type specified is invalid"))
}
}
@ -106,3 +110,35 @@ func awsValidateInstanceInterruptionBehavior(fieldPath *field.Path, ig *kops.Ins
}
return allErrs
}
// awsValidateMixedInstancesPolicy is responsible for validating the user input of a mixed instance policy
func awsValidateMixedInstancesPolicy(path *field.Path, spec *kops.MixedInstancesPolicySpec, ig *kops.InstanceGroup, cloud awsup.AWSCloud) field.ErrorList {
var errs field.ErrorList
// @step: check the instance types are valid
for i, x := range spec.Instances {
errs = append(errs, awsValidateInstanceType(path.Child("instances").Index(i), x, cloud)...)
}
if spec.OnDemandBase != nil {
if fi.Int64Value(spec.OnDemandBase) < 0 {
errs = append(errs, field.Invalid(path.Child("onDemandBase"), spec.OnDemandBase, "cannot be less than zero"))
}
if fi.Int64Value(spec.OnDemandBase) > int64(fi.Int32Value(ig.Spec.MaxSize)) {
errs = append(errs, field.Invalid(path.Child("onDemandBase"), spec.OnDemandBase, "cannot be greater than max size"))
}
}
if spec.OnDemandAboveBase != nil {
if fi.Int64Value(spec.OnDemandAboveBase) < 0 {
errs = append(errs, field.Invalid(path.Child("onDemandAboveBase"), spec.OnDemandAboveBase, "cannot be less than 0"))
}
if fi.Int64Value(spec.OnDemandAboveBase) > 100 {
errs = append(errs, field.Invalid(path.Child("onDemandAboveBase"), spec.OnDemandAboveBase, "cannot be greater than 100"))
}
}
errs = append(errs, IsValidValue(path.Child("spotAllocationStrategy"), spec.SpotAllocationStrategy, kops.SpotAllocationStrategies)...)
return errs
}

View File

@ -20,6 +20,7 @@ import (
"testing"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kops/pkg/apis/kops"
@ -143,6 +144,7 @@ func TestValidateInstanceGroupSpec(t *testing.T) {
ExpectedErrors: []string{},
},
}
cloud := awsup.BuildMockAWSCloud("us-east-1", "abc")
for _, g := range grid {
ig := &kops.InstanceGroup{
ObjectMeta: v1.ObjectMeta{
@ -150,7 +152,7 @@ func TestValidateInstanceGroupSpec(t *testing.T) {
},
Spec: g.Input,
}
errs := awsValidateInstanceGroup(ig)
errs := awsValidateInstanceGroup(ig, cloud)
testErrors(t, g.Input, errs, g.ExpectedErrors)
}

View File

@ -24,10 +24,11 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
)
// ValidateInstanceGroup is responsible for validating the configuration of a instancegroup
func ValidateInstanceGroup(g *kops.InstanceGroup) field.ErrorList {
func ValidateInstanceGroup(g *kops.InstanceGroup, cloud fi.Cloud) field.ErrorList {
allErrs := field.ErrorList{}
if g.ObjectMeta.Name == "" {
@ -75,10 +76,6 @@ func ValidateInstanceGroup(g *kops.InstanceGroup) field.ErrorList {
allErrs = append(allErrs, validateFileAssetSpec(&g.Spec.FileAssets[i], field.NewPath("spec", "fileAssets").Index(i))...)
}
if g.Spec.MixedInstancesPolicy != nil {
allErrs = append(allErrs, validatedMixedInstancesPolicy(field.NewPath("spec", "mixedInstancesPolicy"), g.Spec.MixedInstancesPolicy, g)...)
}
for _, UserDataInfo := range g.Spec.AdditionalUserData {
allErrs = append(allErrs, validateExtraUserData(&UserDataInfo)...)
}
@ -118,41 +115,13 @@ func ValidateInstanceGroup(g *kops.InstanceGroup) field.ErrorList {
allErrs = append(allErrs, validateRollingUpdate(g.Spec.RollingUpdate, field.NewPath("spec", "rollingUpdate"), g.Spec.Role == kops.InstanceGroupRoleMaster)...)
}
if cloud != nil && cloud.ProviderID() == kops.CloudProviderAWS {
allErrs = append(allErrs, awsValidateInstanceGroup(g, cloud.(awsup.AWSCloud))...)
}
return allErrs
}
// validatedMixedInstancesPolicy is responsible for validating the user input of a mixed instance policy
func validatedMixedInstancesPolicy(path *field.Path, spec *kops.MixedInstancesPolicySpec, ig *kops.InstanceGroup) field.ErrorList {
var errs field.ErrorList
// @step: check the instances are validate
for i, x := range spec.Instances {
errs = append(errs, awsValidateMachineType(path.Child("instances").Index(i).Child("instanceType"), x)...)
}
if spec.OnDemandBase != nil {
if fi.Int64Value(spec.OnDemandBase) < 0 {
errs = append(errs, field.Invalid(path.Child("onDemandBase"), spec.OnDemandBase, "cannot be less than zero"))
}
if fi.Int64Value(spec.OnDemandBase) > int64(fi.Int32Value(ig.Spec.MaxSize)) {
errs = append(errs, field.Invalid(path.Child("onDemandBase"), spec.OnDemandBase, "cannot be greater than max size"))
}
}
if spec.OnDemandAboveBase != nil {
if fi.Int64Value(spec.OnDemandAboveBase) < 0 {
errs = append(errs, field.Invalid(path.Child("onDemandAboveBase"), spec.OnDemandAboveBase, "cannot be less than 0"))
}
if fi.Int64Value(spec.OnDemandAboveBase) > 100 {
errs = append(errs, field.Invalid(path.Child("onDemandAboveBase"), spec.OnDemandAboveBase, "cannot be greater than 100"))
}
}
errs = append(errs, IsValidValue(path.Child("spotAllocationStrategy"), spec.SpotAllocationStrategy, kops.SpotAllocationStrategies)...)
return errs
}
// validateVolumeSpec is responsible for checking a volume spec is ok
func validateVolumeSpec(path *field.Path, v *kops.VolumeSpec) field.ErrorList {
allErrs := field.ErrorList{}
@ -187,8 +156,8 @@ func validateVolumeMountSpec(path *field.Path, spec *kops.VolumeMountSpec) field
// CrossValidateInstanceGroup performs validation of the instance group, including that it is consistent with the Cluster
// It calls ValidateInstanceGroup, so all that validation is included.
func CrossValidateInstanceGroup(g *kops.InstanceGroup, cluster *kops.Cluster) field.ErrorList {
allErrs := ValidateInstanceGroup(g)
func CrossValidateInstanceGroup(g *kops.InstanceGroup, cluster *kops.Cluster, cloud fi.Cloud) field.ErrorList {
allErrs := ValidateInstanceGroup(g, cloud)
if g.Spec.Role == kops.InstanceGroupRoleMaster {
allErrs = append(allErrs, ValidateMasterInstanceGroup(g, cluster)...)

View File

@ -217,7 +217,7 @@ func TestValidBootDevice(t *testing.T) {
RootVolumeType: fi.String(g.volumeType),
},
}
errs := CrossValidateInstanceGroup(ig, cluster)
errs := CrossValidateInstanceGroup(ig, cluster, nil)
testErrors(t, g.volumeType, errs, g.expected)
}
}

View File

@ -507,7 +507,7 @@ func validateSubnetCIDR(networkCIDR *net.IPNet, additionalNetworkCIDRs []*net.IP
}
// DeepValidate is responsible for validating the instancegroups within the cluster spec
func DeepValidate(c *kops.Cluster, groups []*kops.InstanceGroup, strict bool) error {
func DeepValidate(c *kops.Cluster, groups []*kops.InstanceGroup, strict bool, cloud fi.Cloud) error {
if errs := ValidateCluster(c, strict); len(errs) != 0 {
return errs.ToAggregate()
}
@ -535,17 +535,11 @@ func DeepValidate(c *kops.Cluster, groups []*kops.InstanceGroup, strict bool) er
}
for _, g := range groups {
errs := CrossValidateInstanceGroup(g, c)
errs := CrossValidateInstanceGroup(g, c, cloud)
// Additional cloud-specific validation rules,
// such as making sure that identifiers match the expected formats for the given cloud
switch kops.CloudProviderID(c.Spec.CloudProvider) {
case kops.CloudProviderAWS:
errs = append(errs, awsValidateInstanceGroup(g)...)
default:
if len(g.Spec.Volumes) > 0 {
errs = append(errs, field.Forbidden(field.NewPath("spec", "volumes"), "instancegroup volumes are only available with aws at present"))
}
// Additional cloud-specific validation rules
if kops.CloudProviderID(c.Spec.CloudProvider) != kops.CloudProviderAWS && len(g.Spec.Volumes) > 0 {
errs = append(errs, field.Forbidden(field.NewPath("spec", "volumes"), "instancegroup volumes are only available with aws at present"))
}
if len(errs) != 0 {

View File

@ -61,7 +61,7 @@ func NewInstanceGroupMirror(cluster *kopsapi.Cluster, configBase vfs.Path) Insta
}
r.init(kind, configBase.Join("instancegroup"), StoreVersion)
r.validate = func(o runtime.Object) error {
return validation.ValidateInstanceGroup(o.(*kopsapi.InstanceGroup)).ToAggregate()
return validation.ValidateInstanceGroup(o.(*kopsapi.InstanceGroup), nil).ToAggregate()
}
return r
}
@ -80,7 +80,7 @@ func newInstanceGroupVFS(c *VFSClientset, cluster *kopsapi.Cluster) *InstanceGro
}
r.init(kind, c.basePath.Join(clusterName, "instancegroup"), StoreVersion)
r.validate = func(o runtime.Object) error {
return validation.ValidateInstanceGroup(o.(*kopsapi.InstanceGroup)).ToAggregate()
return validation.ValidateInstanceGroup(o.(*kopsapi.InstanceGroup), nil).ToAggregate()
}
return r
}

View File

@ -41,7 +41,7 @@ func UpdateCluster(ctx context.Context, clientset simple.Clientset, cluster *kop
return err
}
err = validation.DeepValidate(fullCluster, instanceGroups, true)
err = validation.DeepValidate(fullCluster, instanceGroups, true, nil)
if err != nil {
return err
}

View File

@ -232,12 +232,17 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) error {
return err
}
err = validation.DeepValidate(c.Cluster, c.InstanceGroups, true)
cluster := c.Cluster
cloud, err := BuildCloud(cluster)
if err != nil {
return err
}
cluster := c.Cluster
err = validation.DeepValidate(c.Cluster, c.InstanceGroups, true, cloud)
if err != nil {
return err
}
if cluster.Spec.KubernetesVersion == "" {
return fmt.Errorf("KubernetesVersion not set")
@ -323,11 +328,6 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) error {
"mirrorSecrets": &fitasks.MirrorSecrets{},
})
cloud, err := BuildCloud(cluster)
if err != nil {
return err
}
region := ""
project := ""
@ -446,8 +446,6 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) error {
if len(sshPublicKeys) > 1 {
return fmt.Errorf("exactly one 'admin' SSH public key can be specified when running with AWS; please delete a key using `kops delete secret`")
}
l.TemplateFunctions["MachineTypeInfo"] = awsup.GetMachineTypeInfo
}
case kops.CloudProviderALI:

View File

@ -26,7 +26,7 @@ import (
// buildEphemeralDevices looks up the machine type and discovery any ephemeral device mappings
func buildEphemeralDevices(cloud awsup.AWSCloud, machineType string) (map[string]*BlockDeviceMapping, error) {
mt, err := awsup.GetMachineTypeInfo(machineType)
mt, err := awsup.GetMachineTypeInfo(cloud, machineType)
if err != nil {
return nil, fmt.Errorf("failed to find instance type details on: %s, error: %s", machineType, err)
}

View File

@ -155,6 +155,9 @@ type AWSCloud interface {
// DefaultInstanceType determines a suitable instance type for the specified instance group
DefaultInstanceType(cluster *kops.Cluster, ig *kops.InstanceGroup) (string, error)
// DescribeInstanceType calls ec2.DescribeInstanceType to get information for a particular instance type
DescribeInstanceType(instanceType string) (*ec2.InstanceTypeInfo, error)
// FindClusterStatus gets the status of the cluster as it exists in AWS, inferred from volumes
FindClusterStatus(cluster *kops.Cluster) (*kops.ClusterStatus, error)
}
@ -174,6 +177,8 @@ type awsCloudImplementation struct {
tags map[string]string
regionDelayers *RegionDelayers
instanceTypes *instanceTypes
}
type RegionDelayers struct {
@ -181,6 +186,11 @@ type RegionDelayers struct {
delayerMap map[string]*k8s_aws.CrossRequestRetryDelay
}
type instanceTypes struct {
mutex sync.Mutex
typeMap map[string]*ec2.InstanceTypeInfo
}
var _ fi.Cloud = &awsCloudImplementation{}
func (c *awsCloudImplementation) ProviderID() kops.CloudProviderID {
@ -201,6 +211,9 @@ func NewAWSCloud(region string, tags map[string]string) (AWSCloud, error) {
regionDelayers: &RegionDelayers{
delayerMap: make(map[string]*k8s_aws.CrossRequestRetryDelay),
},
instanceTypes: &instanceTypes{
typeMap: make(map[string]*ec2.InstanceTypeInfo),
},
}
config := aws.NewConfig().WithRegion(region)
@ -1537,3 +1550,33 @@ func (c *awsCloudImplementation) zonesWithInstanceType(instanceType string) (set
return zones, nil
}
// DescribeInstanceType calls ec2.DescribeInstanceType to get information for a particular instance type
func (c *awsCloudImplementation) DescribeInstanceType(instanceType string) (*ec2.InstanceTypeInfo, error) {
if info, ok := c.instanceTypes.typeMap[instanceType]; ok {
return info, nil
}
c.instanceTypes.mutex.Lock()
defer c.instanceTypes.mutex.Unlock()
info, err := describeInstanceType(c, instanceType)
if err != nil {
return nil, err
}
c.instanceTypes.typeMap[instanceType] = info
return info, nil
}
func describeInstanceType(c AWSCloud, instanceType string) (*ec2.InstanceTypeInfo, error) {
req := &ec2.DescribeInstanceTypesInput{
InstanceTypes: aws.StringSlice([]string{instanceType}),
}
resp, err := c.EC2().DescribeInstanceTypes(req)
if err != nil {
return nil, err
}
if len(resp.InstanceTypes) != 1 {
return nil, fmt.Errorf("invalid instance type specified: %v", instanceType)
}
return resp.InstanceTypes[0], nil
}

File diff suppressed because it is too large Load Diff

View File

@ -270,3 +270,33 @@ func (c *MockAWSCloud) DefaultInstanceType(cluster *kops.Cluster, ig *kops.Insta
return "", fmt.Errorf("MockAWSCloud DefaultInstanceType does not handle %s", ig.Spec.Role)
}
}
// DescribeInstanceType calls ec2.DescribeInstanceType to get information for a particular instance type
func (c *MockAWSCloud) DescribeInstanceType(instanceType string) (*ec2.InstanceTypeInfo, error) {
if instanceType == "t2.invalidType" {
return nil, fmt.Errorf("invalid instance type specified: t2.invalidType")
}
info := &ec2.InstanceTypeInfo{
NetworkInfo: &ec2.NetworkInfo{
MaximumNetworkInterfaces: aws.Int64(1),
Ipv4AddressesPerInterface: aws.Int64(1),
},
MemoryInfo: &ec2.MemoryInfo{
SizeInMiB: aws.Int64(1024),
},
VCpuInfo: &ec2.VCpuInfo{
DefaultVCpus: aws.Int64(2),
},
}
if instanceType == "m3.medium" {
info.InstanceStorageInfo = &ec2.InstanceStorageInfo{
Disks: []*ec2.DiskInfo{
{
Count: aws.Int64(1),
SizeInGB: aws.Int64(1024),
},
},
}
}
return info, nil
}

View File

@ -31,7 +31,7 @@ func TestDeepValidate_OK(t *testing.T) {
var groups []*kopsapi.InstanceGroup
groups = append(groups, buildMinimalMasterInstanceGroup("subnet-us-mock-1a"))
groups = append(groups, buildMinimalNodeInstanceGroup("subnet-us-mock-1a"))
err := validation.DeepValidate(c, groups, true)
err := validation.DeepValidate(c, groups, true, nil)
if err != nil {
t.Fatalf("Expected no error from DeepValidate, got %v", err)
}
@ -172,7 +172,7 @@ func TestDeepValidate_MissingEtcdMember(t *testing.T) {
}
func expectErrorFromDeepValidate(t *testing.T, c *kopsapi.Cluster, groups []*kopsapi.InstanceGroup, message string) {
err := validation.DeepValidate(c, groups, true)
err := validation.DeepValidate(c, groups, true, nil)
if err == nil {
t.Fatalf("Expected error %q from DeepValidate (strict=true), not no error raised", message)
}

View File

@ -47,6 +47,7 @@ const (
defaultALINodeImage = "centos_7_04_64_20G_alibase_201701015.vhd"
)
// TODO: this hardcoded list can be replaced with DescribeInstanceTypes' DedicatedHostsSupported field
var awsDedicatedInstanceExceptions = map[string]bool{
"t2.nano": true,
"t2.micro": true,
@ -60,7 +61,7 @@ var awsDedicatedInstanceExceptions = map[string]bool{
// The InstanceGroup is simpler than the cluster spec, so we just populate in place (like the rest of k8s)
func PopulateInstanceGroupSpec(cluster *kops.Cluster, input *kops.InstanceGroup, channel *kops.Channel) (*kops.InstanceGroup, error) {
var err error
err = validation.ValidateInstanceGroup(input).ToAggregate()
err = validation.ValidateInstanceGroup(input, nil).ToAggregate()
if err != nil {
return nil, err
}

View File

@ -528,7 +528,7 @@ func (_ *Elastigroup) create(cloud awsup.AWSCloud, a, e, changes *Elastigroup) e
return err
}
ephemeralDevices, err := e.buildEphemeralDevices(e.OnDemandInstanceType)
ephemeralDevices, err := e.buildEphemeralDevices(cloud, e.OnDemandInstanceType)
if err != nil {
return err
}
@ -956,7 +956,7 @@ func (_ *Elastigroup) update(cloud awsup.AWSCloud, a, e, changes *Elastigroup) e
return err
}
ephemeralDevices, err := e.buildEphemeralDevices(e.OnDemandInstanceType)
ephemeralDevices, err := e.buildEphemeralDevices(cloud, e.OnDemandInstanceType)
if err != nil {
return err
}
@ -1494,7 +1494,7 @@ func (_ *Elastigroup) RenderTerraform(t *terraform.TerraformTarget, a, e, change
return err
}
ephemeralDevices, err := e.buildEphemeralDevices(e.OnDemandInstanceType)
ephemeralDevices, err := e.buildEphemeralDevices(cloud, e.OnDemandInstanceType)
if err != nil {
return err
}
@ -1638,12 +1638,12 @@ func (e *Elastigroup) buildAutoScaleLabels(labelsMap map[string]string) []*aws.A
return labels
}
func (e *Elastigroup) buildEphemeralDevices(instanceTypeName *string) (map[string]*awstasks.BlockDeviceMapping, error) {
func (e *Elastigroup) buildEphemeralDevices(c awsup.AWSCloud, instanceTypeName *string) (map[string]*awstasks.BlockDeviceMapping, error) {
if instanceTypeName == nil {
return nil, fi.RequiredField("InstanceType")
}
instanceType, err := awsup.GetMachineTypeInfo(*instanceTypeName)
instanceType, err := awsup.GetMachineTypeInfo(c, *instanceTypeName)
if err != nil {
return nil, err
}