Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,25 @@ spec:
AuditLogRoleARN defines the role that is used to forward audit logs to AWS CloudWatch.
If not set, audit log forwarding is disabled.
type: string
autoNode:
description: autoNode set the autoNode mode and roleARN.
properties:
mode:
default: Disabled
description: mode specifies the mode for the AutoNode. Setting
Enable/Disable mode will allows/disallow karpenter AutoNode
scaling.
enum:
- Enabled
- Disabled
type: string
roleARN:
description: |-
roleARN sets the autoNode role ARN, which includes the IAM policy and cluster-specific role that grant the necessary permissions to the Karpenter controller.
The role must be attached with the same OIDC-ID that is used with the ROSA-HCP cluster.
maxLength: 2048
type: string
type: object
availabilityZones:
description: |-
AvailabilityZones describe AWS AvailabilityZones of the worker nodes.
Expand Down
30 changes: 30 additions & 0 deletions controlplane/rosa/api/v1beta2/rosacontrolplane_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ const (
Nightly ChannelGroupType = "nightly"
)

// AutoNodeMode specifies the AutoNode mode for the ROSA Control Plane.
type AutoNodeMode string

const (
// AutoNodeModeEnabled enable AutoNode
AutoNodeModeEnabled AutoNodeMode = "Enabled"

// AutoNodeModeDisabled Disabled AutoNode
AutoNodeModeDisabled AutoNodeMode = "Disabled"
)

// RosaControlPlaneSpec defines the desired state of ROSAControlPlane.
type RosaControlPlaneSpec struct { //nolint: maligned
// Cluster name must be valid DNS-1035 label, so it must consist of lower case alphanumeric
Expand Down Expand Up @@ -249,6 +260,25 @@ type RosaControlPlaneSpec struct { //nolint: maligned
// ClusterRegistryConfig represents registry config used with the cluster.
// +optional
ClusterRegistryConfig *RegistryConfig `json:"clusterRegistryConfig,omitempty"`

// autoNode set the autoNode mode and roleARN.
// +optional
AutoNode *AutoNode `json:"autoNode,omitempty"`
}

// AutoNode set the AutoNode mode and AutoNode role ARN.
type AutoNode struct {
// mode specifies the mode for the AutoNode. Setting Enable/Disable mode will allows/disallow karpenter AutoNode scaling.
// +kubebuilder:validation:Enum=Enabled;Disabled
// +kubebuilder:default=Disabled
// +optional
Mode AutoNodeMode `json:"mode,omitempty"`

// roleARN sets the autoNode role ARN, which includes the IAM policy and cluster-specific role that grant the necessary permissions to the Karpenter controller.
// The role must be attached with the same OIDC-ID that is used with the ROSA-HCP cluster.
// +kubebuilder:validation:MaxLength:=2048
// +optional
RoleARN string `json:"roleARN,omitempty"`
}

// RegistryConfig for ROSA-HCP cluster
Expand Down
20 changes: 20 additions & 0 deletions controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 28 additions & 4 deletions controlplane/rosa/controllers/rosacontrolplane_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc
return ctrl.Result{}, err
}

validationMessage, err := validateControlPlaneSpec(ocmClient, rosaScope)
validationMessage, err := validateControlPlaneSpec(ocmClient, rosaScope.ControlPlane)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to validate ROSAControlPlane.spec: %w", err)
}
Expand Down Expand Up @@ -627,6 +627,18 @@ func (r *ROSAControlPlaneReconciler) updateOCMClusterSpec(rosaControlPlane *rosa
updated = true
}

if rosaControlPlane.Spec.AutoNode != nil {
if !strings.EqualFold(ocmClusterSpec.AutoNodeMode, string(rosaControlPlane.Spec.AutoNode.Mode)) {
ocmClusterSpec.AutoNodeMode = strings.ToLower(string(rosaControlPlane.Spec.AutoNode.Mode))
updated = true
}

if ocmClusterSpec.AutoNodeRoleARN != rosaControlPlane.Spec.AutoNode.RoleARN {
ocmClusterSpec.AutoNodeRoleARN = rosaControlPlane.Spec.AutoNode.RoleARN
updated = true
}
}

return ocmClusterSpec, updated
}

Expand Down Expand Up @@ -958,9 +970,9 @@ func (r *ROSAControlPlaneReconciler) reconcileClusterAdminPassword(ctx context.C
return password, nil
}

func validateControlPlaneSpec(ocmClient rosa.OCMClient, rosaScope *scope.ROSAControlPlaneScope) (string, error) {
version := rosaScope.ControlPlane.Spec.Version
channelGroup := string(rosaScope.ControlPlane.Spec.ChannelGroup)
func validateControlPlaneSpec(ocmClient rosa.OCMClient, rosaControlPlane *rosacontrolplanev1.ROSAControlPlane) (string, error) {
version := rosaControlPlane.Spec.Version
channelGroup := string(rosaControlPlane.Spec.ChannelGroup)
valid, err := ocmClient.ValidateHypershiftVersion(version, channelGroup)
if err != nil {
return "", fmt.Errorf("error validating version in this channelGroup : %w", err)
Expand All @@ -969,6 +981,12 @@ func validateControlPlaneSpec(ocmClient rosa.OCMClient, rosaScope *scope.ROSACon
return fmt.Sprintf("this version %s is not supported in this channelGroup", version), nil
}

if rosaControlPlane.Spec.AutoNode != nil {
if rosaControlPlane.Spec.AutoNode.Mode == rosacontrolplanev1.AutoNodeModeEnabled && rosaControlPlane.Spec.AutoNode.RoleARN == "" {
return "", fmt.Errorf("error ROSAControlPlane autoNode.roleARN, must be set when autoNode mode is enabled")
}
}

// TODO: add more input validations
return "", nil
}
Expand Down Expand Up @@ -1088,6 +1106,12 @@ func buildOCMClusterSpec(controlPlaneSpec rosacontrolplanev1.RosaControlPlaneSpe
}
}

// Set auto node karpenter config
if controlPlaneSpec.AutoNode != nil {
ocmClusterSpec.AutoNodeMode = strings.ToLower(string(controlPlaneSpec.AutoNode.Mode))
ocmClusterSpec.AutoNodeRoleARN = controlPlaneSpec.AutoNode.RoleARN
}

return ocmClusterSpec, nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -181,7 +182,7 @@ func TestUpdateOCMClusterSpec(t *testing.T) {
})

