Skip to content
This repository was archived by the owner on Aug 12, 2025. It is now read-only.

Commit 5d20550

Browse files
authored
Merge pull request #771 from cprivitere/emlb-deletes
✨ Add EMLB deletion
2 parents 105c296 + 53f1647 commit 5d20550

File tree

4 files changed

+117
-16
lines changed

4 files changed

+117
-16
lines changed

api/v1beta1/packetcluster_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import (
2222
)
2323

2424
const (
25+
// ClusterFinalizer allows DockerClusterReconciler to clean up resources associated with DockerCluster before
26+
// removing it from the apiserver.
27+
ClusterFinalizer = "packetcluster.infrastructure.cluster.x-k8s.io"
2528
// NetworkInfrastructureReadyCondition reports of current status of cluster infrastructure.
2629
NetworkInfrastructureReadyCondition clusterv1.ConditionType = "NetworkInfrastructureReady"
2730
)

controllers/packetcluster_controller.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package controllers
2020
import (
2121
"context"
2222
"errors"
23+
"fmt"
2324

2425
apierrors "k8s.io/apimachinery/pkg/api/errors"
2526
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
@@ -31,6 +32,7 @@ import (
3132
"sigs.k8s.io/controller-runtime/pkg/builder"
3233
"sigs.k8s.io/controller-runtime/pkg/client"
3334
"sigs.k8s.io/controller-runtime/pkg/controller"
35+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3436
"sigs.k8s.io/controller-runtime/pkg/handler"
3537

3638
infrav1 "sigs.k8s.io/cluster-api-provider-packet/api/v1beta1"
@@ -102,7 +104,14 @@ func (r *PacketClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reques
102104

103105
// Handle deleted clusters
104106
if !cluster.DeletionTimestamp.IsZero() {
105-
return r.reconcileDelete(ctx, clusterScope)
107+
return ctrl.Result{}, r.reconcileDelete(ctx, clusterScope)
108+
}
109+
110+
// Add finalizer first if not set to avoid the race condition between init and delete.
111+
// Note: Finalizers in general can only be added when the deletionTimestamp is not set.
112+
if !controllerutil.ContainsFinalizer(packetcluster, infrav1.ClusterFinalizer) {
113+
controllerutil.AddFinalizer(packetcluster, infrav1.ClusterFinalizer)
114+
return ctrl.Result{}, nil
106115
}
107116

108117
err = r.reconcileNormal(ctx, clusterScope)
@@ -177,12 +186,28 @@ func (r *PacketClusterReconciler) reconcileNormal(ctx context.Context, clusterSc
177186
return nil
178187
}
179188

180-
func (r *PacketClusterReconciler) reconcileDelete(_ context.Context, _ *scope.ClusterScope) (ctrl.Result, error) {
189+
func (r *PacketClusterReconciler) reconcileDelete(ctx context.Context, clusterScope *scope.ClusterScope) error {
190+
log := ctrl.LoggerFrom(ctx).WithValues("cluster", clusterScope.Cluster.Name)
191+
log.Info("Reconciling PacketCluster Deletion")
192+
193+
packetCluster := clusterScope.PacketCluster
194+
195+
if packetCluster.Spec.VIPManager == emlb.EMLBVIPID {
196+
// Create new EMLB object
197+
lb := emlb.NewEMLB(r.PacketClient.GetConfig().DefaultHeader["X-Auth-Token"], packetCluster.Spec.ProjectID, packetCluster.Spec.Metro)
198+
199+
if err := lb.DeleteLoadBalancer(ctx, clusterScope); err != nil {
200+
return fmt.Errorf("failed to delete load balancer: %w", err)
201+
}
202+
}
181203
// Initially I created this handler to remove an elastic IP when a cluster
182204
// gets delete, but it does not sound like a good idea. It is better to
183205
// leave to the users the ability to decide if they want to keep and resign
184206
// the IP or if they do not need it anymore
185-
return ctrl.Result{}, nil
207+
208+
// Cluster is deleted so remove the finalizer.
209+
controllerutil.RemoveFinalizer(packetCluster, infrav1.ClusterFinalizer)
210+
return nil
186211
}
187212

188213
func (r *PacketClusterReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {

controllers/packetmachine_controller.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,13 @@ func (r *PacketMachineReconciler) Reconcile(ctx context.Context, req ctrl.Reques
164164
}
165165
}()
166166

167+
// Add finalizer first if not set to avoid the race condition between init and delete.
168+
// Note: Finalizers in general can only be added when the deletionTimestamp is not set.
169+
if packetmachine.ObjectMeta.DeletionTimestamp.IsZero() && !controllerutil.ContainsFinalizer(packetmachine, infrav1.MachineFinalizer) {
170+
controllerutil.AddFinalizer(packetmachine, infrav1.MachineFinalizer)
171+
return ctrl.Result{}, nil
172+
}
173+
167174
// Handle deleted machines
168175
if !packetmachine.ObjectMeta.DeletionTimestamp.IsZero() {
169176
err = r.reconcileDelete(ctx, machineScope)
@@ -262,12 +269,6 @@ func (r *PacketMachineReconciler) reconcile(ctx context.Context, machineScope *s
262269
return ctrl.Result{}, nil
263270
}
264271

265-
// If the PacketMachine doesn't have our finalizer, add it.
266-
controllerutil.AddFinalizer(packetmachine, infrav1.MachineFinalizer)
267-
if err := machineScope.PatchObject(ctx); err != nil {
268-
log.Error(err, "unable to patch object")
269-
}
270-
271272
if !machineScope.Cluster.Status.InfrastructureReady {
272273
log.Info("Cluster infrastructure is not ready yet")
273274
conditions.MarkFalse(machineScope.PacketMachine, infrav1.DeviceReadyCondition, infrav1.WaitingForClusterInfrastructureReason, clusterv1.ConditionSeverityInfo, "")
@@ -536,12 +537,21 @@ func (r *PacketMachineReconciler) reconcileDelete(ctx context.Context, machineSc
536537
device = dev
537538
}
538539

539-
// We should never get there but this is a safetly check
540+
// We should never get there but this is a safety check
540541
if device == nil {
541542
controllerutil.RemoveFinalizer(packetmachine, infrav1.MachineFinalizer)
542543
return fmt.Errorf("%w: %s", errMissingDevice, packetmachine.Name)
543544
}
544545

546+
if machineScope.PacketCluster.Spec.VIPManager == emlb.EMLBVIPID {
547+
// Create new EMLB object
548+
lb := emlb.NewEMLB(r.PacketClient.GetConfig().DefaultHeader["X-Auth-Token"], machineScope.PacketCluster.Spec.ProjectID, packetmachine.Spec.Metro)
549+
550+
if err := lb.DeleteLoadBalancerOrigin(ctx, machineScope); err != nil {
551+
return fmt.Errorf("failed to delete load balancer origin: %w", err)
552+
}
553+
}
554+
545555
apiRequest := r.PacketClient.DevicesApi.DeleteDevice(ctx, device.GetId()).ForceDelete(force)
546556
if _, err := apiRequest.Execute(); err != nil { //nolint:bodyclose // see https://github.com/timakin/bodyclose/issues/42
547557
return fmt.Errorf("failed to delete the machine: %w", err)

internal/emlb/emlb.go

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func NewEMLB(metalAPIKey, projectID, metro string) *EMLB {
9999
return manager
100100
}
101101

102-
// ReconcileLoadBalancer creates a new Equinix Metal Load Balancer.
102+
// ReconcileLoadBalancer creates a new Equinix Metal Load Balancer and associates it with the given ClusterScope.
103103
func (e *EMLB) ReconcileLoadBalancer(ctx context.Context, clusterScope *scope.ClusterScope) error {
104104
log := ctrl.LoggerFrom(ctx)
105105

@@ -156,7 +156,7 @@ func (e *EMLB) ReconcileVIPOrigin(ctx context.Context, machineScope *scope.Machi
156156
}
157157

158158
// Fetch the Load Balancer object.
159-
lb, err := e.getLoadBalancer(ctx, lbID)
159+
lb, _, err := e.getLoadBalancer(ctx, lbID)
160160
if err != nil {
161161
return err
162162
}
@@ -233,12 +233,65 @@ func (e *EMLB) ReconcileVIPOrigin(ctx context.Context, machineScope *scope.Machi
233233
return nil
234234
}
235235

236+
// DeleteLoadBalancer deletes the Equinix Metal Load Balancer associated with a given ClusterScope.
237+
func (e *EMLB) DeleteLoadBalancer(ctx context.Context, clusterScope *scope.ClusterScope) error {
238+
log := ctrl.LoggerFrom(ctx)
239+
240+
packetCluster := clusterScope.PacketCluster
241+
clusterName := packetCluster.Name
242+
243+
// Make sure the cluster already has an EMLB ID in its packetCluster annotations, otherwise abort.
244+
lbID, exists := packetCluster.Annotations[loadBalancerIDAnnotation]
245+
if !exists || (lbID == "") {
246+
log.Info("no Equinix Metal Load Balancer found in cluster's annotations, skipping EMLB delete")
247+
return nil
248+
}
249+
250+
log.Info("Deleting EMLB", "Cluster Metro", e.metro, "Cluster Name", clusterName, "Project ID", e.projectID, "Load Balancer ID", lbID)
251+
252+
resp, err := e.deleteLoadBalancer(ctx, lbID)
253+
if err != nil {
254+
if resp.StatusCode == http.StatusNotFound {
255+
return nil
256+
}
257+
log.Error(err, "LB Delete Failed", "EMLB ID", lbID, "Response Body", resp.Body)
258+
}
259+
260+
return err
261+
}
262+
263+
// DeleteLoadBalancerOrigin deletes the Equinix Metal Load Balancer associated with a given ClusterScope.
264+
func (e *EMLB) DeleteLoadBalancerOrigin(ctx context.Context, machineScope *scope.MachineScope) error {
265+
// Initially, we're creating a single pool per origin, logic below needs to be updated if we move to a shared load balancer pool model.
266+
log := ctrl.LoggerFrom(ctx)
267+
268+
clusterName := machineScope.Cluster.Name
269+
270+
// Make sure the machine has an EMLB Pool ID in its packetMachine annotations, otherwise abort.
271+
lbPoolID, exists := machineScope.PacketMachine.Annotations[loadBalancerPoolIDAnnotation]
272+
if !exists || (lbPoolID == "") {
273+
return fmt.Errorf("no Equinix Metal Load Balancer Pool found in machine's annotations")
274+
}
275+
276+
log.Info("Deleting EMLB Pool", "Cluster Metro", e.metro, "Cluster Name", clusterName, "Project ID", e.projectID, "Pool ID", lbPoolID)
277+
278+
resp, err := e.deletePool(ctx, lbPoolID)
279+
if err != nil {
280+
if resp.StatusCode != http.StatusNotFound {
281+
return nil
282+
}
283+
log.Error(err, "LB Pool Delete Failed", "Pool ID", lbPoolID, "Response Body", resp.Body)
284+
}
285+
286+
return err
287+
}
288+
236289
// getLoadBalancer Returns a Load Balancer object given an id.
237-
func (e *EMLB) getLoadBalancer(ctx context.Context, id string) (*lbaas.LoadBalancer, error) {
290+
func (e *EMLB) getLoadBalancer(ctx context.Context, id string) (*lbaas.LoadBalancer, *http.Response, error) {
238291
ctx = context.WithValue(ctx, lbaas.ContextOAuth2, e.tokenExchanger)
239292

240-
LoadBalancer, _, err := e.client.LoadBalancersApi.GetLoadBalancer(ctx, id).Execute()
241-
return LoadBalancer, err
293+
LoadBalancer, resp, err := e.client.LoadBalancersApi.GetLoadBalancer(ctx, id).Execute()
294+
return LoadBalancer, resp, err
242295
}
243296

244297
// getLoadBalancerPort Returns a Load Balancer Port object given an id.
@@ -350,7 +403,7 @@ func (e *EMLB) ensureLoadBalancer(ctx context.Context, lbID, lbname string, port
350403
}
351404

352405
// Regardless of whether we just created it, fetch the loadbalancer object.
353-
lb, err := e.getLoadBalancer(ctx, lbID)
406+
lb, _, err := e.getLoadBalancer(ctx, lbID)
354407
if err != nil {
355408
return nil, nil, err
356409
}
@@ -401,6 +454,16 @@ func (e *EMLB) createOrigin(ctx context.Context, poolID, originName string, targ
401454
return e.client.PoolsApi.CreateLoadBalancerPoolOrigin(ctx, poolID).LoadBalancerPoolOriginCreate(createOriginRequest).Execute()
402455
}
403456

457+
func (e *EMLB) deleteLoadBalancer(ctx context.Context, lbID string) (*http.Response, error) {
458+
ctx = context.WithValue(ctx, lbaas.ContextOAuth2, e.tokenExchanger)
459+
return e.client.LoadBalancersApi.DeleteLoadBalancer(ctx, lbID).Execute()
460+
}
461+
462+
func (e *EMLB) deletePool(ctx context.Context, poolID string) (*http.Response, error) {
463+
ctx = context.WithValue(ctx, lbaas.ContextOAuth2, e.tokenExchanger)
464+
return e.client.PoolsApi.DeleteLoadBalancerPool(ctx, poolID).Execute()
465+
}
466+
404467
func (e *EMLB) updateListenerPort(ctx context.Context, poolID, lbPortID string) (*lbaas.LoadBalancerPort, error) {
405468
ctx = context.WithValue(ctx, lbaas.ContextOAuth2, e.tokenExchanger)
406469

0 commit comments

Comments
 (0)