Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add traffic manager backend controller to handle delete #203

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions pkg/common/objectmeta/objectmeta.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const (
// TrafficManagerProfileFinalizer a finalizer added by the TrafficManagerProfile controller to all trafficManagerProfiles,
// to make sure that the controller can react to profile deletions if necessary.
TrafficManagerProfileFinalizer = fleetNetworkingPrefix + "traffic-manager-profile-cleanup"

// TrafficManagerBackendFinalizer a finalizer added by the TrafficManagerBackend controller to all trafficManagerBackends,
// to make sure that the controller can react to backend deletions if necessary.
TrafficManagerBackendFinalizer = fleetNetworkingPrefix + "traffic-manager-backend-cleanup"
)

// Labels
Expand Down
288 changes: 288 additions & 0 deletions pkg/controllers/hub/trafficmanagerbackend/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
/*
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
*/

// Package trafficmanagerbackend features the TrafficManagerBackend controller to reconcile TrafficManagerBackend CRs.
package trafficmanagerbackend

import (
"context"
"errors"
"fmt"
"strings"
"time"

"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/trafficmanager/armtrafficmanager"
"golang.org/x/sync/errgroup"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

"go.goms.io/fleet/pkg/utils/controller"

fleetnetv1alpha1 "go.goms.io/fleet-networking/api/v1alpha1"
"go.goms.io/fleet-networking/pkg/common/azureerrors"
"go.goms.io/fleet-networking/pkg/common/objectmeta"
"go.goms.io/fleet-networking/pkg/controllers/hub/trafficmanagerprofile"
)

const (
trafficManagerBackendProfileFieldKey = ".spec.profile.name"
trafficManagerBackendBackendFieldKey = ".spec.backend.name"

// AzureResourceEndpointNamePrefix is the prefix format of the Azure Traffic Manager Endpoint created by the fleet controller.
// The naming convention of a Traffic Manager Endpoint is fleet-{TrafficManagerBackendUUID}#.
// Using the UUID of the backend here in case to support cross namespace TrafficManagerBackend in the future.
AzureResourceEndpointNamePrefix = "fleet-%s#"

// AzureResourceEndpointNameFormat is the name format of the Azure Traffic Manager Endpoint created by the fleet controller.
// The naming convention of a Traffic Manager Endpoint is fleet-{TrafficManagerBackendUUID}#{ServiceImportName}#{ClusterName}.
// All the object name length should be restricted to <= 63 characters.
// The endpoint name must contain no more than 260 characters, excluding the following characters "< > * % $ : \ ? + /".
AzureResourceEndpointNameFormat = AzureResourceEndpointNamePrefix + "%s#%s"
)

var (
// create the func as a variable so that the integration test can use a customized function.
generateAzureTrafficManagerProfileNameFunc = func(profile *fleetnetv1alpha1.TrafficManagerProfile) string {
return trafficmanagerprofile.GenerateAzureTrafficManagerProfileName(profile)
}
generateAzureTrafficManagerEndpointNamePrefixFunc = func(backend *fleetnetv1alpha1.TrafficManagerBackend) string {
return fmt.Sprintf(AzureResourceEndpointNamePrefix, backend.UID)
}

Check warning on line 58 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L53-L58

Added lines #L53 - L58 were not covered by tests
)

// Reconciler reconciles a trafficManagerBackend object.
type Reconciler struct {
client.Client

ProfilesClient *armtrafficmanager.ProfilesClient
EndpointsClient *armtrafficmanager.EndpointsClient
ResourceGroupName string // default resource group name to create azure traffic manager resources
}

//+kubebuilder:rbac:groups=networking.fleet.azure.com,resources=trafficmanagerbackends,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=networking.fleet.azure.com,resources=trafficmanagerbackends/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=networking.fleet.azure.com,resources=trafficmanagerbackends/finalizers,verbs=get;update
//+kubebuilder:rbac:groups=networking.fleet.azure.com,resources=trafficmanagerprofiles,verbs=get;list;watch
//+kubebuilder:rbac:groups=networking.fleet.azure.com,resources=serviceimports,verbs=get;list;watch
//+kubebuilder:rbac:groups="",resources=events,verbs=create;patch

