Skip to content

Commit 9d71fef

Browse files
sebltmAndiDog
authored andcommitted
feat: custom lifecyclehooks for machinepools
Taken and rebased from unfinished PR #4875 at commit 2421ec3
1 parent df09e6c commit 9d71fef

22 files changed

+964
-9
lines changed

config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinepools.yaml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,55 @@ spec:
883883
after it enters the InService state.
884884
If no value is supplied by user a default value of 300 seconds is set
885885
type: string
886+
lifecycleHooks:
887+
description: AWSLifecycleHooks specifies lifecycle hooks for the autoscaling
888+
group.
889+
items:
890+
description: AWSLifecycleHook describes an AWS lifecycle hook
891+
properties:
892+
defaultResult:
893+
description: The default result for the lifecycle hook. The
894+
possible values are CONTINUE and ABANDON.
895+
enum:
896+
- CONTINUE
897+
- ABANDON
898+
type: string
899+
heartbeatTimeout:
900+
description: |-
901+
The maximum time, in seconds, that an instance can remain in a Pending:Wait or
902+
Terminating:Wait state. The maximum is 172800 seconds (48 hours) or 100 times
903+
HeartbeatTimeout, whichever is smaller.
904+
format: duration
905+
type: string
906+
lifecycleTransition:
907+
description: The state of the EC2 instance to which to attach
908+
the lifecycle hook.
909+
enum:
910+
- autoscaling:EC2_INSTANCE_LAUNCHING
911+
- autoscaling:EC2_INSTANCE_TERMINATING
912+
type: string
913+
name:
914+
description: The name of the lifecycle hook.
915+
type: string
916+
notificationMetadata:
917+
description: Contains additional metadata that will be passed
918+
to the notification target.
919+
type: string
920+
notificationTargetARN:
921+
description: |-
922+
The ARN of the notification target that Amazon EC2 Auto Scaling uses to
923+
notify you when an instance is in the transition state for the lifecycle hook.
924+
type: string
925+
roleARN:
926+
description: |-
927+
The ARN of the IAM role that allows the Auto Scaling group to publish to the
928+
specified notification target.
929+
type: string
930+
required:
931+
- lifecycleTransition
932+
- name
933+
type: object
934+
type: array
886935
maxSize:
887936
default: 1
888937
description: MaxSize defines the maximum size of the group.

config/crd/bases/infrastructure.cluster.x-k8s.io_awsmanagedmachinepools.yaml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,55 @@ spec:
890890
type: string
891891
description: Labels specifies labels for the Kubernetes node objects
892892
type: object
893+
lifecycleHooks:
894+
description: AWSLifecycleHooks specifies lifecycle hooks for the managed
895+
node group.
896+
items:
897+
description: AWSLifecycleHook describes an AWS lifecycle hook
898+
properties:
899+
defaultResult:
900+
description: The default result for the lifecycle hook. The
901+
possible values are CONTINUE and ABANDON.
902+
enum:
903+
- CONTINUE
904+
- ABANDON
905+
type: string
906+
heartbeatTimeout:
907+
description: |-
908+
The maximum time, in seconds, that an instance can remain in a Pending:Wait or
909+
Terminating:Wait state. The maximum is 172800 seconds (48 hours) or 100 times
910+
HeartbeatTimeout, whichever is smaller.
911+
format: duration
912+
type: string
913+
lifecycleTransition:
914+
description: The state of the EC2 instance to which to attach
915+
the lifecycle hook.
916+
enum:
917+
- autoscaling:EC2_INSTANCE_LAUNCHING
918+
- autoscaling:EC2_INSTANCE_TERMINATING
919+
type: string
920+
name:
921+
description: The name of the lifecycle hook.
922+
type: string
923+
notificationMetadata:
924+
description: Contains additional metadata that will be passed
925+
to the notification target.
926+
type: string
927+
notificationTargetARN:
928+
description: |-
929+
The ARN of the notification target that Amazon EC2 Auto Scaling uses to
930+
notify you when an instance is in the transition state for the lifecycle hook.
931+
type: string
932+
roleARN:
933+
description: |-
934+
The ARN of the IAM role that allows the Auto Scaling group to publish to the
935+
specified notification target.
936+
type: string
937+
required:
938+
- lifecycleTransition
939+
- name
940+
type: object
941+
type: array
893942
providerIDList:
894943
description: |-
895944
ProviderIDList are the provider IDs of instances in the

