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

apiv2: keyspace management #5313

Merged
merged 105 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
54a9a53
storage: keyspace storage
AmoebaProtozoa Jul 5, 2022
235d463
storage: encode spaceID string as base 10
AmoebaProtozoa Jul 5, 2022
2008600
storage: add save/load keyspace ID
AmoebaProtozoa Jul 5, 2022
78ee5c6
server: keyspace
AmoebaProtozoa Jul 7, 2022
01bb8e1
make id allocator more general purporse
AmoebaProtozoa Jul 7, 2022
406a79f
use new idAllocator
AmoebaProtozoa Jul 7, 2022
60b72e9
added tests
AmoebaProtozoa Jul 8, 2022
4a7a4d2
Merge branch 'tikv:master' into keyspace_server
AmoebaProtozoa Jul 8, 2022
e3de2be
server: added concurrent update tests
AmoebaProtozoa Jul 11, 2022
6782d26
client: keyspace client
AmoebaProtozoa Jul 11, 2022
7d1fa32
server: fix updare and create request
AmoebaProtozoa Jul 12, 2022
d3d0dc8
server: fix updare and create request
AmoebaProtozoa Jul 12, 2022
db42ebf
storage: remove keyspace
AmoebaProtozoa Jul 13, 2022
f3e0e65
server: change create keyspace order
AmoebaProtozoa Jul 13, 2022
b6c248d
API: basic implementation
AmoebaProtozoa Jul 13, 2022
2311533
API: added swaggo annotations
AmoebaProtozoa Jul 14, 2022
27ba850
update comments
AmoebaProtozoa Jul 15, 2022
253b2c7
address comments
AmoebaProtozoa Jul 11, 2022
43632d9
move helper functions to utils.go
AmoebaProtozoa Jul 20, 2022
812e38e
Merge branch 'keyspace_server' of github.com:AmoebaProtozoa/pd into k…
AmoebaProtozoa Jul 20, 2022
ff7718d
get keyspace manager
AmoebaProtozoa Jul 20, 2022
a97405d
get keyspace manager
AmoebaProtozoa Jul 20, 2022
ee5a028
server: fix watch
AmoebaProtozoa Jul 20, 2022
f7e13fb
client: added integration tests
AmoebaProtozoa Jul 20, 2022
90f8dbf
server: fix watch
AmoebaProtozoa Jul 20, 2022
baf49ca
Merge branch 'keyspace_client' of github.com:AmoebaProtozoa/pd into k…
AmoebaProtozoa Jul 20, 2022
3af3d55
update update apis
AmoebaProtozoa Jul 21, 2022
09e8526
server: fix case where request does not contain initial config
AmoebaProtozoa Jul 21, 2022
1ca5ebc
api: use updated design
AmoebaProtozoa Jul 21, 2022
299f612
api: use updated design
AmoebaProtozoa Jul 21, 2022
abad762
Merge branch 'tikv:master' into keyspace_api
AmoebaProtozoa Jul 21, 2022
a983476
added JSON unmarshall method to handler request
AmoebaProtozoa Jul 21, 2022
2445ca1
added API Integration tests
AmoebaProtozoa Jul 21, 2022
32a29c6
update state integration test
AmoebaProtozoa Jul 21, 2022
73930f9
Merge branch 'master' of github.com:tikv/pd into keyspace_server
AmoebaProtozoa Jul 21, 2022
7e0c73b
server: initi default keyspace
AmoebaProtozoa Jul 21, 2022
15b8085
Merge branch 'keyspace_server' of github.com:AmoebaProtozoa/pd into k…
AmoebaProtozoa Jul 21, 2022
b3fce35
client: removed update config rpc, added default keyspace tests
AmoebaProtozoa Jul 22, 2022
0200684
Merge branch 'keyspace_client' of github.com:AmoebaProtozoa/pd into k…
AmoebaProtozoa Jul 22, 2022
156d046
Merge branch 'master' of github.com:tikv/pd into keyspace_api
AmoebaProtozoa Jul 22, 2022
fbe2581
api: fix tests
AmoebaProtozoa Jul 22, 2022
dec67b5
added RunInTxn style interface to base kv
AmoebaProtozoa Jul 22, 2022
898b2d1
added unit tests
AmoebaProtozoa Jul 22, 2022
290487b
server: export default keyspace name and id
AmoebaProtozoa Jul 22, 2022
7df25aa
client: use exported default keyspace
AmoebaProtozoa Jul 22, 2022
4a4c4db
api: add unit test for load all
AmoebaProtozoa Jul 22, 2022
8c61ff2
update comments
AmoebaProtozoa Jul 27, 2022
cf54caf
Merge branch 'master' into RunInTxn
AmoebaProtozoa Jul 27, 2022
5811b0d
storage: fix lint
AmoebaProtozoa Jul 27, 2022
a96fd68
server: export default keyspace name and id
AmoebaProtozoa Jul 22, 2022
29da7ff
client: use exported default keyspace
AmoebaProtozoa Jul 22, 2022
7cc5a59
server: export default keyspace name and id
AmoebaProtozoa Jul 22, 2022
2bd8eac
storage: fix lint
AmoebaProtozoa Jul 27, 2022
3a4426b
storage: fix lint
AmoebaProtozoa Jul 27, 2022
3fbd8e2
server: fix lint
AmoebaProtozoa Jul 27, 2022
7ada7b9
Merge branch 'keyspace_server' of github.com:AmoebaProtozoa/pd into k…
AmoebaProtozoa Jul 27, 2022
d205a80
fmt
AmoebaProtozoa Jul 27, 2022
d64d8fd
Merge branch 'keyspace_client' of github.com:AmoebaProtozoa/pd into k…
AmoebaProtozoa Jul 27, 2022
04961f7
fix lint
AmoebaProtozoa Jul 27, 2022
a6f2521
change update config to follow apiv2 standard
AmoebaProtozoa Aug 2, 2022
7d8760c
use lock group to control keyspace concurrency
AmoebaProtozoa Aug 5, 2022
20d6ed5
Merge branch 'keyspace_server' of github.com:AmoebaProtozoa/pd into k…
AmoebaProtozoa Aug 5, 2022
6f5a8ff
Merge branch 'master' of github.com:tikv/pd into keyspace_server
AmoebaProtozoa Aug 8, 2022
26990e4
typo fix
AmoebaProtozoa Aug 8, 2022
834116b
lower lockgroup test concurrency for ci test
AmoebaProtozoa Aug 8, 2022
fab7886
address comments
AmoebaProtozoa Aug 8, 2022
f4d66f1
let keyspace service wrap GrpcService, add cluster id check
AmoebaProtozoa Aug 9, 2022
7360193
make lockGroup more general
AmoebaProtozoa Aug 9, 2022
0753820
Merge branch 'keyspace_server' of github.com:AmoebaProtozoa/pd into k…
AmoebaProtozoa Aug 9, 2022
9383b4c
Merge branch 'keyspace_client' of github.com:AmoebaProtozoa/pd into k…
AmoebaProtozoa Aug 9, 2022
517b990
added init keyspace check
AmoebaProtozoa Aug 10, 2022
23cdc0a
fixed dial client leak
AmoebaProtozoa Aug 10, 2022
9b274df
lower lockgroup test concurrency
AmoebaProtozoa Aug 10, 2022
e91776c
cleanup cluster before watch keyspace test
AmoebaProtozoa Aug 10, 2022
bbac817
typo fix
AmoebaProtozoa Aug 10, 2022
bd6b9ac
reinstate checks for leadership
AmoebaProtozoa Aug 10, 2022
9dda292
reformat import for client
AmoebaProtozoa Aug 10, 2022
57c178a
Merge branch 'master' of github.com:tikv/pd into keyspace_api
AmoebaProtozoa Aug 31, 2022
af3d314
update keyspace api to new manager requests
AmoebaProtozoa Aug 31, 2022
a31704e
update keyspace api to RESTful
AmoebaProtozoa Sep 1, 2022
a6e0cbb
Merge branch 'master' of github.com:tikv/pd into keyspace_api
AmoebaProtozoa Sep 1, 2022
2dca250
swag fmt
AmoebaProtozoa Sep 1, 2022
05cf81c
address comments
AmoebaProtozoa Sep 2, 2022
0ad3539
Merge remote-tracking branch 'upstream/master' into RunInTxn
AmoebaProtozoa Jan 4, 2023
be81039
sort import
AmoebaProtozoa Jan 4, 2023
d98d3d2
Merge branch 'master' into RunInTxn
AmoebaProtozoa Jan 6, 2023
149ce0c
pin create revision when key not exist
AmoebaProtozoa Jan 9, 2023
d5f462e
init commit
AmoebaProtozoa Jan 9, 2023
dfaa8cd
fix test
AmoebaProtozoa Jan 9, 2023
3106c83
Merge remote-tracking branch 'upstream/master' into RefactorKeyspaceM…
AmoebaProtozoa Jan 9, 2023
b65de48
fix test
AmoebaProtozoa Jan 9, 2023
2b61a68
Merge branch 'RefactorKeyspaceManager' into keyspace_api
AmoebaProtozoa Jan 9, 2023
c6e1a58
fix test
AmoebaProtozoa Jan 9, 2023
fefea02
update kvproto go mod
AmoebaProtozoa Jan 10, 2023
8840e9e
Merge branch 'master' into keyspace_api
AmoebaProtozoa Jan 10, 2023
85a85c8
Merge remote-tracking branch 'upstream/master' into keyspace_api
AmoebaProtozoa Jan 12, 2023
62f7c16
remove unused manager method
AmoebaProtozoa Jan 12, 2023
59e3ac9
Merge branch 'master' into keyspace_api
AmoebaProtozoa Jan 13, 2023
3f3618b
Merge branch 'master' into keyspace_api
AmoebaProtozoa Jan 16, 2023
352a3ef
Merge branch 'master' into keyspace_api
AmoebaProtozoa Jan 17, 2023
5ea4cb6
Merge branch 'master' into keyspace_api
AmoebaProtozoa Jan 28, 2023
3ac722b
address comments
AmoebaProtozoa Jan 29, 2023
0e84e4c
Merge branch 'master' into keyspace_api
ti-chi-bot Feb 1, 2023
186a821
empty commit to trigger ci
AmoebaProtozoa Feb 2, 2023
52547cd
Merge branch 'master' into keyspace_api
ti-chi-bot Feb 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
341 changes: 341 additions & 0 deletions server/apiv2/handlers/keyspace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
// Copyright 2023 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 handlers

