diff --git a/db/migrate/20200607025146_add_ancestry_to_vm.rb b/db/migrate/20200607025146_add_ancestry_to_vm.rb new file mode 100644 index 000000000..196e5dd45 --- /dev/null +++ b/db/migrate/20200607025146_add_ancestry_to_vm.rb @@ -0,0 +1,56 @@ +class AddAncestryToVm < ActiveRecord::Migration[5.2] + class VmOrTemplate < ActiveRecord::Base + self.inheritance_column = :_type_disabled + self.table_name = 'vms' + has_many :all_relationships, :class_name => "AddAncestryToVm::Relationship", :dependent => :destroy, :as => :resource + end + + class Vm < VmOrTemplate + end + + class Relationship < ActiveRecord::Base + belongs_to :vm, :class_name => 'AddAncestryToVm::VmOrTemplate', :foreign_key => :vm_id + end + + def up + add_column :vms, :ancestry, :string + add_index :vms, :ancestry + + say_with_time("set vm ancestry from existing genealogy relationship resource information") do + connection.execute <<-SQL + UPDATE vms + SET ancestry = new_ancestry + FROM ( + SELECT ancestor_resources_for_vms.vm_id AS vm_id, + ARRAY_TO_STRING(ARRAY_AGG(res_rels.resource_id)::VARCHAR[], '/') AS new_ancestry + FROM ( + SELECT vms.id AS vm_id, relationships.id AS rel_id + FROM vms + JOIN relationships a_rels ON a_rels.resource_id = vms.id + AND a_rels.relationship = 'genealogy' + AND a_rels.resource_type = 'VmOrTemplate' + LEFT JOIN LATERAL UNNEST(STRING_TO_ARRAY(a_rels.ancestry, '/')::BIGINT[]) + WITH ORDINALITY AS relationships(id, indx) ON TRUE + ORDER BY vms.id, relationships.indx + ) AS ancestor_resources_for_vms + JOIN relationships res_rels ON res_rels.id = ancestor_resources_for_vms.rel_id AND res_rels.relationship = 'genealogy' + GROUP BY ancestor_resources_for_vms.vm_id + ) AS new_ancestors_for_vms + WHERE new_ancestors_for_vms.vm_id = vms.id + SQL + end + + Relationship.where(:relationship => 'genealogy', :resource_type => 'VmOrTemplate', :resource_id => Vm.all.select(:id)).delete_all + end + + def down + say_with_time("create relationship records from vm ancestry") do + vms_with_ancestry = Vm.select { |vm| vm.ancestry.present? } + vms_with_ancestry.each do |vm| + Relationship.create!(:relationship => 'genealogy', :resource_type => 'VmOrTemplate', :resource_id => vm.id, :ancestry => vm.ancestry) + end + + remove_column :vms, :ancestry + end + end +end diff --git a/spec/migrations/20200607025146_add_ancestry_to_vm_spec.rb b/spec/migrations/20200607025146_add_ancestry_to_vm_spec.rb new file mode 100644 index 000000000..c3c8cdfee --- /dev/null +++ b/spec/migrations/20200607025146_add_ancestry_to_vm_spec.rb @@ -0,0 +1,133 @@ +require_migration + +describe AddAncestryToVm do + let(:rel_stub) { migration_stub(:Relationship) } + let(:vm_stub) { migration_stub :VmOrTemplate } + let(:vm) { vm_stub.create! } + let(:vm2) { vm_stub.create! } + let(:vm3) { vm_stub.create! } + let(:vm4) { vm_stub.create! } + let(:vm5) { vm_stub.create! } + let(:vm6) { vm_stub.create! } + let(:root) { vm_stub.create! } + + migration_context :up do + context "single parent/child rel" do + it 'updates ancestry' do + parent_rel = rel_stub.create!(:relationship => 'genealogy', :ancestry => nil, :resource_type => 'VmOrTemplate', :resource_id => root.id) + rel_stub.create!(:relationship => 'genealogy', :ancestry => parent_rel.id, :resource_type => 'VmOrTemplate', :resource_id => vm.id) + + migrate + + expect(vm.reload.ancestry).to eq(root.id.to_s) + expect(root.reload.ancestry).to eq(nil) + expect(AddAncestryToVm::Relationship.count).to eq(0) + end + end + + context "slightly more complicated tree" do + it 'updates ancestry' do + parent_rel = rel_stub.create!(:relationship => 'genealogy', :ancestry => nil, :resource_type => 'VmOrTemplate', :resource_id => root.id) + child_rel = rel_stub.create!(:relationship => 'genealogy', :ancestry => parent_rel.id, :resource_type => 'VmOrTemplate', :resource_id => vm.id) + rel_stub.create!(:relationship => 'genealogy', :ancestry => child_rel.id.to_s + '/' + parent_rel.id.to_s, :resource_type => 'VmOrTemplate', :resource_id => vm2.id) + + migrate + + expect(vm.reload.ancestry).to eq(root.id.to_s) + expect(vm2.reload.ancestry).to eq("#{vm.id}/#{root.id}") + expect(root.reload.ancestry).to eq(nil) + expect(AddAncestryToVm::Relationship.count).to eq(0) + end + end + + context "complicated tree" do + # a + # b c + # d g + # e f + it 'updates ancestry' do + rel_a = rel_stub.create!(:relationship => 'genealogy', :ancestry => nil, :resource_type => 'VmOrTemplate', :resource_id => root.id) + rel_c = rel_stub.create!(:relationship => 'genealogy', :ancestry => rel_a.id, :resource_type => 'VmOrTemplate', :resource_id => vm.id) + rel_stub.create!(:relationship => 'genealogy', :ancestry => rel_c.id.to_s + '/' + rel_a.id.to_s, :resource_type => 'VmOrTemplate', :resource_id => vm2.id) + rel_b = rel_stub.create!(:relationship => 'genealogy', :ancestry => rel_a.id.to_s, :resource_type => 'VmOrTemplate', :resource_id => vm3.id) + rel_d = rel_stub.create!(:relationship => 'genealogy', :ancestry => rel_b.id.to_s + '/' + rel_a.id.to_s, :resource_type => 'VmOrTemplate', :resource_id => vm4.id) + rel_stub.create!(:relationship => 'genealogy', :ancestry => rel_d.id.to_s + '/' + rel_b.id.to_s + '/' + rel_a.id.to_s, :resource_type => 'VmOrTemplate', :resource_id => vm5.id) + rel_stub.create!(:relationship => 'genealogy', :ancestry => rel_d.id.to_s + '/' + rel_b.id.to_s + '/' + rel_a.id.to_s, :resource_type => 'VmOrTemplate', :resource_id => vm6.id) + + migrate + + expect(vm5.reload.ancestry).to eq("#{vm4.id}/#{vm3.id}/#{root.id}") + expect(vm6.reload.ancestry).to eq("#{vm4.id}/#{vm3.id}/#{root.id}") + expect(vm3.reload.ancestry).to eq(root.id.to_s) + expect(vm.reload.ancestry).to eq(root.id.to_s) + expect(vm2.reload.ancestry).to eq("#{vm.id}/#{root.id}") + expect(root.reload.ancestry).to eq(nil) + expect(AddAncestryToVm::Relationship.count).to eq(0) + end + end + + context "vm without rels" do + it 'nil ancestry' do + migrate + + expect(AddAncestryToVm::Vm.find(vm.id).ancestry).to eq(nil) + end + end + + context "with only ems_metadata relationship tree" do + it 'sets nothing' do + parent_rel = rel_stub.create!(:relationship => 'ems_metadata', :ancestry => nil, :resource_type => 'VmOrTemplate', :resource_id => root.id) + rel_stub.create!(:relationship => 'ems_metadata', :ancestry => parent_rel.id, :resource_type => 'VmOrTemplate', :resource_id => vm.id) + + migrate + + expect(vm.reload.ancestry).to eq(nil) + expect(root.reload.ancestry).to eq(nil) + expect(AddAncestryToVm::Relationship.count).to eq(2) + end + end + + context "with both genealogy and ems_metadata rels" do + it 'only sets ancestry from genealogy rels' do + invalid_parent_rel = rel_stub.create!(:relationship => 'ems_metadata', :ancestry => nil, :resource_type => 'VmOrTemplate', :resource_id => root.id) + rel_stub.create!(:relationship => 'ems_metadata', :ancestry => invalid_parent_rel.id, :resource_type => 'VmOrTemplate', :resource_id => vm.id) + parent_rel = rel_stub.create!(:relationship => 'genealogy', :ancestry => nil, :resource_type => 'VmOrTemplate', :resource_id => root.id) + rel_stub.create!(:relationship => 'genealogy', :ancestry => parent_rel.id, :resource_type => 'VmOrTemplate', :resource_id => vm.id) + + migrate + + expect(vm.reload.ancestry).to eq(root.id.to_s) + expect(root.reload.ancestry).to eq(nil) + expect(AddAncestryToVm::Relationship.count).to eq(2) + end + end + end + + migration_context :down do + context "multiple rels" do + let!(:vm) { vm_stub.create!(:ancestry => '6/5/4') } + it 'creates rel and removes ancestry' do + migrate + + rel = AddAncestryToVm::Relationship.first + expect(rel.relationship).to eq('genealogy') + expect(rel.ancestry).to eq('6/5/4') + expect(rel.resource_type).to eq('VmOrTemplate') + expect(rel.resource_id).to eq(vm.id) + end + end + + context "single rel" do + let!(:vm) { vm_stub.create!(:ancestry => '645') } + it 'creates rel and removes ancestry' do + migrate + + rel = AddAncestryToVm::Relationship.first + expect(rel.relationship).to eq('genealogy') + expect(rel.ancestry).to eq('645') + expect(rel.resource_type).to eq('VmOrTemplate') + expect(rel.resource_id).to eq(vm.id) + end + end + end +end