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