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