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 }