diff --git a/app/models/transformation_mapping.rb b/app/models/transformation_mapping.rb index 925e717b4392..b15628601902 100644 --- a/app/models/transformation_mapping.rb +++ b/app/models/transformation_mapping.rb @@ -1,11 +1,5 @@ class TransformationMapping < ApplicationRecord - VM_CONFLICT = "conflict".freeze - VM_EMPTY_NAME = "empty_name".freeze - VM_IN_OTHER_PLAN = "in_other_plan".freeze - VM_MIGRATED = "migrated".freeze - VM_NOT_EXIST = "not_exist".freeze - VM_VALID = "ok".freeze - VM_INACTIVE = "inactive".freeze + require_nested :VmMigrationValidator has_many :transformation_mapping_items, :dependent => :destroy has_many :service_resources, :as => :resource, :dependent => :nullify @@ -18,124 +12,7 @@ def destination(source) end # vm_list: collection of hashes, each descriping a VM. - def validate_vms(vm_list = nil) - vm_list.present? ? identify_vms(vm_list) : select_vms - end - - private - - def select_vms - valid_list = [] - - transformation_mapping_items.where(:source_type => EmsCluster).collect(&:source).each do |cluster| - cluster.vms.each do |vm| - reason = validate_vm(vm, true) - valid_list << describe_vm(vm, reason) if reason == VM_VALID - end - end - - {"valid_vms" => valid_list} - end - - def identify_vms(vm_list) - valid_list = [] - invalid_list = [] - conflict_list = [] - - vm_list.each do |row| - vm_name = row['name'] - - if vm_name.blank? - invalid_list << describe_non_vm(vm_name) - next - end - - query = Vm.where(:name => vm_name) - query = query.where(:uid_ems => row['uid_ems']) if row['uid_ems'].present? - query = query.joins(:host).where(:hosts => {:name => row['host']}) if row['host'].present? - query = query.joins(:ext_management_system).where(:ext_management_systems => {:name => row['provider']}) if row['provider'].present? - - vms = query.to_a - if vms.size.zero? - invalid_list << describe_non_vm(vm_name) - elsif vms.size == 1 - reason = validate_vm(vms.first, false) - (reason == VM_VALID ? valid_list : invalid_list) << describe_vm(vms.first, reason) - else - vms.each { |vm| conflict_list << describe_vm(vm, VM_CONFLICT) } - end - end - - { - "valid_vms" => valid_list, - "invalid_vms" => invalid_list, - "conflict_vms" => conflict_list - } - end - - def describe_non_vm(vm_name) - { - "name" => vm_name, - "reason" => vm_name.blank? ? VM_EMPTY_NAME : VM_NOT_EXIST - } - end - - def describe_vm(vm, reason) - { - "name" => vm.name, - "cluster" => vm.ems_cluster.try(:name) || '', - "path" => vm.ext_management_system ? "#{vm.ext_management_system.name}/#{vm.v_parent_blue_folder_display_path}" : '', - "allocated_size" => vm.allocated_disk_storage, - "id" => vm.id, - "ems_cluster_id" => vm.ems_cluster_id, - "reason" => reason - } - end - - def validate_vm(vm, quick = true) - validate_result = vm.validate_v2v_migration - return validate_result unless validate_result == VM_VALID - - # a valid vm must find all resources in the mapping and has never been migrated - invalid_list = [] - - unless valid_cluster?(vm) - invalid_list << "cluster: %{name}" % {:name => vm.ems_cluster.name} - return no_mapping_msg(invalid_list) if quick - end - - invalid_storages = unmapped_storages(vm) - if invalid_storages.present? - invalid_list << "storages: %{list}" % {:list => invalid_storages.collect(&:name).join(", ")} - return no_mapping_msg(invalid_list) if quick - end - - invalid_lans = unmapped_lans(vm) - if invalid_lans.present? - invalid_list << "lans: %{list}" % {:list => invalid_lans.collect(&:name).join(', ')} - return no_mapping_msg(invalid_list) if quick - end - - invalid_list.present? ? no_mapping_msg(invalid_list) : VM_VALID - end - - def no_mapping_msg(list) - "Mapping source not found - %{list}" % {:list => list.join('. ')} - end - - def valid_cluster?(vm) - transformation_mapping_items.where(:source => vm.ems_cluster).exists? - end - - # return an empty array if all storages are valid for transformation - # otherwise return an array of invalid datastores - def unmapped_storages(vm) - vm.datastores - transformation_mapping_items.where(:source => vm.datastores).collect(&:source) - end - - # return an empty array if all lans are valid for transformation - # otherwise return an array of invalid lans - def unmapped_lans(vm) - vm.lans - transformation_mapping_items.where(:source => vm.lans).collect(&:source) + def search_vms_and_validate(vm_list = nil) + VmMigrationValidator.new(self, vm_list).validate end end diff --git a/app/models/transformation_mapping/vm_migration_validator.rb b/app/models/transformation_mapping/vm_migration_validator.rb new file mode 100644 index 000000000000..1f231b21f3cd --- /dev/null +++ b/app/models/transformation_mapping/vm_migration_validator.rb @@ -0,0 +1,144 @@ +class TransformationMapping::VmMigrationValidator + require 'miq-hash_struct' + + VM_CONFLICT = "conflict".freeze + VM_EMPTY_NAME = "empty_name".freeze + VM_IN_OTHER_PLAN = "in_other_plan".freeze + VM_INACTIVE = "inactive".freeze + VM_INVALID = "invalid".freeze + VM_MIGRATED = "migrated".freeze + VM_NOT_EXIST = "not_exist".freeze + VM_VALID = "ok".freeze + + def initialize(mapping, vm_list = nil) + @mapping = mapping + @vm_list = vm_list + end + + def validate + @vm_list.present? ? identify_vms : select_vms + end + + def select_vms + valid_list = [] + + Vm.where(:ems_cluster => mapped_clusters).includes(:lans, :storages).each do |vm| + reason = validate_vm(vm, true) + valid_list << VmMigrateStruct.new(vm.name, vm, VM_VALID, reason) if reason == VM_VALID + end + + {"valid" => valid_list} + end + + def identify_vms + valid_list = [] + invalid_list = [] + conflict_list = [] + + vm_names = @vm_list.collect { |row| row['name'] } + vm_objects = Vm.where(:name => vm_names, :ems_cluster => mapped_clusters).includes(:lans, :storages, :host, :ext_management_system) + + @vm_list.each do |row| + vm_name = row['name'] + + if vm_name.blank? + invalid_list << VmMigrateStruct.new('', nil, VM_INVALID, VM_EMPTY_NAME) + next + end + + vms = vm_objects.select { |vm| vm.name == vm_name } + vms = vms.select { |vm| vm.uid_ems == row['uid_ems'] } if row['uid_ems'].present? + vms = vms.select { |vm| vm.host.name == row['host'] } if row['host'].present? + vms = vms.select { |vm| vm.ext_management_system.name == row['provider'] } if row['provider'].present? + + if vms.empty? + invalid_list << VmMigrateStruct.new(vm_name, nil, VM_INVALID, VM_NOT_EXIST) + elsif vms.size == 1 + vm = vms.first + reason = validate_vm(vm, false) + if reason == VM_VALID + valid_list << VmMigrateStruct.new(vm.name, vm, VM_VALID, reason) + else + invalid_list << VmMigrateStruct.new(vm.name, vm, VM_INVALID, reason) + end + else + vms.each { |v| conflict_list << VmMigrateStruct.new(v.name, v, VM_CONFLICT, VM_CONFLICT) } + end + end + + { + "valid" => valid_list, + "invalid" => invalid_list, + "conflicted" => conflict_list + } + end + + def validate_vm(vm, quick = true) + validate_result = vm_migration_status(vm) + return validate_result unless validate_result == VM_VALID + + # a valid vm must find all resources in the mapping and has never been migrated + invalid_list = [] + + invalid_storages = vm.datastores - mapped_storages + if invalid_storages.present? + invalid_list << "storages: %{list}" % {:list => invalid_storages.collect(&:name).join(", ")} + return no_mapping_msg(invalid_list) if quick + end + + invalid_lans = vm.lans - mapped_lans + if invalid_lans.present? + invalid_list << "lans: %{list}" % {:list => invalid_lans.collect(&:name).join(', ')} + return no_mapping_msg(invalid_list) if quick + end + + invalid_list.present? ? no_mapping_msg(invalid_list) : VM_VALID + end + + def vm_migration_status(vm) + return VM_INACTIVE unless vm.active? + + vm_as_resources = ServiceResource.joins(:service_template).where(:resource => vm, :service_templates => {:type => 'ServiceTemplateTransformationPlan'}) + + # VM has not been migrated before + return VM_VALID if vm_as_resources.empty? + + return VM_MIGRATED unless vm_as_resources.where(:status => ServiceResource::STATUS_COMPLETED).empty? + + # VM failed in previous migration + vm_as_resources.all? { |rsc| rsc.status == ServiceResource::STATUS_FAILED } ? VM_VALID : VM_IN_OTHER_PLAN + end + + def no_mapping_msg(list) + "Mapping source not found - %{list}" % {:list => list.join('. ')} + end + + def mapped_clusters + @mapped_clusters ||= EmsCluster.where(:id => @mapping.transformation_mapping_items.where(:source_type => 'EmsCluster').select(:source_id)) + end + + def mapped_storages + @mapped_storages ||= Storage.where(:id => @mapping.transformation_mapping_items.where(:source_type => 'Storage').select(:source_id)) + end + + def mapped_lans + @mapped_lans ||= Lan.where(:id => @mapping.transformation_mapping_items.where(:source_type => 'Lan').select(:source_id)) + end + + class VmMigrateStruct < MiqHashStruct + def initialize(vm_name, vm, status, reason) + options = {"name" => vm_name, "status" => status, "reason" => reason} + + if vm.present? + options.merge!( + "cluster" => vm.ems_cluster.try(:name) || '', + "path" => vm.ext_management_system ? "#{vm.ext_management_system.name}/#{vm.v_parent_blue_folder_display_path}" : '', + "allocated_size" => vm.allocated_disk_storage, + "id" => vm.id.to_s + ) + end + + super(options) + end + end +end diff --git a/app/models/vm.rb b/app/models/vm.rb index 1dc2a56d9398..e00ba6aaf752 100644 --- a/app/models/vm.rb +++ b/app/models/vm.rb @@ -122,20 +122,6 @@ def supported_consoles } end - def validate_v2v_migration - return TransformationMapping::VM_INACTIVE unless active? - - vm_as_resources = ServiceResource.where(:resource => self).includes(:service_template).where(:service_templates => {:type => "ServiceTemplateTransformationPlan"}) - - # VM has not been migrated before - return TransformationMapping::VM_VALID if vm_as_resources.blank? - - return TransformationMapping::VM_MIGRATED if vm_as_resources.any? { |rsc| rsc.status == ServiceResource::STATUS_COMPLETED } - - # VM failed in previous migration - vm_as_resources.all? { |rsc| rsc.status == ServiceResource::STATUS_FAILED } ? TransformationMapping::VM_VALID : TransformationMapping::VM_IN_OTHER_PLAN - end - def self.display_name(number = 1) n_('VM and Instance', 'VMs and Instances', number) end diff --git a/spec/models/transformation_mapping_spec.rb b/spec/models/transformation_mapping_spec.rb index ca3534d5f52f..a14f5012e00e 100644 --- a/spec/models/transformation_mapping_spec.rb +++ b/spec/models/transformation_mapping_spec.rb @@ -29,7 +29,7 @@ end end - describe '#validate_vms' do + describe '#search_vms_and_validate' do let(:vm) { FactoryGirl.create(:vm_vmware, :name => 'test_vm', :ems_cluster => src, :ext_management_system => FactoryGirl.create(:ext_management_system)) } let(:inactive_vm) { FactoryGirl.create(:vm_vmware, :name => 'test_vm_inactive', :ems_cluster => src, :ext_management_system => nil) } let(:storage) { FactoryGirl.create(:storage) } @@ -46,14 +46,14 @@ context 'with VM list' do context 'returns invalid vms' do it 'if VM does not exist' do - result = mapping.validate_vms(['name' => 'vm1']) - expect(result['invalid_vms'].first).to match(hash_including('reason' => TransformationMapping::VM_NOT_EXIST)) + result = mapping.search_vms_and_validate(['name' => 'vm1']) + expect(result['invalid'].first.reason).to eq(TransformationMapping::VmMigrationValidator::VM_NOT_EXIST) end it 'if VM is inactive' do inactive_vm.storages << FactoryGirl.create(:storage, :name => 'storage_for_inactive_vm') - result = mapping.validate_vms(['name' => 'test_vm_inactive']) - expect(result['invalid_vms'].first).to match(hash_including('reason' => TransformationMapping::VM_INACTIVE)) + result = mapping.search_vms_and_validate(['name' => 'test_vm_inactive']) + expect(result['invalid'].first.reason).to eq(TransformationMapping::VmMigrationValidator::VM_INACTIVE) end it "if VM's cluster is not in the mapping" do @@ -63,27 +63,27 @@ :ems_cluster => FactoryGirl.create(:ems_cluster, :name => 'cluster1'), :ext_management_system => FactoryGirl.create(:ext_management_system) ) - result = mapping.validate_vms(['name' => 'vm2']) - expect(result['invalid_vms'].first['reason']).to match(/Mapping source not found - cluster: cluster1/) + result = mapping.search_vms_and_validate(['name' => 'vm2']) + expect(result['invalid'].first.reason).to match(/not_exist/) end it "if VM's storages are not all in the mapping" do vm.storages << FactoryGirl.create(:storage, :name => 'storage2') - result = mapping.validate_vms(['name' => vm.name]) - expect(result['invalid_vms'].first['reason']).to match(/Mapping source not found - storages: storage2/) + result = mapping.search_vms_and_validate(['name' => vm.name]) + expect(result['invalid'].first.reason).to match(/Mapping source not found - storages: storage2/) end it "if VM's lans are not all in the mapping" do vm.hardware.guest_devices << FactoryGirl.create(:guest_device_nic, :lan =>FactoryGirl.create(:lan, :name => 'lan2')) - result = mapping.validate_vms(['name' => vm.name]) - expect(result['invalid_vms'].first['reason']).to match(/Mapping source not found - lans: lan2/) + result = mapping.search_vms_and_validate(['name' => vm.name]) + expect(result['invalid'].first.reason).to match(/Mapping source not found - lans: lan2/) end it "if any source is invalid" do vm.storages << FactoryGirl.create(:storage, :name => 'storage2') vm.hardware.guest_devices << FactoryGirl.create(:guest_device_nic, :lan =>FactoryGirl.create(:lan, :name => 'lan2')) - result = mapping.validate_vms(['name' => vm.name]) - expect(result['invalid_vms'].first['reason']).to match(/Mapping source not found - storages: storage2. lans: lan2/) + result = mapping.search_vms_and_validate(['name' => vm.name]) + expect(result['invalid'].first.reason).to match(/Mapping source not found - storages: storage2. lans: lan2/) end it 'if VM is in another migration plan' do @@ -95,8 +95,8 @@ :status => status ) - result = mapping.validate_vms(['name' => vm.name]) - expect(result['invalid_vms'].first['reason']).to match(/in_other_plan/) + result = mapping.search_vms_and_validate(['name' => vm.name]) + expect(result['invalid'].first.reason).to match(/in_other_plan/) end end @@ -108,27 +108,27 @@ :status => 'Completed' ) - result = mapping.validate_vms(['name' => vm.name]) - expect(result['invalid_vms'].first['reason']).to match(/migrated/) + result = mapping.search_vms_and_validate(['name' => vm.name]) + expect(result['invalid'].first.reason).to match(/migrated/) end end it 'returns valid vms' do - result = mapping.validate_vms(['name' => vm.name]) - expect(result['valid_vms'].first).to match(hash_including('reason' => TransformationMapping::VM_VALID)) + result = mapping.search_vms_and_validate(['name' => vm.name]) + expect(result['valid'].first.reason).to eq(TransformationMapping::VmMigrationValidator::VM_VALID) end it 'returns conflict vms' do FactoryGirl.create(:vm_vmware, :name => 'test_vm', :ems_cluster => src, :ext_management_system => FactoryGirl.create(:ext_management_system)) - result = mapping.validate_vms(['name' => vm.name]) - expect(result['conflict_vms'].first).to match(hash_including('reason' => TransformationMapping::VM_CONFLICT)) + result = mapping.search_vms_and_validate(['name' => vm.name]) + expect(result['conflicted'].first.reason).to eq(TransformationMapping::VmMigrationValidator::VM_CONFLICT) end end context 'without VM list' do it 'returns valid vms' do - result = mapping.validate_vms - expect(result['valid_vms'].count).to eq(1) + result = mapping.search_vms_and_validate + expect(result['valid'].count).to eq(1) end it 'skips invalid vms' do @@ -138,8 +138,8 @@ :ems_cluster => FactoryGirl.create(:ems_cluster, :name => 'cluster1'), :ext_management_system => FactoryGirl.create(:ext_management_system) ) - result = mapping.validate_vms - expect(result['valid_vms'].count).to eq(1) + result = mapping.search_vms_and_validate + expect(result['valid'].count).to eq(1) end end end