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

show store location topology #719

Merged
merged 22 commits into from Aug 14, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 19 additions & 0 deletions pkg/apiserver/clusterinfo/service.go
Expand Up @@ -71,6 +71,8 @@ func Register(r *gin.RouterGroup, auth *user.AuthService, s *Service) {
endpoint.GET("/alertmanager/:address/count", s.getAlertManagerCounts)
endpoint.GET("/grafana", s.getGrafanaTopology)

endpoint.GET("/store_location", s.getStoreLocationTopology)

endpoint = r.Group("/host")
endpoint.Use(auth.MWAuthRequired())
endpoint.Use(utils.MWConnectTiDB(s.tidbForwarder))
Expand Down Expand Up @@ -160,6 +162,23 @@ func (s *Service) getStoreTopology(c *gin.Context) {
})
}

// @ID getStoreLocationTopology
// @Summary Get store location
// @Description Get store location topology
// @Produce json
// @Success 200 {object} topology.StoreLocation
// @Router /topology/store_location [get]
// @Security JwtAuth
// @Failure 401 {object} utils.APIError "Unauthorized failure"
func (s *Service) getStoreLocationTopology(c *gin.Context) {
storeLocation, err := topology.FetchStoreLocation(s.pdClient)
if err != nil {
_ = c.Error(err)
return
}
c.JSON(http.StatusOK, storeLocation)
}

// @ID getPDTopology
// @Summary Get PD instances
// @Description Get PD instances topology
Expand Down
10 changes: 10 additions & 0 deletions pkg/utils/topology/models.go
Expand Up @@ -57,6 +57,16 @@ type StoreInfo struct {
StartTimestamp int64 `json:"start_timestamp"`
}

type StoreLabels struct {
Address string `json:"address"`
Labels map[string]string `json:"labels"`
}

type StoreLocation struct {
LocationLabels string `json:"location_labels"`
baurine marked this conversation as resolved.
Show resolved Hide resolved
Stores []StoreLabels `json:"stores"`
}

type StandardComponentInfo struct {
IP string `json:"ip"`
Port uint `json:"port"`
Expand Down
16 changes: 16 additions & 0 deletions pkg/utils/topology/pd.go
Expand Up @@ -135,3 +135,19 @@ func fetchPDHealth(pdClient *pd.Client) (map[uint64]struct{}, error) {
}
return memberHealth, nil
}

func fetchLocationLabels(pdClient *pd.Client) (string, error) {
data, err := pdClient.SendGetRequest("/config/replicate")
if err != nil {
return "", err
}

var replicateConfig struct {
LocationLabels string `json:"location-labels"`
}
err = json.Unmarshal(data, &replicateConfig)
if err != nil {
return "", ErrInvalidTopologyData.Wrap(err, "PD config/replicate API unmarshal failed")
}
return replicateConfig.LocationLabels, nil
}
35 changes: 33 additions & 2 deletions pkg/utils/topology/store.go
Expand Up @@ -50,6 +50,37 @@ func FetchStoreTopology(pdClient *pd.Client) ([]StoreInfo, []StoreInfo, error) {
return buildStoreTopology(tiKVStores), buildStoreTopology(tiFlashStores), nil
}

func FetchStoreLocation(pdClient *pd.Client) (*StoreLocation, error) {
locationLabels, err := fetchLocationLabels(pdClient)
if err != nil {
return nil, err
}

stores, err := fetchStores(pdClient)
if err != nil {
return nil, err
}

nodes := make([]StoreLabels, 0, len(stores))
for _, s := range stores {
node := StoreLabels{
Address: s.Address,
Labels: map[string]string{},
}
for _, l := range s.Labels {
node.Labels[l.Key] = l.Value
}
nodes = append(nodes, node)
}

storeLocation := StoreLocation{
LocationLabels: locationLabels,
Stores: nodes,
}

return &storeLocation, nil
}

func buildStoreTopology(stores []store) []StoreInfo {
nodes := make([]StoreInfo, 0, len(stores))
for _, v := range stores {
Expand Down Expand Up @@ -81,7 +112,7 @@ func buildStoreTopology(stores []store) []StoreInfo {
StartTimestamp: v.StartTimestamp,
}
for _, v := range v.Labels {
node.Labels[v.Key] = node.Labels[v.Value]
node.Labels[v.Key] = v.Value
}
nodes = append(nodes, node)
}
Expand All @@ -95,7 +126,7 @@ type store struct {
Labels []struct {
Key string `json:"key"`
Value string `json:"value"`
}
} `json:"labels"`
StateName string `json:"state_name"`
Version string `json:"version"`
StatusAddress string `json:"status_address"`
Expand Down
58 changes: 58 additions & 0 deletions ui/lib/apps/ClusterInfo/components/StoreLocation.tsx
@@ -0,0 +1,58 @@
import React, { useMemo } from 'react'
import { useClientRequest } from '@lib/utils/useClientRequest'
import client, { TopologyStoreLocation } from '@lib/client'
import { Pre } from '@lib/components'

type TreeNode = {
name: string
value: string
children: TreeNode[]
}

function buildTopology(data: TopologyStoreLocation | undefined) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is easier to do the aggregation in the frontend, so I didn't do it in the backend.

Copy link
Member

Choose a reason for hiding this comment

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

I agree

let treeData: TreeNode = { name: '', value: '', children: [] }
if ((data?.location_labels?.length || 0) > 0) {
const locationLabels: string[] = data?.location_labels?.split(',') || []
treeData.name = locationLabels[0]

for (const store of data?.stores || []) {
// reset curNode, point to tree nodes beginning
let curNode = treeData
for (const curLabel of locationLabels) {
const curLabelVal = store.labels![curLabel]
if (curLabelVal === undefined) {
continue
}
let subNode: TreeNode | undefined = curNode.children.find(
(el) => el.name === curLabel && el.value === curLabelVal
)
if (subNode === undefined) {
subNode = { name: curLabel, value: curLabelVal, children: [] }
curNode.children.push(subNode)
}
// make curNode point to subNode
curNode = subNode
}
curNode.children.push({
name: 'address',
value: store.address!,
children: [],
})
}
}
return treeData
}

export default function StoreLocation() {
const { data } = useClientRequest((cancelToken) =>
client.getInstance().getStoreLocationTopology({ cancelToken })
)
const locationTopology = useMemo(() => buildTopology(data), [data])

return (
<div>
<Pre>{JSON.stringify(data, undefined, 2)}</Pre>
<Pre>{JSON.stringify(locationTopology, undefined, 2)}</Pre>
</div>
)
}
4 changes: 4 additions & 0 deletions ui/lib/apps/ClusterInfo/pages/List.tsx
Expand Up @@ -9,6 +9,7 @@ import CardTabs from '@lib/components/CardTabs'

import HostTable from '../components/HostTable'
import InstanceTable from '../components/InstanceTable'
import StoreLocation from '../components/StoreLocation'

function renderTabBar(props, DefaultTabBar) {
return (
Expand Down Expand Up @@ -46,6 +47,9 @@ export default function ListPage() {
>
<HostTable />
</CardTabs.TabPane>
<CardTabs.TabPane tab="Store Location" key="store_location">
<StoreLocation />
</CardTabs.TabPane>
</CardTabs>
</Card>
</ScrollablePane>
Expand Down