diff --git a/spec/lib/miq_preloader_spec.rb b/spec/lib/miq_preloader_spec.rb index c66db0ad8c5..0893e5b4c6b 100644 --- a/spec/lib/miq_preloader_spec.rb +++ b/spec/lib/miq_preloader_spec.rb @@ -1,4 +1,18 @@ RSpec.describe MiqPreloader do + # implied vms (simple association) + let(:ems) { FactoryBot.create(:ems_infra).tap { |ems| FactoryBot.create_list(:vm, 2, :ext_management_system => ems) } } + + # implied container_nodes (through association) + let(:image) do + FactoryBot.create(:container_image).tap do |image| + FactoryBot.create( + :container, + :container_image => image, + :container_group => FactoryBot.create(:container_group, :container_node => FactoryBot.create(:container_node)) + ) + end + end + describe ".preload" do it "preloads once from an object" do ems = FactoryBot.create(:ems_infra) @@ -29,52 +43,72 @@ it "preloads with a relation (records is a relation)" do ems = FactoryBot.create(:ems_infra) FactoryBot.create_list(:vm, 2, :ext_management_system => ems) - emses = ExtManagementSystem.all.load vms = Vm.where(:ems_id => emses.select(:id)) - expect { preload(emses, :vms, vms) }.to make_database_queries(:count => 1) + + expect { expect(emses.first.vms.size).to eq(2) }.not_to make_database_queries end - it "preloads with a loaded relation (records is a relation)" do - ems = FactoryBot.create(:ems_infra) - FactoryBot.create_list(:vm, 2, :ext_management_system => ems) + # original behavior - not calling our code + it "preloads with an unloaded simple relation" do + vms = Vm.where(:ems_id => ems.id) + vms2 = vms.order(:id).to_a # preloaded vms to be used in tests + + # there is a query for every ems + expect { preload(ems, :vms, vms) }.to make_database_queries(:count => 1) + expect { preload(ems, :vms, vms) }.not_to make_database_queries + expect { expect(ems.vms).to match_array(vms2) }.not_to make_database_queries + # vms does not get loaded, so it is not preloaded + expect { expect(vms.first.ext_management_system).to eq(ems) }.to make_database_queries(:count => 2) + end + it "preloads with a loaded relation (records is a relation)" do + ems emses = ExtManagementSystem.all.load vms = Vm.where(:ems_id => emses.select(:id)).load - expect { preload(emses, :vms, vms) }.not_to make_database_queries + expect { preload(emses, :vms, vms) }.not_to make_database_queries expect { expect(emses.first.vms.size).to eq(2) }.not_to make_database_queries expect { expect(vms.first.ext_management_system).to eq(ems) }.not_to make_database_queries end it "preloads with an array (records is a relation)" do - ems = FactoryBot.create(:ems_infra) - FactoryBot.create_list(:vm, 2, :ext_management_system => ems) - + ems emses = ExtManagementSystem.all.load vms = Vm.where(:ems_id => emses.select(:id)).to_a - expect { preload(emses, :vms, vms) }.not_to make_database_queries + expect { preload(emses, :vms, vms) }.not_to make_database_queries expect { expect(emses.first.vms.size).to eq(2) }.not_to make_database_queries expect { expect(vms.first.ext_management_system).to eq(ems) }.not_to make_database_queries end it "preloads with an array (records is an array)" do - ems = FactoryBot.create(:ems_infra) - FactoryBot.create_list(:vm, 2, :ext_management_system => ems) - + ems emses = ExtManagementSystem.all.load.to_a vms = Vm.where(:ems_id => emses.map(&:id)).to_a - expect { preload(emses, :vms, vms) }.not_to make_database_queries + expect { preload(emses, :vms, vms) }.not_to make_database_queries expect { expect(emses.first.vms.size).to eq(2) }.not_to make_database_queries expect { expect(vms.first.ext_management_system).to eq(ems) }.not_to make_database_queries end + it "preloads a through with a loaded scope" do + image + nodes = ContainerNode.all.load + + # NOTE: it currently ignores the locally loaded records + # TODO: not_to make_database_queries + expect { preload(image, :container_nodes, nodes) }.to make_database_queries(:count => 3) + expect { preload(image, :container_nodes, nodes) }.not_to make_database_queries + expect { expect(image.container_nodes).to eq(nodes) }.not_to make_database_queries + # TODO: not_to make_database_queries + expect { expect(nodes.first.container_images).to eq([image]) }.to make_database_queries(:count => 1) + end + def preload(*args) MiqPreloader.preload(*args) end @@ -84,9 +118,9 @@ def preload(*args) it "preloads from an object" do ems = FactoryBot.create(:ems_infra) FactoryBot.create_list(:vm, 2, :ext_management_system => ems) - vms = nil expect { vms = preload_and_map(ems, :vms) }.to make_database_queries(:count => 1) + expect { expect(vms.size).to eq(2) }.not_to make_database_queries end @@ -147,12 +181,9 @@ def preload_and_map(*args) it "preloads (object.all).belongs_to.has_many" do ems = FactoryBot.create(:ems_infra) host = FactoryBot.create(:host, :ext_management_system => ems) - FactoryBot.create_list(:vm, 2, - :ext_management_system => ems, - :host => host) + FactoryBot.create_list(:vm, 2, :ext_management_system => ems, :host => host) host = FactoryBot.create(:host, :ext_management_system => ems) - FactoryBot.create(:vm, :ext_management_system => ems, - :host => host) + FactoryBot.create(:vm, :ext_management_system => ems, :host => host) emses = nil vms = nil diff --git a/spec/models/metric/ci_mixin/state_finders_spec.rb b/spec/models/metric/ci_mixin/state_finders_spec.rb new file mode 100644 index 00000000000..c1574a4a201 --- /dev/null +++ b/spec/models/metric/ci_mixin/state_finders_spec.rb @@ -0,0 +1,130 @@ +RSpec.describe Metric::CiMixin::StateFinders do + let(:image) { FactoryBot.create(:container_image) } + let(:container1) { FactoryBot.create(:container, :container_image => image) } + let(:container2) { FactoryBot.create(:container, :container_image => image) } + let(:node1) { FactoryBot.create(:container_node) } + let(:node2) { FactoryBot.create(:container_node) } + + let(:ts_now) { Time.now.utc.beginning_of_hour.to_s } + let(:timestamp) { 2.hours.ago.utc.beginning_of_hour.to_s } + + # NOTE: in these specs, we could let perf_capture_state be called + # but using this reduces the queries + describe "#vim_performance_state_for_ts" do + let(:vps_now) { create_vps(image, ts_now) } + let(:vps) { create_vps(image, timestamp) } + + context "when no cache" do + it "creates new value when one is not found in the database" do + expect(image).to receive(:perf_capture_state).once.and_return(vps_now) + + expect do + expect(image.vim_performance_state_for_ts(timestamp)).to eq(vps_now) + end.to make_database_queries(:count => 1) + + # reuses cached / created value + expect do + expect(image.vim_performance_state_for_ts(timestamp)).to eq(vps_now) + end.not_to make_database_queries + end + + it "caches the newly created value" do + expect(image).to receive(:perf_capture_state).once.and_return(vps_now) + + image.vim_performance_state_for_ts(timestamp) + expect do + expect(image.vim_performance_state_for_ts(timestamp)).to eq(vps_now) + end.not_to make_database_queries + end + end + + # ci_mixin/processing.rb uses this + context "when using preload_vim_performance_state_for_ts_iso8601" do + it "finds cached value" do + vps_now + vps + image.preload_vim_performance_state_for_ts_iso8601(:timestamp => [ts_now, timestamp]) + expect(image).to receive(:perf_capture_state).never + + expect do + expect(image.vim_performance_state_for_ts(timestamp)).to eq(vps_now) # TODO: should be vps + end.to make_database_queries(:count => 0) + end + + it "falls back to cached now" do + vps_now + image.preload_vim_performance_state_for_ts_iso8601(:timestamp => [ts_now, timestamp]) + expect(image).to receive(:perf_capture_state).never + + expect do + expect(image.vim_performance_state_for_ts(timestamp)).to eq(vps_now) + end.not_to make_database_queries + end + + it "creates (and caches) a value when now isn't cached" do + image.preload_vim_performance_state_for_ts_iso8601(:timestamp => []) + expect(image).to receive(:perf_capture_state).once.and_return(vps_now) + + # NOTE: this performs a query because the back end does not know if this + # value was searched and not found, or no caching was performed + expect do + expect(image.vim_performance_state_for_ts(timestamp)).to eq(vps_now) + end.to make_database_queries(:count => 1) + expect { image.vim_performance_state_for_ts(timestamp) }.not_to make_database_queries + end + end + + # ci_mixin/rollup.rb uses this + context "when using preload" do + it "finds cached value" do + vps_now + vps + rec_states = VimPerformanceState.where(:timestamp => [ts_now, timestamp]) + MiqPreloader.preload(image, :vim_performance_states, rec_states) + expect(image).to receive(:perf_capture_state).never + + expect do + expect(image.vim_performance_state_for_ts(timestamp)).to eq(vps) + end.not_to make_database_queries + end + + it "falls back to cached now" do + vps_now + rec_states = VimPerformanceState.where(:timestamp => [ts_now, timestamp]) + MiqPreloader.preload(image, :vim_performance_states, rec_states) + expect(image).to receive(:perf_capture_state).never + + expect do + expect(image.vim_performance_state_for_ts(timestamp)).to eq(vps_now) + end.not_to make_database_queries + end + + it "creates (and caches) a value when now isn't cached" do + rec_states = VimPerformanceState.where(:timestamp => [ts_now, timestamp]) + MiqPreloader.preload(image, :vim_performance_states, rec_states) + expect(image).to receive(:perf_capture_state).twice.and_return(vps_now) # fix + + expect do + expect(image.vim_performance_state_for_ts(timestamp)).to eq(vps_now) + end.not_to make_database_queries + expect { image.vim_performance_state_for_ts(timestamp) }.not_to make_database_queries + end + end + end + + private + + def create_vps(image, ts, containers = [], nodes = []) + FactoryBot.create( + :vim_performance_state, + :resource => image, + :timestamp => ts, + :state_data => { + :assoc_ids => { + :containers => {:on => containers.map(&:id), :off => []}, + :container_nodes => {:on => nodes.map(&:id), :off => []}, + } + } + ) + end +end diff --git a/spec/models/vim_performance_state_spec.rb b/spec/models/vim_performance_state_spec.rb index 5a4f1106f29..3c938b2e8a6 100644 --- a/spec/models/vim_performance_state_spec.rb +++ b/spec/models/vim_performance_state_spec.rb @@ -1,22 +1,11 @@ RSpec.describe VimPerformanceState do - describe ".capture_host_sockets" do - it "returns the host sockets when given a host" do - hardware = FactoryBot.build(:hardware, :cpu_sockets => 2) - host = FactoryBot.create(:host, :hardware => hardware) + describe ".capture" do + it "uses now" do + host = FactoryBot.create(:host) state = VimPerformanceState.capture(host) - expect(state.host_sockets).to eq(2) - end - - it "rolls up the total sockets when given something that has hosts" do - hardware_1 = FactoryBot.build(:hardware, :cpu_sockets => 2) - hardware_2 = FactoryBot.build(:hardware, :cpu_sockets => 4) - host_1 = FactoryBot.build(:host, :hardware => hardware_1) - host_2 = FactoryBot.build(:host, :hardware => hardware_2) - cluster = FactoryBot.create(:ems_cluster, :hosts => [host_1, host_2]) - state = VimPerformanceState.capture(cluster) - - expect(state.host_sockets).to eq(6) + expect(state.timestamp).to be_within(1.minute).of(Time.now.utc.beginning_of_hour) + expect(state.capture_interval).to eq(3600) # 1.hour end end @@ -37,10 +26,24 @@ end describe "#host_sockets" do - it "returns the host sockets" do - state_data = {:host_sockets => 2} - actual = described_class.new(:state_data => state_data) - expect(actual.host_sockets).to eq(2) + it "captures the host sockets when given a host" do + hardware = FactoryBot.create(:hardware, :cpu_sockets => 2) + host = FactoryBot.create(:host, :hardware => hardware) + state = VimPerformanceState.capture(host) + + expect(state.state_data).to include(:host_sockets => 2) + expect(state.host_sockets).to eq(2) + end + + it "captures the rolled up sockets when given a cluster" do + hardware_1 = FactoryBot.build(:hardware, :cpu_sockets => 2) + hardware_2 = FactoryBot.build(:hardware, :cpu_sockets => 4) + host_1 = FactoryBot.build(:host, :hardware => hardware_1) + host_2 = FactoryBot.build(:host, :hardware => hardware_2) + cluster = FactoryBot.create(:ems_cluster, :hosts => [host_1, host_2]) + state = VimPerformanceState.capture(cluster) + + expect(state.host_sockets).to eq(6) end end