├── .gitignore ├── LICENSE ├── README.md └── etcd ├── add_child.go ├── add_child_test.go ├── client.go ├── client_test.go ├── cluster.go ├── compare_and_delete.go ├── compare_and_delete_test.go ├── compare_and_swap.go ├── compare_and_swap_test.go ├── debug.go ├── debug_test.go ├── delete.go ├── delete_test.go ├── error.go ├── get.go ├── get_test.go ├── member.go ├── member_test.go ├── options.go ├── requests.go ├── requests_test.go ├── response.generated.go ├── response.go ├── response_test.go ├── set_curl_chan_test.go ├── set_update_create.go ├── set_update_create_test.go ├── shuffle.go ├── version.go ├── watch.go └── watch_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | config.json 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-etcd 2 | 3 | [![GoDoc](https://godoc.org/github.com/coreos/go-etcd/etcd?status.png)](https://godoc.org/github.com/coreos/go-etcd/etcd) 4 | 5 | # DEPRECATED 6 | 7 | etcd now has an [official Go client](https://github.com/coreos/etcd/tree/master/client), which has 8 | a nicer API and better support. 9 | 10 | We strongly suggest you use the official Go client instead of go-etcd in your new projects. 11 | For existing projects, we suggest you migrate to the official Go client. 12 | 13 | ## Usage 14 | 15 | The current version of go-etcd supports etcd v2.0+, if you need support for etcd v0.4 please use go-etcd from the [release-0.4](https://github.com/coreos/go-etcd/tree/release-0.4) branch. 16 | 17 | ``` 18 | package main 19 | 20 | import ( 21 | "log" 22 | 23 | "github.com/coreos/go-etcd/etcd" 24 | ) 25 | 26 | func main() { 27 | machines := []string{"http://127.0.0.1:2379"} 28 | client := etcd.NewClient(machines) 29 | 30 | if _, err := client.Set("/foo", "bar", 0); err != nil { 31 | log.Fatal(err) 32 | } 33 | } 34 | ``` 35 | 36 | ## Install 37 | 38 | ```bash 39 | go get github.com/coreos/go-etcd/etcd 40 | ``` 41 | 42 | ## Caveat 43 | 44 | 1. go-etcd always talks to one member if the member works well. This saves socket resources, and improves efficiency for both client and server side. It doesn't hurt the consistent view of the client because each etcd member has data replication. 45 | 46 | 2. go-etcd does round-robin rotation when it fails to connect the member in use. For example, if the member that go-etcd connects to is hard killed, go-etcd will fail on the first attempt with the killed member, and succeed on the second attempt with another member. The default CheckRetry function does 2*machine_number retries before returning error. 47 | 48 | 3. The default transport in go-etcd sets 1s DialTimeout and 1s TCP keepalive period. A customized transport could be set by calling `Client.SetTransport`. 49 | 50 | 4. Default go-etcd cannot handle the case that the remote server is SIGSTOPed now. TCP keepalive mechanism doesn't help in this scenario because operating system may still send TCP keep-alive packets. We will improve it, but it is not in high priority because we don't see a solid real-life case which server is stopped but connection is alive. 51 | 52 | 5. go-etcd is not thread-safe, and it may have race when switching member or updating cluster. 53 | 54 | 6. go-etcd cannot detect whether the member in use is healthy when doing read requests. If the member is isolated from the cluster, go-etcd may retrieve outdated data. We will improve this. 55 | 56 | ## License 57 | 58 | See LICENSE file. 59 | -------------------------------------------------------------------------------- /etcd/add_child.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | // Add a new directory with a random etcd-generated key under the given path. 4 | func (c *Client) AddChildDir(key string, ttl uint64) (*Response, error) { 5 | raw, err := c.post(key, "", ttl) 6 | 7 | if err != nil { 8 | return nil, err 9 | } 10 | 11 | return raw.Unmarshal() 12 | } 13 | 14 | // Add a new file with a random etcd-generated key under the given path. 15 | func (c *Client) AddChild(key string, value string, ttl uint64) (*Response, error) { 16 | raw, err := c.post(key, value, ttl) 17 | 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | return raw.Unmarshal() 23 | } 24 | -------------------------------------------------------------------------------- /etcd/add_child_test.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import "testing" 4 | 5 | func TestAddChild(t *testing.T) { 6 | c := NewClient(nil) 7 | defer func() { 8 | c.Delete("fooDir", true) 9 | c.Delete("nonexistentDir", true) 10 | }() 11 | 12 | c.CreateDir("fooDir", 5) 13 | 14 | _, err := c.AddChild("fooDir", "v0", 5) 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | 19 | _, err = c.AddChild("fooDir", "v1", 5) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | resp, err := c.Get("fooDir", true, false) 25 | // The child with v0 should proceed the child with v1 because it's added 26 | // earlier, so it should have a lower key. 27 | if !(len(resp.Node.Nodes) == 2 && (resp.Node.Nodes[0].Value == "v0" && resp.Node.Nodes[1].Value == "v1")) { 28 | t.Fatalf("AddChild 1 failed. There should be two chlidren whose values are v0 and v1, respectively."+ 29 | " The response was: %#v", resp) 30 | } 31 | 32 | // Creating a child under a nonexistent directory should succeed. 33 | // The directory should be created. 34 | resp, err = c.AddChild("nonexistentDir", "foo", 5) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | } 39 | 40 | func TestAddChildDir(t *testing.T) { 41 | c := NewClient(nil) 42 | defer func() { 43 | c.Delete("fooDir", true) 44 | c.Delete("nonexistentDir", true) 45 | }() 46 | 47 | c.CreateDir("fooDir", 5) 48 | 49 | _, err := c.AddChildDir("fooDir", 5) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | 54 | _, err = c.AddChildDir("fooDir", 5) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | 59 | resp, err := c.Get("fooDir", true, false) 60 | // The child with v0 should proceed the child with v1 because it's added 61 | // earlier, so it should have a lower key. 62 | if !(len(resp.Node.Nodes) == 2 && (len(resp.Node.Nodes[0].Nodes) == 0 && len(resp.Node.Nodes[1].Nodes) == 0)) { 63 | t.Fatalf("AddChildDir 1 failed. There should be two chlidren whose values are v0 and v1, respectively."+ 64 | " The response was: %#v", resp) 65 | } 66 | 67 | // Creating a child under a nonexistent directory should succeed. 68 | // The directory should be created. 69 | resp, err = c.AddChildDir("nonexistentDir", 5) 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /etcd/client.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "encoding/json" 7 | "errors" 8 | "io" 9 | "io/ioutil" 10 | "math/rand" 11 | "net" 12 | "net/http" 13 | "net/url" 14 | "os" 15 | "path" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | // See SetConsistency for how to use these constants. 21 | const ( 22 | // Using strings rather than iota because the consistency level 23 | // could be persisted to disk, so it'd be better to use 24 | // human-readable values. 25 | STRONG_CONSISTENCY = "STRONG" 26 | WEAK_CONSISTENCY = "WEAK" 27 | ) 28 | 29 | const ( 30 | defaultBufferSize = 10 31 | ) 32 | 33 | func init() { 34 | rand.Seed(int64(time.Now().Nanosecond())) 35 | } 36 | 37 | type Config struct { 38 | CertFile string `json:"certFile"` 39 | KeyFile string `json:"keyFile"` 40 | CaCertFile []string `json:"caCertFiles"` 41 | DialTimeout time.Duration `json:"timeout"` 42 | Consistency string `json:"consistency"` 43 | } 44 | 45 | type credentials struct { 46 | username string 47 | password string 48 | } 49 | 50 | type Client struct { 51 | config Config `json:"config"` 52 | cluster *Cluster `json:"cluster"` 53 | httpClient *http.Client 54 | credentials *credentials 55 | transport *http.Transport 56 | persistence io.Writer 57 | cURLch chan string 58 | // CheckRetry can be used to control the policy for failed requests 59 | // and modify the cluster if needed. 60 | // The client calls it before sending requests again, and 61 | // stops retrying if CheckRetry returns some error. The cases that 62 | // this function needs to handle include no response and unexpected 63 | // http status code of response. 64 | // If CheckRetry is nil, client will call the default one 65 | // `DefaultCheckRetry`. 66 | // Argument cluster is the etcd.Cluster object that these requests have been made on. 67 | // Argument numReqs is the number of http.Requests that have been made so far. 68 | // Argument lastResp is the http.Responses from the last request. 69 | // Argument err is the reason of the failure. 70 | CheckRetry func(cluster *Cluster, numReqs int, 71 | lastResp http.Response, err error) error 72 | } 73 | 74 | // NewClient create a basic client that is configured to be used 75 | // with the given machine list. 76 | func NewClient(machines []string) *Client { 77 | config := Config{ 78 | // default timeout is one second 79 | DialTimeout: time.Second, 80 | Consistency: WEAK_CONSISTENCY, 81 | } 82 | 83 | client := &Client{ 84 | cluster: NewCluster(machines), 85 | config: config, 86 | } 87 | 88 | client.initHTTPClient() 89 | client.saveConfig() 90 | 91 | return client 92 | } 93 | 94 | // NewTLSClient create a basic client with TLS configuration 95 | func NewTLSClient(machines []string, cert, key, caCert string) (*Client, error) { 96 | // overwrite the default machine to use https 97 | if len(machines) == 0 { 98 | machines = []string{"https://127.0.0.1:4001"} 99 | } 100 | 101 | config := Config{ 102 | // default timeout is one second 103 | DialTimeout: time.Second, 104 | Consistency: WEAK_CONSISTENCY, 105 | CertFile: cert, 106 | KeyFile: key, 107 | CaCertFile: make([]string, 0), 108 | } 109 | 110 | client := &Client{ 111 | cluster: NewCluster(machines), 112 | config: config, 113 | } 114 | 115 | err := client.initHTTPSClient(cert, key) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | err = client.AddRootCA(caCert) 121 | 122 | client.saveConfig() 123 | 124 | return client, nil 125 | } 126 | 127 | // NewClientFromFile creates a client from a given file path. 128 | // The given file is expected to use the JSON format. 129 | func NewClientFromFile(fpath string) (*Client, error) { 130 | fi, err := os.Open(fpath) 131 | if err != nil { 132 | return nil, err 133 | } 134 | 135 | defer func() { 136 | if err := fi.Close(); err != nil { 137 | panic(err) 138 | } 139 | }() 140 | 141 | return NewClientFromReader(fi) 142 | } 143 | 144 | // NewClientFromReader creates a Client configured from a given reader. 145 | // The configuration is expected to use the JSON format. 146 | func NewClientFromReader(reader io.Reader) (*Client, error) { 147 | c := new(Client) 148 | 149 | b, err := ioutil.ReadAll(reader) 150 | if err != nil { 151 | return nil, err 152 | } 153 | 154 | err = json.Unmarshal(b, c) 155 | if err != nil { 156 | return nil, err 157 | } 158 | if c.config.CertFile == "" { 159 | c.initHTTPClient() 160 | } else { 161 | err = c.initHTTPSClient(c.config.CertFile, c.config.KeyFile) 162 | } 163 | 164 | if err != nil { 165 | return nil, err 166 | } 167 | 168 | for _, caCert := range c.config.CaCertFile { 169 | if err := c.AddRootCA(caCert); err != nil { 170 | return nil, err 171 | } 172 | } 173 | 174 | return c, nil 175 | } 176 | 177 | // Override the Client's HTTP Transport object 178 | func (c *Client) SetTransport(tr *http.Transport) { 179 | c.httpClient.Transport = tr 180 | c.transport = tr 181 | } 182 | 183 | func (c *Client) SetCredentials(username, password string) { 184 | c.credentials = &credentials{username, password} 185 | } 186 | 187 | func (c *Client) Close() { 188 | c.transport.DisableKeepAlives = true 189 | c.transport.CloseIdleConnections() 190 | } 191 | 192 | // initHTTPClient initializes a HTTP client for etcd client 193 | func (c *Client) initHTTPClient() { 194 | c.transport = &http.Transport{ 195 | Dial: c.DefaultDial, 196 | TLSClientConfig: &tls.Config{ 197 | InsecureSkipVerify: true, 198 | }, 199 | } 200 | c.httpClient = &http.Client{Transport: c.transport} 201 | } 202 | 203 | // initHTTPClient initializes a HTTPS client for etcd client 204 | func (c *Client) initHTTPSClient(cert, key string) error { 205 | if cert == "" || key == "" { 206 | return errors.New("Require both cert and key path") 207 | } 208 | 209 | tlsCert, err := tls.LoadX509KeyPair(cert, key) 210 | if err != nil { 211 | return err 212 | } 213 | 214 | tlsConfig := &tls.Config{ 215 | Certificates: []tls.Certificate{tlsCert}, 216 | InsecureSkipVerify: true, 217 | } 218 | 219 | c.transport = &http.Transport{ 220 | TLSClientConfig: tlsConfig, 221 | Dial: c.DefaultDial, 222 | } 223 | 224 | c.httpClient = &http.Client{Transport: c.transport} 225 | return nil 226 | } 227 | 228 | // SetPersistence sets a writer to which the config will be 229 | // written every time it's changed. 230 | func (c *Client) SetPersistence(writer io.Writer) { 231 | c.persistence = writer 232 | } 233 | 234 | // SetConsistency changes the consistency level of the client. 235 | // 236 | // When consistency is set to STRONG_CONSISTENCY, all requests, 237 | // including GET, are sent to the leader. This means that, assuming 238 | // the absence of leader failures, GET requests are guaranteed to see 239 | // the changes made by previous requests. 240 | // 241 | // When consistency is set to WEAK_CONSISTENCY, other requests 242 | // are still sent to the leader, but GET requests are sent to a 243 | // random server from the server pool. This reduces the read 244 | // load on the leader, but it's not guaranteed that the GET requests 245 | // will see changes made by previous requests (they might have not 246 | // yet been committed on non-leader servers). 247 | func (c *Client) SetConsistency(consistency string) error { 248 | if !(consistency == STRONG_CONSISTENCY || consistency == WEAK_CONSISTENCY) { 249 | return errors.New("The argument must be either STRONG_CONSISTENCY or WEAK_CONSISTENCY.") 250 | } 251 | c.config.Consistency = consistency 252 | return nil 253 | } 254 | 255 | // Sets the DialTimeout value 256 | func (c *Client) SetDialTimeout(d time.Duration) { 257 | c.config.DialTimeout = d 258 | } 259 | 260 | // AddRootCA adds a root CA cert for the etcd client 261 | func (c *Client) AddRootCA(caCert string) error { 262 | if c.httpClient == nil { 263 | return errors.New("Client has not been initialized yet!") 264 | } 265 | 266 | certBytes, err := ioutil.ReadFile(caCert) 267 | if err != nil { 268 | return err 269 | } 270 | 271 | tr, ok := c.httpClient.Transport.(*http.Transport) 272 | 273 | if !ok { 274 | panic("AddRootCA(): Transport type assert should not fail") 275 | } 276 | 277 | if tr.TLSClientConfig.RootCAs == nil { 278 | caCertPool := x509.NewCertPool() 279 | ok = caCertPool.AppendCertsFromPEM(certBytes) 280 | if ok { 281 | tr.TLSClientConfig.RootCAs = caCertPool 282 | } 283 | tr.TLSClientConfig.InsecureSkipVerify = false 284 | } else { 285 | ok = tr.TLSClientConfig.RootCAs.AppendCertsFromPEM(certBytes) 286 | } 287 | 288 | if !ok { 289 | err = errors.New("Unable to load caCert") 290 | } 291 | 292 | c.config.CaCertFile = append(c.config.CaCertFile, caCert) 293 | c.saveConfig() 294 | 295 | return err 296 | } 297 | 298 | // SetCluster updates cluster information using the given machine list. 299 | func (c *Client) SetCluster(machines []string) bool { 300 | success := c.internalSyncCluster(machines) 301 | return success 302 | } 303 | 304 | func (c *Client) GetCluster() []string { 305 | return c.cluster.Machines 306 | } 307 | 308 | // SyncCluster updates the cluster information using the internal machine list. 309 | // If no members are found, the intenral machine list is left untouched. 310 | func (c *Client) SyncCluster() bool { 311 | return c.internalSyncCluster(c.cluster.Machines) 312 | } 313 | 314 | // internalSyncCluster syncs cluster information using the given machine list. 315 | func (c *Client) internalSyncCluster(machines []string) bool { 316 | // comma-separated list of machines in the cluster. 317 | members := "" 318 | 319 | for _, machine := range machines { 320 | httpPath := c.createHttpPath(machine, path.Join(version, "members")) 321 | resp, err := c.httpClient.Get(httpPath) 322 | if err != nil { 323 | // try another machine in the cluster 324 | continue 325 | } 326 | 327 | if resp.StatusCode != http.StatusOK { // fall-back to old endpoint 328 | httpPath := c.createHttpPath(machine, path.Join(version, "machines")) 329 | resp, err := c.httpClient.Get(httpPath) 330 | if err != nil { 331 | // try another machine in the cluster 332 | continue 333 | } 334 | b, err := ioutil.ReadAll(resp.Body) 335 | resp.Body.Close() 336 | if err != nil { 337 | // try another machine in the cluster 338 | continue 339 | } 340 | members = string(b) 341 | } else { 342 | b, err := ioutil.ReadAll(resp.Body) 343 | resp.Body.Close() 344 | if err != nil { 345 | // try another machine in the cluster 346 | continue 347 | } 348 | 349 | var mCollection memberCollection 350 | if err := json.Unmarshal(b, &mCollection); err != nil { 351 | // try another machine 352 | continue 353 | } 354 | 355 | urls := make([]string, 0) 356 | for _, m := range mCollection { 357 | urls = append(urls, m.ClientURLs...) 358 | } 359 | 360 | members = strings.Join(urls, ",") 361 | } 362 | 363 | // We should never do an empty cluster update. 364 | if members == "" { 365 | continue 366 | } 367 | 368 | // update Machines List 369 | c.cluster.updateFromStr(members) 370 | logger.Debug("sync.machines ", c.cluster.Machines) 371 | c.saveConfig() 372 | return true 373 | } 374 | 375 | return false 376 | } 377 | 378 | // createHttpPath creates a complete HTTP URL. 379 | // serverName should contain both the host name and a port number, if any. 380 | func (c *Client) createHttpPath(serverName string, _path string) string { 381 | u, err := url.Parse(serverName) 382 | if err != nil { 383 | panic(err) 384 | } 385 | 386 | u.Path = path.Join(u.Path, _path) 387 | 388 | if u.Scheme == "" { 389 | u.Scheme = "http" 390 | } 391 | return u.String() 392 | } 393 | 394 | // DefaultDial attempts to open a TCP connection to the provided address, explicitly 395 | // enabling keep-alives with a one-second interval. 396 | func (c *Client) DefaultDial(network, addr string) (net.Conn, error) { 397 | dialer := net.Dialer{ 398 | Timeout: c.config.DialTimeout, 399 | KeepAlive: time.Second, 400 | } 401 | 402 | return dialer.Dial(network, addr) 403 | } 404 | 405 | func (c *Client) OpenCURL() { 406 | c.cURLch = make(chan string, defaultBufferSize) 407 | } 408 | 409 | func (c *Client) CloseCURL() { 410 | c.cURLch = nil 411 | } 412 | 413 | func (c *Client) sendCURL(command string) { 414 | go func() { 415 | select { 416 | case c.cURLch <- command: 417 | default: 418 | } 419 | }() 420 | } 421 | 422 | func (c *Client) RecvCURL() string { 423 | return <-c.cURLch 424 | } 425 | 426 | // saveConfig saves the current config using c.persistence. 427 | func (c *Client) saveConfig() error { 428 | if c.persistence != nil { 429 | b, err := json.Marshal(c) 430 | if err != nil { 431 | return err 432 | } 433 | 434 | _, err = c.persistence.Write(b) 435 | if err != nil { 436 | return err 437 | } 438 | } 439 | 440 | return nil 441 | } 442 | 443 | // MarshalJSON implements the Marshaller interface 444 | // as defined by the standard JSON package. 445 | func (c *Client) MarshalJSON() ([]byte, error) { 446 | b, err := json.Marshal(struct { 447 | Config Config `json:"config"` 448 | Cluster *Cluster `json:"cluster"` 449 | }{ 450 | Config: c.config, 451 | Cluster: c.cluster, 452 | }) 453 | 454 | if err != nil { 455 | return nil, err 456 | } 457 | 458 | return b, nil 459 | } 460 | 461 | // UnmarshalJSON implements the Unmarshaller interface 462 | // as defined by the standard JSON package. 463 | func (c *Client) UnmarshalJSON(b []byte) error { 464 | temp := struct { 465 | Config Config `json:"config"` 466 | Cluster *Cluster `json:"cluster"` 467 | }{} 468 | err := json.Unmarshal(b, &temp) 469 | if err != nil { 470 | return err 471 | } 472 | 473 | c.cluster = temp.Cluster 474 | c.config = temp.Config 475 | return nil 476 | } 477 | -------------------------------------------------------------------------------- /etcd/client_test.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net" 7 | "net/url" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | // To pass this test, we need to create a cluster of 3 machines 13 | // The server should be listening on localhost:4001, 4002, 4003 14 | func TestSync(t *testing.T) { 15 | fmt.Println("Make sure there are three nodes at 0.0.0.0:4001-4003") 16 | 17 | // Explicit trailing slash to ensure this doesn't reproduce: 18 | // https://github.com/coreos/go-etcd/issues/82 19 | c := NewClient([]string{"http://127.0.0.1:4001/"}) 20 | 21 | success := c.SyncCluster() 22 | if !success { 23 | t.Fatal("cannot sync machines") 24 | } 25 | 26 | for _, m := range c.GetCluster() { 27 | u, err := url.Parse(m) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | if u.Scheme != "http" { 32 | t.Fatal("scheme must be http") 33 | } 34 | 35 | host, _, err := net.SplitHostPort(u.Host) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | if host != "localhost" { 40 | t.Fatal("Host must be localhost") 41 | } 42 | } 43 | 44 | badMachines := []string{"abc", "edef"} 45 | 46 | success = c.SetCluster(badMachines) 47 | 48 | if success { 49 | t.Fatal("should not sync on bad machines") 50 | } 51 | 52 | goodMachines := []string{"127.0.0.1:4002"} 53 | 54 | success = c.SetCluster(goodMachines) 55 | 56 | if !success { 57 | t.Fatal("cannot sync machines") 58 | } else { 59 | fmt.Println(c.cluster.Machines) 60 | } 61 | 62 | } 63 | 64 | func TestPersistence(t *testing.T) { 65 | c := NewClient(nil) 66 | c.SyncCluster() 67 | 68 | fo, err := os.Create("config.json") 69 | if err != nil { 70 | t.Fatal(err) 71 | } 72 | defer func() { 73 | if err := fo.Close(); err != nil { 74 | panic(err) 75 | } 76 | }() 77 | 78 | c.SetPersistence(fo) 79 | err = c.saveConfig() 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | 84 | c2, err := NewClientFromFile("config.json") 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | 89 | // Verify that the two clients have the same config 90 | b1, _ := json.Marshal(c) 91 | b2, _ := json.Marshal(c2) 92 | 93 | if string(b1) != string(b2) { 94 | t.Fatalf("The two configs should be equal!") 95 | } 96 | } 97 | 98 | func TestClientRetry(t *testing.T) { 99 | c := NewClient([]string{"http://strange", "http://127.0.0.1:4001"}) 100 | // use first endpoint as the picked url 101 | c.cluster.picked = 0 102 | if _, err := c.Set("foo", "bar", 5); err != nil { 103 | t.Fatal(err) 104 | } 105 | if _, err := c.Delete("foo", true); err != nil { 106 | t.Fatal(err) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /etcd/cluster.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "math/rand" 5 | "strings" 6 | "sync" 7 | ) 8 | 9 | type Cluster struct { 10 | Leader string `json:"leader"` 11 | Machines []string `json:"machines"` 12 | picked int 13 | mu sync.RWMutex 14 | } 15 | 16 | func NewCluster(machines []string) *Cluster { 17 | // if an empty slice was sent in then just assume HTTP 4001 on localhost 18 | if len(machines) == 0 { 19 | machines = []string{"http://127.0.0.1:4001"} 20 | } 21 | 22 | machines = shuffleStringSlice(machines) 23 | logger.Debug("Shuffle cluster machines", machines) 24 | // default leader and machines 25 | return &Cluster{ 26 | Leader: "", 27 | Machines: machines, 28 | picked: rand.Intn(len(machines)), 29 | } 30 | } 31 | 32 | func (cl *Cluster) failure() { 33 | cl.mu.Lock() 34 | defer cl.mu.Unlock() 35 | cl.picked = (cl.picked + 1) % len(cl.Machines) 36 | } 37 | 38 | func (cl *Cluster) pick() string { 39 | cl.mu.Lock() 40 | defer cl.mu.Unlock() 41 | return cl.Machines[cl.picked] 42 | } 43 | 44 | func (cl *Cluster) updateFromStr(machines string) { 45 | cl.mu.Lock() 46 | defer cl.mu.Unlock() 47 | 48 | cl.Machines = strings.Split(machines, ",") 49 | for i := range cl.Machines { 50 | cl.Machines[i] = strings.TrimSpace(cl.Machines[i]) 51 | } 52 | cl.Machines = shuffleStringSlice(cl.Machines) 53 | cl.picked = rand.Intn(len(cl.Machines)) 54 | } 55 | -------------------------------------------------------------------------------- /etcd/compare_and_delete.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import "fmt" 4 | 5 | func (c *Client) CompareAndDelete(key string, prevValue string, prevIndex uint64) (*Response, error) { 6 | raw, err := c.RawCompareAndDelete(key, prevValue, prevIndex) 7 | if err != nil { 8 | return nil, err 9 | } 10 | 11 | return raw.Unmarshal() 12 | } 13 | 14 | func (c *Client) RawCompareAndDelete(key string, prevValue string, prevIndex uint64) (*RawResponse, error) { 15 | if prevValue == "" && prevIndex == 0 { 16 | return nil, fmt.Errorf("You must give either prevValue or prevIndex.") 17 | } 18 | 19 | options := Options{} 20 | if prevValue != "" { 21 | options["prevValue"] = prevValue 22 | } 23 | if prevIndex != 0 { 24 | options["prevIndex"] = prevIndex 25 | } 26 | 27 | raw, err := c.delete(key, options) 28 | 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return raw, err 34 | } 35 | -------------------------------------------------------------------------------- /etcd/compare_and_delete_test.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCompareAndDelete(t *testing.T) { 8 | c := NewClient(nil) 9 | defer func() { 10 | c.Delete("foo", true) 11 | }() 12 | 13 | c.Set("foo", "bar", 5) 14 | 15 | // This should succeed an correct prevValue 16 | resp, err := c.CompareAndDelete("foo", "bar", 0) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) { 21 | t.Fatalf("CompareAndDelete 1 prevNode failed: %#v", resp) 22 | } 23 | 24 | resp, _ = c.Set("foo", "bar", 5) 25 | // This should fail because it gives an incorrect prevValue 26 | _, err = c.CompareAndDelete("foo", "xxx", 0) 27 | if err == nil { 28 | t.Fatalf("CompareAndDelete 2 should have failed. The response is: %#v", resp) 29 | } 30 | 31 | // This should succeed because it gives an correct prevIndex 32 | resp, err = c.CompareAndDelete("foo", "", resp.Node.ModifiedIndex) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) { 37 | t.Fatalf("CompareAndSwap 3 prevNode failed: %#v", resp) 38 | } 39 | 40 | c.Set("foo", "bar", 5) 41 | // This should fail because it gives an incorrect prevIndex 42 | resp, err = c.CompareAndDelete("foo", "", 29817514) 43 | if err == nil { 44 | t.Fatalf("CompareAndDelete 4 should have failed. The response is: %#v", resp) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /etcd/compare_and_swap.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import "fmt" 4 | 5 | func (c *Client) CompareAndSwap(key string, value string, ttl uint64, 6 | prevValue string, prevIndex uint64) (*Response, error) { 7 | raw, err := c.RawCompareAndSwap(key, value, ttl, prevValue, prevIndex) 8 | if err != nil { 9 | return nil, err 10 | } 11 | 12 | return raw.Unmarshal() 13 | } 14 | 15 | func (c *Client) RawCompareAndSwap(key string, value string, ttl uint64, 16 | prevValue string, prevIndex uint64) (*RawResponse, error) { 17 | if prevValue == "" && prevIndex == 0 { 18 | return nil, fmt.Errorf("You must give either prevValue or prevIndex.") 19 | } 20 | 21 | options := Options{} 22 | if prevValue != "" { 23 | options["prevValue"] = prevValue 24 | } 25 | if prevIndex != 0 { 26 | options["prevIndex"] = prevIndex 27 | } 28 | 29 | raw, err := c.put(key, value, ttl, options) 30 | 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | return raw, err 36 | } 37 | -------------------------------------------------------------------------------- /etcd/compare_and_swap_test.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCompareAndSwap(t *testing.T) { 8 | c := NewClient(nil) 9 | defer func() { 10 | c.Delete("foo", true) 11 | }() 12 | 13 | c.Set("foo", "bar", 5) 14 | 15 | // This should succeed 16 | resp, err := c.CompareAndSwap("foo", "bar2", 5, "bar", 0) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | if !(resp.Node.Value == "bar2" && resp.Node.Key == "/foo" && resp.Node.TTL == 5) { 21 | t.Fatalf("CompareAndSwap 1 failed: %#v", resp) 22 | } 23 | 24 | if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) { 25 | t.Fatalf("CompareAndSwap 1 prevNode failed: %#v", resp) 26 | } 27 | 28 | // This should fail because it gives an incorrect prevValue 29 | resp, err = c.CompareAndSwap("foo", "bar3", 5, "xxx", 0) 30 | if err == nil { 31 | t.Fatalf("CompareAndSwap 2 should have failed. The response is: %#v", resp) 32 | } 33 | 34 | resp, err = c.Set("foo", "bar", 5) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | 39 | // This should succeed 40 | resp, err = c.CompareAndSwap("foo", "bar2", 5, "", resp.Node.ModifiedIndex) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | if !(resp.Node.Value == "bar2" && resp.Node.Key == "/foo" && resp.Node.TTL == 5) { 45 | t.Fatalf("CompareAndSwap 3 failed: %#v", resp) 46 | } 47 | 48 | if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) { 49 | t.Fatalf("CompareAndSwap 3 prevNode failed: %#v", resp) 50 | } 51 | 52 | // This should fail because it gives an incorrect prevIndex 53 | resp, err = c.CompareAndSwap("foo", "bar3", 5, "", 29817514) 54 | if err == nil { 55 | t.Fatalf("CompareAndSwap 4 should have failed. The response is: %#v", resp) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /etcd/debug.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "strings" 8 | ) 9 | 10 | var logger *etcdLogger 11 | 12 | func SetLogger(l *log.Logger) { 13 | logger = &etcdLogger{l} 14 | } 15 | 16 | func GetLogger() *log.Logger { 17 | return logger.log 18 | } 19 | 20 | type etcdLogger struct { 21 | log *log.Logger 22 | } 23 | 24 | func (p *etcdLogger) Debug(args ...interface{}) { 25 | msg := "DEBUG: " + fmt.Sprint(args...) 26 | p.log.Println(msg) 27 | } 28 | 29 | func (p *etcdLogger) Debugf(f string, args ...interface{}) { 30 | msg := "DEBUG: " + fmt.Sprintf(f, args...) 31 | // Append newline if necessary 32 | if !strings.HasSuffix(msg, "\n") { 33 | msg = msg + "\n" 34 | } 35 | p.log.Print(msg) 36 | } 37 | 38 | func (p *etcdLogger) Warning(args ...interface{}) { 39 | msg := "WARNING: " + fmt.Sprint(args...) 40 | p.log.Println(msg) 41 | } 42 | 43 | func (p *etcdLogger) Warningf(f string, args ...interface{}) { 44 | msg := "WARNING: " + fmt.Sprintf(f, args...) 45 | // Append newline if necessary 46 | if !strings.HasSuffix(msg, "\n") { 47 | msg = msg + "\n" 48 | } 49 | p.log.Print(msg) 50 | } 51 | 52 | func init() { 53 | // Default logger uses the go default log. 54 | SetLogger(log.New(ioutil.Discard, "go-etcd", log.LstdFlags)) 55 | } 56 | -------------------------------------------------------------------------------- /etcd/debug_test.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type Foo struct{} 8 | type Bar struct { 9 | one string 10 | two int 11 | } 12 | 13 | // Tests that logs don't panic with arbitrary interfaces 14 | func TestDebug(t *testing.T) { 15 | f := &Foo{} 16 | b := &Bar{"asfd", 3} 17 | for _, test := range []interface{}{ 18 | 1234, 19 | "asdf", 20 | f, 21 | b, 22 | } { 23 | logger.Debug(test) 24 | logger.Debugf("something, %s", test) 25 | logger.Warning(test) 26 | logger.Warningf("something, %s", test) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /etcd/delete.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | // Delete deletes the given key. 4 | // 5 | // When recursive set to false, if the key points to a 6 | // directory the method will fail. 7 | // 8 | // When recursive set to true, if the key points to a file, 9 | // the file will be deleted; if the key points to a directory, 10 | // then everything under the directory (including all child directories) 11 | // will be deleted. 12 | func (c *Client) Delete(key string, recursive bool) (*Response, error) { 13 | raw, err := c.RawDelete(key, recursive, false) 14 | 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | return raw.Unmarshal() 20 | } 21 | 22 | // DeleteDir deletes an empty directory or a key value pair 23 | func (c *Client) DeleteDir(key string) (*Response, error) { 24 | raw, err := c.RawDelete(key, false, true) 25 | 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | return raw.Unmarshal() 31 | } 32 | 33 | func (c *Client) RawDelete(key string, recursive bool, dir bool) (*RawResponse, error) { 34 | ops := Options{ 35 | "recursive": recursive, 36 | "dir": dir, 37 | } 38 | 39 | return c.delete(key, ops) 40 | } 41 | -------------------------------------------------------------------------------- /etcd/delete_test.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDelete(t *testing.T) { 8 | c := NewClient(nil) 9 | defer func() { 10 | c.Delete("foo", true) 11 | }() 12 | 13 | c.Set("foo", "bar", 5) 14 | resp, err := c.Delete("foo", false) 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | 19 | if !(resp.Node.Value == "") { 20 | t.Fatalf("Delete failed with %s", resp.Node.Value) 21 | } 22 | 23 | if !(resp.PrevNode.Value == "bar") { 24 | t.Fatalf("Delete PrevNode failed with %s", resp.Node.Value) 25 | } 26 | 27 | resp, err = c.Delete("foo", false) 28 | if err == nil { 29 | t.Fatalf("Delete should have failed because the key foo did not exist. "+ 30 | "The response was: %v", resp) 31 | } 32 | } 33 | 34 | func TestDeleteAll(t *testing.T) { 35 | c := NewClient(nil) 36 | defer func() { 37 | c.Delete("foo", true) 38 | c.Delete("fooDir", true) 39 | }() 40 | 41 | c.SetDir("foo", 5) 42 | // test delete an empty dir 43 | resp, err := c.DeleteDir("foo") 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | 48 | if !(resp.Node.Value == "") { 49 | t.Fatalf("DeleteAll 1 failed: %#v", resp) 50 | } 51 | 52 | if !(resp.PrevNode.Dir == true && resp.PrevNode.Value == "") { 53 | t.Fatalf("DeleteAll 1 PrevNode failed: %#v", resp) 54 | } 55 | 56 | c.CreateDir("fooDir", 5) 57 | c.Set("fooDir/foo", "bar", 5) 58 | _, err = c.DeleteDir("fooDir") 59 | if err == nil { 60 | t.Fatal("should not able to delete a non-empty dir with deletedir") 61 | } 62 | 63 | resp, err = c.Delete("fooDir", true) 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | 68 | if !(resp.Node.Value == "") { 69 | t.Fatalf("DeleteAll 2 failed: %#v", resp) 70 | } 71 | 72 | if !(resp.PrevNode.Dir == true && resp.PrevNode.Value == "") { 73 | t.Fatalf("DeleteAll 2 PrevNode failed: %#v", resp) 74 | } 75 | 76 | resp, err = c.Delete("foo", true) 77 | if err == nil { 78 | t.Fatalf("DeleteAll should have failed because the key foo did not exist. "+ 79 | "The response was: %v", resp) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /etcd/error.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | const ( 9 | ErrCodeEtcdNotReachable = 501 10 | ErrCodeUnhandledHTTPStatus = 502 11 | ) 12 | 13 | var ( 14 | errorMap = map[int]string{ 15 | ErrCodeEtcdNotReachable: "All the given peers are not reachable", 16 | } 17 | ) 18 | 19 | type EtcdError struct { 20 | ErrorCode int `json:"errorCode"` 21 | Message string `json:"message"` 22 | Cause string `json:"cause,omitempty"` 23 | Index uint64 `json:"index"` 24 | } 25 | 26 | func (e EtcdError) Error() string { 27 | return fmt.Sprintf("%v: %v (%v) [%v]", e.ErrorCode, e.Message, e.Cause, e.Index) 28 | } 29 | 30 | func newError(errorCode int, cause string, index uint64) *EtcdError { 31 | return &EtcdError{ 32 | ErrorCode: errorCode, 33 | Message: errorMap[errorCode], 34 | Cause: cause, 35 | Index: index, 36 | } 37 | } 38 | 39 | func handleError(b []byte) error { 40 | etcdErr := new(EtcdError) 41 | 42 | err := json.Unmarshal(b, etcdErr) 43 | if err != nil { 44 | logger.Warningf("cannot unmarshal etcd error: %v", err) 45 | return err 46 | } 47 | 48 | return etcdErr 49 | } 50 | -------------------------------------------------------------------------------- /etcd/get.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | // Get gets the file or directory associated with the given key. 4 | // If the key points to a directory, files and directories under 5 | // it will be returned in sorted or unsorted order, depending on 6 | // the sort flag. 7 | // If recursive is set to false, contents under child directories 8 | // will not be returned. 9 | // If recursive is set to true, all the contents will be returned. 10 | func (c *Client) Get(key string, sort, recursive bool) (*Response, error) { 11 | raw, err := c.RawGet(key, sort, recursive) 12 | 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | return raw.Unmarshal() 18 | } 19 | 20 | func (c *Client) RawGet(key string, sort, recursive bool) (*RawResponse, error) { 21 | var q bool 22 | if c.config.Consistency == STRONG_CONSISTENCY { 23 | q = true 24 | } 25 | ops := Options{ 26 | "recursive": recursive, 27 | "sorted": sort, 28 | "quorum": q, 29 | } 30 | 31 | return c.get(key, ops) 32 | } 33 | -------------------------------------------------------------------------------- /etcd/get_test.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | // cleanNode scrubs Expiration, ModifiedIndex and CreatedIndex of a node. 9 | func cleanNode(n *Node) { 10 | n.Expiration = nil 11 | n.ModifiedIndex = 0 12 | n.CreatedIndex = 0 13 | } 14 | 15 | // cleanResult scrubs a result object two levels deep of Expiration, 16 | // ModifiedIndex and CreatedIndex. 17 | func cleanResult(result *Response) { 18 | // TODO(philips): make this recursive. 19 | cleanNode(result.Node) 20 | for i, _ := range result.Node.Nodes { 21 | cleanNode(result.Node.Nodes[i]) 22 | for j, _ := range result.Node.Nodes[i].Nodes { 23 | cleanNode(result.Node.Nodes[i].Nodes[j]) 24 | } 25 | } 26 | } 27 | 28 | func TestGet(t *testing.T) { 29 | c := NewClient(nil) 30 | defer func() { 31 | c.Delete("foo", true) 32 | }() 33 | 34 | c.Set("foo", "bar", 5) 35 | 36 | result, err := c.Get("foo", false, false) 37 | 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | 42 | if result.Node.Key != "/foo" || result.Node.Value != "bar" { 43 | t.Fatalf("Get failed with %s %s %v", result.Node.Key, result.Node.Value, result.Node.TTL) 44 | } 45 | 46 | result, err = c.Get("goo", false, false) 47 | if err == nil { 48 | t.Fatalf("should not be able to get non-exist key") 49 | } 50 | } 51 | 52 | func TestGetAll(t *testing.T) { 53 | c := NewClient(nil) 54 | defer func() { 55 | c.Delete("fooDir", true) 56 | }() 57 | 58 | c.CreateDir("fooDir", 5) 59 | c.Set("fooDir/k0", "v0", 5) 60 | c.Set("fooDir/k1", "v1", 5) 61 | 62 | // Return kv-pairs in sorted order 63 | result, err := c.Get("fooDir", true, false) 64 | 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | 69 | expected := Nodes{ 70 | &Node{ 71 | Key: "/fooDir/k0", 72 | Value: "v0", 73 | TTL: 5, 74 | }, 75 | &Node{ 76 | Key: "/fooDir/k1", 77 | Value: "v1", 78 | TTL: 5, 79 | }, 80 | } 81 | 82 | cleanResult(result) 83 | 84 | if !reflect.DeepEqual(result.Node.Nodes, expected) { 85 | t.Fatalf("(actual) %v != (expected) %v", result.Node.Nodes, expected) 86 | } 87 | 88 | // Test the `recursive` option 89 | c.CreateDir("fooDir/childDir", 5) 90 | c.Set("fooDir/childDir/k2", "v2", 5) 91 | 92 | // Return kv-pairs in sorted order 93 | result, err = c.Get("fooDir", true, true) 94 | 95 | cleanResult(result) 96 | 97 | if err != nil { 98 | t.Fatal(err) 99 | } 100 | 101 | expected = Nodes{ 102 | &Node{ 103 | Key: "/fooDir/childDir", 104 | Dir: true, 105 | Nodes: Nodes{ 106 | &Node{ 107 | Key: "/fooDir/childDir/k2", 108 | Value: "v2", 109 | TTL: 5, 110 | }, 111 | }, 112 | TTL: 5, 113 | }, 114 | &Node{ 115 | Key: "/fooDir/k0", 116 | Value: "v0", 117 | TTL: 5, 118 | }, 119 | &Node{ 120 | Key: "/fooDir/k1", 121 | Value: "v1", 122 | TTL: 5, 123 | }, 124 | } 125 | 126 | cleanResult(result) 127 | 128 | if !reflect.DeepEqual(result.Node.Nodes, expected) { 129 | t.Fatalf("(actual) %v != (expected) %v", result.Node.Nodes, expected) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /etcd/member.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import "encoding/json" 4 | 5 | type Member struct { 6 | ID string `json:"id"` 7 | Name string `json:"name"` 8 | PeerURLs []string `json:"peerURLs"` 9 | ClientURLs []string `json:"clientURLs"` 10 | } 11 | 12 | type memberCollection []Member 13 | 14 | func (c *memberCollection) UnmarshalJSON(data []byte) error { 15 | d := struct { 16 | Members []Member 17 | }{} 18 | 19 | if err := json.Unmarshal(data, &d); err != nil { 20 | return err 21 | } 22 | 23 | if d.Members == nil { 24 | *c = make([]Member, 0) 25 | return nil 26 | } 27 | 28 | *c = d.Members 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /etcd/member_test.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "encoding/json" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestMemberCollectionUnmarshal(t *testing.T) { 10 | tests := []struct { 11 | body []byte 12 | want memberCollection 13 | }{ 14 | { 15 | body: []byte(`{"members":[]}`), 16 | want: memberCollection([]Member{}), 17 | }, 18 | { 19 | body: []byte(`{"members":[{"id":"2745e2525fce8fe","peerURLs":["http://127.0.0.1:7003"],"name":"node3","clientURLs":["http://127.0.0.1:4003"]},{"id":"42134f434382925","peerURLs":["http://127.0.0.1:2380","http://127.0.0.1:7001"],"name":"node1","clientURLs":["http://127.0.0.1:2379","http://127.0.0.1:4001"]},{"id":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"],"name":"node2","clientURLs":["http://127.0.0.1:4002"]}]}`), 20 | want: memberCollection( 21 | []Member{ 22 | { 23 | ID: "2745e2525fce8fe", 24 | Name: "node3", 25 | PeerURLs: []string{ 26 | "http://127.0.0.1:7003", 27 | }, 28 | ClientURLs: []string{ 29 | "http://127.0.0.1:4003", 30 | }, 31 | }, 32 | { 33 | ID: "42134f434382925", 34 | Name: "node1", 35 | PeerURLs: []string{ 36 | "http://127.0.0.1:2380", 37 | "http://127.0.0.1:7001", 38 | }, 39 | ClientURLs: []string{ 40 | "http://127.0.0.1:2379", 41 | "http://127.0.0.1:4001", 42 | }, 43 | }, 44 | { 45 | ID: "94088180e21eb87b", 46 | Name: "node2", 47 | PeerURLs: []string{ 48 | "http://127.0.0.1:7002", 49 | }, 50 | ClientURLs: []string{ 51 | "http://127.0.0.1:4002", 52 | }, 53 | }, 54 | }, 55 | ), 56 | }, 57 | } 58 | 59 | for i, tt := range tests { 60 | var got memberCollection 61 | err := json.Unmarshal(tt.body, &got) 62 | if err != nil { 63 | t.Errorf("#%d: unexpected error: %v", i, err) 64 | continue 65 | } 66 | 67 | if !reflect.DeepEqual(tt.want, got) { 68 | t.Errorf("#%d: incorrect output: want=%#v, got=%#v", i, tt.want, got) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /etcd/options.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "reflect" 7 | ) 8 | 9 | type Options map[string]interface{} 10 | 11 | // An internally-used data structure that represents a mapping 12 | // between valid options and their kinds 13 | type validOptions map[string]reflect.Kind 14 | 15 | // Valid options for GET, PUT, POST, DELETE 16 | // Using CAPITALIZED_UNDERSCORE to emphasize that these 17 | // values are meant to be used as constants. 18 | var ( 19 | VALID_GET_OPTIONS = validOptions{ 20 | "recursive": reflect.Bool, 21 | "quorum": reflect.Bool, 22 | "sorted": reflect.Bool, 23 | "wait": reflect.Bool, 24 | "waitIndex": reflect.Uint64, 25 | } 26 | 27 | VALID_PUT_OPTIONS = validOptions{ 28 | "prevValue": reflect.String, 29 | "prevIndex": reflect.Uint64, 30 | "prevExist": reflect.Bool, 31 | "dir": reflect.Bool, 32 | } 33 | 34 | VALID_POST_OPTIONS = validOptions{} 35 | 36 | VALID_DELETE_OPTIONS = validOptions{ 37 | "recursive": reflect.Bool, 38 | "dir": reflect.Bool, 39 | "prevValue": reflect.String, 40 | "prevIndex": reflect.Uint64, 41 | } 42 | ) 43 | 44 | // Convert options to a string of HTML parameters 45 | func (ops Options) toParameters(validOps validOptions) (string, error) { 46 | p := "?" 47 | values := url.Values{} 48 | 49 | if ops == nil { 50 | return "", nil 51 | } 52 | 53 | for k, v := range ops { 54 | // Check if the given option is valid (that it exists) 55 | kind := validOps[k] 56 | if kind == reflect.Invalid { 57 | return "", fmt.Errorf("Invalid option: %v", k) 58 | } 59 | 60 | // Check if the given option is of the valid type 61 | t := reflect.TypeOf(v) 62 | if kind != t.Kind() { 63 | return "", fmt.Errorf("Option %s should be of %v kind, not of %v kind.", 64 | k, kind, t.Kind()) 65 | } 66 | 67 | values.Set(k, fmt.Sprintf("%v", v)) 68 | } 69 | 70 | p += values.Encode() 71 | return p, nil 72 | } 73 | -------------------------------------------------------------------------------- /etcd/requests.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "net/url" 10 | "path" 11 | "strings" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | // Errors introduced by handling requests 17 | var ( 18 | ErrRequestCancelled = errors.New("sending request is cancelled") 19 | ) 20 | 21 | type RawRequest struct { 22 | Method string 23 | RelativePath string 24 | Values url.Values 25 | Cancel <-chan bool 26 | } 27 | 28 | // NewRawRequest returns a new RawRequest 29 | func NewRawRequest(method, relativePath string, values url.Values, cancel <-chan bool) *RawRequest { 30 | return &RawRequest{ 31 | Method: method, 32 | RelativePath: relativePath, 33 | Values: values, 34 | Cancel: cancel, 35 | } 36 | } 37 | 38 | // getCancelable issues a cancelable GET request 39 | func (c *Client) getCancelable(key string, options Options, 40 | cancel <-chan bool) (*RawResponse, error) { 41 | logger.Debugf("get %s [%s]", key, c.cluster.pick()) 42 | p := keyToPath(key) 43 | 44 | str, err := options.toParameters(VALID_GET_OPTIONS) 45 | if err != nil { 46 | return nil, err 47 | } 48 | p += str 49 | 50 | req := NewRawRequest("GET", p, nil, cancel) 51 | resp, err := c.SendRequest(req) 52 | 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | return resp, nil 58 | } 59 | 60 | // get issues a GET request 61 | func (c *Client) get(key string, options Options) (*RawResponse, error) { 62 | return c.getCancelable(key, options, nil) 63 | } 64 | 65 | // put issues a PUT request 66 | func (c *Client) put(key string, value string, ttl uint64, 67 | options Options) (*RawResponse, error) { 68 | 69 | logger.Debugf("put %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.pick()) 70 | p := keyToPath(key) 71 | 72 | str, err := options.toParameters(VALID_PUT_OPTIONS) 73 | if err != nil { 74 | return nil, err 75 | } 76 | p += str 77 | 78 | req := NewRawRequest("PUT", p, buildValues(value, ttl), nil) 79 | resp, err := c.SendRequest(req) 80 | 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | return resp, nil 86 | } 87 | 88 | // post issues a POST request 89 | func (c *Client) post(key string, value string, ttl uint64) (*RawResponse, error) { 90 | logger.Debugf("post %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.pick()) 91 | p := keyToPath(key) 92 | 93 | req := NewRawRequest("POST", p, buildValues(value, ttl), nil) 94 | resp, err := c.SendRequest(req) 95 | 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | return resp, nil 101 | } 102 | 103 | // delete issues a DELETE request 104 | func (c *Client) delete(key string, options Options) (*RawResponse, error) { 105 | logger.Debugf("delete %s [%s]", key, c.cluster.pick()) 106 | p := keyToPath(key) 107 | 108 | str, err := options.toParameters(VALID_DELETE_OPTIONS) 109 | if err != nil { 110 | return nil, err 111 | } 112 | p += str 113 | 114 | req := NewRawRequest("DELETE", p, nil, nil) 115 | resp, err := c.SendRequest(req) 116 | 117 | if err != nil { 118 | return nil, err 119 | } 120 | 121 | return resp, nil 122 | } 123 | 124 | // SendRequest sends a HTTP request and returns a Response as defined by etcd 125 | func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) { 126 | var req *http.Request 127 | var resp *http.Response 128 | var httpPath string 129 | var err error 130 | var respBody []byte 131 | 132 | var numReqs = 1 133 | 134 | checkRetry := c.CheckRetry 135 | if checkRetry == nil { 136 | checkRetry = DefaultCheckRetry 137 | } 138 | 139 | cancelled := make(chan bool, 1) 140 | reqLock := new(sync.Mutex) 141 | 142 | if rr.Cancel != nil { 143 | cancelRoutine := make(chan bool) 144 | defer close(cancelRoutine) 145 | 146 | go func() { 147 | select { 148 | case <-rr.Cancel: 149 | cancelled <- true 150 | logger.Debug("send.request is cancelled") 151 | case <-cancelRoutine: 152 | return 153 | } 154 | 155 | // Repeat canceling request until this thread is stopped 156 | // because we have no idea about whether it succeeds. 157 | for { 158 | reqLock.Lock() 159 | c.httpClient.Transport.(*http.Transport).CancelRequest(req) 160 | reqLock.Unlock() 161 | 162 | select { 163 | case <-time.After(100 * time.Millisecond): 164 | case <-cancelRoutine: 165 | return 166 | } 167 | } 168 | }() 169 | } 170 | 171 | // If we connect to a follower and consistency is required, retry until 172 | // we connect to a leader 173 | sleep := 25 * time.Millisecond 174 | maxSleep := time.Second 175 | 176 | for attempt := 0; ; attempt++ { 177 | if attempt > 0 { 178 | select { 179 | case <-cancelled: 180 | return nil, ErrRequestCancelled 181 | case <-time.After(sleep): 182 | sleep = sleep * 2 183 | if sleep > maxSleep { 184 | sleep = maxSleep 185 | } 186 | } 187 | } 188 | 189 | logger.Debug("Connecting to etcd: attempt ", attempt+1, " for ", rr.RelativePath) 190 | 191 | // get httpPath if not set 192 | if httpPath == "" { 193 | httpPath = c.getHttpPath(rr.RelativePath) 194 | } 195 | 196 | // Return a cURL command if curlChan is set 197 | if c.cURLch != nil { 198 | command := fmt.Sprintf("curl -X %s %s", rr.Method, httpPath) 199 | for key, value := range rr.Values { 200 | command += fmt.Sprintf(" -d %s=%s", key, value[0]) 201 | } 202 | if c.credentials != nil { 203 | command += fmt.Sprintf(" -u %s", c.credentials.username) 204 | } 205 | c.sendCURL(command) 206 | } 207 | 208 | logger.Debug("send.request.to ", httpPath, " | method ", rr.Method) 209 | 210 | req, err := func() (*http.Request, error) { 211 | reqLock.Lock() 212 | defer reqLock.Unlock() 213 | 214 | if rr.Values == nil { 215 | if req, err = http.NewRequest(rr.Method, httpPath, nil); err != nil { 216 | return nil, err 217 | } 218 | } else { 219 | body := strings.NewReader(rr.Values.Encode()) 220 | if req, err = http.NewRequest(rr.Method, httpPath, body); err != nil { 221 | return nil, err 222 | } 223 | 224 | req.Header.Set("Content-Type", 225 | "application/x-www-form-urlencoded; param=value") 226 | } 227 | return req, nil 228 | }() 229 | 230 | if err != nil { 231 | return nil, err 232 | } 233 | 234 | if c.credentials != nil { 235 | req.SetBasicAuth(c.credentials.username, c.credentials.password) 236 | } 237 | 238 | resp, err = c.httpClient.Do(req) 239 | // clear previous httpPath 240 | httpPath = "" 241 | defer func() { 242 | if resp != nil { 243 | resp.Body.Close() 244 | } 245 | }() 246 | 247 | // If the request was cancelled, return ErrRequestCancelled directly 248 | select { 249 | case <-cancelled: 250 | return nil, ErrRequestCancelled 251 | default: 252 | } 253 | 254 | numReqs++ 255 | 256 | // network error, change a machine! 257 | if err != nil { 258 | logger.Debug("network error: ", err.Error()) 259 | lastResp := http.Response{} 260 | if checkErr := checkRetry(c.cluster, numReqs, lastResp, err); checkErr != nil { 261 | return nil, checkErr 262 | } 263 | 264 | c.cluster.failure() 265 | continue 266 | } 267 | 268 | // if there is no error, it should receive response 269 | logger.Debug("recv.response.from ", httpPath) 270 | 271 | if validHttpStatusCode[resp.StatusCode] { 272 | // try to read byte code and break the loop 273 | respBody, err = ioutil.ReadAll(resp.Body) 274 | if err == nil { 275 | logger.Debug("recv.success ", httpPath) 276 | break 277 | } 278 | // ReadAll error may be caused due to cancel request 279 | select { 280 | case <-cancelled: 281 | return nil, ErrRequestCancelled 282 | default: 283 | } 284 | 285 | if err == io.ErrUnexpectedEOF { 286 | // underlying connection was closed prematurely, probably by timeout 287 | // TODO: empty body or unexpectedEOF can cause http.Transport to get hosed; 288 | // this allows the client to detect that and take evasive action. Need 289 | // to revisit once code.google.com/p/go/issues/detail?id=8648 gets fixed. 290 | respBody = []byte{} 291 | break 292 | } 293 | } 294 | 295 | if resp.StatusCode == http.StatusTemporaryRedirect { 296 | u, err := resp.Location() 297 | 298 | if err != nil { 299 | logger.Warning(err) 300 | } else { 301 | // set httpPath for following redirection 302 | httpPath = u.String() 303 | } 304 | resp.Body.Close() 305 | continue 306 | } 307 | 308 | if checkErr := checkRetry(c.cluster, numReqs, *resp, 309 | errors.New("Unexpected HTTP status code")); checkErr != nil { 310 | return nil, checkErr 311 | } 312 | resp.Body.Close() 313 | } 314 | 315 | r := &RawResponse{ 316 | StatusCode: resp.StatusCode, 317 | Body: respBody, 318 | Header: resp.Header, 319 | } 320 | 321 | return r, nil 322 | } 323 | 324 | // DefaultCheckRetry defines the retrying behaviour for bad HTTP requests 325 | // If we have retried 2 * machine number, stop retrying. 326 | // If status code is InternalServerError, sleep for 200ms. 327 | func DefaultCheckRetry(cluster *Cluster, numReqs int, lastResp http.Response, 328 | err error) error { 329 | 330 | if numReqs > 2*len(cluster.Machines) { 331 | errStr := fmt.Sprintf("failed to propose on members %v twice [last error: %v]", cluster.Machines, err) 332 | return newError(ErrCodeEtcdNotReachable, errStr, 0) 333 | } 334 | 335 | if isEmptyResponse(lastResp) { 336 | // always retry if it failed to get response from one machine 337 | return nil 338 | } 339 | if !shouldRetry(lastResp) { 340 | body := []byte("nil") 341 | if lastResp.Body != nil { 342 | if b, err := ioutil.ReadAll(lastResp.Body); err == nil { 343 | body = b 344 | } 345 | } 346 | errStr := fmt.Sprintf("unhandled http status [%s] with body [%s]", http.StatusText(lastResp.StatusCode), body) 347 | return newError(ErrCodeUnhandledHTTPStatus, errStr, 0) 348 | } 349 | // sleep some time and expect leader election finish 350 | time.Sleep(time.Millisecond * 200) 351 | logger.Warning("bad response status code ", lastResp.StatusCode) 352 | return nil 353 | } 354 | 355 | func isEmptyResponse(r http.Response) bool { return r.StatusCode == 0 } 356 | 357 | // shouldRetry returns whether the reponse deserves retry. 358 | func shouldRetry(r http.Response) bool { 359 | // TODO: only retry when the cluster is in leader election 360 | // We cannot do it exactly because etcd doesn't support it well. 361 | return r.StatusCode == http.StatusInternalServerError 362 | } 363 | 364 | func (c *Client) getHttpPath(s ...string) string { 365 | fullPath := c.cluster.pick() + "/" + version 366 | for _, seg := range s { 367 | fullPath = fullPath + "/" + seg 368 | } 369 | return fullPath 370 | } 371 | 372 | // buildValues builds a url.Values map according to the given value and ttl 373 | func buildValues(value string, ttl uint64) url.Values { 374 | v := url.Values{} 375 | 376 | if value != "" { 377 | v.Set("value", value) 378 | } 379 | 380 | if ttl > 0 { 381 | v.Set("ttl", fmt.Sprintf("%v", ttl)) 382 | } 383 | 384 | return v 385 | } 386 | 387 | // convert key string to http path exclude version, including URL escaping 388 | // for example: key[foo] -> path[keys/foo] 389 | // key[/%z] -> path[keys/%25z] 390 | // key[/] -> path[keys/] 391 | func keyToPath(key string) string { 392 | // URL-escape our key, except for slashes 393 | p := strings.Replace(url.QueryEscape(path.Join("keys", key)), "%2F", "/", -1) 394 | 395 | // corner case: if key is "/" or "//" ect 396 | // path join will clear the tailing "/" 397 | // we need to add it back 398 | if p == "keys" { 399 | p = "keys/" 400 | } 401 | 402 | return p 403 | } 404 | -------------------------------------------------------------------------------- /etcd/requests_test.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import "testing" 4 | 5 | func TestKeyToPath(t *testing.T) { 6 | tests := []struct { 7 | key string 8 | wpath string 9 | }{ 10 | {"", "keys/"}, 11 | {"foo", "keys/foo"}, 12 | {"foo/bar", "keys/foo/bar"}, 13 | {"%z", "keys/%25z"}, 14 | {"/", "keys/"}, 15 | } 16 | for i, tt := range tests { 17 | path := keyToPath(tt.key) 18 | if path != tt.wpath { 19 | t.Errorf("#%d: path = %s, want %s", i, path, tt.wpath) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /etcd/response.generated.go: -------------------------------------------------------------------------------- 1 | // ************************************************************ 2 | // DO NOT EDIT. 3 | // THIS FILE IS AUTO-GENERATED BY codecgen. 4 | // ************************************************************ 5 | 6 | package etcd 7 | 8 | import ( 9 | "errors" 10 | "fmt" 11 | codec1978 "github.com/ugorji/go/codec" 12 | pkg1_http "net/http" 13 | "reflect" 14 | "runtime" 15 | time "time" 16 | ) 17 | 18 | const ( 19 | // ----- content types ---- 20 | codecSelferC_UTF81978 = 1 21 | codecSelferC_RAW1978 = 0 22 | // ----- value types used ---- 23 | codecSelferValueTypeArray1978 = 10 24 | codecSelferValueTypeMap1978 = 9 25 | // ----- containerStateValues ---- 26 | codecSelfer_containerMapKey1978 = 2 27 | codecSelfer_containerMapValue1978 = 3 28 | codecSelfer_containerMapEnd1978 = 4 29 | codecSelfer_containerArrayElem1978 = 6 30 | codecSelfer_containerArrayEnd1978 = 7 31 | ) 32 | 33 | var ( 34 | codecSelferBitsize1978 = uint8(reflect.TypeOf(uint(0)).Bits()) 35 | codecSelferOnlyMapOrArrayEncodeToStructErr1978 = errors.New(`only encoded map or array can be decoded into a struct`) 36 | ) 37 | 38 | type codecSelfer1978 struct{} 39 | 40 | func init() { 41 | if codec1978.GenVersion != 5 { 42 | _, file, _, _ := runtime.Caller(0) 43 | err := fmt.Errorf("codecgen version mismatch: current: %v, need %v. Re-generate file: %v", 44 | 5, codec1978.GenVersion, file) 45 | panic(err) 46 | } 47 | if false { // reference the types, but skip this branch at build/run time 48 | var v0 pkg1_http.Header 49 | var v1 time.Time 50 | _, _ = v0, v1 51 | } 52 | } 53 | 54 | func (x responseType) CodecEncodeSelf(e *codec1978.Encoder) { 55 | var h codecSelfer1978 56 | z, r := codec1978.GenHelperEncoder(e) 57 | _, _, _ = h, z, r 58 | yym1 := z.EncBinary() 59 | _ = yym1 60 | if false { 61 | } else if z.HasExtensions() && z.EncExt(x) { 62 | } else { 63 | r.EncodeInt(int64(x)) 64 | } 65 | } 66 | 67 | func (x *responseType) CodecDecodeSelf(d *codec1978.Decoder) { 68 | var h codecSelfer1978 69 | z, r := codec1978.GenHelperDecoder(d) 70 | _, _, _ = h, z, r 71 | yym2 := z.DecBinary() 72 | _ = yym2 73 | if false { 74 | } else if z.HasExtensions() && z.DecExt(x) { 75 | } else { 76 | *((*int)(x)) = int(r.DecodeInt(codecSelferBitsize1978)) 77 | } 78 | } 79 | 80 | func (x *RawResponse) CodecEncodeSelf(e *codec1978.Encoder) { 81 | var h codecSelfer1978 82 | z, r := codec1978.GenHelperEncoder(e) 83 | _, _, _ = h, z, r 84 | if x == nil { 85 | r.EncodeNil() 86 | } else { 87 | yym3 := z.EncBinary() 88 | _ = yym3 89 | if false { 90 | } else if z.HasExtensions() && z.EncExt(x) { 91 | } else { 92 | yysep4 := !z.EncBinary() 93 | yy2arr4 := z.EncBasicHandle().StructToArray 94 | var yyq4 [3]bool 95 | _, _, _ = yysep4, yyq4, yy2arr4 96 | const yyr4 bool = false 97 | var yynn4 int 98 | if yyr4 || yy2arr4 { 99 | r.EncodeArrayStart(3) 100 | } else { 101 | yynn4 = 3 102 | for _, b := range yyq4 { 103 | if b { 104 | yynn4++ 105 | } 106 | } 107 | r.EncodeMapStart(yynn4) 108 | yynn4 = 0 109 | } 110 | if yyr4 || yy2arr4 { 111 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 112 | yym6 := z.EncBinary() 113 | _ = yym6 114 | if false { 115 | } else { 116 | r.EncodeInt(int64(x.StatusCode)) 117 | } 118 | } else { 119 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 120 | r.EncodeString(codecSelferC_UTF81978, string("StatusCode")) 121 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 122 | yym7 := z.EncBinary() 123 | _ = yym7 124 | if false { 125 | } else { 126 | r.EncodeInt(int64(x.StatusCode)) 127 | } 128 | } 129 | if yyr4 || yy2arr4 { 130 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 131 | if x.Body == nil { 132 | r.EncodeNil() 133 | } else { 134 | yym9 := z.EncBinary() 135 | _ = yym9 136 | if false { 137 | } else { 138 | r.EncodeStringBytes(codecSelferC_RAW1978, []byte(x.Body)) 139 | } 140 | } 141 | } else { 142 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 143 | r.EncodeString(codecSelferC_UTF81978, string("Body")) 144 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 145 | if x.Body == nil { 146 | r.EncodeNil() 147 | } else { 148 | yym10 := z.EncBinary() 149 | _ = yym10 150 | if false { 151 | } else { 152 | r.EncodeStringBytes(codecSelferC_RAW1978, []byte(x.Body)) 153 | } 154 | } 155 | } 156 | if yyr4 || yy2arr4 { 157 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 158 | if x.Header == nil { 159 | r.EncodeNil() 160 | } else { 161 | yym12 := z.EncBinary() 162 | _ = yym12 163 | if false { 164 | } else if z.HasExtensions() && z.EncExt(x.Header) { 165 | } else { 166 | h.enchttp_Header((pkg1_http.Header)(x.Header), e) 167 | } 168 | } 169 | } else { 170 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 171 | r.EncodeString(codecSelferC_UTF81978, string("Header")) 172 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 173 | if x.Header == nil { 174 | r.EncodeNil() 175 | } else { 176 | yym13 := z.EncBinary() 177 | _ = yym13 178 | if false { 179 | } else if z.HasExtensions() && z.EncExt(x.Header) { 180 | } else { 181 | h.enchttp_Header((pkg1_http.Header)(x.Header), e) 182 | } 183 | } 184 | } 185 | if yyr4 || yy2arr4 { 186 | z.EncSendContainerState(codecSelfer_containerArrayEnd1978) 187 | } else { 188 | z.EncSendContainerState(codecSelfer_containerMapEnd1978) 189 | } 190 | } 191 | } 192 | } 193 | 194 | func (x *RawResponse) CodecDecodeSelf(d *codec1978.Decoder) { 195 | var h codecSelfer1978 196 | z, r := codec1978.GenHelperDecoder(d) 197 | _, _, _ = h, z, r 198 | yym14 := z.DecBinary() 199 | _ = yym14 200 | if false { 201 | } else if z.HasExtensions() && z.DecExt(x) { 202 | } else { 203 | yyct15 := r.ContainerType() 204 | if yyct15 == codecSelferValueTypeMap1978 { 205 | yyl15 := r.ReadMapStart() 206 | if yyl15 == 0 { 207 | z.DecSendContainerState(codecSelfer_containerMapEnd1978) 208 | } else { 209 | x.codecDecodeSelfFromMap(yyl15, d) 210 | } 211 | } else if yyct15 == codecSelferValueTypeArray1978 { 212 | yyl15 := r.ReadArrayStart() 213 | if yyl15 == 0 { 214 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 215 | } else { 216 | x.codecDecodeSelfFromArray(yyl15, d) 217 | } 218 | } else { 219 | panic(codecSelferOnlyMapOrArrayEncodeToStructErr1978) 220 | } 221 | } 222 | } 223 | 224 | func (x *RawResponse) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { 225 | var h codecSelfer1978 226 | z, r := codec1978.GenHelperDecoder(d) 227 | _, _, _ = h, z, r 228 | var yys16Slc = z.DecScratchBuffer() // default slice to decode into 229 | _ = yys16Slc 230 | var yyhl16 bool = l >= 0 231 | for yyj16 := 0; ; yyj16++ { 232 | if yyhl16 { 233 | if yyj16 >= l { 234 | break 235 | } 236 | } else { 237 | if r.CheckBreak() { 238 | break 239 | } 240 | } 241 | z.DecSendContainerState(codecSelfer_containerMapKey1978) 242 | yys16Slc = r.DecodeBytes(yys16Slc, true, true) 243 | yys16 := string(yys16Slc) 244 | z.DecSendContainerState(codecSelfer_containerMapValue1978) 245 | switch yys16 { 246 | case "StatusCode": 247 | if r.TryDecodeAsNil() { 248 | x.StatusCode = 0 249 | } else { 250 | x.StatusCode = int(r.DecodeInt(codecSelferBitsize1978)) 251 | } 252 | case "Body": 253 | if r.TryDecodeAsNil() { 254 | x.Body = nil 255 | } else { 256 | yyv18 := &x.Body 257 | yym19 := z.DecBinary() 258 | _ = yym19 259 | if false { 260 | } else { 261 | *yyv18 = r.DecodeBytes(*(*[]byte)(yyv18), false, false) 262 | } 263 | } 264 | case "Header": 265 | if r.TryDecodeAsNil() { 266 | x.Header = nil 267 | } else { 268 | yyv20 := &x.Header 269 | yym21 := z.DecBinary() 270 | _ = yym21 271 | if false { 272 | } else if z.HasExtensions() && z.DecExt(yyv20) { 273 | } else { 274 | h.dechttp_Header((*pkg1_http.Header)(yyv20), d) 275 | } 276 | } 277 | default: 278 | z.DecStructFieldNotFound(-1, yys16) 279 | } // end switch yys16 280 | } // end for yyj16 281 | z.DecSendContainerState(codecSelfer_containerMapEnd1978) 282 | } 283 | 284 | func (x *RawResponse) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { 285 | var h codecSelfer1978 286 | z, r := codec1978.GenHelperDecoder(d) 287 | _, _, _ = h, z, r 288 | var yyj22 int 289 | var yyb22 bool 290 | var yyhl22 bool = l >= 0 291 | yyj22++ 292 | if yyhl22 { 293 | yyb22 = yyj22 > l 294 | } else { 295 | yyb22 = r.CheckBreak() 296 | } 297 | if yyb22 { 298 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 299 | return 300 | } 301 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 302 | if r.TryDecodeAsNil() { 303 | x.StatusCode = 0 304 | } else { 305 | x.StatusCode = int(r.DecodeInt(codecSelferBitsize1978)) 306 | } 307 | yyj22++ 308 | if yyhl22 { 309 | yyb22 = yyj22 > l 310 | } else { 311 | yyb22 = r.CheckBreak() 312 | } 313 | if yyb22 { 314 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 315 | return 316 | } 317 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 318 | if r.TryDecodeAsNil() { 319 | x.Body = nil 320 | } else { 321 | yyv24 := &x.Body 322 | yym25 := z.DecBinary() 323 | _ = yym25 324 | if false { 325 | } else { 326 | *yyv24 = r.DecodeBytes(*(*[]byte)(yyv24), false, false) 327 | } 328 | } 329 | yyj22++ 330 | if yyhl22 { 331 | yyb22 = yyj22 > l 332 | } else { 333 | yyb22 = r.CheckBreak() 334 | } 335 | if yyb22 { 336 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 337 | return 338 | } 339 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 340 | if r.TryDecodeAsNil() { 341 | x.Header = nil 342 | } else { 343 | yyv26 := &x.Header 344 | yym27 := z.DecBinary() 345 | _ = yym27 346 | if false { 347 | } else if z.HasExtensions() && z.DecExt(yyv26) { 348 | } else { 349 | h.dechttp_Header((*pkg1_http.Header)(yyv26), d) 350 | } 351 | } 352 | for { 353 | yyj22++ 354 | if yyhl22 { 355 | yyb22 = yyj22 > l 356 | } else { 357 | yyb22 = r.CheckBreak() 358 | } 359 | if yyb22 { 360 | break 361 | } 362 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 363 | z.DecStructFieldNotFound(yyj22-1, "") 364 | } 365 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 366 | } 367 | 368 | func (x *Response) CodecEncodeSelf(e *codec1978.Encoder) { 369 | var h codecSelfer1978 370 | z, r := codec1978.GenHelperEncoder(e) 371 | _, _, _ = h, z, r 372 | if x == nil { 373 | r.EncodeNil() 374 | } else { 375 | yym28 := z.EncBinary() 376 | _ = yym28 377 | if false { 378 | } else if z.HasExtensions() && z.EncExt(x) { 379 | } else { 380 | yysep29 := !z.EncBinary() 381 | yy2arr29 := z.EncBasicHandle().StructToArray 382 | var yyq29 [6]bool 383 | _, _, _ = yysep29, yyq29, yy2arr29 384 | const yyr29 bool = false 385 | yyq29[2] = x.PrevNode != nil 386 | var yynn29 int 387 | if yyr29 || yy2arr29 { 388 | r.EncodeArrayStart(6) 389 | } else { 390 | yynn29 = 5 391 | for _, b := range yyq29 { 392 | if b { 393 | yynn29++ 394 | } 395 | } 396 | r.EncodeMapStart(yynn29) 397 | yynn29 = 0 398 | } 399 | if yyr29 || yy2arr29 { 400 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 401 | yym31 := z.EncBinary() 402 | _ = yym31 403 | if false { 404 | } else { 405 | r.EncodeString(codecSelferC_UTF81978, string(x.Action)) 406 | } 407 | } else { 408 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 409 | r.EncodeString(codecSelferC_UTF81978, string("action")) 410 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 411 | yym32 := z.EncBinary() 412 | _ = yym32 413 | if false { 414 | } else { 415 | r.EncodeString(codecSelferC_UTF81978, string(x.Action)) 416 | } 417 | } 418 | if yyr29 || yy2arr29 { 419 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 420 | if x.Node == nil { 421 | r.EncodeNil() 422 | } else { 423 | x.Node.CodecEncodeSelf(e) 424 | } 425 | } else { 426 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 427 | r.EncodeString(codecSelferC_UTF81978, string("node")) 428 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 429 | if x.Node == nil { 430 | r.EncodeNil() 431 | } else { 432 | x.Node.CodecEncodeSelf(e) 433 | } 434 | } 435 | if yyr29 || yy2arr29 { 436 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 437 | if yyq29[2] { 438 | if x.PrevNode == nil { 439 | r.EncodeNil() 440 | } else { 441 | x.PrevNode.CodecEncodeSelf(e) 442 | } 443 | } else { 444 | r.EncodeNil() 445 | } 446 | } else { 447 | if yyq29[2] { 448 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 449 | r.EncodeString(codecSelferC_UTF81978, string("prevNode")) 450 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 451 | if x.PrevNode == nil { 452 | r.EncodeNil() 453 | } else { 454 | x.PrevNode.CodecEncodeSelf(e) 455 | } 456 | } 457 | } 458 | if yyr29 || yy2arr29 { 459 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 460 | yym36 := z.EncBinary() 461 | _ = yym36 462 | if false { 463 | } else { 464 | r.EncodeUint(uint64(x.EtcdIndex)) 465 | } 466 | } else { 467 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 468 | r.EncodeString(codecSelferC_UTF81978, string("etcdIndex")) 469 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 470 | yym37 := z.EncBinary() 471 | _ = yym37 472 | if false { 473 | } else { 474 | r.EncodeUint(uint64(x.EtcdIndex)) 475 | } 476 | } 477 | if yyr29 || yy2arr29 { 478 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 479 | yym39 := z.EncBinary() 480 | _ = yym39 481 | if false { 482 | } else { 483 | r.EncodeUint(uint64(x.RaftIndex)) 484 | } 485 | } else { 486 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 487 | r.EncodeString(codecSelferC_UTF81978, string("raftIndex")) 488 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 489 | yym40 := z.EncBinary() 490 | _ = yym40 491 | if false { 492 | } else { 493 | r.EncodeUint(uint64(x.RaftIndex)) 494 | } 495 | } 496 | if yyr29 || yy2arr29 { 497 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 498 | yym42 := z.EncBinary() 499 | _ = yym42 500 | if false { 501 | } else { 502 | r.EncodeUint(uint64(x.RaftTerm)) 503 | } 504 | } else { 505 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 506 | r.EncodeString(codecSelferC_UTF81978, string("raftTerm")) 507 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 508 | yym43 := z.EncBinary() 509 | _ = yym43 510 | if false { 511 | } else { 512 | r.EncodeUint(uint64(x.RaftTerm)) 513 | } 514 | } 515 | if yyr29 || yy2arr29 { 516 | z.EncSendContainerState(codecSelfer_containerArrayEnd1978) 517 | } else { 518 | z.EncSendContainerState(codecSelfer_containerMapEnd1978) 519 | } 520 | } 521 | } 522 | } 523 | 524 | func (x *Response) CodecDecodeSelf(d *codec1978.Decoder) { 525 | var h codecSelfer1978 526 | z, r := codec1978.GenHelperDecoder(d) 527 | _, _, _ = h, z, r 528 | yym44 := z.DecBinary() 529 | _ = yym44 530 | if false { 531 | } else if z.HasExtensions() && z.DecExt(x) { 532 | } else { 533 | yyct45 := r.ContainerType() 534 | if yyct45 == codecSelferValueTypeMap1978 { 535 | yyl45 := r.ReadMapStart() 536 | if yyl45 == 0 { 537 | z.DecSendContainerState(codecSelfer_containerMapEnd1978) 538 | } else { 539 | x.codecDecodeSelfFromMap(yyl45, d) 540 | } 541 | } else if yyct45 == codecSelferValueTypeArray1978 { 542 | yyl45 := r.ReadArrayStart() 543 | if yyl45 == 0 { 544 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 545 | } else { 546 | x.codecDecodeSelfFromArray(yyl45, d) 547 | } 548 | } else { 549 | panic(codecSelferOnlyMapOrArrayEncodeToStructErr1978) 550 | } 551 | } 552 | } 553 | 554 | func (x *Response) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { 555 | var h codecSelfer1978 556 | z, r := codec1978.GenHelperDecoder(d) 557 | _, _, _ = h, z, r 558 | var yys46Slc = z.DecScratchBuffer() // default slice to decode into 559 | _ = yys46Slc 560 | var yyhl46 bool = l >= 0 561 | for yyj46 := 0; ; yyj46++ { 562 | if yyhl46 { 563 | if yyj46 >= l { 564 | break 565 | } 566 | } else { 567 | if r.CheckBreak() { 568 | break 569 | } 570 | } 571 | z.DecSendContainerState(codecSelfer_containerMapKey1978) 572 | yys46Slc = r.DecodeBytes(yys46Slc, true, true) 573 | yys46 := string(yys46Slc) 574 | z.DecSendContainerState(codecSelfer_containerMapValue1978) 575 | switch yys46 { 576 | case "action": 577 | if r.TryDecodeAsNil() { 578 | x.Action = "" 579 | } else { 580 | x.Action = string(r.DecodeString()) 581 | } 582 | case "node": 583 | if r.TryDecodeAsNil() { 584 | if x.Node != nil { 585 | x.Node = nil 586 | } 587 | } else { 588 | if x.Node == nil { 589 | x.Node = new(Node) 590 | } 591 | x.Node.CodecDecodeSelf(d) 592 | } 593 | case "prevNode": 594 | if r.TryDecodeAsNil() { 595 | if x.PrevNode != nil { 596 | x.PrevNode = nil 597 | } 598 | } else { 599 | if x.PrevNode == nil { 600 | x.PrevNode = new(Node) 601 | } 602 | x.PrevNode.CodecDecodeSelf(d) 603 | } 604 | case "etcdIndex": 605 | if r.TryDecodeAsNil() { 606 | x.EtcdIndex = 0 607 | } else { 608 | x.EtcdIndex = uint64(r.DecodeUint(64)) 609 | } 610 | case "raftIndex": 611 | if r.TryDecodeAsNil() { 612 | x.RaftIndex = 0 613 | } else { 614 | x.RaftIndex = uint64(r.DecodeUint(64)) 615 | } 616 | case "raftTerm": 617 | if r.TryDecodeAsNil() { 618 | x.RaftTerm = 0 619 | } else { 620 | x.RaftTerm = uint64(r.DecodeUint(64)) 621 | } 622 | default: 623 | z.DecStructFieldNotFound(-1, yys46) 624 | } // end switch yys46 625 | } // end for yyj46 626 | z.DecSendContainerState(codecSelfer_containerMapEnd1978) 627 | } 628 | 629 | func (x *Response) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { 630 | var h codecSelfer1978 631 | z, r := codec1978.GenHelperDecoder(d) 632 | _, _, _ = h, z, r 633 | var yyj53 int 634 | var yyb53 bool 635 | var yyhl53 bool = l >= 0 636 | yyj53++ 637 | if yyhl53 { 638 | yyb53 = yyj53 > l 639 | } else { 640 | yyb53 = r.CheckBreak() 641 | } 642 | if yyb53 { 643 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 644 | return 645 | } 646 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 647 | if r.TryDecodeAsNil() { 648 | x.Action = "" 649 | } else { 650 | x.Action = string(r.DecodeString()) 651 | } 652 | yyj53++ 653 | if yyhl53 { 654 | yyb53 = yyj53 > l 655 | } else { 656 | yyb53 = r.CheckBreak() 657 | } 658 | if yyb53 { 659 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 660 | return 661 | } 662 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 663 | if r.TryDecodeAsNil() { 664 | if x.Node != nil { 665 | x.Node = nil 666 | } 667 | } else { 668 | if x.Node == nil { 669 | x.Node = new(Node) 670 | } 671 | x.Node.CodecDecodeSelf(d) 672 | } 673 | yyj53++ 674 | if yyhl53 { 675 | yyb53 = yyj53 > l 676 | } else { 677 | yyb53 = r.CheckBreak() 678 | } 679 | if yyb53 { 680 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 681 | return 682 | } 683 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 684 | if r.TryDecodeAsNil() { 685 | if x.PrevNode != nil { 686 | x.PrevNode = nil 687 | } 688 | } else { 689 | if x.PrevNode == nil { 690 | x.PrevNode = new(Node) 691 | } 692 | x.PrevNode.CodecDecodeSelf(d) 693 | } 694 | yyj53++ 695 | if yyhl53 { 696 | yyb53 = yyj53 > l 697 | } else { 698 | yyb53 = r.CheckBreak() 699 | } 700 | if yyb53 { 701 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 702 | return 703 | } 704 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 705 | if r.TryDecodeAsNil() { 706 | x.EtcdIndex = 0 707 | } else { 708 | x.EtcdIndex = uint64(r.DecodeUint(64)) 709 | } 710 | yyj53++ 711 | if yyhl53 { 712 | yyb53 = yyj53 > l 713 | } else { 714 | yyb53 = r.CheckBreak() 715 | } 716 | if yyb53 { 717 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 718 | return 719 | } 720 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 721 | if r.TryDecodeAsNil() { 722 | x.RaftIndex = 0 723 | } else { 724 | x.RaftIndex = uint64(r.DecodeUint(64)) 725 | } 726 | yyj53++ 727 | if yyhl53 { 728 | yyb53 = yyj53 > l 729 | } else { 730 | yyb53 = r.CheckBreak() 731 | } 732 | if yyb53 { 733 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 734 | return 735 | } 736 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 737 | if r.TryDecodeAsNil() { 738 | x.RaftTerm = 0 739 | } else { 740 | x.RaftTerm = uint64(r.DecodeUint(64)) 741 | } 742 | for { 743 | yyj53++ 744 | if yyhl53 { 745 | yyb53 = yyj53 > l 746 | } else { 747 | yyb53 = r.CheckBreak() 748 | } 749 | if yyb53 { 750 | break 751 | } 752 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 753 | z.DecStructFieldNotFound(yyj53-1, "") 754 | } 755 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 756 | } 757 | 758 | func (x *Node) CodecEncodeSelf(e *codec1978.Encoder) { 759 | var h codecSelfer1978 760 | z, r := codec1978.GenHelperEncoder(e) 761 | _, _, _ = h, z, r 762 | if x == nil { 763 | r.EncodeNil() 764 | } else { 765 | yym60 := z.EncBinary() 766 | _ = yym60 767 | if false { 768 | } else if z.HasExtensions() && z.EncExt(x) { 769 | } else { 770 | yysep61 := !z.EncBinary() 771 | yy2arr61 := z.EncBasicHandle().StructToArray 772 | var yyq61 [8]bool 773 | _, _, _ = yysep61, yyq61, yy2arr61 774 | const yyr61 bool = false 775 | yyq61[1] = x.Value != "" 776 | yyq61[2] = x.Dir != false 777 | yyq61[3] = x.Expiration != nil 778 | yyq61[4] = x.TTL != 0 779 | yyq61[5] = len(x.Nodes) != 0 780 | yyq61[6] = x.ModifiedIndex != 0 781 | yyq61[7] = x.CreatedIndex != 0 782 | var yynn61 int 783 | if yyr61 || yy2arr61 { 784 | r.EncodeArrayStart(8) 785 | } else { 786 | yynn61 = 1 787 | for _, b := range yyq61 { 788 | if b { 789 | yynn61++ 790 | } 791 | } 792 | r.EncodeMapStart(yynn61) 793 | yynn61 = 0 794 | } 795 | if yyr61 || yy2arr61 { 796 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 797 | yym63 := z.EncBinary() 798 | _ = yym63 799 | if false { 800 | } else { 801 | r.EncodeString(codecSelferC_UTF81978, string(x.Key)) 802 | } 803 | } else { 804 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 805 | r.EncodeString(codecSelferC_UTF81978, string("key")) 806 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 807 | yym64 := z.EncBinary() 808 | _ = yym64 809 | if false { 810 | } else { 811 | r.EncodeString(codecSelferC_UTF81978, string(x.Key)) 812 | } 813 | } 814 | if yyr61 || yy2arr61 { 815 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 816 | if yyq61[1] { 817 | yym66 := z.EncBinary() 818 | _ = yym66 819 | if false { 820 | } else { 821 | r.EncodeString(codecSelferC_UTF81978, string(x.Value)) 822 | } 823 | } else { 824 | r.EncodeString(codecSelferC_UTF81978, "") 825 | } 826 | } else { 827 | if yyq61[1] { 828 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 829 | r.EncodeString(codecSelferC_UTF81978, string("value")) 830 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 831 | yym67 := z.EncBinary() 832 | _ = yym67 833 | if false { 834 | } else { 835 | r.EncodeString(codecSelferC_UTF81978, string(x.Value)) 836 | } 837 | } 838 | } 839 | if yyr61 || yy2arr61 { 840 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 841 | if yyq61[2] { 842 | yym69 := z.EncBinary() 843 | _ = yym69 844 | if false { 845 | } else { 846 | r.EncodeBool(bool(x.Dir)) 847 | } 848 | } else { 849 | r.EncodeBool(false) 850 | } 851 | } else { 852 | if yyq61[2] { 853 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 854 | r.EncodeString(codecSelferC_UTF81978, string("dir")) 855 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 856 | yym70 := z.EncBinary() 857 | _ = yym70 858 | if false { 859 | } else { 860 | r.EncodeBool(bool(x.Dir)) 861 | } 862 | } 863 | } 864 | if yyr61 || yy2arr61 { 865 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 866 | if yyq61[3] { 867 | if x.Expiration == nil { 868 | r.EncodeNil() 869 | } else { 870 | yym72 := z.EncBinary() 871 | _ = yym72 872 | if false { 873 | } else if yym73 := z.TimeRtidIfBinc(); yym73 != 0 { 874 | r.EncodeBuiltin(yym73, x.Expiration) 875 | } else if z.HasExtensions() && z.EncExt(x.Expiration) { 876 | } else if yym72 { 877 | z.EncBinaryMarshal(x.Expiration) 878 | } else if !yym72 && z.IsJSONHandle() { 879 | z.EncJSONMarshal(x.Expiration) 880 | } else { 881 | z.EncFallback(x.Expiration) 882 | } 883 | } 884 | } else { 885 | r.EncodeNil() 886 | } 887 | } else { 888 | if yyq61[3] { 889 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 890 | r.EncodeString(codecSelferC_UTF81978, string("expiration")) 891 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 892 | if x.Expiration == nil { 893 | r.EncodeNil() 894 | } else { 895 | yym74 := z.EncBinary() 896 | _ = yym74 897 | if false { 898 | } else if yym75 := z.TimeRtidIfBinc(); yym75 != 0 { 899 | r.EncodeBuiltin(yym75, x.Expiration) 900 | } else if z.HasExtensions() && z.EncExt(x.Expiration) { 901 | } else if yym74 { 902 | z.EncBinaryMarshal(x.Expiration) 903 | } else if !yym74 && z.IsJSONHandle() { 904 | z.EncJSONMarshal(x.Expiration) 905 | } else { 906 | z.EncFallback(x.Expiration) 907 | } 908 | } 909 | } 910 | } 911 | if yyr61 || yy2arr61 { 912 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 913 | if yyq61[4] { 914 | yym77 := z.EncBinary() 915 | _ = yym77 916 | if false { 917 | } else { 918 | r.EncodeInt(int64(x.TTL)) 919 | } 920 | } else { 921 | r.EncodeInt(0) 922 | } 923 | } else { 924 | if yyq61[4] { 925 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 926 | r.EncodeString(codecSelferC_UTF81978, string("ttl")) 927 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 928 | yym78 := z.EncBinary() 929 | _ = yym78 930 | if false { 931 | } else { 932 | r.EncodeInt(int64(x.TTL)) 933 | } 934 | } 935 | } 936 | if yyr61 || yy2arr61 { 937 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 938 | if yyq61[5] { 939 | if x.Nodes == nil { 940 | r.EncodeNil() 941 | } else { 942 | x.Nodes.CodecEncodeSelf(e) 943 | } 944 | } else { 945 | r.EncodeNil() 946 | } 947 | } else { 948 | if yyq61[5] { 949 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 950 | r.EncodeString(codecSelferC_UTF81978, string("nodes")) 951 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 952 | if x.Nodes == nil { 953 | r.EncodeNil() 954 | } else { 955 | x.Nodes.CodecEncodeSelf(e) 956 | } 957 | } 958 | } 959 | if yyr61 || yy2arr61 { 960 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 961 | if yyq61[6] { 962 | yym81 := z.EncBinary() 963 | _ = yym81 964 | if false { 965 | } else { 966 | r.EncodeUint(uint64(x.ModifiedIndex)) 967 | } 968 | } else { 969 | r.EncodeUint(0) 970 | } 971 | } else { 972 | if yyq61[6] { 973 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 974 | r.EncodeString(codecSelferC_UTF81978, string("modifiedIndex")) 975 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 976 | yym82 := z.EncBinary() 977 | _ = yym82 978 | if false { 979 | } else { 980 | r.EncodeUint(uint64(x.ModifiedIndex)) 981 | } 982 | } 983 | } 984 | if yyr61 || yy2arr61 { 985 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 986 | if yyq61[7] { 987 | yym84 := z.EncBinary() 988 | _ = yym84 989 | if false { 990 | } else { 991 | r.EncodeUint(uint64(x.CreatedIndex)) 992 | } 993 | } else { 994 | r.EncodeUint(0) 995 | } 996 | } else { 997 | if yyq61[7] { 998 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 999 | r.EncodeString(codecSelferC_UTF81978, string("createdIndex")) 1000 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 1001 | yym85 := z.EncBinary() 1002 | _ = yym85 1003 | if false { 1004 | } else { 1005 | r.EncodeUint(uint64(x.CreatedIndex)) 1006 | } 1007 | } 1008 | } 1009 | if yyr61 || yy2arr61 { 1010 | z.EncSendContainerState(codecSelfer_containerArrayEnd1978) 1011 | } else { 1012 | z.EncSendContainerState(codecSelfer_containerMapEnd1978) 1013 | } 1014 | } 1015 | } 1016 | } 1017 | 1018 | func (x *Node) CodecDecodeSelf(d *codec1978.Decoder) { 1019 | var h codecSelfer1978 1020 | z, r := codec1978.GenHelperDecoder(d) 1021 | _, _, _ = h, z, r 1022 | yym86 := z.DecBinary() 1023 | _ = yym86 1024 | if false { 1025 | } else if z.HasExtensions() && z.DecExt(x) { 1026 | } else { 1027 | yyct87 := r.ContainerType() 1028 | if yyct87 == codecSelferValueTypeMap1978 { 1029 | yyl87 := r.ReadMapStart() 1030 | if yyl87 == 0 { 1031 | z.DecSendContainerState(codecSelfer_containerMapEnd1978) 1032 | } else { 1033 | x.codecDecodeSelfFromMap(yyl87, d) 1034 | } 1035 | } else if yyct87 == codecSelferValueTypeArray1978 { 1036 | yyl87 := r.ReadArrayStart() 1037 | if yyl87 == 0 { 1038 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 1039 | } else { 1040 | x.codecDecodeSelfFromArray(yyl87, d) 1041 | } 1042 | } else { 1043 | panic(codecSelferOnlyMapOrArrayEncodeToStructErr1978) 1044 | } 1045 | } 1046 | } 1047 | 1048 | func (x *Node) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { 1049 | var h codecSelfer1978 1050 | z, r := codec1978.GenHelperDecoder(d) 1051 | _, _, _ = h, z, r 1052 | var yys88Slc = z.DecScratchBuffer() // default slice to decode into 1053 | _ = yys88Slc 1054 | var yyhl88 bool = l >= 0 1055 | for yyj88 := 0; ; yyj88++ { 1056 | if yyhl88 { 1057 | if yyj88 >= l { 1058 | break 1059 | } 1060 | } else { 1061 | if r.CheckBreak() { 1062 | break 1063 | } 1064 | } 1065 | z.DecSendContainerState(codecSelfer_containerMapKey1978) 1066 | yys88Slc = r.DecodeBytes(yys88Slc, true, true) 1067 | yys88 := string(yys88Slc) 1068 | z.DecSendContainerState(codecSelfer_containerMapValue1978) 1069 | switch yys88 { 1070 | case "key": 1071 | if r.TryDecodeAsNil() { 1072 | x.Key = "" 1073 | } else { 1074 | x.Key = string(r.DecodeString()) 1075 | } 1076 | case "value": 1077 | if r.TryDecodeAsNil() { 1078 | x.Value = "" 1079 | } else { 1080 | x.Value = string(r.DecodeString()) 1081 | } 1082 | case "dir": 1083 | if r.TryDecodeAsNil() { 1084 | x.Dir = false 1085 | } else { 1086 | x.Dir = bool(r.DecodeBool()) 1087 | } 1088 | case "expiration": 1089 | if r.TryDecodeAsNil() { 1090 | if x.Expiration != nil { 1091 | x.Expiration = nil 1092 | } 1093 | } else { 1094 | if x.Expiration == nil { 1095 | x.Expiration = new(time.Time) 1096 | } 1097 | yym93 := z.DecBinary() 1098 | _ = yym93 1099 | if false { 1100 | } else if yym94 := z.TimeRtidIfBinc(); yym94 != 0 { 1101 | r.DecodeBuiltin(yym94, x.Expiration) 1102 | } else if z.HasExtensions() && z.DecExt(x.Expiration) { 1103 | } else if yym93 { 1104 | z.DecBinaryUnmarshal(x.Expiration) 1105 | } else if !yym93 && z.IsJSONHandle() { 1106 | z.DecJSONUnmarshal(x.Expiration) 1107 | } else { 1108 | z.DecFallback(x.Expiration, false) 1109 | } 1110 | } 1111 | case "ttl": 1112 | if r.TryDecodeAsNil() { 1113 | x.TTL = 0 1114 | } else { 1115 | x.TTL = int64(r.DecodeInt(64)) 1116 | } 1117 | case "nodes": 1118 | if r.TryDecodeAsNil() { 1119 | x.Nodes = nil 1120 | } else { 1121 | yyv96 := &x.Nodes 1122 | yyv96.CodecDecodeSelf(d) 1123 | } 1124 | case "modifiedIndex": 1125 | if r.TryDecodeAsNil() { 1126 | x.ModifiedIndex = 0 1127 | } else { 1128 | x.ModifiedIndex = uint64(r.DecodeUint(64)) 1129 | } 1130 | case "createdIndex": 1131 | if r.TryDecodeAsNil() { 1132 | x.CreatedIndex = 0 1133 | } else { 1134 | x.CreatedIndex = uint64(r.DecodeUint(64)) 1135 | } 1136 | default: 1137 | z.DecStructFieldNotFound(-1, yys88) 1138 | } // end switch yys88 1139 | } // end for yyj88 1140 | z.DecSendContainerState(codecSelfer_containerMapEnd1978) 1141 | } 1142 | 1143 | func (x *Node) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { 1144 | var h codecSelfer1978 1145 | z, r := codec1978.GenHelperDecoder(d) 1146 | _, _, _ = h, z, r 1147 | var yyj99 int 1148 | var yyb99 bool 1149 | var yyhl99 bool = l >= 0 1150 | yyj99++ 1151 | if yyhl99 { 1152 | yyb99 = yyj99 > l 1153 | } else { 1154 | yyb99 = r.CheckBreak() 1155 | } 1156 | if yyb99 { 1157 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 1158 | return 1159 | } 1160 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 1161 | if r.TryDecodeAsNil() { 1162 | x.Key = "" 1163 | } else { 1164 | x.Key = string(r.DecodeString()) 1165 | } 1166 | yyj99++ 1167 | if yyhl99 { 1168 | yyb99 = yyj99 > l 1169 | } else { 1170 | yyb99 = r.CheckBreak() 1171 | } 1172 | if yyb99 { 1173 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 1174 | return 1175 | } 1176 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 1177 | if r.TryDecodeAsNil() { 1178 | x.Value = "" 1179 | } else { 1180 | x.Value = string(r.DecodeString()) 1181 | } 1182 | yyj99++ 1183 | if yyhl99 { 1184 | yyb99 = yyj99 > l 1185 | } else { 1186 | yyb99 = r.CheckBreak() 1187 | } 1188 | if yyb99 { 1189 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 1190 | return 1191 | } 1192 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 1193 | if r.TryDecodeAsNil() { 1194 | x.Dir = false 1195 | } else { 1196 | x.Dir = bool(r.DecodeBool()) 1197 | } 1198 | yyj99++ 1199 | if yyhl99 { 1200 | yyb99 = yyj99 > l 1201 | } else { 1202 | yyb99 = r.CheckBreak() 1203 | } 1204 | if yyb99 { 1205 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 1206 | return 1207 | } 1208 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 1209 | if r.TryDecodeAsNil() { 1210 | if x.Expiration != nil { 1211 | x.Expiration = nil 1212 | } 1213 | } else { 1214 | if x.Expiration == nil { 1215 | x.Expiration = new(time.Time) 1216 | } 1217 | yym104 := z.DecBinary() 1218 | _ = yym104 1219 | if false { 1220 | } else if yym105 := z.TimeRtidIfBinc(); yym105 != 0 { 1221 | r.DecodeBuiltin(yym105, x.Expiration) 1222 | } else if z.HasExtensions() && z.DecExt(x.Expiration) { 1223 | } else if yym104 { 1224 | z.DecBinaryUnmarshal(x.Expiration) 1225 | } else if !yym104 && z.IsJSONHandle() { 1226 | z.DecJSONUnmarshal(x.Expiration) 1227 | } else { 1228 | z.DecFallback(x.Expiration, false) 1229 | } 1230 | } 1231 | yyj99++ 1232 | if yyhl99 { 1233 | yyb99 = yyj99 > l 1234 | } else { 1235 | yyb99 = r.CheckBreak() 1236 | } 1237 | if yyb99 { 1238 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 1239 | return 1240 | } 1241 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 1242 | if r.TryDecodeAsNil() { 1243 | x.TTL = 0 1244 | } else { 1245 | x.TTL = int64(r.DecodeInt(64)) 1246 | } 1247 | yyj99++ 1248 | if yyhl99 { 1249 | yyb99 = yyj99 > l 1250 | } else { 1251 | yyb99 = r.CheckBreak() 1252 | } 1253 | if yyb99 { 1254 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 1255 | return 1256 | } 1257 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 1258 | if r.TryDecodeAsNil() { 1259 | x.Nodes = nil 1260 | } else { 1261 | yyv107 := &x.Nodes 1262 | yyv107.CodecDecodeSelf(d) 1263 | } 1264 | yyj99++ 1265 | if yyhl99 { 1266 | yyb99 = yyj99 > l 1267 | } else { 1268 | yyb99 = r.CheckBreak() 1269 | } 1270 | if yyb99 { 1271 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 1272 | return 1273 | } 1274 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 1275 | if r.TryDecodeAsNil() { 1276 | x.ModifiedIndex = 0 1277 | } else { 1278 | x.ModifiedIndex = uint64(r.DecodeUint(64)) 1279 | } 1280 | yyj99++ 1281 | if yyhl99 { 1282 | yyb99 = yyj99 > l 1283 | } else { 1284 | yyb99 = r.CheckBreak() 1285 | } 1286 | if yyb99 { 1287 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 1288 | return 1289 | } 1290 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 1291 | if r.TryDecodeAsNil() { 1292 | x.CreatedIndex = 0 1293 | } else { 1294 | x.CreatedIndex = uint64(r.DecodeUint(64)) 1295 | } 1296 | for { 1297 | yyj99++ 1298 | if yyhl99 { 1299 | yyb99 = yyj99 > l 1300 | } else { 1301 | yyb99 = r.CheckBreak() 1302 | } 1303 | if yyb99 { 1304 | break 1305 | } 1306 | z.DecSendContainerState(codecSelfer_containerArrayElem1978) 1307 | z.DecStructFieldNotFound(yyj99-1, "") 1308 | } 1309 | z.DecSendContainerState(codecSelfer_containerArrayEnd1978) 1310 | } 1311 | 1312 | func (x Nodes) CodecEncodeSelf(e *codec1978.Encoder) { 1313 | var h codecSelfer1978 1314 | z, r := codec1978.GenHelperEncoder(e) 1315 | _, _, _ = h, z, r 1316 | if x == nil { 1317 | r.EncodeNil() 1318 | } else { 1319 | yym110 := z.EncBinary() 1320 | _ = yym110 1321 | if false { 1322 | } else if z.HasExtensions() && z.EncExt(x) { 1323 | } else { 1324 | h.encNodes((Nodes)(x), e) 1325 | } 1326 | } 1327 | } 1328 | 1329 | func (x *Nodes) CodecDecodeSelf(d *codec1978.Decoder) { 1330 | var h codecSelfer1978 1331 | z, r := codec1978.GenHelperDecoder(d) 1332 | _, _, _ = h, z, r 1333 | yym111 := z.DecBinary() 1334 | _ = yym111 1335 | if false { 1336 | } else if z.HasExtensions() && z.DecExt(x) { 1337 | } else { 1338 | h.decNodes((*Nodes)(x), d) 1339 | } 1340 | } 1341 | 1342 | func (x codecSelfer1978) enchttp_Header(v pkg1_http.Header, e *codec1978.Encoder) { 1343 | var h codecSelfer1978 1344 | z, r := codec1978.GenHelperEncoder(e) 1345 | _, _, _ = h, z, r 1346 | r.EncodeMapStart(len(v)) 1347 | for yyk112, yyv112 := range v { 1348 | z.EncSendContainerState(codecSelfer_containerMapKey1978) 1349 | yym113 := z.EncBinary() 1350 | _ = yym113 1351 | if false { 1352 | } else { 1353 | r.EncodeString(codecSelferC_UTF81978, string(yyk112)) 1354 | } 1355 | z.EncSendContainerState(codecSelfer_containerMapValue1978) 1356 | if yyv112 == nil { 1357 | r.EncodeNil() 1358 | } else { 1359 | yym114 := z.EncBinary() 1360 | _ = yym114 1361 | if false { 1362 | } else { 1363 | z.F.EncSliceStringV(yyv112, false, e) 1364 | } 1365 | } 1366 | } 1367 | z.EncSendContainerState(codecSelfer_containerMapEnd1978) 1368 | } 1369 | 1370 | func (x codecSelfer1978) dechttp_Header(v *pkg1_http.Header, d *codec1978.Decoder) { 1371 | var h codecSelfer1978 1372 | z, r := codec1978.GenHelperDecoder(d) 1373 | _, _, _ = h, z, r 1374 | 1375 | yyv115 := *v 1376 | yyl115 := r.ReadMapStart() 1377 | yybh115 := z.DecBasicHandle() 1378 | if yyv115 == nil { 1379 | yyrl115, _ := z.DecInferLen(yyl115, yybh115.MaxInitLen, 40) 1380 | yyv115 = make(map[string][]string, yyrl115) 1381 | *v = yyv115 1382 | } 1383 | var yymk115 string 1384 | var yymv115 []string 1385 | var yymg115 bool 1386 | if yybh115.MapValueReset { 1387 | yymg115 = true 1388 | } 1389 | if yyl115 > 0 { 1390 | for yyj115 := 0; yyj115 < yyl115; yyj115++ { 1391 | z.DecSendContainerState(codecSelfer_containerMapKey1978) 1392 | if r.TryDecodeAsNil() { 1393 | yymk115 = "" 1394 | } else { 1395 | yymk115 = string(r.DecodeString()) 1396 | } 1397 | 1398 | if yymg115 { 1399 | yymv115 = yyv115[yymk115] 1400 | } else { 1401 | yymv115 = nil 1402 | } 1403 | z.DecSendContainerState(codecSelfer_containerMapValue1978) 1404 | if r.TryDecodeAsNil() { 1405 | yymv115 = nil 1406 | } else { 1407 | yyv117 := &yymv115 1408 | yym118 := z.DecBinary() 1409 | _ = yym118 1410 | if false { 1411 | } else { 1412 | z.F.DecSliceStringX(yyv117, false, d) 1413 | } 1414 | } 1415 | 1416 | if yyv115 != nil { 1417 | yyv115[yymk115] = yymv115 1418 | } 1419 | } 1420 | } else if yyl115 < 0 { 1421 | for yyj115 := 0; !r.CheckBreak(); yyj115++ { 1422 | z.DecSendContainerState(codecSelfer_containerMapKey1978) 1423 | if r.TryDecodeAsNil() { 1424 | yymk115 = "" 1425 | } else { 1426 | yymk115 = string(r.DecodeString()) 1427 | } 1428 | 1429 | if yymg115 { 1430 | yymv115 = yyv115[yymk115] 1431 | } else { 1432 | yymv115 = nil 1433 | } 1434 | z.DecSendContainerState(codecSelfer_containerMapValue1978) 1435 | if r.TryDecodeAsNil() { 1436 | yymv115 = nil 1437 | } else { 1438 | yyv120 := &yymv115 1439 | yym121 := z.DecBinary() 1440 | _ = yym121 1441 | if false { 1442 | } else { 1443 | z.F.DecSliceStringX(yyv120, false, d) 1444 | } 1445 | } 1446 | 1447 | if yyv115 != nil { 1448 | yyv115[yymk115] = yymv115 1449 | } 1450 | } 1451 | } // else len==0: TODO: Should we clear map entries? 1452 | z.DecSendContainerState(codecSelfer_containerMapEnd1978) 1453 | } 1454 | 1455 | func (x codecSelfer1978) encNodes(v Nodes, e *codec1978.Encoder) { 1456 | var h codecSelfer1978 1457 | z, r := codec1978.GenHelperEncoder(e) 1458 | _, _, _ = h, z, r 1459 | r.EncodeArrayStart(len(v)) 1460 | for _, yyv122 := range v { 1461 | z.EncSendContainerState(codecSelfer_containerArrayElem1978) 1462 | if yyv122 == nil { 1463 | r.EncodeNil() 1464 | } else { 1465 | yyv122.CodecEncodeSelf(e) 1466 | } 1467 | } 1468 | z.EncSendContainerState(codecSelfer_containerArrayEnd1978) 1469 | } 1470 | 1471 | func (x codecSelfer1978) decNodes(v *Nodes, d *codec1978.Decoder) { 1472 | var h codecSelfer1978 1473 | z, r := codec1978.GenHelperDecoder(d) 1474 | _, _, _ = h, z, r 1475 | 1476 | yyv123 := *v 1477 | yyh123, yyl123 := z.DecSliceHelperStart() 1478 | var yyc123 bool 1479 | if yyl123 == 0 { 1480 | if yyv123 == nil { 1481 | yyv123 = []*Node{} 1482 | yyc123 = true 1483 | } else if len(yyv123) != 0 { 1484 | yyv123 = yyv123[:0] 1485 | yyc123 = true 1486 | } 1487 | } else if yyl123 > 0 { 1488 | var yyrr123, yyrl123 int 1489 | var yyrt123 bool 1490 | if yyl123 > cap(yyv123) { 1491 | 1492 | yyrg123 := len(yyv123) > 0 1493 | yyv2123 := yyv123 1494 | yyrl123, yyrt123 = z.DecInferLen(yyl123, z.DecBasicHandle().MaxInitLen, 8) 1495 | if yyrt123 { 1496 | if yyrl123 <= cap(yyv123) { 1497 | yyv123 = yyv123[:yyrl123] 1498 | } else { 1499 | yyv123 = make([]*Node, yyrl123) 1500 | } 1501 | } else { 1502 | yyv123 = make([]*Node, yyrl123) 1503 | } 1504 | yyc123 = true 1505 | yyrr123 = len(yyv123) 1506 | if yyrg123 { 1507 | copy(yyv123, yyv2123) 1508 | } 1509 | } else if yyl123 != len(yyv123) { 1510 | yyv123 = yyv123[:yyl123] 1511 | yyc123 = true 1512 | } 1513 | yyj123 := 0 1514 | for ; yyj123 < yyrr123; yyj123++ { 1515 | yyh123.ElemContainerState(yyj123) 1516 | if r.TryDecodeAsNil() { 1517 | if yyv123[yyj123] != nil { 1518 | *yyv123[yyj123] = Node{} 1519 | } 1520 | } else { 1521 | if yyv123[yyj123] == nil { 1522 | yyv123[yyj123] = new(Node) 1523 | } 1524 | yyw124 := yyv123[yyj123] 1525 | yyw124.CodecDecodeSelf(d) 1526 | } 1527 | 1528 | } 1529 | if yyrt123 { 1530 | for ; yyj123 < yyl123; yyj123++ { 1531 | yyv123 = append(yyv123, nil) 1532 | yyh123.ElemContainerState(yyj123) 1533 | if r.TryDecodeAsNil() { 1534 | if yyv123[yyj123] != nil { 1535 | *yyv123[yyj123] = Node{} 1536 | } 1537 | } else { 1538 | if yyv123[yyj123] == nil { 1539 | yyv123[yyj123] = new(Node) 1540 | } 1541 | yyw125 := yyv123[yyj123] 1542 | yyw125.CodecDecodeSelf(d) 1543 | } 1544 | 1545 | } 1546 | } 1547 | 1548 | } else { 1549 | yyj123 := 0 1550 | for ; !r.CheckBreak(); yyj123++ { 1551 | 1552 | if yyj123 >= len(yyv123) { 1553 | yyv123 = append(yyv123, nil) // var yyz123 *Node 1554 | yyc123 = true 1555 | } 1556 | yyh123.ElemContainerState(yyj123) 1557 | if yyj123 < len(yyv123) { 1558 | if r.TryDecodeAsNil() { 1559 | if yyv123[yyj123] != nil { 1560 | *yyv123[yyj123] = Node{} 1561 | } 1562 | } else { 1563 | if yyv123[yyj123] == nil { 1564 | yyv123[yyj123] = new(Node) 1565 | } 1566 | yyw126 := yyv123[yyj123] 1567 | yyw126.CodecDecodeSelf(d) 1568 | } 1569 | 1570 | } else { 1571 | z.DecSwallow() 1572 | } 1573 | 1574 | } 1575 | if yyj123 < len(yyv123) { 1576 | yyv123 = yyv123[:yyj123] 1577 | yyc123 = true 1578 | } else if yyj123 == 0 && yyv123 == nil { 1579 | yyv123 = []*Node{} 1580 | yyc123 = true 1581 | } 1582 | } 1583 | yyh123.End() 1584 | if yyc123 { 1585 | *v = yyv123 1586 | } 1587 | } 1588 | -------------------------------------------------------------------------------- /etcd/response.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | //go:generate codecgen -d 1978 -o response.generated.go response.go 4 | 5 | import ( 6 | "net/http" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/ugorji/go/codec" 11 | ) 12 | 13 | const ( 14 | rawResponse = iota 15 | normalResponse 16 | ) 17 | 18 | type responseType int 19 | 20 | type RawResponse struct { 21 | StatusCode int 22 | Body []byte 23 | Header http.Header 24 | } 25 | 26 | var ( 27 | validHttpStatusCode = map[int]bool{ 28 | http.StatusCreated: true, 29 | http.StatusOK: true, 30 | http.StatusBadRequest: true, 31 | http.StatusNotFound: true, 32 | http.StatusPreconditionFailed: true, 33 | http.StatusForbidden: true, 34 | http.StatusUnauthorized: true, 35 | } 36 | ) 37 | 38 | // Unmarshal parses RawResponse and stores the result in Response 39 | func (rr *RawResponse) Unmarshal() (*Response, error) { 40 | if rr.StatusCode != http.StatusOK && rr.StatusCode != http.StatusCreated { 41 | return nil, handleError(rr.Body) 42 | } 43 | 44 | resp := new(Response) 45 | 46 | err := codec.NewDecoderBytes(rr.Body, new(codec.JsonHandle)).Decode(resp) 47 | 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | // attach index and term to response 53 | resp.EtcdIndex, _ = strconv.ParseUint(rr.Header.Get("X-Etcd-Index"), 10, 64) 54 | resp.RaftIndex, _ = strconv.ParseUint(rr.Header.Get("X-Raft-Index"), 10, 64) 55 | resp.RaftTerm, _ = strconv.ParseUint(rr.Header.Get("X-Raft-Term"), 10, 64) 56 | 57 | return resp, nil 58 | } 59 | 60 | type Response struct { 61 | Action string `json:"action"` 62 | Node *Node `json:"node"` 63 | PrevNode *Node `json:"prevNode,omitempty"` 64 | EtcdIndex uint64 `json:"etcdIndex"` 65 | RaftIndex uint64 `json:"raftIndex"` 66 | RaftTerm uint64 `json:"raftTerm"` 67 | } 68 | 69 | type Node struct { 70 | Key string `json:"key, omitempty"` 71 | Value string `json:"value,omitempty"` 72 | Dir bool `json:"dir,omitempty"` 73 | Expiration *time.Time `json:"expiration,omitempty"` 74 | TTL int64 `json:"ttl,omitempty"` 75 | Nodes Nodes `json:"nodes,omitempty"` 76 | ModifiedIndex uint64 `json:"modifiedIndex,omitempty"` 77 | CreatedIndex uint64 `json:"createdIndex,omitempty"` 78 | } 79 | 80 | type Nodes []*Node 81 | 82 | // interfaces for sorting 83 | func (ns Nodes) Len() int { 84 | return len(ns) 85 | } 86 | 87 | func (ns Nodes) Less(i, j int) bool { 88 | return ns[i].Key < ns[j].Key 89 | } 90 | 91 | func (ns Nodes) Swap(i, j int) { 92 | ns[i], ns[j] = ns[j], ns[i] 93 | } 94 | -------------------------------------------------------------------------------- /etcd/response_test.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "net/http" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/ugorji/go/codec" 10 | ) 11 | 12 | func createTestNode(size int) *Node { 13 | return &Node{ 14 | Key: strings.Repeat("a", 30), 15 | Value: strings.Repeat("a", size), 16 | TTL: 123456789, 17 | ModifiedIndex: 123456, 18 | CreatedIndex: 123456, 19 | } 20 | } 21 | 22 | func createTestNodeWithChildren(children, size int) *Node { 23 | node := createTestNode(size) 24 | for i := 0; i < children; i++ { 25 | node.Nodes = append(node.Nodes, createTestNode(size)) 26 | } 27 | return node 28 | } 29 | 30 | func createTestResponse(children, size int) *Response { 31 | return &Response{ 32 | Action: "aaaaa", 33 | Node: createTestNodeWithChildren(children, size), 34 | PrevNode: nil, 35 | EtcdIndex: 123456, 36 | RaftIndex: 123456, 37 | RaftTerm: 123456, 38 | } 39 | } 40 | 41 | func benchmarkResponseUnmarshalling(b *testing.B, children, size int) { 42 | response := createTestResponse(children, size) 43 | 44 | rr := RawResponse{http.StatusOK, make([]byte, 0), http.Header{}} 45 | codec.NewEncoderBytes(&rr.Body, new(codec.JsonHandle)).Encode(response) 46 | 47 | b.ResetTimer() 48 | newResponse := new(Response) 49 | var err error 50 | for i := 0; i < b.N; i++ { 51 | if newResponse, err = rr.Unmarshal(); err != nil { 52 | b.Errorf("Error: %v", err) 53 | } 54 | 55 | } 56 | if !reflect.DeepEqual(response.Node, newResponse.Node) { 57 | b.Errorf("Unexpected difference in a parsed response: \n%+v\n%+v", response, newResponse) 58 | } 59 | } 60 | 61 | func BenchmarkSmallResponseUnmarshal(b *testing.B) { 62 | benchmarkResponseUnmarshalling(b, 30, 20) 63 | } 64 | 65 | func BenchmarkManySmallResponseUnmarshal(b *testing.B) { 66 | benchmarkResponseUnmarshalling(b, 3000, 20) 67 | } 68 | 69 | func BenchmarkMediumResponseUnmarshal(b *testing.B) { 70 | benchmarkResponseUnmarshalling(b, 300, 200) 71 | } 72 | 73 | func BenchmarkLargeResponseUnmarshal(b *testing.B) { 74 | benchmarkResponseUnmarshalling(b, 3000, 2000) 75 | } 76 | -------------------------------------------------------------------------------- /etcd/set_curl_chan_test.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestSetCurlChan(t *testing.T) { 9 | c := NewClient(nil) 10 | c.OpenCURL() 11 | 12 | defer func() { 13 | c.Delete("foo", true) 14 | }() 15 | 16 | _, err := c.Set("foo", "bar", 5) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | expected := fmt.Sprintf("curl -X PUT %s/v2/keys/foo -d value=bar -d ttl=5", 22 | c.cluster.pick()) 23 | actual := c.RecvCURL() 24 | if expected != actual { 25 | t.Fatalf(`Command "%s" is not equal to expected value "%s"`, 26 | actual, expected) 27 | } 28 | 29 | c.SetConsistency(STRONG_CONSISTENCY) 30 | _, err = c.Get("foo", false, false) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | expected = fmt.Sprintf("curl -X GET %s/v2/keys/foo?quorum=true&recursive=false&sorted=false", 36 | c.cluster.pick()) 37 | actual = c.RecvCURL() 38 | if expected != actual { 39 | t.Fatalf(`Command "%s" is not equal to expected value "%s"`, 40 | actual, expected) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /etcd/set_update_create.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | // Set sets the given key to the given value. 4 | // It will create a new key value pair or replace the old one. 5 | // It will not replace a existing directory. 6 | func (c *Client) Set(key string, value string, ttl uint64) (*Response, error) { 7 | raw, err := c.RawSet(key, value, ttl) 8 | 9 | if err != nil { 10 | return nil, err 11 | } 12 | 13 | return raw.Unmarshal() 14 | } 15 | 16 | // SetDir sets the given key to a directory. 17 | // It will create a new directory or replace the old key value pair by a directory. 18 | // It will not replace a existing directory. 19 | func (c *Client) SetDir(key string, ttl uint64) (*Response, error) { 20 | raw, err := c.RawSetDir(key, ttl) 21 | 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | return raw.Unmarshal() 27 | } 28 | 29 | // CreateDir creates a directory. It succeeds only if 30 | // the given key does not yet exist. 31 | func (c *Client) CreateDir(key string, ttl uint64) (*Response, error) { 32 | raw, err := c.RawCreateDir(key, ttl) 33 | 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | return raw.Unmarshal() 39 | } 40 | 41 | // UpdateDir updates the given directory. It succeeds only if the 42 | // given key already exists. 43 | func (c *Client) UpdateDir(key string, ttl uint64) (*Response, error) { 44 | raw, err := c.RawUpdateDir(key, ttl) 45 | 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | return raw.Unmarshal() 51 | } 52 | 53 | // Create creates a file with the given value under the given key. It succeeds 54 | // only if the given key does not yet exist. 55 | func (c *Client) Create(key string, value string, ttl uint64) (*Response, error) { 56 | raw, err := c.RawCreate(key, value, ttl) 57 | 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | return raw.Unmarshal() 63 | } 64 | 65 | // CreateInOrder creates a file with a key that's guaranteed to be higher than other 66 | // keys in the given directory. It is useful for creating queues. 67 | func (c *Client) CreateInOrder(dir string, value string, ttl uint64) (*Response, error) { 68 | raw, err := c.RawCreateInOrder(dir, value, ttl) 69 | 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | return raw.Unmarshal() 75 | } 76 | 77 | // Update updates the given key to the given value. It succeeds only if the 78 | // given key already exists. 79 | func (c *Client) Update(key string, value string, ttl uint64) (*Response, error) { 80 | raw, err := c.RawUpdate(key, value, ttl) 81 | 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | return raw.Unmarshal() 87 | } 88 | 89 | func (c *Client) RawUpdateDir(key string, ttl uint64) (*RawResponse, error) { 90 | ops := Options{ 91 | "prevExist": true, 92 | "dir": true, 93 | } 94 | 95 | return c.put(key, "", ttl, ops) 96 | } 97 | 98 | func (c *Client) RawCreateDir(key string, ttl uint64) (*RawResponse, error) { 99 | ops := Options{ 100 | "prevExist": false, 101 | "dir": true, 102 | } 103 | 104 | return c.put(key, "", ttl, ops) 105 | } 106 | 107 | func (c *Client) RawSet(key string, value string, ttl uint64) (*RawResponse, error) { 108 | return c.put(key, value, ttl, nil) 109 | } 110 | 111 | func (c *Client) RawSetDir(key string, ttl uint64) (*RawResponse, error) { 112 | ops := Options{ 113 | "dir": true, 114 | } 115 | 116 | return c.put(key, "", ttl, ops) 117 | } 118 | 119 | func (c *Client) RawUpdate(key string, value string, ttl uint64) (*RawResponse, error) { 120 | ops := Options{ 121 | "prevExist": true, 122 | } 123 | 124 | return c.put(key, value, ttl, ops) 125 | } 126 | 127 | func (c *Client) RawCreate(key string, value string, ttl uint64) (*RawResponse, error) { 128 | ops := Options{ 129 | "prevExist": false, 130 | } 131 | 132 | return c.put(key, value, ttl, ops) 133 | } 134 | 135 | func (c *Client) RawCreateInOrder(dir string, value string, ttl uint64) (*RawResponse, error) { 136 | return c.post(dir, value, ttl) 137 | } 138 | -------------------------------------------------------------------------------- /etcd/set_update_create_test.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSet(t *testing.T) { 8 | c := NewClient(nil) 9 | defer func() { 10 | c.Delete("foo", true) 11 | }() 12 | 13 | resp, err := c.Set("foo", "bar", 5) 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | if resp.Node.Key != "/foo" || resp.Node.Value != "bar" || resp.Node.TTL != 5 { 18 | t.Fatalf("Set 1 failed: %#v", resp) 19 | } 20 | if resp.PrevNode != nil { 21 | t.Fatalf("Set 1 PrevNode failed: %#v", resp) 22 | } 23 | 24 | resp, err = c.Set("foo", "bar2", 5) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | if !(resp.Node.Key == "/foo" && resp.Node.Value == "bar2" && resp.Node.TTL == 5) { 29 | t.Fatalf("Set 2 failed: %#v", resp) 30 | } 31 | if resp.PrevNode.Key != "/foo" || resp.PrevNode.Value != "bar" || resp.Node.TTL != 5 { 32 | t.Fatalf("Set 2 PrevNode failed: %#v", resp) 33 | } 34 | } 35 | 36 | func TestUpdate(t *testing.T) { 37 | c := NewClient(nil) 38 | defer func() { 39 | c.Delete("foo", true) 40 | c.Delete("nonexistent", true) 41 | }() 42 | 43 | resp, err := c.Set("foo", "bar", 5) 44 | 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | 49 | // This should succeed. 50 | resp, err = c.Update("foo", "wakawaka", 5) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | 55 | if !(resp.Action == "update" && resp.Node.Key == "/foo" && resp.Node.TTL == 5) { 56 | t.Fatalf("Update 1 failed: %#v", resp) 57 | } 58 | if !(resp.PrevNode.Key == "/foo" && resp.PrevNode.Value == "bar" && resp.Node.TTL == 5) { 59 | t.Fatalf("Update 1 prevValue failed: %#v", resp) 60 | } 61 | 62 | // This should fail because the key does not exist. 63 | resp, err = c.Update("nonexistent", "whatever", 5) 64 | if err == nil { 65 | t.Fatalf("The key %v did not exist, so the update should have failed."+ 66 | "The response was: %#v", resp.Node.Key, resp) 67 | } 68 | } 69 | 70 | func TestCreate(t *testing.T) { 71 | c := NewClient(nil) 72 | defer func() { 73 | c.Delete("newKey", true) 74 | }() 75 | 76 | newKey := "/newKey" 77 | newValue := "/newValue" 78 | 79 | // This should succeed 80 | resp, err := c.Create(newKey, newValue, 5) 81 | if err != nil { 82 | t.Fatal(err) 83 | } 84 | 85 | if !(resp.Action == "create" && resp.Node.Key == newKey && 86 | resp.Node.Value == newValue && resp.Node.TTL == 5) { 87 | t.Fatalf("Create 1 failed: %#v", resp) 88 | } 89 | if resp.PrevNode != nil { 90 | t.Fatalf("Create 1 PrevNode failed: %#v", resp) 91 | } 92 | 93 | // This should fail, because the key is already there 94 | resp, err = c.Create(newKey, newValue, 5) 95 | if err == nil { 96 | t.Fatalf("The key %v did exist, so the creation should have failed."+ 97 | "The response was: %#v", resp.Node.Key, resp) 98 | } 99 | } 100 | 101 | func TestCreateInOrder(t *testing.T) { 102 | c := NewClient(nil) 103 | dir := "/queue" 104 | defer func() { 105 | c.DeleteDir(dir) 106 | }() 107 | 108 | var firstKey, secondKey string 109 | 110 | resp, err := c.CreateInOrder(dir, "1", 5) 111 | if err != nil { 112 | t.Fatal(err) 113 | } 114 | 115 | if !(resp.Action == "create" && resp.Node.Value == "1" && resp.Node.TTL == 5) { 116 | t.Fatalf("Create 1 failed: %#v", resp) 117 | } 118 | 119 | firstKey = resp.Node.Key 120 | 121 | resp, err = c.CreateInOrder(dir, "2", 5) 122 | if err != nil { 123 | t.Fatal(err) 124 | } 125 | 126 | if !(resp.Action == "create" && resp.Node.Value == "2" && resp.Node.TTL == 5) { 127 | t.Fatalf("Create 2 failed: %#v", resp) 128 | } 129 | 130 | secondKey = resp.Node.Key 131 | 132 | if firstKey >= secondKey { 133 | t.Fatalf("Expected first key to be greater than second key, but %s is not greater than %s", 134 | firstKey, secondKey) 135 | } 136 | } 137 | 138 | func TestSetDir(t *testing.T) { 139 | c := NewClient(nil) 140 | defer func() { 141 | c.Delete("foo", true) 142 | c.Delete("fooDir", true) 143 | }() 144 | 145 | resp, err := c.CreateDir("fooDir", 5) 146 | if err != nil { 147 | t.Fatal(err) 148 | } 149 | if !(resp.Node.Key == "/fooDir" && resp.Node.Value == "" && resp.Node.TTL == 5) { 150 | t.Fatalf("SetDir 1 failed: %#v", resp) 151 | } 152 | if resp.PrevNode != nil { 153 | t.Fatalf("SetDir 1 PrevNode failed: %#v", resp) 154 | } 155 | 156 | // This should fail because /fooDir already points to a directory 157 | resp, err = c.CreateDir("/fooDir", 5) 158 | if err == nil { 159 | t.Fatalf("fooDir already points to a directory, so SetDir should have failed."+ 160 | "The response was: %#v", resp) 161 | } 162 | 163 | _, err = c.Set("foo", "bar", 5) 164 | if err != nil { 165 | t.Fatal(err) 166 | } 167 | 168 | // This should succeed 169 | // It should replace the key 170 | resp, err = c.SetDir("foo", 5) 171 | if err != nil { 172 | t.Fatal(err) 173 | } 174 | if !(resp.Node.Key == "/foo" && resp.Node.Value == "" && resp.Node.TTL == 5) { 175 | t.Fatalf("SetDir 2 failed: %#v", resp) 176 | } 177 | if !(resp.PrevNode.Key == "/foo" && resp.PrevNode.Value == "bar" && resp.PrevNode.TTL == 5) { 178 | t.Fatalf("SetDir 2 failed: %#v", resp) 179 | } 180 | } 181 | 182 | func TestUpdateDir(t *testing.T) { 183 | c := NewClient(nil) 184 | defer func() { 185 | c.Delete("fooDir", true) 186 | }() 187 | 188 | resp, err := c.CreateDir("fooDir", 5) 189 | if err != nil { 190 | t.Fatal(err) 191 | } 192 | 193 | // This should succeed. 194 | resp, err = c.UpdateDir("fooDir", 5) 195 | if err != nil { 196 | t.Fatal(err) 197 | } 198 | 199 | if !(resp.Action == "update" && resp.Node.Key == "/fooDir" && 200 | resp.Node.Value == "" && resp.Node.TTL == 5) { 201 | t.Fatalf("UpdateDir 1 failed: %#v", resp) 202 | } 203 | if !(resp.PrevNode.Key == "/fooDir" && resp.PrevNode.Dir == true && resp.PrevNode.TTL == 5) { 204 | t.Fatalf("UpdateDir 1 PrevNode failed: %#v", resp) 205 | } 206 | 207 | // This should fail because the key does not exist. 208 | resp, err = c.UpdateDir("nonexistentDir", 5) 209 | if err == nil { 210 | t.Fatalf("The key %v did not exist, so the update should have failed."+ 211 | "The response was: %#v", resp.Node.Key, resp) 212 | } 213 | } 214 | 215 | func TestCreateDir(t *testing.T) { 216 | c := NewClient(nil) 217 | defer func() { 218 | c.Delete("fooDir", true) 219 | }() 220 | 221 | // This should succeed 222 | resp, err := c.CreateDir("fooDir", 5) 223 | if err != nil { 224 | t.Fatal(err) 225 | } 226 | 227 | if !(resp.Action == "create" && resp.Node.Key == "/fooDir" && 228 | resp.Node.Value == "" && resp.Node.TTL == 5) { 229 | t.Fatalf("CreateDir 1 failed: %#v", resp) 230 | } 231 | if resp.PrevNode != nil { 232 | t.Fatalf("CreateDir 1 PrevNode failed: %#v", resp) 233 | } 234 | 235 | // This should fail, because the key is already there 236 | resp, err = c.CreateDir("fooDir", 5) 237 | if err == nil { 238 | t.Fatalf("The key %v did exist, so the creation should have failed."+ 239 | "The response was: %#v", resp.Node.Key, resp) 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /etcd/shuffle.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "math/rand" 5 | ) 6 | 7 | func shuffleStringSlice(cards []string) []string { 8 | size := len(cards) 9 | //Do not need to copy if nothing changed 10 | if size <= 1 { 11 | return cards 12 | } 13 | shuffled := make([]string, size) 14 | index := rand.Perm(size) 15 | for i := range cards { 16 | shuffled[index[i]] = cards[i] 17 | } 18 | return shuffled 19 | } 20 | -------------------------------------------------------------------------------- /etcd/version.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | const ( 4 | version = "v2" 5 | packageVersion = "v2.0.0+git" 6 | ) 7 | -------------------------------------------------------------------------------- /etcd/watch.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // Errors introduced by the Watch command. 8 | var ( 9 | ErrWatchStoppedByUser = errors.New("Watch stopped by the user via stop channel") 10 | ) 11 | 12 | // If recursive is set to true the watch returns the first change under the given 13 | // prefix since the given index. 14 | // 15 | // If recursive is set to false the watch returns the first change to the given key 16 | // since the given index. 17 | // 18 | // To watch for the latest change, set waitIndex = 0. 19 | // 20 | // If a receiver channel is given, it will be a long-term watch. Watch will block at the 21 | //channel. After someone receives the channel, it will go on to watch that 22 | // prefix. If a stop channel is given, the client can close long-term watch using 23 | // the stop channel. 24 | func (c *Client) Watch(prefix string, waitIndex uint64, recursive bool, 25 | receiver chan *Response, stop chan bool) (*Response, error) { 26 | logger.Debugf("watch %s [%s]", prefix, c.cluster.Leader) 27 | if receiver == nil { 28 | raw, err := c.watchOnce(prefix, waitIndex, recursive, stop) 29 | 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | return raw.Unmarshal() 35 | } 36 | defer close(receiver) 37 | 38 | for { 39 | raw, err := c.watchOnce(prefix, waitIndex, recursive, stop) 40 | 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | resp, err := raw.Unmarshal() 46 | 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | waitIndex = resp.Node.ModifiedIndex + 1 52 | receiver <- resp 53 | } 54 | } 55 | 56 | func (c *Client) RawWatch(prefix string, waitIndex uint64, recursive bool, 57 | receiver chan *RawResponse, stop chan bool) (*RawResponse, error) { 58 | 59 | logger.Debugf("rawWatch %s [%s]", prefix, c.cluster.Leader) 60 | if receiver == nil { 61 | return c.watchOnce(prefix, waitIndex, recursive, stop) 62 | } 63 | 64 | for { 65 | raw, err := c.watchOnce(prefix, waitIndex, recursive, stop) 66 | 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | resp, err := raw.Unmarshal() 72 | 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | waitIndex = resp.Node.ModifiedIndex + 1 78 | receiver <- raw 79 | } 80 | } 81 | 82 | // helper func 83 | // return when there is change under the given prefix 84 | func (c *Client) watchOnce(key string, waitIndex uint64, recursive bool, stop chan bool) (*RawResponse, error) { 85 | 86 | options := Options{ 87 | "wait": true, 88 | } 89 | if waitIndex > 0 { 90 | options["waitIndex"] = waitIndex 91 | } 92 | if recursive { 93 | options["recursive"] = true 94 | } 95 | 96 | resp, err := c.getCancelable(key, options, stop) 97 | 98 | if err == ErrRequestCancelled { 99 | return nil, ErrWatchStoppedByUser 100 | } 101 | 102 | return resp, err 103 | } 104 | -------------------------------------------------------------------------------- /etcd/watch_test.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestWatch(t *testing.T) { 11 | c := NewClient(nil) 12 | defer func() { 13 | c.Delete("watch_foo", true) 14 | }() 15 | 16 | go setHelper("watch_foo", "bar", c) 17 | 18 | resp, err := c.Watch("watch_foo", 0, false, nil, nil) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | if !(resp.Node.Key == "/watch_foo" && resp.Node.Value == "bar") { 23 | t.Fatalf("Watch 1 failed: %#v", resp) 24 | } 25 | 26 | go setHelper("watch_foo", "bar", c) 27 | 28 | resp, err = c.Watch("watch_foo", resp.Node.ModifiedIndex+1, false, nil, nil) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | if !(resp.Node.Key == "/watch_foo" && resp.Node.Value == "bar") { 33 | t.Fatalf("Watch 2 failed: %#v", resp) 34 | } 35 | 36 | routineNum := runtime.NumGoroutine() 37 | 38 | ch := make(chan *Response, 10) 39 | stop := make(chan bool, 1) 40 | 41 | go setLoop("watch_foo", "bar", c) 42 | 43 | go receiver(ch, stop) 44 | 45 | _, err = c.Watch("watch_foo", 0, false, ch, stop) 46 | if err != ErrWatchStoppedByUser { 47 | t.Fatalf("Watch returned a non-user stop error") 48 | } 49 | 50 | if newRoutineNum := runtime.NumGoroutine(); newRoutineNum != routineNum { 51 | t.Fatalf("Routine numbers differ after watch stop: %v, %v", routineNum, newRoutineNum) 52 | } 53 | } 54 | 55 | func TestWatchAll(t *testing.T) { 56 | c := NewClient(nil) 57 | defer func() { 58 | c.Delete("watch_foo", true) 59 | }() 60 | 61 | go setHelper("watch_foo/foo", "bar", c) 62 | 63 | resp, err := c.Watch("watch_foo", 0, true, nil, nil) 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | if !(resp.Node.Key == "/watch_foo/foo" && resp.Node.Value == "bar") { 68 | t.Fatalf("WatchAll 1 failed: %#v", resp) 69 | } 70 | 71 | go setHelper("watch_foo/foo", "bar", c) 72 | 73 | resp, err = c.Watch("watch_foo", resp.Node.ModifiedIndex+1, true, nil, nil) 74 | if err != nil { 75 | t.Fatal(err) 76 | } 77 | if !(resp.Node.Key == "/watch_foo/foo" && resp.Node.Value == "bar") { 78 | t.Fatalf("WatchAll 2 failed: %#v", resp) 79 | } 80 | 81 | ch := make(chan *Response, 10) 82 | stop := make(chan bool, 1) 83 | 84 | routineNum := runtime.NumGoroutine() 85 | 86 | go setLoop("watch_foo/foo", "bar", c) 87 | 88 | go receiver(ch, stop) 89 | 90 | _, err = c.Watch("watch_foo", 0, true, ch, stop) 91 | if err != ErrWatchStoppedByUser { 92 | t.Fatalf("Watch returned a non-user stop error") 93 | } 94 | 95 | if newRoutineNum := runtime.NumGoroutine(); newRoutineNum != routineNum { 96 | t.Fatalf("Routine numbers differ after watch stop: %v, %v", routineNum, newRoutineNum) 97 | } 98 | } 99 | 100 | func setHelper(key, value string, c *Client) { 101 | time.Sleep(time.Second) 102 | c.Set(key, value, 100) 103 | } 104 | 105 | func setLoop(key, value string, c *Client) { 106 | time.Sleep(time.Second) 107 | for i := 0; i < 10; i++ { 108 | newValue := fmt.Sprintf("%s_%v", value, i) 109 | c.Set(key, newValue, 100) 110 | time.Sleep(time.Second / 10) 111 | } 112 | } 113 | 114 | func receiver(c chan *Response, stop chan bool) { 115 | for i := 0; i < 10; i++ { 116 | <-c 117 | } 118 | stop <- true 119 | } 120 | --------------------------------------------------------------------------------