etcd-rb 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f5096dd7b703a7f42348e4eaae037cfa642e054f
4
- data.tar.gz: 8f93a5c5affe7ffaaa0557a7df893a2fa6d9f00c
3
+ metadata.gz: 3f44e73b7a78f25139d79210e6802c4837b116e6
4
+ data.tar.gz: ffbe7d2df48e3cc9e050a127b605d412c2a352f6
5
5
  SHA512:
6
- metadata.gz: 64631fd825ea6326288c8730f60539afd63e62f12015a657232e663153564ce628025f78b5c7026844901c790a76cbcddd70bb36963af529e7c3f8871b29a7d4
7
- data.tar.gz: a2170e3f1d2ea0d9dfac6353d221539f593cc534083782bf2a53704907429adf9cbd2da3ec844a2d6d956571ce9ee08b50fe7416160127edb449dc2d8d5bf8d4
6
+ metadata.gz: 2b6de922dd29798bb860b69081d8c0e1ba16fc06a89ab985c7f6f7f4e3bc0cb288c045bd65cfbe527fcafbec1ec301a69aee5879b647dcd0e331195620fe207e
7
+ data.tar.gz: e43e9f2006c706bff16b6a3985d0d554c38b566379f2e87dabe87006b4c6e39980bce142ad4445dc9a216e373cd9d4dc5dfce6ef34177b329ccfe0f01a2dc8f3
data/README.md CHANGED
@@ -5,10 +5,10 @@
5
5
 
6
6
  # Requirements
7
7
 
8
- - A modern Ruby, compatible with 1.9.3 or later. Continously tested with MRI 1.9.3, 2.0.0 and JRuby 1.7.x.
9
8
  - Linux/OSX OS
10
- - to have a local Etcd binary, run
11
- - `$ sh/install-etcd.sh`
9
+ - A modern Ruby, compatible with 2.0.0 or later. Continously tested with MRI 2.0.0, 2.2 and JRuby 9.0.0.
10
+ - Etcd 2.0.0 or later (only the V2 API is supported). To have a local etcd binary, run:
11
+ - `$ sh/install-etcd.sh`
12
12
 
13
13
  # Installation
14
14
 
@@ -214,4 +214,4 @@ _Licensed under the Apache License, Version 2.0 (the "License"); you may not use
214
214
 
