inventory_refresh 0.3.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +25 -30
  3. data/.github/workflows/ci.yaml +47 -0
  4. data/.rubocop.yml +3 -3
  5. data/.rubocop_cc.yml +3 -4
  6. data/.rubocop_local.yml +5 -2
  7. data/.whitesource +3 -0
  8. data/CHANGELOG.md +19 -0
  9. data/Gemfile +10 -4
  10. data/README.md +1 -2
  11. data/Rakefile +2 -2
  12. data/inventory_refresh.gemspec +9 -10
  13. data/lib/inventory_refresh/application_record_iterator.rb +25 -12
  14. data/lib/inventory_refresh/graph/topological_sort.rb +24 -26
  15. data/lib/inventory_refresh/graph.rb +2 -2
  16. data/lib/inventory_refresh/inventory_collection/builder.rb +37 -15
  17. data/lib/inventory_refresh/inventory_collection/data_storage.rb +9 -0
  18. data/lib/inventory_refresh/inventory_collection/helpers/initialize_helper.rb +147 -38
  19. data/lib/inventory_refresh/inventory_collection/helpers/questions_helper.rb +49 -5
  20. data/lib/inventory_refresh/inventory_collection/index/proxy.rb +35 -3
  21. data/lib/inventory_refresh/inventory_collection/index/type/base.rb +8 -0
  22. data/lib/inventory_refresh/inventory_collection/index/type/local_db.rb +2 -0
  23. data/lib/inventory_refresh/inventory_collection/index/type/skeletal.rb +1 -0
  24. data/lib/inventory_refresh/inventory_collection/reference.rb +1 -0
  25. data/lib/inventory_refresh/inventory_collection/references_storage.rb +17 -0
  26. data/lib/inventory_refresh/inventory_collection/scanner.rb +91 -3
  27. data/lib/inventory_refresh/inventory_collection/serialization.rb +16 -10
  28. data/lib/inventory_refresh/inventory_collection.rb +122 -64
  29. data/lib/inventory_refresh/inventory_object.rb +74 -40
  30. data/lib/inventory_refresh/inventory_object_lazy.rb +17 -10
  31. data/lib/inventory_refresh/null_logger.rb +2 -2
  32. data/lib/inventory_refresh/persister.rb +31 -65
  33. data/lib/inventory_refresh/save_collection/base.rb +4 -2
  34. data/lib/inventory_refresh/save_collection/saver/base.rb +114 -15
  35. data/lib/inventory_refresh/save_collection/saver/batch.rb +17 -0
  36. data/lib/inventory_refresh/save_collection/saver/concurrent_safe_batch.rb +129 -51
  37. data/lib/inventory_refresh/save_collection/saver/default.rb +57 -0
  38. data/lib/inventory_refresh/save_collection/saver/partial_upsert_helper.rb +2 -19
  39. data/lib/inventory_refresh/save_collection/saver/retention_helper.rb +68 -3
  40. data/lib/inventory_refresh/save_collection/saver/sql_helper.rb +125 -0
  41. data/lib/inventory_refresh/save_collection/saver/sql_helper_update.rb +10 -6
  42. data/lib/inventory_refresh/save_collection/saver/sql_helper_upsert.rb +28 -16
  43. data/lib/inventory_refresh/save_collection/sweeper.rb +17 -93
  44. data/lib/inventory_refresh/save_collection/topological_sort.rb +5 -5
  45. data/lib/inventory_refresh/save_inventory.rb +5 -12
  46. data/lib/inventory_refresh/target.rb +73 -0
  47. data/lib/inventory_refresh/target_collection.rb +92 -0
  48. data/lib/inventory_refresh/version.rb +1 -1
  49. data/lib/inventory_refresh.rb +2 -0
  50. metadata +42 -39
  51. data/.travis.yml +0 -23
  52. data/lib/inventory_refresh/exception.rb +0 -8
