@@ -20,7 +20,9 @@ import (
20
20
"context"
21
21
"errors"
22
22
"fmt"
23
+ "time"
23
24
25
+ "github.com/google/uuid"
24
26
"github.com/lxc/incus/shared/api"
25
27
infrav1alpha1 "github.com/miscord-dev/cluster-api-provider-incus/api/v1alpha1"
26
28
"github.com/miscord-dev/cluster-api-provider-incus/pkg/incus"
@@ -55,6 +57,8 @@ type IncusMachineReconciler struct {
55
57
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=incusmachines/finalizers,verbs=update
56
58
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters,verbs=get;list;watch
57
59
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machines,verbs=get;list;watch
60
+ // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machinesets,verbs=get;list;watch
61
+ // +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch
58
62
59
63
// Reconcile is part of the main kubernetes reconciliation loop which aims to
60
64
// move the current state of the cluster closer to the desired state.
@@ -139,6 +143,13 @@ func (r *IncusMachineReconciler) Reconcile(ctx context.Context, req ctrl.Request
139
143
if err != nil {
140
144
return ctrl.Result {}, err
141
145
}
146
+ deleted := ! incusMachine .ObjectMeta .DeletionTimestamp .IsZero ()
147
+ containsFinalizer := controllerutil .ContainsFinalizer (incusMachine , infrav1alpha1 .MachineFinalizer )
148
+
149
+ if deleted && ! containsFinalizer {
150
+ // Return early since the object is being deleted and doesn't have the finalizer.
151
+ return ctrl.Result {}, nil
152
+ }
142
153
// Always attempt to Patch the IncusMachine object and status after each reconciliation.
143
154
defer func () {
144
155
if err := patchIncusMachine (ctx , patchHelper , incusMachine ); err != nil {
@@ -149,18 +160,24 @@ func (r *IncusMachineReconciler) Reconcile(ctx context.Context, req ctrl.Request
149
160
}
150
161
}()
151
162
152
- // Add finalizer first if not set to avoid the race condition between init and delete.
153
- // Note: Finalizers in general can only be added when the deletionTimestamp is not set.
154
- if incusMachine .ObjectMeta .DeletionTimestamp .IsZero () && ! controllerutil .ContainsFinalizer (incusMachine , infrav1alpha1 .MachineFinalizer ) {
155
- controllerutil .AddFinalizer (incusMachine , infrav1alpha1 .MachineFinalizer )
156
- return ctrl.Result {}, nil
157
- }
163
+ log .Info ("Reconciling IncusMachine" )
158
164
159
165
// Handle deleted machines
160
- if ! incusMachine . ObjectMeta . DeletionTimestamp . IsZero () {
166
+ if deleted {
161
167
return r .reconcileDelete (ctx , incusCluster , machine , incusMachine )
162
168
}
163
169
170
+ // Add finalizer first if not set to avoid the race condition between init and delete.
171
+ // Note: Finalizers in general can only be added when the deletionTimestamp is not set.
172
+ if ! containsFinalizer {
173
+ log .Info ("Adding finalizer for IncusMachine" )
174
+
175
+ controllerutil .AddFinalizer (incusMachine , infrav1alpha1 .MachineFinalizer )
176
+ return ctrl.Result {
177
+ Requeue : true ,
178
+ }, nil
179
+ }
180
+
164
181
// Handle non-deleted machines
165
182
return r .reconcileNormal (ctx , cluster , incusCluster , machine , incusMachine )
166
183
}
@@ -198,25 +215,43 @@ func (r *IncusMachineReconciler) reconcileDelete(ctx context.Context, _ *infrav1
198
215
// return err
199
216
// }
200
217
201
- output , err := r .IncusClient .GetInstance (ctx , incusMachine .Name )
202
- if errors .Is (err , incus .ErrorInstanceNotFound ) {
218
+ exists , err := r .IncusClient .InstanceExists (ctx , incusMachine .Name )
219
+ if err != nil {
220
+ return ctrl.Result {}, fmt .Errorf ("failed to check if instance exists: %w" , err )
221
+ }
222
+ if ! exists {
203
223
// Instance is already deleted so remove the finalizer.
224
+ log .Info ("Deleting finalizer from IncusMachine" )
204
225
controllerutil .RemoveFinalizer (incusMachine , infrav1alpha1 .MachineFinalizer )
205
226
return ctrl.Result {}, nil
206
227
}
228
+
229
+ output , err := r .IncusClient .GetInstance (ctx , incusMachine .Name )
207
230
if err != nil {
208
231
return ctrl.Result {}, fmt .Errorf ("failed to get instance: %w" , err )
209
232
}
210
233
211
234
if output .StatusCode != api .Stopped &&
212
235
output .StatusCode != api .Stopping {
236
+ log .Info ("Stopping instance" )
237
+
213
238
if err := r .IncusClient .StopInstance (ctx , incusMachine .Name ); err != nil {
214
239
log .Info ("Failed to stop instance" , "error" , err )
215
240
}
241
+
242
+ return ctrl.Result {
243
+ RequeueAfter : 5 * time .Second ,
244
+ }, nil
216
245
} else if output .StatusCode != api .OperationCreated {
246
+ log .Info ("Deleting instance" )
247
+
217
248
if err := r .IncusClient .DeleteInstance (ctx , incusMachine .Name ); err != nil {
218
249
return ctrl.Result {}, fmt .Errorf ("failed to delete instance: %w" , err )
219
250
}
251
+
252
+ return ctrl.Result {
253
+ RequeueAfter : 5 * time .Second ,
254
+ }, nil
220
255
}
221
256
222
257
return ctrl.Result {
@@ -240,9 +275,20 @@ func (r *IncusMachineReconciler) reconcileNormal(ctx context.Context, cluster *c
240
275
}
241
276
dataSecretName := * machine .Spec .Bootstrap .DataSecretName
242
277
243
- _ , err := r .IncusClient .GetInstance (ctx , incusMachine .Name )
278
+ output , err := r .IncusClient .GetInstance (ctx , incusMachine .Name )
244
279
if err == nil {
245
- return ctrl.Result {}, nil
280
+ if r .isMachineReady (output ) {
281
+ log .Info ("IncusMachine instance is ready" )
282
+
283
+ incusMachine .Spec .ProviderID = & output .ProviderID
284
+ incusMachine .Status .Ready = true
285
+
286
+ return ctrl.Result {}, nil
287
+ }
288
+
289
+ return ctrl.Result {
290
+ RequeueAfter : 10 * time .Second ,
291
+ }, nil
246
292
}
247
293
if ! errors .Is (err , incus .ErrorInstanceNotFound ) {
248
294
return ctrl.Result {}, fmt .Errorf ("failed to get instance: %w" , err )
@@ -253,20 +299,32 @@ func (r *IncusMachineReconciler) reconcileNormal(ctx context.Context, cluster *c
253
299
return ctrl.Result {}, err
254
300
}
255
301
302
+ log .Info ("Creating IncusMachine instance" )
303
+
304
+ uuid , err := uuid .NewV7 ()
305
+ if err != nil {
306
+ return ctrl.Result {}, fmt .Errorf ("failed to generate UUID: %w" , err )
307
+ }
308
+
309
+ providerID := fmt .Sprintf ("incus://%s" , uuid .String ())
310
+
256
311
// Create the instance
257
312
err = r .IncusClient .CreateInstance (ctx , incus.CreateInstanceInput {
258
313
Name : incusMachine .Name ,
259
314
BootstrapData : incus.BootstrapData {
260
315
Data : bootstrapData ,
261
316
Format : string (bootstrapFormat ),
262
317
},
318
+ ProviderID : providerID ,
263
319
InstanceSpec : incusMachine .Spec .InstanceSpec ,
264
320
})
265
321
if err != nil {
266
322
return ctrl.Result {}, fmt .Errorf ("failed to create instance: %w" , err )
267
323
}
268
324
269
- return ctrl.Result {}, nil
325
+ return ctrl.Result {
326
+ RequeueAfter : 10 * time .Second ,
327
+ }, nil
270
328
}
271
329
272
330
func (r * IncusMachineReconciler ) getBootstrapData (ctx context.Context , namespace string , dataSecretName string ) (string , bootstrapv1.Format , error ) {
@@ -289,6 +347,10 @@ func (r *IncusMachineReconciler) getBootstrapData(ctx context.Context, namespace
289
347
return string (value ), bootstrapv1 .Format (format ), nil
290
348
}
291
349
350
+ func (r * IncusMachineReconciler ) isMachineReady (output * incus.GetInstanceOutput ) bool {
351
+ return output .StatusCode == api .Running
352
+ }
353
+
292
354
// SetupWithManager sets up the controller with the Manager.
293
355
func (r * IncusMachineReconciler ) SetupWithManager (ctx context.Context , mgr ctrl.Manager ) error {
294
356
clusterToIncusMachines , err := util .ClusterToTypedObjectsMapper (mgr .GetClient (), & infrav1alpha1.IncusMachineList {}, mgr .GetScheme ())
0 commit comments