Skip to content

Commit

Permalink
Add support for using run_role_async
Browse files Browse the repository at this point in the history
Add the option to use the run_role_async using AnsibleWorkflow
  • Loading branch information
Boris Odnopozov committed Oct 29, 2018
1 parent 84a80a0 commit 7af794d
Show file tree
Hide file tree
Showing 5 changed files with 281 additions and 36 deletions.
44 changes: 44 additions & 0 deletions app/models/manageiq/providers/ansible_playbook_workflow.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
class ManageIQ::Providers::AnsiblePlaybookWorkflow < ManageIQ::Providers::AnsibleRunnerWorkflow
def self.job_options(env_vars, extra_vars, playbook_options, timeout, poll_interval)
{
:env_vars => env_vars,
:extra_vars => extra_vars,
:playbook_path => playbook_options[:playbook_path],
:timeout => timeout,
:poll_interval => poll_interval,
}
end

def pre_playbook
# A step before running the playbook for any optional setup tasks
queue_signal(:run_playbook)
end

def run_playbook
env_vars, extra_vars, playbook_path = options.values_at(:env_vars, :extra_vars, :playbook_path)

response = Ansible::Runner.run_async(env_vars, extra_vars, playbook_path)
if response.nil?
queue_signal(:abort, "Failed to run ansible playbook", "error")
else
context[:ansible_runner_response] = response.dump

started_on = Time.now.utc
update_attributes!(:context => context, :started_on => started_on)
miq_task.update_attributes!(:started_on => started_on)

queue_signal(:poll_runner)
end
end

def load_transitions
super.tap do |transactions|
transactions.merge!(
:start => {'waiting_to_start' => 'pre_playbook'},
:run_playbook => {'pre_playbook' => 'running'},
)
end
end

alias start pre_playbook
end
46 changes: 46 additions & 0 deletions app/models/manageiq/providers/ansible_role_workflow.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
class ManageIQ::Providers::AnsibleRoleWorkflow < ManageIQ::Providers::AnsibleRunnerWorkflow
def self.job_options(env_vars, extra_vars, role_options, timeout, poll_interval)
{
:env_vars => env_vars,
:extra_vars => extra_vars,
:role_name => role_options[:role_name],
:roles_path => role_options[:roles_path],
:role_skip_facts => role_options[:role_skip_facts],
:timeout => timeout,
:poll_interval => poll_interval
}
end

def pre_role
# A step before running the playbook for any optional setup tasks
queue_signal(:run_role)
end

def run_role
env_vars, extra_vars, role_name, roles_path, role_skip_facts = options.values_at(:env_vars, :extra_vars, :role_name, :roles_path, :role_skip_facts)
role_skip_facts = true if role_skip_facts.nil?
response = Ansible::Runner.run_role_async(env_vars, extra_vars, role_name, :roles_path => roles_path, :role_skip_facts => role_skip_facts)
if response.nil?
queue_signal(:abort, "Failed to run ansible role", "error")
else
context[:ansible_runner_response] = response.dump

started_on = Time.now.utc
update_attributes!(:context => context, :started_on => started_on)
miq_task.update_attributes!(:started_on => started_on)

queue_signal(:poll_runner)
end
end

def load_transitions
super.tap do |transactions|
transactions.merge!(
:start => {'waiting_to_start' => 'pre_role'},
:run_role => {'pre_role' => 'running' },
)
end
end

alias start pre_role
end
47 changes: 14 additions & 33 deletions app/models/manageiq/providers/ansible_runner_workflow.rb
Original file line number Diff line number Diff line change
@@ -1,36 +1,14 @@
class ManageIQ::Providers::AnsibleRunnerWorkflow < Job
def self.create_job(env_vars, extra_vars, playbook_path, timeout: 1.hour, poll_interval: 1.second)
options = {
:env_vars => env_vars,
:extra_vars => extra_vars,
:playbook_path => playbook_path,
:timeout => timeout,
:poll_interval => poll_interval,
}

super(name, options)
def self.create_job(env_vars, extra_vars, role_or_playbook_options, timeout: 1.hour, poll_interval: 1.second)
super(name, job_options(env_vars, extra_vars, role_or_playbook_options, timeout, poll_interval))
end

def pre_playbook
# A step before running the playbook for any optional setup tasks
queue_signal(:run_playbook)
def current_job_timeout(_timeout_adjustment = 1)
options[:timeout] || super
end

