etcd 0.2.0.alpha → 0.2.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|