constraint.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. package constraint
  2. import (
  3. "fmt"
  4. "net"
  5. "regexp"
  6. "strings"
  7. "github.com/docker/swarmkit/api"
  8. )
  9. const (
  10. eq = iota
  11. noteq
  12. nodeLabelPrefix = "node.labels."
  13. engineLabelPrefix = "engine.labels."
  14. )
  15. var (
  16. alphaNumeric = regexp.MustCompile(`^(?i)[a-z_][a-z0-9\-_.]+$`)
  17. // value can be alphanumeric and some special characters. it shouldn't container
  18. // current or future operators like '>, <, ~', etc.
  19. valuePattern = regexp.MustCompile(`^(?i)[a-z0-9:\-_\s\.\*\(\)\?\+\[\]\\\^\$\|\/]+$`)
  20. // operators defines list of accepted operators
  21. operators = []string{"==", "!="}
  22. )
  23. // Constraint defines a constraint.
  24. type Constraint struct {
  25. key string
  26. operator int
  27. exp string
  28. }
  29. // Parse parses list of constraints.
  30. func Parse(env []string) ([]Constraint, error) {
  31. exprs := []Constraint{}
  32. for _, e := range env {
  33. found := false
  34. // each expr is in the form of "key op value"
  35. for i, op := range operators {
  36. if !strings.Contains(e, op) {
  37. continue
  38. }
  39. // split with the op
  40. parts := strings.SplitN(e, op, 2)
  41. if len(parts) < 2 {
  42. return nil, fmt.Errorf("invalid expr: %s", e)
  43. }
  44. part0 := strings.TrimSpace(parts[0])
  45. // validate key
  46. matched := alphaNumeric.MatchString(part0)
  47. if matched == false {
  48. return nil, fmt.Errorf("key '%s' is invalid", part0)
  49. }
  50. part1 := strings.TrimSpace(parts[1])
  51. // validate Value
  52. matched = valuePattern.MatchString(part1)
  53. if matched == false {
  54. return nil, fmt.Errorf("value '%s' is invalid", part1)
  55. }
  56. // TODO(dongluochen): revisit requirements to see if globing or regex are useful
  57. exprs = append(exprs, Constraint{key: part0, operator: i, exp: part1})
  58. found = true
  59. break // found an op, move to next entry
  60. }
  61. if !found {
  62. return nil, fmt.Errorf("constraint expected one operator from %s", strings.Join(operators, ", "))
  63. }
  64. }
  65. return exprs, nil
  66. }
  67. // Match checks if the Constraint matches the target strings.
  68. func (c *Constraint) Match(whats ...string) bool {
  69. var match bool
  70. // full string match
  71. for _, what := range whats {
  72. // case insensitive compare
  73. if strings.EqualFold(c.exp, what) {
  74. match = true
  75. break
  76. }
  77. }
  78. switch c.operator {
  79. case eq:
  80. return match
  81. case noteq:
  82. return !match
  83. }
  84. return false
  85. }
  86. // NodeMatches returns true if the node satisfies the given constraints.
  87. func NodeMatches(constraints []Constraint, n *api.Node) bool {
  88. for _, constraint := range constraints {
  89. switch {
  90. case strings.EqualFold(constraint.key, "node.id"):
  91. if !constraint.Match(n.ID) {
  92. return false
  93. }
  94. case strings.EqualFold(constraint.key, "node.hostname"):
  95. // if this node doesn't have hostname
  96. // it's equivalent to match an empty hostname
  97. // where '==' would fail, '!=' matches
  98. if n.Description == nil {
  99. if !constraint.Match("") {
  100. return false
  101. }
  102. continue
  103. }
  104. if !constraint.Match(n.Description.Hostname) {
  105. return false
  106. }
  107. case strings.EqualFold(constraint.key, "node.ip"):
  108. nodeIP := net.ParseIP(n.Status.Addr)
  109. // single IP address, node.ip == 2001:db8::2
  110. if ip := net.ParseIP(constraint.exp); ip != nil {
  111. ipEq := ip.Equal(nodeIP)
  112. if (ipEq && constraint.operator != eq) || (!ipEq && constraint.operator == eq) {
  113. return false
  114. }
  115. continue
  116. }
  117. // CIDR subnet, node.ip != 210.8.4.0/24
  118. if _, subnet, err := net.ParseCIDR(constraint.exp); err == nil {
  119. within := subnet.Contains(nodeIP)
  120. if (within && constraint.operator != eq) || (!within && constraint.operator == eq) {
  121. return false
  122. }
  123. continue
  124. }
  125. // reject constraint with malformed address/network
  126. return false
  127. case strings.EqualFold(constraint.key, "node.role"):
  128. if !constraint.Match(n.Role.String()) {
  129. return false
  130. }
  131. case strings.EqualFold(constraint.key, "node.platform.os"):
  132. if n.Description == nil || n.Description.Platform == nil {
  133. if !constraint.Match("") {
  134. return false
  135. }
  136. continue
  137. }
  138. if !constraint.Match(n.Description.Platform.OS) {
  139. return false
  140. }
  141. case strings.EqualFold(constraint.key, "node.platform.arch"):
  142. if n.Description == nil || n.Description.Platform == nil {
  143. if !constraint.Match("") {
  144. return false
  145. }
  146. continue
  147. }
  148. if !constraint.Match(n.Description.Platform.Architecture) {
  149. return false
  150. }
  151. // node labels constraint in form like 'node.labels.key==value'
  152. case len(constraint.key) > len(nodeLabelPrefix) && strings.EqualFold(constraint.key[:len(nodeLabelPrefix)], nodeLabelPrefix):
  153. if n.Spec.Annotations.Labels == nil {
  154. if !constraint.Match("") {
  155. return false
  156. }
  157. continue
  158. }
  159. label := constraint.key[len(nodeLabelPrefix):]
  160. // label itself is case sensitive
  161. val := n.Spec.Annotations.Labels[label]
  162. if !constraint.Match(val) {
  163. return false
  164. }
  165. // engine labels constraint in form like 'engine.labels.key!=value'
  166. case len(constraint.key) > len(engineLabelPrefix) && strings.EqualFold(constraint.key[:len(engineLabelPrefix)], engineLabelPrefix):
  167. if n.Description == nil || n.Description.Engine == nil || n.Description.Engine.Labels == nil {
  168. if !constraint.Match("") {
  169. return false
  170. }
  171. continue
  172. }
  173. label := constraint.key[len(engineLabelPrefix):]
  174. val := n.Description.Engine.Labels[label]
  175. if !constraint.Match(val) {
  176. return false
  177. }
  178. default:
  179. // key doesn't match predefined syntax
  180. return false
  181. }
  182. }
  183. return true
  184. }