зеркало из https://github.com/microsoft/docker.git
Merge pull request #18888 from calavera/event_types
Event all the things!
This commit is contained in:
Коммит
723be0a332
|
@ -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 {
|
||||
|
|
|
@ -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 machine’s 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"]))
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче