class DTK::Agent::Command

This is container for command as received from Node Agent

Constants

STDOUT_REDIRECT

command - string to be run on system, e.g. ifconfig type - type of command e.g. syscall, ruby if - callback to be run if exit status is = 0 unless - callback to be run if exit status is != 0 stdout_redirect - redirect all output to stdout

STREAM_TIMEOUT

Attributes

backtrace[RW]
command[RW]
command_type[RW]
process[RW]

Public Class Methods

new(value_hash) click to toggle source
# File lib/command.rb, line 28
def initialize(value_hash)
  @command_type    = value_hash['type']
  @command         = value_hash['command']
  @stdout_redirect = !!value_hash['stdout_redirect']

  @if              = value_hash['if']
  @unless          = value_hash['unless']

  @timeout         = (value_hash['timeout'] || 0).to_i

  @env_vars        = value_hash['env_vars']

  if @if && @unless
    Log.warn "Unexpected case, both if/unless conditions have been set for command #{@command}(#{@command_type})"
  end
end

Public Instance Methods

callback_pending?() click to toggle source

Checks if there is callaback present, callback beeing if/unless command

# File lib/command.rb, line 73
def callback_pending?
  @if || @unless
end
err() click to toggle source
# File lib/command.rb, line 126
def err
  return @error_message if @error_message
  @err.encode!('UTF-8', :invalid => :replace, :undef => :replace, :replace => '')
end
exited?() click to toggle source
# File lib/command.rb, line 106
def exited?
  return true if @error_message
  @process_status.exited?
end
exitstatus() click to toggle source
# File lib/command.rb, line 116
def exitstatus
  return 1 if @error_message
  @process_status.exitstatus
end
is_positioning?() click to toggle source
# File lib/command.rb, line 77
def is_positioning?
  'file'.eql?(@command_type)
end
out() click to toggle source
# File lib/command.rb, line 121
def out
  return '' if @error_message
  @out.encode('UTF-8', :invalid => :replace, :undef => :replace, :replace => '')
end
run_condition_task() click to toggle source

Returns true/false based on condition data and result of process

# File lib/command.rb, line 84
def run_condition_task
  condition_command   = @if
  condition_command ||= @unless

  # this is needed since Timeout block will not allow initialization of new variables
  condition_process_status = nil

  begin
    Timeout.timeout(@timeout) do
      _out, _err, condition_process_status = Open3.capture3(condition_command)
    end
  rescue Exception => e
    # do not log error in cases it was expected. Meaning that 'unless' condition was set.
    Log.warn("Condition command '#{condition_command}' ran into an exception, message: #{e.message}") unless @unless
    # return true if unless condition was used
    return @unless ? true : false
  end

  return condition_process_status.exitstatus > 0 ? false : true if @if
  return condition_process_status.exitstatus > 0 ? true  : false if @unless
end
start_task() click to toggle source

Creates Posix Spawn of given process

# File lib/command.rb, line 48
def start_task
  begin
    Commander.set_environment_variables(@env_vars)


    results = capture3_with_timeout(formulate_command)

    @out = results[:stdout]
    @err = results[:stderr]
    @process_status = results[:status]

    @error_message = "Timeout (#{@timeout} sec) for this action has been exceeded" if results[:timeout]

  rescue Exception => e
    @error_message = e.message
    @backtrace = e.backtrace
    Log.error(@error_message, @backtrace)
  ensure
    Commander.clear_environment_variables(@env_vars)
  end
end
started?() click to toggle source
# File lib/command.rb, line 111
def started?
  return true if @error_message
  !!@process_status
end
to_s() click to toggle source
# File lib/command.rb, line 131
def to_s
  "#{formulate_command} (#{command_type})"
end

Private Instance Methods

capture3_with_timeout(*cmd) click to toggle source

Open3 method extended with timeout, more info gist.github.com/pasela/9392115

# File lib/command.rb, line 141
def capture3_with_timeout(*cmd)
  spawn_opts = Hash === cmd.last ? cmd.pop.dup : {}
  opts = {
    :stdin_data => "",
    :timeout    => @timeout,
    :signal     => :TERM,
    :kill_after => nil,
  }

  in_r,  in_w  = IO.pipe
  out_r, out_w = IO.pipe
  err_r, err_w = IO.pipe
  in_w.sync = true

  spawn_opts[:in]  = in_r
  spawn_opts[:out] = out_w
  spawn_opts[:err] = err_w

  result = {
    :pid     => nil,
    :status  => nil,
    :stdout  => nil,
    :stderr  => nil,
    :timeout => false,
  }

  out_reader = nil
  err_reader = nil
  wait_thr = nil

  begin
    Timeout.timeout(opts[:timeout]) do
      result[:pid] = spawn(*cmd, spawn_opts)
      wait_thr = Process.detach(result[:pid])
      in_r.close
      out_w.close
      err_w.close

      out_reader = Thread.new { out_r.read }
      err_reader = Thread.new { err_r.read }

      in_w.close

      result[:status] = wait_thr.value
    end
  rescue Timeout::Error
    result[:timeout] = true
    pid = result[:pid]
    Process.kill(opts[:signal], pid)
    if opts[:kill_after]
      unless wait_thr.join(opts[:kill_after])
        Process.kill(:KILL, pid)
      end
    end
  ensure
    result[:status] = wait_thr.value if wait_thr
    begin
      # there is a bug where there is infinite leg on out_reader (e.g. hohup) commands
      Timeout.timeout(STREAM_TIMEOUT) do
        result[:stdout] = out_reader.value if out_reader
        result[:stderr] = err_reader.value if err_reader
      end
    rescue Timeout::Error
      result[:stdout] ||= ''
      result[:stderr] ||= ''
    end
    out_r.close unless out_r.closed?
    err_r.close unless err_r.closed?
  end

  result
end
formulate_command() click to toggle source

Based on stdout-redirect flag

# File lib/command.rb, line 217
def formulate_command
  @command
end