def run_playbook
env_vars, extra_vars, playbook_path = options.values_at(:env_vars, :extra_vars, :playbook_path)

response = Ansible::Runner.run_async(env_vars, extra_vars, playbook_path)
if response.nil?
queue_signal(:abort, "Failed to run ansible playbook", "error")
else
context[:ansible_runner_response] = response.dump

started_on = Time.now.utc
update_attributes!(:context => context, :started_on => started_on)
miq_task.update_attributes!(:started_on => started_on)

queue_signal(:poll_runner)
end
def job_options(options)
raise(NotImplementedError, 'abstract')
end

def poll_runner
Expand All @@ -52,19 +30,24 @@ def poll_runner
if result.return_code != 0
set_status("Playbook failed", "error")
_log.warn("Playbook failed:\n#{result.parsed_stdout.join("\n")}")
else
set_status("Playbook completed with no errors", "ok")
end

queue_signal(:post_playbook)
end
end

def post_playbook
# A step after running the playbook for any optional cleanup tasks
queue_signal(:finish)
queue_signal(:finish, message, status)
end

def fail_unimplamented
raise(NotImplementedError, "this is an abstract class, use a subclass that implaments a 'start' method")
end

alias initializing dispatch_start
alias start pre_playbook
alias start fail_unimplamented
alias finish process_finished
alias abort_job process_abort
alias cancel process_cancel
Expand Down Expand Up @@ -99,8 +82,6 @@ def load_transitions

