inventory_refresh 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|