diff --git a/pkg/tarmak/cluster/cluster.go b/pkg/tarmak/cluster/cluster.go index f73fb47f6a..db07bf18a9 100644 --- a/pkg/tarmak/cluster/cluster.go +++ b/pkg/tarmak/cluster/cluster.go @@ -85,7 +85,7 @@ func NewFromConfig(environment interfaces.Environment, conf *clusterv1alpha1.Clu } // setup instance pools - var result error + var result *multierror.Error for pos, _ := range cluster.conf.InstancePools { instancePool := cluster.conf.InstancePools[pos] // create instance pools @@ -97,7 +97,7 @@ func NewFromConfig(environment interfaces.Environment, conf *clusterv1alpha1.Clu cluster.instancePools = append(cluster.instancePools, pool) } - return cluster, result + return cluster, result.ErrorOrNil() } func (c *Cluster) InstancePools() []interfaces.InstancePool { @@ -204,6 +204,17 @@ func (c *Cluster) validateSingleInstancePoolMap(poolMap map[string][]*clusterv1a return result.ErrorOrNil() } +// validate cluster instancePool types +func validateClusterTypes(poolMap map[string][]*clusterv1alpha1.InstancePool, clusterType string) error { + var result *multierror.Error + + if len(poolMap[clusterv1alpha1.InstancePoolTypeEtcd]) != 1 { + result = multierror.Append(result, fmt.Errorf("a %s needs to have exactly one '%s' server pool", clusterType, clusterv1alpha1.InstancePoolTypeEtcd)) + } + + return result.ErrorOrNil() +} + func (c *Cluster) validateMultiInstancePoolMap(poolMap map[string][]*clusterv1alpha1.InstancePool, instanceType string) error { if len(poolMap[instanceType]) < 1 { return fmt.Errorf("cluster type '%s' requires one or more instance pool of type '%s'", c.Type(), instanceType) @@ -303,7 +314,7 @@ func (c *Cluster) validateInstancePools() error { return err } - return nil + return result.ErrorOrNil() } // Verify cluster @@ -314,6 +325,10 @@ func (c *Cluster) Verify() error { result = multierror.Append(result, err) } + if err := c.Environment().Provider().VerifyInstanceTypes(c.InstancePools()); err != nil { + result = multierror.Append(result, err) + } + if c.Type() == clusterv1alpha1.ClusterTypeClusterMulti { if err := c.verifyHubState(); err != nil { result = multierror.Append(result, err) @@ -374,6 +389,7 @@ func (c *Cluster) verifyHubState() error { // Verify instance pools func (c *Cluster) VerifyInstancePools() (result error) { + // Verify instance pools imageIDs, err := c.ImageIDs() if err != nil { return fmt.Errorf("error getting image IDs: %s]", err) @@ -386,6 +402,7 @@ func (c *Cluster) VerifyInstancePools() (result error) { return fmt.Errorf("error getting the image ID of %s", instancePool.TFName()) } } + return nil } @@ -453,7 +470,7 @@ func (c *Cluster) validateNetwork() (result error) { } // validate logging configuration -func (c *Cluster) validateLoggingSinks() (result error) { +func (c *Cluster) validateLoggingSinks() error { if c.Config().LoggingSinks != nil { for index, loggingSink := range c.Config().LoggingSinks { @@ -506,7 +523,9 @@ func (c *Cluster) validateClusterAutoscaler() (result error) { } // Validate APIServer -func (c *Cluster) validateAPIServer() (result error) { +func (c *Cluster) validateAPIServer() error { + var result *multierror.Error + for _, cidr := range c.Config().Kubernetes.APIServer.AllowCIDRs { _, _, err := net.ParseCIDR(cidr) if err != nil { @@ -533,7 +552,7 @@ func (c *Cluster) validateAPIServer() (result error) { } } - return result + return result.ErrorOrNil() } func (c *Cluster) validatePrometheusMode() error { diff --git a/pkg/tarmak/config/cluster.go b/pkg/tarmak/config/cluster.go index 0b930d484f..2bae067c97 100644 --- a/pkg/tarmak/config/cluster.go +++ b/pkg/tarmak/config/cluster.go @@ -82,7 +82,7 @@ func newInstancePoolEtcd() *clusterv1alpha1.InstancePool { sp.Type = clusterv1alpha1.InstancePoolTypeEtcd sp.MinCount = 3 sp.MaxCount = 3 - sp.Size = clusterv1alpha1.InstancePoolSizeSmall + sp.Size = clusterv1alpha1.InstancePoolSizeMedium sp.Volumes = []clusterv1alpha1.Volume{ clusterv1alpha1.Volume{ ObjectMeta: metav1.ObjectMeta{Name: "root"}, diff --git a/pkg/tarmak/instance_pool/instance_pool.go b/pkg/tarmak/instance_pool/instance_pool.go index 5a47c547dc..35eb63a246 100644 --- a/pkg/tarmak/instance_pool/instance_pool.go +++ b/pkg/tarmak/instance_pool/instance_pool.go @@ -53,7 +53,7 @@ func NewFromConfig(cluster interfaces.Cluster, conf *clusterv1alpha1.InstancePoo provider := cluster.Environment().Provider() instanceType, err := provider.InstanceType(conf.Size) if err != nil { - return nil, fmt.Errorf("instanceType '%s' is not valid for this provier", conf.Size) + return nil, fmt.Errorf("instanceType '%s' is not valid for this provider: %v", conf.Size, err) } instancePool.instanceType = instanceType diff --git a/pkg/tarmak/provider/amazon/amazon.go b/pkg/tarmak/provider/amazon/amazon.go index a47d41d0c7..5e49b15a1a 100644 --- a/pkg/tarmak/provider/amazon/amazon.go +++ b/pkg/tarmak/provider/amazon/amazon.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "sort" + "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" @@ -71,8 +72,6 @@ type Route53 interface { ListHostedZonesByName(input *route53.ListHostedZonesByNameInput) (*route53.ListHostedZonesByNameOutput, error) } -var _ interfaces.Provider = &Amazon{} - func NewFromConfig(tarmak interfaces.Tarmak, conf *tarmakv1alpha1.Provider) (*Amazon, error) { a := &Amazon{ @@ -317,17 +316,8 @@ func (a *Amazon) readVaultToken() (string, error) { } func (a *Amazon) Validate() error { - return nil -} - -func (a *Amazon) Verify() error { var result *multierror.Error - // If this fails we don't want to verify any of the other steps as they will have the same error - if err := a.VerifyAWSCredentials(); err != nil { - return err - } - // These checks only make sense with an environment given if a.tarmak.Environment() != nil { if err := a.validateRemoteStateBucket(); err != nil { @@ -346,6 +336,9 @@ func (a *Amazon) Verify() error { result = multierror.Append(result, err) } } + if err := a.validateRemoteStateBucket(); err != nil { + result = multierror.Append(result, err) + } if err := a.validatePublicZone(); err != nil { result = multierror.Append(result, err) @@ -354,6 +347,19 @@ func (a *Amazon) Verify() error { return result.ErrorOrNil() } +func (a *Amazon) Verify() error { + var result *multierror.Error + + if a.tarmak.Environment() != nil { + // If this fails we don't want to verify any of the other steps as they will have the same error + if err := a.VerifyAWSCredentials(); err != nil { + return err + } + } + + return result.ErrorOrNil() +} + // Check if AWS credentials are setup correctly. // AWS GO SDK doesn't have an default check if access is successfull. We check if we can query the region without errors func (a *Amazon) VerifyAWSCredentials() error { @@ -522,7 +528,7 @@ func (a *Amazon) vaultSession() (*session.Session, error) { } func (a *Amazon) VerifyInstanceTypes(instancePools []interfaces.InstancePool) error { - var result error + var result *multierror.Error svc, err := a.EC2() if err != nil { @@ -532,20 +538,44 @@ func (a *Amazon) VerifyInstanceTypes(instancePools []interfaces.InstancePool) er for _, instance := range instancePools { instanceType, err := a.InstanceType(instance.Config().Size) if err != nil { - return err + result = multierror.Append(result, err) + continue } if err := a.verifyInstanceType(instanceType, instance.Zones(), svc); err != nil { result = multierror.Append(result, err) } + + switch instance.Role().Name() { + + case clusterv1alpha1.InstancePoolTypeMaster: + found := false + for _, s := range a.nonMasterType() { + if s == instanceType { + found = true + break + } + } + + if found { + a.tarmak.Log().Warnf("Type '%s' is not advised for master instance", instanceType) + } + break + + case clusterv1alpha1.InstancePoolTypeEtcd, clusterv1alpha1.InstancePoolTypeVault: + if a.awsInstanceBurstable(instanceType) { + a.tarmak.Log().Warnf("Burstable type '%s' is not advised for instance '%s'", instanceType, instance.Name()) + } + break + + } } - return result + return result.ErrorOrNil() } func (a *Amazon) verifyInstanceType(instanceType string, zones []string, svc EC2) error { - var result error - var available bool + var result *multierror.Error //Request offering, filter by given instance type request := &ec2.DescribeReservedInstancesOfferingsInput{ @@ -563,7 +593,7 @@ func (a *Amazon) verifyInstanceType(instanceType string, zones []string, svc EC2 //Loop through the given zones for _, zone := range zones { - available = false + available := false //Loop through every offer given. Check the zone against the current looped zone. for _, offer := range response.ReservedInstancesOfferings { @@ -579,7 +609,7 @@ func (a *Amazon) verifyInstanceType(instanceType string, zones []string, svc EC2 } } - return result + return result.ErrorOrNil() } // This methods converts and possibly validates a generic instance type to a @@ -598,7 +628,18 @@ func (a *Amazon) InstanceType(typeIn string) (typeOut string, err error) { return "m4.xlarge", nil } - // TODO: Validate custom instance type here + found := false + for _, t := range a.awsInstanceTypes() { + if t == typeIn { + found = true + break + } + } + + if !found { + return "", fmt.Errorf("'%s' is not a supported intance type", typeIn) + } + return typeIn, nil } @@ -611,6 +652,36 @@ func (a *Amazon) VolumeType(typeIn string) (typeOut string, err error) { if typeIn == clusterv1alpha1.VolumeTypeSSD { return "gp2", nil } - // TODO: Validate custom instance type here + + found := false + for _, t := range a.awsVolumeTypes() { + if t == typeIn { + found = true + break + } + } + + if !found { + return "", fmt.Errorf("'%s' is not a supported volume type", typeIn) + } + return typeIn, nil } + +func (a *Amazon) awsVolumeTypes() []string { + return []string{"io1", "gp2", "st1", "sc1"} +} + +func (a *Amazon) awsInstanceBurstable(typeName string) bool { + return strings.HasPrefix(typeName, "t2.") +} + +func (a *Amazon) awsInstanceTypes() []string { + instanceTypes := []string{"t2.nano", "t2.micro", "t2.small", "t2.medium", "t2.large", "t2.xlarge", "t2.2xlarge", "m4.large", "m4.xlarge", "m4.2xlarge", "m4.4xlarge", "m4.10xlarge", "m4.16xlarge", "m5.large", "m5.xlarge", "m5.2xlarge", "m5.4xlarge", "m5.12xlarge", "m5.24xlarge", "m5d.large", "m5d.xlarge", "m5d.2xlarge", "m5d.4xlarge", "m5d.12xlarge", "m5d.24xlarge", "c4.large", "c4.xlarge", "c4.2xlarge", "c4.4xlarge", "c4.8xlarge", "c5.large", "c5.xlarge", "c5.2xlarge", "c5.4xlarge", "c5.9xlarge", "c5.18xlarge", "c5d.xlarge", "c5d.2xlarge", "c5d.4xlarge", "c5d.9xlarge", "c5d.18xlarge", "r4.large", "r4.xlarge", "r4.2xlarge", "r4.4xlarge", "r4.8xlarge", "r4.16xlarge", "x1.16xlarge", "x1.32xlarge", "x1e.xlarge", "x1e.2xlarge", "x1e.4xlarge", "x1e.8xlarge", "x1e.16xlarge", "x1e.32xlarge", "d2.xlarge", "d2.2xlarge", "d2.4xlarge", "d2.8xlarge", "h1.2xlarge", "h1.4xlarge", "h1.8xlarge", "h1.16xlarge", "i3.large", "i3.xlarge", "i3.2xlarge", "i3.4xlarge", "i3.8xlarge", "i3.16xlarge", "i3.metal", "f1.2xlarge", "f1.16xlarge", "g3.4xlarge", "g3.8xlarge", "g3.16xlarge", "p2.xlarge", "p2.8xlarge", "p2.16xlarge", "p3.2xlarge", "p3.8xlarge", "p3.16xlarge"} + + return instanceTypes +} + +func (a *Amazon) nonMasterType() []string { + return []string{"t2.nano", "t2.micro"} +}