Пример #1
0
		err := dbConn.Close()
		Expect(err).NotTo(HaveOccurred())

		err = listener.Close()
		Expect(err).NotTo(HaveOccurred())
	})

	It("can keep track of workers", func() {
		Expect(database.Workers()).To(BeEmpty())

		infoA := db.WorkerInfo{
			Name:             "workerName1",
			GardenAddr:       "1.2.3.4:7777",
			BaggageclaimURL:  "5.6.7.8:7788",
			ActiveContainers: 42,
			ResourceTypes: []atc.WorkerResourceType{
				{Type: "some-resource-a", Image: "some-image-a"},
			},
			Platform: "webos",
			Tags:     []string{"palm", "was", "great"},
		}

		infoB := db.WorkerInfo{
			Name:             "1.2.3.4:8888",
			GardenAddr:       "1.2.3.4:8888",
			ActiveContainers: 42,
			ResourceTypes: []atc.WorkerResourceType{
				{Type: "some-resource-b", Image: "some-image-b"},
			},
			Platform: "plan9",
			Tags:     []string{"russ", "cox", "was", "here"},
Пример #2
0
func dbSharedBehavior(database *dbSharedBehaviorInput) func() {
	return func() {
		Describe("CreatePipe", func() {
			It("saves a pipe to the db", func() {
				myGuid, err := uuid.NewV4()
				Expect(err).NotTo(HaveOccurred())

				err = database.CreatePipe(myGuid.String(), "a-url")
				Expect(err).NotTo(HaveOccurred())

				pipe, err := database.GetPipe(myGuid.String())
				Expect(err).NotTo(HaveOccurred())
				Expect(pipe.ID).To(Equal(myGuid.String()))
				Expect(pipe.URL).To(Equal("a-url"))
			})
		})

		It("can get a build's inputs", func() {
			build, err := database.PipelineDB.CreateJobBuild("some-job")
			Expect(err).ToNot(HaveOccurred())

			expectedBuildInput, err := database.PipelineDB.SaveBuildInput(build.ID, db.BuildInput{
				Name: "some-input",
				VersionedResource: db.VersionedResource{
					Resource: "some-resource",
					Type:     "some-type",
					Version: db.Version{
						"some": "version",
					},
					Metadata: []db.MetadataField{
						{
							Name:  "meta1",
							Value: "data1",
						},
						{
							Name:  "meta2",
							Value: "data2",
						},
					},
					PipelineName: "some-pipeline",
				},
			})
			Expect(err).ToNot(HaveOccurred())

			actualBuildInput, err := database.DB.GetBuildInputVersionedResouces(build.ID)
			Expect(err).ToNot(HaveOccurred())
			Expect(len(actualBuildInput)).To(Equal(1))
			Expect(actualBuildInput[0]).To(Equal(expectedBuildInput))
		})

		It("can get a build's output", func() {
			build, err := database.PipelineDB.CreateJobBuild("some-job")
			Expect(err).ToNot(HaveOccurred())

			expectedBuildOutput, err := database.PipelineDB.SaveBuildOutput(build.ID, db.VersionedResource{
				Resource: "some-explicit-resource",
				Type:     "some-type",
				Version: db.Version{
					"some": "version",
				},
				Metadata: []db.MetadataField{
					{
						Name:  "meta1",
						Value: "data1",
					},
					{
						Name:  "meta2",
						Value: "data2",
					},
				},
				PipelineName: "some-pipeline",
			}, true)

			_, err = database.PipelineDB.SaveBuildOutput(build.ID, db.VersionedResource{
				Resource: "some-implicit-resource",
				Type:     "some-type",
				Version: db.Version{
					"some": "version",
				},
				Metadata: []db.MetadataField{
					{
						Name:  "meta1",
						Value: "data1",
					},
					{
						Name:  "meta2",
						Value: "data2",
					},
				},
				PipelineName: "some-pipeline",
			}, false)
			Expect(err).ToNot(HaveOccurred())

			actualBuildOutput, err := database.DB.GetBuildOutputVersionedResouces(build.ID)
			Expect(err).ToNot(HaveOccurred())
			Expect(len(actualBuildOutput)).To(Equal(1))
			Expect(actualBuildOutput[0]).To(Equal(expectedBuildOutput))
		})

		It("can keep track of volume data", func() {
			By("allowing you to insert")
			expectedVolume := db.Volume{
				WorkerName:      "some-worker",
				TTL:             time.Hour,
				Handle:          "some-volume-handle",
				ResourceVersion: atc.Version{"some": "version"},
				ResourceHash:    "some-hash",
			}
			err := database.InsertVolume(expectedVolume)
			Expect(err).NotTo(HaveOccurred())

			By("getting volume information from the db")
			volumes, err := database.GetVolumes()
			Expect(err).NotTo(HaveOccurred())
			Expect(len(volumes)).To(Equal(1))
			actualVolume := volumes[0]
			Expect(actualVolume.WorkerName).To(Equal(expectedVolume.WorkerName))
			Expect(actualVolume.TTL).To(Equal(expectedVolume.TTL))
			Expect(actualVolume.ExpiresIn).To(BeNumerically("~", expectedVolume.TTL, time.Second))
			Expect(actualVolume.Handle).To(Equal(expectedVolume.Handle))
			Expect(actualVolume.ResourceVersion).To(Equal(expectedVolume.ResourceVersion))
			Expect(actualVolume.ResourceHash).To(Equal(expectedVolume.ResourceHash))

			By("allowing you to call insert idempotently")
			err = database.InsertVolume(expectedVolume)
			Expect(err).NotTo(HaveOccurred())

			By("not returning volumes that have expired")
			err = database.InsertVolume(db.Volume{
				WorkerName:      "some-worker",
				TTL:             -time.Hour,
				Handle:          "some-other-volume-handle",
				ResourceVersion: atc.Version{"some": "version"},
				ResourceHash:    "some-hash",
			})
			Expect(err).NotTo(HaveOccurred())

			volumes, err = database.GetVolumes()
			Expect(err).NotTo(HaveOccurred())
			Expect(len(volumes)).To(Equal(1))

			By("allowing you to insert the same volume handle on a different worker")
			err = database.InsertVolume(db.Volume{
				WorkerName:      "some-other-worker",
				TTL:             time.Hour,
				Handle:          "some-volume-handle",
				ResourceVersion: atc.Version{"some": "version"},
				ResourceHash:    "some-hash",
			})
			Expect(err).NotTo(HaveOccurred())
			volumes, err = database.GetVolumes()
			Expect(err).NotTo(HaveOccurred())
			Expect(len(volumes)).To(Equal(2))

			By("letting you get the ttl of a volume")
			actualTTL, err := database.GetVolumeTTL(actualVolume.Handle)
			Expect(err).NotTo(HaveOccurred())
			Expect(actualTTL).To(Equal(actualVolume.TTL))

			By("letting you update the ttl of the volume data")
			err = database.SetVolumeTTL(actualVolume, -time.Hour)
			Expect(err).NotTo(HaveOccurred())
			volumes, err = database.GetVolumes()
			Expect(err).NotTo(HaveOccurred())
			Expect(len(volumes)).To(Equal(1))
		})

		It("saves and propagates events correctly", func() {
			build, err := database.CreateOneOffBuild()
			Expect(err).NotTo(HaveOccurred())
			Expect(build.Name).To(Equal("1"))

			By("allowing you to subscribe when no events have yet occurred")
			events, err := database.GetBuildEvents(build.ID, 0)
			Expect(err).NotTo(HaveOccurred())

			defer events.Close()

			By("saving them in order")
			err = database.SaveBuildEvent(build.ID, event.Log{
				Payload: "some ",
			})
			Expect(err).NotTo(HaveOccurred())

			Expect(events.Next()).To(Equal(event.Log{
				Payload: "some ",
			}))

			err = database.SaveBuildEvent(build.ID, event.Log{
				Payload: "log",
			})
			Expect(err).NotTo(HaveOccurred())

			Expect(events.Next()).To(Equal(event.Log{
				Payload: "log",
			}))

			By("allowing you to subscribe from an offset")
			eventsFrom1, err := database.GetBuildEvents(build.ID, 1)
			Expect(err).NotTo(HaveOccurred())

			defer eventsFrom1.Close()

			Expect(eventsFrom1.Next()).To(Equal(event.Log{
				Payload: "log",
			}))

			By("notifying those waiting on events as soon as they're saved")
			nextEvent := make(chan atc.Event)
			nextErr := make(chan error)

			go func() {
				event, err := events.Next()
				if err != nil {
					nextErr <- err
				} else {
					nextEvent <- event
				}
			}()

			Consistently(nextEvent).ShouldNot(Receive())
			Consistently(nextErr).ShouldNot(Receive())

			err = database.SaveBuildEvent(build.ID, event.Log{
				Payload: "log 2",
			})
			Expect(err).NotTo(HaveOccurred())

			Eventually(nextEvent).Should(Receive(Equal(event.Log{
				Payload: "log 2",
			})))

			By("returning ErrBuildEventStreamClosed for Next calls after Close")
			events3, err := database.GetBuildEvents(build.ID, 0)
			Expect(err).NotTo(HaveOccurred())

			err = events3.Close()
			Expect(err).NotTo(HaveOccurred())

			Eventually(func() error {
				_, err := events3.Next()
				return err
			}).Should(Equal(db.ErrBuildEventStreamClosed))
		})

		It("saves and emits status events", func() {
			build, err := database.CreateOneOffBuild()
			Expect(err).NotTo(HaveOccurred())
			Expect(build.Name).To(Equal("1"))

			By("allowing you to subscribe when no events have yet occurred")
			events, err := database.GetBuildEvents(build.ID, 0)
			Expect(err).NotTo(HaveOccurred())

			defer events.Close()

			By("emitting a status event when started")
			started, err := database.StartBuild(build.ID, "engine", "metadata")
			Expect(err).NotTo(HaveOccurred())
			Expect(started).To(BeTrue())

			startedBuild, found, err := database.GetBuild(build.ID)
			Expect(err).NotTo(HaveOccurred())
			Expect(found).To(BeTrue())

			Expect(events.Next()).To(Equal(event.Status{
				Status: atc.StatusStarted,
				Time:   startedBuild.StartTime.Unix(),
			}))

			By("emitting a status event when finished")
			err = database.FinishBuild(build.ID, db.StatusSucceeded)
			Expect(err).NotTo(HaveOccurred())

			finishedBuild, found, err := database.GetBuild(build.ID)
			Expect(err).NotTo(HaveOccurred())
			Expect(found).To(BeTrue())

			Expect(events.Next()).To(Equal(event.Status{
				Status: atc.StatusSucceeded,
				Time:   finishedBuild.EndTime.Unix(),
			}))

			By("ending the stream when finished")
			_, err = events.Next()
			Expect(err).To(Equal(db.ErrEndOfBuildEventStream))
		})

		It("can keep track of workers", func() {
			Expect(database.Workers()).To(BeEmpty())

			infoA := db.WorkerInfo{
				Name:             "workerName1",
				GardenAddr:       "1.2.3.4:7777",
				BaggageclaimURL:  "5.6.7.8:7788",
				ActiveContainers: 42,
				ResourceTypes: []atc.WorkerResourceType{
					{Type: "some-resource-a", Image: "some-image-a"},
				},
				Platform: "webos",
				Tags:     []string{"palm", "was", "great"},
			}

			infoB := db.WorkerInfo{
				GardenAddr:       "1.2.3.4:8888",
				ActiveContainers: 42,
				ResourceTypes: []atc.WorkerResourceType{
					{Type: "some-resource-b", Image: "some-image-b"},
				},
				Platform: "plan9",
				Tags:     []string{"russ", "cox", "was", "here"},
			}

			By("persisting workers with no TTLs")
			err := database.SaveWorker(infoA, 0)
			Expect(err).NotTo(HaveOccurred())

			Expect(database.Workers()).To(ConsistOf(infoA))

			By("being idempotent")
			err = database.SaveWorker(infoA, 0)
			Expect(err).NotTo(HaveOccurred())

			Expect(database.Workers()).To(ConsistOf(infoA))

			By("updating attributes by name")
			infoA.GardenAddr = "1.2.3.4:9876"

			err = database.SaveWorker(infoA, 0)
			Expect(err).NotTo(HaveOccurred())

			Expect(database.Workers()).To(ConsistOf(infoA))

			By("updating attributes by address")
			infoA.Name = "someNewName"

			err = database.SaveWorker(infoA, 0)
			Expect(err).NotTo(HaveOccurred())

			Expect(database.Workers()).To(ConsistOf(infoA))

			By("expiring TTLs")
			ttl := 1 * time.Second

			err = database.SaveWorker(infoB, ttl)
			Expect(err).NotTo(HaveOccurred())

			// name is defaulted to addr
			infoBFromDB := infoB
			infoBFromDB.Name = "1.2.3.4:8888"

			Consistently(database.Workers, ttl/2).Should(ConsistOf(infoA, infoBFromDB))
			Eventually(database.Workers, 2*ttl).Should(ConsistOf(infoA))

			By("overwriting TTLs")
			err = database.SaveWorker(infoA, ttl)
			Expect(err).NotTo(HaveOccurred())

			Consistently(database.Workers, ttl/2).Should(ConsistOf(infoA))
			Eventually(database.Workers, 2*ttl).Should(BeEmpty())

			By("updating attributes by name with ttls")
			ttl = 1 * time.Hour
			err = database.SaveWorker(infoA, ttl)
			Expect(err).NotTo(HaveOccurred())

			Expect(database.Workers()).To(ConsistOf(infoA))

			infoA.GardenAddr = "1.2.3.4:1234"

			err = database.SaveWorker(infoA, ttl)
			Expect(err).NotTo(HaveOccurred())

			Expect(database.Workers()).To(ConsistOf(infoA))
		})

		It("it can keep track of a worker", func() {
			By("calling it with worker names that do not exist")

			workerInfo, found, err := database.GetWorker("nope")
			Expect(err).NotTo(HaveOccurred())
			Expect(workerInfo).To(Equal(db.WorkerInfo{}))
			Expect(found).To(BeFalse())

			infoA := db.WorkerInfo{
				GardenAddr:       "1.2.3.4:7777",
				BaggageclaimURL:  "http://5.6.7.8:7788",
				ActiveContainers: 42,
				ResourceTypes: []atc.WorkerResourceType{
					{Type: "some-resource-a", Image: "some-image-a"},
				},
				Platform: "webos",
				Tags:     []string{"palm", "was", "great"},
				Name:     "workerName1",
			}

			infoB := db.WorkerInfo{
				GardenAddr:       "1.2.3.4:8888",
				BaggageclaimURL:  "http://5.6.7.8:8899",
				ActiveContainers: 42,
				ResourceTypes: []atc.WorkerResourceType{
					{Type: "some-resource-b", Image: "some-image-b"},
				},
				Platform: "plan9",
				Tags:     []string{"russ", "cox", "was", "here"},
				Name:     "workerName2",
			}

			infoC := db.WorkerInfo{
				GardenAddr:       "1.2.3.5:8888",
				BaggageclaimURL:  "http://5.6.7.9:8899",
				ActiveContainers: 42,
				ResourceTypes: []atc.WorkerResourceType{
					{Type: "some-resource-b", Image: "some-image-b"},
				},
				Platform: "plan9",
				Tags:     []string{"russ", "cox", "was", "here"},
			}

			err = database.SaveWorker(infoA, 0)
			Expect(err).NotTo(HaveOccurred())

			err = database.SaveWorker(infoB, 0)
			Expect(err).NotTo(HaveOccurred())

			err = database.SaveWorker(infoC, 0)
			Expect(err).NotTo(HaveOccurred())

			By("returning one workerinfo by worker name")
			workerInfo, found, err = database.GetWorker("workerName2")
			Expect(err).NotTo(HaveOccurred())
			Expect(found).To(BeTrue())
			Expect(workerInfo).To(Equal(infoB))

			By("returning one workerinfo by addr if name is null")
			workerInfo, found, err = database.GetWorker("1.2.3.5:8888")
			Expect(err).NotTo(HaveOccurred())
			Expect(found).To(BeTrue())
			Expect(workerInfo.Name).To(Equal("1.2.3.5:8888"))

			By("expiring TTLs")
			ttl := 1 * time.Second

			err = database.SaveWorker(infoA, ttl)
			Expect(err).NotTo(HaveOccurred())

			workerFound := func() bool {
				_, found, _ = database.GetWorker("workerName1")
				return found
			}

			Consistently(workerFound, ttl/2).Should(BeTrue())
			Eventually(workerFound, 2*ttl).Should(BeFalse())
		})

		It("can create and get a container info object", func() {
			expectedContainer := db.Container{
				ContainerIdentifier: db.ContainerIdentifier{
					Name:                 "some-container",
					PipelineName:         "some-pipeline",
					BuildID:              123,
					Type:                 db.ContainerTypeTask,
					WorkerName:           "some-worker",
					WorkingDirectory:     "tmp/build/some-guid",
					CheckType:            "some-type",
					CheckSource:          atc.Source{"uri": "http://example.com"},
					StepLocation:         456,
					EnvironmentVariables: []string{"VAR1=val1", "VAR2=val2"},
				},
				Handle: "some-handle",
			}

			By("creating a container")
			err := database.CreateContainer(expectedContainer, time.Minute)
			Expect(err).NotTo(HaveOccurred())

			By("trying to create a container with the same handle")
			err = database.CreateContainer(db.Container{Handle: "some-handle"}, time.Second)
			Expect(err).To(HaveOccurred())

			By("getting the saved info object by h andle")
			actualContainer, found, err := database.GetContainer("some-handle")
			Expect(err).NotTo(HaveOccurred())
			Expect(found).To(BeTrue())

			Expect(actualContainer.Handle).To(Equal("some-handle"))
			Expect(actualContainer.Name).To(Equal("some-container"))
			Expect(actualContainer.PipelineName).To(Equal("some-pipeline"))
			Expect(actualContainer.BuildID).To(Equal(123))
			Expect(actualContainer.Type).To(Equal(db.ContainerTypeTask))
			Expect(actualContainer.WorkerName).To(Equal("some-worker"))
			Expect(actualContainer.WorkingDirectory).To(Equal("tmp/build/some-guid"))
			Expect(actualContainer.CheckType).To(Equal("some-type"))
			Expect(actualContainer.CheckSource).To(Equal(atc.Source{"uri": "http://example.com"}))
			Expect(actualContainer.StepLocation).To(Equal(uint(456)))
			Expect(actualContainer.EnvironmentVariables).To(Equal([]string{"VAR1=val1", "VAR2=val2"}))

			By("returning found = false when getting by a handle that does not exist")
			_, found, err = database.GetContainer("nope")
			Expect(err).NotTo(HaveOccurred())
			Expect(found).To(BeFalse())
		})

		It("can update the time to live for a container info object", func() {
			updatedTTL := 5 * time.Minute

			originalContainer := db.Container{
				ContainerIdentifier: db.ContainerIdentifier{
					Type: db.ContainerTypeTask,
				},
				Handle: "some-handle",
			}
			err := database.CreateContainer(originalContainer, time.Minute)
			Expect(err).NotTo(HaveOccurred())

			// comparisonContainer is used to get the expected expiration time in the
			// database timezone to avoid timezone errors
			comparisonContainer := db.Container{
				ContainerIdentifier: db.ContainerIdentifier{
					Type: db.ContainerTypeTask,
				},
				Handle: "comparison-handle",
			}
			err = database.CreateContainer(comparisonContainer, updatedTTL)
			Expect(err).NotTo(HaveOccurred())

			comparisonContainer, found, err := database.GetContainer("comparison-handle")
			Expect(err).NotTo(HaveOccurred())
			Expect(found).To(BeTrue())

			err = database.UpdateExpiresAtOnContainer("some-handle", updatedTTL)
			Expect(err).NotTo(HaveOccurred())

			updatedContainer, found, err := database.GetContainer("some-handle")
			Expect(err).NotTo(HaveOccurred())
			Expect(found).To(BeTrue())

			Expect(updatedContainer.ExpiresAt).To(BeTemporally("~", comparisonContainer.ExpiresAt, time.Second))
		})

		It("can reap a container", func() {
			info := db.Container{
				ContainerIdentifier: db.ContainerIdentifier{
					Type: db.ContainerTypeTask,
				},
				Handle: "some-handle",
			}

			err := database.CreateContainer(info, time.Minute)
			Expect(err).NotTo(HaveOccurred())

			_, found, err := database.GetContainer("some-handle")
			Expect(err).NotTo(HaveOccurred())
			Expect(found).To(BeTrue())

			By("reaping an existing container")
			err = database.ReapContainer("some-handle")
			Expect(err).NotTo(HaveOccurred())

			_, found, err = database.GetContainer("some-handle")
			Expect(err).NotTo(HaveOccurred())
			Expect(found).To(BeFalse())

			By("not failing if the container's already been reaped")
			err = database.ReapContainer("some-handle")
			Expect(err).NotTo(HaveOccurred())
		})

		type findContainersByIdentifierExample struct {
			containersToCreate   []db.Container
			identifierToFilerFor db.ContainerIdentifier
			expectedHandles      []string
		}

		DescribeTable("filtering containers by identifier",
			func(example findContainersByIdentifierExample) {
				var results []db.Container
				var handles []string
				var err error

				for _, containerToCreate := range example.containersToCreate {
					if containerToCreate.Type.String() == "" {
						containerToCreate.Type = db.ContainerTypeTask
					}

					err = database.CreateContainer(containerToCreate, 1*time.Minute)
					Expect(err).NotTo(HaveOccurred())
				}

				results, err = database.FindContainersByIdentifier(example.identifierToFilerFor)
				Expect(err).NotTo(HaveOccurred())

				for _, result := range results {
					handles = append(handles, result.Handle)
				}

				Expect(handles).To(ConsistOf(example.expectedHandles))

				for _, containerToDelete := range example.containersToCreate {
					err = database.DeleteContainer(containerToDelete.Handle)
					Expect(err).NotTo(HaveOccurred())
				}
			},

			Entry("returns everything when no filters are passed", findContainersByIdentifierExample{
				containersToCreate: []db.Container{
					{Handle: "a"},
					{Handle: "b"},
				},
				identifierToFilerFor: db.ContainerIdentifier{},
				expectedHandles:      []string{"a", "b"},
			}),

			Entry("does not return things that the filter doesn't match", findContainersByIdentifierExample{
				containersToCreate: []db.Container{
					{Handle: "a"},
					{Handle: "b"},
				},
				identifierToFilerFor: db.ContainerIdentifier{Name: "some-name"},
				expectedHandles:      nil,
			}),

			Entry("returns containers where the name matches", findContainersByIdentifierExample{
				containersToCreate: []db.Container{
					{Handle: "a", ContainerIdentifier: db.ContainerIdentifier{Name: "some-container"}},
					{Handle: "b", ContainerIdentifier: db.ContainerIdentifier{Name: "some-container"}},
					{Handle: "c", ContainerIdentifier: db.ContainerIdentifier{Name: "some-other"}},
				},
				identifierToFilerFor: db.ContainerIdentifier{Name: "some-container"},
				expectedHandles:      []string{"a", "b"},
			}),

			Entry("returns containers where the pipeline matches", findContainersByIdentifierExample{
				containersToCreate: []db.Container{
					{Handle: "a", ContainerIdentifier: db.ContainerIdentifier{PipelineName: "some-pipeline"}},
					{Handle: "b", ContainerIdentifier: db.ContainerIdentifier{PipelineName: "some-other"}},
					{Handle: "c", ContainerIdentifier: db.ContainerIdentifier{PipelineName: "some-pipeline"}},
				},
				identifierToFilerFor: db.ContainerIdentifier{PipelineName: "some-pipeline"},
				expectedHandles:      []string{"a", "c"},
			}),

			Entry("returns containers where the build id matches", findContainersByIdentifierExample{
				containersToCreate: []db.Container{
					{Handle: "a", ContainerIdentifier: db.ContainerIdentifier{BuildID: 1}},
					{Handle: "b", ContainerIdentifier: db.ContainerIdentifier{BuildID: 2}},
					{Handle: "c", ContainerIdentifier: db.ContainerIdentifier{BuildID: 2}},
				},
				identifierToFilerFor: db.ContainerIdentifier{BuildID: 2},
				expectedHandles:      []string{"b", "c"},
			}),

			Entry("returns containers where the type matches", findContainersByIdentifierExample{
				containersToCreate: []db.Container{
					{Handle: "a", ContainerIdentifier: db.ContainerIdentifier{Type: db.ContainerTypePut}},
					{Handle: "b", ContainerIdentifier: db.ContainerIdentifier{Type: db.ContainerTypePut}},
					{Handle: "c", ContainerIdentifier: db.ContainerIdentifier{Type: db.ContainerTypeGet}},
				},
				identifierToFilerFor: db.ContainerIdentifier{Type: db.ContainerTypePut},
				expectedHandles:      []string{"a", "b"},
			}),

			Entry("returns containers where the worker name matches", findContainersByIdentifierExample{
				containersToCreate: []db.Container{
					{Handle: "a", ContainerIdentifier: db.ContainerIdentifier{WorkerName: "some-worker"}},
					{Handle: "b", ContainerIdentifier: db.ContainerIdentifier{WorkerName: "some-worker"}},
					{Handle: "c", ContainerIdentifier: db.ContainerIdentifier{WorkerName: "other"}},
				},
				identifierToFilerFor: db.ContainerIdentifier{WorkerName: "some-worker"},
				expectedHandles:      []string{"a", "b"},
			}),

			Entry("returns containers where the check type matches", findContainersByIdentifierExample{
				containersToCreate: []db.Container{
					{Handle: "a", ContainerIdentifier: db.ContainerIdentifier{CheckType: "some-type"}},
					{Handle: "b", ContainerIdentifier: db.ContainerIdentifier{CheckType: "nope"}},
					{Handle: "c", ContainerIdentifier: db.ContainerIdentifier{CheckType: "some-type"}},
				},
				identifierToFilerFor: db.ContainerIdentifier{CheckType: "some-type"},
				expectedHandles:      []string{"a", "c"},
			}),

			Entry("returns containers where the check source matches", findContainersByIdentifierExample{
				containersToCreate: []db.Container{
					{Handle: "a", ContainerIdentifier: db.ContainerIdentifier{CheckSource: atc.Source{"some": "other-source"}}},
					{Handle: "b", ContainerIdentifier: db.ContainerIdentifier{CheckSource: atc.Source{"some": "source"}}},
					{Handle: "c", ContainerIdentifier: db.ContainerIdentifier{CheckSource: atc.Source{"some": "source"}}},
				},
				identifierToFilerFor: db.ContainerIdentifier{CheckSource: atc.Source{"some": "source"}},
				expectedHandles:      []string{"b", "c"},
			}),

			Entry("returns containers where the step location matches", findContainersByIdentifierExample{
				containersToCreate: []db.Container{
					{Handle: "a", ContainerIdentifier: db.ContainerIdentifier{StepLocation: 123}},
					{Handle: "b", ContainerIdentifier: db.ContainerIdentifier{StepLocation: 123}},
					{Handle: "c", ContainerIdentifier: db.ContainerIdentifier{StepLocation: 456}},
				},
				identifierToFilerFor: db.ContainerIdentifier{StepLocation: 123},
				expectedHandles:      []string{"a", "b"},
			}),

			Entry("returns containers where all fields match", findContainersByIdentifierExample{
				containersToCreate: []db.Container{
					{
						ContainerIdentifier: db.ContainerIdentifier{
							Name:         "some-name",
							PipelineName: "some-pipeline",
							BuildID:      123,
							Type:         db.ContainerTypeCheck,
							WorkerName:   "some-worker",
						},
						Handle: "a",
					},
					{
						ContainerIdentifier: db.ContainerIdentifier{
							Name:         "WROONG",
							PipelineName: "some-pipeline",
							BuildID:      123,
							Type:         db.ContainerTypeCheck,
							WorkerName:   "some-worker",
						},
						Handle: "b",
					},
					{
						ContainerIdentifier: db.ContainerIdentifier{
							Name:         "some-name",
							PipelineName: "some-pipeline",
							BuildID:      123,
							Type:         db.ContainerTypeCheck,
							WorkerName:   "some-worker",
						},
						Handle: "c",
					},
					{
						ContainerIdentifier: db.ContainerIdentifier{
							WorkerName: "Wat",
						},
						Handle: "d",
					},
				},
				identifierToFilerFor: db.ContainerIdentifier{
					Name:         "some-name",
					PipelineName: "some-pipeline",
					BuildID:      123,
					Type:         db.ContainerTypeCheck,
					WorkerName:   "some-worker",
				},
				expectedHandles: []string{"a", "c"},
			}),
		)

		It("can find a single container info by identifier", func() {
			expectedContainer := db.Container{
				Handle: "some-handle",
				ContainerIdentifier: db.ContainerIdentifier{
					PipelineName: "some-pipeline",
					BuildID:      123,
					Name:         "some-container",
					WorkerName:   "some-worker",
					Type:         db.ContainerTypeTask,
					CheckType:    "some-type",
					CheckSource:  atc.Source{"some": "other-source"},
				},
			}
			otherContainer := db.Container{
				Handle: "other-handle",
				ContainerIdentifier: db.ContainerIdentifier{
					Name: "other-container",
					Type: db.ContainerTypeTask,
				},
			}

			err := database.CreateContainer(expectedContainer, time.Minute)
			Expect(err).NotTo(HaveOccurred())
			err = database.CreateContainer(otherContainer, time.Minute)
			Expect(err).NotTo(HaveOccurred())

			By("returning a single matching container info")
			actualContainer, found, err := database.FindContainerByIdentifier(db.ContainerIdentifier{Name: "some-container"})

			Expect(err).NotTo(HaveOccurred())
			Expect(found).To(BeTrue())
			Expect(actualContainer.Handle).To(Equal("some-handle"))
			Expect(actualContainer.Name).To(Equal("some-container"))
			Expect(actualContainer.PipelineName).To(Equal("some-pipeline"))
			Expect(actualContainer.BuildID).To(Equal(123))
			Expect(actualContainer.Type).To(Equal(db.ContainerTypeTask))
			Expect(actualContainer.WorkerName).To(Equal("some-worker"))
			Expect(actualContainer.CheckType).To(Equal("some-type"))
			Expect(actualContainer.CheckSource).To(Equal(atc.Source{"some": "other-source"}))
			Expect(actualContainer.ExpiresAt.String()).NotTo(BeEmpty())

			By("erroring if more than one container matches the filter")
			actualContainer, found, err = database.FindContainerByIdentifier(db.ContainerIdentifier{Type: db.ContainerTypeTask})
			Expect(err).To(HaveOccurred())
			Expect(err).To(Equal(db.ErrMultipleContainersFound))
			Expect(found).To(BeFalse())
			Expect(actualContainer.Handle).To(BeEmpty())

			By("returning found of false if no containers match the filter")
			actualContainer, found, err = database.FindContainerByIdentifier(db.ContainerIdentifier{Name: "nope"})
			Expect(err).NotTo(HaveOccurred())
			Expect(found).To(BeFalse())
			Expect(actualContainer.Handle).To(BeEmpty())

			By("removing it if the TTL has expired")
			ttl := 1 * time.Second
			ttlContainer := db.Container{
				Handle: "some-ttl-handle",
				ContainerIdentifier: db.ContainerIdentifier{
					Name: "some-ttl-name",
					Type: db.ContainerTypeTask,
				},
			}

			err = database.CreateContainer(ttlContainer, -ttl)
			Expect(err).NotTo(HaveOccurred())
			_, found, err = database.FindContainerByIdentifier(db.ContainerIdentifier{Name: "some-ttl-name"})
			Expect(err).NotTo(HaveOccurred())
			Expect(found).To(BeFalse())
		})

		It("can create one-off builds with increasing names", func() {
			oneOff, err := database.CreateOneOffBuild()
			Expect(err).NotTo(HaveOccurred())
			Expect(oneOff.ID).NotTo(BeZero())
			Expect(oneOff.JobName).To(BeZero())
			Expect(oneOff.Name).To(Equal("1"))
			Expect(oneOff.Status).To(Equal(db.StatusPending))

			oneOffGot, found, err := database.GetBuild(oneOff.ID)
			Expect(err).NotTo(HaveOccurred())
			Expect(found).To(BeTrue())
			Expect(oneOffGot).To(Equal(oneOff))

			jobBuild, err := database.PipelineDB.CreateJobBuild("some-other-job")
			Expect(err).NotTo(HaveOccurred())
			Expect(jobBuild.Name).To(Equal("1"))

			nextOneOff, err := database.CreateOneOffBuild()
			Expect(err).NotTo(HaveOccurred())
			Expect(nextOneOff.ID).NotTo(BeZero())
			Expect(nextOneOff.ID).NotTo(Equal(oneOff.ID))
			Expect(nextOneOff.JobName).To(BeZero())
			Expect(nextOneOff.Name).To(Equal("2"))
			Expect(nextOneOff.Status).To(Equal(db.StatusPending))

			allBuilds, err := database.GetAllBuilds()
			Expect(err).NotTo(HaveOccurred())
			Expect(allBuilds).To(Equal([]db.Build{nextOneOff, jobBuild, oneOff}))
		})

		Describe("GetAllStartedBuilds", func() {
			var build1 db.Build
			var build2 db.Build
			BeforeEach(func() {
				var err error

				build1, err = database.CreateOneOffBuild()
				Expect(err).NotTo(HaveOccurred())

				build2, err = database.PipelineDB.CreateJobBuild("some-job")
				Expect(err).NotTo(HaveOccurred())

				_, err = database.CreateOneOffBuild()
				Expect(err).NotTo(HaveOccurred())

				started, err := database.StartBuild(build1.ID, "some-engine", "so-meta")
				Expect(err).NotTo(HaveOccurred())
				Expect(started).To(BeTrue())

				started, err = database.StartBuild(build2.ID, "some-engine", "so-meta")
				Expect(err).NotTo(HaveOccurred())
				Expect(started).To(BeTrue())
			})

			It("returns all builds that have been started, regardless of pipeline", func() {
				builds, err := database.GetAllStartedBuilds()
				Expect(err).NotTo(HaveOccurred())

				Expect(len(builds)).To(Equal(2))

				build1, found, err := database.GetBuild(build1.ID)
				Expect(err).NotTo(HaveOccurred())
				Expect(found).To(BeTrue())
				build2, found, err := database.GetBuild(build2.ID)
				Expect(err).NotTo(HaveOccurred())
				Expect(found).To(BeTrue())

				Expect(builds).To(ConsistOf(build1, build2))
			})
		})
	}
}