From 6897026699de55c5707e939052e11b2d52c04dc7 Mon Sep 17 00:00:00 2001 From: "allen.wang" Date: Fri, 26 Apr 2019 15:08:32 +0800 Subject: [PATCH] support filter format map[string]map[string]bool and change multi label filter to AND match Signed-off-by: allen.wang --- daemon/mgr/container_list.go | 28 ++++++----- daemon/mgr/container_list_test.go | 11 ++-- pkg/utils/filters/filter.go | 23 ++++++++- pkg/utils/filters/filter_test.go | 83 +++++++++++++++++++++++++++++++ test/api_container_list_test.go | 76 ++++++++++++++++++++++++++++ 5 files changed, 204 insertions(+), 17 deletions(-) diff --git a/daemon/mgr/container_list.go b/daemon/mgr/container_list.go index 5f0fe0915..6a1a456bd 100644 --- a/daemon/mgr/container_list.go +++ b/daemon/mgr/container_list.go @@ -108,23 +108,25 @@ func (fc *filterContext) matchKVFilter(field string, value map[string]string) bo return false } - match := false - for k, v := range value { - // filter equal condition - if equalValue, exist := equalKV[k]; exist { - if equalValue == "" || equalValue == v { - match = true - break - } + // filter equal condition + for k, equalValue := range equalKV { + // if not find equal (k, v) pair, return false + if v, exist := value[k]; !exist || (equalValue != "" && equalValue != v) { + return false } - // filter unequal condition - if unequalValue, exist := unequalKV[k]; exist && unequalValue != v { - match = true - break + } + + // filter unequal condition + for k, unequalValue := range unequalKV { + // if key not exist or pair (k, v) found, return false + if v, exist := value[k]; exist && unequalValue != v { + continue } + + return false } - return match + return true } // filter does all select container work. diff --git a/daemon/mgr/container_list_test.go b/daemon/mgr/container_list_test.go index 303bc81eb..1300bac9b 100644 --- a/daemon/mgr/container_list_test.go +++ b/daemon/mgr/container_list_test.go @@ -132,12 +132,17 @@ func TestMatchKVFilter(t *testing.T) { { field: "label", value: map[string]string{"foo": "a"}, + isFilter: false, + }, + { + field: "label", + value: map[string]string{"foo": "a", "hello": "word"}, isFilter: true, }, { field: "label", value: map[string]string{"hello": "word"}, - isFilter: true, + isFilter: false, }, } { assert.Equal(t.isFilter, fc.matchKVFilter(t.field, t.value), fmt.Sprintf("%+v", t.value)) @@ -165,7 +170,7 @@ func TestMatchKVFilter(t *testing.T) { }, { field: "label", - value: map[string]string{"a": "c"}, + value: map[string]string{"a": "c", "d": "b"}, isFilter: true, }, { @@ -176,7 +181,7 @@ func TestMatchKVFilter(t *testing.T) { { field: "label", value: map[string]string{"d": "word"}, - isFilter: true, + isFilter: false, }, } { assert.Equal(t.isFilter, fc.matchKVFilter(t.field, t.value), fmt.Sprintf("%+v", t.value)) diff --git a/pkg/utils/filters/filter.go b/pkg/utils/filters/filter.go index 3b0befed4..c2a1b5218 100644 --- a/pkg/utils/filters/filter.go +++ b/pkg/utils/filters/filter.go @@ -85,7 +85,28 @@ func FromURLParam(param string) (map[string][]string, error) { var filter map[string][]string err := json.NewDecoder(strings.NewReader(param)).Decode(&filter) if err != nil { - return nil, err + // support filters as map[string]map[string]bool + var otherFilters map[string]map[string]bool + err2 := json.NewDecoder(strings.NewReader(param)).Decode(&otherFilters) + if err2 != nil { + return nil, err + } + + filter = make(map[string][]string) + for key, innerMap := range otherFilters { + if len(innerMap) == 0 { + continue + } + + value := []string{} + for k, v := range innerMap { + if v { + value = append(value, k) + } + } + + filter[key] = value + } } // params from url may not passed through api, so we need validate here diff --git a/pkg/utils/filters/filter_test.go b/pkg/utils/filters/filter_test.go index 7fb9d99dd..6c89340ae 100644 --- a/pkg/utils/filters/filter_test.go +++ b/pkg/utils/filters/filter_test.go @@ -1,6 +1,9 @@ package filters import ( + "encoding/json" + "sort" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -98,3 +101,83 @@ func TestValidate(t *testing.T) { }) } } + +type sortString []string + +func (s sortString) Len() int { + return len(s) +} + +func (s sortString) Swap(i, j int) { + tmp := s[i] + s[i] = s[j] + s[j] = tmp +} + +func (s sortString) Less(i, j int) bool { + return strings.Compare(s[i], s[j]) < 0 +} + +func TestFromURLParam(t *testing.T) { + f := func(i interface{}) string { + data, _ := json.Marshal(i) + return string(data) + } + + tests := []struct { + name string + params string + wantErr bool + expected map[string][]string + }{ + { + name: "normal successful case", + params: f(map[string][]string{ + "label": {"a=a", "b=b"}, + "id": {"id1"}, + }), + wantErr: false, + expected: map[string][]string{ + "label": {"a=a", "b=b"}, + "id": {"id1"}, + }, + }, + { + name: "normal successful case2", + params: f(map[string]map[string]bool{ + "label": {"a=a": true, "b=b": true}, + "id": {"id1": true}, + }), + wantErr: false, + expected: map[string][]string{ + "label": {"a=a", "b=b"}, + "id": {"id1"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, err := FromURLParam(tt.params) + if (err != nil) != tt.wantErr { + t.Errorf("FromURLParam() error = %v, wantErr %v", err, tt.wantErr) + } + + for k, v := range tt.expected { + compV, exist := res[k] + if !exist || len(compV) != len(v) { + t.Errorf("FromURLParam() return %v, want %v", res, tt.expected) + } + + sort.Sort(sortString(compV)) + sort.Sort(sortString(v)) + + for i := range compV { + if v[i] != compV[i] { + t.Errorf("FromURLParam() return %v, want %v", res, tt.expected) + } + } + } + + }) + } +} diff --git a/test/api_container_list_test.go b/test/api_container_list_test.go index 6fe17f709..0e91f22ce 100644 --- a/test/api_container_list_test.go +++ b/test/api_container_list_test.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "net/url" "strings" @@ -179,3 +180,78 @@ func getContainerListOK(c *check.C, filters string, all bool) (success bool, got return success, got, errResp } + +// TestListFilterMapMapFormat test label filter format of map[string]map[string]bool +func (suite *APIContainerListSuite) TestListFilterMapMapFormat(c *check.C) { + containerA := "TestListFilterMapMapFormatContainerA" + resA := command.PouchRun("run", "-d", "--name", containerA, "-l", "label="+containerA, busyboxImage125, "top").Assert(c, icmd.Success) + defer DelContainerForceMultyTime(c, containerA) + containerAID := strings.TrimSpace(resA.Combined()) + + containerB := "TestListFilterMapMapFormatContainerB" + resB := command.PouchRun("run", "-d", "--name", containerB, "-l", "label="+containerB, busyboxImage125, "top").Assert(c, icmd.Success) + defer DelContainerForceMultyTime(c, containerB) + containerBID := strings.TrimSpace(resB.Combined()) + + filterA := map[string]map[string]bool{ + "label": { + fmt.Sprintf("label=%s", containerA): true, + }, + } + + filterB := map[string]map[string]bool{ + "label": { + fmt.Sprintf("label!=%s", containerA): true, + }, + } + + filterAStr, _ := json.Marshal(filterA) + filterBStr, _ := json.Marshal(filterB) + + success, got, _ := getContainerListOK(c, string(filterAStr), true) + c.Assert(success, check.Equals, true) + c.Assert(len(got), check.Equals, 1) + c.Assert(got[0].ID, check.Equals, containerAID) + + success, got, _ = getContainerListOK(c, string(filterBStr), true) + c.Assert(success, check.Equals, true) + c.Assert(len(got), check.Equals, 1) + c.Assert(got[0].ID, check.Equals, containerBID) +} + +// TestListFilterMultiLabel test multi label filter +func (suite *APIContainerListSuite) TestListFilterMultiLabel(c *check.C) { + containerA := "TestListFilterMultiLabelContainerA" + resA := command.PouchRun("run", "-d", "--name", containerA, "-l", "label1="+containerA, "-l", "label2=v1", busyboxImage125, "top").Assert(c, icmd.Success) + defer DelContainerForceMultyTime(c, containerA) + containerAID := strings.TrimSpace(resA.Combined()) + + containerB := "TestListFilterMultiLabelContainerB" + resB := command.PouchRun("run", "-d", "--name", containerB, "-l", "label1="+containerB, "-l", "label2=v1", busyboxImage125, "top").Assert(c, icmd.Success) + defer DelContainerForceMultyTime(c, containerB) + containerBID := strings.TrimSpace(resB.Combined()) + + filterA := map[string]map[string]bool{ + "label": { + fmt.Sprintf("label1=%s", containerA): true, + "label2=v1": true, + }, + } + + filterB := map[string][]string{ + "label": {fmt.Sprintf("label1!=%s", containerA), "label2=v1"}, + } + + filterAStr, _ := json.Marshal(filterA) + filterBStr, _ := json.Marshal(filterB) + + success, got, _ := getContainerListOK(c, string(filterAStr), true) + c.Assert(success, check.Equals, true) + c.Assert(len(got), check.Equals, 1) + c.Assert(got[0].ID, check.Equals, containerAID) + + success, got, _ = getContainerListOK(c, string(filterBStr), true) + c.Assert(success, check.Equals, true) + c.Assert(len(got), check.Equals, 1) + c.Assert(got[0].ID, check.Equals, containerBID) +}