Update vmware/govmomi vendor: add vapi package

Zones implementation for vSphere cloud provider needs dependencies
which are not included in current vmware/govmomi vendor. So this
update added "vapi" package to support zones.
This commit is contained in:
jiatongw
2018-08-03 13:24:51 -07:00
parent 99abd4bc79
commit 5c44fd871f
41 changed files with 2046 additions and 283 deletions

View File

@@ -7,6 +7,7 @@ Cédric Blomart <cblomart@gmail.com> cedric <cblomart@gmail.com>
David Stark <dave@davidstark.name> <david.stark@bskyb.com>
Eric Gray <egray@vmware.com> <ericgray@users.noreply.github.com>
Eric Yutao <eric.yutao@gmail.com> eric <eric.yutao@gmail.com>
Fabio Rapposelli <fabio@vmware.com> <fabio@rapposelli.org>
Henrik Hodne <henrik@travis-ci.com> <henrik@hodne.io>
Jeremy Canady <jcanady@jackhenry.com> <jcanady@gmail.com>
Pieter Noordhuis <pnoordhuis@vmware.com> <pcnoordhuis@gmail.com>

View File

@@ -1,10 +1,8 @@
sudo: false
sudo: required
language: go
go:
- 1.8.x
- 1.9.x
- '1.10'
go_import_path: github.com/vmware/govmomi
@@ -27,4 +25,4 @@ deploy:
on:
tags: true
condition: $TRAVIS_OS_NAME = linux
go: '1.10'
go: '1.10'

View File

@@ -37,6 +37,7 @@ filegroup(
"//vendor/github.com/vmware/govmomi/simulator:all-srcs",
"//vendor/github.com/vmware/govmomi/sts:all-srcs",
"//vendor/github.com/vmware/govmomi/task:all-srcs",
"//vendor/github.com/vmware/govmomi/vapi/tags:all-srcs",
"//vendor/github.com/vmware/govmomi/vim25:all-srcs",
],
tags = ["automanaged"],

View File

@@ -1,5 +1,31 @@
# changelog
### unreleased
* SetRootCAs on the soap.Client returns an error for invalid certificates
* Add ClusterComputeResource.MoveInto method
### 0.18.0 (2018-05-24)
* Add VirtualDiskManager wrapper to set UUID
* Add vmxnet2, pcnet32 and sriov to VirtualDeviceList.EthernetCardTypes
* Add new vSphere 6.7 APIs
* Decrease LoginExtensionByCertificate tunnel usage
* SAML token authentication support via SessionManager.LoginByToken
* New SSO admin client for managing users
* New STS client for issuing and renewing SAML tokens
* New Lookup Service client for discovering endpoints such as STS and ssoadmin
* Switch from gvt to go dep for managing dependencies
### 0.17.1 (2018-03-19)
* vcsim: add Destroy method for Folder and Datacenter types

View File

@@ -6,9 +6,11 @@
Abhijeet Kasurde <akasurde@redhat.com>
abrarshivani <abrarshivani@users.noreply.github.com>
Adam Shannon <adamkshannon@gmail.com>
akutz <sakutz@gmail.com>
Alessandro Cortiana <alessandro.cortiana@gmail.com>
Alex Bozhenko <alexbozhenko@fb.com>
Alvaro Miranda <kikitux@gmail.com>
amandahla <amanda.andrade@serpro.gov.br>
Amanda H. L. de Andrade <amanda.andrade@serpro.gov.br>
Amit Bathla <abathla@.vmware.com>
amit bezalel <amit.bezalel@hpe.com>
@@ -23,14 +25,15 @@ bastienbc <bastien.barbe.creuly@gmail.com>
Bob Killen <killen.bob@gmail.com>
Brad Fitzpatrick <bradfitz@golang.org>
Bruce Downs <bruceadowns@gmail.com>
Cédric Blomart <cblomart@gmail.com>
Chris Marchesi <chrism@vancluevertech.com>
Christian Höltje <docwhat@gerf.org>
Clint Greenwood <cgreenwood@vmware.com>
Cédric Blomart <cblomart@gmail.com>
Danny Lockard <danny.lockard@banno.com>
Dave Tucker <dave@dtucker.co.uk>
David Stark <dave@davidstark.name>
Davide Agnello <dagnello@hp.com>
David Stark <dave@davidstark.name>
Deric Crago <deric.crago@gmail.com>
Doug MacEachern <dougm@vmware.com>
Eloy Coto <eloy.coto@gmail.com>
Eric Gray <egray@vmware.com>
@@ -52,11 +55,14 @@ Jason Kincl <jkincl@gmail.com>
Jeremy Canady <jcanady@jackhenry.com>
jeremy-clerc <jeremy@clerc.io>
João Pereira <joaodrp@gmail.com>
Jorge Sevilla <jorge.sevilla@rstor.io>
leslie-qiwa <leslie.qiwa@gmail.com>
Louie Jiang <jiangl@vmware.com>
Marc Carmier <mcarmier@gmail.com>
Matthew Cosgrove <matthew.cosgrove@dell.com>
Mevan Samaratunga <mevansam@gmail.com>
Nicolas Lamirault <nicolas.lamirault@gmail.com>
Omar Kohl <omarkohl@gmail.com>
Parham Alvani <parham.alvani@gmail.com>
Pieter Noordhuis <pnoordhuis@vmware.com>
runner.mei <runner.mei@gmail.com>

View File

@@ -18,7 +18,7 @@ install:
go install -v github.com/vmware/govmomi/vcsim
go-test:
go test -race -v $(TEST_OPTS) ./...
GORACE=history_size=5 go test -timeout 5m -count 1 -race -v $(TEST_OPTS) ./...
govc-test: install
(cd govc/test && ./vendor/github.com/sstephenson/bats/libexec/bats -t .)

View File

@@ -15,9 +15,9 @@ In addition to the vSphere API client, this repository includes:
## Compatibility
This library is built for and tested against ESXi and vCenter 6.0 and 6.5.
This library is built for and tested against ESXi and vCenter 6.0, 6.5 and 6.7.
It should work with versions 5.5 and 5.1, but neither are officially supported.
It may work with versions 5.5 and 5.1, but neither are officially supported.
## Documentation

View File

@@ -68,3 +68,22 @@ func (c ClusterComputeResource) AddHost(ctx context.Context, spec types.HostConn
return NewTask(c.c, res.Returnval), nil
}
func (c ClusterComputeResource) MoveInto(ctx context.Context, hosts ...*HostSystem) (*Task, error) {
req := types.MoveInto_Task{
This: c.Reference(),
}
hostReferences := make([]types.ManagedObjectReference, len(hosts))
for i, host := range hosts {
hostReferences[i] = host.Reference()
}
req.Host = hostReferences
res, err := methods.MoveInto_Task(ctx, c.c, &req)
if err != nil {
return nil, err
}
return NewTask(c.c, res.Returnval), nil
}

View File

@@ -209,3 +209,19 @@ func (m VirtualDiskManager) QueryVirtualDiskUuid(ctx context.Context, name strin
return res.Returnval, nil
}
func (m VirtualDiskManager) SetVirtualDiskUuid(ctx context.Context, name string, dc *Datacenter, uuid string) error {
req := types.SetVirtualDiskUuid{
This: m.Reference(),
Name: name,
Uuid: uuid,
}
if dc != nil {
ref := dc.Reference()
req.Datacenter = &ref
}
_, err := methods.SetVirtualDiskUuid(ctx, m.c, &req)
return err
}

View File

@@ -799,3 +799,16 @@ func (v VirtualMachine) UpgradeVM(ctx context.Context, version string) (*Task, e
return NewTask(v.c, res.Returnval), nil
}
// UUID is a helper to get the UUID of the VirtualMachine managed object.
// This method returns an empty string if an error occurs when retrieving UUID from the VirtualMachine object.
func (v VirtualMachine) UUID(ctx context.Context) string {
var o mo.VirtualMachine
err := v.Properties(ctx, v.Reference(), []string{"config.uuid"}, &o)
if err != nil {
return ""
}
return o.Config.Uuid
}

View File