exp/api/v1beta1/conversion.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ func (src *AWSMachinePool) ConvertTo(dstRaw conversion.Hub) error {
5252
if restored.Spec.AvailabilityZoneSubnetType != nil {
5353
dst.Spec.AvailabilityZoneSubnetType = restored.Spec.AvailabilityZoneSubnetType
5454
}
55+
if restored.Spec.AWSLifecycleHooks != nil {
56+
dst.Spec.AWSLifecycleHooks = restored.Spec.AWSLifecycleHooks
57+
}
5558

5659
if restored.Spec.AWSLaunchTemplate.PrivateDNSName != nil {
5760
dst.Spec.AWSLaunchTemplate.PrivateDNSName = restored.Spec.AWSLaunchTemplate.PrivateDNSName
@@ -113,6 +116,9 @@ func (src *AWSManagedMachinePool) ConvertTo(dstRaw conversion.Hub) error {
113116
if restored.Spec.AvailabilityZoneSubnetType != nil {
114117
dst.Spec.AvailabilityZoneSubnetType = restored.Spec.AvailabilityZoneSubnetType
115118
}
119+
if restored.Spec.AWSLifecycleHooks != nil {
120+
dst.Spec.AWSLifecycleHooks = restored.Spec.AWSLifecycleHooks
121+
}
116122

117123
return nil
118124
}

exp/api/v1beta1/zz_generated.conversion.go

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

exp/api/v1beta2/awsmachinepool_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ type AWSMachinePoolSpec struct {
101101
// SuspendProcesses defines a list of processes to suspend for the given ASG. This is constantly reconciled.
102102
// If a process is removed from this list it will automatically be resumed.
103103
SuspendProcesses *SuspendProcessesTypes `json:"suspendProcesses,omitempty"`
104+
105+
// AWSLifecycleHooks specifies lifecycle hooks for the autoscaling group.
106+
// +optional
107+
AWSLifecycleHooks []AWSLifecycleHook `json:"lifecycleHooks,omitempty"`
104108
}
105109

106110
// SuspendProcessesTypes contains user friendly auto-completable values for suspended process names.

exp/api/v1beta2/awsmachinepool_webhook.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package v1beta2
1818

1919
import (
20+
"fmt"
2021
"time"
2122

2223
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -133,7 +134,6 @@ func (r *AWSMachinePool) validateAdditionalSecurityGroups() field.ErrorList {
133134
}
134135
return allErrs
135136
}
136-
137137
func (r *AWSMachinePool) validateSpotInstances() field.ErrorList {
138138
var allErrs field.ErrorList
139139
if r.Spec.AWSLaunchTemplate.SpotMarketOptions != nil && r.Spec.MixedInstancesPolicy != nil {
@@ -162,6 +162,37 @@ func (r *AWSMachinePool) validateRefreshPreferences() field.ErrorList {
162162
return allErrs
163163
}
164164

165+
func (r *AWSMachinePool) validateLifecycleHooks() field.ErrorList {
166+
return validateLifecycleHooks(r.Spec.AWSLifecycleHooks)
167+
}
168+
169+
func validateLifecycleHooks(hooks []AWSLifecycleHook) field.ErrorList {
170+
var allErrs field.ErrorList
171+
172+
for _, hook := range hooks {
173+
if hook.Name == "" {
174+
allErrs = append(allErrs, field.Required(field.NewPath("spec.lifecycleHooks.name"), "Name is required"))
175+
}
176+
if hook.NotificationTargetARN != nil && hook.RoleARN == nil {
177+
allErrs = append(allErrs, field.Required(field.NewPath("spec.lifecycleHooks.roleARN"), "RoleARN is required if NotificationTargetARN is provided"))
178+
}
179+
if hook.RoleARN != nil && hook.NotificationTargetARN == nil {
180+
allErrs = append(allErrs, field.Required(field.NewPath("spec.lifecycleHooks.notificationTargetARN"), "NotificationTargetARN is required if RoleARN is provided"))
181+
}
182+
if hook.LifecycleTransition != LifecycleTransitionInstanceLaunch && hook.LifecycleTransition != LifecycleTransitionInstanceTerminate {
183+
allErrs = append(allErrs, field.Invalid(field.NewPath("spec.lifecycleHooks.lifecycleTransition"), hook.LifecycleTransition, fmt.Sprintf("LifecycleTransition must be either %q or %q", LifecycleTransitionInstanceLaunch, LifecycleTransitionInstanceTerminate)))
184+
}
185+
if hook.DefaultResult != nil && (*hook.DefaultResult != DefaultResultContinue && *hook.DefaultResult != DefaultResultAbandon) {
186+
allErrs = append(allErrs, field.Invalid(field.NewPath("spec.lifecycleHooks.defaultResult"), *hook.DefaultResult, "DefaultResult must be either CONTINUE or ABANDON"))
187+
}
188+
if hook.HeartbeatTimeout != nil && (hook.HeartbeatTimeout.Seconds() < float64(30) || hook.HeartbeatTimeout.Seconds() > float64(172800)) {
189+
allErrs = append(allErrs, field.Invalid(field.NewPath("spec.lifecycleHooks.heartbeatTimeout"), *hook.HeartbeatTimeout, "HeartbeatTimeout must be between 30 and 172800 seconds"))
190+
}
191+
}
192+
193+
return allErrs
194+
}
195+
165196
// ValidateCreate will do any extra validation when creating a AWSMachinePool.
166197
func (r *AWSMachinePool) ValidateCreate() (admission.Warnings, error) {
167198
log.Info("AWSMachinePool validate create", "machine-pool", klog.KObj(r))
@@ -176,6 +207,7 @@ func (r *AWSMachinePool) ValidateCreate() (admission.Warnings, error) {
176207
allErrs = append(allErrs, r.validateAdditionalSecurityGroups()...)
177208
allErrs = append(allErrs, r.validateSpotInstances()...)
178209
allErrs = append(allErrs, r.validateRefreshPreferences()...)
210+
allErrs = append(allErrs, r.validateLifecycleHooks()...)
179211

180212
if len(allErrs) == 0 {
181213
return nil, nil
@@ -198,6 +230,7 @@ func (r *AWSMachinePool) ValidateUpdate(_ runtime.Object) (admission.Warnings, e
198230
allErrs = append(allErrs, r.validateAdditionalSecurityGroups()...)
199231
allErrs = append(allErrs, r.validateSpotInstances()...)
200232
allErrs = append(allErrs, r.validateRefreshPreferences()...)
233+
allErrs = append(allErrs, r.validateLifecycleHooks()...)
201234

202235
if len(allErrs) == 0 {
203236
return nil, nil

exp/api/v1beta2/awsmachinepool_webhook_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package v1beta2
1919
import (
2020
"strings"
2121
"testing"
22+
"time"
2223

2324
"github.com/aws/aws-sdk-go/aws"
2425
. "github.com/onsi/gomega"
@@ -174,6 +175,33 @@ func TestAWSMachinePoolValidateCreate(t *testing.T) {
174175
},
175176
wantErr: true,
176177
},
178+
{
179+
name: "Should fail if either roleARN or notifcationARN is set but not both",
180+
pool: &AWSMachinePool{
181+
Spec: AWSMachinePoolSpec{
182+
AWSLifecycleHooks: []AWSLifecycleHook{
183+
{
184+
RoleARN: aws.String("role-arn"),
185+
},
186+
},
187+
},
188+
},
189+
wantErr: true,
190+
},
191+
{
192+
name: "Should fail if the heartbeat timeout is less than 30 seconds",
193+
pool: &AWSMachinePool{
194+
Spec: AWSMachinePoolSpec{
195+
AWSLifecycleHooks: []AWSLifecycleHook{
196+
{
197+
RoleARN: aws.String("role-arn"),
198+
HeartbeatTimeout: &metav1.Duration{Duration: 29 * time.Second},
199+
},
200+
},
201+
},
202+
},
203+
wantErr: true,
204+
},
177205
}
178206
for _, tt := range tests {
179207
t.Run(tt.name, func(t *testing.T) {

exp/api/v1beta2/awsmanagedmachinepool_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ type AWSManagedMachinePoolSpec struct {
159159
// are prohibited (https://docs.aws.amazon.com/eks/latest/userguide/launch-templates.html).
160160
// +optional
161161
AWSLaunchTemplate *AWSLaunchTemplate `json:"awsLaunchTemplate,omitempty"`
162+
163+
// AWSLifecycleHooks specifies lifecycle hooks for the managed node group.
164+
// +optional
165+
AWSLifecycleHooks []AWSLifecycleHook `json:"lifecycleHooks,omitempty"`
162166
}
163167

164168
// ManagedMachinePoolScaling specifies scaling options.

exp/api/v1beta2/awsmanagedmachinepool_webhook.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ func (r *AWSManagedMachinePool) validateLaunchTemplate() field.ErrorList {
138138
return allErrs
139139
}
140140

141+
func (r *AWSManagedMachinePool) validateLifecycleHooks() field.ErrorList {
142+
return validateLifecycleHooks(r.Spec.AWSLifecycleHooks)
143+
}
144+
141145
// ValidateCreate will do any extra validation when creating a AWSManagedMachinePool.
142146
func (r *AWSManagedMachinePool) ValidateCreate() (admission.Warnings, error) {
143147
mmpLog.Info("AWSManagedMachinePool validate create", "managed-machine-pool", klog.KObj(r))
@@ -159,6 +163,9 @@ func (r *AWSManagedMachinePool) ValidateCreate() (admission.Warnings, error) {
159163
if errs := r.validateLaunchTemplate(); len(errs) > 0 {
160164
allErrs = append(allErrs, errs...)
161165
}
166+
if errs := r.validateLifecycleHooks(); len(errs) > 0 {
167+
allErrs = append(allErrs, errs...)
168+
}
162169

163170
allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...)
164171

@@ -196,6 +203,9 @@ func (r *AWSManagedMachinePool) ValidateUpdate(old runtime.Object) (admission.Wa
196203
if errs := r.validateLaunchTemplate(); len(errs) > 0 {
197204
allErrs = append(allErrs, errs...)
198205
}
206+
if errs := r.validateLifecycleHooks(); len(errs) > 0 {
207+
allErrs = append(allErrs, errs...)
208+
}
199209

200210
if len(allErrs) == 0 {
201211
return nil, nil

exp/api/v1beta2/conditions_consts.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ const (
5454
InstanceRefreshNotReadyReason = "InstanceRefreshNotReady"
5555
// InstanceRefreshFailedReason used to report when there instance refresh is not initiated.
5656
InstanceRefreshFailedReason = "InstanceRefreshFailed"
57+
58+
// LifecycleHookReadyCondition reports on the status of the lifecycle hook.
59+
LifecycleHookReadyCondition clusterv1.ConditionType = "LifecycleHookReady"
60+
// LifecycleHookNotFoundReason used when the lifecycle hook couldn't be retrieved.
61+
LifecycleHookNotFoundReason = "LifecycleHookNotFound"
62+
// LifecycleHookCreationFailedReason used for failures during lifecycle hook creation.
63+
LifecycleHookCreationFailedReason = "LifecycleHookCreationFailed"
64+
// LifecycleHookUpdateFailedReason used for failures during lifecycle hook update.
65+
LifecycleHookUpdateFailedReason = "LifecycleHookUpdateFailed"
66+
// LifecycleHookDeletionFailedReason used for failures during lifecycle hook deletion.
67+
LifecycleHookDeletionFailedReason = "LifecycleHookDeletionFailed"
5768
)
5869

5970
const (

0 commit comments

Comments
 (0)