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

Fix container downtime #622

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
15 changes: 10 additions & 5 deletions internal/actions/actions_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ var _ = Describe("the actions package", func() {
"test-container",
"test-container",
"watchtower",
time.Now()),
time.Now(),
make([]string,0)),
}
err := actions.CheckForMultipleWatchtowerInstances(client, false, "")
Expect(err).NotTo(HaveOccurred())
Expand All @@ -75,12 +76,14 @@ var _ = Describe("the actions package", func() {
"test-container-01",
"test-container-01",
"watchtower",
time.Now().AddDate(0, 0, -1)),
time.Now().AddDate(0, 0, -1),
make([]string,0),),
CreateMockContainer(
"test-container-02",
"test-container-02",
"watchtower",
time.Now()),
time.Now(),
make([]string,0)),
},
},
dockerClient,
Expand All @@ -106,12 +109,14 @@ var _ = Describe("the actions package", func() {
"test-container-01",
"test-container-01",
"watchtower",
time.Now().AddDate(0, 0, -1)),
time.Now().AddDate(0, 0, -1),
make([]string,0),),
CreateMockContainer(
"test-container-02",
"test-container-02",
"watchtower",
time.Now()),
time.Now(),
make([]string,0),),
},
},
dockerClient,
Expand Down
4 changes: 4 additions & 0 deletions internal/actions/mocks/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type TestData struct {
TriedToRemoveImageCount int
NameOfContainerToKeep string
Containers []container.Container
StopOrder []string
RestartOrder []string
}

// TriedToRemoveImage is a test helper function to check whether RemoveImageByID has been called
Expand Down Expand Up @@ -49,11 +51,13 @@ func (client MockClient) StopContainer(c container.Container, d time.Duration) e
if c.Name() == client.TestData.NameOfContainerToKeep {
return errors.New("tried to stop the instance we want to keep")
}
client.TestData.StopOrder = append(client.TestData.StopOrder, c.Name())
return nil
}

// StartContainer is a mock method
func (client MockClient) StartContainer(c container.Container) (string, error) {
client.TestData.RestartOrder = append(client.TestData.RestartOrder, c.Name())
return "", nil
}

Expand Down
11 changes: 10 additions & 1 deletion internal/actions/mocks/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

