Skip to content

Commit

Permalink
Merge pull request #3682 from dougm/vcsim-vslm
Browse files Browse the repository at this point in the history
vcsim: add disk query and metadata support
  • Loading branch information
dougm authored Jan 27, 2025
2 parents a6f1508 + 116ba7e commit 13cb119
Show file tree
Hide file tree
Showing 12 changed files with 1,049 additions and 49 deletions.
95 changes: 92 additions & 3 deletions cli/disk/ls.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package disk

import (
"bytes"
"context"
"flag"
"fmt"
Expand All @@ -18,6 +19,7 @@ import (
"github.com/vmware/govmomi/fault"
"github.com/vmware/govmomi/units"
"github.com/vmware/govmomi/vim25/types"
vslm "github.com/vmware/govmomi/vslm/types"
)

type ls struct {
Expand All @@ -29,6 +31,7 @@ type ls struct {
category string
tag string
tags bool
query flags.StringList
}

func init() {
Expand All @@ -46,20 +49,37 @@ func (cmd *ls) Register(ctx context.Context, f *flag.FlagSet) {
f.StringVar(&cmd.category, "c", "", "Query tag category")
f.StringVar(&cmd.tag, "t", "", "Query tag name")
f.BoolVar(&cmd.tags, "T", false, "List attached tags")
f.Var(&cmd.query, "q", "Query spec")
}

func (cmd *ls) Usage() string {
return "[ID]..."
}

func (cmd *ls) Description() string {
return `List disk IDs on DS.
var fields vslm.VslmVsoVStorageObjectQuerySpecQueryFieldEnum

return fmt.Sprintf(`List disk IDs on DS.
The '-q' flag can be used to match disk fields.
Each query must be in the form of:
FIELD.OP=VAL
Where FIELD can be one of:
%s
And OP can be one of:
%s
Examples:
govc disk.ls
govc disk.ls -l -T
govc disk.ls -l e9b06a8b-d047-4d3c-b15b-43ea9608b1a6
govc disk.ls -c k8s-region -t us-west-2`
govc disk.ls -c k8s-region -t us-west-2
govc disk.ls -q capacity.ge=100 # capacity in MB
govc disk.ls -q name.sw=my-disk
govc disk.ls -q metadataKey.eq=cns.k8s.pvc.namespace -q metadataValue.eq=dev`,
strings.Join(fields.Strings(), "\n "),
aliasHelp())
}

type VStorageObject struct {
Expand All @@ -80,6 +100,70 @@ type lsResult struct {
Objects []VStorageObject `json:"objects"`
}

var alias = []struct {
name string
kind vslm.VslmVsoVStorageObjectQuerySpecQueryOperatorEnum
}{
{"eq", vslm.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumEquals},
{"ne", vslm.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumNotEquals},
{"lt", vslm.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumLessThan},
{"le", vslm.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumLessThanOrEqual},
{"gt", vslm.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumGreaterThan},
{"ge", vslm.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumGreaterThanOrEqual},
{"ct", vslm.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumContains},
{"sw", vslm.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumStartsWith},
{"ew", vslm.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumEndsWith},
}

func opAlias(value string) string {
if len(value) != 2 {
return value
}

for _, a := range alias {
if a.name == value {
return string(a.kind)
}
}

return value
}

func aliasHelp() string {
var help bytes.Buffer

for _, a := range alias {
fmt.Fprintf(&help, " %s %s\n", a.name, a.kind)
}

return help.String()
}

func (cmd *ls) querySpec() ([]vslm.VslmVsoVStorageObjectQuerySpec, error) {
q := make([]vslm.VslmVsoVStorageObjectQuerySpec, len(cmd.query))

for i, s := range cmd.query {
val := strings.SplitN(s, "=", 2)
if len(val) != 2 {
return nil, fmt.Errorf("invalid query: %s", s)
}

op := string(vslm.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumEquals)
field := strings.SplitN(val[0], ".", 2)
if len(field) == 2 {
op = field[1]
}

q[i] = vslm.VslmVsoVStorageObjectQuerySpec{
QueryField: field[0],
QueryOperator: opAlias(op),
QueryValue: []string{val[1]},
}
}

return q, nil
}

func (r *lsResult) Write(w io.Writer) error {
tw := tabwriter.NewWriter(r.cmd.Out, 2, 0, 2, ' ', 0)

Expand Down Expand Up @@ -124,11 +208,16 @@ func (cmd *ls) Run(ctx context.Context, f *flag.FlagSet) error {

filterNotFound := false
ids := f.Args()
q, err := cmd.querySpec()
if err != nil {
return err
}

if len(ids) == 0 {
filterNotFound = true
var oids []types.ID
if cmd.category == "" {
oids, err = m.List(ctx)
oids, err = m.List(ctx, q...)
} else {
oids, err = m.ListAttachedObjects(ctx, cmd.category, cmd.tag)
}
Expand Down
34 changes: 5 additions & 29 deletions cli/disk/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,41 +139,17 @@ func (m *Manager) Retrieve(ctx context.Context, id string) (*types.VStorageObjec
return m.GlobalObjectManager.Retrieve(ctx, types.ID{Id: id})
}

func (m *Manager) List(ctx context.Context) ([]types.ID, error) {
func (m *Manager) List(ctx context.Context, qs ...vslmtypes.VslmVsoVStorageObjectQuerySpec) ([]types.ID, error) {
if m.Datastore != nil {
return m.ObjectManager.List(ctx, m.Datastore)
}

// TODO: move this logic to vslm.GlobalObjectManager
// Need to better understand the QuerySpec + implement in vcsim.
// For now we just want the complete list of IDs (govc disk.ls)
maxResults := int32(100)

spec := vslmtypes.VslmVsoVStorageObjectQuerySpec{
QueryField: string(vslmtypes.VslmVsoVStorageObjectQuerySpecQueryFieldEnumId),
QueryOperator: string(vslmtypes.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumGreaterThan),
}
var query []vslmtypes.VslmVsoVStorageObjectQuerySpec
var ids []types.ID

for {
res, err := m.GlobalObjectManager.ListObjectsForSpec(ctx, query, maxResults)
if err != nil {
return nil, err
}

ids = append(ids, res.Id...)

if res.AllRecordsReturned {
break
}

spec.QueryValue = []string{ids[len(ids)-1].Id}

query = []vslmtypes.VslmVsoVStorageObjectQuerySpec{spec}
res, err := m.GlobalObjectManager.List(ctx, qs...)
if err != nil {
return nil, err
}

return ids, nil
return res.Id, nil
}

func (m *Manager) RegisterDisk(ctx context.Context, path, name string) (*types.VStorageObject, error) {
Expand Down
115 changes: 115 additions & 0 deletions cli/disk/metadata/ls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// © Broadcom. All Rights Reserved.
// The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
// SPDX-License-Identifier: Apache-2.0

package metadata

import (
"context"
"flag"
"fmt"
"io"
"os"
"text/tabwriter"

"github.com/vmware/govmomi/cli"
"github.com/vmware/govmomi/cli/flags"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/govmomi/vslm"
)

type ls struct {
*flags.OutputFlag
*flags.ClientFlag

key string
prefix string
snapshot string
}

func init() {
cli.Register("disk.metadata.ls", &ls{})
}

func (cmd *ls) Register(ctx context.Context, f *flag.FlagSet) {
cmd.OutputFlag, ctx = flags.NewOutputFlag(ctx)
cmd.OutputFlag.Register(ctx, f)
cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
cmd.ClientFlag.Register(ctx, f)

f.StringVar(&cmd.key, "K", "", "Get value for key only")
f.StringVar(&cmd.prefix, "p", "", "Key filter prefix")
f.StringVar(&cmd.snapshot, "s", "", "Snapshot ID")
}

func (cmd *ls) Process(ctx context.Context) error {
if err := cmd.OutputFlag.Process(ctx); err != nil {
return err
}
return cmd.ClientFlag.Process(ctx)
}

func (cmd *ls) Usage() string {
return "ID"
}

func (cmd *ls) Description() string {
return `List metadata for disk ID.
Examples:
govc disk.metadata.ls 9b06a8b-d047-4d3c-b15b-43ea9608b1a6`
}

type lsResult []types.KeyValue

func (r lsResult) Write(w io.Writer) error {
tw := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)
for _, data := range r {
fmt.Fprintf(tw, "%s\t%s\n", data.Key, data.Value)
}
return tw.Flush()
}

func (r lsResult) Dump() interface{} {
return []types.KeyValue(r)
}

func (cmd *ls) Run(ctx context.Context, f *flag.FlagSet) error {
if f.NArg() != 1 {
return flag.ErrHelp
}

c, err := cmd.Client()
if err != nil {
return err
}

vc, err := vslm.NewClient(ctx, c)
if err != nil {
return err
}

m := vslm.NewGlobalObjectManager(vc)

id := types.ID{Id: f.Arg(0)}
var data []types.KeyValue
var sid *types.ID
if cmd.snapshot != "" {
sid = &types.ID{Id: cmd.snapshot}
}

if cmd.key != "" {
val, err := m.RetrieveMetadataValue(ctx, id, sid, cmd.key)
if err != nil {
return err
}
data = []types.KeyValue{{Key: cmd.key, Value: val}}
} else {
data, err = m.RetrieveMetadata(ctx, id, sid, cmd.prefix)
if err != nil {
return err
}
}

return cmd.WriteResult(lsResult(data))
}
80 changes: 80 additions & 0 deletions cli/disk/metadata/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// © Broadcom. All Rights Reserved.
// The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
// SPDX-License-Identifier: Apache-2.0

package metadata

import (
"context"
"flag"
"strings"
"time"

"github.com/vmware/govmomi/cli"
"github.com/vmware/govmomi/cli/flags"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/govmomi/vslm"
)

type update struct {
*flags.ClientFlag

remove flags.StringList
}

func init() {
cli.Register("disk.metadata.update", &update{})
}

func (cmd *update) Register(ctx context.Context, f *flag.FlagSet) {
cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
cmd.ClientFlag.Register(ctx, f)

f.Var(&cmd.remove, "d", "Delete keys")
}

func (cmd *update) Usage() string {
return "ID"
}

func (cmd *update) Description() string {
return `Update metadata for disk ID.
Examples:
govc disk.metadata.update $id foo=bar biz=baz
govc disk.metadata.update -d foo -d biz $id`
}

func (cmd *update) Run(ctx context.Context, f *flag.FlagSet) error {
c, err := cmd.Client()
if err != nil {
return err
}

vc, err := vslm.NewClient(ctx, c)
if err != nil {
return err
}

m := vslm.NewGlobalObjectManager(vc)

id := types.ID{Id: f.Arg(0)}

var update []types.KeyValue

for _, arg := range f.Args()[1:] {
kv := strings.SplitN(arg, "=", 2)
if len(kv) == 1 {
kv = append(kv, "")
}
update = append(update, types.KeyValue{Key: kv[0], Value: kv[1]})
}

task, err := m.UpdateMetadata(ctx, id, update, cmd.remove)
if err != nil {
return err
}

_, err = task.Wait(ctx, time.Hour)
return err
}
Loading

0 comments on commit 13cb119

Please sign in to comment.