@@ -386,6 +386,31 @@ var _ = Describe("StackitMachine Controller", func() {
386386 }).Should (BeTrue ())
387387 })
388388
389+ // AddFinalizer only mutated the object in memory, and the write to etcd
390+ // happened in the deferred PatchObject at the end of Reconcile — after
391+ // CreateServer. A process dying in between left a running server behind an
392+ // object with no finalizer to clean it up. The hook observes API server
393+ // state from inside the cloud call, so it proves the ordering rather than
394+ // only the end result.
395+ It ("persists the finalizer before creating the server" , func () {
396+ updateMachineBootstrapSecret (ctx , machineName , bootstrapName )
397+ createBootstrapSecret (ctx , bootstrapName )
398+
399+ var finalizersAtCreate []string
400+ fakeCloud .BeforeCreateServer = func () {
401+ got := & infrav1.StackitMachine {}
402+ Expect (k8sClient .Get (ctx , stackitKey , got )).To (Succeed ())
403+ finalizersAtCreate = got .Finalizers
404+ }
405+
406+ _ , err := reconciler .Reconcile (ctx , request )
407+ Expect (err ).NotTo (HaveOccurred ())
408+ Expect (fakeCloud .ServerCount ()).To (Equal (1 ))
409+
410+ Expect (finalizersAtCreate ).To (ContainElement (infrav1 .MachineFinalizer ),
411+ "the server was created while the API server had no finalizer to clean it up" )
412+ })
413+
389414 // An empty status.instanceID used to be taken as proof that no VM had ever
390415 // been created, so the finalizer went away without a single cloud call. If
391416 // CreateServer had succeeded and the status patch had not, that server kept
0 commit comments