Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
class ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Job < Job
def self.create_job(template, env_vars, input_vars, credentials, poll_interval: 1.minute)
super(
:template_id => template.id,
:env_vars => env_vars,
:input_vars => input_vars,
:credentials => credentials,
:poll_interval => poll_interval,
)
end

def start
queue_signal(:pre_execute)
end

def pre_execute
checkout_git_repository
signal(:execute)
end

def execute
template_path = File.join(options[:git_checkout_tempdir], template_relative_path)

response = Terraform::Runner.run(
options[:input_vars],
template_path,
:credentials => options[:credentials],
:env_vars => options[:env_vars]
)

options[:terraform_stack_id] = response.stack_id
save!

queue_poll_runner
end

def poll_runner
if running?
queue_poll_runner
else
signal(:post_execute)
end
end

def post_execute
cleanup_git_repository

success? ? queue_signal(:finish, message, status) : abort_job("Failed to run template", "error")
end

alias initializing dispatch_start
alias finish process_finished
alias abort_job process_abort
alias cancel process_cancel
alias error process_error

protected

def running?
stack_response&.running?
end

def success?
stack_response&.response&.status == "SUCCESS"
end

def load_transitions
self.state ||= 'initialize'

{
:initializing => {'initialize' => 'waiting_to_start'},
:start => {'waiting_to_start' => 'pre_execute'},
:pre_execute => {'pre_execute' => 'execute'},
:execute => {'execute' => 'running'},
:poll_runner => {'running' => 'running'},
:post_execute => {'running' => 'post_execute'},
:finish => {'*' => 'finished'},
:abort_job => {'*' => 'aborting'},
:cancel => {'*' => 'canceling'},
:error => {'*' => '*'}
}
end

def poll_interval
options.fetch(:poll_interval, 1.minute).to_i

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 minute seems like a long time. How long are we expecting short and long templates to take to run? Would 30 seconds be better?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably a good question for @putmanoj

end

private

def template
@template ||= self.class.module_parent::Template.find(options[:template_id])
end

def template_relative_path
JSON.parse(template.payload)["relative_path"]
end

def stack_response
return if options[:terraform_stack_id].nil?

@stack_response ||= Terraform::Runner::ResponseAsync.new(options[:terraform_stack_id])
end

def configuration_script_source
@configuration_script_source ||= template.configuration_script_source
end

def queue_poll_runner
queue_signal(:poll_runner, :deliver_on => Time.now.utc + poll_interval)
end

def checkout_git_repository
options[:git_checkout_tempdir] = Dir.mktmpdir("embedded-terraform-runner-git")
save!

_log.info("Checking out git repository to #{options[:git_checkout_tempdir].inspect}...")
configuration_script_source.checkout_git_repository(options[:git_checkout_tempdir])
rescue MiqException::MiqUnreachableError => err
miq_task.job.timeout!
raise "Failed to connect with [#{err.class}: #{err}], job aborted"
end

def cleanup_git_repository
return unless options[:git_checkout_tempdir]

_log.info("Cleaning up git repository checkout at #{options[:git_checkout_tempdir].inspect}...")
FileUtils.rm_rf(options[:git_checkout_tempdir])
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
class ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Stack < ManageIQ::Providers::EmbeddedAutomationManager::OrchestrationStack
belongs_to :ext_management_system, :foreign_key => :ems_id, :class_name => "ManageIQ::Providers::EmbeddedTerraform::AutomationManager", :inverse_of => false
belongs_to :configuration_script_payload, :foreign_key => :configuration_script_base_id, :class_name => "ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Template", :inverse_of => :stacks
belongs_to :miq_task, :foreign_key => :ems_ref, :inverse_of => false

class << self
alias create_job create_stack
alias raw_create_job raw_create_stack

def create_stack(terraform_template, options = {})
authentications = collect_authentications(terraform_template.manager, options)

job = raw_create_stack(terraform_template, options)

miq_task = job&.miq_task

create!(
:name => terraform_template.name,
:ext_management_system => terraform_template.manager,
:verbosity => options[:verbosity].to_i,
:authentications => authentications,
:configuration_script_payload => terraform_template,
:miq_task => miq_task,
:status => miq_task&.state,
:start_time => miq_task&.started_on
)
end

