Skip to content

Commit

Permalink
feature: events service logic code of all links
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Wan <[email protected]>
  • Loading branch information
HusterWan committed Aug 10, 2018
1 parent 9482e6a commit d97bb02
Show file tree
Hide file tree
Showing 45 changed files with 8,538 additions and 34 deletions.
1 change: 1 addition & 0 deletions apis/server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func initRoute(s *Server) http.Handler {
s.addRoute(r, http.MethodGet, "/info", s.info)
s.addRoute(r, http.MethodGet, "/version", s.version)
s.addRoute(r, http.MethodPost, "/auth", s.auth)
s.addRoute(r, http.MethodGet, "/events", s.events)

// daemon, we still list this API into system manager.
s.addRoute(r, http.MethodPost, "/daemon/update", s.updateDaemon)
Expand Down
70 changes: 70 additions & 0 deletions apis/server/system_bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@ package server
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"

"github.com/alibaba/pouch/apis/filters"
"github.com/alibaba/pouch/apis/types"
"github.com/alibaba/pouch/pkg/httputils"
"github.com/alibaba/pouch/pkg/ioutils"

timetypes "github.com/docker/engine-api/types/time"
"github.com/pkg/errors"
)

func (s *Server) ping(context context.Context, rw http.ResponseWriter, req *http.Request) (err error) {
Expand Down Expand Up @@ -60,3 +67,66 @@ func (s *Server) auth(ctx context.Context, rw http.ResponseWriter, req *http.Req
}
return EncodeResponse(rw, http.StatusOK, authResp)
}

func (s *Server) events(ctx context.Context, rw http.ResponseWriter, req *http.Request) (err error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()

rw.Header().Set("Content-Type", "application/json")
output := ioutils.NewWriteFlusher(rw)
defer output.Close()
output.Flush()
enc := json.NewEncoder(output)

// TODO(ziren): support since and until parameters
until, err := eventTime(req.Form.Get("until"))
if err != nil {
return err
}

var (
timeout <-chan time.Time
)
if !until.IsZero() {
now := time.Now()
if until.Before(now) {
return fmt.Errorf("`until` time (%s) cannot be after now()", req.Form.Get("until"))
}

dur := until.Sub(now)
timeout = time.NewTimer(dur).C
}

ef, err := filters.FromParam(req.FormValue("filters"))
if err != nil {
return err
}

eventq, errq := s.SystemMgr.SubscribeToEvents(ctx, ef)
for {
select {
case ev := <-eventq:
if err := enc.Encode(ev); err != nil {
return err
}
case err := <-errq:
if err != nil {
return errors.Wrapf(err, "subscribe failed")
}
return nil
case <-timeout:
return nil
}
}
}

func eventTime(formTime string) (time.Time, error) {
t, tNano, err := timetypes.ParseTimestamps(formTime, -1)
if err != nil {
return time.Time{}, err
}
if t == -1 {
return time.Time{}, nil
}
return time.Unix(t, tNano), nil
}
46 changes: 46 additions & 0 deletions apis/swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,52 @@ paths:
schema:
$ref: "#/definitions/DaemonUpdateConfig"