import (
"encoding/json"
"net/http"
"strconv"
"strings"
"time"

"github.com/gin-gonic/gin"
"github.com/pingcap/errors"
"github.com/pingcap/kvproto/pkg/keyspacepb"
"github.com/tikv/pd/pkg/errs"
"github.com/tikv/pd/server"
"github.com/tikv/pd/server/apiv2/middlewares"
"github.com/tikv/pd/server/keyspace"
)

// RegisterKeyspace register keyspace related handlers to router paths.
func RegisterKeyspace(r *gin.RouterGroup) {
AmoebaProtozoa marked this conversation as resolved.
Show resolved Hide resolved
router := r.Group("keyspaces")
router.Use(middlewares.BootstrapChecker())
router.POST("", CreateKeyspace)
router.GET("", LoadAllKeyspaces)
router.GET("/:name", LoadKeyspace)
router.PATCH("/:name/config", UpdateKeyspaceConfig)
router.PUT("/:name/state", UpdateKeyspaceState)
}

// CreateKeyspaceParams represents parameters needed when creating a new keyspace.
// NOTE: This type is exported by HTTP API. Please pay more attention when modifying it.
type CreateKeyspaceParams struct {
Name string `json:"name"`
Config map[string]string `json:"config"`
}

// CreateKeyspace creates keyspace according to given input.
// @Tags keyspaces
// @Summary Create new keyspace.
// @Param body body CreateKeyspaceParams true "Create keyspace parameters"
// @Produce json
// @Success 200 {object} KeyspaceMeta
// @Failure 400 {string} string "The input is invalid."
// @Failure 500 {string} string "PD server failed to proceed the request."
// @Router /keyspaces [post]
func CreateKeyspace(c *gin.Context) {
svr := c.MustGet("server").(*server.Server)
manager := svr.GetKeyspaceManager()
createParams := &CreateKeyspaceParams{}
err := c.BindJSON(createParams)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, errs.ErrBindJSON.Wrap(err).GenWithStackByCause())
return
}
req := &keyspace.CreateKeyspaceRequest{
Name: createParams.Name,
Config: createParams.Config,
Now: time.Now().Unix(),
}
meta, err := manager.CreateKeyspace(req)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, err.Error())
return
}
c.IndentedJSON(http.StatusOK, &KeyspaceMeta{meta})
}

