1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-22 18:50:08 +03:00

feature #4716: add timeout to CommandManager

Add timeout parameter to Local and RemoteCommand. It also spawns new
processes in a new process group. This makes easier to kill all
children when timeout happens.

This change makes it incompatible with ruby < 1.9.2.
This commit is contained in:
Javi Fontan 2016-09-06 16:32:12 +02:00
parent 473db733b5
commit 954ac5362e

View File

@ -17,6 +17,7 @@
require 'pp'
require 'open3'
require 'stringio'
require 'timeout'
# Generic command executor that holds the code shared by all the command
# executors.
@ -45,8 +46,8 @@ class GenericCommand
attr_reader :code, :stdout, :stderr, :command
# Creates a command and runs it
def self.run(command, logger=nil, stdin=nil)
cmd = self.new(command, logger, stdin)
def self.run(command, logger=nil, stdin=nil, timeout=nil)
cmd = self.new(command, logger, stdin, timeout)
cmd.run
cmd
end
@ -54,10 +55,11 @@ class GenericCommand
# Creates the new command:
# +command+: string with the command to be executed
# +logger+: proc that takes a message parameter and logs it
def initialize(command, logger=nil, stdin=nil)
def initialize(command, logger=nil, stdin=nil, timeout=nil)
@command = command
@logger = logger
@stdin = stdin
@timeout = timeout
end
# Sends a log message to the logger proc
@ -65,31 +67,57 @@ class GenericCommand
@logger.call(message, all) if @logger
end
def kill(pid)
# executed processes now have its own process group to be able
# to kill all children
pgid = Process.getpgid(pid)
# Kill all processes belonging to process group
Process.kill("HUP", pgid * -1)
end
# Runs the command
def run
std = execute
std = nil
process = Proc.new do
std = execute
# Close standard IO descriptors
if @stdin
std[0] << @stdin
std[0].flush
# Close standard IO descriptors
if @stdin
std[0] << @stdin
std[0].flush
end
std[0].close if !std[0].closed?
@stdout=std[1].read
std[1].close if !std[1].closed?
@stderr=std[2].read
std[2].close if !std[2].closed?
@code=get_exit_code(@stderr)
if @code!=0
log("Command execution fail: #{command}")
log(@stderr)
end
end
std[0].close if !std[0].closed?
begin
if @timeout
Timeout.timeout(@timeout, nil, &process)
else
process.call
end
rescue Timeout::Error
log("Timeout executing #{command}")
@stdout = std[1].read
std[1].close if !std[1].closed?
3.times {|n| std[n].close if !std[n].closed? }
@stderr = std[2].read
std[2].close if !std[2].closed?
pid = std[-1].pid
self.kill(pid)
@code = get_exit_code(@stderr)
if @code != 0
log(@stderr)
log("Command execution fail: #{command}")
else
log(@stderr, false)
@code = 255
end
return @code
@ -127,7 +155,8 @@ class LocalCommand < GenericCommand
private
def execute
Open3.popen3("#{command} ; echo ExitCode: $? 1>&2")
Open3.popen3("#{command} ; echo ExitCode: $? 1>&2",
:pgroup => true)
end
end
@ -137,26 +166,28 @@ class SSHCommand < GenericCommand
attr_accessor :host
# Creates a command and runs it
def self.run(command, host, logger=nil, stdin=nil)
cmd=self.new(command, host, logger, stdin)
def self.run(command, host, logger=nil, stdin=nil, timeout=nil)
cmd=self.new(command, host, logger, stdin, timeout)
cmd.run
cmd
end
# This one takes another parameter. +host+ is the machine
# where the command is going to be executed
def initialize(command, host, logger=nil, stdin=nil)
def initialize(command, host, logger=nil, stdin=nil, timeout=nil)
@host=host
super(command, logger, stdin)
super(command, logger, stdin, timeout)
end
private
def execute
if @stdin
Open3.popen3("ssh #{@host} #{@command} ; echo ExitCode: $? 1>&2")
Open3.popen3("ssh #{@host} #{@command} ; echo ExitCode: $? 1>&2",
:pgroup => true)
else
Open3.popen3("ssh -n #{@host} #{@command} ; echo ExitCode: $? 1>&2")
Open3.popen3("ssh -n #{@host} #{@command} ; echo ExitCode: $? 1>&2",
:pgroup => true)
end
end
end
@ -164,13 +195,13 @@ end
class RemotesCommand < SSHCommand
# Creates a command and runs it
def self.run(command, host, remote_dir, logger=nil, stdin=nil, retries=0)
def self.run(command, host, remote_dir, logger=nil, stdin=nil, retries=0, timeout=nil)
cmd_file = command.split(' ')[0]
cmd_string = "'if [ -x \"#{cmd_file}\" ]; then #{command}; else\
exit #{MAGIC_RC}; fi'"
cmd = self.new(cmd_string, host, logger, stdin)
cmd = self.new(cmd_string, host, logger, stdin, timeout)
cmd.run
while cmd.code != 0 and retries != 0