1package gclient_test
2
3import (
4	"bytes"
5	"errors"
6	"fmt"
7	"io/ioutil"
8	"net"
9	"strings"
10	"time"
11
12	. "github.com/onsi/ginkgo"
13	. "github.com/onsi/gomega"
14	"github.com/onsi/gomega/gbytes"
15
16	"code.cloudfoundry.org/garden"
17	. "code.cloudfoundry.org/garden/client"
18	"code.cloudfoundry.org/garden/client/connection/connectionfakes"
19	"code.cloudfoundry.org/garden/gardenfakes"
20)
21
22var _ = Describe("Container", func() {
23	var container garden.Container
24
25	var fakeConnection *connectionfakes.FakeConnection
26
27	BeforeEach(func() {
28		fakeConnection = new(connectionfakes.FakeConnection)
29	})
30
31	JustBeforeEach(func() {
32		var err error
33
34		client := New(fakeConnection)
35
36		fakeConnection.CreateReturns("some-handle", nil)
37
38		container, err = client.Create(garden.ContainerSpec{})
39		Ω(err).ShouldNot(HaveOccurred())
40	})
41
42	Describe("Handle", func() {
43		It("returns the container's handle", func() {
44			Ω(container.Handle()).Should(Equal("some-handle"))
45		})
46	})
47
48	Describe("Stop", func() {
49		It("sends a stop request", func() {
50			err := container.Stop(true)
51			Ω(err).ShouldNot(HaveOccurred())
52
53			handle, kill := fakeConnection.StopArgsForCall(0)
54			Ω(handle).Should(Equal("some-handle"))
55			Ω(kill).Should(BeTrue())
56		})
57
58		Context("when stopping fails", func() {
59			disaster := errors.New("oh no!")
60
61			BeforeEach(func() {
62				fakeConnection.StopReturns(disaster)
63			})
64
65			It("returns the error", func() {
66				err := container.Stop(true)
67				Ω(err).Should(Equal(disaster))
68			})
69		})
70	})
71
72	Describe("Info", func() {
73		It("sends an info request", func() {
74			infoToReturn := garden.ContainerInfo{
75				State: "chillin",
76			}
77
78			fakeConnection.InfoReturns(infoToReturn, nil)
79
80			info, err := container.Info()
81			Ω(err).ShouldNot(HaveOccurred())
82
83			Ω(fakeConnection.InfoArgsForCall(0)).Should(Equal("some-handle"))
84
85			Ω(info).Should(Equal(infoToReturn))
86		})
87
88		Context("when getting info fails", func() {
89			disaster := errors.New("oh no!")
90
91			BeforeEach(func() {
92				fakeConnection.InfoReturns(garden.ContainerInfo{}, disaster)
93			})
94
95			It("returns the error", func() {
96				_, err := container.Info()
97				Ω(err).Should(Equal(disaster))
98			})
99		})
100	})
101
102	Describe("Properties", func() {
103		Context("when getting properties succeeds", func() {
104			BeforeEach(func() {
105				fakeConnection.PropertiesReturns(garden.Properties{"Foo": "bar"}, nil)
106			})
107
108			It("returns the properties map", func() {
109				result, err := container.Properties()
110				Ω(err).ShouldNot(HaveOccurred())
111				Ω(result).Should(Equal(garden.Properties{"Foo": "bar"}))
112			})
113		})
114
115		Context("when getting properties fails", func() {
116			disaster := errors.New("oh no!")
117
118			BeforeEach(func() {
119				fakeConnection.PropertiesReturns(nil, disaster)
120			})
121
122			It("returns the error", func() {
123				_, err := container.Properties()
124				Ω(err).Should(Equal(disaster))
125			})
126		})
127	})
128
129	Describe("Property", func() {
130
131		propertyName := "propertyName"
132		propertyValue := "propertyValue"
133
134		Context("when getting property succeeds", func() {
135			BeforeEach(func() {
136				fakeConnection.PropertyReturns(propertyValue, nil)
137			})
138
139			It("returns the value", func() {
140				result, err := container.Property(propertyName)
141				Ω(err).ShouldNot(HaveOccurred())
142				Ω(result).Should(Equal(propertyValue))
143			})
144		})
145
146		Context("when getting property fails", func() {
147			disaster := errors.New("oh no!")
148
149			BeforeEach(func() {
150				fakeConnection.PropertyReturns("", disaster)
151			})
152
153			It("returns the error", func() {
154				_, err := container.Property(propertyName)
155				Ω(err).Should(Equal(disaster))
156			})
157		})
158	})
159
160	Describe("StreamIn", func() {
161		It("sends a stream in request", func() {
162			fakeConnection.StreamInStub = func(handle string, spec garden.StreamInSpec) error {
163				Ω(spec.Path).Should(Equal("to"))
164				Ω(spec.User).Should(Equal("frank"))
165
166				content, err := ioutil.ReadAll(spec.TarStream)
167				Ω(err).ShouldNot(HaveOccurred())
168				Ω(string(content)).Should(Equal("stuff"))
169
170				return nil
171			}
172
173			err := container.StreamIn(garden.StreamInSpec{
174				User:      "frank",
175				Path:      "to",
176				TarStream: bytes.NewBufferString("stuff"),
177			})
178			Ω(err).ShouldNot(HaveOccurred())
179		})
180
181		Context("when streaming in fails", func() {
182			disaster := errors.New("oh no!")
183
184			BeforeEach(func() {
185				fakeConnection.StreamInReturns(
186					disaster)
187			})
188
189			It("returns the error", func() {
190				err := container.StreamIn(garden.StreamInSpec{
191					Path: "to",
192				})
193				Ω(err).Should(Equal(disaster))
194			})
195		})
196	})
197
198	Describe("StreamOut", func() {
199		It("sends a stream out request", func() {
200			fakeConnection.StreamOutReturns(ioutil.NopCloser(strings.NewReader("kewl")), nil)
201
202			reader, err := container.StreamOut(garden.StreamOutSpec{
203				User: "deandra",
204				Path: "from",
205			})
206			bytes, err := ioutil.ReadAll(reader)
207			Ω(err).ShouldNot(HaveOccurred())
208			Ω(string(bytes)).Should(Equal("kewl"))
209
210			handle, spec := fakeConnection.StreamOutArgsForCall(0)
211			Ω(handle).Should(Equal("some-handle"))
212			Ω(spec.Path).Should(Equal("from"))
213			Ω(spec.User).Should(Equal("deandra"))
214		})
215
216		Context("when streaming out fails", func() {
217			disaster := errors.New("oh no!")
218
219			BeforeEach(func() {
220				fakeConnection.StreamOutReturns(nil, disaster)
221			})
222
223			It("returns the error", func() {
224				_, err := container.StreamOut(garden.StreamOutSpec{
225					Path: "from",
226				})
227				Ω(err).Should(Equal(disaster))
228			})
229		})
230	})
231
232	Describe("CurrentBandwidthLimits", func() {
233		It("sends an empty limit request and returns its response", func() {
234			limitsToReturn := garden.BandwidthLimits{
235				RateInBytesPerSecond:      1,
236				BurstRateInBytesPerSecond: 2,
237			}
238
239			fakeConnection.CurrentBandwidthLimitsReturns(limitsToReturn, nil)
240
241			limits, err := container.CurrentBandwidthLimits()
242			Ω(err).ShouldNot(HaveOccurred())
243
244			Ω(limits).Should(Equal(limitsToReturn))
245		})
246
247		Context("when the request fails", func() {
248			disaster := errors.New("oh no!")
249
250			BeforeEach(func() {
251				fakeConnection.CurrentBandwidthLimitsReturns(garden.BandwidthLimits{}, disaster)
252			})
253
254			It("returns the error", func() {
255				_, err := container.CurrentBandwidthLimits()
256				Ω(err).Should(Equal(disaster))
257			})
258		})
259	})
260
261	Describe("CurrentCPULimits", func() {
262		It("sends an empty limit request and returns its response", func() {
263			limitsToReturn := garden.CPULimits{
264				LimitInShares: 1,
265			}
266
267			fakeConnection.CurrentCPULimitsReturns(limitsToReturn, nil)
268
269			limits, err := container.CurrentCPULimits()
270			Ω(err).ShouldNot(HaveOccurred())
271
272			Ω(limits).Should(Equal(limitsToReturn))
273		})
274
275		Context("when the request fails", func() {
276			disaster := errors.New("oh no!")
277
278			BeforeEach(func() {
279				fakeConnection.CurrentCPULimitsReturns(garden.CPULimits{}, disaster)
280			})
281
282			It("returns the error", func() {
283				_, err := container.CurrentCPULimits()
284				Ω(err).Should(Equal(disaster))
285			})
286		})
287	})
288
289	Describe("CurrentDiskLimits", func() {
290		It("sends an empty limit request and returns its response", func() {
291			limitsToReturn := garden.DiskLimits{
292				InodeSoft: 7,
293				InodeHard: 8,
294				ByteSoft:  11,
295				ByteHard:  12,
296				Scope:     garden.DiskLimitScopeExclusive,
297			}
298
299			fakeConnection.CurrentDiskLimitsReturns(limitsToReturn, nil)
300
301			limits, err := container.CurrentDiskLimits()
302			Ω(err).ShouldNot(HaveOccurred())
303
304			Ω(limits).Should(Equal(limitsToReturn))
305		})
306
307		Context("when the request fails", func() {
308			disaster := errors.New("oh no!")
309
310			BeforeEach(func() {
311				fakeConnection.CurrentDiskLimitsReturns(garden.DiskLimits{}, disaster)
312			})
313
314			It("returns the error", func() {
315				_, err := container.CurrentDiskLimits()
316				Ω(err).Should(Equal(disaster))
317			})
318		})
319	})
320
321	Describe("CurrentMemoryLimits", func() {
322		It("gets the current limits", func() {
323			limitsToReturn := garden.MemoryLimits{
324				LimitInBytes: 1,
325			}
326
327			fakeConnection.CurrentMemoryLimitsReturns(limitsToReturn, nil)
328
329			limits, err := container.CurrentMemoryLimits()
330			Ω(err).ShouldNot(HaveOccurred())
331
332			Ω(limits).Should(Equal(limitsToReturn))
333		})
334
335		Context("when the request fails", func() {
336			disaster := errors.New("oh no!")
337
338			BeforeEach(func() {
339				fakeConnection.CurrentMemoryLimitsReturns(garden.MemoryLimits{}, disaster)
340			})
341
342			It("returns the error", func() {
343				_, err := container.CurrentMemoryLimits()
344				Ω(err).Should(Equal(disaster))
345			})
346		})
347	})
348
349	Describe("Run", func() {
350		It("sends a run request and returns the process id and a stream", func() {
351			fakeConnection.RunStub = func(handle string, spec garden.ProcessSpec, io garden.ProcessIO) (garden.Process, error) {
352				process := new(gardenfakes.FakeProcess)
353
354				process.IDReturns("process-handle")
355				process.WaitReturns(123, nil)
356
357				go func() {
358					defer GinkgoRecover()
359
360					_, err := fmt.Fprintf(io.Stdout, "stdout data")
361					Ω(err).ShouldNot(HaveOccurred())
362
363					_, err = fmt.Fprintf(io.Stderr, "stderr data")
364					Ω(err).ShouldNot(HaveOccurred())
365				}()
366
367				return process, nil
368			}
369
370			spec := garden.ProcessSpec{
371				Path: "some-script",
372			}
373
374			stdout := gbytes.NewBuffer()
375			stderr := gbytes.NewBuffer()
376
377			processIO := garden.ProcessIO{
378				Stdout: stdout,
379				Stderr: stderr,
380			}
381
382			process, err := container.Run(spec, processIO)
383			Ω(err).ShouldNot(HaveOccurred())
384
385			ranHandle, ranSpec, ranIO := fakeConnection.RunArgsForCall(0)
386			Ω(ranHandle).Should(Equal("some-handle"))
387			Ω(ranSpec).Should(Equal(spec))
388			Ω(ranIO).Should(Equal(processIO))
389
390			Ω(process.ID()).Should(Equal("process-handle"))
391
392			status, err := process.Wait()
393			Ω(err).ShouldNot(HaveOccurred())
394			Ω(status).Should(Equal(123))
395
396			Eventually(stdout).Should(gbytes.Say("stdout data"))
397			Eventually(stderr).Should(gbytes.Say("stderr data"))
398		})
399	})
400
401	Describe("Attach", func() {
402		It("sends an attach request and returns a stream", func() {
403			fakeConnection.AttachStub = func(handle string, processID string, io garden.ProcessIO) (garden.Process, error) {
404				process := new(gardenfakes.FakeProcess)
405
406				process.IDReturns("process-handle")
407				process.WaitReturns(123, nil)
408
409				go func() {
410					defer GinkgoRecover()
411
412					_, err := fmt.Fprintf(io.Stdout, "stdout data")
413					Ω(err).ShouldNot(HaveOccurred())
414
415					_, err = fmt.Fprintf(io.Stderr, "stderr data")
416					Ω(err).ShouldNot(HaveOccurred())
417				}()
418
419				return process, nil
420			}
421
422			stdout := gbytes.NewBuffer()
423			stderr := gbytes.NewBuffer()
424
425			processIO := garden.ProcessIO{
426				Stdout: stdout,
427				Stderr: stderr,
428			}
429
430			process, err := container.Attach("process-handle", processIO)
431			Ω(err).ShouldNot(HaveOccurred())
432
433			attachedHandle, attachedID, attachedIO := fakeConnection.AttachArgsForCall(0)
434			Ω(attachedHandle).Should(Equal("some-handle"))
435			Ω(attachedID).Should(Equal("process-handle"))
436			Ω(attachedIO).Should(Equal(processIO))
437
438			Ω(process.ID()).Should(Equal("process-handle"))
439
440			status, err := process.Wait()
441			Ω(err).ShouldNot(HaveOccurred())
442			Ω(status).Should(Equal(123))
443
444			Eventually(stdout).Should(gbytes.Say("stdout data"))
445			Eventually(stderr).Should(gbytes.Say("stderr data"))
446		})
447
448		Context("when the process requested is not found", func() {
449			It("returns ProcessNotFoundError", func() {
450				fakeConnection.AttachReturns(nil, garden.ProcessNotFoundError{
451					ProcessID: "not-existing-process",
452				})
453
454				_, err := container.Attach("notExistingProcess", garden.ProcessIO{})
455				Ω(err).Should(Equal(garden.ProcessNotFoundError{
456					ProcessID: "not-existing-process",
457				}))
458			})
459		})
460	})
461
462	Describe("NetIn", func() {
463		It("sends a net in request", func() {
464			fakeConnection.NetInReturns(111, 222, nil)
465
466			hostPort, containerPort, err := container.NetIn(123, 456)
467			Ω(err).ShouldNot(HaveOccurred())
468			Ω(hostPort).Should(Equal(uint32(111)))
469			Ω(containerPort).Should(Equal(uint32(222)))
470
471			h, hp, cp := fakeConnection.NetInArgsForCall(0)
472			Ω(h).Should(Equal("some-handle"))
473			Ω(hp).Should(Equal(uint32(123)))
474			Ω(cp).Should(Equal(uint32(456)))
475		})
476
477		Context("when the request fails", func() {
478			disaster := errors.New("oh no!")
479
480			BeforeEach(func() {
481				fakeConnection.NetInReturns(0, 0, disaster)
482			})
483
484			It("returns the error", func() {
485				_, _, err := container.NetIn(123, 456)
486				Ω(err).Should(Equal(disaster))
487			})
488		})
489	})
490
491	Describe("NetOut", func() {
492		It("sends NetOut requests over the connection", func() {
493			Ω(container.NetOut(garden.NetOutRule{
494				Networks: []garden.IPRange{garden.IPRangeFromIP(net.ParseIP("1.2.3.4"))},
495				Ports: []garden.PortRange{
496					{Start: 12, End: 24},
497				},
498				Log: true,
499			})).Should(Succeed())
500
501			h, rule := fakeConnection.NetOutArgsForCall(0)
502			Ω(h).Should(Equal("some-handle"))
503
504			Ω(rule.Networks).Should(HaveLen(1))
505			Ω(rule.Networks[0]).Should(Equal(garden.IPRange{Start: net.ParseIP("1.2.3.4"), End: net.ParseIP("1.2.3.4")}))
506
507			Ω(rule.Ports).Should(HaveLen(1))
508			Ω(rule.Ports[0]).Should(Equal(garden.PortRange{Start: 12, End: 24}))
509
510			Ω(rule.Log).Should(Equal(true))
511		})
512	})
513
514	Describe(("GraceTime"), func() {
515		It("send the set grace time request", func() {
516			graceTime := time.Second * 5
517
518			Ω(container.SetGraceTime(graceTime)).Should(Succeed())
519
520			Ω(fakeConnection.SetGraceTimeCallCount()).Should(Equal(1))
521			handle, actualGraceTime := fakeConnection.SetGraceTimeArgsForCall(0)
522			Ω(handle).Should(Equal("some-handle"))
523			Ω(actualGraceTime).Should(Equal(graceTime))
524		})
525
526		Context("when the request fails", func() {
527			disaster := errors.New("banana")
528
529			BeforeEach(func() {
530				fakeConnection.SetGraceTimeReturns(disaster)
531			})
532
533			It("returns the error", func() {
534				err := container.SetGraceTime(time.Second * 5)
535				Ω(err).Should(Equal(disaster))
536			})
537		})
538	})
539
540	Context("when the request fails", func() {
541		disaster := errors.New("oh no!")
542
543		BeforeEach(func() {
544			fakeConnection.NetOutReturns(disaster)
545		})
546
547		It("returns the error", func() {
548			err := container.NetOut(garden.NetOutRule{})
549			Ω(err).Should(Equal(disaster))
550		})
551	})
552})
553