github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/vsphere/internal/vsphereclient/createvm_test.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package vsphereclient
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"time"
    12  
    13  	"github.com/juju/clock/testclock"
    14  	"github.com/juju/testing"
    15  	jc "github.com/juju/testing/checkers"
    16  	"github.com/vmware/govmomi/object"
    17  	"github.com/vmware/govmomi/vim25/mo"
    18  	"github.com/vmware/govmomi/vim25/types"
    19  	"golang.org/x/net/context"
    20  	gc "gopkg.in/check.v1"
    21  
    22  	"github.com/juju/juju/core/constraints"
    23  	"github.com/juju/juju/provider/vsphere/internal/ovatest"
    24  	coretesting "github.com/juju/juju/testing"
    25  )
    26  
    27  func (s *clientSuite) TestCreateTemplateVM(c *gc.C) {
    28  	var statusUpdates []string
    29  	statusUpdatesCh := make(chan string, 4)
    30  	dequeueStatusUpdates := func() {
    31  		for {
    32  			select {
    33  			case <-statusUpdatesCh:
    34  			default:
    35  				return
    36  			}
    37  		}
    38  	}
    39  	client := s.newFakeClient(&s.roundTripper, "dc0")
    40  	args := baseImportOVAParameters(c, client)
    41  	testClock := args.StatusUpdateParams.Clock.(*testclock.Clock)
    42  	s.onImageUpload = func(r *http.Request) {
    43  		dequeueStatusUpdates()
    44  
    45  		// Wait 1.5 seconds, which is long enough to trigger the
    46  		// status update timer.
    47  		testClock.WaitAdvance(1500*time.Millisecond, coretesting.LongWait, 1)
    48  
    49  		// Waiting for the status update here guarantees that a report is
    50  		// available, since we don't update status until that is true.
    51  		<-statusUpdatesCh
    52  
    53  		s.onImageUpload = nil
    54  	}
    55  	args.StatusUpdateParams.UpdateProgress = func(status string) {
    56  		statusUpdatesCh <- status
    57  		statusUpdates = append(statusUpdates, status)
    58  	}
    59  
    60  	_, err := client.CreateTemplateVM(context.Background(), args)
    61  	c.Assert(err, jc.ErrorIsNil)
    62  	c.Assert(statusUpdates, jc.DeepEquals, []string{
    63  		fmt.Sprintf(`creating template VM "juju-template-%s"`, args.OVASHA256),
    64  		"streaming vmdk: 100.00% (0B/s)",
    65  	})
    66  	c.Assert(s.uploadRequests, gc.HasLen, 1)
    67  	contents, err := io.ReadAll(s.uploadRequests[0].Body)
    68  	c.Assert(err, jc.ErrorIsNil)
    69  	c.Assert(string(contents), gc.Equals, "FakeVmdkContent")
    70  
    71  	templateCisp := baseCisp()
    72  	templateCisp.EntityName = args.TemplateName
    73  	s.roundTripper.CheckCalls(c, []testing.StubCall{
    74  		{FuncName: "CreateImportSpec", Args: []interface{}{
    75  			UbuntuOVF,
    76  			types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"},
    77  			templateCisp,
    78  		}},
    79  		{FuncName: "ImportVApp", Args: []interface{}{
    80  			&types.VirtualMachineImportSpec{
    81  				ConfigSpec: types.VirtualMachineConfigSpec{
    82  					Name: "vm-name",
    83  				},
    84  			},
    85  		}},
    86  		{FuncName: "CreatePropertyCollector", Args: nil},
    87  		{FuncName: "CreateFilter", Args: nil},
    88  		{FuncName: "WaitForUpdatesEx", Args: nil},
    89  		{FuncName: "HttpNfcLeaseComplete", Args: []interface{}{"FakeLease"}},
    90  		{FuncName: "ReconfigVM_Task", Args: []interface{}{
    91  			types.VirtualMachineConfigSpec{
    92  				ExtraConfig: []types.BaseOptionValue{
    93  					&types.OptionValue{Key: ArchTag, Value: "amd64"},
    94  				},
    95  			},
    96  		}},
    97  		{FuncName: "CreatePropertyCollector", Args: nil},
    98  		{FuncName: "CreateFilter", Args: nil},
    99  		{FuncName: "WaitForUpdatesEx", Args: nil},
   100  		{FuncName: "MarkAsTemplate", Args: []interface{}{"FakeVm0"}},
   101  	})
   102  }
   103  
   104  func (s *clientSuite) TestCreateVirtualMachine(c *gc.C) {
   105  	var statusUpdates []string
   106  	statusUpdatesCh := make(chan string, 4)
   107  	dequeueStatusUpdates := func() {
   108  		for {
   109  			select {
   110  			case <-statusUpdatesCh:
   111  			default:
   112  				return
   113  			}
   114  		}
   115  	}
   116  	client := s.newFakeClient(&s.roundTripper, "dc0")
   117  
   118  	args := baseCreateVirtualMachineParams(c, client)
   119  	testClock := args.StatusUpdateParams.Clock.(*testclock.Clock)
   120  	s.onImageUpload = func(r *http.Request) {
   121  		dequeueStatusUpdates()
   122  
   123  		// Wait 1.5 seconds, which is long enough to trigger the
   124  		// status update timer.
   125  		testClock.WaitAdvance(1500*time.Millisecond, coretesting.LongWait, 1)
   126  
   127  		// Waiting for the status update here guarantees that a report is
   128  		// available, since we don't update status until that is true.
   129  		<-statusUpdatesCh
   130  
   131  		s.onImageUpload = nil
   132  	}
   133  	args.StatusUpdateParams.UpdateProgress = func(status string) {
   134  		statusUpdatesCh <- status
   135  		statusUpdates = append(statusUpdates, status)
   136  	}
   137  
   138  	_, err := client.CreateVirtualMachine(context.Background(), args)
   139  	c.Assert(err, jc.ErrorIsNil)
   140  	c.Assert(statusUpdates, jc.DeepEquals, []string{
   141  		"cloning template",
   142  		"VM cloned",
   143  		"powering on",
   144  	})
   145  
   146  	datastore := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"}
   147  	s.roundTripper.CheckCalls(c, []testing.StubCall{
   148  		retrievePropertiesStubCall("FakeRootFolder"),
   149  		retrievePropertiesStubCall("FakeRootFolder"),
   150  		retrievePropertiesStubCall("FakeRootFolder"),
   151  		retrievePropertiesStubCall("FakeRootFolder"),
   152  		retrievePropertiesStubCall("FakeDatacenter"),
   153  		retrievePropertiesStubCall("FakeRootFolder"),
   154  		retrievePropertiesStubCall("FakeDatacenter"),
   155  		retrievePropertiesStubCall("FakeVmFolder"),
   156  		retrievePropertiesStubCall("FakeHostFolder"),
   157  		retrievePropertiesStubCall("network-0", "network-1"),
   158  		retrievePropertiesStubCall("onetwork-0"),
   159  		retrievePropertiesStubCall("dvportgroup-0"),
   160  		retrievePropertiesStubCall("FakeVm0"),
   161  		retrievePropertiesStubCall("FakeVm0"),
   162  		{FuncName: "CloneVM_Task", Args: []interface{}{
   163  			types.ManagedObjectReference{
   164  				Type: "Folder", Value: "FakeControllerVmFolder",
   165  			},
   166  			"vm-0",
   167  			&types.VirtualMachineConfigSpec{
   168  				ExtraConfig: []types.BaseOptionValue{
   169  					&types.OptionValue{Key: "k", Value: "v"},
   170  				},
   171  				Flags: &types.VirtualMachineFlagInfo{
   172  					DiskUuidEnabled: newBool(true),
   173  				},
   174  				VAppConfig: &types.VmConfigSpec{
   175  					Property: []types.VAppPropertySpec{{
   176  						ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   177  						Info:            &types.VAppPropertyInfo{Key: 1, Value: "vm-0"},
   178  					}, {
   179  						ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   180  						Info:            &types.VAppPropertyInfo{Key: 4, Value: "baz"},
   181  					}},
   182  				},
   183  			},
   184  			types.VirtualMachineRelocateSpec{
   185  				Pool:      &args.ResourcePool,
   186  				Datastore: &datastore,
   187  				Disk: []types.VirtualMachineRelocateSpecDiskLocator{
   188  					{
   189  						DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{
   190  							EagerlyScrub:    newBool(false),
   191  							ThinProvisioned: newBool(true),
   192  						},
   193  						DiskId:    0,
   194  						Datastore: datastore,
   195  					},
   196  				},
   197  			},
   198  		}},
   199  		{FuncName: "CreatePropertyCollector", Args: nil},
   200  		{FuncName: "CreateFilter", Args: nil},
   201  		{FuncName: "WaitForUpdatesEx", Args: nil},
   202  		{FuncName: "PowerOnVM_Task", Args: nil},
   203  		{FuncName: "CreatePropertyCollector", Args: nil},
   204  		{FuncName: "CreateFilter", Args: nil},
   205  		{FuncName: "WaitForUpdatesEx", Args: nil},
   206  		retrievePropertiesStubCall(""),
   207  	})
   208  }
   209  
   210  func (s *clientSuite) TestCreateVirtualMachineForceHWVersion(c *gc.C) {
   211  	client := s.newFakeClient(&s.roundTripper, "dc0")
   212  	args := baseCreateVirtualMachineParams(c, client)
   213  	args.ForceVMHardwareVersion = 11
   214  	args.ComputeResource.EnvironmentBrowser = &types.ManagedObjectReference{
   215  		Type:  "EnvironmentBrowser",
   216  		Value: "FakeEnvironmentBrowser",
   217  	}
   218  	_, err := client.CreateVirtualMachine(context.Background(), args)
   219  	c.Assert(err, jc.ErrorIsNil)
   220  
   221  	s.roundTripper.CheckCall(c, 18, "RetrieveProperties", "FakeVm1")
   222  	s.roundTripper.CheckCall(c, 19, "QueryConfigOption", "FakeEnvironmentBrowser")
   223  	// Mock server max version is vmx-13
   224  	// Mock template VM version is vmx-10
   225  	// We requested vmx-11. This should match the call to UpgradeVM_Task.
   226  	s.roundTripper.CheckCall(c, 20, "UpgradeVM_Task", "vmx-11")
   227  }
   228  
   229  func (s *clientSuite) TestCreateVirtualMachineNoDiskUUID(c *gc.C) {
   230  	client := s.newFakeClient(&s.roundTripper, "dc0")
   231  	args := baseCreateVirtualMachineParams(c, client)
   232  	args.EnableDiskUUID = false
   233  	_, err := client.CreateVirtualMachine(context.Background(), args)
   234  	c.Assert(err, jc.ErrorIsNil)
   235  
   236  	datastore := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"}
   237  	s.roundTripper.CheckCall(
   238  		c, 14, "CloneVM_Task",
   239  		types.ManagedObjectReference{
   240  			Type: "Folder", Value: "FakeControllerVmFolder",
   241  		},
   242  		"vm-0", &types.VirtualMachineConfigSpec{
   243  			ExtraConfig: []types.BaseOptionValue{
   244  				&types.OptionValue{Key: "k", Value: "v"},
   245  			},
   246  			Flags: &types.VirtualMachineFlagInfo{DiskUuidEnabled: newBool(args.EnableDiskUUID)},
   247  			VAppConfig: &types.VmConfigSpec{
   248  				Property: []types.VAppPropertySpec{{
   249  					ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   250  					Info:            &types.VAppPropertyInfo{Key: 1, Value: "vm-0"},
   251  				}, {
   252  					ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   253  					Info:            &types.VAppPropertyInfo{Key: 4, Value: "baz"},
   254  				}},
   255  			},
   256  		}, types.VirtualMachineRelocateSpec{
   257  			Pool:      &args.ResourcePool,
   258  			Datastore: &datastore,
   259  			Disk: []types.VirtualMachineRelocateSpecDiskLocator{
   260  				{
   261  					DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{
   262  						EagerlyScrub:    newBool(false),
   263  						ThinProvisioned: newBool(true),
   264  					},
   265  					DiskId:    0,
   266  					Datastore: datastore,
   267  				},
   268  			},
   269  		})
   270  }
   271  
   272  func (s *clientSuite) TestCreateVirtualMachineThickDiskProvisioning(c *gc.C) {
   273  	client := s.newFakeClient(&s.roundTripper, "dc0")
   274  	args := baseCreateVirtualMachineParams(c, client)
   275  	args.DiskProvisioningType = DiskTypeThickLazyZero
   276  	_, err := client.CreateVirtualMachine(context.Background(), args)
   277  	c.Assert(err, jc.ErrorIsNil)
   278  
   279  	datastore := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"}
   280  
   281  	s.roundTripper.CheckCall(
   282  		c, 14, "CloneVM_Task",
   283  		types.ManagedObjectReference{
   284  			Type: "Folder", Value: "FakeControllerVmFolder",
   285  		},
   286  		"vm-0", &types.VirtualMachineConfigSpec{
   287  			ExtraConfig: []types.BaseOptionValue{
   288  				&types.OptionValue{Key: "k", Value: "v"},
   289  			},
   290  			Flags: &types.VirtualMachineFlagInfo{DiskUuidEnabled: newBool(true)},
   291  			VAppConfig: &types.VmConfigSpec{
   292  				Property: []types.VAppPropertySpec{{
   293  					ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   294  					Info:            &types.VAppPropertyInfo{Key: 1, Value: "vm-0"},
   295  				}, {
   296  					ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   297  					Info:            &types.VAppPropertyInfo{Key: 4, Value: "baz"},
   298  				}},
   299  			},
   300  		}, types.VirtualMachineRelocateSpec{
   301  			Pool:      &args.ResourcePool,
   302  			Datastore: &datastore,
   303  			Disk: []types.VirtualMachineRelocateSpecDiskLocator{
   304  				{
   305  					DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{
   306  						// Thick disk provisioning, lazy zeros
   307  						EagerlyScrub:    newBool(false),
   308  						ThinProvisioned: newBool(false),
   309  					},
   310  					DiskId:    0,
   311  					Datastore: datastore,
   312  				},
   313  			},
   314  		})
   315  }
   316  
   317  func (s *clientSuite) TestCreateVirtualMachineThickEagerZeroDiskProvisioning(c *gc.C) {
   318  	client := s.newFakeClient(&s.roundTripper, "dc0")
   319  	args := baseCreateVirtualMachineParams(c, client)
   320  	args.DiskProvisioningType = DiskTypeThick
   321  
   322  	_, err := client.CreateVirtualMachine(context.Background(), args)
   323  	c.Assert(err, jc.ErrorIsNil)
   324  
   325  	datastore := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"}
   326  
   327  	s.roundTripper.CheckCall(
   328  		c, 14, "CloneVM_Task",
   329  		types.ManagedObjectReference{
   330  			Type: "Folder", Value: "FakeControllerVmFolder",
   331  		},
   332  		"vm-0", &types.VirtualMachineConfigSpec{
   333  			ExtraConfig: []types.BaseOptionValue{
   334  				&types.OptionValue{Key: "k", Value: "v"},
   335  			},
   336  			Flags: &types.VirtualMachineFlagInfo{DiskUuidEnabled: newBool(true)},
   337  			VAppConfig: &types.VmConfigSpec{
   338  				Property: []types.VAppPropertySpec{{
   339  					ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   340  					Info:            &types.VAppPropertyInfo{Key: 1, Value: "vm-0"},
   341  				}, {
   342  					ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   343  					Info:            &types.VAppPropertyInfo{Key: 4, Value: "baz"},
   344  				}},
   345  			},
   346  		}, types.VirtualMachineRelocateSpec{
   347  			Pool:      &args.ResourcePool,
   348  			Datastore: &datastore,
   349  			Disk: []types.VirtualMachineRelocateSpecDiskLocator{
   350  				{
   351  					DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{
   352  						// Thick disk provisioning, eager zeros
   353  						EagerlyScrub:    newBool(true),
   354  						ThinProvisioned: newBool(false),
   355  					},
   356  					DiskId:    0,
   357  					Datastore: datastore,
   358  				},
   359  			},
   360  		})
   361  }
   362  
   363  func (s *clientSuite) TestCreateVirtualMachineThinDiskProvisioning(c *gc.C) {
   364  	client := s.newFakeClient(&s.roundTripper, "dc0")
   365  	args := baseCreateVirtualMachineParams(c, client)
   366  	args.DiskProvisioningType = DiskTypeThin
   367  
   368  	_, err := client.CreateVirtualMachine(context.Background(), args)
   369  	c.Assert(err, jc.ErrorIsNil)
   370  
   371  	datastore := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"}
   372  
   373  	s.roundTripper.CheckCall(c, 14, "CloneVM_Task", types.ManagedObjectReference{Type: "Folder", Value: "FakeControllerVmFolder"}, "vm-0", &types.VirtualMachineConfigSpec{
   374  		ExtraConfig: []types.BaseOptionValue{
   375  			&types.OptionValue{Key: "k", Value: "v"},
   376  		},
   377  		Flags: &types.VirtualMachineFlagInfo{DiskUuidEnabled: newBool(true)},
   378  		VAppConfig: &types.VmConfigSpec{
   379  			Property: []types.VAppPropertySpec{{
   380  				ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   381  				Info:            &types.VAppPropertyInfo{Key: 1, Value: "vm-0"},
   382  			}, {
   383  				ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   384  				Info:            &types.VAppPropertyInfo{Key: 4, Value: "baz"},
   385  			}},
   386  		},
   387  	}, types.VirtualMachineRelocateSpec{
   388  		Pool:      &args.ResourcePool,
   389  		Datastore: &datastore,
   390  		Disk: []types.VirtualMachineRelocateSpecDiskLocator{
   391  			{
   392  				DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{
   393  					// Thin disk provisioning
   394  					EagerlyScrub:    newBool(false),
   395  					ThinProvisioned: newBool(true),
   396  				},
   397  				DiskId:    0,
   398  				Datastore: datastore,
   399  			},
   400  		},
   401  	})
   402  }
   403  
   404  func (s *clientSuite) TestCreateVirtualMachineDatastoreSpecified(c *gc.C) {
   405  	client := s.newFakeClient(&s.roundTripper, "dc0")
   406  	args := baseCreateVirtualMachineParams(c, client)
   407  	datastore := "datastore1"
   408  	args.Constraints.RootDiskSource = &datastore
   409  	args.ComputeResource.Datastore = []types.ManagedObjectReference{{
   410  		Type:  "Datastore",
   411  		Value: "FakeDatastore2",
   412  	}, {
   413  		Type:  "Datastore",
   414  		Value: "FakeDatastore1",
   415  	}}
   416  
   417  	_, err := client.CreateVirtualMachine(context.Background(), args)
   418  	c.Assert(err, jc.ErrorIsNil)
   419  
   420  	datastoreLocation := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"}
   421  	s.roundTripper.CheckCall(
   422  		c, 14, "CloneVM_Task", types.ManagedObjectReference{
   423  			Type: "Folder", Value: "FakeControllerVmFolder",
   424  		},
   425  		"vm-0", &types.VirtualMachineConfigSpec{
   426  			ExtraConfig: []types.BaseOptionValue{
   427  				&types.OptionValue{Key: "k", Value: "v"},
   428  			},
   429  			Flags: &types.VirtualMachineFlagInfo{DiskUuidEnabled: newBool(true)},
   430  			VAppConfig: &types.VmConfigSpec{
   431  				Property: []types.VAppPropertySpec{{
   432  					ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   433  					Info:            &types.VAppPropertyInfo{Key: 1, Value: "vm-0"},
   434  				}, {
   435  					ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   436  					Info:            &types.VAppPropertyInfo{Key: 4, Value: "baz"},
   437  				}},
   438  			},
   439  		}, types.VirtualMachineRelocateSpec{
   440  			Pool:      &args.ResourcePool,
   441  			Datastore: &datastoreLocation,
   442  			Disk: []types.VirtualMachineRelocateSpecDiskLocator{
   443  				{
   444  					DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{
   445  						EagerlyScrub:    newBool(false),
   446  						ThinProvisioned: newBool(true),
   447  					},
   448  					DiskId:    0,
   449  					Datastore: datastoreLocation,
   450  				},
   451  			},
   452  		})
   453  }
   454  
   455  func (s *clientSuite) TestGetTargetDatastoreDatastoreNotFound(c *gc.C) {
   456  	client := s.newFakeClient(&s.roundTripper, "dc0")
   457  	args := baseCreateVirtualMachineParams(c, client)
   458  	datastore := "datastore3"
   459  
   460  	_, err := client.GetTargetDatastore(context.Background(), args.ComputeResource, datastore)
   461  	c.Assert(err, gc.ErrorMatches, `could not find datastore "datastore3", datastore\(s\) accessible: "datastore2"`)
   462  }
   463  
   464  func (s *clientSuite) TestGetTargetDatastoreDatastoreNoneAccessible(c *gc.C) {
   465  	client := s.newFakeClient(&s.roundTripper, "dc0")
   466  	args := baseCreateVirtualMachineParams(c, client)
   467  	args.ComputeResource.Datastore = []types.ManagedObjectReference{{
   468  		Type:  "Datastore",
   469  		Value: "FakeDatastore1",
   470  	}}
   471  
   472  	_, err := client.GetTargetDatastore(context.Background(), args.ComputeResource, args.Datastore.Name())
   473  	c.Assert(err, gc.ErrorMatches, "no accessible datastores available")
   474  }
   475  
   476  func (s *clientSuite) TestGetTargetDatastoreDatastoreNotFoundWithMultipleAvailable(c *gc.C) {
   477  	client := s.newFakeClient(&s.roundTripper, "dc0")
   478  	args := baseCreateVirtualMachineParams(c, client)
   479  	datastore := "datastore3"
   480  
   481  	s.roundTripper.updateContents("FakeDatastore1",
   482  		[]types.ObjectContent{{
   483  			Obj: types.ManagedObjectReference{
   484  				Type:  "Datastore",
   485  				Value: "FakeDatastore1",
   486  			},
   487  			PropSet: []types.DynamicProperty{
   488  				{Name: "name", Val: "datastore1"},
   489  				{Name: "summary.accessible", Val: true},
   490  			},
   491  		}},
   492  	)
   493  
   494  	_, err := client.GetTargetDatastore(context.Background(), args.ComputeResource, datastore)
   495  	c.Assert(err, gc.ErrorMatches, `could not find datastore "datastore3", datastore\(s\) accessible: "datastore1", "datastore2"`)
   496  }
   497  
   498  func (s *clientSuite) TestGetTargetDatastoreDatastoreNotFoundWithNoAvailable(c *gc.C) {
   499  	client := s.newFakeClient(&s.roundTripper, "dc0")
   500  	args := baseCreateVirtualMachineParams(c, client)
   501  	datastore := "datastore3"
   502  
   503  	s.roundTripper.updateContents("FakeDatastore2",
   504  		[]types.ObjectContent{{
   505  			Obj: types.ManagedObjectReference{
   506  				Type:  "Datastore",
   507  				Value: "FakeDatastore2",
   508  			},
   509  			PropSet: []types.DynamicProperty{
   510  				{Name: "name", Val: "datastore2"},
   511  				{Name: "summary.accessible", Val: false},
   512  			},
   513  		}},
   514  	)
   515  
   516  	_, err := client.GetTargetDatastore(context.Background(), args.ComputeResource, datastore)
   517  	c.Assert(err, gc.ErrorMatches, `no accessible datastores available`)
   518  }
   519  
   520  func (s *clientSuite) TestCreateVirtualMachineMultipleNetworksSpecifiedFirstDefault(c *gc.C) {
   521  	client := s.newFakeClient(&s.roundTripper, "dc0")
   522  	args := baseCreateVirtualMachineParams(c, client)
   523  	args.NetworkDevices = []NetworkDevice{
   524  		{MAC: "00:50:56:11:22:33"},
   525  		{Network: "arpa"},
   526  	}
   527  
   528  	_, err := client.CreateVirtualMachine(context.Background(), args)
   529  	c.Assert(err, jc.ErrorIsNil)
   530  
   531  	var networkDevice1, networkDevice2 types.VirtualVmxnet3
   532  	wakeOnLan := true
   533  	networkDevice1.Key = -1
   534  	networkDevice1.WakeOnLanEnabled = &wakeOnLan
   535  	networkDevice1.Connectable = &types.VirtualDeviceConnectInfo{
   536  		StartConnected:    true,
   537  		AllowGuestControl: true,
   538  	}
   539  	networkDevice1.AddressType = "Manual"
   540  	networkDevice1.MacAddress = "00:50:56:11:22:33"
   541  	networkDevice1.Backing = &types.VirtualEthernetCardNetworkBackingInfo{
   542  		VirtualDeviceDeviceBackingInfo: types.VirtualDeviceDeviceBackingInfo{
   543  			DeviceName: "VM Network",
   544  		},
   545  	}
   546  
   547  	networkDevice2.Key = -2
   548  	networkDevice2.WakeOnLanEnabled = &wakeOnLan
   549  	networkDevice2.Connectable = &types.VirtualDeviceConnectInfo{
   550  		StartConnected:    true,
   551  		AllowGuestControl: true,
   552  	}
   553  	networkDevice2.Backing = &types.VirtualEthernetCardNetworkBackingInfo{
   554  		VirtualDeviceDeviceBackingInfo: types.VirtualDeviceDeviceBackingInfo{
   555  			DeviceName: "arpa",
   556  		},
   557  	}
   558  	datastore := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"}
   559  	s.roundTripper.CheckCall(
   560  		c, 14, "CloneVM_Task",
   561  		types.ManagedObjectReference{
   562  			Type: "Folder", Value: "FakeControllerVmFolder",
   563  		},
   564  		"vm-0", &types.VirtualMachineConfigSpec{
   565  			ExtraConfig: []types.BaseOptionValue{
   566  				&types.OptionValue{Key: "k", Value: "v"},
   567  			},
   568  			DeviceChange: []types.BaseVirtualDeviceConfigSpec{
   569  				&types.VirtualDeviceConfigSpec{
   570  					Operation: "add",
   571  					Device:    &networkDevice1,
   572  				},
   573  				&types.VirtualDeviceConfigSpec{
   574  					Operation: "add",
   575  					Device:    &networkDevice2,
   576  				},
   577  			},
   578  			Flags: &types.VirtualMachineFlagInfo{DiskUuidEnabled: newBool(true)},
   579  			VAppConfig: &types.VmConfigSpec{
   580  				Property: []types.VAppPropertySpec{{
   581  					ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   582  					Info:            &types.VAppPropertyInfo{Key: 1, Value: "vm-0"},
   583  				}, {
   584  					ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   585  					Info:            &types.VAppPropertyInfo{Key: 4, Value: "baz"},
   586  				}},
   587  			},
   588  		}, types.VirtualMachineRelocateSpec{
   589  			Pool:      &args.ResourcePool,
   590  			Datastore: &datastore,
   591  			Disk: []types.VirtualMachineRelocateSpecDiskLocator{
   592  				{
   593  					DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{
   594  						EagerlyScrub:    newBool(false),
   595  						ThinProvisioned: newBool(true),
   596  					},
   597  					DiskId:    0,
   598  					Datastore: datastore,
   599  				},
   600  			},
   601  		})
   602  }
   603  
   604  func (s *clientSuite) TestCreateVirtualMachineNetworkSpecifiedDVPortgroup(c *gc.C) {
   605  	client := s.newFakeClient(&s.roundTripper, "dc0")
   606  	args := baseCreateVirtualMachineParams(c, client)
   607  	args.NetworkDevices = []NetworkDevice{
   608  		{Network: "yoink"},
   609  	}
   610  
   611  	_, err := client.CreateVirtualMachine(context.Background(), args)
   612  	c.Assert(err, jc.ErrorIsNil)
   613  
   614  	var networkDevice types.VirtualVmxnet3
   615  	wakeOnLan := true
   616  	networkDevice.Key = -1
   617  	networkDevice.WakeOnLanEnabled = &wakeOnLan
   618  	networkDevice.Connectable = &types.VirtualDeviceConnectInfo{
   619  		StartConnected:    true,
   620  		AllowGuestControl: true,
   621  	}
   622  	networkDevice.Backing = &types.VirtualEthernetCardDistributedVirtualPortBackingInfo{
   623  		Port: types.DistributedVirtualSwitchPortConnection{
   624  			SwitchUuid:   "yup",
   625  			PortgroupKey: "hole",
   626  		},
   627  	}
   628  
   629  	retrieveDVSCall := retrievePropertiesStubCall("dvs-0")
   630  	s.roundTripper.CheckCall(c, 12, retrieveDVSCall.FuncName, retrieveDVSCall.Args...)
   631  
   632  	datastore := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"}
   633  	// When the external network is a distributed virtual portgroup,
   634  	// we must make an additional RetrieveProperties call to fetch
   635  	// the DVS's UUID. This bumps the ImportVApp position by one.
   636  	s.roundTripper.CheckCall(
   637  		c, 15, "CloneVM_Task",
   638  		types.ManagedObjectReference{
   639  			Type: "Folder", Value: "FakeControllerVmFolder",
   640  		},
   641  		"vm-0", &types.VirtualMachineConfigSpec{
   642  			ExtraConfig: []types.BaseOptionValue{
   643  				&types.OptionValue{Key: "k", Value: "v"},
   644  			},
   645  			DeviceChange: []types.BaseVirtualDeviceConfigSpec{
   646  				&types.VirtualDeviceConfigSpec{
   647  					Operation: "add",
   648  					Device:    &networkDevice,
   649  				},
   650  			},
   651  			Flags: &types.VirtualMachineFlagInfo{DiskUuidEnabled: newBool(true)},
   652  			VAppConfig: &types.VmConfigSpec{
   653  				Property: []types.VAppPropertySpec{{
   654  					ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   655  					Info:            &types.VAppPropertyInfo{Key: 1, Value: "vm-0"},
   656  				}, {
   657  					ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   658  					Info:            &types.VAppPropertyInfo{Key: 4, Value: "baz"},
   659  				}},
   660  			},
   661  		}, types.VirtualMachineRelocateSpec{
   662  			Pool:      &args.ResourcePool,
   663  			Datastore: &datastore,
   664  			Disk: []types.VirtualMachineRelocateSpecDiskLocator{
   665  				{
   666  					DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{
   667  						EagerlyScrub:    newBool(false),
   668  						ThinProvisioned: newBool(true),
   669  					},
   670  					DiskId:    0,
   671  					Datastore: datastore,
   672  				},
   673  			},
   674  		})
   675  }
   676  
   677  func (s *clientSuite) TestCreateVirtualMachineNetworkNotFound(c *gc.C) {
   678  	client := s.newFakeClient(&s.roundTripper, "dc0")
   679  	args := baseCreateVirtualMachineParams(c, client)
   680  	args.NetworkDevices = []NetworkDevice{
   681  		{Network: "fourtytwo"},
   682  	}
   683  
   684  	_, err := client.CreateVirtualMachine(context.Background(), args)
   685  	c.Assert(err, gc.ErrorMatches, `cloning template VM: building clone VM config: network "fourtytwo" not found`)
   686  }
   687  
   688  func (s *clientSuite) TestCreateVirtualMachineInvalidMAC(c *gc.C) {
   689  	client := s.newFakeClient(&s.roundTripper, "dc0")
   690  	args := baseCreateVirtualMachineParams(c, client)
   691  	args.NetworkDevices = []NetworkDevice{
   692  		{MAC: "00:11:22:33:44:55"},
   693  	}
   694  
   695  	_, err := client.CreateVirtualMachine(context.Background(), args)
   696  	c.Assert(err, gc.ErrorMatches, `cloning template VM: building clone VM config: adding network device 0 - network VM Network: invalid MAC address: "00:11:22:33:44:55"`)
   697  }
   698  
   699  func (s *clientSuite) TestCreateVirtualMachineRootDiskSize(c *gc.C) {
   700  	client := s.newFakeClient(&s.roundTripper, "dc0")
   701  	args := baseCreateVirtualMachineParams(c, client)
   702  	rootDisk := uint64(1024 * 20) // 20 GiB
   703  	args.Constraints.RootDisk = &rootDisk
   704  
   705  	_, err := client.CreateVirtualMachine(context.Background(), args)
   706  	c.Assert(err, jc.ErrorIsNil)
   707  
   708  	s.roundTripper.CheckCall(c, 19, "ReconfigVM_Task", types.VirtualMachineConfigSpec{
   709  		DeviceChange: []types.BaseVirtualDeviceConfigSpec{
   710  			&types.VirtualDeviceConfigSpec{
   711  				Operation:     types.VirtualDeviceConfigSpecOperationEdit,
   712  				FileOperation: "",
   713  				Device: &types.VirtualDisk{
   714  					VirtualDevice: types.VirtualDevice{
   715  						Backing: &types.VirtualDiskFlatVer2BackingInfo{
   716  							VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
   717  								FileName: "disk.vmdk",
   718  							},
   719  						},
   720  					},
   721  					CapacityInKB: 1024 * 1024 * 20, // 20 GiB
   722  				},
   723  			},
   724  		},
   725  	})
   726  }
   727  
   728  func (s *clientSuite) TestCreateVirtualMachineWithCustomizedVMFolder(c *gc.C) {
   729  	client := s.newFakeClient(&s.roundTripper, "dc0")
   730  	args := baseCreateVirtualMachineParams(c, client)
   731  	rootDisk := uint64(1024 * 20) // 20 GiB
   732  	args.Constraints.RootDisk = &rootDisk
   733  
   734  	args.Folder = "k8s"
   735  
   736  	_, err := client.CreateVirtualMachine(context.Background(), args)
   737  	c.Assert(err, jc.ErrorIsNil)
   738  
   739  	datastore := types.ManagedObjectReference{Type: "Datastore", Value: "FakeDatastore1"}
   740  	// The template import and the create from template have been split in two separate
   741  	// functions. We now have to check the folder passed to CloneVm to determine if the
   742  	// correct folder was selected when testing CreateVirtualMachine().
   743  	s.roundTripper.CheckCall(
   744  		c, 14, "CloneVM_Task",
   745  		types.ManagedObjectReference{Type: "Folder", Value: "FakeK8sVMFolder"},
   746  		"vm-0", &types.VirtualMachineConfigSpec{
   747  			ExtraConfig: []types.BaseOptionValue{
   748  				&types.OptionValue{Key: "k", Value: "v"},
   749  			},
   750  			Flags: &types.VirtualMachineFlagInfo{DiskUuidEnabled: newBool(args.EnableDiskUUID)},
   751  			VAppConfig: &types.VmConfigSpec{
   752  				Property: []types.VAppPropertySpec{{
   753  					ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   754  					Info:            &types.VAppPropertyInfo{Key: 1, Value: "vm-0"},
   755  				}, {
   756  					ArrayUpdateSpec: types.ArrayUpdateSpec{Operation: "edit"},
   757  					Info:            &types.VAppPropertyInfo{Key: 4, Value: "baz"},
   758  				}},
   759  			},
   760  		}, types.VirtualMachineRelocateSpec{
   761  			Pool:      &args.ResourcePool,
   762  			Datastore: &datastore,
   763  			Disk: []types.VirtualMachineRelocateSpecDiskLocator{
   764  				{
   765  					DiskBackingInfo: &types.VirtualDiskFlatVer2BackingInfo{
   766  						EagerlyScrub:    newBool(false),
   767  						ThinProvisioned: newBool(true),
   768  					},
   769  					DiskId:    0,
   770  					Datastore: datastore,
   771  				},
   772  			},
   773  		})
   774  }
   775  
   776  func (s *clientSuite) TestVerifyMAC(c *gc.C) {
   777  	var testData = []struct {
   778  		Mac    string
   779  		Result bool
   780  	}{
   781  		{"foo:bar:baz", false},
   782  		{"00:22:55:11:34:11", false},
   783  		{"00:50:56:123:11:11", false},
   784  		{"00:50:56:40:12:23", false},
   785  		{"00:50:56:3f:ff:ff", true},
   786  		{"00:50:56:12:34:56", true},
   787  		{"00:50:56:2A:eB:Cd", true},
   788  		{"00:50:56:2a:xy:cd", false},
   789  		{"00:50:560:2a:xy:cd", false},
   790  	}
   791  	for i, test := range testData {
   792  		c.Logf("test #%d: MAC=%s expected %s", i, test.Mac, test.Result)
   793  		c.Check(VerifyMAC(test.Mac), gc.Equals, test.Result)
   794  	}
   795  }
   796  
   797  func baseImportOVAParameters(c *gc.C, client *Client) ImportOVAParameters {
   798  	readOVA := func() (string, io.ReadCloser, error) {
   799  		r := bytes.NewReader(ovatest.FakeOVAContents())
   800  		return "fake-ova-location", io.NopCloser(r), nil
   801  	}
   802  	fakeSHA256 := ovatest.FakeOVASHA256()
   803  	fakeDS := types.ManagedObjectReference{
   804  		Type:  "Datastore",
   805  		Value: "FakeDatastore1",
   806  	}
   807  	return ImportOVAParameters{
   808  		ReadOVA:   readOVA,
   809  		OVASHA256: fakeSHA256,
   810  		StatusUpdateParams: StatusUpdateParams{
   811  			UpdateProgress:         func(status string) {},
   812  			UpdateProgressInterval: time.Second,
   813  			Clock:                  testclock.NewClock(time.Time{}),
   814  		},
   815  		ResourcePool: types.ManagedObjectReference{
   816  			Type:  "ResourcePool",
   817  			Value: "FakeResourcePool1",
   818  		},
   819  		TemplateName: "juju-template-" + fakeSHA256,
   820  		Arch:         "amd64",
   821  		Series:       "xenial",
   822  		DestinationFolder: &object.Folder{
   823  			Common: object.Common{
   824  				InventoryPath: "/dc0/vm/juju-vmdks/ctrl/xenial",
   825  			},
   826  		},
   827  		Datastore: object.NewDatastore(client.client.Client, fakeDS),
   828  	}
   829  }
   830  
   831  func baseCreateVirtualMachineParams(c *gc.C, client *Client) CreateVirtualMachineParams {
   832  	fakeVM := types.ManagedObjectReference{
   833  		Type:  "VirtualMachine",
   834  		Value: "FakeVm0",
   835  	}
   836  
   837  	fakeDS := types.ManagedObjectReference{
   838  		Type:  "Datastore",
   839  		Value: "FakeDatastore1",
   840  	}
   841  
   842  	return CreateVirtualMachineParams{
   843  		Name:     "vm-0",
   844  		Folder:   "foo",
   845  		Series:   "xenial",
   846  		UserData: "baz",
   847  		ComputeResource: &mo.ComputeResource{
   848  			ResourcePool: &types.ManagedObjectReference{
   849  				Type:  "ResourcePool",
   850  				Value: "FakeResourcePool1",
   851  			},
   852  			Datastore: []types.ManagedObjectReference{{
   853  				Type:  "Datastore",
   854  				Value: "FakeDatastore1",
   855  			}, {
   856  				Type:  "Datastore",
   857  				Value: "FakeDatastore2",
   858  			}},
   859  			Network: []types.ManagedObjectReference{{
   860  				Type:  "Network",
   861  				Value: "network-0",
   862  			}, {
   863  				Type:  "Network",
   864  				Value: "network-1",
   865  			}, {
   866  				Type:  "OpaqueNetwork",
   867  				Value: "onetwork-0",
   868  			}, {
   869  				Type:  "DistributedVirtualPortgroup",
   870  				Value: "dvportgroup-0",
   871  			}},
   872  		},
   873  		ResourcePool: types.ManagedObjectReference{
   874  			Type:  "ResourcePool",
   875  			Value: "FakeResourcePool1",
   876  		},
   877  		Metadata:    map[string]string{"k": "v"},
   878  		Constraints: constraints.Value{},
   879  		StatusUpdateParams: StatusUpdateParams{
   880  			UpdateProgress:         func(status string) {},
   881  			UpdateProgressInterval: time.Second,
   882  			Clock:                  testclock.NewClock(time.Time{}),
   883  		},
   884  		EnableDiskUUID:       true,
   885  		DiskProvisioningType: DiskTypeThin,
   886  		VMTemplate:           object.NewVirtualMachine(client.client.Client, fakeVM),
   887  		Datastore:            object.NewDatastore(client.client.Client, fakeDS),
   888  	}
   889  }
   890  
   891  func baseCisp() types.OvfCreateImportSpecParams {
   892  	return types.OvfCreateImportSpecParams{
   893  		EntityName: "vm-0",
   894  	}
   895  }
   896  
   897  func newBool(v bool) *bool {
   898  	return &v
   899  }