Merge pull request #8893 from vieux/filter_events

Events filtering (daemon side)
This commit is contained in:
Jessie Frazelle 2014-11-25 17:52:27 -08:00
commit 9160e01cef
6 changed files with 178 additions and 24 deletions

View File

@ -1782,6 +1782,10 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
cmd := cli.Subcmd("events", "", "Get real time events from the server")
since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
flFilter := opts.NewListOpts(nil)
cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'event=stop')")
if err := cmd.Parse(args); err != nil {
return nil
}
@ -1791,9 +1795,20 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
return nil
}
var (
v = url.Values{}
loc = time.FixedZone(time.Now().Zone())
v = url.Values{}
loc = time.FixedZone(time.Now().Zone())
eventFilterArgs = filters.Args{}
)
// Consolidate all filter flags, and sanity check them early.
// They'll get process in the daemon/server.
for _, f := range flFilter.GetAll() {
var err error
eventFilterArgs, err = filters.ParseFlag(f, eventFilterArgs)
if err != nil {
return err
}
}
var setTime = func(key, value string) {
format := timeutils.RFC3339NanoFixed
if len(value) < len(format) {
@ -1811,6 +1826,13 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
if *until != "" {
setTime("until", *until)
}
if len(eventFilterArgs) > 0 {
filterJson, err := filters.ToParam(eventFilterArgs)
if err != nil {
return err
}
v.Set("filters", filterJson)
}
if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil {
return err
}

View File

@ -315,6 +315,7 @@ func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWrite
streamJSON(job, w, true)
job.Setenv("since", r.Form.Get("since"))
job.Setenv("until", r.Form.Get("until"))
job.Setenv("filters", r.Form.Get("filters"))
return job.Run()
}

View File

@ -1388,6 +1388,7 @@ Query Parameters:
- **since** timestamp used for polling
- **until** timestamp used for polling
- **filters** a json encoded value of the filters (a map[string][]string) to process on the event list.
Status Codes:

View File

@ -614,7 +614,10 @@ For example:
Usage: docker events [OPTIONS]
Get real time events from the server
-f, --filter=[] Provide filter values. Valid filters:
event=<string> - event to filter
image=<string> - image to filter
container=<string> - container to filter
--since="" Show all events created since timestamp
--until="" Stream events until this timestamp
@ -626,6 +629,24 @@ and Docker images will report:
untag, delete
#### Filtering
The filtering flag (`-f` or `--filter`) format is of "key=value". If you would like to use
multiple filters, pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
Using the same filter multiple times will be handled as a *OR*; for example
`--filter container=588a23dac085 --filter container=a8f7720b8c22` will display events for
container 588a23dac085 *OR* container a8f7720b8c22
Using multiple filters will be handled as a *AND*; for example
`--filter container=588a23dac085 --filter event=start` will display events for container
container 588a23dac085 *AND* the event type is *start*
Current filters:
* event
* image
* container
#### Examples
You'll need two shells for this example.
@ -634,31 +655,64 @@ You'll need two shells for this example.
$ sudo docker events
**Shell 2: Start and Stop a Container:**
**Shell 2: Start and Stop containers:**
$ sudo docker start 4386fb97867d
$ sudo docker stop 4386fb97867d
$ sudo docker stop 7805c1d35632
**Shell 1: (Again .. now showing events):**
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) start
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) die
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) stop
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) start
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
**Show events in the past from a specified time:**
$ sudo docker events --since 1378216169
2014-03-10T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) die
2014-03-10T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) stop
2014-03-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
2014-03-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
$ sudo docker events --since '2013-09-03'
2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) start
2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) die
2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from 12de384bfb10) stop
2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) start
2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
2014-09-03T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
$ sudo docker events --since '2013-09-03 15:49:29 +0200 CEST'
2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from 12de384bfb10) die
2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from 12de384bfb10) stop
2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
**Filter events:**
$ sudo docker events --filter 'event=stop'
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
2014-09-03T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
$ sudo docker events --filter 'image=ubuntu-1:14.04'
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) start
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
$ sudo docker events --filter 'container=7805c1d35632'
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
$ sudo docker events --filter 'container=7805c1d35632' --filter 'container=4386fb97867d'
2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop
2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die
2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
$ sudo docker events --filter 'container=7805c1d35632' --filter 'event=stop'
2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
## exec
@ -777,7 +831,7 @@ uses up the `VIRTUAL SIZE` listed only once.
#### Filtering
The filtering flag (`-f` or `--filter`) format is of "key=value". If there are more
The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more
than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
Current filters:

View File

