Skip to content

Commit

Permalink
Merge pull request #629 from lfu/content_library_service
Browse files Browse the repository at this point in the history
Service for VMware content library item deployment.
  • Loading branch information
agrare committed Sep 28, 2020
2 parents 478e0af + 6ab9227 commit f7db3a1
Show file tree
Hide file tree
Showing 6 changed files with 356 additions and 1 deletion.
2 changes: 1 addition & 1 deletion app/models/manageiq/providers/vmware/infra_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def supports_authentication?(authtype)
end

def self.catalog_types
{"vmware" => N_("VMware")}
{"vmware" => N_("VMware"), "generic_ovf_template" => N_("VMware Content Libary OVF Template")}
end

def streaming_refresh_enabled?
Expand Down
140 changes: 140 additions & 0 deletions app/models/manageiq/providers/vmware/infra_manager/ovf_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
class ManageIQ::Providers::Vmware::InfraManager::OvfService < ServiceGeneric
delegate :ovf_template, :manager, :to => :service_template, :allow_nil => true

# A chance for taking options from automate script to override options from a service dialog
def preprocess(action, new_options = {})
return unless action == ResourceAction::PROVISION

if new_options.present?
_log.info("Override with new options:\n#{new_options}")
end

save_action_options(action, new_options)
end

def execute(action)
return unless action == ResourceAction::PROVISION

deploy_library_item_queue
end

def deploy_library_item_queue
task_options = {:action => "Deploying VMware Content Library Item", :userid => "system"}
queue_options = {
:class_name => self.class.name,
:instance_id => id,
:method_name => "deploy_library_item",
:args => {},
:role => "ems_operations",
:queue_name => manager.queue_name_for_ems_operations,
:zone => manager.my_zone
}

task_id = MiqTask.generic_action_with_callback(task_options, queue_options)
update(:options => options.merge(:deploy_task_id => task_id))
end

def deploy_library_item(_options)
_log.info("OVF template provisioning with template ID: [#{ovf_template.id}] name:[#{ovf_template.name}] was initiated.")
opts = provision_options
_log.info("VMware Content Library OVF Tempalte provisioning with options:\n#{opts}")

@deploy_response = ovf_template.deploy(opts).to_hash
_log.info("Content Library request response: #{@deploy_response}")
update(:options => options.merge(:deploy_response => @deploy_response))
rescue VSphereAutomation::ApiError => e
_log.error("Failed to deploy content library template(#{ovf_template.name}), error: #{e}")
raise MiqException::MiqOrchestrationProvisionError, "Content library OVF template deployment failed: #{e}"
end

def check_completed(action)
return [true, 'not supported'] unless action == ResourceAction::PROVISION
return [false, nil] if deploy_task.state != "Finished"

message = deploy_response&.dig(:value, :succeeded) ? nil : deploy_response&.dig(:value, :error).to_json || deploy_response&.to_json || deploy_task.message
[true, message]
end

def refresh(_action)
end

def check_refreshed(action)
return [true, nil] unless deploy_response&.dig(:value, :succeeded)

dest = find_destination_in_vmdb
if dest
add_resource!(dest, :name => action)

if dest.kind_of?(ResourcePool)
dest.vms.each { |vm| add_resource!(vm, :name => action) }
end

[true, nil]
else
[false, nil]
end
end

private

def deploy_response
@deploy_response ||= options[:deploy_response]
end

def deploy_task
@deploy_task ||= MiqTask.find_by(:id => options[:deploy_task_id])
end

def find_destination_in_vmdb
target_model_class.find_by(:ems_id => manager.id, :ems_ref => deploy_response.dig(:value, :resource_id, :id))
end

def target_model_class
case deploy_response.dig(:value, :resource_id, :type)
when "VirtualMachine"
manager.class::Vm
when "VirtualApp"
manager.class::ResourcePool
end
end

def get_action_options(action)
options[action_option_key(action)]
end

def provision_options
@provision_options ||= get_action_options(ResourceAction::PROVISION)
end

def save_action_options(action, overrides)
return unless action == ResourceAction::PROVISION

