diff --git a/scheduler/filter/constraint.go b/scheduler/filter/constraint.go index 9c7b370629..664f987ba3 100644 --- a/scheduler/filter/constraint.go +++ b/scheduler/filter/constraint.go @@ -58,6 +58,16 @@ func (f *ConstraintFilter) Filter(config *dockerclient.ContainerConfig, nodes [] negate = true } + gte := strings.HasSuffix(k, ">") + lte := strings.HasSuffix(k, "<") + if gte { + log.Debugf("gt (>) detected in key") + k = strings.TrimSuffix(k, ">") + } else if lte { + log.Debugf("lt (<) detected in key") + k = strings.TrimSuffix(k, "<") + } + useRegex := false if strings.HasPrefix(v, "/") && strings.HasSuffix(v, "/") { log.Debugf("regex detected") @@ -70,20 +80,32 @@ func (f *ConstraintFilter) Filter(config *dockerclient.ContainerConfig, nodes [] switch k { case "node": // "node" label is a special case pinning a container to a specific node. -<<<<<<< HEAD - if match(v, node.ID) || match(v, node.Name) { -======= matchResult := f.match(v, node.ID, useRegex) || f.match(v, node.Name, useRegex) if (negate && !matchResult) || (!negate && matchResult) { ->>>>>>> improve regexp matching + + if gte && node.ID >= v { candidates = append(candidates, node) + } else if lte && node.ID <= v { + candidates = append(candidates, node) + } else { + // "node" label is a special case pinning a container to a specific node. + matchResult := f.match(v, node.ID, useRegex) || f.match(v, node.Name, useRegex) + if (negate && !matchResult) || (!negate && matchResult) { + candidates = append(candidates, node) + } } default: // By default match the node labels. if label, ok := node.Labels[k]; ok { - matchResult := f.match(v, label, useRegex) - if (negate && !matchResult) || (!negate && matchResult) { + if gte && label >= v { candidates = append(candidates, node) + } else if lte && label <= v { + candidates = append(candidates, node) + } else { + matchResult := f.match(v, label, useRegex) + if (negate && !matchResult) || (!negate && matchResult) { + candidates = append(candidates, node) + } } } } diff --git a/scheduler/filter/constraint_test.go b/scheduler/filter/constraint_test.go index 9fc1fb830f..e39ec69752 100644 --- a/scheduler/filter/constraint_test.go +++ b/scheduler/filter/constraint_test.go @@ -305,3 +305,54 @@ func TestFilterRegExpCaseInsensitive(t *testing.T) { assert.NoError(t, err) assert.Len(t, result, 3) } + +func TestFilterWithRelativeComparisons(t *testing.T) { + var ( + f = ConstraintFilter{} + nodes = testFixtures() + result []*cluster.Node + err error + ) + + // Prepare node with a strange name + node3 := cluster.NewNode("node-3", 0) + node3.ID = "node-3-id" + node3.Name = "node-3-name" + node3.Labels = map[string]string{ + "name": "aBcDeF", + "group": "4", + "kernel": "3.1", + "region": "eu", + } + nodes = append(nodes, node3) + + // Check with less than or equal + result, err = f.Filter(&dockerclient.ContainerConfig{ + Env: []string{`constraint:group<=3`}, + }, nodes) + assert.NoError(t, err) + assert.Len(t, result, 3) + + // Check with greater than or equal + result, err = f.Filter(&dockerclient.ContainerConfig{ + Env: []string{`constraint:group>=4`}, + }, nodes) + assert.NoError(t, err) + assert.Len(t, result, 1) + + // Another gte check with a complex string + result, err = f.Filter(&dockerclient.ContainerConfig{ + Env: []string{`constraint:kernel>=3.0`}, + }, nodes) + assert.NoError(t, err) + assert.Len(t, result, 1) + assert.Equal(t, result[0], nodes[3]) + assert.Equal(t, result[0].Labels["kernel"], "3.1") + + // Check with greater than or equal. This should match node-3-id. + result, err = f.Filter(&dockerclient.ContainerConfig{ + Env: []string{`constraint:node>=node-3`}, + }, nodes) + assert.NoError(t, err) + assert.Len(t, result, 1) +}