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  }