Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make containers stopped after the linked containers stopped #1809

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
50 changes: 35 additions & 15 deletions agent/engine/dependencygraph/graph.go
Expand Up @@ -120,27 +120,44 @@ func DependenciesAreResolved(target *apicontainer.Container,
for _, cont := range by {
nameMap[cont.Name] = cont
}
neededVolumeContainers := make([]string, len(target.VolumesFrom))
for i, volume := range target.VolumesFrom {
neededVolumeContainers[i] = volume.SourceContainer
}

resourcesMap := make(map[string]taskresource.TaskResource)
for _, resource := range resources {
resourcesMap[resource.GetName()] = resource
if target.GetDesiredStatus() != apicontainerstatus.ContainerStopped {
neededVolumeContainers := make([]string, len(target.VolumesFrom))
for i, volume := range target.VolumesFrom {
neededVolumeContainers[i] = volume.SourceContainer
}
if !verifyStatusResolvable(target, nameMap, neededVolumeContainers, volumeIsResolved) {
return volumeUnresolvedErr
}
if !verifyStatusResolvable(target, nameMap, target.SteadyStateDependencies, onSteadyStateIsResolved) {
return DependentContainerNotResolvedErr
}
}

if !verifyStatusResolvable(target, nameMap, neededVolumeContainers, volumeIsResolved) {
return volumeUnresolvedErr
var linkedContainers []string
if target.GetDesiredStatus() == apicontainerstatus.ContainerStopped {
// build reverse dependencies in stopping
for _, cont := range by {
for _, link := range cont.Links {
link = strings.Split(link, ":")[0]
if link == target.Name {
linkedContainers = append(linkedContainers, cont.Name)
}
}
}
} else {
linkedContainers = linksToContainerNames(target.Links)
}

if !verifyStatusResolvable(target, nameMap, linksToContainerNames(target.Links), linkIsResolved) {
if !verifyStatusResolvable(target, nameMap, linkedContainers, linkIsResolved) {
return linkUnresolvedErr
}

if !verifyStatusResolvable(target, nameMap, target.SteadyStateDependencies, onSteadyStateIsResolved) {
return DependentContainerNotResolvedErr
resourcesMap := make(map[string]taskresource.TaskResource)
for _, resource := range resources {
resourcesMap[resource.GetName()] = resource
}

if err := verifyTransitionDependenciesResolved(target, nameMap, resourcesMap); err != nil {
return err
}
Expand Down Expand Up @@ -175,9 +192,7 @@ func executionCredentialsResolved(target *apicontainer.Container, id string, man
func verifyStatusResolvable(target *apicontainer.Container, existingContainers map[string]*apicontainer.Container,
dependencies []string, resolves func(*apicontainer.Container, *apicontainer.Container) bool) bool {
targetGoal := target.GetDesiredStatus()
if targetGoal != target.GetSteadyStateStatus() && targetGoal != apicontainerstatus.ContainerCreated {
// A container can always stop, die, or reach whatever other state it
// wants regardless of what dependencies it has
if targetGoal != target.GetSteadyStateStatus() && targetGoal != apicontainerstatus.ContainerCreated && targetGoal != apicontainerstatus.ContainerStopped {
return true
}

Expand Down Expand Up @@ -261,6 +276,11 @@ func linkIsResolved(target *apicontainer.Container, link *apicontainer.Container
// 'Created' or if the linked container is in 'steady state'
linkKnownStatus := link.GetKnownStatus()
return linkKnownStatus == apicontainerstatus.ContainerCreated || link.IsKnownSteadyState()
} else if targetDesiredStatus == apicontainerstatus.ContainerStopped {
// The 'target' container desires to be moved to 'Stopped' state.
// Allow this only if the known status of the linking container is 'Stopped'
linkKnownStatus := link.GetKnownStatus()
return linkKnownStatus >= apicontainerstatus.ContainerStopped
} else if targetDesiredStatus == target.GetSteadyStateStatus() {
// The 'target' container desires to be moved to its 'steady' state.
// Allow this only if the linked container is in 'steady state' as well
Expand Down
145 changes: 145 additions & 0 deletions agent/engine/dependencygraph/graph_test.go
Expand Up @@ -56,6 +56,15 @@ func createdContainer(name string, links, volumes []string, steadyState apiconta
return container
}

func stoppingContainer(name string, links, volumes []string) *apicontainer.Container {
container := apicontainer.NewContainerWithSteadyState(apicontainerstatus.ContainerRunning)
container.Name = name
container.Links = links
container.VolumesFrom = volumeStrToVol(volumes)
container.DesiredStatusUnsafe = apicontainerstatus.ContainerStopped
return container
}

func TestValidDependencies(t *testing.T) {
// Empty task
task := &apitask.Task{}
Expand Down Expand Up @@ -165,6 +174,47 @@ func TestDependenciesAreResolvedWhenSteadyStateIsRunning(t *testing.T) {
assert.NoError(t, err, "Php should resolve")
}

func TestDependenciesAreResolvedWhenStopping(t *testing.T) {
task := &apitask.Task{
Containers: []*apicontainer.Container{
{
Name: "redis",
DesiredStatusUnsafe: apicontainerstatus.ContainerStopped,
},
},
}
err := DependenciesAreResolved(task.Containers[0], task.Containers, "", nil, nil)
assert.NoError(t, err, "One container should resolve trivially")

// Webserver stack
php := stoppingContainer("php", []string{"db"}, []string{})
db := stoppingContainer("db", []string{}, []string{"dbdatavolume"})
dbdata := stoppingContainer("dbdatavolume", []string{}, []string{})
webserver := stoppingContainer("webserver", []string{"php"}, []string{"htmldata"})
htmldata := stoppingContainer("htmldata", []string{}, []string{"sharedcssfiles"})
sharedcssfiles := stoppingContainer("sharedcssfiles", []string{}, []string{})

task = &apitask.Task{
Containers: []*apicontainer.Container{
php, db, dbdata, webserver, htmldata, sharedcssfiles,
},
}

err = DependenciesAreResolved(webserver, task.Containers, "", nil, nil)
assert.NoError(t, err, "webserver with no deps should be resovled")

err = DependenciesAreResolved(htmldata, task.Containers, "", nil, nil)
assert.NoError(t, err, "Htmldata should be resolved, even though webserver volumes from htmldata")

err = DependenciesAreResolved(php, task.Containers, "", nil, nil)
assert.Error(t, err, "Php shouldn't be resolved, webserver linking to php")

webserver.KnownStatusUnsafe = apicontainerstatus.ContainerStopped

err = DependenciesAreResolved(php, task.Containers, "", nil, nil)
assert.NoError(t, err, "Php should be resolved, webserver (link to php) is stopped")
}

func TestRunDependencies(t *testing.T) {
c1 := &apicontainer.Container{
Name: "a",
Expand Down Expand Up @@ -389,6 +439,101 @@ func TestVolumeIsResolved(t *testing.T) {
assertResolved(volumeIsResolved, tc.TargetDesired, tc.VolumeKnown, tc.Resolved))
}
}
func TestLinkIsResolved(t *testing.T) {
testcases := []struct {
TargetDesired apicontainerstatus.ContainerStatus
VolumeKnown apicontainerstatus.ContainerStatus
Resolved bool
}{
{
TargetDesired: apicontainerstatus.ContainerCreated,
VolumeKnown: apicontainerstatus.ContainerStatusNone,
Resolved: false,
},
{
TargetDesired: apicontainerstatus.ContainerCreated,
VolumeKnown: apicontainerstatus.ContainerCreated,
Resolved: true,
},
{
TargetDesired: apicontainerstatus.ContainerCreated,
VolumeKnown: apicontainerstatus.ContainerRunning,
Resolved: true,
},
{
TargetDesired: apicontainerstatus.ContainerCreated,
VolumeKnown: apicontainerstatus.ContainerStopped,
Resolved: false,
},
{
TargetDesired: apicontainerstatus.ContainerCreated,
VolumeKnown: apicontainerstatus.ContainerZombie,
Resolved: false,
},
{
TargetDesired: apicontainerstatus.ContainerRunning,
VolumeKnown: apicontainerstatus.ContainerStatusNone,
Resolved: false,
},
{
TargetDesired: apicontainerstatus.ContainerRunning,
VolumeKnown: apicontainerstatus.ContainerCreated,
Resolved: false,
},
{
TargetDesired: apicontainerstatus.ContainerRunning,
VolumeKnown: apicontainerstatus.ContainerRunning,
Resolved: true,
},
{
TargetDesired: apicontainerstatus.ContainerRunning,
VolumeKnown: apicontainerstatus.ContainerStopped,
Resolved: false,
},
{
TargetDesired: apicontainerstatus.ContainerRunning,
VolumeKnown: apicontainerstatus.ContainerZombie,
Resolved: false,
},
{
TargetDesired: apicontainerstatus.ContainerStopped,
VolumeKnown: apicontainerstatus.ContainerStatusNone,
Resolved: false,
},
{
TargetDesired: apicontainerstatus.ContainerStopped,
VolumeKnown: apicontainerstatus.ContainerCreated,
Resolved: false,
},
{
TargetDesired: apicontainerstatus.ContainerStopped,
VolumeKnown: apicontainerstatus.ContainerRunning,
Resolved: false,
},
{
TargetDesired: apicontainerstatus.ContainerStopped,
VolumeKnown: apicontainerstatus.ContainerStopped,
Resolved: true,
},
{
TargetDesired: apicontainerstatus.ContainerStopped,
VolumeKnown: apicontainerstatus.ContainerZombie,
Resolved: true,
},
{
TargetDesired: apicontainerstatus.ContainerStatusNone,
Resolved: false,
},
{
TargetDesired: apicontainerstatus.ContainerZombie,
Resolved: false,
},
}
for _, tc := range testcases {
t.Run(fmt.Sprintf("T:%s+V:%s", tc.TargetDesired.String(), tc.VolumeKnown.String()),
assertResolved(linkIsResolved, tc.TargetDesired, tc.VolumeKnown, tc.Resolved))
}
}

func TestOnSteadyStateIsResolved(t *testing.T) {
testcases := []struct {
Expand Down