Skip to content

Commit 2278e0e

Browse files
author
Pradeep Ramachandra
committed
api: Update folder.PlaceVMsXCluster to return available candidate networks
Signed-off-by: Pradeep Ramachandra <[email protected]>
1 parent 7d04264 commit 2278e0e

File tree

6 files changed

+258
-20
lines changed

6 files changed

+258
-20
lines changed

cli/folder/place.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"flag"
1111
"fmt"
1212
"io"
13+
"reflect"
1314
"strings"
1415
"text/tabwriter"
1516

@@ -144,6 +145,10 @@ func (cmd *place) Process(ctx context.Context) error {
144145
}
145146

146147
func (cmd *place) Run(ctx context.Context, f *flag.FlagSet) error {
148+
149+
// Register runtime override before any unmarshalling/type casting.
150+
types.Add("ClusterClusterInitialPlacementAction", reflect.TypeOf((*types.ClusterClusterInitialPlacementActionEx)(nil)).Elem())
151+
147152
client, err := cmd.Client()
148153
if err != nil {
149154
return err
@@ -296,6 +301,20 @@ func (res *placementResult) initialPlacementAction(w io.Writer, pinfo types.Plac
296301
fmt.Fprintf(w, "%s:\t%s\n", f.name, path)
297302
}
298303

304+
// Display the available network references from the placement recommendation.
305+
if act, ok := any(action).(*types.ClusterClusterInitialPlacementActionEx); ok {
306+
if len(act.AvailableNetworks) > 0 {
307+
fmt.Fprintf(w, " AvailableNetworks:\n")
308+
for _, net := range act.AvailableNetworks {
309+
path, err := find.InventoryPath(res.ctx, res.vimClient, net)
310+
if err != nil {
311+
return err
312+
}
313+
fmt.Fprintf(w, "\t- %s\n", path)
314+
}
315+
}
316+
}
317+
299318
return nil
300319
}
301320

@@ -329,6 +348,17 @@ func (res *placementResult) relocatePlacementAction(w io.Writer, pinfo types.Pla
329348
fmt.Fprintf(w, "%s:\t%s\n", f.name, path)
330349
}
331350

351+
// Display the available network references from the placement recommendation.
352+
if len(action.AvailableNetworks) > 0 {
353+
fmt.Fprintf(w, " AvailableNetworks:\n")
354+
for _, net := range action.AvailableNetworks {
355+
path, err := find.InventoryPath(res.ctx, res.vimClient, net)
356+
if err != nil {
357+
return err
358+
}
359+
fmt.Fprintf(w, "\t- %s\n", path)
360+
}
361+
}
332362
return nil
333363
}
334364

object/folder.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package object
66