action_options = options.fetch_path(:config_info, action.downcase.to_sym).with_indifferent_access
%w[resource_pool ems_folder host].each do |r|
next if action_options[r].blank?

action_options["#{r}_id"] = action_options.delete(r).split.first.to_i
end

action_options.deep_merge!(parse_dialog_options)
action_options.deep_merge!(overrides)

options[action_option_key(action)] = action_options
save!
end

def parse_dialog_options
dialog_options = options[:dialog] || {}
options = {:vm_name => dialog_options['dialog_vm_name']}
options[:accept_all_EULA] = dialog_options['dialog_accept_all_EULA'] == 't'

%w[resource_pool ems_folder host storage].each do |r|
options["#{r}_id"] = dialog_options["dialog_#{r}"].split.first.to_i if dialog_options["dialog_#{r}"].present?
end
options
end

def action_option_key(action)
"#{action.downcase}_options".to_sym
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
class ManageIQ::Providers::Vmware::InfraManager::OvfServiceTemplate < ServiceTemplateGeneric
def self.default_provisioning_entry_point(_service_type)
'/Service/Generic/StateMachines/GenericLifecycle/provision'
end

# create ServiceTemplate and supporting ServiceResources and ResourceActions
# options
# :name
# :description
# :service_template_catalog_id
# :config_info
# :provision
# :dialog_id or :dialog
# :ovf_template_id or :ovf_template
#
def self.create_catalog_item(options, _auth_user)
options = options.merge(:service_type => 'atomic', :prov_type => 'generic_ovf_template')
config_info = validate_config_info(options[:config_info])
enhanced_config = config_info.deep_merge(
:provision => {
:configuration_template => ovf_template_from_config_info(config_info)
}
)

transaction do
create_from_options(options).tap do |service_template|
service_template.create_resource_actions(enhanced_config)
end
end
end

def self.validate_config_info(info)
info[:provision][:fqname] ||= default_provisioning_entry_point(SERVICE_TYPE_ATOMIC) if info.key?(:provision)

# TODO: Add more validation for required fields
info
end
private_class_method :validate_config_info

def self.ovf_template_from_config_info(info)
ovf_template_id = info[:provision][:ovf_template_id]
ovf_template_id ? OrchestrationTemplate.find(ovf_template_id) : info[:provision][:ovf_template]
end
private_class_method :ovf_template_from_config_info

def ovf_template
@ovf_template ||= resource_actions.find_by(:action => "Provision").try(:configuration_template)
end

def manager
@manager ||= ovf_template.try(:ext_management_system)
end

def update_catalog_item(options)
config_info = validate_update_config_info(options)
config_info[:provision][:configuration_template] ||= ovf_template_from_config_info(config_info) if config_info.key?(:provision)
options[:config_info] = config_info

super
end

private

def ovf_template_from_config_info(info)
self.class.send(:ovf_template_from_config_info, info)
end

def validate_update_config_info(options)
opts = super
self.class.send(:validate_config_info, opts)
end

def update_service_resources(_config_info, _auth_user = nil)
# do nothing since no service resources for this template
end

def update_from_options(params)
options[:config_info] = Hash[params[:config_info].collect { |k, v| [k, v.except(:configuration_template)] }]
update!(params.except(:config_info))
end
end
3 changes: 3 additions & 0 deletions spec/factories/service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FactoryBot.define do
factory :service_ovf, :class => "ManageIQ::Providers::Vmware::InfraManager::OvfService", :parent => :service
end
3 changes: 3 additions & 0 deletions spec/factories/service_template.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FactoryBot.define do
factory :service_template_ovf, :class => "ManageIQ::Providers::Vmware::InfraManager::OvfServiceTemplate", :parent => :service_template
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
require 'vsphere-automation-vcenter'

describe(ManageIQ::Providers::Vmware::InfraManager::OvfService) do
let(:action) { ResourceAction::PROVISION }
let(:ovf_template) { FactoryBot.create(:orchestration_template_vmware_infra) }

let(:ems) { FactoryBot.create(:ems_vmware) }

let(:service) do
FactoryBot.create(:service_ovf, :options => config_info_options.merge(dialog_options)).tap do |svc|
allow(svc).to receive(:manager).and_return(ems)
end
end