// LoadKeyspace returns target keyspace.
// @Tags keyspaces
// @Summary Get keyspace info.
// @Param name path string true "Keyspace Name"
// @Produce json
// @Success 200 {object} KeyspaceMeta
// @Failure 500 {string} string "PD server failed to proceed the request."
// @Router /keyspaces/{name} [get]
func LoadKeyspace(c *gin.Context) {
svr := c.MustGet("server").(*server.Server)
manager := svr.GetKeyspaceManager()
name := c.Param("name")
meta, err := manager.LoadKeyspace(name)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, err.Error())
return
}
c.IndentedJSON(http.StatusOK, &KeyspaceMeta{meta})
}

// parseLoadAllQuery parses LoadAllKeyspaces' query parameters.
// page_token:
// The keyspace id of the scan start. If not set, scan from keyspace with id 1.
// It's string of spaceID of the previous scan result's last element (next_page_token).
// limit:
// The maximum number of keyspace metas to return. If not set, no limit is posed.
// Every scan scans limit + 1 keyspaces (if limit != 0), the extra scanned keyspace
// is to check if there's more, and used to set next_page_token in response.
func parseLoadAllQuery(c *gin.Context) (scanStart uint32, scanLimit int, err error) {
pageToken, set := c.GetQuery("page_token")
if !set || pageToken == "" {
// If pageToken is empty or unset, then scan from spaceID of 1.
scanStart = 0
} else {
scanStart64, err := strconv.ParseUint(pageToken, 10, 32)
if err != nil {
return 0, 0, err
}
scanStart = uint32(scanStart64)
}

limitStr, set := c.GetQuery("limit")
if !set || limitStr == "" || limitStr == "0" {
// If limit is unset or empty or 0, then no limit is posed for scan.
scanLimit = 0
} else {
scanLimit64, err := strconv.ParseInt(limitStr, 10, 64)
if err != nil {
return 0, 0, err
}
// Scan an extra element for next_page_token.
scanLimit = int(scanLimit64) + 1
}

return scanStart, scanLimit, nil
}

