mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
![Yong Tang](/assets/img/avatar_default.png)
This fix updates SwarmKit to ed384f3b3957f65e3111bd020f9815f3d4296fa2. Notable changes since last update (3ca4775ba4a5519e2225c3337c7db8901ec39d26): 1. Fix duplicated ports allocation with restarted swarm. (Docker issue #29247) 2. Topology-aware scheduling (Docker PR #30725) Docker issue #29247 was labeled 1.13.1, though it is advised that related SwarmKit changes only to be merged to master (based on the feedback https://github.com/docker/swarmkit/pull/1802#issuecomment-274143500) This fix fixes #29247 (master only). This fix is related to #30725. Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
207 lines
5.4 KiB
Go
207 lines
5.4 KiB
Go
package constraint
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/docker/swarmkit/api"
|
|
)
|
|
|
|
const (
|
|
eq = iota
|
|
noteq
|
|
|
|
// NodeLabelPrefix is the constraint key prefix for node labels.
|
|
NodeLabelPrefix = "node.labels."
|
|
// EngineLabelPrefix is the constraint key prefix for engine labels.
|
|
EngineLabelPrefix = "engine.labels."
|
|
)
|
|
|
|
var (
|
|
alphaNumeric = regexp.MustCompile(`^(?i)[a-z_][a-z0-9\-_.]+$`)
|
|
// value can be alphanumeric and some special characters. it shouldn't container
|
|
// current or future operators like '>, <, ~', etc.
|
|
valuePattern = regexp.MustCompile(`^(?i)[a-z0-9:\-_\s\.\*\(\)\?\+\[\]\\\^\$\|\/]+$`)
|
|
|
|
// operators defines list of accepted operators
|
|
operators = []string{"==", "!="}
|
|
)
|
|
|
|
// Constraint defines a constraint.
|
|
type Constraint struct {
|
|
key string
|
|
operator int
|
|
exp string
|
|
}
|
|
|
|
// Parse parses list of constraints.
|
|
func Parse(env []string) ([]Constraint, error) {
|
|
exprs := []Constraint{}
|
|
for _, e := range env {
|
|
found := false
|
|
// each expr is in the form of "key op value"
|
|
for i, op := range operators {
|
|
if !strings.Contains(e, op) {
|
|
continue
|
|
}
|
|
// split with the op
|
|
parts := strings.SplitN(e, op, 2)
|
|
|
|
if len(parts) < 2 {
|
|
return nil, fmt.Errorf("invalid expr: %s", e)
|
|
}
|
|
|
|
part0 := strings.TrimSpace(parts[0])
|
|
// validate key
|
|
matched := alphaNumeric.MatchString(part0)
|
|
if matched == false {
|
|
return nil, fmt.Errorf("key '%s' is invalid", part0)
|
|
}
|
|
|
|
part1 := strings.TrimSpace(parts[1])
|
|
|
|
// validate Value
|
|
matched = valuePattern.MatchString(part1)
|
|
if matched == false {
|
|
return nil, fmt.Errorf("value '%s' is invalid", part1)
|
|
}
|
|
// TODO(dongluochen): revisit requirements to see if globing or regex are useful
|
|
exprs = append(exprs, Constraint{key: part0, operator: i, exp: part1})
|
|
|
|
found = true
|
|
break // found an op, move to next entry
|
|
}
|
|
if !found {
|
|
return nil, fmt.Errorf("constraint expected one operator from %s", strings.Join(operators, ", "))
|
|
}
|
|
}
|
|
return exprs, nil
|
|
}
|
|
|
|
// Match checks if the Constraint matches the target strings.
|
|
func (c *Constraint) Match(whats ...string) bool {
|
|
var match bool
|
|
|
|
// full string match
|
|
for _, what := range whats {
|
|
// case insensitive compare
|
|
if strings.EqualFold(c.exp, what) {
|
|
match = true
|
|
break
|
|
}
|
|
}
|
|
|
|
switch c.operator {
|
|
case eq:
|
|
return match
|
|
case noteq:
|
|
return !match
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// NodeMatches returns true if the node satisfies the given constraints.
|
|
func NodeMatches(constraints []Constraint, n *api.Node) bool {
|
|
for _, constraint := range constraints {
|
|
switch {
|
|
case strings.EqualFold(constraint.key, "node.id"):
|
|
if !constraint.Match(n.ID) {
|
|
return false
|
|
}
|
|
case strings.EqualFold(constraint.key, "node.hostname"):
|
|
// if this node doesn't have hostname
|
|
// it's equivalent to match an empty hostname
|
|
// where '==' would fail, '!=' matches
|
|
if n.Description == nil {
|
|
if !constraint.Match("") {
|
|
return false
|
|
}
|
|
continue
|
|
}
|
|
if !constraint.Match(n.Description.Hostname) {
|
|
return false
|
|
}
|
|
case strings.EqualFold(constraint.key, "node.ip"):
|
|
nodeIP := net.ParseIP(n.Status.Addr)
|
|
// single IP address, node.ip == 2001:db8::2
|
|
if ip := net.ParseIP(constraint.exp); ip != nil {
|
|
ipEq := ip.Equal(nodeIP)
|
|
if (ipEq && constraint.operator != eq) || (!ipEq && constraint.operator == eq) {
|
|
return false
|
|
}
|
|
continue
|
|
}
|
|
// CIDR subnet, node.ip != 210.8.4.0/24
|
|
if _, subnet, err := net.ParseCIDR(constraint.exp); err == nil {
|
|
within := subnet.Contains(nodeIP)
|
|
if (within && constraint.operator != eq) || (!within && constraint.operator == eq) {
|
|
return false
|
|
}
|
|
continue
|
|
}
|
|
// reject constraint with malformed address/network
|
|
return false
|
|
case strings.EqualFold(constraint.key, "node.role"):
|
|
if !constraint.Match(n.Role.String()) {
|
|
return false
|
|
}
|
|
case strings.EqualFold(constraint.key, "node.platform.os"):
|
|
if n.Description == nil || n.Description.Platform == nil {
|
|
if !constraint.Match("") {
|
|
return false
|
|
}
|
|
continue
|
|
}
|
|
if !constraint.Match(n.Description.Platform.OS) {
|
|
return false
|
|
}
|
|
case strings.EqualFold(constraint.key, "node.platform.arch"):
|
|
if n.Description == nil || n.Description.Platform == nil {
|
|
if !constraint.Match("") {
|
|
return false
|
|
}
|
|
continue
|
|
}
|
|
if !constraint.Match(n.Description.Platform.Architecture) {
|
|
return false
|
|
}
|
|
|
|
// node labels constraint in form like 'node.labels.key==value'
|
|
case len(constraint.key) > len(NodeLabelPrefix) && strings.EqualFold(constraint.key[:len(NodeLabelPrefix)], NodeLabelPrefix):
|
|
if n.Spec.Annotations.Labels == nil {
|
|
if !constraint.Match("") {
|
|
return false
|
|
}
|
|
continue
|
|
}
|
|
label := constraint.key[len(NodeLabelPrefix):]
|
|
// label itself is case sensitive
|
|
val := n.Spec.Annotations.Labels[label]
|
|
if !constraint.Match(val) {
|
|
return false
|
|
}
|
|
|
|
// engine labels constraint in form like 'engine.labels.key!=value'
|
|
case len(constraint.key) > len(EngineLabelPrefix) && strings.EqualFold(constraint.key[:len(EngineLabelPrefix)], EngineLabelPrefix):
|
|
if n.Description == nil || n.Description.Engine == nil || n.Description.Engine.Labels == nil {
|
|
if !constraint.Match("") {
|
|
return false
|
|
}
|
|
continue
|
|
}
|
|
label := constraint.key[len(EngineLabelPrefix):]
|
|
val := n.Description.Engine.Labels[label]
|
|
if !constraint.Match(val) {
|
|
return false
|
|
}
|
|
default:
|
|
// key doesn't match predefined syntax
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|