Skip to content

Commit

Permalink
Add Conversion webhooks
Browse files Browse the repository at this point in the history
  • Loading branch information
engedaam committed Jul 31, 2024
1 parent acef2b5 commit 071154e
Show file tree
Hide file tree
Showing 18 changed files with 1,975 additions and 7 deletions.
13 changes: 13 additions & 0 deletions kwok/cloudprovider/cloudprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,20 @@ import (
"math/rand"
"strings"

"github.com/awslabs/operatorpkg/object"
"github.com/docker/docker/pkg/namesgenerator"
"github.com/samber/lo"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"

"sigs.k8s.io/karpenter/pkg/apis/v1beta1"
"sigs.k8s.io/karpenter/pkg/cloudprovider"
"sigs.k8s.io/karpenter/pkg/scheduling"
"sigs.k8s.io/karpenter/pkg/test/v1alpha1"
)

func NewCloudProvider(ctx context.Context, kubeClient client.Client, instanceTypes []*cloudprovider.InstanceType) *CloudProvider {
Expand Down Expand Up @@ -118,6 +121,16 @@ func (c CloudProvider) Name() string {
return "kwok"
}

func (c *CloudProvider) GetSupportedNodeClasses() []schema.GroupVersionKind {
return []schema.GroupVersionKind{
{
Group: object.GVK(&v1alpha1.TestNodeClass{}).Group,
Version: object.GVK(&v1alpha1.TestNodeClass{}).Version,
Kind: object.GVK(&v1alpha1.TestNodeClass{}).Kind,
},
}
}

func (c CloudProvider) getInstanceType(instanceTypeName string) (*cloudprovider.InstanceType, error) {
it, found := lo.Find(c.instanceTypes, func(it *cloudprovider.InstanceType) bool {
return it.Name == instanceTypeName
Expand Down
2 changes: 1 addition & 1 deletion kwok/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,5 @@ func main() {
state.NewCluster(op.Clock, op.GetClient(), cloudProvider),
op.EventRecorder,
cloudProvider,
)...).Start(ctx)
)...).Start(ctx, cloudProvider)
}
190 changes: 190 additions & 0 deletions pkg/apis/v1/nodeclaim_conversion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
Copyright 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 v1

import (
"context"
"encoding/json"
"fmt"

"github.com/awslabs/operatorpkg/status"
"github.com/samber/lo"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"knative.dev/pkg/apis"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"sigs.k8s.io/karpenter/pkg/apis/v1beta1"
"sigs.k8s.io/karpenter/pkg/operator/injection"
)

// convert v1 to v1beta1
func (in *NodeClaim) ConvertTo(ctx context.Context, to apis.Convertible) error {
v1beta1NC := to.(*v1beta1.NodeClaim)
v1beta1NC.ObjectMeta = in.ObjectMeta

in.Status.convertTo((&v1beta1NC.Status))
return in.Spec.convertTo(ctx, &v1beta1NC.Spec, in.Annotations[KubeletCompatibilityAnnotationKey])
}

func (in *NodeClaimSpec) convertTo(ctx context.Context, v1beta1nc *v1beta1.NodeClaimSpec, kubeletAnnotation string) error {
v1beta1nc.Taints = in.Taints
v1beta1nc.StartupTaints = in.StartupTaints
v1beta1nc.Resources = v1beta1.ResourceRequirements(in.Resources)
v1beta1nc.Requirements = lo.Map(in.Requirements, func(v1Requirements NodeSelectorRequirementWithMinValues, _ int) v1beta1.NodeSelectorRequirementWithMinValues {
return v1beta1.NodeSelectorRequirementWithMinValues{
NodeSelectorRequirement: v1.NodeSelectorRequirement{
Key: v1Requirements.Key,
Operator: v1Requirements.Operator,
Values: v1Requirements.Values,
},
MinValues: v1Requirements.MinValues,
}
})

if in.NodeClassRef != nil {
nodeclass, found := lo.Find(injection.GetNodeClasses(ctx), func(nc schema.GroupVersionKind) bool {
return nc.Kind == in.NodeClassRef.Kind && nc.Group == in.NodeClassRef.Group
})
v1beta1nc.NodeClassRef = &v1beta1.NodeClassReference{
Kind: in.NodeClassRef.Kind,
Name: in.NodeClassRef.Name,
APIVersion: lo.Ternary(found, nodeclass.GroupVersion().String(), ""),
}
}

if kubeletAnnotation != "" {
v1beta1kubelet := &v1beta1.KubeletConfiguration{}
err := json.Unmarshal([]byte(kubeletAnnotation), v1beta1kubelet)
if err != nil {
return fmt.Errorf("unmarshaling kubelet config annotation, %w", err)
}
v1beta1nc.Kubelet = v1beta1kubelet
}
return nil
}

