inventory_refresh 0.3.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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"