etcd 0.2.0.alpha → 0.2.0.beta.1
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.
- checksums.yaml +8 -8
- data/.rspec +2 -1
- data/.rubocop.yml +12 -0
- data/.travis.yml +1 -1
- data/Gemfile +5 -0
- data/Guardfile +9 -0
- data/README.md +6 -2
- data/Rakefile +13 -0
- data/lib/etcd.rb +2 -2
- data/lib/etcd/client.rb +20 -8
- data/lib/etcd/exceptions.rb +2 -0
- data/lib/etcd/keys.rb +36 -58
- data/lib/etcd/mod/leader.rb +2 -2
- data/lib/etcd/mod/lock.rb +20 -18
- data/lib/etcd/node.rb +7 -6
- data/lib/etcd/response.rb +5 -5
- data/lib/etcd/stats.rb +5 -5
- data/lib/etcd/version.rb +2 -2
- data/spec/etcd/basic_auth_client_spec.rb +27 -0
- data/spec/etcd/client_spec.rb +11 -3
- data/spec/etcd/keys_spec.rb +44 -2
- data/spec/etcd/lock_spec.rb +3 -3
- data/spec/etcd/node_spec.rb +4 -4
- data/spec/etcd/read_only_client_spec.rb +10 -11
- data/spec/etcd/readme_spec.rb +31 -32
- data/spec/etcd/test_and_set_spec.rb +23 -22
- data/spec/etcd/watch_spec.rb +6 -6
- data/spec/spec_helper.rb +15 -17
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YjhkNjNmODJjMmU3YTg3NDliYTU4ODk3MjY0NDYyYjNjODg1Y2QwZg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
NzEyZjMxMTcwYmFiOWY3ZWU0OTg0YzdiMDFlNTNlNGJiM2Q2NjAwNw==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
YTM3YWI4NDQ1YWJhY2E1NjgwYmJlMjkyNGZiYmQ4YWNmYjQ3NDlmNDlmNGIy
|
10
|
+
ZjUwYTA0MWNjMGVkZWE1Y2Y3MTliNWNjYzMzODc5YzQwNjA2YzVhNzVhMDRh
|
11
|
+
MTk4MDYxYWI0NjAwYzEyYTVhYzc3YzI3ZDM2YmI3OWVkMWJlZjU=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YWUwNGJkYWIwOWZkOGVkMDRlNDg5NmQ1MzY5NWI1OGI1MGI3MjNjODc3ODNh
|
14
|
+
NzBhNjc2YzI2ZGI1ZDAzMGU1MDYzMmNjNDAxZTFkYjkzZTZlYmNiYjJlNDkz
|
15
|
+
N2ZkNDk5NTcyMGIyZDM0OTlkNDMxZDJkOGFiNzc1OGI5ZThlNmI=
|
data/.rspec
CHANGED
data/.rubocop.yml
ADDED
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/Guardfile
ADDED
data/README.md
CHANGED
@@ -22,6 +22,7 @@ Or install it yourself as:
|
|
22
22
|
client = Etcd.client # this will create a client against etcd server running on localhost on port 4001
|
23
23
|
client = Etcd.client(port: 4002)
|
24
24
|
client = Etcd.client(host: '127.0.0.1', port: 4003)
|
25
|
+
client = Etcd.client(:user_name => 'test', :password => 'pwd') # populates the authentication header for basic HTTP auth with user name and password (useful for proxied connections)
|
25
26
|
client = Etcd.client(host: '127.0.0.1', port: 4003, allow_redirect: false) # wont let you run sensitive commands on non-leader machines, default is true
|
26
27
|
```
|
27
28
|
### Set a key
|
@@ -66,11 +67,14 @@ client.machines
|
|
66
67
|
```ruby
|
67
68
|
client.leader
|
68
69
|
```
|
70
|
+
More examples and api details can be found in the [wiki](https://github.com/ranjib/etcd-ruby/wiki)
|
69
71
|
|
70
72
|
## Contributors
|
71
73
|
* Ranjib Dey
|
72
|
-
* Jesse Nelson
|
73
|
-
|
74
|
+
* [Jesse Nelson](https://github.com/spheromak)
|
75
|
+
* [Nilesh Bairagi](https://github.com/Bairagi)
|
76
|
+
* [Dr Nic Williams](https://github.com/drnic)
|
77
|
+
* [Eric Buth] (https://github.com/buth)
|
74
78
|
|
75
79
|
|
76
80
|
## Contributing
|
data/Rakefile
CHANGED
@@ -1,10 +1,23 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rspec/core/rake_task"
|
3
3
|
require "rdoc/task"
|
4
|
+
require "rubocop/rake_task"
|
4
5
|
|
5
6
|
RSpec::Core::RakeTask.new("spec")
|
6
7
|
|
8
|
+
Rubocop::RakeTask.new do |task|
|
9
|
+
task.fail_on_error = true
|
10
|
+
end
|
11
|
+
|
7
12
|
RDoc::Task.new do |rdoc|
|
8
13
|
rdoc.main = "README.rdoc"
|
9
14
|
rdoc.rdoc_files.include("lib /*.rb")
|
10
15
|
end
|
16
|
+
|
17
|
+
namespace :test do
|
18
|
+
desc 'Run all of the quick tests.'
|
19
|
+
task :quick do
|
20
|
+
Rake::Task['rubocop'].invoke
|
21
|
+
Rake::Task['spec'].invoke
|
22
|
+
end
|
23
|
+
end
|
data/lib/etcd.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'etcd/client'
|
4
4
|
##
|
5
|
-
# This module provides the Etcd:: name space for the gem and few
|
5
|
+
# This module provides the Etcd:: name space for the gem and few
|
6
6
|
# factory methods for Etcd domain objects
|
7
7
|
module Etcd
|
8
8
|
##
|
@@ -10,7 +10,7 @@ module Etcd
|
|
10
10
|
# as an argument which gets passed to the Etcd::Client.new method
|
11
11
|
# directly
|
12
12
|
# If +opts+ is not passed default options are used, defined by Etcd::Client.new
|
13
|
-
def self.client(opts={})
|
13
|
+
def self.client(opts = {})
|
14
14
|
Etcd::Client.new(opts)
|
15
15
|
end
|
16
16
|
end
|
data/lib/etcd/client.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# Encoding: utf-8
|
2
2
|
|
3
|
+
require 'openssl'
|
3
4
|
require 'net/http'
|
4
5
|
require 'json'
|
5
6
|
require 'etcd/log'
|
@@ -16,10 +17,9 @@ module Etcd
|
|
16
17
|
# etcd api, like Etcd::Client#lock and Etcd::Client#eternal_watch, they
|
17
18
|
# are defined in separate modules and included in this class
|
18
19
|
class Client
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
HTTP_CLIENT_ERROR = ->(r){ r.is_a? Net::HTTPClientError }
|
20
|
+
HTTP_REDIRECT = ->(r) { r.is_a? Net::HTTPRedirection }
|
21
|
+
HTTP_SUCCESS = ->(r) { r.is_a? Net::HTTPSuccess }
|
22
|
+
HTTP_CLIENT_ERROR = ->(r) { r.is_a? Net::HTTPClientError }
|
23
23
|
|
24
24
|
include Stats
|
25
25
|
include Keys
|
@@ -28,6 +28,7 @@ module Etcd
|
|
28
28
|
|
29
29
|
attr_reader :host, :port, :http, :allow_redirect
|
30
30
|
attr_reader :use_ssl, :verify_mode, :read_timeout
|
31
|
+
attr_reader :user_name, :password
|
31
32
|
|
32
33
|
##
|
33
34
|
# Creates an Etcd::Client object. It accepts a hash +opts+ as argument
|
@@ -36,6 +37,7 @@ module Etcd
|
|
36
37
|
# @opts [String] :host IP address of the etcd server (default 127.0.0.1)
|
37
38
|
# @opts [Fixnum] :port Port number of the etcd server (default 4001)
|
38
39
|
# @opts [Fixnum] :read_timeout set HTTP read timeouts (default 60)
|
40
|
+
# rubocop:disable CyclomaticComplexity
|
39
41
|
def initialize(opts = {})
|
40
42
|
@host = opts[:host] || '127.0.0.1'
|
41
43
|
@port = opts[:port] || 4001
|
@@ -43,7 +45,10 @@ module Etcd
|
|
43
45
|
@allow_redirect = opts.key?(:allow_redirect) ? opts[:allow_redirect] : true
|
44
46
|
@use_ssl = opts[:use_ssl] || false
|
45
47
|
@verify_mode = opts.key?(:verify_mode) ? opts[:verify_mode] : OpenSSL::SSL::VERIFY_PEER
|
48
|
+
@user_name = opts[:user_name] || nil
|
49
|
+
@password = opts[:password] || nil
|
46
50
|
end
|
51
|
+
# rubocop:enable CyclomaticComplexity
|
47
52
|
|
48
53
|
# Returns the etcd api version that will be used for across API methods
|
49
54
|
def version_prefix
|
@@ -71,6 +76,7 @@ module Etcd
|
|
71
76
|
# * path - etcd server path (etcd server end point)
|
72
77
|
# * method - the request method used
|
73
78
|
# * options - any additional parameters used by request method (optional)
|
79
|
+
# rubocop:disable MethodLength, CyclomaticComplexity
|
74
80
|
def api_execute(path, method, options = {})
|
75
81
|
params = options[:params]
|
76
82
|
case method
|
@@ -90,21 +96,26 @@ module Etcd
|
|
90
96
|
http.read_timeout = timeout
|
91
97
|
http.use_ssl = use_ssl
|
92
98
|
http.verify_mode = verify_mode
|
99
|
+
req.basic_auth(user_name, password) if [user_name, password].all?
|
93
100
|
Log.debug("Invoking: '#{req.class}' against '#{path}")
|
94
101
|
res = http.request(req)
|
95
102
|
Log.debug("Response code: #{res.code}")
|
96
|
-
process_http_request(res)
|
103
|
+
process_http_request(res, req, params)
|
97
104
|
end
|
98
105
|
|
99
|
-
|
106
|
+
# need to ahve original request to process the response when it redirects
|
107
|
+
def process_http_request(res, req = nil, params = nil)
|
100
108
|
case res
|
101
109
|
when HTTP_SUCCESS
|
102
110
|
Log.debug('Http success')
|
103
111
|
res
|
104
112
|
when HTTP_REDIRECT
|
105
113
|
if allow_redirect
|
106
|
-
|
107
|
-
|
114
|
+
uri = URI(res['location'])
|
115
|
+
@host = uri.host
|
116
|
+
@port = uri.port
|
117
|
+
Log.debug("Http redirect, setting new host to: #{@host}:#{@port}, and retrying")
|
118
|
+
api_execute(uri.path, req.method.downcase.to_sym, params: params)
|
108
119
|
else
|
109
120
|
Log.debug('Http redirect not allowed')
|
110
121
|
res.error!
|
@@ -117,6 +128,7 @@ module Etcd
|
|
117
128
|
res.error!
|
118
129
|
end
|
119
130
|
end
|
131
|
+
# rubocop:enable MethodLength
|
120
132
|
|
121
133
|
def build_http_request(klass, path, params = nil, body = nil)
|
122
134
|
path += '?' + URI.encode_www_form(params) unless params.nil?
|
data/lib/etcd/exceptions.rb
CHANGED
@@ -36,6 +36,7 @@ module Etcd
|
|
36
36
|
class NotDir < Error; end
|
37
37
|
class NodeExist < Error; end
|
38
38
|
class KeyIsPreserved < Error; end
|
39
|
+
class DirNotEmpty < Error; end
|
39
40
|
|
40
41
|
# Post form related error
|
41
42
|
class ValueRequired < Error; end
|
@@ -60,6 +61,7 @@ module Etcd
|
|
60
61
|
104 => NotDir,
|
61
62
|
105 => NodeExist,
|
62
63
|
106 => KeyIsPreserved,
|
64
|
+
108 => DirNotEmpty,
|
63
65
|
|
64
66
|
# Post form related error
|
65
67
|
200 => ValueRequired,
|
data/lib/etcd/keys.rb
CHANGED
@@ -16,8 +16,8 @@ module Etcd
|
|
16
16
|
# Retrives a key with its associated data, if key is not present it will
|
17
17
|
# return with message "Key Not Found"
|
18
18
|
#
|
19
|
-
# This method
|
20
|
-
# * key - whose data to be
|
19
|
+
# This method takes the following parameters as arguments
|
20
|
+
# * key - whose data is to be retrieved
|
21
21
|
def get(key, opts = {})
|
22
22
|
response = api_execute(key_endpoint + key, :get, params: opts)
|
23
23
|
Response.from_http_response(response)
|
@@ -25,30 +25,16 @@ module Etcd
|
|
25
25
|
|
26
26
|
# Create or update a new key
|
27
27
|
#
|
28
|
-
# This method
|
28
|
+
# This method takes the following parameters as arguments
|
29
29
|
# * key - whose value to be set
|
30
30
|
# * value - value to be set for specified key
|
31
|
-
# * ttl - shelf life of a key (in
|
32
|
-
def set(key,
|
31
|
+
# * ttl - shelf life of a key (in seconds) (optional)
|
32
|
+
def set(key, opts = nil)
|
33
|
+
fail ArgumentError, 'Second argument must be a hash' unless opts.is_a?(Hash)
|
33
34
|
path = key_endpoint + key
|
34
35
|
payload = {}
|
35
|
-
|
36
|
-
|
37
|
-
else
|
38
|
-
payload['value'] = value
|
39
|
-
end
|
40
|
-
if opts.is_a? Fixnum
|
41
|
-
warn '[DEPRECATION] Passing ttl as raw argument is deprecated \
|
42
|
-
please use :ttl => value, this will be removed in next minor release'
|
43
|
-
payload['ttl'] = opts
|
44
|
-
elsif opts.is_a? Hash
|
45
|
-
[:ttl, :dir, :prevExist, :prevValue, :prevIndex].each do |k|
|
46
|
-
payload[k] = opts[k] if opts.key?(k)
|
47
|
-
end
|
48
|
-
elsif opts.nil?
|
49
|
-
# do nothing
|
50
|
-
else
|
51
|
-
fail ArgumentError, "Dont know how to parse #{opts}"
|
36
|
+
[:ttl, :value, :dir, :prevExist, :prevValue, :prevIndex].each do |k|
|
37
|
+
payload[k] = opts[k] if opts.key?(k)
|
52
38
|
end
|
53
39
|
response = api_execute(path, :put, params: payload)
|
54
40
|
Response.from_http_response(response)
|
@@ -56,7 +42,7 @@ module Etcd
|
|
56
42
|
|
57
43
|
# Deletes a key (and its content)
|
58
44
|
#
|
59
|
-
# This method
|
45
|
+
# This method takes the following parameters as arguments
|
60
46
|
# * key - key to be deleted
|
61
47
|
def delete(key, opts = {})
|
62
48
|
response = api_execute(key_endpoint + key, :delete, params: opts)
|
@@ -65,71 +51,63 @@ module Etcd
|
|
65
51
|
|
66
52
|
# Set a new value for key if previous value of key is matched
|
67
53
|
#
|
68
|
-
# This method takes following parameters as
|
54
|
+
# This method takes the following parameters as arguments
|
69
55
|
# * key - whose value is going to change if previous value is matched
|
70
56
|
# * value - new value to be set for specified key
|
71
57
|
# * prevValue - value of a key to compare with existing value of key
|
72
58
|
# * ttl - shelf life of a key (in secsonds) (optional)
|
73
|
-
def compare_and_swap(key,
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
response = api_execute(path, :put, params: payload)
|
78
|
-
Response.from_http_response(response)
|
59
|
+
def compare_and_swap(key, opts = {})
|
60
|
+
fail ArgumentError, 'Second argument must be a hash' unless opts.is_a?(Hash)
|
61
|
+
fail ArgumentError, 'You must pass prevValue' unless opts.key?(:prevValue)
|
62
|
+
set(key, opts)
|
79
63
|
end
|
80
64
|
|
81
65
|
# Gives a notification when specified key changes
|
82
66
|
#
|
83
|
-
# This method
|
67
|
+
# This method takes the following parameters as arguments
|
84
68
|
# @ key - key to be watched
|
85
69
|
# @options [Hash] additional options for watching a key
|
86
70
|
# @options [Fixnum] :index watch the specified key from given index
|
87
71
|
# @options [Fixnum] :timeout specify http timeout
|
88
|
-
def watch(key,
|
72
|
+
def watch(key, opts = {})
|
89
73
|
params = { wait: true }
|
90
|
-
|
91
|
-
|
74
|
+
fail ArgumentError, 'Second argument must be a hash' unless opts.is_a?(Hash)
|
75
|
+
timeout = opts[:timeout] || @read_timeout
|
76
|
+
index = opts[:waitIndex] || opts[:index]
|
92
77
|
params[:waitIndex] = index unless index.nil?
|
93
|
-
params[:consistent] =
|
78
|
+
params[:consistent] = opts[:consistent] if opts.key?(:consistent)
|
94
79
|
|
95
80
|
response = api_execute(key_endpoint + key, :get,
|
96
81
|
timeout: timeout, params: params)
|
97
82
|
Response.from_http_response(response)
|
98
83
|
end
|
99
84
|
|
100
|
-
def create_in_order(dir,
|
85
|
+
def create_in_order(dir, opts = {})
|
101
86
|
path = key_endpoint + dir
|
102
|
-
|
103
|
-
payload
|
87
|
+
fail ArgumentError, 'Second argument must be a hash' unless opts.is_a?(Hash)
|
88
|
+
payload = {}
|
89
|
+
[:ttl, :value].each do |k|
|
90
|
+
payload[k] = opts[k] if opts.key?(k)
|
91
|
+
end
|
104
92
|
response = api_execute(path, :post, params: payload)
|
105
93
|
Response.from_http_response(response)
|
106
94
|
end
|
107
95
|
|
108
96
|
def exists?(key)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
false
|
116
|
-
end
|
97
|
+
Etcd::Log.debug("Checking if key:' #{key}' exists")
|
98
|
+
get(key)
|
99
|
+
true
|
100
|
+
rescue KeyNotFound => e
|
101
|
+
Etcd::Log.debug("Key does not exist #{e}")
|
102
|
+
false
|
117
103
|
end
|
118
104
|
|
119
|
-
def create(key,
|
120
|
-
|
121
|
-
payload = { value: value, prevExist: false }
|
122
|
-
payload['ttl'] = ttl unless ttl.nil?
|
123
|
-
response = api_execute(path, :put, params: payload)
|
124
|
-
Response.from_http_response(response)
|
105
|
+
def create(key, opts = {})
|
106
|
+
set(key, opts.merge(prevExist: false))
|
125
107
|
end
|
126
108
|
|
127
|
-
def update(key,
|
128
|
-
|
129
|
-
payload = { value: value, prevExist: true }
|
130
|
-
payload['ttl'] = ttl unless ttl.nil?
|
131
|
-
response = api_execute(path, :put, params: payload)
|
132
|
-
Response.from_http_response(response)
|
109
|
+
def update(key, opts = {})
|
110
|
+
set(key, opts.merge(prevExist: true))
|
133
111
|
end
|
134
112
|
|
135
113
|
def eternal_watch(key, index = nil)
|
data/lib/etcd/mod/leader.rb
CHANGED
@@ -4,15 +4,15 @@ require 'timeout'
|
|
4
4
|
|
5
5
|
module Etcd
|
6
6
|
module Mod
|
7
|
+
# Implemetn Etcd's Leader module
|
7
8
|
module Leader
|
8
|
-
|
9
9
|
def mod_leader_endpoint
|
10
10
|
'/mod/v2/leader'
|
11
11
|
end
|
12
12
|
|
13
13
|
def set_leader(key, value, ttl)
|
14
14
|
path = mod_leader_endpoint + "#{key}?ttl=#{ttl}"
|
15
|
-
api_execute(path, :put, params:{name: value}).body
|
15
|
+
api_execute(path, :put, params: { name: value }).body
|
16
16
|
end
|
17
17
|
|
18
18
|
def get_leader(key)
|
data/lib/etcd/mod/lock.rb
CHANGED
@@ -4,45 +4,46 @@ require 'timeout'
|
|
4
4
|
|
5
5
|
module Etcd
|
6
6
|
module Mod
|
7
|
+
# implement etcd lock module
|
7
8
|
module Lock
|
8
|
-
|
9
9
|
def mod_lock_endpoint
|
10
10
|
'/mod/v2/lock'
|
11
11
|
end
|
12
12
|
|
13
|
-
def acquire_lock(key, ttl, opts={})
|
13
|
+
def acquire_lock(key, ttl, opts = {})
|
14
14
|
path = mod_lock_endpoint + key + "?ttl=#{ttl}"
|
15
15
|
timeout = opts[:timeout] || 60
|
16
|
-
Timeout
|
17
|
-
api_execute(path, :post, params:opts)
|
16
|
+
Timeout.timeout(timeout) do
|
17
|
+
api_execute(path, :post, params: opts)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
def renew_lock(key, ttl, opts={})
|
22
|
-
unless opts.
|
23
|
-
|
21
|
+
def renew_lock(key, ttl, opts = {})
|
22
|
+
unless opts.key?(:index) || opts.key?(:value)
|
23
|
+
fail ArgumentError, 'You mast pass index or value'
|
24
24
|
end
|
25
25
|
path = mod_lock_endpoint + key + "?ttl=#{ttl}"
|
26
26
|
timeout = opts[:timeout] || 60
|
27
|
-
Timeout
|
28
|
-
api_execute(path, :put, params:opts).body
|
27
|
+
Timeout.timeout(timeout) do
|
28
|
+
api_execute(path, :put, params: opts).body
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
def get_lock(key, opts={})
|
33
|
-
api_execute(mod_lock_endpoint + key, :get, params:opts).body
|
32
|
+
def get_lock(key, opts = {})
|
33
|
+
api_execute(mod_lock_endpoint + key, :get, params: opts).body
|
34
34
|
end
|
35
35
|
|
36
|
-
def delete_lock(key, opts={})
|
37
|
-
unless opts.
|
38
|
-
|
36
|
+
def delete_lock(key, opts = {})
|
37
|
+
unless opts.key?(:index) || opts.key?(:value)
|
38
|
+
fail ArgumentError, 'You must pass index or value'
|
39
39
|
end
|
40
|
-
api_execute(mod_lock_endpoint + key, :delete, params:opts)
|
40
|
+
api_execute(mod_lock_endpoint + key, :delete, params: opts)
|
41
41
|
end
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
# rubocop:disable RescueException
|
44
|
+
def lock(key, ttl, opts = {})
|
45
|
+
acquire_lock('/' + key, ttl, opts)
|
46
|
+
index = get_lock('/' + key, field: index)
|
46
47
|
begin
|
47
48
|
yield key
|
48
49
|
rescue Exception => e
|
@@ -51,6 +52,7 @@ module Etcd
|
|
51
52
|
delete_lock(key, index: index)
|
52
53
|
end
|
53
54
|
end
|
55
|
+
# rubocop:enable RescueException
|
54
56
|
|
55
57
|
alias_method :retrive_lock, :get_lock
|
56
58
|
alias_method :release_lock, :delete_lock
|