etcdv3 0.10.1 → 0.11.5
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 +1 -0
- data/README.md +40 -0
- data/lib/etcdv3/connection.rb +26 -5
- data/lib/etcdv3/connection_wrapper.rb +22 -7
- data/lib/etcdv3/lock.rb +2 -2
- 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.rb +16 -2
- data/spec/etcdv3/connection_spec.rb +3 -3
- data/spec/etcdv3/connection_wrapper_spec.rb +39 -8
- data/spec/etcdv3/lock_spec.rb +19 -5
- data/spec/etcdv3/namespace/kv_spec.rb +82 -0
- data/spec/etcdv3/namespace/lock_spec.rb +42 -0
- data/spec/etcdv3/namespace/watch_spec.rb +36 -0
- data/spec/etcdv3/watch_spec.rb +12 -5
- data/spec/etcdv3_spec.rb +257 -1
- data/spec/helpers/connections.rb +18 -2
- data/spec/helpers/metadata_passthrough.rb +21 -0
- data/spec/helpers/shared_examples_for_timeout.rb +4 -4
- data/spec/helpers/test_instance.rb +2 -0
- data/spec/spec_helper.rb +4 -0
- metadata +17 -4
@@ -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
|
data/spec/etcdv3/lock_spec.rb
CHANGED
@@ -10,14 +10,28 @@ unless $instance.version < Gem::Version.new("3.2.0")
|
|
10
10
|
#it_should_behave_like "a method with a GRPC timeout", described_class, :lock, :lock, 'foo'
|
11
11
|
|
12
12
|
describe '#lock' do
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
it 'returns a response' do
|
14
|
+
lease_id = lease_stub.lease_grant(10)['ID']
|
15
|
+
|
16
|
+
expect(stub.lock('example1', lease_id)).to be_an_instance_of(V3lockpb::LockResponse)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'passes metadata correctly' do
|
20
|
+
lease_id = lease_stub.lease_grant(10)['ID']
|
21
|
+
stub = expect_metadata_passthrough(described_class, :lock, :lock)
|
22
|
+
stub.lock('example2', lease_id)
|
23
|
+
end
|
16
24
|
end
|
17
25
|
|
18
26
|
describe '#unlock' do
|
19
|
-
|
20
|
-
|
27
|
+
it 'returns a response' do
|
28
|
+
expect(stub.unlock('example3')).to be_an_instance_of(V3lockpb::UnlockResponse)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'passes metadata correctly' do
|
32
|
+
stub = expect_metadata_passthrough(described_class, :unlock, :unlock)
|
33
|
+
stub.unlock('example4')
|
34
|
+
end
|
21
35
|
end
|
22
36
|
end
|
23
37
|
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,42 @@
|
|
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::Namespace::Lock do
|
6
|
+
let(:stub) { local_namespace_stub(Etcdv3::Namespace::Lock, 1, '/namespace/') }
|
7
|
+
let(:lease_stub) { local_stub(Etcdv3::Lease, 1) }
|
8
|
+
|
9
|
+
# NOTE: this was running duplicate tests against Etcdv3::Lock before, but it
|
10
|
+
# doesn't work with Etcdv3::Namespace::Lock
|
11
|
+
#
|
12
|
+
# it_should_behave_like "a method with a GRPC timeout", described_class, :unlock, :unlock, 'foo'
|
13
|
+
|
14
|
+
# it_should_behave_like "a method with a GRPC timeout", described_class, :lock, :lock, 'foo'
|
15
|
+
|
16
|
+
describe '#lock' do
|
17
|
+
it 'returns a response' do
|
18
|
+
lease_id = lease_stub.lease_grant(10)['ID']
|
19
|
+
expect(stub.lock('example1', lease_id)).to(
|
20
|
+
be_an_instance_of(V3lockpb::LockResponse)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'passes metadata correctly' do
|
25
|
+
lease_id = lease_stub.lease_grant(10)['ID']
|
26
|
+
stub = expect_metadata_passthrough_namespace(described_class, :lock, :lock, '/namespace/')
|
27
|
+
stub.lock('example2', lease_id)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#unlock' do
|
32
|
+
it 'returns a response' do
|
33
|
+
expect(stub.unlock('example3')).to be_an_instance_of(V3lockpb::UnlockResponse)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'passes metadata correctly' do
|
37
|
+
stub = expect_metadata_passthrough_namespace(described_class, :unlock, :unlock, '/namespace/')
|
38
|
+
stub.unlock('example4')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
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
|
+
|
data/spec/etcdv3/watch_spec.rb
CHANGED
@@ -4,14 +4,21 @@ require 'securerandom'
|
|
4
4
|
# Locking is not implemented in etcd v3.1.X
|
5
5
|
unless $instance.version < Gem::Version.new("3.2.0")
|
6
6
|
describe Etcdv3::Watch do
|
7
|
-
let(:stub) { local_stub(Etcdv3::Watch,
|
7
|
+
let(:stub) { local_stub(Etcdv3::Watch, 5) }
|
8
8
|
let(:kv_stub) { local_stub(Etcdv3::KV, 1) }
|
9
9
|
|
10
|
-
context '
|
11
|
-
|
12
|
-
|
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')
|
13
21
|
end
|
14
|
-
it_should_behave_like "a method with a GRPC timeout", described_class, :watch, :watch, 'foo', "\0", 1, nil
|
15
22
|
end
|
16
23
|
end
|
17
24
|
end
|
data/spec/etcdv3_spec.rb
CHANGED
@@ -293,7 +293,7 @@ describe Etcdv3 do
|
|
293
293
|
describe '#role_grant_permission' do
|
294
294
|
before { conn.role_add('grant') }
|
295
295
|
after { conn.role_delete('grant') }
|
296
|
-
subject { conn.role_grant_permission('grant', :readwrite, 'a', {range_end: 'Z'}) }
|
296
|
+
subject { conn.role_grant_permission('grant', :readwrite, 'a', **{range_end: 'Z'}) }
|
297
297
|
it { is_expected.to_not be_nil }
|
298
298
|
it_should_behave_like "Etcdv3 instance using a timeout", :role_grant_permission, 'grant', :readwrite, 'a'
|
299
299
|
end
|
@@ -521,5 +521,261 @@ describe Etcdv3 do
|
|
521
521
|
end
|
522
522
|
end
|
523
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
|
524
780
|
end
|
525
781
|
end
|
data/spec/helpers/connections.rb
CHANGED
@@ -5,18 +5,34 @@ 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_stub_with_metadata(interface, timeout: nil, metadata: {})
|
25
|
+
interface.new(local_url, :this_channel_is_insecure, timeout, metadata)
|
26
|
+
end
|
27
|
+
|
28
|
+
def local_namespace_stub(interface, timeout=nil, namespace)
|
29
|
+
interface.new(local_url, :this_channel_is_insecure, timeout, namespace, {})
|
30
|
+
end
|
31
|
+
|
32
|
+
def local_namespace_stub_with_metadata(interface, timeout: nil, namespace:, metadata: {})
|
33
|
+
interface.new(local_url, :this_channel_is_insecure, timeout, namespace, metadata)
|
34
|
+
end
|
35
|
+
|
20
36
|
def local_url
|
21
37
|
"127.0.0.1:#{port}"
|
22
38
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Helpers
|
2
|
+
module MetadataPassthrough
|
3
|
+
include Connections
|
4
|
+
|
5
|
+
def expect_metadata_passthrough(stub_class, method_name, expectation_target)
|
6
|
+
metadata = { user: "foo", password: "bar" }
|
7
|
+
handler = local_stub_with_metadata(stub_class, metadata: metadata, timeout: 1)
|
8
|
+
inner_stub = handler.instance_variable_get("@stub")
|
9
|
+
expect(inner_stub).to receive(expectation_target).with(anything, hash_including(metadata: metadata)).and_call_original
|
10
|
+
return handler
|
11
|
+
end
|
12
|
+
|
13
|
+
def expect_metadata_passthrough_namespace(stub_class, method_name, expectation_target, namespace)
|
14
|
+
metadata = { user: "foo", password: "bar" }
|
15
|
+
handler = local_namespace_stub_with_metadata(stub_class, metadata: metadata, timeout: 1, namespace: namespace)
|
16
|
+
inner_stub = handler.instance_variable_get("@stub")
|
17
|
+
expect(inner_stub).to receive(expectation_target).with(anything, hash_including(metadata: metadata)).and_call_original
|
18
|
+
return handler
|
19
|
+
end
|
20
|
+
end
|
21
|
+
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
|
@@ -3,10 +3,12 @@ require 'tmpdir'
|
|
3
3
|
require 'socket'
|
4
4
|
require 'timeout'
|
5
5
|
require 'helpers/connections'
|
6
|
+
require 'helpers/metadata_passthrough'
|
6
7
|
|
7
8
|
module Helpers
|
8
9
|
class TestInstance
|
9
10
|
include Helpers::Connections
|
11
|
+
include Helpers::MetadataPassthrough
|
10
12
|
|
11
13
|
class InvalidVersionException < StandardError; end
|
12
14
|
class PortInUseException < StandardError; end
|