Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 14 additions & 2 deletions pkg/mcs/resourcemanager/server/apis/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ func NewService(srv *rmserver.Service) *Service {
apiHandlerEngine.GET("status", utils.StatusHandler)
pprof.Register(apiHandlerEngine)
endpoint := apiHandlerEngine.Group(APIPathPrefix)
endpoint.Use(multiservicesapi.ServiceRedirector())
s := &Service{
manager: manager,
apiHandlerEngine: apiHandlerEngine,
Expand All @@ -100,6 +99,8 @@ func (s *Service) RegisterAdminRouter() {
// RegisterRouter registers the router of the service.
func (s *Service) RegisterRouter() {
configEndpoint := s.root.Group("/config")
configEndpoint.GET("", getConfig)
configEndpoint.Use(multiservicesapi.ServiceRedirector())
configEndpoint.POST("/group", s.postResourceGroup)
configEndpoint.PUT("/group", s.putResourceGroup)
configEndpoint.GET("/group/:name", s.getResourceGroup)
Expand All @@ -122,7 +123,7 @@ func (s *Service) handler() http.Handler {
}

func changeLogLevel(c *gin.Context) {
svr := c.MustGet(multiservicesapi.ServiceContextKey).(*rmserver.Service)
svr := c.MustGet(multiservicesapi.ServiceContextKey).(*rmserver.Server)
var level string
if err := c.Bind(&level); err != nil {
c.String(http.StatusBadRequest, err.Error())
Expand Down Expand Up @@ -376,3 +377,14 @@ func (s *Service) getKeyspaceServiceLimit(c *gin.Context) {
}
c.IndentedJSON(http.StatusOK, limiter)
}

// GetConfig
//
// @Tags ResourceManager
// @Summary Get the resource manager config.
// @Success 200 {string} json format of rmserver.Config
func getConfig(c *gin.Context) {
svr := c.MustGet(multiservicesapi.ServiceContextKey).(*rmserver.Server)
config := svr.GetConfig()
c.IndentedJSON(http.StatusOK, config)
}
5 changes: 5 additions & 0 deletions pkg/mcs/resourcemanager/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@ func (s *Server) Close() {
log.Info("resource manager server is closed")
}

// GetConfig returns the config.
func (s *Server) GetConfig() *Config {
return s.cfg
}

// GetControllerConfig returns the controller config.
func (s *Server) GetControllerConfig() *ControllerConfig {
return &s.cfg.Controller
Expand Down
90 changes: 90 additions & 0 deletions tests/integrations/mcs/resourcemanager/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/tikv/pd/pkg/keyspace"
"github.com/tikv/pd/pkg/mcs/resourcemanager/server"
"github.com/tikv/pd/pkg/mcs/resourcemanager/server/apis/v1"
"github.com/tikv/pd/pkg/utils/testutil"
"github.com/tikv/pd/tests"
)

Expand Down Expand Up @@ -397,3 +398,92 @@ func tryToSetKeyspaceServiceLimit(re *require.Assertions, leaderAddr, keyspaceNa
)
return string(bodyBytes), statusCode
}

// resourceManagerForwardingTestSuite is a test suite for testing the forwarding behavior of Resource Manager APIs.
type resourceManagerForwardingTestSuite struct {
suite.Suite
ctx context.Context
cancel context.CancelFunc
pdCluster *tests.TestCluster
rmCluster *tests.TestResourceManagerCluster
backendEndpoints string
primary *server.Server
follower *server.Server
}

func TestResourceManagerForwarding(t *testing.T) {
suite.Run(t, new(resourceManagerForwardingTestSuite))
}

func (suite *resourceManagerForwardingTestSuite) SetupTest() {
re := suite.Require()
var err error
suite.ctx, suite.cancel = context.WithCancel(context.Background())

suite.pdCluster, err = tests.NewTestCluster(suite.ctx, 1)
re.NoError(err)
err = suite.pdCluster.RunInitialServers()
re.NoError(err)
leaderName := suite.pdCluster.WaitLeader()
re.NotEmpty(leaderName)
pdLeaderServer := suite.pdCluster.GetServer(leaderName)
re.NoError(pdLeaderServer.BootstrapCluster())
suite.backendEndpoints = pdLeaderServer.GetAddr()

suite.rmCluster, err = tests.NewTestResourceManagerCluster(suite.ctx, 2, suite.backendEndpoints)
re.NoError(err)

suite.primary = suite.rmCluster.WaitForPrimaryServing(re)
re.NotNil(suite.primary)
for _, srv := range suite.rmCluster.GetServers() {
if srv.GetAddr() != suite.primary.GetAddr() {
suite.follower = srv
break
}
}
re.NotNil(suite.follower, "follower should not be nil")
re.False(suite.follower.IsServing(), "follower should not be serving")
}

func (suite *resourceManagerForwardingTestSuite) TearDownTest() {
suite.cancel()
suite.rmCluster.Destroy()
suite.pdCluster.Destroy()
}

// TestResourceManagerForwardingBehavior checks that requests are correctly forwarded or handled locally.
func (suite *resourceManagerForwardingTestSuite) TestResourceManagerForwardingBehavior() {
re := suite.Require()
followerAddr := suite.follower.GetAddr()
followerURL := func(path string) string {
return fmt.Sprintf("%s%s%s", followerAddr, apis.APIPathPrefix, path)
}

// Case 1: PUT /admin/log should be handled by the follower locally.
logURL := followerURL("admin/log")
level := "debug"
logPayload, err := json.Marshal(level)
re.NoError(err)
req, err := http.NewRequest(http.MethodPut, logURL, bytes.NewBuffer(logPayload))
re.NoError(err)
req.Header.Set("Content-Type", "application/json")
resp, err := tests.TestDialClient.Do(req)
re.NoError(err)
defer resp.Body.Close()
re.Equal(http.StatusOK, resp.StatusCode)

// Case 2: GET /config should be handled by the follower locally.
configURL := followerURL("config")
var followerCfg server.Config
err = testutil.ReadGetJSON(re, tests.TestDialClient, configURL, &followerCfg, testutil.StatusOK(re))
re.NoError(err)
re.Equal(suite.follower.GetConfig().GetListenAddr(), followerCfg.GetListenAddr())
re.NotEqual(suite.primary.GetConfig().GetListenAddr(), followerCfg.GetListenAddr())
re.Equal(level, followerCfg.Log.Level)

// Case 3: GET /config/groups should be handled by the follower forwarded to the primary.
controllerURL := followerURL("config/groups")
groups := make([]*server.ResourceGroup, 0)
err = testutil.ReadGetJSON(re, tests.TestDialClient, controllerURL, &groups, testutil.StatusOK(re))
re.NoError(err)
}
107 changes: 107 additions & 0 deletions tests/resource_manager_cluster.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright 2025 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.

// in tests/resource_manager_cluster.go

package tests

import (
"context"
"time"

"github.com/stretchr/testify/require"

rmserver "github.com/tikv/pd/pkg/mcs/resourcemanager/server"
"github.com/tikv/pd/pkg/utils/tempurl"
"github.com/tikv/pd/pkg/utils/testutil"
)

// TestResourceManagerCluster is a test cluster for Resource Manager.
type TestResourceManagerCluster struct {
ctx context.Context

backendEndpoints string
servers map[string]*rmserver.Server
cleanupFuncs map[string]testutil.CleanupFunc
}

// NewTestResourceManagerCluster creates a new Resource Manager test cluster.
func NewTestResourceManagerCluster(ctx context.Context, initialServerCount int, backendEndpoints string) (tc *TestResourceManagerCluster, err error) {
tc = &TestResourceManagerCluster{
ctx: ctx,
backendEndpoints: backendEndpoints,
servers: make(map[string]*rmserver.Server, initialServerCount),
cleanupFuncs: make(map[string]testutil.CleanupFunc, initialServerCount),
}
for range initialServerCount {
err = tc.AddServer(tempurl.Alloc())
if err != nil {
return nil, err
}
}
return tc, nil
}

// AddServer adds a new Resource Manager server to the test cluster.
func (tc *TestResourceManagerCluster) AddServer(addr string) error {
cfg := rmserver.NewConfig()
cfg.BackendEndpoints = tc.backendEndpoints
cfg.ListenAddr = addr
cfg.Name = cfg.ListenAddr
generatedCfg, err := rmserver.GenerateConfig(cfg)
if err != nil {
return err
}
err = InitLogger(generatedCfg.Log, generatedCfg.Logger, generatedCfg.LogProps, generatedCfg.Security.RedactInfoLog)
if err != nil {
return err
}
server, cleanup, err := NewResourceManagerTestServer(tc.ctx, generatedCfg)
if err != nil {
return err
}
tc.servers[generatedCfg.GetListenAddr()] = server
tc.cleanupFuncs[generatedCfg.GetListenAddr()] = cleanup
return nil
}

// Destroy stops and destroy the test cluster.
func (tc *TestResourceManagerCluster) Destroy() {
for _, cleanup := range tc.cleanupFuncs {
cleanup()
}
tc.cleanupFuncs = nil
tc.servers = nil
}

// GetServers returns all Resource Manager servers.
func (tc *TestResourceManagerCluster) GetServers() map[string]*rmserver.Server {
return tc.servers
}

// WaitForPrimaryServing waits for one of servers being elected to be the primary.
func (tc *TestResourceManagerCluster) WaitForPrimaryServing(re *require.Assertions) *rmserver.Server {
var primary *rmserver.Server
testutil.Eventually(re, func() bool {
for _, server := range tc.servers {
if server.IsServing() {
primary = server
return true
}
}
return false
}, testutil.WithWaitFor(30*time.Second), testutil.WithTickInterval(100*time.Millisecond))

return primary
}
12 changes: 12 additions & 0 deletions tests/testutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -877,3 +877,15 @@ func MustCallSchedulerConfigAPI(
re.NoError(err)
re.Equal(http.StatusOK, resp.StatusCode, string(data))
}

// NewResourceManagerTestServer creates a resource manager server with given config for testing.
func NewResourceManagerTestServer(ctx context.Context, cfg *rm.Config) (*rm.Server, testutil.CleanupFunc, error) {
s := rm.CreateServer(ctx, cfg)
if err := s.Run(); err != nil {
return nil, nil, err
}
cleanup := func() {
s.Close()
}
return s, cleanup, nil
}