github.com/schwarzm/garden-linux@v0.0.0-20150507151835-33bca2147c47/integration/lifecycle/lifecycle_test.go (about)

     1  package lifecycle_test
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"compress/gzip"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/cloudfoundry-incubator/garden"
    18  	. "github.com/onsi/ginkgo"
    19  	. "github.com/onsi/gomega"
    20  	"github.com/onsi/gomega/gbytes"
    21  	"github.com/onsi/gomega/gexec"
    22  	archiver "github.com/pivotal-golang/archiver/extractor/test_helper"
    23  )
    24  
    25  var _ = Describe("Creating a container", func() {
    26  
    27  	Describe("Overlapping networks", func() {
    28  		Context("when the requested Network overlaps the dynamic allocation range", func() {
    29  			It("returns an error message naming the overlapped range", func() {
    30  				client = startGarden("--networkPool", "1.2.3.0/24")
    31  				_, err := client.Create(garden.ContainerSpec{Network: "1.2.3.0/25"})
    32  				Expect(err).To(MatchError("the requested subnet (1.2.3.0/25) overlaps the dynamic allocation range (1.2.3.0/24)"))
    33  			})
    34  		})
    35  
    36  		Context("when the requested Network overlaps another subnet", func() {
    37  			It("returns an error message naming the overlapped range", func() {
    38  				client = startGarden()
    39  				_, err := client.Create(garden.ContainerSpec{Privileged: false, Network: "10.2.0.0/29"})
    40  				Expect(err).ToNot(HaveOccurred())
    41  				_, err = client.Create(garden.ContainerSpec{Privileged: false, Network: "10.2.0.0/30"})
    42  				Expect(err).To(MatchError("the requested subnet (10.2.0.0/30) overlaps an existing subnet (10.2.0.0/29)"))
    43  			})
    44  		})
    45  	})
    46  
    47  	Describe("Docker image download", func() {
    48  		It("returns a helpful error message when image not found from default registry", func() {
    49  			client = startGarden()
    50  			_, err := client.Create(garden.ContainerSpec{RootFSPath: "docker:///cloudfoundry/doesnotexist"})
    51  			Expect(err.Error()).To(ContainSubstring("could not fetch image cloudfoundry/doesnotexist from registry https://index.docker.io/v1/"))
    52  		})
    53  
    54  		It("returns a helpful error message when registry does not exist", func() {
    55  			client = startGarden()
    56  
    57  			// Note: Using a valid url that is not a docker registry would make the test assertion below fail due to a bug in
    58  			//       docker https://github.com/docker/docker/blob/v1.3.3/registry/endpoint.go#L107-L157
    59  			//       eg. client.Create(garden.ContainerSpec{RootFSPath: "docker://example.com/cloudfoundry/doesnotexist"})
    60  			_, err := client.Create(garden.ContainerSpec{RootFSPath: "docker://does-not.exist/cloudfoundry/doesnotexist"})
    61  			Expect(err.Error()).To(ContainSubstring("could not fetch image cloudfoundry/doesnotexist from registry does-not.exist"))
    62  		})
    63  	})
    64  
    65  	Describe("concurrently destroying", func() {
    66  		allBridges := func() []byte {
    67  			stdout := gbytes.NewBuffer()
    68  			cmd, err := gexec.Start(exec.Command("ip", "a"), stdout, GinkgoWriter)
    69  			Expect(err).ToNot(HaveOccurred())
    70  			cmd.Wait()
    71  
    72  			return stdout.Contents()
    73  		}
    74  
    75  		It("does not leave residual bridges", func() {
    76  			client = startGarden()
    77  
    78  			bridgePrefix := fmt.Sprintf("w%db-", GinkgoParallelNode())
    79  			Expect(allBridges()).ToNot(ContainSubstring(bridgePrefix))
    80  
    81  			handles := make([]string, 0)
    82  			for i := 0; i < 5; i++ {
    83  				c, err := client.Create(garden.ContainerSpec{})
    84  				Expect(err).ToNot(HaveOccurred())
    85  
    86  				handles = append(handles, c.Handle())
    87  			}
    88  
    89  			retry := func(fn func() error) error {
    90  				var err error
    91  				for retry := 0; retry < 3; retry++ {
    92  					err = fn()
    93  					if err == nil {
    94  						break
    95  					}
    96  				}
    97  				return err
    98  			}
    99  
   100  			wg := new(sync.WaitGroup)
   101  			errors := make(chan error, 50)
   102  			for _, h := range handles {
   103  				wg.Add(1)
   104  				go func(h string) {
   105  					err := retry(func() error { return client.Destroy(h) })
   106  
   107  					if err != nil {
   108  						errors <- err
   109  					}
   110  
   111  					wg.Done()
   112  				}(h)
   113  			}
   114  
   115  			wg.Wait()
   116  
   117  			Expect(errors).ToNot(Receive())
   118  			Expect(client.Containers(garden.Properties{})).To(HaveLen(0)) // sanity check
   119  
   120  			Eventually(allBridges).ShouldNot(ContainSubstring(bridgePrefix))
   121  		})
   122  	})
   123  
   124  	Context("when the container fails to start", func() {
   125  		It("does not leave resources around", func() {
   126  			client = startGarden()
   127  			client.Create(garden.ContainerSpec{
   128  				BindMounts: []garden.BindMount{{
   129  					SrcPath: "fictional",
   130  					DstPath: "whereami",
   131  				}},
   132  			})
   133  
   134  			depotDir := filepath.Join(
   135  				os.TempDir(),
   136  				fmt.Sprintf("test-garden-%d", GinkgoParallelNode()),
   137  				"containers",
   138  			)
   139  			Expect(ioutil.ReadDir(depotDir)).To(HaveLen(0))
   140  		})
   141  	})
   142  
   143  	Context("when the container is created succesfully", func() {
   144  		var container garden.Container
   145  
   146  		var privilegedContainer bool
   147  		var rootfs string
   148  
   149  		JustBeforeEach(func() {
   150  			client = startGarden()
   151  
   152  			var err error
   153  			container, err = client.Create(garden.ContainerSpec{Privileged: privilegedContainer, RootFSPath: rootfs})
   154  			Expect(err).ToNot(HaveOccurred())
   155  		})
   156  
   157  		AfterEach(func() {
   158  			if container != nil {
   159  				Expect(client.Destroy(container.Handle())).To(Succeed())
   160  			}
   161  		})
   162  
   163  		BeforeEach(func() {
   164  			privilegedContainer = false
   165  			rootfs = ""
   166  		})
   167  
   168  		It("sources /etc/seed", func() {
   169  			process, err := container.Run(garden.ProcessSpec{
   170  				Path: "test",
   171  				Args: []string{"-e", "/tmp/ran-seed"},
   172  			}, garden.ProcessIO{})
   173  			Expect(err).ToNot(HaveOccurred())
   174  
   175  			Expect(process.Wait()).To(Equal(0))
   176  		})
   177  
   178  		It("provides /dev/shm as tmpfs in the container", func() {
   179  			process, err := container.Run(garden.ProcessSpec{
   180  				Path: "dd",
   181  				Args: []string{"if=/dev/urandom", "of=/dev/shm/some-data", "count=64", "bs=1k"},
   182  			}, garden.ProcessIO{})
   183  			Expect(err).ToNot(HaveOccurred())
   184  
   185  			Expect(process.Wait()).To(Equal(0))
   186  
   187  			outBuf := gbytes.NewBuffer()
   188  
   189  			process, err = container.Run(garden.ProcessSpec{
   190  				Path: "cat",
   191  				Args: []string{"/proc/mounts"},
   192  			}, garden.ProcessIO{
   193  				Stdout: outBuf,
   194  				Stderr: GinkgoWriter,
   195  			})
   196  			Expect(err).ToNot(HaveOccurred())
   197  
   198  			Expect(process.Wait()).To(Equal(0))
   199  
   200  			Expect(outBuf).To(gbytes.Say("tmpfs /dev/shm tmpfs"))
   201  			Expect(outBuf).To(gbytes.Say("rw,nodev,relatime"))
   202  		})
   203  
   204  		Context("and sending a List request", func() {
   205  			It("includes the created container", func() {
   206  				Expect(getContainerHandles()).To(ContainElement(container.Handle()))
   207  			})
   208  		})
   209  
   210  		Context("and sending an Info request", func() {
   211  			It("returns the container's info", func() {
   212  				info, err := container.Info()
   213  				Expect(err).ToNot(HaveOccurred())
   214  
   215  				Expect(info.State).To(Equal("active"))
   216  			})
   217  		})
   218  
   219  		It("gives the container a hostname based on its id", func() {
   220  			stdout := gbytes.NewBuffer()
   221  
   222  			_, err := container.Run(garden.ProcessSpec{
   223  				Path: "hostname",
   224  			}, garden.ProcessIO{
   225  				Stdout: stdout,
   226  			})
   227  			Expect(err).ToNot(HaveOccurred())
   228  
   229  			Eventually(stdout).Should(gbytes.Say(fmt.Sprintf("%s\n", container.Handle())))
   230  		})
   231  
   232  		Context("Using a docker image", func() {
   233  			Context("when there is a VOLUME associated with the docker image", func() {
   234  				BeforeEach(func() {
   235  					// dockerfile contains `VOLUME /foo`, see diego-dockerfiles/with-volume
   236  					rootfs = "docker:///cloudfoundry/with-volume"
   237  				})
   238  
   239  				It("creates the volume directory, if it does not already exist", func() {
   240  					stdout := gbytes.NewBuffer()
   241  					process, err := container.Run(garden.ProcessSpec{
   242  						Path: "ls",
   243  						Args: []string{"-l", "/"},
   244  					}, garden.ProcessIO{
   245  						Stdout: io.MultiWriter(GinkgoWriter, stdout),
   246  						Stderr: GinkgoWriter,
   247  					})
   248  
   249  					Expect(err).ToNot(HaveOccurred())
   250  
   251  					process.Wait()
   252  					Expect(stdout).To(gbytes.Say("foo"))
   253  				})
   254  			})
   255  
   256  			Context("when the docker image specifies $PATH", func() {
   257  				BeforeEach(func() {
   258  					// Dockerfile contains:
   259  					//   ENV PATH /usr/local/bin:/usr/bin:/bin:/from-dockerfile
   260  					//   ENV TEST test-from-dockerfile
   261  					//   ENV TEST second-test-from-dockerfile:$TEST
   262  					// see diego-dockerfiles/with-volume
   263  					rootfs = "docker:///cloudfoundry/with-volume"
   264  				})
   265  
   266  				It("$PATH is taken from the docker image", func() {
   267  					stdout := gbytes.NewBuffer()
   268  					process, err := container.Run(garden.ProcessSpec{
   269  						Path: "/bin/sh",
   270  						Args: []string{"-c", "echo $PATH"},
   271  					}, garden.ProcessIO{
   272  						Stdout: io.MultiWriter(GinkgoWriter, stdout),
   273  						Stderr: GinkgoWriter,
   274  					})
   275  
   276  					Expect(err).ToNot(HaveOccurred())
   277  
   278  					process.Wait()
   279  					Expect(stdout).To(gbytes.Say("/usr/local/bin:/usr/bin:/bin:/from-dockerfile"))
   280  				})
   281  
   282  				It("$TEST is taken from the docker image", func() {
   283  					stdout := gbytes.NewBuffer()
   284  					process, err := container.Run(garden.ProcessSpec{
   285  						Path: "/bin/sh",
   286  						Args: []string{"-c", "echo $TEST"},
   287  					}, garden.ProcessIO{
   288  						Stdout: io.MultiWriter(GinkgoWriter, stdout),
   289  						Stderr: GinkgoWriter,
   290  					})
   291  
   292  					Expect(err).ToNot(HaveOccurred())
   293  
   294  					process.Wait()
   295  					Expect(stdout).To(gbytes.Say("second-test-from-dockerfile:test-from-dockerfile"))
   296  				})
   297  			})
   298  		})
   299  
   300  		Context("and running a process", func() {
   301  			It("runs as the vcap user by default", func() {
   302  				stdout := gbytes.NewBuffer()
   303  
   304  				_, err := container.Run(garden.ProcessSpec{
   305  					Path: "whoami",
   306  				}, garden.ProcessIO{
   307  					Stdout: stdout,
   308  				})
   309  
   310  				Expect(err).ToNot(HaveOccurred())
   311  				Eventually(stdout).Should(gbytes.Say("vcap\n"))
   312  			})
   313  
   314  			Context("when root is requested", func() {
   315  				It("runs as root inside the container", func() {
   316  					stdout := gbytes.NewBuffer()
   317  
   318  					_, err := container.Run(garden.ProcessSpec{
   319  						Path: "whoami",
   320  						User: "root",
   321  					}, garden.ProcessIO{
   322  						Stdout: stdout,
   323  						Stderr: GinkgoWriter,
   324  					})
   325  
   326  					Expect(err).ToNot(HaveOccurred())
   327  					Eventually(stdout).Should(gbytes.Say("root\n"))
   328  				})
   329  
   330  				Context("and there is no /root directory in the image", func() {
   331  					BeforeEach(func() {
   332  						rootfs = "docker:///onsi/grace-busybox"
   333  					})
   334  
   335  					It("still allows running as root", func() {
   336  						_, err := container.Run(garden.ProcessSpec{
   337  							Path: "ls",
   338  							User: "root",
   339  						}, garden.ProcessIO{})
   340  
   341  						Expect(err).ToNot(HaveOccurred())
   342  					})
   343  				})
   344  
   345  				Context("by default (unprivileged)", func() {
   346  					It("does not get root privileges on host resources", func() {
   347  						process, err := container.Run(garden.ProcessSpec{
   348  							Path: "sh",
   349  							User: "root",
   350  							Args: []string{"-c", "echo h > /proc/sysrq-trigger"},
   351  						}, garden.ProcessIO{})
   352  						Expect(err).ToNot(HaveOccurred())
   353  
   354  						Expect(process.Wait()).ToNot(Equal(0))
   355  					})
   356  
   357  					It("can write to files in the /root directory", func() {
   358  						process, err := container.Run(garden.ProcessSpec{
   359  							User: "root",
   360  							Path: "sh",
   361  							Args: []string{"-c", `touch /root/potato`},
   362  						}, garden.ProcessIO{})
   363  						Expect(err).ToNot(HaveOccurred())
   364  
   365  						Expect(process.Wait()).To(Equal(0))
   366  					})
   367  
   368  					Context("with a docker image", func() {
   369  						BeforeEach(func() {
   370  							rootfs = "docker:///cloudfoundry/preexisting_users"
   371  						})
   372  
   373  						It("sees root-owned files in the rootfs as owned by the container's root user", func() {
   374  							stdout := gbytes.NewBuffer()
   375  							process, err := container.Run(garden.ProcessSpec{
   376  								User: "root",
   377  								Path: "sh",
   378  								Args: []string{"-c", `ls -l /sbin | grep -v wsh | grep -v hook`},
   379  							}, garden.ProcessIO{Stdout: stdout})
   380  							Expect(err).ToNot(HaveOccurred())
   381  
   382  							Expect(process.Wait()).To(Equal(0))
   383  							Expect(stdout).NotTo(gbytes.Say("nobody"))
   384  							Expect(stdout).NotTo(gbytes.Say("65534"))
   385  							Expect(stdout).To(gbytes.Say(" root "))
   386  						})
   387  
   388  						It("sees alice-owned files as owned by alice", func() {
   389  							stdout := gbytes.NewBuffer()
   390  							process, err := container.Run(garden.ProcessSpec{
   391  								User: "alice",
   392  								Path: "sh",
   393  								Args: []string{"-c", `ls -l /home/alice`},
   394  							}, garden.ProcessIO{Stdout: stdout})
   395  							Expect(err).ToNot(HaveOccurred())
   396  
   397  							Expect(process.Wait()).To(Equal(0))
   398  							Expect(stdout).To(gbytes.Say(" alice "))
   399  							Expect(stdout).To(gbytes.Say(" alicesfile"))
   400  						})
   401  
   402  						It("lets alice write in /home/alice", func() {
   403  							process, err := container.Run(garden.ProcessSpec{
   404  								User: "alice",
   405  								Path: "touch",
   406  								Args: []string{"/home/alice/newfile"},
   407  							}, garden.ProcessIO{})
   408  							Expect(err).ToNot(HaveOccurred())
   409  							Expect(process.Wait()).To(Equal(0))
   410  						})
   411  
   412  						It("lets root write to files in the /root directory", func() {
   413  							process, err := container.Run(garden.ProcessSpec{
   414  								User: "root",
   415  								Path: "sh",
   416  								Args: []string{"-c", `touch /root/potato`},
   417  							}, garden.ProcessIO{})
   418  							Expect(err).ToNot(HaveOccurred())
   419  							Expect(process.Wait()).To(Equal(0))
   420  						})
   421  
   422  						It("preserves pre-existing dotfiles from base image", func() {
   423  							out := gbytes.NewBuffer()
   424  							process, err := container.Run(garden.ProcessSpec{
   425  								User: "root",
   426  								Path: "cat",
   427  								Args: []string{"/.foo"},
   428  							}, garden.ProcessIO{
   429  								Stdout: out,
   430  							})
   431  							Expect(err).ToNot(HaveOccurred())
   432  							Expect(process.Wait()).To(Equal(0))
   433  							Expect(out).To(gbytes.Say("this is a pre-existing dotfile"))
   434  						})
   435  					})
   436  				})
   437  
   438  				Context("when the 'privileged' flag is set on the create call", func() {
   439  					BeforeEach(func() {
   440  						privilegedContainer = true
   441  					})
   442  
   443  					It("gets real root privileges", func() {
   444  						process, err := container.Run(garden.ProcessSpec{
   445  							Path: "sh",
   446  							User: "root",
   447  							Args: []string{"-c", "echo h > /proc/sysrq-trigger"},
   448  						}, garden.ProcessIO{})
   449  						Expect(err).ToNot(HaveOccurred())
   450  
   451  						Expect(process.Wait()).To(Equal(0))
   452  					})
   453  
   454  					It("can write to files in the /root directory", func() {
   455  						process, err := container.Run(garden.ProcessSpec{
   456  							User: "root",
   457  							Path: "sh",
   458  							Args: []string{"-c", `touch /root/potato`},
   459  						}, garden.ProcessIO{})
   460  						Expect(err).ToNot(HaveOccurred())
   461  
   462  						Expect(process.Wait()).To(Equal(0))
   463  					})
   464  
   465  					It("sees root-owned files in the rootfs as owned by the container's root user", func() {
   466  						stdout := gbytes.NewBuffer()
   467  						process, err := container.Run(garden.ProcessSpec{
   468  							User: "root",
   469  							Path: "sh",
   470  							Args: []string{"-c", `ls -l /sbin | grep -v wsh | grep -v hook`},
   471  						}, garden.ProcessIO{Stdout: io.MultiWriter(GinkgoWriter, stdout)})
   472  						Expect(err).ToNot(HaveOccurred())
   473  
   474  						Expect(process.Wait()).To(Equal(0))
   475  						Expect(stdout).NotTo(gbytes.Say("nobody"))
   476  						Expect(stdout).NotTo(gbytes.Say("65534"))
   477  						Expect(stdout).To(gbytes.Say(" root "))
   478  					})
   479  				})
   480  			})
   481  
   482  			Measure("it should stream stdout and stderr efficiently", func(b Benchmarker) {
   483  				b.Time("(baseline) streaming 50M of stdout to /dev/null", func() {
   484  					stdout := gbytes.NewBuffer()
   485  					stderr := gbytes.NewBuffer()
   486  
   487  					_, err := container.Run(garden.ProcessSpec{
   488  						Path: "sh",
   489  						Args: []string{"-c", "tr '\\0' 'a' < /dev/zero | dd count=50 bs=1M of=/dev/null; echo done"},
   490  					}, garden.ProcessIO{
   491  						Stdout: stdout,
   492  						Stderr: stderr,
   493  					})
   494  					Expect(err).ToNot(HaveOccurred())
   495  
   496  					Eventually(stdout, "2s").Should(gbytes.Say("done\n"))
   497  				})
   498  
   499  				time := b.Time("streaming 50M of data via garden", func() {
   500  					stdout := gbytes.NewBuffer()
   501  					stderr := gbytes.NewBuffer()
   502  
   503  					_, err := container.Run(garden.ProcessSpec{
   504  						Path: "sh",
   505  						Args: []string{"-c", "tr '\\0' 'a' < /dev/zero | dd count=50 bs=1M; echo done"},
   506  					}, garden.ProcessIO{
   507  						Stdout: stdout,
   508  						Stderr: stderr,
   509  					})
   510  					Expect(err).ToNot(HaveOccurred())
   511  
   512  					Eventually(stdout, "2s").Should(gbytes.Say("done\n"))
   513  				})
   514  
   515  				Expect(time.Seconds()).To(BeNumerically("<", 1))
   516  			}, 10)
   517  
   518  			It("streams output back and reports the exit status", func() {
   519  				stdout := gbytes.NewBuffer()
   520  				stderr := gbytes.NewBuffer()
   521  
   522  				process, err := container.Run(garden.ProcessSpec{
   523  					Path: "sh",
   524  					Args: []string{"-c", "sleep 0.5; echo $FIRST; sleep 0.5; echo $SECOND >&2; sleep 0.5; exit 42"},
   525  					Env:  []string{"FIRST=hello", "SECOND=goodbye"},
   526  				}, garden.ProcessIO{
   527  					Stdout: stdout,
   528  					Stderr: stderr,
   529  				})
   530  				Expect(err).ToNot(HaveOccurred())
   531  
   532  				Eventually(stdout).Should(gbytes.Say("hello\n"))
   533  				Eventually(stderr).Should(gbytes.Say("goodbye\n"))
   534  				Expect(process.Wait()).To(Equal(42))
   535  			})
   536  
   537  			It("sends a TERM signal to the process if requested", func() {
   538  				stdout := gbytes.NewBuffer()
   539  
   540  				process, err := container.Run(garden.ProcessSpec{
   541  					Path: "sh",
   542  					Args: []string{"-c", `
   543  				  trap 'echo termed; exit 42' SIGTERM
   544  
   545  					while true; do
   546  					  echo waiting
   547  					  sleep 1
   548  					done
   549  				`},
   550  				}, garden.ProcessIO{
   551  					Stdout: io.MultiWriter(GinkgoWriter, stdout),
   552  					Stderr: GinkgoWriter,
   553  				})
   554  				Expect(err).ToNot(HaveOccurred())
   555  
   556  				Eventually(stdout).Should(gbytes.Say("waiting"))
   557  				Expect(process.Signal(garden.SignalTerminate)).To(Succeed())
   558  				Eventually(stdout, "2s").Should(gbytes.Say("termed"))
   559  				Expect(process.Wait()).To(Equal(42))
   560  			})
   561  
   562  			It("sends a KILL signal to the process if requested", func() {
   563  				stdout := gbytes.NewBuffer()
   564  
   565  				process, err := container.Run(garden.ProcessSpec{
   566  					Path: "sh",
   567  					Args: []string{"-c", `
   568  				while true; do
   569  				  echo waiting
   570  					sleep 1
   571  				done
   572  			`},
   573  				}, garden.ProcessIO{
   574  					Stdout: io.MultiWriter(GinkgoWriter, stdout),
   575  					Stderr: GinkgoWriter,
   576  				})
   577  				Expect(err).ToNot(HaveOccurred())
   578  
   579  				Eventually(stdout).Should(gbytes.Say("waiting"))
   580  				Expect(process.Signal(garden.SignalKill)).To(Succeed())
   581  				Expect(process.Wait()).ToNot(Equal(0))
   582  			})
   583  
   584  			It("avoids a race condition when sending a kill signal", func(done Done) {
   585  				stdout := gbytes.NewBuffer()
   586  
   587  				for i := 0; i < 200; i++ {
   588  					process, err := container.Run(garden.ProcessSpec{
   589  						Path: "sh",
   590  						Args: []string{"-c", `while true; do echo -n "x"; sleep 1; done`},
   591  					}, garden.ProcessIO{
   592  						Stdout: io.MultiWriter(GinkgoWriter, stdout),
   593  						Stderr: GinkgoWriter,
   594  					})
   595  					Expect(err).ToNot(HaveOccurred())
   596  
   597  					Expect(process.Signal(garden.SignalKill)).To(Succeed())
   598  					Expect(process.Wait()).To(Equal(255))
   599  				}
   600  				close(done)
   601  			}, 30.0)
   602  
   603  			It("collects the process's full output, even if it exits quickly after", func() {
   604  				for i := 0; i < 100; i++ {
   605  					stdout := gbytes.NewBuffer()
   606  
   607  					process, err := container.Run(garden.ProcessSpec{
   608  						Path: "sh",
   609  						Args: []string{"-c", "cat <&0"},
   610  					}, garden.ProcessIO{
   611  						Stdin:  bytes.NewBuffer([]byte("hi stdout")),
   612  						Stderr: os.Stderr,
   613  						Stdout: stdout,
   614  					})
   615  
   616  					if err != nil {
   617  						println("ERROR: " + err.Error())
   618  						select {}
   619  					}
   620  
   621  					Expect(err).ToNot(HaveOccurred())
   622  					Expect(process.Wait()).To(Equal(0))
   623  
   624  					Expect(stdout).To(gbytes.Say("hi stdout"))
   625  				}
   626  			})
   627  
   628  			It("streams input to the process's stdin", func() {
   629  				stdout := gbytes.NewBuffer()
   630  
   631  				process, err := container.Run(garden.ProcessSpec{
   632  					Path: "sh",
   633  					Args: []string{"-c", "cat <&0"},
   634  				}, garden.ProcessIO{
   635  					Stdin:  bytes.NewBufferString("hello\nworld"),
   636  					Stdout: stdout,
   637  				})
   638  				Expect(err).ToNot(HaveOccurred())
   639  
   640  				Eventually(stdout).Should(gbytes.Say("hello\nworld"))
   641  				Expect(process.Wait()).To(Equal(0))
   642  			})
   643  
   644  			It("does not leak open files", func() {
   645  				openFileCount := func() int {
   646  					procFd := fmt.Sprintf("/proc/%d/fd", gardenRunner.Command.Process.Pid)
   647  					files, err := ioutil.ReadDir(procFd)
   648  					Expect(err).ToNot(HaveOccurred())
   649  					return len(files)
   650  				}
   651  
   652  				initialOpenFileCount := openFileCount()
   653  
   654  				for i := 0; i < 50; i++ {
   655  					process, err := container.Run(garden.ProcessSpec{
   656  						Path: "true",
   657  					}, garden.ProcessIO{
   658  						Stdout: GinkgoWriter,
   659  						Stderr: GinkgoWriter,
   660  					})
   661  					Expect(err).ToNot(HaveOccurred())
   662  					Expect(process.Wait()).To(Equal(0))
   663  				}
   664  
   665  				// there's some noise in 'open files' check, but it shouldn't grow
   666  				// linearly with the number of processes spawned
   667  				Eventually(openFileCount, "10s").Should(BeNumerically("<", initialOpenFileCount+10))
   668  			})
   669  
   670  			It("forwards the exit status even if stdin is still being written", func() {
   671  				// this covers the case of intermediaries shuffling i/o around (e.g. wsh)
   672  				// receiving SIGPIPE on write() due to the backing process exiting without
   673  				// flushing stdin
   674  				//
   675  				// in practice it's flaky; sometimes write() finishes just before the
   676  				// process exits, so run it ~10 times (observed it fail often in this range)
   677  
   678  				for i := 0; i < 10; i++ {
   679  					process, err := container.Run(garden.ProcessSpec{
   680  						Path: "ls",
   681  					}, garden.ProcessIO{
   682  						Stdin: bytes.NewBufferString(strings.Repeat("x", 1024)),
   683  					})
   684  					Expect(err).ToNot(HaveOccurred())
   685  
   686  					Expect(process.Wait()).To(Equal(0))
   687  				}
   688  			})
   689  
   690  			Context("with a memory limit", func() {
   691  				JustBeforeEach(func() {
   692  					err := container.LimitMemory(garden.MemoryLimits{
   693  						LimitInBytes: 64 * 1024 * 1024,
   694  					})
   695  					Expect(err).ToNot(HaveOccurred())
   696  				})
   697  
   698  				Context("when the process writes too much to /dev/shm", func() {
   699  					It("is killed", func() {
   700  						process, err := container.Run(garden.ProcessSpec{
   701  							Path: "dd",
   702  							Args: []string{"if=/dev/urandom", "of=/dev/shm/too-big", "bs=1M", "count=65"},
   703  						}, garden.ProcessIO{})
   704  						Expect(err).ToNot(HaveOccurred())
   705  
   706  						Expect(process.Wait()).ToNot(Equal(0))
   707  					})
   708  				})
   709  			})
   710  
   711  			Context("with a tty", func() {
   712  				It("executes the process with a raw tty with the given window size", func() {
   713  					stdout := gbytes.NewBuffer()
   714  
   715  					inR, inW := io.Pipe()
   716  
   717  					process, err := container.Run(garden.ProcessSpec{
   718  						Path: "sh",
   719  						Args: []string{"-c", "read foo; stty -a"},
   720  						TTY: &garden.TTYSpec{
   721  							WindowSize: &garden.WindowSize{
   722  								Columns: 123,
   723  								Rows:    456,
   724  							},
   725  						},
   726  					}, garden.ProcessIO{
   727  						Stdin:  inR,
   728  						Stdout: stdout,
   729  					})
   730  					Expect(err).ToNot(HaveOccurred())
   731  
   732  					_, err = inW.Write([]byte("hello"))
   733  					Expect(err).ToNot(HaveOccurred())
   734  
   735  					Eventually(stdout).Should(gbytes.Say("hello"))
   736  
   737  					_, err = inW.Write([]byte("\n"))
   738  					Expect(err).ToNot(HaveOccurred())
   739  
   740  					Eventually(stdout).Should(gbytes.Say("rows 456; columns 123;"))
   741  
   742  					Expect(process.Wait()).To(Equal(0))
   743  				})
   744  
   745  				It("can have its terminal resized", func() {
   746  					stdout := gbytes.NewBuffer()
   747  
   748  					inR, inW := io.Pipe()
   749  
   750  					process, err := container.Run(garden.ProcessSpec{
   751  						Path: "sh",
   752  						Args: []string{
   753  							"-c",
   754  							`
   755  							trap "stty -a" SIGWINCH
   756  
   757  							# continuously read so that the trap can keep firing
   758  							while true; do
   759  								echo waiting
   760  								if read; then
   761  									exit 0
   762  								fi
   763  							done
   764  						`,
   765  						},
   766  						TTY: &garden.TTYSpec{},
   767  					}, garden.ProcessIO{
   768  						Stdin:  inR,
   769  						Stdout: stdout,
   770  					})
   771  					Expect(err).ToNot(HaveOccurred())
   772  
   773  					Eventually(stdout).Should(gbytes.Say("waiting"))
   774  
   775  					err = process.SetTTY(garden.TTYSpec{
   776  						WindowSize: &garden.WindowSize{
   777  							Columns: 123,
   778  							Rows:    456,
   779  						},
   780  					})
   781  					Expect(err).ToNot(HaveOccurred())
   782  
   783  					Eventually(stdout).Should(gbytes.Say("rows 456; columns 123;"))
   784  
   785  					_, err = fmt.Fprintf(inW, "ok\n")
   786  					Expect(err).ToNot(HaveOccurred())
   787  
   788  					Expect(process.Wait()).To(Equal(0))
   789  				})
   790  			})
   791  
   792  			Context("with a working directory", func() {
   793  				It("executes with the working directory as the dir", func() {
   794  					stdout := gbytes.NewBuffer()
   795  
   796  					process, err := container.Run(garden.ProcessSpec{
   797  						Path: "pwd",
   798  						Dir:  "/usr",
   799  					}, garden.ProcessIO{
   800  						Stdout: stdout,
   801  					})
   802  					Expect(err).ToNot(HaveOccurred())
   803  
   804  					Eventually(stdout).Should(gbytes.Say("/usr\n"))
   805  					Expect(process.Wait()).To(Equal(0))
   806  				})
   807  			})
   808  
   809  			Context("and then attaching to it", func() {
   810  				It("streams output and the exit status to the attached request", func(done Done) {
   811  					stdout1 := gbytes.NewBuffer()
   812  					stdout2 := gbytes.NewBuffer()
   813  
   814  					process, err := container.Run(garden.ProcessSpec{
   815  						Path: "sh",
   816  						Args: []string{"-c", "sleep 2; echo hello; sleep 0.5; echo goodbye; sleep 0.5; exit 42"},
   817  					}, garden.ProcessIO{
   818  						Stdout: stdout1,
   819  					})
   820  					Expect(err).ToNot(HaveOccurred())
   821  
   822  					attached, err := container.Attach(process.ID(), garden.ProcessIO{
   823  						Stdout: stdout2,
   824  					})
   825  					Expect(err).ToNot(HaveOccurred())
   826  
   827  					time.Sleep(2 * time.Second)
   828  
   829  					Eventually(stdout1).Should(gbytes.Say("hello\n"))
   830  					Eventually(stdout1).Should(gbytes.Say("goodbye\n"))
   831  
   832  					Eventually(stdout2).Should(gbytes.Say("hello\n"))
   833  					Eventually(stdout2).Should(gbytes.Say("goodbye\n"))
   834  
   835  					Expect(process.Wait()).To(Equal(42))
   836  					Expect(attached.Wait()).To(Equal(42))
   837  
   838  					close(done)
   839  				}, 10.0)
   840  			})
   841  
   842  			Context("and then sending a Stop request", func() {
   843  				It("terminates all running processes", func() {
   844  					stdout := gbytes.NewBuffer()
   845  
   846  					process, err := container.Run(garden.ProcessSpec{
   847  						Path: "sh",
   848  						Args: []string{
   849  							"-c",
   850  							`
   851  						trap 'exit 42' SIGTERM
   852  
   853  						# sync with test, and allow trap to fire when not sleeping
   854  						while true; do
   855  							echo waiting
   856  							sleep 0.5
   857  						done
   858  						`,
   859  						},
   860  					}, garden.ProcessIO{
   861  						Stdout: stdout,
   862  					})
   863  					Expect(err).ToNot(HaveOccurred())
   864  
   865  					Eventually(stdout, 30).Should(gbytes.Say("waiting"))
   866  
   867  					err = container.Stop(false)
   868  					Expect(err).ToNot(HaveOccurred())
   869  
   870  					Expect(process.Wait()).To(Equal(42))
   871  				})
   872  
   873  				It("recursively terminates all child processes", func(done Done) {
   874  					defer close(done)
   875  
   876  					stdout := gbytes.NewBuffer()
   877  
   878  					process, err := container.Run(garden.ProcessSpec{
   879  						Path: "sh",
   880  						Args: []string{
   881  							"-c",
   882  							`
   883  						# don't die until child processes die
   884  						trap wait SIGTERM
   885  
   886  						# spawn child that exits when it receives TERM
   887  						sh -c 'sleep 100 & wait' &
   888  
   889  						# sync with test
   890  						echo waiting
   891  
   892  						# wait on children
   893  						wait
   894  						`,
   895  						},
   896  					}, garden.ProcessIO{
   897  						Stdout: stdout,
   898  					})
   899  
   900  					Expect(err).ToNot(HaveOccurred())
   901  
   902  					Eventually(stdout, 5).Should(gbytes.Say("waiting\n"))
   903  
   904  					stoppedAt := time.Now()
   905  
   906  					err = container.Stop(false)
   907  					Expect(err).ToNot(HaveOccurred())
   908  
   909  					Expect(process.Wait()).To(Equal(143)) // 143 = 128 + SIGTERM
   910  
   911  					Expect(time.Since(stoppedAt)).To(BeNumerically("<=", 5*time.Second))
   912  				}, 15)
   913  
   914  				Context("when a process does not die 10 seconds after receiving SIGTERM", func() {
   915  					It("is forcibly killed", func(done Done) {
   916  						defer close(done)
   917  
   918  						process, err := container.Run(garden.ProcessSpec{
   919  							Path: "sh",
   920  							Args: []string{
   921  								"-c",
   922  								`
   923                  trap "echo cant touch this; sleep 1000" SIGTERM
   924  
   925                  echo waiting
   926                  sleep 1000 &
   927                  wait
   928                `,
   929  							},
   930  						}, garden.ProcessIO{})
   931  
   932  						Expect(err).ToNot(HaveOccurred())
   933  
   934  						stoppedAt := time.Now()
   935  
   936  						err = container.Stop(false)
   937  						Expect(err).ToNot(HaveOccurred())
   938  
   939  						Expect(process.Wait()).ToNot(Equal(0)) // either 137 or 255
   940  
   941  						Expect(time.Since(stoppedAt)).To(BeNumerically(">=", 10*time.Second))
   942  					}, 15)
   943  				})
   944  			})
   945  		})
   946  
   947  		Context("and streaming files in", func() {
   948  			var tarStream io.Reader
   949  
   950  			JustBeforeEach(func() {
   951  				tmpdir, err := ioutil.TempDir("", "some-temp-dir-parent")
   952  				Expect(err).ToNot(HaveOccurred())
   953  
   954  				tgzPath := filepath.Join(tmpdir, "some.tgz")
   955  
   956  				archiver.CreateTarGZArchive(
   957  					tgzPath,
   958  					[]archiver.ArchiveFile{
   959  						{
   960  							Name: "./some-temp-dir",
   961  							Dir:  true,
   962  						},
   963  						{
   964  							Name: "./some-temp-dir/some-temp-file",
   965  							Body: "some-body",
   966  						},
   967  					},
   968  				)
   969  
   970  				tgz, err := os.Open(tgzPath)
   971  				Expect(err).ToNot(HaveOccurred())
   972  
   973  				tarStream, err = gzip.NewReader(tgz)
   974  				Expect(err).ToNot(HaveOccurred())
   975  			})
   976  
   977  			It("creates the files in the container, as the vcap user", func() {
   978  				err := container.StreamIn("/home/vcap", tarStream)
   979  				Expect(err).ToNot(HaveOccurred())
   980  
   981  				process, err := container.Run(garden.ProcessSpec{
   982  					Path: "test",
   983  					Args: []string{"-f", "/home/vcap/some-temp-dir/some-temp-file"},
   984  				}, garden.ProcessIO{})
   985  				Expect(err).ToNot(HaveOccurred())
   986  
   987  				Expect(process.Wait()).To(Equal(0))
   988  
   989  				output := gbytes.NewBuffer()
   990  				process, err = container.Run(garden.ProcessSpec{
   991  					Path: "ls",
   992  					Args: []string{"-al", "/home/vcap/some-temp-dir/some-temp-file"},
   993  				}, garden.ProcessIO{
   994  					Stdout: output,
   995  				})
   996  				Expect(err).ToNot(HaveOccurred())
   997  
   998  				Expect(process.Wait()).To(Equal(0))
   999  
  1000  				// output should look like -rwxrwxrwx 1 vcap vcap 9 Jan  1  1970 /tmp/some-container-dir/some-temp-dir/some-temp-file
  1001  				Expect(output).To(gbytes.Say("vcap"))
  1002  				Expect(output).To(gbytes.Say("vcap"))
  1003  			})
  1004  
  1005  			PIt("can create files in /tmp")
  1006  
  1007  			Context("in a privileged container", func() {
  1008  				BeforeEach(func() {
  1009  					privilegedContainer = true
  1010  				})
  1011  
  1012  				It("streams in relative to the default run directory", func() {
  1013  					err := container.StreamIn(".", tarStream)
  1014  					Expect(err).ToNot(HaveOccurred())
  1015  
  1016  					process, err := container.Run(garden.ProcessSpec{
  1017  						Path: "test",
  1018  						Args: []string{"-f", "some-temp-dir/some-temp-file"},
  1019  					}, garden.ProcessIO{})
  1020  					Expect(err).ToNot(HaveOccurred())
  1021  
  1022  					Expect(process.Wait()).To(Equal(0))
  1023  				})
  1024  			})
  1025  
  1026  			It("streams in relative to the default run directory", func() {
  1027  				err := container.StreamIn(".", tarStream)
  1028  				Expect(err).ToNot(HaveOccurred())
  1029  
  1030  				process, err := container.Run(garden.ProcessSpec{
  1031  					Path: "test",
  1032  					Args: []string{"-f", "some-temp-dir/some-temp-file"},
  1033  				}, garden.ProcessIO{})
  1034  				Expect(err).ToNot(HaveOccurred())
  1035  
  1036  				Expect(process.Wait()).To(Equal(0))
  1037  			})
  1038  
  1039  			It("returns an error when the tar process dies", func() {
  1040  				err := container.StreamIn("/tmp/some-container-dir", &io.LimitedReader{
  1041  					R: tarStream,
  1042  					N: 10,
  1043  				})
  1044  				Expect(err).To(HaveOccurred())
  1045  			})
  1046  
  1047  			Context("and then copying them out", func() {
  1048  				It("streams the directory", func() {
  1049  					process, err := container.Run(garden.ProcessSpec{
  1050  						Path: "sh",
  1051  						Args: []string{"-c", `mkdir -p some-outer-dir/some-inner-dir && touch some-outer-dir/some-inner-dir/some-file`},
  1052  					}, garden.ProcessIO{})
  1053  					Expect(err).ToNot(HaveOccurred())
  1054  
  1055  					Expect(process.Wait()).To(Equal(0))
  1056  
  1057  					tarOutput, err := container.StreamOut("some-outer-dir/some-inner-dir")
  1058  					Expect(err).ToNot(HaveOccurred())
  1059  
  1060  					tarReader := tar.NewReader(tarOutput)
  1061  
  1062  					header, err := tarReader.Next()
  1063  					Expect(err).ToNot(HaveOccurred())
  1064  					Expect(header.Name).To(Equal("some-inner-dir/"))
  1065  
  1066  					header, err = tarReader.Next()
  1067  					Expect(err).ToNot(HaveOccurred())
  1068  					Expect(header.Name).To(Equal("some-inner-dir/some-file"))
  1069  				})
  1070  
  1071  				Context("with a trailing slash", func() {
  1072  					It("streams the contents of the directory", func() {
  1073  						process, err := container.Run(garden.ProcessSpec{
  1074  							Path: "sh",
  1075  							Args: []string{"-c", `mkdir -p some-container-dir && touch some-container-dir/some-file`},
  1076  						}, garden.ProcessIO{})
  1077  						Expect(err).ToNot(HaveOccurred())
  1078  
  1079  						Expect(process.Wait()).To(Equal(0))
  1080  
  1081  						tarOutput, err := container.StreamOut("some-container-dir/")
  1082  						Expect(err).ToNot(HaveOccurred())
  1083  
  1084  						tarReader := tar.NewReader(tarOutput)
  1085  
  1086  						header, err := tarReader.Next()
  1087  						Expect(err).ToNot(HaveOccurred())
  1088  						Expect(header.Name).To(Equal("./"))
  1089  
  1090  						header, err = tarReader.Next()
  1091  						Expect(err).ToNot(HaveOccurred())
  1092  						Expect(header.Name).To(Equal("./some-file"))
  1093  					})
  1094  				})
  1095  			})
  1096  		})
  1097  
  1098  		Context("and sending a Stop request", func() {
  1099  			It("changes the container's state to 'stopped'", func() {
  1100  				err := container.Stop(false)
  1101  				Expect(err).ToNot(HaveOccurred())
  1102  
  1103  				info, err := container.Info()
  1104  				Expect(err).ToNot(HaveOccurred())
  1105  
  1106  				Expect(info.State).To(Equal("stopped"))
  1107  			})
  1108  		})
  1109  
  1110  		Context("after destroying the container", func() {
  1111  			It("should return api.ContainerNotFoundError when deleting the container again", func() {
  1112  				Expect(client.Destroy(container.Handle())).To(Succeed())
  1113  				Expect(client.Destroy(container.Handle())).To(MatchError(garden.ContainerNotFoundError{container.Handle()}))
  1114  				container = nil
  1115  			})
  1116  
  1117  			It("should ensure any iptables rules which were created no longer exist", func() {
  1118  				handle := container.Handle()
  1119  				Expect(client.Destroy(handle)).To(Succeed())
  1120  				container = nil
  1121  
  1122  				iptables, err := gexec.Start(exec.Command("iptables", "-L"), GinkgoWriter, GinkgoWriter)
  1123  				Expect(err).ToNot(HaveOccurred())
  1124  				Eventually(iptables, "2s").Should(gexec.Exit())
  1125  				Expect(iptables).ToNot(gbytes.Say(handle))
  1126  			})
  1127  
  1128  			It("destroys multiple containers based on same rootfs", func() {
  1129  				c1, err := client.Create(garden.ContainerSpec{
  1130  					RootFSPath: "docker:///busybox",
  1131  					Privileged: false,
  1132  				})
  1133  				Expect(err).ToNot(HaveOccurred())
  1134  				c2, err := client.Create(garden.ContainerSpec{
  1135  					RootFSPath: "docker:///busybox",
  1136  					Privileged: false,
  1137  				})
  1138  				Expect(err).ToNot(HaveOccurred())
  1139  
  1140  				Expect(client.Destroy(c1.Handle())).To(Succeed())
  1141  				Expect(client.Destroy(c2.Handle())).To(Succeed())
  1142  			})
  1143  
  1144  			It("should not leak network namespace", func() {
  1145  				info, err := container.Info()
  1146  				Expect(err).ToNot(HaveOccurred())
  1147  				Expect(info.State).To(Equal("active"))
  1148  
  1149  				pidPath := filepath.Join(info.ContainerPath, "run", "wshd.pid")
  1150  
  1151  				_, err = ioutil.ReadFile(pidPath)
  1152  				Expect(err).ToNot(HaveOccurred())
  1153  
  1154  				Expect(client.Destroy(container.Handle())).To(Succeed())
  1155  				container = nil
  1156  
  1157  				stdout := gbytes.NewBuffer()
  1158  				cmd, err := gexec.Start(
  1159  					exec.Command(
  1160  						"sh",
  1161  						"-c",
  1162  						"mount -n -t tmpfs tmpfs /sys && ip netns list && umount /sys",
  1163  					),
  1164  					stdout,
  1165  					GinkgoWriter,
  1166  				)
  1167  
  1168  				Expect(err).ToNot(HaveOccurred())
  1169  				Expect(cmd.Wait("1s").ExitCode()).To(Equal(0))
  1170  				Expect(stdout.Contents()).To(Equal([]byte{}))
  1171  			})
  1172  		})
  1173  	})
  1174  })