Skip to content

Commit f36d34c

Browse files
author
jwcesign
authored
Merge pull request #48 from helen-frank/helen-frank/dev-general
feat: nodeclass add termination
2 parents c350073 + aa7fd8f commit f36d34c

File tree

3 files changed

+163
-0
lines changed

3 files changed

+163
-0
lines changed

pkg/controllers/nodeclass/hash/controller.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
/*
2+
Copyright 2024 The CloudPilot AI Authors.
3+
24
Licensed under the Apache License, Version 2.0 (the "License");
35
you may not use this file except in compliance with the License.
46
You may obtain a copy of the License at
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
Copyright 2024 The CloudPilot AI Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package termination
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"time"
23+
24+
"github.com/awslabs/operatorpkg/reasonable"
25+
"github.com/samber/lo"
26+
"k8s.io/apimachinery/pkg/api/equality"
27+
"k8s.io/apimachinery/pkg/api/errors"
28+
"k8s.io/apimachinery/pkg/types"
29+
controllerruntime "sigs.k8s.io/controller-runtime"
30+
"sigs.k8s.io/controller-runtime/pkg/builder"
31+
"sigs.k8s.io/controller-runtime/pkg/client"
32+
"sigs.k8s.io/controller-runtime/pkg/controller"
33+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
34+
"sigs.k8s.io/controller-runtime/pkg/event"
35+
"sigs.k8s.io/controller-runtime/pkg/handler"
36+
"sigs.k8s.io/controller-runtime/pkg/manager"
37+
"sigs.k8s.io/controller-runtime/pkg/predicate"
38+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
39+
karpv1 "sigs.k8s.io/karpenter/pkg/apis/v1"
40+
"sigs.k8s.io/karpenter/pkg/events"
41+
"sigs.k8s.io/karpenter/pkg/operator/injection"
42+
43+
"github.com/cloudpilot-ai/karpenter-provider-alicloud/pkg/apis/v1alpha1"
44+
"github.com/cloudpilot-ai/karpenter-provider-alicloud/pkg/providers/launchtemplate"
45+
)
46+
47+
type Controller struct {
48+
kubeClient client.Client
49+
recorder events.Recorder
50+
51+
launchTemplateProvider launchtemplate.Provider
52+
}
53+
54+
func NewController(kubeClient client.Client, recorder events.Recorder, launchTemplateProvider launchtemplate.Provider) *Controller {
55+
return &Controller{
56+
kubeClient: kubeClient,
57+
recorder: recorder,
58+
launchTemplateProvider: launchTemplateProvider,
59+
}
60+
}
61+
62+
func (c *Controller) Reconcile(ctx context.Context, nodeClass *v1alpha1.ECSNodeClass) (reconcile.Result, error) {
63+
ctx = injection.WithControllerName(ctx, "nodeclass.termination")
64+
65+
if !nodeClass.GetDeletionTimestamp().IsZero() {
66+
return c.finalize(ctx, nodeClass)
67+
}
68+
return reconcile.Result{}, nil
69+
}
70+
71+
func (c *Controller) finalize(ctx context.Context, nodeClass *v1alpha1.ECSNodeClass) (reconcile.Result, error) {
72+
stored := nodeClass.DeepCopy()
73+
if !controllerutil.ContainsFinalizer(nodeClass, v1alpha1.TerminationFinalizer) {
74+
return reconcile.Result{}, nil
75+
}
76+
nodeClaimList := &karpv1.NodeClaimList{}
77+
if err := c.kubeClient.List(ctx, nodeClaimList, client.MatchingFields{"spec.nodeClassRef.name": nodeClass.Name}); err != nil {
78+
return reconcile.Result{}, fmt.Errorf("listing nodeclaims that are using nodeclass, %w", err)
79+
}
80+
if len(nodeClaimList.Items) > 0 {
81+
c.recorder.Publish(WaitingOnNodeClaimTerminationEvent(nodeClass, lo.Map(nodeClaimList.Items, func(nc karpv1.NodeClaim, _ int) string { return nc.Name })))
82+
return reconcile.Result{RequeueAfter: time.Minute * 10}, nil // periodically fire the event
83+
}
84+
if err := c.launchTemplateProvider.DeleteAll(ctx, nodeClass); err != nil {
85+
return reconcile.Result{}, fmt.Errorf("deleting launch templates, %w", err)
86+
}
87+
controllerutil.RemoveFinalizer(nodeClass, v1alpha1.TerminationFinalizer)
88+
if !equality.Semantic.DeepEqual(stored, nodeClass) {
89+
if err := c.kubeClient.Patch(ctx, nodeClass, client.MergeFromWithOptions(stored, client.MergeFromWithOptimisticLock{})); err != nil {
90+
if errors.IsConflict(err) {
91+
return reconcile.Result{Requeue: true}, nil
92+
}
93+
return reconcile.Result{}, client.IgnoreNotFound(fmt.Errorf("removing termination finalizer, %w", err))
94+
}
95+
}
96+
return reconcile.Result{}, nil
97+
}
98+
99+
func (c *Controller) Register(_ context.Context, m manager.Manager) error {
100+
return controllerruntime.NewControllerManagedBy(m).
101+
Named("nodeclass.termination").
102+
For(&v1alpha1.ECSNodeClass{}).
103+
Watches(
104+
&karpv1.NodeClaim{},
105+
handler.EnqueueRequestsFromMapFunc(func(_ context.Context, o client.Object) []reconcile.Request {
106+
nc := o.(*karpv1.NodeClaim)
107+
if nc.Spec.NodeClassRef == nil {
108+
return nil
109+
}
110+
return []reconcile.Request{{NamespacedName: types.NamespacedName{Name: nc.Spec.NodeClassRef.Name}}}
111+
}),
112+
// Watch for NodeClaim deletion events
113+
builder.WithPredicates(predicate.Funcs{
114+
CreateFunc: func(e event.CreateEvent) bool { return false },
115+
UpdateFunc: func(e event.UpdateEvent) bool { return false },
116+
DeleteFunc: func(e event.DeleteEvent) bool { return true },
117+
}),
118+
).
119+
WithOptions(controller.Options{
120+
RateLimiter: reasonable.RateLimiter(),
121+
MaxConcurrentReconciles: 10,
122+
}).
123+
Complete(reconcile.AsReconciler(m.GetClient(), c))
124+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
Copyright 2024 The CloudPilot AI Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package termination
18+
19+
import (
20+
"fmt"
21+
22+
corev1 "k8s.io/api/core/v1"
23+
"sigs.k8s.io/karpenter/pkg/events"
24+
25+
"github.com/cloudpilot-ai/karpenter-provider-alicloud/pkg/apis/v1alpha1"
26+
"github.com/cloudpilot-ai/karpenter-provider-alicloud/pkg/utils"
27+
)
28+
29+
func WaitingOnNodeClaimTerminationEvent(nodeClass *v1alpha1.ECSNodeClass, names []string) events.Event {
30+
return events.Event{
31+
InvolvedObject: nodeClass,
32+
Type: corev1.EventTypeNormal,
33+
Reason: "WaitingOnNodeClaimTermination",
34+
Message: fmt.Sprintf("Waiting on NodeClaim termination for %s", utils.PrettySlice(names, 5)),
35+
DedupeValues: []string{string(nodeClass.UID)},
36+
}
37+
}

0 commit comments

Comments
 (0)