Skip to content

Commit 318aa65

Browse files
author
jwcesign
authored
Merge pull request #45 from helen-frank/cloudprovider_create
feat: cloudprovider add create
2 parents 1a59486 + 74c4bba commit 318aa65

File tree

3 files changed

+121
-2
lines changed

3 files changed

+121
-2
lines changed

pkg/apis/v1alpha1/ecsnodeclass.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ limitations under the License.
1717
package v1alpha1
1818

1919
import (
20+
"fmt"
2021
"log"
2122
"strings"
2223

24+
"github.com/mitchellh/hashstructure/v2"
2325
"github.com/samber/lo"
2426
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2527
)
@@ -206,6 +208,22 @@ type ECSNodeClass struct {
206208
Status ECSNodeClassStatus `json:"status,omitempty"`
207209
}
208210

211+
// We need to bump the ECSNodeClassHashVersion when we make an update to the ECSNodeClass CRD under these conditions:
212+
// 1. A field changes its default value for an existing field that is already hashed
213+
// 2. A field is added to the hash calculation with an already-set value
214+
// 3. A field is removed from the hash calculations
215+
const ECSNodeClassHashVersion = "v1"
216+
217+
func (in *ECSNodeClass) Hash() string {
218+
return fmt.Sprint(lo.Must(hashstructure.Hash([]interface{}{
219+
in.Spec,
220+
}, hashstructure.FormatV2, &hashstructure.HashOptions{
221+
SlicesAsSets: true,
222+
IgnoreZeroValue: true,
223+
ZeroNil: true,
224+
})))
225+
}
226+
209227
// ImageFamily If an alias is specified, return alias, or be 'Custom' (enforced via validation).
210228
func (in *ECSNodeClass) ImageFamily() string {
211229
if term, ok := lo.Find(in.Spec.ImageSelectorTerms, func(t ImageSelectorTerm) bool {

pkg/apis/v1alpha1/zz_generated.deepcopy.go

Lines changed: 38 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/cloudprovider/cloudprovider.go

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package cloudprovider
1818

1919
import (
2020
"context"
21+
stderrors "errors"
2122
"fmt"
2223
"net/http"
2324
"time"
@@ -36,6 +37,7 @@ import (
3637
karpv1 "sigs.k8s.io/karpenter/pkg/apis/v1"
3738
"sigs.k8s.io/karpenter/pkg/cloudprovider"
3839
"sigs.k8s.io/karpenter/pkg/events"
40+
"sigs.k8s.io/karpenter/pkg/scheduling"
3941
"sigs.k8s.io/karpenter/pkg/utils/resources"
4042

4143
"github.com/cloudpilot-ai/karpenter-provider-alicloud/config"
@@ -65,8 +67,42 @@ func New(kubeClient client.Client, recorder events.Recorder) *CloudProvider {
6567

6668
// Create a NodeClaim given the constraints.
6769
func (c *CloudProvider) Create(ctx context.Context, nodeClaim *karpv1.NodeClaim) (*karpv1.NodeClaim, error) {
68-
// TODO: Implement this
69-
return nil, nil
70+
nodeClass, err := c.resolveNodeClassFromNodeClaim(ctx, nodeClaim)
71+
if err != nil {
72+
if errors.IsNotFound(err) {
73+
c.recorder.Publish(cloudproviderevents.NodeClaimFailedToResolveNodeClass(nodeClaim))
74+
}
75+
// We treat a failure to resolve the NodeClass as an ICE since this means there is no capacity possibilities for this NodeClaim
76+
return nil, cloudprovider.NewInsufficientCapacityError(fmt.Errorf("resolving node class, %w", err))
77+
}
78+
79+
nodeClassReady := nodeClass.StatusConditions().Get(status.ConditionReady)
80+
if nodeClassReady.IsFalse() {
81+
return nil, cloudprovider.NewNodeClassNotReadyError(stderrors.New(nodeClassReady.Message))
82+
}
83+
if nodeClassReady.IsUnknown() {
84+
return nil, fmt.Errorf("resolving NodeClass readiness, NodeClass is in Ready=Unknown, %s", nodeClassReady.Message)
85+
}
86+
instanceTypes, err := c.resolveInstanceTypes(ctx, nodeClaim, nodeClass)
87+
if err != nil {
88+
return nil, fmt.Errorf("resolving instance types, %w", err)
89+
}
90+
if len(instanceTypes) == 0 {
91+
return nil, cloudprovider.NewInsufficientCapacityError(fmt.Errorf("all requested instance types were unavailable during launch"))
92+
}
93+
instance, err := c.instanceProvider.Create(ctx, nodeClass, nodeClaim, instanceTypes)
94+
if err != nil {
95+
return nil, fmt.Errorf("creating instance, %w", err)
96+
}
97+
instanceType, _ := lo.Find(instanceTypes, func(i *cloudprovider.InstanceType) bool {
98+
return i.Name == instance.Type
99+
})
100+
nc := c.instanceToNodeClaim(instance, instanceType, nodeClass)
101+
nc.Annotations = lo.Assign(nc.Annotations, map[string]string{
102+
v1alpha1.AnnotationECSNodeClassHash: nodeClass.Hash(),
103+
v1alpha1.AnnotationECSNodeClassHashVersion: v1alpha1.ECSNodeClassHashVersion,
104+
})
105+
return nc, nil
70106
}
71107

72108
func (c *CloudProvider) List(ctx context.Context) ([]*karpv1.NodeClaim, error) {
@@ -282,3 +318,30 @@ func (c *CloudProvider) instanceToNodeClaim(i *instance.Instance, instanceType *
282318
nodeClaim.Status.ImageID = i.ImageID
283319
return nodeClaim
284320
}
321+
322+
func (c *CloudProvider) resolveNodeClassFromNodeClaim(ctx context.Context, nodeClaim *karpv1.NodeClaim) (*v1alpha1.ECSNodeClass, error) {
323+
nodeClass := &v1alpha1.ECSNodeClass{}
324+
if err := c.kubeClient.Get(ctx, types.NamespacedName{Name: nodeClaim.Spec.NodeClassRef.Name}, nodeClass); err != nil {
325+
return nil, err
326+
}
327+
// For the purposes of NodeClass CloudProvider resolution, we treat deleting NodeClasses as NotFound
328+
if !nodeClass.DeletionTimestamp.IsZero() {
329+
// For the purposes of NodeClass CloudProvider resolution, we treat deleting NodeClasses as NotFound,
330+
// but we return a different error message to be clearer to users
331+
return nil, newTerminatingNodeClassError(nodeClass.Name)
332+
}
333+
return nodeClass, nil
334+
}
335+
336+
func (c *CloudProvider) resolveInstanceTypes(ctx context.Context, nodeClaim *karpv1.NodeClaim, nodeClass *v1alpha1.ECSNodeClass) ([]*cloudprovider.InstanceType, error) {
337+
instanceTypes, err := c.instanceTypeProvider.List(ctx, nodeClass.Spec.KubeletConfiguration, nodeClass)
338+
if err != nil {
339+
return nil, fmt.Errorf("getting instance types, %w", err)
340+
}
341+
reqs := scheduling.NewNodeSelectorRequirementsWithMinValues(nodeClaim.Spec.Requirements...)
342+
return lo.Filter(instanceTypes, func(i *cloudprovider.InstanceType, _ int) bool {
343+
return reqs.Compatible(i.Requirements, scheduling.AllowUndefinedWellKnownLabels) == nil &&
344+
len(i.Offerings.Compatible(reqs).Available()) > 0 &&
345+
resources.Fits(nodeClaim.Spec.Resources.Requests, i.Allocatable())
346+
}), nil
347+
}

0 commit comments

Comments
 (0)