blob: 8b9fd3f87a496338b98575e7137af4d3adbcb14b [file] [log] [blame]
khenaidooffe076b2019-01-15 16:08:08 -05001// Copyright 2015 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package client
16
17//go:generate codecgen -d 1819 -r "Node|Response|Nodes" -o keys.generated.go keys.go
18
19import (
20 "context"
21 "encoding/json"
22 "errors"
23 "fmt"
24 "net/http"
25 "net/url"
26 "strconv"
27 "strings"
28 "time"
29
30 "github.com/coreos/etcd/pkg/pathutil"
31 "github.com/ugorji/go/codec"
32)
33
34const (
35 ErrorCodeKeyNotFound = 100
36 ErrorCodeTestFailed = 101
37 ErrorCodeNotFile = 102
38 ErrorCodeNotDir = 104
39 ErrorCodeNodeExist = 105
40 ErrorCodeRootROnly = 107
41 ErrorCodeDirNotEmpty = 108
42 ErrorCodeUnauthorized = 110
43
44 ErrorCodePrevValueRequired = 201
45 ErrorCodeTTLNaN = 202
46 ErrorCodeIndexNaN = 203
47 ErrorCodeInvalidField = 209
48 ErrorCodeInvalidForm = 210
49
50 ErrorCodeRaftInternal = 300
51 ErrorCodeLeaderElect = 301
52
53 ErrorCodeWatcherCleared = 400
54 ErrorCodeEventIndexCleared = 401
55)
56
57type Error struct {
58 Code int `json:"errorCode"`
59 Message string `json:"message"`
60 Cause string `json:"cause"`
61 Index uint64 `json:"index"`
62}
63
64func (e Error) Error() string {
65 return fmt.Sprintf("%v: %v (%v) [%v]", e.Code, e.Message, e.Cause, e.Index)
66}
67
68var (
69 ErrInvalidJSON = errors.New("client: response is invalid json. The endpoint is probably not valid etcd cluster endpoint.")
70 ErrEmptyBody = errors.New("client: response body is empty")
71)
72
73// PrevExistType is used to define an existence condition when setting
74// or deleting Nodes.
75type PrevExistType string
76
77const (
78 PrevIgnore = PrevExistType("")
79 PrevExist = PrevExistType("true")
80 PrevNoExist = PrevExistType("false")
81)
82
83var (
84 defaultV2KeysPrefix = "/v2/keys"
85)
86
87// NewKeysAPI builds a KeysAPI that interacts with etcd's key-value
88// API over HTTP.
89func NewKeysAPI(c Client) KeysAPI {
90 return NewKeysAPIWithPrefix(c, defaultV2KeysPrefix)
91}
92
93// NewKeysAPIWithPrefix acts like NewKeysAPI, but allows the caller
94// to provide a custom base URL path. This should only be used in
95// very rare cases.
96func NewKeysAPIWithPrefix(c Client, p string) KeysAPI {
97 return &httpKeysAPI{
98 client: c,
99 prefix: p,
100 }
101}
102
103type KeysAPI interface {
104 // Get retrieves a set of Nodes from etcd
105 Get(ctx context.Context, key string, opts *GetOptions) (*Response, error)
106
107 // Set assigns a new value to a Node identified by a given key. The caller
108 // may define a set of conditions in the SetOptions. If SetOptions.Dir=true
109 // then value is ignored.
110 Set(ctx context.Context, key, value string, opts *SetOptions) (*Response, error)
111
112 // Delete removes a Node identified by the given key, optionally destroying
113 // all of its children as well. The caller may define a set of required
114 // conditions in an DeleteOptions object.
115 Delete(ctx context.Context, key string, opts *DeleteOptions) (*Response, error)
116
117 // Create is an alias for Set w/ PrevExist=false
118 Create(ctx context.Context, key, value string) (*Response, error)
119
120 // CreateInOrder is used to atomically create in-order keys within the given directory.
121 CreateInOrder(ctx context.Context, dir, value string, opts *CreateInOrderOptions) (*Response, error)
122
123 // Update is an alias for Set w/ PrevExist=true
124 Update(ctx context.Context, key, value string) (*Response, error)
125
126 // Watcher builds a new Watcher targeted at a specific Node identified
127 // by the given key. The Watcher may be configured at creation time
128 // through a WatcherOptions object. The returned Watcher is designed
129 // to emit events that happen to a Node, and optionally to its children.
130 Watcher(key string, opts *WatcherOptions) Watcher
131}
132
133type WatcherOptions struct {
134 // AfterIndex defines the index after-which the Watcher should
135 // start emitting events. For example, if a value of 5 is
136 // provided, the first event will have an index >= 6.
137 //
138 // Setting AfterIndex to 0 (default) means that the Watcher
139 // should start watching for events starting at the current
140 // index, whatever that may be.
141 AfterIndex uint64
142
143 // Recursive specifies whether or not the Watcher should emit
144 // events that occur in children of the given keyspace. If set
145 // to false (default), events will be limited to those that
146 // occur for the exact key.
147 Recursive bool
148}
149
150type CreateInOrderOptions struct {
151 // TTL defines a period of time after-which the Node should
152 // expire and no longer exist. Values <= 0 are ignored. Given
153 // that the zero-value is ignored, TTL cannot be used to set
154 // a TTL of 0.
155 TTL time.Duration
156}
157
158type SetOptions struct {
159 // PrevValue specifies what the current value of the Node must
160 // be in order for the Set operation to succeed.
161 //
162 // Leaving this field empty means that the caller wishes to
163 // ignore the current value of the Node. This cannot be used
164 // to compare the Node's current value to an empty string.
165 //
166 // PrevValue is ignored if Dir=true
167 PrevValue string
168
169 // PrevIndex indicates what the current ModifiedIndex of the
170 // Node must be in order for the Set operation to succeed.
171 //
172 // If PrevIndex is set to 0 (default), no comparison is made.
173 PrevIndex uint64
174
175 // PrevExist specifies whether the Node must currently exist
176 // (PrevExist) or not (PrevNoExist). If the caller does not
177 // care about existence, set PrevExist to PrevIgnore, or simply
178 // leave it unset.
179 PrevExist PrevExistType
180
181 // TTL defines a period of time after-which the Node should
182 // expire and no longer exist. Values <= 0 are ignored. Given
183 // that the zero-value is ignored, TTL cannot be used to set
184 // a TTL of 0.
185 TTL time.Duration
186
187 // Refresh set to true means a TTL value can be updated
188 // without firing a watch or changing the node value. A
189 // value must not be provided when refreshing a key.
190 Refresh bool
191
192 // Dir specifies whether or not this Node should be created as a directory.
193 Dir bool
194
195 // NoValueOnSuccess specifies whether the response contains the current value of the Node.
196 // If set, the response will only contain the current value when the request fails.
197 NoValueOnSuccess bool
198}
199
200type GetOptions struct {
201 // Recursive defines whether or not all children of the Node
202 // should be returned.
203 Recursive bool
204
205 // Sort instructs the server whether or not to sort the Nodes.
206 // If true, the Nodes are sorted alphabetically by key in
207 // ascending order (A to z). If false (default), the Nodes will
208 // not be sorted and the ordering used should not be considered
209 // predictable.
210 Sort bool
211
212 // Quorum specifies whether it gets the latest committed value that
213 // has been applied in quorum of members, which ensures external
214 // consistency (or linearizability).
215 Quorum bool
216}
217
218type DeleteOptions struct {
219 // PrevValue specifies what the current value of the Node must
220 // be in order for the Delete operation to succeed.
221 //
222 // Leaving this field empty means that the caller wishes to
223 // ignore the current value of the Node. This cannot be used
224 // to compare the Node's current value to an empty string.
225 PrevValue string
226
227 // PrevIndex indicates what the current ModifiedIndex of the
228 // Node must be in order for the Delete operation to succeed.
229 //
230 // If PrevIndex is set to 0 (default), no comparison is made.
231 PrevIndex uint64
232
233 // Recursive defines whether or not all children of the Node
234 // should be deleted. If set to true, all children of the Node
235 // identified by the given key will be deleted. If left unset
236 // or explicitly set to false, only a single Node will be
237 // deleted.
238 Recursive bool
239
240 // Dir specifies whether or not this Node should be removed as a directory.
241 Dir bool
242}
243
244type Watcher interface {
245 // Next blocks until an etcd event occurs, then returns a Response
246 // representing that event. The behavior of Next depends on the
247 // WatcherOptions used to construct the Watcher. Next is designed to
248 // be called repeatedly, each time blocking until a subsequent event
249 // is available.
250 //
251 // If the provided context is cancelled, Next will return a non-nil
252 // error. Any other failures encountered while waiting for the next
253 // event (connection issues, deserialization failures, etc) will
254 // also result in a non-nil error.
255 Next(context.Context) (*Response, error)
256}
257
258type Response struct {
259 // Action is the name of the operation that occurred. Possible values
260 // include get, set, delete, update, create, compareAndSwap,
261 // compareAndDelete and expire.
262 Action string `json:"action"`
263
264 // Node represents the state of the relevant etcd Node.
265 Node *Node `json:"node"`
266
267 // PrevNode represents the previous state of the Node. PrevNode is non-nil
268 // only if the Node existed before the action occurred and the action
269 // caused a change to the Node.
270 PrevNode *Node `json:"prevNode"`
271
272 // Index holds the cluster-level index at the time the Response was generated.
273 // This index is not tied to the Node(s) contained in this Response.
274 Index uint64 `json:"-"`
275
276 // ClusterID holds the cluster-level ID reported by the server. This
277 // should be different for different etcd clusters.
278 ClusterID string `json:"-"`
279}
280
281type Node struct {
282 // Key represents the unique location of this Node (e.g. "/foo/bar").
283 Key string `json:"key"`
284
285 // Dir reports whether node describes a directory.
286 Dir bool `json:"dir,omitempty"`
287
288 // Value is the current data stored on this Node. If this Node
289 // is a directory, Value will be empty.
290 Value string `json:"value"`
291
292 // Nodes holds the children of this Node, only if this Node is a directory.
293 // This slice of will be arbitrarily deep (children, grandchildren, great-
294 // grandchildren, etc.) if a recursive Get or Watch request were made.
295 Nodes Nodes `json:"nodes"`
296
297 // CreatedIndex is the etcd index at-which this Node was created.
298 CreatedIndex uint64 `json:"createdIndex"`
299
300 // ModifiedIndex is the etcd index at-which this Node was last modified.
301 ModifiedIndex uint64 `json:"modifiedIndex"`
302
303 // Expiration is the server side expiration time of the key.
304 Expiration *time.Time `json:"expiration,omitempty"`
305
306 // TTL is the time to live of the key in second.
307 TTL int64 `json:"ttl,omitempty"`
308}
309
310func (n *Node) String() string {
311 return fmt.Sprintf("{Key: %s, CreatedIndex: %d, ModifiedIndex: %d, TTL: %d}", n.Key, n.CreatedIndex, n.ModifiedIndex, n.TTL)
312}
313
314// TTLDuration returns the Node's TTL as a time.Duration object
315func (n *Node) TTLDuration() time.Duration {
316 return time.Duration(n.TTL) * time.Second
317}
318
319type Nodes []*Node
320
321// interfaces for sorting
322
323func (ns Nodes) Len() int { return len(ns) }
324func (ns Nodes) Less(i, j int) bool { return ns[i].Key < ns[j].Key }
325func (ns Nodes) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] }
326
327type httpKeysAPI struct {
328 client httpClient
329 prefix string
330}
331
332func (k *httpKeysAPI) Set(ctx context.Context, key, val string, opts *SetOptions) (*Response, error) {
333 act := &setAction{
334 Prefix: k.prefix,
335 Key: key,
336 Value: val,
337 }
338
339 if opts != nil {
340 act.PrevValue = opts.PrevValue
341 act.PrevIndex = opts.PrevIndex
342 act.PrevExist = opts.PrevExist
343 act.TTL = opts.TTL
344 act.Refresh = opts.Refresh
345 act.Dir = opts.Dir
346 act.NoValueOnSuccess = opts.NoValueOnSuccess
347 }
348
349 doCtx := ctx
350 if act.PrevExist == PrevNoExist {
351 doCtx = context.WithValue(doCtx, &oneShotCtxValue, &oneShotCtxValue)
352 }
353 resp, body, err := k.client.Do(doCtx, act)
354 if err != nil {
355 return nil, err
356 }
357
358 return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
359}
360
361func (k *httpKeysAPI) Create(ctx context.Context, key, val string) (*Response, error) {
362 return k.Set(ctx, key, val, &SetOptions{PrevExist: PrevNoExist})
363}
364
365func (k *httpKeysAPI) CreateInOrder(ctx context.Context, dir, val string, opts *CreateInOrderOptions) (*Response, error) {
366 act := &createInOrderAction{
367 Prefix: k.prefix,
368 Dir: dir,
369 Value: val,
370 }
371
372 if opts != nil {
373 act.TTL = opts.TTL
374 }
375
376 resp, body, err := k.client.Do(ctx, act)
377 if err != nil {
378 return nil, err
379 }
380
381 return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
382}
383
384func (k *httpKeysAPI) Update(ctx context.Context, key, val string) (*Response, error) {
385 return k.Set(ctx, key, val, &SetOptions{PrevExist: PrevExist})
386}
387
388func (k *httpKeysAPI) Delete(ctx context.Context, key string, opts *DeleteOptions) (*Response, error) {
389 act := &deleteAction{
390 Prefix: k.prefix,
391 Key: key,
392 }
393
394 if opts != nil {
395 act.PrevValue = opts.PrevValue
396 act.PrevIndex = opts.PrevIndex
397 act.Dir = opts.Dir
398 act.Recursive = opts.Recursive
399 }
400
401 doCtx := context.WithValue(ctx, &oneShotCtxValue, &oneShotCtxValue)
402 resp, body, err := k.client.Do(doCtx, act)
403 if err != nil {
404 return nil, err
405 }
406
407 return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
408}
409
410func (k *httpKeysAPI) Get(ctx context.Context, key string, opts *GetOptions) (*Response, error) {
411 act := &getAction{
412 Prefix: k.prefix,
413 Key: key,
414 }
415
416 if opts != nil {
417 act.Recursive = opts.Recursive
418 act.Sorted = opts.Sort
419 act.Quorum = opts.Quorum
420 }
421
422 resp, body, err := k.client.Do(ctx, act)
423 if err != nil {
424 return nil, err
425 }
426
427 return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
428}
429
430func (k *httpKeysAPI) Watcher(key string, opts *WatcherOptions) Watcher {
431 act := waitAction{
432 Prefix: k.prefix,
433 Key: key,
434 }
435
436 if opts != nil {
437 act.Recursive = opts.Recursive
438 if opts.AfterIndex > 0 {
439 act.WaitIndex = opts.AfterIndex + 1
440 }
441 }
442
443 return &httpWatcher{
444 client: k.client,
445 nextWait: act,
446 }
447}
448
449type httpWatcher struct {
450 client httpClient
451 nextWait waitAction
452}
453
454func (hw *httpWatcher) Next(ctx context.Context) (*Response, error) {
455 for {
456 httpresp, body, err := hw.client.Do(ctx, &hw.nextWait)
457 if err != nil {
458 return nil, err
459 }
460
461 resp, err := unmarshalHTTPResponse(httpresp.StatusCode, httpresp.Header, body)
462 if err != nil {
463 if err == ErrEmptyBody {
464 continue
465 }
466 return nil, err
467 }
468
469 hw.nextWait.WaitIndex = resp.Node.ModifiedIndex + 1
470 return resp, nil
471 }
472}
473
474// v2KeysURL forms a URL representing the location of a key.
475// The endpoint argument represents the base URL of an etcd
476// server. The prefix is the path needed to route from the
477// provided endpoint's path to the root of the keys API
478// (typically "/v2/keys").
479func v2KeysURL(ep url.URL, prefix, key string) *url.URL {
480 // We concatenate all parts together manually. We cannot use
481 // path.Join because it does not reserve trailing slash.
482 // We call CanonicalURLPath to further cleanup the path.
483 if prefix != "" && prefix[0] != '/' {
484 prefix = "/" + prefix
485 }
486 if key != "" && key[0] != '/' {
487 key = "/" + key
488 }
489 ep.Path = pathutil.CanonicalURLPath(ep.Path + prefix + key)
490 return &ep
491}
492
493type getAction struct {
494 Prefix string
495 Key string
496 Recursive bool
497 Sorted bool
498 Quorum bool
499}
500
501func (g *getAction) HTTPRequest(ep url.URL) *http.Request {
502 u := v2KeysURL(ep, g.Prefix, g.Key)
503
504 params := u.Query()
505 params.Set("recursive", strconv.FormatBool(g.Recursive))
506 params.Set("sorted", strconv.FormatBool(g.Sorted))
507 params.Set("quorum", strconv.FormatBool(g.Quorum))
508 u.RawQuery = params.Encode()
509
510 req, _ := http.NewRequest("GET", u.String(), nil)
511 return req
512}
513
514type waitAction struct {
515 Prefix string
516 Key string
517 WaitIndex uint64
518 Recursive bool
519}
520
521func (w *waitAction) HTTPRequest(ep url.URL) *http.Request {
522 u := v2KeysURL(ep, w.Prefix, w.Key)
523
524 params := u.Query()
525 params.Set("wait", "true")
526 params.Set("waitIndex", strconv.FormatUint(w.WaitIndex, 10))
527 params.Set("recursive", strconv.FormatBool(w.Recursive))
528 u.RawQuery = params.Encode()
529
530 req, _ := http.NewRequest("GET", u.String(), nil)
531 return req
532}
533
534type setAction struct {
535 Prefix string
536 Key string
537 Value string
538 PrevValue string
539 PrevIndex uint64
540 PrevExist PrevExistType
541 TTL time.Duration
542 Refresh bool
543 Dir bool
544 NoValueOnSuccess bool
545}
546
547func (a *setAction) HTTPRequest(ep url.URL) *http.Request {
548 u := v2KeysURL(ep, a.Prefix, a.Key)
549
550 params := u.Query()
551 form := url.Values{}
552
553 // we're either creating a directory or setting a key
554 if a.Dir {
555 params.Set("dir", strconv.FormatBool(a.Dir))
556 } else {
557 // These options are only valid for setting a key
558 if a.PrevValue != "" {
559 params.Set("prevValue", a.PrevValue)
560 }
561 form.Add("value", a.Value)
562 }
563
564 // Options which apply to both setting a key and creating a dir
565 if a.PrevIndex != 0 {
566 params.Set("prevIndex", strconv.FormatUint(a.PrevIndex, 10))
567 }
568 if a.PrevExist != PrevIgnore {
569 params.Set("prevExist", string(a.PrevExist))
570 }
571 if a.TTL > 0 {
572 form.Add("ttl", strconv.FormatUint(uint64(a.TTL.Seconds()), 10))
573 }
574
575 if a.Refresh {
576 form.Add("refresh", "true")
577 }
578 if a.NoValueOnSuccess {
579 params.Set("noValueOnSuccess", strconv.FormatBool(a.NoValueOnSuccess))
580 }
581
582 u.RawQuery = params.Encode()
583 body := strings.NewReader(form.Encode())
584
585 req, _ := http.NewRequest("PUT", u.String(), body)
586 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
587
588 return req
589}
590
591type deleteAction struct {
592 Prefix string
593 Key string
594 PrevValue string
595 PrevIndex uint64
596 Dir bool
597 Recursive bool
598}
599
600func (a *deleteAction) HTTPRequest(ep url.URL) *http.Request {
601 u := v2KeysURL(ep, a.Prefix, a.Key)
602
603 params := u.Query()
604 if a.PrevValue != "" {
605 params.Set("prevValue", a.PrevValue)
606 }
607 if a.PrevIndex != 0 {
608 params.Set("prevIndex", strconv.FormatUint(a.PrevIndex, 10))
609 }
610 if a.Dir {
611 params.Set("dir", "true")
612 }
613 if a.Recursive {
614 params.Set("recursive", "true")
615 }
616 u.RawQuery = params.Encode()
617
618 req, _ := http.NewRequest("DELETE", u.String(), nil)
619 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
620
621 return req
622}
623
624type createInOrderAction struct {
625 Prefix string
626 Dir string
627 Value string
628 TTL time.Duration
629}
630
631func (a *createInOrderAction) HTTPRequest(ep url.URL) *http.Request {
632 u := v2KeysURL(ep, a.Prefix, a.Dir)
633
634 form := url.Values{}
635 form.Add("value", a.Value)
636 if a.TTL > 0 {
637 form.Add("ttl", strconv.FormatUint(uint64(a.TTL.Seconds()), 10))
638 }
639 body := strings.NewReader(form.Encode())
640
641 req, _ := http.NewRequest("POST", u.String(), body)
642 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
643 return req
644}
645
646func unmarshalHTTPResponse(code int, header http.Header, body []byte) (res *Response, err error) {
647 switch code {
648 case http.StatusOK, http.StatusCreated:
649 if len(body) == 0 {
650 return nil, ErrEmptyBody
651 }
652 res, err = unmarshalSuccessfulKeysResponse(header, body)
653 default:
654 err = unmarshalFailedKeysResponse(body)
655 }
656 return res, err
657}
658
659func unmarshalSuccessfulKeysResponse(header http.Header, body []byte) (*Response, error) {
660 var res Response
661 err := codec.NewDecoderBytes(body, new(codec.JsonHandle)).Decode(&res)
662 if err != nil {
663 return nil, ErrInvalidJSON
664 }
665 if header.Get("X-Etcd-Index") != "" {
666 res.Index, err = strconv.ParseUint(header.Get("X-Etcd-Index"), 10, 64)
667 if err != nil {
668 return nil, err
669 }
670 }
671 res.ClusterID = header.Get("X-Etcd-Cluster-ID")
672 return &res, nil
673}
674
675func unmarshalFailedKeysResponse(body []byte) error {
676 var etcdErr Error
677 if err := json.Unmarshal(body, &etcdErr); err != nil {
678 return ErrInvalidJSON
679 }
680 return etcdErr
681}