github.com/cloudfoundry-incubator/stembuild@v0.0.0-20211223202937-5b61d62226c6/iaas_cli/iaas_clients/vcenter_client_test.go (about)

     1  package iaas_clients
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  
     9  	"github.com/cloudfoundry-incubator/stembuild/iaas_cli/iaas_clifakes"
    10  	. "github.com/onsi/ginkgo"
    11  	. "github.com/onsi/gomega"
    12  )
    13  
    14  var _ = Describe("VcenterClient", func() {
    15  	var (
    16  		runner                  *iaas_clifakes.FakeCliRunner
    17  		username, password, url string
    18  		vcenterClient           *VcenterClient
    19  		credentialUrl           string
    20  		caCertFile              string
    21  	)
    22  
    23  	BeforeEach(func() {
    24  		runner = &iaas_clifakes.FakeCliRunner{}
    25  		username, password, caCertFile, url = "username", "password", "", "url"
    26  		vcenterClient = NewVcenterClient(username, password, url, caCertFile, runner)
    27  		credentialUrl = fmt.Sprintf("%s:%s@%s", username, password, url)
    28  	})
    29  
    30  	Context("NewVcenterClient", func() {
    31  		It("url encodes credentials with special characters", func() {
    32  			client := NewVcenterClient(`special\chars!user#`, `special^chars*pass`, url, caCertFile, runner)
    33  
    34  			urlEncodedCredentials := `special%5Cchars%21user%23:special%5Echars%2Apass@url`
    35  			expectedArgs := []string{"about", "-u", urlEncodedCredentials}
    36  
    37  			runner.RunReturns(0)
    38  			err := client.ValidateCredentials()
    39  			argsForRun := runner.RunArgsForCall(0)
    40  
    41  			Expect(err).To(Not(HaveOccurred()))
    42  			Expect(runner.RunCallCount()).To(Equal(1))
    43  			Expect(argsForRun).To(Equal(expectedArgs))
    44  		})
    45  	})
    46  
    47  	Context("A ca cert file is specified", func() {
    48  		It("Passes the ca cert to govc", func() {
    49  			vcenterClient = NewVcenterClient(username, password, url, "somefile.txt", runner)
    50  			expectedArgs := []string{"about", "-u", credentialUrl, "-tls-ca-certs=somefile.txt"}
    51  
    52  			runner.RunReturns(0)
    53  			err := vcenterClient.ValidateCredentials()
    54  			argsForRun := runner.RunArgsForCall(0)
    55  
    56  			Expect(err).To(Not(HaveOccurred()))
    57  			Expect(runner.RunCallCount()).To(Equal(1))
    58  			Expect(argsForRun).To(Equal(expectedArgs))
    59  		})
    60  	})
    61  
    62  	Context("ValidateCredentials", func() {
    63  		It("When the login credentials are correct, login is successful", func() {
    64  			expectedArgs := []string{"about", "-u", credentialUrl}
    65  
    66  			runner.RunReturns(0)
    67  			err := vcenterClient.ValidateCredentials()
    68  			argsForRun := runner.RunArgsForCall(0)
    69  
    70  			Expect(err).To(Not(HaveOccurred()))
    71  			Expect(runner.RunCallCount()).To(Equal(1))
    72  			Expect(argsForRun).To(Equal(expectedArgs))
    73  		})
    74  
    75  		It("When the login credentials are incorrect, login is a failure", func() {
    76  			expectedArgs := []string{"about", "-u", credentialUrl}
    77  
    78  			runner.RunReturns(1)
    79  			err := vcenterClient.ValidateCredentials()
    80  			argsForRun := runner.RunArgsForCall(0)
    81  
    82  			Expect(err).To(HaveOccurred())
    83  			Expect(runner.RunCallCount()).To(Equal(1))
    84  			Expect(argsForRun).To(Equal(expectedArgs))
    85  			Expect(err).To(MatchError("vcenter_client - invalid credentials for: username:REDACTED@url"))
    86  		})
    87  	})
    88  
    89  	Context("validateUrl", func() {
    90  		It("When the url is valid, there is no error", func() {
    91  			expectedArgs := []string{"about", "-u", url}
    92  
    93  			runner.RunReturns(0)
    94  			err := vcenterClient.ValidateUrl()
    95  			argsForRun := runner.RunArgsForCall(0)
    96  
    97  			Expect(err).To(Not(HaveOccurred()))
    98  			Expect(runner.RunCallCount()).To(Equal(1))
    99  			Expect(argsForRun).To(Equal(expectedArgs))
   100  		})
   101  
   102  		It("When the url is invalid, there is an error", func() {
   103  			expectedArgs := []string{"about", "-u", url}
   104  
   105  			runner.RunReturns(1)
   106  			err := vcenterClient.ValidateUrl()
   107  			argsForRun := runner.RunArgsForCall(0)
   108  
   109  			Expect(err).To(HaveOccurred())
   110  			Expect(runner.RunCallCount()).To(Equal(1))
   111  			Expect(argsForRun).To(Equal(expectedArgs))
   112  			Expect(err).To(MatchError("vcenter_client - unable to validate url: url"))
   113  		})
   114  
   115  		It("a validateUrl failure mentions the ca cert if one was specified", func() {
   116  
   117  			vcenterClient = NewVcenterClient(username, password, url, "somefile.txt", runner)
   118  			expectedArgs := []string{"about", "-u", url, "-tls-ca-certs=somefile.txt"}
   119  
   120  			runner.RunReturns(1)
   121  			err := vcenterClient.ValidateUrl()
   122  			argsForRun := runner.RunArgsForCall(0)
   123  
   124  			Expect(err).To(HaveOccurred())
   125  			Expect(runner.RunCallCount()).To(Equal(1))
   126  			Expect(argsForRun).To(Equal(expectedArgs))
   127  			Expect(err).To(MatchError("vcenter_client - invalid ca certs or url: url"))
   128  		})
   129  
   130  		It("passes the ca cert to govc when specified", func() {
   131  
   132  			vcenterClient = NewVcenterClient(username, password, url, "somefile.txt", runner)
   133  			expectedArgs := []string{"about", "-u", url, "-tls-ca-certs=somefile.txt"}
   134  
   135  			runner.RunReturns(0)
   136  			err := vcenterClient.ValidateUrl()
   137  			argsForRun := runner.RunArgsForCall(0)
   138  
   139  			Expect(err).To(Not(HaveOccurred()))
   140  			Expect(runner.RunCallCount()).To(Equal(1))
   141  			Expect(argsForRun).To(Equal(expectedArgs))
   142  		})
   143  	})
   144  
   145  	Context("FindVM", func() {
   146  		It("If the VM path is valid, and the VM is found", func() {
   147  			expectedArgs := []string{"find", "-u", credentialUrl, "-maxdepth=0", "validVMPath"}
   148  			runner.RunReturns(0)
   149  			err := vcenterClient.FindVM("validVMPath")
   150  			argsForRun := runner.RunArgsForCall(0)
   151  
   152  			Expect(err).To(Not(HaveOccurred()))
   153  			Expect(runner.RunCallCount()).To(Equal(1))
   154  			Expect(argsForRun).To(Equal(expectedArgs))
   155  		})
   156  
   157  		It("If the VM path is invalid", func() {
   158  			expectedArgs := []string{"find", "-u", credentialUrl, "-maxdepth=0", "invalidVMPath"}
   159  			runner.RunReturns(1)
   160  			err := vcenterClient.FindVM("invalidVMPath")
   161  			argsForRun := runner.RunArgsForCall(0)
   162  
   163  			Expect(err).To(HaveOccurred())
   164  			Expect(runner.RunCallCount()).To(Equal(1))
   165  			Expect(argsForRun).To(Equal(expectedArgs))
   166  			Expect(err).To(MatchError("vcenter_client - unable to find VM: invalidVMPath. Ensure your inventory path is formatted properly and includes \"vm\" in its path, example: /my-datacenter/vm/my-folder/my-vm-name"))
   167  		})
   168  	})
   169  
   170  	Describe("RemoveDevice", func() {
   171  		It("Removes a device from the given VM", func() {
   172  			runner.RunReturns(0)
   173  			err := vcenterClient.RemoveDevice("validVMPath", "device")
   174  
   175  			Expect(err).To(Not(HaveOccurred()))
   176  			expectedArgs := []string{"device.remove", "-u", credentialUrl, "-vm", "validVMPath", "device"}
   177  			Expect(runner.RunArgsForCall(0)).To(Equal(expectedArgs))
   178  		})
   179  
   180  		It("Returns an error if VCenter reports a failure removing a device", func() {
   181  			runner.RunReturns(1)
   182  			err := vcenterClient.RemoveDevice("VMPath", "deviceName")
   183  
   184  			Expect(err).To(HaveOccurred())
   185  			Expect(err).To(MatchError("vcenter_client - deviceName could not be removed"))
   186  		})
   187  	})
   188  
   189  	Describe("EjectCDrom", func() {
   190  		It("Ejects a cd rom from the given VM", func() {
   191  			runner.RunReturns(0)
   192  			err := vcenterClient.EjectCDRom("validVMPath", "deviceName")
   193  
   194  			Expect(err).To(Not(HaveOccurred()))
   195  			expectedArgs := []string{"device.cdrom.eject", "-u", credentialUrl, "-vm", "validVMPath", "-device", "deviceName"}
   196  			Expect(runner.RunArgsForCall(0)).To(Equal(expectedArgs))
   197  		})
   198  
   199  		It("Returns an error if VCenter reports a failure ejecting the cd rom", func() {
   200  			runner.RunReturns(1)
   201  			err := vcenterClient.EjectCDRom("VMPath", "deviceName")
   202  
   203  			Expect(err).To(HaveOccurred())
   204  			Expect(err).To(MatchError("vcenter_client - deviceName could not be ejected"))
   205  		})
   206  	})
   207  
   208  	Context("ListDevices", func() {
   209  		var govcListDevicesOutput = `ide-200            VirtualIDEController          IDE 0
   210  ide-201            VirtualIDEController          IDE 1
   211  ps2-300            VirtualPS2Controller          PS2 controller 0
   212  pci-100            VirtualPCIController          PCI controller 0
   213  sio-400            VirtualSIOController          SIO controller 0
   214  floppy-8000        VirtualFloppy                 Remote
   215  ethernet-0         VirtualE1000e                 internal-network
   216  `
   217  
   218  		It("returns a list of devices for the given VM", func() {
   219  			runner.RunWithOutputReturns(govcListDevicesOutput, 0, nil)
   220  
   221  			devices, err := vcenterClient.ListDevices("/path/to/vm")
   222  
   223  			Expect(err).NotTo(HaveOccurred())
   224  			Expect(devices).To(ConsistOf(
   225  				"ide-200", "ide-201", "ps2-300", "pci-100", "sio-400", "floppy-8000", "ethernet-0",
   226  			))
   227  
   228  			Expect(runner.RunWithOutputArgsForCall(0)).To(Equal([]string{"device.ls", "-u", credentialUrl, "-vm", "/path/to/vm"}))
   229  		})
   230  
   231  		It("returns an error if govc runner returns non zero exit code", func() {
   232  			runner.RunWithOutputReturns("", 1, nil)
   233  
   234  			_, err := vcenterClient.ListDevices("/path/to/vm")
   235  
   236  			Expect(err).To(MatchError("vcenter_client - failed to list devices in vCenter, govc exit code 1"))
   237  		})
   238  
   239  		It("returns an error if RunWithOutput encounters an error", func() {
   240  			runner.RunWithOutputReturns("", 0, errors.New("some environment error"))
   241  
   242  			_, err := vcenterClient.ListDevices("/path/to/vm")
   243  
   244  			Expect(err).To(MatchError("vcenter_client - failed to parse list of devices. Err: some environment error"))
   245  		})
   246  
   247  		It("returns govc exit code error, when both govc exit code is non zero and RunWithOutput encounters an error", func() {
   248  			runner.RunWithOutputReturns("", 1, errors.New("some environment error"))
   249  
   250  			_, err := vcenterClient.ListDevices("/path/to/vm")
   251  
   252  			Expect(err).To(MatchError("vcenter_client - failed to list devices in vCenter, govc exit code 1"))
   253  		})
   254  	})
   255  
   256  	Context("ExportVM", func() {
   257  		var destinationDir string
   258  		BeforeEach(func() {
   259  			destinationDir, _ = ioutil.TempDir(os.TempDir(), "destinationDir")
   260  		})
   261  		It("exports the VM to local machine from vcenter using vm inventory path", func() {
   262  			expectedArgs := []string{"export.ovf", "-u", credentialUrl, "-sha", "1", "-vm", "validVMPath", destinationDir}
   263  			runner.RunReturns(0)
   264  			err := vcenterClient.ExportVM("validVMPath", destinationDir)
   265  
   266  			Expect(err).To(Not(HaveOccurred()))
   267  			Expect(runner.RunCallCount()).To(Equal(1))
   268  
   269  			argsForRun := runner.RunArgsForCall(0)
   270  			Expect(argsForRun).To(Equal(expectedArgs))
   271  		})
   272  
   273  		It("Returns an error message if ExportVM fails to export the VM", func() {
   274  			vmInventoryPath := "validVMPath"
   275  			expectedArgs := []string{"export.ovf", "-u", credentialUrl, "-sha", "1", "-vm", vmInventoryPath, destinationDir}
   276  			runner.RunReturns(1)
   277  			err := vcenterClient.ExportVM("validVMPath", destinationDir)
   278  
   279  			expectedErrorMsg := fmt.Sprintf("vcenter_client - %s could not be exported", vmInventoryPath)
   280  			Expect(err).To(HaveOccurred())
   281  			Expect(runner.RunCallCount()).To(Equal(1))
   282  
   283  			argsForRun := runner.RunArgsForCall(0)
   284  			Expect(argsForRun).To(Equal(expectedArgs))
   285  			Expect(err.Error()).To(Equal(expectedErrorMsg))
   286  		})
   287  
   288  		It("prints an appropriate error message if the given directory doesn't exist", func() {
   289  			err := vcenterClient.ExportVM("validVMPath", "/FooBar/stuff")
   290  			Expect(err).To(HaveOccurred())
   291  
   292  			Expect(err.Error()).To(Equal("vcenter_client - provided destination directory: /FooBar/stuff does not exist"))
   293  		})
   294  	})
   295  
   296  	Describe("UploadArtifact", func() {
   297  		It("Uploads artifact to the given vm", func() {
   298  			runner.RunReturns(0)
   299  			err := vcenterClient.UploadArtifact("validVMPath", "artifact", "C:\\provision\\artifact", "user", "pass")
   300  
   301  			Expect(err).To(Not(HaveOccurred()))
   302  			expectedArgs := []string{"guest.upload", "-u", credentialUrl, "-f", "-l", "user:pass", "-vm", "validVMPath", "artifact", "C:\\provision\\artifact"}
   303  			Expect(runner.RunArgsForCall(0)).To(Equal(expectedArgs))
   304  		})
   305  
   306  		It("Returns an error if VCenter reports a failure uploading the artifact", func() {
   307  			runner.RunReturns(1)
   308  			err := vcenterClient.UploadArtifact("validVMPath", "artifact", "C:\\provision\\artifact", "user", "pass")
   309  
   310  			Expect(err).To(HaveOccurred())
   311  			Expect(err).To(MatchError("vcenter_client - artifact could not be uploaded"))
   312  		})
   313  	})
   314  
   315  	Describe("MakeDirectory", func() {
   316  		It("Creates the directory on the vm", func() {
   317  			runner.RunReturns(0)
   318  			err := vcenterClient.MakeDirectory("validVMPath", "C:\\provision", "user", "pass")
   319  
   320  			Expect(err).To(Not(HaveOccurred()))
   321  			expectedArgs := []string{"guest.mkdir", "-u", credentialUrl, "-l", "user:pass", "-vm", "validVMPath", "-p", "C:\\provision"}
   322  			Expect(runner.RunArgsForCall(0)).To(Equal(expectedArgs))
   323  		})
   324  
   325  		It("Returns an error if VCenter reports a failure making the directory", func() {
   326  			runner.RunReturns(1)
   327  			err := vcenterClient.MakeDirectory("validVMPath", "C:\\provision", "user", "pass")
   328  
   329  			Expect(err).To(HaveOccurred())
   330  			expectedArgs := []string{"guest.mkdir", "-u", credentialUrl, "-l", "user:pass", "-vm", "validVMPath", "-p", "C:\\provision"}
   331  			Expect(runner.RunArgsForCall(0)).To(Equal(expectedArgs))
   332  
   333  			Expect(err).To(MatchError("vcenter_client - directory `C:\\provision` could not be created"))
   334  		})
   335  	})
   336  
   337  	Describe("Start", func() {
   338  		It("runs the command on the vm", func() {
   339  			runner.RunWithOutputReturns("1856\n", 0, nil) // govc add '\n' to the output
   340  			pid, err := vcenterClient.Start("validVMPath", "user", "pass", "command", "arg1", "arg2", "arg3")
   341  
   342  			Expect(err).To(Not(HaveOccurred()))
   343  			Expect(pid).To(Equal("1856"))
   344  			expectedArgs := []string{"guest.start", "-u", credentialUrl, "-l", "user:pass", "-vm", "validVMPath", "command", "arg1", "arg2", "arg3"}
   345  			Expect(runner.RunWithOutputCallCount()).To(Equal(1))
   346  			Expect(runner.RunWithOutputArgsForCall(0)).To(Equal(expectedArgs))
   347  		})
   348  		It("returns an error when RunWithOutput fails", func() {
   349  			runner.RunWithOutputReturns("", 0, errors.New("error"))
   350  			_, err := vcenterClient.Start("validVMPath", "user", "pass", "command2", "arg1", "arg2", "arg3")
   351  			Expect(err).To(HaveOccurred())
   352  			Expect(err).To(MatchError("vcenter_client - failed to run 'command2': error"))
   353  
   354  		})
   355  		It("returns an error when RunWithOutput returns an errCode", func() {
   356  			runner.RunWithOutputReturns("", 1, nil)
   357  			_, err := vcenterClient.Start("validVMPath", "user", "pass", "command2", "arg1", "arg2", "arg3")
   358  			Expect(err).To(HaveOccurred())
   359  			Expect(err).To(MatchError("vcenter_client - 'command2' returned exit code: 1"))
   360  		})
   361  	})
   362  
   363  	Describe("WaitForExit", func() {
   364  		// Sample output came from running `govc guest.ps` with the JSON flag set
   365  		const sampleOutput = `{"ProcessInfo":[{"Name":"powershell.exe","Pid":1296,"Owner":"Administrator","CmdLine":"\"c:\\Windows\\System32\\WindowsPowershell\\v1.0\\powershell.exe\" dir","StartTime":"2019-03-26T18:33:31Z","EndTime":"2019-03-26T18:33:34Z","ExitCode":42}]}`
   366  		const sampleOutputPidNotFound = `{"ProcessInfo":null}`
   367  		const sampleOutputBadJson = `bad bad json format`
   368  
   369  		It("returns the process' exit code upon success", func() {
   370  			runner.RunWithOutputReturns(sampleOutput, 0, nil)
   371  			exitCode, err := vcenterClient.WaitForExit("validVMPath", "user", "pass", "1296")
   372  
   373  			Expect(err).To(Not(HaveOccurred()))
   374  			Expect(exitCode).To(Equal(42))
   375  			expectedArgs := []string{"guest.ps", "-u", credentialUrl, "-l", "user:pass", "-vm", "validVMPath", "-p", "1296", "-X", "-json"}
   376  			Expect(runner.RunWithOutputCallCount()).To(Equal(1))
   377  			Expect(runner.RunWithOutputArgsForCall(0)).To(Equal(expectedArgs))
   378  		})
   379  
   380  		It("returns an error if the process ID cannot be found", func() {
   381  			runner.RunWithOutputReturns(sampleOutputPidNotFound, 0, nil)
   382  			_, err := vcenterClient.WaitForExit("validVMPath", "user", "pass", "1296")
   383  
   384  			Expect(err).To(HaveOccurred())
   385  			Expect(err).To(MatchError("vcenter_client - couldn't get exit code for PID 1296"))
   386  		})
   387  
   388  		It("returns an error if a malformed json is returned", func() {
   389  			runner.RunWithOutputReturns(sampleOutputBadJson, 0, nil)
   390  			_, err := vcenterClient.WaitForExit("validVMPath", "user", "pass", "1296")
   391  
   392  			Expect(err).To(HaveOccurred())
   393  			Expect(err).To(MatchError("vcenter_client - received bad JSON output for PID 1296: bad bad json format"))
   394  
   395  		})
   396  
   397  		It("returns an error when RunWithOutput fails", func() {
   398  			runner.RunWithOutputReturns(sampleOutput, 0, errors.New("bad command error"))
   399  			_, err := vcenterClient.WaitForExit("validVMPath", "user", "pass", "3369")
   400  
   401  			Expect(err).To(HaveOccurred())
   402  			Expect(err).To(MatchError("vcenter_client - failed to fetch exit code for PID 3369: bad command error"))
   403  
   404  		})
   405  
   406  		It("returns an error when RunWithOutput returns an errCode", func() {
   407  			runner.RunWithOutputReturns(sampleOutput, 20, nil)
   408  			_, err := vcenterClient.WaitForExit("validVMPath", "user", "pass", "11678")
   409  
   410  			Expect(err).To(HaveOccurred())
   411  			Expect(err).To(MatchError("vcenter_client - fetching PID 11678 returned with exit code: 20"))
   412  		})
   413  	})
   414  
   415  	Describe("IsPoweredOff", func() {
   416  		It("Uses vm.info correctly to get power state", func() {
   417  			expectedArgs := []string{"vm.info", "-u", credentialUrl, "validVMPath"}
   418  			runner.RunWithOutputReturns("some result", 0, nil)
   419  
   420  			_, err := vcenterClient.IsPoweredOff("validVMPath")
   421  			argsForRun := runner.RunWithOutputArgsForCall(0)
   422  
   423  			Expect(err).To(Not(HaveOccurred()))
   424  			Expect(argsForRun).To(Not(ContainElement("-vm.ipath")))
   425  			Expect(argsForRun).To(Equal(expectedArgs))
   426  
   427  		})
   428  		It("Gets the power state of the vm and returns false when vm is not powered off", func() {
   429  			runner.RunWithOutputReturns("Power state:  poweredOn", 0, nil)
   430  			out, err := vcenterClient.IsPoweredOff("validVMPath")
   431  
   432  			Expect(out).To(BeFalse())
   433  			Expect(err).To(Not(HaveOccurred()))
   434  			Expect(runner.RunWithOutputCallCount()).To(Equal(1))
   435  		})
   436  		It("Gets the power state of the vm and returns true when the vm is powered off", func() {
   437  			runner.RunWithOutputReturns("Power state:  poweredOff", 0, nil)
   438  			out, err := vcenterClient.IsPoweredOff("validVMPath")
   439  
   440  			Expect(out).To(BeTrue())
   441  			Expect(err).To(Not(HaveOccurred()))
   442  			Expect(runner.RunWithOutputCallCount()).To(Equal(1))
   443  		})
   444  
   445  		It("Returns an exit code error if the runner returns a non zero exit code", func() {
   446  			runner.RunWithOutputReturns("", 1, nil)
   447  			_, err := vcenterClient.IsPoweredOff("validVMPath")
   448  
   449  			Expect(err).To(HaveOccurred())
   450  			Expect(err).To(MatchError("vcenter_client - failed to get vm info, govc exit code: 1"))
   451  		})
   452  
   453  		It("Returns an error if VCenter reports a failure getting the power state", func() {
   454  			runner.RunWithOutputReturns("", 0, errors.New("some power state issue"))
   455  			_, err := vcenterClient.IsPoweredOff("validVMPath")
   456  
   457  			Expect(err).To(HaveOccurred())
   458  			Expect(err).To(MatchError("vcenter_client - failed to determine vm power state: some power state issue"))
   459  		})
   460  
   461  		It("Returns an exit code error if the runner returns a non zero exit code and VCenter reports a failure getting the power state", func() {
   462  			runner.RunWithOutputReturns("", 1, errors.New("some power state issue"))
   463  			_, err := vcenterClient.IsPoweredOff("validVMPath")
   464  
   465  			Expect(err).To(HaveOccurred())
   466  			Expect(err).To(MatchError("vcenter_client - failed to get vm info, govc exit code: 1"))
   467  		})
   468  	})
   469  
   470  })