hawk/app/controllers/simulator_controller.rb
# Copyright (c) 2009-2015 Tim Serong <tserong@suse.com>
# See COPYING for license.
class SimulatorController < ApplicationController
before_action :login_required
before_action :set_title
before_action :set_cib
def run
if current_cib.id == "live"
head :bad_request
return
end
# TODO: cache simulator runs
# always reset status before run (so we effectively run from current
# state of cluster, not state as saved back to shadow cib)
sim_reload_state
injections = []
params[:injections].each do |i|
parts = i.split(/\s+/)
case parts[0]
when "node"
case parts[2]
when "online"
injections << "-u" << parts[1]
when "offline"
injections << "-d" << parts[1]
when "unclean"
injections << "-f" << parts[1]
end
when "op"
# we have something like:
# "op monitor:0 stonith-sbd success node-0"
parts[1].sub!(":", "_")
injections << "-i" << "#{parts[2]}_#{parts[1]}@#{parts[4]}=#{parts[3]}"
when "ticket"
if Util.has_feature?(:sim_ticket)
case parts[2]
when "grant"
injections << "-g" << parts[1]
when "revoke"
injections << "-r" << parts[1]
when "standby"
injections << "-b" << parts[1]
when "activate"
injections << "-e" << parts[1]
end
end
end
end if params[:injections]
f = File.new("#{Rails.root}/tmp/sim.info", "w")
# "live", but will be against shadow CIB
out, err, status = Invoker.instance.crm_simulate(
"-S", "-L",
"-G", "#{Rails.root}/tmp/sim.graph",
"-D", "#{Rails.root}/tmp/sim.dot",
*injections)
# FIXME: Return value of crm_simulate is
# completely unpredictable and nonsensical,
# so only presume it's an error if there's
# actually output on stderr.
if status != 0 && !err.blank?
render json: { error: err }, status: 500
return
end
f.write(out)
f.close
is_empty = true
begin
f = File.open("#{Rails.root}/tmp/sim.graph")
if f.readline().match(/^<transition_graph.*[^\/]>$/)
# Cheap test - if the first line is a non-closed transition_graph element,
# we know it's not an empty graph.
is_empty = false
end
f.close
rescue Exception
# TODO(could): actually handle potential failure of crm_simulate run
render json: { error: "Could not read graph" }, status: 500
end
render json: { is_empty: is_empty }
end
# TODO(must): make sure dot is installed
def result
case params[:file]
when "info"
send_data File.new("#{Rails.root}/tmp/sim.info").read,
type: "text/plain", disposition: :inline
when "in"
shadow_id = ENV["CIB_shadow"]
begin
ENV.delete("CIB_shadow")
send_data Invoker.instance.cibadmin('-Ql'), type: (params[:munge] == "txt" ? "text/plain" : "text/xml"), disposition: :inline
ensure
ENV["CIB_shadow"] = shadow_id
end
when "out"
send_data Invoker.instance.cibadmin('-Ql'), type: (params[:munge] == "txt" ? "text/plain" : "text/xml"), disposition: :inline
when "graph-xml"
send_data File.new("#{Rails.root}/tmp/sim.graph").read, type: (params[:munge] == "txt" ? "text/plain" : "text/xml"), disposition: :inline
when "diff"
shadow_id = ENV["CIB_shadow"]
begin
ENV.delete("CIB_shadow")
base_configuration, err, rc = Invoker.instance.crm_configure("show")
ensure
ENV["CIB_shadow"] = shadow_id
end
if rc != 0
render json: { error: err }, status: 500
return
end
shadow_configuration, err, rc = Invoker.instance.crm_configure("show")
if rc != 0
render json: { error: err }, status: 500
return
end
cibdiff, ok = Util.diff(base_configuration.gsub(/\s+\\$\s+/, ' '), shadow_configuration.gsub(/\s+\\$\s+/, ' '))
if ok
send_data cibdiff, type: "text/plain", disposition: :inline
else
render json: { error: err }, status: 500
end
when "graph"
svg, err, status = Util.capture3("/usr/bin/dot", "-Tsvg", "#{Rails.root}/tmp/sim.dot")
if status != 0
render json: { error: err }, status: 500
else
send_data svg, type: :svg, disposition: :inline
end
else
head :not_found
end
end
# Bit of a hack, used only by simulator to get valid intervals
# for the monitor op in milliseconds. Returns an array of
# possible intervals (zero elements if no monitor op defined,
# one element in the general case, but should be two for m/s
# resources, or more if there's depths etc.).
def intervals
intervals = []
res = Primitive.find params[:id] # RORSCAN_ITL (authz via cibadmin)
Rails.logger.debug "#{res.ops}"
res.ops["monitor"].each do |op|
Rails.logger.debug "#{params[:id]}, #{op}"
intervals << Util.crm_get_msec(op["interval"])
end if res.ops.key?("monitor")
render json: intervals
end
def help
render
end
protected
def set_title
@title = _("Simulator")
end
def set_cib
@cib = current_cib
end
def default_base_layout
if ["help"].include? params[:action]
"modal"
else
super
end
end
def sim_reload_state
require "tempfile"
shadow_id = ENV["CIB_shadow"]
ENV.delete("CIB_shadow")
begin
tmpfile = Tempfile.new("cib_state")
tmpfile.write(Invoker.instance.cibadmin('-Ql', '--xpath', '//status'))
tmpfile.close
File.chmod(0666, tmpfile.path)
ENV["CIB_shadow"] = shadow_id
# TODO(must): Handle error here
Rails.logger.debug("CIB_shadow: #{shadow_id}")
Invoker.instance.cibadmin('--replace', '--xml-file', tmpfile.path)
ensure
tmpfile.unlink
end
end
end