Skip to content

Commit

Permalink
support for auto upgrade channel
Browse files Browse the repository at this point in the history
  • Loading branch information
LochanRn committed Jan 8, 2024
1 parent 1c7c802 commit 4e8da9f
Show file tree
Hide file tree
Showing 20 changed files with 819 additions and 14 deletions.
35 changes: 35 additions & 0 deletions api/v1beta1/azuremanagedcontrolplane_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,36 @@ const (
PrivateDNSZoneModeNone string = "None"
)

// UpgradeChannel determines the type of upgrade channel for automatically upgrading the cluster.
// [AKS doc]: https://learn.microsoft.com/en-us/azure/aks/auto-upgrade-cluster
type UpgradeChannel string

const (
// UpgradeChannelNodeImage automatically upgrades the node image to the latest version available.
// Consider using nodeOSUpgradeChannel instead as that allows you to configure node OS patching separate from Kubernetes version patching.
UpgradeChannelNodeImage UpgradeChannel = "node-image"

// UpgradeChannelNone disables auto-upgrades and keeps the cluster at its current version of Kubernetes.
UpgradeChannelNone UpgradeChannel = "none"

// UpgradeChannelPatch automatically upgrade the cluster to the latest supported patch version when it becomes available
// while keeping the minor version the same. For example, if a cluster is running version 1.17.7 and versions 1.17.9, 1.18.4,
// 1.18.6, and 1.19.1 are available, your cluster is upgraded to 1.17.9.
UpgradeChannelPatch UpgradeChannel = "patch"

// UpgradeChannelRapid automatically upgrade the cluster to the latest supported patch release on the latest supported minor
// version. In cases where the cluster is at a version of Kubernetes that is at an N-2 minor version where N is the latest
// supported minor version, the cluster first upgrades to the latest supported patch version on N-1 minor version. For example,
// if a cluster is running version 1.17.7 and versions 1.17.9, 1.18.4, 1.18.6, and 1.19.1 are available, your cluster first
// is upgraded to 1.18.6, then is upgraded to 1.19.1.
UpgradeChannelRapid UpgradeChannel = "rapid"

// UpgradeChannelStable automatically upgrade the cluster to the latest supported patch release on minor version N-1, where
// N is the latest supported minor version. For example, if a cluster is running version 1.17.7 and versions 1.17.9, 1.18.4,
// 1.18.6, and 1.19.1 are available, your cluster is upgraded to 1.18.6.
UpgradeChannelStable UpgradeChannel = "stable"
)

// ManagedControlPlaneOutboundType enumerates the values for the managed control plane OutboundType.
type ManagedControlPlaneOutboundType string

