From 228754a99ca5641bf2e43158c4b93f64b0b91d2b Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Tue, 19 Nov 2013 16:12:58 -0700 Subject: [PATCH 1/8] mod_lock --- mod/lock/handler.go | 44 ++++++++++++++++++++++++++++++++++++++++ mod/lock/handler_test.go | 35 ++++++++++++++++++++++++++++++++ mod/mod.go | 9 +++++--- 3 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 mod/lock/handler.go create mode 100644 mod/lock/handler_test.go diff --git a/mod/lock/handler.go b/mod/lock/handler.go new file mode 100644 index 00000000000..ccb2a3c5d2b --- /dev/null +++ b/mod/lock/handler.go @@ -0,0 +1,44 @@ +package lock + +import ( + "bytes" + "net/http" + "os" + "path" + "time" + + "github.com/coreos/go-etcd/etcd" +) + +// handler manages the lock HTTP request. +type handler struct { + *mux.Router + client string +} + +// NewHandler creates an HTTP handler that can be registered on a router. +func NewHandler(addr string) (http.Handler) { + h := &handler{ + Router: mux.NewRouter(), + client: etcd.NewClient([]string{addr}), + } + h.HandleFunc("/{key:.+}", h.getLockHandler).Methods("GET") + h.HandleFunc("/{key:.+}", h.acquireLockHandler).Methods("PUT") + h.HandleFunc("/{key:.+}", h.releaseLockHandler).Methods("DELETE") +} + +// getLockHandler retrieves whether a lock has been obtained for a given key. +func (h *handler) getLockHandler(w http.ResponseWriter, req *http.Request) { + // TODO +} + +// acquireLockHandler attempts to acquire a lock on the given key. +// The lock is released when the connection is disconnected. +func (h *handler) acquireLockHandler(w http.ResponseWriter, req *http.Request) { + // TODO +} + +// releaseLockHandler forces the release of a lock on the given key. +func (h *handler) releaseLockHandler(w http.ResponseWriter, req *http.Request) { + // TODO +} diff --git a/mod/lock/handler_test.go b/mod/lock/handler_test.go new file mode 100644 index 00000000000..91ecc310d1c --- /dev/null +++ b/mod/lock/handler_test.go @@ -0,0 +1,35 @@ +package lock + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// Ensure that a lock can be acquired and released. +func TestModLockAcquire(t *testing.T) { + // TODO: Acquire lock. + // TODO: Check that it has been acquired. + // TODO: Release lock. + // TODO: Check that it has been released. +} + +// Ensure that a lock can be acquired and another process is blocked until released. +func TestModLockAcquireBlocked(t *testing.T) { + // TODO: Acquire lock with process #1. + // TODO: Acquire lock with process #2. + // TODO: Check that process #2 has not obtained lock. + // TODO: Release lock from process #1. + // TODO: Check that process #2 obtains the lock. + // TODO: Release lock from process #2. + // TODO: Check that no lock exists. +} + +// Ensure that an unowned lock can be released by force. +func TestModLockForceRelease(t *testing.T) { + // TODO: Acquire lock. + // TODO: Check that it has been acquired. + // TODO: Force release lock. + // TODO: Check that it has been released. + // TODO: Check that acquiring goroutine is notified that their lock has been released. +} diff --git a/mod/mod.go b/mod/mod.go index 741b19002a0..7964c683f88 100644 --- a/mod/mod.go +++ b/mod/mod.go @@ -5,14 +5,17 @@ import ( "net/http" "github.com/coreos/etcd/mod/dashboard" + "github.com/coreos/etcd/mod/lock" "github.com/gorilla/mux" ) var ServeMux *http.Handler func HttpHandler() (handler http.Handler) { - modMux := mux.NewRouter() - modMux.PathPrefix("/dashboard/"). + r := mux.NewRouter() + r.PathPrefix("/dashboard/"). Handler(http.StripPrefix("/dashboard/", dashboard.HttpHandler())) - return modMux + r.PathPrefix("/lock/"). + Handler(http.StripPrefix("/lock", lock.NewHandler())) + return r } From 32861246b974f2c1678c1f10b2b10f7f580a5df7 Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Wed, 27 Nov 2013 14:36:14 -0700 Subject: [PATCH 2/8] mod/lock --- mod/lock/acquire_handler.go | 49 ++++++++++++++++++++++++++++ mod/lock/handler.go | 47 ++++++++++++++++---------- mod/lock/release_handler.go | 11 +++++++ mod/lock/renew_handler.go | 16 +++++++++ mod/lock/{ => tests}/handler_test.go | 23 ++++++++++--- mod/mod.go | 8 ++--- 6 files changed, 128 insertions(+), 26 deletions(-) create mode 100644 mod/lock/acquire_handler.go create mode 100644 mod/lock/release_handler.go create mode 100644 mod/lock/renew_handler.go rename mod/lock/{ => tests}/handler_test.go (62%) diff --git a/mod/lock/acquire_handler.go b/mod/lock/acquire_handler.go new file mode 100644 index 00000000000..d142a3f2e6f --- /dev/null +++ b/mod/lock/acquire_handler.go @@ -0,0 +1,49 @@ +package lock + +import ( + "net/http" + "path" + "strconv" + + "github.com/gorilla/mux" +) + +// acquireHandler attempts to acquire a lock on the given key. +func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + keypath := path.Join(prefix, vars["key"]) + ttl, err := strconv.Atoi(vars["ttl"]) + if err != nil { + http.Error(w, "invalid ttl: " + err.Error(), http.StatusInternalServerError) + return + } + + // Create an incrementing id for the lock. + resp, err := h.client.AddChild(keypath, "X", ttl) + if err != nil { + http.Error(w, "add lock index error: " + err.Error(), http.StatusInternalServerError) + return + } + + // Extract the lock index. + index, _ := strconv.Atoi(path.Base(resp.Key)) + + // Read all indices. + resp, err = h.client.GetAll(key) + if err != nil { + http.Error(w, "lock children lookup error: " + err.Error(), http.StatusInternalServerError) + return + } + indices := extractResponseIndices(resp) + + // TODO: child_keys := parse_and_sort_child_keys + // TODO: if index == min(child_keys) then return 200 + // TODO: else: + // TODO: h.client.WatchAll(key) + // TODO: if next_lowest_key is deleted + // TODO: get_all_keys + // TODO: if index == min(child_keys) then return 200 + // TODO: rinse_and_repeat until we're the lowest. + + // TODO: +} diff --git a/mod/lock/handler.go b/mod/lock/handler.go index ccb2a3c5d2b..66a62be4f0a 100644 --- a/mod/lock/handler.go +++ b/mod/lock/handler.go @@ -1,19 +1,20 @@ package lock import ( - "bytes" + "fmt" "net/http" - "os" "path" - "time" + "github.com/gorilla/mux" "github.com/coreos/go-etcd/etcd" ) +const prefix = "/_etcd/locks" + // handler manages the lock HTTP request. type handler struct { *mux.Router - client string + client *etcd.Client } // NewHandler creates an HTTP handler that can be registered on a router. @@ -22,23 +23,33 @@ func NewHandler(addr string) (http.Handler) { Router: mux.NewRouter(), client: etcd.NewClient([]string{addr}), } - h.HandleFunc("/{key:.+}", h.getLockHandler).Methods("GET") - h.HandleFunc("/{key:.+}", h.acquireLockHandler).Methods("PUT") - h.HandleFunc("/{key:.+}", h.releaseLockHandler).Methods("DELETE") + h.StrictSlash(false) + h.HandleFunc("/{key:.*}", h.acquireHandler).Methods("POST") + h.HandleFunc("/{key_with_index:.*}", h.renewLockHandler).Methods("PUT") + h.HandleFunc("/{key_with_index:.*}", h.releaseLockHandler).Methods("DELETE") + return h } -// getLockHandler retrieves whether a lock has been obtained for a given key. -func (h *handler) getLockHandler(w http.ResponseWriter, req *http.Request) { - // TODO -} -// acquireLockHandler attempts to acquire a lock on the given key. -// The lock is released when the connection is disconnected. -func (h *handler) acquireLockHandler(w http.ResponseWriter, req *http.Request) { - // TODO +// extractResponseIndices extracts a sorted list of indicies from a response. +func extractResponseIndices(resp *etcd.Response) []int { + var indices []int + for _, kv := range resp.Kvs { + if index, _ := strconv.Atoi(path.Base(kv.Key)); index > 0 { + indicies = append(indices, index) + } + } + return indices } -// releaseLockHandler forces the release of a lock on the given key. -func (h *handler) releaseLockHandler(w http.ResponseWriter, req *http.Request) { - // TODO +// findPrevIndex retrieves the previous index before the given index. +func findPrevIndex(indices []int, idx int) int { + var prevIndex int + for _, index := range indices { + if index == idx { + break + } + prevIndex = index + } + return prevIndex } diff --git a/mod/lock/release_handler.go b/mod/lock/release_handler.go new file mode 100644 index 00000000000..09b875183c2 --- /dev/null +++ b/mod/lock/release_handler.go @@ -0,0 +1,11 @@ +package lock + +import ( + "net/http" +) + +// releaseLockHandler deletes the lock. +func (h *handler) releaseLockHandler(w http.ResponseWriter, req *http.Request) { + // TODO: h.client.Delete(key_with_index) +} + diff --git a/mod/lock/renew_handler.go b/mod/lock/renew_handler.go new file mode 100644 index 00000000000..da9c0b8c255 --- /dev/null +++ b/mod/lock/renew_handler.go @@ -0,0 +1,16 @@ +package lock + +import ( + "net/http" +) + +// renewLockHandler attempts to update the TTL on an existing lock. +// Returns a 200 OK if successful. Otherwie +func (h *handler) renewLockHandler(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + key := path.Join(prefix, vars["key"]) + ttl := vars["ttl"] + w.Write([]byte(fmt.Sprintf("%s-%s", key, ttl))) + + // TODO: +} diff --git a/mod/lock/handler_test.go b/mod/lock/tests/handler_test.go similarity index 62% rename from mod/lock/handler_test.go rename to mod/lock/tests/handler_test.go index 91ecc310d1c..fbc36ea0082 100644 --- a/mod/lock/handler_test.go +++ b/mod/lock/tests/handler_test.go @@ -1,17 +1,32 @@ package lock import ( + "fmt" + "net/url" "testing" + "time" + "github.com/coreos/etcd/server" + "github.com/coreos/etcd/tests" "github.com/stretchr/testify/assert" ) // Ensure that a lock can be acquired and released. func TestModLockAcquire(t *testing.T) { - // TODO: Acquire lock. - // TODO: Check that it has been acquired. - // TODO: Release lock. - // TODO: Check that it has been released. + v := url.Values{} + tests.RunServer(func(s *server.Server) { + // Acquire lock. + resp, err := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/mod/lock"), v) + assert.NoError(t, err) + ret := tests.ReadBody(resp) + assert.Equal(t, string(ret), "XXX") + + fmt.Println("URL:", fmt.Sprintf("http://%s%s", s.URL(), "/mod/lock")) + time.Sleep(60 * time.Second) + // TODO: Check that it has been acquired. + // TODO: Release lock. + // TODO: Check that it has been released. + }) } // Ensure that a lock can be acquired and another process is blocked until released. diff --git a/mod/mod.go b/mod/mod.go index 7964c683f88..9d3b54d6903 100644 --- a/mod/mod.go +++ b/mod/mod.go @@ -13,9 +13,9 @@ var ServeMux *http.Handler func HttpHandler() (handler http.Handler) { r := mux.NewRouter() - r.PathPrefix("/dashboard/"). - Handler(http.StripPrefix("/dashboard/", dashboard.HttpHandler())) - r.PathPrefix("/lock/"). - Handler(http.StripPrefix("/lock", lock.NewHandler())) + r.PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", dashboard.HttpHandler())) + + // TODO: Use correct addr. + r.PathPrefix("/lock").Handler(http.StripPrefix("/lock", lock.NewHandler("127.0.0.1:4001"))) return r } From 22c2935ddb3ab7ce5e642a43abb114f3778cc866 Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Wed, 27 Nov 2013 16:59:05 -0700 Subject: [PATCH 3/8] Initial mod_lock acquire. --- mod/lock/acquire_handler.go | 64 +++++++++++++------ mod/lock/handler.go | 7 +- mod/lock/renew_handler.go | 8 ++- mod/lock/tests/handler_test.go | 4 +- mod/mod.go | 5 +- server/registry.go | 1 + server/server.go | 4 +- test.sh | 16 ++--- tests/server_utils.go | 5 +- .../coreos/go-etcd/etcd/requests.go | 2 +- 10 files changed, 75 insertions(+), 41 deletions(-) diff --git a/mod/lock/acquire_handler.go b/mod/lock/acquire_handler.go index d142a3f2e6f..3e7f2e973f8 100644 --- a/mod/lock/acquire_handler.go +++ b/mod/lock/acquire_handler.go @@ -4,46 +4,72 @@ import ( "net/http" "path" "strconv" + "time" "github.com/gorilla/mux" ) // acquireHandler attempts to acquire a lock on the given key. func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) { + h.client.SyncCluster() + vars := mux.Vars(req) keypath := path.Join(prefix, vars["key"]) - ttl, err := strconv.Atoi(vars["ttl"]) + ttl, err := strconv.Atoi(req.FormValue("ttl")) if err != nil { http.Error(w, "invalid ttl: " + err.Error(), http.StatusInternalServerError) return } // Create an incrementing id for the lock. - resp, err := h.client.AddChild(keypath, "X", ttl) + resp, err := h.client.AddChild(keypath, "-", uint64(ttl)) if err != nil { http.Error(w, "add lock index error: " + err.Error(), http.StatusInternalServerError) return } + // Keep updating TTL to make sure lock request is not expired before acquisition. + stopChan := make(chan bool) + defer close(stopChan) + go func(k string) { + stopped := false + for { + select { + case <-time.After(time.Duration(ttl / 2) * time.Second): + case <-stopChan: + stopped = true + } + h.client.Update(k, "-", uint64(ttl)) + if stopped { + break + } + } + }(resp.Key) + // Extract the lock index. index, _ := strconv.Atoi(path.Base(resp.Key)) - // Read all indices. - resp, err = h.client.GetAll(key) - if err != nil { - http.Error(w, "lock children lookup error: " + err.Error(), http.StatusInternalServerError) - return + for { + // Read all indices. + resp, err = h.client.GetAll(keypath, true) + if err != nil { + http.Error(w, "lock children lookup error: " + err.Error(), http.StatusInternalServerError) + return + } + indices := extractResponseIndices(resp) + waitIndex := resp.ModifiedIndex + prevIndex := findPrevIndex(indices, index) + + // If there is no previous index then we have the lock. + if prevIndex == 0 { + break + } + + // Otherwise watch previous index until it's gone. + _, err = h.client.Watch(path.Join(keypath, strconv.Itoa(prevIndex)), waitIndex, nil, nil) + if err != nil { + http.Error(w, "lock watch error: " + err.Error(), http.StatusInternalServerError) + return + } } - indices := extractResponseIndices(resp) - - // TODO: child_keys := parse_and_sort_child_keys - // TODO: if index == min(child_keys) then return 200 - // TODO: else: - // TODO: h.client.WatchAll(key) - // TODO: if next_lowest_key is deleted - // TODO: get_all_keys - // TODO: if index == min(child_keys) then return 200 - // TODO: rinse_and_repeat until we're the lowest. - - // TODO: } diff --git a/mod/lock/handler.go b/mod/lock/handler.go index 66a62be4f0a..355a6339fef 100644 --- a/mod/lock/handler.go +++ b/mod/lock/handler.go @@ -1,9 +1,10 @@ package lock import ( - "fmt" "net/http" "path" + "strconv" + "sort" "github.com/gorilla/mux" "github.com/coreos/go-etcd/etcd" @@ -19,6 +20,7 @@ type handler struct { // NewHandler creates an HTTP handler that can be registered on a router. func NewHandler(addr string) (http.Handler) { + etcd.OpenDebug() h := &handler{ Router: mux.NewRouter(), client: etcd.NewClient([]string{addr}), @@ -36,9 +38,10 @@ func extractResponseIndices(resp *etcd.Response) []int { var indices []int for _, kv := range resp.Kvs { if index, _ := strconv.Atoi(path.Base(kv.Key)); index > 0 { - indicies = append(indices, index) + indices = append(indices, index) } } + sort.Ints(indices) return indices } diff --git a/mod/lock/renew_handler.go b/mod/lock/renew_handler.go index da9c0b8c255..ba9fe31d219 100644 --- a/mod/lock/renew_handler.go +++ b/mod/lock/renew_handler.go @@ -2,15 +2,17 @@ package lock import ( "net/http" + _ "path" + + _ "github.com/gorilla/mux" ) // renewLockHandler attempts to update the TTL on an existing lock. // Returns a 200 OK if successful. Otherwie func (h *handler) renewLockHandler(w http.ResponseWriter, req *http.Request) { + /* vars := mux.Vars(req) key := path.Join(prefix, vars["key"]) ttl := vars["ttl"] - w.Write([]byte(fmt.Sprintf("%s-%s", key, ttl))) - - // TODO: + */ } diff --git a/mod/lock/tests/handler_test.go b/mod/lock/tests/handler_test.go index fbc36ea0082..e3caafe254c 100644 --- a/mod/lock/tests/handler_test.go +++ b/mod/lock/tests/handler_test.go @@ -16,12 +16,12 @@ func TestModLockAcquire(t *testing.T) { v := url.Values{} tests.RunServer(func(s *server.Server) { // Acquire lock. - resp, err := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/mod/lock"), v) + url := fmt.Sprintf("http://%s%s", s.URL(), "/mod/lock/foo?ttl=2") + resp, err := tests.PutForm(url, v) assert.NoError(t, err) ret := tests.ReadBody(resp) assert.Equal(t, string(ret), "XXX") - fmt.Println("URL:", fmt.Sprintf("http://%s%s", s.URL(), "/mod/lock")) time.Sleep(60 * time.Second) // TODO: Check that it has been acquired. // TODO: Release lock. diff --git a/mod/mod.go b/mod/mod.go index d9b0ee01b8e..7c0194f56ee 100644 --- a/mod/mod.go +++ b/mod/mod.go @@ -17,13 +17,12 @@ func addSlash(w http.ResponseWriter, req *http.Request) { return } -func HttpHandler() (handler http.Handler) { +func HttpHandler(addr string) http.Handler { r := mux.NewRouter() r.HandleFunc("/dashboard", addSlash) r.PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", dashboard.HttpHandler())) // TODO: Use correct addr. - r.HandleFunc("/lock", addSlash) - r.PathPrefix("/lock").Handler(http.StripPrefix("/lock", lock.NewHandler("127.0.0.1:4001"))) + r.PathPrefix("/lock").Handler(http.StripPrefix("/lock", lock.NewHandler(addr))) return r } diff --git a/server/registry.go b/server/registry.go index d1d98d9edd0..27b0ce414bf 100644 --- a/server/registry.go +++ b/server/registry.go @@ -46,6 +46,7 @@ func (r *Registry) Register(name string, peerURL string, url string) error { key := path.Join(RegistryKey, name) value := fmt.Sprintf("raft=%s&etcd=%s", peerURL, url) _, err := r.store.Create(key, value, false, store.Permanent) + fmt.Println("register.1:", key, value, err) log.Debugf("Register: %s", name) return err } diff --git a/server/server.go b/server/server.go index 4f75df2e061..f0de64a6737 100644 --- a/server/server.go +++ b/server/server.go @@ -130,7 +130,7 @@ func (s *Server) installV2() { func (s *Server) installMod() { r := s.router - r.PathPrefix("/mod").Handler(http.StripPrefix("/mod", mod.HttpHandler())) + r.PathPrefix("/mod").Handler(http.StripPrefix("/mod", mod.HttpHandler(s.url))) } // Adds a v1 server handler to the router. @@ -320,12 +320,14 @@ func (s *Server) GetVersionHandler(w http.ResponseWriter, req *http.Request) err // Handler to return the current leader's raft address func (s *Server) GetLeaderHandler(w http.ResponseWriter, req *http.Request) error { leader := s.peerServer.RaftServer().Leader() + fmt.Println("/leader.1?", leader) if leader == "" { return etcdErr.NewError(etcdErr.EcodeLeaderElect, "", s.Store().Index()) } w.WriteHeader(http.StatusOK) url, _ := s.registry.PeerURL(leader) w.Write([]byte(url)) + fmt.Println("/leader.2?", leader, url) return nil } diff --git a/test.sh b/test.sh index 5cc633975f4..690f3a93294 100755 --- a/test.sh +++ b/test.sh @@ -1,6 +1,9 @@ #!/bin/sh set -e +PKGS="./mod/lock/tests" +# PKGS="./store ./server ./server/v2/tests" + # Get GOPATH, etc from build . ./build @@ -8,14 +11,11 @@ set -e export GOPATH="${PWD}" # Unit tests -go test -i ./server -go test -v ./server - -go test -i ./server/v2/tests -go test -v ./server/v2/tests - -go test -i ./store -go test -v ./store +for PKG in $PKGS +do + go test -i $PKG + go test -v $PKG +done # Functional tests go test -i ./tests/functional diff --git a/tests/server_utils.go b/tests/server_utils.go index e3e7d532368..b02eb637168 100644 --- a/tests/server_utils.go +++ b/tests/server_utils.go @@ -23,8 +23,9 @@ func RunServer(f func(*server.Server)) { store := store.New() registry := server.NewRegistry(store) - ps := server.NewPeerServer(testName, path, testRaftURL, testRaftURL, &server.TLSConfig{Scheme: "http"}, &server.TLSInfo{}, registry, store, testSnapshotCount) - s := server.New(testName, testClientURL, testClientURL, &server.TLSConfig{Scheme: "http"}, &server.TLSInfo{}, ps, registry, store) + ps := server.NewPeerServer(testName, path, "http://" + testRaftURL, testRaftURL, &server.TLSConfig{Scheme: "http"}, &server.TLSInfo{}, registry, store, testSnapshotCount) + ps.MaxClusterSize = 9 + s := server.New(testName, "http://" + testClientURL, testClientURL, &server.TLSConfig{Scheme: "http"}, &server.TLSInfo{}, ps, registry, store) ps.SetServer(s) // Start up peer server. diff --git a/third_party/github.com/coreos/go-etcd/etcd/requests.go b/third_party/github.com/coreos/go-etcd/etcd/requests.go index 83e3b519ef7..4db818f97d4 100644 --- a/third_party/github.com/coreos/go-etcd/etcd/requests.go +++ b/third_party/github.com/coreos/go-etcd/etcd/requests.go @@ -207,7 +207,7 @@ func (c *Client) sendRequest(method string, _path string, values url.Values) (*R if err != nil { retry++ if retry > 2*len(c.cluster.Machines) { - return nil, errors.New("Cannot reach servers") + return nil, errors.New("Cannot reach servers" + err.Error()) } num := retry % len(c.cluster.Machines) logger.Debug("update.leader[", c.cluster.Leader, ",", c.cluster.Machines[num], "]") From d8e9838c382cf826b24471077b12a15934bdf8b0 Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Fri, 29 Nov 2013 16:33:49 -0700 Subject: [PATCH 4/8] Lock testing. --- mod/lock/acquire_handler.go | 6 +- mod/lock/get_index_handler.go | 30 +++++ mod/lock/handler.go | 4 +- mod/lock/release_handler.go | 15 ++- mod/lock/renew_handler.go | 26 +++-- mod/lock/tests/handler_test.go | 194 ++++++++++++++++++++++++++++----- 6 files changed, 236 insertions(+), 39 deletions(-) create mode 100644 mod/lock/get_index_handler.go diff --git a/mod/lock/acquire_handler.go b/mod/lock/acquire_handler.go index 3e7f2e973f8..8ad9e528a4d 100644 --- a/mod/lock/acquire_handler.go +++ b/mod/lock/acquire_handler.go @@ -27,6 +27,7 @@ func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) { http.Error(w, "add lock index error: " + err.Error(), http.StatusInternalServerError) return } + indexpath := resp.Key // Keep updating TTL to make sure lock request is not expired before acquisition. stopChan := make(chan bool) @@ -44,7 +45,7 @@ func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) { break } } - }(resp.Key) + }(indexpath) // Extract the lock index. index, _ := strconv.Atoi(path.Base(resp.Key)) @@ -72,4 +73,7 @@ func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) { return } } + + // Write lock index to response body. + w.Write([]byte(strconv.Itoa(index))) } diff --git a/mod/lock/get_index_handler.go b/mod/lock/get_index_handler.go new file mode 100644 index 00000000000..2bb97a83cd3 --- /dev/null +++ b/mod/lock/get_index_handler.go @@ -0,0 +1,30 @@ +package lock + +import ( + "net/http" + "path" + "strconv" + + "github.com/gorilla/mux" +) + +// getIndexHandler retrieves the current lock index. +func (h *handler) getIndexHandler(w http.ResponseWriter, req *http.Request) { + h.client.SyncCluster() + + vars := mux.Vars(req) + keypath := path.Join(prefix, vars["key"]) + + // Read all indices. + resp, err := h.client.GetAll(keypath, true) + if err != nil { + http.Error(w, "lock children lookup error: " + err.Error(), http.StatusInternalServerError) + return + } + + // Write out the index of the last one to the response body. + indices := extractResponseIndices(resp) + if len(indices) > 0 { + w.Write([]byte(strconv.Itoa(indices[0]))) + } +} diff --git a/mod/lock/handler.go b/mod/lock/handler.go index 355a6339fef..43e1491450f 100644 --- a/mod/lock/handler.go +++ b/mod/lock/handler.go @@ -10,7 +10,7 @@ import ( "github.com/coreos/go-etcd/etcd" ) -const prefix = "/_etcd/locks" +const prefix = "/_etcd/mod/lock" // handler manages the lock HTTP request. type handler struct { @@ -20,12 +20,12 @@ type handler struct { // NewHandler creates an HTTP handler that can be registered on a router. func NewHandler(addr string) (http.Handler) { - etcd.OpenDebug() h := &handler{ Router: mux.NewRouter(), client: etcd.NewClient([]string{addr}), } h.StrictSlash(false) + h.HandleFunc("/{key:.*}", h.getIndexHandler).Methods("GET") h.HandleFunc("/{key:.*}", h.acquireHandler).Methods("POST") h.HandleFunc("/{key_with_index:.*}", h.renewLockHandler).Methods("PUT") h.HandleFunc("/{key_with_index:.*}", h.releaseLockHandler).Methods("DELETE") diff --git a/mod/lock/release_handler.go b/mod/lock/release_handler.go index 09b875183c2..09251f25928 100644 --- a/mod/lock/release_handler.go +++ b/mod/lock/release_handler.go @@ -1,11 +1,24 @@ package lock import ( + "path" "net/http" + + "github.com/gorilla/mux" ) // releaseLockHandler deletes the lock. func (h *handler) releaseLockHandler(w http.ResponseWriter, req *http.Request) { - // TODO: h.client.Delete(key_with_index) + h.client.SyncCluster() + + vars := mux.Vars(req) + keypath := path.Join(prefix, vars["key_with_index"]) + + // Delete the lock. + _, err := h.client.Delete(keypath) + if err != nil { + http.Error(w, "delete lock index error: " + err.Error(), http.StatusInternalServerError) + return + } } diff --git a/mod/lock/renew_handler.go b/mod/lock/renew_handler.go index ba9fe31d219..7933931e4a7 100644 --- a/mod/lock/renew_handler.go +++ b/mod/lock/renew_handler.go @@ -1,18 +1,30 @@ package lock import ( + "path" "net/http" - _ "path" + "strconv" - _ "github.com/gorilla/mux" + "github.com/gorilla/mux" ) // renewLockHandler attempts to update the TTL on an existing lock. -// Returns a 200 OK if successful. Otherwie +// Returns a 200 OK if successful. Returns non-200 on error. func (h *handler) renewLockHandler(w http.ResponseWriter, req *http.Request) { - /* + h.client.SyncCluster() + vars := mux.Vars(req) - key := path.Join(prefix, vars["key"]) - ttl := vars["ttl"] - */ + keypath := path.Join(prefix, vars["key_with_index"]) + ttl, err := strconv.Atoi(req.FormValue("ttl")) + if err != nil { + http.Error(w, "invalid ttl: " + err.Error(), http.StatusInternalServerError) + return + } + + // Renew the lock, if it exists. + _, err = h.client.Update(keypath, "-", uint64(ttl)) + if err != nil { + http.Error(w, "renew lock index error: " + err.Error(), http.StatusInternalServerError) + return + } } diff --git a/mod/lock/tests/handler_test.go b/mod/lock/tests/handler_test.go index e3caafe254c..7e9091a0fa4 100644 --- a/mod/lock/tests/handler_test.go +++ b/mod/lock/tests/handler_test.go @@ -2,7 +2,6 @@ package lock import ( "fmt" - "net/url" "testing" "time" @@ -12,39 +11,178 @@ import ( ) // Ensure that a lock can be acquired and released. -func TestModLockAcquire(t *testing.T) { - v := url.Values{} +func TestModLockAcquireAndRelease(t *testing.T) { tests.RunServer(func(s *server.Server) { // Acquire lock. - url := fmt.Sprintf("http://%s%s", s.URL(), "/mod/lock/foo?ttl=2") - resp, err := tests.PutForm(url, v) + body, err := testAcquireLock(s, "foo", 10) assert.NoError(t, err) - ret := tests.ReadBody(resp) - assert.Equal(t, string(ret), "XXX") + assert.Equal(t, body, "2") - time.Sleep(60 * time.Second) - // TODO: Check that it has been acquired. - // TODO: Release lock. - // TODO: Check that it has been released. + // Check that we have the lock. + body, err = testGetLockIndex(s, "foo") + assert.NoError(t, err) + assert.Equal(t, body, "2") + + // Release lock. + body, err = testReleaseLock(s, "foo", 2) + assert.NoError(t, err) + assert.Equal(t, body, "") + + // Check that we have the lock. + body, err = testGetLockIndex(s, "foo") + assert.NoError(t, err) + assert.Equal(t, body, "") }) } // Ensure that a lock can be acquired and another process is blocked until released. -func TestModLockAcquireBlocked(t *testing.T) { - // TODO: Acquire lock with process #1. - // TODO: Acquire lock with process #2. - // TODO: Check that process #2 has not obtained lock. - // TODO: Release lock from process #1. - // TODO: Check that process #2 obtains the lock. - // TODO: Release lock from process #2. - // TODO: Check that no lock exists. -} - -// Ensure that an unowned lock can be released by force. -func TestModLockForceRelease(t *testing.T) { - // TODO: Acquire lock. - // TODO: Check that it has been acquired. - // TODO: Force release lock. - // TODO: Check that it has been released. - // TODO: Check that acquiring goroutine is notified that their lock has been released. +func TestModLockBlockUntilAcquire(t *testing.T) { + tests.RunServer(func(s *server.Server) { + c := make(chan bool) + + // Acquire lock #1. + go func() { + body, err := testAcquireLock(s, "foo", 10) + assert.NoError(t, err) + assert.Equal(t, body, "2") + c <- true + }() + <- c + + // Acquire lock #2. + go func() { + c <- true + body, err := testAcquireLock(s, "foo", 10) + assert.NoError(t, err) + assert.Equal(t, body, "4") + }() + <- c + + time.Sleep(1 * time.Second) + + // Check that we have the lock #1. + body, err := testGetLockIndex(s, "foo") + assert.NoError(t, err) + assert.Equal(t, body, "2") + + // Release lock #1. + body, err = testReleaseLock(s, "foo", 2) + assert.NoError(t, err) + + // Check that we have lock #2. + body, err = testGetLockIndex(s, "foo") + assert.NoError(t, err) + assert.Equal(t, body, "4") + + // Release lock #2. + body, err = testReleaseLock(s, "foo", 4) + assert.NoError(t, err) + + // Check that we have no lock. + body, err = testGetLockIndex(s, "foo") + assert.NoError(t, err) + assert.Equal(t, body, "") + }) +} + +// Ensure that a lock will be released after the TTL. +func TestModLockExpireAndRelease(t *testing.T) { + tests.RunServer(func(s *server.Server) { + c := make(chan bool) + + // Acquire lock #1. + go func() { + body, err := testAcquireLock(s, "foo", 2) + assert.NoError(t, err) + assert.Equal(t, body, "2") + c <- true + }() + <- c + + // Acquire lock #2. + go func() { + c <- true + body, err := testAcquireLock(s, "foo", 10) + assert.NoError(t, err) + assert.Equal(t, body, "4") + }() + <- c + + time.Sleep(1 * time.Second) + + // Check that we have the lock #1. + body, err := testGetLockIndex(s, "foo") + assert.NoError(t, err) + assert.Equal(t, body, "2") + + // Wait for lock #1 TTL. + time.Sleep(2 * time.Second) + + // Check that we have lock #2. + body, err = testGetLockIndex(s, "foo") + assert.NoError(t, err) + assert.Equal(t, body, "4") + }) +} + +// Ensure that a lock can be renewed. +func TestModLockRenew(t *testing.T) { + tests.RunServer(func(s *server.Server) { + // Acquire lock. + body, err := testAcquireLock(s, "foo", 3) + assert.NoError(t, err) + assert.Equal(t, body, "2") + + time.Sleep(2 * time.Second) + + // Check that we have the lock. + body, err = testGetLockIndex(s, "foo") + assert.NoError(t, err) + assert.Equal(t, body, "2") + + // Renew lock. + body, err = testRenewLock(s, "foo", 2, 3) + assert.NoError(t, err) + assert.Equal(t, body, "") + + time.Sleep(2 * time.Second) + + // Check that we still have the lock. + body, err = testGetLockIndex(s, "foo") + assert.NoError(t, err) + assert.Equal(t, body, "2") + + time.Sleep(2 * time.Second) + + // Check that lock was released. + body, err = testGetLockIndex(s, "foo") + assert.NoError(t, err) + assert.Equal(t, body, "") + }) +} + + + +func testAcquireLock(s *server.Server, key string, ttl int) (string, error) { + resp, err := tests.PostForm(fmt.Sprintf("%s/mod/lock/%s?ttl=%d", s.URL(), key, ttl), nil) + ret := tests.ReadBody(resp) + return string(ret), err +} + +func testGetLockIndex(s *server.Server, key string) (string, error) { + resp, err := tests.Get(fmt.Sprintf("%s/mod/lock/%s", s.URL(), key)) + ret := tests.ReadBody(resp) + return string(ret), err +} + +func testReleaseLock(s *server.Server, key string, index int) (string, error) { + resp, err := tests.DeleteForm(fmt.Sprintf("%s/mod/lock/%s/%d", s.URL(), key, index), nil) + ret := tests.ReadBody(resp) + return string(ret), err +} + +func testRenewLock(s *server.Server, key string, index int, ttl int) (string, error) { + resp, err := tests.PutForm(fmt.Sprintf("%s/mod/lock/%s/%d?ttl=%d", s.URL(), key, index, ttl), nil) + ret := tests.ReadBody(resp) + return string(ret), err } From df20be775cd182abeecf6628754281577691655d Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Fri, 29 Nov 2013 16:35:06 -0700 Subject: [PATCH 5/8] Fix test harness. --- server/registry.go | 1 - server/server.go | 2 - server/v2/tests/delete_handler_test.go | 6 +-- server/v2/tests/get_handler_test.go | 28 ++++++------- server/v2/tests/post_handler_test.go | 14 +++---- server/v2/tests/put_handler_test.go | 58 +++++++++++++------------- test.sh | 3 +- 7 files changed, 54 insertions(+), 58 deletions(-) diff --git a/server/registry.go b/server/registry.go index 27b0ce414bf..d1d98d9edd0 100644 --- a/server/registry.go +++ b/server/registry.go @@ -46,7 +46,6 @@ func (r *Registry) Register(name string, peerURL string, url string) error { key := path.Join(RegistryKey, name) value := fmt.Sprintf("raft=%s&etcd=%s", peerURL, url) _, err := r.store.Create(key, value, false, store.Permanent) - fmt.Println("register.1:", key, value, err) log.Debugf("Register: %s", name) return err } diff --git a/server/server.go b/server/server.go index f0de64a6737..3bce222f05a 100644 --- a/server/server.go +++ b/server/server.go @@ -320,14 +320,12 @@ func (s *Server) GetVersionHandler(w http.ResponseWriter, req *http.Request) err // Handler to return the current leader's raft address func (s *Server) GetLeaderHandler(w http.ResponseWriter, req *http.Request) error { leader := s.peerServer.RaftServer().Leader() - fmt.Println("/leader.1?", leader) if leader == "" { return etcdErr.NewError(etcdErr.EcodeLeaderElect, "", s.Store().Index()) } w.WriteHeader(http.StatusOK) url, _ := s.registry.PeerURL(leader) w.Write([]byte(url)) - fmt.Println("/leader.2?", leader, url) return nil } diff --git a/server/v2/tests/delete_handler_test.go b/server/v2/tests/delete_handler_test.go index 997127a9ecc..7ae47209028 100644 --- a/server/v2/tests/delete_handler_test.go +++ b/server/v2/tests/delete_handler_test.go @@ -19,11 +19,11 @@ func TestV2DeleteKey(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} v.Set("value", "XXX") - resp, err := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, err := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) tests.ReadBody(resp) - resp, err = tests.DeleteForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), url.Values{}) + resp, err = tests.DeleteForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), url.Values{}) body := tests.ReadBody(resp) assert.Nil(t, err, "") - assert.Equal(t, string(body), `{"action":"delete","key":"/foo/bar","prevValue":"XXX","modifiedIndex":2}`, "") + assert.Equal(t, string(body), `{"action":"delete","key":"/foo/bar","prevValue":"XXX","modifiedIndex":3}`, "") }) } diff --git a/server/v2/tests/get_handler_test.go b/server/v2/tests/get_handler_test.go index b15195873db..417d7edae4e 100644 --- a/server/v2/tests/get_handler_test.go +++ b/server/v2/tests/get_handler_test.go @@ -20,14 +20,14 @@ func TestV2GetKey(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} v.Set("value", "XXX") - resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) tests.ReadBody(resp) - resp, _ = tests.Get(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar")) + resp, _ = tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar")) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["action"], "get", "") assert.Equal(t, body["key"], "/foo/bar", "") assert.Equal(t, body["value"], "XXX", "") - assert.Equal(t, body["modifiedIndex"], 1, "") + assert.Equal(t, body["modifiedIndex"], 2, "") }) } @@ -42,19 +42,19 @@ func TestV2GetKeyRecursively(t *testing.T) { v := url.Values{} v.Set("value", "XXX") v.Set("ttl", "10") - resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/x"), v) + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/x"), v) tests.ReadBody(resp) v.Set("value", "YYY") - resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/y/z"), v) + resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/y/z"), v) tests.ReadBody(resp) - resp, _ = tests.Get(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo?recursive=true")) + resp, _ = tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo?recursive=true")) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["action"], "get", "") assert.Equal(t, body["key"], "/foo", "") assert.Equal(t, body["dir"], true, "") - assert.Equal(t, body["modifiedIndex"], 1, "") + assert.Equal(t, body["modifiedIndex"], 2, "") assert.Equal(t, len(body["kvs"].([]interface{})), 2, "") kv0 := body["kvs"].([]interface{})[0].(map[string]interface{}) @@ -82,7 +82,7 @@ func TestV2WatchKey(t *testing.T) { var body map[string]interface{} c := make(chan bool) go func() { - resp, _ := tests.Get(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar?wait=true")) + resp, _ := tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar?wait=true")) body = tests.ReadBodyJSON(resp) c <- true }() @@ -94,7 +94,7 @@ func TestV2WatchKey(t *testing.T) { // Set a value. v := url.Values{} v.Set("value", "XXX") - resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) tests.ReadBody(resp) // A response should follow from the GET above. @@ -111,7 +111,7 @@ func TestV2WatchKey(t *testing.T) { assert.Equal(t, body["action"], "set", "") assert.Equal(t, body["key"], "/foo/bar", "") assert.Equal(t, body["value"], "XXX", "") - assert.Equal(t, body["modifiedIndex"], 1, "") + assert.Equal(t, body["modifiedIndex"], 2, "") }) } @@ -126,7 +126,7 @@ func TestV2WatchKeyWithIndex(t *testing.T) { var body map[string]interface{} c := make(chan bool) go func() { - resp, _ := tests.Get(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar?wait=true&waitIndex=2")) + resp, _ := tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar?wait=true&waitIndex=3")) body = tests.ReadBodyJSON(resp) c <- true }() @@ -138,7 +138,7 @@ func TestV2WatchKeyWithIndex(t *testing.T) { // Set a value (before given index). v := url.Values{} v.Set("value", "XXX") - resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) tests.ReadBody(resp) // Make sure response didn't fire early. @@ -147,7 +147,7 @@ func TestV2WatchKeyWithIndex(t *testing.T) { // Set a value (before given index). v.Set("value", "YYY") - resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) tests.ReadBody(resp) // A response should follow from the GET above. @@ -164,6 +164,6 @@ func TestV2WatchKeyWithIndex(t *testing.T) { assert.Equal(t, body["action"], "set", "") assert.Equal(t, body["key"], "/foo/bar", "") assert.Equal(t, body["value"], "YYY", "") - assert.Equal(t, body["modifiedIndex"], 2, "") + assert.Equal(t, body["modifiedIndex"], 3, "") }) } diff --git a/server/v2/tests/post_handler_test.go b/server/v2/tests/post_handler_test.go index 856633ef018..ada6db101cc 100644 --- a/server/v2/tests/post_handler_test.go +++ b/server/v2/tests/post_handler_test.go @@ -18,21 +18,21 @@ import ( func TestV2CreateUnique(t *testing.T) { tests.RunServer(func(s *server.Server) { // POST should add index to list. - resp, _ := tests.PostForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), nil) + resp, _ := tests.PostForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), nil) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["action"], "create", "") - assert.Equal(t, body["key"], "/foo/bar/1", "") + assert.Equal(t, body["key"], "/foo/bar/2", "") assert.Equal(t, body["dir"], true, "") - assert.Equal(t, body["modifiedIndex"], 1, "") + assert.Equal(t, body["modifiedIndex"], 2, "") // Second POST should add next index to list. - resp, _ = tests.PostForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), nil) + resp, _ = tests.PostForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), nil) body = tests.ReadBodyJSON(resp) - assert.Equal(t, body["key"], "/foo/bar/2", "") + assert.Equal(t, body["key"], "/foo/bar/3", "") // POST to a different key should add index to that list. - resp, _ = tests.PostForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/baz"), nil) + resp, _ = tests.PostForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/baz"), nil) body = tests.ReadBodyJSON(resp) - assert.Equal(t, body["key"], "/foo/baz/3", "") + assert.Equal(t, body["key"], "/foo/baz/4", "") }) } diff --git a/server/v2/tests/put_handler_test.go b/server/v2/tests/put_handler_test.go index 3ee64260415..c72995c81d1 100644 --- a/server/v2/tests/put_handler_test.go +++ b/server/v2/tests/put_handler_test.go @@ -19,10 +19,10 @@ func TestV2SetKey(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} v.Set("value", "XXX") - resp, err := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, err := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) body := tests.ReadBody(resp) assert.Nil(t, err, "") - assert.Equal(t, string(body), `{"action":"set","key":"/foo/bar","value":"XXX","modifiedIndex":1}`, "") + assert.Equal(t, string(body), `{"action":"set","key":"/foo/bar","value":"XXX","modifiedIndex":2}`, "") }) } @@ -36,7 +36,7 @@ func TestV2SetKeyWithTTL(t *testing.T) { v := url.Values{} v.Set("value", "XXX") v.Set("ttl", "20") - resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["ttl"], 20, "") @@ -55,7 +55,7 @@ func TestV2SetKeyWithBadTTL(t *testing.T) { v := url.Values{} v.Set("value", "XXX") v.Set("ttl", "bad_ttl") - resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["errorCode"], 202, "") assert.Equal(t, body["message"], "The given TTL in POST form is not a number", "") @@ -72,7 +72,7 @@ func TestV2CreateKeySuccess(t *testing.T) { v := url.Values{} v.Set("value", "XXX") v.Set("prevExist", "false") - resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["value"], "XXX", "") }) @@ -88,9 +88,9 @@ func TestV2CreateKeyFail(t *testing.T) { v := url.Values{} v.Set("value", "XXX") v.Set("prevExist", "false") - resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) tests.ReadBody(resp) - resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + 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", "") @@ -108,12 +108,12 @@ func TestV2UpdateKeySuccess(t *testing.T) { v := url.Values{} v.Set("value", "XXX") - resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) tests.ReadBody(resp) v.Set("value", "YYY") v.Set("prevExist", "true") - resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["action"], "update", "") assert.Equal(t, body["prevValue"], "XXX", "") @@ -127,11 +127,11 @@ func TestV2UpdateKeySuccess(t *testing.T) { func TestV2UpdateKeyFailOnValue(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} - resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo"), v) + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo"), v) v.Set("value", "YYY") v.Set("prevExist", "true") - resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + 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", "") @@ -149,7 +149,7 @@ func TestV2UpdateKeyFailOnMissingDirectory(t *testing.T) { v := url.Values{} v.Set("value", "YYY") v.Set("prevExist", "true") - resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + 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", "") @@ -166,16 +166,16 @@ func TestV2SetKeyCASOnIndexSuccess(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} v.Set("value", "XXX") - resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) tests.ReadBody(resp) v.Set("value", "YYY") - v.Set("prevIndex", "1") - resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + v.Set("prevIndex", "2") + resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["action"], "compareAndSwap", "") assert.Equal(t, body["prevValue"], "XXX", "") assert.Equal(t, body["value"], "YYY", "") - assert.Equal(t, body["modifiedIndex"], 2, "") + assert.Equal(t, body["modifiedIndex"], 3, "") }) } @@ -188,16 +188,16 @@ func TestV2SetKeyCASOnIndexFail(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} v.Set("value", "XXX") - resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) tests.ReadBody(resp) v.Set("value", "YYY") v.Set("prevIndex", "10") - resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + 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["cause"], "[ != XXX] [10 != 1]", "") - assert.Equal(t, body["index"], 1, "") + assert.Equal(t, body["cause"], "[ != XXX] [10 != 2]", "") + assert.Equal(t, body["index"], 2, "") }) } @@ -210,7 +210,7 @@ func TestV2SetKeyCASWithInvalidIndex(t *testing.T) { v := url.Values{} v.Set("value", "YYY") v.Set("prevIndex", "bad_index") - resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["errorCode"], 203, "") assert.Equal(t, body["message"], "The given index in POST form is not a number", "") @@ -227,16 +227,16 @@ func TestV2SetKeyCASOnValueSuccess(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} v.Set("value", "XXX") - resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) tests.ReadBody(resp) v.Set("value", "YYY") v.Set("prevValue", "XXX") - resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["action"], "compareAndSwap", "") assert.Equal(t, body["prevValue"], "XXX", "") assert.Equal(t, body["value"], "YYY", "") - assert.Equal(t, body["modifiedIndex"], 2, "") + assert.Equal(t, body["modifiedIndex"], 3, "") }) } @@ -249,16 +249,16 @@ func TestV2SetKeyCASOnValueFail(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} v.Set("value", "XXX") - resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) tests.ReadBody(resp) v.Set("value", "YYY") v.Set("prevValue", "AAA") - resp, _ = tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + 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["cause"], "[AAA != XXX] [0 != 1]", "") - assert.Equal(t, body["index"], 1, "") + assert.Equal(t, body["cause"], "[AAA != XXX] [0 != 2]", "") + assert.Equal(t, body["index"], 2, "") }) } @@ -271,7 +271,7 @@ func TestV2SetKeyCASWithMissingValueFails(t *testing.T) { v := url.Values{} v.Set("value", "XXX") v.Set("prevValue", "") - resp, _ := tests.PutForm(fmt.Sprintf("http://%s%s", s.URL(), "/v2/keys/foo/bar"), v) + resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["errorCode"], 201, "") assert.Equal(t, body["message"], "PrevValue is Required in POST form", "") diff --git a/test.sh b/test.sh index 690f3a93294..246b03ad411 100755 --- a/test.sh +++ b/test.sh @@ -1,8 +1,7 @@ #!/bin/sh set -e -PKGS="./mod/lock/tests" -# PKGS="./store ./server ./server/v2/tests" +PKGS="./store ./server ./server/v2/tests ./mod/lock/tests" # Get GOPATH, etc from build . ./build From f3d438a93fb10443fb303361ff5ea37181357425 Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Wed, 4 Dec 2013 16:23:27 -0700 Subject: [PATCH 6/8] Add mod/lock connection monitoring. --- mod/lock/acquire_handler.go | 91 ++++++++++++++++++++++++++++--------- test.sh | 10 ++-- 2 files changed, 76 insertions(+), 25 deletions(-) diff --git a/mod/lock/acquire_handler.go b/mod/lock/acquire_handler.go index 8ad9e528a4d..db5cbba8b7f 100644 --- a/mod/lock/acquire_handler.go +++ b/mod/lock/acquire_handler.go @@ -6,13 +6,22 @@ import ( "strconv" "time" + "github.com/coreos/go-etcd/etcd" "github.com/gorilla/mux" ) // acquireHandler attempts to acquire a lock on the given key. +// The "key" parameter specifies the resource to lock. +// The "ttl" parameter specifies how long the lock will persist for. +// The "timeout" parameter specifies how long the request should wait for the lock. func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) { h.client.SyncCluster() + // Setup connection watcher. + closeNotifier, _ := w.(http.CloseNotifier) + closeChan := closeNotifier.CloseNotify() + + // Parse "key" and "ttl" query parameters. vars := mux.Vars(req) keypath := path.Join(prefix, vars["key"]) ttl, err := strconv.Atoi(req.FormValue("ttl")) @@ -20,6 +29,16 @@ func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) { http.Error(w, "invalid ttl: " + err.Error(), http.StatusInternalServerError) return } + + // Parse "timeout" parameter. + var timeout int + if len(req.FormValue("timeout")) == 0 { + timeout = -1 + } else if timeout, err = strconv.Atoi(req.FormValue("timeout")); err != nil { + http.Error(w, "invalid timeout: " + err.Error(), http.StatusInternalServerError) + return + } + timeout = timeout + 1 // Create an incrementing id for the lock. resp, err := h.client.AddChild(keypath, "-", uint64(ttl)) @@ -30,32 +49,31 @@ func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) { indexpath := resp.Key // Keep updating TTL to make sure lock request is not expired before acquisition. - stopChan := make(chan bool) - defer close(stopChan) - go func(k string) { - stopped := false - for { - select { - case <-time.After(time.Duration(ttl / 2) * time.Second): - case <-stopChan: - stopped = true - } - h.client.Update(k, "-", uint64(ttl)) - if stopped { - break - } + stop := make(chan bool) + go h.ttlKeepAlive(indexpath, ttl, stop) + + // Monitor for broken connection. + stopWatchChan := make(chan bool) + go func() { + select { + case <-closeChan: + stopWatchChan <- true + case <-stop: + // Stop watching for connection disconnect. } - }(indexpath) + }() // Extract the lock index. index, _ := strconv.Atoi(path.Base(resp.Key)) + // Wait until we successfully get a lock or we get a failure. + var success bool for { // Read all indices. resp, err = h.client.GetAll(keypath, true) if err != nil { http.Error(w, "lock children lookup error: " + err.Error(), http.StatusInternalServerError) - return + break } indices := extractResponseIndices(resp) waitIndex := resp.ModifiedIndex @@ -63,17 +81,48 @@ func (h *handler) acquireHandler(w http.ResponseWriter, req *http.Request) { // If there is no previous index then we have the lock. if prevIndex == 0 { + success = true break } // Otherwise watch previous index until it's gone. - _, err = h.client.Watch(path.Join(keypath, strconv.Itoa(prevIndex)), waitIndex, nil, nil) - if err != nil { + _, err = h.client.Watch(path.Join(keypath, strconv.Itoa(prevIndex)), waitIndex, nil, stopWatchChan) + if err == etcd.ErrWatchStoppedByUser { + break + } else if err != nil { http.Error(w, "lock watch error: " + err.Error(), http.StatusInternalServerError) - return + break } } - // Write lock index to response body. - w.Write([]byte(strconv.Itoa(index))) + // Check for connection disconnect before we write the lock index. + select { + case <-stopWatchChan: + success = false + default: + } + + // Stop the ttl keep-alive. + close(stop) + + if success { + // Write lock index to response body if we acquire the lock. + h.client.Update(indexpath, "-", uint64(ttl)) + w.Write([]byte(strconv.Itoa(index))) + } else { + // Make sure key is deleted if we couldn't acquire. + h.client.Delete(indexpath) + } +} + +// ttlKeepAlive continues to update a key's TTL until the stop channel is closed. +func (h *handler) ttlKeepAlive(k string, ttl int, stop chan bool) { + for { + select { + case <-time.After(time.Duration(ttl / 2) * time.Second): + h.client.Update(k, "-", uint64(ttl)) + case <-stop: + return + } + } } diff --git a/test.sh b/test.sh index 246b03ad411..cb4c51fa914 100755 --- a/test.sh +++ b/test.sh @@ -1,7 +1,9 @@ #!/bin/sh set -e -PKGS="./store ./server ./server/v2/tests ./mod/lock/tests" +if [ -z "$PKG" ]; then + PKG="./store ./server ./server/v2/tests ./mod/lock/tests" +fi # Get GOPATH, etc from build . ./build @@ -10,10 +12,10 @@ PKGS="./store ./server ./server/v2/tests ./mod/lock/tests" export GOPATH="${PWD}" # Unit tests -for PKG in $PKGS +for i in $PKG do - go test -i $PKG - go test -v $PKG + go test -i $i + go test -v $i done # Functional tests From e76b7d1e8b1a2bde0d63e84fe2bd63f9480bb745 Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Wed, 4 Dec 2013 22:24:04 -0700 Subject: [PATCH 7/8] Add mod/lock version. --- mod/lock/{ => v2}/acquire_handler.go | 2 +- mod/lock/{ => v2}/get_index_handler.go | 2 +- mod/lock/{ => v2}/handler.go | 2 +- mod/lock/{ => v2}/release_handler.go | 2 +- mod/lock/{ => v2}/renew_handler.go | 2 +- mod/lock/{ => v2}/tests/handler_test.go | 8 ++++---- mod/mod.go | 4 ++-- test.sh | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) rename mod/lock/{ => v2}/acquire_handler.go (99%) rename mod/lock/{ => v2}/get_index_handler.go (98%) rename mod/lock/{ => v2}/handler.go (99%) rename mod/lock/{ => v2}/release_handler.go (97%) rename mod/lock/{ => v2}/renew_handler.go (98%) rename mod/lock/{ => v2}/tests/handler_test.go (92%) diff --git a/mod/lock/acquire_handler.go b/mod/lock/v2/acquire_handler.go similarity index 99% rename from mod/lock/acquire_handler.go rename to mod/lock/v2/acquire_handler.go index db5cbba8b7f..de82cdd1626 100644 --- a/mod/lock/acquire_handler.go +++ b/mod/lock/v2/acquire_handler.go @@ -1,4 +1,4 @@ -package lock +package v2 import ( "net/http" diff --git a/mod/lock/get_index_handler.go b/mod/lock/v2/get_index_handler.go similarity index 98% rename from mod/lock/get_index_handler.go rename to mod/lock/v2/get_index_handler.go index 2bb97a83cd3..2393da76c42 100644 --- a/mod/lock/get_index_handler.go +++ b/mod/lock/v2/get_index_handler.go @@ -1,4 +1,4 @@ -package lock +package v2 import ( "net/http" diff --git a/mod/lock/handler.go b/mod/lock/v2/handler.go similarity index 99% rename from mod/lock/handler.go rename to mod/lock/v2/handler.go index 43e1491450f..2713758b336 100644 --- a/mod/lock/handler.go +++ b/mod/lock/v2/handler.go @@ -1,4 +1,4 @@ -package lock +package v2 import ( "net/http" diff --git a/mod/lock/release_handler.go b/mod/lock/v2/release_handler.go similarity index 97% rename from mod/lock/release_handler.go rename to mod/lock/v2/release_handler.go index 09251f25928..b41157ef2a9 100644 --- a/mod/lock/release_handler.go +++ b/mod/lock/v2/release_handler.go @@ -1,4 +1,4 @@ -package lock +package v2 import ( "path" diff --git a/mod/lock/renew_handler.go b/mod/lock/v2/renew_handler.go similarity index 98% rename from mod/lock/renew_handler.go rename to mod/lock/v2/renew_handler.go index 7933931e4a7..cdd65b3aacc 100644 --- a/mod/lock/renew_handler.go +++ b/mod/lock/v2/renew_handler.go @@ -1,4 +1,4 @@ -package lock +package v2 import ( "path" diff --git a/mod/lock/tests/handler_test.go b/mod/lock/v2/tests/handler_test.go similarity index 92% rename from mod/lock/tests/handler_test.go rename to mod/lock/v2/tests/handler_test.go index 7e9091a0fa4..b589865bf57 100644 --- a/mod/lock/tests/handler_test.go +++ b/mod/lock/v2/tests/handler_test.go @@ -164,25 +164,25 @@ func TestModLockRenew(t *testing.T) { func testAcquireLock(s *server.Server, key string, ttl int) (string, error) { - resp, err := tests.PostForm(fmt.Sprintf("%s/mod/lock/%s?ttl=%d", s.URL(), key, ttl), nil) + resp, err := tests.PostForm(fmt.Sprintf("%s/mod/lock/v2/%s?ttl=%d", s.URL(), key, ttl), nil) ret := tests.ReadBody(resp) return string(ret), err } func testGetLockIndex(s *server.Server, key string) (string, error) { - resp, err := tests.Get(fmt.Sprintf("%s/mod/lock/%s", s.URL(), key)) + resp, err := tests.Get(fmt.Sprintf("%s/mod/lock/v2/%s", s.URL(), key)) ret := tests.ReadBody(resp) return string(ret), err } func testReleaseLock(s *server.Server, key string, index int) (string, error) { - resp, err := tests.DeleteForm(fmt.Sprintf("%s/mod/lock/%s/%d", s.URL(), key, index), nil) + resp, err := tests.DeleteForm(fmt.Sprintf("%s/mod/lock/v2/%s/%d", s.URL(), key, index), nil) ret := tests.ReadBody(resp) return string(ret), err } func testRenewLock(s *server.Server, key string, index int, ttl int) (string, error) { - resp, err := tests.PutForm(fmt.Sprintf("%s/mod/lock/%s/%d?ttl=%d", s.URL(), key, index, ttl), nil) + resp, err := tests.PutForm(fmt.Sprintf("%s/mod/lock/v2/%s/%d?ttl=%d", s.URL(), key, index, ttl), nil) ret := tests.ReadBody(resp) return string(ret), err } diff --git a/mod/mod.go b/mod/mod.go index 7c0194f56ee..b5625db3f63 100644 --- a/mod/mod.go +++ b/mod/mod.go @@ -6,7 +6,7 @@ import ( "path" "github.com/coreos/etcd/mod/dashboard" - "github.com/coreos/etcd/mod/lock" + lock2 "github.com/coreos/etcd/mod/lock/v2" "github.com/gorilla/mux" ) @@ -23,6 +23,6 @@ func HttpHandler(addr string) http.Handler { r.PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", dashboard.HttpHandler())) // TODO: Use correct addr. - r.PathPrefix("/lock").Handler(http.StripPrefix("/lock", lock.NewHandler(addr))) + r.PathPrefix("/lock/v2").Handler(http.StripPrefix("/lock/v2", lock2.NewHandler(addr))) return r } diff --git a/test.sh b/test.sh index cb4c51fa914..ae40d82002d 100755 --- a/test.sh +++ b/test.sh @@ -2,7 +2,7 @@ set -e if [ -z "$PKG" ]; then - PKG="./store ./server ./server/v2/tests ./mod/lock/tests" + PKG="./store ./server ./server/v2/tests ./mod/lock/v2/tests" fi # Get GOPATH, etc from build From b784ced5175d77d2ce17657f71ebb3858f4dafda Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Wed, 4 Dec 2013 22:39:59 -0700 Subject: [PATCH 8/8] Update mod/lock versioning. --- mod/lock/v2/tests/handler_test.go | 8 ++++---- mod/mod.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mod/lock/v2/tests/handler_test.go b/mod/lock/v2/tests/handler_test.go index b589865bf57..b07572bbe43 100644 --- a/mod/lock/v2/tests/handler_test.go +++ b/mod/lock/v2/tests/handler_test.go @@ -164,25 +164,25 @@ func TestModLockRenew(t *testing.T) { func testAcquireLock(s *server.Server, key string, ttl int) (string, error) { - resp, err := tests.PostForm(fmt.Sprintf("%s/mod/lock/v2/%s?ttl=%d", s.URL(), key, ttl), nil) + resp, err := tests.PostForm(fmt.Sprintf("%s/mod/v2/lock/%s?ttl=%d", s.URL(), key, ttl), nil) ret := tests.ReadBody(resp) return string(ret), err } func testGetLockIndex(s *server.Server, key string) (string, error) { - resp, err := tests.Get(fmt.Sprintf("%s/mod/lock/v2/%s", s.URL(), key)) + resp, err := tests.Get(fmt.Sprintf("%s/mod/v2/lock/%s", s.URL(), key)) ret := tests.ReadBody(resp) return string(ret), err } func testReleaseLock(s *server.Server, key string, index int) (string, error) { - resp, err := tests.DeleteForm(fmt.Sprintf("%s/mod/lock/v2/%s/%d", s.URL(), key, index), nil) + resp, err := tests.DeleteForm(fmt.Sprintf("%s/mod/v2/lock/%s/%d", s.URL(), key, index), nil) ret := tests.ReadBody(resp) return string(ret), err } func testRenewLock(s *server.Server, key string, index int, ttl int) (string, error) { - resp, err := tests.PutForm(fmt.Sprintf("%s/mod/lock/v2/%s/%d?ttl=%d", s.URL(), key, index, ttl), nil) + resp, err := tests.PutForm(fmt.Sprintf("%s/mod/v2/lock/%s/%d?ttl=%d", s.URL(), key, index, ttl), nil) ret := tests.ReadBody(resp) return string(ret), err } diff --git a/mod/mod.go b/mod/mod.go index b5625db3f63..34a380689f8 100644 --- a/mod/mod.go +++ b/mod/mod.go @@ -23,6 +23,6 @@ func HttpHandler(addr string) http.Handler { r.PathPrefix("/dashboard/").Handler(http.StripPrefix("/dashboard/", dashboard.HttpHandler())) // TODO: Use correct addr. - r.PathPrefix("/lock/v2").Handler(http.StripPrefix("/lock/v2", lock2.NewHandler(addr))) + r.PathPrefix("/v2/lock").Handler(http.StripPrefix("/v2/lock", lock2.NewHandler(addr))) return r }