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 })