Files
podman/libpod/sql_state_test.go
Matthew Heon 20df2196f2 Add ability for states to track container dependencies
Also prevent containers with dependencies from being removed from
in memory states. SQLite already enforced this via FOREIGN KEY
constraints.

Signed-off-by: Matthew Heon <matthew.heon@gmail.com>

Closes: #220
Approved by: rhatdan
2018-01-16 14:58:06 +00:00

704 lines
18 KiB
Go

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