github.com/cloudfoundry-attic/garden-linux@v0.333.2-candidate/resource_pool/resource_pool_test.go (about) 1 package resource_pool_test 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "net" 11 "net/url" 12 "os" 13 "os/exec" 14 "path" 15 "path/filepath" 16 "runtime/pprof" 17 "time" 18 19 "github.com/blang/semver" 20 . "github.com/onsi/ginkgo" 21 . "github.com/onsi/gomega" 22 "github.com/onsi/gomega/gbytes" 23 "github.com/pivotal-golang/lager/lagertest" 24 25 "github.com/cloudfoundry-incubator/garden" 26 "github.com/cloudfoundry-incubator/garden-linux/linux_backend" 27 "github.com/cloudfoundry-incubator/garden-linux/linux_container" 28 "github.com/cloudfoundry-incubator/garden-linux/linux_container/fake_iptables_manager" 29 "github.com/cloudfoundry-incubator/garden-linux/linux_container/fake_quota_manager" 30 "github.com/cloudfoundry-incubator/garden-linux/network/bridgemgr/fake_bridge_manager" 31 "github.com/cloudfoundry-incubator/garden-linux/network/fakes" 32 "github.com/cloudfoundry-incubator/garden-linux/network/iptables" 33 "github.com/cloudfoundry-incubator/garden-linux/network/subnets" 34 "github.com/cloudfoundry-incubator/garden-linux/port_pool/fake_port_pool" 35 "github.com/cloudfoundry-incubator/garden-linux/resource_pool" 36 "github.com/cloudfoundry-incubator/garden-linux/resource_pool/fake_filter_provider" 37 "github.com/cloudfoundry-incubator/garden-linux/resource_pool/fake_mkdir_chowner" 38 "github.com/cloudfoundry-incubator/garden-linux/resource_pool/fake_rootfs_cleaner" 39 "github.com/cloudfoundry-incubator/garden-linux/resource_pool/fake_rootfs_provider" 40 "github.com/cloudfoundry-incubator/garden-linux/resource_pool/fake_subnet_pool" 41 "github.com/cloudfoundry-incubator/garden-linux/sysconfig" 42 "github.com/cloudfoundry-incubator/garden-shed/layercake" 43 "github.com/cloudfoundry-incubator/garden-shed/rootfs_provider" 44 "github.com/cloudfoundry/gunk/command_runner/fake_command_runner" 45 . "github.com/cloudfoundry/gunk/command_runner/fake_command_runner/matchers" 46 ) 47 48 var _ = Describe("Container pool", func() { 49 50 var ( 51 depotPath string 52 fakeRunner *fake_command_runner.FakeCommandRunner 53 fakeSubnetPool *fake_subnet_pool.FakeSubnetPool 54 fakeQuotaManager *fake_quota_manager.FakeQuotaManager 55 fakePortPool *fake_port_pool.FakePortPool 56 fakeRootFSProvider *fake_rootfs_provider.FakeRootFSProvider 57 fakeRootFSCleaner *fake_rootfs_cleaner.FakeRootFSCleaner 58 fakeBridges *fake_bridge_manager.FakeBridgeManager 59 fakeIPTablesManager *fake_iptables_manager.FakeIPTablesManager 60 fakeFilterProvider *fake_filter_provider.FakeFilterProvider 61 fakeFilter *fakes.FakeFilter 62 pool *resource_pool.LinuxResourcePool 63 config sysconfig.Config 64 containerNetwork *linux_backend.Network 65 defaultVersion string 66 logger *lagertest.TestLogger 67 fakeMkdirChowner *fake_mkdir_chowner.FakeMkdirChowner 68 ) 69 70 BeforeEach(func() { 71 fakeSubnetPool = new(fake_subnet_pool.FakeSubnetPool) 72 73 var err error 74 containerNetwork = &linux_backend.Network{} 75 containerNetwork.IP, containerNetwork.Subnet, err = net.ParseCIDR("10.2.0.2/30") 76 Expect(err).ToNot(HaveOccurred()) 77 fakeSubnetPool.AcquireReturns(containerNetwork, nil) 78 79 fakeBridges = new(fake_bridge_manager.FakeBridgeManager) 80 fakeIPTablesManager = new(fake_iptables_manager.FakeIPTablesManager) 81 82 fakeBridges.ReserveStub = func(n *net.IPNet, c string) (string, error) { 83 return fmt.Sprintf("bridge-for-%s-%s", n, c), nil 84 } 85 86 fakeFilter = new(fakes.FakeFilter) 87 fakeFilterProvider = new(fake_filter_provider.FakeFilterProvider) 88 fakeFilterProvider.ProvideFilterReturns(fakeFilter) 89 90 fakeRunner = fake_command_runner.New() 91 fakeQuotaManager = new(fake_quota_manager.FakeQuotaManager) 92 fakePortPool = fake_port_pool.New(1000) 93 fakeRootFSProvider = new(fake_rootfs_provider.FakeRootFSProvider) 94 fakeRootFSCleaner = new(fake_rootfs_cleaner.FakeRootFSCleaner) 95 96 defaultVersion = "1.0.0" 97 fakeRootFSProvider.CreateReturns("/provided/rootfs/path", nil, nil) 98 99 depotPath, err = ioutil.TempDir("", "depot-path") 100 Expect(err).ToNot(HaveOccurred()) 101 102 currentContainerVersion, err := semver.Make("1.0.0") 103 Expect(err).ToNot(HaveOccurred()) 104 105 config = sysconfig.NewConfig("0", false) 106 logger = lagertest.NewTestLogger("test") 107 fakeMkdirChowner = new(fake_mkdir_chowner.FakeMkdirChowner) 108 pool = resource_pool.New( 109 logger, 110 "/root/path", 111 depotPath, 112 config, 113 fakeRootFSProvider, 114 fakeRootFSCleaner, 115 rootfs_provider.MappingList{ 116 { 117 ContainerID: 0, 118 HostID: 700000, 119 Size: 65536, 120 }, 121 }, 122 net.ParseIP("1.2.3.4"), 123 345, 124 fakeSubnetPool, 125 fakeBridges, 126 fakeIPTablesManager, 127 fakeFilterProvider, 128 iptables.NewGlobalChain("global-default-chain", fakeRunner, logger), 129 fakePortPool, 130 []string{"1.1.0.0/16", "", "2.2.0.0/16"}, // empty string to test that this is ignored 131 []string{"1.1.1.1/32", "", "2.2.2.2/32"}, 132 fakeRunner, 133 fakeQuotaManager, 134 currentContainerVersion, 135 fakeMkdirChowner, 136 ) 137 }) 138 139 AfterEach(func() { 140 os.RemoveAll(depotPath) 141 }) 142 143 Describe("MaxContainer", func() { 144 Context("when constrained by network pool size", func() { 145 BeforeEach(func() { 146 fakeSubnetPool.CapacityReturns(5) 147 }) 148 149 It("returns the network pool size", func() { 150 Expect(pool.MaxContainers()).To(Equal(5)) 151 }) 152 }) 153 }) 154 155 Describe("Setup", func() { 156 It("executes setup.sh with the correct environment", func() { 157 err := pool.Setup() 158 Expect(err).ToNot(HaveOccurred()) 159 160 Expect(fakeRunner).To(HaveExecutedSerially( 161 fake_command_runner.CommandSpec{ 162 Path: "/root/path/setup.sh", 163 Env: []string{ 164 "CONTAINER_DEPOT_PATH=" + depotPath, 165 "PATH=" + os.Getenv("PATH"), 166 }, 167 }, 168 )) 169 }) 170 171 Context("when setup.sh fails", func() { 172 nastyError := errors.New("oh no!") 173 174 BeforeEach(func() { 175 fakeRunner.WhenRunning( 176 fake_command_runner.CommandSpec{ 177 Path: "/root/path/setup.sh", 178 }, func(*exec.Cmd) error { 179 return nastyError 180 }, 181 ) 182 }) 183 184 It("returns the error", func() { 185 err := pool.Setup() 186 Expect(err).To(Equal(nastyError)) 187 }) 188 }) 189 190 It("enables disk quotas", func() { 191 Expect(pool.Setup()).To(Succeed()) 192 Expect(fakeQuotaManager.SetupCallCount()).To(Equal(1)) 193 }) 194 195 Context("when setting up disk quotas fails", func() { 196 It("returns the error", func() { 197 fakeQuotaManager.SetupReturns(errors.New("cant cook wont cook")) 198 err := pool.Setup() 199 Expect(err).To(MatchError("resource_pool: enable disk quotas: cant cook wont cook")) 200 }) 201 }) 202 203 Describe("Setting up IPTables", func() { 204 It("sets up global allow and deny rules, adding allow before deny", func() { 205 err := pool.Setup() 206 Expect(err).ToNot(HaveOccurred()) 207 208 Expect(fakeRunner).To(HaveExecutedSerially( 209 fake_command_runner.CommandSpec{ 210 Path: "/root/path/setup.sh", // must run iptables rules after setup.sh 211 }, 212 fake_command_runner.CommandSpec{ 213 Path: "/sbin/iptables", 214 Args: []string{"-w", "-A", "global-default-chain", "--destination", "1.1.1.1/32", "--jump", "RETURN"}, 215 }, 216 fake_command_runner.CommandSpec{ 217 Path: "/sbin/iptables", 218 Args: []string{"-w", "-A", "global-default-chain", "--destination", "2.2.2.2/32", "--jump", "RETURN"}, 219 }, 220 fake_command_runner.CommandSpec{ 221 Path: "/sbin/iptables", 222 Args: []string{"-w", "-A", "global-default-chain", "--destination", "1.1.0.0/16", "--jump", "REJECT"}, 223 }, 224 fake_command_runner.CommandSpec{ 225 Path: "/sbin/iptables", 226 Args: []string{"-w", "-A", "global-default-chain", "--destination", "2.2.0.0/16", "--jump", "REJECT"}, 227 }, 228 )) 229 }) 230 231 Context("when setting up a rule fails", func() { 232 nastyError := errors.New("oh no!") 233 234 BeforeEach(func() { 235 fakeRunner.WhenRunning( 236 fake_command_runner.CommandSpec{ 237 Path: "/sbin/iptables", 238 }, func(*exec.Cmd) error { 239 return nastyError 240 }, 241 ) 242 }) 243 244 It("returns a wrapped error", func() { 245 err := pool.Setup() 246 Expect(err).To(MatchError("resource_pool: setting up allow rules in iptables: oh no!")) 247 }) 248 }) 249 }) 250 }) 251 252 Describe("creating", func() { 253 itReleasesTheIPBlock := func() { 254 It("returns the container's IP block to the pool", func() { 255 Expect(fakeSubnetPool.ReleaseCallCount()).To(Equal(1)) 256 actualNetwork, _ := fakeSubnetPool.ReleaseArgsForCall(0) 257 Expect(actualNetwork).To(Equal(containerNetwork)) 258 }) 259 } 260 261 itShouldNotLeakContainerDirectory := func() { 262 It("should not leak the container directory", func() { 263 entries, err := ioutil.ReadDir(depotPath) 264 Expect(err).ToNot(HaveOccurred()) 265 Expect(entries).To(HaveLen(0)) 266 }) 267 } 268 269 itRunsTheDestroyScript := func() { 270 It("runs the destroy script", func() { 271 executedCommands := fakeRunner.ExecutedCommands() 272 273 createCommand := executedCommands[0] 274 Expect(createCommand.Path).To(Equal("/root/path/create.sh")) 275 containerPath := createCommand.Args[1] 276 277 lastCommand := executedCommands[len(executedCommands)-1] 278 Expect(lastCommand.Path).To(Equal("/root/path/destroy.sh")) 279 Expect(lastCommand.Args[1]).To(Equal(containerPath)) 280 281 Expect(fakeIPTablesManager.ContainerTeardownCallCount()).To(Equal(1)) 282 }) 283 } 284 285 itCleansUpTheRootfs := func() { 286 It("cleans up the rootfs for the container", func() { 287 Expect(fakeRootFSProvider.DestroyCallCount()).To(Equal(1)) 288 _, providedID, _ := fakeRootFSProvider.CreateArgsForCall(0) 289 _, cleanedUpID := fakeRootFSProvider.DestroyArgsForCall(0) 290 Expect(cleanedUpID).To(Equal(providedID)) 291 }) 292 } 293 294 itReleasesAndDestroysTheBridge := func() { 295 It("releases the bridge", func() { 296 Expect(fakeBridges.ReleaseCallCount()).To(Equal(1)) 297 _, containerId := fakeBridges.ReserveArgsForCall(0) 298 299 Expect(fakeBridges.ReleaseCallCount()).To(Equal(1)) 300 bridgeName, releasedContainerId := fakeBridges.ReleaseArgsForCall(0) 301 Expect(bridgeName).To(Equal("bridge-for-10.2.0.0/30-" + containerId)) 302 Expect(releasedContainerId).To(Equal(containerId)) 303 }) 304 } 305 306 itTearsDownTheIPTableFilters := func() { 307 It("tears down the IP table filters", func() { 308 Expect(fakeFilterProvider.ProvideFilterCallCount()).To(Equal(2)) // one to setup, one to teae down 309 Expect(fakeFilter.TearDownCallCount()).To(Equal(1)) 310 }) 311 312 It("does not leak the iptable setup goroutine", func() { 313 Eventually(func() []byte { 314 buffer := gbytes.NewBuffer() 315 defer buffer.Close() 316 Expect(pprof.Lookup("goroutine").WriteTo(buffer, 1)).To(Succeed()) 317 318 return buffer.Contents() 319 }).ShouldNot(ContainSubstring("Acquire")) 320 }) 321 } 322 323 It("returns containers with unique IDs", func() { 324 containerSpec1, err := pool.Acquire(garden.ContainerSpec{}) 325 Expect(err).ToNot(HaveOccurred()) 326 327 containerSpec2, err := pool.Acquire(garden.ContainerSpec{}) 328 Expect(err).ToNot(HaveOccurred()) 329 330 Expect(containerSpec1.ID).ToNot(Equal(containerSpec2.ID)) 331 }) 332 333 It("creates containers with the correct grace time", func() { 334 containerSpec, err := pool.Acquire(garden.ContainerSpec{ 335 GraceTime: 1 * time.Second, 336 }) 337 Expect(err).ToNot(HaveOccurred()) 338 339 Expect(containerSpec.GraceTime).To(Equal(1 * time.Second)) 340 }) 341 342 It("creates containers with the correct properties", func() { 343 properties := garden.Properties(map[string]string{ 344 "foo": "bar", 345 }) 346 347 containerSpec, err := pool.Acquire(garden.ContainerSpec{ 348 Properties: properties, 349 }) 350 Expect(err).ToNot(HaveOccurred()) 351 352 Expect(containerSpec.Properties).To(Equal(properties)) 353 }) 354 355 It("sets up iptable filters for the container", func() { 356 containerSpec, err := pool.Acquire(garden.ContainerSpec{Handle: "test-handle"}) 357 Expect(err).ToNot(HaveOccurred()) 358 359 Expect(fakeFilterProvider.ProvideFilterCallCount()).To(BeNumerically(">", 0)) 360 Expect(fakeFilterProvider.ProvideFilterArgsForCall(0)).To(Equal(containerSpec.ID)) 361 Expect(fakeFilter.SetupCallCount()).To(Equal(1)) 362 Expect(fakeFilter.SetupArgsForCall(0)).To(Equal("test-handle")) 363 }) 364 365 Describe("Disk limit", func() { 366 It("should create a rootfs provider with the container's disk quota", func() { 367 _, err := pool.Acquire(garden.ContainerSpec{ 368 Limits: garden.Limits{ 369 Disk: garden.DiskLimits{ 370 ByteHard: 98765, 371 Scope: garden.DiskLimitScopeExclusive, 372 }, 373 }, 374 }) 375 Expect(err).ToNot(HaveOccurred()) 376 377 Expect(fakeRootFSProvider.CreateCallCount()).To(Equal(1)) 378 _, _, spec := fakeRootFSProvider.CreateArgsForCall(0) 379 Expect(spec.QuotaSize).To(Equal(int64(98765))) 380 Expect(spec.QuotaScope).To(Equal(rootfs_provider.QuotaScopeExclusive)) 381 }) 382 }) 383 384 Context("when setting up iptables fails", func() { 385 var err error 386 387 BeforeEach(func() { 388 fakeFilter.SetupReturns(errors.New("iptables says no")) 389 _, err = pool.Acquire(garden.ContainerSpec{}) 390 Expect(err).To(HaveOccurred()) 391 }) 392 393 It("returns a wrapped error", func() { 394 Expect(err).To(MatchError("resource_pool: set up filter: iptables says no")) 395 }) 396 397 itReleasesTheIPBlock() 398 itCleansUpTheRootfs() 399 itRunsTheDestroyScript() 400 }) 401 402 Context("in an unprivileged container", func() { 403 It("executes create.sh with a translated rootfs", func() { 404 _, err := pool.Acquire(garden.ContainerSpec{Privileged: false}) 405 Expect(err).ToNot(HaveOccurred()) 406 407 Expect(fakeRootFSProvider.CreateCallCount()).To(Equal(1)) 408 _, _, spec := fakeRootFSProvider.CreateArgsForCall(0) 409 Expect(spec.Namespaced).To(Equal(true)) 410 }) 411 412 It("always executes create.sh with a root_uid of 10001", func() { 413 for i := 0; i < 2; i++ { 414 container, err := pool.Acquire(garden.ContainerSpec{Privileged: false}) 415 Expect(err).ToNot(HaveOccurred()) 416 417 Expect(fakeRunner).To(HaveExecutedSerially( 418 fake_command_runner.CommandSpec{ 419 Path: "/root/path/create.sh", 420 Args: []string{path.Join(depotPath, container.ID)}, 421 Env: []string{ 422 "PATH=" + os.Getenv("PATH"), 423 "bridge_iface=bridge-for-10.2.0.0/30-" + container.ID, 424 "container_iface_mtu=345", 425 "external_ip=1.2.3.4", 426 "id=" + container.ID, 427 "network_cidr=10.2.0.0/30", 428 "network_cidr_suffix=30", 429 "network_container_ip=10.2.0.2", 430 "network_host_ip=10.2.0.1", 431 "root_uid=700000", 432 "rootfs_path=/provided/rootfs/path", 433 }, 434 }, 435 )) 436 } 437 }) 438 }) 439 440 Context("when the privileged flag is specified and true", func() { 441 It("executes create.sh with a root_uid of 0", func() { 442 container, err := pool.Acquire(garden.ContainerSpec{Privileged: true}) 443 Expect(err).ToNot(HaveOccurred()) 444 445 Expect(fakeRunner).To(HaveExecutedSerially( 446 fake_command_runner.CommandSpec{ 447 Path: "/root/path/create.sh", 448 Args: []string{path.Join(depotPath, container.ID)}, 449 Env: []string{ 450 "PATH=" + os.Getenv("PATH"), 451 "bridge_iface=bridge-for-10.2.0.0/30-" + container.ID, 452 "container_iface_mtu=345", 453 "external_ip=1.2.3.4", 454 "id=" + container.ID, 455 "network_cidr=10.2.0.0/30", 456 "network_cidr_suffix=30", 457 "network_container_ip=10.2.0.2", 458 "network_host_ip=10.2.0.1", 459 "root_uid=0", 460 "rootfs_path=/provided/rootfs/path", 461 }, 462 }, 463 )) 464 }) 465 }) 466 467 Context("when no Network parameter is specified", func() { 468 It("executes create.sh with the correct args and environment", func() { 469 container, err := pool.Acquire(garden.ContainerSpec{}) 470 Expect(err).ToNot(HaveOccurred()) 471 472 Expect(fakeRunner).To(HaveExecutedSerially( 473 fake_command_runner.CommandSpec{ 474 Path: "/root/path/create.sh", 475 Args: []string{path.Join(depotPath, container.ID)}, 476 Env: []string{ 477 "PATH=" + os.Getenv("PATH"), 478 "bridge_iface=bridge-for-10.2.0.0/30-" + container.ID, 479 "container_iface_mtu=345", 480 "external_ip=1.2.3.4", 481 "id=" + container.ID, 482 "network_cidr=10.2.0.0/30", 483 "network_cidr_suffix=30", 484 "network_container_ip=10.2.0.2", 485 "network_host_ip=10.2.0.1", 486 "root_uid=700000", 487 "rootfs_path=/provided/rootfs/path", 488 }, 489 }, 490 )) 491 }) 492 }) 493 494 Context("when the Network parameter is specified", func() { 495 It("executes create.sh with the correct args and environment", func() { 496 differentNetwork := &linux_backend.Network{} 497 differentNetwork.IP, differentNetwork.Subnet, _ = net.ParseCIDR("10.3.0.2/29") 498 fakeSubnetPool.AcquireReturns(differentNetwork, nil) 499 500 container, err := pool.Acquire(garden.ContainerSpec{ 501 Network: "1.3.0.0/30", 502 }) 503 Expect(err).ToNot(HaveOccurred()) 504 505 Expect(fakeRunner).To(HaveExecutedSerially( 506 fake_command_runner.CommandSpec{ 507 Path: "/root/path/create.sh", 508 Args: []string{path.Join(depotPath, container.ID)}, 509 Env: []string{ 510 "PATH=" + os.Getenv("PATH"), 511 "bridge_iface=bridge-for-10.3.0.0/29-" + container.ID, 512 "container_iface_mtu=345", 513 "external_ip=1.2.3.4", 514 "id=" + container.ID, 515 "network_cidr=10.3.0.0/29", 516 "network_cidr_suffix=29", 517 "network_container_ip=10.3.0.2", 518 "network_host_ip=10.3.0.1", 519 "root_uid=700000", 520 "rootfs_path=/provided/rootfs/path", 521 }, 522 }, 523 )) 524 }) 525 526 It("creates the container directory", func() { 527 container, err := pool.Acquire(garden.ContainerSpec{}) 528 Expect(err).To(Succeed()) 529 530 containerDir := path.Join(depotPath, container.ID) 531 _, err = os.Stat(containerDir) 532 Expect(err).ToNot(HaveOccurred()) 533 }) 534 535 Context("when creating the container directory fails", func() { 536 JustBeforeEach(func() { 537 Expect(os.Remove(depotPath)).To(Succeed()) 538 ioutil.WriteFile(depotPath, []byte(""), 0755) 539 }) 540 541 It("returns an error", func() { 542 _, err := pool.Acquire(garden.ContainerSpec{}) 543 Expect(err).To(MatchError(HavePrefix("resource_pool: creating container directory"))) 544 }) 545 }) 546 547 Describe("allocating the requested network", func() { 548 itShouldAcquire := func(subnet subnets.SubnetSelector, ip subnets.IPSelector) { 549 Expect(fakeSubnetPool.AcquireCallCount()).To(Equal(1)) 550 s, i, _ := fakeSubnetPool.AcquireArgsForCall(0) 551 552 Expect(s).To(Equal(subnet)) 553 Expect(i).To(Equal(ip)) 554 } 555 556 Context("when the network string is empty", func() { 557 It("allocates a dynamic subnet and ip", func() { 558 _, err := pool.Acquire(garden.ContainerSpec{Network: ""}) 559 Expect(err).ToNot(HaveOccurred()) 560 561 itShouldAcquire(subnets.DynamicSubnetSelector, subnets.DynamicIPSelector) 562 }) 563 }) 564 565 Context("when the network parameter is not empty", func() { 566 Context("when it contains a prefix length", func() { 567 It("statically allocates the requested subnet ", func() { 568 _, err := pool.Acquire(garden.ContainerSpec{Network: "1.2.3.0/30"}) 569 Expect(err).ToNot(HaveOccurred()) 570 571 _, sn, _ := net.ParseCIDR("1.2.3.0/30") 572 itShouldAcquire(subnets.StaticSubnetSelector{sn}, subnets.DynamicIPSelector) 573 }) 574 }) 575 576 Context("when it does not contain a prefix length", func() { 577 It("statically allocates the requested Network from Subnets as a /30", func() { 578 _, err := pool.Acquire(garden.ContainerSpec{Network: "1.2.3.0"}) 579 Expect(err).ToNot(HaveOccurred()) 580 581 _, sn, _ := net.ParseCIDR("1.2.3.0/30") 582 itShouldAcquire(subnets.StaticSubnetSelector{sn}, subnets.DynamicIPSelector) 583 }) 584 }) 585 586 Context("when the network parameter has non-zero host bits", func() { 587 It("statically allocates an IP address based on the network parameter", func() { 588 _, err := pool.Acquire(garden.ContainerSpec{Network: "1.2.3.1/20"}) 589 Expect(err).ToNot(HaveOccurred()) 590 591 _, sn, _ := net.ParseCIDR("1.2.3.0/20") 592 itShouldAcquire(subnets.StaticSubnetSelector{sn}, subnets.StaticIPSelector{net.ParseIP("1.2.3.1")}) 593 }) 594 }) 595 596 Context("when the network parameter has zero host bits", func() { 597 It("dynamically allocates an IP address", func() { 598 _, err := pool.Acquire(garden.ContainerSpec{Network: "1.2.3.0/24"}) 599 Expect(err).ToNot(HaveOccurred()) 600 601 _, sn, _ := net.ParseCIDR("1.2.3.0/24") 602 itShouldAcquire(subnets.StaticSubnetSelector{sn}, subnets.DynamicIPSelector) 603 }) 604 }) 605 606 Context("when an invalid network string is passed", func() { 607 It("returns an error", func() { 608 _, err := pool.Acquire(garden.ContainerSpec{Network: "not a network"}) 609 Expect(err).To(MatchError("create container: invalid network spec: invalid CIDR address: not a network/30")) 610 }) 611 612 It("does not acquire any resources", func() { 613 Expect(fakePortPool.Acquired).To(HaveLen(0)) 614 Expect(fakeSubnetPool.AcquireCallCount()).To(Equal(0)) 615 }) 616 }) 617 }) 618 }) 619 620 Context("when allocation of the specified Network fails", func() { 621 var err error 622 allocateError := errors.New("allocateError") 623 624 BeforeEach(func() { 625 fakeSubnetPool.AcquireReturns(nil, allocateError) 626 627 _, err = pool.Acquire(garden.ContainerSpec{ 628 Network: "1.2.0.0/30", 629 }) 630 }) 631 632 It("returns the error", func() { 633 Expect(err).To(Equal(allocateError)) 634 }) 635 636 It("does not execute create.sh", func() { 637 Expect(fakeRunner).ToNot(HaveExecutedSerially( 638 fake_command_runner.CommandSpec{ 639 Path: "/root/path/create.sh", 640 }, 641 )) 642 }) 643 644 It("doesn't attempt to release the network if it has not been assigned", func() { 645 Expect(fakeSubnetPool.ReleaseCallCount()).To(Equal(0)) 646 }) 647 }) 648 }) 649 650 It("saves the bridge name to the depot", func() { 651 container, err := pool.Acquire(garden.ContainerSpec{}) 652 Expect(err).ToNot(HaveOccurred()) 653 654 body, err := ioutil.ReadFile(path.Join(depotPath, container.ID, "bridge-name")) 655 Expect(err).ToNot(HaveOccurred()) 656 657 Expect(string(body)).To(Equal("bridge-for-10.2.0.0/30-" + container.ID)) 658 }) 659 660 It("saves the container version to the depot", func() { 661 container, err := pool.Acquire(garden.ContainerSpec{}) 662 Expect(err).ToNot(HaveOccurred()) 663 664 body, err := ioutil.ReadFile(path.Join(depotPath, container.ID, "version")) 665 Expect(err).ToNot(HaveOccurred()) 666 667 Expect(string(body)).To(Equal(defaultVersion)) 668 }) 669 670 It("initializes the container with the current version", func() { 671 spec, err := pool.Acquire(garden.ContainerSpec{}) 672 Expect(err).ToNot(HaveOccurred()) 673 674 Expect(spec.Version).To(Equal(semver.MustParse(defaultVersion))) 675 }) 676 677 It("runs garbage collection", func() { 678 _, err := pool.Acquire(garden.ContainerSpec{}) 679 Expect(err).NotTo(HaveOccurred()) 680 681 Expect(fakeRootFSProvider.GCCallCount()).To(Equal(1)) 682 }) 683 684 Context("when garbage collection fails", func() { 685 It("does NOT return an error", func() { 686 fakeRootFSProvider.GCReturns(errors.New("potato")) 687 688 _, err := pool.Acquire(garden.ContainerSpec{}) 689 Expect(err).NotTo(HaveOccurred()) 690 }) 691 }) 692 693 Context("when a rootfs is specified", func() { 694 It("is used to provide a rootfs", func() { 695 container, err := pool.Acquire(garden.ContainerSpec{ 696 RootFSPath: "fake:///path/to/custom-rootfs", 697 }) 698 Expect(err).ToNot(HaveOccurred()) 699 700 _, id, spec := fakeRootFSProvider.CreateArgsForCall(0) 701 Expect(id).To(Equal(container.ID)) 702 Expect(spec.RootFS).To(Equal(&url.URL{ 703 Scheme: "fake", 704 Host: "", 705 Path: "/path/to/custom-rootfs", 706 })) 707 }) 708 709 It("should clean the rootfs", func() { 710 fakeRootFSProvider.CreateReturns("/path/to/rootfs", []string{}, nil) 711 712 _, err := pool.Acquire(garden.ContainerSpec{ 713 RootFSPath: "fake:///path/to/custom-rootfs", 714 }) 715 Expect(err).ToNot(HaveOccurred()) 716 717 Expect(fakeRootFSCleaner.CleanCallCount()).To(Equal(1)) 718 _, path := fakeRootFSCleaner.CleanArgsForCall(0) 719 Expect(path).To(Equal("/path/to/rootfs")) 720 }) 721 722 Context("when cleaning the rootfs fails", func() { 723 }) 724 725 It("passes the provided rootfs as $rootfs_path to create.sh", func() { 726 fakeRootFSProvider.CreateReturns("/var/some/mount/point", nil, nil) 727 728 _, err := pool.Acquire(garden.ContainerSpec{ 729 RootFSPath: "fake:///path/to/custom-rootfs", 730 }) 731 Expect(err).ToNot(HaveOccurred()) 732 }) 733 734 It("saves the composite rootfs provider to the depot", func() { 735 container, err := pool.Acquire(garden.ContainerSpec{ 736 RootFSPath: "fake:///path/to/custom-rootfs", 737 }) 738 Expect(err).ToNot(HaveOccurred()) 739 740 body, err := ioutil.ReadFile(path.Join(depotPath, container.ID, "rootfs-provider")) 741 Expect(err).ToNot(HaveOccurred()) 742 743 Expect(string(body)).To(Equal("docker-composite")) 744 }) 745 746 It("returns an error if the supplied environment is invalid", func() { 747 _, err := pool.Acquire(garden.ContainerSpec{ 748 Env: []string{ 749 "hello", 750 }, 751 }) 752 Expect(err).To(MatchError(HavePrefix("process: malformed environment"))) 753 }) 754 755 It("merges the env vars associated with the rootfs with those in the spec", func() { 756 fakeRootFSProvider.CreateReturns("/provided/rootfs/path", []string{ 757 "var2=rootfs-value-2", 758 "var3=rootfs-value-3", 759 }, nil) 760 761 containerSpec, err := pool.Acquire(garden.ContainerSpec{ 762 RootFSPath: "fake:///path/to/custom-rootfs", 763 Env: []string{ 764 "var1=spec-value1", 765 "var2=spec-value2", 766 }, 767 }) 768 769 Expect(err).ToNot(HaveOccurred()) 770 Expect(containerSpec.Env).To(Equal([]string{ 771 "var1=spec-value1", 772 "var2=spec-value2", 773 "var3=rootfs-value-3", 774 })) 775 }) 776 777 Context("when the rootfs URL is not valid", func() { 778 var err error 779 780 BeforeEach(func() { 781 _, err = pool.Acquire(garden.ContainerSpec{ 782 RootFSPath: "::::::", 783 }) 784 }) 785 786 It("returns an error", func() { 787 Expect(err).To(BeAssignableToTypeOf(&url.Error{})) 788 }) 789 790 itShouldNotLeakContainerDirectory() 791 792 itReleasesTheIPBlock() 793 794 It("does not acquire a bridge", func() { 795 Expect(fakeBridges.ReserveCallCount()).To(Equal(0)) 796 }) 797 }) 798 799 Context("when providing the mount point fails", func() { 800 var err error 801 providerErr := errors.New("oh no!") 802 803 BeforeEach(func() { 804 fakeRootFSProvider.CreateReturns("", nil, providerErr) 805 806 _, err = pool.Acquire(garden.ContainerSpec{ 807 RootFSPath: "fake:///path/to/custom-rootfs", 808 }) 809 }) 810 811 It("returns the error", func() { 812 Expect(err).To(Equal(providerErr)) 813 }) 814 815 itReleasesTheIPBlock() 816 817 itShouldNotLeakContainerDirectory() 818 819 It("does not acquire a bridge", func() { 820 Expect(fakeBridges.ReserveCallCount()).To(Equal(0)) 821 }) 822 823 It("does not execute create.sh", func() { 824 Expect(fakeRunner).ToNot(HaveExecutedSerially( 825 fake_command_runner.CommandSpec{ 826 Path: "/root/path/create.sh", 827 }, 828 )) 829 }) 830 }) 831 832 Context("when cleaning the rootfs fails", func() { 833 var err error 834 providerErr := errors.New("oh no!") 835 836 BeforeEach(func() { 837 fakeRootFSCleaner.CleanReturns(providerErr) 838 839 _, err = pool.Acquire(garden.ContainerSpec{ 840 RootFSPath: "fake:///path/to/custom-rootfs", 841 }) 842 }) 843 844 It("returns the error", func() { 845 Expect(err).To(Equal(providerErr)) 846 }) 847 848 itReleasesTheIPBlock() 849 850 itShouldNotLeakContainerDirectory() 851 852 It("does not acquire a bridge", func() { 853 Expect(fakeBridges.ReserveCallCount()).To(Equal(0)) 854 }) 855 856 It("does not execute create.sh", func() { 857 Expect(fakeRunner).ToNot(HaveExecutedSerially( 858 fake_command_runner.CommandSpec{ 859 Path: "/root/path/create.sh", 860 }, 861 )) 862 }) 863 }) 864 }) 865 866 Context("when acquiring the bridge fails", func() { 867 var err error 868 BeforeEach(func() { 869 fakeRootFSProvider.CreateReturns("the-rootfs", nil, nil) 870 fakeBridges.ReserveReturns("", errors.New("o no")) 871 _, err = pool.Acquire(garden.ContainerSpec{ 872 RootFSPath: "fake:///path/to/custom-rootfs", 873 }) 874 }) 875 876 It("does not execute create.sh", func() { 877 Expect(fakeRunner).ToNot(HaveExecutedSerially( 878 fake_command_runner.CommandSpec{ 879 Path: "/root/path/create.sh", 880 }, 881 )) 882 }) 883 884 itCleansUpTheRootfs() 885 886 itShouldNotLeakContainerDirectory() 887 888 It("returns an error", func() { 889 Expect(err).To(HaveOccurred()) 890 }) 891 }) 892 893 Context("when bind mounts are specified", func() { 894 var ( 895 container linux_backend.LinuxContainerSpec 896 ) 897 898 BeforeEach(func() { 899 var err error 900 container, err = pool.Acquire(garden.ContainerSpec{ 901 BindMounts: []garden.BindMount{ 902 { 903 SrcPath: "/src/path-ro", 904 DstPath: "/dst/path-ro", 905 Mode: garden.BindMountModeRO, 906 }, 907 { 908 SrcPath: "/src/path-rw", 909 DstPath: "/dst/path-rw", 910 Mode: garden.BindMountModeRW, 911 }, 912 { 913 SrcPath: "/src/path-rw", 914 DstPath: "/dst/path-rw", 915 Mode: garden.BindMountModeRW, 916 Origin: garden.BindMountOriginContainer, 917 }, 918 }, 919 }) 920 921 Expect(err).ToNot(HaveOccurred()) 922 }) 923 924 It("delegates creating the bind mount directories to the mkdirChowner", func() { 925 Expect(fakeMkdirChowner.MkdirChownCallCount()).To(Equal(3)) 926 path, uid, gid, mode := fakeMkdirChowner.MkdirChownArgsForCall(0) 927 Expect(path).To(Equal("/provided/rootfs/path/dst/path-ro")) 928 Expect(uid).To(BeEquivalentTo(700000)) 929 Expect(gid).To(BeEquivalentTo(700000)) 930 Expect(mode).To(BeEquivalentTo(0755)) 931 }) 932 933 Context("when the mkdirChowner fails", func() { 934 It("propagates the error", func() { 935 myErr := fmt.Errorf("wow!") 936 fakeMkdirChowner.MkdirChownReturns(myErr) 937 _, err := pool.Acquire(garden.ContainerSpec{ 938 BindMounts: []garden.BindMount{{}}, 939 }) 940 941 Expect(err).To(MatchError(ContainSubstring("wow"))) 942 }) 943 }) 944 945 It("appends mount commands to hook-parent-before-clone.sh", func() { 946 containerPath := path.Join(depotPath, container.ID) 947 rootfsPath := "/provided/rootfs/path" 948 949 Expect(fakeRunner).To(HaveExecutedSerially( 950 fake_command_runner.CommandSpec{ 951 Path: "bash", 952 Args: []string{ 953 "-c", 954 "echo >> " + containerPath + "/lib/hook-parent-before-clone.sh", 955 }, 956 }, 957 fake_command_runner.CommandSpec{ 958 Path: "bash", 959 Args: []string{ 960 "-c", 961 "echo mount -n --bind /src/path-ro " + rootfsPath + "/dst/path-ro" + 962 " >> " + containerPath + "/lib/hook-parent-before-clone.sh", 963 }, 964 }, 965 fake_command_runner.CommandSpec{ 966 Path: "bash", 967 Args: []string{ 968 "-c", 969 "echo mount -n --bind -o remount,ro /src/path-ro " + rootfsPath + "/dst/path-ro" + 970 " >> " + containerPath + "/lib/hook-parent-before-clone.sh", 971 }, 972 }, 973 fake_command_runner.CommandSpec{ 974 Path: "bash", 975 Args: []string{ 976 "-c", 977 "echo >> " + containerPath + "/lib/hook-parent-before-clone.sh", 978 }, 979 }, 980 fake_command_runner.CommandSpec{ 981 Path: "bash", 982 Args: []string{ 983 "-c", 984 "echo mount -n --bind /src/path-rw " + rootfsPath + "/dst/path-rw" + 985 " >> " + containerPath + "/lib/hook-parent-before-clone.sh", 986 }, 987 }, 988 fake_command_runner.CommandSpec{ 989 Path: "bash", 990 Args: []string{ 991 "-c", 992 "echo mount -n --bind -o remount,rw /src/path-rw " + rootfsPath + "/dst/path-rw" + 993 " >> " + containerPath + "/lib/hook-parent-before-clone.sh", 994 }, 995 }, 996 997 fake_command_runner.CommandSpec{ 998 Path: "bash", 999 Args: []string{ 1000 "-c", 1001 "echo mount -n --bind " + rootfsPath + "/src/path-rw " + rootfsPath + "/dst/path-rw" + 1002 " >> " + containerPath + "/lib/hook-parent-before-clone.sh", 1003 }, 1004 }, 1005 fake_command_runner.CommandSpec{ 1006 Path: "bash", 1007 Args: []string{ 1008 "-c", 1009 "echo mount -n --bind -o remount,rw " + rootfsPath + "/src/path-rw " + rootfsPath + "/dst/path-rw" + 1010 " >> " + containerPath + "/lib/hook-parent-before-clone.sh", 1011 }, 1012 }, 1013 )) 1014 }) 1015 }) 1016 1017 Context("when appending to hook-parent-before-clone.sh fails", func() { 1018 var err error 1019 disaster := errors.New("oh no!") 1020 1021 BeforeEach(func() { 1022 fakeRunner.WhenRunning(fake_command_runner.CommandSpec{ 1023 Path: "bash", 1024 }, func(*exec.Cmd) error { 1025 return disaster 1026 }) 1027 1028 _, err = pool.Acquire(garden.ContainerSpec{ 1029 BindMounts: []garden.BindMount{ 1030 { 1031 SrcPath: "/src/path-ro", 1032 DstPath: "/dst/path-ro", 1033 Mode: garden.BindMountModeRO, 1034 }, 1035 { 1036 SrcPath: "/src/path-rw", 1037 DstPath: "/dst/path-rw", 1038 Mode: garden.BindMountModeRW, 1039 }, 1040 }, 1041 }) 1042 }) 1043 1044 It("returns the error", func() { 1045 Expect(err).To(Equal(disaster)) 1046 }) 1047 1048 itReleasesTheIPBlock() 1049 itCleansUpTheRootfs() 1050 itRunsTheDestroyScript() 1051 }) 1052 1053 Context("when executing create.sh fails", func() { 1054 nastyError := errors.New("oh no!") 1055 var err error 1056 1057 BeforeEach(func() { 1058 fakeRunner.WhenRunning( 1059 fake_command_runner.CommandSpec{ 1060 Path: "/root/path/create.sh", 1061 }, func(cmd *exec.Cmd) error { 1062 return nastyError 1063 }, 1064 ) 1065 1066 _, err = pool.Acquire(garden.ContainerSpec{}) 1067 }) 1068 1069 It("returns the error and releases the uid and network", func() { 1070 Expect(err).To(Equal(nastyError)) 1071 1072 Expect(fakeSubnetPool.ReleaseCallCount()).To(Equal(1)) 1073 actualNetwork, _ := fakeSubnetPool.ReleaseArgsForCall(0) 1074 Expect(actualNetwork).To(Equal(containerNetwork)) 1075 }) 1076 1077 itReleasesTheIPBlock() 1078 itRunsTheDestroyScript() 1079 itCleansUpTheRootfs() 1080 itReleasesAndDestroysTheBridge() 1081 }) 1082 1083 Context("when saving the rootfs provider fails", func() { 1084 var err error 1085 1086 BeforeEach(func() { 1087 fakeRunner.WhenRunning( 1088 fake_command_runner.CommandSpec{ 1089 Path: "/root/path/create.sh", 1090 }, func(cmd *exec.Cmd) error { 1091 containerPath := cmd.Args[1] 1092 rootfsProviderPath := filepath.Join(containerPath, "rootfs-provider") 1093 1094 // creating a directory with this name will cause the write to the 1095 // file to fail. 1096 err := os.MkdirAll(rootfsProviderPath, 0755) 1097 Expect(err).ToNot(HaveOccurred()) 1098 1099 return nil 1100 }, 1101 ) 1102 1103 _, err = pool.Acquire(garden.ContainerSpec{}) 1104 }) 1105 1106 It("returns an error", func() { 1107 Expect(err).To(HaveOccurred()) 1108 }) 1109 1110 itReleasesTheIPBlock() 1111 itCleansUpTheRootfs() 1112 itRunsTheDestroyScript() 1113 }) 1114 1115 Context("the container environment is invalid", func() { 1116 var err error 1117 1118 BeforeEach(func() { 1119 _, err = pool.Acquire(garden.ContainerSpec{ 1120 Env: []string{ 1121 "hello=world", 1122 "invalidstring", 1123 "", 1124 "=12", 1125 }, 1126 }) 1127 }) 1128 1129 It("returns an error", func() { 1130 Expect(err).To(HaveOccurred()) 1131 }) 1132 1133 itTearsDownTheIPTableFilters() 1134 itReleasesTheIPBlock() 1135 itRunsTheDestroyScript() 1136 itCleansUpTheRootfs() 1137 itReleasesAndDestroysTheBridge() 1138 }) 1139 }) 1140 1141 Describe("restoring", func() { 1142 var snapshot io.Reader 1143 var buf *bytes.Buffer 1144 1145 var containerNetwork *linux_backend.Network 1146 var rootUID int 1147 var bridgeName string 1148 1149 BeforeEach(func() { 1150 rootUID = 10001 1151 1152 buf = new(bytes.Buffer) 1153 snapshot = buf 1154 _, subnet, _ := net.ParseCIDR("2.3.4.5/29") 1155 containerNetwork = &linux_backend.Network{ 1156 Subnet: subnet, 1157 IP: net.ParseIP("1.2.3.4"), 1158 } 1159 1160 bridgeName = "some-bridge" 1161 }) 1162 1163 JustBeforeEach(func() { 1164 err := json.NewEncoder(buf).Encode( 1165 linux_container.ContainerSnapshot{ 1166 ID: "some-restored-id", 1167 Handle: "some-restored-handle", 1168 1169 GraceTime: 1 * time.Second, 1170 1171 State: "some-restored-state", 1172 Events: []string{ 1173 "some-restored-event", 1174 "some-other-restored-event", 1175 }, 1176 1177 Resources: linux_container.ResourcesSnapshot{ 1178 RootUID: rootUID, 1179 Network: containerNetwork, 1180 Bridge: bridgeName, 1181 Ports: []uint32{61001, 61002, 61003}, 1182 }, 1183 1184 Properties: map[string]string{ 1185 "foo": "bar", 1186 }, 1187 }, 1188 ) 1189 Expect(err).ToNot(HaveOccurred()) 1190 }) 1191 1192 It("constructs a container from the snapshot", func() { 1193 containerSpec, err := pool.Restore(snapshot) 1194 Expect(err).ToNot(HaveOccurred()) 1195 1196 Expect(containerSpec.ID).To(Equal("some-restored-id")) 1197 Expect(containerSpec.Handle).To(Equal("some-restored-handle")) 1198 Expect(containerSpec.GraceTime).To(Equal(1 * time.Second)) 1199 Expect(containerSpec.Properties).To(Equal(garden.Properties(map[string]string{ 1200 "foo": "bar", 1201 }))) 1202 1203 Expect(containerSpec.State).To(Equal(linux_backend.State("some-restored-state"))) 1204 Expect(containerSpec.Events).To(Equal([]string{ 1205 "some-restored-event", 1206 "some-other-restored-event", 1207 })) 1208 1209 Expect(containerSpec.Resources.Network).To(Equal(containerNetwork)) 1210 Expect(containerSpec.Resources.Bridge).To(Equal("some-bridge")) 1211 }) 1212 1213 Context("when a version file exists in the container", func() { 1214 var ( 1215 expectedVersion semver.Version 1216 containerSpec linux_backend.LinuxContainerSpec 1217 versionFilePath string 1218 ) 1219 1220 JustBeforeEach(func() { 1221 var err error 1222 1223 expectedVersion, err = semver.Make("1.0.0") 1224 Expect(err).ToNot(HaveOccurred()) 1225 1226 id := "some-restored-id" 1227 Expect(os.MkdirAll(filepath.Join(depotPath, id), 0755)).To(Succeed()) 1228 versionFilePath = filepath.Join(depotPath, id, "version") 1229 1230 err = ioutil.WriteFile(versionFilePath, []byte(expectedVersion.String()), 0644) 1231 Expect(err).ToNot(HaveOccurred()) 1232 1233 containerSpec, err = pool.Restore(snapshot) 1234 Expect(err).ToNot(HaveOccurred()) 1235 }) 1236 1237 AfterEach(func() { 1238 Expect(os.RemoveAll(versionFilePath)).To(Succeed()) 1239 }) 1240 1241 It("restores the container version", func() { 1242 Expect(containerSpec.Version).To(Equal(expectedVersion)) 1243 }) 1244 }) 1245 1246 Context("when a version file does not exists in the container", func() { 1247 var ( 1248 expectedVersion semver.Version 1249 containerSpec linux_backend.LinuxContainerSpec 1250 ) 1251 1252 JustBeforeEach(func() { 1253 var err error 1254 1255 expectedVersion, err = semver.Make("0.0.0") 1256 Expect(err).ToNot(HaveOccurred()) 1257 1258 containerSpec, err = pool.Restore(snapshot) 1259 Expect(err).ToNot(HaveOccurred()) 1260 }) 1261 1262 It("restores the empty version", func() { 1263 Expect(containerSpec.Version).To(Equal(expectedVersion)) 1264 }) 1265 }) 1266 1267 It("removes its network from the pool", func() { 1268 _, err := pool.Restore(snapshot) 1269 Expect(err).ToNot(HaveOccurred()) 1270 1271 Expect(fakeSubnetPool.RemoveCallCount()).To(Equal(1)) 1272 actualNetwork, _ := fakeSubnetPool.RemoveArgsForCall(0) 1273 Expect(actualNetwork).To(Equal(containerNetwork)) 1274 }) 1275 1276 It("removes its ports from the pool", func() { 1277 _, err := pool.Restore(snapshot) 1278 Expect(err).ToNot(HaveOccurred()) 1279 1280 Expect(fakePortPool.Removed).To(ContainElement(uint32(61001))) 1281 Expect(fakePortPool.Removed).To(ContainElement(uint32(61002))) 1282 Expect(fakePortPool.Removed).To(ContainElement(uint32(61003))) 1283 }) 1284 1285 It("rereserves the bridge for the subnet from the pool", func() { 1286 _, err := pool.Restore(snapshot) 1287 Expect(err).ToNot(HaveOccurred()) 1288 1289 Expect(fakeBridges.RereserveCallCount()).To(Equal(1)) 1290 bridgeName, subnet, containerId := fakeBridges.RereserveArgsForCall(0) 1291 Expect(bridgeName).To(Equal("some-bridge")) 1292 Expect(subnet.String()).To(Equal("2.3.4.0/29")) 1293 Expect(containerId).To(Equal("some-restored-id")) 1294 }) 1295 1296 Context("when rereserving the bridge fails", func() { 1297 var err error 1298 1299 JustBeforeEach(func() { 1300 fakeBridges.RereserveReturns(errors.New("boom")) 1301 _, err = pool.Restore(snapshot) 1302 }) 1303 1304 It("returns the error", func() { 1305 Expect(err).To(HaveOccurred()) 1306 }) 1307 1308 It("returns the subnet to the pool", func() { 1309 Expect(fakeSubnetPool.ReleaseCallCount()).To(Equal(1)) 1310 actualNetwork, _ := fakeSubnetPool.ReleaseArgsForCall(0) 1311 Expect(actualNetwork).To(Equal(containerNetwork)) 1312 }) 1313 }) 1314 1315 Context("when decoding the snapshot fails", func() { 1316 BeforeEach(func() { 1317 snapshot = new(bytes.Buffer) 1318 }) 1319 1320 It("fails", func() { 1321 _, err := pool.Restore(snapshot) 1322 Expect(err).To(HaveOccurred()) 1323 }) 1324 }) 1325 1326 Context("when removing the network from the pool fails", func() { 1327 disaster := errors.New("oh no!") 1328 1329 JustBeforeEach(func() { 1330 fakeSubnetPool.RemoveReturns(disaster) 1331 }) 1332 1333 It("returns the error", func() { 1334 _, err := pool.Restore(snapshot) 1335 Expect(err).To(Equal(disaster)) 1336 }) 1337 }) 1338 1339 Context("when removing a port from the pool fails", func() { 1340 disaster := errors.New("oh no!") 1341 1342 JustBeforeEach(func() { 1343 fakePortPool.RemoveError = disaster 1344 }) 1345 1346 It("returns the error and releases the network and all ports", func() { 1347 _, err := pool.Restore(snapshot) 1348 Expect(err).To(Equal(disaster)) 1349 1350 Expect(fakeSubnetPool.ReleaseCallCount()).To(Equal(1)) 1351 actualNetwork, _ := fakeSubnetPool.ReleaseArgsForCall(0) 1352 Expect(actualNetwork).To(Equal(containerNetwork)) 1353 1354 Expect(fakePortPool.Released).To(ContainElement(uint32(61001))) 1355 Expect(fakePortPool.Released).To(ContainElement(uint32(61002))) 1356 Expect(fakePortPool.Released).To(ContainElement(uint32(61003))) 1357 }) 1358 1359 Context("when the container is privileged", func() { 1360 BeforeEach(func() { 1361 rootUID = 0 1362 }) 1363 1364 It("returns the error", func() { 1365 _, err := pool.Restore(snapshot) 1366 Expect(err).To(Equal(disaster)) 1367 }) 1368 }) 1369 }) 1370 }) 1371 1372 Describe("pruning", func() { 1373 Context("when containers are found in the depot", func() { 1374 BeforeEach(func() { 1375 err := os.MkdirAll(path.Join(depotPath, "container-1"), 0755) 1376 Expect(err).ToNot(HaveOccurred()) 1377 1378 err = os.MkdirAll(path.Join(depotPath, "container-2"), 0755) 1379 Expect(err).ToNot(HaveOccurred()) 1380 1381 err = os.MkdirAll(path.Join(depotPath, "container-3"), 0755) 1382 Expect(err).ToNot(HaveOccurred()) 1383 1384 err = os.MkdirAll(path.Join(depotPath, "tmp"), 0755) 1385 Expect(err).ToNot(HaveOccurred()) 1386 1387 err = ioutil.WriteFile(path.Join(depotPath, "container-1", "bridge-name"), []byte("fake-bridge-1"), 0644) 1388 Expect(err).ToNot(HaveOccurred()) 1389 1390 err = ioutil.WriteFile(path.Join(depotPath, "container-2", "bridge-name"), []byte("fake-bridge-2"), 0644) 1391 Expect(err).ToNot(HaveOccurred()) 1392 1393 err = ioutil.WriteFile(path.Join(depotPath, "container-1", "rootfs-provider"), []byte("docker-remote-vfs"), 0644) 1394 Expect(err).ToNot(HaveOccurred()) 1395 1396 err = ioutil.WriteFile(path.Join(depotPath, "container-2", "rootfs-provider"), []byte("docker-remote-vfs"), 0644) 1397 Expect(err).ToNot(HaveOccurred()) 1398 1399 err = ioutil.WriteFile(path.Join(depotPath, "container-3", "rootfs-provider"), []byte(""), 0644) 1400 Expect(err).ToNot(HaveOccurred()) 1401 }) 1402 1403 It("destroys each container", func() { 1404 err := pool.Prune(map[string]bool{}) 1405 Expect(err).ToNot(HaveOccurred()) 1406 1407 Expect(fakeIPTablesManager.ContainerTeardownCallCount()).To(Equal(3)) 1408 1409 containerID := fakeIPTablesManager.ContainerTeardownArgsForCall(0) 1410 Expect(containerID).To(Equal("container-1")) 1411 1412 containerID = fakeIPTablesManager.ContainerTeardownArgsForCall(1) 1413 Expect(containerID).To(Equal("container-2")) 1414 1415 containerID = fakeIPTablesManager.ContainerTeardownArgsForCall(2) 1416 Expect(containerID).To(Equal("container-3")) 1417 1418 Expect(fakeRunner).To(HaveExecutedSerially( 1419 fake_command_runner.CommandSpec{ 1420 Path: "/root/path/destroy.sh", 1421 Args: []string{path.Join(depotPath, "container-1")}, 1422 }, 1423 fake_command_runner.CommandSpec{ 1424 Path: "/root/path/destroy.sh", 1425 Args: []string{path.Join(depotPath, "container-2")}, 1426 }, 1427 fake_command_runner.CommandSpec{ 1428 Path: "/root/path/destroy.sh", 1429 Args: []string{path.Join(depotPath, "container-3")}, 1430 }, 1431 )) 1432 }) 1433 1434 Context("after destroying it", func() { 1435 BeforeEach(func() { 1436 fakeRunner.WhenRunning( 1437 fake_command_runner.CommandSpec{ 1438 Path: "/root/path/destroy.sh", 1439 }, func(cmd *exec.Cmd) error { 1440 return os.RemoveAll(cmd.Args[0]) 1441 }, 1442 ) 1443 }) 1444 1445 It("cleans up each container's rootfs after destroying it", func() { 1446 err := pool.Prune(map[string]bool{}) 1447 Expect(err).ToNot(HaveOccurred()) 1448 1449 Expect(fakeRootFSProvider.DestroyCallCount()).To(Equal(2)) 1450 _, id1 := fakeRootFSProvider.DestroyArgsForCall(0) 1451 _, id2 := fakeRootFSProvider.DestroyArgsForCall(1) 1452 Expect(id1).To(Equal("container-1")) 1453 Expect(id2).To(Equal("container-2")) 1454 1455 Expect(fakeIPTablesManager.ContainerTeardownCallCount()).To(Equal(3)) 1456 1457 containerID1 := fakeIPTablesManager.ContainerTeardownArgsForCall(0) 1458 Expect(containerID1).To(Equal("container-1")) 1459 1460 containerID2 := fakeIPTablesManager.ContainerTeardownArgsForCall(1) 1461 Expect(containerID2).To(Equal("container-2")) 1462 }) 1463 1464 It("releases the bridge", func() { 1465 err := pool.Prune(map[string]bool{}) 1466 Expect(err).ToNot(HaveOccurred()) 1467 1468 Expect(fakeBridges.ReleaseCallCount()).To(Equal(2)) 1469 1470 bridge, containerId := fakeBridges.ReleaseArgsForCall(0) 1471 Expect(bridge).To(Equal("fake-bridge-1")) 1472 Expect(containerId).To(Equal("container-1")) 1473 1474 bridge, containerId = fakeBridges.ReleaseArgsForCall(1) 1475 Expect(bridge).To(Equal("fake-bridge-2")) 1476 Expect(containerId).To(Equal("container-2")) 1477 1478 Expect(fakeIPTablesManager.ContainerTeardownCallCount()).To(Equal(3)) 1479 1480 containerID1 := fakeIPTablesManager.ContainerTeardownArgsForCall(0) 1481 Expect(containerID1).To(Equal("container-1")) 1482 1483 containerID2 := fakeIPTablesManager.ContainerTeardownArgsForCall(1) 1484 Expect(containerID2).To(Equal("container-2")) 1485 }) 1486 }) 1487 1488 Context("when a container does not declare a bridge name", func() { 1489 It("does nothing much", func() { 1490 err := pool.Prune(map[string]bool{"container-1": true, "container-2": true}) 1491 Expect(err).ToNot(HaveOccurred()) 1492 1493 Expect(fakeBridges.ReleaseCallCount()).To(Equal(0)) 1494 }) 1495 }) 1496 1497 Context("when a container declares a docker rootfs provider", func() { 1498 BeforeEach(func() { 1499 err := ioutil.WriteFile(path.Join(depotPath, "container-2", "rootfs-provider"), []byte("docker"), 0644) 1500 Expect(err).ToNot(HaveOccurred()) 1501 }) 1502 1503 It("does not clean the rootfs", func() { 1504 err := pool.Prune(map[string]bool{}) 1505 Expect(err).ToNot(HaveOccurred()) 1506 1507 for i := 0; i < fakeRootFSProvider.DestroyCallCount(); i++ { 1508 _, arg := fakeRootFSProvider.DestroyArgsForCall(i) 1509 Expect(arg).ToNot(Equal(layercake.ContainerID("container-2"))) 1510 } 1511 }) 1512 }) 1513 1514 Context("when a container declares a rootfs provider", func() { 1515 BeforeEach(func() { 1516 err := os.MkdirAll(path.Join(depotPath, "container-4"), 0755) 1517 Expect(err).ToNot(HaveOccurred()) 1518 1519 err = ioutil.WriteFile(path.Join(depotPath, "container-4", "rootfs-provider"), []byte("docker-composite"), 0644) 1520 Expect(err).ToNot(HaveOccurred()) 1521 }) 1522 1523 It("cleans up the rootfs", func() { 1524 err := pool.Prune(map[string]bool{}) 1525 Expect(err).ToNot(HaveOccurred()) 1526 1527 Expect(fakeRootFSProvider.DestroyCallCount()).To(Equal(3)) 1528 _, id1 := fakeRootFSProvider.DestroyArgsForCall(0) 1529 _, id3 := fakeRootFSProvider.DestroyArgsForCall(2) 1530 Expect(id1).To(Equal("container-1")) 1531 Expect(id3).To(Equal("container-4")) 1532 1533 Expect(fakeIPTablesManager.ContainerTeardownCallCount()).To(Equal(4)) 1534 containerID := fakeIPTablesManager.ContainerTeardownArgsForCall(0) 1535 Expect(containerID).To(Equal("container-1")) 1536 containerID = fakeIPTablesManager.ContainerTeardownArgsForCall(3) 1537 Expect(containerID).To(Equal("container-4")) 1538 }) 1539 }) 1540 1541 Context("when a container does not declare a rootfs provider", func() { 1542 BeforeEach(func() { 1543 err := os.Remove(path.Join(depotPath, "container-2", "rootfs-provider")) 1544 Expect(err).ToNot(HaveOccurred()) 1545 }) 1546 1547 JustBeforeEach(func() { 1548 err := pool.Prune(map[string]bool{}) 1549 Expect(err).ToNot(HaveOccurred()) 1550 }) 1551 1552 It("cleans it up using the default provider", func() { 1553 Expect(fakeRootFSProvider.DestroyCallCount()).To(Equal(2)) 1554 _, id1 := fakeRootFSProvider.DestroyArgsForCall(0) 1555 _, id2 := fakeRootFSProvider.DestroyArgsForCall(1) 1556 Expect(id1).To(Equal("container-1")) 1557 Expect(id2).To(Equal("container-2")) 1558 }) 1559 1560 Context("when a container exists with an unknown rootfs provider", func() { 1561 BeforeEach(func() { 1562 err := ioutil.WriteFile(path.Join(depotPath, "container-2", "rootfs-provider"), []byte("unknown"), 0644) 1563 Expect(err).ToNot(HaveOccurred()) 1564 }) 1565 1566 It("ignores the error", func() { 1567 for i := 0; i < fakeRootFSProvider.DestroyCallCount(); i++ { 1568 _, arg := fakeRootFSProvider.DestroyArgsForCall(i) 1569 Expect(arg).ToNot(Equal(layercake.ContainerID("container-2"))) 1570 } 1571 }) 1572 1573 It("cleans up the iptables", func() { 1574 Expect(fakeIPTablesManager.ContainerTeardownCallCount()).To(Equal(3)) 1575 containerID := fakeIPTablesManager.ContainerTeardownArgsForCall(0) 1576 Expect(containerID).To(Equal("container-1")) 1577 containerID = fakeIPTablesManager.ContainerTeardownArgsForCall(1) 1578 Expect(containerID).To(Equal("container-2")) 1579 containerID = fakeIPTablesManager.ContainerTeardownArgsForCall(2) 1580 Expect(containerID).To(Equal("container-3")) 1581 }) 1582 }) 1583 1584 Context("when a container exists with an empty rootfs provider", func() { 1585 BeforeEach(func() { 1586 err := ioutil.WriteFile(path.Join(depotPath, "container-2", "rootfs-provider"), []byte(""), 0644) 1587 Expect(err).ToNot(HaveOccurred()) 1588 }) 1589 1590 It("does not clean the rootfs", func() { 1591 for i := 0; i < fakeRootFSProvider.DestroyCallCount(); i++ { 1592 _, arg := fakeRootFSProvider.DestroyArgsForCall(i) 1593 Expect(arg).ToNot(Equal(layercake.ContainerID("container-2"))) 1594 1595 containerID := fakeIPTablesManager.ContainerTeardownArgsForCall(i) 1596 Expect(containerID).ToNot(Equal("container-2")) 1597 Expect(containerID).To(Equal(arg)) 1598 } 1599 }) 1600 }) 1601 }) 1602 1603 Context("when iptables manager fails", func() { 1604 disaster := errors.New("oh no!") 1605 1606 BeforeEach(func() { 1607 fakeIPTablesManager.ContainerTeardownReturns(disaster) 1608 }) 1609 1610 It("ignores the error", func() { 1611 err := pool.Prune(map[string]bool{"container-2": true}) 1612 Expect(err).ToNot(HaveOccurred()) 1613 1614 Expect(fakeRunner).ToNot(HaveExecutedSerially( 1615 fake_command_runner.CommandSpec{ 1616 Path: "/root/path/destroy.sh", 1617 Args: []string{path.Join(depotPath, "container-2")}, 1618 }, 1619 )) 1620 }) 1621 }) 1622 1623 Context("when cleaning up the rootfs fails", func() { 1624 disaster := errors.New("oh no!") 1625 1626 BeforeEach(func() { 1627 fakeRootFSProvider.DestroyReturns(disaster) 1628 }) 1629 1630 It("ignores the error", func() { 1631 err := pool.Prune(map[string]bool{}) 1632 Expect(err).ToNot(HaveOccurred()) 1633 }) 1634 }) 1635 1636 Context("when a container to keep is specified", func() { 1637 It("is not destroyed", func() { 1638 err := pool.Prune(map[string]bool{"container-2": true}) 1639 Expect(err).ToNot(HaveOccurred()) 1640 1641 Expect(fakeRunner).ToNot(HaveExecutedSerially( 1642 fake_command_runner.CommandSpec{ 1643 Path: "/root/path/destroy.sh", 1644 Args: []string{path.Join(depotPath, "container-2")}, 1645 }, 1646 )) 1647 1648 Expect(fakeIPTablesManager.ContainerTeardownCallCount()).To(Equal(2)) 1649 1650 containerID := fakeIPTablesManager.ContainerTeardownArgsForCall(0) 1651 Expect(containerID).ToNot(Equal("container-2")) 1652 1653 containerID = fakeIPTablesManager.ContainerTeardownArgsForCall(1) 1654 Expect(containerID).ToNot(Equal("container-2")) 1655 }) 1656 1657 It("is not cleaned up", func() { 1658 err := pool.Prune(map[string]bool{"container-2": true}) 1659 Expect(err).ToNot(HaveOccurred()) 1660 1661 Expect(fakeRootFSProvider.DestroyCallCount()).To(Equal(1)) 1662 _, prunedId := fakeRootFSProvider.DestroyArgsForCall(0) 1663 Expect(prunedId).ToNot(Equal(layercake.ContainerID("container-2"))) 1664 1665 Expect(fakeIPTablesManager.ContainerTeardownCallCount()).To(Equal(2)) 1666 }) 1667 1668 It("does not release the bridge", func() { 1669 err := pool.Prune(map[string]bool{"container-2": true}) 1670 Expect(err).ToNot(HaveOccurred()) 1671 1672 Expect(fakeBridges.ReleaseCallCount()).To(Equal(1)) 1673 Expect(fakeIPTablesManager.ContainerTeardownCallCount()).To(Equal(2)) 1674 }) 1675 }) 1676 1677 Context("when executing destroy.sh fails", func() { 1678 disaster := errors.New("oh no!") 1679 1680 BeforeEach(func() { 1681 fakeRunner.WhenRunning( 1682 fake_command_runner.CommandSpec{ 1683 Path: "/root/path/destroy.sh", 1684 }, func(cmd *exec.Cmd) error { 1685 return disaster 1686 }, 1687 ) 1688 }) 1689 1690 It("ignores the error", func() { 1691 err := pool.Prune(map[string]bool{}) 1692 Expect(err).ToNot(HaveOccurred()) 1693 1694 By("and does not clean up the container's rootfs") 1695 Expect(fakeRootFSProvider.DestroyCallCount()).To(Equal(0)) 1696 }) 1697 }) 1698 1699 It("prunes any remaining bridges", func() { 1700 err := pool.Prune(map[string]bool{}) 1701 Expect(err).ToNot(HaveOccurred()) 1702 1703 Expect(fakeBridges.PruneCallCount()).To(Equal(1)) 1704 }) 1705 }) 1706 }) 1707 1708 Describe("destroying", func() { 1709 var container linux_backend.LinuxContainerSpec 1710 var err error 1711 1712 BeforeEach(func() { 1713 container = linux_backend.LinuxContainerSpec{ 1714 Resources: &linux_backend.Resources{ 1715 Network: &linux_backend.Network{ 1716 IP: net.ParseIP("1.2.3.4"), 1717 }, 1718 Ports: []uint32{123, 456}, 1719 }, 1720 } 1721 }) 1722 1723 It("executes destroy.sh with the correct args and environment", func() { 1724 err = pool.Release(container) 1725 Expect(err).ToNot(HaveOccurred()) 1726 1727 Expect(fakeRunner).To(HaveExecutedSerially( 1728 fake_command_runner.CommandSpec{ 1729 Path: "/root/path/destroy.sh", 1730 Args: []string{path.Join(depotPath, container.ID)}, 1731 }, 1732 )) 1733 1734 Expect(fakeIPTablesManager.ContainerTeardownCallCount()).To(Equal(1)) 1735 containerID := fakeIPTablesManager.ContainerTeardownArgsForCall(0) 1736 Expect(containerID).To(Equal(container.ID)) 1737 }) 1738 1739 It("releases the container's ports and network", func() { 1740 err := pool.Release(container) 1741 Expect(err).ToNot(HaveOccurred()) 1742 1743 Expect(fakePortPool.Released).To(ContainElement(uint32(123))) 1744 Expect(fakePortPool.Released).To(ContainElement(uint32(456))) 1745 1746 Expect(fakeSubnetPool.ReleaseCallCount()).To(Equal(1)) 1747 actualNetwork, _ := fakeSubnetPool.ReleaseArgsForCall(0) 1748 Expect(actualNetwork).To(Equal(container.Resources.Network)) 1749 1750 Expect(fakeIPTablesManager.ContainerTeardownCallCount()).To(Equal(1)) 1751 Expect(fakeIPTablesManager.ContainerTeardownArgsForCall(0)).To(Equal(container.ID)) 1752 }) 1753 1754 Context("when a bridge was created", func() { 1755 BeforeEach(func() { 1756 Expect(ioutil.WriteFile(path.Join(depotPath, container.ID, "bridge-name"), []byte("the-bridge"), 0700)).To(Succeed()) 1757 }) 1758 1759 It("releases the bridge from the pool", func() { 1760 err := pool.Release(container) 1761 Expect(err).ToNot(HaveOccurred()) 1762 1763 Expect(fakeBridges.ReleaseCallCount()).To(Equal(1)) 1764 bridgeName, containerId := fakeBridges.ReleaseArgsForCall(0) 1765 1766 Expect(bridgeName).To(Equal("the-bridge")) 1767 Expect(containerId).To(Equal(container.ID)) 1768 1769 Expect(fakeIPTablesManager.ContainerTeardownCallCount()).To(Equal(1)) 1770 Expect(fakeIPTablesManager.ContainerTeardownArgsForCall(0)).To(Equal(container.ID)) 1771 }) 1772 1773 Context("when the releasing the bridge fails", func() { 1774 It("returns the error", func() { 1775 releaseErr := errors.New("jam in the bridge") 1776 fakeBridges.ReleaseReturns(releaseErr) 1777 err := pool.Release(container) 1778 Expect(err).To(MatchError("containerpool: release bridge the-bridge: jam in the bridge")) 1779 }) 1780 }) 1781 }) 1782 1783 It("tears down filter chains", func() { 1784 err := pool.Release(container) 1785 Expect(err).ToNot(HaveOccurred()) 1786 1787 Expect(fakeFilterProvider.ProvideFilterCallCount()).To(BeNumerically(">", 0)) 1788 Expect(fakeFilterProvider.ProvideFilterArgsForCall(0)).To(Equal(container.Handle)) 1789 Expect(fakeFilter.TearDownCallCount()).To(Equal(1)) 1790 1791 Expect(fakeIPTablesManager.ContainerTeardownCallCount()).To(Equal(1)) 1792 Expect(fakeIPTablesManager.ContainerTeardownArgsForCall(0)).To(Equal(container.ID)) 1793 }) 1794 1795 Context("when the container has a rootfs provider defined", func() { 1796 BeforeEach(func() { 1797 err := os.MkdirAll(path.Join(depotPath, container.ID), 0755) 1798 Expect(err).ToNot(HaveOccurred()) 1799 1800 err = ioutil.WriteFile(path.Join(depotPath, container.ID, "rootfs-provider"), []byte("docker-remote-vfs"), 0644) 1801 Expect(err).ToNot(HaveOccurred()) 1802 }) 1803 1804 It("cleans up the container's rootfs", func() { 1805 err := pool.Release(container) 1806 Expect(err).ToNot(HaveOccurred()) 1807 1808 Expect(fakeRootFSProvider.DestroyCallCount()).To(Equal(1)) 1809 _, id := fakeRootFSProvider.DestroyArgsForCall(0) 1810 Expect(id).To(Equal(container.ID)) 1811 }) 1812 1813 It("clean ups the iptables", func() { 1814 err := pool.Release(container) 1815 Expect(err).ToNot(HaveOccurred()) 1816 1817 Expect(fakeIPTablesManager.ContainerTeardownCallCount()).To(Equal(1)) 1818 Expect(fakeIPTablesManager.ContainerTeardownArgsForCall(0)).To(Equal((container.ID))) 1819 }) 1820 1821 Context("when cleaning up the container's rootfs fails", func() { 1822 disaster := errors.New("oh no!") 1823 1824 BeforeEach(func() { 1825 fakeRootFSProvider.DestroyReturns(disaster) 1826 }) 1827 1828 It("returns the error", func() { 1829 err := pool.Release(container) 1830 Expect(err).To(Equal(disaster)) 1831 }) 1832 1833 It("does not release the container's ports", func() { 1834 pool.Release(container) 1835 1836 Expect(fakePortPool.Released).ToNot(ContainElement(uint32(123))) 1837 Expect(fakePortPool.Released).ToNot(ContainElement(uint32(456))) 1838 }) 1839 1840 It("does not release the network", func() { 1841 pool.Release(container) 1842 1843 Expect(fakeSubnetPool.ReleaseCallCount()).To(Equal(0)) 1844 }) 1845 1846 It("does not tear down the filter", func() { 1847 pool.Release(container) 1848 Expect(fakeFilter.TearDownCallCount()).To(Equal(0)) 1849 }) 1850 }) 1851 }) 1852 1853 Context("when iptables manager fails", func() { 1854 disaster := errors.New("oh no!") 1855 1856 BeforeEach(func() { 1857 fakeIPTablesManager.ContainerTeardownReturns(disaster) 1858 }) 1859 1860 It("returns the error", func() { 1861 err := pool.Release(container) 1862 Expect(err).To(Equal(disaster)) 1863 1864 Expect(fakeRunner).ToNot(HaveExecutedSerially( 1865 fake_command_runner.CommandSpec{ 1866 Path: "/root/path/destroy.sh", 1867 Args: []string{path.Join(depotPath, container.ID)}, 1868 }, 1869 )) 1870 }) 1871 }) 1872 1873 Context("when destroy.sh fails", func() { 1874 disaster := errors.New("oh no!") 1875 1876 BeforeEach(func() { 1877 fakeRunner.WhenRunning( 1878 fake_command_runner.CommandSpec{ 1879 Path: "/root/path/destroy.sh", 1880 Args: []string{path.Join(depotPath, container.ID)}, 1881 }, 1882 func(*exec.Cmd) error { 1883 return disaster 1884 }, 1885 ) 1886 }) 1887 1888 It("returns the error", func() { 1889 err := pool.Release(container) 1890 Expect(err).To(Equal(disaster)) 1891 }) 1892 1893 It("does not clean up the container's rootfs", func() { 1894 err := pool.Release(container) 1895 Expect(err).To(HaveOccurred()) 1896 1897 Expect(fakeRootFSProvider.DestroyCallCount()).To(Equal(0)) 1898 }) 1899 1900 It("does not release the container's ports", func() { 1901 err := pool.Release(container) 1902 Expect(err).To(HaveOccurred()) 1903 1904 Expect(fakePortPool.Released).To(BeEmpty()) 1905 Expect(fakePortPool.Released).To(BeEmpty()) 1906 }) 1907 1908 It("does not release the network", func() { 1909 err := pool.Release(container) 1910 Expect(err).To(HaveOccurred()) 1911 Expect(fakeSubnetPool.ReleaseCallCount()).To(Equal(0)) 1912 }) 1913 1914 It("does not tear down the filter", func() { 1915 pool.Release(container) 1916 Expect(fakeFilter.TearDownCallCount()).To(Equal(0)) 1917 }) 1918 }) 1919 }) 1920 1921 Describe("Logging", func() { 1922 Context("when acquiring", func() { 1923 It("should log before and after bridge setup", func() { 1924 _, err := pool.Acquire(garden.ContainerSpec{}) 1925 Expect(err).ToNot(HaveOccurred()) 1926 1927 Expect(logger.LogMessages()).To(ContainElement(ContainSubstring("setup-bridge-starting"))) 1928 Expect(logger.LogMessages()).To(ContainElement(ContainSubstring("setup-bridge-ended"))) 1929 }) 1930 1931 It("should log before and after iptables setup", func() { 1932 _, err := pool.Acquire(garden.ContainerSpec{}) 1933 Expect(err).ToNot(HaveOccurred()) 1934 1935 Expect(logger.LogMessages()).To(ContainElement(ContainSubstring("setup-iptables-starting"))) 1936 Expect(logger.LogMessages()).To(ContainElement(ContainSubstring("setup-iptables-ended"))) 1937 }) 1938 1939 It("should log before and after RootFS provision", func() { 1940 _, err := pool.Acquire(garden.ContainerSpec{}) 1941 Expect(err).ToNot(HaveOccurred()) 1942 1943 Expect(logger.LogMessages()).To(ContainElement(ContainSubstring("provide-rootfs-starting"))) 1944 Expect(logger.LogMessages()).To(ContainElement(ContainSubstring("provide-rootfs-ended"))) 1945 }) 1946 }) 1947 }) 1948 })