From 67b6c132d83b94d20c9ef204c8dcba3de5581f60 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 4 Jan 2018 16:04:46 -0500 Subject: Modify unit tests for state to run on all state implementations Signed-off-by: Matthew Heon Closes: #229 Approved by: rhatdan --- libpod/in_memory_state.go | 24 ++ libpod/sql_state.go | 2 + libpod/sql_state_test.go | 703 ---------------------------------------------- libpod/state_test.go | 627 +++++++++++++++++++++++++++++++++++++++++ libpod/test_common.go | 114 ++++++++ 5 files changed, 767 insertions(+), 703 deletions(-) delete mode 100644 libpod/sql_state_test.go create mode 100644 libpod/state_test.go create mode 100644 libpod/test_common.go diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index 244a1ab25..19d14366c 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -182,6 +182,18 @@ func (s *InMemoryState) RemoveContainer(ctr *Container) error { // As all state is in-memory, no update will be required // As such this is a no-op func (s *InMemoryState) UpdateContainer(ctr *Container) error { + // If the container is invalid, return error + if !ctr.valid { + return errors.Wrapf(ErrCtrRemoved, "container with ID %s is not valid", ctr.ID()) + } + + // If the container does not exist, return error + _, ok := s.containers[ctr.ID()] + if !ok { + ctr.valid = false + return errors.Wrapf(ErrNoSuchCtr, "container with ID %s not found in state", ctr.ID()) + } + return nil } @@ -190,6 +202,18 @@ func (s *InMemoryState) UpdateContainer(ctr *Container) error { // are made // As such this is a no-op func (s *InMemoryState) SaveContainer(ctr *Container) error { + // If the container is invalid, return error + if !ctr.valid { + return errors.Wrapf(ErrCtrRemoved, "container with ID %s is not valid", ctr.ID()) + } + + // If the container does not exist, return error + _, ok := s.containers[ctr.ID()] + if !ok { + ctr.valid = false + return errors.Wrapf(ErrNoSuchCtr, "container with ID %s not found in state", ctr.ID()) + } + return nil } diff --git a/libpod/sql_state.go b/libpod/sql_state.go index 3bdca0e63..866da27bc 100644 --- a/libpod/sql_state.go +++ b/libpod/sql_state.go @@ -651,6 +651,8 @@ func (s *SQLState) SaveContainer(ctr *Container) error { return errors.Wrapf(err, "error retrieving number of rows modified by update of container %s", ctr.ID()) } if rows == 0 { + // Container was probably removed elsewhere + ctr.valid = false return ErrNoSuchCtr } diff --git a/libpod/sql_state_test.go b/libpod/sql_state_test.go deleted file mode 100644 index 0c08467f0..000000000 --- a/libpod/sql_state_test.go +++ /dev/null @@ -1,703 +0,0 @@ -package libpod - -import ( - "encoding/json" - "io/ioutil" - "net" - "os" - "path/filepath" - "reflect" - "testing" - "time" - - "github.com/containers/storage" - "github.com/cri-o/ocicni/pkg/ocicni" - "github.com/opencontainers/runtime-tools/generate" - "github.com/stretchr/testify/assert" -) - -func getTestContainer(id, name, locksDir string) (*Container, error) { - ctr := &Container{ - config: &ContainerConfig{ - ID: id, - Name: name, - RootfsImageID: id, - RootfsImageName: "testimg", - ImageVolumes: true, - ReadOnly: true, - StaticDir: "/does/not/exist/", - Stdin: true, - Labels: make(map[string]string), - StopSignal: 0, - StopTimeout: 0, - CreatedTime: time.Now(), - Privileged: true, - Mounts: []string{"/does/not/exist"}, - DNSServer: []net.IP{net.ParseIP("192.168.1.1"), net.ParseIP("192.168.2.2")}, - DNSSearch: []string{"example.com", "example.example.com"}, - PortMappings: []ocicni.PortMapping{ - { - HostPort: 80, - ContainerPort: 90, - Protocol: "tcp", - HostIP: "192.168.3.3", - }, - { - HostPort: 100, - ContainerPort: 110, - Protocol: "udp", - HostIP: "192.168.4.4", - }, - }, - }, - state: &containerRuntimeInfo{ - State: ContainerStateRunning, - ConfigPath: "/does/not/exist/specs/" + id, - RunDir: "/does/not/exist/tmp/", - Mounted: true, - Mountpoint: "/does/not/exist/tmp/" + id, - PID: 1234, - }, - valid: true, - } - - g := generate.New() - ctr.config.Spec = g.Spec() - - ctr.config.Labels["test"] = "testing" - - // Must make lockfile or container will error on being retrieved from DB - lockPath := filepath.Join(locksDir, id) - lock, err := storage.GetLockfile(lockPath) - if err != nil { - return nil, err - } - ctr.lock = lock - - return ctr, nil -} - -// This horrible hack tests if containers are equal in a way that should handle -// empty arrays being dropped to nil pointers in the spec JSON -func testContainersEqual(a, b *Container) bool { - if a == nil && b == nil { - return true - } else if a == nil || b == nil { - return false - } - - if a.valid != b.valid { - return false - } - - aConfigJSON, err := json.Marshal(a.config) - if err != nil { - return false - } - - bConfigJSON, err := json.Marshal(b.config) - if err != nil { - return false - } - - if !reflect.DeepEqual(aConfigJSON, bConfigJSON) { - return false - } - - aStateJSON, err := json.Marshal(a.state) - if err != nil { - return false - } - - bStateJSON, err := json.Marshal(b.state) - if err != nil { - return false - } - - return reflect.DeepEqual(aStateJSON, bStateJSON) -} - -// Get an empty state for use in tests -// An empty Runtime is provided -func getEmptyState() (s State, p string, p2 string, err error) { - tmpDir, err := ioutil.TempDir("", "libpod_state_test_") - if err != nil { - return nil, "", "", err - } - defer func() { - if err != nil { - os.RemoveAll(tmpDir) - } - }() - - dbPath := filepath.Join(tmpDir, "db.sql") - specsDir := filepath.Join(tmpDir, "specs") - lockDir := filepath.Join(tmpDir, "locks") - - runtime := new(Runtime) - runtime.config = new(RuntimeConfig) - runtime.config.StorageConfig = storage.StoreOptions{} - - state, err := NewSQLState(dbPath, specsDir, lockDir, runtime) - if err != nil { - return nil, "", "", err - } - - return state, tmpDir, lockDir, nil -} - -func TestAddAndGetContainer(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - retrievedCtr, err := state.Container(testCtr.ID()) - assert.NoError(t, err) - - // Use assert.EqualValues if the test fails to pretty print diff - // between actual and expected - if !testContainersEqual(testCtr, retrievedCtr) { - assert.EqualValues(t, testCtr, retrievedCtr) - } -} - -func TestAddAndGetContainerFromMultiple(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) - assert.NoError(t, err) - testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr1) - assert.NoError(t, err) - - err = state.AddContainer(testCtr2) - assert.NoError(t, err) - - retrievedCtr, err := state.Container(testCtr1.ID()) - assert.NoError(t, err) - - // Use assert.EqualValues if the test fails to pretty print diff - // between actual and expected - if !testContainersEqual(testCtr1, retrievedCtr) { - assert.EqualValues(t, testCtr1, retrievedCtr) - } -} - -func TestAddInvalidContainerFails(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - err = state.AddContainer(&Container{}) - assert.Error(t, err) -} - -func TestAddDuplicateIDFails(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) - assert.NoError(t, err) - testCtr2, err := getTestContainer(testCtr1.ID(), "test2", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr1) - assert.NoError(t, err) - - err = state.AddContainer(testCtr2) - assert.Error(t, err) -} - -func TestAddDuplicateNameFails(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) - assert.NoError(t, err) - testCtr2, err := getTestContainer("22222222222222222222222222222222", testCtr1.Name(), lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr1) - assert.NoError(t, err) - - err = state.AddContainer(testCtr2) - assert.Error(t, err) -} - -func TestGetNonexistantContainerFails(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - _, err = state.Container("does not exist") - assert.Error(t, err) -} - -func TestGetContainerWithEmptyIDFails(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - _, err = state.Container("") - assert.Error(t, err) -} - -func TestLookupContainerWithEmptyIDFails(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - _, err = state.LookupContainer("") - assert.Error(t, err) -} - -func TestLookupNonexistantContainerFails(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - - _, err = state.LookupContainer("does not exist") - assert.Error(t, err) -} - -func TestLookupContainerByFullID(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - retrievedCtr, err := state.LookupContainer(testCtr.ID()) - assert.NoError(t, err) - - // Use assert.EqualValues if the test fails to pretty print diff - // between actual and expected - if !testContainersEqual(testCtr, retrievedCtr) { - assert.EqualValues(t, testCtr, retrievedCtr) - } -} - -func TestLookupContainerByUniquePartialID(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - retrievedCtr, err := state.LookupContainer(testCtr.ID()[0:8]) - assert.NoError(t, err) - - // Use assert.EqualValues if the test fails to pretty print diff - // between actual and expected - if !testContainersEqual(testCtr, retrievedCtr) { - assert.EqualValues(t, testCtr, retrievedCtr) - } -} - -func TestLookupContainerByNonUniquePartialIDFails(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr1, err := getTestContainer("00000000000000000000000000000000", "test1", lockPath) - assert.NoError(t, err) - testCtr2, err := getTestContainer("00000000000000000000000000000001", "test2", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr1) - assert.NoError(t, err) - - err = state.AddContainer(testCtr2) - assert.NoError(t, err) - - _, err = state.LookupContainer(testCtr1.ID()[0:8]) - assert.Error(t, err) -} - -func TestLookupContainerByName(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - retrievedCtr, err := state.LookupContainer(testCtr.Name()) - assert.NoError(t, err) - - // Use assert.EqualValues if the test fails to pretty print diff - // between actual and expected - if !testContainersEqual(testCtr, retrievedCtr) { - assert.EqualValues(t, testCtr, retrievedCtr) - } -} - -func TestHasContainerEmptyIDFails(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - _, err = state.HasContainer("") - assert.Error(t, err) -} - -func TestHasContainerNoSuchContainerReturnsFalse(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - exists, err := state.HasContainer("does not exist") - assert.NoError(t, err) - assert.False(t, exists) -} - -func TestHasContainerFindsContainer(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - exists, err := state.HasContainer(testCtr.ID()) - assert.NoError(t, err) - assert.True(t, exists) -} - -func TestSaveAndUpdateContainer(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - retrievedCtr, err := state.Container(testCtr.ID()) - assert.NoError(t, err) - - retrievedCtr.state.State = ContainerStateStopped - retrievedCtr.state.ExitCode = 127 - retrievedCtr.state.FinishedTime = time.Now() - - err = state.SaveContainer(retrievedCtr) - assert.NoError(t, err) - - err = state.UpdateContainer(testCtr) - assert.NoError(t, err) - - // Use assert.EqualValues if the test fails to pretty print diff - // between actual and expected - if !testContainersEqual(testCtr, retrievedCtr) { - assert.EqualValues(t, testCtr, retrievedCtr) - } -} - -func TestUpdateContainerNotInDatabaseReturnsError(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.UpdateContainer(testCtr) - assert.Error(t, err) - assert.False(t, testCtr.valid) -} - -func TestUpdateInvalidContainerReturnsError(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - err = state.UpdateContainer(&Container{}) - assert.Error(t, err) -} - -func TestSaveInvalidContainerReturnsError(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - err = state.SaveContainer(&Container{}) - assert.Error(t, err) -} - -func TestSaveContainerNotInStateReturnsError(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.SaveContainer(testCtr) - assert.Error(t, err) -} - -func TestRemoveContainer(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - ctrs, err := state.AllContainers() - assert.NoError(t, err) - assert.Equal(t, 1, len(ctrs)) - - err = state.RemoveContainer(testCtr) - assert.NoError(t, err) - - ctrs2, err := state.AllContainers() - assert.NoError(t, err) - assert.Equal(t, 0, len(ctrs2)) -} - -func TestRemoveNonexistantContainerFails(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.RemoveContainer(testCtr) - assert.Error(t, err) -} - -func TestGetAllContainersOnNewStateIsEmpty(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - ctrs, err := state.AllContainers() - assert.NoError(t, err) - assert.Equal(t, 0, len(ctrs)) -} - -func TestGetAllContainersWithOneContainer(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr) - assert.NoError(t, err) - - ctrs, err := state.AllContainers() - assert.NoError(t, err) - assert.Equal(t, 1, len(ctrs)) - - // Use assert.EqualValues if the test fails to pretty print diff - // between actual and expected - if !testContainersEqual(testCtr, ctrs[0]) { - assert.EqualValues(t, testCtr, ctrs[0]) - } -} - -func TestGetAllContainersTwoContainers(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) - assert.NoError(t, err) - testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) - assert.NoError(t, err) - - err = state.AddContainer(testCtr1) - assert.NoError(t, err) - - err = state.AddContainer(testCtr2) - assert.NoError(t, err) - - ctrs, err := state.AllContainers() - assert.NoError(t, err) - assert.Equal(t, 2, len(ctrs)) - - // Containers should be ordered by creation time - - // Use assert.EqualValues if the test fails to pretty print diff - // between actual and expected - if !testContainersEqual(testCtr2, ctrs[0]) { - assert.EqualValues(t, testCtr2, ctrs[0]) - } - if !testContainersEqual(testCtr1, ctrs[1]) { - assert.EqualValues(t, testCtr1, ctrs[1]) - } -} - -func TestContainerInUseInvalidContainer(t *testing.T) { - state, path, _, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - _, err = state.ContainerInUse(&Container{}) - assert.Error(t, err) -} - -func TestContainerInUseOneContainer(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) - assert.NoError(t, err) - testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) - assert.NoError(t, err) - - testCtr2.config.UserNsCtr = testCtr1.config.ID - - err = state.AddContainer(testCtr1) - assert.NoError(t, err) - - err = state.AddContainer(testCtr2) - assert.NoError(t, err) - - ids, err := state.ContainerInUse(testCtr1) - assert.NoError(t, err) - assert.Equal(t, 1, len(ids)) - assert.Equal(t, testCtr2.config.ID, ids[0]) -} - -func TestContainerInUseTwoContainers(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) - assert.NoError(t, err) - testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) - assert.NoError(t, err) - testCtr3, err := getTestContainer("33333333333333333333333333333333", "test3", lockPath) - assert.NoError(t, err) - - testCtr2.config.UserNsCtr = testCtr1.config.ID - testCtr3.config.IPCNsCtr = testCtr1.config.ID - - err = state.AddContainer(testCtr1) - assert.NoError(t, err) - - err = state.AddContainer(testCtr2) - assert.NoError(t, err) - - err = state.AddContainer(testCtr3) - assert.NoError(t, err) - - ids, err := state.ContainerInUse(testCtr1) - assert.NoError(t, err) - assert.Equal(t, 2, len(ids)) -} - -func TestCannotRemoveContainerWithDependency(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) - assert.NoError(t, err) - testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) - assert.NoError(t, err) - - testCtr2.config.UserNsCtr = testCtr1.config.ID - - err = state.AddContainer(testCtr1) - assert.NoError(t, err) - - err = state.AddContainer(testCtr2) - assert.NoError(t, err) - - err = state.RemoveContainer(testCtr1) - assert.Error(t, err) -} - -func TestCanRemoveContainerAfterDependencyRemoved(t *testing.T) { - state, path, lockPath, err := getEmptyState() - assert.NoError(t, err) - defer os.RemoveAll(path) - defer state.Close() - - testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) - assert.NoError(t, err) - testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) - assert.NoError(t, err) - - testCtr2.config.UserNsCtr = testCtr1.config.ID - - err = state.AddContainer(testCtr1) - assert.NoError(t, err) - - err = state.AddContainer(testCtr2) - assert.NoError(t, err) - - err = state.RemoveContainer(testCtr2) - assert.NoError(t, err) - - err = state.RemoveContainer(testCtr1) - assert.NoError(t, err) -} diff --git a/libpod/state_test.go b/libpod/state_test.go new file mode 100644 index 000000000..ac84a63d9 --- /dev/null +++ b/libpod/state_test.go @@ -0,0 +1,627 @@ +package libpod + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + "time" + + "github.com/containers/storage" + "github.com/stretchr/testify/assert" +) + +// Returns state, tmp directory containing all state files, locks directory +// (subdirectory of tmp dir), and error +// Closing the state and removing the given tmp directory should be sufficient +// to clean up +type emptyStateFunc func() (State, string, string, error) + +const ( + tmpDirPrefix = "libpod_state_test_" +) + +var ( + testedStates = map[string]emptyStateFunc{ + "sql": getEmptySQLState, + "in-memory": getEmptyInMemoryState, + } +) + +// Get an empty in-memory state for use in tests +func getEmptyInMemoryState() (s State, p string, p2 string, err error) { + tmpDir, err := ioutil.TempDir("", tmpDirPrefix) + if err != nil { + return nil, "", "", err + } + defer func() { + if err != nil { + os.RemoveAll(tmpDir) + } + }() + + state, err := NewInMemoryState() + if err != nil { + return nil, "", "", err + } + + // Don't need a separate locks dir as InMemoryState stores nothing on + // disk + return state, tmpDir, tmpDir, nil +} + +// Get an empty SQL state for use in tests +// An empty Runtime is provided +func getEmptySQLState() (s State, p string, p2 string, err error) { + tmpDir, err := ioutil.TempDir("", tmpDirPrefix) + if err != nil { + return nil, "", "", err + } + defer func() { + if err != nil { + os.RemoveAll(tmpDir) + } + }() + + dbPath := filepath.Join(tmpDir, "db.sql") + specsDir := filepath.Join(tmpDir, "specs") + lockDir := filepath.Join(tmpDir, "locks") + + runtime := new(Runtime) + runtime.config = new(RuntimeConfig) + runtime.config.StorageConfig = storage.StoreOptions{} + + state, err := NewSQLState(dbPath, specsDir, lockDir, runtime) + if err != nil { + return nil, "", "", err + } + + return state, tmpDir, lockDir, nil +} + +func runForAllStates(t *testing.T, testName string, testFunc func(*testing.T, State, string)) { + for stateName, stateFunc := range testedStates { + state, path, lockPath, err := stateFunc() + if err != nil { + t.Fatalf("Error initializing state %s", stateName) + } + defer os.RemoveAll(path) + defer state.Close() + + testName = testName + "-" + stateName + + success := t.Run(testName, func(t *testing.T) { + testFunc(t, state, lockPath) + }) + if !success { + t.Fail() + t.Logf("%s failed for state %s", testName, stateName) + } + } +} + +func TestAddAndGetContainer(t *testing.T) { + runForAllStates(t, "TestAddAndGetContainer", addAndGetContainer) +} + +func addAndGetContainer(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + retrievedCtr, err := state.Container(testCtr.ID()) + assert.NoError(t, err) + + // Use assert.EqualValues if the test fails to pretty print diff + // between actual and expected + if !testContainersEqual(testCtr, retrievedCtr) { + assert.EqualValues(t, testCtr, retrievedCtr) + } +} + +func TestAddAndGetContainerFromMultiple(t *testing.T) { + runForAllStates(t, "TestAddAndGetContainerFromMultiple", addAndGetContainerFromMultiple) +} + +func addAndGetContainerFromMultiple(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + retrievedCtr, err := state.Container(testCtr1.ID()) + assert.NoError(t, err) + + // Use assert.EqualValues if the test fails to pretty print diff + // between actual and expected + if !testContainersEqual(testCtr1, retrievedCtr) { + assert.EqualValues(t, testCtr1, retrievedCtr) + } +} + +func TestAddInvalidContainerFails(t *testing.T) { + runForAllStates(t, "TestAddInvalidContainerFails", addInvalidContainerFails) +} + +func addInvalidContainerFails(t *testing.T, state State, lockPath string) { + err := state.AddContainer(&Container{config:&ContainerConfig{ID: "1234"}}) + assert.Error(t, err) +} + +func TestAddDuplicateCtrIDFails(t *testing.T) { + runForAllStates(t, "TestAddDuplicateCtrIDFails", addDuplicateCtrIDFails) +} + +func addDuplicateCtrIDFails(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer(testCtr1.ID(), "test2", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.Error(t, err) +} + +func TestAddDuplicateCtrNameFails(t *testing.T) { + runForAllStates(t, "TestAddDuplicateCtrNameFails", addDuplicateCtrNameFails) +} + +func addDuplicateCtrNameFails(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer("22222222222222222222222222222222", testCtr1.Name(), lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.Error(t, err) +} + +func TestGetNonexistentContainerFails(t *testing.T) { + runForAllStates(t, "TestGetNonexistentContainerFails", getNonexistentContainerFails) +} + +func getNonexistentContainerFails(t *testing.T, state State, lockPath string) { + _, err := state.Container("does not exist") + assert.Error(t, err) +} + +func TestGetContainerWithEmptyIDFails(t *testing.T) { + runForAllStates(t, "TestGetContainerWithEmptyIDFails", getContainerWithEmptyIDFails) +} + +func getContainerWithEmptyIDFails(t *testing.T, state State, lockPath string) { + _, err := state.Container("") + assert.Error(t, err) +} + +func TestLookupContainerWithEmptyIDFails(t *testing.T) { + runForAllStates(t, "TestLookupContainerWithEmptyIDFails", lookupContainerWithEmptyIDFails) +} + +func lookupContainerWithEmptyIDFails(t *testing.T, state State, lockPath string) { + _, err := state.LookupContainer("") + assert.Error(t, err) +} + +func TestLookupNonexistentContainerFails(t *testing.T) { + runForAllStates(t, "TestLookupNonexistantContainerFails", lookupNonexistentContainerFails) +} + +func lookupNonexistentContainerFails(t *testing.T, state State, lockPath string) { + _, err := state.LookupContainer("does not exist") + assert.Error(t, err) +} + +func TestLookupContainerByFullID(t *testing.T) { + runForAllStates(t, "TestLookupContainerByFullID", lookupContainerByFullID) +} + +func lookupContainerByFullID(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + retrievedCtr, err := state.LookupContainer(testCtr.ID()) + assert.NoError(t, err) + + // Use assert.EqualValues if the test fails to pretty print diff + // between actual and expected + if !testContainersEqual(testCtr, retrievedCtr) { + assert.EqualValues(t, testCtr, retrievedCtr) + } +} + +func TestLookupContainerByUniquePartialID(t *testing.T) { + runForAllStates(t, "TestLookupContainerByUniquePartialID", lookupContainerByUniquePartialID) +} + +func lookupContainerByUniquePartialID(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + retrievedCtr, err := state.LookupContainer(testCtr.ID()[0:8]) + assert.NoError(t, err) + + // Use assert.EqualValues if the test fails to pretty print diff + // between actual and expected + if !testContainersEqual(testCtr, retrievedCtr) { + assert.EqualValues(t, testCtr, retrievedCtr) + } +} + +func TestLookupContainerByNonUniquePartialIDFails(t *testing.T) { + runForAllStates(t, "TestLookupContainerByNonUniquePartialIDFails", lookupContainerByNonUniquePartialIDFails) +} + +func lookupContainerByNonUniquePartialIDFails(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestContainer("00000000000000000000000000000000", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer("00000000000000000000000000000001", "test2", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + _, err = state.LookupContainer(testCtr1.ID()[0:8]) + assert.Error(t, err) +} + +func TestLookupContainerByName(t *testing.T) { + runForAllStates(t, "TestLookupContainerByName", lookupContainerByName) +} + +func lookupContainerByName(t *testing.T, state State, lockPath string) { + state, path, lockPath, err := getEmptySQLState() + assert.NoError(t, err) + defer os.RemoveAll(path) + defer state.Close() + + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + retrievedCtr, err := state.LookupContainer(testCtr.Name()) + assert.NoError(t, err) + + // Use assert.EqualValues if the test fails to pretty print diff + // between actual and expected + if !testContainersEqual(testCtr, retrievedCtr) { + assert.EqualValues(t, testCtr, retrievedCtr) + } +} + +func TestHasContainerEmptyIDFails(t *testing.T) { + runForAllStates(t, "TestHasContainerEmptyIDFails", hasContainerEmptyIDFails) +} + +func hasContainerEmptyIDFails(t *testing.T, state State, lockPath string) { + _, err := state.HasContainer("") + assert.Error(t, err) +} + +func TestHasContainerNoSuchContainerReturnsFalse(t *testing.T) { + runForAllStates(t, "TestHasContainerNoSuchContainerReturnsFalse", hasContainerNoSuchContainerReturnsFalse) +} + +func hasContainerNoSuchContainerReturnsFalse(t *testing.T, state State, lockPath string) { + exists, err := state.HasContainer("does not exist") + assert.NoError(t, err) + assert.False(t, exists) +} + +func TestHasContainerFindsContainer(t *testing.T) { + runForAllStates(t, "TestHasContainerFindsContainer", hasContainerFindsContainer) +} + +func hasContainerFindsContainer(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + exists, err := state.HasContainer(testCtr.ID()) + assert.NoError(t, err) + assert.True(t, exists) +} + +func TestSaveAndUpdateContainer(t *testing.T) { + runForAllStates(t, "TestSaveAndUpdateContainer", saveAndUpdateContainer) +} + +func saveAndUpdateContainer(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + retrievedCtr, err := state.Container(testCtr.ID()) + assert.NoError(t, err) + + retrievedCtr.state.State = ContainerStateStopped + retrievedCtr.state.ExitCode = 127 + retrievedCtr.state.FinishedTime = time.Now() + + err = state.SaveContainer(retrievedCtr) + assert.NoError(t, err) + + err = state.UpdateContainer(testCtr) + assert.NoError(t, err) + + // Use assert.EqualValues if the test fails to pretty print diff + // between actual and expected + if !testContainersEqual(testCtr, retrievedCtr) { + assert.EqualValues(t, testCtr, retrievedCtr) + } +} + +func TestUpdateContainerNotInDatabaseReturnsError(t *testing.T) { + runForAllStates(t, "TestUpdateContainerNotInDatabaseReturnsError", updateContainerNotInDatabaseReturnsError) +} + +func updateContainerNotInDatabaseReturnsError(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.UpdateContainer(testCtr) + assert.Error(t, err) + assert.False(t, testCtr.valid) +} + +func TestUpdateInvalidContainerReturnsError(t *testing.T) { + runForAllStates(t, "TestUpdateInvalidContainerReturnsError", updateInvalidContainerReturnsError) +} + +func updateInvalidContainerReturnsError(t *testing.T, state State, lockPath string) { + err := state.UpdateContainer(&Container{config:&ContainerConfig{ID: "1234"}}) + assert.Error(t, err) +} + +func TestSaveInvalidContainerReturnsError(t *testing.T) { + runForAllStates(t, "TestSaveInvalidContainerReturnsError", saveInvalidContainerReturnsError) +} + +func saveInvalidContainerReturnsError(t *testing.T, state State, lockPath string) { + err := state.SaveContainer(&Container{config:&ContainerConfig{ID: "1234"}}) + assert.Error(t, err) +} + +func TestSaveContainerNotInStateReturnsError(t *testing.T) { + runForAllStates(t, "TestSaveContainerNotInStateReturnsError", saveContainerNotInStateReturnsError) +} + +func saveContainerNotInStateReturnsError(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.SaveContainer(testCtr) + assert.Error(t, err) + assert.False(t, testCtr.valid) +} + +func TestRemoveContainer(t *testing.T) { + runForAllStates(t, "TestRemoveContainer", removeContainer) +} + +func removeContainer(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 1, len(ctrs)) + + err = state.RemoveContainer(testCtr) + assert.NoError(t, err) + + ctrs2, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 0, len(ctrs2)) +} + +func TestRemoveNonexistantContainerFails(t *testing.T) { + runForAllStates(t, "TestRemoveNonexistantContainerFails", removeNonexistantContainerFails) +} + +func removeNonexistantContainerFails(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.RemoveContainer(testCtr) + assert.Error(t, err) +} + +func TestGetAllContainersOnNewStateIsEmpty(t *testing.T) { + runForAllStates(t, "TestGetAllContainersOnNewStateIsEmpty", getAllContainersOnNewStateIsEmpty) +} + +func getAllContainersOnNewStateIsEmpty(t *testing.T, state State, lockPath string) { + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 0, len(ctrs)) +} + +func TestGetAllContainersWithOneContainer(t *testing.T) { + runForAllStates(t, "TestGetAllContainersWithOneContainer", getAllContainersWithOneContainer) +} + +func getAllContainersWithOneContainer(t *testing.T, state State, lockPath string) { + testCtr, err := getTestContainer("0123456789ABCDEF0123456789ABCDEF", "test", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 1, len(ctrs)) + + // Use assert.EqualValues if the test fails to pretty print diff + // between actual and expected + if !testContainersEqual(testCtr, ctrs[0]) { + assert.EqualValues(t, testCtr, ctrs[0]) + } +} + +func TestGetAllContainersTwoContainers(t *testing.T) { + runForAllStates(t, "TestGetAllContainersTwoContainers", getAllContainersTwoContainers) +} + +func getAllContainersTwoContainers(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) + assert.NoError(t, err) + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 2, len(ctrs)) +} + +func TestContainerInUseInvalidContainer(t *testing.T) { + state, path, _, err := getEmptyState() + assert.NoError(t, err) + defer os.RemoveAll(path) + defer state.Close() + + _, err = state.ContainerInUse(&Container{}) + assert.Error(t, err) +} + +func TestContainerInUseOneContainer(t *testing.T) { + state, path, lockPath, err := getEmptyState() + assert.NoError(t, err) + defer os.RemoveAll(path) + defer state.Close() + + testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) + assert.NoError(t, err) + + testCtr2.config.UserNsCtr = testCtr1.config.ID + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + ids, err := state.ContainerInUse(testCtr1) + assert.NoError(t, err) + assert.Equal(t, 1, len(ids)) + assert.Equal(t, testCtr2.config.ID, ids[0]) +} + +func TestContainerInUseTwoContainers(t *testing.T) { + state, path, lockPath, err := getEmptyState() + assert.NoError(t, err) + defer os.RemoveAll(path) + defer state.Close() + + testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) + assert.NoError(t, err) + testCtr3, err := getTestContainer("33333333333333333333333333333333", "test3", lockPath) + assert.NoError(t, err) + + testCtr2.config.UserNsCtr = testCtr1.config.ID + testCtr3.config.IPCNsCtr = testCtr1.config.ID + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + err = state.AddContainer(testCtr3) + assert.NoError(t, err) + + ids, err := state.ContainerInUse(testCtr1) + assert.NoError(t, err) + assert.Equal(t, 2, len(ids)) +} + +func TestCannotRemoveContainerWithDependency(t *testing.T) { + state, path, lockPath, err := getEmptyState() + assert.NoError(t, err) + defer os.RemoveAll(path) + defer state.Close() + + testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) + assert.NoError(t, err) + + testCtr2.config.UserNsCtr = testCtr1.config.ID + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + err = state.RemoveContainer(testCtr1) + assert.Error(t, err) +} + +func TestCanRemoveContainerAfterDependencyRemoved(t *testing.T) { + state, path, lockPath, err := getEmptyState() + assert.NoError(t, err) + defer os.RemoveAll(path) + defer state.Close() + + testCtr1, err := getTestContainer("11111111111111111111111111111111", "test1", lockPath) + assert.NoError(t, err) + testCtr2, err := getTestContainer("22222222222222222222222222222222", "test2", lockPath) + assert.NoError(t, err) + + testCtr2.config.UserNsCtr = testCtr1.config.ID + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + err = state.RemoveContainer(testCtr2) + assert.NoError(t, err) + + err = state.RemoveContainer(testCtr1) + assert.NoError(t, err) +} diff --git a/libpod/test_common.go b/libpod/test_common.go new file mode 100644 index 000000000..f27c94f1f --- /dev/null +++ b/libpod/test_common.go @@ -0,0 +1,114 @@ +package libpod + +import ( + "encoding/json" + "net" + "path/filepath" + "reflect" + "time" + + "github.com/containers/storage" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/opencontainers/runtime-tools/generate" +) + +func getTestContainer(id, name, locksDir string) (*Container, error) { + ctr := &Container{ + config: &ContainerConfig{ + ID: id, + Name: name, + RootfsImageID: id, + RootfsImageName: "testimg", + ImageVolumes: true, + ReadOnly: true, + StaticDir: "/does/not/exist/", + Stdin: true, + Labels: make(map[string]string), + StopSignal: 0, + StopTimeout: 0, + CreatedTime: time.Now(), + Privileged: true, + Mounts: []string{"/does/not/exist"}, + DNSServer: []net.IP{net.ParseIP("192.168.1.1"), net.ParseIP("192.168.2.2")}, + DNSSearch: []string{"example.com", "example.example.com"}, + PortMappings: []ocicni.PortMapping{ + { + HostPort: 80, + ContainerPort: 90, + Protocol: "tcp", + HostIP: "192.168.3.3", + }, + { + HostPort: 100, + ContainerPort: 110, + Protocol: "udp", + HostIP: "192.168.4.4", + }, + }, + }, + state: &containerRuntimeInfo{ + State: ContainerStateRunning, + ConfigPath: "/does/not/exist/specs/" + id, + RunDir: "/does/not/exist/tmp/", + Mounted: true, + Mountpoint: "/does/not/exist/tmp/" + id, + PID: 1234, + }, + valid: true, + } + + g := generate.New() + ctr.config.Spec = g.Spec() + + ctr.config.Labels["test"] = "testing" + + // Must make lockfile or container will error on being retrieved from DB + lockPath := filepath.Join(locksDir, id) + lock, err := storage.GetLockfile(lockPath) + if err != nil { + return nil, err + } + ctr.lock = lock + + return ctr, nil +} + +// This horrible hack tests if containers are equal in a way that should handle +// empty arrays being dropped to nil pointers in the spec JSON +func testContainersEqual(a, b *Container) bool { + if a == nil && b == nil { + return true + } else if a == nil || b == nil { + return false + } + + if a.valid != b.valid { + return false + } + + aConfigJSON, err := json.Marshal(a.config) + if err != nil { + return false + } + + bConfigJSON, err := json.Marshal(b.config) + if err != nil { + return false + } + + if !reflect.DeepEqual(aConfigJSON, bConfigJSON) { + return false + } + + aStateJSON, err := json.Marshal(a.state) + if err != nil { + return false + } + + bStateJSON, err := json.Marshal(b.state) + if err != nil { + return false + } + + return reflect.DeepEqual(aStateJSON, bStateJSON) +} -- cgit v1.2.3-54-g00ecf