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

api: add Rate-limit config update API #4843

Merged
merged 85 commits into from Jun 15, 2022
Merged
Show file tree
Hide file tree
Changes from 78 commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
e8102b4
add limit config
CabinfeverB Apr 24, 2022
b9bcfbf
add rate-limit config
CabinfeverB Apr 24, 2022
1a93604
fix test
CabinfeverB Apr 24, 2022
cefebc9
remove future code
CabinfeverB Apr 24, 2022
826ef7f
add rate-limit config presist reload
CabinfeverB Apr 25, 2022
8f24203
add rate-limit config api
CabinfeverB Apr 25, 2022
176d73b
fix test
CabinfeverB Apr 25, 2022
fdbf344
Merge branch 'master' into rata_limit_config_api
CabinfeverB Apr 25, 2022
0d73c53
fix comment
CabinfeverB Apr 25, 2022
3318100
add test
CabinfeverB Apr 25, 2022
4e2a840
fix comment
CabinfeverB Apr 25, 2022
6d1c289
Merge branch 'limiter_config' into server_rate_limit
CabinfeverB Apr 25, 2022
001cb93
Merge branch 'limiter_config' into rata_limit_config_api
CabinfeverB Apr 25, 2022
713c8a8
Merge branch 'master' into limiter_config
CabinfeverB Apr 25, 2022
1744bd6
Merge branch 'limiter_config' into rata_limit_config_api
CabinfeverB Apr 25, 2022
456d504
add test
CabinfeverB Apr 25, 2022
049c4d6
fix typo
CabinfeverB Apr 25, 2022
1b183a0
fix typo
CabinfeverB Apr 26, 2022
900df00
add service config
CabinfeverB Apr 28, 2022
133ab15
add service config
CabinfeverB Apr 28, 2022
899e1f9
merge service config
CabinfeverB Apr 28, 2022
71bbf8c
Merge branch 'master' into service_config
CabinfeverB Apr 28, 2022
79eb437
merge master
CabinfeverB Apr 28, 2022
f9bd7ec
merge master
CabinfeverB May 18, 2022
3a6be7c
merge master
CabinfeverB May 18, 2022
124658c
merge master
CabinfeverB May 18, 2022
cadaebb
merge master
CabinfeverB May 18, 2022
6cd4621
merge master
CabinfeverB May 18, 2022
464bc85
merge master
CabinfeverB May 18, 2022
f57c923
Merge branch 'master' into server_rate_limit
CabinfeverB May 18, 2022
afdbb90
merge master
CabinfeverB May 18, 2022
c637fb0
merge master
CabinfeverB May 18, 2022
273a970
merge master
CabinfeverB May 18, 2022
c87f49b
fix test
CabinfeverB May 18, 2022
b29445d
allow list
CabinfeverB May 18, 2022
f18956e
fix
CabinfeverB May 19, 2022
6e0819c
fix
CabinfeverB May 19, 2022
1be372a
Merge branch 'master' into limiter_config
rleungx May 24, 2022
9988a7e
change into point
CabinfeverB May 25, 2022
052e49c
Merge branch 'limiter_config' of github.com:CabinfeverB/pd into limit…
CabinfeverB May 25, 2022
1ee0dbc
Merge remote-tracking branch 'upstream/master' into limiter_config
CabinfeverB May 25, 2022
c12a3a2
Merge branch 'limiter_config' into server_rate_limit
CabinfeverB May 25, 2022
e682407
change into point
CabinfeverB May 25, 2022
c3b65a9
Merge branch 'limiter_config' into server_rate_limit
CabinfeverB May 25, 2022
24fa55b
merge
CabinfeverB May 25, 2022
c35fab6
Merge branch 'server_rate_limit' into rata_limit_config_api
CabinfeverB May 25, 2022
3d87b64
merge
CabinfeverB May 25, 2022
4f93cac
merge
CabinfeverB May 25, 2022
9022724
merge
CabinfeverB May 25, 2022
db80b3c
merge
CabinfeverB May 25, 2022
df78088
merge
CabinfeverB May 25, 2022
76f347c
merge
CabinfeverB May 25, 2022
f640762
remove unnecessary code
CabinfeverB May 26, 2022
4a250d0
Merge branch 'limiter_config' into server_rate_limit
CabinfeverB May 26, 2022
cc697ea
Merge branch 'server_rate_limit' into rata_limit_config_api
CabinfeverB May 26, 2022
099e49a
fix type
CabinfeverB May 26, 2022
40832d8
remove config in Limiter
CabinfeverB May 30, 2022
5799d37
fix eps
CabinfeverB May 31, 2022
f5af578
Merge branch 'limiter_config' into server_rate_limit
CabinfeverB Jun 2, 2022
2653577
Merge branch 'server_rate_limit' into rata_limit_config_api
CabinfeverB Jun 2, 2022
5c55e8b
add json merge util
CabinfeverB Jun 13, 2022
0e4a60d
add json merge util
CabinfeverB Jun 13, 2022
392ce5a
add json merge util
CabinfeverB Jun 13, 2022
de4f4af
merge master
CabinfeverB Jun 13, 2022
c332aae
add comment
CabinfeverB Jun 13, 2022
0dc5dc1
Merge branch 'merge_config_util' into rata_limit_config_api
CabinfeverB Jun 13, 2022
39c0868
add comment
CabinfeverB Jun 13, 2022
21664fc
Merge branch 'merge_config_util' into rata_limit_config_api
CabinfeverB Jun 13, 2022
cc6bd2b
add comment
CabinfeverB Jun 13, 2022
2c7c24a
add test
CabinfeverB Jun 13, 2022
2d0f39f
Merge branch 'merge_config_util' into rata_limit_config_api
CabinfeverB Jun 13, 2022
c2f6f23
add test
CabinfeverB Jun 13, 2022
79ada21
fix
CabinfeverB Jun 14, 2022
7161d5c
fix
CabinfeverB Jun 14, 2022
c741126
fix
CabinfeverB Jun 14, 2022
1fedfbc
fix
CabinfeverB Jun 14, 2022
e9ee0e2
fix
CabinfeverB Jun 14, 2022
dd70f46
fix
CabinfeverB Jun 14, 2022
b15ac5a
fix type
CabinfeverB Jun 14, 2022
2d9f124
fix typo
CabinfeverB Jun 14, 2022
97d1b9e
change url path
CabinfeverB Jun 14, 2022
fa28dd4
fix
CabinfeverB Jun 14, 2022
aca84d7
fix
CabinfeverB Jun 15, 2022
d558575
fix
CabinfeverB Jun 15, 2022
3774d3b
Merge branch 'master' into rata_limit_config_api
ti-chi-bot Jun 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
49 changes: 49 additions & 0 deletions pkg/jsonutil/jsonutil.go
@@ -0,0 +1,49 @@
// Copyright 2022 TiKV Project Authors.
//
// Licensed 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 jsonutil

