events_utils.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. package main
  2. import (
  3. "bufio"
  4. "bytes"
  5. "fmt"
  6. "io"
  7. "os/exec"
  8. "regexp"
  9. "strconv"
  10. "strings"
  11. "github.com/Sirupsen/logrus"
  12. "github.com/docker/docker/pkg/integration/checker"
  13. "github.com/go-check/check"
  14. )
  15. var (
  16. reTimestamp = `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{9}(:?(:?(:?-|\+)\d{2}:\d{2})|Z)`
  17. reEventType = `(?P<eventType>\w+)`
  18. reAction = `(?P<action>\w+)`
  19. reID = `(?P<id>[^\s]+)`
  20. reAttributes = `(\s\((?P<attributes>[^\)]+)\))?`
  21. reString = fmt.Sprintf(`\A%s\s%s\s%s\s%s%s\z`, reTimestamp, reEventType, reAction, reID, reAttributes)
  22. // eventCliRegexp is a regular expression that matches all possible event outputs in the cli
  23. eventCliRegexp = regexp.MustCompile(reString)
  24. )
  25. // eventMatcher is a function that tries to match an event input.
  26. // It returns true if the event matches and a map with
  27. // a set of key/value to identify the match.
  28. type eventMatcher func(text string) (map[string]string, bool)
  29. // eventMatchProcessor is a function to handle an event match.
  30. // It receives a map of key/value with the information extracted in a match.
  31. type eventMatchProcessor func(matches map[string]string)
  32. // eventObserver runs an events commands and observes its output.
  33. type eventObserver struct {
  34. buffer *bytes.Buffer
  35. command *exec.Cmd
  36. scanner *bufio.Scanner
  37. startTime string
  38. disconnectionError error
  39. }
  40. // newEventObserver creates the observer and initializes the command
  41. // without running it. Users must call `eventObserver.Start` to start the command.
  42. func newEventObserver(c *check.C, args ...string) (*eventObserver, error) {
  43. since := daemonTime(c).Unix()
  44. return newEventObserverWithBacklog(c, since, args...)
  45. }
  46. // newEventObserverWithBacklog creates a new observer changing the start time of the backlog to return.
  47. func newEventObserverWithBacklog(c *check.C, since int64, args ...string) (*eventObserver, error) {
  48. startTime := strconv.FormatInt(since, 10)
  49. cmdArgs := []string{"events", "--since", startTime}
  50. if len(args) > 0 {
  51. cmdArgs = append(cmdArgs, args...)
  52. }
  53. eventsCmd := exec.Command(dockerBinary, cmdArgs...)
  54. stdout, err := eventsCmd.StdoutPipe()
  55. if err != nil {
  56. return nil, err
  57. }
  58. return &eventObserver{
  59. buffer: new(bytes.Buffer),
  60. command: eventsCmd,
  61. scanner: bufio.NewScanner(stdout),
  62. startTime: startTime,
  63. }, nil
  64. }
  65. // Start starts the events command.
  66. func (e *eventObserver) Start() error {
  67. return e.command.Start()
  68. }
  69. // Stop stops the events command.
  70. func (e *eventObserver) Stop() {
  71. e.command.Process.Kill()
  72. e.command.Process.Release()
  73. }
  74. // Match tries to match the events output with a given matcher.
  75. func (e *eventObserver) Match(match eventMatcher, process eventMatchProcessor) {
  76. for e.scanner.Scan() {
  77. text := e.scanner.Text()
  78. e.buffer.WriteString(text)
  79. e.buffer.WriteString("\n")
  80. if matches, ok := match(text); ok {
  81. process(matches)
  82. }
  83. }
  84. err := e.scanner.Err()
  85. if err == nil {
  86. err = io.EOF
  87. }
  88. logrus.Debug("EventObserver scanner loop finished: %v", err)
  89. e.disconnectionError = err
  90. }
  91. func (e *eventObserver) CheckEventError(c *check.C, id, event string, match eventMatcher) {
  92. var foundEvent bool
  93. scannerOut := e.buffer.String()
  94. if e.disconnectionError != nil {
  95. until := strconv.FormatInt(daemonTime(c).Unix(), 10)
  96. out, _ := dockerCmd(c, "events", "--since", e.startTime, "--until", until)
  97. events := strings.Split(strings.TrimSpace(out), "\n")
  98. for _, e := range events {
  99. if _, ok := match(e); ok {
  100. foundEvent = true
  101. break
  102. }
  103. }
  104. scannerOut = out
  105. }
  106. if !foundEvent {
  107. c.Fatalf("failed to observe event `%s` for %s. Disconnection error: %v\nout:\n%v", event, id, e.disconnectionError, scannerOut)
  108. }
  109. }
  110. // matchEventLine matches a text with the event regular expression.
  111. // It returns the matches and true if the regular expression matches with the given id and event type.
  112. // It returns an empty map and false if there is no match.
  113. func matchEventLine(id, eventType string, actions map[string]chan bool) eventMatcher {
  114. return func(text string) (map[string]string, bool) {
  115. matches := parseEventText(text)
  116. if len(matches) == 0 {
  117. return matches, false
  118. }
  119. if matchIDAndEventType(matches, id, eventType) {
  120. if _, ok := actions[matches["action"]]; ok {
  121. return matches, true
  122. }
  123. }
  124. return matches, false
  125. }
  126. }
  127. // processEventMatch closes an action channel when an event line matches the expected action.
  128. func processEventMatch(actions map[string]chan bool) eventMatchProcessor {
  129. return func(matches map[string]string) {
  130. if ch, ok := actions[matches["action"]]; ok {
  131. close(ch)
  132. }
  133. }
  134. }
  135. // parseEventText parses a line of events coming from the cli and returns
  136. // the matchers in a map.
  137. func parseEventText(text string) map[string]string {
  138. matches := eventCliRegexp.FindAllStringSubmatch(text, -1)
  139. md := map[string]string{}
  140. if len(matches) == 0 {
  141. return md
  142. }
  143. names := eventCliRegexp.SubexpNames()
  144. for i, n := range matches[0] {
  145. md[names[i]] = n
  146. }
  147. return md
  148. }
  149. // parseEventAction parses an event text and returns the action.
  150. // It fails if the text is not in the event format.
  151. func parseEventAction(c *check.C, text string) string {
  152. matches := parseEventText(text)
  153. return matches["action"]
  154. }
  155. // eventActionsByIDAndType returns the actions for a given id and type.
  156. // It fails if the text is not in the event format.
  157. func eventActionsByIDAndType(c *check.C, events []string, id, eventType string) []string {
  158. var filtered []string
  159. for _, event := range events {
  160. matches := parseEventText(event)
  161. c.Assert(matches, checker.Not(checker.IsNil))
  162. if matchIDAndEventType(matches, id, eventType) {
  163. filtered = append(filtered, matches["action"])
  164. }
  165. }
  166. return filtered
  167. }
  168. // matchIDAndEventType returns true if an event matches a given id and type.
  169. // It also resolves names in the event attributes if the id doesn't match.
  170. func matchIDAndEventType(matches map[string]string, id, eventType string) bool {
  171. return matchEventID(matches, id) && matches["eventType"] == eventType
  172. }
  173. func matchEventID(matches map[string]string, id string) bool {
  174. matchID := matches["id"] == id || strings.HasPrefix(matches["id"], id)
  175. if !matchID && matches["attributes"] != "" {
  176. // try matching a name in the attributes
  177. attributes := map[string]string{}
  178. for _, a := range strings.Split(matches["attributes"], ", ") {
  179. kv := strings.Split(a, "=")
  180. attributes[kv[0]] = kv[1]
  181. }
  182. matchID = attributes["name"] == id
  183. }
  184. return matchID
  185. }
  186. func parseEvents(c *check.C, out, match string) {
  187. events := strings.Split(strings.TrimSpace(out), "\n")
  188. for _, event := range events {
  189. matches := parseEventText(event)
  190. matched, err := regexp.MatchString(match, matches["action"])
  191. c.Assert(err, checker.IsNil)
  192. c.Assert(matched, checker.True, check.Commentf("Matcher: %s did not match %s", match, matches["action"]))
  193. }
  194. }
  195. func parseEventsWithID(c *check.C, out, match, id string) {
  196. events := strings.Split(strings.TrimSpace(out), "\n")
  197. for _, event := range events {
  198. matches := parseEventText(event)
  199. c.Assert(matchEventID(matches, id), checker.True)
  200. matched, err := regexp.MatchString(match, matches["action"])
  201. c.Assert(err, checker.IsNil)
  202. c.Assert(matched, checker.True, check.Commentf("Matcher: %s did not match %s", match, matches["action"]))
  203. }
  204. }