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 822aa6d feat: support global rules for Manager API (#1057)
822aa6d is described below
commit 822aa6d1f40c5eb5bfef00030304eb5b619a9eea
Author: nic-chen <[email protected]>
AuthorDate: Mon Dec 28 16:23:03 2020 +0800
feat: support global rules for Manager API (#1057)
* feat: support global rules
---
api/internal/core/entity/entity.go | 6 +
api/internal/core/store/storehub.go | 24 +-
api/internal/filter/schema.go | 11 +-
api/internal/handler/global_rule/global_rule.go | 175 +++++++++++
.../handler/global_rule/global_rule_test.go | 336 +++++++++++++++++++++
api/internal/route.go | 2 +
api/test/e2e/base.go | 1 -
api/test/e2e/global_rule_test.go | 234 ++++++++++++++
8 files changed, 781 insertions(+), 8 deletions(-)
diff --git a/api/internal/core/entity/entity.go
b/api/internal/core/entity/entity.go
index 0e68078..32ed6e0 100644
--- a/api/internal/core/entity/entity.go
+++ b/api/internal/core/entity/entity.go
@@ -235,6 +235,12 @@ type Script struct {
Script interface{} `json:"script,omitempty"`
}
+// swagger:model GlobalPlugins
+type GlobalPlugins struct {
+ ID interface{} `json:"id"`
+ Plugins map[string]interface{} `json:"plugins,omitempty"`
+}
+
type ServerInfo struct {
BaseInfo
LastReportTime int64 `json:"last_report_time,omitempty"`
diff --git a/api/internal/core/store/storehub.go
b/api/internal/core/store/storehub.go
index fe61953..b1e1acf 100644
--- a/api/internal/core/store/storehub.go
+++ b/api/internal/core/store/storehub.go
@@ -34,6 +34,7 @@ const (
HubKeySsl HubKey = "ssl"
HubKeyUpstream HubKey = "upstream"
HubKeyScript HubKey = "script"
+ HubKeyGlobalRule HubKey = "global_rule"
HubKeyServerInfo HubKey = `server_info`
)
@@ -42,8 +43,15 @@ var (
)
func InitStore(key HubKey, opt GenericStoreOption) error {
- if key == HubKeyConsumer || key == HubKeyRoute ||
- key == HubKeyService || key == HubKeySsl || key ==
HubKeyUpstream {
+ hubsNeedCheck := map[HubKey]bool{
+ HubKeyConsumer: true,
+ HubKeyRoute: true,
+ HubKeySsl: true,
+ HubKeyService: true,
+ HubKeyUpstream: true,
+ HubKeyGlobalRule: true,
+ }
+ if _, ok := hubsNeedCheck[key]; ok {
validator, err := NewAPISIXJsonSchemaValidator("main." +
string(key))
if err != nil {
return err
@@ -145,6 +153,18 @@ func InitStores() error {
return err
}
+ err = InitStore(HubKeyGlobalRule, GenericStoreOption{
+ BasePath: "/apisix/global_rules",
+ ObjType: reflect.TypeOf(entity.GlobalPlugins{}),
+ KeyFunc: func(obj interface{}) string {
+ r := obj.(*entity.GlobalPlugins)
+ return utils.InterfaceToString(r.ID)
+ },
+ })
+ if err != nil {
+ return err
+ }
+
err = InitStore(HubKeyServerInfo, GenericStoreOption{
BasePath: "/apisix/data_plane/server_info",
ObjType: reflect.TypeOf(entity.ServerInfo{}),
diff --git a/api/internal/filter/schema.go b/api/internal/filter/schema.go
index a2a99ee..e9d50f2 100644
--- a/api/internal/filter/schema.go
+++ b/api/internal/filter/schema.go
@@ -37,11 +37,12 @@ import (
)
var resources = map[string]string{
- "routes": "route",
- "upstreams": "upstream",
- "services": "service",
- "consumers": "consumer",
- "ssl": "ssl",
+ "routes": "route",
+ "upstreams": "upstream",
+ "services": "service",
+ "consumers": "consumer",
+ "ssl": "ssl",
+ "global_rules": "global_rule",
}
const (
diff --git a/api/internal/handler/global_rule/global_rule.go
b/api/internal/handler/global_rule/global_rule.go
new file mode 100644
index 0000000..81b1877
--- /dev/null
+++ b/api/internal/handler/global_rule/global_rule.go
@@ -0,0 +1,175 @@
+/*
+ * 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 global_rule
+
+import (
+ "encoding/json"
+ "reflect"
+
+ "github.com/gin-gonic/gin"
+ "github.com/shiningrush/droplet"
+ "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"
+ "github.com/apisix/manager-api/internal/utils/consts"
+)
+
+type Handler struct {
+ globalRuleStore store.Interface
+}
+
+func NewHandler() (handler.RouteRegister, error) {
+ return &Handler{
+ globalRuleStore: store.GetStore(store.HubKeyGlobalRule),
+ }, nil
+}
+
+func (h *Handler) ApplyRoute(r *gin.Engine) {
+ // global plugins
+ r.GET("/apisix/admin/global_rules/:id", wgin.Wraps(h.Get,
+ wrapper.InputType(reflect.TypeOf(GetInput{}))))
+ r.GET("/apisix/admin/global_rules", wgin.Wraps(h.List,
+ wrapper.InputType(reflect.TypeOf(ListInput{}))))
+ r.PUT("/apisix/admin/global_rules/:id", wgin.Wraps(h.Set,
+ wrapper.InputType(reflect.TypeOf(entity.GlobalPlugins{}))))
+ r.PUT("/apisix/admin/global_rules", wgin.Wraps(h.Set,
+ wrapper.InputType(reflect.TypeOf(entity.GlobalPlugins{}))))
+
+ r.PATCH("/apisix/admin/global_rules/:id", consts.ErrorWrapper(Patch))
+ r.PATCH("/apisix/admin/global_rules/:id/*path",
consts.ErrorWrapper(Patch))
+
+ r.DELETE("/apisix/admin/global_rules/:id", wgin.Wraps(h.BatchDelete,
+ wrapper.InputType(reflect.TypeOf(BatchDeleteInput{}))))
+}
+
+type GetInput struct {
+ ID string `auto_read:"id,path" validate:"required"`
+}
+
+func (h *Handler) Get(c droplet.Context) (interface{}, error) {
+ input := c.Input().(*GetInput)
+
+ r, err := h.globalRuleStore.Get(input.ID)
+ if err != nil {
+ return handler.SpecCodeResponse(err), err
+ }
+ return r, nil
+}
+
+type ListInput struct {
+ store.Pagination
+}
+
+// swagger:operation GET /apisix/admin/global_rules getGlobalRuleList
+//
+// Return the global rule list according to the specified page number and page
size.
+//
+// ---
+// 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
+// responses:
+// '0':
+// description: list response
+// schema:
+// type: array
+// items:
+// "$ref": "#/definitions/GlobalPlugins"
+// default:
+// description: unexpected error
+// schema:
+// "$ref": "#/definitions/ApiError"
+func (h *Handler) List(c droplet.Context) (interface{}, error) {
+ input := c.Input().(*ListInput)
+
+ ret, err := h.globalRuleStore.List(store.ListInput{
+ PageSize: input.PageSize,
+ PageNumber: input.PageNumber,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return ret, nil
+}
+
+func (h *Handler) Set(c droplet.Context) (interface{}, error) {
+ input := c.Input().(*entity.GlobalPlugins)
+
+ if err := h.globalRuleStore.Create(c.Context(), input); err != nil {
+ return handler.SpecCodeResponse(err), err
+ }
+
+ return nil, nil
+}
+
+func Patch(c *gin.Context) (interface{}, error) {
+ reqBody, _ := c.GetRawData()
+ ID := c.Param("id")
+ subPath := c.Param("path")
+
+ routeStore := store.GetStore(store.HubKeyGlobalRule)
+ stored, err := routeStore.Get(ID)
+ if err != nil {
+ return handler.SpecCodeResponse(err), err
+ }
+
+ res, err := utils.MergePatch(stored, subPath, reqBody)
+ if err != nil {
+ return handler.SpecCodeResponse(err), err
+ }
+
+ var globalRule entity.GlobalPlugins
+ err = json.Unmarshal(res, &globalRule)
+ if err != nil {
+ return handler.SpecCodeResponse(err), err
+ }
+
+ if err := routeStore.Update(c, &globalRule, false); err != nil {
+ return handler.SpecCodeResponse(err), err
+ }
+
+ return nil, nil
+}
+
+type BatchDeleteInput struct {
+ ID string `auto_read:"id,path"`
+}
+
+func (h *Handler) BatchDelete(c droplet.Context) (interface{}, error) {
+ input := c.Input().(*BatchDeleteInput)
+
+ if err := h.globalRuleStore.BatchDelete(c.Context(),
[]string{input.ID}); err != nil {
+ return handler.SpecCodeResponse(err), err
+ }
+
+ return nil, nil
+}
diff --git a/api/internal/handler/global_rule/global_rule_test.go
b/api/internal/handler/global_rule/global_rule_test.go
new file mode 100644
index 0000000..62a719d
--- /dev/null
+++ b/api/internal/handler/global_rule/global_rule_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 global_rule
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/gin-gonic/gin"
+ "github.com/shiningrush/droplet"
+ "github.com/shiningrush/droplet/data"
+ "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"
+)
+
+func performRequest(r http.Handler, method, path string)
*httptest.ResponseRecorder {
+ req := httptest.NewRequest(method, path, nil)
+ w := httptest.NewRecorder()
+ r.ServeHTTP(w, req)
+ return w
+}
+
+func TestHandler_ApplyRoute(t *testing.T) {
+ mStore := &store.MockInterface{}
+ giveRet := `{
+ "id": "test",
+ "plugins": {
+ "jwt-auth": {}
+ }
+ }`
+ mStore.On("Get", mock.Anything).Run(func(args mock.Arguments) {
+ assert.Equal(t, "test", args.Get(0))
+ }).Return(giveRet, nil)
+
+ h := Handler{globalRuleStore: mStore}
+ r := gin.New()
+ h.ApplyRoute(r)
+
+ w := performRequest(r, "GET", "/apisix/admin/global_rules/test")
+ assert.Equal(t, 200, w.Code)
+}
+
+func TestHandler_Get(t *testing.T) {
+ tests := []struct {
+ caseDesc string
+ giveInput *GetInput
+ giveRet interface{}
+ giveErr error
+ wantErr error
+ wantGetKey string
+ wantRet interface{}
+ }{
+ {
+ caseDesc: "normal",
+ giveInput: &GetInput{ID: "test"},
+ wantGetKey: "test",
+ giveRet: `{
+ "id": "test",
+ "plugins": {
+ "jwt-auth": {}
+ }
+ }`,
+ wantRet: `{
+ "id": "test",
+ "plugins": {
+ "jwt-auth": {}
+ }
+ }`,
+ },
+ {
+ caseDesc: "store get failed",
+ giveInput: &GetInput{ID: "non-existent-key"},
+ wantGetKey: "non-existent-key",
+ giveErr: fmt.Errorf("data not found"),
+ wantErr: fmt.Errorf("data not found"),
+ wantRet: &data.SpecCodeResponse{
+ StatusCode: http.StatusNotFound,
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.caseDesc, func(t *testing.T) {
+ getCalled := true
+ mStore := &store.MockInterface{}
+ mStore.On("Get", mock.Anything).Run(func(args
mock.Arguments) {
+ getCalled = true
+ assert.Equal(t, tc.wantGetKey, args.Get(0))
+ }).Return(tc.giveRet, tc.giveErr)
+
+ h := Handler{globalRuleStore: mStore}
+ ctx := droplet.NewContext()
+ ctx.SetInput(tc.giveInput)
+ ret, err := h.Get(ctx)
+ assert.True(t, getCalled)
+ assert.Equal(t, tc.wantRet, ret)
+ assert.Equal(t, tc.wantErr, err)
+ })
+ }
+}
+
+func TestHandler_List(t *testing.T) {
+ tests := []struct {
+ caseDesc string
+ giveInput *ListInput
+ giveData []*entity.GlobalPlugins
+ giveErr error
+ wantErr error
+ wantInput store.ListInput
+ wantRet interface{}
+ }{
+ {
+ caseDesc: "list all condition",
+ giveInput: &ListInput{
+ Pagination: store.Pagination{
+ PageSize: 10,
+ PageNumber: 1,
+ },
+ },
+ wantInput: store.ListInput{
+ PageSize: 10,
+ PageNumber: 1,
+ },
+ giveData: []*entity.GlobalPlugins{
+ {ID: "global-rules-1"},
+ {ID: "global-rules-2"},
+ {ID: "global-rules-3"},
+ },
+ wantRet: &store.ListOutput{
+ Rows: []interface{}{
+ &entity.GlobalPlugins{ID:
"global-rules-1"},
+ &entity.GlobalPlugins{ID:
"global-rules-2"},
+ &entity.GlobalPlugins{ID:
"global-rules-3"},
+ },
+ TotalSize: 3,
+ },
+ },
+ {
+ caseDesc: "store list failed",
+ giveInput: &ListInput{
+ Pagination: store.Pagination{
+ PageSize: 10,
+ PageNumber: 10,
+ },
+ },
+ wantInput: store.ListInput{
+ PageSize: 10,
+ PageNumber: 10,
+ },
+ giveData: []*entity.GlobalPlugins{},
+ giveErr: fmt.Errorf("list failed"),
+ wantErr: fmt.Errorf("list failed"),
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.caseDesc, func(t *testing.T) {
+ getCalled := true
+ mStore := &store.MockInterface{}
+ mStore.On("List", mock.Anything).Run(func(args
mock.Arguments) {
+ getCalled = true
+ input := args.Get(0).(store.ListInput)
+ assert.Equal(t, tc.wantInput.PageSize,
input.PageSize)
+ assert.Equal(t, tc.wantInput.PageNumber,
input.PageNumber)
+ }).Return(func(input store.ListInput) *store.ListOutput
{
+ var returnData []interface{}
+ for _, c := range tc.giveData {
+ if input.Predicate == nil ||
input.Predicate(c) {
+ returnData = append(returnData,
c)
+ }
+ }
+ return &store.ListOutput{
+ Rows: returnData,
+ TotalSize: len(returnData),
+ }
+ }, tc.giveErr)
+
+ h := Handler{globalRuleStore: mStore}
+ ctx := droplet.NewContext()
+ ctx.SetInput(tc.giveInput)
+ ret, err := h.List(ctx)
+ assert.True(t, getCalled)
+ assert.Equal(t, tc.wantRet, ret)
+ assert.Equal(t, tc.wantErr, err)
+ })
+ }
+}
+
+func TestHandler_Set(t *testing.T) {
+ tests := []struct {
+ caseDesc string
+ giveInput *entity.GlobalPlugins
+ giveCtx context.Context
+ giveErr error
+ wantErr error
+ wantInput *entity.GlobalPlugins
+ wantRet interface{}
+ wantCalled bool
+ }{
+ {
+ caseDesc: "normal",
+ giveInput: &entity.GlobalPlugins{
+ ID: "name",
+ Plugins: map[string]interface{}{
+ "jwt-auth": map[string]interface{}{},
+ },
+ },
+ giveCtx: context.WithValue(context.Background(),
"test", "value"),
+ wantInput: &entity.GlobalPlugins{
+ ID: "name",
+ Plugins: map[string]interface{}{
+ "jwt-auth": map[string]interface{}{},
+ },
+ },
+ wantRet: nil,
+ wantCalled: true,
+ },
+ {
+ caseDesc: "store create failed",
+ giveInput: &entity.GlobalPlugins{
+ ID: "name",
+ Plugins: nil,
+ },
+ giveErr: fmt.Errorf("create failed"),
+ wantInput: &entity.GlobalPlugins{
+ ID: "name",
+ Plugins: map[string]interface{}(nil),
+ },
+ wantErr: fmt.Errorf("create failed"),
+ wantRet: &data.SpecCodeResponse{
+ StatusCode: http.StatusInternalServerError,
+ },
+ wantCalled: true,
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.caseDesc, func(t *testing.T) {
+ methodCalled := true
+ mStore := &store.MockInterface{}
+ mStore.On("Create", mock.Anything,
mock.Anything).Run(func(args mock.Arguments) {
+ methodCalled = true
+ assert.Equal(t, tc.giveCtx, args.Get(0))
+ assert.Equal(t, tc.wantInput, args.Get(1))
+ }).Return(tc.giveErr)
+
+ h := Handler{globalRuleStore: mStore}
+ ctx := droplet.NewContext()
+ ctx.SetInput(tc.giveInput)
+ ctx.SetContext(tc.giveCtx)
+ ret, err := h.Set(ctx)
+ assert.Equal(t, tc.wantCalled, methodCalled)
+ assert.Equal(t, tc.wantRet, ret)
+ assert.Equal(t, tc.wantErr, err)
+ })
+ }
+}
+
+func TestHandler_BatchDelete(t *testing.T) {
+ tests := []struct {
+ caseDesc string
+ giveInput *BatchDeleteInput
+ giveCtx context.Context
+ giveErr error
+ wantErr error
+ wantInput []string
+ wantRet interface{}
+ }{
+ {
+ caseDesc: "normal",
+ giveInput: &BatchDeleteInput{
+ ID: "user1",
+ },
+ giveCtx: context.WithValue(context.Background(),
"test", "value"),
+ wantInput: []string{
+ "user1",
+ },
+ },
+ {
+ caseDesc: "store delete failed",
+ giveInput: &BatchDeleteInput{
+ ID: "user2",
+ },
+ giveCtx: context.WithValue(context.Background(),
"test", "value"),
+ giveErr: fmt.Errorf("delete failed"),
+ wantInput: []string{
+ "user2",
+ },
+ wantErr: fmt.Errorf("delete failed"),
+ wantRet: &data.SpecCodeResponse{
+ StatusCode: http.StatusInternalServerError,
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.caseDesc, func(t *testing.T) {
+ methodCalled := true
+ mStore := &store.MockInterface{}
+ mStore.On("BatchDelete", mock.Anything, mock.Anything,
mock.Anything).Run(func(args mock.Arguments) {
+ methodCalled = true
+ assert.Equal(t, tc.giveCtx, args.Get(0))
+ assert.Equal(t, tc.wantInput, args.Get(1))
+ }).Return(tc.giveErr)
+
+ h := Handler{globalRuleStore: mStore}
+ ctx := droplet.NewContext()
+ ctx.SetInput(tc.giveInput)
+ ctx.SetContext(tc.giveCtx)
+ ret, err := h.BatchDelete(ctx)
+ assert.True(t, methodCalled)
+ assert.Equal(t, tc.wantErr, err)
+ assert.Equal(t, tc.wantRet, ret)
+ })
+ }
+}
diff --git a/api/internal/route.go b/api/internal/route.go
index f36e819..02e1650 100644
--- a/api/internal/route.go
+++ b/api/internal/route.go
@@ -31,6 +31,7 @@ import (
"github.com/apisix/manager-api/internal/handler"
"github.com/apisix/manager-api/internal/handler/authentication"
"github.com/apisix/manager-api/internal/handler/consumer"
+ "github.com/apisix/manager-api/internal/handler/global_rule"
"github.com/apisix/manager-api/internal/handler/healthz"
"github.com/apisix/manager-api/internal/handler/label"
"github.com/apisix/manager-api/internal/handler/plugin"
@@ -68,6 +69,7 @@ func SetUpRouter() *gin.Engine {
plugin.NewHandler,
healthz.NewHandler,
authentication.NewHandler,
+ global_rule.NewHandler,
route_online_debug.NewHandler,
server_info.NewHandler,
label.NewHandler,
diff --git a/api/test/e2e/base.go b/api/test/e2e/base.go
index 5c79f85..4bed9cb 100644
--- a/api/test/e2e/base.go
+++ b/api/test/e2e/base.go
@@ -238,5 +238,4 @@ func testCaseCheck(tc HttpTestCase, t *testing.T) {
resp.Body().Contains(tc.ExpectBody)
}
})
-
}
diff --git a/api/test/e2e/global_rule_test.go b/api/test/e2e/global_rule_test.go
new file mode 100644
index 0000000..19a59b3
--- /dev/null
+++ b/api/test/e2e/global_rule_test.go
@@ -0,0 +1,234 @@
+/*
+ * 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 TestGlobalRule(t *testing.T) {
+ tests := []HttpTestCase{
+ {
+ Desc: "make sure the route doesn't exist",
+ Object: APISIXExpect(t),
+ Method: http.MethodGet,
+ Path: "/hello",
+ ExpectStatus: http.StatusNotFound,
+ ExpectBody: `{"error_msg":"404 Route Not Found"}`,
+ },
+ {
+ Desc: "create route",
+ Object: ManagerApiExpect(t),
+ Method: http.MethodPut,
+ Path: "/apisix/admin/routes/r1",
+ Body: `{
+ "uri": "/hello",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": [{
+ "host": "172.16.238.20",
+ "port": 1981,
+ "weight": 1
+ }]
+ }
+ }`,
+ Headers: map[string]string{"Authorization": token},
+ ExpectStatus: http.StatusOK,
+ },
+ {
+ Desc: "create global rule",
+ Object: ManagerApiExpect(t),
+ Path: "/apisix/admin/global_rules/1",
+ Method: http.MethodPut,
+ Body: `{
+ "id": "1",
+ "plugins": {
+ "response-rewrite": {
+ "headers": {
+ "X-VERSION":"1.0"
+ }
+ },
+ "uri-blocker": {
+ "block_rules":
["select.+(from|limit)", "(?:(union(.*?)select))"]
+ }
+ }
+ }`,
+ Headers: map[string]string{"Authorization": token},
+ ExpectStatus: http.StatusOK,
+ },
+ {
+ Desc: "verify route with header",
+ Object: APISIXExpect(t),
+ Method: http.MethodGet,
+ Path: "/hello",
+ ExpectStatus: http.StatusOK,
+ ExpectBody: "hello world",
+ ExpectHeaders: map[string]string{"X-VERSION": "1.0"},
+ Sleep: sleepTime,
+ },
+ {
+ Desc: "verify route that should be blocked",
+ Object: APISIXExpect(t),
+ Method: http.MethodGet,
+ Path: "/hello",
+ Query: "name=;select%20from%20sys",
+ ExpectStatus: http.StatusForbidden,
+ ExpectHeaders: map[string]string{"X-VERSION": "1.0"},
+ },
+ {
+ Desc: "update route with same plugin
response-rewrite",
+ Object: ManagerApiExpect(t),
+ Method: http.MethodPut,
+ Path: "/apisix/admin/routes/r1",
+ Body: `{
+ "uri": "/hello",
+ "plugins": {
+ "response-rewrite": {
+ "headers": {
+ "X-VERSION":"2.0"
+ }
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": [{
+ "host": "172.16.238.20",
+ "port": 1981,
+ "weight": 1
+ }]
+ }
+ }`,
+ Headers: map[string]string{"Authorization": token},
+ ExpectStatus: http.StatusOK,
+ },
+ {
+ Desc: "verify route that header should be the
same as the route config",
+ Object: APISIXExpect(t),
+ Method: http.MethodGet,
+ Path: "/hello",
+ ExpectStatus: http.StatusOK,
+ ExpectBody: "hello world",
+ ExpectHeaders: map[string]string{"X-VERSION": "2.0"},
+ Sleep: sleepTime,
+ },
+ {
+ Desc: "the uncovered global plugin should
works",
+ Object: APISIXExpect(t),
+ Method: http.MethodGet,
+ Path: "/hello",
+ Query: "name=;select%20from%20sys",
+ ExpectStatus: http.StatusForbidden,
+ //ExpectHeaders: map[string]string{"X-VERSION":"2.0"},
+ },
+ {
+ Desc: "route patch to enable key-auth",
+ Object: ManagerApiExpect(t),
+ Method: http.MethodPatch,
+ Path: "/apisix/admin/global_rules/1/plugins",
+ Body: `{
+ "response-rewrite": {
+ "headers": {
+ "X-VERSION":"1.0"
+ }
+ },
+ "key-auth": {}
+ }`,
+ Headers: map[string]string{"Authorization": token},
+ ExpectStatus: http.StatusOK,
+ },
+ {
+ Desc: "make sure that patch succeeded",
+ Object: APISIXExpect(t),
+ Method: http.MethodGet,
+ Path: "/hello",
+ ExpectStatus: http.StatusUnauthorized,
+ Sleep: sleepTime,
+ },
+ {
+ Desc: "route patch to disable key-auth",
+ Object: ManagerApiExpect(t),
+ Method: http.MethodPatch,
+ Path: "/apisix/admin/global_rules/1",
+ Body: `{
+ "plugins": {
+ "key-auth": null
+ }
+ }`,
+ Headers: map[string]string{"Authorization": token},
+ ExpectStatus: http.StatusOK,
+ },
+ {
+ Desc: "make sure that patch succeeded",
+ Object: APISIXExpect(t),
+ Method: http.MethodGet,
+ Path: "/hello",
+ ExpectStatus: http.StatusOK,
+ ExpectBody: "hello world",
+ ExpectHeaders: map[string]string{"X-VERSION": "2.0"},
+ Sleep: sleepTime,
+ },
+ {
+ Desc: "delete global rule",
+ Object: ManagerApiExpect(t),
+ Method: http.MethodDelete,
+ Path: "/apisix/admin/global_rules/1",
+ Headers: map[string]string{"Authorization": token},
+ ExpectStatus: http.StatusOK,
+ },
+ {
+ Desc: "make sure the global rule has been
deleted",
+ Object: ManagerApiExpect(t),
+ Method: http.MethodGet,
+ Path: "/apisix/admin/global_rules/1",
+ Headers: map[string]string{"Authorization": token},
+ ExpectStatus: http.StatusNotFound,
+ ExpectBody: `{"code":10001,"message":"data not
found"`,
+ Sleep: sleepTime,
+ },
+ {
+ Desc: "verify route that should not be
blocked",
+ Object: APISIXExpect(t),
+ Method: http.MethodGet,
+ Path: "/hello",
+ Query: "name=;select%20from%20sys",
+ ExpectStatus: http.StatusOK,
+ ExpectHeaders: map[string]string{"X-VERSION": "2.0"},
+ },
+ {
+ Desc: "delete route",
+ Object: ManagerApiExpect(t),
+ Method: http.MethodDelete,
+ Path: "/apisix/admin/routes/r1",
+ Headers: map[string]string{"Authorization": token},
+ ExpectStatus: http.StatusOK,
+ },
+ {
+ Desc: "make sure the route has been deleted",
+ Object: APISIXExpect(t),
+ Method: http.MethodGet,
+ Path: "/hello",
+ ExpectStatus: http.StatusNotFound,
+ ExpectBody: `{"error_msg":"404 Route Not Found"}`,
+ Sleep: sleepTime,
+ },
+ }
+
+ for _, tc := range tests {
+ testCaseCheck(tc, t)
+ }
+}