diff --git a/cluster/config.go b/cluster/config.go index 69c547a907..a46b5f5731 100644 --- a/cluster/config.go +++ b/cluster/config.go @@ -146,3 +146,15 @@ func (c *ContainerConfig) Affinities() []string { func (c *ContainerConfig) Constraints() []string { return c.extractExprs("constraints") } + +// AddAffinity to config +func (c *ContainerConfig) AddAffinity(affinity string) error { + affinities := c.extractExprs("affinities") + affinities = append(affinities, affinity) + labels, err := json.Marshal(affinities) + if err != nil { + return err + } + c.Labels[SwarmLabelNamespace+".affinities"] = string(labels) + return nil +} diff --git a/cluster/config_test.go b/cluster/config_test.go index f4452d5fc4..15e0a6fe4f 100644 --- a/cluster/config_test.go +++ b/cluster/config_test.go @@ -81,3 +81,11 @@ func TestConsolidateResourceFields(t *testing.T) { } } + +func TestAddAffinity(t *testing.T) { + config := BuildContainerConfig(dockerclient.ContainerConfig{}) + assert.Empty(t, config.Affinities()) + + config.AddAffinity("image==~testimage") + assert.Len(t, config.Affinities(), 1) +} diff --git a/cluster/swarm/cluster.go b/cluster/swarm/cluster.go index 8f2c7d1f65..d7d960f2a5 100644 --- a/cluster/swarm/cluster.go +++ b/cluster/swarm/cluster.go @@ -89,6 +89,20 @@ func (c *Cluster) generateUniqueID() string { // CreateContainer aka schedule a brand new container into the cluster. func (c *Cluster) CreateContainer(config *cluster.ContainerConfig, name string) (*cluster.Container, error) { + container, err := c.createContainer(config, name, false) + + // fails with image not found, then try to reschedule with soft-image-affinity + if err != nil && strings.HasSuffix(err.Error(), "not found") { + // Check if the image exists in the cluster + // If exists, retry with a soft-image-affinity + if image := c.Image(config.Image); image != nil { + container, err = c.createContainer(config, name, true) + } + } + return container, err +} + +func (c *Cluster) createContainer(config *cluster.ContainerConfig, name string, withSoftImageAffinity bool) (*cluster.Container, error) { c.scheduler.Lock() defer c.scheduler.Unlock() @@ -100,7 +114,12 @@ func (c *Cluster) CreateContainer(config *cluster.ContainerConfig, name string) // Associate a Swarm ID to the container we are creating. config.SetSwarmID(c.generateUniqueID()) - n, err := c.scheduler.SelectNodeForContainer(c.listNodes(), config) + configTemp := config + if withSoftImageAffinity { + configTemp.AddAffinity("image==~" + config.Image) + } + + n, err := c.scheduler.SelectNodeForContainer(c.listNodes(), configTemp) if err != nil { return nil, err } diff --git a/test/integration/api/run.bats b/test/integration/api/run.bats index 2c07b1486c..7729c6a972 100644 --- a/test/integration/api/run.bats +++ b/test/integration/api/run.bats @@ -66,3 +66,25 @@ function teardown() { # pid [[ "${output}" == *"\"PidMode\": \"host\""* ]] } + +@test "docker run - reschedule with soft-image-affinity" { + start_docker_with_busybox 1 + start_docker 1 + + docker -H ${HOSTS[0]} tag busybox:latest busyboxabcde:latest + swarm_manage + + # make sure busyboxabcde exists + run docker_swarm images + [ "$status" -eq 0 ] + [[ "${output}" == *"busyboxabcde"* ]] + + # try to create container on node-1, node-1 does not have busyboxabcde and will pull it + # but can not find busyboxabcde in dockerhub + # then will retry with soft-image-affinity + docker_swarm run -d --name test_container -e constraint:node==~node-1 busyboxabcde sleep 1000 + + # check container running on node-0 + run docker_swarm ps + [[ "${output}" == *"node-0/test_container"* ]] +}