func (in *NodeClaimStatus) convertTo(v1beta1nc *v1beta1.NodeClaimStatus) {
v1beta1nc.NodeName = in.NodeName
v1beta1nc.ProviderID = in.ProviderID
v1beta1nc.ImageID = in.ImageID
v1beta1nc.Capacity = in.Capacity
v1beta1nc.Allocatable = in.Allocatable
v1beta1nc.Conditions = lo.Map(in.Conditions, func(v1status status.Condition, _ int) apis.Condition {
return apis.Condition{
Type: apis.ConditionType(v1status.Type),
Reason: v1status.Reason,
Status: v1.ConditionStatus(v1status.Status),
Message: v1status.Message,
LastTransitionTime: apis.VolatileTime{Inner: v1status.LastTransitionTime},
}
})
}

// convert v1beta1 to v1
func (in *NodeClaim) ConvertFrom(ctx context.Context, from apis.Convertible) error {
v1beta1NC := from.(*v1beta1.NodeClaim)
in.ObjectMeta = v1beta1NC.ObjectMeta

in.Status.convertFrom((&v1beta1NC.Status))
kubeletAnnotation, err := in.Spec.convertFrom(ctx, &v1beta1NC.Spec)
if err != nil {
return err
}
if kubeletAnnotation == "" {
in.Annotations = lo.OmitByKeys(in.Annotations, []string{KubeletCompatibilityAnnotationKey})
} else {
in.Annotations = lo.Assign(in.Annotations, map[string]string{KubeletCompatibilityAnnotationKey: kubeletAnnotation})
}
return in.setExpireAfter(ctx, v1beta1NC)
}

// only need to set expireAfter for v1beta1 to v1
func (in *NodeClaim) setExpireAfter(ctx context.Context, v1beta1nc *v1beta1.NodeClaim) error {
kubeClient := injection.GetClient(ctx)
nodePoolName, ok := v1beta1nc.Labels[NodePoolLabelKey]
if !ok {
// If we don't have a nodepool for this nodeclaim, there's nothing to look up
return nil
}
nodePool := &NodePool{}
if err := kubeClient.Get(ctx, types.NamespacedName{Name: nodePoolName}, nodePool); err != nil {
if errors.IsNotFound(err) {
// If the nodepool doesn't exist, fallback to no expiry, and use the CRD default
return nil
}
return fmt.Errorf("getting nodepool, %w", err)
}
in.Spec.ExpireAfter = nodePool.Spec.Template.Spec.ExpireAfter
return nil
}

func (in *NodeClaimSpec) convertFrom(ctx context.Context, v1beta1nc *v1beta1.NodeClaimSpec) (string, error) {
in.Taints = v1beta1nc.Taints
in.StartupTaints = v1beta1nc.StartupTaints
in.Resources = ResourceRequirements(v1beta1nc.Resources)
in.Requirements = lo.Map(v1beta1nc.Requirements, func(v1beta1Requirements v1beta1.NodeSelectorRequirementWithMinValues, _ int) NodeSelectorRequirementWithMinValues {
return NodeSelectorRequirementWithMinValues{
NodeSelectorRequirement: v1.NodeSelectorRequirement{
Key: v1beta1Requirements.Key,
Operator: v1beta1Requirements.Operator,
Values: v1beta1Requirements.Values,
},
MinValues: v1beta1Requirements.MinValues,
}
})

defaultNodeClassGVK := injection.GetNodeClasses(ctx)[0]
nodeclassGroupVersion, err := schema.ParseGroupVersion(v1beta1nc.NodeClassRef.APIVersion)
if err != nil {
return "", err
}
in.NodeClassRef = &NodeClassReference{
Name: v1beta1nc.NodeClassRef.Name,
Kind: lo.Ternary(v1beta1nc.NodeClassRef.Kind == "", defaultNodeClassGVK.Kind, v1beta1nc.NodeClassRef.Kind),
Group: lo.Ternary(v1beta1nc.NodeClassRef.APIVersion == "", defaultNodeClassGVK.Group, nodeclassGroupVersion.Group),
}

if v1beta1nc.Kubelet != nil {
kubelet, err := json.Marshal(v1beta1nc.Kubelet)
if err != nil {
return "", fmt.Errorf("marshaling kubelet config annotation, %w", err)
}
return string(kubelet), nil
}
return "", nil
}

func (in *NodeClaimStatus) convertFrom(v1beta1nc *v1beta1.NodeClaimStatus) {
in.NodeName = v1beta1nc.NodeName
in.ProviderID = v1beta1nc.ProviderID
in.ImageID = v1beta1nc.ImageID
in.Capacity = v1beta1nc.Capacity
in.Allocatable = v1beta1nc.Allocatable
in.Conditions = lo.Map(v1beta1nc.Conditions, func(v1beta1status apis.Condition, _ int) status.Condition {
return status.Condition{
Type: string(v1beta1status.Type),
Reason: v1beta1status.Reason,
Status: metav1.ConditionStatus(v1beta1status.Status),
Message: v1beta1status.Message,
LastTransitionTime: v1beta1status.LastTransitionTime.Inner,
}
})
}
Loading

0 comments on commit 071154e

Please sign in to comment.