diff --git a/app/models/vm_or_template.rb b/app/models/vm_or_template.rb index 16a602e77900..fe80d1918010 100644 --- a/app/models/vm_or_template.rb +++ b/app/models/vm_or_template.rb @@ -1618,6 +1618,39 @@ def self.vms_by_ipaddress(ipaddress) end end + # This creates the following SQL conditional: + # + # 1 = (SELECT 1 + # FROM hardwares + # JOIN networks ON networks.hardware_id = hardwares.id + # WHERE hardwares.vm_or_template_id = vms.id + # AND (networks.ipaddress LIKE "%IPADDRESS%" + # OR networks.ipv6address LIKE "%IPADDRESS%") + # LIMIT 1 + # ) + # + # This is simply an existance check, so when one record is found matching the + # following conditions: + # + # - It is a hardware record that is associated with the vm + # - It has an ipaddress or ipv6address that matches the search + # + # It will return the VM record. + def self.miq_expression_includes_any_ipaddresses_arel(ipaddress) + vms = arel_table + networks = Network.arel_table + hardwares = Hardware.arel_table + + match_grouping = networks[:ipaddress].matches("%#{ipaddress}%") + .or(networks[:ipv6address].matches("%#{ipaddress}%")) + + query = hardwares.project(1) + .join(networks).on(networks[:hardware_id].eq(hardwares[:id])) + .where(hardwares[:vm_or_template_id].eq(vms[:id]).and(match_grouping)) + .take(1) + Arel::Nodes::SqlLiteral.new("1").eq(query) + end + def self.scan_by_property(property, value, _options = {}) _log.info("scan_vm_by_property called with property:[#{property}] value:[#{value}]") case property diff --git a/lib/miq_expression.rb b/lib/miq_expression.rb index b909bc21c477..97e528767f5b 100644 --- a/lib/miq_expression.rb +++ b/lib/miq_expression.rb @@ -344,6 +344,15 @@ def sql_supports_atom?(exp) # TODO: Support includes operator for sub-sub-tables return false end + when "includes any", "includes all", "includes only" + # Support this only from the main model (for now) + if exp[operator].keys.include?("field") && exp[operator]["field"].split(".").length == 1 + model, field = exp[operator]["field"].split("-") + method = "miq_expression_#{operator.downcase.tr(' ', '_')}_#{field}_arel" + return model.constantize.respond_to?(method) + else + return false + end when "find", "regular expression matches", "regular expression does not match", "key exists", "value exists" return false else @@ -1343,6 +1352,11 @@ def to_arel(exp, tz) escape = nil case_sensitive = true arel_attribute.matches("%#{parsed_value}%", escape, case_sensitive) + when "includes all", "includes any", "includes only" + method = "miq_expression_" + method << "#{operator.downcase.tr(' ', '_')}_" + method << "#{field.column}_arel" + field.model.send(method, parsed_value) when "starts with" escape = nil case_sensitive = true diff --git a/spec/models/vm_or_template_spec.rb b/spec/models/vm_or_template_spec.rb index 2e792fde84ed..36a46dabf614 100644 --- a/spec/models/vm_or_template_spec.rb +++ b/spec/models/vm_or_template_spec.rb @@ -82,6 +82,30 @@ end end + describe ".miq_expression_includes_any_ipaddresses_arel" do + subject { FactoryGirl.create(:vm) } + let(:no_hardware_vm) { FactoryGirl.create(:vm) } + let(:wrong_ip_vm) { FactoryGirl.create(:vm) } + + before do + hw1 = FactoryGirl.create(:hardware, :vm => subject) + FactoryGirl.create(:network, :hardware => hw1, :ipaddress => "10.11.11.11") + FactoryGirl.create(:network, :hardware => hw1, :ipaddress => "10.10.10.10") + FactoryGirl.create(:network, :hardware => hw1, :ipaddress => "10.10.10.11") + + hw2 = FactoryGirl.create(:hardware, :vm => wrong_ip_vm) + FactoryGirl.create(:network, :hardware => hw2, :ipaddress => "11.11.11.11") + end + + it "runs a single query, returning only the valid vm" do + expect do + query = Vm.miq_expression_includes_any_ipaddresses_arel("10.10.10") + result = Vm.where(query) + expect(result.to_a).to eq([subject]) + end.to match_query_limit_of(1) + end + end + context ".event_by_property" do context "should add an EMS event" do before do