github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/vsphere/internal/vsphereclient/vmdk.go (about) 1 // Copyright 2015-2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package vsphereclient 5 6 import ( 7 "archive/tar" 8 "context" 9 "crypto/sha256" 10 "fmt" 11 "io" 12 "path" 13 "strings" 14 "time" 15 16 "github.com/juju/clock" 17 "github.com/juju/errors" 18 "github.com/juju/mutex" 19 "github.com/vmware/govmomi/object" 20 "github.com/vmware/govmomi/vim25/progress" 21 "github.com/vmware/govmomi/vim25/soap" 22 ) 23 24 // ensureVMDK ensures that the VMDK contained within the OVA returned 25 // by args.ReadOVA is either already in the datastore, or else stores it. 26 // 27 // ensureVMDK takes a machine lock for using the VMDKs, and returns 28 // a function to release that lock. The caller must call the release 29 // function once it has finished using the VMDK. 30 func (c *Client) ensureVMDK( 31 ctx context.Context, 32 args CreateVirtualMachineParams, 33 datastore *object.Datastore, 34 datacenter *object.Datacenter, 35 taskWaiter *taskWaiter, 36 ) (datastorePath string, release func(), resultErr error) { 37 38 // Each controller maintains its own image cache. All compute 39 // provisioners (i.e. each model's) run on the same controller 40 // machine, so taking a machine lock ensures that only one 41 // process is updating VMDKs at the same time. We lock around 42 // access to the series directory. 43 mutexReleaser, err := mutex.Acquire(mutex.Spec{ 44 Name: "juju-vsphere-" + args.Series, 45 Clock: args.Clock, 46 Delay: time.Second, 47 }) 48 if err != nil { 49 return "", nil, errors.Annotate(err, "acquiring lock") 50 } 51 defer func() { 52 if release == nil { 53 mutexReleaser.Release() 54 } 55 }() 56 57 // First, check if the VMDK has already been cached. If it hasn't, 58 // but the VMDK directory exists already, we delete it and recreate 59 // it; this is to remove older VMDKs. 60 vmdkDirectory := path.Join(args.VMDKDirectory, args.Series) 61 vmdkFilename := path.Join(vmdkDirectory, args.OVASHA256+".vmdk") 62 vmdkDatastorePath := datastore.Path(vmdkFilename) 63 dirDatastorePath := datastore.Path(vmdkDirectory) 64 fileManager := object.NewFileManager(c.client.Client) 65 if _, err := datastore.Stat(ctx, vmdkFilename); err != nil { 66 switch errors.Cause(err).(type) { 67 case object.DatastoreNoSuchFileError: 68 // Image directory exists. Delete it so we remove any 69 // existing, older VMDK, and then create it below. 70 task, err := fileManager.DeleteDatastoreFile(ctx, dirDatastorePath, datacenter) 71 if err != nil { 72 return "", nil, errors.Trace(err) 73 } 74 if _, err := taskWaiter.waitTask(ctx, task, "deleting image directory"); err != nil { 75 return "", nil, errors.Annotate(err, "deleting image directory") 76 } 77 case object.DatastoreNoSuchDirectoryError: 78 // Image directory doesn't exist; create it below. 79 break 80 default: 81 return "", nil, errors.Trace(err) 82 } 83 if err := fileManager.MakeDirectory(ctx, dirDatastorePath, datacenter, true); err != nil { 84 return "", nil, errors.Annotate(err, "creating image directory") 85 } 86 } else { 87 // The disk has already been uploaded. 88 return vmdkDatastorePath, mutexReleaser.Release, nil 89 } 90 91 // Fetch the OVA, and decode in-memory to find the VMDK stream. 92 // An OVA is a tar archive. 93 ovaLocation, ovaReadCloser, err := args.ReadOVA() 94 if err != nil { 95 return "", nil, errors.Annotate(err, "fetching OVA") 96 } 97 defer ovaReadCloser.Close() 98 99 sha256sum := sha256.New() 100 ovaTarReader := tar.NewReader(io.TeeReader(ovaReadCloser, sha256sum)) 101 var vmdkSize int64 102 for { 103 header, err := ovaTarReader.Next() 104 if err != nil { 105 return "", nil, errors.Annotate(err, "reading OVA") 106 } 107 if strings.HasSuffix(header.Name, ".vmdk") { 108 vmdkSize = header.Size 109 break 110 } 111 } 112 113 // Upload the VMDK, and then convert it to a disk. 114 tempFilename := vmdkFilename + ".tmp" 115 c.logger.Debugf("uploading %s contents to %s", ovaLocation, tempFilename) 116 if err := c.uploadToDatastore( 117 ctx, ovaTarReader, vmdkSize, datastore, tempFilename, 118 args.Clock, args.UpdateProgress, args.UpdateProgressInterval, 119 ); err != nil { 120 return "", nil, errors.Annotate(err, "uploading VMDK to datastore") 121 } 122 123 // Finish reading the rest of the OVA, so we can compute the hash. 124 if _, err := io.Copy(sha256sum, ovaReadCloser); err != nil { 125 return "", nil, errors.Annotate(err, "reading OVA") 126 } 127 if fmt.Sprintf("%x", sha256sum.Sum(nil)) != args.OVASHA256 { 128 return "", nil, errors.New("SHA-256 hash mismatch for OVA") 129 } 130 131 // Move the temporary VMDK into its target location. 132 task, err := fileManager.MoveDatastoreFile( 133 ctx, 134 datastore.Path(tempFilename), 135 datacenter, 136 vmdkDatastorePath, 137 datacenter, 138 true, 139 ) 140 if err != nil { 141 return "", nil, errors.Trace(err) 142 } 143 if _, err := taskWaiter.waitTask(ctx, task, "moving VMDK"); err != nil { 144 return "", nil, errors.Trace(err) 145 } 146 return vmdkDatastorePath, mutexReleaser.Release, nil 147 } 148 149 func (c *Client) uploadToDatastore( 150 ctx context.Context, 151 r io.Reader, 152 size int64, 153 datastore *object.Datastore, 154 filename string, 155 clock clock.Clock, 156 updateProgress func(string), 157 updateProgressInterval time.Duration, 158 ) error { 159 var err error 160 withStatusUpdater( 161 ctx, fmt.Sprintf("uploading %s", filename), 162 clock, updateProgress, updateProgressInterval, 163 func(ctx context.Context, s progress.Sinker) { 164 p := soap.DefaultUpload 165 p.Progress = s 166 p.ContentLength = size 167 err = datastore.Upload(ctx, r, filename, &p) 168 }, 169 ) 170 return errors.Annotate(err, "uploading VMDK to datastore") 171 }