Skip to content

Commit

Permalink
Merge pull request #14249 from Ladas/target_and_target_collection_abs…
Browse files Browse the repository at this point in the history
…tractions

Target and target collection abstractions
  • Loading branch information
agrare authored Mar 10, 2017
2 parents 011e3ea + e5e35b1 commit 7d7fe08
Show file tree
Hide file tree
Showing 9 changed files with 627 additions and 18 deletions.
4 changes: 4 additions & 0 deletions app/models/ems_event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -324,4 +324,8 @@ def ems_cluster_refresh_target
def ems_refresh_target
ext_management_system
end

def manager_refresh_targets
ext_management_system.class::EventTargetParser.new(self).parse
end
end
8 changes: 8 additions & 0 deletions app/models/ems_event/automate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ class EmsEvent
module Automate
extend ActiveSupport::Concern

def graph_refresh(sync: false)
refresh_targets = manager_refresh_targets

return if refresh_targets.empty?

EmsRefresh.queue_refresh(refresh_targets, nil, sync)
end

def refresh(*targets, sync)
targets = targets.flatten
return if targets.blank?
Expand Down
35 changes: 20 additions & 15 deletions app/models/ems_refresh.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def self.queue_refresh_task(target, id = nil)

def self.queue_refresh(target, id = nil, opts = {})
# Handle targets passed as a single class/id pair, an array of class/id pairs, or an array of references
targets = get_ar_objects(target, id)
targets = get_target_objects(target, id)

# Group the target refs by zone and role
targets_by_ems = targets.each_with_object(Hash.new { |h, k| h[k] = [] }) do |t, h|
Expand Down Expand Up @@ -82,7 +82,7 @@ def self.refresh(target, id = nil)
EmsRefresh.init_console if defined?(Rails::Console)

# Handle targets passed as a single class/id pair, an array of class/id pairs, or an array of references
targets = get_ar_objects(target, id)
targets = get_target_objects(target, id)

# Split the targets into refresher groups
groups = targets.group_by do |t|
Expand All @@ -109,40 +109,45 @@ def self.refresh_new_target(target_hash, ems_id)
return
end

ems.refresher.refresh(get_ar_objects(target))
ems.refresher.refresh(get_target_objects(target))
end

def self.get_ar_objects(target, single_id = nil)
def self.get_target_objects(target, single_id = nil)
# Handle targets passed as a single class/id pair, an array of class/id pairs, an array of references
target = [[target, single_id]] unless single_id.nil?
return [target] unless target.kind_of?(Array)
return target unless target[0].kind_of?(Array)

# Group by type for a more optimized search
targets_by_type = target.each_with_object(Hash.new { |h, k| h[k] = [] }) do |(c, id), h|
targets_by_type = target.each_with_object(Hash.new { |h, k| h[k] = [] }) do |(target_class, id), hash|
# Take care of both String or Class type being passed in
c = c.to_s.constantize unless c.kind_of?(Class)
if [VmOrTemplate, Host, ExtManagementSystem].none? { |k| c <= k }
_log.warn "Unknown target type: [#{c}]."
target_class = target_class.to_s.constantize unless target_class.kind_of?(Class)
if [VmOrTemplate, Host, ExtManagementSystem, ManagerRefresh::Target].none? { |k| target_class <= k }
_log.warn "Unknown target type: [#{target_class}]."
next
end

h[c] << id
hash[target_class] << id
end

# Do lookups to get ActiveRecord objects
targets_by_type.each_with_object([]) do |(c, ids), a|
# Do lookups to get ActiveRecord objects or initialize ManagerRefresh::Target for ids that are Hash
targets_by_type.each_with_object([]) do |(target_class, ids), target_objects|
ids.uniq!

recs = c.where(:id => ids)
recs = recs.includes(:ext_management_system) unless c <= ExtManagementSystem
recs = if target_class <= ManagerRefresh::Target
ids.map { |x| ManagerRefresh::Target.load(x) }
else
active_record_recs = target_class.where(:id => ids)
active_record_recs = active_record_recs.includes(:ext_management_system) unless target_class <= ExtManagementSystem
active_record_recs
end

