etcdv3 0.10.2 → 0.11.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +27 -8
- data/lib/etcdv3/connection.rb +19 -4
- data/lib/etcdv3/connection_wrapper.rb +4 -4
- 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 +10 -0
- data/spec/etcdv3/connection_spec.rb +3 -3
- data/spec/etcdv3/connection_wrapper_spec.rb +1 -1
- 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 +12 -5
- data/spec/etcdv3_spec.rb +256 -0
- data/spec/helpers/connections.rb +8 -0
- data/spec/spec_helper.rb +6 -4
- metadata +15 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 59585a18eada27829edbf162733146e90e4c336bcbbefa761b3063e3f9400139
|
4
|
+
data.tar.gz: edc026da35b1396e664a431a79eb2012238ea4bd49c8ef43e1f70f93366935fe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5afcdcad7f96e422bebcd6d4d4db148bbe48c8600be017650ae229d988cd2e5c6d3547f2026eb1db18a10c91643ba3e84bb0a2555b2f8add7f687dd9bfc014a
|
7
|
+
data.tar.gz: 310680f7f8f11b65dfb9ea1a116536394678faf05f29087a77b87b1665138f85c4bc39614299dff22a528a63f0a0771dbb3841e9b60a610611dacff33048fbf1
|
data/README.md
CHANGED
@@ -26,14 +26,16 @@ conn = Etcdv3.new(endpoints: 'https://hostname:port')
|
|
26
26
|
# Secure connection with Auth
|
27
27
|
conn = Etcdv3.new(endpoints: 'https://hostname:port', user: 'root', password: 'mysecretpassword')
|
28
28
|
|
29
|
+
# Scope CRUD operations to a specific keyspace.
|
30
|
+
conn = Etcdv3.new(endpoints: 'https://hostname:port', namespace: "/target_keyspace/")
|
31
|
+
|
29
32
|
# Secure connection specifying custom certificates
|
30
33
|
# Coming soon...
|
31
34
|
|
32
35
|
```
|
33
36
|
**High Availability**
|
34
37
|
|
35
|
-
In the event of a failure, the client will work to restore connectivity by cycling through the specified endpoints until a connection can be established. With that being said, it is encouraged to specify multiple endpoints when available.
|
36
|
-
behaviour.
|
38
|
+
In the event of a failure, the client will work to restore connectivity by cycling through the specified endpoints until a connection can be established. With that being said, it is encouraged to specify multiple endpoints when available.
|
37
39
|
|
38
40
|
However, sometimes this is not what you want. If you need more control over
|
39
41
|
failures, you can suppress this mechanism by using
|
@@ -43,12 +45,29 @@ conn = Etcdv3.new(endpoints: 'https://hostname:port', allow_reconnect: false)
|
|
43
45
|
```
|
44
46
|
|
45
47
|
This will still rotate the endpoints, but it will raise an exception so you can
|
46
|
-
handle the failure yourself. On next call new endpoint (since they were
|
47
|
-
rotated) is tried. One thing you need to keep in mind if
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
handle the failure yourself. On next call to the new endpoint (since they were
|
49
|
+
rotated) is tried. One thing you need to keep in mind if auth is enabled, you
|
50
|
+
need to take care of `GRPC::Unauthenticated` exception and manually re-authenticate
|
51
|
+
when token expires. To reiterate, you are responsible for handling the errors, so
|
52
|
+
some understanding of how this gem and etcd works is recommended.
|
53
|
+
|
54
|
+
## Namespace support
|
55
|
+
|
56
|
+
Namespacing is a convenience feature used to scope CRUD based operations to a specific keyspace.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
# Establish connection
|
60
|
+
conn = Etcdv3.new(endpoints: 'https://hostname:port', namespace: '/service-a/')
|
61
|
+
|
62
|
+
# Write key to /service-a/test_key
|
63
|
+
conn.put("test_key", "value").
|
64
|
+
|
65
|
+
# Get the key we just wrote.
|
66
|
+
conn.get("test_key")
|
67
|
+
```
|
68
|
+
|
69
|
+
_Note: Namespaces are stripped from responses._
|
70
|
+
|
52
71
|
|
53
72
|
## Adding, Fetching and Deleting Keys
|
54
73
|
```ruby
|
data/lib/etcdv3/connection.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
class Etcdv3
|
2
2
|
class Connection
|
3
3
|
|
4
|
+
NAMESPACE_HANDLERS = {
|
5
|
+
kv: Etcdv3::Namespace::KV,
|
6
|
+
watch: Etcdv3::Namespace::Watch,
|
7
|
+
lock: Etcdv3::Namespace::Lock,
|
8
|
+
}
|
9
|
+
|
4
10
|
HANDLERS = {
|
5
11
|
auth: Etcdv3::Auth,
|
6
12
|
kv: Etcdv3::KV,
|
@@ -10,11 +16,12 @@ class Etcdv3
|
|
10
16
|
lock: Etcdv3::Lock,
|
11
17
|
}
|
12
18
|
|
13
|
-
attr_reader :endpoint, :hostname, :handlers, :credentials
|
19
|
+
attr_reader :endpoint, :hostname, :handlers, :credentials, :namespace
|
14
20
|
|
15
|
-
def initialize(url, timeout, metadata={})
|
21
|
+
def initialize(url, timeout, namespace, metadata={})
|
16
22
|
@endpoint = URI(url)
|
17
23
|
@hostname = "#{@endpoint.hostname}:#{@endpoint.port}"
|
24
|
+
@namespace = namespace
|
18
25
|
@credentials = resolve_credentials
|
19
26
|
@timeout = timeout
|
20
27
|
@handlers = handler_map(metadata)
|
@@ -31,11 +38,19 @@ class Etcdv3
|
|
31
38
|
private
|
32
39
|
|
33
40
|
def handler_map(metadata={})
|
34
|
-
Hash[
|
41
|
+
handlers = Hash[
|
35
42
|
HANDLERS.map do |key, klass|
|
36
|
-
[key, klass.new(
|
43
|
+
[key, klass.new(@hostname, @credentials, @timeout, metadata)]
|
37
44
|
end
|
38
45
|
]
|
46
|
+
# Override any handlers that are namespace compatable.
|
47
|
+
if @namespace
|
48
|
+
NAMESPACE_HANDLERS.each do |key, klass|
|
49
|
+
handlers[key] = klass.new(@hostname, @credentials, @timeout, @namespace, metadata)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
handlers
|
39
54
|
end
|
40
55
|
|
41
56
|
def resolve_credentials
|
@@ -3,12 +3,13 @@ class Etcdv3
|
|
3
3
|
|
4
4
|
attr_accessor :connection, :endpoints, :user, :password, :token, :timeout
|
5
5
|
|
6
|
-
def initialize(timeout, *endpoints, allow_reconnect)
|
6
|
+
def initialize(timeout, *endpoints, namespace, allow_reconnect)
|
7
7
|
@user, @password, @token = nil, nil, nil
|
8
8
|
@timeout = timeout
|
9
|
-
@
|
10
|
-
@
|
9
|
+
@namespace = namespace
|
10
|
+
@endpoints = endpoints.map{|endpoint| Etcdv3::Connection.new(endpoint, @timeout, @namespace) }
|
11
11
|
@allow_reconnect = allow_reconnect
|
12
|
+
@connection = @endpoints.first
|
12
13
|
end
|
13
14
|
|
14
15
|
private def retry_or_raise(*args)
|
@@ -26,7 +27,6 @@ class Etcdv3
|
|
26
27
|
$stderr.puts("Failed to connect to endpoint '#{@connection.hostname}'")
|
27
28
|
if @endpoints.size > 1
|
28
29
|
rotate_connection_endpoint
|
29
|
-
$stderr.puts("Failover event triggered. Failing over to '#{@connection.hostname}'")
|
30
30
|
return retry_or_raise(stub, method, method_args)
|
31
31
|
else
|
32
32
|
return retry_or_raise(stub, method, method_args)
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class Etcdv3::Namespace::KV
|
2
|
+
module Requests
|
3
|
+
|
4
|
+
SORT_TARGET = {
|
5
|
+
key: 0,
|
6
|
+
version: 1,
|
7
|
+
create: 2,
|
8
|
+
mod: 3,
|
9
|
+
value: 4
|
10
|
+
}
|
11
|
+
|
12
|
+
SORT_ORDER = {
|
13
|
+
none: 0,
|
14
|
+
ascend: 1,
|
15
|
+
descend: 2
|
16
|
+
}
|
17
|
+
|
18
|
+
def get_request(key, opts)
|
19
|
+
key = prepend_prefix(@namespace, key)
|
20
|
+
# In order to enforce the scope of the specified namespace, we are going to
|
21
|
+
# intercept the zero-byte reference and re-target everything under the given namespace.
|
22
|
+
if opts[:range_end] =~ /\x00/
|
23
|
+
opts[:range_end] = (@namespace[0..-2] + (@namespace[-1].ord + 1).chr)
|
24
|
+
else
|
25
|
+
opts[:range_end] = prepend_prefix(@namespace, opts[:range_end]) if opts[:range_end]
|
26
|
+
end
|
27
|
+
opts[:sort_order] = SORT_ORDER[opts[:sort_order]] \
|
28
|
+
if opts[:sort_order]
|
29
|
+
opts[:sort_target] = SORT_TARGET[opts[:sort_target]] \
|
30
|
+
if opts[:sort_target]
|
31
|
+
opts[:key] = key
|
32
|
+
Etcdserverpb::RangeRequest.new(opts)
|
33
|
+
end
|
34
|
+
|
35
|
+
def del_request(key, range_end=nil)
|
36
|
+
key = prepend_prefix(@namespace, key)
|
37
|
+
# In order to enforce the scope of the specified namespace, we are going to
|
38
|
+
# intercept the zero-byte reference and re-target everything under the given namespace.
|
39
|
+
if range_end =~ /\x00/
|
40
|
+
range_end = (@namespace[0..-2] + (@namespace[-1].ord + 1).chr)
|
41
|
+
else
|
42
|
+
range_end = prepend_prefix(@namespace, range_end) if range_end
|
43
|
+
end
|
44
|
+
Etcdserverpb::DeleteRangeRequest.new(key: key, range_end: range_end)
|
45
|
+
end
|
46
|
+
|
47
|
+
def put_request(key, value, lease=nil)
|
48
|
+
key = prepend_prefix(@namespace, key)
|
49
|
+
kv = Etcdserverpb::PutRequest.new(key: key, value: value)
|
50
|
+
kv.lease = lease if lease
|
51
|
+
kv
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
class Etcdv3::Namespace::KV
|
2
|
+
class Transaction
|
3
|
+
include Etcdv3::Namespace::Utilities
|
4
|
+
include Etcdv3::Namespace::KV::Requests
|
5
|
+
|
6
|
+
# Available comparison identifiers.
|
7
|
+
COMPARISON_IDENTIFIERS = {
|
8
|
+
equal: 0,
|
9
|
+
greater: 1,
|
10
|
+
less: 2,
|
11
|
+
not_equal: 3
|
12
|
+
}
|
13
|
+
|
14
|
+
# Available targets to compare with.
|
15
|
+
TARGETS = {
|
16
|
+
version: 0,
|
17
|
+
create_revision: 1,
|
18
|
+
mod_revision: 2,
|
19
|
+
value: 3
|
20
|
+
}
|
21
|
+
|
22
|
+
attr_writer :compare, :success, :failure
|
23
|
+
|
24
|
+
def initialize(namespace)
|
25
|
+
@namespace = namespace
|
26
|
+
end
|
27
|
+
|
28
|
+
def compare
|
29
|
+
@compare ||= []
|
30
|
+
end
|
31
|
+
|
32
|
+
def success
|
33
|
+
@success ||= []
|
34
|
+
end
|
35
|
+
|
36
|
+
def failure
|
37
|
+
@failure ||=[]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Request Operations
|
41
|
+
|
42
|
+
# txn.put('my', 'key', lease_id: 1)
|
43
|
+
def put(key, value, lease=nil)
|
44
|
+
put_request(key, value, lease)
|
45
|
+
end
|
46
|
+
|
47
|
+
# txn.get('key')
|
48
|
+
def get(key, opts={})
|
49
|
+
get_request(key, opts)
|
50
|
+
end
|
51
|
+
|
52
|
+
# txn.del('key')
|
53
|
+
def del(key, range_end='')
|
54
|
+
del_request(key, range_end)
|
55
|
+
end
|
56
|
+
|
57
|
+
### Compare Operations
|
58
|
+
|
59
|
+
# txn.version('names', :greater, 0 )
|
60
|
+
def version(key, compare_type, value)
|
61
|
+
generate_compare(:version, key, compare_type, value)
|
62
|
+
end
|
63
|
+
|
64
|
+
# txn.value('names', :equal, 'wowza' )
|
65
|
+
def value(key, compare_type, value)
|
66
|
+
generate_compare(:value, key, compare_type, value)
|
67
|
+
end
|
68
|
+
|
69
|
+
# txn.mod_revision('names', :not_equal, 0)
|
70
|
+
def mod_revision(key, compare_type, value)
|
71
|
+
generate_compare(:mod_revision, key, compare_type, value)
|
72
|
+
end
|
73
|
+
|
74
|
+
# txn.create_revision('names', :less, 10)
|
75
|
+
def create_revision(key, compare_type, value)
|
76
|
+
generate_compare(:create_revision, key, compare_type, value)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def generate_compare(target_union, key, compare_type, value)
|
82
|
+
key = prepend_prefix(@namespace, key)
|
83
|
+
Etcdserverpb::Compare.new(
|
84
|
+
key: key,
|
85
|
+
result: COMPARISON_IDENTIFIERS[compare_type],
|
86
|
+
target: TARGETS[target_union],
|
87
|
+
target_union => value
|
88
|
+
)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class Etcdv3::Namespace
|
2
|
+
class KV
|
3
|
+
include Etcdv3::Namespace::KV::Requests
|
4
|
+
include Etcdv3::Namespace::Utilities
|
5
|
+
include GRPC::Core::TimeConsts
|
6
|
+
|
7
|
+
def initialize(hostname, credentials, timeout, namespace, metadata={})
|
8
|
+
@stub = Etcdserverpb::KV::Stub.new(hostname, credentials)
|
9
|
+
@timeout = timeout
|
10
|
+
@namespace = namespace
|
11
|
+
@metadata = metadata
|
12
|
+
end
|
13
|
+
|
14
|
+
def get(key, opts={})
|
15
|
+
timeout = opts.delete(:timeout)
|
16
|
+
resp = @stub.range(get_request(key, opts), metadata: @metadata, deadline: deadline(timeout))
|
17
|
+
strip_prefix(@namespace, resp)
|
18
|
+
end
|
19
|
+
|
20
|
+
def del(key, range_end: '', timeout: nil)
|
21
|
+
resp = @stub.delete_range(del_request(key, range_end), metadata: @metadata, deadline: deadline(timeout))
|
22
|
+
strip_prefix(@namespace, resp)
|
23
|
+
end
|
24
|
+
|
25
|
+
def put(key, value, lease: nil, timeout: nil)
|
26
|
+
resp = @stub.put(put_request(key, value, lease), metadata: @metadata, deadline: deadline(timeout))
|
27
|
+
strip_prefix(@namespace, resp)
|
28
|
+
end
|
29
|
+
|
30
|
+
def transaction(block, timeout: nil)
|
31
|
+
txn = Etcdv3::Namespace::KV::Transaction.new(@namespace)
|
32
|
+
block.call(txn)
|
33
|
+
request = Etcdserverpb::TxnRequest.new(
|
34
|
+
compare: txn.compare,
|
35
|
+
success: generate_request_ops(txn.success),
|
36
|
+
failure: generate_request_ops(txn.failure),
|
37
|
+
)
|
38
|
+
resp = @stub.txn(request, metadata: @metadata, deadline: deadline(timeout))
|
39
|
+
strip_prefix(@namespace, resp)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def deadline(timeout)
|
45
|
+
from_relative_time(timeout || @timeout)
|
46
|
+
end
|
47
|
+
|
48
|
+
def generate_request_ops(requests)
|
49
|
+
requests.map do |request|
|
50
|
+
if request.is_a?(Etcdserverpb::RangeRequest)
|
51
|
+
Etcdserverpb::RequestOp.new(request_range: request)
|
52
|
+
elsif request.is_a?(Etcdserverpb::PutRequest)
|
53
|
+
Etcdserverpb::RequestOp.new(request_put: request)
|
54
|
+
elsif request.is_a?(Etcdserverpb::DeleteRangeRequest)
|
55
|
+
Etcdserverpb::RequestOp.new(request_delete_range: request)
|
56
|
+
else
|
57
|
+
raise "Invalid command. Not sure how you got here!"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Etcdv3::Namespace
|
2
|
+
class Lock
|
3
|
+
include GRPC::Core::TimeConsts
|
4
|
+
include Etcdv3::Namespace::Utilities
|
5
|
+
|
6
|
+
def initialize(hostname, credentials, timeout, namespace, metadata = {})
|
7
|
+
@stub = V3lockpb::Lock::Stub.new(hostname, credentials)
|
8
|
+
@timeout = timeout
|
9
|
+
@namespace = namespace
|
10
|
+
@metadata = metadata
|
11
|
+
end
|
12
|
+
|
13
|
+
def lock(name, lease_id, timeout: nil)
|
14
|
+
name = prepend_prefix(@namespace, name)
|
15
|
+
request = V3lockpb::LockRequest.new(name: name, lease: lease_id)
|
16
|
+
resp = @stub.lock(request, deadline: deadline(timeout))
|
17
|
+
strip_prefix_from_lock(@namespace, resp)
|
18
|
+
end
|
19
|
+
|
20
|
+
def unlock(key, timeout: nil)
|
21
|
+
key = prepend_prefix(@namespace, key)
|
22
|
+
request = V3lockpb::UnlockRequest.new(key: key)
|
23
|
+
@stub.unlock(request, deadline: deadline(timeout))
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def deadline(timeout)
|
29
|
+
from_relative_time(timeout || @timeout)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class Etcdv3::Namespace
|
2
|
+
module Utilities
|
3
|
+
|
4
|
+
def prepend_prefix(prefix, key)
|
5
|
+
key = key.dup if key.frozen?
|
6
|
+
key.prepend(prefix)
|
7
|
+
end
|
8
|
+
|
9
|
+
def strip_prefix(prefix, resp)
|
10
|
+
[:kvs, :prev_kvs].each do |field|
|
11
|
+
if resp.respond_to?(field)
|
12
|
+
resp.send(field).each do |kv|
|
13
|
+
kv.key = delete_prefix(prefix, kv.key)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
resp
|
18
|
+
end
|
19
|
+
|
20
|
+
def strip_prefix_from_lock(prefix, resp)
|
21
|
+
if resp.key
|
22
|
+
resp.key = delete_prefix(prefix, resp.key)
|
23
|
+
end
|
24
|
+
resp
|
25
|
+
end
|
26
|
+
|
27
|
+
def strip_prefix_from_events(prefix, events)
|
28
|
+
events.each do |event|
|
29
|
+
if event.kv
|
30
|
+
event.kv.key = delete_prefix(prefix, event.kv.key)
|
31
|
+
end
|
32
|
+
if event.prev_kv
|
33
|
+
event.prev_kv.key = delete_prefix(prefix, event.prev_kv.key)
|
34
|
+
end
|
35
|
+
event
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete_prefix(prefix, str)
|
40
|
+
str.sub(/\A#{prefix}/, '')
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class Etcdv3::Namespace
|
2
|
+
class Watch
|
3
|
+
include GRPC::Core::TimeConsts
|
4
|
+
include Etcdv3::Namespace::Utilities
|
5
|
+
|
6
|
+
def initialize(hostname, credentials, timeout, namespace, metadata = {})
|
7
|
+
@stub = Etcdserverpb::Watch::Stub.new(hostname, credentials)
|
8
|
+
@timeout = timeout
|
9
|
+
@namespace = namespace
|
10
|
+
@metadata = metadata
|
11
|
+
end
|
12
|
+
|
13
|
+
def watch(key, range_end, start_revision, block, timeout: nil)
|
14
|
+
key = prepend_prefix(@namespace, key)
|
15
|
+
range_end = prepend_prefix(@namespace, range_end) if range_end
|
16
|
+
create_req = Etcdserverpb::WatchCreateRequest.new(key: key)
|
17
|
+
create_req.range_end = range_end if range_end
|
18
|
+
create_req.start_revision = start_revision if start_revision
|
19
|
+
watch_req = Etcdserverpb::WatchRequest.new(create_request: create_req)
|
20
|
+
events = nil
|
21
|
+
@stub.watch([watch_req], metadata: @metadata, deadline: deadline(timeout)).each do |resp|
|
22
|
+
next if resp.events.empty?
|
23
|
+
if block
|
24
|
+
block.call(strip_prefix_from_events(@namespace, resp.events))
|
25
|
+
else
|
26
|
+
events = strip_prefix_from_events(@namespace, resp.events)
|
27
|
+
break
|
28
|
+
end
|
29
|
+
end
|
30
|
+
events
|
31
|
+
end
|
32
|
+
|
33
|
+
def deadline(timeout)
|
34
|
+
from_relative_time(timeout || @timeout)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/etcdv3/version.rb
CHANGED
data/lib/etcdv3.rb
CHANGED
@@ -7,6 +7,14 @@ require 'etcdv3/auth'
|
|
7
7
|
require 'etcdv3/kv/requests'
|
8
8
|
require 'etcdv3/kv/transaction'
|
9
9
|
require 'etcdv3/kv'
|
10
|
+
|
11
|
+
require 'etcdv3/namespace/utilities'
|
12
|
+
require 'etcdv3/namespace/kv/requests'
|
13
|
+
require 'etcdv3/namespace/kv/transaction'
|
14
|
+
require 'etcdv3/namespace/lock'
|
15
|
+
require 'etcdv3/namespace/kv'
|
16
|
+
require 'etcdv3/namespace/watch'
|
17
|
+
|
10
18
|
require 'etcdv3/maintenance'
|
11
19
|
require 'etcdv3/lease'
|
12
20
|
require 'etcdv3/watch'
|
@@ -24,9 +32,11 @@ class Etcdv3
|
|
24
32
|
def initialize(**options)
|
25
33
|
@options = options
|
26
34
|
@timeout = options[:command_timeout] || DEFAULT_TIMEOUT
|
35
|
+
@namespace = options[:namespace]
|
27
36
|
@conn = ConnectionWrapper.new(
|
28
37
|
@timeout,
|
29
38
|
*sanitized_endpoints,
|
39
|
+
@namespace,
|
30
40
|
@options.fetch(:allow_reconnect, true),
|
31
41
|
)
|
32
42
|
warn "WARNING: `url` is deprecated. Please use `endpoints` instead." if @options.key?(:url)
|
@@ -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,7 +3,7 @@ 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, true) }
|
6
|
+
subject { Etcdv3::ConnectionWrapper.new(10, *endpoints, nil, true) }
|
7
7
|
|
8
8
|
describe '#initialize' do
|
9
9
|
it { is_expected.to have_attributes(user: nil, password: nil, token: nil) }
|
@@ -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
|
+
|
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
@@ -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
@@ -13,10 +13,18 @@ module Helpers
|
|
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
|
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'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: etcdv3
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shaun Davis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: grpc
|
@@ -97,6 +97,12 @@ files:
|
|
97
97
|
- lib/etcdv3/lease.rb
|
98
98
|
- lib/etcdv3/lock.rb
|
99
99
|
- lib/etcdv3/maintenance.rb
|
100
|
+
- lib/etcdv3/namespace/kv.rb
|
101
|
+
- lib/etcdv3/namespace/kv/requests.rb
|
102
|
+
- lib/etcdv3/namespace/kv/transaction.rb
|
103
|
+
- lib/etcdv3/namespace/lock.rb
|
104
|
+
- lib/etcdv3/namespace/utilities.rb
|
105
|
+
- lib/etcdv3/namespace/watch.rb
|
100
106
|
- lib/etcdv3/protos/annotations.proto
|
101
107
|
- lib/etcdv3/protos/auth.proto
|
102
108
|
- lib/etcdv3/protos/descriptor.proto
|
@@ -114,6 +120,9 @@ files:
|
|
114
120
|
- spec/etcdv3/lease_spec.rb
|
115
121
|
- spec/etcdv3/lock_spec.rb
|
116
122
|
- spec/etcdv3/maintenance_spec.rb
|
123
|
+
- spec/etcdv3/namespace/kv_spec.rb
|
124
|
+
- spec/etcdv3/namespace/lock_spec.rb
|
125
|
+
- spec/etcdv3/namespace/watch_spec.rb
|
117
126
|
- spec/etcdv3/watch_spec.rb
|
118
127
|
- spec/etcdv3_spec.rb
|
119
128
|
- spec/helpers/connections.rb
|
@@ -139,8 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
139
148
|
- !ruby/object:Gem::Version
|
140
149
|
version: '0'
|
141
150
|
requirements: []
|
142
|
-
|
143
|
-
rubygems_version: 2.4.5.1
|
151
|
+
rubygems_version: 3.2.20
|
144
152
|
signing_key:
|
145
153
|
specification_version: 4
|
146
154
|
summary: A Etcd client library for Version 3
|
@@ -152,6 +160,9 @@ test_files:
|
|
152
160
|
- spec/etcdv3/lease_spec.rb
|
153
161
|
- spec/etcdv3/lock_spec.rb
|
154
162
|
- spec/etcdv3/maintenance_spec.rb
|
163
|
+
- spec/etcdv3/namespace/kv_spec.rb
|
164
|
+
- spec/etcdv3/namespace/lock_spec.rb
|
165
|
+
- spec/etcdv3/namespace/watch_spec.rb
|
155
166
|
- spec/etcdv3/watch_spec.rb
|
156
167
|
- spec/etcdv3_spec.rb
|
157
168
|
- spec/helpers/connections.rb
|