From b7d614e9c22b9526e818f7453d51d697523b493f Mon Sep 17 00:00:00 2001 From: disksing Date: Wed, 6 Nov 2019 20:53:42 +0800 Subject: [PATCH] placement: add API support (#1894) Signed-off-by: disksing --- docs/api.html | 2 +- server/api/api.raml | 134 ++++++++++++++++++++++++++- server/api/router.go | 9 ++ server/api/rule.go | 210 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 server/api/rule.go diff --git a/docs/api.html b/docs/api.html index 6c1dbe1e45a..79602c773c6 100644 --- a/docs/api.html +++ b/docs/api.html @@ -166,4 +166,4 @@ .resource-modal li > ul { margin-bottom: 1em; } -

/cluster/status

Cluster status.

get

Get cluster status.

/version

The version of PD server.

get

Get the version of PD server.

/status

The build info of PD server.

get

Get the build info of PD server.

/diagnose

Diagnostic information of the cluster.

get

/members

The PD servers in the cluster.

get

List all PD servers in the cluster.

A specific PD server.

delete

Remove a PD server from the cluster.

post

Set leader priority of a PD member.

A specific PD server.

delete

Remove a PD server from the cluster.

/leader

The leader PD server of the cluster.

get

Get the leader PD server of the cluster.

post

Transfer leadership to another PD server.

post

Transfer leadership to the specific PD server.

/health

Health status of PD servers.

get

/config

PD cluster configuration.

get

Get full config.

post

Update a config item.

Schedule configuration.

get

Get schedule config.

post

Update a schedule config item.

Replication configuration.

get

Get replication config.

post

Update a replication config item.

The label property configuration.

get

Get label property config.

post

Update label property config item.

/stores

The stores in the cluster.

get

Get stores in the cluster.

The balance rate limit for all stores.

get

Get all stores' balance rate limit.

post

Set all stores' balance rate limit.

Remove all tombstone stores.

delete

Remove all tombstone stores.

/store/{storeId}

A specific store.

get

Get a store's information.

delete

Take down a store from the cluster.

The state for the specific store.

post

Set the store's state.

The label for the specific store.

post

Set the store's label.

The weight for the specific store.

post

Set the store's leader/region weight.

The balance rate limit for the specific store.

post

Set the store's balance rate limit.

/labels

The store label values in the cluster.

get

List all label values.

get

List stores that have specific label values.

/region

A specific region in the cluster.

get

Search for a region by region ID.

get

Search for a region by a key.

/regions

The regions in the cluster.

get

List all regions in the cluster.

get

Get region count in the cluster.

get

List regions with the highest write flow.

get

List regions with the highest read flow.

get

List regions with the largest conf version.

get

List regions with the largest version.

get

List regions with the largest size.

get

List regions start from a key.

get

List regions with unhealthy status.

get

List sibling regions of a specific region.

get

List all regions of a specific store.

/schedulers

Running schedulers.

get

List running schedulers.

post

Create a scheduler.

A specific scheduler.

delete

Delete a scheduler.

/operators

Pending operators.

get

List pending operators.

post

Create an operator.

A specific Region's pending operator.

get

Get a Region's pending operator.

delete

Cancel a Region's pending operator.

/hotspot

The hot spots status in the cluster.

get

List the hot write regions.

get

List the hot read regions.

get

List the hot stores.

/stats

Statistics of the cluster.

get

Get region statistics of a specified range.

/trend

Trend of data growth and movements.

get

Get the growth and changes of data in the most recent period of time.

/admin

delete

Drop a specific region from cache.

The log level of PD server.

post

Set log level.

\ No newline at end of file +

/cluster/status

Cluster status.

get

Get cluster status.

/version

The version of PD server.

get

Get the version of PD server.

/status

The build info of PD server.

get

Get the build info of PD server.

/diagnose

Diagnostic information of the cluster.

get

/members

The PD servers in the cluster.

get

List all PD servers in the cluster.

A specific PD server.

delete

Remove a PD server from the cluster.

post

Set leader priority of a PD member.

A specific PD server.

delete

Remove a PD server from the cluster.

/leader

The leader PD server of the cluster.

get

Get the leader PD server of the cluster.

post

Transfer leadership to another PD server.

post

Transfer leadership to the specific PD server.

/health

Health status of PD servers.

get

/config

PD cluster configuration.

get

Get full config.

post

Update a config item.

Schedule configuration.

get

Get schedule config.

post

Update a schedule config item.

Replication configuration.

get

Get replication config.

post

Update a replication config item.

The label property configuration.

get

Get label property config.

post

Update label property config item.

Placement rules.

get

Get all placement rules.

Placement rules of a group.

get

Get placement rules of a group.

Placement rules matched by a region.

get

Get placement rules matched by a region.

Placement rules matched by a key.

get

Get placement rules matched by a key.

A Placement Rule.

get

Get a single Placement Rule.

delete

Delete a Placement Rule.

A Placement Rule.

post

Add or update a Placement rule.

/stores

The stores in the cluster.

get

Get stores in the cluster.

The balance rate limit for all stores.

get

Get all stores' balance rate limit.

post

Set all stores' balance rate limit.

Remove all tombstone stores.

delete

Remove all tombstone stores.

/store/{storeId}

A specific store.

get

Get a store's information.

delete

Take down a store from the cluster.

The state for the specific store.

post

Set the store's state.

The label for the specific store.

post

Set the store's label.

The weight for the specific store.

post

Set the store's leader/region weight.

The balance rate limit for the specific store.

post

Set the store's balance rate limit.

/labels

The store label values in the cluster.

get

List all label values.

get

List stores that have specific label values.

/region

A specific region in the cluster.

get

Search for a region by region ID.

get

Search for a region by a key.

/regions

The regions in the cluster.

get

List all regions in the cluster.

get

Get region count in the cluster.

get

List regions with the highest write flow.

get

List regions with the highest read flow.

get

List regions with the largest conf version.

get

List regions with the largest version.

get

List regions with the largest size.

get

List regions start from a key.

get

List regions with unhealthy status.

get

List sibling regions of a specific region.

get

List all regions of a specific store.

/schedulers

Running schedulers.

get

List running schedulers.

post

Create a scheduler.

A specific scheduler.

delete

Delete a scheduler.

/operators

Pending operators.

get

List pending operators.

post

Create an operator.

A specific Region's pending operator.

get

Get a Region's pending operator.

delete

Cancel a Region's pending operator.

/hotspot

The hot spots status in the cluster.

get

List the hot write regions.

get

List the hot read regions.

get

List the hot stores.

/stats

Statistics of the cluster.

get

Get region statistics of a specified range.

/trend

Trend of data growth and movements.

get

Get the growth and changes of data in the most recent period of time.

/admin

delete

Drop a specific region from cache.

The log level of PD server.

post

Set log level.

\ No newline at end of file diff --git a/server/api/api.raml b/server/api/api.raml index 5a3ba7159b4..0ae99d721d2 100644 --- a/server/api/api.raml +++ b/server/api/api.raml @@ -374,6 +374,32 @@ types: type: string enum: [ leader, region ] count: integer + + Rule: + type: object + properties: + group_id: string + id: string + index?: integer + override?: boolean + start_key: string + end_key: string + role: + type: string + enum: [voter, leader, follower, learner] + count: + type: integer + minimum: 1 + label_constraints: LabelConstraint[] + location_labels: string[] + LabelConstraint: + type: object + properties: + key: string + op: + type: string + enum: [ in, notIn, exists, notExists ] + values?: string[] /cluster/status: description: Cluster status. @@ -608,7 +634,113 @@ types: description: The config is updated. 500: description: PD server failed to proceed the request. - + /rules: + description: Placement rules. + get: + description: Get all placement rules. + responses: + 200: + body: + application/json: + type: Rule[] + 412: + description: Placement rules feature is not enabled. + 500: + description: PD server failed to proceed the request. + /rules/group/{group}: + description: Placement rules of a group. + uriParameters: + group: string + get: + description: Get placement rules of a group. + responses: + 200: + body: + application/json: + type: Rule[] + 412: + description: Placement rules feature is not enabled. + 500: + description: PD server failed to proceed the request. + /rules/region/{region}: + description: Placement rules matched by a region. + uriParameters: + region: integer + get: + description: Get placement rules matched by a region. + responses: + 200: + body: + application/json: + type: Rule[] + 400: + description: The region ID is invalid. + 404: + description: The region is not found. + 500: + description: PD server failed to proceed the request. + /rules/key/{key}: + description: Placement rules matched by a key. + uriParameters: + key: string + get: + description: Get placement rules matched by a key. + responses: + 200: + body: + application/json: + type: Rule[] + 400: + description: The key is not in hex format. + 412: + description: Placement rules feature is not enabled. + 500: + description: PD server failed to proceed the request. + /rule/{group}/{id}: + description: A Placement Rule. + uriParameters: + group: string + id: string + get: + description: Get a single Placement Rule. + responses: + 200: + body: + application/json: + type: Rule + 404: + description: The Rule is not found. + 412: + description: Placement rules feature is not enabled. + 500: + description: PD server failed to proceed the request. + delete: + description: Delete a Placement Rule. + responses: + 200: + description: The Rule is delete. + 412: + description: Placement rules feature is not enabled. + 500: + description: PD server failed to proceed the request. + /rule: + description: A Placement Rule. + post: + description: Add or update a Placement rule. + body: + application/json: + description: Placement Rule. + type: Rule + responses: + 200: + description: The rule is created or updated. + 400: + description: The input is invalid. + 412: + description: Placement rules feature is not enabled. + 500: + description: PD server failed to proceed the request. + /stores: description: The stores in the cluster. get: diff --git a/server/api/router.go b/server/api/router.go index 94186a2402d..2704e2c1e0c 100644 --- a/server/api/router.go +++ b/server/api/router.go @@ -60,6 +60,15 @@ func createRouter(prefix string, svr *server.Server) *mux.Router { router.HandleFunc("/api/v1/config/cluster-version", confHandler.GetClusterVersion).Methods("GET") router.HandleFunc("/api/v1/config/cluster-version", confHandler.SetClusterVersion).Methods("POST") + rulesHandler := newRulesHandler(svr, rd) + router.HandleFunc("/api/v1/config/rules", rulesHandler.GetAll).Methods("GET") + router.HandleFunc("/api/v1/config/rules/group/{group}", rulesHandler.GetAllByGroup).Methods("GET") + router.HandleFunc("/api/v1/config/rules/region/{region}", rulesHandler.GetAllByRegion).Methods("GET") + router.HandleFunc("/api/v1/config/rules/key/{key}", rulesHandler.GetAllByKey).Methods("GET") + router.HandleFunc("/api/v1/config/rule/{group}/{id}", rulesHandler.Get).Methods("GET") + router.HandleFunc("/api/v1/config/rule", rulesHandler.Set).Methods("POST") + router.HandleFunc("/api/v1/config/rule/{group}/{id}", rulesHandler.Delete).Methods("DELETE") + storeHandler := newStoreHandler(handler, rd) router.HandleFunc("/api/v1/store/{id}", storeHandler.Get).Methods("GET") router.HandleFunc("/api/v1/store/{id}", storeHandler.Delete).Methods("DELETE") diff --git a/server/api/rule.go b/server/api/rule.go new file mode 100644 index 00000000000..d4c4c67ecc5 --- /dev/null +++ b/server/api/rule.go @@ -0,0 +1,210 @@ +// Copyright 2019 PingCAP, Inc. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "bytes" + "encoding/hex" + "net/http" + "strconv" + + "github.com/gorilla/mux" + "github.com/pingcap/pd/pkg/apiutil" + "github.com/pingcap/pd/pkg/codec" + "github.com/pingcap/pd/server" + "github.com/pingcap/pd/server/core" + "github.com/pingcap/pd/server/schedule/placement" + "github.com/pkg/errors" + "github.com/unrolled/render" +) + +var errPlacementDisabled = errors.New("placement rules feature is disabled") + +type ruleHandler struct { + svr *server.Server + rd *render.Render +} + +func newRulesHandler(svr *server.Server, rd *render.Render) *ruleHandler { + return &ruleHandler{ + svr: svr, + rd: rd, + } +} + +func (h *ruleHandler) GetAll(w http.ResponseWriter, r *http.Request) { + cluster := h.svr.GetRaftCluster() + if cluster == nil { + h.rd.JSON(w, http.StatusInternalServerError, server.ErrNotBootstrapped.Error()) + return + } + if !cluster.IsPlacementRulesEnabled() { + h.rd.JSON(w, http.StatusPreconditionFailed, errPlacementDisabled.Error()) + return + } + rules := cluster.GetRuleManager().GetAllRules() + h.rd.JSON(w, http.StatusOK, rules) +} + +func (h *ruleHandler) GetAllByGroup(w http.ResponseWriter, r *http.Request) { + cluster := h.svr.GetRaftCluster() + if cluster == nil { + h.rd.JSON(w, http.StatusInternalServerError, server.ErrNotBootstrapped.Error()) + return + } + if !cluster.IsPlacementRulesEnabled() { + h.rd.JSON(w, http.StatusPreconditionFailed, errPlacementDisabled.Error()) + return + } + group := mux.Vars(r)["group"] + rules := cluster.GetRuleManager().GetRulesByGroup(group) + h.rd.JSON(w, http.StatusOK, rules) +} + +func (h *ruleHandler) GetAllByRegion(w http.ResponseWriter, r *http.Request) { + cluster := h.svr.GetRaftCluster() + if cluster == nil { + h.rd.JSON(w, http.StatusInternalServerError, server.ErrNotBootstrapped.Error()) + return + } + if !cluster.IsPlacementRulesEnabled() { + h.rd.JSON(w, http.StatusPreconditionFailed, errPlacementDisabled.Error()) + return + } + regionStr := mux.Vars(r)["region"] + regionID, err := strconv.ParseUint(regionStr, 10, 64) + if err != nil { + h.rd.JSON(w, http.StatusBadRequest, "invalid region id") + return + } + region := cluster.GetRegion(regionID) + if region == nil { + h.rd.JSON(w, http.StatusNotFound, server.ErrRegionNotFound(regionID).Error()) + return + } + rules := cluster.GetRuleManager().GetRulesForApplyRegion(region) + h.rd.JSON(w, http.StatusOK, rules) +} + +func (h *ruleHandler) GetAllByKey(w http.ResponseWriter, r *http.Request) { + cluster := h.svr.GetRaftCluster() + if cluster == nil { + h.rd.JSON(w, http.StatusInternalServerError, server.ErrNotBootstrapped.Error()) + return + } + if !cluster.IsPlacementRulesEnabled() { + h.rd.JSON(w, http.StatusPreconditionFailed, errPlacementDisabled.Error()) + return + } + keyHex := mux.Vars(r)["key"] + key, err := hex.DecodeString(keyHex) + if err != nil { + h.rd.JSON(w, http.StatusBadRequest, "key should be in hex format") + return + } + rules := cluster.GetRuleManager().GetRulesByKey(key) + h.rd.JSON(w, http.StatusOK, rules) +} + +func (h *ruleHandler) Get(w http.ResponseWriter, r *http.Request) { + cluster := h.svr.GetRaftCluster() + if cluster == nil { + h.rd.JSON(w, http.StatusInternalServerError, server.ErrNotBootstrapped.Error()) + return + } + if !cluster.IsPlacementRulesEnabled() { + h.rd.JSON(w, http.StatusPreconditionFailed, errPlacementDisabled.Error()) + return + } + group, id := mux.Vars(r)["group"], mux.Vars(r)["id"] + rule := cluster.GetRuleManager().GetRule(group, id) + if rule == nil { + h.rd.JSON(w, http.StatusNotFound, nil) + return + } + h.rd.JSON(w, http.StatusOK, rule) +} + +func (h *ruleHandler) Set(w http.ResponseWriter, r *http.Request) { + cluster := h.svr.GetRaftCluster() + if cluster == nil { + h.rd.JSON(w, http.StatusInternalServerError, server.ErrNotBootstrapped.Error()) + return + } + if !cluster.IsPlacementRulesEnabled() { + h.rd.JSON(w, http.StatusPreconditionFailed, errPlacementDisabled.Error()) + return + } + var rule placement.Rule + if err := apiutil.ReadJSONRespondError(h.rd, w, r.Body, &rule); err != nil { + return + } + if err := h.checkRule(&rule); err != nil { + h.rd.JSON(w, http.StatusBadRequest, err.Error()) + return + } + if err := cluster.GetRuleManager().SetRule(&rule); err != nil { + h.rd.JSON(w, http.StatusInternalServerError, err.Error()) + return + } + h.rd.JSON(w, http.StatusOK, nil) +} + +func (h *ruleHandler) checkRule(r *placement.Rule) error { + start, err := hex.DecodeString(r.StartKeyHex) + if err != nil { + return errors.Wrap(err, "start key is not in hex format") + } + end, err := hex.DecodeString(r.EndKeyHex) + if err != nil { + return errors.Wrap(err, "end key is not hex format") + } + if len(start) > 0 && bytes.Compare(end, start) <= 0 { + return errors.New("endKey should be greater than startKey") + } + + keyType := h.svr.GetConfig().PDServerCfg.KeyType + if keyType == core.Table.String() || keyType == core.Txn.String() { + if len(start) > 0 { + if _, _, err = codec.DecodeBytes(start); err != nil { + return errors.Wrapf(err, "start key should be encoded in %s mode", keyType) + } + } + if len(end) > 0 { + if _, _, err = codec.DecodeBytes(end); err != nil { + return errors.Wrapf(err, "end key should be encoded in %s mode", keyType) + } + } + } + + return nil +} + +func (h *ruleHandler) Delete(w http.ResponseWriter, r *http.Request) { + cluster := h.svr.GetRaftCluster() + if cluster == nil { + h.rd.JSON(w, http.StatusInternalServerError, server.ErrNotBootstrapped.Error()) + return + } + if !cluster.IsPlacementRulesEnabled() { + h.rd.JSON(w, http.StatusPreconditionFailed, errPlacementDisabled.Error()) + return + } + group, id := mux.Vars(r)["group"], mux.Vars(r)["id"] + if err := cluster.GetRuleManager().DeleteRule(group, id); err != nil { + h.rd.JSON(w, http.StatusInternalServerError, err.Error()) + return + } + h.rd.JSON(w, http.StatusOK, nil) +}