Adding vSphere Volume support for vSphere Cloud Provider

This commit is contained in:
Abitha Palaniappan
2016-04-13 17:36:05 -07:00
parent 346f965871
commit 95c009dbdb
31 changed files with 2672 additions and 493 deletions

View File

@@ -9,15 +9,9 @@ build:
- GOVC_INSECURE=1
- VCA=1
commands:
- go get golang.org/x/tools/cmd/vet
- go get golang.org/x/tools/cmd/goimports
- go get github.com/davecgh/go-spew/spew
- go get
- make all
- make install
- make all install
- git clone https://github.com/sstephenson/bats.git /tmp/bats
- /tmp/bats/install.sh /usr/local
- apt-get -qq update && apt-get install -yqq uuid-runtime bsdmainutils jq
- govc/test/images/update.sh
- bats govc/test
- govc/test/clean.sh

1
vendor/github.com/vmware/govmomi/.gitignore generated vendored Normal file
View File

@@ -0,0 +1 @@
secrets.yml

View File

@@ -1,11 +1,12 @@
sudo: false
language: go
go: 1.4
go:
- 1.6
before_install:
- go get golang.org/x/tools/cmd/vet
- go get golang.org/x/tools/cmd/goimports
- go get github.com/davecgh/go-spew/spew
- make vendor
script:
- make check test

View File

@@ -1,5 +1,33 @@
# changelog
### 0.6.2 (2016-05-11)
* Get complete file details in Datastore.Stat
* SOAP decoding fixes
* Add VirtualMachine.RemoveAllSnapshot
### 0.6.1 (2016-04-30)
* Fix mo.Entity interface
### 0.6.0 (2016-04-29)
* Add Common.Rename method
* Add mo.Entity interface
* Add OptionManager
* Add Finder.FolderList method
* Add VirtualMachine.WaitForNetIP method
* Add VirtualMachine.RevertToSnapshot method
* Add Datastore.Download method
### 0.5.0 (2016-03-30)
Generated fields using xsd type 'int' change to Go type 'int32'

View File

@@ -7,11 +7,13 @@ Alvaro Miranda <kikitux@gmail.com>
Amit Bathla <abathla@.vmware.com>
Andrew Chin <andrew@andrewtchin.com>
Arran Walker <arran.walker@zopa.com>
Austin Parker <aparker@apprenda.com>
Bob Killen <killen.bob@gmail.com>
Bruce Downs <bdowns@vmware.com>
Clint Greenwood <cgreenwood@vmware.com> <clint.greenwood@gmail.com>
Cédric Blomart <cblomart@gmail.com>
Danny Lockard <danny.lockard@banno.com>
Dave Tucker <dave@dtucker.co.uk>
Doug MacEachern <dougm@vmware.com>
Eloy Coto <eloy.coto@gmail.com>
Eric Yutao <eric.yutao@gmail.com>
@@ -28,6 +30,7 @@ Mevan Samaratunga <mevansam@gmail.com>
Pieter Noordhuis <pnoordhuis@vmware.com> <pcnoordhuis@gmail.com>
runner.mei <runner.mei@gmail.com>
S.Çağlar Onur <conur@vmware.com>
Sergey Ignatov <sergey.ignatov@jetbrains.com>
Takaaki Furukawa <takaaki.frkw@gmail.com> <takaaki.furukawa@mail.rakuten.com>
Steve Purcell <steve@sanityinc.com>
Yang Yang <yangy@vmware.com>

View File

@@ -4,7 +4,12 @@ all: check test
check: goimports govet
goimports:
vendor:
go get golang.org/x/tools/cmd/goimports
go get github.com/davecgh/go-spew/spew
go get golang.org/x/net/context
goimports: vendor
@echo checking go imports...
@! goimports -d . 2>&1 | egrep -v '^$$'
@@ -12,9 +17,8 @@ govet:
@echo checking go vet...
@go tool vet -structtags=false -methods=false .
test:
go get
test: vendor
go test -v $(TEST_OPTS) ./...
install:
install: vendor
go install github.com/vmware/govmomi/govc

