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 CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1b268d252f6591fe8015fcf257369ec0502d62b2
4
- data.tar.gz: 0b2567328a06ca5f6433948c7431ee7c4bb4a2d8
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MjEwNDYyYjk0OWMyNzU2M2NhMjk2N2QxYmVmYTFhMmI2NjhlNzdiOQ==
5
+ data.tar.gz: !binary |-
6
+ NDdkYzdhMzM4ZmFhZWU1YTkxZTM3ZWEwYTA1MDE1YjI5NWZmODgxZg==
5
7
  SHA512:
6
- metadata.gz: 9925238076f679e9983358c87f24d53e4f95726bfdea41f06aae831277f5126a99c956997848489c31ed0b228a8605a9f0b5c30fad323bbeca06e3564379f273
7
- data.tar.gz: b65c505cbad680da0cf1967f750408986c69cf17c0d781b21a218a3706afaf069dd10b13b3c7a0d9bbf394cb3b163e5237d7a1c2b5424d549da809f17455586d
8
+ metadata.gz: !binary |-
9
+ OGI3MTA1YmM5MjA4ZjUzYmE4Mjc0MTVmYWEzODU1NDI0ZjhiMzNlZDM3MWQ1
10
+ Mjk1NTIxNmY2MzA4MGYyZTEyZTRiZjExOTFiZTRkNGI3MzAyZjFkOWY3Yjdm
11
+ MGY5NGNmYjU5NDEwOTVjZDNjMzBiOWU5YTA5YzNmOTUwNGEzMDA=
12
+ data.tar.gz: !binary |-
13
+ OGYwZThkZjFmMDcxNzUyZTBjODBhMTgwZWYwMWVmZjU1NzAyZmVjNTg5YmY5
14
+ ZGQ0ODgxYmMzNjBlMTFmMDZhOTI3MmI2MTgzMGE0NDQ3ODkxYWIzYWI0NDc0
15
+ YzAzNzNjYzc3YzIwYzZiMGI5ZjEzN2ZjMWI5NmFlMDkzNDNlOWM=
@@ -0,0 +1,5 @@
1
+ :directories:
2
+ - lib
3
+ :excludes:
4
+ - spec
5
+ - .bundle
data/.gitignore CHANGED
@@ -4,16 +4,7 @@
4
4
  .config
5
5
  .yardoc
6
6
  Gemfile.lock
7
- InstalledFiles
8
7
  _yardoc
9
8
  coverage
10
- doc/
11
- lib/bundler/man
12
- pkg
13
- rdoc
14
9
  spec/reports
15
- test/tmp
16
- test/version_tmp
17
10
  tmp
18
- html/
19
- etcd
@@ -1,14 +1,10 @@
1
- before_install:
2
- - sudo apt-get update -y
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
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in etcd.gemspec
4
4
  gemspec
5
+ gem 'coco'
6
+ gem 'rubocop'
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(: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
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
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+
3
+ wget -c https://go.googlecode.com/files/go1.2.linux-amd64.tar.gz
4
+ tar -zxf go1.2.linux-amd64.tar.gz
5
+ git clone https://github.com/coreos/etcd
6
+ export GOROOT=$PWD/go
7
+ export PATH=$GOROOT/bin:$PATH
8
+ cd etcd && ./build
@@ -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
@@ -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
@@ -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
@@ -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