github.com/geofffranks/garden-linux@v0.0.0-20160715111146-26c893169cfa/linux_backend/linux_backend_test.go (about)

     1  package linux_backend_test
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"net"
     9  	"os"
    10  	"path"
    11  	"time"
    12  
    13  	. "github.com/onsi/ginkgo"
    14  	. "github.com/onsi/gomega"
    15  	"code.cloudfoundry.org/lager/lagertest"
    16  
    17  	"code.cloudfoundry.org/garden"
    18  	"code.cloudfoundry.org/garden-linux/container_repository"
    19  	"code.cloudfoundry.org/garden-linux/linux_backend"
    20  	"code.cloudfoundry.org/garden-linux/linux_backend/fakes"
    21  	"code.cloudfoundry.org/garden-linux/sysinfo/fake_sysinfo"
    22  )
    23  
    24  var _ = Describe("LinuxBackend", func() {
    25  	var logger *lagertest.TestLogger
    26  
    27  	var fakeResourcePool *fakes.FakeResourcePool
    28  	var fakeSystemInfo *fake_sysinfo.FakeProvider
    29  	var fakeContainerProvider *fakes.FakeContainerProvider
    30  	var fakeHealthCheck *fakes.FakeHealthChecker
    31  	var containerRepo linux_backend.ContainerRepository
    32  	var linuxBackend *linux_backend.LinuxBackend
    33  	var snapshotsPath string
    34  	var maxContainers int
    35  	var fakeContainers map[string]*fakes.FakeContainer
    36  
    37  	newTestContainer := func(spec linux_backend.LinuxContainerSpec) *fakes.FakeContainer {
    38  		container := new(fakes.FakeContainer)
    39  		container.HandleReturns(spec.Handle)
    40  		container.GraceTimeReturns(spec.GraceTime)
    41  
    42  		if spec.ID == "" {
    43  			spec.ID = spec.Handle
    44  		}
    45  
    46  		container.IDReturns(spec.ID)
    47  
    48  		container.HasPropertiesStub = func(props garden.Properties) bool {
    49  			for k, v := range props {
    50  				if value, ok := spec.Properties[k]; !ok || (ok && value != v) {
    51  					return false
    52  				}
    53  			}
    54  			return true
    55  		}
    56  
    57  		return container
    58  	}
    59  
    60  	registerTestContainer := func(container *fakes.FakeContainer) *fakes.FakeContainer {
    61  		fakeContainers[container.Handle()] = container
    62  		return container
    63  	}
    64  
    65  	BeforeEach(func() {
    66  		fakeContainers = make(map[string]*fakes.FakeContainer)
    67  		logger = lagertest.NewTestLogger("test")
    68  		fakeResourcePool = new(fakes.FakeResourcePool)
    69  		containerRepo = container_repository.New()
    70  		fakeSystemInfo = new(fake_sysinfo.FakeProvider)
    71  		fakeHealthCheck = new(fakes.FakeHealthChecker)
    72  
    73  		snapshotsPath = ""
    74  		maxContainers = 0
    75  
    76  		id := 0
    77  		fakeResourcePool.AcquireStub = func(spec garden.ContainerSpec) (linux_backend.LinuxContainerSpec, error) {
    78  			if spec.Handle == "" {
    79  				id = id + 1
    80  				spec.Handle = fmt.Sprintf("handle-%d", id)
    81  			}
    82  			return linux_backend.LinuxContainerSpec{ContainerSpec: spec}, nil
    83  		}
    84  
    85  		fakeResourcePool.RestoreStub = func(snapshot io.Reader) (linux_backend.LinuxContainerSpec, error) {
    86  			b, err := ioutil.ReadAll(snapshot)
    87  			Expect(err).NotTo(HaveOccurred())
    88  
    89  			return linux_backend.LinuxContainerSpec{
    90  				ID:            string(b),
    91  				ContainerSpec: garden.ContainerSpec{Handle: string(b)},
    92  			}, nil
    93  		}
    94  
    95  		fakeContainerProvider = new(fakes.FakeContainerProvider)
    96  		fakeContainerProvider.ProvideContainerStub = func(spec linux_backend.LinuxContainerSpec) linux_backend.Container {
    97  			if c, ok := fakeContainers[spec.Handle]; ok {
    98  				return c
    99  			}
   100  
   101  			return newTestContainer(spec)
   102  		}
   103  
   104  		fakeResourcePool.AcquireStub = func(spec garden.ContainerSpec) (linux_backend.LinuxContainerSpec, error) {
   105  			return linux_backend.LinuxContainerSpec{
   106  				ContainerSpec: spec,
   107  				Resources: &linux_backend.Resources{
   108  					Network: &linux_backend.Network{
   109  						IP: net.ParseIP("192.0.2.1"),
   110  					},
   111  				},
   112  			}, nil
   113  		}
   114  	})
   115  
   116  	JustBeforeEach(func() {
   117  		linuxBackend = linux_backend.New(
   118  			logger,
   119  			fakeResourcePool,
   120  			containerRepo,
   121  			fakeContainerProvider,
   122  			fakeSystemInfo,
   123  			fakeHealthCheck,
   124  			snapshotsPath,
   125  			maxContainers,
   126  		)
   127  	})
   128  
   129  	Describe("Setup", func() {
   130  		It("sets up the container pool", func() {
   131  			err := linuxBackend.Setup()
   132  			Expect(err).ToNot(HaveOccurred())
   133  
   134  			Expect(fakeResourcePool.SetupCallCount()).To(Equal(1))
   135  		})
   136  	})
   137  
   138  	Describe("Ping", func() {
   139  		It("should not return an error normally", func() {
   140  			Expect(linuxBackend.Ping()).To(Succeed())
   141  		})
   142  
   143  		Context("when a health check fails", func() {
   144  			var sick error
   145  
   146  			BeforeEach(func() {
   147  				sick = errors.New("sick as a parrot")
   148  				fakeHealthCheck.HealthCheckReturns(sick)
   149  			})
   150  
   151  			It("should return an unrecoverable error", func() {
   152  				Expect(linuxBackend.Ping()).To(MatchError(garden.UnrecoverableError{"sick as a parrot"}))
   153  			})
   154  		})
   155  	})
   156  
   157  	Describe("Start", func() {
   158  		var tmpdir string
   159  
   160  		BeforeEach(func() {
   161  			var err error
   162  
   163  			tmpdir, err = ioutil.TempDir(os.TempDir(), "garden-server-test")
   164  			Expect(err).ToNot(HaveOccurred())
   165  
   166  			snapshotsPath = path.Join(tmpdir, "snapshots")
   167  		})
   168  
   169  		It("creates the snapshots directory if it's not already there", func() {
   170  			err := linuxBackend.Start()
   171  			Expect(err).ToNot(HaveOccurred())
   172  
   173  			stat, err := os.Stat(snapshotsPath)
   174  			Expect(err).ToNot(HaveOccurred())
   175  
   176  			Expect(stat.IsDir()).To(BeTrue())
   177  		})
   178  
   179  		Context("when the snapshots directory fails to be created", func() {
   180  			BeforeEach(func() {
   181  				tmpfile, err := ioutil.TempFile(os.TempDir(), "garden-server-test")
   182  				Expect(err).ToNot(HaveOccurred())
   183  
   184  				snapshotsPath = path.Join(tmpfile.Name(), "snapshots")
   185  			})
   186  
   187  			It("fails to start", func() {
   188  				err := linuxBackend.Start()
   189  				Expect(err).To(HaveOccurred())
   190  			})
   191  		})
   192  
   193  		Context("when no snapshots directory is given", func() {
   194  			It("successfully starts", func() {
   195  				err := linuxBackend.Start()
   196  				Expect(err).ToNot(HaveOccurred())
   197  			})
   198  		})
   199  
   200  		Describe("when snapshots are present", func() {
   201  			var snapshotsPath string
   202  
   203  			BeforeEach(func() {
   204  				snapshotsPath = path.Join(tmpdir, "snapshots")
   205  
   206  				err := os.MkdirAll(snapshotsPath, 0755)
   207  				Expect(err).ToNot(HaveOccurred())
   208  
   209  				file, err := os.Create(path.Join(snapshotsPath, "some-id"))
   210  				Expect(err).ToNot(HaveOccurred())
   211  
   212  				file.Write([]byte("handle-a"))
   213  				file.Close()
   214  
   215  				file, err = os.Create(path.Join(snapshotsPath, "some-other-id"))
   216  				Expect(err).ToNot(HaveOccurred())
   217  
   218  				file.Write([]byte("handle-b"))
   219  				file.Close()
   220  			})
   221  
   222  			It("restores them via the container pool", func() {
   223  				Expect(fakeResourcePool.RestoreCallCount()).To(Equal(0))
   224  
   225  				err := linuxBackend.Start()
   226  				Expect(err).ToNot(HaveOccurred())
   227  
   228  				Expect(fakeResourcePool.RestoreCallCount()).To(Equal(2))
   229  			})
   230  
   231  			It("removes the snapshots", func() {
   232  				Expect(fakeResourcePool.RestoreCallCount()).To(Equal(0))
   233  
   234  				err := linuxBackend.Start()
   235  				Expect(err).ToNot(HaveOccurred())
   236  
   237  				_, err = os.Stat(path.Join(snapshotsPath, "some-id"))
   238  				Expect(err).To(HaveOccurred())
   239  
   240  				_, err = os.Stat(path.Join(snapshotsPath, "some-other-id"))
   241  				Expect(err).To(HaveOccurred())
   242  			})
   243  
   244  			It("registers the containers", func() {
   245  				err := linuxBackend.Start()
   246  				Expect(err).ToNot(HaveOccurred())
   247  
   248  				containers, err := linuxBackend.Containers(nil)
   249  				Expect(err).ToNot(HaveOccurred())
   250  
   251  				Expect(containers).To(HaveLen(2))
   252  			})
   253  
   254  			It("keeps them when pruning the container pool", func() {
   255  				err := linuxBackend.Start()
   256  				Expect(err).ToNot(HaveOccurred())
   257  
   258  				Expect(fakeResourcePool.PruneCallCount()).To(Equal(1))
   259  				Expect(fakeResourcePool.PruneArgsForCall(0)).To(Equal(map[string]bool{
   260  					"handle-a": true,
   261  					"handle-b": true,
   262  				}))
   263  			})
   264  
   265  			Context("when restoring the container fails", func() {
   266  				disaster := errors.New("failed to restore")
   267  
   268  				BeforeEach(func() {
   269  					fakeResourcePool.RestoreReturns(linux_backend.LinuxContainerSpec{}, disaster)
   270  				})
   271  
   272  				It("successfully starts anyway", func() {
   273  					err := linuxBackend.Start()
   274  					Expect(err).ToNot(HaveOccurred())
   275  				})
   276  			})
   277  		})
   278  
   279  		It("prunes the container pool", func() {
   280  			err := linuxBackend.Start()
   281  			Expect(err).ToNot(HaveOccurred())
   282  
   283  			Expect(fakeResourcePool.PruneCallCount()).To(Equal(1))
   284  			Expect(fakeResourcePool.PruneArgsForCall(0)).To(Equal(map[string]bool{}))
   285  		})
   286  
   287  		Context("when pruning the container pool fails", func() {
   288  			disaster := errors.New("failed to prune")
   289  
   290  			BeforeEach(func() {
   291  				fakeResourcePool.PruneReturns(disaster)
   292  			})
   293  
   294  			It("returns the error", func() {
   295  				err := linuxBackend.Start()
   296  				Expect(err).To(Equal(disaster))
   297  			})
   298  		})
   299  	})
   300  
   301  	Describe("Stop", func() {
   302  		var (
   303  			container1 *fakes.FakeContainer
   304  			container2 *fakes.FakeContainer
   305  		)
   306  
   307  		BeforeEach(func() {
   308  			container1 = registerTestContainer(newTestContainer(linux_backend.LinuxContainerSpec{
   309  				ContainerSpec: garden.ContainerSpec{Handle: "container-1"},
   310  			}))
   311  			containerRepo.Add(container1)
   312  			container2 = registerTestContainer(newTestContainer(linux_backend.LinuxContainerSpec{
   313  				ContainerSpec: garden.ContainerSpec{Handle: "container-2"},
   314  			}))
   315  			containerRepo.Add(container2)
   316  		})
   317  
   318  		Context("when no snapshot directory is passed", func() {
   319  			It("stops succesfully without saving snapshots", func() {
   320  				Expect(func() { linuxBackend.Stop() }).ToNot(Panic())
   321  
   322  				Expect(container1.SnapshotCallCount()).To(Equal(0))
   323  				Expect(container2.SnapshotCallCount()).To(Equal(0))
   324  			})
   325  		})
   326  
   327  		Context("when the snapshot directory is passed", func() {
   328  			BeforeEach(func() {
   329  				tmpdir, err := ioutil.TempDir(os.TempDir(), "garden-server-test")
   330  				Expect(err).ToNot(HaveOccurred())
   331  
   332  				snapshotsPath = path.Join(tmpdir, "snapshots")
   333  			})
   334  
   335  			JustBeforeEach(func() {
   336  				err := linuxBackend.Start()
   337  				Expect(err).ToNot(HaveOccurred())
   338  			})
   339  
   340  			It("takes a snapshot of each container", func() {
   341  				linuxBackend.Stop()
   342  
   343  				Expect(container1.SnapshotCallCount()).To(Equal(1))
   344  				Expect(container2.SnapshotCallCount()).To(Equal(1))
   345  			})
   346  
   347  			It("cleans up each container", func() {
   348  				linuxBackend.Stop()
   349  
   350  				Expect(container1.CleanupCallCount()).To(Equal(1))
   351  				Expect(container2.CleanupCallCount()).To(Equal(1))
   352  			})
   353  		})
   354  	})
   355  
   356  	Describe("Capacity", func() {
   357  		It("returns the right capacity values", func() {
   358  			fakeSystemInfo.TotalMemoryReturns(1111, nil)
   359  			fakeSystemInfo.TotalDiskReturns(2222, nil)
   360  			fakeResourcePool.MaxContainersReturns(42)
   361  
   362  			capacity, err := linuxBackend.Capacity()
   363  			Expect(err).ToNot(HaveOccurred())
   364  
   365  			Expect(capacity.MemoryInBytes).To(Equal(uint64(1111)))
   366  			Expect(capacity.DiskInBytes).To(Equal(uint64(2222)))
   367  			Expect(capacity.MaxContainers).To(Equal(uint64(42)))
   368  		})
   369  
   370  		Context("when the max containers argument is set", func() {
   371  			Context("and pool.MaxContainers is lower", func() {
   372  				BeforeEach(func() {
   373  					maxContainers = 60
   374  					fakeResourcePool.MaxContainersReturns(40)
   375  				})
   376  
   377  				It("returns the pool.MaxContainers", func() {
   378  					capacity, err := linuxBackend.Capacity()
   379  					Expect(err).ToNot(HaveOccurred())
   380  					Expect(capacity.MaxContainers).To(Equal(uint64(40)))
   381  				})
   382  			})
   383  			Context("and pool.MaxContainers is higher", func() {
   384  				BeforeEach(func() {
   385  					maxContainers = 50
   386  					fakeResourcePool.MaxContainersReturns(60)
   387  				})
   388  
   389  				It("returns the max containers argument", func() {
   390  					capacity, err := linuxBackend.Capacity()
   391  					Expect(err).ToNot(HaveOccurred())
   392  					Expect(capacity.MaxContainers).To(Equal(uint64(50)))
   393  				})
   394  			})
   395  		})
   396  
   397  		Context("when getting memory info fails", func() {
   398  			disaster := errors.New("oh no!")
   399  
   400  			BeforeEach(func() {
   401  				fakeSystemInfo.TotalMemoryReturns(0, disaster)
   402  			})
   403  
   404  			It("returns the error", func() {
   405  				_, err := linuxBackend.Capacity()
   406  				Expect(err).To(Equal(disaster))
   407  			})
   408  		})
   409  
   410  		Context("when getting disk info fails", func() {
   411  			disaster := errors.New("oh no!")
   412  
   413  			BeforeEach(func() {
   414  				fakeSystemInfo.TotalMemoryReturns(0, disaster)
   415  			})
   416  
   417  			It("returns the error", func() {
   418  				_, err := linuxBackend.Capacity()
   419  				Expect(err).To(Equal(disaster))
   420  			})
   421  		})
   422  	})
   423  
   424  	Describe("Create", func() {
   425  		It("acquires container resources from the pool", func() {
   426  			Expect(fakeResourcePool.AcquireCallCount()).To(Equal(0))
   427  
   428  			spec := garden.ContainerSpec{Handle: "foo"}
   429  
   430  			_, err := linuxBackend.Create(spec)
   431  			Expect(err).ToNot(HaveOccurred())
   432  
   433  			Expect(fakeResourcePool.AcquireArgsForCall(0)).To(Equal(spec))
   434  		})
   435  
   436  		It("starts the container", func() {
   437  			fakeContainer := registerTestContainer(newTestContainer(
   438  				linux_backend.LinuxContainerSpec{
   439  					ContainerSpec: garden.ContainerSpec{Handle: "foo"},
   440  				},
   441  			))
   442  
   443  			returnedContainer, err := linuxBackend.Create(garden.ContainerSpec{Handle: "foo"})
   444  			Expect(err).ToNot(HaveOccurred())
   445  
   446  			Expect(returnedContainer).To(Equal(fakeContainer))
   447  			Expect(fakeContainer.StartCallCount()).To(Equal(1))
   448  		})
   449  
   450  		Context("when starting the container fails", func() {
   451  			It("destroys the container", func() {
   452  				container := registerTestContainer(newTestContainer(
   453  					linux_backend.LinuxContainerSpec{
   454  						ContainerSpec: garden.ContainerSpec{Handle: "disastrous"},
   455  					},
   456  				))
   457  
   458  				container.StartReturns(errors.New("insufficient banana!"))
   459  
   460  				_, err := linuxBackend.Create(garden.ContainerSpec{Handle: "disastrous"})
   461  				Expect(err).To(HaveOccurred())
   462  				Expect(fakeResourcePool.ReleaseCallCount()).To(Equal(1))
   463  				Expect(fakeResourcePool.ReleaseArgsForCall(0).Handle).To(Equal("disastrous"))
   464  			})
   465  		})
   466  
   467  		It("registers the container", func() {
   468  			container, err := linuxBackend.Create(garden.ContainerSpec{})
   469  			Expect(err).ToNot(HaveOccurred())
   470  
   471  			foundContainer, err := linuxBackend.Lookup(container.Handle())
   472  			Expect(err).ToNot(HaveOccurred())
   473  
   474  			Expect(foundContainer).To(Equal(container))
   475  		})
   476  
   477  		Context("when creating the container fails", func() {
   478  			disaster := errors.New("failed to create")
   479  
   480  			BeforeEach(func() {
   481  				fakeResourcePool.AcquireReturns(linux_backend.LinuxContainerSpec{}, disaster)
   482  			})
   483  
   484  			It("returns the error", func() {
   485  				container, err := linuxBackend.Create(garden.ContainerSpec{})
   486  				Expect(err).To(HaveOccurred())
   487  				Expect(err).To(Equal(disaster))
   488  
   489  				Expect(container).To(BeNil())
   490  			})
   491  		})
   492  
   493  		Context("when a container with the given handle already exists", func() {
   494  			It("returns a HandleExistsError", func() {
   495  				_, err := linuxBackend.Create(garden.ContainerSpec{Handle: "foo-handle"})
   496  				Expect(err).ToNot(HaveOccurred())
   497  
   498  				_, err = linuxBackend.Create(garden.ContainerSpec{Handle: "foo-handle"})
   499  				Expect(err).To(Equal(linux_backend.HandleExistsError{"foo-handle"}))
   500  			})
   501  		})
   502  
   503  		Context("when a container acquires the same IP address as an existing container", func() {
   504  			var fakeContainer *fakes.FakeContainer
   505  
   506  			JustBeforeEach(func() {
   507  				fakeContainer = registerTestContainer(newTestContainer(linux_backend.LinuxContainerSpec{
   508  					ContainerSpec: garden.ContainerSpec{Handle: "foo"},
   509  				}))
   510  				containerRepo.Add(fakeContainer)
   511  				fakeContainer.InfoReturns(garden.ContainerInfo{
   512  					ContainerIP: "192.0.2.1",
   513  				}, nil)
   514  			})
   515  
   516  			Context("when a container in the repo returns an error from Info", func() {
   517  				JustBeforeEach(func() {
   518  					fakeContainer.InfoReturns(garden.ContainerInfo{}, errors.New("info-errored"))
   519  				})
   520  
   521  				It("returns an error", func() {
   522  					_, err := linuxBackend.Create(garden.ContainerSpec{})
   523  					Expect(err).To(HaveOccurred())
   524  				})
   525  
   526  				It("releases resources", func() {
   527  					_, err := linuxBackend.Create(garden.ContainerSpec{})
   528  					Expect(err).To(HaveOccurred())
   529  					Expect(fakeResourcePool.ReleaseCallCount()).To(Equal(1))
   530  				})
   531  			})
   532  
   533  			It("returns an error", func() {
   534  				_, err := linuxBackend.Create(garden.ContainerSpec{})
   535  				Expect(err).To(MatchError("IP address 192.0.2.1 has already been acquired by container 'foo' - garden-linux may be in an unexpected state"))
   536  			})
   537  
   538  			It("does not start the container", func() {
   539  				_, err := linuxBackend.Create(garden.ContainerSpec{Handle: "foo"})
   540  				Expect(err).To(HaveOccurred())
   541  
   542  				Expect(fakeContainer.StartCallCount()).To(Equal(0))
   543  			})
   544  
   545  			It("releases resources", func() {
   546  				_, err := linuxBackend.Create(garden.ContainerSpec{})
   547  				Expect(err).To(HaveOccurred())
   548  				Expect(fakeResourcePool.ReleaseCallCount()).To(Equal(1))
   549  			})
   550  		})
   551  
   552  		Context("when starting the container fails", func() {
   553  			disaster := errors.New("failed to start")
   554  
   555  			BeforeEach(func() {
   556  				container := new(fakes.FakeContainer)
   557  				fakeContainerProvider.ProvideContainerReturns(container)
   558  				container.StartReturns(disaster)
   559  			})
   560  
   561  			It("returns the error", func() {
   562  				container, err := linuxBackend.Create(garden.ContainerSpec{})
   563  				Expect(err).To(HaveOccurred())
   564  				Expect(err).To(Equal(disaster))
   565  
   566  				Expect(container).To(BeNil())
   567  			})
   568  
   569  			It("does not register the container", func() {
   570  				_, err := linuxBackend.Create(garden.ContainerSpec{})
   571  				Expect(err).To(HaveOccurred())
   572  
   573  				containers, err := linuxBackend.Containers(nil)
   574  				Expect(err).ToNot(HaveOccurred())
   575  
   576  				Expect(containers).To(BeEmpty())
   577  			})
   578  		})
   579  
   580  		Context("when the max containers parameter is set", func() {
   581  			BeforeEach(func() {
   582  				maxContainers = 2
   583  			})
   584  
   585  			It("obeys the limit", func() {
   586  				_, err := linuxBackend.Create(garden.ContainerSpec{Handle: "container1"})
   587  				Expect(err).ToNot(HaveOccurred())
   588  
   589  				_, err = linuxBackend.Create(garden.ContainerSpec{Handle: "container2"})
   590  				Expect(err).ToNot(HaveOccurred())
   591  
   592  				_, err = linuxBackend.Create(garden.ContainerSpec{Handle: "container3"})
   593  				Expect(err).To(MatchError("cannot create more than 2 containers"))
   594  			})
   595  		})
   596  
   597  		Context("when limits are set in the container spec", func() {
   598  			var containerSpec garden.ContainerSpec
   599  			var container *fakes.FakeContainer
   600  
   601  			BeforeEach(func() {
   602  				container = new(fakes.FakeContainer)
   603  				fakeContainerProvider.ProvideContainerReturns(container)
   604  
   605  				containerSpec = garden.ContainerSpec{
   606  					Handle: "limits",
   607  					Limits: garden.Limits{
   608  						Disk: garden.DiskLimits{
   609  							InodeSoft: 1,
   610  							InodeHard: 2,
   611  							ByteSoft:  3,
   612  							ByteHard:  4,
   613  							Scope:     garden.DiskLimitScopeExclusive,
   614  						},
   615  						Memory: garden.MemoryLimits{
   616  							LimitInBytes: 1024,
   617  						},
   618  					},
   619  				}
   620  			})
   621  
   622  			It("applies the limits", func() {
   623  				_, err := linuxBackend.Create(containerSpec)
   624  				Expect(err).ToNot(HaveOccurred())
   625  
   626  				Expect(container.LimitCPUCallCount()).To(Equal(0))
   627  				Expect(container.LimitBandwidthCallCount()).To(Equal(0))
   628  
   629  				Expect(container.LimitDiskCallCount()).To(Equal(1))
   630  				Expect(container.LimitDiskArgsForCall(0)).To(Equal(containerSpec.Limits.Disk))
   631  
   632  				Expect(container.LimitMemoryCallCount()).To(Equal(1))
   633  				Expect(container.LimitMemoryArgsForCall(0)).To(Equal(containerSpec.Limits.Memory))
   634  			})
   635  
   636  			Context("when applying limits fails", func() {
   637  				limitErr := errors.New("failed to limit")
   638  
   639  				BeforeEach(func() {
   640  					container.LimitDiskReturns(limitErr)
   641  				})
   642  
   643  				It("returns the error", func() {
   644  					_, err := linuxBackend.Create(containerSpec)
   645  					Expect(err).To(MatchError(limitErr))
   646  				})
   647  			})
   648  		})
   649  	})
   650  
   651  	Describe("Destroy", func() {
   652  		var container *fakes.FakeContainer
   653  
   654  		resources := linux_backend.LinuxContainerSpec{ID: "something"}
   655  
   656  		JustBeforeEach(func() {
   657  			container = new(fakes.FakeContainer)
   658  			container.HandleReturns("some-handle")
   659  			container.ResourceSpecReturns(resources)
   660  
   661  			containerRepo.Add(container)
   662  		})
   663  
   664  		It("removes the given container's resoureces from the pool", func() {
   665  			Expect(fakeResourcePool.ReleaseCallCount()).To(Equal(0))
   666  
   667  			err := linuxBackend.Destroy("some-handle")
   668  			Expect(err).ToNot(HaveOccurred())
   669  
   670  			Expect(fakeResourcePool.ReleaseCallCount()).To(Equal(1))
   671  			Expect(fakeResourcePool.ReleaseArgsForCall(0)).To(Equal(resources))
   672  		})
   673  
   674  		It("unregisters the container", func() {
   675  			err := linuxBackend.Destroy("some-handle")
   676  			Expect(err).ToNot(HaveOccurred())
   677  
   678  			_, err = linuxBackend.Lookup("some-handle")
   679  			Expect(err).To(HaveOccurred())
   680  			Expect(err).To(MatchError(garden.ContainerNotFoundError{"some-handle"}))
   681  		})
   682  
   683  		Context("when the container does not exist", func() {
   684  			It("returns ContainerNotFoundError", func() {
   685  				err := linuxBackend.Destroy("bogus-handle")
   686  				Expect(err).To(HaveOccurred())
   687  				Expect(err).To(Equal(garden.ContainerNotFoundError{"bogus-handle"}))
   688  			})
   689  		})
   690  
   691  		Context("when destroying the container fails", func() {
   692  			disaster := errors.New("failed to destroy")
   693  
   694  			BeforeEach(func() {
   695  				fakeResourcePool.ReleaseReturns(disaster)
   696  			})
   697  
   698  			It("returns the error", func() {
   699  				err := linuxBackend.Destroy("some-handle")
   700  				Expect(err).To(HaveOccurred())
   701  				Expect(err).To(Equal(disaster))
   702  			})
   703  
   704  			It("does not unregister the container", func() {
   705  				err := linuxBackend.Destroy("some-handle")
   706  				Expect(err).To(HaveOccurred())
   707  
   708  				foundContainer, err := linuxBackend.Lookup("some-handle")
   709  				Expect(err).ToNot(HaveOccurred())
   710  				Expect(foundContainer).To(Equal(container))
   711  			})
   712  		})
   713  	})
   714  
   715  	Describe("BulkInfo", func() {
   716  		newContainer := func(handle string) *fakes.FakeContainer {
   717  			fakeContainer := &fakes.FakeContainer{}
   718  			fakeContainer.HandleReturns(handle)
   719  			fakeContainer.InfoReturns(
   720  				garden.ContainerInfo{
   721  					HostIP: "hostip for " + handle,
   722  				},
   723  				nil,
   724  			)
   725  			return fakeContainer
   726  		}
   727  
   728  		container1 := newContainer("handle1")
   729  		container2 := newContainer("handle2")
   730  		handles := []string{"handle1", "handle2"}
   731  
   732  		BeforeEach(func() {
   733  			containerRepo.Add(container1)
   734  			containerRepo.Add(container2)
   735  		})
   736  
   737  		It("returns info about containers", func() {
   738  			bulkInfo, err := linuxBackend.BulkInfo(handles)
   739  			Expect(err).ToNot(HaveOccurred())
   740  
   741  			Expect(bulkInfo).To(Equal(map[string]garden.ContainerInfoEntry{
   742  				container1.Handle(): garden.ContainerInfoEntry{
   743  					Info: garden.ContainerInfo{
   744  						HostIP: "hostip for handle1",
   745  					},
   746  				},
   747  				container2.Handle(): garden.ContainerInfoEntry{
   748  					Info: garden.ContainerInfo{
   749  						HostIP: "hostip for handle2",
   750  					},
   751  				},
   752  			}))
   753  		})
   754  
   755  		Context("when not all of the handles in the system are requested", func() {
   756  			handles := []string{"handle2"}
   757  
   758  			It("returns info about the specified containers", func() {
   759  				bulkInfo, err := linuxBackend.BulkInfo(handles)
   760  				Expect(err).ToNot(HaveOccurred())
   761  
   762  				Expect(bulkInfo).To(Equal(map[string]garden.ContainerInfoEntry{
   763  					container2.Handle(): garden.ContainerInfoEntry{
   764  						Info: garden.ContainerInfo{
   765  							HostIP: "hostip for handle2",
   766  						},
   767  					},
   768  				}))
   769  			})
   770  		})
   771  
   772  		Context("when getting one of the infos for a container fails", func() {
   773  			handles := []string{"handle1", "handle2"}
   774  
   775  			BeforeEach(func() {
   776  				container2.InfoReturns(garden.ContainerInfo{}, errors.New("Oh no!"))
   777  			})
   778  
   779  			It("returns the err for the failed container", func() {
   780  				bulkInfo, err := linuxBackend.BulkInfo(handles)
   781  				Expect(err).ToNot(HaveOccurred())
   782  
   783  				Expect(bulkInfo).To(Equal(map[string]garden.ContainerInfoEntry{
   784  					container1.Handle(): garden.ContainerInfoEntry{
   785  						Info: garden.ContainerInfo{
   786  							HostIP: "hostip for handle1",
   787  						},
   788  					},
   789  					container2.Handle(): garden.ContainerInfoEntry{
   790  						Err: garden.NewError("Oh no!"),
   791  					},
   792  				}))
   793  			})
   794  		})
   795  	})
   796  
   797  	Describe("BulkMetrics", func() {
   798  		newContainer := func(n uint64) *fakes.FakeContainer {
   799  			fakeContainer := &fakes.FakeContainer{}
   800  			fakeContainer.HandleReturns(fmt.Sprintf("handle%d", n))
   801  			fakeContainer.MetricsReturns(
   802  				garden.Metrics{
   803  					DiskStat: garden.ContainerDiskStat{
   804  						TotalInodesUsed: n,
   805  					},
   806  				},
   807  				nil,
   808  			)
   809  			return fakeContainer
   810  		}
   811  
   812  		container1 := newContainer(1)
   813  		container2 := newContainer(2)
   814  		handles := []string{"handle1", "handle2"}
   815  
   816  		BeforeEach(func() {
   817  			containerRepo.Add(container1)
   818  			containerRepo.Add(container2)
   819  		})
   820  
   821  		It("returns info about containers", func() {
   822  			bulkMetrics, err := linuxBackend.BulkMetrics(handles)
   823  			Expect(err).ToNot(HaveOccurred())
   824  
   825  			Expect(bulkMetrics).To(Equal(map[string]garden.ContainerMetricsEntry{
   826  				container1.Handle(): garden.ContainerMetricsEntry{
   827  					Metrics: garden.Metrics{
   828  						DiskStat: garden.ContainerDiskStat{
   829  							TotalInodesUsed: 1,
   830  						},
   831  					},
   832  				},
   833  				container2.Handle(): garden.ContainerMetricsEntry{
   834  					Metrics: garden.Metrics{
   835  						DiskStat: garden.ContainerDiskStat{
   836  							TotalInodesUsed: 2,
   837  						},
   838  					},
   839  				},
   840  			}))
   841  		})
   842  
   843  		Context("when not all of the handles in the system are requested", func() {
   844  			handles := []string{"handle2"}
   845  
   846  			It("returns info about the specified containers", func() {
   847  				bulkMetrics, err := linuxBackend.BulkMetrics(handles)
   848  				Expect(err).ToNot(HaveOccurred())
   849  
   850  				Expect(bulkMetrics).To(Equal(map[string]garden.ContainerMetricsEntry{
   851  					container2.Handle(): garden.ContainerMetricsEntry{
   852  						Metrics: garden.Metrics{
   853  							DiskStat: garden.ContainerDiskStat{
   854  								TotalInodesUsed: 2,
   855  							},
   856  						},
   857  					},
   858  				}))
   859  			})
   860  		})
   861  
   862  		Context("when getting one of the infos for a container fails", func() {
   863  			handles := []string{"handle1", "handle2"}
   864  
   865  			BeforeEach(func() {
   866  				container2.MetricsReturns(garden.Metrics{}, errors.New("Oh no!"))
   867  			})
   868  
   869  			It("returns the err for the failed container", func() {
   870  				bulkMetrics, err := linuxBackend.BulkMetrics(handles)
   871  				Expect(err).ToNot(HaveOccurred())
   872  
   873  				Expect(bulkMetrics).To(Equal(map[string]garden.ContainerMetricsEntry{
   874  					container1.Handle(): garden.ContainerMetricsEntry{
   875  						Metrics: garden.Metrics{
   876  							DiskStat: garden.ContainerDiskStat{
   877  								TotalInodesUsed: 1,
   878  							},
   879  						},
   880  					},
   881  					container2.Handle(): garden.ContainerMetricsEntry{
   882  						Err: garden.NewError("Oh no!"),
   883  					},
   884  				}))
   885  			})
   886  		})
   887  	})
   888  
   889  	Describe("Lookup", func() {
   890  		It("returns the container", func() {
   891  			container, err := linuxBackend.Create(garden.ContainerSpec{})
   892  			Expect(err).ToNot(HaveOccurred())
   893  
   894  			foundContainer, err := linuxBackend.Lookup(container.Handle())
   895  			Expect(err).ToNot(HaveOccurred())
   896  
   897  			Expect(foundContainer).To(Equal(container))
   898  		})
   899  
   900  		Context("when the handle is not found", func() {
   901  			It("returns ContainerNotFoundError", func() {
   902  				foundContainer, err := linuxBackend.Lookup("bogus-handle")
   903  				Expect(err).To(HaveOccurred())
   904  				Expect(foundContainer).To(BeNil())
   905  
   906  				Expect(err).To(Equal(garden.ContainerNotFoundError{"bogus-handle"}))
   907  			})
   908  		})
   909  	})
   910  
   911  	Describe("Containers", func() {
   912  		It("returns a list of all existing containers", func() {
   913  			container1, err := linuxBackend.Create(garden.ContainerSpec{Handle: "container-1"})
   914  			Expect(err).ToNot(HaveOccurred())
   915  
   916  			container2, err := linuxBackend.Create(garden.ContainerSpec{Handle: "container-2"})
   917  			Expect(err).ToNot(HaveOccurred())
   918  
   919  			containers, err := linuxBackend.Containers(nil)
   920  			Expect(err).ToNot(HaveOccurred())
   921  
   922  			Expect(containers).To(ContainElement(container1))
   923  			Expect(containers).To(ContainElement(container2))
   924  		})
   925  
   926  		Context("when given properties to filter by", func() {
   927  			It("returns only containers with matching properties", func() {
   928  				container1, err := linuxBackend.Create(garden.ContainerSpec{
   929  					Handle:     "container-1",
   930  					Properties: garden.Properties{"a": "b"},
   931  				})
   932  				Expect(err).ToNot(HaveOccurred())
   933  
   934  				container2, err := linuxBackend.Create(garden.ContainerSpec{
   935  					Handle:     "container-2",
   936  					Properties: garden.Properties{"a": "b"},
   937  				})
   938  				Expect(err).ToNot(HaveOccurred())
   939  
   940  				container3, err := linuxBackend.Create(garden.ContainerSpec{
   941  					Handle:     "container-3",
   942  					Properties: garden.Properties{"a": "b", "c": "d", "e": "f"},
   943  				})
   944  				Expect(err).ToNot(HaveOccurred())
   945  
   946  				containers, err := linuxBackend.Containers(
   947  					garden.Properties{"a": "b", "e": "f"},
   948  				)
   949  				Expect(err).ToNot(HaveOccurred())
   950  
   951  				Expect(containers).ToNot(ContainElement(container1))
   952  				Expect(containers).ToNot(ContainElement(container2))
   953  				Expect(containers).To(ContainElement(container3))
   954  			})
   955  		})
   956  
   957  		Describe("logging", func() {
   958  			It("should log the entry and exit when there are containers", func() {
   959  				_, err := linuxBackend.Create(garden.ContainerSpec{
   960  					Handle:     "container-1",
   961  					Properties: garden.Properties{"a": "b"},
   962  				})
   963  				Expect(err).ToNot(HaveOccurred())
   964  
   965  				_, err = linuxBackend.Create(garden.ContainerSpec{
   966  					Handle:     "container-2",
   967  					Properties: garden.Properties{"a": "b"},
   968  				})
   969  				Expect(err).ToNot(HaveOccurred())
   970  
   971  				_, err = linuxBackend.Containers(
   972  					garden.Properties{"a": "b"},
   973  				)
   974  				Expect(err).ToNot(HaveOccurred())
   975  
   976  				Expect(logger.LogMessages()).To(ConsistOf("test.backend.containers.started", "test.backend.containers.matched", "test.backend.containers.matched", "test.backend.containers.ending"))
   977  
   978  				logs := logger.Logs()
   979  				Expect(logs[3].Data["handles"]).To(ConsistOf("container-1", "container-2"))
   980  			})
   981  
   982  			It("should log the entry and exit when there are no containers", func() {
   983  				_, err := linuxBackend.Containers(
   984  					garden.Properties{"a": "b", "e": "f"},
   985  				)
   986  				Expect(err).ToNot(HaveOccurred())
   987  
   988  				Expect(logger.LogMessages()).To(ConsistOf("test.backend.containers.started", "test.backend.containers.ending"))
   989  			})
   990  		})
   991  	})
   992  
   993  	Describe("GraceTime", func() {
   994  		It("returns the container's grace time", func() {
   995  			container, err := linuxBackend.Create(garden.ContainerSpec{
   996  				GraceTime: time.Second,
   997  			})
   998  			Expect(err).ToNot(HaveOccurred())
   999  
  1000  			Expect(linuxBackend.GraceTime(container)).To(Equal(time.Second))
  1001  		})
  1002  	})
  1003  })