etcdv3 0.10.1 → 0.11.5

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,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
@@ -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
- 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) }
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
- subject { stub.unlock('foo') }
20
- it { is_expected.to be_an_instance_of(V3lockpb::UnlockResponse) }
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
+
@@ -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, 1) }
7
+ let(:stub) { local_stub(Etcdv3::Watch, 5) }
8
8
  let(:kv_stub) { local_stub(Etcdv3::KV, 1) }
9
9
 
10
- context 'xxx' do
11
- before(:each) do
12
- kv_stub.put 'foo', 'bar'
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
@@ -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
- 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
@@ -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