etcd 0.0.6 → 0.2.0.alpha
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 +13 -5
- data/.coco.yml +5 -0
- data/.gitignore +0 -9
- data/.travis.yml +3 -7
- data/Gemfile +2 -0
- data/README.md +4 -3
- data/build_etcd +8 -0
- data/etcd.gemspec +1 -3
- data/lib/etcd.rb +2 -2
- data/lib/etcd/client.rb +129 -0
- data/lib/etcd/exceptions.rb +78 -0
- data/lib/etcd/keys.rb +147 -0
- data/lib/etcd/log.rb +10 -0
- data/lib/etcd/mod/leader.rb +28 -0
- data/lib/etcd/mod/lock.rb +59 -0
- data/lib/etcd/node.rb +44 -0
- data/lib/etcd/response.rb +33 -0
- data/lib/etcd/stats.rb +25 -0
- data/lib/etcd/version.rb +5 -0
- data/spec/etcd/client_spec.rb +54 -0
- data/spec/etcd/keys_spec.rb +26 -0
- data/spec/etcd/leader_spec.rb +29 -0
- data/spec/etcd/lock_spec.rb +46 -0
- data/spec/etcd/node_spec.rb +27 -0
- data/spec/{functional → etcd}/read_only_client_spec.rb +11 -2
- data/spec/etcd/readme_spec.rb +312 -0
- data/spec/etcd/stats_spec.rb +40 -0
- data/spec/{functional → etcd}/test_and_set_spec.rb +11 -3
- data/spec/{functional → etcd}/watch_spec.rb +13 -4
- data/spec/spec_helper.rb +88 -8
- metadata +46 -68
- data/spec/functional/client_spec.rb +0 -59
- data/spec/functional/lock_spec.rb +0 -70
- data/spec/functional_spec_helpers.rb +0 -56
- data/spec/unit/etcd_spec.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MjEwNDYyYjk0OWMyNzU2M2NhMjk2N2QxYmVmYTFhMmI2NjhlNzdiOQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NDdkYzdhMzM4ZmFhZWU1YTkxZTM3ZWEwYTA1MDE1YjI5NWZmODgxZg==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
OGI3MTA1YmM5MjA4ZjUzYmE4Mjc0MTVmYWEzODU1NDI0ZjhiMzNlZDM3MWQ1
|
10
|
+
Mjk1NTIxNmY2MzA4MGYyZTEyZTRiZjExOTFiZTRkNGI3MzAyZjFkOWY3Yjdm
|
11
|
+
MGY5NGNmYjU5NDEwOTVjZDNjMzBiOWU5YTA5YzNmOTUwNGEzMDA=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
OGYwZThkZjFmMDcxNzUyZTBjODBhMTgwZWYwMWVmZjU1NzAyZmVjNTg5YmY5
|
14
|
+
ZGQ0ODgxYmMzNjBlMTFmMDZhOTI3MmI2MTgzMGE0NDQ3ODkxYWIzYWI0NDc0
|
15
|
+
YzAzNzNjYzc3YzIwYzZiMGI5ZjEzN2ZjMWI5NmFlMDkzNDNlOWM=
|
data/.coco.yml
ADDED
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,14 +1,10 @@
|
|
1
|
-
before_install:
|
2
|
-
-
|
3
|
-
- sudo add-apt-repository ppa:duh/golang -y
|
4
|
-
- sudo apt-get update -y
|
5
|
-
- sudo apt-get install golang -y
|
6
|
-
- git clone https://github.com/coreos/etcd
|
7
|
-
- cd etcd && bash build
|
1
|
+
before_install:
|
2
|
+
- bash build_etcd
|
8
3
|
- bundle install --path .bundle
|
9
4
|
rvm:
|
10
5
|
- 1.9.3
|
11
6
|
- 2.0.0
|
7
|
+
- 2.1.0
|
12
8
|
branches:
|
13
9
|
only:
|
14
10
|
- master
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -20,9 +20,9 @@ Or install it yourself as:
|
|
20
20
|
### Create a client object
|
21
21
|
```ruby
|
22
22
|
client = Etcd.client # this will create a client against etcd server running on localhost on port 4001
|
23
|
-
client = Etcd.client(:
|
24
|
-
client = Etcd.client(:
|
25
|
-
client = Etcd.client(:
|
23
|
+
client = Etcd.client(port: 4002)
|
24
|
+
client = Etcd.client(host: '127.0.0.1', port: 4003)
|
25
|
+
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
26
|
```
|
27
27
|
### Set a key
|
28
28
|
```ruby
|
@@ -38,6 +38,7 @@ client.get('/nodes/n2').value
|
|
38
38
|
### Delete a key
|
39
39
|
```ruby
|
40
40
|
client.delete('/nodes/n1')
|
41
|
+
client.delete('/nodes/', recursive: true)
|
41
42
|
```
|
42
43
|
|
43
44
|
### Test and set
|
data/build_etcd
ADDED
data/etcd.gemspec
CHANGED
@@ -18,12 +18,10 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "mixlib-cli"
|
22
21
|
spec.add_dependency "mixlib-log"
|
23
|
-
spec.add_dependency "uuid"
|
24
22
|
|
23
|
+
spec.add_development_dependency "uuid"
|
25
24
|
spec.add_development_dependency "bundler"
|
26
25
|
spec.add_development_dependency "rake"
|
27
26
|
spec.add_development_dependency "rspec"
|
28
|
-
spec.add_development_dependency "simplecov"
|
29
27
|
end
|
data/lib/etcd.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
+
# Encoding: utf-8
|
1
2
|
|
3
|
+
require 'etcd/client'
|
2
4
|
##
|
3
5
|
# This module provides the Etcd:: name space for the gem and few
|
4
6
|
# factory methods for Etcd domain objects
|
5
|
-
require 'etcd/client'
|
6
7
|
module Etcd
|
7
|
-
|
8
8
|
##
|
9
9
|
# Create and return a Etcd::Client object. It takes a hash +opts+
|
10
10
|
# as an argument which gets passed to the Etcd::Client.new method
|
data/lib/etcd/client.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'json'
|
5
|
+
require 'etcd/log'
|
6
|
+
require 'etcd/stats'
|
7
|
+
require 'etcd/keys'
|
8
|
+
require 'etcd/exceptions'
|
9
|
+
require 'etcd/mod/lock'
|
10
|
+
require 'etcd/mod/leader'
|
11
|
+
|
12
|
+
module Etcd
|
13
|
+
##
|
14
|
+
# This is the central ruby class for Etcd. It provides methods for all
|
15
|
+
# etcd api calls. It also provides few additional methods beyond the core
|
16
|
+
# etcd api, like Etcd::Client#lock and Etcd::Client#eternal_watch, they
|
17
|
+
# are defined in separate modules and included in this class
|
18
|
+
class Client
|
19
|
+
|
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
|
+
|
24
|
+
include Stats
|
25
|
+
include Keys
|
26
|
+
include Mod::Lock
|
27
|
+
include Mod::Leader
|
28
|
+
|
29
|
+
attr_reader :host, :port, :http, :allow_redirect
|
30
|
+
attr_reader :use_ssl, :verify_mode, :read_timeout
|
31
|
+
|
32
|
+
##
|
33
|
+
# Creates an Etcd::Client object. It accepts a hash +opts+ as argument
|
34
|
+
#
|
35
|
+
# @param [Hash] opts The options for new Etcd::Client object
|
36
|
+
# @opts [String] :host IP address of the etcd server (default 127.0.0.1)
|
37
|
+
# @opts [Fixnum] :port Port number of the etcd server (default 4001)
|
38
|
+
# @opts [Fixnum] :read_timeout set HTTP read timeouts (default 60)
|
39
|
+
def initialize(opts = {})
|
40
|
+
@host = opts[:host] || '127.0.0.1'
|
41
|
+
@port = opts[:port] || 4001
|
42
|
+
@read_timeout = opts[:read_timeout] || 60
|
43
|
+
@allow_redirect = opts.key?(:allow_redirect) ? opts[:allow_redirect] : true
|
44
|
+
@use_ssl = opts[:use_ssl] || false
|
45
|
+
@verify_mode = opts.key?(:verify_mode) ? opts[:verify_mode] : OpenSSL::SSL::VERIFY_PEER
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the etcd api version that will be used for across API methods
|
49
|
+
def version_prefix
|
50
|
+
'/v2'
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the etcd daemon version
|
54
|
+
def version
|
55
|
+
api_execute('/version', :get).body
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns array of all machines in the cluster
|
59
|
+
def machines
|
60
|
+
api_execute(version_prefix + '/machines', :get).body.split(',').map(&:strip)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get the current leader
|
64
|
+
def leader
|
65
|
+
api_execute(version_prefix + '/leader', :get).body.strip
|
66
|
+
end
|
67
|
+
|
68
|
+
# This method sends api request to etcd server.
|
69
|
+
#
|
70
|
+
# This method has following parameters as argument
|
71
|
+
# * path - etcd server path (etcd server end point)
|
72
|
+
# * method - the request method used
|
73
|
+
# * options - any additional parameters used by request method (optional)
|
74
|
+
def api_execute(path, method, options = {})
|
75
|
+
params = options[:params]
|
76
|
+
case method
|
77
|
+
when :get
|
78
|
+
req = build_http_request(Net::HTTP::Get, path, params)
|
79
|
+
when :post
|
80
|
+
req = build_http_request(Net::HTTP::Post, path, nil, params)
|
81
|
+
when :put
|
82
|
+
req = build_http_request(Net::HTTP::Put, path, nil, params)
|
83
|
+
when :delete
|
84
|
+
req = build_http_request(Net::HTTP::Delete, path, params)
|
85
|
+
else
|
86
|
+
fail "Unknown http action: #{method}"
|
87
|
+
end
|
88
|
+
timeout = options[:timeout] || @read_timeout
|
89
|
+
http = Net::HTTP.new(host, port)
|
90
|
+
http.read_timeout = timeout
|
91
|
+
http.use_ssl = use_ssl
|
92
|
+
http.verify_mode = verify_mode
|
93
|
+
Log.debug("Invoking: '#{req.class}' against '#{path}")
|
94
|
+
res = http.request(req)
|
95
|
+
Log.debug("Response code: #{res.code}")
|
96
|
+
process_http_request(res)
|
97
|
+
end
|
98
|
+
|
99
|
+
def process_http_request(res)
|
100
|
+
case res
|
101
|
+
when HTTP_SUCCESS
|
102
|
+
Log.debug('Http success')
|
103
|
+
res
|
104
|
+
when HTTP_REDIRECT
|
105
|
+
if allow_redirect
|
106
|
+
Log.debug('Http redirect, following')
|
107
|
+
api_execute(res['location'], method, params: params)
|
108
|
+
else
|
109
|
+
Log.debug('Http redirect not allowed')
|
110
|
+
res.error!
|
111
|
+
end
|
112
|
+
when HTTP_CLIENT_ERROR
|
113
|
+
fail Error.from_http_response(res)
|
114
|
+
else
|
115
|
+
Log.debug('Http error')
|
116
|
+
Log.debug(res.body)
|
117
|
+
res.error!
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def build_http_request(klass, path, params = nil, body = nil)
|
122
|
+
path += '?' + URI.encode_www_form(params) unless params.nil?
|
123
|
+
req = klass.new(path)
|
124
|
+
req.body = URI.encode_www_form(body) unless body.nil?
|
125
|
+
Etcd::Log.debug("Built #{klass} path:'#{path}' body:'#{req.body}'")
|
126
|
+
req
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
# Provides Etcd namespace
|
6
|
+
module Etcd
|
7
|
+
# Represents all etcd custom errors
|
8
|
+
class Error < StandardError
|
9
|
+
attr_reader :cause, :error_code, :index
|
10
|
+
|
11
|
+
def initialize(opts = {})
|
12
|
+
super(opts['message'])
|
13
|
+
@cause = opts['cause']
|
14
|
+
@index = opts['index']
|
15
|
+
@error_code = opts['errorCode']
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.from_http_response(response)
|
19
|
+
opts = JSON.parse(response.body)
|
20
|
+
unless ERROR_CODE_MAPPING.key?(opts['errorCode'])
|
21
|
+
fail "Unknown error code: #{opts['errorCode']}"
|
22
|
+
end
|
23
|
+
ERROR_CODE_MAPPING[opts['errorCode']].new(opts)
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
"<#{self.class}: index:#{index}, code:#{error_code}, cause:'#{cause}'>"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# command related error
|
32
|
+
class KeyNotFound < Error; end
|
33
|
+
class TestFailed < Error; end
|
34
|
+
class NotFile < Error; end
|
35
|
+
class NoMorePeer < Error; end
|
36
|
+
class NotDir < Error; end
|
37
|
+
class NodeExist < Error; end
|
38
|
+
class KeyIsPreserved < Error; end
|
39
|
+
|
40
|
+
# Post form related error
|
41
|
+
class ValueRequired < Error; end
|
42
|
+
class PrevValueRequired < Error; end
|
43
|
+
class TTLNaN < Error; end
|
44
|
+
class IndexNaN < Error; end
|
45
|
+
|
46
|
+
# Raft related error
|
47
|
+
class RaftInternal < Error; end
|
48
|
+
class LeaderElect < Error; end
|
49
|
+
|
50
|
+
# Etcd related error
|
51
|
+
class WatcherCleared < Error; end
|
52
|
+
class EventIndexCleared < Error; end
|
53
|
+
|
54
|
+
ERROR_CODE_MAPPING = {
|
55
|
+
# command related error
|
56
|
+
100 => KeyNotFound,
|
57
|
+
101 => TestFailed,
|
58
|
+
102 => NotFile,
|
59
|
+
103 => NoMorePeer,
|
60
|
+
104 => NotDir,
|
61
|
+
105 => NodeExist,
|
62
|
+
106 => KeyIsPreserved,
|
63
|
+
|
64
|
+
# Post form related error
|
65
|
+
200 => ValueRequired,
|
66
|
+
201 => PrevValueRequired,
|
67
|
+
202 => TTLNaN,
|
68
|
+
203 => IndexNaN,
|
69
|
+
|
70
|
+
# Raft related error
|
71
|
+
300 => RaftInternal,
|
72
|
+
301 => LeaderElect,
|
73
|
+
|
74
|
+
# Etcd related error
|
75
|
+
400 => WatcherCleared,
|
76
|
+
401 => EventIndexCleared
|
77
|
+
}
|
78
|
+
end
|
data/lib/etcd/keys.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'etcd/response'
|
5
|
+
require 'etcd/log'
|
6
|
+
|
7
|
+
module Etcd
|
8
|
+
# Keys module provides the basic key value operations against
|
9
|
+
# etcd /keys namespace
|
10
|
+
module Keys
|
11
|
+
# return etcd endpoint that is reserved for key/value store
|
12
|
+
def key_endpoint
|
13
|
+
version_prefix + '/keys'
|
14
|
+
end
|
15
|
+
|
16
|
+
# Retrives a key with its associated data, if key is not present it will
|
17
|
+
# return with message "Key Not Found"
|
18
|
+
#
|
19
|
+
# This method has following parameters as argument
|
20
|
+
# * key - whose data to be retrive
|
21
|
+
def get(key, opts = {})
|
22
|
+
response = api_execute(key_endpoint + key, :get, params: opts)
|
23
|
+
Response.from_http_response(response)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Create or update a new key
|
27
|
+
#
|
28
|
+
# This method has following parameters as argument
|
29
|
+
# * key - whose value to be set
|
30
|
+
# * value - value to be set for specified key
|
31
|
+
# * ttl - shelf life of a key (in secsonds) (optional)
|
32
|
+
def set(key, value, opts = nil)
|
33
|
+
path = key_endpoint + key
|
34
|
+
payload = {}
|
35
|
+
if value.is_a?(Hash) # directory
|
36
|
+
opts = value.dup
|
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}"
|
52
|
+
end
|
53
|
+
response = api_execute(path, :put, params: payload)
|
54
|
+
Response.from_http_response(response)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Deletes a key (and its content)
|
58
|
+
#
|
59
|
+
# This method has following parameters as argument
|
60
|
+
# * key - key to be deleted
|
61
|
+
def delete(key, opts = {})
|
62
|
+
response = api_execute(key_endpoint + key, :delete, params: opts)
|
63
|
+
Response.from_http_response(response)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Set a new value for key if previous value of key is matched
|
67
|
+
#
|
68
|
+
# This method takes following parameters as argument
|
69
|
+
# * key - whose value is going to change if previous value is matched
|
70
|
+
# * value - new value to be set for specified key
|
71
|
+
# * prevValue - value of a key to compare with existing value of key
|
72
|
+
# * ttl - shelf life of a key (in secsonds) (optional)
|
73
|
+
def compare_and_swap(key, value, prevValue, ttl = nil)
|
74
|
+
path = key_endpoint + key
|
75
|
+
payload = { 'value' => value, 'prevValue' => prevValue }
|
76
|
+
payload['ttl'] = ttl unless ttl.nil?
|
77
|
+
response = api_execute(path, :put, params: payload)
|
78
|
+
Response.from_http_response(response)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Gives a notification when specified key changes
|
82
|
+
#
|
83
|
+
# This method has following parameters as argument
|
84
|
+
# @ key - key to be watched
|
85
|
+
# @options [Hash] additional options for watching a key
|
86
|
+
# @options [Fixnum] :index watch the specified key from given index
|
87
|
+
# @options [Fixnum] :timeout specify http timeout
|
88
|
+
def watch(key, options = {})
|
89
|
+
params = { wait: true }
|
90
|
+
timeout = options[:timeout] || @read_timeout
|
91
|
+
index = options[:waitIndex] || options[:index]
|
92
|
+
params[:waitIndex] = index unless index.nil?
|
93
|
+
params[:consistent] = options[:consistent] if options.key?(:consistent)
|
94
|
+
|
95
|
+
response = api_execute(key_endpoint + key, :get,
|
96
|
+
timeout: timeout, params: params)
|
97
|
+
Response.from_http_response(response)
|
98
|
+
end
|
99
|
+
|
100
|
+
def create_in_order(dir, value, opts = {})
|
101
|
+
path = key_endpoint + dir
|
102
|
+
payload = { 'value' => value }
|
103
|
+
payload['ttl'] = opts[:ttl] if opts[:ttl]
|
104
|
+
response = api_execute(path, :post, params: payload)
|
105
|
+
Response.from_http_response(response)
|
106
|
+
end
|
107
|
+
|
108
|
+
def exists?(key)
|
109
|
+
begin
|
110
|
+
Etcd::Log.debug("Checking if key:' #{key}' exists")
|
111
|
+
get(key)
|
112
|
+
true
|
113
|
+
rescue KeyNotFound => e
|
114
|
+
Etcd::Log.debug("Key does not exist #{e}")
|
115
|
+
false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def create(key, value, ttl = nil)
|
120
|
+
path = key_endpoint + key
|
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)
|
125
|
+
end
|
126
|
+
|
127
|
+
def update(key, value, ttl = nil)
|
128
|
+
path = key_endpoint + key
|
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)
|
133
|
+
end
|
134
|
+
|
135
|
+
def eternal_watch(key, index = nil)
|
136
|
+
loop do
|
137
|
+
response = watch(key, index)
|
138
|
+
yield response
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
alias_method :key?, :exists?
|
143
|
+
alias_method :exist?, :exists?
|
144
|
+
alias_method :has_key?, :exists?
|
145
|
+
alias_method :test_and_set, :compare_and_swap
|
146
|
+
end
|
147
|
+
end
|