Skip to content

Commit 8ede7dc

Browse files
authored
add a disk deletion func to prevent spruious requeues (#55)
- Handle disk deletion via a separate func (similar to instance deletion) that flows through the disk state machine - Reduce spurious re-queues and remove (some) duplicate logging - Pass build info from goreleaser into main (version and commit hash) - Use the build info to construct a useragent string for the oxide sdk
1 parent 732ce9a commit 8ede7dc

7 files changed

Lines changed: 249 additions & 35 deletions

File tree

.goreleaser.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ builds:
3636
- CGO_ENABLED=0
3737
flags:
3838
- -trimpath
39+
ldflags:
40+
- -s -w
41+
- -X main.version={{.Version}}
42+
- -X main.commit={{.Commit}}
43+
mod_timestamp: '{{ .CommitTimestamp }}'
3944
goos:
4045
- linux
4146
goarch:

cmd/main.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package main
1919
import (
2020
"crypto/tls"
2121
"flag"
22+
"fmt"
2223
"os"
2324

2425
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
@@ -44,6 +45,8 @@ import (
4445
var (
4546
scheme = runtime.NewScheme()
4647
setupLog = ctrl.Log.WithName("setup")
48+
version = "dev"
49+
commit = "none"
4750
)
4851

4952
func init() {
@@ -100,7 +103,7 @@ func main() {
100103
"The name of the metrics server key file.",
101104
)
102105
flag.BoolVar(&enableHTTP2, "enable-http2", false,
103-
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
106+
"If set, HTTP/2 will be enabled for the metrics server")
104107
opts := zap.Options{
105108
Development: true,
106109
}
@@ -193,18 +196,20 @@ func main() {
193196
os.Exit(1)
194197
}
195198

199+
userAgent := fmt.Sprintf("cluster-api-provider-oxide/%s-%s", version, commit)
200+
196201
if err := (&controller.OxideMachineReconciler{
197202
Client: mgr.GetClient(),
198203
Scheme: mgr.GetScheme(),
199-
OxideClientFactory: cloud.NewOxideClient,
204+
OxideClientFactory: cloud.NewOxideClientFactory(userAgent),
200205
}).SetupWithManager(mgr); err != nil {
201206
setupLog.Error(err, "Failed to create controller", "controller", "OxideMachine")
202207
os.Exit(1)
203208
}
204209
if err := (&controller.OxideClusterReconciler{
205210
Client: mgr.GetClient(),
206211
Scheme: mgr.GetScheme(),
207-
OxideClientFactory: cloud.NewOxideClient,
212+
OxideClientFactory: cloud.NewOxideClientFactory(userAgent),
208213
}).SetupWithManager(mgr); err != nil {
209214
setupLog.Error(err, "Failed to create controller", "controller", "OxideCluster")
210215
os.Exit(1)

internal/cloud/mock/mock_client.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/cloud/oxide.go

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cloud
33
import (
44
"context"
55
"fmt"
6+
"time"
67

78
"sigs.k8s.io/controller-runtime/pkg/client"
89

@@ -37,6 +38,7 @@ type OxideClient interface {
3738
oxide.InstanceExternalIpListParams,
3839
) (*oxide.ExternalIpResultsPage, error)
3940

41+
DiskView(context.Context, oxide.DiskViewParams) (*oxide.Disk, error)
4042
DiskDelete(context.Context, oxide.DiskDeleteParams) error
4143
}
4244

@@ -45,26 +47,31 @@ const (
4547
SecretDataTokenKey = "oxide-token"
4648
)
4749

48-
// NewOxideClient constructs an oxide.Client using the secret reference from the provided
50+
// NewOxideClientFactory constructs oxide.Client instances using the secret reference from the
51+
// provided
4952
// OxideCluster.
50-
func NewOxideClient(
51-
ctx context.Context,
52-
k8sClient client.Client,
53-
oxideCluster *infrav1.OxideCluster,
54-
) (OxideClient, error) {
55-
secret := &corev1.Secret{}
56-
if err := k8sClient.Get(ctx, client.ObjectKey{
57-
Namespace: oxideCluster.Spec.CredentialsRef.Namespace,
58-
Name: oxideCluster.Spec.CredentialsRef.Name,
59-
}, secret); err != nil {
60-
return nil, fmt.Errorf("loading oxide credentials: %w", err)
53+
func NewOxideClientFactory(userAgent string) OxideClientFactory {
54+
return func(
55+
ctx context.Context,
56+
k8sClient client.Client,
57+
oxideCluster *infrav1.OxideCluster,
58+
) (OxideClient, error) {
59+
secret := &corev1.Secret{}
60+
if err := k8sClient.Get(ctx, client.ObjectKey{
61+
Namespace: oxideCluster.Spec.CredentialsRef.Namespace,
62+
Name: oxideCluster.Spec.CredentialsRef.Name,
63+
}, secret); err != nil {
64+
return nil, fmt.Errorf("loading oxide credentials: %w", err)
65+
}
66+
oxideClient, err := oxide.NewClient(
67+
oxide.WithHost(string(secret.Data[SecretDataHostKey])),
68+
oxide.WithToken(string(secret.Data[SecretDataTokenKey])),
69+
oxide.WithTimeout(30*time.Second),
70+
oxide.WithUserAgent(userAgent),
71+
)
72+
if err != nil {
73+
return nil, fmt.Errorf("constructing oxide client: %w", err)
74+
}
75+
return oxideClient, nil
6176
}
62-
oxideClient, err := oxide.NewClient(
63-
oxide.WithHost(string(secret.Data[SecretDataHostKey])),
64-
oxide.WithToken(string(secret.Data[SecretDataTokenKey])),
65-
)
66-
if err != nil {
67-
return nil, fmt.Errorf("constructing oxide client: %w", err)
68-
}
69-
return oxideClient, nil
7077
}

internal/controller/oxidecluster_controller.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,13 @@ func (r *OxideClusterReconciler) Reconcile(
118118
}
119119
controllerutil.RemoveFinalizer(oxideCluster, infrav1.ClusterFinalizer)
120120
return ctrl.Result{}, retErr
121+
} else if !cluster.DeletionTimestamp.IsZero() {
122+
log.Info(
123+
"cluster is being deleted, aborting OxideCluster reconcile",
124+
"cluster",
125+
cluster.Name,
126+
)
127+
return ctrl.Result{}, nil
121128
}
122129

123130
controllerutil.AddFinalizer(oxideCluster, infrav1.ClusterFinalizer)
@@ -203,17 +210,35 @@ func (r *OxideClusterReconciler) Reconcile(
203210
// Attach the floating IP to the 0th provisioned instance.
204211
for _, machine := range machines.Items {
205212
if machine.Spec.ProviderID == "" {
213+
log.Info(
214+
"skipping floating IP attachment to machine due to missing providerID",
215+
"machine",
216+
machine.Name,
217+
)
206218
continue
207219
}
208220
provisioned := machine.Status.Initialization.Provisioned
209221
if provisioned == nil || !*provisioned {
222+
log.Info(
223+
"skipping floating IP attachment to machine since it is not provisioned",
224+
"machine",
225+
machine.Name,
226+
)
227+
continue
228+
}
229+
if !machine.DeletionTimestamp.IsZero() {
230+
log.Info(
231+
"skipping floating IP attachment to machine since it is being deleted",
232+
"machine",
233+
machine.Name,
234+
)
210235
continue
211236
}
212237
instanceID, err := cloud.InstanceIDFromProviderID(machine.Spec.ProviderID)
213238
if err != nil {
214239
return ctrl.Result{}, fmt.Errorf("parsing provider id: %w", err)
215240
}
216-
log.Info("attaching floating ip", "ip", ip.Ip, "instance", instanceID)
241+
log.Info("attaching floating IP", "ip", ip.Ip, "instance", instanceID)
217242
ip, err = oxideClient.FloatingIpAttach(ctx, oxide.FloatingIpAttachParams{
218243
FloatingIp: oxide.NameOrId(ip.Id),
219244
Body: &oxide.FloatingIpAttach{

internal/controller/oxidemachine_controller.go

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,10 @@ func (r *OxideMachineReconciler) Reconcile(
247247
Status: metav1.ConditionTrue,
248248
Reason: getReadyReason(instance),
249249
})
250+
if oxideMachine.Status.Initialization.Provisioned == nil ||
251+
!*oxideMachine.Status.Initialization.Provisioned {
252+
log.Info("instance is running; marking as provisioned", "instance", instance.Name)
253+
}
250254
oxideMachine.Status.Initialization.Provisioned = new(true)
251255

252256
// Look up instance addresses if not already known. As of this writing, the controller isn't
@@ -311,7 +315,6 @@ func (r *OxideMachineReconciler) ensureInstanceRunning(
311315
log.Info("waiting for instance to start", "state", instance.RunState)
312316
return false, instance, nil
313317
case oxide.InstanceStateRunning:
314-
log.Info("instance is running; marking as provisioned", "instance", instance.Name)
315318
return true, instance, nil
316319
default:
317320
log.Info("waiting for instance", "instance", instance.Id, "state", instance.RunState)
@@ -329,8 +332,6 @@ func (r *OxideMachineReconciler) handleDelete(
329332
instanceName string,
330333
diskName string,
331334
) (ctrl.Result, error) {
332-
log := logf.FromContext(ctx)
333-
334335
instanceDeleted, instance, err := r.ensureInstanceDeleted(
335336
ctx,
336337
oxideClient,
@@ -353,14 +354,17 @@ func (r *OxideMachineReconciler) handleDelete(
353354
// assume the disk isn't attached. If the disk was attached to another instance out of band, or
354355
// is otherwise in an unexpected state, the reconciler isn't responsible for detaching it, and
355356
// returns an error.
356-
log.Info("deleting disk", "disk", diskName)
357-
if err := oxideClient.DiskDelete(ctx, oxide.DiskDeleteParams{
358-
Project: oxide.NameOrId(projectName),
359-
Disk: oxide.NameOrId(diskName),
360-
}); err != nil {
361-
if !errors.Is(err, oxide.ErrObjectNotFound) {
362-
return ctrl.Result{}, fmt.Errorf("deleting disk: %w", err)
363-
}
357+
diskDeleted, err := r.ensureDiskDeleted(
358+
ctx,
359+
oxideClient,
360+
projectName,
361+
diskName,
362+
)
363+
if err != nil {
364+
return ctrl.Result{}, fmt.Errorf("ensuring disk deleted: %w", err)
365+
}
366+
if !diskDeleted {
367+
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
364368
}
365369

366370
controllerutil.RemoveFinalizer(oxideMachine, infrav1.MachineFinalizer)
@@ -399,6 +403,9 @@ func (r *OxideMachineReconciler) ensureInstanceDeleted(
399403
Instance: oxide.NameOrId(instanceName),
400404
})
401405
if err != nil {
406+
if errors.Is(err, oxide.ErrObjectNotFound) {
407+
return true, nil, nil
408+
}
402409
return false, instance, fmt.Errorf("stopping instance: %w", err)
403410
}
404411
return false, instance, nil
@@ -408,8 +415,13 @@ func (r *OxideMachineReconciler) ensureInstanceDeleted(
408415
Project: oxide.NameOrId(projectName),
409416
Instance: oxide.NameOrId(instanceName),
410417
}); err != nil {
418+
if errors.Is(err, oxide.ErrObjectNotFound) {
419+
return true, nil, nil
420+
}
411421
return false, instance, fmt.Errorf("deleting instance: %w", err)
412422
}
423+
// return false here since we need to wait for the instance to be deleted from the control
424+
// plane or reach a terminal state
413425
return false, nil, nil
414426
default:
415427
log.Info(
@@ -423,6 +435,56 @@ func (r *OxideMachineReconciler) ensureInstanceDeleted(
423435
}
424436
}
425437

438+
func (r *OxideMachineReconciler) ensureDiskDeleted(
439+
ctx context.Context,
440+
oxideClient cloud.OxideClient,
441+
projectName string,
442+
diskName string,
443+
) (bool, error) {
444+
log := logf.FromContext(ctx)
445+
446+
// View the disk. If it doesn't exist, we're done.
447+
disk, err := oxideClient.DiskView(ctx, oxide.DiskViewParams{
448+
Project: oxide.NameOrId(projectName),
449+
Disk: oxide.NameOrId(diskName),
450+
})
451+
if err != nil {
452+
if errors.Is(err, oxide.ErrObjectNotFound) {
453+
return true, nil
454+
}
455+
return false, fmt.Errorf("viewing disk: %w", err)
456+
}
457+
458+
// Disk deletion state machine:
459+
// * If detached, delete
460+
// * Else log and requeue.
461+
switch disk.State.State() {
462+
case oxide.DiskStateStateDetached, oxide.DiskStateStateFaulted:
463+
log.Info("destroying disk", "disk", disk.Id)
464+
err := oxideClient.DiskDelete(ctx, oxide.DiskDeleteParams{
465+
Project: oxide.NameOrId(projectName),
466+
Disk: oxide.NameOrId(diskName),
467+
})
468+
if err != nil {
469+
if errors.Is(err, oxide.ErrObjectNotFound) {
470+
return true, nil
471+
}
472+
return false, fmt.Errorf("destroying disk: %w", err)
473+
}
474+
log.Info("destroyed disk", "disk", disk.Id)
475+
return true, nil
476+
default:
477+
log.Info(
478+
"waiting for disk; requeueing",
479+
"disk",
480+
disk.Id,
481+
"state",
482+
disk.State.State(),
483+
)
484+
return false, nil
485+
}
486+
}
487+
426488
// SetupWithManager sets up the controller with the Manager.
427489
func (r *OxideMachineReconciler) SetupWithManager(mgr ctrl.Manager) error {
428490
return ctrl.NewControllerManagedBy(mgr).

0 commit comments

Comments
 (0)