github.com/geofffranks/garden-linux@v0.0.0-20160715111146-26c893169cfa/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 "code.cloudfoundry.org/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 containerWithMemoryLimit, err := client.Create(garden.ContainerSpec{ 274 Limits: garden.Limits{Memory: garden.MemoryLimits{4 * 1024 * 1024}}, 275 }) 276 Expect(err).ToNot(HaveOccurred()) 277 278 restartGarden(gardenArgs...) 279 280 process, err := containerWithMemoryLimit.Run(garden.ProcessSpec{ 281 User: "alice", 282 Path: "sh", 283 Args: []string{"-c", "echo $(yes | head -c 67108864); echo goodbye; exit 42"}, 284 }, garden.ProcessIO{}) 285 Expect(err).ToNot(HaveOccurred()) 286 287 // cgroups OOM killer seems to leave no trace of the process; 288 // there's no exit status indicator, so just assert that the one 289 // we tried to exit with after over-allocating is not seen 290 291 Expect(process.Wait()).ToNot(Equal(42), "process did not get OOM killed") 292 }) 293 }) 294 295 Describe("a container's active job", func() { 296 It("is still tracked", func() { 297 process, err := container.Run(garden.ProcessSpec{ 298 User: "alice", 299 Path: "sh", 300 Args: []string{"-c", "while true; do echo hi; sleep 0.5; done"}, 301 }, garden.ProcessIO{}) 302 Expect(err).ToNot(HaveOccurred()) 303 304 restartGarden(gardenArgs...) 305 306 info, err := container.Info() 307 Expect(err).ToNot(HaveOccurred()) 308 309 Expect(info.ProcessIDs).To(ContainElement(process.ID())) 310 }) 311 }) 312 313 Describe("a container's list of events", func() { 314 It("is still reported", func() { 315 containerWithMemoryLimit, err := client.Create(garden.ContainerSpec{ 316 Limits: garden.Limits{Memory: garden.MemoryLimits{4 * 1024 * 1024}}, 317 }) 318 Expect(err).ToNot(HaveOccurred()) 319 320 // trigger 'out of memory' event 321 process, err := containerWithMemoryLimit.Run(garden.ProcessSpec{ 322 User: "alice", 323 Path: "sh", 324 Args: []string{"-c", "echo $(yes | head -c 67108864); echo goodbye; exit 42"}, 325 }, garden.ProcessIO{}) 326 Expect(err).ToNot(HaveOccurred()) 327 328 Expect(process.Wait()).ToNot(Equal(42), "process did not get OOM killed") 329 330 Eventually(func() []string { 331 info, err := containerWithMemoryLimit.Info() 332 Expect(err).ToNot(HaveOccurred()) 333 334 return info.Events 335 }).Should(ContainElement("out of memory")) 336 337 restartGarden(gardenArgs...) 338 339 info, err := containerWithMemoryLimit.Info() 340 Expect(err).ToNot(HaveOccurred()) 341 342 Expect(info.Events).To(ContainElement("out of memory")) 343 }) 344 }) 345 346 Describe("a container's properties", func() { 347 It("are retained", func() { 348 containerWithProperties, err := client.Create(garden.ContainerSpec{ 349 Properties: garden.Properties{ 350 "foo": "bar", 351 }, 352 }) 353 Expect(err).ToNot(HaveOccurred()) 354 355 info, err := containerWithProperties.Info() 356 Expect(err).ToNot(HaveOccurred()) 357 358 Expect(info.Properties["foo"]).To(Equal("bar")) 359 360 restartGarden(gardenArgs...) 361 362 info, err = containerWithProperties.Info() 363 Expect(err).ToNot(HaveOccurred()) 364 365 Expect(info.Properties["foo"]).To(Equal("bar")) 366 }) 367 }) 368 369 Describe("a container's state", func() { 370 It("is still reported", func() { 371 info, err := container.Info() 372 Expect(err).ToNot(HaveOccurred()) 373 374 Expect(info.State).To(Equal("active")) 375 376 restartGarden(gardenArgs...) 377 378 info, err = container.Info() 379 Expect(err).ToNot(HaveOccurred()) 380 381 Expect(info.State).To(Equal("active")) 382 383 err = container.Stop(false) 384 Expect(err).ToNot(HaveOccurred()) 385 386 restartGarden(gardenArgs...) 387 388 info, err = container.Info() 389 Expect(err).ToNot(HaveOccurred()) 390 391 Expect(info.State).To(Equal("stopped")) 392 }) 393 }) 394 395 Describe("a container's network", func() { 396 It("does not get reused", func() { 397 infoA, err := container.Info() 398 Expect(err).ToNot(HaveOccurred()) 399 400 restartGarden(gardenArgs...) 401 402 newContainer, err := client.Create(garden.ContainerSpec{}) 403 Expect(err).ToNot(HaveOccurred()) 404 405 infoB, err := newContainer.Info() 406 Expect(err).ToNot(HaveOccurred()) 407 408 Expect(infoA.HostIP).ToNot(Equal(infoB.HostIP)) 409 Expect(infoA.ContainerIP).ToNot(Equal(infoB.ContainerIP)) 410 }) 411 412 Context("when denying all networks initially", func() { 413 var ByAllowingTCPTo func(net.IP) 414 var ByDenyingTCPTo func(net.IP) 415 var externalIP net.IP 416 417 BeforeEach(func() { 418 ips, err := net.LookupIP("www.example.com") 419 Expect(err).ToNot(HaveOccurred()) 420 Expect(ips).ToNot(BeEmpty()) 421 externalIP = ips[0] 422 423 gardenArgs = []string{ 424 "-denyNetworks", "0.0.0.0/0", // deny everything 425 "-allowNetworks", "", // allow nothing 426 } 427 428 ByAllowingTCPTo = func(ip net.IP) { 429 By("Allowing TCP to"+ip.String(), func() { 430 process, _ := runInContainer( 431 container, 432 fmt.Sprintf("(echo 'GET / HTTP/1.1'; echo 'Host: example.com'; echo) | nc -w5 %s 80", ip), 433 ) 434 status, err := process.Wait() 435 Expect(err).ToNot(HaveOccurred()) 436 Expect(status).To(Equal(0)) 437 }) 438 } 439 440 ByDenyingTCPTo = func(ip net.IP) { 441 By("Denying TCP to"+ip.String(), func() { 442 process, _ := runInContainer( 443 container, 444 fmt.Sprintf("(echo 'GET / HTTP/1.1'; echo 'Host: example.com'; echo) | nc -w5 %s 80", ip), 445 ) 446 status, err := process.Wait() 447 Expect(err).ToNot(HaveOccurred()) 448 Expect(status).ToNot(Equal(0)) 449 }) 450 } 451 }) 452 453 It("preserves NetOut rules", func() { 454 // Initially prevented from accessing (sanity check) 455 ByDenyingTCPTo(externalIP) 456 457 // Allow access 458 Expect(container.NetOut(garden.NetOutRule{ 459 Protocol: garden.ProtocolTCP, 460 Networks: []garden.IPRange{ 461 garden.IPRangeFromIP(externalIP), 462 }, 463 })).To(Succeed()) 464 465 // Check it worked (sanity check) 466 ByAllowingTCPTo(externalIP) 467 468 restartGarden(gardenArgs...) 469 ByAllowingTCPTo(externalIP) 470 }) 471 }) 472 473 }) 474 475 Describe("a container's mapped port", func() { 476 It("does not get reused", func() { 477 netInAHost, netInAContainer, err := container.NetIn(0, 0) 478 Expect(err).ToNot(HaveOccurred()) 479 480 restartGarden(gardenArgs...) 481 482 containerB, err := client.Create(garden.ContainerSpec{}) 483 Expect(err).ToNot(HaveOccurred()) 484 485 netInBHost, netInBContainer, err := containerB.NetIn(0, 0) 486 Expect(err).ToNot(HaveOccurred()) 487 488 Expect(netInAHost).ToNot(Equal(netInBHost)) 489 Expect(netInAContainer).ToNot(Equal(netInBContainer)) 490 }) 491 }) 492 493 Describe("a container's grace time", func() { 494 BeforeEach(func() { 495 gardenArgs = []string{"--containerGraceTime", "5s"} 496 }) 497 498 It("is still enforced", func() { 499 restartGarden(gardenArgs...) 500 501 Expect(getContainerHandles()).To(ContainElement(container.Handle())) 502 Eventually(getContainerHandles, 20*time.Second).ShouldNot(ContainElement(container.Handle())) 503 container = nil 504 }) 505 }) 506 507 Describe("a privileged container", func() { 508 BeforeEach(func() { 509 privileged = true 510 }) 511 512 It("is still present", func() { 513 restartGarden(gardenArgs...) 514 Expect(getContainerHandles()).To(ContainElement(container.Handle())) 515 }) 516 }) 517 }) 518 519 func getContainerHandles() []string { 520 containers, err := client.Containers(nil) 521 Expect(err).ToNot(HaveOccurred()) 522 523 handles := make([]string, len(containers)) 524 for i, c := range containers { 525 handles[i] = c.Handle() 526 } 527 528 return handles 529 } 530 531 func runInContainer(container garden.Container, script string) (garden.Process, *gbytes.Buffer) { 532 out := gbytes.NewBuffer() 533 process, err := container.Run(garden.ProcessSpec{ 534 User: "alice", 535 Path: "sh", 536 Args: []string{"-c", script}, 537 }, garden.ProcessIO{ 538 Stdout: io.MultiWriter(out, GinkgoWriter), 539 Stderr: GinkgoWriter, 540 }) 541 Expect(err).ToNot(HaveOccurred()) 542 543 return process, out 544 } 545 546 func runEcho(container garden.Container) { 547 process, _ := runInContainer(container, "echo hello") 548 status, err := process.Wait() 549 Expect(err).ToNot(HaveOccurred()) 550 Expect(status).To(Equal(0)) 551 }