messaging system working for pipes, custom serialization. broken for ssh, since it runs commands.
This commit is contained in:
parent
9916d7a6e5
commit
33b9885bc0
|
@ -0,0 +1,29 @@
|
||||||
|
module Hydra #:nodoc:
|
||||||
|
module MessagingIO
|
||||||
|
# Read a line from the input IO object.
|
||||||
|
def gets
|
||||||
|
raise IOError unless @reader
|
||||||
|
message = @reader.gets
|
||||||
|
return nil unless message
|
||||||
|
return Message.build(eval(message.chomp))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Write a line to the output IO object
|
||||||
|
def write(message)
|
||||||
|
raise IOError unless @writer
|
||||||
|
raise UnprocessableMessage unless message.is_a?(Hydra::Message)
|
||||||
|
begin
|
||||||
|
@writer.write(message.serialize+"\n")
|
||||||
|
rescue Errno::EPIPE
|
||||||
|
raise IOError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class UnprocessableMessage < RuntimeError
|
||||||
|
attr_accessor :message
|
||||||
|
def initialize(message = "Message expected")
|
||||||
|
@message = message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,7 +1,12 @@
|
||||||
module Hydra #:nodoc:
|
module Hydra #:nodoc:
|
||||||
class Message #:nodoc:
|
class Message #:nodoc:
|
||||||
def self.build(str)
|
def self.build(hash)
|
||||||
eval(str).new
|
hash.delete(:class).new(hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize(opts = {})
|
||||||
|
opts[:class] = self.class
|
||||||
|
opts.inspect
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
module Hydra #:nodoc:
|
module Hydra #:nodoc:
|
||||||
module Messages #:nodoc:
|
module Messages #:nodoc:
|
||||||
|
# Message indicating that a Runner needs a file to run
|
||||||
class RunnerRequestsFile < Hydra::Message
|
class RunnerRequestsFile < Hydra::Message
|
||||||
def serialize
|
|
||||||
"Hydra::Messages::RunnerRequestsFile"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
require 'hydra/io'
|
||||||
module Hydra #:nodoc:
|
module Hydra #:nodoc:
|
||||||
# Read and write between two processes via pipes. For example:
|
# Read and write between two processes via pipes. For example:
|
||||||
# @pipe = Hydra::Pipe.new
|
# @pipe = Hydra::Pipe.new
|
||||||
|
@ -24,32 +25,13 @@ module Hydra #:nodoc:
|
||||||
# One tube is for sending from parent to child, and the other
|
# One tube is for sending from parent to child, and the other
|
||||||
# tube is for sending from child to parent.
|
# tube is for sending from child to parent.
|
||||||
class Pipe
|
class Pipe
|
||||||
|
include Hydra::MessagingIO
|
||||||
# Creates a new uninitialized pipe pair.
|
# Creates a new uninitialized pipe pair.
|
||||||
def initialize
|
def initialize
|
||||||
@child_read, @parent_write = IO.pipe
|
@child_read, @parent_write = IO.pipe
|
||||||
@parent_read, @child_write = IO.pipe
|
@parent_read, @child_write = IO.pipe
|
||||||
end
|
end
|
||||||
|
|
||||||
# Read a line from a pipe. It will have a trailing newline.
|
|
||||||
def gets
|
|
||||||
force_identification
|
|
||||||
@reader.gets.chomp
|
|
||||||
end
|
|
||||||
|
|
||||||
# Write a line to a pipe. It must have a trailing newline.
|
|
||||||
def write(str)
|
|
||||||
force_identification
|
|
||||||
unless str =~ /\n$/
|
|
||||||
str += "\n"
|
|
||||||
end
|
|
||||||
begin
|
|
||||||
@writer.write(str)
|
|
||||||
return str
|
|
||||||
rescue Errno::EPIPE
|
|
||||||
raise Hydra::PipeError::Broken
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Identify this side of the pipe as the child.
|
# Identify this side of the pipe as the child.
|
||||||
def identify_as_child
|
def identify_as_child
|
||||||
@parent_write.close
|
@parent_write.close
|
||||||
|
@ -65,43 +47,5 @@ module Hydra #:nodoc:
|
||||||
@reader = @parent_read
|
@reader = @parent_read
|
||||||
@writer = @parent_write
|
@writer = @parent_write
|
||||||
end
|
end
|
||||||
|
|
||||||
# closes the pipes. Once a pipe is closed on one end, the other
|
|
||||||
# end will get a PipeError::Broken if it tries to write.
|
|
||||||
def close
|
|
||||||
done_reading
|
|
||||||
done_writing
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def done_writing #:nodoc:
|
|
||||||
@writer.close unless @writer.closed?
|
|
||||||
end
|
|
||||||
|
|
||||||
def done_reading #:nodoc:
|
|
||||||
@reader.close unless @reader.closed?
|
|
||||||
end
|
|
||||||
|
|
||||||
def force_identification #:nodoc:
|
|
||||||
raise PipeError::Unidentified if @reader.nil? or @writer.nil?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module PipeError #:nodoc:
|
|
||||||
# Raised if you try to read or write to a pipe when it is unidentified.
|
|
||||||
# Use identify_as_parent and identify_as_child to identify a pipe.
|
|
||||||
class Unidentified < RuntimeError
|
|
||||||
def message #:nodoc:
|
|
||||||
"Must identify as child or parent"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
# Raised when a pipe has been broken between two processes.
|
|
||||||
# This happens when a process exits, and is a signal that
|
|
||||||
# there is no more data to communicate.
|
|
||||||
class Broken < RuntimeError
|
|
||||||
def message #:nodoc:
|
|
||||||
"Other side closed the connection"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
require 'open3'
|
require 'open3'
|
||||||
|
require 'hydra/io'
|
||||||
module Hydra #:nodoc:
|
module Hydra #:nodoc:
|
||||||
# Read and write with an ssh connection. For example:
|
# Read and write with an ssh connection. For example:
|
||||||
# @ssh = Hydra::SSH.new('nick@nite')
|
# @ssh = Hydra::SSH.new('nick@nite')
|
||||||
|
@ -17,6 +18,7 @@ module Hydra #:nodoc:
|
||||||
# => "8" # the output from irb
|
# => "8" # the output from irb
|
||||||
class SSH
|
class SSH
|
||||||
include Open3
|
include Open3
|
||||||
|
include Hydra::MessagingIO
|
||||||
|
|
||||||
# Initialize new SSH connection. The single parameters is passed
|
# Initialize new SSH connection. The single parameters is passed
|
||||||
# directly to ssh for starting a connection. So you can do:
|
# directly to ssh for starting a connection. So you can do:
|
||||||
|
@ -25,24 +27,7 @@ module Hydra #:nodoc:
|
||||||
# Hydra::SSH.new('-p 3022 user@server.com')
|
# Hydra::SSH.new('-p 3022 user@server.com')
|
||||||
# etc..
|
# etc..
|
||||||
def initialize(connection_options)
|
def initialize(connection_options)
|
||||||
@stdin, @stdout, @stderr = popen3("ssh #{connection_options}")
|
@writer, @reader, @error = popen3("ssh #{connection_options}")
|
||||||
end
|
|
||||||
|
|
||||||
# Write a string to ssh. This method returns the string passed to
|
|
||||||
# ssh. Note that if you do not add a newline at the end, it adds
|
|
||||||
# one for you, and the modified string is returned
|
|
||||||
def write(str)
|
|
||||||
unless str =~ /\n$/
|
|
||||||
str += "\n"
|
|
||||||
end
|
|
||||||
@stdin.write(str)
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
|
|
||||||
# Read a line from ssh. This call blocks when there is nothing
|
|
||||||
# to read.
|
|
||||||
def gets
|
|
||||||
@stdout.gets.chomp
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,3 +8,18 @@ require 'hydra'
|
||||||
|
|
||||||
class Test::Unit::TestCase
|
class Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module Hydra #:nodoc:
|
||||||
|
module Messages #:nodoc:
|
||||||
|
class TestMessage < Hydra::Message
|
||||||
|
attr_accessor :text
|
||||||
|
def initialize(opts = {})
|
||||||
|
@text = opts.fetch(:text){ "test" }
|
||||||
|
end
|
||||||
|
def serialize
|
||||||
|
super(:text => @text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
|
@ -6,39 +6,29 @@ class TestPipe < Test::Unit::TestCase
|
||||||
@pipe = Hydra::Pipe.new
|
@pipe = Hydra::Pipe.new
|
||||||
end
|
end
|
||||||
should "be able to write messages" do
|
should "be able to write messages" do
|
||||||
Process.fork do
|
child = Process.fork do
|
||||||
@pipe.identify_as_child
|
@pipe.identify_as_child
|
||||||
assert_equal "Test Message", @pipe.gets
|
assert_equal "Test Message", @pipe.gets.text
|
||||||
@pipe.write "Message Received"
|
@pipe.write Hydra::Messages::TestMessage.new(:text => "Message Received")
|
||||||
@pipe.write "Second Message"
|
@pipe.write Hydra::Messages::TestMessage.new(:text => "Second Message")
|
||||||
@pipe.close
|
|
||||||
end
|
end
|
||||||
@pipe.identify_as_parent
|
@pipe.identify_as_parent
|
||||||
@pipe.write "Test Message"
|
@pipe.write Hydra::Messages::TestMessage.new(:text => "Test Message")
|
||||||
assert_equal "Message Received", @pipe.gets
|
assert_equal "Message Received", @pipe.gets.text
|
||||||
assert_equal "Second Message", @pipe.gets
|
assert_equal "Second Message", @pipe.gets.text
|
||||||
assert_raise Hydra::PipeError::Broken do
|
assert_raise IOError do
|
||||||
@pipe.write "anybody home?"
|
@pipe.write Hydra::Messages::TestMessage.new(:text => "anyone there?")
|
||||||
end
|
end
|
||||||
@pipe.close
|
|
||||||
end
|
end
|
||||||
should "not allow writing if unidentified" do
|
should "not allow writing if unidentified" do
|
||||||
assert_raise Hydra::PipeError::Unidentified do
|
assert_raise IOError do
|
||||||
@pipe.write "hey\n"
|
@pipe.write Hydra::Messages::TestMessage.new(:text => "Test Message")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
should "not allow reading if unidentified" do
|
should "not allow reading if unidentified" do
|
||||||
assert_raise Hydra::PipeError::Unidentified do
|
assert_raise IOError do
|
||||||
@pipe.gets
|
@pipe.gets
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
should "handle newlines" do
|
|
||||||
Process.fork do
|
|
||||||
@pipe.identify_as_child
|
|
||||||
@pipe.write "Message\n"
|
|
||||||
end
|
|
||||||
@pipe.identify_as_parent
|
|
||||||
assert_equal "Message", @pipe.gets
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,7 +14,9 @@ class TestRunner < Test::Unit::TestCase
|
||||||
@message = Hydra::Message.build(@pipe.gets)
|
@message = Hydra::Message.build(@pipe.gets)
|
||||||
assert @message.is_a?(Hydra::Messages::RunnerRequestsFile)
|
assert @message.is_a?(Hydra::Messages::RunnerRequestsFile)
|
||||||
end
|
end
|
||||||
should "return a result message after processing a file"
|
should "return a result message after processing a file" do
|
||||||
|
|
||||||
|
end
|
||||||
should "terminate when sent a shutdown message"
|
should "terminate when sent a shutdown message"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue