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  }