// CreateMockContainer creates a container substitute valid for testing
func CreateMockContainer(id string, name string, image string, created time.Time) container.Container {
func CreateMockContainer(id string, name string, image string, created time.Time, depends []string) container.Container {
content := types.ContainerJSON{
ContainerJSONBase: &types.ContainerJSONBase{
ID: id,
Expand All @@ -20,6 +20,15 @@ func CreateMockContainer(id string, name string, image string, created time.Time
Labels: make(map[string]string),
},
}
dependencyString := ""
for ind, i := range depends {
if ind == 0 {
dependencyString += i;
}else{
dependencyString += "," + i;
}
}
content.Config.Labels["com.centurylinklabs.watchtower.depends-on"] = dependencyString
return *container.NewContainer(
&content,
&types.ImageInspect{
Expand Down
100 changes: 73 additions & 27 deletions internal/actions/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,36 @@ import (
log "github.com/sirupsen/logrus"
)

// Update looks at the running Docker containers to see if any of the images
// used to start those containers have been updated. If a change is detected in
// any of the images, the associated containers are stopped and restarted with
// the new image.
func Update(client container.Client, params types.UpdateParams) error {
log.Debug("Checking containers for updated images")
// CreateUndirectedLinks creates a map of undirected links
// Key: Name of a container
// Value: List of containers that are linked to the container
// i.e if Container A depends on B, undirectedNodes['A'] will initially contain B.
// This function adds 'A' into undirectedNodes['B'] to make the link undirected.
func CreateUndirectedLinks(containers []container.Container) map[string][]string {

if params.LifecycleHooks {
lifecycle.ExecutePreChecks(client, params)
undirectedNodes := make(map[string][]string)
for i:= 0; i < len(containers); i++ {
undirectedNodes[containers[i].Name()] = containers[i].Links()
}

for i:= 0; i< len(containers); i++ {
for j:=0; j < len(containers[i].Links()); j++ {
undirectedNodes[containers[i].Links()[j]] = append(undirectedNodes[containers[i].Links()[j]], containers[i].Name())
}
}

return undirectedNodes;
}

// PrepareContainerList prepares a dependency sorted list of list of containers
// Each list inside the outer list contains containers that are related by links
// This method checks for staleness, checks dependencies, sorts the containers and returns the final
// [][]container.Container
func PrepareContainerList(client container.Client, params types.UpdateParams) ([]container.Container, error) {

containers, err := client.ListContainers(params.Filter)
if err != nil {
return err
return nil, err
}

for i, targetContainer := range containers {
Expand All @@ -38,31 +54,67 @@ func Update(client container.Client, params types.UpdateParams) error {
containers[i].Stale = stale
}

containers, err = sorter.SortByDependencies(containers)
checkDependencies(containers)

return containers, nil
}

// Update looks at the running Docker containers to see if any of the images
// used to start those containers have been updated. If a change is detected in
// any of the images, the associated containers are stopped and restarted with
// the new image.
func Update(client container.Client, params types.UpdateParams) error {
log.Debug("Checking containers for updated images")

if params.LifecycleHooks {
lifecycle.ExecutePreChecks(client, params)
}

containers, err := PrepareContainerList(client, params)
if err != nil {
return err
}

checkDependencies(containers)

containersToUpdate := []container.Container{}
if !params.MonitorOnly {
for i := len(containers) - 1; i >= 0; i-- {
for i := 0; i < len(containers); i++ {
if !containers[i].IsMonitorOnly() {
containersToUpdate = append(containersToUpdate, containers[i])
}
}
}

//shared map for independent and linked update
imageIDs := make(map[string]bool)

if params.RollingRestart {
performRollingRestart(containersToUpdate, client, params)
} else {
stopContainersInReversedOrder(containersToUpdate, client, params)
restartContainersInSortedOrder(containersToUpdate, client, params)
var dependencySortedGraphs [][]container.Container

undirectedNodes := CreateUndirectedLinks(containersToUpdate)
dependencySortedGraphs, err := sorter.SortByDependencies(containersToUpdate,undirectedNodes)

if err != nil {
return err
}

//Use ordered start and stop for each independent set of containers
for _, dependencyGraph:= range dependencySortedGraphs {
stopContainersInReversedOrder(dependencyGraph, client, params)
restartContainersInSortedOrder(dependencyGraph, client, params, imageIDs)
}

//clean up after containers updated
if params.Cleanup {
cleanupImages(client,imageIDs)
}
}

if params.LifecycleHooks {
lifecycle.ExecutePostChecks(client, params)
}

return nil
}

Expand Down Expand Up @@ -109,19 +161,13 @@ func stopStaleContainer(container container.Container, client container.Client,
}
}

func restartContainersInSortedOrder(containers []container.Container, client container.Client, params types.UpdateParams) {
imageIDs := make(map[string]bool)

for _, staleContainer := range containers {
if !staleContainer.Stale {
func restartContainersInSortedOrder(containers []container.Container, client container.Client, params types.UpdateParams, imageIDs map[string]bool) {
for _, container := range containers {
if !container.Stale {
continue
}
restartStaleContainer(staleContainer, client, params)
imageIDs[staleContainer.ImageID()] = true
}

if params.Cleanup {
cleanupImages(client, imageIDs)
restartStaleContainer(container, client, params)
imageIDs[container.ImageID()] = true
}
}

Expand Down Expand Up @@ -171,4 +217,4 @@ func checkDependencies(containers []container.Container) {
}
}
}
}
}
Loading