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

feature: Query Editor #713

Merged
merged 4 commits into from
Aug 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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ endif
go build -o bin/tidb-dashboard -ldflags '$(LDFLAGS)' -tags "${BUILD_TAGS}" cmd/tidb-dashboard/main.go

run:
bin/tidb-dashboard --debug
bin/tidb-dashboard --debug --experimental
3 changes: 2 additions & 1 deletion cmd/tidb-dashboard/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ func NewCLIConfig() *DashboardCLIConfig {
flag.StringVar(&cfg.CoreConfig.DataDir, "data-dir", "/tmp/dashboard-data", "path to the Dashboard Server data directory")
flag.StringVar(&cfg.CoreConfig.PublicPathPrefix, "path-prefix", config.DefaultPublicPathPrefix, "public URL path prefix for reverse proxies")
flag.StringVar(&cfg.CoreConfig.PDEndPoint, "pd", "http://127.0.0.1:2379", "PD endpoint address that Dashboard Server connects to")
flag.BoolVar(&cfg.CoreConfig.EnableTelemetry, "enable-telemetry", true, "enable client to report data for analysis")
flag.BoolVar(&cfg.CoreConfig.EnableTelemetry, "telemetry", true, "allow telemetry")
flag.BoolVar(&cfg.CoreConfig.EnableExperimental, "experimental", false, "allow experimental features")

showVersion := flag.BoolP("version", "v", false, "print version information and exit")

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/pingcap-incubator/tidb-dashboard
go 1.13

require (
github.com/VividCortex/mysqlerr v0.0.0-20200629151747-c28746d985dd
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/appleboy/gin-jwt/v2 v2.6.3
github.com/cenkalti/backoff/v4 v4.0.2
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VividCortex/mysqlerr v0.0.0-20200629151747-c28746d985dd h1:59Whn6shj5MTVjTf2OX6+7iMcmY6h5CK0kTWwRaplL4=
github.com/VividCortex/mysqlerr v0.0.0-20200629151747-c28746d985dd/go.mod h1:f3HiCrHjHBdcm6E83vGaXh1KomZMA2P6aeo3hKx/wg0=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
Expand Down
6 changes: 3 additions & 3 deletions pkg/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ import (

"github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver/clusterinfo"
"github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver/diagnose"
"github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver/foo"
"github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver/info"
"github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver/logsearch"
"github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver/metrics"
"github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver/profiling"
"github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver/queryeditor"
"github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver/slowquery"
"github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver/statement"
"github.com/pingcap-incubator/tidb-dashboard/pkg/apiserver/user"
Expand Down Expand Up @@ -110,7 +110,6 @@ func (s *Service) Start(ctx context.Context) error {
tidb.NewForwarder,
pkghttp.NewHTTPClientWithConf,
user.NewAuthService,
foo.NewService,
info.NewService,
clusterinfo.NewService,
profiling.NewService,
Expand All @@ -120,11 +119,11 @@ func (s *Service) Start(ctx context.Context) error {
diagnose.NewService,
keyvisual.NewService,
metrics.NewService,
queryeditor.NewService,
),
fx.Populate(&s.apiHandlerEngine),
fx.Invoke(
user.Register,
foo.Register,
info.Register,
clusterinfo.Register,
profiling.Register,
Expand All @@ -134,6 +133,7 @@ func (s *Service) Start(ctx context.Context) error {
diagnose.Register,
keyvisual.Register,
metrics.Register,
queryeditor.Register,
// Must be at the end
s.status.Register,
),
Expand Down
53 changes: 0 additions & 53 deletions pkg/apiserver/foo/foo.go

This file was deleted.

10 changes: 6 additions & 4 deletions pkg/apiserver/info/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ func Register(r *gin.RouterGroup, auth *user.AuthService, s *Service) {
}

type InfoResponse struct { //nolint:golint
Version *version.Info `json:"version"`
EnableTelemetry bool `json:"enable_telemetry"`
Version *version.Info `json:"version"`
EnableTelemetry bool `json:"enable_telemetry"`
EnableExperimental bool `json:"enable_experimental"`
}

// @Summary Dashboard info
Expand All @@ -61,8 +62,9 @@ type InfoResponse struct { //nolint:golint
// @Failure 401 {object} utils.APIError "Unauthorized failure"
func (s *Service) infoHandler(c *gin.Context) {
resp := InfoResponse{
Version: version.GetInfo(),
EnableTelemetry: s.config.EnableTelemetry,
Version: version.GetInfo(),
EnableTelemetry: s.config.EnableTelemetry,
EnableExperimental: s.config.EnableExperimental,
}
c.JSON(http.StatusOK, resp)
}
Expand Down
174 changes: 174 additions & 0 deletions pkg/apiserver/queryeditor/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright 2020 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package queryeditor

import (
"context"
"database/sql"
"net/http"
"time"

"github.com/gin-gonic/gin"
"github.com/pingcap/log"
"go.uber.org/fx"
"go.uber.org/zap"

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

type Service struct {
lifecycleCtx context.Context

config *config.Config
tidbForwarder *tidb.Forwarder
}

func NewService(lc fx.Lifecycle, config *config.Config, tidbForwarder *tidb.Forwarder) *Service {
service := &Service{config: config, tidbForwarder: tidbForwarder}
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
service.lifecycleCtx = ctx
return nil
},
})

return service
}

func Register(r *gin.RouterGroup, auth *user.AuthService, s *Service) {
endpoint := r.Group("/query_editor")
endpoint.Use(auth.MWAuthRequired())
endpoint.Use(utils.MWConnectTiDB(s.tidbForwarder))
endpoint.POST("/run", s.runHandler)
}

type RunRequest struct {
Statements string `json:"statements" example:"show databases;"`
MaxRows int `json:"max_rows" example:"1000"`
}

type RunResponse struct {
ErrorMsg string `json:"error_msg"`
ColumnNames []string `json:"column_names"`
Rows [][]interface{} `json:"rows"`
ExecutionMs int64 `json:"execution_ms"`
ActualRows int `json:"actual_rows"`
}

func executeStatements(context context.Context, db *sql.DB, statements string) ([]string, [][]interface{}, error) {
rows, err := db.QueryContext(context, statements)
if err != nil {
return nil, nil, err
}

defer rows.Close()

colNames, err := rows.Columns()
if err != nil {
return nil, nil, err
}

retRows := make([][]interface{}, 0)

values := make([]sql.RawBytes, len(colNames))
scanArgs := make([]interface{}, len(values))
for i := range values {
scanArgs[i] = &values[i]
}

for rows.Next() {
err = rows.Scan(scanArgs...)
if err != nil {
return nil, nil, err
}

retRow := make([]interface{}, 0, len(values))
var value interface{}
for _, col := range values {
if col == nil {
value = nil
} else {
value = string(col)
}
retRow = append(retRow, value)
}
retRows = append(retRows, retRow)
}

if err = rows.Err(); err != nil {
return nil, nil, err
}

return colNames, retRows, nil
}

// @ID queryEditorRun
// @Summary Run
// @Description Run statements
// @Produce json
// @Param request body RunRequest true "Request body"
// @Success 200 {object} RunResponse
// @Router /query_editor/run [post]
// @Security JwtAuth
// @Failure 400 {object} utils.APIError "Bad request"
// @Failure 401 {object} utils.APIError "Unauthorized failure"
// @Failure 403 {object} utils.APIError "Experimental feature not enabled"
func (s *Service) runHandler(c *gin.Context) {
if !s.config.EnableExperimental {
c.Status(http.StatusForbidden)
_ = c.Error(utils.ErrExpNotEnabled.NewWithNoMessage())
return
}

var req RunRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.Status(http.StatusBadRequest)
_ = c.Error(utils.ErrInvalidRequest.WrapWithNoMessage(err))
return
}

ctx, cancel := context.WithTimeout(s.lifecycleCtx, time.Minute*5)
defer cancel()

startTime := time.Now()
colNames, rows, err := executeStatements(ctx, utils.GetTiDBConnection(c).DB(), req.Statements)
elapsedTime := time.Since(startTime)

if err != nil {
log.Warn("Failed to execute user input statements", zap.String("statements", req.Statements), zap.Error(err))
c.JSON(http.StatusOK, RunResponse{
ErrorMsg: err.Error(),
ColumnNames: nil,
Rows: nil,
ExecutionMs: elapsedTime.Milliseconds(),
ActualRows: 0,
})
return
}

truncatedRows := rows
if len(truncatedRows) > req.MaxRows {
truncatedRows = truncatedRows[:req.MaxRows]
}

c.JSON(http.StatusOK, RunResponse{
ColumnNames: colNames,
Rows: truncatedRows,
ExecutionMs: elapsedTime.Milliseconds(),
ActualRows: len(rows),
})
}
2 changes: 1 addition & 1 deletion pkg/apiserver/statement/statement.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (s *Service) configHandler(c *gin.Context) {
c.JSON(http.StatusOK, cfg)
}

// @Summary Statement configurationt
// @Summary Statement configuration
// @Description Modify configuration of statements
// @Param request body statement.Config true "Request body"
// @Success 204 {object} string
Expand Down
6 changes: 3 additions & 3 deletions pkg/apiserver/user/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ type AuthService struct {
}

type authenticateForm struct {
IsTiDBAuth bool `json:"is_tidb_auth" binding:"required"`
Username string `json:"username" binding:"required"`
Password string `json:"password"`
IsTiDBAuth bool `json:"is_tidb_auth" binding:"required" example:"true"`
Username string `json:"username" binding:"required" example:"root"`
Password string `json:"password" example:""`
}

type TokenResponse struct {
Expand Down
1 change: 1 addition & 0 deletions pkg/apiserver/utils/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ var (
ErrUnauthorized = ErrNS.NewType("unauthorized")
ErrInsufficientPrivilege = ErrNS.NewType("insufficient_privilege")
ErrInvalidRequest = ErrNS.NewType("invalid_request")
ErrExpNotEnabled = ErrNS.NewType("experimental_feature_not_enabled")
)

type APIError struct {
Expand Down
11 changes: 4 additions & 7 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,11 @@ type Config struct {
PDEndPoint string
PublicPathPrefix string

// TLS config for mTLS authentication between TiDB components.
ClusterTLSConfig *tls.Config
ClusterTLSConfig *tls.Config // TLS config for mTLS authentication between TiDB components.
TiDBTLSConfig *tls.Config // TLS config for mTLS authentication between TiDB and MySQL client.

// TLS config for mTLS authentication between TiDB and MySQL client.
TiDBTLSConfig *tls.Config

// Enable client to report data for analysis
EnableTelemetry bool
EnableTelemetry bool
EnableExperimental bool
}

func (c *Config) NormalizePDEndPoint() error {
Expand Down
Loading