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

dashboard: support internal proxy #2508

Merged
merged 4 commits into from
Jun 5, 2020
Merged
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
4 changes: 4 additions & 0 deletions conf/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,7 @@ location-labels = []
## is running behind a reverse proxy. Do not configure it if you access
## Dashboard directly.
# public-path-prefix = "/dashboard"

## When enabled, request will be proxied to the instance running Dashboard
## internally instead of result in a 307 redirection.
# internal-proxy = false
12 changes: 12 additions & 0 deletions pkg/dashboard/adapter/redirector.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"sync"

"github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver"
"github.com/pingcap-incubator/tidb-dashboard/pkg/utils"
)

const (
Expand All @@ -36,13 +37,17 @@ type Redirector struct {

address string
proxy *httputil.ReverseProxy
// The status of the dashboard in the cluster.
// It is not equal to `apiserver.Service.status`.
status *utils.ServiceStatus
}

// NewRedirector creates a new Redirector.
func NewRedirector(name string, tlsConfig *tls.Config) *Redirector {
return &Redirector{
name: name,
tlsConfig: tlsConfig,
status: utils.NewServiceStatus(),
}
}

Expand All @@ -56,11 +61,13 @@ func (h *Redirector) SetAddress(addr string) {
}

if addr == "" {
h.status.Stop()
h.address = ""
h.proxy = nil
return
}

h.status.Start()
h.address = addr
target, _ := url.Parse(addr) // error has been handled in checkAddress
h.proxy = httputil.NewSingleHostReverseProxy(target)
Expand Down Expand Up @@ -117,3 +124,8 @@ func (h *Redirector) ReverseProxy(w http.ResponseWriter, r *http.Request) {

proxy.ServeHTTP(w, r)
}

// NewStatusAwareHandler returns a Handler that switches between different Handlers based on status.
func (h *Redirector) NewStatusAwareHandler(handler http.Handler) http.Handler {
return h.status.NewStatusAwareHandler(handler, apiserver.StoppedHandler)
}
34 changes: 24 additions & 10 deletions pkg/dashboard/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,24 +53,34 @@ func SetCheckInterval(d time.Duration) {
// GetServiceBuilders returns all ServiceBuilders required by Dashboard
func GetServiceBuilders() []server.HandlerBuilder {
var (
err error
cfg *config.Config
redirector *adapter.Redirector
assets http.FileSystem
s *apiserver.Service
err error
cfg *config.Config
internalProxy bool
redirector *adapter.Redirector
assets http.FileSystem
s *apiserver.Service
)

// The order of execution must be sequential.
return []server.HandlerBuilder{
// Dashboard API Service
func(ctx context.Context, srv *server.Server) (http.Handler, server.ServiceGroup, error) {
if cfg, err = adapter.GenDashboardConfig(srv); err != nil {
return nil, apiServiceGroup, err
}
internalProxy = srv.GetConfig().Dashboard.InternalProxy
redirector = adapter.NewRedirector(srv.Name(), cfg.ClusterTLSConfig)
assets = ui.Assets(cfg)

var stoppedHandler http.Handler
if internalProxy {
stoppedHandler = http.HandlerFunc(redirector.ReverseProxy)
} else {
stoppedHandler = http.HandlerFunc(redirector.TemporaryRedirect)
}
s = apiserver.NewService(
cfg,
http.HandlerFunc(redirector.ReverseProxy),
stoppedHandler,
assets,
adapter.GenPDDataProviderConstructor(srv),
)
Expand All @@ -87,10 +97,14 @@ func GetServiceBuilders() []server.HandlerBuilder {
return nil, uiServiceGroup, err
}

handler := s.NewStatusAwareHandler(
http.StripPrefix(uiServiceGroup.PathPrefix, uiserver.Handler(assets)),
http.HandlerFunc(redirector.TemporaryRedirect),
)
var handler http.Handler
uiHandler := http.StripPrefix(uiServiceGroup.PathPrefix, uiserver.Handler(assets))
if internalProxy {
handler = redirector.NewStatusAwareHandler(uiHandler)
} else {
handler = s.NewStatusAwareHandler(uiHandler, http.HandlerFunc(redirector.TemporaryRedirect))
}

return handler, uiServiceGroup, nil
},
}
Expand Down
1 change: 1 addition & 0 deletions server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,7 @@ type DashboardConfig struct {
TiDBCertPath string `toml:"tidb-cert-path" json:"tidb_cert_path"`
TiDBKeyPath string `toml:"tidb-key-path" json:"tidb_key_path"`
PublicPathPrefix string `toml:"public-path-prefix" json:"public_path_prefix"`
InternalProxy bool `toml:"internal-proxy" json:"internal_proxy"`
}

// ToTiDBTLSConfig generates tls config for connecting to TiDB, used by tidb-dashboard.
Expand Down
29 changes: 21 additions & 8 deletions tests/dashboard/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/pingcap/pd/v4/pkg/dashboard"
"github.com/pingcap/pd/v4/pkg/testutil"
"github.com/pingcap/pd/v4/server"
"github.com/pingcap/pd/v4/server/config"
"github.com/pingcap/pd/v4/tests"
"github.com/pingcap/pd/v4/tests/pdctl"

Expand Down Expand Up @@ -72,6 +73,14 @@ func (s *serverTestSuite) TearDownSuite(c *C) {
dashboard.SetCheckInterval(time.Second)
}

func (s *serverTestSuite) TestDashboardRedirect(c *C) {
s.testDashboard(c, false)
}

func (s *serverTestSuite) TestDashboardProxy(c *C) {
s.testDashboard(c, true)
}

func (s *serverTestSuite) checkRespCode(c *C, url string, code int) {
resp, err := s.httpClient.Get(url) //nolint:gosec
c.Assert(err, IsNil)
Expand All @@ -85,20 +94,22 @@ func (s *serverTestSuite) waitForConfigSync() {
time.Sleep(time.Second)
}

func (s *serverTestSuite) checkServiceIsStarted(c *C, servers map[string]*tests.TestServer, leader *tests.TestServer) string {
func (s *serverTestSuite) checkServiceIsStarted(c *C, internalProxy bool, servers map[string]*tests.TestServer, leader *tests.TestServer) string {
s.waitForConfigSync()
dashboardAddress := leader.GetServer().GetPersistOptions().GetDashboardAddress()
hasServiceNode := false
for _, srv := range servers {
c.Assert(srv.GetPersistOptions().GetDashboardAddress(), Equals, dashboardAddress)
addr := srv.GetAddr()
if addr == dashboardAddress {
if addr == dashboardAddress || internalProxy {
s.checkRespCode(c, fmt.Sprintf("%s/dashboard/", addr), http.StatusOK)
s.checkRespCode(c, fmt.Sprintf("%s/dashboard/api/keyvisual/heatmaps", addr), http.StatusUnauthorized)
hasServiceNode = true
if addr == dashboardAddress {
hasServiceNode = true
}
} else {
s.checkRespCode(c, fmt.Sprintf("%s/dashboard/", addr), http.StatusTemporaryRedirect)
s.checkRespCode(c, fmt.Sprintf("%s/dashboard/api/keyvisual/heatmaps", addr), http.StatusUnauthorized)
s.checkRespCode(c, fmt.Sprintf("%s/dashboard/api/keyvisual/heatmaps", addr), http.StatusTemporaryRedirect)
}
}
c.Assert(hasServiceNode, IsTrue)
Expand All @@ -115,8 +126,10 @@ func (s *serverTestSuite) checkServiceIsStopped(c *C, servers map[string]*tests.
}
}

func (s *serverTestSuite) TestDashboard(c *C) {
cluster, err := tests.NewTestCluster(s.ctx, 3)
func (s *serverTestSuite) testDashboard(c *C, internalProxy bool) {
cluster, err := tests.NewTestCluster(s.ctx, 3, func(conf *config.Config) {
conf.Dashboard.InternalProxy = internalProxy
})
c.Assert(err, IsNil)
defer cluster.Destroy()
err = cluster.RunInitialServers()
Expand All @@ -130,7 +143,7 @@ func (s *serverTestSuite) TestDashboard(c *C) {
leaderAddr := leader.GetAddr()

// auto select node
dashboardAddress1 := s.checkServiceIsStarted(c, servers, leader)
dashboardAddress1 := s.checkServiceIsStarted(c, internalProxy, servers, leader)

// pd-ctl set another addr
var dashboardAddress2 string
Expand All @@ -143,7 +156,7 @@ func (s *serverTestSuite) TestDashboard(c *C) {
args := []string{"-u", leaderAddr, "config", "set", "dashboard-address", dashboardAddress2}
_, _, err = pdctl.ExecuteCommandC(cmd, args...)
c.Assert(err, IsNil)
s.checkServiceIsStarted(c, servers, leader)
s.checkServiceIsStarted(c, internalProxy, servers, leader)
c.Assert(leader.GetServer().GetPersistOptions().GetDashboardAddress(), Equals, dashboardAddress2)

// pd-ctl set stop
Expand Down