Skip to content

Commit dbfa0d4

Browse files
committed
poc change with DeleteVolume call per CNS team recom
1 parent ae2af4b commit dbfa0d4

File tree

1 file changed

+175
-1
lines changed
  • pkg/providers/vsphere/virtualmachine

1 file changed

+175
-1
lines changed

pkg/providers/vsphere/virtualmachine/delete.go

Lines changed: 175 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// © Broadcom. All Rights Reserved.
2-
// The term Broadcom refers to Broadcom Inc. and/or its subsidiaries.
2+
// The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
33
// SPDX-License-Identifier: Apache-2.0
44

55
package virtualmachine
@@ -10,13 +10,17 @@ import (
1010
"path"
1111

1212
"github.com/go-logr/logr"
13+
"github.com/vmware/govmomi/cns"
14+
cnstypes "github.com/vmware/govmomi/cns/types"
1315
"github.com/vmware/govmomi/fault"
1416
"github.com/vmware/govmomi/object"
1517
"github.com/vmware/govmomi/vim25"
1618
vimtypes "github.com/vmware/govmomi/vim25/types"
19+
"github.com/vmware/govmomi/vslm"
1720

1821
pkgconst "github.com/vmware-tanzu/vm-operator/pkg/constants"
1922
pkgctx "github.com/vmware-tanzu/vm-operator/pkg/context"
23+
pkglog "github.com/vmware-tanzu/vm-operator/pkg/log"
2024
vmutil "github.com/vmware-tanzu/vm-operator/pkg/util/vsphere/vm"
2125
)
2226

@@ -26,6 +30,7 @@ var VMDeletePropertiesSelector = []string{
2630
"recentTask",
2731
"config.extraConfig",
2832
"config.files",
33+
"config.hardware.device",
2934
"summary.runtime.connectionState",
3035
}
3136

@@ -94,6 +99,18 @@ func DeleteVirtualMachine(
9499
return fmt.Errorf("destroy VM task failed: %w", err)
95100
}
96101

