In doing work in Ruby for back-end infrastructure that needs to contact multiple RouterOS devices quickly and efficiently (using the RouterOS API), I've been exploring some non-blocking Ruby libraries. Looking at
eventmachine and
Rev, I'm leaning towards Rev due to Ruby 1.9 having integrated a lot of nonblocking stuff now.
So for asynchronous DNS resolution, I whipped up a little test script:
require 'rev'
def resolve_name(query, success_callback=nil, fail_callback=nil, loop=nil)
unless query.is_a?(String) && query.length > 0
raise ArgumentError.new(
"unexpected argument query (expected String of non-zero length)"
)
end
unless loop.nil? || loop.is_a?(Rev::Loop)
raise ArgumentError.new(
"unexpected argument class '#{loop.class}' for loop (expected Nil" +
" or Rev::Looop)"
)
end
unless success_callback.nil? || success_callback.is_a?(Proc)
raise ArgumentError.new(
"unexpected argument class '#{success_callback.class}' for " +
"success_callback (expected Nil or Proc)"
)
end
unless fail_callback.nil? || fail_callback.is_a?(Proc)
raise ArgumentError.new(
"unexpected argument class '#{fail_callback.class}' for " +
"fail_callback (expected Nil or Proc)"
)
end
aloop = loop.nil? ? Rev::Loop.new() : loop
answer = nil
onsuccess = success_callback.nil? ?
lambda { |result| answer = result } :
success_callback
onfail = fail_callback.nil? ?
lambda { onsuccess.(nil) } :
fail_callback
Class.new(Rev::DNSResolver) do
def initialize(query, on_success, on_fail)
@on_success = on_success
@on_fail = on_fail
super(query)
end
def on_fail
return @on_fail.nil? ? nil : @on_fail.()
end
def on_success(result)
if @on_success.nil?
return result
else
return @on_success.(result)
end
end
end.new(query, onsuccess, onfail).attach(aloop)
aloop.run if loop.nil?
return answer
end
loop = Rev::Loop.new()
def rq(query, loop)
resolve_name(
query,
lambda {|result|
if result.nil?
print "'#{query} failed to resolve.\n"
else
print "'#{query} resolves to '#{result}'\n"
end
},
nil,
loop
)
end
ARGV.each do |query|
rq(query, loop)
if query =~ /^\d+\.\d+\.\d+\.\d+$/
rq(query.split(/\./).reverse.join('.') + '.in-addr.arpa', loop)
end
end
loop.run
This lets me test it from the command line:
$ ./x.rb www.google.com www.infowest.com \
www.eq.net hatrack.com eq.net fooaasdvwedf.df \
127.0.0.1 204.17.177.10
'www.google.com resolves to '74.125.155.103'
'127.0.0.1 resolves to '127.0.0.1'
'www.infowest.com resolves to '204.17.177.250'
'www.eq.net resolves to '69.46.228.43'
'hatrack.com resolves to '66.223.20.195'
'eq.net resolves to '69.46.226.134'
'1.0.0.127.in-addr.arpa failed to resolve.
'204.17.177.10 resolves to '204.17.177.10'
'10.177.17.204.in-addr.arpa failed to resolve.
'fooaasdvwedf.df failed to resolve.
Ah, looks like Rev::DNSResolver.new() doesn't like to look up PTR records... I need to find better documentation to learn how to tell it to do PTR resolution instead of just IP resolution. Currently actual useful documentation is scarce, which seems quite common for most things in Ruby. *sigh*
Still, I quite like the single-threaded non-blocking event-style this (and similar libraries) allow. Now if only the Ruby MySQL module were fully asynchronous/non-blocking and event-ified (and I am ignoring
NeverBlock's work in this area, as I've not tried it out yet, and also I'm hoping for something to become "mainstream").
With all this non-blocking stuff, monitoring tons of RouterOS devices using a single process (except for the MySQL database updates) should go smoothly. I like it!