1package vm_test
2
3import (
4	"errors"
5	"time"
6
7	. "github.com/cloudfoundry/bosh-cli/deployment/vm"
8	. "github.com/onsi/ginkgo"
9	. "github.com/onsi/gomega"
10
11	biagentclient "github.com/cloudfoundry/bosh-agent/agentclient"
12	bias "github.com/cloudfoundry/bosh-agent/agentclient/applyspec"
13	bicloud "github.com/cloudfoundry/bosh-cli/cloud"
14	biconfig "github.com/cloudfoundry/bosh-cli/config"
15	bidisk "github.com/cloudfoundry/bosh-cli/deployment/disk"
16	bideplmanifest "github.com/cloudfoundry/bosh-cli/deployment/manifest"
17	"github.com/cloudfoundry/bosh-utils/logger/loggerfakes"
18	biproperty "github.com/cloudfoundry/bosh-utils/property"
19	fakesys "github.com/cloudfoundry/bosh-utils/system/fakes"
20
21	fakebiagentclient "github.com/cloudfoundry/bosh-agent/agentclient/fakes"
22	fakebicloud "github.com/cloudfoundry/bosh-cli/cloud/fakes"
23	fakebiconfig "github.com/cloudfoundry/bosh-cli/config/fakes"
24	fakebidisk "github.com/cloudfoundry/bosh-cli/deployment/disk/fakes"
25	fakebivm "github.com/cloudfoundry/bosh-cli/deployment/vm/fakes"
26	fakebiui "github.com/cloudfoundry/bosh-cli/ui/fakes"
27)
28
29var _ = Describe("VM", func() {
30	var (
31		vm               VM
32		fakeVMRepo       *fakebiconfig.FakeVMRepo
33		fakeStemcellRepo *fakebiconfig.FakeStemcellRepo
34		fakeDiskDeployer *fakebivm.FakeDiskDeployer
35		fakeAgentClient  *fakebiagentclient.FakeAgentClient
36		fakeCloud        *fakebicloud.FakeCloud
37		applySpec        bias.ApplySpec
38		diskPool         bideplmanifest.DiskPool
39		timeService      *FakeClock
40		fs               *fakesys.FakeFileSystem
41		logger           *loggerfakes.FakeLogger
42	)
43
44	BeforeEach(func() {
45		fakeAgentClient = &fakebiagentclient.FakeAgentClient{}
46		timeService = &FakeClock{Times: []time.Time{time.Now(), time.Now().Add(10 * time.Minute)}}
47
48		// apply spec is only being passed to the agent client, so it doesn't need much content for testing
49		applySpec = bias.ApplySpec{
50			Deployment: "fake-deployment-name",
51		}
52
53		diskPool = bideplmanifest.DiskPool{
54			Name:     "fake-persistent-disk-pool-name",
55			DiskSize: 1024,
56			CloudProperties: biproperty.Map{
57				"fake-disk-pool-cloud-property-key": "fake-disk-pool-cloud-property-value",
58			},
59		}
60
61		logger = &loggerfakes.FakeLogger{}
62		fs = fakesys.NewFakeFileSystem()
63		fakeCloud = fakebicloud.NewFakeCloud()
64		fakeVMRepo = fakebiconfig.NewFakeVMRepo()
65		fakeStemcellRepo = fakebiconfig.NewFakeStemcellRepo()
66		fakeDiskDeployer = fakebivm.NewFakeDiskDeployer()
67		vm = NewVM(
68			"fake-vm-cid",
69			fakeVMRepo,
70			fakeStemcellRepo,
71			fakeDiskDeployer,
72			fakeAgentClient,
73			fakeCloud,
74			timeService,
75			fs,
76			logger,
77		)
78	})
79
80	Describe("Exists", func() {
81		It("returns true when the vm exists", func() {
82			fakeCloud.HasVMFound = true
83
84			exists, err := vm.Exists()
85			Expect(err).ToNot(HaveOccurred())
86			Expect(exists).To(BeTrue())
87		})
88
89		It("returns false when the vm does not exist", func() {
90			fakeCloud.HasVMFound = false
91
92			exists, err := vm.Exists()
93			Expect(err).ToNot(HaveOccurred())
94			Expect(exists).To(BeFalse())
95		})
96
97		It("returns error when checking fails", func() {
98			fakeCloud.HasVMErr = errors.New("fake-has-vm-error")
99
100			_, err := vm.Exists()
101			Expect(err).To(HaveOccurred())
102			Expect(err.Error()).To(ContainSubstring("fake-has-vm-error"))
103		})
104	})
105
106	Describe("UpdateDisks", func() {
107		var expectedDisks []bidisk.Disk
108
109		BeforeEach(func() {
110			fakeDisk := fakebidisk.NewFakeDisk("fake-disk-cid")
111			expectedDisks = []bidisk.Disk{fakeDisk}
112			fakeDiskDeployer.SetDeployBehavior(expectedDisks, nil)
113		})
114
115		It("delegates to DiskDeployer.Deploy", func() {
116			fakeStage := fakebiui.NewFakeStage()
117
118			disks, err := vm.UpdateDisks(diskPool, fakeStage)
119			Expect(err).NotTo(HaveOccurred())
120			Expect(disks).To(Equal(expectedDisks))
121
122			Expect(fakeDiskDeployer.DeployInputs).To(Equal([]fakebivm.DeployInput{
123				{
124					DiskPool:         diskPool,
125					Cloud:            fakeCloud,
126					VM:               vm,
127					EventLoggerStage: fakeStage,
128				},
129			}))
130		})
131	})
132
133	Describe("Drain", func() {
134		It("drains and waits a specific amount of time", func() {
135			fakeAgentClient.DrainReturns(15, nil)
136			err := vm.Drain()
137			Expect(err).ToNot(HaveOccurred())
138			Expect(fakeAgentClient.DrainCallCount()).To(Equal(1))
139			Expect(len(timeService.SleepCalls)).To(Equal(1))
140			Expect(timeService.SleepCalls[0]).To(Equal(15 * time.Second))
141		})
142
143		It("drains, waits, and retries until given a positive result", func() {
144			fakeAgentClient.DrainReturnsOnCall(0, -15, nil)
145			fakeAgentClient.DrainReturnsOnCall(1, -16, nil)
146			fakeAgentClient.DrainReturnsOnCall(2, 10, nil)
147			err := vm.Drain()
148			Expect(err).ToNot(HaveOccurred())
149			Expect(fakeAgentClient.DrainCallCount()).To(Equal(3))
150			Expect(fakeAgentClient.DrainArgsForCall(0)).To(Equal("shutdown"))
151			Expect(fakeAgentClient.DrainArgsForCall(1)).To(Equal("status"))
152			Expect(fakeAgentClient.DrainArgsForCall(2)).To(Equal("status"))
153			Expect(len(timeService.SleepCalls)).To(Equal(3))
154			Expect(timeService.SleepCalls[0]).To(Equal(15 * time.Second))
155			Expect(timeService.SleepCalls[1]).To(Equal(16 * time.Second))
156			Expect(timeService.SleepCalls[2]).To(Equal(10 * time.Second))
157		})
158
159		Context("when draining an agent fails", func() {
160			BeforeEach(func() {
161				fakeAgentClient.DrainReturns(0, errors.New("fake-drain-error"))
162			})
163
164			It("returns an error", func() {
165				err := vm.Drain()
166				Expect(err).To(HaveOccurred())
167				Expect(err.Error()).To(ContainSubstring("fake-drain-error"))
168			})
169		})
170
171		Context("when drain get_status fails", func() {
172			BeforeEach(func() {
173				fakeAgentClient.DrainReturnsOnCall(0, -15, nil)
174				fakeAgentClient.DrainReturnsOnCall(1, 0, errors.New("fake-drain-error"))
175			})
176
177			It("returns an error", func() {
178				err := vm.Drain()
179				Expect(err).To(HaveOccurred())
180				Expect(err.Error()).To(ContainSubstring("fake-drain-error"))
181			})
182		})
183	})
184
185	Describe("Stop", func() {
186		It("stops agent services", func() {
187			err := vm.Stop()
188			Expect(err).ToNot(HaveOccurred())
189			Expect(fakeAgentClient.StopCallCount()).To(Equal(1))
190		})
191
192		Context("when stopping an agent fails", func() {
193			BeforeEach(func() {
194				fakeAgentClient.StopReturns(errors.New("fake-stop-error"))
195			})
196
197			It("returns an error", func() {
198				err := vm.Stop()
199				Expect(err).To(HaveOccurred())
200				Expect(err.Error()).To(ContainSubstring("fake-stop-error"))
201			})
202		})
203	})
204
205	Describe("Apply", func() {
206		It("sends apply spec to the agent", func() {
207			err := vm.Apply(applySpec)
208			Expect(err).ToNot(HaveOccurred())
209			Expect(fakeAgentClient.ApplyArgsForCall(0)).To(Equal(applySpec))
210		})
211
212		Context("when sending apply spec to the agent fails", func() {
213			BeforeEach(func() {
214				fakeAgentClient.ApplyReturns(errors.New("fake-agent-apply-err"))
215			})
216
217			It("returns an error", func() {
218				err := vm.Apply(applySpec)
219				Expect(err).To(HaveOccurred())
220				Expect(err.Error()).To(ContainSubstring("fake-agent-apply-err"))
221			})
222		})
223	})
224
225	Describe("Start", func() {
226		It("starts agent services", func() {
227			err := vm.Start()
228			Expect(err).ToNot(HaveOccurred())
229			Expect(fakeAgentClient.StartCallCount()).To(Equal(1))
230		})
231
232		Context("when starting an agent fails", func() {
233			BeforeEach(func() {
234				fakeAgentClient.StartReturns(errors.New("fake-start-error"))
235			})
236
237			It("returns an error", func() {
238				err := vm.Start()
239				Expect(err).To(HaveOccurred())
240				Expect(err.Error()).To(ContainSubstring("fake-start-error"))
241			})
242		})
243	})
244
245	Describe("WaitToBeRunning", func() {
246		var invocations int
247
248		BeforeEach(func() {
249			responses := []struct {
250				state biagentclient.AgentState
251				err   error
252			}{
253				{biagentclient.AgentState{JobState: "pending"}, nil},
254				{biagentclient.AgentState{JobState: "pending"}, nil},
255				{biagentclient.AgentState{JobState: "running"}, nil},
256			}
257			fakeAgentClient.GetStateStub = func() (biagentclient.AgentState, error) {
258				i := responses[invocations]
259				invocations++
260				return i.state, i.err
261			}
262		})
263
264		It("waits until agent reports state as running", func() {
265			err := vm.WaitToBeRunning(5, 0)
266			Expect(err).ToNot(HaveOccurred())
267			Expect(invocations).To(Equal(3))
268		})
269	})
270
271	Describe("AttachDisk", func() {
272		var disk *fakebidisk.FakeDisk
273
274		BeforeEach(func() {
275			fakeTime := time.Date(2016, time.November, 10, 23, 0, 0, 0, time.UTC)
276			timeService = &FakeClock{Times: []time.Time{fakeTime, time.Now(), time.Now().Add(10 * time.Minute)}}
277			disk = fakebidisk.NewFakeDisk("fake-disk-cid")
278
279			metadata := bicloud.VMMetadata{
280				"director":       "bosh-init",
281				"deployment":     "some-deployment",
282				"name":           "some-instance-group/0",
283				"job":            "some-instance-group",
284				"instance_group": "some-instance-group",
285				"index":          "0",
286				"custom_tag1":    "custom_value1",
287				"custom_tag2":    "custom_value2",
288			}
289			vm = NewVMWithMetadata(
290				"fake-vm-cid",
291				fakeVMRepo,
292				fakeStemcellRepo,
293				fakeDiskDeployer,
294				fakeAgentClient,
295				fakeCloud,
296				timeService,
297				fs,
298				logger,
299				metadata,
300			)
301		})
302
303		It("attaches disk to vm in the cloud", func() {
304			err := vm.AttachDisk(disk)
305			Expect(err).ToNot(HaveOccurred())
306			Expect(fakeCloud.AttachDiskInput).To(Equal(fakebicloud.AttachDiskInput{
307				VMCID:   "fake-vm-cid",
308				DiskCID: "fake-disk-cid",
309			}))
310		})
311
312		It("does not call agent AddPersistentDisk when diskHints are nil", func() {
313			fakeCloud.AttachDiskHints = nil
314
315			err := vm.AttachDisk(disk)
316
317			Expect(err).ToNot(HaveOccurred())
318			Expect(fakeAgentClient.AddPersistentDiskCallCount()).To(Equal(0))
319		})
320
321		It("adds the persistent disk to the agent", func() {
322			fakeCloud.AttachDiskHints = "/dev/sdb"
323
324			err := vm.AttachDisk(disk)
325
326			Expect(err).ToNot(HaveOccurred())
327			Expect(fakeAgentClient.AddPersistentDiskCallCount()).To(Equal(1))
328			diskCid, diskHints := fakeAgentClient.AddPersistentDiskArgsForCall(0)
329			Expect(diskCid).To(Equal("fake-disk-cid"))
330			Expect(diskHints).To(Equal("/dev/sdb"))
331		})
332
333		It("sends mount disk to the agent after pinging the agent", func() {
334			err := vm.AttachDisk(disk)
335			Expect(err).ToNot(HaveOccurred())
336			Expect(fakeAgentClient.PingCallCount()).To(Equal(1))
337			Expect(fakeAgentClient.MountDiskArgsForCall(0)).To(Equal("fake-disk-cid"))
338		})
339
340		Context("when metadata is set", func() {
341			It("sets the metadata to the disk", func() {
342				expectedDiskMetadata := bicloud.DiskMetadata{
343					"director":       "bosh-init",
344					"deployment":     "some-deployment",
345					"instance_group": "some-instance-group",
346					"instance_index": "0",
347					"attached_at":    "2016-11-10T23:00:00Z",
348					"custom_tag1":    "custom_value1",
349					"custom_tag2":    "custom_value2",
350				}
351
352				err := vm.AttachDisk(disk)
353				Expect(err).ToNot(HaveOccurred())
354
355				Expect(fakeCloud.SetDiskMetadataCid).To(Equal("fake-disk-cid"))
356				Expect(fakeCloud.SetDiskMetadataMetadata).To(Equal(expectedDiskMetadata))
357			})
358
359			Context("when setting metadata is not supported by the CPI", func() {
360				BeforeEach(func() {
361					cmdError := bicloud.CmdError{
362						Type: bicloud.NotImplementedError,
363					}
364					fakeCloud.SetDiskMetadataError = bicloud.NewCPIError("set_disk_metadata", cmdError)
365				})
366
367				It("logs a warning", func() {
368					err := vm.AttachDisk(disk)
369					Expect(err).ToNot(HaveOccurred())
370
371					Expect(logger.WarnCallCount()).To(Equal(1))
372					tag, msg, _ := logger.WarnArgsForCall(0)
373					Expect(tag).To(Equal("vm"))
374					Expect(msg).To(Equal("'SetDiskMetadata' not implemented by CPI"))
375				})
376			})
377
378			Context("when setting metadata fails", func() {
379				BeforeEach(func() {
380					cmdError := bicloud.CmdError{
381						Message: "some error",
382					}
383					fakeCloud.SetDiskMetadataError = bicloud.NewCPIError("set_disk_metadata", cmdError)
384				})
385
386				It("returns an error", func() {
387					err := vm.AttachDisk(disk)
388					Expect(err).To(HaveOccurred())
389					Expect(err.Error()).To(ContainSubstring("some error"))
390					Expect(err.Error()).To(ContainSubstring("Setting disk metadata"))
391				})
392			})
393		})
394
395		Context("when AddPersistentDisk returns 'unknown message add_persistent_disk'", func() {
396			BeforeEach(func() {
397				fakeCloud.AttachDiskHints = "/dev/sdb"
398				fakeAgentClient.AddPersistentDiskReturns(errors.New("Agent responded with error: unknown message add_persistent_disk"))
399			})
400
401			It("recovers from unimplemented AddPersistentDisk in the agent", func() {
402				err := vm.AttachDisk(disk)
403				Expect(err).ToNot(HaveOccurred())
404			})
405		})
406
407		Context("when AddPersistentDisk returns anything other than 'unknown message add_persistent_disk'", func() {
408			BeforeEach(func() {
409				fakeCloud.AttachDiskHints = "/dev/sdb"
410				fakeAgentClient.AddPersistentDiskReturns(errors.New("fake-agent-error"))
411			})
412
413			It("fails with the AddPersistentDisk error", func() {
414				err := vm.AttachDisk(disk)
415				Expect(err).To(HaveOccurred())
416				Expect(err.Error()).To(ContainSubstring("fake-agent-error"))
417			})
418		})
419
420		Context("when attaching disk to cloud fails", func() {
421			BeforeEach(func() {
422				fakeCloud.AttachDiskErr = errors.New("fake-attach-error")
423			})
424
425			It("returns an error", func() {
426				err := vm.AttachDisk(disk)
427				Expect(err).To(HaveOccurred())
428				Expect(err.Error()).To(ContainSubstring("fake-attach-error"))
429
430				Expect(fakeAgentClient.PingCallCount()).To(Equal(0))
431			})
432		})
433
434		Context("when mounting disk fails", func() {
435			BeforeEach(func() {
436				fakeAgentClient.MountDiskReturns(errors.New("fake-mount-error"))
437			})
438
439			It("returns an error", func() {
440				err := vm.AttachDisk(disk)
441				Expect(err).To(HaveOccurred())
442				Expect(err.Error()).To(ContainSubstring("fake-mount-error"))
443			})
444		})
445
446		It("returns an error if pinging fails", func() {
447			fakeAgentClient.PingReturns("", errors.New("fake-error"))
448
449			err := vm.AttachDisk(disk)
450			Expect(err).To(HaveOccurred())
451			Expect(err.Error()).To(ContainSubstring("fake-error"))
452
453			Expect(fakeAgentClient.MountDiskCallCount()).To(Equal(0))
454		})
455	})
456
457	Describe("DetachDisk", func() {
458		var disk *fakebidisk.FakeDisk
459
460		BeforeEach(func() {
461			disk = fakebidisk.NewFakeDisk("fake-disk-cid")
462		})
463
464		It("removes the disk from the vm", func() {
465			err := vm.DetachDisk(disk)
466			Expect(err).ToNot(HaveOccurred())
467			Expect(fakeAgentClient.RemovePersistentDiskCallCount()).To(Equal(1))
468			Expect(fakeAgentClient.RemovePersistentDiskArgsForCall(0)).To(Equal(disk.CID()))
469		})
470
471		It("detaches disk from vm in the cloud", func() {
472			err := vm.DetachDisk(disk)
473			Expect(err).ToNot(HaveOccurred())
474			Expect(fakeCloud.DetachDiskInput).To(Equal(fakebicloud.DetachDiskInput{
475				VMCID:   "fake-vm-cid",
476				DiskCID: "fake-disk-cid",
477			}))
478			Expect(fakeAgentClient.PingCallCount()).To(Equal(1))
479		})
480
481		Context("when RemovePersistentDisk returns 'unknown message remove_persistent_disk'", func() {
482			BeforeEach(func() {
483				fakeAgentClient.RemovePersistentDiskReturns(errors.New("Agent responded with error: unknown message remove_persistent_disk"))
484			})
485
486			It("recovers from unimplemented RemovePersistentDisk in the agent", func() {
487				err := vm.DetachDisk(disk)
488				Expect(err).ToNot(HaveOccurred())
489			})
490		})
491
492		Context("when RemovePersistentDisk returns anything other than 'unknown message remove_persistent_disk'", func() {
493			BeforeEach(func() {
494				fakeAgentClient.RemovePersistentDiskReturns(errors.New("fake-agent-error"))
495			})
496
497			It("fails with the RemovePersistentDisk error", func() {
498				err := vm.DetachDisk(disk)
499				Expect(err).To(HaveOccurred())
500				Expect(err.Error()).To(ContainSubstring("fake-agent-error"))
501			})
502		})
503
504		Context("when detaching disk to cloud fails", func() {
505			BeforeEach(func() {
506				fakeCloud.DetachDiskErr = errors.New("fake-detach-error")
507			})
508
509			It("returns an error", func() {
510				err := vm.DetachDisk(disk)
511				Expect(err).To(HaveOccurred())
512				Expect(err.Error()).To(ContainSubstring("fake-detach-error"))
513
514				Expect(fakeAgentClient.PingCallCount()).To(Equal(0))
515			})
516		})
517
518		It("returns an error if pinging fails", func() {
519			fakeAgentClient.PingReturns("", errors.New("fake-error"))
520
521			err := vm.DetachDisk(disk)
522			Expect(err).To(HaveOccurred())
523			Expect(err.Error()).To(ContainSubstring("fake-error"))
524		})
525	})
526
527	Describe("UnmountDisk", func() {
528		var disk *fakebidisk.FakeDisk
529
530		BeforeEach(func() {
531			disk = fakebidisk.NewFakeDisk("fake-disk-cid")
532		})
533
534		It("sends unmount disk to the agent", func() {
535			err := vm.UnmountDisk(disk)
536			Expect(err).ToNot(HaveOccurred())
537			Expect(fakeAgentClient.UnmountDiskArgsForCall(0)).To(Equal("fake-disk-cid"))
538		})
539
540		Context("when unmounting disk fails", func() {
541			BeforeEach(func() {
542				fakeAgentClient.UnmountDiskReturns(errors.New("fake-unmount-error"))
543			})
544
545			It("returns an error", func() {
546				err := vm.UnmountDisk(disk)
547				Expect(err).To(HaveOccurred())
548				Expect(err.Error()).To(ContainSubstring("fake-unmount-error"))
549			})
550		})
551	})
552
553	Describe("Disks", func() {
554		BeforeEach(func() {
555			fakeAgentClient.ListDiskReturns([]string{"fake-disk-cid-1", "fake-disk-cid-2"}, nil)
556		})
557
558		It("returns disks that are reported by the agent", func() {
559			disks, err := vm.Disks()
560			Expect(err).ToNot(HaveOccurred())
561			expectedFirstDisk := bidisk.NewDisk(biconfig.DiskRecord{CID: "fake-disk-cid-1"}, nil, nil)
562			expectedSecondDisk := bidisk.NewDisk(biconfig.DiskRecord{CID: "fake-disk-cid-2"}, nil, nil)
563			Expect(disks).To(Equal([]bidisk.Disk{expectedFirstDisk, expectedSecondDisk}))
564		})
565
566		Context("when listing disks fails", func() {
567			BeforeEach(func() {
568				fakeAgentClient.ListDiskReturns([]string{}, errors.New("fake-list-disk-error"))
569			})
570
571			It("returns an error", func() {
572				_, err := vm.Disks()
573				Expect(err).To(HaveOccurred())
574				Expect(err.Error()).To(ContainSubstring("fake-list-disk-error"))
575			})
576		})
577	})
578
579	Describe("Delete", func() {
580		It("deletes vm in the cloud", func() {
581			err := vm.Delete()
582			Expect(err).ToNot(HaveOccurred())
583			Expect(fakeCloud.DeleteVMInput).To(Equal(fakebicloud.DeleteVMInput{
584				VMCID: "fake-vm-cid",
585			}))
586		})
587
588		It("deletes VM in the vm repo", func() {
589			err := vm.Delete()
590			Expect(err).ToNot(HaveOccurred())
591			Expect(fakeVMRepo.ClearCurrentCalled).To(BeTrue())
592		})
593
594		It("clears current stemcell in the stemcell repo", func() {
595			err := vm.Delete()
596			Expect(err).ToNot(HaveOccurred())
597			Expect(fakeStemcellRepo.ClearCurrentCalled).To(BeTrue())
598		})
599
600		Context("when deleting vm in the cloud fails", func() {
601			BeforeEach(func() {
602				fakeCloud.DeleteVMErr = errors.New("fake-delete-vm-error")
603			})
604
605			It("returns an error", func() {
606				err := vm.Delete()
607				Expect(err).To(HaveOccurred())
608				Expect(err.Error()).To(ContainSubstring("fake-delete-vm-error"))
609			})
610		})
611
612		Context("when deleting vm in the cloud fails with VMNotFoundError", func() {
613			var deleteErr = bicloud.NewCPIError("delete_vm", bicloud.CmdError{
614				Type:    bicloud.VMNotFoundError,
615				Message: "fake-vm-not-found-message",
616			})
617
618			BeforeEach(func() {
619				fakeCloud.DeleteVMErr = deleteErr
620			})
621
622			It("deletes vm in the cloud", func() {
623				err := vm.Delete()
624				Expect(err).To(HaveOccurred())
625				Expect(err).To(Equal(deleteErr))
626				Expect(fakeCloud.DeleteVMInput).To(Equal(fakebicloud.DeleteVMInput{
627					VMCID: "fake-vm-cid",
628				}))
629			})
630
631			It("deletes VM in the vm repo", func() {
632				err := vm.Delete()
633				Expect(err).To(HaveOccurred())
634				Expect(err).To(Equal(deleteErr))
635				Expect(fakeVMRepo.ClearCurrentCalled).To(BeTrue())
636			})
637
638			It("clears current stemcell in the stemcell repo", func() {
639				err := vm.Delete()
640				Expect(err).To(HaveOccurred())
641				Expect(err).To(Equal(deleteErr))
642				Expect(fakeStemcellRepo.ClearCurrentCalled).To(BeTrue())
643			})
644		})
645	})
646
647	Describe("MigrateDisk", func() {
648		It("sends migrate_disk to the agent", func() {
649			err := vm.MigrateDisk()
650			Expect(err).ToNot(HaveOccurred())
651			Expect(fakeAgentClient.MigrateDiskCallCount()).To(Equal(1))
652		})
653
654		Context("when migrating disk fails", func() {
655			BeforeEach(func() {
656				fakeAgentClient.MigrateDiskReturns(errors.New("fake-migrate-error"))
657			})
658
659			It("returns an error", func() {
660				err := vm.MigrateDisk()
661				Expect(err).To(HaveOccurred())
662				Expect(err.Error()).To(ContainSubstring("fake-migrate-error"))
663			})
664		})
665	})
666
667	Describe("GetState", func() {
668		BeforeEach(func() {
669			fakeAgentClient.GetStateReturns(biagentclient.AgentState{JobState: "testing"}, nil)
670		})
671
672		It("sends get_state to the agent", func() {
673			agentState, err := vm.GetState()
674			Expect(err).ToNot(HaveOccurred())
675			Expect(agentState).To(Equal(biagentclient.AgentState{JobState: "testing"}))
676		})
677	})
678})
679
680type FakeClock struct {
681	Times      []time.Time
682	SleepCalls []time.Duration
683}
684
685func (c *FakeClock) Sleep(t time.Duration) {
686	c.SleepCalls = append(c.SleepCalls, t)
687}
688
689func (c *FakeClock) Now() time.Time {
690	t1 := c.Times[0]
691	c.Times = c.Times[1:]
692	return t1
693}
694