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

server: Add table range api to http status server #20456

Merged
merged 4 commits into from
Oct 21, 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
82 changes: 75 additions & 7 deletions server/http_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ type valueHandler struct {

const (
opTableRegions = "regions"
opTableRanges = "ranges"
opTableDiskUsage = "disk-usage"
opTableScatter = "scatter-table"
opStopTableScatter = "stop-scatter-table"
Expand Down Expand Up @@ -521,6 +522,34 @@ type TableRegions struct {
Indices []IndexRegions `json:"indices"`
}

// RangeDetail contains detail information about a particular range
type RangeDetail struct {
StartKey []byte `json:"start_key"`
EndKey []byte `json:"end_key"`
StartKeyHex string `json:"start_key_hex"`
EndKeyHex string `json:"end_key_hex"`
}

func createRangeDetail(start, end []byte) RangeDetail {
return RangeDetail{
StartKey: start,
EndKey: end,
StartKeyHex: hex.EncodeToString(start),
EndKeyHex: hex.EncodeToString(end),
}
}

// TableRanges is the response data for list table's ranges.
// It contains ranges list for record and indices as well as the whole table.
type TableRanges struct {
TableName string `json:"name"`
TableID int64 `json:"id"`
Range RangeDetail `json:"table"`
Record RangeDetail `json:"record"`
Index RangeDetail `json:"index"`
Indices map[string]RangeDetail `json:"indices,omitempty"`
}

// RegionMeta contains a region's peer detail
type RegionMeta struct {
ID uint64 `json:"region_id"`
Expand All @@ -539,10 +568,9 @@ type IndexRegions struct {
// RegionDetail is the response data for get region by ID
// it includes indices and records detail in current region.
type RegionDetail struct {
RegionID uint64 `json:"region_id"`
StartKey []byte `json:"start_key"`
EndKey []byte `json:"end_key"`
Frames []*helper.FrameItem `json:"frames"`
RangeDetail `json:",inline"`
RegionID uint64 `json:"region_id"`
Frames []*helper.FrameItem `json:"frames"`
}

// addTableInRange insert a table into RegionDetail
Expand Down Expand Up @@ -968,6 +996,8 @@ func (h tableHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch h.op {
case opTableRegions:
h.handleRegionRequest(schema, tableVal, w, req)
case opTableRanges:
h.handleRangeRequest(schema, tableVal, w, req)
case opTableDiskUsage:
h.handleDiskUsageRequest(tableVal, w)
case opTableScatter:
Expand Down Expand Up @@ -1223,6 +1253,45 @@ func (h tableHandler) handleRegionRequest(schema infoschema.InfoSchema, tbl tabl
writeData(w, tableRegions)
}

func createTableRanges(tblID int64, tblName string, indices []*model.IndexInfo) *TableRanges {
indexPrefix := tablecodec.GenTableIndexPrefix(tblID)
recordPrefix := tablecodec.GenTableRecordPrefix(tblID)
tableEnd := tablecodec.EncodeTablePrefix(tblID + 1)
ranges := &TableRanges{
TableName: tblName,
TableID: tblID,
Range: createRangeDetail(tablecodec.EncodeTablePrefix(tblID), tableEnd),
Record: createRangeDetail(recordPrefix, tableEnd),
Index: createRangeDetail(indexPrefix, recordPrefix),
}
if len(indices) != 0 {
indexRanges := make(map[string]RangeDetail)
for _, index := range indices {
start := tablecodec.EncodeTableIndexPrefix(tblID, index.ID)
end := tablecodec.EncodeTableIndexPrefix(tblID, index.ID+1)
indexRanges[index.Name.String()] = createRangeDetail(start, end)
}
ranges.Indices = indexRanges
}
return ranges
}

func (h tableHandler) handleRangeRequest(schema infoschema.InfoSchema, tbl table.Table, w http.ResponseWriter, req *http.Request) {
meta := tbl.Meta()
pi := meta.GetPartitionInfo()
if pi != nil {
// Partitioned table.
var data []*TableRanges
for _, def := range pi.Definitions {
data = append(data, createTableRanges(def.ID, def.Name.String(), meta.Indices))
}
writeData(w, data)
return
}

writeData(w, createTableRanges(meta.ID, meta.Name.String(), meta.Indices))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same API will return different data structure responses for the partitioned tables. Can we use []*TableRanges{createTableRanges(meta.ID, meta.Name.String(), meta.Indices)} to make the API users use it friendly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this is the way that table regions api does, I wouldn't mind make this api consistent as long as we don't care these two similar APIs handle partition tables in different way.

image

}

func (h tableHandler) getRegionsByID(tbl table.Table, id int64, name string) (*TableRegions, error) {
// for record
startKey, endKey := tablecodec.GetTableHandleKeyRange(id)
Expand Down Expand Up @@ -1376,9 +1445,8 @@ func (h regionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {

// create RegionDetail from RegionFrameRange
regionDetail := &RegionDetail{
RegionID: regionID,
StartKey: region.StartKey,
EndKey: region.EndKey,
RegionID: regionID,
RangeDetail: createRangeDetail(region.StartKey, region.EndKey),
}
schema, err := h.schema()
if err != nil {
Expand Down
43 changes: 43 additions & 0 deletions server/http_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,25 @@ func (ts *HTTPHandlerTestSuite) TestRegionsAPIForClusterIndex(c *C) {
}
}

func (ts *HTTPHandlerTestSuite) TestRangesAPI(c *C) {
ts.startServer(c)
defer ts.stopServer(c)
ts.prepareData(c)
resp, err := ts.fetchStatus("/tables/tidb/t/ranges")
c.Assert(err, IsNil)
c.Assert(resp.StatusCode, Equals, http.StatusOK)
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)

var data TableRanges
err = decoder.Decode(&data)
c.Assert(err, IsNil)
c.Assert(data.TableName, Equals, "t")
c.Assert(len(data.Indices), Equals, 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the indices name should also be asserted here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert added

_, ok := data.Indices["PRIMARY"]
c.Assert(ok, IsTrue)
}

func (ts *HTTPHandlerTestSuite) regionContainsTable(c *C, regionID uint64, tableID int64) bool {
resp, err := ts.fetchStatus(fmt.Sprintf("/regions/%d", regionID))
c.Assert(err, IsNil)
Expand Down Expand Up @@ -320,6 +339,30 @@ func (ts *HTTPHandlerTestSuite) TestListTableRegions(c *C) {
c.Assert(err, IsNil)
}

func (ts *HTTPHandlerTestSuite) TestListTableRanges(c *C) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can merge this test into TestRangesAPI?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's copied from TestListTableRegions which turns out to be a test for not existing table and a partitioned table which indeed is different test.

ts.startServer(c)
defer ts.stopServer(c)
ts.prepareData(c)
// Test list table regions with error
resp, err := ts.fetchStatus("/tables/fdsfds/aaa/ranges")
c.Assert(err, IsNil)
defer resp.Body.Close()
c.Assert(resp.StatusCode, Equals, http.StatusBadRequest)

resp, err = ts.fetchStatus("/tables/tidb/pt/ranges")
c.Assert(err, IsNil)
defer resp.Body.Close()

var data []*TableRanges
dec := json.NewDecoder(resp.Body)
err = dec.Decode(&data)
c.Assert(err, IsNil)
c.Assert(len(data), Equals, 3)
for i, partition := range data {
c.Assert(partition.TableName, Equals, fmt.Sprintf("p%d", i))
}
}

func (ts *HTTPHandlerTestSuite) TestGetRegionByIDWithError(c *C) {
ts.startServer(c)
defer ts.stopServer(c)
Expand Down
1 change: 1 addition & 0 deletions server/http_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ func (s *Server) startHTTPServer() {
if s.cfg.Store == "tikv" {
// HTTP path for tikv.
router.Handle("/tables/{db}/{table}/regions", tableHandler{tikvHandlerTool, opTableRegions})
router.Handle("/tables/{db}/{table}/ranges", tableHandler{tikvHandlerTool, opTableRanges})
router.Handle("/tables/{db}/{table}/scatter", tableHandler{tikvHandlerTool, opTableScatter})
router.Handle("/tables/{db}/{table}/stop-scatter", tableHandler{tikvHandlerTool, opStopTableScatter})
router.Handle("/tables/{db}/{table}/disk-usage", tableHandler{tikvHandlerTool, opTableDiskUsage})
Expand Down