mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
client: Add CreateInOrder method to client.KeysAPI
Allows creating nodes within a given directory with atomically increasing keys
This commit is contained in:
parent
f480a8b051
commit
d89a8628c6
@ -106,6 +106,9 @@ type KeysAPI interface {
|
|||||||
// Create is an alias for Set w/ PrevExist=false
|
// Create is an alias for Set w/ PrevExist=false
|
||||||
Create(ctx context.Context, key, value string) (*Response, error)
|
Create(ctx context.Context, key, value string) (*Response, error)
|
||||||
|
|
||||||
|
// CreateInOrder is used to atomically create in-order keys within the given directory.
|
||||||
|
CreateInOrder(ctx context.Context, dir, value string, opts *CreateInOrderOptions) (*Response, error)
|
||||||
|
|
||||||
// Update is an alias for Set w/ PrevExist=true
|
// Update is an alias for Set w/ PrevExist=true
|
||||||
Update(ctx context.Context, key, value string) (*Response, error)
|
Update(ctx context.Context, key, value string) (*Response, error)
|
||||||
|
|
||||||
@ -133,6 +136,14 @@ type WatcherOptions struct {
|
|||||||
Recursive bool
|
Recursive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateInOrderOptions struct {
|
||||||
|
// TTL defines a period of time after-which the Node should
|
||||||
|
// expire and no longer exist. Values <= 0 are ignored. Given
|
||||||
|
// that the zero-value is ignored, TTL cannot be used to set
|
||||||
|
// a TTL of 0.
|
||||||
|
TTL time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
type SetOptions struct {
|
type SetOptions struct {
|
||||||
// PrevValue specifies what the current value of the Node must
|
// PrevValue specifies what the current value of the Node must
|
||||||
// be in order for the Set operation to succeed.
|
// be in order for the Set operation to succeed.
|
||||||
@ -294,6 +305,25 @@ func (k *httpKeysAPI) Create(ctx context.Context, key, val string) (*Response, e
|
|||||||
return k.Set(ctx, key, val, &SetOptions{PrevExist: PrevNoExist})
|
return k.Set(ctx, key, val, &SetOptions{PrevExist: PrevNoExist})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *httpKeysAPI) CreateInOrder(ctx context.Context, dir, val string, opts *CreateInOrderOptions) (*Response, error) {
|
||||||
|
act := &createInOrderAction{
|
||||||
|
Prefix: k.prefix,
|
||||||
|
Dir: dir,
|
||||||
|
Value: val,
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts != nil {
|
||||||
|
act.TTL = opts.TTL
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, body, err := k.client.Do(ctx, act)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
|
||||||
|
}
|
||||||
|
|
||||||
func (k *httpKeysAPI) Update(ctx context.Context, key, val string) (*Response, error) {
|
func (k *httpKeysAPI) Update(ctx context.Context, key, val string) (*Response, error) {
|
||||||
return k.Set(ctx, key, val, &SetOptions{PrevExist: PrevExist})
|
return k.Set(ctx, key, val, &SetOptions{PrevExist: PrevExist})
|
||||||
}
|
}
|
||||||
@ -492,6 +522,28 @@ func (a *deleteAction) HTTPRequest(ep url.URL) *http.Request {
|
|||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type createInOrderAction struct {
|
||||||
|
Prefix string
|
||||||
|
Dir string
|
||||||
|
Value string
|
||||||
|
TTL time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *createInOrderAction) HTTPRequest(ep url.URL) *http.Request {
|
||||||
|
u := v2KeysURL(ep, a.Prefix, a.Dir)
|
||||||
|
|
||||||
|
form := url.Values{}
|
||||||
|
form.Add("value", a.Value)
|
||||||
|
if a.TTL > 0 {
|
||||||
|
form.Add("ttl", strconv.FormatUint(uint64(a.TTL.Seconds()), 10))
|
||||||
|
}
|
||||||
|
body := strings.NewReader(form.Encode())
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("POST", u.String(), body)
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
func unmarshalHTTPResponse(code int, header http.Header, body []byte) (res *Response, err error) {
|
func unmarshalHTTPResponse(code int, header http.Header, body []byte) (res *Response, err error) {
|
||||||
switch code {
|
switch code {
|
||||||
case http.StatusOK, http.StatusCreated:
|
case http.StatusOK, http.StatusCreated:
|
||||||
|
@ -345,6 +345,107 @@ func TestSetAction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateInOrderAction(t *testing.T) {
|
||||||
|
wantHeader := http.Header(map[string][]string{
|
||||||
|
"Content-Type": []string{"application/x-www-form-urlencoded"},
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
act createInOrderAction
|
||||||
|
wantURL string
|
||||||
|
wantBody string
|
||||||
|
}{
|
||||||
|
// default prefix
|
||||||
|
{
|
||||||
|
act: createInOrderAction{
|
||||||
|
Prefix: defaultV2KeysPrefix,
|
||||||
|
Dir: "foo",
|
||||||
|
},
|
||||||
|
wantURL: "http://example.com/v2/keys/foo",
|
||||||
|
wantBody: "value=",
|
||||||
|
},
|
||||||
|
|
||||||
|
// non-default prefix
|
||||||
|
{
|
||||||
|
act: createInOrderAction{
|
||||||
|
Prefix: "/pfx",
|
||||||
|
Dir: "foo",
|
||||||
|
},
|
||||||
|
wantURL: "http://example.com/pfx/foo",
|
||||||
|
wantBody: "value=",
|
||||||
|
},
|
||||||
|
|
||||||
|
// no prefix
|
||||||
|
{
|
||||||
|
act: createInOrderAction{
|
||||||
|
Dir: "foo",
|
||||||
|
},
|
||||||
|
wantURL: "http://example.com/foo",
|
||||||
|
wantBody: "value=",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Key with path separators
|
||||||
|
{
|
||||||
|
act: createInOrderAction{
|
||||||
|
Prefix: defaultV2KeysPrefix,
|
||||||
|
Dir: "foo/bar/baz",
|
||||||
|
},
|
||||||
|
wantURL: "http://example.com/v2/keys/foo/bar/baz",
|
||||||
|
wantBody: "value=",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Key with leading slash, Prefix with trailing slash
|
||||||
|
{
|
||||||
|
act: createInOrderAction{
|
||||||
|
Prefix: "/foo/",
|
||||||
|
Dir: "/bar",
|
||||||
|
},
|
||||||
|
wantURL: "http://example.com/foo/bar",
|
||||||
|
wantBody: "value=",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Key with trailing slash
|
||||||
|
{
|
||||||
|
act: createInOrderAction{
|
||||||
|
Dir: "/foo/",
|
||||||
|
},
|
||||||
|
wantURL: "http://example.com/foo",
|
||||||
|
wantBody: "value=",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Value is set
|
||||||
|
{
|
||||||
|
act: createInOrderAction{
|
||||||
|
Dir: "foo",
|
||||||
|
Value: "baz",
|
||||||
|
},
|
||||||
|
wantURL: "http://example.com/foo",
|
||||||
|
wantBody: "value=baz",
|
||||||
|
},
|
||||||
|
// TTL is set
|
||||||
|
{
|
||||||
|
act: createInOrderAction{
|
||||||
|
Dir: "foo",
|
||||||
|
TTL: 3 * time.Minute,
|
||||||
|
},
|
||||||
|
wantURL: "http://example.com/foo",
|
||||||
|
wantBody: "ttl=180&value=",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
u, err := url.Parse(tt.wantURL)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("#%d: unable to use wantURL fixture: %v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := tt.act.HTTPRequest(url.URL{Scheme: "http", Host: "example.com"})
|
||||||
|
if err := assertRequest(*got, "POST", u, wantHeader, []byte(tt.wantBody)); err != nil {
|
||||||
|
t.Errorf("#%d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDeleteAction(t *testing.T) {
|
func TestDeleteAction(t *testing.T) {
|
||||||
wantHeader := http.Header(map[string][]string{
|
wantHeader := http.Header(map[string][]string{
|
||||||
"Content-Type": []string{"application/x-www-form-urlencoded"},
|
"Content-Type": []string{"application/x-www-form-urlencoded"},
|
||||||
@ -822,7 +923,7 @@ func TestHTTPKeysAPIWatcherAction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTTPKeysAPISetAction(t *testing.T) {
|
func TestHTTPKeysAPIcreateInOrderAction(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
key string
|
key string
|
||||||
value string
|
value string
|
||||||
@ -1189,6 +1290,16 @@ func TestHTTPKeysAPICreateAction(t *testing.T) {
|
|||||||
kAPI.Create(context.Background(), "/foo", "bar")
|
kAPI.Create(context.Background(), "/foo", "bar")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHTTPKeysAPICreateInOrderAction(t *testing.T) {
|
||||||
|
act := &createInOrderAction{
|
||||||
|
Dir: "/foo",
|
||||||
|
Value: "bar",
|
||||||
|
TTL: 0,
|
||||||
|
}
|
||||||
|
kAPI := httpKeysAPI{client: &actionAssertingHTTPClient{t: t, act: act}}
|
||||||
|
kAPI.CreateInOrder(context.Background(), "/foo", "bar", nil)
|
||||||
|
}
|
||||||
|
|
||||||
func TestHTTPKeysAPIUpdateAction(t *testing.T) {
|
func TestHTTPKeysAPIUpdateAction(t *testing.T) {
|
||||||
act := &setAction{
|
act := &setAction{
|
||||||
Key: "/foo",
|
Key: "/foo",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user