// Reconcile triggers a single reconcile round.
func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
name := req.NamespacedName
backendKRef := klog.KRef(name.Namespace, name.Name)

startTime := time.Now()
klog.V(2).InfoS("Reconciliation starts", "trafficManagerBackend", backendKRef)
defer func() {
latency := time.Since(startTime).Milliseconds()
klog.V(2).InfoS("Reconciliation ends", "trafficManagerBackend", backendKRef, "latency", latency)
}()

backend := &fleetnetv1alpha1.TrafficManagerBackend{}
if err := r.Client.Get(ctx, name, backend); err != nil {
if apierrors.IsNotFound(err) {
klog.V(4).InfoS("Ignoring NotFound trafficManagerBackend", "trafficManagerBackend", backendKRef)
return ctrl.Result{}, nil
}
klog.ErrorS(err, "Failed to get trafficManagerBackend", "trafficManagerBackend", backendKRef)
return ctrl.Result{}, controller.NewAPIServerError(true, err)

Check warning on line 96 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L95-L96

Added lines #L95 - L96 were not covered by tests
}

if !backend.ObjectMeta.DeletionTimestamp.IsZero() {
return r.handleDelete(ctx, backend)
}

// register finalizer
if !controllerutil.ContainsFinalizer(backend, objectmeta.TrafficManagerBackendFinalizer) {
controllerutil.AddFinalizer(backend, objectmeta.TrafficManagerBackendFinalizer)
if err := r.Update(ctx, backend); err != nil {
klog.ErrorS(err, "Failed to add finalizer to trafficManagerBackend", "trafficManagerBackend", backend)
return ctrl.Result{}, controller.NewUpdateIgnoreConflictError(err)
}

Check warning on line 109 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L107-L109

Added lines #L107 - L109 were not covered by tests
}
return r.handleUpdate(ctx, backend)
}

func (r *Reconciler) handleDelete(ctx context.Context, backend *fleetnetv1alpha1.TrafficManagerBackend) (ctrl.Result, error) {
backendKObj := klog.KObj(backend)
// The backend is being deleted
if !controllerutil.ContainsFinalizer(backend, objectmeta.TrafficManagerBackendFinalizer) {
klog.V(4).InfoS("TrafficManagerBackend is being deleted", "trafficManagerBackend", backendKObj)
return ctrl.Result{}, nil
}

Check warning on line 120 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L118-L120

Added lines #L118 - L120 were not covered by tests

if err := r.deleteAzureTrafficManagerEndpoints(ctx, backend); err != nil {
klog.ErrorS(err, "Failed to delete Azure Traffic Manager endpoints", "trafficManagerBackend", backendKObj)
return ctrl.Result{}, err
}

Check warning on line 125 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L123-L125

Added lines #L123 - L125 were not covered by tests

controllerutil.RemoveFinalizer(backend, objectmeta.TrafficManagerBackendFinalizer)
if err := r.Client.Update(ctx, backend); err != nil {
klog.ErrorS(err, "Failed to remove trafficManagerBackend finalizer", "trafficManagerBackend", backendKObj)
return ctrl.Result{}, controller.NewUpdateIgnoreConflictError(err)
}

Check warning on line 131 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L129-L131

Added lines #L129 - L131 were not covered by tests
klog.V(2).InfoS("Removed trafficManagerBackend finalizer", "trafficManagerBackend", backendKObj)
return ctrl.Result{}, nil
}

