VMware vSphere Integrated Containers provider (#206)
* Add Virtual Kubelet provider for VIC Initial virtual kubelet provider for VMware VIC. This provider currently handles creating and starting of a pod VM via the VIC portlayer and persona server. Image store handling via the VIC persona server. This provider currently requires the feature/wolfpack branch of VIC. * Added pod stop and delete. Also added node capacity. Added the ability to stop and delete pod VMs via VIC. Also retrieve node capacity information from the VCH. * Cleanup and readme file Some file clean up and added a Readme.md markdown file for the VIC provider. * Cleaned up errors, added function comments, moved operation code 1. Cleaned up error handling. Set standard for creating errors. 2. Added method prototype comments for all interface functions. 3. Moved PodCreator, PodStarter, PodStopper, and PodDeleter to a new folder. * Add mocking code and unit tests for podcache, podcreator, and podstarter Used the unit test framework used in VIC to handle assertions in the provider's unit test. Mocking code generated using OSS project mockery, which is compatible with the testify assertion framework. * Vendored packages for the VIC provider Requires feature/wolfpack branch of VIC and a few specific commit sha of projects used within VIC. * Implementation of POD Stopper and Deleter unit tests (#4) * Updated files for initial PR
This commit is contained in:
102
vendor/github.com/vmware/vic/pkg/vsphere/disk/config.go
generated
vendored
Normal file
102
vendor/github.com/vmware/vic/pkg/vsphere/disk/config.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
// 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
Normal file
473
vendor/github.com/vmware/vic/pkg/vsphere/disk/disk.go
generated
vendored
Normal file
@@ -0,0 +1,473 @@
|
||||
// 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
Normal file
292
vendor/github.com/vmware/vic/pkg/vsphere/disk/disk_ext4_test.go
generated
vendored
Normal file
@@ -0,0 +1,292 @@
|
||||
// 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
Normal file
668
vendor/github.com/vmware/vic/pkg/vsphere/disk/disk_manager.go
generated
vendored
Normal file
@@ -0,0 +1,668 @@
|
||||
// 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
Normal file
739
vendor/github.com/vmware/vic/pkg/vsphere/disk/disk_manager_test.go
generated
vendored
Normal file
@@ -0,0 +1,739 @@
|
||||
// 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
Normal file
238
vendor/github.com/vmware/vic/pkg/vsphere/disk/lazy_detach_test.go
generated
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
// 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
Normal file
339
vendor/github.com/vmware/vic/pkg/vsphere/disk/util.go
generated
vendored
Normal file
@@ -0,0 +1,339 @@
|
||||
// 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
Normal file
33
vendor/github.com/vmware/vic/pkg/vsphere/disk/util_test.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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
Normal file
125
vendor/github.com/vmware/vic/pkg/vsphere/disk/vmdk.go
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
// 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
Normal file
60
vendor/github.com/vmware/vic/pkg/vsphere/disk/vmdk_test.go
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user