102+
// After Destroy(), delete any FCD-backed disks left in the VM's directory
103+
// (left behind because keepAfterDeleteVm=true) via the CNS DeleteVolume
104+
// API. This MUST run after Destroy() so the disk is no longer attached
105+
// and CNS permits the delete. Using the CNS API (not raw VSLM) keeps
106+
// CNS's internal state consistent so the subsequent PVC cleanup path
107+
// handles any duplicate delete gracefully with no VC UI error.
108+
// See deleteFCDsInVMDir for full context.
109+
deleteFCDsInVMDir(
110+
vmCtx,
111+
vcVM.Client(),
112+
vmCtx.MoVM.Config)
113+
97114
// Best-effort cleanup of VM dir after Destroy();
98115
// no error return since delete is still considered successful.
99116
if dc == nil ||
@@ -112,3 +129,160 @@ func DeleteVirtualMachine(
112129
namespacePath)
113130
return nil
114131
}
132+
133+
// deleteFCDsInVMDir finds all FCD-backed disks in the VM's own directory and
134+
// deletes them via the CNS DeleteVolume API (with deleteDisk=true) before the
135+
// VM is destroyed.
136+
//
137+
// The unmanaged volumes registration process (unmanagedvolumes_register)
138+
// registers a VM's ephemeral disks as CNS FCDs via CnsRegisterVolume, which
139+
// sets keepAfterDeleteVm=true. During VM deletion, vcVM.Destroy() skips such
140+
// disks because of this flag. CleanupVMDir() then removes the directory via
141+
// the raw FileManager API without updating the FCD catalog, leaving a stale
142+
// catalog entry pointing to a now-deleted file.
143+
//
144+
// The CNS DeleteVolume API (rather than the lower-level VSLM
145+
// DeleteVStorageObject) is used because CNS tracks the deletion in its own
146+
// internal state. When CNS's PVC cleanup path subsequently also tries to
147+
// delete the same volume, CNS recognises it is already gone and handles the
148+
// duplicate call gracefully — avoiding "not found" errors visible in the
149+
// vCenter task UI.
150+
//
151+
// Only disks whose backing file is in the VM's own directory are affected;
152+
// user-attached PVC disks in separate directories are not touched.
153+
//
154+
// Note: after CSI registers a VMDK as an FCD, the VM hardware config's
155+
// backingObjectId field is not updated. The VSLM catalog is queried directly
156+
// to find FCDs whose backing file path matches the VM's disk paths.
157+
func deleteFCDsInVMDir(
158+
ctx context.Context,
159+
vimClient *vim25.Client,
160+
config *vimtypes.VirtualMachineConfigInfo) {
161+
162+
if config == nil {
163+
return
164+
}
165+
if vimClient.ServiceContent.VStorageObjectManager == nil {
166+
return
167+
}
168+
169+
vmDir := path.Dir(config.Files.VmPathName)
170+
logger := pkglog.FromContextOrDefault(ctx)
171+
172+
logger.Info(
173+
"Scanning VM disks for FCD cleanup",
174+
"vmDir", vmDir,
175+
"numDevices", len(config.Hardware.Device))
176+
177+
// Collect the file paths of all disks in the VM's own directory, and
178+
// capture one datastore MoRef to use for VSLM queries.
179+
diskFilesInVMDir := map[string]struct{}{}
180+
var datastoreMoRef *vimtypes.ManagedObjectReference
181+
for _, dev := range config.Hardware.Device {
182+
disk, ok := dev.(*vimtypes.VirtualDisk)
183+
if !ok {
184+
continue
185+
}
186+
bi := getVirtualDiskFileBackingInfo(disk)
187+
if bi == nil || bi.FileName == "" || bi.Datastore == nil {
188+
continue
189+
}
190+
logger.Info(
191+
"Found disk in VM",
192+
"fileName", bi.FileName,
193+
"inVMDir", path.Dir(bi.FileName) == vmDir)
194+
if path.Dir(bi.FileName) != vmDir {
195+
continue
196+
}
197+
diskFilesInVMDir[bi.FileName] = struct{}{}
198+
datastoreMoRef = bi.Datastore
199+
}
200+
201+
if len(diskFilesInVMDir) == 0 || datastoreMoRef == nil {
202+
logger.Info(
203+
"No disks found in VM directory, skipping FCD cleanup",
204+
"vmDir", vmDir)
205+
return
206+
}
207+
208+
// List all FCDs on the datastore and find those whose backing file
209+
// matches a disk in the VM's directory.
210+
mgr := vslm.NewObjectManager(vimClient)
211+
ds := object.NewDatastore(vimClient, *datastoreMoRef)
212+
213+
ids, err := mgr.List(ctx, ds)
214+
if err != nil {
215+
logger.Error(err,
216+
"Failed to list FCDs on datastore for cleanup")
217+
return
218+
}
219+
220+
// Build CNS client once; used to delete matched FCDs via CNS DeleteVolume
221+
// so that CNS internal state is updated and its PVC cleanup path handles
222+
// the subsequent duplicate delete gracefully (no VC UI errors).
223+
cnsClient, err := cns.NewClient(ctx, vimClient)
224+
if err != nil {
225+
logger.Error(err, "Failed to create CNS client for FCD cleanup")
226+
return
227+
}
228+
229+
for _, id := range ids {
230+
obj, err := mgr.Retrieve(ctx, ds, id.Id)
231+
if err != nil {
232+
logger.Info(
233+
"Failed to retrieve FCD, skipping",
234+
"fcdID", id.Id,
235+
"err", err)
236+
continue
237+
}
238+
239+
// Extract the backing file path.
240+
fb, ok := obj.Config.Backing.(
241+
vimtypes.BaseBaseConfigInfoFileBackingInfo)
242+
if !ok {
243+
continue
244+
}
245+
filePath := fb.GetBaseConfigInfoFileBackingInfo().FilePath
246+
247+
if _, found := diskFilesInVMDir[filePath]; !found {
248+
continue
249+
}
250+
251+
logger.Info(
252+
"Deleting FCD disk in VM directory via CNS",
253+
"fcdID", id.Id,
254+
"diskFile", filePath)
255+
256+
task, err := cnsClient.DeleteVolume(
257+
ctx,
258+
[]cnstypes.CnsVolumeId{{Id: id.Id}},
259+
true)
260+
if err != nil {
261+
logger.Error(err,
262+
"Failed to initiate CNS volume delete",
263+
"fcdID", id.Id,
264+
"diskFile", filePath)
265+
continue
266+
}
267+
if _, err := task.WaitForResult(ctx); err != nil {
268+
logger.Error(err,
269+
"CNS volume delete task failed",
270+
"fcdID", id.Id,
271+
"diskFile", filePath)
272+
}
273+
}
274+
}
275+
276+
// getVirtualDiskFileBackingInfo returns the VirtualDeviceFileBackingInfo for
277+
// a virtual disk if the disk uses file-based backing, otherwise nil.
278+
func getVirtualDiskFileBackingInfo(
279+
disk *vimtypes.VirtualDisk) *vimtypes.VirtualDeviceFileBackingInfo {
280+
281+
type hasFileBackingInfo interface {
282+
GetVirtualDeviceFileBackingInfo() *vimtypes.VirtualDeviceFileBackingInfo
283+
}
284+
if fb, ok := disk.Backing.(hasFileBackingInfo); ok {
285+
return fb.GetVirtualDeviceFileBackingInfo()
286+
}
287+
return nil
288+
}

0 commit comments

Comments
 (0)