Class: MogileFS::Backend

Inherits:
Object
  • Object
show all
Defined in:
lib/mogilefs/backend.rb

Overview

MogileFS::Backend communicates with the MogileFS trackers.

Constant Summary collapse

BACKEND_ERRORS =
{}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args) ⇒ Backend

Creates a new MogileFS::Backend.

:hosts is a required argument and must be an Array containing one or more ‘hostname:port’ pairs as Strings.

:timeout adjusts the request timeout before an error is returned.

Raises:

  • (ArgumentError)


55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/mogilefs/backend.rb', line 55

def initialize(args)
  @hosts = args[:hosts]
  raise ArgumentError, "must specify at least one host" unless @hosts
  raise ArgumentError, "must specify at least one host" if @hosts.empty?
  unless @hosts == @hosts.select { |h| h =~ /:\d+$/ } then
    raise ArgumentError, ":hosts must be in 'host:port' form"
  end

  @mutex = Mutex.new
  @timeout = args[:timeout] || 3
  @socket = nil
  @lasterr = nil
  @lasterrstr = nil

  @dead = {}
end

Instance Attribute Details

#lasterrObject (readonly)

The last error



40
41
42
# File 'lib/mogilefs/backend.rb', line 40

def lasterr
  @lasterr
end

#lasterrstrObject (readonly)

The string attached to the last error



45
46
47
# File 'lib/mogilefs/backend.rb', line 45

def lasterrstr
  @lasterrstr
end

Class Method Details

.add_command(*names) ⇒ Object

Adds MogileFS commands names.



13
14
15
16
17
18
19
# File 'lib/mogilefs/backend.rb', line 13

def self.add_command(*names)
  names.each do |name|
    define_method name do |*args|
      do_request name, args.first || {}
    end
  end
end

.add_error(err_snake) ⇒ Object

this converts an error code from a mogilefsd tracker to an exception:

Examples of some exceptions that get created:

class AfterMismatchError < MogileFS::Error; end
class DomainNotFoundError < MogileFS::Error; end
class InvalidCharsError < MogileFS::Error; end


29
30
31
32
33
34
35
# File 'lib/mogilefs/backend.rb', line 29

def self.add_error(err_snake)
  err_camel = err_snake.gsub(/(?:^|_)([a-z])/) { $1.upcase } << 'Error'
  unless self.const_defined?(err_camel)
    self.class_eval("class #{err_camel} < MogileFS::Error; end")
  end
  BACKEND_ERRORS[err_snake] = self.const_get(err_camel)
end

Instance Method Details

#do_request(cmd, args) ⇒ Object

Performs the cmd request with args.



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/mogilefs/backend.rb', line 151

def do_request(cmd, args)
  @mutex.synchronize do
    request = make_request cmd, args

    begin
      bytes_sent = socket.send request, 0
    rescue SystemCallError
      shutdown
      raise MogileFS::UnreachableBackendError
    end

    unless bytes_sent == request.length then
      raise MogileFS::RequestTruncatedError,
        "request truncated (sent #{bytes_sent} expected #{request.length})"
    end

    readable?

    parse_response(socket.gets)
  end
end

#error(err_snake) ⇒ Object

this converts an error code from a mogilefsd tracker to an exception Most of these exceptions should already be defined, but since the MogileFS server code is liable to change and we may not always be able to keep up with the changes



184
185
186
# File 'lib/mogilefs/backend.rb', line 184

def error(err_snake)
  BACKEND_ERRORS[err_snake] || self.class.add_error(err_snake)
end

#make_request(cmd, args) ⇒ Object

Makes a new request string for cmd and args.



176
177
178
# File 'lib/mogilefs/backend.rb', line 176

def make_request(cmd, args)
  "#{cmd} #{url_encode args}\r\n"
end

#parse_response(line) ⇒ Object

Turns the line response from the server into a Hash of options, an error, or raises, as appropriate.



192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/mogilefs/backend.rb', line 192

def parse_response(line)
  if line =~ /^ERR\s+(\w+)\s*(.*)/ then
    @lasterr = $1
    @lasterrstr = $2 ? url_unescape($2) : nil
    raise error(@lasterr)
    return nil
  end

  return url_decode($1) if line =~ /^OK\s+\d*\s*(\S*)/

  raise MogileFS::InvalidResponseError,
        "Invalid response from server: #{line.inspect}"
end

#readable?Boolean

Raises if the socket does not become readable in @timeout seconds.

Returns:

  • (Boolean)


209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/mogilefs/backend.rb', line 209

def readable?
  timeleft = @timeout
  peer = nil
  loop do
    t0 = Time.now
    found = IO.select([socket], nil, nil, timeleft)
    return true if found && found[0]
    timeleft -= (Time.now - t0)

    if timeleft < 0
      peer = @socket ? "#{@socket.mogilefs_peername} " : nil

      # we DO NOT want the response we timed out waiting for, to crop up later
      # on, on the same socket, intersperesed with a subsequent request! so,
      # we close the socket if it times out like this
      shutdown
      raise MogileFS::UnreadableSocketError, "#{peer}never became readable"
      break
    end
    shutdown
  end
  false
end

#shutdownObject

Closes this backend’s socket.



75
76
77
78
79
80
# File 'lib/mogilefs/backend.rb', line 75

def shutdown
  if @socket
    @socket.close rescue nil # ignore errors
    @socket = nil
  end
end

#socketObject

Returns a socket connected to a MogileFS tracker.



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/mogilefs/backend.rb', line 236

def socket
  return @socket if @socket and not @socket.closed?

  now = Time.now

  @hosts.sort_by { rand(3) - 1 }.each do |host|
    next if @dead.include? host and @dead[host] > now - 5

    begin
      @socket = Socket.mogilefs_new(*(host.split(/:/) << @timeout))
    rescue SystemCallError, MogileFS::Timeout
      @dead[host] = now
      next
    end

    return @socket
  end

  raise MogileFS::UnreachableBackendError
end

#url_decode(str) ⇒ Object

Turns a url params string into a Hash.



260
261
262
263
264
# File 'lib/mogilefs/backend.rb', line 260

def url_decode(str)
  Hash[*(str.split(/&/).map { |pair|
    pair.split(/=/, 2).map { |x| url_unescape(x) }
  } ).flatten]
end

#url_encode(params) ⇒ Object

Turns a Hash (or Array of pairs) into a url params string.



269
270
271
272
273
# File 'lib/mogilefs/backend.rb', line 269

def url_encode(params)
  params.map do |k,v|
    "#{url_escape k.to_s}=#{url_escape v.to_s}"
  end.join("&")
end

#url_escape(str) ⇒ Object

Ruby 1.8



282
283
284
# File 'lib/mogilefs/backend.rb', line 282

def url_escape(str)
  str.gsub(/([^\w\,\-.\/\\\: ])/) { "%%%02x" % $1.ord }.tr(' ', '+')
end

#url_unescape(str) ⇒ Object

Unescapes naughty URL characters.



290
291
292
# File 'lib/mogilefs/backend.rb', line 290

def url_unescape(str)
  str.gsub(/%([a-f0-9][a-f0-9])/i) { [$1.to_i(16)].pack 'C' }.tr('+', ' ')
end