events_utils_test.go 5.8 KB

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