77
import (
88
"context"
9+
"reflect"
910

1011
"github.com/vmware/govmomi/vim25"
1112
"github.com/vmware/govmomi/vim25/methods"
@@ -215,6 +216,7 @@ func (f Folder) MoveInto(ctx context.Context, list []types.ManagedObjectReferenc
215216
}
216217

217218
func (f Folder) PlaceVmsXCluster(ctx context.Context, spec types.PlaceVmsXClusterSpec) (*types.PlaceVmsXClusterResult, error) {
219+
types.Add("ClusterClusterInitialPlacementAction", reflect.TypeOf((*types.ClusterClusterInitialPlacementActionEx)(nil)).Elem())
218220
req := types.PlaceVmsXCluster{
219221
This: f.Reference(),
220222
PlacementSpec: spec,

simulator/folder.go

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -774,7 +774,7 @@ func addPlacementFault(body *methods.PlaceVmsXClusterBody, vmName string, vmRef
774774
body.Res.Returnval.Faults = append(body.Res.Returnval.Faults, faults)
775775
}
776776

777-
func generateRelocatePlacementAction(ctx *Context, inputRelocateSpec *types.VirtualMachineRelocateSpec, pool *ResourcePool,
777+
func generateRelocatePlacementAction(ctx *Context, vmSpec types.PlaceVmsXClusterSpecVmPlacementSpec, pool *ResourcePool,
778778
cluster *ClusterComputeResource, hostRequired, datastoreRequired bool) *types.ClusterClusterRelocatePlacementAction {
779779
var relocateSpec *types.VirtualMachineRelocateSpec
780780

@@ -788,7 +788,7 @@ func generateRelocatePlacementAction(ctx *Context, inputRelocateSpec *types.Virt
788788
}
789789

790790
if datastoreRequired {
791-
relocateSpec = inputRelocateSpec
791+
relocateSpec = vmSpec.RelocateSpec
792792

793793
ds := ctx.Map.Get(cluster.Datastore[rand.Intn(len(cluster.Datastore))]).(*Datastore)
794794

@@ -799,6 +799,27 @@ func generateRelocatePlacementAction(ctx *Context, inputRelocateSpec *types.Virt
799799
}
800800
}
801801

802+
// Get all networks available in the cluster.
803+
clusterNetworks := make(map[string]bool)
804+
for _, netRef := range cluster.Network {
805+
clusterNetworks[netRef.Value] = true
806+
}
807+
808+
// Select candidate networks that are also available in the target cluster.
809+
unique := make(map[string]types.ManagedObjectReference)
810+
for _, nic := range vmSpec.CandidateNetworks {
811+
for _, cand := range nic.Networks {
812+
if clusterNetworks[cand.Value] {
813+
unique[cand.Value] = cand
814+
}
815+
}
816+
}
817+
818+
// Store in AvailableNetworks.
819+
for _, ref := range unique {
820+
placementAction.AvailableNetworks = append(placementAction.AvailableNetworks, ref)
821+
}
822+
802823
placementAction.RelocateSpec = relocateSpec
803824
return &placementAction
804825
}
@@ -852,7 +873,7 @@ func generateRecommendationForRelocate(ctx *Context, req *types.PlaceVmsXCluster
852873
Target: &cluster.Self,
853874
}
854875

855-
placementAction := generateRelocatePlacementAction(ctx, spec.RelocateSpec, pool, cluster, hostRequired, datastoreRequired)
876+
placementAction := generateRelocatePlacementAction(ctx, spec, pool, cluster, hostRequired, datastoreRequired)
856877

857878
reco.Action = append(reco.Action, placementAction)
858879

@@ -1002,11 +1023,10 @@ func fillConfigSpecWithDatastore(ctx *Context, inputConfigSpec, configSpec *type
10021023
}
10031024
}
10041025

1005-
func generateInitialPlacementAction(ctx *Context, inputConfigSpec *types.VirtualMachineConfigSpec, pool *ResourcePool,
1006-
cluster *ClusterComputeResource, hostRequired, datastoreRequired bool) *types.ClusterClusterInitialPlacementAction {
1007-
var configSpec *types.VirtualMachineConfigSpec
1026+
func generateInitialPlacementAction(ctx *Context, vmSpec *types.PlaceVmsXClusterSpecVmPlacementSpec, pool *ResourcePool,
1027+
cluster *ClusterComputeResource, hostRequired, datastoreRequired bool) types.BaseClusterAction {
10081028

1009-
placementAction := types.ClusterClusterInitialPlacementAction{
1029+
placementAction := &types.ClusterClusterInitialPlacementActionEx{
10101030
Pool: pool.Self,
10111031
}
10121032

@@ -1016,18 +1036,40 @@ func generateInitialPlacementAction(ctx *Context, inputConfigSpec *types.Virtual
10161036
}
10171037

10181038
if datastoreRequired {
1019-
configSpec = inputConfigSpec
1039+
configSpec := vmSpec.ConfigSpec // value copy
10201040

10211041
// TODO: This is just an initial implementation aimed at returning some data but it is not
10221042
// necessarily fully consistent, like we should ensure the host, if also required, has the
10231043
// datastore mounted.
10241044
ds := ctx.Map.Get(cluster.Datastore[rand.Intn(len(cluster.Datastore))]).(*Datastore)
10251045

1026-
fillConfigSpecWithDatastore(ctx, inputConfigSpec, configSpec, ds)
1046+
fillConfigSpecWithDatastore(ctx, &vmSpec.ConfigSpec, &configSpec, ds)
1047+
placementAction.ConfigSpec = &configSpec
1048+
} else {
1049+
placementAction.ConfigSpec = &vmSpec.ConfigSpec
10271050
}
10281051

1029-
placementAction.ConfigSpec = configSpec
1030-
return &placementAction
1052+
// Get all networks available in the cluster.
1053+
clusterNetworks := make(map[string]bool)
1054+
for _, netRef := range cluster.Network {
1055+
clusterNetworks[netRef.Value] = true
1056+
}
1057+
1058+
// Select candidate networks that are also available in the target cluster.
1059+
unique := make(map[string]types.ManagedObjectReference)
1060+
for _, nic := range vmSpec.CandidateNetworks {
1061+
for _, cand := range nic.Networks {
1062+
if clusterNetworks[cand.Value] {
1063+
unique[cand.Value] = cand
1064+
}
1065+
}
1066+
}
1067+
1068+
// Store in AvailableNetworks.
1069+
for _, ref := range unique {
1070+
placementAction.AvailableNetworks = append(placementAction.AvailableNetworks, ref)
1071+
}
1072+
return placementAction
10311073
}
10321074

10331075
func generateRecommendationForCreateAndPowerOn(ctx *Context, req *types.PlaceVmsXCluster) *methods.PlaceVmsXClusterBody {
@@ -1072,7 +1114,7 @@ func generateRecommendationForCreateAndPowerOn(ctx *Context, req *types.PlaceVms
10721114
Target: &cluster.Self,
10731115
}
10741116

1075-
placementAction := generateInitialPlacementAction(ctx, &spec.ConfigSpec, pool, cluster, hostRequired, datastoreRequired)
1117+
placementAction := generateInitialPlacementAction(ctx, &spec, pool, cluster, hostRequired, datastoreRequired)
10761118

10771119
reco.Action = append(reco.Action, placementAction)
10781120

simulator/folder_test.go

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,38 @@ func TestPlaceVmsXClusterCreateAndPowerOnWithCandidateNetworks(t *testing.T) {
721721
if len(res.PlacementInfos) != len(spec.VmPlacementSpecs) {
722722
t.Errorf("%d PlacementInfos vs %d VmPlacementSpecs", len(res.PlacementInfos), len(spec.VmPlacementSpecs))
723723
}
724+
725+
// Validate AvailableNetworks returned in placement recommendations.
726+
for _, pinfo := range res.PlacementInfos {
727+
for i, action := range pinfo.Recommendation.Action {
728+
// Ensure the action is of expected extended type.
729+
initPlaceAction, ok := action.(*types.ClusterClusterInitialPlacementActionEx)
730+
if !ok {
731+
t.Errorf("Action[%d] is not ClusterClusterInitialPlacementActionEx, got %T", i, action)
732+
continue
733+
}
734+
if len(initPlaceAction.AvailableNetworks) == 0 {
735+
t.Errorf("AvailableNetworks is empty for VM %v", pinfo.Vm)
736+
} else {
737+
t.Logf("AvailableNetworks for VM %v:", pinfo.Vm)
738+
for _, net := range initPlaceAction.AvailableNetworks {
739+
t.Logf("- %s", net.Value)
740+
}
741+
}
742+
// Define the expected networks that should be present in AvailableNetworks.
743+
expected := map[string]bool{
744+
netA.Reference().Value: true,
745+
netB.Reference().Value: true,
746+
}
747+
748+
// Verify that all returned networks are part of the expected set.
749+
for _, actual := range initPlaceAction.AvailableNetworks {
750+
if !expected[actual.Value] {
751+
t.Errorf("unexpected network in availableNetworks: %s", actual.Value)
752+
}
753+
}
754+
}
755+
}
724756
}, vpx)
725757
}
726758

@@ -850,14 +882,36 @@ func TestPlaceVmsXClusterRelocate(t *testing.T) {
850882
t.Errorf("Test %v: %d PlacementInfos vs %d VmPlacementSpecs", testNo, len(res.PlacementInfos), len(placeVmsXClusterSpec.VmPlacementSpecs))
851883
}
852884

885+
// Validate AvailableNetworks returned in placement recommendations.
853886
for _, pinfo := range res.PlacementInfos {
854887
for _, action := range pinfo.Recommendation.Action {
855-
if relocateAction, ok := action.(*types.ClusterClusterRelocatePlacementAction); ok {
856-
if relocateAction.TargetHost == nil {
857-
t.Errorf("Test %v: received nil host recommendation", testNo)
858-
}
859-
} else {
888+
relocateAction, ok := action.(*types.ClusterClusterRelocatePlacementAction)
889+
if !ok {
860890
t.Errorf("Test %v: received wrong action type in recommendation", testNo)
891+
continue
892+
}
893+
if relocateAction.TargetHost == nil {
894+
t.Errorf("Test %v: received nil host recommendation", testNo)
895+
}
896+
// Check if AvailableNetworks field is populated.
897+
if len(relocateAction.AvailableNetworks) == 0 {
898+
t.Errorf("AvailableNetworks is empty for VM %v", pinfo.Vm)
899+
} else {
900+
t.Logf("AvailableNetworks for VM %v:", pinfo.Vm)
901+
for _, net := range relocateAction.AvailableNetworks {
902+
t.Logf("- %s", net.Value)
903+
}
904+
}
905+
// Define the expected networks that should be present in AvailableNetworks.
906+
expected := map[string]bool{
907+
netA.Reference().Value: true,
908+
netB.Reference().Value: true,
909+
}
910+
// Verify that all returned networks are part of the expected set.
911+
for _, actual := range relocateAction.AvailableNetworks {
912+
if !expected[actual.Value] {
913+
t.Errorf("unexpected network in availableNetworks: %s", actual.Value)
914+
}
861915
}
862916
}
863917
}

vim25/types/unreleased.go

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,10 @@ func init() {
145145

146146
type ClusterClusterRelocatePlacementAction struct {
147147
ClusterAction
148-
TargetHost *ManagedObjectReference `xml:"targetHost,omitempty"`
149-
Pool ManagedObjectReference `xml:"pool"`
150-
RelocateSpec *VirtualMachineRelocateSpec `xml:"relocateSpec,omitempty"`
148+
TargetHost *ManagedObjectReference `xml:"targetHost,omitempty"`
149+
Pool ManagedObjectReference `xml:"pool"`
150+
RelocateSpec *VirtualMachineRelocateSpec `xml:"relocateSpec,omitempty"`
151+
AvailableNetworks []ManagedObjectReference `xml:"availableNetworks,omitempty"`
151152
}
152153

153154
func init() {
@@ -163,3 +164,68 @@ type PodVMOverheadInfo struct {
163164
PodVMOverheadWithoutPageSharing int32 `xml:"podVMOverheadWithoutPageSharing"`
164165
PodVMOverheadWithPageSharing int32 `xml:"podVMOverheadWithPageSharing"`
165166
}
167+
168+
// Describes an action for the initial placement of a virtual machine in a cluster.
169+
//
170+
// This action is used by the cross cluster placement API when a virtual machine
171+
// needs to be placed across a set of given clusters. See `Folder.PlaceVmsXCluster`.
172+
// This action encapsulates details about the chosen cluster (via the resource pool
173+
// inside that cluster), the chosen host and the chosen datastores for the disks of
174+
// the virtual machine.
175+
type ClusterClusterInitialPlacementActionEx struct {
176+
ClusterAction
177+
178+
// The host where the virtual machine should be initially placed.
179+
//
180+
// This field is optional because the primary use case of
181+
// `Folder.PlaceVmsXCluster` is to select the best cluster for placing VMs. This
182+
// `ClusterClusterInitialPlacementAction.targetHost` denotes the best host
183+
// within the best cluster and it is only returned if the client asks for it,
184+
// which is determined by `PlaceVmsXClusterSpec.hostRecommRequired`.
185+
// If `PlaceVmsXClusterSpec.hostRecommRequired` is set to true, then the
186+
// targetHost is returned with a valid value and if it is either set to false
187+
// or left unset, then targetHost is also left unset. When this field is unset,
188+
// then it means that the client did not ask for the target host within the
189+
// recommended cluster. It does not mean that there is no recommended host
190+
// for placing this VM in the recommended cluster.
191+
//
192+
// Refers instance of `HostSystem`.
193+
TargetHost *ManagedObjectReference `xml:"targetHost,omitempty" json:"targetHost,omitempty"`
194+
195+
// The chosen resource pool for placing the virtual machine.
196+
//
197+
// This is non-optional because recommending the best cluster (by recommending the
198+
// resource pool in the best cluster) is the primary use case for the
199+
// `ClusterClusterInitialPlacementAction`.
200+
//
201+
// Refers instance of `ResourcePool`.
202+
Pool ManagedObjectReference `xml:"pool" json:"pool"`
203+
204+
// The config spec of the virtual machine to be placed.
205+
//
206+
// The `Folder.PlaceVmsXCluster` method takes input of `VirtualMachineConfigSpec`
207+
// from client and populates the backing for each virtual disk and the VM home
208+
// path in it unless the input ConfigSpec already provides them. The existing
209+
// settings in the input ConfigSpec are preserved and not overridden in the
210+
// returned ConfigSpec in this action as well as the resulting
211+
// `ClusterRecommendation`. This field is set based on whether the client needs
212+
// `Folder.PlaceVmsXCluster` to recommend a backing datastore for the disks of
213+
// the candidate VMs or not, which is specified via
214+
// `PlaceVmsXClusterSpec.datastoreRecommRequired`. If
215+
// `PlaceVmsXClusterSpec.datastoreRecommRequired` is set to true, then this
216+
// `ClusterClusterInitialPlacementAction.configSpec` is also set with the
217+
// backing of each disk populated. If
218+
// `PlaceVmsXClusterSpec.datastoreRecommRequired` is either set to false or left
219+
// unset, then this field is also left unset. When this field is left unset,
220+
// then it means that the client did not ask to populate the backing datastore
221+
// for the disks of the candidate VMs.
222+
ConfigSpec *VirtualMachineConfigSpec `xml:"configSpec,omitempty" json:"configSpec,omitempty"`
223+
224+
AvailableNetworks []ManagedObjectReference `xml:"availableNetworks,omitempty" json:"availableNetworks,omitempty"`
225+
}
226+
227+
func init() {
228+
minAPIVersionForType["ClusterClusterInitialPlacementActionEx"] = "9.1.0.0"
229+
t["ClusterClusterInitialPlacementActionEx"] = reflect.TypeOf((*ClusterClusterInitialPlacementActionEx)(nil)).Elem()
230+
Add("ClusterClusterInitialPlacementAction", reflect.TypeOf((*ClusterClusterInitialPlacementActionEx)(nil)).Elem())
231+
}

0 commit comments

Comments
 (0)