Unverified Commit 9d260ff1 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #66795 from jiatongw/zones_vendor

Automatic merge from submit-queue (batch tested with PRs 67052, 67094, 66795). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Add zones support for vSphere cloud provider(in-tree) **What this PR does / why we need it**: This PR added zones(built-in node labels) support for vSphere cloud provider(in-tree). More details can be found in the issue as below. **Which issue(s) this PR fixes** : Partially fixes phase 1 of issue #64021 **Special notes for your reviewer**: **Release note**: ```release-note NONE ```
parents e5e04767 af5084ea
...@@ -29,6 +29,7 @@ go_library( ...@@ -29,6 +29,7 @@ go_library(
"//staging/src/k8s.io/client-go/listers/core/v1:go_default_library", "//staging/src/k8s.io/client-go/listers/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/tools/cache:go_default_library", "//staging/src/k8s.io/client-go/tools/cache:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/vmware/govmomi/vapi/tags:go_default_library",
"//vendor/github.com/vmware/govmomi/vim25:go_default_library", "//vendor/github.com/vmware/govmomi/vim25:go_default_library",
"//vendor/github.com/vmware/govmomi/vim25/mo:go_default_library", "//vendor/github.com/vmware/govmomi/vim25/mo:go_default_library",
"//vendor/gopkg.in/gcfg.v1:go_default_library", "//vendor/gopkg.in/gcfg.v1:go_default_library",
......
...@@ -82,6 +82,21 @@ func (dc *Datacenter) GetVMByUUID(ctx context.Context, vmUUID string) (*VirtualM ...@@ -82,6 +82,21 @@ func (dc *Datacenter) GetVMByUUID(ctx context.Context, vmUUID string) (*VirtualM
return &virtualMachine, nil return &virtualMachine, nil
} }
// GetHostByVMUUID gets the host object from the given vmUUID
func (dc *Datacenter) GetHostByVMUUID(ctx context.Context, vmUUID string) (*types.ManagedObjectReference, error) {
virtualMachine, err := dc.GetVMByUUID(ctx, vmUUID)
var vmMo mo.VirtualMachine
pc := property.DefaultCollector(virtualMachine.Client())
err = pc.RetrieveOne(ctx, virtualMachine.Reference(), []string{"summary.runtime.host"}, &vmMo)
if err != nil {
glog.Errorf("Failed to retrive VM runtime host, err: %v", err)
return nil, err
}
host := vmMo.Summary.Runtime.Host
glog.Infof("%s host is %s", virtualMachine.Reference(), host)
return host, nil
}
// GetVMByPath gets the VM object from the given vmPath // GetVMByPath gets the VM object from the given vmPath
// vmPath should be the full path to VM and not just the name // vmPath should be the full path to VM and not just the name
func (dc *Datacenter) GetVMByPath(ctx context.Context, vmPath string) (*VirtualMachine, error) { func (dc *Datacenter) GetVMByPath(ctx context.Context, vmPath string) (*VirtualMachine, error) {
......
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"net/url"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
...@@ -33,6 +34,7 @@ import ( ...@@ -33,6 +34,7 @@ import (
"gopkg.in/gcfg.v1" "gopkg.in/gcfg.v1"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/vmware/govmomi/vapi/tags"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
k8stypes "k8s.io/apimachinery/pkg/types" k8stypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
...@@ -177,6 +179,12 @@ type VSphereConfig struct { ...@@ -177,6 +179,12 @@ type VSphereConfig struct {
DefaultDatastore string `gcfg:"default-datastore"` DefaultDatastore string `gcfg:"default-datastore"`
ResourcePoolPath string `gcfg:"resourcepool-path"` ResourcePoolPath string `gcfg:"resourcepool-path"`
} }
// Tag categories and tags which correspond to "built-in node labels: zones and region"
Labels struct {
Zone string `gcfg:"zone"`
Region string `gcfg:"region"`
}
} }
type Volumes interface { type Volumes interface {
...@@ -808,8 +816,11 @@ func (vs *VSphere) LoadBalancer() (cloudprovider.LoadBalancer, bool) { ...@@ -808,8 +816,11 @@ func (vs *VSphere) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
// Zones returns an implementation of Zones for vSphere. // Zones returns an implementation of Zones for vSphere.
func (vs *VSphere) Zones() (cloudprovider.Zones, bool) { func (vs *VSphere) Zones() (cloudprovider.Zones, bool) {
glog.V(1).Info("The vSphere cloud provider does not support zones") if vs.cfg == nil {
return nil, false glog.V(1).Info("The vSphere cloud provider does not support zones")
return nil, false
}
return vs, true
} }
// Routes returns a false since the interface is not supported for vSphere. // Routes returns a false since the interface is not supported for vSphere.
...@@ -1306,3 +1317,99 @@ func (vs *VSphere) NodeManager() (nodeManager *NodeManager) { ...@@ -1306,3 +1317,99 @@ func (vs *VSphere) NodeManager() (nodeManager *NodeManager) {
} }
return vs.nodeManager return vs.nodeManager
} }
func withTagsClient(ctx context.Context, connection *vclib.VSphereConnection, f func(c *tags.RestClient) error) error {
vsURL := connection.Client.URL()
vsURL.User = url.UserPassword(connection.Username, connection.Password)
c := tags.NewClient(vsURL, connection.Insecure, "")
if err := c.Login(ctx); err != nil {
return err
}
defer c.Logout(ctx)
return f(c)
}
// GetZone implements Zones.GetZone
func (vs *VSphere) GetZone(ctx context.Context) (cloudprovider.Zone, error) {
nodeName, err := vs.CurrentNodeName(ctx, vs.hostName)
if err != nil {
glog.Errorf("Cannot get node name.")
return cloudprovider.Zone{}, err
}
zone := cloudprovider.Zone{}
vsi, err := vs.getVSphereInstanceForServer(vs.cfg.Workspace.VCenterIP, ctx)
if err != nil {
glog.Errorf("Cannot connent to vsphere. Get zone for node %s error", nodeName)
return cloudprovider.Zone{}, err
}
dc, err := vclib.GetDatacenter(ctx, vsi.conn, vs.cfg.Workspace.Datacenter)
if err != nil {
glog.Errorf("Cannot connent to datacenter. Get zone for node %s error", nodeName)
return cloudprovider.Zone{}, err
}
vmHost, err := dc.GetHostByVMUUID(ctx, vs.vmUUID)
if err != nil {
glog.Errorf("Cannot find VM runtime host. Get zone for node %s error", nodeName)
return cloudprovider.Zone{}, err
}
client := vsi.conn
err = withTagsClient(ctx, client, func(client *tags.RestClient) error {
tags, err := client.ListAttachedTags(ctx, vmHost)
if err != nil {
glog.Errorf("Cannot list attached tags. Get zone for node %s error", nodeName)
return err
}
for _, value := range tags {
tag, err := client.GetTag(ctx, value)
if err != nil {
glog.Errorf("Get tag %s error", value)
return err
}
category, err := client.GetCategory(ctx, tag.CategoryID)
if err != nil {
glog.Errorf("Get category %s error", value)
return err
}
switch {
case category.Name == vs.cfg.Labels.Zone:
zone.FailureDomain = tag.Name
case category.Name == vs.cfg.Labels.Region:
zone.Region = tag.Name
default:
zone.FailureDomain = ""
zone.Region = ""
}
}
switch {
case zone.Region == "":
if vs.cfg.Labels.Zone != "" {
return fmt.Errorf("The zone in vSphere configuration file not match for node %s ", nodeName)
}
glog.Infof("No zones support for node %s error", nodeName)
return nil
case zone.FailureDomain == "":
if vs.cfg.Labels.Region != "" {
return fmt.Errorf("The zone in vSphere configuration file not match for node %s ", nodeName)
}
glog.Infof("No zones support for node %s error", nodeName)
return nil
}
return nil
})
if err != nil {
glog.Errorf("Get zone for node %s error", nodeName)
return cloudprovider.Zone{}, err
}
return zone, nil
}
func (vs *VSphere) GetZoneByNodeName(ctx context.Context, nodeName k8stypes.NodeName) (cloudprovider.Zone, error) {
return cloudprovider.Zone{}, cloudprovider.NotImplemented
}
func (vs *VSphere) GetZoneByProviderID(ctx context.Context, providerID string) (cloudprovider.Zone, error) {
return cloudprovider.Zone{}, cloudprovider.NotImplemented
}
...@@ -315,17 +315,21 @@ func TestVSphereLoginWithCaCert(t *testing.T) { ...@@ -315,17 +315,21 @@ func TestVSphereLoginWithCaCert(t *testing.T) {
} }
func TestZones(t *testing.T) { func TestZones(t *testing.T) {
cfg := VSphereConfig{}
cfg.Global.Datacenter = "myDatacenter"
// Create vSphere configuration object // Create vSphere configuration object
cfg, ok := configFromEnv()
vs := VSphere{ vs := VSphere{
cfg: &cfg, cfg: &cfg,
} }
if !ok {
_, ok := vs.Zones() t.Skipf("No config found in environment")
if ok { }
t.Fatalf("Zones() returned true") _, err := vs.GetZone(context.TODO())
if err != nil {
t.Fatalf("GetZone() failed: %s", err)
}
_, ok = vs.Zones()
if !ok {
t.Fatalf("Zones() returned false")
} }
} }
......
...@@ -7,6 +7,7 @@ Cédric Blomart <cblomart@gmail.com> cedric <cblomart@gmail.com> ...@@ -7,6 +7,7 @@ Cédric Blomart <cblomart@gmail.com> cedric <cblomart@gmail.com>
David Stark <dave@davidstark.name> <david.stark@bskyb.com> David Stark <dave@davidstark.name> <david.stark@bskyb.com>
Eric Gray <egray@vmware.com> <ericgray@users.noreply.github.com> Eric Gray <egray@vmware.com> <ericgray@users.noreply.github.com>
Eric Yutao <eric.yutao@gmail.com> eric <eric.yutao@gmail.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> Henrik Hodne <henrik@travis-ci.com> <henrik@hodne.io>
Jeremy Canady <jcanady@jackhenry.com> <jcanady@gmail.com> Jeremy Canady <jcanady@jackhenry.com> <jcanady@gmail.com>
Pieter Noordhuis <pnoordhuis@vmware.com> <pcnoordhuis@gmail.com> Pieter Noordhuis <pnoordhuis@vmware.com> <pcnoordhuis@gmail.com>
......
sudo: false sudo: required
language: go language: go
go: go:
- 1.8.x
- 1.9.x
- '1.10' - '1.10'
go_import_path: github.com/vmware/govmomi go_import_path: github.com/vmware/govmomi
...@@ -27,4 +25,4 @@ deploy: ...@@ -27,4 +25,4 @@ deploy:
on: on:
tags: true tags: true
condition: $TRAVIS_OS_NAME = linux condition: $TRAVIS_OS_NAME = linux
go: '1.10' go: '1.10'
\ No newline at end of file
...@@ -37,6 +37,7 @@ filegroup( ...@@ -37,6 +37,7 @@ filegroup(
"//vendor/github.com/vmware/govmomi/simulator:all-srcs", "//vendor/github.com/vmware/govmomi/simulator:all-srcs",
"//vendor/github.com/vmware/govmomi/sts:all-srcs", "//vendor/github.com/vmware/govmomi/sts:all-srcs",
"//vendor/github.com/vmware/govmomi/task: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", "//vendor/github.com/vmware/govmomi/vim25:all-srcs",
], ],
tags = ["automanaged"], tags = ["automanaged"],
......
# changelog # 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) ### 0.17.1 (2018-03-19)
* vcsim: add Destroy method for Folder and Datacenter types * vcsim: add Destroy method for Folder and Datacenter types
......
...@@ -6,9 +6,11 @@ ...@@ -6,9 +6,11 @@
Abhijeet Kasurde <akasurde@redhat.com> Abhijeet Kasurde <akasurde@redhat.com>
abrarshivani <abrarshivani@users.noreply.github.com> abrarshivani <abrarshivani@users.noreply.github.com>
Adam Shannon <adamkshannon@gmail.com> Adam Shannon <adamkshannon@gmail.com>
akutz <sakutz@gmail.com>
Alessandro Cortiana <alessandro.cortiana@gmail.com> Alessandro Cortiana <alessandro.cortiana@gmail.com>
Alex Bozhenko <alexbozhenko@fb.com> Alex Bozhenko <alexbozhenko@fb.com>
Alvaro Miranda <kikitux@gmail.com> Alvaro Miranda <kikitux@gmail.com>
amandahla <amanda.andrade@serpro.gov.br>
Amanda H. L. de Andrade <amanda.andrade@serpro.gov.br> Amanda H. L. de Andrade <amanda.andrade@serpro.gov.br>
Amit Bathla <abathla@.vmware.com> Amit Bathla <abathla@.vmware.com>
amit bezalel <amit.bezalel@hpe.com> amit bezalel <amit.bezalel@hpe.com>
...@@ -23,14 +25,15 @@ bastienbc <bastien.barbe.creuly@gmail.com> ...@@ -23,14 +25,15 @@ bastienbc <bastien.barbe.creuly@gmail.com>
Bob Killen <killen.bob@gmail.com> Bob Killen <killen.bob@gmail.com>
Brad Fitzpatrick <bradfitz@golang.org> Brad Fitzpatrick <bradfitz@golang.org>
Bruce Downs <bruceadowns@gmail.com> Bruce Downs <bruceadowns@gmail.com>
Cédric Blomart <cblomart@gmail.com>
Chris Marchesi <chrism@vancluevertech.com> Chris Marchesi <chrism@vancluevertech.com>
Christian Höltje <docwhat@gerf.org> Christian Höltje <docwhat@gerf.org>
Clint Greenwood <cgreenwood@vmware.com> Clint Greenwood <cgreenwood@vmware.com>
Cédric Blomart <cblomart@gmail.com>
Danny Lockard <danny.lockard@banno.com> Danny Lockard <danny.lockard@banno.com>
Dave Tucker <dave@dtucker.co.uk> Dave Tucker <dave@dtucker.co.uk>
David Stark <dave@davidstark.name>
Davide Agnello <dagnello@hp.com> Davide Agnello <dagnello@hp.com>
David Stark <dave@davidstark.name>
Deric Crago <deric.crago@gmail.com>
Doug MacEachern <dougm@vmware.com> Doug MacEachern <dougm@vmware.com>
Eloy Coto <eloy.coto@gmail.com> Eloy Coto <eloy.coto@gmail.com>
Eric Gray <egray@vmware.com> Eric Gray <egray@vmware.com>
...@@ -52,11 +55,14 @@ Jason Kincl <jkincl@gmail.com> ...@@ -52,11 +55,14 @@ Jason Kincl <jkincl@gmail.com>
Jeremy Canady <jcanady@jackhenry.com> Jeremy Canady <jcanady@jackhenry.com>
jeremy-clerc <jeremy@clerc.io> jeremy-clerc <jeremy@clerc.io>
João Pereira <joaodrp@gmail.com> João Pereira <joaodrp@gmail.com>
Jorge Sevilla <jorge.sevilla@rstor.io>
leslie-qiwa <leslie.qiwa@gmail.com>
Louie Jiang <jiangl@vmware.com> Louie Jiang <jiangl@vmware.com>
Marc Carmier <mcarmier@gmail.com> Marc Carmier <mcarmier@gmail.com>
Matthew Cosgrove <matthew.cosgrove@dell.com> Matthew Cosgrove <matthew.cosgrove@dell.com>
Mevan Samaratunga <mevansam@gmail.com> Mevan Samaratunga <mevansam@gmail.com>
Nicolas Lamirault <nicolas.lamirault@gmail.com> Nicolas Lamirault <nicolas.lamirault@gmail.com>
Omar Kohl <omarkohl@gmail.com>
Parham Alvani <parham.alvani@gmail.com> Parham Alvani <parham.alvani@gmail.com>
Pieter Noordhuis <pnoordhuis@vmware.com> Pieter Noordhuis <pnoordhuis@vmware.com>
runner.mei <runner.mei@gmail.com> runner.mei <runner.mei@gmail.com>
......
...@@ -18,7 +18,7 @@ install: ...@@ -18,7 +18,7 @@ install:
go install -v github.com/vmware/govmomi/vcsim go install -v github.com/vmware/govmomi/vcsim
go-test: 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 govc-test: install
(cd govc/test && ./vendor/github.com/sstephenson/bats/libexec/bats -t .) (cd govc/test && ./vendor/github.com/sstephenson/bats/libexec/bats -t .)
......
...@@ -15,9 +15,9 @@ In addition to the vSphere API client, this repository includes: ...@@ -15,9 +15,9 @@ In addition to the vSphere API client, this repository includes:
## Compatibility ## 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 ## Documentation
......
...@@ -68,3 +68,22 @@ func (c ClusterComputeResource) AddHost(ctx context.Context, spec types.HostConn ...@@ -68,3 +68,22 @@ func (c ClusterComputeResource) AddHost(ctx context.Context, spec types.HostConn
return NewTask(c.c, res.Returnval), nil 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
}
...@@ -209,3 +209,19 @@ func (m VirtualDiskManager) QueryVirtualDiskUuid(ctx context.Context, name strin ...@@ -209,3 +209,19 @@ func (m VirtualDiskManager) QueryVirtualDiskUuid(ctx context.Context, name strin
return res.Returnval, nil 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
}
...@@ -799,3 +799,16 @@ func (v VirtualMachine) UpgradeVM(ctx context.Context, version string) (*Task, e ...@@ -799,3 +799,16 @@ func (v VirtualMachine) UpgradeVM(ctx context.Context, version string) (*Task, e
return NewTask(v.c, res.Returnval), nil 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
}
...@@ -111,6 +111,12 @@ func (p *Collector) WaitForUpdates(ctx context.Context, v string) (*types.Update ...@@ -111,6 +111,12 @@ func (p *Collector) WaitForUpdates(ctx context.Context, v string) (*types.Update
return res.Returnval, nil 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) { func (p *Collector) RetrieveProperties(ctx context.Context, req types.RetrieveProperties) (*types.RetrievePropertiesResponse, error) {
req.This = p.Reference() req.This = p.Reference()
return methods.RetrieveProperties(ctx, p.roundTripper, &req) return methods.RetrieveProperties(ctx, p.roundTripper, &req)
......
...@@ -19,12 +19,14 @@ package property ...@@ -19,12 +19,14 @@ package property
import ( import (
"context" "context"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/types" "github.com/vmware/govmomi/vim25/types"
) )
// WaitFilter provides helpers to construct a types.CreateFilter for use with property.Wait // WaitFilter provides helpers to construct a types.CreateFilter for use with property.Wait
type WaitFilter struct { type WaitFilter struct {
types.CreateFilter types.CreateFilter
Options *types.WaitOptions
} }
// Add a new ObjectSpec and PropertySpec to the WaitFilter // Add a new ObjectSpec and PropertySpec to the WaitFilter
...@@ -75,6 +77,7 @@ func Wait(ctx context.Context, c *Collector, obj types.ManagedObjectReference, p ...@@ -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 // creates a new property collector and calls CreateFilter. A new property
// collector is required because filters can only be added, not removed. // 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 // The newly created collector is destroyed before this function returns (both
// in case of success or error). // in case of success or error).
// //
...@@ -85,7 +88,7 @@ func WaitForUpdates(ctx context.Context, c *Collector, filter *WaitFilter, f fun ...@@ -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 // 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()) defer p.Destroy(context.Background())
err = p.CreateFilter(ctx, filter.CreateFilter) err = p.CreateFilter(ctx, filter.CreateFilter)
...@@ -93,20 +96,33 @@ func WaitForUpdates(ctx context.Context, c *Collector, filter *WaitFilter, f fun ...@@ -93,20 +96,33 @@ func WaitForUpdates(ctx context.Context, c *Collector, filter *WaitFilter, f fun
return err return err
} }
for version := ""; ; { req := types.WaitForUpdatesEx{
res, err := p.WaitForUpdates(ctx, version) This: p.Reference(),
Options: filter.Options,
}
for {
res, err := methods.WaitForUpdatesEx(ctx, p.roundTripper, &req)
if err != nil { if err != nil {
if ctx.Err() == context.Canceled {
werr := p.CancelWaitForUpdates(context.Background())
return werr
}
return err return err
} }
// Retry if the result came back empty set := res.Returnval
if res == nil { if set == nil {
if req.Options != nil && req.Options.MaxWaitSeconds != nil {
return nil // WaitOptions.MaxWaitSeconds exceeded
}
// Retry if the result came back empty
continue continue
} }
version = res.Version req.Version = set.Version
for _, fs := range res.FilterSet { for _, fs := range set.FilterSet {
if f(fs.ObjectSet) { if f(fs.ObjectSet) {
return nil return nil
} }
......
...@@ -106,11 +106,10 @@ func (s *DistributedVirtualSwitch) ReconfigureDvsTask(req *types.ReconfigureDvs_ ...@@ -106,11 +106,10 @@ func (s *DistributedVirtualSwitch) ReconfigureDvsTask(req *types.ReconfigureDvs_
switch types.ConfigSpecOperation(member.Operation) { switch types.ConfigSpecOperation(member.Operation) {
case types.ConfigSpecOperationAdd: 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} return nil, &types.AlreadyExists{Name: host.Name}
} }
Map.AppendReference(host, &host.Network, s.Self)
Map.AppendReference(host, &host.Network, s.Portgroup...) Map.AppendReference(host, &host.Network, s.Portgroup...)
s.Summary.HostMember = append(s.Summary.HostMember, member.Host) s.Summary.HostMember = append(s.Summary.HostMember, member.Host)
...@@ -129,8 +128,7 @@ func (s *DistributedVirtualSwitch) ReconfigureDvsTask(req *types.ReconfigureDvs_ ...@@ -129,8 +128,7 @@ func (s *DistributedVirtualSwitch) ReconfigureDvsTask(req *types.ReconfigureDvs_
} }
} }
Map.RemoveReference(host, &host.Network, s.Self) RemoveReference(&s.Summary.HostMember, member.Host)
RemoveReference(&s.Summary.HostMember, s.Self)
case types.ConfigSpecOperationEdit: case types.ConfigSpecOperationEdit:
return nil, &types.NotSupported{} return nil, &types.NotSupported{}
} }
......
...@@ -162,6 +162,12 @@ var EventInfo = []types.EventDescriptionEventDetail{ ...@@ -162,6 +162,12 @@ var EventInfo = []types.EventDescriptionEventDetail{
FullFormat: "{{.Vm.Name}} on host {{.Host.Name}} in {{.Datacenter.Name}} is starting", 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", Key: "VmSuspendingEvent",
Description: "VM being suspended", Description: "VM being suspended",
Category: "info", Category: "info",
......
...@@ -157,15 +157,18 @@ func (m *EventManager) PostEvent(ctx *Context, req *types.PostEvent) soap.HasFau ...@@ -157,15 +157,18 @@ func (m *EventManager) PostEvent(ctx *Context, req *types.PostEvent) soap.HasFau
event.CreatedTime = time.Now() event.CreatedTime = time.Now()
event.UserName = ctx.Session.UserName event.UserName = ctx.Session.UserName
m.page = m.page.Next() m.page = m.page.Prev()
m.page.Value = req.EventToPost m.page.Value = req.EventToPost
m.formatMessage(req.EventToPost) m.formatMessage(req.EventToPost)
for _, c := range m.collectors { for _, c := range m.collectors {
if c.eventMatches(req.EventToPost) { ctx.WithLock(c, func() {
c.page = c.page.Next() if c.eventMatches(req.EventToPost) {
c.page.Value = event c.page = c.page.Prev()
} c.page.Value = req.EventToPost
Map.Update(c, []types.PropertyChange{{Name: "latestPage", Val: c.GetLatestPage()}})
}
})
} }
return &methods.PostEventBody{ return &methods.PostEventBody{
......
...@@ -29,12 +29,10 @@ func (ds *Datastore) stat() error { ...@@ -29,12 +29,10 @@ func (ds *Datastore) stat() error {
return err return err
} }
bsize := uint64(stat.Bsize) / 512 info.FreeSpace = int64(stat.Bfree * uint64(stat.Bsize))
info.FreeSpace = int64(stat.Bfree*bsize) >> 1
ds.Summary.FreeSpace = info.FreeSpace ds.Summary.FreeSpace = info.FreeSpace
ds.Summary.Capacity = int64(stat.Blocks*bsize) >> 1 ds.Summary.Capacity = int64(stat.Blocks * uint64(stat.Bsize))
return nil return nil
} }
...@@ -17,6 +17,9 @@ limitations under the License. ...@@ -17,6 +17,9 @@ limitations under the License.
package simulator package simulator
import ( import (
"reflect"
"strings"
"github.com/vmware/govmomi/vim25/methods" "github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/soap" "github.com/vmware/govmomi/vim25/soap"
...@@ -26,17 +29,71 @@ import ( ...@@ -26,17 +29,71 @@ import (
type PropertyFilter struct { type PropertyFilter struct {
mo.PropertyFilter 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{} body := &methods.DestroyPropertyFilterBody{}
RemoveReference(&f.pc.Filter, c.This) RemoveReference(&f.pc.Filter, c.This)
Map.Remove(c.This) ctx.Session.Remove(c.This)
body.Res = &types.DestroyPropertyFilterResponse{} body.Res = &types.DestroyPropertyFilterResponse{}
return body 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
}
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"reflect" "reflect"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"github.com/vmware/govmomi/vim25" "github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/mo"
...@@ -46,10 +47,11 @@ var refValueMap = map[string]string{ ...@@ -46,10 +47,11 @@ var refValueMap = map[string]string{
// Map is the default Registry instance. // Map is the default Registry instance.
var Map = NewRegistry() 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 { type RegisterObject interface {
mo.Reference mo.Reference
PutObject(mo.Reference) PutObject(mo.Reference)
UpdateObject(mo.Reference, []types.PropertyChange)
RemoveObject(types.ManagedObjectReference) RemoveObject(types.ManagedObjectReference)
} }
...@@ -59,7 +61,7 @@ type Registry struct { ...@@ -59,7 +61,7 @@ type Registry struct {
objects map[types.ManagedObjectReference]mo.Reference objects map[types.ManagedObjectReference]mo.Reference
handlers map[types.ManagedObjectReference]RegisterObject handlers map[types.ManagedObjectReference]RegisterObject
locks map[types.ManagedObjectReference]sync.Locker locks map[types.ManagedObjectReference]sync.Locker
counter int counter int64
Namespace string Namespace string
Path string Path string
...@@ -112,8 +114,8 @@ func (r *Registry) newReference(item mo.Reference) types.ManagedObjectReference ...@@ -112,8 +114,8 @@ func (r *Registry) newReference(item mo.Reference) types.ManagedObjectReference
} }
if ref.Value == "" { if ref.Value == "" {
r.counter++ n := atomic.AddInt64(&r.counter, 1)
ref.Value = fmt.Sprintf("%s-%d", valuePrefix(ref.Type), r.counter) ref.Value = fmt.Sprintf("%s-%d", valuePrefix(ref.Type), n)
} }
return ref return ref
...@@ -126,7 +128,9 @@ func (r *Registry) setReference(item mo.Reference, ref types.ManagedObjectRefere ...@@ -126,7 +128,9 @@ func (r *Registry) setReference(item mo.Reference, ref types.ManagedObjectRefere
// AddHandler adds a RegisterObject handler to the Registry. // AddHandler adds a RegisterObject handler to the Registry.
func (r *Registry) AddHandler(h RegisterObject) { func (r *Registry) AddHandler(h RegisterObject) {
r.m.Lock()
r.handlers[h.Reference()] = h r.handlers[h.Reference()] = h
r.m.Unlock()
} }
// NewEntity sets Entity().Self with a new, unique Value. // NewEntity sets Entity().Self with a new, unique Value.
...@@ -173,10 +177,23 @@ func (r *Registry) Any(kind string) mo.Entity { ...@@ -173,10 +177,23 @@ func (r *Registry) Any(kind string) mo.Entity {
return nil 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. // Put adds a new object to Registry, generating a ManagedObjectReference if not already set.
func (r *Registry) Put(item mo.Reference) mo.Reference { func (r *Registry) Put(item mo.Reference) mo.Reference {
r.m.Lock() r.m.Lock()
defer r.m.Unlock()
ref := item.Reference() ref := item.Reference()
if ref.Type == "" || ref.Value == "" { if ref.Type == "" || ref.Value == "" {
...@@ -192,25 +209,50 @@ func (r *Registry) Put(item mo.Reference) mo.Reference { ...@@ -192,25 +209,50 @@ func (r *Registry) Put(item mo.Reference) mo.Reference {
r.objects[ref] = item r.objects[ref] = item
for _, h := range r.handlers { r.m.Unlock()
h.PutObject(item)
} r.applyHandlers(func(o RegisterObject) {
o.PutObject(item)
})
return item return item
} }
// Remove removes an object from the Registry. // Remove removes an object from the Registry.
func (r *Registry) Remove(item types.ManagedObjectReference) { func (r *Registry) Remove(item types.ManagedObjectReference) {
r.m.Lock() r.applyHandlers(func(o RegisterObject) {
defer r.m.Unlock() o.RemoveObject(item)
})
for _, h := range r.handlers {
h.RemoveObject(item)
}
r.m.Lock()
delete(r.objects, item) delete(r.objects, item)
delete(r.handlers, item) delete(r.handlers, item)
delete(r.locks, 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. // getEntityParent traverses up the inventory and returns the first object of type kind.
...@@ -417,11 +459,23 @@ func (r *Registry) MarshalJSON() ([]byte, error) { ...@@ -417,11 +459,23 @@ func (r *Registry) MarshalJSON() ([]byte, error) {
} }
func (r *Registry) locker(obj mo.Reference) sync.Locker { 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 { if mu, ok := obj.(sync.Locker); ok {
return mu return mu
} }
ref := obj.Reference()
r.m.Lock() r.m.Lock()
mu, ok := r.locks[ref] mu, ok := r.locks[ref]
if !ok { if !ok {
...@@ -444,3 +498,9 @@ func (r *Registry) WithLock(obj mo.Reference, f func()) { ...@@ -444,3 +498,9 @@ func (r *Registry) WithLock(obj mo.Reference, f func()) {
} }
f() f()
} }
// nopLocker can be embedded to opt-out of auto-locking (see Registry.WithLock)
type nopLocker struct{}
func (*nopLocker) Lock() {}
func (*nopLocker) Unlock() {}
...@@ -57,15 +57,18 @@ func (s *SearchIndex) FindByDatastorePath(r *types.FindByDatastorePath) soap.Has ...@@ -57,15 +57,18 @@ func (s *SearchIndex) FindByDatastorePath(r *types.FindByDatastorePath) soap.Has
func (s *SearchIndex) FindByInventoryPath(req *types.FindByInventoryPath) soap.HasFault { func (s *SearchIndex) FindByInventoryPath(req *types.FindByInventoryPath) soap.HasFault {
body := &methods.FindByInventoryPathBody{Res: new(types.FindByInventoryPathResponse)} body := &methods.FindByInventoryPathBody{Res: new(types.FindByInventoryPathResponse)}
path := strings.Split(req.InventoryPath, "/") split := func(c rune) bool {
if len(path) <= 1 { return c == '/'
}
path := strings.FieldsFunc(req.InventoryPath, split)
if len(path) < 1 {
return body return body
} }
root := Map.content().RootFolder root := Map.content().RootFolder
o := &root o := &root
for _, name := range path[1:] { for _, name := range path {
f := s.FindChild(&types.FindChild{Entity: *o, Name: name}) f := s.FindChild(&types.FindChild{Entity: *o, Name: name})
o = f.(*methods.FindChildBody).Res.Returnval o = f.(*methods.FindChildBody).Res.Returnval
...@@ -132,9 +135,16 @@ func (s *SearchIndex) FindByUuid(req *types.FindByUuid) soap.HasFault { ...@@ -132,9 +135,16 @@ func (s *SearchIndex) FindByUuid(req *types.FindByUuid) soap.HasFault {
if !ok { if !ok {
continue continue
} }
if vm.Config.Uuid == req.Uuid { if req.InstanceUuid != nil && *req.InstanceUuid {
body.Res.Returnval = &ref if vm.Config.InstanceUuid == req.Uuid {
break body.Res.Returnval = &ref
break
}
} else {
if vm.Config.Uuid == req.Uuid {
body.Res.Returnval = &ref
break
}
} }
} }
} else { } else {
......
...@@ -138,6 +138,12 @@ func (s *SessionManager) Logout(ctx *Context, _ *types.Logout) soap.HasFault { ...@@ -138,6 +138,12 @@ func (s *SessionManager) Logout(ctx *Context, _ *types.Logout) soap.HasFault {
session := ctx.Session session := ctx.Session
delete(s.sessions, session.Key) 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{ ctx.postEvent(&types.UserLogoutSessionEvent{
IpAddress: session.IpAddress, IpAddress: session.IpAddress,
UserAgent: session.UserAgent, UserAgent: session.UserAgent,
......
...@@ -390,7 +390,7 @@ func (s *Service) findDatastore(query url.Values) (*Datastore, error) { ...@@ -390,7 +390,7 @@ func (s *Service) findDatastore(query url.Values) (*Datastore, error) {
ctx := context.Background() ctx := context.Background()
finder := find.NewFinder(s.client, false) 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 { if err != nil {
return nil, err return nil, err
} }
......
...@@ -55,18 +55,19 @@ func CreateTask(e mo.Reference, name string, run func(*Task) (types.AnyType, typ ...@@ -55,18 +55,19 @@ func CreateTask(e mo.Reference, name string, run func(*Task) (types.AnyType, typ
Execute: run, Execute: run,
} }
Map.Put(task) task.Self = Map.newReference(task)
task.Info.Key = task.Self.Value task.Info.Key = task.Self.Value
task.Info.Task = task.Self task.Info.Task = task.Self
task.Info.Name = ucFirst(name) task.Info.Name = ucFirst(name)
task.Info.DescriptionId = fmt.Sprintf("%s.%s", ref.Type, id) task.Info.DescriptionId = fmt.Sprintf("%s.%s", ref.Type, id)
task.Info.Entity = &ref task.Info.Entity = &ref
task.Info.EntityName = ref.Value task.Info.EntityName = ref.Value
task.Info.Reason = &types.TaskReasonUser{UserName: "vcsim"} // TODO: Context.Session.User
task.Info.QueueTime = time.Now() task.Info.QueueTime = time.Now()
task.Info.State = types.TaskInfoStateQueued task.Info.State = types.TaskInfoStateQueued
Map.Put(task)
return task return task
} }
...@@ -78,25 +79,31 @@ type TaskRunner interface { ...@@ -78,25 +79,31 @@ type TaskRunner interface {
func (t *Task) Run() types.ManagedObjectReference { func (t *Task) Run() types.ManagedObjectReference {
now := time.Now() 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) res, err := t.Execute(t)
state := types.TaskInfoStateSuccess
now = time.Now() var fault interface{}
t.Info.CompleteTime = &now
if err != nil { if err != nil {
t.Info.State = types.TaskInfoStateError state = types.TaskInfoStateError
t.Info.Error = &types.LocalizedMethodFault{ fault = types.LocalizedMethodFault{
Fault: err, Fault: err,
LocalizedMessage: fmt.Sprintf("%T", 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 return t.Self
} }
...@@ -17,6 +17,8 @@ limitations under the License. ...@@ -17,6 +17,8 @@ limitations under the License.
package simulator package simulator
import ( import (
"sync"
"github.com/vmware/govmomi/object" "github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types" "github.com/vmware/govmomi/vim25/types"
...@@ -26,6 +28,7 @@ var recentTaskMax = 200 // the VC limit ...@@ -26,6 +28,7 @@ var recentTaskMax = 200 // the VC limit
type TaskManager struct { type TaskManager struct {
mo.TaskManager mo.TaskManager
sync.Mutex
} }
func NewTaskManager(ref types.ManagedObjectReference) object.Reference { func NewTaskManager(ref types.ManagedObjectReference) object.Reference {
...@@ -41,12 +44,16 @@ func (m *TaskManager) PutObject(obj mo.Reference) { ...@@ -41,12 +44,16 @@ func (m *TaskManager) PutObject(obj mo.Reference) {
return return
} }
m.RecentTask = append(m.RecentTask, ref) m.Lock()
recent := append(m.RecentTask, ref)
if len(m.RecentTask) > recentTaskMax { if len(recent) > recentTaskMax {
m.RecentTask = m.RecentTask[1:] recent = recent[1:]
} }
}
func (m *TaskManager) RemoveObject(_ types.ManagedObjectReference) { Map.Update(m, []types.PropertyChange{{Name: "recentTask", Val: recent}})
m.Unlock()
} }
func (*TaskManager) RemoveObject(types.ManagedObjectReference) {}
func (*TaskManager) UpdateObject(mo.Reference, []types.PropertyChange) {}
...@@ -137,7 +137,8 @@ type ContainerView struct { ...@@ -137,7 +137,8 @@ type ContainerView struct {
types map[string]bool 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) return destroyView(c.This)
} }
...@@ -192,3 +193,83 @@ func (v *ContainerView) add(root mo.Reference, seen map[types.ManagedObjectRefer ...@@ -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
}
...@@ -210,3 +210,10 @@ func (m *VirtualDiskManager) QueryVirtualDiskUuid(req *types.QueryVirtualDiskUui ...@@ -210,3 +210,10 @@ func (m *VirtualDiskManager) QueryVirtualDiskUuid(req *types.QueryVirtualDiskUui
return body 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
}
...@@ -444,6 +444,13 @@ func numberToString(n int64, sep rune) string { ...@@ -444,6 +444,13 @@ func numberToString(n int64, sep rune) string {
return buf.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 { func (vm *VirtualMachine) configureDevice(devices object.VirtualDeviceList, spec *types.VirtualDeviceConfigSpec) types.BaseMethodFault {
device := spec.Device device := spec.Device
d := device.GetVirtualDevice() d := device.GetVirtualDevice()
...@@ -518,9 +525,16 @@ func (vm *VirtualMachine) configureDevice(devices object.VirtualDeviceList, spec ...@@ -518,9 +525,16 @@ func (vm *VirtualMachine) configureDevice(devices object.VirtualDeviceList, spec
p, _ := parseDatastorePath(info.FileName) p, _ := parseDatastorePath(info.FileName)
host := Map.Get(*vm.Runtime.Host).(*HostSystem) 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 ...@@ -556,6 +570,15 @@ func (vm *VirtualMachine) removeDevice(devices object.VirtualDeviceList, spec *t
switch b := device.Backing.(type) { switch b := device.Backing.(type) {
case types.BaseVirtualDeviceFileBackingInfo: case types.BaseVirtualDeviceFileBackingInfo:
file = b.GetVirtualDeviceFileBackingInfo().FileName 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 != "" { if file != "" {
...@@ -686,15 +709,9 @@ func (c *powerVMTask) Run(task *Task) (types.AnyType, types.BaseMethodFault) { ...@@ -686,15 +709,9 @@ func (c *powerVMTask) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
} }
} }
c.VirtualMachine.Runtime.PowerState = c.state var boot types.AnyType
c.VirtualMachine.Summary.Runtime.PowerState = c.state
bt := &c.VirtualMachine.Summary.Runtime.BootTime
if c.state == types.VirtualMachinePowerStatePoweredOn { if c.state == types.VirtualMachinePowerStatePoweredOn {
now := time.Now() boot = time.Now()
*bt = &now
} else {
*bt = nil
} }
event := c.event() event := c.event()
...@@ -705,9 +722,23 @@ func (c *powerVMTask) Run(task *Task) (types.AnyType, types.BaseMethodFault) { ...@@ -705,9 +722,23 @@ func (c *powerVMTask) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
&types.VmPoweredOnEvent{VmEvent: event}, &types.VmPoweredOnEvent{VmEvent: event},
) )
case types.VirtualMachinePowerStatePoweredOff: 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 return nil, nil
} }
...@@ -739,6 +770,37 @@ func (vm *VirtualMachine) PowerOffVMTask(ctx *Context, c *types.PowerOffVM_Task) ...@@ -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 { func (vm *VirtualMachine) ReconfigVMTask(ctx *Context, req *types.ReconfigVM_Task) soap.HasFault {
task := CreateTask(vm, "reconfigVm", func(t *Task) (types.AnyType, types.BaseMethodFault) { task := CreateTask(vm, "reconfigVm", func(t *Task) (types.AnyType, types.BaseMethodFault) {
err := vm.configure(&req.Spec) err := vm.configure(&req.Spec)
...@@ -771,6 +833,11 @@ func (vm *VirtualMachine) DestroyTask(ctx *Context, req *types.Destroy_Task) soa ...@@ -771,6 +833,11 @@ func (vm *VirtualMachine) DestroyTask(ctx *Context, req *types.Destroy_Task) soa
return nil, r.Fault().VimFault().(types.BaseMethodFault) 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) // Delete VM files from the datastore (ignoring result for now)
m := Map.FileManager() m := Map.FileManager()
dc := Map.getEntityDatacenter(vm).Reference() dc := Map.getEntityDatacenter(vm).Reference()
...@@ -907,29 +974,36 @@ func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soa ...@@ -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 { func (vm *VirtualMachine) RelocateVMTask(req *types.RelocateVM_Task) soap.HasFault {
task := CreateTask(vm, "relocateVm", func(t *Task) (types.AnyType, types.BaseMethodFault) { task := CreateTask(vm, "relocateVm", func(t *Task) (types.AnyType, types.BaseMethodFault) {
var changes []types.PropertyChange
if ref := req.Spec.Datastore; ref != nil { if ref := req.Spec.Datastore; ref != nil {
ds := Map.Get(*ref).(*Datastore) ds := Map.Get(*ref).(*Datastore)
Map.RemoveReference(ds, &ds.Vm, *ref) Map.RemoveReference(ds, &ds.Vm, *ref)
vm.Datastore = []types.ManagedObjectReference{*ref}
// TODO: migrate vm.Config.Files (and vm.Summary.Config.VmPathName) // 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 { if ref := req.Spec.Pool; ref != nil {
pool := Map.Get(*ref).(*ResourcePool) pool := Map.Get(*ref).(*ResourcePool)
Map.RemoveReference(pool, &pool.Vm, *ref) 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 { if ref := req.Spec.Host; ref != nil {
host := Map.Get(*ref).(*HostSystem) host := Map.Get(*ref).(*HostSystem)
Map.RemoveReference(host, &host.Vm, *ref) 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 return nil, nil
}) })
...@@ -1032,7 +1106,7 @@ func (vm *VirtualMachine) RemoveAllSnapshotsTask(req *types.RemoveAllSnapshots_T ...@@ -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{} r := &methods.ShutdownGuestBody{}
// should be poweron // should be poweron
if vm.Runtime.PowerState == types.VirtualMachinePowerStatePoweredOff { if vm.Runtime.PowerState == types.VirtualMachinePowerStatePoweredOff {
...@@ -1047,6 +1121,17 @@ func (vm *VirtualMachine) ShutdownGuest(c *types.ShutdownGuest) soap.HasFault { ...@@ -1047,6 +1121,17 @@ func (vm *VirtualMachine) ShutdownGuest(c *types.ShutdownGuest) soap.HasFault {
vm.Runtime.PowerState = types.VirtualMachinePowerStatePoweredOff vm.Runtime.PowerState = types.VirtualMachinePowerStatePoweredOff
vm.Summary.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) r.Res = new(types.ShutdownGuestResponse)
return r return r
......
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"],
)
// 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
}
// 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
}
// 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
}
// 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
}
...@@ -28,7 +28,7 @@ import ( ...@@ -28,7 +28,7 @@ import (
const ( const (
Namespace = "vim25" Namespace = "vim25"
Version = "6.5" Version = "6.7"
Path = "/sdk" Path = "/sdk"
) )
......
...@@ -65,6 +65,22 @@ func ObjectContentToType(o types.ObjectContent) (interface{}, error) { ...@@ -65,6 +65,22 @@ func ObjectContentToType(o types.ObjectContent) (interface{}, error) {
return v.Elem().Interface(), nil 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 // LoadRetrievePropertiesResponse converts the response of a call to
// RetrieveProperties to one or more managed objects. // RetrieveProperties to one or more managed objects.
func LoadRetrievePropertiesResponse(res *types.RetrievePropertiesResponse, dst interface{}) error { func LoadRetrievePropertiesResponse(res *types.RetrievePropertiesResponse, dst interface{}) error {
......
...@@ -155,6 +155,8 @@ func (t *typeInfo) build(typ reflect.Type, fn string, fi []int) { ...@@ -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 // 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 // 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 // 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) { ...@@ -172,6 +174,11 @@ func assignValue(val reflect.Value, fi []int, pv reflect.Value) {
rv := val.Field(fi[0]) rv := val.Field(fi[0])
fi = fi[1:] fi = fi[1:]
if len(fi) == 0 { if len(fi) == 0 {
if pv == nilValue {
pv = reflect.Zero(rv.Type())
rv.Set(pv)
return
}
rt := rv.Type() rt := rv.Type()
pt := pv.Type() pt := pv.Type()
...@@ -182,6 +189,24 @@ func assignValue(val reflect.Value, fi []int, pv reflect.Value) { ...@@ -182,6 +189,24 @@ func assignValue(val reflect.Value, fi []int, pv reflect.Value) {
rt = rv.Type() 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 type is an interface, check if pv implements it.
if rt.Kind() == reflect.Interface && !pt.Implements(rt) { if rt.Kind() == reflect.Interface && !pt.Implements(rt) {
// Check if pointer to pv implements it. // Check if pointer to pv implements it.
...@@ -200,7 +225,7 @@ func assignValue(val reflect.Value, fi []int, pv reflect.Value) { ...@@ -200,7 +225,7 @@ func assignValue(val reflect.Value, fi []int, pv reflect.Value) {
} else if rt.ConvertibleTo(pt) { } else if rt.ConvertibleTo(pt) {
rv.Set(pv.Convert(rt)) rv.Set(pv.Convert(rt))
} else { } 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 return
...@@ -211,23 +236,6 @@ func assignValue(val reflect.Value, fi []int, pv reflect.Value) { ...@@ -211,23 +236,6 @@ func assignValue(val reflect.Value, fi []int, pv reflect.Value) {
var arrayOfRegexp = regexp.MustCompile("ArrayOf(.*)$") 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 // LoadObjectFromContent loads properties from the 'PropSet' field in the
// specified ObjectContent value into the value it represents, which is // specified ObjectContent value into the value it represents, which is
// returned as a reflect.Value. // returned as a reflect.Value.
...@@ -240,7 +248,7 @@ func (t *typeInfo) LoadFromObjectContent(o types.ObjectContent) (reflect.Value, ...@@ -240,7 +248,7 @@ func (t *typeInfo) LoadFromObjectContent(o types.ObjectContent) (reflect.Value,
if !ok { if !ok {
continue continue
} }
assignValue(v, rv, anyTypeToValue(p.Val)) assignValue(v, rv, reflect.ValueOf(p.Val))
} }
return v, nil return v, nil
......
...@@ -77,6 +77,17 @@ type Client struct { ...@@ -77,6 +77,17 @@ type Client struct {
var schemeMatch = regexp.MustCompile(`^\w+://`) 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" // ParseURL is wrapper around url.Parse, where Scheme defaults to "https" and Path defaults to "/sdk"
func ParseURL(s string) (*url.URL, error) { func ParseURL(s string) (*url.URL, error) {
var err error var err error
...@@ -200,7 +211,11 @@ func (c *Client) SetRootCAs(file string) error { ...@@ -200,7 +211,11 @@ func (c *Client) SetRootCAs(file string) error {
return err return err
} }
pool.AppendCertsFromPEM(pem) if ok := pool.AppendCertsFromPEM(pem); !ok {
return errInvalidCACertificate{
File: name,
}
}
} }
c.t.TLSClientConfig.RootCAs = pool c.t.TLSClientConfig.RootCAs = pool
......
...@@ -39,7 +39,11 @@ func (s soapFaultError) Error() string { ...@@ -39,7 +39,11 @@ func (s soapFaultError) Error() string {
msg := s.fault.String msg := s.fault.String
if msg == "" { 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) return fmt.Sprintf("%s: %s", s.fault.Code, msg)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment