From a316da5c7c2f19e33102b685c416a89f63e1dcb5 Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Tue, 20 Aug 2024 10:02:13 -0700 Subject: [PATCH 1/9] api: VirtualMachine.AttachDisk unitNumber param is optional BREAKING: switching from int32 to *int32, otherwise the client must choose a valid unitNumber. vCenter will choose a unitNumber when not provided. --- object/virtual_machine.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/object/virtual_machine.go b/object/virtual_machine.go index 4665fcb74..e1808b493 100644 --- a/object/virtual_machine.go +++ b/object/virtual_machine.go @@ -541,13 +541,13 @@ func (v VirtualMachine) RemoveDevice(ctx context.Context, keepFiles bool, device } // AttachDisk attaches the given disk to the VirtualMachine -func (v VirtualMachine) AttachDisk(ctx context.Context, id string, datastore *Datastore, controllerKey int32, unitNumber int32) error { +func (v VirtualMachine) AttachDisk(ctx context.Context, id string, datastore *Datastore, controllerKey int32, unitNumber *int32) error { req := types.AttachDisk_Task{ This: v.Reference(), DiskId: types.ID{Id: id}, Datastore: datastore.Reference(), ControllerKey: controllerKey, - UnitNumber: &unitNumber, + UnitNumber: unitNumber, } res, err := methods.AttachDisk_Task(ctx, v.c, &req) From f881d9b6cf670e6ff50ec064e346e7b2e323585c Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Tue, 20 Aug 2024 10:04:06 -0700 Subject: [PATCH 2/9] govc add disk.attach and disk.detach commands These commands use FCD (First Class Disk) methods added in vSphere 6.7 While the existing 'vm.disk.attach' and 'device.remove -keep' commands cand also attach and detach FCDs, these commands take the disk ID as input, rather than Datastore path (attach) and device name (detach). --- govc/disk/attach.go | 97 +++++++++++++++++++++++++++++++++++++++++++++ govc/disk/detach.go | 66 ++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 govc/disk/attach.go create mode 100644 govc/disk/detach.go diff --git a/govc/disk/attach.go b/govc/disk/attach.go new file mode 100644 index 000000000..3f7f959c8 --- /dev/null +++ b/govc/disk/attach.go @@ -0,0 +1,97 @@ +/* +Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package disk + +import ( + "context" + "flag" + + "github.com/vmware/govmomi/govc/cli" + "github.com/vmware/govmomi/govc/flags" + "github.com/vmware/govmomi/vim25/mo" +) + +type attach struct { + *flags.VirtualMachineFlag + *flags.DatastoreFlag +} + +func init() { + cli.Register("disk.attach", &attach{}) +} + +func (cmd *attach) Register(ctx context.Context, f *flag.FlagSet) { + cmd.VirtualMachineFlag, ctx = flags.NewVirtualMachineFlag(ctx) + cmd.VirtualMachineFlag.Register(ctx, f) + cmd.DatastoreFlag, ctx = flags.NewDatastoreFlag(ctx) + cmd.DatastoreFlag.Register(ctx, f) +} + +func (cmd *attach) Process(ctx context.Context) error { + if err := cmd.VirtualMachineFlag.Process(ctx); err != nil { + return err + } + return cmd.DatastoreFlag.Process(ctx) +} + +func (cmd *attach) Usage() string { + return "ID" +} + +func (cmd *attach) Description() string { + return `Attach disk ID on VM. + +See also: govc vm.disk.attach + +Examples: + govc disk.attach -vm $vm ID + govc disk.attach -vm $vm -ds $ds ID` +} + +func (cmd *attach) Run(ctx context.Context, f *flag.FlagSet) error { + if f.NArg() != 1 { + return flag.ErrHelp + } + + ds, err := cmd.DatastoreIfSpecified() + if err != nil { + return err + } + + vm, err := cmd.VirtualMachine() + if err != nil { + return err + } + + if ds == nil { + var props mo.VirtualMachine + err = vm.Properties(ctx, vm.Reference(), []string{"datastore"}, &props) + if err != nil { + return err + } + if len(props.Datastore) != 1 { + ds, err = cmd.Datastore() // likely results in MultipleFoundError + if err != nil { + return err + } + } + } + + id := f.Arg(0) + + return vm.AttachDisk(ctx, id, ds, 0, nil) +} diff --git a/govc/disk/detach.go b/govc/disk/detach.go new file mode 100644 index 000000000..b135528b9 --- /dev/null +++ b/govc/disk/detach.go @@ -0,0 +1,66 @@ +/* +Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package disk + +import ( + "context" + "flag" + + "github.com/vmware/govmomi/govc/cli" + "github.com/vmware/govmomi/govc/flags" +) + +type detach struct { + *flags.VirtualMachineFlag +} + +func init() { + cli.Register("disk.detach", &detach{}) +} + +func (cmd *detach) Register(ctx context.Context, f *flag.FlagSet) { + cmd.VirtualMachineFlag, ctx = flags.NewVirtualMachineFlag(ctx) + cmd.VirtualMachineFlag.Register(ctx, f) +} + +func (cmd *detach) Usage() string { + return "ID" +} + +func (cmd *detach) Description() string { + return `Detach disk ID from VM. + +See also: govc device.remove + +Examples: + govc disk.detach -vm $vm ID` +} + +func (cmd *detach) Run(ctx context.Context, f *flag.FlagSet) error { + if f.NArg() != 1 { + return flag.ErrHelp + } + + vm, err := cmd.VirtualMachine() + if err != nil { + return err + } + + id := f.Arg(0) + + return vm.DetachDisk(ctx, id) +} From f06d08349dbfad9b9f064c3f1d7059bbd12fe167 Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Wed, 21 Aug 2024 20:30:32 -0700 Subject: [PATCH 3/9] vcsim: add VirtualMachine AttachDisk and DetachDisk methods --- govc/test/disk.bats | 8 ++++ simulator/virtual_machine.go | 57 ++++++++++++++++++++++++++++ simulator/vstorage_object_manager.go | 8 +++- 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/govc/test/disk.bats b/govc/test/disk.bats index cf151a424..288dc193f 100755 --- a/govc/test/disk.bats +++ b/govc/test/disk.bats @@ -26,6 +26,14 @@ load test_helper govc disk.ls -json "$id" | jq . + vm=DC0_H0_VM0 + + run govc disk.attach -vm $vm "$id" + assert_success + + run govc disk.detach -vm $vm "$id" + assert_success + run govc disk.rm "$id" assert_success diff --git a/simulator/virtual_machine.go b/simulator/virtual_machine.go index 263f41ac2..8448cd770 100644 --- a/simulator/virtual_machine.go +++ b/simulator/virtual_machine.go @@ -2605,6 +2605,63 @@ func (vm *VirtualMachine) RemoveAllSnapshotsTask(ctx *Context, req *types.Remove } } +func (vm *VirtualMachine) fcd(ctx *Context, ds types.ManagedObjectReference, id types.ID) *VStorageObject { + m := ctx.Map.Get(*ctx.Map.content().VStorageObjectManager).(*VcenterVStorageObjectManager) + if ds.Value != "" { + return m.objects[ds][id] + } + for _, set := range m.objects { + for key, val := range set { + if key == id { + return val + } + } + } + return nil +} + +func (vm *VirtualMachine) AttachDiskTask(ctx *Context, req *types.AttachDisk_Task) soap.HasFault { + task := CreateTask(vm, "attachDisk", func(t *Task) (types.AnyType, types.BaseMethodFault) { + fcd := vm.fcd(ctx, req.Datastore, req.DiskId) + if fcd == nil { + return nil, new(types.InvalidArgument) + } + + fcd.Config.ConsumerId = []types.ID{{Id: vm.Config.Uuid}} + + // TODO: add device + + return nil, nil + }) + + return &methods.AttachDisk_TaskBody{ + Res: &types.AttachDisk_TaskResponse{ + Returnval: task.Run(ctx), + }, + } +} + +func (vm *VirtualMachine) DetachDiskTask(ctx *Context, req *types.DetachDisk_Task) soap.HasFault { + task := CreateTask(vm, "detachDisk", func(t *Task) (types.AnyType, types.BaseMethodFault) { + fcd := vm.fcd(ctx, types.ManagedObjectReference{}, req.DiskId) + if fcd == nil { + return nil, new(types.InvalidArgument) + } + + fcd.Config.ConsumerId = nil + + // TODO: remove device + + return nil, nil + }) + + return &methods.DetachDisk_TaskBody{ + Res: &types.DetachDisk_TaskResponse{ + Returnval: task.Run(ctx), + }, + } +} + func (vm *VirtualMachine) ShutdownGuest(ctx *Context, c *types.ShutdownGuest) soap.HasFault { r := &methods.ShutdownGuestBody{} diff --git a/simulator/vstorage_object_manager.go b/simulator/vstorage_object_manager.go index 5dc138e01..451dc043f 100644 --- a/simulator/vstorage_object_manager.go +++ b/simulator/vstorage_object_manager.go @@ -1,11 +1,11 @@ /* -Copyright (c) 2018 VMware, Inc. All Rights Reserved. +Copyright (c) 2018-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -287,6 +287,10 @@ func (m *VcenterVStorageObjectManager) DeleteVStorageObjectTask(ctx *Context, re return nil, &types.InvalidArgument{} } + if len(obj.Config.ConsumerId) != 0 { + return nil, &types.InvalidState{} + } + backing := obj.Config.Backing.(*types.BaseConfigInfoDiskFileBackingInfo) ds := ctx.Map.Get(req.Datastore).(*Datastore) dc := ctx.Map.getEntityDatacenter(ds) From caad54aa419d19de6a1bb3e95e0167d87ead0f87 Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Tue, 3 Sep 2024 17:07:19 -0700 Subject: [PATCH 4/9] api: add VirtualMachine.AddDeviceWithProfile method Storage profile can be specified when creating or updating a VirtualDisk. --- object/virtual_machine.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/object/virtual_machine.go b/object/virtual_machine.go index e1808b493..3f8f6ebe2 100644 --- a/object/virtual_machine.go +++ b/object/virtual_machine.go @@ -1,5 +1,5 @@ /* -Copyright (c) 2015-2023 VMware, Inc. All Rights Reserved. +Copyright (c) 2015-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -500,7 +500,7 @@ func diskFileOperation(op types.VirtualDeviceConfigSpecOperation, fop types.Virt return "" } -func (v VirtualMachine) configureDevice(ctx context.Context, op types.VirtualDeviceConfigSpecOperation, fop types.VirtualDeviceConfigSpecFileOperation, devices ...types.BaseVirtualDevice) error { +func (v VirtualMachine) configureDevice(ctx context.Context, profile []types.BaseVirtualMachineProfileSpec, op types.VirtualDeviceConfigSpecOperation, fop types.VirtualDeviceConfigSpecFileOperation, devices ...types.BaseVirtualDevice) error { spec := types.VirtualMachineConfigSpec{} for _, device := range devices { @@ -508,6 +508,7 @@ func (v VirtualMachine) configureDevice(ctx context.Context, op types.VirtualDev Device: device, Operation: op, FileOperation: diskFileOperation(op, fop, device), + Profile: profile, } spec.DeviceChange = append(spec.DeviceChange, config) @@ -523,12 +524,22 @@ func (v VirtualMachine) configureDevice(ctx context.Context, op types.VirtualDev // AddDevice adds the given devices to the VirtualMachine func (v VirtualMachine) AddDevice(ctx context.Context, device ...types.BaseVirtualDevice) error { - return v.configureDevice(ctx, types.VirtualDeviceConfigSpecOperationAdd, types.VirtualDeviceConfigSpecFileOperationCreate, device...) + return v.AddDeviceWithProfile(ctx, nil, device...) +} + +// AddDeviceWithProfile adds the given devices to the VirtualMachine with the given profile +func (v VirtualMachine) AddDeviceWithProfile(ctx context.Context, profile []types.BaseVirtualMachineProfileSpec, device ...types.BaseVirtualDevice) error { + return v.configureDevice(ctx, profile, types.VirtualDeviceConfigSpecOperationAdd, types.VirtualDeviceConfigSpecFileOperationCreate, device...) } // EditDevice edits the given (existing) devices on the VirtualMachine func (v VirtualMachine) EditDevice(ctx context.Context, device ...types.BaseVirtualDevice) error { - return v.configureDevice(ctx, types.VirtualDeviceConfigSpecOperationEdit, types.VirtualDeviceConfigSpecFileOperationReplace, device...) + return v.EditDeviceWithProfile(ctx, nil, device...) +} + +// EditDeviceWithProfile edits the given (existing) devices on the VirtualMachine with the given profile +func (v VirtualMachine) EditDeviceWithProfile(ctx context.Context, profile []types.BaseVirtualMachineProfileSpec, device ...types.BaseVirtualDevice) error { + return v.configureDevice(ctx, profile, types.VirtualDeviceConfigSpecOperationEdit, types.VirtualDeviceConfigSpecFileOperationReplace, device...) } // RemoveDevice removes the given devices on the VirtualMachine @@ -537,7 +548,7 @@ func (v VirtualMachine) RemoveDevice(ctx context.Context, keepFiles bool, device if keepFiles { fop = "" } - return v.configureDevice(ctx, types.VirtualDeviceConfigSpecOperationRemove, fop, device...) + return v.configureDevice(ctx, nil, types.VirtualDeviceConfigSpecOperationRemove, fop, device...) } // AttachDisk attaches the given disk to the VirtualMachine From 51105db8c8985b3bb3ef0fd20066945cce716254 Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Tue, 3 Sep 2024 17:13:30 -0700 Subject: [PATCH 5/9] chore: govc: add common StorageProfileFlag Fold storage profile related duplication into a shared type + helpers. --- govc/flags/storage_profile.go | 124 ++++++++++++++++++++++++++++++++++ govc/library/clone.go | 28 +++++--- govc/library/deploy.go | 31 ++++++--- govc/namespace/create.go | 2 +- govc/namespace/flag.go | 29 ++------ govc/test/namespace.bats | 9 +++ govc/vm/create.go | 28 +++----- 7 files changed, 190 insertions(+), 61 deletions(-) create mode 100644 govc/flags/storage_profile.go diff --git a/govc/flags/storage_profile.go b/govc/flags/storage_profile.go new file mode 100644 index 000000000..3dc1dab35 --- /dev/null +++ b/govc/flags/storage_profile.go @@ -0,0 +1,124 @@ +/* +Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flags + +import ( + "context" + "errors" + "flag" + "fmt" + + "github.com/vmware/govmomi/vim25/types" +) + +type StorageProfileFlag struct { + *ClientFlag + + Name []string + + option string +} + +func NewStorageProfileFlag(ctx context.Context, option ...string) (*StorageProfileFlag, context.Context) { + v := &StorageProfileFlag{} + if len(option) == 1 { + v.option = option[0] + } else { + v.option = "profile" + } + v.ClientFlag, ctx = NewClientFlag(ctx) + return v, ctx +} + +func (e *StorageProfileFlag) String() string { + return fmt.Sprint(e.Name) +} + +func (e *StorageProfileFlag) Set(value string) error { + e.Name = append(e.Name, value) + return nil +} + +func (flag *StorageProfileFlag) Register(ctx context.Context, f *flag.FlagSet) { + flag.ClientFlag.Register(ctx, f) + + f.Var(flag, flag.option, "Storage profile name or ID") +} + +func (flag *StorageProfileFlag) StorageProfileList(ctx context.Context) ([]string, error) { + if len(flag.Name) == 0 { + return nil, nil + } + + c, err := flag.PbmClient() + if err != nil { + return nil, err + } + m, err := c.ProfileMap(ctx) + if err != nil { + return nil, err + } + + list := make([]string, len(flag.Name)) + + for i, name := range flag.Name { + p, ok := m.Name[name] + if !ok { + return nil, fmt.Errorf("storage profile %q not found", name) + } + + list[i] = p.GetPbmProfile().ProfileId.UniqueId + } + + return list, nil +} + +func (flag *StorageProfileFlag) StorageProfile(ctx context.Context) (string, error) { + switch len(flag.Name) { + case 0: + return "", nil + case 1: + default: + return "", errors.New("only 1 '-profile' can be specified") + } + + list, err := flag.StorageProfileList(ctx) + if err != nil { + return "", err + } + + return list[0], nil +} + +func (flag *StorageProfileFlag) StorageProfileSpec(ctx context.Context) ([]types.BaseVirtualMachineProfileSpec, error) { + if len(flag.Name) == 0 { + return nil, nil + } + + list, err := flag.StorageProfileList(ctx) + if err != nil { + return nil, err + } + + spec := make([]types.BaseVirtualMachineProfileSpec, len(list)) + for i, name := range list { + spec[i] = &types.VirtualMachineDefinedProfileSpec{ + ProfileId: name, + } + } + return spec, nil +} diff --git a/govc/library/clone.go b/govc/library/clone.go index c72d78fd9..06d75194d 100644 --- a/govc/library/clone.go +++ b/govc/library/clone.go @@ -1,11 +1,11 @@ /* -Copyright (c) 2019 VMware, Inc. All Rights Reserved. +Copyright (c) 2019-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -33,11 +33,11 @@ type clone struct { *flags.ResourcePoolFlag *flags.HostSystemFlag *flags.VirtualMachineFlag + *flags.StorageProfileFlag - profile string - ovf bool - extra bool - mac bool + ovf bool + extra bool + mac bool } func init() { @@ -63,10 +63,12 @@ func (cmd *clone) Register(ctx context.Context, f *flag.FlagSet) { cmd.VirtualMachineFlag, ctx = flags.NewVirtualMachineFlag(ctx) cmd.VirtualMachineFlag.Register(ctx, f) + cmd.StorageProfileFlag, ctx = flags.NewStorageProfileFlag(ctx) + cmd.StorageProfileFlag.Register(ctx, f) + f.BoolVar(&cmd.ovf, "ovf", false, "Clone as OVF (default is VM Template)") f.BoolVar(&cmd.extra, "e", false, "Include extra configuration") f.BoolVar(&cmd.mac, "m", false, "Preserve MAC-addresses on network adapters") - f.StringVar(&cmd.profile, "profile", "", "Storage profile") } func (cmd *clone) Process(ctx context.Context) error { @@ -85,6 +87,9 @@ func (cmd *clone) Process(ctx context.Context) error { if err := cmd.FolderFlag.Process(ctx); err != nil { return err } + if err := cmd.StorageProfileFlag.Process(ctx); err != nil { + return err + } return cmd.VirtualMachineFlag.Process(ctx) } @@ -176,14 +181,19 @@ func (cmd *clone) Run(ctx context.Context, f *flag.FlagSet) error { return nil } + profile, err := cmd.StorageProfile(ctx) + if err != nil { + return err + } + storage := &vcenter.DiskStorage{ Datastore: dsID, StoragePolicy: &vcenter.StoragePolicy{ - Policy: cmd.profile, + Policy: profile, Type: "USE_SOURCE_POLICY", }, } - if cmd.profile != "" { + if profile != "" { storage.StoragePolicy.Type = "USE_SPECIFIED_POLICY" } diff --git a/govc/library/deploy.go b/govc/library/deploy.go index e3a178902..57cfee6f1 100644 --- a/govc/library/deploy.go +++ b/govc/library/deploy.go @@ -1,11 +1,11 @@ /* -Copyright (c) 2019 VMware, Inc. All Rights Reserved. +Copyright (c) 2019-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -35,10 +35,10 @@ type deploy struct { *flags.ResourcePoolFlag *flags.HostSystemFlag *flags.FolderFlag + *flags.StorageProfileFlag *importx.OptionsFlag - profile string - config string + config string } func init() { @@ -58,11 +58,12 @@ func (cmd *deploy) Register(ctx context.Context, f *flag.FlagSet) { cmd.FolderFlag, ctx = flags.NewFolderFlag(ctx) cmd.FolderFlag.Register(ctx, f) + cmd.StorageProfileFlag, ctx = flags.NewStorageProfileFlag(ctx) + cmd.StorageProfileFlag.Register(ctx, f) + cmd.OptionsFlag = new(importx.OptionsFlag) cmd.OptionsFlag.Register(ctx, f) - f.StringVar(&cmd.profile, "profile", "", "Storage profile") - if cli.ShowUnreleased() { f.StringVar(&cmd.config, "config", "", "VM config spec") } @@ -81,6 +82,9 @@ func (cmd *deploy) Process(ctx context.Context) error { if err := cmd.FolderFlag.Process(ctx); err != nil { return err } + if err := cmd.StorageProfileFlag.Process(ctx); err != nil { + return err + } return cmd.OptionsFlag.Process(ctx) } @@ -114,13 +118,13 @@ func (cmd *deploy) Run(ctx context.Context, f *flag.FlagSet) error { if err != nil { return err } - cmd.KeepAlive(vc) + cmd.FolderFlag.KeepAlive(vc) c, err := cmd.DatastoreFlag.RestClient() if err != nil { return err } - cmd.KeepAlive(c) + cmd.FolderFlag.KeepAlive(c) m := vcenter.NewManager(c) @@ -194,6 +198,11 @@ func (cmd *deploy) Run(ctx context.Context, f *flag.FlagSet) error { var ref *types.ManagedObjectReference + profile, err := cmd.StorageProfile(ctx) + if err != nil { + return err + } + switch item.Type { case library.ItemTypeOVF: deploy := vcenter.Deploy{ @@ -217,7 +226,7 @@ func (cmd *deploy) Run(ctx context.Context, f *flag.FlagSet) error { }, NetworkMappings: networks, StorageProvisioning: cmd.Options.DiskProvisioning, - StorageProfileID: cmd.profile, + StorageProfileID: profile, }, Target: vcenter.Target{ ResourcePoolID: rp.Reference().Value, @@ -241,11 +250,11 @@ func (cmd *deploy) Run(ctx context.Context, f *flag.FlagSet) error { storage := &vcenter.DiskStorage{ Datastore: dsID, StoragePolicy: &vcenter.StoragePolicy{ - Policy: cmd.profile, + Policy: profile, Type: "USE_SOURCE_POLICY", }, } - if cmd.profile != "" { + if profile != "" { storage.StoragePolicy.Type = "USE_SPECIFIED_POLICY" } diff --git a/govc/namespace/create.go b/govc/namespace/create.go index e4e99737a..dc37e052b 100644 --- a/govc/namespace/create.go +++ b/govc/namespace/create.go @@ -88,7 +88,7 @@ func (cmd *create) Run(ctx context.Context, f *flag.FlagSet) error { return flag.ErrHelp } - rc, err := cmd.RestClient() + rc, err := cmd.namespaceFlag.RestClient() if err != nil { return err } diff --git a/govc/namespace/flag.go b/govc/namespace/flag.go index a0db25b3c..3f50fb073 100644 --- a/govc/namespace/flag.go +++ b/govc/namespace/flag.go @@ -25,24 +25,23 @@ import ( ) type namespaceFlag struct { - *flags.ClientFlag + *flags.StorageProfileFlag library flags.StringList vmclass flags.StringList - storage flags.StringList + storage []string } func (ns *namespaceFlag) Register(ctx context.Context, f *flag.FlagSet) { - ns.ClientFlag, ctx = flags.NewClientFlag(ctx) - ns.ClientFlag.Register(ctx, f) + ns.StorageProfileFlag, ctx = flags.NewStorageProfileFlag(ctx, "storage") + ns.StorageProfileFlag.Register(ctx, f) f.Var(&ns.library, "library", "Content library IDs to associate with the vSphere Namespace.") f.Var(&ns.vmclass, "vmclass", "Virtual machine class IDs to associate with the vSphere Namespace.") - f.Var(&ns.storage, "storage", "Storage profile IDs to associate with the vSphere Namespace.") } func (ns *namespaceFlag) Process(ctx context.Context) error { - if err := ns.ClientFlag.Process(ctx); err != nil { + if err := ns.StorageProfileFlag.Process(ctx); err != nil { return err } @@ -58,23 +57,9 @@ func (ns *namespaceFlag) Process(ctx context.Context) error { } } - pc, err := ns.PbmClient() - if err != nil { - return err - } - - m, err := pc.ProfileMap(ctx) - if err != nil { - return err - } - - for i, name := range ns.storage { - if n, ok := m.Name[name]; ok { - ns.storage[i] = n.GetPbmProfile().ProfileId.UniqueId - } - } + ns.storage, err = ns.StorageProfileList(ctx) - return nil + return err } func (ns *namespaceFlag) storageSpec() []namespace.StorageSpec { diff --git a/govc/test/namespace.bats b/govc/test/namespace.bats index 0ca1fc90f..09a4c4194 100755 --- a/govc/test/namespace.bats +++ b/govc/test/namespace.bats @@ -130,6 +130,15 @@ load test_helper ns=$(govc namespace.info -json test-namespace-2 | jq) assert_equal "2" $(echo $ns | jq -r '."vm_service_spec"."content_libraries"' | jq length) + + run govc namespace.create -cluster DC0_C0 -storage MyStoragePolicy test-namespace-2 + assert_failure # storage policy does not exist + + run govc storage.policy.create -category my_cat -tag my_tag MyStoragePolicy + assert_success + + run govc namespace.create -cluster DC0_C0 -storage MyStoragePolicy test-namespace-2 + assert_success } @test "namespace.update" { diff --git a/govc/vm/create.go b/govc/vm/create.go index 757fc2ea5..35f98d974 100644 --- a/govc/vm/create.go +++ b/govc/vm/create.go @@ -51,6 +51,7 @@ type create struct { *flags.HostSystemFlag *flags.NetworkFlag *flags.FolderFlag + *flags.StorageProfileFlag name string memory int @@ -64,7 +65,6 @@ type create struct { firmware string version string place bool - profile string iso string isoDatastoreFlag *flags.DatastoreFlag @@ -119,6 +119,9 @@ func (cmd *create) Register(ctx context.Context, f *flag.FlagSet) { cmd.FolderFlag, ctx = flags.NewFolderFlag(ctx) cmd.FolderFlag.Register(ctx, f) + cmd.StorageProfileFlag, ctx = flags.NewStorageProfileFlag(ctx) + cmd.StorageProfileFlag.Register(ctx, f) + f.IntVar(&cmd.memory, "m", 1024, "Size in MB of memory") f.IntVar(&cmd.cpus, "c", 1, "Number of CPUs") f.StringVar(&cmd.guestID, "g", "otherGuest", "Guest OS ID") @@ -128,7 +131,6 @@ func (cmd *create) Register(ctx context.Context, f *flag.FlagSet) { f.StringVar(&cmd.controller, "disk.controller", "scsi", "Disk controller type") f.StringVar(&cmd.annotation, "annotation", "", "VM description") f.StringVar(&cmd.firmware, "firmware", FirmwareTypes[0], FirmwareUsage) - f.StringVar(&cmd.profile, "profile", "", "Storage profile name or ID") if cli.ShowUnreleased() { f.BoolVar(&cmd.place, "place", false, "Place VM without creating") } @@ -178,6 +180,9 @@ func (cmd *create) Process(ctx context.Context) error { if err := cmd.FolderFlag.Process(ctx); err != nil { return err } + if err := cmd.StorageProfileFlag.Process(ctx); err != nil { + return err + } // Default iso/disk datastores to the VM's datastore if cmd.isoDatastoreFlag.Name == "" { @@ -411,22 +416,9 @@ func (cmd *create) createVM(ctx context.Context) (*object.Task, error) { Version: cmd.version, } - if cmd.profile != "" { - c, err := cmd.PbmClient() - if err != nil { - return nil, err - } - m, err := c.ProfileMap(ctx) - if err != nil { - return nil, err - } - p, ok := m.Name[cmd.profile] - if !ok { - return nil, fmt.Errorf("profile %q not found", cmd.profile) - } - spec.VmProfile = []types.BaseVirtualMachineProfileSpec{&types.VirtualMachineDefinedProfileSpec{ - ProfileId: p.GetPbmProfile().ProfileId.UniqueId, - }} + spec.VmProfile, err = cmd.StorageProfileSpec(ctx) + if err != nil { + return nil, err } devices, err = cmd.addStorage(nil) From e73c34fa0d3116c1b6b6669069a3ae84a6071627 Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Tue, 3 Sep 2024 17:15:20 -0700 Subject: [PATCH 6/9] govc: add vm.disk.attach '-profile' flag --- govc/vm/disk/attach.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/govc/vm/disk/attach.go b/govc/vm/disk/attach.go index 6e06aecd0..36cf2d404 100644 --- a/govc/vm/disk/attach.go +++ b/govc/vm/disk/attach.go @@ -1,11 +1,11 @@ /* -Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved. +Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -30,6 +30,7 @@ import ( type attach struct { *flags.DatastoreFlag *flags.VirtualMachineFlag + *flags.StorageProfileFlag persist bool link bool @@ -48,6 +49,8 @@ func (cmd *attach) Register(ctx context.Context, f *flag.FlagSet) { cmd.DatastoreFlag.Register(ctx, f) cmd.VirtualMachineFlag, ctx = flags.NewVirtualMachineFlag(ctx) cmd.VirtualMachineFlag.Register(ctx, f) + cmd.StorageProfileFlag, ctx = flags.NewStorageProfileFlag(ctx) + cmd.StorageProfileFlag.Register(ctx, f) f.BoolVar(&cmd.persist, "persist", true, "Persist attached disk") f.BoolVar(&cmd.link, "link", true, "Link specified disk") @@ -64,6 +67,9 @@ func (cmd *attach) Process(ctx context.Context) error { if err := cmd.VirtualMachineFlag.Process(ctx); err != nil { return err } + if err := cmd.StorageProfileFlag.Process(ctx); err != nil { + return err + } return nil } @@ -129,5 +135,10 @@ func (cmd *attach) Run(ctx context.Context, f *flag.FlagSet) error { backing.DiskMode = cmd.mode } - return vm.AddDevice(ctx, disk) + profile, err := cmd.StorageProfileSpec(ctx) + if err != nil { + return err + } + + return vm.AddDeviceWithProfile(ctx, profile, disk) } From ea3cf25712897b0af9eb3f112b3711178e8d5d6f Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Wed, 4 Sep 2024 08:46:09 -0700 Subject: [PATCH 7/9] govc: add vm.disk.create '-profile' flag --- govc/test/vm.bats | 7 +++++++ govc/vm/disk/create.go | 13 ++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/govc/test/vm.bats b/govc/test/vm.bats index da01c59b8..7628a2475 100755 --- a/govc/test/vm.bats +++ b/govc/test/vm.bats @@ -695,6 +695,13 @@ load test_helper run govc vm.disk.change -vm "$vm" -disk.name "$disk" -size 1M assert_failure # cannot shrink disk + + name=$(new_id) + run govc vm.disk.create -vm "$vm" -name "$vm/$name" -profile enoent + assert_failure # profile does not exist + + run govc vm.disk.create -vm "$vm" -name "$vm/$name" -profile "vSAN Default Storage Policy" + assert_success } @test "vm.disk.attach" { diff --git a/govc/vm/disk/create.go b/govc/vm/disk/create.go index 44822ff4e..7c4b27fb4 100644 --- a/govc/vm/disk/create.go +++ b/govc/vm/disk/create.go @@ -33,6 +33,7 @@ type create struct { *flags.DatastoreFlag *flags.OutputFlag *flags.VirtualMachineFlag + *flags.StorageProfileFlag controller string Name string @@ -58,6 +59,8 @@ func (cmd *create) Register(ctx context.Context, f *flag.FlagSet) { cmd.OutputFlag.Register(ctx, f) cmd.VirtualMachineFlag, ctx = flags.NewVirtualMachineFlag(ctx) cmd.VirtualMachineFlag.Register(ctx, f) + cmd.StorageProfileFlag, ctx = flags.NewStorageProfileFlag(ctx) + cmd.StorageProfileFlag.Register(ctx, f) err := (&cmd.Bytes).Set("10G") if err != nil { @@ -83,6 +86,9 @@ func (cmd *create) Process(ctx context.Context) error { if err := cmd.VirtualMachineFlag.Process(ctx); err != nil { return err } + if err := cmd.StorageProfileFlag.Process(ctx); err != nil { + return err + } return nil } @@ -112,6 +118,11 @@ func (cmd *create) Run(ctx context.Context, f *flag.FlagSet) error { return err } + profile, err := cmd.StorageProfileSpec(ctx) + if err != nil { + return err + } + devices, err := vm.Device(ctx) if err != nil { return err @@ -154,5 +165,5 @@ func (cmd *create) Run(ctx context.Context, f *flag.FlagSet) error { _, _ = cmd.Log("Creating disk\n") disk.CapacityInKB = int64(cmd.Bytes) / 1024 - return vm.AddDevice(ctx, disk) + return vm.AddDeviceWithProfile(ctx, profile, disk) } From 8fd869123fb3e2443155a84713b6f938e48522f6 Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Wed, 4 Sep 2024 10:53:17 -0700 Subject: [PATCH 8/9] govc: add disk.create '-profile' flag --- govc/disk/create.go | 17 +++++++++++++++-- govc/test/disk.bats | 7 +++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/govc/disk/create.go b/govc/disk/create.go index e7f8958ab..49db23432 100644 --- a/govc/disk/create.go +++ b/govc/disk/create.go @@ -1,11 +1,11 @@ /* -Copyright (c) 2018 VMware, Inc. All Rights Reserved. +Copyright (c) 2018-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -34,6 +34,7 @@ type disk struct { *flags.DatastoreFlag *flags.ResourcePoolFlag *flags.StoragePodFlag + *flags.StorageProfileFlag size units.ByteSize keep *bool @@ -50,6 +51,9 @@ func (cmd *disk) Register(ctx context.Context, f *flag.FlagSet) { cmd.StoragePodFlag, ctx = flags.NewStoragePodFlag(ctx) cmd.StoragePodFlag.Register(ctx, f) + cmd.StorageProfileFlag, ctx = flags.NewStorageProfileFlag(ctx) + cmd.StorageProfileFlag.Register(ctx, f) + cmd.ResourcePoolFlag, ctx = flags.NewResourcePoolFlag(ctx) cmd.ResourcePoolFlag.Register(ctx, f) @@ -65,6 +69,9 @@ func (cmd *disk) Process(ctx context.Context) error { if err := cmd.StoragePodFlag.Process(ctx); err != nil { return err } + if err := cmd.StorageProfileFlag.Process(ctx); err != nil { + return err + } return cmd.ResourcePoolFlag.Process(ctx) } @@ -108,12 +115,18 @@ func (cmd *disk) Run(ctx context.Context, f *flag.FlagSet) error { } } + profile, err := cmd.StorageProfileSpec(ctx) + if err != nil { + return err + } + m := vslm.NewObjectManager(c) spec := types.VslmCreateSpec{ Name: name, CapacityInMB: int64(cmd.size) / units.MB, KeepAfterDeleteVm: cmd.keep, + Profile: profile, BackingSpec: &types.VslmCreateSpecDiskFileBackingSpec{ VslmCreateSpecBackingSpec: types.VslmCreateSpecBackingSpec{ Datastore: ds.Reference(), diff --git a/govc/test/disk.bats b/govc/test/disk.bats index 288dc193f..530487867 100755 --- a/govc/test/disk.bats +++ b/govc/test/disk.bats @@ -39,6 +39,13 @@ load test_helper run govc disk.rm "$id" assert_failure + + name=$(new_id) + run govc disk.create -profile enoent "$name" + assert_failure # profile does not exist + + run govc disk.create -profile "vSAN Default Storage Policy" "$name" + assert_success } @test "disk.create -datastore-cluster" { From 93da4a20340b269f3170e2d082fa4d27870317fc Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Wed, 4 Sep 2024 10:55:27 -0700 Subject: [PATCH 9/9] chore: sync generated govc/USAGE.md --- govc/USAGE.md | 49 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/govc/USAGE.md b/govc/USAGE.md index fcaa7ccbd..38549fe4a 100644 --- a/govc/USAGE.md +++ b/govc/USAGE.md @@ -122,7 +122,9 @@ but appear via `govc $cmd -h`: - [device.serial.connect](#deviceserialconnect) - [device.serial.disconnect](#deviceserialdisconnect) - [device.usb.add](#deviceusbadd) + - [disk.attach](#diskattach) - [disk.create](#diskcreate) + - [disk.detach](#diskdetach) - [disk.ls](#diskls) - [disk.register](#diskregister) - [disk.rm](#diskrm) @@ -2008,6 +2010,24 @@ Options: -vm= Virtual machine [GOVC_VM] ``` +## disk.attach + +``` +Usage: govc disk.attach [OPTIONS] ID + +Attach disk ID on VM. + +See also: govc vm.disk.attach + +Examples: + govc disk.attach -vm $vm ID + govc disk.attach -vm $vm -ds $ds ID + +Options: + -ds= Datastore [GOVC_DATASTORE] + -vm= Virtual machine [GOVC_VM] +``` + ## disk.create ``` @@ -2023,9 +2043,26 @@ Options: -ds= Datastore [GOVC_DATASTORE] -keep= Keep disk after VM is deleted -pool= Resource pool [GOVC_RESOURCE_POOL] + -profile=[] Storage profile name or ID -size=10.0GB Size of new disk ``` +## disk.detach + +``` +Usage: govc disk.detach [OPTIONS] ID + +Detach disk ID from VM. + +See also: govc device.remove + +Examples: + govc disk.detach -vm $vm ID + +Options: + -vm= Virtual machine [GOVC_VM] +``` + ## disk.ls ``` @@ -3602,7 +3639,7 @@ Options: -m=false Preserve MAC-addresses on network adapters -ovf=false Clone as OVF (default is VM Template) -pool= Resource pool [GOVC_RESOURCE_POOL] - -profile= Storage profile + -profile=[] Storage profile name or ID -vm= Virtual machine [GOVC_VM] ``` @@ -3667,7 +3704,7 @@ Options: -host= Host system [GOVC_HOST] -options= Options spec file path for VM deployment -pool= Resource pool [GOVC_RESOURCE_POOL] - -profile= Storage profile + -profile=[] Storage profile name or ID ``` ## library.evict @@ -4434,7 +4471,7 @@ Examples: Options: -cluster= Cluster [GOVC_CLUSTER] -library=[] Content library IDs to associate with the vSphere Namespace. - -storage=[] Storage profile IDs to associate with the vSphere Namespace. + -storage=[] Storage profile name or ID -vmclass=[] Virtual machine class IDs to associate with the vSphere Namespace. ``` @@ -4609,7 +4646,7 @@ Examples: Options: -library=[] Content library IDs to associate with the vSphere Namespace. - -storage=[] Storage profile IDs to associate with the vSphere Namespace. + -storage=[] Storage profile name or ID -vmclass=[] Virtual machine class IDs to associate with the vSphere Namespace. ``` @@ -6449,7 +6486,7 @@ Options: -net.protocol= Network device protocol. Applicable to vmxnet3vrdma. Default to 'rocev2' -on=true Power on VM -pool= Resource pool [GOVC_RESOURCE_POOL] - -profile= Storage profile name or ID + -profile=[] Storage profile name or ID -version= ESXi hardware version [2|3|4|5.0|5.1|5.5|6.0|6.5|6.7|6.7.2|7.0|7.0.1|7.0.2|8.0|8.0.1|8.0.2] ``` @@ -6693,6 +6730,7 @@ Options: -link=true Link specified disk -mode= Disk mode (persistent|nonpersistent|undoable|independent_persistent|independent_nonpersistent|append) -persist=true Persist attached disk + -profile=[] Storage profile name or ID -sharing= Sharing (sharingNone|sharingMultiWriter) -vm= Virtual machine [GOVC_VM] ``` @@ -6741,6 +6779,7 @@ Options: -eager=false Eagerly scrub new disk -mode=persistent Disk mode (persistent|nonpersistent|undoable|independent_persistent|independent_nonpersistent|append) -name= Name for new disk + -profile=[] Storage profile name or ID -sharing= Sharing (sharingNone|sharingMultiWriter) -size=10.0GB Size of new disk -thick=false Thick provision new disk