Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vcsim: add disk query and metadata support #3682

Merged
merged 3 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading