etcdv3 0.9.0 → 0.11.4
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 +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'
|