Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Directly run a playbook #16161

Merged
merged 1 commit into from
Oct 16, 2017
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
@@ -1,2 +1,12 @@
class ManageIQ::Providers::EmbeddedAnsible::AutomationManager::Inventory < ManageIQ::Providers::EmbeddedAutomationManager::InventoryRootGroup
def self.raw_create_inventory(tower, inventory_name, hosts)
miq_org = tower.provider.default_organization
tower.with_provider_connection do |connection|
connection.api.inventories.create!(:name => inventory_name, :organization => miq_org).tap do |inventory|
hosts.split(',').each do |host|
connection.api.hosts.create!(:name => host, :inventory => inventory.id)
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,46 @@ class ManageIQ::Providers::EmbeddedAnsible::AutomationManager::Playbook <
ManageIQ::Providers::EmbeddedAutomationManager::ConfigurationScriptPayload

has_many :jobs, :class_name => 'OrchestrationStack', :foreign_key => :configuration_script_base_id

def run(options, userid = nil)
options[:playbook_id] = id
options[:userid] = userid || 'system'
options[:name] = "Playbook: #{name}"
miq_job = ManageIQ::Providers::EmbeddedAnsible::AutomationManager::PlaybookRunner.create_job(options)
Copy link
Contributor

Choose a reason for hiding this comment

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

@bzwei
Do we need try to make sure we have a miq_job?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think so. create_job is a straight forward method.

miq_job.signal(:start)
miq_job.miq_task.id
end

# return provider raw object
def raw_create_job_template(options)
job_template_klass = ManageIQ::Providers::EmbeddedAnsible::AutomationManager::ConfigurationScript
job_template_klass.raw_create_in_provider(manager, build_parameter_list(options))
end

private

def build_parameter_list(options)
Copy link
Contributor

Choose a reason for hiding this comment

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

This method is largely identical to the same method in ServiceTemplateAnsiblePlaybook. That code should be shared.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. I planned to have another PR to reuse code introduced in this PR.

params = {
:name => options[:template_name] || "#{name}_#{Time.zone.now.to_i}",
:description => options[:description] || '',
:project => configuration_script_source.manager_ref,
:playbook => name,
:become_enabled => options[:become_enabled].present?,
:verbosity => options[:verbosity].presence || 0,
:ask_variables_on_launch => true,
:ask_limit_on_launch => true,
:ask_inventory_on_launch => true,
:ask_credential_on_launch => true,
:limit => options[:limit],
:inventory => options[:inventory] || manager.provider.default_inventory,
:extra_vars => options[:extra_vars].try(:to_json)
Copy link
Contributor

@syncrou syncrou Oct 16, 2017

Choose a reason for hiding this comment

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

@bzwei - have you been able to verify that this call will deep stringify nested hashes? We had issues with testing where the :manageiq key would be removed when we called to_json on the :extra_vars hash. -cc @mkanoor - To clarify - to_json works if all the keys are strings, it fails if there is a nested key that is passed as a symbol.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I cannot reproduce what you observed. For example:

irb(main):007:0> {:test => {:manageiq => {'a' => 'x'}}}.to_json
=> "{\"test\":{\"manageiq\":{\"a\":\"x\"}}}"
irb(main):008:0> JSON.parse(_)
=> {"test"=>{"manageiq"=>{"a"=>"x"}}}

}

%i(credential cloud_credential network_credential).each do |credential|
cred_sym = "#{credential}_id".to_sym
params[credential] = Authentication.find(options[cred_sym]).manager_ref if options[cred_sym]
end

params.compact
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
class ManageIQ::Providers::EmbeddedAnsible::AutomationManager::PlaybookRunner < ::Job
# options are job table columns, including options column which is the playbook context info
def self.create_job(options)
super(name, options)
end

def start
time = Time.zone.now
update_attributes(:started_on => time)
miq_task.update_attributes(:started_on => time)
if options[:inventory]
queue_signal(:create_job_template)
else
queue_signal(:create_inventory)
end
end

def create_inventory
set_status('creating inventory')
tower = playbook.manager
hosts = options[:hosts] || options.fetch_path(:extra_vars, :hosts)
Copy link
Contributor

Choose a reason for hiding this comment

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

@bzwei Should this default to localhost if its not available in either the config options or in the method input parameters (coming via extra vars). So if someone wants to run it on localhost they dont have to add a hosts method input parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It has been taken cared of. Yes, if the user does not provide any hosts, default (localhost) is used in https://github.com/ManageIQ/manageiq/pull/16161/files#diff-713a4162657cdeb3c36e2194e0181ab7R36

