github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/vsphere/internal/vsphereclient/client_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  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"net/url"
    13  
    14  	"github.com/juju/loggo"
    15  	"github.com/juju/testing"
    16  	jc "github.com/juju/testing/checkers"
    17  	"github.com/vmware/govmomi"
    18  	"github.com/vmware/govmomi/session"
    19  	"github.com/vmware/govmomi/vim25"
    20  	"github.com/vmware/govmomi/vim25/methods"
    21  	"github.com/vmware/govmomi/vim25/mo"
    22  	"github.com/vmware/govmomi/vim25/soap"
    23  	"github.com/vmware/govmomi/vim25/types"
    24  	"github.com/vmware/govmomi/vim25/xml"
    25  	"golang.org/x/net/context"
    26  	gc "gopkg.in/check.v1"
    27  )
    28  
    29  type clientSuite struct {
    30  	testing.IsolationSuite
    31  
    32  	server         *httptest.Server
    33  	serviceContent types.ServiceContent
    34  	roundTripper   mockRoundTripper
    35  	uploadRequests []*http.Request
    36  	onImageUpload  func(*http.Request)
    37  }
    38  
    39  var _ = gc.Suite(&clientSuite{})
    40  
    41  func (s *clientSuite) SetUpTest(c *gc.C) {
    42  	s.IsolationSuite.SetUpTest(c)
    43  	s.serviceContent = types.ServiceContent{
    44  		RootFolder: types.ManagedObjectReference{
    45  			Type:  "Folder",
    46  			Value: "FakeRootFolder",
    47  		},
    48  		OvfManager: &types.ManagedObjectReference{
    49  			Type:  "OvfManager",
    50  			Value: "FakeOvfManager",
    51  		},
    52  		SessionManager: &types.ManagedObjectReference{
    53  			Type:  "SessionManager",
    54  			Value: "FakeSessionManager",
    55  		},
    56  		FileManager: &types.ManagedObjectReference{
    57  			Type:  "FileManager",
    58  			Value: "FakeFileManager",
    59  		},
    60  		VirtualDiskManager: &types.ManagedObjectReference{
    61  			Type:  "VirtualDiskManager",
    62  			Value: "FakeVirtualDiskManager",
    63  		},
    64  		PropertyCollector: types.ManagedObjectReference{
    65  			Type:  "PropertyCollector",
    66  			Value: "FakePropertyCollector",
    67  		},
    68  		SearchIndex: &types.ManagedObjectReference{
    69  			Type:  "SearchIndex",
    70  			Value: "FakeSearchIndex",
    71  		},
    72  	}
    73  	s.roundTripper = mockRoundTripper{
    74  		collectors: make(map[string]*collector),
    75  		taskResult: make(map[types.ManagedObjectReference]types.AnyType),
    76  		taskError:  make(map[types.ManagedObjectReference]*types.LocalizedMethodFault),
    77  	}
    78  	s.roundTripper.contents = map[string][]types.ObjectContent{
    79  		"FakeRootFolder": {{
    80  			Obj: types.ManagedObjectReference{
    81  				Type:  "Datacenter",
    82  				Value: "FakeDatacenter",
    83  			},
    84  			PropSet: []types.DynamicProperty{
    85  				{Name: "name", Val: "dc0"},
    86  			},
    87  		}},
    88  		"FakeDatacenter": {{
    89  			Obj: types.ManagedObjectReference{
    90  				Type:  "Datacenter",
    91  				Value: "FakeDatacenter",
    92  			},
    93  			PropSet: []types.DynamicProperty{
    94  				{Name: "name", Val: "dc0"},
    95  				{Name: "hostFolder", Val: types.ManagedObjectReference{
    96  					Type:  "Folder",
    97  					Value: "FakeHostFolder",
    98  				}},
    99  				{Name: "vmFolder", Val: types.ManagedObjectReference{
   100  					Type:  "Folder",
   101  					Value: "FakeVmFolder",
   102  				}},
   103  				{Name: "datastoreFolder", Val: types.ManagedObjectReference{
   104  					Type:  "Folder",
   105  					Value: "FakeDatastoreFolder",
   106  				}},
   107  			},
   108  		}, {
   109  			Obj: types.ManagedObjectReference{
   110  				Type:  "Folder",
   111  				Value: "FakeVmFolder",
   112  			},
   113  			PropSet: []types.DynamicProperty{{Name: "name", Val: "vm"}},
   114  		}, {
   115  			Obj: types.ManagedObjectReference{
   116  				Type:  "Folder",
   117  				Value: "FakeHostFolder",
   118  			},
   119  			PropSet: []types.DynamicProperty{{Name: "name", Val: "vm"}},
   120  		}},
   121  		"FakeHostFolder": {{
   122  			Obj: types.ManagedObjectReference{
   123  				Type:  "ComputeResource",
   124  				Value: "z0",
   125  			},
   126  			PropSet: []types.DynamicProperty{
   127  				{Name: "resourcePool", Val: types.ManagedObjectReference{
   128  					Type:  "ResourcePool",
   129  					Value: "FakeResourcePool1",
   130  				}},
   131  				{Name: "datastore", Val: []types.ManagedObjectReference{{
   132  					Type:  "Datastore",
   133  					Value: "FakeDatastore1",
   134  				}}},
   135  				{Name: "name", Val: "z0"},
   136  			},
   137  		}, {
   138  			Obj: types.ManagedObjectReference{
   139  				Type:  "ComputeResource",
   140  				Value: "z1",
   141  			},
   142  			PropSet: []types.DynamicProperty{
   143  				{Name: "resourcePool", Val: types.ManagedObjectReference{
   144  					Type:  "ResourcePool",
   145  					Value: "FakeResourcePool2",
   146  				}},
   147  				{Name: "datastore", Val: []types.ManagedObjectReference{{
   148  					Type:  "Datastore",
   149  					Value: "FakeDatastore2",
   150  				}}},
   151  				{Name: "name", Val: "z1"},
   152  			},
   153  		}},
   154  		"FakeDatastoreFolder": {{
   155  			Obj: types.ManagedObjectReference{
   156  				Type:  "Datastore",
   157  				Value: "FakeDatastore1",
   158  			},
   159  			PropSet: []types.DynamicProperty{
   160  				{Name: "name", Val: "datastore1"},
   161  				{Name: "summary.accessible", Val: false},
   162  			},
   163  		}, {
   164  			Obj: types.ManagedObjectReference{
   165  				Type:  "Datastore",
   166  				Value: "FakeDatastore2",
   167  			},
   168  			PropSet: []types.DynamicProperty{
   169  				{Name: "name", Val: "datastore2"},
   170  				{Name: "summary.accessible", Val: true},
   171  			},
   172  		}},
   173  		"FakeVmFolder": {{
   174  			Obj: types.ManagedObjectReference{
   175  				Type:  "Folder",
   176  				Value: "FakeControllerVmFolder",
   177  			},
   178  			PropSet: []types.DynamicProperty{
   179  				{Name: "name", Val: "foo"},
   180  			},
   181  		}},
   182  		"FakeControllerVmFolder": {{
   183  			Obj: types.ManagedObjectReference{
   184  				Type:  "Folder",
   185  				Value: "FakeModelVmFolder",
   186  			},
   187  			PropSet: []types.DynamicProperty{
   188  				{Name: "name", Val: "bar"},
   189  			},
   190  		}},
   191  		"FakeModelVmFolder": {{
   192  			Obj: types.ManagedObjectReference{
   193  				Type:  "VirtualMachine",
   194  				Value: "FakeVm0",
   195  			},
   196  			PropSet: []types.DynamicProperty{
   197  				{Name: "name", Val: "vm-0"},
   198  			},
   199  		}, {
   200  			Obj: types.ManagedObjectReference{
   201  				Type:  "VirtualMachine",
   202  				Value: "FakeVm1",
   203  			},
   204  			PropSet: []types.DynamicProperty{
   205  				{Name: "name", Val: "vm-1"},
   206  			},
   207  		}},
   208  		"FakeVm0": {{
   209  			Obj: types.ManagedObjectReference{
   210  				Type:  "VirtualMachine",
   211  				Value: "FakeVm0",
   212  			},
   213  			PropSet: []types.DynamicProperty{
   214  				{Name: "name", Val: "vm-0"},
   215  				{Name: "runtime.powerState", Val: "poweredOff"},
   216  				{
   217  					Name: "config.hardware.device",
   218  					Val: []types.BaseVirtualDevice{
   219  						&types.VirtualDisk{
   220  							VirtualDevice: types.VirtualDevice{
   221  								Backing: &types.VirtualDiskFlatVer2BackingInfo{
   222  									VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
   223  										FileName: "disk.vmdk",
   224  									},
   225  								},
   226  							},
   227  						},
   228  					},
   229  				},
   230  				{
   231  					Name: "resourcePool",
   232  					Val: types.ManagedObjectReference{
   233  						Type:  "ResourcePool",
   234  						Value: "FakeResourcePool0",
   235  					},
   236  				},
   237  			},
   238  		}},
   239  		"FakeVm1": {{
   240  			Obj: types.ManagedObjectReference{
   241  				Type:  "VirtualMachine",
   242  				Value: "FakeVm1",
   243  			},
   244  			PropSet: []types.DynamicProperty{
   245  				{Name: "name", Val: "vm-1"},
   246  				{Name: "runtime.powerState", Val: "poweredOn"},
   247  				{
   248  					Name: "config.hardware.device",
   249  					Val: []types.BaseVirtualDevice{
   250  						&types.VirtualDisk{
   251  							VirtualDevice: types.VirtualDevice{
   252  								Backing: &types.VirtualDiskFlatVer2BackingInfo{
   253  									VirtualDeviceFileBackingInfo: types.VirtualDeviceFileBackingInfo{
   254  										FileName: "disk.vmdk",
   255  									},
   256  								},
   257  							},
   258  							CapacityInKB: 1024 * 1024 * 10, // 10 GiB
   259  						},
   260  					},
   261  				},
   262  				{
   263  					Name: "resourcePool",
   264  					Val: types.ManagedObjectReference{
   265  						Type:  "ResourcePool",
   266  						Value: "FakeResourcePool1",
   267  					},
   268  				},
   269  			},
   270  		}},
   271  		"FakeDatastore1": {{
   272  			Obj: types.ManagedObjectReference{
   273  				Type:  "Datastore",
   274  				Value: "FakeDatastore1",
   275  			},
   276  			PropSet: []types.DynamicProperty{
   277  				{Name: "name", Val: "datastore1"},
   278  				{Name: "summary.accessible", Val: false},
   279  			},
   280  		}},
   281  		"FakeDatastore2": {{
   282  			Obj: types.ManagedObjectReference{
   283  				Type:  "Datastore",
   284  				Value: "FakeDatastore2",
   285  			},
   286  			PropSet: []types.DynamicProperty{
   287  				{Name: "name", Val: "datastore2"},
   288  				{Name: "summary.accessible", Val: true},
   289  			},
   290  		}},
   291  		"network-0": {{
   292  			Obj: types.ManagedObjectReference{
   293  				Type:  "Network",
   294  				Value: "network-0",
   295  			},
   296  			PropSet: []types.DynamicProperty{
   297  				{Name: "name", Val: "VM Network"},
   298  			},
   299  		}},
   300  		"network-1": {{
   301  			Obj: types.ManagedObjectReference{
   302  				Type:  "Network",
   303  				Value: "network-1",
   304  			},
   305  			PropSet: []types.DynamicProperty{
   306  				{Name: "name", Val: "zing"},
   307  			},
   308  		}},
   309  		"onetwork-0": {{
   310  			Obj: types.ManagedObjectReference{
   311  				Type:  "OpaqueNetwork",
   312  				Value: "onetwork-0",
   313  			},
   314  			PropSet: []types.DynamicProperty{
   315  				{Name: "name", Val: "arpa"},
   316  			},
   317  		}},
   318  		"dvportgroup-0": {{
   319  			Obj: types.ManagedObjectReference{
   320  				Type:  "DistributedVirtualPortgroup",
   321  				Value: "dvportgroup-0",
   322  			},
   323  			PropSet: []types.DynamicProperty{
   324  				{Name: "name", Val: "yoink"},
   325  				{Name: "config.key", Val: "hole"},
   326  				{
   327  					Name: "config.distributedVirtualSwitch",
   328  					Val: types.ManagedObjectReference{
   329  						Type:  "DistributedVirtualSwitch",
   330  						Value: "dvs-0",
   331  					},
   332  				},
   333  			},
   334  		}},
   335  		"dvs-0": {{
   336  			Obj: types.ManagedObjectReference{
   337  				Type:  "DistributedVirtualSwitch",
   338  				Value: "dvs-0",
   339  			},
   340  			PropSet: []types.DynamicProperty{
   341  				{Name: "uuid", Val: "yup"},
   342  			},
   343  		}},
   344  	}
   345  
   346  	s.roundTripper.importVAppResult = types.ManagedObjectReference{
   347  		Type:  "VirtualMachine",
   348  		Value: "FakeVm0",
   349  	}
   350  	s.roundTripper.taskResult[searchDatastoreTask] = types.HostDatastoreBrowserSearchResults{}
   351  	s.roundTripper.taskResult[cloneVMTask] = types.ManagedObjectReference{
   352  		Type:  "VirtualMachine",
   353  		Value: "FakeVm1",
   354  	}
   355  
   356  	// Create an HTTP server to receive image uploads.
   357  	s.uploadRequests = nil
   358  	s.onImageUpload = nil
   359  	s.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   360  		var buf bytes.Buffer
   361  		io.Copy(&buf, r.Body)
   362  		rcopy := *r
   363  		rcopy.Body = ioutil.NopCloser(&buf)
   364  		s.uploadRequests = append(s.uploadRequests, &rcopy)
   365  		if s.onImageUpload != nil {
   366  			s.onImageUpload(r)
   367  		}
   368  	}))
   369  	s.AddCleanup(func(*gc.C) {
   370  		s.server.Close()
   371  	})
   372  	s.roundTripper.serverURL = s.server.URL
   373  }
   374  
   375  func (s *clientSuite) newFakeClient(roundTripper soap.RoundTripper, dc string) *Client {
   376  	soapURL, err := url.Parse(s.server.URL + "/soap")
   377  	if err != nil {
   378  		panic(err)
   379  	}
   380  
   381  	vimClient := &vim25.Client{
   382  		Client:         soap.NewClient(soapURL, true),
   383  		ServiceContent: s.serviceContent,
   384  		RoundTripper:   roundTripper,
   385  	}
   386  	return &Client{
   387  		client: &govmomi.Client{
   388  			Client:         vimClient,
   389  			SessionManager: session.NewManager(vimClient),
   390  		},
   391  		datacenter: dc,
   392  		logger:     loggo.GetLogger("vsphereclient"),
   393  	}
   394  }
   395  
   396  func (s *clientSuite) TestDial(c *gc.C) {
   397  	mux := http.NewServeMux()
   398  	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
   399  		e := xml.NewEncoder(w)
   400  		e.Encode(soap.Envelope{Body: methods.RetrieveServiceContentBody{
   401  			Res: &types.RetrieveServiceContentResponse{s.serviceContent},
   402  		}})
   403  		e.Flush()
   404  	})
   405  	server := httptest.NewServer(mux)
   406  	defer server.Close()
   407  
   408  	url, err := url.Parse(server.URL)
   409  	c.Assert(err, jc.ErrorIsNil)
   410  
   411  	ctx := context.Background()
   412  	client, err := Dial(ctx, url, "dc", loggo.GetLogger("vsphereclient"))
   413  	c.Assert(err, jc.ErrorIsNil)
   414  	c.Assert(client, gc.NotNil)
   415  }
   416  
   417  func (s *clientSuite) TestClose(c *gc.C) {
   418  	client := s.newFakeClient(&s.roundTripper, "dc0")
   419  	err := client.Close(context.Background())
   420  	c.Assert(err, jc.ErrorIsNil)
   421  	s.roundTripper.CheckCallNames(c, "Logout")
   422  }
   423  
   424  func (s *clientSuite) TestComputeResources(c *gc.C) {
   425  	client := s.newFakeClient(&s.roundTripper, "dc0")
   426  	result, err := client.ComputeResources(context.Background())
   427  	c.Assert(err, jc.ErrorIsNil)
   428  
   429  	s.roundTripper.CheckCalls(c, []testing.StubCall{
   430  		retrievePropertiesStubCall("FakeRootFolder"),
   431  		retrievePropertiesStubCall("FakeRootFolder"),
   432  		retrievePropertiesStubCall("FakeDatacenter"),
   433  		retrievePropertiesStubCall("FakeHostFolder"),
   434  	})
   435  
   436  	c.Assert(result, gc.HasLen, 2)
   437  	c.Assert(result[0].Name, gc.Equals, "z0")
   438  	c.Assert(result[1].Name, gc.Equals, "z1")
   439  }
   440  
   441  func (s *clientSuite) TestDestroyVMFolder(c *gc.C) {
   442  	client := s.newFakeClient(&s.roundTripper, "dc0")
   443  	err := client.DestroyVMFolder(context.Background(), "foo")
   444  	c.Assert(err, jc.ErrorIsNil)
   445  
   446  	s.roundTripper.CheckCalls(c, []testing.StubCall{
   447  		retrievePropertiesStubCall("FakeRootFolder"),
   448  		retrievePropertiesStubCall("FakeRootFolder"),
   449  		retrievePropertiesStubCall("FakeDatacenter"),
   450  		retrievePropertiesStubCall("FakeRootFolder"),
   451  		retrievePropertiesStubCall("FakeDatacenter"),
   452  		retrievePropertiesStubCall("FakeVmFolder"),
   453  		retrievePropertiesStubCall("FakeHostFolder"),
   454  		{"Destroy_Task", nil},
   455  		{"CreatePropertyCollector", nil},
   456  		{"CreateFilter", nil},
   457  		{"WaitForUpdatesEx", nil},
   458  	})
   459  }
   460  
   461  func (s *clientSuite) TestDestroyVMFolderRace(c *gc.C) {
   462  	s.roundTripper.taskError[destroyTask] = &types.LocalizedMethodFault{
   463  		Fault: &types.ManagedObjectNotFound{},
   464  	}
   465  	client := s.newFakeClient(&s.roundTripper, "dc0")
   466  	err := client.DestroyVMFolder(context.Background(), "foo")
   467  	c.Assert(err, jc.ErrorIsNil)
   468  }
   469  
   470  func (s *clientSuite) TestEnsureVMFolder(c *gc.C) {
   471  	client := s.newFakeClient(&s.roundTripper, "dc0")
   472  	folder, err := client.EnsureVMFolder(context.Background(), "foo/bar")
   473  	c.Assert(err, jc.ErrorIsNil)
   474  	c.Assert(folder, gc.NotNil)
   475  
   476  	s.roundTripper.CheckCalls(c, []testing.StubCall{
   477  		retrievePropertiesStubCall("FakeRootFolder"),
   478  		retrievePropertiesStubCall("FakeRootFolder"),
   479  		retrievePropertiesStubCall("FakeDatacenter"),
   480  		{"CreateFolder", nil},
   481  		{"CreateFolder", nil},
   482  	})
   483  }
   484  
   485  func (s *clientSuite) TestMoveVMFolderInto(c *gc.C) {
   486  	client := s.newFakeClient(&s.roundTripper, "dc0")
   487  	err := client.MoveVMFolderInto(context.Background(), "foo", "foo/bar")
   488  	c.Assert(err, jc.ErrorIsNil)
   489  
   490  	s.roundTripper.CheckCalls(c, []testing.StubCall{
   491  		retrievePropertiesStubCall("FakeRootFolder"),
   492  		retrievePropertiesStubCall("FakeRootFolder"),
   493  		retrievePropertiesStubCall("FakeDatacenter"),
   494  		retrievePropertiesStubCall("FakeRootFolder"),
   495  		retrievePropertiesStubCall("FakeDatacenter"),
   496  		retrievePropertiesStubCall("FakeVmFolder"),
   497  		retrievePropertiesStubCall("FakeHostFolder"),
   498  		retrievePropertiesStubCall("FakeRootFolder"),
   499  		retrievePropertiesStubCall("FakeDatacenter"),
   500  		retrievePropertiesStubCall("FakeVmFolder"),
   501  		retrievePropertiesStubCall("FakeControllerVmFolder"),
   502  		retrievePropertiesStubCall("FakeHostFolder"),
   503  		{"MoveIntoFolder_Task", nil},
   504  		{"CreatePropertyCollector", nil},
   505  		{"CreateFilter", nil},
   506  		{"WaitForUpdatesEx", nil},
   507  	})
   508  }
   509  
   510  func (s *clientSuite) TestMoveVMsInto(c *gc.C) {
   511  	client := s.newFakeClient(&s.roundTripper, "dc0")
   512  	err := client.MoveVMsInto(
   513  		context.Background(), "foo",
   514  		types.ManagedObjectReference{
   515  			Type:  "VirtualMachine",
   516  			Value: "vm-0",
   517  		},
   518  		types.ManagedObjectReference{
   519  			Type:  "VirtualMachine",
   520  			Value: "vm-1",
   521  		},
   522  	)
   523  	c.Assert(err, jc.ErrorIsNil)
   524  
   525  	s.roundTripper.CheckCalls(c, []testing.StubCall{
   526  		retrievePropertiesStubCall("FakeRootFolder"),
   527  		retrievePropertiesStubCall("FakeRootFolder"),
   528  		retrievePropertiesStubCall("FakeDatacenter"),
   529  		retrievePropertiesStubCall("FakeRootFolder"),
   530  		retrievePropertiesStubCall("FakeDatacenter"),
   531  		retrievePropertiesStubCall("FakeVmFolder"),
   532  		retrievePropertiesStubCall("FakeHostFolder"),
   533  		{"MoveIntoFolder_Task", nil},
   534  		{"CreatePropertyCollector", nil},
   535  		{"CreateFilter", nil},
   536  		{"WaitForUpdatesEx", nil},
   537  	})
   538  }
   539  
   540  func (s *clientSuite) TestRemoveVirtualMachines(c *gc.C) {
   541  	client := s.newFakeClient(&s.roundTripper, "dc0")
   542  	err := client.RemoveVirtualMachines(context.Background(), "foo/bar/*")
   543  	c.Assert(err, jc.ErrorIsNil)
   544  
   545  	s.roundTripper.CheckCalls(c, []testing.StubCall{
   546  		retrievePropertiesStubCall("FakeRootFolder"),
   547  		retrievePropertiesStubCall("FakeRootFolder"),
   548  		retrievePropertiesStubCall("FakeDatacenter"),
   549  		retrievePropertiesStubCall("FakeVmFolder"),
   550  		retrievePropertiesStubCall("FakeVmFolder"),
   551  		retrievePropertiesStubCall("FakeControllerVmFolder"),
   552  		retrievePropertiesStubCall("FakeModelVmFolder"),
   553  		retrievePropertiesStubCall("FakeVm0", "FakeVm1"),
   554  		{"Destroy_Task", nil},
   555  		{"PowerOffVM_Task", nil},
   556  		{"Destroy_Task", nil},
   557  		{"CreatePropertyCollector", nil},
   558  		{"CreateFilter", nil},
   559  		{"WaitForUpdatesEx", nil},
   560  		{"CreatePropertyCollector", nil},
   561  		{"CreateFilter", nil},
   562  		{"WaitForUpdatesEx", nil},
   563  		{"CreatePropertyCollector", nil},
   564  		{"CreateFilter", nil},
   565  		{"WaitForUpdatesEx", nil},
   566  	})
   567  }
   568  
   569  func (s *clientSuite) TestRemoveVirtualMachinesDestroyRace(c *gc.C) {
   570  	s.roundTripper.taskError[destroyTask] = &types.LocalizedMethodFault{
   571  		Fault: &types.ManagedObjectNotFound{},
   572  	}
   573  	client := s.newFakeClient(&s.roundTripper, "dc0")
   574  	err := client.RemoveVirtualMachines(context.Background(), "foo/bar/*")
   575  	c.Assert(err, jc.ErrorIsNil)
   576  }
   577  
   578  func (s *clientSuite) TestUpdateVirtualMachineExtraConfig(c *gc.C) {
   579  	client := s.newFakeClient(&s.roundTripper, "dc0")
   580  	var vm mo.VirtualMachine
   581  	vm.Self = types.ManagedObjectReference{
   582  		Type:  "VirtualMachine",
   583  		Value: "FakeVm0",
   584  	}
   585  	err := client.UpdateVirtualMachineExtraConfig(
   586  		context.Background(), &vm, map[string]string{
   587  			"k0": "v0",
   588  			"k1": "",
   589  		},
   590  	)
   591  	c.Assert(err, jc.ErrorIsNil)
   592  
   593  	s.roundTripper.CheckCallNames(c,
   594  		"ReconfigVM_Task",
   595  		"CreatePropertyCollector",
   596  		"CreateFilter",
   597  		"WaitForUpdatesEx",
   598  	)
   599  }
   600  
   601  func (s *clientSuite) TestVirtualMachines(c *gc.C) {
   602  	client := s.newFakeClient(&s.roundTripper, "dc0")
   603  	result, err := client.VirtualMachines(context.Background(), "foo/bar/*")
   604  	c.Assert(err, jc.ErrorIsNil)
   605  
   606  	s.roundTripper.CheckCalls(c, []testing.StubCall{
   607  		retrievePropertiesStubCall("FakeRootFolder"),
   608  		retrievePropertiesStubCall("FakeRootFolder"),
   609  		retrievePropertiesStubCall("FakeDatacenter"),
   610  		retrievePropertiesStubCall("FakeVmFolder"),
   611  		retrievePropertiesStubCall("FakeVmFolder"),
   612  		retrievePropertiesStubCall("FakeControllerVmFolder"),
   613  		retrievePropertiesStubCall("FakeModelVmFolder"),
   614  		retrievePropertiesStubCall("FakeVm0"),
   615  		retrievePropertiesStubCall("FakeVm1"),
   616  	})
   617  
   618  	c.Assert(result, gc.HasLen, 2)
   619  	c.Assert(result[0].Name, gc.Equals, "vm-0")
   620  	c.Assert(result[1].Name, gc.Equals, "vm-1")
   621  }
   622  
   623  func (s *clientSuite) TestDatastores(c *gc.C) {
   624  	client := s.newFakeClient(&s.roundTripper, "dc0")
   625  	result, err := client.Datastores(context.Background())
   626  	c.Assert(err, jc.ErrorIsNil)
   627  
   628  	s.roundTripper.CheckCalls(c, []testing.StubCall{
   629  		retrievePropertiesStubCall("FakeRootFolder"),
   630  		retrievePropertiesStubCall("FakeRootFolder"),
   631  		retrievePropertiesStubCall("FakeDatacenter"),
   632  		retrievePropertiesStubCall("FakeDatastoreFolder"),
   633  	})
   634  
   635  	c.Assert(result, gc.HasLen, 2)
   636  	c.Assert(result[0].Name, gc.Equals, "datastore1")
   637  	c.Assert(result[1].Name, gc.Equals, "datastore2")
   638  }
   639  
   640  func (s *clientSuite) TestDeleteDatastoreFile(c *gc.C) {
   641  	client := s.newFakeClient(&s.roundTripper, "dc0")
   642  	err := client.DeleteDatastoreFile(context.Background(), "[datastore1] file/path")
   643  	c.Assert(err, jc.ErrorIsNil)
   644  
   645  	s.roundTripper.CheckCalls(c, []testing.StubCall{
   646  		retrievePropertiesStubCall("FakeRootFolder"),
   647  		retrievePropertiesStubCall("FakeRootFolder"),
   648  		{"DeleteDatastoreFile", []interface{}{"[datastore1] file/path"}},
   649  		{"CreatePropertyCollector", nil},
   650  		{"CreateFilter", nil},
   651  		{"WaitForUpdatesEx", nil},
   652  	})
   653  }
   654  
   655  func (s *clientSuite) TestDeleteDatastoreFileNotFound(c *gc.C) {
   656  	s.roundTripper.taskError[deleteDatastoreFileTask] = &types.LocalizedMethodFault{
   657  		Fault: &types.FileNotFound{},
   658  	}
   659  
   660  	client := s.newFakeClient(&s.roundTripper, "dc0")
   661  	err := client.DeleteDatastoreFile(context.Background(), "[datastore1] file/path")
   662  	c.Assert(err, jc.ErrorIsNil)
   663  }
   664  
   665  func (s *clientSuite) TestDeleteDatastoreError(c *gc.C) {
   666  	s.roundTripper.taskError[deleteDatastoreFileTask] = &types.LocalizedMethodFault{
   667  		Fault:            &types.NotAuthenticated{},
   668  		LocalizedMessage: "nope",
   669  	}
   670  
   671  	client := s.newFakeClient(&s.roundTripper, "dc0")
   672  	err := client.DeleteDatastoreFile(context.Background(), "[datastore1] file/path")
   673  	c.Assert(err, gc.ErrorMatches, "nope")
   674  }