github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/provider/vmware/ovf_import_manager.go (about)

     1  package vmware
     2  
     3  import (
     4  	"encoding/base64"
     5  	"io"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"net/url"
     9  	"strings"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/vmware/govmomi"
    13  	"github.com/vmware/govmomi/vim25/progress"
    14  	"github.com/vmware/govmomi/vim25/soap"
    15  	"github.com/vmware/govmomi/vim25/types"
    16  
    17  	"github.com/juju/juju/instance"
    18  	"github.com/juju/juju/provider/common"
    19  )
    20  
    21  type ovfFileItem struct {
    22  	url  *url.URL
    23  	item types.OvfFileItem
    24  	ch   chan progress.Report
    25  }
    26  
    27  func (o ovfFileItem) Sink() chan<- progress.Report {
    28  	return o.ch
    29  }
    30  
    31  type ovfImportManager struct {
    32  	client *client
    33  }
    34  
    35  func (m *ovfImportManager) importOvf(machineID string, hwc *instance.HardwareCharacteristics, img *OvfFileMetadata, userData []byte, sshKey string, isState bool) (*govmomi.VirtualMachine, error) {
    36  	folders, err := m.client.datacenter.Folders()
    37  	if err != nil {
    38  		return nil, errors.Trace(err)
    39  	}
    40  
    41  	ovf, err := m.downloadOvf(img.Url)
    42  	if err != nil {
    43  		return nil, errors.Trace(err)
    44  	}
    45  
    46  	cisp := types.OvfCreateImportSpecParams{
    47  		EntityName: machineID,
    48  		PropertyMapping: []types.KeyValue{
    49  			types.KeyValue{Key: "public-keys", Value: sshKey},
    50  			types.KeyValue{Key: "user-data", Value: base64.StdEncoding.EncodeToString(userData)},
    51  		},
    52  	}
    53  
    54  	spec, err := m.client.connection.OvfManager().CreateImportSpec(string(ovf), m.client.resourcePool, m.client.datastore, cisp)
    55  	if err != nil {
    56  		return nil, errors.Trace(err)
    57  	}
    58  
    59  	if spec.Error != nil {
    60  		return nil, errors.New(spec.Error[0].LocalizedMessage)
    61  	}
    62  	s := &spec.ImportSpec.(*types.VirtualMachineImportSpec).ConfigSpec
    63  	s.NumCPUs = int(*hwc.CpuCores)
    64  	s.MemoryMB = int64(*hwc.Mem)
    65  	s.CpuAllocation = &types.ResourceAllocationInfo{
    66  		Limit:       int64(*hwc.CpuPower),
    67  		Reservation: int64(*hwc.CpuPower),
    68  	}
    69  	/*if isState {
    70  		s.ExtraConfig = append(s.ExtraConfig, &types.OptionValue{Key: metadataKeyIsState, Value: ""})
    71  	}*/
    72  	for _, d := range s.DeviceChange {
    73  		if disk, ok := d.GetVirtualDeviceConfigSpec().Device.(*types.VirtualDisk); ok {
    74  			disk.CapacityInKB = int64(common.MBToKB(*hwc.RootDisk))
    75  		}
    76  		//Set UnitNumber to -1 is it is unset in ovf file template (in this case it is parces as 0)
    77  		//but 0 causes an error for some devices
    78  		n := &d.GetVirtualDeviceConfigSpec().Device.GetVirtualDevice().UnitNumber
    79  		if *n == 0 {
    80  			*n = -1
    81  		}
    82  	}
    83  
    84  	lease, err := m.client.resourcePool.ImportVApp(spec.ImportSpec, folders.VmFolder, nil)
    85  	if err != nil {
    86  		return nil, errors.Annotatef(err, "Error while importing vapp")
    87  	}
    88  
    89  	info, err := lease.Wait()
    90  	if err != nil {
    91  		return nil, errors.Trace(err)
    92  	}
    93  	items := []ovfFileItem{}
    94  	for _, device := range info.DeviceUrl {
    95  		for _, item := range spec.FileItem {
    96  			if device.ImportKey != item.DeviceId {
    97  				continue
    98  			}
    99  
   100  			u, err := m.client.connection.Client.ParseURL(device.Url)
   101  			if err != nil {
   102  				return nil, errors.Trace(err)
   103  			}
   104  
   105  			i := ovfFileItem{
   106  				url:  u,
   107  				item: item,
   108  				ch:   make(chan progress.Report),
   109  			}
   110  			items = append(items, i)
   111  		}
   112  	}
   113  
   114  	for _, i := range items {
   115  		ind := strings.LastIndex(img.Url, "/")
   116  		err = m.uploadFileItem(i, img.Url[:ind])
   117  		if err != nil {
   118  			return nil, errors.Trace(err)
   119  		}
   120  	}
   121  	lease.HttpNfcLeaseComplete()
   122  	return govmomi.NewVirtualMachine(m.client.connection, info.Entity), nil
   123  }
   124  
   125  func (m *ovfImportManager) downloadOvf(url string) (string, error) {
   126  	logger.Debugf("Downloading ovf file from url: %s", url)
   127  	resp, err := http.Get(url)
   128  	if err != nil {
   129  		return "", errors.Trace(err)
   130  	}
   131  	defer resp.Body.Close()
   132  	if resp.StatusCode != http.StatusOK {
   133  		return "", errors.Errorf("Can't download ovf file from url: %s, status: %s", url, resp.StatusCode)
   134  	}
   135  	bytes, err := ioutil.ReadAll(resp.Body)
   136  	if err != nil {
   137  		return "", errors.Trace(err)
   138  	}
   139  	return string(bytes), nil
   140  }
   141  
   142  func (m *ovfImportManager) uploadFileItem(ofi ovfFileItem, baseUrl string) error {
   143  	f, err := m.downloadFileItem(strings.Join([]string{baseUrl, ofi.item.Path}, "/"))
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	defer f.Close()
   149  
   150  	opts := soap.Upload{
   151  		ContentLength: ofi.item.Size,
   152  		Progress:      ofi,
   153  	}
   154  
   155  	opts.Method = "POST"
   156  	opts.Type = "application/x-vnd.vmware-streamVmdk"
   157  	logger.Debugf("Uploading image to %s", ofi.url)
   158  	go func() {
   159  		lastPercent := 0
   160  		for pr := <-ofi.ch; pr != nil; pr = <-ofi.ch {
   161  			curPercent := int(pr.Percentage())
   162  			if curPercent-lastPercent >= 10 {
   163  				lastPercent = curPercent
   164  				logger.Debugf("Progress: %d%", lastPercent)
   165  			}
   166  		}
   167  	}()
   168  	err = m.client.connection.Client.Upload(f, ofi.url, &opts)
   169  	if err == nil {
   170  		logger.Debugf("Image uploaded")
   171  	} else {
   172  		err = errors.Trace(err)
   173  	}
   174  
   175  	return err
   176  }
   177  
   178  func (m *ovfImportManager) downloadFileItem(url string) (io.ReadCloser, error) {
   179  	logger.Debugf("Downloading file from url %s", url)
   180  
   181  	resp, err := http.Get(url)
   182  	if err != nil {
   183  		return nil, errors.Trace(err)
   184  	}
   185  	if resp.StatusCode != 200 {
   186  		resp.Body.Close()
   187  		return nil, errors.Errorf("Can't download file from url: %s, status: %s", url, resp.StatusCode)
   188  	}
   189  
   190  	return resp.Body, nil
   191  }