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

Add support for pre/post-migration playbook #355

Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
object_type: class
version: 1.0
object:
attributes:
description:
display_name:
name: Ansible
type:
inherits:
visibility:
owner:
schema:
- field:
aetype: method
name: execute
display_name:
datatype:
priority: 1
owner:
default_value:
substitute: true
message: create
visibility:
collect:
scope:
description:
condition:
on_entry:
on_exit:
on_error:
max_retries:
max_time:
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
module ManageIQ
module Automate
module Transformation
module Ansible
class CheckPlaybookAsAService
def initialize(handle = $evm)
@handle = handle
end

def set_retry(message, interval = '1.minutes')
end

def main
transformation_hook = @handle.inputs['transformation_hook']
task = @handle.root['service_template_transformation_plan_task']
service_request_id = task.get_option("#{transformation_hook}_ansible_playbook_service_request_id".to_sym)

if service_request_id.present?
service_request = @handle.vmdb(:miq_request).find_by(:id => service_request_id)

playbooks_status = task.get_option(:playbooks) || {}
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't mix string and symbol as keys at the same level. Here the examples are :playbooks and "pre_ansible_playbook_service_request_id" (BTW, the key is very long)

Copy link
Author

Choose a reason for hiding this comment

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

@bzwei
Code changed to use to_sym with the substituted string.
I know the key is very long, but it makes it really explicit.

playbooks_status[transformation_hook] = { :job_state => service_request.request_state }

if service_request.request_state == 'finished'
@handle.log(:info, "Ansible playbook service request (id: #{service_request_id}) is finished.")
playbooks_status[transformation_hook][:job_status] = service_request.status
playbooks_status[transformation_hook][:job_id] = service_request.miq_request_tasks.first.destination.service_resources.first.resource.id
Copy link
Contributor

Choose a reason for hiding this comment

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

@fdupont-redhat This is a pretty long chain of commands where we could get a nil in the middle?

Copy link
Author

Choose a reason for hiding this comment

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

@mkanoor
Good catch. I had the same feeling, but aesthetics won over security. I'll add extra tests.

task.set_option(:playbooks, playbooks_status)
if service_request.status == 'Error' && transformation_hook == 'pre'
Copy link
Contributor

Choose a reason for hiding this comment

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

@fdupont-redhat is it intentional to ignore the errors for post playbooks?

Copy link
Author

Choose a reason for hiding this comment

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

@mkanoor
Yes, it is. We consider that the migration has gone far enough to not cancel it for a post migration playbook failure. The playbook or any other problem can be fixed afterwards, with a warning message. The UI knows that the playbook has failed, and it can show the log to the user.

raise "Ansible playbook has failed (hook=#{transformation_hook})"
end
else
@handle.log(:info, "Playbook for #{transformation_hook} migration is not finished. Retrying.")
@handle.root['ae_result'] = 'retry'
@handle.root['ae_retry_interval'] = '15.seconds'
end
task.set_option(:playbooks, playbooks_status)
end
rescue => e
@handle.set_state_var(:ae_state_progress, 'message' => e.message)
Copy link
Contributor

Choose a reason for hiding this comment

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

@fdupont-redhat do you want to report progress only when an error happens or should that also happen on success?
Does :ae_state_progress only mean error messages, if it does we should change it to something like :ae_state_error

Copy link
Author

Choose a reason for hiding this comment

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

@mkanoor
It's a default behavior to catch the error and log it in :ae_state_progress, so that it can be used by WeightedUpdateStatus. Here, we don't plan to add additional information because the description argument set in the state machine is explicit enough.

raise
end
end
end
end
end
end

if $PROGRAM_NAME == __FILE__
ManageIQ::Automate::Transformation::Ansible::CheckPlaybookAsAService.new.main
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
object_type: method
version: 1.0
object:
attributes:
name: CheckPlaybookAsAService
display_name:
description:
scope: instance
language: ruby
location: inline
options: {}
inputs:
- field:
aetype:
name: transformation_hook
display_name:
datatype:
priority: 1
owner:
default_value: _
substitute: false
message: create
visibility:
collect:
scope:
description:
condition:
on_entry:
on_exit:
on_error:
max_retries:
max_time:
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
module ManageIQ
module Automate
module Transformation
module Ansible
class LaunchPlaybookAsAService
def initialize(handle = $evm)
@handle = handle
end

def target_host(task, transformation_hook)
target_host = nil
case transformation_hook
when 'pre'
target_host = task.source
when 'post'
target_host = @handle.vmdb(:vm).find_by(:id => task.get_option(:destination_vm_id))
end
target_host
end

def main
task = @handle.root['service_template_transformation_plan_task']
transformation_hook = @handle.inputs['transformation_hook']

return if transformation_hook == '_'
service_template = task.send("#{transformation_hook}_ansible_playbook_service_template")
return if service_template.nil?
target_host = target_host(task, transformation_hook)
return if target_host.nil? || target_host.power_state != 'on'
service_dialog_options = { :hosts => target_host.ipaddresses.first }
service_request = @handle.execute(:create_service_provision_request, service_template, service_dialog_options)
task.set_option("#{transformation_hook}_ansible_playbook_service_request_id".to_sym, service_request.id)
rescue => e
@handle.set_state_var(:ae_state_progress, 'message' => e.message)
raise
end
end
end
end
end
end

