etcdv3 0.9.0 → 0.11.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +6 -5
- data/.travis.yml +4 -5
- data/README.md +45 -2
- data/Rakefile +3 -2
- data/lib/etcdv3/connection.rb +26 -5
- data/lib/etcdv3/connection_wrapper.rb +22 -7
- data/lib/etcdv3/namespace/kv/requests.rb +54 -0
- data/lib/etcdv3/namespace/kv/transaction.rb +91 -0
- data/lib/etcdv3/namespace/kv.rb +62 -0
- data/lib/etcdv3/namespace/lock.rb +32 -0
- data/lib/etcdv3/namespace/utilities.rb +44 -0
- data/lib/etcdv3/namespace/watch.rb +38 -0
- data/lib/etcdv3/version.rb +1 -1
- data/lib/etcdv3/watch.rb +12 -4
- data/lib/etcdv3.rb +18 -4
- data/spec/etcdv3/connection_spec.rb +3 -3
- data/spec/etcdv3/connection_wrapper_spec.rb +39 -8
- data/spec/etcdv3/namespace/kv_spec.rb +82 -0
- data/spec/etcdv3/namespace/lock_spec.rb +23 -0
- data/spec/etcdv3/namespace/watch_spec.rb +36 -0
- data/spec/etcdv3/watch_spec.rb +24 -0
- data/spec/etcdv3_spec.rb +271 -1
- data/spec/helpers/connections.rb +10 -2
- data/spec/helpers/shared_examples_for_timeout.rb +4 -4
- data/spec/spec_helper.rb +6 -4
- metadata +17 -4
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe Etcdv3::Connection do
|
4
4
|
|
5
5
|
describe '#initialize - without metadata' do
|
6
|
-
subject { Etcdv3::Connection.new('http://localhost:2379', 10) }
|
6
|
+
subject { Etcdv3::Connection.new('http://localhost:2379', 10, nil) }
|
7
7
|
|
8
8
|
it { is_expected.to have_attributes(endpoint: URI('http://localhost:2379')) }
|
9
9
|
it { is_expected.to have_attributes(credentials: :this_channel_is_insecure) }
|
@@ -22,7 +22,7 @@ describe Etcdv3::Connection do
|
|
22
22
|
end
|
23
23
|
|
24
24
|
describe '#initialize - with metadata' do
|
25
|
-
subject { Etcdv3::Connection.new('http://localhost:2379', 10, token: 'token123') }
|
25
|
+
subject { Etcdv3::Connection.new('http://localhost:2379', 10, nil, token: 'token123') }
|
26
26
|
|
27
27
|
[:kv, :maintenance, :lease, :watch, :auth].each do |handler|
|
28
28
|
let(:handler_stub) { subject.handlers[handler].instance_variable_get(:@stub) }
|
@@ -37,7 +37,7 @@ describe Etcdv3::Connection do
|
|
37
37
|
end
|
38
38
|
|
39
39
|
describe '#refresh_metadata' do
|
40
|
-
subject { Etcdv3::Connection.new('http://localhost:2379', token: 'token123') }
|
40
|
+
subject { Etcdv3::Connection.new('http://localhost:2379', nil, token: 'token123') }
|
41
41
|
before { subject.refresh_metadata(token: 'newtoken') }
|
42
42
|
[:kv, :maintenance, :lease, :watch, :auth].each do |handler|
|
43
43
|
let(:handler_metadata) { subject.handlers[handler].instance_variable_get(:@metadata) }
|
@@ -3,9 +3,9 @@ require 'spec_helper'
|
|
3
3
|
describe Etcdv3::ConnectionWrapper do
|
4
4
|
let(:conn) { local_connection }
|
5
5
|
let(:endpoints) { ['http://localhost:2379', 'http://localhost:2389'] }
|
6
|
+
subject { Etcdv3::ConnectionWrapper.new(10, *endpoints, nil, true) }
|
6
7
|
|
7
8
|
describe '#initialize' do
|
8
|
-
subject { Etcdv3::ConnectionWrapper.new(10, *endpoints) }
|
9
9
|
it { is_expected.to have_attributes(user: nil, password: nil, token: nil) }
|
10
10
|
it 'sets hostnames in correct order' do
|
11
11
|
expect(subject.endpoints.map(&:hostname)).to eq(['localhost:2379', 'localhost:2389'])
|
@@ -16,7 +16,6 @@ describe Etcdv3::ConnectionWrapper do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
describe "#rotate_connection_endpoint" do
|
19
|
-
subject { Etcdv3::ConnectionWrapper.new(10, *endpoints) }
|
20
19
|
before do
|
21
20
|
subject.rotate_connection_endpoint
|
22
21
|
end
|
@@ -29,11 +28,30 @@ describe Etcdv3::ConnectionWrapper do
|
|
29
28
|
end
|
30
29
|
|
31
30
|
describe "Failover Simulation" do
|
32
|
-
let(:
|
31
|
+
let(:allow_reconnect) { true }
|
32
|
+
let(:modified_conn) {
|
33
|
+
local_connection(
|
34
|
+
"http://localhost:2369, http://localhost:2379",
|
35
|
+
allow_reconnect: allow_reconnect
|
36
|
+
)
|
37
|
+
}
|
38
|
+
subject { modified_conn.get('boom') }
|
39
|
+
|
33
40
|
context 'without auth' do
|
34
41
|
# Set primary endpoint to a non-existing etcd endpoint
|
35
|
-
|
36
|
-
|
42
|
+
context 'with reconnect' do
|
43
|
+
it { is_expected.to be_an_instance_of(Etcdserverpb::RangeResponse) }
|
44
|
+
end
|
45
|
+
context 'without reconnect' do
|
46
|
+
let(:allow_reconnect) { false }
|
47
|
+
it { expect { subject }.to raise_error(GRPC::Unavailable) }
|
48
|
+
end
|
49
|
+
context 'without reconnect with single endpoint' do
|
50
|
+
let(:modified_conn) {
|
51
|
+
local_connection("http://localhost:2369",allow_reconnect: false)
|
52
|
+
}
|
53
|
+
it { expect { subject }.to raise_error(GRPC::Unavailable) }
|
54
|
+
end
|
37
55
|
end
|
38
56
|
context 'with auth' do
|
39
57
|
before do
|
@@ -50,12 +68,19 @@ describe Etcdv3::ConnectionWrapper do
|
|
50
68
|
modified_conn.auth_disable
|
51
69
|
modified_conn.user_delete('root')
|
52
70
|
end
|
53
|
-
|
54
|
-
|
71
|
+
context 'with reconnect' do
|
72
|
+
it { is_expected.to be_an_instance_of(Etcdserverpb::RangeResponse) }
|
73
|
+
end
|
74
|
+
context 'without reconnect' do
|
75
|
+
let(:allow_reconnect) { false }
|
76
|
+
it { expect { subject }.to raise_error(GRPC::Unavailable) }
|
77
|
+
end
|
55
78
|
end
|
56
79
|
end
|
57
80
|
|
58
81
|
describe "GRPC::Unauthenticated recovery" do
|
82
|
+
let(:allow_reconnect) { true }
|
83
|
+
let(:conn) { local_connection(allow_reconnect: allow_reconnect) }
|
59
84
|
let(:wrapper) { conn.send(:conn) }
|
60
85
|
let(:connection) { wrapper.connection }
|
61
86
|
before do
|
@@ -71,6 +96,12 @@ describe Etcdv3::ConnectionWrapper do
|
|
71
96
|
conn.user_delete('root')
|
72
97
|
end
|
73
98
|
subject { conn.user_get('root') }
|
74
|
-
|
99
|
+
context 'with reconnect' do
|
100
|
+
it { is_expected.to be_an_instance_of(Etcdserverpb::AuthUserGetResponse) }
|
101
|
+
end
|
102
|
+
context 'without reconnect' do
|
103
|
+
let(:allow_reconnect) { false }
|
104
|
+
it { expect { subject }.to raise_error(GRPC::Unauthenticated) }
|
105
|
+
end
|
75
106
|
end
|
76
107
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Etcdv3::KV do
|
4
|
+
let(:stub) { local_namespace_stub(Etcdv3::Namespace::KV, 1, '/namespace/') }
|
5
|
+
let(:lease_stub) { local_stub(Etcdv3::Lease, 1) }
|
6
|
+
|
7
|
+
it_should_behave_like "a method with a GRPC timeout", described_class, :get, :range, "key"
|
8
|
+
it_should_behave_like "a method with a GRPC timeout", described_class, :del, :delete_range, "key"
|
9
|
+
it_should_behave_like "a method with a GRPC timeout", described_class, :put, :put, "key", "val"
|
10
|
+
|
11
|
+
it "should timeout transactions" do
|
12
|
+
stub = local_namespace_stub(Etcdv3::Namespace::KV, 0, '/namespace/')
|
13
|
+
expect { stub.transaction(Proc.new { nil }) }.to raise_error(GRPC::DeadlineExceeded)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#put' do
|
17
|
+
context 'without lease' do
|
18
|
+
subject { stub.put('test', 'test') }
|
19
|
+
it { is_expected.to be_an_instance_of(Etcdserverpb::PutResponse) }
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'with lease' do
|
23
|
+
let(:lease_id) { lease_stub.lease_grant(1)['ID'] }
|
24
|
+
subject { stub.put('lease', 'test', lease: lease_id) }
|
25
|
+
it { is_expected.to be_an_instance_of(Etcdserverpb::PutResponse) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#get' do
|
30
|
+
subject { stub.get('test') }
|
31
|
+
it { is_expected.to be_an_instance_of(Etcdserverpb::RangeResponse) }
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#del' do
|
35
|
+
context 'del without range' do
|
36
|
+
subject { stub.del('test') }
|
37
|
+
it { is_expected.to be_an_instance_of(Etcdserverpb::DeleteRangeResponse) }
|
38
|
+
end
|
39
|
+
context 'del with range' do
|
40
|
+
subject { stub.del('test', range_end: 'testtt') }
|
41
|
+
it { is_expected.to be_an_instance_of(Etcdserverpb::DeleteRangeResponse) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#transaction' do
|
46
|
+
context 'put' do
|
47
|
+
let!(:block) do
|
48
|
+
Proc.new do |txn|
|
49
|
+
txn.compare = [ txn.value('txn', :equal, 'value') ]
|
50
|
+
txn.success = [ txn.put('txn-test', 'success') ]
|
51
|
+
txn.failure = [ txn.put('txn-test', 'failed') ]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
subject { stub.transaction(block) }
|
55
|
+
it { is_expected.to be_an_instance_of(Etcdserverpb::TxnResponse) }
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'del' do
|
59
|
+
let!(:block) do
|
60
|
+
Proc.new do |txn|
|
61
|
+
txn.compare = [ txn.value('txn', :equal, 'value') ]
|
62
|
+
txn.success = [ txn.del('txn-one') ]
|
63
|
+
txn.failure = [ txn.del('txn-two') ]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
subject { stub.transaction(block) }
|
67
|
+
it { is_expected.to be_an_instance_of(Etcdserverpb::TxnResponse) }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'get' do
|
72
|
+
let!(:block) do
|
73
|
+
Proc.new do |txn|
|
74
|
+
txn.compare = [ txn.value('txn', :equal, 'value') ]
|
75
|
+
txn.success = [ txn.get('txn-success') ]
|
76
|
+
txn.failure = [ txn.get('txn-failure') ]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
subject { stub.transaction(block) }
|
80
|
+
it { is_expected.to be_an_instance_of(Etcdserverpb::TxnResponse) }
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# Locking is not implemented in etcd v3.1.X
|
4
|
+
unless $instance.version < Gem::Version.new("3.2.0")
|
5
|
+
describe Etcdv3::Lock do
|
6
|
+
let(:stub) { local_namespace_stub(Etcdv3::Namespace::Lock, 1, '/namespace/') }
|
7
|
+
let(:lease_stub) { local_stub(Etcdv3::Lease, 1) }
|
8
|
+
|
9
|
+
it_should_behave_like "a method with a GRPC timeout", described_class, :unlock, :unlock, 'foo'
|
10
|
+
# it_should_behave_like "a method with a GRPC timeout", described_class, :lock, :lock, 'foo'
|
11
|
+
|
12
|
+
describe '#lock' do
|
13
|
+
let(:lease_id) { lease_stub.lease_grant(10)['ID'] }
|
14
|
+
subject { stub.lock('foo', lease_id) }
|
15
|
+
it { is_expected.to be_an_instance_of(V3lockpb::LockResponse) }
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#unlock' do
|
19
|
+
subject { stub.unlock('foo') }
|
20
|
+
it { is_expected.to be_an_instance_of(V3lockpb::UnlockResponse) }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
|
5
|
+
describe Etcdv3::Namespace::Watch do
|
6
|
+
let(:stub) { local_namespace_stub(Etcdv3::Namespace::Watch, 5, '/namespace/') }
|
7
|
+
let(:kv_stub_no_ns) { local_stub(Etcdv3::KV, 1) }
|
8
|
+
let(:kv_stub) { local_namespace_stub(Etcdv3::Namespace::KV, 1, '/namespace/') }
|
9
|
+
|
10
|
+
context 'watch' do
|
11
|
+
it 'should return an event' do
|
12
|
+
resp = nil
|
13
|
+
thr = Thread.new do |thr|
|
14
|
+
resp = stub.watch("foo", nil, 1, nil)
|
15
|
+
end
|
16
|
+
sleep 2
|
17
|
+
kv_stub.put("foo", "works")
|
18
|
+
thr.join
|
19
|
+
expect(resp).to be_an_instance_of(Google::Protobuf::RepeatedField)
|
20
|
+
expect(resp.last.kv.key).to eq('foo')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should return event when non-namespace client writes to key' do
|
24
|
+
resp = nil
|
25
|
+
thr = Thread.new do |thr|
|
26
|
+
resp = stub.watch("foobar", nil, 1, nil)
|
27
|
+
end
|
28
|
+
sleep 2
|
29
|
+
kv_stub_no_ns.put("/namespace/foobar", "works")
|
30
|
+
thr.join
|
31
|
+
expect(resp).to be_an_instance_of(Google::Protobuf::RepeatedField)
|
32
|
+
expect(resp.last.kv.key).to eq('foobar')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
# Locking is not implemented in etcd v3.1.X
|
5
|
+
unless $instance.version < Gem::Version.new("3.2.0")
|
6
|
+
describe Etcdv3::Watch do
|
7
|
+
let(:stub) { local_stub(Etcdv3::Watch, 5) }
|
8
|
+
let(:kv_stub) { local_stub(Etcdv3::KV, 1) }
|
9
|
+
|
10
|
+
context 'watch' do
|
11
|
+
it 'should return an event' do
|
12
|
+
resp = nil
|
13
|
+
thr = Thread.new do |thr|
|
14
|
+
resp = stub.watch("foo", nil, 1, nil)
|
15
|
+
end
|
16
|
+
sleep 2
|
17
|
+
kv_stub.put("foo", "works")
|
18
|
+
thr.join
|
19
|
+
expect(resp).to be_an_instance_of(Google::Protobuf::RepeatedField)
|
20
|
+
expect(resp.last.kv.key).to eq('foo')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/spec/etcdv3_spec.rb
CHANGED
@@ -181,6 +181,20 @@ describe Etcdv3 do
|
|
181
181
|
end
|
182
182
|
end
|
183
183
|
|
184
|
+
describe '#watch' do
|
185
|
+
let!(:foo) { conn.put('foo', 'bar') }
|
186
|
+
subject { conn.watch('foo', start_revision: 1) }
|
187
|
+
it { is_expected.to_not be_nil }
|
188
|
+
it "raises a GRPC::DeadlineExceeded exception when it takes too long" do
|
189
|
+
expect do
|
190
|
+
conn.watch('foo', timeout: 0)
|
191
|
+
end.to raise_exception(GRPC::DeadlineExceeded)
|
192
|
+
end
|
193
|
+
it "accepts a timeout" do
|
194
|
+
expect{ conn.watch('foo', start_revision: 1, timeout: 10) }.to_not raise_exception
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
184
198
|
describe '#lease_keep_alive_once' do
|
185
199
|
let!(:lease_id) { conn.lease_grant(2)['ID'] }
|
186
200
|
subject { conn.lease_keep_alive_once(lease_id) }
|
@@ -279,7 +293,7 @@ describe Etcdv3 do
|
|
279
293
|
describe '#role_grant_permission' do
|
280
294
|
before { conn.role_add('grant') }
|
281
295
|
after { conn.role_delete('grant') }
|
282
|
-
subject { conn.role_grant_permission('grant', :readwrite, 'a', {range_end: 'Z'}) }
|
296
|
+
subject { conn.role_grant_permission('grant', :readwrite, 'a', **{range_end: 'Z'}) }
|
283
297
|
it { is_expected.to_not be_nil }
|
284
298
|
it_should_behave_like "Etcdv3 instance using a timeout", :role_grant_permission, 'grant', :readwrite, 'a'
|
285
299
|
end
|
@@ -507,5 +521,261 @@ describe Etcdv3 do
|
|
507
521
|
end
|
508
522
|
end
|
509
523
|
end
|
524
|
+
|
525
|
+
describe "namespace" do
|
526
|
+
|
527
|
+
describe '#get' do
|
528
|
+
let(:get_conn) { local_connection_with_namespace("/namespace-get/") }
|
529
|
+
|
530
|
+
before do
|
531
|
+
conn.put('/apples/', 'app')
|
532
|
+
conn.put('/namespace-get/apple', 'apple')
|
533
|
+
conn.put('/namespace-get/apples', 'apples')
|
534
|
+
conn.put('/namespace-get/appless', 'appless')
|
535
|
+
end
|
536
|
+
|
537
|
+
it 'returns key w/o namespace' do
|
538
|
+
expect(get_conn.get("apple").kvs.last.value).to eq('apple')
|
539
|
+
end
|
540
|
+
|
541
|
+
it 'returns keys w/o namespace' do
|
542
|
+
expect(get_conn.get("apple", range_end: 'applf').kvs.size).to eq(3)
|
543
|
+
end
|
544
|
+
|
545
|
+
it 'returns all keys under namespace' do
|
546
|
+
expect(get_conn.get("", range_end: "\0").kvs.size).to eq(3)
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
describe '#put' do
|
551
|
+
let(:put_conn) { local_connection_with_namespace("/namespace-put/") }
|
552
|
+
|
553
|
+
before do
|
554
|
+
put_conn.put('apple_put', 'test')
|
555
|
+
end
|
556
|
+
it 'returns key with namespace' do
|
557
|
+
expect(conn.get("/namespace-put/apple_put").kvs.last.value).to eq('test')
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
describe '#del' do
|
562
|
+
let(:del_conn) { local_connection_with_namespace("/del-test/") }
|
563
|
+
|
564
|
+
context 'zero-byte' do
|
565
|
+
before do
|
566
|
+
del_conn.put('test', "key")
|
567
|
+
del_conn.put('test2', "key2")
|
568
|
+
conn.put('wall', 'zzzz')
|
569
|
+
conn.put('walzz', 'adsfas')
|
570
|
+
end
|
571
|
+
|
572
|
+
it 'deleting all keys should be scoped to namespace' do
|
573
|
+
resp = del_conn.del("", range_end: "\0")
|
574
|
+
expect(resp.deleted).to eq(2)
|
575
|
+
expect(conn.get("wall").kvs.last.value).to eq('zzzz')
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
context 'no range' do
|
580
|
+
before { del_conn.put('test', 'value') }
|
581
|
+
subject { del_conn.del('test') }
|
582
|
+
it { is_expected.to_not be_nil }
|
583
|
+
end
|
584
|
+
|
585
|
+
context 'ranged del' do
|
586
|
+
before do
|
587
|
+
del_conn.put('test', 'value')
|
588
|
+
del_conn.put('testt', 'value')
|
589
|
+
end
|
590
|
+
subject { del_conn.del('test', range_end: 'testtt') }
|
591
|
+
it { is_expected.to_not be_nil }
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
describe '#transaction' do
|
596
|
+
let(:trans_conn) { local_connection_with_namespace("/namespace/") }
|
597
|
+
|
598
|
+
describe 'txn.value' do
|
599
|
+
before { trans_conn.put('txn', 'value') }
|
600
|
+
after { trans_conn.del('txn') }
|
601
|
+
context 'success' do
|
602
|
+
subject! do
|
603
|
+
trans_conn.transaction do |txn|
|
604
|
+
txn.compare = [ txn.value('txn', :equal, 'value') ]
|
605
|
+
txn.success = [ txn.put('txn-test', 'success') ]
|
606
|
+
txn.failure = [ txn.put('txn-test', 'failed') ]
|
607
|
+
end
|
608
|
+
end
|
609
|
+
it 'sets correct key' do
|
610
|
+
expect(trans_conn.get('txn-test').kvs.first.value).to eq('success')
|
611
|
+
expect(conn.get("/namespace/txn-test").kvs.first.value).to eq('success')
|
612
|
+
end
|
613
|
+
it "raises a GRPC::DeadlineExceeded exception when it takes too long" do
|
614
|
+
expect do
|
615
|
+
trans_conn.transaction(timeout: 0) do |txn|
|
616
|
+
txn.compare = [ txn.value('txn', :equal, 'value') ]
|
617
|
+
txn.success = [ txn.put('txn-test', 'success') ]
|
618
|
+
txn.failure = [ txn.put('txn-test', 'failed') ]
|
619
|
+
end
|
620
|
+
end.to raise_exception(GRPC::DeadlineExceeded)
|
621
|
+
end
|
622
|
+
it "accepts a timeout" do
|
623
|
+
expect do
|
624
|
+
trans_conn.transaction(timeout: 1) do |txn|
|
625
|
+
txn.compare = [ txn.value('txn', :equal, 'value') ]
|
626
|
+
txn.success = [ txn.put('txn-test', 'success') ]
|
627
|
+
txn.failure = [ txn.put('txn-test', 'failed') ]
|
628
|
+
end
|
629
|
+
end.to_not raise_exception
|
630
|
+
end
|
631
|
+
end
|
632
|
+
context "success, value with lease" do
|
633
|
+
let!(:lease_id) { trans_conn.lease_grant(2)['ID'] }
|
634
|
+
subject! do
|
635
|
+
trans_conn.transaction do |txn|
|
636
|
+
txn.compare = [ txn.value('txn', :equal, 'value') ]
|
637
|
+
txn.success = [ txn.put('txn-test', 'success', lease_id) ]
|
638
|
+
txn.failure = [ txn.put('txn-test', 'failed', lease_id) ]
|
639
|
+
end
|
640
|
+
end
|
641
|
+
it 'sets correct key, with a lease' do
|
642
|
+
expect(trans_conn.get('txn-test').kvs.first.value).to eq('success')
|
643
|
+
expect(trans_conn.get('txn-test').kvs.first.lease).to eq(lease_id)
|
644
|
+
end
|
645
|
+
end
|
646
|
+
context 'failure' do
|
647
|
+
subject! do
|
648
|
+
trans_conn.transaction do |txn|
|
649
|
+
txn.compare = [ txn.value('txn', :equal, 'notright') ]
|
650
|
+
txn.success = [ txn.put('txn-test', 'success') ]
|
651
|
+
txn.failure = [ txn.put('txn-test', 'failed') ]
|
652
|
+
end
|
653
|
+
end
|
654
|
+
it 'sets correct key' do
|
655
|
+
expect(trans_conn.get('txn-test').kvs.first.value).to eq('failed')
|
656
|
+
end
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
660
|
+
describe 'txn.create_revision' do
|
661
|
+
before { trans_conn.put('txn', 'value') }
|
662
|
+
after { trans_conn.del('txn') }
|
663
|
+
context 'success' do
|
664
|
+
subject! do
|
665
|
+
trans_conn.transaction do |txn|
|
666
|
+
txn.compare = [ txn.create_revision('txn', :greater, 1) ]
|
667
|
+
txn.success = [ txn.put('txn-test', 'success') ]
|
668
|
+
txn.failure = [ txn.put('txn-test', 'failed') ]
|
669
|
+
end
|
670
|
+
end
|
671
|
+
it 'sets correct key' do
|
672
|
+
expect(trans_conn.get('txn-test').kvs.first.value).to eq('success')
|
673
|
+
end
|
674
|
+
end
|
675
|
+
context 'failure' do
|
676
|
+
subject! do
|
677
|
+
trans_conn.transaction do |txn|
|
678
|
+
txn.compare = [ txn.create_revision('txn', :equal, 1) ]
|
679
|
+
txn.success = [ txn.put('txn-test', 'success') ]
|
680
|
+
txn.failure = [ txn.put('txn-test', 'failed') ]
|
681
|
+
end
|
682
|
+
end
|
683
|
+
it 'sets correct key' do
|
684
|
+
expect(trans_conn.get('txn-test').kvs.first.value).to eq('failed')
|
685
|
+
expect(conn.get('/namespace/txn-test').kvs.first.value).to eq('failed')
|
686
|
+
end
|
687
|
+
end
|
688
|
+
end
|
689
|
+
|
690
|
+
describe 'txn.mod_revision' do
|
691
|
+
before { trans_conn.put('txn', 'value') }
|
692
|
+
after { trans_conn.del('txn') }
|
693
|
+
context 'success' do
|
694
|
+
subject! do
|
695
|
+
trans_conn.transaction do |txn|
|
696
|
+
txn.compare = [ txn.mod_revision('txn', :less, 1000) ]
|
697
|
+
txn.success = [ txn.put('txn-test', 'success') ]
|
698
|
+
txn.failure = [ txn.put('txn-test', 'failed') ]
|
699
|
+
end
|
700
|
+
end
|
701
|
+
it 'sets correct key' do
|
702
|
+
expect(trans_conn.get('txn-test').kvs.first.value).to eq('success')
|
703
|
+
expect(conn.get('/namespace/txn-test').kvs.first.value).to eq('success')
|
704
|
+
end
|
705
|
+
end
|
706
|
+
context 'failure' do
|
707
|
+
subject! do
|
708
|
+
trans_conn.transaction do |txn|
|
709
|
+
txn.compare = [ txn.mod_revision('txn', :greater, 1000) ]
|
710
|
+
txn.success = [ txn.put('txn-test', 'success') ]
|
711
|
+
txn.failure = [ txn.put('txn-test', 'failed') ]
|
712
|
+
end
|
713
|
+
end
|
714
|
+
it 'sets correct key' do
|
715
|
+
expect(trans_conn.get('txn-test').kvs.first.value).to eq('failed')
|
716
|
+
expect(conn.get('/namespace/txn-test').kvs.first.value).to eq('failed')
|
717
|
+
|
718
|
+
end
|
719
|
+
end
|
720
|
+
end
|
721
|
+
|
722
|
+
describe 'txn.version' do
|
723
|
+
before { trans_conn.put('txn-version', 'value') }
|
724
|
+
after { trans_conn.del('txn-version') }
|
725
|
+
context 'success' do
|
726
|
+
subject! do
|
727
|
+
trans_conn.transaction do |txn|
|
728
|
+
txn.compare = [ txn.version('txn-version', :equal, 1) ]
|
729
|
+
txn.success = [ txn.put('txn-test', 'success') ]
|
730
|
+
txn.failure = [ txn.put('txn-test', 'failed') ]
|
731
|
+
end
|
732
|
+
end
|
733
|
+
it 'sets correct key' do
|
734
|
+
expect(trans_conn.get('txn-test').kvs.first.value).to eq('success')
|
735
|
+
expect(conn.get('/namespace/txn-test').kvs.first.value).to eq('success')
|
736
|
+
end
|
737
|
+
end
|
738
|
+
context 'failure' do
|
739
|
+
subject! do
|
740
|
+
trans_conn.transaction do |txn|
|
741
|
+
txn.compare = [ txn.version('txn', :equal, 100)]
|
742
|
+
txn.success = [ txn.put('txn-test', 'success') ]
|
743
|
+
txn.failure = [ txn.put('txn-test', 'failed') ]
|
744
|
+
end
|
745
|
+
end
|
746
|
+
it 'sets correct key' do
|
747
|
+
expect(trans_conn.get('txn-test').kvs.first.value).to eq('failed')
|
748
|
+
expect(conn.get('/namespace/txn-test').kvs.first.value).to eq('failed')
|
749
|
+
end
|
750
|
+
end
|
751
|
+
end
|
752
|
+
end
|
753
|
+
|
754
|
+
# Locking is not implemented in etcd v3.1.X
|
755
|
+
unless $instance.version < Gem::Version.new("3.2.0")
|
756
|
+
describe "locking" do
|
757
|
+
let(:ns_conn) { local_connection_with_namespace("/namespace/") }
|
758
|
+
|
759
|
+
describe '#lock' do
|
760
|
+
let(:lease_id) { lease_stub.lease_grant(10)['ID'] }
|
761
|
+
subject { ns_conn.lock('mylocklock', lease_id) }
|
762
|
+
it 'should lock key under specified namespace' do
|
763
|
+
expect(conn.get("/namespace/#{subject.key}").kvs).to_not be_empty
|
764
|
+
end
|
765
|
+
end
|
766
|
+
|
767
|
+
describe '#with_lock' do
|
768
|
+
let(:lease_id) { lease_stub.lease_grant(10)['ID'] }
|
769
|
+
let(:lease_id_2) { lease_stub.lease_grant(15)['ID'] }
|
770
|
+
it 'enforces lock' do
|
771
|
+
ns_conn.with_lock('mylock', lease_id) do
|
772
|
+
expect { ns_conn.lock('mylock', lease_id_2, timeout: 0.1) }
|
773
|
+
.to raise_error(GRPC::DeadlineExceeded)
|
774
|
+
end
|
775
|
+
end
|
776
|
+
end
|
777
|
+
end
|
778
|
+
end
|
779
|
+
end
|
510
780
|
end
|
511
781
|
end
|
data/spec/helpers/connections.rb
CHANGED
@@ -5,18 +5,26 @@ module Helpers
|
|
5
5
|
Etcdv3.new(endpoints: "http://#{local_url}", user: user, password: password)
|
6
6
|
end
|
7
7
|
|
8
|
-
def local_connection(endpoints="http://#{local_url}")
|
9
|
-
Etcdv3.new(endpoints: endpoints)
|
8
|
+
def local_connection(endpoints="http://#{local_url}", allow_reconnect: true)
|
9
|
+
Etcdv3.new(endpoints: endpoints, allow_reconnect: allow_reconnect)
|
10
10
|
end
|
11
11
|
|
12
12
|
def local_connection_with_timeout(timeout)
|
13
13
|
Etcdv3.new(endpoints: "http://#{local_url}", command_timeout: timeout)
|
14
14
|
end
|
15
15
|
|
16
|
+
def local_connection_with_namespace(namespace)
|
17
|
+
Etcdv3.new(endpoints: "http://#{local_url}", namespace: namespace)
|
18
|
+
end
|
19
|
+
|
16
20
|
def local_stub(interface, timeout=nil)
|
17
21
|
interface.new(local_url, :this_channel_is_insecure, timeout, {})
|
18
22
|
end
|
19
23
|
|
24
|
+
def local_namespace_stub(interface, timeout=nil, namespace)
|
25
|
+
interface.new(local_url, :this_channel_is_insecure, timeout, namespace, {})
|
26
|
+
end
|
27
|
+
|
20
28
|
def local_url
|
21
29
|
"127.0.0.1:#{port}"
|
22
30
|
end
|
@@ -32,13 +32,13 @@ shared_examples_for "Etcdv3 instance using a timeout" do |command, *args|
|
|
32
32
|
it "raises a GRPC::DeadlineExceeded exception when it takes too long" do
|
33
33
|
expect do
|
34
34
|
test_args = args.dup
|
35
|
-
|
36
|
-
conn.public_send(command, *test_args)
|
35
|
+
test_kwargs = {timeout: 0}
|
36
|
+
conn.public_send(command, *test_args, **test_kwargs)
|
37
37
|
end.to raise_exception(GRPC::DeadlineExceeded)
|
38
38
|
end
|
39
39
|
it "accepts a timeout" do
|
40
40
|
test_args = args.dup
|
41
|
-
|
42
|
-
expect{ conn.public_send(command, *test_args) }.to_not raise_exception
|
41
|
+
test_kwargs = {timeout: 10}
|
42
|
+
expect{ conn.public_send(command, *test_args, **test_kwargs) }.to_not raise_exception
|
43
43
|
end
|
44
44
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
2
2
|
$LOAD_PATH.unshift File.expand_path('./helpers', __FILE__)
|
3
|
+
$LOAD_PATH.unshift File.expand_path('./namespace', __FILE__)
|
3
4
|
|
4
|
-
|
5
|
-
require '
|
6
|
-
|
7
|
-
SimpleCov.
|
5
|
+
|
6
|
+
# require 'simplecov'
|
7
|
+
# require 'codecov'
|
8
|
+
# SimpleCov.start
|
9
|
+
# SimpleCov.formatter = SimpleCov::Formatter::Codecov
|
8
10
|
|
9
11
|
require 'etcdv3'
|
10
12
|
require 'helpers/test_instance'
|