Class: RubyDoozer::Client

Inherits:
Object
  • Object
show all
Includes:
SemanticLogger::Loggable
Defined in:
lib/ruby_doozer/client.rb

Instance Method Summary collapse

Constructor Details

#initialize(params = {}) ⇒ Client

Create a resilient client connection to a Doozer server

Since Doozer does not support ‘_’ and it is very prevalent in Ruby, all ‘_’ passed in the path will be converted to ‘-’ when calling doozer. All paths in doozer that contain ‘-’ on the way back from doozer will be converted to ‘_’



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/ruby_doozer/client.rb', line 16

def initialize(params={})
  # User configurable options
  params[:read_timeout]           ||= 5
  params[:connect_timeout]        ||= 3
  params[:connect_retry_interval] ||= 0.1
  params[:connect_retry_count]    ||= 3

  # Server name and port where Doozer is running
  # Defaults to 127.0.0.1:8046
  params[:server] ||= '127.0.0.1:8046' unless params[:servers]

  # Disable buffering the send since it is a RPC call
  params[:buffered] = false

  logger.trace "Socket Connection parameters", params.dup

  # For each new connection
  params[:on_connect] = Proc.new do |socket|
    # Reset user_data on each connection
    socket.user_data = 0
  end

  @socket = ResilientSocket::TCPClient.new(params)
end

Instance Method Details

#[](path) ⇒ Object

Returns just the value at the supplied path, not the revision Returns nil when the key was not found



79
80
81
82
# File 'lib/ruby_doozer/client.rb', line 79

def [](path)
  result = get(path)
  result.rev > 0 ? result.value : nil
end

#[]=(path, value) ⇒ Object

Sets the current value at the supplied path



68
69
70
# File 'lib/ruby_doozer/client.rb', line 68

def []=(path,value)
  set(path, value)
end

#access(secret) ⇒ Object



109
110
111
# File 'lib/ruby_doozer/client.rb', line 109

def access(secret)
  invoke(Request.new(:path => secret, :verb => Request::Verb::ACCESS))
end

#closeObject

Close this client connection to doozer



42
43
44
# File 'lib/ruby_doozer/client.rb', line 42

def close
  @socket.close if @socket
end

#current_revisionObject

Returns the current Doozer revision



47
48
49
# File 'lib/ruby_doozer/client.rb', line 47

def current_revision
  invoke(Request.new(:verb => Request::Verb::REV)).rev
end

#delete(path, rev = -1)) ⇒ Object

Deletes the file at path if rev is greater than or equal to the file’s revision. Returns nil when the file was removed Raises an exception if an attempt to remove the file and its revision

is greater than that supplied


88
89
90
91
# File 'lib/ruby_doozer/client.rb', line 88

def delete(path, rev=-1)
  invoke(Request.new(:path => path, :rev => rev, :verb => Request::Verb::DEL))
  nil
end

#directory(path, offset = 0, rev = nil) ⇒ Object

Returns the directory in the supplied path Use offset to get the next returns nil if no further paths are available



96
97
98
99
100
101
102
103
# File 'lib/ruby_doozer/client.rb', line 96

def directory(path, offset = 0, rev = nil)
  begin
    invoke(Request.new(:path => path, :rev => rev, :offset => offset, :verb => Request::Verb::GETDIR))
  rescue RubyDoozer::ResponseError => exc
    raise exc unless exc.message.include?('RANGE')
    nil
  end
end

#doozer_hostsObject

Returns [Array] of hostname [String] with each string representing another Doozer server that can be connected to



154
155
156
157
158
159
160
# File 'lib/ruby_doozer/client.rb', line 154

def doozer_hosts
  hosts = []
  walk('/ctl/node/*/addr') do |path, value, revision|
    hosts << value unless hosts.include? value
  end
  hosts
end

#get(path, rev = nil) ⇒ Object

Return the value at the supplied path and revision



73
74
75
# File 'lib/ruby_doozer/client.rb', line 73

def get(path, rev = nil)
  invoke(Request.new(:path => path, :rev => rev, :verb => Request::Verb::GET))
end

#invoke(request, readonly = true, timeout = nil) ⇒ Object

Call the Doozer server

When readonly ==> true the request is always retried on network failure When readonly ==> false the request is retried on network failure

_only_ if a rev has been supplied

When modifier is true

Raises:



193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/ruby_doozer/client.rb', line 193

