Merge pull request #75742 from rhockenbury/automated-cherry-pick-of-#75515-upstream-release-1.14

Automated cherry pick of #75515: godeps: update vmware/govmomi to v0.20 release
pull/564/head
Kubernetes Prow Robot 2019-04-05 13:11:22 -07:00 committed by GitHub
commit b5e1ea4a5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 54840 additions and 318 deletions

132
Godeps/Godeps.json generated
View File

@ -3319,168 +3319,168 @@
}, },
{ {
"ImportPath": "github.com/vmware/govmomi", "ImportPath": "github.com/vmware/govmomi",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/find", "ImportPath": "github.com/vmware/govmomi/find",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/list", "ImportPath": "github.com/vmware/govmomi/list",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/lookup", "ImportPath": "github.com/vmware/govmomi/lookup",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/lookup/methods", "ImportPath": "github.com/vmware/govmomi/lookup/methods",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/lookup/simulator", "ImportPath": "github.com/vmware/govmomi/lookup/simulator",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/lookup/types", "ImportPath": "github.com/vmware/govmomi/lookup/types",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/nfc", "ImportPath": "github.com/vmware/govmomi/nfc",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/object", "ImportPath": "github.com/vmware/govmomi/object",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/pbm", "ImportPath": "github.com/vmware/govmomi/pbm",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/pbm/methods", "ImportPath": "github.com/vmware/govmomi/pbm/methods",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/pbm/types", "ImportPath": "github.com/vmware/govmomi/pbm/types",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/property", "ImportPath": "github.com/vmware/govmomi/property",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/session", "ImportPath": "github.com/vmware/govmomi/session",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/simulator", "ImportPath": "github.com/vmware/govmomi/simulator",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/simulator/esx", "ImportPath": "github.com/vmware/govmomi/simulator/esx",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/simulator/vpx", "ImportPath": "github.com/vmware/govmomi/simulator/vpx",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/sts", "ImportPath": "github.com/vmware/govmomi/sts",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/sts/internal", "ImportPath": "github.com/vmware/govmomi/sts/internal",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/sts/simulator", "ImportPath": "github.com/vmware/govmomi/sts/simulator",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/task", "ImportPath": "github.com/vmware/govmomi/task",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vapi/internal", "ImportPath": "github.com/vmware/govmomi/vapi/internal",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vapi/rest", "ImportPath": "github.com/vmware/govmomi/vapi/rest",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vapi/simulator", "ImportPath": "github.com/vmware/govmomi/vapi/simulator",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vapi/tags", "ImportPath": "github.com/vmware/govmomi/vapi/tags",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vim25", "ImportPath": "github.com/vmware/govmomi/vim25",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vim25/debug", "ImportPath": "github.com/vmware/govmomi/vim25/debug",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vim25/methods", "ImportPath": "github.com/vmware/govmomi/vim25/methods",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vim25/mo", "ImportPath": "github.com/vmware/govmomi/vim25/mo",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vim25/progress", "ImportPath": "github.com/vmware/govmomi/vim25/progress",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vim25/soap", "ImportPath": "github.com/vmware/govmomi/vim25/soap",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vim25/types", "ImportPath": "github.com/vmware/govmomi/vim25/types",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/govmomi/vim25/xml", "ImportPath": "github.com/vmware/govmomi/vim25/xml",
"Comment": "v0.18.0-48-g22f74650cf39ba", "Comment": "v0.20.0",
"Rev": "22f74650cf39ba4649fba45e770df0f44df6f758" "Rev": "bdf05b6cab86b1e9f40ee80a4d2cb07a0c25ef78"
}, },
{ {
"ImportPath": "github.com/vmware/photon-controller-go-sdk/SSPI", "ImportPath": "github.com/vmware/photon-controller-go-sdk/SSPI",

View File

@ -86,34 +86,27 @@ func (connection *VSphereConnection) Connect(ctx context.Context) error {
return nil return nil
} }
// login calls SessionManager.LoginByToken if certificate and private key are configured, // Signer returns an sts.Signer for use with SAML token auth if connection is configured for such.
// otherwise calls SessionManager.Login with user and password. // Returns nil if username/password auth is configured for the connection.
func (connection *VSphereConnection) login(ctx context.Context, client *vim25.Client) error { func (connection *VSphereConnection) Signer(ctx context.Context, client *vim25.Client) (*sts.Signer, error) {
m := session.NewManager(client)
connection.credentialsLock.Lock()
defer connection.credentialsLock.Unlock()
// TODO: Add separate fields for certificate and private-key. // TODO: Add separate fields for certificate and private-key.
// For now we can leave the config structs and validation as-is and // For now we can leave the config structs and validation as-is and
// decide to use LoginByToken if the username value is PEM encoded. // decide to use LoginByToken if the username value is PEM encoded.
b, _ := pem.Decode([]byte(connection.Username)) b, _ := pem.Decode([]byte(connection.Username))
if b == nil { if b == nil {
klog.V(3).Infof("SessionManager.Login with username '%s'", connection.Username) return nil, nil
return m.Login(ctx, neturl.UserPassword(connection.Username, connection.Password))
} }
klog.V(3).Infof("SessionManager.LoginByToken with certificate '%s'", connection.Username)
cert, err := tls.X509KeyPair([]byte(connection.Username), []byte(connection.Password)) cert, err := tls.X509KeyPair([]byte(connection.Username), []byte(connection.Password))
if err != nil { if err != nil {
klog.Errorf("Failed to load X509 key pair. err: %+v", err) klog.Errorf("Failed to load X509 key pair. err: %+v", err)
return err return nil, err
} }
tokens, err := sts.NewClient(ctx, client) tokens, err := sts.NewClient(ctx, client)
if err != nil { if err != nil {
klog.Errorf("Failed to create STS client. err: %+v", err) klog.Errorf("Failed to create STS client. err: %+v", err)
return err return nil, err
} }
req := sts.TokenRequest{ req := sts.TokenRequest{
@ -123,9 +116,31 @@ func (connection *VSphereConnection) login(ctx context.Context, client *vim25.Cl
signer, err := tokens.Issue(ctx, req) signer, err := tokens.Issue(ctx, req)
if err != nil { if err != nil {
klog.Errorf("Failed to issue SAML token. err: %+v", err) klog.Errorf("Failed to issue SAML token. err: %+v", err)
return nil, err
}
return signer, nil
}
// login calls SessionManager.LoginByToken if certificate and private key are configured,
// otherwise calls SessionManager.Login with user and password.
func (connection *VSphereConnection) login(ctx context.Context, client *vim25.Client) error {
m := session.NewManager(client)
connection.credentialsLock.Lock()
defer connection.credentialsLock.Unlock()
signer, err := connection.Signer(ctx, client)
if err != nil {
return err return err
} }
if signer == nil {
klog.V(3).Infof("SessionManager.Login with username %q", connection.Username)
return m.Login(ctx, neturl.UserPassword(connection.Username, connection.Password))
}
klog.V(3).Infof("SessionManager.LoginByToken with certificate %q", connection.Username)
header := soap.Header{Security: signer} header := soap.Header{Security: signer}
return m.LoginByToken(client.WithHeader(ctx, header)) return m.LoginByToken(client.WithHeader(ctx, header))

View File

@ -38,7 +38,7 @@ import (
"github.com/vmware/govmomi/vapi/tags" "github.com/vmware/govmomi/vapi/tags"
"github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/mo"
vmwaretypes "github.com/vmware/govmomi/vim25/types" vmwaretypes "github.com/vmware/govmomi/vim25/types"
"k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
k8stypes "k8s.io/apimachinery/pkg/types" k8stypes "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
@ -1432,10 +1432,20 @@ func (vs *VSphere) NodeManager() (nodeManager *NodeManager) {
func withTagsClient(ctx context.Context, connection *vclib.VSphereConnection, f func(c *rest.Client) error) error { func withTagsClient(ctx context.Context, connection *vclib.VSphereConnection, f func(c *rest.Client) error) error {
c := rest.NewClient(connection.Client) c := rest.NewClient(connection.Client)
user := url.UserPassword(connection.Username, connection.Password) signer, err := connection.Signer(ctx, connection.Client)
if err := c.Login(ctx, user); err != nil { if err != nil {
return err return err
} }
if signer == nil {
user := url.UserPassword(connection.Username, connection.Password)
err = c.Login(ctx, user)
} else {
err = c.LoginByToken(c.WithSigner(ctx, signer))
}
if err != nil {
return err
}
defer func() { defer func() {
if err := c.Logout(ctx); err != nil { if err := c.Logout(ctx); err != nil {
klog.Errorf("failed to logout: %v", err) klog.Errorf("failed to logout: %v", err)

View File

@ -22,6 +22,7 @@ import (
"crypto/x509" "crypto/x509"
"io/ioutil" "io/ioutil"
"log" "log"
"net/url"
"os" "os"
"reflect" "reflect"
"sort" "sort"
@ -40,7 +41,7 @@ import (
"github.com/vmware/govmomi/vapi/tags" "github.com/vmware/govmomi/vapi/tags"
"github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/mo"
vmwaretypes "github.com/vmware/govmomi/vim25/types" vmwaretypes "github.com/vmware/govmomi/vim25/types"
"k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/rand" "k8s.io/apimachinery/pkg/util/rand"
cloudprovider "k8s.io/cloud-provider" cloudprovider "k8s.io/cloud-provider"
@ -343,6 +344,10 @@ func TestZones(t *testing.T) {
cfg, cleanup := configFromSim() cfg, cleanup := configFromSim()
defer cleanup() defer cleanup()
// Configure for SAML token auth
cfg.Global.User = localhostCert
cfg.Global.Password = localhostKey
// Create vSphere configuration object // Create vSphere configuration object
vs, err := newControllerNode(cfg) vs, err := newControllerNode(cfg)
if err != nil { if err != nil {
@ -381,6 +386,13 @@ func TestZones(t *testing.T) {
// Tag manager instance // Tag manager instance
m := tags.NewManager(rest.NewClient(vsi.conn.Client)) m := tags.NewManager(rest.NewClient(vsi.conn.Client))
signer, err := vsi.conn.Signer(ctx, vsi.conn.Client)
if err != nil {
t.Fatal(err)
}
if err = m.LoginByToken(m.WithSigner(ctx, signer)); err != nil {
t.Fatal(err)
}
// Create a region category // Create a region category
regionID, err := m.CreateCategory(ctx, &tags.Category{Name: vs.cfg.Labels.Region}) regionID, err := m.CreateCategory(ctx, &tags.Category{Name: vs.cfg.Labels.Region})
@ -560,6 +572,10 @@ func TestGetZoneToHosts(t *testing.T) {
// Tag manager instance // Tag manager instance
m := tags.NewManager(rest.NewClient(vsi.conn.Client)) m := tags.NewManager(rest.NewClient(vsi.conn.Client))
user := url.UserPassword(vsi.conn.Username, vsi.conn.Password)
if err = m.Login(ctx, user); err != nil {
t.Fatal(err)
}
// Create a region category // Create a region category
regionCat, err := m.CreateCategory(ctx, &tags.Category{Name: vs.cfg.Labels.Region}) regionCat, err := m.CreateCategory(ctx, &tags.Category{Name: vs.cfg.Labels.Region})

View File

@ -1,2 +1,3 @@
secrets.yml secrets.yml
dist/ dist/
.idea/

View File

@ -1,6 +1,11 @@
Amanda H. L. de Andrade <amanda.andrade@serpro.gov.br> amandahla <amanda.andrade@serpro.gov.br>
Amit Bathla <abathla@.vmware.com> <abathla@promb-1s-dhcp216.eng.vmware.com> Amit Bathla <abathla@.vmware.com> <abathla@promb-1s-dhcp216.eng.vmware.com>
Andrew Kutz <akutz@vmware.com> akutz <akutz@vmware.com>
Andrew Kutz <akutz@vmware.com> <sakutz@gmail.com>
Andrew Kutz <akutz@vmware.com> Andrew Kutz <101085+akutz@users.noreply.github.com>
Bruce Downs <bruceadowns@gmail.com> <bdowns@vmware.com> Bruce Downs <bruceadowns@gmail.com> <bdowns@vmware.com>
Bruce Downs <bruceadowns@gmail.com> <bruce.downs@jivesoftware.com> Bruce Downs <bruceadowns@gmail.com> <bruce.downs@jivesoftware.com>
Bruce Downs <bruceadowns@gmail.com> <bruce.downs@autodesk.com>
Clint Greenwood <cgreenwood@vmware.com> <clint.greenwood@gmail.com> Clint Greenwood <cgreenwood@vmware.com> <clint.greenwood@gmail.com>
Cédric Blomart <cblomart@gmail.com> <cedric.blomart@minfin.fed.be> Cédric Blomart <cblomart@gmail.com> <cedric.blomart@minfin.fed.be>
Cédric Blomart <cblomart@gmail.com> cedric <cblomart@gmail.com> Cédric Blomart <cblomart@gmail.com> cedric <cblomart@gmail.com>
@ -18,3 +23,6 @@ Anfernee Yongkun Gui <agui@vmware.com> <anfernee.gui@gmail.com>
Anfernee Yongkun Gui <agui@vmware.com> Yongkun Anfernee Gui <agui@vmware.com> Anfernee Yongkun Gui <agui@vmware.com> Yongkun Anfernee Gui <agui@vmware.com>
Zach Tucker <ztucker@vmware.com> <jzt@users.noreply.github.com> Zach Tucker <ztucker@vmware.com> <jzt@users.noreply.github.com>
Zee Yang <zeey@vmware.com> <zee.yang@gmail.com> Zee Yang <zeey@vmware.com> <zee.yang@gmail.com>
Jiatong Wang <wjiatong@vmware.com> jiatongw <wjiatong@vmware.com>
Uwe Bessle <Uwe.Bessle@iteratec.de> Uwe Bessle <u.bessle.extern@eos-ts.com>
Uwe Bessle <Uwe.Bessle@iteratec.de> Uwe Bessle <uwe.bessle@web.de>

View File

@ -1,28 +1,95 @@
sudo: required # Use the newer Travis-CI build templates based on the
# Debian Linux distribution "Trusty" release.
os: linux
dist: trusty
# Disable sudo for all builds by default. This ensures all jobs use
# Travis-CI's containerized build environment unless specified otherwise.
# The container builds have *much* shorter queue times than the VM-based
# build environment on which the sudo builds depend.
sudo: false
services: false
# Set the version of Go.
language: go language: go
go: 1.11
go: # Always set the project's Go import path to ensure that forked
- '1.10' # builds get cloned to the correct location.
go_import_path: github.com/vmware/govmomi go_import_path: github.com/vmware/govmomi
before_install: # Ensure all the jobs know where the temp directory is.
- sudo apt-get -qq update env:
- sudo apt-get install -y xmlstarlet global: TMPDIR=/tmp
- make vendor
script: jobs:
- make check test include:
- GOOS=windows make install
after_success: # The "lint" stage runs the various linters against the project.
- test -n "$TRAVIS_TAG" && docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" - &lint-stage
stage: lint
env: LINTER=govet
install: true
script: make "${LINTER}"
deploy: - <<: *lint-stage
- provider: script env: LINTER=goimports
# The "build" stage verifies the program can be built against the
# various GOOS and GOARCH combinations found in the Go releaser
# config file, ".goreleaser.yml".
- &build-stage
stage: build
env: GOOS=linux GOARCH=amd64
install: true
script: make install
- <<: *build-stage
env: GOOS=linux GOARCH=386
- <<: *build-stage
env: GOOS=darwin GOARCH=amd64
- <<: *build-stage
env: GOOS=darwin GOARCH=386
- <<: *build-stage
env: GOOS=freebsd GOARCH=amd64
- <<: *build-stage
env: GOOS=freebsd GOARCH=386
- <<: *build-stage
env: GOOS=windows GOARCH=amd64
- <<: *build-stage
env: GOOS=windows GOARCH=386
# The test stage executes the test target.
- stage: test
install: true
script: make test
# The deploy stage deploys the build artifacts using goreleaser.
#
# This stage will only be activated when there is an annotated tag present
# or when the text "/ci-deploy" is present in the commit message. However,
# the "deploy" phase of the build will still only be executed on non-PR
# builds as that restriction is baked into Travis-CI.
#
# Finally, this stage requires the Travis-CI VM infrastructure in order to
# leverage Docker. This will increase the amount of time the jobs sit
# in the queue, waiting to be built. However, it's a necessity as Travis-CI
# only allows the use of Docker with VM builds.
- stage: deploy
if: tag IS present OR commit_message =~ /\/ci-deploy/
sudo: required
services: docker
install: true
script: make install
after_success: docker login -u="${DOCKER_USERNAME}" -p="${DOCKER_PASSWORD}"
deploy:
- provider: script
skip_cleanup: true skip_cleanup: true
script: curl -sL http://git.io/goreleaser | bash script: curl -sL http://git.io/goreleaser | bash
on: addons:
tags: true apt:
condition: $TRAVIS_OS_NAME = linux update: true
go: '1.10' packages: xmlstarlet

View File

@ -1,6 +1,18 @@
# changelog # changelog
### unreleased ### 0.20.0 (2018-02-06)
* Add vslm package for managing First Class Disks
* Add LoginByToken to session KeepAliveHandler
### 0.19.0 (2018-09-30)
* New vapi/rest and and vapi/tags packages
* Allowing the use of STS for exchanging tokens
* Add object.VirtualMachine.UUID method
* SetRootCAs on the soap.Client returns an error for invalid certificates * SetRootCAs on the soap.Client returns an error for invalid certificates

View File

@ -6,16 +6,18 @@
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>
Alex Ellis (VMware) <alexellis2@gmail.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>
Andrew <AndrewDi@users.noreply.github.com>
Andrew Chin <andrew@andrewtchin.com> Andrew Chin <andrew@andrewtchin.com>
Andrew Kutz <akutz@vmware.com>
Anfernee Yongkun Gui <agui@vmware.com> Anfernee Yongkun Gui <agui@vmware.com>
angystardust <angystardust@users.noreply.github.com>
aniketGslab <aniket.shinde@gslab.com> aniketGslab <aniket.shinde@gslab.com>
Arran Walker <arran.walker@zopa.com> Arran Walker <arran.walker@zopa.com>
Aryeh Weinreb <aryehweinreb@gmail.com> Aryeh Weinreb <aryehweinreb@gmail.com>
@ -29,19 +31,23 @@ 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>
CuiHaozhi <cuihaozhi@chinacloud.com.cn>
Danny Lockard <danny.lockard@banno.com> Danny Lockard <danny.lockard@banno.com>
Dave Tucker <dave@dtucker.co.uk> Dave Tucker <dave@dtucker.co.uk>
Davide Agnello <dagnello@hp.com> Davide Agnello <dagnello@hp.com>
David Stark <dave@davidstark.name> David Stark <dave@davidstark.name>
Davinder Kumar <davinderk@vmware.com>
Deric Crago <deric.crago@gmail.com> 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>
Eric Yutao <eric.yutao@gmail.com> Eric Yutao <eric.yutao@gmail.com>
Erik Hollensbe <github@hollensbe.org> Erik Hollensbe <github@hollensbe.org>
Ethan Kaley <ethan.kaley@emc.com>
Fabio Rapposelli <fabio@vmware.com> Fabio Rapposelli <fabio@vmware.com>
Faiyaz Ahmed <ahmedf@vmware.com> Faiyaz Ahmed <ahmedf@vmware.com>
forkbomber <forkbomber@users.noreply.github.com> forkbomber <forkbomber@users.noreply.github.com>
freebsdly <qinhuajun@outlook.com>
Gavin Gray <gavin@infinio.com> Gavin Gray <gavin@infinio.com>
Gavrie Philipson <gavrie.philipson@elastifile.com> Gavrie Philipson <gavrie.philipson@elastifile.com>
George Hicken <ghicken@vmware.com> George Hicken <ghicken@vmware.com>
@ -51,33 +57,52 @@ Hasan Mahmood <mahmoodh@vmware.com>
Henrik Hodne <henrik@travis-ci.com> Henrik Hodne <henrik@travis-ci.com>
Isaac Rodman <isaac@eyz.us> Isaac Rodman <isaac@eyz.us>
Ivan Porto Carrero <icarrero@vmware.com> Ivan Porto Carrero <icarrero@vmware.com>
James King <james.king@emc.com>
Jason Kincl <jkincl@gmail.com> 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>
Jiatong Wang <wjiatong@vmware.com>
João Pereira <joaodrp@gmail.com> João Pereira <joaodrp@gmail.com>
Jonas Ausevicius <jonas.ausevicius@virtustream.com>
Jorge Sevilla <jorge.sevilla@rstor.io> Jorge Sevilla <jorge.sevilla@rstor.io>
kayrus <kay.diam@gmail.com>
Kevin George <georgek@vmware.com>
leslie-qiwa <leslie.qiwa@gmail.com> leslie-qiwa <leslie.qiwa@gmail.com>
Louie Jiang <jiangl@vmware.com> Louie Jiang <jiangl@vmware.com>
maplain <fangyuanl@vmware.com>
Marc Carmier <mcarmier@gmail.com> Marc Carmier <mcarmier@gmail.com>
Maria Ntalla <maria.ntalla@gmail.com>
Marin Atanasov Nikolov <mnikolov@vmware.com>
Matt Clay <matt@mystile.com>
Matthew Cosgrove <matthew.cosgrove@dell.com> Matthew Cosgrove <matthew.cosgrove@dell.com>
Matt Moriarity <matt@mattmoriarity.com>
Mevan Samaratunga <mevansam@gmail.com> Mevan Samaratunga <mevansam@gmail.com>
Michal Jankowski <mjankowski@vmware.com>
mingwei <mingwei@smartx.com>
Nicolas Lamirault <nicolas.lamirault@gmail.com> Nicolas Lamirault <nicolas.lamirault@gmail.com>
Omar Kohl <omarkohl@gmail.com> Omar Kohl <omarkohl@gmail.com>
Parham Alvani <parham.alvani@gmail.com> Parham Alvani <parham.alvani@gmail.com>
Pierre Gronlier <pierre.gronlier@corp.ovh.com>
Pieter Noordhuis <pnoordhuis@vmware.com> Pieter Noordhuis <pnoordhuis@vmware.com>
prydin <prydin@vmware.com>
Rowan Jacobs <rojacobs@pivotal.io>
runner.mei <runner.mei@gmail.com> runner.mei <runner.mei@gmail.com>
S.Çağlar Onur <conur@vmware.com> S.Çağlar Onur <conur@vmware.com>
Sergey Ignatov <sergey.ignatov@jetbrains.com> Sergey Ignatov <sergey.ignatov@jetbrains.com>
Steve Purcell <steve@sanityinc.com> Steve Purcell <steve@sanityinc.com>
Takaaki Furukawa <takaaki.frkw@gmail.com> Takaaki Furukawa <takaaki.frkw@gmail.com>
Tamas Eger <tamas.eger@bitrise.io>
tanishi <tanishi503@gmail.com> tanishi <tanishi503@gmail.com>
Ted Zlatanov <tzz@lifelogs.com> Ted Zlatanov <tzz@lifelogs.com>
Thibaut Ackermann <thibaut.ackermann@alcatel-lucent.com> Thibaut Ackermann <thibaut.ackermann@alcatel-lucent.com>
Trevor Dawe <trevor.dawe@gmail.com> Trevor Dawe <trevor.dawe@gmail.com>
Uwe Bessle <Uwe.Bessle@iteratec.de>
Vadim Egorov <vegorov@vmware.com> Vadim Egorov <vegorov@vmware.com>
Vikram Krishnamurthy <vikramkrishnamu@vmware.com>
Volodymyr Bobyr <pupsua@gmail.com> Volodymyr Bobyr <pupsua@gmail.com>
Witold Krecicki <wpk@culm.net> Witold Krecicki <wpk@culm.net>
Yang Yang <yangy@vmware.com> Yang Yang <yangy@vmware.com>
Yuya Kusakabe <yuya.kusakabe@gmail.com> Yuya Kusakabe <yuya.kusakabe@gmail.com>
Zacharias Taubert <zacharias.taubert@gmail.com>
Zach Tucker <ztucker@vmware.com> Zach Tucker <ztucker@vmware.com>
Zee Yang <zeey@vmware.com> Zee Yang <zeey@vmware.com>

View File

@ -3,42 +3,58 @@
[[projects]] [[projects]]
branch = "improvements" branch = "improvements"
digest = "1:b183578c34fabccaf65f1a57d2efeec2086abdce1446978d69ab3a0016cb750c"
name = "github.com/davecgh/go-xdr" name = "github.com/davecgh/go-xdr"
packages = ["xdr2"] packages = ["xdr2"]
pruneopts = "NUT"
revision = "4930550ba2e22f87187498acfd78348b15f4e7a8" revision = "4930550ba2e22f87187498acfd78348b15f4e7a8"
source = "https://github.com/rasky/go-xdr" source = "https://github.com/rasky/go-xdr"
[[projects]] [[projects]]
digest = "1:1ab18cf8c2084968d6dca0dd46fbda9efba08664ecd7957b63c7ca57bb2455df"
name = "github.com/google/uuid" name = "github.com/google/uuid"
packages = ["."] packages = ["."]
pruneopts = "NUT"
revision = "6a5e28554805e78ea6141142aba763936c4761c0" revision = "6a5e28554805e78ea6141142aba763936c4761c0"
[[projects]] [[projects]]
branch = "govmomi" branch = "govmomi"
digest = "1:f49ed6cb2129e9a3ce9dde5037cb243b5849c0ec0c7973b9d1e987872d8b8cc6"
name = "github.com/kr/pretty" name = "github.com/kr/pretty"
packages = ["."] packages = ["."]
pruneopts = "NUT"
revision = "2ee9d7453c02ef7fa518a83ae23644eb8872186a" revision = "2ee9d7453c02ef7fa518a83ae23644eb8872186a"
source = "https://github.com/dougm/pretty" source = "https://github.com/dougm/pretty"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:c3a7836b5904db0f8b609595b619916a6831cb35b8b714aec39f96d00c6155d8"
name = "github.com/kr/text" name = "github.com/kr/text"
packages = ["."] packages = ["."]
pruneopts = "NUT"
revision = "7cafcd837844e784b526369c9bce262804aebc60" revision = "7cafcd837844e784b526369c9bce262804aebc60"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:4bea31865971675c482ed875caeabe7d2182dcb47d52900b7da5236d66dc9970"
name = "github.com/vmware/vmw-guestinfo" name = "github.com/vmware/vmw-guestinfo"
packages = [ packages = [
"bdoor", "bdoor",
"message", "message",
"vmcheck" "vmcheck",
] ]
pruneopts = "NUT"
revision = "25eff159a728be87e103a0b8045e08273f4dbec4" revision = "25eff159a728be87e103a0b8045e08273f4dbec4"
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "376638fa6c0621cbd980caf8fc53494d880886f100663da8de47ecb6e596e439" input-imports = [
"github.com/davecgh/go-xdr/xdr2",
"github.com/google/uuid",
"github.com/kr/pretty",
"github.com/vmware/vmw-guestinfo/message",
"github.com/vmware/vmw-guestinfo/vmcheck",
]
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View File

@ -6,7 +6,7 @@ check: goimports govet
goimports: goimports:
@echo checking go imports... @echo checking go imports...
@go get golang.org/x/tools/cmd/goimports @command -v goimports >/dev/null 2>&1 || go get golang.org/x/tools/cmd/goimports
@! goimports -d . 2>&1 | egrep -v '^$$' @! goimports -d . 2>&1 | egrep -v '^$$'
govet: govet:
@ -14,8 +14,8 @@ govet:
@go tool vet -structtags=false -methods=false $$(find . -mindepth 1 -maxdepth 1 -type d -not -name vendor) @go tool vet -structtags=false -methods=false $$(find . -mindepth 1 -maxdepth 1 -type d -not -name vendor)
install: install:
go install -v github.com/vmware/govmomi/govc $(MAKE) -C govc install
go install -v github.com/vmware/govmomi/vcsim $(MAKE) -C vcsim install
go-test: go-test:
GORACE=history_size=5 go test -timeout 5m -count 1 -race -v $(TEST_OPTS) ./... GORACE=history_size=5 go test -timeout 5m -count 1 -race -v $(TEST_OPTS) ./...

View File

@ -59,6 +59,10 @@ Refer to the [CHANGELOG](CHANGELOG.md) for version to version changes.
* [Kubernetes](https://github.com/kubernetes/kubernetes/tree/master/pkg/cloudprovider/providers/vsphere) * [Kubernetes](https://github.com/kubernetes/kubernetes/tree/master/pkg/cloudprovider/providers/vsphere)
* [Kubernetes Cloud Provider](https://github.com/kubernetes/cloud-provider-vsphere)
* [Kubernetes Cluster API](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere)
* [Kubernetes kops](https://github.com/kubernetes/kops/tree/master/upup/pkg/fi/cloudup/vsphere) * [Kubernetes kops](https://github.com/kubernetes/kops/tree/master/upup/pkg/fi/cloudup/vsphere)
* [Terraform](https://github.com/terraform-providers/terraform-provider-vsphere) * [Terraform](https://github.com/terraform-providers/terraform-provider-vsphere)
@ -75,6 +79,10 @@ Refer to the [CHANGELOG](CHANGELOG.md) for version to version changes.
* [Libretto](https://github.com/apcera/libretto/tree/master/virtualmachine/vsphere) * [Libretto](https://github.com/apcera/libretto/tree/master/virtualmachine/vsphere)
* [Telegraf](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/vsphere)
* [Open Storage](https://github.com/libopenstorage/openstorage/tree/master/pkg/storageops/vsphere)
## Related projects ## Related projects
* [rbvmomi](https://github.com/vmware/rbvmomi) * [rbvmomi](https://github.com/vmware/rbvmomi)

View File

@ -57,10 +57,10 @@ func (o FileItem) File() types.OvfFile {
} }
type LeaseUpdater struct { type LeaseUpdater struct {
lease *Lease pos int64 // Number of bytes (keep first to ensure 64 bit aligment)
total int64 // Total number of bytes (keep first to ensure 64 bit aligment)
pos int64 // Number of bytes lease *Lease
total int64 // Total number of bytes
done chan struct{} // When lease updater should stop done chan struct{} // When lease updater should stop

View File

@ -100,6 +100,7 @@ func (c Common) ObjectName(ctx context.Context) (string, error) {
return n.Name, nil return n.Name, nil
} }
// Properties is a wrapper for property.DefaultCollector().RetrieveOne()
func (c Common) Properties(ctx context.Context, r types.ManagedObjectReference, ps []string, dst interface{}) error { func (c Common) Properties(ctx context.Context, r types.ManagedObjectReference, ps []string, dst interface{}) error {
return property.DefaultCollector(c.c).RetrieveOne(ctx, r, ps, dst) return property.DefaultCollector(c.c).RetrieveOne(ctx, r, ps, dst)
} }
@ -130,3 +131,14 @@ func (c Common) Rename(ctx context.Context, name string) (*Task, error) {
return NewTask(c.c, res.Returnval), nil return NewTask(c.c, res.Returnval), nil
} }
func (c Common) SetCustomValue(ctx context.Context, key string, value string) error {
req := types.SetCustomValue{
This: c.Reference(),
Key: key,
Value: value,
}
_, err := methods.SetCustomValue(ctx, c.c, &req)
return err
}

View File

@ -17,17 +17,16 @@ limitations under the License.
package object package object
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"math/rand" "math/rand"
"net/http"
"net/url"
"os" "os"
"path" "path"
"strings" "strings"
"context"
"net/http"
"net/url"
"github.com/vmware/govmomi/property" "github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/session" "github.com/vmware/govmomi/session"
"github.com/vmware/govmomi/vim25" "github.com/vmware/govmomi/vim25"

View File

@ -82,6 +82,20 @@ func (v VirtualMachine) PowerOff(ctx context.Context) (*Task, error) {
return NewTask(v.c, res.Returnval), nil return NewTask(v.c, res.Returnval), nil
} }
func (v VirtualMachine) PutUsbScanCodes(ctx context.Context, spec types.UsbScanCodeSpec) (int32, error) {
req := types.PutUsbScanCodes{
This: v.Reference(),
Spec: spec,
}
res, err := methods.PutUsbScanCodes(ctx, v.c, &req)
if err != nil {
return 0, err
}
return res.Returnval, nil
}
func (v VirtualMachine) Reset(ctx context.Context) (*Task, error) { func (v VirtualMachine) Reset(ctx context.Context) (*Task, error) {
req := types.ResetVM_Task{ req := types.ResetVM_Task{
This: v.Reference(), This: v.Reference(),
@ -198,6 +212,15 @@ func (v VirtualMachine) Reconfigure(ctx context.Context, config types.VirtualMac
return NewTask(v.c, res.Returnval), nil return NewTask(v.c, res.Returnval), nil
} }
func (v VirtualMachine) RefreshStorageInfo(ctx context.Context) error {
req := types.RefreshStorageInfo{
This: v.Reference(),
}
_, err := methods.RefreshStorageInfo(ctx, v.c, &req)
return err
}
func (v VirtualMachine) WaitForIP(ctx context.Context) (string, error) { func (v VirtualMachine) WaitForIP(ctx context.Context) (string, error) {
var ip string var ip string

View File

@ -29,7 +29,7 @@ import (
const ( const (
Namespace = "pbm" Namespace = "pbm"
Path = "/pbm" + vim25.Path Path = "/pbm"
) )
var ( var (

48
vendor/github.com/vmware/govmomi/program.mk generated vendored Normal file
View File

@ -0,0 +1,48 @@
ifneq (,$(strip $(GOOS)))
ifeq (,$(strip $(GOARCH)))
GOARCH := $(shell go env | grep GOARCH | awk -F= '{print $$2}' | tr -d '"')
endif
endif
ifneq (,$(strip $(GOARCH)))
ifeq (,$(strip $(GOOS)))
GOOS := $(shell go env | grep GOOS | awk -F= '{print $$2}' | tr -d '"')
endif
endif
ifeq (2,$(words $(GOOS) $(GOARCH)))
PROGRAM := $(PROGRAM)_$(GOOS)_$(GOARCH)
endif
ifeq (windows,$(GOOS))
PROGRAM := $(PROGRAM).exe
endif
all: $(PROGRAM)
TAGS += netgo
ifeq (,$(strip $(findstring -w,$(LDFLAGS))))
LDFLAGS += -w
endif
BUILD_ARGS := -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -v
$(PROGRAM):
CGO_ENABLED=0 go build -a $(BUILD_ARGS) -o $@
install:
CGO_ENABLED=0 go install $(BUILD_ARGS)
ifneq (,$(strip $(BUILD_OS)))
ifneq (,$(strip $(BUILD_ARCH)))
GOOS_GOARCH_TARGETS := $(foreach a,$(BUILD_ARCH),$(patsubst %,%_$a,$(BUILD_OS)))
XBUILD := $(addprefix $(PROGRAM)_,$(GOOS_GOARCH_TARGETS))
$(XBUILD):
GOOS=$(word 2,$(subst _, ,$@)) GOARCH=$(word 3,$(subst _, ,$@)) $(MAKE) --output-sync=target
build-all: $(XBUILD)
endif
endif
clean:
@rm -f $(PROGRAM) $(XBUILD)
.PHONY: build-all install clean

View File

@ -126,30 +126,30 @@ func (p *Collector) RetrieveProperties(ctx context.Context, req types.RetrievePr
// must be a pointer to a []interface{}, which is populated with the instances // must be a pointer to a []interface{}, which is populated with the instances
// of the specified managed objects, with the relevant properties filled in. If // of the specified managed objects, with the relevant properties filled in. If
// the properties slice is nil, all properties are loaded. // the properties slice is nil, all properties are loaded.
// Note that pointer types are optional fields that may be left as a nil value.
// The caller should check such fields for a nil value before dereferencing.
func (p *Collector) Retrieve(ctx context.Context, objs []types.ManagedObjectReference, ps []string, dst interface{}) error { func (p *Collector) Retrieve(ctx context.Context, objs []types.ManagedObjectReference, ps []string, dst interface{}) error {
if len(objs) == 0 { if len(objs) == 0 {
return errors.New("object references is empty") return errors.New("object references is empty")
} }
var propSpec *types.PropertySpec kinds := make(map[string]bool)
var propSet []types.PropertySpec
var objectSet []types.ObjectSpec var objectSet []types.ObjectSpec
for _, obj := range objs { for _, obj := range objs {
// Ensure that all object reference types are the same if _, ok := kinds[obj.Type]; !ok {
if propSpec == nil { spec := types.PropertySpec{
propSpec = &types.PropertySpec{
Type: obj.Type, Type: obj.Type,
} }
if ps == nil { if ps == nil {
propSpec.All = types.NewBool(true) spec.All = types.NewBool(true)
} else { } else {
propSpec.PathSet = ps spec.PathSet = ps
}
} else {
if obj.Type != propSpec.Type {
return errors.New("object references must have the same type")
} }
propSet = append(propSet, spec)
kinds[obj.Type] = true
} }
objectSpec := types.ObjectSpec{ objectSpec := types.ObjectSpec{
@ -164,7 +164,7 @@ func (p *Collector) Retrieve(ctx context.Context, objs []types.ManagedObjectRefe
SpecSet: []types.PropertyFilterSpec{ SpecSet: []types.PropertyFilterSpec{
{ {
ObjectSet: objectSet, ObjectSet: objectSet,
PropSet: []types.PropertySpec{*propSpec}, PropSet: propSet,
}, },
}, },
} }
@ -204,7 +204,7 @@ func (p *Collector) RetrieveWithFilter(ctx context.Context, objs []types.Managed
return p.Retrieve(ctx, objs, ps, dst) return p.Retrieve(ctx, objs, ps, dst)
} }
// RetrieveOne calls Retrieve with a single managed object reference. // RetrieveOne calls Retrieve with a single managed object reference via Collector.Retrieve().
func (p *Collector) RetrieveOne(ctx context.Context, obj types.ManagedObjectReference, ps []string, dst interface{}) error { func (p *Collector) RetrieveOne(ctx context.Context, obj types.ManagedObjectReference, ps []string, dst interface{}) error {
var objs = []types.ManagedObjectReference{obj} var objs = []types.ManagedObjectReference{obj}
return p.Retrieve(ctx, objs, ps, dst) return p.Retrieve(ctx, objs, ps, dst)

View File

@ -114,10 +114,9 @@ func (k *keepAlive) RoundTrip(ctx context.Context, req, res soap.HasFault) error
if err != nil { if err != nil {
return err return err
} }
// Start ticker on login, stop ticker on logout. // Start ticker on login, stop ticker on logout.
switch req.(type) { switch req.(type) {
case *methods.LoginBody, *methods.LoginExtensionByCertificateBody: case *methods.LoginBody, *methods.LoginExtensionByCertificateBody, *methods.LoginByTokenBody:
k.start() k.start()
case *methods.LogoutBody: case *methods.LogoutBody:
k.stop() k.stop()

View File

@ -5,12 +5,14 @@ go_library(
srcs = [ srcs = [
"authorization_manager.go", "authorization_manager.go",
"cluster_compute_resource.go", "cluster_compute_resource.go",
"container.go",
"custom_fields_manager.go", "custom_fields_manager.go",
"datacenter.go", "datacenter.go",
"datastore.go", "datastore.go",
"doc.go", "doc.go",
"dvs.go", "dvs.go",
"entity.go", "entity.go",
"environment_browser.go",
"event_manager.go", "event_manager.go",
"file_manager.go", "file_manager.go",
"folder.go", "folder.go",
@ -24,6 +26,7 @@ go_library(
"ip_pool_manager.go", "ip_pool_manager.go",
"license_manager.go", "license_manager.go",
"model.go", "model.go",
"object.go",
"option_manager.go", "option_manager.go",
"os_unix.go", "os_unix.go",
"os_windows.go", "os_windows.go",
@ -38,12 +41,14 @@ go_library(
"session_manager.go", "session_manager.go",
"simulator.go", "simulator.go",
"snapshot.go", "snapshot.go",
"storage_resource_manager.go",
"task.go", "task.go",
"task_manager.go", "task_manager.go",
"user_directory.go", "user_directory.go",
"view_manager.go", "view_manager.go",
"virtual_disk_manager.go", "virtual_disk_manager.go",
"virtual_machine.go", "virtual_machine.go",
"vstorage_object_manager.go",
], ],
importmap = "k8s.io/kubernetes/vendor/github.com/vmware/govmomi/simulator", importmap = "k8s.io/kubernetes/vendor/github.com/vmware/govmomi/simulator",
importpath = "github.com/vmware/govmomi/simulator", importpath = "github.com/vmware/govmomi/simulator",

View File

@ -33,6 +33,10 @@ type ClusterComputeResource struct {
ruleKey int32 ruleKey int32
} }
func (c *ClusterComputeResource) RenameTask(req *types.Rename_Task) soap.HasFault {
return RenameTask(c, req)
}
type addHost struct { type addHost struct {
*ClusterComputeResource *ClusterComputeResource
@ -300,6 +304,7 @@ func CreateClusterComputeResource(f *Folder, name string, spec types.ClusterConf
} }
cluster := &ClusterComputeResource{} cluster := &ClusterComputeResource{}
cluster.EnvironmentBrowser = newEnvironmentBrowser()
cluster.Name = name cluster.Name = name
cluster.Summary = &types.ClusterComputeResourceSummary{ cluster.Summary = &types.ClusterComputeResourceSummary{
UsageSummary: new(types.ClusterUsageSummary), UsageSummary: new(types.ClusterUsageSummary),

167
vendor/github.com/vmware/govmomi/simulator/container.go generated vendored Normal file
View File

@ -0,0 +1,167 @@
/*
Copyright (c) 2018 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 simulator
import (
"bytes"
"encoding/json"
"log"
"os/exec"
"strings"
"github.com/vmware/govmomi/vim25/types"
)
// container provides methods to manage a container within a simulator VM lifecycle.
type container struct {
id string
}
// inspect applies container network settings to vm.Guest properties.
func (c *container) inspect(vm *VirtualMachine) error {
if c.id == "" {
return nil
}
var objects []struct {
NetworkSettings struct {
Gateway string
IPAddress string
IPPrefixLen int
MacAddress string
}
}
cmd := exec.Command("docker", "inspect", c.id)
out, err := cmd.Output()
if err != nil {
return err
}
if err = json.NewDecoder(bytes.NewReader(out)).Decode(&objects); err != nil {
return err
}
vm.Config.Annotation = strings.Join(cmd.Args, " ")
vm.logPrintf("%s: %s", vm.Config.Annotation, string(out))
for _, o := range objects {
s := o.NetworkSettings
if s.IPAddress == "" {
continue
}
vm.Guest.IpAddress = s.IPAddress
vm.Summary.Guest.IpAddress = s.IPAddress
if len(vm.Guest.Net) != 0 {
net := &vm.Guest.Net[0]
net.IpAddress = []string{s.IPAddress}
net.MacAddress = s.MacAddress
}
}
return nil
}
// start runs the container if specified by the RUN.container extraConfig property.
func (c *container) start(vm *VirtualMachine) {
if c.id != "" {
start := "start"
if vm.Runtime.PowerState == types.VirtualMachinePowerStateSuspended {
start = "unpause"
}
cmd := exec.Command("docker", start, c.id)
err := cmd.Run()
if err != nil {
log.Printf("%s %s: %s", vm.Name, cmd.Args, err)
}
return
}
var args []string
for _, opt := range vm.Config.ExtraConfig {
val := opt.GetOptionValue()
if val.Key == "RUN.container" {
run := val.Value.(string)
err := json.Unmarshal([]byte(run), &args)
if err != nil {
args = []string{run}
}
break
}
}
if len(args) == 0 {
return
}
args = append([]string{"run", "-d", "--name", vm.Name}, args...)
cmd := exec.Command("docker", args...)
out, err := cmd.Output()
if err != nil {
log.Printf("%s %s: %s", vm.Name, cmd.Args, err)
return
}
c.id = strings.TrimSpace(string(out))
vm.logPrintf("%s %s: %s", cmd.Path, cmd.Args, c.id)
if err = c.inspect(vm); err != nil {
log.Printf("%s inspect %s: %s", vm.Name, c.id, err)
}
}
// stop the container (if any) for the given vm.
func (c *container) stop(vm *VirtualMachine) {
if c.id == "" {
return
}
cmd := exec.Command("docker", "stop", c.id)
err := cmd.Run()
if err != nil {
log.Printf("%s %s: %s", vm.Name, cmd.Args, err)
}
}
// pause the container (if any) for the given vm.
func (c *container) pause(vm *VirtualMachine) {
if c.id == "" {
return
}
cmd := exec.Command("docker", "pause", c.id)
err := cmd.Run()
if err != nil {
log.Printf("%s %s: %s", vm.Name, cmd.Args, err)
}
}
// remove the container (if any) for the given vm.
func (c *container) remove(vm *VirtualMachine) {
if c.id == "" {
return
}
cmd := exec.Command("docker", "rm", "-f", c.id)
err := cmd.Run()
if err != nil {
log.Printf("%s %s: %s", vm.Name, cmd.Args, err)
}
}

View File

@ -36,7 +36,70 @@ func NewCustomFieldsManager(ref types.ManagedObjectReference) object.Reference {
return m return m
} }
func (c *CustomFieldsManager) find(key int32) (int, *types.CustomFieldDef) { // Iterates through all entities of passed field type;
// Removes found field from their custom field properties.
func entitiesFieldRemove(field types.CustomFieldDef) {
entities := Map.All(field.ManagedObjectType)
for _, e := range entities {
entity := e.Entity()
Map.WithLock(entity, func() {
aFields := entity.AvailableField
for i, aField := range aFields {
if aField.Key == field.Key {
entity.AvailableField = append(aFields[:i], aFields[i+1:]...)
break
}
}
values := e.Entity().Value
for i, value := range values {
if value.(*types.CustomFieldStringValue).Key == field.Key {
entity.Value = append(values[:i], values[i+1:]...)
break
}
}
cValues := e.Entity().CustomValue
for i, cValue := range cValues {
if cValue.(*types.CustomFieldStringValue).Key == field.Key {
entity.CustomValue = append(cValues[:i], cValues[i+1:]...)
break
}
}
})
}
}
// Iterates through all entities of passed field type;
// Renames found field in entity's AvailableField property.
func entitiesFieldRename(field types.CustomFieldDef) {
entities := Map.All(field.ManagedObjectType)
for _, e := range entities {
entity := e.Entity()
Map.WithLock(entity, func() {
aFields := entity.AvailableField
for i, aField := range aFields {
if aField.Key == field.Key {
aFields[i].Name = field.Name
break
}
}
})
}
}
func (c *CustomFieldsManager) findByNameType(name, moType string) (int, *types.CustomFieldDef) {
for i, field := range c.Field {
if (field.ManagedObjectType == "" || field.ManagedObjectType == moType || moType == "") &&
field.Name == name {
return i, &c.Field[i]
}
}
return -1, nil
}
func (c *CustomFieldsManager) findByKey(key int32) (int, *types.CustomFieldDef) {
for i, field := range c.Field { for i, field := range c.Field {
if field.Key == key { if field.Key == key {
return i, &c.Field[i] return i, &c.Field[i]
@ -49,6 +112,15 @@ func (c *CustomFieldsManager) find(key int32) (int, *types.CustomFieldDef) {
func (c *CustomFieldsManager) AddCustomFieldDef(req *types.AddCustomFieldDef) soap.HasFault { func (c *CustomFieldsManager) AddCustomFieldDef(req *types.AddCustomFieldDef) soap.HasFault {
body := &methods.AddCustomFieldDefBody{} body := &methods.AddCustomFieldDefBody{}
_, field := c.findByNameType(req.Name, req.MoType)
if field != nil {
body.Fault_ = Fault("", &types.DuplicateName{
Name: req.Name,
Object: c.Reference(),
})
return body
}
def := types.CustomFieldDef{ def := types.CustomFieldDef{
Key: c.nextKey, Key: c.nextKey,
Name: req.Name, Name: req.Name,
@ -58,6 +130,14 @@ func (c *CustomFieldsManager) AddCustomFieldDef(req *types.AddCustomFieldDef) so
FieldInstancePrivileges: req.FieldPolicy, FieldInstancePrivileges: req.FieldPolicy,
} }
entities := Map.All(req.MoType)
for _, e := range entities {
entity := e.Entity()
Map.WithLock(entity, func() {
entity.AvailableField = append(entity.AvailableField, def)
})
}
c.Field = append(c.Field, def) c.Field = append(c.Field, def)
c.nextKey++ c.nextKey++
@ -70,12 +150,14 @@ func (c *CustomFieldsManager) AddCustomFieldDef(req *types.AddCustomFieldDef) so
func (c *CustomFieldsManager) RemoveCustomFieldDef(req *types.RemoveCustomFieldDef) soap.HasFault { func (c *CustomFieldsManager) RemoveCustomFieldDef(req *types.RemoveCustomFieldDef) soap.HasFault {
body := &methods.RemoveCustomFieldDefBody{} body := &methods.RemoveCustomFieldDefBody{}
i, field := c.find(req.Key) i, field := c.findByKey(req.Key)
if field == nil { if field == nil {
body.Fault_ = Fault("", &types.NotFound{}) body.Fault_ = Fault("", &types.NotFound{})
return body return body
} }
entitiesFieldRemove(*field)
c.Field = append(c.Field[:i], c.Field[i+1:]...) c.Field = append(c.Field[:i], c.Field[i+1:]...)
body.Res = &types.RemoveCustomFieldDefResponse{} body.Res = &types.RemoveCustomFieldDefResponse{}
@ -85,7 +167,7 @@ func (c *CustomFieldsManager) RemoveCustomFieldDef(req *types.RemoveCustomFieldD
func (c *CustomFieldsManager) RenameCustomFieldDef(req *types.RenameCustomFieldDef) soap.HasFault { func (c *CustomFieldsManager) RenameCustomFieldDef(req *types.RenameCustomFieldDef) soap.HasFault {
body := &methods.RenameCustomFieldDefBody{} body := &methods.RenameCustomFieldDefBody{}
_, field := c.find(req.Key) _, field := c.findByKey(req.Key)
if field == nil { if field == nil {
body.Fault_ = Fault("", &types.NotFound{}) body.Fault_ = Fault("", &types.NotFound{})
return body return body
@ -93,19 +175,30 @@ func (c *CustomFieldsManager) RenameCustomFieldDef(req *types.RenameCustomFieldD
field.Name = req.Name field.Name = req.Name
entitiesFieldRename(*field)
body.Res = &types.RenameCustomFieldDefResponse{} body.Res = &types.RenameCustomFieldDefResponse{}
return body return body
} }
func (c *CustomFieldsManager) SetField(req *types.SetField) soap.HasFault { func (c *CustomFieldsManager) SetField(ctx *Context, req *types.SetField) soap.HasFault {
body := &methods.SetFieldBody{} body := &methods.SetFieldBody{}
entity := Map.Get(req.Entity).(mo.Entity).Entity() _, field := c.findByKey(req.Key)
Map.WithLock(entity, func() { if field == nil {
entity.CustomValue = append(entity.CustomValue, &types.CustomFieldStringValue{ body.Fault_ = Fault("", &types.InvalidArgument{InvalidProperty: "key"})
return body
}
newValue := &types.CustomFieldStringValue{
CustomFieldValue: types.CustomFieldValue{Key: req.Key}, CustomFieldValue: types.CustomFieldValue{Key: req.Key},
Value: req.Value, Value: req.Value,
}) }
entity := Map.Get(req.Entity).(mo.Entity).Entity()
ctx.WithLock(entity, func() {
entity.CustomValue = append(entity.CustomValue, newValue)
entity.Value = append(entity.Value, newValue)
}) })
body.Res = &types.SetFieldResponse{} body.Res = &types.SetFieldResponse{}

View File

@ -31,6 +31,9 @@ func (s *DistributedVirtualSwitch) AddDVPortgroupTask(c *types.AddDVPortgroup_Ta
task := CreateTask(s, "addDVPortgroup", func(t *Task) (types.AnyType, types.BaseMethodFault) { task := CreateTask(s, "addDVPortgroup", func(t *Task) (types.AnyType, types.BaseMethodFault) {
f := Map.getEntityParent(s, "Folder").(*Folder) f := Map.getEntityParent(s, "Folder").(*Folder)
portgroups := s.Portgroup
portgroupNames := s.Summary.PortgroupName
for _, spec := range c.Spec { for _, spec := range c.Spec {
pg := &DistributedVirtualPortgroup{} pg := &DistributedVirtualPortgroup{}
pg.Name = spec.Name pg.Name = spec.Name
@ -71,17 +74,28 @@ func (s *DistributedVirtualSwitch) AddDVPortgroupTask(c *types.AddDVPortgroup_Ta
pg.PortKeys = []string{} pg.PortKeys = []string{}
s.Portgroup = append(s.Portgroup, pg.Self) portgroups = append(portgroups, pg.Self)
s.Summary.PortgroupName = append(s.Summary.PortgroupName, pg.Name) portgroupNames = append(portgroupNames, pg.Name)
for _, h := range s.Summary.HostMember { for _, h := range s.Summary.HostMember {
pg.Host = append(pg.Host, h) pg.Host = append(pg.Host, h)
host := Map.Get(h).(*HostSystem) host := Map.Get(h).(*HostSystem)
Map.AppendReference(host, &host.Network, pg.Reference()) Map.AppendReference(host, &host.Network, pg.Reference())
parent := Map.Get(*host.HostSystem.Parent)
computeNetworks := append(hostParent(&host.HostSystem).Network, pg.Reference())
Map.Update(parent, []types.PropertyChange{
{Name: "network", Val: computeNetworks},
})
} }
} }
Map.Update(s, []types.PropertyChange{
{Name: "portgroup", Val: portgroups},
{Name: "summary.portgroupName", Val: portgroupNames},
})
return nil, nil return nil, nil
}) })
@ -96,6 +110,8 @@ func (s *DistributedVirtualSwitch) ReconfigureDvsTask(req *types.ReconfigureDvs_
task := CreateTask(s, "reconfigureDvs", func(t *Task) (types.AnyType, types.BaseMethodFault) { task := CreateTask(s, "reconfigureDvs", func(t *Task) (types.AnyType, types.BaseMethodFault) {
spec := req.Spec.GetDVSConfigSpec() spec := req.Spec.GetDVSConfigSpec()
members := s.Summary.HostMember
for _, member := range spec.Host { for _, member := range spec.Host {
h := Map.Get(member.Host) h := Map.Get(member.Host)
if h == nil { if h == nil {
@ -110,13 +126,27 @@ func (s *DistributedVirtualSwitch) ReconfigureDvsTask(req *types.ReconfigureDvs_
return nil, &types.AlreadyExists{Name: host.Name} return nil, &types.AlreadyExists{Name: host.Name}
} }
Map.AppendReference(host, &host.Network, s.Portgroup...) hostNetworks := append(host.Network, s.Portgroup...)
s.Summary.HostMember = append(s.Summary.HostMember, member.Host) Map.Update(host, []types.PropertyChange{
{Name: "network", Val: hostNetworks},
})
members = append(members, member.Host)
parent := Map.Get(*host.HostSystem.Parent)
var pgs []types.ManagedObjectReference
for _, ref := range s.Portgroup { for _, ref := range s.Portgroup {
pg := Map.Get(ref).(*DistributedVirtualPortgroup) pg := Map.Get(ref).(*DistributedVirtualPortgroup)
Map.AddReference(pg, &pg.Host, member.Host) pgs = append(pgs, ref)
pgHosts := append(pg.Host, member.Host)
Map.Update(pg, []types.PropertyChange{
{Name: "host", Val: pgHosts},
})
} }
Map.Update(parent, []types.PropertyChange{
{Name: "network", Val: pgs},
})
case types.ConfigSpecOperationRemove: case types.ConfigSpecOperationRemove:
for _, ref := range host.Vm { for _, ref := range host.Vm {
vm := Map.Get(ref).(*VirtualMachine) vm := Map.Get(ref).(*VirtualMachine)
@ -128,12 +158,16 @@ func (s *DistributedVirtualSwitch) ReconfigureDvsTask(req *types.ReconfigureDvs_
} }
} }
RemoveReference(&s.Summary.HostMember, member.Host) RemoveReference(&members, member.Host)
case types.ConfigSpecOperationEdit: case types.ConfigSpecOperationEdit:
return nil, &types.NotSupported{} return nil, &types.NotSupported{}
} }
} }
Map.Update(s, []types.PropertyChange{
{Name: "summary.hostMember", Val: members},
})
return nil, nil return nil, nil
}) })

View File

@ -33,7 +33,7 @@ func RenameTask(e mo.Entity, r *types.Rename_Task) soap.HasFault {
} }
} }
obj.Name = r.NewName Map.Update(e, []types.PropertyChange{{Name: "name", Val: r.NewName}})
return nil, nil return nil, nil
}) })

View File

@ -0,0 +1,65 @@
/*
Copyright (c) 2019 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 simulator
import (
"github.com/vmware/govmomi/simulator/esx"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
)
type EnvironmentBrowser struct {
mo.EnvironmentBrowser
}
func newEnvironmentBrowser() *types.ManagedObjectReference {
env := new(EnvironmentBrowser)
Map.Put(env)
return &env.Self
}
func (b *EnvironmentBrowser) QueryConfigOption(req *types.QueryConfigOption) soap.HasFault {
body := new(methods.QueryConfigOptionBody)
opt := &types.VirtualMachineConfigOption{
Version: esx.HardwareVersion,
DefaultDevice: esx.VirtualDevice,
}
body.Res = &types.QueryConfigOptionResponse{
Returnval: opt,
}
return body
}
func (b *EnvironmentBrowser) QueryConfigOptionEx(req *types.QueryConfigOptionEx) soap.HasFault {
body := new(methods.QueryConfigOptionExBody)
opt := &types.VirtualMachineConfigOption{
Version: esx.HardwareVersion,
DefaultDevice: esx.VirtualDevice,
}
body.Res = &types.QueryConfigOptionExResponse{
Returnval: opt,
}
return body
}

View File

@ -13,10 +13,12 @@ go_library(
"host_storage_device_info.go", "host_storage_device_info.go",
"host_system.go", "host_system.go",
"performance_manager.go", "performance_manager.go",
"performance_manager_data.go",
"resource_pool.go", "resource_pool.go",
"root_folder.go", "root_folder.go",
"service_content.go", "service_content.go",
"setting.go", "setting.go",
"task_manager.go",
"virtual_device.go", "virtual_device.go",
], ],
importmap = "k8s.io/kubernetes/vendor/github.com/vmware/govmomi/simulator/esx", importmap = "k8s.io/kubernetes/vendor/github.com/vmware/govmomi/simulator/esx",

View File

@ -77,6 +77,12 @@ var EventInfo = []types.EventDescriptionEventDetail{
Category: "info", Category: "info",
FullFormat: "Host {{.Host.Name}} in {{.Datacenter.Name}} has exited maintenance mode", FullFormat: "Host {{.Host.Name}} in {{.Datacenter.Name}} has exited maintenance mode",
}, },
{
Key: "HostRemovedEvent",
Description: "Host removed",
FullFormat: "Removed host {{.Host.Name}} in {{.Datacenter.Name}}",
Category: "info",
},
{ {
Key: "VmSuspendedEvent", Key: "VmSuspendedEvent",
Description: "VM suspended", Description: "VM suspended",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,9 @@ package esx
import "github.com/vmware/govmomi/vim25/types" import "github.com/vmware/govmomi/vim25/types"
// HardwareVersion is the default VirtualMachine.Config.Version
var HardwareVersion = "vmx-13"
// Setting is captured from ESX's HostSystem.configManager.advancedOption // Setting is captured from ESX's HostSystem.configManager.advancedOption
// Capture method: // Capture method:
// govc object.collect -s -dump $(govc object.collect -s HostSystem:ha-host configManager.advancedOption) setting // govc object.collect -s -dump $(govc object.collect -s HostSystem:ha-host configManager.advancedOption) setting

File diff suppressed because it is too large Load Diff

View File

@ -138,11 +138,13 @@ func (m *EventManager) formatMessage(event types.BaseEvent) {
} }
} }
if t != nil {
var buf bytes.Buffer var buf bytes.Buffer
if err := t.Execute(&buf, event); err != nil { if err := t.Execute(&buf, event); err != nil {
log.Print(err) log.Print(err)
} }
e.FullFormattedMessage = buf.String() e.FullFormattedMessage = buf.String()
}
if logEvents { if logEvents {
log.Printf("[%s] %s", id, e.FullFormattedMessage) log.Printf("[%s] %s", id, e.FullFormattedMessage)
@ -181,6 +183,7 @@ type EventHistoryCollector struct {
m *EventManager m *EventManager
page *ring.Ring page *ring.Ring
pos int
} }
// doEntityEventArgument calls f for each entity argument in the event. // doEntityEventArgument calls f for each entity argument in the event.
@ -328,6 +331,7 @@ func (c *EventHistoryCollector) eventMatches(event types.BaseEvent) bool {
// filePage copies the manager's latest events into the collector's page with Filter applied. // filePage copies the manager's latest events into the collector's page with Filter applied.
func (c *EventHistoryCollector) fillPage(size int) { func (c *EventHistoryCollector) fillPage(size int) {
c.pos = 0
l := c.page.Len() l := c.page.Len()
delta := size - l delta := size - l
@ -392,6 +396,66 @@ func (c *EventHistoryCollector) SetCollectorPageSize(ctx *Context, req *types.Se
return body return body
} }
func (c *EventHistoryCollector) RewindCollector(ctx *Context, req *types.RewindCollector) soap.HasFault {
c.pos = 0
return &methods.RewindCollectorBody{
Res: new(types.RewindCollectorResponse),
}
}
func (c *EventHistoryCollector) ReadNextEvents(ctx *Context, req *types.ReadNextEvents) soap.HasFault {
body := &methods.ReadNextEventsBody{}
if req.MaxCount <= 0 {
body.Fault_ = Fault("", &types.InvalidArgument{InvalidProperty: "maxCount"})
return body
}
body.Res = new(types.ReadNextEventsResponse)
events := c.GetLatestPage()
nevents := len(events)
if c.pos == nevents {
return body // already read to EOF
}
start := c.pos
end := start + int(req.MaxCount)
c.pos += int(req.MaxCount)
if end > nevents {
end = nevents
c.pos = nevents
}
body.Res.Returnval = events[start:end]
return body
}
func (c *EventHistoryCollector) ReadPreviousEvents(ctx *Context, req *types.ReadPreviousEvents) soap.HasFault {
body := &methods.ReadPreviousEventsBody{}
if req.MaxCount <= 0 {
body.Fault_ = Fault("", &types.InvalidArgument{InvalidProperty: "maxCount"})
return body
}
body.Res = new(types.ReadPreviousEventsResponse)
events := c.GetLatestPage()
if c.pos == 0 {
return body // already read to EOF
}
start := c.pos - int(req.MaxCount)
end := c.pos
c.pos -= int(req.MaxCount)
if start < 0 {
start = 0
c.pos = 0
}
body.Res.Returnval = events[start:end]
return body
}
func (c *EventHistoryCollector) DestroyCollector(ctx *Context, req *types.DestroyCollector) soap.HasFault { func (c *EventHistoryCollector) DestroyCollector(ctx *Context, req *types.DestroyCollector) soap.HasFault {
ctx.Session.Remove(req.This) ctx.Session.Remove(req.This)

View File

@ -178,7 +178,7 @@ func (f *FileManager) moveDatastoreFile(req *types.MoveDatastoreFile_Task) types
if !isTrue(req.Force) { if !isTrue(req.Force) {
_, err := os.Stat(dst) _, err := os.Stat(dst)
if err == nil { if err == nil {
return f.fault(dst, nil, new(types.FileAlreadyExistsFault)) return f.fault(dst, nil, new(types.FileAlreadyExists))
} }
} }
@ -216,7 +216,7 @@ func (f *FileManager) copyDatastoreFile(req *types.CopyDatastoreFile_Task) types
if !isTrue(req.Force) { if !isTrue(req.Force) {
_, err := os.Stat(dst) _, err := os.Stat(dst)
if err == nil { if err == nil {
return f.fault(dst, nil, new(types.FileAlreadyExistsFault)) return f.fault(dst, nil, new(types.FileAlreadyExists))
} }
} }

View File

@ -23,7 +23,7 @@ import (
"strings" "strings"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/vmware/govmomi/object"
"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"
@ -177,6 +177,9 @@ func (f *Folder) CreateStoragePod(c *types.CreateStoragePod) soap.HasFault {
pod.Name = c.Name pod.Name = c.Name
pod.ChildType = []string{"Datastore"} pod.ChildType = []string{"Datastore"}
pod.Summary = new(types.StoragePodSummary)
pod.PodStorageDrsEntry = new(types.PodStorageDrsEntry)
pod.PodStorageDrsEntry.StorageDrsConfig.PodConfig.Enabled = true
f.putChild(pod) f.putChild(pod)
@ -191,7 +194,10 @@ func (f *Folder) CreateStoragePod(c *types.CreateStoragePod) soap.HasFault {
} }
func (p *StoragePod) MoveIntoFolderTask(c *types.MoveIntoFolder_Task) soap.HasFault { func (p *StoragePod) MoveIntoFolderTask(c *types.MoveIntoFolder_Task) soap.HasFault {
return (&Folder{Folder: p.Folder}).MoveIntoFolderTask(c) f := &Folder{Folder: p.Folder}
res := f.MoveIntoFolderTask(c)
p.ChildEntity = append(p.ChildEntity, f.ChildEntity...)
return res
} }
func (f *Folder) CreateDatacenter(ctx *Context, c *types.CreateDatacenter) soap.HasFault { func (f *Folder) CreateDatacenter(ctx *Context, c *types.CreateDatacenter) soap.HasFault {
@ -250,9 +256,26 @@ type createVM struct {
register bool register bool
} }
// hostsWithDatastore returns hosts that have access to the given datastore path
func hostsWithDatastore(hosts []types.ManagedObjectReference, path string) []types.ManagedObjectReference {
attached := hosts[:0]
var p object.DatastorePath
p.FromString(path)
for _, host := range hosts {
h := Map.Get(host).(*HostSystem)
if Map.FindByName(p.Datastore, h.Datastore) != nil {
attached = append(attached, host)
}
}
return attached
}
func (c *createVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) { func (c *createVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
vm, err := NewVirtualMachine(c.Folder.Self, &c.req.Config) vm, err := NewVirtualMachine(c.Folder.Self, &c.req.Config)
if err != nil { if err != nil {
c.Folder.removeChild(vm)
return nil, err return nil, err
} }
@ -270,7 +293,7 @@ func (c *createVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
hosts = cr.Host hosts = cr.Host
} }
// Assuming for now that all hosts have access to the datastore hosts = hostsWithDatastore(hosts, c.req.Config.Files.VmPathName)
host := hosts[rand.Intn(len(hosts))] host := hosts[rand.Intn(len(hosts))]
vm.Runtime.Host = &host vm.Runtime.Host = &host
} else { } else {
@ -290,13 +313,13 @@ func (c *createVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
err = vm.create(&c.req.Config, c.register) err = vm.create(&c.req.Config, c.register)
if err != nil { if err != nil {
c.Folder.removeChild(vm)
return nil, err return nil, err
} }
c.Folder.putChild(vm)
host := Map.Get(*vm.Runtime.Host).(*HostSystem) host := Map.Get(*vm.Runtime.Host).(*HostSystem)
Map.AppendReference(host, &host.Vm, vm.Self) Map.AppendReference(host, &host.Vm, vm.Self)
vm.EnvironmentBrowser = *hostParent(&host.HostSystem).EnvironmentBrowser
for i := range vm.Datastore { for i := range vm.Datastore {
ds := Map.Get(vm.Datastore[i]).(*Datastore) ds := Map.Get(vm.Datastore[i]).(*Datastore)
@ -333,6 +356,8 @@ func (c *createVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
}, },
) )
vm.RefreshStorageInfo(c.ctx, nil)
return vm.Reference(), nil return vm.Reference(), nil
} }

View File

@ -187,7 +187,7 @@ func (s *searchDatastore) search(ds *types.ManagedObjectReference, folder string
return nil return nil
} }
func (s *searchDatastore) Run(Task *Task) (types.AnyType, types.BaseMethodFault) { func (s *searchDatastore) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
p, fault := parseDatastorePath(s.DatastorePath) p, fault := parseDatastorePath(s.DatastorePath)
if fault != nil { if fault != nil {
return nil, fault return nil, fault
@ -199,6 +199,8 @@ func (s *searchDatastore) Run(Task *Task) (types.AnyType, types.BaseMethodFault)
} }
ds := ref.(*Datastore) ds := ref.(*Datastore)
task.Info.Entity = &ds.Self // TODO: CreateTask() should require mo.Entity, rather than mo.Reference
task.Info.EntityName = ds.Name
dir := path.Join(ds.Info.GetDatastoreInfo().Url, p.Path) dir := path.Join(ds.Info.GetDatastoreInfo().Url, p.Path)

View File

@ -17,6 +17,7 @@ limitations under the License.
package simulator package simulator
import ( import (
"os"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
@ -27,11 +28,21 @@ import (
"github.com/vmware/govmomi/vim25/types" "github.com/vmware/govmomi/vim25/types"
) )
var (
hostPortUnique = os.Getenv("VCSIM_HOST_PORT_UNIQUE") == "true"
)
type HostSystem struct { type HostSystem struct {
mo.HostSystem mo.HostSystem
} }
func NewHostSystem(host mo.HostSystem) *HostSystem { func NewHostSystem(host mo.HostSystem) *HostSystem {
if hostPortUnique { // configure unique port for each host
port := &esx.HostSystem.Summary.Config.Port
*port++
host.Summary.Config.Port = *port
}
now := time.Now() now := time.Now()
hs := &HostSystem{ hs := &HostSystem{
@ -71,6 +82,16 @@ func NewHostSystem(host mo.HostSystem) *HostSystem {
return hs return hs
} }
func (h *HostSystem) event() types.HostEvent {
return types.HostEvent{
Event: types.Event{
Datacenter: datacenterEventArgument(h),
ComputeResource: h.eventArgumentParent(),
Host: h.eventArgument(),
},
}
}
func (h *HostSystem) eventArgument() *types.HostEventArgument { func (h *HostSystem) eventArgument() *types.HostEventArgument {
return &types.HostEventArgument{ return &types.HostEventArgument{
Host: h.Self, Host: h.Self,
@ -121,6 +142,7 @@ func CreateDefaultESX(f *Folder) {
addComputeResource(summary, host) addComputeResource(summary, host)
cr := &mo.ComputeResource{Summary: summary} cr := &mo.ComputeResource{Summary: summary}
cr.EnvironmentBrowser = newEnvironmentBrowser()
cr.Self = *host.Parent cr.Self = *host.Parent
cr.Name = host.Name cr.Name = host.Name
cr.Host = append(cr.Host, host.Reference()) cr.Host = append(cr.Host, host.Reference())
@ -156,6 +178,7 @@ func CreateStandaloneHost(f *Folder, spec types.HostConnectSpec) (*HostSystem, t
VmSwapPlacement: string(types.VirtualMachineConfigInfoSwapPlacementTypeVmDirectory), VmSwapPlacement: string(types.VirtualMachineConfigInfoSwapPlacementTypeVmDirectory),
}, },
Summary: summary, Summary: summary,
EnvironmentBrowser: newEnvironmentBrowser(),
} }
Map.PutEntity(cr, Map.NewEntity(host)) Map.PutEntity(cr, Map.NewEntity(host))
@ -173,12 +196,14 @@ func CreateStandaloneHost(f *Folder, spec types.HostConnectSpec) (*HostSystem, t
return host, nil return host, nil
} }
func (h *HostSystem) DestroyTask(req *types.Destroy_Task) soap.HasFault { func (h *HostSystem) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFault {
task := CreateTask(h, "destroy", func(t *Task) (types.AnyType, types.BaseMethodFault) { task := CreateTask(h, "destroy", func(t *Task) (types.AnyType, types.BaseMethodFault) {
if len(h.Vm) > 0 { if len(h.Vm) > 0 {
return nil, &types.ResourceInUse{} return nil, &types.ResourceInUse{}
} }
ctx.postEvent(&types.HostRemovedEvent{HostEvent: h.event()})
f := Map.getEntityParent(h, "Folder").(*Folder) f := Map.getEntityParent(h, "Folder").(*Folder)
f.removeChild(h.Reference()) f.removeChild(h.Reference())

View File

@ -113,6 +113,43 @@ func (m *LicenseManager) RemoveLicense(req *types.RemoveLicense) soap.HasFault {
return body return body
} }
func (m *LicenseManager) UpdateLicenseLabel(req *types.UpdateLicenseLabel) soap.HasFault {
body := &methods.UpdateLicenseLabelBody{}
for i := range m.Licenses {
license := &m.Licenses[i]
if req.LicenseKey != license.LicenseKey {
continue
}
body.Res = new(types.UpdateLicenseLabelResponse)
for j := range license.Labels {
label := &license.Labels[j]
if label.Key == req.LabelKey {
if req.LabelValue == "" {
license.Labels = append(license.Labels[:i], license.Labels[i+1:]...)
} else {
label.Value = req.LabelValue
}
return body
}
}
license.Labels = append(license.Labels, types.KeyValue{
Key: req.LabelKey,
Value: req.LabelValue,
})
return body
}
body.Fault_ = Fault("", &types.InvalidArgument{InvalidProperty: "licenseKey"})
return body
}
type LicenseAssignmentManager struct { type LicenseAssignmentManager struct {
mo.LicenseAssignmentManager mo.LicenseAssignmentManager
} }

View File

@ -30,6 +30,21 @@ import (
"github.com/vmware/govmomi/vim25/types" "github.com/vmware/govmomi/vim25/types"
) )
type DelayConfig struct {
// Delay specifies the number of milliseconds to delay serving a SOAP call. 0 means no delay.
// This can be used to simulate a poorly performing vCenter or network lag.
Delay int
// Delay specifies the number of milliseconds to delay serving a specific method.
// Each entry in the map represents the name of a method and its associated delay in milliseconds,
// This can be used to simulate a poorly performing vCenter or network lag.
MethodDelay map[string]int
// DelayJitter defines the delay jitter as a coefficient of variation (stddev/mean).
// This can be used to simulate unpredictable delay. 0 means no jitter, i.e. all invocations get the same delay.
DelayJitter float64
}
// Model is used to populate a Model with an initial set of managed entities. // Model is used to populate a Model with an initial set of managed entities.
// This is a simple helper for tests running against a simulator, to populate an inventory // This is a simple helper for tests running against a simulator, to populate an inventory
// with commonly used models. // with commonly used models.
@ -79,6 +94,9 @@ type Model struct {
// Pod specifies the number of StoragePod to create per Cluster // Pod specifies the number of StoragePod to create per Cluster
Pod int Pod int
// Delay configurations
DelayConfig DelayConfig
// total number of inventory objects, set by Count() // total number of inventory objects, set by Count()
total int total int
@ -93,6 +111,11 @@ func ESX() *Model {
Autostart: true, Autostart: true,
Datastore: 1, Datastore: 1,
Machine: 2, Machine: 2,
DelayConfig: DelayConfig{
Delay: 0,
DelayJitter: 0,
MethodDelay: nil,
},
} }
} }
@ -109,6 +132,11 @@ func VPX() *Model {
ClusterHost: 3, ClusterHost: 3,
Datastore: 1, Datastore: 1,
Machine: 2, Machine: 2,
DelayConfig: DelayConfig{
Delay: 0,
DelayJitter: 0,
MethodDelay: nil,
},
} }
} }
@ -454,6 +482,9 @@ func (m *Model) Create() error {
} }
} }
// Turn on delay AFTER we're done building the service content
m.Service.delay = &m.DelayConfig
return nil return nil
} }

51
vendor/github.com/vmware/govmomi/simulator/object.go generated vendored Normal file
View File

@ -0,0 +1,51 @@
/*
Copyright (c) 2017-2018 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 simulator
import (
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
)
func SetCustomValue(ctx *Context, req *types.SetCustomValue) soap.HasFault {
ctx.Caller = &req.This
body := &methods.SetCustomValueBody{}
cfm := Map.CustomFieldsManager()
_, field := cfm.findByNameType(req.Key, req.This.Type)
if field == nil {
body.Fault_ = Fault("", &types.InvalidArgument{InvalidProperty: "key"})
return body
}
res := cfm.SetField(ctx, &types.SetField{
This: cfm.Reference(),
Entity: req.This,
Key: field.Key,
Value: req.Value,
})
if res.Fault() != nil {
body.Fault_ = res.Fault()
return body
}
body.Res = &types.SetCustomValueResponse{}
return body
}

View File

@ -17,19 +17,243 @@ limitations under the License.
package simulator package simulator
import ( import (
"math/rand"
"strconv"
"time"
"github.com/vmware/govmomi/object" "github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/simulator/esx" "github.com/vmware/govmomi/simulator/esx"
"github.com/vmware/govmomi/simulator/vpx"
"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/types" "github.com/vmware/govmomi/vim25/types"
) )
var realtimeProviderSummary = types.PerfProviderSummary{
CurrentSupported: true,
SummarySupported: true,
RefreshRate: 20,
}
var historicProviderSummary = types.PerfProviderSummary{
CurrentSupported: false,
SummarySupported: true,
RefreshRate: -1,
}
type PerformanceManager struct { type PerformanceManager struct {
mo.PerformanceManager mo.PerformanceManager
vmMetrics []types.PerfMetricId
hostMetrics []types.PerfMetricId
rpMetrics []types.PerfMetricId
clusterMetrics []types.PerfMetricId
datastoreMetrics []types.PerfMetricId
datacenterMetrics []types.PerfMetricId
perfCounterIndex map[int32]types.PerfCounterInfo
metricData map[string]map[int32][]int64
} }
func NewPerformanceManager(ref types.ManagedObjectReference) object.Reference { func NewPerformanceManager(ref types.ManagedObjectReference) object.Reference {
m := &PerformanceManager{} m := &PerformanceManager{}
m.Self = ref m.Self = ref
m.PerfCounter = esx.PerfCounter if Map.IsESX() {
m.PerfCounter = esx.PerfCounter[:]
m.hostMetrics = esx.HostMetrics[:]
m.vmMetrics = esx.VmMetrics[:]
m.rpMetrics = esx.ResourcePoolMetrics[:]
m.metricData = esx.MetricData
} else {
m.PerfCounter = vpx.PerfCounter[:]
m.hostMetrics = vpx.HostMetrics[:]
m.vmMetrics = vpx.VmMetrics[:]
m.rpMetrics = vpx.ResourcePoolMetrics[:]
m.clusterMetrics = vpx.ClusterMetrics[:]
m.datastoreMetrics = vpx.DatastoreMetrics[:]
m.datacenterMetrics = vpx.DatacenterMetrics[:]
m.metricData = vpx.MetricData
}
m.perfCounterIndex = make(map[int32]types.PerfCounterInfo, len(m.PerfCounter))
for _, p := range m.PerfCounter {
m.perfCounterIndex[p.Key] = p
}
return m return m
} }
func (p *PerformanceManager) QueryPerfCounter(ctx *Context, req *types.QueryPerfCounter) soap.HasFault {
body := new(methods.QueryPerfCounterBody)
body.Req = req
body.Res.Returnval = make([]types.PerfCounterInfo, len(req.CounterId))
for i, id := range req.CounterId {
if info, ok := p.perfCounterIndex[id]; !ok {
body.Fault_ = Fault("", &types.InvalidArgument{
InvalidProperty: "CounterId",
})
return body
} else {
body.Res.Returnval[i] = info
}
}
return body
}
func (p *PerformanceManager) QueryPerfProviderSummary(ctx *Context, req *types.QueryPerfProviderSummary) soap.HasFault {
body := new(methods.QueryPerfProviderSummaryBody)
body.Req = req
body.Res = new(types.QueryPerfProviderSummaryResponse)
// The entity must exist
if Map.Get(req.Entity) == nil {
body.Fault_ = Fault("", &types.InvalidArgument{
InvalidProperty: "Entity",
})
return body
}
switch req.Entity.Type {
case "VirtualMachine", "HostSystem", "ResourcePool":
body.Res.Returnval = realtimeProviderSummary
default:
body.Res.Returnval = historicProviderSummary
}
body.Res.Returnval.Entity = req.Entity
return body
}
func (p *PerformanceManager) buildAvailablePerfMetricsQueryResponse(ids []types.PerfMetricId, numCPU int, datastoreURL string) *types.QueryAvailablePerfMetricResponse {
r := new(types.QueryAvailablePerfMetricResponse)
r.Returnval = make([]types.PerfMetricId, 0, len(ids))
for _, id := range ids {
switch id.Instance {
case "$cpu":
for i := 0; i < numCPU; i++ {
r.Returnval = append(r.Returnval, types.PerfMetricId{CounterId: id.CounterId, Instance: strconv.Itoa(i)})
}
case "$physDisk":
r.Returnval = append(r.Returnval, types.PerfMetricId{CounterId: id.CounterId, Instance: datastoreURL})
case "$file":
r.Returnval = append(r.Returnval, types.PerfMetricId{CounterId: id.CounterId, Instance: "DISKFILE"})
r.Returnval = append(r.Returnval, types.PerfMetricId{CounterId: id.CounterId, Instance: "DELTAFILE"})
r.Returnval = append(r.Returnval, types.PerfMetricId{CounterId: id.CounterId, Instance: "SWAPFILE"})
r.Returnval = append(r.Returnval, types.PerfMetricId{CounterId: id.CounterId, Instance: "OTHERFILE"})
default:
r.Returnval = append(r.Returnval, types.PerfMetricId{CounterId: id.CounterId, Instance: id.Instance})
}
}
return r
}
func (p *PerformanceManager) queryAvailablePerfMetric(entity types.ManagedObjectReference, interval int32) *types.QueryAvailablePerfMetricResponse {
switch entity.Type {
case "VirtualMachine":
vm := Map.Get(entity).(*VirtualMachine)
return p.buildAvailablePerfMetricsQueryResponse(p.vmMetrics, int(vm.Summary.Config.NumCpu), vm.Datastore[0].Value)
case "HostSystem":
host := Map.Get(entity).(*HostSystem)
return p.buildAvailablePerfMetricsQueryResponse(p.hostMetrics, int(host.Hardware.CpuInfo.NumCpuThreads), host.Datastore[0].Value)
case "ResourcePool":
return p.buildAvailablePerfMetricsQueryResponse(p.rpMetrics, 0, "")
case "ClusterComputeResource":
if interval != 20 {
return p.buildAvailablePerfMetricsQueryResponse(p.clusterMetrics, 0, "")
}
case "Datastore":
if interval != 20 {
return p.buildAvailablePerfMetricsQueryResponse(p.datastoreMetrics, 0, "")
}
case "Datacenter":
if interval != 20 {
return p.buildAvailablePerfMetricsQueryResponse(p.datacenterMetrics, 0, "")
}
}
// Don't know how to handle this. Return empty response.
return new(types.QueryAvailablePerfMetricResponse)
}
func (p *PerformanceManager) QueryAvailablePerfMetric(ctx *Context, req *types.QueryAvailablePerfMetric) soap.HasFault {
body := new(methods.QueryAvailablePerfMetricBody)
body.Req = req
body.Res = p.queryAvailablePerfMetric(req.Entity, req.IntervalId)
return body
}
func (p *PerformanceManager) QueryPerf(ctx *Context, req *types.QueryPerf) soap.HasFault {
body := new(methods.QueryPerfBody)
body.Req = req
body.Res = new(types.QueryPerfResponse)
body.Res.Returnval = make([]types.BasePerfEntityMetricBase, len(req.QuerySpec))
for i, qs := range req.QuerySpec {
metrics := new(types.PerfEntityMetric)
metrics.Entity = qs.Entity
// Get metric data for this entity type
metricData, ok := p.metricData[qs.Entity.Type]
if !ok {
body.Fault_ = Fault("", &types.InvalidArgument{
InvalidProperty: "Entity",
})
}
var start, end time.Time
if qs.StartTime == nil {
start = time.Now().Add(time.Duration(-365*24) * time.Hour) // Assume we have data for a year
} else {
start = *qs.StartTime
}
if qs.EndTime == nil {
end = time.Now()
} else {
end = *qs.EndTime
}
// Generate metric series. Divide into n buckets of interval seconds
interval := qs.IntervalId
if interval == -1 || interval == 0 {
interval = 20 // TODO: Determine from entity type
}
n := 1 + int32(end.Sub(start).Seconds())/interval
if n > qs.MaxSample {
n = qs.MaxSample
}
// Loop through each interval "tick"
metrics.SampleInfo = make([]types.PerfSampleInfo, n)
metrics.Value = make([]types.BasePerfMetricSeries, len(qs.MetricId))
for tick := int32(0); tick < n; tick++ {
metrics.SampleInfo[tick] = types.PerfSampleInfo{Timestamp: end.Add(time.Duration(-interval*tick) * time.Second), Interval: interval}
}
for j, mid := range qs.MetricId {
// Create list of metrics for this tick
series := &types.PerfMetricIntSeries{Value: make([]int64, n)}
series.Id = mid
points := metricData[mid.CounterId]
offset := int64(start.Unix()) / int64(interval)
for tick := int32(0); tick < n; tick++ {
var p int64
// Use sample data if we have it. Otherwise, just send 0.
if len(points) > 0 {
p = points[(offset+int64(tick))%int64(len(points))]
scale := p / 5
if scale > 0 {
// Add some gaussian noise to make the data look more "real"
p += int64(rand.NormFloat64() * float64(scale))
if p < 0 {
p = 0
}
}
} else {
p = 0
}
series.Value[tick] = p
}
metrics.Value[j] = series
}
body.Res.Returnval[i] = metrics
}
return body
}

View File

@ -337,6 +337,7 @@ func (rr *retrieveResult) collect(ctx *Context, ref types.ManagedObjectReference
} }
rtype := rval.Type() rtype := rval.Type()
match := false
for _, spec := range rr.req.SpecSet { for _, spec := range rr.req.SpecSet {
for _, p := range spec.PropSet { for _, p := range spec.PropSet {
@ -348,7 +349,7 @@ func (rr *retrieveResult) collect(ctx *Context, ref types.ManagedObjectReference
continue continue
} }
} }
match = true
if isTrue(p.All) { if isTrue(p.All) {
rr.collectAll(ctx, rval, rtype, &content) rr.collectAll(ctx, rval, rtype, &content)
continue continue
@ -358,7 +359,7 @@ func (rr *retrieveResult) collect(ctx *Context, ref types.ManagedObjectReference
} }
} }
if len(content.PropSet) != 0 || len(content.MissingSet) != 0 { if match {
rr.Objects = append(rr.Objects, content) rr.Objects = append(rr.Objects, content)
} }
@ -498,7 +499,12 @@ func (pc *PropertyCollector) RetrievePropertiesEx(ctx *Context, r *types.Retriev
res, fault := pc.collect(ctx, r) res, fault := pc.collect(ctx, r)
if fault != nil { if fault != nil {
switch fault.(type) {
case *types.ManagedObjectNotFound:
body.Fault_ = Fault("The object has already been deleted or has not been completely created", fault)
default:
body.Fault_ = Fault("", fault) body.Fault_ = Fault("", fault)
}
} else { } else {
objects := res.Objects[:0] objects := res.Objects[:0]
for _, o := range res.Objects { for _, o := range res.Objects {
@ -627,11 +633,16 @@ func (pc *PropertyCollector) apply(ctx *Context, update *types.UpdateSet) types.
func (pc *PropertyCollector) WaitForUpdatesEx(ctx *Context, r *types.WaitForUpdatesEx) soap.HasFault { func (pc *PropertyCollector) WaitForUpdatesEx(ctx *Context, r *types.WaitForUpdatesEx) soap.HasFault {
wait, cancel := context.WithCancel(context.Background()) wait, cancel := context.WithCancel(context.Background())
oneUpdate := false
if r.Options != nil { if r.Options != nil {
if max := r.Options.MaxWaitSeconds; max != nil { if max := r.Options.MaxWaitSeconds; max != nil {
// A value of 0 causes WaitForUpdatesEx to do one update calculation and return any results.
oneUpdate = (*max == 0)
if *max > 0 {
wait, cancel = context.WithTimeout(context.Background(), time.Second*time.Duration(*max)) wait, cancel = context.WithTimeout(context.Background(), time.Second*time.Duration(*max))
} }
} }
}
pc.mu.Lock() pc.mu.Lock()
pc.cancel = cancel pc.cancel = cancel
pc.mu.Unlock() pc.mu.Unlock()
@ -688,6 +699,10 @@ func (pc *PropertyCollector) WaitForUpdatesEx(ctx *Context, r *types.WaitForUpda
pc.updates = nil // clear updates collected by the managed object CRUD listeners pc.updates = nil // clear updates collected by the managed object CRUD listeners
pc.mu.Unlock() pc.mu.Unlock()
if len(updates) == 0 { if len(updates) == 0 {
if oneUpdate == true {
body.Res.Returnval = nil
return body
}
continue continue
} }
@ -732,6 +747,10 @@ func (pc *PropertyCollector) WaitForUpdatesEx(ctx *Context, r *types.WaitForUpda
if len(set.FilterSet) != 0 { if len(set.FilterSet) != 0 {
return body return body
} }
if oneUpdate == true {
body.Res.Returnval = nil
return body
}
} }
} }
} }

View File

@ -47,10 +47,19 @@ func (f *PropertyFilter) DestroyPropertyFilter(ctx *Context, c *types.DestroyPro
// matches returns true if the change matches one of the filter Spec.PropSet // 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 { func (f *PropertyFilter) matches(ctx *Context, ref types.ManagedObjectReference, change *types.PropertyChange) bool {
var kind reflect.Type
for _, p := range f.Spec.PropSet { for _, p := range f.Spec.PropSet {
if p.Type != ref.Type { if p.Type != ref.Type {
if kind == nil {
kind = getManagedObject(ctx.Map.Get(ref)).Type()
}
// e.g. ManagedEntity, ComputeResource
field, ok := kind.FieldByName(p.Type)
if !(ok && field.Anonymous) {
continue continue
} }
}
if isTrue(p.All) { if isTrue(p.All) {
return true return true

View File

@ -57,14 +57,24 @@ type RegisterObject interface {
// Registry manages a map of mo.Reference objects // Registry manages a map of mo.Reference objects
type Registry struct { type Registry struct {
counter int64 // Keep first to ensure 64-bit alignment
m sync.Mutex m sync.Mutex
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 int64
Namespace string Namespace string
Path string Path string
tagManager tagManager
}
// tagManager is an interface to simplify internal interaction with the vapi tag manager simulator.
type tagManager interface {
AttachedObjects(types.VslmTagEntry) ([]types.ManagedObjectReference, types.BaseMethodFault)
AttachedTags(id types.ManagedObjectReference) ([]types.VslmTagEntry, types.BaseMethodFault)
AttachTag(types.ManagedObjectReference, types.VslmTagEntry) types.BaseMethodFault
DetachTag(types.ManagedObjectReference, types.VslmTagEntry) types.BaseMethodFault
} }
// NewRegistry creates a new instances of Registry // NewRegistry creates a new instances of Registry
@ -177,6 +187,24 @@ func (r *Registry) Any(kind string) mo.Entity {
return nil return nil
} }
// All returns all entities of type specified by kind.
// If kind is empty - all entities will be returned.
func (r *Registry) All(kind string) []mo.Entity {
r.m.Lock()
defer r.m.Unlock()
var entities []mo.Entity
for ref, val := range r.objects {
if kind == "" || ref.Type == kind {
if e, ok := val.(mo.Entity); ok {
entities = append(entities, e)
}
}
}
return entities
}
// applyHandlers calls the given func for each r.handlers // applyHandlers calls the given func for each r.handlers
func (r *Registry) applyHandlers(f func(o RegisterObject)) { func (r *Registry) applyHandlers(f func(o RegisterObject)) {
r.m.Lock() r.m.Lock()
@ -443,6 +471,11 @@ func (r *Registry) OptionManager() *OptionManager {
return r.Get(r.content().Setting.Reference()).(*OptionManager) return r.Get(r.content().Setting.Reference()).(*OptionManager)
} }
// CustomFieldsManager returns CustomFieldsManager singleton
func (r *Registry) CustomFieldsManager() *CustomFieldsManager {
return r.Get(r.content().CustomFieldsManager.Reference()).(*CustomFieldsManager)
}
func (r *Registry) MarshalJSON() ([]byte, error) { func (r *Registry) MarshalJSON() ([]byte, error) {
r.m.Lock() r.m.Lock()
defer r.m.Unlock() defer r.m.Unlock()

View File

@ -163,3 +163,101 @@ func (s *SearchIndex) FindByUuid(req *types.FindByUuid) soap.HasFault {
return body return body
} }
func (s *SearchIndex) FindByDnsName(req *types.FindByDnsName) soap.HasFault {
body := &methods.FindByDnsNameBody{Res: new(types.FindByDnsNameResponse)}
all := types.FindAllByDnsName(*req)
switch r := s.FindAllByDnsName(&all).(type) {
case *methods.FindAllByDnsNameBody:
if len(r.Res.Returnval) > 0 {
body.Res.Returnval = &r.Res.Returnval[0]
}
default:
// no need until FindAllByDnsName below returns a Fault
}
return body
}
func (s *SearchIndex) FindAllByDnsName(req *types.FindAllByDnsName) soap.HasFault {
body := &methods.FindAllByDnsNameBody{Res: new(types.FindAllByDnsNameResponse)}
if req.VmSearch {
// Find Virtual Machine using DNS name
for ref, obj := range Map.objects {
vm, ok := obj.(*VirtualMachine)
if !ok {
continue
}
if vm.Guest.HostName == req.DnsName {
body.Res.Returnval = append(body.Res.Returnval, ref)
}
}
} else {
// Find Host System using DNS name
for ref, obj := range Map.objects {
host, ok := obj.(*HostSystem)
if !ok {
continue
}
for _, net := range host.Config.Network.NetStackInstance {
if net.DnsConfig.GetHostDnsConfig().HostName == req.DnsName {
body.Res.Returnval = append(body.Res.Returnval, ref)
}
}
}
}
return body
}
func (s *SearchIndex) FindByIp(req *types.FindByIp) soap.HasFault {
body := &methods.FindByIpBody{Res: new(types.FindByIpResponse)}
all := types.FindAllByIp(*req)
switch r := s.FindAllByIp(&all).(type) {
case *methods.FindAllByIpBody:
if len(r.Res.Returnval) > 0 {
body.Res.Returnval = &r.Res.Returnval[0]
}
default:
// no need until FindAllByIp below returns a Fault
}
return body
}
func (s *SearchIndex) FindAllByIp(req *types.FindAllByIp) soap.HasFault {
body := &methods.FindAllByIpBody{Res: new(types.FindAllByIpResponse)}
if req.VmSearch {
// Find Virtual Machine using IP
for ref, obj := range Map.objects {
vm, ok := obj.(*VirtualMachine)
if !ok {
continue
}
if vm.Guest.IpAddress == req.Ip {
body.Res.Returnval = append(body.Res.Returnval, ref)
}
}
} else {
// Find Host System using IP
for ref, obj := range Map.objects {
host, ok := obj.(*HostSystem)
if !ok {
continue
}
for _, net := range host.Config.Network.Vnic {
if net.Spec.Ip.IpAddress == req.Ip {
body.Res.Returnval = append(body.Res.Returnval, ref)
}
}
}
}
return body
}

View File

@ -69,6 +69,14 @@ func NewServiceInstance(content types.ServiceContent, folder mo.Folder) *Service
NewTaskManager(*s.Content.TaskManager), NewTaskManager(*s.Content.TaskManager),
NewUserDirectory(*s.Content.UserDirectory), NewUserDirectory(*s.Content.UserDirectory),
NewOptionManager(s.Content.Setting, setting), NewOptionManager(s.Content.Setting, setting),
NewStorageResourceManager(*s.Content.StorageResourceManager),
}
switch content.VStorageObjectManager.Type {
case "HostVStorageObjectManager":
// TODO: NewHostVStorageObjectManager(*content.VStorageObjectManager)
case "VcenterVStorageObjectManager":
objects = append(objects, NewVcenterVStorageObjectManager(*content.VStorageObjectManager))
} }
if s.Content.CustomFieldsManager != nil { if s.Content.CustomFieldsManager != nil {

View File

@ -24,7 +24,6 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/vmware/govmomi/object" "github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/session" "github.com/vmware/govmomi/session"
"github.com/vmware/govmomi/vim25/methods" "github.com/vmware/govmomi/vim25/methods"
@ -173,6 +172,23 @@ func (s *SessionManager) TerminateSession(ctx *Context, req *types.TerminateSess
return body return body
} }
func (s *SessionManager) SessionIsActive(ctx *Context, req *types.SessionIsActive) soap.HasFault {
body := new(methods.SessionIsActiveBody)
if ctx.Map.IsESX() {
body.Fault_ = Fault("", new(types.NotImplemented))
return body
}
body.Res = new(types.SessionIsActiveResponse)
if session, exists := s.sessions[req.SessionID]; exists {
body.Res.Returnval = session.UserName == req.UserName
}
return body
}
func (s *SessionManager) AcquireCloneTicket(ctx *Context, _ *types.AcquireCloneTicket) soap.HasFault { func (s *SessionManager) AcquireCloneTicket(ctx *Context, _ *types.AcquireCloneTicket) soap.HasFault {
session := *ctx.Session session := *ctx.Session
session.Key = uuid.New().String() session.Key = uuid.New().String()
@ -234,7 +250,7 @@ var invalidLogin = Fault("Login failure", new(types.InvalidLogin))
type Context struct { type Context struct {
req *http.Request req *http.Request
res http.ResponseWriter res http.ResponseWriter
m *SessionManager svc *Service
context.Context context.Context
Session *Session Session *Session
@ -246,7 +262,7 @@ type Context struct {
// mapSession maps an HTTP cookie to a Session. // mapSession maps an HTTP cookie to a Session.
func (c *Context) mapSession() { func (c *Context) mapSession() {
if cookie, err := c.req.Cookie(soap.SessionCookieName); err == nil { if cookie, err := c.req.Cookie(soap.SessionCookieName); err == nil {
if val, ok := c.m.sessions[cookie.Value]; ok { if val, ok := c.svc.sm.sessions[cookie.Value]; ok {
c.SetSession(val, false) c.SetSession(val, false)
} }
} }
@ -258,7 +274,7 @@ func (c *Context) SetSession(session Session, login bool) {
session.IpAddress = strings.Split(c.req.RemoteAddr, ":")[0] session.IpAddress = strings.Split(c.req.RemoteAddr, ":")[0]
session.LastActiveTime = time.Now() session.LastActiveTime = time.Now()
c.m.sessions[session.Key] = session c.svc.sm.sessions[session.Key] = session
c.Session = &session c.Session = &session
if login { if login {

View File

@ -29,6 +29,7 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
"math/rand"
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -39,6 +40,7 @@ import (
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/vmware/govmomi/find" "github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object" "github.com/vmware/govmomi/object"
@ -65,6 +67,7 @@ type Service struct {
client *vim25.Client client *vim25.Client
sm *SessionManager sm *SessionManager
sdk map[string]*Registry sdk map[string]*Registry
delay *DelayConfig
readAll func(io.Reader) ([]byte, error) readAll func(io.Reader) ([]byte, error)
@ -122,7 +125,7 @@ func (s *Service) call(ctx *Context, method *Method) soap.HasFault {
if session == nil { if session == nil {
switch method.Name { switch method.Name {
case "RetrieveServiceContent", "List", "Login", "LoginByToken", "LoginExtensionByCertificate", "RetrieveProperties", "RetrievePropertiesEx", "CloneSession": case "RetrieveServiceContent", "PbmRetrieveServiceContent", "List", "Login", "LoginByToken", "LoginExtensionByCertificate", "RetrieveProperties", "RetrievePropertiesEx", "CloneSession":
// ok for now, TODO: authz // ok for now, TODO: authz
default: default:
fault := &types.NotAuthenticated{ fault := &types.NotAuthenticated{
@ -147,7 +150,8 @@ func (s *Service) call(ctx *Context, method *Method) soap.HasFault {
return &serverFaultBody{Reason: Fault(msg, fault)} return &serverFaultBody{Reason: Fault(msg, fault)}
} }
name := method.Name // Lowercase methods can't be accessed outside their package
name := strings.Title(method.Name)
if strings.HasSuffix(name, vTaskSuffix) { if strings.HasSuffix(name, vTaskSuffix) {
// Make golint happy renaming "Foo_Task" -> "FooTask" // Make golint happy renaming "Foo_Task" -> "FooTask"
@ -172,6 +176,25 @@ func (s *Service) call(ctx *Context, method *Method) soap.HasFault {
} }
} }
// We have a valid call. Introduce a delay if requested
//
if s.delay != nil {
d := 0
if s.delay.Delay > 0 {
d = s.delay.Delay
}
if md, ok := s.delay.MethodDelay[method.Name]; ok {
d += md
}
if s.delay.DelayJitter > 0 {
d += int(rand.NormFloat64() * s.delay.DelayJitter * float64(d))
}
if d > 0 {
//fmt.Printf("Delaying method %s %d ms\n", name, d)
time.Sleep(time.Duration(d) * time.Millisecond)
}
}
var args, res []reflect.Value var args, res []reflect.Value
if m.Type().NumIn() == 2 { if m.Type().NumIn() == 2 {
args = append(args, reflect.ValueOf(ctx)) args = append(args, reflect.ValueOf(ctx))
@ -230,16 +253,36 @@ type soapEnvelope struct {
Body interface{} `xml:"soapenv:Body"` Body interface{} `xml:"soapenv:Body"`
} }
type faultDetail struct {
Fault types.AnyType
}
// soapFault is a copy of soap.Fault, with the same changes as soapEnvelope // soapFault is a copy of soap.Fault, with the same changes as soapEnvelope
type soapFault struct { type soapFault struct {
XMLName xml.Name `xml:"soapenv:Fault"` XMLName xml.Name `xml:"soapenv:Fault"`
Code string `xml:"faultcode"` Code string `xml:"faultcode"`
String string `xml:"faultstring"` String string `xml:"faultstring"`
Detail struct { Detail struct {
Fault types.AnyType `xml:",any,typeattr"` Fault *faultDetail
} `xml:"detail"` } `xml:"detail"`
} }
// MarshalXML renames the start element from "Fault" to "${Type}Fault"
func (d *faultDetail) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
kind := reflect.TypeOf(d.Fault).Elem().Name()
start.Name.Local = kind + "Fault"
start.Attr = append(start.Attr,
xml.Attr{
Name: xml.Name{Local: "xmlns"},
Value: "urn:" + vim25.Namespace,
},
xml.Attr{
Name: xml.Name{Local: "xsi:type"},
Value: kind,
})
return e.EncodeElement(d.Fault, start)
}
// About generates some info about the simulator. // About generates some info about the simulator.
func (s *Service) About(w http.ResponseWriter, r *http.Request) { func (s *Service) About(w http.ResponseWriter, r *http.Request) {
var about struct { var about struct {
@ -289,6 +332,16 @@ func (s *Service) About(w http.ResponseWriter, r *http.Request) {
_ = enc.Encode(&about) _ = enc.Encode(&about)
} }
// Handle registers the handler for the given pattern with Service.ServeMux.
func (s *Service) Handle(pattern string, handler http.Handler) {
s.ServeMux.Handle(pattern, handler)
// Not ideal, but avoids having to add yet another registration mechanism
// so we can optionally use vapi/simulator internally.
if m, ok := handler.(tagManager); ok {
s.sdk[vim25.Path].tagManager = m
}
}
// RegisterSDK adds an HTTP handler for the Registry's Path and Namespace. // RegisterSDK adds an HTTP handler for the Registry's Path and Namespace.
func (s *Service) RegisterSDK(r *Registry) { func (s *Service) RegisterSDK(r *Registry) {
if s.ServeMux == nil { if s.ServeMux == nil {
@ -321,7 +374,7 @@ func (s *Service) ServeSDK(w http.ResponseWriter, r *http.Request) {
ctx := &Context{ ctx := &Context{
req: r, req: r,
res: w, res: w,
m: s.sm, svc: s,
Map: s.sdk[r.URL.Path], Map: s.sdk[r.URL.Path],
Context: context.Background(), Context: context.Background(),
@ -350,7 +403,9 @@ func (s *Service) ServeSDK(w http.ResponseWriter, r *http.Request) {
&soapFault{ &soapFault{
Code: f.Code, Code: f.Code,
String: f.String, String: f.String,
Detail: f.Detail, Detail: struct {
Fault *faultDetail
}{&faultDetail{f.Detail.Fault}},
}, },
} }
} else { } else {
@ -431,6 +486,9 @@ func (s *Service) ServeDatastore(w http.ResponseWriter, r *http.Request) {
// File does not exist, fallthrough to create via PUT logic // File does not exist, fallthrough to create via PUT logic
fallthrough fallthrough
case "PUT": case "PUT":
dir := path.Dir(p)
_ = os.MkdirAll(dir, 0700)
f, err := os.Create(p) f, err := os.Create(p)
if err != nil { if err != nil {
log.Printf("failed to %s '%s': %s", r.Method, p, err) log.Printf("failed to %s '%s': %s", r.Method, p, err)
@ -465,6 +523,37 @@ func (*Service) ServiceVersions(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, versions) fmt.Fprint(w, versions)
} }
// defaultIP returns addr.IP if specified, otherwise attempts to find a non-loopback ipv4 IP
func defaultIP(addr *net.TCPAddr) string {
if !addr.IP.IsUnspecified() {
return addr.IP.String()
}
nics, err := net.Interfaces()
if err != nil {
return addr.IP.String()
}
for _, nic := range nics {
if nic.Name == "docker0" || strings.HasPrefix(nic.Name, "vmnet") {
continue
}
addrs, aerr := nic.Addrs()
if aerr != nil {
continue
}
for _, addr := range addrs {
if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() {
if ip.IP.To4() != nil {
return ip.IP.String()
}
}
}
}
return addr.IP.String()
}
// NewServer returns an http Server instance for the given service // NewServer returns an http Server instance for the given service
func (s *Service) NewServer() *Server { func (s *Service) NewServer() *Server {
s.RegisterSDK(Map) s.RegisterSDK(Map)
@ -478,11 +567,12 @@ func (s *Service) NewServer() *Server {
// for use in main.go, where Start() blocks, we can still set ServiceHostName // for use in main.go, where Start() blocks, we can still set ServiceHostName
ts := httptest.NewUnstartedServer(mux) ts := httptest.NewUnstartedServer(mux)
addr := ts.Listener.Addr().(*net.TCPAddr)
port := strconv.Itoa(addr.Port)
u := &url.URL{ u := &url.URL{
Scheme: "http", Scheme: "http",
Host: ts.Listener.Addr().String(), Host: net.JoinHostPort(defaultIP(addr), port),
Path: Map.Path, Path: Map.Path,
User: url.UserPassword("user", "pass"),
} }
// Redirect clients to this http server, rather than HostSystem.Name // Redirect clients to this http server, rather than HostSystem.Name
@ -510,7 +600,7 @@ func (s *Service) NewServer() *Server {
m.Setting = append(m.Setting, m.Setting = append(m.Setting,
&types.OptionValue{ &types.OptionValue{
Key: "vcsim.server.url", Key: "vcsim.server.url",
Value: ts.URL, Value: u.String(),
}, },
&types.OptionValue{ &types.OptionValue{
Key: "vcsim.server.cert", Key: "vcsim.server.cert",
@ -518,6 +608,8 @@ func (s *Service) NewServer() *Server {
}, },
) )
u.User = url.UserPassword("user", "pass")
return &Server{ return &Server{
Server: ts, Server: ts,
URL: u, URL: u,

View File

@ -17,6 +17,11 @@ limitations under the License.
package simulator package simulator
import ( import (
"fmt"
"os"
"path"
"github.com/vmware/govmomi/object"
"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"
@ -27,24 +32,110 @@ type VirtualMachineSnapshot struct {
mo.VirtualMachineSnapshot mo.VirtualMachineSnapshot
} }
func (v *VirtualMachineSnapshot) RemoveSnapshotTask(req *types.RemoveSnapshot_Task) soap.HasFault { func (v *VirtualMachineSnapshot) createSnapshotFiles() types.BaseMethodFault {
vm := Map.Get(v.Vm).(*VirtualMachine)
snapshotDirectory := vm.Config.Files.SnapshotDirectory
if snapshotDirectory == "" {
snapshotDirectory = vm.Config.Files.VmPathName
}
index := 1
for {
fileName := fmt.Sprintf("%s-Snapshot%d.vmsn", vm.Name, index)
f, err := vm.createFile(snapshotDirectory, fileName, false)
if err != nil {
switch err.(type) {
case *types.FileAlreadyExists:
index++
continue
default:
return err
}
}
_ = f.Close()
p, _ := parseDatastorePath(snapshotDirectory)
vm.useDatastore(p.Datastore)
datastorePath := object.DatastorePath{
Datastore: p.Datastore,
Path: path.Join(p.Path, fileName),
}
dataLayoutKey := vm.addFileLayoutEx(datastorePath, 0)
vm.addSnapshotLayout(v.Self, dataLayoutKey)
vm.addSnapshotLayoutEx(v.Self, dataLayoutKey, -1)
return nil
}
}
func (v *VirtualMachineSnapshot) removeSnapshotFiles(ctx *Context) types.BaseMethodFault {
// TODO: also remove delta disks that were created when snapshot was taken
vm := Map.Get(v.Vm).(*VirtualMachine)
for idx, sLayout := range vm.Layout.Snapshot {
if sLayout.Key == v.Self {
vm.Layout.Snapshot = append(vm.Layout.Snapshot[:idx], vm.Layout.Snapshot[idx+1:]...)
break
}
}
for idx, sLayoutEx := range vm.LayoutEx.Snapshot {
if sLayoutEx.Key == v.Self {
for _, file := range vm.LayoutEx.File {
if file.Key == sLayoutEx.DataKey || file.Key == sLayoutEx.MemoryKey {
p, fault := parseDatastorePath(file.Name)
if fault != nil {
return fault
}
host := Map.Get(*vm.Runtime.Host).(*HostSystem)
datastore := Map.FindByName(p.Datastore, host.Datastore).(*Datastore)
dFilePath := path.Join(datastore.Info.GetDatastoreInfo().Url, p.Path)
_ = os.Remove(dFilePath)
}
}
vm.LayoutEx.Snapshot = append(vm.LayoutEx.Snapshot[:idx], vm.LayoutEx.Snapshot[idx+1:]...)
}
}
vm.RefreshStorageInfo(ctx, nil)
return nil
}
func (v *VirtualMachineSnapshot) RemoveSnapshotTask(ctx *Context, req *types.RemoveSnapshot_Task) soap.HasFault {
task := CreateTask(v, "removeSnapshot", func(t *Task) (types.AnyType, types.BaseMethodFault) { task := CreateTask(v, "removeSnapshot", func(t *Task) (types.AnyType, types.BaseMethodFault) {
Map.Remove(req.This) var changes []types.PropertyChange
vm := Map.Get(v.Vm).(*VirtualMachine) vm := Map.Get(v.Vm).(*VirtualMachine)
Map.WithLock(vm, func() { Map.WithLock(vm, func() {
if vm.Snapshot.CurrentSnapshot != nil && *vm.Snapshot.CurrentSnapshot == req.This { if vm.Snapshot.CurrentSnapshot != nil && *vm.Snapshot.CurrentSnapshot == req.This {
parent := findParentSnapshotInTree(vm.Snapshot.RootSnapshotList, req.This) parent := findParentSnapshotInTree(vm.Snapshot.RootSnapshotList, req.This)
vm.Snapshot.CurrentSnapshot = parent changes = append(changes, types.PropertyChange{Name: "snapshot.currentSnapshot", Val: parent})
} }
vm.Snapshot.RootSnapshotList = removeSnapshotInTree(vm.Snapshot.RootSnapshotList, req.This, req.RemoveChildren) rootSnapshots := removeSnapshotInTree(vm.Snapshot.RootSnapshotList, req.This, req.RemoveChildren)
changes = append(changes, types.PropertyChange{Name: "snapshot.rootSnapshotList", Val: rootSnapshots})
if len(vm.Snapshot.RootSnapshotList) == 0 { if len(rootSnapshots) == 0 {
vm.Snapshot = nil changes = []types.PropertyChange{
{Name: "snapshot", Val: nil},
} }
}
Map.Get(req.This).(*VirtualMachineSnapshot).removeSnapshotFiles(ctx)
Map.Update(vm, changes)
}) })
Map.Remove(req.This)
return nil, nil return nil, nil
}) })
@ -59,7 +150,11 @@ func (v *VirtualMachineSnapshot) RevertToSnapshotTask(req *types.RevertToSnapsho
task := CreateTask(v, "revertToSnapshot", func(t *Task) (types.AnyType, types.BaseMethodFault) { task := CreateTask(v, "revertToSnapshot", func(t *Task) (types.AnyType, types.BaseMethodFault) {
vm := Map.Get(v.Vm).(*VirtualMachine) vm := Map.Get(v.Vm).(*VirtualMachine)
Map.WithLock(vm, func() { vm.Snapshot.CurrentSnapshot = &v.Self }) Map.WithLock(vm, func() {
Map.Update(vm, []types.PropertyChange{
{Name: "snapshot.currentSnapshot", Val: v.Self},
})
})
return nil, nil return nil, nil
}) })

View File

@ -0,0 +1,191 @@
/*
Copyright (c) 2018 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 simulator
import (
"strconv"
"time"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
)
type StorageResourceManager struct {
mo.StorageResourceManager
}
func NewStorageResourceManager(ref types.ManagedObjectReference) object.Reference {
m := &StorageResourceManager{}
m.Self = ref
return m
}
func (m *StorageResourceManager) ConfigureStorageDrsForPodTask(req *types.ConfigureStorageDrsForPod_Task) soap.HasFault {
task := CreateTask(m, "configureStorageDrsForPod", func(*Task) (types.AnyType, types.BaseMethodFault) {
cluster := Map.Get(req.Pod).(*StoragePod)
if s := req.Spec.PodConfigSpec; s != nil {
config := &cluster.PodStorageDrsEntry.StorageDrsConfig.PodConfig
if s.Enabled != nil {
config.Enabled = *s.Enabled
}
if s.DefaultVmBehavior != "" {
config.DefaultVmBehavior = s.DefaultVmBehavior
}
}
return nil, nil
})
return &methods.ConfigureStorageDrsForPod_TaskBody{
Res: &types.ConfigureStorageDrsForPod_TaskResponse{
Returnval: task.Run(),
},
}
}
func (m *StorageResourceManager) pod(ref *types.ManagedObjectReference) *StoragePod {
if ref == nil {
return nil
}
cluster := Map.Get(*ref).(*StoragePod)
config := &cluster.PodStorageDrsEntry.StorageDrsConfig.PodConfig
if !config.Enabled {
return nil
}
if len(cluster.ChildEntity) == 0 {
return nil
}
return cluster
}
func (m *StorageResourceManager) RecommendDatastores(req *types.RecommendDatastores) soap.HasFault {
spec := req.StorageSpec.PodSelectionSpec
body := new(methods.RecommendDatastoresBody)
res := new(types.RecommendDatastoresResponse)
key := 0
invalid := func(prop string) soap.HasFault {
body.Fault_ = Fault("", &types.InvalidArgument{
InvalidProperty: prop,
})
return body
}
add := func(cluster *StoragePod, ds types.ManagedObjectReference) {
key++
res.Returnval.Recommendations = append(res.Returnval.Recommendations, types.ClusterRecommendation{
Key: strconv.Itoa(key),
Type: "V1",
Time: time.Now(),
Rating: 1,
Reason: "storagePlacement",
ReasonText: "Satisfy storage initial placement requests",
WarningText: "",
WarningDetails: (*types.LocalizableMessage)(nil),
Prerequisite: nil,
Action: []types.BaseClusterAction{
&types.StoragePlacementAction{
ClusterAction: types.ClusterAction{
Type: "StoragePlacementV1",
Target: (*types.ManagedObjectReference)(nil),
},
Vm: (*types.ManagedObjectReference)(nil),
RelocateSpec: types.VirtualMachineRelocateSpec{
Service: (*types.ServiceLocator)(nil),
Folder: (*types.ManagedObjectReference)(nil),
Datastore: &ds,
DiskMoveType: "moveAllDiskBackingsAndAllowSharing",
Pool: (*types.ManagedObjectReference)(nil),
Host: (*types.ManagedObjectReference)(nil),
Disk: nil,
Transform: "",
DeviceChange: nil,
Profile: nil,
},
Destination: ds,
SpaceUtilBefore: 5.00297212600708,
SpaceDemandBefore: 5.00297212600708,
SpaceUtilAfter: 5.16835880279541,
SpaceDemandAfter: 5.894514083862305,
IoLatencyBefore: 0,
},
},
Target: &cluster.Self,
})
}
var devices object.VirtualDeviceList
switch types.StoragePlacementSpecPlacementType(req.StorageSpec.Type) {
case types.StoragePlacementSpecPlacementTypeCreate:
if req.StorageSpec.ResourcePool == nil {
return invalid("resourcePool")
}
if req.StorageSpec.ConfigSpec == nil {
return invalid("configSpec")
}
for _, d := range req.StorageSpec.ConfigSpec.DeviceChange {
devices = append(devices, d.GetVirtualDeviceConfigSpec().Device)
}
cluster := m.pod(spec.StoragePod)
if cluster == nil {
if f := req.StorageSpec.ConfigSpec.Files; f == nil || f.VmPathName == "" {
return invalid("configSpec.files")
}
}
case types.StoragePlacementSpecPlacementTypeClone:
if req.StorageSpec.Folder == nil {
return invalid("folder")
}
if req.StorageSpec.Vm == nil {
return invalid("vm")
}
if req.StorageSpec.CloneName == "" {
return invalid("cloneName")
}
if req.StorageSpec.CloneSpec == nil {
return invalid("cloneSpec")
}
}
for _, placement := range spec.InitialVmConfig {
cluster := m.pod(&placement.StoragePod)
if cluster == nil {
return invalid("podSelectionSpec.storagePod")
}
for _, disk := range placement.Disk {
if devices.FindByKey(disk.DiskId) == nil {
return invalid("podSelectionSpec.initialVmConfig.disk.fileBacking")
}
}
for _, ds := range cluster.ChildEntity {
add(cluster, ds)
}
}
body.Res = res
return body
}

View File

@ -20,6 +20,8 @@ import (
"sync" "sync"
"github.com/vmware/govmomi/object" "github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/simulator/esx"
"github.com/vmware/govmomi/simulator/vpx"
"github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types" "github.com/vmware/govmomi/vim25/types"
) )
@ -34,6 +36,11 @@ type TaskManager struct {
func NewTaskManager(ref types.ManagedObjectReference) object.Reference { func NewTaskManager(ref types.ManagedObjectReference) object.Reference {
s := &TaskManager{} s := &TaskManager{}
s.Self = ref s.Self = ref
if Map.IsESX() {
s.Description = esx.Description
} else {
s.Description = vpx.Description
}
Map.AddHandler(s) Map.AddHandler(s)
return s return s
} }

View File

@ -19,17 +19,18 @@ package simulator
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"net" "net"
"os" "os"
"path" "path"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/vmware/govmomi/object" "github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/simulator/esx" "github.com/vmware/govmomi/simulator/esx"
"github.com/vmware/govmomi/vim25/methods" "github.com/vmware/govmomi/vim25/methods"
@ -43,18 +44,21 @@ type VirtualMachine struct {
log string log string
sid int32 sid int32
run container
} }
func NewVirtualMachine(parent types.ManagedObjectReference, spec *types.VirtualMachineConfigSpec) (*VirtualMachine, types.BaseMethodFault) { func NewVirtualMachine(parent types.ManagedObjectReference, spec *types.VirtualMachineConfigSpec) (*VirtualMachine, types.BaseMethodFault) {
vm := &VirtualMachine{} vm := &VirtualMachine{}
vm.Parent = &parent vm.Parent = &parent
Map.Get(parent).(*Folder).putChild(vm)
if spec.Name == "" { if spec.Name == "" {
return nil, &types.InvalidVmConfig{Property: "configSpec.name"} return vm, &types.InvalidVmConfig{Property: "configSpec.name"}
} }
if spec.Files == nil || spec.Files.VmPathName == "" { if spec.Files == nil || spec.Files.VmPathName == "" {
return nil, &types.InvalidVmConfig{Property: "configSpec.files.vmPathName"} return vm, &types.InvalidVmConfig{Property: "configSpec.files.vmPathName"}
} }
rspec := types.DefaultResourceConfigSpec() rspec := types.DefaultResourceConfigSpec()
@ -65,6 +69,10 @@ func NewVirtualMachine(parent types.ManagedObjectReference, spec *types.VirtualM
MemoryAllocation: &rspec.MemoryAllocation, MemoryAllocation: &rspec.MemoryAllocation,
CpuAllocation: &rspec.CpuAllocation, CpuAllocation: &rspec.CpuAllocation,
} }
vm.Layout = &types.VirtualMachineFileLayout{}
vm.LayoutEx = &types.VirtualMachineFileLayoutEx{
Timestamp: time.Now(),
}
vm.Snapshot = nil // intentionally set to nil until a snapshot is created vm.Snapshot = nil // intentionally set to nil until a snapshot is created
vm.Storage = &types.VirtualMachineStorageInfo{ vm.Storage = &types.VirtualMachineStorageInfo{
Timestamp: time.Now(), Timestamp: time.Now(),
@ -92,7 +100,7 @@ func NewVirtualMachine(parent types.ManagedObjectReference, spec *types.VirtualM
MemoryMB: 32, MemoryMB: 32,
Uuid: uuid.New().String(), Uuid: uuid.New().String(),
InstanceUuid: uuid.New().String(), InstanceUuid: uuid.New().String(),
Version: "vmx-11", Version: esx.HardwareVersion,
Files: &types.VirtualMachineFileInfo{ Files: &types.VirtualMachineFileInfo{
SnapshotDirectory: dsPath, SnapshotDirectory: dsPath,
SuspendDirectory: dsPath, SuspendDirectory: dsPath,
@ -105,7 +113,7 @@ func NewVirtualMachine(parent types.ManagedObjectReference, spec *types.VirtualM
err := vm.configure(&defaults) err := vm.configure(&defaults)
if err != nil { if err != nil {
return nil, err return vm, err
} }
vm.Runtime.PowerState = types.VirtualMachinePowerStatePoweredOff vm.Runtime.PowerState = types.VirtualMachinePowerStatePoweredOff
@ -166,6 +174,7 @@ func (vm *VirtualMachine) apply(spec *types.VirtualMachineConfigSpec) {
{spec.Files.VmPathName, &vm.Config.Files.VmPathName}, {spec.Files.VmPathName, &vm.Config.Files.VmPathName},
{spec.Files.VmPathName, &vm.Summary.Config.VmPathName}, {spec.Files.VmPathName, &vm.Summary.Config.VmPathName},
{spec.Files.SnapshotDirectory, &vm.Config.Files.SnapshotDirectory}, {spec.Files.SnapshotDirectory, &vm.Config.Files.SnapshotDirectory},
{spec.Files.SuspendDirectory, &vm.Config.Files.SuspendDirectory},
{spec.Files.LogDirectory, &vm.Config.Files.LogDirectory}, {spec.Files.LogDirectory, &vm.Config.Files.LogDirectory},
} }
@ -259,7 +268,33 @@ func (vm *VirtualMachine) apply(spec *types.VirtualMachineConfigSpec) {
vm.Config.Hardware.NumCoresPerSocket = spec.NumCoresPerSocket vm.Config.Hardware.NumCoresPerSocket = spec.NumCoresPerSocket
} }
vm.Config.ExtraConfig = append(vm.Config.ExtraConfig, spec.ExtraConfig...) var changes []types.PropertyChange
for _, c := range spec.ExtraConfig {
val := c.GetOptionValue()
key := strings.TrimPrefix(val.Key, "SET.")
if key == val.Key {
vm.Config.ExtraConfig = append(vm.Config.ExtraConfig, c)
continue
}
changes = append(changes, types.PropertyChange{Name: key, Val: val.Value})
switch key {
case "guest.ipAddress":
ip := val.Value.(string)
vm.Guest.Net[0].IpAddress = []string{ip}
changes = append(changes,
types.PropertyChange{Name: "summary." + key, Val: ip},
types.PropertyChange{Name: "guest.net", Val: vm.Guest.Net},
)
case "guest.hostName":
changes = append(changes,
types.PropertyChange{Name: "summary." + key, Val: val.Value},
)
}
}
if len(changes) != 0 {
Map.Update(vm, changes)
}
vm.Config.Modified = time.Now() vm.Config.Modified = time.Now()
} }
@ -298,6 +333,400 @@ func (vm *VirtualMachine) configure(spec *types.VirtualMachineConfigSpec) types.
return vm.configureDevices(spec) return vm.configureDevices(spec)
} }
func getVMFileType(fileName string) types.VirtualMachineFileLayoutExFileType {
var fileType types.VirtualMachineFileLayoutExFileType
fileExt := path.Ext(fileName)
fileNameNoExt := strings.TrimSuffix(fileName, fileExt)
switch fileExt {
case ".vmx":
fileType = types.VirtualMachineFileLayoutExFileTypeConfig
case ".core":
fileType = types.VirtualMachineFileLayoutExFileTypeCore
case ".vmdk":
fileType = types.VirtualMachineFileLayoutExFileTypeDiskDescriptor
if strings.HasSuffix(fileNameNoExt, "-digest") {
fileType = types.VirtualMachineFileLayoutExFileTypeDigestDescriptor
}
extentSuffixes := []string{"-flat", "-delta", "-s", "-rdm", "-rdmp"}
for _, suffix := range extentSuffixes {
if strings.HasSuffix(fileNameNoExt, suffix) {
fileType = types.VirtualMachineFileLayoutExFileTypeDiskExtent
} else if strings.HasSuffix(fileNameNoExt, "-digest"+suffix) {
fileType = types.VirtualMachineFileLayoutExFileTypeDigestExtent
}
}
case ".psf":
fileType = types.VirtualMachineFileLayoutExFileTypeDiskReplicationState
case ".vmxf":
fileType = types.VirtualMachineFileLayoutExFileTypeExtendedConfig
case ".vmft":
fileType = types.VirtualMachineFileLayoutExFileTypeFtMetadata
case ".log":
fileType = types.VirtualMachineFileLayoutExFileTypeLog
case ".nvram":
fileType = types.VirtualMachineFileLayoutExFileTypeNvram
case ".png", ".bmp":
fileType = types.VirtualMachineFileLayoutExFileTypeScreenshot
case ".vmsn":
fileType = types.VirtualMachineFileLayoutExFileTypeSnapshotData
case ".vmsd":
fileType = types.VirtualMachineFileLayoutExFileTypeSnapshotList
case ".xml":
if strings.HasSuffix(fileNameNoExt, "-aux") {
fileType = types.VirtualMachineFileLayoutExFileTypeSnapshotManifestList
}
case ".stat":
fileType = types.VirtualMachineFileLayoutExFileTypeStat
case ".vmss":
fileType = types.VirtualMachineFileLayoutExFileTypeSuspend
case ".vmem":
if strings.Contains(fileNameNoExt, "Snapshot") {
fileType = types.VirtualMachineFileLayoutExFileTypeSnapshotMemory
} else {
fileType = types.VirtualMachineFileLayoutExFileTypeSuspendMemory
}
case ".vswp":
if strings.HasPrefix(fileNameNoExt, "vmx-") {
fileType = types.VirtualMachineFileLayoutExFileTypeUwswap
} else {
fileType = types.VirtualMachineFileLayoutExFileTypeSwap
}
case "":
if strings.HasPrefix(fileNameNoExt, "imcf-") {
fileType = types.VirtualMachineFileLayoutExFileTypeGuestCustomization
}
}
return fileType
}
func (vm *VirtualMachine) addFileLayoutEx(datastorePath object.DatastorePath, fileSize int64) int32 {
var newKey int32
for _, layoutFile := range vm.LayoutEx.File {
if layoutFile.Name == datastorePath.String() {
return layoutFile.Key
}
if layoutFile.Key >= newKey {
newKey = layoutFile.Key + 1
}
}
fileType := getVMFileType(filepath.Base(datastorePath.Path))
switch fileType {
case types.VirtualMachineFileLayoutExFileTypeNvram, types.VirtualMachineFileLayoutExFileTypeSnapshotList:
vm.addConfigLayout(datastorePath.Path)
case types.VirtualMachineFileLayoutExFileTypeLog:
vm.addLogLayout(datastorePath.Path)
case types.VirtualMachineFileLayoutExFileTypeSwap:
vm.addSwapLayout(datastorePath.String())
}
vm.LayoutEx.File = append(vm.LayoutEx.File, types.VirtualMachineFileLayoutExFileInfo{
Accessible: types.NewBool(true),
BackingObjectId: "",
Key: newKey,
Name: datastorePath.String(),
Size: fileSize,
Type: string(fileType),
UniqueSize: fileSize,
})
vm.LayoutEx.Timestamp = time.Now()
vm.updateStorage()
return newKey
}
func (vm *VirtualMachine) addConfigLayout(name string) {
for _, config := range vm.Layout.ConfigFile {
if config == name {
return
}
}
vm.Layout.ConfigFile = append(vm.Layout.ConfigFile, name)
vm.updateStorage()
}
func (vm *VirtualMachine) addLogLayout(name string) {
for _, log := range vm.Layout.LogFile {
if log == name {
return
}
}
vm.Layout.LogFile = append(vm.Layout.LogFile, name)
vm.updateStorage()
}
func (vm *VirtualMachine) addSwapLayout(name string) {
vm.Layout.SwapFile = name
vm.updateStorage()
}
func (vm *VirtualMachine) addSnapshotLayout(snapshot types.ManagedObjectReference, dataKey int32) {
for _, snapshotLayout := range vm.Layout.Snapshot {
if snapshotLayout.Key == snapshot {
return
}
}
var snapshotFiles []string
for _, file := range vm.LayoutEx.File {
if file.Key == dataKey || file.Type == "diskDescriptor" {
snapshotFiles = append(snapshotFiles, file.Name)
}
}
vm.Layout.Snapshot = append(vm.Layout.Snapshot, types.VirtualMachineFileLayoutSnapshotLayout{
Key: snapshot,
SnapshotFile: snapshotFiles,
})
vm.updateStorage()
}
func (vm *VirtualMachine) addSnapshotLayoutEx(snapshot types.ManagedObjectReference, dataKey int32, memoryKey int32) {
for _, snapshotLayoutEx := range vm.LayoutEx.Snapshot {
if snapshotLayoutEx.Key == snapshot {
return
}
}
vm.LayoutEx.Snapshot = append(vm.LayoutEx.Snapshot, types.VirtualMachineFileLayoutExSnapshotLayout{
DataKey: dataKey,
Disk: vm.LayoutEx.Disk,
Key: snapshot,
MemoryKey: memoryKey,
})
vm.LayoutEx.Timestamp = time.Now()
vm.updateStorage()
}
// Updates both vm.Layout.Disk and vm.LayoutEx.Disk
func (vm *VirtualMachine) updateDiskLayouts() types.BaseMethodFault {
var disksLayout []types.VirtualMachineFileLayoutDiskLayout
var disksLayoutEx []types.VirtualMachineFileLayoutExDiskLayout
disks := object.VirtualDeviceList(vm.Config.Hardware.Device).SelectByType((*types.VirtualDisk)(nil))
for _, disk := range disks {
disk := disk.(*types.VirtualDisk)
diskBacking := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo)
diskLayout := &types.VirtualMachineFileLayoutDiskLayout{Key: disk.Key}
diskLayoutEx := &types.VirtualMachineFileLayoutExDiskLayout{Key: disk.Key}
// Iterate through disk and its parents
for {
dFileName := diskBacking.GetVirtualDeviceFileBackingInfo().FileName
var fileKeys []int32
dm := Map.VirtualDiskManager()
// Add disk descriptor and extent files
for _, diskName := range dm.names(dFileName) {
// get full path including datastore location
p, fault := parseDatastorePath(diskName)
if fault != nil {
return fault
}
datastore := vm.useDatastore(p.Datastore)
dFilePath := path.Join(datastore.Info.GetDatastoreInfo().Url, p.Path)
var fileSize int64
// If file can not be opened - fileSize will be 0
if dFileInfo, err := os.Stat(dFilePath); err == nil {
fileSize = dFileInfo.Size()
}
diskKey := vm.addFileLayoutEx(*p, fileSize)
fileKeys = append(fileKeys, diskKey)
}
diskLayout.DiskFile = append(diskLayout.DiskFile, dFileName)
diskLayoutEx.Chain = append(diskLayoutEx.Chain, types.VirtualMachineFileLayoutExDiskUnit{
FileKey: fileKeys,
})
if parent := diskBacking.Parent; parent != nil {
diskBacking = parent
} else {
break
}
}
disksLayout = append(disksLayout, *diskLayout)
disksLayoutEx = append(disksLayoutEx, *diskLayoutEx)
}
vm.Layout.Disk = disksLayout
vm.LayoutEx.Disk = disksLayoutEx
vm.LayoutEx.Timestamp = time.Now()
vm.updateStorage()
return nil
}
func (vm *VirtualMachine) updateStorage() types.BaseMethodFault {
// Committed - sum of Size for each file in vm.LayoutEx.File
// Unshared - sum of Size for each disk (.vmdk) in vm.LayoutEx.File
// Uncommitted - disk capacity minus disk usage (only currently used disk)
var datastoresUsage []types.VirtualMachineUsageOnDatastore
disks := object.VirtualDeviceList(vm.Config.Hardware.Device).SelectByType((*types.VirtualDisk)(nil))
for _, file := range vm.LayoutEx.File {
p, fault := parseDatastorePath(file.Name)
if fault != nil {
return fault
}
datastore := vm.useDatastore(p.Datastore)
dsUsage := &types.VirtualMachineUsageOnDatastore{
Datastore: datastore.Self,
}
for idx, usage := range datastoresUsage {
if usage.Datastore == datastore.Self {
datastoresUsage = append(datastoresUsage[:idx], datastoresUsage[idx+1:]...)
dsUsage = &usage
break
}
}
dsUsage.Committed = file.Size
if path.Ext(file.Name) == ".vmdk" {
dsUsage.Unshared = file.Size
}
for _, disk := range disks {
disk := disk.(*types.VirtualDisk)
backing := disk.Backing.(types.BaseVirtualDeviceFileBackingInfo).GetVirtualDeviceFileBackingInfo()
if backing.FileName == file.Name {
dsUsage.Uncommitted = disk.CapacityInBytes
}
}
datastoresUsage = append(datastoresUsage, *dsUsage)
}
vm.Storage.PerDatastoreUsage = datastoresUsage
vm.Storage.Timestamp = time.Now()
storageSummary := &types.VirtualMachineStorageSummary{
Timestamp: time.Now(),
}
for _, usage := range datastoresUsage {
storageSummary.Committed += usage.Committed
storageSummary.Uncommitted += usage.Uncommitted
storageSummary.Unshared += usage.Unshared
}
vm.Summary.Storage = storageSummary
return nil
}
func (vm *VirtualMachine) RefreshStorageInfo(ctx *Context, req *types.RefreshStorageInfo) soap.HasFault {
body := new(methods.RefreshStorageInfoBody)
if vm.Runtime.Host == nil {
// VM not fully created
return body
}
// Validate that all files in vm.LayoutEx.File can still be found
for idx := len(vm.LayoutEx.File) - 1; idx >= 0; idx-- {
file := vm.LayoutEx.File[idx]
p, fault := parseDatastorePath(file.Name)
if fault != nil {
body.Fault_ = Fault("", fault)
return body
}
if _, err := os.Stat(p.String()); err != nil {
vm.LayoutEx.File = append(vm.LayoutEx.File[:idx], vm.LayoutEx.File[idx+1:]...)
}
}
// Directories will be used to locate VM files.
// Does not include information about virtual disk file locations.
locations := []string{
vm.Config.Files.VmPathName,
vm.Config.Files.SnapshotDirectory,
vm.Config.Files.LogDirectory,
vm.Config.Files.SuspendDirectory,
vm.Config.Files.FtMetadataDirectory,
}
for _, directory := range locations {
if directory == "" {
continue
}
p, fault := parseDatastorePath(directory)
if fault != nil {
body.Fault_ = Fault("", fault)
return body
}
datastore := vm.useDatastore(p.Datastore)
directory := path.Join(datastore.Info.GetDatastoreInfo().Url, p.Path)
if path.Ext(p.Path) == ".vmx" {
directory = path.Dir(directory) // vm.Config.Files.VmPathName can be a directory or full path to .vmx
}
if _, err := os.Stat(directory); err != nil {
// Can not access the directory
continue
}
files, err := ioutil.ReadDir(directory)
if err != nil {
body.Fault_ = soap.ToSoapFault(err)
return body
}
for _, file := range files {
datastorePath := object.DatastorePath{
Datastore: p.Datastore,
Path: strings.TrimPrefix(file.Name(), datastore.Info.GetDatastoreInfo().Url),
}
vm.addFileLayoutEx(datastorePath, file.Size())
}
}
fault := vm.updateDiskLayouts()
if fault != nil {
body.Fault_ = Fault("", fault)
return body
}
vm.LayoutEx.Timestamp = time.Now()
return body
}
func (vm *VirtualMachine) useDatastore(name string) *Datastore { func (vm *VirtualMachine) useDatastore(name string) *Datastore {
host := Map.Get(*vm.Runtime.Host).(*HostSystem) host := Map.Get(*vm.Runtime.Host).(*HostSystem)
@ -321,8 +750,8 @@ func (vm *VirtualMachine) createFile(spec string, name string, register bool) (*
file := path.Join(ds.Info.GetDatastoreInfo().Url, p.Path) file := path.Join(ds.Info.GetDatastoreInfo().Url, p.Path)
if name != "" { if name != "" {
if path.Ext(file) != "" { if path.Ext(p.Path) == ".vmx" {
file = path.Dir(file) file = path.Dir(file) // vm.Config.Files.VmPathName can be a directory or full path to .vmx
} }
file = path.Join(file, name) file = path.Join(file, name)
@ -380,6 +809,15 @@ func (vm *VirtualMachine) logPrintf(format string, v ...interface{}) {
func (vm *VirtualMachine) create(spec *types.VirtualMachineConfigSpec, register bool) types.BaseMethodFault { func (vm *VirtualMachine) create(spec *types.VirtualMachineConfigSpec, register bool) types.BaseMethodFault {
vm.apply(spec) vm.apply(spec)
if spec.Version != "" {
v := strings.TrimPrefix(spec.Version, "vmx-")
_, err := strconv.Atoi(v)
if err != nil {
log.Printf("unsupported hardware version: %s", spec.Version)
return new(types.NotSupported)
}
}
files := []struct { files := []struct {
spec string spec string
name string name string
@ -456,21 +894,19 @@ func (vm *VirtualMachine) configureDevice(devices object.VirtualDeviceList, spec
d := device.GetVirtualDevice() d := device.GetVirtualDevice()
var controller types.BaseVirtualController var controller types.BaseVirtualController
if d.Key < 0 { if d.Key <= 0 {
// Choose a unique key // Keys can't be negative; Key 0 is reserved
if d.Key == -1 {
d.Key = devices.NewKey() d.Key = devices.NewKey()
d.Key *= -1
} }
d.Key *= -1 // Choose a unique key
for { for {
if devices.FindByKey(d.Key) == nil { if devices.FindByKey(d.Key) == nil {
break break
} }
d.Key++ d.Key++
} }
}
label := devices.Name(device) label := devices.Name(device)
summary := label summary := label
@ -481,10 +917,12 @@ func (vm *VirtualMachine) configureDevice(devices object.VirtualDeviceList, spec
case types.BaseVirtualEthernetCard: case types.BaseVirtualEthernetCard:
controller = devices.PickController((*types.VirtualPCIController)(nil)) controller = devices.PickController((*types.VirtualPCIController)(nil))
var net types.ManagedObjectReference var net types.ManagedObjectReference
var name string
switch b := d.Backing.(type) { switch b := d.Backing.(type) {
case *types.VirtualEthernetCardNetworkBackingInfo: case *types.VirtualEthernetCardNetworkBackingInfo:
summary = b.DeviceName name = b.DeviceName
summary = name
net = Map.FindByName(b.DeviceName, dc.Network).Reference() net = Map.FindByName(b.DeviceName, dc.Network).Reference()
b.Network = &net b.Network = &net
case *types.VirtualEthernetCardDistributedVirtualPortBackingInfo: case *types.VirtualEthernetCardDistributedVirtualPortBackingInfo:
@ -493,20 +931,35 @@ func (vm *VirtualMachine) configureDevice(devices object.VirtualDeviceList, spec
net.Value = b.Port.PortgroupKey net.Value = b.Port.PortgroupKey
} }
vm.Network = append(vm.Network, net) Map.Update(vm, []types.PropertyChange{
{Name: "summary.config.numEthernetCards", Val: vm.Summary.Config.NumEthernetCards + 1},
{Name: "network", Val: append(vm.Network, net)},
})
c := x.GetVirtualEthernetCard() c := x.GetVirtualEthernetCard()
if c.MacAddress == "" { if c.MacAddress == "" {
c.MacAddress = vm.generateMAC() c.MacAddress = vm.generateMAC()
} }
if spec.Operation == types.VirtualDeviceConfigSpecOperationAdd {
vm.Guest.Net = append(vm.Guest.Net, types.GuestNicInfo{
Network: name,
IpAddress: nil,
MacAddress: c.MacAddress,
Connected: true,
DeviceConfigId: c.Key,
})
}
case *types.VirtualDisk: case *types.VirtualDisk:
summary = fmt.Sprintf("%s KB", numberToString(x.CapacityInKB, ',')) summary = fmt.Sprintf("%s KB", numberToString(x.CapacityInKB, ','))
switch b := d.Backing.(type) { switch b := d.Backing.(type) {
case types.BaseVirtualDeviceFileBackingInfo: case types.BaseVirtualDeviceFileBackingInfo:
info := b.GetVirtualDeviceFileBackingInfo() info := b.GetVirtualDeviceFileBackingInfo()
var path object.DatastorePath
path.FromString(info.FileName)
if info.FileName == "" { if path.Path == "" {
filename, err := vm.genVmdkPath() filename, err := vm.genVmdkPath(path)
if err != nil { if err != nil {
return err return err
} }
@ -522,6 +975,10 @@ func (vm *VirtualMachine) configureDevice(devices object.VirtualDeviceList, spec
return err return err
} }
Map.Update(vm, []types.PropertyChange{
{Name: "summary.config.numVirtualDisks", Val: vm.Summary.Config.NumVirtualDisks + 1},
})
p, _ := parseDatastorePath(info.FileName) p, _ := parseDatastorePath(info.FileName)
host := Map.Get(*vm.Runtime.Host).(*HostSystem) host := Map.Get(*vm.Runtime.Host).(*HostSystem)
@ -535,6 +992,8 @@ func (vm *VirtualMachine) configureDevice(devices object.VirtualDeviceList, spec
// XXX: compare disk size and free space until windows stat is supported // XXX: compare disk size and free space until windows stat is supported
ds.Summary.FreeSpace -= getDiskSize(x) ds.Summary.FreeSpace -= getDiskSize(x)
ds.Info.GetDatastoreInfo().FreeSpace = ds.Summary.FreeSpace ds.Info.GetDatastoreInfo().FreeSpace = ds.Summary.FreeSpace
vm.updateDiskLayouts()
} }
} }
@ -591,6 +1050,11 @@ func (vm *VirtualMachine) removeDevice(devices object.VirtualDeviceList, spec *t
}) })
} }
} }
Map.Update(vm, []types.PropertyChange{
{Name: "summary.config.numVirtualDisks", Val: vm.Summary.Config.NumVirtualDisks - 1},
})
vm.updateDiskLayouts()
case types.BaseVirtualEthernetCard: case types.BaseVirtualEthernetCard:
var net types.ManagedObjectReference var net types.ManagedObjectReference
@ -602,7 +1066,12 @@ func (vm *VirtualMachine) removeDevice(devices object.VirtualDeviceList, spec *t
net.Value = b.Port.PortgroupKey net.Value = b.Port.PortgroupKey
} }
RemoveReference(&vm.Network, net) networks := vm.Network
RemoveReference(&networks, net)
Map.Update(vm, []types.PropertyChange{
{Name: "summary.config.numEthernetCards", Val: vm.Summary.Config.NumEthernetCards - 1},
{Name: "network", Val: networks},
})
} }
break break
@ -611,9 +1080,16 @@ func (vm *VirtualMachine) removeDevice(devices object.VirtualDeviceList, spec *t
return devices return devices
} }
func (vm *VirtualMachine) genVmdkPath() (string, types.BaseMethodFault) { func (vm *VirtualMachine) genVmdkPath(p object.DatastorePath) (string, types.BaseMethodFault) {
vmdir := path.Dir(vm.Config.Files.VmPathName) if p.Datastore == "" {
p.FromString(vm.Config.Files.VmPathName)
}
if p.Path == "" {
p.Path = vm.Config.Name
} else {
p.Path = path.Dir(p.Path)
}
vmdir := p.String()
index := 0 index := 0
for { for {
var filename string var filename string
@ -651,13 +1127,22 @@ func (vm *VirtualMachine) configureDevices(spec *types.VirtualMachineConfigSpec)
switch dspec.Operation { switch dspec.Operation {
case types.VirtualDeviceConfigSpecOperationAdd: case types.VirtualDeviceConfigSpecOperationAdd:
if devices.FindByKey(device.Key) != nil { if devices.FindByKey(device.Key) != nil && device.ControllerKey == 0 {
if vm.Self.Value != "" { // moid isn't set until CreateVM is done // Note: real ESX does not allow adding base controllers (ControllerKey = 0)
return invalid // after VM is created (returns success but device is not added).
continue
} else if device.UnitNumber != nil && devices.SelectByType(dspec.Device).Select(func(d types.BaseVirtualDevice) bool {
base := d.GetVirtualDevice()
if base.UnitNumber != nil {
if base.ControllerKey != device.ControllerKey {
return false
} }
return *base.UnitNumber == *device.UnitNumber
// In this case, the CreateVM() spec included one of the default devices }
devices = vm.removeDevice(devices, dspec) return false
}) != nil {
// UnitNumber for this device type is taken
return invalid
} }
err := vm.configureDevice(devices, dspec) err := vm.configureDevice(devices, dspec)
@ -686,7 +1171,11 @@ func (vm *VirtualMachine) configureDevices(spec *types.VirtualMachineConfigSpec)
} }
} }
vm.Config.Hardware.Device = []types.BaseVirtualDevice(devices) Map.Update(vm, []types.PropertyChange{
{Name: "config.hardware.device", Val: []types.BaseVirtualDevice(devices)},
})
vm.updateDiskLayouts()
return nil return nil
} }
@ -717,16 +1206,19 @@ func (c *powerVMTask) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
event := c.event() event := c.event()
switch c.state { switch c.state {
case types.VirtualMachinePowerStatePoweredOn: case types.VirtualMachinePowerStatePoweredOn:
c.run.start(c.VirtualMachine)
c.ctx.postEvent( c.ctx.postEvent(
&types.VmStartingEvent{VmEvent: event}, &types.VmStartingEvent{VmEvent: event},
&types.VmPoweredOnEvent{VmEvent: event}, &types.VmPoweredOnEvent{VmEvent: event},
) )
case types.VirtualMachinePowerStatePoweredOff: case types.VirtualMachinePowerStatePoweredOff:
c.run.stop(c.VirtualMachine)
c.ctx.postEvent( c.ctx.postEvent(
&types.VmStoppingEvent{VmEvent: event}, &types.VmStoppingEvent{VmEvent: event},
&types.VmPoweredOffEvent{VmEvent: event}, &types.VmPoweredOffEvent{VmEvent: event},
) )
case types.VirtualMachinePowerStateSuspended: case types.VirtualMachinePowerStateSuspended:
c.run.pause(c.VirtualMachine)
c.ctx.postEvent( c.ctx.postEvent(
&types.VmSuspendingEvent{VmEvent: event}, &types.VmSuspendingEvent{VmEvent: event},
&types.VmSuspendedEvent{VmEvent: event}, &types.VmSuspendedEvent{VmEvent: event},
@ -823,6 +1315,25 @@ func (vm *VirtualMachine) ReconfigVMTask(ctx *Context, req *types.ReconfigVM_Tas
} }
} }
func (vm *VirtualMachine) UpgradeVMTask(req *types.UpgradeVM_Task) soap.HasFault {
body := &methods.UpgradeVM_TaskBody{}
task := CreateTask(vm, "upgradeVm", func(t *Task) (types.AnyType, types.BaseMethodFault) {
if vm.Config.Version != esx.HardwareVersion {
Map.Update(vm, []types.PropertyChange{{
Name: "config.version", Val: esx.HardwareVersion,
}})
}
return nil, nil
})
body.Res = &types.UpgradeVM_TaskResponse{
Returnval: task.Run(),
}
return body
}
func (vm *VirtualMachine) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFault { func (vm *VirtualMachine) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFault {
task := CreateTask(vm, "destroy", func(t *Task) (types.AnyType, types.BaseMethodFault) { task := CreateTask(vm, "destroy", func(t *Task) (types.AnyType, types.BaseMethodFault) {
r := vm.UnregisterVM(ctx, &types.UnregisterVM{ r := vm.UnregisterVM(ctx, &types.UnregisterVM{
@ -848,6 +1359,8 @@ func (vm *VirtualMachine) DestroyTask(ctx *Context, req *types.Destroy_Task) soa
Datacenter: &dc, Datacenter: &dc,
}) })
vm.run.remove(vm)
return nil, nil return nil, nil
}) })
@ -858,6 +1371,10 @@ func (vm *VirtualMachine) DestroyTask(ctx *Context, req *types.Destroy_Task) soa
} }
} }
func (vm *VirtualMachine) SetCustomValue(ctx *Context, req *types.SetCustomValue) soap.HasFault {
return SetCustomValue(ctx, req)
}
func (vm *VirtualMachine) UnregisterVM(ctx *Context, c *types.UnregisterVM) soap.HasFault { func (vm *VirtualMachine) UnregisterVM(ctx *Context, c *types.UnregisterVM) soap.HasFault {
r := &methods.UnregisterVMBody{} r := &methods.UnregisterVMBody{}
@ -917,21 +1434,24 @@ func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soa
}, },
} }
for _, device := range vm.Config.Hardware.Device { defaultDevices := object.VirtualDeviceList(esx.VirtualDevice)
devices := vm.Config.Hardware.Device
for _, device := range devices {
var fop types.VirtualDeviceConfigSpecFileOperation var fop types.VirtualDeviceConfigSpecFileOperation
switch device.(type) { if defaultDevices.Find(object.VirtualDeviceList(devices).Name(device)) != nil {
// Default devices are added during CreateVMTask
continue
}
switch disk := device.(type) {
case *types.VirtualDisk: case *types.VirtualDisk:
// TODO: consider VirtualMachineCloneSpec.DiskMoveType // TODO: consider VirtualMachineCloneSpec.DiskMoveType
fop = types.VirtualDeviceConfigSpecFileOperationCreate fop = types.VirtualDeviceConfigSpecFileOperationCreate
device = &types.VirtualDisk{
VirtualDevice: types.VirtualDevice{
Backing: &types.VirtualDiskFlatVer2BackingInfo{
DiskMode: string(types.VirtualDiskModePersistent),
// Leave FileName empty so CreateVM will just create a new one under VmPathName // Leave FileName empty so CreateVM will just create a new one under VmPathName
}, disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo).FileName = ""
}, disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo).Parent = nil
}
} }
config.DeviceChange = append(config.DeviceChange, &types.VirtualDeviceConfigSpec{ config.DeviceChange = append(config.DeviceChange, &types.VirtualDeviceConfigSpec{
@ -956,6 +1476,9 @@ func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soa
ref := ctask.Info.Result.(types.ManagedObjectReference) ref := ctask.Info.Result.(types.ManagedObjectReference)
clone := Map.Get(ref).(*VirtualMachine) clone := Map.Get(ref).(*VirtualMachine)
clone.configureDevices(&types.VirtualMachineConfigSpec{DeviceChange: req.Spec.Location.DeviceChange}) clone.configureDevices(&types.VirtualMachineConfigSpec{DeviceChange: req.Spec.Location.DeviceChange})
if req.Spec.Config != nil && req.Spec.Config.DeviceChange != nil {
clone.configureDevices(&types.VirtualMachineConfigSpec{DeviceChange: req.Spec.Config.DeviceChange})
}
ctx.postEvent(&types.VmClonedEvent{ ctx.postEvent(&types.VmClonedEvent{
VmCloneEvent: types.VmCloneEvent{VmEvent: clone.event()}, VmCloneEvent: types.VmCloneEvent{VmEvent: clone.event()},
@ -980,7 +1503,7 @@ func (vm *VirtualMachine) RelocateVMTask(req *types.RelocateVM_Task) soap.HasFau
ds := Map.Get(*ref).(*Datastore) ds := Map.Get(*ref).(*Datastore)
Map.RemoveReference(ds, &ds.Vm, *ref) Map.RemoveReference(ds, &ds.Vm, *ref)
// TODO: migrate vm.Config.Files (and vm.Summary.Config.VmPathName) // TODO: migrate vm.Config.Files, vm.Summary.Config.VmPathName, vm.Layout and vm.LayoutEx
changes = append(changes, types.PropertyChange{Name: "datastore", Val: []types.ManagedObjectReference{*ref}}) changes = append(changes, types.PropertyChange{Name: "datastore", Val: []types.ManagedObjectReference{*ref}})
} }
@ -989,7 +1512,7 @@ func (vm *VirtualMachine) RelocateVMTask(req *types.RelocateVM_Task) soap.HasFau
pool := Map.Get(*ref).(*ResourcePool) pool := Map.Get(*ref).(*ResourcePool)
Map.RemoveReference(pool, &pool.Vm, *ref) Map.RemoveReference(pool, &pool.Vm, *ref)
changes = append(changes, types.PropertyChange{Name: "resourcePool", Val: *ref}) changes = append(changes, types.PropertyChange{Name: "resourcePool", Val: ref})
} }
if ref := req.Spec.Host; ref != nil { if ref := req.Spec.Host; ref != nil {
@ -997,8 +1520,8 @@ func (vm *VirtualMachine) RelocateVMTask(req *types.RelocateVM_Task) soap.HasFau
Map.RemoveReference(host, &host.Vm, *ref) Map.RemoveReference(host, &host.Vm, *ref)
changes = append(changes, changes = append(changes,
types.PropertyChange{Name: "runtime.host", Val: *ref}, types.PropertyChange{Name: "runtime.host", Val: ref},
types.PropertyChange{Name: "summary.runtime.host", Val: *ref}, types.PropertyChange{Name: "summary.runtime.host", Val: ref},
) )
} }
@ -1016,6 +1539,8 @@ func (vm *VirtualMachine) RelocateVMTask(req *types.RelocateVM_Task) soap.HasFau
func (vm *VirtualMachine) CreateSnapshotTask(req *types.CreateSnapshot_Task) soap.HasFault { func (vm *VirtualMachine) CreateSnapshotTask(req *types.CreateSnapshot_Task) soap.HasFault {
task := CreateTask(vm, "createSnapshot", func(t *Task) (types.AnyType, types.BaseMethodFault) { task := CreateTask(vm, "createSnapshot", func(t *Task) (types.AnyType, types.BaseMethodFault) {
var changes []types.PropertyChange
if vm.Snapshot == nil { if vm.Snapshot == nil {
vm.Snapshot = &types.VirtualMachineSnapshotInfo{} vm.Snapshot = &types.VirtualMachineSnapshotInfo{}
} }
@ -1047,10 +1572,16 @@ func (vm *VirtualMachine) CreateSnapshotTask(req *types.CreateSnapshot_Task) soa
ss := findSnapshotInTree(vm.Snapshot.RootSnapshotList, *cur) ss := findSnapshotInTree(vm.Snapshot.RootSnapshotList, *cur)
ss.ChildSnapshotList = append(ss.ChildSnapshotList, treeItem) ss.ChildSnapshotList = append(ss.ChildSnapshotList, treeItem)
} else { } else {
vm.Snapshot.RootSnapshotList = append(vm.Snapshot.RootSnapshotList, treeItem) changes = append(changes, types.PropertyChange{
Name: "snapshot.rootSnapshotList",
Val: append(vm.Snapshot.RootSnapshotList, treeItem),
})
} }
vm.Snapshot.CurrentSnapshot = &snapshot.Self snapshot.createSnapshotFiles()
changes = append(changes, types.PropertyChange{Name: "snapshot.currentSnapshot", Val: snapshot.Self})
Map.Update(vm, changes)
return nil, nil return nil, nil
}) })
@ -1082,7 +1613,7 @@ func (vm *VirtualMachine) RevertToCurrentSnapshotTask(req *types.RevertToCurrent
return body return body
} }
func (vm *VirtualMachine) RemoveAllSnapshotsTask(req *types.RemoveAllSnapshots_Task) soap.HasFault { func (vm *VirtualMachine) RemoveAllSnapshotsTask(ctx *Context, req *types.RemoveAllSnapshots_Task) soap.HasFault {
task := CreateTask(vm, "RemoveAllSnapshots", func(t *Task) (types.AnyType, types.BaseMethodFault) { task := CreateTask(vm, "RemoveAllSnapshots", func(t *Task) (types.AnyType, types.BaseMethodFault) {
if vm.Snapshot == nil { if vm.Snapshot == nil {
return nil, nil return nil, nil
@ -1090,9 +1621,12 @@ func (vm *VirtualMachine) RemoveAllSnapshotsTask(req *types.RemoveAllSnapshots_T
refs := allSnapshotsInTree(vm.Snapshot.RootSnapshotList) refs := allSnapshotsInTree(vm.Snapshot.RootSnapshotList)
vm.Snapshot = nil Map.Update(vm, []types.PropertyChange{
{Name: "snapshot", Val: nil},
})
for _, ref := range refs { for _, ref := range refs {
Map.Get(ref).(*VirtualMachineSnapshot).removeSnapshotFiles(ctx)
Map.Remove(ref) Map.Remove(ref)
} }
@ -1126,6 +1660,7 @@ func (vm *VirtualMachine) ShutdownGuest(ctx *Context, c *types.ShutdownGuest) so
&types.VmGuestShutdownEvent{VmEvent: event}, &types.VmGuestShutdownEvent{VmEvent: event},
&types.VmPoweredOffEvent{VmEvent: event}, &types.VmPoweredOffEvent{VmEvent: event},
) )
vm.run.stop(vm)
Map.Update(vm, []types.PropertyChange{ Map.Update(vm, []types.PropertyChange{
{Name: "runtime.powerState", Val: types.VirtualMachinePowerStatePoweredOff}, {Name: "runtime.powerState", Val: types.VirtualMachinePowerStatePoweredOff},

View File

@ -4,9 +4,12 @@ go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"doc.go", "doc.go",
"performance_manager.go",
"performance_manager_data.go",
"root_folder.go", "root_folder.go",
"service_content.go", "service_content.go",
"setting.go", "setting.go",
"task_manager.go",
], ],
importmap = "k8s.io/kubernetes/vendor/github.com/vmware/govmomi/simulator/vpx", importmap = "k8s.io/kubernetes/vendor/github.com/vmware/govmomi/simulator/vpx",
importpath = "github.com/vmware/govmomi/simulator/vpx", importpath = "github.com/vmware/govmomi/simulator/vpx",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,404 @@
/*
Copyright (c) 2018 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 simulator
import (
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/google/uuid"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
)
type VStorageObject struct {
types.VStorageObject
types.VStorageObjectSnapshotInfo
}
type VcenterVStorageObjectManager struct {
mo.VcenterVStorageObjectManager
objects map[types.ManagedObjectReference]map[types.ID]*VStorageObject
}
func NewVcenterVStorageObjectManager(ref types.ManagedObjectReference) object.Reference {
m := &VcenterVStorageObjectManager{}
m.Self = ref
m.objects = make(map[types.ManagedObjectReference]map[types.ID]*VStorageObject)
return m
}
func (m *VcenterVStorageObjectManager) object(ds types.ManagedObjectReference, id types.ID) *VStorageObject {
if objects, ok := m.objects[ds]; ok {
return objects[id]
}
return nil
}
func (m *VcenterVStorageObjectManager) ListVStorageObject(req *types.ListVStorageObject) soap.HasFault {
body := &methods.ListVStorageObjectBody{
Res: &types.ListVStorageObjectResponse{},
}
if objects, ok := m.objects[req.Datastore]; ok {
for id := range objects {
body.Res.Returnval = append(body.Res.Returnval, id)
}
}
return body
}
func (m *VcenterVStorageObjectManager) RetrieveVStorageObject(req *types.RetrieveVStorageObject) soap.HasFault {
body := new(methods.RetrieveVStorageObjectBody)
obj := m.object(req.Datastore, req.Id)
if obj == nil {
body.Fault_ = Fault("", new(types.InvalidArgument))
} else {
body.Res = &types.RetrieveVStorageObjectResponse{
Returnval: obj.VStorageObject,
}
}
return body
}
func (m *VcenterVStorageObjectManager) RegisterDisk(ctx *Context, req *types.RegisterDisk) soap.HasFault {
body := new(methods.RegisterDiskBody)
invalid := func() soap.HasFault {
body.Fault_ = Fault("", &types.InvalidArgument{InvalidProperty: "path"})
return body
}
u, err := url.Parse(req.Path)
if err != nil {
return invalid()
}
u.Path = strings.TrimPrefix(u.Path, folderPrefix)
ds, err := ctx.svc.findDatastore(u.Query())
if err != nil {
return invalid()
}
st, err := os.Stat(filepath.Join(ds.Info.GetDatastoreInfo().Url, u.Path))
if err != nil {
return invalid()
}
if st.IsDir() {
return invalid()
}
path := (&object.DatastorePath{Datastore: ds.Name, Path: u.Path}).String()
for _, obj := range m.objects[ds.Self] {
backing := obj.Config.BaseConfigInfo.Backing.(*types.BaseConfigInfoDiskFileBackingInfo)
if backing.FilePath == path {
return invalid()
}
}
creq := &types.CreateDisk_Task{
Spec: types.VslmCreateSpec{
Name: req.Name,
BackingSpec: &types.VslmCreateSpecDiskFileBackingSpec{
VslmCreateSpecBackingSpec: types.VslmCreateSpecBackingSpec{
Datastore: ds.Self,
Path: u.Path,
},
},
},
}
obj, fault := m.createObject(creq, true)
if fault != nil {
body.Fault_ = Fault("", fault)
return body
}
body.Res = &types.RegisterDiskResponse{
Returnval: *obj,
}
return body
}
func (m *VcenterVStorageObjectManager) createObject(req *types.CreateDisk_Task, register bool) (*types.VStorageObject, types.BaseMethodFault) {
dir := "fcd"
ref := req.Spec.BackingSpec.GetVslmCreateSpecBackingSpec().Datastore
ds := Map.Get(ref).(*Datastore)
dc := Map.getEntityDatacenter(ds)
dm := Map.VirtualDiskManager()
objects, ok := m.objects[ds.Self]
if !ok {
objects = make(map[types.ID]*VStorageObject)
m.objects[ds.Self] = objects
_ = os.Mkdir(filepath.Join(ds.Info.GetDatastoreInfo().Url, dir), 0755)
}
id := uuid.New().String()
obj := types.VStorageObject{
Config: types.VStorageObjectConfigInfo{
BaseConfigInfo: types.BaseConfigInfo{
Id: types.ID{
Id: id,
},
Name: req.Spec.Name,
CreateTime: time.Now(),
KeepAfterDeleteVm: req.Spec.KeepAfterDeleteVm,
RelocationDisabled: types.NewBool(false),
NativeSnapshotSupported: types.NewBool(false),
ChangedBlockTrackingEnabled: types.NewBool(false),
Iofilter: nil,
},
CapacityInMB: req.Spec.CapacityInMB,
ConsumptionType: []string{"disk"},
ConsumerId: nil,
},
}
backing := req.Spec.BackingSpec.(*types.VslmCreateSpecDiskFileBackingSpec)
path := object.DatastorePath{
Datastore: ds.Name,
Path: backing.Path,
}
if path.Path == "" {
path.Path = dir + "/" + id + ".vmdk"
}
if register == false {
err := dm.createVirtualDisk(types.VirtualDeviceConfigSpecFileOperationCreate, &types.CreateVirtualDisk_Task{
Datacenter: &dc.Self,
Name: path.String(),
})
if err != nil {
return nil, err
}
}
obj.Config.BaseConfigInfo.Backing = &types.BaseConfigInfoDiskFileBackingInfo{
BaseConfigInfoFileBackingInfo: types.BaseConfigInfoFileBackingInfo{
BaseConfigInfoBackingInfo: types.BaseConfigInfoBackingInfo{
Datastore: ds.Self,
},
FilePath: path.String(),
BackingObjectId: uuid.New().String(),
Parent: nil,
DeltaSizeInMB: 0,
},
ProvisioningType: backing.ProvisioningType,
}
objects[obj.Config.Id] = &VStorageObject{VStorageObject: obj}
return &obj, nil
}
func (m *VcenterVStorageObjectManager) CreateDiskTask(req *types.CreateDisk_Task) soap.HasFault {
task := CreateTask(m, "createDisk", func(*Task) (types.AnyType, types.BaseMethodFault) {
return m.createObject(req, false)
})
return &methods.CreateDisk_TaskBody{
Res: &types.CreateDisk_TaskResponse{
Returnval: task.Run(),
},
}
}
func (m *VcenterVStorageObjectManager) DeleteVStorageObjectTask(req *types.DeleteVStorageObject_Task) soap.HasFault {
task := CreateTask(m, "deleteDisk", func(*Task) (types.AnyType, types.BaseMethodFault) {
obj := m.object(req.Datastore, req.Id)
if obj == nil {
return nil, &types.InvalidArgument{}
}
backing := obj.Config.Backing.(*types.BaseConfigInfoDiskFileBackingInfo)
ds := Map.Get(req.Datastore).(*Datastore)
dc := Map.getEntityDatacenter(ds)
dm := Map.VirtualDiskManager()
dm.DeleteVirtualDiskTask(&types.DeleteVirtualDisk_Task{
Name: backing.FilePath,
Datacenter: &dc.Self,
})
delete(m.objects[req.Datastore], req.Id)
return nil, nil
})
return &methods.DeleteVStorageObject_TaskBody{
Res: &types.DeleteVStorageObject_TaskResponse{
Returnval: task.Run(),
},
}
}
func (m *VcenterVStorageObjectManager) RetrieveSnapshotInfo(req *types.RetrieveSnapshotInfo) soap.HasFault {
body := new(methods.RetrieveSnapshotInfoBody)
obj := m.object(req.Datastore, req.Id)
if obj == nil {
body.Fault_ = Fault("", new(types.InvalidArgument))
} else {
body.Res = &types.RetrieveSnapshotInfoResponse{
Returnval: obj.VStorageObjectSnapshotInfo,
}
}
return body
}
func (m *VcenterVStorageObjectManager) VStorageObjectCreateSnapshotTask(req *types.VStorageObjectCreateSnapshot_Task) soap.HasFault {
task := CreateTask(m, "createSnapshot", func(*Task) (types.AnyType, types.BaseMethodFault) {
obj := m.object(req.Datastore, req.Id)
if obj == nil {
return nil, new(types.InvalidArgument)
}
snapshot := types.VStorageObjectSnapshotInfoVStorageObjectSnapshot{
Id: &types.ID{
Id: uuid.New().String(),
},
BackingObjectId: uuid.New().String(),
CreateTime: time.Now(),
Description: req.Description,
}
obj.Snapshots = append(obj.Snapshots, snapshot)
return snapshot.Id, nil
})
return &methods.VStorageObjectCreateSnapshot_TaskBody{
Res: &types.VStorageObjectCreateSnapshot_TaskResponse{
Returnval: task.Run(),
},
}
}
func (m *VcenterVStorageObjectManager) DeleteSnapshotTask(req *types.DeleteSnapshot_Task) soap.HasFault {
task := CreateTask(m, "deleteSnapshot", func(*Task) (types.AnyType, types.BaseMethodFault) {
obj := m.object(req.Datastore, req.Id)
if obj != nil {
for i := range obj.Snapshots {
if *obj.Snapshots[i].Id == req.SnapshotId {
obj.Snapshots = append(obj.Snapshots[:i], obj.Snapshots[i+1:]...)
return nil, nil
}
}
}
return nil, new(types.InvalidArgument)
})
return &methods.DeleteSnapshot_TaskBody{
Res: &types.DeleteSnapshot_TaskResponse{
Returnval: task.Run(),
},
}
}
func (m *VcenterVStorageObjectManager) tagID(id types.ID) types.ManagedObjectReference {
return types.ManagedObjectReference{
Type: "fcd",
Value: id.Id,
}
}
func (m *VcenterVStorageObjectManager) AttachTagToVStorageObject(ctx *Context, req *types.AttachTagToVStorageObject) soap.HasFault {
body := new(methods.AttachTagToVStorageObjectBody)
ref := m.tagID(req.Id)
err := ctx.Map.tagManager.AttachTag(ref, types.VslmTagEntry{
ParentCategoryName: req.Category,
TagName: req.Tag,
})
if err == nil {
body.Res = new(types.AttachTagToVStorageObjectResponse)
} else {
body.Fault_ = Fault("", err)
}
return body
}
func (m *VcenterVStorageObjectManager) DetachTagFromVStorageObject(ctx *Context, req *types.DetachTagFromVStorageObject) soap.HasFault {
body := new(methods.DetachTagFromVStorageObjectBody)
ref := m.tagID(req.Id)
err := ctx.Map.tagManager.DetachTag(ref, types.VslmTagEntry{
ParentCategoryName: req.Category,
TagName: req.Tag,
})
if err == nil {
body.Res = new(types.DetachTagFromVStorageObjectResponse)
} else {
body.Fault_ = Fault("", err)
}
return body
}
func (m *VcenterVStorageObjectManager) ListVStorageObjectsAttachedToTag(ctx *Context, req *types.ListVStorageObjectsAttachedToTag) soap.HasFault {
body := new(methods.ListVStorageObjectsAttachedToTagBody)
refs, err := ctx.Map.tagManager.AttachedObjects(types.VslmTagEntry{
ParentCategoryName: req.Category,
TagName: req.Tag,
})
if err == nil {
body.Res = new(types.ListVStorageObjectsAttachedToTagResponse)
for _, ref := range refs {
body.Res.Returnval = append(body.Res.Returnval, types.ID{Id: ref.Value})
}
} else {
body.Fault_ = Fault("", err)
}
return body
}
func (m *VcenterVStorageObjectManager) ListTagsAttachedToVStorageObject(ctx *Context, req *types.ListTagsAttachedToVStorageObject) soap.HasFault {
body := new(methods.ListTagsAttachedToVStorageObjectBody)
ref := m.tagID(req.Id)
tags, err := ctx.Map.tagManager.AttachedTags(ref)
if err == nil {
body.Res = &types.ListTagsAttachedToVStorageObjectResponse{
Returnval: tags,
}
} else {
body.Fault_ = Fault("", err)
}
return body
}

View File

@ -70,7 +70,10 @@ type TokenRequest struct {
Lifetime time.Duration // Lifetime is the token's lifetime, defaults to 10m Lifetime time.Duration // Lifetime is the token's lifetime, defaults to 10m
Renewable bool // Renewable allows the issued token to be renewed Renewable bool // Renewable allows the issued token to be renewed
Delegatable bool // Delegatable allows the issued token to be delegated (e.g. for use with ActAs) Delegatable bool // Delegatable allows the issued token to be delegated (e.g. for use with ActAs)
Token string // Token for Renew request or Issue request ActAs identity ActAs bool // ActAs allows to request an ActAs token based on the passed Token.
Token string // Token for Renew request or Issue request ActAs identity or to be exchanged.
KeyType string // KeyType for requested token (if not set will be decucted from Userinfo and Certificate options)
KeyID string // KeyID used for signing the requests
} }
func (c *Client) newRequest(req TokenRequest, kind string, s *Signer) (internal.RequestSecurityToken, error) { func (c *Client) newRequest(req TokenRequest, kind string, s *Signer) (internal.RequestSecurityToken, error) {
@ -96,8 +99,11 @@ func (c *Client) newRequest(req TokenRequest, kind string, s *Signer) (internal.
OK: false, OK: false,
}, },
Delegatable: req.Delegatable, Delegatable: req.Delegatable,
KeyType: req.KeyType,
} }
if req.KeyType == "" {
// Deduce KeyType based on Certificate nad Userinfo.
if req.Certificate == nil { if req.Certificate == nil {
if req.Userinfo == nil { if req.Userinfo == nil {
return rst, errors.New("one of TokenRequest Certificate or Userinfo is required") return rst, errors.New("one of TokenRequest Certificate or Userinfo is required")
@ -105,7 +111,15 @@ func (c *Client) newRequest(req TokenRequest, kind string, s *Signer) (internal.
rst.KeyType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer" rst.KeyType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer"
} else { } else {
rst.KeyType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey" rst.KeyType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey"
rst.UseKey = &internal.UseKey{Sig: newID()} // For HOK KeyID is required.
if req.KeyID == "" {
req.KeyID = newID()
}
}
}
if req.KeyID != "" {
rst.UseKey = &internal.UseKey{Sig: req.KeyID}
s.keyID = rst.UseKey.Sig s.keyID = rst.UseKey.Sig
} }
@ -131,6 +145,8 @@ func (s *Signer) setLifetime(lifetime *internal.Lifetime) error {
func (c *Client) Issue(ctx context.Context, req TokenRequest) (*Signer, error) { func (c *Client) Issue(ctx context.Context, req TokenRequest) (*Signer, error) {
s := &Signer{ s := &Signer{
Certificate: req.Certificate, Certificate: req.Certificate,
keyID: req.KeyID,
Token: req.Token,
user: req.Userinfo, user: req.Userinfo,
} }
@ -139,7 +155,7 @@ func (c *Client) Issue(ctx context.Context, req TokenRequest) (*Signer, error) {
return nil, err return nil, err
} }
if req.Token != "" { if req.ActAs {
rst.ActAs = &internal.Target{ rst.ActAs = &internal.Target{
Token: req.Token, Token: req.Token,
} }

View File

@ -17,6 +17,8 @@ limitations under the License.
package sts package sts
import ( import (
"bytes"
"compress/gzip"
"crypto" "crypto"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
@ -25,7 +27,12 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil"
mrand "math/rand"
"net/http"
"net/url" "net/url"
"strings"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
@ -218,3 +225,101 @@ func (s *Signer) Sign(env soap.Envelope) ([]byte, error) {
Body: body, Body: body,
}) })
} }
// SignRequest is a rest.Signer implementation which can be used to sign rest.Client.LoginByTokenBody requests.
func (s *Signer) SignRequest(req *http.Request) error {
type param struct {
key, val string
}
var params []string
add := func(p param) {
params = append(params, fmt.Sprintf(`%s="%s"`, p.key, p.val))
}
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
if _, err := io.WriteString(gz, s.Token); err != nil {
return fmt.Errorf("zip token: %s", err)
}
if err := gz.Close(); err != nil {
return fmt.Errorf("zip token: %s", err)
}
add(param{
key: "token",
val: base64.StdEncoding.EncodeToString(buf.Bytes()),
})
if s.Certificate != nil {
nonce := fmt.Sprintf("%d:%d", time.Now().UnixNano()/1e6, mrand.Int())
var body []byte
if req.GetBody != nil {
r, rerr := req.GetBody()
if rerr != nil {
return fmt.Errorf("sts: getting http.Request body: %s", rerr)
}
defer r.Close()
body, rerr = ioutil.ReadAll(r)
if rerr != nil {
return fmt.Errorf("sts: reading http.Request body: %s", rerr)
}
}
bhash := sha256.New().Sum(body)
// Port in the signature must be that of the reverse proxy port, vCenter's default is port 80
port := "80" // TODO: get from lookup service
var buf bytes.Buffer
msg := []string{
nonce,
req.Method,
req.URL.Path,
strings.ToLower(req.URL.Hostname()),
port,
}
for i := range msg {
buf.WriteString(msg[i])
buf.WriteByte('\n')
}
buf.Write(bhash)
buf.WriteByte('\n')
sum := sha256.Sum256(buf.Bytes())
key, ok := s.Certificate.PrivateKey.(*rsa.PrivateKey)
if !ok {
return errors.New("sts: rsa.PrivateKey is required to sign http.Request")
}
sig, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, sum[:])
if err != nil {
return err
}
add(param{
key: "signature_alg",
val: "RSA-SHA256",
})
add(param{
key: "signature",
val: base64.StdEncoding.EncodeToString(sig),
})
add(param{
key: "nonce",
val: nonce,
})
add(param{
key: "bodyhash",
val: base64.StdEncoding.EncodeToString(bhash),
})
}
req.Header.Set("Authorization", fmt.Sprintf("SIGN %s", strings.Join(params, ", ")))
return nil
}
func (s *Signer) NewRequest() TokenRequest {
return TokenRequest{
Token: s.Token,
Certificate: s.Certificate,
Userinfo: s.user,
KeyID: s.keyID,
}
}

View File

@ -51,15 +51,13 @@ func (o AssociatedObject) Reference() types.ManagedObjectReference {
// Association for tag-association requests. // Association for tag-association requests.
type Association struct { type Association struct {
TagID string `json:"tag_id,omitempty"`
ObjectID *AssociatedObject `json:"object_id,omitempty"` ObjectID *AssociatedObject `json:"object_id,omitempty"`
} }
// NewAssociation returns an Association, converting ref to an AssociatedObject. // NewAssociation returns an Association, converting ref to an AssociatedObject.
func NewAssociation(tagID string, ref mo.Reference) Association { func NewAssociation(ref mo.Reference) Association {
obj := AssociatedObject(ref.Reference()) obj := AssociatedObject(ref.Reference())
return Association{ return Association{
TagID: tagID,
ObjectID: &obj, ObjectID: &obj,
} }
} }

View File

@ -43,6 +43,16 @@ func NewClient(c *vim25.Client) *Client {
return &Client{sc} return &Client{sc}
} }
type Signer interface {
SignRequest(*http.Request) error
}
type signerContext struct{}
func (c *Client) WithSigner(ctx context.Context, s Signer) context.Context {
return context.WithValue(ctx, signerContext{}, s)
}
// Do sends the http.Request, decoding resBody if provided. // Do sends the http.Request, decoding resBody if provided.
func (c *Client) Do(ctx context.Context, req *http.Request, resBody interface{}) error { func (c *Client) Do(ctx context.Context, req *http.Request, resBody interface{}) error {
switch req.Method { switch req.Method {
@ -52,6 +62,12 @@ func (c *Client) Do(ctx context.Context, req *http.Request, resBody interface{})
req.Header.Set("Accept", "application/json") req.Header.Set("Accept", "application/json")
if s, ok := ctx.Value(signerContext{}).(Signer); ok {
if err := s.SignRequest(req); err != nil {
return err
}
}
return c.Client.Do(ctx, req, func(res *http.Response) error { return c.Client.Do(ctx, req, func(res *http.Response) error {
switch res.StatusCode { switch res.StatusCode {
case http.StatusOK: case http.StatusOK:
@ -98,6 +114,10 @@ func (c *Client) Login(ctx context.Context, user *url.Userinfo) error {
return c.Do(ctx, req, nil) return c.Do(ctx, req, nil)
} }
func (c *Client) LoginByToken(ctx context.Context) error {
return c.Login(ctx, nil)
}
// Logout deletes the current session. // Logout deletes the current session.
func (c *Client) Logout(ctx context.Context) error { func (c *Client) Logout(ctx context.Context) error {
req := internal.URL(c, internal.SessionPath).Request(http.MethodDelete) req := internal.URL(c, internal.SessionPath).Request(http.MethodDelete)

View File

@ -26,6 +26,7 @@ import (
"reflect" "reflect"
"strings" "strings"
"sync" "sync"
"time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/vmware/govmomi/vapi/internal" "github.com/vmware/govmomi/vapi/internal"
@ -33,12 +34,19 @@ import (
vim "github.com/vmware/govmomi/vim25/types" vim "github.com/vmware/govmomi/vim25/types"
) )
type session struct {
User string `json:"user"`
Created time.Time `json:"created_time"`
LastAccessed time.Time `json:"last_accessed_time"`
}
type handler struct { type handler struct {
*http.ServeMux *http.ServeMux
sync.Mutex sync.Mutex
Category map[string]*tags.Category Category map[string]*tags.Category
Tag map[string]*tags.Tag Tag map[string]*tags.Tag
Association map[string]map[internal.AssociatedObject]bool Association map[string]map[internal.AssociatedObject]bool
Session map[string]*session
} }
// New creates a vAPI simulator. // New creates a vAPI simulator.
@ -48,6 +56,7 @@ func New(u *url.URL, settings []vim.BaseOptionValue) (string, http.Handler) {
Category: make(map[string]*tags.Category), Category: make(map[string]*tags.Category),
Tag: make(map[string]*tags.Tag), Tag: make(map[string]*tags.Tag),
Association: make(map[string]map[internal.AssociatedObject]bool), Association: make(map[string]map[internal.AssociatedObject]bool),
Session: make(map[string]*session),
} }
handlers := []struct { handlers := []struct {
@ -60,6 +69,7 @@ func New(u *url.URL, settings []vim.BaseOptionValue) (string, http.Handler) {
{internal.TagPath, s.tag}, {internal.TagPath, s.tag},
{internal.TagPath + "/", s.tagID}, {internal.TagPath + "/", s.tagID},
{internal.AssociationPath, s.association}, {internal.AssociationPath, s.association},
{internal.AssociationPath + "/", s.associationID},
} }
for i := range handlers { for i := range handlers {
@ -68,6 +78,11 @@ func New(u *url.URL, settings []vim.BaseOptionValue) (string, http.Handler) {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
if !s.isAuthorized(r) {
w.WriteHeader(http.StatusUnauthorized)
return
}
h.m(w, r) h.m(w, r)
}) })
} }
@ -75,6 +90,99 @@ func New(u *url.URL, settings []vim.BaseOptionValue) (string, http.Handler) {
return internal.Path + "/", s return internal.Path + "/", s
} }
func (s *handler) isAuthorized(r *http.Request) bool {
if r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, internal.SessionPath) {
return true
}
id := r.Header.Get(internal.SessionCookieName)
if id == "" {
if cookie, err := r.Cookie(internal.SessionCookieName); err == nil {
id = cookie.Value
r.Header.Set(internal.SessionCookieName, id)
}
}
info, ok := s.Session[id]
if ok {
info.LastAccessed = time.Now()
}
return ok
}
func (s *handler) hasAuthorization(r *http.Request) (string, bool) {
u, p, ok := r.BasicAuth()
if ok { // user+pass auth
if u == "" || p == "" {
return u, false
}
return u, true
}
auth := r.Header.Get("Authorization")
return "TODO", strings.HasPrefix(auth, "SIGN ") // token auth
}
func (s *handler) findTag(e vim.VslmTagEntry) *tags.Tag {
for _, c := range s.Category {
if c.Name == e.ParentCategoryName {
for _, t := range s.Tag {
if t.Name == e.TagName && t.CategoryID == c.ID {
return t
}
}
}
}
return nil
}
// AttachedObjects is meant for internal use via simulator.Registry.tagManager
func (s *handler) AttachedObjects(tag vim.VslmTagEntry) ([]vim.ManagedObjectReference, vim.BaseMethodFault) {
t := s.findTag(tag)
if t == nil {
return nil, new(vim.NotFound)
}
var ids []vim.ManagedObjectReference
for id := range s.Association[t.ID] {
ids = append(ids, vim.ManagedObjectReference(id))
}
return ids, nil
}
// AttachedTags is meant for internal use via simulator.Registry.tagManager
func (s *handler) AttachedTags(ref vim.ManagedObjectReference) ([]vim.VslmTagEntry, vim.BaseMethodFault) {
oid := internal.AssociatedObject(ref)
var tags []vim.VslmTagEntry
for id, objs := range s.Association {
if objs[oid] {
tag := s.Tag[id]
cat := s.Category[tag.CategoryID]
tags = append(tags, vim.VslmTagEntry{
TagName: tag.Name,
ParentCategoryName: cat.Name,
})
}
}
return tags, nil
}
// AttachTag is meant for internal use via simulator.Registry.tagManager
func (s *handler) AttachTag(ref vim.ManagedObjectReference, tag vim.VslmTagEntry) vim.BaseMethodFault {
t := s.findTag(tag)
if t == nil {
return new(vim.NotFound)
}
s.Association[t.ID][internal.AssociatedObject(ref)] = true
return nil
}
// DetachTag is meant for internal use via simulator.Registry.tagManager
func (s *handler) DetachTag(id vim.ManagedObjectReference, tag vim.VslmTagEntry) vim.BaseMethodFault {
t := s.findTag(tag)
if t == nil {
return new(vim.NotFound)
}
delete(s.Association[t.ID], internal.AssociatedObject(id))
return nil
}
// ok responds with http.StatusOK and json encodes val if given. // ok responds with http.StatusOK and json encodes val if given.
func (s *handler) ok(w http.ResponseWriter, val ...interface{}) { func (s *handler) ok(w http.ResponseWriter, val ...interface{}) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
@ -136,23 +244,28 @@ func (s *handler) decode(r *http.Request, w http.ResponseWriter, val interface{}
} }
func (s *handler) session(w http.ResponseWriter, r *http.Request) { func (s *handler) session(w http.ResponseWriter, r *http.Request) {
var id string id := r.Header.Get(internal.SessionCookieName)
switch r.Method { switch r.Method {
case http.MethodPost: case http.MethodPost:
user, ok := s.hasAuthorization(r)
if !ok {
w.WriteHeader(http.StatusUnauthorized)
return
}
id = uuid.New().String() id = uuid.New().String()
// TODO: save session now := time.Now()
s.Session[id] = &session{user, now, now}
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: internal.SessionCookieName, Name: internal.SessionCookieName,
Value: id, Value: id,
}) })
s.ok(w) s.ok(w, id)
case http.MethodDelete: case http.MethodDelete:
// TODO: delete session delete(s.Session, id)
s.ok(w) s.ok(w)
case http.MethodGet: case http.MethodGet:
// TODO: test is session is valid s.ok(w, s.Session[id])
s.ok(w, id)
} }
} }
@ -161,8 +274,12 @@ func (s *handler) action(r *http.Request) string {
} }
func (s *handler) id(r *http.Request) string { func (s *handler) id(r *http.Request) string {
id := path.Base(r.URL.Path) base := path.Base(r.URL.Path)
return strings.TrimPrefix(id, "id:") id := strings.TrimPrefix(base, "id:")
if id == base {
return "" // trigger 404 Not Found w/o id: prefix
}
return id
} }
func newID(kind string) string { func newID(kind string) string {
@ -321,21 +438,7 @@ func (s *handler) association(w http.ResponseWriter, r *http.Request) {
return return
} }
if spec.TagID != "" {
if _, exists := s.Association[spec.TagID]; !exists {
log.Printf("association tag not found: %s", spec.TagID)
http.NotFound(w, r)
return
}
}
switch s.action(r) { switch s.action(r) {
case "attach":
s.Association[spec.TagID][*spec.ObjectID] = true
s.ok(w)
case "detach":
delete(s.Association[spec.TagID], *spec.ObjectID)
s.ok(w)
case "list-attached-tags": case "list-attached-tags":
var ids []string var ids []string
for id, objs := range s.Association { for id, objs := range s.Association {
@ -344,9 +447,37 @@ func (s *handler) association(w http.ResponseWriter, r *http.Request) {
} }
} }
s.ok(w, ids) s.ok(w, ids)
}
}
func (s *handler) associationID(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
id := s.id(r)
if _, exists := s.Association[id]; !exists {
log.Printf("association tag not found: %s", id)
http.NotFound(w, r)
return
}
var spec internal.Association
if !s.decode(r, w, &spec) {
return
}
switch s.action(r) {
case "attach":
s.Association[id][*spec.ObjectID] = true
s.ok(w)
case "detach":
delete(s.Association[id], *spec.ObjectID)
s.ok(w)
case "list-attached-objects": case "list-attached-objects":
var ids []internal.AssociatedObject var ids []internal.AssociatedObject
for id := range s.Association[spec.TagID] { for id := range s.Association[id] {
ids = append(ids, id) ids = append(ids, id)
} }
s.ok(w, ids) s.ok(w, ids)

View File

@ -42,8 +42,8 @@ func (c *Manager) AttachTag(ctx context.Context, tagID string, ref mo.Reference)
if err != nil { if err != nil {
return err return err
} }
spec := internal.NewAssociation(id, ref) spec := internal.NewAssociation(ref)
url := internal.URL(c, internal.AssociationPath).WithAction("attach") url := internal.URL(c, internal.AssociationPath).WithID(id).WithAction("attach")
return c.Do(ctx, url.Request(http.MethodPost, spec), nil) return c.Do(ctx, url.Request(http.MethodPost, spec), nil)
} }
@ -54,14 +54,14 @@ func (c *Manager) DetachTag(ctx context.Context, tagID string, ref mo.Reference)
if err != nil { if err != nil {
return err return err
} }
spec := internal.NewAssociation(id, ref) spec := internal.NewAssociation(ref)
url := internal.URL(c, internal.AssociationPath).WithAction("detach") url := internal.URL(c, internal.AssociationPath).WithID(id).WithAction("detach")
return c.Do(ctx, url.Request(http.MethodPost, spec), nil) return c.Do(ctx, url.Request(http.MethodPost, spec), nil)
} }
// ListAttachedTags fetches the array of tag IDs attached to the given object. // ListAttachedTags fetches the array of tag IDs attached to the given object.
func (c *Manager) ListAttachedTags(ctx context.Context, ref mo.Reference) ([]string, error) { func (c *Manager) ListAttachedTags(ctx context.Context, ref mo.Reference) ([]string, error) {
spec := internal.NewAssociation("", ref) spec := internal.NewAssociation(ref)
url := internal.URL(c, internal.AssociationPath).WithAction("list-attached-tags") url := internal.URL(c, internal.AssociationPath).WithAction("list-attached-tags")
var res []string var res []string
return res, c.Do(ctx, url.Request(http.MethodPost, spec), &res) return res, c.Do(ctx, url.Request(http.MethodPost, spec), &res)
@ -91,12 +91,9 @@ func (c *Manager) ListAttachedObjects(ctx context.Context, tagID string) ([]mo.R
if err != nil { if err != nil {
return nil, err return nil, err
} }
spec := internal.Association{ url := internal.URL(c, internal.AssociationPath).WithID(id).WithAction("list-attached-objects")
TagID: id,
}
url := internal.URL(c, internal.AssociationPath).WithAction("list-attached-objects")
var res []internal.AssociatedObject var res []internal.AssociatedObject
if err := c.Do(ctx, url.Request(http.MethodPost, spec), &res); err != nil { if err := c.Do(ctx, url.Request(http.MethodPost, nil), &res); err != nil {
return nil, err return nil, err
} }

View File

@ -137,6 +137,30 @@ func (c *Manager) GetTag(ctx context.Context, id string) (*Tag, error) {
} }
// GetTagForCategory fetches the tag information for the given identifier in the given category.
func (c *Manager) GetTagForCategory(ctx context.Context, id, category string) (*Tag, error) {
if category == "" {
return c.GetTag(ctx, id)
}
ids, err := c.ListTagsForCategory(ctx, category)
if err != nil {
return nil, err
}
for _, id := range ids {
tag, err := c.GetTag(ctx, id)
if err != nil {
return nil, fmt.Errorf("get tag for category %s %s: %s", category, id, err)
}
if tag.ID == id || tag.Name == id {
return tag, nil
}
}
return nil, fmt.Errorf("tag %q not found in category %q", id, category)
}
// ListTags returns all tag IDs in the system. // ListTags returns all tag IDs in the system.
func (c *Manager) ListTags(ctx context.Context) ([]string, error) { func (c *Manager) ListTags(ctx context.Context) ([]string, error) {
url := internal.URL(c, internal.TagPath) url := internal.URL(c, internal.TagPath)

View File

@ -218,6 +218,9 @@ func assignValue(val reflect.Value, fi []int, pv reflect.Value) {
} else { } else {
panic(fmt.Sprintf("type %s doesn't implement %s", pt.Name(), rt.Name())) panic(fmt.Sprintf("type %s doesn't implement %s", pt.Name(), rt.Name()))
} }
} else if rt.Kind() == reflect.Struct && pt.Kind() == reflect.Ptr {
pv = pv.Elem()
pt = pv.Type()
} }
if pt.AssignableTo(rt) { if pt.AssignableTo(rt) {

View File

@ -26,11 +26,11 @@ import (
) )
type readerReport struct { type readerReport struct {
t time.Time pos int64 // Keep first to ensure 64-bit alignment
size int64 // Keep first to ensure 64-bit alignment
bps *uint64 // Keep first to ensure 64-bit alignment
pos int64 t time.Time
size int64
bps *uint64
err error err error
} }

View File

@ -656,6 +656,8 @@ func (c *Client) Upload(ctx context.Context, f io.Reader, u *url.URL, param *Upl
return err return err
} }
defer res.Body.Close()
switch res.StatusCode { switch res.StatusCode {
case http.StatusOK: case http.StatusOK:
case http.StatusCreated: case http.StatusCreated:

View File

@ -786,6 +786,7 @@ func (b *DvsFilterConfig) GetDvsFilterConfig() *DvsFilterConfig { return b }
type BaseDvsFilterConfig interface { type BaseDvsFilterConfig interface {
GetDvsFilterConfig() *DvsFilterConfig GetDvsFilterConfig() *DvsFilterConfig
GetDvsTrafficFilterConfig() *DvsTrafficFilterConfig
} }
func init() { func init() {
@ -828,12 +829,21 @@ func (b *DvsNetworkRuleQualifier) GetDvsNetworkRuleQualifier() *DvsNetworkRuleQu
type BaseDvsNetworkRuleQualifier interface { type BaseDvsNetworkRuleQualifier interface {
GetDvsNetworkRuleQualifier() *DvsNetworkRuleQualifier GetDvsNetworkRuleQualifier() *DvsNetworkRuleQualifier
GetDvsIpNetworkRuleQualifier() *DvsIpNetworkRuleQualifier
} }
func init() { func init() {
t["BaseDvsNetworkRuleQualifier"] = reflect.TypeOf((*DvsNetworkRuleQualifier)(nil)).Elem() t["BaseDvsNetworkRuleQualifier"] = reflect.TypeOf((*DvsNetworkRuleQualifier)(nil)).Elem()
} }
func (b *DvsIpNetworkRuleQualifier) GetDvsIpNetworkRuleQualifier() *DvsIpNetworkRuleQualifier {
return b
}
type BaseDvsIpNetworkRuleQualifier interface {
GetDvsIpNetworkRuleQualifier() *DvsIpNetworkRuleQualifier
}
func (b *DvsTrafficFilterConfig) GetDvsTrafficFilterConfig() *DvsTrafficFilterConfig { return b } func (b *DvsTrafficFilterConfig) GetDvsTrafficFilterConfig() *DvsTrafficFilterConfig { return b }
type BaseDvsTrafficFilterConfig interface { type BaseDvsTrafficFilterConfig interface {