blob: a6e29c2c563426ca544068fd06d6c36014be05a6 [file] [log] [blame]
/*
Copyright (c) 2017-2018 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package object
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"log"
"path"
"strings"
"github.com/vmware/govmomi/vim25/progress"
"github.com/vmware/govmomi/vim25/soap"
)
// DatastoreFileManager combines FileManager and VirtualDiskManager to manage files on a Datastore
type DatastoreFileManager struct {
Datacenter *Datacenter
Datastore *Datastore
FileManager *FileManager
VirtualDiskManager *VirtualDiskManager
Force bool
DatacenterTarget *Datacenter
}
// NewFileManager creates a new instance of DatastoreFileManager
func (d Datastore) NewFileManager(dc *Datacenter, force bool) *DatastoreFileManager {
c := d.Client()
m := &DatastoreFileManager{
Datacenter: dc,
Datastore: &d,
FileManager: NewFileManager(c),
VirtualDiskManager: NewVirtualDiskManager(c),
Force: force,
DatacenterTarget: dc,
}
return m
}
func (m *DatastoreFileManager) WithProgress(ctx context.Context, s progress.Sinker) context.Context {
return context.WithValue(ctx, m, s)
}
func (m *DatastoreFileManager) wait(ctx context.Context, task *Task) error {
var logger progress.Sinker
if s, ok := ctx.Value(m).(progress.Sinker); ok {
logger = s
}
_, err := task.WaitForResult(ctx, logger)
return err
}
// Delete dispatches to the appropriate Delete method based on file name extension
func (m *DatastoreFileManager) Delete(ctx context.Context, name string) error {
switch path.Ext(name) {
case ".vmdk":
return m.DeleteVirtualDisk(ctx, name)
default:
return m.DeleteFile(ctx, name)
}
}
// DeleteFile calls FileManager.DeleteDatastoreFile
func (m *DatastoreFileManager) DeleteFile(ctx context.Context, name string) error {
p := m.Path(name)
task, err := m.FileManager.DeleteDatastoreFile(ctx, p.String(), m.Datacenter)
if err != nil {
return err
}
return m.wait(ctx, task)
}
// DeleteVirtualDisk calls VirtualDiskManager.DeleteVirtualDisk
// Regardless of the Datastore type, DeleteVirtualDisk will fail if 'ddb.deletable=false',
// so if Force=true this method attempts to set 'ddb.deletable=true' before starting the delete task.
func (m *DatastoreFileManager) DeleteVirtualDisk(ctx context.Context, name string) error {
p := m.Path(name)
var merr error
if m.Force {
merr = m.markDiskAsDeletable(ctx, p)
}
task, err := m.VirtualDiskManager.DeleteVirtualDisk(ctx, p.String(), m.Datacenter)
if err != nil {
log.Printf("markDiskAsDeletable(%s): %s", p, merr)
return err
}
return m.wait(ctx, task)
}
// CopyFile calls FileManager.CopyDatastoreFile
func (m *DatastoreFileManager) CopyFile(ctx context.Context, src string, dst string) error {
srcp := m.Path(src)
dstp := m.Path(dst)
task, err := m.FileManager.CopyDatastoreFile(ctx, srcp.String(), m.Datacenter, dstp.String(), m.DatacenterTarget, m.Force)
if err != nil {
return err
}
return m.wait(ctx, task)
}
// Copy dispatches to the appropriate FileManager or VirtualDiskManager Copy method based on file name extension
func (m *DatastoreFileManager) Copy(ctx context.Context, src string, dst string) error {
srcp := m.Path(src)
dstp := m.Path(dst)
f := m.FileManager.CopyDatastoreFile
if srcp.IsVMDK() {
// types.VirtualDiskSpec=nil as it is not implemented by vCenter
f = func(ctx context.Context, src string, srcDC *Datacenter, dst string, dstDC *Datacenter, force bool) (*Task, error) {
return m.VirtualDiskManager.CopyVirtualDisk(ctx, src, srcDC, dst, dstDC, nil, force)
}
}
task, err := f(ctx, srcp.String(), m.Datacenter, dstp.String(), m.DatacenterTarget, m.Force)
if err != nil {
return err
}
return m.wait(ctx, task)
}
// MoveFile calls FileManager.MoveDatastoreFile
func (m *DatastoreFileManager) MoveFile(ctx context.Context, src string, dst string) error {
srcp := m.Path(src)
dstp := m.Path(dst)
task, err := m.FileManager.MoveDatastoreFile(ctx, srcp.String(), m.Datacenter, dstp.String(), m.DatacenterTarget, m.Force)
if err != nil {
return err
}
return m.wait(ctx, task)
}
// Move dispatches to the appropriate FileManager or VirtualDiskManager Move method based on file name extension
func (m *DatastoreFileManager) Move(ctx context.Context, src string, dst string) error {
srcp := m.Path(src)
dstp := m.Path(dst)
f := m.FileManager.MoveDatastoreFile
if srcp.IsVMDK() {
f = m.VirtualDiskManager.MoveVirtualDisk
}
task, err := f(ctx, srcp.String(), m.Datacenter, dstp.String(), m.DatacenterTarget, m.Force)
if err != nil {
return err
}
return m.wait(ctx, task)
}
// Path converts path name to a DatastorePath
func (m *DatastoreFileManager) Path(name string) *DatastorePath {
var p DatastorePath
if !p.FromString(name) {
p.Path = name
p.Datastore = m.Datastore.Name()
}
return &p
}
func (m *DatastoreFileManager) markDiskAsDeletable(ctx context.Context, path *DatastorePath) error {
r, _, err := m.Datastore.Download(ctx, path.Path, &soap.DefaultDownload)
if err != nil {
return err
}
defer r.Close()
hasFlag := false
buf := new(bytes.Buffer)
s := bufio.NewScanner(&io.LimitedReader{R: r, N: 2048}) // should be only a few hundred bytes, limit to be sure
for s.Scan() {
line := s.Text()
if strings.HasPrefix(line, "ddb.deletable") {
hasFlag = true
continue
}
fmt.Fprintln(buf, line)
}
if err := s.Err(); err != nil {
return err // any error other than EOF
}
if !hasFlag {
return nil // already deletable, so leave as-is
}
// rewrite the .vmdk with ddb.deletable flag removed (the default is true)
return m.Datastore.Upload(ctx, buf, path.Path, &soap.DefaultUpload)
}