Skip to content

Commit ae1bc34

Browse files
committed
resourcemanager: make controller config updates atomic
Signed-off-by: okjiang <819421878@qq.com>
1 parent ffaeac9 commit ae1bc34

File tree

4 files changed

+117
-26
lines changed

4 files changed

+117
-26
lines changed

pkg/mcs/resourcemanager/server/apis/v1/api.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -330,16 +330,18 @@ func (s *Service) setControllerConfig(c *gin.Context) {
330330
c.String(http.StatusBadRequest, err.Error())
331331
return
332332
}
333+
resolvedConf := make(map[string]any, len(conf))
333334
for k, v := range conf {
334335
key := reflectutil.FindJSONFullTagByChildTag(reflect.TypeOf(rmserver.ControllerConfig{}), k)
335336
if key == "" {
336337
c.String(http.StatusBadRequest, fmt.Sprintf("config item %s not found", k))
337338
return
338339
}
339-
if err := s.manager.UpdateControllerConfigItem(key, v); err != nil {
340-
c.String(http.StatusBadRequest, err.Error())
341-
return
342-
}
340+
resolvedConf[key] = v
341+
}
342+
if err := s.manager.UpdateControllerConfigItems(resolvedConf); err != nil {
343+
c.String(http.StatusBadRequest, err.Error())
344+
return
343345
}
344346
c.String(http.StatusOK, "Success!")
345347
}