Expand Down Expand Up @@ -242,6 +272,11 @@ type ManagedControlPlaneSubnet struct {

// AzureManagedControlPlaneStatus defines the observed state of AzureManagedControlPlane.
type AzureManagedControlPlaneStatus struct {
// AutoUpgradeVersion is the Kubernetes version populated after autoupgrade based on the upgrade channel.
// +kubebuilder:validation:MinLength=2
// +optional
AutoUpgradeVersion string `json:"autoUpgradeVersion,omitempty"`

// Ready is true when the provider resource is ready.
// +optional
Ready bool `json:"ready,omitempty"`
Expand Down
48 changes: 46 additions & 2 deletions api/v1beta1/azuremanagedcontrolplane_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
"sigs.k8s.io/cluster-api-provider-azure/feature"
"sigs.k8s.io/cluster-api-provider-azure/util/versions"
webhookutils "sigs.k8s.io/cluster-api-provider-azure/util/webhook"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
capifeature "sigs.k8s.io/cluster-api/feature"
Expand Down Expand Up @@ -94,7 +95,6 @@ func (mw *azureManagedControlPlaneWebhook) Default(ctx context.Context, obj runt
m.setDefaultSubnet()
m.setDefaultOIDCIssuerProfile()
m.setDefaultDNSPrefix()

return nil
}

Expand Down Expand Up @@ -256,6 +256,14 @@ func (mw *azureManagedControlPlaneWebhook) ValidateUpdate(ctx context.Context, o
allErrs = append(allErrs, errs...)
}

if errs := m.validateAutoUpgradeProfile(old); len(errs) > 0 {
allErrs = append(allErrs, errs...)
}

if errs := m.validateK8sVersionUpdate(old); len(errs) > 0 {
allErrs = append(allErrs, errs...)
}

if errs := m.validateOIDCIssuerProfileUpdate(old); len(errs) > 0 {
allErrs = append(allErrs, errs...)
}
Expand Down Expand Up @@ -332,7 +340,7 @@ func (m *AzureManagedControlPlane) validateDNSPrefix(_ client.Client) field.Erro
return allErrs
}

// validateVersion disabling local accounts for AAD based clusters.
// validateDisableLocalAccounts disabling local accounts for AAD based clusters.
func (m *AzureManagedControlPlane) validateDisableLocalAccounts(_ client.Client) field.ErrorList {
if m.Spec.DisableLocalAccounts != nil && m.Spec.AADProfile == nil {
return field.ErrorList{
Expand Down Expand Up @@ -499,6 +507,42 @@ func validateManagedClusterNetwork(cli client.Client, labels map[string]string,
return allErrs
}

// validateAutoUpgradeProfile validates auto upgrade profile.
func (m *AzureManagedControlPlane) validateAutoUpgradeProfile(old *AzureManagedControlPlane) field.ErrorList {
var allErrs field.ErrorList
if old.Spec.AutoUpgradeProfile != nil {
if old.Spec.AutoUpgradeProfile.UpgradeChannel != nil && (m.Spec.AutoUpgradeProfile == nil || m.Spec.AutoUpgradeProfile.UpgradeChannel == nil) {
// Prevent AutoUpgradeProfile.UpgradeChannel to be set to nil.
// Unsetting the field is not allowed.
allErrs = append(allErrs,
field.Invalid(
field.NewPath("Spec", "AutoUpgradeProfile", "UpgradeChannel"),
old.Spec.AutoUpgradeProfile.UpgradeChannel,
"field cannot be set to nil, to disable auto upgrades set the channel to none."))
}
}
return allErrs
}

// validateK8sVersionUpdate validates K8s version.
func (m *AzureManagedControlPlane) validateK8sVersionUpdate(old *AzureManagedControlPlane) field.ErrorList {
var allErrs field.ErrorList
if hv := versions.GetHigherK8sVersion(m.Spec.Version, old.Spec.Version); hv != m.Spec.Version {
allErrs = append(allErrs, field.Invalid(field.NewPath("Spec", "Version"),
m.Spec.Version, "field version cannot be downgraded"),
)
}

if old.Status.AutoUpgradeVersion != "" && m.Spec.Version != old.Spec.Version {
if hv := versions.GetHigherK8sVersion(m.Spec.Version, old.Status.AutoUpgradeVersion); hv != m.Spec.Version {
allErrs = append(allErrs, field.Invalid(field.NewPath("Spec", "Version"),
m.Spec.Version, "version is auto-upgraded to "+old.Status.AutoUpgradeVersion+",cannot be downgraded"),
)
}
}
return allErrs
}

// validateAPIServerAccessProfileUpdate validates update to APIServerAccessProfile.
func (m *AzureManagedControlPlane) validateAPIServerAccessProfileUpdate(old *AzureManagedControlPlane) field.ErrorList {
var allErrs field.ErrorList
Expand Down
131 changes: 131 additions & 0 deletions api/v1beta1/azuremanagedcontrolplane_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ func TestDefaultingWebhook(t *testing.T) {
Enabled: ptr.To(true),
}
amcp.Spec.DNSPrefix = ptr.To("test-prefix")
amcp.Spec.AutoUpgradeProfile = &ManagedClusterAutoUpgradeProfile{
UpgradeChannel: ptr.To(UpgradeChannelPatch),
}

err = mcpw.Default(context.Background(), amcp)
g.Expect(err).NotTo(HaveOccurred())
Expand All @@ -94,6 +97,10 @@ func TestDefaultingWebhook(t *testing.T) {
g.Expect(*amcp.Spec.OIDCIssuerProfile.Enabled).To(BeTrue())
g.Expect(amcp.Spec.DNSPrefix).ToNot(BeNil())
g.Expect(*amcp.Spec.DNSPrefix).To(Equal("test-prefix"))
g.Expect(amcp.Spec.AutoUpgradeProfile).ToNot(BeNil())
g.Expect(amcp.Spec.AutoUpgradeProfile.UpgradeChannel).ToNot(BeNil())
g.Expect(*amcp.Spec.AutoUpgradeProfile.UpgradeChannel).To(Equal(UpgradeChannelPatch))

t.Logf("Testing amcp defaulting webhook with overlay")
amcp = &AzureManagedControlPlane{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -104,6 +111,9 @@ func TestDefaultingWebhook(t *testing.T) {
Location: "fooLocation",
Version: "1.17.5",
NetworkPluginMode: ptr.To(NetworkPluginModeOverlay),
AutoUpgradeProfile: &ManagedClusterAutoUpgradeProfile{
UpgradeChannel: ptr.To(UpgradeChannelRapid),
},
},
ResourceGroupName: "fooRg",
SSHPublicKey: ptr.To(""),
Expand All @@ -113,6 +123,9 @@ func TestDefaultingWebhook(t *testing.T) {
g.Expect(err).NotTo(HaveOccurred())
g.Expect(amcp.Spec.VirtualNetwork.CIDRBlock).To(Equal(defaultAKSVnetCIDRForOverlay))
g.Expect(amcp.Spec.VirtualNetwork.Subnet.CIDRBlock).To(Equal(defaultAKSNodeSubnetCIDRForOverlay))
g.Expect(amcp.Spec.AutoUpgradeProfile).ToNot(BeNil())
g.Expect(amcp.Spec.AutoUpgradeProfile.UpgradeChannel).ToNot(BeNil())
g.Expect(*amcp.Spec.AutoUpgradeProfile.UpgradeChannel).To(Equal(UpgradeChannelRapid))
}

func TestValidateVersion(t *testing.T) {
Expand Down Expand Up @@ -1567,6 +1580,124 @@ func TestAzureManagedControlPlane_ValidateUpdate(t *testing.T) {
},
wantErr: true,
},
{
name: "AzureManagedControlPlane invalid version downgrade change",
oldAMCP: &AzureManagedControlPlane{
Spec: AzureManagedControlPlaneSpec{
AzureManagedControlPlaneClassSpec: AzureManagedControlPlaneClassSpec{
Version: "v1.18.0",
},
},
},
amcp: &AzureManagedControlPlane{
Spec: AzureManagedControlPlaneSpec{
AzureManagedControlPlaneClassSpec: AzureManagedControlPlaneClassSpec{
Version: "v1.17.0",
},
},
},
wantErr: true,
},
{
name: "AzureManagedControlPlane invalid version downgrade change",
oldAMCP: &AzureManagedControlPlane{
Spec: AzureManagedControlPlaneSpec{
AzureManagedControlPlaneClassSpec: AzureManagedControlPlaneClassSpec{
Version: "v1.18.0",
},
},
Status: AzureManagedControlPlaneStatus{
AutoUpgradeVersion: "v1.18.3",
},
},
amcp: &AzureManagedControlPlane{
Spec: AzureManagedControlPlaneSpec{
AzureManagedControlPlaneClassSpec: AzureManagedControlPlaneClassSpec{
Version: "v1.18.1",
},
},
},
wantErr: true,
},
{
name: "AzureManagedControlPlane Autoupgrade cannot be set to nil",
oldAMCP: &AzureManagedControlPlane{
Spec: AzureManagedControlPlaneSpec{
AzureManagedControlPlaneClassSpec: AzureManagedControlPlaneClassSpec{
DNSServiceIP: ptr.To("192.168.0.10"),
SubscriptionID: "212ec1q8",
Version: "v1.18.0",
AutoUpgradeProfile: &ManagedClusterAutoUpgradeProfile{
UpgradeChannel: ptr.To(UpgradeChannelStable),
},
},
},
},
amcp: &AzureManagedControlPlane{
Spec: AzureManagedControlPlaneSpec{
AzureManagedControlPlaneClassSpec: AzureManagedControlPlaneClassSpec{
DNSServiceIP: ptr.To("192.168.0.10"),
SubscriptionID: "212ec1q8",
Version: "v1.18.0",
},
},
},
wantErr: true,
},
{
name: "AzureManagedControlPlane Autoupgrade cannot be set to nil",
oldAMCP: &AzureManagedControlPlane{
Spec: AzureManagedControlPlaneSpec{
AzureManagedControlPlaneClassSpec: AzureManagedControlPlaneClassSpec{
DNSServiceIP: ptr.To("192.168.0.10"),
SubscriptionID: "212ec1q8",
Version: "v1.18.0",
AutoUpgradeProfile: &ManagedClusterAutoUpgradeProfile{
UpgradeChannel: ptr.To(UpgradeChannelStable),
},
},
},
},
amcp: &AzureManagedControlPlane{
Spec: AzureManagedControlPlaneSpec{
AzureManagedControlPlaneClassSpec: AzureManagedControlPlaneClassSpec{
DNSServiceIP: ptr.To("192.168.0.10"),
SubscriptionID: "212ec1q8",
Version: "v1.18.0",
AutoUpgradeProfile: &ManagedClusterAutoUpgradeProfile{},
},
},
},
wantErr: true,
},
{
name: "AzureManagedControlPlane Autoupgrade is mutable",
oldAMCP: &AzureManagedControlPlane{
Spec: AzureManagedControlPlaneSpec{
AzureManagedControlPlaneClassSpec: AzureManagedControlPlaneClassSpec{
DNSServiceIP: ptr.To("192.168.0.10"),
SubscriptionID: "212ec1q8",
Version: "v1.18.0",
AutoUpgradeProfile: &ManagedClusterAutoUpgradeProfile{
UpgradeChannel: ptr.To(UpgradeChannelStable),
},
},
},
},
amcp: &AzureManagedControlPlane{
Spec: AzureManagedControlPlaneSpec{
AzureManagedControlPlaneClassSpec: AzureManagedControlPlaneClassSpec{
DNSServiceIP: ptr.To("192.168.0.10"),
SubscriptionID: "212ec1q8",
Version: "v1.18.0",
AutoUpgradeProfile: &ManagedClusterAutoUpgradeProfile{
UpgradeChannel: ptr.To(UpgradeChannelNone),
},
},
},
},
wantErr: false,
},
{
name: "AzureManagedControlPlane SubscriptionID is immutable",
oldAMCP: &AzureManagedControlPlane{
Expand Down
11 changes: 11 additions & 0 deletions api/v1beta1/types_class.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,17 @@ type AzureManagedControlPlaneClassSpec struct {
// DisableLocalAccounts disables getting static credentials for this cluster when set. Expected to only be used for AAD clusters.
// +optional
DisableLocalAccounts *bool `json:"disableLocalAccounts,omitempty"`
// AutoUpgradeProfile defines the auto upgrade configuration.
// +optional
AutoUpgradeProfile *ManagedClusterAutoUpgradeProfile `json:"autoUpgradeProfile,omitempty"`
}

// ManagedClusterAutoUpgradeProfile defines Auto upgrade profile for a managed cluster.
type ManagedClusterAutoUpgradeProfile struct {
// UpgradeChannel upgrade channel for auto upgrade.
// +kubebuilder:validation:Enum=node-image;none;patch;rapid;stable
// +optional
UpgradeChannel *UpgradeChannel `json:"upgradeChannel,omitempty"`
}

// AzureManagedMachinePoolClassSpec defines the AzureManagedMachinePool properties that may be shared across several Azure managed machinepools.
Expand Down
25 changes: 25 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

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

29 changes: 29 additions & 0 deletions azure/scope/managedcontrolplane.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,28 @@ func (s *ManagedControlPlaneScope) IsAADEnabled() bool {
return false
}

// SetVersionStatus sets the k8s version in status.
func (s *ManagedControlPlaneScope) SetVersionStatus(version string) {
s.ControlPlane.Status.Version = version

Check warning on line 480 in azure/scope/managedcontrolplane.go

View check run for this annotation

Codecov / codecov/patch

azure/scope/managedcontrolplane.go#L479-L480

Added lines #L479 - L480 were not covered by tests
}

// SetAutoUpgradeVersionStatus sets the auto upgrade version in status.
func (s *ManagedControlPlaneScope) SetAutoUpgradeVersionStatus(version string) {
s.ControlPlane.Status.AutoUpgradeVersion = version

Check warning on line 485 in azure/scope/managedcontrolplane.go

View check run for this annotation

Codecov / codecov/patch

azure/scope/managedcontrolplane.go#L484-L485

Added lines #L484 - L485 were not covered by tests
}

// IsManagedVersionUpgrade checks if version is auto managed by AKS.
func (s *ManagedControlPlaneScope) IsManagedVersionUpgrade() bool {
return isManagedVersionUpgrade(s.ControlPlane)

Check warning on line 490 in azure/scope/managedcontrolplane.go

View check run for this annotation

Codecov / codecov/patch

azure/scope/managedcontrolplane.go#L489-L490

Added lines #L489 - L490 were not covered by tests
}

func isManagedVersionUpgrade(managedControlPlane *infrav1.AzureManagedControlPlane) bool {
return managedControlPlane.Spec.AutoUpgradeProfile != nil &&
managedControlPlane.Spec.AutoUpgradeProfile.UpgradeChannel != nil &&
(*managedControlPlane.Spec.AutoUpgradeProfile.UpgradeChannel != infrav1.UpgradeChannelNone &&
*managedControlPlane.Spec.AutoUpgradeProfile.UpgradeChannel != infrav1.UpgradeChannelNodeImage)

Check warning on line 497 in azure/scope/managedcontrolplane.go

View check run for this annotation

Codecov / codecov/patch

azure/scope/managedcontrolplane.go#L493-L497

Added lines #L493 - L497 were not covered by tests
}

// ManagedClusterSpec returns the managed cluster spec.
func (s *ManagedControlPlaneScope) ManagedClusterSpec() azure.ASOResourceSpecGetter[*asocontainerservicev1.ManagedCluster] {
managedClusterSpec := managedclusters.ManagedClusterSpec{
Expand Down Expand Up @@ -607,6 +629,13 @@ func (s *ManagedControlPlaneScope) ManagedClusterSpec() azure.ASOResourceSpecGet
}
}

if s.ControlPlane.Spec.AutoUpgradeProfile != nil {
managedClusterSpec.AutoUpgradeProfile = &managedclusters.ManagedClusterAutoUpgradeProfile{}
if s.ControlPlane.Spec.AutoUpgradeProfile.UpgradeChannel != nil {
managedClusterSpec.AutoUpgradeProfile.UpgradeChannel = s.ControlPlane.Spec.AutoUpgradeProfile.UpgradeChannel
}
}

return &managedClusterSpec
}

Expand Down
Loading

0 comments on commit 4e8da9f

Please sign in to comment.