// LoadAllKeyspacesResponse represents response given when loading all keyspaces.
// NOTE: This type is exported by HTTP API. Please pay more attention when modifying it.
type LoadAllKeyspacesResponse struct {
Keyspaces []*KeyspaceMeta `json:"keyspaces"`
// Token that can be used to read immediate next page.
// If it's empty, then end has been reached.
NextPageToken string `json:"next_page_token"`
}

// LoadAllKeyspaces loads range of keyspaces.
// @Tags keyspaces
// @Summary list keyspaces.
// @Param page_token query string false "page token"
// @Param limit query string false "maximum number of results to return"
// @Produce json
// @Success 200 {object} LoadAllKeyspacesResponse
// @Failure 400 {string} string "The input is invalid."
// @Failure 500 {string} string "PD server failed to proceed the request."
// @Router /keyspaces [get]
func LoadAllKeyspaces(c *gin.Context) {
svr := c.MustGet("server").(*server.Server)
manager := svr.GetKeyspaceManager()
scanStart, scanLimit, err := parseLoadAllQuery(c)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, err.Error())
return
}
scanned, err := manager.LoadRangeKeyspace(scanStart, scanLimit)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, err.Error())
return
}
resp := &LoadAllKeyspacesResponse{}
// If scanned 0 keyspaces, return result immediately.
if len(scanned) == 0 {
c.IndentedJSON(http.StatusOK, resp)
return
}
var resultKeyspaces []*KeyspaceMeta
if scanLimit == 0 || len(scanned) < scanLimit {
// No next page, all scanned are results.
resultKeyspaces = make([]*KeyspaceMeta, len(scanned))
for i, meta := range scanned {
resultKeyspaces[i] = &KeyspaceMeta{meta}
}
} else {
// Scanned limit + 1 keyspaces, there is next page, all but last are results.
resultKeyspaces = make([]*KeyspaceMeta, len(scanned)-1)
for i := range resultKeyspaces {
resultKeyspaces[i] = &KeyspaceMeta{scanned[i]}
}
// Also set next_page_token here.
resp.NextPageToken = strconv.Itoa(int(scanned[len(scanned)-1].Id))
}
resp.Keyspaces = resultKeyspaces
c.IndentedJSON(http.StatusOK, resp)
}

