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