Skip to content

Commit 842c726

Browse files
Working version which gets all Vitess keys from etcd
Signed-off-by: Rohit Nayak <[email protected]>
1 parent 781b63b commit 842c726

File tree

5 files changed

+553
-0
lines changed

5 files changed

+553
-0
lines changed

etcd-dump/README.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Etcd Dump
2+
3+
Get a list of all the keys stored by Vitess in Etcd for debugging and understanding how Vitess uses topo.
4+
5+
## Getting Started
6+
7+
To create a dump of all the keys in Vitess' etcd topology run ```go build``` followed by ```./etcd_dump``` or
8+
```go run etcd_dump.go tree.go```
9+
10+
### Options
11+
12+
```
13+
-file string
14+
Stores html output into this file path
15+
-quiet
16+
Do not open in browser (by default it opens the topo tree in a browser)
17+
-server string
18+
Etcd API endpoint (default "127.0.0.1:2379")
19+
```
20+
21+
## Future Work
22+
23+
1. Currently define the mapping of the protobuf to a Vitess internal type in this tool. We should ideally use
24+
existing vitess libraries. However etcd V2 does not seem to have a simple way to get all keys without making recursive
25+
calls to etcd2. If I use etcd V3 it seems to clash with the Vitess libraries since vitess currently uses V2.
26+
27+
2. Test for ZooKeeper and Consul and add support for them if required.

etcd-dump/etcd_dump.go

