| /* |
| 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) |
| } |