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