diff options
-rw-r--r-- | libpod/container.go | 13 | ||||
-rw-r--r-- | libpod/options.go | 32 | ||||
-rw-r--r-- | libpod/state_test.go | 117 |
3 files changed, 162 insertions, 0 deletions
diff --git a/libpod/container.go b/libpod/container.go index 965ac67fe..cbfa09538 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -205,6 +205,8 @@ type ContainerConfig struct { // Namespace Config // IDs of container to share namespaces with // NetNsCtr conflicts with the CreateNetNS bool + // These containers are considered dependencies of the given container + // They must be started before the given container is started IPCNsCtr string `json:"ipcNsCtr,omitempty"` MountNsCtr string `json:"mountNsCtr,omitempty"` NetNsCtr string `json:"netNsCtr,omitempty"` @@ -213,6 +215,10 @@ type ContainerConfig struct { UTSNsCtr string `json:"utsNsCtr,omitempty"` CgroupNsCtr string `json:"cgroupNsCtr,omitempty"` + // IDs of dependency containers + // These containers must be started before this container is started + Dependencies []string + // Network Config // CreateNetNS indicates that libpod should create and configure a new // network namespace for the container @@ -363,6 +369,8 @@ func (c *Container) User() string { func (c *Container) Dependencies() []string { // Collect in a map first to remove dupes dependsCtrs := map[string]bool{} + + // First add all namespace containers if c.config.IPCNsCtr != "" { dependsCtrs[c.config.IPCNsCtr] = true } @@ -385,6 +393,11 @@ func (c *Container) Dependencies() []string { dependsCtrs[c.config.CgroupNsCtr] = true } + // Add all generic dependencies + for _, id := range c.config.Dependencies { + dependsCtrs[id] = true + } + if len(dependsCtrs) == 0 { return []string{} } diff --git a/libpod/options.go b/libpod/options.go index 987a37429..739d53a8b 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -625,6 +625,38 @@ func WithCgroupNSFrom(nsCtr *Container) CtrCreateOption { } } +// WithDependencies sets dependency containers of the given container +// Dependency containers must be running before this container is started +func WithDependencyCtrs(ctrs []*Container) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + + deps := make([]string, 0, len(ctrs)) + + for _, dep := range ctrs { + if !dep.valid { + return errors.Wrapf(ErrCtrRemoved, "container %s is not valid", dep.ID()) + } + + if dep.ID() == ctr.ID() { + return errors.Wrapf(ErrInvalidArg, "must specify another container") + } + + if ctr.config.Pod != "" && dep.config.Pod != ctr.config.Pod { + return errors.Wrapf(ErrInvalidArg, "container has joined pod %s and dependency container %s is not a member of the pod", ctr.config.Pod, dep.ID()) + } + + deps = append(deps, dep.ID()) + } + + ctr.config.Dependencies = deps + + return nil + } +} + // WithNetNS indicates that the container should be given a new network // namespace with a minimal configuration // An optional array of port mappings can be provided diff --git a/libpod/state_test.go b/libpod/state_test.go index f989dfefd..f0c65b134 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -656,6 +656,83 @@ func TestContainerInUseOneContainerMultipleDependencies(t *testing.T) { }) } +func TestContainerInUseGenericDependency(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestCtr1(lockPath) + assert.NoError(t, err) + testCtr2, err := getTestCtr2(lockPath) + assert.NoError(t, err) + + testCtr2.config.Dependencies = []string{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 TestContainerInUseMultipleGenericDependencies(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestCtr1(lockPath) + assert.NoError(t, err) + testCtr2, err := getTestCtr2(lockPath) + assert.NoError(t, err) + testCtr3, err := getTestCtrN("3", lockPath) + assert.NoError(t, err) + + testCtr3.config.Dependencies = []string{testCtr1.config.ID, testCtr2.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) + + ids1, err := state.ContainerInUse(testCtr1) + assert.NoError(t, err) + assert.Equal(t, 1, len(ids1)) + assert.Equal(t, testCtr3.config.ID, ids1[0]) + + ids2, err := state.ContainerInUse(testCtr2) + assert.NoError(t, err) + assert.Equal(t, 1, len(ids2)) + assert.Equal(t, testCtr3.config.ID, ids2[0]) + }) +} + +func TestContainerInUseGenericAndNamespaceDependencies(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestCtr1(lockPath) + assert.NoError(t, err) + testCtr2, err := getTestCtr2(lockPath) + assert.NoError(t, err) + + testCtr2.config.Dependencies = []string{testCtr1.config.ID} + testCtr2.config.IPCNsCtr = 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 TestCannotRemoveContainerWithDependency(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { testCtr1, err := getTestCtr1(lockPath) @@ -680,6 +757,30 @@ func TestCannotRemoveContainerWithDependency(t *testing.T) { }) } +func TestCannotRemoveContainerWithGenericDependency(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr1, err := getTestCtr1(lockPath) + assert.NoError(t, err) + testCtr2, err := getTestCtr2(lockPath) + assert.NoError(t, err) + + testCtr2.config.Dependencies = []string{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) + + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 2, len(ctrs)) + }) +} + func TestCanRemoveContainerAfterDependencyRemoved(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { testCtr1, err := getTestCtr1(lockPath) @@ -773,6 +874,22 @@ func TestCannotUseBadIDAsDependency(t *testing.T) { }) } +func TestCannotUseBadIDAsGenericDependency(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, lockPath string) { + testCtr, err := getTestCtr1(lockPath) + assert.NoError(t, err) + + testCtr.config.Dependencies = []string{strings.Repeat("5", 32)} + + err = state.AddContainer(testCtr) + assert.Error(t, err) + + ctrs, err := state.AllContainers() + assert.NoError(t, err) + assert.Equal(t, 0, len(ctrs)) + }) +} + func TestGetPodDoesNotExist(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, lockPath string) { _, err := state.Pod("doesnotexist") |