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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +25 -30
- data/.github/workflows/ci.yaml +47 -0
- data/.rubocop.yml +3 -3
- data/.rubocop_cc.yml +3 -4
- data/.rubocop_local.yml +5 -2
- data/.whitesource +3 -0
- data/CHANGELOG.md +19 -0
- data/Gemfile +10 -4
- data/README.md +1 -2
- data/Rakefile +2 -2
- data/inventory_refresh.gemspec +9 -10
- data/lib/inventory_refresh/application_record_iterator.rb +25 -12
- data/lib/inventory_refresh/graph/topological_sort.rb +24 -26
- data/lib/inventory_refresh/graph.rb +2 -2
- data/lib/inventory_refresh/inventory_collection/builder.rb +37 -15
- data/lib/inventory_refresh/inventory_collection/data_storage.rb +9 -0
- data/lib/inventory_refresh/inventory_collection/helpers/initialize_helper.rb +147 -38
- data/lib/inventory_refresh/inventory_collection/helpers/questions_helper.rb +49 -5
- data/lib/inventory_refresh/inventory_collection/index/proxy.rb +35 -3
- data/lib/inventory_refresh/inventory_collection/index/type/base.rb +8 -0
- data/lib/inventory_refresh/inventory_collection/index/type/local_db.rb +2 -0
- data/lib/inventory_refresh/inventory_collection/index/type/skeletal.rb +1 -0
- data/lib/inventory_refresh/inventory_collection/reference.rb +1 -0
- data/lib/inventory_refresh/inventory_collection/references_storage.rb +17 -0
- data/lib/inventory_refresh/inventory_collection/scanner.rb +91 -3
- data/lib/inventory_refresh/inventory_collection/serialization.rb +16 -10
- data/lib/inventory_refresh/inventory_collection.rb +122 -64
- data/lib/inventory_refresh/inventory_object.rb +74 -40
- data/lib/inventory_refresh/inventory_object_lazy.rb +17 -10
- data/lib/inventory_refresh/null_logger.rb +2 -2
- data/lib/inventory_refresh/persister.rb +31 -65
- data/lib/inventory_refresh/save_collection/base.rb +4 -2
- data/lib/inventory_refresh/save_collection/saver/base.rb +114 -15
- data/lib/inventory_refresh/save_collection/saver/batch.rb +17 -0
- data/lib/inventory_refresh/save_collection/saver/concurrent_safe_batch.rb +129 -51
- data/lib/inventory_refresh/save_collection/saver/default.rb +57 -0
- data/lib/inventory_refresh/save_collection/saver/partial_upsert_helper.rb +2 -19
- data/lib/inventory_refresh/save_collection/saver/retention_helper.rb +68 -3
- data/lib/inventory_refresh/save_collection/saver/sql_helper.rb +125 -0
- data/lib/inventory_refresh/save_collection/saver/sql_helper_update.rb +10 -6
- data/lib/inventory_refresh/save_collection/saver/sql_helper_upsert.rb +28 -16
- data/lib/inventory_refresh/save_collection/sweeper.rb +17 -93
- data/lib/inventory_refresh/save_collection/topological_sort.rb +5 -5
- data/lib/inventory_refresh/save_inventory.rb +5 -12
- data/lib/inventory_refresh/target.rb +73 -0
- data/lib/inventory_refresh/target_collection.rb +92 -0
- data/lib/inventory_refresh/version.rb +1 -1
- data/lib/inventory_refresh.rb +2 -0
- metadata +42 -39
- data/.travis.yml +0 -23
- 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
|
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
|
-
|
134
|
-
|
135
|
-
|
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,
|
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
|
-
|
68
|
+
case on_conflict
|
69
|
+
when :do_nothing
|
67
70
|
<<-SQL
|
68
71
|
ON CONFLICT DO NOTHING
|
69
72
|
SQL
|
70
|
-
|
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
|
-
|
111
|
+
case mode
|
112
|
+
when :full
|
107
113
|
full_update_condition(version_attribute)
|
108
|
-
|
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}
|
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 =
|
143
|
+
cast = case attr_full
|
144
|
+
when :resource_timestamp
|
137
145
|
"timestamp"
|
138
|
-
|
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}
|
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}
|
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
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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,
|
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,
|
16
|
+
next unless sweep_possible?(inventory_collection, refresh_state)
|
24
17
|
|
25
|
-
new(inventory_collection, refresh_state
|
18
|
+
new(inventory_collection, refresh_state).sweep
|
26
19
|
end
|
27
20
|
end
|
28
21
|
|
29
|
-
def sweep_possible?(inventory_collection,
|
30
|
-
inventory_collection.supports_column?(:last_seen_at) &&
|
31
|
-
|
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
|
38
|
-
return
|
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
|
-
|
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, :
|
38
|
+
attr_reader :inventory_collection, :refresh_state, :model_class, :primary_key
|
51
39
|
|
52
|
-
|
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
|
-
@
|
60
|
-
@
|
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
|
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.
|
20
|
+
logger.debug("Saving manager #{ems.name}...")
|
21
21
|
|
22
|
-
sorted_graph_log = "Topological sorting of manager #{ems.
|
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.
|
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.
|
31
|
+
logger.debug("Saved manager #{ems.name} | Layer #{index}")
|
32
32
|
end
|
33
33
|
|
34
|
-
logger.debug("Saving manager #{ems.
|
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,
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
data/lib/inventory_refresh.rb
CHANGED
@@ -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"
|