github.com/duglin/docker@v1.13.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  	eventstestutils "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  }