inventory_refresh 0.0.1
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 +7 -0
- data/.codeclimate.yml +47 -0
- data/.gitignore +13 -0
- data/.rspec +4 -0
- data/.rspec_ci +4 -0
- data/.rubocop.yml +4 -0
- data/.rubocop_cc.yml +5 -0
- data/.rubocop_local.yml +2 -0
- data/.travis.yml +12 -0
- data/.yamllint +12 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +6 -0
- data/LICENSE +202 -0
- data/README.md +35 -0
- data/Rakefile +47 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/inventory_refresh.gemspec +34 -0
- data/lib/inventory_refresh.rb +11 -0
- data/lib/inventory_refresh/application_record_iterator.rb +56 -0
- data/lib/inventory_refresh/application_record_reference.rb +15 -0
- data/lib/inventory_refresh/graph.rb +157 -0
- data/lib/inventory_refresh/graph/topological_sort.rb +66 -0
- data/lib/inventory_refresh/inventory_collection.rb +1175 -0
- data/lib/inventory_refresh/inventory_collection/data_storage.rb +178 -0
- data/lib/inventory_refresh/inventory_collection/graph.rb +170 -0
- data/lib/inventory_refresh/inventory_collection/index/proxy.rb +230 -0
- data/lib/inventory_refresh/inventory_collection/index/type/base.rb +80 -0
- data/lib/inventory_refresh/inventory_collection/index/type/data.rb +26 -0
- data/lib/inventory_refresh/inventory_collection/index/type/local_db.rb +286 -0
- data/lib/inventory_refresh/inventory_collection/index/type/skeletal.rb +116 -0
- data/lib/inventory_refresh/inventory_collection/reference.rb +96 -0
- data/lib/inventory_refresh/inventory_collection/references_storage.rb +106 -0
- data/lib/inventory_refresh/inventory_collection/scanner.rb +117 -0
- data/lib/inventory_refresh/inventory_collection/serialization.rb +140 -0
- data/lib/inventory_refresh/inventory_object.rb +303 -0
- data/lib/inventory_refresh/inventory_object_lazy.rb +151 -0
- data/lib/inventory_refresh/save_collection/base.rb +38 -0
- data/lib/inventory_refresh/save_collection/recursive.rb +52 -0
- data/lib/inventory_refresh/save_collection/saver/base.rb +390 -0
- data/lib/inventory_refresh/save_collection/saver/batch.rb +17 -0
- data/lib/inventory_refresh/save_collection/saver/concurrent_safe.rb +71 -0
- data/lib/inventory_refresh/save_collection/saver/concurrent_safe_batch.rb +632 -0
- data/lib/inventory_refresh/save_collection/saver/default.rb +57 -0
- data/lib/inventory_refresh/save_collection/saver/sql_helper.rb +85 -0
- data/lib/inventory_refresh/save_collection/saver/sql_helper_update.rb +120 -0
- data/lib/inventory_refresh/save_collection/saver/sql_helper_upsert.rb +196 -0
- data/lib/inventory_refresh/save_collection/topological_sort.rb +38 -0
- data/lib/inventory_refresh/save_inventory.rb +38 -0
- data/lib/inventory_refresh/target.rb +73 -0
- data/lib/inventory_refresh/target_collection.rb +80 -0
- data/lib/inventory_refresh/version.rb +3 -0
- data/tools/ci/create_db_user.sh +3 -0
- metadata +207 -0
@@ -0,0 +1,178 @@
|
|
1
|
+
require "active_support/core_ext/module/delegation"
|
2
|
+
|
3
|
+
module InventoryRefresh
|
4
|
+
class InventoryCollection
|
5
|
+
class DataStorage
|
6
|
+
# @return [Array<InventoryObject>] objects of the InventoryCollection in an Array
|
7
|
+
attr_accessor :data
|
8
|
+
|
9
|
+
attr_reader :index_proxy, :inventory_collection
|
10
|
+
|
11
|
+
delegate :each, :size, :to => :data
|
12
|
+
|
13
|
+
delegate :primary_index,
|
14
|
+
:build_primary_index_for,
|
15
|
+
:build_secondary_indexes_for,
|
16
|
+
:named_ref,
|
17
|
+
:skeletal_primary_index,
|
18
|
+
:to => :index_proxy
|
19
|
+
|
20
|
+
delegate :association_to_foreign_key_mapping,
|
21
|
+
:default_values,
|
22
|
+
:inventory_object?,
|
23
|
+
:inventory_object_lazy?,
|
24
|
+
:manager_ref,
|
25
|
+
:new_inventory_object,
|
26
|
+
:to => :inventory_collection
|
27
|
+
|
28
|
+
# @param inventory_collection [InventoryRefresh::InventoryCollection] InventoryCollection object we want the storage
|
29
|
+
# for
|
30
|
+
# @param secondary_refs [Hash] Secondary_refs in format {:name_of_the_ref => [:attribute1, :attribute2]}
|
31
|
+
def initialize(inventory_collection, secondary_refs)
|
32
|
+
@inventory_collection = inventory_collection
|
33
|
+
@data = []
|
34
|
+
|
35
|
+
@index_proxy = InventoryRefresh::InventoryCollection::Index::Proxy.new(inventory_collection, secondary_refs)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Adds passed InventoryObject into the InventoryCollection's storage
|
39
|
+
#
|
40
|
+
# @param inventory_object [InventoryRefresh::InventoryObject]
|
41
|
+
# @return [InventoryRefresh::InventoryCollection] Returns current InventoryCollection, to allow chaining
|
42
|
+
def <<(inventory_object)
|
43
|
+
if inventory_object.manager_uuid.present? && !primary_index.find(inventory_object.manager_uuid)
|
44
|
+
data << inventory_object
|
45
|
+
|
46
|
+
# TODO(lsmola) Maybe we do not need the secondary indexes here?
|
47
|
+
# Maybe we should index it like LocalDb indexes, on demand, and storing what was
|
48
|
+
# indexed? Maybe we should allow only lazy access and no direct find from a parser. Since for streaming
|
49
|
+
# refresh, things won't be parsed together and no full state will be taken.
|
50
|
+
build_primary_index_for(inventory_object)
|
51
|
+
build_secondary_indexes_for(inventory_object)
|
52
|
+
end
|
53
|
+
inventory_collection
|
54
|
+
end
|
55
|
+
|
56
|
+
alias push <<
|
57
|
+
|
58
|
+
# Finds of builds a new InventoryObject. By building it, we also put in into the InventoryCollection's storage.
|
59
|
+
#
|
60
|
+
# @param manager_uuid [String] manager_uuid of the InventoryObject
|
61
|
+
# @return [InventoryRefresh::InventoryObject] Found or built InventoryObject
|
62
|
+
def find_or_build(manager_uuid)
|
63
|
+
raise "The uuid consists of #{manager_ref.size} attributes, please find_or_build_by method" if manager_ref.size > 1
|
64
|
+
|
65
|
+
find_or_build_by(manager_ref.first => manager_uuid)
|
66
|
+
end
|
67
|
+
|
68
|
+
# (see #build)
|
69
|
+
def find_or_build_by(hash)
|
70
|
+
build(hash)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Finds InventoryObject.
|
74
|
+
#
|
75
|
+
# @param hash [Hash] Hash that needs to contain attributes defined in :manager_ref of the InventoryCollection
|
76
|
+
# @return [InventoryRefresh::InventoryObject] Found or built InventoryObject object
|
77
|
+
def find_in_data(hash)
|
78
|
+
_hash, _uuid, inventory_object = primary_index_scan(hash)
|
79
|
+
inventory_object
|
80
|
+
end
|
81
|
+
|
82
|
+
# Finds of builds a new InventoryObject. By building it, we also put in into the InventoryCollection's storage.
|
83
|
+
#
|
84
|
+
# @param hash [Hash] Hash that needs to contain attributes defined in :manager_ref of the
|
85
|
+
# InventoryCollection
|
86
|
+
# @return [InventoryRefresh::InventoryObject] Found or built InventoryObject object
|
87
|
+
def build(hash)
|
88
|
+
hash, uuid, inventory_object = primary_index_scan(hash)
|
89
|
+
|
90
|
+
# Return InventoryObject if found in primary index
|
91
|
+
return inventory_object unless inventory_object.nil?
|
92
|
+
|
93
|
+
# We will take existing skeletal record, so we don't duplicate references for saving. We can have duplicated
|
94
|
+
# reference from local_db index, (if we are using .find in parser, that causes N+1 db queries), but that is ok,
|
95
|
+
# since that one is not being saved.
|
96
|
+
inventory_object = skeletal_primary_index.delete(uuid)
|
97
|
+
|
98
|
+
# We want to update the skeletal record with actual data
|
99
|
+
inventory_object&.assign_attributes(hash)
|
100
|
+
|
101
|
+
# Build the InventoryObject
|
102
|
+
inventory_object ||= new_inventory_object(enrich_data(hash))
|
103
|
+
|
104
|
+
# Store new InventoryObject and return it
|
105
|
+
push(inventory_object)
|
106
|
+
inventory_object
|
107
|
+
end
|
108
|
+
|
109
|
+
# Finds of builds a new InventoryObject with incomplete data.
|
110
|
+
#
|
111
|
+
# @param hash [Hash] Hash that needs to contain attributes defined in :manager_ref of the
|
112
|
+
# InventoryCollection
|
113
|
+
# @return [InventoryRefresh::InventoryObject] Found or built InventoryObject object
|
114
|
+
def build_partial(hash)
|
115
|
+
skeletal_primary_index.build(hash)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns array of built InventoryObject objects
|
119
|
+
#
|
120
|
+
# @return [Array<InventoryRefresh::InventoryObject>] Array of built InventoryObject objects
|
121
|
+
def to_a
|
122
|
+
data
|
123
|
+
end
|
124
|
+
|
125
|
+
def to_hash
|
126
|
+
InventoryRefresh::InventoryCollection::Serialization.new(inventory_collection).to_hash
|
127
|
+
end
|
128
|
+
|
129
|
+
def from_hash(inventory_objects_data, available_inventory_collections)
|
130
|
+
InventoryRefresh::InventoryCollection::Serialization
|
131
|
+
.new(inventory_collection)
|
132
|
+
.from_hash(inventory_objects_data, available_inventory_collections)
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
# Scans primary index for existing InventoryObject, that would be equivalent to passed hash. It also returns
|
138
|
+
# enriched data and uuid, so we do not have to compute it multiple times.
|
139
|
+
#
|
140
|
+
# @param hash [Hash] Attributes for the InventoryObject
|
141
|
+
# @return [Array(Hash, String, InventoryRefresh::InventoryObject)] Returns enriched data, uuid and InventoryObject
|
142
|
+
# if found (otherwise nil)
|
143
|
+
def primary_index_scan(hash)
|
144
|
+
hash = enrich_data(hash)
|
145
|
+
|
146
|
+
assert_all_keys_present(hash)
|
147
|
+
assert_only_primary_index(hash)
|
148
|
+
|
149
|
+
uuid = ::InventoryRefresh::InventoryCollection::Reference.build_stringified_reference(hash, named_ref)
|
150
|
+
return hash, uuid, primary_index.find(uuid)
|
151
|
+
end
|
152
|
+
|
153
|
+
def assert_all_keys_present(hash)
|
154
|
+
if manager_ref.any? { |x| !hash.key?(x) }
|
155
|
+
raise "Needed find_or_build_by keys are: #{manager_ref}, data provided: #{hash}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def assert_only_primary_index(data)
|
160
|
+
named_ref.each do |key|
|
161
|
+
if data[key].kind_of?(InventoryRefresh::InventoryObjectLazy) && !data[key].primary?
|
162
|
+
raise "Wrong index for key :#{key}, all references under this index must point to default :ref called"\
|
163
|
+
" :manager_ref. Any other :ref is not valid. This applies also to nested lazy links."
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Returns new hash enriched by (see InventoryRefresh::InventoryCollection#default_values) hash
|
169
|
+
#
|
170
|
+
# @param hash [Hash] Input hash
|
171
|
+
# @return [Hash] Enriched hash by (see InventoryRefresh::InventoryCollection#default_values)
|
172
|
+
def enrich_data(hash)
|
173
|
+
# This is 25% faster than default_values.merge(hash)
|
174
|
+
{}.merge!(default_values).merge!(hash)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
module InventoryRefresh
|
2
|
+
class InventoryCollection
|
3
|
+
class Graph < ::InventoryRefresh::Graph
|
4
|
+
# @param nodes [Array<InventoryRefresh::InventoryCollection>] List of Inventory collection nodes
|
5
|
+
def initialize(nodes)
|
6
|
+
super(nodes)
|
7
|
+
|
8
|
+
assert_inventory_collections(nodes)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Turns the graph into DAG (Directed Acyclic Graph)
|
12
|
+
#
|
13
|
+
# @return [InventoryRefresh::InventoryCollection::Graph] Graph object modified into DAG
|
14
|
+
def build_directed_acyclic_graph!
|
15
|
+
################################################################################################################
|
16
|
+
## Description of an algorithm for building DAG
|
17
|
+
################################################################################################################
|
18
|
+
# Terms:
|
19
|
+
##############
|
20
|
+
# Fixed Edges - Are edges that cannot be removed from Graph, in our case these are edges caused by attributes
|
21
|
+
# that has to be present before saving the record. These are attributes that are part of the
|
22
|
+
# record identity (unique index of the DB record) and attributes validated for presence.
|
23
|
+
# Feedback Edge Set - Is a set of edges that are causing a cycle in the graph
|
24
|
+
# DAG - Directed Acyclic Graph
|
25
|
+
#
|
26
|
+
# Alghoritm:
|
27
|
+
##############
|
28
|
+
# Building a Feedback Edge Set:
|
29
|
+
# We will be building DAG from a Graph containing cycles, the algorithm is building a Feedback Edge Set by
|
30
|
+
# adding edges to a DAG made from Fixed Edges, while checking if by adding a new edge we haven't created
|
31
|
+
# a cycle in the graph.
|
32
|
+
# Converting original graph to DAG:
|
33
|
+
# Using the Feedback Edge Set, we remove the attributes causing cycles from the original graph. This way, we
|
34
|
+
# get a DAG, but the DAG is missing attributes.
|
35
|
+
# Using the Feedback Edge Set we create new Nodes, containing only removed attributes in a step before. These
|
36
|
+
# nodes will be attached to Graph according to their dependencies. By saving these nodes, we will add the
|
37
|
+
# missing relations.
|
38
|
+
################################################################################################################
|
39
|
+
|
40
|
+
# Assert that Fixed edges do not have a cycle, we cannot move with fixed edges, so exception is thrown here
|
41
|
+
assert_graph!(fixed_edges)
|
42
|
+
|
43
|
+
# Collect Feedback Edge (Arc) Set
|
44
|
+
feedback_edge_set = build_feedback_edge_set(edges, fixed_edges)
|
45
|
+
|
46
|
+
# We will build a DAG using the Feedback Edge (Arc) Set. All edges from this set has to be removed, and the
|
47
|
+
# edges are transferred to newly created nodes.
|
48
|
+
convert_to_dag!(nodes, feedback_edge_set)
|
49
|
+
|
50
|
+
# And assert again we've really built a DAG
|
51
|
+
# TODO(lsmola) And if the result causes a cycle, we should repeat the build_dag method, with a max
|
52
|
+
# depth 10. We should throw a warning maybe asking for simplifying the interconnections in the models.
|
53
|
+
assert_graph!(edges)
|
54
|
+
|
55
|
+
self.nodes = sort_nodes(nodes)
|
56
|
+
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# Sort the nodes putting child nodes as last. Child nodes are InventoryCollection objects having
|
63
|
+
# :parent_inventory_collections definition.
|
64
|
+
#
|
65
|
+
# @param nodes [Array<InventoryRefresh::InventoryCollection>] List of Inventory collection nodes
|
66
|
+
# @return [Array<InventoryRefresh::InventoryCollection>] Sorted list of Inventory collection nodes
|
67
|
+
def sort_nodes(nodes)
|
68
|
+
# Separate to root nodes and child nodes, where child nodes are determined by having parent_inventory_collections
|
69
|
+
root_nodes, child_nodes = nodes.partition { |node| node.parent_inventory_collections.blank? }
|
70
|
+
# And order it that root nodes comes first, that way the disconnect of the child nodes should be always done as
|
71
|
+
# a part of the root nodes, as intended.
|
72
|
+
root_nodes + child_nodes
|
73
|
+
end
|
74
|
+
|
75
|
+
# Asserts all nodes are of class ::InventoryRefresh::InventoryCollection
|
76
|
+
#
|
77
|
+
# @param inventory_collections [Array<InventoryRefresh::InventoryCollection>] List of Inventory collection nodes
|
78
|
+
def assert_inventory_collections(inventory_collections)
|
79
|
+
inventory_collections.each do |inventory_collection|
|
80
|
+
unless inventory_collection.kind_of?(::InventoryRefresh::InventoryCollection)
|
81
|
+
raise "A InventoryRefresh::SaveInventory needs a InventoryCollection object, it got: #{inventory_collection.inspect}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Converts the graph into DAG
|
87
|
+
#
|
88
|
+
# @param nodes [Array<InventoryRefresh::InventoryCollection>] List of Inventory collection nodes
|
89
|
+
# @param feedback_edge_set [Array<Array>] Is a set of edges that are causing a cycle in the graph
|
90
|
+
# @return [InventoryRefresh::InventoryCollection::Graph] Graph object modified into DAG
|
91
|
+
def convert_to_dag!(nodes, feedback_edge_set)
|
92
|
+
new_nodes = []
|
93
|
+
inventory_collection_transformations = {}
|
94
|
+
nodes.each do |inventory_collection|
|
95
|
+
feedback_dependencies = feedback_edge_set.select { |e| e.second == inventory_collection }.map(&:first)
|
96
|
+
attrs = inventory_collection.dependency_attributes_for(feedback_dependencies)
|
97
|
+
|
98
|
+
next if attrs.blank?
|
99
|
+
|
100
|
+
new_inventory_collection = inventory_collection.clone
|
101
|
+
|
102
|
+
# Add inventory_collection as a dependency of the new_inventory_collection, so we make sure it runs after
|
103
|
+
# TODO(lsmola) add a nice dependency_attributes setter? It's used also in actualize_dependencies method
|
104
|
+
new_inventory_collection.dependency_attributes[:__feedback_edge_set_parent] = Set.new([inventory_collection])
|
105
|
+
new_nodes << new_inventory_collection
|
106
|
+
|
107
|
+
inventory_collection.blacklist_attributes!(attrs)
|
108
|
+
new_inventory_collection.whitelist_attributes!(attrs)
|
109
|
+
|
110
|
+
# Store a simple hash for transforming inventory_collection to new_inventory_collection
|
111
|
+
inventory_collection_transformations[inventory_collection] = new_inventory_collection
|
112
|
+
end
|
113
|
+
|
114
|
+
all_nodes = nodes + new_nodes
|
115
|
+
|
116
|
+
# If we remove an attribute that was a dependency of another node, we need to move also the
|
117
|
+
# dependency. So e.g. floating_ip depends on network_port's attribute vm, but we move that attribute to new
|
118
|
+
# network_port inventory_collection. We will need to move also the dependency to point to the new
|
119
|
+
# inventory_collection.
|
120
|
+
#
|
121
|
+
# So we have to go through all dependencies that loads a key, which is the moved attribute. We can get a list
|
122
|
+
# of attributes that are using a key from transitive_dependency_attributes, from there we can get a list of
|
123
|
+
# dependencies. And from the list of dependencies, we can check which ones were moved just by looking into
|
124
|
+
# inventory_collection_transformations.
|
125
|
+
all_nodes.each do |inventory_collection|
|
126
|
+
inventory_collection.transitive_dependency_attributes.each do |transitive_dependency_attribute|
|
127
|
+
transitive_dependencies = inventory_collection.dependency_attributes[transitive_dependency_attribute]
|
128
|
+
next if transitive_dependencies.blank?
|
129
|
+
|
130
|
+
transitive_dependencies.map! do |dependency|
|
131
|
+
transformed_dependency = inventory_collection_transformations[dependency]
|
132
|
+
transformed_dependency.blank? ? dependency : transformed_dependency
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Add the new InventoryCollections to the list of nodes our our graph
|
138
|
+
construct_graph!(all_nodes)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Build edges by introspecting the passed array of InventoryCollection objects
|
142
|
+
#
|
143
|
+
# @param inventory_collections [Array<InventoryRefresh::InventoryCollection>] List of Inventory collection nodes
|
144
|
+
# @return [Array<Array>] Nested array representing edges
|
145
|
+
def build_edges(inventory_collections)
|
146
|
+
edges = []
|
147
|
+
transitive_edges = []
|
148
|
+
fixed_edges = []
|
149
|
+
inventory_collections.each do |inventory_collection|
|
150
|
+
inventory_collection.dependencies.each do |dependency|
|
151
|
+
fixed_edges << [dependency, inventory_collection] if inventory_collection.fixed_dependencies.include?(dependency)
|
152
|
+
if inventory_collection.dependency_attributes_for([dependency]).any? { |x| inventory_collection.transitive_dependency_attributes.include?(x) }
|
153
|
+
# The condition checks if the dependency is a transitive dependency, in other words a InventoryObjectLazy with :key
|
154
|
+
# pointing to another object.
|
155
|
+
transitive_edges << [dependency, inventory_collection]
|
156
|
+
else
|
157
|
+
edges << [dependency, inventory_collection]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
# We put transitive edges to the end. Transitive edge is e.g.: given graph (X,Y,Z), we have a lazy link, from X
|
162
|
+
# to Y, making edge (Y, X), using a :key pointing to Z. Which means that also edge from Y to Z (Z, Y) exists.
|
163
|
+
# If the edge (Z, Y) is placed before (Y, X), we process it first. Then the edge (Y, X), causing hidden
|
164
|
+
# transitive relation X to Z (it's hidden because edge (Z, X) is not present), is processed as last and we do a
|
165
|
+
# more effective cycle removal if needed.
|
166
|
+
return edges + transitive_edges, fixed_edges
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
require "inventory_refresh/inventory_collection/index/type/data"
|
2
|
+
require "inventory_refresh/inventory_collection/index/type/local_db"
|
3
|
+
require "inventory_refresh/inventory_collection/index/type/skeletal"
|
4
|
+
require "active_support/core_ext/module/delegation"
|
5
|
+
|
6
|
+
module InventoryRefresh
|
7
|
+
class InventoryCollection
|
8
|
+
module Index
|
9
|
+
class Proxy
|
10
|
+
attr_reader :skeletal_primary_index
|
11
|
+
|
12
|
+
# @param inventory_collection [InventoryRefresh::InventoryCollection] InventoryCollection object owning the proxy
|
13
|
+
# @param secondary_refs [Hash] Secondary_refs in format {:name_of_the_ref => [:attribute1, :attribute2]}
|
14
|
+
def initialize(inventory_collection, secondary_refs = {})
|
15
|
+
@inventory_collection = inventory_collection
|
16
|
+
|
17
|
+
@primary_ref = {primary_index_ref => @inventory_collection.manager_ref}
|
18
|
+
@secondary_refs = secondary_refs
|
19
|
+
@all_refs = @primary_ref.merge(@secondary_refs)
|
20
|
+
|
21
|
+
@data_indexes = {}
|
22
|
+
@local_db_indexes = {}
|
23
|
+
|
24
|
+
@all_refs.each do |index_name, attribute_names|
|
25
|
+
@data_indexes[index_name] = InventoryRefresh::InventoryCollection::Index::Type::Data.new(
|
26
|
+
inventory_collection,
|
27
|
+
index_name,
|
28
|
+
attribute_names
|
29
|
+
)
|
30
|
+
|
31
|
+
@local_db_indexes[index_name] = InventoryRefresh::InventoryCollection::Index::Type::LocalDb.new(
|
32
|
+
inventory_collection,
|
33
|
+
index_name,
|
34
|
+
attribute_names,
|
35
|
+
@data_indexes[index_name]
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
@skeletal_primary_index = InventoryRefresh::InventoryCollection::Index::Type::Skeletal.new(
|
40
|
+
inventory_collection,
|
41
|
+
:skeletal_primary_index_ref,
|
42
|
+
named_ref,
|
43
|
+
primary_index
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Builds primary index for passed InventoryObject
|
48
|
+
#
|
49
|
+
# @param inventory_object [InventoryRefresh::InventoryObject] InventoryObject we want to index
|
50
|
+
# @return [InventoryRefresh::InventoryObject] Passed InventoryObject
|
51
|
+
def build_primary_index_for(inventory_object)
|
52
|
+
# Building the object, we need to provide all keys of a primary index
|
53
|
+
|
54
|
+
assert_index(inventory_object.data, primary_index_ref)
|
55
|
+
primary_index.store_index_for(inventory_object)
|
56
|
+
end
|
57
|
+
|
58
|
+
def build_secondary_indexes_for(inventory_object)
|
59
|
+
secondary_refs.keys.each do |ref|
|
60
|
+
data_index(ref).store_index_for(inventory_object)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def reindex_secondary_indexes!
|
65
|
+
data_indexes.each do |ref, index|
|
66
|
+
next if ref == primary_index_ref
|
67
|
+
|
68
|
+
index.reindex!
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def primary_index
|
73
|
+
data_index(primary_index_ref)
|
74
|
+
end
|
75
|
+
|
76
|
+
def find(reference, ref: primary_index_ref)
|
77
|
+
# TODO(lsmola) lazy_find will support only hash, then we can remove the _by variant
|
78
|
+
# TODO(lsmola) this method should return lazy too, the rest of the finders should be deprecated
|
79
|
+
return if reference.nil?
|
80
|
+
assert_index(reference, ref)
|
81
|
+
|
82
|
+
reference = inventory_collection.build_reference(reference, ref)
|
83
|
+
|
84
|
+
case strategy
|
85
|
+
when :local_db_find_references, :local_db_cache_all
|
86
|
+
local_db_index_find(reference)
|
87
|
+
when :local_db_find_missing_references
|
88
|
+
find_in_data_or_skeletal_index(reference) || local_db_index_find(reference)
|
89
|
+
else
|
90
|
+
find_in_data_or_skeletal_index(reference)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def find_by(manager_uuid_hash, ref: primary_index_ref)
|
95
|
+
# TODO(lsmola) deprecate this, it's enough to have find method
|
96
|
+
find(manager_uuid_hash, :ref => ref)
|
97
|
+
end
|
98
|
+
|
99
|
+
def lazy_find_by(manager_uuid_hash, ref: primary_index_ref, key: nil, default: nil)
|
100
|
+
# TODO(lsmola) deprecate this, it's enough to have lazy_find method
|
101
|
+
|
102
|
+
lazy_find(manager_uuid_hash, :ref => ref, :key => key, :default => default)
|
103
|
+
end
|
104
|
+
|
105
|
+
def lazy_find(manager_uuid, ref: primary_index_ref, key: nil, default: nil, transform_nested_lazy_finds: false)
|
106
|
+
# TODO(lsmola) also, it should be enough to have only 1 find method, everything can be lazy, until we try to
|
107
|
+
# access the data
|
108
|
+
# TODO(lsmola) lazy_find will support only hash, then we can remove the _by variant
|
109
|
+
return if manager_uuid.nil?
|
110
|
+
assert_index(manager_uuid, ref)
|
111
|
+
|
112
|
+
::InventoryRefresh::InventoryObjectLazy.new(inventory_collection,
|
113
|
+
manager_uuid,
|
114
|
+
:ref => ref,
|
115
|
+
:key => key,
|
116
|
+
:default => default,
|
117
|
+
:transform_nested_lazy_finds => transform_nested_lazy_finds)
|
118
|
+
end
|
119
|
+
|
120
|
+
def named_ref(ref = primary_index_ref)
|
121
|
+
all_refs[ref]
|
122
|
+
end
|
123
|
+
|
124
|
+
def primary_index_ref
|
125
|
+
:manager_ref
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
delegate :association_to_foreign_key_mapping,
|
131
|
+
:build_stringified_reference,
|
132
|
+
:parallel_safe?,
|
133
|
+
:strategy,
|
134
|
+
:to => :inventory_collection
|
135
|
+
|
136
|
+
attr_reader :all_refs, :data_indexes, :inventory_collection, :primary_ref, :local_db_indexes, :secondary_refs
|
137
|
+
|
138
|
+
def find_in_data_or_skeletal_index(reference)
|
139
|
+
if parallel_safe?
|
140
|
+
# With parallel safe strategies, we create skeletal nodes that we can look for
|
141
|
+
data_index_find(reference) || skeletal_index_find(reference)
|
142
|
+
else
|
143
|
+
data_index_find(reference)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def skeletal_index_find(reference)
|
148
|
+
# Find in skeletal index, but we are able to create skeletal index only for primary indexes
|
149
|
+
skeletal_primary_index.find(reference.stringified_reference) if reference.primary?
|
150
|
+
end
|
151
|
+
|
152
|
+
def data_index_find(reference)
|
153
|
+
data_index(reference.ref).find(reference.stringified_reference)
|
154
|
+
end
|
155
|
+
|
156
|
+
def local_db_index_find(reference)
|
157
|
+
local_db_index(reference.ref).find(reference)
|
158
|
+
end
|
159
|
+
|
160
|
+
def data_index(name)
|
161
|
+
data_indexes[name] || raise("Index :#{name} not defined for #{inventory_collection}")
|
162
|
+
end
|
163
|
+
|
164
|
+
def local_db_index(name)
|
165
|
+
local_db_indexes[name] || raise("Index :#{name} not defined for #{inventory_collection}")
|
166
|
+
end
|
167
|
+
|
168
|
+
def missing_keys(data_keys, ref)
|
169
|
+
named_ref(ref) - data_keys
|
170
|
+
end
|
171
|
+
|
172
|
+
def required_index_keys_present?(data_keys, ref)
|
173
|
+
missing_keys(data_keys, ref).empty?
|
174
|
+
end
|
175
|
+
|
176
|
+
def assert_relation_keys(data, ref)
|
177
|
+
named_ref(ref).each do |key|
|
178
|
+
# Skip if the key is not a foreign key
|
179
|
+
next unless association_to_foreign_key_mapping[key]
|
180
|
+
# Skip if data on key are nil or InventoryObject or InventoryObjectLazy
|
181
|
+
next if data[key].nil? || data[key].kind_of?(InventoryRefresh::InventoryObject) || data[key].kind_of?(InventoryRefresh::InventoryObjectLazy)
|
182
|
+
# Raise error since relation must be nil or InventoryObject or InventoryObjectLazy
|
183
|
+
raise "Wrong index for key :#{key}, the value must be of type Nil or InventoryObject or InventoryObjectLazy, got: #{data[key]}"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def assert_index_exists(ref)
|
188
|
+
raise "Index :#{ref} doesn't exist on #{inventory_collection}" if named_ref(ref).nil?
|
189
|
+
end
|
190
|
+
|
191
|
+
def assert_index(manager_uuid, ref)
|
192
|
+
# TODO(lsmola) do we need some production logging too? Maybe the refresh log level could drive this
|
193
|
+
# Let' do this really slick development and test env, but disable for production, since the checks are pretty
|
194
|
+
# slow.
|
195
|
+
# TODO: return if Rails.env.production?
|
196
|
+
|
197
|
+
if manager_uuid.kind_of?(InventoryRefresh::InventoryCollection::Reference)
|
198
|
+
# InventoryRefresh::InventoryCollection::Reference has been already asserted, skip
|
199
|
+
elsif manager_uuid.kind_of?(Hash)
|
200
|
+
# Test te index exists
|
201
|
+
assert_index_exists(ref)
|
202
|
+
|
203
|
+
# Test we are sending all keys required for the index
|
204
|
+
unless required_index_keys_present?(manager_uuid.keys, ref)
|
205
|
+
raise "Finder has missing keys for index :#{ref}, missing indexes are: #{missing_keys(manager_uuid.keys, ref)}"
|
206
|
+
end
|
207
|
+
# Test that keys, that are relations, are nil or InventoryObject or InventoryObjectlazy class
|
208
|
+
assert_relation_keys(manager_uuid, ref)
|
209
|
+
else
|
210
|
+
# Test te index exists
|
211
|
+
assert_index_exists(ref)
|
212
|
+
|
213
|
+
# Check that other value (possibly String or Integer)) has no composite index
|
214
|
+
if named_ref(ref).size > 1
|
215
|
+
right_format = "collection.find(#{named_ref(ref).map { |x| ":#{x} => 'X'" }.join(", ")}"
|
216
|
+
|
217
|
+
raise "The index :#{ref} has composite index, finder has to be called as: #{right_format})"
|
218
|
+
end
|
219
|
+
|
220
|
+
# Assert the that possible relation is nil or InventoryObject or InventoryObjectlazy class
|
221
|
+
assert_relation_keys({named_ref(ref).first => manager_uuid}, ref)
|
222
|
+
end
|
223
|
+
rescue => e
|
224
|
+
#_log.error("Error when asserting index: #{manager_uuid}, with ref: #{ref} of: #{inventory_collection}")
|
225
|
+
raise e
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|