fix soft affinities/constraints

Signed-off-by: Victor Vieux <vieux@docker.com>
This commit is contained in:
Victor Vieux 2015-11-03 11:17:08 -08:00
parent 40f26856a5
commit ea5b2290ed
12 changed files with 143 additions and 106 deletions

View File

@ -19,14 +19,17 @@ func (f *AffinityFilter) Name() string {
}
// Filter is exported
func (f *AffinityFilter) Filter(config *cluster.ContainerConfig, nodes []*node.Node) ([]*node.Node, error) {
func (f *AffinityFilter) Filter(config *cluster.ContainerConfig, nodes []*node.Node, soft bool) ([]*node.Node, error) {
affinities, err := parseExprs(config.Affinities())
if err != nil {
return nil, err
}
for _, affinity := range affinities {
log.Debugf("matching affinity: %s%s%s", affinity.key, OPERATORS[affinity.operator], affinity.value)
if !soft && affinity.isSoft {
continue
}
log.Debugf("matching affinity: %s%s%s (soft=%t)", affinity.key, OPERATORS[affinity.operator], affinity.value, affinity.isSoft)
candidates := []*node.Node{}
for _, node := range nodes {
@ -65,12 +68,10 @@ func (f *AffinityFilter) Filter(config *cluster.ContainerConfig, nodes []*node.N
}
}
if len(candidates) == 0 {
if affinity.isSoft {
continue
}
return nil, fmt.Errorf("unable to find a node that satisfies %s%s%s", affinity.key, OPERATORS[affinity.operator], affinity.value)
}
nodes = candidates
}
return nodes, nil
}

View File

@ -62,137 +62,140 @@ func TestAffinityFilter(t *testing.T) {
)
// Without constraints we should get the unfiltered list of nodes back.
result, err = f.Filter(&cluster.ContainerConfig{}, nodes)
result, err = f.Filter(&cluster.ContainerConfig{}, nodes, true)
assert.NoError(t, err)
assert.Equal(t, result, nodes)
// Set a constraint that cannot be fulfilled and expect an error back.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container==does_not_exsits"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container==does_not_exsits"}}), nodes, true)
assert.Error(t, err)
// Set a constraint that can only be filled by a single node.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container==container-n0*"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container==container-n0*"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[0])
// This constraint can only be fulfilled by a subset of nodes.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container==container-*"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container==container-*"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
assert.NotContains(t, result, nodes[2])
// Validate by id.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container==container-n0-0-id"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container==container-n0-0-id"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[0])
// Validate by id.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container!=container-n0-0-id"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container!=container-n0-0-id"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
assert.NotContains(t, result, nodes[0])
// Validate by id.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container!=container-n0-1-id"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container!=container-n0-1-id"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
assert.NotContains(t, result, nodes[0])
// Validate by name.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container==container-n1-0-name"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container==container-n1-0-name"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[1])
// Validate by name.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container!=container-n1-0-name"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container!=container-n1-0-name"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
assert.NotContains(t, result, nodes[1])
// Validate by name.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container!=container-n1-1-name"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container!=container-n1-1-name"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
assert.NotContains(t, result, nodes[1])
// Conflicting Constraint
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container!=container-n1-1-name", "affinity:container==container-n1-1-name"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container!=container-n1-1-name", "affinity:container==container-n1-1-name"}}), nodes, true)
assert.Error(t, err)
assert.Len(t, result, 0)
// Validate images by id
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==image-0-id"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==image-0-id"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[0])
// Validate images by name
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==image-0:tag3"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==image-0:tag3"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[1])
// Validate images by name
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image!=image-0:tag3"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image!=image-0:tag3"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
// Validate images by name
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==image-1"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==image-1"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[1])
// Validate images by name
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image!=image-1"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image!=image-1"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
// Ensure that constraints can be chained.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container!=container-n0-1-id", "affinity:container!=container-n1-1-id"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container!=container-n0-1-id", "affinity:container!=container-n1-1-id"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[2])
// Ensure that constraints can be chained.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container==container-n0-1-id", "affinity:container==container-n1-1-id"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:container==container-n0-1-id", "affinity:container==container-n1-1-id"}}), nodes, true)
assert.Error(t, err)
//Tests for Soft affinity
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==~image-0:tag3"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==~image-0:tag3"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==~ima~ge-0:tag3"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==~ima~ge-0:tag3"}}), nodes, true)
assert.Error(t, err)
assert.Len(t, result, 0)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==~image-1:tag3"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==~image-1:tag3"}}), nodes, true)
assert.Error(t, err)
assert.Len(t, result, 0)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==~image-1:tag3"}}), nodes, false)
assert.NoError(t, err)
assert.Len(t, result, 3)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==~image-*"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==~image-*"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image!=~image-*"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image!=~image-*"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[2])
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==~/image-\\d*/"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==~/image-\\d*/"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
// Not support = any more
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image=image-0:tag3"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image=image-0:tag3"}}), nodes, true)
assert.Error(t, err)
assert.Len(t, result, 0)
// Not support =! any more
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image=!image-0:tag3"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image=!image-0:tag3"}}), nodes, true)
assert.Error(t, err)
assert.Len(t, result, 0)
@ -237,22 +240,22 @@ func TestAffinityFilterLabels(t *testing.T) {
err error
)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==image-1"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image==image-1"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[1])
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image!=image-1"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"affinity:image!=image-1"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[0])
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Labels: map[string]string{"com.docker.swarm.affinities": "[\"image==image-1\"]"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Labels: map[string]string{"com.docker.swarm.affinities": "[\"image==image-1\"]"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[1])
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Labels: map[string]string{"com.docker.swarm.affinities": "[\"image!=image-1\"]"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Labels: map[string]string{"com.docker.swarm.affinities": "[\"image!=image-1\"]"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[0])

View File

@ -18,14 +18,17 @@ func (f *ConstraintFilter) Name() string {
}
// Filter is exported
func (f *ConstraintFilter) Filter(config *cluster.ContainerConfig, nodes []*node.Node) ([]*node.Node, error) {
func (f *ConstraintFilter) Filter(config *cluster.ContainerConfig, nodes []*node.Node, soft bool) ([]*node.Node, error) {
constraints, err := parseExprs(config.Constraints())
if err != nil {
return nil, err
}
for _, constraint := range constraints {
log.Debugf("matching constraint: %s %s %s", constraint.key, OPERATORS[constraint.operator], constraint.value)
if !soft && constraint.isSoft {
continue
}
log.Debugf("matching constraint: %s%s%s (soft=%t)", constraint.key, OPERATORS[constraint.operator], constraint.value, constraint.isSoft)
candidates := []*node.Node{}
for _, node := range nodes {
@ -42,9 +45,6 @@ func (f *ConstraintFilter) Filter(config *cluster.ContainerConfig, nodes []*node
}
}
if len(candidates) == 0 {
if constraint.isSoft {
continue
}
return nil, fmt.Errorf("unable to find a node that satisfies %s%s%s", constraint.key, OPERATORS[constraint.operator], constraint.value)
}
nodes = candidates

View File

@ -61,54 +61,54 @@ func TestConstrainteFilter(t *testing.T) {
)
// Without constraints we should get the unfiltered list of nodes back.
result, err = f.Filter(&cluster.ContainerConfig{}, nodes)
result, err = f.Filter(&cluster.ContainerConfig{}, nodes, true)
assert.NoError(t, err)
assert.Equal(t, result, nodes)
// Set a constraint that cannot be fulfilled and expect an error back.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:does_not_exist==true"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:does_not_exist==true"}}), nodes, true)
assert.Error(t, err)
// Set a contraint that can only be filled by a single node.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:name==node1"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:name==node1"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[1])
// This constraint can only be fulfilled by a subset of nodes.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:group==1"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:group==1"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
assert.NotContains(t, result, nodes[2])
// Validate node pinning by id.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:node==node-2-id"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:node==node-2-id"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[2])
// Validate node pinning by name.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:node==node-1-name"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:node==node-1-name"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[1])
// Make sure constraints are evaluated as logical ANDs.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:name==node0", "constraint:group==1"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:name==node0", "constraint:group==1"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[0])
// Check matching
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:region==us"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:region==us"}}), nodes, true)
assert.Error(t, err)
assert.Len(t, result, 0)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:region==us*"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:region==us*"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:region==*us*"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:region==*us*"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
}
@ -122,22 +122,22 @@ func TestConstraintNotExpr(t *testing.T) {
)
// Check not (!) expression
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:name!=node0"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:name!=node0"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 3)
// Check not does_not_exist. All should be found
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:name!=does_not_exist"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:name!=does_not_exist"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 4)
// Check name must not start with n
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:name!=n*"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:name!=n*"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
// Check not with globber pattern
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:region!=us*"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:region!=us*"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
}
@ -151,22 +151,22 @@ func TestConstraintRegExp(t *testing.T) {
)
// Check with regular expression /node\d/ matches node{0..2}
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{`constraint:name==/node\d/`}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{`constraint:name==/node\d/`}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 3)
// Check with regular expression /node\d/ matches node{0..2}
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{`constraint:name==/node[12]/`}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{`constraint:name==/node[12]/`}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
// Check with regular expression ! and regexp /node[12]/ matches node[0] and node[3]
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{`constraint:name!=/node[12]/`}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{`constraint:name!=/node[12]/`}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
// Validate node pinning by ! and regexp.
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:node!=/node-[01]-id/"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:node!=/node-[01]-id/"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
}
@ -187,19 +187,19 @@ func TestFilterRegExpCaseInsensitive(t *testing.T) {
}
// Case-sensitive, so not match
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{`constraint:name==/abcdef/`}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{`constraint:name==/abcdef/`}}), nodes, true)
assert.Error(t, err)
assert.Len(t, result, 0)
// Match with case-insensitive
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{`constraint:name==/(?i)abcdef/`}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{`constraint:name==/(?i)abcdef/`}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[3])
assert.Equal(t, result[0].Labels["name"], "aBcDeF")
// Test ! filter combined with case insensitive
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{`constraint:name!=/(?i)abc*/`}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{`constraint:name!=/(?i)abc*/`}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 3)
}
@ -213,17 +213,17 @@ func TestFilterEquals(t *testing.T) {
)
// Check == comparison
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:name==node0"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:name==node0"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
// Test == with glob
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:region==us*"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:region==us*"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
// Validate node name with ==
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:node==node-1-name"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:node==node-1-name"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[1])
@ -237,11 +237,11 @@ func TestUnsupportedOperators(t *testing.T) {
err error
)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:name=node0"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:name=node0"}}), nodes, true)
assert.Error(t, err)
assert.Len(t, result, 0)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:name=!node0"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:name=!node0"}}), nodes, true)
assert.Error(t, err)
assert.Len(t, result, 0)
}
@ -254,26 +254,26 @@ func TestFilterSoftConstraint(t *testing.T) {
err error
)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:node==~node-1-name"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:node==~node-1-name"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[1])
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{`constraint:name!=~/(?i)abc*/`}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{`constraint:name!=~/(?i)abc*/`}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 4)
// Check not with globber pattern
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:region!=~us*"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:region!=~us*"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 2)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:region!=~can*"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:region!=~can*"}}), nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 4)
// Check matching
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:region==~us~"}}), nodes)
result, err = f.Filter(cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:region==~us~"}}), nodes, true)
assert.Error(t, err)
assert.Len(t, result, 0)
}

