Skip to content

Commit

Permalink
Add support for joining cluster to AKS Fleet
Browse files Browse the repository at this point in the history
  • Loading branch information
willie-yao committed Jan 17, 2024
1 parent 2c47af5 commit 434d82b
Show file tree
Hide file tree
Showing 28 changed files with 1,020 additions and 3 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ WEBHOOK_ROOT ?= $(MANIFEST_ROOT)/webhook
RBAC_ROOT ?= $(MANIFEST_ROOT)/rbac
ASO_CRDS_PATH := $(MANIFEST_ROOT)/aso/crds.yaml
ASO_VERSION := v2.5.0
ASO_CRDS := resourcegroups.resources.azure.com natgateways.network.azure.com managedclusters.containerservice.azure.com managedclustersagentpools.containerservice.azure.com bastionhosts.network.azure.com virtualnetworks.network.azure.com virtualnetworkssubnets.network.azure.com privateendpoints.network.azure.com
ASO_CRDS := resourcegroups.resources.azure.com natgateways.network.azure.com managedclusters.containerservice.azure.com managedclustersagentpools.containerservice.azure.com bastionhosts.network.azure.com virtualnetworks.network.azure.com virtualnetworkssubnets.network.azure.com privateendpoints.network.azure.com fleetsmembers.containerservice.azure.com

# Allow overriding the imagePullPolicy
PULL_POLICY ?= Always
Expand Down
9 changes: 9 additions & 0 deletions api/v1beta1/azuremanagedcontrolplane_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ func (m *AzureManagedControlPlane) setDefaultSubnet() {
}
}

// setDefaultFleetsMember sets the default FleetsMember for an AzureManagedControlPlane.
func setDefaultFleetsMember(fleetsMember *FleetsMember, labels map[string]string) *FleetsMember {
result := fleetsMember.DeepCopy()
if clusterName, ok := labels[clusterv1.ClusterNameLabel]; ok && fleetsMember != nil && fleetsMember.Name == "" {
result.Name = clusterName
}
return result
}

