From f45b6770c16090645b7256e87b21c224048008bc Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Fri, 24 Mar 2017 20:25:57 -0700 Subject: [PATCH] Resource suggestion fixes for vic-machine - Support vic-machine create targeting Datacenter within an inventory folder. - Simplify compute-resource suggestions. Removes quite a bit of code, which also did not work when clusters/hosts were within inventory folders. Some of the removed code was working around the old govmomi Finder "list mode" limitations. - Add vcsim based tests for vic-machine validation against multiple resources of all types, within inventory folders. These tests also cover most of the error paths. - Add LicenseManager support to vcsim Fixes #4203 --- cmd/vic-machine/common/compute.go | 2 +- lib/install/validate/compute.go | 203 ++++-------------- lib/install/validate/storage.go | 3 +- lib/install/validate/validator.go | 99 +-------- lib/install/validate/validator_test.go | 179 +++++++++++---- pkg/vsphere/compute/rp.go | 20 -- pkg/vsphere/compute/rp_test.go | 18 -- pkg/vsphere/simulator/license_manager.go | 74 +++++++ pkg/vsphere/simulator/license_manager_test.go | 57 +++++ pkg/vsphere/simulator/service_instance.go | 1 + pkg/vsphere/simulator/simulator.go | 1 + 11 files changed, 324 insertions(+), 333 deletions(-) create mode 100644 pkg/vsphere/simulator/license_manager.go create mode 100644 pkg/vsphere/simulator/license_manager_test.go diff --git a/cmd/vic-machine/common/compute.go b/cmd/vic-machine/common/compute.go index fb47d3dd7b..0c9f2f91aa 100644 --- a/cmd/vic-machine/common/compute.go +++ b/cmd/vic-machine/common/compute.go @@ -39,7 +39,7 @@ func (c *Compute) ComputeFlagsNoName() []cli.Flag { cli.StringFlag{ Name: "compute-resource, r", Value: "", - Usage: "Compute resource path, e.g. myCluster/Resources/myRP. Default to /Resources", + Usage: "Compute resource path, e.g. myCluster", Destination: &c.ComputeResourcePath, }, } diff --git a/lib/install/validate/compute.go b/lib/install/validate/compute.go index 8366931fb2..78c2263396 100644 --- a/lib/install/validate/compute.go +++ b/lib/install/validate/compute.go @@ -15,18 +15,13 @@ package validate import ( - "fmt" - "path" - "path/filepath" - "sort" - "strings" - "context" log "github.com/Sirupsen/logrus" "github.com/vmware/govmomi/find" "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/vic/lib/config" "github.com/vmware/vic/lib/install/data" "github.com/vmware/vic/pkg/errors" @@ -36,11 +31,7 @@ import ( func (v *Validator) compute(ctx context.Context, input *data.Data, conf *config.VirtualContainerHostConfigSpec) { defer trace.End(trace.Begin("")) - // Compute - - // compute resource looks like / - // this should map to /datacenter-name/host//Resources/ - // we need to validate that exists and then that the combined path exists. + // ComputeResourcePath should resolve to a ComputeResource, ClusterComputeResource or ResourcePool pool, err := v.ResourcePoolHelper(ctx, input.ComputeResourcePath) v.NoteIssue(err) @@ -48,29 +39,6 @@ func (v *Validator) compute(ctx context.Context, input *data.Data, conf *config. return } - // stash the pool for later use - v.ResourcePoolPath = pool.InventoryPath - - // some hoops for while we're still using session package - v.Session.Pool = pool - v.Session.PoolPath = pool.InventoryPath - v.Session.ClusterPath = v.inventoryPathToCluster(pool.InventoryPath) - - clusters, err := v.Session.Finder.ComputeResourceList(v.Context, v.Session.ClusterPath) - if err != nil { - log.Errorf("Unable to acquire reference to cluster %q: %s", path.Base(v.Session.ClusterPath), err) - v.NoteIssue(err) - return - } - - if len(clusters) != 1 { - err := fmt.Errorf("Unable to acquire unambiguous reference to cluster %q", path.Base(v.Session.ClusterPath)) - log.Error(err) - v.NoteIssue(err) - } - - v.Session.Cluster = clusters[0] - // TODO: for vApp creation assert that the name doesn't exist // TODO: for RP creation assert whatever we decide about the pool - most likely that it's empty } @@ -81,172 +49,79 @@ func (v *Validator) ResourcePoolHelper(ctx context.Context, path string) (*objec defer trace.End(trace.Begin(path)) // if compute-resource is unspecified is there a default - if path == "" || path == "/" { + if path == "" { if v.Session.Pool != nil { log.Debugf("Using default resource pool for compute resource: %q", v.Session.Pool.InventoryPath) return v.Session.Pool, nil } // if no path specified and no default available the show all - v.suggestComputeResource("*") + v.suggestComputeResource() return nil, errors.New("No unambiguous default compute resource available: --compute-resource must be specified") } - ipath := v.computePathToInventoryPath(path) - log.Debugf("Converted original path %q to %q", path, ipath) - - // first try the path directly without any processing - pools, err := v.Session.Finder.ResourcePoolList(ctx, path) + pool, err := v.Session.Finder.ResourcePool(ctx, path) if err != nil { log.Debugf("Failed to look up compute resource as absolute path %q: %s", path, err) if _, ok := err.(*find.NotFoundError); !ok { // we return err directly here so we can check the type return nil, err } - - // if it starts with datacenter then we know it's absolute and invalid - if strings.HasPrefix(path, "/"+v.Session.DatacenterPath) { - v.suggestComputeResource(path) - return nil, err - } } - if len(pools) == 0 { - // assume it's a cluster specifier - that's the formal case, e.g. /cluster/resource/pool - // not /cluster/Resources/resource/pool - // everything from now on will use this assumption + var compute *object.ComputeResource - pools, err = v.Session.Finder.ResourcePoolList(ctx, ipath) + if pool == nil { + // check if its a ComputeResource or ClusterComputeResource + + compute, err = v.Session.Finder.ComputeResource(ctx, path) if err != nil { - log.Debugf("failed to look up compute resource as cluster path %q: %s", ipath, err) - if _, ok := err.(*find.NotFoundError); !ok { - // we return err directly here so we can check the type - return nil, err + if _, ok := err.(*find.NotFoundError); ok { + v.suggestComputeResource() } - } - } - - if len(pools) == 1 { - log.Debugf("Selected compute resource %q", pools[0].InventoryPath) - return pools[0], nil - } - - // both cases we want to suggest options - v.suggestComputeResource(ipath) - - if len(pools) == 0 { - log.Debugf("no such compute resource %q", path) - // we return err directly here so we can check the type - return nil, err - } - - // TODO: error about required disabmiguation and list entries in nets - return nil, errors.New("ambiguous compute resource " + path) -} - -func (v *Validator) suggestComputeResource(path string) { - defer trace.End(trace.Begin(path)) - - log.Infof("Suggesting valid values for --compute-resource based on %q", path) - - // allow us to work on inventory paths - path = v.computePathToInventoryPath(path) - var matches []string - for matches = nil; matches == nil; matches = v.findValidPool(path) { - // back up the path until we find a pool - newpath := filepath.Dir(path) - if newpath == "." { - // filepath.Dir returns . which has no meaning for us - newpath = "/" - } - if newpath == path { - break + return nil, err } - path = newpath - } - - if matches == nil { - // Backing all the way up didn't help - log.Info("Failed to find resource pool in the provided path, showing all top level resource pools.") - matches = v.findValidPool("*") - } - if matches != nil { - // we've collected recommendations - displayname - log.Info("Suggested values for --compute-resource:") - for _, p := range matches { - log.Infof(" %q", v.inventoryPathToComputePath(p)) + // Use the default pool + pool, err = compute.ResourcePool(ctx) + if err != nil { + return nil, err } - return - } - - log.Info("No resource pools found") -} - -func (v *Validator) findValidPool(path string) []string { - defer trace.End(trace.Begin(path)) - - // list pools in path - matches := v.listResourcePools(path) - if matches != nil { - sort.Strings(matches) - return matches - } - - // no pools in path, but if path is cluster, list pools in cluster - clusters, err := v.Session.Finder.ComputeResourceList(v.Context, path) - if len(clusters) == 0 { - // not a cluster - log.Debugf("Path %q does not identify a cluster (or clusters) or the list could not be obtained: %s", path, err) - return nil - } + } else { + // TODO: add an object.ResourcePool.Owner method (see compute.ResourcePool.GetCluster) + var p mo.ResourcePool - if len(clusters) > 1 { - log.Debugf("Suggesting clusters as there are multiple matches") - matches = make([]string, len(clusters)) - for i, c := range clusters { - matches[i] = c.InventoryPath + if err = pool.Properties(ctx, pool.Reference(), []string{"owner"}, &p); err != nil { + log.Errorf("Unable to get cluster of resource pool %s: %s", pool.Name(), err) + return nil, err } - sort.Strings(matches) - return matches - } - log.Debugf("Suggesting pools for cluster %q", clusters[0].Name()) - matches = v.listResourcePools(fmt.Sprintf("%s/Resources/*", clusters[0].InventoryPath)) - if matches == nil { - // no child pools so recommend cluster directly - return []string{clusters[0].InventoryPath} + compute = object.NewComputeResource(pool.Client(), p.Owner) } - return matches -} - -func (v *Validator) listResourcePools(path string) []string { - defer trace.End(trace.Begin(path)) - - pools, err := v.Session.Finder.ResourcePoolList(v.Context, path+"/*") - if err != nil { - log.Debugf("Unable to list pools for %q: %s", path, err) - return nil - } + // stash the pool for later use + v.ResourcePoolPath = pool.InventoryPath - if len(pools) == 0 { - return nil - } + // some hoops for while we're still using session package + v.Session.Pool = pool + v.Session.PoolPath = pool.InventoryPath - matches := make([]string, len(pools)) - for i, p := range pools { - matches[i] = p.InventoryPath - } + v.Session.Cluster = compute + v.Session.ClusterPath = compute.InventoryPath - return matches + return pool, nil } -func (v *Validator) GetResourcePool(input *data.Data) (*object.ResourcePool, error) { +func (v *Validator) suggestComputeResource() { defer trace.End(trace.Begin("")) - return v.ResourcePoolHelper(v.Context, input.ComputeResourcePath) + compute, _ := v.Session.Finder.ComputeResourceList(v.Context, "*") + + log.Info("Suggested values for --compute-resource:") + for _, c := range compute { + log.Infof(" %q", c.Name()) + } } func (v *Validator) ValidateCompute(ctx context.Context, input *data.Data, computeRequired bool) (*config.VirtualContainerHostConfigSpec, error) { diff --git a/lib/install/validate/storage.go b/lib/install/validate/storage.go index a31e6cadf0..9731d88704 100644 --- a/lib/install/validate/storage.go +++ b/lib/install/validate/storage.go @@ -37,7 +37,7 @@ func (v *Validator) storage(ctx context.Context, input *data.Data, conf *config. // Image Store imageDSpath, ds, err := v.DatastoreHelper(ctx, input.ImageDatastorePath, "", "--image-store") - if imageDSpath == nil { + if err != nil { v.NoteIssue(err) return } @@ -47,7 +47,6 @@ func (v *Validator) storage(ctx context.Context, input *data.Data, conf *config. imageDSpath.Path = input.DisplayName } - v.NoteIssue(err) if ds != nil { v.SetDatastore(ds, imageDSpath) conf.AddImageStore(imageDSpath) diff --git a/lib/install/validate/validator.go b/lib/install/validate/validator.go index cc176b5f42..bed7f40792 100644 --- a/lib/install/validate/validator.go +++ b/lib/install/validate/validator.go @@ -19,6 +19,7 @@ import ( "crypto/x509" "fmt" "net/url" + "path" "strings" "context" @@ -81,7 +82,7 @@ func NewValidator(ctx context.Context, input *data.Data) (*Validator, error) { v := &Validator{} v.Context = ctx - tURL := input.URL + tURL := *input.URL // normalize the path - strip trailing / tURL.Path = strings.TrimSuffix(tURL.Path, "/") @@ -99,7 +100,7 @@ func NewValidator(ctx context.Context, input *data.Data) (*Validator, error) { if tURL.Scheme == "https" && input.Thumbprint == "" { var cert object.HostCertificateInfo - if err = cert.FromURL(tURL, new(tls.Config)); err != nil { + if err = cert.FromURL(&tURL, new(tls.Config)); err != nil { return nil, err } @@ -124,6 +125,7 @@ func NewValidator(ctx context.Context, input *data.Data) (*Validator, error) { // if a datacenter was specified, set it v.DatacenterPath = tURL.Path if v.DatacenterPath != "" { + v.DatacenterPath = strings.TrimPrefix(v.DatacenterPath, "/") sessionconfig.DatacenterPath = v.DatacenterPath // path needs to be stripped before we can use it as a service url tURL.Path = "" @@ -143,11 +145,10 @@ func NewValidator(ctx context.Context, input *data.Data) (*Validator, error) { finder := find.NewFinder(v.Session.Client.Client, false) v.Session.Finder = finder - v.Session.Populate(ctx) + // Intentionally ignore any error returned by Populate + _, _ = v.Session.Populate(ctx) - // only allow the datacenter to be specified in the target url, if any - pElems := strings.Split(v.DatacenterPath, "/") - if len(pElems) > 2 { + if strings.Contains(sessionconfig.DatacenterPath, "/") { detail := "--target should only specify datacenter in the path (e.g. https://addr/datacenter) - specify cluster, resource pool, or folder with --compute-resource" log.Error(detail) v.suggestDatacenter() @@ -195,7 +196,7 @@ func (v *Validator) datacenter() error { } var detail string if v.DatacenterPath != "" { - detail = fmt.Sprintf("Datacenter %q in --target is not found", strings.TrimPrefix(v.DatacenterPath, "/")) + detail = fmt.Sprintf("Datacenter %q in --target is not found", path.Base(v.DatacenterPath)) } else { // this means multiple datacenter exists, but user did not specify it in --target detail = "Datacenter must be specified in --target (e.g. https://addr/datacenter)" @@ -617,90 +618,6 @@ func intersect(one []*object.HostSystem, two []*object.HostSystem) []*object.Hos return result } -func (v *Validator) computePathToInventoryPath(path string) string { - defer trace.End(trace.Begin(path)) - - // if it opens with the datacenter prefix the assume it's an absolute - if strings.HasPrefix(path, v.DatacenterPath) { - log.Debugf("Path is treated as absolute given datacenter prefix %q", v.DatacenterPath) - return path - } - - parts := []string{ - v.DatacenterPath, // has leading / - "host", - "*", // easy for ESX - "Resources", - } - - // normalize the path - strip leading / - path = strings.TrimPrefix(path, "/") - - // if it's vCenter the first element is the cluster or host, then resource pool path - if v.IsVC() { - pElem := strings.SplitN(path, "/", 2) - if pElem[0] != "" { - parts[2] = pElem[0] - } - if len(pElem) > 1 { - parts = append(parts, pElem[1]) - } - } else if path != "" { - // for ESX, first element is a pool - parts = append(parts, path) - } - - return strings.Join(parts, "/") -} - -func (v *Validator) inventoryPathToComputePath(path string) string { - defer trace.End(trace.Begin(path)) - - // sanity check datacenter - if !strings.HasPrefix(path, v.DatacenterPath) { - log.Debugf("Expected path to be within target datacenter %q: %q", v.DatacenterPath, path) - v.NoteIssue(errors.New("inventory path was not in datacenter scope")) - return "" - } - - // inventory path is always /dc/host/computeResource/Resources/path/to/pool - // NOTE: all of the indexes are +1 because the leading / means we have an empty string for [0] - pElems := strings.Split(path, "/") - if len(pElems) < 4 { - log.Debugf("Expected path to be fully qualified, e.g. /dcName/host/clusterName/Resources/poolName: %s", path) - v.NoteIssue(errors.New("inventory path format was not recognised")) - return "" - } - - if len(pElems) == 4 || len(pElems) == 5 { - // cluster only or cluster/Resources - return pElems[3] - } - - // messy but avoid reallocation - overwrite Resources with cluster name - pElems[4] = pElems[3] - - // /dc/host/cluster/Resources/path/to/pool - return strings.Join(pElems[4:], "/") -} - -// inventoryPathToCluster is a convenience method that will return the cluster -// path prefix or "" in the case of unexpected path structure -func (v *Validator) inventoryPathToCluster(path string) string { - defer trace.End(trace.Begin(path)) - - // inventory path is always /dc/host/computeResource/Resources/path/to/pool - pElems := strings.Split(path, "/") - if len(pElems) < 3 { - log.Debugf("Expected path to be fully qualified, e.g. /dcName/host/clusterName/Resources/poolName: %s", path) - v.NoteIssue(errors.New("inventory path format was not recognised")) - return "" - } - - // /dc/host/cluster/Resources/path/to/pool - return strings.Join(pElems[:4], "/") -} - func (v *Validator) IsVC() bool { return v.isVC } diff --git a/lib/install/validate/validator_test.go b/lib/install/validate/validator_test.go index 736e9d3207..38c71eca0d 100644 --- a/lib/install/validate/validator_test.go +++ b/lib/install/validate/validator_test.go @@ -15,8 +15,13 @@ package validate import ( + "context" + "crypto/tls" + "fmt" "net" "net/url" + "os" + "strings" "testing" log "github.com/Sirupsen/logrus" @@ -30,9 +35,6 @@ import ( "github.com/vmware/vic/pkg/trace" "github.com/vmware/vic/pkg/vsphere/session" "github.com/vmware/vic/pkg/vsphere/simulator" - - "context" - "fmt" ) type TestValidator struct { @@ -111,39 +113,6 @@ func TestParseURL(t *testing.T) { } } -func TestPathConversionESX(t *testing.T) { - v := &TestValidator{} - - v.DatacenterPath = "/ha-datacenter" - - ipath := v.computePathToInventoryPath("/") - assert.Equal(t, "/ha-datacenter/host/*/Resources", ipath, "Expected top level resource pool") - cpath := v.inventoryPathToComputePath(ipath) - assert.Equal(t, "*", cpath, "Expected root resource specifier") - - ipath = v.computePathToInventoryPath("*") - assert.Equal(t, "/ha-datacenter/host/*/Resources/*", ipath, "Expected top level resource pool") - cpath = v.inventoryPathToComputePath(ipath) - assert.Equal(t, "*/*", cpath, "Expected top level wildcard specifier") -} - -func TestSampleConversion(t *testing.T) { - v := &TestValidator{} - v.DatacenterPath = "/ha-datacenter" - - translations := map[string]string{ - "/": "/ha-datacenter/host/*/Resources", - "*": "/ha-datacenter/host/*/Resources/*", - "testpool": "/ha-datacenter/host/*/Resources/testpool", - "test/deep/path": "/ha-datacenter/host/*/Resources/test/deep/path", - } - - for in, expected := range translations { - ipath := v.computePathToInventoryPath(in) - assert.Equal(t, expected, ipath, "Translation did not match") - } -} - func TestMain(t *testing.T) { log.SetLevel(log.DebugLevel) trace.Logger.Level = log.DebugLevel @@ -277,7 +246,7 @@ func testCompute(v *Validator, input *data.Data, t *testing.T) *config.VirtualCo hasErr bool }{ {"DC0_C0/Resources/validator", true, false}, - {"DC0_C0/validator", true, false}, + {"DC0_C0/validator", true, true}, {"validator", true, false}, {"DC0_C0/test", true, true}, {"/DC0_C1/test", true, true}, @@ -428,3 +397,139 @@ func testStorage(v *Validator, input *data.Data, conf *config.VirtualContainerHo v.issues = nil } } + +func TestValidateWithFolders(t *testing.T) { + log.SetLevel(log.InfoLevel) + ctx := context.Background() + + m := simulator.VPX() + m.Datacenter = 3 + m.Folder = 2 + m.Datastore = 2 + m.ClusterHost = 3 + m.Pool = 1 + + defer m.Remove() + + err := m.Create() + if err != nil { + t.Fatal(err) + } + + m.Service.TLS = new(tls.Config) + s := m.Service.NewServer() + defer s.Close() + + input := data.NewData() + input.URL = &url.URL{ + Scheme: s.URL.Scheme, + Host: s.URL.Host, + } + + newShouldFail := true + license := simulator.EvalLicense + simulator.EvalLicense.Properties = nil // erase features + + var validator *Validator + + // Cover various failure paths while we're at it + steps := []func(){ + func() {}, + func() { + input.Thumbprint = "nope" + }, + func() { + input.Force = true + input.Thumbprint = "" + }, + func() { + input.Force = false + input.Thumbprint = s.CertificateInfo().ThumbprintSHA1 + }, + func() { + input.URL.User = s.URL.User + newShouldFail = false + }, + func() { + input.URL.Path = "/enoent" // Datacenter "enoent" in --target is not found + }, + func() { + input.URL.Path = "/DC1/sorry" // --target should only specify datacenter in the path + }, + func() { + input.URL.Path = "/DC1" // ok + }, + func() { + input.ComputeResourcePath = "enoent" + }, + func() { + input.ComputeResourcePath = "DC1_C0" + }, + func() { + input.PublicNetwork.Name = "enoent" + }, + func() { + input.PublicNetwork.Name = "VM Network" + input.ManagementNetwork.Name = input.PublicNetwork.Name + input.ClientNetwork.Name = input.PublicNetwork.Name + input.BridgeNetworkName = "DC1_DVPG0" + }, + func() { + input.ScratchSize = "10GB" + p, _ := s.URL.User.Password() + input.OpsPassword = &p + }, + func() { + input.ImageDatastorePath = "enoent" + }, + func() { + input.ImageDatastorePath = "LocalDS_*" // > 1 + }, + func() { + input.ImageDatastorePath = "LocalDS_0" + }, + func() { + // TODO: volume + }, + func() { + input.OpsUser = s.URL.User.Username() + }, + func() { + simulator.EvalLicense.Properties = license.Properties // restore license features + }, + } + + for i, step := range steps { + if testing.Verbose() { + fmt.Fprintf(os.Stderr, "TestValidateVPX(%d)%s\n", i, strings.Repeat(".", 30)) + } + step() + + validator, err = NewValidator(ctx, input) + if err != nil { + continue + } + + if newShouldFail { + t.Fatalf("%d: expected error", i) + } + + validator.DisableFirewallCheck = true + + _, err = validator.Validate(ctx, input) + if i == len(steps)-1 { + if err != nil { + t.Fatal(err) + } + } else { + if err == nil { + t.Fatal("expected error") + } + } + } + + _, err = validator.ValidateTarget(ctx, input) + if err != nil { + t.Fatal(err) + } +} diff --git a/pkg/vsphere/compute/rp.go b/pkg/vsphere/compute/rp.go index d1d274b70c..ad66a9cd87 100644 --- a/pkg/vsphere/compute/rp.go +++ b/pkg/vsphere/compute/rp.go @@ -15,8 +15,6 @@ package compute import ( - "path" - log "github.com/Sirupsen/logrus" "context" @@ -49,24 +47,6 @@ func NewResourcePool(ctx context.Context, session *session.Session, moref types. } } -func FindResourcePool(ctx context.Context, s *session.Session, name string) (*ResourcePool, error) { - var err error - - if name != "" { - if !path.IsAbs(name) { - name = path.Join(s.Cluster.InventoryPath, "Resources", name) - } - } else { - name = path.Join(s.Cluster.InventoryPath, "Resources") - } - - pool, err := s.Finder.ResourcePoolOrDefault(ctx, name) - if err != nil { - return nil, err - } - return NewResourcePool(ctx, s, pool.Reference()), nil -} - func (rp *ResourcePool) GetChildrenVMs(ctx context.Context, s *session.Session) ([]*vm.VirtualMachine, error) { var err error var mrp mo.ResourcePool diff --git a/pkg/vsphere/compute/rp_test.go b/pkg/vsphere/compute/rp_test.go index 400ffc7440..3cea3e9b73 100644 --- a/pkg/vsphere/compute/rp_test.go +++ b/pkg/vsphere/compute/rp_test.go @@ -19,8 +19,6 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/vmware/vic/pkg/vsphere/session" "github.com/vmware/vic/pkg/vsphere/simulator" @@ -57,7 +55,6 @@ func TestMain(t *testing.T) { defer sess.Logout(ctx) testGetChildrenVMs(ctx, sess, t) testGetChildVM(ctx, sess, t) - testFindResourcePool(ctx, sess, t) testGetCluster(ctx, sess, t) } } @@ -129,21 +126,6 @@ func testGetChildVM(ctx context.Context, sess *session.Session, t *testing.T) { } } -func testFindResourcePool(ctx context.Context, sess *session.Session, t *testing.T) { - tests := []struct { - name string - hasErr bool - }{ - {"", false}, - {"random123", true}, - } - - for _, test := range tests { - _, err := FindResourcePool(ctx, sess, test.name) - assert.Equal(t, test.hasErr, err != nil) - } -} - func testGetCluster(ctx context.Context, sess *session.Session, t *testing.T) { rp := NewResourcePool(ctx, sess, sess.Pool.Reference()) cluster, err := rp.GetCluster(ctx) diff --git a/pkg/vsphere/simulator/license_manager.go b/pkg/vsphere/simulator/license_manager.go new file mode 100644 index 0000000000..1b4eb02e1d --- /dev/null +++ b/pkg/vsphere/simulator/license_manager.go @@ -0,0 +1,74 @@ +// Copyright 2017 VMware, Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package simulator + +import ( + "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 LicenseManager struct { + mo.LicenseManager +} + +func NewLicenseManager(ref types.ManagedObjectReference) object.Reference { + m := &LicenseManager{} + m.Self = ref + am := Map.Put(&LicenseAssignmentManager{}).Reference() + m.LicenseAssignmentManager = &am + return m +} + +type LicenseAssignmentManager struct { + mo.LicenseAssignmentManager +} + +var EvalLicense = types.LicenseManagerLicenseInfo{ + LicenseKey: "00000-00000-00000-00000-00000", + EditionKey: "eval", + Name: "Evaluation Mode", + Properties: []types.KeyAnyValue{ + { + Key: "feature", + Value: types.KeyValue{ + Key: "serialuri:2", + Value: "Remote virtual Serial Port Concentrator", + }, + }, + { + Key: "feature", + Value: types.KeyValue{ + Key: "dvs", + Value: "vSphere Distributed Switch", + }, + }, + }, +} + +func (m *LicenseAssignmentManager) QueryAssignedLicenses(req *types.QueryAssignedLicenses) soap.HasFault { + return &methods.QueryAssignedLicensesBody{ + Res: &types.QueryAssignedLicensesResponse{ + Returnval: []types.LicenseAssignmentManagerLicenseAssignment{ + { + EntityId: req.EntityId, + AssignedLicense: EvalLicense, + }, + }, + }, + } +} diff --git a/pkg/vsphere/simulator/license_manager_test.go b/pkg/vsphere/simulator/license_manager_test.go new file mode 100644 index 0000000000..6023a30cf8 --- /dev/null +++ b/pkg/vsphere/simulator/license_manager_test.go @@ -0,0 +1,57 @@ +// Copyright 2017 VMware, Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package simulator + +import ( + "context" + "reflect" + "testing" + + "github.com/vmware/govmomi/license" + "github.com/vmware/vic/pkg/vsphere/simulator/esx" +) + +func TestLicenseManager(t *testing.T) { + m := ESX() + + defer m.Remove() + + err := m.Create() + if err != nil { + t.Fatal(err) + } + + ctx := context.Background() + c := m.Service.client + + lm := license.NewManager(c) + am, err := lm.AssignmentManager(ctx) + if err != nil { + t.Fatal(err) + } + + la, err := am.QueryAssigned(ctx, esx.HostSystem.Reference().Value) + if err != nil { + t.Fatal(err) + } + + if len(la) != 1 { + t.Fatal("no licenses") + } + + if !reflect.DeepEqual(la[0].AssignedLicense, EvalLicense) { + t.Fatal("invalid license") + } +} diff --git a/pkg/vsphere/simulator/service_instance.go b/pkg/vsphere/simulator/service_instance.go index dadbc55d2f..386d4799ec 100644 --- a/pkg/vsphere/simulator/service_instance.go +++ b/pkg/vsphere/simulator/service_instance.go @@ -54,6 +54,7 @@ func NewServiceInstance(content types.ServiceContent, folder mo.Folder) *Service NewSessionManager(*s.Content.SessionManager), NewPropertyCollector(s.Content.PropertyCollector), NewFileManager(*s.Content.FileManager), + NewLicenseManager(*s.Content.LicenseManager), NewSearchIndex(*s.Content.SearchIndex), } diff --git a/pkg/vsphere/simulator/simulator.go b/pkg/vsphere/simulator/simulator.go index b4bea5ecb1..a2a08244de 100644 --- a/pkg/vsphere/simulator/simulator.go +++ b/pkg/vsphere/simulator/simulator.go @@ -278,6 +278,7 @@ func (s *Service) NewServer() *Server { // Using NewUnstartedServer() instead of NewServer(), // for use in main.go, where Start() blocks, we can still set ServiceHostName ts := httptest.NewUnstartedServer(mux) + ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0) // we don't need this noise u := &url.URL{ Scheme: "http",