github.com/cloudfoundry-attic/garden-linux@v0.333.2-candidate/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  	"github.com/cloudfoundry-incubator/garden"
    13  	"github.com/cloudfoundry-incubator/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  					container, err := client.Create(garden.ContainerSpec{})
    92  					if err != nil {
    93  						errors <- err
    94  					} else {
    95  						client.Destroy(container.Handle())
    96  					}
    97  
    98  					wg.Done()
    99  				}()
   100  			}
   101  			wg.Wait()
   102  
   103  			Expect(errors).ToNot(Receive())
   104  		})
   105  	})
   106  
   107  	Describe("concurrently destroying", func() {
   108  		allBridges := func() []byte {
   109  			stdout := gbytes.NewBuffer()
   110  			cmd, err := gexec.Start(exec.Command("ip", "a"), stdout, GinkgoWriter)
   111  			Expect(err).ToNot(HaveOccurred())
   112  			cmd.Wait()
   113  
   114  			return stdout.Contents()
   115  		}
   116  
   117  		It("does not leave residual bridges", func() {
   118  			client = startGarden()
   119  
   120  			bridgePrefix := fmt.Sprintf("w%db-", GinkgoParallelNode())
   121  			Expect(allBridges()).ToNot(ContainSubstring(bridgePrefix))
   122  
   123  			handles := make([]string, 0)
   124  			for i := 0; i < 5; i++ {
   125  				c, err := client.Create(garden.ContainerSpec{})
   126  				Expect(err).ToNot(HaveOccurred())
   127  
   128  				handles = append(handles, c.Handle())
   129  			}
   130  
   131  			retry := func(fn func() error) error {
   132  				var err error
   133  				for retry := 0; retry < 3; retry++ {
   134  					err = fn()
   135  					if err == nil {
   136  						break
   137  					}
   138  				}
   139  				return err
   140  			}
   141  
   142  			wg := new(sync.WaitGroup)
   143  			errors := make(chan error, 50)
   144  			for _, h := range handles {
   145  				wg.Add(1)
   146  				go func(h string) {
   147  					err := retry(func() error { return client.Destroy(h) })
   148  
   149  					if err != nil {
   150  						errors <- err
   151  					}
   152  
   153  					wg.Done()
   154  				}(h)
   155  			}
   156  
   157  			wg.Wait()
   158  
   159  			Expect(errors).ToNot(Receive())
   160  			Expect(client.Containers(garden.Properties{})).To(HaveLen(0)) // sanity check
   161  
   162  			Eventually(allBridges, "60s", "10s").ShouldNot(ContainSubstring(bridgePrefix))
   163  		})
   164  	})
   165  
   166  	Context("when the create container fails because of env failure", func() {
   167  		allBridges := func() []byte {
   168  			stdout := gbytes.NewBuffer()
   169  			cmd, err := gexec.Start(exec.Command("ip", "a"), stdout, GinkgoWriter)
   170  			Expect(err).ToNot(HaveOccurred())
   171  			cmd.Wait()
   172  			return stdout.Contents()
   173  		}
   174  
   175  		It("does not leave bridges resources around", func() {
   176  			client = startGarden()
   177  			bridgePrefix := fmt.Sprintf("w%db-", GinkgoParallelNode())
   178  			Expect(allBridges()).ToNot(ContainSubstring(bridgePrefix))
   179  			var err error
   180  			_, err = client.Create(garden.ContainerSpec{
   181  				Env: []string{"hello"}})
   182  			Expect(err).To(HaveOccurred())
   183  			Expect(err).To(MatchError(HavePrefix("process: malformed environment")))
   184  			//check no bridges are leaked
   185  			Eventually(allBridges).ShouldNot(ContainSubstring(bridgePrefix))
   186  		})
   187  
   188  		It("does not leave network namespaces resources around", func() {
   189  			client = startGarden()
   190  			var err error
   191  			_, err = client.Create(garden.ContainerSpec{
   192  				Env: []string{"hello"}})
   193  			Expect(err).To(HaveOccurred())
   194  			Expect(err).To(MatchError(HavePrefix("process: malformed environment")))
   195  			//check no network namespaces are leaked
   196  			stdout := gbytes.NewBuffer()
   197  			cmd, err := gexec.Start(
   198  				exec.Command(
   199  					"sh",
   200  					"-c",
   201  					"mount -n -t tmpfs tmpfs /sys && ip netns list && umount /sys",
   202  				),
   203  				stdout,
   204  				GinkgoWriter,
   205  			)
   206  			Expect(err).ToNot(HaveOccurred())
   207  			Expect(cmd.Wait("1s").ExitCode()).To(Equal(0))
   208  			Expect(stdout.Contents()).To(Equal([]byte{}))
   209  		})
   210  	})
   211  
   212  	Context("when the container fails to start", func() {
   213  		It("does not leave resources around", func() {
   214  			client = startGarden()
   215  			client.Create(garden.ContainerSpec{
   216  				BindMounts: []garden.BindMount{{
   217  					SrcPath: "fictional",
   218  					DstPath: "whereami",
   219  				}},
   220  			})
   221  
   222  			depotDir := filepath.Join(
   223  				os.TempDir(),
   224  				fmt.Sprintf("test-garden-%d", GinkgoParallelNode()),
   225  				"containers",
   226  			)
   227  			Expect(ioutil.ReadDir(depotDir)).To(HaveLen(0))
   228  		})
   229  	})
   230  
   231  	Context("when the container is created succesfully", func() {
   232  		var container garden.Container
   233  
   234  		var privilegedContainer bool
   235  		var rootfs string
   236  
   237  		JustBeforeEach(func() {
   238  			client = startGarden()
   239  
   240  			var err error
   241  			container, err = client.Create(garden.ContainerSpec{Privileged: privilegedContainer, RootFSPath: rootfs})
   242  			Expect(err).ToNot(HaveOccurred())
   243  		})
   244  
   245  		AfterEach(func() {
   246  			if container != nil {
   247  				Expect(client.Destroy(container.Handle())).To(Succeed())
   248  			}
   249  		})
   250  
   251  		BeforeEach(func() {
   252  			privilegedContainer = false
   253  			rootfs = ""
   254  		})
   255  
   256  		Context("when the rootfs is a symlink", func() {
   257  			var symlinkDir string
   258  
   259  			BeforeEach(func() {
   260  				symlinkDir, err := ioutil.TempDir("", "test-symlink")
   261  				Expect(err).ToNot(HaveOccurred())
   262  
   263  				rootfs = path.Join(symlinkDir, "rootfs")
   264  
   265  				err = os.Symlink(runner.RootFSPath, rootfs)
   266  				Expect(err).ToNot(HaveOccurred())
   267  			})
   268  
   269  			AfterEach(func() {
   270  				os.RemoveAll(symlinkDir)
   271  			})
   272  
   273  			It("follows the symlink", func() {
   274  				stdout := gbytes.NewBuffer()
   275  
   276  				process, err := container.Run(garden.ProcessSpec{
   277  					User: "alice",
   278  					Path: "ls",
   279  					Args: []string{"/"},
   280  				}, garden.ProcessIO{
   281  					Stdout: stdout,
   282  				})
   283  				Expect(err).ToNot(HaveOccurred())
   284  				Expect(process.Wait()).To(BeZero())
   285  
   286  				Expect(stdout).To(gbytes.Say("bin"))
   287  			})
   288  		})
   289  
   290  		Context("and running a process", func() {
   291  			It("does not leak open files", func() {
   292  				openFileCount := func() int {
   293  					procFd := fmt.Sprintf("/proc/%d/fd", client.Pid)
   294  					files, err := ioutil.ReadDir(procFd)
   295  					Expect(err).ToNot(HaveOccurred())
   296  
   297  					return len(files)
   298  				}
   299  
   300  				initialOpenFileCount := openFileCount()
   301  
   302  				for i := 0; i < 50; i++ {
   303  					process, err := container.Run(garden.ProcessSpec{
   304  						User: "alice",
   305  						Path: "true",
   306  					}, garden.ProcessIO{})
   307  					Expect(err).ToNot(HaveOccurred())
   308  					Expect(process.Wait()).To(Equal(0))
   309  				}
   310  
   311  				// there's some noise in 'open files' check, but it shouldn't grow
   312  				// linearly with the number of processes spawned
   313  				Eventually(openFileCount, "10s").Should(BeNumerically("<", initialOpenFileCount+10))
   314  			})
   315  		})
   316  
   317  		Context("after destroying the container", func() {
   318  			It("should return api.ContainerNotFoundError when deleting the container again", func() {
   319  				Expect(client.Destroy(container.Handle())).To(Succeed())
   320  				Expect(client.Destroy(container.Handle())).To(MatchError(garden.ContainerNotFoundError{container.Handle()}))
   321  				container = nil
   322  			})
   323  
   324  			It("should ensure any iptables rules which were created no longer exist", func() {
   325  				handle := container.Handle()
   326  				Expect(client.Destroy(handle)).To(Succeed())
   327  				container = nil
   328  
   329  				iptables, err := gexec.Start(exec.Command("iptables", "-L"), GinkgoWriter, GinkgoWriter)
   330  				Expect(err).ToNot(HaveOccurred())
   331  				Eventually(iptables, "10s").Should(gexec.Exit())
   332  				Expect(iptables).ToNot(gbytes.Say(handle))
   333  			})
   334  
   335  			It("destroys multiple containers based on same rootfs", func() {
   336  				c1, err := client.Create(garden.ContainerSpec{
   337  					RootFSPath: "docker:///busybox",
   338  					Privileged: false,
   339  				})
   340  				Expect(err).ToNot(HaveOccurred())
   341  				c2, err := client.Create(garden.ContainerSpec{
   342  					RootFSPath: "docker:///busybox",
   343  					Privileged: false,
   344  				})
   345  				Expect(err).ToNot(HaveOccurred())
   346  
   347  				Expect(client.Destroy(c1.Handle())).To(Succeed())
   348  				Expect(client.Destroy(c2.Handle())).To(Succeed())
   349  			})
   350  
   351  			It("should not leak network namespace", func() {
   352  				info, err := container.Info()
   353  				Expect(err).ToNot(HaveOccurred())
   354  				Expect(info.State).To(Equal("active"))
   355  
   356  				pidPath := filepath.Join(info.ContainerPath, "run", "wshd.pid")
   357  
   358  				_, err = ioutil.ReadFile(pidPath)
   359  				Expect(err).ToNot(HaveOccurred())
   360  
   361  				Expect(client.Destroy(container.Handle())).To(Succeed())
   362  				container = nil
   363  
   364  				stdout := gbytes.NewBuffer()
   365  				cmd, err := gexec.Start(
   366  					exec.Command(
   367  						"sh",
   368  						"-c",
   369  						"mount -n -t tmpfs tmpfs /sys && ip netns list && umount /sys",
   370  					),
   371  					stdout,
   372  					GinkgoWriter,
   373  				)
   374  
   375  				Expect(err).ToNot(HaveOccurred())
   376  				Expect(cmd.Wait("2s").ExitCode()).To(Equal(0))
   377  				Expect(stdout.Contents()).To(Equal([]byte{}))
   378  			})
   379  		})
   380  	})
   381  })