etcd 0.0.6 → 0.2.0.alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/.coco.yml +5 -0
- data/.gitignore +0 -9
- data/.travis.yml +3 -7
- data/Gemfile +2 -0
- data/README.md +4 -3
- data/build_etcd +8 -0
- data/etcd.gemspec +1 -3
- data/lib/etcd.rb +2 -2
- data/lib/etcd/client.rb +129 -0
- data/lib/etcd/exceptions.rb +78 -0
- data/lib/etcd/keys.rb +147 -0
- data/lib/etcd/log.rb +10 -0
- data/lib/etcd/mod/leader.rb +28 -0
- data/lib/etcd/mod/lock.rb +59 -0
- data/lib/etcd/node.rb +44 -0
- data/lib/etcd/response.rb +33 -0
- data/lib/etcd/stats.rb +25 -0
- data/lib/etcd/version.rb +5 -0
- data/spec/etcd/client_spec.rb +54 -0
- data/spec/etcd/keys_spec.rb +26 -0
- data/spec/etcd/leader_spec.rb +29 -0
- data/spec/etcd/lock_spec.rb +46 -0
- data/spec/etcd/node_spec.rb +27 -0
- data/spec/{functional → etcd}/read_only_client_spec.rb +11 -2
- data/spec/etcd/readme_spec.rb +312 -0
- data/spec/etcd/stats_spec.rb +40 -0
- data/spec/{functional → etcd}/test_and_set_spec.rb +11 -3
- data/spec/{functional → etcd}/watch_spec.rb +13 -4
- data/spec/spec_helper.rb +88 -8
- metadata +46 -68
- data/spec/functional/client_spec.rb +0 -59
- data/spec/functional/lock_spec.rb +0 -70
- data/spec/functional_spec_helpers.rb +0 -56
- data/spec/unit/etcd_spec.rb +0 -15
data/lib/etcd/log.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
module Etcd
|
6
|
+
module Mod
|
7
|
+
module Leader
|
8
|
+
|
9
|
+
def mod_leader_endpoint
|
10
|
+
'/mod/v2/leader'
|
11
|
+
end
|
12
|
+
|
13
|
+
def set_leader(key, value, ttl)
|
14
|
+
path = mod_leader_endpoint + "#{key}?ttl=#{ttl}"
|
15
|
+
api_execute(path, :put, params:{name: value}).body
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_leader(key)
|
19
|
+
api_execute(mod_leader_endpoint + key, :get).body
|
20
|
+
end
|
21
|
+
|
22
|
+
def delete_leader(key, value)
|
23
|
+
path = mod_leader_endpoint + key + '?' + URI.encode_www_form(name: value)
|
24
|
+
api_execute(path, :delete).body
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
module Etcd
|
6
|
+
module Mod
|
7
|
+
module Lock
|
8
|
+
|
9
|
+
def mod_lock_endpoint
|
10
|
+
'/mod/v2/lock'
|
11
|
+
end
|
12
|
+
|
13
|
+
def acquire_lock(key, ttl, opts={})
|
14
|
+
path = mod_lock_endpoint + key + "?ttl=#{ttl}"
|
15
|
+
timeout = opts[:timeout] || 60
|
16
|
+
Timeout::timeout(timeout) do
|
17
|
+
api_execute(path, :post, params:opts)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def renew_lock(key, ttl, opts={})
|
22
|
+
unless opts.has_key?(:index) or opts.has_key?(:value)
|
23
|
+
raise ArgumentError, 'You mast pass index or value'
|
24
|
+
end
|
25
|
+
path = mod_lock_endpoint + key + "?ttl=#{ttl}"
|
26
|
+
timeout = opts[:timeout] || 60
|
27
|
+
Timeout::timeout(timeout) do
|
28
|
+
api_execute(path, :put, params:opts).body
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_lock(key, opts={})
|
33
|
+
api_execute(mod_lock_endpoint + key, :get, params:opts).body
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete_lock(key, opts={})
|
37
|
+
unless opts.has_key?(:index) or opts.has_key?(:value)
|
38
|
+
raise ArgumentError, 'You must pass index or value'
|
39
|
+
end
|
40
|
+
api_execute(mod_lock_endpoint + key, :delete, params:opts)
|
41
|
+
end
|
42
|
+
|
43
|
+
def lock(key, ttl, opts={})
|
44
|
+
acquire_lock('/'+key, ttl, opts)
|
45
|
+
index= get_lock('/'+key, field: index)
|
46
|
+
begin
|
47
|
+
yield key
|
48
|
+
rescue Exception => e
|
49
|
+
raise e
|
50
|
+
ensure
|
51
|
+
delete_lock(key, index: index)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
alias_method :retrive_lock, :get_lock
|
56
|
+
alias_method :release_lock, :delete_lock
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/etcd/node.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
module Etcd
|
4
|
+
class Node
|
5
|
+
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
attr_reader :created_index, :modified_index, :expiration, :ttl, :key, :value
|
9
|
+
alias :createdIndex :created_index
|
10
|
+
alias :modifiedIndex :modified_index
|
11
|
+
|
12
|
+
def initialize(opts={})
|
13
|
+
@created_index = opts['createdIndex']
|
14
|
+
@modified_index = opts['modifiedIndex']
|
15
|
+
@ttl = opts['ttl']
|
16
|
+
@key = opts['key']
|
17
|
+
@value = opts['value']
|
18
|
+
@expiration = opts['expiration']
|
19
|
+
@dir = opts['dir']
|
20
|
+
|
21
|
+
if opts['dir'] and (!!opts['nodes'])
|
22
|
+
opts['nodes'].each do |data|
|
23
|
+
children << Node.new(data)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def <=>(other)
|
29
|
+
key <=> other.key
|
30
|
+
end
|
31
|
+
|
32
|
+
def children
|
33
|
+
if directory?
|
34
|
+
@children ||= []
|
35
|
+
else
|
36
|
+
raise "This is not a directory, cant have children"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def directory?
|
41
|
+
!! @dir
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'etcd/node'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Etcd
|
7
|
+
|
8
|
+
class Response
|
9
|
+
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
attr_reader :action, :node, :etcd_index, :raft_index, :raft_term
|
13
|
+
|
14
|
+
def_delegators :@node, :key, :value, :directory?, :children
|
15
|
+
|
16
|
+
def initialize(opts, headers={})
|
17
|
+
@action = opts['action']
|
18
|
+
@node = Node.new(opts['node'])
|
19
|
+
@etcd_index = headers[:etcd_index]
|
20
|
+
@raft_index = headers[:raft_index]
|
21
|
+
@raft_term = headers[:raft_term]
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.from_http_response(response)
|
25
|
+
data = JSON.parse(response.body)
|
26
|
+
headers = Hash.new
|
27
|
+
headers[:etcd_index] = response['X-Etcd-Index'].to_i
|
28
|
+
headers[:raft_index] = response['X-Raft-Index'].to_i
|
29
|
+
headers[:raft_term] = response['X-Raft-Term'].to_i
|
30
|
+
response = Response.new(data, headers)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/etcd/stats.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Etcd
|
6
|
+
module Stats
|
7
|
+
|
8
|
+
def stats_endpoint
|
9
|
+
version_prefix + '/stats'
|
10
|
+
end
|
11
|
+
|
12
|
+
def stats(type)
|
13
|
+
case type
|
14
|
+
when :leader
|
15
|
+
JSON.parse(api_execute(stats_endpoint+'/leader', :get).body)
|
16
|
+
when :store
|
17
|
+
JSON.parse(api_execute(stats_endpoint+'/store', :get).body)
|
18
|
+
when :self
|
19
|
+
JSON.parse(api_execute(stats_endpoint+'/self', :get).body)
|
20
|
+
else
|
21
|
+
raise ArgumentError, "Invalid stats type '#{type}'"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/etcd/version.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Etcd::Client do
|
4
|
+
|
5
|
+
let(:client) do
|
6
|
+
Etcd.client
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should return the leader address' do
|
10
|
+
expect(client.leader).to_not be_nil
|
11
|
+
end
|
12
|
+
|
13
|
+
it '#machines' do
|
14
|
+
expect(client.machines).to include('http://127.0.0.1:4001')
|
15
|
+
end
|
16
|
+
|
17
|
+
it '#version' do
|
18
|
+
expect(client.version).to match(/^etcd v0\.2\./)
|
19
|
+
end
|
20
|
+
|
21
|
+
it '#version_prefix' do
|
22
|
+
expect(client.version_prefix).to eq('/v2')
|
23
|
+
end
|
24
|
+
|
25
|
+
context '#api_execute' do
|
26
|
+
it 'should raise exception when non http methods are passed' do
|
27
|
+
expect do
|
28
|
+
client.api_execute('/v2/keys/x', :do)
|
29
|
+
end.to raise_error
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should redirect api request when allo_redirect is set'
|
33
|
+
end
|
34
|
+
|
35
|
+
context '#http header based metadata' do
|
36
|
+
before(:all) do
|
37
|
+
key = random_key
|
38
|
+
value = uuid.generate
|
39
|
+
@response = Etcd.client.set(key,value)
|
40
|
+
end
|
41
|
+
|
42
|
+
it '#etcd_index' do
|
43
|
+
expect(@response.etcd_index).to_not be_nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it '#raft_index' do
|
47
|
+
expect(@response.raft_index).to_not be_nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it '#raft_term' do
|
51
|
+
expect(@response.raft_term).to_not be_nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Etcd::Keys do
|
4
|
+
|
5
|
+
let(:client) do
|
6
|
+
Etcd.client
|
7
|
+
end
|
8
|
+
|
9
|
+
it '#set/#get' do
|
10
|
+
key = random_key
|
11
|
+
value = uuid.generate
|
12
|
+
client.set(key, value)
|
13
|
+
expect(client.get(key).value).to eq(value)
|
14
|
+
end
|
15
|
+
|
16
|
+
context '#exists?' do
|
17
|
+
it 'should be true for existing keys' do
|
18
|
+
key = random_key
|
19
|
+
client.create(key, 10)
|
20
|
+
expect(client.exists?(key)).to be_true
|
21
|
+
end
|
22
|
+
it 'should be true for existing keys' do
|
23
|
+
expect(client.exists?(random_key)).to be_false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe 'mod leader' do
|
6
|
+
|
7
|
+
let(:client) do
|
8
|
+
Etcd.client
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should allow setting a key value with ttl' do
|
12
|
+
client.set_leader('/db_master1', 'db01', 10)
|
13
|
+
expect(client.get_leader('/db_master1')).to eq('db01')
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should allow deleting key with value' do
|
17
|
+
client.set_leader('/db_master4', 'db04', 10)
|
18
|
+
expect do
|
19
|
+
client.delete_leader('/db_master4', 'db04')
|
20
|
+
end.to_not raise_error
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should not allow deleting key without value' do
|
24
|
+
client.set_leader('/db_master5', 'db05', 10)
|
25
|
+
expect do
|
26
|
+
client.delete_leader('/db_master5', 'db04')
|
27
|
+
end.to raise_error
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe 'lock' do
|
6
|
+
|
7
|
+
let(:client) do
|
8
|
+
Etcd.client
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should be able to acquire a lock' do
|
12
|
+
expect do
|
13
|
+
client.acquire_lock('/my_lock',10)
|
14
|
+
end.to_not raise_error
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should be able to renew a lock based on value' do
|
18
|
+
client.acquire_lock('/my_lock1', 10, value: 123)
|
19
|
+
expect do
|
20
|
+
client.renew_lock('/my_lock1', 10, value: 123)
|
21
|
+
end.to_not raise_error
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should be able to renew a lock based on index' do
|
25
|
+
client.acquire_lock('/my_lock2', 10)
|
26
|
+
index = client.get_lock('/my_lock2', field:'index')
|
27
|
+
expect do
|
28
|
+
client.renew_lock('/my_lock2', 10, index: index)
|
29
|
+
end.to_not raise_error
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should be able to delete a lock based on value' do
|
33
|
+
client.acquire_lock('/my_lock3', 10, value: 123)
|
34
|
+
expect do
|
35
|
+
client.delete_lock('/my_lock3', value: 123)
|
36
|
+
end.to_not raise_error
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should be able to delete a lock based on index' do
|
40
|
+
client.acquire_lock('/my_lock4', 10)
|
41
|
+
index = client.get_lock('/my_lock4', field:'index')
|
42
|
+
expect do
|
43
|
+
client.delete_lock('/my_lock4', index: index)
|
44
|
+
end.to_not raise_error
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Etcd::Node do
|
4
|
+
|
5
|
+
let(:client) do
|
6
|
+
Etcd.client
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should create a directory with parent key when nested keys are set" do
|
10
|
+
parent = random_key
|
11
|
+
child = random_key
|
12
|
+
value = uuid.generate
|
13
|
+
client.set(parent+child, value)
|
14
|
+
expect(client.get(parent+child)).to_not be_directory
|
15
|
+
expect(client.get(parent)).to be_directory
|
16
|
+
end
|
17
|
+
|
18
|
+
context '#children' do
|
19
|
+
it 'should raise exception when invoked against a leaf node' do
|
20
|
+
parent = random_key
|
21
|
+
client.create(random_key, 10)
|
22
|
+
expect do
|
23
|
+
client.get(random_key).children
|
24
|
+
end.to raise_error
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,4 +1,13 @@
|
|
1
|
-
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe "Etcd read only client" do
|
6
|
+
|
7
|
+
let(:client) do
|
8
|
+
Etcd.client
|
9
|
+
end
|
10
|
+
|
2
11
|
it "should not allow write" do
|
3
12
|
key= random_key
|
4
13
|
expect{
|
@@ -17,7 +26,7 @@ shared_examples "read only client" do
|
|
17
26
|
it "should allow watch" do
|
18
27
|
key = random_key
|
19
28
|
value = uuid.generate
|
20
|
-
index = client.set(key, value).
|
29
|
+
index = client.set(key, value).node.modified_index
|
21
30
|
expect(read_only_client.watch(key, index: index).value).to eq(value)
|
22
31
|
end
|
23
32
|
end
|
@@ -0,0 +1,312 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe 'Etcd specs for the main etcd README examples' do
|
6
|
+
|
7
|
+
let(:client) do
|
8
|
+
Etcd.client
|
9
|
+
end
|
10
|
+
|
11
|
+
shared_examples 'response with valid node data' do |action|
|
12
|
+
if action == :delete
|
13
|
+
it 'should not have value' do
|
14
|
+
expect(@response.node.value).to be_nil
|
15
|
+
end
|
16
|
+
else
|
17
|
+
it 'should set the value correctly' do
|
18
|
+
expect(@response.node.value).to eq('PinkFloyd')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
if action == :create
|
22
|
+
it 'should set the parent key correctly' do
|
23
|
+
expect(@response.node.key).to match /^\/queue\/+/
|
24
|
+
end
|
25
|
+
else
|
26
|
+
it 'should set the key properly' do
|
27
|
+
expect(@response.node.key).to eq('/message')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'modified index should be a positive integer' do
|
32
|
+
expect(@response.node.created_index).to be > 0
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'created index should be a positive integer' do
|
36
|
+
expect(@response.node.modified_index).to be > 0
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
shared_examples 'response with valid http headers' do
|
41
|
+
|
42
|
+
it 'should have a positive etcd index (comes from http header)' do
|
43
|
+
expect(@response.etcd_index).to be > 0
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should have a positive raft index (comes from http header)' do
|
47
|
+
expect(@response.raft_index).to be > 0
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should have a positive raft term (comes from http header)' do
|
51
|
+
expect(@response.raft_term).to be >= 0
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'set a key named "/message"' do
|
56
|
+
|
57
|
+
|
58
|
+
before(:all) do
|
59
|
+
@response = Etcd.client.set('/message', 'PinkFloyd')
|
60
|
+
end
|
61
|
+
|
62
|
+
it_should_behave_like 'response with valid http headers'
|
63
|
+
it_should_behave_like 'response with valid node data'
|
64
|
+
|
65
|
+
it 'should set the return action to SET' do
|
66
|
+
expect(@response.action).to eq('set')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'get a key named "/message"' do
|
71
|
+
|
72
|
+
before(:all) do
|
73
|
+
Etcd.client.set('/message', 'PinkFloyd')
|
74
|
+
@response = Etcd.client.get('/message')
|
75
|
+
end
|
76
|
+
|
77
|
+
it_should_behave_like 'response with valid http headers'
|
78
|
+
it_should_behave_like 'response with valid node data'
|
79
|
+
|
80
|
+
it 'should set the return action to GET' do
|
81
|
+
expect(@response.action).to eq('get')
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'change the value of a key named "/message"' do
|
86
|
+
|
87
|
+
before(:all) do
|
88
|
+
Etcd.client.set('/message', 'World')
|
89
|
+
@response = Etcd.client.set('/message','PinkFloyd')
|
90
|
+
end
|
91
|
+
|
92
|
+
it_should_behave_like 'response with valid http headers'
|
93
|
+
it_should_behave_like 'response with valid node data'
|
94
|
+
|
95
|
+
it 'should set the return action to SET' do
|
96
|
+
expect(@response.action).to eq('set')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'delete a key named "/message"' do
|
101
|
+
|
102
|
+
before(:all) do
|
103
|
+
Etcd.client.set('/message', 'World')
|
104
|
+
Etcd.client.set('/message','PinkFloyd')
|
105
|
+
@response = Etcd.client.delete('/message')
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'should set the return action to SET' do
|
109
|
+
expect(@response.action).to eq('delete')
|
110
|
+
end
|
111
|
+
|
112
|
+
it_should_behave_like 'response with valid http headers'
|
113
|
+
it_should_behave_like 'response with valid node data', :delete
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'using ttl a key named "/message"' do
|
117
|
+
|
118
|
+
before(:all) do
|
119
|
+
Etcd.client.set('/message', 'World')
|
120
|
+
@set_time = Time.now
|
121
|
+
@response = Etcd.client.set('/message','PinkFloyd', 5)
|
122
|
+
end
|
123
|
+
|
124
|
+
it_should_behave_like 'response with valid http headers'
|
125
|
+
it_should_behave_like 'response with valid node data'
|
126
|
+
|
127
|
+
it 'should set the return action to SET' do
|
128
|
+
expect(@response.action).to eq('set')
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'should have valid expiration time' do
|
132
|
+
expect(@response.node.expiration).to_not be_nil
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'should have ttl available from the node' do
|
136
|
+
expect(@response.node.ttl).to eq(5)
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'should throw exception after the expiration time' do
|
140
|
+
sleep 8
|
141
|
+
expect do
|
142
|
+
Etcd.client.get('/message')
|
143
|
+
end.to raise_error
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'waiting for a change against a key named "/message"' do
|
149
|
+
|
150
|
+
before(:all) do
|
151
|
+
Etcd.client.set('/message', 'foo')
|
152
|
+
thr = Thread.new do
|
153
|
+
@response = Etcd.client.watch('/message')
|
154
|
+
end
|
155
|
+
Etcd.client.set('/message', 'PinkFloyd')
|
156
|
+
thr.join
|
157
|
+
end
|
158
|
+
|
159
|
+
it_should_behave_like 'response with valid http headers'
|
160
|
+
it_should_behave_like 'response with valid node data'
|
161
|
+
|
162
|
+
it 'should set the return action to SET' do
|
163
|
+
expect(@response.action).to eq('set')
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'should get the exact value by specifying a waitIndex' do
|
167
|
+
client.set('/message', 'someshit')
|
168
|
+
w_response = client.watch('/message', index: @response.node.modified_index)
|
169
|
+
expect(w_response.node.value).to eq('PinkFloyd')
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'atomic in-order keys' do
|
174
|
+
|
175
|
+
before(:all) do
|
176
|
+
@response = Etcd.client.create_in_order('/queue', 'PinkFloyd')
|
177
|
+
end
|
178
|
+
|
179
|
+
it_should_behave_like 'response with valid http headers'
|
180
|
+
it_should_behave_like 'response with valid node data', :create
|
181
|
+
|
182
|
+
it 'should set the return action to create' do
|
183
|
+
expect(@response.action).to eq('create')
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'should have the child key as a positive integer' do
|
187
|
+
expect(@response.key.split('/').last.to_i).to be > 0
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'should have the child keys as monotonically increasing' do
|
191
|
+
first_response = client.create_in_order('/queue', 'The Jimi Hendrix Experience')
|
192
|
+
second_response = client.create_in_order('/queue', 'The Doors')
|
193
|
+
first_key = first_response.key.split('/').last.to_i
|
194
|
+
second_key = second_response.key.split('/').last.to_i
|
195
|
+
expect(first_key).to be < second_key
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'should enlist all children in sorted manner' do
|
199
|
+
responses = []
|
200
|
+
10.times do |n|
|
201
|
+
responses << client.create_in_order('/queue', 'Deep Purple - Track #{n}')
|
202
|
+
end
|
203
|
+
directory = client.get('/queue', sorted: true)
|
204
|
+
past_index = directory.children.index(responses.first.node)
|
205
|
+
9.times do |n|
|
206
|
+
current_index = directory.children.index(responses[n+1].node)
|
207
|
+
expect(current_index).to be > past_index
|
208
|
+
past_index = current_index
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
context 'directory with ttl' do
|
214
|
+
|
215
|
+
before(:all) do
|
216
|
+
@response = Etcd.client.set('/directory', dir: true, ttl: 4)
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'should create a directory' do
|
220
|
+
expect(client.get('/directory')).to be_directory
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'should have valid expiration time' do
|
224
|
+
expect(client.get('/directory').node.expiration).to_not be_nil
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'should have pre-designated ttl' do
|
228
|
+
expect(client.get('/directory').node.ttl).to eq(4)
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'will throw error if updated without setting prevExist' do
|
232
|
+
expect do
|
233
|
+
client.set('/directory', dir:true, ttl:5)
|
234
|
+
end.to raise_error
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'can be updated by setting prevExist to true' do
|
238
|
+
client.set('/directory', prevExist: true, dir:true, ttl:5)
|
239
|
+
expect(client.get('/directory').node.ttl).to eq(5)
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'watchers should get expriy notification' do
|
243
|
+
client.set('/directory/a', 'Test')
|
244
|
+
client.set('/directory', prevExist: true, dir:true, ttl:2)
|
245
|
+
response = client.watch('/directory/a', consistent: true, timeout: 3)
|
246
|
+
expect(response.action).to eq('expire')
|
247
|
+
end
|
248
|
+
it 'should be expired after ttl' do
|
249
|
+
sleep 5
|
250
|
+
expect do
|
251
|
+
client.get('/directory')
|
252
|
+
end.to raise_error
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
context 'atomic compare and swap' do
|
257
|
+
|
258
|
+
it 'should raise error if prevExist is passed a false' do
|
259
|
+
client.set('/foo', 'one')
|
260
|
+
expect do
|
261
|
+
client.set('/foo','three', prevExist: false)
|
262
|
+
end.to raise_error
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'should raise error is prevValue is wrong' do
|
266
|
+
client.set('/foo', 'one')
|
267
|
+
expect do
|
268
|
+
client.set('/foo','three', prevValue: 'two')
|
269
|
+
end.to raise_error
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'should allow setting the value when prevValue is right' do
|
273
|
+
client.set('/foo', 'one')
|
274
|
+
expect(client.set('/foo','three', prevValue: 'one').value).to eq('three')
|
275
|
+
end
|
276
|
+
end
|
277
|
+
context 'directory manipulation' do
|
278
|
+
it 'should allow creating directory' do
|
279
|
+
expect(client.set('/dir',dir:true)).to be_directory
|
280
|
+
end
|
281
|
+
|
282
|
+
it 'should allow listing directory' do
|
283
|
+
client.set('/foo_dir/foo','bar')
|
284
|
+
expect(client.get('/').children.map(&:key)).to include('/foo_dir')
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'should allow recursive directory listing' do
|
288
|
+
response = client.get('/', recursive: true)
|
289
|
+
expect(response.children.find{|n|n.key=='/foo_dir'}.children).to_not be_empty
|
290
|
+
end
|
291
|
+
|
292
|
+
it 'should be able to delete empty directory without the recusrive flag' do
|
293
|
+
expect(client.delete('/dir', dir: true).action).to eq('delete')
|
294
|
+
end
|
295
|
+
|
296
|
+
it 'should be able to delete directory with children with the recusrive flag' do
|
297
|
+
expect(client.delete('/foo_dir', recursive: true).action).to eq('delete')
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
context 'hidden nodes' do
|
302
|
+
|
303
|
+
before(:all) do
|
304
|
+
Etcd.client.set('/_message', 'Hello Hidden World')
|
305
|
+
Etcd.client.set('/message', 'Hello World')
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'should not be visible in directory listing' do
|
309
|
+
expect(client.get('/').children.map(&:key)).to_not include('_message')
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|