let(:loaded_service) do
service_template = FactoryBot.create(:service_template_ovf).tap do |st|
allow(st).to receive(:manager).and_return(ems)
end

FactoryBot.create(:service_ovf,
:options => {:provision_options => provision_options}.merge(config_info_options),
:service_template => service_template).tap do |svc|
allow(svc).to receive(:ovf_template).and_return(ovf_template)
end
end

let(:dialog_options) do
{
:dialog => {
"dialog_vm_name" => "dialog_vm_name",
"dialog_resource_pool" => "5 test resource pool",
"dialog_ems_folder" => "30 lucy",
}
}
end

let(:config_info_options) do
{
:config_info => {
:provision => {
:dialog_id => "2",
:ovf_template_id => ovf_template.id,
:vm_name => "template_vm_name",
:accept_all_EULA => true,
:resource_pool => "2 Default for Cluster dev-vc67-cluster",
:ems_folder => "3 test_folder",
:host => "1 test_host"
}
}
}
end

let(:override_options) { {:vm_name => 'override_vm_name'} }

let(:provision_options) do
{
"ovf_template_id" => ovf_template.id,
"dialog_id" => "2",
"vm_name" => "dialog_vm_name",
"accept_all_EULA" => false,
"resource_pool_id" => 5,
"ems_folder_id" => 30
}
end

let(:failed_response ) { {:value => {:succeeded => false, :error=>{:errors=>[{:category=>"SERVER", :error=>{:@class=>"com.vmware.vapi.std.errors.already_exists", :messages=>[{:args=>["VirtualMachine", "lucy-api-vm-2"], :default_message=>"An object of type \"VirtualMachine\" named \"lucy-api-vm-2\" already exists.", :id=>"com.vmware.vdcs.util.duplicate_name"}]}}], :warnings=>[], :information=>[]}}} }

let(:deploy_task) { FactoryBot.create(:miq_task, :state => "Active")}

describe '#preprocess' do
it 'prepares job options from dialog' do
service.preprocess(action)
expect(service.options[:provision_options]).to match a_hash_including(provision_options)
end

it 'prepares job options combined from dialog and overrides' do
service.preprocess(action, override_options)
expect(service.options[:provision_options]).to match a_hash_including(
"vm_name" => override_options[:vm_name]
)
end
end

describe '#deploy_library_item' do
it 'Provisions with an ovf template' do
expect(ovf_template).to receive(:deploy) do |options|
expect(options).to eq(provision_options)
failed_response
end
loaded_service.deploy_library_item(action)
end
end

describe '#check_completed' do
it 'created VM ends in VMDB' do
deploy_task.update(:state => "Finished")
loaded_service.update(:options => loaded_service.options.merge(:deploy_task_id => deploy_task.id))
loaded_service.update(:options => loaded_service.options.merge(:deploy_response => failed_response))
expect(loaded_service.check_completed(action)).to eq([true, failed_response.dig(:value, :error).to_json])
end

it 'created VM not ends in VMDB yet' do
loaded_service.update(:options => loaded_service.options.merge(:deploy_task_id => deploy_task.id))
expect(loaded_service.check_completed(action)).to eq([false, nil])
end
end

describe '#check_refreshed' do
it 'successful deployment response ' do
response = {:value => {:succeeded => true, :resource_id=>{:type=>"VirtualMachine", :id=>"vm-934"}}}
loaded_service.update(:options => loaded_service.options.merge(:deploy_response => response))
expect(loaded_service.check_refreshed(action)).to eq([false, nil])

FactoryBot.create(:vm_vmware, :ems_ref_type => "VirtualMachine", :ems_ref => "vm-934", :ems_id => ems.id)
expect(loaded_service.check_refreshed(action)).to eq([true, nil])
end

it 'no successful deployment response ' do
response = {:value => {:succeeded => false}}
loaded_service.update(:options => loaded_service.options.merge(:deploy_response => response))
expect(loaded_service.check_refreshed(action)).to eq([true, nil])
end

it 'no deployment response' do
expect(loaded_service.check_refreshed(action)).to eq([true, nil])
end
end
end

0 comments on commit f7db3a1

Please sign in to comment.