etcd-rb 1.0.0.pre1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +160 -13
- data/lib/etcd/client/failover.rb +109 -0
- data/lib/etcd/client/observing.rb +60 -0
- data/lib/etcd/client/protocol.rb +186 -0
- data/lib/etcd/client.rb +15 -405
- data/lib/etcd/cluster.rb +98 -0
- data/lib/etcd/constants.rb +18 -0
- data/lib/etcd/heartbeat.rb +44 -0
- data/lib/etcd/loggable.rb +13 -0
- data/lib/etcd/node.rb +45 -0
- data/lib/etcd/observer.rb +76 -0
- data/lib/etcd/requestable.rb +24 -0
- data/lib/etcd/version.rb +1 -1
- data/lib/etcd.rb +16 -3
- data/spec/etcd/client_spec.rb +34 -257
- data/spec/etcd/cluster_spec.rb +176 -0
- data/spec/etcd/node_spec.rb +58 -0
- data/spec/etcd/observer_spec.rb +163 -0
- data/spec/integration/etcd_spec.rb +43 -10
- data/spec/resources/cluster_controller.rb +19 -0
- data/spec/spec_helper.rb +3 -2
- data/spec/support/client_helper.rb +19 -0
- data/spec/support/cluster_helper.rb +75 -0
- data/spec/support/common_helper.rb +13 -0
- metadata +41 -22
@@ -0,0 +1,58 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
|
5
|
+
module Etcd
|
6
|
+
describe Node do
|
7
|
+
|
8
|
+
def default_node(opts = {})
|
9
|
+
args = {:name => "node1", :etcd => "http://example.com:4001", :raft => "http://example.com:7001"}
|
10
|
+
Etcd::Node.new(args.merge(opts))
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#initialize' do
|
14
|
+
it "works with all required parameters" do
|
15
|
+
default_node({}).should_not == nil
|
16
|
+
end
|
17
|
+
|
18
|
+
it "raises if :etcd url is missing" do
|
19
|
+
expect {
|
20
|
+
default_node({:etcd => nil})
|
21
|
+
}.to raise_error(ArgumentError)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#update_status' do
|
26
|
+
it "sets status :running if alive" do
|
27
|
+
node = default_node
|
28
|
+
stub_request(:get, node.leader_uri).to_return(body: node.raft)
|
29
|
+
node.update_status
|
30
|
+
node.status.should == :running
|
31
|
+
end
|
32
|
+
|
33
|
+
it "sets status :down if down" do
|
34
|
+
node = default_node
|
35
|
+
stub_request(:get, node.leader_uri).to_timeout
|
36
|
+
node.update_status
|
37
|
+
node.status.should == :down
|
38
|
+
end
|
39
|
+
|
40
|
+
it "marks leader-flag if leader" do
|
41
|
+
node = default_node
|
42
|
+
stub_request(:get, node.leader_uri).to_return(body: node.raft)
|
43
|
+
node.update_status
|
44
|
+
node.is_leader.should eq(true)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "marks leader-flag as :false if leader looses leadership" do
|
48
|
+
node = default_node
|
49
|
+
stub_request(:get, node.leader_uri).to_return(body: node.raft)
|
50
|
+
node.update_status
|
51
|
+
node.is_leader.should eq(true)
|
52
|
+
stub_request(:get, node.leader_uri).to_return(body: "bla")
|
53
|
+
node.update_status
|
54
|
+
node.is_leader.should eq(false)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
module Etcd
|
5
|
+
describe Client do
|
6
|
+
include ClusterHelper
|
7
|
+
include ClientHelper
|
8
|
+
|
9
|
+
def base_uri
|
10
|
+
"http://127.0.0.1:4001/v1"
|
11
|
+
end
|
12
|
+
|
13
|
+
let :client do
|
14
|
+
default_client
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#watch' do
|
18
|
+
it 'sends a GET request for a watch of a key prefix' do
|
19
|
+
stub_request(:get, "#{base_uri}/watch/foo").with(query: {}).to_return(body: MultiJson.dump({}))
|
20
|
+
client.watch('/foo') { }
|
21
|
+
WebMock.should have_requested(:get, "#{base_uri}/watch/foo").with(query: {})
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'sends a GET request for a watch of a key prefix from a specified index' do
|
25
|
+
stub_request(:post, "#{base_uri}/watch/foo").with(query: {'index' => 3}).to_return(body: MultiJson.dump({}))
|
26
|
+
client.watch('/foo', index: 3) { }
|
27
|
+
WebMock.should have_requested(:post, "#{base_uri}/watch/foo").with(query: {'index' => 3})
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'yields the value' do
|
31
|
+
body = MultiJson.dump({'value' => 'bar'})
|
32
|
+
stub_request(:get, "#{base_uri}/watch/foo").with(query: {}).to_return(body: body)
|
33
|
+
value = nil
|
34
|
+
client.watch('/foo') do |v|
|
35
|
+
value = v
|
36
|
+
end
|
37
|
+
value.should == 'bar'
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'yields the changed key' do
|
41
|
+
body = MultiJson.dump({'key' => '/foo/bar', 'value' => 'bar'})
|
42
|
+
stub_request(:get, "#{base_uri}/watch/foo").with(query: {}).to_return(body: body)
|
43
|
+
key = nil
|
44
|
+
client.watch('/foo') do |_, k|
|
45
|
+
key = k
|
46
|
+
end
|
47
|
+
key.should == '/foo/bar'
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'yields info about the key, when it is a new key' do
|
51
|
+
body = MultiJson.dump({'action' => 'SET', 'key' => '/foo/bar', 'value' => 'bar', 'index' => 3, 'newKey' => true})
|
52
|
+
stub_request(:get, "#{base_uri}/watch/foo").with(query: {}).to_return(body: body)
|
53
|
+
info = nil
|
54
|
+
client.watch('/foo') do |_, _, i|
|
55
|
+
info = i
|
56
|
+
end
|
57
|
+
info[:action].should == :set
|
58
|
+
info[:key].should == '/foo/bar'
|
59
|
+
info[:value].should == 'bar'
|
60
|
+
info[:index].should == 3
|
61
|
+
info[:new_key].should eq(true)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'yields info about the key, when the key was changed' do
|
65
|
+
body = MultiJson.dump({'action' => 'SET', 'key' => '/foo/bar', 'value' => 'bar', 'prevValue' => 'baz', 'index' => 3})
|
66
|
+
stub_request(:get, "#{base_uri}/watch/foo").with(query: {}).to_return(body: body)
|
67
|
+
info = nil
|
68
|
+
client.watch('/foo') do |_, _, i|
|
69
|
+
info = i
|
70
|
+
end
|
71
|
+
info[:action].should == :set
|
72
|
+
info[:key].should == '/foo/bar'
|
73
|
+
info[:value].should == 'bar'
|
74
|
+
info[:index].should == 3
|
75
|
+
info[:previous_value].should == 'baz'
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'yields info about the key, when the key has a TTL' do
|
79
|
+
body = MultiJson.dump({'action' => 'SET', 'key' => '/foo/bar', 'value' => 'bar', 'index' => 3, 'expiration' => '2013-12-11T12:09:08.123+02:00', 'ttl' => 7})
|
80
|
+
stub_request(:get, "#{base_uri}/watch/foo").with(query: {}).to_return(body: body)
|
81
|
+
info = nil
|
82
|
+
client.watch('/foo') do |_, _, i|
|
83
|
+
info = i
|
84
|
+
end
|
85
|
+
info[:action].should == :set
|
86
|
+
info[:key].should == '/foo/bar'
|
87
|
+
info[:value].should == 'bar'
|
88
|
+
info[:index].should == 3
|
89
|
+
# rounding because of ruby 2.0 time parsing bug @see https://gist.github.com/mindreframer/6746829
|
90
|
+
info[:expiration].to_f.round.should == (Time.utc(2013, 12, 11, 10, 9, 8) + 0.123).to_f.round
|
91
|
+
info[:ttl].should == 7
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'returns the return value of the block' do
|
95
|
+
body = MultiJson.dump({'action' => 'SET', 'key' => '/foo/bar', 'value' => 'bar', 'index' => 3, 'expiration' => '2013-12-11T12:09:08.123+02:00', 'ttl' => 7})
|
96
|
+
stub_request(:get, "#{base_uri}/watch/foo").with(query: {}).to_return(body: body)
|
97
|
+
return_value = client.watch('/foo') do |_, k, _|
|
98
|
+
k
|
99
|
+
end
|
100
|
+
return_value.should == '/foo/bar'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
describe '#observe' do
|
106
|
+
it 'watches the specified key prefix' do
|
107
|
+
stub_request(:get, "#{base_uri}/watch/foo").with(query: {}).to_return(body: MultiJson.dump({}))
|
108
|
+
barrier = Queue.new
|
109
|
+
observer = client.observe('/foo') do
|
110
|
+
barrier << :ping
|
111
|
+
observer.cancel
|
112
|
+
observer.join
|
113
|
+
end
|
114
|
+
barrier.pop
|
115
|
+
WebMock.should have_requested(:get, "#{base_uri}/watch/foo").with(query: {})
|
116
|
+
end
|
117
|
+
|
118
|
+
it 're-watches the prefix with the (last seen index + 1) immediately' do
|
119
|
+
stub_request(:get, "#{base_uri}/watch/foo").with(query: {}).to_return(body: MultiJson.dump({'index' => 3}))
|
120
|
+
stub_request(:post, "#{base_uri}/watch/foo").with(query: {'index' => 4}).to_return(body: MultiJson.dump({'index' => 4}))
|
121
|
+
barrier = Queue.new
|
122
|
+
observer = client.observe('/foo') do |_, _, info|
|
123
|
+
if info[:index] == 4
|
124
|
+
barrier << :ping
|
125
|
+
observer.cancel
|
126
|
+
observer.join
|
127
|
+
end
|
128
|
+
end
|
129
|
+
barrier.pop
|
130
|
+
WebMock.should have_requested(:get, "#{base_uri}/watch/foo").with(query: {})
|
131
|
+
WebMock.should have_requested(:post, "#{base_uri}/watch/foo").with(query: {'index' => 4})
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'yields the value, key and info to the block given' do
|
135
|
+
stub_request(:get, "#{base_uri}/watch/foo").with(query: {}).to_return(body: MultiJson.dump({'action' => 'SET', 'key' => '/foo/bar', 'value' => 'bar', 'index' => 3, 'newKey' => true}))
|
136
|
+
stub_request(:post, "#{base_uri}/watch/foo").with(query: {'index' => 4}).to_return(body: MultiJson.dump({'action' => 'DELETE', 'key' => '/foo/baz', 'value' => 'foo', 'index' => 4}))
|
137
|
+
stub_request(:post, "#{base_uri}/watch/foo").with(query: {'index' => 5}).to_return(body: MultiJson.dump({'action' => 'SET', 'key' => '/foo/bar', 'value' => 'hello', 'index' => 5}))
|
138
|
+
barrier = Queue.new
|
139
|
+
values = []
|
140
|
+
keys = []
|
141
|
+
actions = []
|
142
|
+
new_keys = []
|
143
|
+
observer = client.observe('/foo') do |value, key, info|
|
144
|
+
values << value
|
145
|
+
keys << key
|
146
|
+
actions << info[:action]
|
147
|
+
new_keys << info[:new_key]
|
148
|
+
if info[:index] == 5
|
149
|
+
barrier << :ping
|
150
|
+
observer.cancel
|
151
|
+
observer.join
|
152
|
+
end
|
153
|
+
end
|
154
|
+
barrier.pop
|
155
|
+
values.should == %w[bar foo hello]
|
156
|
+
keys.should == %w[/foo/bar /foo/baz /foo/bar]
|
157
|
+
actions.should == [:set, :delete, :set]
|
158
|
+
new_keys.should == [true, nil, nil]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
@@ -4,9 +4,10 @@ require 'spec_helper'
|
|
4
4
|
require 'open-uri'
|
5
5
|
|
6
6
|
|
7
|
-
describe '
|
7
|
+
describe 'With real server an etcd client' do
|
8
|
+
|
8
9
|
let :client do
|
9
|
-
Etcd::Client.
|
10
|
+
Etcd::Client.test_client
|
10
11
|
end
|
11
12
|
|
12
13
|
let :prefix do
|
@@ -19,14 +20,15 @@ describe 'A etcd client' do
|
|
19
20
|
|
20
21
|
before do
|
21
22
|
WebMock.disable!
|
23
|
+
WebMock.allow_net_connect!
|
22
24
|
end
|
23
25
|
|
24
26
|
before do
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
ClusterController.start_cluster
|
28
|
+
end
|
29
|
+
|
30
|
+
before do
|
31
|
+
pending('etcd could not be started, check it with `sh/cluster start`') unless client
|
30
32
|
end
|
31
33
|
|
32
34
|
before do
|
@@ -46,13 +48,44 @@ describe 'A etcd client' do
|
|
46
48
|
|
47
49
|
it 'watches for changes to a key' do
|
48
50
|
Thread.start { sleep(0.1); client.set(key, 'baz') }
|
49
|
-
new_value = client.watch(key) { |
|
51
|
+
new_value, info = *client.watch(key) { |v, k, info| [v, info] }
|
50
52
|
new_value.should == 'baz'
|
53
|
+
info[:new_key].should == true
|
51
54
|
end
|
52
55
|
|
53
56
|
it 'conditionally sets the value for a key' do
|
54
57
|
client.set(key, 'bar')
|
55
|
-
client.update(key, 'qux', 'baz').should
|
56
|
-
client.update(key, 'qux', 'bar').should
|
58
|
+
client.update(key, 'qux', 'baz').should eq(false)
|
59
|
+
client.update(key, 'qux', 'bar').should eq(true)
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
it "has heartbeat, that resets observed watches" do
|
64
|
+
ClusterController.start_cluster
|
65
|
+
client = Etcd::Client.test_client(:heartbeat_freq => 0.2)
|
66
|
+
client.cluster.nodes.map(&:status).uniq.should == [:running]
|
67
|
+
changes = Queue.new
|
68
|
+
|
69
|
+
client.observe('/foo') do |v,k,info|
|
70
|
+
puts "triggered #{info.inspect}"
|
71
|
+
changes << info
|
72
|
+
end
|
73
|
+
|
74
|
+
changes.size.should == 0
|
75
|
+
|
76
|
+
### simulate second console
|
77
|
+
a = Thread.new do
|
78
|
+
client = Etcd::Client.test_client
|
79
|
+
ClusterController.kill_node(client.cluster.leader.name)
|
80
|
+
sleep 0.4
|
81
|
+
puts "1.st try"
|
82
|
+
client.set("/foo", "bar")
|
83
|
+
sleep 0.4
|
84
|
+
puts "2.nd try"
|
85
|
+
client.set("/foo", "barss")
|
86
|
+
end
|
87
|
+
|
88
|
+
sleep 1.5
|
89
|
+
changes.size.should == 2
|
57
90
|
end
|
58
91
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class ClusterController
|
2
|
+
|
3
|
+
def self.sh_path
|
4
|
+
File.expand_path(File.join(File.dirname(__FILE__), '..', '..', "sh"))
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.kill_node(node_name)
|
8
|
+
node_pid = `ps -ef|grep #{node_name}|grep -v grep`.split[1]
|
9
|
+
`kill -9 #{node_pid}`
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.start_cluster
|
13
|
+
`#{sh_path}/cluster start`
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.stop_cluster
|
17
|
+
`#{sh_path}/cluster stop`
|
18
|
+
end
|
19
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
module ClientHelper
|
2
|
+
def default_client(uri = "http://127.0.0.1:4001")
|
3
|
+
client = Etcd::Client.new(:uris => uri)
|
4
|
+
client.cluster = healthy_cluster(uri)
|
5
|
+
client
|
6
|
+
end
|
7
|
+
|
8
|
+
# manually construct a valid cluster object
|
9
|
+
# clumsy, but works atm
|
10
|
+
def healthy_cluster(uri = "http://127.0.0.1:4001")
|
11
|
+
data = Etcd::Cluster.parse_cluster_status(status_data)
|
12
|
+
nodes = Etcd::Cluster.nodes_from_attributes(data)
|
13
|
+
cluster = Etcd::Cluster.new(uri)
|
14
|
+
cluster.nodes = nodes
|
15
|
+
nodes.map{|x| x.status = :running}
|
16
|
+
nodes.first.is_leader = true
|
17
|
+
cluster
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module ClusterHelper
|
2
|
+
def status_data
|
3
|
+
[
|
4
|
+
{"action"=>"GET",
|
5
|
+
"key"=>"/_etcd/machines/node1",
|
6
|
+
"value"=>
|
7
|
+
"raft=http://127.0.0.1:7001&etcd=http://127.0.0.1:4001&raftVersion=v0.1.1",
|
8
|
+
"index"=>360},
|
9
|
+
{"action"=>"GET",
|
10
|
+
"key"=>"/_etcd/machines/node2",
|
11
|
+
"value"=>
|
12
|
+
"raft=http://127.0.0.1:7002&etcd=http://127.0.0.1:4002&raftVersion=v0.1.1",
|
13
|
+
"index"=>360},
|
14
|
+
{"action"=>"GET",
|
15
|
+
"key"=>"/_etcd/machines/node3",
|
16
|
+
"value"=>
|
17
|
+
"raft=http://127.0.0.1:7003&etcd=http://127.0.0.1:4003&raftVersion=v0.1.1",
|
18
|
+
"index"=>360}
|
19
|
+
]
|
20
|
+
end
|
21
|
+
|
22
|
+
def healthy_cluster_config
|
23
|
+
{
|
24
|
+
'http://127.0.0.1:4001' => 'http://127.0.0.1:7001',
|
25
|
+
'http://127.0.0.1:4002' => 'http://127.0.0.1:7001',
|
26
|
+
'http://127.0.0.1:4003' => 'http://127.0.0.1:7001'
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def one_down_cluster_config
|
31
|
+
{
|
32
|
+
'http://127.0.0.1:4001' => 'http://127.0.0.1:7001',
|
33
|
+
'http://127.0.0.1:4002' => 'http://127.0.0.1:7001',
|
34
|
+
'http://127.0.0.1:4003' => :down
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def healthy_cluster_changed_leader_config
|
39
|
+
{
|
40
|
+
'http://127.0.0.1:4001' => 'http://127.0.0.1:7002',
|
41
|
+
'http://127.0.0.1:4002' => 'http://127.0.0.1:7002',
|
42
|
+
'http://127.0.0.1:4003' => 'http://127.0.0.1:7002'
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def with_stubbed_status(uri)
|
47
|
+
status_uri = Etcd::Cluster.status_uri(uri)
|
48
|
+
stub_request(:get, status_uri).to_return(body: MultiJson.dump(status_data))
|
49
|
+
yield if block_given?
|
50
|
+
#WebMock.should have_requested(:get, status_uri)
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def leader_uri(uri)
|
55
|
+
"#{uri}/v1/leader"
|
56
|
+
end
|
57
|
+
|
58
|
+
def stub_leader_uri(uri, opts = {})
|
59
|
+
leader = (opts[:leader] ||"http://127.0.0.1:7001")
|
60
|
+
if leader == :down
|
61
|
+
stub_request(:get, leader_uri(uri)).to_timeout
|
62
|
+
else
|
63
|
+
stub_request(:get, leader_uri(uri)).to_return(body: leader)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# [{etcd_url => leader_string},{etcd_url => leader_string}]
|
68
|
+
def with_stubbed_leaders(cluster_config)
|
69
|
+
cluster_config.each do |url, leader_uri|
|
70
|
+
stub_leader_uri(url, :leader => leader_uri)
|
71
|
+
end
|
72
|
+
yield if block_given?
|
73
|
+
#urls.each { |url| WebMock.should have_requested(:get, leader_uri(url))}
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require './spec/resources/cluster_controller'
|
2
|
+
|
3
|
+
class Etcd::Client
|
4
|
+
def self.test_client(opts = {})
|
5
|
+
seed_uris = ["http://127.0.0.1:4001", "http://127.0.0.1:4002", "http://127.0.0.1:4003"]
|
6
|
+
opts.merge!(:uris => seed_uris)
|
7
|
+
begin
|
8
|
+
client = Etcd::Client.connect(opts)
|
9
|
+
rescue Etcd::AllNodesDownError
|
10
|
+
return nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
metadata
CHANGED
@@ -1,48 +1,44 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: etcd-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0
|
5
|
-
prerelease: 6
|
4
|
+
version: 1.0.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Theo Hultberg
|
8
|
+
- Roman Heinrich
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2014-10-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: httpclient
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
17
|
requirements:
|
19
|
-
- - ~>
|
18
|
+
- - "~>"
|
20
19
|
- !ruby/object:Gem::Version
|
21
20
|
version: 2.3.0
|
22
21
|
type: :runtime
|
23
22
|
prerelease: false
|
24
23
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
24
|
requirements:
|
27
|
-
- - ~>
|
25
|
+
- - "~>"
|
28
26
|
- !ruby/object:Gem::Version
|
29
27
|
version: 2.3.0
|
30
28
|
- !ruby/object:Gem::Dependency
|
31
29
|
name: multi_json
|
32
30
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
31
|
requirements:
|
35
|
-
- - ~>
|
32
|
+
- - "~>"
|
36
33
|
- !ruby/object:Gem::Version
|
37
|
-
version: 1.7
|
34
|
+
version: '1.7'
|
38
35
|
type: :runtime
|
39
36
|
prerelease: false
|
40
37
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
38
|
requirements:
|
43
|
-
- - ~>
|
39
|
+
- - "~>"
|
44
40
|
- !ruby/object:Gem::Version
|
45
|
-
version: 1.7
|
41
|
+
version: '1.7'
|
46
42
|
description: ''
|
47
43
|
email:
|
48
44
|
- theo@iconara.net
|
@@ -50,40 +46,63 @@ executables: []
|
|
50
46
|
extensions: []
|
51
47
|
extra_rdoc_files: []
|
52
48
|
files:
|
49
|
+
- README.md
|
50
|
+
- lib/etcd.rb
|
53
51
|
- lib/etcd/client.rb
|
52
|
+
- lib/etcd/client/failover.rb
|
53
|
+
- lib/etcd/client/observing.rb
|
54
|
+
- lib/etcd/client/protocol.rb
|
55
|
+
- lib/etcd/cluster.rb
|
56
|
+
- lib/etcd/constants.rb
|
57
|
+
- lib/etcd/heartbeat.rb
|
58
|
+
- lib/etcd/loggable.rb
|
59
|
+
- lib/etcd/node.rb
|
60
|
+
- lib/etcd/observer.rb
|
61
|
+
- lib/etcd/requestable.rb
|
54
62
|
- lib/etcd/version.rb
|
55
|
-
- lib/etcd.rb
|
56
|
-
- README.md
|
57
63
|
- spec/etcd/client_spec.rb
|
64
|
+
- spec/etcd/cluster_spec.rb
|
65
|
+
- spec/etcd/node_spec.rb
|
66
|
+
- spec/etcd/observer_spec.rb
|
58
67
|
- spec/integration/etcd_spec.rb
|
68
|
+
- spec/resources/cluster_controller.rb
|
59
69
|
- spec/spec_helper.rb
|
70
|
+
- spec/support/client_helper.rb
|
71
|
+
- spec/support/cluster_helper.rb
|
72
|
+
- spec/support/common_helper.rb
|
60
73
|
homepage: http://github.com/iconara/etcd-rb
|
61
74
|
licenses:
|
62
75
|
- Apache License 2.0
|
76
|
+
metadata: {}
|
63
77
|
post_install_message:
|
64
78
|
rdoc_options: []
|
65
79
|
require_paths:
|
66
80
|
- lib
|
67
81
|
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
-
none: false
|
69
82
|
requirements:
|
70
|
-
- -
|
83
|
+
- - ">="
|
71
84
|
- !ruby/object:Gem::Version
|
72
85
|
version: 1.9.3
|
73
86
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
-
none: false
|
75
87
|
requirements:
|
76
|
-
- -
|
88
|
+
- - ">="
|
77
89
|
- !ruby/object:Gem::Version
|
78
|
-
version:
|
90
|
+
version: '0'
|
79
91
|
requirements: []
|
80
92
|
rubyforge_project:
|
81
|
-
rubygems_version:
|
93
|
+
rubygems_version: 2.2.2
|
82
94
|
signing_key:
|
83
|
-
specification_version:
|
95
|
+
specification_version: 4
|
84
96
|
summary: ''
|
85
97
|
test_files:
|
86
98
|
- spec/etcd/client_spec.rb
|
99
|
+
- spec/etcd/cluster_spec.rb
|
100
|
+
- spec/etcd/node_spec.rb
|
101
|
+
- spec/etcd/observer_spec.rb
|
87
102
|
- spec/integration/etcd_spec.rb
|
103
|
+
- spec/resources/cluster_controller.rb
|
88
104
|
- spec/spec_helper.rb
|
105
|
+
- spec/support/client_helper.rb
|
106
|
+
- spec/support/cluster_helper.rb
|
107
|
+
- spec/support/common_helper.rb
|
89
108
|
has_rdoc:
|