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

feat add dir_flag #376

Merged
merged 9 commits into from
Dec 9, 2013
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
12 changes: 7 additions & 5 deletions error/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const (
EcodeNodeExist = 105
EcodeKeyIsPreserved = 106
EcodeRootROnly = 107
EcodeDirNotEmpty = 108

EcodeValueRequired = 200
EcodePrevValueRequired = 201
Expand All @@ -51,14 +52,15 @@ func init() {
errors = make(map[int]string)

// command related errors
errors[EcodeKeyNotFound] = "Key Not Found"
errors[EcodeTestFailed] = "Test Failed" //test and set
errors[EcodeNotFile] = "Not A File"
errors[EcodeKeyNotFound] = "Key not found"
errors[EcodeTestFailed] = "Compare failed" //test and set
errors[EcodeNotFile] = "Not a file"
errors[EcodeNoMorePeer] = "Reached the max number of peers in the cluster"
errors[EcodeNotDir] = "Not A Directory"
errors[EcodeNodeExist] = "Already exists" // create
errors[EcodeNotDir] = "Not a directory"
errors[EcodeNodeExist] = "Key already exists" // create
errors[EcodeRootROnly] = "Root is read only"
errors[EcodeKeyIsPreserved] = "The prefix of given key is a keyword in etcd"
errors[EcodeDirNotEmpty] = "Directory not empty"

// Post form related errors
errors[EcodeValueRequired] = "Value is Required in POST form"
Expand Down
4 changes: 2 additions & 2 deletions server/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (r *Registry) Register(name string, peerURL string, url string) error {
// Write data to store.
key := path.Join(RegistryKey, name)
value := fmt.Sprintf("raft=%s&etcd=%s", peerURL, url)
_, err := r.store.Create(key, value, false, store.Permanent)
_, err := r.store.Create(key, false, value, false, store.Permanent)
log.Debugf("Register: %s", name)
return err
}
Expand All @@ -59,7 +59,7 @@ func (r *Registry) Unregister(name string) error {
// delete(r.nodes, name)

// Remove the key from the store.
_, err := r.store.Delete(path.Join(RegistryKey, name), false)
_, err := r.store.Delete(path.Join(RegistryKey, name), false, false)
log.Debugf("Unregister: %s", name)
return err
}
Expand Down
18 changes: 9 additions & 9 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ func New(name string, urlStr string, bindAddr string, tlsConf *TLSConfig, tlsInf
TLSConfig: &tlsConf.Server,
Addr: bindAddr,
},
name: name,
store: store,
registry: registry,
url: urlStr,
tlsConf: tlsConf,
tlsInfo: tlsInfo,
peerServer: peerServer,
router: r,
name: name,
store: store,
registry: registry,
url: urlStr,
tlsConf: tlsConf,
tlsInfo: tlsInfo,
peerServer: peerServer,
router: r,
corsHandler: cors,
}

Expand Down Expand Up @@ -377,7 +377,7 @@ func (s *Server) SpeedTestHandler(w http.ResponseWriter, req *http.Request) erro
for i := 0; i < count; i++ {
go func() {
for j := 0; j < 10; j++ {
c := s.Store().CommandFactory().CreateSetCommand("foo", "bar", time.Unix(0, 0))
c := s.Store().CommandFactory().CreateSetCommand("foo", false, "bar", time.Unix(0, 0))
s.peerServer.RaftServer().Do(c)
}
c <- true
Expand Down
2 changes: 1 addition & 1 deletion server/v1/delete_key_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ import (
func DeleteKeyHandler(w http.ResponseWriter, req *http.Request, s Server) error {
vars := mux.Vars(req)
key := "/" + vars["key"]
c := s.Store().CommandFactory().CreateDeleteCommand(key, false)
c := s.Store().CommandFactory().CreateDeleteCommand(key, false, false)
return s.Dispatch(c, w, req)
}
4 changes: 2 additions & 2 deletions server/v1/set_key_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ func SetKeyHandler(w http.ResponseWriter, req *http.Request, s Server) error {
c = s.Store().CommandFactory().CreateCompareAndSwapCommand(key, value, prevValueArr[0], 0, expireTime)
} else {
// test against existence
c = s.Store().CommandFactory().CreateCreateCommand(key, value, expireTime, false)
c = s.Store().CommandFactory().CreateCreateCommand(key, false, value, expireTime, false)
}

} else {
c = s.Store().CommandFactory().CreateSetCommand(key, value, expireTime)
c = s.Store().CommandFactory().CreateSetCommand(key, false, value, expireTime)
}

return s.Dispatch(c, w, req)
Expand Down
4 changes: 3 additions & 1 deletion server/v2/delete_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import (
func DeleteHandler(w http.ResponseWriter, req *http.Request, s Server) error {
vars := mux.Vars(req)
key := "/" + vars["key"]

recursive := (req.FormValue("recursive") == "true")
dir := (req.FormValue("dir") == "true")

c := s.Store().CommandFactory().CreateDeleteCommand(key, recursive)
c := s.Store().CommandFactory().CreateDeleteCommand(key, dir, recursive)
return s.Dispatch(c, w, req)
}
3 changes: 2 additions & 1 deletion server/v2/post_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ func PostHandler(w http.ResponseWriter, req *http.Request, s Server) error {
key := "/" + vars["key"]

value := req.FormValue("value")
dir := (req.FormValue("dir") == "true")
expireTime, err := store.TTL(req.FormValue("ttl"))
if err != nil {
return etcdErr.NewError(etcdErr.EcodeTTLNaN, "Create", s.Store().Index())
}

c := s.Store().CommandFactory().CreateCreateCommand(key, value, expireTime, true)
c := s.Store().CommandFactory().CreateCreateCommand(key, dir, value, expireTime, true)
return s.Dispatch(c, w, req)
}
20 changes: 11 additions & 9 deletions server/v2/put_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,33 @@ func PutHandler(w http.ResponseWriter, req *http.Request, s Server) error {
req.ParseForm()

value := req.Form.Get("value")
dir := (req.FormValue("dir") == "true")

expireTime, err := store.TTL(req.Form.Get("ttl"))
if err != nil {
return etcdErr.NewError(etcdErr.EcodeTTLNaN, "Update", s.Store().Index())
}

_, valueOk := req.Form["prevValue"]
prevValue := req.Form.Get("prevValue")
prevValue := req.FormValue("prevValue")

_, indexOk := req.Form["prevIndex"]
prevIndexStr := req.Form.Get("prevIndex")
prevIndexStr := req.FormValue("prevIndex")

_, existOk := req.Form["prevExist"]
prevExist := req.Form.Get("prevExist")
prevExist := req.FormValue("prevExist")

// Set handler: create a new node or replace the old one.
if !valueOk && !indexOk && !existOk {
return SetHandler(w, req, s, key, value, expireTime)
return SetHandler(w, req, s, key, dir, value, expireTime)
}

// update with test
if existOk {
if prevExist == "false" {
// Create command: create a new node. Fail, if a node already exists
// Ignore prevIndex and prevValue
return CreateHandler(w, req, s, key, value, expireTime)
return CreateHandler(w, req, s, key, dir, value, expireTime)
}

if prevExist == "true" && !indexOk && !valueOk {
Expand Down Expand Up @@ -75,13 +77,13 @@ func PutHandler(w http.ResponseWriter, req *http.Request, s Server) error {
return s.Dispatch(c, w, req)
}

func SetHandler(w http.ResponseWriter, req *http.Request, s Server, key, value string, expireTime time.Time) error {
c := s.Store().CommandFactory().CreateSetCommand(key, value, expireTime)
func SetHandler(w http.ResponseWriter, req *http.Request, s Server, key string, dir bool, value string, expireTime time.Time) error {
c := s.Store().CommandFactory().CreateSetCommand(key, dir, value, expireTime)
return s.Dispatch(c, w, req)
}

func CreateHandler(w http.ResponseWriter, req *http.Request, s Server, key, value string, expireTime time.Time) error {
c := s.Store().CommandFactory().CreateCreateCommand(key, value, expireTime, false)
func CreateHandler(w http.ResponseWriter, req *http.Request, s Server, key string, dir bool, value string, expireTime time.Time) error {
c := s.Store().CommandFactory().CreateCreateCommand(key, dir, value, expireTime, false)
return s.Dispatch(c, w, req)
}

Expand Down
56 changes: 56 additions & 0 deletions server/v2/tests/delete_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,59 @@ func TestV2DeleteKey(t *testing.T) {
assert.Equal(t, string(body), `{"action":"delete","node":{"key":"/foo/bar","prevValue":"XXX","modifiedIndex":3,"createdIndex":2}}`, "")
})
}

// Ensures that an empty directory is deleted when dir is set.
//
// $ curl -X PUT localhost:4001/v2/keys/foo?dir=true
// $ curl -X PUT localhost:4001/v2/keys/foo ->fail
// $ curl -X DELETE localhost:4001/v2/keys/foo?dir=true
//
func TestV2DeleteEmptyDirectory(t *testing.T) {
tests.RunServer(func(s *server.Server) {
resp, err := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo?dir=true"), url.Values{})
tests.ReadBody(resp)
resp, err = tests.DeleteForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo"), url.Values{})
bodyJson := tests.ReadBodyJSON(resp)
assert.Equal(t, bodyJson["errorCode"], 102, "")
resp, err = tests.DeleteForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo?dir=true"), url.Values{})
body := tests.ReadBody(resp)
assert.Nil(t, err, "")
assert.Equal(t, string(body), `{"action":"delete","node":{"key":"/foo","dir":true,"modifiedIndex":3,"createdIndex":2}}`, "")
})
}

// Ensures that a not-empty directory is deleted when dir is set.
//
// $ curl -X PUT localhost:4001/v2/keys/foo/bar?dir=true
// $ curl -X DELETE localhost:4001/v2/keys/foo?dir=true ->fail
// $ curl -X DELETE localhost:4001/v2/keys/foo?dir=true&recursive=true
//
func TestV2DeleteNonEmptyDirectory(t *testing.T) {
tests.RunServer(func(s *server.Server) {
resp, err := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar?dir=true"), url.Values{})
tests.ReadBody(resp)
resp, err = tests.DeleteForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo?dir=true"), url.Values{})
bodyJson := tests.ReadBodyJSON(resp)
assert.Equal(t, bodyJson["errorCode"], 108, "")
resp, err = tests.DeleteForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo?dir=true&recursive=true"), url.Values{})
body := tests.ReadBody(resp)
assert.Nil(t, err, "")
assert.Equal(t, string(body), `{"action":"delete","node":{"key":"/foo","dir":true,"modifiedIndex":3,"createdIndex":2}}`, "")
})
}

// Ensures that a directory is deleted when recursive is set.
//
// $ curl -X PUT localhost:4001/v2/keys/foo?dir=true
// $ curl -X DELETE localhost:4001/v2/keys/foo?recursive=true
//
func TestV2DeleteDirectoryRecursiveImpliesDir(t *testing.T) {
tests.RunServer(func(s *server.Server) {
resp, err := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo?dir=true"), url.Values{})
tests.ReadBody(resp)
resp, err = tests.DeleteForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo?recursive=true"), url.Values{})
body := tests.ReadBody(resp)
assert.Nil(t, err, "")
assert.Equal(t, string(body), `{"action":"delete","node":{"key":"/foo","dir":true,"modifiedIndex":3,"createdIndex":2}}`, "")
})
}
2 changes: 1 addition & 1 deletion server/v2/tests/post_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestV2CreateUnique(t *testing.T) {

node := body["node"].(map[string]interface{})
assert.Equal(t, node["key"], "/foo/bar/2", "")
assert.Equal(t, node["dir"], true, "")
assert.Nil(t, node["dir"], "")
assert.Equal(t, node["modifiedIndex"], 2, "")

// Second POST should add next index to list.
Expand Down
26 changes: 20 additions & 6 deletions server/v2/tests/put_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ func TestV2SetKey(t *testing.T) {
})
}

// Ensures that a directory is created
//
// $ curl -X PUT localhost:4001/v2/keys/foo/bar?dir=true
//
func TestV2SetDirectory(t *testing.T) {
tests.RunServer(func(s *server.Server) {
resp, err := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo?dir=true"), url.Values{})
body := tests.ReadBody(resp)
assert.Nil(t, err, "")
assert.Equal(t, string(body), `{"action":"set","node":{"key":"/foo","dir":true,"modifiedIndex":2,"createdIndex":2}}`, "")
})
}

// Ensures that a time-to-live is added to a key.
//
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX -d ttl=20
Expand Down Expand Up @@ -95,7 +108,7 @@ func TestV2CreateKeyFail(t *testing.T) {
resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["errorCode"], 105, "")
assert.Equal(t, body["message"], "Already exists", "")
assert.Equal(t, body["message"], "Key already exists", "")
assert.Equal(t, body["cause"], "/foo/bar", "")
})
}
Expand Down Expand Up @@ -126,19 +139,20 @@ func TestV2UpdateKeySuccess(t *testing.T) {

// Ensures that a key is not conditionally set if it previously did not exist.
//
// $ curl -X PUT localhost:4001/v2/keys/foo?dir=true
// $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX -d prevExist=true
//
func TestV2UpdateKeyFailOnValue(t *testing.T) {
tests.RunServer(func(s *server.Server) {
v := url.Values{}
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo"), v)
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo?dir=true"), v)

v.Set("value", "YYY")
v.Set("prevExist", "true")
resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["errorCode"], 100, "")
assert.Equal(t, body["message"], "Key Not Found", "")
assert.Equal(t, body["message"], "Key not found", "")
assert.Equal(t, body["cause"], "/foo/bar", "")
})
}
Expand All @@ -156,7 +170,7 @@ func TestV2UpdateKeyFailOnMissingDirectory(t *testing.T) {
resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["errorCode"], 100, "")
assert.Equal(t, body["message"], "Key Not Found", "")
assert.Equal(t, body["message"], "Key not found", "")
assert.Equal(t, body["cause"], "/foo", "")
})
}
Expand Down Expand Up @@ -200,7 +214,7 @@ func TestV2SetKeyCASOnIndexFail(t *testing.T) {
resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["errorCode"], 101, "")
assert.Equal(t, body["message"], "Test Failed", "")
assert.Equal(t, body["message"], "Compare failed", "")
assert.Equal(t, body["cause"], "[ != XXX] [10 != 2]", "")
assert.Equal(t, body["index"], 2, "")
})
Expand Down Expand Up @@ -262,7 +276,7 @@ func TestV2SetKeyCASOnValueFail(t *testing.T) {
resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
body := tests.ReadBodyJSON(resp)
assert.Equal(t, body["errorCode"], 101, "")
assert.Equal(t, body["message"], "Test Failed", "")
assert.Equal(t, body["message"], "Compare failed", "")
assert.Equal(t, body["cause"], "[AAA != XXX] [0 != 2]", "")
assert.Equal(t, body["index"], 2, "")
})
Expand Down
9 changes: 5 additions & 4 deletions store/command_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ var minVersion, maxVersion int
type CommandFactory interface {
Version() int
CreateUpgradeCommand() raft.Command
CreateSetCommand(key string, value string, expireTime time.Time) raft.Command
CreateCreateCommand(key string, value string, expireTime time.Time, unique bool) raft.Command
CreateSetCommand(key string, dir bool, value string, expireTime time.Time) raft.Command
CreateCreateCommand(key string, dir bool, value string, expireTime time.Time, unique bool) raft.Command
CreateUpdateCommand(key string, value string, expireTime time.Time) raft.Command
CreateDeleteCommand(key string, recursive bool) raft.Command
CreateCompareAndSwapCommand(key string, value string, prevValue string, prevIndex uint64, expireTime time.Time) raft.Command
CreateDeleteCommand(key string, dir, recursive bool) raft.Command
CreateCompareAndSwapCommand(key string, value string, prevValue string,
prevIndex uint64, expireTime time.Time) raft.Command
CreateSyncCommand(now time.Time) raft.Command
}

Expand Down
18 changes: 13 additions & 5 deletions store/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,19 @@ func (n *node) Add(child *node) *etcdErr.Error {
}

// Remove function remove the node.
func (n *node) Remove(recursive bool, callback func(path string)) *etcdErr.Error {
func (n *node) Remove(dir, recursive bool, callback func(path string)) *etcdErr.Error {

if n.IsDir() && !recursive {
// cannot delete a directory without set recursive to true
return etcdErr.NewError(etcdErr.EcodeNotFile, "", n.store.Index())
if n.IsDir() {
if !dir {
// cannot delete a directory without recursive set to true
return etcdErr.NewError(etcdErr.EcodeNotFile, n.Path, n.store.Index())
}

if len(n.Children) != 0 && !recursive {
// cannot delete a directory if it is not empty and the operation
// is not recursive
return etcdErr.NewError(etcdErr.EcodeDirNotEmpty, n.Path, n.store.Index())
}
}

if !n.IsDir() { // key-value pair
Expand All @@ -202,7 +210,7 @@ func (n *node) Remove(recursive bool, callback func(path string)) *etcdErr.Error
}

for _, child := range n.Children { // delete all children
child.Remove(true, callback)
child.Remove(true, true, callback)
}

// delete self
Expand Down
Loading