Skip to content

Commit 116ba7e

Browse files
committedJan 22, 2025·
api: add vlsm.GlobalObjectManager.List method
Signed-off-by: Doug MacEachern <dougm@broadcom.com>
1 parent c87a209 commit 116ba7e

File tree

3 files changed

+267
-28
lines changed

3 files changed

+267
-28
lines changed
 

‎cli/disk/manager.go

+4-28
Original file line numberDiff line numberDiff line change
@@ -144,36 +144,12 @@ func (m *Manager) List(ctx context.Context, qs ...vslmtypes.VslmVsoVStorageObjec
144144
return m.ObjectManager.List(ctx, m.Datastore)
145145
}
146146

147-
// TODO: move this logic to vslm.GlobalObjectManager
148-
// Need to better understand the QuerySpec + implement in vcsim.
149-
// For now we just want the complete list of IDs (govc disk.ls)
150-
maxResults := int32(100)
151-
152-
spec := vslmtypes.VslmVsoVStorageObjectQuerySpec{
153-
QueryField: string(vslmtypes.VslmVsoVStorageObjectQuerySpecQueryFieldEnumId),
154-
QueryOperator: string(vslmtypes.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumGreaterThan),
155-
}
156-
query := qs
157-
var ids []types.ID
158-
159-
for {
160-
res, err := m.GlobalObjectManager.ListObjectsForSpec(ctx, query, maxResults)
161-
if err != nil {
162-
return nil, err
163-
}
164-
165-
ids = append(ids, res.Id...)
166-
167-
if res.AllRecordsReturned || len(ids) == 0 {
168-
break
169-
}
170-
171-
spec.QueryValue = []string{ids[len(ids)-1].Id}
172-
173-
query = append(qs, spec)
147+
res, err := m.GlobalObjectManager.List(ctx, qs...)
148+
if err != nil {
149+
return nil, err
174150
}
175151

176-
return ids, nil
152+
return res.Id, nil
177153
}
178154

