Skip to content

Commit

Permalink
Merge pull request #2942 from hashicorp/f-distinct-count
Browse files Browse the repository at this point in the history
Distinct Property supports arbitrary limit
  • Loading branch information
dadgar committed Aug 1, 2017
2 parents 3895ae1 + 5a5e043 commit 2e7d8ad
Show file tree
Hide file tree
Showing 8 changed files with 504 additions and 61 deletions.
14 changes: 14 additions & 0 deletions helper/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,20 @@ func IntMin(a, b int) int {
return b
}

func IntMax(a, b int) int {
if a > b {
return a
}
return b
}

func Uint64Max(a, b uint64) uint64 {
if a > b {
return a
}
return b
}

// MapStringStringSliceValueSet returns the set of values in a map[string][]string
func MapStringStringSliceValueSet(m map[string][]string) []string {
set := make(map[string]struct{})
Expand Down
38 changes: 38 additions & 0 deletions nomad/structs/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -3874,8 +3874,24 @@ func (c *Constraint) Validate() error {
mErr.Errors = append(mErr.Errors, errors.New("Missing constraint operand"))
}

// requireLtarget specifies whether the constraint requires an LTarget to be
// provided.
requireLtarget := true

// Perform additional validation based on operand
switch c.Operand {
case ConstraintDistinctHosts:
requireLtarget = false
if c.RTarget != "" {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Distinct hosts constraint doesn't allow RTarget. Got %q", c.RTarget))
}
if c.LTarget != "" {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Distinct hosts constraint doesn't allow LTarget. Got %q", c.LTarget))
}
case ConstraintSetContains:
if c.RTarget == "" {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Set contains constraint requires an RTarget"))
}
case ConstraintRegex:
if _, err := regexp.Compile(c.RTarget); err != nil {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Regular expression failed to compile: %v", err))
Expand All @@ -3884,7 +3900,29 @@ func (c *Constraint) Validate() error {
if _, err := version.NewConstraint(c.RTarget); err != nil {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Version constraint is invalid: %v", err))
}
case ConstraintDistinctProperty:
// If a count is set, make sure it is convertible to a uint64
if c.RTarget != "" {
count, err := strconv.ParseUint(c.RTarget, 10, 64)
if err != nil {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Failed to convert RTarget %q to uint64: %v", c.RTarget, err))
} else if count < 1 {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Distinct Property must have an allowed count of 1 or greater: %d < 1", count))
}
}
case "=", "==", "is", "!=", "not", "<", "<=", ">", ">=":
if c.RTarget == "" {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Operator %q requires an RTarget", c.Operand))
}
default:
mErr.Errors = append(mErr.Errors, fmt.Errorf("Unknown constraint type %q", c.Operand))
}

// Ensure we have an LTarget for the constraints that need one
if requireLtarget && c.LTarget == "" {
mErr.Errors = append(mErr.Errors, fmt.Errorf("No LTarget provided but is required by constraint"))
}

return mErr.ErrorOrNil()
}

Expand Down
55 changes: 55 additions & 0 deletions nomad/structs/structs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1323,6 +1323,61 @@ func TestConstraint_Validate(t *testing.T) {
if !strings.Contains(mErr.Errors[0].Error(), "Malformed constraint") {
t.Fatalf("err: %s", err)
}

// Perform distinct_property validation
c.Operand = ConstraintDistinctProperty
c.RTarget = "0"
err = c.Validate()
mErr = err.(*multierror.Error)
if !strings.Contains(mErr.Errors[0].Error(), "count of 1 or greater") {
t.Fatalf("err: %s", err)
}

c.RTarget = "-1"
err = c.Validate()
mErr = err.(*multierror.Error)
if !strings.Contains(mErr.Errors[0].Error(), "to uint64") {
t.Fatalf("err: %s", err)
}

// Perform distinct_hosts validation
c.Operand = ConstraintDistinctHosts
c.RTarget = "foo"
err = c.Validate()
mErr = err.(*multierror.Error)
if !strings.Contains(mErr.Errors[0].Error(), "doesn't allow RTarget") {
t.Fatalf("err: %s", err)
}
if !strings.Contains(mErr.Errors[1].Error(), "doesn't allow LTarget") {
t.Fatalf("err: %s", err)
}

// Perform set_contains validation
c.Operand = ConstraintSetContains
c.RTarget = ""
err = c.Validate()
mErr = err.(*multierror.Error)
if !strings.Contains(mErr.Errors[0].Error(), "requires an RTarget") {
t.Fatalf("err: %s", err)
}

// Perform LTarget validation
c.Operand = ConstraintRegex
c.RTarget = "foo"
c.LTarget = ""
err = c.Validate()
mErr = err.(*multierror.Error)
if !strings.Contains(mErr.Errors[0].Error(), "No LTarget") {
t.Fatalf("err: %s", err)
}

// Perform constraint type validation
c.Operand = "foo"
err = c.Validate()
mErr = err.(*multierror.Error)
if !strings.Contains(mErr.Errors[0].Error(), "Unknown constraint type") {
t.Fatalf("err: %s", err)
}
}

func TestUpdateStrategy_Validate(t *testing.T) {
Expand Down
Loading

0 comments on commit 2e7d8ad

Please sign in to comment.