@ -6,6 +6,7 @@ import (
"time"
"github.com/docker/docker/engine"
"github.com/docker/docker/pkg/parsers/filters"
"github.com/docker/docker/utils"
)
@ -48,6 +49,11 @@ func (e *Events) Get(job *engine.Job) engine.Status {
timeout = time.NewTimer(time.Unix(until, 0).Sub(time.Now()))
)
eventFilters, err := filters.FromParam(job.Getenv("filters"))
if err != nil {
return job.Error(err)
}
// If no until, disable timeout
if until == 0 {
timeout.Stop()
@ -61,7 +67,7 @@ func (e *Events) Get(job *engine.Job) engine.Status {
// Resend every event in the [since, until] time interval.
if since != 0 {
if err := e.writeCurrent(job, since, until); err != nil {
if err := e.writeCurrent(job, since, until, eventFilters); err != nil {
return job.Error(err)
}
}
@ -72,7 +78,7 @@ func (e *Events) Get(job *engine.Job) engine.Status {
if !ok {
return engine.StatusOK
}
if err := writeEvent(job, event); err != nil {
if err := writeEvent(job, event, eventFilters); err != nil {
return job.Error(err)
}
case <-timeout.C:
@ -97,7 +103,23 @@ func (e *Events) SubscribersCount(job *engine.Job) engine.Status {
return engine.StatusOK
}
func writeEvent(job *engine.Job, event *utils.JSONMessage) error {
func writeEvent(job *engine.Job, event *utils.JSONMessage, eventFilters filters.Args) error {
isFiltered := func(field string, filter []string) bool {
if len(filter) == 0 {
return false
}
for _, v := range filter {
if v == field {
return false
}
}
return true
}
if isFiltered(event.Status, eventFilters["event"]) || isFiltered(event.From, eventFilters["image"]) || isFiltered(event.ID, eventFilters["container"]) {
return nil
}
// When sending an event JSON serialization errors are ignored, but all
// other errors lead to the eviction of the listener.
if b, err := json.Marshal(event); err == nil {
@ -108,11 +130,11 @@ func writeEvent(job *engine.Job, event *utils.JSONMessage) error {
return nil
}
func (e *Events) writeCurrent(job *engine.Job, since, until int64) error {
func (e *Events) writeCurrent(job *engine.Job, since, until int64, eventFilters filters.Args) error {
e.mu.RLock()
for _, event := range e.events {
if event.Time >= since && (event.Time <= until || until == 0) {
if err := writeEvent(job, event); err != nil {
if err := writeEvent(job, event, eventFilters); err != nil {
e.mu.RUnlock()
return err
}

View File

@ -61,7 +61,7 @@ func TestEventsPause(t *testing.T) {
t.Fatalf("event should be pause, not %#v", pauseEvent)
}
if unpauseEvent[len(unpauseEvent)-1] != "unpause" {
t.Fatalf("event should be pause, not %#v", unpauseEvent)
t.Fatalf("event should be unpause, not %#v", unpauseEvent)
}
waitCmd := exec.Command(dockerBinary, "wait", name)
@ -138,13 +138,13 @@ func TestEventsContainerEvents(t *testing.T) {
t.Fatalf("event should be create, not %#v", createEvent)
}
if startEvent[len(startEvent)-1] != "start" {
t.Fatalf("event should be pause, not %#v", startEvent)
t.Fatalf("event should be start, not %#v", startEvent)
}
if dieEvent[len(dieEvent)-1] != "die" {
t.Fatalf("event should be pause, not %#v", dieEvent)
t.Fatalf("event should be die, not %#v", dieEvent)
}
if destroyEvent[len(destroyEvent)-1] != "destroy" {
t.Fatalf("event should be pause, not %#v", destroyEvent)
t.Fatalf("event should be destroy, not %#v", destroyEvent)
}
logDone("events - container create, start, die, destroy is logged")
@ -283,3 +283,57 @@ func TestEventsImageImport(t *testing.T) {
logDone("events - image import is logged")
}
func TestEventsFilters(t *testing.T) {
since := time.Now().Unix()
cmd(t, "run", "--rm", "busybox", "true")
cmd(t, "run", "--rm", "busybox", "true")
eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", time.Now().Unix()), "--filter", "event=die")
out, exitCode, err := runCommandWithOutput(eventsCmd)
if exitCode != 0 || err != nil {
t.Fatalf("Failed to get events with exit code %d: %s", exitCode, err)
}
events := strings.Split(out, "\n")
events = events[:len(events)-1]
if len(events) != 2 {
t.Fatalf("Expected 2 events, got %d: %v", len(events), events)
}
dieEvent := strings.Fields(events[len(events)-1])
if dieEvent[len(dieEvent)-1] != "die" {
t.Fatalf("event should be die, not %#v", dieEvent)
}
dieEvent = strings.Fields(events[len(events)-2])
if dieEvent[len(dieEvent)-1] != "die" {
t.Fatalf("event should be die, not %#v", dieEvent)
}
eventsCmd = exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", time.Now().Unix()), "--filter", "event=die", "--filter", "event=start")
out, exitCode, err = runCommandWithOutput(eventsCmd)
if exitCode != 0 || err != nil {
t.Fatalf("Failed to get events with exit code %d: %s", exitCode, err)
}
events = strings.Split(out, "\n")
events = events[:len(events)-1]
if len(events) != 4 {
t.Fatalf("Expected 4 events, got %d: %v", len(events), events)
}
startEvent := strings.Fields(events[len(events)-4])
if startEvent[len(startEvent)-1] != "start" {
t.Fatalf("event should be start, not %#v", startEvent)
}
dieEvent = strings.Fields(events[len(events)-3])
if dieEvent[len(dieEvent)-1] != "die" {
t.Fatalf("event should be die, not %#v", dieEvent)
}
startEvent = strings.Fields(events[len(events)-2])
if startEvent[len(startEvent)-1] != "start" {
t.Fatalf("event should be start, not %#v", startEvent)
}
dieEvent = strings.Fields(events[len(events)-1])
if dieEvent[len(dieEvent)-1] != "die" {
t.Fatalf("event should be die, not %#v", dieEvent)
}
logDone("events - filters")
}