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  }