constraint.go 5.4 KB

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