From 54c7c12d057357408752e3f15ef6e92bcc577646 Mon Sep 17 00:00:00 2001 From: Chanwit Kaewkasi Date: Sun, 11 Jan 2015 13:54:01 +0700 Subject: [PATCH] improve regexp matching Signed-off-by: Chanwit Kaewkasi --- scheduler/filter/constraint.go | 56 +++++++++++++++++++++++----- scheduler/filter/constraint_test.go | 57 +++++++++++++++++++++++++++-- 2 files changed, 101 insertions(+), 12 deletions(-) diff --git a/scheduler/filter/constraint.go b/scheduler/filter/constraint.go index f2192d45ad..b06645ac8b 100644 --- a/scheduler/filter/constraint.go +++ b/scheduler/filter/constraint.go @@ -12,41 +12,79 @@ import ( type ConstraintFilter struct { } +func (f *ConstraintFilter) extractConstraints(env []string) map[string]string { + constraints := make(map[string]string) + for _, e := range env { + if strings.HasPrefix(e, "constraint:") { + constraint := strings.TrimPrefix(e, "constraint:") + parts := strings.SplitN(constraint, "=", 2) + constraints[strings.ToLower(parts[0])] = strings.ToLower(parts[1]) + } + } + return constraints +} + +// Create the regex for globbing (ex: ub*t* -> ^ub.*t.*$) +// and match. +func (f *ConstraintFilter) match(pattern, s string, useRegex bool) bool { + regex := pattern + if !useRegex { + regex = "^" + strings.Replace(pattern, "*", ".*", -1) + "$" + } + matched, err := regexp.MatchString(regex, strings.ToLower(s)) + if err != nil { + log.Error(err) + } + return matched +} + func (f *ConstraintFilter) Filter(config *dockerclient.ContainerConfig, nodes []*cluster.Node) ([]*cluster.Node, error) { constraints := extractEnv("constraint", config.Env) for k, v := range constraints { + + v0 := v + log.Debugf("matching constraint: %s=%s", k, v) + negate := false + useRegex := false + if strings.HasPrefix(v, "!") { log.Debugf("negate detected") v = strings.TrimPrefix(v, "!") negate = true } + if strings.HasPrefix(v, "/") && strings.HasSuffix(v, "/") { + log.Infof("regex detected") + v = strings.TrimPrefix(strings.TrimSuffix(v, "/"), "/") + useRegex = true + } + candidates := []*cluster.Node{} for _, node := range 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 candidates = append(candidates, node) } default: // By default match the node labels. if label, ok := node.Labels[k]; ok { - if negate { - if f.match(v, label) == false { - candidates = append(candidates, node) - } - } else { - if f.match(v, label) { - candidates = append(candidates, node) - } + matchResult := f.match(v, label, useRegex) + if (negate && !matchResult) || (!negate && matchResult) { + candidates = append(candidates, node) } } } } if len(candidates) == 0 { - return nil, fmt.Errorf("unable to find a node that satisfies %s == %s", k, v) + return nil, fmt.Errorf("unable to find a node that satisfies %s = %s", k, v0) } nodes = candidates } diff --git a/scheduler/filter/constraint_test.go b/scheduler/filter/constraint_test.go index 85116f77cb..7f7e4c9168 100644 --- a/scheduler/filter/constraint_test.go +++ b/scheduler/filter/constraint_test.go @@ -40,7 +40,6 @@ func testFixtures() (nodes []*cluster.Node) { return } -/* func TestConstrainteFilter(t *testing.T) { var ( f = ConstraintFilter{} @@ -113,9 +112,8 @@ func TestConstrainteFilter(t *testing.T) { assert.NoError(t, err) assert.Len(t, result, 2) } -*/ -func TestConstraintNotExpression(t *testing.T) { +func TestConstraintNotExpr(t *testing.T) { var ( f = ConstraintFilter{} nodes = testFixtures() @@ -130,6 +128,20 @@ func TestConstraintNotExpression(t *testing.T) { assert.NoError(t, err) assert.Len(t, result, 2) + // Check not does_not_exist. All should be found + result, err = f.Filter(&dockerclient.ContainerConfig{ + Env: []string{"constraint:name=!does_not_exist"}, + }, nodes) + assert.NoError(t, err) + assert.Len(t, result, 3) + + // Check name must not start with n + result, err = f.Filter(&dockerclient.ContainerConfig{ + Env: []string{"constraint:name=!n*"}, + }, nodes) + assert.Error(t, err) + assert.Len(t, result, 0) + // Check not with globber pattern result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{"constraint:region=!us*"}, @@ -138,3 +150,42 @@ func TestConstraintNotExpression(t *testing.T) { assert.Len(t, result, 1) assert.Equal(t, result[0].Labels["region"], "eu") } + +func TestConstraintRegExp(t *testing.T) { + var ( + f = ConstraintFilter{} + nodes = testFixtures() + result []*cluster.Node + err error + ) + + // Check with regular expression /node\d/ matches node{0..2} + result, err = f.Filter(&dockerclient.ContainerConfig{ + Env: []string{`constraint:name=/node\d/`}, + }, nodes) + assert.NoError(t, err) + assert.Len(t, result, 3) + + // Check with regular expression /node\d/ matches node{0..2} + result, err = f.Filter(&dockerclient.ContainerConfig{ + Env: []string{`constraint:name=/node[12]/`}, + }, nodes) + assert.NoError(t, err) + assert.Len(t, result, 2) + + // Check with regular expression ! and regexp /node[12]/ matches node[0] + result, err = f.Filter(&dockerclient.ContainerConfig{ + Env: []string{`constraint:name=!/node[12]/`}, + }, nodes) + assert.NoError(t, err) + assert.Len(t, result, 1) + assert.Equal(t, result[0], nodes[0]) + + // Validate node pinning by ! and regexp. + result, err = f.Filter(&dockerclient.ContainerConfig{ + Env: []string{"constraint:node=!/node-[01]-id/"}, + }, nodes) + assert.NoError(t, err) + assert.Len(t, result, 1) + assert.Equal(t, result[0], nodes[2]) +}