github.com/geofffranks/garden-linux@v0.0.0-20160715111146-26c893169cfa/integration/lifecycle/lifecycle_test.go (about)

     1  package lifecycle_test
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"os/exec"
     8  	"path"
     9  	"path/filepath"
    10  	"sync"
    11  
    12  	"code.cloudfoundry.org/garden"
    13  	"code.cloudfoundry.org/garden-linux/integration/runner"
    14  	. "github.com/onsi/ginkgo"
    15  	. "github.com/onsi/gomega"
    16  	"github.com/onsi/gomega/gbytes"
    17  	"github.com/onsi/gomega/gexec"
    18  )
    19  
    20  var _ = Describe("Creating a container", func() {
    21  	Describe("Overlapping networks", func() {
    22  		Context("when the requested Network overlaps the dynamic allocation range", func() {
    23  			It("returns an error message naming the overlapped range", func() {
    24  				client = startGarden("--networkPool", "1.2.3.0/24")
    25  				_, err := client.Create(garden.ContainerSpec{Network: "1.2.3.0/25"})
    26  				Expect(err).To(MatchError("the requested subnet (1.2.3.0/25) overlaps the dynamic allocation range (1.2.3.0/24)"))
    27  			})
    28  		})
    29  
    30  		Context("when the requested Network overlaps another subnet", func() {
    31  			It("returns an error message naming the overlapped range", func() {
    32  				client = startGarden()
    33  				_, err := client.Create(garden.ContainerSpec{Privileged: false, Network: "10.2.0.0/29"})
    34  				Expect(err).ToNot(HaveOccurred())
    35  				_, err = client.Create(garden.ContainerSpec{Privileged: false, Network: "10.2.0.0/30"})
    36  				Expect(err).To(MatchError("the requested subnet (10.2.0.0/30) overlaps an existing subnet (10.2.0.0/29)"))
    37  			})
    38  		})
    39  	})
    40  
    41  	Describe("concurrent creation of containers based on same docker rootfs", func() {
    42  		It("retains the full rootFS without truncating files", func() {
    43  			client = startGarden()
    44  			c1chan := make(chan garden.Container)
    45  			c2chan := make(chan garden.Container)
    46  			c3chan := make(chan garden.Container)
    47  
    48  			createContainer := func(ch chan<- garden.Container) {
    49  				defer GinkgoRecover()
    50  				c, err := client.Create(garden.ContainerSpec{RootFSPath: "docker:///cfgarden/large_layers", Privileged: false})
    51  				Expect(err).ToNot(HaveOccurred())
    52  				ch <- c
    53  			}
    54  
    55  			runInContainer := func(c garden.Container) {
    56  				out := gbytes.NewBuffer()
    57  				process, err := c.Run(garden.ProcessSpec{
    58  					User: "alice",
    59  					Path: "/usr/local/go/bin/go",
    60  					Args: []string{"version"},
    61  				}, garden.ProcessIO{
    62  					Stdout: out,
    63  					Stderr: out,
    64  				})
    65  				Expect(err).ToNot(HaveOccurred())
    66  				Expect(process.Wait()).To(Equal(0))
    67  				Eventually(out).Should(gbytes.Say("go version go1.4.2 linux/amd64"))
    68  			}
    69  
    70  			go createContainer(c1chan)
    71  			go createContainer(c2chan)
    72  			go createContainer(c3chan)
    73  
    74  			runInContainer(<-c1chan)
    75  			runInContainer(<-c2chan)
    76  			runInContainer(<-c3chan)
    77  		})
    78  	})
    79  
    80  	Describe("concurrently creating", func() {
    81  		It("does not deadlock", func() {
    82  			client = startGarden()
    83  			wg := new(sync.WaitGroup)
    84  
    85  			errors := make(chan error, 50)
    86  			for i := 0; i < 40; i++ {
    87  				wg.Add(1)
    88  				go func() {
    89  					defer GinkgoRecover()
    90  
    91  					_, err := client.Create(garden.ContainerSpec{})
    92  					if err != nil {
    93  						errors <- err
    94  					}
    95  
    96  					wg.Done()
    97  				}()
    98  			}
    99  			wg.Wait()
   100  
   101  			Expect(errors).ToNot(Receive())
   102  		})
   103  	})
   104  
   105  	Describe("concurrently destroying", func() {
   106  		allBridges := func() []byte {
   107  			stdout := gbytes.NewBuffer()
   108  			cmd, err := gexec.Start(exec.Command("ip", "a"), stdout, GinkgoWriter)
   109  			Expect(err).ToNot(HaveOccurred())
   110  			cmd.Wait()
   111  
   112  			return stdout.Contents()
   113  		}
   114  
   115  		It("does not leave residual bridges", func() {
   116  			client = startGarden()
   117  
   118  			bridgePrefix := fmt.Sprintf("w%db-", GinkgoParallelNode())
   119  			Expect(allBridges()).ToNot(ContainSubstring(bridgePrefix))
   120  
   121  			handles := make([]string, 0)
   122  			for i := 0; i < 5; i++ {
   123  				c, err := client.Create(garden.ContainerSpec{})
   124  				Expect(err).ToNot(HaveOccurred())
   125  
   126  				handles = append(handles, c.Handle())
   127  			}
   128  
   129  			retry := func(fn func() error) error {
   130  				var err error
   131  				for retry := 0; retry < 3; retry++ {
   132  					err = fn()
   133  					if err == nil {
   134  						break
   135  					}
   136  				}
   137  				return err
   138  			}
   139  
   140  			wg := new(sync.WaitGroup)
   141  			errors := make(chan error, 50)
   142  			for _, h := range handles {
   143  				wg.Add(1)
   144  				go func(h string) {
   145  					err := retry(func() error { return client.Destroy(h) })
   146  
   147  					if err != nil {
   148  						errors <- err
   149  					}
   150  
   151  					wg.Done()
   152  				}(h)
   153  			}
   154  
   155  			wg.Wait()
   156  
   157  			Expect(errors).ToNot(Receive())
   158  			Expect(client.Containers(garden.Properties{})).To(HaveLen(0)) // sanity check
   159  
   160  			Eventually(allBridges, "60s", "10s").ShouldNot(ContainSubstring(bridgePrefix))
   161  		})
   162  	})
   163  
   164  	Context("when the create container fails because of env failure", func() {
   165  		allBridges := func() []byte {
   166  			stdout := gbytes.NewBuffer()
   167  			cmd, err := gexec.Start(exec.Command("ip", "a"), stdout, GinkgoWriter)
   168  			Expect(err).ToNot(HaveOccurred())
   169  			cmd.Wait()
   170  			return stdout.Contents()
   171  		}
   172  
   173  		It("does not leave bridges resources around", func() {
   174  			client = startGarden()
   175  			bridgePrefix := fmt.Sprintf("w%db-", GinkgoParallelNode())
   176  			Expect(allBridges()).ToNot(ContainSubstring(bridgePrefix))
   177  			var err error
   178  			_, err = client.Create(garden.ContainerSpec{
   179  				Env: []string{"hello"}})
   180  			Expect(err).To(HaveOccurred())
   181  			Expect(err).To(MatchError(HavePrefix("process: malformed environment")))
   182  			//check no bridges are leaked
   183  			Eventually(allBridges).ShouldNot(ContainSubstring(bridgePrefix))
   184  		})
   185  
   186  		It("does not leave network namespaces resources around", func() {
   187  			client = startGarden()
   188  			var err error
   189  			_, err = client.Create(garden.ContainerSpec{
   190  				Env: []string{"hello"}})
   191  			Expect(err).To(HaveOccurred())
   192  			Expect(err).To(MatchError(HavePrefix("process: malformed environment")))
   193  			//check no network namespaces are leaked
   194  			stdout := gbytes.NewBuffer()
   195  			cmd, err := gexec.Start(
   196  				exec.Command(
   197  					"sh",
   198  					"-c",
   199  					"mount -n -t tmpfs tmpfs /sys && ip netns list && umount /sys",
   200  				),
   201  				stdout,
   202  				GinkgoWriter,
   203  			)
   204  			Expect(err).ToNot(HaveOccurred())
   205  			Expect(cmd.Wait("1s").ExitCode()).To(Equal(0))
   206  			Expect(stdout.Contents()).To(Equal([]byte{}))
   207  		})
   208  	})
   209  
   210  	Context("when the container fails to start", func() {
   211  		It("does not leave resources around", func() {
   212  			client = startGarden()
   213  			client.Create(garden.ContainerSpec{
   214  				BindMounts: []garden.BindMount{{
   215  					SrcPath: "fictional",
   216  					DstPath: "whereami",
   217  				}},
   218  			})
   219  
   220  			depotDir := filepath.Join(
   221  				os.TempDir(),
   222  				fmt.Sprintf("test-garden-%d", GinkgoParallelNode()),
   223  				"containers",
   224  			)
   225  			Expect(ioutil.ReadDir(depotDir)).To(HaveLen(0))
   226  		})
   227  	})
   228  
   229  	Context("when the container is created succesfully", func() {
   230  		var container garden.Container
   231  
   232  		var privilegedContainer bool
   233  		var rootfs string
   234  
   235  		JustBeforeEach(func() {
   236  			client = startGarden()
   237  
   238  			var err error
   239  			container, err = client.Create(garden.ContainerSpec{Privileged: privilegedContainer, RootFSPath: rootfs})
   240  			Expect(err).ToNot(HaveOccurred())
   241  		})
   242  
   243  		AfterEach(func() {
   244  			if container != nil {
   245  				Expect(client.Destroy(container.Handle())).To(Succeed())
   246  			}
   247  		})
   248  
   249  		BeforeEach(func() {
   250  			privilegedContainer = false
   251  			rootfs = ""
   252  		})
   253  
   254  		Context("when the rootfs is a symlink", func() {
   255  			var symlinkDir string
   256  
   257  			BeforeEach(func() {
   258  				symlinkDir, err := ioutil.TempDir("", "test-symlink")
   259  				Expect(err).ToNot(HaveOccurred())
   260  
   261  				rootfs = path.Join(symlinkDir, "rootfs")
   262  
   263  				err = os.Symlink(runner.RootFSPath, rootfs)
   264  				Expect(err).ToNot(HaveOccurred())
   265  			})
   266  
   267  			AfterEach(func() {
   268  				os.RemoveAll(symlinkDir)
   269  			})
   270  
   271  			It("follows the symlink", func() {
   272  				stdout := gbytes.NewBuffer()
   273  
   274  				process, err := container.Run(garden.ProcessSpec{
   275  					User: "alice",
   276  					Path: "ls",
   277  					Args: []string{"/"},
   278  				}, garden.ProcessIO{
   279  					Stdout: stdout,
   280  				})
   281  				Expect(err).ToNot(HaveOccurred())
   282  				Expect(process.Wait()).To(BeZero())
   283  
   284  				Expect(stdout).To(gbytes.Say("bin"))
   285  			})
   286  		})
   287  
   288  		Context("and running a process", func() {
   289  			It("does not leak open files", func() {
   290  				openFileCount := func() int {
   291  					procFd := fmt.Sprintf("/proc/%d/fd", client.Pid)
   292  					files, err := ioutil.ReadDir(procFd)
   293  					Expect(err).ToNot(HaveOccurred())
   294  
   295  					return len(files)
   296  				}
   297  
   298  				initialOpenFileCount := openFileCount()
   299  
   300  				for i := 0; i < 50; i++ {
   301  					process, err := container.Run(garden.ProcessSpec{
   302  						User: "alice",
   303  						Path: "true",
   304  					}, garden.ProcessIO{})
   305  					Expect(err).ToNot(HaveOccurred())
   306  					Expect(process.Wait()).To(Equal(0))
   307  				}
   308  
   309  				// there's some noise in 'open files' check, but it shouldn't grow
   310  				// linearly with the number of processes spawned
   311  				Eventually(openFileCount, "10s").Should(BeNumerically("<", initialOpenFileCount+10))
   312  			})
   313  		})
   314  
   315  		Context("after destroying the container", func() {
   316  			It("should return api.ContainerNotFoundError when deleting the container again", func() {
   317  				Expect(client.Destroy(container.Handle())).To(Succeed())
   318  				Expect(client.Destroy(container.Handle())).To(MatchError(garden.ContainerNotFoundError{container.Handle()}))
   319  				container = nil
   320  			})
   321  
   322  			It("should ensure any iptables rules which were created no longer exist", func() {
   323  				handle := container.Handle()
   324  				Expect(client.Destroy(handle)).To(Succeed())
   325  				container = nil
   326  
   327  				iptables, err := gexec.Start(exec.Command("iptables", "-L"), GinkgoWriter, GinkgoWriter)
   328  				Expect(err).ToNot(HaveOccurred())
   329  				Eventually(iptables, "10s").Should(gexec.Exit())
   330  				Expect(iptables).ToNot(gbytes.Say(handle))
   331  			})
   332  
   333  			It("destroys multiple containers based on same rootfs", func() {
   334  				c1, err := client.Create(garden.ContainerSpec{
   335  					RootFSPath: "docker:///busybox",
   336  					Privileged: false,
   337  				})
   338  				Expect(err).ToNot(HaveOccurred())
   339  				c2, err := client.Create(garden.ContainerSpec{
   340  					RootFSPath: "docker:///busybox",
   341  					Privileged: false,
   342  				})
   343  				Expect(err).ToNot(HaveOccurred())
   344  
   345  				Expect(client.Destroy(c1.Handle())).To(Succeed())
   346  				Expect(client.Destroy(c2.Handle())).To(Succeed())
   347  			})
   348  
   349  			It("should not leak network namespace", func() {
   350  				info, err := container.Info()
   351  				Expect(err).ToNot(HaveOccurred())
   352  				Expect(info.State).To(Equal("active"))
   353  
   354  				pidPath := filepath.Join(info.ContainerPath, "run", "wshd.pid")
   355  
   356  				_, err = ioutil.ReadFile(pidPath)
   357  				Expect(err).ToNot(HaveOccurred())
   358  
   359  				Expect(client.Destroy(container.Handle())).To(Succeed())
   360  				container = nil
   361  
   362  				stdout := gbytes.NewBuffer()
   363  				cmd, err := gexec.Start(
   364  					exec.Command(
   365  						"sh",
   366  						"-c",
   367  						"mount -n -t tmpfs tmpfs /sys && ip netns list && umount /sys",
   368  					),
   369  					stdout,
   370  					GinkgoWriter,
   371  				)
   372  
   373  				Expect(err).ToNot(HaveOccurred())
   374  				Expect(cmd.Wait("2s").ExitCode()).To(Equal(0))
   375  				Expect(stdout.Contents()).To(Equal([]byte{}))
   376  			})
   377  		})
   378  	})
   379  })