github.com/vmware/govmomi@v0.51.0/cli/folder/place.go (about) 1 // © Broadcom. All Rights Reserved. 2 // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. 3 // SPDX-License-Identifier: Apache-2.0 4 5 package folder 6 7 import ( 8 "context" 9 "errors" 10 "flag" 11 "fmt" 12 "io" 13 "strings" 14 "text/tabwriter" 15 16 "github.com/vmware/govmomi/cli" 17 "github.com/vmware/govmomi/cli/flags" 18 "github.com/vmware/govmomi/find" 19 "github.com/vmware/govmomi/object" 20 "github.com/vmware/govmomi/vim25" 21 "github.com/vmware/govmomi/vim25/types" 22 ) 23 24 var allTypes = []string{} 25 26 var createAndPowerOnTypes = []string{ 27 string(types.PlaceVmsXClusterSpecPlacementTypeCreateAndPowerOn), 28 } 29 30 var relocateTypes = []string{ 31 string(types.PlaceVmsXClusterSpecPlacementTypeRelocate), 32 } 33 34 var reconfigureTypes = []string{ 35 string(types.PlaceVmsXClusterSpecPlacementTypeReconfigure), 36 } 37 38 func init() { 39 allTypes = append(allTypes, createAndPowerOnTypes...) 40 allTypes = append(allTypes, relocateTypes...) 41 allTypes = append(allTypes, reconfigureTypes...) 42 } 43 44 type typeFlag string 45 46 func (t *typeFlag) Set(s string) error { 47 s = strings.ToLower(s) 48 for _, e := range allTypes { 49 if s == strings.ToLower(e) { 50 *t = typeFlag(e) 51 return nil 52 } 53 } 54 55 return fmt.Errorf("unknown type") 56 } 57 58 func (t *typeFlag) String() string { 59 return string(*t) 60 } 61 62 func (t *typeFlag) partOf(m []string) bool { 63 for _, e := range m { 64 if t.String() == e { 65 return true 66 } 67 } 68 return false 69 } 70 71 func (t *typeFlag) IsCreateAndPowerOnType() bool { 72 return t.partOf(createAndPowerOnTypes) 73 } 74 75 func (t *typeFlag) IsRelocateType() bool { 76 return t.partOf(relocateTypes) 77 } 78 79 func (t *typeFlag) IsReconfigureType() bool { 80 return t.partOf(reconfigureTypes) 81 } 82 83 type place struct { 84 *flags.ClientFlag 85 *flags.DatacenterFlag 86 *flags.VirtualMachineFlag 87 *flags.OutputFlag 88 89 pool flags.StringList 90 Type typeFlag 91 } 92 93 func init() { 94 cli.Register("folder.place", &place{}, true) 95 } 96 97 func (cmd *place) Register(ctx context.Context, f *flag.FlagSet) { 98 cmd.ClientFlag, ctx = flags.NewClientFlag(ctx) 99 cmd.ClientFlag.Register(ctx, f) 100 cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx) 101 cmd.DatacenterFlag.Register(ctx, f) 102 cmd.VirtualMachineFlag, ctx = flags.NewVirtualMachineFlag(ctx) 103 cmd.VirtualMachineFlag.Register(ctx, f) 104 cmd.OutputFlag, ctx = flags.NewOutputFlag(ctx) 105 cmd.OutputFlag.Register(ctx, f) 106 107 f.Var(&cmd.pool, "pool", "Resource Pools to use for placement.") 108 f.Var(&cmd.Type, "type", fmt.Sprintf("Placement type (%s)", strings.Join(allTypes, "|"))) 109 } 110 111 func (cmd *place) Usage() string { 112 return "PATH..." 113 } 114 115 func (cmd *place) Description() string { 116 return `Get a placement recommendation for an existing VM 117 118 Examples: 119 govc folder.place -rp $rp1Name -rp $rp2Name -rp $rp3Name-vm $vmName` 120 } 121 122 func (cmd *place) Process(ctx context.Context) error { 123 if err := cmd.ClientFlag.Process(ctx); err != nil { 124 return err 125 } 126 if err := cmd.DatacenterFlag.Process(ctx); err != nil { 127 return err 128 } 129 if err := cmd.VirtualMachineFlag.Process(ctx); err != nil { 130 return err 131 } 132 if err := cmd.OutputFlag.Process(ctx); err != nil { 133 return err 134 } 135 return nil 136 } 137 138 func (cmd *place) Run(ctx context.Context, f *flag.FlagSet) error { 139 c, err := cmd.Client() 140 if err != nil { 141 return err 142 } 143 144 // Use latest version to pick up latest PlaceVmsXCluster API. 145 err = c.UseServiceVersion() 146 if err != nil { 147 return err 148 } 149 150 vm, err := cmd.VirtualMachine() 151 if err != nil { 152 return err 153 } 154 155 if vm == nil { 156 return flag.ErrHelp 157 } 158 159 finder, err := cmd.Finder() 160 if err != nil { 161 return err 162 } 163 164 var relocateSpec *types.VirtualMachineRelocateSpec 165 166 // TODO: Support createAndPowerOn and reconfigure. 167 switch { 168 case cmd.Type.IsRelocateType(): 169 relocateSpec = &types.VirtualMachineRelocateSpec{} 170 break 171 case cmd.Type.IsReconfigureType(): 172 return errors.New("reconfigure is currently an unsupported placement type") 173 case cmd.Type.IsCreateAndPowerOnType(): 174 return errors.New("createAndPowerOn is currently an unsupported placement type") 175 default: 176 return errors.New("please specify a valid type") 177 } 178 179 // PlaceVMsXCluster is only valid against the root folder. 180 folder := object.NewRootFolder(c) 181 182 refs := make([]types.ManagedObjectReference, 0, len(cmd.pool)) 183 184 for _, arg := range cmd.pool { 185 rp, err := finder.ResourcePool(ctx, arg) 186 if err != nil { 187 return err 188 } 189 190 refs = append(refs, rp.Reference()) 191 } 192 193 vmPlacementSpecs := []types.PlaceVmsXClusterSpecVmPlacementSpec{{ 194 Vm: types.NewReference(vm.Reference()), 195 ConfigSpec: types.VirtualMachineConfigSpec{}, 196 RelocateSpec: relocateSpec, 197 }} 198 199 placementSpec := types.PlaceVmsXClusterSpec{ 200 ResourcePools: refs, 201 PlacementType: cmd.Type.String(), 202 VmPlacementSpecs: vmPlacementSpecs, 203 HostRecommRequired: types.NewBool(true), 204 DatastoreRecommRequired: types.NewBool(true), 205 } 206 207 res, err := folder.PlaceVmsXCluster(ctx, placementSpec) 208 if err != nil { 209 return err 210 } 211 212 vimClient, err := cmd.ClientFlag.Client() 213 if err != nil { 214 return err 215 } 216 217 return cmd.WriteResult(&placementResult{res, vimClient, ctx, cmd.VirtualMachineFlag}) 218 } 219 220 type placementResult struct { 221 Result *types.PlaceVmsXClusterResult `json:"result,omitempty"` 222 vimClient *vim25.Client 223 ctx context.Context 224 vm *flags.VirtualMachineFlag 225 } 226 227 func (res *placementResult) Dump() any { 228 return res.Result 229 } 230 231 func (res *placementResult) initialPlacementAction(w io.Writer, pinfo types.PlaceVmsXClusterResultPlacementInfo, action *types.ClusterClusterInitialPlacementAction) error { 232 233 spec := action.ConfigSpec 234 if spec == nil { 235 return nil 236 } 237 238 fields := []struct { 239 name string 240 moid *types.ManagedObjectReference 241 }{ 242 {"Vm", pinfo.Vm}, 243 {" Target", pinfo.Recommendation.Target}, 244 {" TargetHost", action.TargetHost}, 245 {" Pool", &action.Pool}, 246 } 247 248 for _, f := range fields { 249 if f.moid == nil { 250 continue 251 } 252 path, err := find.InventoryPath(res.ctx, res.vimClient, *f.moid) 253 if err != nil { 254 return err 255 } 256 fmt.Fprintf(w, "%s:\t%s\n", f.name, path) 257 } 258 259 return nil 260 } 261 262 func (res *placementResult) relocatePlacementAction(w io.Writer, pinfo types.PlaceVmsXClusterResultPlacementInfo, action *types.ClusterClusterRelocatePlacementAction) error { 263 264 spec := action.RelocateSpec 265 if spec == nil { 266 return nil 267 } 268 269 fields := []struct { 270 name string 271 moid *types.ManagedObjectReference 272 }{ 273 {"Vm", pinfo.Vm}, 274 {" Target", pinfo.Recommendation.Target}, 275 {" Folder", spec.Folder}, 276 {" Datastore", spec.Datastore}, 277 {" Pool", spec.Pool}, 278 {" Host", spec.Host}, 279 } 280 281 for _, f := range fields { 282 if f.moid == nil { 283 continue 284 } 285 path, err := find.InventoryPath(res.ctx, res.vimClient, *f.moid) 286 if err != nil { 287 return err 288 } 289 fmt.Fprintf(w, "%s:\t%s\n", f.name, path) 290 } 291 292 return nil 293 } 294 295 func (res *placementResult) reconfigurePlacementAction(w io.Writer, pinfo types.PlaceVmsXClusterResultPlacementInfo, action *types.ClusterClusterReconfigurePlacementAction) error { 296 297 spec := action.ConfigSpec 298 if spec == nil { 299 return nil 300 } 301 302 fields := []struct { 303 name string 304 moid *types.ManagedObjectReference 305 }{ 306 {"Vm", pinfo.Vm}, 307 {" Target", pinfo.Recommendation.Target}, 308 {" TargetHost", action.TargetHost}, 309 {" Pool", &action.Pool}, 310 } 311 312 for _, f := range fields { 313 if f.moid == nil { 314 continue 315 } 316 path, err := find.InventoryPath(res.ctx, res.vimClient, *f.moid) 317 if err != nil { 318 return err 319 } 320 fmt.Fprintf(w, "%s:\t%s\n", f.name, path) 321 } 322 323 return nil 324 } 325 326 func (res *placementResult) placementFault(w io.Writer, pfault types.PlaceVmsXClusterResultPlacementFaults, fault *types.LocalizedMethodFault) error { 327 328 fields := []struct { 329 name string 330 message string 331 moid *types.ManagedObjectReference 332 }{ 333 {"Vm", "", pfault.Vm}, 334 {" Message", fault.LocalizedMessage, nil}, 335 } 336 337 for _, f := range fields { 338 if f.moid == nil { 339 if f.message != "" { 340 fmt.Fprintf(w, "%s:\t%s\n", f.name, f.message) 341 } 342 continue 343 } 344 path, err := find.InventoryPath(res.ctx, res.vimClient, *f.moid) 345 if err != nil { 346 return err 347 } 348 fmt.Fprintf(w, "%s:\t%s\n", f.name, path) 349 } 350 351 return nil 352 } 353 354 func (res placementResult) Write(w io.Writer) error { 355 tw := tabwriter.NewWriter(w, 2, 0, 2, ' ', 0) 356 357 for _, pinfo := range res.Result.PlacementInfos { 358 359 for _, action := range pinfo.Recommendation.Action { 360 361 if initPlaceAction, ok := action.(*types.ClusterClusterInitialPlacementAction); ok { 362 err := res.initialPlacementAction(w, pinfo, initPlaceAction) 363 if err != nil { 364 return err 365 } 366 } 367 368 if relocateAction, ok := action.(*types.ClusterClusterRelocatePlacementAction); ok { 369 err := res.relocatePlacementAction(w, pinfo, relocateAction) 370 if err != nil { 371 return err 372 } 373 } 374 375 if reconfigureAction, ok := action.(*types.ClusterClusterReconfigurePlacementAction); ok { 376 err := res.reconfigurePlacementAction(w, pinfo, reconfigureAction) 377 if err != nil { 378 return err 379 } 380 } 381 } 382 } 383 384 for _, pfault := range res.Result.Faults { 385 386 for _, fault := range pfault.Faults { 387 err := res.placementFault(w, pfault, &fault) 388 if err != nil { 389 return err 390 } 391 } 392 } 393 394 return tw.Flush() 395 }