Browse Source

Merge pull request #2673 from ecnahc515/create_in_order

client: Add CreateInOrder method to client.KeysAPI
Xiang Li 10 years ago
parent
commit
54c4d5005d
2 changed files with 164 additions and 1 deletions
  1. 52 0
      client/keys.go
  2. 112 1
      client/keys_test.go

+ 52 - 0
client/keys.go

@@ -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:

+ 112 - 1
client/keys_test.go

@@ -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",