Compare commits

...

9 Commits
v0.2 ... master

Author SHA1 Message Date
b1818dc955 Usage scheme 2017-08-08 12:23:55 +03:00
aa1342c22a Docker instruction 2017-07-20 15:32:23 +03:00
9a1be42481 udpate README 2017-07-20 15:09:01 +03:00
0cc4c91e01 Sort childrens in ls: /v1/ls/path/to/node 2017-07-09 16:53:49 +03:00
38c820280e Fix memcache 2017-06-23 10:51:32 +03:00
ad9635920a Go 1.8 in Travis 2017-06-22 17:16:24 +03:00
1641bbfbab Memcached support 2017-06-22 17:09:26 +03:00
d14ad459dd * New method: PATCH
* Fix return path string in rmr

* Version bump
2017-05-04 18:45:29 +03:00
8a575eac18 return chrooted path in RMR instead of full node path 2017-05-04 16:44:22 +03:00
8 changed files with 249 additions and 28 deletions

View File

@ -2,6 +2,7 @@ language: go
go: go:
- 1.6 - 1.6
- 1.7 - 1.7
- 1.8
# tests script # tests script
script: script:

View File

@ -2,7 +2,6 @@
[![Build Status](https://travis-ci.org/Difrex/zoorest.svg?branch=master)](https://travis-ci.org/Difrex/zoorest) [![Build Status](https://travis-ci.org/Difrex/zoorest.svg?branch=master)](https://travis-ci.org/Difrex/zoorest)
Zookeeper HTTP rest API Zookeeper HTTP rest API
<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-generate-toc again --> <!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-generate-toc again -->
**Table of Contents** **Table of Contents**
@ -14,6 +13,8 @@ Zookeeper HTTP rest API
- [Get node data](#get-node-data) - [Get node data](#get-node-data)
- [Errors](#errors) - [Errors](#errors)
- [Create node recursive](#create-node-recursive) - [Create node recursive](#create-node-recursive)
- [Create node children](#create-node-children)
- [Errors](#errors)
- [Update node](#update-node) - [Update node](#update-node)
- [Errors](#errors) - [Errors](#errors)
- [Delete node recursive](#delete-node-recursive) - [Delete node recursive](#delete-node-recursive)
@ -23,24 +24,37 @@ Zookeeper HTTP rest API
- [Docker build](#docker-build) - [Docker build](#docker-build)
- [Binary file](#binary-file) - [Binary file](#binary-file)
- [Docker image](#docker-image) - [Docker image](#docker-image)
- [Get docker image](#get-docker-image)
- [AUTHORS](#authors) - [AUTHORS](#authors)
- [LICENSE](#license) - [LICENSE](#license)
- [DONATE](#donate)
<!-- markdown-toc end --> <!-- markdown-toc end -->
## Usage ## Usage
``` ```
Usage of ./zoorest: Usage of ./zoorest:
-listen string -listen string
Address to listen (default "127.0.0.1:8889") Address to listen (default "127.0.0.1:8889")
-mc
Enable memcached support
-mchosts string
Memcached servers. Comma separated (default "127.0.0.1:11211")
-mcprefix string
Memcached key prefix (default "zoorest")
-path string -path string
Zk root path (default "/") Zk root path (default "/")
-zk string -zk string
Zk servers. Comma separated (default "127.0.0.1:2181") Zk servers. Comma separated (default "127.0.0.1:2181")
``` ```
NOTE: If memcached support is enabled zkStat metrics will not be returned.
Tupical usage scheme:
[![tupical usage](img/usage.png)](img/usage.png)
## API ## API
### List node childrens ### List node childrens
@ -122,6 +136,25 @@ curl -XPUT http://127.0.0.1:8889/v1/up/two/three/four -d '{"four": "json"}'
/zoorest/two/three/four /zoorest/two/three/four
``` ```
### Create node children
Method: **PATCH**
Location: **/v1/up**
Return string with created children path
```
curl -XPATCH http://127.0.0.1:8889/v1/up/one/test -d 'test'
/one/test
```
#### Errors
```
curl -XPATCH http://127.0.0.1:8889/v1/up/six/test -d '{"six": "json"}'
zk: node does not exist
```
### Update node ### Update node
Method: **POST** Method: **POST**
@ -216,6 +249,18 @@ cd zoorest
Image will be tagged as zoorest:latest Image will be tagged as zoorest:latest
## Get docker image
Pull image
```
docker pull lessmore/zoorest
```
And run it
```
/usr/bin/docker run -d -p 8889:8889 --name zoorest lessmore/zoorest:latest --zk zoo1:2181,zoo2:2181,zoo3:2181 --path /zoorest/jail --listen 0.0.0.0:8889 -mc -mchosts mc1:11211,mc2:11211,mc3:11211
```
# AUTHORS # AUTHORS
Denis Zheleztsov <difrex.punk@gmail.com> Denis Zheleztsov <difrex.punk@gmail.com>
@ -223,3 +268,8 @@ Denis Zheleztsov <difrex.punk@gmail.com>
# LICENSE # LICENSE
GPLv3 see [LICENSE](LICENSE) GPLv3 see [LICENSE](LICENSE)
# DONATE
BTC 1JCmZQdESKPCrcjUxDRNgt5HaSgUEWZ8pV<br>
DASH XxEWcJgfiAav1gxTtVLGXqE5T66uMA7te7

View File

@ -16,7 +16,7 @@ ENTRYPOINT go build -ldflags "-linkmode external -extldflags -static" && mv zoor
EOF EOF
# Build builder # Build builder
docker build -t zoorest_builder -f Dockerfile.builder . docker build --no-cache -t zoorest_builder -f Dockerfile.builder .
# Build bin # Build bin
docker run -v $(pwd)/out:/out zoorest_builder docker run -v $(pwd)/out:/out zoorest_builder

BIN
img/usage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

30
main.go
View File

@ -10,6 +10,9 @@ var (
zk string zk string
listen string listen string
path string path string
mc bool
mcHosts string
mcPrefix string
) )
// init ... // init ...
@ -17,24 +20,39 @@ func init() {
flag.StringVar(&zk, "zk", "127.0.0.1:2181", "Zk servers. Comma separated") flag.StringVar(&zk, "zk", "127.0.0.1:2181", "Zk servers. Comma separated")
flag.StringVar(&listen, "listen", "127.0.0.1:8889", "Address to listen") flag.StringVar(&listen, "listen", "127.0.0.1:8889", "Address to listen")
flag.StringVar(&path, "path", "/", "Zk root path") flag.StringVar(&path, "path", "/", "Zk root path")
flag.BoolVar(&mc, "mc", false, "Enable memcached support")
flag.StringVar(&mcHosts, "mchosts", "127.0.0.1:11211", "Memcached servers. Comma separated")
flag.StringVar(&mcPrefix, "mcprefix", "zoorest", "Memcached key prefix")
flag.Parse() flag.Parse()
} }
// main ... // main ...
func main() { func main() {
var z rest.Zk var z rest.Zk
hosts := strings.Split(zk, ",") hosts := getSlice(zk, ",")
z.Hosts = hosts z.Hosts = hosts
conn, err := z.InitConnection() conn, err := z.InitConnection()
if err != nil { if err != nil {
panic(err) panic(err)
} }
zoo := rest.ZooNode{ var zoo rest.ZooNode
path, zoo.Path = path
conn, zoo.Conn = conn
z, zoo.Zoo = z
}
var MC rest.MC
MC.Hosts = getSlice(mcHosts, ",")
MC.Prefix = mcPrefix
MC.Enabled = mc
MC.Client = MC.InitConnection()
zoo.MC = MC
rest.Serve(listen, zoo) rest.Serve(listen, zoo)
} }
// getSlice returm slice
func getSlice(s string, delimeter string) []string {
return strings.Split(s, ",")
}

View File

@ -90,6 +90,8 @@ func (zk ZooNode) UP(w http.ResponseWriter, r *http.Request) {
go func() { ch <- zk.CreateNode(path, content) }() go func() { ch <- zk.CreateNode(path, content) }()
} else if r.Method == "POST" { } else if r.Method == "POST" {
go func() { ch <- zk.UpdateNode(path, content) }() go func() { ch <- zk.UpdateNode(path, content) }()
} else if r.Method == "PATCH" {
go func() { ch <- zk.CreateChild(path, content) }()
} else { } else {
e := strings.Join([]string{"Method", r.Method, "not alowed"}, " ") e := strings.Join([]string{"Method", r.Method, "not alowed"}, " ")
w.WriteHeader(500) w.WriteHeader(500)
@ -115,8 +117,6 @@ func (zk ZooNode) RM(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
path := vars["path"] path := vars["path"]
var rmPath string
rmPath = strings.Join([]string{zk.Path, path}, "")
if path == "/" { if path == "/" {
e := "Skiping root path" e := "Skiping root path"
w.WriteHeader(500) w.WriteHeader(500)
@ -124,6 +124,9 @@ func (zk ZooNode) RM(w http.ResponseWriter, r *http.Request) {
return return
} }
var rmPath string
rmPath = strings.Join([]string{zk.Path, path}, "")
if strings.Contains(rmPath, "//") { if strings.Contains(rmPath, "//") {
rmPath = strings.Replace(rmPath, "//", "/", 1) rmPath = strings.Replace(rmPath, "//", "/", 1)
} }
@ -131,7 +134,7 @@ func (zk ZooNode) RM(w http.ResponseWriter, r *http.Request) {
go func() { zk.RMR(rmPath) }() go func() { zk.RMR(rmPath) }()
w.WriteHeader(200) w.WriteHeader(200)
w.Write([]byte(rmPath)) w.Write([]byte(path))
} }
// GET ... // GET ...
@ -171,10 +174,10 @@ func (zk ZooNode) GET(w http.ResponseWriter, r *http.Request) {
func Serve(listen string, zk ZooNode) { func Serve(listen string, zk ZooNode) {
r := mux.NewRouter() r := mux.NewRouter()
r.HandleFunc("/v1/ls{path:[a-z0-9-_/.:]+}", zk.LS) r.HandleFunc("/v1/ls{path:[A-Za-z0-9-_/.:]+}", zk.LS)
r.HandleFunc("/v1/get{path:[a-z0-9-_/.:]+}", zk.GET) r.HandleFunc("/v1/get{path:[A-Za-z0-9-_/.:]+}", zk.GET)
r.HandleFunc("/v1/rmr{path:[a-z0-9-_/.:]+}", zk.RM) r.HandleFunc("/v1/rmr{path:[A-Za-z0-9-_/.:]+}", zk.RM)
r.HandleFunc("/v1/up{path:[a-z0-9-_/.:]+}", zk.UP) r.HandleFunc("/v1/up{path:[A-Za-z0-9-_/.:]+}", zk.UP)
http.Handle("/", r) http.Handle("/", r)

69
rest/cache.go Normal file
View File

@ -0,0 +1,69 @@
package rest
import (
"crypto/md5"
"encoding/base64"
"github.com/bradfitz/gomemcache/memcache"
"strings"
)
// Memcached connection settings
type MC struct {
Enabled bool `json:"enabled"`
Hosts []string `json:"hosts"`
Prefix string `json:"prefix"`
Client *memcache.Client
}
// InitConnection to memcached cluster
func (mc MC) InitConnection() *memcache.Client {
client := memcache.New(mc.Hosts...)
return client
}
// StoreToCache save data to memecache
func (mc MC) StoreToCache(key string, data []byte) error {
prefixed_key := strings.Join([]string{mc.Prefix, key}, "_")
var item memcache.Item
// item.Key = hashKey(prefixed_key)
item.Key = hashKey(prefixed_key)
item.Value = data
// store
err := mc.Client.Set(&item)
return err
}
// GetFromCache get node data from memcache
func (mc MC) GetFromCache(key string) ([]byte, error) {
var data *memcache.Item
prefixed_key := strings.Join([]string{mc.Prefix, key}, "_")
data, err := mc.Client.Get(hashKey(prefixed_key))
if err != nil {
return []byte(""), err
}
return data.Value, err
}
// DeleteFromCache delete key from memcache
func (mc MC) DeleteFromCache(key string) error {
prefixed_key := strings.Join([]string{mc.Prefix, key}, "_")
err := mc.Client.Delete(hashKey(prefixed_key))
return err
}
// hashKey return base64 encoded md5sum of prefixed_key
func hashKey(key string) string {
h := md5.New()
sum := h.Sum([]byte(key))
b64 := base64.StdEncoding.EncodeToString(sum)
return b64
}

View File

@ -3,6 +3,7 @@ package rest
import ( import (
"github.com/samuel/go-zookeeper/zk" "github.com/samuel/go-zookeeper/zk"
"log" "log"
"sort"
"strings" "strings"
"time" "time"
) )
@ -12,6 +13,7 @@ type ZooNode struct {
Path string Path string
Conn *zk.Conn Conn *zk.Conn
Zoo Zk Zoo Zk
MC MC
} }
//Zk Zookeeper connection settings //Zk Zookeeper connection settings
@ -25,13 +27,13 @@ type Zk struct {
func (conf Zk) InitConnection() (*zk.Conn, error) { func (conf Zk) InitConnection() (*zk.Conn, error) {
conn, _, err := zk.Connect(conf.Hosts, time.Second) conn, _, err := zk.Connect(conf.Hosts, time.Second)
if err != nil { if err != nil {
log.Panic("[ERROR] ", err) log.Fatal("[ERROR] ", err)
} }
return conn, err return conn, err
} }
// GetChildrens ... // GetChildrens get Zookeeper node childrens
func (z ZooNode) GetChildrens(path string) Ls { func (z ZooNode) GetChildrens(path string) Ls {
var lsPath string var lsPath string
lsPath = strings.Join([]string{z.Path, path}, "") lsPath = strings.Join([]string{z.Path, path}, "")
@ -43,10 +45,23 @@ func (z ZooNode) GetChildrens(path string) Ls {
lsPath = strings.Replace(lsPath, "//", "/", 1) lsPath = strings.Replace(lsPath, "//", "/", 1)
} }
log.Print("ls: ", lsPath) log.Print("V1 LS: ", lsPath)
var l Ls var l Ls
l.State = "OK" l.State = "OK"
l.Path = path
// if z.MC.Enabled {
// data, err := z.MC.GetFromCache(lsPath)
// if err != nil {
// log.Print("V1 LS ERROR: ", err.Error())
// } else {
// log.Print("We are get it from memecache!")
// childrens := strings.Split(string(data), ",")
// l.Childrens = childrens
// return l
// }
// }
childrens, zkStat, err := z.Conn.Children(lsPath) childrens, zkStat, err := z.Conn.Children(lsPath)
if err != nil { if err != nil {
@ -55,15 +70,25 @@ func (z ZooNode) GetChildrens(path string) Ls {
return l return l
} }
// // Store to cache
// if z.MC.Enabled {
// err := z.MC.StoreToCache(lsPath, []byte(strings.Join(childrens, ",")))
// if err != nil {
// log.Print("V1 LS: ", err.Error())
// }
// }
// Sort childrens alphabeticaly
sort.Strings(childrens)
l.Error = "" l.Error = ""
l.Childrens = childrens l.Childrens = childrens
l.Path = path
l.ZkStat = zkStat l.ZkStat = zkStat
return l return l
} }
// GetNode ... // GetNode data
func (z ZooNode) GetNode(path string) Get { func (z ZooNode) GetNode(path string) Get {
var getPath string var getPath string
getPath = strings.Join([]string{z.Path, path}, "") getPath = strings.Join([]string{z.Path, path}, "")
@ -75,10 +100,21 @@ func (z ZooNode) GetNode(path string) Get {
getPath = strings.Replace(getPath, "//", "/", 1) getPath = strings.Replace(getPath, "//", "/", 1)
} }
log.Print("get: ", getPath) log.Print("V1 GET: ", getPath)
var g Get var g Get
g.State = "OK" g.State = "OK"
g.Path = path
// Get data from memcached
if z.MC.Enabled {
if data, err := z.MC.GetFromCache(getPath); err != nil {
log.Print("[mc ERROR] ", err.Error())
} else {
g.Data = data
return g
}
}
data, zkStat, err := z.Conn.Get(getPath) data, zkStat, err := z.Conn.Get(getPath)
if err != nil { if err != nil {
@ -87,9 +123,16 @@ func (z ZooNode) GetNode(path string) Get {
return g return g
} }
// Store to cache
if z.MC.Enabled {
err := z.MC.StoreToCache(getPath, data)
if err != nil {
log.Print("[mc ERROR] ", err.Error())
}
}
g.Error = "" g.Error = ""
g.Data = data g.Data = data
g.Path = path
g.ZkStat = zkStat g.ZkStat = zkStat
return g return g
@ -112,9 +155,16 @@ func (z ZooNode) RMR(path string) {
err = z.Conn.Delete(path, -1) err = z.Conn.Delete(path, -1)
if err != nil { if err != nil {
log.Print("[zk ERROR] ", err) log.Print("[zk ERROR] ", err)
} else {
if z.MC.Enabled {
err := z.MC.DeleteFromCache(path)
if err != nil {
log.Print("[mc ERROR] ", err.Error())
}
} }
log.Print("[WARNING] ", path, " deleted") log.Print("[WARNING] ", path, " deleted")
} }
}
// CreateNode ... // CreateNode ...
func (z ZooNode) CreateNode(path string, content []byte) string { func (z ZooNode) CreateNode(path string, content []byte) string {
@ -134,7 +184,7 @@ func (z ZooNode) CreateNode(path string, content []byte) string {
return z.UpdateNode(path, content) return z.UpdateNode(path, content)
} }
// UpdateNode ... // UpdateNode update existing node
func (z ZooNode) UpdateNode(path string, content []byte) string { func (z ZooNode) UpdateNode(path string, content []byte) string {
upPath := strings.Join([]string{z.Path, path}, "") upPath := strings.Join([]string{z.Path, path}, "")
if strings.Contains(upPath, "//") { if strings.Contains(upPath, "//") {
@ -149,6 +199,36 @@ func (z ZooNode) UpdateNode(path string, content []byte) string {
return err.Error() return err.Error()
} }
if z.MC.Enabled {
if err := z.MC.StoreToCache(upPath, content); err != nil {
log.Print("[mc ERROR] ", err.Error())
}
}
return path
}
// CreateChild create child in /node/path
func (z ZooNode) CreateChild(path string, content []byte) string {
crPath := strings.Join([]string{z.Path, path}, "")
if strings.Contains(crPath, "//") {
crPath = strings.Replace(crPath, "//", "/", 1)
}
if crPath == "/" {
return "Not updating root path"
}
_, err := z.Conn.Create(crPath, content, 0, zk.WorldACL(zk.PermAll))
if err != nil {
return err.Error()
}
if z.MC.Enabled {
if err := z.MC.StoreToCache(crPath, content); err != nil {
log.Print("[mc ERROR] ", err.Error())
}
}
return path return path
} }