github.com/cloudfoundry-attic/garden-linux@v0.333.2-candidate/integration/lifecycle/rootfs_test.go (about) 1 package lifecycle_test 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "net/http" 7 "net/http/httptest" 8 "net/http/httputil" 9 "net/url" 10 "os" 11 "os/exec" 12 "path/filepath" 13 14 "github.com/cloudfoundry-incubator/garden" 15 16 . "github.com/onsi/ginkgo" 17 . "github.com/onsi/gomega" 18 "github.com/onsi/gomega/types" 19 ) 20 21 var ( 22 dockerRegistryRootFSPath = os.Getenv("GARDEN_DOCKER_REGISTRY_TEST_ROOTFS") 23 dockerRegistryV2RootFSPath = os.Getenv("GARDEN_DOCKER_REGISTRY_V2_TEST_ROOTFS") 24 ) 25 26 var _ = Describe("Rootfs container create parameter", func() { 27 var container garden.Container 28 var args []string 29 30 BeforeEach(func() { 31 container = nil 32 args = []string{} 33 }) 34 35 JustBeforeEach(func() { 36 client = startGarden(args...) 37 }) 38 39 AfterEach(func() { 40 if container != nil { 41 Expect(client.Destroy(container.Handle())).To(Succeed()) 42 } 43 }) 44 45 Context("without a default rootfs", func() { 46 BeforeEach(func() { 47 args = []string{"--rootfs", ""} 48 }) 49 50 It("without a rootfs in container spec, the container creation fails", func() { 51 var err error 52 53 container, err = client.Create(garden.ContainerSpec{RootFSPath: ""}) 54 Ω(err).Should(HaveOccurred()) 55 Ω(err).Should(MatchError(ContainSubstring( 56 "RootFSPath: is a required parameter, since no default rootfs was provided to the server. To provide a default rootfs, use the --rootfs flag on startup.", 57 ))) 58 }) 59 60 It("with a rootfs in container spec, the container is created successfully", func() { 61 var err error 62 63 container, err = client.Create(garden.ContainerSpec{RootFSPath: os.Getenv("GARDEN_TEST_ROOTFS")}) 64 Ω(err).ShouldNot(HaveOccurred()) 65 }) 66 }) 67 68 Context("with a default rootfs", func() { 69 It("the container is created successfully", func() { 70 var err error 71 72 container, err = client.Create(garden.ContainerSpec{RootFSPath: ""}) 73 Expect(err).ToNot(HaveOccurred()) 74 }) 75 }) 76 77 Context("with a docker rootfs URI", func() { 78 Context("not containing a host", func() { 79 It("succesfully creates the container", func() { 80 var err error 81 82 container, err = client.Create(garden.ContainerSpec{RootFSPath: "docker:///busybox"}) 83 Expect(err).ToNot(HaveOccurred()) 84 }) 85 86 Context("when image does not exist", func() { 87 It("should not leak the depot directory", func() { 88 _, err := client.Create( 89 garden.ContainerSpec{ 90 RootFSPath: "docker:///cfgarden/doesnotexist", 91 }, 92 ) 93 Expect(err).To(HaveOccurred()) 94 95 entries, err := ioutil.ReadDir(client.DepotPath) 96 Expect(err).ToNot(HaveOccurred()) 97 Expect(entries).To(HaveLen(0)) 98 }) 99 }) 100 101 Context("when the -registry flag targets a non-existing registry", func() { 102 BeforeEach(func() { 103 args = []string{"--registry", "registry-12.banana-docker.io"} 104 }) 105 106 It("should fail to create a container", func() { 107 var err error 108 109 container, err = client.Create(garden.ContainerSpec{RootFSPath: "docker:///busybox"}) 110 Expect(err).To(HaveOccurred()) 111 }) 112 }) 113 }) 114 115 Context("containing a host", func() { 116 Context("which is valid", func() { 117 It("creates the container successfully", func() { 118 var err error 119 120 container, err = client.Create(garden.ContainerSpec{RootFSPath: "docker://registry-1.docker.io/busybox"}) 121 Expect(err).ToNot(HaveOccurred()) 122 }) 123 }) 124 125 Context("which is invalid", func() { 126 It("the container is not created successfully", func() { 127 var err error 128 container, err = client.Create(garden.ContainerSpec{RootFSPath: "docker://xindex.docker.io/busybox"}) 129 Expect(err).To(HaveOccurred()) 130 }) 131 }) 132 133 Context("which is insecure", func() { 134 var ( 135 dockerRegistry garden.Container 136 dockerRegistryIP string 137 dockerRegistryPort string 138 ) 139 140 BeforeEach(func() { 141 dockerRegistryIP = "10.0.0.2" 142 dockerRegistryPort = "5000" 143 }) 144 145 JustBeforeEach(func() { 146 if dockerRegistryV2RootFSPath == "" { 147 Skip("GARDEN_DOCKER_REGISTRY_V2_TEST_ROOTFS undefined") 148 } 149 150 dockerRegistry = startV2DockerRegistry(dockerRegistryIP, dockerRegistryPort) 151 }) 152 153 AfterEach(func() { 154 if dockerRegistry != nil { 155 Expect(client.Destroy(dockerRegistry.Handle())).To(Succeed()) 156 } 157 }) 158 159 Context("when the host is listed in -insecureDockerRegistry", func() { 160 BeforeEach(func() { 161 args = []string{ 162 "-allowHostAccess=true", 163 } 164 }) 165 166 Context("when the registry is NOT using TLS", func() { 167 BeforeEach(func() { 168 args = append( 169 args, 170 "-insecureDockerRegistry", 171 fmt.Sprintf("%s:%s", dockerRegistryIP, dockerRegistryPort), 172 ) 173 }) 174 175 It("creates the container successfully ", func() { 176 _, err := client.Create(garden.ContainerSpec{ 177 RootFSPath: fmt.Sprintf("docker://%s:%s/busybox", dockerRegistryIP, 178 dockerRegistryPort), 179 }) 180 Expect(err).ToNot(HaveOccurred()) 181 }) 182 }) 183 184 Context("when the registry is in a CIDR", func() { 185 BeforeEach(func() { 186 args = append( 187 args, 188 "-insecureDockerRegistry", 189 fmt.Sprintf("%s/24", dockerRegistryIP), 190 ) 191 }) 192 193 It("creates the container successfully ", func() { 194 _, err := client.Create(garden.ContainerSpec{ 195 RootFSPath: fmt.Sprintf("docker://%s:%s/busybox", dockerRegistryIP, dockerRegistryPort), 196 }) 197 Expect(err).ToNot(HaveOccurred()) 198 }) 199 }) 200 201 Context("when the registry is using TLS", func() { 202 var server *httptest.Server 203 var serverURL *url.URL 204 205 BeforeEach(func() { 206 proxyTo, err := url.Parse(fmt.Sprintf("http://%s:%s", dockerRegistryIP, 207 dockerRegistryPort)) 208 Expect(err).NotTo(HaveOccurred()) 209 210 server = httptest.NewTLSServer(httputil.NewSingleHostReverseProxy(proxyTo)) 211 serverURL, err = url.Parse(server.URL) 212 Expect(err).NotTo(HaveOccurred()) 213 214 args = append( 215 args, 216 "-insecureDockerRegistry", 217 serverURL.Host, 218 ) 219 }) 220 221 AfterEach(func() { 222 server.Close() 223 }) 224 225 It("creates the container successfully", func() { 226 _, err := client.Create(garden.ContainerSpec{ 227 RootFSPath: fmt.Sprintf("docker://%s/busybox", serverURL.Host), 228 }) 229 Expect(err).ToNot(HaveOccurred()) 230 }) 231 232 Context("and its specified as --registry", func() { 233 BeforeEach(func() { 234 args = append(args, "--registry", serverURL.Host) 235 }) 236 237 It("still works when the host is specified", func() { 238 _, err := client.Create(garden.ContainerSpec{ 239 RootFSPath: fmt.Sprintf("docker://%s/busybox", serverURL.Host), 240 }) 241 Expect(err).ToNot(HaveOccurred()) 242 }) 243 244 It("still works using the default host", func() { 245 _, err := client.Create(garden.ContainerSpec{ 246 RootFSPath: fmt.Sprintf("docker:///busybox"), 247 }) 248 Expect(err).ToNot(HaveOccurred()) 249 }) 250 }) 251 }) 252 }) 253 254 Context("when the host is NOT listed in -insecureDockerRegistry", func() { 255 It("fails", func() { 256 _, err := client.Create(garden.ContainerSpec{ 257 RootFSPath: fmt.Sprintf("docker://%s:%s/busybox", dockerRegistryIP, 258 dockerRegistryPort), 259 }) 260 261 Expect(err).To(HaveOccurred()) 262 }) 263 }) 264 }) 265 }) 266 }) 267 268 Context("when the modified timestamp of the rootfs top-level directory changes", func() { 269 var ( 270 rootfspath string 271 privilegedContainer bool 272 container2 garden.Container 273 ) 274 275 JustBeforeEach(func() { 276 var err error 277 rootfspath = createSmallRootfs() 278 279 container, err = client.Create(garden.ContainerSpec{ 280 RootFSPath: rootfspath, 281 Privileged: privilegedContainer, 282 }) 283 Expect(err).NotTo(HaveOccurred()) 284 285 // ls is convenient, but any file modification is sufficient 286 ls := filepath.Join(rootfspath, "bin", "ls") 287 Expect(exec.Command("cp", ls, rootfspath).Run()).To(Succeed()) 288 289 container2, err = client.Create(garden.ContainerSpec{ 290 RootFSPath: rootfspath, 291 Privileged: privilegedContainer, 292 }) 293 Expect(err).NotTo(HaveOccurred()) 294 }) 295 296 AfterEach(func() { 297 if container2 != nil { 298 Expect(client.Destroy(container2.Handle())).To(Succeed()) 299 } 300 }) 301 302 Context("with a non-privileged container", func() { 303 BeforeEach(func() { 304 privilegedContainer = false 305 }) 306 307 It("should use the updated rootfs when creating a new container", func() { 308 process, err := container2.Run(garden.ProcessSpec{ 309 Path: "/ls", 310 User: "root", 311 }, garden.ProcessIO{Stdout: GinkgoWriter, Stderr: GinkgoWriter}) 312 Expect(err).NotTo(HaveOccurred()) 313 314 exitStatus, err := process.Wait() 315 Expect(err).NotTo(HaveOccurred()) 316 Expect(exitStatus).To(Equal(0)) 317 }) 318 }) 319 }) 320 321 }) 322 323 func startV2DockerRegistry(dockerRegistryIP string, dockerRegistryPort string) garden.Container { 324 dockerRegistry, err := client.Create( 325 garden.ContainerSpec{ 326 RootFSPath: dockerRegistryV2RootFSPath, 327 Network: dockerRegistryIP, 328 }, 329 ) 330 Expect(err).ToNot(HaveOccurred()) 331 332 _, err = dockerRegistry.Run(garden.ProcessSpec{ 333 User: "root", 334 Env: []string{ 335 "REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/opt/docker-registry", 336 }, 337 Path: "/go/bin/registry", 338 Args: []string{"/go/src/github.com/docker/distribution/cmd/registry/config.yml"}, 339 }, garden.ProcessIO{Stdout: GinkgoWriter, Stderr: GinkgoWriter}) 340 Expect(err).ToNot(HaveOccurred()) 341 342 Eventually( 343 fmt.Sprintf("http://%s:%s/v2/", dockerRegistryIP, dockerRegistryPort), 344 "60s", 345 ).Should(RespondToGETWith(200)) 346 347 return dockerRegistry 348 } 349 350 type statusMatcher struct { 351 expectedStatus int 352 353 httpError error 354 actualStatus int 355 } 356 357 func RespondToGETWith(expected int) types.GomegaMatcher { 358 return &statusMatcher{expected, nil, 200} 359 } 360 361 func (m *statusMatcher) Match(actual interface{}) (success bool, err error) { 362 response, err := http.Get(fmt.Sprintf("%s", actual)) 363 if err != nil { 364 m.httpError = err 365 return false, nil 366 } 367 368 m.httpError = nil 369 m.actualStatus = response.StatusCode 370 return response.StatusCode == m.expectedStatus, nil 371 } 372 373 func (m *statusMatcher) FailureMessage(actual interface{}) string { 374 if m.httpError != nil { 375 return fmt.Sprintf("Expected http request to have status %d but got error: %s", m.expectedStatus, m.httpError.Error()) 376 } 377 378 return fmt.Sprintf("Expected http status code to be %d but was %d", m.expectedStatus, m.actualStatus) 379 } 380 381 func (m *statusMatcher) NegatedFailureMessage(actual interface{}) string { 382 if m.httpError != nil { 383 return fmt.Sprintf("Expected http request to have status %d, but got error: %s", m.expectedStatus, m.httpError.Error()) 384 } 385 386 return fmt.Sprintf("Expected http status code not to be %d", m.expectedStatus) 387 } 388 389 func createSmallRootfs() string { 390 rootfs := os.Getenv("GARDEN_PREEXISTING_USERS_TEST_ROOTFS") 391 if rootfs == "" { 392 Skip("pre-existing users rootfs not found") 393 } 394 395 rootfspath, err := ioutil.TempDir("", "rootfs-cache-invalidation") 396 Expect(err).NotTo(HaveOccurred()) 397 cmd := exec.Command("cp", "-rf", rootfs, rootfspath) 398 cmd.Stdout = GinkgoWriter 399 cmd.Stderr = GinkgoWriter 400 Expect(cmd.Run()).To(Succeed()) 401 return filepath.Join(rootfspath, filepath.Base(rootfs)) 402 }