ClusterLabs/hawk

View on GitHub
hawk/app/lib/hb_report.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# Copyright (c) 2009-2015 Tim Serong <tserong@suse.com>
# See COPYING for license.

class HbReport
  # Note: outfile, errfile are based off path passed to generate() -
  # don't use them prior to a generate run, or you'll get the wrong path.
  # Lastexit is global (this is freaky/dumb - everything should be based
  # off path, and callers need to be updated to understand this - this
  # will happen when we allow multiple hb_report runs, as
  # hb_reports_controller is what cares about lastexit)
  attr_reader :path
  attr_reader :outfile
  attr_reader :errfile
  attr_reader :lastexit

  def initialize(name = nil)
    tmpbase = Rails.root.join('tmp', 'pids')
    reports = Rails.root.join('tmp', 'reports')
    tmpbase.mkpath unless tmpbase.directory?
    reports.mkpath unless reports.directory?

    @pidfile = tmpbase.join("report.pid").to_s
    @exitfile = tmpbase.join("report.exit").to_s
    @timefile = tmpbase.join("report.time").to_s
    if name
      @path = reports.join(name).to_s
      @outfile = reports.join("#{name}.stdout").to_s
      @errfile = reports.join("#{name}.stderr").to_s
    else
      @path = nil
      @outfile = tmpbase.join("report.stdout").to_s
      @errfile = tmpbase.join("report.stderr").to_s
    end
    @lastexit = File.exist?(@exitfile) ? File.new(@exitfile).read.to_i : nil
  end

  def running?
    Util.child_active(@pidfile)
  end

  def cancel!
    pid = File.new(@pidfile).read.to_i
    return 0 if pid <= 0
    Process.detach(pid)
    Process.kill("TERM", pid)
    pid
  rescue Errno::ENOENT, Errno::ESRCH, Errno::EINVAL
    0
  end

  # Returns [from_time, to_time], as strings.  Note that to_time might be
  # an empty string, if no to_time was specified when calling generate.
  def lasttime
    File.exist?(@timefile) ? File.new(@timefile).read.split(",", -1) : nil
  end

  # contents of errfile as array
  def err_lines
    err = []
    begin
      File.new(@errfile).read.split(/\n/).each do |e|
        next if e.empty?
        err << e
      end if File.exist?(@errfile)
    rescue ArgumentError => e
      # This will catch 'invalid byte sequence in UTF-8' (bnc#854060)
      err << "ArgumentError: #{e.message}"
    end
    err
  end

  # contents of errfile as array, with "INFO" lines stripped (e.g. for
  # displaying warnings after an otherwise successful run)
  def err_filtered
    err_lines.select do |e|
      !e.match(/( INFO: |(cat|tail): write error)/) && !e.match(/^tar:.*time stamp/)
    end
  end

  # Note: This assumes pidfile doesn't exist (will always blow away what's
  # there), so there's a possibility of a race (or lost hb_report status)
  # if two clients kick off generation at almost exactly the same time.
  # from_time and to_time (if specified) are expected to be in a sensible
  # format (e.g.: iso8601)
  def generate(from_time, to_time, all_nodes = true)
    [@outfile, @errfile, @exitfile, @timefile].each do |fn|
      File.unlink(fn) if File.exist?(fn)
    end
    @lastexit = nil

    f = File.new(@timefile, "w")
    f.write("#{from_time},#{to_time}")
    f.close
    pid = fork do
      args = ["-f", from_time]
      args.push("-t", to_time) if to_time
      args.push("-Z") # Remove destination directories if they exist
      args.push("-Q") # Requires a version of crm report which supports this
      args.push("-S") unless all_nodes
      args.push(@path)
      out, err, status = Util.capture3('crm', "report", *args)
      f = File.new(@outfile, "w")
      f.write(out)
      f.close
      f = File.new(@errfile, "w")
      f.write(err)
      f.close

      # Record exit status
      f = File.new(@exitfile, "w")
      f.write(status.exitstatus)
      f.close

      # Delete pidfile
      File.unlink(@pidfile)
    end
    f = File.new(@pidfile, "w")
    f.write(pid)
    f.close
    Process.detach(pid)
  end
end