events_utils_test.go 5.8 KB

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