mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
Merge pull request #6308 from gyuho/manual2
client: do not send previous node data (optional)
This commit is contained in:
commit
48941cea95
@ -559,6 +559,25 @@ Let's create a key-value pair first: `foo=one`.
|
|||||||
curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d value=one
|
curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d value=one
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action":"set",
|
||||||
|
"node":{
|
||||||
|
"key":"/foo",
|
||||||
|
"value":"one",
|
||||||
|
"modifiedIndex":4,
|
||||||
|
"createdIndex":4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Specifying `noValueOnSuccess` option skips returning the node as value.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl http://127.0.0.1:2379/v2/keys/foo?noValueOnSuccess=true -XPUT -d value=one
|
||||||
|
# {"action":"set"}
|
||||||
|
```
|
||||||
|
|
||||||
Now let's try some invalid `CompareAndSwap` commands.
|
Now let's try some invalid `CompareAndSwap` commands.
|
||||||
|
|
||||||
Trying to set this existing key with `prevExist=false` fails as expected:
|
Trying to set this existing key with `prevExist=false` fails as expected:
|
||||||
|
@ -8,10 +8,11 @@ package client
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
codec1978 "github.com/ugorji/go/codec"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
time "time"
|
time "time"
|
||||||
|
|
||||||
|
codec1978 "github.com/ugorji/go/codec"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -191,6 +191,10 @@ type SetOptions struct {
|
|||||||
|
|
||||||
// Dir specifies whether or not this Node should be created as a directory.
|
// Dir specifies whether or not this Node should be created as a directory.
|
||||||
Dir bool
|
Dir bool
|
||||||
|
|
||||||
|
// NoValueOnSuccess specifies whether the response contains the current value of the Node.
|
||||||
|
// If set, the response will only contain the current value when the request fails.
|
||||||
|
NoValueOnSuccess bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetOptions struct {
|
type GetOptions struct {
|
||||||
@ -335,6 +339,7 @@ func (k *httpKeysAPI) Set(ctx context.Context, key, val string, opts *SetOptions
|
|||||||
act.TTL = opts.TTL
|
act.TTL = opts.TTL
|
||||||
act.Refresh = opts.Refresh
|
act.Refresh = opts.Refresh
|
||||||
act.Dir = opts.Dir
|
act.Dir = opts.Dir
|
||||||
|
act.NoValueOnSuccess = opts.NoValueOnSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
doCtx := ctx
|
doCtx := ctx
|
||||||
@ -523,15 +528,16 @@ func (w *waitAction) HTTPRequest(ep url.URL) *http.Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type setAction struct {
|
type setAction struct {
|
||||||
Prefix string
|
Prefix string
|
||||||
Key string
|
Key string
|
||||||
Value string
|
Value string
|
||||||
PrevValue string
|
PrevValue string
|
||||||
PrevIndex uint64
|
PrevIndex uint64
|
||||||
PrevExist PrevExistType
|
PrevExist PrevExistType
|
||||||
TTL time.Duration
|
TTL time.Duration
|
||||||
Refresh bool
|
Refresh bool
|
||||||
Dir bool
|
Dir bool
|
||||||
|
NoValueOnSuccess bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *setAction) HTTPRequest(ep url.URL) *http.Request {
|
func (a *setAction) HTTPRequest(ep url.URL) *http.Request {
|
||||||
@ -565,6 +571,9 @@ func (a *setAction) HTTPRequest(ep url.URL) *http.Request {
|
|||||||
if a.Refresh {
|
if a.Refresh {
|
||||||
form.Add("refresh", "true")
|
form.Add("refresh", "true")
|
||||||
}
|
}
|
||||||
|
if a.NoValueOnSuccess {
|
||||||
|
params.Set("noValueOnSuccess", strconv.FormatBool(a.NoValueOnSuccess))
|
||||||
|
}
|
||||||
|
|
||||||
u.RawQuery = params.Encode()
|
u.RawQuery = params.Encode()
|
||||||
body := strings.NewReader(form.Encode())
|
body := strings.NewReader(form.Encode())
|
||||||
|
@ -407,6 +407,15 @@ func TestSetAction(t *testing.T) {
|
|||||||
wantURL: "http://example.com/foo?dir=true",
|
wantURL: "http://example.com/foo?dir=true",
|
||||||
wantBody: "",
|
wantBody: "",
|
||||||
},
|
},
|
||||||
|
// NoValueOnSuccess is set
|
||||||
|
{
|
||||||
|
act: setAction{
|
||||||
|
Key: "foo",
|
||||||
|
NoValueOnSuccess: true,
|
||||||
|
},
|
||||||
|
wantURL: "http://example.com/foo?noValueOnSuccess=true",
|
||||||
|
wantBody: "value=",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
|
@ -153,7 +153,7 @@ func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
clock := clockwork.NewRealClock()
|
clock := clockwork.NewRealClock()
|
||||||
startTime := clock.Now()
|
startTime := clock.Now()
|
||||||
rr, err := parseKeyRequest(r, clock)
|
rr, noValueOnSuccess, err := parseKeyRequest(r, clock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeKeyError(w, err)
|
writeKeyError(w, err)
|
||||||
return
|
return
|
||||||
@ -175,7 +175,7 @@ func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case resp.Event != nil:
|
case resp.Event != nil:
|
||||||
if err := writeKeyEvent(w, resp.Event, h.timer); err != nil {
|
if err := writeKeyEvent(w, resp.Event, noValueOnSuccess, h.timer); err != nil {
|
||||||
// Should never be reached
|
// Should never be reached
|
||||||
plog.Errorf("error writing event (%v)", err)
|
plog.Errorf("error writing event (%v)", err)
|
||||||
}
|
}
|
||||||
@ -449,19 +449,20 @@ func logHandleFunc(w http.ResponseWriter, r *http.Request) {
|
|||||||
// parseKeyRequest converts a received http.Request on keysPrefix to
|
// parseKeyRequest converts a received http.Request on keysPrefix to
|
||||||
// a server Request, performing validation of supplied fields as appropriate.
|
// a server Request, performing validation of supplied fields as appropriate.
|
||||||
// If any validation fails, an empty Request and non-nil error is returned.
|
// If any validation fails, an empty Request and non-nil error is returned.
|
||||||
func parseKeyRequest(r *http.Request, clock clockwork.Clock) (etcdserverpb.Request, error) {
|
func parseKeyRequest(r *http.Request, clock clockwork.Clock) (etcdserverpb.Request, bool, error) {
|
||||||
|
noValueOnSuccess := false
|
||||||
emptyReq := etcdserverpb.Request{}
|
emptyReq := etcdserverpb.Request{}
|
||||||
|
|
||||||
err := r.ParseForm()
|
err := r.ParseForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return emptyReq, etcdErr.NewRequestError(
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
etcdErr.EcodeInvalidForm,
|
etcdErr.EcodeInvalidForm,
|
||||||
err.Error(),
|
err.Error(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(r.URL.Path, keysPrefix) {
|
if !strings.HasPrefix(r.URL.Path, keysPrefix) {
|
||||||
return emptyReq, etcdErr.NewRequestError(
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
etcdErr.EcodeInvalidForm,
|
etcdErr.EcodeInvalidForm,
|
||||||
"incorrect key prefix",
|
"incorrect key prefix",
|
||||||
)
|
)
|
||||||
@ -470,13 +471,13 @@ func parseKeyRequest(r *http.Request, clock clockwork.Clock) (etcdserverpb.Reque
|
|||||||
|
|
||||||
var pIdx, wIdx uint64
|
var pIdx, wIdx uint64
|
||||||
if pIdx, err = getUint64(r.Form, "prevIndex"); err != nil {
|
if pIdx, err = getUint64(r.Form, "prevIndex"); err != nil {
|
||||||
return emptyReq, etcdErr.NewRequestError(
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
etcdErr.EcodeIndexNaN,
|
etcdErr.EcodeIndexNaN,
|
||||||
`invalid value for "prevIndex"`,
|
`invalid value for "prevIndex"`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if wIdx, err = getUint64(r.Form, "waitIndex"); err != nil {
|
if wIdx, err = getUint64(r.Form, "waitIndex"); err != nil {
|
||||||
return emptyReq, etcdErr.NewRequestError(
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
etcdErr.EcodeIndexNaN,
|
etcdErr.EcodeIndexNaN,
|
||||||
`invalid value for "waitIndex"`,
|
`invalid value for "waitIndex"`,
|
||||||
)
|
)
|
||||||
@ -484,45 +485,45 @@ func parseKeyRequest(r *http.Request, clock clockwork.Clock) (etcdserverpb.Reque
|
|||||||
|
|
||||||
var rec, sort, wait, dir, quorum, stream bool
|
var rec, sort, wait, dir, quorum, stream bool
|
||||||
if rec, err = getBool(r.Form, "recursive"); err != nil {
|
if rec, err = getBool(r.Form, "recursive"); err != nil {
|
||||||
return emptyReq, etcdErr.NewRequestError(
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
etcdErr.EcodeInvalidField,
|
etcdErr.EcodeInvalidField,
|
||||||
`invalid value for "recursive"`,
|
`invalid value for "recursive"`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if sort, err = getBool(r.Form, "sorted"); err != nil {
|
if sort, err = getBool(r.Form, "sorted"); err != nil {
|
||||||
return emptyReq, etcdErr.NewRequestError(
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
etcdErr.EcodeInvalidField,
|
etcdErr.EcodeInvalidField,
|
||||||
`invalid value for "sorted"`,
|
`invalid value for "sorted"`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if wait, err = getBool(r.Form, "wait"); err != nil {
|
if wait, err = getBool(r.Form, "wait"); err != nil {
|
||||||
return emptyReq, etcdErr.NewRequestError(
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
etcdErr.EcodeInvalidField,
|
etcdErr.EcodeInvalidField,
|
||||||
`invalid value for "wait"`,
|
`invalid value for "wait"`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// TODO(jonboulle): define what parameters dir is/isn't compatible with?
|
// TODO(jonboulle): define what parameters dir is/isn't compatible with?
|
||||||
if dir, err = getBool(r.Form, "dir"); err != nil {
|
if dir, err = getBool(r.Form, "dir"); err != nil {
|
||||||
return emptyReq, etcdErr.NewRequestError(
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
etcdErr.EcodeInvalidField,
|
etcdErr.EcodeInvalidField,
|
||||||
`invalid value for "dir"`,
|
`invalid value for "dir"`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if quorum, err = getBool(r.Form, "quorum"); err != nil {
|
if quorum, err = getBool(r.Form, "quorum"); err != nil {
|
||||||
return emptyReq, etcdErr.NewRequestError(
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
etcdErr.EcodeInvalidField,
|
etcdErr.EcodeInvalidField,
|
||||||
`invalid value for "quorum"`,
|
`invalid value for "quorum"`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if stream, err = getBool(r.Form, "stream"); err != nil {
|
if stream, err = getBool(r.Form, "stream"); err != nil {
|
||||||
return emptyReq, etcdErr.NewRequestError(
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
etcdErr.EcodeInvalidField,
|
etcdErr.EcodeInvalidField,
|
||||||
`invalid value for "stream"`,
|
`invalid value for "stream"`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if wait && r.Method != "GET" {
|
if wait && r.Method != "GET" {
|
||||||
return emptyReq, etcdErr.NewRequestError(
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
etcdErr.EcodeInvalidField,
|
etcdErr.EcodeInvalidField,
|
||||||
`"wait" can only be used with GET requests`,
|
`"wait" can only be used with GET requests`,
|
||||||
)
|
)
|
||||||
@ -530,19 +531,26 @@ func parseKeyRequest(r *http.Request, clock clockwork.Clock) (etcdserverpb.Reque
|
|||||||
|
|
||||||
pV := r.FormValue("prevValue")
|
pV := r.FormValue("prevValue")
|
||||||
if _, ok := r.Form["prevValue"]; ok && pV == "" {
|
if _, ok := r.Form["prevValue"]; ok && pV == "" {
|
||||||
return emptyReq, etcdErr.NewRequestError(
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
etcdErr.EcodePrevValueRequired,
|
etcdErr.EcodePrevValueRequired,
|
||||||
`"prevValue" cannot be empty`,
|
`"prevValue" cannot be empty`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if noValueOnSuccess, err = getBool(r.Form, "noValueOnSuccess"); err != nil {
|
||||||
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
|
etcdErr.EcodeInvalidField,
|
||||||
|
`invalid value for "noValueOnSuccess"`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// TTL is nullable, so leave it null if not specified
|
// TTL is nullable, so leave it null if not specified
|
||||||
// or an empty string
|
// or an empty string
|
||||||
var ttl *uint64
|
var ttl *uint64
|
||||||
if len(r.FormValue("ttl")) > 0 {
|
if len(r.FormValue("ttl")) > 0 {
|
||||||
i, err := getUint64(r.Form, "ttl")
|
i, err := getUint64(r.Form, "ttl")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return emptyReq, etcdErr.NewRequestError(
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
etcdErr.EcodeTTLNaN,
|
etcdErr.EcodeTTLNaN,
|
||||||
`invalid value for "ttl"`,
|
`invalid value for "ttl"`,
|
||||||
)
|
)
|
||||||
@ -555,7 +563,7 @@ func parseKeyRequest(r *http.Request, clock clockwork.Clock) (etcdserverpb.Reque
|
|||||||
if _, ok := r.Form["prevExist"]; ok {
|
if _, ok := r.Form["prevExist"]; ok {
|
||||||
bv, err := getBool(r.Form, "prevExist")
|
bv, err := getBool(r.Form, "prevExist")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return emptyReq, etcdErr.NewRequestError(
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
etcdErr.EcodeInvalidField,
|
etcdErr.EcodeInvalidField,
|
||||||
"invalid value for prevExist",
|
"invalid value for prevExist",
|
||||||
)
|
)
|
||||||
@ -568,7 +576,7 @@ func parseKeyRequest(r *http.Request, clock clockwork.Clock) (etcdserverpb.Reque
|
|||||||
if _, ok := r.Form["refresh"]; ok {
|
if _, ok := r.Form["refresh"]; ok {
|
||||||
bv, err := getBool(r.Form, "refresh")
|
bv, err := getBool(r.Form, "refresh")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return emptyReq, etcdErr.NewRequestError(
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
etcdErr.EcodeInvalidField,
|
etcdErr.EcodeInvalidField,
|
||||||
"invalid value for refresh",
|
"invalid value for refresh",
|
||||||
)
|
)
|
||||||
@ -577,13 +585,13 @@ func parseKeyRequest(r *http.Request, clock clockwork.Clock) (etcdserverpb.Reque
|
|||||||
if refresh != nil && *refresh {
|
if refresh != nil && *refresh {
|
||||||
val := r.FormValue("value")
|
val := r.FormValue("value")
|
||||||
if _, ok := r.Form["value"]; ok && val != "" {
|
if _, ok := r.Form["value"]; ok && val != "" {
|
||||||
return emptyReq, etcdErr.NewRequestError(
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
etcdErr.EcodeRefreshValue,
|
etcdErr.EcodeRefreshValue,
|
||||||
`A value was provided on a refresh`,
|
`A value was provided on a refresh`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if ttl == nil {
|
if ttl == nil {
|
||||||
return emptyReq, etcdErr.NewRequestError(
|
return emptyReq, false, etcdErr.NewRequestError(
|
||||||
etcdErr.EcodeRefreshTTLRequired,
|
etcdErr.EcodeRefreshTTLRequired,
|
||||||
`No TTL value set`,
|
`No TTL value set`,
|
||||||
)
|
)
|
||||||
@ -621,13 +629,13 @@ func parseKeyRequest(r *http.Request, clock clockwork.Clock) (etcdserverpb.Reque
|
|||||||
rr.Expiration = clock.Now().Add(expr).UnixNano()
|
rr.Expiration = clock.Now().Add(expr).UnixNano()
|
||||||
}
|
}
|
||||||
|
|
||||||
return rr, nil
|
return rr, noValueOnSuccess, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeKeyEvent trims the prefix of key path in a single Event under
|
// writeKeyEvent trims the prefix of key path in a single Event under
|
||||||
// StoreKeysPrefix, serializes it and writes the resulting JSON to the given
|
// StoreKeysPrefix, serializes it and writes the resulting JSON to the given
|
||||||
// ResponseWriter, along with the appropriate headers.
|
// ResponseWriter, along with the appropriate headers.
|
||||||
func writeKeyEvent(w http.ResponseWriter, ev *store.Event, rt etcdserver.RaftTimer) error {
|
func writeKeyEvent(w http.ResponseWriter, ev *store.Event, noValueOnSuccess bool, rt etcdserver.RaftTimer) error {
|
||||||
if ev == nil {
|
if ev == nil {
|
||||||
return errors.New("cannot write empty Event!")
|
return errors.New("cannot write empty Event!")
|
||||||
}
|
}
|
||||||
@ -641,6 +649,12 @@ func writeKeyEvent(w http.ResponseWriter, ev *store.Event, rt etcdserver.RaftTim
|
|||||||
}
|
}
|
||||||
|
|
||||||
ev = trimEventPrefix(ev, etcdserver.StoreKeysPrefix)
|
ev = trimEventPrefix(ev, etcdserver.StoreKeysPrefix)
|
||||||
|
if noValueOnSuccess &&
|
||||||
|
(ev.Action == store.Set || ev.Action == store.CompareAndSwap ||
|
||||||
|
ev.Action == store.Create || ev.Action == store.Update) {
|
||||||
|
ev.Node = nil
|
||||||
|
ev.PrevNode = nil
|
||||||
|
}
|
||||||
return json.NewEncoder(w).Encode(ev)
|
return json.NewEncoder(w).Encode(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@ func TestBadRefreshRequest(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
got, err := parseKeyRequest(tt.in, clockwork.NewFakeClock())
|
got, _, err := parseKeyRequest(tt.in, clockwork.NewFakeClock())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("#%d: unexpected nil error!", i)
|
t.Errorf("#%d: unexpected nil error!", i)
|
||||||
continue
|
continue
|
||||||
@ -360,7 +360,7 @@ func TestBadParseRequest(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
got, err := parseKeyRequest(tt.in, clockwork.NewFakeClock())
|
got, _, err := parseKeyRequest(tt.in, clockwork.NewFakeClock())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("#%d: unexpected nil error!", i)
|
t.Errorf("#%d: unexpected nil error!", i)
|
||||||
continue
|
continue
|
||||||
@ -384,8 +384,9 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
fc := clockwork.NewFakeClock()
|
fc := clockwork.NewFakeClock()
|
||||||
fc.Advance(1111)
|
fc.Advance(1111)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
in *http.Request
|
in *http.Request
|
||||||
w etcdserverpb.Request
|
w etcdserverpb.Request
|
||||||
|
noValue bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
// good prefix, all other values default
|
// good prefix, all other values default
|
||||||
@ -394,6 +395,7 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// value specified
|
// value specified
|
||||||
@ -407,6 +409,7 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
Val: "some_value",
|
Val: "some_value",
|
||||||
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// prevIndex specified
|
// prevIndex specified
|
||||||
@ -420,6 +423,7 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
PrevIndex: 98765,
|
PrevIndex: 98765,
|
||||||
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// recursive specified
|
// recursive specified
|
||||||
@ -433,6 +437,7 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
Recursive: true,
|
Recursive: true,
|
||||||
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// sorted specified
|
// sorted specified
|
||||||
@ -446,6 +451,7 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
Sorted: true,
|
Sorted: true,
|
||||||
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// quorum specified
|
// quorum specified
|
||||||
@ -459,6 +465,7 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
Quorum: true,
|
Quorum: true,
|
||||||
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// wait specified
|
// wait specified
|
||||||
@ -468,6 +475,7 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
Wait: true,
|
Wait: true,
|
||||||
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// empty TTL specified
|
// empty TTL specified
|
||||||
@ -477,6 +485,7 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
Expiration: 0,
|
Expiration: 0,
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// non-empty TTL specified
|
// non-empty TTL specified
|
||||||
@ -486,6 +495,7 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
Expiration: fc.Now().Add(5678 * time.Second).UnixNano(),
|
Expiration: fc.Now().Add(5678 * time.Second).UnixNano(),
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// zero TTL specified
|
// zero TTL specified
|
||||||
@ -495,6 +505,7 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
Expiration: fc.Now().UnixNano(),
|
Expiration: fc.Now().UnixNano(),
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// dir specified
|
// dir specified
|
||||||
@ -504,6 +515,7 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
Dir: true,
|
Dir: true,
|
||||||
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// dir specified negatively
|
// dir specified negatively
|
||||||
@ -513,6 +525,7 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
Dir: false,
|
Dir: false,
|
||||||
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// prevExist should be non-null if specified
|
// prevExist should be non-null if specified
|
||||||
@ -526,6 +539,7 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
PrevExist: boolp(true),
|
PrevExist: boolp(true),
|
||||||
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// prevExist should be non-null if specified
|
// prevExist should be non-null if specified
|
||||||
@ -539,6 +553,7 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
PrevExist: boolp(false),
|
PrevExist: boolp(false),
|
||||||
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
// mix various fields
|
// mix various fields
|
||||||
{
|
{
|
||||||
@ -558,6 +573,7 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
Val: "some value",
|
Val: "some value",
|
||||||
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
// query parameters should be used if given
|
// query parameters should be used if given
|
||||||
{
|
{
|
||||||
@ -571,6 +587,7 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
PrevValue: "woof",
|
PrevValue: "woof",
|
||||||
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
},
|
},
|
||||||
// but form values should take precedence over query parameters
|
// but form values should take precedence over query parameters
|
||||||
{
|
{
|
||||||
@ -586,14 +603,33 @@ func TestGoodParseRequest(t *testing.T) {
|
|||||||
PrevValue: "miaow",
|
PrevValue: "miaow",
|
||||||
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// noValueOnSuccess specified
|
||||||
|
mustNewForm(
|
||||||
|
t,
|
||||||
|
"foo",
|
||||||
|
url.Values{"noValueOnSuccess": []string{"true"}},
|
||||||
|
),
|
||||||
|
etcdserverpb.Request{
|
||||||
|
Method: "PUT",
|
||||||
|
Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
|
||||||
|
},
|
||||||
|
true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
got, err := parseKeyRequest(tt.in, fc)
|
got, noValueOnSuccess, err := parseKeyRequest(tt.in, fc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("#%d: err = %v, want %v", i, err, nil)
|
t.Errorf("#%d: err = %v, want %v", i, err, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if noValueOnSuccess != tt.noValue {
|
||||||
|
t.Errorf("#%d: noValue=%t, want %t", i, noValueOnSuccess, tt.noValue)
|
||||||
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(got, tt.w) {
|
if !reflect.DeepEqual(got, tt.w) {
|
||||||
t.Errorf("#%d: request=%#v, want %#v", i, got, tt.w)
|
t.Errorf("#%d: request=%#v, want %#v", i, got, tt.w)
|
||||||
}
|
}
|
||||||
@ -1112,7 +1148,7 @@ func TestServeMembersFail(t *testing.T) {
|
|||||||
func TestWriteEvent(t *testing.T) {
|
func TestWriteEvent(t *testing.T) {
|
||||||
// nil event should not panic
|
// nil event should not panic
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
writeKeyEvent(rec, nil, dummyRaftTimer{})
|
writeKeyEvent(rec, nil, false, dummyRaftTimer{})
|
||||||
h := rec.Header()
|
h := rec.Header()
|
||||||
if len(h) > 0 {
|
if len(h) > 0 {
|
||||||
t.Fatalf("unexpected non-empty headers: %#v", h)
|
t.Fatalf("unexpected non-empty headers: %#v", h)
|
||||||
@ -1123,8 +1159,9 @@ func TestWriteEvent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
ev *store.Event
|
ev *store.Event
|
||||||
idx string
|
noValue bool
|
||||||
|
idx string
|
||||||
// TODO(jonboulle): check body as well as just status code
|
// TODO(jonboulle): check body as well as just status code
|
||||||
code int
|
code int
|
||||||
err error
|
err error
|
||||||
@ -1136,6 +1173,7 @@ func TestWriteEvent(t *testing.T) {
|
|||||||
Node: &store.NodeExtern{},
|
Node: &store.NodeExtern{},
|
||||||
PrevNode: &store.NodeExtern{},
|
PrevNode: &store.NodeExtern{},
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
"0",
|
"0",
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
nil,
|
nil,
|
||||||
@ -1147,6 +1185,7 @@ func TestWriteEvent(t *testing.T) {
|
|||||||
Node: &store.NodeExtern{},
|
Node: &store.NodeExtern{},
|
||||||
PrevNode: &store.NodeExtern{},
|
PrevNode: &store.NodeExtern{},
|
||||||
},
|
},
|
||||||
|
false,
|
||||||
"0",
|
"0",
|
||||||
http.StatusCreated,
|
http.StatusCreated,
|
||||||
nil,
|
nil,
|
||||||
@ -1155,7 +1194,7 @@ func TestWriteEvent(t *testing.T) {
|
|||||||
|
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
writeKeyEvent(rw, tt.ev, dummyRaftTimer{})
|
writeKeyEvent(rw, tt.ev, tt.noValue, dummyRaftTimer{})
|
||||||
if gct := rw.Header().Get("Content-Type"); gct != "application/json" {
|
if gct := rw.Header().Get("Content-Type"); gct != "application/json" {
|
||||||
t.Errorf("case %d: bad Content-Type: got %q, want application/json", i, gct)
|
t.Errorf("case %d: bad Content-Type: got %q, want application/json", i, gct)
|
||||||
}
|
}
|
||||||
@ -1550,45 +1589,76 @@ func TestServeKeysGood(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestServeKeysEvent(t *testing.T) {
|
func TestServeKeysEvent(t *testing.T) {
|
||||||
req := mustNewRequest(t, "foo")
|
tests := []struct {
|
||||||
server := &resServer{
|
req *http.Request
|
||||||
etcdserver.Response{
|
rsp etcdserver.Response
|
||||||
Event: &store.Event{
|
wcode int
|
||||||
|
event *store.Event
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
mustNewRequest(t, "foo"),
|
||||||
|
etcdserver.Response{
|
||||||
|
Event: &store.Event{
|
||||||
|
Action: store.Get,
|
||||||
|
Node: &store.NodeExtern{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
http.StatusOK,
|
||||||
|
&store.Event{
|
||||||
Action: store.Get,
|
Action: store.Get,
|
||||||
Node: &store.NodeExtern{},
|
Node: &store.NodeExtern{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
mustNewForm(
|
||||||
|
t,
|
||||||
|
"foo",
|
||||||
|
url.Values{"noValueOnSuccess": []string{"true"}},
|
||||||
|
),
|
||||||
|
etcdserver.Response{
|
||||||
|
Event: &store.Event{
|
||||||
|
Action: store.CompareAndSwap,
|
||||||
|
Node: &store.NodeExtern{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
http.StatusOK,
|
||||||
|
&store.Event{
|
||||||
|
Action: store.CompareAndSwap,
|
||||||
|
Node: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server := &resServer{}
|
||||||
h := &keysHandler{
|
h := &keysHandler{
|
||||||
timeout: time.Hour,
|
timeout: time.Hour,
|
||||||
server: server,
|
server: server,
|
||||||
cluster: &fakeCluster{id: 1},
|
cluster: &fakeCluster{id: 1},
|
||||||
timer: &dummyRaftTimer{},
|
timer: &dummyRaftTimer{},
|
||||||
}
|
}
|
||||||
rw := httptest.NewRecorder()
|
|
||||||
|
|
||||||
h.ServeHTTP(rw, req)
|
for _, tt := range tests {
|
||||||
|
server.res = tt.rsp
|
||||||
|
rw := httptest.NewRecorder()
|
||||||
|
h.ServeHTTP(rw, tt.req)
|
||||||
|
|
||||||
wcode := http.StatusOK
|
wbody := mustMarshalEvent(
|
||||||
wbody := mustMarshalEvent(
|
t,
|
||||||
t,
|
tt.event,
|
||||||
&store.Event{
|
)
|
||||||
Action: store.Get,
|
|
||||||
Node: &store.NodeExtern{},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
if rw.Code != wcode {
|
if rw.Code != tt.wcode {
|
||||||
t.Errorf("got code=%d, want %d", rw.Code, wcode)
|
t.Errorf("got code=%d, want %d", rw.Code, tt.wcode)
|
||||||
}
|
}
|
||||||
gcid := rw.Header().Get("X-Etcd-Cluster-ID")
|
gcid := rw.Header().Get("X-Etcd-Cluster-ID")
|
||||||
wcid := h.cluster.ID().String()
|
wcid := h.cluster.ID().String()
|
||||||
if gcid != wcid {
|
if gcid != wcid {
|
||||||
t.Errorf("cid = %s, want %s", gcid, wcid)
|
t.Errorf("cid = %s, want %s", gcid, wcid)
|
||||||
}
|
}
|
||||||
g := rw.Body.String()
|
g := rw.Body.String()
|
||||||
if g != wbody {
|
if g != wbody {
|
||||||
t.Errorf("got body=%#v, want %#v", g, wbody)
|
t.Errorf("got body=%#v, want %#v", g, wbody)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +45,9 @@ func TestV2Set(t *testing.T) {
|
|||||||
tc := NewTestClient()
|
tc := NewTestClient()
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
v.Set("value", "bar")
|
v.Set("value", "bar")
|
||||||
|
vAndNoValue := url.Values{}
|
||||||
|
vAndNoValue.Set("value", "bar")
|
||||||
|
vAndNoValue.Set("noValueOnSuccess", "true")
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
relativeURL string
|
relativeURL string
|
||||||
@ -70,6 +73,12 @@ func TestV2Set(t *testing.T) {
|
|||||||
http.StatusCreated,
|
http.StatusCreated,
|
||||||
`{"action":"set","node":{"key":"/fooempty","value":"","modifiedIndex":6,"createdIndex":6}}`,
|
`{"action":"set","node":{"key":"/fooempty","value":"","modifiedIndex":6,"createdIndex":6}}`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"/v2/keys/foo/novalue",
|
||||||
|
vAndNoValue,
|
||||||
|
http.StatusCreated,
|
||||||
|
`{"action":"set"}`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
@ -183,6 +192,31 @@ func TestV2CreateUpdate(t *testing.T) {
|
|||||||
"cause": "/nonexist",
|
"cause": "/nonexist",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// create with no value on success
|
||||||
|
{
|
||||||
|
"/v2/keys/create/novalue",
|
||||||
|
url.Values(map[string][]string{"value": {"XXX"}, "prevExist": {"false"}, "noValueOnSuccess": {"true"}}),
|
||||||
|
http.StatusCreated,
|
||||||
|
map[string]interface{}{},
|
||||||
|
},
|
||||||
|
// update with no value on success
|
||||||
|
{
|
||||||
|
"/v2/keys/create/novalue",
|
||||||
|
url.Values(map[string][]string{"value": {"XXX"}, "prevExist": {"true"}, "noValueOnSuccess": {"true"}}),
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]interface{}{},
|
||||||
|
},
|
||||||
|
// created key failed with no value on success
|
||||||
|
{
|
||||||
|
"/v2/keys/create/foo",
|
||||||
|
url.Values(map[string][]string{"value": {"XXX"}, "prevExist": {"false"}, "noValueOnSuccess": {"true"}}),
|
||||||
|
http.StatusPreconditionFailed,
|
||||||
|
map[string]interface{}{
|
||||||
|
"errorCode": float64(105),
|
||||||
|
"message": "Key already exists",
|
||||||
|
"cause": "/create/foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
@ -312,6 +346,25 @@ func TestV2CAS(t *testing.T) {
|
|||||||
"cause": "[bad_value != ZZZ]",
|
"cause": "[bad_value != ZZZ]",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"/v2/keys/cas/foo",
|
||||||
|
url.Values(map[string][]string{"value": {"YYY"}, "prevIndex": {"6"}, "noValueOnSuccess": {"true"}}),
|
||||||
|
http.StatusOK,
|
||||||
|
map[string]interface{}{
|
||||||
|
"action": "compareAndSwap",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"/v2/keys/cas/foo",
|
||||||
|
url.Values(map[string][]string{"value": {"YYY"}, "prevIndex": {"10"}, "noValueOnSuccess": {"true"}}),
|
||||||
|
http.StatusPreconditionFailed,
|
||||||
|
map[string]interface{}{
|
||||||
|
"errorCode": float64(101),
|
||||||
|
"message": "Compare failed",
|
||||||
|
"cause": "[10 != 7]",
|
||||||
|
"index": float64(7),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user