Merge pull request #18888 from calavera/event_types

Event all the things!
This commit is contained in:
David Calavera 2016-01-04 13:07:33 -08:00
Родитель dc4ca0e897 851fe00c64
Коммит 723be0a332
40 изменённых файлов: 1315 добавлений и 562 удалений

Просмотреть файл

@ -1,11 +1,18 @@
package client
import (
"encoding/json"
"fmt"
"io"
"strings"
"time"
"github.com/docker/docker/api/types"
eventtypes "github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/jsonlog"
flag "github.com/docker/docker/pkg/mflag"
)
@ -46,5 +53,56 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
}
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut)
return streamEvents(responseBody, cli.out)
}
// streamEvents decodes prints the incoming events in the provided output.
func streamEvents(input io.Reader, output io.Writer) error {
return decodeEvents(input, func(event eventtypes.Message, err error) error {
if err != nil {
return err
}
printOutput(event, output)
return nil
})
}
type eventProcessor func(event eventtypes.Message, err error) error
func decodeEvents(input io.Reader, ep eventProcessor) error {
dec := json.NewDecoder(input)
for {
var event eventtypes.Message
err := dec.Decode(&event)
if err != nil && err == io.EOF {
break
}
if procErr := ep(event, err); procErr != nil {
return procErr
}
}
return nil
}
// printOutput prints all types of event information.
// Each output includes the event type, actor id, name and action.
// Actor attributes are printed at the end if the actor has any.
func printOutput(event eventtypes.Message, output io.Writer) {
if event.TimeNano != 0 {
fmt.Fprintf(output, "%s ", time.Unix(0, event.TimeNano).Format(jsonlog.RFC3339NanoFixed))
} else if event.Time != 0 {
fmt.Fprintf(output, "%s ", time.Unix(event.Time, 0).Format(jsonlog.RFC3339NanoFixed))
}
fmt.Fprintf(output, "%s %s %s", event.Type, event.Action, event.Actor.ID)
if len(event.Actor.Attributes) > 0 {
var attrs []string
for k, v := range event.Actor.Attributes {
attrs = append(attrs, fmt.Sprintf("%s=%s", k, v))
}
fmt.Fprintf(output, " (%s)", strings.Join(attrs, ", "))
}
fmt.Fprint(output, "\n")
}

Просмотреть файл