{
:initializing => {'initialize' => 'waiting_to_start'},
:start => {'waiting_to_start' => 'pre_playbook'},
:run_playbook => {'pre_playbook' => 'running'},
:poll_runner => {'running' => 'running'},
:post_playbook => {'running' => 'post_playbook'},
:finish => {'*' => 'finished'},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
describe ManageIQ::Providers::AnsibleRunnerWorkflow do
describe ManageIQ::Providers::AnsiblePlaybookWorkflow do
let(:job) { described_class.create_job(*options).tap { |job| job.state = state } }
let(:options) { [{"ENV" => "VAR"}, %w(arg1 arg2), "/path/to/playbook"] }
let(:options) { [{"ENV" => "VAR"}, %w(arg1 arg2), {:playbook_path => "/path/to/playbook"}] }
let(:state) { "waiting_to_start" }

context ".create_job" do
Expand Down Expand Up @@ -151,7 +151,7 @@
end

context ".deliver_on" do
let(:options) { [{"ENV" => "VAR"}, %w(arg1 arg2), "/path/to/playbook", :poll_interval => 5.minutes] }
let(:options) { [{"ENV" => "VAR"}, %w(arg1 arg2), {:playbook_path => "/path/to/playbook"}, :poll_interval => 5.minutes] }

it "uses the option to queue poll_runner" do
now = Time.now.utc
Expand Down
174 changes: 174 additions & 0 deletions spec/models/manageiq/providers/ansible_role_workflow_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
describe ManageIQ::Providers::AnsibleRoleWorkflow do
let(:job) { described_class.create_job(*options).tap { |job| job.state = state } }
let(:role_options) { {:role_name => 'role_name', :roles_path => 'path/role', :role_skip_facts => true } }
let(:options) { [{"ENV" => "VAR"}, %w(arg1 arg2), role_options] }
let(:state) { "waiting_to_start" }

context ".create_job" do
it "leaves job waiting to start" do
expect(job.state).to eq("waiting_to_start")
end
end

context ".signal" do
%w(start pre_role run_role poll_runner post_playbook finish abort_job cancel error).each do |signal|
shared_examples_for "allows #{signal} signal" do
it signal.to_s do
expect(job).to receive(signal.to_sym)
job.signal(signal.to_sym)
end
end
end

%w(start pre_role run_role poll_runner post_playbook).each do |signal|
shared_examples_for "doesn't allow #{signal} signal" do
it signal.to_s do
expect { job.signal(signal.to_sym) }.to raise_error(RuntimeError, /#{signal} is not permitted at state #{job.state}/)
end
end
end

context "waiting_to_start" do
let(:state) { "waiting_to_start" }

it_behaves_like "allows start signal"
it_behaves_like "allows finish signal"
it_behaves_like "allows abort_job signal"
it_behaves_like "allows cancel signal"
it_behaves_like "allows error signal"

it_behaves_like "doesn't allow run_role signal"
it_behaves_like "doesn't allow poll_runner signal"
it_behaves_like "doesn't allow post_playbook signal"
end

context "per_role" do
let(:state) { "pre_role" }

it_behaves_like "allows run_role signal"
it_behaves_like "allows finish signal"
it_behaves_like "allows abort_job signal"
it_behaves_like "allows cancel signal"
it_behaves_like "allows error signal"

it_behaves_like "doesn't allow start signal"
it_behaves_like "doesn't allow poll_runner signal"
it_behaves_like "doesn't allow post_playbook signal"
end

context "running" do
let(:state) { "running" }

it_behaves_like "allows poll_runner signal"
it_behaves_like "allows finish signal"
it_behaves_like "allows abort_job signal"
it_behaves_like "allows cancel signal"
it_behaves_like "allows error signal"

it_behaves_like "doesn't allow start signal"
it_behaves_like "doesn't allow pre_role signal"
end

context "post_playbook" do
let(:state) { "post_playbook" }

it_behaves_like "allows finish signal"
it_behaves_like "allows abort_job signal"
it_behaves_like "allows cancel signal"
it_behaves_like "allows error signal"

it_behaves_like "doesn't allow start signal"
it_behaves_like "doesn't allow pre_role signal"
it_behaves_like "doesn't allow run_role signal"
it_behaves_like "doesn't allow poll_runner signal"
it_behaves_like "doesn't allow post_playbook signal"
end
end

context ".run_role" do
let(:state) { "pre_role" }
let(:response_async) { Ansible::Runner::ResponseAsync.new(:base_dir => "/path/to/results") }

it "ansible-runner succeeds" do
response_async = Ansible::Runner::ResponseAsync.new(:base_dir => "/path/to/results")

expect(Ansible::Runner).to receive(:run_role_async)
.with({"ENV"=>"VAR"}, %w(arg1 arg2), "role_name", :roles_path => "path/role", :role_skip_facts => true).and_return(response_async)
expect(job).to receive(:queue_signal).with(:poll_runner)

job.signal(:run_role)

expect(job.context[:ansible_runner_response]).to eq(response_async.dump)
end

it "ansible-runner fails" do
expect(Ansible::Runner).to receive(:run_role_async).and_return(nil)
expect(job).to receive(:queue_signal).with(:abort, "Failed to run ansible role", "error")

job.signal(:run_role)
end
end

context "#current_job_timeout" do
it "sets the job current timeout" do
expect(job.current_job_timeout).to eq(1.hour)
end
end

context ".poll_runner" do
let(:state) { "running" }
let(:response_async) { Ansible::Runner::ResponseAsync.new(:base_dir => "/path/to/results") }

before do
allow(Ansible::Runner::ResponseAsync).to receive(:new).and_return(response_async)

job.context[:ansible_runner_response] = response_async.dump
job.started_on = Time.now.utc
job.save!
end

it "ansible-runner completed" do
expect(response_async).to receive(:running?).and_return(false)

response = Ansible::Runner::Response.new(response_async.dump.merge(:return_code => 0))
expect(response_async).to receive(:response).and_return(response)
expect(job).to receive(:queue_signal).with(:post_playbook)

job.signal(:poll_runner)
end

it "ansible-runner still running" do
now = Time.now.utc
allow(Time).to receive(:now).and_return(now)
expect(response_async).to receive(:running?).and_return(true)
expect(job).to receive(:queue_signal).with(:poll_runner, :deliver_on => now + 1.second)

job.signal(:poll_runner)
end

it "fails if the playbook has been running too long" do
time = job.started_on + job.options[:timeout] + 5.minutes

Timecop.travel(time) do
expect(response_async).to receive(:running?).and_return(true)
expect(response_async).to receive(:stop)
expect(job).to receive(:queue_signal).with(:abort, "Playbook has been running longer than timeout", "error")

job.signal(:poll_runner)
end
end

context ".deliver_on" do
let(:options) { [{"ENV" => "VAR"}, %w(arg1 arg2), {:playbook_path => "/path/to/playbook"}, :poll_interval => 5.minutes] }

it "uses the option to queue poll_runner" do
now = Time.now.utc
allow(Time).to receive(:now).and_return(now)
expect(response_async).to receive(:running?).and_return(true)
expect(job).to receive(:queue_signal).with(:poll_runner, :deliver_on => now + 5.minutes)

job.signal(:poll_runner)
end
end
end
end

0 comments on commit 7af794d

Please sign in to comment.