Skip to content

Commit

Permalink
Add TransformationMapping#search_vms_and_validate method
Browse files Browse the repository at this point in the history
  • Loading branch information
bzwei authored and lfu committed Apr 25, 2018
1 parent e5d11d7 commit 978b97f
Show file tree
Hide file tree
Showing 3 changed files with 260 additions and 0 deletions.
7 changes: 7 additions & 0 deletions app/models/transformation_mapping.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class TransformationMapping < ApplicationRecord
require_nested :VmMigrationValidator

has_many :transformation_mapping_items, :dependent => :destroy
has_many :service_resources, :as => :resource, :dependent => :nullify
has_many :service_templates, :through => :service_resources
Expand All @@ -8,4 +10,9 @@ class TransformationMapping < ApplicationRecord
def destination(source)
transformation_mapping_items.find_by(:source => source).try(:destination)
end

# vm_list: collection of hashes, each descriping a VM.
def search_vms_and_validate(vm_list = nil)
VmMigrationValidator.new(self, vm_list).validate
end
end
144 changes: 144 additions & 0 deletions app/models/transformation_mapping/vm_migration_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
class TransformationMapping::VmMigrationValidator
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

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.where(:source_type => EmsCluster).collect(&:source).collect(&:vms).flatten.each do |vm|
reason = validate_vm(vm, true)
valid_list << VmMigrateStruct.new(vm, 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, '')
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 << VmMigrateStruct.new(nil, vm_name)
elsif vms.size == 1
reason = validate_vm(vms.first, false)
(reason == VM_VALID ? valid_list : invalid_list) << VmMigrateStruct.new(vms.first, reason)
else
vms.each { |vm| conflict_list << VmMigrateStruct.new(vm, 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 = []

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_migration_status(vm)
end

def vm_migration_status(vm)
vm_as_resources = ServiceResource.where(:resource => vm).includes(:service_template).where(:service_templates => {:type => "ServiceTemplateTransformationPlan"})

# VM has not been migrated before
return VM_VALID if vm_as_resources.blank?

return 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 } ? 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

# 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, reason)
options =
if vm.nil?
{
"name" => reason,
"reason" => reason.blank? ? VM_EMPTY_NAME : VM_NOT_EXIST
}
else
{
"name" => vm.name,
"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,
"reason" => reason
}
end

super(options)
end
end
end
109 changes: 109 additions & 0 deletions spec/models/transformation_mapping_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
describe TransformationMapping do
let(:src) { FactoryGirl.create(:ems_cluster) }
let(:dst) { FactoryGirl.create(:ems_cluster) }
let(:vm) { FactoryGirl.create(:vm_vmware, :ems_cluster => src) }

let(:mapping) do
FactoryGirl.create(
Expand All @@ -27,4 +28,112 @@
expect(mapping.service_templates).to match([plan])
end
end

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) }
let(:nic) { FactoryGirl.create(:guest_device_nic, :lan => lan) }

before do
mapping.transformation_mapping_items << TransformationMappingItem.new(:source => storage, :destination => storage)
mapping.transformation_mapping_items << TransformationMappingItem.new(:source => lan, :destination => lan)
vm.storages << storage
vm.hardware = FactoryGirl.create(:hardware, :guest_devices => [nic])
end

context 'with VM list' do
context 'returns invalid vms' do
it 'if VM does not exist' do
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
FactoryGirl.create(
:vm_vmware,
:name => 'vm2',
:ems_cluster => FactoryGirl.create(:ems_cluster, :name => 'cluster1'),
:ext_management_system => FactoryGirl.create(:ext_management_system)
)
result = mapping.search_vms_and_validate(['name' => 'vm2'])
expect(result['invalid'].first.reason).to match(/Mapping source not found - cluster: cluster1/)
end

it "if VM's storages are not all in the mapping" do
vm.storages << FactoryGirl.create(:storage, :name => '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.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.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
%w(Queued Approved Active).each do |status|
FactoryGirl.create(
:service_resource,
:resource => vm,
:service_template => FactoryGirl.create(:service_template_transformation_plan),
:status => status
)

result = mapping.search_vms_and_validate(['name' => vm.name])
expect(result['invalid'].first.reason).to match(/in_other_plan/)
end
end

it 'if VM has been migrated' do
FactoryGirl.create(
:service_resource,
:resource => vm,
:service_template => FactoryGirl.create(:service_template_transformation_plan),
:status => 'Completed'
)

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.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.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.search_vms_and_validate
expect(result['valid'].count).to eq(1)
end

it 'skips invalid vms' do
FactoryGirl.create(
:vm_vmware,
:name => 'vm2',
:ems_cluster => FactoryGirl.create(:ems_cluster, :name => 'cluster1'),
:ext_management_system => FactoryGirl.create(:ext_management_system)
)
result = mapping.search_vms_and_validate
expect(result['valid'].count).to eq(1)
end
end
end
end

0 comments on commit 978b97f

Please sign in to comment.