diff --git a/app/models/conversion_host.rb b/app/models/conversion_host.rb index 493569ff804..6556a4edf0c 100644 --- a/app/models/conversion_host.rb +++ b/app/models/conversion_host.rb @@ -4,4 +4,41 @@ class ConversionHost < ApplicationRecord acts_as_miq_taggable belongs_to :resource, :polymorphic => true + has_many :service_template_transformation_plan_tasks, :dependent => :nullify + has_many :active_tasks, -> { where(:state => 'active') }, :class_name => ServiceTemplateTransformationPlanTask, :inverse_of => :conversion_host + + # To be eligible, a conversion host must have the following properties + # - A transport mechanism is configured for source (set by 3rd party) + # - Credentials are set on the resource + # - The number of concurrent tasks has not reached the limit + def eligible? + source_transport_method.present? && check_resource_credentials && check_concurrent_tasks + end + + def check_concurrent_tasks + max_tasks = max_concurrent_tasks || Settings.transformation.limits.max_concurrent_tasks_per_host + active_tasks.size < max_tasks + end + + def check_resource_credentials + send("check_resource_credentials_#{resource.ext_management_system.emstype}") + end + + def source_transport_method + return 'vddk' if vddk_transport_supported + return 'ssh' if ssh_transport_supported + end + + private + + def check_resource_credentials_rhevm + !(resource.authentication_userid.nil? || resource.authentication_password.nil?) + end + + def check_resource_credentials_openstack + ssh_authentications = resource.ext_management_system.authentications + .where(:authtype => 'ssh_keypair') + .where.not(:userid => nil, :auth_key => nil) + !ssh_authentications.empty? + end end diff --git a/app/models/service_template_transformation_plan_task.rb b/app/models/service_template_transformation_plan_task.rb index 000ba171a93..ade30a8bb5a 100644 --- a/app/models/service_template_transformation_plan_task.rb +++ b/app/models/service_template_transformation_plan_task.rb @@ -1,4 +1,7 @@ class ServiceTemplateTransformationPlanTask < ServiceTemplateProvisionTask + belongs_to :conversion_host + delegate :source_transport_method, :to => :conversion_host + def self.base_model ServiceTemplateTransformationPlanTask end @@ -46,21 +49,72 @@ def task_active vm_resource.update_attributes(:status => ServiceResource::STATUS_ACTIVE) end - def conversion_host - Host.find_by(:id => options[:transformation_host_id]) + def source_ems + source.ext_management_system + end + + def destination_ems + transformation_destination(source.ems_cluster).ext_management_system + end + + def source_disks + options[:source_disks] ||= source.hardware.disks.select { |d| d.device_type == 'disk' }.collect do |disk| + source_storage = disk.storage + destination_storage = transformation_destination(disk.storage) + raise "[#{source.name}] Disk #{disk.device_name} [#{source_storage.name}] has no mapping. Aborting." if destination_storage.nil? + { + :path => disk.filename, + :size => disk.size, + :percent => 0, + :weight => disk.size.to_f / source.allocated_disk_storage.to_f * 100 + } + end + end + + def network_mappings + options[:network_mappings] ||= source.hardware.nics.select { |n| n.device_type == 'ethernet' }.collect do |nic| + source_network = nic.lan + destination_network = transformation_destination(source_network) + raise "[#{source.name}] NIC #{nic.device_name} [#{source_network.name}] has no mapping. Aborting." if destination_network.nil? + { + :source => source_network.name, + :destination => destination_network_ref(destination_network), + :mac_address => nic.address + } + end + end + + def destination_network_ref(network) + send("destination_network_ref_#{destination_ems.emstype}", network) + end + + def destination_network_ref_rhevm(network) + network.name + end + + def destination_network_ref_openstack(network) + network.ems_ref + end + + def destination_flavor + Flavor.find_by(:id => miq_request.source.options[:config_info][:osp_flavor]) + end + + def destination_security_group + SecurityGroup.find_by(:id => miq_request.source.options[:config_info][:osp_security_group]) end def transformation_log host = conversion_host if host.nil? - msg = "Conversion host was not found: ID [#{options[:transformation_host_id]}]. Download of transformation log aborted." + msg = "Conversion host was not found. Download of transformation log aborted." _log.error(msg) raise MiqException::Error, msg end - userid, password = host.auth_user_pwd(:remote) + userid, password = host.resource.auth_user_pwd(:remote) if userid.blank? || password.blank? - msg = "Credential was not found for host #{host.name}. Download of transformation log aborted." + msg = "Credential was not found for host #{host.resource.name}. Download of transformation log aborted." _log.error(msg) raise MiqException::Error, msg end @@ -74,7 +128,7 @@ def transformation_log begin require 'net/scp' - Net::SCP.download!(host.ipaddress, userid, logfile, nil, :ssh => {:password => password}) + Net::SCP.download!(host.resource.ipaddress, userid, logfile, nil, :ssh => {:password => password}) rescue Net::SCP::Error => scp_err _log.error("Download of transformation log for #{description} with ID [#{id}] failed with error: #{scp_err.message}") raise scp_err @@ -87,7 +141,7 @@ def transformation_log_queue(userid = nil) userid ||= User.current_userid || 'system' host = conversion_host if host.nil? - msg = "Conversion host was not found: ID [#{options[:transformation_host_id]}]. Cannot queue the download of transformation log." + msg = "Conversion host was not found. Cannot queue the download of transformation log." return create_error_status_task(userid, msg).id end @@ -98,7 +152,7 @@ def transformation_log_queue(userid = nil) :instance_id => id, :priority => MiqQueue::HIGH_PRIORITY, :args => [], - :zone => host.my_zone} + :zone => host.resource.my_zone} MiqTask.generic_action_with_callback(options, queue_options) end @@ -114,6 +168,23 @@ def canceled update_attributes(:cancelation_status => MiqRequestTask::CANCEL_STATUS_FINISHED) end + def conversion_options + source_cluster = source.ems_cluster + source_storage = source.hardware.disks.select { |d| d.device_type == 'disk' }.first.storage + destination_cluster = transformation_destination(source_cluster) + destination_storage = transformation_destination(source_storage) + + options = { + :source_disks => source_disks.map { |disk| disk[:path] }, + :network_mappings => network_mappings + } + + options.merge!(send("conversion_options_source_provider_#{source_ems.emstype}_#{source_transport_method}", source_storage)) + options.merge!(send("conversion_options_destination_provider_#{destination_ems.emstype}", destination_cluster, destination_storage)) + + options + end + private def vm_resource @@ -129,4 +200,60 @@ def create_error_status_task(userid, msg) :message => msg ) end + + def conversion_options_source_provider_vmwarews_vddk(_storage) + { + :vm_name => source.name, + :transport_method => 'vddk', + :vmware_fingerprint => source.host.thumbprint_sha1, + :vmware_uri => URI::Generic.build( + :scheme => 'esx', + :userinfo => CGI.escape(source.host.authentication_userid), + :host => source.host.ipaddress, + :path => '/', + :query => { :no_verify => 1 }.to_query + ).to_s, + :vmware_password => source.host.authentication_password + } + end + + def conversion_options_source_provider_vmwarews_ssh(storage) + { + :vm_name => URI::Generic.build(:scheme => 'ssh', :userinfo => 'root', :host => source.host.ipaddress, :path => "/vmfs/volumes").to_s + "/#{storage.name}/#{source.location}", + :transport_method => 'ssh' + } + end + + def conversion_options_destination_provider_rhevm(cluster, storage) + { + :rhv_url => URI::Generic.build(:scheme => 'https', :host => destination_ems.hostname, :path => '/ovirt-engine/api').to_s, + :rhv_cluster => cluster.name, + :rhv_storage => storage.name, + :rhv_password => destination_ems.authentication_password, + :install_drivers => true, + :insecure_connection => true + } + end + + def conversion_options_destination_provider_openstack(cluster, storage) + { + :osp_environment => { + :os_no_cache => true, + :os_auth_url => URI::Generic.build( + :scheme => destination_ems.security_protocol == 'non-ssl' ? 'http' : 'https', + :host => destination_ems.hostname, + :port => destination_ems.port, + :path => destination_ems.api_version + ), + :os_user_domain_name => destination_ems.uid_ems, + :os_username => destination_ems.authentication_userid, + :os_password => destination_ems.authentication_password, + :os_project_name => cluster.name + }, + :osp_destination_project_id => cluster.ems_ref, + :osp_volume_type_id => storage.ems_ref, + :osp_flavor_id => destination_flavor.ems_ref, + :osp_security_groups_ids => [destination_security_group.ems_ref] + } + end end diff --git a/config/settings.yml b/config/settings.yml index b93bc505ff8..ccec2474aee 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -1098,7 +1098,9 @@ :history: :keep_tasks: 1.week :purge_window_size: 1000 - +:transformation: + :limits: + :max_concurrent_tasks_per_host: 10 :ui: :mark_translated_strings: false :url: diff --git a/spec/models/conversion_host_spec.rb b/spec/models/conversion_host_spec.rb new file mode 100644 index 00000000000..5ccd29276c9 --- /dev/null +++ b/spec/models/conversion_host_spec.rb @@ -0,0 +1,101 @@ +describe ConversionHost do + let(:apst) { FactoryGirl.create(:service_template_ansible_playbook) } + + context "provider independent methods" do + let(:host) { FactoryGirl.create(:host) } + let(:vm) { FactoryGirl.create(:vm_or_template) } + let(:conversion_host_1) { FactoryGirl.create(:conversion_host, :resource => host) } + let(:conversion_host_2) { FactoryGirl.create(:conversion_host, :resource => vm) } + let(:task_1) { FactoryGirl.create(:service_template_transformation_plan_task, :state => 'active', :conversion_host => conversion_host_1) } + let(:task_2) { FactoryGirl.create(:service_template_transformation_plan_task, :conversion_host => conversion_host_1) } + let(:task_3) { FactoryGirl.create(:service_template_transformation_plan_task, :state => 'active', :conversion_host => conversion_host_2) } + + before do + allow(conversion_host_1).to receive(:active_tasks).and_return([task_1]) + allow(conversion_host_2).to receive(:active_tasks).and_return([task_3]) + end + + describe "#check_concurrent_tasks" do + context "default max concurrent tasks is equal to current active tasks" do + before { stub_settings_merge(:transformation => {:limits => {:max_concurrent_tasks_per_host => 1}}) } + it { expect(conversion_host_1.check_concurrent_tasks).to eq(false) } + end + + context "default max concurrent tasks is greater than current active tasks" do + before { stub_settings_merge(:transformation => {:limits => {:max_concurrent_tasks_per_host => 10}}) } + it { expect(conversion_host_1.check_concurrent_tasks).to eq(true) } + end + + context "host's max concurrent tasks is equal to current active tasks" do + before { conversion_host_1.max_concurrent_tasks = "1" } + it { expect(conversion_host_1.check_concurrent_tasks).to eq(false) } + end + + context "host's max concurrent tasks greater than current active tasks" do + before { conversion_host_2.max_concurrent_tasks = "2" } + it { expect(conversion_host_2.check_concurrent_tasks).to eq(true) } + end + end + + context "#source_transport_method" do + it { expect(conversion_host_2.source_transport_method).to be_nil } + + context "ssh transport enabled" do + before { conversion_host_2.ssh_transport_supported = true } + it { expect(conversion_host_2.source_transport_method).to eq('ssh') } + + context "vddk transport enabled" do + before { conversion_host_2.vddk_transport_supported = true } + it { expect(conversion_host_2.source_transport_method).to eq('vddk') } + end + end + end + end + + context "resource provider is rhevm" do + let(:ems) { FactoryGirl.create(:ems_redhat, :zone => FactoryGirl.create(:zone)) } + let(:host) { FactoryGirl.create(:host, :ext_management_system => ems) } + let(:conversion_host) { FactoryGirl.create(:conversion_host, :resource => host, :vddk_transport_supported => true) } + + context "host userid is nil" do + before { allow(host).to receive(:authentication_userid).and_return(nil) } + it { expect(conversion_host.check_resource_credentials).to eq(false) } + end + + context "host userid is set" do + before { allow(host).to receive(:authentication_userid).and_return('root') } + + context "and host password is nil" do + before { allow(host).to receive(:authentication_password).and_return(nil) } + it { expect(conversion_host.check_resource_credentials).to eq(false) } + end + + context "and host password is set" do + before { allow(host).to receive(:authentication_password).and_return('password') } + it { expect(conversion_host.check_resource_credentials).to eq(true) } + end + end + end + + context "resource provider is openstack" do + let(:ems) { FactoryGirl.create(:ems_openstack, :zone => FactoryGirl.create(:zone)) } + let(:vm) { FactoryGirl.create(:vm, :ext_management_system => ems) } + let(:conversion_host) { FactoryGirl.create(:conversion_host, :resource => vm, :vddk_transport_supported => true) } + + context "ems authentications is empty" do + it { expect(conversion_host.check_resource_credentials).to be(false) } + end + + context "ems authentications contains ssh_auth" do + let(:ssh_auth) { FactoryGirl.create(:authentication_ssh_keypair, :resource => ems) } + + it "with fake auth" do + allow(ems).to receive(:authentications).and_return(ssh_auth) + allow(ssh_auth).to receive(:where).with(:authype => 'ssh_keypair').and_return(ssh_auth) + allow(ssh_auth).to receive(:where).and_return(ssh_auth) + allow(ssh_auth).to receive(:not).with(:userid => nil, :auth_key => nil).and_return([ssh_auth]) + expect(conversion_host.check_resource_credentials).to be(true) + end + end + end +end diff --git a/spec/models/service_template_transformation_plan_task_spec.rb b/spec/models/service_template_transformation_plan_task_spec.rb index f379db74cfb..61985907687 100644 --- a/spec/models/service_template_transformation_plan_task_spec.rb +++ b/spec/models/service_template_transformation_plan_task_spec.rb @@ -12,12 +12,15 @@ end end - context 'populated request and task' do + context 'independent of provider' do let(:src) { FactoryGirl.create(:ems_cluster) } let(:dst) { FactoryGirl.create(:ems_cluster) } + let(:host) { FactoryGirl.create(:host, :ext_management_system => FactoryGirl.create(:ext_management_system, :zone => FactoryGirl.create(:zone))) } let(:vm) { FactoryGirl.create(:vm_or_template) } let(:vm2) { FactoryGirl.create(:vm_or_template) } let(:apst) { FactoryGirl.create(:service_template_ansible_playbook) } + let(:conversion_host) { FactoryGirl.create(:conversion_host, :resource => host) } + let(:mapping) do FactoryGirl.create( :transformation_mapping, @@ -102,16 +105,9 @@ end describe '#transformation_log_queue' do - let(:host_id) { 22 } - - before do - task.options[:transformation_host_id] = host_id - task.save! - end - context 'when conversion host exists' do before do - FactoryGirl.create(:host, :id => host_id, :ext_management_system => FactoryGirl.create(:ext_management_system, :zone => FactoryGirl.create(:zone))) + task.conversion_host = conversion_host allow(described_class).to receive(:find).and_return(task) @@ -146,7 +142,7 @@ it 'returns an error message' do taskid = task.transformation_log_queue('user') expect(MiqTask.find(taskid)).to have_attributes( - :message => "Conversion host was not found: ID [#{host_id}]. Cannot queue the download of transformation log.", + :message => "Conversion host was not found. Cannot queue the download of transformation log.", :status => 'Error' ) end @@ -154,11 +150,9 @@ end describe '#transformation_log' do - let(:host) { FactoryGirl.create(:host, :id => 9) } - before do EvmSpecHelper.create_guid_miq_server_zone - task.options[:transformation_host_id] = host.id + task.conversion_host = conversion_host task.options.store_path(:virtv2v_wrapper, "v2v_log", "/path/to/log.file") task.save! @@ -205,4 +199,304 @@ end end end + + context 'populated request and task' do + let(:src_ems) { FactoryGirl.create(:ext_management_system, :zone => FactoryGirl.create(:zone)) } + let(:src_cluster) { FactoryGirl.create(:ems_cluster, :ext_management_system => src_ems) } + let(:dst_ems) { FactoryGirl.create(:ext_management_system, :zone => FactoryGirl.create(:zone)) } + let(:dst_cluster) { FactoryGirl.create(:ems_cluster, :ext_management_system => dst_ems) } + + let(:src_vm_1) { FactoryGirl.create(:vm_or_template, :ext_management_system => src_ems, :ems_cluster => src_cluster) } + let(:src_vm_2) { FactoryGirl.create(:vm_or_template, :ext_management_system => src_ems, :ems_cluster => src_cluster) } + let(:apst) { FactoryGirl.create(:service_template_ansible_playbook) } + + let(:dst_flavor) { FactoryGirl.create(:flavor) } + let(:dst_security_group) { FactoryGirl.create(:security_group) } + + let(:mapping) do + FactoryGirl.create( + :transformation_mapping, + :transformation_mapping_items => [TransformationMappingItem.new(:source => src_cluster, :destination => dst_cluster)] + ) + end + + let(:catalog_item_options) do + { + :name => 'Transformation Plan', + :description => 'a description', + :config_info => { + :transformation_mapping_id => mapping.id, + :pre_service_id => apst.id, + :post_service_id => apst.id, + :osp_flavor => dst_flavor.id, + :osp_security_group => dst_security_group.id, + :actions => [ + {:vm_id => src_vm_1.id.to_s, :pre_service => true, :post_service => true}, + {:vm_id => src_vm_2.id.to_s, :pre_service => false, :post_service => false}, + ], + } + } + end + + let(:plan) { ServiceTemplateTransformationPlan.create_catalog_item(catalog_item_options) } + let(:request) { FactoryGirl.create(:service_template_transformation_plan_request, :source => plan) } + let(:task_1) { FactoryGirl.create(:service_template_transformation_plan_task, :miq_request => request, :request_type => 'transformation_plan', :source => src_vm_1) } + let(:task_2) { FactoryGirl.create(:service_template_transformation_plan_task, :miq_request => request, :request_type => 'transformation_plan', :source => src_vm_2) } + + describe '#transformation_destination' do + it { expect(task_1.transformation_destination(src_cluster)).to eq(dst_cluster) } + end + + describe '#pre_ansible_playbook_service_template' do + it { expect(task_1.pre_ansible_playbook_service_template).to eq(apst) } + it { expect(task_2.pre_ansible_playbook_service_template).to be_nil } + end + + describe '#post_ansible_playbook_service_template' do + it { expect(task_1.post_ansible_playbook_service_template).to eq(apst) } + it { expect(task_2.post_ansible_playbook_service_template).to be_nil } + end + + context 'source is vmwarews' do + let(:src_ems) { FactoryGirl.create(:ems_vmware, :zone => FactoryGirl.create(:zone)) } + let(:src_host) { FactoryGirl.create(:host, :ext_management_system => src_ems, :ipaddress => '10.0.0.1') } + let(:src_storage) { FactoryGirl.create(:storage, :ext_management_system => src_ems) } + + let(:src_lan_1) { FactoryGirl.create(:lan) } + let(:src_lan_2) { FactoryGirl.create(:lan) } + let(:src_nic_1) { FactoryGirl.create(:guest_device_nic, :lan => src_lan_1) } + let(:src_nic_2) { FactoryGirl.create(:guest_device_nic, :lan => src_lan_2) } + + let(:src_disk_1) { instance_double("disk", :device_name => "Hard disk 1", :device_type => "disk", :filename => "[datastore12] test_vm/test_vm.vmdk", :size => 17_179_869_184) } + let(:src_disk_2) { instance_double("disk", :device_name => "Hard disk 2", :device_type => "disk", :filename => "[datastore12] test_vm/test_vm-2.vmdk", :size => 17_179_869_184) } + + let(:src_hardware) { FactoryGirl.create(:hardware, :nics => [src_nic_1, src_nic_2]) } + + let(:src_vm_1) { FactoryGirl.create(:vm_vmware, :ext_management_system => src_ems, :ems_cluster => src_cluster, :host => src_host, :hardware => src_hardware) } + let(:src_vm_2) { FactoryGirl.create(:vm_vmware, :ext_management_system => src_ems, :ems_cluster => src_cluster, :host => src_host) } + + let(:conversion_host) { FactoryGirl.create(:conversion_host) } + + # Disks have to be stubbed because there's no factory for Disk class + before do + allow(src_hardware).to receive(:disks).and_return([src_disk_1, src_disk_2]) + allow(src_disk_1).to receive(:storage).and_return(src_storage) + allow(src_disk_2).to receive(:storage).and_return(src_storage) + allow(src_vm_1).to receive(:allocated_disk_storage).and_return(34_359_738_368) + allow(src_host).to receive(:thumbprint_sha1).and_return('01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef:01:23:45:67') + allow(src_host).to receive(:authentication_userid).and_return('esx_user') + allow(src_host).to receive(:authentication_password).and_return('esx_passwd') + task_1.options[:transformation_host_id] = conversion_host.id + end + + it "finds the source ems based on source vm" do + expect(task_1.source_ems).to eq(src_ems) + end + + it 'find the destination ems based on mapping' do + expect(task_1.destination_ems).to eq(dst_ems) + end + + context 'destination is rhevm' do + let(:dst_ems) { FactoryGirl.create(:ems_redhat, :zone => FactoryGirl.create(:zone)) } + let(:dst_storage) { FactoryGirl.create(:storage) } + let(:dst_lan_1) { FactoryGirl.create(:lan) } + let(:dst_lan_2) { FactoryGirl.create(:lan) } + let(:conversion_host) { FactoryGirl.create(:conversion_host, :resource => FactoryGirl.create(:host, :ext_management_system => dst_ems)) } + + let(:mapping) do + FactoryGirl.create( + :transformation_mapping, + :transformation_mapping_items => [ + TransformationMappingItem.new(:source => src_cluster, :destination => dst_cluster), + TransformationMappingItem.new(:source => src_storage, :destination => dst_storage), + TransformationMappingItem.new(:source => src_lan_1, :destination => dst_lan_1), + TransformationMappingItem.new(:source => src_lan_2, :destination => dst_lan_2) + ] + ) + end + + before do + task_1.conversion_host = conversion_host + end + + it "checks mapping and generates source_disks hash" do + expect(task_1.source_disks).to eq( + [ + { :path => src_disk_1.filename, :size => src_disk_1.size, :percent => 0, :weight => 50.0 }, + { :path => src_disk_2.filename, :size => src_disk_2.size, :percent => 0, :weight => 50.0 } + ] + ) + end + + it "checks network mappings and generates network_mappings hash" do + expect(task_1.network_mappings).to eq( + [ + { :source => src_lan_1.name, :destination => dst_lan_1.name, :mac_address => src_nic_1.address }, + { :source => src_lan_2.name, :destination => dst_lan_2.name, :mac_address => src_nic_2.address } + ] + ) + end + + context "transport method is vddk" do + before do + conversion_host.vddk_transport_supported = true + end + + it "generates conversion options hash" do + expect(task_1.conversion_options).to eq( + :vm_name => src_vm_1.name, + :transport_method => 'vddk', + :vmware_fingerprint => '01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef:01:23:45:67', + :vmware_uri => "esx://esx_user@10.0.0.1/?no_verify=1", + :vmware_password => 'esx_passwd', + :rhv_url => "https://#{dst_ems.hostname}/ovirt-engine/api", + :rhv_cluster => dst_cluster.name, + :rhv_storage => dst_storage.name, + :rhv_password => dst_ems.authentication_password, + :source_disks => [src_disk_1.filename, src_disk_2.filename], + :network_mappings => task_1.network_mappings, + :install_drivers => true, + :insecure_connection => true + ) + end + end + + context "transport method is ssh" do + before do + conversion_host.vddk_transport_supported = false + conversion_host.ssh_transport_supported = true + end + + it "generates conversion options hash" do + expect(task_1.conversion_options).to eq( + :vm_name => "ssh://root@10.0.0.1/vmfs/volumes/#{src_storage.name}/#{src_vm_1.location}", + :transport_method => 'ssh', + :rhv_url => "https://#{dst_ems.hostname}/ovirt-engine/api", + :rhv_cluster => dst_cluster.name, + :rhv_storage => dst_storage.name, + :rhv_password => dst_ems.authentication_password, + :source_disks => [src_disk_1.filename, src_disk_2.filename], + :network_mappings => task_1.network_mappings, + :install_drivers => true, + :insecure_connection => true + ) + end + end + end + + context 'destination is openstack' do + let(:dst_ems) { FactoryGirl.create(:ems_openstack, :zone => FactoryGirl.create(:zone)) } + let(:dst_cloud_tenant) { FactoryGirl.create(:cloud_tenant, :ext_management_system => dst_ems) } + let(:dst_cloud_volume_type) { FactoryGirl.create(:cloud_volume_type) } + let(:dst_cloud_network_1) { FactoryGirl.create(:cloud_network) } + let(:dst_cloud_network_2) { FactoryGirl.create(:cloud_network) } + let(:dst_flavor) { FactoryGirl.create(:flavor) } + let(:dst_security_group) { FactoryGirl.create(:security_group) } + let(:conversion_host) { FactoryGirl.create(:conversion_host, :resource => FactoryGirl.create(:vm, :ext_management_system => dst_ems)) } + + let(:mapping) do + FactoryGirl.create( + :transformation_mapping, + :transformation_mapping_items => [ + TransformationMappingItem.new(:source => src_cluster, :destination => dst_cloud_tenant), + TransformationMappingItem.new(:source => src_storage, :destination => dst_cloud_volume_type), + TransformationMappingItem.new(:source => src_lan_1, :destination => dst_cloud_network_1), + TransformationMappingItem.new(:source => src_lan_2, :destination => dst_cloud_network_2) + ] + ) + end + + before do + task_1.conversion_host = conversion_host + end + + it "checks mapping and generates source_disks hash" do + expect(task_1.source_disks).to eq( + [ + { :path => src_disk_1.filename, :size => src_disk_1.size, :percent => 0, :weight => 50.0 }, + { :path => src_disk_2.filename, :size => src_disk_2.size, :percent => 0, :weight => 50.0 } + ] + ) + end + + it "checks network mappings and generates network_mappings hash" do + expect(task_1.network_mappings).to eq( + [ + { :source => src_lan_1.name, :destination => dst_cloud_network_1.ems_ref, :mac_address => src_nic_1.address }, + { :source => src_lan_2.name, :destination => dst_cloud_network_2.ems_ref, :mac_address => src_nic_2.address } + ] + ) + end + + context "transport method is vddk" do + before do + conversion_host.vddk_transport_supported = true + end + + it "generates conversion options hash" do + expect(task_1.conversion_options).to eq( + :vm_name => src_vm_1.name, + :transport_method => 'vddk', + :vmware_fingerprint => '01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef:01:23:45:67', + :vmware_uri => "esx://esx_user@10.0.0.1/?no_verify=1", + :vmware_password => 'esx_passwd', + :osp_environment => { + :os_no_cache => true, + :os_auth_url => URI::Generic.build( + :scheme => dst_ems.security_protocol == 'non-ssl' ? 'http' : 'https', + :host => dst_ems.hostname, + :port => dst_ems.port, + :path => dst_ems.api_version + ), + :os_user_domain_name => dst_ems.uid_ems, + :os_username => dst_ems.authentication_userid, + :os_password => dst_ems.authentication_password, + :os_project_name => dst_cloud_tenant.name + }, + :osp_destination_project_id => dst_cloud_tenant.ems_ref, + :osp_volume_type_id => dst_cloud_volume_type.ems_ref, + :osp_flavor_id => dst_flavor.ems_ref, + :osp_security_groups_ids => [dst_security_group.ems_ref], + :source_disks => [src_disk_1.filename, src_disk_2.filename], + :network_mappings => task_1.network_mappings + ) + end + end + + context "transport method is ssh" do + before do + conversion_host.vddk_transport_supported = false + conversion_host.ssh_transport_supported = true + end + + it "generates conversion options hash" do + expect(task_1.conversion_options).to eq( + :vm_name => "ssh://root@10.0.0.1/vmfs/volumes/#{src_storage.name}/#{src_vm_1.location}", + :transport_method => 'ssh', + :osp_environment => { + :os_no_cache => true, + :os_auth_url => URI::Generic.build( + :scheme => dst_ems.security_protocol == 'non-ssl' ? 'http' : 'https', + :host => dst_ems.hostname, + :port => dst_ems.port, + :path => dst_ems.api_version + ), + :os_user_domain_name => dst_ems.uid_ems, + :os_username => dst_ems.authentication_userid, + :os_password => dst_ems.authentication_password, + :os_project_name => dst_cloud_tenant.name + }, + :osp_destination_project_id => dst_cloud_tenant.ems_ref, + :osp_volume_type_id => dst_cloud_volume_type.ems_ref, + :osp_flavor_id => dst_flavor.ems_ref, + :osp_security_groups_ids => [dst_security_group.ems_ref], + :source_disks => [src_disk_1.filename, src_disk_2.filename], + :network_mappings => task_1.network_mappings + ) + end + end + end + end + end end