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 }