@@ -23,13 +23,13 @@ module InventoryRefresh::SaveCollection
23
23
  connection = get_connection
24
24
 
25
25
  # We want to ignore create timestamps when updating
26
- all_attribute_keys_array = all_attribute_keys.to_a.delete_if { |x| %i(created_at created_on).include?(x) }
26
+ all_attribute_keys_array = all_attribute_keys.to_a.delete_if { |x| %i[created_at created_on].include?(x) }
27
27
  all_attribute_keys_array << :id
28
28
 
29
29
  # If there is not version attribute, the version conditions will be ignored
30
- version_attribute = if supports_remote_data_timestamp?(all_attribute_keys)
30
+ version_attribute = if inventory_collection.parallel_safe? && supports_remote_data_timestamp?(all_attribute_keys)
31
31
  :resource_timestamp
32
- elsif supports_remote_data_version?(all_attribute_keys)
32
+ elsif inventory_collection.parallel_safe? && supports_remote_data_version?(all_attribute_keys)
33
33
  :resource_counter
34
34
  end
35
35
 
@@ -130,9 +130,13 @@ module InventoryRefresh::SaveCollection
130
130
  end
131
131
 
132
132
  def update_query_returning
133
- <<-SQL
134
- RETURNING updated_values.#{quote_column_name("id")}, #{unique_index_columns.map { |x| "updated_values.#{quote_column_name(x)}" }.join(",")}
135
- SQL
133
+ if inventory_collection.parallel_safe?
134
+ <<-SQL
135
+ RETURNING updated_values.#{quote_column_name("id")}, #{unique_index_columns.map { |x| "updated_values.#{quote_column_name(x)}" }.join(",")}
136
+ SQL
137
+ else
138
+ ""
139
+ end
136
140
  end
137
141
  end
138
142
  end
@@ -19,7 +19,7 @@ module InventoryRefresh::SaveCollection
19
19
  # columns of a row, :partial is when we save only few columns, so a partial row.
20
20
  # @param on_conflict [Symbol, NilClass] defines behavior on conflict with unique index constraint, allowed values
21
21
  # are :do_update, :do_nothing, nil
22
- def build_insert_query(all_attribute_keys, hashes, on_conflict: nil, mode:, column_name: nil)
22
+ def build_insert_query(all_attribute_keys, hashes, mode:, on_conflict: nil, column_name: nil)
23
23
  logger.debug("Building insert query for #{inventory_collection} of size #{inventory_collection.size}...")
24
24
 
25
25
  # Cache the connection for the batch
@@ -55,6 +55,8 @@ module InventoryRefresh::SaveCollection
55
55
  end
56
56
 
57
57
  def insert_query_on_conflict_behavior(all_attribute_keys, on_conflict, mode, ignore_cols, column_name)
58
+ return "" unless inventory_collection.parallel_safe?
59
+
58
60
  insert_query_on_conflict = insert_query_on_conflict_do(on_conflict)
59
61
  if on_conflict == :do_update
60
62
  insert_query_on_conflict += insert_query_on_conflict_update(all_attribute_keys, mode, ignore_cols, column_name)
@@ -63,11 +65,12 @@ module InventoryRefresh::SaveCollection
63
65
  end
64
66
 
65
67
  def insert_query_on_conflict_do(on_conflict)
66
- if on_conflict == :do_nothing
68
+ case on_conflict
69
+ when :do_nothing
67
70
  <<-SQL
68
71
  ON CONFLICT DO NOTHING
69
72
  SQL
70
- elsif on_conflict == :do_update
73
+ when :do_update
71
74
  index_where_condition = unique_index_for(unique_index_keys).where
72
75
  where_to_sql = index_where_condition ? "WHERE #{index_where_condition}" : ""
73
76
 
@@ -92,6 +95,8 @@ module InventoryRefresh::SaveCollection
92
95
  :resource_counter
93
96
  end
94
97
 