// UpdateConfigParams represents parameters needed to modify target keyspace's configs.
// NOTE: This type is exported by HTTP API. Please pay more attention when modifying it.
// A Map of string to string pointer is used to differentiate between json null and "",
// which will both be set to "" if value type is string during binding.
type UpdateConfigParams struct {
Config map[string]*string `json:"config"`
}

// UpdateKeyspaceConfig updates target keyspace's config.
// This api uses PATCH semantic and supports JSON Merge Patch.
// format and processing rules.
// @Tags keyspaces
// @Summary Update keyspace config.
// @Param name path string true "Keyspace Name"
// @Param body body UpdateConfigParams true "Update keyspace parameters"
// @Produce json
// @Success 200 {object} KeyspaceMeta
// @Failure 400 {string} string "The input is invalid."
// @Failure 500 {string} string "PD server failed to proceed the request."
// Router /keyspaces/{name}/config [patch]
func UpdateKeyspaceConfig(c *gin.Context) {
svr := c.MustGet("server").(*server.Server)
manager := svr.GetKeyspaceManager()
name := c.Param("name")
configParams := &UpdateConfigParams{}
err := c.BindJSON(configParams)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, errs.ErrBindJSON.Wrap(err).GenWithStackByCause())
return
}
mutations := getMutations(configParams.Config)
meta, err := manager.UpdateKeyspaceConfig(name, mutations)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, err.Error())
return
}
c.IndentedJSON(http.StatusOK, &KeyspaceMeta{meta})
}

// getMutations converts a given JSON merge patch to a series of keyspace config mutations.
func getMutations(patch map[string]*string) []*keyspace.Mutation {
mutations := make([]*keyspace.Mutation, 0, len(patch))
for k, v := range patch {
if v == nil {
mutations = append(mutations, &keyspace.Mutation{
Op: keyspace.OpDel,
Key: k,
})
} else {
mutations = append(mutations, &keyspace.Mutation{
Op: keyspace.OpPut,
Key: k,
Value: *v,
})
}
}
return mutations
}

// UpdateStateParam represents parameters needed to modify target keyspace's state.
// NOTE: This type is exported by HTTP API. Please pay more attention when modifying it.
type UpdateStateParam struct {
State string `json:"state"`
}