import (
"bytes"
"encoding/json"

"github.com/tikv/pd/pkg/reflectutil"
)

// AddKeyValue is used to add a key value pair into `old`
func AddKeyValue(old interface{}, key string, value interface{}) (updated bool, found bool, err error) {
data, err := json.Marshal(map[string]interface{}{key: value})
if err != nil {
return false, false, err
}
return MergeJSONObject(old, data)
}

// MergeJSONObject is used to merge a marshaled json object into v
func MergeJSONObject(v interface{}, data []byte) (updated bool, found bool, err error) {
old, _ := json.Marshal(v)
if err := json.Unmarshal(data, v); err != nil {
return false, false, err
}
new, _ := json.Marshal(v)
if !bytes.Equal(old, new) {
return true, true, nil
}
m := make(map[string]interface{})
if err := json.Unmarshal(data, &m); err != nil {
return false, false, err
}
found = reflectutil.FindSameFieldByJSON(v, m)
return false, found, nil
}
65 changes: 65 additions & 0 deletions pkg/jsonutil/jsonutil_test.go
@@ -0,0 +1,65 @@
// Copyright 2022 TiKV Project Authors.
//
// Licensed 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 jsonutil

import (
"testing"

"github.com/stretchr/testify/require"
)

type testJSONStructLevel1 struct {
Name string `json:"name"`
Sub1 testJSONStructLevel2 `json:"sub1"`
Sub2 testJSONStructLevel2 `json:"sub2"`
}