+257
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"flag"
6+
"fmt"
7+
_ "fmt"
8+
"github.com/golang/protobuf/jsonpb"
9+
"github.com/golang/protobuf/proto"
10+
"github.com/pkg/browser"
11+
"go.etcd.io/etcd/clientv3"
12+
"html"
13+
"io/ioutil"
14+
"strings"
15+
"time"
16+
"unicode"
17+
"vitess.io/vitess/go/vt/log"
18+
"vitess.io/vitess/go/vt/proto/topodata"
19+
"vitess.io/vitess/go/vt/proto/vschema"
20+
)
21+
22+
var (
23+
server = flag.String("server", "127.0.0.1:2379", "Etcd API endpoint")
24+
outputFile = flag.String("out", "", "Stores html output into this file path")
25+
quiet = flag.Bool("quiet", false, "Do not open in browser (by default it opens the topo tree in a browser)")
26+
dialTimeout = 2 * time.Second
27+
requestTimeout = 10 * time.Second
28+
protoDecodeError = "Error decoding"
29+
messages = map[string]proto.Message {
30+
"RoutingRules": &vschema.RoutingRules{},
31+
"CellInfo":&topodata.CellInfo{},
32+
"Keyspace":&topodata.Keyspace{},
33+
"Shard":&topodata.Shard{},
34+
"SrvVSchema":&vschema.SrvVSchema{},
35+
"SrvKeyspace":&topodata.SrvKeyspace{},
36+
"ShardReplication":&topodata.ShardReplication{},
37+
"Tablet":&topodata.Tablet{},
38+
"VSchema":&vschema.Keyspace{},
39+
}
40+
41+
)
42+
43+
var css = `
44+
<style>
45+
/* Remove default bullets */
46+
ul, #root {
47+
list-style-type: none;
48+
}
49+
50+
/* Remove margins and padding from the parent ul */
51+
#root {
52+
margin: 0;
53+
padding: 0;
54+
}
55+
56+
/* Style the caret/arrow */
57+
.caret {
58+
cursor: pointer;
59+
user-select: none; /* Prevent text selection */
60+
}
61+
62+
/* Create the caret/arrow with a unicode, and style it */
63+
.caret::before {
64+
content: "\25B6";
65+
color: black;
66+
display: inline-block;
67+
margin-right: 6px;
68+
}
69+
70+
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
71+
.caret-down::before {
72+
transform: rotate(90deg);
73+
}
74+
75+
/* Hide the nested list */
76+
.nested {
77+
display: none;
78+
}
79+
80+
/* Show the nested list when the user clicks on the caret/arrow (with JavaScript) */
81+
.active {
82+
display: block;
83+
}
84+
</style>`
85+
86+
var js = `
87+
<script>
88+
var toggler = document.getElementsByClassName("caret");
89+
var i;
90+
91+
for (i = 0; i < toggler.length; i++) {
92+
toggler[i].addEventListener("click", function() {
93+
this.parentElement.querySelector(".nested").classList.toggle("active");
94+
this.classList.toggle("caret-down");
95+
});
96+
}
97+
98+
function all(open) {
99+
var elems = document.getElementsByClassName("nested")
100+
for (i=0; i < elems.length; i++)
101+
open ? elems[i].classList.add("active") : elems[i].classList.remove("active")
102+
elems = document.getElementsByClassName("caret")
103+
for (i=0; i < elems.length; i++)
104+
open ? elems[i].classList.add("caret-down") : elems[i].classList.remove("caret-down")
105+
}
106+
</script>
107+
`
108+
109+
func decodeProtoBuf(objectType string, objectBuf []byte) string {
110+
msg, found := messages[objectType]
111+
if !found {
112+
log.Errorf("No mapping for %v", objectType)
113+
return string(objectBuf)
114+
}
115+
err := proto.Unmarshal(objectBuf, msg)
116+
if err != nil {
117+
log.Errorf("Could not unmarshal %v:%v", objectType, objectBuf)
118+
return string(objectBuf)
119+
}
120+
json, err := new(jsonpb.Marshaler).MarshalToString(msg)
121+
if err != nil {
122+
log.Errorf("JSON error %v %v", err, json)
123+
return msg.String()
124+
}
125+
return json
126+
}
127+
128+
func store(tree *Tree, key string, value string ) {
129+
paths := strings.Split(key, "/")
130+
typeString := paths[len(paths)-1]
131+
if unicode.IsUpper(rune(typeString[0])) {
132+
value = decodeProtoBuf(typeString, []byte(value))
133+
value = html.EscapeString(value)
134+
}
135+
136+
paths = append(paths, value)
137+
paths = paths[1:]
138+
parent := tree.root
139+
for _, path := range paths {
140+
parent = tree.addChild(parent, path)
141+
}
142+
}
143+
144+
func loadKeys() *Tree{
145+
ctx, _ := context.WithTimeout(context.Background(), requestTimeout)
146+
client, err := clientv3.New(clientv3.Config{
147+
DialTimeout: dialTimeout,
148+
Endpoints: []string{*server},
149+
})
150+
151+
if err != nil {
152+
fmt.Errorf("Cannot connect to server at %s : %v\n", *server, err)
153+
return nil
154+
}
155+
defer client.Close()
156+
157+
tree := NewTree("Vitess Etcd")
158+
tree.setRoot("root")
159+
160+
kv := clientv3.NewKV(client)
161+
162+
gr, err := kv.Get(ctx, "", clientv3.WithPrefix(),
163+
clientv3.WithSort(clientv3.SortByKey, clientv3.SortAscend))
164+
if err != nil {
165+
return nil
166+
}
167+
for _, item := range gr.Kvs {
168+
key := string(item.Key)
169+
store(tree, key, string(item.Value))
170+
}
171+
return tree
172+
}
173+
174+
type Item struct {
175+
level int
176+
name string
177+
next int
178+
}
179+
180+
func flatten(tree *Tree) ([]Item, int) {
181+
var list []Item
182+
maxLevel := 0 //maxlevel not required for a tree representation, but useful for displaying in a a table for example
183+
callback := func (name string, level int) {
184+
list = append(list, Item{
185+
level: level,
186+
name: name,
187+
})
188+
if level > maxLevel {
189+
maxLevel = level
190+
}
191+
}
192+
tree.root.traverse(0, callback)
193+
for idx, item := range list {
194+
if idx != len(list) -1 {
195+
list[idx].next = list[idx+1].level - item.level
196+
if list[idx].next < -1 {
197+
list[idx].next = -1
198+
}
199+
}
200+
}
201+
return list, maxLevel
202+
}
203+
204+
func toHtml(list []Item) string {
205+
html := "<html><body><h1>etcd: dump of all keys</h1>\n"
206+
html += css
207+
html += "<a href='javascript:all(true)' style='text-decoration:none'>Expand All</a>&nbsp;&nbsp;&nbsp;&nbsp;"
208+
html += "<a href='javascript:all(false)' style='text-decoration:none'>Collapse All</a>\n"
209+
html += "<br><br><ul id='root'>\n"
210+
closeTags := make([]string,0)
211+
push := func (s string) {
212+
closeTags = append(closeTags, s)
213+
}
214+
pop := func () string {
215+
var s string
216+
s, closeTags = closeTags[len(closeTags)-1], closeTags[:len(closeTags)-1]
217+
return s
218+
}
219+
push("</ul>")
220+
for _, item := range list {
221+
switch item.next {
222+
case 1:
223+
html += "<li><span class='caret'>" + item.name + "</span>\n"
224+
html += "<ul class='nested'>\n"
225+
push("</ul></li>")
226+
break
227+
case 0:
228+
html += "<li>"+item.name+"</li>"
229+
break
230+
case -1:
231+
html += "<li>"+item.name+"</li>"
232+
html += pop()
233+
break
234+
default:
235+
panic(fmt.Sprintf("Invalid item.next %d", item.next))
236+
237+
}
238+
}
239+
return html + js
240+
}
241+
242+
func main() {
243+
flag.Parse()
244+
tree := loadKeys()
245+
if tree == nil {
246+
fmt.Errorf("no keys found")
247+
return
248+
}
249+
list, _ := flatten(tree)
250+
html := toHtml(list)
251+
if *outputFile != "" {
252+
_ = ioutil.WriteFile(*outputFile, []byte(html), 0644)
253+
}
254+
if !*quiet {
255+
browser.OpenReader(strings.NewReader(html))
256+
}
257+
}

0 commit comments

Comments
 (0)