etcdv3 0.9.0 → 0.11.4
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 +4 -5
- data/README.md +45 -2
- data/Rakefile +3 -2
- data/lib/etcdv3/connection.rb +26 -5
- data/lib/etcdv3/connection_wrapper.rb +22 -7
- 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/watch.rb +12 -4
- data/lib/etcdv3.rb +18 -4
- data/spec/etcdv3/connection_spec.rb +3 -3
- data/spec/etcdv3/connection_wrapper_spec.rb +39 -8
- 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 +24 -0
- data/spec/etcdv3_spec.rb +271 -1
- data/spec/helpers/connections.rb +10 -2
- data/spec/helpers/shared_examples_for_timeout.rb +4 -4
- data/spec/spec_helper.rb +6 -4
- 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: 18d76403d57c3e56b75e603931345900a4583b2e94d773b33ca04aa1547e304d
|
4
|
+
data.tar.gz: f1d1f9336f62f18b32fb9d3f055e0043bddb6076022b922b097c0104a7467c8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9633a47e982b7777ccda7e5472a663a661e4cf6368689cfbbd8d06a6ca862be47e7c3a75d075919b645611879513d4c88c7f59686b2a0eac15cc160e3ea7520f
|
7
|
+
data.tar.gz: 5a330de810e02ebdec73c279e33b7c2ece0c2aa7c4c877da89a2b285976ce68578ca20b3c51206d9a14c9fccbf18ef6310254010053eaefa59b06b898e1c195a
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
+
- 3.0.2
|
3
4
|
- 2.5.3
|
4
5
|
- 2.4.5
|
5
6
|
- 2.3.8
|
6
7
|
|
7
8
|
env:
|
8
|
-
-
|
9
|
-
-
|
10
|
-
|
11
|
-
# locally for me)
|
12
|
-
# - ETCD_VERSION=v3.3.10
|
9
|
+
- ETCD_VERSION_TO_TEST=v3.1.20
|
10
|
+
- ETCD_VERSION_TO_TEST=v3.2.25
|
11
|
+
- ETCD_VERSION_TO_TEST=v3.3.10
|
13
12
|
|
14
13
|
install:
|
15
14
|
- bundle install
|
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
|
@@ -149,12 +189,15 @@ end
|
|
149
189
|
|
150
190
|
## Watch
|
151
191
|
```ruby
|
152
|
-
# Watch for changes on a specified key and return
|
153
|
-
events = conn.watch('foo')
|
192
|
+
# Watch for changes on a specified key for at most 10 seconds and return
|
193
|
+
events = conn.watch('foo', timeout: 10)
|
154
194
|
|
155
195
|
# Watch for changes on a specified key range and return
|
156
196
|
events = conn.watch('foo', range_end: 'fop')
|
157
197
|
|
198
|
+
# Watch for changes since a given revision
|
199
|
+
events = conn.watch('foo', start_revision: 42)
|
200
|
+
|
158
201
|
# Watches for changes continuously until killed.
|
159
202
|
event_count = 0
|
160
203
|
conn.watch('boom') do |events|
|
data/Rakefile
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
ETCD_VERSION = ENV["
|
4
|
-
ETCD_URL = "https://github.com/coreos/etcd/releases/download
|
3
|
+
ETCD_VERSION = ENV["ETCD_VERSION_TO_TEST"] || "v3.2.0"
|
4
|
+
ETCD_URL = "https://github.com/coreos/etcd/releases/download/" \
|
5
|
+
"#{ETCD_VERSION}/etcd-#{ETCD_VERSION}-linux-amd64.tar.gz"
|
5
6
|
|
6
7
|
require "tmpdir"
|
7
8
|
|
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
|
@@ -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/watch.rb
CHANGED
@@ -1,16 +1,20 @@
|
|
1
1
|
class Etcdv3
|
2
2
|
class Watch
|
3
|
+
include GRPC::Core::TimeConsts
|
3
4
|
|
4
|
-
def initialize(hostname, credentials,
|
5
|
+
def initialize(hostname, credentials, timeout, metadata = {})
|
5
6
|
@stub = Etcdserverpb::Watch::Stub.new(hostname, credentials)
|
7
|
+
@timeout = timeout
|
6
8
|
@metadata = metadata
|
7
9
|
end
|
8
10
|
|
9
|
-
def watch(key, range_end, block)
|
10
|
-
create_req = Etcdserverpb::WatchCreateRequest.new(key: key
|
11
|
+
def watch(key, range_end, start_revision, block, timeout: nil)
|
12
|
+
create_req = Etcdserverpb::WatchCreateRequest.new(key: key)
|
13
|
+
create_req.range_end = range_end if range_end
|
14
|
+
create_req.start_revision = start_revision if start_revision
|
11
15
|
watch_req = Etcdserverpb::WatchRequest.new(create_request: create_req)
|
12
16
|
events = nil
|
13
|
-
@stub.watch([watch_req], metadata: @metadata).each do |resp|
|
17
|
+
@stub.watch([watch_req], metadata: @metadata, deadline: deadline(timeout)).each do |resp|
|
14
18
|
next if resp.events.empty?
|
15
19
|
if block
|
16
20
|
block.call(resp.events)
|
@@ -21,5 +25,9 @@ class Etcdv3
|
|
21
25
|
end
|
22
26
|
events
|
23
27
|
end
|
28
|
+
|
29
|
+
def deadline(timeout)
|
30
|
+
from_relative_time(timeout || @timeout)
|
31
|
+
end
|
24
32
|
end
|
25
33
|
end
|
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
|
@@ -219,8 +233,8 @@ class Etcdv3
|
|
219
233
|
end
|
220
234
|
|
221
235
|
# Watches for changes on a specified key range.
|
222
|
-
def watch(key, range_end:
|
223
|
-
@conn.handle(:watch, 'watch', [key, range_end, block])
|
236
|
+
def watch(key, range_end: nil, start_revision: nil, timeout: nil, &block)
|
237
|
+
@conn.handle(:watch, 'watch', [key, range_end, start_revision, block, timeout: timeout])
|
224
238
|
end
|
225
239
|
|
226
240
|
def transaction(timeout: nil, &block)
|