func (r *Reconciler) deleteAzureTrafficManagerEndpoints(ctx context.Context, backend *fleetnetv1alpha1.TrafficManagerBackend) error {
backendKObj := klog.KObj(backend)
profile := &fleetnetv1alpha1.TrafficManagerProfile{}
if err := r.Client.Get(ctx, types.NamespacedName{Name: backend.Spec.Profile.Name, Namespace: backend.Namespace}, profile); err != nil {
if apierrors.IsNotFound(err) {
klog.V(2).InfoS("NotFound trafficManagerProfile and Azure resources should be deleted ", "trafficManagerBackend", backendKObj, "trafficManagerProfile", backend.Spec.Profile.Name)
return nil
}
klog.ErrorS(err, "Failed to get trafficManagerProfile", "trafficManagerBackend", backendKObj, "trafficManagerProfile", backend.Spec.Profile.Name)
return controller.NewAPIServerError(true, err)

Check warning on line 145 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L144-L145

Added lines #L144 - L145 were not covered by tests
}

profileKObj := klog.KObj(profile)
azureProfileName := generateAzureTrafficManagerProfileNameFunc(profile)
getRes, getErr := r.ProfilesClient.Get(ctx, r.ResourceGroupName, azureProfileName, nil)
if getErr != nil {
if !azureerrors.IsNotFound(getErr) {
klog.ErrorS(getErr, "Failed to get the profile", "trafficManagerBackend", backendKObj, "trafficManagerProfile", profileKObj, "azureProfileName", azureProfileName)
return getErr
}
klog.V(2).InfoS("Azure Traffic Manager profile does not exist", "trafficManagerBackend", backendKObj, "trafficManagerProfile", profileKObj, "azureProfileName", azureProfileName)
return nil // skip handling endpoints deletion

Check warning on line 157 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L152-L157

Added lines #L152 - L157 were not covered by tests
}
if getRes.Profile.Properties == nil {
klog.V(2).InfoS("Azure Traffic Manager profile has nil properties and skipping handling endpoints deletion", "trafficManagerBackend", backendKObj, "trafficManagerProfile", profileKObj, "azureProfileName", azureProfileName)
return nil
}

Check warning on line 162 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L160-L162

Added lines #L160 - L162 were not covered by tests

klog.V(2).InfoS("Deleting Azure Traffic Manager endpoints", "trafficManagerBackend", backendKObj, "trafficManagerProfile", backend.Spec.Profile.Name)
errs, cctx := errgroup.WithContext(ctx)
for i := range getRes.Profile.Properties.Endpoints {
endpoint := getRes.Profile.Properties.Endpoints[i]
if endpoint.Name == nil {
err := controller.NewUnexpectedBehaviorError(errors.New("azure Traffic Manager endpoint name is nil"))
klog.ErrorS(err, "Invalid Traffic Manager endpoint", "azureEndpoint", endpoint)
continue

Check warning on line 171 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L169-L171

Added lines #L169 - L171 were not covered by tests
}
if !strings.HasPrefix(*endpoint.Name, generateAzureTrafficManagerEndpointNamePrefixFunc(backend)) {
continue // skipping deleting the endpoints which are not created by this backend
}
errs.Go(func() error {
if _, err := r.EndpointsClient.Delete(cctx, r.ResourceGroupName, azureProfileName, armtrafficmanager.EndpointTypeAzureEndpoints, *endpoint.Name, nil); err != nil {
if azureerrors.IsNotFound(getErr) {
klog.V(2).InfoS("Ignoring NotFound Azure Traffic Manager endpoint", "trafficManagerBackend", backendKObj, "azureProfileName", azureProfileName, "azureEndpointName", *endpoint.Name)
return nil
}
klog.ErrorS(err, "Failed to delete the endpoint", "trafficManagerBackend", backendKObj, "azureProfileName", azureProfileName, "azureEndpointName", *endpoint.Name)
return err

Check warning on line 183 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L178-L183

Added lines #L178 - L183 were not covered by tests
}
klog.V(2).InfoS("Deleted Azure Traffic Manager endpoint", "trafficManagerBackend", backendKObj, "azureProfileName", azureProfileName, "azureEndpointName", *endpoint.Name)
return nil
})
}
return errs.Wait()
}