215
215
  [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
216
216
 
217
- _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._
217
+ _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._
@@ -1,7 +1,5 @@
1
1
  module Etcd
2
2
  class Client
3
-
4
-
5
3
  # @param options [Hash]
6
4
  # @option options [Array] :uris (['http://127.0.0.1:4001']) seed uris with etcd cluster nodes
7
5
  # @option options [Float] :heartbeat_freq (0.0) check-frequency for leader status (in seconds)
@@ -34,7 +32,6 @@ module Etcd
34
32
  self
35
33
  end
36
34
 
37
-
38
35
  # Creates a Cluster-instance from `@seed_uris`
39
36
  # and stores the cluster leader information
40
37
  def update_cluster
@@ -59,10 +56,9 @@ module Etcd
59
56
  end
60
57
 
61
58
  def leader_uri
62
- leader && leader.etcd
59
+ leader && leader.client_urls && leader.client_urls.first
63
60
  end
64
61
 
65
-
66
62
  def start_heartbeat_if_needed
67
63
  logger.debug("client - starting heartbeat")
68
64
  @heartbeat = Etcd::Heartbeat.new(self, @heartbeat_freq)
@@ -77,27 +73,26 @@ module Etcd
77
73
  http_client.default_redirect_uri_callback(uri, response)
78
74
  end
79
75
 
76
+ private
80
77
 
81
- private
82
78
  # :uri and :request_data are the only methods calling :leader method
83
79
  # so they both need to handle the case for missing leader in cluster
84
80
  def uri(key, action=S_KEYS)
85
81
  raise AllNodesDownError unless leader
86
82
  key = "/#{key}" unless key.start_with?(S_SLASH)
87
- "#{leader_uri}/v1/#{action}#{key}"
83
+ "#{leader_uri}/v2/#{action}#{key}"
88
84
  end
89
85
 
90
-
91
86
  def request_data(method, uri, args={})
92
87
  logger.debug("request_data: #{method} - #{uri} #{args.inspect}")
93
88
  begin
94
89
  super
95
90
  rescue Errno::ECONNREFUSED, HTTPClient::TimeoutError => e
96
91
  logger.debug("request_data: re-election handling")
97
- old_leader_uri = @leader.etcd
92
+ old_leader_uri = @leader.client_urls.first
98
93
  update_cluster
99
94
  if @leader
100
- uri = uri.gsub(old_leader_uri, @leader.etcd)
95
+ uri = uri.gsub(old_leader_uri, @leader.client_urls.first)
101
96
  retry
102
97
  else
103
98
  raise AllNodesDownError
@@ -106,4 +101,4 @@ private
106
101
  end
107
102
 
108
103
  end
109
- end
104
+ end
@@ -31,8 +31,8 @@ module Etcd
31
31
  #
32
32
  # @return [#cancel, #join] an observer object which you can call cancel and
33
33
  # join on
34
- def observe(prefix, &handler)
35
- ob = Observer.new(self, prefix, handler).tap(&:run)
34
+ def observe(prefix, options = {}, &handler)
35
+ ob = Observer.new(self, prefix, handler, options).tap(&:run)
36
36
  @observers[prefix] = ob
37
37
  ob
38
38
  end
@@ -1,3 +1,12 @@
1
+ # Implements the etcd V2 client API
2
+ #
3
+ # Sample API requests/responses
4
+ # $ curl -L http://127.0.0.1:4001/v2/keys
5
+ # {"action":"get","node":{"dir":true,"nodes":[{"key":"/foo","value":"bar","modifiedIndex":22,"createdIndex":22}]}}
6
+ #
7
+ # $ curl -L http://127.0.0.1:4001/v2/keys/foo
8
+ # {"action":"get","node":{"key":"/foo","value":"bar","modifiedIndex":22,"createdIndex":22}}
9
+
1
10
  module Etcd
2
11
  class Client
3
12
 
@@ -15,8 +24,8 @@ module Etcd
15
24
  def set(key, value, options={})
16
25
  body = {:value => value}
17
26
  body[:ttl] = options[:ttl] if options[:ttl]
18
- data = request_data(:post, key_uri(key), body: body)
19
- data[S_PREV_VALUE]
27
+ data = request_data(:PUT, key_uri(key), body: body)
28
+ data[S_PREV_NODE][S_VALUE] if data && data[S_PREV_NODE]
20
29
  end
21
30
 
22
31
  # Gets the value or values for a key.
@@ -30,12 +39,12 @@ module Etcd
30
39
  def get(key)
31
40
  data = request_data(:get, key_uri(key))
32
41
  return nil unless data
33
- if data.is_a?(Array)
34
- data.each_with_object({}) do |e, acc|
35
- acc[e[S_KEY]] = e[S_VALUE]
42
+ if nodes = data[S_NODE][S_NODES]
43
+ nodes.each_with_object({}) do |node, acc|
44
+ acc[node[S_KEY]] = node[S_VALUE]
36
45
  end
37
46
  else
38
- data[S_VALUE]
47
+ data[S_NODE][S_VALUE]
39
48
  end
40
49
  end
41
50
 
@@ -58,7 +67,7 @@ module Etcd
58
67
  def update(key, value, expected_value, options={})
59
68
  body = {:value => value, :prevValue => expected_value}
60
69
  body[:ttl] = options[:ttl] if options[:ttl]
61
- data = request_data(:post, key_uri(key), body: body)
70
+ data = request_data(:put, key_uri(key), body: body)
62
71
  !! data
63
72
  end
64
73
 
@@ -71,7 +80,7 @@ module Etcd
71
80
  def delete(key)
72
81
  data = request_data(:delete, key_uri(key))
73
82
  return nil unless data
74
- data[S_PREV_VALUE]
83
+ data[S_PREV_NODE][S_VALUE]
75
84
  end
76
85
 
77
86
  # Returns true if the specified key exists.
@@ -102,8 +111,8 @@ module Etcd
102
111
  def info(key)
103
112
  data = request_data(:get, uri(key))
104
113
  return nil unless data
105
- if data.is_a?(Array)
106
- data.each_with_object({}) do |d, acc|
114
+ if nodes = data[S_NODE][S_NODES]
115
+ nodes.each_with_object({}) do |d, acc|
107
116
  info = extract_info(d)
108
117
  info.delete(:action)
109
118
  acc[info[:key]] = info
@@ -140,14 +149,10 @@ module Etcd
140
149
  # @yieldparam [Hash] info the info for the key that changed
141
150
  # @return [Object] the result of the given block
142
151
  def watch(prefix, options={})
143
- if options[:index]
144
- parameters = {:index => options[:index]}
145
- data = request_data(:post, watch_uri(prefix), query: parameters)
146
- else
147
- data = request_data(:get, watch_uri(prefix), query: {})
148
- end
149
-
150
- info = extract_info(data)
152
+ options.merge!(wait: 'true')
153
+ options.delete(:index) if options.has_key?(:index) && options[:index].nil?
154
+ data = request_data(:get, watch_uri(prefix), query: options)
155
+ info = extract_info(data)
151
156
  yield info[:value], info[:key], info
152
157
  end
153
158
 
@@ -157,30 +162,35 @@ module Etcd
157
162
  end
158
163
 
159
164
  def watch_uri(key)
160
- uri(key, S_WATCH)
165
+ uri(key, S_KEYS)
161
166
  end
162
167
 
163
168
  private
164
169
 
165
170
  def extract_info(data)
171
+ if data[S_NODE]
172
+ node = data[S_NODE]
173
+ else
174
+ node = data
175
+ end
176
+ return {} unless node
166
177
  info = {
167
- :key => data[S_KEY],
168
- :value => data[S_VALUE],
169
- :index => data[S_INDEX],
178
+ :key => node[S_KEY],
179
+ :value => node[S_VALUE],
180
+ :index => node[S_INDEX],
170
181
  }
171
- expiration_s = data[S_EXPIRATION]
172
- ttl = data[S_TTL]
173
- previous_value = data[S_PREV_VALUE]
182
+ expiration_s = node[S_EXPIRATION]
183
+ ttl = node[S_TTL]
174
184
  action_s = data[S_ACTION]
185
+ previous_node = data[S_PREV_NODE]
175
186
  info[:expiration] = Time.iso8601(expiration_s) if expiration_s
176
187
  info[:ttl] = ttl if ttl
177
- info[:new_key] = data[S_NEW_KEY] if data.include?(S_NEW_KEY)
178
- info[:dir] = data[S_DIR] if data.include?(S_DIR)
179
- info[:previous_value] = previous_value if previous_value
188
+ info[:dir] = node[S_DIR] if node.include?(S_DIR)
189
+ info[:previous_value] = previous_node[S_VALUE] if previous_node
180
190
  info[:action] = action_s.downcase.to_sym if action_s
191
+ info[:new_key] = !data[S_PREV_NODE]
181
192
  info
182
193
  end
183
194
 
184
-
185
195
  end
186
- end
196
+ end
@@ -24,21 +24,15 @@ module Etcd
24
24
  end
25
25
 
26
26
  def status_uri(uri)
27
- "#{uri}/v1/keys/_etcd/machines/"
27
+ "#{uri}/v2/members/"
28
28
  end
29
29
 
30
30
  def parse_cluster_status(cluster_status_response)
31
- cluster_status_response.map do |attrs|
32
- node_name = attrs[S_KEY].split(S_SLASH).last
33
- urls = attrs[S_VALUE].split(S_AND)
34
- etcd = urls.grep(/etcd/).first.split("=").last
35
- raft = urls.grep(/raft/).first.split("=").last
36
- {:name => node_name, :raft => raft, :etcd => etcd}
31
+ cluster_status_response['members'].map do |attrs|
32
+ Etcd::Node.parse_node_data(attrs)
37
33
  end
38
34
  end
39
35
 
40
-
41
-
42
36
  # @example
43
37
  # Etcd::Cluster.nodes_from_uri("http://127.0.0.1:4001")
44
38
  #
@@ -50,8 +44,8 @@ module Etcd
50
44
  end
51
45
 
52
46
  def nodes_from_attributes(node_attributes)
53
- res = node_attributes.map do |attr|
54
- Etcd::Node.new(attr)
47
+ res = node_attributes.map do |attrs|
48
+ Etcd::Node.new(attrs)
55
49
  end
56
50
  end
57
51
 
@@ -95,4 +89,4 @@ module Etcd
95
89
  nodes.select{|x| x.is_leader}.first
96
90
  end
97
91
  end
98
- end
92
+ end
@@ -1,18 +1,18 @@
1
1
  module Etcd
2
2
  module Constants
3
+ S_NODE = 'node'.freeze
4
+ S_NODES = 'nodes'.freeze
3
5
  S_KEY = 'key'.freeze
4
6
  S_KEYS = 'keys'.freeze
5
7
  S_VALUE = 'value'.freeze
6
8
  S_INDEX = 'index'.freeze
7
9
  S_EXPIRATION = 'expiration'.freeze
8
10
  S_TTL = 'ttl'.freeze
9
- S_NEW_KEY = 'newKey'.freeze
10
11
  S_DIR = 'dir'.freeze
11
- S_PREV_VALUE = 'prevValue'.freeze
12
+ S_PREV_NODE = 'prevNode'.freeze
12
13
  S_ACTION = 'action'.freeze
13
- S_WATCH = 'watch'.freeze
14
14
  S_LOCATION = 'location'.freeze
15
15
  S_SLASH = '/'.freeze
16
16
  S_AND = '&'.freeze
17
17
  end
18
- end
18
+ end
@@ -13,7 +13,7 @@ module Etcd
13
13
  # Initiates heartbeating the leader node in a background thread
14
14
  # ensures, that observers are refreshed after leader re-election
15
15
  def start_heartbeat_if_needed
16
- logger.debug ("start_heartbeat_if_needed - enter")
16
+ logger.debug("start_heartbeat_if_needed - enter")
17
17
  return if freq == 0
18
18
  return if @heartbeat_thread
19
19
  @heartbeat_thread = Thread.new do
@@ -41,4 +41,4 @@ module Etcd
41
41
  end
42
42
 
43
43
  end
44
- end
44
+ end
@@ -10,4 +10,4 @@ module Etcd::Loggable
10
10
  log
11
11
  end
12
12
  end
13
- end
13
+ end
@@ -2,44 +2,61 @@ module Etcd
2
2
  class Node
3
3
  include Etcd::Constants
4
4
  include Etcd::Requestable
5
- attr_accessor :name, :etcd, :raft
5
+ attr_accessor :name, :id, :peer_urls, :client_urls
6
6
  # possible values: :unknown, :running, :down
7
7
  attr_accessor :status
8
8
  attr_accessor :is_leader
9
9
 
10
+ class << self
11
+ def parse_node_data(attrs)
12
+ {
13
+ :id => attrs["id"],
14
+ :name => attrs["name"],
15
+ :peer_urls => attrs["peerURLs"],
16
+ :client_urls => attrs["clientURLs"]
17
+ }
18
+ end
19
+ end
20
+
10
21
  def initialize(opts={})
11
22
  check_required(opts)
12
- @name = opts[:name]
13
- @etcd = URI.decode(opts[:etcd])
14
- @raft = URI.decode(opts[:raft])
23
+ @name = opts[:name]
24
+ @id = opts[:id]
25
+ @peer_urls = opts[:peer_urls]
26
+ @client_urls = opts[:client_urls]
15
27
  @status = :unknown
16
28
  end
17
29
 
18
30
  def check_required(opts)
19
- raise ArgumentError, "etcd URL is required!" unless opts[:etcd]
31
+ raise ArgumentError, "Client URL is required!" unless opts[:client_urls] && opts[:client_urls].any?
32
+ raise ArgumentError, "Node ID is required!" unless opts[:id]
20
33
  end
21
34
 
22
35
  def update_status
23
36
  begin
24
- response = request(:get, leader_uri)
25
- @status = :running
26
- @is_leader = (response.body == @raft)
37
+ leader_data = request_data(:get, leader_uri)
38
+ @status = :running
39
+ @is_leader = (leader_data["id"] == @id)
27
40
  rescue HTTPClient::TimeoutError, Errno::ECONNREFUSED => e
28
41
  @status = :down
29
42
  end
30
43
  end
31
44
 
32
45
  def leader_uri
33
- "#{@etcd}/v1/leader"
46
+ "#{@client_urls.first}/v2/members/leader"
34
47
  end
35
48
 
36
49
  def inspect
37
- %Q(<#{self.class} - #{name_with_status} - #{etcd}>)
50
+ %Q(<#{self.class} - #{id} - #{name_with_status} - #{peer_urls}>)
38
51
  end
39
52
 
40
53
  def name_with_status
41
54
  print_status = @is_leader ? "leader" : status
42
55
  "#{name} (#{print_status})"
43
56
  end
57
+
58
+ def to_json
59
+ { :name => name, :id => id, :client_urls => client_urls, :peer_urls => peer_urls }.to_json
60
+ end
44
61
  end
45
62
  end
@@ -2,10 +2,11 @@ module Etcd
2
2
  class Observer
3
3
  include Etcd::Loggable
4
4
 
5
- def initialize(client, prefix, handler)
5
+ def initialize(client, prefix, handler, options = {})
6
6
  @client = client
7
7
  @prefix = prefix
8
8
  @handler = handler
9
+ @options = options
9
10
  @index = nil
10
11
  reset_logger!(Logger::DEBUG)
11
12
  end
@@ -15,7 +16,7 @@ module Etcd
15
16
  @thread = Thread.start do
16
17
  while @running
17
18
  logger.debug "********* watching #{@prefix} with index #{@index}"
18
- @client.watch(@prefix, index: @index) do |value, key, info|
19
+ @client.watch(@prefix, @options.merge(index: @index)) do |value, key, info|
19
20
  if @running
20
21
  logger.debug "watch fired for #{@prefix} with #{info.inspect} "
21
22
  call_handler_in_needed(value, key, info)
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Etcd
4
- VERSION = '1.0.0'.freeze
5
- end
4
+ VERSION = '1.1.0'.freeze
5
+ end
@@ -2,12 +2,13 @@
2
2
  require 'spec_helper'
3
3
 
4
4
  module Etcd
5
+
5
6
  describe Client do
6
7
  include ClusterHelper
7
8
  include ClientHelper
8
9
 
9
10
  def base_uri
10
- "http://127.0.0.1:4001/v1"
11
+ "http://127.0.0.1:4001/v2"
11
12
  end
12
13
 
13
14
  let :client do
@@ -16,7 +17,7 @@ module Etcd
16
17
 
17
18
  describe '#get' do
18
19
  before do
19
- stub_request(:get, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({'value' => 'bar'}))
20
+ stub_request(:get, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({'node' => {'value' => 'bar'}}))
20
21
  end
21
22
 
22
23
  it 'sends a GET request to retrieve the value for a key' do
@@ -30,7 +31,7 @@ module Etcd
30
31
  end
31
32
 
32
33
  it 'parses the response and returns the value' do
33
- client.get('/foo').should == 'bar'
34
+ client.get('/foo').should eq('bar')
34
35
  end
35
36
 
36
37
  it 'returns nil if when the key does not exist' do
@@ -40,10 +41,10 @@ module Etcd
40
41
 
41
42
  context 'when listing a prefix' do
42
43
  it 'returns a hash of keys and their values' do
43
- values = [
44
+ values = {'node' => {'nodes' => [
44
45
  {'key' => '/foo/bar', 'value' => 'bar'},
45
- {'key' => '/foo/baz', 'value' => 'baz'},
46
- ]
46
+ {'key' => '/foo/baz', 'value' => 'baz'}
47
+ ]}}
47
48
  stub_request(:get, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump(values))
48
49
  client.get('/foo').should eql({'/foo/bar' => 'bar', '/foo/baz' => 'baz'})
49
50
  end
@@ -53,65 +54,65 @@ module Etcd
53
54
 
54
55
  describe '#set' do
55
56
  before do
56
- stub_request(:post, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({}))
57
+ stub_request(:put, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({'prevNode' => {'value' => 1}}))
57
58
  end
58
59
 
59
- it 'sends a POST request to set the value for a key' do
60
+ it 'sends a PUT request to set the value for a key' do
60
61
  client.set('/foo', 'bar')
61
- WebMock.should have_requested(:post, "#{base_uri}/keys/foo").with(body: 'value=bar')
62
+ WebMock.should have_requested(:put, "#{base_uri}/keys/foo").with(body: 'value=bar')
62
63
  end
63
64
 
64
65
  it 'prepends a slash to keys when necessary' do
65
66
  client.set('foo', 'bar')
66
- WebMock.should have_requested(:post, "#{base_uri}/keys/foo").with(body: 'value=bar')
67
+ WebMock.should have_requested(:put, "#{base_uri}/keys/foo").with(body: 'value=bar')
67
68
  end
68
69
 
69
70
  it 'parses the response and returns the previous value' do
70
- stub_request(:post, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({'prevValue' => 'baz'}))
71
- client.set('/foo', 'bar').should == 'baz'
71
+ stub_request(:put, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({'prevNode' => {'value' => 'baz'}}))
72
+ client.set('/foo', 'bar').should eq('baz')
72
73
  end
73
74
 
74
75
  it 'returns nil when there is no previous value' do
75
- stub_request(:post, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({}))
76
+ stub_request(:put, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({}))
76
77
  client.set('/foo', 'bar').should be_nil
77
78
  end
78
79
 
79
80
  it 'sets a TTL when the :ttl option is given' do
80
81
  client.set('/foo', 'bar', ttl: 3)
81
- WebMock.should have_requested(:post, "#{base_uri}/keys/foo").with(body: 'value=bar&ttl=3')
82
+ WebMock.should have_requested(:put, "#{base_uri}/keys/foo").with(body: 'value=bar&ttl=3')
82
83
  end
83
84
  end
84
85
 
85
86
  describe '#update' do
86
87
  before do
87
- stub_request(:post, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({}))
88
+ stub_request(:put, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({}))
88
89
  end
89
90
 
90
91
  it 'sends a POST request to set the value conditionally' do
91
92
  client.update('/foo', 'bar', 'baz')
92
- WebMock.should have_requested(:post, "#{base_uri}/keys/foo").with(body: 'value=bar&prevValue=baz')
93
+ WebMock.should have_requested(:put, "#{base_uri}/keys/foo").with(body: 'value=bar&prevValue=baz')
93
94
  end
94
95
 
95
96
  it 'returns true when the key is successfully changed' do
96
- stub_request(:post, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({}))
97
+ stub_request(:put, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({}))
97
98
  client.update('/foo', 'bar', 'baz').should eq(true)
98
99
  end
99
100
 
100
101
  it 'returns false when an error is returned' do
101
- stub_request(:post, "#{base_uri}/keys/foo").to_return(status: 400, body: MultiJson.dump({}))
102
+ stub_request(:put, "#{base_uri}/keys/foo").to_return(status: 400, body: MultiJson.dump({}))
102
103
  client.update('/foo', 'bar', 'baz').should eq(false)
103
104
  end
104
105
 
105
106
  it 'sets a TTL when the :ttl option is given' do
106
107
  client.update('/foo', 'bar', 'baz', ttl: 3)
107
- WebMock.should have_requested(:post, "#{base_uri}/keys/foo").with(body: 'value=bar&prevValue=baz&ttl=3')
108
+ WebMock.should have_requested(:put, "#{base_uri}/keys/foo").with(body: 'value=bar&prevValue=baz&ttl=3')
108
109
  end
109
110
  end
110
111
 
111
112
 
112
113
  describe '#delete' do
113
114
  before do
114
- stub_request(:delete, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({}))
115
+ stub_request(:delete, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({'prevNode' => {'value' => 1}}))
115
116
  end
116
117
 
117
118
  it 'sends a DELETE request to remove a key' do
@@ -120,8 +121,8 @@ module Etcd
120
121
  end
121
122
 
122
123
  it 'returns the previous value' do
123
- stub_request(:delete, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({'prevValue' => 'bar'}))
124
- client.delete('/foo').should == 'bar'
124
+ stub_request(:delete, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({'prevNode' => {'value' => 'bar'}}))
125
+ client.delete('/foo').should eq('bar')
125
126
  end
126
127
 
127
128
  it 'returns nil when there is no previous value' do
@@ -132,7 +133,7 @@ module Etcd
132
133
 
133
134
  describe '#exists?' do
134
135
  it 'returns true if the key has a value' do
135
- stub_request(:get, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({'value' => 'bar'}))
136
+ stub_request(:get, "#{base_uri}/keys/foo").to_return(body: MultiJson.dump({'node' => {'value' => 'bar'}}))
136
137
  client.exists?('/foo').should eq(true)
137
138
  end
138
139
 
@@ -144,32 +145,32 @@ module Etcd
144
145
 
145
146
  describe '#info' do
146
147
  it 'returns the key, value, index, expiration and TTL for a key' do
147
- body = MultiJson.dump({'action' => 'GET', 'key' => '/foo', 'value' => 'bar', 'index' => 31, 'expiration' => '2013-12-11T12:09:08.123+02:00', 'ttl' => 7})
148
+ body = MultiJson.dump({'action' => 'get', 'node' => {'key' => '/foo', 'value' => 'bar', 'index' => 31, 'expiration' => '2013-12-11T12:09:08.123+02:00', 'ttl' => 7}})
148
149
  stub_request(:get, "#{base_uri}/keys/foo").to_return(body: body)
149
150
  info = client.info('/foo')
150
- info[:key].should == '/foo'
151
- info[:value].should == 'bar'
152
- info[:index].should == 31
151
+ info[:key].should eq('/foo')
152
+ info[:value].should eq('bar')
153
+ info[:index].should eq(31)
153
154
  # rounding because of ruby 2.0 time parsing bug @see https://gist.github.com/mindreframer/6746829
154
- info[:expiration].to_f.round.should == (Time.utc(2013, 12, 11, 10, 9, 8) + 0.123).to_f.round
155
- info[:ttl].should == 7
155
+ info[:expiration].to_f.round.should eq((Time.utc(2013, 12, 11, 10, 9, 8) + 0.123).to_f.round)
156
+ info[:ttl].should eq(7)
156
157
  end
157
158
 
158
159
  it 'returns the dir flag' do
159
- body = MultiJson.dump({'action' => 'GET', 'key' => '/foo', 'dir' => true})
160
+ body = MultiJson.dump({'action' => 'get', 'node' => {'key' => '/foo', 'dir' => true}})
160
161
  stub_request(:get, "#{base_uri}/keys/foo").to_return(body: body)
161
162
  info = client.info('/foo')
162
- info[:key].should == '/foo'
163
+ info[:key].should eq('/foo')
163
164
  info[:dir].should eq(true)
164
165
  end
165
166
 
166
167
  it 'returns only the pieces of information that are returned' do
167
- body = MultiJson.dump({'action' => 'GET', 'key' => '/foo', 'value' => 'bar', 'index' => 31})
168
+ body = MultiJson.dump({'action' => 'get', 'node' => {'key' => '/foo', 'value' => 'bar', 'index' => 31}})
168
169
  stub_request(:get, "#{base_uri}/keys/foo").to_return(body: body)
169
170
  info = client.info('/foo')
170
- info[:key].should == '/foo'
171
- info[:value].should == 'bar'
172
- info[:index].should == 31
171
+ info[:key].should eq('/foo')
172
+ info[:value].should eq('bar')
173
+ info[:index].should eq(31)
173
174
  end
174
175
 
175
176
  it 'returns nil when the key does not exist' do
@@ -179,18 +180,19 @@ module Etcd
179
180
 
180
181
  context 'when listing a prefix' do
181
182
  it 'returns a hash of keys and their info' do
182
- body = MultiJson.dump([
183
+ body = MultiJson.dump({'node' => {'nodes' => [
183
184
  {'action' => 'GET', 'key' => '/foo/bar', 'value' => 'bar', 'index' => 31},
184
185
  {'action' => 'GET', 'key' => '/foo/baz', 'value' => 'baz', 'index' => 55},
185
- ])
186
+ ]}})
186
187
  stub_request(:get, "#{base_uri}/keys/foo").to_return(body: body)
187
188
  info = client.info('/foo')
188
- info['/foo/bar'][:key].should == '/foo/bar'
189
- info['/foo/baz'][:key].should == '/foo/baz'
190
- info['/foo/bar'][:value].should == 'bar'
191
- info['/foo/baz'][:value].should == 'baz'
192
- info['/foo/bar'][:index].should == 31
193
- info['/foo/baz'][:index].should == 55
189
+ puts info
190
+ info['/foo/bar'][:key].should eq('/foo/bar')
191
+ info['/foo/baz'][:key].should eq('/foo/baz')
192
+ info['/foo/bar'][:value].should eq('bar')
193
+ info['/foo/baz'][:value].should eq('baz')
194
+ info['/foo/bar'][:index].should eq(31)
195
+ info['/foo/baz'][:index].should eq(55)
194
196
  end
195
197
  end
196
198
  end
@@ -211,16 +213,16 @@ module Etcd
211
213
  with_stubbed_leaders(healthy_cluster_config)
212
214
 
213
215
  client = Etcd::Client.connect(:uris => etcd1_uri)
214
- client.leader.etcd.should == etcd1_uri
215
- client.leader.name.should == "node1"
216
+ client.leader.client_urls.first.should eq(etcd1_uri)
217
+ client.leader.name.should eq("node1")
216
218
 
217
219
  with_stubbed_leaders(healthy_cluster_changed_leader_config)
218
220
 
219
- stub_request(:post, "#{etcd1_uri}/v1/keys/foo").to_return(status: 307, headers: {'Location' => "#{etcd2_uri}/v1/keys/foo"})
220
- stub_request(:post, "#{etcd2_uri}/v1/keys/foo").to_return(body: MultiJson.dump({'value' => 'bar'}))
221
+ stub_request(:put, "#{etcd1_uri}/v2/keys/foo").to_return(status: 307, headers: {'Location' => "#{etcd2_uri}/v2/keys/foo"})
222
+ stub_request(:put, "#{etcd2_uri}/v2/keys/foo").to_return(body: MultiJson.dump({'value' => 'bar'}))
221
223
  client.set("foo", "bar")
222
- client.leader.etcd.should == etcd2_uri
223
- client.leader.name.should == "node2"
224
+ client.leader.client_urls.first.should eq(etcd2_uri)
225
+ client.leader.name.should eq("node2")
224
226
  end
225
227
  end
226
228