From 4b894cfd3fb7280b500249b9f6457d04867c42d9 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 21 Dec 2020 16:49:11 +0800 Subject: [PATCH] feat: support labels list (#1072) * feat: support labels list --- api/internal/handler/label/label.go | 261 ++++++++++++++++++ api/internal/handler/label/label_test.go | 336 +++++++++++++++++++++++ api/internal/route.go | 2 + api/test/e2e/label_test.go | 269 ++++++++++++++++++ 4 files changed, 868 insertions(+) create mode 100644 api/internal/handler/label/label.go create mode 100644 api/internal/handler/label/label_test.go create mode 100644 api/test/e2e/label_test.go diff --git a/api/internal/handler/label/label.go b/api/internal/handler/label/label.go new file mode 100644 index 0000000000..93ff0de425 --- /dev/null +++ b/api/internal/handler/label/label.go @@ -0,0 +1,261 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package label + +import ( + "encoding/json" + "fmt" + "net/http" + "reflect" + "sort" + "strconv" + "strings" + + "github.com/gin-gonic/gin" + "github.com/shiningrush/droplet" + "github.com/shiningrush/droplet/data" + "github.com/shiningrush/droplet/wrapper" + wgin "github.com/shiningrush/droplet/wrapper/gin" + + "github.com/apisix/manager-api/internal/core/entity" + "github.com/apisix/manager-api/internal/core/store" + "github.com/apisix/manager-api/internal/handler" + "github.com/apisix/manager-api/internal/utils" +) + +type Handler struct { + routeStore store.Interface + serviceStore store.Interface + upstreamStore store.Interface + sslStore store.Interface + consumerStore store.Interface +} + +var _ json.Marshaler = Pair{} + +type Pair struct { + Key string + Val string +} + +func (p Pair) MarshalJSON() ([]byte, error) { + res := fmt.Sprintf("{%s:%s}", strconv.Quote(p.Key), strconv.Quote(p.Val)) + return []byte(res), nil +} + +func NewHandler() (handler.RouteRegister, error) { + return &Handler{ + routeStore: store.GetStore(store.HubKeyRoute), + serviceStore: store.GetStore(store.HubKeyService), + upstreamStore: store.GetStore(store.HubKeyUpstream), + sslStore: store.GetStore(store.HubKeySsl), + consumerStore: store.GetStore(store.HubKeyConsumer), + }, nil +} + +func (h *Handler) ApplyRoute(r *gin.Engine) { + r.GET("/apisix/admin/labels/:type", wgin.Wraps(h.List, + wrapper.InputType(reflect.TypeOf(ListInput{})))) +} + +type ListInput struct { + store.Pagination + Type string `auto_read:"type,path" validate:"required"` + Label string `auto_read:"label,query"` +} + +func subsetOf(reqLabels, 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) { + res[k] = v + } + } + + return res +} + +// swagger:operation GET /api/labels getLabelsList +// +// Return the labels list among `route,ssl,consumer,upstream,service` +// according to the specified page number and page size, and can search labels by label. +// +// --- +// produces: +// - application/json +// parameters: +// - name: page +// in: query +// description: page number +// required: false +// type: integer +// - name: page_size +// in: query +// description: page size +// required: false +// type: integer +// - name: label +// in: query +// description: label filter of labels +// required: false +// type: string +// responses: +// '0': +// description: list response +// schema: +// type: array +// items: +// "$ref": "#/definitions/service" +// default: +// description: unexpected error +// schema: +// "$ref": "#/definitions/ApiError" +func (h *Handler) List(c droplet.Context) (interface{}, error) { + input := c.Input().(*ListInput) + + typ := input.Type + reqLabels, err := utils.GenLabelMap(input.Label) + if err != nil { + return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, + fmt.Errorf("%s: \"%s\"", err.Error(), input.Label) + } + + var items []interface{} + switch typ { + case "route": + items = append(items, h.routeStore) + case "service": + items = append(items, h.serviceStore) + case "consumer": + items = append(items, h.consumerStore) + case "ssl": + items = append(items, h.sslStore) + case "upstream": + items = append(items, h.upstreamStore) + case "all": + items = append(items, h.routeStore, h.serviceStore, h.upstreamStore, + h.sslStore, h.consumerStore) + } + + predicate := func(obj interface{}) bool { + var ls map[string]string + + switch obj := obj.(type) { + case *entity.Route: + ls = obj.Labels + case *entity.Consumer: + ls = obj.Labels + case *entity.SSL: + ls = obj.Labels + case *entity.Service: + ls = obj.Labels + case *entity.Upstream: + ls = obj.Labels + default: + return false + } + + return utils.LabelContains(ls, reqLabels) + } + + format := func(obj interface{}) interface{} { + val := reflect.ValueOf(obj).Elem() + l := val.FieldByName("Labels") + if l.IsNil() { + return nil + } + + ls := l.Interface().(map[string]string) + return subsetOf(reqLabels, ls) + } + + var totalRet = new(store.ListOutput) + var existMap = make(map[string]struct{}) + for _, item := range items { + ret, err := item.(store.Interface).List( + store.ListInput{ + Predicate: predicate, + Format: format, + // Sort it later. + PageSize: 0, + PageNumber: 0, + Less: func(i, j interface{}) bool { + return true + }, + }, + ) + + if err != nil { + return nil, err + } + + for _, r := range ret.Rows { + if r == nil { + continue + } + + for k, v := range r.(map[string]string) { + key := fmt.Sprintf("%s:%s", k, v) + if _, exist := existMap[key]; exist { + continue + } + + existMap[key] = struct{}{} + p := Pair{Key: k, Val: v} + totalRet.Rows = append(totalRet.Rows, p) + } + } + } + totalRet.TotalSize = len(totalRet.Rows) + + sort.Slice(totalRet.Rows, func(i, j int) bool { + p1 := totalRet.Rows[i].(Pair) + p2 := totalRet.Rows[j].(Pair) + + if strings.Compare(p1.Key, p2.Key) == 0 { + return strings.Compare(p1.Val, p2.Val) < 0 + } + + return strings.Compare(p1.Key, p2.Key) < 0 + }) + + /* There are more than one store items, + So we need sort after getting all of labels. + */ + if input.PageSize > 0 && input.PageNumber > 0 { + skipCount := (input.PageNumber - 1) * input.PageSize + if skipCount > totalRet.TotalSize { + totalRet.Rows = []interface{}{} + return totalRet, nil + } + + endIdx := skipCount + input.PageSize + if endIdx >= totalRet.TotalSize { + totalRet.Rows = totalRet.Rows[skipCount:] + return totalRet, nil + } + + totalRet.Rows = totalRet.Rows[skipCount:endIdx] + } + + return totalRet, nil +} diff --git a/api/internal/handler/label/label_test.go b/api/internal/handler/label/label_test.go new file mode 100644 index 0000000000..1d0205b955 --- /dev/null +++ b/api/internal/handler/label/label_test.go @@ -0,0 +1,336 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package label + +import ( + "encoding/json" + "math/rand" + "testing" + + "github.com/shiningrush/droplet" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/apisix/manager-api/internal/core/entity" + "github.com/apisix/manager-api/internal/core/store" +) + +type testCase struct { + giveInput *ListInput + giveData []interface{} + wantRet interface{} +} + +func TestPair_MarshalJSON(t *testing.T) { + type tempStruct struct { + Val string `json:"test_key"` + } + + temp := tempStruct{Val: "test_val"} + expect, err := json.Marshal(temp) + assert.Nil(t, err) + + p := Pair{Key: "test_key", Val: `test_val`} + content, err := json.Marshal(p) + assert.Nil(t, err, nil) + assert.Equal(t, expect, content) + + mp := make(map[string]string) + err = json.Unmarshal(content, &mp) + assert.Nil(t, err) + assert.Equal(t, mp["test_key"], "test_val") + + // Because the quote in json key is not allowed. + // So we only test the quote in json value. + temp = tempStruct{Val: "test_val\""} + expect, err = json.Marshal(temp) + assert.Nil(t, err) + + p = Pair{Key: "test_key", Val: `test_val"`} + content, err = json.Marshal(p) + assert.Nil(t, err, nil) + assert.Equal(t, expect, content) + + mp = make(map[string]string) + err = json.Unmarshal(content, &mp) + assert.Nil(t, err) + assert.Equal(t, mp["test_key"], "test_val\"") +} + +func genMockStore(t *testing.T, giveData []interface{}) *store.MockInterface { + mStore := &store.MockInterface{} + mStore.On("List", mock.Anything).Run(func(args mock.Arguments) { + input := args.Get(0).(store.ListInput) + assert.Equal(t, 0, input.PageSize) + assert.Equal(t, 0, input.PageNumber) + }).Return(func(input store.ListInput) *store.ListOutput { + var returnData []interface{} + for _, c := range giveData { + if input.Predicate(c) { + returnData = append(returnData, input.Format(c)) + } + } + return &store.ListOutput{ + Rows: returnData, + TotalSize: len(returnData), + } + }, nil) + + return mStore +} + +func newCase(giveData []interface{}, ret []interface{}) *testCase { + t := testCase{} + t.giveInput = &ListInput{ + Pagination: store.Pagination{ + PageSize: 10, + PageNumber: 1, + }, + } + + t.giveData = giveData + t.wantRet = &store.ListOutput{ + Rows: ret, + TotalSize: len(ret), + } + + return &t +} + +func genRoute(labels map[string]string) *entity.Route { + r := entity.Route{ + BaseInfo: entity.BaseInfo{ + ID: rand.Int(), + CreateTime: rand.Int63(), + }, + Host: "test.com", + URI: "/test/route", + Labels: labels, + } + + return &r +} + +func genService(labels map[string]string) *entity.Service { + r := entity.Service{ + BaseInfo: entity.BaseInfo{ + ID: rand.Int(), + CreateTime: rand.Int63(), + }, + EnableWebsocket: true, + Labels: labels, + } + + return &r +} + +func genSSL(labels map[string]string) *entity.SSL { + r := entity.SSL{ + BaseInfo: entity.BaseInfo{ + ID: rand.Int(), + CreateTime: rand.Int63(), + }, + Labels: labels, + } + + return &r +} + +func genUpstream(labels map[string]string) *entity.Upstream { + r := entity.Upstream{ + BaseInfo: entity.BaseInfo{ + ID: rand.Int(), + CreateTime: rand.Int63(), + }, + UpstreamDef: entity.UpstreamDef{ + Labels: labels, + }, + } + + return &r +} + +func genConsumer(labels map[string]string) *entity.Consumer { + r := entity.Consumer{ + BaseInfo: entity.BaseInfo{ + ID: rand.Int(), + CreateTime: rand.Int63(), + }, + Username: "test", + Labels: labels, + } + + return &r +} + +func TestLabel(t *testing.T) { + m1 := map[string]string{ + "label1": "value1", + "label2": "value2", + } + + m2 := map[string]string{ + "label1": "value2", + } + + // TODO: Test SSL after the ssl config bug fixed + types := []string{"route", "service", "upstream", "consumer"} + + var giveData []interface{} + for _, typ := range types { + switch typ { + case "route": + giveData = []interface{}{ + genRoute(m1), + genRoute(m2), + } + case "service": + giveData = []interface{}{ + genService(m1), + genService(m2), + } + case "ssl": + giveData = []interface{}{ + genSSL(m1), + genSSL(m2), + } + case "upstream": + giveData = []interface{}{ + genUpstream(m1), + genUpstream(m2), + } + case "consumer": + giveData = []interface{}{ + genConsumer(m1), + genConsumer(m2), + } + } + + expect := []interface{}{ + Pair{"label1", "value1"}, + Pair{"label1", "value2"}, + Pair{"label2", "value2"}, + } + case1 := newCase(giveData, expect) + case1.giveInput.Type = typ + + expect = []interface{}{ + Pair{"label1", "value1"}, + Pair{"label1", "value2"}, + } + case2 := newCase(giveData, expect) + case2.giveInput.Type = typ + case2.giveInput.Label = "label1" + + expect = []interface{}{ + Pair{"label1", "value2"}, + } + case3 := newCase(giveData, expect) + case3.giveInput.Type = typ + case3.giveInput.Label = "label1:value2" + + testCases := []*testCase{case1, case2, case3} + handler := Handler{} + for _, tc := range testCases { + switch typ { + case "route": + handler.routeStore = genMockStore(t, tc.giveData) + case "service": + handler.serviceStore = genMockStore(t, tc.giveData) + case "ssl": + handler.sslStore = genMockStore(t, tc.giveData) + case "upstream": + handler.upstreamStore = genMockStore(t, tc.giveData) + case "consumer": + handler.consumerStore = genMockStore(t, tc.giveData) + } + + ctx := droplet.NewContext() + ctx.SetInput(tc.giveInput) + ret, err := handler.List(ctx) + assert.Nil(t, err) + assert.Equal(t, tc.wantRet, ret) + } + } + + // test all + m3 := map[string]string{ + "label3": "value3", + } + + m4 := map[string]string{ + "label4": "value4", + } + + m5 := map[string]string{ + "label4": "value4", + "label5": "value5", + } + + handler := Handler{ + routeStore: genMockStore(t, []interface{}{genRoute(m1)}), + sslStore: genMockStore(t, []interface{}{genSSL(m2)}), + upstreamStore: genMockStore(t, []interface{}{genUpstream(m3)}), + consumerStore: genMockStore(t, []interface{}{genConsumer(m4)}), + serviceStore: genMockStore(t, []interface{}{genService(m5)}), + } + + expect := []interface{}{ + Pair{"label1", "value1"}, + Pair{"label1", "value2"}, + Pair{"label2", "value2"}, + Pair{"label3", "value3"}, + Pair{"label4", "value4"}, + Pair{"label5", "value5"}, + } + case1 := newCase(nil, expect) + case1.giveInput.Type = "all" + + expect = []interface{}{ + Pair{"label1", "value1"}, + Pair{"label1", "value2"}, + } + case2 := newCase(nil, expect) + case2.giveInput.Type = "all" + case2.giveInput.Label = "label1" + + expect = []interface{}{ + Pair{"label1", "value2"}, + } + case3 := newCase(nil, expect) + case3.giveInput.Type = "all" + case3.giveInput.Label = "label1:value2" + + 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" + + testcase := []*testCase{case1, case2, case3, case4} + for _, tc := range testcase { + ctx := droplet.NewContext() + ctx.SetInput(tc.giveInput) + ret, err := handler.List(ctx) + assert.Nil(t, err) + assert.Equal(t, tc.wantRet, ret) + } +} diff --git a/api/internal/route.go b/api/internal/route.go index 801f369071..3e38ea1608 100644 --- a/api/internal/route.go +++ b/api/internal/route.go @@ -32,6 +32,7 @@ import ( "github.com/apisix/manager-api/internal/handler/authentication" "github.com/apisix/manager-api/internal/handler/consumer" "github.com/apisix/manager-api/internal/handler/healthz" + "github.com/apisix/manager-api/internal/handler/label" "github.com/apisix/manager-api/internal/handler/plugin" "github.com/apisix/manager-api/internal/handler/route" "github.com/apisix/manager-api/internal/handler/route_online_debug" @@ -69,6 +70,7 @@ func SetUpRouter() *gin.Engine { authentication.NewHandler, route_online_debug.NewHandler, server_info.NewHandler, + label.NewHandler, } for i := range factories { diff --git a/api/test/e2e/label_test.go b/api/test/e2e/label_test.go new file mode 100644 index 0000000000..4fe5713405 --- /dev/null +++ b/api/test/e2e/label_test.go @@ -0,0 +1,269 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package e2e + +import ( + "net/http" + "testing" +) + +func TestLabel(t *testing.T) { + // Todo: test ssl after ssl bug fixed + tests := []HttpTestCase{ + { + caseDesc: "config route", + Object: ManagerApiExpect(t), + Path: "/apisix/admin/routes/r1", + Method: http.MethodPut, + Body: `{ + "uri": "/hello", + "labels": { + "build":"16", + "env":"production", + "version":"v2" + }, + "upstream": { + "type": "roundrobin", + "nodes": [{ + "host": "172.16.238.20", + "port": 1980, + "weight": 1 + }] + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "create consumer", + Object: ManagerApiExpect(t), + Path: "/apisix/admin/consumers/c1", + Method: http.MethodPut, + Body: `{ + "username": "jack", + "plugins": { + "key-auth": { + "key": "auth-one" + } + }, + "labels": { + "build":"16", + "env":"production", + "version":"v3" + }, + "desc": "test description" + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "create upstream", + Object: ManagerApiExpect(t), + Method: http.MethodPut, + Path: "/apisix/admin/upstreams/u1", + Body: `{ + "nodes": [{ + "host": "172.16.238.20", + "port": 1980, + "weight": 1 + }], + "labels": { + "build":"17", + "env":"production", + "version":"v2" + }, + "type": "roundrobin" + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "create service", + Object: ManagerApiExpect(t), + Method: http.MethodPost, + Path: "/apisix/admin/services", + Body: `{ + "id": "s1", + "plugins": { + "limit-count": { + "count": 2, + "time_window": 60, + "rejected_code": 503, + "key": "remote_addr" + } + }, + "upstream": { + "type": "roundrobin", + "nodes": [{ + "host": "39.97.63.215", + "port": 80, + "weight": 1 + }] + }, + "labels": { + "build":"16", + "env":"production", + "version":"v2", + "extra": "test" + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "get route label", + Object: ManagerApiExpect(t), + Method: http.MethodGet, + Headers: map[string]string{"Authorization": token}, + Path: "/apisix/admin/labels/route", + ExpectStatus: http.StatusOK, + ExpectBody: "{\"build\":\"16\"},{\"env\":\"production\"},{\"version\":\"v2\"}", + }, + { + caseDesc: "get consumer label", + Object: ManagerApiExpect(t), + Method: http.MethodGet, + Headers: map[string]string{"Authorization": token}, + Path: "/apisix/admin/labels/consumer", + ExpectStatus: http.StatusOK, + ExpectBody: "{\"build\":\"16\"},{\"env\":\"production\"},{\"version\":\"v3\"}", + }, + { + caseDesc: "get upstream label", + Object: ManagerApiExpect(t), + Method: http.MethodGet, + Headers: map[string]string{"Authorization": token}, + Path: "/apisix/admin/labels/upstream", + ExpectStatus: http.StatusOK, + ExpectBody: "{\"build\":\"17\"},{\"env\":\"production\"},{\"version\":\"v2\"}", + }, + { + caseDesc: "get service label", + Object: ManagerApiExpect(t), + Method: http.MethodGet, + Headers: map[string]string{"Authorization": token}, + Path: "/apisix/admin/labels/service", + ExpectStatus: http.StatusOK, + ExpectBody: "{\"build\":\"16\"},{\"env\":\"production\"},{\"extra\":\"test\"},{\"version\":\"v2\"}", + }, + { + caseDesc: "get all label", + Object: ManagerApiExpect(t), + Method: http.MethodGet, + Headers: map[string]string{"Authorization": token}, + Path: "/apisix/admin/labels/all", + ExpectStatus: http.StatusOK, + ExpectBody: "{\"build\":\"16\"},{\"build\":\"17\"},{\"env\":\"production\"},{\"extra\":\"test\"},{\"version\":\"v2\"},{\"version\":\"v3\"}", + }, + { + caseDesc: "get label with page", + Object: ManagerApiExpect(t), + Method: http.MethodGet, + Query: "page=1&page_size=1", + Headers: map[string]string{"Authorization": token}, + Path: "/apisix/admin/labels/all", + ExpectStatus: http.StatusOK, + ExpectBody: "{\"build\":\"16\"}", + }, + { + caseDesc: "get label with page", + Object: ManagerApiExpect(t), + Method: http.MethodGet, + Query: "page=3&page_size=1", + Headers: map[string]string{"Authorization": token}, + Path: "/apisix/admin/labels/all", + ExpectStatus: http.StatusOK, + ExpectBody: "{\"env\":\"production\"}", + }, + { + caseDesc: "get labels (key = build)", + Object: ManagerApiExpect(t), + Method: http.MethodGet, + Headers: map[string]string{"Authorization": token}, + Query: "label=build", + Path: "/apisix/admin/labels/all", + ExpectStatus: http.StatusOK, + ExpectBody: "{\"build\":\"16\"},{\"build\":\"17\"}", + }, + { + caseDesc: "get labels (key = build) with page", + Object: ManagerApiExpect(t), + Method: http.MethodGet, + Headers: map[string]string{"Authorization": token}, + Query: "label=build&page=2&page_size=1", + Path: "/apisix/admin/labels/all", + ExpectStatus: http.StatusOK, + ExpectBody: "{\"build\":\"17\"}", + }, + { + caseDesc: "get labels (key = build && env = production)", + Object: ManagerApiExpect(t), + Method: http.MethodGet, + Headers: map[string]string{"Authorization": token}, + Query: "label=build,env:production", + Path: "/apisix/admin/labels/all", + ExpectStatus: http.StatusOK, + ExpectBody: "{\"build\":\"16\"},{\"build\":\"17\"},{\"env\":\"production\"}", + }, + { + caseDesc: "get labels (key = build && env = production) with page", + Object: ManagerApiExpect(t), + Method: http.MethodGet, + Headers: map[string]string{"Authorization": token}, + Query: "label=build,env:production&page=3&page_size=1", + Path: "/apisix/admin/labels/all", + ExpectStatus: http.StatusOK, + ExpectBody: "{\"env\":\"production\"}", + }, + { + caseDesc: "delete route", + Object: ManagerApiExpect(t), + Method: http.MethodDelete, + Path: "/apisix/admin/routes/r1", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "delete consumer", + Object: ManagerApiExpect(t), + Method: http.MethodDelete, + Path: "/apisix/admin/consumers/c1", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "delete service", + Object: ManagerApiExpect(t), + Method: http.MethodDelete, + Path: "/apisix/admin/services/s1", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "delete upstream", + Object: ManagerApiExpect(t), + Method: http.MethodDelete, + Path: "/apisix/admin/upstreams/u1", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + } + + for _, tc := range tests { + testCaseCheck(tc) + } +}