Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support filter format map[string]map[string]bool and change multi label filter to And match #2812

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions daemon/mgr/container_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 8 additions & 3 deletions daemon/mgr/container_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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,
},
{
Expand All @@ -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))
Expand Down
23 changes: 22 additions & 1 deletion pkg/utils/filters/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
83 changes: 83 additions & 0 deletions pkg/utils/filters/filter_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package filters

import (
"encoding/json"
"sort"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -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)
}
}
}

})
}
}
76 changes: 76 additions & 0 deletions test/api_container_list_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"encoding/json"
"fmt"
"net/url"
"strings"
Expand Down Expand Up @@ -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)
}