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  }