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