etcd 0.0.6 → 0.2.0.alpha
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 +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
|