func (r *Reconciler) handleUpdate(_ context.Context, _ *fleetnetv1alpha1.TrafficManagerBackend) (ctrl.Result, error) {
return ctrl.Result{}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
// set up an index for efficient trafficManagerBackend lookup
profileIndexerFunc := func(o client.Object) []string {
tmb, ok := o.(*fleetnetv1alpha1.TrafficManagerBackend)
if !ok {
return []string{}
}

Check warning on line 203 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L202-L203

Added lines #L202 - L203 were not covered by tests
return []string{tmb.Spec.Profile.Name}
}
if err := mgr.GetFieldIndexer().IndexField(ctx, &fleetnetv1alpha1.TrafficManagerBackend{}, trafficManagerBackendProfileFieldKey, profileIndexerFunc); err != nil {
klog.ErrorS(err, "Failed to setup profile field indexer for TrafficManagerBackend")
return err
}

Check warning on line 209 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L207-L209

Added lines #L207 - L209 were not covered by tests

backendIndexerFunc := func(o client.Object) []string {
tmb, ok := o.(*fleetnetv1alpha1.TrafficManagerBackend)
if !ok {
return []string{}
}

Check warning on line 215 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L214-L215

Added lines #L214 - L215 were not covered by tests
return []string{tmb.Spec.Backend.Name}
}
if err := mgr.GetFieldIndexer().IndexField(ctx, &fleetnetv1alpha1.TrafficManagerBackend{}, trafficManagerBackendBackendFieldKey, backendIndexerFunc); err != nil {
klog.ErrorS(err, "Failed to setup backend field indexer for TrafficManagerBackend")
return err
}

Check warning on line 221 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L219-L221

Added lines #L219 - L221 were not covered by tests

return ctrl.NewControllerManagedBy(mgr).
For(&fleetnetv1alpha1.TrafficManagerBackend{}).
Watches(
&fleetnetv1alpha1.TrafficManagerProfile{},
handler.EnqueueRequestsFromMapFunc(r.trafficManagerProfileEventHandler()),
).
Watches(
&fleetnetv1alpha1.ServiceImport{},
handler.EnqueueRequestsFromMapFunc(r.serviceImportEventHandler()),
).
Complete(r)
}

func (r *Reconciler) trafficManagerProfileEventHandler() handler.MapFunc {
return func(ctx context.Context, object client.Object) []reconcile.Request {
trafficManagerBackendList := &fleetnetv1alpha1.TrafficManagerBackendList{}
fieldMatcher := client.MatchingFields{
trafficManagerBackendProfileFieldKey: object.GetName(),
}
// For now, we only support the backend and profile in the same namespace.
if err := r.Client.List(ctx, trafficManagerBackendList, client.InNamespace(object.GetNamespace()), fieldMatcher); err != nil {
klog.ErrorS(err,
"Failed to list trafficManagerBackends for the profile",
"trafficManagerProfile", klog.KObj(object))
return []reconcile.Request{}
}

Check warning on line 248 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L244-L248

Added lines #L244 - L248 were not covered by tests

res := make([]reconcile.Request, 0, len(trafficManagerBackendList.Items))
for _, backend := range trafficManagerBackendList.Items {
res = append(res, reconcile.Request{
NamespacedName: types.NamespacedName{
Namespace: backend.Namespace,
Name: backend.Name,
},
})
}

Check warning on line 258 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L252-L258

Added lines #L252 - L258 were not covered by tests
return res
}
}

func (r *Reconciler) serviceImportEventHandler() handler.MapFunc {
return func(ctx context.Context, object client.Object) []reconcile.Request {
trafficManagerBackendList := &fleetnetv1alpha1.TrafficManagerBackendList{}
fieldMatcher := client.MatchingFields{
trafficManagerBackendBackendFieldKey: object.GetName(),
}
// ServiceImport and TrafficManagerBackend should be in the same namespace.
if err := r.Client.List(ctx, trafficManagerBackendList, client.InNamespace(object.GetNamespace()), fieldMatcher); err != nil {
klog.ErrorS(err,
"Failed to list trafficManagerBackends for the serviceImport",
"serviceImport", klog.KObj(object))
return []reconcile.Request{}
}

Check warning on line 275 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L265-L275

Added lines #L265 - L275 were not covered by tests

res := make([]reconcile.Request, 0, len(trafficManagerBackendList.Items))
for _, backend := range trafficManagerBackendList.Items {
res = append(res, reconcile.Request{
NamespacedName: types.NamespacedName{
Namespace: backend.Namespace,
Name: backend.Name,
},
})
}
return res

Check warning on line 286 in pkg/controllers/hub/trafficmanagerbackend/controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controllers/hub/trafficmanagerbackend/controller.go#L277-L286

Added lines #L277 - L286 were not covered by tests
}
}
Loading
Loading