98
+ # TODO(lsmola) should we add :deleted => false to the update clause? That should handle a reconnect, without a
99
+ # a need to list :deleted anywhere in the parser. We just need to check that a model has the :deleted attribute
95
100
  query = <<-SQL
96
101
  SET #{(all_attribute_keys - ignore_cols).map { |key| build_insert_set_cols(key) }.join(", ")}
97
102
  SQL
@@ -103,10 +108,12 @@ module InventoryRefresh::SaveCollection
103
108
  end
104
109
 
105
110
  def insert_query_on_conflict_update_mode(mode, version_attribute, column_name)
106
- if mode == :full
111
+ case mode
112
+ when :full
107
113
  full_update_condition(version_attribute)
108
- elsif mode == :partial
114
+ when :partial
109
115
  raise "Column name must be provided" unless column_name
116
+
110
117
  partial_update_condition(version_attribute, column_name)
111
118
  end
112
119
  end
@@ -124,7 +131,7 @@ module InventoryRefresh::SaveCollection
124
131
  , #{attr_partial} = '{}', #{attr_partial_max} = NULL
125
132
 
126
133
  WHERE EXCLUDED.#{attr_full} IS NULL OR (
127
- (#{q_table_name}.#{attr_full} IS NULL OR EXCLUDED.#{attr_full} >= #{q_table_name}.#{attr_full}) AND
134
+ (#{q_table_name}.#{attr_full} IS NULL OR EXCLUDED.#{attr_full} > #{q_table_name}.#{attr_full}) AND
128
135
  (#{q_table_name}.#{attr_partial_max} IS NULL OR EXCLUDED.#{attr_full} >= #{q_table_name}.#{attr_partial_max})
129
136
  )
130
137
  SQL
@@ -133,9 +140,10 @@ module InventoryRefresh::SaveCollection
133
140
  def partial_update_condition(attr_full, column_name)
134
141
  attr_partial = attr_full.to_s.pluralize # Changes resource_counter/timestamp to resource_counters/timestamps
135
142
  attr_partial_max = "#{attr_partial}_max"
136
- cast = if attr_full == :resource_timestamp
143
+ cast = case attr_full
144
+ when :resource_timestamp
137
145
  "timestamp"
138
- elsif attr_full == :resource_counter
146
+ when :resource_counter
139
147
  "integer"
140
148
  end
141
149
 
@@ -150,9 +158,9 @@ module InventoryRefresh::SaveCollection
150
158
  #{insert_query_set_jsonb_version(cast, attr_partial, attr_partial_max, column_name)}
151
159
  , #{attr_partial_max} = greatest(#{q_table_name}.#{attr_partial_max}::#{cast}, EXCLUDED.#{attr_partial_max}::#{cast})
152
160
  WHERE EXCLUDED.#{attr_partial_max} IS NULL OR (
153
- (#{q_table_name}.#{attr_full} IS NULL OR EXCLUDED.#{attr_partial_max} >= #{q_table_name}.#{attr_full}) AND (
161
+ (#{q_table_name}.#{attr_full} IS NULL OR EXCLUDED.#{attr_partial_max} > #{q_table_name}.#{attr_full}) AND (
154
162
  (#{q_table_name}.#{attr_partial}->>'#{column_name}')::#{cast} IS NULL OR
155
- EXCLUDED.#{attr_partial_max}::#{cast} >= (#{q_table_name}.#{attr_partial}->>'#{column_name}')::#{cast}
163
+ EXCLUDED.#{attr_partial_max}::#{cast} > (#{q_table_name}.#{attr_partial}->>'#{column_name}')::#{cast}
156
164
  )
157
165
  )
158
166
  SQL
@@ -179,12 +187,16 @@ module InventoryRefresh::SaveCollection
179
187
  end
180
188
 
181
189
  def insert_query_returning_timestamps
182
- # For upsert, we'll return also created and updated timestamps, so we can recognize what was created and what
183
- # updated
184
- if inventory_collection.internal_timestamp_columns.present?
185
- <<-SQL
186
- , #{inventory_collection.internal_timestamp_columns.map { |x| quote_column_name(x) }.join(",")}
187
- SQL
190
+ if inventory_collection.parallel_safe?
191
+ # For upsert, we'll return also created and updated timestamps, so we can recognize what was created and what
192
+ # updated
193
+ if inventory_collection.internal_timestamp_columns.present?
194
+ <<-SQL
195
+ , #{inventory_collection.internal_timestamp_columns.map { |x| quote_column_name(x) }.join(",")}
196
+ SQL
197
+ end
198
+ else
199
+ ""
188
200
  end
189
201
  end
190
202
  end
@@ -1,7 +1,5 @@
1
- require "inventory_refresh/exception"
2
1
  require "inventory_refresh/logging"
3
2
  require "inventory_refresh/save_collection/saver/retention_helper"
4
- require "inventory_refresh/inventory_collection/index/type/local_db"
5
3
 
6
4
  module InventoryRefresh::SaveCollection
7
5
  class Sweeper < InventoryRefresh::SaveCollection::Base
@@ -12,111 +10,39 @@ module InventoryRefresh::SaveCollection
12
10
  # @param _ems [ActiveRecord] Manager owning the inventory_collections
13
11
  # @param inventory_collections [Array<InventoryRefresh::InventoryCollection>] Array of InventoryCollection objects
14
12
  # for sweeping
15
- # @param sweep_scope [Array<String, Symbol, Hash>] Array of inventory collection names marking sweep. Or for
16
- # targeted sweeping it's array of hashes, where key is inventory collection name pointing to an array of
17
- # identifiers of inventory objects we want to target for sweeping.
18
13
  # @param refresh_state [ActiveRecord] Record of :refresh_states
19
- def sweep(_ems, inventory_collections, sweep_scope, refresh_state)
20
- scope_set = build_scope_set(sweep_scope)
21
-
14
+ def sweep(_ems, inventory_collections, refresh_state)
22
15
  inventory_collections.each do |inventory_collection|
23
- next unless sweep_possible?(inventory_collection, scope_set)
16
+ next unless sweep_possible?(inventory_collection, refresh_state)
24
17
 
25
- new(inventory_collection, refresh_state, sweep_scope).sweep
18
+ new(inventory_collection, refresh_state).sweep
26
19
  end
27
20
  end
28
21
 
29
- def sweep_possible?(inventory_collection, scope_set)
30
- inventory_collection.supports_column?(:last_seen_at) && in_scope?(inventory_collection, scope_set)
31
- end
32
-
33
- def in_scope?(inventory_collection, scope_set)
34
- scope_set.include?(inventory_collection&.name)
22
+ def sweep_possible?(inventory_collection, refresh_state)
23
+ inventory_collection.supports_column?(:last_seen_at) && inventory_collection.parallel_safe? &&
24
+ inventory_collection.strategy == :local_db_find_missing_references &&
25
+ in_scope?(inventory_collection, refresh_state.sweep_scope)
35
26
  end
36
27
 
37
- def build_scope_set(sweep_scope)
38
- return [] unless sweep_scope
28
+ def in_scope?(inventory_collection, sweep_scope)
29
+ return true unless sweep_scope
30
+ return true if sweep_scope.kind_of?(Array) && sweep_scope.include?(inventory_collection&.name&.to_s)
39
31
 
40
- if sweep_scope.kind_of?(Array)
41
- sweep_scope.map(&:to_sym).to_set
42
- elsif sweep_scope.kind_of?(Hash)
43
- sweep_scope.keys.map(&:to_sym).to_set
44
- end
32
+ false
45
33
  end
46
34
  end
47
35
 
48
36
  include InventoryRefresh::SaveCollection::Saver::RetentionHelper
49
37
 
50
- attr_reader :inventory_collection, :refresh_state, :sweep_scope, :model_class, :primary_key
38
+ attr_reader :inventory_collection, :refresh_state, :model_class, :primary_key
51
39
 
52
- delegate :inventory_object_lazy?,
53
- :inventory_object?,
54
- :to => :inventory_collection
55
-
56
- def initialize(inventory_collection, refresh_state, sweep_scope)
40
+ def initialize(inventory_collection, refresh_state)
57
41
  @inventory_collection = inventory_collection
42
+ @refresh_state = refresh_state
58
43
 
59
- @refresh_state = refresh_state
60
- @sweep_scope = sweep_scope
61
-
62
- @model_class = inventory_collection.model_class
63
- @primary_key = @model_class.primary_key
64
- end
65
-
66
- def apply_targeted_sweep_scope(all_entities_query)
67
- if sweep_scope.kind_of?(Hash)
68
- scope = sweep_scope[inventory_collection.name]
69
- return all_entities_query if scope.nil? || scope.empty?
70
-
71
- # Scan the scope to find all references, so we can load them from DB in batches
72
- scan_sweep_scope!(scope)
73
-
74
- scope_keys = Set.new
75
- conditions = scope.map { |x| InventoryRefresh::InventoryObject.attributes_with_keys(x, inventory_collection, scope_keys) }
76
- assert_conditions!(conditions, scope_keys)
77
-
78
- all_entities_query.where(inventory_collection.build_multi_selection_condition(conditions, scope_keys))
79
- else
80
- all_entities_query
81
- end
82
- end
83
-
84
- def loadable?(value)
85
- inventory_object_lazy?(value) || inventory_object?(value)
86
- end
87
-
88
- def scan_sweep_scope!(scope)
89
- scope.each do |sc|
90
- sc.each_value do |value|
91
- next unless loadable?(value)
92
-
93
- value_inventory_collection = value.inventory_collection
94
- value_inventory_collection.add_reference(value.reference, :key => value.key)
95
- end
96
- end
97
- end
98
-
99
- def assert_conditions!(conditions, scope_keys)
100
- conditions.each do |cond|
101
- assert_uniform_keys!(cond, scope_keys)
102
- assert_non_existent_keys!(cond)
103
- end
104
- end
105
-
106
- def assert_uniform_keys!(cond, scope_keys)
107
- return if (diff = (scope_keys - cond.keys.to_set)).empty?
108
-
109
- raise(InventoryRefresh::Exception::SweeperNonUniformScopeKeyFoundError,
110
- "Sweeping scope for #{inventory_collection} contained non uniform keys. All keys for the"\
111
- "scope must be the same, it's possible to send multiple sweeps with different key set. Missing keys"\
112
- " for a scope were: #{diff.to_a}")
113
- end
114
-
115
- def assert_non_existent_keys!(cond)
116
- return if (diff = (cond.keys.to_set - inventory_collection.all_column_names)).empty?
117
-
118
- raise(InventoryRefresh::Exception::SweeperNonExistentScopeKeyFoundError,
119
- "Sweeping scope for #{inventory_collection} contained keys that are not columns: #{diff.to_a}")
44
+ @model_class = inventory_collection.model_class
45
+ @primary_key = @model_class.primary_key
120
46
  end
121
47
 
122
48
  def sweep
@@ -126,9 +52,7 @@ module InventoryRefresh::SaveCollection
126
52
  table = model_class.arel_table
127
53
  date_field = table[:last_seen_at]
128
54
  all_entities_query = inventory_collection.full_collection_for_comparison
129
- all_entities_query = all_entities_query.active if inventory_collection.retention_strategy == :archive && inventory_collection.supports_column?(:archived_at)
130
-
131
- all_entities_query = apply_targeted_sweep_scope(all_entities_query)
55
+ all_entities_query.active if inventory_collection.retention_strategy == :archive
132
56
 
133
57
  query = all_entities_query
134
58
  .where(date_field.lt(refresh_start)).or(all_entities_query.where(:last_seen_at => nil))
@@ -17,21 +17,21 @@ module InventoryRefresh::SaveCollection
17
17
 
18
18
  layers = InventoryRefresh::Graph::TopologicalSort.new(graph).topological_sort
19
19
 
20
- logger.debug("Saving manager #{ems.id}...")
20
+ logger.debug("Saving manager #{ems.name}...")
21
21
 
22
- sorted_graph_log = "Topological sorting of manager #{ems.id} resulted in these layers processable in parallel:\n"
22
+ sorted_graph_log = "Topological sorting of manager #{ems.name} resulted in these layers processable in parallel:\n"
23
23
  sorted_graph_log += graph.to_graphviz(:layers => layers)
24
24
  logger.debug(sorted_graph_log)
25
25
 
26
26
  layers.each_with_index do |layer, index|
27
- logger.debug("Saving manager #{ems.id} | Layer #{index}")
27
+ logger.debug("Saving manager #{ems.name} | Layer #{index}")
28
28
  layer.each do |inventory_collection|
29
29
  save_inventory_object_inventory(ems, inventory_collection) unless inventory_collection.saved?
30
30
  end
31
- logger.debug("Saved manager #{ems.id} | Layer #{index}")
31
+ logger.debug("Saved manager #{ems.name} | Layer #{index}")
32
32
  end
33
33
 
34
- logger.debug("Saving manager #{ems.id}...Complete")
34
+ logger.debug("Saving manager #{ems.name}...Complete")
35
35
  end
36
36
  end
37
37
  end
@@ -29,18 +29,11 @@ module InventoryRefresh
29
29
  # @param ems [ExtManagementSystem] manager owning the inventory_collections
30
30
  # @param inventory_collections [Array<InventoryRefresh::InventoryCollection>] array of InventoryCollection objects
31
31
  # for sweeping
32
- # @param sweep_scope [Array<String, Symbol, Hash>] Array of inventory collection names marking sweep. Or for
33
- # targeted sweeping it's array of hashes, where key is inventory collection name pointing to an array of
34
- # identifiers of inventory objects we want to target for sweeping.
35
32
  # @param refresh_state [ActiveRecord] Record of :refresh_states
36
- def sweep_inactive_records(ems, inventory_collections, sweep_scope, refresh_state)
37
- inventory_collections.each do |inventory_collection|
38
- inventory_collection.strategy = :local_db_find_references
39
- end
40
-
41
- logger.info("#{log_header(ems)} Sweeping EMS Inventory with scope #{sweep_scope} and date #{refresh_state.created_at} ...")
42
- InventoryRefresh::SaveCollection::Sweeper.sweep(ems, inventory_collections, sweep_scope, refresh_state)
43
- logger.info("#{log_header(ems)} Sweeping EMS Inventory with scope #{sweep_scope} and date #{refresh_state.created_at}...Complete")
33
+ def sweep_inactive_records(ems, inventory_collections, refresh_state)
34
+ logger.info("#{log_header(ems)} Sweeping EMS Inventory...")
35
+ InventoryRefresh::SaveCollection::Sweeper.sweep(ems, inventory_collections, refresh_state)
36
+ logger.info("#{log_header(ems)} Sweeping EMS Inventory...Complete")
44
37
 
45
38
  ems
46
39
  end
@@ -50,7 +43,7 @@ module InventoryRefresh
50
43
  # @param ems [ExtManagementSystem] manager owning the inventory_collections
51
44
  # @return [String] helper string for logging
52
45
  def log_header(ems)
53
- "EMS: [#{ems.id}]"
46
+ "EMS: [#{ems.name}], id: [#{ems.id}]"
54
47
  end
55
48
  end
56
49
  end
@@ -0,0 +1,73 @@
1
+ module InventoryRefresh
2
+ class Target
3
+ attr_reader :association, :manager_ref, :event_id, :options
4
+
5
+ # @param association [Symbol] An existing association on Manager, that lists objects represented by a Target, naming
6
+ # should be the same of association of a counterpart InventoryCollection object
7
+ # @param manager_ref [Hash] A Hash that can be used to find_by on a given association and returning a unique object.
8
+ # The keys should be the same as the keys of the counterpart InventoryObject
9
+ # @param manager [ManageIQ::Providers::BaseManager] The Manager owning the Target
10
+ # @param manager_id [Integer] A primary key of the Manager owning the Target
11
+ # @param event_id [Integer] A primary key of the EmsEvent associated with the Target
12
+ # @param options [Hash] A free form options hash
13
+ def initialize(association:, manager_ref:, manager: nil, manager_id: nil, event_id: nil, options: {})
14
+ raise "Provide either :manager or :manager_id argument" if manager.nil? && manager_id.nil?
15
+
16
+ @manager = manager
17
+ @manager_id = manager_id
18
+ @association = association
19
+ @manager_ref = manager_ref
20
+ @event_id = event_id
21
+ @options = options
22
+ end
23
+
24
+ # A Rails recommended interface for deserializing an object
25
+ # @return [InventoryRefresh::Target] InventoryRefresh::Target instance
26
+ def self.load(*args)
27
+ new(*args)
28
+ end
29
+
30
+ # A Rails recommended interface for serializing an object
31
+ #
32
+ # @param obj [InventoryRefresh::Target] InventoryRefresh::Target instance we want to serialize
33
+ # @return [Hash] serialized object
34
+ def self.dump(obj)
35
+ obj.dump
36
+ end
37
+
38
+ # Returns a serialized InventoryRefresh::Target object. This can be used to initialize a new object, then the object
39
+ # target acts the same as the object InventoryRefresh::Target.new(target.serialize)
40
+ #
41
+ # @return [Hash] serialized object
42
+ def dump
43
+ {
44
+ :manager_id => manager_id,
45
+ :association => association,
46
+ :manager_ref => manager_ref,
47
+ :event_id => event_id,
48
+ :options => options
49
+ }
50
+ end
51
+
52
+ alias id dump
53
+ alias name manager_ref
54
+
55
+ # @return [ManageIQ::Providers::BaseManager] The Manager owning the Target
56
+ def manager
57
+ @manager || ManageIQ::Providers::BaseManager.find(@manager_id)
58
+ end
59
+
60
+ # @return [Integer] A primary key of the Manager owning the Target
61
+ def manager_id
62
+ @manager_id || manager.try(:id)
63
+ end
64
+
65
+ # Loads InventoryRefresh::Target ApplicationRecord representation from our DB, this requires that InventoryRefresh::Target
66
+ # has been refreshed, otherwise the AR object can be missing.
67
+ #
68
+ # @return [ApplicationRecord] A InventoryRefresh::Target loaded from the database as AR object
69
+ def load_from_db
70
+ manager.public_send(association).find_by(manager_ref)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,92 @@
1
+ require "active_support/core_ext/module/delegation"
2
+
3
+ module InventoryRefresh
4
+ class TargetCollection
5
+ attr_reader :targets
6
+
7
+ delegate :<<, :to => :targets
8
+
9
+ # @param manager [ManageIQ::Providers::BaseManager] manager owning the TargetCollection
10
+ # @param manager_id [Integer] primary key of manager owning the TargetCollection
11
+ # @param event [EmsEvent] EmsEvent associated with the TargetCollection
12
+ # @param targets [Array<InventoryRefresh::Target, ApplicationRecord>] Array of InventoryRefresh::Target objects or
13
+ # ApplicationRecord objects
14
+ def initialize(manager: nil, manager_id: nil, event: nil, targets: [])
15
+ @manager = manager
16
+ @manager_id = manager_id
17
+ @event = event
18
+ @targets = targets
19
+ end
20
+
21
+ # @param association [Symbol] An existing association on Manager, that lists objects represented by a Target, naming
22
+ # should be the same of association of a counterpart InventoryCollection object
23
+ # @param manager_ref [Hash] A Hash that can be used to find_by on a given association and returning a unique object.
24
+ # The keys should be the same as the keys of the counterpart InventoryObject
25
+ # @param manager [ManageIQ::Providers::BaseManager] The Manager owning the Target
26
+ # @param manager_id [Integer] A primary key of the Manager owning the Target
27
+ # @param event_id [Integer] A primary key of the EmsEvent associated with the Target
28
+ # @param options [Hash] A free form options hash
29
+ def add_target(association:, manager_ref:, manager: nil, manager_id: nil, event_id: nil, options: {})
30
+ self << InventoryRefresh::Target.new(:association => association,
31
+ :manager_ref => manager_ref,
32
+ :manager => manager || @manager,
33
+ :manager_id => manager_id || @manager_id || @manager.try(:id),
34
+ :event_id => event_id || @event.try(:id),
35
+ :options => options)
36
+ end
37
+
38
+ # @return [String] A String containing a summary
39
+ def name
40
+ "Collection of #{targets.size} targets"
41
+ end
42
+
43
+ # @return [String] A String containing an id of each target in the TargetCollection
44
+ def id
45
+ "Collection of targets with id: #{targets.collect(&:name)}"
46
+ end
47
+
48
+ # Returns targets in a format:
49
+ # {
50
+ # :vms => {:ems_ref => Set.new(["vm_ref_1", "vm_ref2"])},
51
+ # :network_ports => {:ems_ref => Set.new(["network_port_1", "network_port2"])
52
+ # }
53
+ #
54
+ # Then we can quickly access all objects affected by:
55
+ # NetworkPort.where(target_collection.manager_refs_by_association[:network_ports].to_a) =>
56
+ # return AR objects with ems_refs ["network_port_1", "network_port2"]
57
+ # And we can get a list of ids for the API query by:
58
+ # target_collection.manager_refs_by_association[:network_ports][:ems_ref].to_a =>
59
+ # ["network_port_1", "network_port2"]
60
+ #
61
+ # Only targets of a type InventoryRefresh::Target are processed, any other targets present should be converted to
62
+ # InventoryRefresh::Target, e.g. in the Inventory::Collector code.
63
+ def manager_refs_by_association
64
+ @manager_refs_by_association ||= targets.select { |x| x.kind_of?(InventoryRefresh::Target) }.each_with_object({}) do |x, obj|
65
+ if obj[x.association].blank?
66
+ obj[x.association] = x.manager_ref.each_with_object({}) { |(key, value), hash| hash[key] = Set.new([value]) }
67
+ else
68
+ obj[x.association].each do |key, value|
69
+ value << x.manager_ref[key]
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ # Resets the cached @manager_refs_by_association to enforce reload when calling :manager_refs_by_association method
76
+ def manager_refs_by_association_reset
77
+ @manager_refs_by_association = nil
78
+ end
79
+
80
+ # Returns list of ems_refs
81
+ # @return [Array<String>]
82
+ def references(collection)
83
+ manager_refs_by_association.try(:[], collection).try(:[], :ems_ref)&.to_a || []
84
+ end
85
+
86
+ # Returns list of names
87
+ # @return [Array<String>]
88
+ def name_references(collection)
89
+ manager_refs_by_association.try(:[], collection).try(:[], :name)&.to_a || []
90
+ end
91
+ end
92
+ end
@@ -1,3 +1,3 @@
1
1
  module InventoryRefresh
2
- VERSION = "0.3.3".freeze
2
+ VERSION = "1.0.0".freeze
3
3
  end
@@ -6,4 +6,6 @@ require "inventory_refresh/logging"
6
6
  require "inventory_refresh/null_logger"
7
7
  require "inventory_refresh/persister"
8
8
  require "inventory_refresh/save_inventory"
9
+ require "inventory_refresh/target"
10
+ require "inventory_refresh/target_collection"
9
11
  require "inventory_refresh/version"