// Test case 6: channel group update
t.Run("Update AllowedRegistriesForImport", func(t *testing.T) {
t.Run("Update channel group", func(t *testing.T) {
rosaControlPlane := &rosacontrolplanev1.ROSAControlPlane{
Spec: rosacontrolplanev1.RosaControlPlaneSpec{
ChannelGroup: rosacontrolplanev1.Candidate,
Expand All @@ -203,6 +204,99 @@ func TestUpdateOCMClusterSpec(t *testing.T) {
g.Expect(updated).To(BeTrue())
g.Expect(ocmSpec).To(Equal(expectedOCMSpec))
})

// Test case 7: AutoNode update
t.Run("Update Auto Node", func(t *testing.T) {
rosaControlPlane := &rosacontrolplanev1.ROSAControlPlane{
Spec: rosacontrolplanev1.RosaControlPlaneSpec{
AutoNode: &rosacontrolplanev1.AutoNode{
Mode: rosacontrolplanev1.AutoNodeModeEnabled,
RoleARN: "autoNodeARN",
},
},
}

mockCluster, _ := v1.NewCluster().
AutoNode(v1.NewClusterAutoNode().Mode("disabled")).
AWS(v1.NewAWS().AutoNode(v1.NewAwsAutoNode().RoleArn("anyARN"))).
Build()

expectedOCMSpec := ocm.Spec{
AutoNodeMode: "enabled",
AutoNodeRoleARN: "autoNodeARN",
}

reconciler := &ROSAControlPlaneReconciler{}
ocmSpec, updated := reconciler.updateOCMClusterSpec(rosaControlPlane, mockCluster)

g.Expect(updated).To(BeTrue())
g.Expect(ocmSpec).To(Equal(expectedOCMSpec))
})
}

func TestValidateControlPlaneSpec(t *testing.T) {
g := NewWithT(t)

mockCtrl := gomock.NewController(t)
ocmMock := mocks.NewMockOCMClient(mockCtrl)
expect := func(m *mocks.MockOCMClientMockRecorder) {
m.ValidateHypershiftVersion(gomock.Any(), gomock.Any()).DoAndReturn(func(versionRawID string, channelGroup string) (bool, error) {
return true, nil
}).AnyTimes()
}
expect(ocmMock.EXPECT())

// Test case 1: AutoNode and Version are set valid
t.Run("AutoNode is valid.", func(t *testing.T) {
rosaControlPlane := &rosacontrolplanev1.ROSAControlPlane{
Spec: rosacontrolplanev1.RosaControlPlaneSpec{
AutoNode: &rosacontrolplanev1.AutoNode{
Mode: rosacontrolplanev1.AutoNodeModeEnabled,
RoleARN: "autoNodeARN",
},
Version: "4.19.0",
ChannelGroup: rosacontrolplanev1.Stable,
},
}
str, err := validateControlPlaneSpec(ocmMock, rosaControlPlane)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(str).To(Equal(""))
})

// Test case 2: AutoNode is enabled and AutoNode ARN is empty.
t.Run("AutoNode is enabled and AutoNode ARN is empty", func(t *testing.T) {
rosaControlPlane := &rosacontrolplanev1.ROSAControlPlane{
Spec: rosacontrolplanev1.RosaControlPlaneSpec{
AutoNode: &rosacontrolplanev1.AutoNode{
Mode: rosacontrolplanev1.AutoNodeModeEnabled,
RoleARN: "",
},
Version: "4.19.0",
ChannelGroup: rosacontrolplanev1.Stable,
},
}
str, err := validateControlPlaneSpec(ocmMock, rosaControlPlane)
g.Expect(err).To(HaveOccurred())
g.Expect(strings.Contains(err.Error(), "autoNode.roleARN, must be set when autoNode mode is enabled")).To(BeTrue())
g.Expect(str).To(Equal(""))
})

// Test case 3: AutoNode is disabled and AutoNode ARN is empty.
t.Run("AutoNode is disabled and AutoNode ARN is empty.", func(t *testing.T) {
rosaControlPlane := &rosacontrolplanev1.ROSAControlPlane{
Spec: rosacontrolplanev1.RosaControlPlaneSpec{
AutoNode: &rosacontrolplanev1.AutoNode{
Mode: rosacontrolplanev1.AutoNodeModeDisabled,
RoleARN: "",
},
Version: "4.19.0",
ChannelGroup: rosacontrolplanev1.Stable,
},
}
str, err := validateControlPlaneSpec(ocmMock, rosaControlPlane)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(str).To(Equal(""))
})
}

func TestRosaControlPlaneReconcileStatusVersion(t *testing.T) {
Expand Down
Loading