@ -11,8 +11,9 @@ import (
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
Cli "github.com/docker/docker/cli"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/go-units"
)
@ -189,7 +190,11 @@ func (cli *DockerCli) CmdStats(args ...string) error {
err error
}
getNewContainers := func(c chan<- watch) {
options := types.EventsOptions{}
f := filters.NewArgs()
f.Add("type", "container")
options := types.EventsOptions{
Filters: f,
}
resBody, err := cli.client.Events(options)
if err != nil {
c <- watch{err: err}
@ -197,15 +202,15 @@ func (cli *DockerCli) CmdStats(args ...string) error {
}
defer resBody.Close()
dec := json.NewDecoder(resBody)
for {
var j *jsonmessage.JSONMessage
if err := dec.Decode(&j); err != nil {
decodeEvents(resBody, func(event events.Message, err error) error {
if err != nil {
c <- watch{err: err}
return
return nil
}
c <- watch{j.ID[:12], j.Status, nil}
}
c <- watch{event.ID[:12], event.Action, nil}
return nil
})
}
go func(stopChan chan<- error) {
cChan := make(chan watch)

Просмотреть файл

@ -19,4 +19,5 @@ type Backend interface {
DisconnectContainerFromNetwork(containerName string,
network libnetwork.Network) error
NetworkControllerEnabled() bool
DeleteNetwork(name string) error
}

Просмотреть файл

@ -148,21 +148,7 @@ func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.Respon
}
func (n *networkRouter) deleteNetwork(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := httputils.ParseForm(r); err != nil {
return err
}
nw, err := n.backend.FindNetwork(vars["id"])
if err != nil {
return err
}
if runconfig.IsPreDefinedNetwork(nw.Name()) {
return httputils.WriteJSON(w, http.StatusForbidden,
fmt.Sprintf("%s is a pre-defined network and cannot be removed", nw.Name()))
}
return nw.Delete()
return n.backend.DeleteNetwork(vars["id"])
}
func buildNetworkResource(nw libnetwork.Network) *types.NetworkResource {

Просмотреть файл

@ -2,8 +2,8 @@ package system
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/pkg/jsonmessage"
)
// Backend is the methods that need to be implemented to provide
@ -11,7 +11,7 @@ import (
type Backend interface {
SystemInfo() (*types.Info, error)
SystemVersion() types.Version
SubscribeToEvents(since, sinceNano int64, ef filters.Args) ([]*jsonmessage.JSONMessage, chan interface{})
SubscribeToEvents(since, sinceNano int64, ef filters.Args) ([]events.Message, chan interface{})
UnsubscribeFromEvents(chan interface{})
AuthenticateToRegistry(authConfig *types.AuthConfig) (string, error)
}

Просмотреть файл

@ -9,10 +9,10 @@ import (
"github.com/docker/docker/api"
"github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
timetypes "github.com/docker/docker/api/types/time"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/jsonmessage"
"golang.org/x/net/context"
)
@ -98,8 +98,9 @@ func (s *systemRouter) getEvents(ctx context.Context, w http.ResponseWriter, r *
for {
select {
case ev := <-l:
jev, ok := ev.(*jsonmessage.JSONMessage)
jev, ok := ev.(events.Message)
if !ok {
logrus.Warnf("unexpected event message: %q", ev)
continue
}
if err := enc.Encode(jev); err != nil {

Просмотреть файл

@ -0,0 +1,38 @@
package events
const (
// ContainerEventType is the event type that containers generate
ContainerEventType = "container"
// ImageEventType is the event type that images generate
ImageEventType = "image"
// VolumeEventType is the event type that volumes generate
VolumeEventType = "volume"
// NetworkEventType is the event type that networks generate
NetworkEventType = "network"
)
// Actor describes something that generates events,
// like a container, or a network, or a volume.
// It has a defined name and a set or attributes.
// The container attributes are its labels, other actors
// can generate these attributes from other properties.
type Actor struct {
ID string
Attributes map[string]string
}
// Message represents the information an event contains
type Message struct {
// Deprecated information from JSONMessage.
// With data only in container events.
Status string `json:"status,omitempty"`
ID string `json:"id,omitempty"`
From string `json:"from,omitempty"`
Type string
Action string
Actor Actor
Time int64 `json:"time,omitempty"`
TimeNano int64 `json:"timeNano,omitempty"`
}

Просмотреть файл

@ -197,6 +197,22 @@ func (filters Args) ExactMatch(field, source string) bool {
return false
}
// FuzzyMatch returns true if the source matches exactly one of the filters,
// or the source has one of the filters as a prefix.
func (filters Args) FuzzyMatch(field, source string) bool {
if filters.ExactMatch(field, source) {
return true
}
fieldValues := filters.fields[field]
for prefix := range fieldValues {
if strings.HasPrefix(source, prefix) {
return true
}
}
return false
}
// Include returns true if the name of the field to filter is in the filters.
func (filters Args) Include(field string) bool {
_, ok := filters.fields[field]

Просмотреть файл

@ -349,3 +349,21 @@ func TestWalkValues(t *testing.T) {
t.Fatalf("Expected to not iterate when the field doesn't exist, got %v", err)
}
}
func TestFuzzyMatch(t *testing.T) {
f := NewArgs()
f.Add("container", "foo")
cases := map[string]bool{
"foo": true,
"foobar": true,
"barfoo": false,
"bar": false,
}
for source, match := range cases {
got := f.FuzzyMatch("container", source)
if got != match {
t.Fatalf("Expected %v, got %v: %s", match, got, source)
}
}
}

Просмотреть файл

@ -618,7 +618,7 @@ func detachMounted(path string) error {
}
// UnmountVolumes unmounts all volumes
func (container *Container) UnmountVolumes(forceSyscall bool) error {
func (container *Container) UnmountVolumes(forceSyscall bool, volumeEventLog func(name, action string, attributes map[string]string)) error {
var (
volumeMounts []volume.MountPoint
err error
@ -649,6 +649,12 @@ func (container *Container) UnmountVolumes(forceSyscall bool) error {
if err := volumeMount.Volume.Unmount(); err != nil {
return err
}
attributes := map[string]string{
"driver": volumeMount.Volume.DriverName(),
"container": container.ID,
}
volumeEventLog(volumeMount.Volume.Name(), "unmount", attributes)
}
}

Просмотреть файл

@ -39,7 +39,7 @@ func (container *Container) IpcMounts() []execdriver.Mount {
}
// UnmountVolumes explicitly unmounts volumes from the container.
func (container *Container) UnmountVolumes(forceSyscall bool) error {
func (container *Container) UnmountVolumes(forceSyscall bool, volumeEventLog func(name, action string, attributes map[string]string)) error {
return nil
}

Просмотреть файл

@ -84,7 +84,7 @@ func (daemon *Daemon) containerStatPath(container *container.Container, path str
defer daemon.Unmount(container)
err = daemon.mountVolumes(container)
defer container.UnmountVolumes(true)
defer container.UnmountVolumes(true, daemon.LogVolumeEvent)
if err != nil {
return nil, err
}
@ -119,7 +119,7 @@ func (daemon *Daemon) containerArchivePath(container *container.Container, path
defer func() {
if err != nil {
// unmount any volumes
container.UnmountVolumes(true)
container.UnmountVolumes(true, daemon.LogVolumeEvent)
// unmount the container's rootfs
daemon.Unmount(container)
}
@ -154,7 +154,7 @@ func (daemon *Daemon) containerArchivePath(container *container.Container, path
content = ioutils.NewReadCloserWrapper(data, func() error {
err := data.Close()
container.UnmountVolumes(true)
container.UnmountVolumes(true, daemon.LogVolumeEvent)
daemon.Unmount(container)
container.Unlock()
return err
@ -181,7 +181,7 @@ func (daemon *Daemon) containerExtractToDir(container *container.Container, path
defer daemon.Unmount(container)
err = daemon.mountVolumes(container)
defer container.UnmountVolumes(true)
defer container.UnmountVolumes(true, daemon.LogVolumeEvent)
if err != nil {
return err
}
@ -283,7 +283,7 @@ func (daemon *Daemon) containerCopy(container *container.Container, resource str
defer func() {
if err != nil {
// unmount any volumes
container.UnmountVolumes(true)
container.UnmountVolumes(true, daemon.LogVolumeEvent)
// unmount the container's rootfs
daemon.Unmount(container)
}
@ -320,7 +320,7 @@ func (daemon *Daemon) containerCopy(container *container.Container, resource str
reader := ioutils.NewReadCloserWrapper(archive, func() error {
err := archive.Close()
container.UnmountVolumes(true)
container.UnmountVolumes(true, daemon.LogVolumeEvent)
daemon.Unmount(container)
container.Unlock()
return err

Просмотреть файл

@ -699,6 +699,7 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
return derr.ErrorCodeJoinInfo.WithArgs(err)
}
daemon.LogNetworkEventWithAttributes(n, "connect", map[string]string{"container": container.ID})
return nil
}
@ -719,6 +720,11 @@ func (daemon *Daemon) DisconnectFromNetwork(container *container.Container, n li
if err := container.ToDiskLocking(); err != nil {
return fmt.Errorf("Error saving container to disk: %v", err)
}
attributes := map[string]string{
"container": container.ID,
}
daemon.LogNetworkEventWithAttributes(n, "disconnect", attributes)
return nil
}
@ -844,14 +850,18 @@ func (daemon *Daemon) releaseNetwork(container *container.Container) {
}
sid := container.NetworkSettings.SandboxID
networks := container.NetworkSettings.Networks
for n := range networks {
networks[n] = &networktypes.EndpointSettings{}
settings := container.NetworkSettings.Networks
var networks []libnetwork.Network
for n := range settings {
if nw, err := daemon.FindNetwork(n); err == nil {
networks = append(networks, nw)
}
settings[n] = &networktypes.EndpointSettings{}
}
container.NetworkSettings = &network.Settings{Networks: networks}
container.NetworkSettings = &network.Settings{Networks: settings}
if sid == "" || len(networks) == 0 {
if sid == "" || len(settings) == 0 {
return
}
@ -864,6 +874,13 @@ func (daemon *Daemon) releaseNetwork(container *container.Container) {
if err := sb.Delete(); err != nil {
logrus.Errorf("Error deleting sandbox id %s for container %s: %v", sid, container.ID, err)
}
attributes := map[string]string{
"container": container.ID,
}
for _, nw := range networks {
daemon.LogNetworkEventWithAttributes(nw, "disconnect", attributes)
}
}
func (daemon *Daemon) setupIpcDirs(c *container.Container) error {

Просмотреть файл

@ -169,5 +169,10 @@ func (daemon *Daemon) VolumeCreate(name, driverName string, opts map[string]stri
if (driverName != "" && v.DriverName() != driverName) || (driverName == "" && v.DriverName() != volume.DefaultDriverName) {
return nil, derr.ErrorVolumeNameTaken.WithArgs(name, v.DriverName())
}
if driverName == "" {
driverName = volume.DefaultDriverName
}
daemon.LogVolumeEvent(name, "create", map[string]string{"driver": driverName})
return volumeToAPIType(v), nil
}

Просмотреть файл

@ -22,6 +22,7 @@ import (
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
containertypes "github.com/docker/docker/api/types/container"
eventtypes "github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/strslice"
@ -47,7 +48,6 @@ import (
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/graphdb"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/namesgenerator"
"github.com/docker/docker/pkg/progress"
@ -554,23 +554,9 @@ func (daemon *Daemon) GetByName(name string) (*container.Container, error) {
return e, nil
}
// getEventFilter returns a filters.Filter for a set of filters
func (daemon *Daemon) getEventFilter(filter filters.Args) *events.Filter {
// incoming container filter can be name, id or partial id, convert to
// a full container id
for _, cn := range filter.Get("container") {
c, err := daemon.GetContainer(cn)
filter.Del("container", cn)
if err == nil {
filter.Add("container", c.ID)
}
}
return events.NewFilter(filter, daemon.GetLabels)
}
// SubscribeToEvents returns the currently record of events, a channel to stream new events from, and a function to cancel the stream of events.
func (daemon *Daemon) SubscribeToEvents(since, sinceNano int64, filter filters.Args) ([]*jsonmessage.JSONMessage, chan interface{}) {
ef := daemon.getEventFilter(filter)
func (daemon *Daemon) SubscribeToEvents(since, sinceNano int64, filter filters.Args) ([]eventtypes.Message, chan interface{}) {
ef := events.NewFilter(filter)
return daemon.EventsService.SubscribeTopic(since, sinceNano, ef)
}
@ -580,21 +566,6 @@ func (daemon *Daemon) UnsubscribeFromEvents(listener chan interface{}) {
daemon.EventsService.Evict(listener)
}
// GetLabels for a container or image id
func (daemon *Daemon) GetLabels(id string) map[string]string {
// TODO: TestCase
container := daemon.containers.Get(id)
if container != nil {
return container.Config.Labels
}
img, err := daemon.GetImage(id)
if err == nil {
return img.ContainerConfig.Labels
}
return nil
}
// children returns all child containers of the container with the
// given name. The containers are returned as a map from the container
// name to a pointer to Container.
@ -1032,7 +1003,8 @@ func (daemon *Daemon) TagImage(newTag reference.Named, imageName string) error {
if err := daemon.referenceStore.AddTag(newTag, imageID, true); err != nil {
return err
}
daemon.EventsService.Log("tag", newTag.String(), "")
daemon.LogImageEvent(imageID.String(), newTag.String(), "tag")
return nil
}
@ -1068,15 +1040,15 @@ func (daemon *Daemon) PullImage(ref reference.Named, metaHeaders map[string][]st
}()
imagePullConfig := &distribution.ImagePullConfig{
MetaHeaders: metaHeaders,
AuthConfig: authConfig,
ProgressOutput: progress.ChanOutput(progressChan),
RegistryService: daemon.RegistryService,
EventsService: daemon.EventsService,
MetadataStore: daemon.distributionMetadataStore,
ImageStore: daemon.imageStore,
ReferenceStore: daemon.referenceStore,
DownloadManager: daemon.downloadManager,
MetaHeaders: metaHeaders,
AuthConfig: authConfig,
ProgressOutput: progress.ChanOutput(progressChan),
RegistryService: daemon.RegistryService,
ImageEventLogger: daemon.LogImageEvent,
MetadataStore: daemon.distributionMetadataStore,
ImageStore: daemon.imageStore,
ReferenceStore: daemon.referenceStore,
DownloadManager: daemon.downloadManager,
}
err := distribution.Pull(ctx, ref, imagePullConfig)
@ -1111,17 +1083,17 @@ func (daemon *Daemon) PushImage(ref reference.Named, metaHeaders map[string][]st
}()
imagePushConfig := &distribution.ImagePushConfig{
MetaHeaders: metaHeaders,
AuthConfig: authConfig,
ProgressOutput: progress.ChanOutput(progressChan),
RegistryService: daemon.RegistryService,
EventsService: daemon.EventsService,
MetadataStore: daemon.distributionMetadataStore,
LayerStore: daemon.layerStore,
ImageStore: daemon.imageStore,
ReferenceStore: daemon.referenceStore,
TrustKey: daemon.trustKey,
UploadManager: daemon.uploadManager,
MetaHeaders: metaHeaders,
AuthConfig: authConfig,
ProgressOutput: progress.ChanOutput(progressChan),
RegistryService: daemon.RegistryService,
ImageEventLogger: daemon.LogImageEvent,
MetadataStore: daemon.distributionMetadataStore,
LayerStore: daemon.layerStore,
ImageStore: daemon.imageStore,
ReferenceStore: daemon.referenceStore,
TrustKey: daemon.trustKey,
UploadManager: daemon.uploadManager,
}
err := distribution.Push(ctx, ref, imagePushConfig)

Просмотреть файл

@ -157,5 +157,6 @@ func (daemon *Daemon) VolumeRm(name string) error {
}
return derr.ErrorCodeRmVolume.WithArgs(name, err)
}
daemon.LogVolumeEvent(v.Name(), "destroy", map[string]string{"driver": v.DriverName()})
return nil
}

Просмотреть файл

@ -1,14 +1,81 @@
package daemon
import (
"strings"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/container"
"github.com/docker/libnetwork"
)
// LogContainerEvent generates an event related to a container.
func (daemon *Daemon) LogContainerEvent(container *container.Container, action string) {
daemon.EventsService.Log(
action,
container.ID,
container.Config.Image,
)
attributes := copyAttributes(container.Config.Labels)
if container.Config.Image != "" {
attributes["image"] = container.Config.Image
}
attributes["name"] = strings.TrimLeft(container.Name, "/")
actor := events.Actor{
ID: container.ID,
Attributes: attributes,
}
daemon.EventsService.Log(action, events.ContainerEventType, actor)
}
// LogImageEvent generates an event related to a container.
func (daemon *Daemon) LogImageEvent(imageID, refName, action string) {
attributes := map[string]string{}
img, err := daemon.GetImage(imageID)
if err == nil && img.Config != nil {
// image has not been removed yet.
// it could be missing if the event is `delete`.
attributes = copyAttributes(img.Config.Labels)
}
if refName != "" {
attributes["name"] = refName
}
actor := events.Actor{
ID: imageID,
Attributes: attributes,
}
daemon.EventsService.Log(action, events.ImageEventType, actor)
}
// LogVolumeEvent generates an event related to a volume.
func (daemon *Daemon) LogVolumeEvent(volumeID, action string, attributes map[string]string) {
actor := events.Actor{
ID: volumeID,
Attributes: attributes,
}
daemon.EventsService.Log(action, events.VolumeEventType, actor)
}
// LogNetworkEvent generates an event related to a network with only the default attributes.
func (daemon *Daemon) LogNetworkEvent(nw libnetwork.Network, action string) {
daemon.LogNetworkEventWithAttributes(nw, action, map[string]string{})
}
// LogNetworkEventWithAttributes generates an event related to a network with specific given attributes.
func (daemon *Daemon) LogNetworkEventWithAttributes(nw libnetwork.Network, action string, attributes map[string]string) {
attributes["name"] = nw.Name()
attributes["type"] = nw.Type()
actor := events.Actor{
ID: nw.ID(),
Attributes: attributes,
}
daemon.EventsService.Log(action, events.NetworkEventType, actor)
}
// copyAttributes guarantees that labels are not mutated by event triggers.
func copyAttributes(labels map[string]string) map[string]string {
attributes := map[string]string{}
if labels == nil {
return attributes
}
for k, v := range labels {
attributes[k] = v
}
return attributes
}

Просмотреть файл

@ -4,7 +4,7 @@ import (
"sync"
"time"
"github.com/docker/docker/pkg/jsonmessage"
eventtypes "github.com/docker/docker/api/types/events"
"github.com/docker/docker/pkg/pubsub"
)
@ -13,17 +13,17 @@ const (
bufferSize = 1024
)
// Events is pubsub channel for *jsonmessage.JSONMessage
// Events is pubsub channel for events generated by the engine.
type Events struct {
mu sync.Mutex
events []*jsonmessage.JSONMessage
events []eventtypes.Message
pub *pubsub.Publisher
}
// New returns new *Events instance
func New() *Events {
return &Events{
events: make([]*jsonmessage.JSONMessage, 0, eventsLimit),
events: make([]eventtypes.Message, 0, eventsLimit),
pub: pubsub.NewPublisher(100*time.Millisecond, bufferSize),
}
}
@ -32,9 +32,9 @@ func New() *Events {
// last events, a channel in which you can expect new events (in form
// of interface{}, so you need type assertion), and a function to call
// to stop the stream of events.
func (e *Events) Subscribe() ([]*jsonmessage.JSONMessage, chan interface{}, func()) {
func (e *Events) Subscribe() ([]eventtypes.Message, chan interface{}, func()) {
e.mu.Lock()
current := make([]*jsonmessage.JSONMessage, len(e.events))
current := make([]eventtypes.Message, len(e.events))
copy(current, e.events)
l := e.pub.Subscribe()
e.mu.Unlock()
@ -48,13 +48,13 @@ func (e *Events) Subscribe() ([]*jsonmessage.JSONMessage, chan interface{}, func
// SubscribeTopic adds new listener to events, returns slice of 64 stored
// last events, a channel in which you can expect new events (in form
// of interface{}, so you need type assertion).
func (e *Events) SubscribeTopic(since, sinceNano int64, ef *Filter) ([]*jsonmessage.JSONMessage, chan interface{}) {
func (e *Events) SubscribeTopic(since, sinceNano int64, ef *Filter) ([]eventtypes.Message, chan interface{}) {
e.mu.Lock()
defer e.mu.Unlock()
var buffered []*jsonmessage.JSONMessage
var buffered []eventtypes.Message
topic := func(m interface{}) bool {
return ef.Include(m.(*jsonmessage.JSONMessage))
return ef.Include(m.(eventtypes.Message))
}
if since != -1 {
@ -64,7 +64,7 @@ func (e *Events) SubscribeTopic(since, sinceNano int64, ef *Filter) ([]*jsonmess
break
}
if ef.filter.Len() == 0 || topic(ev) {
buffered = append([]*jsonmessage.JSONMessage{ev}, buffered...)
buffered = append([]eventtypes.Message{ev}, buffered...)
}
}
}
@ -87,9 +87,27 @@ func (e *Events) Evict(l chan interface{}) {
// Log broadcasts event to listeners. Each listener has 100 millisecond for
// receiving event or it will be skipped.
func (e *Events) Log(action, id, from string) {
func (e *Events) Log(action, eventType string, actor eventtypes.Actor) {
now := time.Now().UTC()
jm := &jsonmessage.JSONMessage{Status: action, ID: id, From: from, Time: now.Unix(), TimeNano: now.UnixNano()}
jm := eventtypes.Message{
Action: action,
Type: eventType,
Actor: actor,
Time: now.Unix(),
TimeNano: now.UnixNano(),
}
// fill deprecated fields for container and images
switch eventType {
case eventtypes.ContainerEventType:
jm.ID = actor.ID
jm.Status = action
jm.From = actor.Attributes["image"]
case eventtypes.ImageEventType:
jm.ID = actor.ID
jm.Status = action
}
e.mu.Lock()
if len(e.events) == cap(e.events) {
// discard oldest event

Просмотреть файл

@ -5,7 +5,7 @@ import (
"testing"
"time"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/api/types/events"
)
func TestEventsLog(t *testing.T) {
@ -18,10 +18,14 @@ func TestEventsLog(t *testing.T) {
if count != 2 {
t.Fatalf("Must be 2 subscribers, got %d", count)
}
e.Log("test", "cont", "image")
actor := events.Actor{
ID: "cont",
Attributes: map[string]string{"image": "image"},
}
e.Log("test", events.ContainerEventType, actor)
select {
case msg := <-l1:
jmsg, ok := msg.(*jsonmessage.JSONMessage)
jmsg, ok := msg.(events.Message)
if !ok {
t.Fatalf("Unexpected type %T", msg)
}
@ -42,7 +46,7 @@ func TestEventsLog(t *testing.T) {
}
select {
case msg := <-l2:
jmsg, ok := msg.(*jsonmessage.JSONMessage)
jmsg, ok := msg.(events.Message)
if !ok {
t.Fatalf("Unexpected type %T", msg)
}
@ -70,7 +74,10 @@ func TestEventsLogTimeout(t *testing.T) {
c := make(chan struct{})
go func() {
e.Log("test", "cont", "image")
actor := events.Actor{
ID: "image",
}
e.Log("test", events.ImageEventType, actor)
close(c)
}()
@ -88,7 +95,12 @@ func TestLogEvents(t *testing.T) {
action := fmt.Sprintf("action_%d", i)
id := fmt.Sprintf("cont_%d", i)
from := fmt.Sprintf("image_%d", i)
e.Log(action, id, from)
actor := events.Actor{
ID: id,
Attributes: map[string]string{"image": from},
}
e.Log(action, events.ContainerEventType, actor)
}
time.Sleep(50 * time.Millisecond)
current, l, _ := e.Subscribe()
@ -97,16 +109,21 @@ func TestLogEvents(t *testing.T) {
action := fmt.Sprintf("action_%d", num)
id := fmt.Sprintf("cont_%d", num)
from := fmt.Sprintf("image_%d", num)
e.Log(action, id, from)
actor := events.Actor{
ID: id,
Attributes: map[string]string{"image": from},
}
e.Log(action, events.ContainerEventType, actor)
}
if len(e.events) != eventsLimit {
t.Fatalf("Must be %d events, got %d", eventsLimit, len(e.events))
}
var msgs []*jsonmessage.JSONMessage
var msgs []events.Message
for len(msgs) < 10 {
m := <-l
jm, ok := (m).(*jsonmessage.JSONMessage)
jm, ok := (m).(events.Message)
if !ok {
t.Fatalf("Unexpected type %T", m)
}

Просмотреть файл

@ -1,46 +1,76 @@
package events
import (
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/reference"
)
// Filter can filter out docker events from a stream
type Filter struct {
filter filters.Args
getLabels func(id string) map[string]string
filter filters.Args
}
// NewFilter creates a new Filter
func NewFilter(filter filters.Args, getLabels func(id string) map[string]string) *Filter {
return &Filter{filter: filter, getLabels: getLabels}
func NewFilter(filter filters.Args) *Filter {
return &Filter{filter: filter}
}
// Include returns true when the event ev is included by the filters
func (ef *Filter) Include(ev *jsonmessage.JSONMessage) bool {
return ef.filter.ExactMatch("event", ev.Status) &&
ef.filter.ExactMatch("container", ev.ID) &&
ef.isImageIncluded(ev.ID, ev.From) &&
ef.isLabelFieldIncluded(ev.ID)
func (ef *Filter) Include(ev events.Message) bool {
return ef.filter.ExactMatch("event", ev.Action) &&
ef.filter.ExactMatch("type", ev.Type) &&
ef.matchContainer(ev) &&
ef.matchVolume(ev) &&
ef.matchNetwork(ev) &&
ef.matchImage(ev) &&
ef.matchLabels(ev.Actor.Attributes)
}
func (ef *Filter) isLabelFieldIncluded(id string) bool {
func (ef *Filter) matchLabels(attributes map[string]string) bool {
if !ef.filter.Include("label") {
return true
}
return ef.filter.MatchKVList("label", ef.getLabels(id))
return ef.filter.MatchKVList("label", attributes)
}
// The image filter will be matched against both event.ID (for image events)
// and event.From (for container events), so that any container that was created
func (ef *Filter) matchContainer(ev events.Message) bool {
return ef.fuzzyMatchName(ev, events.ContainerEventType)
}
func (ef *Filter) matchVolume(ev events.Message) bool {
return ef.fuzzyMatchName(ev, events.VolumeEventType)
}
func (ef *Filter) matchNetwork(ev events.Message) bool {
return ef.fuzzyMatchName(ev, events.NetworkEventType)
}
func (ef *Filter) fuzzyMatchName(ev events.Message, eventType string) bool {
return ef.filter.FuzzyMatch(eventType, ev.Actor.ID) ||
ef.filter.FuzzyMatch(eventType, ev.Actor.Attributes["name"])
}
// matchImage matches against both event.Actor.ID (for image events)
// and event.Actor.Attributes["image"] (for container events), so that any container that was created
// from an image will be included in the image events. Also compare both
// against the stripped repo name without any tags.
func (ef *Filter) isImageIncluded(eventID string, eventFrom string) bool {
return ef.filter.ExactMatch("image", eventID) ||
ef.filter.ExactMatch("image", eventFrom) ||
ef.filter.ExactMatch("image", stripTag(eventID)) ||
ef.filter.ExactMatch("image", stripTag(eventFrom))
func (ef *Filter) matchImage(ev events.Message) bool {
id := ev.Actor.ID
nameAttr := "image"
var imageName string
if ev.Type == events.ImageEventType {
nameAttr = "name"
}
if n, ok := ev.Actor.Attributes[nameAttr]; ok {
imageName = n
}
return ef.filter.ExactMatch("image", id) ||
ef.filter.ExactMatch("image", imageName) ||
ef.filter.ExactMatch("image", stripTag(id)) ||
ef.filter.ExactMatch("image", stripTag(imageName))
}
func stripTag(image string) string {

36
daemon/events_test.go Normal file
Просмотреть файл

@ -0,0 +1,36 @@
package daemon
import (
"testing"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/events"
)
func TestLogContainerCopyLabels(t *testing.T) {
e := events.New()
_, l, _ := e.Subscribe()
defer e.Evict(l)
container := &container.Container{
CommonContainer: container.CommonContainer{
ID: "container_id",
Name: "container_name",
Config: &containertypes.Config{
Labels: map[string]string{
"node": "1",
"os": "alpine",
},
},
},
}
daemon := &Daemon{
EventsService: e,
}
daemon.LogContainerEvent(container, "create")
if _, mutated := container.Config.Labels["image"]; mutated {
t.Fatalf("Expected to not mutate the container labels, got %q", container.Config.Labels)
}
}

Просмотреть файл

@ -87,7 +87,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
daemon.EventsService.Log("untag", imgID.String(), "")
daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
records = append(records, untaggedRecord)
// If has remaining references then untag finishes the remove
@ -109,7 +109,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
daemon.EventsService.Log("untag", imgID.String(), "")
daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
records = append(records, untaggedRecord)
}
}
@ -174,7 +174,7 @@ func (daemon *Daemon) removeAllReferencesToImageID(imgID image.ID, records *[]ty
untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
daemon.EventsService.Log("untag", imgID.String(), "")
daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
*records = append(*records, untaggedRecord)
}
@ -243,7 +243,7 @@ func (daemon *Daemon) imageDeleteHelper(imgID image.ID, records *[]types.ImageDe
return err
}
daemon.EventsService.Log("delete", imgID.String(), "")
daemon.LogImageEvent(imgID.String(), imgID.String(), "delete")
*records = append(*records, types.ImageDelete{Deleted: imgID.String()})
for _, removedLayer := range removedLayers {
*records = append(*records, types.ImageDelete{Deleted: removedLayer.ChainID.String()})

Просмотреть файл

@ -97,7 +97,7 @@ func (daemon *Daemon) ImportImage(src string, newRef reference.Named, msg string
}
}
daemon.EventsService.Log("import", id.String(), "")
daemon.LogImageEvent(id.String(), id.String(), "import")
outStream.Write(sf.FormatStatus("", id.String()))
return nil
}

Просмотреть файл

@ -7,6 +7,8 @@ import (
"strings"
"github.com/docker/docker/api/types/network"
derr "github.com/docker/docker/errors"
"github.com/docker/docker/runconfig"
"github.com/docker/libnetwork"
)
@ -114,7 +116,13 @@ func (daemon *Daemon) CreateNetwork(name, driver string, ipam network.IPAM, opti
nwOptions = append(nwOptions, libnetwork.NetworkOptionIpam(ipam.Driver, "", v4Conf, v6Conf))
nwOptions = append(nwOptions, libnetwork.NetworkOptionDriverOpts(options))
return c.NewNetwork(driver, name, nwOptions...)
n, err := c.NewNetwork(driver, name, nwOptions...)
if err != nil {
return nil, err
}
daemon.LogNetworkEvent(n, "create")
return n, nil
}
func getIpamConfig(data []network.IPAMConfig) ([]*libnetwork.IpamConf, []*libnetwork.IpamConf, error) {
@ -178,3 +186,21 @@ func (daemon *Daemon) GetNetworkDriverList() map[string]bool {
return pluginList
}
// DeleteNetwork destroys a network unless it's one of docker's predefined networks.
func (daemon *Daemon) DeleteNetwork(networkID string) error {
nw, err := daemon.FindNetwork(networkID)
if err != nil {
return err
}
if runconfig.IsPreDefinedNetwork(nw.Name()) {
return derr.ErrorCodeCantDeletePredefinedNetwork.WithArgs(nw.Name())
}
if err := nw.Delete(); err != nil {
return err
}
daemon.LogNetworkEvent(nw, "destroy")
return nil
}

Просмотреть файл

@ -156,7 +156,7 @@ func (daemon *Daemon) Cleanup(container *container.Container) {
daemon.unregisterExecCommand(container, eConfig)
}
if err := container.UnmountVolumes(false); err != nil {
if err := container.UnmountVolumes(false, daemon.LogVolumeEvent); err != nil {
logrus.Warnf("%s cleanup: Failed to umount volumes: %v", container.ID, err)
}
}

Просмотреть файл

@ -54,5 +54,7 @@ func (daemon *Daemon) update(name string, hostConfig *container.HostConfig) erro
}
}
daemon.LogContainerEvent(container, "update")
return nil
}

Просмотреть файл

@ -5,6 +5,7 @@ package daemon
import (
"os"
"sort"
"strconv"
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/execdriver"
@ -30,6 +31,16 @@ func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver.
Writable: m.RW,
Propagation: m.Propagation,
}
if m.Volume != nil {
attributes := map[string]string{
"driver": m.Volume.DriverName(),
"container": container.ID,
"destination": m.Destination,
"read/write": strconv.FormatBool(m.RW),
"propagation": m.Propagation,
}
daemon.LogVolumeEvent(m.Volume.Name(), "mount", attributes)
}
mounts = append(mounts, mnt)
}
}

Просмотреть файл

@ -8,7 +8,6 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/daemon/events"
"github.com/docker/docker/distribution/metadata"
"github.com/docker/docker/distribution/xfer"
"github.com/docker/docker/image"
@ -32,8 +31,8 @@ type ImagePullConfig struct {
// RegistryService is the registry service to use for TLS configuration
// and endpoint lookup.
RegistryService *registry.Service
// EventsService is the events service to use for logging.
EventsService *events.Events
// ImageEventLogger notifies events for a given image
ImageEventLogger func(id, name, action string)
// MetadataStore is the storage backend for distribution-specific
// metadata.
MetadataStore metadata.Store
@ -161,7 +160,7 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
}
}
imagePullConfig.EventsService.Log("pull", ref.String(), "")
imagePullConfig.ImageEventLogger(ref.String(), repoInfo.Name(), "pull")
return nil
}

Просмотреть файл

@ -9,7 +9,6 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/digest"
"github.com/docker/docker/api/types"
"github.com/docker/docker/daemon/events"
"github.com/docker/docker/distribution/metadata"
"github.com/docker/docker/distribution/xfer"
"github.com/docker/docker/image"
@ -35,8 +34,8 @@ type ImagePushConfig struct {
// RegistryService is the registry service to use for TLS configuration
// and endpoint lookup.
RegistryService *registry.Service
// EventsService is the events service to use for logging.
EventsService *events.Events
// ImageEventLogger notifies events for a given image
ImageEventLogger func(id, name, action string)
// MetadataStore is the storage backend for distribution-specific
// metadata.
MetadataStore metadata.Store
@ -156,7 +155,7 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
return err
}
imagePushConfig.EventsService.Log("push", repoInfo.Name(), "")
imagePushConfig.ImageEventLogger(ref.String(), repoInfo.Name(), "push")
return nil
}

Просмотреть файл

@ -12,6 +12,12 @@ parent = "mn_use_docker"
The following list of features are deprecated.
### Ambiguous event fields in API
**Deprecated In Release: v1.10**
The fields `ID`, `Status` and `From` in the events API have been deprecated in favor of a more rich structure.
See the events API documentation for the new format.
### `-f` flag on `docker tag`
**Deprecated In Release: v1.10**

Просмотреть файл

@ -2274,17 +2274,24 @@ Status Codes:
`GET /events`
Get container events from docker, either in real time via streaming, or via
polling (using since).
Get container events from docker, either in real time via streaming, or via polling (using since).
Docker containers report the following events:
attach, commit, copy, create, destroy, die, exec_create, exec_start, export, kill, oom, pause, rename, resize, restart, start, stop, top, unpause
attach, commit, copy, create, destroy, die, exec_create, exec_start, export, kill, oom, pause, rename, resize, restart, start, stop, top, unpause, update
and Docker images report:
Docker images report the following events:
delete, import, pull, push, tag, untag
Docker volumes report the following events:
create, mount, unmount, destroy
Docker networks report the following events:
create, connect, disconnect, destroy
**Example request**:
GET /events?since=1374067924
@ -2294,10 +2301,48 @@ and Docker images report:
HTTP/1.1 200 OK
Content-Type: application/json
{"status":"pull","id":"busybox:latest","time":1442421700,"timeNano":1442421700598988358}
{"status":"create","id":"5745704abe9caa5","from":"busybox","time":1442421716,"timeNano":1442421716853979870}
{"status":"attach","id":"5745704abe9caa5","from":"busybox","time":1442421716,"timeNano":1442421716894759198}
{"status":"start","id":"5745704abe9caa5","from":"busybox","time":1442421716,"timeNano":1442421716983607193}
[
{
"action": "pull",
"type": "image",
"actor": {
"id": "busybox:latest",
"attributes": {}
}
"time": 1442421700,
"timeNano": 1442421700598988358
},
{
"action": "create",
"type": "container",
"actor": {
"id": "5745704abe9caa5",
"attributes": {"image": "busybox"}
}
"time": 1442421716,
"timeNano": 1442421716853979870
},
{
"action": "attach",
"type": "container",
"actor": {
"id": "5745704abe9caa5",
"attributes": {"image": "busybox"}
}
"time": 1442421716,
"timeNano": 1442421716894759198
},
{
"action": "start",
"type": "container",
"actor": {
"id": "5745704abe9caa5",
"attributes": {"image": "busybox"}
}
"time": 1442421716,
"timeNano": 1442421716983607193
}
]
Query Parameters:
@ -2308,6 +2353,9 @@ Query Parameters:
- `event=<string>`; -- event to filter
- `image=<string>`; -- image to filter
- `label=<string>`; -- image and container label to filter
- `type=<string>`; -- either `container` or `image` or `volume` or `network`
- `volume=<string>`; -- volume to filter
- `network=<string>`; -- network to filter
Status Codes:

Просмотреть файл

@ -19,14 +19,22 @@ parent = "smn_cli"
--since="" Show all events created since timestamp
--until="" Stream events until this timestamp
Docker containers will report the following events:
Docker containers report the following events:
attach, commit, copy, create, destroy, die, exec_create, exec_start, export, kill, oom, pause, rename, resize, restart, start, stop, top, unpause
attach, commit, copy, create, destroy, die, exec_create, exec_start, export, kill, oom, pause, rename, resize, restart, start, stop, top, unpause, update
and Docker images will report:
Docker images report the following events:
delete, import, pull, push, tag, untag
Docker volumes report the following events:
create, mount, unmount, destroy
Docker networks report the following events:
create, connect, disconnect, destroy
The `--since` and `--until` parameters can be Unix timestamps, date formatted
timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed
relative to the client machines time. If you do not provide the --since option,
@ -57,9 +65,12 @@ container container 588a23dac085 *AND* the event type is *start*
The currently supported filters are:
* container (`container=<name or id>`)
* event (`event=<event type>`)
* event (`event=<event action>`)
* image (`image=<tag or id>`)
* label (`label=<key>` or `label=<key>=<value>`)
* type (`type=<container or image or volume or network>`)
* volume (`volume=<name or id>`)
* network (`network=<name or id>`)
## Examples
@ -77,68 +88,78 @@ You'll need two shells for this example.
**Shell 1: (Again .. now showing events):**
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
2015-05-12T11:51:30.999999999Z07:00 container start 4386fb97867d (image=ubuntu-1:14.04)
2015-05-12T11:51:30.999999999Z07:00 container die 4386fb97867d (image=ubuntu-1:14.04)
2015-05-12T15:52:12.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
2015-05-12T15:53:45.999999999Z07:00 container die 7805c1d35632 (image=redis:2.8)
2015-05-12T15:54:03.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
**Show events in the past from a specified time:**
$ docker events --since 1378216169
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
2015-05-12T11:51:30.999999999Z07:00 container die 4386fb97867d (image=ubuntu-1:14.04)
2015-05-12T15:52:12.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
2015-05-12T15:53:45.999999999Z07:00 container die 7805c1d35632 (image=redis:2.8)
2015-05-12T15:54:03.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
$ docker events --since '2013-09-03'
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
2015-05-12T11:51:30.999999999Z07:00 container start 4386fb97867d (image=ubuntu-1:14.04)
2015-05-12T11:51:30.999999999Z07:00 container die 4386fb97867d (image=ubuntu-1:14.04)
2015-05-12T15:52:12.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
2015-05-12T15:53:45.999999999Z07:00 container die 7805c1d35632 (image=redis:2.8)
2015-05-12T15:54:03.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
$ docker events --since '2013-09-03T15:49:29'
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
2015-05-12T11:51:30.999999999Z07:00 container die 4386fb97867d (image=ubuntu-1:14.04)
2015-05-12T15:52:12.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
2015-05-12T15:53:45.999999999Z07:00 container die 7805c1d35632 (image=redis:2.8)
2015-05-12T15:54:03.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
This example outputs all events that were generated in the last 3 minutes,
relative to the current time on the client machine:
$ docker events --since '3m'
2015-05-12T11:51:30.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die
2015-05-12T15:52:12.999999999Z07:00 4 4386fb97867d: (from ubuntu-1:14.04) stop
2015-05-12T15:53:45.999999999Z07:00 7805c1d35632: (from redis:2.8) die
2015-05-12T15:54:03.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
2015-05-12T11:51:30.999999999Z07:00 container die 4386fb97867d (image=ubuntu-1:14.04)
2015-05-12T15:52:12.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
2015-05-12T15:53:45.999999999Z07:00 container die 7805c1d35632 (image=redis:2.8)
2015-05-12T15:54:03.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
**Filter events:**
$ 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
2014-05-10T17:42:14.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
2014-09-03T17:42:14.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
$ 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
2014-05-10T17:42:14.999999999Z07:00 container start 4386fb97867d (image=ubuntu-1:14.04)
2014-05-10T17:42:14.999999999Z07:00 container die 4386fb97867d (image=ubuntu-1:14.04)
2014-05-10T17:42:14.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
$ 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
2014-05-10T17:42:14.999999999Z07:00 container die 7805c1d35632 (image=redis:2.8)
2014-09-03T15:49:29.999999999Z07:00 container stop 7805c1d35632 (image= redis:2.8)
$ 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
2014-09-03T15:49:29.999999999Z07:00 container die 4386fb97867d (image=ubuntu-1:14.04)
2014-05-10T17:42:14.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
2014-05-10T17:42:14.999999999Z07:00 container die 7805c1d35632 (image=redis:2.8)
2014-09-03T15:49:29.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
$ docker events --filter 'container=7805c1d35632' --filter 'event=stop'
2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop
2014-09-03T15:49:29.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
$ docker events --filter 'container=container_1' --filter 'container=container_2'
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
2014-09-03T15:49:29.999999999Z07:00 container die 4386fb97867d (image=ubuntu-1:14.04)
2014-05-10T17:42:14.999999999Z07:00 container stop 4386fb97867d (image=ubuntu-1:14.04)
2014-05-10T17:42:14.999999999Z07:00 container die 7805c1d35632 (imager=redis:2.8)
2014-09-03T15:49:29.999999999Z07:00 container stop 7805c1d35632 (image=redis:2.8)
$ docker events --filter 'type=volume'
2015-12-23T21:05:28.136212689Z volume create test-event-volume-local (driver=local)
2015-12-23T21:05:28.383462717Z volume mount test-event-volume-local (read/write=true, container=562fe10671e9273da25eed36cdce26159085ac7ee6707105fd534866340a5025, destination=/foo, driver=local, propagation=rprivate)
2015-12-23T21:05:28.650314265Z volume unmount test-event-volume-local (container=562fe10671e9273da25eed36cdce26159085ac7ee6707105fd534866340a5025, driver=local)
2015-12-23T21:05:28.716218405Z volume destroy test-event-volume-local (driver=local)
$ docker events --filter 'type=network'
2015-12-23T21:38:24.705709133Z network create 8b111217944ba0ba844a65b13efcd57dc494932ee2527577758f939315ba2c5b (name=test-event-network-local, type=bridge)
2015-12-23T21:38:25.119625123Z network connect 8b111217944ba0ba844a65b13efcd57dc494932ee2527577758f939315ba2c5b (name=test-event-network-local, container=b4be644031a3d90b400f88ab3d4bdf4dc23adb250e696b6328b85441abe2c54e, type=bridge)

Просмотреть файл

@ -939,4 +939,13 @@ var (
Description: "There was an error while trying to start a container",
HTTPStatusCode: http.StatusInternalServerError,
})
// ErrorCodeCantDeletePredefinedNetwork is generated when one of the predefined networks
// is attempted to be deleted.
ErrorCodeCantDeletePredefinedNetwork = errcode.Register(errGroup, errcode.ErrorDescriptor{
Value: "CANT_DELETE_PREDEFINED_NETWORK",
Message: "%s is a pre-defined network and cannot be removed",
Description: "Engine's predefined networks cannot be deleted",
HTTPStatusCode: http.StatusForbidden,
})
)

Просмотреть файл

@ -1,10 +1,16 @@
package main
import (
"encoding/json"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/docker/docker/pkg/integration/checker"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/go-check/check"
)
@ -28,3 +34,40 @@ func (s *DockerSuite) TestEventsApiEmptyOutput(c *check.C) {
c.Fatal("timeout waiting for events api to respond, should have responded immediately")
}
}
func (s *DockerSuite) TestEventsApiBackwardsCompatible(c *check.C) {
since := daemonTime(c).Unix()
ts := strconv.FormatInt(since, 10)
out, _ := dockerCmd(c, "run", "--name=foo", "-d", "busybox", "top")
containerID := strings.TrimSpace(out)
c.Assert(waitRun(containerID), checker.IsNil)
q := url.Values{}
q.Set("since", ts)
_, body, err := sockRequestRaw("GET", "/events?"+q.Encode(), nil, "")
c.Assert(err, checker.IsNil)
defer body.Close()
dec := json.NewDecoder(body)
var containerCreateEvent *jsonmessage.JSONMessage
for {
var event jsonmessage.JSONMessage
if err := dec.Decode(&event); err != nil {
if err == io.EOF {
break
}
c.Fatal(err)
}
if event.Status == "create" && event.ID == containerID {
containerCreateEvent = &event
break
}
}
c.Assert(containerCreateEvent, checker.Not(checker.IsNil))
c.Assert(containerCreateEvent.Status, checker.Equals, "create")
c.Assert(containerCreateEvent.ID, checker.Equals, containerID)
c.Assert(containerCreateEvent.From, checker.Equals, "busybox")
}

Просмотреть файл

@ -2,7 +2,6 @@ package main
import (
"archive/tar"
"bufio"
"bytes"
"encoding/json"
"fmt"
@ -1863,104 +1862,6 @@ func (s *DockerSuite) TestBuildForceRm(c *check.C) {
}
// Test that an infinite sleep during a build is killed if the client disconnects.
// This test is fairly hairy because there are lots of ways to race.
// Strategy:
// * Monitor the output of docker events starting from before
// * Run a 1-year-long sleep from a docker build.
// * When docker events sees container start, close the "docker build" command
// * Wait for docker events to emit a dying event.
func (s *DockerSuite) TestBuildCancellationKillsSleep(c *check.C) {
testRequires(c, DaemonIsLinux)
name := "testbuildcancellation"
// (Note: one year, will never finish)
ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil)
if err != nil {
c.Fatal(err)
}
defer ctx.Close()
eventStart := make(chan struct{})
eventDie := make(chan struct{})
observer, err := newEventObserver(c)
c.Assert(err, checker.IsNil)
err = observer.Start()
c.Assert(err, checker.IsNil)
defer observer.Stop()
buildCmd := exec.Command(dockerBinary, "build", "-t", name, ".")
buildCmd.Dir = ctx.Dir
stdoutBuild, err := buildCmd.StdoutPipe()
if err := buildCmd.Start(); err != nil {
c.Fatalf("failed to run build: %s", err)
}
matchCID := regexp.MustCompile("Running in (.+)")
scanner := bufio.NewScanner(stdoutBuild)
outputBuffer := new(bytes.Buffer)
var buildID string
for scanner.Scan() {
line := scanner.Text()
outputBuffer.WriteString(line)
outputBuffer.WriteString("\n")
if matches := matchCID.FindStringSubmatch(line); len(matches) > 0 {
buildID = matches[1]
break
}
}
if buildID == "" {
c.Fatalf("Unable to find build container id in build output:\n%s", outputBuffer.String())
}
matchStart := regexp.MustCompile(buildID + `.* start\z`)
matchDie := regexp.MustCompile(buildID + `.* die\z`)
matcher := func(text string) {
switch {
case matchStart.MatchString(text):
close(eventStart)
case matchDie.MatchString(text):
close(eventDie)
}
}
go observer.Match(matcher)
select {
case <-time.After(10 * time.Second):
c.Fatal(observer.TimeoutError(buildID, "start"))
case <-eventStart:
// Proceeds from here when we see the container fly past in the
// output of "docker events".
// Now we know the container is running.
}
// Send a kill to the `docker build` command.
// Causes the underlying build to be cancelled due to socket close.
if err := buildCmd.Process.Kill(); err != nil {
c.Fatalf("error killing build command: %s", err)
}
// Get the exit status of `docker build`, check it exited because killed.
if err := buildCmd.Wait(); err != nil && !isKilled(err) {
c.Fatalf("wait failed during build run: %T %s", err, err)
}
select {
case <-time.After(10 * time.Second):
// If we don't get here in a timely fashion, it wasn't killed.
c.Fatal(observer.TimeoutError(buildID, "die"))
case <-eventDie:
// We saw the container shut down in the `docker events` stream,
// as expected.
}
}
func (s *DockerSuite) TestBuildRm(c *check.C) {
testRequires(c, DaemonIsLinux)
name := "testbuildrm"
@ -6489,33 +6390,26 @@ func (s *DockerSuite) TestBuildNoNamedVolume(c *check.C) {
func (s *DockerSuite) TestBuildTagEvent(c *check.C) {
testRequires(c, DaemonIsLinux)
observer, err := newEventObserver(c, "--filter", "event=tag")
c.Assert(err, check.IsNil)
err = observer.Start()
c.Assert(err, check.IsNil)
defer observer.Stop()
since := daemonTime(c).Unix()
dockerFile := `FROM busybox
RUN echo events
`
_, err = buildImage("test", dockerFile, false)
_, err := buildImage("test", dockerFile, false)
c.Assert(err, check.IsNil)
matchTag := regexp.MustCompile("test:latest")
eventTag := make(chan bool)
matcher := func(text string) {
if matchTag.MatchString(text) {
close(eventTag)
out, _ := dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "type=image")
events := strings.Split(strings.TrimSpace(out), "\n")
actions := eventActionsByIDAndType(c, events, "test:latest", "image")
var foundTag bool
for _, a := range actions {
if a == "tag" {
foundTag = true
break
}
}
go observer.Match(matcher)
select {
case <-time.After(10 * time.Second):
c.Fatal(observer.TimeoutError("test:latest", "tag"))
case <-eventTag:
// We saw the tag event as expected.
}
c.Assert(foundTag, checker.True, check.Commentf("No tag event found:\n%s", out))
}
// #15780

Просмотреть файл

@ -3,12 +3,16 @@
package main
import (
"bufio"
"bytes"
"encoding/json"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/docker/docker/pkg/integration/checker"
"github.com/docker/go-units"
@ -115,5 +119,89 @@ func (s *DockerSuite) TestBuildAddChangeOwnership(c *check.C) {
if _, err := buildImageFromContext(name, ctx, true); err != nil {
c.Fatalf("build failed to complete for TestBuildAddChangeOwnership: %v", err)
}
}
// Test that an infinite sleep during a build is killed if the client disconnects.
// This test is fairly hairy because there are lots of ways to race.
// Strategy:
// * Monitor the output of docker events starting from before
// * Run a 1-year-long sleep from a docker build.
// * When docker events sees container start, close the "docker build" command
// * Wait for docker events to emit a dying event.
func (s *DockerSuite) TestBuildCancellationKillsSleep(c *check.C) {
testRequires(c, DaemonIsLinux)
name := "testbuildcancellation"
observer, err := newEventObserver(c)
c.Assert(err, checker.IsNil)
err = observer.Start()
c.Assert(err, checker.IsNil)
defer observer.Stop()
// (Note: one year, will never finish)
ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil)
if err != nil {
c.Fatal(err)
}
defer ctx.Close()
buildCmd := exec.Command(dockerBinary, "build", "-t", name, ".")
buildCmd.Dir = ctx.Dir
stdoutBuild, err := buildCmd.StdoutPipe()
if err := buildCmd.Start(); err != nil {
c.Fatalf("failed to run build: %s", err)
}
matchCID := regexp.MustCompile("Running in (.+)")
scanner := bufio.NewScanner(stdoutBuild)
outputBuffer := new(bytes.Buffer)
var buildID string
for scanner.Scan() {
line := scanner.Text()
outputBuffer.WriteString(line)
outputBuffer.WriteString("\n")
if matches := matchCID.FindStringSubmatch(line); len(matches) > 0 {
buildID = matches[1]
break
}
}
if buildID == "" {
c.Fatalf("Unable to find build container id in build output:\n%s", outputBuffer.String())
}
testActions := map[string]chan bool{
"start": make(chan bool),
"die": make(chan bool),
}
matcher := matchEventLine(buildID, "container", testActions)
go observer.Match(matcher)
select {
case <-time.After(10 * time.Second):
observer.CheckEventError(c, buildID, "start", matcher)
case <-testActions["start"]:
// ignore, done
}
// Send a kill to the `docker build` command.
// Causes the underlying build to be cancelled due to socket close.
if err := buildCmd.Process.Kill(); err != nil {
c.Fatalf("error killing build command: %s", err)
}
// Get the exit status of `docker build`, check it exited because killed.
if err := buildCmd.Wait(); err != nil && !isKilled(err) {
c.Fatalf("wait failed during build run: %T %s", err, err)
}
select {
case <-time.After(10 * time.Second):
observer.CheckEventError(c, buildID, "die", matcher)
case <-testActions["die"]:
// ignore, done
}
}

Просмотреть файл

@ -7,7 +7,6 @@ import (
"net/http"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"sync"
@ -67,22 +66,31 @@ func (s *DockerSuite) TestEventsUntag(c *check.C) {
}
func (s *DockerSuite) TestEventsContainerFailStartDie(c *check.C) {
out, _ := dockerCmd(c, "images", "-q")
image := strings.Split(out, "\n")[0]
_, _, err := dockerCmdWithError("run", "--name", "testeventdie", image, "blerg")
c.Assert(err, checker.NotNil, check.Commentf("Container run with command blerg should have failed, but it did not, out=%s", out))
out, _ = dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(out, "\n")
c.Assert(len(events), checker.GreaterThan, 1) //Missing expected event
events := strings.Split(strings.TrimSpace(out), "\n")
startEvent := strings.Fields(events[len(events)-3])
dieEvent := strings.Fields(events[len(events)-2])
nEvents := len(events)
c.Assert(nEvents, checker.GreaterOrEqualThan, 1) //Missing expected event
c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
c.Assert(dieEvent[len(dieEvent)-1], checker.Equals, "die", check.Commentf("event should be die, not %#v", dieEvent))
actions := eventActionsByIDAndType(c, events, "testeventdie", "container")
var startEvent bool
var dieEvent bool
for _, a := range actions {
switch a {
case "start":
startEvent = true
case "die":
dieEvent = true
}
}
c.Assert(startEvent, checker.True, check.Commentf("Start event not found: %v\n%v", actions, events))
c.Assert(dieEvent, checker.True, check.Commentf("Die event not found: %v\n%v", actions, events))
}
func (s *DockerSuite) TestEventsLimit(c *check.C) {
@ -114,65 +122,44 @@ func (s *DockerSuite) TestEventsLimit(c *check.C) {
func (s *DockerSuite) TestEventsContainerEvents(c *check.C) {
testRequires(c, DaemonIsLinux)
dockerCmd(c, "run", "--rm", "busybox", "true")
containerID, _ := dockerCmd(c, "run", "--rm", "--name", "container-events-test", "busybox", "true")
containerID = strings.TrimSpace(containerID)
out, _ := dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(out, "\n")
events = events[:len(events)-1]
c.Assert(len(events), checker.GreaterOrEqualThan, 5) //Missing expected event
createEvent := strings.Fields(events[len(events)-5])
attachEvent := strings.Fields(events[len(events)-4])
startEvent := strings.Fields(events[len(events)-3])
dieEvent := strings.Fields(events[len(events)-2])
destroyEvent := strings.Fields(events[len(events)-1])
c.Assert(createEvent[len(createEvent)-1], checker.Equals, "create", check.Commentf("event should be create, not %#v", createEvent))
c.Assert(attachEvent[len(attachEvent)-1], checker.Equals, "attach", check.Commentf("event should be attach, not %#v", attachEvent))
c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
c.Assert(dieEvent[len(dieEvent)-1], checker.Equals, "die", check.Commentf("event should be die, not %#v", dieEvent))
c.Assert(destroyEvent[len(destroyEvent)-1], checker.Equals, "destroy", check.Commentf("event should be destroy, not %#v", destroyEvent))
nEvents := len(events)
c.Assert(nEvents, checker.GreaterOrEqualThan, 5) //Missing expected event
containerEvents := eventActionsByIDAndType(c, events, "container-events-test", "container")
c.Assert(containerEvents, checker.HasLen, 5, check.Commentf("events: %v", events))
c.Assert(containerEvents[0], checker.Equals, "create", check.Commentf(out))
c.Assert(containerEvents[1], checker.Equals, "attach", check.Commentf(out))
c.Assert(containerEvents[2], checker.Equals, "start", check.Commentf(out))
c.Assert(containerEvents[3], checker.Equals, "die", check.Commentf(out))
c.Assert(containerEvents[4], checker.Equals, "destroy", check.Commentf(out))
}
func (s *DockerSuite) TestEventsContainerEventsSinceUnixEpoch(c *check.C) {
testRequires(c, DaemonIsLinux)
dockerCmd(c, "run", "--rm", "busybox", "true")
dockerCmd(c, "run", "--rm", "--name", "since-epoch-test", "busybox", "true")
timeBeginning := time.Unix(0, 0).Format(time.RFC3339Nano)
timeBeginning = strings.Replace(timeBeginning, "Z", ".000000000Z", -1)
out, _ := dockerCmd(c, "events", fmt.Sprintf("--since='%s'", timeBeginning),
fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
out, _ := dockerCmd(c, "events", fmt.Sprintf("--since='%s'", timeBeginning), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(out, "\n")
events = events[:len(events)-1]
c.Assert(len(events), checker.GreaterOrEqualThan, 5) //Missing expected event
createEvent := strings.Fields(events[len(events)-5])
attachEvent := strings.Fields(events[len(events)-4])
startEvent := strings.Fields(events[len(events)-3])
dieEvent := strings.Fields(events[len(events)-2])
destroyEvent := strings.Fields(events[len(events)-1])
c.Assert(createEvent[len(createEvent)-1], checker.Equals, "create", check.Commentf("event should be create, not %#v", createEvent))
c.Assert(attachEvent[len(attachEvent)-1], checker.Equals, "attach", check.Commentf("event should be attach, not %#v", attachEvent))
c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
c.Assert(dieEvent[len(dieEvent)-1], checker.Equals, "die", check.Commentf("event should be die, not %#v", dieEvent))
c.Assert(destroyEvent[len(destroyEvent)-1], checker.Equals, "destroy", check.Commentf("event should be destroy, not %#v", destroyEvent))
}
nEvents := len(events)
c.Assert(nEvents, checker.GreaterOrEqualThan, 5) //Missing expected event
containerEvents := eventActionsByIDAndType(c, events, "since-epoch-test", "container")
c.Assert(containerEvents, checker.HasLen, 5, check.Commentf("events: %v", events))
func (s *DockerSuite) TestEventsImageUntagDelete(c *check.C) {
testRequires(c, DaemonIsLinux)
name := "testimageevents"
_, err := buildImage(name,
`FROM scratch
MAINTAINER "docker"`,
true)
c.Assert(err, checker.IsNil)
c.Assert(deleteImages(name), checker.IsNil)
out, _ := dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(out, "\n")
events = events[:len(events)-1]
c.Assert(len(events), checker.GreaterOrEqualThan, 2) //Missing expected event
untagEvent := strings.Fields(events[len(events)-2])
deleteEvent := strings.Fields(events[len(events)-1])
c.Assert(untagEvent[len(untagEvent)-1], checker.Equals, "untag", check.Commentf("untag should be untag, not %#v", untagEvent))
c.Assert(deleteEvent[len(deleteEvent)-1], checker.Equals, "delete", check.Commentf("untag should be delete, not %#v", untagEvent))
c.Assert(containerEvents[0], checker.Equals, "create", check.Commentf(out))
c.Assert(containerEvents[1], checker.Equals, "attach", check.Commentf(out))
c.Assert(containerEvents[2], checker.Equals, "start", check.Commentf(out))
c.Assert(containerEvents[3], checker.Equals, "die", check.Commentf(out))
c.Assert(containerEvents[4], checker.Equals, "destroy", check.Commentf(out))
}
func (s *DockerSuite) TestEventsImageTag(c *check.C) {
@ -189,10 +176,10 @@ func (s *DockerSuite) TestEventsImageTag(c *check.C) {
events := strings.Split(strings.TrimSpace(out), "\n")
c.Assert(events, checker.HasLen, 1, check.Commentf("was expecting 1 event. out=%s", out))
event := strings.TrimSpace(events[0])
expectedStr := image + ": tag"
c.Assert(event, checker.HasSuffix, expectedStr, check.Commentf("wrong event format. expected='%s' got=%s", expectedStr, event))
matches := parseEventText(event)
c.Assert(matchEventID(matches, image), checker.True, check.Commentf("matches: %v\nout:\n%s", matches, out))
c.Assert(matches["action"], checker.Equals, "tag")
}
func (s *DockerSuite) TestEventsImagePull(c *check.C) {
@ -208,68 +195,45 @@ func (s *DockerSuite) TestEventsImagePull(c *check.C) {
events := strings.Split(strings.TrimSpace(out), "\n")
event := strings.TrimSpace(events[len(events)-1])
c.Assert(event, checker.HasSuffix, "hello-world:latest: pull", check.Commentf("Missing pull event - got:%q", event))
matches := parseEventText(event)
c.Assert(matches["id"], checker.Equals, "hello-world:latest")
c.Assert(matches["action"], checker.Equals, "pull")
}
func (s *DockerSuite) TestEventsImageImport(c *check.C) {
testRequires(c, DaemonIsLinux)
observer, err := newEventObserver(c)
c.Assert(err, checker.IsNil)
err = observer.Start()
c.Assert(err, checker.IsNil)
defer observer.Stop()
out, _ := dockerCmd(c, "run", "-d", "busybox", "true")
cleanedContainerID := strings.TrimSpace(out)
out, _, err = runCommandPipelineWithOutput(
since := daemonTime(c).Unix()
out, _, err := runCommandPipelineWithOutput(
exec.Command(dockerBinary, "export", cleanedContainerID),
exec.Command(dockerBinary, "import", "-"),
)
c.Assert(err, checker.IsNil, check.Commentf("import failed with output: %q", out))
imageRef := strings.TrimSpace(out)
eventImport := make(chan bool)
matchImport := regexp.MustCompile(imageRef + `: import\z`)
matcher := func(text string) {
if matchImport.MatchString(text) {
close(eventImport)
}
}
go observer.Match(matcher)
select {
case <-time.After(5 * time.Second):
c.Fatal(observer.TimeoutError(imageRef, "import"))
case <-eventImport:
// ignore, done
}
out, _ = dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=import")
events := strings.Split(strings.TrimSpace(out), "\n")
c.Assert(events, checker.HasLen, 1)
matches := parseEventText(events[0])
c.Assert(matches["id"], checker.Equals, imageRef, check.Commentf("matches: %v\nout:\n%s\n", matches, out))
c.Assert(matches["action"], checker.Equals, "import", check.Commentf("matches: %v\nout:\n%s\n", matches, out))
}
func (s *DockerSuite) TestEventsFilters(c *check.C) {
testRequires(c, DaemonIsLinux)
parseEvents := func(out, match string) {
events := strings.Split(out, "\n")
events = events[:len(events)-1]
for _, event := range events {
eventFields := strings.Fields(event)
eventName := eventFields[len(eventFields)-1]
c.Assert(eventName, checker.Matches, match)
}
}
since := daemonTime(c).Unix()
dockerCmd(c, "run", "--rm", "busybox", "true")
dockerCmd(c, "run", "--rm", "busybox", "true")
out, _ := dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=die")
parseEvents(out, "die")
parseEvents(c, out, "die")
out, _ = dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=die", "--filter", "event=start")
parseEvents(out, "((die)|(start))")
parseEvents(c, out, "die|start")
// make sure we at least got 2 start events
count := strings.Count(out, "start")
@ -355,7 +319,8 @@ func (s *DockerSuite) TestEventsFilterImageLabels(c *check.C) {
"events",
fmt.Sprintf("--since=%d", since),
fmt.Sprintf("--until=%d", daemonTime(c).Unix()),
"--filter", fmt.Sprintf("label=%s", label))
"--filter", fmt.Sprintf("label=%s", label),
"--filter", "type=image")
events := strings.Split(strings.TrimSpace(out), "\n")
@ -385,15 +350,9 @@ func (s *DockerSuite) TestEventsFilterContainer(c *check.C) {
return fmt.Errorf("expected 4 events, got %v", events)
}
for _, event := range events {
e := strings.Fields(event)
if len(e) < 3 {
return fmt.Errorf("got malformed event: %s", event)
}
// Check the id
parsedID := strings.TrimSuffix(e[1], ":")
if parsedID != id {
return fmt.Errorf("expected event for container id %s: %s - parsed container id: %s", id, event, parsedID)
matches := parseEventText(event)
if !matchEventID(matches, id) {
return fmt.Errorf("expected event for container id %s: %s - parsed container id: %s", id, event, matches["id"])
}
}
return nil
@ -412,72 +371,6 @@ func (s *DockerSuite) TestEventsFilterContainer(c *check.C) {
}
}
func (s *DockerSuite) TestEventsStreaming(c *check.C) {
testRequires(c, DaemonIsLinux)
eventCreate := make(chan struct{})
eventStart := make(chan struct{})
eventDie := make(chan struct{})
eventDestroy := make(chan struct{})
observer, err := newEventObserver(c)
c.Assert(err, checker.IsNil)
err = observer.Start()
c.Assert(err, checker.IsNil)
defer observer.Stop()
out, _ := dockerCmd(c, "run", "-d", "busybox:latest", "true")
containerID := strings.TrimSpace(out)
matchCreate := regexp.MustCompile(containerID + `: \(from busybox:latest\) create\z`)
matchStart := regexp.MustCompile(containerID + `: \(from busybox:latest\) start\z`)
matchDie := regexp.MustCompile(containerID + `: \(from busybox:latest\) die\z`)
matchDestroy := regexp.MustCompile(containerID + `: \(from busybox:latest\) destroy\z`)
matcher := func(text string) {
switch {
case matchCreate.MatchString(text):
close(eventCreate)
case matchStart.MatchString(text):
close(eventStart)
case matchDie.MatchString(text):
close(eventDie)
case matchDestroy.MatchString(text):
close(eventDestroy)
}
}
go observer.Match(matcher)
select {
case <-time.After(5 * time.Second):
c.Fatal(observer.TimeoutError(containerID, "create"))
case <-eventCreate:
// ignore, done
}
select {
case <-time.After(5 * time.Second):
c.Fatal(observer.TimeoutError(containerID, "start"))
case <-eventStart:
// ignore, done
}
select {
case <-time.After(5 * time.Second):
c.Fatal(observer.TimeoutError(containerID, "die"))
case <-eventDie:
// ignore, done
}
dockerCmd(c, "rm", containerID)
select {
case <-time.After(5 * time.Second):
c.Fatal(observer.TimeoutError(containerID, "destroy"))
case <-eventDestroy:
// ignore, done
}
}
func (s *DockerSuite) TestEventsCommit(c *check.C) {
testRequires(c, DaemonIsLinux)
since := daemonTime(c).Unix()
@ -490,7 +383,7 @@ func (s *DockerSuite) TestEventsCommit(c *check.C) {
dockerCmd(c, "stop", cID)
out, _ = dockerCmd(c, "events", "--since=0", "-f", "container="+cID, "--until="+strconv.Itoa(int(since)))
c.Assert(out, checker.Contains, " commit\n", check.Commentf("Missing 'commit' log event"))
c.Assert(out, checker.Contains, "commit", check.Commentf("Missing 'commit' log event"))
}
func (s *DockerSuite) TestEventsCopy(c *check.C) {
@ -515,12 +408,12 @@ func (s *DockerSuite) TestEventsCopy(c *check.C) {
dockerCmd(c, "cp", "cptest:/tmp/file", tempFile.Name())
out, _ := dockerCmd(c, "events", "--since=0", "-f", "container=cptest", "--until="+strconv.Itoa(int(since)))
c.Assert(out, checker.Contains, " archive-path\n", check.Commentf("Missing 'archive-path' log event\n"))
c.Assert(out, checker.Contains, "archive-path", check.Commentf("Missing 'archive-path' log event\n"))
dockerCmd(c, "cp", tempFile.Name(), "cptest:/tmp/filecopy")
out, _ = dockerCmd(c, "events", "--since=0", "-f", "container=cptest", "--until="+strconv.Itoa(int(since)))
c.Assert(out, checker.Contains, " extract-to-dir\n", check.Commentf("Missing 'extract-to-dir' log event"))
c.Assert(out, checker.Contains, "extract-to-dir", check.Commentf("Missing 'extract-to-dir' log event"))
}
func (s *DockerSuite) TestEventsResize(c *check.C) {
@ -539,7 +432,7 @@ func (s *DockerSuite) TestEventsResize(c *check.C) {
dockerCmd(c, "stop", cID)
out, _ = dockerCmd(c, "events", "--since=0", "-f", "container="+cID, "--until="+strconv.Itoa(int(since)))
c.Assert(out, checker.Contains, " resize\n", check.Commentf("Missing 'resize' log event"))
c.Assert(out, checker.Contains, "resize", check.Commentf("Missing 'resize' log event"))
}
func (s *DockerSuite) TestEventsAttach(c *check.C) {
@ -571,7 +464,7 @@ func (s *DockerSuite) TestEventsAttach(c *check.C) {
dockerCmd(c, "stop", cID)
out, _ = dockerCmd(c, "events", "--since=0", "-f", "container="+cID, "--until="+strconv.Itoa(int(since)))
c.Assert(out, checker.Contains, " attach\n", check.Commentf("Missing 'attach' log event"))
c.Assert(out, checker.Contains, "attach", check.Commentf("Missing 'attach' log event"))
}
func (s *DockerSuite) TestEventsRename(c *check.C) {
@ -582,7 +475,7 @@ func (s *DockerSuite) TestEventsRename(c *check.C) {
dockerCmd(c, "rename", "oldName", "newName")
out, _ := dockerCmd(c, "events", "--since=0", "-f", "container=newName", "--until="+strconv.Itoa(int(since)))
c.Assert(out, checker.Contains, " rename\n", check.Commentf("Missing 'rename' log event\n"))
c.Assert(out, checker.Contains, "rename", check.Commentf("Missing 'rename' log event\n"))
}
func (s *DockerSuite) TestEventsTop(c *check.C) {
@ -597,7 +490,7 @@ func (s *DockerSuite) TestEventsTop(c *check.C) {
dockerCmd(c, "stop", cID)
out, _ = dockerCmd(c, "events", "--since=0", "-f", "container="+cID, "--until="+strconv.Itoa(int(since)))
c.Assert(out, checker.Contains, " top\n", check.Commentf("Missing 'top' log event"))
c.Assert(out, checker.Contains, " top", check.Commentf("Missing 'top' log event"))
}
// #13753
@ -624,5 +517,71 @@ func (s *DockerRegistrySuite) TestEventsImageFilterPush(c *check.C) {
dockerCmd(c, "push", repoName)
out, _ = dockerCmd(c, "events", "--since=0", "-f", "image="+repoName, "-f", "event=push", "--until="+strconv.Itoa(int(since)))
c.Assert(out, checker.Contains, repoName+": push\n", check.Commentf("Missing 'push' log event"))
c.Assert(out, checker.Contains, repoName, check.Commentf("Missing 'push' log event for %s", repoName))
}
func (s *DockerSuite) TestEventsFilterType(c *check.C) {
testRequires(c, DaemonIsLinux)
since := daemonTime(c).Unix()
name := "labelfiltertest"
label := "io.docker.testing=image"
// Build a test image.
_, err := buildImage(name, fmt.Sprintf(`
FROM busybox:latest
LABEL %s`, label), true)
c.Assert(err, checker.IsNil, check.Commentf("Couldn't create image"))
dockerCmd(c, "tag", name, "labelfiltertest:tag1")
dockerCmd(c, "tag", name, "labelfiltertest:tag2")
dockerCmd(c, "tag", "busybox:latest", "labelfiltertest:tag3")
out, _ := dockerCmd(
c,
"events",
fmt.Sprintf("--since=%d", since),
fmt.Sprintf("--until=%d", daemonTime(c).Unix()),
"--filter", fmt.Sprintf("label=%s", label),
"--filter", "type=image")
events := strings.Split(strings.TrimSpace(out), "\n")
// 2 events from the "docker tag" command, another one is from "docker build"
c.Assert(events, checker.HasLen, 3, check.Commentf("Events == %s", events))
for _, e := range events {
c.Assert(e, checker.Contains, "labelfiltertest")
}
out, _ = dockerCmd(
c,
"events",
fmt.Sprintf("--since=%d", since),
fmt.Sprintf("--until=%d", daemonTime(c).Unix()),
"--filter", fmt.Sprintf("label=%s", label),
"--filter", "type=container")
events = strings.Split(strings.TrimSpace(out), "\n")
// Events generated by the container that builds the image
c.Assert(events, checker.HasLen, 3, check.Commentf("Events == %s", events))
out, _ = dockerCmd(
c,
"events",
fmt.Sprintf("--since=%d", since),
fmt.Sprintf("--until=%d", daemonTime(c).Unix()),
"--filter", "type=network")
events = strings.Split(strings.TrimSpace(out), "\n")
c.Assert(len(events), checker.GreaterOrEqualThan, 1, check.Commentf("Events == %s", events))
}
func (s *DockerSuite) TestEventsFilterImageInContainerAction(c *check.C) {
testRequires(c, DaemonIsLinux)
since := daemonTime(c).Unix()
dockerCmd(c, "run", "--name", "test-container", "-d", "busybox", "true")
waitRun("test-container")
out, _ := dockerCmd(c, "events", "--filter", "image=busybox", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(strings.TrimSpace(out), "\n")
c.Assert(len(events), checker.GreaterThan, 1, check.Commentf(out))
}

Просмотреть файл

@ -65,18 +65,14 @@ func (s *DockerSuite) TestEventsOOMDisableFalse(c *check.C) {
out, _ := dockerCmd(c, "events", "--since=0", "-f", "container=oomFalse", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(strings.TrimSuffix(out, "\n"), "\n")
c.Assert(len(events), checker.GreaterOrEqualThan, 5) //Missing expected event
nEvents := len(events)
createEvent := strings.Fields(events[len(events)-5])
attachEvent := strings.Fields(events[len(events)-4])
startEvent := strings.Fields(events[len(events)-3])
oomEvent := strings.Fields(events[len(events)-2])
dieEvent := strings.Fields(events[len(events)-1])
c.Assert(createEvent[len(createEvent)-1], checker.Equals, "create", check.Commentf("event should be create, not %#v", createEvent))
c.Assert(attachEvent[len(attachEvent)-1], checker.Equals, "attach", check.Commentf("event should be attach, not %#v", attachEvent))
c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
c.Assert(oomEvent[len(oomEvent)-1], checker.Equals, "oom", check.Commentf("event should be oom, not %#v", oomEvent))
c.Assert(dieEvent[len(dieEvent)-1], checker.Equals, "die", check.Commentf("event should be die, not %#v", dieEvent))
c.Assert(nEvents, checker.GreaterOrEqualThan, 5) //Missing expected event
c.Assert(parseEventAction(c, events[nEvents-5]), checker.Equals, "create")
c.Assert(parseEventAction(c, events[nEvents-4]), checker.Equals, "attach")
c.Assert(parseEventAction(c, events[nEvents-3]), checker.Equals, "start")
c.Assert(parseEventAction(c, events[nEvents-2]), checker.Equals, "oom")
c.Assert(parseEventAction(c, events[nEvents-1]), checker.Equals, "die")
}
func (s *DockerSuite) TestEventsOOMDisableTrue(c *check.C) {
@ -98,19 +94,252 @@ func (s *DockerSuite) TestEventsOOMDisableTrue(c *check.C) {
out, _ := dockerCmd(c, "events", "--since=0", "-f", "container=oomTrue", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(strings.TrimSuffix(out, "\n"), "\n")
c.Assert(len(events), checker.GreaterOrEqualThan, 4) //Missing expected event
nEvents := len(events)
c.Assert(nEvents, checker.GreaterOrEqualThan, 4) //Missing expected event
createEvent := strings.Fields(events[len(events)-4])
attachEvent := strings.Fields(events[len(events)-3])
startEvent := strings.Fields(events[len(events)-2])
oomEvent := strings.Fields(events[len(events)-1])
c.Assert(createEvent[len(createEvent)-1], checker.Equals, "create", check.Commentf("event should be create, not %#v", createEvent))
c.Assert(attachEvent[len(attachEvent)-1], checker.Equals, "attach", check.Commentf("event should be attach, not %#v", attachEvent))
c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
c.Assert(oomEvent[len(oomEvent)-1], checker.Equals, "oom", check.Commentf("event should be oom, not %#v", oomEvent))
c.Assert(parseEventAction(c, events[nEvents-4]), checker.Equals, "create")
c.Assert(parseEventAction(c, events[nEvents-3]), checker.Equals, "attach")
c.Assert(parseEventAction(c, events[nEvents-2]), checker.Equals, "start")
c.Assert(parseEventAction(c, events[nEvents-1]), checker.Equals, "oom")
out, _ = dockerCmd(c, "inspect", "-f", "{{.State.Status}}", "oomTrue")
c.Assert(strings.TrimSpace(out), checker.Equals, "running", check.Commentf("container should be still running"))
}
}
// #18453
func (s *DockerSuite) TestEventsContainerFilterByName(c *check.C) {
testRequires(c, DaemonIsLinux)
cOut, _ := dockerCmd(c, "run", "--name=foo", "-d", "busybox", "top")
c1 := strings.TrimSpace(cOut)
waitRun("foo")
cOut, _ = dockerCmd(c, "run", "--name=bar", "-d", "busybox", "top")
c2 := strings.TrimSpace(cOut)
waitRun("bar")
out, _ := dockerCmd(c, "events", "-f", "container=foo", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
c.Assert(out, checker.Contains, c1, check.Commentf(out))
c.Assert(out, checker.Not(checker.Contains), c2, check.Commentf(out))
}
// #18453
func (s *DockerSuite) TestEventsContainerFilterBeforeCreate(c *check.C) {
testRequires(c, DaemonIsLinux)
var (
out string
ch chan struct{}
)
ch = make(chan struct{})
// calculate the time it takes to create and start a container and sleep 2 seconds
// this is to make sure the docker event will recevie the event of container
since := daemonTime(c).Unix()
id, _ := dockerCmd(c, "run", "-d", "busybox", "top")
cID := strings.TrimSpace(id)
waitRun(cID)
time.Sleep(2 * time.Second)
duration := daemonTime(c).Unix() - since
go func() {
out, _ = dockerCmd(c, "events", "-f", "container=foo", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()+2*duration))
close(ch)
}()
// Sleep 2 second to wait docker event to start
time.Sleep(2 * time.Second)
id, _ = dockerCmd(c, "run", "--name=foo", "-d", "busybox", "top")
cID = strings.TrimSpace(id)
waitRun(cID)
<-ch
c.Assert(out, checker.Contains, cID, check.Commentf("Missing event of container (foo)"))
}
func (s *DockerSuite) TestVolumeEvents(c *check.C) {
testRequires(c, DaemonIsLinux)
since := daemonTime(c).Unix()
// Observe create/mount volume actions
dockerCmd(c, "volume", "create", "--name", "test-event-volume-local")
dockerCmd(c, "run", "--name", "test-volume-container", "--volume", "test-event-volume-local:/foo", "-d", "busybox", "true")
waitRun("test-volume-container")
// Observe unmount/destroy volume actions
dockerCmd(c, "rm", "-f", "test-volume-container")
dockerCmd(c, "volume", "rm", "test-event-volume-local")
out, _ := dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(strings.TrimSpace(out), "\n")
c.Assert(len(events), checker.GreaterThan, 4)
volumeEvents := eventActionsByIDAndType(c, events, "test-event-volume-local", "volume")
c.Assert(volumeEvents, checker.HasLen, 4)
c.Assert(volumeEvents[0], checker.Equals, "create")
c.Assert(volumeEvents[1], checker.Equals, "mount")
c.Assert(volumeEvents[2], checker.Equals, "unmount")
c.Assert(volumeEvents[3], checker.Equals, "destroy")
}
func (s *DockerSuite) TestNetworkEvents(c *check.C) {
testRequires(c, DaemonIsLinux)
since := daemonTime(c).Unix()
// Observe create/connect network actions
dockerCmd(c, "network", "create", "test-event-network-local")
dockerCmd(c, "run", "--name", "test-network-container", "--net", "test-event-network-local", "-d", "busybox", "true")
waitRun("test-network-container")
// Observe disconnect/destroy network actions
dockerCmd(c, "rm", "-f", "test-network-container")
dockerCmd(c, "network", "rm", "test-event-network-local")
out, _ := dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(strings.TrimSpace(out), "\n")
c.Assert(len(events), checker.GreaterThan, 4)
netEvents := eventActionsByIDAndType(c, events, "test-event-network-local", "network")
c.Assert(netEvents, checker.HasLen, 4)
c.Assert(netEvents[0], checker.Equals, "create")
c.Assert(netEvents[1], checker.Equals, "connect")
c.Assert(netEvents[2], checker.Equals, "disconnect")
c.Assert(netEvents[3], checker.Equals, "destroy")
}
func (s *DockerSuite) TestEventsStreaming(c *check.C) {
testRequires(c, DaemonIsLinux)
observer, err := newEventObserver(c)
c.Assert(err, checker.IsNil)
err = observer.Start()
c.Assert(err, checker.IsNil)
defer observer.Stop()
out, _ := dockerCmd(c, "run", "-d", "busybox:latest", "true")
containerID := strings.TrimSpace(out)
testActions := map[string]chan bool{
"create": make(chan bool),
"start": make(chan bool),
"die": make(chan bool),
"destroy": make(chan bool),
}
matcher := matchEventLine(containerID, "container", testActions)
go observer.Match(matcher)
select {
case <-time.After(5 * time.Second):
observer.CheckEventError(c, containerID, "create", matcher)
case <-testActions["create"]:
// ignore, done
}
select {
case <-time.After(5 * time.Second):
observer.CheckEventError(c, containerID, "start", matcher)
case <-testActions["start"]:
// ignore, done
}
select {
case <-time.After(5 * time.Second):
observer.CheckEventError(c, containerID, "die", matcher)
case <-testActions["die"]:
// ignore, done
}
dockerCmd(c, "rm", containerID)
select {
case <-time.After(5 * time.Second):
observer.CheckEventError(c, containerID, "destroy", matcher)
case <-testActions["destroy"]:
// ignore, done
}
}
func (s *DockerSuite) TestEventsImageUntagDelete(c *check.C) {
testRequires(c, DaemonIsLinux)
observer, err := newEventObserver(c)
c.Assert(err, checker.IsNil)
err = observer.Start()
c.Assert(err, checker.IsNil)
defer observer.Stop()
name := "testimageevents"
imageID, err := buildImage(name,
`FROM scratch
MAINTAINER "docker"`,
true)
c.Assert(err, checker.IsNil)
c.Assert(deleteImages(name), checker.IsNil)
testActions := map[string]chan bool{
"untag": make(chan bool),
"delete": make(chan bool),
}
matcher := matchEventLine(imageID, "image", testActions)
go observer.Match(matcher)
select {
case <-time.After(10 * time.Second):
observer.CheckEventError(c, imageID, "untag", matcher)
case <-testActions["untag"]:
// ignore, done
}
select {
case <-time.After(10 * time.Second):
observer.CheckEventError(c, imageID, "delete", matcher)
case <-testActions["delete"]:
// ignore, done
}
}
func (s *DockerSuite) TestEventsFilterVolumeAndNetworkType(c *check.C) {
testRequires(c, DaemonIsLinux)
since := daemonTime(c).Unix()
dockerCmd(c, "network", "create", "test-event-network-type")
dockerCmd(c, "volume", "create", "--name", "test-event-volume-type")
out, _ := dockerCmd(c, "events", "--filter", "type=volume", "--filter", "type=network", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(strings.TrimSpace(out), "\n")
c.Assert(len(events), checker.GreaterOrEqualThan, 2, check.Commentf(out))
networkActions := eventActionsByIDAndType(c, events, "test-event-network-type", "network")
volumeActions := eventActionsByIDAndType(c, events, "test-event-volume-type", "volume")
c.Assert(volumeActions[0], checker.Equals, "create")
c.Assert(networkActions[0], checker.Equals, "create")
}
func (s *DockerSuite) TestEventsFilterVolumeID(c *check.C) {
testRequires(c, DaemonIsLinux)
since := daemonTime(c).Unix()
dockerCmd(c, "volume", "create", "--name", "test-event-volume-id")
out, _ := dockerCmd(c, "events", "--filter", "volume=test-event-volume-id", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(strings.TrimSpace(out), "\n")
c.Assert(events, checker.HasLen, 1)
c.Assert(events[0], checker.Contains, "test-event-volume-id")
c.Assert(events[0], checker.Contains, "driver=local")
}
func (s *DockerSuite) TestEventsFilterNetworkID(c *check.C) {
testRequires(c, DaemonIsLinux)
since := daemonTime(c).Unix()
dockerCmd(c, "network", "create", "test-event-network-local")
out, _ := dockerCmd(c, "events", "--filter", "network=test-event-network-local", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(strings.TrimSpace(out), "\n")
c.Assert(events, checker.HasLen, 1)
c.Assert(events[0], checker.Contains, "test-event-network-local")
c.Assert(events[0], checker.Contains, "type=bridge")
}

Просмотреть файл

@ -23,15 +23,11 @@ func (s *DockerSuite) TestPause(c *check.C) {
dockerCmd(c, "unpause", name)
out, _ := dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(out, "\n")
c.Assert(len(events) > 1, checker.Equals, true)
pauseEvent := strings.Fields(events[len(events)-3])
unpauseEvent := strings.Fields(events[len(events)-2])
c.Assert(pauseEvent[len(pauseEvent)-1], checker.Equals, "pause")
c.Assert(unpauseEvent[len(unpauseEvent)-1], checker.Equals, "unpause")
events := strings.Split(strings.TrimSpace(out), "\n")
actions := eventActionsByIDAndType(c, events, name, "container")
c.Assert(actions[len(actions)-2], checker.Equals, "pause")
c.Assert(actions[len(actions)-1], checker.Equals, "unpause")
}
func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) {
@ -53,21 +49,12 @@ func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) {
dockerCmd(c, append([]string{"unpause"}, containers...)...)
out, _ := dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
events := strings.Split(out, "\n")
c.Assert(len(events) > len(containers)*3-2, checker.Equals, true)
events := strings.Split(strings.TrimSpace(out), "\n")
pauseEvents := make([][]string, len(containers))
unpauseEvents := make([][]string, len(containers))
for i := range containers {
pauseEvents[i] = strings.Fields(events[len(events)-len(containers)*2-1+i])
unpauseEvents[i] = strings.Fields(events[len(events)-len(containers)-1+i])
}
for _, name := range containers {
actions := eventActionsByIDAndType(c, events, name, "container")
for _, pauseEvent := range pauseEvents {
c.Assert(pauseEvent[len(pauseEvent)-1], checker.Equals, "pause")
c.Assert(actions[len(actions)-2], checker.Equals, "pause")
c.Assert(actions[len(actions)-1], checker.Equals, "unpause")
}
for _, unpauseEvent := range unpauseEvents {
c.Assert(unpauseEvent[len(unpauseEvent)-1], checker.Equals, "unpause")
}
}

Просмотреть файл

@ -6,27 +6,50 @@ import (
"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.
type eventMatcher func(text string)
type eventMatcher func(text string) bool
// eventObserver runs an events commands and observes its output.
type eventObserver struct {
buffer *bytes.Buffer
command *exec.Cmd
stdout io.Reader
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...)
}
cmdArgs := []string{"events", "--since", strconv.FormatInt(since, 10)}
// 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...)
}
@ -37,9 +60,10 @@ func newEventObserver(c *check.C, args ...string) (*eventObserver, error) {
}
return &eventObserver{
buffer: new(bytes.Buffer),
command: eventsCmd,
stdout: stdout,
buffer: new(bytes.Buffer),
command: eventsCmd,
scanner: bufio.NewScanner(stdout),
startTime: startTime,
}, nil
}
@ -51,28 +75,144 @@ func (e *eventObserver) Start() error {
// 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) {
scanner := bufio.NewScanner(e.stdout)
for scanner.Scan() {
text := scanner.Text()
for e.scanner.Scan() {
text := e.scanner.Text()
e.buffer.WriteString(text)
e.buffer.WriteString("\n")
match(text)
}
err := e.scanner.Err()
if err == nil {
err = io.EOF
}
logrus.Debug("EventObserver scanner loop finished: %v", err)
e.disconnectionError = err
}
// TimeoutError generates an error for a given containerID and event type.
// It attaches the events command output to the error.
func (e *eventObserver) TimeoutError(id, event string) error {
return fmt.Errorf("failed to observe event `%s` for %s\n%v", event, id, e.output())
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 match(e) {
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)
}
}
// output returns the events command output read until now by the Match goroutine.
func (e *eventObserver) output() string {
return e.buffer.String()
// matchEventLine matches a text with the event regular expression.
// It returns the action and true if the regular expression matches with the given id and event type.
// It returns an empty string and false if there is no match.
func matchEventLine(id, eventType string, actions map[string]chan bool) eventMatcher {
return func(text string) bool {
matches := parseEventText(text)
if len(matches) == 0 {
return false
}
if matchIDAndEventType(matches, id, eventType) {
if ch, ok := actions[matches["action"]]; ok {
close(ch)
return true
}
}
return false
}
}
// 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"]))
}
}