Initial commit
This commit is contained in:
234
vendor/github.com/hyperhq/hypercli/integration-cli/events_utils.go
generated
vendored
Normal file
234
vendor/github.com/hyperhq/hypercli/integration-cli/events_utils.go
generated
vendored
Normal file
@@ -0,0 +1,234 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/integration/checker"
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
var (
|
||||
reTimestamp = `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{9}(:?(:?(:?-|\+)\d{2}:\d{2})|Z)`
|
||||
reEventType = `(?P<eventType>\w+)`
|
||||
reAction = `(?P<action>\w+)`
|
||||
reID = `(?P<id>[^\s]+)`
|
||||
reAttributes = `(\s\((?P<attributes>[^\)]+)\))?`
|
||||
reString = fmt.Sprintf(`\A%s\s%s\s%s\s%s%s\z`, reTimestamp, reEventType, reAction, reID, reAttributes)
|
||||
|
||||
// eventCliRegexp is a regular expression that matches all possible event outputs in the cli
|
||||
eventCliRegexp = regexp.MustCompile(reString)
|
||||
)
|
||||
|
||||
// eventMatcher is a function that tries to match an event input.
|
||||
// It returns true if the event matches and a map with
|
||||
// a set of key/value to identify the match.
|
||||
type eventMatcher func(text string) (map[string]string, bool)
|
||||
|
||||
// eventMatchProcessor is a function to handle an event match.
|
||||
// It receives a map of key/value with the information extracted in a match.
|
||||
type eventMatchProcessor func(matches map[string]string)
|
||||
|
||||
// eventObserver runs an events commands and observes its output.
|
||||
type eventObserver struct {
|
||||
buffer *bytes.Buffer
|
||||
command *exec.Cmd
|
||||
scanner *bufio.Scanner
|
||||
startTime string
|
||||
disconnectionError error
|
||||
}
|
||||
|
||||
// newEventObserver creates the observer and initializes the command
|
||||
// without running it. Users must call `eventObserver.Start` to start the command.
|
||||
func newEventObserver(c *check.C, args ...string) (*eventObserver, error) {
|
||||
since := daemonTime(c).Unix()
|
||||
return newEventObserverWithBacklog(c, since, args...)
|
||||
}
|
||||
|
||||
// newEventObserverWithBacklog creates a new observer changing the start time of the backlog to return.
|
||||
func newEventObserverWithBacklog(c *check.C, since int64, args ...string) (*eventObserver, error) {
|
||||
startTime := strconv.FormatInt(since, 10)
|
||||
cmdArgs := []string{"events", "--since", startTime}
|
||||
if len(args) > 0 {
|
||||
cmdArgs = append(cmdArgs, args...)
|
||||
}
|
||||
eventsCmd := exec.Command(dockerBinary, cmdArgs...)
|
||||
stdout, err := eventsCmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &eventObserver{
|
||||
buffer: new(bytes.Buffer),
|
||||
command: eventsCmd,
|
||||
scanner: bufio.NewScanner(stdout),
|
||||
startTime: startTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start starts the events command.
|
||||
func (e *eventObserver) Start() error {
|
||||
return e.command.Start()
|
||||
}
|
||||
|
||||
// Stop stops the events command.
|
||||
func (e *eventObserver) Stop() {
|
||||
e.command.Process.Kill()
|
||||
e.command.Process.Release()
|
||||
}
|
||||
|
||||
// Match tries to match the events output with a given matcher.
|
||||
func (e *eventObserver) Match(match eventMatcher, process eventMatchProcessor) {
|
||||
for e.scanner.Scan() {
|
||||
text := e.scanner.Text()
|
||||
e.buffer.WriteString(text)
|
||||
e.buffer.WriteString("\n")
|
||||
|
||||
if matches, ok := match(text); ok {
|
||||
process(matches)
|
||||
}
|
||||
}
|
||||
|
||||
err := e.scanner.Err()
|
||||
if err == nil {
|
||||
err = io.EOF
|
||||
}
|
||||
|
||||
logrus.Debug("EventObserver scanner loop finished: %v", err)
|
||||
e.disconnectionError = err
|
||||
}
|
||||
|
||||
func (e *eventObserver) CheckEventError(c *check.C, id, event string, match eventMatcher) {
|
||||
var foundEvent bool
|
||||
scannerOut := e.buffer.String()
|
||||
|
||||
if e.disconnectionError != nil {
|
||||
until := strconv.FormatInt(daemonTime(c).Unix(), 10)
|
||||
out, _ := dockerCmd(c, "events", "--since", e.startTime, "--until", until)
|
||||
events := strings.Split(strings.TrimSpace(out), "\n")
|
||||
for _, e := range events {
|
||||
if _, ok := match(e); ok {
|
||||
foundEvent = true
|
||||
break
|
||||
}
|
||||
}
|
||||
scannerOut = out
|
||||
}
|
||||
if !foundEvent {
|
||||
c.Fatalf("failed to observe event `%s` for %s. Disconnection error: %v\nout:\n%v", event, id, e.disconnectionError, scannerOut)
|
||||
}
|
||||
}
|
||||
|
||||
// matchEventLine matches a text with the event regular expression.
|
||||
// It returns the matches and true if the regular expression matches with the given id and event type.
|
||||
// It returns an empty map and false if there is no match.
|
||||
func matchEventLine(id, eventType string, actions map[string]chan bool) eventMatcher {
|
||||
return func(text string) (map[string]string, bool) {
|
||||
matches := parseEventText(text)
|
||||
if len(matches) == 0 {
|
||||
return matches, false
|
||||
}
|
||||
|
||||
if matchIDAndEventType(matches, id, eventType) {
|
||||
if _, ok := actions[matches["action"]]; ok {
|
||||
return matches, true
|
||||
}
|
||||
}
|
||||
return matches, false
|
||||
}
|
||||
}
|
||||
|
||||
// processEventMatch closes an action channel when an event line matches the expected action.
|
||||
func processEventMatch(actions map[string]chan bool) eventMatchProcessor {
|
||||
return func(matches map[string]string) {
|
||||
if ch, ok := actions[matches["action"]]; ok {
|
||||
close(ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseEventText parses a line of events coming from the cli and returns
|
||||
// the matchers in a map.
|
||||
func parseEventText(text string) map[string]string {
|
||||
matches := eventCliRegexp.FindAllStringSubmatch(text, -1)
|
||||
md := map[string]string{}
|
||||
if len(matches) == 0 {
|
||||
return md
|
||||
}
|
||||
|
||||
names := eventCliRegexp.SubexpNames()
|
||||
for i, n := range matches[0] {
|
||||
md[names[i]] = n
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// parseEventAction parses an event text and returns the action.
|
||||
// It fails if the text is not in the event format.
|
||||
func parseEventAction(c *check.C, text string) string {
|
||||
matches := parseEventText(text)
|
||||
return matches["action"]
|
||||
}
|
||||
|
||||
// eventActionsByIDAndType returns the actions for a given id and type.
|
||||
// It fails if the text is not in the event format.
|
||||
func eventActionsByIDAndType(c *check.C, events []string, id, eventType string) []string {
|
||||
var filtered []string
|
||||
for _, event := range events {
|
||||
matches := parseEventText(event)
|
||||
c.Assert(matches, checker.Not(checker.IsNil))
|
||||
if matchIDAndEventType(matches, id, eventType) {
|
||||
filtered = append(filtered, matches["action"])
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// matchIDAndEventType returns true if an event matches a given id and type.
|
||||
// It also resolves names in the event attributes if the id doesn't match.
|
||||
func matchIDAndEventType(matches map[string]string, id, eventType string) bool {
|
||||
return matchEventID(matches, id) && matches["eventType"] == eventType
|
||||
}
|
||||
|
||||
func matchEventID(matches map[string]string, id string) bool {
|
||||
matchID := matches["id"] == id || strings.HasPrefix(matches["id"], id)
|
||||
if !matchID && matches["attributes"] != "" {
|
||||
// try matching a name in the attributes
|
||||
attributes := map[string]string{}
|
||||
for _, a := range strings.Split(matches["attributes"], ", ") {
|
||||
kv := strings.Split(a, "=")
|
||||
attributes[kv[0]] = kv[1]
|
||||
}
|
||||
matchID = attributes["name"] == id
|
||||
}
|
||||
return matchID
|
||||
}
|
||||
|
||||
func parseEvents(c *check.C, out, match string) {
|
||||
events := strings.Split(strings.TrimSpace(out), "\n")
|
||||
for _, event := range events {
|
||||
matches := parseEventText(event)
|
||||
matched, err := regexp.MatchString(match, matches["action"])
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(matched, checker.True, check.Commentf("Matcher: %s did not match %s", match, matches["action"]))
|
||||
}
|
||||
}
|
||||
|
||||
func parseEventsWithID(c *check.C, out, match, id string) {
|
||||
events := strings.Split(strings.TrimSpace(out), "\n")
|
||||
for _, event := range events {
|
||||
matches := parseEventText(event)
|
||||
c.Assert(matchEventID(matches, id), checker.True)
|
||||
|
||||
matched, err := regexp.MatchString(match, matches["action"])
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(matched, checker.True, check.Commentf("Matcher: %s did not match %s", match, matches["action"]))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user