Skip to content

Commit a36674b

Browse files
chore: support more commands && add tests
1 parent e01d8e7 commit a36674b

File tree

5 files changed

+123
-60
lines changed

5 files changed

+123
-60
lines changed

build.sh

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,3 @@
22
# doc https://stackoverflow.com/questions/3861634/how-to-reduce-compiled-file-size
33
go clean
44
go build -ldflags "-w"
5-
6-

cmd/cmd.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ import (
88
"medusar.org/zooklient/util"
99
)
1010

11+
//SupportedCmds stores all the supported commands for zookeeper
12+
var SupportedCmds = []ZkCmd{
13+
&LsCmd{}, &GetCmd{}, &StatCmd{}, &SetCmd{},
14+
&DeleteCmd{}, &CreateCmd{}, &DeleteAllCmd{},
15+
}
16+
1117
//ZkCmd represents a console command of zk client
1218
type ZkCmd interface {
1319
//Usage get the usage information of the cmd
@@ -190,3 +196,25 @@ func ParseCreate(args []string) (*CreateCmd, error) {
190196
acl := cmd.Arg(2)
191197
return &CreateCmd{HasS: *hasS, HasE: *hasE, HasC: *hasC, HasT: hasT, TTL: *ttl, Path: path, Data: data, ACL: acl}, nil
192198
}
199+
200+
//DeleteAllCmd stores all the parameters followed by `deleteall`,
201+
//usage: deleteall path
202+
type DeleteAllCmd struct {
203+
Path string
204+
}
205+
206+
func (*DeleteAllCmd) Usage() string {
207+
return "deleteall path"
208+
}
209+
210+
func ParseDeleteAll(args []string) (*DeleteAllCmd, error) {
211+
cmd := flag.NewFlagSet("deleteall", flag.ContinueOnError)
212+
if err := cmd.Parse(args); err != nil {
213+
return nil, err
214+
}
215+
path := cmd.Arg(0)
216+
if path == "" {
217+
return nil, fmt.Errorf("deleteall path")
218+
}
219+
return &DeleteAllCmd{path}, nil
220+
}

main.go

Lines changed: 64 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,6 @@ func execZkCmd(con *zk.Conn, cmd string, args []string) error {
186186
printChildren(children, stat)
187187
}
188188
}
189-
break
190189
case "get":
191190
get, err := cmdParser.ParseGet(args)
192191
if err != nil {
@@ -204,7 +203,6 @@ func execZkCmd(con *zk.Conn, cmd string, args []string) error {
204203
if get.WithStat {
205204
printStat(stat)
206205
}
207-
break
208206
case "stat":
209207
statCmd, err := cmdParser.ParseStat(args)
210208
if err != nil {
@@ -218,7 +216,6 @@ func execZkCmd(con *zk.Conn, cmd string, args []string) error {
218216
return fmt.Errorf("Node does not exist: %s", statCmd.Path)
219217
}
220218
printStat(stat)
221-
break
222219
case "set":
223220
set, err := cmdParser.ParseSet(args)
224221
if err != nil {
@@ -231,7 +228,6 @@ func execZkCmd(con *zk.Conn, cmd string, args []string) error {
231228
if set.WithStat {
232229
printStat(stat)
233230
}
234-
break
235231
case "create":
236232
create, err := cmdParser.ParseCreate(args)
237233
if err != nil {
@@ -286,20 +282,20 @@ func execZkCmd(con *zk.Conn, cmd string, args []string) error {
286282
return err
287283
}
288284
fmt.Println("Created", s)
289-
break
290285
case "getAcl":
291-
fmt.Println("Not supported yet")
292-
break
286+
fallthrough
293287
case "setAcl":
294-
fmt.Println("Not supported yet")
295-
break
288+
fallthrough
296289
case "setquota":
290+
fallthrough
297291
case "listquota":
292+
fallthrough
298293
case "delquota":
294+
fallthrough
299295
case "addauth":
296+
fallthrough
300297
case "config":
301298
fmt.Println("Not supported yet")
302-
break
303299
case "sync":
304300
if len(args) != 1 {
305301
return fmt.Errorf("sync path")
@@ -309,7 +305,6 @@ func execZkCmd(con *zk.Conn, cmd string, args []string) error {
309305
return err
310306
}
311307
fmt.Println("Sync is OK")
312-
break
313308
case "delete":
314309
del, err := cmdParser.ParseDelete(args)
315310
if err != nil {
@@ -318,17 +313,17 @@ func execZkCmd(con *zk.Conn, cmd string, args []string) error {
318313
if err := con.Delete(del.Path, del.Version); err != nil {
319314
return err
320315
}
321-
break
322316
case "deleteall":
323-
fmt.Println("Not supported yet")
324-
break
317+
da, err := cmdParser.ParseDeleteAll(args)
318+
if err != nil {
319+
return err
320+
}
321+
return deleteRecursive(zkconn.con, da.Path)
325322
case "close":
326323
con.Close()
327-
break
328324
default:
329325
printUsage()
330326
fmt.Println("Command not found: Command not found", cmd)
331-
break
332327
}
333328
return nil
334329
}
@@ -340,34 +335,60 @@ func saveHistory(cmd string) {
340335
}
341336
}
342337

343-
//TODO: print only supported cmds
344338
func printUsage() {
345-
fmt.Println(`ZooKeeper -server host:port cmd args
346-
addauth scheme auth
347-
close
348-
config [-c] [-w] [-s]
349-
connect host:port
350-
create [-s] [-e] [-c] [-t ttl] path [data] [acl]
351-
delete [-v version] path
352-
deleteall path
353-
delquota [-n|-b] path
354-
get [-s] [-w] path
355-
getAcl [-s] path
356-
history
357-
listquota path
358-
ls [-s] [-w] [-R] path
359-
ls2 path [watch]
360-
printwatches on|off
361-
quit
362-
reconfig [-s] [-v version] [[-file path] | [-members serverID=host:port1:port2;port3[,...]*]] | [-add serverId=host:port1:port2;port3[,...]]* [-remove serverId[,...]*]
363-
redo cmdno
364-
removewatches path [-c|-d|-a] [-l]
365-
rmr path
366-
set [-s] [-v version] path data
367-
setAcl [-s] [-v version] [-R] path acl
368-
setquota -n|-b val path
369-
stat [-w] path
370-
sync path`)
339+
fmt.Println("ZooKeeper -server host:port cmd args")
340+
fmt.Println("\t\t connect host:port")
341+
fmt.Println("\t\t history")
342+
fmt.Println("\t\t quit")
343+
for _, c := range cmdParser.SupportedCmds {
344+
fmt.Println("\t\t", c.Usage())
345+
}
346+
}
347+
348+
func deleteRecursive(con *zk.Conn, path string) error {
349+
err := util.ValidatePath(path)
350+
if err != nil {
351+
return err
352+
}
353+
354+
paths := listSubTreeBFS(con, path)
355+
356+
deleteReqs := make([]interface{}, 0)
357+
total := len(paths)
358+
for i := total - 1; i >= 0; i-- {
359+
p := paths[i]
360+
deleteReqs = append(deleteReqs, &zk.DeleteRequest{Path: p, Version: -1})
361+
}
362+
363+
if _, err := con.Multi(deleteReqs...); err != nil {
364+
return err
365+
}
366+
return nil
367+
}
368+
369+
func listSubTreeBFS(con *zk.Conn, pathRoot string) []string {
370+
queue := make([]string, 0)
371+
tree := make([]string, 0)
372+
373+
queue = append(queue, pathRoot)
374+
tree = append(tree, pathRoot)
375+
376+
for len(queue) > 0 {
377+
node := queue[0]
378+
queue = queue[1:]
379+
children, _, err := con.Children(node)
380+
if err != nil {
381+
log.Println(err)
382+
continue
383+
}
384+
for _, child := range children {
385+
childPath := node + "/" + child
386+
queue = append(queue, childPath)
387+
tree = append(tree, childPath)
388+
}
389+
}
390+
391+
return tree
371392
}
372393

373394
func visitSubTree(con *zk.Conn, path string) {

util/util.go

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,17 @@ func ValidatePath(path string) error {
2727
}
2828

2929
var reason string
30-
lastc := '/'
30+
lastr := '/'
3131
chars := []rune(path)
32-
for i, r := range chars {
33-
lastc = r
32+
for i := 1; i < len(chars); i++ {
33+
r := chars[i]
3434
if r == 0 {
3535
reason = fmt.Sprintf("null character not allowed @%d", i)
3636
break
37-
} else if r == '/' && lastc == '/' {
37+
} else if r == '/' && lastr == '/' {
3838
reason = fmt.Sprintf("empty node name specified @%d", i)
3939
break
40-
} else if r == '.' && lastc == '.' {
40+
} else if r == '.' && lastr == '.' {
4141
if chars[i-2] == '/' && (i+1 == len(chars) || chars[i+1] == '/') {
4242
reason = fmt.Sprintf("relative paths not allowed @%d", i)
4343
break
@@ -47,19 +47,21 @@ func ValidatePath(path string) error {
4747
reason = fmt.Sprintf("relative paths not allowed @%d", i)
4848
break
4949
}
50-
//TODO: check if this is the right way
5150
} else if r > '\u0000' && r <= '\u001f' || r >= '\u007f' && r <= '\u009F' || r >= 0xd800 && r <= 0xf8ff || r >= 0xfff0 && r <= 0xffff {
5251
reason = fmt.Sprintf("invalid character @%d", i)
5352
break
5453
}
54+
55+
lastr = r
5556
}
5657
if reason != "" {
5758
return fmt.Errorf("Invalid path string \"" + path + "\" caused by " + reason)
5859
}
5960
return nil
6061
}
6162

62-
//ParseACL parses acl strings into zk.ACL
63+
//ParseACL parses acl strings into zk.ACL;
64+
//notation of acl: `scheme:id:perm`
6365
func ParseACL(acl string) []zk.ACL {
6466
acls := strings.Split(acl, ",")
6567
zkACLs := make([]zk.ACL, 0)
@@ -70,32 +72,27 @@ func ParseACL(acl string) []zk.ACL {
7072
fmt.Println(a + " does not have the form scheme:id:perm")
7173
continue
7274
}
73-
perms := getPerms(a[lastColon+1:])
75+
perms := parsePerms(a[lastColon+1:])
7476
zkACLs = append(zkACLs, zk.ACL{Scheme: a[0:firstColon], ID: a[firstColon+1 : lastColon], Perms: perms})
7577
}
7678
return zkACLs
7779
}
7880

79-
func getPerms(s string) int32 {
81+
func parsePerms(s string) int32 {
8082
perm := int32(0)
8183
for _, r := range s {
8284
char := string(r)
8385
switch char {
8486
case "r":
8587
perm |= zk.PermRead
86-
break
8788
case "w":
8889
perm |= zk.PermWrite
89-
break
9090
case "c":
9191
perm |= zk.PermCreate
92-
break
9392
case "d":
9493
perm |= zk.PermDelete
95-
break
9694
case "a":
9795
perm |= zk.PermAdmin
98-
break
9996
default:
10097
fmt.Println("Unknown perm type:", char)
10198
}

util/util_test.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,26 @@ func TestValidatePath(t *testing.T) {
1111
args args
1212
wantErr bool
1313
}{
14-
// TODO: Add test cases.
14+
{"valid", args{"/this is / a valid/path"}, false},
15+
{"valid_1", args{"/name/with.period."}, false},
16+
{"valid_2", args{"/test\u0020"}, false},
17+
{"valid_3", args{"/test\u007e"}, false},
18+
{"valid_4", args{"/test\uffef"}, false},
19+
{"invalid", args{}, true},
20+
{"invalid_1", args{""}, true},
21+
{"invalid_2", args{"not/valid"}, true},
22+
{"/ends/with/slash/", args{"/ends/with/slash/"}, true},
23+
{"/double//slash", args{"/double//slash"}, true},
24+
{"/single/./period", args{"/single/./period"}, true},
25+
{"/double/../period", args{"/double/../period"}, true},
26+
{"illegal_char", args{"/test\u0000"}, true},
27+
{"illegal_char_1", args{"/test\u0001"}, true},
28+
{"illegal_char_2", args{"/test\u001F"}, true},
29+
{"illegal_char_3", args{"/test\u007F"}, true},
30+
{"illegal_char_4", args{"/test\u009F"}, true},
31+
{"illegal_char_5", args{string([]rune{'/', 't', 'e', 's', 't', 0xd800})}, true},
32+
{"illegal_char_6", args{"/test\uf8ff"}, true},
33+
{"illegal_char_7", args{"/test\ufff0"}, true},
1534
}
1635
for _, tt := range tests {
1736
t.Run(tt.name, func(t *testing.T) {

0 commit comments

Comments
 (0)