/events:
get:
summary: "Subscribe pouchd events to users"
description: |
Stream real-time events from the server.
Report various object events of pouchd when something happens to them.
Containers report these events: `attach`, create`, `destroy`, `detach`, `die`, `exec_create`, `exec_start`, `exec_die`, `oom`, `pause`, `rename`, `resize`, `restart`, `start`, `stop`, `top`, `unpause`, and `update`
Images report these events: `delete`, `import`, `load`, `pull`, `push`, `save`, `tag`, and `untag`
Volumes report these events: `create`, `mount`, `unmount`, and `destroy`
Networks report these events: `create`, `connect`, `disconnect`, `destroy`, `update`, and `remove`
produces:
- "application/json"
responses:
200:
description: "no error"
schema:
$ref: '#definitions/EventsMessage'
400:
description: "bad parameter"
schema:
$ref: '#/definitions/Error'
500:
$ref: "#/responses/500ErrorResponse"
parameters:
- name: "since"
in: "query"
description: "Show events created since this timestamp then stream new events."
type: "string"
- name: "until"
in: "query"
description: "Show events created until this timestamp then stop streaming"
type: "string"
- name: "filters"
in: "query"
description: |
A JSON encoded value of filters (a `map[string][]string`) to process on the event list. Available filters:
- `container=<string>` container name or ID
- `event=<string>` event type
- `image=<string>` image name or ID
- `label=<string>` image or container label
- `network=<string>` network name or ID
- `type=<string>` object to filter by, one of `container`, `image`, `volume`, `network`
- `volume=<string>` volume name
type: "string"


/images/create:
post:
summary: "Create an image by pulling from a registry or importing from an existing source file"
Expand Down
142 changes: 142 additions & 0 deletions cli/events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package main

import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"sort"
"strings"
"time"

"github.com/alibaba/pouch/apis/filters"
"github.com/alibaba/pouch/apis/types"
"github.com/spf13/cobra"
)

// eventsDescription is used to describe events command in detail and auto generate command doc.
var eventsDescription = ""

// EventsCommand use to implement 'events' command.
type EventsCommand struct {
baseCommand
since string
until string
filter []string
}

// Init initialize events command.
func (e *EventsCommand) Init(c *Cli) {
e.cli = c
e.cmd = &cobra.Command{
Use: "events [OPTIONS]",
Short: "Get real time events from the daemon",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return e.runEvents()
},
Example: eventsExample(),
}
e.addFlags()
}

// addFlags adds flags for specific command.
func (e *EventsCommand) addFlags() {
flagSet := e.cmd.Flags()

flagSet.StringVarP(&e.since, "since", "s", "", "Show all events created since timestamp")
flagSet.StringVarP(&e.until, "until", "u", "", "Stream events until this timestamp")
flagSet.StringSliceVarP(&e.filter, "filter", "f", []string{}, "Filter output based on conditions provided")
}

// runEvents is the entry of events command.
func (e *EventsCommand) runEvents() error {
ctx := context.Background()
apiClient := e.cli.Client()

eventFilterArgs := filters.NewArgs()

// TODO: parse params
for _, f := range e.filter {
var err error
eventFilterArgs, err = filters.ParseFlag(f, eventFilterArgs)
if err != nil {
return err
}
}

responseBody, err := apiClient.Events(ctx, e.since, e.until, eventFilterArgs)
if err != nil {
return err
}

return streamEvents(responseBody, os.Stdout)
}

// streamEvents decodes prints the incoming events in the provided output.
func streamEvents(input io.Reader, output io.Writer) error {
return DecodeEvents(input, func(event types.EventsMessage, err error) error {
if err != nil {
return err
}
printOutput(event, output)
return nil
})
}

type eventProcessor func(event types.EventsMessage, err error) error

// 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 types.EventsMessage, output io.Writer) {
// RFC3339NanoFixed is our own version of RFC339Nano because we want one
// that pads the nano seconds part with zeros to ensure
// the timestamps are aligned in the logs.
RFC3339NanoFixed := "2006-01-02T15:04:05.000000000Z07:00"

if event.TimeNano != 0 {
fmt.Fprintf(output, "%s ", time.Unix(0, event.TimeNano).Format(RFC3339NanoFixed))
} else if event.Time != 0 {
fmt.Fprintf(output, "%s ", time.Unix(event.Time, 0).Format(RFC3339NanoFixed))
}

fmt.Fprintf(output, "%s %s %s", event.Type, event.Action, event.Actor.ID)

if len(event.Actor.Attributes) > 0 {
var attrs []string
var keys []string
for k := range event.Actor.Attributes {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := event.Actor.Attributes[k]
attrs = append(attrs, fmt.Sprintf("%s=%s", k, v))
}
fmt.Fprintf(output, " (%s)", strings.Join(attrs, ", "))
}
fmt.Fprint(output, "\n")
}

// DecodeEvents decodes event from input stream
func DecodeEvents(input io.Reader, ep eventProcessor) error {
dec := json.NewDecoder(input)
for {
var event types.EventsMessage
err := dec.Decode(&event)
if err != nil && err == io.EOF {
break
}

if procErr := ep(event, err); procErr != nil {
return procErr
}
}
return nil
}

func eventsExample() string {
return ""
}
1 change: 1 addition & 0 deletions cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func main() {
cli.AddCommand(base, &RemountLxcfsCommand{})
cli.AddCommand(base, &WaitCommand{})
cli.AddCommand(base, &DaemonUpdateCommand{})
cli.AddCommand(base, &EventsCommand{})

// add generate doc command
cli.AddCommand(base, &GenDocCommand{})
Expand Down
2 changes: 2 additions & 0 deletions client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"net"

"github.com/alibaba/pouch/apis/filters"
"github.com/alibaba/pouch/apis/types"
)

Expand Down Expand Up @@ -68,6 +69,7 @@ type SystemAPIClient interface {
SystemInfo(ctx context.Context) (*types.SystemInfo, error)
RegistryLogin(ctx context.Context, auth *types.AuthConfig) (*types.AuthResponse, error)
DaemonUpdate(ctx context.Context, daemonConfig *types.DaemonUpdateConfig) error
Events(ctx context.Context, since string, until string, filters filters.Args) (io.ReadCloser, error)
}

// NetworkAPIClient defines methods of Network client.
Expand Down
44 changes: 44 additions & 0 deletions client/system_events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package client

import (
"context"
"io"
"net/url"
"time"

"github.com/alibaba/pouch/apis/filters"

timetypes "github.com/docker/engine-api/types/time"
)

// Events returns a stream of events in the daemon in a ReadClosed.
// It's up to the caller to close the stream.
func (client *APIClient) Events(ctx context.Context, since string, until string, f filters.Args) (io.ReadCloser, error) {
query := url.Values{}
now := time.Now()

// TODO: parse since and until params
if until != "" {
ts, err := timetypes.GetTimestamp(until, now)
if err != nil {
return nil, err
}
query.Set("until", ts)
}

if f.Len() > 0 {
filtersJSON, err := filters.ToParam(f)
if err != nil {
return nil, err
}

query.Set("filters", filtersJSON)
}

resp, err := client.get(ctx, "/events", query, nil)
if err != nil {
return nil, err
}

return resp.Body, nil
}
1 change: 1 addition & 0 deletions client/system_events_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package client
Loading

0 comments on commit d97bb02

Please sign in to comment.