From fbc1021c31d93830e451469077ffbff02d32fb20 Mon Sep 17 00:00:00 2001 From: Lucy Fu Date: Fri, 27 Apr 2018 12:05:33 -0400 Subject: [PATCH] Add VmMigrationValidator. --- app/models/transformation_mapping.rb | 125 +-------------- .../vm_migration_validator.rb | 145 ++++++++++++++++++ spec/models/transformation_mapping_spec.rb | 46 +++--- 3 files changed, 171 insertions(+), 145 deletions(-) create mode 100644 app/models/transformation_mapping/vm_migration_validator.rb diff --git a/app/models/transformation_mapping.rb b/app/models/transformation_mapping.rb index 4e351de50e23..b15628601902 100644 --- a/app/models/transformation_mapping.rb +++ b/app/models/transformation_mapping.rb @@ -1,10 +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 + require_nested :VmMigrationValidator has_many :transformation_mapping_items, :dependent => :destroy has_many :service_resources, :as => :resource, :dependent => :nullify @@ -17,121 +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.name, - "path" => "#{vm.ext_management_system.name}/#{vm.parent_blue_folder_path(:exclude_non_display_folders => true)}", - "allocated_size" => vm.allocated_disk_storage, - "id" => vm.id, - "reason" => reason - } - end - - def validate_vm(vm, quick = true) - # 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 - - return no_mapping_msg(invalid_list) if invalid_list.present? - vm.validate_v2v_migration - 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..1826ffeafc94 --- /dev/null +++ b/app/models/transformation_mapping/vm_migration_validator.rb @@ -0,0 +1,145 @@ +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_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 = [] + + @mapping.transformation_mapping_items.includes(:source => :vms).where(:source_type => EmsCluster).collect(&:source).flat_map(&:vms).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_list.each do |row| + vm_name = row['name'] + + if vm_name.blank? + invalid_list << VmMigrateStruct.new('', nil, VM_INVALID, VM_EMPTY_NAME) + next + end + + query = Vm.where(:name => vm_name, :ems_cluster => valid_clusters) + 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 << 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) + # a valid vm must find all resources in the mapping and has never been migrated + invalid_list = [] + + 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 + + return no_mapping_msg(invalid_list) if invalid_list.present? + vm_migration_status(vm) + end + + def vm_migration_status(vm) + 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 valid_cluster?(vm) + @mapping.transformation_mapping_items.where(:source => vm.ems_cluster).exists? + end + + def valid_clusters + @mapping.transformation_mapping_items.where(:source_type => EmsCluster).collect(&:source) + 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 - @mapping.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 - @mapping.transformation_mapping_items.where(:source => vm.lans).collect(&:source) + 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.name}/#{vm.parent_blue_folder_path(:exclude_non_display_folders => true)}", + "allocated_size" => vm.allocated_disk_storage, + "id" => vm.id + ) + end + + super(options) + end + end +end diff --git a/spec/models/transformation_mapping_spec.rb b/spec/models/transformation_mapping_spec.rb index b63d15afe894..31c2f9b75ade 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(:storage) { FactoryGirl.create(:storage) } let(:lan) { FactoryGirl.create(:lan) } @@ -45,8 +45,8 @@ 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's cluster is not in the mapping" do @@ -56,27 +56,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 @@ -88,8 +88,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 @@ -101,27 +101,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 @@ -131,8 +131,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