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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 03fa84076fc6d31e895bc80d55abb4501398a0be305cefa1f492b5e217e52277
|
4
|
+
data.tar.gz: 020aa912711d848ceb60a842a2d04bc1d5d3aacbf2714a380782cefd37c3b9c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7541f20326b9577fa7b595e371a0e94713b0b0d83ced62eaae120b5fe77c853625020425258ed941f49b345d40025f7c9d8398ba7bbb136e666f13d1b64e181b
|
7
|
+
data.tar.gz: 3e1226dcf9a6f7a3fcfce148148fcdcabe3f07bf92f24335a7a624b8af3e3b07176162a8f52f606b80dc9df864574ce8e430b95c456a54e79444f6466d3b37b4
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -26,6 +26,9 @@ 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
|
|
@@ -34,6 +37,43 @@ conn = Etcdv3.new(endpoints: 'https://hostname:port', user: 'root', password: 'm
|
|
34
37
|
|
35
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.
|
36
39
|
|
40
|
+
However, sometimes this is not what you want. If you need more control over
|
41
|
+
failures, you can suppress this mechanism by using
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
conn = Etcdv3.new(endpoints: 'https://hostname:port', allow_reconnect: false)
|
45
|
+
```
|
46
|
+
|
47
|
+
This will still rotate the endpoints, but it will raise an exception so you can
|
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
|
+
# Retrieve everything under the namespace.
|
69
|
+
conn.get("", "\0")
|
70
|
+
|
71
|
+
# Delete everything under the namespace.
|
72
|
+
conn.del("", "\0")
|
73
|
+
```
|
74
|
+
|
75
|
+
_Note: Namespaces are stripped from responses._
|
76
|
+
|
37
77
|
|
38
78
|
## Adding, Fetching and Deleting Keys
|
39
79
|
```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,18 +16,25 @@ 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)
|
21
28
|
end
|
22
29
|
|
23
30
|
def call(stub, method, method_args=[])
|
24
|
-
|
31
|
+
*method_args, method_kwargs = method_args if method_args.last.class == Hash
|
32
|
+
|
33
|
+
if method_kwargs.nil?
|
34
|
+
@handlers.fetch(stub).send(method, *method_args)
|
35
|
+
else
|
36
|
+
@handlers.fetch(stub).send(method, *method_args, **method_kwargs)
|
37
|
+
end
|
25
38
|
end
|
26
39
|
|
27
40
|
def refresh_metadata(metadata)
|
@@ -31,11 +44,19 @@ class Etcdv3
|
|
31
44
|
private
|
32
45
|
|
33
46
|
def handler_map(metadata={})
|
34
|
-
Hash[
|
47
|
+
handlers = Hash[
|
35
48
|
HANDLERS.map do |key, klass|
|
36
|
-
[key, klass.new(
|
49
|
+
[key, klass.new(@hostname, @credentials, @timeout, metadata)]
|
37
50
|
end
|
38
51
|
]
|
52
|
+
# Override any handlers that are namespace compatable.
|
53
|
+
if @namespace
|
54
|
+
NAMESPACE_HANDLERS.each do |key, klass|
|
55
|
+
handlers[key] = klass.new(@hostname, @credentials, @timeout, @namespace, metadata)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
handlers
|
39
60
|
end
|
40
61
|
|
41
62
|
def resolve_credentials
|
@@ -3,31 +3,46 @@ class Etcdv3
|
|
3
3
|
|
4
4
|
attr_accessor :connection, :endpoints, :user, :password, :token, :timeout
|
5
5
|
|
6
|
-
def initialize(timeout, *endpoints)
|
6
|
+
def initialize(timeout, *endpoints, namespace, allow_reconnect)
|
7
7
|
@user, @password, @token = nil, nil, nil
|
8
8
|
@timeout = timeout
|
9
|
-
@
|
9
|
+
@namespace = namespace
|
10
|
+
@endpoints = endpoints.map{|endpoint| Etcdv3::Connection.new(endpoint, @timeout, @namespace) }
|
11
|
+
@allow_reconnect = allow_reconnect
|
10
12
|
@connection = @endpoints.first
|
11
13
|
end
|
12
14
|
|
15
|
+
private def retry_or_raise(*args)
|
16
|
+
if @allow_reconnect
|
17
|
+
*args, kwargs = args if args.last.class == Hash
|
18
|
+
|
19
|
+
if kwargs.nil?
|
20
|
+
handle(*args)
|
21
|
+
else
|
22
|
+
handle(*args, **kwargs)
|
23
|
+
end
|
24
|
+
else
|
25
|
+
raise
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
13
29
|
def handle(stub, method, method_args=[], retries: 1)
|
14
30
|
@connection.call(stub, method, method_args)
|
15
31
|
|
16
|
-
rescue GRPC::Unavailable, GRPC::Core::CallError
|
32
|
+
rescue GRPC::Unavailable, GRPC::Core::CallError
|
17
33
|
$stderr.puts("Failed to connect to endpoint '#{@connection.hostname}'")
|
18
34
|
if @endpoints.size > 1
|
19
35
|
rotate_connection_endpoint
|
20
|
-
|
21
|
-
return handle(stub, method, method_args)
|
36
|
+
return retry_or_raise(stub, method, method_args)
|
22
37
|
else
|
23
|
-
return
|
38
|
+
return retry_or_raise(stub, method, method_args)
|
24
39
|
end
|
25
40
|
rescue GRPC::Unauthenticated => exception
|
26
41
|
# Regenerate token in the event it expires.
|
27
42
|
if exception.details == 'etcdserver: invalid auth token'
|
28
43
|
if retries > 0
|
29
44
|
authenticate(@user, @password)
|
30
|
-
return
|
45
|
+
return retry_or_raise(stub, method, method_args, retries: retries - 1)
|
31
46
|
end
|
32
47
|
end
|
33
48
|
raise exception
|
data/lib/etcdv3/lock.rb
CHANGED
@@ -10,12 +10,12 @@ class Etcdv3
|
|
10
10
|
|
11
11
|
def lock(name, lease_id, timeout: nil)
|
12
12
|
request = V3lockpb::LockRequest.new(name: name, lease: lease_id)
|
13
|
-
@stub.lock(request, deadline: deadline(timeout))
|
13
|
+
@stub.lock(request, metadata: @metadata, deadline: deadline(timeout))
|
14
14
|
end
|
15
15
|
|
16
16
|
def unlock(key, timeout: nil)
|
17
17
|
request = V3lockpb::UnlockRequest.new(key: key)
|
18
|
-
@stub.unlock(request, deadline: deadline(timeout))
|
18
|
+
@stub.unlock(request, metadata: @metadata, deadline: deadline(timeout))
|
19
19
|
end
|
20
20
|
|
21
21
|
private
|
@@ -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, metadata: @metadata, 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, metadata: @metadata, 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'
|
@@ -21,10 +29,16 @@ class Etcdv3
|
|
21
29
|
attr_reader :conn, :credentials, :options
|
22
30
|
DEFAULT_TIMEOUT = 120
|
23
31
|
|
24
|
-
def initialize(options
|
32
|
+
def initialize(**options)
|
25
33
|
@options = options
|
26
34
|
@timeout = options[:command_timeout] || DEFAULT_TIMEOUT
|
27
|
-
@
|
35
|
+
@namespace = options[:namespace]
|
36
|
+
@conn = ConnectionWrapper.new(
|
37
|
+
@timeout,
|
38
|
+
*sanitized_endpoints,
|
39
|
+
@namespace,
|
40
|
+
@options.fetch(:allow_reconnect, true),
|
41
|
+
)
|
28
42
|
warn "WARNING: `url` is deprecated. Please use `endpoints` instead." if @options.key?(:url)
|
29
43
|
authenticate(@options[:user], @options[:password]) if @options.key?(:user)
|
30
44
|
end
|
@@ -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) }
|