Fix the dependency issue (#231)
This commit is contained in:
142
vendor/github.com/vmware/vic/pkg/vsphere/compute/rp.go
generated
vendored
142
vendor/github.com/vmware/vic/pkg/vsphere/compute/rp.go
generated
vendored
@@ -1,142 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package compute
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
// ResourcePool struct defines the ResourcePool which provides additional
|
||||
// VIC specific methods over object.ResourcePool as well as keeps some state
|
||||
type ResourcePool struct {
|
||||
*object.ResourcePool
|
||||
|
||||
*session.Session
|
||||
}
|
||||
|
||||
// NewResourcePool returns a New ResourcePool object
|
||||
func NewResourcePool(ctx context.Context, session *session.Session, moref types.ManagedObjectReference) *ResourcePool {
|
||||
return &ResourcePool{
|
||||
ResourcePool: object.NewResourcePool(
|
||||
session.Vim25(),
|
||||
moref,
|
||||
),
|
||||
Session: session,
|
||||
}
|
||||
}
|
||||
|
||||
func (rp *ResourcePool) GetChildrenVMs(ctx context.Context, s *session.Session) ([]*vm.VirtualMachine, error) {
|
||||
op := trace.FromContext(ctx, "GetChildrenVMs")
|
||||
|
||||
var err error
|
||||
var mrp mo.ResourcePool
|
||||
var vms []*vm.VirtualMachine
|
||||
|
||||
if err = rp.Properties(op, rp.Reference(), []string{"vm"}, &mrp); err != nil {
|
||||
op.Errorf("Unable to get children vm of resource pool %s: %s", rp.Name(), err)
|
||||
return vms, err
|
||||
}
|
||||
|
||||
for _, o := range mrp.Vm {
|
||||
v := vm.NewVirtualMachine(op, s, o)
|
||||
vms = append(vms, v)
|
||||
}
|
||||
return vms, nil
|
||||
}
|
||||
|
||||
func (rp *ResourcePool) GetChildVM(ctx context.Context, s *session.Session, name string) (*vm.VirtualMachine, error) {
|
||||
op := trace.FromContext(ctx, "GetChildVM")
|
||||
|
||||
searchIndex := object.NewSearchIndex(s.Client.Client)
|
||||
child, err := searchIndex.FindChild(op, rp.Reference(), name)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("Unable to find VM(%s): %s", name, err.Error())
|
||||
}
|
||||
if child == nil {
|
||||
return nil, nil
|
||||
}
|
||||
// instantiate the vm object
|
||||
return vm.NewVirtualMachine(op, s, child.Reference()), nil
|
||||
}
|
||||
|
||||
func (rp *ResourcePool) GetCluster(ctx context.Context) (*object.ComputeResource, error) {
|
||||
op := trace.FromContext(ctx, "GetCluster")
|
||||
|
||||
var err error
|
||||
var mrp mo.ResourcePool
|
||||
|
||||
if err = rp.Properties(op, rp.Reference(), []string{"owner"}, &mrp); err != nil {
|
||||
op.Errorf("Unable to get cluster of resource pool %s: %s", rp.Name(), err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return object.NewComputeResource(rp.Client.Client, mrp.Owner), nil
|
||||
}
|
||||
|
||||
func (rp *ResourcePool) GetDatacenter(ctx context.Context) (*object.Datacenter, error) {
|
||||
op := trace.FromContext(ctx, "GetDatacenter")
|
||||
|
||||
dcRef, err := rp.getLowestAncestor(op, "Datacenter")
|
||||
if err != nil || dcRef == nil {
|
||||
op.Errorf("Unable to get datacenter ancestor of rp %s: %s", rp.Name(), err)
|
||||
return nil, errors.Errorf("Unable to get datacenter ancestor of rp %s: %s", rp.Name(), err)
|
||||
}
|
||||
|
||||
return object.NewDatacenter(rp.Client.Client, *dcRef), nil
|
||||
}
|
||||
|
||||
func (rp *ResourcePool) getAncestors(op trace.Operation, inType string) ([]types.ManagedObjectReference, error) {
|
||||
client := rp.Session.Vim25()
|
||||
|
||||
ancestors, err := mo.Ancestors(op, client, client.ServiceContent.PropertyCollector, rp.Reference())
|
||||
if err != nil {
|
||||
op.Errorf("Unable to get ancestors of rp %s: %s", rp.Name(), err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
outAncestors := make([]types.ManagedObjectReference, 0, len(ancestors))
|
||||
for _, ancestor := range ancestors {
|
||||
if ancestor.Self.Type == inType {
|
||||
a := ancestor.Self
|
||||
outAncestors = append(outAncestors, a)
|
||||
}
|
||||
}
|
||||
|
||||
return outAncestors, nil
|
||||
}
|
||||
|
||||
func (rp *ResourcePool) getLowestAncestor(op trace.Operation, inType string) (*types.ManagedObjectReference, error) {
|
||||
ancestors, err := rp.getAncestors(op, inType)
|
||||
if err != nil {
|
||||
op.Errorf("Unable to get ancestors of rp %s: %s", rp.Name(), err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(ancestors) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
index := len(ancestors) - 1
|
||||
return &ancestors[index], nil
|
||||
}
|
||||
103
vendor/github.com/vmware/vic/pkg/vsphere/compute/rp_test.go
generated
vendored
103
vendor/github.com/vmware/vic/pkg/vsphere/compute/rp_test.go
generated
vendored
@@ -1,103 +0,0 @@
|
||||
// Copyright 2016-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 compute
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/govmomi/simulator"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/test"
|
||||
)
|
||||
|
||||
func TestRp(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
for i, model := range []*simulator.Model{simulator.ESX(), simulator.VPX()} {
|
||||
t.Logf("%d", i)
|
||||
defer model.Remove()
|
||||
err := model.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s := model.Service.NewServer()
|
||||
defer s.Close()
|
||||
|
||||
s.URL.User = url.UserPassword("user", "pass")
|
||||
t.Logf("server URL: %s", s.URL)
|
||||
|
||||
var sess *session.Session
|
||||
if i == 0 {
|
||||
sess, err = test.SessionWithESX(ctx, s.URL.String())
|
||||
} else {
|
||||
sess, err = test.SessionWithVPX(ctx, s.URL.String())
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer sess.Logout(ctx)
|
||||
testGetChildrenVMs(ctx, sess, t)
|
||||
testGetChildVM(ctx, sess, t)
|
||||
testGetCluster(ctx, sess, t)
|
||||
testGetDatacenter(ctx, sess, t)
|
||||
}
|
||||
}
|
||||
|
||||
func testGetChildrenVMs(ctx context.Context, sess *session.Session, t *testing.T) {
|
||||
rp := NewResourcePool(ctx, sess, sess.Pool.Reference())
|
||||
vms, err := rp.GetChildrenVMs(ctx, sess)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get children vm of resource pool %s, %s", rp.Name(), err)
|
||||
}
|
||||
// if vms == nil || len(vms) == 0 {
|
||||
// t.Error("Didn't get children VM")
|
||||
// }
|
||||
for _, vm := range vms {
|
||||
t.Logf("vm: %s", vm)
|
||||
}
|
||||
}
|
||||
|
||||
func testGetChildVM(ctx context.Context, sess *session.Session, t *testing.T) {
|
||||
rp := NewResourcePool(ctx, sess, sess.Pool.Reference())
|
||||
vm, err := rp.GetChildVM(ctx, sess, "random")
|
||||
if err == nil && vm != nil {
|
||||
t.Logf("vm: %s", vm.Reference())
|
||||
t.Errorf("Should not find VM random")
|
||||
}
|
||||
}
|
||||
|
||||
func testGetCluster(ctx context.Context, sess *session.Session, t *testing.T) {
|
||||
rp := NewResourcePool(ctx, sess, sess.Pool.Reference())
|
||||
cluster, err := rp.GetCluster(ctx)
|
||||
if err != nil {
|
||||
t.Logf("Failed to owner cluster: %s", err)
|
||||
t.Errorf("Should get owner")
|
||||
}
|
||||
t.Logf("Cluster: %s", cluster)
|
||||
}
|
||||
|
||||
func testGetDatacenter(ctx context.Context, sess *session.Session, t *testing.T) {
|
||||
rp := NewResourcePool(ctx, sess, sess.Pool.Reference())
|
||||
datacenter, err := rp.GetDatacenter(ctx)
|
||||
if err != nil {
|
||||
t.Logf("Failed to find parent Datacenter: %s", err)
|
||||
t.Errorf("Should get Datacenter")
|
||||
}
|
||||
t.Logf("Datacenter: %s", datacenter)
|
||||
}
|
||||
43
vendor/github.com/vmware/vic/pkg/vsphere/compute/vapp.go
generated
vendored
43
vendor/github.com/vmware/vic/pkg/vsphere/compute/vapp.go
generated
vendored
@@ -1,43 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package compute
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
// VirtualApp struct defines the VirtualApp which provides additional
|
||||
// VIC specific methods over object.VirtualApp as well as keeps some state
|
||||
type VirtualApp struct {
|
||||
*object.VirtualApp
|
||||
|
||||
*session.Session
|
||||
}
|
||||
|
||||
// NewResourcePool returns a New ResourcePool object
|
||||
func NewVirtualApp(ctx context.Context, session *session.Session, moref types.ManagedObjectReference) *VirtualApp {
|
||||
return &VirtualApp{
|
||||
VirtualApp: object.NewVirtualApp(
|
||||
session.Vim25(),
|
||||
moref,
|
||||
),
|
||||
Session: session,
|
||||
}
|
||||
}
|
||||
395
vendor/github.com/vmware/vic/pkg/vsphere/datastore/datastore.go
generated
vendored
395
vendor/github.com/vmware/vic/pkg/vsphere/datastore/datastore.go
generated
vendored
@@ -1,395 +0,0 @@
|
||||
// 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 datastore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/tasks"
|
||||
)
|
||||
|
||||
// Helper gives access to the datastore regardless of type (esx, esx + vc,
|
||||
// or esx + vc + vsan). Also wraps paths to a given root directory
|
||||
type Helper struct {
|
||||
// The Datastore API likes everything in "path/to/thing" format.
|
||||
ds *object.Datastore
|
||||
|
||||
s *session.Session
|
||||
|
||||
// The FileManager API likes everything in "[dsname] path/to/thing" format.
|
||||
fm *object.FileManager
|
||||
|
||||
// The datastore url (including root) in "[dsname] /path" format.
|
||||
RootURL object.DatastorePath
|
||||
}
|
||||
|
||||
// NewDatastore returns a Datastore.
|
||||
// ctx is a context,
|
||||
// s is an authenticated session
|
||||
// ds is the vsphere datastore
|
||||
// rootdir is the top level directory to root all data. If root does not exist,
|
||||
// it will be created. If it already exists, NOOP. This cannot be empty.
|
||||
func NewHelper(ctx context.Context, s *session.Session, ds *object.Datastore, rootdir string) (*Helper, error) {
|
||||
op := trace.FromContext(ctx, "NewHelper")
|
||||
|
||||
d := &Helper{
|
||||
ds: ds,
|
||||
s: s,
|
||||
fm: object.NewFileManager(s.Vim25()),
|
||||
}
|
||||
|
||||
if path.IsAbs(rootdir) {
|
||||
rootdir = rootdir[1:]
|
||||
}
|
||||
|
||||
if err := d.mkRootDir(op, rootdir); err != nil {
|
||||
op.Infof("error creating root directory %s: %s", rootdir, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if d.RootURL.Path == "" {
|
||||
return nil, fmt.Errorf("failed to create root directory")
|
||||
}
|
||||
|
||||
op.Infof("Datastore path is %s", d.RootURL.String())
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func NewHelperFromURL(ctx context.Context, s *session.Session, u *url.URL) (*Helper, error) {
|
||||
fm := object.NewFileManager(s.Vim25())
|
||||
vsDs, err := s.Finder.DatastoreOrDefault(ctx, u.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := &Helper{
|
||||
ds: vsDs,
|
||||
s: s,
|
||||
fm: fm,
|
||||
}
|
||||
|
||||
d.RootURL.FromString(u.Path)
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func NewHelperFromSession(ctx context.Context, s *session.Session) *Helper {
|
||||
return &Helper{
|
||||
ds: s.Datastore,
|
||||
s: s,
|
||||
fm: object.NewFileManager(s.Vim25()),
|
||||
}
|
||||
}
|
||||
|
||||
// GetDatastores returns a map of datastores given a map of names and urls
|
||||
func GetDatastores(ctx context.Context, s *session.Session, dsURLs map[string]*url.URL) (map[string]*Helper, error) {
|
||||
stores := make(map[string]*Helper)
|
||||
|
||||
for name, dsURL := range dsURLs {
|
||||
d, err := NewHelperFromURL(ctx, s, dsURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stores[name] = d
|
||||
}
|
||||
|
||||
return stores, nil
|
||||
}
|
||||
|
||||
func (d *Helper) Summary(ctx context.Context) (*types.DatastoreSummary, error) {
|
||||
|
||||
var mds mo.Datastore
|
||||
if err := d.ds.Properties(ctx, d.ds.Reference(), []string{"info", "summary"}, &mds); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &mds.Summary, nil
|
||||
}
|
||||
|
||||
func mkdir(op trace.Operation, sess *session.Session, fm *object.FileManager, createParentDirectories bool, path string) (string, error) {
|
||||
op.Infof("Creating directory %s", path)
|
||||
|
||||
if err := fm.MakeDirectory(op, path, sess.Datacenter, createParentDirectories); err != nil {
|
||||
if soap.IsSoapFault(err) {
|
||||
soapFault := soap.ToSoapFault(err)
|
||||
if _, ok := soapFault.VimFault().(types.FileAlreadyExists); ok {
|
||||
op.Debugf("File already exists: %s", path)
|
||||
return "", os.ErrExist
|
||||
}
|
||||
}
|
||||
op.Debugf("Creating %s error: %s", path, err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// Mkdir creates directories.
|
||||
func (d *Helper) Mkdir(ctx context.Context, createParentDirectories bool, dirs ...string) (string, error) {
|
||||
op := trace.FromContext(ctx, "Mkdir")
|
||||
|
||||
return mkdir(op, d.s, d.fm, createParentDirectories, path.Join(d.RootURL.String(), path.Join(dirs...)))
|
||||
}
|
||||
|
||||
// Ls returns a list of dirents at the given path (relative to root)
|
||||
//
|
||||
// A note aboutpaths and the datastore browser.
|
||||
// None of these work paths work
|
||||
// r, err := ds.Ls(ctx, "ds:///vmfs/volumes/vsan:52a67632ac3497a3-411916fd50bedc27/0ea65357-0494-d42d-2ede-000c292dc5b5")
|
||||
// r, err := ds.Ls(ctx, "[vsanDatastore] ds:///vmfs/volumes/vsan:52a67632ac3497a3-411916fd50bedc27/")
|
||||
// r, err := ds.Ls(ctx, "[vsanDatastore] //vmfs/volumes/vsan:52a67632ac3497a3-411916fd50bedc27/")
|
||||
// r, err := ds.Ls(ctx, "[] ds:///vmfs/volumes/vsan:52a67632ac3497a3-411916fd50bedc27/0ea65357-0494-d42d-2ede-000c292dc5b5")
|
||||
// r, err := ds.Ls(ctx, "[] /vmfs/volumes/vsan:52a67632ac3497a3-411916fd50bedc27/0ea65357-0494-d42d-2ede-000c292dc5b5")
|
||||
// r, err := ds.Ls(ctx, "[] ../vmfs/volumes/vsan:52a67632ac3497a3-411916fd50bedc27/0ea65357-0494-d42d-2ede-000c292dc5b5")
|
||||
// r, err := ds.Ls(ctx, "[] ./vmfs/volumes/vsan:52a67632ac3497a3-411916fd50bedc27/0ea65357-0494-d42d-2ede-000c292dc5b5")
|
||||
// r, err := ds.Ls(ctx, "[52a67632ac3497a3-411916fd50bedc27] /0ea65357-0494-d42d-2ede-000c292dc5b5")
|
||||
// r, err := ds.Ls(ctx, "[vsan:52a67632ac3497a3-411916fd50bedc27] /0ea65357-0494-d42d-2ede-000c292dc5b5")
|
||||
// r, err := ds.Ls(ctx, "[vsan:52a67632ac3497a3-411916fd50bedc27] 0ea65357-0494-d42d-2ede-000c292dc5b5")
|
||||
// r, err := ds.Ls(ctx, "[vsanDatastore] /vmfs/volumes/vsan:52a67632ac3497a3-411916fd50bedc27/0ea65357-0494-d42d-2ede-000c292dc5b5")
|
||||
|
||||
// The only URI that works on VC + VSAN.
|
||||
// r, err := ds.Ls(ctx, "[vsanDatastore] /0ea65357-0494-d42d-2ede-000c292dc5b5")
|
||||
//
|
||||
func (d *Helper) Ls(ctx context.Context, p string) (*types.HostDatastoreBrowserSearchResults, error) {
|
||||
spec := types.HostDatastoreBrowserSearchSpec{
|
||||
MatchPattern: []string{"*"},
|
||||
Details: &types.FileQueryFlags{
|
||||
FileType: true,
|
||||
FileOwner: types.NewBool(true),
|
||||
},
|
||||
}
|
||||
|
||||
b, err := d.ds.Browser(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
task, err := b.SearchDatastore(ctx, path.Join(d.RootURL.String(), p), &spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := task.WaitForResult(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := info.Result.(types.HostDatastoreBrowserSearchResults)
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
// LsDirs returns a list of dirents at the given path (relative to root)
|
||||
func (d *Helper) LsDirs(ctx context.Context, p string) (*types.ArrayOfHostDatastoreBrowserSearchResults, error) {
|
||||
spec := &types.HostDatastoreBrowserSearchSpec{
|
||||
MatchPattern: []string{"*"},
|
||||
Details: &types.FileQueryFlags{
|
||||
FileType: true,
|
||||
FileOwner: types.NewBool(true),
|
||||
},
|
||||
}
|
||||
|
||||
b, err := d.ds.Browser(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
task, err := b.SearchDatastoreSubFolders(ctx, path.Join(d.RootURL.String(), p), spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := task.WaitForResult(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := info.Result.(types.ArrayOfHostDatastoreBrowserSearchResults)
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (d *Helper) Upload(ctx context.Context, r io.Reader, pth string) error {
|
||||
return d.ds.Upload(ctx, r, path.Join(d.RootURL.Path, pth), &soap.DefaultUpload)
|
||||
}
|
||||
|
||||
func (d *Helper) Download(ctx context.Context, pth string) (io.ReadCloser, error) {
|
||||
rc, _, err := d.ds.Download(ctx, path.Join(d.RootURL.Path, pth), &soap.DefaultDownload)
|
||||
return rc, err
|
||||
}
|
||||
|
||||
func (d *Helper) Stat(ctx context.Context, pth string) (types.BaseFileInfo, error) {
|
||||
i, err := d.ds.Stat(ctx, path.Join(d.RootURL.Path, pth))
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case object.DatastoreNoSuchDirectoryError:
|
||||
return nil, os.ErrNotExist
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (d *Helper) Mv(ctx context.Context, fromPath, toPath string) error {
|
||||
op := trace.FromContext(ctx, "Mv")
|
||||
|
||||
from := path.Join(d.RootURL.String(), fromPath)
|
||||
to := path.Join(d.RootURL.String(), toPath)
|
||||
op.Infof("Moving %s to %s", from, to)
|
||||
err := tasks.Wait(ctx, func(context.Context) (tasks.Task, error) {
|
||||
return d.fm.MoveDatastoreFile(ctx, from, d.s.Datacenter, to, d.s.Datacenter, true)
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Helper) Rm(ctx context.Context, pth string) error {
|
||||
op := trace.FromContext(ctx, "Rm")
|
||||
|
||||
f := path.Join(d.RootURL.String(), pth)
|
||||
op.Infof("Removing %s", pth)
|
||||
return d.ds.NewFileManager(d.s.Datacenter, true).Delete(ctx, f) // TODO: NewHelper should create the DatastoreFileManager
|
||||
}
|
||||
|
||||
func (d *Helper) IsVSAN(ctx context.Context) bool {
|
||||
// #nosec: Errors unhandled.
|
||||
dsType, _ := d.ds.Type(ctx)
|
||||
return dsType == types.HostFileSystemVolumeFileSystemTypeVsan
|
||||
}
|
||||
|
||||
// This creates the root directory in the datastore and sets the rooturl and
|
||||
// rootdir in the datastore struct so we can reuse it for other routines. This
|
||||
// handles vsan + vc, vsan + esx, and esx. The URI conventions are not the
|
||||
// same for each and this tries to create the directory and stash the relevant
|
||||
// result so the URI doesn't need to be recomputed for every datastore
|
||||
// operation.
|
||||
func (d *Helper) mkRootDir(op trace.Operation, rootdir string) error {
|
||||
if rootdir == "" {
|
||||
return fmt.Errorf("root directory is empty")
|
||||
}
|
||||
|
||||
if path.IsAbs(rootdir) {
|
||||
return fmt.Errorf("root directory (%s) must not be an absolute path", rootdir)
|
||||
}
|
||||
|
||||
// Handle vsan
|
||||
// Vsan will complain if the root dir exists. Just call it directly and
|
||||
// swallow the error if it's already there.
|
||||
if d.IsVSAN(op) {
|
||||
comps := strings.Split(rootdir, "/")
|
||||
|
||||
nm := object.NewDatastoreNamespaceManager(d.s.Vim25())
|
||||
|
||||
// This returns the vmfs path (including the datastore and directory
|
||||
// UUIDs). Use the directory UUID in future operations because it is
|
||||
// the stable path which we can use regardless of vsan state.
|
||||
uuid, err := nm.CreateDirectory(op, d.ds, comps[0], "")
|
||||
if err != nil {
|
||||
if !soap.IsSoapFault(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
soapFault := soap.ToSoapFault(err)
|
||||
if _, ok := soapFault.VimFault().(types.FileAlreadyExists); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
// XXX UGLY HACK until we move this into the installer. Use the
|
||||
// display name if the dir exists since we can't get the UUID after the
|
||||
// directory is created.
|
||||
uuid = comps[0]
|
||||
err = nil
|
||||
}
|
||||
|
||||
rootdir = path.Join(path.Base(uuid), path.Join(comps[1:]...))
|
||||
}
|
||||
|
||||
rooturl := d.ds.Path(rootdir)
|
||||
|
||||
// create the rest of the root dir in case of vSAN, otherwise
|
||||
// create the full path
|
||||
if _, err := mkdir(op, d.s, d.fm, true, rooturl); err != nil {
|
||||
if !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
op.Infof("datastore root %s already exists", rooturl)
|
||||
}
|
||||
|
||||
d.RootURL.FromString(rooturl)
|
||||
return nil
|
||||
}
|
||||
|
||||
func PathFromString(dsp string) (*object.DatastorePath, error) {
|
||||
var p object.DatastorePath
|
||||
if !p.FromString(dsp) {
|
||||
return nil, errors.New(dsp + " not a datastore path")
|
||||
}
|
||||
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
// Parse the datastore format ([datastore1] /path/to/thing) to groups.
|
||||
var datastoreFormat = regexp.MustCompile(`^\[([\w\d\(\)-_\.\s]+)\]`)
|
||||
var pathFormat = regexp.MustCompile(`\s([\/\w-_\.]*$)`)
|
||||
|
||||
// Converts `[datastore] /path` to ds:// URL
|
||||
func ToURL(ds string) (*url.URL, error) {
|
||||
u := new(url.URL)
|
||||
var matches []string
|
||||
if matches = datastoreFormat.FindStringSubmatch(ds); len(matches) != 2 {
|
||||
return nil, fmt.Errorf("Ambiguous datastore hostname format encountered from input: %s.", ds)
|
||||
}
|
||||
u.Host = matches[1]
|
||||
if matches = pathFormat.FindStringSubmatch(ds); len(matches) != 2 {
|
||||
return nil, fmt.Errorf("Ambiguous datastore path format encountered from input: %s.", ds)
|
||||
}
|
||||
|
||||
u.Path = path.Clean(matches[1])
|
||||
u.Scheme = "ds"
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// Converts ds:// URL for datastores to datastore format ([datastore1] /path/to/thing)
|
||||
func URLtoDatastore(u *url.URL) (string, error) {
|
||||
scheme := "ds"
|
||||
if u.Scheme != scheme {
|
||||
return "", fmt.Errorf("url (%s) is not a datastore", u.String())
|
||||
}
|
||||
return fmt.Sprintf("[%s] %s", u.Host, u.Path), nil
|
||||
}
|
||||
|
||||
// TestName builds a unique datastore name
|
||||
func TestName(suffix string) string {
|
||||
return uuid.New().String()[0:16] + "-" + suffix
|
||||
}
|
||||
219
vendor/github.com/vmware/vic/pkg/vsphere/datastore/datastore_test.go
generated
vendored
219
vendor/github.com/vmware/vic/pkg/vsphere/datastore/datastore_test.go
generated
vendored
@@ -1,219 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"context"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// test if we can get a Datastore via the rooturl
|
||||
func TestDatastoreGetDatastores(t *testing.T) {
|
||||
ctx, ds, cleanupfunc := DSsetup(t)
|
||||
if t.Failed() {
|
||||
return
|
||||
}
|
||||
defer cleanupfunc()
|
||||
|
||||
firstSummary, err := ds.Summary(ctx)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("Name:\t%s\n", firstSummary.Name)
|
||||
t.Logf(" Path:\t%s\n", ds.ds.InventoryPath)
|
||||
t.Logf(" Type:\t%s\n", firstSummary.Type)
|
||||
t.Logf(" URL:\t%s\n", firstSummary.Url)
|
||||
t.Logf(" Capacity:\t%.1f GB\n", float64(firstSummary.Capacity)/(1<<30))
|
||||
t.Logf(" Free:\t%.1f GB\n", float64(firstSummary.FreeSpace)/(1<<30))
|
||||
|
||||
inMap := make(map[string]*url.URL)
|
||||
|
||||
p, err := url.Parse(ds.RootURL.String())
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
inMap["foo"] = p
|
||||
|
||||
dstores, err := GetDatastores(context.TODO(), ds.s, inMap)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, dstores) {
|
||||
return
|
||||
}
|
||||
|
||||
secondSummary, err := ds.Summary(ctx)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, firstSummary, secondSummary) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatastoreRestart(t *testing.T) {
|
||||
// creates a root in the datastore
|
||||
ctx, ds, cleanupfunc := DSsetup(t)
|
||||
if t.Failed() {
|
||||
return
|
||||
}
|
||||
defer cleanupfunc()
|
||||
|
||||
// Create a nested dir in the root and use that as the datastore
|
||||
nestedRoot := path.Join(ds.RootURL.Path, "foo")
|
||||
ds, err := NewHelper(ctx, ds.s, ds.s.Datastore, nestedRoot)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// test we can ls the root
|
||||
_, err = ds.Ls(ctx, "")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// create a dir
|
||||
_, err = ds.Mkdir(ctx, true, "baz")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// create a new datastore object with the same path as the nested one
|
||||
ds, err = NewHelper(ctx, ds.s, ds.s.Datastore, nestedRoot)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// try to create the same baz dir, assert it exists
|
||||
_, err = ds.Mkdir(ctx, true, "baz")
|
||||
if !assert.Error(t, err) || !assert.True(t, os.IsExist(err)) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.NotEmpty(t, ds.RootURL)
|
||||
}
|
||||
|
||||
func TestDatastoreCreateDir(t *testing.T) {
|
||||
ctx, ds, cleanupfunc := DSsetup(t)
|
||||
if t.Failed() {
|
||||
return
|
||||
}
|
||||
defer cleanupfunc()
|
||||
|
||||
_, err := ds.Ls(ctx, "")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// assert create dir of a dir that exists is os.ErrExists
|
||||
_, err = ds.Mkdir(ctx, true, "foo")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = ds.Mkdir(ctx, true, "foo")
|
||||
if !assert.Error(t, err) || !assert.True(t, os.IsExist(err)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatastoreMkdirAndLs(t *testing.T) {
|
||||
ctx, ds, cleanupfunc := DSsetup(t)
|
||||
if t.Failed() {
|
||||
return
|
||||
}
|
||||
defer cleanupfunc()
|
||||
|
||||
dirs := []string{"dir1", "dir1/child1"}
|
||||
|
||||
// create the dir then test it exists by calling ls
|
||||
for _, dir := range dirs {
|
||||
_, err := ds.Mkdir(ctx, true, dir)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = ds.Ls(ctx, dir)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatastoreToURLParsing(t *testing.T) {
|
||||
expectedURL := "ds://datastore1/path/to/thing"
|
||||
|
||||
input := [][]string{
|
||||
{"[datastore1] /path/to/thing", expectedURL},
|
||||
{"[datastore1] path/to/thing", expectedURL},
|
||||
{"[datastore1] ///path////to/thing", expectedURL},
|
||||
{"[Datastore (1)] /path/to/thing", "ds://Datastore%20(1)/path/to/thing"},
|
||||
{"[datastore1] path", "ds://datastore1/path"},
|
||||
{"[datastore1] pa-th", "ds://datastore1/pa-th"},
|
||||
{"[datastore1] pa_th", "ds://datastore1/pa_th"},
|
||||
{"[data_store1] pa_th", "ds://data_store1/pa_th"},
|
||||
}
|
||||
|
||||
dsoutputs := []string{
|
||||
"[datastore1] /path/to/thing",
|
||||
"[datastore1] path/to/thing",
|
||||
"[datastore1] /path/to/thing",
|
||||
"[Datastore (1)] /path/to/thing",
|
||||
"[datastore1] path",
|
||||
"[datastore1] pa-th",
|
||||
"[datastore1] pa_th",
|
||||
"[data_store1] pa_th",
|
||||
}
|
||||
|
||||
for i, in := range input {
|
||||
u, err := ToURL(in[0])
|
||||
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, u) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, in[1], u.String()) {
|
||||
return
|
||||
}
|
||||
|
||||
out, err := URLtoDatastore(u)
|
||||
if !assert.NoError(t, err) || !assert.True(t, len(out) > 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Equal(t, dsoutputs[i], out) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// From https://siongui.github.io/2015/04/13/go-generate-random-string/
|
||||
func RandomString(strlen int) string {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
result := make([]byte, strlen)
|
||||
for i := 0; i < strlen; i++ {
|
||||
result[i] = chars[rand.Intn(len(chars))]
|
||||
}
|
||||
return string(result)
|
||||
}
|
||||
83
vendor/github.com/vmware/vic/pkg/vsphere/datastore/datastore_util.go
generated
vendored
83
vendor/github.com/vmware/vic/pkg/vsphere/datastore/datastore_util.go
generated
vendored
@@ -1,83 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package datastore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/tasks"
|
||||
"github.com/vmware/vic/pkg/vsphere/test/env"
|
||||
)
|
||||
|
||||
// Used in testing
|
||||
|
||||
func Session(ctx context.Context, t *testing.T) *session.Session {
|
||||
config := &session.Config{
|
||||
Service: env.URL(t),
|
||||
|
||||
/// XXX Why does this insist on having this field populated?
|
||||
DatastorePath: env.DS(t),
|
||||
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
}
|
||||
|
||||
s := session.NewSession(config)
|
||||
_, err := s.Connect(ctx)
|
||||
if err != nil {
|
||||
s.Client.Logout(ctx)
|
||||
t.Log(err.Error())
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
_, err = s.Populate(ctx)
|
||||
if err != nil {
|
||||
t.Log(err.Error())
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func DSsetup(t *testing.T) (context.Context, *Helper, func()) {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
ctx := context.Background()
|
||||
sess := Session(ctx, t)
|
||||
|
||||
ds, err := NewHelper(ctx, sess, sess.Datastore, TestName("dstests"))
|
||||
if !assert.NoError(t, err) {
|
||||
return ctx, nil, nil
|
||||
}
|
||||
|
||||
f := func() {
|
||||
log.Infof("Removing test root %s", ds.RootURL.String())
|
||||
err := tasks.Wait(ctx, func(context.Context) (tasks.Task, error) {
|
||||
return ds.fm.DeleteDatastoreFile(ctx, ds.RootURL.String(), sess.Datacenter)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return ctx, ds, f
|
||||
}
|
||||
152
vendor/github.com/vmware/vic/pkg/vsphere/diag/diag.go
generated
vendored
152
vendor/github.com/vmware/vic/pkg/vsphere/diag/diag.go
generated
vendored
@@ -1,152 +0,0 @@
|
||||
// Copyright 2016-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 diag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// StatusCodeFatalThreshold defines a threshold after which all codes can be treated as fatal.
|
||||
const StatusCodeFatalThreshold = 64
|
||||
|
||||
const (
|
||||
// VCStatusOK vSphere API is available.
|
||||
VCStatusOK = 0
|
||||
// VCStatusInvalidURL Provided vSphere API URL is wrong.
|
||||
VCStatusInvalidURL = 64
|
||||
// VCStatusErrorQuery Error happened trying to query vSphere API
|
||||
VCStatusErrorQuery = 65
|
||||
// VCStatusErrorResponse Received response doesn't contain expected data.
|
||||
VCStatusErrorResponse = 66
|
||||
// VCStatusIncorrectResponse Received in case if returned data from server is different from expected.
|
||||
VCStatusIncorrectResponse = 67
|
||||
// VCStatusNotXML Received response is not XML
|
||||
VCStatusNotXML = 68
|
||||
// VCStatusUnknownHost is returned in case if DNS failed to resolve name.
|
||||
VCStatusUnknownHost = 69
|
||||
// VCStatusHostIsNotReachable
|
||||
VCStatusHostIsNotReachable = 70
|
||||
)
|
||||
|
||||
// UserReadableVCAPITestDescription convert API test code into user readable text
|
||||
func UserReadableVCAPITestDescription(code int) string {
|
||||
switch code {
|
||||
case VCStatusOK:
|
||||
return "vSphere API target responds as expected"
|
||||
case VCStatusInvalidURL:
|
||||
return "vSphere API target url is invalid"
|
||||
case VCStatusErrorQuery:
|
||||
return "vSphere API target failed to respond to the query"
|
||||
case VCStatusIncorrectResponse:
|
||||
return "vSphere API target returns unexpected response"
|
||||
case VCStatusErrorResponse:
|
||||
return "vSphere API target returns error"
|
||||
case VCStatusNotXML:
|
||||
return "vSphere API target returns non XML response"
|
||||
case VCStatusUnknownHost:
|
||||
return "vSphere API target can not be resolved from VCH"
|
||||
case VCStatusHostIsNotReachable:
|
||||
return "vSphere API target is out of reach. Wrong routing table?"
|
||||
default:
|
||||
return "vSphere API target test returned unknown code"
|
||||
}
|
||||
}
|
||||
|
||||
// CheckAPIAvailability accesses vSphere API to ensure it is a correct end point that is up and running.
|
||||
func CheckAPIAvailability(targetURL string) int {
|
||||
op := trace.NewOperation(context.Background(), "api test")
|
||||
errorCode := VCStatusErrorQuery
|
||||
|
||||
u, err := url.Parse(targetURL)
|
||||
if err != nil {
|
||||
return VCStatusInvalidURL
|
||||
}
|
||||
|
||||
u.Path = "/sdk/vimService.wsdl"
|
||||
apiURL := u.String()
|
||||
|
||||
op.Debugf("Checking access to: %s", apiURL)
|
||||
|
||||
for attempts := 5; errorCode != VCStatusOK && attempts > 0; attempts-- {
|
||||
|
||||
// #nosec: TLS InsecureSkipVerify set true
|
||||
c := http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
// Is 20 seconds enough to receive any response from vSphere target server?
|
||||
Timeout: time.Second * 20,
|
||||
}
|
||||
errorCode = queryAPI(op, c.Get, apiURL)
|
||||
}
|
||||
return errorCode
|
||||
}
|
||||
|
||||
func queryAPI(op trace.Operation, getter func(string) (*http.Response, error), apiURL string) int {
|
||||
resp, err := getter(apiURL)
|
||||
if err != nil {
|
||||
errTxt := err.Error()
|
||||
op.Errorf("Query error: %s", err)
|
||||
if strings.Contains(errTxt, "no such host") {
|
||||
return VCStatusUnknownHost
|
||||
}
|
||||
if strings.Contains(errTxt, "no route to host") {
|
||||
return VCStatusHostIsNotReachable
|
||||
}
|
||||
if strings.Contains(errTxt, "host is down") {
|
||||
return VCStatusHostIsNotReachable
|
||||
}
|
||||
return VCStatusErrorQuery
|
||||
}
|
||||
|
||||
data := make([]byte, 65636)
|
||||
n, err := io.ReadFull(resp.Body, data)
|
||||
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||
op.Errorf("Query error: %s", err)
|
||||
return VCStatusErrorResponse
|
||||
}
|
||||
if n >= len(data) {
|
||||
// #nosec: Errors unhandled.
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
}
|
||||
// #nosec: Errors unhandled.
|
||||
resp.Body.Close()
|
||||
|
||||
contentType := strings.ToLower(resp.Header.Get("Content-Type"))
|
||||
if !strings.Contains(contentType, "text/xml") {
|
||||
op.Errorf("Unexpected content type %s, should be text/xml", contentType)
|
||||
op.Errorf("Response from the server: %s", string(data))
|
||||
return VCStatusNotXML
|
||||
}
|
||||
// we just want to make sure that response contains something familiar that we could
|
||||
// use as vSphere API marker.
|
||||
if !bytes.Contains(data, []byte("urn:vim25Service")) {
|
||||
op.Errorf("Server response doesn't contain 'urn:vim25Service': %s", string(data))
|
||||
return VCStatusIncorrectResponse
|
||||
}
|
||||
return VCStatusOK
|
||||
}
|
||||
117
vendor/github.com/vmware/vic/pkg/vsphere/diag/diag_test.go
generated
vendored
117
vendor/github.com/vmware/vic/pkg/vsphere/diag/diag_test.go
generated
vendored
@@ -1,117 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package diag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
func TestCheckAPIAvailability(t *testing.T) {
|
||||
assert.Equal(t, VCStatusErrorQuery, CheckAPIAvailability("http://127.0.0.1:65535"))
|
||||
assert.Equal(t, VCStatusErrorQuery, CheckAPIAvailability("http://127.0.0.1:65536"))
|
||||
}
|
||||
|
||||
func TestCheckAPIAvailabilityQueryWithGetterError(t *testing.T) {
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
f := func(s string) (*http.Response, error) { return nil, errors.New("wrong query") }
|
||||
code := queryAPI(op, f, "testurl")
|
||||
assert.Equal(t, VCStatusErrorQuery, code)
|
||||
}
|
||||
|
||||
type readerWithError struct {
|
||||
err error
|
||||
data *bytes.Reader
|
||||
}
|
||||
|
||||
func (r *readerWithError) Read(b []byte) (int, error) {
|
||||
if r.err != nil {
|
||||
return 0, r.err
|
||||
}
|
||||
return r.data.Read(b)
|
||||
}
|
||||
|
||||
func (r *readerWithError) Close() error {
|
||||
return r.err
|
||||
}
|
||||
|
||||
func TestCheckAPIAvailabilityQueryReadError(t *testing.T) {
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
f := func(s string) (*http.Response, error) {
|
||||
hr := &http.Response{
|
||||
Body: &readerWithError{
|
||||
err: errors.New("read error happened"),
|
||||
},
|
||||
}
|
||||
return hr, nil
|
||||
}
|
||||
code := queryAPI(op, f, "testurl")
|
||||
assert.Equal(t, VCStatusErrorResponse, code)
|
||||
}
|
||||
|
||||
func TestCheckAPIAvailabilityQueryIncorrectDataType(t *testing.T) {
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
f := func(s string) (*http.Response, error) {
|
||||
hr := &http.Response{
|
||||
Body: &readerWithError{
|
||||
data: bytes.NewReader([]byte("some data")),
|
||||
},
|
||||
}
|
||||
return hr, nil
|
||||
}
|
||||
code := queryAPI(op, f, "testurl")
|
||||
assert.Equal(t, VCStatusNotXML, code)
|
||||
}
|
||||
|
||||
func TestCheckAPIAvailabilityQueryIncorrectData(t *testing.T) {
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
f := func(s string) (*http.Response, error) {
|
||||
hr := &http.Response{
|
||||
Body: &readerWithError{
|
||||
data: bytes.NewReader([]byte("some data")),
|
||||
},
|
||||
Header: http.Header{"Content-Type": []string{"text/xml"}},
|
||||
}
|
||||
return hr, nil
|
||||
}
|
||||
code := queryAPI(op, f, "testurl")
|
||||
assert.Equal(t, VCStatusIncorrectResponse, code)
|
||||
}
|
||||
|
||||
func TestCheckAPIAvailabilityQueryCorrectData(t *testing.T) {
|
||||
op := trace.NewOperation(context.Background(), "test")
|
||||
f := func(s string) (*http.Response, error) {
|
||||
hr := &http.Response{
|
||||
Body: &readerWithError{
|
||||
data: bytes.NewReader([]byte("some urn:vim25Service data")),
|
||||
},
|
||||
Header: http.Header{"Content-Type": []string{"text/xml"}},
|
||||
}
|
||||
return hr, nil
|
||||
}
|
||||
code := queryAPI(op, f, "testurl")
|
||||
assert.Equal(t, VCStatusOK, code)
|
||||
}
|
||||
|
||||
func TestCheckAPIAvailabilityIncorrectDNSName(t *testing.T) {
|
||||
assert.Equal(t, VCStatusUnknownHost, CheckAPIAvailability("https://example.notexisting.domain"))
|
||||
}
|
||||
39
vendor/github.com/vmware/vic/pkg/vsphere/diagnostic/manager.go
generated
vendored
39
vendor/github.com/vmware/vic/pkg/vsphere/diagnostic/manager.go
generated
vendored
@@ -1,39 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package diagnostic
|
||||
|
||||
import (
|
||||
"github.com/vmware/govmomi/object"
|
||||
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
// Manager struct defines the Manager which provides additional
|
||||
// VIC specific methods over object.DiagnosticManager
|
||||
type Manager struct {
|
||||
*object.DiagnosticManager
|
||||
|
||||
*session.Session
|
||||
}
|
||||
|
||||
// NewDiagnosticManager returns a new DiagnosticManager object
|
||||
func NewDiagnosticManager(session *session.Session) *Manager {
|
||||
return &Manager{
|
||||
DiagnosticManager: object.NewDiagnosticManager(
|
||||
session.Vim25(),
|
||||
),
|
||||
Session: session,
|
||||
}
|
||||
}
|
||||
102
vendor/github.com/vmware/vic/pkg/vsphere/disk/config.go
generated
vendored
102
vendor/github.com/vmware/vic/pkg/vsphere/disk/config.go
generated
vendored
@@ -1,102 +0,0 @@
|
||||
// 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 disk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/pkg/fs"
|
||||
)
|
||||
|
||||
type VirtualDiskConfig struct {
|
||||
// The URI in the datastore this disk can be found with
|
||||
DatastoreURI *object.DatastorePath
|
||||
|
||||
// The URI in the datastore to the parent of this disk
|
||||
ParentDatastoreURI *object.DatastorePath
|
||||
|
||||
// The size of the disk
|
||||
CapacityInKB int64
|
||||
|
||||
// Underlying filesystem
|
||||
Filesystem Filesystem
|
||||
|
||||
// Base disk UUID
|
||||
UUID string
|
||||
|
||||
DiskMode types.VirtualDiskMode
|
||||
}
|
||||
|
||||
func NewPersistentDisk(URI *object.DatastorePath) *VirtualDiskConfig {
|
||||
return &VirtualDiskConfig{
|
||||
DatastoreURI: URI,
|
||||
DiskMode: types.VirtualDiskModeIndependent_persistent,
|
||||
Filesystem: fs.NewExt4(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewNonPersistentDisk(URI *object.DatastorePath) *VirtualDiskConfig {
|
||||
return &VirtualDiskConfig{
|
||||
DatastoreURI: URI,
|
||||
DiskMode: types.VirtualDiskModeIndependent_nonpersistent,
|
||||
Filesystem: fs.NewExt4(),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *VirtualDiskConfig) WithParent(parent *object.DatastorePath) *VirtualDiskConfig {
|
||||
d.ParentDatastoreURI = parent
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *VirtualDiskConfig) WithFilesystem(ftype FilesystemType) *VirtualDiskConfig {
|
||||
switch ftype {
|
||||
case Xfs:
|
||||
d.Filesystem = fs.NewXFS()
|
||||
default:
|
||||
d.Filesystem = fs.NewExt4()
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *VirtualDiskConfig) WithCapacity(capacity int64) *VirtualDiskConfig {
|
||||
d.CapacityInKB = capacity
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// WithUUID can only be set on the base disk layer due to disklib bug
|
||||
// TODO: add an error mechanism for validating conditional settings like this
|
||||
func (d *VirtualDiskConfig) WithUUID(uuid string) *VirtualDiskConfig {
|
||||
d.UUID = uuid
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *VirtualDiskConfig) Hash() uint64 {
|
||||
key := fmt.Sprintf("%s-%t", d.DatastoreURI, d.IsPersistent())
|
||||
|
||||
hash := fnv.New64a()
|
||||
hash.Write([]byte(key))
|
||||
|
||||
return hash.Sum64()
|
||||
}
|
||||
|
||||
func (d *VirtualDiskConfig) IsPersistent() bool {
|
||||
return d.DiskMode == types.VirtualDiskModeIndependent_persistent || d.DiskMode == types.VirtualDiskModePersistent
|
||||
}
|
||||
473
vendor/github.com/vmware/vic/pkg/vsphere/disk/disk.go
generated
vendored
473
vendor/github.com/vmware/vic/pkg/vsphere/disk/disk.go
generated
vendored
@@ -1,473 +0,0 @@
|
||||
// Copyright 2016-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 disk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// FilesystemType represents the filesystem in use by a virtual disk
|
||||
type FilesystemType uint8
|
||||
|
||||
const (
|
||||
// Ext4 represents the ext4 file system
|
||||
Ext4 FilesystemType = iota + 1
|
||||
|
||||
// Xfs represents the XFS file system
|
||||
Xfs
|
||||
|
||||
// Ntfs represents the NTFS file system
|
||||
Ntfs
|
||||
|
||||
// directory in which to perform the direct mount of disk for bind mount
|
||||
// to actual target
|
||||
diskBindBase = "/.filesystem-by-label/"
|
||||
|
||||
// used to isolate applications from the lost+found in the root of ext4
|
||||
VolumeDataDir = "/.vic.vol.data"
|
||||
)
|
||||
|
||||
// Filesystem defines the interface for handling an attached virtual disk
|
||||
type Filesystem interface {
|
||||
Mkfs(op trace.Operation, devPath, label string) error
|
||||
SetLabel(op trace.Operation, devPath, labelName string) error
|
||||
Mount(op trace.Operation, devPath, targetPath string, options []string) error
|
||||
Unmount(op trace.Operation, path string) error
|
||||
}
|
||||
|
||||
// Semaphore represents the number of references to a disk
|
||||
type Semaphore struct {
|
||||
resource string
|
||||
refname string
|
||||
count uint64
|
||||
}
|
||||
|
||||
// NewSemaphore creates and returns a Semaphore initialized to 0
|
||||
func NewSemaphore(r, n string) *Semaphore {
|
||||
return &Semaphore{
|
||||
resource: r,
|
||||
refname: n,
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Increment increases the reference count by one
|
||||
func (r *Semaphore) Increment() uint64 {
|
||||
return atomic.AddUint64(&r.count, 1)
|
||||
}
|
||||
|
||||
// Decrement decreases the reference count by one
|
||||
func (r *Semaphore) Decrement() uint64 {
|
||||
return atomic.AddUint64(&r.count, ^uint64(0))
|
||||
}
|
||||
|
||||
// Count returns the current reference count
|
||||
func (r *Semaphore) Count() uint64 {
|
||||
return atomic.LoadUint64(&r.count)
|
||||
}
|
||||
|
||||
// InUseError is returned when a detach is attempted on a disk that is
|
||||
// still in use
|
||||
type InUseError struct {
|
||||
error
|
||||
}
|
||||
|
||||
// VirtualDisk represents a VMDK in the datastore, the device node it may be
|
||||
// attached at (if it's attached), the mountpoint it is mounted at (if
|
||||
// mounted), and other configuration.
|
||||
type VirtualDisk struct {
|
||||
*VirtualDiskConfig
|
||||
|
||||
// The device node the disk is attached to
|
||||
DevicePath string
|
||||
|
||||
// The path on the filesystem this device is attached to.
|
||||
mountPath string
|
||||
// The options that the disk is currently mounted with.
|
||||
mountOpts string
|
||||
|
||||
// To avoid attach/detach races, this lock serializes operations to the disk.
|
||||
l sync.Mutex
|
||||
|
||||
mountedRefs *Semaphore
|
||||
|
||||
attachedRefs *Semaphore
|
||||
}
|
||||
|
||||
// NewVirtualDisk creates and returns a new VirtualDisk object associated with the
|
||||
// given datastore formatted with the specified FilesystemType
|
||||
func NewVirtualDisk(op trace.Operation, config *VirtualDiskConfig, disks map[uint64]*VirtualDisk) (*VirtualDisk, error) {
|
||||
if !strings.HasSuffix(config.DatastoreURI.String(), ".vmdk") {
|
||||
return nil, fmt.Errorf("%s doesn't have a vmdk suffix", config.DatastoreURI.String())
|
||||
}
|
||||
|
||||
if d, ok := disks[config.Hash()]; ok {
|
||||
return d, nil
|
||||
}
|
||||
op.Debugf("Didn't find the disk %s in the DiskManager cache, creating it", config.DatastoreURI)
|
||||
|
||||
uri := config.DatastoreURI.String()
|
||||
d := &VirtualDisk{
|
||||
VirtualDiskConfig: config,
|
||||
mountedRefs: NewSemaphore(uri, "mount"),
|
||||
attachedRefs: NewSemaphore(uri, "attach"),
|
||||
}
|
||||
disks[config.Hash()] = d
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *VirtualDisk) setAttached(op trace.Operation, devicePath string) (err error) {
|
||||
if d.DevicePath == "" {
|
||||
// Question: what happens if this is called a second time with a different devicePath?
|
||||
d.DevicePath = devicePath
|
||||
}
|
||||
|
||||
count := d.attachedRefs.Increment()
|
||||
op.Debugf("incremented attach count for %s: %d", d.DatastoreURI, count)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *VirtualDisk) canBeDetached() error {
|
||||
if !d.attached() {
|
||||
return fmt.Errorf("%s is already detached", d.DatastoreURI)
|
||||
}
|
||||
|
||||
if d.mounted() {
|
||||
return fmt.Errorf("%s is mounted (%s)", d.DatastoreURI, d.mountPath)
|
||||
}
|
||||
|
||||
if d.inUseByOther() {
|
||||
return fmt.Errorf("Detach skipped - %s is still in use", d.DatastoreURI)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *VirtualDisk) setDetached(op trace.Operation, disks map[uint64]*VirtualDisk) {
|
||||
// we only call this when it's been detached, so always make the updates
|
||||
op.Debugf("Dropping %s from the DiskManager cache", d.DatastoreURI)
|
||||
d.DevicePath = ""
|
||||
delete(disks, d.Hash())
|
||||
}
|
||||
|
||||
// Mkfs formats the disk with Filesystem and sets the disk label
|
||||
func (d *VirtualDisk) Mkfs(op trace.Operation, labelName string) error {
|
||||
d.l.Lock()
|
||||
defer d.l.Unlock()
|
||||
|
||||
if !d.attached() {
|
||||
return fmt.Errorf("%s isn't attached", d.DatastoreURI)
|
||||
}
|
||||
|
||||
if d.mounted() {
|
||||
return fmt.Errorf("%s is still mounted (%s)", d.DatastoreURI, d.mountPath)
|
||||
}
|
||||
|
||||
return d.Filesystem.Mkfs(op, d.DevicePath, labelName)
|
||||
}
|
||||
|
||||
// SetLabel sets this disk's label
|
||||
func (d *VirtualDisk) SetLabel(op trace.Operation, labelName string) error {
|
||||
d.l.Lock()
|
||||
defer d.l.Unlock()
|
||||
|
||||
if !d.attached() {
|
||||
return fmt.Errorf("%s isn't attached", d.DatastoreURI)
|
||||
}
|
||||
|
||||
return d.Filesystem.SetLabel(op, d.DevicePath, labelName)
|
||||
}
|
||||
|
||||
func (d *VirtualDisk) attached() bool {
|
||||
return d.DevicePath != ""
|
||||
}
|
||||
|
||||
// Attached returns true if this disk is attached, false otherwise
|
||||
func (d *VirtualDisk) Attached() bool {
|
||||
d.l.Lock()
|
||||
defer d.l.Unlock()
|
||||
|
||||
return d.attached()
|
||||
}
|
||||
|
||||
func (d *VirtualDisk) attachedByOther() bool {
|
||||
return d.attachedRefs.Count() > 1
|
||||
}
|
||||
|
||||
// AttachedByOther returns true if the attached references are > 1
|
||||
func (d *VirtualDisk) AttachedByOther() bool {
|
||||
d.l.Lock()
|
||||
defer d.l.Unlock()
|
||||
|
||||
return d.attachedByOther()
|
||||
}
|
||||
|
||||
func (d *VirtualDisk) mountedByOther() bool {
|
||||
return d.mountedRefs.Count() > 1
|
||||
}
|
||||
|
||||
// MountedByOther returns true if the mounted references are > 1
|
||||
func (d *VirtualDisk) MountedByOther() bool {
|
||||
d.l.Lock()
|
||||
defer d.l.Unlock()
|
||||
|
||||
return d.mountedByOther()
|
||||
}
|
||||
|
||||
func (d *VirtualDisk) inUseByOther() bool {
|
||||
return d.mountedByOther() || d.attachedByOther()
|
||||
}
|
||||
|
||||
// InUseByOther returns true if the disk is currently attached or
|
||||
// mounted by someone else
|
||||
func (d *VirtualDisk) InUseByOther() bool {
|
||||
d.l.Lock()
|
||||
defer d.l.Unlock()
|
||||
|
||||
return d.inUseByOther()
|
||||
}
|
||||
|
||||
// Mount attempts to mount this disk. A NOP occurs if the disk is already mounted
|
||||
// It returns the path at which the disk is mounted
|
||||
// Enhancement: allow provision of mount path and refcount for:
|
||||
// specific mount point and options
|
||||
func (d *VirtualDisk) Mount(op trace.Operation, options []string) (string, error) {
|
||||
d.l.Lock()
|
||||
defer d.l.Unlock()
|
||||
|
||||
op.Debugf("Mounting %s", d.DatastoreURI)
|
||||
|
||||
if !d.attached() {
|
||||
err := fmt.Errorf("%s isn't attached", d.DatastoreURI)
|
||||
op.Error(err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
opts := strings.Join(options, ";")
|
||||
if !d.mounted() {
|
||||
mntpath, err := ioutil.TempDir("", "mnt")
|
||||
if err != nil {
|
||||
err := fmt.Errorf("unable to create mountpint: %s", err)
|
||||
op.Error(err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// get mount source, disk is already mounted if this func returns without error
|
||||
mntsrc, err := d.getMountSource(op, options)
|
||||
if err != nil {
|
||||
op.Error(err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// then mount it at the correct source
|
||||
if strings.HasSuffix(mntsrc, VolumeDataDir) {
|
||||
// append bind mount options if we are masking lost+found
|
||||
options = append(options, "bind")
|
||||
}
|
||||
|
||||
if err = d.Filesystem.Mount(op, mntsrc, mntpath, options); err != nil {
|
||||
op.Errorf("Failed to mount disk: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
d.mountPath = mntpath
|
||||
d.mountOpts = opts
|
||||
} else {
|
||||
// basic santiy check for matching options - we don't want to share a r/o mount
|
||||
// if the request was for r/w. Ideally we'd just mount this at a different location with the
|
||||
// requested options but that requires separate ref counting.
|
||||
// TODO: support differing mount opts
|
||||
if d.mountOpts != opts {
|
||||
op.Errorf("Unable to use mounted disk due to differing options: %s != %s", d.mountOpts, opts)
|
||||
return "", fmt.Errorf("incompatible mount options for disk reuse")
|
||||
}
|
||||
}
|
||||
|
||||
count := d.mountedRefs.Increment()
|
||||
op.Debugf("incremented mount count for %s: %d", d.mountPath, count)
|
||||
|
||||
return d.mountPath, nil
|
||||
}
|
||||
|
||||
// getMountSource mounts the disk rootfs, checks if it has volumeDataDir, if so it returns volumeDataDir
|
||||
// as the mount source to mask the lost+found folder, otherwise it returns the device path
|
||||
// NOTE: this mount should not be counted in the ref counts, bindTarget will be unmounted when disk detaches.
|
||||
// TODO: if we support different mount opts, we can't use the same bindTarget anymore.
|
||||
// need to assign each opt a different name, we can add a field in VirtualDisk that tracks bindTarget
|
||||
func (d *VirtualDisk) getMountSource(op trace.Operation, options []string) (string, error) {
|
||||
// need to first mount the disk under the diskBindBase
|
||||
bindTarget := path.Join(diskBindBase, d.DevicePath)
|
||||
|
||||
// sanity check to make sure previous bindTarget is cleaned up properly
|
||||
var e1, e2 error
|
||||
_, e1 = os.Stat(bindTarget)
|
||||
if e1 == nil {
|
||||
// bindTarget exists, check whether or not bindTarget is a mount point
|
||||
e2 = os.Remove(bindTarget)
|
||||
}
|
||||
|
||||
// we don't want to remount under the same mountpoint, so we only mounts under the following cases
|
||||
// first case: bindTarget exists but not a mountpoint
|
||||
// second case: bindTarget doesn't exist
|
||||
if (e1 == nil && e2 == nil) || os.IsNotExist(e1) {
|
||||
// #nosec
|
||||
if err := os.MkdirAll(bindTarget, 0744); err != nil {
|
||||
err = fmt.Errorf("unable to create mount point %s: %s", bindTarget, err)
|
||||
op.Error(err)
|
||||
return "", err
|
||||
}
|
||||
if err := d.Filesystem.Mount(op, d.DevicePath, bindTarget, options); err != nil {
|
||||
op.Errorf("Failed to mount disk: %s", err)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
mntsrc := path.Join(bindTarget, VolumeDataDir)
|
||||
// if the volume contains a volumeDataDir directory then mount that instead of the root of the filesystem
|
||||
// if we cannot read it we go with the root of the filesystem
|
||||
_, err := os.Stat(mntsrc)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// if there's no such directory then revert to using the device directly
|
||||
op.Infof("No " + VolumeDataDir + " data directory in volume, mounting filesystem directly")
|
||||
mntsrc = d.DevicePath
|
||||
} else {
|
||||
return "", fmt.Errorf("unable to determine whether lost+found masking is required: %s", err)
|
||||
}
|
||||
}
|
||||
return mntsrc, nil
|
||||
}
|
||||
|
||||
// Unmount attempts to unmount a virtual disk
|
||||
func (d *VirtualDisk) Unmount(op trace.Operation) error {
|
||||
d.l.Lock()
|
||||
defer d.l.Unlock()
|
||||
|
||||
if !d.mounted() {
|
||||
return fmt.Errorf("%s already unmounted", d.DatastoreURI)
|
||||
}
|
||||
|
||||
count := d.mountedRefs.Decrement()
|
||||
op.Debugf("decremented mount count for %s: %d", d.mountPath, count)
|
||||
|
||||
if count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// no more mount references to this disk, so actually unmount
|
||||
if err := d.Filesystem.Unmount(op, d.mountPath); err != nil {
|
||||
err := fmt.Errorf("failed to unmount disk: %s", err)
|
||||
op.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// only remove the mount directory - if we've succeeded in the unmount there won't be anything in it
|
||||
// if we somehow get here and there is content we do NOT want to delete it
|
||||
if err := os.Remove(d.mountPath); err != nil {
|
||||
err := fmt.Errorf("failed to clean up mount point: %s", err)
|
||||
op.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
d.mountPath = ""
|
||||
|
||||
// mountpath is cleaned, we need to clean up the bindTarget as well
|
||||
bindTarget := path.Join(diskBindBase, d.DevicePath)
|
||||
if err := d.Filesystem.Unmount(op, bindTarget); err != nil {
|
||||
return fmt.Errorf("failed to clean up actual mount point on device: %s", err)
|
||||
}
|
||||
|
||||
// only remove the mount directory - if we've succeeded in the unmount there won't be anything in it
|
||||
// if we somehow get here and there is content we do NOT want to delete it
|
||||
if err := os.Remove(bindTarget); err != nil {
|
||||
err := fmt.Errorf("failed to clean up actual mount point: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *VirtualDisk) mountPathFn() (string, error) {
|
||||
if !d.mounted() {
|
||||
return "", fmt.Errorf("%s isn't mounted", d.DatastoreURI)
|
||||
}
|
||||
|
||||
return d.mountPath, nil
|
||||
}
|
||||
|
||||
// MountPath returns the path on which the virtual disk is mounted,
|
||||
// or an error if the disk is not mounted
|
||||
func (d *VirtualDisk) MountPath() (string, error) {
|
||||
d.l.Lock()
|
||||
defer d.l.Unlock()
|
||||
|
||||
return d.mountPathFn()
|
||||
}
|
||||
|
||||
// DiskPath returns a URL referencing the path of the virtual disk
|
||||
// on the datastore
|
||||
func (d *VirtualDisk) DiskPath() url.URL {
|
||||
d.l.Lock()
|
||||
defer d.l.Unlock()
|
||||
|
||||
return url.URL{
|
||||
Scheme: "ds",
|
||||
Path: d.DatastoreURI.String(),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *VirtualDisk) mounted() bool {
|
||||
return d.mountPath != ""
|
||||
}
|
||||
|
||||
// Mounted returns true if the virtual disk is mounted, false otherwise
|
||||
func (d *VirtualDisk) Mounted() bool {
|
||||
d.l.Lock()
|
||||
defer d.l.Unlock()
|
||||
|
||||
return d.mounted()
|
||||
}
|
||||
|
||||
func (d *VirtualDisk) canBeUnmounted() error {
|
||||
if !d.attached() {
|
||||
return fmt.Errorf("%s is detached", d.DatastoreURI)
|
||||
}
|
||||
|
||||
if !d.mounted() {
|
||||
return fmt.Errorf("%s is unmounted", d.DatastoreURI)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *VirtualDisk) setUmounted() error {
|
||||
if !d.mounted() {
|
||||
return fmt.Errorf("%s already unmounted", d.DatastoreURI)
|
||||
}
|
||||
|
||||
d.mountPath = ""
|
||||
return nil
|
||||
}
|
||||
292
vendor/github.com/vmware/vic/pkg/vsphere/disk/disk_ext4_test.go
generated
vendored
292
vendor/github.com/vmware/vic/pkg/vsphere/disk/disk_ext4_test.go
generated
vendored
@@ -1,292 +0,0 @@
|
||||
// Copyright 2016-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 disk
|
||||
|
||||
import (
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/vic/lib/guest"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/datastore"
|
||||
)
|
||||
|
||||
// Create a disk, make an ext filesystem on it, set the label, mount it,
|
||||
// unmount it, then clean up.
|
||||
func TestCreateFS(t *testing.T) {
|
||||
log.SetLevel(log.InfoLevel)
|
||||
if testing.Verbose() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
session := Session(context.Background(), t)
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
op := trace.NewOperation(context.TODO(), t.Name())
|
||||
|
||||
vchvm, err := guest.GetSelf(op, session)
|
||||
if err != nil {
|
||||
t.Skip("Not in a vm")
|
||||
}
|
||||
view := ContainerView(op, session, vchvm)
|
||||
if view == nil {
|
||||
t.Skip("Can't create a view")
|
||||
}
|
||||
|
||||
imagestore := &object.DatastorePath{
|
||||
Datastore: session.Datastore.Name(),
|
||||
Path: datastore.TestName(t.Name()),
|
||||
}
|
||||
|
||||
// file manager
|
||||
fm := object.NewFileManager(session.Vim25())
|
||||
// create a directory in the datastore
|
||||
err = fm.MakeDirectory(context.TODO(), imagestore.String(), nil, true)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Nuke the image store
|
||||
defer func() {
|
||||
task, err := fm.DeleteDatastoreFile(context.TODO(), imagestore.String(), nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
_, err = task.WaitForResult(context.TODO(), nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// create a diskmanager
|
||||
vdm, err := NewDiskManager(op, session, view)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, vdm) {
|
||||
return
|
||||
}
|
||||
|
||||
diskSize := int64(1 << 10)
|
||||
scratch := &object.DatastorePath{
|
||||
Datastore: session.Datastore.Name(),
|
||||
Path: path.Join(imagestore.Path, "scratch.vmdk"),
|
||||
}
|
||||
|
||||
config := NewPersistentDisk(scratch).WithCapacity(diskSize)
|
||||
d, err := vdm.CreateAndAttach(op, config)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// make the filesysetem
|
||||
if err = d.Mkfs(op, "foo"); !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// set the label
|
||||
if err = d.SetLabel(op, "foo"); !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// do the mount
|
||||
dir, err := d.Mount(op, nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// boom
|
||||
if mounted, err := mount.Mounted(dir); !assert.NoError(t, err) || !assert.True(t, mounted) {
|
||||
return
|
||||
}
|
||||
|
||||
// clean up
|
||||
err = d.Unmount(op)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
err = vdm.Detach(op, config)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachFS(t *testing.T) {
|
||||
log.SetLevel(log.InfoLevel)
|
||||
if testing.Verbose() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
session := Session(context.Background(), t)
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
op := trace.NewOperation(context.TODO(), t.Name())
|
||||
|
||||
vchvm, err := guest.GetSelf(op, session)
|
||||
if err != nil {
|
||||
t.Skip("Not in a vm")
|
||||
}
|
||||
view := ContainerView(op, session, vchvm)
|
||||
if view == nil {
|
||||
t.Skip("Can't create a view")
|
||||
}
|
||||
|
||||
imagestore := &object.DatastorePath{
|
||||
Datastore: session.Datastore.Name(),
|
||||
Path: datastore.TestName(t.Name()),
|
||||
}
|
||||
|
||||
// file manager
|
||||
fm := object.NewFileManager(session.Vim25())
|
||||
// create a directory in the datastore
|
||||
err = fm.MakeDirectory(context.TODO(), imagestore.String(), nil, true)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Nuke the image store
|
||||
defer func() {
|
||||
task, err := fm.DeleteDatastoreFile(context.TODO(), imagestore.String(), nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
_, err = task.WaitForResult(context.TODO(), nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// create a diskmanager
|
||||
vdm, err := NewDiskManager(op, session, view)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, vdm) {
|
||||
return
|
||||
}
|
||||
|
||||
diskSize := int64(1 << 10)
|
||||
scratch := &object.DatastorePath{
|
||||
Datastore: session.Datastore.Name(),
|
||||
Path: path.Join(imagestore.Path, "scratch.vmdk"),
|
||||
}
|
||||
|
||||
config := NewPersistentDisk(scratch).WithCapacity(diskSize)
|
||||
d, err := vdm.CreateAndAttach(op, config)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// make the filesysetem
|
||||
if err = d.Mkfs(op, "foo"); !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// set the label
|
||||
if err = d.SetLabel(op, "foo"); !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// do the mount
|
||||
dir, err := d.Mount(op, nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// boom
|
||||
if mounted, err := mount.Mounted(dir); !assert.NoError(t, err) || !assert.True(t, mounted) {
|
||||
return
|
||||
}
|
||||
|
||||
// clean up
|
||||
err = d.Unmount(op)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
err = vdm.Detach(op, config)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
child := &object.DatastorePath{
|
||||
Datastore: session.Datastore.Name(),
|
||||
Path: path.Join(imagestore.Path, "child.vmdk"),
|
||||
}
|
||||
|
||||
config = NewPersistentDisk(child).WithParent(scratch)
|
||||
d, err = vdm.CreateAndAttach(op, config)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// do the mount
|
||||
dir, err = d.Mount(op, nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// boom
|
||||
if mounted, err := mount.Mounted(dir); !assert.NoError(t, err) || !assert.True(t, mounted) {
|
||||
return
|
||||
}
|
||||
|
||||
// clean up
|
||||
err = d.Unmount(op)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
err = vdm.Detach(op, config)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
config = NewPersistentDisk(child)
|
||||
d, err = vdm.CreateAndAttach(op, config)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// do the mount
|
||||
dir, err = d.Mount(op, nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// boom
|
||||
if mounted, err := mount.Mounted(dir); !assert.NoError(t, err) || !assert.True(t, mounted) {
|
||||
return
|
||||
}
|
||||
|
||||
// clean up
|
||||
err = d.Unmount(op)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
err = vdm.Detach(op, config)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
668
vendor/github.com/vmware/vic/pkg/vsphere/disk/disk_manager.go
generated
vendored
668
vendor/github.com/vmware/vic/pkg/vsphere/disk/disk_manager.go
generated
vendored
@@ -1,668 +0,0 @@
|
||||
// Copyright 2016-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 disk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/view"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/datastore"
|
||||
"github.com/vmware/vic/pkg/vsphere/guest"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/tasks"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
const (
|
||||
// You can assign the device to (1:z ), where 1 is SCSI controller 1 and z is a virtual device node from 0 to 15.
|
||||
// https://pubs.vmware.com/vsphere-65/index.jsp#com.vmware.vsphere.vm_admin.doc/GUID-5872D173-A076-42FE-8D0B-9DB0EB0E7362.html
|
||||
MaxAttachedDisks = 16
|
||||
)
|
||||
|
||||
// Manager manages disks for the vm it runs on. The expectation is this is run
|
||||
// from a VM on a vsphere instance. This VM creates disks on ESX, attaches
|
||||
// them to itself, writes to them, then detaches them.
|
||||
type Manager struct {
|
||||
// We can't have more than this number of disks attached.
|
||||
maxAttached chan bool
|
||||
|
||||
// reference to the vm this is running on.
|
||||
vm *vm.VirtualMachine
|
||||
|
||||
// VirtualDiskManager that is used to create vmdks directly on datastore
|
||||
// from https://pubs.vmware.com/vsphere-65/index.jsp?topic=%2Fcom.vmware.vspsdk.apiref.doc%2Fvim.VirtualDiskManager.html
|
||||
// Most VirtualDiskManager APIs will be DEPRECATED as of vSphere 6.5. Please use VStorageObjectManager APIs to manage Virtual disks.
|
||||
vdMngr *object.VirtualDiskManager
|
||||
|
||||
// ContainerView - https://pubs.vmware.com/vsphere-6-0/index.jsp#com.vmware.wssdk.apiref.doc/vim.view.ContainerView.html
|
||||
view *view.ContainerView
|
||||
|
||||
// The controller on this vm.
|
||||
controller *types.ParaVirtualSCSIController
|
||||
|
||||
// The PCI + SCSI device /dev node string format the disks can be attached with
|
||||
byPathFormat string
|
||||
|
||||
// serialize reconfigure operations
|
||||
mu sync.Mutex
|
||||
|
||||
// map of URIs to VirtualDisk structs so that we can return the same instance to the caller, required for ref counting
|
||||
Disks map[uint64]*VirtualDisk
|
||||
// used for locking the disk cache
|
||||
disksLock sync.Mutex
|
||||
}
|
||||
|
||||
// NewDiskManager creates a new Manager instance associated with the caller VM
|
||||
func NewDiskManager(op trace.Operation, session *session.Session, v *view.ContainerView) (*Manager, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
vm, err := guest.GetSelf(op, session)
|
||||
if err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
// create handle to the docker daemon VM as we need to mount disks on it
|
||||
controller, byPathFormat, err := verifyParavirtualScsiController(op, vm)
|
||||
if err != nil {
|
||||
op.Errorf("scsi controller verification failed: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Manager{
|
||||
maxAttached: make(chan bool, MaxAttachedDisks),
|
||||
vm: vm,
|
||||
vdMngr: object.NewVirtualDiskManager(vm.Vim25()),
|
||||
view: v,
|
||||
controller: controller,
|
||||
byPathFormat: byPathFormat,
|
||||
Disks: make(map[uint64]*VirtualDisk),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// toSpec converts the given config to VirtualDisk spec
|
||||
func (m *Manager) toSpec(config *VirtualDiskConfig) *types.VirtualDisk {
|
||||
backing := &types.VirtualDiskFlatVer2BackingInfo{
|
||||
DiskMode: string(config.DiskMode),
|
||||
ThinProvisioned: types.NewBool(true),
|
||||
VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
|
||||
FileName: config.DatastoreURI.String(),
|
||||
},
|
||||
}
|
||||
|
||||
if config.UUID != "" {
|
||||
backing.Uuid = config.UUID
|
||||
}
|
||||
|
||||
disk := &types.VirtualDisk{
|
||||
VirtualDevice: types.VirtualDevice{
|
||||
Key: -1,
|
||||
ControllerKey: m.controller.Key,
|
||||
UnitNumber: new(int32),
|
||||
Backing: backing,
|
||||
},
|
||||
// As of vSphere API 5.5 capacityInKB is deprecated. Documentation suggest using capacityInBytes but we can't unset CapacityInKB and its default value 0 causes problems
|
||||
// ... Exception thrown during reconfigure: (vim.vm.ConfigSpec) {
|
||||
// ...
|
||||
// --> unitNumber = -1,
|
||||
// --> capacityInKB = 0,
|
||||
// --> capacityInBytes = 8192000000,
|
||||
// --> shares = (vim.SharesInfo) null,
|
||||
// ...
|
||||
CapacityInBytes: config.CapacityInKB * 1024,
|
||||
CapacityInKB: config.CapacityInKB,
|
||||
}
|
||||
|
||||
if config.ParentDatastoreURI != nil {
|
||||
backing.Parent = &types.VirtualDiskFlatVer2BackingInfo{
|
||||
VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
|
||||
FileName: config.ParentDatastoreURI.String(),
|
||||
},
|
||||
}
|
||||
|
||||
// Capacity needs to be 0 as we inherit it from the parent
|
||||
disk.CapacityInBytes = 0
|
||||
disk.CapacityInKB = 0
|
||||
}
|
||||
|
||||
// It's possible the VCH has a disk already attached.
|
||||
*disk.VirtualDevice.UnitNumber = -1
|
||||
|
||||
return disk
|
||||
}
|
||||
|
||||
// CreateAndAttach creates a new VMDK, attaches it and ensures that the device becomes visible to the caller.
|
||||
// Returns a VirtualDisk corresponding to the created and attached disk.
|
||||
func (m *Manager) CreateAndAttach(op trace.Operation, config *VirtualDiskConfig) (*VirtualDisk, error) {
|
||||
defer trace.End(trace.Begin(config.DatastoreURI.String()))
|
||||
|
||||
// Get or create entry in disk cache
|
||||
m.disksLock.Lock()
|
||||
d, err := NewVirtualDisk(op, config, m.Disks)
|
||||
if err != nil {
|
||||
m.disksLock.Unlock()
|
||||
|
||||
op.Errorf("Unable to create disk entry: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
// take disk lock before we release the cache lock - this prevents the disk being removed from the cache
|
||||
// before we get a chance to adjust refcounts
|
||||
d.l.Lock()
|
||||
defer d.l.Unlock()
|
||||
|
||||
m.disksLock.Unlock()
|
||||
|
||||
// check if the disk is attached from the perspective of the cache entry
|
||||
if d.DevicePath != "" {
|
||||
// this is a horrificaly misnamed call - it's incrementing the reference count
|
||||
d.setAttached(op, "")
|
||||
return d, nil
|
||||
}
|
||||
|
||||
op.Infof("Create/attach vmdk %s from parent %s", config.DatastoreURI, config.ParentDatastoreURI)
|
||||
|
||||
// we use findDiskByFilename to check if the disk is already attached
|
||||
// if it is then it's indicative of an error because it wasn't found in the cache, but this lets us recover
|
||||
_, ferr := findDiskByFilename(op, m.vm, d.DatastoreURI.String(), d.IsPersistent())
|
||||
if os.IsNotExist(ferr) {
|
||||
if err := m.attach(op, config); err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
} else {
|
||||
op.Errorf("Failed to determine if disk is already attached: %s", err)
|
||||
// this will be tidied up if/when the waitForDevice fails
|
||||
}
|
||||
|
||||
op.Debugf("Mapping vmdk to pci device %s", config.DatastoreURI)
|
||||
devicePath, err := m.devicePathByURI(op, config.DatastoreURI, d.IsPersistent())
|
||||
if err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
blockDev, err := waitForDevice(op, devicePath)
|
||||
if err != nil {
|
||||
op.Errorf("waitForDevice failed for %s with %s", d.DatastoreURI, errors.ErrorStack(err))
|
||||
// ensure that the disk is detached if it's the publish that's failed
|
||||
|
||||
disk, findErr := findDiskByFilename(op, m.vm, d.DatastoreURI.String(), d.IsPersistent())
|
||||
if findErr != nil {
|
||||
op.Debugf("findDiskByFilename(%s) failed with %s", d.DatastoreURI, errors.ErrorStack(findErr))
|
||||
}
|
||||
|
||||
if detachErr := m.detach(op, disk); detachErr != nil {
|
||||
op.Debugf("detach(%s) failed with %s", d.DatastoreURI, errors.ErrorStack(detachErr))
|
||||
}
|
||||
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
err = d.setAttached(op, blockDev)
|
||||
|
||||
return d, err
|
||||
}
|
||||
|
||||
// Create creates a disk without a parent (and doesn't attach it).
|
||||
func (m *Manager) Create(op trace.Operation, config *VirtualDiskConfig) (*VirtualDisk, error) {
|
||||
defer trace.End(trace.Begin(config.DatastoreURI.String()))
|
||||
|
||||
var err error
|
||||
|
||||
d, err := NewVirtualDisk(op, config, m.Disks)
|
||||
if err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
d.l.Lock()
|
||||
defer d.l.Unlock()
|
||||
|
||||
spec := &types.FileBackedVirtualDiskSpec{
|
||||
VirtualDiskSpec: types.VirtualDiskSpec{
|
||||
DiskType: string(types.VirtualDiskTypeThin),
|
||||
AdapterType: string(types.VirtualDiskAdapterTypeLsiLogic),
|
||||
},
|
||||
CapacityKb: config.CapacityInKB,
|
||||
}
|
||||
|
||||
op.Infof("Creating vmdk for layer or volume %s", d.DatastoreURI)
|
||||
err = tasks.Wait(op, func(ctx context.Context) (tasks.Task, error) {
|
||||
return m.vdMngr.CreateVirtualDisk(ctx, d.DatastoreURI.String(), nil, spec)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Gets a disk given a datastore path URI to the vmdk
|
||||
func (m *Manager) Get(op trace.Operation, config *VirtualDiskConfig) (*VirtualDisk, error) {
|
||||
defer trace.End(trace.Begin(config.DatastoreURI.String()))
|
||||
|
||||
d, err := NewVirtualDisk(op, config, m.Disks)
|
||||
if err != nil {
|
||||
return nil, errors.Trace(err)
|
||||
}
|
||||
|
||||
d.l.Lock()
|
||||
defer d.l.Unlock()
|
||||
|
||||
d.ParentDatastoreURI, err = m.DiskParent(op, config)
|
||||
return d, err
|
||||
}
|
||||
|
||||
// DiskParent returns the parent for an existing disk, based on the disk datastore URI in the config,
|
||||
// and ignoring any parent specified in the config.
|
||||
// datastore path will be nil if the disk has no parent
|
||||
func (m *Manager) DiskParent(op trace.Operation, config *VirtualDiskConfig) (*object.DatastorePath, error) {
|
||||
defer trace.End(trace.Begin(config.DatastoreURI.String()))
|
||||
|
||||
info, err := m.vdMngr.QueryVirtualDiskInfo(op, config.DatastoreURI.String(), m.vm.Datacenter, true)
|
||||
if err != nil {
|
||||
op.Errorf("Error querying parents (%s): %s", config.DatastoreURI, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// the last elem in the info list is the disk we just looked up.
|
||||
p := info[len(info)-1]
|
||||
|
||||
if p.Parent != "" {
|
||||
ppth, err := datastore.PathFromString(p.Parent)
|
||||
if err != nil {
|
||||
op.Errorf("Error converting parent to datastore URI (%s): %s", p.Parent, err)
|
||||
return nil, err
|
||||
}
|
||||
return ppth, nil
|
||||
}
|
||||
|
||||
// no parent
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// TODO(FA) this doesn't work since delta disks get set with `deletable =
|
||||
// false` when they become parents. This needs some thought and will require
|
||||
// some answers from a larger context.
|
||||
//func (m *DiskManager) Delete(ctx context.Context, d *VirtualDisk) error {
|
||||
// defer trace.End(trace.Begin(d.DatastoreURI))
|
||||
//
|
||||
// log.Infof("Deleting %s", d.DatastoreURI)
|
||||
//
|
||||
// d.l.Lock()
|
||||
// defer d.l.Unlock()
|
||||
//
|
||||
// if d.isAttached() {
|
||||
// return fmt.Errorf("cannot delete %s, still attached (%s)", d.DatastoreURI, d.devicePath)
|
||||
// }
|
||||
//
|
||||
// // TODO(FA) Check if disk is a parent.
|
||||
//
|
||||
// vdm := object.NewVirtualDiskManager(m.vm.Client())
|
||||
// task, err := vdm.DeleteVirtualDisk(ctx, d.DatastoreURI, nil)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// err = task.Wait(ctx)
|
||||
// if err != nil {
|
||||
// return errors.Trace(err)
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// Attach attempts to attach a virtual disk
|
||||
func (m *Manager) attach(op trace.Operation, config *VirtualDiskConfig) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
disk := m.toSpec(config)
|
||||
|
||||
deviceList := object.VirtualDeviceList{}
|
||||
deviceList = append(deviceList, disk)
|
||||
|
||||
changeSpec, err := deviceList.ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
machineSpec := types.VirtualMachineConfigSpec{}
|
||||
machineSpec.DeviceChange = append(machineSpec.DeviceChange, changeSpec...)
|
||||
|
||||
// ensure we abide by max attached disks limits
|
||||
m.maxAttached <- true
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
// make sure the op is still valid as the above line could block for a long time
|
||||
select {
|
||||
case <-op.Done():
|
||||
return op.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
_, err = m.vm.WaitForResult(op, func(ctx context.Context) (tasks.Task, error) {
|
||||
t, er := m.vm.Reconfigure(ctx, machineSpec)
|
||||
|
||||
if t != nil {
|
||||
op.Debugf("Attach reconfigure task=%s", t.Reference())
|
||||
}
|
||||
|
||||
return t, er
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
select {
|
||||
case <-m.maxAttached:
|
||||
default:
|
||||
}
|
||||
|
||||
op.Errorf("vmdk storage driver failed to attach disk: %s", errors.ErrorStack(err))
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Detach attempts to detach a virtual disk
|
||||
func (m *Manager) Detach(op trace.Operation, config *VirtualDiskConfig) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
// we have to hold the cache lock until we're done deleting the cache entry
|
||||
// or until we know we're not going to delete the entry
|
||||
m.disksLock.Lock()
|
||||
defer m.disksLock.Unlock()
|
||||
|
||||
d, err := NewVirtualDisk(op, config, m.Disks)
|
||||
if err != nil {
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
d.l.Lock()
|
||||
defer d.l.Unlock()
|
||||
|
||||
count := d.attachedRefs.Decrement()
|
||||
op.Debugf("decremented attach count for %s: %d", d.DatastoreURI, count)
|
||||
if count > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := d.canBeDetached(); err != nil {
|
||||
op.Errorf("disk needs to be detached but is still in use: %s", err)
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
op.Infof("Detaching disk %s", d.DevicePath)
|
||||
|
||||
disk, err := findDiskByFilename(op, m.vm, d.DatastoreURI.String(), d.IsPersistent())
|
||||
if err != nil {
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
if err = m.detach(op, disk); err != nil {
|
||||
op.Errorf("detach for %s failed with %s", d.DevicePath, errors.ErrorStack(err))
|
||||
return errors.Trace(err)
|
||||
}
|
||||
|
||||
// this deletes the disk from the disk cache
|
||||
d.setDetached(op, m.Disks)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) DetachAll(op trace.Operation) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
disks, err := findAllDisks(op, m.vm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, disk := range disks {
|
||||
if err2 := m.detach(op, disk); err != nil {
|
||||
op.Errorf("error detaching disk: %s", err2.Error())
|
||||
// return the last error on the return of this function
|
||||
err = err2
|
||||
// if we failed here that means we have a disk attached, ensure we abide by max attached disks limits
|
||||
m.maxAttached <- true
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) detach(op trace.Operation, disk *types.VirtualDisk) error {
|
||||
config := []types.BaseVirtualDeviceConfigSpec{
|
||||
&types.VirtualDeviceConfigSpec{
|
||||
Device: disk,
|
||||
Operation: types.VirtualDeviceConfigSpecOperationRemove,
|
||||
},
|
||||
}
|
||||
|
||||
spec := types.VirtualMachineConfigSpec{}
|
||||
spec.DeviceChange = config
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
_, err := m.vm.WaitForResult(op, func(ctx context.Context) (tasks.Task, error) {
|
||||
t, er := m.vm.Reconfigure(ctx, spec)
|
||||
|
||||
if t != nil {
|
||||
op.Debugf("Detach reconfigure task=%s", t.Reference())
|
||||
}
|
||||
|
||||
return t, er
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
select {
|
||||
case <-m.maxAttached:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) devicePathByURI(op trace.Operation, datastoreURI *object.DatastorePath, persistent bool) (string, error) {
|
||||
disk, err := findDiskByFilename(op, m.vm, datastoreURI.String(), persistent)
|
||||
if err != nil {
|
||||
op.Errorf("findDisk failed for %s with %s", datastoreURI.String(), errors.ErrorStack(err))
|
||||
return "", errors.Trace(err)
|
||||
}
|
||||
|
||||
sysPath := fmt.Sprintf(m.byPathFormat, *disk.UnitNumber)
|
||||
|
||||
return sysPath, nil
|
||||
}
|
||||
|
||||
// AttachAndMount creates and attaches a vmdk as a non-persistent disk, mounts it, and returns the mount path.
|
||||
func (m *Manager) AttachAndMount(op trace.Operation, datastoreURI *object.DatastorePath, persistent bool) (string, error) {
|
||||
var config *VirtualDiskConfig
|
||||
|
||||
op.Infof("Attach/Mount %s", datastoreURI.String())
|
||||
|
||||
if !persistent {
|
||||
config = NewNonPersistentDisk(datastoreURI)
|
||||
} else {
|
||||
config = NewPersistentDisk(datastoreURI)
|
||||
}
|
||||
|
||||
d, err := m.CreateAndAttach(op, config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// don't update access time - that would cause the diff operation to mutate the filesystem
|
||||
opts := []string{"noatime"}
|
||||
|
||||
if !persistent {
|
||||
opts = append(opts, "ro")
|
||||
}
|
||||
|
||||
return d.Mount(op, opts)
|
||||
|
||||
}
|
||||
|
||||
// UnmountAndDetach unmounts and detaches a disk, subsequently cleaning the mount path
|
||||
func (m *Manager) UnmountAndDetach(op trace.Operation, datastoreURI *object.DatastorePath, persistent bool) error {
|
||||
var config *VirtualDiskConfig
|
||||
|
||||
if !persistent {
|
||||
config = NewNonPersistentDisk(datastoreURI)
|
||||
} else {
|
||||
config = NewPersistentDisk(datastoreURI)
|
||||
}
|
||||
|
||||
d, err := NewVirtualDisk(op, config, m.Disks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
op.Infof("Unmount and Detach %s:%s", d.mountPath, d.DatastoreURI)
|
||||
|
||||
err = d.Unmount(op)
|
||||
derr := m.Detach(op, config)
|
||||
|
||||
if err != nil || derr != nil {
|
||||
op.Errorf("Error during unmount or detach, unmount: %s, detach: %s", err, derr)
|
||||
// prioritize first error
|
||||
if err == nil {
|
||||
err = derr
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Manager) InUse(op trace.Operation, config *VirtualDiskConfig, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
mngr := view.NewManager(m.vm.Vim25())
|
||||
|
||||
// Create view of VirtualMachine objects under the VCH's resource pool
|
||||
view2, err := mngr.CreateContainerView(op, m.vm.Session.Pool.Reference(), []string{"VirtualMachine"}, true)
|
||||
if err != nil {
|
||||
op.Errorf("failed to create view: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
defer view2.Destroy(op)
|
||||
|
||||
var mos []mo.VirtualMachine
|
||||
// Retrieve needed properties of all machines under this view
|
||||
err = view2.Retrieve(op, []string{"VirtualMachine"}, []string{"name", "config.hardware", "runtime.powerState"}, &mos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var vms []*vm.VirtualMachine
|
||||
// iterate over them to see whether they have the disk we want
|
||||
for i := range mos {
|
||||
mo := mos[i]
|
||||
op.Debugf("Working on vm %q", mo.Name)
|
||||
|
||||
if !filter(&mo) {
|
||||
op.Debugf("Filtering out vm %q", mo.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, device := range mo.Config.Hardware.Device {
|
||||
label := device.GetVirtualDevice().DeviceInfo.GetDescription().Label
|
||||
db := device.GetVirtualDevice().Backing
|
||||
if db == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
switch t := db.(type) {
|
||||
case types.BaseVirtualDeviceFileBackingInfo:
|
||||
if config.DatastoreURI.String() == t.GetVirtualDeviceFileBackingInfo().FileName {
|
||||
op.Infof("Found active user of target disk %s: %q", label, mo.Name)
|
||||
vms = append(vms, vm.NewVirtualMachine(context.Background(), m.vm.Session, mo.Reference()))
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
return vms, nil
|
||||
}
|
||||
|
||||
func (m *Manager) DiskFinder(op trace.Operation, filter func(p string) bool) (string, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
mngr := view.NewManager(m.vm.Vim25())
|
||||
|
||||
// Create view of VirtualMachine objects under the VCH's resource pool
|
||||
view2, err := mngr.CreateContainerView(op, m.vm.Session.Pool.Reference(), []string{"VirtualMachine"}, true)
|
||||
if err != nil {
|
||||
op.Errorf("failed to create view: %s", err)
|
||||
return "", err
|
||||
}
|
||||
defer view2.Destroy(op)
|
||||
|
||||
var mos []mo.VirtualMachine
|
||||
// Retrieve needed properties of all machines under this view
|
||||
err = view2.Retrieve(op, []string{"VirtualMachine"}, []string{"name", "config.hardware", "runtime.powerState"}, &mos)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// iterate over them to see whether they have the disk we want
|
||||
for i := range mos {
|
||||
mo := mos[i]
|
||||
|
||||
op.Debugf("Working on vm %q", mo.Name)
|
||||
|
||||
// observed empty fields here when copying to all 14 volumes on a cVM so being paranoid
|
||||
if mo.Config == nil || mo.Config.Hardware.Device == nil {
|
||||
op.Warnf("Skipping disk presence check for %q: failed to retrieve vm config", mo.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, device := range mo.Config.Hardware.Device {
|
||||
label := device.GetVirtualDevice().DeviceInfo.GetDescription().Label
|
||||
db := device.GetVirtualDevice().Backing
|
||||
if db == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
switch t := db.(type) {
|
||||
case types.BaseVirtualDeviceFileBackingInfo:
|
||||
diskPath := t.GetVirtualDeviceFileBackingInfo().FileName
|
||||
if filter(diskPath) {
|
||||
op.Infof("Found disk matching filter: (label: %s), %q", label, diskPath)
|
||||
return diskPath, nil
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", errors.New("Not found")
|
||||
}
|
||||
|
||||
func (m *Manager) Owners(op trace.Operation, url *url.URL, filter func(vm *mo.VirtualMachine) bool) ([]*vm.VirtualMachine, error) {
|
||||
dsPath, err := datastore.PathFromString(url.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m.InUse(op, NewPersistentDisk(dsPath), filter)
|
||||
}
|
||||
739
vendor/github.com/vmware/vic/pkg/vsphere/disk/disk_manager_test.go
generated
vendored
739
vendor/github.com/vmware/vic/pkg/vsphere/disk/disk_manager_test.go
generated
vendored
@@ -1,739 +0,0 @@
|
||||
// Copyright 2016-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 disk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/view"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/guest"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/datastore"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/tasks"
|
||||
"github.com/vmware/vic/pkg/vsphere/test/env"
|
||||
)
|
||||
|
||||
func Session(ctx context.Context, t *testing.T) *session.Session {
|
||||
config := &session.Config{
|
||||
Service: env.URL(t),
|
||||
|
||||
DatastorePath: env.DS(t),
|
||||
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
}
|
||||
|
||||
s := session.NewSession(config)
|
||||
|
||||
_, err := s.Connect(ctx)
|
||||
if err != nil {
|
||||
t.Skip(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
// we're treating this as an atomic behaviour, so log out if we failed
|
||||
defer func() {
|
||||
if err != nil {
|
||||
t.Skip(err.Error())
|
||||
s.Client.Logout(ctx)
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = s.Populate(ctx)
|
||||
if err != nil {
|
||||
t.Skip(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Vsan has special UUID / URI handling of top level directories which
|
||||
// we've implemented at another level. We can't import them here or it'd
|
||||
// be a circular dependency. Also, we already have tests that test these
|
||||
// cases but from a higher level.
|
||||
if err != nil || s.IsVSAN(ctx) {
|
||||
t.Logf("error: %s", err.Error())
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func ContainerView(ctx context.Context, session *session.Session, vm *object.VirtualMachine) *view.ContainerView {
|
||||
mngr := view.NewManager(session.Vim25())
|
||||
|
||||
pool, err := vm.ResourcePool(ctx)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create view of VirtualMachine objects under the VCH's resource pool
|
||||
v, err := mngr.CreateContainerView(ctx, pool.Reference(), []string{"VirtualMachine"}, true)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Create a lineage of disks inheriting from eachother, write portion of a
|
||||
// string to each, the confirm the result is the whole string
|
||||
func TestCreateAndDetach(t *testing.T) {
|
||||
log.SetLevel(log.InfoLevel)
|
||||
if testing.Verbose() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
session := Session(context.Background(), t)
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
op := trace.NewOperation(context.TODO(), t.Name())
|
||||
|
||||
vchvm, err := guest.GetSelf(op, session)
|
||||
if err != nil {
|
||||
t.Skip("Not in a vm")
|
||||
}
|
||||
view := ContainerView(op, session, vchvm)
|
||||
if view == nil {
|
||||
t.Skip("Can't create a view")
|
||||
}
|
||||
|
||||
imagestore := &object.DatastorePath{
|
||||
Datastore: session.Datastore.Name(),
|
||||
Path: datastore.TestName(t.Name()),
|
||||
}
|
||||
|
||||
// file manager
|
||||
fm := object.NewFileManager(session.Vim25())
|
||||
// create a directory in the datastore
|
||||
err = fm.MakeDirectory(context.TODO(), imagestore.String(), nil, true)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Nuke the image store
|
||||
defer func() {
|
||||
task, err := fm.DeleteDatastoreFile(context.TODO(), imagestore.String(), nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
_, err = task.WaitForResult(context.TODO(), nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// create a diskmanager
|
||||
vdm, err := NewDiskManager(op, session, view)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, vdm) {
|
||||
return
|
||||
}
|
||||
|
||||
diskSize := int64(1 << 10)
|
||||
scratch := &object.DatastorePath{
|
||||
Datastore: session.Datastore.Name(),
|
||||
Path: path.Join(imagestore.Path, "scratch.vmdk"),
|
||||
}
|
||||
config := NewPersistentDisk(scratch).WithCapacity(diskSize)
|
||||
parent, err := vdm.Create(op, config)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
numChildren := 3
|
||||
children := make([]*VirtualDisk, numChildren)
|
||||
|
||||
testString := "Ground control to Major Tom"
|
||||
writeSize := len(testString) / numChildren
|
||||
// Create children which inherit from each other
|
||||
for i := 0; i < numChildren; i++ {
|
||||
|
||||
p := &object.DatastorePath{
|
||||
Datastore: imagestore.Datastore,
|
||||
Path: path.Join(imagestore.Path, fmt.Sprintf("child%d.vmdk", i)),
|
||||
}
|
||||
|
||||
config := NewPersistentDisk(p).WithParent(parent.DatastoreURI)
|
||||
child, cerr := vdm.CreateAndAttach(op, config)
|
||||
if !assert.NoError(t, cerr) {
|
||||
return
|
||||
}
|
||||
refs := child.attachedRefs.Count()
|
||||
assert.EqualValues(t, 1, refs, "Expected %d attach references, found %d", refs)
|
||||
|
||||
// attempt to recreate and attach the already attached disk
|
||||
config = NewPersistentDisk(p).WithParent(parent.DatastoreURI)
|
||||
child, cerr = vdm.CreateAndAttach(op, config)
|
||||
if !assert.NoError(t, cerr) {
|
||||
return
|
||||
}
|
||||
refs = child.attachedRefs.Count()
|
||||
assert.EqualValues(t, 2, refs, "Expected %d attach references, found %d", refs)
|
||||
|
||||
// attempt detach
|
||||
cerr = vdm.Detach(op, config)
|
||||
if !assert.NoError(t, cerr) {
|
||||
return
|
||||
}
|
||||
// should not actually detach, and should still have 1 reference
|
||||
refs = child.attachedRefs.Count()
|
||||
assert.EqualValues(t, 1, refs, "Expected %d attach references, found %d", refs)
|
||||
|
||||
children[i] = child
|
||||
|
||||
// Write directly to the disk
|
||||
f, cerr := os.OpenFile(child.DevicePath, os.O_RDWR, os.FileMode(0777))
|
||||
if !assert.NoError(t, cerr) {
|
||||
return
|
||||
}
|
||||
|
||||
start := i * writeSize
|
||||
end := start + writeSize
|
||||
|
||||
if i == numChildren-1 {
|
||||
// last chunk, write to the end.
|
||||
_, cerr = f.WriteAt([]byte(testString[start:]), int64(start))
|
||||
if !assert.NoError(t, cerr) || !assert.NoError(t, f.Sync()) {
|
||||
return
|
||||
}
|
||||
|
||||
// Try to read the whole string
|
||||
b := make([]byte, len(testString))
|
||||
f.Seek(0, 0)
|
||||
_, cerr = f.Read(b)
|
||||
if !assert.NoError(t, cerr) {
|
||||
return
|
||||
}
|
||||
|
||||
//check against the test string
|
||||
if !assert.Equal(t, testString, string(b)) {
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
_, cerr = f.WriteAt([]byte(testString[start:end]), int64(start))
|
||||
if !assert.NoError(t, cerr) || !assert.NoError(t, f.Sync()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
f.Close()
|
||||
|
||||
cerr = vdm.Detach(op, config)
|
||||
if !assert.NoError(t, cerr) {
|
||||
return
|
||||
}
|
||||
|
||||
// use this image as the next parent
|
||||
parent = child
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefCounting(t *testing.T) {
|
||||
log.SetLevel(log.InfoLevel)
|
||||
if testing.Verbose() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
session := Session(context.Background(), t)
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
op := trace.NewOperation(context.TODO(), t.Name())
|
||||
|
||||
vchvm, err := guest.GetSelf(op, session)
|
||||
if err != nil {
|
||||
t.Skip("Not in a vm")
|
||||
}
|
||||
view := ContainerView(op, session, vchvm)
|
||||
if view == nil {
|
||||
t.Skip("Can't create a view")
|
||||
}
|
||||
|
||||
imagestore := &object.DatastorePath{
|
||||
Datastore: session.Datastore.Name(),
|
||||
Path: datastore.TestName(t.Name()),
|
||||
}
|
||||
|
||||
// file manager
|
||||
fm := object.NewFileManager(session.Vim25())
|
||||
// create a directory in the datastore
|
||||
err = fm.MakeDirectory(context.TODO(), imagestore.String(), nil, true)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Nuke the image store
|
||||
defer func() {
|
||||
task, err := fm.DeleteDatastoreFile(context.TODO(), imagestore.String(), nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
_, err = task.WaitForResult(context.TODO(), nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// create a diskmanager
|
||||
vdm, err := NewDiskManager(op, session, view)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, vdm) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, vdm) {
|
||||
return
|
||||
}
|
||||
|
||||
diskSize := int64(1 << 10)
|
||||
scratch := &object.DatastorePath{
|
||||
Datastore: session.Datastore.Name(),
|
||||
Path: path.Join(imagestore.Path, "scratch.vmdk"),
|
||||
}
|
||||
config := NewPersistentDisk(scratch).WithCapacity(diskSize)
|
||||
p, err := vdm.Create(op, config)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.False(t, p.Attached(), "%s is attached but should not be", p.DatastoreURI)
|
||||
|
||||
child := &object.DatastorePath{
|
||||
Datastore: imagestore.Datastore,
|
||||
Path: path.Join(imagestore.Path, "testDisk.vmdk"),
|
||||
}
|
||||
config = NewPersistentDisk(child).WithParent(scratch)
|
||||
|
||||
// attempt attach
|
||||
assert.NoError(t, vdm.attach(op, config), "Error attempting to attach %s", config)
|
||||
|
||||
devicePath, err := vdm.devicePathByURI(op, child, config.IsPersistent())
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
d, err := NewVirtualDisk(op, config, vdm.Disks)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
blockDev, err := waitForDevice(op, devicePath)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.False(t, d.Attached(), "%s is attached but should not be", d.DatastoreURI)
|
||||
|
||||
// Attach the disk
|
||||
assert.NoError(t, d.setAttached(op, blockDev), "Error attempting to mark %s as attached", d.DatastoreURI)
|
||||
|
||||
assert.True(t, d.Attached(), "%s is not attached but should be", d.DatastoreURI)
|
||||
assert.NoError(t, d.canBeDetached(), "%s should be detachable but is not", d.DatastoreURI)
|
||||
assert.False(t, d.InUseByOther(), "%s is in use but should not be", d.DatastoreURI)
|
||||
assert.Equal(t, 1, d.attachedRefs.Count(), "%s has %d attach references but should have 1", d.DatastoreURI, d.attachedRefs.Count())
|
||||
|
||||
// attempt another attach at disk level to increase reference count
|
||||
// TODO(jzt): This should probably eventually use the attach code coming in
|
||||
// https://github.com/vmware/vic/issues/5422
|
||||
assert.NoError(t, d.setAttached(op, blockDev), "Error attempting to mark %s as attached", d.DatastoreURI)
|
||||
|
||||
assert.True(t, d.Attached(), "%s is not attached but should be", d.DatastoreURI)
|
||||
assert.Error(t, d.canBeDetached(), "%s should not be detachable but is", d.DatastoreURI)
|
||||
assert.True(t, d.InUseByOther(), "%s is not in use but should be", d.DatastoreURI)
|
||||
assert.Equal(t, 2, d.attachedRefs.Count(), "%s has %d attach references but should have 2", d.DatastoreURI, d.attachedRefs.Count())
|
||||
|
||||
// reduce reference count by calling detach
|
||||
d.setDetached(op, vdm.Disks)
|
||||
|
||||
assert.True(t, d.Attached(), "%s is not attached but should be", d.DatastoreURI)
|
||||
assert.NoError(t, d.canBeDetached(), "%s should be detachable but is not", d.DatastoreURI)
|
||||
assert.False(t, d.InUseByOther(), "%s is in use but should not be", d.DatastoreURI)
|
||||
assert.Equal(t, 1, d.attachedRefs.Count(), "%s has %d attach references but should have 1", d.DatastoreURI, d.attachedRefs.Count())
|
||||
|
||||
// test mount reference counting
|
||||
assert.NoError(t, d.Mkfs(op, "testDisk"), "Error attempting to format %s", d.DatastoreURI)
|
||||
|
||||
// create temp mount path
|
||||
dir, err := ioutil.TempDir("", "mnt")
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// cleanup
|
||||
defer func() {
|
||||
assert.NoError(t, os.RemoveAll(dir), "Error cleaning up mount path %s", dir)
|
||||
}()
|
||||
|
||||
// initial mount
|
||||
dir, err = d.Mount(op, nil)
|
||||
assert.NoError(t, err, "Error attempting to mount %s at %s", d.DatastoreURI, dir)
|
||||
|
||||
mountPath, err := d.MountPath()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.True(t, d.Mounted(), "%s is not mounted but should be", d.DatastoreURI)
|
||||
assert.Error(t, d.canBeDetached(), "%s should not be detachable but is", d.DatastoreURI)
|
||||
assert.False(t, d.InUseByOther(), "%s is in use but should not be", d.DatastoreURI)
|
||||
assert.Equal(t, 1, d.mountedRefs.Count(), "%s has %d mount references but should have 1", d.DatastoreURI, d.mountedRefs.Count())
|
||||
assert.Equal(t, dir, mountPath, "%s is mounted at %s but should be mounted at %s", d.DatastoreURI, mountPath, dir)
|
||||
|
||||
// attempt another mount
|
||||
dir, err = d.Mount(op, nil)
|
||||
assert.NoError(t, err, "Error attempting to mount %s at %s", d.DatastoreURI, dir)
|
||||
|
||||
assert.True(t, d.Mounted(), "%s is not mounted but should be", d.DatastoreURI)
|
||||
assert.Error(t, d.canBeDetached(), "%s should not be detachable but is", d.DatastoreURI)
|
||||
assert.True(t, d.InUseByOther(), "%s is not in use but should be", d.DatastoreURI)
|
||||
assert.Equal(t, 2, d.mountedRefs.Count(), "%s has %d mount references but should have 2", d.DatastoreURI, d.mountedRefs.Count())
|
||||
|
||||
// attempt unmount
|
||||
assert.NoError(t, d.Unmount(op), "Error attempting to unmount %s", d.DatastoreURI)
|
||||
|
||||
assert.True(t, d.Mounted(), "%s is not mounted but should be", d.DatastoreURI)
|
||||
assert.Error(t, d.canBeDetached(), "%s should not be detachable but is", d.DatastoreURI)
|
||||
assert.False(t, d.InUseByOther(), "%s is in use but should not be", d.DatastoreURI)
|
||||
assert.Equal(t, 1, d.mountedRefs.Count(), "%s has %d mount references but should have 1", d.DatastoreURI, d.mountedRefs.Count())
|
||||
|
||||
// actually unmount
|
||||
assert.NoError(t, d.Unmount(op), "Error attempting to unmount %s", d.DatastoreURI)
|
||||
|
||||
assert.False(t, d.Mounted(), "%s is mounted but should not be", d.DatastoreURI)
|
||||
assert.NoError(t, d.canBeDetached(), "%s should be detachable but is not", d.DatastoreURI)
|
||||
assert.False(t, d.InUseByOther(), "%s is in use but should not be", d.DatastoreURI)
|
||||
assert.Equal(t, 0, d.mountedRefs.Count(), "%s has %d mount references but should have 0", d.DatastoreURI, d.mountedRefs.Count())
|
||||
|
||||
// detach
|
||||
assert.NoError(t, vdm.Detach(op, config), "Error attempting to detach %s", d.DatastoreURI)
|
||||
|
||||
assert.False(t, d.Attached(), "%s is attached but should not be", d.DatastoreURI)
|
||||
assert.False(t, d.Mounted(), "%s is mounted but should not be", d.DatastoreURI)
|
||||
assert.Error(t, d.canBeDetached(), "%s should not be detachable but is", d.DatastoreURI)
|
||||
assert.False(t, d.InUseByOther(), "%s is in use but should not be", d.DatastoreURI)
|
||||
assert.Equal(t, 0, d.attachedRefs.Count(), "%s has %d attach references but should have 0", d.DatastoreURI, d.attachedRefs.Count())
|
||||
assert.Equal(t, 0, d.mountedRefs.Count(), "%s has %d mount references but should have 0", d.DatastoreURI, d.mountedRefs.Count())
|
||||
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefCountingParallel(t *testing.T) {
|
||||
log.SetLevel(log.InfoLevel)
|
||||
if testing.Verbose() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
session := Session(context.Background(), t)
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
op := trace.NewOperation(context.TODO(), t.Name())
|
||||
|
||||
vchvm, err := guest.GetSelf(op, session)
|
||||
if err != nil {
|
||||
t.Skip("Not in a vm")
|
||||
}
|
||||
view := ContainerView(op, session, vchvm)
|
||||
if view == nil {
|
||||
t.Skip("Can't create a view")
|
||||
}
|
||||
|
||||
imagestore := &object.DatastorePath{
|
||||
Datastore: session.Datastore.Name(),
|
||||
Path: datastore.TestName(t.Name()),
|
||||
}
|
||||
|
||||
// file manager
|
||||
fm := object.NewFileManager(session.Vim25())
|
||||
// create a directory in the datastore
|
||||
err = fm.MakeDirectory(context.TODO(), imagestore.String(), nil, true)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Nuke the image store
|
||||
defer func() {
|
||||
task, err := fm.DeleteDatastoreFile(context.TODO(), imagestore.String(), nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
_, err = task.WaitForResult(context.TODO(), nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// create a diskmanager
|
||||
vdm, err := NewDiskManager(op, session, view)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, vdm) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, vdm) {
|
||||
return
|
||||
}
|
||||
|
||||
diskSize := int64(1 << 10)
|
||||
uri := &object.DatastorePath{
|
||||
Datastore: session.Datastore.Name(),
|
||||
Path: path.Join(imagestore.Path, "test.vmdk"),
|
||||
}
|
||||
config := NewPersistentDisk(uri).WithCapacity(diskSize)
|
||||
d, err := vdm.CreateAndAttach(op, config)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.True(t, d.Attached(), "%s is not attached but should be", d.DatastoreURI)
|
||||
assert.NoError(t, d.canBeDetached(), "%s should be detachable but is not", d.DatastoreURI)
|
||||
assert.False(t, d.InUseByOther(), "%s is in use but should not be", d.DatastoreURI)
|
||||
assert.EqualValues(t, 1, d.attachedRefs.Count(), "%s has %d attach references but should have 1", d.DatastoreURI, d.attachedRefs.Count())
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
for i := 0; i < 5; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
var err error
|
||||
defer wg.Done()
|
||||
|
||||
for j := 0; j < 5; j++ {
|
||||
|
||||
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
|
||||
|
||||
d, err = vdm.CreateAndAttach(op, config)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
|
||||
|
||||
err = vdm.Detach(op, config)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
assert.True(t, d.Attached(), "%s is not attached but should be", d.DatastoreURI)
|
||||
assert.NoError(t, d.canBeDetached(), "%s should be detachable but is not", d.DatastoreURI)
|
||||
assert.False(t, d.InUseByOther(), "%s is in use but should not be", d.DatastoreURI)
|
||||
assert.EqualValues(t, 1, d.attachedRefs.Count(), "%s has %d attach references but should have 1", d.DatastoreURI, d.attachedRefs.Count())
|
||||
|
||||
err = vdm.Detach(op, config)
|
||||
if !assert.NoError(t, err) {
|
||||
log.Error("Error detaching disk: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
assert.False(t, d.Attached(), "%s is attached but should not be", d.DatastoreURI)
|
||||
assert.Error(t, d.canBeDetached(), "%s should be detachable but is not", d.DatastoreURI)
|
||||
assert.False(t, d.InUseByOther(), "%s is in use but should not be", d.DatastoreURI)
|
||||
assert.EqualValues(t, 0, d.attachedRefs.Count(), "%s has %d attach references but should have 0", d.DatastoreURI, d.attachedRefs.Count())
|
||||
}
|
||||
|
||||
func TestInUse(t *testing.T) {
|
||||
log.SetLevel(log.InfoLevel)
|
||||
if testing.Verbose() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
session := Session(context.Background(), t)
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
op := trace.NewOperation(context.TODO(), t.Name())
|
||||
|
||||
vchvm, err := guest.GetSelf(op, session)
|
||||
if err != nil {
|
||||
t.Skip("Not in a vm")
|
||||
}
|
||||
view := ContainerView(op, session, vchvm)
|
||||
if view == nil {
|
||||
t.Skip("Can't create a view")
|
||||
}
|
||||
|
||||
imagestore := &object.DatastorePath{
|
||||
Datastore: session.Datastore.Name(),
|
||||
Path: datastore.TestName(t.Name()),
|
||||
}
|
||||
|
||||
// file manager
|
||||
fm := object.NewFileManager(session.Vim25())
|
||||
// create a directory in the datastore
|
||||
err = fm.MakeDirectory(context.TODO(), imagestore.String(), nil, true)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Nuke the image store
|
||||
defer func() {
|
||||
task, err := fm.DeleteDatastoreFile(context.TODO(), imagestore.String(), nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
_, err = task.WaitForResult(context.TODO(), nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// create a diskmanager
|
||||
vdm, err := NewDiskManager(op, session, view)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, vdm) {
|
||||
return
|
||||
}
|
||||
|
||||
// helper fn
|
||||
reconfigure := func(changes []types.BaseVirtualDeviceConfigSpec) error {
|
||||
t.Logf("Calling reconfigure")
|
||||
|
||||
machineSpec := types.VirtualMachineConfigSpec{}
|
||||
machineSpec.DeviceChange = changes
|
||||
|
||||
_, err := vdm.vm.WaitForResult(op, func(ctx context.Context) (tasks.Task, error) {
|
||||
t, er := vdm.vm.Reconfigure(ctx, machineSpec)
|
||||
|
||||
if t != nil {
|
||||
op.Debugf("reconfigure task=%s", t.Reference())
|
||||
}
|
||||
|
||||
return t, er
|
||||
})
|
||||
return err
|
||||
}
|
||||
// 1MB
|
||||
diskSize := int64(1 << 10)
|
||||
scratch := &object.DatastorePath{
|
||||
Datastore: session.Datastore.Name(),
|
||||
Path: path.Join(imagestore.Path, "scratch.vmdk"),
|
||||
}
|
||||
// config
|
||||
config := NewPersistentDisk(scratch).WithCapacity(diskSize)
|
||||
|
||||
// attach + create spec (scratch)
|
||||
spec := []types.BaseVirtualDeviceConfigSpec{
|
||||
&types.VirtualDeviceConfigSpec{
|
||||
Device: vdm.toSpec(config),
|
||||
Operation: types.VirtualDeviceConfigSpecOperationAdd,
|
||||
FileOperation: types.VirtualDeviceConfigSpecFileOperationCreate,
|
||||
},
|
||||
}
|
||||
|
||||
// filter powered off vms
|
||||
filter := func(vm *mo.VirtualMachine) bool {
|
||||
return vm.Runtime.PowerState != types.VirtualMachinePowerStatePoweredOn
|
||||
}
|
||||
|
||||
vms, err := vdm.InUse(op, config, filter)
|
||||
if !assert.NoError(t, err) && !assert.Len(t, vms, 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// reconfigure
|
||||
err = reconfigure(spec)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
t.Logf("scratch created and attached")
|
||||
|
||||
vms, err = vdm.InUse(op, config, filter)
|
||||
if !assert.NoError(t, err) && !assert.Len(t, vms, 1) {
|
||||
return
|
||||
}
|
||||
t.Logf("InUse by %s", vms)
|
||||
|
||||
// ref to scratch (needed for detach as initial spec's Key and UnitNumber was unset)
|
||||
disk, err := findDiskByFilename(op, vdm.vm, scratch.Path, true)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// DO NOT DETACH AND START WORKING ON THE CHILD
|
||||
|
||||
// child
|
||||
child := &object.DatastorePath{
|
||||
Datastore: session.Datastore.Name(),
|
||||
Path: path.Join(imagestore.Path, "child.vmdk"),
|
||||
}
|
||||
// config
|
||||
config = NewPersistentDisk(child).WithParent(scratch)
|
||||
|
||||
// detach (scratch) AND attach + create (child) spec
|
||||
spec = []types.BaseVirtualDeviceConfigSpec{
|
||||
&types.VirtualDeviceConfigSpec{
|
||||
Device: disk,
|
||||
Operation: types.VirtualDeviceConfigSpecOperationRemove,
|
||||
},
|
||||
&types.VirtualDeviceConfigSpec{
|
||||
Device: vdm.toSpec(config),
|
||||
Operation: types.VirtualDeviceConfigSpecOperationAdd,
|
||||
FileOperation: types.VirtualDeviceConfigSpecFileOperationCreate,
|
||||
},
|
||||
}
|
||||
|
||||
// reconfigure
|
||||
err = reconfigure(spec)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
t.Logf("scratch detached, child created and attached")
|
||||
|
||||
vms, err = vdm.InUse(op, config, filter)
|
||||
if !assert.NoError(t, err) && !assert.Len(t, vms, 1) {
|
||||
return
|
||||
}
|
||||
t.Logf("InUse by %s", vms)
|
||||
|
||||
// ref to child (needed for detach as initial spec's Key and UnitNumber was unset)
|
||||
disk, err = findDiskByFilename(op, vdm.vm, child.Path, true)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// detach spec (child)
|
||||
spec = []types.BaseVirtualDeviceConfigSpec{
|
||||
&types.VirtualDeviceConfigSpec{
|
||||
Device: disk,
|
||||
Operation: types.VirtualDeviceConfigSpecOperationRemove,
|
||||
},
|
||||
}
|
||||
// reconfigure
|
||||
err = reconfigure(spec)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
t.Logf("child detached")
|
||||
}
|
||||
238
vendor/github.com/vmware/vic/pkg/vsphere/disk/lazy_detach_test.go
generated
vendored
238
vendor/github.com/vmware/vic/pkg/vsphere/disk/lazy_detach_test.go
generated
vendored
@@ -1,238 +0,0 @@
|
||||
// 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 disk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/guest"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/datastore"
|
||||
"github.com/vmware/vic/pkg/vsphere/tasks"
|
||||
)
|
||||
|
||||
// TestLazyDetach tests lazy detach functionality to make sure that every ESXi version shows this behaviour
|
||||
// https://github.com/vmware/vic/issues/5565
|
||||
func TestLazyDetach(t *testing.T) {
|
||||
log.SetLevel(log.InfoLevel)
|
||||
if testing.Verbose() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
session := Session(context.Background(), t)
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
|
||||
op := trace.NewOperation(context.TODO(), t.Name())
|
||||
|
||||
vchvm, err := guest.GetSelf(op, session)
|
||||
if err != nil {
|
||||
t.Skip("Not in a vm")
|
||||
}
|
||||
view := ContainerView(op, session, vchvm)
|
||||
if view == nil {
|
||||
t.Skip("Can't create a view")
|
||||
}
|
||||
|
||||
imagestore := &object.DatastorePath{
|
||||
Datastore: session.Datastore.Name(),
|
||||
Path: datastore.TestName(t.Name()),
|
||||
}
|
||||
|
||||
// file manager
|
||||
fm := object.NewFileManager(session.Vim25())
|
||||
// create a directory in the datastore
|
||||
err = fm.MakeDirectory(context.TODO(), imagestore.String(), nil, true)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// Nuke the image store
|
||||
defer func() {
|
||||
task, err := fm.DeleteDatastoreFile(context.TODO(), imagestore.String(), nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
_, err = task.WaitForResult(context.TODO(), nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// create a diskmanager
|
||||
vdm, err := NewDiskManager(op, session, view)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, vdm) {
|
||||
return
|
||||
}
|
||||
|
||||
// helper fn
|
||||
reconfigure := func(changes []types.BaseVirtualDeviceConfigSpec) error {
|
||||
t.Logf("Calling reconfigure")
|
||||
|
||||
machineSpec := types.VirtualMachineConfigSpec{}
|
||||
machineSpec.DeviceChange = changes
|
||||
|
||||
_, err := vdm.vm.WaitForResult(op, func(ctx context.Context) (tasks.Task, error) {
|
||||
t, er := vdm.vm.Reconfigure(ctx, machineSpec)
|
||||
|
||||
if t != nil {
|
||||
op.Debugf("reconfigure task=%s", t.Reference())
|
||||
}
|
||||
|
||||
return t, er
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
oddity := "Ground control to Major Tom"
|
||||
operation := func(path *object.DatastorePath, read bool) error {
|
||||
// this is fundamentally checking persistent disks
|
||||
devicePath, err := vdm.devicePathByURI(op, path, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
blockDev, err := waitForDevice(op, devicePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(blockDev, os.O_RDWR, os.FileMode(0777))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if read {
|
||||
// Try to read the whole string
|
||||
b := make([]byte, len(oddity))
|
||||
_, err = f.Read(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Check against the test string
|
||||
if oddity != string(b) {
|
||||
return fmt.Errorf("Read string is not the same one we wrote")
|
||||
}
|
||||
} else {
|
||||
// Write directly to the disk
|
||||
_, err = f.Write([]byte(oddity))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return f.Sync()
|
||||
}
|
||||
|
||||
// 1MB
|
||||
diskSize := int64(1 << 10)
|
||||
scratch := &object.DatastorePath{
|
||||
Datastore: session.Datastore.Name(),
|
||||
Path: path.Join(imagestore.Path, "scratch.vmdk"),
|
||||
}
|
||||
// config
|
||||
config := NewPersistentDisk(scratch).WithCapacity(diskSize)
|
||||
|
||||
// attach + create spec (scratch)
|
||||
spec := []types.BaseVirtualDeviceConfigSpec{
|
||||
&types.VirtualDeviceConfigSpec{
|
||||
Device: vdm.toSpec(config),
|
||||
Operation: types.VirtualDeviceConfigSpecOperationAdd,
|
||||
FileOperation: types.VirtualDeviceConfigSpecFileOperationCreate,
|
||||
},
|
||||
}
|
||||
// reconfigure
|
||||
err = reconfigure(spec)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
t.Logf("scratch created and attached")
|
||||
|
||||
// ref to scratch (needed for detach as initial spec's Key and UnitNumber was unset)
|
||||
disk, err := findDiskByFilename(op, vdm.vm, scratch.Path, true)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
err = operation(scratch, false)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// DO NOT DETACH AND START WORKING ON THE CHILD
|
||||
|
||||
// child
|
||||
child := &object.DatastorePath{
|
||||
Datastore: session.Datastore.Name(),
|
||||
Path: path.Join(imagestore.Path, "child.vmdk"),
|
||||
}
|
||||
// config
|
||||
config = NewPersistentDisk(child).WithParent(scratch)
|
||||
|
||||
// detach (scratch) AND attach + create (child) spec
|
||||
spec = []types.BaseVirtualDeviceConfigSpec{
|
||||
&types.VirtualDeviceConfigSpec{
|
||||
Device: disk,
|
||||
Operation: types.VirtualDeviceConfigSpecOperationRemove,
|
||||
},
|
||||
&types.VirtualDeviceConfigSpec{
|
||||
Device: vdm.toSpec(config),
|
||||
Operation: types.VirtualDeviceConfigSpecOperationAdd,
|
||||
FileOperation: types.VirtualDeviceConfigSpecFileOperationCreate,
|
||||
},
|
||||
}
|
||||
// reconfigure
|
||||
err = reconfigure(spec)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
t.Logf("scratch detached, child created and attached")
|
||||
|
||||
err = operation(child, true)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// ref to child (needed for detach as initial spec's Key and UnitNumber was unset)
|
||||
disk, err = findDiskByFilename(op, vdm.vm, child.Path, true)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// detach spec (child)
|
||||
spec = []types.BaseVirtualDeviceConfigSpec{
|
||||
&types.VirtualDeviceConfigSpec{
|
||||
Device: disk,
|
||||
Operation: types.VirtualDeviceConfigSpecOperationRemove,
|
||||
},
|
||||
}
|
||||
// reconfigure
|
||||
err = reconfigure(spec)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
t.Logf("child detached")
|
||||
}
|
||||
339
vendor/github.com/vmware/vic/pkg/vsphere/disk/util.go
generated
vendored
339
vendor/github.com/vmware/vic/pkg/vsphere/disk/util.go
generated
vendored
@@ -1,339 +0,0 @@
|
||||
// Copyright 2016-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 disk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
const (
|
||||
// The duration waitForPath will tolerate before timing out.
|
||||
// TODO FIXME see GH issues 2340 and 2385
|
||||
// TODO We need to add a vSphere cancellation step to cancel calls that are taking too long
|
||||
// TODO Remove these TODOs after 2385 is completed
|
||||
pathTimeout = 60 * time.Second
|
||||
)
|
||||
|
||||
// scsiScan tells the kernel to rescan the scsi bus.
|
||||
func scsiScan() error {
|
||||
root := "/sys/class/scsi_host"
|
||||
|
||||
dirs, err := ioutil.ReadDir(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, dir := range dirs {
|
||||
file := path.Join(root, dir.Name(), "scan")
|
||||
// Channel, SCSI target ID, and LUN: "-" == rescan all
|
||||
err = ioutil.WriteFile(file, []byte("- - -"), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Waits for a device to appear in the given directory and returns the
|
||||
// resultant dev path (e.g /dev/sda). For instance, if sysPath is
|
||||
// /sys/bus/pci/devices/0000:03:00.0/host0/subsystem/devices/0:0:0:0/block, the
|
||||
// directory to appear in block will be mapped to /dev/<device>. waitForDevice
|
||||
// will wait for the entry to appear as a scsi target AND the blockdev to exist
|
||||
// in /dev/, returning the path to /dev/<blockdev>.
|
||||
func waitForDevice(op trace.Operation, sysPath string) (string, error) {
|
||||
defer trace.End(trace.Begin(sysPath))
|
||||
|
||||
var err error
|
||||
op, _ = trace.WithTimeout(&op, time.Duration(pathTimeout), "waitForDevice(%s)", sysPath)
|
||||
errCh := make(chan error)
|
||||
|
||||
var blockDev string
|
||||
go func() {
|
||||
t := time.NewTicker(200 * time.Microsecond)
|
||||
defer t.Stop()
|
||||
defer close(errCh)
|
||||
|
||||
for range t.C {
|
||||
// We've timed out.
|
||||
if op.Err() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Syspath includes the scsi target itself. Wait for it and try
|
||||
// again before trying to identify the device node it maps to.
|
||||
dirents, err := ioutil.ReadDir(sysPath)
|
||||
if err != nil {
|
||||
|
||||
// try again
|
||||
if os.IsNotExist(err) {
|
||||
op.Debugf("Expected %s to appear. Trying again.", sysPath)
|
||||
continue
|
||||
}
|
||||
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
|
||||
if len(dirents) > 1 {
|
||||
errCh <- fmt.Errorf("too many devices returned: %#v", dirents)
|
||||
return
|
||||
}
|
||||
|
||||
if len(dirents) == 1 {
|
||||
blockDev = "/dev/" + dirents[0].Name()
|
||||
|
||||
// check it exists
|
||||
if _, err := os.Stat(blockDev); err != nil {
|
||||
|
||||
// try again
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
|
||||
// happy path
|
||||
return
|
||||
}
|
||||
|
||||
// run a manual scan of the scsi bus
|
||||
if serr := scsiScan(); serr != nil {
|
||||
op.Warnf("scsi scan: %s", serr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
op.Debugf("Waiting for attached disk to appear in %s, or timeout", sysPath)
|
||||
select {
|
||||
case err = <-errCh:
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
op.Infof("Attached disk present at %s", blockDev)
|
||||
case <-op.Done():
|
||||
if op.Err() != nil {
|
||||
return "", errors.Errorf("timeout waiting for layer to present in %s", sysPath)
|
||||
}
|
||||
}
|
||||
|
||||
return blockDev, nil
|
||||
}
|
||||
|
||||
// Ensures that a paravirtual scsi controller is present and determines the
|
||||
// base path of disks attached to it returns a handle to the controller and a
|
||||
// format string, with a single decimal for the disk unit number which will
|
||||
// result in the /sys/bus/pci/devices/{pci id like
|
||||
// 0000:03:00.0}/host{N provided by kernel}/subsystem/devices/N:0:{disk id like 0}:0/block/sd{Y provided by kernel} path.
|
||||
// The directory inside block isn't a devnode, but it's name can be mapped to
|
||||
// its /dev/ path.
|
||||
func verifyParavirtualScsiController(op trace.Operation, vm *vm.VirtualMachine) (*types.ParaVirtualSCSIController, string, error) {
|
||||
devices, err := vm.Device(op)
|
||||
if err != nil {
|
||||
op.Errorf("vmware driver failed to retrieve device list for VM %s: %s", vm, errors.ErrorStack(err))
|
||||
return nil, "", errors.Trace(err)
|
||||
}
|
||||
|
||||
controller, ok := devices.PickController((*types.ParaVirtualSCSIController)(nil)).(*types.ParaVirtualSCSIController)
|
||||
if controller == nil || !ok {
|
||||
err = errors.Errorf("vmware driver failed to find a paravirtual SCSI controller - ensure setup ran correctly")
|
||||
op.Errorf(err.Error())
|
||||
return nil, "", errors.Trace(err)
|
||||
}
|
||||
|
||||
// build the base path
|
||||
// first we determine which label we're looking for (requires VMW hardware version >=10)
|
||||
controllerLabel := fmt.Sprintf("SCSI%d", controller.BusNumber)
|
||||
op.Debugf("Looking for scsi controller with label %s", controllerLabel)
|
||||
|
||||
pciDevicesDir := "/sys/bus/pci/devices"
|
||||
pciBus, err := os.Open(pciDevicesDir)
|
||||
if err != nil {
|
||||
op.Errorf("Failed to open %s for reading: %s", pciDevicesDir, errors.ErrorStack(err))
|
||||
return controller, "", errors.Trace(err)
|
||||
}
|
||||
defer pciBus.Close()
|
||||
|
||||
pciDevices, err := pciBus.Readdirnames(0)
|
||||
if err != nil {
|
||||
op.Errorf("Failed to read contents of %s: %s", pciDevicesDir, errors.ErrorStack(err))
|
||||
return controller, "", errors.Trace(err)
|
||||
}
|
||||
|
||||
var controllerName string
|
||||
|
||||
for _, pciDev := range pciDevices {
|
||||
labelPath := path.Join(pciDevicesDir, pciDev, "label")
|
||||
flabel, err := os.Open(labelPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
op.Errorf("Unable to read label from %s: %s", labelPath, errors.ErrorStack(err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
defer flabel.Close()
|
||||
|
||||
buf := make([]byte, len(controllerLabel))
|
||||
_, err = flabel.Read(buf)
|
||||
if err != nil {
|
||||
op.Errorf("Unable to read label from %s: %s", labelPath, errors.ErrorStack(err))
|
||||
continue
|
||||
}
|
||||
|
||||
if controllerLabel == string(buf) {
|
||||
// we've found our controller
|
||||
controllerName = pciDev
|
||||
op.Debugf("Found pvscsi controller directory: %s", controllerName)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if controllerName == "" {
|
||||
err := errors.Errorf("Failed to locate pvscsi controller directory")
|
||||
return controller, "", errors.Trace(err)
|
||||
}
|
||||
|
||||
// Use the block subsystem directly.
|
||||
// /sys/bus/pci/devices/0000:03:00.0/host0/subsystem/devices/0:0:0:0/block
|
||||
// hostN (host0 in this case) is provided to us by the kernel.
|
||||
// N:0:0:X where N is from above X is provided by vsphere
|
||||
|
||||
// Glob for the scsi host
|
||||
matches, err := filepath.Glob(path.Join(pciDevicesDir, controllerName, "host*"))
|
||||
if err != nil {
|
||||
return controller, "", fmt.Errorf("scsi host glob failed: %s", err.Error())
|
||||
}
|
||||
|
||||
if len(matches) != 1 {
|
||||
return controller, "", fmt.Errorf("too many scsi hosts")
|
||||
}
|
||||
|
||||
// Get the number on the end
|
||||
hostStr := matches[0]
|
||||
hostidx := string(hostStr[len(hostStr)-1])
|
||||
|
||||
// First param in the X:X:X:X path is the host id. So `host2` will mean all devices will start with 2.
|
||||
formatString := path.Join(hostStr, fmt.Sprintf("subsystem/devices/%s:0:%%d:0/block/", hostidx))
|
||||
op.Debugf("Disk location format: %s", formatString)
|
||||
return controller, formatString, nil
|
||||
}
|
||||
|
||||
func findDisk(op trace.Operation, vm *vm.VirtualMachine, filter func(diskName string, mode string) bool) ([]*types.VirtualDisk, error) {
|
||||
defer trace.End(trace.Begin(vm.String()))
|
||||
|
||||
devices, err := vm.Device(op)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to refresh devices for vm: %s", errors.ErrorStack(err))
|
||||
}
|
||||
|
||||
candidates := devices.Select(func(device types.BaseVirtualDevice) bool {
|
||||
db := device.GetVirtualDevice().Backing
|
||||
if db == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
backing, ok := device.GetVirtualDevice().Backing.(*types.VirtualDiskFlatVer2BackingInfo)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
backingFileName := backing.VirtualDeviceFileBackingInfo.FileName
|
||||
mode := backing.DiskMode
|
||||
op.Debugf("backing file name %s, mode: %s", backingFileName, mode)
|
||||
|
||||
return filter(backingFileName, mode)
|
||||
})
|
||||
|
||||
if len(candidates) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
disks := make([]*types.VirtualDisk, len(candidates))
|
||||
for idx, disk := range candidates {
|
||||
disks[idx] = disk.(*types.VirtualDisk)
|
||||
}
|
||||
|
||||
return disks, nil
|
||||
}
|
||||
|
||||
// Find the disk by name attached to the given vm.
|
||||
func findDiskByFilename(op trace.Operation, vm *vm.VirtualMachine, name string, persistent bool) (*types.VirtualDisk, error) {
|
||||
defer trace.End(trace.Begin(vm.String()))
|
||||
|
||||
op.Debugf("Looking for attached disk matching filename %s", name)
|
||||
|
||||
candidates, err := findDisk(op, vm, func(diskName string, mode string) bool {
|
||||
if persistent != (mode == string(types.VirtualDiskModePersistent) || mode == string(types.VirtualDiskModeIndependent_persistent)) {
|
||||
return false
|
||||
}
|
||||
|
||||
match := strings.HasSuffix(diskName, name)
|
||||
if match {
|
||||
op.Debugf("Found candidate disk for %s at %s", name, diskName)
|
||||
}
|
||||
return match
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
op.Errorf("error finding disk: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(candidates) == 0 {
|
||||
op.Infof("No disks match name and persistence: %s, %t", name, persistent)
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
if len(candidates) > 1 {
|
||||
op.Errorf("Multiple disks match name: %s", name)
|
||||
// returning the first allows doing something with it
|
||||
return candidates[0], errors.Errorf("multiple disks match name: %s", name)
|
||||
}
|
||||
|
||||
return candidates[0], nil
|
||||
}
|
||||
|
||||
func findAllDisks(op trace.Operation, vm *vm.VirtualMachine) ([]*types.VirtualDisk, error) {
|
||||
defer trace.End(trace.Begin(vm.String()))
|
||||
|
||||
op.Debugf("Looking for all attached disks")
|
||||
|
||||
disks, err := findDisk(op, vm, func(diskName string, mode string) bool {
|
||||
return true
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
op.Errorf("error finding disk: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return disks, nil
|
||||
}
|
||||
33
vendor/github.com/vmware/vic/pkg/vsphere/disk/util_test.go
generated
vendored
33
vendor/github.com/vmware/vic/pkg/vsphere/disk/util_test.go
generated
vendored
@@ -1,33 +0,0 @@
|
||||
// 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 disk
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestScsiScan(t *testing.T) {
|
||||
if runtime.GOOS != "linux" {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
err := scsiScan()
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
// ignoring "permission denied" or "read-only file system" errors
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
125
vendor/github.com/vmware/vic/pkg/vsphere/disk/vmdk.go
generated
vendored
125
vendor/github.com/vmware/vic/pkg/vsphere/disk/vmdk.go
generated
vendored
@@ -1,125 +0,0 @@
|
||||
// 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 disk
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/vmware/govmomi/task"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/datastore"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
// Vmdk is intended to be embedded by stores that manage VMDK-based data resources
|
||||
type Vmdk struct {
|
||||
*Manager
|
||||
*datastore.Helper
|
||||
*session.Session
|
||||
}
|
||||
|
||||
const (
|
||||
DiskBackendKey = "msg.disk.noBackEnd"
|
||||
LockedFileKey = "msg.fileio.lock"
|
||||
)
|
||||
|
||||
// Mount mounts the disk, returning the mount path and the function used to unmount/detaches
|
||||
// when no longer in use
|
||||
func (v *Vmdk) Mount(op trace.Operation, uri *url.URL, persistent bool) (string, func(), error) {
|
||||
if uri.Scheme != "ds" {
|
||||
return "", nil, errors.New("vmdk path must be a datastore url with \"ds\" scheme")
|
||||
}
|
||||
|
||||
dsPath, err := datastore.PathFromString(uri.Path)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
cleanFunc := func() {
|
||||
if err := v.UnmountAndDetach(op, dsPath, persistent); err != nil {
|
||||
op.Errorf("Error cleaning up disk: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
mountPath, err := v.AttachAndMount(op, dsPath, persistent)
|
||||
return mountPath, cleanFunc, err
|
||||
}
|
||||
|
||||
func LockedVMDKFilter(vm *mo.VirtualMachine) bool {
|
||||
if vm == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return vm.Runtime.PowerState == types.VirtualMachinePowerStatePoweredOn
|
||||
}
|
||||
|
||||
// IsLockedError will determine if the error received is:
|
||||
// a. related to a vmdk
|
||||
// b. due to the vmdk being locked
|
||||
// It will return false in absence of confirmation, meaning incomplete vim errors
|
||||
// will return false
|
||||
func IsLockedError(err error) bool {
|
||||
disks := LockedDisks(err)
|
||||
//if device is locked, disks will not be nil
|
||||
return len(disks) > 0
|
||||
}
|
||||
|
||||
// LockedDisks returns locked devices path in the error if it's device lock error
|
||||
func LockedDisks(err error) []string {
|
||||
var faultMessage []types.LocalizableMessage
|
||||
|
||||
if soap.IsSoapFault(err) {
|
||||
switch f := soap.ToSoapFault(err).VimFault().(type) {
|
||||
case *types.GenericVmConfigFault:
|
||||
faultMessage = f.FaultMessage
|
||||
}
|
||||
} else if soap.IsVimFault(err) {
|
||||
faultMessage = soap.ToVimFault(err).GetMethodFault().FaultMessage
|
||||
} else {
|
||||
switch err := err.(type) {
|
||||
case task.Error:
|
||||
faultMessage = err.Fault().GetMethodFault().FaultMessage
|
||||
}
|
||||
}
|
||||
|
||||
if faultMessage == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
lockedFile := false
|
||||
var devices []string
|
||||
for _, message := range faultMessage {
|
||||
switch message.Key {
|
||||
case LockedFileKey:
|
||||
lockedFile = true
|
||||
case DiskBackendKey:
|
||||
for _, arg := range message.Arg {
|
||||
if device, ok := arg.Value.(string); ok {
|
||||
devices = append(devices, device)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if lockedFile {
|
||||
// make sure locked devices are returned only when both keys appear in the error
|
||||
return devices
|
||||
}
|
||||
return nil
|
||||
}
|
||||
60
vendor/github.com/vmware/vic/pkg/vsphere/disk/vmdk_test.go
generated
vendored
60
vendor/github.com/vmware/vic/pkg/vsphere/disk/vmdk_test.go
generated
vendored
File diff suppressed because one or more lines are too long
454
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/basic_test.go
generated
vendored
454
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/basic_test.go
generated
vendored
@@ -1,454 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package extraconfig
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// [BEGIN] SLIMMED DOWNED and MODIFIED VERSION of github.com/vmware/vic/lib/metadata
|
||||
type Common struct {
|
||||
ExecutionEnvironment string `vic:"0.1" recurse:"depth=0"`
|
||||
|
||||
ID string `vic:"0.1" scope:"read-only" key:"id"`
|
||||
|
||||
Name string `vic:"0.1" scope:"read-only" key:"name"`
|
||||
|
||||
Notes string `vic:"0.1" scope:"read-only" key:"notes"`
|
||||
}
|
||||
|
||||
type ContainerVM struct {
|
||||
Common `vic:"0.1" scope:"read-only" key:"common"`
|
||||
|
||||
Version string `vic:"0.1" scope:"hidden" key:"version"`
|
||||
|
||||
Aliases map[string]string `vic:"0.1" recurse:"depth=0"`
|
||||
|
||||
Interaction url.URL `vic:"0.1" recurse:"depth=0"`
|
||||
|
||||
AgentKey []byte `vic:"0.1" recurse:"depth=0"`
|
||||
}
|
||||
|
||||
type ExecutorConfig struct {
|
||||
Common `vic:"0.1" scope:"read-only" key:"common"`
|
||||
|
||||
Sessions map[string]SessionConfig `vic:"0.1" scope:"hidden" key:"sessions"`
|
||||
|
||||
Key string `json:"string"`
|
||||
}
|
||||
|
||||
type ExecutorConfigPointers struct {
|
||||
Common `vic:"0.1" scope:"read-only" key:"common"`
|
||||
|
||||
Sessions map[string]*SessionConfig `vic:"0.1" scope:"hidden" key:"sessions"`
|
||||
|
||||
Key string `json:"string"` // will inherit parent vic attributes
|
||||
}
|
||||
|
||||
type Cmd struct {
|
||||
Path string `vic:"0.1" scope:"hidden" key:"path"`
|
||||
|
||||
Args []string `vic:"0.1" scope:"hidden" key:"args"`
|
||||
|
||||
Env []string `vic:"0.1" scope:"hidden" key:"env"`
|
||||
|
||||
Dir string `vic:"0.1" scope:"hidden" key:"dir"`
|
||||
|
||||
Cmd *exec.Cmd `vic:"0.1" scope:"hidden" key:"cmd" recurse:"depth=0"`
|
||||
}
|
||||
|
||||
type SessionConfig struct {
|
||||
Common `vic:"0.1" scope:"hidden" key:"common" json:"page"`
|
||||
|
||||
Cmd Cmd `vic:"0.1" scope:"hidden" key:"cmd"`
|
||||
|
||||
Tty bool `vic:"0.1" scope:"hidden" key:"tty"`
|
||||
}
|
||||
|
||||
type ExecutorConfigPointersVisible struct {
|
||||
Sessions map[string]*VisibleSessionConfig `vic:"0.1" scope:"read-only" key:"sessions"`
|
||||
}
|
||||
|
||||
type VisibleSessionConfig struct {
|
||||
Cmd Cmd `vic:"0.1" scope:"read-only" key:"cmd"`
|
||||
|
||||
Tty bool `vic:"0.1" scope:"read-only" key:"tty"`
|
||||
}
|
||||
|
||||
// [END] SLIMMED VERSION of github.com/vmware/vic/lib/metadata
|
||||
|
||||
// make it verbose during testing
|
||||
func init() {
|
||||
logger.Level = logrus.DebugLevel
|
||||
}
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
type Type struct {
|
||||
Int int `vic:"0.1" scope:"read-write" key:"int"`
|
||||
Bool bool `vic:"0.1" scope:"read-write" key:"bool"`
|
||||
Float float64 `vic:"0.1" scope:"read-write" key:"float"`
|
||||
String string `vic:"0.1" scope:"read-write" key:"string"`
|
||||
}
|
||||
|
||||
Struct := Type{
|
||||
42,
|
||||
true,
|
||||
3.14,
|
||||
"Grrr",
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Struct)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRW("int"): "42",
|
||||
visibleRW("bool"): "true",
|
||||
visibleRW("float"): "3.14E+00",
|
||||
visibleRW("string"): "Grrr",
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Struct, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestBasicMap(t *testing.T) {
|
||||
type Type struct {
|
||||
IntMap map[string]int `vic:"0.1" scope:"read-only" key:"intmap"`
|
||||
}
|
||||
|
||||
// key is not present
|
||||
var decoded Type
|
||||
Decode(MapSource(nil), &decoded)
|
||||
assert.NotNil(t, decoded.IntMap)
|
||||
assert.Empty(t, decoded.IntMap)
|
||||
|
||||
IntMap := Type{
|
||||
map[string]int{
|
||||
"1st": 12345,
|
||||
"2nd": 67890,
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), IntMap)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("intmap" + Separator + "1st"): "12345",
|
||||
visibleRO("intmap" + Separator + "2nd"): "67890",
|
||||
visibleRO("intmap"): "1st" + Separator + "2nd",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
// Decode to new variable
|
||||
decoded = Type{}
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, IntMap, decoded, "Encoded and decoded does not match")
|
||||
|
||||
// Decode to already existing variable
|
||||
IntMapOptimusPrime := Type{
|
||||
map[string]int{
|
||||
"first": 1,
|
||||
"second": 2,
|
||||
"1st": 0,
|
||||
},
|
||||
}
|
||||
Decode(MapSource(encoded), &IntMapOptimusPrime)
|
||||
|
||||
// We expect a merge and over-write
|
||||
expectedOptimusPrime := Type{
|
||||
map[string]int{
|
||||
"1st": 12345,
|
||||
"2nd": 67890,
|
||||
"first": 1,
|
||||
"second": 2,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, IntMapOptimusPrime, expectedOptimusPrime, "Decoded and expected does not match")
|
||||
|
||||
}
|
||||
|
||||
func TestBasicSlice(t *testing.T) {
|
||||
type Type struct {
|
||||
IntSlice []int `vic:"0.1" scope:"read-only" key:"intslice"`
|
||||
}
|
||||
|
||||
IntSlice := Type{
|
||||
[]int{1, 2, 3, 4, 5},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), IntSlice)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("intslice~"): "1" + Separator + "2" + Separator + "3" + Separator + "4" + Separator + "5",
|
||||
visibleRO("intslice"): "4",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
decoded.IntSlice = make([]int, 1)
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, IntSlice, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestStruct(t *testing.T) {
|
||||
type Type struct {
|
||||
Common Common `vic:"0.1" scope:"read-only" key:"common"`
|
||||
}
|
||||
|
||||
Struct := Type{
|
||||
Common: Common{
|
||||
ID: "0xDEADBEEF",
|
||||
Name: "Struct",
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Struct)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("common/id"): "0xDEADBEEF",
|
||||
visibleRO("common/name"): "Struct",
|
||||
visibleRO("common/notes"): "",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Struct, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestTime(t *testing.T) {
|
||||
type Type struct {
|
||||
Time time.Time `vic:"0.1" scope:"read-only" key:"time"`
|
||||
}
|
||||
|
||||
Time := Type{
|
||||
Time: time.Date(2009, 11, 10, 23, 00, 00, 0, time.UTC),
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Time)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("time"): "2009-11-10 23:00:00 +0000 UTC",
|
||||
}
|
||||
assert.Equal(t, encoded, expected, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Time, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestNet(t *testing.T) {
|
||||
type Type struct {
|
||||
Net net.IPNet `vic:"0.1" scope:"read-only" key:"net"`
|
||||
}
|
||||
|
||||
// 127.0.0.1/8
|
||||
n := net.IPNet{IP: net.IP{0x7f, 0x0, 0x0, 0x1}, Mask: net.IPMask{0xff, 0x0, 0x0, 0x0}}
|
||||
Net := Type{
|
||||
Net: n,
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Net)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("net/IP"): base64.StdEncoding.EncodeToString(n.IP),
|
||||
visibleRO("net/Mask"): base64.StdEncoding.EncodeToString(n.Mask),
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Net, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestNilNetPointer(t *testing.T) {
|
||||
type Type struct {
|
||||
Net *net.IPNet `vic:"0.1" scope:"read-only" key:"net"`
|
||||
}
|
||||
|
||||
Net := Type{
|
||||
Net: nil,
|
||||
}
|
||||
|
||||
// Net should be nil - pointers are supposed to be nil if the referenced tree is zero valued
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Net)
|
||||
|
||||
expected := map[string]string{}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Net, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestPointer(t *testing.T) {
|
||||
type Type struct {
|
||||
Pointer *ContainerVM `vic:"0.1" scope:"hidden" key:"pointer"`
|
||||
PointerOmitnested *ContainerVM `vic:"0.1" scope:"non-persistent" key:"pointeromitnested" recurse:"depth=0"`
|
||||
}
|
||||
|
||||
Pointer := Type{
|
||||
Pointer: &ContainerVM{Version: "0.1"},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Pointer)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("pointer/common/id"): "",
|
||||
visibleRO("pointer/common/name"): "",
|
||||
visibleRO("pointer/common/notes"): "",
|
||||
"pointer/version": "0.1",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Pointer, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestInheritenceOfNonPersistence(t *testing.T) {
|
||||
type CommonPersistence struct {
|
||||
ExecutionEnvironment string `vic:"0.1" recurse:"depth=0"`
|
||||
|
||||
ID string `vic:"0.1" scope:"read-only" key:"id"`
|
||||
|
||||
Name string `vic:"0.1" scope:"read-only" key:"name"`
|
||||
|
||||
Notes string `vic:"0.1" scope:"hidden" key:"notes"`
|
||||
}
|
||||
|
||||
type Type struct {
|
||||
Common CommonPersistence `vic:"0.1" scope:"read-write,non-persistent" key:"common"`
|
||||
}
|
||||
|
||||
Struct := Type{
|
||||
Common: CommonPersistence{
|
||||
ID: "0xDEADBEEF",
|
||||
Name: "Struct",
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
filterSink := ScopeFilterSink(NonPersistent|Hidden, MapSink(encoded))
|
||||
Encode(filterSink, Struct)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRONonpersistent("common/id"): "0xDEADBEEF",
|
||||
visibleRONonpersistent("common/name"): "Struct",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Struct, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestInheritenceOfNonPersistenceWithPointer(t *testing.T) {
|
||||
|
||||
type Persistence struct {
|
||||
ExecutorConfigPointersVisible `vic:"0.1" scope:"read-only,non-persistent" key:"pointers"`
|
||||
}
|
||||
|
||||
Struct := Persistence{
|
||||
ExecutorConfigPointersVisible: ExecutorConfigPointersVisible{
|
||||
Sessions: map[string]*VisibleSessionConfig{
|
||||
"primary": {
|
||||
Tty: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
filterSink := ScopeFilterSink(NonPersistent|Hidden, MapSink(encoded))
|
||||
Encode(filterSink, Struct)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRONonpersistent("pointers/sessions"): "primary",
|
||||
visibleRONonpersistent("pointers/sessions" + Separator + "primary/tty"): "true",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Persistence
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Struct, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestFilterSink(t *testing.T) {
|
||||
type CommonPersistence struct {
|
||||
ExecutionEnvironment string `vic:"0.1" recurse:"depth=0"`
|
||||
|
||||
ID string `vic:"0.1" scope:"read-only" key:"id"`
|
||||
|
||||
Name string `vic:"0.1" scope:"read-only,non-persistent" key:"name"`
|
||||
|
||||
Notes string `vic:"0.1" scope:"read-only" key:"notes"`
|
||||
}
|
||||
|
||||
type Type struct {
|
||||
Common CommonPersistence `vic:"0.1" scope:"read-write" key:"common"`
|
||||
}
|
||||
|
||||
Struct := Type{
|
||||
Common: CommonPersistence{
|
||||
ID: "0xDEADBEEF",
|
||||
Name: "Struct",
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
filterSink := ScopeFilterSink(NonPersistent|Hidden, MapSink(encoded))
|
||||
Encode(filterSink, Struct)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRONonpersistent("common/name"): "Struct",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
// strip ID as that would be filtered out
|
||||
Struct.Common.ID = ""
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Struct, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
537
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/clean_test.go
generated
vendored
537
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/clean_test.go
generated
vendored
@@ -1,537 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package extraconfig
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// make it verbose during testing
|
||||
func init() {
|
||||
logger.Level = logrus.DebugLevel
|
||||
}
|
||||
|
||||
func TestEmbedded(t *testing.T) {
|
||||
|
||||
type Type struct {
|
||||
Common `vic:"0.1" scope:"read-only" key:"common"`
|
||||
}
|
||||
|
||||
Embedded := Type{
|
||||
Common: Common{
|
||||
ID: "0xDEADBEEF",
|
||||
Name: "Embedded",
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Embedded)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("common/id"): "0xDEADBEEF",
|
||||
visibleRO("common/name"): "Embedded",
|
||||
visibleRO("common/notes"): "",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Embedded, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestNetPointer(t *testing.T) {
|
||||
type Type struct {
|
||||
Net *net.IPNet `vic:"0.1" scope:"read-only" key:"net"`
|
||||
}
|
||||
|
||||
// 127.0.0.1/8
|
||||
n := net.IPNet{IP: net.IP{0x7f, 0x0, 0x0, 0x1}, Mask: net.IPMask{0xff, 0x0, 0x0, 0x0}}
|
||||
Net := Type{
|
||||
Net: &n,
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Net)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("net/IP"): base64.StdEncoding.EncodeToString(n.IP),
|
||||
visibleRO("net/Mask"): base64.StdEncoding.EncodeToString(n.Mask),
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Net, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestTimePointer(t *testing.T) {
|
||||
d := time.Date(2009, 11, 10, 23, 00, 00, 0, time.UTC)
|
||||
|
||||
type Type struct {
|
||||
Time *time.Time `vic:"0.1" scope:"read-only" key:"time"`
|
||||
}
|
||||
|
||||
Time := Type{
|
||||
Time: &d,
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), Time)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("time"): "2009-11-10 23:00:00 +0000 UTC",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, Time, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestStructMap(t *testing.T) {
|
||||
type Type struct {
|
||||
StructMap map[string]Common `vic:"0.1" scope:"read-only" key:"map"`
|
||||
}
|
||||
|
||||
StructMap := Type{
|
||||
map[string]Common{
|
||||
"Key1": {
|
||||
ID: "0xDEADBEEF",
|
||||
Name: "beef",
|
||||
},
|
||||
"Key2": {
|
||||
ID: "0x8BADF00D",
|
||||
Name: "food",
|
||||
},
|
||||
"Key3": {
|
||||
ID: "0xDEADF00D",
|
||||
Name: "dead",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), StructMap)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("map" + Separator + "Key1/id"): "0xDEADBEEF",
|
||||
visibleRO("map" + Separator + "Key1/name"): "beef",
|
||||
visibleRO("map" + Separator + "Key1/notes"): "",
|
||||
visibleRO("map" + Separator + "Key2/id"): "0x8BADF00D",
|
||||
visibleRO("map" + Separator + "Key2/name"): "food",
|
||||
visibleRO("map" + Separator + "Key2/notes"): "",
|
||||
visibleRO("map" + Separator + "Key3/id"): "0xDEADF00D",
|
||||
visibleRO("map" + Separator + "Key3/name"): "dead",
|
||||
visibleRO("map" + Separator + "Key3/notes"): "",
|
||||
visibleRO("map"): "Key1" + Separator + "Key2" + Separator + "Key3",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, StructMap, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestIntStructMap(t *testing.T) {
|
||||
type Type struct {
|
||||
StructMap map[int]Common `vic:"0.1" scope:"read-only" key:"map"`
|
||||
}
|
||||
|
||||
StructMap := Type{
|
||||
map[int]Common{
|
||||
1: {
|
||||
ID: "0xDEADBEEF",
|
||||
Name: "beef",
|
||||
},
|
||||
2: {
|
||||
ID: "0x8BADF00D",
|
||||
Name: "food",
|
||||
},
|
||||
3: {
|
||||
ID: "0xDEADF00D",
|
||||
Name: "dead",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), StructMap)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("map" + Separator + "1/id"): "0xDEADBEEF",
|
||||
visibleRO("map" + Separator + "1/name"): "beef",
|
||||
visibleRO("map" + Separator + "1/notes"): "",
|
||||
visibleRO("map" + Separator + "2/id"): "0x8BADF00D",
|
||||
visibleRO("map" + Separator + "2/name"): "food",
|
||||
visibleRO("map" + Separator + "2/notes"): "",
|
||||
visibleRO("map" + Separator + "3/id"): "0xDEADF00D",
|
||||
visibleRO("map" + Separator + "3/name"): "dead",
|
||||
visibleRO("map" + Separator + "3/notes"): "",
|
||||
visibleRO("map"): "1" + Separator + "2" + Separator + "3",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, StructMap, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestStructSlice(t *testing.T) {
|
||||
type Type struct {
|
||||
StructSlice []Common `vic:"0.1" scope:"read-only" key:"slice"`
|
||||
}
|
||||
|
||||
StructSlice := Type{
|
||||
[]Common{
|
||||
{
|
||||
ID: "0xDEADFEED",
|
||||
Name: "feed",
|
||||
},
|
||||
{
|
||||
ID: "0xFACEFEED",
|
||||
Name: "face",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), StructSlice)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("slice"): "1",
|
||||
visibleRO("slice" + Separator + "0/id"): "0xDEADFEED",
|
||||
visibleRO("slice" + Separator + "0/name"): "feed",
|
||||
visibleRO("slice" + Separator + "0/notes"): "",
|
||||
visibleRO("slice" + Separator + "1/id"): "0xFACEFEED",
|
||||
visibleRO("slice" + Separator + "1/name"): "face",
|
||||
visibleRO("slice" + Separator + "1/notes"): "",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, StructSlice, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestMultipleScope(t *testing.T) {
|
||||
MultipleScope := struct {
|
||||
MultipleScope string `vic:"0.1" scope:"read-only,hidden,non-persistent" key:"multiscope"`
|
||||
}{
|
||||
"MultipleScope",
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), MultipleScope)
|
||||
|
||||
expected := map[string]string{}
|
||||
assert.Equal(t, expected, encoded, "Not equal")
|
||||
}
|
||||
|
||||
func TestUnknownScope(t *testing.T) {
|
||||
UnknownScope := struct {
|
||||
UnknownScope int `vic:"0.1" scope:"unknownscope" key:"unknownscope"`
|
||||
}{
|
||||
42,
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), UnknownScope)
|
||||
|
||||
expected := map[string]string{}
|
||||
assert.Equal(t, encoded, expected, "Not equal")
|
||||
}
|
||||
|
||||
func TestUnknownProperty(t *testing.T) {
|
||||
UnknownProperty := struct {
|
||||
UnknownProperty int `vic:"0.1" scope:"hidden" key:"unknownproperty" recurse:"unknownproperty"`
|
||||
}{
|
||||
42,
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), UnknownProperty)
|
||||
|
||||
expected := map[string]string{
|
||||
hidden("unknownproperty"): "42",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Not equal")
|
||||
}
|
||||
|
||||
func TestOmitNested(t *testing.T) {
|
||||
OmitNested := struct {
|
||||
Time time.Time `vic:"0.1" scope:"volatile" key:"time" recurse:"depth=0"`
|
||||
CurrentTime time.Time `vic:"0.1" scope:"volatile" key:"time"`
|
||||
}{
|
||||
Time: time.Date(2009, 11, 10, 23, 00, 00, 0, time.UTC),
|
||||
CurrentTime: time.Date(2009, 11, 10, 23, 00, 00, 0, time.UTC),
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), OmitNested)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("time"): "2009-11-10 23:00:00 +0000 UTC",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and decoded does not match")
|
||||
|
||||
}
|
||||
|
||||
func TestComplex(t *testing.T) {
|
||||
type Type struct {
|
||||
ExecutorConfig ExecutorConfig `vic:"0.1" scope:"hidden" key:"executorconfig"`
|
||||
}
|
||||
|
||||
ExecutorConfig := Type{
|
||||
ExecutorConfig{
|
||||
Sessions: map[string]SessionConfig{
|
||||
"Session1": {
|
||||
Common: Common{
|
||||
ID: "SessionID",
|
||||
Name: "SessionName",
|
||||
},
|
||||
Tty: true,
|
||||
Cmd: Cmd{
|
||||
Path: "/vmware",
|
||||
Args: []string{"/bin/imagec", "-standalone"},
|
||||
Env: []string{"PATH=/bin", "USER=imagec"},
|
||||
Dir: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), ExecutorConfig)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("executorconfig/common/id"): "",
|
||||
visibleRO("executorconfig/common/name"): "",
|
||||
visibleRO("executorconfig/common/notes"): "",
|
||||
visibleRO("executorconfig/sessions" + Separator + "Session1/common/id"): "SessionID",
|
||||
visibleRO("executorconfig/sessions" + Separator + "Session1/common/name"): "SessionName",
|
||||
visibleRO("executorconfig/sessions" + Separator + "Session1/common/notes"): "",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/path"): "/vmware",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/args~"): "/bin/imagec" + Separator + "-standalone",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/args"): "1",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/env~"): "PATH=/bin" + Separator + "USER=imagec",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/env"): "1",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/dir"): "/",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/tty"): "true",
|
||||
hidden("executorconfig/sessions"): "Session1",
|
||||
hidden("executorconfig/Key"): "",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, ExecutorConfig, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
func TestComplexPointer(t *testing.T) {
|
||||
type Type struct {
|
||||
ExecutorConfig *ExecutorConfig `vic:"0.1" scope:"hidden" key:"executorconfig"`
|
||||
}
|
||||
|
||||
ExecutorConfig := Type{
|
||||
&ExecutorConfig{
|
||||
Sessions: map[string]SessionConfig{
|
||||
"Session1": {
|
||||
Common: Common{
|
||||
ID: "SessionID",
|
||||
Name: "SessionName",
|
||||
},
|
||||
Tty: true,
|
||||
Cmd: Cmd{
|
||||
Path: "/vmware",
|
||||
Args: []string{"/bin/imagec", "-standalone"},
|
||||
Env: []string{"PATH=/bin", "USER=imagec"},
|
||||
Dir: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), ExecutorConfig)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("executorconfig/common/id"): "",
|
||||
visibleRO("executorconfig/common/name"): "",
|
||||
visibleRO("executorconfig/common/notes"): "",
|
||||
visibleRO("executorconfig/sessions" + Separator + "Session1/common/id"): "SessionID",
|
||||
visibleRO("executorconfig/sessions" + Separator + "Session1/common/name"): "SessionName",
|
||||
visibleRO("executorconfig/sessions" + Separator + "Session1/common/notes"): "",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/path"): "/vmware",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/args~"): "/bin/imagec" + Separator + "-standalone",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/args"): "1",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/env~"): "PATH=/bin" + Separator + "USER=imagec",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/env"): "1",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/cmd/dir"): "/",
|
||||
hidden("executorconfig/sessions" + Separator + "Session1/tty"): "true",
|
||||
hidden("executorconfig/sessions"): "Session1",
|
||||
hidden("executorconfig/Key"): "",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Type
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, ExecutorConfig, decoded, "Encoded and decoded does not match")
|
||||
}
|
||||
|
||||
// TestPointerDecode tests the translation from a type where the sessions are direct values to
|
||||
// one where they are pointers
|
||||
func TestPointerDecode(t *testing.T) {
|
||||
reference := ExecutorConfig{
|
||||
Sessions: map[string]SessionConfig{
|
||||
"Session1": {
|
||||
Common: Common{
|
||||
ID: "SessionID",
|
||||
Name: "SessionName",
|
||||
},
|
||||
Tty: true,
|
||||
Cmd: Cmd{
|
||||
Path: "/vmware",
|
||||
Args: []string{"/bin/imagec", "-standalone"},
|
||||
Env: []string{"PATH=/bin", "USER=imagec"},
|
||||
Dir: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), reference)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("common/id"): "",
|
||||
visibleRO("common/name"): "",
|
||||
visibleRO("common/notes"): "",
|
||||
visibleRO("sessions" + Separator + "Session1/common/id"): "SessionID",
|
||||
visibleRO("sessions" + Separator + "Session1/common/name"): "SessionName",
|
||||
visibleRO("sessions" + Separator + "Session1/common/notes"): "",
|
||||
hidden("sessions" + Separator + "Session1/cmd/path"): "/vmware",
|
||||
hidden("sessions" + Separator + "Session1/cmd/args~"): "/bin/imagec" + Separator + "-standalone",
|
||||
hidden("sessions" + Separator + "Session1/cmd/args"): "1",
|
||||
hidden("sessions" + Separator + "Session1/cmd/env~"): "PATH=/bin" + Separator + "USER=imagec",
|
||||
hidden("sessions" + Separator + "Session1/cmd/env"): "1",
|
||||
hidden("sessions" + Separator + "Session1/cmd/dir"): "/",
|
||||
hidden("sessions" + Separator + "Session1/tty"): "true",
|
||||
hidden("sessions"): "Session1",
|
||||
hidden("Key"): "",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded ExecutorConfigPointers
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
// cannot assert equality at a high level because of the different structure types, but we can test the
|
||||
// common structure fragments
|
||||
assert.Equal(t, reference.Sessions["Session1"], *decoded.Sessions["Session1"], "Encoded and decoded sessions do not match")
|
||||
|
||||
}
|
||||
|
||||
func TestInsideOutside(t *testing.T) {
|
||||
type Inside struct {
|
||||
ID string `vic:"0.1" scope:"read-write" key:"id"`
|
||||
Name string `vic:"0.1" scope:"read-write" key:"name"`
|
||||
}
|
||||
|
||||
type Outside struct {
|
||||
Inside Inside `vic:"0.1" scope:"read-only" key:"inside"`
|
||||
ID string `vic:"0.1" scope:"read-write" key:"id"`
|
||||
Name string `vic:"0.1" scope:"read-write" key:"name"`
|
||||
}
|
||||
outside := Outside{
|
||||
Inside: Inside{
|
||||
ID: "inside",
|
||||
Name: "Inside",
|
||||
},
|
||||
ID: "outside",
|
||||
Name: "Outside",
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(MapSink(encoded), outside)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRW("inside.id"): "inside",
|
||||
visibleRW("inside.name"): "Inside",
|
||||
visibleRW("id"): "outside",
|
||||
visibleRW("name"): "Outside",
|
||||
}
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected does not match")
|
||||
|
||||
var decoded Outside
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
|
||||
assert.Equal(t, outside, decoded, "Encoded and decoded does not match")
|
||||
|
||||
}
|
||||
|
||||
func TestIPSlice(t *testing.T) {
|
||||
type Slice struct {
|
||||
Slice []net.IP `vic:"0.1" scope:"read-only" key:"slice"`
|
||||
}
|
||||
|
||||
ips := []net.IP{
|
||||
net.ParseIP("10.10.10.10"),
|
||||
net.ParseIP("10.10.10.1"),
|
||||
}
|
||||
|
||||
encodedIPs := make([]string, len(ips))
|
||||
for i := range ips {
|
||||
Encode(func(key, value string) error {
|
||||
encodedIPs[i] = value
|
||||
return nil
|
||||
}, ips[i])
|
||||
}
|
||||
|
||||
s := Slice{
|
||||
Slice: ips,
|
||||
}
|
||||
|
||||
encoded := make(map[string]string)
|
||||
Encode(MapSink(encoded), s)
|
||||
|
||||
expected := map[string]string{
|
||||
visibleRO("slice"): fmt.Sprintf("%d", len(ips)-1),
|
||||
}
|
||||
for i := range encodedIPs {
|
||||
expected[visibleRO(fmt.Sprintf("slice"+Separator+"%d", i))] = encodedIPs[i]
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, encoded, "Encoded and expected do not match")
|
||||
|
||||
var decoded Slice
|
||||
Decode(MapSource(encoded), &decoded)
|
||||
assert.Equal(t, s, decoded, "Encoded and decoded do not match")
|
||||
}
|
||||
21
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/existing_test.go
generated
vendored
21
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/existing_test.go
generated
vendored
@@ -1,21 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package extraconfig
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestExistingBasic(t *testing.T) {
|
||||
t.Skip("decode into existing target tests not yet implemented")
|
||||
}
|
||||
269
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/keys_test.go
generated
vendored
269
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/keys_test.go
generated
vendored
@@ -1,269 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package extraconfig
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func visibleRO(key string) string {
|
||||
return calculateKey(calculateScope([]string{"read-only"}), "", key)
|
||||
}
|
||||
|
||||
func visibleRONonpersistent(key string) string {
|
||||
return calculateKey(calculateScope([]string{"read-only", "non-persistent"}), "", key)
|
||||
}
|
||||
|
||||
func visibleRW(key string) string {
|
||||
return calculateKey(calculateScope([]string{"read-write"}), "", key)
|
||||
}
|
||||
|
||||
func hidden(key string) string {
|
||||
return calculateKey(calculateScope([]string{"hidden"}), "", key)
|
||||
}
|
||||
|
||||
func TestHidden(t *testing.T) {
|
||||
scopes := []string{"hidden"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), "a/b", "c")
|
||||
|
||||
assert.Equal(t, "a/b/c", key, "Key should remain hidden")
|
||||
}
|
||||
|
||||
func TestHide(t *testing.T) {
|
||||
scopes := []string{"hidden"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+"/a/b", "c")
|
||||
|
||||
assert.Equal(t, "a/b/c", key, "Key should be hidden")
|
||||
}
|
||||
|
||||
func TestReveal(t *testing.T) {
|
||||
scopes := []string{"read-only"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), "a/b", "c")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+"/a/b/c", key, "Key should be exposed")
|
||||
}
|
||||
|
||||
func TestVisibleReadOnly(t *testing.T) {
|
||||
scopes := []string{"read-only"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+"/a/b", "c")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+"/a/b/c", key, "Key should be remain visible and read-only")
|
||||
}
|
||||
|
||||
func TestVisibleReadWrite(t *testing.T) {
|
||||
scopes := []string{"read-write"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+".a.b", "c")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+".a.b.c", key, "Key should be remain visible and read-write")
|
||||
}
|
||||
|
||||
func TestTopLevelReadOnly(t *testing.T) {
|
||||
scopes := []string{"read-only"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), "", "a")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+"/a", key, "Key should be visible and read-only")
|
||||
}
|
||||
|
||||
func TestReadOnlyToReadWrite(t *testing.T) {
|
||||
scopes := []string{"read-write"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+"/a/b", "c")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+".a.b.c", key, "Key should be visible and change to read-write")
|
||||
}
|
||||
|
||||
func TestReadWriteToReadOnly(t *testing.T) {
|
||||
scopes := []string{"read-only"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+".a.b", "c")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+"/a/b/c", key, "Key should be visible and change to read-only")
|
||||
}
|
||||
|
||||
func TestCompoundKey(t *testing.T) {
|
||||
scopes := []string{"read-write"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+".a", "b/c")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+".a.b.c", key, "Key should be visible and read-write")
|
||||
}
|
||||
|
||||
func TestNoScopes(t *testing.T) {
|
||||
scopes := []string{}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+".a/b", "c")
|
||||
assert.Equal(t, "a/b/c", key, "Key should be completely proscriptive")
|
||||
|
||||
key = calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+".a.b", "c")
|
||||
assert.Equal(t, "a.b/c", key, "Key should be hidden")
|
||||
|
||||
key = calculateKey(calculateScope(scopes), "a.b", "c")
|
||||
assert.Equal(t, "a.b/c", key, "Key should remain hidden")
|
||||
}
|
||||
|
||||
func TestSecret(t *testing.T) {
|
||||
scopes := []string{"secret", "read-write"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+".a.b", "c")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+".a.b.c"+suffixSeparator+secretSuffix, key, "Key should have secret suffix")
|
||||
}
|
||||
|
||||
func TestNonpersistent(t *testing.T) {
|
||||
scopes := []string{"non-persistent", "read-write"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+".a.b", "c")
|
||||
|
||||
assert.Equal(t, DefaultGuestInfoPrefix+".a.b.c"+suffixSeparator+nonpersistentSuffix, key, "Key should have non-persistent suffix")
|
||||
}
|
||||
|
||||
func TestMultipleSuffixes(t *testing.T) {
|
||||
scopes := []string{"non-persistent", "secret", "read-write"}
|
||||
|
||||
key := calculateKey(calculateScope(scopes), DefaultGuestInfoPrefix+".a.b", "c")
|
||||
|
||||
assert.True(t, strings.Contains(key, suffixSeparator+secretSuffix) && strings.Contains(key, suffixSeparator+nonpersistentSuffix), "Key should contain both secret and non-persistent suffix")
|
||||
}
|
||||
|
||||
func TestCalculateKeys(t *testing.T) {
|
||||
type AStruct struct {
|
||||
I int
|
||||
}
|
||||
type Type struct {
|
||||
ExecutorConfig ExecutorConfig `vic:"0.1" scope:"hidden" key:"executorconfig"`
|
||||
Array []AStruct `vic:"0.1" scope:"read-write" key:"array"`
|
||||
Ptr *AStruct `vic:"0.1" scope:"read-only" key:"ptr"`
|
||||
Str string `vic:"0.1" scope:"read-only" key:"str"`
|
||||
Bytes []uint8 `vic:"0.1" scope:"read-write" key:"bytes"`
|
||||
}
|
||||
|
||||
ec := Type{
|
||||
ExecutorConfig: ExecutorConfig{
|
||||
Sessions: map[string]SessionConfig{
|
||||
"Session1": {
|
||||
Common: Common{
|
||||
ID: "SessionID",
|
||||
Name: "SessionName",
|
||||
},
|
||||
Tty: true,
|
||||
Cmd: Cmd{
|
||||
Path: "/vmware",
|
||||
Args: []string{"/bin/imagec", "-standalone"},
|
||||
Env: []string{"PATH=/bin", "USER=imagec"},
|
||||
Dir: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Array: []AStruct{
|
||||
{I: 0},
|
||||
},
|
||||
Ptr: &AStruct{
|
||||
I: 1,
|
||||
},
|
||||
Str: "foo",
|
||||
Bytes: []byte{0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf},
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
in string
|
||||
out []string
|
||||
}{
|
||||
{
|
||||
"ExecutorConfig.*",
|
||||
[]string{
|
||||
visibleRO("executorconfig/common"),
|
||||
hidden("executorconfig/sessions"),
|
||||
"executorconfig/Key",
|
||||
},
|
||||
},
|
||||
{
|
||||
"ExecutorConfig.Sessions.*",
|
||||
[]string{"executorconfig/sessions" + Separator + "Session1"},
|
||||
},
|
||||
{
|
||||
"ExecutorConfig.Sessions.Session1.Cmd.Args",
|
||||
[]string{"executorconfig/sessions" + Separator + "Session1/cmd/args"},
|
||||
},
|
||||
{
|
||||
"ExecutorConfig.Sessions.*.Cmd.Args.*",
|
||||
[]string{"executorconfig/sessions" + Separator + "Session1/cmd/args~"},
|
||||
},
|
||||
{
|
||||
"ExecutorConfig.Sessions.*.Cmd.Args.0",
|
||||
[]string{"executorconfig/sessions" + Separator + "Session1/cmd/args~"},
|
||||
},
|
||||
{
|
||||
"Array.0.I",
|
||||
[]string{visibleRW("array" + Separator + "0/I")},
|
||||
},
|
||||
{
|
||||
"Array.*",
|
||||
[]string{visibleRW("array" + Separator + "0")},
|
||||
},
|
||||
{
|
||||
"Ptr.I",
|
||||
[]string{visibleRO("ptr/I")},
|
||||
},
|
||||
{
|
||||
"Str",
|
||||
[]string{visibleRO("str")},
|
||||
},
|
||||
{
|
||||
"Bytes",
|
||||
[]string{visibleRW("bytes")},
|
||||
},
|
||||
{
|
||||
"Bytes.0",
|
||||
[]string{visibleRW("bytes")},
|
||||
},
|
||||
{
|
||||
"Bytes.*",
|
||||
[]string{visibleRW("bytes")},
|
||||
},
|
||||
}
|
||||
|
||||
for _, te := range tests {
|
||||
keys := CalculateKeys(ec, te.in, "")
|
||||
assert.Equal(t, te.out, keys)
|
||||
}
|
||||
|
||||
panicTests := []string{
|
||||
"Array.1.I",
|
||||
"Array.0.i",
|
||||
"Array.f.i",
|
||||
"ExecutorConfig.foo",
|
||||
"foo",
|
||||
"ExecutorConfig.Sessions.foo",
|
||||
"Str.*",
|
||||
"Str.foo",
|
||||
}
|
||||
|
||||
for _, te := range panicTests {
|
||||
assert.Panics(t, func() {
|
||||
CalculateKeys(ec, te, "")
|
||||
})
|
||||
}
|
||||
}
|
||||
67
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/secret_test.go
generated
vendored
67
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/secret_test.go
generated
vendored
@@ -1,67 +0,0 @@
|
||||
// Copyright 2016-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 extraconfig
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSecretFields(t *testing.T) {
|
||||
type tell struct {
|
||||
Who string `vic:"0.1" scope:"secret" key:"who"`
|
||||
}
|
||||
|
||||
type stuff struct {
|
||||
Username string `vic:"0.1" scope:"read-only" key:"username"`
|
||||
Password string `vic:"0.1" scope:"secret" key:"password"`
|
||||
Tell tell
|
||||
}
|
||||
|
||||
config := stuff{
|
||||
Username: "root",
|
||||
Password: "super-s@fe-passw0rd",
|
||||
Tell: tell{"noone"},
|
||||
}
|
||||
|
||||
out, err := NewSecretKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
encoded := map[string]string{}
|
||||
Encode(out.Sink(MapSink(encoded)), config)
|
||||
|
||||
password := encoded["guestinfo.vice./password"+suffixSeparator+secretSuffix]
|
||||
assert.NotEmpty(t, password, "encrypted password")
|
||||
assert.NotEqual(t, password, config.Password, "encrypted password")
|
||||
|
||||
for _, expectEq := range []bool{true, false} {
|
||||
var in SecretKey
|
||||
|
||||
var decoded stuff
|
||||
Decode(in.Source(MapSource(encoded)), &decoded)
|
||||
|
||||
if expectEq {
|
||||
assert.Equal(t, config, decoded, "Encoded and decoded does not match")
|
||||
} else {
|
||||
assert.NotEqual(t, config, decoded, "Encoded and decoded should not not match")
|
||||
}
|
||||
|
||||
// second time should fail to decrypt w/o GuestInfoSecretKey
|
||||
delete(encoded, GuestInfoSecretKey)
|
||||
}
|
||||
}
|
||||
55
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/vmomi/delta_test.go
generated
vendored
55
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/vmomi/delta_test.go
generated
vendored
@@ -1,55 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package vmomi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"reflect"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
func TestDelta(t *testing.T) {
|
||||
new := map[string]string{
|
||||
"hello": "goodbye",
|
||||
"cruel": "world",
|
||||
"is": "not",
|
||||
"enough": "already",
|
||||
}
|
||||
|
||||
existing := []types.BaseOptionValue{
|
||||
&types.OptionValue{Key: "hello", Value: "goodbye"},
|
||||
&types.OptionValue{Key: "is", Value: "always"},
|
||||
&types.OptionValue{Key: "present", Value: "regardless"},
|
||||
}
|
||||
|
||||
updatesSlice := OptionValueUpdatesFromMap(existing, new)
|
||||
|
||||
expected := map[string]string{
|
||||
"enough": "already", // added
|
||||
"cruel": "world", // added
|
||||
"is": "not", // changed
|
||||
}
|
||||
|
||||
// turn them back into maps for equality check
|
||||
updates := OptionValueMap(updatesSlice)
|
||||
|
||||
if !assert.True(t, reflect.DeepEqual(expected, updates), "DeepEqual says they do not match") {
|
||||
t.Fatalf("Expected: %+q \nActual: %+q\n", expected, updates)
|
||||
}
|
||||
}
|
||||
140
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/vmomi/optionvalue.go
generated
vendored
140
vendor/github.com/vmware/vic/pkg/vsphere/extraconfig/vmomi/optionvalue.go
generated
vendored
@@ -1,140 +0,0 @@
|
||||
// Copyright 2016-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 vmomi is in a separate package to avoid the transitive inclusion of govmomi
|
||||
// as a fundamental dependency of the main extraconfig
|
||||
package vmomi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig"
|
||||
)
|
||||
|
||||
// OptionValueMap returns a map from array of OptionValues
|
||||
func OptionValueMap(src []types.BaseOptionValue) map[string]string {
|
||||
// create the key/value store from the extraconfig slice for lookups
|
||||
kv := make(map[string]string)
|
||||
for i := range src {
|
||||
k := src[i].GetOptionValue().Key
|
||||
v := src[i].GetOptionValue().Value.(string)
|
||||
kv[k] = unescapeNil(v)
|
||||
}
|
||||
return kv
|
||||
}
|
||||
|
||||
// OptionValueSource is a convenience method to generate a MapSource source from
|
||||
// and array of OptionValue's
|
||||
func OptionValueSource(src []types.BaseOptionValue) extraconfig.DataSource {
|
||||
kv := OptionValueMap(src)
|
||||
return extraconfig.MapSource(kv)
|
||||
}
|
||||
|
||||
// OptionValueFromMap is a convenience method to convert a map into a BaseOptionValue array
|
||||
// escapeNil - if true a nil string is replaced with "<nil>". Allows us to distinguish between
|
||||
// deletion and nil as a value
|
||||
func OptionValueFromMap(data map[string]string, escape bool) []types.BaseOptionValue {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
array := make([]types.BaseOptionValue, len(data))
|
||||
|
||||
i := 0
|
||||
for k, v := range data {
|
||||
if escape {
|
||||
v = escapeNil(v)
|
||||
}
|
||||
array[i] = &types.OptionValue{Key: k, Value: v}
|
||||
i++
|
||||
}
|
||||
|
||||
return array
|
||||
}
|
||||
|
||||
// OptionValueArrayToString translates the options array in to a Go formatted structure dump
|
||||
func OptionValueArrayToString(options []types.BaseOptionValue) string {
|
||||
// create the key/value store from the extraconfig slice for lookups
|
||||
kv := make(map[string]string)
|
||||
for i := range options {
|
||||
k := options[i].GetOptionValue().Key
|
||||
v := options[i].GetOptionValue().Value.(string)
|
||||
kv[k] = v
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%#v", kv)
|
||||
}
|
||||
|
||||
// OptionValueUpdatesFromMap generates an optionValue array for those entries in the map that do not
|
||||
// already exist, are changed from the reference array, or a removed
|
||||
// A removed entry will have a nil string for the value
|
||||
// NOTE: DOES NOT CURRENTLY SUPPORT DELETION OF KEYS - KEYS MISSING FROM NEW MAP ARE IGNORED
|
||||
func OptionValueUpdatesFromMap(existing []types.BaseOptionValue, new map[string]string) []types.BaseOptionValue {
|
||||
e := len(existing)
|
||||
if e == 0 {
|
||||
return OptionValueFromMap(new, true)
|
||||
}
|
||||
|
||||
n := len(new)
|
||||
updates := make(map[string]string, n+e)
|
||||
unchanged := make(map[string]struct{}, n+e)
|
||||
|
||||
// first the existing keys
|
||||
for i := range existing {
|
||||
v := existing[i].GetOptionValue()
|
||||
if nV, ok := new[v.Key]; ok && nV == v.Value.(string) {
|
||||
unchanged[v.Key] = struct{}{}
|
||||
// no change
|
||||
continue
|
||||
} else if ok {
|
||||
// changed
|
||||
updates[v.Key] = escapeNil(nV)
|
||||
} else {
|
||||
// deletion
|
||||
// NOTE: ignored as this also deletes non VIC entries currently
|
||||
// there's no prefix for the non-guestinfo keys so cannot easily filter
|
||||
// updates[v.Key] = ""
|
||||
}
|
||||
}
|
||||
|
||||
// now the new keys
|
||||
for k, v := range new {
|
||||
if _, ok := unchanged[k]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := updates[k]; !ok {
|
||||
updates[k] = escapeNil(v)
|
||||
}
|
||||
}
|
||||
|
||||
return OptionValueFromMap(updates, false)
|
||||
}
|
||||
|
||||
func escapeNil(input string) string {
|
||||
if input == "" {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
|
||||
func unescapeNil(input string) string {
|
||||
if input == "<nil>" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
75
vendor/github.com/vmware/vic/pkg/vsphere/guest/util_linux.go
generated
vendored
75
vendor/github.com/vmware/vic/pkg/vsphere/guest/util_linux.go
generated
vendored
@@ -1,75 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package guest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
const (
|
||||
UUIDPath = "/sys/class/dmi/id/product_serial"
|
||||
UUIDPrefix = "VMware-"
|
||||
)
|
||||
|
||||
// UUID gets the BIOS UUID via the sys interface. This UUID is known by vphsere
|
||||
func UUID() (string, error) {
|
||||
id, err := ioutil.ReadFile(UUIDPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error retrieving vm uuid: %s", err)
|
||||
}
|
||||
|
||||
uuidstr := string(id[:])
|
||||
|
||||
// check the uuid starts with "VMware-"
|
||||
if !strings.HasPrefix(uuidstr, UUIDPrefix) {
|
||||
return "", fmt.Errorf("cannot find this VM's UUID")
|
||||
}
|
||||
|
||||
// Strip the prefix, white spaces, and the trailing '\n'
|
||||
uuidstr = strings.Replace(uuidstr[len(UUIDPrefix):(len(uuidstr)-1)], " ", "", -1)
|
||||
|
||||
// need to add dashes, e.g. "564d395e-d807-e18a-cb25-b79f65eb2b9f"
|
||||
uuidstr = fmt.Sprintf("%s-%s-%s-%s", uuidstr[0:8], uuidstr[8:12], uuidstr[12:21], uuidstr[21:])
|
||||
|
||||
return uuidstr, nil
|
||||
}
|
||||
|
||||
// GetSelf gets VirtualMachine reference for the VM this process is running on
|
||||
func GetSelf(ctx context.Context, s *session.Session) (*vm.VirtualMachine, error) {
|
||||
u, err := UUID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
search := object.NewSearchIndex(s.Vim25())
|
||||
ref, err := search.FindByUuid(ctx, s.Datacenter, u, true, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ref == nil {
|
||||
return nil, fmt.Errorf("can't find the hosting vm")
|
||||
}
|
||||
|
||||
return vm.NewVirtualMachine(ctx, s, ref.Reference()), nil
|
||||
}
|
||||
37
vendor/github.com/vmware/vic/pkg/vsphere/guest/util_other.go
generated
vendored
37
vendor/github.com/vmware/vic/pkg/vsphere/guest/util_other.go
generated
vendored
@@ -1,37 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !linux
|
||||
|
||||
package guest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"context"
|
||||
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
// UUID gets the BIOS UUID via the sys interface. This UUID is known by vphsere
|
||||
func UUID() (string, error) {
|
||||
return "", fmt.Errorf("unimplemented on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
// GetSelf gets VirtualMachine reference for the VM this process is running on
|
||||
func GetSelf(ctx context.Context, s *session.Session) (*vm.VirtualMachine, error) {
|
||||
return nil, fmt.Errorf("unimplemented on %s", runtime.GOOS)
|
||||
}
|
||||
49
vendor/github.com/vmware/vic/pkg/vsphere/guest/util_test.go
generated
vendored
49
vendor/github.com/vmware/vic/pkg/vsphere/guest/util_test.go
generated
vendored
@@ -1,49 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package guest
|
||||
|
||||
import (
|
||||
"os/user"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vmw-guestinfo/vmcheck"
|
||||
)
|
||||
|
||||
func TestUUID(t *testing.T) {
|
||||
if isVM, err := vmcheck.IsVirtualWorld(); !isVM || err != nil {
|
||||
t.Skip("can get uuid if not running on a vm")
|
||||
}
|
||||
// need to be root and on esx to run this test
|
||||
u, err := user.Current()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if u.Uid != "0" {
|
||||
t.SkipNow()
|
||||
return
|
||||
}
|
||||
|
||||
s, err := UUID()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.NotNil(t, s) {
|
||||
return
|
||||
}
|
||||
}
|
||||
58
vendor/github.com/vmware/vic/pkg/vsphere/optmanager/optmanager.go
generated
vendored
58
vendor/github.com/vmware/vic/pkg/vsphere/optmanager/optmanager.go
generated
vendored
@@ -1,58 +0,0 @@
|
||||
// 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 optmanager provides govmomi helpers for the OptionManager.
|
||||
package optmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
)
|
||||
|
||||
// QueryOptionValue uses the session client and OptionManager to look up the input option
|
||||
// and return the received value.
|
||||
func QueryOptionValue(ctx context.Context, s *session.Session, option string) (string, error) {
|
||||
client := s.Vim25()
|
||||
optMgr := object.NewOptionManager(client, *client.ServiceContent.Setting)
|
||||
opts, err := optMgr.Query(ctx, option)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error querying option %q: %s", option, err)
|
||||
}
|
||||
|
||||
if len(opts) == 1 {
|
||||
return fmt.Sprintf("%v", opts[0].GetOptionValue().Value), nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("%d values querying option %q", len(opts), option)
|
||||
}
|
||||
|
||||
// UpdateOptionValue uses the session client and OptionManager to set the input option
|
||||
func UpdateOptionValue(ctx context.Context, s *session.Session, option string, value string) error {
|
||||
client := s.Vim25()
|
||||
optMgr := object.NewOptionManager(client, *client.ServiceContent.Setting)
|
||||
var opts []types.BaseOptionValue
|
||||
opts = append(opts, &types.OptionValue{
|
||||
Key: option,
|
||||
Value: value,
|
||||
})
|
||||
err := optMgr.Update(ctx, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error setting option %q: %s", option, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
65
vendor/github.com/vmware/vic/pkg/vsphere/optmanager/optmanager_test.go
generated
vendored
65
vendor/github.com/vmware/vic/pkg/vsphere/optmanager/optmanager_test.go
generated
vendored
@@ -1,65 +0,0 @@
|
||||
// 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 optmanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/govmomi/simulator"
|
||||
"github.com/vmware/vic/pkg/vsphere/test"
|
||||
)
|
||||
|
||||
func TestQueryOptionValue(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
model := simulator.VPX()
|
||||
defer model.Remove()
|
||||
err := model.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
server := model.Service.NewServer()
|
||||
defer server.Close()
|
||||
|
||||
s, err := test.SessionWithVPX(ctx, server.URL.String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Multiple value error
|
||||
optValue, err := QueryOptionValue(ctx, s, "")
|
||||
if err == nil {
|
||||
t.Fatal("expected multiple value error")
|
||||
}
|
||||
|
||||
// Invalid option
|
||||
optValue, err = QueryOptionValue(ctx, s, "foo-bar")
|
||||
if err == nil {
|
||||
t.Fatal("expected invalid query error")
|
||||
}
|
||||
|
||||
// Valid option
|
||||
adminOptKey := "config.vpxd.sso.default.admin"
|
||||
adminOptVal := "Administrator@vsphere.local"
|
||||
optValue, err = QueryOptionValue(ctx, s, "config.vpxd.sso.default.admin")
|
||||
if err != nil {
|
||||
t.Fatalf("expected nil error, got: %s", err)
|
||||
}
|
||||
if optValue != adminOptVal {
|
||||
t.Fatalf("expected value %s for query %q, got: %s", adminOptVal, adminOptKey, optValue)
|
||||
}
|
||||
}
|
||||
226
vendor/github.com/vmware/vic/pkg/vsphere/performance/host_metrics.go
generated
vendored
Normal file
226
vendor/github.com/vmware/vic/pkg/vsphere/performance/host_metrics.go
generated
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
// Copyright 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 performance
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/performance"
|
||||
"github.com/vmware/govmomi/property"
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
// cpuUsage measures the actively used CPU of the host, as a percentage of the total available CPU.
|
||||
cpuUsage = "cpu.usage.average"
|
||||
|
||||
// memActive measures the sum of all active metrics for all powered-on VMs plus vSphere services on the host.
|
||||
memActive = "mem.active.average"
|
||||
|
||||
// memConsumed measures the amount of machine memory used on the host, including vSphere services, VMkernel,
|
||||
// the service console and the total consumed memory metrics for all running VMs.
|
||||
memConsumed = "mem.consumed.average"
|
||||
|
||||
// memTotalCapacity measures the total amount of memory reservation used by and available for powered-on VMs
|
||||
// and vSphere services on the host.
|
||||
memTotalCapacity = "mem.totalCapacity.average"
|
||||
|
||||
// memOverhead measures the total of all overhead metrics for powered-on VMs, plus the overhead of running
|
||||
// vSphere services on the host.
|
||||
memOverhead = "mem.overhead.average"
|
||||
)
|
||||
|
||||
// HostMemory stores an ESXi host's memory metrics.
|
||||
type HostMemory struct {
|
||||
ActiveKB int64
|
||||
ConsumedKB int64
|
||||
OverheadKB int64
|
||||
TotalKB int64
|
||||
}
|
||||
|
||||
// HostCPU stores an ESXi host's CPU metrics.
|
||||
type HostCPU struct {
|
||||
UsagePercent float64
|
||||
}
|
||||
|
||||
// HostMetricsInfo stores an ESXi host's memory and CPU metrics.
|
||||
type HostMetricsInfo struct {
|
||||
Memory HostMemory
|
||||
CPU HostCPU
|
||||
}
|
||||
|
||||
// HostMetricsProvider returns CPU and memory metrics for all ESXi hosts in a cluster
|
||||
// via implementation of the MetricsProvider interface.
|
||||
type HostMetricsProvider struct {
|
||||
*vim25.Client
|
||||
}
|
||||
|
||||
// NewHostMetricsProvider returns a new instance of HostMetricsProvider.
|
||||
func NewHostMetricsProvider(c *vim25.Client) *HostMetricsProvider {
|
||||
return &HostMetricsProvider{Client: c}
|
||||
}
|
||||
|
||||
// GetMetricsForComputeResource gathers host metrics from the supplied compute resource.
|
||||
// Returned map is keyed on the host ManagedObjectReference in string form.
|
||||
func (h *HostMetricsProvider) GetMetricsForComputeResource(op trace.Operation, cr *object.ComputeResource) (map[string]*HostMetricsInfo, error) {
|
||||
// Gather hosts from the session cluster and then obtain their morefs.
|
||||
hosts, err := cr.Hosts(op)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to obtain host morefs from compute resource: %s", err)
|
||||
}
|
||||
|
||||
if hosts == nil {
|
||||
return nil, fmt.Errorf("no hosts found in compute resource")
|
||||
}
|
||||
|
||||
return h.GetMetricsForHosts(op, hosts)
|
||||
}
|
||||
|
||||
// GetMetricsForHosts returns metrics pertaining to supplied ESX hosts.
|
||||
// Returned map is keyed on the host ManagedObjectReference in string form.
|
||||
func (h *HostMetricsProvider) GetMetricsForHosts(op trace.Operation, hosts []*object.HostSystem) (map[string]*HostMetricsInfo, error) {
|
||||
if len(hosts) == 0 {
|
||||
return nil, fmt.Errorf("no hosts provided")
|
||||
}
|
||||
|
||||
if h.Client == nil {
|
||||
return nil, fmt.Errorf("client not set")
|
||||
}
|
||||
|
||||
// filter out hosts that are in maintenance mode or disconnected
|
||||
hosts, err := filterHosts(op, h.Client, hosts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
morefToHost := make(map[string]*object.HostSystem)
|
||||
morefs := make([]types.ManagedObjectReference, len(hosts))
|
||||
for i, host := range hosts {
|
||||
moref := host.Reference()
|
||||
morefToHost[moref.String()] = host
|
||||
morefs[i] = moref
|
||||
}
|
||||
|
||||
// Query CPU and memory metrics for the morefs.
|
||||
spec := types.PerfQuerySpec{
|
||||
Format: string(types.PerfFormatNormal),
|
||||
IntervalId: sampleInterval,
|
||||
}
|
||||
|
||||
counters := []string{cpuUsage, memActive, memConsumed, memTotalCapacity, memOverhead}
|
||||
perfMgr := performance.NewManager(h.Client)
|
||||
sample, err := perfMgr.SampleByName(op.Context, spec, counters, morefs)
|
||||
if err != nil {
|
||||
errStr := "unable to get metric sample: %s"
|
||||
op.Errorf(errStr, err)
|
||||
return nil, fmt.Errorf(errStr, err)
|
||||
}
|
||||
|
||||
results, err := perfMgr.ToMetricSeries(op.Context, sample)
|
||||
if err != nil {
|
||||
errStr := "unable to convert metric sample to metric series: %s"
|
||||
op.Errorf(errStr, err)
|
||||
return nil, fmt.Errorf(errStr, err)
|
||||
}
|
||||
|
||||
metrics := assembleMetrics(op, morefToHost, results)
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
// assembleMetrics processes the metric samples received from govmomi and returns a finalized metrics map
|
||||
// keyed by the hosts.
|
||||
func assembleMetrics(op trace.Operation, morefToHost map[string]*object.HostSystem,
|
||||
results []performance.EntityMetric) map[string]*HostMetricsInfo {
|
||||
metrics := make(map[string]*HostMetricsInfo)
|
||||
|
||||
for _, host := range morefToHost {
|
||||
metrics[host.Reference().String()] = &HostMetricsInfo{}
|
||||
}
|
||||
|
||||
for i := range results {
|
||||
res := results[i]
|
||||
host, exists := morefToHost[res.Entity.String()]
|
||||
if !exists {
|
||||
op.Warnf("moref %s does not exist in requested morefs, skipping", res.Entity.String())
|
||||
continue
|
||||
}
|
||||
|
||||
ref := host.Reference().String()
|
||||
// Process each value and assign it directly to the corresponding metric field
|
||||
// since there is only one sample.
|
||||
for _, v := range res.Value {
|
||||
|
||||
// We don't need to collect non-aggregate (non-empty Instance) metrics.
|
||||
if v.Instance != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(v.Value) == 0 {
|
||||
op.Warnf("metric %s for moref %s has no value, skipping", v.Name, res.Entity.String())
|
||||
continue
|
||||
}
|
||||
|
||||
switch v.Name {
|
||||
case cpuUsage:
|
||||
// Convert percent units from 1/100th of a percent (100 = 1%) to a human-readable percentage.
|
||||
metrics[ref].CPU.UsagePercent = float64(v.Value[0]) / 100.0
|
||||
case memActive:
|
||||
metrics[ref].Memory.ActiveKB = v.Value[0]
|
||||
case memConsumed:
|
||||
metrics[ref].Memory.ConsumedKB = v.Value[0]
|
||||
case memOverhead:
|
||||
metrics[ref].Memory.OverheadKB = v.Value[0]
|
||||
case memTotalCapacity:
|
||||
// Total capacity is in MB, convert to KB so as to have all memory values in KB.
|
||||
metrics[ref].Memory.TotalKB = v.Value[0] * 1024
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return metrics
|
||||
}
|
||||
|
||||
// filterHosts removes candidate hosts who are either disconnected or in maintenance mode.
|
||||
func filterHosts(op trace.Operation, client *vim25.Client, hosts []*object.HostSystem) ([]*object.HostSystem, error) {
|
||||
if len(hosts) == 0 {
|
||||
return nil, fmt.Errorf("no candidate hosts to filter check")
|
||||
}
|
||||
|
||||
props := []string{"summary.runtime"}
|
||||
refs := make([]types.ManagedObjectReference, 0, len(hosts))
|
||||
for _, h := range hosts {
|
||||
refs = append(refs, h.Reference())
|
||||
}
|
||||
|
||||
hs := make([]mo.HostSystem, 0, len(hosts))
|
||||
pc := property.DefaultCollector(client)
|
||||
err := pc.Retrieve(op, refs, props, &hs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := hosts[:0]
|
||||
for i, h := range hs {
|
||||
if h.Summary.Runtime.ConnectionState == types.HostSystemConnectionStateConnected && !h.Summary.Runtime.InMaintenanceMode {
|
||||
result = append(result, hosts[i])
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
30
vendor/github.com/vmware/vic/pkg/vsphere/performance/metrics_provider.go
generated
vendored
Normal file
30
vendor/github.com/vmware/vic/pkg/vsphere/performance/metrics_provider.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 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 performance
|
||||
|
||||
import (
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// MetricsProvider defines the interface for providing metrics.
|
||||
type MetricsProvider interface {
|
||||
// GetMetricsForComputeResource returns metrics for a particular compute resource. The metrics are
|
||||
// returned in a map of HostMetricsInfo keyed on host ManagedObjectReferences.
|
||||
GetMetricsForComputeResource(trace.Operation, *object.ComputeResource) (map[string]*HostMetricsInfo, error)
|
||||
|
||||
// GetMetricsForHosts returns metrics pertaining to supplied ESX hosts.
|
||||
GetMetricsForHosts(trace.Operation, []*object.HostSystem) (map[string]*HostMetricsInfo, error)
|
||||
}
|
||||
5
vendor/github.com/vmware/vic/pkg/vsphere/performance/vm_subscription.go
generated
vendored
5
vendor/github.com/vmware/vic/pkg/vsphere/performance/vm_subscription.go
generated
vendored
@@ -26,13 +26,12 @@ import (
|
||||
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/vm"
|
||||
)
|
||||
|
||||
// vmSubscription is a 1:1 relationship to a Virtual Machine and a
|
||||
// 1:M relationship to subscribers
|
||||
type vmSubscription struct {
|
||||
vm *vm.VirtualMachine
|
||||
vm *object.VirtualMachine
|
||||
id string
|
||||
|
||||
pub *pubsub.Publisher
|
||||
@@ -171,7 +170,7 @@ func newVMSubscription(op trace.Operation, session *session.Session, moref types
|
||||
}
|
||||
|
||||
sub := &vmSubscription{
|
||||
vm: vm.NewVirtualMachine(op.Context, session, moref),
|
||||
vm: object.NewVirtualMachine(session.Vim25(), moref),
|
||||
deviceInstanceToKey: make(map[string]string),
|
||||
}
|
||||
|
||||
|
||||
423
vendor/github.com/vmware/vic/pkg/vsphere/rbac/rbac.go
generated
vendored
423
vendor/github.com/vmware/vic/pkg/vsphere/rbac/rbac.go
generated
vendored
@@ -1,423 +0,0 @@
|
||||
// Copyright 2016-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 rbac
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
VCenter = iota
|
||||
DatacenterReadOnly
|
||||
Datacenter
|
||||
Cluster
|
||||
DatastoreFolder
|
||||
Datastore
|
||||
VSANDatastore
|
||||
Network
|
||||
Endpoint
|
||||
)
|
||||
|
||||
type NameToRef map[string]types.ManagedObjectReference
|
||||
|
||||
type AuthzManager struct {
|
||||
authzManager *object.AuthorizationManager
|
||||
client *vim25.Client
|
||||
resources map[int8]*Resource
|
||||
TargetRoles []types.AuthorizationRole
|
||||
RolePrefix string
|
||||
Principal string
|
||||
Config *Config
|
||||
}
|
||||
|
||||
type Resource struct {
|
||||
Type int8
|
||||
Propagate bool
|
||||
Role types.AuthorizationRole
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Resources []Resource
|
||||
}
|
||||
|
||||
type PermissionList []types.Permission
|
||||
|
||||
type ResourcePermission struct {
|
||||
RType int8
|
||||
Reference types.ManagedObjectReference
|
||||
Permission types.Permission
|
||||
}
|
||||
|
||||
func NewAuthzManager(ctx context.Context, client *vim25.Client) *AuthzManager {
|
||||
authManager := object.NewAuthorizationManager(client)
|
||||
mgr := &AuthzManager{
|
||||
client: client,
|
||||
authzManager: authManager,
|
||||
}
|
||||
return mgr
|
||||
}
|
||||
|
||||
func (am *AuthzManager) InitConfig(principal string, rolePrefix string, config *Config) {
|
||||
am.Principal = principal
|
||||
am.RolePrefix = rolePrefix
|
||||
am.Config = config
|
||||
am.initTargetRoles()
|
||||
am.initResourceMap()
|
||||
}
|
||||
|
||||
func (am *AuthzManager) CreateRoles(ctx context.Context) (int, error) {
|
||||
return am.createOrRepairRoles(ctx)
|
||||
}
|
||||
|
||||
func (am *AuthzManager) DeleteRoles(ctx context.Context) (int, error) {
|
||||
return am.deleteRoles(ctx)
|
||||
}
|
||||
|
||||
func (am *AuthzManager) RoleList(ctx context.Context) (object.AuthorizationRoleList, error) {
|
||||
return am.getRoleList(ctx)
|
||||
}
|
||||
|
||||
func (am *AuthzManager) IsPrincipalAnAdministrator(ctx context.Context) (bool, error) {
|
||||
// Check if the principal belongs to the Administrators group
|
||||
res, err := am.PrincipalBelongsToGroup(ctx, "Administrators")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if res {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Check if the principal has an Admin Role
|
||||
res, err = am.PrincipalHasRole(ctx, "Admin")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (am *AuthzManager) PrincipalBelongsToGroup(ctx context.Context, group string) (bool, error) {
|
||||
op := trace.FromContext(ctx, "PrincipalBelongsToGroup")
|
||||
|
||||
ref := *am.client.ServiceContent.UserDirectory
|
||||
|
||||
components := strings.Split(am.Principal, "@")
|
||||
var domain string
|
||||
name := components[0]
|
||||
if len(components) < 2 {
|
||||
domain = ""
|
||||
} else {
|
||||
domain = components[1]
|
||||
}
|
||||
|
||||
req := types.RetrieveUserGroups{
|
||||
This: ref,
|
||||
Domain: domain,
|
||||
SearchStr: name,
|
||||
ExactMatch: true,
|
||||
BelongsToGroup: group,
|
||||
FindUsers: true,
|
||||
FindGroups: false,
|
||||
}
|
||||
|
||||
results, err := methods.RetrieveUserGroups(ctx, am.client, &req)
|
||||
|
||||
// This is to work around a bug in vSphere, when AD is added to
|
||||
// the identity source list, the API returns Object Not Found,
|
||||
// In this case, we ignore the error and return false (BUG: 2037706)
|
||||
if err != nil && (isNotSupportedError(ctx, err) || isNotFoundError(ctx, err)) {
|
||||
op.Debugf("Received Error (%s) from PrincipalBelongsToGroup(), could not verify user %s is not a member of the Administrators group", err.Error(), am.Principal)
|
||||
op.Warnf("If ops-user (%s) belongs to the Administrators group, permissions on some resources might have been restricted", am.Principal)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
op.Debugf("Error from PrincipalBelongsToGroup: %s", err.Error())
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(results.Returnval) > 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (am *AuthzManager) PrincipalHasRole(ctx context.Context, roleName string) (bool, error) {
|
||||
// Build expected representation of the ops-user
|
||||
principal := strings.ToLower(am.Principal)
|
||||
|
||||
// Get role id for admin Role
|
||||
roleList, err := am.RoleList(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
role := roleList.ByName(roleName)
|
||||
|
||||
allPerms, err := am.authzManager.RetrieveAllPermissions(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, perm := range allPerms {
|
||||
if perm.RoleId != role.RoleId {
|
||||
continue
|
||||
}
|
||||
|
||||
fPrincipal := am.formatPrincipal(perm.Principal)
|
||||
if fPrincipal == principal {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (am *AuthzManager) GetPermissions(ctx context.Context,
|
||||
ref types.ManagedObjectReference) ([]types.Permission, error) {
|
||||
// Get current Permissions
|
||||
return am.authzManager.RetrieveEntityPermissions(ctx, ref, false)
|
||||
}
|
||||
|
||||
func (am *AuthzManager) AddPermission(ctx context.Context, ref types.ManagedObjectReference, resourceType int8, isGroup bool) (*ResourcePermission, error) {
|
||||
|
||||
resource := am.getResource(resourceType)
|
||||
if resource == nil {
|
||||
return nil, fmt.Errorf("cannot find resource of type %d", resourceType)
|
||||
}
|
||||
|
||||
// Collect the new roles, possibly cache the result in the Authz manager
|
||||
roleList, err := am.getRoleList(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Locate target role
|
||||
role := roleList.ByName(am.getRoleName(resource))
|
||||
if role == nil {
|
||||
return nil, fmt.Errorf("cannot find role: %s", resource.Role.Name)
|
||||
}
|
||||
|
||||
// Get current Permissions
|
||||
permissions, err := am.authzManager.RetrieveEntityPermissions(ctx, ref, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, permission := range permissions {
|
||||
if permission.Principal == am.Principal &&
|
||||
permission.RoleId == role.RoleId &&
|
||||
permission.Propagate == resource.Propagate {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// No match found, create new permission
|
||||
permission := types.Permission{
|
||||
Principal: am.Principal,
|
||||
RoleId: role.RoleId,
|
||||
Propagate: resource.Propagate,
|
||||
Group: isGroup,
|
||||
}
|
||||
|
||||
permissions = append(permissions, permission)
|
||||
|
||||
if err = am.authzManager.SetEntityPermissions(ctx, ref, permissions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourcePermission := &ResourcePermission{
|
||||
Permission: permission,
|
||||
Reference: ref,
|
||||
RType: resourceType,
|
||||
}
|
||||
|
||||
return resourcePermission, nil
|
||||
}
|
||||
|
||||
func (am *AuthzManager) createOrRepairRoles(ctx context.Context) (int, error) {
|
||||
// Get all the existing roles
|
||||
mgr := am.authzManager
|
||||
roleList, err := mgr.RoleList(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var count int
|
||||
for _, targetRole := range am.TargetRoles {
|
||||
foundRole := roleList.ByName(targetRole.Name)
|
||||
if foundRole != nil {
|
||||
isMod, err := am.checkAndRepairRole(ctx, &targetRole, foundRole)
|
||||
if isMod && err == nil {
|
||||
count++
|
||||
}
|
||||
} else {
|
||||
_, err = mgr.AddRole(ctx, targetRole.Name, targetRole.Privilege)
|
||||
if err == nil {
|
||||
count++
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (am *AuthzManager) deleteRoles(ctx context.Context) (int, error) {
|
||||
mgr := am.authzManager
|
||||
// Get all the existing roles
|
||||
roleList, err := mgr.RoleList(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var count int
|
||||
for _, targetRole := range am.TargetRoles {
|
||||
foundRole := roleList.ByName(targetRole.Name)
|
||||
if foundRole != nil {
|
||||
err = mgr.RemoveRole(ctx, foundRole.RoleId, true)
|
||||
if err == nil {
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (am *AuthzManager) getRoleList(ctx context.Context) (object.AuthorizationRoleList, error) {
|
||||
return am.authzManager.RoleList(ctx)
|
||||
}
|
||||
|
||||
func (am *AuthzManager) checkAndRepairRole(ctx context.Context, tRole *types.AuthorizationRole, fRole *types.AuthorizationRole) (bool, error) {
|
||||
mgr := am.authzManager
|
||||
// Check that the privileges list in Target Role is a subset of the list in Found role
|
||||
fSet := make(map[string]bool)
|
||||
for _, p := range fRole.Privilege {
|
||||
fSet[p] = true
|
||||
}
|
||||
|
||||
var isModified bool
|
||||
for _, p := range tRole.Privilege {
|
||||
if _, found := fSet[p]; !found {
|
||||
// Privilege not found
|
||||
// Add it to the found Role
|
||||
fRole.Privilege = append(fRole.Privilege, p)
|
||||
isModified = true
|
||||
}
|
||||
}
|
||||
|
||||
if !isModified {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Not a subset need to call go-vmomi to set the new privileges
|
||||
err := mgr.UpdateRole(ctx, fRole.RoleId, fRole.Name, fRole.Privilege)
|
||||
|
||||
return true, err
|
||||
}
|
||||
|
||||
func (am *AuthzManager) initTargetRoles() {
|
||||
count := len(am.Config.Resources)
|
||||
roles := make([]types.AuthorizationRole, 0, count)
|
||||
dSet := make(map[string]bool)
|
||||
for index, resource := range am.Config.Resources {
|
||||
name := am.getRoleName(&am.Config.Resources[index])
|
||||
// Discard duplicates
|
||||
if _, found := dSet[name]; !found {
|
||||
role := new(types.AuthorizationRole)
|
||||
*role = resource.Role
|
||||
role.Name = name
|
||||
dSet[name] = true
|
||||
roles = append(roles, *role)
|
||||
}
|
||||
}
|
||||
am.TargetRoles = roles
|
||||
}
|
||||
|
||||
func (am *AuthzManager) initResourceMap() {
|
||||
am.resources = make(map[int8]*Resource)
|
||||
for i, resource := range am.Config.Resources {
|
||||
am.resources[resource.Type] = &am.Config.Resources[i]
|
||||
}
|
||||
}
|
||||
|
||||
func (am *AuthzManager) getResource(resourceType int8) *Resource {
|
||||
resource, ok := am.resources[resourceType]
|
||||
if !ok {
|
||||
panic(errors.Errorf("Cannot find RBAC resource type: %d", resourceType))
|
||||
}
|
||||
return resource
|
||||
}
|
||||
|
||||
func (am *AuthzManager) formatPrincipal(principal string) string {
|
||||
components := strings.Split(principal, "\\")
|
||||
if len(components) != 2 {
|
||||
return strings.ToLower(principal)
|
||||
}
|
||||
ret := strings.ToLower(components[1]) + "@" + strings.ToLower(components[0])
|
||||
return ret
|
||||
}
|
||||
|
||||
func (am *AuthzManager) getRoleName(resource *Resource) string {
|
||||
switch resource.Type {
|
||||
case DatacenterReadOnly:
|
||||
return resource.Role.Name
|
||||
default:
|
||||
return am.RolePrefix + resource.Role.Name
|
||||
}
|
||||
}
|
||||
|
||||
func isNotSupportedError(ctx context.Context, err error) bool {
|
||||
op := trace.FromContext(ctx, "isNotSupportedError")
|
||||
|
||||
if soap.IsSoapFault(err) {
|
||||
vimFault := soap.ToSoapFault(err).VimFault()
|
||||
op.Debugf("Error type: %s", reflect.TypeOf(vimFault))
|
||||
|
||||
_, ok := soap.ToSoapFault(err).VimFault().(types.NotSupported)
|
||||
return ok
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func isNotFoundError(ctx context.Context, err error) bool {
|
||||
op := trace.FromContext(ctx, "isNotFoundError")
|
||||
|
||||
if soap.IsSoapFault(err) {
|
||||
vimFault := soap.ToSoapFault(err).VimFault()
|
||||
op.Debugf("Error type: %s", reflect.TypeOf(vimFault))
|
||||
|
||||
_, ok := soap.ToSoapFault(err).VimFault().(types.NotFound)
|
||||
return ok
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
240
vendor/github.com/vmware/vic/pkg/vsphere/rbac/rbac_test.go
generated
vendored
240
vendor/github.com/vmware/vic/pkg/vsphere/rbac/rbac_test.go
generated
vendored
@@ -1,240 +0,0 @@
|
||||
// Copyright 2016-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 rbac
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/vmware/govmomi/simulator"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/test/env"
|
||||
)
|
||||
|
||||
var Role1 = types.AuthorizationRole{
|
||||
Name: "vcenter",
|
||||
Privilege: []string{
|
||||
"Datastore.Config",
|
||||
},
|
||||
}
|
||||
|
||||
var Role2 = types.AuthorizationRole{
|
||||
Name: "datacenter",
|
||||
Privilege: []string{
|
||||
"Datastore.Config",
|
||||
"Datastore.FileManagement",
|
||||
"VirtualMachine.Config.AddNewDisk",
|
||||
"VirtualMachine.Config.AdvancedConfig",
|
||||
"VirtualMachine.Config.RemoveDisk",
|
||||
"VirtualMachine.Inventory.Create",
|
||||
"VirtualMachine.Inventory.Delete",
|
||||
},
|
||||
}
|
||||
|
||||
var Role3 = types.AuthorizationRole{
|
||||
Name: "cluster",
|
||||
Privilege: []string{
|
||||
"Datastore.AllocateSpace",
|
||||
"Datastore.Browse",
|
||||
"Datastore.Config",
|
||||
"Datastore.DeleteFile",
|
||||
"Datastore.FileManagement",
|
||||
"Host.Config.SystemManagement",
|
||||
},
|
||||
}
|
||||
|
||||
// Configuration for the ops-user
|
||||
var testRBACConfig = Config{
|
||||
Resources: []Resource{
|
||||
{
|
||||
Type: VCenter,
|
||||
Propagate: false,
|
||||
Role: Role1,
|
||||
},
|
||||
{
|
||||
Type: Datacenter,
|
||||
Propagate: true,
|
||||
Role: Role2,
|
||||
},
|
||||
{
|
||||
Type: Cluster,
|
||||
Propagate: true,
|
||||
Role: Role3,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var testRolePrefix = "test-role-prefix"
|
||||
var testUser = "test-user"
|
||||
|
||||
func TestRolesSimulatorVPX(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
m := simulator.VPX()
|
||||
defer m.Remove()
|
||||
|
||||
err := m.Create()
|
||||
require.NoError(t, err, "Cannot create VPX Simulator")
|
||||
|
||||
s := m.Service.NewServer()
|
||||
defer s.Close()
|
||||
|
||||
config := &session.Config{
|
||||
Service: s.URL.String(),
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
}
|
||||
|
||||
sess, err := session.NewSession(config).Connect(ctx)
|
||||
require.NoError(t, err, "Cannot connect to VPX Simulator")
|
||||
|
||||
am := NewAuthzManager(ctx, sess.Vim25())
|
||||
am.InitConfig(testUser, testRolePrefix, &testRBACConfig)
|
||||
|
||||
var testRoleNames = []string{
|
||||
"datacenter",
|
||||
"cluster",
|
||||
}
|
||||
|
||||
var testRolePrivileges = []string{
|
||||
"VirtualMachine.Config.AddNewDisk",
|
||||
"Host.Config.SystemManagement",
|
||||
}
|
||||
|
||||
DoTestRoles(ctx, t, am, testRoleNames, testRolePrivileges)
|
||||
}
|
||||
|
||||
func TestRolesVCenter(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
config := &session.Config{
|
||||
Service: env.URL(t),
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
}
|
||||
|
||||
sess, err := session.NewSession(config).Connect(ctx)
|
||||
if err != nil {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
am := NewAuthzManager(ctx, sess.Vim25())
|
||||
am.InitConfig(testUser, testRolePrefix, &testRBACConfig)
|
||||
|
||||
var testRoleNames = []string{
|
||||
"datacenter",
|
||||
"cluster",
|
||||
}
|
||||
|
||||
var testRolePrivileges = []string{
|
||||
"VirtualMachine.Config.AddNewDisk",
|
||||
"Host.Config.SystemManagement",
|
||||
}
|
||||
|
||||
DoTestRoles(ctx, t, am, testRoleNames, testRolePrivileges)
|
||||
}
|
||||
|
||||
func TestAdminSimulatorVPX(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
m := simulator.VPX()
|
||||
defer m.Remove()
|
||||
|
||||
err := m.Create()
|
||||
require.NoError(t, err, "Cannot create VPX Simulator")
|
||||
|
||||
s := m.Service.NewServer()
|
||||
defer s.Close()
|
||||
|
||||
config := &session.Config{
|
||||
Service: s.URL.String(),
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
}
|
||||
|
||||
sess, err := session.NewSession(config).Connect(ctx)
|
||||
require.NoError(t, err, "Cannot connect to VPX Simulator")
|
||||
|
||||
am := NewAuthzManager(ctx, sess.Vim25())
|
||||
am.InitConfig("admin", "test-role-prefix", &testRBACConfig)
|
||||
|
||||
// Unfortunately the Sim does not have support for looking up group membership
|
||||
// therefore we can only test the presence of the Admin role
|
||||
|
||||
res, err := am.PrincipalHasRole(ctx, "Admin")
|
||||
require.NoError(t, err, "Failed to verify Admin Privileges")
|
||||
require.True(t, res, "User Administrator@vsphere.local should have an Admin role")
|
||||
|
||||
// Negative test, principal does not have that role
|
||||
res, err = am.PrincipalHasRole(ctx, "NoAccess")
|
||||
require.NoError(t, err, "Failed to verify Admin Privileges")
|
||||
require.False(t, res, "User Administrator@vsphere.local should have an NoAccess role")
|
||||
|
||||
// Check regular user
|
||||
am.Principal = "nouser@vshpere.local"
|
||||
res, err = am.PrincipalHasRole(ctx, "Admin")
|
||||
require.NoError(t, err, "Failed to verify Admin Privileges")
|
||||
require.False(t, res, "User nouser@vsphere.local should not have an Admin role")
|
||||
}
|
||||
|
||||
func TestAdminVCenter(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
config := &session.Config{
|
||||
Service: env.URL(t),
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
}
|
||||
|
||||
sess, err := session.NewSession(config).Connect(ctx)
|
||||
if err != nil {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
am := NewAuthzManager(ctx, sess.Vim25())
|
||||
am.InitConfig("Administrator@vsphere.local", "test-role-prefix", &testRBACConfig)
|
||||
|
||||
res, err := am.PrincipalBelongsToGroup(ctx, "Administrators")
|
||||
require.NoError(t, err, "Failed to verify Admin Privileges")
|
||||
require.True(t, res, "User Administrator@vsphere.local should be a member of Administrators")
|
||||
|
||||
res, err = am.PrincipalHasRole(ctx, "Admin")
|
||||
require.NoError(t, err, "Failed to verify Admin Privileges")
|
||||
require.True(t, res, "User Administrator@vsphere.local should have an Admin role")
|
||||
|
||||
// Negative test, principal does not belong
|
||||
res, err = am.PrincipalBelongsToGroup(ctx, "TestUsers")
|
||||
require.NoError(t, err, "Failed to verify Admin Privileges")
|
||||
require.False(t, res, "User Administrator@vsphere.local should not be a member of TestUsers")
|
||||
|
||||
// Negative test, principal does not have that role
|
||||
res, err = am.PrincipalHasRole(ctx, "NoAccess")
|
||||
require.NoError(t, err, "Failed to verify Admin Privileges")
|
||||
require.False(t, res, "User Administrator@vsphere.local should have an NoAccess role")
|
||||
|
||||
// Check regular user
|
||||
am.Principal = "nouser@vshpere.local"
|
||||
res, err = am.PrincipalHasRole(ctx, "Admin")
|
||||
require.NoError(t, err, "Failed to verify Admin Privileges")
|
||||
require.False(t, res, "User nouser@vsphere.local should not have an Admin role")
|
||||
|
||||
// Check regular user
|
||||
am.Principal = "nouser"
|
||||
res, err = am.PrincipalHasRole(ctx, "Admin")
|
||||
require.NoError(t, err, "Failed to verify Admin Privileges")
|
||||
require.False(t, res, "User nouser@vsphere.local should not have an Admin role")
|
||||
}
|
||||
120
vendor/github.com/vmware/vic/pkg/vsphere/rbac/rbac_test_util.go
generated
vendored
120
vendor/github.com/vmware/vic/pkg/vsphere/rbac/rbac_test_util.go
generated
vendored
@@ -1,120 +0,0 @@
|
||||
// Copyright 2016-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 rbac
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
func DoTestRoles(ctx context.Context, t *testing.T, am *AuthzManager, testRoleNames []string, testRolePrivileges []string) {
|
||||
var roleCount = len(am.TargetRoles)
|
||||
count := InitRoles(ctx, t, am)
|
||||
|
||||
defer Cleanup(ctx, t, am, true)
|
||||
require.Equal(t, roleCount, count, "Incorrect number of roles: expected %d, actual %d", roleCount, count)
|
||||
|
||||
// Test correct role validation, it should return 0
|
||||
roleCount = 0
|
||||
count, err := am.createOrRepairRoles(ctx)
|
||||
require.NoError(t, err, "Failed to create roles")
|
||||
require.Equal(t, roleCount, count, "Incorrect number of roles: expected %d, actual %d", roleCount, count)
|
||||
|
||||
// Remove two Privileges from two roles
|
||||
roles, err := am.getRoleList(ctx)
|
||||
fmt.Println(err)
|
||||
fmt.Println(roles)
|
||||
|
||||
for i, name := range testRoleNames {
|
||||
testRoleNames[i] = am.RolePrefix + name
|
||||
}
|
||||
|
||||
for _, role := range roles {
|
||||
if role.Name == testRoleNames[0] {
|
||||
removePrivilege(&role, testRolePrivileges[0])
|
||||
am.authzManager.UpdateRole(ctx, role.RoleId, role.Name, role.Privilege)
|
||||
}
|
||||
if role.Name == testRoleNames[1] {
|
||||
removePrivilege(&role, testRolePrivileges[1])
|
||||
am.authzManager.UpdateRole(ctx, role.RoleId, role.Name, role.Privilege)
|
||||
}
|
||||
}
|
||||
|
||||
// Test
|
||||
roleCount = 2
|
||||
count, err = am.createOrRepairRoles(ctx)
|
||||
require.NoError(t, err, "Failed to repair roles 1")
|
||||
require.Equal(t, roleCount, count, "Incorrect number of roles: expected %d, actual %d", roleCount, count)
|
||||
|
||||
// Test correct role validation, it should return 0
|
||||
roleCount = 0
|
||||
count, err = am.createOrRepairRoles(ctx)
|
||||
require.NoError(t, err, "Failed to repair roles 2")
|
||||
require.Equal(t, roleCount, count, "Incorrect number of roles: expected %d, actual %d", roleCount, count)
|
||||
}
|
||||
|
||||
func VerifyResourcePermissions(ctx context.Context, t *testing.T, am *AuthzManager, retPerms []ResourcePermission) {
|
||||
for _, retPerm := range retPerms {
|
||||
|
||||
// Validate returned permission against the configured permission
|
||||
configPerm := am.getResource(retPerm.RType)
|
||||
require.Equal(t, am.Principal, retPerm.Permission.Principal)
|
||||
require.Equal(t, configPerm.Propagate, retPerm.Permission.Propagate)
|
||||
|
||||
actPerms, err := am.GetPermissions(ctx, retPerm.Reference)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, actPerm := range actPerms {
|
||||
if actPerm.Principal != am.Principal {
|
||||
continue
|
||||
}
|
||||
// RoleId must be the same
|
||||
require.Equal(t, retPerm.Permission.RoleId, actPerm.RoleId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func InitRoles(ctx context.Context, t *testing.T, am *AuthzManager) int {
|
||||
Cleanup(ctx, t, am, false)
|
||||
|
||||
count, err := am.createOrRepairRoles(ctx)
|
||||
require.NoError(t, err, "Failed to initialize Roles")
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func Cleanup(ctx context.Context, t *testing.T, am *AuthzManager, checkCount bool) {
|
||||
var roleCount = len(am.TargetRoles)
|
||||
count, err := am.deleteRoles(ctx)
|
||||
require.NoError(t, err, "Failed to delete roles")
|
||||
|
||||
if checkCount && count != roleCount {
|
||||
t.Fatalf("Incorrect number of roles: expcted %d, actual %d", roleCount, count)
|
||||
}
|
||||
}
|
||||
|
||||
func removePrivilege(role *types.AuthorizationRole, privilege string) {
|
||||
for i, priv := range role.Privilege {
|
||||
if priv == privilege {
|
||||
role.Privilege = append(role.Privilege[:i], role.Privilege[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
36
vendor/github.com/vmware/vic/pkg/vsphere/session/session.go
generated
vendored
36
vendor/github.com/vmware/vic/pkg/vsphere/session/session.go
generated
vendored
@@ -1,4 +1,4 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
// Copyright 2016-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.
|
||||
@@ -91,9 +91,14 @@ type Session struct {
|
||||
Host *object.HostSystem
|
||||
Pool *object.ResourcePool
|
||||
|
||||
// Default vSphere VMFolder
|
||||
VMFolder *object.Folder
|
||||
// Folder where appliance is located
|
||||
VCHFolder *object.Folder
|
||||
|
||||
Finder *find.Finder
|
||||
|
||||
DRSEnabled *bool
|
||||
}
|
||||
|
||||
// RoundTripFunc alias
|
||||
@@ -122,9 +127,7 @@ func LimitConcurrency(rt http.RoundTripper, limit int) http.RoundTripper {
|
||||
})
|
||||
}
|
||||
|
||||
// NewSession creates a new Session struct. If config is nil,
|
||||
// it creates a Flags object from the command line arguments or
|
||||
// environment, and uses that instead to create a Session.
|
||||
// NewSession creates a new Session struct.
|
||||
func NewSession(config *Config) *Session {
|
||||
return &Session{Config: config}
|
||||
}
|
||||
@@ -299,9 +302,12 @@ func (s *Session) Connect(ctx context.Context) (*Session, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Populate resolves the set of cached resources that should be presented
|
||||
// This returns accumulated error detail if there is ambiguity, but sets all
|
||||
// unambiguous or correct resources.
|
||||
// Populate caches resources on the session object. These resources
|
||||
// are based off of the config provided at session creation.
|
||||
//
|
||||
// vic specific:
|
||||
// The values that end in Path (DataCenterPath, ClusterPath, etc..) are
|
||||
// either from the CLI or have been retreived from the appliance extraConfig
|
||||
func (s *Session) Populate(ctx context.Context) (*Session, error) {
|
||||
op := trace.FromContext(ctx, "Populate")
|
||||
|
||||
@@ -327,6 +333,14 @@ func (s *Session) Populate(ctx context.Context) (*Session, error) {
|
||||
errs = append(errs, fmt.Sprintf("Failure finding cluster (%s): %s", s.ClusterPath, err.Error()))
|
||||
} else {
|
||||
op.Debugf("Cached cluster: %s", s.ClusterPath)
|
||||
// if we have a cluster lets get DRS Status
|
||||
if s.Cluster != nil && s.Cluster.Reference().Type == "ClusterComputeResource" {
|
||||
cc := object.NewClusterComputeResource(s.Client.Client, s.Cluster.Reference())
|
||||
clusterConfig, _ := cc.Configuration(op)
|
||||
if clusterConfig != nil {
|
||||
s.DRSEnabled = clusterConfig.DrsConfig.Enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.Datastore, err = finder.DatastoreOrDefault(op, s.DatastorePath)
|
||||
@@ -360,6 +374,14 @@ func (s *Session) Populate(ctx context.Context) (*Session, error) {
|
||||
op.Debugf("Cached folders: %s", s.DatacenterPath)
|
||||
}
|
||||
s.VMFolder = folders.VmFolder
|
||||
// We don't persist the VCH folder location so set
|
||||
// the VCH folder to the default VM folder.
|
||||
// The actual location of the VCH will be determined later
|
||||
// and this folder ref will be updated accordingly.
|
||||
//
|
||||
// This will provide standalone ESXi and backwards
|
||||
// compatibility to non-folder versions.
|
||||
s.VCHFolder = folders.VmFolder
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
|
||||
186
vendor/github.com/vmware/vic/pkg/vsphere/session/session_test.go
generated
vendored
186
vendor/github.com/vmware/vic/pkg/vsphere/session/session_test.go
generated
vendored
@@ -1,186 +0,0 @@
|
||||
// Copyright 2016-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 session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/find"
|
||||
"github.com/vmware/govmomi/simulator"
|
||||
"github.com/vmware/vic/pkg/vsphere/test/env"
|
||||
)
|
||||
|
||||
func TestSessionDefaults(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
config := &Config{
|
||||
Service: env.URL(t),
|
||||
Insecure: true,
|
||||
}
|
||||
|
||||
session, err := NewSession(config).Create(ctx)
|
||||
if err != nil {
|
||||
eStr := err.Error()
|
||||
t.Logf("%+v", eStr)
|
||||
// FIXME: See comments below
|
||||
if strings.Contains(eStr, "resolves to multiple hosts") {
|
||||
t.SkipNow()
|
||||
}
|
||||
t.Logf("%+v", eStr)
|
||||
if _, ok := err.(*find.DefaultMultipleFoundError); !ok {
|
||||
t.Errorf(eStr)
|
||||
} else {
|
||||
t.SkipNow()
|
||||
}
|
||||
}
|
||||
if session != nil {
|
||||
defer session.Logout(ctx)
|
||||
}
|
||||
|
||||
t.Logf("%+v", session)
|
||||
}
|
||||
|
||||
func TestSession(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
config := &Config{
|
||||
Service: env.URL(t),
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
DatacenterPath: "",
|
||||
DatastorePath: "/ha-datacenter/datastore/*",
|
||||
HostPath: "/ha-datacenter/host/*/*",
|
||||
PoolPath: "/ha-datacenter/host/*/Resources",
|
||||
}
|
||||
|
||||
session, err := NewSession(config).Create(ctx)
|
||||
if err != nil {
|
||||
eStr := err.Error()
|
||||
t.Logf("%+v", eStr)
|
||||
// FIXME: session.Create incorporates Populate which loses the type of any original error from vmomi
|
||||
// In the case where the test is run on a cluster with multiple hosts, find.MultipleFoundError
|
||||
// gets rolled up into a generic error in Populate. As such, the best we can do is just grep for the string, which is lame
|
||||
// The test shouldn't fail if it's run on a cluster with multiple hosts. However, it won't test for anything either.
|
||||
if strings.Contains(eStr, "resolves to multiple hosts") {
|
||||
t.SkipNow()
|
||||
}
|
||||
if _, ok := err.(*find.MultipleFoundError); !ok {
|
||||
t.Errorf(eStr)
|
||||
} else {
|
||||
t.SkipNow()
|
||||
}
|
||||
}
|
||||
|
||||
if session != nil {
|
||||
defer session.Logout(ctx)
|
||||
|
||||
t.Logf("Session: %+v", session)
|
||||
|
||||
t.Logf("IsVC: %t", session.IsVC())
|
||||
t.Logf("IsVSAN: %t", session.IsVSAN(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFolder(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
config := &Config{
|
||||
Service: env.URL(t),
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
DatacenterPath: "",
|
||||
DatastorePath: "/ha-datacenter/datastore/*",
|
||||
HostPath: "/ha-datacenter/host/*/*",
|
||||
PoolPath: "/ha-datacenter/host/*/Resources",
|
||||
}
|
||||
|
||||
session, err := NewSession(config).Create(ctx)
|
||||
if err != nil {
|
||||
eStr := err.Error()
|
||||
t.Logf("%+v", eStr)
|
||||
// FIXME: See comments above
|
||||
if strings.Contains(eStr, "resolves to multiple hosts") {
|
||||
t.SkipNow()
|
||||
}
|
||||
if _, ok := err.(*find.MultipleFoundError); !ok {
|
||||
t.Errorf(eStr)
|
||||
} else {
|
||||
t.SkipNow()
|
||||
}
|
||||
}
|
||||
|
||||
if session != nil {
|
||||
defer session.Logout(ctx)
|
||||
|
||||
if session.VMFolder == nil {
|
||||
t.Errorf("Get empty folder")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnect(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
for _, model := range []*simulator.Model{simulator.ESX(), simulator.VPX()} {
|
||||
defer model.Remove()
|
||||
err := model.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
model.Service.TLS = new(tls.Config)
|
||||
s := model.Service.NewServer()
|
||||
defer s.Close()
|
||||
|
||||
config := &Config{
|
||||
Keepalive: time.Minute,
|
||||
Service: s.URL.String(),
|
||||
}
|
||||
|
||||
for _, thumbprint := range []string{"", s.CertificateInfo().ThumbprintSHA1} {
|
||||
u := *s.URL
|
||||
config.Service = u.String()
|
||||
config.Thumbprint = thumbprint
|
||||
|
||||
_, err = NewSession(config).Connect(ctx)
|
||||
if thumbprint == "" {
|
||||
if err == nil {
|
||||
t.Error("expected x509.UnknownAuthorityError error")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
u.User = nil
|
||||
config.Service = u.String()
|
||||
_, err = NewSession(config).Connect(ctx)
|
||||
if err == nil {
|
||||
t.Fatal("expected login error")
|
||||
}
|
||||
|
||||
config.Service = ""
|
||||
_, err = NewSession(config).Connect(ctx)
|
||||
if err == nil {
|
||||
t.Fatal("expected URL parse error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
212
vendor/github.com/vmware/vic/pkg/vsphere/tags/categories.go
generated
vendored
212
vendor/github.com/vmware/vic/pkg/vsphere/tags/categories.go
generated
vendored
@@ -1,212 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tags
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
CategoryURL = "/com/vmware/cis/tagging/category"
|
||||
ErrAlreadyExists = "already_exists"
|
||||
)
|
||||
|
||||
type CategoryCreateSpec struct {
|
||||
CreateSpec CategoryCreate `json:"create_spec"`
|
||||
}
|
||||
|
||||
type CategoryUpdateSpec struct {
|
||||
UpdateSpec CategoryUpdate `json:"update_spec"`
|
||||
}
|
||||
|
||||
type CategoryCreate struct {
|
||||
AssociableTypes []string `json:"associable_types"`
|
||||
Cardinality string `json:"cardinality"`
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type CategoryUpdate struct {
|
||||
AssociableTypes []string `json:"associable_types"`
|
||||
Cardinality string `json:"cardinality"`
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Category struct {
|
||||
ID string `json:"id"`
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
Cardinality string `json:"cardinality"`
|
||||
AssociableTypes []string `json:"associable_types"`
|
||||
UsedBy []string `json:"used_by"`
|
||||
}
|
||||
|
||||
func (c *RestClient) CreateCategoryIfNotExist(ctx context.Context, name string, description string, categoryType string, multiValue bool) (*string, error) {
|
||||
categories, err := c.GetCategoriesByName(ctx, name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to query category for %s", name)
|
||||
}
|
||||
|
||||
if categories == nil {
|
||||
var multiValueStr string
|
||||
if multiValue {
|
||||
multiValueStr = "MULTIPLE"
|
||||
} else {
|
||||
multiValueStr = "SINGLE"
|
||||
}
|
||||
categoryCreate := CategoryCreate{[]string{categoryType}, multiValueStr, description, name}
|
||||
spec := CategoryCreateSpec{categoryCreate}
|
||||
id, err := c.CreateCategory(ctx, &spec)
|
||||
if err != nil {
|
||||
// in case there are two docker daemon try to create inventory category, query the category once again
|
||||
if strings.Contains(err.Error(), "ErrAlreadyExists") {
|
||||
if categories, err = c.GetCategoriesByName(ctx, name); err != nil {
|
||||
Logger.Debugf("Failed to get inventory category for %s", errors.WithStack(err))
|
||||
return nil, errors.Wrap(err, "create inventory category failed")
|
||||
}
|
||||
} else {
|
||||
Logger.Debugf("Failed to create inventory category for %s", errors.WithStack(err))
|
||||
return nil, errors.Wrap(err, "create inventory category failed")
|
||||
}
|
||||
} else {
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
if categories != nil {
|
||||
return &categories[0].ID, nil
|
||||
}
|
||||
// should not happen
|
||||
Logger.Debugf("Failed to create inventory for it's existed, but could not query back. Please check system")
|
||||
return nil, errors.Errorf("Failed to create inventory for it's existed, but could not query back. Please check system")
|
||||
}
|
||||
|
||||
func (c *RestClient) CreateCategory(ctx context.Context, spec *CategoryCreateSpec) (*string, error) {
|
||||
Logger.Debugf("Create category %v", spec)
|
||||
stream, _, status, err := c.call(ctx, "POST", CategoryURL, spec, nil)
|
||||
|
||||
Logger.Debugf("Get status code: %d", status)
|
||||
if status != http.StatusOK || err != nil {
|
||||
Logger.Debugf("Create category failed with status code: %d, error message: %s", status, errors.WithStack(err))
|
||||
return nil, errors.Wrapf(err, "Status code: %d", status)
|
||||
}
|
||||
|
||||
type RespValue struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
var pID RespValue
|
||||
if err := json.NewDecoder(stream).Decode(&pID); err != nil {
|
||||
Logger.Debugf("Decode response body failed for: %s", errors.WithStack(err))
|
||||
return nil, errors.Wrap(err, "create category failed")
|
||||
}
|
||||
return &(pID.Value), nil
|
||||
}
|
||||
|
||||
func (c *RestClient) GetCategory(ctx context.Context, id string) (*Category, error) {
|
||||
Logger.Debugf("Get category %s", id)
|
||||
|
||||
stream, _, status, err := c.call(ctx, "GET", fmt.Sprintf("%s/id:%s", CategoryURL, id), nil, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
Logger.Debugf("Get category failed with status code: %s, error message: %s", status, errors.WithStack(err))
|
||||
return nil, errors.Errorf("Status code: %d, error: %s", status, err)
|
||||
}
|
||||
|
||||
type RespValue struct {
|
||||
Value Category
|
||||
}
|
||||
|
||||
var pCategory RespValue
|
||||
if err := json.NewDecoder(stream).Decode(&pCategory); err != nil {
|
||||
Logger.Debugf("Decode response body failed for: %s", errors.WithStack(err))
|
||||
return nil, errors.Wrapf(err, "get category %s failed", id)
|
||||
}
|
||||
return &(pCategory.Value), nil
|
||||
}
|
||||
|
||||
func (c *RestClient) UpdateCategory(ctx context.Context, id string, spec *CategoryUpdateSpec) error {
|
||||
Logger.Debugf("Update category %v", spec)
|
||||
_, _, status, err := c.call(ctx, "PATCH", fmt.Sprintf("%s/id:%s", CategoryURL, id), spec, nil)
|
||||
|
||||
Logger.Debugf("Get status code: %d", status)
|
||||
if status != http.StatusOK || err != nil {
|
||||
Logger.Debugf("Update category failed with status code: %d, error message: %s", status, errors.WithStack(err))
|
||||
return errors.Wrapf(err, "Status code: %d", status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RestClient) DeleteCategory(ctx context.Context, id string) error {
|
||||
Logger.Debugf("Delete category %s", id)
|
||||
|
||||
_, _, status, err := c.call(ctx, "DELETE", fmt.Sprintf("%s/id:%s", CategoryURL, id), nil, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
Logger.Debugf("Delete category failed with status code: %s, error message: %s", status, errors.WithStack(err))
|
||||
return errors.Errorf("Status code: %d, error: %s", status, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RestClient) ListCategories(ctx context.Context) ([]string, error) {
|
||||
Logger.Debugf("List all categories")
|
||||
|
||||
stream, _, status, err := c.call(ctx, "GET", CategoryURL, nil, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
Logger.Debugf("Get categories failed with status code: %s, error message: %s", status, errors.WithStack(err))
|
||||
return nil, errors.Errorf("Status code: %d, error: %s", status, err)
|
||||
}
|
||||
|
||||
type Categories struct {
|
||||
Value []string
|
||||
}
|
||||
|
||||
var pCategories Categories
|
||||
if err := json.NewDecoder(stream).Decode(&pCategories); err != nil {
|
||||
Logger.Debugf("Decode response body failed for: %s", errors.WithStack(err))
|
||||
return nil, errors.Wrap(err, "list categories failed")
|
||||
}
|
||||
return pCategories.Value, nil
|
||||
}
|
||||
|
||||
func (c *RestClient) GetCategoriesByName(ctx context.Context, name string) ([]Category, error) {
|
||||
Logger.Debugf("Get category %s", name)
|
||||
categoryIds, err := c.ListCategories(ctx)
|
||||
if err != nil {
|
||||
Logger.Debugf("Get category failed for: %s", errors.WithStack(err))
|
||||
return nil, errors.Wrapf(err, "get categories by name %s failed", name)
|
||||
}
|
||||
|
||||
var categories []Category
|
||||
for _, cID := range categoryIds {
|
||||
category, err := c.GetCategory(ctx, cID)
|
||||
if err != nil {
|
||||
Logger.Debugf("Get category %s failed for %s", cID, errors.WithStack(err))
|
||||
}
|
||||
if category.Name == name {
|
||||
categories = append(categories, *category)
|
||||
}
|
||||
}
|
||||
return categories, nil
|
||||
}
|
||||
202
vendor/github.com/vmware/vic/pkg/vsphere/tags/rest_client.go
generated
vendored
202
vendor/github.com/vmware/vic/pkg/vsphere/tags/rest_client.go
generated
vendored
@@ -1,202 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tags
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
)
|
||||
|
||||
const (
|
||||
RestPrefix = "/rest"
|
||||
loginURL = "/com/vmware/cis/session"
|
||||
)
|
||||
|
||||
type RestClient struct {
|
||||
mu sync.Mutex
|
||||
host string
|
||||
scheme string
|
||||
endpoint *url.URL
|
||||
user *url.Userinfo
|
||||
HTTP *http.Client
|
||||
cookies []*http.Cookie
|
||||
}
|
||||
|
||||
func NewClient(u *url.URL, insecure bool, thumbprint string) *RestClient {
|
||||
endpoint := &url.URL{}
|
||||
*endpoint = *u
|
||||
Logger.Debugf("Create rest client")
|
||||
endpoint.Path = RestPrefix
|
||||
|
||||
sc := soap.NewClient(endpoint, insecure)
|
||||
if thumbprint != "" {
|
||||
sc.SetThumbprint(endpoint.Host, thumbprint)
|
||||
}
|
||||
|
||||
user := endpoint.User
|
||||
endpoint.User = nil
|
||||
|
||||
return &RestClient{
|
||||
endpoint: endpoint,
|
||||
user: user,
|
||||
host: endpoint.Host,
|
||||
scheme: endpoint.Scheme,
|
||||
HTTP: &sc.Client,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RestClient) encodeData(data interface{}) (*bytes.Buffer, error) {
|
||||
params := bytes.NewBuffer(nil)
|
||||
if data != nil {
|
||||
if err := json.NewEncoder(params).Encode(data); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to encode json data")
|
||||
}
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (c *RestClient) call(ctx context.Context, method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
|
||||
// Logger.Debugf("%s: %s, headers: %+v", method, path, headers)
|
||||
params, err := c.encodeData(data)
|
||||
if err != nil {
|
||||
return nil, nil, -1, errors.Wrap(err, "call failed")
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
if headers == nil {
|
||||
headers = make(map[string][]string)
|
||||
}
|
||||
headers["Content-Type"] = []string{"application/json"}
|
||||
}
|
||||
|
||||
body, hdr, statusCode, err := c.clientRequest(ctx, method, path, params, headers)
|
||||
if statusCode == http.StatusUnauthorized && strings.Contains(err.Error(), "This method requires authentication") {
|
||||
c.Login(ctx)
|
||||
Logger.Debugf("Rerun request after login")
|
||||
return c.clientRequest(ctx, method, path, params, headers)
|
||||
}
|
||||
|
||||
return body, hdr, statusCode, errors.Wrap(err, "call failed")
|
||||
}
|
||||
|
||||
func (c *RestClient) clientRequest(ctx context.Context, method, path string, in io.Reader, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
|
||||
expectedPayload := (method == "POST" || method == "PUT")
|
||||
if expectedPayload && in == nil {
|
||||
in = bytes.NewReader([]byte{})
|
||||
}
|
||||
|
||||
req, err := c.newRequest(method, path, in)
|
||||
if err != nil {
|
||||
return nil, nil, -1, errors.Wrap(err, "failed to create request")
|
||||
}
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
c.mu.Lock()
|
||||
if c.cookies != nil {
|
||||
req.AddCookie(c.cookies[0])
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
if headers != nil {
|
||||
for k, v := range headers {
|
||||
req.Header[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if expectedPayload && req.Header.Get("Content-Type") == "" {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
resp, err := c.HTTP.Do(req)
|
||||
return c.handleResponse(resp, err)
|
||||
}
|
||||
|
||||
func (c *RestClient) handleResponse(resp *http.Response, err error) (io.ReadCloser, http.Header, int, error) {
|
||||
statusCode := -1
|
||||
if resp != nil {
|
||||
statusCode = resp.StatusCode
|
||||
}
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return nil, nil, statusCode, errors.Errorf("Cannot connect to endpoint %s. Is vCloud Suite API running on this server?", c.host)
|
||||
}
|
||||
return nil, nil, statusCode, errors.Wrap(err, "error occurred trying to connect")
|
||||
}
|
||||
|
||||
if statusCode < http.StatusOK || statusCode >= http.StatusBadRequest {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, nil, statusCode, errors.Wrap(err, "error reading response")
|
||||
}
|
||||
if len(body) == 0 {
|
||||
return nil, nil, statusCode, errors.Errorf("Error: request returned %s", http.StatusText(statusCode))
|
||||
}
|
||||
Logger.Debugf("Error response: %s", bytes.TrimSpace(body))
|
||||
return nil, nil, statusCode, errors.Errorf("Error response from vCloud Suite API: %s", bytes.TrimSpace(body))
|
||||
}
|
||||
|
||||
return resp.Body, resp.Header, statusCode, nil
|
||||
}
|
||||
|
||||
func (c *RestClient) Login(ctx context.Context) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
Logger.Debugf("Login to %s through rest API.", c.host)
|
||||
|
||||
request, err := c.newRequest("POST", loginURL, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "login failed")
|
||||
}
|
||||
if c.user != nil {
|
||||
password, _ := c.user.Password()
|
||||
request.SetBasicAuth(c.user.Username(), password)
|
||||
}
|
||||
resp, err := c.HTTP.Do(request)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "login failed")
|
||||
}
|
||||
if resp == nil {
|
||||
return errors.New("response is nil in Login")
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
// #nosec: Errors unhandled.
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
return errors.Errorf("Login failed: body: %s, status: %s", bytes.TrimSpace(body), resp.Status)
|
||||
}
|
||||
|
||||
c.cookies = resp.Cookies()
|
||||
|
||||
Logger.Debugf("Login succeeded")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RestClient) newRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
|
||||
return http.NewRequest(method, c.endpoint.String()+urlStr, body)
|
||||
}
|
||||
135
vendor/github.com/vmware/vic/pkg/vsphere/tags/tag_association.go
generated
vendored
135
vendor/github.com/vmware/vic/pkg/vsphere/tags/tag_association.go
generated
vendored
@@ -1,135 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tags
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
TagAssociationURL = "/com/vmware/cis/tagging/tag-association"
|
||||
)
|
||||
|
||||
type AssociatedObject struct {
|
||||
ID *string `json:"id"`
|
||||
Type *string `json:"type"`
|
||||
}
|
||||
|
||||
type TagAssociationSpec struct {
|
||||
ObjectID *AssociatedObject `json:"object_id,omitempty"`
|
||||
TagID *string `json:"tag_id,omitempty"`
|
||||
}
|
||||
|
||||
func (c *RestClient) getAssociatedObject(objID *string, objType *string) *AssociatedObject {
|
||||
if objID == nil && objType == nil {
|
||||
return nil
|
||||
}
|
||||
object := AssociatedObject{
|
||||
ID: objID,
|
||||
Type: objType,
|
||||
}
|
||||
return &object
|
||||
}
|
||||
|
||||
func (c *RestClient) getAssociationSpec(tagID *string, objID *string, objType *string) *TagAssociationSpec {
|
||||
object := c.getAssociatedObject(objID, objType)
|
||||
spec := TagAssociationSpec{
|
||||
TagID: tagID,
|
||||
ObjectID: object,
|
||||
}
|
||||
return &spec
|
||||
}
|
||||
|
||||
func (c *RestClient) AttachTagToObject(ctx context.Context, tagID string, objID string, objType string) error {
|
||||
Logger.Debugf("Attach Tag %s to object id: %s, type: %s", tagID, objID, objType)
|
||||
|
||||
spec := c.getAssociationSpec(&tagID, &objID, &objType)
|
||||
_, _, status, err := c.call(ctx, "POST", fmt.Sprintf("%s?~action=attach", TagAssociationURL), *spec, nil)
|
||||
|
||||
Logger.Debugf("Get status code: %d", status)
|
||||
if status != http.StatusOK || err != nil {
|
||||
Logger.Debugf("Attach tag failed with status code: %d, error message: %s", status, errors.WithStack(err))
|
||||
return errors.Wrapf(err, "Get unexpected status code: %d", status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RestClient) DetachTagFromObject(ctx context.Context, tagID string, objID string, objType string) error {
|
||||
Logger.Debugf("Detach Tag %s to object id: %s, type: %s", tagID, objID, objType)
|
||||
|
||||
spec := c.getAssociationSpec(&tagID, &objID, &objType)
|
||||
_, _, status, err := c.call(ctx, "POST", fmt.Sprintf("%s?~action=detach", TagAssociationURL), *spec, nil)
|
||||
|
||||
Logger.Debugf("Get status code: %d", status)
|
||||
if status != http.StatusOK || err != nil {
|
||||
Logger.Debugf("Detach tag failed with status code: %d, error message: %s", status, errors.WithStack(err))
|
||||
return errors.Wrapf(err, "Get unexpected status code: %d", status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RestClient) ListAttachedTags(ctx context.Context, objID string, objType string) ([]string, error) {
|
||||
Logger.Debugf("List attached tags of object id: %s, type: %s", objID, objType)
|
||||
|
||||
spec := c.getAssociationSpec(nil, &objID, &objType)
|
||||
stream, _, status, err := c.call(ctx, "POST", fmt.Sprintf("%s?~action=list-attached-tags", TagAssociationURL), *spec, nil)
|
||||
|
||||
Logger.Debugf("Get status code: %d", status)
|
||||
if status != http.StatusOK || err != nil {
|
||||
Logger.Debugf("Detach tag failed with status code: %d, error message: %s", status, errors.WithStack(err))
|
||||
return nil, errors.Wrapf(err, "Get unexpected status code: %d", status)
|
||||
}
|
||||
|
||||
type RespValue struct {
|
||||
Value []string
|
||||
}
|
||||
|
||||
var pTag RespValue
|
||||
if err := json.NewDecoder(stream).Decode(&pTag); err != nil {
|
||||
Logger.Debugf("Decode response body failed for: %s", errors.WithStack(err))
|
||||
return nil, errors.Wrap(err, "list attached tags failed")
|
||||
}
|
||||
return pTag.Value, nil
|
||||
}
|
||||
|
||||
func (c *RestClient) ListAttachedObjects(ctx context.Context, tagID string) ([]AssociatedObject, error) {
|
||||
Logger.Debugf("List attached objects of tag: %s", tagID)
|
||||
|
||||
spec := c.getAssociationSpec(&tagID, nil, nil)
|
||||
Logger.Debugf("List attached objects for tag %v", *spec)
|
||||
// stream, _, status, err := c.call("POST", fmt.Sprintf("%s?~action=list-attached-objects", TagAssociationURL), *spec, nil)
|
||||
stream, _, status, err := c.call(ctx, "POST", fmt.Sprintf("%s?~action=list-attached-objects", TagAssociationURL), *spec, nil)
|
||||
Logger.Debugf("Get status code: %d", status)
|
||||
if status != http.StatusOK || err != nil {
|
||||
Logger.Debugf("List object failed with status code: %d, error message: %s", status, errors.WithStack(err))
|
||||
return nil, errors.Wrapf(err, "Get unexpected status code: %d", status)
|
||||
}
|
||||
|
||||
type RespValue struct {
|
||||
Value []AssociatedObject
|
||||
}
|
||||
|
||||
var pTag RespValue
|
||||
if err := json.NewDecoder(stream).Decode(&pTag); err != nil {
|
||||
Logger.Debugf("Decode response body failed for: %s", errors.WithStack(err))
|
||||
return nil, errors.Wrap(err, "list attached tags failed")
|
||||
}
|
||||
return pTag.Value, nil
|
||||
}
|
||||
255
vendor/github.com/vmware/vic/pkg/vsphere/tags/tags.go
generated
vendored
255
vendor/github.com/vmware/vic/pkg/vsphere/tags/tags.go
generated
vendored
@@ -1,255 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tags
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
TagURL = "/com/vmware/cis/tagging/tag"
|
||||
)
|
||||
|
||||
type TagCreateSpec struct {
|
||||
CreateSpec TagCreate `json:"create_spec"`
|
||||
}
|
||||
|
||||
type TagCreate struct {
|
||||
CategoryID string `json:"category_id"`
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type TagUpdateSpec struct {
|
||||
UpdateSpec TagUpdate `json:"update_spec"`
|
||||
}
|
||||
|
||||
type TagUpdate struct {
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
ID string `json:"id"`
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
CategoryID string `json:"category_id"`
|
||||
UsedBy []string `json:"used_by"`
|
||||
}
|
||||
|
||||
var Logger = logrus.New()
|
||||
|
||||
func (c *RestClient) CreateTagIfNotExist(ctx context.Context, name string, description string, categoryID string) (*string, error) {
|
||||
tagCreate := TagCreate{categoryID, description, name}
|
||||
spec := TagCreateSpec{tagCreate}
|
||||
id, err := c.CreateTag(ctx, &spec)
|
||||
if err == nil {
|
||||
return id, nil
|
||||
}
|
||||
Logger.Debugf("Created tag %s failed for %s", errors.WithStack(err))
|
||||
// if already exists, query back
|
||||
if strings.Contains(err.Error(), ErrAlreadyExists) {
|
||||
tagObjs, err := c.GetTagByNameForCategory(ctx, name, categoryID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to query tag %s for category %s", name, categoryID)
|
||||
}
|
||||
if tagObjs != nil {
|
||||
return &tagObjs[0].ID, nil
|
||||
}
|
||||
|
||||
// should not happen
|
||||
return nil, errors.New("Failed to create tag for it's existed, but could not query back. Please check system")
|
||||
}
|
||||
|
||||
return nil, errors.Wrap(err, "failed to create tag")
|
||||
}
|
||||
|
||||
func (c *RestClient) DeleteTagIfNoObjectAttached(ctx context.Context, id string) error {
|
||||
objs, err := c.ListAttachedObjects(ctx, id)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to delete tag")
|
||||
}
|
||||
if objs != nil && len(objs) > 0 {
|
||||
Logger.Debugf("tag %s related objects is not empty, do not delete it.", id)
|
||||
return nil
|
||||
}
|
||||
return c.DeleteTag(ctx, id)
|
||||
}
|
||||
|
||||
func (c *RestClient) CreateTag(ctx context.Context, spec *TagCreateSpec) (*string, error) {
|
||||
Logger.Debugf("Create Tag %v", spec)
|
||||
stream, _, status, err := c.call(ctx, "POST", TagURL, spec, nil)
|
||||
|
||||
Logger.Debugf("Get status code: %d", status)
|
||||
if status != http.StatusOK || err != nil {
|
||||
Logger.Debugf("Create tag failed with status code: %d, error message: %s", status, errors.WithStack(err))
|
||||
return nil, errors.Wrapf(err, "Status code: %d", status)
|
||||
}
|
||||
|
||||
type RespValue struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
var pID RespValue
|
||||
if err := json.NewDecoder(stream).Decode(&pID); err != nil {
|
||||
Logger.Debugf("Decode response body failed for: %s", errors.WithStack(err))
|
||||
return nil, errors.Wrap(err, "create tag failed")
|
||||
}
|
||||
return &pID.Value, nil
|
||||
}
|
||||
|
||||
func (c *RestClient) GetTag(ctx context.Context, id string) (*Tag, error) {
|
||||
Logger.Debugf("Get tag %s", id)
|
||||
|
||||
stream, _, status, err := c.call(ctx, "GET", fmt.Sprintf("%s/id:%s", TagURL, id), nil, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
Logger.Debugf("Get tag failed with status code: %s, error message: %s", status, errors.WithStack(err))
|
||||
return nil, errors.Wrapf(err, "Status code: %d", status)
|
||||
}
|
||||
|
||||
type RespValue struct {
|
||||
Value Tag
|
||||
}
|
||||
|
||||
var pTag RespValue
|
||||
if err := json.NewDecoder(stream).Decode(&pTag); err != nil {
|
||||
Logger.Debugf("Decode response body failed for: %s", errors.WithStack(err))
|
||||
return nil, errors.Wrapf(err, "failed to get tag %s", id)
|
||||
}
|
||||
return &(pTag.Value), nil
|
||||
}
|
||||
|
||||
func (c *RestClient) UpdateTag(ctx context.Context, id string, spec *TagUpdateSpec) error {
|
||||
Logger.Debugf("Update tag %v", spec)
|
||||
_, _, status, err := c.call(ctx, "PATCH", fmt.Sprintf("%s/id:%s", TagURL, id), spec, nil)
|
||||
|
||||
Logger.Debugf("Get status code: %d", status)
|
||||
if status != http.StatusOK || err != nil {
|
||||
Logger.Debugf("Update tag failed with status code: %d, error message: %s", status, errors.WithStack(err))
|
||||
return errors.Wrapf(err, "Status code: %d", status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RestClient) DeleteTag(ctx context.Context, id string) error {
|
||||
Logger.Debugf("Delete tag %s", id)
|
||||
|
||||
_, _, status, err := c.call(ctx, "DELETE", fmt.Sprintf("%s/id:%s", TagURL, id), nil, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
Logger.Debugf("Delete tag failed with status code: %s, error message: %s", status, errors.WithStack(err))
|
||||
return errors.Wrapf(err, "Status code: %d", status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RestClient) ListTags(ctx context.Context) ([]string, error) {
|
||||
Logger.Debugf("List all tags")
|
||||
|
||||
stream, _, status, err := c.call(ctx, "GET", TagURL, nil, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
Logger.Debugf("Get tags failed with status code: %s, error message: %s", status, errors.WithStack(err))
|
||||
return nil, errors.Wrapf(err, "Status code: %d", status)
|
||||
}
|
||||
|
||||
return c.handleTagIDList(stream)
|
||||
}
|
||||
|
||||
func (c *RestClient) ListTagsForCategory(ctx context.Context, id string) ([]string, error) {
|
||||
Logger.Debugf("List tags for category: %s", id)
|
||||
|
||||
type PostCategory struct {
|
||||
CId string `json:"category_id"`
|
||||
}
|
||||
spec := PostCategory{id}
|
||||
stream, _, status, err := c.call(ctx, "POST", fmt.Sprintf("%s/id:%s?~action=list-tags-for-category", TagURL, id), spec, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
Logger.Debugf("List tags for category failed with status code: %s, error message: %s", status, errors.WithStack(err))
|
||||
return nil, errors.Wrapf(err, "Status code: %d", status)
|
||||
}
|
||||
|
||||
return c.handleTagIDList(stream)
|
||||
}
|
||||
|
||||
func (c *RestClient) handleTagIDList(stream io.ReadCloser) ([]string, error) {
|
||||
type Tags struct {
|
||||
Value []string
|
||||
}
|
||||
|
||||
var pTags Tags
|
||||
if err := json.NewDecoder(stream).Decode(&pTags); err != nil {
|
||||
Logger.Debugf("Decode response body failed for: %s", errors.WithStack(err))
|
||||
return nil, errors.Wrap(err, "failed to decode json")
|
||||
}
|
||||
return pTags.Value, nil
|
||||
}
|
||||
|
||||
// Get tag through tag name and category id
|
||||
func (c *RestClient) GetTagByNameForCategory(ctx context.Context, name string, id string) ([]Tag, error) {
|
||||
Logger.Debugf("Get tag %s for category %s", name, id)
|
||||
tagIds, err := c.ListTagsForCategory(ctx, id)
|
||||
if err != nil {
|
||||
Logger.Debugf("Get tag failed for %s", errors.WithStack(err))
|
||||
return nil, errors.Wrapf(err, "get tag failed for name %s category %s", name, id)
|
||||
}
|
||||
|
||||
var tags []Tag
|
||||
for _, tID := range tagIds {
|
||||
tag, err := c.GetTag(ctx, tID)
|
||||
if err != nil {
|
||||
Logger.Debugf("Get tag %s failed for %s", tID, errors.WithStack(err))
|
||||
return nil, errors.Wrapf(err, "get tag failed for name %s category %s", name, id)
|
||||
}
|
||||
if tag.Name == name {
|
||||
tags = append(tags, *tag)
|
||||
}
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// Get attached tags through tag name pattern
|
||||
func (c *RestClient) GetAttachedTagsByNamePattern(ctx context.Context, namePattern string, objID string, objType string) ([]Tag, error) {
|
||||
tagIds, err := c.ListAttachedTags(ctx, objID, objType)
|
||||
if err != nil {
|
||||
Logger.Debugf("Get attached tags failed for %s", errors.WithStack(err))
|
||||
return nil, errors.Wrap(err, "get attached tags failed")
|
||||
}
|
||||
|
||||
var validName = regexp.MustCompile(namePattern)
|
||||
var tags []Tag
|
||||
for _, tID := range tagIds {
|
||||
tag, err := c.GetTag(ctx, tID)
|
||||
if err != nil {
|
||||
Logger.Debugf("Get tag %s failed for %s", tID, errors.WithStack(err))
|
||||
}
|
||||
if validName.MatchString(tag.Name) {
|
||||
tags = append(tags, *tag)
|
||||
}
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
207
vendor/github.com/vmware/vic/pkg/vsphere/tasks/waiter.go
generated
vendored
207
vendor/github.com/vmware/vic/pkg/vsphere/tasks/waiter.go
generated
vendored
@@ -1,207 +0,0 @@
|
||||
// Copyright 2016-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 tasks wraps the operation of VC. It will invoke the operation and wait
|
||||
// until it's finished, and then return the execution result or error message.
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/task"
|
||||
"github.com/vmware/govmomi/vim25/progress"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
const (
|
||||
maxBackoffFactor = int64(16)
|
||||
)
|
||||
|
||||
//FIXME: remove this type and refactor to use object.Task from govmomi
|
||||
// this will require a lot of code being touched in a lot of places.
|
||||
type Task interface {
|
||||
Wait(ctx context.Context) error
|
||||
WaitForResult(ctx context.Context, s progress.Sinker) (*types.TaskInfo, error)
|
||||
}
|
||||
|
||||
type temporary interface {
|
||||
Temporary() bool
|
||||
}
|
||||
|
||||
// Wait wraps govmomi operations and wait the operation to complete
|
||||
// Sample usage:
|
||||
// info, err := Wait(ctx, func(ctx), (*object.Reference, *TaskInfo, error) {
|
||||
// return vm, vm.Reconfigure(ctx, config)
|
||||
// })
|
||||
func Wait(ctx context.Context, f func(context.Context) (Task, error)) error {
|
||||
_, err := WaitForResult(ctx, f)
|
||||
return err
|
||||
}
|
||||
|
||||
// WaitForResult wraps govmomi operations and wait the operation to complete.
|
||||
// Return the operation result
|
||||
// Sample usage:
|
||||
// info, err := WaitForResult(ctx, func(ctx) (*TaskInfo, error) {
|
||||
// return vm, vm.Reconfigure(ctx, config)
|
||||
// })
|
||||
func WaitForResult(ctx context.Context, f func(context.Context) (Task, error)) (*types.TaskInfo, error) {
|
||||
var err error
|
||||
var backoffFactor int64 = 1
|
||||
|
||||
op := trace.FromContext(ctx, "WaitForResult")
|
||||
|
||||
for {
|
||||
var t Task
|
||||
var info *types.TaskInfo
|
||||
|
||||
if t, err = f(op); err == nil {
|
||||
if info, err = t.WaitForResult(op, nil); err == nil {
|
||||
return info, nil
|
||||
}
|
||||
}
|
||||
|
||||
if !IsRetryError(op, err) {
|
||||
return info, err
|
||||
}
|
||||
|
||||
sleepValue := time.Duration(backoffFactor * (rand.Int63n(100) + int64(50)))
|
||||
select {
|
||||
case <-time.After(sleepValue * time.Millisecond):
|
||||
backoffFactor *= 2
|
||||
if backoffFactor > maxBackoffFactor {
|
||||
backoffFactor = maxBackoffFactor
|
||||
}
|
||||
case <-op.Done():
|
||||
return info, op.Err()
|
||||
}
|
||||
|
||||
op.Warnf("retrying task")
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
vimFault = "vim"
|
||||
soapFault = "soap"
|
||||
taskFault = "task"
|
||||
)
|
||||
|
||||
// IsRetryErrors will return true for vSphere errors, which can be fixed by retry.
|
||||
// Currently the error includes TaskInProgress, NetworkDisruptedAndConfigRolledBack and InvalidArgument
|
||||
// Retry on NetworkDisruptedAndConfigRolledBack is to workaround vSphere issue
|
||||
// Retry on InvalidArgument(invlid path) is to workaround vSAN bug: https://bugzilla.eng.vmware.com/show_bug.cgi?id=1770798. TODO: Should remove it after vSAN fixed the bug
|
||||
func IsRetryError(op trace.Operation, err error) bool {
|
||||
if soap.IsSoapFault(err) {
|
||||
switch f := soap.ToSoapFault(err).VimFault().(type) {
|
||||
case types.TaskInProgress:
|
||||
return true
|
||||
case types.NetworkDisruptedAndConfigRolledBack:
|
||||
logExpectedFault(op, soapFault, f)
|
||||
return true
|
||||
case types.InvalidArgument:
|
||||
logExpectedFault(op, soapFault, f)
|
||||
return true
|
||||
case types.VAppTaskInProgress:
|
||||
logExpectedFault(op, soapFault, f)
|
||||
return true
|
||||
case types.FailToLockFaultToleranceVMs:
|
||||
logExpectedFault(op, soapFault, f)
|
||||
return true
|
||||
case types.HostCommunication:
|
||||
logExpectedFault(op, soapFault, f)
|
||||
return true
|
||||
default:
|
||||
logSoapFault(op, f)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if soap.IsVimFault(err) {
|
||||
switch f := soap.ToVimFault(err).(type) {
|
||||
case *types.TaskInProgress:
|
||||
return true
|
||||
case *types.NetworkDisruptedAndConfigRolledBack:
|
||||
logExpectedFault(op, vimFault, f)
|
||||
return true
|
||||
case *types.InvalidArgument:
|
||||
logExpectedFault(op, vimFault, f)
|
||||
return true
|
||||
case *types.VAppTaskInProgress:
|
||||
logExpectedFault(op, soapFault, f)
|
||||
return true
|
||||
case *types.FailToLockFaultToleranceVMs:
|
||||
logExpectedFault(op, soapFault, f)
|
||||
return true
|
||||
case *types.HostCommunication:
|
||||
logExpectedFault(op, soapFault, f)
|
||||
return true
|
||||
default:
|
||||
logFault(op, f)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
switch err := err.(type) {
|
||||
case task.Error:
|
||||
switch f := err.Fault().(type) {
|
||||
case *types.TaskInProgress:
|
||||
return true
|
||||
case *types.NetworkDisruptedAndConfigRolledBack:
|
||||
logExpectedFault(op, taskFault, f)
|
||||
return true
|
||||
case *types.InvalidArgument:
|
||||
logExpectedFault(op, taskFault, f)
|
||||
return true
|
||||
case *types.HostCommunication:
|
||||
logExpectedFault(op, taskFault, f)
|
||||
return true
|
||||
default:
|
||||
logFault(op, err.Fault())
|
||||
return false
|
||||
}
|
||||
default:
|
||||
// retry the temporary errors
|
||||
t, ok := err.(temporary)
|
||||
if ok && t.Temporary() {
|
||||
logExpectedError(op, err)
|
||||
return true
|
||||
}
|
||||
logError(op, err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Helper Functions
|
||||
func logFault(op trace.Operation, fault types.BaseMethodFault) {
|
||||
op.Errorf("unexpected fault on task retry: %#v", fault)
|
||||
}
|
||||
|
||||
func logSoapFault(op trace.Operation, fault types.AnyType) {
|
||||
op.Debugf("unexpected soap fault on task retry: %s", fault)
|
||||
}
|
||||
|
||||
func logError(op trace.Operation, err error) {
|
||||
op.Debugf("unexpected error on task retry: %s", err)
|
||||
}
|
||||
|
||||
func logExpectedFault(op trace.Operation, kind string, fault interface{}) {
|
||||
op.Debugf("task retry on expected %s fault: %#v", kind, fault)
|
||||
}
|
||||
|
||||
func logExpectedError(op trace.Operation, err error) {
|
||||
op.Debugf("task retry on expected error %s", err)
|
||||
}
|
||||
440
vendor/github.com/vmware/vic/pkg/vsphere/tasks/waiter_test.go
generated
vendored
440
vendor/github.com/vmware/vic/pkg/vsphere/tasks/waiter_test.go
generated
vendored
@@ -1,440 +0,0 @@
|
||||
// Copyright 2016-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 tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/govmomi"
|
||||
"github.com/vmware/govmomi/find"
|
||||
"github.com/vmware/govmomi/simulator"
|
||||
"github.com/vmware/govmomi/task"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/progress"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
m.Run()
|
||||
}
|
||||
|
||||
type MyTask struct {
|
||||
success bool
|
||||
}
|
||||
|
||||
func (t *MyTask) Wait(ctx context.Context) error {
|
||||
_, err := t.WaitForResult(ctx, nil)
|
||||
return err
|
||||
}
|
||||
func (t *MyTask) WaitForResult(ctx context.Context, s progress.Sinker) (*types.TaskInfo, error) {
|
||||
if t.success {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.Errorf("Wait failed")
|
||||
}
|
||||
|
||||
func createFailedTask(context.Context) (Task, error) {
|
||||
return nil, errors.Errorf("Create VM failed")
|
||||
}
|
||||
|
||||
func createFailedResultWaiter(context.Context) (Task, error) {
|
||||
task := &MyTask{
|
||||
false,
|
||||
}
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func createResultWaiter(context.Context) (Task, error) {
|
||||
task := &MyTask{
|
||||
true,
|
||||
}
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func TestFailedInvokeResult(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
_, err := WaitForResult(ctx, func(ctx context.Context) (Task, error) {
|
||||
return createFailedTask(ctx)
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "Create VM failed") {
|
||||
t.Errorf("Not expected error message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailedWaitResult(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
_, err := WaitForResult(ctx, func(ctx context.Context) (Task, error) {
|
||||
return createFailedResultWaiter(ctx)
|
||||
})
|
||||
log.Debugf("got error: %s", err.Error())
|
||||
if err == nil || !strings.Contains(err.Error(), "Wait failed") {
|
||||
t.Errorf("Not expected error message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSuccessWaitResult(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
_, err := WaitForResult(ctx, func(ctx context.Context) (Task, error) {
|
||||
return createResultWaiter(ctx)
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func createFailed(context.Context) (Task, error) {
|
||||
return nil, errors.Errorf("Create VM failed")
|
||||
}
|
||||
|
||||
func createFailedWaiter(context.Context) (Task, error) {
|
||||
task := &MyTask{
|
||||
false,
|
||||
}
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func createWaiter(context.Context) (Task, error) {
|
||||
task := &MyTask{
|
||||
true,
|
||||
}
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func TestFailedInvoke(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
err := Wait(ctx, func(ctx context.Context) (Task, error) {
|
||||
return createFailed(ctx)
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "Create VM failed") {
|
||||
t.Errorf("Not expected error message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailedWait(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
err := Wait(ctx, func(ctx context.Context) (Task, error) {
|
||||
return createFailedWaiter(ctx)
|
||||
})
|
||||
log.Debugf("got error: %s", err.Error())
|
||||
if err == nil || !strings.Contains(err.Error(), "Wait failed") {
|
||||
t.Errorf("Not expected error message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSuccessWait(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
err := Wait(ctx, func(ctx context.Context) (Task, error) {
|
||||
return createWaiter(ctx)
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
var taskInProgressFault = task.Error{
|
||||
LocalizedMethodFault: &types.LocalizedMethodFault{
|
||||
Fault: &types.TaskInProgress{},
|
||||
},
|
||||
}
|
||||
|
||||
type taskInProgressTask struct {
|
||||
cur, max int
|
||||
err error
|
||||
info *types.TaskInfo
|
||||
}
|
||||
|
||||
func (t *taskInProgressTask) Wait(ctx context.Context) error {
|
||||
t.cur++
|
||||
if t.cur == t.max {
|
||||
return t.err
|
||||
}
|
||||
|
||||
return taskInProgressFault
|
||||
}
|
||||
|
||||
func (t *taskInProgressTask) WaitForResult(ctx context.Context, s progress.Sinker) (*types.TaskInfo, error) {
|
||||
return t.info, t.Wait(ctx)
|
||||
}
|
||||
|
||||
func mustRunInTime(t *testing.T, d time.Duration, f func()) {
|
||||
done := make(chan bool)
|
||||
|
||||
go func() {
|
||||
f()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), d)
|
||||
defer cancel()
|
||||
|
||||
select {
|
||||
case <-done: // ran within alloted time
|
||||
case <-ctx.Done():
|
||||
t.Fatalf("test did not run in alloted time %s", d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetry(t *testing.T) {
|
||||
mustRunInTime(t, 2*time.Second, func() {
|
||||
ctx := context.Background()
|
||||
i := 0
|
||||
ti, err := WaitForResult(ctx, func(_ context.Context) (Task, error) {
|
||||
i++
|
||||
return nil, assert.AnError
|
||||
})
|
||||
|
||||
assert.Nil(t, ti)
|
||||
assert.Equal(t, i, 1)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, err, assert.AnError)
|
||||
|
||||
// error != TaskInProgress during task creation
|
||||
i = 0
|
||||
e := &task.Error{
|
||||
LocalizedMethodFault: &types.LocalizedMethodFault{
|
||||
Fault: &types.RuntimeFault{}, // random fault != TaskInProgress
|
||||
LocalizedMessage: "random fault",
|
||||
},
|
||||
}
|
||||
ti, err = WaitForResult(ctx, func(_ context.Context) (Task, error) {
|
||||
i++
|
||||
return nil, e
|
||||
})
|
||||
|
||||
assert.Nil(t, ti)
|
||||
assert.Equal(t, i, 1)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, err, e)
|
||||
|
||||
// context cancelled after two retries
|
||||
i = 0
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
ti, err = WaitForResult(ctx, func(_ context.Context) (Task, error) {
|
||||
i++
|
||||
if i == 2 {
|
||||
cancel()
|
||||
}
|
||||
return nil, taskInProgressFault
|
||||
})
|
||||
|
||||
assert.Nil(t, ti)
|
||||
assert.Equal(t, i, 2)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, err, ctx.Err())
|
||||
|
||||
// TaskInProgress from task creation for 2 iterations and
|
||||
// then nil error
|
||||
tsk := &taskInProgressTask{
|
||||
max: 1,
|
||||
info: &types.TaskInfo{
|
||||
Task: types.ManagedObjectReference{
|
||||
Type: "task",
|
||||
Value: "foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
i = 0
|
||||
ti, err = WaitForResult(context.Background(), func(_ context.Context) (Task, error) {
|
||||
i++
|
||||
if i == 2 {
|
||||
return tsk, nil
|
||||
}
|
||||
return nil, taskInProgressFault
|
||||
})
|
||||
|
||||
assert.Equal(t, tsk.info, ti)
|
||||
assert.Equal(t, i, 2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// return TaskInPregress from task.WaitForResult for 2 iterations
|
||||
// and then return assert.AnError
|
||||
tsk = &taskInProgressTask{
|
||||
max: 2,
|
||||
err: assert.AnError,
|
||||
info: &types.TaskInfo{
|
||||
Task: types.ManagedObjectReference{
|
||||
Type: "task",
|
||||
Value: "foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
ti, err = WaitForResult(context.Background(), func(_ context.Context) (Task, error) {
|
||||
return tsk, nil
|
||||
})
|
||||
|
||||
assert.Equal(t, tsk.info, ti)
|
||||
assert.Equal(t, tsk.max, tsk.cur)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, err, tsk.err)
|
||||
|
||||
// return TaskInPregress from task.WaitForResult for 2 iterations
|
||||
// and then return nil error
|
||||
tsk.cur = 0
|
||||
tsk.err = nil
|
||||
ti, err = WaitForResult(context.Background(), func(_ context.Context) (Task, error) {
|
||||
return tsk, nil
|
||||
})
|
||||
|
||||
assert.Equal(t, tsk.info, ti)
|
||||
assert.Equal(t, tsk.info, ti)
|
||||
assert.Equal(t, tsk.cur, tsk.max)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
// faultyVirtualMachine wrap simulator.VirtualMachine with fault injection
|
||||
type faultyVirtualMachine struct {
|
||||
simulator.VirtualMachine
|
||||
|
||||
fault types.BaseMethodFault
|
||||
}
|
||||
|
||||
// Run implements simulator.TaskRunner and always returns vm.fault
|
||||
func (vm *faultyVirtualMachine) Run(task *simulator.Task) (types.AnyType, types.BaseMethodFault) {
|
||||
return nil, vm.fault
|
||||
}
|
||||
|
||||
// Override PowerOffVMTask to inject a fault
|
||||
func (vm *faultyVirtualMachine) PowerOffVMTask(c *types.PowerOffVM_Task) soap.HasFault {
|
||||
r := &methods.PowerOffVM_TaskBody{}
|
||||
|
||||
task := simulator.NewTask(vm)
|
||||
|
||||
r.Res = &types.PowerOffVM_TaskResponse{
|
||||
Returnval: task.Self,
|
||||
}
|
||||
|
||||
task.Run()
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// MarkAsTemplate implements a non-Task method to inject vm.fault
|
||||
func (vm *faultyVirtualMachine) MarkAsTemplate(c *types.MarkAsTemplate) soap.HasFault {
|
||||
return &methods.MarkAsTemplateBody{
|
||||
Fault_: simulator.Fault("nope", vm.fault),
|
||||
}
|
||||
}
|
||||
|
||||
// TestSoapFaults covers the various soap fault checking paths
|
||||
func TestSoapFaults(t *testing.T) {
|
||||
op := trace.NewOperation(context.Background(), "TestSoapFaults")
|
||||
|
||||
// Nothing VC specific in this test, so we use the simpler ESX model
|
||||
model := simulator.ESX()
|
||||
model.Autostart = false
|
||||
defer model.Remove()
|
||||
err := model.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
server := model.Service.NewServer()
|
||||
defer server.Close()
|
||||
|
||||
client, err := govmomi.NewClient(op, server.URL, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Any VM will do
|
||||
finder := find.NewFinder(client.Client, false)
|
||||
vm, err := finder.VirtualMachine(op, "/ha-datacenter/vm/*_VM0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test the success path
|
||||
err = Wait(op, func(ctx context.Context) (Task, error) {
|
||||
return vm.PowerOn(ctx)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Wrap existing vm MO with faultyVirtualMachine
|
||||
ref := simulator.Map.Get(vm.Reference())
|
||||
fvm := &faultyVirtualMachine{*ref.(*simulator.VirtualMachine), nil}
|
||||
simulator.Map.Put(fvm)
|
||||
|
||||
// Inject TaskInProgress fault
|
||||
fvm.fault = new(types.TaskInProgress)
|
||||
task, err := vm.PowerOff(op)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test the task.Error path
|
||||
res, err := task.WaitForResult(op, nil)
|
||||
if !IsRetryError(op, err) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Test the soap.IsVimFault() path
|
||||
if !IsRetryError(op, soap.WrapVimFault(res.Error.Fault)) {
|
||||
t.Errorf("fault=%#v", res.Error.Fault)
|
||||
}
|
||||
|
||||
// Test the soap.IsSoapFault() path
|
||||
err = vm.MarkAsTemplate(op)
|
||||
if !IsRetryError(op, err) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Test a fault other than TaskInProgress
|
||||
fvm.fault = &types.QuestionPending{
|
||||
Text: "now why would you want to do such a thing?",
|
||||
}
|
||||
|
||||
err = Wait(op, func(ctx context.Context) (Task, error) {
|
||||
return vm.PowerOff(ctx)
|
||||
})
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
if IsRetryError(op, err) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Test with retry
|
||||
fvm.fault = new(types.TaskInProgress)
|
||||
called := 0
|
||||
|
||||
err = Wait(op, func(ctx context.Context) (Task, error) {
|
||||
called++
|
||||
if called > 1 {
|
||||
simulator.Map.Put(ref) // remove fault injection
|
||||
}
|
||||
|
||||
return vm.PowerOff(ctx)
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if called != 2 {
|
||||
t.Errorf("called=%d", called)
|
||||
}
|
||||
}
|
||||
38
vendor/github.com/vmware/vic/pkg/vsphere/test/env/env.go
generated
vendored
38
vendor/github.com/vmware/vic/pkg/vsphere/test/env/env.go
generated
vendored
@@ -1,38 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package env
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// URL checks whether or not we set VIC_ESX_TEST_URL environment variable,
|
||||
// skipping the calling test if undefined
|
||||
func URL(t *testing.T) string {
|
||||
s := os.Getenv("VIC_ESX_TEST_URL")
|
||||
if s == "" && t != nil {
|
||||
t.Skip("Skipping: No test ESX URL defined")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func DS(t *testing.T) string {
|
||||
s := os.Getenv("VIC_ESX_TEST_DATASTORE")
|
||||
if s == "" && t != nil {
|
||||
t.Skip("Skipping: No test ESX DATASTORE defined")
|
||||
}
|
||||
return s
|
||||
}
|
||||
125
vendor/github.com/vmware/vic/pkg/vsphere/test/test.go
generated
vendored
125
vendor/github.com/vmware/vic/pkg/vsphere/test/test.go
generated
vendored
@@ -1,125 +0,0 @@
|
||||
// Copyright 2016-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 test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/vic/lib/spec"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/test/env"
|
||||
|
||||
"context"
|
||||
)
|
||||
|
||||
// Session returns a session.Session struct
|
||||
func Session(ctx context.Context, t *testing.T) *session.Session {
|
||||
config := &session.Config{
|
||||
Service: env.URL(t),
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
DatacenterPath: "",
|
||||
DatastorePath: "/ha-datacenter/datastore/*",
|
||||
HostPath: "/ha-datacenter/host/*/*",
|
||||
PoolPath: "/ha-datacenter/host/*/Resources",
|
||||
}
|
||||
|
||||
s, err := session.NewSession(config).Create(ctx)
|
||||
if err != nil {
|
||||
// FIXME: See session_test.go TestSession for detail. We never get to PickRandomHost in the case of multiple hosts
|
||||
if strings.Contains(err.Error(), "resolves to multiple hosts") {
|
||||
t.SkipNow()
|
||||
} else {
|
||||
t.Errorf("ERROR: %s", err)
|
||||
t.SkipNow()
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// SessionWithESX returns a general-purpose ESX session for tests.
|
||||
func SessionWithESX(ctx context.Context, service string) (*session.Session, error) {
|
||||
config := &session.Config{
|
||||
Service: service,
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
DatacenterPath: "/ha-datacenter",
|
||||
ClusterPath: "*",
|
||||
DatastorePath: "/ha-datacenter/datastore/LocalDS_0",
|
||||
PoolPath: "/ha-datacenter/host/localhost.localdomain/Resources",
|
||||
}
|
||||
|
||||
s, err := session.NewSession(config).Connect(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s, err = s.Populate(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// SessionWithVPX returns a general-purpose VPX session for tests.
|
||||
func SessionWithVPX(ctx context.Context, service string) (*session.Session, error) {
|
||||
config := &session.Config{
|
||||
Service: service,
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
DatacenterPath: "/DC0",
|
||||
ClusterPath: "/DC0/host/DC0_C0",
|
||||
DatastorePath: "/DC0/datastore/LocalDS_0",
|
||||
PoolPath: "/DC0/host/DC0_C0/Resources",
|
||||
}
|
||||
|
||||
s, err := session.NewSession(config).Connect(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s, err = s.Populate(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// SpecConfig returns a spec.VirtualMachineConfigSpecConfig struct
|
||||
func SpecConfig(session *session.Session, name string) *spec.VirtualMachineConfigSpecConfig {
|
||||
return &spec.VirtualMachineConfigSpecConfig{
|
||||
NumCPUs: 2,
|
||||
MemoryMB: 2048,
|
||||
VMForkEnabled: true,
|
||||
|
||||
ID: name,
|
||||
Name: "zombie_attack",
|
||||
BootMediaPath: session.Datastore.Path("brainz.iso"),
|
||||
VMPathName: fmt.Sprintf("[%s]", session.Datastore.Name()),
|
||||
}
|
||||
}
|
||||
|
||||
// PickRandomHost returns a random object.HostSystem from the hosts attached to the datastore and also lives in the same cluster
|
||||
func PickRandomHost(ctx context.Context, session *session.Session, t *testing.T) *object.HostSystem {
|
||||
hosts, err := session.Datastore.AttachedClusterHosts(ctx, session.Cluster)
|
||||
if err != nil {
|
||||
t.Errorf("ERROR: %s", err)
|
||||
t.SkipNow()
|
||||
}
|
||||
return hosts[rand.Intn(len(hosts))]
|
||||
}
|
||||
680
vendor/github.com/vmware/vic/pkg/vsphere/vm/vm.go
generated
vendored
680
vendor/github.com/vmware/vic/pkg/vsphere/vm/vm.go
generated
vendored
@@ -1,680 +0,0 @@
|
||||
// Copyright 2016-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 vm
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/property"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig/vmomi"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/tasks"
|
||||
)
|
||||
|
||||
const (
|
||||
DestroyTask = "Destroy_Task"
|
||||
UpdateStatus = "UpdateInProgress"
|
||||
)
|
||||
|
||||
type InvalidState struct {
|
||||
r types.ManagedObjectReference
|
||||
}
|
||||
|
||||
func (i *InvalidState) Error() string {
|
||||
return fmt.Sprintf("vm %s is invalid", i.r.String())
|
||||
}
|
||||
|
||||
// VirtualMachine struct defines the VirtualMachine which provides additional
|
||||
// VIC specific methods over object.VirtualMachine as well as keeps some state
|
||||
type VirtualMachine struct {
|
||||
// TODO: Wrap Internal VirtualMachine struct when we have it
|
||||
// *internal.VirtualMachine
|
||||
|
||||
*object.VirtualMachine
|
||||
|
||||
*session.Session
|
||||
|
||||
// fxing is 1 means this vm is fixing for it's in invalid status. 0 means not in fixing status
|
||||
fixing int32
|
||||
}
|
||||
|
||||
// NewVirtualMachine returns a NewVirtualMachine object
|
||||
func NewVirtualMachine(ctx context.Context, session *session.Session, moref types.ManagedObjectReference) *VirtualMachine {
|
||||
return NewVirtualMachineFromVM(ctx, session, object.NewVirtualMachine(session.Vim25(), moref))
|
||||
}
|
||||
|
||||
// NewVirtualMachineFromVM returns a NewVirtualMachine object
|
||||
func NewVirtualMachineFromVM(ctx context.Context, session *session.Session, vm *object.VirtualMachine) *VirtualMachine {
|
||||
return &VirtualMachine{
|
||||
VirtualMachine: vm,
|
||||
Session: session,
|
||||
}
|
||||
}
|
||||
|
||||
// VMPathNameAsURL returns the full datastore path of the VM as a url. The datastore name is in the host
|
||||
// portion, the path is in the Path field, the scheme is set to "ds"
|
||||
func (vm *VirtualMachine) VMPathNameAsURL(ctx context.Context) (url.URL, error) {
|
||||
op := trace.FromContext(ctx, "VMPathNameAsURL")
|
||||
|
||||
var mvm mo.VirtualMachine
|
||||
|
||||
if err := vm.Properties(op, vm.Reference(), []string{"config.files.vmPathName"}, &mvm); err != nil {
|
||||
op.Errorf("Unable to get managed config for VM: %s", err)
|
||||
return url.URL{}, err
|
||||
}
|
||||
|
||||
if mvm.Config == nil {
|
||||
return url.URL{}, errors.New("failed to get datastore path - config not found")
|
||||
}
|
||||
|
||||
path := path.Dir(mvm.Config.Files.VmPathName)
|
||||
val := url.URL{
|
||||
Scheme: "ds",
|
||||
}
|
||||
|
||||
// split the dsPath into the url components
|
||||
if ix := strings.Index(path, "] "); ix != -1 {
|
||||
val.Host = path[strings.Index(path, "[")+1 : ix]
|
||||
val.Path = path[ix+2:]
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// FolderName returns the name of the namespace(vsan) or directory(vmfs) that holds the VM
|
||||
// this equates to the normal directory that contains the vmx file, stripped of any parent path
|
||||
func (vm *VirtualMachine) FolderName(ctx context.Context) (string, error) {
|
||||
op := trace.FromContext(ctx, "FolderName")
|
||||
|
||||
u, err := vm.VMPathNameAsURL(op)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return path.Base(u.Path), nil
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) getNetworkName(op trace.Operation, nic types.BaseVirtualEthernetCard) (string, error) {
|
||||
if card, ok := nic.GetVirtualEthernetCard().Backing.(*types.VirtualEthernetCardDistributedVirtualPortBackingInfo); ok {
|
||||
pg := card.Port.PortgroupKey
|
||||
pgref := object.NewDistributedVirtualPortgroup(vm.Session.Vim25(), types.ManagedObjectReference{
|
||||
Type: "DistributedVirtualPortgroup",
|
||||
Value: pg,
|
||||
})
|
||||
|
||||
var pgo mo.DistributedVirtualPortgroup
|
||||
err := pgref.Properties(op, pgref.Reference(), []string{"config"}, &pgo)
|
||||
if err != nil {
|
||||
op.Errorf("Failed to query portgroup %s for %s", pg, err)
|
||||
return "", err
|
||||
}
|
||||
return pgo.Config.Name, nil
|
||||
}
|
||||
return nic.GetVirtualEthernetCard().DeviceInfo.GetDescription().Summary, nil
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) FetchExtraConfigBaseOptions(ctx context.Context) ([]types.BaseOptionValue, error) {
|
||||
op := trace.FromContext(ctx, "FetchExtraConfigBaseOptions")
|
||||
|
||||
var err error
|
||||
|
||||
var mvm mo.VirtualMachine
|
||||
|
||||
if err = vm.Properties(op, vm.Reference(), []string{"config.extraConfig"}, &mvm); err != nil {
|
||||
op.Errorf("Unable to get vm config: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mvm.Config.ExtraConfig, nil
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) FetchExtraConfig(ctx context.Context) (map[string]string, error) {
|
||||
op := trace.FromContext(ctx, "FetchExtraConfig")
|
||||
|
||||
info := make(map[string]string)
|
||||
|
||||
v, err := vm.FetchExtraConfigBaseOptions(op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, bov := range v {
|
||||
ov := bov.GetOptionValue()
|
||||
value, _ := ov.Value.(string)
|
||||
info[ov.Key] = value
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// WaitForExtraConfig waits until key shows up with the expected value inside the ExtraConfig
|
||||
func (vm *VirtualMachine) WaitForExtraConfig(ctx context.Context, waitFunc func(pc []types.PropertyChange) bool) error {
|
||||
op := trace.FromContext(ctx, "WaitForExtraConfig")
|
||||
|
||||
// Get the default collector
|
||||
p := property.DefaultCollector(vm.Vim25())
|
||||
|
||||
// Wait on config.extraConfig
|
||||
// https://www.vmware.com/support/developer/vc-sdk/visdk2xpubs/ReferenceGuide/vim.vm.ConfigInfo.html
|
||||
return property.Wait(op, p, vm.Reference(), []string{"config.extraConfig", object.PropRuntimePowerState}, waitFunc)
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) WaitForKeyInExtraConfig(ctx context.Context, key string) (string, error) {
|
||||
op := trace.FromContext(ctx, "WaitForKeyInExtraConfig")
|
||||
|
||||
var detail string
|
||||
var poweredOff error
|
||||
|
||||
waitFunc := func(pc []types.PropertyChange) bool {
|
||||
for _, c := range pc {
|
||||
if c.Op != types.PropertyChangeOpAssign {
|
||||
continue
|
||||
}
|
||||
|
||||
switch v := c.Val.(type) {
|
||||
case types.ArrayOfOptionValue:
|
||||
for _, value := range v.OptionValue {
|
||||
// check the status of the key and return true if it's been set to non-nil
|
||||
if key == value.GetOptionValue().Key {
|
||||
detail = value.GetOptionValue().Value.(string)
|
||||
if detail != "" && detail != "<nil>" {
|
||||
// ensure we clear any tentative error
|
||||
poweredOff = nil
|
||||
|
||||
return true
|
||||
}
|
||||
break // continue the outer loop as we may have a powerState change too
|
||||
}
|
||||
}
|
||||
case types.VirtualMachinePowerState:
|
||||
// Give up if the vm has powered off
|
||||
if v != types.VirtualMachinePowerStatePoweredOn {
|
||||
msg := "powered off"
|
||||
if v == types.VirtualMachinePowerStateSuspended {
|
||||
// Unlikely, but possible if the VM was suspended out-of-band
|
||||
msg = string(v)
|
||||
}
|
||||
poweredOff = fmt.Errorf("container VM has unexpectedly %s", msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return poweredOff != nil
|
||||
}
|
||||
|
||||
err := vm.WaitForExtraConfig(op, waitFunc)
|
||||
if err == nil && poweredOff != nil {
|
||||
err = poweredOff
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return detail, nil
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) Name(ctx context.Context) (string, error) {
|
||||
op := trace.FromContext(ctx, "Name")
|
||||
|
||||
var err error
|
||||
var mvm mo.VirtualMachine
|
||||
|
||||
if err = vm.Properties(op, vm.Reference(), []string{"summary.config"}, &mvm); err != nil {
|
||||
op.Errorf("Unable to get vm summary.config property: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return mvm.Summary.Config.Name, nil
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) UUID(ctx context.Context) (string, error) {
|
||||
op := trace.FromContext(ctx, "UUID")
|
||||
|
||||
var err error
|
||||
var mvm mo.VirtualMachine
|
||||
|
||||
if err = vm.Properties(op, vm.Reference(), []string{"summary.config"}, &mvm); err != nil {
|
||||
op.Errorf("Unable to get vm summary.config property: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return mvm.Summary.Config.Uuid, nil
|
||||
}
|
||||
|
||||
// DeleteExceptDisks destroys the VM after detaching all virtual disks
|
||||
func (vm *VirtualMachine) DeleteExceptDisks(ctx context.Context) (*object.Task, error) {
|
||||
op := trace.FromContext(ctx, "DeleteExceptDisks")
|
||||
|
||||
devices, err := vm.Device(op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
disks := devices.SelectByType(&types.VirtualDisk{})
|
||||
err = vm.RemoveDevice(op, true, disks...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vm.Destroy(op)
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) VMPathName(ctx context.Context) (string, error) {
|
||||
op := trace.FromContext(ctx, "VMPathName")
|
||||
|
||||
var err error
|
||||
var mvm mo.VirtualMachine
|
||||
|
||||
if err = vm.Properties(op, vm.Reference(), []string{"config.files.vmPathName"}, &mvm); err != nil {
|
||||
op.Errorf("Unable to get vm config.files property: %s", err)
|
||||
return "", err
|
||||
}
|
||||
return mvm.Config.Files.VmPathName, nil
|
||||
}
|
||||
|
||||
// GetCurrentSnapshotTree returns current snapshot, with tree information
|
||||
func (vm *VirtualMachine) GetCurrentSnapshotTree(ctx context.Context) (*types.VirtualMachineSnapshotTree, error) {
|
||||
op := trace.FromContext(ctx, "GetCurrentSnapshotTree")
|
||||
|
||||
var err error
|
||||
var mvm mo.VirtualMachine
|
||||
|
||||
if err = vm.Properties(op, vm.Reference(), []string{"snapshot"}, &mvm); err != nil {
|
||||
op.Infof("Unable to get vm properties: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
if mvm.Snapshot == nil {
|
||||
// no snapshot at all
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
current := mvm.Snapshot.CurrentSnapshot
|
||||
q := list.New()
|
||||
for _, c := range mvm.Snapshot.RootSnapshotList {
|
||||
q.PushBack(c)
|
||||
}
|
||||
|
||||
compareID := func(node types.VirtualMachineSnapshotTree) bool {
|
||||
if node.Snapshot == *current {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return vm.bfsSnapshotTree(q, compareID), nil
|
||||
}
|
||||
|
||||
// GetCurrentSnapshotTreeByName returns current snapshot, with tree information
|
||||
func (vm *VirtualMachine) GetSnapshotTreeByName(ctx context.Context, name string) (*types.VirtualMachineSnapshotTree, error) {
|
||||
op := trace.FromContext(ctx, "GetSnapshotTreeByName")
|
||||
|
||||
var err error
|
||||
var mvm mo.VirtualMachine
|
||||
|
||||
if err = vm.Properties(op, vm.Reference(), []string{"snapshot"}, &mvm); err != nil {
|
||||
op.Infof("Unable to get vm properties: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
if mvm.Snapshot == nil {
|
||||
// no snapshot at all
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
q := list.New()
|
||||
for _, c := range mvm.Snapshot.RootSnapshotList {
|
||||
q.PushBack(c)
|
||||
}
|
||||
|
||||
compareName := func(node types.VirtualMachineSnapshotTree) bool {
|
||||
if node.Name == name {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return vm.bfsSnapshotTree(q, compareName), nil
|
||||
}
|
||||
|
||||
// Finds a snapshot tree based on comparator function 'compare' via a breadth first search of the snapshot tree attached to the VM
|
||||
func (vm *VirtualMachine) bfsSnapshotTree(q *list.List, compare func(node types.VirtualMachineSnapshotTree) bool) *types.VirtualMachineSnapshotTree {
|
||||
if q.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
e := q.Front()
|
||||
tree := q.Remove(e).(types.VirtualMachineSnapshotTree)
|
||||
if compare(tree) {
|
||||
return &tree
|
||||
}
|
||||
for _, c := range tree.ChildSnapshotList {
|
||||
q.PushBack(c)
|
||||
}
|
||||
return vm.bfsSnapshotTree(q, compare)
|
||||
}
|
||||
|
||||
// IsConfigureSnapshot is the helper func that returns true if node is a snapshot with specified name prefix
|
||||
func IsConfigureSnapshot(node *types.VirtualMachineSnapshotTree, prefix string) bool {
|
||||
return node != nil && strings.HasPrefix(node.Name, prefix)
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) registerVM(op trace.Operation, path, name string,
|
||||
vapp, pool, host *types.ManagedObjectReference, vmfolder *object.Folder) (*object.Task, error) {
|
||||
op.Debugf("Register VM %s", name)
|
||||
|
||||
if vapp == nil {
|
||||
var hostObject *object.HostSystem
|
||||
if host != nil {
|
||||
hostObject = object.NewHostSystem(vm.Vim25(), *host)
|
||||
}
|
||||
poolObject := object.NewResourcePool(vm.Vim25(), *pool)
|
||||
return vmfolder.RegisterVM(op, path, name, false, poolObject, hostObject)
|
||||
}
|
||||
|
||||
req := types.RegisterChildVM_Task{
|
||||
This: vapp.Reference(),
|
||||
Path: path,
|
||||
Host: host,
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
req.Name = name
|
||||
}
|
||||
|
||||
res, err := methods.RegisterChildVM_Task(op, vm.Vim25(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return object.NewTask(vm.Vim25(), res.Returnval), nil
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) IsFixing() bool {
|
||||
return vm.fixing > 0
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) EnterFixingState() {
|
||||
atomic.AddInt32(&vm.fixing, 1)
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) LeaveFixingState() {
|
||||
atomic.StoreInt32(&vm.fixing, 0)
|
||||
}
|
||||
|
||||
// FixInvalidState fix vm invalid state issue through unregister & register
|
||||
func (vm *VirtualMachine) fixVM(op trace.Operation) error {
|
||||
op.Debugf("Fix invalid state VM: %s", vm.Reference())
|
||||
|
||||
properties := []string{"summary.config", "summary.runtime.host", "resourcePool", "parentVApp"}
|
||||
op.Debugf("Get vm properties %s", properties)
|
||||
var mvm mo.VirtualMachine
|
||||
if err := vm.VirtualMachine.Properties(op, vm.Reference(), properties, &mvm); err != nil {
|
||||
op.Errorf("Unable to get vm properties: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
name := mvm.Summary.Config.Name
|
||||
op.Debugf("Unregister VM %s", name)
|
||||
vm.EnterFixingState()
|
||||
if err := vm.Unregister(op); err != nil {
|
||||
op.Errorf("Unable to unregister vm %q: %s", name, err)
|
||||
|
||||
// Leave fixing state since it will not be reset in the remove event handler
|
||||
vm.LeaveFixingState()
|
||||
return err
|
||||
}
|
||||
|
||||
task, err := vm.registerVM(op, mvm.Summary.Config.VmPathName, name, mvm.ParentVApp, mvm.ResourcePool, mvm.Summary.Runtime.Host, vm.Session.VMFolder)
|
||||
if err != nil {
|
||||
op.Errorf("Unable to register VM %q back: %s", name, err)
|
||||
return err
|
||||
}
|
||||
info, err := task.WaitForResult(op, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// re-register vm will change vm reference, so reset the object reference here
|
||||
if info.Error != nil {
|
||||
return errors.New(info.Error.LocalizedMessage)
|
||||
}
|
||||
|
||||
// set new registered vm attribute back
|
||||
newRef := info.Result.(types.ManagedObjectReference)
|
||||
common := object.NewCommon(vm.Vim25(), newRef)
|
||||
common.InventoryPath = vm.InventoryPath
|
||||
vm.Common = common
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) needsFix(op trace.Operation, err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
if vm.IsInvalidState(op) {
|
||||
op.Debugf("vm %s is invalid", vm.Reference())
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) IsInvalidState(ctx context.Context) bool {
|
||||
op := trace.FromContext(ctx, "IsInvalidState")
|
||||
|
||||
var o mo.VirtualMachine
|
||||
if err := vm.VirtualMachine.Properties(op, vm.Reference(), []string{"summary.runtime.connectionState"}, &o); err != nil {
|
||||
op.Debugf("Failed to get vm properties: %s", err)
|
||||
return false
|
||||
}
|
||||
if o.Summary.Runtime.ConnectionState == types.VirtualMachineConnectionStateInvalid {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// WaitForResult is designed to handle VM invalid state error for any VM operations.
|
||||
// It will call tasks.WaitForResult to retry if there is task in progress error.
|
||||
func (vm *VirtualMachine) WaitForResult(ctx context.Context, f func(context.Context) (tasks.Task, error)) (*types.TaskInfo, error) {
|
||||
op := trace.FromContext(ctx, "WaitForResult")
|
||||
|
||||
info, err := tasks.WaitForResult(op, f)
|
||||
if err == nil || !vm.needsFix(op, err) {
|
||||
return info, err
|
||||
}
|
||||
|
||||
op.Debugf("Try to fix task failure %s", err)
|
||||
if nerr := vm.fixVM(op); nerr != nil {
|
||||
op.Errorf("Failed to fix task failure: %s", nerr)
|
||||
return info, err
|
||||
}
|
||||
op.Debug("Fixed")
|
||||
return tasks.WaitForResult(op, f)
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) Properties(ctx context.Context, r types.ManagedObjectReference, ps []string, o *mo.VirtualMachine) error {
|
||||
// lets ensure we have an operation
|
||||
op := trace.FromContext(ctx, "VM Properties")
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("VM(%s) Properties(%s)", r, ps), op))
|
||||
|
||||
contains := false
|
||||
for _, v := range ps {
|
||||
if v == "summary" || v == "summary.runtime" {
|
||||
contains = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !contains {
|
||||
ps = append(ps, "summary.runtime.connectionState")
|
||||
}
|
||||
|
||||
op.Debugf("properties: %s", ps)
|
||||
|
||||
if err := vm.VirtualMachine.Properties(op, r, ps, o); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.Summary.Runtime.ConnectionState != types.VirtualMachineConnectionStateInvalid {
|
||||
return nil
|
||||
}
|
||||
op.Infof("vm %s is in invalid state", r)
|
||||
if err := vm.fixVM(op); err != nil {
|
||||
op.Errorf("Failed to fix vm %s: %s", vm.Reference(), err)
|
||||
return &InvalidState{r: vm.Reference()}
|
||||
}
|
||||
|
||||
return vm.VirtualMachine.Properties(op, vm.Reference(), ps, o)
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) Parent(ctx context.Context) (*types.ManagedObjectReference, error) {
|
||||
op := trace.FromContext(ctx, "Parent")
|
||||
|
||||
var mvm mo.VirtualMachine
|
||||
|
||||
if err := vm.Properties(op, vm.Reference(), []string{"parentVApp", "resourcePool"}, &mvm); err != nil {
|
||||
op.Errorf("Unable to get VM parent: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
if mvm.ParentVApp != nil {
|
||||
return mvm.ParentVApp, nil
|
||||
}
|
||||
return mvm.ResourcePool, nil
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) DatastoreReference(ctx context.Context) ([]types.ManagedObjectReference, error) {
|
||||
op := trace.FromContext(ctx, "DatastoreReference")
|
||||
|
||||
var mvm mo.VirtualMachine
|
||||
|
||||
if err := vm.Properties(op, vm.Reference(), []string{"datastore"}, &mvm); err != nil {
|
||||
op.Errorf("Unable to get VM datastore: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
return mvm.Datastore, nil
|
||||
}
|
||||
|
||||
// VCHUpdateStatus tells if an upgrade/configure has already been started based on the UpdateInProgress flag in ExtraConfig
|
||||
// It returns the error if the vm operation does not succeed
|
||||
func (vm *VirtualMachine) VCHUpdateStatus(ctx context.Context) (bool, error) {
|
||||
op := trace.FromContext(ctx, "VCHUpdateStatus")
|
||||
|
||||
info, err := vm.FetchExtraConfig(op)
|
||||
if err != nil {
|
||||
op.Errorf("Unable to get vm ExtraConfig: %s", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if v, ok := info[UpdateStatus]; ok {
|
||||
status, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
// If error occurs, the bool return value does not matter for the caller.
|
||||
return false, fmt.Errorf("failed to parse %s to bool: %s", v, err)
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
// If UpdateStatus is not found, it might be the case that no upgrade/configure has been done to this VCH before
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// SetVCHUpdateStatus sets the VCH update status in ExtraConfig
|
||||
func (vm *VirtualMachine) SetVCHUpdateStatus(ctx context.Context, status bool) error {
|
||||
op := trace.FromContext(ctx, "SetVCHUpdateStatus")
|
||||
|
||||
info := make(map[string]string)
|
||||
info[UpdateStatus] = strconv.FormatBool(status)
|
||||
|
||||
s := &types.VirtualMachineConfigSpec{
|
||||
ExtraConfig: vmomi.OptionValueFromMap(info, true),
|
||||
}
|
||||
|
||||
_, err := vm.WaitForResult(op, func(op context.Context) (tasks.Task, error) {
|
||||
return vm.Reconfigure(op, *s)
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DisableDestroy attempts to disable the VirtualMachine.Destroy_Task method.
|
||||
// When the method is disabled, the VC UI will disable/grey out the VM "delete" action.
|
||||
// Requires the "Global.Disable" VC privilege.
|
||||
// The VirtualMachine.Destroy_Task method can still be invoked via the API.
|
||||
func (vm *VirtualMachine) DisableDestroy(ctx context.Context) {
|
||||
if !vm.IsVC() {
|
||||
return
|
||||
}
|
||||
|
||||
op := trace.FromContext(ctx, "DisableDestroy")
|
||||
|
||||
m := object.NewAuthorizationManager(vm.Vim25())
|
||||
|
||||
method := []object.DisabledMethodRequest{
|
||||
{
|
||||
Method: DestroyTask,
|
||||
Reason: "Managed by VIC Engine",
|
||||
},
|
||||
}
|
||||
|
||||
obj := []types.ManagedObjectReference{vm.Reference()}
|
||||
|
||||
err := m.DisableMethods(op, obj, method, "VIC")
|
||||
if err != nil {
|
||||
op.Warnf("Failed to disable method %s for %s: %s", method[0].Method, obj[0], err)
|
||||
}
|
||||
}
|
||||
|
||||
// EnableDestroy attempts to enable the VirtualMachine.Destroy_Task method
|
||||
// so that the VC UI can enable the VM "delete" action.
|
||||
func (vm *VirtualMachine) EnableDestroy(ctx context.Context) {
|
||||
if !vm.IsVC() {
|
||||
return
|
||||
}
|
||||
|
||||
op := trace.FromContext(ctx, "EnableDestroy")
|
||||
|
||||
m := object.NewAuthorizationManager(vm.Vim25())
|
||||
|
||||
obj := []types.ManagedObjectReference{vm.Reference()}
|
||||
|
||||
err := m.EnableMethods(op, obj, []string{DestroyTask}, "VIC")
|
||||
if err != nil {
|
||||
op.Warnf("Failed to enable Destroy_Task for %s: %s", obj[0], err)
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveSnapshot removes a snapshot by reference
|
||||
func (vm *VirtualMachine) RemoveSnapshotByRef(ctx context.Context, snapshot *types.ManagedObjectReference, removeChildren bool, consolidate *bool) (*object.Task, error) {
|
||||
req := types.RemoveSnapshot_Task{
|
||||
This: snapshot.Reference(),
|
||||
RemoveChildren: removeChildren,
|
||||
Consolidate: consolidate,
|
||||
}
|
||||
|
||||
res, err := methods.RemoveSnapshot_Task(ctx, vm.Vim25(), &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return object.NewTask(vm.Vim25(), res.Returnval), nil
|
||||
}
|
||||
777
vendor/github.com/vmware/vic/pkg/vsphere/vm/vm_test.go
generated
vendored
777
vendor/github.com/vmware/vic/pkg/vsphere/vm/vm_test.go
generated
vendored
@@ -1,777 +0,0 @@
|
||||
// Copyright 2016-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 vm
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/govmomi"
|
||||
"github.com/vmware/govmomi/find"
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/simulator"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/vic/lib/guest"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/vsphere/extraconfig/vmomi"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/sys"
|
||||
"github.com/vmware/vic/pkg/vsphere/tasks"
|
||||
"github.com/vmware/vic/pkg/vsphere/test"
|
||||
)
|
||||
|
||||
func CreateVM(ctx context.Context, session *session.Session, host *object.HostSystem, name string) (*types.ManagedObjectReference, error) {
|
||||
// Create the spec config
|
||||
specconfig := test.SpecConfig(session, name)
|
||||
|
||||
// Create a linux guest
|
||||
linux, err := guest.NewLinuxGuest(ctx, session, specconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create the vm
|
||||
task, err := session.VMFolder.CreateVM(ctx, *linux.Spec().Spec(), session.Pool, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, err := task.WaitForResult(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
moref := info.Result.(types.ManagedObjectReference)
|
||||
// Return the moRef
|
||||
return &moref, nil
|
||||
}
|
||||
|
||||
func TestDeleteExceptDisk(t *testing.T) {
|
||||
s := os.Getenv("DRONE")
|
||||
if s != "" {
|
||||
t.Skip("Skipping: test must be run in a VM")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
session := test.Session(ctx, t)
|
||||
defer session.Logout(ctx)
|
||||
|
||||
host := test.PickRandomHost(ctx, session, t)
|
||||
|
||||
uuid, err := sys.UUID()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get UUID for guest - used for VM name: %s", err)
|
||||
}
|
||||
name := fmt.Sprintf("%s-%d", uuid, rand.Intn(math.MaxInt32))
|
||||
|
||||
moref, err := CreateVM(ctx, session, host, name)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
// Wrap the result with our version of VirtualMachine
|
||||
vm := NewVirtualMachine(ctx, session, *moref)
|
||||
|
||||
folder, err := vm.FolderName(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
|
||||
// generate the disk name
|
||||
diskName := fmt.Sprintf("%s/%s.vmdk", folder, folder)
|
||||
|
||||
// Delete the VM but not it's disk
|
||||
task, err := vm.DeleteExceptDisks(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
_, err = task.WaitForResult(ctx, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
|
||||
// check that the disk still exists
|
||||
session.Datastore.Stat(ctx, diskName)
|
||||
if err != nil {
|
||||
t.Fatalf("Disk does not exist")
|
||||
}
|
||||
|
||||
// clean up
|
||||
dm := object.NewVirtualDiskManager(session.Client.Client)
|
||||
|
||||
task, err = dm.DeleteVirtualDisk(context.TODO(), diskName, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to locate orphan vmdk: %s", err)
|
||||
}
|
||||
|
||||
if err = task.Wait(context.TODO()); err != nil {
|
||||
t.Fatalf("Unable to remove orphan vmdk: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVM(t *testing.T) {
|
||||
|
||||
s := os.Getenv("DRONE")
|
||||
if s != "" {
|
||||
t.Skip("Skipping: test must be run in a VM")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
session := test.Session(ctx, t)
|
||||
defer session.Logout(ctx)
|
||||
|
||||
host := test.PickRandomHost(ctx, session, t)
|
||||
|
||||
uuid, err := sys.UUID()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get UUID for guest - used for VM name: %s", err)
|
||||
return
|
||||
}
|
||||
name := fmt.Sprintf("%s-%d", uuid, rand.Intn(math.MaxInt32))
|
||||
|
||||
moref, err := CreateVM(ctx, session, host, name)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
// Wrap the result with our version of VirtualMachine
|
||||
vm := NewVirtualMachine(ctx, session, *moref)
|
||||
|
||||
// Check the state
|
||||
state, err := vm.PowerState(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, types.VirtualMachinePowerStatePoweredOff, state)
|
||||
|
||||
// Check VM name
|
||||
rname, err := vm.Name(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to load VM name: %s", err)
|
||||
}
|
||||
assert.Equal(t, name, rname)
|
||||
|
||||
// Get VM UUID
|
||||
ruuid, err := vm.UUID(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to load VM UUID: %s", err)
|
||||
}
|
||||
t.Logf("Got UUID: %s", ruuid)
|
||||
|
||||
err = vm.fixVM(trace.FromContext(ctx, "TestVM"))
|
||||
if err != nil {
|
||||
t.Errorf("Failed to fix vm: %s", err)
|
||||
}
|
||||
newVM, err := session.Finder.VirtualMachine(ctx, name)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to find fixed vm: %s", err)
|
||||
}
|
||||
assert.Equal(t, vm.Reference(), newVM.Reference())
|
||||
|
||||
// VM properties
|
||||
var ovm mo.VirtualMachine
|
||||
if err = vm.Properties(ctx, newVM.Reference(), []string{"config"}, &ovm); err != nil {
|
||||
t.Errorf("Failed to get vm properties: %s", err)
|
||||
}
|
||||
|
||||
// Destroy the vm
|
||||
task, err := vm.Destroy(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
_, err = task.WaitForResult(ctx, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVMFailureWithTimeout(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
session := test.Session(ctx, t)
|
||||
defer session.Logout(ctx)
|
||||
|
||||
host := test.PickRandomHost(ctx, session, t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, 1*time.Microsecond)
|
||||
defer cancel()
|
||||
|
||||
uuid, err := sys.UUID()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get UUID for guest - used for VM name: %s", err)
|
||||
return
|
||||
}
|
||||
name := fmt.Sprintf("%s-%d", uuid, rand.Intn(math.MaxInt32))
|
||||
|
||||
_, err = CreateVM(ctx, session, host, name)
|
||||
if err != nil && err != context.DeadlineExceeded {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVMAttributes(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
session := test.Session(ctx, t)
|
||||
defer session.Logout(ctx)
|
||||
|
||||
host := test.PickRandomHost(ctx, session, t)
|
||||
|
||||
uuid, err := sys.UUID()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get UUID for guest - used for VM name: %s", err)
|
||||
return
|
||||
}
|
||||
ID := fmt.Sprintf("%s-%d", uuid, rand.Intn(math.MaxInt32))
|
||||
|
||||
moref, err := CreateVM(ctx, session, host, ID)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
// Wrap the result with our version of VirtualMachine
|
||||
vm := NewVirtualMachine(ctx, session, *moref)
|
||||
|
||||
folder, err := vm.FolderName(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
|
||||
name, err := vm.Name(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
assert.Equal(t, name, folder)
|
||||
task, err := vm.PowerOn(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
_, err = task.WaitForResult(ctx, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
|
||||
if guest, err := vm.FetchExtraConfig(ctx); err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
} else {
|
||||
assert.NotEmpty(t, guest)
|
||||
}
|
||||
defer func() {
|
||||
// Destroy the vm
|
||||
task, err := vm.PowerOff(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
_, err = task.WaitForResult(ctx, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
task, err = vm.Destroy(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
_, err = task.WaitForResult(ctx, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func TestWaitForKeyInExtraConfig(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
m := simulator.ESX()
|
||||
defer m.Remove()
|
||||
err := m.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
server := m.Service.NewServer()
|
||||
defer server.Close()
|
||||
|
||||
config := &session.Config{
|
||||
Service: server.URL.String(),
|
||||
}
|
||||
|
||||
s, err := session.NewSession(config).Connect(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if s, err = s.Populate(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
vms, err := s.Finder.VirtualMachineList(ctx, "*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
vm := NewVirtualMachineFromVM(ctx, s, vms[0])
|
||||
|
||||
opt := &types.OptionValue{Key: "foo", Value: "bar"}
|
||||
obj := simulator.Map.Get(vm.Reference()).(*simulator.VirtualMachine)
|
||||
|
||||
val, err := vm.WaitForKeyInExtraConfig(ctx, opt.Key)
|
||||
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
|
||||
obj.Config.ExtraConfig = append(obj.Config.ExtraConfig, opt)
|
||||
obj.Summary.Runtime.PowerState = types.VirtualMachinePowerStatePoweredOn
|
||||
|
||||
val, err = vm.WaitForKeyInExtraConfig(ctx, opt.Key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if val != opt.Value {
|
||||
t.Errorf("%s != %s", val, opt.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func createSnapshotTree(prefix string, deep int, wide int) []types.VirtualMachineSnapshotTree {
|
||||
var result []types.VirtualMachineSnapshotTree
|
||||
if deep == 0 {
|
||||
return nil
|
||||
}
|
||||
for i := 1; i <= wide; i++ {
|
||||
nodeID := fmt.Sprintf("%s%d", prefix, i)
|
||||
node := types.VirtualMachineSnapshotTree{
|
||||
Snapshot: types.ManagedObjectReference{
|
||||
Type: "Snapshot",
|
||||
Value: nodeID,
|
||||
},
|
||||
Name: nodeID,
|
||||
}
|
||||
node.ChildSnapshotList = createSnapshotTree(nodeID, deep-1, wide)
|
||||
result = append(result, node)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func TestBfsSnapshotTree(t *testing.T) {
|
||||
ref := &types.ManagedObjectReference{
|
||||
Type: "Snapshot",
|
||||
Value: "12131",
|
||||
}
|
||||
rootList := createSnapshotTree("", 5, 5)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
session := test.Session(ctx, t)
|
||||
defer session.Logout(ctx)
|
||||
vm := NewVirtualMachine(ctx, session, *ref)
|
||||
q := list.New()
|
||||
for _, c := range rootList {
|
||||
q.PushBack(c)
|
||||
}
|
||||
|
||||
compareID := func(node types.VirtualMachineSnapshotTree) bool {
|
||||
if node.Snapshot == *ref {
|
||||
t.Logf("Found match")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
current := vm.bfsSnapshotTree(q, compareID)
|
||||
if current == nil {
|
||||
t.Errorf("Should found current snapshot")
|
||||
}
|
||||
q = list.New()
|
||||
for _, c := range rootList {
|
||||
q.PushBack(c)
|
||||
}
|
||||
|
||||
ref = &types.ManagedObjectReference{
|
||||
Type: "Snapshot",
|
||||
Value: "185",
|
||||
}
|
||||
current = vm.bfsSnapshotTree(q, compareID)
|
||||
if current != nil {
|
||||
t.Errorf("Should not found snapshot")
|
||||
}
|
||||
|
||||
name := "12131"
|
||||
compareName := func(node types.VirtualMachineSnapshotTree) bool {
|
||||
if node.Name == name {
|
||||
t.Logf("Found match")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
q = list.New()
|
||||
for _, c := range rootList {
|
||||
q.PushBack(c)
|
||||
}
|
||||
found := vm.bfsSnapshotTree(q, compareName)
|
||||
if found == nil {
|
||||
t.Errorf("Should found snapshot %q", name)
|
||||
}
|
||||
q = list.New()
|
||||
for _, c := range rootList {
|
||||
q.PushBack(c)
|
||||
}
|
||||
name = "185"
|
||||
found = vm.bfsSnapshotTree(q, compareName)
|
||||
if found != nil {
|
||||
t.Errorf("Should not found snapshot")
|
||||
}
|
||||
}
|
||||
|
||||
// TestProperties test vm.properties happy path and fix vm path
|
||||
func TestIsFixing(t *testing.T) {
|
||||
mo := types.ManagedObjectReference{Type: "vm", Value: "12"}
|
||||
v := object.NewVirtualMachine(nil, mo)
|
||||
vm := NewVirtualMachineFromVM(nil, nil, v)
|
||||
assert.False(t, vm.IsFixing(), "new vm should not in fixing status")
|
||||
vm.EnterFixingState()
|
||||
assert.True(t, vm.IsFixing(), "vm should be in fixing status")
|
||||
vm.EnterFixingState()
|
||||
assert.True(t, vm.IsFixing(), "vm should be in fixing status")
|
||||
vm.LeaveFixingState()
|
||||
assert.False(t, vm.IsFixing(), "vm should not be in fixing status")
|
||||
}
|
||||
|
||||
// TestProperties test vm.properties happy path and fix vm path
|
||||
func TestProperties(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Nothing VC specific in this test, so we use the simpler ESX model
|
||||
model := simulator.ESX()
|
||||
model.Autostart = false
|
||||
defer model.Remove()
|
||||
err := model.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
server := model.Service.NewServer()
|
||||
defer server.Close()
|
||||
client, err := govmomi.NewClient(ctx, server.URL, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Any VM will do
|
||||
finder := find.NewFinder(client.Client, false)
|
||||
vmo, err := finder.VirtualMachine(ctx, "/ha-datacenter/vm/*_VM0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config := &session.Config{
|
||||
Service: server.URL.String(),
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
DatacenterPath: "",
|
||||
DatastorePath: "/ha-datacenter/datastore/*",
|
||||
HostPath: "/ha-datacenter/host/*/*",
|
||||
PoolPath: "/ha-datacenter/host/*/Resources",
|
||||
}
|
||||
|
||||
s, err := session.NewSession(config).Connect(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s.Populate(ctx)
|
||||
vmm := NewVirtualMachine(ctx, s, vmo.Reference())
|
||||
// Test the success path
|
||||
var o mo.VirtualMachine
|
||||
err = vmm.Properties(ctx, vmo.Reference(), []string{"config", "summary", "resourcePool", "parentVApp"}, &o)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// // Inject invalid connection state to vm
|
||||
ref := simulator.Map.Get(vmo.Reference()).(*simulator.VirtualMachine)
|
||||
ref.Summary.Config.VmPathName = ref.Config.Files.VmPathName
|
||||
ref.Summary.Runtime.ConnectionState = types.VirtualMachineConnectionStateInvalid
|
||||
|
||||
err = vmm.Properties(ctx, vmo.Reference(), []string{"config", "summary"}, &o)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.True(t, o.Summary.Runtime.ConnectionState != types.VirtualMachineConnectionStateInvalid, "vm state should be fixed")
|
||||
}
|
||||
|
||||
// TestWaitForResult covers the success path and invalid vm fix path
|
||||
func TestWaitForResult(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Nothing VC specific in this test, so we use the simpler ESX model
|
||||
model := simulator.ESX()
|
||||
model.Autostart = false
|
||||
defer model.Remove()
|
||||
err := model.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
server := model.Service.NewServer()
|
||||
defer server.Close()
|
||||
|
||||
client, err := govmomi.NewClient(ctx, server.URL, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Any VM will do
|
||||
finder := find.NewFinder(client.Client, false)
|
||||
vmo, err := finder.VirtualMachine(ctx, "/ha-datacenter/vm/*_VM0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config := &session.Config{
|
||||
Service: server.URL.String(),
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
DatacenterPath: "",
|
||||
DatastorePath: "/ha-datacenter/datastore/*",
|
||||
HostPath: "/ha-datacenter/host/*/*",
|
||||
PoolPath: "/ha-datacenter/host/*/Resources",
|
||||
}
|
||||
|
||||
s, err := session.NewSession(config).Connect(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s.Populate(ctx)
|
||||
vmm := NewVirtualMachine(ctx, s, vmo.Reference())
|
||||
// Test the success path
|
||||
_, err = vmm.WaitForResult(ctx, func(ctx context.Context) (tasks.Task, error) {
|
||||
return vmm.PowerOn(ctx)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = vmm.WaitForResult(ctx, func(ctx context.Context) (tasks.Task, error) {
|
||||
return vmm.PowerOff(ctx)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ref := simulator.Map.Get(vmm.Reference()).(*simulator.VirtualMachine)
|
||||
|
||||
// Test task failed, but vm is not in invalid state
|
||||
called := 0
|
||||
_, err = vmm.WaitForResult(ctx, func(ctx context.Context) (tasks.Task, error) {
|
||||
called++
|
||||
return vmm.PowerOff(ctx)
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Should have error")
|
||||
}
|
||||
assert.True(t, called == 1, "task should not be retried")
|
||||
|
||||
// Test task failure with invalid state vm
|
||||
ref.Summary.Config.VmPathName = ref.Config.Files.VmPathName
|
||||
ref.Summary.Runtime.ConnectionState = types.VirtualMachineConnectionStateInvalid
|
||||
called = 0
|
||||
_, err = vmm.WaitForResult(ctx, func(ctx context.Context) (tasks.Task, error) {
|
||||
called++
|
||||
return vmm.PowerOff(ctx)
|
||||
})
|
||||
assert.True(t, called == 2, "task should be retried once")
|
||||
assert.True(t, !vmm.IsInvalidState(ctx), "vm state should be fixed")
|
||||
}
|
||||
|
||||
// SetUpdateStatus sets the VCH upgrade/configure status.
|
||||
func SetUpdateStatus(ctx context.Context, updateStatus string, vm *VirtualMachine) error {
|
||||
info := make(map[string]string)
|
||||
info[UpdateStatus] = updateStatus
|
||||
|
||||
s := &types.VirtualMachineConfigSpec{
|
||||
ExtraConfig: vmomi.OptionValueFromMap(info, true),
|
||||
}
|
||||
|
||||
_, err := vm.WaitForResult(ctx, func(ctx context.Context) (tasks.Task, error) {
|
||||
return vm.Reconfigure(ctx, *s)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestVCHUpdateStatus tests if VCHUpdateStatus() could obtain the correct VCH upgrade/configure status
|
||||
func TestVCHUpdateStatus(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Nothing VC specific in this test, so we use the simpler ESX model
|
||||
model := simulator.ESX()
|
||||
defer model.Remove()
|
||||
err := model.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
server := model.Service.NewServer()
|
||||
defer server.Close()
|
||||
client, err := govmomi.NewClient(ctx, server.URL, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Any VM will do
|
||||
finder := find.NewFinder(client.Client, false)
|
||||
vmo, err := finder.VirtualMachine(ctx, "/ha-datacenter/vm/*_VM0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config := &session.Config{
|
||||
Service: server.URL.String(),
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
DatacenterPath: "",
|
||||
DatastorePath: "/ha-datacenter/datastore/*",
|
||||
HostPath: "/ha-datacenter/host/*/*",
|
||||
PoolPath: "/ha-datacenter/host/*/Resources",
|
||||
}
|
||||
|
||||
s, err := session.NewSession(config).Connect(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s.Populate(ctx)
|
||||
vmm := NewVirtualMachine(ctx, s, vmo.Reference())
|
||||
|
||||
updateStatus, err := vmm.VCHUpdateStatus(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
assert.False(t, updateStatus, "updateStatus should be false if UpdateInProgress is not set in the VCH's ExtraConfig")
|
||||
|
||||
// Set UpdateInProgress to false
|
||||
SetUpdateStatus(ctx, "false", vmm)
|
||||
|
||||
updateStatus, err = vmm.VCHUpdateStatus(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
assert.False(t, updateStatus, "updateStatus should be false since UpdateInProgress is set to false")
|
||||
|
||||
// Set UpdateInProgress to true
|
||||
SetUpdateStatus(ctx, "true", vmm)
|
||||
|
||||
updateStatus, err = vmm.VCHUpdateStatus(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
assert.True(t, updateStatus, "updateStatus should be true since UpdateInProgress is set to true")
|
||||
|
||||
// Set UpdateInProgress to NonBool
|
||||
SetUpdateStatus(ctx, "NonBool", vmm)
|
||||
|
||||
updateStatus, err = vmm.VCHUpdateStatus(ctx)
|
||||
if assert.Error(t, err, "An error was expected") {
|
||||
assert.Contains(t, err.Error(), "failed to parse", "Error msg should contain 'failed to parse' since UpdateInProgress is set to NonBool")
|
||||
}
|
||||
}
|
||||
|
||||
// TestSetVCHUpdateStatus tests if SetVCHUpdateStatus() could set the VCH upgrade/configure status correctly
|
||||
func TestSetVCHUpdateStatus(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Nothing VC specific in this test, so we use the simpler ESX model
|
||||
model := simulator.ESX()
|
||||
defer model.Remove()
|
||||
err := model.Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
server := model.Service.NewServer()
|
||||
defer server.Close()
|
||||
client, err := govmomi.NewClient(ctx, server.URL, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Any VM will do
|
||||
finder := find.NewFinder(client.Client, false)
|
||||
vmo, err := finder.VirtualMachine(ctx, "/ha-datacenter/vm/*_VM0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config := &session.Config{
|
||||
Service: server.URL.String(),
|
||||
Insecure: true,
|
||||
Keepalive: time.Duration(5) * time.Minute,
|
||||
DatacenterPath: "",
|
||||
DatastorePath: "/ha-datacenter/datastore/*",
|
||||
HostPath: "/ha-datacenter/host/*/*",
|
||||
PoolPath: "/ha-datacenter/host/*/Resources",
|
||||
}
|
||||
|
||||
s, err := session.NewSession(config).Connect(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s.Populate(ctx)
|
||||
vmm := NewVirtualMachine(ctx, s, vmo.Reference())
|
||||
|
||||
// Set UpdateInProgress to true and then check status
|
||||
err = vmm.SetVCHUpdateStatus(ctx, true)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
|
||||
info, err := vmm.FetchExtraConfig(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
|
||||
v, ok := info[UpdateStatus]
|
||||
if ok {
|
||||
assert.Equal(t, "true", v, "UpdateInProgress should be true")
|
||||
} else {
|
||||
t.Fatal("ERROR: UpdateInProgress does not exist in ExtraConfig")
|
||||
}
|
||||
|
||||
// Set UpdateInProgress to false and then check status
|
||||
err = vmm.SetVCHUpdateStatus(ctx, false)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
|
||||
info, err = vmm.FetchExtraConfig(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ERROR: %s", err)
|
||||
}
|
||||
|
||||
v, ok = info[UpdateStatus]
|
||||
if ok {
|
||||
assert.Equal(t, "false", v, "UpdateInProgress should be false")
|
||||
} else {
|
||||
t.Fatal("ERROR: UpdateInProgress does not exist in ExtraConfig")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user