options[:inventory] =
if hosts == 'localhost' || hosts.nil?
tower.provider.default_inventory
else
inventory_name = "#{playbook.name}_#{Time.zone.now.to_i}"
ManageIQ::Providers::EmbeddedAnsible::AutomationManager::Inventory.raw_create_inventory(tower, inventory_name, hosts).id
end
save!
queue_signal(:create_job_template)
rescue => err
_log.log_backtrace(err)
queue_signal(:post_ansible_run, err.message, 'error')
end

def create_job_template
set_status('creating job template')
raw_job_template = playbook.raw_create_job_template(options)
options[:job_template_ref] = raw_job_template.id
save!

queue_signal(:launch_ansible_tower_job)
rescue => err
_log.log_backtrace(err)
queue_signal(:post_ansible_run, err.message, 'error')
end

def launch_ansible_tower_job
set_status('launching tower job')
job_template = ManageIQ::Providers::EmbeddedAnsible::AutomationManager::ConfigurationScript.new(
:manager => playbook.manager,
:manager_ref => options[:job_template_ref],
:variables => {}
)
launch_options = options.slice(:extra_vars, :limit)
tower_job = ManageIQ::Providers::EmbeddedAnsible::AutomationManager::Job.create_job(job_template, launch_options)
options[:tower_job_id] = tower_job.id
self.name = "#{name}, Job ID: #{tower_job.id}"
miq_task.update_attributes(:name => name)
save!

queue_signal(:poll_ansible_tower_job_status, 10)
rescue => err
_log.log_backtrace(err)
queue_signal(:post_ansible_run, err.message, 'error')
end

def poll_ansible_tower_job_status(interval)
Copy link
Member

Choose a reason for hiding this comment

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

@bzwei Minor, but can we swap the order the methods are listed in the file to match the "happy path" the methods would take. This would mean swapping the order that launch_ansible_tower_job and poll_ansible_tower_job_status appear in this file.

Also, fix the rubocop warning on line 57 with the extra space.

set_status('waiting for tower job to complete')

tower_job_status = tower_job.raw_status
if tower_job_status.completed?
tower_job.refresh_ems
Copy link
Contributor

Choose a reason for hiding this comment

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

@bzwei is this a synchronous refresh. Should it be a different state in the state machine

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No need. It is very quick, just updates the job record in our DB.

if tower_job_status.succeeded?
queue_signal(:post_ansible_run, 'Playbook ran successfully', 'ok')
else
queue_signal(:post_ansible_run, 'Ansible engine returned an error for the job', 'error')
end
else
interval = 60 if interval > 60
queue_signal(:poll_ansible_tower_job_status, interval * 2, :deliver_on => Time.now.utc + interval)
end
rescue => err
_log.log_backtrace(err)
queue_signal(:post_ansible_run, err.message, 'error')
end

def post_ansible_run(*args)
# delete inventory, job_template, job?
queue_signal(:finish, *args)
end

alias_method :initializing, :dispatch_start
alias_method :finish, :process_finished
alias_method :abort_job, :process_abort
alias_method :cancel, :process_cancel
alias_method :error, :process_error

private

def load_transitions
self.state ||= 'initialize'

{
:initializing => {'initialize' => 'waiting_to_start'},
:start => {'waiting_to_start' => 'running'},
:create_inventory => {'running' => 'inventory'},
:create_job_template => {'inventory' => 'job_template', 'running' => 'job_template'},
:launch_ansible_tower_job => {'job_template' => 'ansible_job'},
:poll_ansible_tower_job_status => {'ansible_job' => 'ansible_job'},
:post_ansible_run => {'inventory' => 'ansible_done', 'job_template' => 'ansible_done', 'ansible_job' => 'ansible_done'},
:finish => {'*' => 'finished'},
:abort_job => {'*' => 'aborting'},
:cancel => {'*' => 'canceling'},
:error => {'*' => '*'}
}
end

def queue_signal(*args, deliver_on: nil)
priority = options[:priority] || MiqQueue::NORMAL_PRIORITY

MiqQueue.put(
:class_name => self.class.name,
:method_name => "signal",
:instance_id => id,
:priority => priority,
:role => 'embedded_ansible',
:args => args,
:deliver_on => deliver_on
)
end

def playbook
ManageIQ::Providers::EmbeddedAnsible::AutomationManager::Playbook.find(options[:playbook_id])
end

def tower_job
ManageIQ::Providers::EmbeddedAnsible::AutomationManager::Job.find(options[:tower_job_id])
end
end
Loading