inventory_refresh 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +47 -0
  3. data/.gitignore +13 -0
  4. data/.rspec +4 -0
  5. data/.rspec_ci +4 -0
  6. data/.rubocop.yml +4 -0
  7. data/.rubocop_cc.yml +5 -0
  8. data/.rubocop_local.yml +2 -0
  9. data/.travis.yml +12 -0
  10. data/.yamllint +12 -0
  11. data/CHANGELOG.md +0 -0
  12. data/Gemfile +6 -0
  13. data/LICENSE +202 -0
  14. data/README.md +35 -0
  15. data/Rakefile +47 -0
  16. data/bin/console +14 -0
  17. data/bin/setup +8 -0
  18. data/inventory_refresh.gemspec +34 -0
  19. data/lib/inventory_refresh.rb +11 -0
  20. data/lib/inventory_refresh/application_record_iterator.rb +56 -0
  21. data/lib/inventory_refresh/application_record_reference.rb +15 -0
  22. data/lib/inventory_refresh/graph.rb +157 -0
  23. data/lib/inventory_refresh/graph/topological_sort.rb +66 -0
  24. data/lib/inventory_refresh/inventory_collection.rb +1175 -0
  25. data/lib/inventory_refresh/inventory_collection/data_storage.rb +178 -0
  26. data/lib/inventory_refresh/inventory_collection/graph.rb +170 -0
  27. data/lib/inventory_refresh/inventory_collection/index/proxy.rb +230 -0
  28. data/lib/inventory_refresh/inventory_collection/index/type/base.rb +80 -0
  29. data/lib/inventory_refresh/inventory_collection/index/type/data.rb +26 -0
  30. data/lib/inventory_refresh/inventory_collection/index/type/local_db.rb +286 -0
  31. data/lib/inventory_refresh/inventory_collection/index/type/skeletal.rb +116 -0
  32. data/lib/inventory_refresh/inventory_collection/reference.rb +96 -0
  33. data/lib/inventory_refresh/inventory_collection/references_storage.rb +106 -0
  34. data/lib/inventory_refresh/inventory_collection/scanner.rb +117 -0
  35. data/lib/inventory_refresh/inventory_collection/serialization.rb +140 -0
  36. data/lib/inventory_refresh/inventory_object.rb +303 -0
  37. data/lib/inventory_refresh/inventory_object_lazy.rb +151 -0
  38. data/lib/inventory_refresh/save_collection/base.rb +38 -0
  39. data/lib/inventory_refresh/save_collection/recursive.rb +52 -0
  40. data/lib/inventory_refresh/save_collection/saver/base.rb +390 -0
  41. data/lib/inventory_refresh/save_collection/saver/batch.rb +17 -0
  42. data/lib/inventory_refresh/save_collection/saver/concurrent_safe.rb +71 -0
  43. data/lib/inventory_refresh/save_collection/saver/concurrent_safe_batch.rb +632 -0
  44. data/lib/inventory_refresh/save_collection/saver/default.rb +57 -0
  45. data/lib/inventory_refresh/save_collection/saver/sql_helper.rb +85 -0
  46. data/lib/inventory_refresh/save_collection/saver/sql_helper_update.rb +120 -0
  47. data/lib/inventory_refresh/save_collection/saver/sql_helper_upsert.rb +196 -0
  48. data/lib/inventory_refresh/save_collection/topological_sort.rb +38 -0
  49. data/lib/inventory_refresh/save_inventory.rb +38 -0
  50. data/lib/inventory_refresh/target.rb +73 -0
  51. data/lib/inventory_refresh/target_collection.rb +80 -0
  52. data/lib/inventory_refresh/version.rb +3 -0
  53. data/tools/ci/create_db_user.sh +3 -0
  54. 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