if recs.length != ids.length
missing = ids - recs.collect(&:id)
_log.warn "Unable to find a record for [#{c}] ids: #{missing.inspect}."
_log.warn "Unable to find a record for [#{target_class}] ids: #{missing.inspect}."
end

a.concat(recs)
target_objects.concat(recs)
end
end

Expand Down
66 changes: 66 additions & 0 deletions app/models/manager_refresh/target.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
module ManagerRefresh
class Target
attr_reader :association, :manager_ref, :event_id, :options

# @param association [Symbol] An existing association on Manager, that lists objects represented by a Target, naming
# should be the same of association of a counterpart InventoryCollection object
# @param manager_ref [Hash] A Hash that can be used to find_by on a given association and returning a unique object.
# The keys should be the same as the keys of the counterpart InventoryObject
# @param manager [ManageIQ::Providers::BaseManager] The Manager owning the Target
# @param manager_id [Integer] A primary key of the Manager owning the Target
# @param event_id [Integer] A primary key of the EmsEvent associated with the Target
# @param options [Hash] A free form options hash
def initialize(association:, manager_ref:, manager: nil, manager_id: nil, event_id: nil, options: {})
raise "Provide either :manager or :manager_id argument" if manager.nil? && manager_id.nil?

@manager = manager
@manager_id = manager_id
@association = association
@manager_ref = manager_ref
@event_id = event_id
@options = options
end

# A Rails recommended interface for deserializing an object
def self.load(*args)
new(*args)
end

# A Rails recommended interface for serializing an object
def self.dump(obj)
obj.dump
end

# Returns a serialized ManagerRefresh::Target object. This can be used to initialize a new object, then the object
# target acts the same as the object ManagerRefresh::Target.new(target.serialize)
def dump
{
:manager_id => manager_id,
:association => association,
:manager_ref => manager_ref,
:event_id => event_id,
:options => options
}
end

alias id dump
alias name manager_ref

# @return [ManageIQ::Providers::BaseManager] The Manager owning the Target
def manager
@manager || ManageIQ::Providers::BaseManager.find(@manager_id)
end

# @return [Integer] A primary key of the Manager owning the Target
def manager_id
@manager_id || manager.try(:id)
end

# Loads ManagerRefresh::Target ApplicationRecord representation from our DB, this requires that ManagerRefresh::Target
# has been refreshed, otherwise the AR object can be missing.
# @return [ApplicationRecord] A ManagerRefresh::Target loaded from the database as AR object
def load_from_db
manager.public_send(association).find_by(manager_ref)
end
end
end
76 changes: 76 additions & 0 deletions app/models/manager_refresh/target_collection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
module ManagerRefresh
class TargetCollection
attr_reader :targets

delegate :<<, :to => :targets

# @param manager [ManageIQ::Providers::BaseManager] The Manager owning the TargetCollection
# @param event [EmsEvent] AnEmsEvent associated with the TargetCollection
# @param targets [Array] An Array of ManagerRefresh::Target objects or ApplicationRecord objects
def initialize(manager: nil, manager_id: nil, event: nil, targets: [])
@manager = manager
@manager_id = manager_id
@event = event
@targets = targets
end

# @param association [Symbol] An existing association on Manager, that lists objects represented by a Target, naming
# should be the same of association of a counterpart InventoryCollection object
# @param manager_ref [Hash] A Hash that can be used to find_by on a given association and returning a unique object.
# The keys should be the same as the keys of the counterpart InventoryObject
# @param manager [ManageIQ::Providers::BaseManager] The Manager owning the Target
# @param manager_id [Integer] A primary key of the Manager owning the Target
# @param event_id [Integer] A primary key of the EmsEvent associated with the Target
# @param options [Hash] A free form options hash
def add_target(association:, manager_ref:, manager: nil, manager_id: nil, event_id: nil, options: {})
self << ManagerRefresh::Target.new(:association => association,
:manager_ref => manager_ref,
:manager => manager || @manager,
:manager_id => manager_id || @manager_id || @manager.try(:id),
:event_id => event_id || @event.try(:id),
:options => options)
end

# @return [String] A String containing a name of each target in the TargetCollection
def name
"Collection of targets with name: #{targets.collect(&:name)}"
end