// UpdateKeyspaceState update the target keyspace's state.
// @Tags keyspaces
// @Summary Update keyspace state.
// @Param name path string true "Keyspace Name"
// @Param body body UpdateStateParam true "New state for the keyspace"
// @Produce json
// @Success 200 {object} KeyspaceMeta
// @Failure 400 {string} string "The input is invalid."
// @Failure 500 {string} string "PD server failed to proceed the request."
// Router /keyspaces/{name}/state [put]
func UpdateKeyspaceState(c *gin.Context) {
svr := c.MustGet("server").(*server.Server)
manager := svr.GetKeyspaceManager()
name := c.Param("name")
param := &UpdateStateParam{}
err := c.BindJSON(param)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, errs.ErrBindJSON.Wrap(err).GenWithStackByCause())
return
}
targetState, ok := keyspacepb.KeyspaceState_value[strings.ToUpper(param.State)]
if !ok {
c.AbortWithStatusJSON(http.StatusBadRequest, errors.Errorf("unknown target state: %s", param.State))
AmoebaProtozoa marked this conversation as resolved.
Show resolved Hide resolved
return
}
meta, err := manager.UpdateKeyspaceState(name, keyspacepb.KeyspaceState(targetState), time.Now().Unix())
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, err.Error())
return
}
c.IndentedJSON(http.StatusOK, &KeyspaceMeta{meta})
}

// KeyspaceMeta wraps keyspacepb.KeyspaceMeta to provide custom JSON marshal.
type KeyspaceMeta struct {
*keyspacepb.KeyspaceMeta
}

// MarshalJSON creates custom marshal of KeyspaceMeta with the following:
// 1. Keyspace ID are removed from marshal result to avoid exposure of internal mechanics.
// 2. Keyspace State are marshaled to their corresponding name for better readability.
func (meta *KeyspaceMeta) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
Name string `json:"name,omitempty"`
State string `json:"state,omitempty"`
CreatedAt int64 `json:"created_at,omitempty"`
StateChangedAt int64 `json:"state_changed_at,omitempty"`
Config map[string]string `json:"config,omitempty"`
}{
meta.Name,
meta.State.String(),
meta.CreatedAt,
meta.StateChangedAt,
meta.Config,
})
}

// UnmarshalJSON reverse KeyspaceMeta's the Custom JSON marshal.
func (meta *KeyspaceMeta) UnmarshalJSON(data []byte) error {
aux := &struct {
Name string `json:"name,omitempty"`
State string `json:"state,omitempty"`
CreatedAt int64 `json:"created_at,omitempty"`
StateChangedAt int64 `json:"state_changed_at,omitempty"`
Config map[string]string `json:"config,omitempty"`
}{}

if err := json.Unmarshal(data, aux); err != nil {
return err
}
pbMeta := &keyspacepb.KeyspaceMeta{
Name: aux.Name,
State: keyspacepb.KeyspaceState(keyspacepb.KeyspaceState_value[aux.State]),
CreatedAt: aux.CreatedAt,
StateChangedAt: aux.StateChangedAt,
Config: aux.Config,
}
meta.KeyspaceMeta = pbMeta
return nil
}
14 changes: 12 additions & 2 deletions server/apiv2/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
"github.com/tikv/pd/server"
"github.com/tikv/pd/server/apiv2/handlers"
"github.com/tikv/pd/server/apiv2/middlewares"
)

Expand All @@ -37,6 +38,15 @@ var group = server.APIServiceGroup{
const apiV2Prefix = "/pd/api/v2/"

// NewV2Handler creates a HTTP handler for API.
// @title Placement Driver Core API
// @version 2.0
// @description This is placement driver.
// @contact.name Placement Driver Support
// @contact.url https://github.com/tikv/pd/issues
// @contact.email [email protected]
// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
// @BasePath /pd/api/v2
func NewV2Handler(_ context.Context, svr *server.Server) (http.Handler, server.APIServiceGroup, error) {
once.Do(func() {
// See https://github.com/pingcap/tidb-dashboard/blob/f8ecb64e3d63f4ed91c3dca7a04362418ade01d8/pkg/apiserver/apiserver.go#L84
Expand All @@ -50,7 +60,7 @@ func NewV2Handler(_ context.Context, svr *server.Server) (http.Handler, server.A
c.Next()
})
router.Use(middlewares.Redirector())
_ = router.Group(apiV2Prefix)

root := router.Group(apiV2Prefix)
handlers.RegisterKeyspace(root)
return router, group, nil
}
Loading