etcd-rb 1.0.0.pre1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|