pkg/mcs/resourcemanager/server/manager.go

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -270,34 +270,47 @@ func (m *Manager) initReserved() {
270270

271271
// UpdateControllerConfigItem updates the controller config item.
272272
func (m *Manager) UpdateControllerConfigItem(key string, value any) error {
273-
kp := strings.Split(key, ".")
274-
if len(kp) == 0 {
275-
return errors.Errorf("invalid key %s", key)
276-
}
273+
return m.UpdateControllerConfigItems(map[string]any{key: value})
274+
}
275+
276+
// UpdateControllerConfigItems updates controller config items atomically.
277+
func (m *Manager) UpdateControllerConfigItems(items map[string]any) error {
277278
m.Lock()
278-
var config any
279-
switch kp[0] {
280-
case "request-unit":
281-
config = &m.controllerConfig.RequestUnit
282-
default:
283-
config = m.controllerConfig
284-
}
285-
updated, found, err := jsonutil.AddKeyValue(config, kp[len(kp)-1], value)
279+
controllerConfig, err := cloneControllerConfig(m.controllerConfig)
286280
if err != nil {
287281
m.Unlock()
288282
return err
289283
}
290-
291-
if !found {
284+
updatedItems := make([]struct {
285+
key string
286+
value any
287+
}, 0, len(items))
288+
for key, value := range items {
289+
updated, err := applyControllerConfigItem(controllerConfig, key, value)
290+
if err != nil {
291+
m.Unlock()
292+
return err
293+
}
294+
if updated {
295+
updatedItems = append(updatedItems, struct {
296+
key string
297+
value any
298+
}{key: key, value: value})
299+
}
300+
}
301+
if len(updatedItems) == 0 {
292302
m.Unlock()
293-
return errors.Errorf("config item %s not found", key)
303+
return nil
294304
}
305+
if err := m.storage.SaveControllerConfig(controllerConfig); err != nil {
306+
m.Unlock()
307+
log.Error("save controller config failed", zap.Error(err))
308+
return err
309+
}
310+
m.controllerConfig = controllerConfig
295311
m.Unlock()
296-
if updated {
297-
if err := m.storage.SaveControllerConfig(m.controllerConfig); err != nil {
298-
log.Error("save controller config failed", zap.Error(err))
299-
}
300-
log.Info("updated controller config item", zap.String("key", key), zap.Any("value", value))
312+
for _, item := range updatedItems {
313+
log.Info("updated controller config item", zap.String("key", item.key), zap.Any("value", item.value))
301314
}
302315
return nil
303316
}
@@ -309,6 +322,40 @@ func (m *Manager) GetControllerConfig() *ControllerConfig {
309322
return m.controllerConfig
310323
}
311324

325+
func cloneControllerConfig(config *ControllerConfig) (*ControllerConfig, error) {
326+
data, err := json.Marshal(config)
327+
if err != nil {
328+
return nil, err
329+
}
330+
cloned := &ControllerConfig{}
331+
if err := json.Unmarshal(data, cloned); err != nil {
332+
return nil, err
333+
}
334+
return cloned, nil
335+
}
336+
337+
func applyControllerConfigItem(config *ControllerConfig, key string, value any) (bool, error) {
338+
kp := strings.Split(key, ".")
339+
if len(kp) == 0 {
340+
return false, errors.Errorf("invalid key %s", key)
341+
}
342+
var target any
343+
switch kp[0] {
344+
case "request-unit":
345+
target = &config.RequestUnit
346+
default:
347+
target = config
348+
}
349+
updated, found, err := jsonutil.AddKeyValue(target, kp[len(kp)-1], value)
350+
if err != nil {
351+
return false, err
352+
}
353+
if !found {
354+
return false, errors.Errorf("config item %s not found", key)
355+
}
356+
return updated, nil
357+
}
358+
312359
// AddResourceGroup puts a resource group.
313360
// NOTE: AddResourceGroup should also be idempotent because tidb depends
314361
// on this retry mechanism.

pkg/mcs/resourcemanager/server/manager_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,27 @@ func TestKeyspaceServiceLimit(t *testing.T) {
334334
re.Equal(DefaultResourceGroupName, krgm.getMutableResourceGroup(DefaultResourceGroupName).Name)
335335
}
336336

337+
func TestUpdateControllerConfigItemsAtomic(t *testing.T) {
338+
re := require.New(t)
339+
m := prepareManager()
340+
341+
ctx, cancel := context.WithCancel(context.Background())
342+
defer cancel()
343+
re.NoError(m.Init(ctx))
344+
345+
before := *m.GetControllerConfig()
346+
err := m.UpdateControllerConfigItems(map[string]any{
347+
"request-unit.write-base-cost": 2.0,
348+
"ltb-max-wait-duration": "not-a-duration",
349+
})
350+
re.Error(err)
351+
352+
after := m.GetControllerConfig()
353+
re.Equal(before.RequestUnit.WriteBaseCost, after.RequestUnit.WriteBaseCost)
354+
re.Equal(before.LTBMaxWaitDuration, after.LTBMaxWaitDuration)
355+
re.Equal(before.EnableControllerTraceLog, after.EnableControllerTraceLog)
356+
}
357+
337358
func TestKeyspaceNameLookup(t *testing.T) {
338359
re := require.New(t)
339360
m := prepareManager()

tests/integrations/mcs/resourcemanager/api_test.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,21 @@ func (suite *resourceManagerAPITestSuite) TestControllerConfigAPI() {
311311
re.Equal(2.0, config.RequestUnit.WriteBaseCost)
312312
}
313313

314+
func (suite *resourceManagerAPITestSuite) TestControllerConfigAPIAllOrNothing() {
315+
re := suite.Require()
316+
317+
before := suite.mustGetControllerConfig(re)
318+
resp, statusCode := tryToSetControllerConfig(re, suite.cluster.GetLeaderServer().GetAddr(), map[string]any{
319+
"enable-controller-trace-log": "true",
320+
"ltb-max-wait-duration": "not-a-duration",
321+
})
322+
re.Equal(http.StatusBadRequest, statusCode)
323+
re.Contains(resp, "time:")
324+
325+
after := suite.mustGetControllerConfig(re)
326+
re.Equal(before, after)
327+
}
328+
314329
func (suite *resourceManagerAPITestSuite) mustGetControllerConfig(re *require.Assertions) *server.ControllerConfig {
315330
bodyBytes := suite.mustSendRequest(re, http.MethodGet, "/config/controller", nil)
316331
config := &server.ControllerConfig{}
@@ -319,8 +334,14 @@ func (suite *resourceManagerAPITestSuite) mustGetControllerConfig(re *require.As
319334
}
320335

321336
func (suite *resourceManagerAPITestSuite) mustSetControllerConfig(re *require.Assertions, config map[string]any) {
322-
bodyBytes := suite.mustSendRequest(re, http.MethodPost, "/config/controller", config)
323-
re.Equal("Success!", string(bodyBytes))
337+
body, statusCode := tryToSetControllerConfig(re, suite.cluster.GetLeaderServer().GetAddr(), config)
338+
re.Equal(http.StatusOK, statusCode, body)
339+
re.Equal("Success!", body)
340+
}
341+
342+
func tryToSetControllerConfig(re *require.Assertions, leaderAddr string, config map[string]any) (string, int) {
343+
bodyBytes, statusCode := sendRequest(re, leaderAddr, http.MethodPost, "/config/controller", nil, config)
344+
return string(bodyBytes), statusCode
324345
}
325346

326347
func (suite *resourceManagerAPITestSuite) TestKeyspaceServiceLimitAPI() {

0 commit comments

Comments
 (0)