View File

@ -18,7 +18,7 @@ func (f *DependencyFilter) Name() string {
}
// Filter is exported
func (f *DependencyFilter) Filter(config *cluster.ContainerConfig, nodes []*node.Node) ([]*node.Node, error) {
func (f *DependencyFilter) Filter(config *cluster.ContainerConfig, nodes []*node.Node, _ bool) ([]*node.Node, error) {
if len(nodes) == 0 {
return nodes, nil
}

View File

@ -50,7 +50,7 @@ func TestDependencyFilterSimple(t *testing.T) {
// No dependencies - make sure we don't filter anything out.
config = &cluster.ContainerConfig{}
result, err = f.Filter(config, nodes)
result, err = f.Filter(config, nodes, true)
assert.NoError(t, err)
assert.Equal(t, result, nodes)
@ -58,7 +58,7 @@ func TestDependencyFilterSimple(t *testing.T) {
config = &cluster.ContainerConfig{dockerclient.ContainerConfig{HostConfig: dockerclient.HostConfig{
VolumesFrom: []string{"c0"},
}}}
result, err = f.Filter(config, nodes)
result, err = f.Filter(config, nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[0])
@ -67,7 +67,7 @@ func TestDependencyFilterSimple(t *testing.T) {
config = &cluster.ContainerConfig{dockerclient.ContainerConfig{HostConfig: dockerclient.HostConfig{
VolumesFrom: []string{"c0:rw"},
}}}
result, err = f.Filter(config, nodes)
result, err = f.Filter(config, nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[0])
@ -76,7 +76,7 @@ func TestDependencyFilterSimple(t *testing.T) {
config = &cluster.ContainerConfig{dockerclient.ContainerConfig{HostConfig: dockerclient.HostConfig{
VolumesFrom: []string{"c0:ro"},
}}}
result, err = f.Filter(config, nodes)
result, err = f.Filter(config, nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[0])
@ -85,7 +85,7 @@ func TestDependencyFilterSimple(t *testing.T) {
config = &cluster.ContainerConfig{dockerclient.ContainerConfig{HostConfig: dockerclient.HostConfig{
Links: []string{"c1:foobar"},
}}}
result, err = f.Filter(config, nodes)
result, err = f.Filter(config, nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[1])
@ -94,7 +94,7 @@ func TestDependencyFilterSimple(t *testing.T) {
config = &cluster.ContainerConfig{dockerclient.ContainerConfig{HostConfig: dockerclient.HostConfig{
NetworkMode: "container:c2",
}}}
result, err = f.Filter(config, nodes)
result, err = f.Filter(config, nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[2])
@ -103,7 +103,7 @@ func TestDependencyFilterSimple(t *testing.T) {
config = &cluster.ContainerConfig{dockerclient.ContainerConfig{HostConfig: dockerclient.HostConfig{
NetworkMode: "bridge",
}}}
result, err = f.Filter(config, nodes)
result, err = f.Filter(config, nodes, true)
assert.NoError(t, err)
assert.Equal(t, result, nodes)
}
@ -158,7 +158,7 @@ func TestDependencyFilterMulti(t *testing.T) {
config = &cluster.ContainerConfig{dockerclient.ContainerConfig{HostConfig: dockerclient.HostConfig{
VolumesFrom: []string{"c0"},
}}}
result, err = f.Filter(config, nodes)
result, err = f.Filter(config, nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[0])
@ -167,7 +167,7 @@ func TestDependencyFilterMulti(t *testing.T) {
config = &cluster.ContainerConfig{dockerclient.ContainerConfig{HostConfig: dockerclient.HostConfig{
VolumesFrom: []string{"c1"},
}}}
result, err = f.Filter(config, nodes)
result, err = f.Filter(config, nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[0])
@ -176,7 +176,7 @@ func TestDependencyFilterMulti(t *testing.T) {
config = &cluster.ContainerConfig{dockerclient.ContainerConfig{HostConfig: dockerclient.HostConfig{
VolumesFrom: []string{"c0", "c1"},
}}}
result, err = f.Filter(config, nodes)
result, err = f.Filter(config, nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[0])
@ -185,7 +185,7 @@ func TestDependencyFilterMulti(t *testing.T) {
config = &cluster.ContainerConfig{dockerclient.ContainerConfig{HostConfig: dockerclient.HostConfig{
VolumesFrom: []string{"c0", "c2"},
}}}
result, err = f.Filter(config, nodes)
result, err = f.Filter(config, nodes, true)
assert.Error(t, err)
}
@ -243,7 +243,7 @@ func TestDependencyFilterChaining(t *testing.T) {
NetworkMode: "container:c1",
},
}}
result, err = f.Filter(config, nodes)
result, err = f.Filter(config, nodes, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodes[0])
@ -256,6 +256,6 @@ func TestDependencyFilterChaining(t *testing.T) {
NetworkMode: "container:c1",
},
}}
result, err = f.Filter(config, nodes)
result, err = f.Filter(config, nodes, true)
assert.Error(t, err)
}

View File

@ -13,7 +13,7 @@ type Filter interface {
Name() string
// Return a subset of nodes that were accepted by the filtering policy.
Filter(*cluster.ContainerConfig, []*node.Node) ([]*node.Node, error)
Filter(*cluster.ContainerConfig, []*node.Node, bool) ([]*node.Node, error)
}
var (
@ -55,15 +55,28 @@ func New(names []string) ([]Filter, error) {
// ApplyFilters applies a set of filters in batch.
func ApplyFilters(filters []Filter, config *cluster.ContainerConfig, nodes []*node.Node) ([]*node.Node, error) {
var err error
candidates, err := applyFilters(filters, config, nodes, true)
if err != nil {
candidates, err = applyFilters(filters, config, nodes, false)
}
return candidates, err
}
func applyFilters(filters []Filter, config *cluster.ContainerConfig, nodes []*node.Node, soft bool) ([]*node.Node, error) {
var (
err error
candidates = nodes
)
for _, filter := range filters {
nodes, err = filter.Filter(config, nodes)
candidates, err = filter.Filter(config, candidates, soft)
if err != nil {
return nil, err
}
}
return nodes, nil
return candidates, nil
}
// List returns the names of all the available filters

View File

@ -22,7 +22,7 @@ func (f *HealthFilter) Name() string {
}
// Filter is exported
func (f *HealthFilter) Filter(_ *cluster.ContainerConfig, nodes []*node.Node) ([]*node.Node, error) {
func (f *HealthFilter) Filter(_ *cluster.ContainerConfig, nodes []*node.Node, _ bool) ([]*node.Node, error) {
result := []*node.Node{}
for _, node := range nodes {
if node.IsHealthy {

View File

@ -66,16 +66,16 @@ func TestHealthyFilter(t *testing.T) {
err error
)
result, err = f.Filter(&cluster.ContainerConfig{}, nodesAllHealth)
result, err = f.Filter(&cluster.ContainerConfig{}, nodesAllHealth, true)
assert.NoError(t, err)
assert.Equal(t, result, nodesAllHealth)
result, err = f.Filter(&cluster.ContainerConfig{}, nodesPartHealth)
result, err = f.Filter(&cluster.ContainerConfig{}, nodesPartHealth, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodesPartHealth[1])
result, err = f.Filter(&cluster.ContainerConfig{}, nodesNoHealth)
result, err = f.Filter(&cluster.ContainerConfig{}, nodesNoHealth, true)
assert.Equal(t, err, ErrNoHealthyNodeAvailable)
assert.Nil(t, result)
}

View File

@ -20,7 +20,7 @@ func (p *PortFilter) Name() string {
}
// Filter is exported
func (p *PortFilter) Filter(config *cluster.ContainerConfig, nodes []*node.Node) ([]*node.Node, error) {
func (p *PortFilter) Filter(config *cluster.ContainerConfig, nodes []*node.Node, _ bool) ([]*node.Node, error) {
if config.HostConfig.NetworkMode == "host" {
return p.filterHost(config, nodes)
}

View File

@ -50,7 +50,7 @@ func TestPortFilterNoConflicts(t *testing.T) {
PortBindings: map[string][]dockerclient.PortBinding{},
}}}
// Make sure we don't filter anything out.
result, err = p.Filter(config, nodes)
result, err = p.Filter(config, nodes, true)
assert.NoError(t, err)
assert.Equal(t, result, nodes)
@ -61,7 +61,7 @@ func TestPortFilterNoConflicts(t *testing.T) {
// Since there are no other containers in the cluster, this shouldn't
// filter anything either.
result, err = p.Filter(config, nodes)
result, err = p.Filter(config, nodes, true)
assert.NoError(t, err)
assert.Equal(t, result, nodes)
@ -70,7 +70,7 @@ func TestPortFilterNoConflicts(t *testing.T) {
assert.NoError(t, nodes[0].AddContainer(container))
// Since no node is using port 80, there should be no filter
result, err = p.Filter(config, nodes)
result, err = p.Filter(config, nodes, true)
assert.NoError(t, err)
assert.Equal(t, result, nodes)
}
@ -109,7 +109,7 @@ func TestPortFilterSimple(t *testing.T) {
}}}
// nodes[0] should be excluded since port 80 is taken away.
result, err = p.Filter(config, nodes)
result, err = p.Filter(config, nodes, true)
assert.NoError(t, err)
assert.NotContains(t, result, nodes[0])
}
@ -149,7 +149,7 @@ func TestPortFilterDifferentInterfaces(t *testing.T) {
// nodes[0] should be excluded since port 80 is taken away for every
// interface.
result, err = p.Filter(config, nodes)
result, err = p.Filter(config, nodes, true)
assert.NoError(t, err)
assert.NotContains(t, result, nodes[0])
@ -163,7 +163,7 @@ func TestPortFilterDifferentInterfaces(t *testing.T) {
}}}
// nodes[1] should be excluded since port 4242 is already taken on that
// interface.
result, err = p.Filter(config, nodes)
result, err = p.Filter(config, nodes, true)
assert.NoError(t, err)
assert.NotContains(t, result, nodes[1])
@ -172,7 +172,7 @@ func TestPortFilterDifferentInterfaces(t *testing.T) {
PortBindings: makeBinding("0.0.0.0", "4242"),
}}}
// nodes[1] should still be excluded since the port is not available on the same interface.
result, err = p.Filter(config, nodes)
result, err = p.Filter(config, nodes, true)
assert.NoError(t, err)
assert.NotContains(t, result, nodes[1])
@ -181,7 +181,7 @@ func TestPortFilterDifferentInterfaces(t *testing.T) {
PortBindings: makeBinding("", "4242"),
}}}
// nodes[1] should still be excluded since the port is not available on the same interface.
result, err = p.Filter(config, nodes)
result, err = p.Filter(config, nodes, true)
assert.NoError(t, err)
assert.NotContains(t, result, nodes[1])
@ -191,7 +191,7 @@ func TestPortFilterDifferentInterfaces(t *testing.T) {
}}}
// nodes[1] should be included this time since the port is available on the
// other interface.
result, err = p.Filter(config, nodes)
result, err = p.Filter(config, nodes, true)
assert.NoError(t, err)
assert.Contains(t, result, nodes[1])
}
@ -262,7 +262,7 @@ func TestPortFilterRandomAssignment(t *testing.T) {
}}}
// Since port "80" has been mapped to "1234", we should be able to request "80".
result, err = p.Filter(config, nodes)
result, err = p.Filter(config, nodes, true)
assert.NoError(t, err)
assert.Equal(t, result, nodes)
@ -270,7 +270,7 @@ func TestPortFilterRandomAssignment(t *testing.T) {
config = &cluster.ContainerConfig{dockerclient.ContainerConfig{HostConfig: dockerclient.HostConfig{
PortBindings: makeBinding("", "1234"),
}}}
result, err = p.Filter(config, nodes)
result, err = p.Filter(config, nodes, true)
assert.NoError(t, err)
assert.NotContains(t, result, nodes[0])
}
@ -323,7 +323,7 @@ func TestPortFilterForHostMode(t *testing.T) {
}}
// nodes[0] should be excluded since port 80 is taken away
result, err = p.Filter(config, nodes)
result, err = p.Filter(config, nodes, true)
assert.NoError(t, err)
assert.Equal(t, 2, len(result))
assert.NotContains(t, result, nodes[0])

View File

@ -145,6 +145,26 @@ function teardown() {
[[ "${output}" == *"not found"* ]]
}
@test "docker run - constraint and soft affinities" {
start_docker_with_busybox 1 --label group=A
start_docker_with_busybox 1 --label group=B
swarm_manage
# start c0 on a node in group=A
docker_swarm run -d --name c0 -e constraint:group==A -e affinity:container==~c0 busybox sleep 100
# check container running on node-0
run docker_swarm ps
[[ "${output}" == *"node-0/c0"* ]]
# start c2 on a node in group==B (soft affinity shouldn't matter here)
docker_swarm run -d --name c2 -e constraint:group==B -e affinity:container==~c0 busybox sleep 100
# check container running on a node in group=B
run docker_swarm ps
[[ "${output}" == *"node-1/c2"* ]]
}
@test "docker run - with not exist volume driver" {
start_docker_with_busybox 2
swarm_manage