View File

@@ -762,28 +762,42 @@ func (f *Finder) VirtualApp(ctx context.Context, path string) (*object.VirtualAp
return apps[0], nil
}
func (f *Finder) Folder(ctx context.Context, path string) (*object.Folder, error) {
mo, err := f.ManagedObjectList(ctx, path)
func (f *Finder) FolderList(ctx context.Context, path string) ([]*object.Folder, error) {
es, err := f.ManagedObjectList(ctx, path)
if err != nil {
return nil, err
}
if len(mo) == 0 {
var folders []*object.Folder
for _, e := range es {
switch o := e.Object.(type) {
case mo.Folder:
folder := object.NewFolder(f.client, o.Reference())
folder.InventoryPath = e.Path
folders = append(folders, folder)
case *object.Folder:
// RootFolder
folders = append(folders, o)
}
}
if len(folders) == 0 {
return nil, &NotFoundError{"folder", path}
}
if len(mo) > 1 {
return folders, nil
}
func (f *Finder) Folder(ctx context.Context, path string) (*object.Folder, error) {
folders, err := f.FolderList(ctx, path)
if err != nil {
return nil, err
}
if len(folders) > 1 {
return nil, &MultipleFoundError{"folder", path}
}
ref := mo[0].Object.Reference()
if ref.Type != "Folder" {
return nil, &NotFoundError{"folder", path}
}
folder := object.NewFolder(f.client, ref)
folder.InventoryPath = mo[0].Path
return folder, nil
return folders[0], nil
}

View File

@@ -69,3 +69,17 @@ func (c Common) Destroy(ctx context.Context) (*Task, error) {
return NewTask(c.c, res.Returnval), nil
}
func (c Common) Rename(ctx context.Context, name string) (*Task, error) {
req := types.Rename_Task{
This: c.Reference(),
NewName: name,
}
res, err := methods.Rename_Task(ctx, c.c, &req)
if err != nil {
return nil, err
}
return NewTask(c.c, res.Returnval), nil
}

View File

@@ -220,7 +220,16 @@ func (d Datastore) UploadFile(ctx context.Context, file string, path string, par
return d.Client().UploadFile(file, u, p)
}
// DownloadFile via soap.Upload with an http service ticket
// Download via soap.Download with an http service ticket
func (d Datastore) Download(ctx context.Context, path string, param *soap.Download) (io.ReadCloser, int64, error) {
u, p, err := d.downloadTicket(ctx, path, param)
if err != nil {
return nil, 0, err
}
return d.Client().Download(u, p)
}
// DownloadFile via soap.Download with an http service ticket
func (d Datastore) DownloadFile(ctx context.Context, path string, file string, param *soap.Download) error {
u, p, err := d.downloadTicket(ctx, path, param)
if err != nil {
@@ -305,8 +314,10 @@ func (d Datastore) Stat(ctx context.Context, file string) (types.BaseFileInfo, e
spec := types.HostDatastoreBrowserSearchSpec{
Details: &types.FileQueryFlags{
FileType: true,
FileOwner: types.NewBool(true), // TODO: omitempty is generated, but seems to be required
FileType: true,
FileSize: true,
Modification: true,
FileOwner: types.NewBool(true),
},
MatchPattern: []string{path.Base(file)},
}

View File

@@ -37,7 +37,9 @@ func NewFolder(c *vim25.Client, ref types.ManagedObjectReference) *Folder {
}
func NewRootFolder(c *vim25.Client) *Folder {
return NewFolder(c, c.ServiceContent.RootFolder)
f := NewFolder(c, c.ServiceContent.RootFolder)
f.InventoryPath = "/"
return f
}
func (f Folder) Children(ctx context.Context) ([]Reference, error) {
@@ -196,3 +198,17 @@ func (f Folder) CreateDVS(ctx context.Context, spec types.DVSCreateSpec) (*Task,
return NewTask(f.c, res.Returnval), nil
}
func (f Folder) MoveInto(ctx context.Context, list []types.ManagedObjectReference) (*Task, error) {
req := types.MoveIntoFolder_Task{
This: f.Reference(),
List: list,
}
res, err := methods.MoveIntoFolder_Task(ctx, f.c, &req)
if err != nil {
return nil, err
}
return NewTask(f.c, res.Returnval), nil
}

View File

@@ -0,0 +1,64 @@
/*
Copyright (c) 2016 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 object
import (
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type HostAccountManager struct {
Common
}
func NewHostAccountManager(c *vim25.Client, ref types.ManagedObjectReference) *HostAccountManager {
return &HostAccountManager{
Common: NewCommon(c, ref),
}
}
func (m HostAccountManager) Create(ctx context.Context, user *types.HostAccountSpec) error {
req := types.CreateUser{
This: m.Reference(),
User: user,
}
_, err := methods.CreateUser(ctx, m.Client(), &req)
return err
}
func (m HostAccountManager) Update(ctx context.Context, user *types.HostAccountSpec) error {
req := types.UpdateUser{
This: m.Reference(),
User: user,
}
_, err := methods.UpdateUser(ctx, m.Client(), &req)
return err
}
func (m HostAccountManager) Remove(ctx context.Context, userName string) error {
req := types.RemoveUser{
This: m.Reference(),
UserName: userName,
}
_, err := methods.RemoveUser(ctx, m.Client(), &req)
return err
}

View File

@@ -98,3 +98,25 @@ func (m HostConfigManager) VsanSystem(ctx context.Context) (*HostVsanSystem, err
return NewHostVsanSystem(m.c, *h.ConfigManager.VsanSystem), nil
}
func (m HostConfigManager) AccountManager(ctx context.Context) (*HostAccountManager, error) {
var h mo.HostSystem
err := m.Properties(ctx, m.Reference(), []string{"configManager.accountManager"}, &h)
if err != nil {
return nil, err
}
return NewHostAccountManager(m.c, *h.ConfigManager.AccountManager), nil
}
func (m HostConfigManager) OptionManager(ctx context.Context) (*OptionManager, error) {
var h mo.HostSystem
err := m.Properties(ctx, m.Reference(), []string{"configManager.advancedOption"}, &h)
if err != nil {
return nil, err
}
return NewOptionManager(m.c, *h.ConfigManager.AdvancedOption), nil
}

View File

@@ -0,0 +1,58 @@
/*
Copyright (c) 2016 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 object
import (
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
)
type OptionManager struct {
Common
}
func NewOptionManager(c *vim25.Client, ref types.ManagedObjectReference) *OptionManager {
return &OptionManager{
Common: NewCommon(c, ref),
}
}
func (m OptionManager) Query(ctx context.Context, name string) ([]types.BaseOptionValue, error) {
req := types.QueryOptions{
This: m.Reference(),
Name: name,
}
res, err := methods.QueryOptions(ctx, m.Client(), &req)
if err != nil {
return nil, err
}
return res.Returnval, nil
}
func (m OptionManager) Update(ctx context.Context, value []types.BaseOptionValue) error {
req := types.UpdateOptions{
This: m.Reference(),
ChangedValue: value,
}
_, err := methods.UpdateOptions(ctx, m.Client(), &req)
return err
}

View File

@@ -63,7 +63,6 @@ func EthernetCardTypes() VirtualDeviceList {
&types.VirtualVmxnet3{},
}).Select(func(device types.BaseVirtualDevice) bool {
c := device.(types.BaseVirtualEthernetCard).GetVirtualEthernetCard()
c.AddressType = string(types.VirtualEthernetCardMacTypeGenerated)
c.GetVirtualDevice().Key = -1
return true
})

View File

@@ -19,6 +19,7 @@ package object
import (
"errors"
"fmt"
"net"
"github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/vim25"
@@ -245,6 +246,77 @@ func (v VirtualMachine) WaitForIP(ctx context.Context) (string, error) {
return ip, nil
}
// WaitForNetIP waits for the VM guest.net property to report an IP address for all VM NICs.
// Only consider IPv4 addresses if the v4 param is true.
// Returns a map with MAC address as the key and IP address list as the value.
func (v VirtualMachine) WaitForNetIP(ctx context.Context, v4 bool) (map[string][]string, error) {
macs := make(map[string][]string)
p := property.DefaultCollector(v.c)
// Wait for all NICs to have a MacAddress, which may not be generated yet.
err := property.Wait(ctx, p, v.Reference(), []string{"config.hardware.device"}, func(pc []types.PropertyChange) bool {
for _, c := range pc {
if c.Op != types.PropertyChangeOpAssign {
continue
}
devices := c.Val.(types.ArrayOfVirtualDevice).VirtualDevice
for _, device := range devices {
if nic, ok := device.(types.BaseVirtualEthernetCard); ok {
mac := nic.GetVirtualEthernetCard().MacAddress
if mac == "" {
return false
}
macs[mac] = nil
}
}
}
return true
})
err = property.Wait(ctx, p, v.Reference(), []string{"guest.net"}, func(pc []types.PropertyChange) bool {
for _, c := range pc {
if c.Op != types.PropertyChangeOpAssign {
continue
}
nics := c.Val.(types.ArrayOfGuestNicInfo).GuestNicInfo
for _, nic := range nics {
mac := nic.MacAddress
if mac == "" || nic.IpConfig == nil {
continue
}
for _, ip := range nic.IpConfig.IpAddress {
if _, ok := macs[mac]; !ok {
continue // Ignore any that don't correspond to a VM device
}
if v4 && net.ParseIP(ip.IpAddress).To4() == nil {
continue // Ignore non IPv4 address
}
macs[mac] = append(macs[mac], ip.IpAddress)
}
}
}
for _, ips := range macs {
if len(ips) == 0 {
return false
}
}
return true
})
if err != nil {
return nil, err
}
return macs, nil
}
// Device returns the VirtualMachine's config.hardware.device property.
func (v VirtualMachine) Device(ctx context.Context) (VirtualDeviceList, error) {
var o mo.VirtualMachine
@@ -336,8 +408,12 @@ func (v VirtualMachine) EditDevice(ctx context.Context, device ...types.BaseVirt
}
// RemoveDevice removes the given devices on the VirtualMachine
func (v VirtualMachine) RemoveDevice(ctx context.Context, device ...types.BaseVirtualDevice) error {
return v.configureDevice(ctx, types.VirtualDeviceConfigSpecOperationRemove, types.VirtualDeviceConfigSpecFileOperationDestroy, device...)
func (v VirtualMachine) RemoveDevice(ctx context.Context, keepFiles bool, device ...types.BaseVirtualDevice) error {
fop := types.VirtualDeviceConfigSpecFileOperationDestroy
if keepFiles {
fop = ""
}
return v.configureDevice(ctx, types.VirtualDeviceConfigSpecOperationRemove, fop, device...)
}
// BootOptions returns the VirtualMachine's config.bootOptions property.
@@ -400,6 +476,76 @@ func (v VirtualMachine) CreateSnapshot(ctx context.Context, name string, descrip
return NewTask(v.c, res.Returnval), nil
}
// RemoveAllSnapshot removes all snapshots of a virtual machine
func (v VirtualMachine) RemoveAllSnapshot(ctx context.Context, consolidate *bool) (*Task, error) {
req := types.RemoveAllSnapshots_Task{
This: v.Reference(),
Consolidate: consolidate,
}
res, err := methods.RemoveAllSnapshots_Task(ctx, v.c, &req)
if err != nil {
return nil, err
}
return NewTask(v.c, res.Returnval), nil
}
// RevertToSnapshot reverts to a named snapshot
func (v VirtualMachine) RevertToSnapshot(ctx context.Context, name string, suppressPowerOn bool) (*Task, error) {
var o mo.VirtualMachine
err := v.Properties(ctx, v.Reference(), []string{"snapshot"}, &o)
snapshotTree := o.Snapshot.RootSnapshotList
if len(snapshotTree) < 1 {
return nil, errors.New("No snapshots for this VM")
}
snapshot, err := traverseSnapshotInTree(snapshotTree, name)
if err != nil {
return nil, err
}
req := types.RevertToSnapshot_Task{
This: snapshot,
SuppressPowerOn: types.NewBool(suppressPowerOn),
}
res, err := methods.RevertToSnapshot_Task(ctx, v.c, &req)
if err != nil {
return nil, err
}
return NewTask(v.c, res.Returnval), nil
}
// traverseSnapshotInTree is a recursive function that will traverse a snapshot tree to find a given snapshot
func traverseSnapshotInTree(tree []types.VirtualMachineSnapshotTree, name string) (types.ManagedObjectReference, error) {
var o types.ManagedObjectReference
if tree == nil {
return o, errors.New("Snapshot tree is empty")
}
for _, s := range tree {
if s.Name == name {
o = s.Snapshot
break
} else {
childTree := s.ChildSnapshotList
var err error
o, err = traverseSnapshotInTree(childTree, name)
if err != nil {
return o, err
}
}
}
if o.Value == "" {
return o, errors.New("Snapshot not found")
}
return o, nil
}
// IsToolsRunning returns true if VMware Tools is currently running in the guest OS, and false otherwise.
func (v VirtualMachine) IsToolsRunning(ctx context.Context) (bool, error) {
var o mo.VirtualMachine

24
vendor/github.com/vmware/govmomi/vim25/mo/entity.go generated vendored Normal file
View File

@@ -0,0 +1,24 @@
/*
Copyright (c) 2016 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 mo
// Entity is the interface that is implemented by all managed objects
// that extend ManagedEntity.
type Entity interface {
Reference
Entity() *ManagedEntity
}

View File

@@ -130,6 +130,10 @@ type ComputeResource struct {
ConfigurationEx types.BaseComputeResourceConfigInfo `mo:"configurationEx"`
}
func (m *ComputeResource) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() {
t["ComputeResource"] = reflect.TypeOf((*ComputeResource)(nil)).Elem()
}
@@ -187,6 +191,10 @@ type Datacenter struct {
Configuration types.DatacenterConfigInfo `mo:"configuration"`
}
func (m *Datacenter) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() {
t["Datacenter"] = reflect.TypeOf((*Datacenter)(nil)).Elem()
}
@@ -203,6 +211,10 @@ type Datastore struct {
IormConfiguration *types.StorageIORMInfo `mo:"iormConfiguration"`
}
func (m *Datastore) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() {
t["Datastore"] = reflect.TypeOf((*Datastore)(nil)).Elem()
}
@@ -255,6 +267,10 @@ type DistributedVirtualSwitch struct {
Runtime *types.DVSRuntimeInfo `mo:"runtime"`
}
func (m *DistributedVirtualSwitch) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() {
t["DistributedVirtualSwitch"] = reflect.TypeOf((*DistributedVirtualSwitch)(nil)).Elem()
}
@@ -359,6 +375,10 @@ type Folder struct {
ChildEntity []types.ManagedObjectReference `mo:"childEntity"`
}
func (m *Folder) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() {
t["Folder"] = reflect.TypeOf((*Folder)(nil)).Elem()
}
@@ -878,6 +898,10 @@ type HostSystem struct {
SystemResources *types.HostSystemResourceInfo `mo:"systemResources"`
}
func (m *HostSystem) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() {
t["HostSystem"] = reflect.TypeOf((*HostSystem)(nil)).Elem()
}
@@ -1117,6 +1141,10 @@ type Network struct {
Vm []types.ManagedObjectReference `mo:"vm"`
}
func (m *Network) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() {
t["Network"] = reflect.TypeOf((*Network)(nil)).Elem()
}
@@ -1286,6 +1314,10 @@ type ResourcePool struct {
ChildConfiguration []types.ResourceConfigSpec `mo:"childConfiguration"`
}
func (m *ResourcePool) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() {
t["ResourcePool"] = reflect.TypeOf((*ResourcePool)(nil)).Elem()
}
@@ -1551,6 +1583,10 @@ type VirtualMachine struct {
GuestHeartbeatStatus types.ManagedEntityStatus `mo:"guestHeartbeatStatus"`
}
func (m *VirtualMachine) Entity() *ManagedEntity {
return &m.ManagedEntity
}
func init() {
t["VirtualMachine"] = reflect.TypeOf((*VirtualMachine)(nil)).Elem()
}

View File

@@ -189,12 +189,20 @@ func assignValue(val reflect.Value, fi []int, pv reflect.Value) {
npv := reflect.New(pt)
npv.Elem().Set(pv)
pv = npv
pt = pv.Type()
} else {
panic(fmt.Sprintf("type %s doesn't implement %s", pt.Name(), rt.Name()))
}
}
rv.Set(pv)
if pt.AssignableTo(rt) {
rv.Set(pv)
} else if rt.ConvertibleTo(pt) {
rv.Set(pv.Convert(rt))
} else {
panic(fmt.Sprintf("cannot assign %s (%s) to %s (%s)", rt.Name(), rt.Kind(), pt.Name(), pt.Kind()))
}
return
}

View File

@@ -428,23 +428,12 @@ var DefaultDownload = Download{
Method: "GET",
}
// DownloadFile GETs the given URL to a local file
func (c *Client) DownloadFile(file string, u *url.URL, param *Download) error {
var err error
if param == nil {
param = &DefaultDownload
}
fh, err := os.Create(file)
if err != nil {
return err
}
defer fh.Close()
// Download GETs the remote file from the given URL
func (c *Client) Download(u *url.URL, param *Download) (io.ReadCloser, int64, error) {
req, err := http.NewRequest(param.Method, u.String(), nil)
if err != nil {
return err
return nil, 0, err
}
if param.Ticket != nil {
@@ -453,11 +442,9 @@ func (c *Client) DownloadFile(file string, u *url.URL, param *Download) error {
res, err := c.Client.Do(req)
if err != nil {
return err
return nil, 0, err
}
defer res.Body.Close()
switch res.StatusCode {
case http.StatusOK:
default:
@@ -465,12 +452,37 @@ func (c *Client) DownloadFile(file string, u *url.URL, param *Download) error {
}
if err != nil {
return err
return nil, 0, err
}
var r io.Reader = res.Body
var r io.ReadCloser = res.Body
return r, res.ContentLength, nil
}
// DownloadFile GETs the given URL to a local file
func (c *Client) DownloadFile(file string, u *url.URL, param *Download) error {
var err error
if param == nil {
param = &DefaultDownload
}
rc, contentLength, err := c.Download(u, param)
if err != nil {
return err
}
defer rc.Close()
var r io.Reader = rc
fh, err := os.Create(file)
if err != nil {
return err
}
defer fh.Close()
if param.Progress != nil {
pr := progress.NewReader(param.Progress, res.Body, res.ContentLength)
pr := progress.NewReader(param.Progress, r, contentLength)
r = pr
// Mark progress reader as done when returning from this function.

View File

@@ -271,9 +271,19 @@ var (
// Find reflect.Type for an element's type attribute.
func (p *Decoder) typeForElement(val reflect.Value, start *StartElement) reflect.Type {
t := ""
for _, a := range start.Attr {
for i, a := range start.Attr {
if a.Name == xmlSchemaInstance {
t = a.Value
// HACK: ensure xsi:type is last in the list to avoid using that value for
// a "type" attribute, such as ManagedObjectReference.Type for example.
// Note that xsi:type is already the last attribute in VC/ESX responses.
// This is only an issue with govmomi simulator generated responses.
// Proper fix will require finding a few needles in this xml package haystack.
x := len(start.Attr) - 1
if i != x {
start.Attr[i] = start.Attr[x]
start.Attr[x] = a
}
break
}
}