You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by ch...@apache.org on 2020/12/31 02:09:04 UTC

[apisix-dashboard] branch master updated: fix: search labels with the same key (#1151)

This is an automated email from the ASF dual-hosted git repository.

chenjunxu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git


The following commit(s) were added to refs/heads/master by this push:
     new c0241ee  fix: search labels with the same key  (#1151)
c0241ee is described below

commit c0241ee83450e2f26695aed12dbb17d6f9da59d3
Author: Peter Zhu <st...@gmail.com>
AuthorDate: Thu Dec 31 10:08:53 2020 +0800

    fix: search labels with the same key  (#1151)
    
    * fix: search labels with the same key (#1130)
---
 api/internal/handler/label/label.go               |  9 ++-
 api/internal/handler/label/label_test.go          | 69 +++++++++++++++--------
 api/internal/utils/utils.go                       | 21 ++++---
 api/internal/utils/utils_test.go                  | 37 ++++++++----
 api/test/e2e/label_test.go                        | 40 +++++++++++++
 api/test/e2e/route_with_management_fileds_test.go | 11 ++++
 6 files changed, 145 insertions(+), 42 deletions(-)

diff --git a/api/internal/handler/label/label.go b/api/internal/handler/label/label.go
index 93ff0de..9a29f8c 100644
--- a/api/internal/handler/label/label.go
+++ b/api/internal/handler/label/label.go
@@ -78,15 +78,18 @@ type ListInput struct {
 	Label string `auto_read:"label,query"`
 }
 
-func subsetOf(reqLabels, labels map[string]string) map[string]string {
+func subsetOf(reqLabels map[string]struct{}, labels map[string]string) map[string]string {
 	if len(reqLabels) == 0 {
 		return labels
 	}
 
 	var res = make(map[string]string)
 	for k, v := range labels {
-		l, exist := reqLabels[k]
-		if exist && ((l == "") || v == l) {
+		if _, exist := reqLabels[k]; exist {
+			res[k] = v
+		}
+
+		if _, exist := reqLabels[k+":"+v]; exist {
 			res[k] = v
 		}
 	}
diff --git a/api/internal/handler/label/label_test.go b/api/internal/handler/label/label_test.go
index 1d0205b..25f9fda 100644
--- a/api/internal/handler/label/label_test.go
+++ b/api/internal/handler/label/label_test.go
@@ -221,30 +221,43 @@ func TestLabel(t *testing.T) {
 			}
 		}
 
+		var testCases []*testCase
+
 		expect := []interface{}{
 			Pair{"label1", "value1"},
 			Pair{"label1", "value2"},
 			Pair{"label2", "value2"},
 		}
-		case1 := newCase(giveData, expect)
-		case1.giveInput.Type = typ
+		tc := newCase(giveData, expect)
+		tc.giveInput.Type = typ
+		testCases = append(testCases, tc)
 
 		expect = []interface{}{
 			Pair{"label1", "value1"},
 			Pair{"label1", "value2"},
 		}
-		case2 := newCase(giveData, expect)
-		case2.giveInput.Type = typ
-		case2.giveInput.Label = "label1"
+		tc = newCase(giveData, expect)
+		tc.giveInput.Type = typ
+		tc.giveInput.Label = "label1"
+		testCases = append(testCases, tc)
+
+		expect = []interface{}{
+			Pair{"label1", "value2"},
+		}
+		tc = newCase(giveData, expect)
+		tc.giveInput.Type = typ
+		tc.giveInput.Label = "label1:value2"
+		testCases = append(testCases, tc)
 
 		expect = []interface{}{
+			Pair{"label1", "value1"},
 			Pair{"label1", "value2"},
 		}
-		case3 := newCase(giveData, expect)
-		case3.giveInput.Type = typ
-		case3.giveInput.Label = "label1:value2"
+		tc = newCase(giveData, expect)
+		tc.giveInput.Type = typ
+		tc.giveInput.Label = "label1:value1,label1:value2"
+		testCases = append(testCases, tc)
 
-		testCases := []*testCase{case1, case2, case3}
 		handler := Handler{}
 		for _, tc := range testCases {
 			switch typ {
@@ -290,6 +303,8 @@ func TestLabel(t *testing.T) {
 		serviceStore:  genMockStore(t, []interface{}{genService(m5)}),
 	}
 
+	var testCases []*testCase
+
 	expect := []interface{}{
 		Pair{"label1", "value1"},
 		Pair{"label1", "value2"},
@@ -298,35 +313,45 @@ func TestLabel(t *testing.T) {
 		Pair{"label4", "value4"},
 		Pair{"label5", "value5"},
 	}
-	case1 := newCase(nil, expect)
-	case1.giveInput.Type = "all"
+	tc := newCase(nil, expect)
+	tc.giveInput.Type = "all"
+	testCases = append(testCases, tc)
 
 	expect = []interface{}{
 		Pair{"label1", "value1"},
 		Pair{"label1", "value2"},
 	}
-	case2 := newCase(nil, expect)
-	case2.giveInput.Type = "all"
-	case2.giveInput.Label = "label1"
+	tc = newCase(nil, expect)
+	tc.giveInput.Type = "all"
+	tc.giveInput.Label = "label1"
+	testCases = append(testCases, tc)
 
 	expect = []interface{}{
 		Pair{"label1", "value2"},
 	}
-	case3 := newCase(nil, expect)
-	case3.giveInput.Type = "all"
-	case3.giveInput.Label = "label1:value2"
+	tc = newCase(nil, expect)
+	tc.giveInput.Type = "all"
+	tc.giveInput.Label = "label1:value2"
+	testCases = append(testCases, tc)
 
 	expect = []interface{}{
 		Pair{"label1", "value1"},
 		Pair{"label1", "value2"},
 		Pair{"label5", "value5"},
 	}
-	case4 := newCase(nil, expect)
-	case4.giveInput.Type = "all"
-	case4.giveInput.Label = "label1,label5:value5"
+	tc = newCase(nil, expect)
+	tc.giveInput.Type = "all"
+	tc.giveInput.Label = "label1,label5:value5"
+
+	expect = []interface{}{
+		Pair{"label1", "value1"},
+		Pair{"label1", "value2"},
+	}
+	tc = newCase(nil, expect)
+	tc.giveInput.Type = "all"
+	tc.giveInput.Label = "label1=value1,label1=value2"
 
-	testcase := []*testCase{case1, case2, case3, case4}
-	for _, tc := range testcase {
+	for _, tc := range testCases {
 		ctx := droplet.NewContext()
 		ctx.SetInput(tc.giveInput)
 		ret, err := handler.List(ctx)
diff --git a/api/internal/utils/utils.go b/api/internal/utils/utils.go
index b8d192c..6b7ea10 100644
--- a/api/internal/utils/utils.go
+++ b/api/internal/utils/utils.go
@@ -109,9 +109,9 @@ func ObjectClone(origin, copy interface{}) error {
 	return err
 }
 
-func GenLabelMap(label string) (map[string]string, error) {
+func GenLabelMap(label string) (map[string]struct{}, error) {
 	var err = errors.New("malformed label")
-	mp := make(map[string]string)
+	mp := make(map[string]struct{})
 
 	if label == "" {
 		return mp, nil
@@ -125,13 +125,15 @@ func GenLabelMap(label string) (map[string]string, error) {
 				return nil, err
 			}
 
-			mp[kv[0]] = kv[1]
+			// Because the labels may contain the same key, like this: label=version:v1,version:v2
+			// we need to combine them as a map's key
+			mp[l] = struct{}{}
 		} else if len(kv) == 1 {
 			if kv[0] == "" {
 				return nil, err
 			}
 
-			mp[kv[0]] = ""
+			mp[kv[0]] = struct{}{}
 		} else {
 			return nil, err
 		}
@@ -140,14 +142,19 @@ func GenLabelMap(label string) (map[string]string, error) {
 	return mp, nil
 }
 
-func LabelContains(labels, reqLabels map[string]string) bool {
+func LabelContains(labels map[string]string, reqLabels map[string]struct{}) bool {
 	if len(reqLabels) == 0 {
 		return true
 	}
 
 	for k, v := range labels {
-		l, exist := reqLabels[k]
-		if exist && ((l == "") || v == l) {
+		// first check the key
+		if _, exist := reqLabels[k]; exist {
+			return true
+		}
+
+		// second check the key:value
+		if _, exist := reqLabels[k+":"+v]; exist {
 			return true
 		}
 	}
diff --git a/api/internal/utils/utils_test.go b/api/internal/utils/utils_test.go
index 8da295f..1d7c154 100644
--- a/api/internal/utils/utils_test.go
+++ b/api/internal/utils/utils_test.go
@@ -66,12 +66,17 @@ func TestGenLabelMap(t *testing.T) {
 	expectedErr := errors.New("malformed label")
 	mp, err := GenLabelMap("l1")
 	assert.Nil(t, err)
-	assert.Equal(t, mp["l1"], "")
+	assert.Equal(t, mp["l1"], struct{}{})
 
 	mp, err = GenLabelMap("l1,l2:v2")
 	assert.Nil(t, err)
-	assert.Equal(t, mp["l1"], "")
-	assert.Equal(t, mp["l2"], "v2")
+	assert.Equal(t, mp["l1"], struct{}{})
+	assert.Equal(t, mp["l2:v2"], struct{}{})
+
+	mp, err = GenLabelMap("l1:v1,l1:v2")
+	assert.Nil(t, err)
+	assert.Equal(t, mp["l1:v1"], struct{}{})
+	assert.Equal(t, mp["l1:v2"], struct{}{})
 
 	mp, err = GenLabelMap(",")
 	assert.Equal(t, expectedErr, err)
@@ -83,20 +88,32 @@ func TestGenLabelMap(t *testing.T) {
 }
 
 func TestLabelContains(t *testing.T) {
-	mp1, _ := GenLabelMap("l1,l2:v2")
-	mp2 := map[string]string{
+	reqMap, _ := GenLabelMap("l1,l2:v2")
+	mp := map[string]string{
 		"l1": "v1",
 	}
-	assert.True(t, LabelContains(mp2, mp1))
+	assert.True(t, LabelContains(mp, reqMap))
 
-	mp3 := map[string]string{
+	mp = map[string]string{
 		"l1": "v1",
 		"l2": "v3",
 	}
-	assert.True(t, LabelContains(mp3, mp1))
+	assert.True(t, LabelContains(mp, reqMap))
 
-	mp4 := map[string]string{
+	mp = map[string]string{
 		"l2": "v3",
 	}
-	assert.False(t, LabelContains(mp4, mp1))
+	assert.False(t, LabelContains(mp, reqMap))
+
+	reqMap, _ = GenLabelMap("l1:v1,l1:v2")
+	mp = map[string]string{
+		"l1": "v1",
+	}
+	assert.True(t, LabelContains(mp, reqMap))
+
+	reqMap, _ = GenLabelMap("l1:v1,l1:v2")
+	mp = map[string]string{
+		"l1": "v2",
+	}
+	assert.True(t, LabelContains(mp, reqMap))
 }
diff --git a/api/test/e2e/label_test.go b/api/test/e2e/label_test.go
index b984c08..690d913 100644
--- a/api/test/e2e/label_test.go
+++ b/api/test/e2e/label_test.go
@@ -201,6 +201,16 @@ func TestLabel(t *testing.T) {
 			ExpectBody:   "{\"build\":\"16\"},{\"build\":\"17\"}",
 		},
 		{
+			Desc:         "get labels with the same key (key = build)",
+			Object:       ManagerApiExpect(t),
+			Method:       http.MethodGet,
+			Headers:      map[string]string{"Authorization": token},
+			Query:        "label=build:16,build:17",
+			Path:         "/apisix/admin/labels/all",
+			ExpectStatus: http.StatusOK,
+			ExpectBody:   "{\"build\":\"16\"},{\"build\":\"17\"}",
+		},
+		{
 			Desc:         "get labels (key = build) with page",
 			Object:       ManagerApiExpect(t),
 			Method:       http.MethodGet,
@@ -211,6 +221,26 @@ func TestLabel(t *testing.T) {
 			ExpectBody:   "{\"build\":\"17\"}",
 		},
 		{
+			Desc:         "get labels with same key (key = build) and page",
+			Object:       ManagerApiExpect(t),
+			Method:       http.MethodGet,
+			Headers:      map[string]string{"Authorization": token},
+			Query:        "label=build:16,build:17&page=1&page_size=2",
+			Path:         "/apisix/admin/labels/all",
+			ExpectStatus: http.StatusOK,
+			ExpectBody:   "{\"build\":\"16\"},{\"build\":\"17\"}",
+		},
+		{
+			Desc:         "get labels with same key (key = build) and page",
+			Object:       ManagerApiExpect(t),
+			Method:       http.MethodGet,
+			Headers:      map[string]string{"Authorization": token},
+			Query:        "label=build:16,build:17&page=2&page_size=1",
+			Path:         "/apisix/admin/labels/all",
+			ExpectStatus: http.StatusOK,
+			ExpectBody:   "{\"build\":\"17\"}",
+		},
+		{
 			Desc:         "get labels (key = build && env = production)",
 			Object:       ManagerApiExpect(t),
 			Method:       http.MethodGet,
@@ -221,6 +251,16 @@ func TestLabel(t *testing.T) {
 			ExpectBody:   "{\"build\":\"16\"},{\"build\":\"17\"},{\"env\":\"production\"}",
 		},
 		{
+			Desc:         "get labels (build=16 | 17 and env = production)",
+			Object:       ManagerApiExpect(t),
+			Method:       http.MethodGet,
+			Headers:      map[string]string{"Authorization": token},
+			Query:        "label=build:16,build:17,env:production",
+			Path:         "/apisix/admin/labels/all",
+			ExpectStatus: http.StatusOK,
+			ExpectBody:   "{\"build\":\"16\"},{\"build\":\"17\"},{\"env\":\"production\"}",
+		},
+		{
 			Desc:         "get labels (key = build && env = production) with page",
 			Object:       ManagerApiExpect(t),
 			Method:       http.MethodGet,
diff --git a/api/test/e2e/route_with_management_fileds_test.go b/api/test/e2e/route_with_management_fileds_test.go
index 207cba9..3c4bb3f 100644
--- a/api/test/e2e/route_with_management_fileds_test.go
+++ b/api/test/e2e/route_with_management_fileds_test.go
@@ -334,6 +334,17 @@ func TestRoute_search_by_label(t *testing.T) {
 			Sleep:        sleepTime,
 		},
 		{
+			Desc:         "search the route by label (combination)",
+			Object:       ManagerApiExpect(t),
+			Path:         "/apisix/admin/routes",
+			Query:        "label=build:16,build:17",
+			Method:       http.MethodGet,
+			Headers:      map[string]string{"Authorization": token},
+			ExpectStatus: http.StatusOK,
+			ExpectBody:   "\"total_size\":2",
+			Sleep:        sleepTime,
+		},
+		{
 			Desc:         "delete the route (r1)",
 			Object:       ManagerApiExpect(t),
 			Method:       http.MethodDelete,