github.com/cloudfoundry-attic/garden-linux@v0.333.2-candidate/integration/lifecycle/drain_test.go (about) 1 package lifecycle_test 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "net" 8 "time" 9 10 . "github.com/onsi/ginkgo" 11 . "github.com/onsi/gomega" 12 "github.com/onsi/gomega/gbytes" 13 14 "github.com/cloudfoundry-incubator/garden" 15 ) 16 17 var _ = Describe("Through a restart", func() { 18 var container garden.Container 19 var gardenArgs []string 20 var privileged bool 21 22 BeforeEach(func() { 23 gardenArgs = []string{} 24 privileged = false 25 }) 26 27 JustBeforeEach(func() { 28 client = startGarden(gardenArgs...) 29 30 var err error 31 32 container, err = client.Create(garden.ContainerSpec{Privileged: privileged}) 33 Expect(err).ToNot(HaveOccurred()) 34 }) 35 36 AfterEach(func() { 37 if container != nil { 38 err := client.Destroy(container.Handle()) 39 Expect(err).ToNot(HaveOccurred()) 40 } 41 }) 42 43 It("retains the container list", func() { 44 restartGarden(gardenArgs...) 45 46 handles := getContainerHandles() 47 Expect(handles).To(ContainElement(container.Handle())) 48 }) 49 50 It("allows us to run processes in the same container before and after restart", func() { 51 By("running a process before restart") 52 runEcho(container) 53 54 restartGarden(gardenArgs...) 55 56 By("and then running a process after restart") 57 runEcho(container) 58 }) 59 60 Describe("a started process", func() { 61 It("continues to stream", func() { 62 process, err := container.Run(garden.ProcessSpec{ 63 User: "alice", 64 Path: "sh", 65 Args: []string{"-c", "while true; do echo hi; sleep 0.5; done"}, 66 }, garden.ProcessIO{}) 67 Expect(err).ToNot(HaveOccurred()) 68 69 restartGarden(gardenArgs...) 70 71 _, err = process.Wait() 72 Expect(err).To(HaveOccurred()) 73 74 stdout := gbytes.NewBuffer() 75 _, err = container.Attach(process.ID(), garden.ProcessIO{ 76 Stdout: stdout, 77 }) 78 Expect(err).ToNot(HaveOccurred()) 79 80 Eventually(stdout, 30.0).Should(gbytes.Say("hi\n")) 81 }) 82 83 It("can still accept stdin", func() { 84 r, w := io.Pipe() 85 86 stdout := gbytes.NewBuffer() 87 88 process, err := container.Run(garden.ProcessSpec{ 89 User: "alice", 90 Path: "sh", 91 Args: []string{"-c", "cat <&0"}, 92 }, garden.ProcessIO{ 93 Stdin: r, 94 Stdout: stdout, 95 }) 96 Expect(err).ToNot(HaveOccurred()) 97 98 _, err = fmt.Fprintf(w, "hello") 99 Expect(err).ToNot(HaveOccurred()) 100 101 Eventually(stdout).Should(gbytes.Say("hello")) 102 103 restartGarden(gardenArgs...) 104 105 _, err = process.Wait() 106 Expect(err).To(HaveOccurred()) 107 108 err = w.Close() 109 Expect(err).ToNot(HaveOccurred()) 110 111 process, err = container.Attach(process.ID(), garden.ProcessIO{ 112 Stdin: bytes.NewBufferString("world"), 113 Stdout: stdout, 114 }) 115 Expect(err).ToNot(HaveOccurred()) 116 117 Eventually(stdout, 10).Should(gbytes.Say("world")) 118 Expect(process.Wait()).To(Equal(0)) 119 }) 120 121 It("can still have its tty window resized", func() { 122 stdout := gbytes.NewBuffer() 123 124 process, err := container.Run(garden.ProcessSpec{ 125 User: "alice", 126 Path: "sh", 127 Args: []string{ 128 "-c", 129 130 // apparently, processes may receive SIGWINCH immediately upon 131 // spawning. the initial approach was to exit after receiving the 132 // signal, but sometimes it would exit immediately. 133 // 134 // so, instead, print whenever we receive SIGWINCH, and only exit 135 // when a line of text is entered. 136 ` 137 trap "stty -a" SIGWINCH 138 139 # continuously read so that the trap can keep firing 140 while true; do 141 echo waiting 142 if read; then 143 exit 0 144 fi 145 done 146 `, 147 }, 148 TTY: &garden.TTYSpec{ 149 WindowSize: &garden.WindowSize{ 150 Columns: 80, 151 Rows: 24, 152 }, 153 }, 154 }, garden.ProcessIO{ 155 Stdout: stdout, 156 }) 157 Expect(err).ToNot(HaveOccurred()) 158 159 Eventually(stdout).Should(gbytes.Say("waiting")) 160 161 restartGarden(gardenArgs...) 162 163 _, err = process.Wait() 164 Expect(err).To(HaveOccurred()) 165 166 inR, inW := io.Pipe() 167 168 process, err = container.Attach(process.ID(), garden.ProcessIO{ 169 Stdin: inR, 170 Stdout: stdout, 171 }) 172 Expect(err).ToNot(HaveOccurred()) 173 174 err = process.SetTTY(garden.TTYSpec{ 175 WindowSize: &garden.WindowSize{ 176 Columns: 123, 177 Rows: 456, 178 }, 179 }) 180 Expect(err).ToNot(HaveOccurred()) 181 182 Eventually(stdout).Should(gbytes.Say("rows 456; columns 123;")) 183 184 _, err = fmt.Fprintf(inW, "ok\n") 185 Expect(err).ToNot(HaveOccurred()) 186 187 Expect(process.Wait()).To(Equal(0)) 188 }) 189 190 It("does not have its job ID repeated", func() { 191 process1, err := container.Run(garden.ProcessSpec{ 192 User: "alice", 193 Path: "sh", 194 Args: []string{"-c", "while true; do echo hi; sleep 0.5; done"}, 195 }, garden.ProcessIO{}) 196 Expect(err).ToNot(HaveOccurred()) 197 198 restartGarden(gardenArgs...) 199 200 process2, err := container.Run(garden.ProcessSpec{ 201 User: "alice", 202 Path: "sh", 203 Args: []string{"-c", "while true; do echo hi; sleep 0.5; done"}, 204 }, garden.ProcessIO{}) 205 Expect(err).ToNot(HaveOccurred()) 206 207 Expect(process1.ID()).ToNot(Equal(process2.ID())) 208 }) 209 210 It("can still be signalled", func() { 211 process, err := container.Run(garden.ProcessSpec{ 212 User: "alice", 213 Path: "sh", 214 Args: []string{"-c", ` 215 trap 'echo termed; exit 42' SIGTERM 216 217 while true; do 218 echo waiting 219 sleep 1 220 done 221 `}, 222 }, garden.ProcessIO{}) 223 Expect(err).ToNot(HaveOccurred()) 224 225 restartGarden(gardenArgs...) 226 227 stdout := gbytes.NewBuffer() 228 attached, err := container.Attach(process.ID(), garden.ProcessIO{ 229 Stdout: io.MultiWriter(GinkgoWriter, stdout), 230 Stderr: GinkgoWriter, 231 }) 232 233 Eventually(stdout).Should(gbytes.Say("waiting")) 234 Expect(attached.Signal(garden.SignalTerminate)).To(Succeed()) 235 Eventually(stdout, "2s").Should(gbytes.Say("termed")) 236 Expect(attached.Wait()).To(Equal(42)) 237 }) 238 239 It("does not duplicate its output on reconnect", func() { 240 stdinR, stdinW := io.Pipe() 241 stdout := gbytes.NewBuffer() 242 243 process, err := container.Run(garden.ProcessSpec{ 244 User: "alice", 245 Path: "cat", 246 }, garden.ProcessIO{ 247 Stdin: stdinR, 248 Stdout: stdout, 249 }) 250 Expect(err).ToNot(HaveOccurred()) 251 252 stdinW.Write([]byte("first-line\n")) 253 Eventually(stdout).Should(gbytes.Say("first-line\n")) 254 255 restartGarden(gardenArgs...) 256 257 stdinR, stdinW = io.Pipe() 258 stdout = gbytes.NewBuffer() 259 260 _, err = container.Attach(process.ID(), garden.ProcessIO{ 261 Stdin: stdinR, 262 Stdout: stdout, 263 }) 264 Expect(err).ToNot(HaveOccurred()) 265 266 stdinW.Write([]byte("second-line\n")) 267 Eventually(stdout.Contents).Should(Equal([]byte("second-line\n"))) 268 }) 269 }) 270 271 Describe("a memory limit", func() { 272 It("is still enforced", func() { 273 err := container.LimitMemory(garden.MemoryLimits{4 * 1024 * 1024}) 274 Expect(err).ToNot(HaveOccurred()) 275 276 restartGarden(gardenArgs...) 277 278 process, err := container.Run(garden.ProcessSpec{ 279 User: "alice", 280 Path: "sh", 281 Args: []string{"-c", "echo $(yes | head -c 67108864); echo goodbye; exit 42"}, 282 }, garden.ProcessIO{}) 283 Expect(err).ToNot(HaveOccurred()) 284 285 // cgroups OOM killer seems to leave no trace of the process; 286 // there's no exit status indicator, so just assert that the one 287 // we tried to exit with after over-allocating is not seen 288 289 Expect(process.Wait()).ToNot(Equal(42), "process did not get OOM killed") 290 }) 291 }) 292 293 Describe("a container's active job", func() { 294 It("is still tracked", func() { 295 process, err := container.Run(garden.ProcessSpec{ 296 User: "alice", 297 Path: "sh", 298 Args: []string{"-c", "while true; do echo hi; sleep 0.5; done"}, 299 }, garden.ProcessIO{}) 300 Expect(err).ToNot(HaveOccurred()) 301 302 restartGarden(gardenArgs...) 303 304 info, err := container.Info() 305 Expect(err).ToNot(HaveOccurred()) 306 307 Expect(info.ProcessIDs).To(ContainElement(process.ID())) 308 }) 309 }) 310 311 Describe("a container's list of events", func() { 312 It("is still reported", func() { 313 err := container.LimitMemory(garden.MemoryLimits{4 * 1024 * 1024}) 314 Expect(err).ToNot(HaveOccurred()) 315 316 // trigger 'out of memory' event 317 process, err := container.Run(garden.ProcessSpec{ 318 User: "alice", 319 Path: "sh", 320 Args: []string{"-c", "echo $(yes | head -c 67108864); echo goodbye; exit 42"}, 321 }, garden.ProcessIO{}) 322 Expect(err).ToNot(HaveOccurred()) 323 324 Expect(process.Wait()).ToNot(Equal(42), "process did not get OOM killed") 325 326 Eventually(func() []string { 327 info, err := container.Info() 328 Expect(err).ToNot(HaveOccurred()) 329 330 return info.Events 331 }).Should(ContainElement("out of memory")) 332 333 restartGarden(gardenArgs...) 334 335 info, err := container.Info() 336 Expect(err).ToNot(HaveOccurred()) 337 338 Expect(info.Events).To(ContainElement("out of memory")) 339 }) 340 }) 341 342 Describe("a container's properties", func() { 343 It("are retained", func() { 344 containerWithProperties, err := client.Create(garden.ContainerSpec{ 345 Properties: garden.Properties{ 346 "foo": "bar", 347 }, 348 }) 349 Expect(err).ToNot(HaveOccurred()) 350 351 info, err := containerWithProperties.Info() 352 Expect(err).ToNot(HaveOccurred()) 353 354 Expect(info.Properties["foo"]).To(Equal("bar")) 355 356 restartGarden(gardenArgs...) 357 358 info, err = containerWithProperties.Info() 359 Expect(err).ToNot(HaveOccurred()) 360 361 Expect(info.Properties["foo"]).To(Equal("bar")) 362 }) 363 }) 364 365 Describe("a container's state", func() { 366 It("is still reported", func() { 367 info, err := container.Info() 368 Expect(err).ToNot(HaveOccurred()) 369 370 Expect(info.State).To(Equal("active")) 371 372 restartGarden(gardenArgs...) 373 374 info, err = container.Info() 375 Expect(err).ToNot(HaveOccurred()) 376 377 Expect(info.State).To(Equal("active")) 378 379 err = container.Stop(false) 380 Expect(err).ToNot(HaveOccurred()) 381 382 restartGarden(gardenArgs...) 383 384 info, err = container.Info() 385 Expect(err).ToNot(HaveOccurred()) 386 387 Expect(info.State).To(Equal("stopped")) 388 }) 389 }) 390 391 Describe("a container's network", func() { 392 It("does not get reused", func() { 393 infoA, err := container.Info() 394 Expect(err).ToNot(HaveOccurred()) 395 396 restartGarden(gardenArgs...) 397 398 newContainer, err := client.Create(garden.ContainerSpec{}) 399 Expect(err).ToNot(HaveOccurred()) 400 401 infoB, err := newContainer.Info() 402 Expect(err).ToNot(HaveOccurred()) 403 404 Expect(infoA.HostIP).ToNot(Equal(infoB.HostIP)) 405 Expect(infoA.ContainerIP).ToNot(Equal(infoB.ContainerIP)) 406 }) 407 408 Context("when denying all networks initially", func() { 409 var ByAllowingTCPTo func(net.IP) 410 var ByDenyingTCPTo func(net.IP) 411 var externalIP net.IP 412 413 BeforeEach(func() { 414 ips, err := net.LookupIP("www.example.com") 415 Expect(err).ToNot(HaveOccurred()) 416 Expect(ips).ToNot(BeEmpty()) 417 externalIP = ips[0] 418 419 gardenArgs = []string{ 420 "-denyNetworks", "0.0.0.0/0", // deny everything 421 "-allowNetworks", "", // allow nothing 422 } 423 424 ByAllowingTCPTo = func(ip net.IP) { 425 By("Allowing TCP to"+ip.String(), func() { 426 process, _ := runInContainer( 427 container, 428 fmt.Sprintf("(echo 'GET / HTTP/1.1'; echo 'Host: example.com'; echo) | nc -w5 %s 80", ip), 429 ) 430 status, err := process.Wait() 431 Expect(err).ToNot(HaveOccurred()) 432 Expect(status).To(Equal(0)) 433 }) 434 } 435 436 ByDenyingTCPTo = func(ip net.IP) { 437 By("Denying TCP to"+ip.String(), func() { 438 process, _ := runInContainer( 439 container, 440 fmt.Sprintf("(echo 'GET / HTTP/1.1'; echo 'Host: example.com'; echo) | nc -w5 %s 80", ip), 441 ) 442 status, err := process.Wait() 443 Expect(err).ToNot(HaveOccurred()) 444 Expect(status).ToNot(Equal(0)) 445 }) 446 } 447 }) 448 449 It("preserves NetOut rules", func() { 450 // Initially prevented from accessing (sanity check) 451 ByDenyingTCPTo(externalIP) 452 453 // Allow access 454 Expect(container.NetOut(garden.NetOutRule{ 455 Protocol: garden.ProtocolTCP, 456 Networks: []garden.IPRange{ 457 garden.IPRangeFromIP(externalIP), 458 }, 459 })).To(Succeed()) 460 461 // Check it worked (sanity check) 462 ByAllowingTCPTo(externalIP) 463 464 restartGarden(gardenArgs...) 465 ByAllowingTCPTo(externalIP) 466 }) 467 }) 468 469 }) 470 471 Describe("a container's mapped port", func() { 472 It("does not get reused", func() { 473 netInAHost, netInAContainer, err := container.NetIn(0, 0) 474 Expect(err).ToNot(HaveOccurred()) 475 476 restartGarden(gardenArgs...) 477 478 containerB, err := client.Create(garden.ContainerSpec{}) 479 Expect(err).ToNot(HaveOccurred()) 480 481 netInBHost, netInBContainer, err := containerB.NetIn(0, 0) 482 Expect(err).ToNot(HaveOccurred()) 483 484 Expect(netInAHost).ToNot(Equal(netInBHost)) 485 Expect(netInAContainer).ToNot(Equal(netInBContainer)) 486 }) 487 }) 488 489 Describe("a container's grace time", func() { 490 BeforeEach(func() { 491 gardenArgs = []string{"--containerGraceTime", "5s"} 492 }) 493 494 It("is still enforced", func() { 495 restartGarden(gardenArgs...) 496 497 Expect(getContainerHandles()).To(ContainElement(container.Handle())) 498 Eventually(getContainerHandles, 20*time.Second).ShouldNot(ContainElement(container.Handle())) 499 container = nil 500 }) 501 }) 502 503 Describe("a privileged container", func() { 504 BeforeEach(func() { 505 privileged = true 506 }) 507 508 It("is still present", func() { 509 restartGarden(gardenArgs...) 510 Expect(getContainerHandles()).To(ContainElement(container.Handle())) 511 }) 512 }) 513 }) 514 515 func getContainerHandles() []string { 516 containers, err := client.Containers(nil) 517 Expect(err).ToNot(HaveOccurred()) 518 519 handles := make([]string, len(containers)) 520 for i, c := range containers { 521 handles[i] = c.Handle() 522 } 523 524 return handles 525 } 526 527 func runInContainer(container garden.Container, script string) (garden.Process, *gbytes.Buffer) { 528 out := gbytes.NewBuffer() 529 process, err := container.Run(garden.ProcessSpec{ 530 User: "alice", 531 Path: "sh", 532 Args: []string{"-c", script}, 533 }, garden.ProcessIO{ 534 Stdout: io.MultiWriter(out, GinkgoWriter), 535 Stderr: GinkgoWriter, 536 }) 537 Expect(err).ToNot(HaveOccurred()) 538 539 return process, out 540 } 541 542 func runEcho(container garden.Container) { 543 process, _ := runInContainer(container, "echo hello") 544 status, err := process.Wait() 545 Expect(err).ToNot(HaveOccurred()) 546 Expect(status).To(Equal(0)) 547 }