github.com/vmware/govmomi@v0.51.0/cli/datastore/create.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 datastore
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"flag"
    11  	"fmt"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  
    16  	"github.com/vmware/govmomi/cli"
    17  	"github.com/vmware/govmomi/cli/flags"
    18  	"github.com/vmware/govmomi/object"
    19  	"github.com/vmware/govmomi/units"
    20  	"github.com/vmware/govmomi/vim25/soap"
    21  	"github.com/vmware/govmomi/vim25/types"
    22  )
    23  
    24  const (
    25  	sectorSize  = 512  // Sector size in bytes
    26  	startSector = 2048 // Start sector (typically fixed)
    27  )
    28  
    29  type create struct {
    30  	*flags.HostSystemFlag
    31  
    32  	// Generic options
    33  	Type  typeFlag
    34  	Name  string
    35  	Force bool
    36  
    37  	// Options for NAS
    38  	RemoteHost string
    39  	RemotePath string
    40  	AccessMode string
    41  	UserName   string
    42  	Password   string
    43  
    44  	// Options for VMFS
    45  	DiskCanonicalName string
    46  	Version           *int32
    47  	Size              units.ByteSize
    48  
    49  	// Options for local
    50  	Path string
    51  }
    52  
    53  func init() {
    54  	cli.Register("datastore.create", &create{})
    55  }
    56  
    57  var nasTypes = []string{
    58  	string(types.HostFileSystemVolumeFileSystemTypeNFS),
    59  	string(types.HostFileSystemVolumeFileSystemTypeNFS41),
    60  	string(types.HostFileSystemVolumeFileSystemTypeCIFS),
    61  }
    62  
    63  var vmfsTypes = []string{
    64  	string(types.HostFileSystemVolumeFileSystemTypeVMFS),
    65  }
    66  
    67  var localTypes = []string{
    68  	"local",
    69  }
    70  
    71  var allTypes = []string{}
    72  
    73  func init() {
    74  	allTypes = append(allTypes, nasTypes...)
    75  	allTypes = append(allTypes, vmfsTypes...)
    76  	allTypes = append(allTypes, localTypes...)
    77  }
    78  
    79  type typeFlag string
    80  
    81  func (t *typeFlag) Set(s string) error {
    82  	s = strings.ToLower(s)
    83  	for _, e := range allTypes {
    84  		if s == strings.ToLower(e) {
    85  			*t = typeFlag(e)
    86  			return nil
    87  		}
    88  	}
    89  
    90  	return fmt.Errorf("unknown type")
    91  }
    92  
    93  func (t *typeFlag) String() string {
    94  	return string(*t)
    95  }
    96  
    97  func (t *typeFlag) partOf(m []string) bool {
    98  	for _, e := range m {
    99  		if t.String() == e {
   100  			return true
   101  		}
   102  	}
   103  	return false
   104  }
   105  
   106  func (t *typeFlag) IsNasType() bool {
   107  	return t.partOf(nasTypes)
   108  }
   109  
   110  func (t *typeFlag) IsVmfsType() bool {
   111  	return t.partOf(vmfsTypes)
   112  }
   113  
   114  func (t *typeFlag) IsLocalType() bool {
   115  	return t.partOf(localTypes)
   116  }
   117  
   118  func (cmd *create) Register(ctx context.Context, f *flag.FlagSet) {
   119  	cmd.HostSystemFlag, ctx = flags.NewHostSystemFlag(ctx)
   120  	cmd.HostSystemFlag.Register(ctx, f)
   121  
   122  	modes := []string{
   123  		string(types.HostMountModeReadOnly),
   124  		string(types.HostMountModeReadWrite),
   125  	}
   126  
   127  	f.StringVar(&cmd.Name, "name", "", "Datastore name")
   128  	f.Var(&cmd.Type, "type", fmt.Sprintf("Datastore type (%s)", strings.Join(allTypes, "|")))
   129  	f.BoolVar(&cmd.Force, "force", false, "Ignore DuplicateName error if datastore is already mounted on a host")
   130  
   131  	// Options for NAS
   132  	f.StringVar(&cmd.RemoteHost, "remote-host", "", "Remote hostname of the NAS datastore")
   133  	f.StringVar(&cmd.RemotePath, "remote-path", "", "Remote path of the NFS mount point")
   134  	f.StringVar(&cmd.AccessMode, "mode", modes[0],
   135  		fmt.Sprintf("Access mode for the mount point (%s)", strings.Join(modes, "|")))
   136  	f.StringVar(&cmd.UserName, "username", "", "Username to use when connecting (CIFS only)")
   137  	f.StringVar(&cmd.Password, "password", "", "Password to use when connecting (CIFS only)")
   138  
   139  	// Options for VMFS
   140  	f.StringVar(&cmd.DiskCanonicalName, "disk", "", "Canonical name of disk (VMFS only)")
   141  	f.Var(flags.NewOptionalInt32(&cmd.Version), "version", "VMFS major version")
   142  	f.Var(&cmd.Size, "size", "Size of new disk. Default is to use entire disk")
   143  
   144  	// Options for Local
   145  	f.StringVar(&cmd.Path, "path", "", "Local directory path for the datastore (local only)")
   146  }
   147  
   148  func (cmd *create) Process(ctx context.Context) error {
   149  	if err := cmd.HostSystemFlag.Process(ctx); err != nil {
   150  		return err
   151  	}
   152  	return nil
   153  }
   154  
   155  func (cmd *create) Usage() string {
   156  	return "HOST..."
   157  }
   158  
   159  func (cmd *create) Description() string {
   160  	return `Create datastore on HOST.
   161  
   162  Examples:
   163    govc datastore.create -type nfs -name nfsDatastore -remote-host 10.143.2.232 -remote-path /share cluster1
   164    govc datastore.create -type vmfs -name vmfsDatastore -disk=mpx.vmhba0:C0:T0:L0 cluster1 # use entire disk
   165    govc datastore.create -type vmfs -name vmfsDatastore -disk=mpx.vmhba0:C0:T0:L0 -size 20G cluster1 # use 20G of disk
   166    govc datastore.create -type local -name localDatastore -path /var/datastore host1`
   167  }
   168  
   169  func (cmd *create) Run(ctx context.Context, f *flag.FlagSet) error {
   170  	hosts, err := cmd.HostSystems(f.Args())
   171  	if err != nil {
   172  		return err
   173  	}
   174  
   175  	switch {
   176  	case cmd.Type.IsNasType():
   177  		return cmd.CreateNasDatastore(ctx, hosts)
   178  	case cmd.Type.IsVmfsType():
   179  		return cmd.CreateVmfsDatastore(ctx, hosts)
   180  	case cmd.Type.IsLocalType():
   181  		return cmd.CreateLocalDatastore(ctx, hosts)
   182  	default:
   183  		return fmt.Errorf("unhandled type %#v", cmd.Type)
   184  	}
   185  }
   186  
   187  func (cmd *create) GetHostNasVolumeSpec() types.HostNasVolumeSpec {
   188  	localPath := cmd.Path
   189  	if localPath == "" {
   190  		localPath = cmd.Name
   191  	}
   192  
   193  	s := types.HostNasVolumeSpec{
   194  		LocalPath:  localPath,
   195  		Type:       cmd.Type.String(),
   196  		RemoteHost: cmd.RemoteHost,
   197  		RemotePath: cmd.RemotePath,
   198  		AccessMode: cmd.AccessMode,
   199  		UserName:   cmd.UserName,
   200  		Password:   cmd.Password,
   201  	}
   202  
   203  	return s
   204  }
   205  
   206  func (cmd *create) CreateNasDatastore(ctx context.Context, hosts []*object.HostSystem) error {
   207  	object := types.ManagedObjectReference{
   208  		Type:  "Datastore",
   209  		Value: fmt.Sprintf("%s:%s", cmd.RemoteHost, cmd.RemotePath),
   210  	}
   211  
   212  	spec := cmd.GetHostNasVolumeSpec()
   213  
   214  	for _, host := range hosts {
   215  		ds, err := host.ConfigManager().DatastoreSystem(ctx)
   216  		if err != nil {
   217  			return err
   218  		}
   219  
   220  		_, err = ds.CreateNasDatastore(ctx, spec)
   221  		if err != nil {
   222  			if soap.IsSoapFault(err) {
   223  				switch fault := soap.ToSoapFault(err).VimFault().(type) {
   224  				case types.PlatformConfigFault:
   225  					if len(fault.FaultMessage) != 0 {
   226  						return errors.New(fault.FaultMessage[0].Message)
   227  					}
   228  				case types.DuplicateName:
   229  					if cmd.Force && fault.Object == object {
   230  						fmt.Fprintf(os.Stderr, "%s: '%s' already mounted\n",
   231  							host.InventoryPath, cmd.Name)
   232  						continue
   233  					}
   234  				}
   235  			}
   236  
   237  			return fmt.Errorf("%s: %s", host.InventoryPath, err)
   238  		}
   239  	}
   240  
   241  	return nil
   242  }
   243  
   244  func (cmd *create) CreateVmfsDatastore(ctx context.Context, hosts []*object.HostSystem) error {
   245  	for _, host := range hosts {
   246  		ds, err := host.ConfigManager().DatastoreSystem(ctx)
   247  		if err != nil {
   248  			return err
   249  		}
   250  
   251  		// Find the specified disk
   252  		disks, err := ds.QueryAvailableDisksForVmfs(ctx)
   253  		if err != nil {
   254  			return err
   255  		}
   256  
   257  		var disk *types.HostScsiDisk
   258  		for _, e := range disks {
   259  			if e.CanonicalName == cmd.DiskCanonicalName {
   260  				disk = &e
   261  				break
   262  			}
   263  		}
   264  
   265  		if disk == nil {
   266  			return fmt.Errorf("no eligible disk found for name %#v", cmd.DiskCanonicalName)
   267  		}
   268  
   269  		// Query for creation options and pick the right one
   270  		options, err := ds.QueryVmfsDatastoreCreateOptions(ctx, disk.DevicePath)
   271  		if err != nil {
   272  			return err
   273  		}
   274  
   275  		var option *types.VmfsDatastoreOption
   276  		for _, e := range options {
   277  			if _, ok := e.Info.(*types.VmfsDatastoreAllExtentOption); ok {
   278  				option = &e
   279  				break
   280  			}
   281  		}
   282  
   283  		if option == nil {
   284  			return fmt.Errorf("cannot use entire disk for datastore for name %#v", cmd.DiskCanonicalName)
   285  		}
   286  
   287  		spec := *option.Spec.(*types.VmfsDatastoreCreateSpec)
   288  		spec.Vmfs.VolumeName = cmd.Name
   289  		if cmd.Size > 0 {
   290  			endSector := CalculateSectors(int64(cmd.Size))
   291  			// set values for Sectors
   292  			spec.Partition.Partition[0].StartSector = startSector
   293  			spec.Partition.Partition[0].EndSector = endSector
   294  		}
   295  		if cmd.Version != nil {
   296  			spec.Vmfs.MajorVersion = *cmd.Version
   297  		}
   298  		_, err = ds.CreateVmfsDatastore(ctx, spec)
   299  		if err != nil {
   300  			return err
   301  		}
   302  	}
   303  
   304  	return nil
   305  }
   306  
   307  // CalculateSectors calculates the start and end sectors based on the given size.
   308  func CalculateSectors(sizeInBytes int64) (endSector int64) {
   309  	totalSectors := sizeInBytes / sectorSize
   310  	endSector = startSector + totalSectors - 1
   311  
   312  	return endSector
   313  }
   314  
   315  func (cmd *create) CreateLocalDatastore(ctx context.Context, hosts []*object.HostSystem) error {
   316  	for _, host := range hosts {
   317  		ds, err := host.ConfigManager().DatastoreSystem(ctx)
   318  		if err != nil {
   319  			return err
   320  		}
   321  
   322  		if cmd.Path == "" {
   323  			cmd.Path = cmd.Name
   324  		}
   325  
   326  		if cmd.Name == "" {
   327  			cmd.Name = filepath.Base(cmd.Path)
   328  		}
   329  
   330  		_, err = ds.CreateLocalDatastore(ctx, cmd.Name, cmd.Path)
   331  		if err != nil {
   332  			return err
   333  		}
   334  	}
   335  
   336  	return nil
   337  }