mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Most of the code in the filters package relates to the unmarshaling, validation and application of filters from client requests. None of this is necessary or particularly useful for Go SDK users. Move the full-fat filters package into daemon/internal and switch all the daemon code to import that package so we are free to iterate upon the code without worrying about source-code interface compatibility. Signed-off-by: Cory Snider <csnider@mirantis.com>
230 lines
6.5 KiB
Go
230 lines
6.5 KiB
Go
package daemon
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
containertypes "github.com/moby/moby/api/types/container"
|
|
"github.com/moby/moby/v2/daemon/container"
|
|
"github.com/moby/moby/v2/daemon/internal/filters"
|
|
"github.com/moby/moby/v2/daemon/internal/image"
|
|
"github.com/moby/moby/v2/daemon/server/backend"
|
|
"github.com/opencontainers/go-digest"
|
|
"gotest.tools/v3/assert"
|
|
is "gotest.tools/v3/assert/cmp"
|
|
)
|
|
|
|
var root string
|
|
|
|
func TestMain(m *testing.M) {
|
|
var err error
|
|
root, err = os.MkdirTemp("", "docker-container-test-")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer os.RemoveAll(root)
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
// This sets up a container with a name so that name filters
|
|
// work against it. It takes in a pointer to Daemon so that
|
|
// minor operations are not repeated by the caller
|
|
func setupContainerWithName(t *testing.T, name string, daemon *Daemon) *container.Container {
|
|
t.Helper()
|
|
var (
|
|
id = uuid.New().String()
|
|
computedImageID = image.ID(digest.FromString(id))
|
|
cRoot = filepath.Join(root, id)
|
|
)
|
|
if err := os.MkdirAll(cRoot, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
c := container.NewBaseContainer(id, cRoot)
|
|
// these are for passing includeContainerInList
|
|
if name[0] != '/' {
|
|
name = "/" + name
|
|
}
|
|
c.Name = name
|
|
c.State.Running = true
|
|
c.HostConfig = &containertypes.HostConfig{}
|
|
c.Created = time.Now()
|
|
|
|
// these are for passing the refreshImage reducer
|
|
c.ImageID = computedImageID
|
|
c.Config = &containertypes.Config{
|
|
Image: computedImageID.String(),
|
|
}
|
|
|
|
// this is done here to avoid requiring these
|
|
// operations n x number of containers in the
|
|
// calling function
|
|
daemon.containersReplica.Save(c)
|
|
daemon.reserveName(id, name)
|
|
|
|
return c
|
|
}
|
|
|
|
func containerListContainsName(containers []*containertypes.Summary, name string) bool {
|
|
for _, ctr := range containers {
|
|
for _, containerName := range ctr.Names {
|
|
if containerName == name {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func TestContainerList(t *testing.T) {
|
|
db, err := container.NewViewDB()
|
|
assert.NilError(t, err)
|
|
d := &Daemon{
|
|
containersReplica: db,
|
|
}
|
|
|
|
// test list with different number of containers
|
|
for _, num := range []int{0, 1, 2, 4, 8, 16, 32, 64, 100} {
|
|
t.Run(fmt.Sprintf("%d containers", num), func(t *testing.T) {
|
|
db, err := container.NewViewDB() // new DB to ignore prior containers
|
|
assert.NilError(t, err)
|
|
d = &Daemon{
|
|
containersReplica: db,
|
|
}
|
|
|
|
// create the containers
|
|
containers := make([]*container.Container, num)
|
|
for i := range num {
|
|
name := fmt.Sprintf("cont-%d", i)
|
|
containers[i] = setupContainerWithName(t, name, d)
|
|
// ensure container timestamps are separated enough so the
|
|
// sort used by d.Containers() can deterministically sort them.
|
|
if i > 0 {
|
|
containers[i].Created = containers[i-1].Created.Add(time.Millisecond)
|
|
}
|
|
}
|
|
|
|
// list them and verify correctness
|
|
containerList, err := d.Containers(context.Background(), &backend.ContainerListOptions{All: true})
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, is.Len(containerList, num))
|
|
|
|
for i := range num {
|
|
// container list should be ordered in descending creation order
|
|
assert.Assert(t, is.Equal(containerList[i].Names[0], containers[num-1-i].Name))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestContainerList_InvalidFilter(t *testing.T) {
|
|
db, err := container.NewViewDB()
|
|
assert.NilError(t, err)
|
|
d := &Daemon{
|
|
containersReplica: db,
|
|
}
|
|
|
|
_, err = d.Containers(context.Background(), &backend.ContainerListOptions{
|
|
Filters: filters.NewArgs(filters.Arg("invalid", "foo")),
|
|
})
|
|
assert.Assert(t, is.Error(err, "invalid filter 'invalid'"))
|
|
}
|
|
|
|
func TestContainerList_NameFilter(t *testing.T) {
|
|
db, err := container.NewViewDB()
|
|
assert.NilError(t, err)
|
|
d := &Daemon{
|
|
containersReplica: db,
|
|
}
|
|
|
|
var (
|
|
one = setupContainerWithName(t, "a1", d)
|
|
two = setupContainerWithName(t, "a2", d)
|
|
three = setupContainerWithName(t, "b1", d)
|
|
)
|
|
|
|
// moby/moby #37453 - ^ regex not working due to prefix slash
|
|
// not being stripped
|
|
containerList, err := d.Containers(context.Background(), &backend.ContainerListOptions{
|
|
Filters: filters.NewArgs(filters.Arg("name", "^a")),
|
|
})
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, is.Len(containerList, 2))
|
|
assert.Assert(t, containerListContainsName(containerList, one.Name))
|
|
assert.Assert(t, containerListContainsName(containerList, two.Name))
|
|
|
|
// Same as above but with slash prefix should produce the same result
|
|
containerListWithPrefix, err := d.Containers(context.Background(), &backend.ContainerListOptions{
|
|
Filters: filters.NewArgs(filters.Arg("name", "^/a")),
|
|
})
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, is.Len(containerListWithPrefix, 2))
|
|
assert.Assert(t, containerListContainsName(containerListWithPrefix, one.Name))
|
|
assert.Assert(t, containerListContainsName(containerListWithPrefix, two.Name))
|
|
|
|
// Same as above but make sure it works for exact names
|
|
containerList, err = d.Containers(context.Background(), &backend.ContainerListOptions{
|
|
Filters: filters.NewArgs(filters.Arg("name", "b1")),
|
|
})
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, is.Len(containerList, 1))
|
|
assert.Assert(t, containerListContainsName(containerList, three.Name))
|
|
|
|
// Same as above but with slash prefix should produce the same result
|
|
containerListWithPrefix, err = d.Containers(context.Background(), &backend.ContainerListOptions{
|
|
Filters: filters.NewArgs(filters.Arg("name", "/b1")),
|
|
})
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, is.Len(containerListWithPrefix, 1))
|
|
assert.Assert(t, containerListContainsName(containerListWithPrefix, three.Name))
|
|
}
|
|
|
|
func TestContainerList_LimitFilter(t *testing.T) {
|
|
db, err := container.NewViewDB()
|
|
assert.NilError(t, err)
|
|
d := &Daemon{
|
|
containersReplica: db,
|
|
}
|
|
|
|
// start containers
|
|
num := 32
|
|
for i := range num {
|
|
name := fmt.Sprintf("cont-%d", i)
|
|
setupContainerWithName(t, name, d)
|
|
}
|
|
|
|
containers, err := db.Snapshot().All()
|
|
assert.NilError(t, err)
|
|
assert.Assert(t, is.Len(containers, num))
|
|
|
|
tests := []struct {
|
|
limit int
|
|
doc string
|
|
}{
|
|
{limit: 0, doc: "no limit"},
|
|
{limit: -1, doc: "negative limit doesn't limit"},
|
|
{limit: 1, doc: "limit 1 container"},
|
|
{limit: 20, doc: "limit less than num containers"},
|
|
{limit: 32, doc: "limit equal num containers"},
|
|
{limit: 40, doc: "limit greater than num containers"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.doc, func(t *testing.T) {
|
|
containerList, err := d.Containers(context.Background(), &backend.ContainerListOptions{Limit: tc.limit})
|
|
assert.NilError(t, err)
|
|
expectedListLen := num
|
|
if tc.limit > 0 {
|
|
expectedListLen = min(num, tc.limit)
|
|
}
|
|
assert.Assert(t, is.Len(containerList, expectedListLen))
|
|
})
|
|
}
|
|
}
|