Skip to content

Commit

Permalink
Merge pull request #2053 from HusterWan/zr/support-event
Browse files Browse the repository at this point in the history
feature: support event service
  • Loading branch information
Wei Fu authored Aug 9, 2018
2 parents 144be3f + c72af5e commit a3ce826
Show file tree
Hide file tree
Showing 10 changed files with 967 additions and 0 deletions.
152 changes: 152 additions & 0 deletions apis/filters/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package filters

import (
"encoding/json"
"errors"
"strings"
)

// Args stores filter arguments as map key:{map key: bool}.
// It contains an aggregation of the map of arguments (which are in the form
// of -f 'key=value') based on the key, and stores values for the same key
// in a map with string keys and boolean values.
// e.g given -f 'label=label1=1' -f 'label=label2=2' -f 'image.name=ubuntu'
// the args will be {"image.name":{"ubuntu":true},"label":{"label1=1":true,"label2=2":true}}
type Args struct {
fields map[string]map[string]bool
}

// KeyValuePair is used to initialize a new Args
type KeyValuePair struct {
Key string
Value string
}

// Arg creates a new KeyValuePair for initializing Args
func Arg(key, value string) KeyValuePair {
return KeyValuePair{Key: key, Value: value}
}

// NewArgs returns a new Args populated with the initial args
func NewArgs(initialArgs ...KeyValuePair) Args {
args := Args{fields: map[string]map[string]bool{}}
for _, arg := range initialArgs {
args.Add(arg.Key, arg.Value)
}
return args
}

// Get returns the list of values associated with the key
func (args Args) Get(key string) []string {
values := args.fields[key]
if values == nil {
return make([]string, 0)
}
slice := make([]string, 0, len(values))
for key := range values {
slice = append(slice, key)
}
return slice
}

// Add a new value to the set of values
func (args Args) Add(key, value string) {
if _, ok := args.fields[key]; ok {
args.fields[key][value] = true
} else {
args.fields[key] = map[string]bool{value: true}
}
}

// Del removes a value from the set
func (args Args) Del(key, value string) {
if _, ok := args.fields[key]; ok {
delete(args.fields[key], value)
if len(args.fields[key]) == 0 {
delete(args.fields, key)
}
}
}

// Len returns the number of fields in the arguments.
func (args Args) Len() int {
return len(args.fields)
}

// ExactMatch returns true if the source matches exactly one of the filters.
func (args Args) ExactMatch(field, source string) bool {
fieldValues, ok := args.fields[field]
//do not filter if there is no filter set or cannot determine filter
if !ok || len(fieldValues) == 0 {
return true
}

// try to match full name value to avoid O(N) regular expression matching
return fieldValues[source]
}

// MarshalJSON returns a JSON byte representation of the Args
func (args Args) MarshalJSON() ([]byte, error) {
if len(args.fields) == 0 {
return []byte{}, nil
}
return json.Marshal(args.fields)
}

// UnmarshalJSON populates the Args from JSON encode bytes
func (args Args) UnmarshalJSON(raw []byte) error {
if len(raw) == 0 {
return nil
}
return json.Unmarshal(raw, &args.fields)
}

// ErrBadFormat is an error returned when a filter is not in the form key=value
var ErrBadFormat = errors.New("bad format of filter (expected name=value)")

// ParseFlag parses a key=value string and adds it to an Args.
func ParseFlag(arg string, prev Args) (Args, error) {
filters := prev
if len(arg) == 0 {
return filters, nil
}

if !strings.Contains(arg, "=") {
return filters, ErrBadFormat
}

f := strings.SplitN(arg, "=", 2)

name := strings.ToLower(strings.TrimSpace(f[0]))
value := strings.TrimSpace(f[1])

filters.Add(name, value)

return filters, nil
}

// ToParam packs the Args into a string for easy transport from client to server.
func ToParam(a Args) (string, error) {
if a.Len() == 0 {
return "", nil
}

buf, err := json.Marshal(a)
return string(buf), err
}

// FromParam decodes a JSON encoded string into Args
func FromParam(p string) (Args, error) {
args := NewArgs()

if p == "" {
return args, nil
}

raw := []byte(p)
err := json.Unmarshal(raw, &args)
if err != nil {
return args, err
}
return args, nil
}
151 changes: 151 additions & 0 deletions apis/filters/parse_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package filters

import (
"testing"
)