@@ -111,6 +111,12 @@ func (p *Collector) WaitForUpdates(ctx context.Context, v string) (*types.Update
return res.Returnval, nil
}
func (p *Collector) CancelWaitForUpdates(ctx context.Context) error {
req := &types.CancelWaitForUpdates{This: p.Reference()}
_, err := methods.CancelWaitForUpdates(ctx, p.roundTripper, req)
return err
}
func (p *Collector) RetrieveProperties(ctx context.Context, req types.RetrieveProperties) (*types.RetrievePropertiesResponse, error) {
req.This = p.Reference()
return methods.RetrieveProperties(ctx, p.roundTripper, &req)

View File

@@ -19,12 +19,14 @@ package property
import (
"context"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types"
)
// WaitFilter provides helpers to construct a types.CreateFilter for use with property.Wait
type WaitFilter struct {
types.CreateFilter
Options *types.WaitOptions
}
// Add a new ObjectSpec and PropertySpec to the WaitFilter
@@ -75,6 +77,7 @@ func Wait(ctx context.Context, c *Collector, obj types.ManagedObjectReference, p
// creates a new property collector and calls CreateFilter. A new property
// collector is required because filters can only be added, not removed.
//
// If the Context is canceled, a call to CancelWaitForUpdates() is made and its error value is returned.
// The newly created collector is destroyed before this function returns (both
// in case of success or error).
//
@@ -85,7 +88,7 @@ func WaitForUpdates(ctx context.Context, c *Collector, filter *WaitFilter, f fun
}
// Attempt to destroy the collector using the background context, as the
// specified context may have timed out or have been cancelled.
// specified context may have timed out or have been canceled.
defer p.Destroy(context.Background())
err = p.CreateFilter(ctx, filter.CreateFilter)
@@ -93,20 +96,33 @@ func WaitForUpdates(ctx context.Context, c *Collector, filter *WaitFilter, f fun
return err
}
for version := ""; ; {
res, err := p.WaitForUpdates(ctx, version)
req := types.WaitForUpdatesEx{
This: p.Reference(),
Options: filter.Options,
}
for {
res, err := methods.WaitForUpdatesEx(ctx, p.roundTripper, &req)
if err != nil {
if ctx.Err() == context.Canceled {
werr := p.CancelWaitForUpdates(context.Background())
return werr
}
return err
}
// Retry if the result came back empty
if res == nil {
set := res.Returnval
if set == nil {
if req.Options != nil && req.Options.MaxWaitSeconds != nil {
return nil // WaitOptions.MaxWaitSeconds exceeded
}
// Retry if the result came back empty
continue
}
version = res.Version
req.Version = set.Version
for _, fs := range res.FilterSet {
for _, fs := range set.FilterSet {
if f(fs.ObjectSet) {
return nil
}

View File

@@ -106,11 +106,10 @@ func (s *DistributedVirtualSwitch) ReconfigureDvsTask(req *types.ReconfigureDvs_
switch types.ConfigSpecOperation(member.Operation) {
case types.ConfigSpecOperationAdd:
if FindReference(host.Network, s.Self) != nil {
if FindReference(s.Summary.HostMember, member.Host) != nil {
return nil, &types.AlreadyExists{Name: host.Name}
}
Map.AppendReference(host, &host.Network, s.Self)
Map.AppendReference(host, &host.Network, s.Portgroup...)
s.Summary.HostMember = append(s.Summary.HostMember, member.Host)
@@ -129,8 +128,7 @@ func (s *DistributedVirtualSwitch) ReconfigureDvsTask(req *types.ReconfigureDvs_
}
}
Map.RemoveReference(host, &host.Network, s.Self)
RemoveReference(&s.Summary.HostMember, s.Self)
RemoveReference(&s.Summary.HostMember, member.Host)
case types.ConfigSpecOperationEdit:
return nil, &types.NotSupported{}
}

View File

@@ -161,6 +161,12 @@ var EventInfo = []types.EventDescriptionEventDetail{
Category: "info",
FullFormat: "{{.Vm.Name}} on host {{.Host.Name}} in {{.Datacenter.Name}} is starting",
},
{
Key: "VmStoppingEvent",
Description: "VM stopping",
Category: "info",
FullFormat: "{{.Vm.Name}} on host {{.Host.Name}} in {{.Datacenter.Name}} is stopping",
},
{
Key: "VmSuspendingEvent",
Description: "VM being suspended",

View File

@@ -157,15 +157,18 @@ func (m *EventManager) PostEvent(ctx *Context, req *types.PostEvent) soap.HasFau
event.CreatedTime = time.Now()
event.UserName = ctx.Session.UserName
m.page = m.page.Next()
m.page = m.page.Prev()
m.page.Value = req.EventToPost
m.formatMessage(req.EventToPost)
for _, c := range m.collectors {
if c.eventMatches(req.EventToPost) {
c.page = c.page.Next()
c.page.Value = event
}
ctx.WithLock(c, func() {
if c.eventMatches(req.EventToPost) {
c.page = c.page.Prev()
c.page.Value = req.EventToPost
Map.Update(c, []types.PropertyChange{{Name: "latestPage", Val: c.GetLatestPage()}})
}
})
}
return &methods.PostEventBody{

View File

@@ -29,12 +29,10 @@ func (ds *Datastore) stat() error {
return err
}
bsize := uint64(stat.Bsize) / 512
info.FreeSpace = int64(stat.Bfree*bsize) >> 1
info.FreeSpace = int64(stat.Bfree * uint64(stat.Bsize))
ds.Summary.FreeSpace = info.FreeSpace
ds.Summary.Capacity = int64(stat.Blocks*bsize) >> 1
ds.Summary.Capacity = int64(stat.Blocks * uint64(stat.Bsize))
return nil
}

View File

@@ -17,11 +17,14 @@ limitations under the License.
package simulator
import (
"context"
"errors"
"log"
"path"
"reflect"
"strings"
"sync"
"time"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/methods"
@@ -32,6 +35,11 @@ import (
type PropertyCollector struct {
mo.PropertyCollector
nopLocker
updates []types.ObjectUpdate
mu sync.Mutex
cancel context.CancelFunc
}
func NewPropertyCollector(ref types.ManagedObjectReference) object.Reference {
@@ -72,6 +80,10 @@ func getObject(ctx *Context, ref types.ManagedObjectReference) (reflect.Value, b
obj = o.Get()
}
return getManagedObject(obj), true
}
func getManagedObject(obj mo.Reference) reflect.Value {
rval := reflect.ValueOf(obj).Elem()
rtype := rval.Type()
@@ -82,26 +94,21 @@ func getObject(ctx *Context, ref types.ManagedObjectReference) (reflect.Value, b
// for the case where the type has a field of the same name, for example:
// mo.ResourcePool.ResourcePool
for {
if path.Base(rtype.PkgPath()) != "mo" {
if rtype.Kind() != reflect.Struct || rtype.NumField() == 0 {
log.Printf("%#v does not have an embedded mo type", ref)
return reflect.Value{}, false
}
rval = rval.Field(0)
rtype = rval.Type()
} else {
if path.Base(rtype.PkgPath()) == "mo" {
break
}
if rtype.Kind() != reflect.Struct || rtype.NumField() == 0 {
log.Panicf("%#v does not have an embedded mo type", obj.Reference())
}
rval = rval.Field(0)
rtype = rval.Type()
}
return rval, true
return rval
}
func fieldValueInterface(f reflect.StructField, rval reflect.Value) interface{} {
if rval.Kind() == reflect.Ptr {
rval = rval.Elem()
}
// wrapValue converts slice types to the appropriate ArrayOf type used in property collector responses.
func wrapValue(rval reflect.Value, rtype reflect.Type) interface{} {
pval := rval.Interface()
if rval.Kind() == reflect.Slice {
@@ -128,7 +135,7 @@ func fieldValueInterface(f reflect.StructField, rval reflect.Value) interface{}
Long: v,
}
default:
kind := f.Type.Elem().Name()
kind := rtype.Elem().Name()
// Remove govmomi interface prefix name
if strings.HasPrefix(kind, "Base") {
kind = kind[4:]
@@ -143,6 +150,14 @@ func fieldValueInterface(f reflect.StructField, rval reflect.Value) interface{}
return pval
}
func fieldValueInterface(f reflect.StructField, rval reflect.Value) interface{} {
if rval.Kind() == reflect.Ptr {
rval = rval.Elem()
}
return wrapValue(rval, f.Type)
}
func fieldValue(rval reflect.Value, p string) (interface{}, error) {
var value interface{}
fields := strings.Split(p, ".")
@@ -401,7 +416,9 @@ func (pc *PropertyCollector) collect(ctx *Context, r *types.RetrievePropertiesEx
// Select object references
for _, spec := range r.SpecSet {
for _, o := range spec.ObjectSet {
rval, ok := getObject(ctx, o.Obj)
var rval reflect.Value
ok := false
ctx.WithLock(o.Obj, func() { rval, ok = getObject(ctx, o.Obj) })
if !ok {
if isFalse(spec.ReportMissingObjectsInResults) {
return nil, &types.ManagedObjectNotFound{Obj: o.Obj}
@@ -420,7 +437,7 @@ func (pc *PropertyCollector) collect(ctx *Context, r *types.RetrievePropertiesEx
}
for _, ref := range refs {
rr.collect(ctx, ref)
ctx.WithLock(ref, func() { rr.collect(ctx, ref) })
}
return rr.RetrieveResult, nil
@@ -429,7 +446,10 @@ func (pc *PropertyCollector) collect(ctx *Context, r *types.RetrievePropertiesEx
func (pc *PropertyCollector) CreateFilter(ctx *Context, c *types.CreateFilter) soap.HasFault {
body := &methods.CreateFilterBody{}
filter := &PropertyFilter{pc: pc}
filter := &PropertyFilter{
pc: pc,
refs: make(map[types.ManagedObjectReference]struct{}),
}
filter.PartialUpdates = c.PartialUpdates
filter.Spec = c.Spec
@@ -455,14 +475,17 @@ func (pc *PropertyCollector) CreatePropertyCollector(ctx *Context, c *types.Crea
}
func (pc *PropertyCollector) DestroyPropertyCollector(ctx *Context, c *types.DestroyPropertyCollector) soap.HasFault {
pc.CancelWaitForUpdates(&types.CancelWaitForUpdates{This: c.This})
body := &methods.DestroyPropertyCollectorBody{}
for _, ref := range pc.Filter {
filter := ctx.Session.Get(ref).(*PropertyFilter)
filter.DestroyPropertyFilter(&types.DestroyPropertyFilter{This: ref})
filter.DestroyPropertyFilter(ctx, &types.DestroyPropertyFilter{This: ref})
}
ctx.Session.Remove(c.This)
ctx.Map.Remove(c.This)
body.Res = &types.DestroyPropertyCollectorResponse{}
@@ -519,24 +542,46 @@ func (pc *PropertyCollector) RetrieveProperties(ctx *Context, r *types.RetrieveP
}
func (pc *PropertyCollector) CancelWaitForUpdates(r *types.CancelWaitForUpdates) soap.HasFault {
pc.mu.Lock()
if pc.cancel != nil {
pc.cancel()
}
pc.mu.Unlock()
return &methods.CancelWaitForUpdatesBody{Res: new(types.CancelWaitForUpdatesResponse)}
}
func (pc *PropertyCollector) WaitForUpdatesEx(ctx *Context, r *types.WaitForUpdatesEx) soap.HasFault {
body := &methods.WaitForUpdatesExBody{}
func (pc *PropertyCollector) update(u types.ObjectUpdate) {
pc.mu.Lock()
pc.updates = append(pc.updates, u)
pc.mu.Unlock()
}
// At the moment we need to support Task completion. Handlers can simply set the Task
// state before returning and the non-incremental update is enough for the client.
// We can wait for incremental updates to simulate timeouts, etc.
if r.Version != "" {
body.Fault_ = Fault("incremental updates not supported yet", &types.NotSupported{})
return body
}
func (pc *PropertyCollector) PutObject(o mo.Reference) {
pc.update(types.ObjectUpdate{
Obj: o.Reference(),
Kind: types.ObjectUpdateKindEnter,
ChangeSet: nil,
})
}
update := &types.UpdateSet{
Version: "-",
}
func (pc *PropertyCollector) UpdateObject(o mo.Reference, changes []types.PropertyChange) {
pc.update(types.ObjectUpdate{
Obj: o.Reference(),
Kind: types.ObjectUpdateKindModify,
ChangeSet: changes,
})
}
func (pc *PropertyCollector) RemoveObject(ref types.ManagedObjectReference) {
pc.update(types.ObjectUpdate{
Obj: ref,
Kind: types.ObjectUpdateKindLeave,
ChangeSet: nil,
})
}
func (pc *PropertyCollector) apply(ctx *Context, update *types.UpdateSet) types.BaseMethodFault {
for _, ref := range pc.Filter {
filter := ctx.Session.Get(ref).(*PropertyFilter)
@@ -545,8 +590,7 @@ func (pc *PropertyCollector) WaitForUpdatesEx(ctx *Context, r *types.WaitForUpda
res, fault := pc.collect(ctx, r)
if fault != nil {
body.Fault_ = Fault("", fault)
return body
return fault
}
fu := types.PropertyFilterUpdate{
@@ -554,6 +598,10 @@ func (pc *PropertyCollector) WaitForUpdatesEx(ctx *Context, r *types.WaitForUpda
}
for _, o := range res.Objects {
if _, ok := filter.refs[o.Obj]; ok {
continue
}
filter.refs[o.Obj] = struct{}{}
ou := types.ObjectUpdate{
Obj: o.Obj,
Kind: types.ObjectUpdateKindEnter,
@@ -570,14 +618,122 @@ func (pc *PropertyCollector) WaitForUpdatesEx(ctx *Context, r *types.WaitForUpda
fu.ObjectSet = append(fu.ObjectSet, ou)
}
update.FilterSet = append(update.FilterSet, fu)
if len(fu.ObjectSet) != 0 {
update.FilterSet = append(update.FilterSet, fu)
}
}
return nil
}
func (pc *PropertyCollector) WaitForUpdatesEx(ctx *Context, r *types.WaitForUpdatesEx) soap.HasFault {
wait, cancel := context.WithCancel(context.Background())
if r.Options != nil {
if max := r.Options.MaxWaitSeconds; max != nil {
wait, cancel = context.WithTimeout(context.Background(), time.Second*time.Duration(*max))
}
}
pc.mu.Lock()
pc.cancel = cancel
pc.mu.Unlock()
body := &methods.WaitForUpdatesExBody{}
set := &types.UpdateSet{
Version: r.Version,
}
body.Res = &types.WaitForUpdatesExResponse{
Returnval: update,
Returnval: set,
}
return body
apply := func() bool {
if fault := pc.apply(ctx, set); fault != nil {
body.Fault_ = Fault("", fault)
body.Res = nil
return false
}
return true
}
if r.Version == "" {
apply() // Collect current state
set.Version = "-" // Next request with Version set will wait via loop below
ctx.Map.AddHandler(pc) // Listen for create, update, delete of managed objects
return body
}
ticker := time.NewTicker(250 * time.Millisecond) // allow for updates to accumulate
defer ticker.Stop()
// Start the wait loop, returning on one of:
// - Client calls CancelWaitForUpdates
// - MaxWaitSeconds was specified and has been exceeded
// - We have updates to send to the client
for {
select {
case <-wait.Done():
body.Res.Returnval = nil
switch wait.Err() {
case context.Canceled:
log.Printf("%s: WaitForUpdates canceled", pc.Self)
body.Fault_ = Fault("", new(types.RequestCanceled)) // CancelWaitForUpdates was called
body.Res = nil
case context.DeadlineExceeded:
log.Printf("%s: WaitForUpdates MaxWaitSeconds exceeded", pc.Self)
}
return body
case <-ticker.C:
pc.mu.Lock()
updates := pc.updates
pc.updates = nil // clear updates collected by the managed object CRUD listeners
pc.mu.Unlock()
if len(updates) == 0 {
continue
}
log.Printf("%s: applying %d updates to %d filters", pc.Self, len(updates), len(pc.Filter))
for _, f := range pc.Filter {
filter := ctx.Session.Get(f).(*PropertyFilter)
fu := types.PropertyFilterUpdate{Filter: f}
for _, update := range updates {
switch update.Kind {
case types.ObjectUpdateKindEnter: // Create
if !apply() {
return body
}
case types.ObjectUpdateKindModify: // Update
log.Printf("%s has %d changes", update.Obj, len(update.ChangeSet))
if !apply() { // An update may apply to collector traversal specs
return body
}
if _, ok := filter.refs[update.Obj]; ok {
// This object has already been applied by the filter,
// now check if the property spec applies for this update.
update = filter.apply(ctx, update)
if len(update.ChangeSet) != 0 {
fu.ObjectSet = append(fu.ObjectSet, update)
}
}
case types.ObjectUpdateKindLeave: // Delete
if _, ok := filter.refs[update.Obj]; !ok {
continue
}
delete(filter.refs, update.Obj)
fu.ObjectSet = append(fu.ObjectSet, update)
}
}
if len(fu.ObjectSet) != 0 {
set.FilterSet = append(set.FilterSet, fu)
}
}
if len(set.FilterSet) != 0 {
return body
}
}
}
}
// WaitForUpdates is deprecated, but pyvmomi is still using it at the moment.

View File

@@ -17,6 +17,9 @@ limitations under the License.
package simulator
import (
"reflect"
"strings"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/soap"
@@ -26,17 +29,71 @@ import (
type PropertyFilter struct {
mo.PropertyFilter
pc *PropertyCollector
pc *PropertyCollector
refs map[types.ManagedObjectReference]struct{}
}
func (f *PropertyFilter) DestroyPropertyFilter(c *types.DestroyPropertyFilter) soap.HasFault {
func (f *PropertyFilter) DestroyPropertyFilter(ctx *Context, c *types.DestroyPropertyFilter) soap.HasFault {
body := &methods.DestroyPropertyFilterBody{}
RemoveReference(&f.pc.Filter, c.This)
Map.Remove(c.This)
ctx.Session.Remove(c.This)
body.Res = &types.DestroyPropertyFilterResponse{}
return body
}
// matches returns true if the change matches one of the filter Spec.PropSet
func (f *PropertyFilter) matches(ctx *Context, ref types.ManagedObjectReference, change *types.PropertyChange) bool {
for _, p := range f.Spec.PropSet {
if p.Type != ref.Type {
continue
}
if isTrue(p.All) {
return true
}
for _, name := range p.PathSet {
if name == change.Name {
return true
}
// strings.HasPrefix("runtime.powerState", "runtime") == parent field matches
if strings.HasPrefix(change.Name, name) {
if obj := ctx.Map.Get(ref); obj != nil { // object may have since been deleted
change.Name = name
change.Val, _ = fieldValue(reflect.ValueOf(obj), name)
}
return true
}
}
}
return false
}
// apply the PropertyFilter.Spec to the given ObjectUpdate
func (f *PropertyFilter) apply(ctx *Context, change types.ObjectUpdate) types.ObjectUpdate {
parents := make(map[string]bool)
set := change.ChangeSet
change.ChangeSet = nil
for i, p := range set {
if f.matches(ctx, change.Obj, &p) {
if p.Name != set[i].Name {
// update matches a parent field from the spec.
if parents[p.Name] {
continue // only return 1 instance of the parent
}
parents[p.Name] = true
}
change.ChangeSet = append(change.ChangeSet, p)
}
}
return change
}

View File

@@ -23,6 +23,7 @@ import (
"reflect"
"strings"
"sync"
"sync/atomic"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/mo"
@@ -46,10 +47,11 @@ var refValueMap = map[string]string{
// Map is the default Registry instance.
var Map = NewRegistry()
// RegisterObject interface supports callbacks when objects are added and removed from the Registry
// RegisterObject interface supports callbacks when objects are created, updated and deleted from the Registry
type RegisterObject interface {
mo.Reference
PutObject(mo.Reference)
UpdateObject(mo.Reference, []types.PropertyChange)
RemoveObject(types.ManagedObjectReference)
}
@@ -59,7 +61,7 @@ type Registry struct {
objects map[types.ManagedObjectReference]mo.Reference
handlers map[types.ManagedObjectReference]RegisterObject
locks map[types.ManagedObjectReference]sync.Locker
counter int
counter int64
Namespace string
Path string
@@ -112,8 +114,8 @@ func (r *Registry) newReference(item mo.Reference) types.ManagedObjectReference
}
if ref.Value == "" {
r.counter++
ref.Value = fmt.Sprintf("%s-%d", valuePrefix(ref.Type), r.counter)
n := atomic.AddInt64(&r.counter, 1)
ref.Value = fmt.Sprintf("%s-%d", valuePrefix(ref.Type), n)
}
return ref
@@ -126,7 +128,9 @@ func (r *Registry) setReference(item mo.Reference, ref types.ManagedObjectRefere
// AddHandler adds a RegisterObject handler to the Registry.
func (r *Registry) AddHandler(h RegisterObject) {
r.m.Lock()
r.handlers[h.Reference()] = h
r.m.Unlock()
}
// NewEntity sets Entity().Self with a new, unique Value.
@@ -173,10 +177,23 @@ func (r *Registry) Any(kind string) mo.Entity {
return nil
}
// applyHandlers calls the given func for each r.handlers
func (r *Registry) applyHandlers(f func(o RegisterObject)) {
r.m.Lock()
handlers := make([]RegisterObject, 0, len(r.handlers))
for _, handler := range r.handlers {
handlers = append(handlers, handler)
}
r.m.Unlock()
for i := range handlers {
f(handlers[i])
}
}
// Put adds a new object to Registry, generating a ManagedObjectReference if not already set.
func (r *Registry) Put(item mo.Reference) mo.Reference {
r.m.Lock()
defer r.m.Unlock()
ref := item.Reference()
if ref.Type == "" || ref.Value == "" {
@@ -192,25 +209,50 @@ func (r *Registry) Put(item mo.Reference) mo.Reference {
r.objects[ref] = item
for _, h := range r.handlers {
h.PutObject(item)
}
r.m.Unlock()
r.applyHandlers(func(o RegisterObject) {
o.PutObject(item)
})
return item
}
// Remove removes an object from the Registry.
func (r *Registry) Remove(item types.ManagedObjectReference) {
r.applyHandlers(func(o RegisterObject) {
o.RemoveObject(item)
})
r.m.Lock()
defer r.m.Unlock()
for _, h := range r.handlers {
h.RemoveObject(item)
}
delete(r.objects, item)
delete(r.handlers, item)
delete(r.locks, item)
r.m.Unlock()
}
// Update dispatches object property changes to RegisterObject handlers,
// such as any PropertyCollector instances with in-progress WaitForUpdates calls.
// The changes are also applied to the given object via mo.ApplyPropertyChange,
// so there is no need to set object fields directly.
func (r *Registry) Update(obj mo.Reference, changes []types.PropertyChange) {
for i := range changes {
if changes[i].Op == "" {
changes[i].Op = types.PropertyChangeOpAssign
}
if changes[i].Val != nil {
rval := reflect.ValueOf(changes[i].Val)
changes[i].Val = wrapValue(rval, rval.Type())
}
}
val := getManagedObject(obj).Addr().Interface().(mo.Reference)
mo.ApplyPropertyChange(val, changes)
r.applyHandlers(func(o RegisterObject) {
o.UpdateObject(val, changes)
})
}
// getEntityParent traverses up the inventory and returns the first object of type kind.
@@ -417,11 +459,23 @@ func (r *Registry) MarshalJSON() ([]byte, error) {
}
func (r *Registry) locker(obj mo.Reference) sync.Locker {
var ref types.ManagedObjectReference
switch x := obj.(type) {
case types.ManagedObjectReference:
ref = x
obj = r.Get(ref) // to check for sync.Locker
case *types.ManagedObjectReference:
ref = *x
obj = r.Get(ref) // to check for sync.Locker
default:
ref = obj.Reference()
}
if mu, ok := obj.(sync.Locker); ok {
return mu
}
ref := obj.Reference()
r.m.Lock()
mu, ok := r.locks[ref]
if !ok {
@@ -444,3 +498,9 @@ func (r *Registry) WithLock(obj mo.Reference, f func()) {
}
f()
}
// nopLocker can be embedded to opt-out of auto-locking (see Registry.WithLock)
type nopLocker struct{}
func (*nopLocker) Lock() {}
func (*nopLocker) Unlock() {}

View File

@@ -57,15 +57,18 @@ func (s *SearchIndex) FindByDatastorePath(r *types.FindByDatastorePath) soap.Has
func (s *SearchIndex) FindByInventoryPath(req *types.FindByInventoryPath) soap.HasFault {
body := &methods.FindByInventoryPathBody{Res: new(types.FindByInventoryPathResponse)}
path := strings.Split(req.InventoryPath, "/")
if len(path) <= 1 {
split := func(c rune) bool {
return c == '/'
}
path := strings.FieldsFunc(req.InventoryPath, split)
if len(path) < 1 {
return body
}
root := Map.content().RootFolder
o := &root
for _, name := range path[1:] {
for _, name := range path {
f := s.FindChild(&types.FindChild{Entity: *o, Name: name})
o = f.(*methods.FindChildBody).Res.Returnval
@@ -132,9 +135,16 @@ func (s *SearchIndex) FindByUuid(req *types.FindByUuid) soap.HasFault {
if !ok {
continue
}
if vm.Config.Uuid == req.Uuid {
body.Res.Returnval = &ref
break
if req.InstanceUuid != nil && *req.InstanceUuid {
if vm.Config.InstanceUuid == req.Uuid {
body.Res.Returnval = &ref
break
}
} else {
if vm.Config.Uuid == req.Uuid {
body.Res.Returnval = &ref
break
}
}
}
} else {

View File

@@ -138,6 +138,12 @@ func (s *SessionManager) Logout(ctx *Context, _ *types.Logout) soap.HasFault {
session := ctx.Session
delete(s.sessions, session.Key)
for ref, obj := range ctx.Session.Registry.objects {
if _, ok := obj.(RegisterObject); ok {
ctx.Map.Remove(ref) // Remove RegisterObject handlers
}
}
ctx.postEvent(&types.UserLogoutSessionEvent{
IpAddress: session.IpAddress,
UserAgent: session.UserAgent,

View File

@@ -390,7 +390,7 @@ func (s *Service) findDatastore(query url.Values) (*Datastore, error) {
ctx := context.Background()
finder := find.NewFinder(s.client, false)
dc, err := finder.DatacenterOrDefault(ctx, query.Get("dcName"))
dc, err := finder.DatacenterOrDefault(ctx, query.Get("dcPath"))
if err != nil {
return nil, err
}

View File

@@ -55,18 +55,19 @@ func CreateTask(e mo.Reference, name string, run func(*Task) (types.AnyType, typ
Execute: run,
}
Map.Put(task)
task.Self = Map.newReference(task)
task.Info.Key = task.Self.Value
task.Info.Task = task.Self
task.Info.Name = ucFirst(name)
task.Info.DescriptionId = fmt.Sprintf("%s.%s", ref.Type, id)
task.Info.Entity = &ref
task.Info.EntityName = ref.Value
task.Info.Reason = &types.TaskReasonUser{UserName: "vcsim"} // TODO: Context.Session.User
task.Info.QueueTime = time.Now()
task.Info.State = types.TaskInfoStateQueued
Map.Put(task)
return task
}
@@ -78,25 +79,31 @@ type TaskRunner interface {
func (t *Task) Run() types.ManagedObjectReference {
now := time.Now()
t.Info.StartTime = &now
t.Info.State = types.TaskInfoStateRunning
Map.Update(t, []types.PropertyChange{
{Name: "info.startTime", Val: now},
{Name: "info.state", Val: types.TaskInfoStateRunning},
})
res, err := t.Execute(t)
now = time.Now()
t.Info.CompleteTime = &now
state := types.TaskInfoStateSuccess
var fault interface{}
if err != nil {
t.Info.State = types.TaskInfoStateError
t.Info.Error = &types.LocalizedMethodFault{
state = types.TaskInfoStateError
fault = types.LocalizedMethodFault{
Fault: err,
LocalizedMessage: fmt.Sprintf("%T", err),
}
} else {
t.Info.Result = res
t.Info.State = types.TaskInfoStateSuccess
}
now = time.Now()
Map.Update(t, []types.PropertyChange{
{Name: "info.completeTime", Val: now},
{Name: "info.state", Val: state},
{Name: "info.result", Val: res},
{Name: "info.error", Val: fault},
})
return t.Self
}

View File

@@ -17,6 +17,8 @@ limitations under the License.
package simulator
import (
"sync"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
@@ -26,6 +28,7 @@ var recentTaskMax = 200 // the VC limit
type TaskManager struct {
mo.TaskManager
sync.Mutex
}
func NewTaskManager(ref types.ManagedObjectReference) object.Reference {
@@ -41,12 +44,16 @@ func (m *TaskManager) PutObject(obj mo.Reference) {
return
}
m.RecentTask = append(m.RecentTask, ref)
if len(m.RecentTask) > recentTaskMax {
m.RecentTask = m.RecentTask[1:]
m.Lock()
recent := append(m.RecentTask, ref)
if len(recent) > recentTaskMax {
recent = recent[1:]
}
Map.Update(m, []types.PropertyChange{{Name: "recentTask", Val: recent}})
m.Unlock()
}
func (m *TaskManager) RemoveObject(_ types.ManagedObjectReference) {
}
func (*TaskManager) RemoveObject(types.ManagedObjectReference) {}
func (*TaskManager) UpdateObject(mo.Reference, []types.PropertyChange) {}

View File

@@ -137,7 +137,8 @@ type ContainerView struct {
types map[string]bool
}
func (v *ContainerView) DestroyView(c *types.DestroyView) soap.HasFault {
func (v *ContainerView) DestroyView(ctx *Context, c *types.DestroyView) soap.HasFault {
ctx.Session.Remove(c.This)
return destroyView(c.This)
}
@@ -192,3 +193,83 @@ func (v *ContainerView) add(root mo.Reference, seen map[types.ManagedObjectRefer
}
})
}
func (m *ViewManager) CreateListView(ctx *Context, req *types.CreateListView) soap.HasFault {
body := new(methods.CreateListViewBody)
list := new(ListView)
if err := list.add(req.Obj); err != nil {
body.Fault_ = Fault("", err)
return body
}
ctx.Session.Put(list)
body.Res = &types.CreateListViewResponse{
Returnval: list.Self,
}
return body
}
type ListView struct {
mo.ListView
}
func (v *ListView) update() {
Map.Update(v, []types.PropertyChange{{Name: "view", Val: v.View}})
}
func (v *ListView) add(refs []types.ManagedObjectReference) *types.ManagedObjectNotFound {
for _, ref := range refs {
obj := Map.Get(ref)
if obj == nil {
return &types.ManagedObjectNotFound{Obj: ref}
}
v.View = append(v.View, ref)
}
return nil
}
func (v *ListView) DestroyView(ctx *Context, c *types.DestroyView) soap.HasFault {
ctx.Session.Remove(c.This)
return destroyView(c.This)
}
func (v *ListView) ModifyListView(req *types.ModifyListView) soap.HasFault {
body := new(methods.ModifyListViewBody)
for _, ref := range req.Remove {
RemoveReference(&v.View, ref)
}
if err := v.add(req.Add); err != nil {
body.Fault_ = Fault("", err)
return body
}
body.Res = new(types.ModifyListViewResponse)
if len(req.Remove) != 0 || len(req.Add) != 0 {
v.update()
}
return body
}
func (v *ListView) ResetListView(req *types.ResetListView) soap.HasFault {
body := new(methods.ResetListViewBody)
v.View = nil
if err := v.add(req.Obj); err != nil {
body.Fault_ = Fault("", err)
return body
}
body.Res = new(types.ResetListViewResponse)
v.update()
return body
}

View File

@@ -210,3 +210,10 @@ func (m *VirtualDiskManager) QueryVirtualDiskUuid(req *types.QueryVirtualDiskUui
return body
}
func (m *VirtualDiskManager) SetVirtualDiskUuid(req *types.SetVirtualDiskUuid) soap.HasFault {
body := new(methods.SetVirtualDiskUuidBody)
// TODO: validate uuid format and persist
body.Res = new(types.SetVirtualDiskUuidResponse)
return body
}

View File

@@ -444,6 +444,13 @@ func numberToString(n int64, sep rune) string {
return buf.String()
}
func getDiskSize(disk *types.VirtualDisk) int64 {
if disk.CapacityInBytes == 0 {
return disk.CapacityInKB * 1024
}
return disk.CapacityInBytes
}
func (vm *VirtualMachine) configureDevice(devices object.VirtualDeviceList, spec *types.VirtualDeviceConfigSpec) types.BaseMethodFault {
device := spec.Device
d := device.GetVirtualDevice()
@@ -518,9 +525,16 @@ func (vm *VirtualMachine) configureDevice(devices object.VirtualDeviceList, spec
p, _ := parseDatastorePath(info.FileName)
host := Map.Get(*vm.Runtime.Host).(*HostSystem)
ds := Map.FindByName(p.Datastore, host.Datastore).Reference()
info.Datastore = &ds
entity := Map.FindByName(p.Datastore, host.Datastore)
ref := entity.Reference()
info.Datastore = &ref
ds := entity.(*Datastore)
// XXX: compare disk size and free space until windows stat is supported
ds.Summary.FreeSpace -= getDiskSize(x)
ds.Info.GetDatastoreInfo().FreeSpace = ds.Summary.FreeSpace
}
}
@@ -556,6 +570,15 @@ func (vm *VirtualMachine) removeDevice(devices object.VirtualDeviceList, spec *t
switch b := device.Backing.(type) {
case types.BaseVirtualDeviceFileBackingInfo:
file = b.GetVirtualDeviceFileBackingInfo().FileName
p, _ := parseDatastorePath(file)
host := Map.Get(*vm.Runtime.Host).(*HostSystem)
ds := Map.FindByName(p.Datastore, host.Datastore).(*Datastore)
ds.Summary.FreeSpace += getDiskSize(device)
ds.Info.GetDatastoreInfo().FreeSpace = ds.Summary.FreeSpace
}
if file != "" {
@@ -686,15 +709,9 @@ func (c *powerVMTask) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
}
}
c.VirtualMachine.Runtime.PowerState = c.state
c.VirtualMachine.Summary.Runtime.PowerState = c.state
bt := &c.VirtualMachine.Summary.Runtime.BootTime
var boot types.AnyType
if c.state == types.VirtualMachinePowerStatePoweredOn {
now := time.Now()
*bt = &now
} else {
*bt = nil
boot = time.Now()
}
event := c.event()
@@ -705,9 +722,23 @@ func (c *powerVMTask) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
&types.VmPoweredOnEvent{VmEvent: event},
)
case types.VirtualMachinePowerStatePoweredOff:
c.ctx.postEvent(&types.VmPoweredOffEvent{VmEvent: event})
c.ctx.postEvent(
&types.VmStoppingEvent{VmEvent: event},
&types.VmPoweredOffEvent{VmEvent: event},
)
case types.VirtualMachinePowerStateSuspended:
c.ctx.postEvent(
&types.VmSuspendingEvent{VmEvent: event},
&types.VmSuspendedEvent{VmEvent: event},
)
}
Map.Update(c.VirtualMachine, []types.PropertyChange{
{Name: "runtime.powerState", Val: c.state},
{Name: "summary.runtime.powerState", Val: c.state},
{Name: "summary.runtime.bootTime", Val: boot},
})
return nil, nil
}
@@ -739,6 +770,37 @@ func (vm *VirtualMachine) PowerOffVMTask(ctx *Context, c *types.PowerOffVM_Task)
}
}
func (vm *VirtualMachine) SuspendVMTask(ctx *Context, req *types.SuspendVM_Task) soap.HasFault {
runner := &powerVMTask{vm, types.VirtualMachinePowerStateSuspended, ctx}
task := CreateTask(runner.Reference(), "suspend", runner.Run)
return &methods.SuspendVM_TaskBody{
Res: &types.SuspendVM_TaskResponse{
Returnval: task.Run(),
},
}
}
func (vm *VirtualMachine) ResetVMTask(ctx *Context, req *types.ResetVM_Task) soap.HasFault {
task := CreateTask(vm, "reset", func(task *Task) (types.AnyType, types.BaseMethodFault) {
res := vm.PowerOffVMTask(ctx, &types.PowerOffVM_Task{This: vm.Self})
ctask := Map.Get(res.(*methods.PowerOffVM_TaskBody).Res.Returnval).(*Task)
if ctask.Info.Error != nil {
return nil, ctask.Info.Error.Fault
}
_ = vm.PowerOnVMTask(ctx, &types.PowerOnVM_Task{This: vm.Self})
return nil, nil
})
return &methods.ResetVM_TaskBody{
Res: &types.ResetVM_TaskResponse{
Returnval: task.Run(),
},
}
}
func (vm *VirtualMachine) ReconfigVMTask(ctx *Context, req *types.ReconfigVM_Task) soap.HasFault {
task := CreateTask(vm, "reconfigVm", func(t *Task) (types.AnyType, types.BaseMethodFault) {
err := vm.configure(&req.Spec)
@@ -771,6 +833,11 @@ func (vm *VirtualMachine) DestroyTask(ctx *Context, req *types.Destroy_Task) soa
return nil, r.Fault().VimFault().(types.BaseMethodFault)
}
// Remove all devices
devices := object.VirtualDeviceList(vm.Config.Hardware.Device)
spec, _ := devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationRemove)
vm.configureDevices(&types.VirtualMachineConfigSpec{DeviceChange: spec})
// Delete VM files from the datastore (ignoring result for now)
m := Map.FileManager()
dc := Map.getEntityDatacenter(vm).Reference()
@@ -907,29 +974,36 @@ func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soa
func (vm *VirtualMachine) RelocateVMTask(req *types.RelocateVM_Task) soap.HasFault {
task := CreateTask(vm, "relocateVm", func(t *Task) (types.AnyType, types.BaseMethodFault) {
var changes []types.PropertyChange
if ref := req.Spec.Datastore; ref != nil {
ds := Map.Get(*ref).(*Datastore)
Map.RemoveReference(ds, &ds.Vm, *ref)
vm.Datastore = []types.ManagedObjectReference{*ref}
// TODO: migrate vm.Config.Files (and vm.Summary.Config.VmPathName)
changes = append(changes, types.PropertyChange{Name: "datastore", Val: []types.ManagedObjectReference{*ref}})
}
if ref := req.Spec.Pool; ref != nil {
pool := Map.Get(*ref).(*ResourcePool)
Map.RemoveReference(pool, &pool.Vm, *ref)
vm.ResourcePool = ref
changes = append(changes, types.PropertyChange{Name: "resourcePool", Val: *ref})
}
if ref := req.Spec.Host; ref != nil {
host := Map.Get(*ref).(*HostSystem)
Map.RemoveReference(host, &host.Vm, *ref)
vm.Runtime.Host = ref
changes = append(changes,
types.PropertyChange{Name: "runtime.host", Val: *ref},
types.PropertyChange{Name: "summary.runtime.host", Val: *ref},
)
}
Map.Update(vm, changes)
return nil, nil
})
@@ -1032,7 +1106,7 @@ func (vm *VirtualMachine) RemoveAllSnapshotsTask(req *types.RemoveAllSnapshots_T
}
}
func (vm *VirtualMachine) ShutdownGuest(c *types.ShutdownGuest) soap.HasFault {
func (vm *VirtualMachine) ShutdownGuest(ctx *Context, c *types.ShutdownGuest) soap.HasFault {
r := &methods.ShutdownGuestBody{}
// should be poweron
if vm.Runtime.PowerState == types.VirtualMachinePowerStatePoweredOff {
@@ -1047,6 +1121,17 @@ func (vm *VirtualMachine) ShutdownGuest(c *types.ShutdownGuest) soap.HasFault {
vm.Runtime.PowerState = types.VirtualMachinePowerStatePoweredOff
vm.Summary.Runtime.PowerState = types.VirtualMachinePowerStatePoweredOff
event := vm.event()
ctx.postEvent(
&types.VmGuestShutdownEvent{VmEvent: event},
&types.VmPoweredOffEvent{VmEvent: event},
)
Map.Update(vm, []types.PropertyChange{
{Name: "runtime.powerState", Val: types.VirtualMachinePowerStatePoweredOff},
{Name: "summary.runtime.powerState", Val: types.VirtualMachinePowerStatePoweredOff},
})
r.Res = new(types.ShutdownGuestResponse)
return r

32
vendor/github.com/vmware/govmomi/vapi/tags/BUILD generated vendored Normal file
View File

@@ -0,0 +1,32 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"categories.go",
"rest_client.go",
"tag_association.go",
"tags.go",
],
importmap = "k8s.io/kubernetes/vendor/github.com/vmware/govmomi/vapi/tags",
importpath = "github.com/vmware/govmomi/vapi/tags",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/vmware/govmomi/vim25/soap:go_default_library",
"//vendor/github.com/vmware/govmomi/vim25/types:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,226 @@
// Copyright 2017 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 tags
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
)
const (
CategoryURL = "/com/vmware/cis/tagging/category"
ErrAlreadyExists = "already_exists"
)
type CategoryCreateSpec struct {
CreateSpec CategoryCreate `json:"create_spec"`
}
type CategoryUpdateSpec struct {
UpdateSpec CategoryUpdate `json:"update_spec,omitempty"`
}
type CategoryCreate struct {
AssociableTypes []string `json:"associable_types"`
Cardinality string `json:"cardinality"`
Description string `json:"description"`
Name string `json:"name"`
}
type CategoryUpdate struct {
AssociableTypes []string `json:"associable_types,omitempty"`
Cardinality string `json:"cardinality,omitempty"`
Description string `json:"description,omitempty"`
Name string `json:"name,omitempty"`
}
type Category struct {
ID string `json:"id"`
Description string `json:"description"`
Name string `json:"name"`
Cardinality string `json:"cardinality"`
AssociableTypes []string `json:"associable_types"`
UsedBy []string `json:"used_by"`
}
type CategoryInfo struct {
Name string
CategoryID string
}
func (c *RestClient) CreateCategoryIfNotExist(ctx context.Context, name string, description string, categoryType string, multiValue bool) (*string, error) {
categories, err := c.GetCategoriesByName(ctx, name)
if err != nil {
return nil, err
}
if categories == nil {
var multiValueStr string
if multiValue {
multiValueStr = "MULTIPLE"
} else {
multiValueStr = "SINGLE"
}
categoryCreate := CategoryCreate{[]string{categoryType}, multiValueStr, description, name}
spec := CategoryCreateSpec{categoryCreate}
id, err := c.CreateCategory(ctx, &spec)
if err != nil {
// in case there are two docker daemon try to create inventory category, query the category once again
if strings.Contains(err.Error(), "ErrAlreadyExists") {
if categories, err = c.GetCategoriesByName(ctx, name); err != nil {
return nil, fmt.Errorf("failed to get inventory category for %s", err)
}
} else {
return nil, fmt.Errorf("failed to create inventory category for %s", err)
}
} else {
return id, nil
}
}
if categories != nil {
return &categories[0].ID, nil
}
// should not happen
return nil, fmt.Errorf("failed to create inventory for it's existed, but could not query back. Please check system")
}
func (c *RestClient) CreateCategory(ctx context.Context, spec *CategoryCreateSpec) (*string, error) {
stream, _, status, err := c.call(ctx, http.MethodPost, CategoryURL, spec, nil)
if status != http.StatusOK || err != nil {
return nil, fmt.Errorf("create category failed with status code: %d, error message: %s", status, err)
}
type RespValue struct {
Value string
}
var pID RespValue
if err := json.NewDecoder(stream).Decode(&pID); err != nil {
return nil, fmt.Errorf("decode response body failed for: %s", err)
}
return &(pID.Value), nil
}
func (c *RestClient) GetCategory(ctx context.Context, id string) (*Category, error) {
stream, _, status, err := c.call(ctx, http.MethodGet, fmt.Sprintf("%s/id:%s", CategoryURL, id), nil, nil)
if status != http.StatusOK || err != nil {
return nil, fmt.Errorf("get category failed with status code: %d, error message: %s", status, err)
}
type RespValue struct {
Value Category
}
var pCategory RespValue
if err := json.NewDecoder(stream).Decode(&pCategory); err != nil {
return nil, fmt.Errorf("decode response body failed for: %s", err)
}
return &(pCategory.Value), nil
}
func (c *RestClient) UpdateCategory(ctx context.Context, id string, spec *CategoryUpdateSpec) error {
_, _, status, err := c.call(ctx, http.MethodPatch, fmt.Sprintf("%s/id:%s", CategoryURL, id), spec, nil)
if status != http.StatusOK || err != nil {
return fmt.Errorf("update category failed with status code: %d, error message: %s", status, err)
}
return nil
}
func (c *RestClient) DeleteCategory(ctx context.Context, id string) error {
_, _, status, err := c.call(ctx, http.MethodDelete, fmt.Sprintf("%s/id:%s", CategoryURL, id), nil, nil)
if status != http.StatusOK || err != nil {
return fmt.Errorf("delete category failed with status code: %d, error message: %s", status, err)
}
return nil
}
func (c *RestClient) ListCategories(ctx context.Context) ([]string, error) {
stream, _, status, err := c.call(ctx, http.MethodGet, CategoryURL, nil, nil)
if status != http.StatusOK || err != nil {
return nil, fmt.Errorf("get categories failed with status code: %d, error message: %s", status, err)
}
type Categories struct {
Value []string
}
var pCategories Categories
if err := json.NewDecoder(stream).Decode(&pCategories); err != nil {
return nil, fmt.Errorf("decode response body failed for: %s", err)
}
return pCategories.Value, nil
}
func (c *RestClient) ListCategoriesByName(ctx context.Context) ([]CategoryInfo, error) {
categoryIds, err := c.ListCategories(ctx)
if err != nil {
return nil, fmt.Errorf("get category failed for: %s", err)
}
var categoryInfoSlice []CategoryInfo
for _, cID := range categoryIds {
category, err := c.GetCategory(ctx, cID)
if err != nil {
return nil, fmt.Errorf("get category %s failed for %s", cID, err)
}
categoryCreate := &CategoryInfo{Name: category.Name, CategoryID: category.ID}
categoryInfoSlice = append(categoryInfoSlice, *categoryCreate)
}
return categoryInfoSlice, nil
}
func (c *RestClient) GetCategoriesByName(ctx context.Context, name string) ([]Category, error) {
categoryIds, err := c.ListCategories(ctx)
if err != nil {
return nil, fmt.Errorf("get category failed for: %s", err)
}
var categories []Category
for _, cID := range categoryIds {
category, err := c.GetCategory(ctx, cID)
if err != nil {
return nil, fmt.Errorf("get category %s failed for %s", cID, err)
}
if category.Name == name {
categories = append(categories, *category)
}
}
return categories, nil
}

View File

@@ -0,0 +1,272 @@
// Copyright 2017 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 tags
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"sync"
"github.com/vmware/govmomi/vim25/soap"
)
const (
RestPrefix = "/rest"
loginURL = "/com/vmware/cis/session"
sessionIDCookieName = "vmware-api-session-id"
)
type RestClient struct {
mu sync.Mutex
host string
scheme string
endpoint *url.URL
user *url.Userinfo
HTTP *http.Client
cookies []*http.Cookie
}
func NewClient(u *url.URL, insecure bool, thumbprint string) *RestClient {
endpoint := &url.URL{}
*endpoint = *u
endpoint.Path = RestPrefix
// Ignore "#" anchor
endpoint.Fragment = ""
sc := soap.NewClient(endpoint, insecure)
if thumbprint != "" {
sc.SetThumbprint(endpoint.Host, thumbprint)
}
user := endpoint.User
endpoint.User = nil
return &RestClient{
endpoint: endpoint,
user: user,
host: endpoint.Host,
scheme: endpoint.Scheme,
HTTP: &sc.Client,
}
}
// NewClientWithSessionID creates a new REST client with a supplied session ID
// to re-connect to existing sessions.
//
// Note that the session is not checked for validity - to check for a valid
// session after creating the client, use the Valid method. If the session is
// no longer valid and the session needs to be re-saved, Login should be called
// again before calling SessionID to extract the new session ID. Clients
// created with this function function work in the exact same way as clients
// created with NewClient, including supporting re-login on invalid sessions on
// all SDK calls.
func NewClientWithSessionID(u *url.URL, insecure bool, thumbprint string, sessionID string) *RestClient {
c := NewClient(u, insecure, thumbprint)
c.SetSessionID(sessionID)
return c
}
func (c *RestClient) encodeData(data interface{}) (*bytes.Buffer, error) {
params := bytes.NewBuffer(nil)
if data != nil {
if err := json.NewEncoder(params).Encode(data); err != nil {
return nil, err
}
}
return params, nil
}
func (c *RestClient) call(ctx context.Context, method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
// Logger.Debugf("%s: %s, headers: %+v", method, path, headers)
params, err := c.encodeData(data)
if err != nil {
return nil, nil, -1, err
}
if data != nil {
if headers == nil {
headers = make(map[string][]string)
}
headers["Content-Type"] = []string{"application/json"}
}
body, hdr, statusCode, err := c.clientRequest(ctx, method, path, params, headers)
if statusCode == http.StatusUnauthorized && strings.Contains(err.Error(), "This method requires authentication") {
c.Login(ctx)
return c.clientRequest(ctx, method, path, params, headers)
}
return body, hdr, statusCode, err
}
func (c *RestClient) clientRequest(ctx context.Context, method, path string, in io.Reader, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
expectedPayload := (method == http.MethodPost || method == http.MethodPut)
if expectedPayload && in == nil {
in = bytes.NewReader([]byte{})
}
req, err := c.newRequest(method, path, in)
if err != nil {
return nil, nil, -1, err
}
req = req.WithContext(ctx)
c.mu.Lock()
if c.cookies != nil {
req.AddCookie(c.cookies[0])
}
c.mu.Unlock()
if headers != nil {
for k, v := range headers {
req.Header[k] = v
}
}
if expectedPayload && req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "application/json")
}
req.Header.Set("Accept", "application/json")
resp, err := c.HTTP.Do(req)
return c.handleResponse(resp, err)
}
func (c *RestClient) handleResponse(resp *http.Response, err error) (io.ReadCloser, http.Header, int, error) {
statusCode := -1
if resp != nil {
statusCode = resp.StatusCode
}
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
return nil, nil, statusCode, err
}
return nil, nil, statusCode, err
}
if statusCode < http.StatusOK || statusCode >= http.StatusBadRequest {
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, nil, statusCode, err
}
if len(body) == 0 {
return nil, nil, statusCode, err
}
return nil, nil, statusCode, fmt.Errorf("error response: %s", bytes.TrimSpace(body))
}
return resp.Body, resp.Header, statusCode, nil
}
func (c *RestClient) Login(ctx context.Context) error {
request, err := c.newRequest(http.MethodPost, loginURL, nil)
if err != nil {
return err
}
if c.user != nil {
password, _ := c.user.Password()
request.SetBasicAuth(c.user.Username(), password)
}
resp, err := c.HTTP.Do(request)
if err != nil {
return err
}
if resp == nil {
return err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return err
}
c.cookies = resp.Cookies()
return nil
}
func (c *RestClient) Logout(ctx context.Context) error {
_, _, status, err := c.call(ctx, http.MethodDelete, loginURL, nil, nil)
if status != http.StatusOK || err != nil {
return err
}
c.SetSessionID("")
return nil
}
func (c *RestClient) newRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
return http.NewRequest(method, c.endpoint.String()+urlStr, body)
}
// SessionID returns the current session ID of the REST client. An empty string
// means there was no session cookie currently loaded.
func (c *RestClient) SessionID() string {
for _, cookie := range c.cookies {
if cookie.Name == sessionIDCookieName {
return cookie.Value
}
}
return ""
}
// SetSessionID sets the session cookie with the supplied session ID.
//
// This does not necessarily mean the session is valid. The session should be
// checked with Valid before proceeding, and logged back in if it has expired.
//
// This function will overwrite any existing session.
func (c *RestClient) SetSessionID(sessionID string) {
idx := -1
for i, cookie := range c.cookies {
if cookie.Name == sessionIDCookieName {
idx = i
}
}
sessionCookie := &http.Cookie{
Name: sessionIDCookieName,
Value: sessionID,
Path: RestPrefix,
}
if idx > -1 {
c.cookies[idx] = sessionCookie
} else {
c.cookies = append(c.cookies, sessionCookie)
}
}
// Valid checks to see if the session cookies in a REST client are still valid.
// This should be used when restoring a session to determine if a new login is
// necessary.
func (c *RestClient) Valid(ctx context.Context) bool {
_, _, statusCode, err := c.clientRequest(ctx, http.MethodPost, loginURL+"?~action=get", nil, nil)
if err != nil {
return false
}
if statusCode == http.StatusOK {
return true
}
return false
}

139
vendor/github.com/vmware/govmomi/vapi/tags/tag_association.go generated vendored Executable file
View File

@@ -0,0 +1,139 @@
// Copyright 2017 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 tags
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/vmware/govmomi/vim25/types"
)
const (
TagAssociationURL = "/com/vmware/cis/tagging/tag-association"
)
type AssociatedObject struct {
ID string `json:"id"`
Type string `json:"type"`
}
type TagAssociationSpec struct {
ObjectID *AssociatedObject `json:"object_id,omitempty"`
TagID *string `json:"tag_id,omitempty"`
}
type AttachedTagsInfo struct {
Name string
TagID string
}
func (c *RestClient) getAssociatedObject(ref *types.ManagedObjectReference) *AssociatedObject {
if ref == nil {
return nil
}
object := AssociatedObject{
ID: ref.Value,
Type: ref.Type,
}
return &object
}
func (c *RestClient) getAssociationSpec(tagID *string, ref *types.ManagedObjectReference) *TagAssociationSpec {
object := c.getAssociatedObject(ref)
spec := TagAssociationSpec{
TagID: tagID,
ObjectID: object,
}
return &spec
}
func (c *RestClient) AttachTagToObject(ctx context.Context, tagID string, ref *types.ManagedObjectReference) error {
spec := c.getAssociationSpec(&tagID, ref)
_, _, status, err := c.call(ctx, http.MethodPost, fmt.Sprintf("%s?~action=attach", TagAssociationURL), *spec, nil)
if status != http.StatusOK || err != nil {
return fmt.Errorf("attach tag failed with status code: %d, error message: %s", status, err)
}
return nil
}
func (c *RestClient) DetachTagFromObject(ctx context.Context, tagID string, ref *types.ManagedObjectReference) error {
spec := c.getAssociationSpec(&tagID, ref)
_, _, status, err := c.call(ctx, http.MethodPost, fmt.Sprintf("%s?~action=detach", TagAssociationURL), *spec, nil)
if status != http.StatusOK || err != nil {
return fmt.Errorf("detach tag failed with status code: %d, error message: %s", status, err)
}
return nil
}
func (c *RestClient) ListAttachedTags(ctx context.Context, ref *types.ManagedObjectReference) ([]string, error) {
spec := c.getAssociationSpec(nil, ref)
stream, _, status, err := c.call(ctx, http.MethodPost, fmt.Sprintf("%s?~action=list-attached-tags", TagAssociationURL), *spec, nil)
if status != http.StatusOK || err != nil {
return nil, fmt.Errorf("detach tag failed with status code: %d, error message: %s", status, err)
}
type RespValue struct {
Value []string
}
var pTag RespValue
if err := json.NewDecoder(stream).Decode(&pTag); err != nil {
return nil, fmt.Errorf("decode response body failed for: %s", err)
}
return pTag.Value, nil
}
func (c *RestClient) ListAttachedTagsByName(ctx context.Context, ref *types.ManagedObjectReference) ([]AttachedTagsInfo, error) {
tagIds, err := c.ListAttachedTags(ctx, ref)
if err != nil {
return nil, fmt.Errorf("get attached tag failed for: %s", err)
}
var attachedTagsInfoSlice []AttachedTagsInfo
for _, cID := range tagIds {
tag, err := c.GetTag(ctx, cID)
if err != nil {
return nil, fmt.Errorf("get tag %s failed for %s", cID, err)
}
attachedTagsCreate := &AttachedTagsInfo{Name: tag.Name, TagID: tag.ID}
attachedTagsInfoSlice = append(attachedTagsInfoSlice, *attachedTagsCreate)
}
return attachedTagsInfoSlice, nil
}
func (c *RestClient) ListAttachedObjects(ctx context.Context, tagID string) ([]AssociatedObject, error) {
spec := c.getAssociationSpec(&tagID, nil)
stream, _, status, err := c.call(ctx, http.MethodPost, fmt.Sprintf("%s?~action=list-attached-objects", TagAssociationURL), *spec, nil)
if status != http.StatusOK || err != nil {
return nil, fmt.Errorf("list object failed with status code: %d, error message: %s", status, err)
}
type RespValue struct {
Value []AssociatedObject
}
var pTag RespValue
if err := json.NewDecoder(stream).Decode(&pTag); err != nil {
return nil, fmt.Errorf("decode response body failed for: %s", err)
}
return pTag.Value, nil
}

252
vendor/github.com/vmware/govmomi/vapi/tags/tags.go generated vendored Normal file
View File

@@ -0,0 +1,252 @@
// Copyright 2017 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 tags
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
const (
TagURL = "/com/vmware/cis/tagging/tag"
)
type TagCreateSpec struct {
CreateSpec TagCreate `json:"create_spec"`
}
type TagCreate struct {
CategoryID string `json:"category_id"`
Description string `json:"description"`
Name string `json:"name"`
}
type TagUpdateSpec struct {
UpdateSpec TagUpdate `json:"update_spec,omitempty"`
}
type TagUpdate struct {
Description string `json:"description,omitempty"`
Name string `json:"name,omitempty"`
}
type Tag struct {
ID string `json:"id"`
Description string `json:"description"`
Name string `json:"name"`
CategoryID string `json:"category_id"`
UsedBy []string `json:"used_by"`
}
func (c *RestClient) CreateTagIfNotExist(ctx context.Context, name string, description string, categoryID string) (*string, error) {
tagCreate := TagCreate{categoryID, description, name}
spec := TagCreateSpec{tagCreate}
id, err := c.CreateTag(ctx, &spec)
if err == nil {
return id, nil
}
// if already exists, query back
if strings.Contains(err.Error(), ErrAlreadyExists) {
tagObjs, err := c.GetTagByNameForCategory(ctx, name, categoryID)
if err != nil {
return nil, err
}
if tagObjs != nil {
return &tagObjs[0].ID, nil
}
// should not happen
return nil, fmt.Errorf("failed to create tag for it's existed, but could not query back. Please check system")
}
return nil, fmt.Errorf("created tag failed for %s", err)
}
func (c *RestClient) DeleteTagIfNoObjectAttached(ctx context.Context, id string) error {
objs, err := c.ListAttachedObjects(ctx, id)
if err != nil {
return err
}
if len(objs) > 0 {
return fmt.Errorf("tag %s related objects is not empty, do not delete it", id)
}
return c.DeleteTag(ctx, id)
}
func (c *RestClient) CreateTag(ctx context.Context, spec *TagCreateSpec) (*string, error) {
stream, _, status, err := c.call(ctx, http.MethodPost, TagURL, spec, nil)
if status != http.StatusOK || err != nil {
return nil, fmt.Errorf("create tag failed with status code: %d, error message: %s", status, err)
}
type RespValue struct {
Value string
}
var pID RespValue
if err := json.NewDecoder(stream).Decode(&pID); err != nil {
return nil, fmt.Errorf("decode response body failed for: %s", err)
}
return &pID.Value, nil
}
func (c *RestClient) GetTag(ctx context.Context, id string) (*Tag, error) {
stream, _, status, err := c.call(ctx, http.MethodGet, fmt.Sprintf("%s/id:%s", TagURL, id), nil, nil)
if status != http.StatusOK || err != nil {
return nil, fmt.Errorf("get tag failed with status code: %d, error message: %s", status, err)
}
type RespValue struct {
Value Tag
}
var pTag RespValue
if err := json.NewDecoder(stream).Decode(&pTag); err != nil {
return nil, fmt.Errorf("decode response body failed for: %s", err)
}
return &(pTag.Value), nil
}
func (c *RestClient) UpdateTag(ctx context.Context, id string, spec *TagUpdateSpec) error {
_, _, status, err := c.call(ctx, http.MethodPatch, fmt.Sprintf("%s/id:%s", TagURL, id), spec, nil)
if status != http.StatusOK || err != nil {
return fmt.Errorf("update tag failed with status code: %d, error message: %s", status, err)
}
return nil
}
func (c *RestClient) DeleteTag(ctx context.Context, id string) error {
_, _, status, err := c.call(ctx, http.MethodDelete, fmt.Sprintf("%s/id:%s", TagURL, id), nil, nil)
if status != http.StatusOK || err != nil {
return fmt.Errorf("delete tag failed with status code: %d, error message: %s", status, err)
}
return nil
}
func (c *RestClient) ListTags(ctx context.Context) ([]string, error) {
stream, _, status, err := c.call(ctx, http.MethodGet, TagURL, nil, nil)
if status != http.StatusOK || err != nil {
return nil, fmt.Errorf("get tags failed with status code: %d, error message: %s", status, err)
}
return c.handleTagIDList(stream)
}
type TagsInfo struct {
Name string
TagID string
}
func (c *RestClient) ListTagsByName(ctx context.Context) ([]TagsInfo, error) {
tagIds, err := c.ListTags(ctx)
if err != nil {
return nil, fmt.Errorf("get tags failed for: %s", err)
}
var tagsInfoSlice []TagsInfo
for _, cID := range tagIds {
tag, err := c.GetTag(ctx, cID)
if err != nil {
return nil, fmt.Errorf("get category %s failed for %s", cID, err)
}
tagsCreate := &TagsInfo{Name: tag.Name, TagID: tag.ID}
tagsInfoSlice = append(tagsInfoSlice, *tagsCreate)
}
return tagsInfoSlice, nil
}
func (c *RestClient) ListTagsForCategory(ctx context.Context, id string) ([]string, error) {
type PostCategory struct {
ID string `json:"category_id"`
}
spec := PostCategory{id}
stream, _, status, err := c.call(ctx, http.MethodPost, fmt.Sprintf("%s/id:%s?~action=list-tags-for-category", TagURL, id), spec, nil)
if status != http.StatusOK || err != nil {
return nil, fmt.Errorf("list tags for category failed with status code: %d, error message: %s", status, err)
}
return c.handleTagIDList(stream)
}
func (c *RestClient) ListTagsInfoForCategory(ctx context.Context, id string) ([]TagsInfo, error) {
type PostCategory struct {
ID string `json:"category_id"`
}
spec := PostCategory{id}
stream, _, status, err := c.call(ctx, http.MethodPost, fmt.Sprintf("%s/id:%s?~action=list-tags-for-category", TagURL, id), spec, nil)
if status != http.StatusOK || err != nil {
return nil, fmt.Errorf("list tags for category failed with status code: %d, error message: %s", status, err)
}
var tagsInfoSlice []TagsInfo
tmp, err := c.handleTagIDList(stream)
for _, item := range tmp {
tag, err := c.GetTag(ctx, item)
if err != nil {
return nil, fmt.Errorf("get category %s failed for %s", item, err)
}
tagsCreate := &TagsInfo{Name: tag.Name, TagID: tag.ID}
tagsInfoSlice = append(tagsInfoSlice, *tagsCreate)
}
return tagsInfoSlice, nil
}
func (c *RestClient) handleTagIDList(stream io.ReadCloser) ([]string, error) {
type Tags struct {
Value []string
}
var pTags Tags
if err := json.NewDecoder(stream).Decode(&pTags); err != nil {
return nil, fmt.Errorf("decode response body failed for: %s", err)
}
return pTags.Value, nil
}
// Get tag through tag name and category id
func (c *RestClient) GetTagByNameForCategory(ctx context.Context, name string, id string) ([]Tag, error) {
tagIds, err := c.ListTagsForCategory(ctx, id)
if err != nil {
return nil, fmt.Errorf("get tag failed for %s", err)
}
var tags []Tag
for _, tID := range tagIds {
tag, err := c.GetTag(ctx, tID)
if err != nil {
return nil, fmt.Errorf("get tag %s failed for %s", tID, err)
}
if tag.Name == name {
tags = append(tags, *tag)
}
}
return tags, nil
}

View File

@@ -28,7 +28,7 @@ import (
const (
Namespace = "vim25"
Version = "6.5"
Version = "6.7"
Path = "/sdk"
)

View File

@@ -65,6 +65,22 @@ func ObjectContentToType(o types.ObjectContent) (interface{}, error) {
return v.Elem().Interface(), nil
}
// ApplyPropertyChange converts the response of a call to WaitForUpdates
// and applies it to the given managed object.
func ApplyPropertyChange(obj Reference, changes []types.PropertyChange) {
t := typeInfoForType(obj.Reference().Type)
v := reflect.ValueOf(obj)
for _, p := range changes {
rv, ok := t.props[p.Name]
if !ok {
continue
}
assignValue(v, rv, reflect.ValueOf(p.Val))
}
}
// LoadRetrievePropertiesResponse converts the response of a call to
// RetrieveProperties to one or more managed objects.
func LoadRetrievePropertiesResponse(res *types.RetrievePropertiesResponse, dst interface{}) error {

View File

@@ -155,6 +155,8 @@ func (t *typeInfo) build(typ reflect.Type, fn string, fi []int) {
}
}
var nilValue reflect.Value
// assignValue assignes a value 'pv' to the struct pointed to by 'val', given a
// slice of field indices. It recurses into the struct until it finds the field
// specified by the indices. It creates new values for pointer types where
@@ -172,6 +174,11 @@ func assignValue(val reflect.Value, fi []int, pv reflect.Value) {
rv := val.Field(fi[0])
fi = fi[1:]
if len(fi) == 0 {
if pv == nilValue {
pv = reflect.Zero(rv.Type())
rv.Set(pv)
return
}
rt := rv.Type()
pt := pv.Type()
@@ -182,6 +189,24 @@ func assignValue(val reflect.Value, fi []int, pv reflect.Value) {
rt = rv.Type()
}
// If the target type is a slice, but the source is not, deference any ArrayOfXYZ type
if rt.Kind() == reflect.Slice && pt.Kind() != reflect.Slice {
if pt.Kind() == reflect.Ptr {
pv = pv.Elem()
pt = pt.Elem()
}
m := arrayOfRegexp.FindStringSubmatch(pt.Name())
if len(m) > 0 {
pv = pv.FieldByName(m[1]) // ArrayOfXYZ type has single field named XYZ
pt = pv.Type()
if !pv.IsValid() {
panic(fmt.Sprintf("expected %s type to have field %s", m[0], m[1]))
}
}
}
// If type is an interface, check if pv implements it.
if rt.Kind() == reflect.Interface && !pt.Implements(rt) {
// Check if pointer to pv implements it.
@@ -200,7 +225,7 @@ func assignValue(val reflect.Value, fi []int, pv reflect.Value) {
} 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()))
panic(fmt.Sprintf("cannot assign %q (%s) to %q (%s)", rt.Name(), rt.Kind(), pt.Name(), pt.Kind()))
}
return
@@ -211,23 +236,6 @@ func assignValue(val reflect.Value, fi []int, pv reflect.Value) {
var arrayOfRegexp = regexp.MustCompile("ArrayOf(.*)$")
func anyTypeToValue(t interface{}) reflect.Value {
rt := reflect.TypeOf(t)
rv := reflect.ValueOf(t)
// Dereference if ArrayOfXYZ type
m := arrayOfRegexp.FindStringSubmatch(rt.Name())
if len(m) > 0 {
// ArrayOfXYZ type has single field named XYZ
rv = rv.FieldByName(m[1])
if !rv.IsValid() {
panic(fmt.Sprintf("expected %s type to have field %s", m[0], m[1]))
}
}
return rv
}
// LoadObjectFromContent loads properties from the 'PropSet' field in the
// specified ObjectContent value into the value it represents, which is
// returned as a reflect.Value.
@@ -240,7 +248,7 @@ func (t *typeInfo) LoadFromObjectContent(o types.ObjectContent) (reflect.Value,
if !ok {
continue
}
assignValue(v, rv, anyTypeToValue(p.Val))
assignValue(v, rv, reflect.ValueOf(p.Val))
}
return v, nil

View File

@@ -77,6 +77,17 @@ type Client struct {
var schemeMatch = regexp.MustCompile(`^\w+://`)
type errInvalidCACertificate struct {
File string
}
func (e errInvalidCACertificate) Error() string {
return fmt.Sprintf(
"invalid certificate '%s', cannot be used as a trusted CA certificate",
e.File,
)
}
// ParseURL is wrapper around url.Parse, where Scheme defaults to "https" and Path defaults to "/sdk"
func ParseURL(s string) (*url.URL, error) {
var err error
@@ -200,7 +211,11 @@ func (c *Client) SetRootCAs(file string) error {
return err
}
pool.AppendCertsFromPEM(pem)
if ok := pool.AppendCertsFromPEM(pem); !ok {
return errInvalidCACertificate{
File: name,
}
}
}
c.t.TLSClientConfig.RootCAs = pool

View File

@@ -39,7 +39,11 @@ func (s soapFaultError) Error() string {
msg := s.fault.String
if msg == "" {
msg = reflect.TypeOf(s.fault.Detail.Fault).Name()
if s.fault.Detail.Fault == nil {
msg = "unknown fault"
} else {
msg = reflect.TypeOf(s.fault.Detail.Fault).Name()
}
}
return fmt.Sprintf("%s: %s", s.fault.Code, msg)