events_utils_test.go 5.9 KB

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