if $PROGRAM_NAME == __FILE__
ManageIQ::Automate::Transformation::Ansible::LaunchPlaybookAsAService.new.main
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
object_type: method
version: 1.0
object:
attributes:
name: LaunchPlaybookAsAService
display_name:
description:
scope: instance
language: ruby
location: inline
options: {}
inputs:
- field:
aetype:
name: transformation_hook
display_name:
datatype:
priority: 1
owner:
default_value: _
substitute: false
message: create
visibility:
collect:
scope:
description:
condition:
on_entry:
on_exit:
on_error:
max_retries:
max_time:
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
object_type: instance
version: 1.0
object:
attributes:
display_name:
name: ".missing"
inherits:
description:
fields:
- execute:
value: "${#_missing_instance}"
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ def main
task = @handle.root['service_template_transformation_plan_task']
if task.get_option(:source_vm_power_state) == 'on'
destination_vm = @handle.vmdb(:vm).find_by(:id => task.get_option(:destination_vm_id))
destination_ems = destination_vm.ext_management_system
destination_vm_sdk = ManageIQ::Automate::Transformation::Infrastructure::VM::RedHat::Utils.new(destination_ems).vm_find_by_name(destination_vm.name)
@handle.log(:info, "Status of VM '#{destination_vm.name}': #{destination_vm_sdk.status}")
unless destination_vm_sdk.status == OvirtSDK4::VmStatus::UP
unless destination_vm.power_state == 'on'
@handle.root["ae_result"] = "retry"
@handle.root["ae_retry_interval"] = "15.seconds"
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,5 @@ object:
scope: instance
language: ruby
location: inline
embedded_methods:
- "/Transformation/Infrastructure/VM/rhevm/Utils"
options: {}
inputs: []
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
---
object_type: class
version: 1.0
object:
attributes:
description:
display_name:
name: Ansible
type:
inherits:
visibility:
owner:
schema:
- field:
aetype: state
name: State1
display_name:
datatype:
priority: 1
owner:
default_value:
substitute: true
message: create
visibility:
collect:
scope:
description:
condition:
on_entry:
on_exit:
on_error:
max_retries:
max_time:
- field:
aetype: state
name: State2
display_name:
datatype:
priority: 2
owner:
default_value:
substitute: true
message: create
visibility:
collect:
scope:
description:
condition:
on_entry:
on_exit:
on_error:
max_retries:
max_time:
- field:
aetype: state
name: State3
display_name:
datatype:
priority: 3
owner:
default_value:
substitute: true
message: create
visibility:
collect:
scope:
description:
condition:
on_entry:
on_exit:
on_error:
max_retries:
max_time:
- field:
aetype: state
name: State4
display_name:
datatype:
priority: 4
owner:
default_value:
substitute: true
message: create
visibility:
collect:
scope:
description:
condition:
on_entry:
on_exit:
on_error:
max_retries:
max_time:
- field:
aetype: state
name: State5
display_name:
datatype:
priority: 5
owner:
default_value:
substitute: true
message: create
visibility:
collect:
scope:
description:
condition:
on_entry:
on_exit:
on_error:
max_retries:
max_time:
- field:
aetype: state
name: State6
display_name:
datatype:
priority: 6
owner:
default_value:
substitute: true
message: create
visibility:
collect:
scope:
description:
condition:
on_entry:
on_exit:
on_error:
max_retries:
max_time:
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
object_type: instance
version: 1.0
object:
attributes:
display_name:
name: TransformationPlaybook
inherits:
description:
fields:
- State2:
value: "/Transformation/Ansible/LaunchPlaybookAsAService"
on_entry: /System/CommonMethods/MiqAe.WeightedUpdateStatus(weight => 20, description
=> "Launch ${#transformation_hook} migration playbook", task_message => "Migrating")
on_exit: /System/CommonMethods/MiqAe.WeightedUpdateStatus(weight => 20, description
=> "Launch ${#transformation_hook} migration playbook", task_message => "Migrating")
on_error: /System/CommonMethods/MiqAe.WeightedUpdateStatus(weight => 20, description
=> "Launch ${#transformation_hook} migration playbook", task_message => "Migrating")
- State5:
value: "/Transformation/Ansible/CheckPlaybookAsAService"
on_entry: /System/CommonMethods/MiqAe.WeightedUpdateStatus(weight => 80, description
=> "Check ${#transformation_hook} migration playbook", task_message => "Migrating")
on_exit: /System/CommonMethods/MiqAe.WeightedUpdateStatus(weight => 80, description
=> "Check ${#transformation_hook} migration playbook", task_message => "Migrating")
on_error: /System/CommonMethods/MiqAe.WeightedUpdateStatus(weight => 80, description
=> "Check ${#transformation_hook} migration playbook", task_message => "Migrating")
max_retries: '1500'
Loading