diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_rosamachinepools.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_rosamachinepools.yaml
index 63af6ab635..699aa25701 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_rosamachinepools.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_rosamachinepools.yaml
@@ -53,6 +53,12 @@ spec:
items:
type: string
type: array
+ additionalTags:
+ additionalProperties:
+ type: string
+ description: AdditionalTags are user-defined tags to be added on the
+ underlying EC2 instances associated with this machine pool.
+ type: object
autoRepair:
default: false
description: AutoRepair specifies whether health checks should be
@@ -83,14 +89,13 @@ spec:
description: Labels specifies labels for the Kubernetes node objects
type: object
nodeDrainGracePeriod:
- description: NodeDrainGracePeriod is grace period for how long Pod
+ description: "NodeDrainGracePeriod is grace period for how long Pod
Disruption Budget-protected workloads will be respected during upgrades.
After this grace period, any workloads protected by Pod Disruption
Budgets that have not been successfully drained from a node will
- be forcibly evicted. The nodeDrainGracePeriod can be defined in
- minutes, hours ex; 30m, 10h. The max value can be assigned is 10080m|168h
- (1 week).
- pattern: ^(([0-9])+[m|h])$
+ be forcibly evicted. \n Valid values are from 0 to 1 week(10080m|168h)
+ . 0 or empty value means that the MachinePool can be drained without
+ any time limitation."
type: string
nodePoolName:
description: NodePoolName specifies the name of the nodepool in Rosa
diff --git a/docs/book/src/crd/index.md b/docs/book/src/crd/index.md
index f543796d61..d901e3f6b2 100644
--- a/docs/book/src/crd/index.md
+++ b/docs/book/src/crd/index.md
@@ -22558,7 +22558,7 @@ Tags
-(Appears on:AWSClusterSpec, AWSMachineSpec, BuildParams, SecurityGroup, SubnetSpec, VPCSpec, AWSIAMRoleSpec, BootstrapUser, AWSIAMRoleSpec, BootstrapUser, AWSManagedControlPlaneSpec, OIDCIdentityProviderConfig, AWSManagedControlPlaneSpec, OIDCIdentityProviderConfig, RosaControlPlaneSpec, AWSMachinePoolSpec, AWSManagedMachinePoolSpec, AutoScalingGroup, FargateProfileSpec, AWSMachinePoolSpec, AWSManagedMachinePoolSpec, AutoScalingGroup, FargateProfileSpec)
+(Appears on:AWSClusterSpec, AWSMachineSpec, BuildParams, SecurityGroup, SubnetSpec, VPCSpec, AWSIAMRoleSpec, BootstrapUser, AWSIAMRoleSpec, BootstrapUser, AWSManagedControlPlaneSpec, OIDCIdentityProviderConfig, AWSManagedControlPlaneSpec, OIDCIdentityProviderConfig, RosaControlPlaneSpec, AWSMachinePoolSpec, AWSManagedMachinePoolSpec, AutoScalingGroup, FargateProfileSpec, AWSMachinePoolSpec, AWSManagedMachinePoolSpec, AutoScalingGroup, FargateProfileSpec, RosaMachinePoolSpec)
Tags defines a map of tags.
@@ -25938,6 +25938,20 @@ map[string]string
+additionalTags
+
+
+Tags
+
+
+ |
+
+(Optional)
+ AdditionalTags are user-defined tags to be added on the underlying EC2 instances associated with this machine pool.
+ |
+
+
+
autoRepair
bool
@@ -26013,6 +26027,24 @@ with all node instances of the machine pool.
ProviderIDList contain a ProviderID for each machine instance that’s currently managed by this machine pool.
|
+
+
+nodeDrainGracePeriod
+
+
+Kubernetes meta/v1.Duration
+
+
+ |
+
+(Optional)
+ NodeDrainGracePeriod is grace period for how long Pod Disruption Budget-protected workloads will be
+respected during upgrades. After this grace period, any workloads protected by Pod Disruption
+Budgets that have not been successfully drained from a node will be forcibly evicted.
+Valid values are from 0 to 1 week(10080m|168h) .
+0 or empty value means that the MachinePool can be drained without any time limitation.
+ |
+
@@ -26233,6 +26265,20 @@ map[string]string
+additionalTags
+
+
+Tags
+
+
+ |
+
+(Optional)
+ AdditionalTags are user-defined tags to be added on the underlying EC2 instances associated with this machine pool.
+ |
+
+
+
autoRepair
bool
@@ -26308,6 +26354,24 @@ with all node instances of the machine pool.
ProviderIDList contain a ProviderID for each machine instance that’s currently managed by this machine pool.
|
+
+
+nodeDrainGracePeriod
+
+
+Kubernetes meta/v1.Duration
+
+
+ |
+
+(Optional)
+ NodeDrainGracePeriod is grace period for how long Pod Disruption Budget-protected workloads will be
+respected during upgrades. After this grace period, any workloads protected by Pod Disruption
+Budgets that have not been successfully drained from a node will be forcibly evicted.
+Valid values are from 0 to 1 week(10080m|168h) .
+0 or empty value means that the MachinePool can be drained without any time limitation.
+ |
+
RosaMachinePoolStatus
diff --git a/exp/api/v1beta2/rosamachinepool_types.go b/exp/api/v1beta2/rosamachinepool_types.go
index d4e86ddb7b..8db3d5a380 100644
--- a/exp/api/v1beta2/rosamachinepool_types.go
+++ b/exp/api/v1beta2/rosamachinepool_types.go
@@ -20,6 +20,7 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
)
@@ -58,6 +59,11 @@ type RosaMachinePoolSpec struct {
// +optional
Taints []RosaTaint `json:"taints,omitempty"`
+ // AdditionalTags are user-defined tags to be added on the underlying EC2 instances associated with this machine pool.
+ // +immutable
+ // +optional
+ AdditionalTags infrav1.Tags `json:"additionalTags,omitempty"`
+
// AutoRepair specifies whether health checks should be enabled for machines
// in the NodePool. The default is false.
// +kubebuilder:default=false
@@ -92,11 +98,13 @@ type RosaMachinePoolSpec struct {
// NodeDrainGracePeriod is grace period for how long Pod Disruption Budget-protected workloads will be
// respected during upgrades. After this grace period, any workloads protected by Pod Disruption
- // Budgets that have not been successfully drained from a node will be forcibly evicted. The nodeDrainGracePeriod
- // can be defined in minutes, hours ex; 30m, 10h. The max value can be assigned is 10080m|168h (1 week).
- // +kubebuilder:validation:Pattern="^(([0-9])+[m|h])$"
+ // Budgets that have not been successfully drained from a node will be forcibly evicted.
+ //
+ // Valid values are from 0 to 1 week(10080m|168h) .
+ // 0 or empty value means that the MachinePool can be drained without any time limitation.
+ //
// +optional
- NodeDrainGracePeriod string `json:"nodeDrainGracePeriod,omitempty"`
+ NodeDrainGracePeriod *metav1.Duration `json:"nodeDrainGracePeriod,omitempty"`
}
// RosaTaint represents a taint to be applied to a node.
diff --git a/exp/api/v1beta2/rosamachinepool_webhook.go b/exp/api/v1beta2/rosamachinepool_webhook.go
index 6335d20a47..acae78576e 100644
--- a/exp/api/v1beta2/rosamachinepool_webhook.go
+++ b/exp/api/v1beta2/rosamachinepool_webhook.go
@@ -33,6 +33,12 @@ func (r *ROSAMachinePool) ValidateCreate() (warnings admission.Warnings, err err
allErrs = append(allErrs, err)
}
+ if err := r.validateNodeDrainGracePeriod(); err != nil {
+ allErrs = append(allErrs, err)
+ }
+
+ allErrs = append(allErrs, r.Spec.AdditionalTags.Validate()...)
+
if len(allErrs) == 0 {
return nil, nil
}
@@ -58,7 +64,12 @@ func (r *ROSAMachinePool) ValidateUpdate(old runtime.Object) (warnings admission
allErrs = append(allErrs, err)
}
+ if err := r.validateNodeDrainGracePeriod(); err != nil {
+ allErrs = append(allErrs, err)
+ }
+
allErrs = append(allErrs, validateImmutable(oldPool.Spec.AdditionalSecurityGroups, r.Spec.AdditionalSecurityGroups, "additionalSecurityGroups")...)
+ allErrs = append(allErrs, validateImmutable(oldPool.Spec.AdditionalTags, r.Spec.AdditionalTags, "additionalTags")...)
if len(allErrs) == 0 {
return nil, nil
@@ -88,6 +99,19 @@ func (r *ROSAMachinePool) validateVersion() *field.Error {
return nil
}
+func (r *ROSAMachinePool) validateNodeDrainGracePeriod() *field.Error {
+ if r.Spec.NodeDrainGracePeriod == nil {
+ return nil
+ }
+
+ if r.Spec.NodeDrainGracePeriod.Minutes() > 10080 {
+ return field.Invalid(field.NewPath("spec.nodeDrainGracePeriod"), r.Spec.NodeDrainGracePeriod,
+ "max supported duration is 1 week (10080m|168h)")
+ }
+
+ return nil
+}
+
func validateImmutable(old, updated interface{}, name string) field.ErrorList {
var allErrs field.ErrorList
diff --git a/exp/api/v1beta2/zz_generated.deepcopy.go b/exp/api/v1beta2/zz_generated.deepcopy.go
index 45f6a806ee..a916ebc059 100644
--- a/exp/api/v1beta2/zz_generated.deepcopy.go
+++ b/exp/api/v1beta2/zz_generated.deepcopy.go
@@ -21,6 +21,7 @@ limitations under the License.
package v1beta2
import (
+ "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
apiv1beta2 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
"sigs.k8s.io/cluster-api/api/v1beta1"
@@ -1095,6 +1096,13 @@ func (in *RosaMachinePoolSpec) DeepCopyInto(out *RosaMachinePoolSpec) {
*out = make([]RosaTaint, len(*in))
copy(*out, *in)
}
+ if in.AdditionalTags != nil {
+ in, out := &in.AdditionalTags, &out.AdditionalTags
+ *out = make(apiv1beta2.Tags, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
if in.Autoscaling != nil {
in, out := &in.Autoscaling, &out.Autoscaling
*out = new(RosaMachinePoolAutoScaling)
@@ -1115,6 +1123,11 @@ func (in *RosaMachinePoolSpec) DeepCopyInto(out *RosaMachinePoolSpec) {
*out = make([]string, len(*in))
copy(*out, *in)
}
+ if in.NodeDrainGracePeriod != nil {
+ in, out := &in.NodeDrainGracePeriod, &out.NodeDrainGracePeriod
+ *out = new(v1.Duration)
+ **out = **in
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RosaMachinePoolSpec.
diff --git a/exp/controllers/rosamachinepool_controller.go b/exp/controllers/rosamachinepool_controller.go
index b8c29d1b56..f809f83b3f 100644
--- a/exp/controllers/rosamachinepool_controller.go
+++ b/exp/controllers/rosamachinepool_controller.go
@@ -14,6 +14,7 @@ import (
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/tools/record"
"k8s.io/klog/v2"
@@ -362,6 +363,7 @@ func (r *ROSAMachinePoolReconciler) updateNodePool(machinePoolScope *scope.RosaM
// zero-out fields that shouldn't be part of the update call.
desiredSpec.Version = ""
desiredSpec.AdditionalSecurityGroups = nil
+ desiredSpec.AdditionalTags = nil
npBuilder := nodePoolBuilder(*desiredSpec, machinePoolScope.MachinePool.Spec)
nodePoolSpec, err := npBuilder.Build()
@@ -401,18 +403,6 @@ func validateMachinePoolSpec(machinePoolScope *scope.RosaMachinePoolScope) (*str
return &message, nil
}
- if machinePoolScope.RosaMachinePool.Spec.NodeDrainGracePeriod != "" {
- nodeDrainDuration, err := time.ParseDuration(machinePoolScope.RosaMachinePool.Spec.NodeDrainGracePeriod)
- if err != nil {
- return nil, err
- }
- // Check if node grace period duration is > 10080m (168h - 1 week)
- if nodeDrainDuration.Minutes() > 10080 {
- message := "Max supported NodeDrainGracePeriod duration is 10080m|168h (1 week)"
- return &message, nil
- }
- }
-
// TODO: add more input validations
return nil, nil
}
@@ -456,16 +446,17 @@ func nodePoolBuilder(rosaMachinePoolSpec expinfrav1.RosaMachinePoolSpec, machine
if rosaMachinePoolSpec.AdditionalSecurityGroups != nil {
awsNodePool = awsNodePool.AdditionalSecurityGroupIds(rosaMachinePoolSpec.AdditionalSecurityGroups...)
}
+ if rosaMachinePoolSpec.AdditionalTags != nil {
+ awsNodePool = awsNodePool.Tags(rosaMachinePoolSpec.AdditionalTags)
+ }
npBuilder.AWSNodePool(awsNodePool)
if rosaMachinePoolSpec.Version != "" {
npBuilder.Version(cmv1.NewVersion().ID(ocm.CreateVersionID(rosaMachinePoolSpec.Version, ocm.DefaultChannelGroup)))
}
- if rosaMachinePoolSpec.NodeDrainGracePeriod != "" {
- duration, _ := time.ParseDuration(rosaMachinePoolSpec.NodeDrainGracePeriod)
- valueBuilder := cmv1.NewValue()
- valueBuilder.Value(duration.Minutes()).Unit("minutes")
+ if rosaMachinePoolSpec.NodeDrainGracePeriod != nil {
+ valueBuilder := cmv1.NewValue().Value(rosaMachinePoolSpec.NodeDrainGracePeriod.Minutes()).Unit("minutes")
npBuilder.NodeDrainGracePeriod(valueBuilder)
}
@@ -479,6 +470,7 @@ func nodePoolToRosaMachinePoolSpec(nodePool *cmv1.NodePool) expinfrav1.RosaMachi
AvailabilityZone: nodePool.AvailabilityZone(),
Subnet: nodePool.Subnet(),
Labels: nodePool.Labels(),
+ AdditionalTags: nodePool.AWSNodePool().Tags(),
AutoRepair: nodePool.AutoRepair(),
InstanceType: nodePool.AWSNodePool().InstanceType(),
TuningConfigs: nodePool.TuningConfigs(),
@@ -502,6 +494,11 @@ func nodePoolToRosaMachinePoolSpec(nodePool *cmv1.NodePool) expinfrav1.RosaMachi
}
spec.Taints = rosaTaints
}
+ if nodePool.NodeDrainGracePeriod() != nil {
+ spec.NodeDrainGracePeriod = &metav1.Duration{
+ Duration: time.Minute * time.Duration(nodePool.NodeDrainGracePeriod().Value()),
+ }
+ }
return spec
}
@@ -534,7 +531,7 @@ func (r *ROSAMachinePoolReconciler) reconcileProviderIDList(ctx context.Context,
}
func buildEC2FiltersFromTags(tags map[string]string) []*ec2.Filter {
- filters := make([]*ec2.Filter, len(tags))
+ filters := make([]*ec2.Filter, len(tags)+1)
for key, value := range tags {
filters = append(filters, &ec2.Filter{
Name: ptr.To(fmt.Sprintf("tag:%s", key)),
@@ -544,6 +541,14 @@ func buildEC2FiltersFromTags(tags map[string]string) []*ec2.Filter {
})
}
+ // only list instances that are running or just started
+ filters = append(filters, &ec2.Filter{
+ Name: ptr.To("instance-state-name"),
+ Values: aws.StringSlice([]string{
+ "running", "pending",
+ }),
+ })
+
return filters
}
diff --git a/exp/controllers/rosamachinepool_controller_test.go b/exp/controllers/rosamachinepool_controller_test.go
index 5df3df6f4b..58f1963ed4 100644
--- a/exp/controllers/rosamachinepool_controller_test.go
+++ b/exp/controllers/rosamachinepool_controller_test.go
@@ -2,10 +2,13 @@ package controllers
import (
"testing"
+ "time"
. "github.com/onsi/gomega"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
+ infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2"
expclusterv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
)
@@ -20,6 +23,12 @@ func TestNodePoolToRosaMachinePoolSpec(t *testing.T) {
AutoRepair: true,
InstanceType: "m5.large",
TuningConfigs: []string{"config1"},
+ NodeDrainGracePeriod: &metav1.Duration{
+ Duration: time.Minute * 10,
+ },
+ AdditionalTags: infrav1.Tags{
+ "tag1": "value1",
+ },
}
machinePoolSpec := expclusterv1.MachinePoolSpec{