# @return [String] A String containing an id of each target in the TargetCollection
def id
"Collection of targets with id: #{targets.collect(&:id)}"
end

# Returns targets in a format:
# {
# :vms => {:ems_ref => Set.new(["vm_ref_1", "vm_ref2"])}
# :network_ports => {:ems_ref => Set.new(["network_port_1", "network_port2"])
# }
#
# Then we can quickly access all objects affected by:
# NetworkPort.where(target_collection.manager_refs_by_association[:network_ports].to_a) =>
# return AR objects with ems_refs ["network_port_1", "network_port2"]
# And we can get a list of ids for the API query by:
# target_collection.manager_refs_by_association[:network_ports][:ems_ref].to_a =>
# ["network_port_1", "network_port2"]
#
# Only targets of a type ManagerRefresh::Target are processed, any other targets present should be converted to
# ManagerRefresh::Target, e.g. in the Inventory::Collector code.
def manager_refs_by_association
@manager_refs_by_association ||= targets.select { |x| x.kind_of?(ManagerRefresh::Target) }.each_with_object({}) do |x, obj|
if obj[x.association].blank?
obj[x.association] = x.manager_ref.each_with_object({}) { |(key, value), hash| hash[key] = Set.new([value]) }
else
obj[x.association].each do |key, value|
value << x.manager_ref[key]
end
end
end
end

# Resets the cached @manager_refs_by_association to enforce reload when calling :manager_refs_by_association method
def manager_refs_by_association_reset
@manager_refs_by_association = nil
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ def self.miq_host_and_storage_least_utilized(obj, _inputs)
["host", "storage"].each { |k| obj[k] = result[k] } unless result.empty?
end

def self.miq_refresh(obj, _inputs)
event_object_from_workspace(obj).graph_refresh(:sync => false)
end

def self.miq_refresh_sync(obj, _inputs)
event_object_from_workspace(obj).graph_refresh(:sync => true)
end

def self.miq_event_action_refresh(obj, inputs)
event_object_from_workspace(obj).refresh(inputs['target'], false)
end
Expand Down
32 changes: 29 additions & 3 deletions spec/models/ems_refresh_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,42 @@ def assert_queue_item(expected_targets)
expect(q_all[0].role).to eq("ems_inventory")
end

context ".get_ar_objects" do
context ".get_target_objects" do
it "array of class/ids pairs" do
ems1 = FactoryGirl.create(:ems_vmware, :name => "ems_vmware1")
ems1 = FactoryGirl.create(:ems_vmware, :name => "ems_vmware1")
ems2 = FactoryGirl.create(:ems_redhat, :name => "ems_redhat1")
pairs = [
[ems1.class, ems1.id],
[ems2.class, ems2.id]
]

expect(described_class.get_ar_objects(pairs)).to match_array([ems1, ems2])
expect(described_class.get_target_objects(pairs)).to match_array([ems1, ems2])
end

it "array of class/hash pairs for ManagerRefresh::Target objects" do
ems1 = FactoryGirl.create(:ems_vmware, :name => "ems_vmware1")
ems2 = FactoryGirl.create(:ems_redhat, :name => "ems_redhat1")

target1 = ManagerRefresh::Target.load(:manager_id => ems1.id,
:association => :vms,
:manager_ref => {:ems_ref => "vm1"})
target2 = ManagerRefresh::Target.load(:manager_id => ems2.id,
:association => :network_ports,
:manager_ref => {:ems_ref => "network_port_1"})
target3 = ManagerRefresh::Target.new(:manager_id => ems1.id,
:association => :vms,
:manager_ref => {:ems_ref => "vm2"})
target1_dup = ManagerRefresh::Target.load(:manager_id => ems1.id,
:association => :vms,
:manager_ref => {:ems_ref => "vm1"})
pairs = [
[target1.class, target1.id],
[target2.class, target2.id],
[target3.class, target3.id],
[target1_dup.class, target1_dup.id],
]

expect(described_class.get_target_objects(pairs).map(&:dump)).to match_array([target1, target2, target3].map(&:dump))
end
end

Expand Down
Loading

0 comments on commit 7d7fe08

Please sign in to comment.