179155
func (m *Manager) RegisterDisk(ctx context.Context, path, name string) (*types.VStorageObject, error) {

‎vslm/global_object_manager.go

+35
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,41 @@ func (this *GlobalObjectManager) ListObjectsForSpec(ctx context.Context, query [
302302
return res.Returnval, nil
303303
}
304304

305+
var DefaultMaxResults = 100
306+
307+
// List wraps ListObjectsForSpec, using maxResult = DefaultMaxResults
308+
// and looping until AllRecordsReturned == true or error is returned.
309+
func (this *GlobalObjectManager) List(ctx context.Context, qs ...types.VslmVsoVStorageObjectQuerySpec) (*types.VslmVsoVStorageObjectQueryResult, error) {
310+
var res types.VslmVsoVStorageObjectQueryResult
311+
312+
query := qs
313+
314+
for {
315+
page, err := this.ListObjectsForSpec(ctx, query, int32(DefaultMaxResults))
316+
if err != nil {
317+
return nil, err
318+
}
319+
320+
res.Id = append(res.Id, page.Id...)
321+
res.QueryResults = append(res.QueryResults, page.QueryResults...)
322+
res.AllRecordsReturned = page.AllRecordsReturned
323+
324+
if page.AllRecordsReturned || len(page.Id) == 0 {
325+
break
326+
}
327+
328+
spec := types.VslmVsoVStorageObjectQuerySpec{
329+
QueryField: string(types.VslmVsoVStorageObjectQuerySpecQueryFieldEnumId),
330+
QueryOperator: string(types.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumGreaterThan),
331+
QueryValue: []string{page.Id[len(page.Id)-1].Id},
332+
}
333+
334+
query = append(qs, spec)
335+
}
336+
337+
return &res, nil
338+
}
339+
305340
func (this *GlobalObjectManager) Clone(ctx context.Context, id vim.ID, spec vim.VslmCloneSpec) (*Task, error) {
306341
req := types.VslmCloneVStorageObject_Task{
307342
This: this.Reference(),

‎vslm/global_object_manager_test.go

+228
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
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 vslm_test
6+
7+
import (
8+
"context"
9+
"fmt"
10+
"strings"
11+
"testing"
12+
"time"
13+
14+
"github.com/vmware/govmomi/find"
15+
"github.com/vmware/govmomi/simulator"
16+
"github.com/vmware/govmomi/vim25"
17+
"github.com/vmware/govmomi/vim25/types"
18+
"github.com/vmware/govmomi/vslm"
19+
_ "github.com/vmware/govmomi/vslm/simulator"
20+
vso "github.com/vmware/govmomi/vslm/types"
21+
)
22+
23+
func TestList(t *testing.T) {
24+
model := simulator.VPX()
25+
model.Datastore = 4
26+
27+
now := time.Now().Format(time.RFC3339Nano)
28+
29+
simulator.Test(func(ctx context.Context, vc *vim25.Client) {
30+
datastores, err := find.NewFinder(vc).DatastoreList(ctx, "*")
31+
if err != nil {
32+
t.Fatal(err)
33+
}
34+
35+
c, err := vslm.NewClient(ctx, vc)
36+
if err != nil {
37+
t.Fatal(err)
38+
}
39+
40+
m := vslm.NewGlobalObjectManager(c)
41+
42+
namespaces := []string{"foo", "bar", "baz"}
43+
44+
dsIDs := make([]string, len(datastores))
45+
46+
for i, ds := range datastores {
47+
dsIDs[i] = strings.SplitN(ds.Reference().Value, "-", 2)[1]
48+
49+
for j, ns := range namespaces {
50+
spec := types.VslmCreateSpec{
51+
Name: fmt.Sprintf("%s-disk-%d", ns, i+j),
52+
CapacityInMB: int64(i+j) * 10,
53+
BackingSpec: &types.VslmCreateSpecDiskFileBackingSpec{
54+
VslmCreateSpecBackingSpec: types.VslmCreateSpecBackingSpec{
55+
Datastore: ds.Reference(),
56+
},
57+
},
58+
}
59+
t.Logf("CreateDisk %s (%dMB) on datastore-%s", spec.Name, spec.CapacityInMB, dsIDs[i])
60+
task, err := m.CreateDisk(ctx, spec)
61+
if err != nil {
62+
t.Fatal(err)
63+
}
64+
disk, err := task.Wait(ctx, time.Hour)
65+
if err != nil {
66+
t.Fatal(err)
67+
}
68+
69+
id := disk.(types.VStorageObject).Config.Id
70+
71+
metadata := []types.KeyValue{
72+
{Key: "namespace", Value: ns},
73+
{Key: "name", Value: spec.Name},
74+
}
75+
76+
task, err = m.UpdateMetadata(ctx, id, metadata, nil)
77+
if err != nil {
78+
t.Fatal(err)
79+
}
80+
_, err = task.Wait(ctx, time.Hour)
81+
if err != nil {
82+
t.Fatal(err)
83+
}
84+
}
85+
}
86+
87+
tests := []struct {
88+
expect int
89+
query []vso.VslmVsoVStorageObjectQuerySpec
90+
}{
91+
{model.Datastore * len(namespaces), nil},
92+
{-1, []vso.VslmVsoVStorageObjectQuerySpec{{
93+
QueryField: "invalid",
94+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumEquals),
95+
QueryValue: []string{"any"},
96+
}}},
97+
{-1, []vso.VslmVsoVStorageObjectQuerySpec{{
98+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumName),
99+
QueryOperator: "invalid",
100+
QueryValue: []string{"any"},
101+
}}},
102+
{-1, []vso.VslmVsoVStorageObjectQuerySpec{{
103+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumName),
104+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumEquals),
105+
QueryValue: nil,
106+
}}},
107+
{-1, []vso.VslmVsoVStorageObjectQuerySpec{{
108+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumName),
109+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumEquals),
110+
QueryValue: []string{"one", "two"},
111+
}}},
112+
{-1, []vso.VslmVsoVStorageObjectQuerySpec{{
113+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumCapacity),
114+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumStartsWith),
115+
QueryValue: []string{"10"},
116+
}}},
117+
{-1, []vso.VslmVsoVStorageObjectQuerySpec{{
118+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumCapacity),
119+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumGreaterThan),
120+
QueryValue: []string{"ten"},
121+
}}},
122+
{3, []vso.VslmVsoVStorageObjectQuerySpec{{
123+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumCapacity),
124+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumGreaterThan),
125+
QueryValue: []string{"30"},
126+
}}},
127+
{0, []vso.VslmVsoVStorageObjectQuerySpec{{
128+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumCapacity),
129+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumGreaterThan),
130+
QueryValue: []string{"5000"},
131+
}}},
132+
{len(namespaces), []vso.VslmVsoVStorageObjectQuerySpec{{
133+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumDatastoreMoId),
134+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumEquals),
135+
QueryValue: []string{dsIDs[0]},
136+
}}},
137+
{model.Datastore, []vso.VslmVsoVStorageObjectQuerySpec{
138+
{
139+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumMetadataKey),
140+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumEquals),
141+
QueryValue: []string{"namespace"},
142+
},
143+
{
144+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumMetadataValue),
145+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumEquals),
146+
QueryValue: []string{namespaces[0]},
147+
},
148+
}},
149+
{model.Datastore, []vso.VslmVsoVStorageObjectQuerySpec{{
150+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumName),
151+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumStartsWith),
152+
QueryValue: []string{namespaces[1]},
153+
}}},
154+
{model.Datastore, []vso.VslmVsoVStorageObjectQuerySpec{{
155+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumName),
156+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumStartsWith),
157+
QueryValue: []string{namespaces[1]},
158+
}}},
159+
{model.Datastore * len(namespaces), []vso.VslmVsoVStorageObjectQuerySpec{{
160+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumName),
161+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumContains),
162+
QueryValue: []string{"disk"},
163+
}}},
164+
{0, []vso.VslmVsoVStorageObjectQuerySpec{
165+
{
166+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumName),
167+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumStartsWith),
168+
QueryValue: []string{namespaces[0]},
169+
},
170+
{
171+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumName),
172+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumStartsWith),
173+
QueryValue: []string{namespaces[1]},
174+
},
175+
}},
176+
{model.Datastore * len(namespaces), []vso.VslmVsoVStorageObjectQuerySpec{{
177+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumCreateTime),
178+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumGreaterThan),
179+
QueryValue: []string{now},
180+
}}},
181+
{0, []vso.VslmVsoVStorageObjectQuerySpec{{
182+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumCreateTime),
183+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumLessThan),
184+
QueryValue: []string{now},
185+
}}},
186+
{-1, []vso.VslmVsoVStorageObjectQuerySpec{{
187+
QueryField: string(vso.VslmVsoVStorageObjectQuerySpecQueryFieldEnumCreateTime),
188+
QueryOperator: string(vso.VslmVsoVStorageObjectQuerySpecQueryOperatorEnumContains),
189+
QueryValue: []string{now},
190+
}}},
191+
}
192+
193+
for _, test := range tests {
194+
if test.expect > 2 {
195+
vslm.DefaultMaxResults = test.expect / 2 // test pagination
196+
}
197+
t.Run(queryString(test.query), func(t *testing.T) {
198+
res, err := m.List(ctx, test.query...)
199+
if test.expect == -1 {
200+
if err == nil {
201+
t.Error("expected error")
202+
}
203+
} else {
204+
if err != nil {
205+
t.Fatal(err)
206+
}
207+
208+
if len(res.Id) != test.expect {
209+
t.Errorf("expected %d, got: %d", test.expect, len(res.Id))
210+
}
211+
}
212+
})
213+
}
214+
}, model)
215+
}
216+
217+
func queryString(query []vso.VslmVsoVStorageObjectQuerySpec) string {
218+
if query == nil {
219+
return "no query"
220+
}
221+
res := make([]string, len(query))
222+
for i, q := range query {
223+
res[i] = fmt.Sprintf("%s.%s=%s",
224+
q.QueryField, q.QueryOperator,
225+
strings.Join(q.QueryValue, ","))
226+
}
227+
return strings.Join(res, " and ")
228+
}

0 commit comments

Comments
 (0)
Please sign in to comment.