def raw_create_stack(terraform_template, options = {})
terraform_template.run(options)
rescue => err
_log.error("Failed to create job from template(#{terraform_template.name}), error: #{err}")
raise MiqException::MiqOrchestrationProvisionError, err.to_s, err.backtrace
end

private

def collect_authentications(manager, options)
credential_ids = options[:credential] || []

manager.credentials.where(:id => credential_ids)
end
end
end
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
class ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Template < ManageIQ::Providers::EmbeddedAutomationManager::ConfigurationScriptPayload
has_many :jobs, :class_name => 'OrchestrationStack', :foreign_key => :configuration_script_base_id
has_many :stacks, :class_name => "ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Stack", :foreign_key => :configuration_script_base_id, :inverse_of => :configuration_script_payload, :dependent => :nullify

def run(vars = {}, _userid = nil)
env_vars = vars.delete(:env) || {}
credentials = vars.delete(:credential) || []

self.class.module_parent::Job.create_job(self, env_vars, vars, credentials).tap(&:signal_start)
end
end
2 changes: 2 additions & 0 deletions lib/terraform/runner/response_async.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ class Runner
class ResponseAsync
include Vmdb::Logging

attr_reader :stack_id

# Response object designed for holding full response from terraform-runner stack job
#
# @param stack_id [String] terraform-runner stack job - stack_id
Expand Down
5 changes: 5 additions & 0 deletions spec/factories/configuration_script_payload.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FactoryBot.define do
factory :terraform_template,
:class => "ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Template",
:parent => :configuration_script_payload
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
RSpec.describe ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Job do
let(:template) { FactoryBot.create(:terraform_template) }
let(:job) { described_class.create_job(template, env_vars, input_vars, credentials).tap { |job| job.state = state} }
let(:state) { "waiting_to_start" }
let(:env_vars) { {} }
let(:input_vars) { {} }
let(:credentials) { [] }

describe ".create_job" do
it "create a job" do
expect(described_class.create_job(template, env_vars, input_vars, credentials)).to have_attributes(
:type => "ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Job",
:options => {
:template_id => template.id,
:env_vars => env_vars,
:input_vars => input_vars,
:credentials => credentials,
:poll_interval => 60
}
)
end
end

describe "#signal" do
%w[start pre_execute execute poll_runner post_execute 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_execute execute poll_runner post_execute finish abort_job cancel error].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 "doesn't allow pre_execute signal"
it_behaves_like "doesn't allow execute signal"
it_behaves_like "doesn't allow poll_runner signal"
it_behaves_like "doesn't allow post_execute 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"
end

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

it_behaves_like "doesn't allow start signal"
it_behaves_like "allows pre_execute signal"
it_behaves_like "doesn't allow execute signal"
it_behaves_like "doesn't allow poll_runner signal"
it_behaves_like "doesn't allow post_execute 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"
end

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

it_behaves_like "doesn't allow start signal"
it_behaves_like "doesn't allow pre_execute signal"
it_behaves_like "doesn't allow execute signal"
it_behaves_like "allows poll_runner signal"
it_behaves_like "allows post_execute 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"
end

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

it_behaves_like "doesn't allow start signal"
it_behaves_like "doesn't allow pre_execute signal"
it_behaves_like "doesn't allow execute signal"
it_behaves_like "doesn't allow poll_runner signal"
it_behaves_like "doesn't allow post_execute 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"
end

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

it_behaves_like "doesn't allow start signal"
it_behaves_like "doesn't allow pre_execute signal"
it_behaves_like "doesn't allow execute signal"
it_behaves_like "doesn't allow poll_runner signal"
it_behaves_like "doesn't allow post_execute 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"
end
end

describe "#start" do
it "moves to state pre_execute" do
job.signal(:start)
expect(job.reload.state).to eq("pre_execute")
end
end

describe "#poll_runner" do
let(:state) { "running" }

context "still running" do
before { expect(job).to receive(:running?).and_return(true) }

it "requeues poll_runner" do
job.signal(:poll_runner)
expect(job.reload.state).to eq("running")
end
end

context "completed" do
before { expect(job).to receive(:running?).and_return(false) }

it "moves to state finished" do
job.signal(:poll_runner)
expect(job.reload.state).to eq("finished")
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
RSpec.describe ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Template do
let(:template) { FactoryBot.create(:terraform_template) }

describe "#run" do
it "creates a Job" do
job = template.run
expect(job).to be_a(ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Job)
end
end
end