Skip to content

Commit a06833d

Browse files
authored
Merge pull request #3510 from dougm/clib-item-vsan
api: convert vSAN directory name to uuid in ResolveLibraryItemStorage
2 parents 49b88eb + 208d532 commit a06833d

File tree

8 files changed

+250
-10
lines changed

8 files changed

+250
-10
lines changed

govc/library/info.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type info struct {
4242
link bool
4343
url bool
4444
stor bool
45+
Stor bool
4546

4647
pathFinder *finder.PathFinder
4748
}
@@ -62,6 +63,9 @@ func (cmd *info) Register(ctx context.Context, f *flag.FlagSet) {
6263
f.BoolVar(&cmd.link, "L", false, "List Datastore path only")
6364
f.BoolVar(&cmd.url, "U", false, "List pub/sub URL(s) only")
6465
f.BoolVar(&cmd.stor, "s", false, "Include file specific storage details")
66+
if cli.ShowUnreleased() {
67+
f.BoolVar(&cmd.Stor, "S", false, "Include file specific storage details (resolved)")
68+
}
6569
}
6670

6771
func (cmd *info) Process(ctx context.Context) error {
@@ -265,14 +269,22 @@ func (r infoResultsWriter) writeFile(
265269
}
266270
fmt.Fprintf(w, " Datastore Path:\t%s\n", p)
267271
}
268-
if r.cmd.stor {
272+
if r.cmd.stor || r.cmd.Stor {
273+
label := "Storage URI"
269274
s, err := r.m.GetLibraryItemStorage(r.ctx, res.GetParent().GetID(), v.Name)
270275
if err != nil {
271276
return err
272277
}
278+
if r.cmd.Stor {
279+
label = "Resolved URI"
280+
err = r.cmd.pathFinder.ResolveLibraryItemStorage(r.ctx, s)
281+
if err != nil {
282+
return err
283+
}
284+
}
273285
for i := range s {
274286
for _, uri := range s[i].StorageURIs {
275-
fmt.Fprintf(w, " Storage URI:\t%s\n", uri)
287+
fmt.Fprintf(w, " %s:\t%s\n", label, uri)
276288
}
277289
}
278290
}

govc/test/library.bats

+3
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ load test_helper
208208
run govc library.info -s "/my-content/ttylinux-live/$TTYLINUX_NAME.ovf"
209209
assert_success
210210

211+
run env GOVC_SHOW_UNRELEASED=true govc library.info -S "/my-content/ttylinux-live/$TTYLINUX_NAME.ovf"
212+
assert_success
213+
211214
run govc library.info -l -s /my-content/$TTYLINUX_NAME/$TTYLINUX_NAME.ovf
212215
assert_success
213216

internal/helpers.go

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
Copyright (c) 2020-2023 VMware, Inc. All Rights Reserved.
2+
Copyright (c) 2020-2024 VMware, Inc. All Rights Reserved.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
66
You may obtain a copy of the License at
77
8-
http://www.apache.org/licenses/LICENSE-2.0
8+
http://www.apache.org/licenses/LICENSE-2.0
99
1010
Unless required by applicable law or agreed to in writing, software
1111
distributed under the License is distributed on an "AS IS" BASIS,
@@ -24,6 +24,7 @@ import (
2424
"net/url"
2525
"os"
2626
"path"
27+
"slices"
2728

2829
"github.com/vmware/govmomi/vim25"
2930
"github.com/vmware/govmomi/vim25/mo"
@@ -51,6 +52,15 @@ func InventoryPath(entities []mo.ManagedEntity) string {
5152
return val
5253
}
5354

55+
var vsanFS = []string{
56+
string(types.HostFileSystemVolumeFileSystemTypeVsan),
57+
string(types.HostFileSystemVolumeFileSystemTypeVVOL),
58+
}
59+
60+
func IsDatastoreVSAN(ds mo.Datastore) bool {
61+
return slices.Contains(vsanFS, ds.Summary.Type)
62+
}
63+
5464
func HostSystemManagementIPs(config []types.VirtualNicManagerNetConfig) []net.IP {
5565
var ips []net.IP
5666

object/namespace_manager.go

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
2+
Copyright (c) 2016-2024 VMware, Inc. All Rights Reserved.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
66
You may obtain a copy of the License at
77
8-
http://www.apache.org/licenses/LICENSE-2.0
8+
http://www.apache.org/licenses/LICENSE-2.0
99
1010
Unless required by applicable law or agreed to in writing, software
1111
distributed under the License is distributed on an "AS IS" BASIS,
@@ -74,3 +74,22 @@ func (nm DatastoreNamespaceManager) DeleteDirectory(ctx context.Context, dc *Dat
7474

7575
return nil
7676
}
77+
78+
func (nm DatastoreNamespaceManager) ConvertNamespacePathToUuidPath(ctx context.Context, dc *Datacenter, datastoreURL string) (string, error) {
79+
req := &types.ConvertNamespacePathToUuidPath{
80+
This: nm.Reference(),
81+
NamespaceUrl: datastoreURL,
82+
}
83+
84+
if dc != nil {
85+
ref := dc.Reference()
86+
req.Datacenter = &ref
87+
}
88+
89+
res, err := methods.ConvertNamespacePathToUuidPath(ctx, nm.c, req)
90+
if err != nil {
91+
return "", err
92+
}
93+
94+
return res.Returnval, nil
95+
}
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package simulator
18+
19+
import (
20+
"strings"
21+
22+
"github.com/vmware/govmomi/internal"
23+
"github.com/vmware/govmomi/vim25/methods"
24+
"github.com/vmware/govmomi/vim25/mo"
25+
"github.com/vmware/govmomi/vim25/soap"
26+
"github.com/vmware/govmomi/vim25/types"
27+
)
28+
29+
type DatastoreNamespaceManager struct {
30+
mo.DatastoreNamespaceManager
31+
}
32+
33+
func (m *DatastoreNamespaceManager) ConvertNamespacePathToUuidPath(ctx *Context, req *types.ConvertNamespacePathToUuidPath) soap.HasFault {
34+
body := new(methods.ConvertNamespacePathToUuidPathBody)
35+
36+
if req.Datacenter == nil {
37+
body.Fault_ = Fault("", &types.InvalidArgument{InvalidProperty: "datacenterRef"})
38+
return body
39+
}
40+
41+
dc := ctx.Map.Get(*req.Datacenter).(*Datacenter)
42+
43+
var ds *Datastore
44+
for _, ref := range dc.Datastore {
45+
ds = ctx.Map.Get(ref).(*Datastore)
46+
if strings.HasPrefix(req.NamespaceUrl, ds.Summary.Url) {
47+
break
48+
}
49+
ds = nil
50+
}
51+
52+
if ds == nil {
53+
body.Fault_ = Fault("", &types.InvalidDatastorePath{DatastorePath: req.NamespaceUrl})
54+
return body
55+
}
56+
57+
if !internal.IsDatastoreVSAN(ds.Datastore) {
58+
body.Fault_ = Fault("", &types.InvalidDatastore{Datastore: &ds.Self})
59+
return body
60+
}
61+
62+
body.Res = &types.ConvertNamespacePathToUuidPathResponse{
63+
Returnval: req.NamespaceUrl,
64+
}
65+
66+
return body
67+
}

simulator/model.go

+1
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ var kinds = map[string]reflect.Type{
238238
"CustomizationSpecManager": reflect.TypeOf((*CustomizationSpecManager)(nil)).Elem(),
239239
"Datacenter": reflect.TypeOf((*Datacenter)(nil)).Elem(),
240240
"Datastore": reflect.TypeOf((*Datastore)(nil)).Elem(),
241+
"DatastoreNamespaceManager": reflect.TypeOf((*DatastoreNamespaceManager)(nil)).Elem(),
241242
"DistributedVirtualPortgroup": reflect.TypeOf((*DistributedVirtualPortgroup)(nil)).Elem(),
242243
"DistributedVirtualSwitch": reflect.TypeOf((*DistributedVirtualSwitch)(nil)).Elem(),
243244
"DistributedVirtualSwitchManager": reflect.TypeOf((*DistributedVirtualSwitchManager)(nil)).Elem(),

vapi/library/finder/path.go

+38-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"path"
2424
"strings"
2525

26+
"github.com/vmware/govmomi/internal"
2627
"github.com/vmware/govmomi/object"
2728
"github.com/vmware/govmomi/property"
2829
"github.com/vmware/govmomi/vapi/library"
@@ -134,6 +135,29 @@ func (f *PathFinder) datastoreName(ctx context.Context, id string) (string, erro
134135
return name, nil
135136
}
136137

138+
func (f *PathFinder) convertPath(ctx context.Context, b *mo.Datastore, path string) (string, error) {
139+
if !internal.IsDatastoreVSAN(*b) {
140+
return path, nil
141+
}
142+
143+
var dc *object.Datacenter
144+
145+
entities, err := mo.Ancestors(ctx, f.c, f.c.ServiceContent.PropertyCollector, b.Self)
146+
if err != nil {
147+
return "", err
148+
}
149+
150+
for _, entity := range entities {
151+
if entity.Self.Type == "Datacenter" {
152+
dc = object.NewDatacenter(f.c, entity.Self)
153+
break
154+
}
155+
}
156+
157+
m := object.NewDatastoreNamespaceManager(f.c)
158+
return m.ConvertNamespacePathToUuidPath(ctx, dc, path)
159+
}
160+
137161
// ResolveLibraryItemStorage transforms StorageURIs Datastore url (uuid) to Datastore name.
138162
func (f *PathFinder) ResolveLibraryItemStorage(ctx context.Context, storage []library.Storage) error {
139163
// TODO:
@@ -154,7 +178,8 @@ func (f *PathFinder) ResolveLibraryItemStorage(ctx context.Context, storage []li
154178

155179
var ds []mo.Datastore
156180
pc := property.DefaultCollector(f.c)
157-
if err := pc.Retrieve(ctx, ids, []string{"name", "info.url"}, &ds); err != nil {
181+
props := []string{"name", "summary.url", "summary.type"}
182+
if err := pc.Retrieve(ctx, ids, props, &ds); err != nil {
158183
return err
159184
}
160185

@@ -164,12 +189,21 @@ func (f *PathFinder) ResolveLibraryItemStorage(ctx context.Context, storage []li
164189

165190
for _, item := range storage {
166191
b := backing[item.StorageBacking.DatastoreID]
167-
dsurl := b.Info.GetDatastoreInfo().Url
192+
dsurl := b.Summary.Url
168193

169194
for i := range item.StorageURIs {
170-
u := strings.TrimPrefix(item.StorageURIs[i], dsurl)
195+
uri, err := url.Parse(item.StorageURIs[i])
196+
if err != nil {
197+
return err
198+
}
199+
uri.Path = path.Clean(uri.Path) // required for ConvertNamespacePathToUuidPath()
200+
uri.RawQuery = ""
201+
u, err := f.convertPath(ctx, b, uri.String())
202+
if err != nil {
203+
return err
204+
}
205+
u = strings.TrimPrefix(u, dsurl)
171206
u = strings.TrimPrefix(u, "/")
172-
u = strings.SplitN(u, "?", 2)[0] // strip query, if any
173207

174208
item.StorageURIs[i] = (&object.DatastorePath{
175209
Datastore: b.Name,

vapi/library/finder/path_test.go

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package finder_test
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"strings"
23+
"testing"
24+
25+
"github.com/vmware/govmomi/find"
26+
"github.com/vmware/govmomi/object"
27+
"github.com/vmware/govmomi/simulator"
28+
"github.com/vmware/govmomi/vapi/library"
29+
"github.com/vmware/govmomi/vapi/library/finder"
30+
"github.com/vmware/govmomi/vapi/rest"
31+
_ "github.com/vmware/govmomi/vapi/simulator"
32+
"github.com/vmware/govmomi/vim25"
33+
"github.com/vmware/govmomi/vim25/mo"
34+
"github.com/vmware/govmomi/vim25/types"
35+
)
36+
37+
func TestResolveLibraryItemStorage(t *testing.T) {
38+
simulator.Test(func(ctx context.Context, vc *vim25.Client) {
39+
rc := rest.NewClient(vc)
40+
41+
ds, err := find.NewFinder(vc).Datastore(ctx, "*")
42+
if err != nil {
43+
t.Fatal(err)
44+
}
45+
46+
var props mo.Datastore
47+
err = ds.Properties(ctx, ds.Reference(), []string{"name", "summary"}, &props)
48+
if err != nil {
49+
t.Fatal(err)
50+
}
51+
52+
fsTypes := []string{
53+
string(types.HostFileSystemVolumeFileSystemTypeOTHER),
54+
string(types.HostFileSystemVolumeFileSystemTypeVsan),
55+
}
56+
57+
for _, fs := range fsTypes {
58+
// client uses DatastoreNamespaceManager only when datastore fs is vsan/vvol
59+
simulator.Map.Get(ds.Reference()).(*simulator.Datastore).Summary.Type = fs
60+
61+
u := props.Summary.Url
62+
63+
storage := []library.Storage{
64+
{
65+
StorageBacking: library.StorageBacking{DatastoreID: ds.Reference().Value, Type: "DATASTORE"},
66+
StorageURIs: []string{
67+
fmt.Sprintf("%s/contentlib-${lib_id}/${item_id}/${file_name}_${file_id}.iso", u),
68+
fmt.Sprintf("%s/contentlib-${lib_id}/${item_id}/${file_name}_${file_id}.iso?serverId=${server_id}", u),
69+
},
70+
},
71+
}
72+
73+
f := finder.NewPathFinder(library.NewManager(rc), vc)
74+
75+
err = f.ResolveLibraryItemStorage(ctx, storage)
76+
if err != nil {
77+
t.Fatal(err)
78+
}
79+
80+
var path object.DatastorePath
81+
for _, s := range storage {
82+
for _, u := range s.StorageURIs {
83+
path.FromString(u)
84+
if path.Datastore != props.Name {
85+
t.Errorf("failed to parse %s", u)
86+
}
87+
if strings.Contains(u, "?") {
88+
t.Errorf("includes query: %s", u)
89+
}
90+
}
91+
}
92+
}
93+
})
94+
}

0 commit comments

Comments
 (0)