diff --git a/app/models/manageiq/providers/ansible_playbook_workflow.rb b/app/models/manageiq/providers/ansible_playbook_workflow.rb new file mode 100644 index 000000000000..beabef8726cb --- /dev/null +++ b/app/models/manageiq/providers/ansible_playbook_workflow.rb @@ -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 diff --git a/app/models/manageiq/providers/ansible_role_workflow.rb b/app/models/manageiq/providers/ansible_role_workflow.rb new file mode 100644 index 000000000000..79b40f3a219c --- /dev/null +++ b/app/models/manageiq/providers/ansible_role_workflow.rb @@ -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 diff --git a/app/models/manageiq/providers/ansible_runner_workflow.rb b/app/models/manageiq/providers/ansible_runner_workflow.rb index f5ddf6ab84e7..4920d3f267c8 100644 --- a/app/models/manageiq/providers/ansible_runner_workflow.rb +++ b/app/models/manageiq/providers/ansible_runner_workflow.rb @@ -1,36 +1,10 @@ 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) - 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 @@ -63,8 +37,12 @@ def post_playbook queue_signal(:finish) 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 @@ -99,8 +77,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'}, diff --git a/spec/models/manageiq/providers/ansible_runner_workflow_spec.rb b/spec/models/manageiq/providers/ansible_runner_workflow_spec.rb deleted file mode 100644 index 3fa0860bf329..000000000000 --- a/spec/models/manageiq/providers/ansible_runner_workflow_spec.rb +++ /dev/null @@ -1,166 +0,0 @@ -describe ManageIQ::Providers::AnsibleRunnerWorkflow do - let(:job) { described_class.create_job(*options).tap { |job| job.state = state } } - let(:options) { [{"ENV" => "VAR"}, %w(arg1 arg2), "/path/to/playbook"] } - 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_playbook run_playbook 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_playbook run_playbook 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_playbook signal" - it_behaves_like "doesn't allow poll_runner signal" - it_behaves_like "doesn't allow post_playbook signal" - end - - context "pre_playbook" do - let(:state) { "pre_playbook" } - - it_behaves_like "allows run_playbook 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_playbook 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_playbook signal" - it_behaves_like "doesn't allow run_playbook signal" - it_behaves_like "doesn't allow poll_runner signal" - it_behaves_like "doesn't allow post_playbook signal" - end - end - - context ".run_playbook" do - let(:state) { "pre_playbook" } - 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_async).and_return(response_async) - expect(job).to receive(:queue_signal).with(:poll_runner) - - job.signal(:run_playbook) - - expect(job.context[:ansible_runner_response]).to eq(response_async.dump) - end - - it "ansible-runner fails" do - expect(Ansible::Runner).to receive(:run_async).and_return(nil) - expect(job).to receive(:queue_signal).with(:abort, "Failed to run ansible playbook", "error") - - job.signal(:run_playbook) - 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), "/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