From b5e93675c4aa2b04fd265a34a4a26a829d8080a4 Mon Sep 17 00:00:00 2001 From: Ryan Johnson Date: Fri, 10 Jul 2026 12:33:59 -0400 Subject: [PATCH] refactor: share multi-disk sdrs placement helper Move shared Storage DRS datastore selection logic into `common.ResolveMultiDiskDatastoreRefs` and use it from both clone and ISO create flows. This keeps multi-disk placement behavior consistent while preserving the existing fallback to the primary datastore when recommendations fail. Signed-off-by: Ryan Johnson --- builder/vsphere/clone/step_clone.go | 37 +----------- .../vsphere/common/step_resolve_datastore.go | 57 +++++++++++++++++++ builder/vsphere/iso/step_create.go | 38 +------------ 3 files changed, 63 insertions(+), 69 deletions(-) diff --git a/builder/vsphere/clone/step_clone.go b/builder/vsphere/clone/step_clone.go index 9c21a87ad..cda2c279c 100644 --- a/builder/vsphere/clone/step_clone.go +++ b/builder/vsphere/clone/step_clone.go @@ -20,7 +20,6 @@ import ( "github.com/hashicorp/packer-plugin-sdk/multistep" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" "github.com/hashicorp/packer-plugin-sdk/packerbuilderdata" - "github.com/vmware/govmomi/vim25/types" "github.com/vmware/packer-plugin-vsphere/builder/vsphere/common" "github.com/vmware/packer-plugin-vsphere/builder/vsphere/driver" ) @@ -341,39 +340,9 @@ func (s *StepCloneVM) cloneFromTemplate(ctx context.Context, state multistep.Sta } // Handle multi-disk placement when using a datastore cluster. - var datastoreRefs []*types.ManagedObjectReference - if s.Location.DatastoreCluster != "" && len(disks) > 1 { - if dsSelector, ok := d.(driver.DatastoreSelector); ok { - // Request Storage DRS recommendations for all disks at once for optimal placement. - ui.Sayf("Requesting Storage DRS recommendations for %d disks...", len(disks)) - - diskDatastores, method, err := dsSelector.SelectDatastoresForDisks(s.Location.DatastoreCluster, disks) - if err != nil { - ui.Errorf("Warning: Failed to get Storage DRS recommendations: %s. Using primary datastore.", err) - if primaryDatastore != nil { - for i := 0; i < len(disks); i++ { - ref := primaryDatastore.Reference() - datastoreRefs = append(datastoreRefs, &ref) - } - } - } else { - // Use the first disk's datastore as the primary datastore. - if len(diskDatastores) > 0 { - datastoreName = diskDatastores[0].Name() - } - - for i, ds := range diskDatastores { - ref := ds.Reference() - if method == driver.SelectionMethodDRS { - log.Printf("[INFO] Disk %d: Storage DRS selected datastore '%s'", i+1, ds.Name()) - } else { - log.Printf("[INFO] Disk %d: Using first available datastore '%s'", i+1, ds.Name()) - } - datastoreRefs = append(datastoreRefs, &ref) - } - } - } - } + datastoreName, datastoreRefs := common.ResolveMultiDiskDatastoreRefs( + ui, d, s.Location.DatastoreCluster, disks, primaryDatastore, datastoreName, + ) vm, err := template.Clone(ctx, &driver.CloneConfig{ Name: s.Location.VMName, diff --git a/builder/vsphere/common/step_resolve_datastore.go b/builder/vsphere/common/step_resolve_datastore.go index 89410e4ac..5febfbadc 100644 --- a/builder/vsphere/common/step_resolve_datastore.go +++ b/builder/vsphere/common/step_resolve_datastore.go @@ -10,6 +10,8 @@ import ( "log" "github.com/hashicorp/packer-plugin-sdk/multistep" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + "github.com/vmware/govmomi/vim25/types" "github.com/vmware/packer-plugin-vsphere/builder/vsphere/driver" ) @@ -89,3 +91,58 @@ func (s *StepResolveDatastore) Run(_ context.Context, state multistep.StateBag) // Cleanup performs any necessary cleanup. func (s *StepResolveDatastore) Cleanup(state multistep.StateBag) {} + +// ResolveMultiDiskDatastoreRefs requests per-disk Storage DRS placement when a +// datastore cluster is configured with multiple disks. It is the companion to +// StepResolveDatastore for VM create/clone steps that attach more than one disk. +func ResolveMultiDiskDatastoreRefs( + ui packersdk.Ui, + d driver.Driver, + datastoreCluster string, + disks []driver.Disk, + primaryDatastore driver.Datastore, + datastoreName string, +) (string, []*types.ManagedObjectReference) { + if datastoreCluster == "" || len(disks) <= 1 { + return datastoreName, nil + } + + dsSelector, ok := d.(driver.DatastoreSelector) + if !ok { + return datastoreName, nil + } + + ui.Sayf("Requesting Storage DRS recommendations for %d disks...", len(disks)) + + diskDatastores, method, err := dsSelector.SelectDatastoresForDisks(datastoreCluster, disks) + if err != nil { + ui.Errorf("Warning: Failed to get Storage DRS recommendations: %s. Using primary datastore.", err) + if primaryDatastore == nil { + return datastoreName, nil + } + + datastoreRefs := make([]*types.ManagedObjectReference, 0, len(disks)) + for i := 0; i < len(disks); i++ { + ref := primaryDatastore.Reference() + datastoreRefs = append(datastoreRefs, &ref) + } + return datastoreName, datastoreRefs + } + + if len(diskDatastores) > 0 { + datastoreName = diskDatastores[0].Name() + } + + datastoreRefs := make([]*types.ManagedObjectReference, 0, len(diskDatastores)) + for i, ds := range diskDatastores { + ref := ds.Reference() + if method == driver.SelectionMethodDRS { + log.Printf("[INFO] Disk %d: Storage DRS selected datastore '%s'", i+1, ds.Name()) + } else { + log.Printf("[INFO] Disk %d: Using first available datastore '%s'", i+1, ds.Name()) + } + datastoreRefs = append(datastoreRefs, &ref) + } + + return datastoreName, datastoreRefs +} diff --git a/builder/vsphere/iso/step_create.go b/builder/vsphere/iso/step_create.go index 5958a5529..a8f7dcfb0 100644 --- a/builder/vsphere/iso/step_create.go +++ b/builder/vsphere/iso/step_create.go @@ -10,14 +10,12 @@ package iso import ( "context" "fmt" - "log" "path" "strings" "github.com/hashicorp/packer-plugin-sdk/multistep" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" "github.com/hashicorp/packer-plugin-sdk/packerbuilderdata" - "github.com/vmware/govmomi/vim25/types" "github.com/vmware/packer-plugin-vsphere/builder/vsphere/common" "github.com/vmware/packer-plugin-vsphere/builder/vsphere/driver" ) @@ -226,39 +224,9 @@ func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multiste } // Handle multi-disk placement when using a datastore cluster. - var datastoreRefs []*types.ManagedObjectReference - if s.Location.DatastoreCluster != "" && len(disks) > 1 { - if dsSelector, ok := d.(driver.DatastoreSelector); ok { - // Request Storage DRS recommendations for all disks at once for optimal placement. - ui.Sayf("Requesting Storage DRS recommendations for %d disks...", len(disks)) - - diskDatastores, method, err := dsSelector.SelectDatastoresForDisks(s.Location.DatastoreCluster, disks) - if err != nil { - ui.Errorf("Warning: Failed to get Storage DRS recommendations: %s. Using primary datastore.", err) - if primaryDatastore != nil { - for i := 0; i < len(disks); i++ { - ref := primaryDatastore.Reference() - datastoreRefs = append(datastoreRefs, &ref) - } - } - } else { - // Use the first disk's datastore as the primary datastore - if len(diskDatastores) > 0 { - datastoreName = diskDatastores[0].Name() - } - - for i, ds := range diskDatastores { - ref := ds.Reference() - if method == driver.SelectionMethodDRS { - log.Printf("[INFO] Disk %d: Storage DRS selected datastore '%s'", i+1, ds.Name()) - } else { - log.Printf("[INFO] Disk %d: Using first available datastore '%s'", i+1, ds.Name()) - } - datastoreRefs = append(datastoreRefs, &ref) - } - } - } - } + datastoreName, datastoreRefs := common.ResolveMultiDiskDatastoreRefs( + ui, d, s.Location.DatastoreCluster, disks, primaryDatastore, datastoreName, + ) vm, err := d.CreateVM(&driver.CreateConfig{ StorageConfig: driver.StorageConfig{