func TestParseArgs(t *testing.T) {
// equivalent of `pouch ps -f 'created=today' -f 'image.name=ubuntu*' -f 'image.name=*untu'`
flagArgs := []string{
"created=today",
"image.name=ubuntu*",
"image.name=*untu",
}
var (
args = NewArgs()
err error
)

for i := range flagArgs {
args, err = ParseFlag(flagArgs[i], args)
if err != nil {
t.Fatalf("ParseFlag got err: %v", err)
}
}

if len(args.Get("created")) != 1 {
t.Fatalf("got unexpected created keys: %v", args.Get("created"))
}
if len(args.Get("image.name")) != 2 {
t.Fatalf("got unexpected image.name keys: %v", args.Get("image.name"))
}
}

func TestAdd(t *testing.T) {
f := NewArgs()
f.Add("status", "running")
v := f.fields["status"]
if len(v) != 1 || !v["running"] {
t.Fatalf("Expected to include a running status, got %v", v)
}

f.Add("status", "paused")
if len(v) != 2 || !v["paused"] {
t.Fatalf("Expected to include a paused status, got %v", v)
}
}

func TestDel(t *testing.T) {
f := NewArgs()
f.Add("status", "running")
f.Del("status", "running")
v := f.fields["status"]
if v["running"] {
t.Fatal("Expected to not include a running status filter, got true")
}
}

func TestLen(t *testing.T) {
f := NewArgs()
if f.Len() != 0 {
t.Fatal("Expected to not include any field")
}
f.Add("status", "running")
if f.Len() != 1 {
t.Fatal("Expected to include one field")
}
}

func TestExactMatch(t *testing.T) {
f := NewArgs()

if !f.ExactMatch("status", "running") {
t.Fatal("Expected to match `running` when there are no filters, got false")
}

f.Add("status", "running")
f.Add("status", "pause*")

if !f.ExactMatch("status", "running") {
t.Fatal("Expected to match `running` with one of the filters, got false")
}

if f.ExactMatch("status", "paused") {
t.Fatal("Expected to not match `paused` with one of the filters, got true")
}
}

func TestToParam(t *testing.T) {
fields := map[string]map[string]bool{
"created": {"today": true},
"image.name": {"ubuntu*": true, "*untu": true},
}
a := Args{fields: fields}

_, err := ToParam(a)
if err != nil {
t.Errorf("failed to marshal the filters: %s", err)
}
}

func TestFromParam(t *testing.T) {
invalids := []string{
"anything",
"['a','list']",
"{'key': 'value'}",
`{"key": "value"}`,
`{"key": ["value"]}`,
}
valid := map[*Args][]string{
{fields: map[string]map[string]bool{"key": {"value": true}}}: {
`{"key": {"value": true}}`,
},
{fields: map[string]map[string]bool{"key": {"value1": true, "value2": true}}}: {
`{"key": {"value1": true, "value2": true}}`,
},
{fields: map[string]map[string]bool{"key1": {"value1": true}, "key2": {"value2": true}}}: {
`{"key1": {"value1": true}, "key2": {"value2": true}}`,
},
}

for _, invalid := range invalids {
if _, err := FromParam(invalid); err == nil {
t.Fatalf("Expected an error with %v, got nothing", invalid)
}
}

for expectedArgs, matchers := range valid {
for _, json := range matchers {
args, err := FromParam(json)
if err != nil {
t.Fatal(err)
}
if args.Len() != expectedArgs.Len() {
t.Fatalf("Expected %v, go %v", expectedArgs, args)
}
for key, expectedValues := range expectedArgs.fields {
values := args.Get(key)

if len(values) != len(expectedValues) {
t.Fatalf("Expected %v, go %v", expectedArgs, args)
}

for _, v := range values {
if !expectedValues[v] {
t.Fatalf("Expected %v, go %v", expectedArgs, args)
}
}
}
}
}
}
52 changes: 52 additions & 0 deletions apis/swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3597,6 +3597,58 @@ definitions:
items:
type: "string"

EventsMessage:
description: |
EventsMessage represents the information an event contains, the message
at least contains type, action and id. type specifies which object generates
the event, like container, or a network, or a volume. the action specifies
the action name, like create, or destroy. the id identifies the object that
generates the event.
The message also can contain the EventsActor that describes the extra
attributes that describe the event.
type: "object"
properties:
status:
type: "string"
id:
type: "string"
from:
type: "string"
type:
$ref: "#/definitions/EventType"
action:
type: "string"
actor:
$ref: "#/definitions/EventsActor"
time:
type: "integer"
timeNano:
type: "integer"

EventsActor:
description: |
EventsActor 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: "object"
properties:
ID:
type: "string"
Attributes:
type: "object"
additionalProperties:
type: "string"

EventType:
description: |
The type of event. For example, "container" or "image",
Now we only support container, image, network and volume events.
type: "string"
enum: ["container", "daemon", "image", "network", "plugin", "volume"]


parameters:
id:
name: id
Expand Down
Loading

0 comments on commit a3ce826

Please sign in to comment.