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.
@@ -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(:modified_conn) { local_connection("http://localhost:2369, http://localhost:2379") }
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
- subject { modified_conn.get('boom') }
36
- it { is_expected.to be_an_instance_of(Etcdserverpb::RangeResponse) }
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
- subject { modified_conn.get('boom') }
54
- it { is_expected.to be_an_instance_of(Etcdserverpb::RangeResponse) }
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
- it { is_expected.to be_an_instance_of(Etcdserverpb::AuthUserGetResponse) }
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
@@ -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
- test_args.push({timeout: 0})
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
- test_args.push({timeout: 10})
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
- require 'simplecov'
5
- require 'codecov'
6
- SimpleCov.start
7
- SimpleCov.formatter = SimpleCov::Formatter::Codecov
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'