type testJSONStructLevel2 struct {
SubName string `json:"sub-name"`
}

func TestJSONUtil(t *testing.T) {
t.Parallel()
re := require.New(t)
father := &testJSONStructLevel1{
Name: "father",
}
son1 := &testJSONStructLevel2{
SubName: "son1",
}
update, found, err := AddKeyValue(&father, "sub1", &son1)
re.NoError(err)
re.True(update)
re.True(found)

son2 := &testJSONStructLevel2{
SubName: "son2",
}

update, found, err = AddKeyValue(father, "sub2", &son2)
re.NoError(err)
re.True(update)
re.True(found)

update, found, err = AddKeyValue(father, "sub3", &son2)
re.NoError(err)
re.False(update)
re.False(found)

update, found, err = AddKeyValue(father, "sub2", &son2)
re.NoError(err)
re.False(update)
re.True(found)
}
43 changes: 5 additions & 38 deletions server/api/config.go
Expand Up @@ -15,7 +15,6 @@
package api

import (
"bytes"
"encoding/json"
"fmt"
"io"
Expand All @@ -29,6 +28,7 @@ import (
"github.com/pingcap/errors"
"github.com/pingcap/log"
"github.com/tikv/pd/pkg/apiutil"
"github.com/tikv/pd/pkg/jsonutil"
"github.com/tikv/pd/pkg/logutil"
"github.com/tikv/pd/pkg/reflectutil"
"github.com/tikv/pd/server"
Expand Down Expand Up @@ -164,12 +164,7 @@ func (h *confHandler) updateConfig(cfg *config.Config, key string, value interfa
}

func (h *confHandler) updateSchedule(config *config.Config, key string, value interface{}) error {
data, err := json.Marshal(map[string]interface{}{key: value})
if err != nil {
return err
}

updated, found, err := mergeConfig(&config.Schedule, data)
updated, found, err := jsonutil.AddKeyValue(&config.Schedule, key, value)
if err != nil {
return err
}
Expand All @@ -185,12 +180,7 @@ func (h *confHandler) updateSchedule(config *config.Config, key string, value in
}

func (h *confHandler) updateReplication(config *config.Config, key string, value interface{}) error {
data, err := json.Marshal(map[string]interface{}{key: value})
if err != nil {
return err
}

updated, found, err := mergeConfig(&config.Replication, data)
updated, found, err := jsonutil.AddKeyValue(&config.Replication, key, value)
if err != nil {
return err
}
Expand All @@ -212,8 +202,7 @@ func (h *confHandler) updateReplicationModeConfig(config *config.Config, key []s
if err != nil {
return err
}

updated, found, err := mergeConfig(&config.ReplicationMode, data)
updated, found, err := jsonutil.MergeJSONObject(&config.ReplicationMode, data)
if err != nil {
return err
}
Expand All @@ -229,12 +218,7 @@ func (h *confHandler) updateReplicationModeConfig(config *config.Config, key []s
}

func (h *confHandler) updatePDServerConfig(config *config.Config, key string, value interface{}) error {
data, err := json.Marshal(map[string]interface{}{key: value})
if err != nil {
return err
}

updated, found, err := mergeConfig(&config.PDServerCfg, data)
updated, found, err := jsonutil.AddKeyValue(&config.PDServerCfg, key, value)
if err != nil {
return err
}
Expand Down Expand Up @@ -286,23 +270,6 @@ func getConfigMap(cfg map[string]interface{}, key []string, value interface{}) m
return cfg
}

func mergeConfig(v interface{}, data []byte) (updated bool, found bool, err error) {
old, _ := json.Marshal(v)
if err := json.Unmarshal(data, v); err != nil {
return false, false, err
}
new, _ := json.Marshal(v)
if !bytes.Equal(old, new) {
return true, true, nil
}
m := make(map[string]interface{})
if err := json.Unmarshal(data, &m); err != nil {
return false, false, err
}
found = reflectutil.FindSameFieldByJSON(v, m)
return false, found, nil
}

// @Tags config
// @Summary Get schedule config.
// @Produce json
Expand Down
1 change: 1 addition & 0 deletions server/api/router.go
Expand Up @@ -285,6 +285,7 @@ func createRouter(prefix string, svr *server.Server) *mux.Router {
serviceMiddlewareHandler := newServiceMiddlewareHandler(svr, rd)
registerFunc(apiRouter, "/service-middleware/config", serviceMiddlewareHandler.GetServiceMiddlewareConfig, setMethods("GET"))
registerFunc(apiRouter, "/service-middleware/config", serviceMiddlewareHandler.SetServiceMiddlewareConfig, setMethods("POST"), setAuditBackend(localLog))
registerFunc(apiRouter, "/service-middleware/rate-limit/config", serviceMiddlewareHandler.SetRatelimitConfig, setMethods("POST"), setAuditBackend(localLog))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/service-middleware/rate-limit/config is used to update Incrementally. If users want to overwrite config, we can use /service-middleware/config


logHandler := newLogHandler(svr, rd)
registerFunc(apiRouter, "/admin/log", logHandler.SetLogLevel, setMethods("POST"), setAuditBackend(localLog))
Expand Down
125 changes: 103 additions & 22 deletions server/api/service_middleware.go
Expand Up @@ -23,6 +23,9 @@ import (
"strings"

"github.com/pingcap/errors"
"github.com/tikv/pd/pkg/apiutil"
"github.com/tikv/pd/pkg/jsonutil"
"github.com/tikv/pd/pkg/ratelimit"
"github.com/tikv/pd/pkg/reflectutil"
"github.com/tikv/pd/server"
"github.com/tikv/pd/server/config"
Expand Down Expand Up @@ -107,18 +110,13 @@ func (h *serviceMiddlewareHandler) updateServiceMiddlewareConfig(cfg *config.Ser
case "audit":
return h.updateAudit(cfg, kp[len(kp)-1], value)
case "rate-limit":
return h.updateRateLimit(cfg, kp[len(kp)-1], value)
return h.svr.UpdateRateLimit(&cfg.RateLimitConfig, kp[len(kp)-1], value)
}
return errors.Errorf("config prefix %s not found", kp[0])
}

func (h *serviceMiddlewareHandler) updateAudit(config *config.ServiceMiddlewareConfig, key string, value interface{}) error {
data, err := json.Marshal(map[string]interface{}{key: value})
if err != nil {
return err
}

updated, found, err := mergeConfig(&config.AuditConfig, data)
updated, found, err := jsonutil.AddKeyValue(&config.AuditConfig, key, value)
if err != nil {
return err
}
Expand All @@ -133,23 +131,106 @@ func (h *serviceMiddlewareHandler) updateAudit(config *config.ServiceMiddlewareC
return err
}

func (h *serviceMiddlewareHandler) updateRateLimit(config *config.ServiceMiddlewareConfig, key string, value interface{}) error {
data, err := json.Marshal(map[string]interface{}{key: value})
if err != nil {
return err
// @Tags service_middleware
// @Summary update ratelimit config
// @Param body body object string "json params"
// @Produce json
// @Success 200 {string} string ""
// @Failure 400 {string} string ""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

500 is missing

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// @Failure 400 {string} string ""
// @Failure 400 {string} string "The input is invalid."

// @Failure 500 {string} string "config item not found"
// @Router /service-middleware/rate-limit/config [POST]
func (h *serviceMiddlewareHandler) SetRatelimitConfig(w http.ResponseWriter, r *http.Request) {
var input map[string]interface{}
if err := apiutil.ReadJSONRespondError(h.rd, w, r.Body, &input); err != nil {
return
}

updated, found, err := mergeConfig(&config.RateLimitConfig, data)
if err != nil {
return err
typeStr, ok := input["type"].(string)
if !ok {
h.rd.JSON(w, http.StatusBadRequest, "The type is empty.")
return
}

if !found {
return errors.Errorf("config item %s not found", key)
var serviceLabel string
switch typeStr {
case "label":
serviceLabel, ok = input["label"].(string)
if !ok || len(serviceLabel) == 0 {
h.rd.JSON(w, http.StatusBadRequest, "The label is empty.")
return
}
if len(h.svr.GetServiceLabels(serviceLabel)) == 0 {
h.rd.JSON(w, http.StatusBadRequest, "There is no label matched.")
return
}
case "path":
method, _ := input["method"].(string)
path, ok := input["path"].(string)
if !ok || len(path) == 0 {
h.rd.JSON(w, http.StatusBadRequest, "The path is empty.")
return
}
serviceLabel = h.svr.GetAPIAccessServiceLabel(apiutil.NewAccessPath(path, method))
if len(serviceLabel) == 0 {
h.rd.JSON(w, http.StatusBadRequest, "There is no label matched.")
return
}
default:
h.rd.JSON(w, http.StatusBadRequest, "The type is invalid.")
return
}

if updated {
err = h.svr.SetRateLimitConfig(config.RateLimitConfig)
if h.svr.IsInRateLimitAllowList(serviceLabel) {
h.rd.JSON(w, http.StatusBadRequest, "This service is in allow list whose config can not be changed.")
return
}
return err
cfg := h.svr.GetRateLimitConfig().LimiterConfig[serviceLabel]
// update concurrency limiter
concurrencyUpdatedFlag := "Concurrency limiter is not changed."
concurrencyFloat, okc := input["concurrency"].(float64)
if okc {
concurrency := uint64(concurrencyFloat)
cfg.ConcurrencyLimit = concurrency
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
concurrency := uint64(concurrencyFloat)
cfg.ConcurrencyLimit = concurrency
}
cfg.ConcurrencyLimit = uint64(concurrencyFloat)
}

// update qps rate limiter
qpsRateUpdatedFlag := "QPS rate limiter is not changed."
qps, okq := input["qps"].(float64)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if QPS is less than 0, does it belong to an error?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It means delete QPS limit

if okq {
brust := 0

if int(qps) > 1 {
brust = int(qps)
} else if qps > 0 {
brust = 1
}
cfg.QPS = qps
cfg.QPSBurst = brust
}
if !okc && !okq {
h.rd.JSON(w, http.StatusOK, "No changed.")
} else {
status := h.svr.UpdateServiceRateLimiter(serviceLabel, ratelimit.UpdateDimensionConfig(&cfg))
switch {
case status&ratelimit.QPSChanged != 0:
qpsRateUpdatedFlag = "QPS rate limiter is changed."
case status&ratelimit.QPSDeleted != 0:
qpsRateUpdatedFlag = "QPS rate limiter is deleted."
}
switch {
CabinfeverB marked this conversation as resolved.
Show resolved Hide resolved
case status&ratelimit.ConcurrencyChanged != 0:
concurrencyUpdatedFlag = "Concurrency limiter is changed."
case status&ratelimit.ConcurrencyDeleted != 0:
concurrencyUpdatedFlag = "Concurrency limiter is deleted."
}
err := h.svr.UpdateRateLimitConfig("limiter-config", serviceLabel, cfg)
if err != nil {
h.rd.JSON(w, http.StatusInternalServerError, err.Error())
} else {
result := rateLimitResult{concurrencyUpdatedFlag, qpsRateUpdatedFlag, h.svr.GetServiceMiddlewareConfig().RateLimitConfig.LimiterConfig}
h.rd.JSON(w, http.StatusOK, result)
}
}
}

type rateLimitResult struct {
ConcurrencyUpdatedFlag string `json:"Concurrency"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use small letter?

QpsRateUpdatedFlag string `json:"qps"`
LimiterConfig map[string]ratelimit.DimensionConfig `json:"new-config"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LimiterConfig map[string]ratelimit.DimensionConfig `json:"new-config"`
LimiterConfig map[string]ratelimit.DimensionConfig `json:"limiter-config"`

}