diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index a1dda88..ede5b80 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,6 +1,7 @@ { "ImportPath": "github.com/upfluence/etcdenv", "GoVersion": "go1.4", + "GodepVersion": "v80", "Packages": [ "./..." ], @@ -11,8 +12,8 @@ }, { "ImportPath": "github.com/coreos/go-etcd/etcd", - "Comment": "v0.2.0-rc1-127-g6fe04d5", - "Rev": "6fe04d580dfb71c9e34cbce2f4df9eefd1e1241e" + "Comment": "v2.0.0", + "Rev": "f02171fbd43c7b9b53ce8679b03235a1ef3c7b12" }, { "ImportPath": "github.com/op/go-logging", diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/LICENSE b/Godeps/_workspace/src/github.com/coreos/go-etcd/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/add_child_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/add_child_test.go deleted file mode 100644 index 26223ff..0000000 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/add_child_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package etcd - -import "testing" - -func TestAddChild(t *testing.T) { - c := NewClient(nil) - defer func() { - c.Delete("fooDir", true) - c.Delete("nonexistentDir", true) - }() - - c.CreateDir("fooDir", 5) - - _, err := c.AddChild("fooDir", "v0", 5) - if err != nil { - t.Fatal(err) - } - - _, err = c.AddChild("fooDir", "v1", 5) - if err != nil { - t.Fatal(err) - } - - resp, err := c.Get("fooDir", true, false) - // The child with v0 should proceed the child with v1 because it's added - // earlier, so it should have a lower key. - if !(len(resp.Node.Nodes) == 2 && (resp.Node.Nodes[0].Value == "v0" && resp.Node.Nodes[1].Value == "v1")) { - t.Fatalf("AddChild 1 failed. There should be two chlidren whose values are v0 and v1, respectively."+ - " The response was: %#v", resp) - } - - // Creating a child under a nonexistent directory should succeed. - // The directory should be created. - resp, err = c.AddChild("nonexistentDir", "foo", 5) - if err != nil { - t.Fatal(err) - } -} - -func TestAddChildDir(t *testing.T) { - c := NewClient(nil) - defer func() { - c.Delete("fooDir", true) - c.Delete("nonexistentDir", true) - }() - - c.CreateDir("fooDir", 5) - - _, err := c.AddChildDir("fooDir", 5) - if err != nil { - t.Fatal(err) - } - - _, err = c.AddChildDir("fooDir", 5) - if err != nil { - t.Fatal(err) - } - - resp, err := c.Get("fooDir", true, false) - // The child with v0 should proceed the child with v1 because it's added - // earlier, so it should have a lower key. - if !(len(resp.Node.Nodes) == 2 && (len(resp.Node.Nodes[0].Nodes) == 0 && len(resp.Node.Nodes[1].Nodes) == 0)) { - t.Fatalf("AddChildDir 1 failed. There should be two chlidren whose values are v0 and v1, respectively."+ - " The response was: %#v", resp) - } - - // Creating a child under a nonexistent directory should succeed. - // The directory should be created. - resp, err = c.AddChildDir("nonexistentDir", 5) - if err != nil { - t.Fatal(err) - } -} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client.go index f6ae548..c6cf334 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client.go +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client.go @@ -7,11 +7,13 @@ import ( "errors" "io" "io/ioutil" + "math/rand" "net" "net/http" "net/url" "os" "path" + "strings" "time" ) @@ -28,6 +30,10 @@ const ( defaultBufferSize = 10 ) +func init() { + rand.Seed(int64(time.Now().Nanosecond())) +} + type Config struct { CertFile string `json:"certFile"` KeyFile string `json:"keyFile"` @@ -36,10 +42,17 @@ type Config struct { Consistency string `json:"consistency"` } +type credentials struct { + username string + password string +} + type Client struct { config Config `json:"config"` cluster *Cluster `json:"cluster"` httpClient *http.Client + credentials *credentials + transport *http.Transport persistence io.Writer cURLch chan string // CheckRetry can be used to control the policy for failed requests @@ -64,8 +77,7 @@ func NewClient(machines []string) *Client { config := Config{ // default timeout is one second DialTimeout: time.Second, - // default consistency level is STRONG - Consistency: STRONG_CONSISTENCY, + Consistency: WEAK_CONSISTENCY, } client := &Client{ @@ -89,8 +101,7 @@ func NewTLSClient(machines []string, cert, key, caCert string) (*Client, error) config := Config{ // default timeout is one second DialTimeout: time.Second, - // default consistency level is STRONG - Consistency: STRONG_CONSISTENCY, + Consistency: WEAK_CONSISTENCY, CertFile: cert, KeyFile: key, CaCertFile: make([]string, 0), @@ -166,17 +177,27 @@ func NewClientFromReader(reader io.Reader) (*Client, error) { // Override the Client's HTTP Transport object func (c *Client) SetTransport(tr *http.Transport) { c.httpClient.Transport = tr + c.transport = tr +} + +func (c *Client) SetCredentials(username, password string) { + c.credentials = &credentials{username, password} +} + +func (c *Client) Close() { + c.transport.DisableKeepAlives = true + c.transport.CloseIdleConnections() } // initHTTPClient initializes a HTTP client for etcd client func (c *Client) initHTTPClient() { - tr := &http.Transport{ + c.transport = &http.Transport{ Dial: c.dial, TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, } - c.httpClient = &http.Client{Transport: tr} + c.httpClient = &http.Client{Transport: c.transport} } // initHTTPClient initializes a HTTPS client for etcd client @@ -292,31 +313,56 @@ func (c *Client) SyncCluster() bool { // internalSyncCluster syncs cluster information using the given machine list. func (c *Client) internalSyncCluster(machines []string) bool { for _, machine := range machines { - httpPath := c.createHttpPath(machine, path.Join(version, "machines")) + httpPath := c.createHttpPath(machine, path.Join(version, "members")) resp, err := c.httpClient.Get(httpPath) if err != nil { // try another machine in the cluster continue - } else { + } + + if resp.StatusCode != http.StatusOK { // fall-back to old endpoint + httpPath := c.createHttpPath(machine, path.Join(version, "machines")) + resp, err := c.httpClient.Get(httpPath) + if err != nil { + // try another machine in the cluster + continue + } b, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { // try another machine in the cluster continue } - // update Machines List c.cluster.updateFromStr(string(b)) + } else { + b, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + // try another machine in the cluster + continue + } + + var mCollection memberCollection + if err := json.Unmarshal(b, &mCollection); err != nil { + // try another machine + continue + } - // update leader - // the first one in the machine list is the leader - c.cluster.switchLeader(0) + urls := make([]string, 0) + for _, m := range mCollection { + urls = append(urls, m.ClientURLs...) + } - logger.Debug("sync.machines ", c.cluster.Machines) - c.saveConfig() - return true + // update Machines List + c.cluster.updateFromStr(strings.Join(urls, ",")) } + + logger.Debug("sync.machines ", c.cluster.Machines) + c.saveConfig() + return true } + return false } diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client_test.go deleted file mode 100644 index c245e47..0000000 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package etcd - -import ( - "encoding/json" - "fmt" - "net" - "net/url" - "os" - "testing" -) - -// To pass this test, we need to create a cluster of 3 machines -// The server should be listening on 127.0.0.1:4001, 4002, 4003 -func TestSync(t *testing.T) { - fmt.Println("Make sure there are three nodes at 0.0.0.0:4001-4003") - - // Explicit trailing slash to ensure this doesn't reproduce: - // https://github.com/coreos/go-etcd/issues/82 - c := NewClient([]string{"http://127.0.0.1:4001/"}) - - success := c.SyncCluster() - if !success { - t.Fatal("cannot sync machines") - } - - for _, m := range c.GetCluster() { - u, err := url.Parse(m) - if err != nil { - t.Fatal(err) - } - if u.Scheme != "http" { - t.Fatal("scheme must be http") - } - - host, _, err := net.SplitHostPort(u.Host) - if err != nil { - t.Fatal(err) - } - if host != "127.0.0.1" { - t.Fatal("Host must be 127.0.0.1") - } - } - - badMachines := []string{"abc", "edef"} - - success = c.SetCluster(badMachines) - - if success { - t.Fatal("should not sync on bad machines") - } - - goodMachines := []string{"127.0.0.1:4002"} - - success = c.SetCluster(goodMachines) - - if !success { - t.Fatal("cannot sync machines") - } else { - fmt.Println(c.cluster.Machines) - } - -} - -func TestPersistence(t *testing.T) { - c := NewClient(nil) - c.SyncCluster() - - fo, err := os.Create("config.json") - if err != nil { - t.Fatal(err) - } - defer func() { - if err := fo.Close(); err != nil { - panic(err) - } - }() - - c.SetPersistence(fo) - err = c.saveConfig() - if err != nil { - t.Fatal(err) - } - - c2, err := NewClientFromFile("config.json") - if err != nil { - t.Fatal(err) - } - - // Verify that the two clients have the same config - b1, _ := json.Marshal(c) - b2, _ := json.Marshal(c2) - - if string(b1) != string(b2) { - t.Fatalf("The two configs should be equal!") - } -} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/cluster.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/cluster.go index aaa2054..1ad3e15 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/cluster.go +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/cluster.go @@ -1,13 +1,14 @@ package etcd import ( - "net/url" + "math/rand" "strings" ) type Cluster struct { Leader string `json:"leader"` Machines []string `json:"machines"` + picked int } func NewCluster(machines []string) *Cluster { @@ -18,34 +19,19 @@ func NewCluster(machines []string) *Cluster { // default leader and machines return &Cluster{ - Leader: machines[0], + Leader: "", Machines: machines, + picked: rand.Intn(len(machines)), } } -// switchLeader switch the current leader to machines[num] -func (cl *Cluster) switchLeader(num int) { - logger.Debugf("switch.leader[from %v to %v]", - cl.Leader, cl.Machines[num]) - - cl.Leader = cl.Machines[num] -} +func (cl *Cluster) failure() { cl.picked = rand.Intn(len(cl.Machines)) } +func (cl *Cluster) pick() string { return cl.Machines[cl.picked] } func (cl *Cluster) updateFromStr(machines string) { - cl.Machines = strings.Split(machines, ", ") -} - -func (cl *Cluster) updateLeader(leader string) { - logger.Debugf("update.leader[%s,%s]", cl.Leader, leader) - cl.Leader = leader -} - -func (cl *Cluster) updateLeaderFromURL(u *url.URL) { - var leader string - if u.Scheme == "" { - leader = "http://" + u.Host - } else { - leader = u.Scheme + "://" + u.Host + cl.Machines = strings.Split(machines, ",") + for i := range cl.Machines { + cl.Machines[i] = strings.TrimSpace(cl.Machines[i]) } - cl.updateLeader(leader) + cl.picked = rand.Intn(len(cl.Machines)) } diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_delete_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_delete_test.go deleted file mode 100644 index 223e50f..0000000 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_delete_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package etcd - -import ( - "testing" -) - -func TestCompareAndDelete(t *testing.T) { - c := NewClient(nil) - defer func() { - c.Delete("foo", true) - }() - - c.Set("foo", "bar", 5) - - // This should succeed an correct prevValue - resp, err := c.CompareAndDelete("foo", "bar", 0) - if err != nil { - t.Fatal(err) - } - if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) { - t.Fatalf("CompareAndDelete 1 prevNode failed: %#v", resp) - } - - resp, _ = c.Set("foo", "bar", 5) - // This should fail because it gives an incorrect prevValue - _, err = c.CompareAndDelete("foo", "xxx", 0) - if err == nil { - t.Fatalf("CompareAndDelete 2 should have failed. The response is: %#v", resp) - } - - // This should succeed because it gives an correct prevIndex - resp, err = c.CompareAndDelete("foo", "", resp.Node.ModifiedIndex) - if err != nil { - t.Fatal(err) - } - if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) { - t.Fatalf("CompareAndSwap 3 prevNode failed: %#v", resp) - } - - c.Set("foo", "bar", 5) - // This should fail because it gives an incorrect prevIndex - resp, err = c.CompareAndDelete("foo", "", 29817514) - if err == nil { - t.Fatalf("CompareAndDelete 4 should have failed. The response is: %#v", resp) - } -} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_swap_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_swap_test.go deleted file mode 100644 index 14a1b00..0000000 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_swap_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package etcd - -import ( - "testing" -) - -func TestCompareAndSwap(t *testing.T) { - c := NewClient(nil) - defer func() { - c.Delete("foo", true) - }() - - c.Set("foo", "bar", 5) - - // This should succeed - resp, err := c.CompareAndSwap("foo", "bar2", 5, "bar", 0) - if err != nil { - t.Fatal(err) - } - if !(resp.Node.Value == "bar2" && resp.Node.Key == "/foo" && resp.Node.TTL == 5) { - t.Fatalf("CompareAndSwap 1 failed: %#v", resp) - } - - if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) { - t.Fatalf("CompareAndSwap 1 prevNode failed: %#v", resp) - } - - // This should fail because it gives an incorrect prevValue - resp, err = c.CompareAndSwap("foo", "bar3", 5, "xxx", 0) - if err == nil { - t.Fatalf("CompareAndSwap 2 should have failed. The response is: %#v", resp) - } - - resp, err = c.Set("foo", "bar", 5) - if err != nil { - t.Fatal(err) - } - - // This should succeed - resp, err = c.CompareAndSwap("foo", "bar2", 5, "", resp.Node.ModifiedIndex) - if err != nil { - t.Fatal(err) - } - if !(resp.Node.Value == "bar2" && resp.Node.Key == "/foo" && resp.Node.TTL == 5) { - t.Fatalf("CompareAndSwap 3 failed: %#v", resp) - } - - if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) { - t.Fatalf("CompareAndSwap 3 prevNode failed: %#v", resp) - } - - // This should fail because it gives an incorrect prevIndex - resp, err = c.CompareAndSwap("foo", "bar3", 5, "", 29817514) - if err == nil { - t.Fatalf("CompareAndSwap 4 should have failed. The response is: %#v", resp) - } -} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/debug_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/debug_test.go deleted file mode 100644 index 97f6d11..0000000 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/debug_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package etcd - -import ( - "testing" -) - -type Foo struct{} -type Bar struct { - one string - two int -} - -// Tests that logs don't panic with arbitrary interfaces -func TestDebug(t *testing.T) { - f := &Foo{} - b := &Bar{"asfd", 3} - for _, test := range []interface{}{ - 1234, - "asdf", - f, - b, - } { - logger.Debug(test) - logger.Debugf("something, %s", test) - logger.Warning(test) - logger.Warningf("something, %s", test) - } -} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/delete_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/delete_test.go deleted file mode 100644 index 5904971..0000000 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/delete_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package etcd - -import ( - "testing" -) - -func TestDelete(t *testing.T) { - c := NewClient(nil) - defer func() { - c.Delete("foo", true) - }() - - c.Set("foo", "bar", 5) - resp, err := c.Delete("foo", false) - if err != nil { - t.Fatal(err) - } - - if !(resp.Node.Value == "") { - t.Fatalf("Delete failed with %s", resp.Node.Value) - } - - if !(resp.PrevNode.Value == "bar") { - t.Fatalf("Delete PrevNode failed with %s", resp.Node.Value) - } - - resp, err = c.Delete("foo", false) - if err == nil { - t.Fatalf("Delete should have failed because the key foo did not exist. "+ - "The response was: %v", resp) - } -} - -func TestDeleteAll(t *testing.T) { - c := NewClient(nil) - defer func() { - c.Delete("foo", true) - c.Delete("fooDir", true) - }() - - c.SetDir("foo", 5) - // test delete an empty dir - resp, err := c.DeleteDir("foo") - if err != nil { - t.Fatal(err) - } - - if !(resp.Node.Value == "") { - t.Fatalf("DeleteAll 1 failed: %#v", resp) - } - - if !(resp.PrevNode.Dir == true && resp.PrevNode.Value == "") { - t.Fatalf("DeleteAll 1 PrevNode failed: %#v", resp) - } - - c.CreateDir("fooDir", 5) - c.Set("fooDir/foo", "bar", 5) - _, err = c.DeleteDir("fooDir") - if err == nil { - t.Fatal("should not able to delete a non-empty dir with deletedir") - } - - resp, err = c.Delete("fooDir", true) - if err != nil { - t.Fatal(err) - } - - if !(resp.Node.Value == "") { - t.Fatalf("DeleteAll 2 failed: %#v", resp) - } - - if !(resp.PrevNode.Dir == true && resp.PrevNode.Value == "") { - t.Fatalf("DeleteAll 2 PrevNode failed: %#v", resp) - } - - resp, err = c.Delete("foo", true) - if err == nil { - t.Fatalf("DeleteAll should have failed because the key foo did not exist. "+ - "The response was: %v", resp) - } -} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/error.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/error.go index 7e69287..66dca54 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/error.go +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/error.go @@ -6,7 +6,8 @@ import ( ) const ( - ErrCodeEtcdNotReachable = 501 + ErrCodeEtcdNotReachable = 501 + ErrCodeUnhandledHTTPStatus = 502 ) var ( diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get.go index 976bf07..09fe641 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get.go +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get.go @@ -18,9 +18,14 @@ func (c *Client) Get(key string, sort, recursive bool) (*Response, error) { } func (c *Client) RawGet(key string, sort, recursive bool) (*RawResponse, error) { + var q bool + if c.config.Consistency == STRONG_CONSISTENCY { + q = true + } ops := Options{ "recursive": recursive, "sorted": sort, + "quorum": q, } return c.get(key, ops) diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get_test.go deleted file mode 100644 index 279c4e2..0000000 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get_test.go +++ /dev/null @@ -1,131 +0,0 @@ -package etcd - -import ( - "reflect" - "testing" -) - -// cleanNode scrubs Expiration, ModifiedIndex and CreatedIndex of a node. -func cleanNode(n *Node) { - n.Expiration = nil - n.ModifiedIndex = 0 - n.CreatedIndex = 0 -} - -// cleanResult scrubs a result object two levels deep of Expiration, -// ModifiedIndex and CreatedIndex. -func cleanResult(result *Response) { - // TODO(philips): make this recursive. - cleanNode(result.Node) - for i, _ := range result.Node.Nodes { - cleanNode(result.Node.Nodes[i]) - for j, _ := range result.Node.Nodes[i].Nodes { - cleanNode(result.Node.Nodes[i].Nodes[j]) - } - } -} - -func TestGet(t *testing.T) { - c := NewClient(nil) - defer func() { - c.Delete("foo", true) - }() - - c.Set("foo", "bar", 5) - - result, err := c.Get("foo", false, false) - - if err != nil { - t.Fatal(err) - } - - if result.Node.Key != "/foo" || result.Node.Value != "bar" { - t.Fatalf("Get failed with %s %s %v", result.Node.Key, result.Node.Value, result.Node.TTL) - } - - result, err = c.Get("goo", false, false) - if err == nil { - t.Fatalf("should not be able to get non-exist key") - } -} - -func TestGetAll(t *testing.T) { - c := NewClient(nil) - defer func() { - c.Delete("fooDir", true) - }() - - c.CreateDir("fooDir", 5) - c.Set("fooDir/k0", "v0", 5) - c.Set("fooDir/k1", "v1", 5) - - // Return kv-pairs in sorted order - result, err := c.Get("fooDir", true, false) - - if err != nil { - t.Fatal(err) - } - - expected := Nodes{ - &Node{ - Key: "/fooDir/k0", - Value: "v0", - TTL: 5, - }, - &Node{ - Key: "/fooDir/k1", - Value: "v1", - TTL: 5, - }, - } - - cleanResult(result) - - if !reflect.DeepEqual(result.Node.Nodes, expected) { - t.Fatalf("(actual) %v != (expected) %v", result.Node.Nodes, expected) - } - - // Test the `recursive` option - c.CreateDir("fooDir/childDir", 5) - c.Set("fooDir/childDir/k2", "v2", 5) - - // Return kv-pairs in sorted order - result, err = c.Get("fooDir", true, true) - - cleanResult(result) - - if err != nil { - t.Fatal(err) - } - - expected = Nodes{ - &Node{ - Key: "/fooDir/childDir", - Dir: true, - Nodes: Nodes{ - &Node{ - Key: "/fooDir/childDir/k2", - Value: "v2", - TTL: 5, - }, - }, - TTL: 5, - }, - &Node{ - Key: "/fooDir/k0", - Value: "v0", - TTL: 5, - }, - &Node{ - Key: "/fooDir/k1", - Value: "v1", - TTL: 5, - }, - } - - cleanResult(result) - - if !reflect.DeepEqual(result.Node.Nodes, expected) { - t.Fatalf("(actual) %v != (expected) %v", result.Node.Nodes, expected) - } -} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/member.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/member.go new file mode 100644 index 0000000..5b13b28 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/member.go @@ -0,0 +1,30 @@ +package etcd + +import "encoding/json" + +type Member struct { + ID string `json:"id"` + Name string `json:"name"` + PeerURLs []string `json:"peerURLs"` + ClientURLs []string `json:"clientURLs"` +} + +type memberCollection []Member + +func (c *memberCollection) UnmarshalJSON(data []byte) error { + d := struct { + Members []Member + }{} + + if err := json.Unmarshal(data, &d); err != nil { + return err + } + + if d.Members == nil { + *c = make([]Member, 0) + return nil + } + + *c = d.Members + return nil +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/options.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/options.go index 701c9b3..d21c96f 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/options.go +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/options.go @@ -17,11 +17,11 @@ type validOptions map[string]reflect.Kind // values are meant to be used as constants. var ( VALID_GET_OPTIONS = validOptions{ - "recursive": reflect.Bool, - "consistent": reflect.Bool, - "sorted": reflect.Bool, - "wait": reflect.Bool, - "waitIndex": reflect.Uint64, + "recursive": reflect.Bool, + "quorum": reflect.Bool, + "sorted": reflect.Bool, + "wait": reflect.Bool, + "waitIndex": reflect.Uint64, } VALID_PUT_OPTIONS = validOptions{ diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests.go index fa6d36a..156c362 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests.go +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "io/ioutil" - "math/rand" "net/http" "net/url" "path" @@ -39,15 +38,9 @@ func NewRawRequest(method, relativePath string, values url.Values, cancel <-chan // getCancelable issues a cancelable GET request func (c *Client) getCancelable(key string, options Options, cancel <-chan bool) (*RawResponse, error) { - logger.Debugf("get %s [%s]", key, c.cluster.Leader) + logger.Debugf("get %s [%s]", key, c.cluster.pick()) p := keyToPath(key) - // If consistency level is set to STRONG, append - // the `consistent` query string. - if c.config.Consistency == STRONG_CONSISTENCY { - options["consistent"] = true - } - str, err := options.toParameters(VALID_GET_OPTIONS) if err != nil { return nil, err @@ -73,7 +66,7 @@ func (c *Client) get(key string, options Options) (*RawResponse, error) { func (c *Client) put(key string, value string, ttl uint64, options Options) (*RawResponse, error) { - logger.Debugf("put %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.Leader) + logger.Debugf("put %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.pick()) p := keyToPath(key) str, err := options.toParameters(VALID_PUT_OPTIONS) @@ -94,7 +87,7 @@ func (c *Client) put(key string, value string, ttl uint64, // post issues a POST request func (c *Client) post(key string, value string, ttl uint64) (*RawResponse, error) { - logger.Debugf("post %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.Leader) + logger.Debugf("post %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.pick()) p := keyToPath(key) req := NewRawRequest("POST", p, buildValues(value, ttl), nil) @@ -109,7 +102,7 @@ func (c *Client) post(key string, value string, ttl uint64) (*RawResponse, error // delete issues a DELETE request func (c *Client) delete(key string, options Options) (*RawResponse, error) { - logger.Debugf("delete %s [%s]", key, c.cluster.Leader) + logger.Debugf("delete %s [%s]", key, c.cluster.pick()) p := keyToPath(key) str, err := options.toParameters(VALID_DELETE_OPTIONS) @@ -130,7 +123,6 @@ func (c *Client) delete(key string, options Options) (*RawResponse, error) { // SendRequest sends a HTTP request and returns a Response as defined by etcd func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) { - var req *http.Request var resp *http.Response var httpPath string @@ -196,13 +188,9 @@ func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) { logger.Debug("Connecting to etcd: attempt ", attempt+1, " for ", rr.RelativePath) - if rr.Method == "GET" && c.config.Consistency == WEAK_CONSISTENCY { - // If it's a GET and consistency level is set to WEAK, - // then use a random machine. - httpPath = c.getHttpPath(true, rr.RelativePath) - } else { - // Else use the leader. - httpPath = c.getHttpPath(false, rr.RelativePath) + // get httpPath if not set + if httpPath == "" { + httpPath = c.getHttpPath(rr.RelativePath) } // Return a cURL command if curlChan is set @@ -211,6 +199,9 @@ func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) { for key, value := range rr.Values { command += fmt.Sprintf(" -d %s=%s", key, value[0]) } + if c.credentials != nil { + command += fmt.Sprintf(" -u %s", c.credentials.username) + } c.sendCURL(command) } @@ -240,7 +231,13 @@ func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) { return nil, err } + if c.credentials != nil { + req.SetBasicAuth(c.credentials.username, c.credentials.password) + } + resp, err = c.httpClient.Do(req) + // clear previous httpPath + httpPath = "" defer func() { if resp != nil { resp.Body.Close() @@ -264,7 +261,7 @@ func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) { return nil, checkErr } - c.cluster.switchLeader(attempt % len(c.cluster.Machines)) + c.cluster.failure() continue } @@ -295,17 +292,14 @@ func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) { } } - // if resp is TemporaryRedirect, set the new leader and retry if resp.StatusCode == http.StatusTemporaryRedirect { u, err := resp.Location() if err != nil { logger.Warning(err) } else { - // Update cluster leader based on redirect location - // because it should point to the leader address - c.cluster.updateLeaderFromURL(u) - logger.Debug("recv.response.relocate ", u.String()) + // set httpPath for following redirection + httpPath = u.String() } resp.Body.Close() continue @@ -333,34 +327,47 @@ func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) { func DefaultCheckRetry(cluster *Cluster, numReqs int, lastResp http.Response, err error) error { - if numReqs >= 2*len(cluster.Machines) { - return newError(ErrCodeEtcdNotReachable, - "Tried to connect to each peer twice and failed", 0) + if isEmptyResponse(lastResp) { + // always retry if it failed to get response from one machine + return err + } else if !shouldRetry(lastResp) { + body := []byte("nil") + if lastResp.Body != nil { + if b, err := ioutil.ReadAll(lastResp.Body); err == nil { + body = b + } + } + errStr := fmt.Sprintf("unhandled http status [%s] with body [%s]", http.StatusText(lastResp.StatusCode), body) + return newError(ErrCodeUnhandledHTTPStatus, errStr, 0) } - code := lastResp.StatusCode - if code == http.StatusInternalServerError { + if numReqs > 2*len(cluster.Machines) { + errStr := fmt.Sprintf("failed to propose on members %v twice [last error: %v]", cluster.Machines, err) + return newError(ErrCodeEtcdNotReachable, errStr, 0) + } + if shouldRetry(lastResp) { + // sleep some time and expect leader election finish time.Sleep(time.Millisecond * 200) - } - logger.Warning("bad response status code", code) + logger.Warning("bad response status code", lastResp.StatusCode) return nil } -func (c *Client) getHttpPath(random bool, s ...string) string { - var machine string - if random { - machine = c.cluster.Machines[rand.Intn(len(c.cluster.Machines))] - } else { - machine = c.cluster.Leader - } +func isEmptyResponse(r http.Response) bool { return r.StatusCode == 0 } - fullPath := machine + "/" + version +// shouldRetry returns whether the reponse deserves retry. +func shouldRetry(r http.Response) bool { + // TODO: only retry when the cluster is in leader election + // We cannot do it exactly because etcd doesn't support it well. + return r.StatusCode == http.StatusInternalServerError +} + +func (c *Client) getHttpPath(s ...string) string { + fullPath := c.cluster.pick() + "/" + version for _, seg := range s { fullPath = fullPath + "/" + seg } - return fullPath } @@ -379,11 +386,13 @@ func buildValues(value string, ttl uint64) url.Values { return v } -// convert key string to http path exclude version +// convert key string to http path exclude version, including URL escaping // for example: key[foo] -> path[keys/foo] +// key[/%z] -> path[keys/%25z] // key[/] -> path[keys/] func keyToPath(key string) string { - p := path.Join("keys", key) + // URL-escape our key, except for slashes + p := strings.Replace(url.QueryEscape(path.Join("keys", key)), "%2F", "/", -1) // corner case: if key is "/" or "//" ect // path join will clear the tailing "/" diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go deleted file mode 100644 index 756e317..0000000 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package etcd - -import ( - "fmt" - "testing" -) - -func TestSetCurlChan(t *testing.T) { - c := NewClient(nil) - c.OpenCURL() - - defer func() { - c.Delete("foo", true) - }() - - _, err := c.Set("foo", "bar", 5) - if err != nil { - t.Fatal(err) - } - - expected := fmt.Sprintf("curl -X PUT %s/v2/keys/foo -d value=bar -d ttl=5", - c.cluster.Leader) - actual := c.RecvCURL() - if expected != actual { - t.Fatalf(`Command "%s" is not equal to expected value "%s"`, - actual, expected) - } - - c.SetConsistency(STRONG_CONSISTENCY) - _, err = c.Get("foo", false, false) - if err != nil { - t.Fatal(err) - } - - expected = fmt.Sprintf("curl -X GET %s/v2/keys/foo?consistent=true&recursive=false&sorted=false", - c.cluster.Leader) - actual = c.RecvCURL() - if expected != actual { - t.Fatalf(`Command "%s" is not equal to expected value "%s"`, - actual, expected) - } -} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_update_create_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_update_create_test.go deleted file mode 100644 index ced0f06..0000000 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_update_create_test.go +++ /dev/null @@ -1,241 +0,0 @@ -package etcd - -import ( - "testing" -) - -func TestSet(t *testing.T) { - c := NewClient(nil) - defer func() { - c.Delete("foo", true) - }() - - resp, err := c.Set("foo", "bar", 5) - if err != nil { - t.Fatal(err) - } - if resp.Node.Key != "/foo" || resp.Node.Value != "bar" || resp.Node.TTL != 5 { - t.Fatalf("Set 1 failed: %#v", resp) - } - if resp.PrevNode != nil { - t.Fatalf("Set 1 PrevNode failed: %#v", resp) - } - - resp, err = c.Set("foo", "bar2", 5) - if err != nil { - t.Fatal(err) - } - if !(resp.Node.Key == "/foo" && resp.Node.Value == "bar2" && resp.Node.TTL == 5) { - t.Fatalf("Set 2 failed: %#v", resp) - } - if resp.PrevNode.Key != "/foo" || resp.PrevNode.Value != "bar" || resp.Node.TTL != 5 { - t.Fatalf("Set 2 PrevNode failed: %#v", resp) - } -} - -func TestUpdate(t *testing.T) { - c := NewClient(nil) - defer func() { - c.Delete("foo", true) - c.Delete("nonexistent", true) - }() - - resp, err := c.Set("foo", "bar", 5) - - if err != nil { - t.Fatal(err) - } - - // This should succeed. - resp, err = c.Update("foo", "wakawaka", 5) - if err != nil { - t.Fatal(err) - } - - if !(resp.Action == "update" && resp.Node.Key == "/foo" && resp.Node.TTL == 5) { - t.Fatalf("Update 1 failed: %#v", resp) - } - if !(resp.PrevNode.Key == "/foo" && resp.PrevNode.Value == "bar" && resp.Node.TTL == 5) { - t.Fatalf("Update 1 prevValue failed: %#v", resp) - } - - // This should fail because the key does not exist. - resp, err = c.Update("nonexistent", "whatever", 5) - if err == nil { - t.Fatalf("The key %v did not exist, so the update should have failed."+ - "The response was: %#v", resp.Node.Key, resp) - } -} - -func TestCreate(t *testing.T) { - c := NewClient(nil) - defer func() { - c.Delete("newKey", true) - }() - - newKey := "/newKey" - newValue := "/newValue" - - // This should succeed - resp, err := c.Create(newKey, newValue, 5) - if err != nil { - t.Fatal(err) - } - - if !(resp.Action == "create" && resp.Node.Key == newKey && - resp.Node.Value == newValue && resp.Node.TTL == 5) { - t.Fatalf("Create 1 failed: %#v", resp) - } - if resp.PrevNode != nil { - t.Fatalf("Create 1 PrevNode failed: %#v", resp) - } - - // This should fail, because the key is already there - resp, err = c.Create(newKey, newValue, 5) - if err == nil { - t.Fatalf("The key %v did exist, so the creation should have failed."+ - "The response was: %#v", resp.Node.Key, resp) - } -} - -func TestCreateInOrder(t *testing.T) { - c := NewClient(nil) - dir := "/queue" - defer func() { - c.DeleteDir(dir) - }() - - var firstKey, secondKey string - - resp, err := c.CreateInOrder(dir, "1", 5) - if err != nil { - t.Fatal(err) - } - - if !(resp.Action == "create" && resp.Node.Value == "1" && resp.Node.TTL == 5) { - t.Fatalf("Create 1 failed: %#v", resp) - } - - firstKey = resp.Node.Key - - resp, err = c.CreateInOrder(dir, "2", 5) - if err != nil { - t.Fatal(err) - } - - if !(resp.Action == "create" && resp.Node.Value == "2" && resp.Node.TTL == 5) { - t.Fatalf("Create 2 failed: %#v", resp) - } - - secondKey = resp.Node.Key - - if firstKey >= secondKey { - t.Fatalf("Expected first key to be greater than second key, but %s is not greater than %s", - firstKey, secondKey) - } -} - -func TestSetDir(t *testing.T) { - c := NewClient(nil) - defer func() { - c.Delete("foo", true) - c.Delete("fooDir", true) - }() - - resp, err := c.CreateDir("fooDir", 5) - if err != nil { - t.Fatal(err) - } - if !(resp.Node.Key == "/fooDir" && resp.Node.Value == "" && resp.Node.TTL == 5) { - t.Fatalf("SetDir 1 failed: %#v", resp) - } - if resp.PrevNode != nil { - t.Fatalf("SetDir 1 PrevNode failed: %#v", resp) - } - - // This should fail because /fooDir already points to a directory - resp, err = c.CreateDir("/fooDir", 5) - if err == nil { - t.Fatalf("fooDir already points to a directory, so SetDir should have failed."+ - "The response was: %#v", resp) - } - - _, err = c.Set("foo", "bar", 5) - if err != nil { - t.Fatal(err) - } - - // This should succeed - // It should replace the key - resp, err = c.SetDir("foo", 5) - if err != nil { - t.Fatal(err) - } - if !(resp.Node.Key == "/foo" && resp.Node.Value == "" && resp.Node.TTL == 5) { - t.Fatalf("SetDir 2 failed: %#v", resp) - } - if !(resp.PrevNode.Key == "/foo" && resp.PrevNode.Value == "bar" && resp.PrevNode.TTL == 5) { - t.Fatalf("SetDir 2 failed: %#v", resp) - } -} - -func TestUpdateDir(t *testing.T) { - c := NewClient(nil) - defer func() { - c.Delete("fooDir", true) - }() - - resp, err := c.CreateDir("fooDir", 5) - if err != nil { - t.Fatal(err) - } - - // This should succeed. - resp, err = c.UpdateDir("fooDir", 5) - if err != nil { - t.Fatal(err) - } - - if !(resp.Action == "update" && resp.Node.Key == "/fooDir" && - resp.Node.Value == "" && resp.Node.TTL == 5) { - t.Fatalf("UpdateDir 1 failed: %#v", resp) - } - if !(resp.PrevNode.Key == "/fooDir" && resp.PrevNode.Dir == true && resp.PrevNode.TTL == 5) { - t.Fatalf("UpdateDir 1 PrevNode failed: %#v", resp) - } - - // This should fail because the key does not exist. - resp, err = c.UpdateDir("nonexistentDir", 5) - if err == nil { - t.Fatalf("The key %v did not exist, so the update should have failed."+ - "The response was: %#v", resp.Node.Key, resp) - } -} - -func TestCreateDir(t *testing.T) { - c := NewClient(nil) - defer func() { - c.Delete("fooDir", true) - }() - - // This should succeed - resp, err := c.CreateDir("fooDir", 5) - if err != nil { - t.Fatal(err) - } - - if !(resp.Action == "create" && resp.Node.Key == "/fooDir" && - resp.Node.Value == "" && resp.Node.TTL == 5) { - t.Fatalf("CreateDir 1 failed: %#v", resp) - } - if resp.PrevNode != nil { - t.Fatalf("CreateDir 1 PrevNode failed: %#v", resp) - } - - // This should fail, because the key is already there - resp, err = c.CreateDir("fooDir", 5) - if err == nil { - t.Fatalf("The key %v did exist, so the creation should have failed."+ - "The response was: %#v", resp.Node.Key, resp) - } -} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/version.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/version.go index b3d05df..6e88993 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/version.go +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/version.go @@ -1,3 +1,6 @@ package etcd -const version = "v2" +const ( + version = "v2" + packageVersion = "v2.0.0" +) diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/watch_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/watch_test.go deleted file mode 100644 index 43e1dfe..0000000 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/watch_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package etcd - -import ( - "fmt" - "runtime" - "testing" - "time" -) - -func TestWatch(t *testing.T) { - c := NewClient(nil) - defer func() { - c.Delete("watch_foo", true) - }() - - go setHelper("watch_foo", "bar", c) - - resp, err := c.Watch("watch_foo", 0, false, nil, nil) - if err != nil { - t.Fatal(err) - } - if !(resp.Node.Key == "/watch_foo" && resp.Node.Value == "bar") { - t.Fatalf("Watch 1 failed: %#v", resp) - } - - go setHelper("watch_foo", "bar", c) - - resp, err = c.Watch("watch_foo", resp.Node.ModifiedIndex+1, false, nil, nil) - if err != nil { - t.Fatal(err) - } - if !(resp.Node.Key == "/watch_foo" && resp.Node.Value == "bar") { - t.Fatalf("Watch 2 failed: %#v", resp) - } - - routineNum := runtime.NumGoroutine() - - ch := make(chan *Response, 10) - stop := make(chan bool, 1) - - go setLoop("watch_foo", "bar", c) - - go receiver(ch, stop) - - _, err = c.Watch("watch_foo", 0, false, ch, stop) - if err != ErrWatchStoppedByUser { - t.Fatalf("Watch returned a non-user stop error") - } - - if newRoutineNum := runtime.NumGoroutine(); newRoutineNum != routineNum { - t.Fatalf("Routine numbers differ after watch stop: %v, %v", routineNum, newRoutineNum) - } -} - -func TestWatchAll(t *testing.T) { - c := NewClient(nil) - defer func() { - c.Delete("watch_foo", true) - }() - - go setHelper("watch_foo/foo", "bar", c) - - resp, err := c.Watch("watch_foo", 0, true, nil, nil) - if err != nil { - t.Fatal(err) - } - if !(resp.Node.Key == "/watch_foo/foo" && resp.Node.Value == "bar") { - t.Fatalf("WatchAll 1 failed: %#v", resp) - } - - go setHelper("watch_foo/foo", "bar", c) - - resp, err = c.Watch("watch_foo", resp.Node.ModifiedIndex+1, true, nil, nil) - if err != nil { - t.Fatal(err) - } - if !(resp.Node.Key == "/watch_foo/foo" && resp.Node.Value == "bar") { - t.Fatalf("WatchAll 2 failed: %#v", resp) - } - - ch := make(chan *Response, 10) - stop := make(chan bool, 1) - - routineNum := runtime.NumGoroutine() - - go setLoop("watch_foo/foo", "bar", c) - - go receiver(ch, stop) - - _, err = c.Watch("watch_foo", 0, true, ch, stop) - if err != ErrWatchStoppedByUser { - t.Fatalf("Watch returned a non-user stop error") - } - - if newRoutineNum := runtime.NumGoroutine(); newRoutineNum != routineNum { - t.Fatalf("Routine numbers differ after watch stop: %v, %v", routineNum, newRoutineNum) - } -} - -func setHelper(key, value string, c *Client) { - time.Sleep(time.Second) - c.Set(key, value, 100) -} - -func setLoop(key, value string, c *Client) { - time.Sleep(time.Second) - for i := 0; i < 10; i++ { - newValue := fmt.Sprintf("%s_%v", value, i) - c.Set(key, newValue, 100) - time.Sleep(time.Second / 10) - } -} - -func receiver(c chan *Response, stop chan bool) { - for i := 0; i < 10; i++ { - <-c - } - stop <- true -} diff --git a/etcdenv.go b/etcdenv.go index a1630da..9599d74 100644 --- a/etcdenv.go +++ b/etcdenv.go @@ -35,6 +35,8 @@ var ( Server string Namespace string WatchedKeys string + UserName string + Password string }{} ) @@ -66,6 +68,12 @@ func init() { flagset.StringVar(&flags.WatchedKeys, "watched", "", "environment variables to watch, comma-separated") flagset.StringVar(&flags.WatchedKeys, "w", "", "environment variables to watch, comma-separated") + + flagset.StringVar(&flags.UserName, "user", "", "user to authenticate to etcd server") + flagset.StringVar(&flags.UserName, "u", "", "user to authenticate to etcd server") + + flagset.StringVar(&flags.Password, "password", "", "password to authenticate to etcd server") + flagset.StringVar(&flags.Password, "p", "", "password to authenticate to etcd server") } func main() { @@ -99,6 +107,8 @@ func main() { flagset.Args(), flags.ShutdownBehaviour, watchedKeysList, + flags.UserName, + flags.Password, ) if err != nil { diff --git a/etcdenv/context.go b/etcdenv/context.go index 024f86b..f4c6001 100644 --- a/etcdenv/context.go +++ b/etcdenv/context.go @@ -23,7 +23,7 @@ type Context struct { } func NewContext(namespaces []string, endpoints, command []string, - shutdownBehaviour string, watchedKeys []string) (*Context, error) { + shutdownBehaviour string, watchedKeys []string, username string, password string) (*Context, error) { if shutdownBehaviour != "keepalive" && shutdownBehaviour != "restart" && shutdownBehaviour != "exit" { @@ -33,10 +33,16 @@ func NewContext(namespaces []string, endpoints, command []string, ) } + etcdClient := etcd.NewClient(endpoints) + + if username != "" && password != "" { + etcdClient.SetCredentials(username, password) + } + return &Context{ Namespaces: namespaces, Runner: NewRunner(command), - etcdClient: etcd.NewClient(endpoints), + etcdClient: etcdClient, ShutdownBehaviour: shutdownBehaviour, ExitChan: make(chan bool), WatchedKeys: watchedKeys,