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