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 })