Skip to content

Commit

Permalink
Removed Factory from CloudProvider (#460)
Browse files Browse the repository at this point in the history
* Removed Factory from CloudProvider

* changed api to cloudProvider
  • Loading branch information
njtran authored Jun 17, 2021
1 parent ee801aa commit 10f5634
Show file tree
Hide file tree
Showing 24 changed files with 261 additions and 413 deletions.
4 changes: 2 additions & 2 deletions cmd/webhook/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ var (
)

type Options struct {
Port int
HealthProbePort int
Port int
HealthProbePort int
}

func main() {
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ
github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.31.12/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.38.11 h1:jmxKh557ZRc+Z8fALnGrL01Ctjks2aSUFLb7n/BZoEs=
github.com/aws/aws-sdk-go v1.38.11/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.38.62 h1:w7r48cTciWCJK//YH+oN8HhNXzPDdlucV3XT6KGDMjE=
github.com/aws/aws-sdk-go v1.38.62/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
Expand Down
90 changes: 0 additions & 90 deletions pkg/cloudprovider/aws/capacity.go

This file was deleted.

206 changes: 206 additions & 0 deletions pkg/cloudprovider/aws/cloudprovider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/*
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 aws

import (
"context"
"fmt"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/awslabs/karpenter/pkg/apis/provisioning/v1alpha1"
"github.com/awslabs/karpenter/pkg/cloudprovider"
"github.com/awslabs/karpenter/pkg/cloudprovider/aws/utils"
"github.com/awslabs/karpenter/pkg/utils/functional"
"github.com/awslabs/karpenter/pkg/utils/project"
"github.com/patrickmn/go-cache"
"go.uber.org/zap"
v1 "k8s.io/api/core/v1"
"knative.dev/pkg/apis"
)

const (
// CacheTTL restricts QPS to AWS APIs to this interval for verifying setup resources.
CacheTTL = 5 * time.Minute
// CacheCleanupInterval triggers cache cleanup (lazy eviction) at this interval.
CacheCleanupInterval = 10 * time.Minute
// ClusterTagKeyFormat is set on all Kubernetes owned resources.
ClusterTagKeyFormat = "kubernetes.io/cluster/%s"
// KarpenterTagKeyFormat is set on all Karpenter owned resources.
KarpenterTagKeyFormat = "karpenter.sh/cluster/%s"
)

var (
SupportedOperatingSystems = []string{
v1alpha1.OperatingSystemLinux,
}
SupportedArchitectures = []string{
v1alpha1.ArchitectureAmd64,
v1alpha1.ArchitectureArm64,
}
)

type CloudProvider struct {
nodeAPI *NodeFactory
launchTemplateProvider *LaunchTemplateProvider
subnetProvider *SubnetProvider
instanceTypeProvider *InstanceTypeProvider
instanceProvider *InstanceProvider
}

func NewCloudProvider(options cloudprovider.Options) *CloudProvider {
sess := withUserAgent(session.Must(
session.NewSession(request.WithRetryer(
&aws.Config{STSRegionalEndpoint: endpoints.RegionalSTSEndpoint},
utils.NewRetryer()))))
if *sess.Config.Region == "" {
zap.S().Debug("AWS region not configured, asking EC2 Instance Metadata Service")
*sess.Config.Region = getRegionFromIMDS(sess)
}
zap.S().Debugf("Using AWS region %s", *sess.Config.Region)
ec2api := ec2.New(sess)
return &CloudProvider{
nodeAPI: &NodeFactory{ec2api: ec2api},
launchTemplateProvider: &LaunchTemplateProvider{
ec2api: ec2api,
cache: cache.New(CacheTTL, CacheCleanupInterval),
securityGroupProvider: NewSecurityGroupProvider(ec2api),
ssm: ssm.New(sess),
clientSet: options.ClientSet,
},
subnetProvider: NewSubnetProvider(ec2api),
instanceTypeProvider: NewInstanceTypeProvider(ec2api),
instanceProvider: &InstanceProvider{ec2api: ec2api},
}
}

// get the current region from EC2 IMDS
func getRegionFromIMDS(sess *session.Session) string {
region, err := ec2metadata.New(sess).Region()
if err != nil {
panic(fmt.Sprintf("Failed to call the metadata server's region API, %s", err.Error()))
}
return region
}

// withUserAgent adds a karpenter specific user-agent string to AWS session
func withUserAgent(sess *session.Session) *session.Session {
userAgent := fmt.Sprintf("karpenter.sh-%s", project.Version)
sess.Handlers.Build.PushBack(request.MakeAddToUserAgentFreeFormHandler(userAgent))
return sess
}

// Create a set of nodes given the constraints.
func (a *CloudProvider) Create(ctx context.Context, provisioner *v1alpha1.Provisioner, packings []*cloudprovider.Packing) ([]*cloudprovider.PackedNode, error) {
instanceIDs := []*string{}
instancePackings := map[string]*cloudprovider.Packing{}
for _, packing := range packings {
constraints := Constraints(*packing.Constraints)
// 1. Get Subnets and constrain by zones
zonalSubnets, err := a.subnetProvider.GetZonalSubnets(ctx, provisioner.Spec.Cluster.Name)
if err != nil {
return nil, fmt.Errorf("getting zonal subnets, %w", err)
}
zonalSubnetOptions := map[string][]*ec2.Subnet{}
for zone, subnets := range zonalSubnets {
if len(constraints.Zones) == 0 || functional.ContainsString(constraints.Zones, zone) {
zonalSubnetOptions[zone] = subnets
}
}
// 2. Get Launch Template
launchTemplate, err := a.launchTemplateProvider.Get(ctx, provisioner, &constraints)
if err != nil {
return nil, fmt.Errorf("getting launch template, %w", err)
}
// 3. Create instance
instanceID, err := a.instanceProvider.Create(ctx, launchTemplate, packing.InstanceTypeOptions, zonalSubnets, constraints.GetCapacityType())
if err != nil {
// TODO Aggregate errors and continue
return nil, fmt.Errorf("creating capacity %w", err)
}
instancePackings[*instanceID] = packing
instanceIDs = append(instanceIDs, instanceID)
}

// 4. Convert to Nodes
nodes, err := a.nodeAPI.For(ctx, instanceIDs)
if err != nil {
return nil, fmt.Errorf("determining nodes, %w", err)
}
// 5. Convert to PackedNodes, TODO: move this logic into NodeAPI
packedNodes := []*cloudprovider.PackedNode{}
for instanceID, node := range nodes {
packing := instancePackings[instanceID]
node.Labels = packing.Constraints.Labels
node.Spec.Taints = packing.Constraints.Taints
packedNodes = append(packedNodes, &cloudprovider.PackedNode{
Node: node,
Pods: packing.Pods,
})
}
return packedNodes, nil
}

func (a *CloudProvider) GetInstanceTypes(ctx context.Context) ([]cloudprovider.InstanceType, error) {
return a.instanceTypeProvider.Get(ctx)
}

func (a *CloudProvider) Terminate(ctx context.Context, nodes []*v1.Node) error {
return a.instanceProvider.Terminate(ctx, nodes)
}

// Validate cloud provider specific components of the cluster spec
func (a *CloudProvider) Validate(ctx context.Context, spec *v1alpha1.ProvisionerSpec) (errs *apis.FieldError) {
return errs.Also(
validateAllowedLabels(*spec),
validateCapacityTypeLabel(*spec),
validateLaunchTemplateLabels(*spec),
)
}

func validateAllowedLabels(spec v1alpha1.ProvisionerSpec) (errs *apis.FieldError) {
for key := range spec.Labels {
if strings.HasPrefix(key, AWSLabelPrefix) && !functional.ContainsString(AllowedLabels, key) {
errs = errs.Also(apis.ErrInvalidKeyName(key, "spec.labels"))
}
}
return errs
}

func validateCapacityTypeLabel(spec v1alpha1.ProvisionerSpec) (errs *apis.FieldError) {
capacityType, ok := spec.Labels[CapacityTypeLabel]
if !ok {
return nil
}
capacityTypes := []string{CapacityTypeSpot, CapacityTypeOnDemand}
if !functional.ContainsString(capacityTypes, capacityType) {
errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("%s not in %v", capacityType, capacityTypes), fmt.Sprintf("spec.labels[%s]", CapacityTypeLabel)))
}
return errs
}

func validateLaunchTemplateLabels(spec v1alpha1.ProvisionerSpec) (errs *apis.FieldError) {
if _, versionExists := spec.Labels[LaunchTemplateVersionLabel]; versionExists {
if _, bothExist := spec.Labels[LaunchTemplateIdLabel]; !bothExist {
return errs.Also(apis.ErrMissingField(fmt.Sprintf("spec.labels[%s]", LaunchTemplateIdLabel)))
}
}
return errs
}
Loading

0 comments on commit 10f5634

Please sign in to comment.