Skip to content

Commit 4e9bed3

Browse files
committed
Embed OVF
1 parent c4fe14e commit 4e9bed3

File tree

4 files changed

+441
-155
lines changed

4 files changed

+441
-155
lines changed

provider/vsphere/environ_broker.go

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ package vsphere
55

66
import (
77
"fmt"
8-
"io/ioutil"
9-
"os"
108
"path"
119
"sync"
1210
"time"
@@ -170,20 +168,13 @@ func (env *sessionEnviron) newRawInstance(
170168
updateProgress := func(message string) {
171169
args.StatusCallback(status.Provisioning, message, nil)
172170
}
173-
ovaDir, ovf, ovaCleanup, err := env.prepareOVA(img, args.InstanceConfig, updateProgress)
174-
if err != nil {
175-
return nil, nil, errors.Trace(err)
176-
}
177-
defer ovaCleanup()
178171

179172
createVMArgs := vsphereclient.CreateVirtualMachineParams{
180173
Name: vmName,
181174
Folder: path.Join(
182175
controllerFolderName(args.ControllerUUID),
183176
env.modelFolderName(),
184177
),
185-
OVADir: ovaDir,
186-
OVF: string(ovf),
187178
UserData: string(userData),
188179
Metadata: args.InstanceConfig.Tags,
189180
Constraints: cons,
@@ -230,61 +221,6 @@ func (env *sessionEnviron) newRawInstance(
230221
return vm, hw, err
231222
}
232223

233-
// prepareOVA downloads and extracts the OVA, and reads the contents of the
234-
// .ovf file contained within it.
235-
func (env *environ) prepareOVA(
236-
img *OvaFileMetadata,
237-
instanceConfig *instancecfg.InstanceConfig,
238-
updateProgress func(string),
239-
) (ovaDir, ovf string, cleanup func(), err error) {
240-
fail := func(err error) (string, string, func(), error) {
241-
return "", "", cleanup, errors.Trace(err)
242-
}
243-
defer func() {
244-
if err != nil && cleanup != nil {
245-
cleanup()
246-
}
247-
}()
248-
249-
var ovaBaseDir string
250-
if instanceConfig.Bootstrap != nil {
251-
ovaTempDir, err := ioutil.TempDir("", "juju-ova")
252-
if err != nil {
253-
return fail(errors.Trace(err))
254-
}
255-
cleanup = func() {
256-
if err := os.RemoveAll(ovaTempDir); err != nil {
257-
logger.Warningf("failed to remove temp directory: %s", err)
258-
}
259-
}
260-
ovaBaseDir = ovaTempDir
261-
} else {
262-
// Lock the OVA cache directory for the remainder of the
263-
// provisioning process. It's not enough to lock just
264-
// around or in downloadOVA, because we refer to the
265-
// contents after it returns.
266-
unlock, err := env.provider.ovaCacheLocker.Lock()
267-
if err != nil {
268-
return fail(errors.Annotate(err, "locking OVA cache dir"))
269-
}
270-
cleanup = unlock
271-
ovaBaseDir = env.provider.ovaCacheDir
272-
}
273-
274-
ovaDir, ovfPath, err := downloadOVA(
275-
ovaBaseDir, instanceConfig.Series, img, updateProgress,
276-
)
277-
if err != nil {
278-
return fail(errors.Trace(err))
279-
}
280-
ovfBytes, err := ioutil.ReadFile(ovfPath)
281-
if err != nil {
282-
return fail(errors.Trace(err))
283-
}
284-
285-
return ovaDir, string(ovfBytes), cleanup, nil
286-
}
287-
288224
// AllInstances implements environs.InstanceBroker.
289225
func (env *environ) AllInstances() (instances []instance.Instance, err error) {
290226
err = env.withSession(func(env *sessionEnviron) error {

provider/vsphere/internal/vsphereclient/createvm.go

Lines changed: 77 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import (
3131
"github.com/juju/juju/worker/catacomb"
3232
)
3333

34+
//go:generate go run ../../../../generate/filetoconst/filetoconst.go UbuntuOVF ubuntu.ovf ovf_ubuntu.go 2017 vsphereclient
35+
3436
// CreateVirtualMachineParams contains the parameters required for creating
3537
// a new virtual machine.
3638
type CreateVirtualMachineParams struct {
@@ -42,11 +44,8 @@ type CreateVirtualMachineParams struct {
4244
// in which to create the VM.
4345
Folder string
4446

45-
// OVAContentsDir is the directory containing the extracted OVA contents.
46-
OVADir string
47-
48-
// OVF contains the OVF content.
49-
OVF string
47+
// VMDK is the URL to the VMDK to use.
48+
//VMDK *url.URL
5049

5150
// UserData is the cloud-init user-data.
5251
UserData string
@@ -90,6 +89,7 @@ type CreateVirtualMachineParams struct {
9089

9190
// CreateVirtualMachine creates and powers on a new VM.
9291
//
92+
// TODO(axw) revise below
9393
// This method imports an OVF template using the vSphere API. This process
9494
// comprises the following steps:
9595
// 1. Download the OVA archive, extract it, and load the OVF file contained
@@ -114,12 +114,6 @@ func (c *Client) CreateVirtualMachine(
114114
args CreateVirtualMachineParams,
115115
) (*mo.VirtualMachine, error) {
116116

117-
args.UpdateProgress("creating import spec")
118-
spec, err := c.createImportSpec(ctx, args)
119-
if err != nil {
120-
return nil, errors.Annotate(err, "creating import spec")
121-
}
122-
123117
// Locate the folder in which to create the VM.
124118
finder, datacenter, err := c.finder(ctx)
125119
if err != nil {
@@ -135,66 +129,31 @@ func (c *Client) CreateVirtualMachine(
135129
return nil, errors.Trace(err)
136130
}
137131

132+
// Select the datastore.
133+
datastoreMo, err := c.selectDatastore(ctx, args)
134+
if err != nil {
135+
return nil, errors.Trace(err)
136+
}
137+
datastore := object.NewDatastore(c.client.Client, datastoreMo.Reference())
138+
datastore.SetInventoryPath(path.Join(folders.DatastoreFolder.InventoryPath, datastoreMo.Name))
139+
138140
// Import the VApp.
141+
args.UpdateProgress("creating import spec")
142+
spec, err := c.createImportSpec(ctx, args, datastore)
143+
if err != nil {
144+
return nil, errors.Annotate(err, "creating import spec")
145+
}
139146
args.UpdateProgress(fmt.Sprintf("creating VM %q", args.Name))
140147
c.logger.Debugf("creating VM in folder %s", vmFolder)
141148
rp := object.NewResourcePool(c.client.Client, *args.ComputeResource.ResourcePool)
142149
lease, err := rp.ImportVApp(ctx, spec.ImportSpec, vmFolder, nil)
143150
if err != nil {
144151
return nil, errors.Annotatef(err, "failed to import vapp")
145152
}
146-
147-
// Upload the VMDK.
148-
info, err := lease.Wait(ctx, spec.FileItem)
153+
info, err := lease.Wait(ctx, nil)
149154
if err != nil {
150155
return nil, errors.Trace(err)
151156
}
152-
type uploadItem struct {
153-
item types.OvfFileItem
154-
url *url.URL
155-
}
156-
var uploadItems []uploadItem
157-
for _, device := range info.DeviceUrl {
158-
for _, item := range spec.FileItem {
159-
if device.ImportKey != item.DeviceId {
160-
continue
161-
}
162-
u, err := c.client.Client.ParseURL(device.Url)
163-
if err != nil {
164-
return nil, errors.Trace(err)
165-
}
166-
uploadItems = append(uploadItems, uploadItem{
167-
item: item,
168-
url: u,
169-
})
170-
}
171-
}
172-
leaseUpdaterContext := leaseUpdaterContext{lease: lease}
173-
for _, item := range uploadItems {
174-
leaseUpdaterContext.total += item.item.Size
175-
}
176-
for _, item := range uploadItems {
177-
leaseUpdaterContext.size = item.item.Size
178-
if err := uploadImage(
179-
ctx,
180-
c.client.Client,
181-
item.item,
182-
args.OVADir,
183-
item.url,
184-
args.UpdateProgress,
185-
args.UpdateProgressInterval,
186-
leaseUpdaterContext,
187-
args.Clock,
188-
c.logger,
189-
); err != nil {
190-
return nil, errors.Annotatef(
191-
err, "uploading %s to %s",
192-
filepath.Base(item.item.Path),
193-
item.url,
194-
)
195-
}
196-
leaseUpdaterContext.start += leaseUpdaterContext.size
197-
}
198157
if err := lease.Complete(ctx); err != nil {
199158
return nil, errors.Trace(err)
200159
}
@@ -220,6 +179,7 @@ func (c *Client) CreateVirtualMachine(
220179
func (c *Client) createImportSpec(
221180
ctx context.Context,
222181
args CreateVirtualMachineParams,
182+
datastore *object.Datastore,
223183
) (*types.OvfCreateImportSpecResult, error) {
224184
cisp := types.OvfCreateImportSpecParams{
225185
EntityName: args.Name,
@@ -256,12 +216,8 @@ func (c *Client) createImportSpec(
256216

257217
ovfManager := ovf.NewManager(c.client.Client)
258218
resourcePool := object.NewReference(c.client.Client, *args.ComputeResource.ResourcePool)
259-
datastore, err := c.selectDatastore(ctx, args)
260-
if err != nil {
261-
return nil, errors.Trace(err)
262-
}
263219

264-
spec, err := ovfManager.CreateImportSpec(ctx, args.OVF, resourcePool, datastore, cisp)
220+
spec, err := ovfManager.CreateImportSpec(ctx, UbuntuOVF, resourcePool, datastore, cisp)
265221
if err != nil {
266222
return nil, errors.Trace(err)
267223
} else if spec.Error != nil {
@@ -283,31 +239,13 @@ func (c *Client) createImportSpec(
283239
Reservation: cpuPower,
284240
}
285241
}
286-
for _, d := range s.DeviceChange {
287-
disk, ok := d.GetVirtualDeviceConfigSpec().Device.(*types.VirtualDisk)
288-
if !ok {
289-
continue
290-
}
291-
var rootDisk int64
292-
if args.Constraints.RootDisk != nil {
293-
rootDisk = int64(*args.Constraints.RootDisk) * 1024
294-
}
295-
if disk.CapacityInKB < rootDisk {
296-
disk.CapacityInKB = rootDisk
297-
}
298-
// Set UnitNumber to -1 if it is unset in ovf file template
299-
// (in this case it is parses as 0), because 0 causes an error
300-
// for disk devices.
301-
var unitNumber int32
302-
if disk.UnitNumber != nil {
303-
unitNumber = *disk.UnitNumber
304-
}
305-
if unitNumber == 0 {
306-
unitNumber = -1
307-
disk.UnitNumber = &unitNumber
308-
}
242+
if err := c.addRootDisk(s, args, datastore); err != nil {
243+
return nil, errors.Trace(err)
309244
}
310245

246+
// We don't upload the VMDK, so clear out the file items.
247+
spec.FileItem = nil
248+
311249
// Apply metadata. Note that we do not have the ability set create or
312250
// apply tags that will show up in vCenter, as that requires a separate
313251
// vSphere Automation that we do not have an SDK for.
@@ -329,10 +267,58 @@ func (c *Client) createImportSpec(
329267
return spec, nil
330268
}
331269

270+
func (c *Client) addRootDisk(
271+
s *types.VirtualMachineConfigSpec,
272+
args CreateVirtualMachineParams,
273+
diskDatastore *object.Datastore,
274+
) error {
275+
// TODO(axw)
276+
vmdkName := "ubuntu-16.04-server-cloudimg-amd64-disk1.vmdk"
277+
vmdkPath := diskDatastore.Path(vmdkName)
278+
ds := diskDatastore.Reference()
279+
280+
for _, d := range s.DeviceChange {
281+
deviceConfigSpec := d.GetVirtualDeviceConfigSpec()
282+
existingDisk, ok := deviceConfigSpec.Device.(*types.VirtualDisk)
283+
if !ok {
284+
continue
285+
}
286+
// Create a linked disk to avoid copying the VMDK for each VM.
287+
parentDisk := &types.VirtualDisk{
288+
VirtualDevice: types.VirtualDevice{
289+
Key: existingDisk.VirtualDevice.Key,
290+
ControllerKey: existingDisk.VirtualDevice.ControllerKey,
291+
UnitNumber: existingDisk.VirtualDevice.UnitNumber,
292+
Backing: &types.VirtualDiskFlatVer2BackingInfo{
293+
DiskMode: string(types.VirtualDiskModePersistent),
294+
ThinProvisioned: types.NewBool(true),
295+
VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
296+
FileName: vmdkPath,
297+
Datastore: &ds,
298+
},
299+
},
300+
},
301+
}
302+
var l object.VirtualDeviceList
303+
disk := l.ChildDisk(parentDisk)
304+
// TODO(axw) override root disk size. We need to
305+
// fetch the size of the existing disk first.
306+
//var rootDiskKB int64
307+
//if args.Constraints.RootDisk != nil {
308+
// rootDiskKB = int64(*args.Constraints.RootDisk) * 1024
309+
//}
310+
//if disk.CapacityInKB < rootDiskKB {
311+
// disk.CapacityInKB = rootDiskKB
312+
//}
313+
deviceConfigSpec.Device = disk
314+
}
315+
return nil
316+
}
317+
332318
func (c *Client) selectDatastore(
333319
ctx context.Context,
334320
args CreateVirtualMachineParams,
335-
) (*object.Datastore, error) {
321+
) (*mo.Datastore, error) {
336322
// Select a datastore. If the user specified one, use that; otherwise
337323
// choose the first one in the list that is accessible.
338324
refs := make([]types.ManagedObjectReference, len(args.ComputeResource.Datastore))
@@ -346,15 +332,15 @@ func (c *Client) selectDatastore(
346332
if args.Datastore != "" {
347333
for _, ds := range datastores {
348334
if ds.Name == args.Datastore {
349-
return object.NewDatastore(c.client.Client, ds.Reference()), nil
335+
return &ds, nil
350336
}
351337
}
352338
return nil, errors.Errorf("could not find datastore %q", args.Datastore)
353339
}
354340
for _, ds := range datastores {
355341
if ds.Summary.Accessible {
356342
c.logger.Debugf("using datastore %q", ds.Name)
357-
return object.NewDatastore(c.client.Client, ds.Reference()), nil
343+
return &ds, nil
358344
}
359345
}
360346
return nil, errors.New("could not find an accessible datastore")

0 commit comments

Comments
 (0)