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