Skip to content

Commit

Permalink
Add VmMigrationValidator.
Browse files Browse the repository at this point in the history
  • Loading branch information
lfu committed May 2, 2018
1 parent 2d81fce commit fbc1021
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 145 deletions.
125 changes: 3 additions & 122 deletions app/models/transformation_mapping.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
145 changes: 145 additions & 0 deletions app/models/transformation_mapping/vm_migration_validator.rb
Original file line number Diff line number Diff line change
@@ -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
46 changes: 23 additions & 23 deletions spec/models/transformation_mapping_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit fbc1021

Please sign in to comment.