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
This commit is contained in:
Matthew Heon 2018-01-12 12:41:10 -05:00 committed by Atomic Bot
parent d2ec1f7628
commit 20df2196f2
4 changed files with 249 additions and 0 deletions

View file

@ -1,6 +1,8 @@
package libpod
import (
"strings"
"github.com/docker/docker/pkg/truncindex"
"github.com/pkg/errors"
"github.com/projectatomic/libpod/pkg/registrar"
@ -10,6 +12,7 @@ import (
type InMemoryState struct {
pods map[string]*Pod
containers map[string]*Container
ctrDepends map[string][]string
podNameIndex *registrar.Registrar
podIDIndex *truncindex.TruncIndex
ctrNameIndex *registrar.Registrar
@ -137,6 +140,15 @@ func (s *InMemoryState) AddContainer(ctr *Container) error {
s.containers[ctr.ID()] = ctr
// Add containers this container depends on
s.addCtrToDependsMap(ctr.ID(), ctr.config.IPCNsCtr)
s.addCtrToDependsMap(ctr.ID(), ctr.config.MountNsCtr)
s.addCtrToDependsMap(ctr.ID(), ctr.config.NetNsCtr)
s.addCtrToDependsMap(ctr.ID(), ctr.config.PIDNsCtr)
s.addCtrToDependsMap(ctr.ID(), ctr.config.UserNsCtr)
s.addCtrToDependsMap(ctr.ID(), ctr.config.UTSNsCtr)
s.addCtrToDependsMap(ctr.ID(), ctr.config.CgroupNsCtr)
return nil
}
@ -146,6 +158,13 @@ func (s *InMemoryState) RemoveContainer(ctr *Container) error {
// Almost no validity checks are performed, to ensure we can kick
// misbehaving containers out of the state
// Ensure we don't remove a container which other containers depend on
deps, ok := s.ctrDepends[ctr.ID()]
if ok && len(deps) != 0 {
depsStr := strings.Join(deps, ", ")
return errors.Wrapf(ErrCtrExists, "the following containers depend on container %s: %s", ctr.ID(), depsStr)
}
if _, ok := s.containers[ctr.ID()]; !ok {
return errors.Wrapf(ErrNoSuchCtr, "no container exists in state with ID %s", ctr.ID())
}
@ -156,6 +175,16 @@ func (s *InMemoryState) RemoveContainer(ctr *Container) error {
delete(s.containers, ctr.ID())
s.ctrNameIndex.Release(ctr.Name())
delete(s.ctrDepends, ctr.ID())
s.removeCtrFromDependsMap(ctr.ID(), ctr.config.IPCNsCtr)
s.removeCtrFromDependsMap(ctr.ID(), ctr.config.MountNsCtr)
s.removeCtrFromDependsMap(ctr.ID(), ctr.config.NetNsCtr)
s.removeCtrFromDependsMap(ctr.ID(), ctr.config.PIDNsCtr)
s.removeCtrFromDependsMap(ctr.ID(), ctr.config.UserNsCtr)
s.removeCtrFromDependsMap(ctr.ID(), ctr.config.UTSNsCtr)
s.removeCtrFromDependsMap(ctr.ID(), ctr.config.CgroupNsCtr)
return nil
}
@ -174,6 +203,20 @@ func (s *InMemoryState) SaveContainer(ctr *Container) error {
return nil
}
// ContainerInUse checks if the given container is being used by other containers
func (s *InMemoryState) ContainerInUse(ctr *Container) ([]string, error) {
if !ctr.valid {
return nil, ErrCtrRemoved
}
arr, ok := s.ctrDepends[ctr.ID()]
if !ok {
return []string{}, nil
}
return arr, nil
}
// AllContainers retrieves all containers from the state
func (s *InMemoryState) AllContainers() ([]*Container, error) {
ctrs := make([]*Container, 0, len(s.containers))
@ -298,3 +341,43 @@ func (s *InMemoryState) AllPods() ([]*Pod, error) {
return pods, nil
}
// Internal Functions
// Add a container to the dependency mappings
func (s *InMemoryState) addCtrToDependsMap(ctrID, dependsID string) {
if dependsID != "" {
arr, ok := s.ctrDepends[dependsID]
if !ok {
// Do not have a mapping for that container yet
s.ctrDepends[dependsID] = []string{ctrID}
} else {
// Have a mapping for the container
arr = append(arr, ctrID)
s.ctrDepends[dependsID] = arr
}
}
}
// Remove a container from dependency mappings
func (s *InMemoryState) removeCtrFromDependsMap(ctrID, dependsID string) {
if dependsID != "" {
arr, ok := s.ctrDepends[dependsID]
if !ok {
// Internal state seems inconsistent
// But the dependency is definitely gone
// So just return
return
}
newArr := make([]string, len(arr), 0)
for _, id := range arr {
if id != ctrID {
newArr = append(newArr, id)
}
}
s.ctrDepends[dependsID] = newArr
}
}

View file

@ -661,6 +661,51 @@ func (s *SQLState) SaveContainer(ctr *Container) error {
return nil
}
// ContainerInUse checks if other containers depend on the given container
// It returns the IDs of containers which depend on the given container
func (s *SQLState) ContainerInUse(ctr *Container) ([]string, error) {
const inUseQuery = `SELECT Id FROM containers WHERE
IPCNsCtr=? OR
MountNsCtr=? OR
NetNsCtr=? OR
PIDNsCtr=? OR
UserNsCtr=? OR
UTSNsCtr=? OR
CgroupNsCtr=?;`
if !s.valid {
return nil, ErrDBClosed
}
if !ctr.valid {
return nil, ErrCtrRemoved
}
id := ctr.ID()
rows, err := s.db.Query(inUseQuery, id, id, id, id, id, id, id)
if err != nil {
return nil, errors.Wrapf(err, "error querying database for containers that depend on container %s", id)
}
defer rows.Close()
ids := []string{}
for rows.Next() {
var ctrID string
if err := rows.Scan(&ctrID); err != nil {
return nil, errors.Wrapf(err, "error scanning container IDs from db rows for container %s", id)
}
ids = append(ids, ctrID)
}
if err := rows.Err(); err != nil {
return nil, errors.Wrapf(err, "error retrieving rows for container %s", id)
}
return ids, nil
}
// RemoveContainer removes the container from the state
func (s *SQLState) RemoveContainer(ctr *Container) error {
const (

View file

@ -587,3 +587,117 @@ func TestGetAllContainersTwoContainers(t *testing.T) {
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)
}

View file

@ -28,6 +28,13 @@ type State interface {
UpdateContainer(ctr *Container) error
// SaveContainer saves a container's current state to the backing store
SaveContainer(ctr *Container) error
// ContainerInUse checks if other containers depend upon a given
// container
// It returns a slice of the IDs of containers which depend on the given
// container. If the slice is empty, no container depend on the given
// container.
// A container cannot be removed if other containers depend on it
ContainerInUse(ctr *Container) ([]string, error)
// Retrieves all containers presently in state
AllContainers() ([]*Container, error)