def invoke(request, readonly=true, timeout=nil)
  retry_read = readonly || !request.rev.nil?
  response = nil
  @socket.retry_on_connection_failure do
    send(request)
    response = read(timeout) if retry_read
  end
  # Network error on read must be sent back to caller since we do not
  # know if the modification was made
  response = read(timeout) unless retry_read
  raise ResponseError.new("#{Response::Err.name_by_value(response.err_code)} (#{response.err_code}): #{response.err_detail}") if response.err_code != 0
  response
end

#read(timeout = nil) ⇒ Object

Read the protobuf Response from Doozer



221
222
223
224
225
226
227
228
229
230
# File 'lib/ruby_doozer/client.rb', line 221

def read(timeout=nil)
  # First strip the additional header indicating the size of the subsequent response
  head = @socket.read(4,nil,timeout)
  length = head.unpack("N")[0]
  response = Response.new.parse_from_string(@socket.read(length))
  # Translate returned path so that minuses are converted to underscores
  response.path.gsub!('-', '_')
  logger.trace('Received') {response.to_hash}
  response
end

#send(request) ⇒ Object

Send the protobuf Request to Doozer



208
209
210
211
212
213
214
215
216
217
218
# File 'lib/ruby_doozer/client.rb', line 208

def send(request)
  # Translate path so that underscores are converted to minuses
  # Don't change the original input value
  request.path = request.path.gsub('_', '-')
  request.tag = 0
  data = request.serialize_to_string
  # An additional header is added to the request indicating the size of the request
  head = [data.length].pack("N")
  logger.trace('Sending') {request.to_hash}
  @socket.write(head+data)
end

#set(path, value, rev = -1)) ⇒ Object

Set a value in Doozer

path:  Path to the value to be set
value: Value to set
rev:   Revision at which to set the value
      If not supplied it will replace the latest version on the server

Returns the new revision of the updated value

It is recommended to set the revision so that multiple clients do not attempt to update the value at the same time. Setting the revision also allows the call to be retried automatically in the event of a network failure



63
64
65
# File 'lib/ruby_doozer/client.rb', line 63

def set(path, value, rev=-1)
  invoke(Request.new(:path => path, :value => value, :rev => rev, :verb => Request::Verb::SET), false).rev
end

#stat(path, rev = nil) ⇒ Object



105
106
107
# File 'lib/ruby_doozer/client.rb', line 105

def stat(path, rev = nil)
  invoke(Request.new(:path => path, :rev => rev, :verb => Request::Verb::STAT))
end

#wait(path, rev = current_revision, timeout = -1)) ⇒ Object

Wait for changes to the supplied path Returns the next change to the supplied path



164
165
166
# File 'lib/ruby_doozer/client.rb', line 164

def wait(path, rev=current_revision, timeout=-1)
  invoke(Request.new(:path => path, :rev => rev, :verb => Request::Verb::WAIT), true, timeout)
end

#walk(path, rev = nil, offset = 0, &block) ⇒ Object

Iterate over every entry in the supplied path path can also contain wildcard characters such as ‘*’

Parameters:

path
  The path including wildcard specifiers
  The path must begin with '/'
  The path can contain any of the following wildcard specifiers:
    **            Includes the directory and it's children
    *             Only values with that path
  Example paths:
    /**
    /*
    /configs/**
    /ctl/node/*/addr

Example:

client.walk('/**') do |path, value, revision|
  puts "Found: #{path} => #{value}, Revision: #{revision}"
end


133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/ruby_doozer/client.rb', line 133

def walk(path, rev = nil, offset = 0, &block)
  paths = block ? nil : []
  revision = rev || current_revision
  # Resume walk on network connection failure
  @socket.retry_on_connection_failure do
    while true
      send(Request.new(:path => path, :rev => revision , :offset => offset, :verb => Request::Verb::WALK))
      response = read
      if response.err_code != 0
        break if response.err_code == Response::Err::RANGE
        raise ResponseError.new("#{Response::Err.name_by_value(response.err_code)}: #{response.err_detail}")
      end
      block ? block.call(response.path, response.value, response.rev) : paths << response
      offset += 1
    end
  end
  paths
end

#watch(path, rev = current_revision) ⇒ Object

Watch for any changes to the supplied path, calling the supplied block for every change Runs until an exception is thrown

If a connection error occurs it will create a new connection to doozer and resubmit the wait. I.e. Will continue from where it left off without any noticeable effect to the supplied block



175
176
177
178
179
180
181
# File 'lib/ruby_doozer/client.rb', line 175

def watch(path, rev=current_revision)
  loop do
    result = wait(path, rev, -1)
    yield result
    rev = result.rev + 1
  end
end