// 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{}) assert.Equal(t, resp.StatusCode, http.StatusOK) body := tests.ReadBody(resp) assert.Nil(t, err, "") assert.Equal(t, string(body), `{"action":"delete","node":{"key":"/foo","dir":true,"modifiedIndex":3,"createdIndex":2},"prevNode":{"key":"/foo","dir":true,"modifiedIndex":2,"createdIndex":2}}`, "") }) }
// Ensures that a key is deleted. // // $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX // $ curl -X DELETE localhost:4001/v2/keys/foo/bar // func TestV2DeleteKey(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} v.Set("value", "XXX") resp, err := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) tests.ReadBody(resp) resp, err = tests.DeleteForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), url.Values{}) assert.Equal(t, resp.StatusCode, http.StatusOK) body := tests.ReadBody(resp) assert.Nil(t, err, "") assert.Equal(t, string(body), `{"action":"delete","node":{"key":"/foo/bar","modifiedIndex":3,"createdIndex":2},"prevNode":{"key":"/foo/bar","value":"XXX","modifiedIndex":2,"createdIndex":2}}`, "") }) }
// Ensure that we can start a v2 cluster from the logs of a v1 cluster. func TestV1ClusterMigration(t *testing.T) { path, _ := ioutil.TempDir("", "etcd-") os.RemoveAll(path) defer os.RemoveAll(path) nodes := []string{"node0", "node2"} for i, node := range nodes { nodepath := filepath.Join(path, node) fixturepath, _ := filepath.Abs(filepath.Join("../fixtures/v1.cluster/", node)) fmt.Println("FIXPATH =", fixturepath) fmt.Println("NODEPATH =", nodepath) os.MkdirAll(filepath.Dir(nodepath), 0777) // Copy over fixture files. c := exec.Command("cp", "-rf", fixturepath, nodepath) if out, err := c.CombinedOutput(); err != nil { fmt.Println(">>>>>>\n", string(out), "<<<<<<") panic("Fixture initialization error:" + err.Error()) } procAttr := new(os.ProcAttr) procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr} args := []string{"etcd", fmt.Sprintf("-data-dir=%s", nodepath)} args = append(args, "-addr", fmt.Sprintf("127.0.0.1:%d", 4001+i)) args = append(args, "-peer-addr", fmt.Sprintf("127.0.0.1:%d", 7001+i)) args = append(args, "-name", node) process, err := os.StartProcess(EtcdBinPath, args, procAttr) if err != nil { t.Fatal("start process failed:" + err.Error()) return } defer process.Kill() time.Sleep(time.Second) } // Ensure deleted message is removed. resp, err := tests.Get("http://localhost:4001/v2/keys/message") body := tests.ReadBody(resp) assert.Nil(t, err, "") assert.Equal(t, resp.StatusCode, http.StatusNotFound) assert.Equal(t, string(body), `{"errorCode":100,"message":"Key not found","cause":"/message","index":11}`+"\n") // Ensure TTL'd message is removed. resp, err = tests.Get("http://localhost:4001/v2/keys/foo") body = tests.ReadBody(resp) assert.Nil(t, err, "") assert.Equal(t, resp.StatusCode, 200, "") assert.Equal(t, string(body), `{"action":"get","node":{"key":"/foo","value":"one","modifiedIndex":9,"createdIndex":9}}`) }
// Ensures that a watcher can wait for a value to be set after a given index. // // $ curl localhost:4001/v2/keys/foo/bar?wait=true&waitIndex=4 // $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX // $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=YYY // func TestV2WatchKeyWithIndex(t *testing.T) { tests.RunServer(func(s *server.Server) { var body map[string]interface{} c := make(chan bool) go func() { resp, _ := tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar?wait=true&waitIndex=3")) body = tests.ReadBodyJSON(resp) c <- true }() // Make sure response didn't fire early. time.Sleep(1 * time.Millisecond) assert.Nil(t, body, "") // Set a value (before given index). v := url.Values{} v.Set("value", "XXX") resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) tests.ReadBody(resp) // Make sure response didn't fire early. time.Sleep(1 * time.Millisecond) assert.Nil(t, body, "") // Set a value (before given index). v.Set("value", "YYY") 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. time.Sleep(1 * time.Millisecond) select { case <-c: default: t.Fatal("cannot get watch result") } assert.NotNil(t, body, "") assert.Equal(t, body["action"], "set", "") node := body["node"].(map[string]interface{}) assert.Equal(t, node["key"], "/foo/bar", "") assert.Equal(t, node["value"], "YYY", "") assert.Equal(t, node["modifiedIndex"], 3, "") }) }
// 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{}) assert.Equal(t, resp.StatusCode, http.StatusCreated) 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 an error is thrown if an invalid previous value is provided. // // $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX // $ curl -X DELETE localhost:4001/v2/keys/foo/bar?prevIndex= // func TestV2DeleteKeyCADWithInvalidValue(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} v.Set("value", "XXX") resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) tests.ReadBody(resp) resp, _ = tests.DeleteForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar?prevValue="), v) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["errorCode"], 201) }) }
// Ensures that a watcher can wait for a value to be set after a given index. // // $ curl localhost:4001/v2/keys/keyindir/bar?wait=true // $ curl -X PUT localhost:4001/v2/keys/keyindir -d dir=true -d ttl=1 // $ curl -X PUT localhost:4001/v2/keys/keyindir/bar -d value=YYY // func TestV2WatchKeyInDir(t *testing.T) { tests.RunServer(func(s *server.Server) { var body map[string]interface{} c := make(chan bool) // Set a value (before given index). v := url.Values{} v.Set("dir", "true") v.Set("ttl", "1") resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/keyindir"), v) tests.ReadBody(resp) // Set a value (before given index). v = url.Values{} v.Set("value", "XXX") resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/keyindir/bar"), v) tests.ReadBody(resp) go func() { resp, _ := tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/keyindir/bar?wait=true")) body = tests.ReadBodyJSON(resp) c <- true }() // wait for expiration, we do have a up to 500 millisecond delay time.Sleep(2000 * time.Millisecond) select { case <-c: default: t.Fatal("cannot get watch result") } assert.NotNil(t, body, "") assert.Equal(t, body["action"], "expire", "") node := body["node"].(map[string]interface{}) assert.Equal(t, node["key"], "/keyindir", "") }) }
// Ensures that a key is not deleted if the previous index does not match // // $ curl -X PUT localhost:4001/v2/keys/foo -d value=XXX // $ curl -X DELETE localhost:4001/v2/keys/foo?prevIndex=100 // func TestV2DeleteKeyCADOnIndexFail(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} v.Set("value", "XXX") resp, err := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo"), v) tests.ReadBody(resp) resp, err = tests.DeleteForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo?prevIndex=100"), url.Values{}) assert.Nil(t, err, "") body := tests.ReadBodyJSON(resp) assert.Equal(t, body["errorCode"], 101) }) }
// Ensures that a key is deleted only if the previous value matches. // // $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX // $ curl -X DELETE localhost:4001/v2/keys/foo/bar?prevValue=XXX // func TestV2DeleteKeyCADOnValueSuccess(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} v.Set("value", "XXX") resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v) tests.ReadBody(resp) resp, _ = tests.DeleteForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar?prevValue=XXX"), v) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["action"], "compareAndDelete", "") node := body["node"].(map[string]interface{}) assert.Equal(t, node["modifiedIndex"], 3, "") }) }
// Ensures that a directory of values can be recursively retrieved for a given key. // // $ curl -X PUT localhost:4001/v2/keys/foo/x -d value=XXX // $ curl -X PUT localhost:4001/v2/keys/foo/y/z -d value=YYY // $ curl localhost:4001/v2/keys/foo -d recursive=true // func TestV2GetKeyRecursively(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} v.Set("value", "XXX") v.Set("ttl", "10") 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("%s%s", s.URL(), "/v2/keys/foo/y/z"), v) tests.ReadBody(resp) resp, _ = tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo?recursive=true")) assert.Equal(t, resp.StatusCode, http.StatusOK) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["action"], "get", "") node := body["node"].(map[string]interface{}) assert.Equal(t, node["key"], "/foo", "") assert.Equal(t, node["dir"], true, "") assert.Equal(t, node["modifiedIndex"], 2, "") assert.Equal(t, len(node["nodes"].([]interface{})), 2, "") node0 := node["nodes"].([]interface{})[0].(map[string]interface{}) assert.Equal(t, node0["key"], "/foo/x", "") assert.Equal(t, node0["value"], "XXX", "") assert.Equal(t, node0["ttl"], 10, "") node1 := node["nodes"].([]interface{})[1].(map[string]interface{}) assert.Equal(t, node1["key"], "/foo/y", "") assert.Equal(t, node1["dir"], true, "") node2 := node1["nodes"].([]interface{})[0].(map[string]interface{}) assert.Equal(t, node2["key"], "/foo/y/z", "") assert.Equal(t, node2["value"], "YYY", "") }) }
// Ensures that a key is not conditionally set because it previously existed. // // $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX -d prevExist=false // $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX -d prevExist=false -> fail // func TestV2CreateKeyFail(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} v.Set("value", "XXX") v.Set("prevExist", "false") fullURL := fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar") resp, _ := tests.PutForm(fullURL, v) assert.Equal(t, resp.StatusCode, http.StatusCreated) tests.ReadBody(resp) resp, _ = tests.PutForm(fullURL, v) assert.Equal(t, resp.StatusCode, http.StatusPreconditionFailed) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["errorCode"], 105, "") assert.Equal(t, body["message"], "Key already exists", "") assert.Equal(t, body["cause"], "/foo/bar", "") }) }
// Ensures that a key is conditionally set only if it previously did exist. // // $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX // $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=YYY -d prevExist=true // func TestV2UpdateKeySuccess(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} v.Set("value", "XXX") fullURL := fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar") resp, _ := tests.PutForm(fullURL, v) assert.Equal(t, resp.StatusCode, http.StatusCreated) tests.ReadBody(resp) v.Set("value", "YYY") v.Set("prevExist", "true") resp, _ = tests.PutForm(fullURL, v) assert.Equal(t, resp.StatusCode, http.StatusOK) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["action"], "update", "") }) }
// Ensures that a key is not set if the previous value does not match. // // $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX // $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=YYY -d prevValue=AAA // func TestV2SetKeyCASOnValueFail(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} v.Set("value", "XXX") fullURL := fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar") resp, _ := tests.PutForm(fullURL, v) assert.Equal(t, resp.StatusCode, http.StatusCreated) tests.ReadBody(resp) v.Set("value", "YYY") v.Set("prevValue", "AAA") resp, _ = tests.PutForm(fullURL, v) assert.Equal(t, resp.StatusCode, http.StatusPreconditionFailed) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["errorCode"], 101, "") assert.Equal(t, body["message"], "Compare failed", "") assert.Equal(t, body["cause"], "[AAA != XXX] [0 != 2]", "") assert.Equal(t, body["index"], 2, "") }) }
// Ensures that a key is set only if the previous value matches. // // $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX // $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=YYY -d prevValue=XXX // func TestV2SetKeyCASOnValueSuccess(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} v.Set("value", "XXX") fullURL := fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar") resp, _ := tests.PutForm(fullURL, v) assert.Equal(t, resp.StatusCode, http.StatusCreated) tests.ReadBody(resp) v.Set("value", "YYY") v.Set("prevValue", "XXX") resp, _ = tests.PutForm(fullURL, v) assert.Equal(t, resp.StatusCode, http.StatusOK) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["action"], "compareAndSwap", "") node := body["node"].(map[string]interface{}) assert.Equal(t, node["value"], "YYY", "") assert.Equal(t, node["modifiedIndex"], 3, "") }) }
// Ensures that a value can be retrieve for a given key. // // $ curl localhost:4001/v2/keys/foo/bar -> fail // $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX // $ curl localhost:4001/v2/keys/foo/bar // func TestV2GetKey(t *testing.T) { tests.RunServer(func(s *server.Server) { v := url.Values{} v.Set("value", "XXX") fullURL := fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar") resp, _ := tests.Get(fullURL) assert.Equal(t, resp.StatusCode, http.StatusNotFound) resp, _ = tests.PutForm(fullURL, v) tests.ReadBody(resp) resp, _ = tests.Get(fullURL) assert.Equal(t, resp.StatusCode, http.StatusOK) body := tests.ReadBodyJSON(resp) assert.Equal(t, body["action"], "get", "") node := body["node"].(map[string]interface{}) assert.Equal(t, node["key"], "/foo/bar", "") assert.Equal(t, node["value"], "XXX", "") assert.Equal(t, node["modifiedIndex"], 2, "") }) }
// Ensure that we can start a v2 node from the log of a v1 node. func TestV1SoloMigration(t *testing.T) { path, _ := ioutil.TempDir("", "etcd-") os.MkdirAll(path, 0777) defer os.RemoveAll(path) nodepath := filepath.Join(path, "node0") fixturepath, _ := filepath.Abs("../fixtures/v1.solo/node0") fmt.Println("DATA_DIR =", nodepath) // Copy over fixture files. c := exec.Command("cp", "-rf", fixturepath, nodepath) if out, err := c.CombinedOutput(); err != nil { fmt.Println(">>>>>>\n", string(out), "<<<<<<") panic("Fixture initialization error:" + err.Error()) } procAttr := new(os.ProcAttr) procAttr.Files = []*os.File{nil, os.Stdout, os.Stderr} args := []string{"etcd", fmt.Sprintf("-data-dir=%s", nodepath)} args = append(args, "-addr", "127.0.0.1:4001") args = append(args, "-peer-addr", "127.0.0.1:7001") args = append(args, "-name", "v1") process, err := os.StartProcess(EtcdBinPath, args, procAttr) if err != nil { t.Fatal("start process failed:" + err.Error()) return } defer process.Kill() time.Sleep(time.Second) // Ensure deleted message is removed. resp, err := tests.Get("http://localhost:4001/v2/keys/message") tests.ReadBody(resp) assert.Nil(t, err, "") assert.Equal(t, resp.StatusCode, 200, "") }
func testRenewLock(s *server.Server, key string, index string, value string, ttl int) (string, int, error) { resp, err := tests.PutForm(fmt.Sprintf("%s/mod/v2/lock/%s?index=%s&value=%s&ttl=%d", s.URL(), key, index, value, ttl), nil) ret := tests.ReadBody(resp) return string(ret), resp.StatusCode, err }
func testReleaseLock(s *server.Server, key string, index string, value string) (string, int, error) { resp, err := tests.DeleteForm(fmt.Sprintf("%s/mod/v2/lock/%s?index=%s&value=%s", s.URL(), key, index, value), nil) ret := tests.ReadBody(resp) return string(ret), resp.StatusCode, err }
func testSetLeader(s *server.Server, key string, name string, ttl int) (string, int, error) { resp, err := tests.PutForm(fmt.Sprintf("%s/mod/v2/leader/%s?name=%s&ttl=%d", s.URL(), key, name, ttl), nil) ret := tests.ReadBody(resp) return string(ret), resp.StatusCode, err }
func testGetLockValue(s *server.Server, key string) (string, int, error) { resp, err := tests.Get(fmt.Sprintf("%s/mod/v2/lock/%s", s.URL(), key)) ret := tests.ReadBody(resp) return string(ret), resp.StatusCode, err }
func testDeleteLeader(s *server.Server, key string, name string) (string, int, error) { resp, err := tests.DeleteForm(fmt.Sprintf("%s/mod/v2/leader/%s?name=%s", s.URL(), key, name), nil) ret := tests.ReadBody(resp) return string(ret), resp.StatusCode, err }