Skip to content

Commit b380e7e

Browse files
authored
Adding support for deletion when a VM is detected as expunging (#70)
* Adding support for deletion when a VM is detected as expunging or expunged * Cleaning up misses and refactoring per comments
1 parent 5a8e7ab commit b380e7e

File tree

3 files changed

+83
-2
lines changed

3 files changed

+83
-2
lines changed

pkg/cloud/client.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,12 @@ func NewClient(ccPath string) (Client, error) {
5858
return nil, errors.Wrapf(err, "error encountered while parsing [Global] section from config at path: %s", ccPath)
5959
}
6060

61+
// The client returned from NewAsyncClient works in a synchronous way. On the other hand,
62+
// a client returned from NewClient works in an asynchronous way. Dive into the constructor definition
63+
// comments for more details
6164
c.cs = cloudstack.NewAsyncClient(cfg.APIURL, cfg.APIKey, cfg.SecretKey, cfg.VerifySSL)
6265
c.csAsync = cloudstack.NewClient(cfg.APIURL, cfg.APIKey, cfg.SecretKey, cfg.VerifySSL)
66+
6367
_, err := c.cs.APIDiscovery.ListApis(c.cs.APIDiscovery.NewListApisParams())
6468
if err != nil && strings.Contains(strings.ToLower(err.Error()), "i/o timeout") {
6569
return c, errors.Wrap(err, "timeout while checking CloudStack API Client connectivity")
@@ -68,6 +72,6 @@ func NewClient(ccPath string) (Client, error) {
6872
}
6973

7074
func NewClientFromCSAPIClient(cs *cloudstack.CloudStackClient) Client {
71-
c := &client{cs: cs}
75+
c := &client{cs: cs, csAsync: cs}
7276
return c
7377
}

pkg/cloud/instance.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package cloud
1818

1919
import (
2020
"fmt"
21-
2221
"strings"
2322

2423
capiv1 "sigs.k8s.io/cluster-api/api/v1beta1"
@@ -239,5 +238,18 @@ func (c *client) DestroyVMInstance(csMachine *infrav1.CloudStackMachine) error {
239238
} else if err != nil {
240239
return err
241240
}
241+
242+
if err := c.ResolveVMInstanceDetails(csMachine); err == nil && (csMachine.Status.InstanceState == "Expunging" ||
243+
csMachine.Status.InstanceState == "Expunged") {
244+
// VM is stopped and getting expunged. So the desired state is getting satisfied. Let's move on.
245+
return nil
246+
} else if err != nil {
247+
if strings.Contains(strings.ToLower(err.Error()), "no match found") {
248+
// VM doesn't exist. So the desired state is in effect. Our work is done here.
249+
return nil
250+
}
251+
return err
252+
}
253+
242254
return errors.New("VM deletion in progress")
243255
}

pkg/cloud/instance_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,4 +325,69 @@ var _ = Describe("Instance", func() {
325325
})
326326
})
327327
})
328+
329+
Context("when destroying a VM instance", func() {
330+
expungeDestroyParams := &cloudstack.DestroyVirtualMachineParams{}
331+
expungeDestroyParams.SetExpunge(true)
332+
333+
It("calls destroy and finds VM doesn't exist, then returns nil", func() {
334+
vms.EXPECT().NewDestroyVirtualMachineParams(*dummies.CSMachine1.Spec.InstanceID).
335+
Return(expungeDestroyParams)
336+
vms.EXPECT().DestroyVirtualMachine(expungeDestroyParams).Return(nil, fmt.Errorf("unable to find uuid for id"))
337+
Ω(client.DestroyVMInstance(dummies.CSMachine1)).
338+
Should(Succeed())
339+
})
340+
341+
It("calls destroy and returns unexpected error", func() {
342+
vms.EXPECT().NewDestroyVirtualMachineParams(*dummies.CSMachine1.Spec.InstanceID).
343+
Return(expungeDestroyParams)
344+
vms.EXPECT().DestroyVirtualMachine(expungeDestroyParams).Return(nil, fmt.Errorf("new error"))
345+
Ω(client.DestroyVMInstance(dummies.CSMachine1)).Should(MatchError("new error"))
346+
})
347+
348+
It("calls destroy without error but cannot resolve VM after", func() {
349+
vms.EXPECT().NewDestroyVirtualMachineParams(*dummies.CSMachine1.Spec.InstanceID).
350+
Return(expungeDestroyParams)
351+
vms.EXPECT().DestroyVirtualMachine(expungeDestroyParams).Return(nil, nil)
352+
vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).Return(nil, -1, notFoundError)
353+
vms.EXPECT().GetVirtualMachinesMetricByName(dummies.CSMachine1.Name).Return(nil, -1, notFoundError)
354+
Ω(client.DestroyVMInstance(dummies.CSMachine1)).
355+
Should(Succeed())
356+
})
357+
358+
It("calls destroy without error and identifies it as expunging", func() {
359+
vms.EXPECT().NewDestroyVirtualMachineParams(*dummies.CSMachine1.Spec.InstanceID).
360+
Return(expungeDestroyParams)
361+
vms.EXPECT().DestroyVirtualMachine(expungeDestroyParams).Return(nil, nil)
362+
vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).
363+
Return(&cloudstack.VirtualMachinesMetric{
364+
State: "Expunging",
365+
}, 1, nil)
366+
Ω(client.DestroyVMInstance(dummies.CSMachine1)).
367+
Should(Succeed())
368+
})
369+
370+
It("calls destroy without error and identifies it as expunged", func() {
371+
vms.EXPECT().NewDestroyVirtualMachineParams(*dummies.CSMachine1.Spec.InstanceID).
372+
Return(expungeDestroyParams)
373+
vms.EXPECT().DestroyVirtualMachine(expungeDestroyParams).Return(nil, nil)
374+
vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).
375+
Return(&cloudstack.VirtualMachinesMetric{
376+
State: "Expunged",
377+
}, 1, nil)
378+
Ω(client.DestroyVMInstance(dummies.CSMachine1)).
379+
Should(Succeed())
380+
})
381+
382+
It("calls destroy without error and identifies it as stopping", func() {
383+
vms.EXPECT().NewDestroyVirtualMachineParams(*dummies.CSMachine1.Spec.InstanceID).
384+
Return(expungeDestroyParams)
385+
vms.EXPECT().DestroyVirtualMachine(expungeDestroyParams).Return(nil, nil)
386+
vms.EXPECT().GetVirtualMachinesMetricByID(*dummies.CSMachine1.Spec.InstanceID).
387+
Return(&cloudstack.VirtualMachinesMetric{
388+
State: "Stopping",
389+
}, 1, nil)
390+
Ω(client.DestroyVMInstance(dummies.CSMachine1)).Should(MatchError("VM deletion in progress"))
391+
})
392+
})
328393
})

0 commit comments

Comments
 (0)