func setDefaultSku(sku *AKSSku) *AKSSku {
result := sku.DeepCopy()
if sku == nil {
Expand Down
4 changes: 4 additions & 0 deletions api/v1beta1/azuremanagedcontrolplane_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ type AzureManagedControlPlaneSpec struct {
// Immutable.
// +optional
DNSPrefix *string `json:"dnsPrefix,omitempty"`

// FleetsMember is the spec for the fleet this cluster is a member of.
// +optional
FleetsMember *FleetsMember `json:"fleetsMember,omitempty"`
}

// HTTPProxyConfig is the HTTP proxy configuration for the cluster.
Expand Down
24 changes: 24 additions & 0 deletions api/v1beta1/azuremanagedcontrolplane_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func (mw *azureManagedControlPlaneWebhook) Default(ctx context.Context, obj runt
m.Spec.Version = setDefaultVersion(m.Spec.Version)
m.Spec.SKU = setDefaultSku(m.Spec.SKU)
m.Spec.AutoScalerProfile = setDefaultAutoScalerProfile(m.Spec.AutoScalerProfile)
m.Spec.FleetsMember = setDefaultFleetsMember(m.Spec.FleetsMember, m.Labels)

if err := m.setDefaultSSHPublicKey(); err != nil {
ctrl.Log.WithName("AzureManagedControlPlaneWebHookLogger").Error(err, "setDefaultSSHPublicKey failed")
Expand Down Expand Up @@ -260,6 +261,10 @@ func (mw *azureManagedControlPlaneWebhook) ValidateUpdate(ctx context.Context, o
allErrs = append(allErrs, errs...)
}

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

Check warning on line 266 in api/v1beta1/azuremanagedcontrolplane_webhook.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L265-L266

Added lines #L265 - L266 were not covered by tests

if len(allErrs) == 0 {
return nil, m.Validate(mw.Client)
}
Expand Down Expand Up @@ -687,6 +692,25 @@ func (m *AzureManagedControlPlane) validateOIDCIssuerProfileUpdate(old *AzureMan
return allErrs
}

// validateFleetsMember validates a FleetsMember.
func (m *AzureManagedControlPlane) validateFleetsMember(old *AzureManagedControlPlane) field.ErrorList {
var allErrs field.ErrorList

if old.Spec.FleetsMember == nil || m.Spec.FleetsMember == nil {
return allErrs
}
if old.Spec.FleetsMember.Name != "" && old.Spec.FleetsMember.Name != m.Spec.FleetsMember.Name {
allErrs = append(allErrs,
field.Forbidden(
field.NewPath("Spec", "FleetsMember", "Name"),
"Name is immutable",
),
)
}

Check warning on line 709 in api/v1beta1/azuremanagedcontrolplane_webhook.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L702-L709

Added lines #L702 - L709 were not covered by tests

return allErrs

Check warning on line 711 in api/v1beta1/azuremanagedcontrolplane_webhook.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta1/azuremanagedcontrolplane_webhook.go#L711

Added line #L711 was not covered by tests
}

func validateName(name string, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
if lName := strings.ToLower(name); strings.Contains(lName, "microsoft") ||
Expand Down
6 changes: 6 additions & 0 deletions api/v1beta1/azuremanagedcontrolplane_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ func TestDefaultingWebhook(t *testing.T) {
amcp := &AzureManagedControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: "fooName",
Labels: map[string]string{
clusterv1.ClusterNameLabel: "fooCluster",
},
},
Spec: AzureManagedControlPlaneSpec{
AzureManagedControlPlaneClassSpec: AzureManagedControlPlaneClassSpec{
Expand Down Expand Up @@ -80,6 +83,7 @@ func TestDefaultingWebhook(t *testing.T) {
Enabled: ptr.To(true),
}
amcp.Spec.DNSPrefix = ptr.To("test-prefix")
amcp.Spec.FleetsMember = &FleetsMember{}

err = mcpw.Default(context.Background(), amcp)
g.Expect(err).NotTo(HaveOccurred())
Expand All @@ -94,6 +98,8 @@ 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.FleetsMember.Name).To(Equal("fooCluster"))

t.Logf("Testing amcp defaulting webhook with overlay")
amcp = &AzureManagedControlPlane{
ObjectMeta: metav1.ObjectMeta{
Expand Down
2 changes: 2 additions & 0 deletions api/v1beta1/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ const (
NetworkInterfaceReadyCondition clusterv1.ConditionType = "NetworkInterfacesReady"
// PrivateEndpointsReadyCondition means the private endpoints exist and are ready to be used.
PrivateEndpointsReadyCondition clusterv1.ConditionType = "PrivateEndpointsReady"
// FleetReadyCondition means the Fleet exists and is ready to be used.
FleetReadyCondition clusterv1.ConditionType = "FleetReady"

// CreatingReason means the resource is being created.
CreatingReason = "Creating"
Expand Down
9 changes: 9 additions & 0 deletions api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,15 @@ type AzureBastion struct {
EnableTunneling bool `json:"enableTunneling,omitempty"`
}

// FleetsMember defines the fleets member configuration.
type FleetsMember struct {
// Name is the name of the member.
// +optional
Name string `json:"name,omitempty"`

FleetsMemberClassSpec `json:",inline"`
}

// BackendPool describes the backend pool of the load balancer.
type BackendPool struct {
// Name specifies the name of backend pool for the load balancer. If not specified, the default name will
Expand Down
17 changes: 17 additions & 0 deletions api/v1beta1/types_class.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ 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"`

// FleetsMember is the spec for the fleet this cluster is a member of.
// +optional
FleetsMember *FleetsMemberClassSpec `json:"fleetsMember,omitempty"`
}

// AzureManagedMachinePoolClassSpec defines the AzureManagedMachinePool properties that may be shared across several Azure managed machinepools.
Expand Down Expand Up @@ -448,6 +452,19 @@ type LoadBalancerClassSpec struct {
IdleTimeoutInMinutes *int32 `json:"idleTimeoutInMinutes,omitempty"`
}

// FleetsMemberClassSpec defines the FleetsMemberSpec properties that may be shared across several Azure clusters.
type FleetsMemberClassSpec struct {
// Group is the group this member belongs to for multi-cluster update management.
// +optional
Group string `json:"group,omitempty"`

// ManagerName is the name of the fleet manager.
ManagerName string `json:"managerName"`

// ManagerResourceGroup is the resource group of the fleet manager.
ManagerResourceGroup string `json:"managerResourceGroup"`
}

// SecurityGroupClass defines the SecurityGroup properties that may be shared across several Azure clusters.
type SecurityGroupClass struct {
// +optional
Expand Down
41 changes: 41 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.

5 changes: 5 additions & 0 deletions azure/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,11 @@ func ManagedClusterID(subscriptionID, resourceGroup, managedClusterName string)
return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.ContainerService/managedClusters/%s", subscriptionID, resourceGroup, managedClusterName)
}

// FleetID returns the azure resource ID for a given fleet manager.
func FleetID(subscriptionID, resourceGroup, fleetName string) string {
return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.ContainerService/fleets/%s", subscriptionID, resourceGroup, fleetName)

Check warning on line 300 in azure/defaults.go

View check run for this annotation

Codecov / codecov/patch

azure/defaults.go#L299-L300

Added lines #L299 - L300 were not covered by tests
}

// GetBootstrappingVMExtension returns the CAPZ Bootstrapping VM extension.
// The CAPZ Bootstrapping extension is a simple clone of https://github.com/Azure/custom-script-extension-linux for Linux or
// https://learn.microsoft.com/azure/virtual-machines/extensions/custom-script-windows for Windows.
Expand Down
24 changes: 24 additions & 0 deletions azure/scope/managedcontrolplane.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"strings"
"time"

asocontainerservicev1preview "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20230315preview"
asocontainerservicev1 "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20231001"
asonetworkv1api20201101 "github.com/Azure/azure-service-operator/v2/api/network/v1api20201101"
asonetworkv1api20220701 "github.com/Azure/azure-service-operator/v2/api/network/v1api20220701"
Expand All @@ -38,6 +39,7 @@ import (
"k8s.io/utils/ptr"
infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
"sigs.k8s.io/cluster-api-provider-azure/azure"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/fleetsmembers"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/groups"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/managedclusters"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/privateendpoints"
Expand Down Expand Up @@ -204,6 +206,11 @@ func (s *ManagedControlPlaneScope) AdditionalTags() infrav1.Tags {
return tags
}

// AzureFleetMembership returns the cluster AzureFleetMembership.
func (s *ManagedControlPlaneScope) AzureFleetMembership() *infrav1.FleetsMember {
return s.ControlPlane.Spec.FleetsMember

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

View check run for this annotation

Codecov / codecov/patch

azure/scope/managedcontrolplane.go#L210-L211

Added lines #L210 - L211 were not covered by tests
}

// SubscriptionID returns the Azure client Subscription ID.
func (s *ManagedControlPlaneScope) SubscriptionID() string {
return s.AzureClients.SubscriptionID()
Expand Down Expand Up @@ -281,6 +288,23 @@ func (s *ManagedControlPlaneScope) VNetSpec() azure.ASOResourceSpecGetter[*asone
}
}

// AzureFleetsMemberSpec returns the fleet spec.
func (s *ManagedControlPlaneScope) AzureFleetsMemberSpec() azure.ASOResourceSpecGetter[*asocontainerservicev1preview.FleetsMember] {
if s.AzureFleetMembership() == nil {
return nil
}
return &fleetsmembers.AzureFleetsMemberSpec{
Name: s.AzureFleetMembership().Name,
Namespace: s.Cluster.Namespace,
ClusterName: s.ClusterName(),
ClusterResourceGroup: s.ResourceGroup(),
Group: s.AzureFleetMembership().Group,
SubscriptionID: s.SubscriptionID(),
ManagerName: s.AzureFleetMembership().ManagerName,
ManagerResourceGroup: s.AzureFleetMembership().ManagerResourceGroup,
}

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

View check run for this annotation

Codecov / codecov/patch

azure/scope/managedcontrolplane.go#L292-L305

Added lines #L292 - L305 were not covered by tests
}

// ControlPlaneRouteTable returns the cluster controlplane routetable.
func (s *ManagedControlPlaneScope) ControlPlaneRouteTable() infrav1.RouteTable {
return infrav1.RouteTable{}
Expand Down
4 changes: 2 additions & 2 deletions azure/services/bastionhosts/spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,10 @@ func TestAzureBastionSpec_Parameters(t *testing.T) {
},
)

// ObjectMeta should be carried over from existing private endpoint.
// ObjectMeta should be carried over from existing bastion host.
g.Expect(result.ObjectMeta).To(Equal(resultantASOBastionHost.ObjectMeta))

// EnableTunneling addition is accepted.
// DisableCopyPaste addition is accepted.
g.Expect(result.Spec).To(Equal(resultantASOBastionHost.Spec))

// Status should be carried over.
Expand Down
52 changes: 52 additions & 0 deletions azure/services/fleetsmembers/fleetsmembers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package fleetsmembers

import (
asocontainerservicev1 "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20230315preview"
infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
"sigs.k8s.io/cluster-api-provider-azure/azure"
"sigs.k8s.io/cluster-api-provider-azure/azure/services/aso"
)

const serviceName = "fleetsmember"

// FleetsMemberScope defines the scope interface for a Fleet host service.
type FleetsMemberScope interface {
aso.Scope
AzureFleetsMemberSpec() azure.ASOResourceSpecGetter[*asocontainerservicev1.FleetsMember]
}

// Service provides operations on Azure resources.
type Service struct {
Scope FleetsMemberScope
*aso.Service[*asocontainerservicev1.FleetsMember, FleetsMemberScope]
}

// New creates a new service.
func New(scope FleetsMemberScope) *Service {
svc := aso.NewService[*asocontainerservicev1.FleetsMember, FleetsMemberScope](serviceName, scope)
spec := scope.AzureFleetsMemberSpec()
if spec != nil {
svc.Specs = []azure.ASOResourceSpecGetter[*asocontainerservicev1.FleetsMember]{spec}
}
svc.ConditionType = infrav1.FleetReadyCondition
return &Service{
Scope: scope,
Service: svc,
}

Check warning on line 51 in azure/services/fleetsmembers/fleetsmembers.go

View check run for this annotation

Codecov / codecov/patch

azure/services/fleetsmembers/fleetsmembers.go#L41-L51

Added lines #L41 - L51 were not covered by tests
}
Loading

0 comments on commit 434d82b

Please sign in to comment.