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  }