Skip to content

Commit 0b70d5d

Browse files
meta-service-group: change add method to patch (tikv#380)
* change to patch Signed-off-by: AmoebaProtozoa <8039876+AmoebaProtozoa@users.noreply.github.com> * fix swagger doc Signed-off-by: AmoebaProtozoa <8039876+AmoebaProtozoa@users.noreply.github.com> --------- Signed-off-by: AmoebaProtozoa <8039876+AmoebaProtozoa@users.noreply.github.com>
1 parent 45bc455 commit 0b70d5d

File tree

4 files changed

+126
-100
lines changed

4 files changed

+126
-100
lines changed

server/apiv2/handlers/meta_service_group.go

Lines changed: 47 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -15,104 +15,91 @@
1515
package handlers
1616

1717
import (
18-
"fmt"
18+
"maps"
1919
"net/http"
2020
"sort"
2121

2222
"github.com/gin-gonic/gin"
2323
"github.com/tikv/pd/pkg/errs"
24+
"github.com/tikv/pd/pkg/keyspace"
2425
"github.com/tikv/pd/server"
2526
"github.com/tikv/pd/server/apiv2/middlewares"
2627
)
2728

2829
const (
2930
metaServiceGroupUninitializedErr = "meta-service groups manager is not initialized"
30-
metaServiceGroupAlreadyExistsErr = "meta-service group %s already exists"
3131
)
3232

3333
// RegisterMetaServiceGroup registers meta-service group related handlers to router paths.
3434
func RegisterMetaServiceGroup(r *gin.RouterGroup) {
3535
router := r.Group("meta-service-groups")
3636
router.Use(middlewares.BootstrapChecker())
3737
router.GET("", GetMetaServiceGroups)
38-
router.POST("", AddMetaServiceGroups)
38+
router.PATCH("", PatchMetaServiceGroups)
3939
}
4040

4141
// MetaServiceGroupStatus represents the status of a meta-service group.
4242
// NOTE: This type is exported by HTTP API. Please pay more attention when modifying it.
4343
type MetaServiceGroupStatus struct {
44-
ID string `json:"id"`
45-
Addresses string `json:"addresses"`
46-
AssignedKeyspaces int `json:"assigned_keyspaces"`
47-
}
48-
49-
// AddMetaServiceGroupRequest represents a request to add a meta-service group.
50-
// NOTE: This type is exported by HTTP API. Please pay more attention when modifying it.
51-
type AddMetaServiceGroupRequest struct {
52-
ID string `json:"id"`
44+
// ID is the unique identifier of the meta-service group.
45+
ID string `json:"id"`
46+
// Addresses is a comma-separated list of addresses for the meta-service group.
5347
Addresses string `json:"addresses"`
48+
// AssignedKeyspaces is the number of keyspaces assigned to this meta-service group.
49+
AssignedKeyspaces int `json:"assigned_keyspaces"`
5450
}
5551

56-
// AddMetaServiceGroups adds one or more meta-service groups as specified in the request.
52+
// PatchMetaServiceGroups applies a JSON Merge Patch to the meta-service groups.
5753
//
5854
// @Tags meta-service-groups
59-
// @Summary Adds new meta-service groups.
60-
// @Param body body []AddMetaServiceGroupRequest true "List of meta-service groups to add"
55+
// @Summary Patch meta-service groups using JSON Merge Patch.
56+
// @Param body body object true "JSON Merge Patch for meta-service groups (string values for add/update, null for delete)"
6157
// @Produce json
62-
// @Success 200 {object} []MetaServiceGroupStatus "List of newly added plus existing groups"
63-
// @Failure 400 {string} string "Bad request (invalid JSON or duplicate group)"
58+
// @Success 200 {object} []MetaServiceGroupStatus "List of all meta-service groups after patch"
59+
// @Failure 400 {string} string "Bad request (invalid JSON or invalid operation)"
6460
// @Failure 500 {string} string "Internal server error"
65-
// @Router /meta-service-groups [post]
66-
func AddMetaServiceGroups(c *gin.Context) {
61+
// @Router /meta-service-groups [patch]
62+
func PatchMetaServiceGroups(c *gin.Context) {
6763
svr := c.MustGet(middlewares.ServerContextKey).(*server.Server)
6864
manager := svr.GetMetaServiceGroupManager()
6965
if manager == nil {
7066
c.AbortWithStatusJSON(http.StatusInternalServerError, metaServiceGroupUninitializedErr)
7167
return
7268
}
7369

74-
var requests []AddMetaServiceGroupRequest
75-
err := c.BindJSON(&requests)
76-
if err != nil {
70+
var patch map[string]*string
71+
if err := c.BindJSON(&patch); err != nil {
7772
c.AbortWithStatusJSON(http.StatusBadRequest, errs.ErrBindJSON.Wrap(err).GenWithStackByCause())
7873
return
7974
}
80-
// Constructs new meta-service groups.
75+
8176
currentGroups := manager.GetGroups()
8277
newGroups := make(map[string]string)
83-
for _, request := range requests {
84-
// Update existing newGroups is not allowed via the post-method.
85-
if _, exists := currentGroups[request.ID]; exists {
86-
c.AbortWithStatusJSON(http.StatusBadRequest, fmt.Errorf(metaServiceGroupAlreadyExistsErr, request.ID))
87-
return
78+
maps.Copy(newGroups, currentGroups)
79+
80+
for id, addresses := range patch {
81+
if addresses == nil {
82+
// Remove operation
83+
delete(newGroups, id)
84+
} else {
85+
// Add or update operation
86+
newGroups[id] = *addresses
8887
}
89-
newGroups[request.ID] = request.Addresses
9088
}
91-
for id, addresses := range currentGroups {
92-
newGroups[id] = addresses
93-
}
94-
// Update persisted pd config.
89+
9590
keyspaceCfg := svr.GetConfig().Keyspace
9691
keyspaceCfg.MetaServiceGroups = newGroups
97-
if err = svr.SetKeyspaceConfig(keyspaceCfg); err != nil {
92+
if err := svr.SetKeyspaceConfig(keyspaceCfg); err != nil {
9893
c.AbortWithStatusJSON(http.StatusInternalServerError, err.Error())
9994
return
10095
}
10196

102-
assignmentCounts, err := manager.GetAssignmentCounts()
97+
status, err := buildMetaServiceGroupStatus(manager)
10398
if err != nil {
10499
c.AbortWithStatusJSON(http.StatusInternalServerError, err.Error())
105100
return
106101
}
107-
response := make([]MetaServiceGroupStatus, 0, len(newGroups))
108-
for id, addresses := range newGroups {
109-
response = append(response, MetaServiceGroupStatus{
110-
ID: id,
111-
Addresses: addresses,
112-
AssignedKeyspaces: assignmentCounts[id],
113-
})
114-
}
115-
c.IndentedJSON(http.StatusOK, response)
102+
c.IndentedJSON(http.StatusOK, status)
116103
}
117104

118105
// GetMetaServiceGroups returns a list of all meta-service groups and their assignment counts.
@@ -131,24 +118,32 @@ func GetMetaServiceGroups(c *gin.Context) {
131118
return
132119
}
133120

134-
groups := manager.GetGroups()
135-
assignmentCounts, err := manager.GetAssignmentCounts()
121+
status, err := buildMetaServiceGroupStatus(manager)
136122
if err != nil {
137123
c.AbortWithStatusJSON(http.StatusInternalServerError, err.Error())
138124
return
139125
}
126+
c.IndentedJSON(http.StatusOK, status)
127+
}
140128

141-
response := make([]MetaServiceGroupStatus, 0, len(groups))
142-
for id, addresses := range groups {
143-
response = append(response, MetaServiceGroupStatus{
129+
func buildMetaServiceGroupStatus(manager *keyspace.MetaServiceGroupManager) ([]MetaServiceGroupStatus, error) {
130+
currentGroups := manager.GetGroups()
131+
assignmentCounts, err := manager.GetAssignmentCounts()
132+
if err != nil {
133+
return nil, err
134+
}
135+
136+
status := make([]MetaServiceGroupStatus, 0, len(currentGroups))
137+
for id, addresses := range currentGroups {
138+
status = append(status, MetaServiceGroupStatus{
144139
ID: id,
145140
Addresses: addresses,
146141
AssignedKeyspaces: assignmentCounts[id],
147142
})
148143
}
149144
// sort for deterministic output
150-
sort.Slice(response, func(i, j int) bool {
151-
return response[i].ID < response[j].ID
145+
sort.Slice(status, func(i, j int) bool {
146+
return status[i].ID < status[j].ID
152147
})
153-
c.IndentedJSON(http.StatusOK, response)
148+
return status, nil
154149
}

tests/server/apiv2/handlers/meta_service_group_test.go

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ package handlers
1616

1717
import (
1818
"context"
19-
"net/http"
2019
"testing"
2120

2221
"github.com/pingcap/failpoint"
@@ -114,18 +113,15 @@ func (suite *metaServiceGroupTestSuite) TestMetaServiceGroupOperations() {
114113
re.InDelta(collectedStatus.AssignedKeyspaces, len(keyspaces)/len(groups), 1)
115114
}
116115
// Add two more meta-service groups.
117-
newGroups := []*handlers.AddMetaServiceGroupRequest{
118-
{
119-
ID: "etcd-group-4",
120-
Addresses: "etcd-group-4.tidb-serverless.cluster.svc.local",
121-
},
122-
{
123-
ID: "etcd-group-5",
124-
Addresses: "etcd-group-5.tidb-serverless.cluster.svc.local",
125-
},
116+
addr4 := "etcd-group-4.tidb-serverless.cluster.svc.local"
117+
addr5 := "etcd-group-5.tidb-serverless.cluster.svc.local"
118+
patch := map[string]*string{
119+
"etcd-group-4": &addr4,
120+
"etcd-group-5": &addr5,
126121
}
127-
groups = mustAddMetaServiceGroups(re, suite.server, newGroups)
128-
re.Equal(len(groups), len(mockMetaServiceGroups())+len(newGroups))
122+
123+
groups = mustAddMetaServiceGroups(re, suite.server, patch)
124+
re.Equal(len(groups), len(mockMetaServiceGroups())+len(patch))
129125
// Newly assigned meta-service group should have no assigned keyspace.
130126
for _, group := range groups {
131127
if collectedGroups[group.ID] == nil {
@@ -144,7 +140,27 @@ func (suite *metaServiceGroupTestSuite) TestMetaServiceGroupOperations() {
144140
// Make sure keyspaces are relatively evenly distributed among meta-service groups.
145141
re.InDelta(collectedStatus.AssignedKeyspaces, len(keyspaces)/len(groups), 1)
146142
}
147-
// Add the same keyspace group should result in error.
148-
code, _ := tryAddMetaServiceGroups(re, suite.server, newGroups)
149-
re.Equal(http.StatusBadRequest, code)
143+
// Modify address of etcd-group-1
144+
newAddr := "etcd-group-1-modified.tidb-serverless.cluster.svc.local"
145+
modifyPatch := map[string]*string{
146+
"etcd-group-1": &newAddr,
147+
}
148+
groups = mustAddMetaServiceGroups(re, suite.server, modifyPatch)
149+
found := false
150+
for _, group := range groups {
151+
if group.ID == "etcd-group-1" {
152+
found = true
153+
re.Equal(newAddr, group.Addresses)
154+
}
155+
}
156+
re.True(found, "etcd-group-1 should exist after modify")
157+
158+
// Delete etcd-group-2
159+
deletePatch := map[string]*string{
160+
"etcd-group-2": nil,
161+
}
162+
groups = mustAddMetaServiceGroups(re, suite.server, deletePatch)
163+
for _, group := range groups {
164+
re.NotEqual("etcd-group-2", group.ID, "etcd-group-2 should be deleted")
165+
}
150166
}

tests/server/apiv2/handlers/testutil.go

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -287,26 +287,18 @@ func mustLoadMetaServiceGroups(re *require.Assertions, server *tests.TestServer)
287287
return groups
288288
}
289289

290-
func mustAddMetaServiceGroups(re *require.Assertions, server *tests.TestServer, request []*handlers.AddMetaServiceGroupRequest) []*handlers.MetaServiceGroupStatus {
291-
code, groups := tryAddMetaServiceGroups(re, server, request)
292-
re.Equal(http.StatusOK, code)
293-
return groups
294-
}
295-
296-
func tryAddMetaServiceGroups(re *require.Assertions, server *tests.TestServer, request []*handlers.AddMetaServiceGroupRequest) (int, []*handlers.MetaServiceGroupStatus) {
297-
data, err := json.Marshal(request)
290+
func mustAddMetaServiceGroups(re *require.Assertions, server *tests.TestServer, patch map[string]*string) []*handlers.MetaServiceGroupStatus {
291+
data, err := json.Marshal(patch)
298292
re.NoError(err)
299-
httpReq, err := http.NewRequest(http.MethodPost, server.GetAddr()+metaServiceGroupsPrefix, bytes.NewBuffer(data))
293+
httpReq, err := http.NewRequest(http.MethodPatch, server.GetAddr()+metaServiceGroupsPrefix, bytes.NewBuffer(data))
300294
re.NoError(err)
301295
resp, err := tests.TestDialClient.Do(httpReq)
302296
re.NoError(err)
303297
defer resp.Body.Close()
304298
data, err = io.ReadAll(resp.Body)
305299
re.NoError(err)
306-
if resp.StatusCode != http.StatusOK {
307-
return resp.StatusCode, nil
308-
}
300+
re.Equal(http.StatusOK, resp.StatusCode)
309301
var groups []*handlers.MetaServiceGroupStatus
310302
re.NoError(json.Unmarshal(data, &groups))
311-
return resp.StatusCode, groups
303+
return groups
312304
}

tools/pd-ctl/pdctl/command/meta_service_group_command.go

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"strings"
2222

2323
"github.com/spf13/cobra"
24-
"github.com/tikv/pd/server/apiv2/handlers"
2524
)
2625

2726
const (
@@ -36,7 +35,8 @@ func NewMetaServiceGroupCommand() *cobra.Command {
3635
Short: "meta-service group commands",
3736
}
3837
cmd.AddCommand(newListMetaServiceGroupCommand())
39-
cmd.AddCommand(newAddMetaServiceGroupCommand())
38+
cmd.AddCommand(newUpdateMetaServiceGroupCommand())
39+
cmd.AddCommand(newDeleteMetaServiceGroupCommand())
4040
return cmd
4141
}
4242

@@ -62,54 +62,77 @@ func listMetaServiceGroupFunc(cmd *cobra.Command, args []string) {
6262
cmd.Println(resp)
6363
}
6464

65-
func newAddMetaServiceGroupCommand() *cobra.Command {
65+
func newUpdateMetaServiceGroupCommand() *cobra.Command {
6666
r := &cobra.Command{
67-
Use: "add",
68-
Short: "add one or more meta-service groups",
69-
Run: newAddMetaServiceGroupFunc,
67+
Use: "update",
68+
Short: "add or update one or more meta-service groups",
69+
Run: newUpdateMetaServiceGroupFunc,
7070
}
71-
r.Flags().StringArrayP(nmGroup, "g", nil, "meta-service group in format id=addr1,addr2,...")
71+
r.Flags().StringArrayP(nmGroup, "g", nil, "meta-service group in format id=addr1,addr2,... (for add/update)")
7272
_ = r.MarkFlagRequired(nmGroup)
7373
return r
7474
}
7575

76-
func newAddMetaServiceGroupFunc(cmd *cobra.Command, _ []string) {
77-
// Parse the new groups.
76+
func newUpdateMetaServiceGroupFunc(cmd *cobra.Command, _ []string) {
7877
metaServiceGroups, err := cmd.Flags().GetStringArray("group")
7978
if err != nil {
8079
cmd.PrintErrln("Failed to read --group flag:", err)
8180
return
8281
}
83-
8482
if len(metaServiceGroups) == 0 {
8583
cmd.PrintErrln("At least one --group must be specified")
8684
cmd.Usage()
8785
return
8886
}
89-
var params []handlers.AddMetaServiceGroupRequest
87+
patch := make(map[string]*string)
9088
for _, group := range metaServiceGroups {
9189
parts := strings.SplitN(group, "=", 2)
9290
if len(parts) != 2 {
93-
cmd.PrintErrf("Invalid --group format: %q (expected id=addr1,addr2,...)\n", group)
91+
cmd.PrintErrf("Invalid --group format: %q (expected id=addr1,addr2,...)", group)
9492
return
9593
}
96-
params = append(params, handlers.AddMetaServiceGroupRequest{
97-
ID: strings.TrimSpace(parts[0]),
98-
Addresses: strings.TrimSpace(parts[1]),
99-
})
94+
addr := strings.TrimSpace(parts[1])
95+
patch[strings.TrimSpace(parts[0])] = &addr
10096
}
101-
102-
body, err := json.Marshal(params)
97+
body, err := json.Marshal(patch)
10398
if err != nil {
10499
cmd.PrintErrln("Failed to marshal request:", err)
105100
return
106101
}
107-
resp, err := doRequest(cmd, metaServiceGroupPrefix, http.MethodPost,
102+
resp, err := doRequest(cmd, metaServiceGroupPrefix, http.MethodPatch,
108103
http.Header{"Content-Type": {"application/json"}}, WithBody(bytes.NewBuffer(body)))
109104
if err != nil {
110-
cmd.PrintErrln("Failed to add meta-service group:", err)
105+
cmd.PrintErrln("Failed to update meta-service group:", err)
111106
return
112107
}
108+
cmd.Println(resp)
109+
}
110+
111+
func newDeleteMetaServiceGroupCommand() *cobra.Command {
112+
r := &cobra.Command{
113+
Use: "delete <id> [<id> ...]",
114+
Short: "delete one or more meta-service groups by id",
115+
Args: cobra.MinimumNArgs(1),
116+
Run: newDeleteMetaServiceGroupFunc,
117+
}
118+
return r
119+
}
113120

121+
func newDeleteMetaServiceGroupFunc(cmd *cobra.Command, args []string) {
122+
patch := make(map[string]*string)
123+
for _, id := range args {
124+
patch[strings.TrimSpace(id)] = nil
125+
}
126+
body, err := json.Marshal(patch)
127+
if err != nil {
128+
cmd.PrintErrln("Failed to marshal request:", err)
129+
return
130+
}
131+
resp, err := doRequest(cmd, metaServiceGroupPrefix, http.MethodPatch,
132+
http.Header{"Content-Type": {"application/json"}}, WithBody(bytes.NewBuffer(body)))
133+
if err != nil {
134+
cmd.PrintErrln("Failed to delete meta-service group:", err)
135+
return
136+
}
114137
cmd.Println(resp)
115138
}

0 commit comments

Comments
 (0)