github.com/ncdc/docker@v0.10.1-0.20160129113957-6c6729ef5b74/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  // It returns true if the event matches and a map with
    32  // a set of key/value to identify the match.
    33  type eventMatcher func(text string) (map[string]string, bool)
    34  
    35  // eventMatchProcessor is a function to handle an event match.
    36  // It receives a map of key/value with the information extracted in a match.
    37  type eventMatchProcessor func(matches map[string]string)
    38  
    39  // eventObserver runs an events commands and observes its output.
    40  type eventObserver struct {
    41  	buffer             *bytes.Buffer
    42  	command            *exec.Cmd
    43  	scanner            *bufio.Scanner
    44  	startTime          string
    45  	disconnectionError error
    46  }
    47  
    48  // newEventObserver creates the observer and initializes the command
    49  // without running it. Users must call `eventObserver.Start` to start the command.
    50  func newEventObserver(c *check.C, args ...string) (*eventObserver, error) {
    51  	since := daemonTime(c).Unix()
    52  	return newEventObserverWithBacklog(c, since, args...)
    53  }
    54  
    55  // newEventObserverWithBacklog creates a new observer changing the start time of the backlog to return.
    56  func newEventObserverWithBacklog(c *check.C, since int64, args ...string) (*eventObserver, error) {
    57  	startTime := strconv.FormatInt(since, 10)
    58  	cmdArgs := []string{"events", "--since", startTime}
    59  	if len(args) > 0 {
    60  		cmdArgs = append(cmdArgs, args...)
    61  	}
    62  	eventsCmd := exec.Command(dockerBinary, cmdArgs...)
    63  	stdout, err := eventsCmd.StdoutPipe()
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	return &eventObserver{
    69  		buffer:    new(bytes.Buffer),
    70  		command:   eventsCmd,
    71  		scanner:   bufio.NewScanner(stdout),
    72  		startTime: startTime,
    73  	}, nil
    74  }
    75  
    76  // Start starts the events command.
    77  func (e *eventObserver) Start() error {
    78  	return e.command.Start()
    79  }
    80  
    81  // Stop stops the events command.
    82  func (e *eventObserver) Stop() {
    83  	e.command.Process.Kill()
    84  	e.command.Process.Release()
    85  }
    86  
    87  // Match tries to match the events output with a given matcher.
    88  func (e *eventObserver) Match(match eventMatcher, process eventMatchProcessor) {
    89  	for e.scanner.Scan() {
    90  		text := e.scanner.Text()
    91  		e.buffer.WriteString(text)
    92  		e.buffer.WriteString("\n")
    93  
    94  		if matches, ok := match(text); ok {
    95  			process(matches)
    96  		}
    97  	}
    98  
    99  	err := e.scanner.Err()
   100  	if err == nil {
   101  		err = io.EOF
   102  	}
   103  
   104  	logrus.Debug("EventObserver scanner loop finished: %v", err)
   105  	e.disconnectionError = err
   106  }
   107  
   108  func (e *eventObserver) CheckEventError(c *check.C, id, event string, match eventMatcher) {
   109  	var foundEvent bool
   110  	scannerOut := e.buffer.String()
   111  
   112  	if e.disconnectionError != nil {
   113  		until := strconv.FormatInt(daemonTime(c).Unix(), 10)
   114  		out, _ := dockerCmd(c, "events", "--since", e.startTime, "--until", until)
   115  		events := strings.Split(strings.TrimSpace(out), "\n")
   116  		for _, e := range events {
   117  			if _, ok := match(e); ok {
   118  				foundEvent = true
   119  				break
   120  			}
   121  		}
   122  		scannerOut = out
   123  	}
   124  	if !foundEvent {
   125  		c.Fatalf("failed to observe event `%s` for %s. Disconnection error: %v\nout:\n%v", event, id, e.disconnectionError, scannerOut)
   126  	}
   127  }
   128  
   129  // matchEventLine matches a text with the event regular expression.
   130  // It returns the matches and true if the regular expression matches with the given id and event type.
   131  // It returns an empty map and false if there is no match.
   132  func matchEventLine(id, eventType string, actions map[string]chan bool) eventMatcher {
   133  	return func(text string) (map[string]string, bool) {
   134  		matches := parseEventText(text)
   135  		if len(matches) == 0 {
   136  			return matches, false
   137  		}
   138  
   139  		if matchIDAndEventType(matches, id, eventType) {
   140  			if _, ok := actions[matches["action"]]; ok {
   141  				return matches, true
   142  			}
   143  		}
   144  		return matches, false
   145  	}
   146  }
   147  
   148  // processEventMatch closes an action channel when an event line matches the expected action.
   149  func processEventMatch(actions map[string]chan bool) eventMatchProcessor {
   150  	return func(matches map[string]string) {
   151  		if ch, ok := actions[matches["action"]]; ok {
   152  			close(ch)
   153  		}
   154  	}
   155  }
   156  
   157  // parseEventText parses a line of events coming from the cli and returns
   158  // the matchers in a map.
   159  func parseEventText(text string) map[string]string {
   160  	matches := eventCliRegexp.FindAllStringSubmatch(text, -1)
   161  	md := map[string]string{}
   162  	if len(matches) == 0 {
   163  		return md
   164  	}
   165  
   166  	names := eventCliRegexp.SubexpNames()
   167  	for i, n := range matches[0] {
   168  		md[names[i]] = n
   169  	}
   170  	return md
   171  }
   172  
   173  // parseEventAction parses an event text and returns the action.
   174  // It fails if the text is not in the event format.
   175  func parseEventAction(c *check.C, text string) string {
   176  	matches := parseEventText(text)
   177  	return matches["action"]
   178  }
   179  
   180  // eventActionsByIDAndType returns the actions for a given id and type.
   181  // It fails if the text is not in the event format.
   182  func eventActionsByIDAndType(c *check.C, events []string, id, eventType string) []string {
   183  	var filtered []string
   184  	for _, event := range events {
   185  		matches := parseEventText(event)
   186  		c.Assert(matches, checker.Not(checker.IsNil))
   187  		if matchIDAndEventType(matches, id, eventType) {
   188  			filtered = append(filtered, matches["action"])
   189  		}
   190  	}
   191  	return filtered
   192  }
   193  
   194  // matchIDAndEventType returns true if an event matches a given id and type.
   195  // It also resolves names in the event attributes if the id doesn't match.
   196  func matchIDAndEventType(matches map[string]string, id, eventType string) bool {
   197  	return matchEventID(matches, id) && matches["eventType"] == eventType
   198  }
   199  
   200  func matchEventID(matches map[string]string, id string) bool {
   201  	matchID := matches["id"] == id || strings.HasPrefix(matches["id"], id)
   202  	if !matchID && matches["attributes"] != "" {
   203  		// try matching a name in the attributes
   204  		attributes := map[string]string{}
   205  		for _, a := range strings.Split(matches["attributes"], ", ") {
   206  			kv := strings.Split(a, "=")
   207  			attributes[kv[0]] = kv[1]
   208  		}
   209  		matchID = attributes["name"] == id
   210  	}
   211  	return matchID
   212  }
   213  
   214  func parseEvents(c *check.C, out, match string) {
   215  	events := strings.Split(strings.TrimSpace(out), "\n")
   216  	for _, event := range events {
   217  		matches := parseEventText(event)
   218  		matched, err := regexp.MatchString(match, matches["action"])
   219  		c.Assert(err, checker.IsNil)
   220  		c.Assert(matched, checker.True, check.Commentf("Matcher: %s did not match %s", match, matches["action"]))
   221  	}
   222  }
   223  
   224  func parseEventsWithID(c *check.C, out, match, id string) {
   225  	events := strings.Split(strings.TrimSpace(out), "\n")
   226  	for _, event := range events {
   227  		matches := parseEventText(event)
   228  		c.Assert(matchEventID(matches, id), checker.True)
   229  
   230  		matched, err := regexp.MatchString(match, matches["action"])
   231  		c.Assert(err, checker.IsNil)
   232  		c.Assert(matched, checker.True, check.Commentf("Matcher: %s did not match %s", match, matches["action"]))
   233  	}
   234  }