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,96 @@
1
+ require "active_support/core_ext/module/delegation"
2
+
3
+ module InventoryRefresh
4
+ class InventoryCollection
5
+ class Reference
6
+ attr_reader :full_reference, :keys, :ref, :stringified_reference
7
+
8
+ delegate :[], :to => :full_reference
9
+
10
+ # @param data [Hash, String] Data needed for creating the reference
11
+ # @param ref [String] Name of the reference (and of the index associated)
12
+ # @param keys [Array<Symbol>] Attribute/column names of the reference, that are used as indexes of the passed
13
+ # data hash
14
+ def initialize(data, ref, keys)
15
+ @full_reference = build_full_reference(data, keys)
16
+ @ref = ref
17
+ @keys = keys
18
+
19
+ @nested_secondary_index = keys.select { |key| full_reference[key].kind_of?(InventoryRefresh::InventoryObjectLazy) }.any? do |key|
20
+ !full_reference[key].primary?
21
+ end
22
+
23
+ @stringified_reference = self.class.build_stringified_reference(full_reference, keys)
24
+ end
25
+
26
+ # Return true if reference is to primary index, false otherwise. Reference is primary, only if all the nested
27
+ # references are primary.
28
+ #
29
+ # @return [Boolean] true if reference is to primary index, false otherwise
30
+ def primary?
31
+ ref == :manager_ref && !nested_secondary_index
32
+ end
33
+
34
+ def nested_secondary_index?
35
+ nested_secondary_index
36
+ end
37
+
38
+ class << self
39
+ # Builds string uuid from passed Hash and keys
40
+ #
41
+ # @param hash [Hash] Hash data
42
+ # @param keys [Array<Symbol>] Indexes into the Hash data
43
+ # @return [String] Concatenated values on keys from data
44
+ def build_stringified_reference(hash, keys)
45
+ stringify_reference(keys.map { |attribute| hash[attribute].to_s })
46
+ end
47
+
48
+ # Builds string uuid from passed Object and keys
49
+ #
50
+ # @param record [ApplicationRecord] ActiveRecord record
51
+ # @param keys [Array<Symbol>] Indexes into the Hash data
52
+ # @return [String] Concatenated values on keys from data
53
+ def build_stringified_reference_for_record(record, keys)
54
+ stringify_reference(keys.map { |attribute| record.public_send(attribute).to_s })
55
+ end
56
+
57
+ # Returns passed array joined by stringify_joiner
58
+ #
59
+ # @param reference [Array<String>]
60
+ # @return [String] Passed array joined by stringify_joiner
61
+ def stringify_reference(reference)
62
+ reference.join(stringify_joiner)
63
+ end
64
+
65
+ private
66
+
67
+ # Returns joiner for string UIID
68
+ #
69
+ # @return [String] Joiner for string UIID
70
+ def stringify_joiner
71
+ "__"
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ # @return Returns true if reference has nested references that are not pointing to primary index, but to
78
+ # secondary indexes.
79
+ attr_reader :nested_secondary_index
80
+
81
+ # Returns original Hash, or build hash out of passed string
82
+ #
83
+ # @param data [Hash, String] Passed data
84
+ # @param keys [Array<Symbol>] Keys for the reference
85
+ # @return [Hash] Original Hash, or build hash out of passed string
86
+ def build_full_reference(data, keys)
87
+ if data.kind_of?(Hash)
88
+ data
89
+ else
90
+ raise "Please provide Hash as a reference, :manager_ref count includes more than 1 attribute. keys: #{keys}, data: #{data}" if keys.size > 1
91
+ {keys.first => data}
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,106 @@
1
+ module InventoryRefresh
2
+ class InventoryCollection
3
+ class ReferencesStorage
4
+ # @return [Hash] A set of InventoryObjects manager_uuids, which tells us which InventoryObjects were
5
+ # referenced by other InventoryObjects using a lazy_find.
6
+ attr_reader :references
7
+
8
+ # @return [Set] A set of InventoryObject attributes names, which tells us InventoryObject attributes
9
+ # were referenced by other InventoryObject objects using a lazy_find with :key.
10
+ attr_reader :attribute_references
11
+
12
+ def initialize(index_proxy)
13
+ @index_proxy = index_proxy
14
+ @references = {}
15
+ @references[primary_index_ref] = {}
16
+ @attribute_references = Set.new
17
+ end
18
+
19
+ # Adds reference to the storage. The reference can be already existing, otherwise we attempt to build it.
20
+ #
21
+ # @param reference_data [InventoryRefresh::InventoryCollection::References, Hash, Object] Either existing Reference
22
+ # object, or data we will build the reference object from. For InventoryCollection with :manager_ref size
23
+ # bigger than 1, it's required to pass a Hash.
24
+ # @param key [String] If the reference comes from a InventoryObjectLazy, pointing to specific attribute using :key
25
+ # we want to record what attribute was referenced.
26
+ # @param ref [Symbol] A key to specific reference, if it's a reference pointing to something else than primary
27
+ # index.
28
+ def add_reference(reference_data, key: nil, ref: nil)
29
+ reference = build_reference(reference_data, ref)
30
+ specific_references = references[reference.ref] ||= {}
31
+
32
+ specific_references[reference.stringified_reference] = reference
33
+
34
+ # If we access an attribute of the value, using a :key, we want to keep a track of that
35
+ attribute_references << key if key
36
+ end
37
+
38
+ # Adds reference to the storage. The reference can be already existing, otherwise we attempt to build it. This is
39
+ # simplified version of add_reference, not allowing to define :key or :ref.
40
+ #
41
+ # @param reference_data [InventoryRefresh::InventoryCollection::References, Hash, Object] Either existing Reference
42
+ # object, or data we will build the reference object from. For InventoryCollection with :manager_ref size
43
+ # bigger than 1, it's required to pass a Hash.
44
+ def <<(reference_data)
45
+ add_reference(reference_data)
46
+ end
47
+
48
+ # Adds array of references to the storage. The reference can be already existing, otherwise we attempt to build
49
+ # it.
50
+ #
51
+ # @param references_array [Array] Array of reference objects acceptable by add_reference method.
52
+ # @param ref [Symbol] A key to specific reference, if it's a reference pointing to something else than primary
53
+ # index.
54
+ # @return [InventoryRefresh::InventoryCollection::ReferencesStorage] Returns self
55
+ def merge!(references_array, ref: nil)
56
+ references_array.each { |reference_data| add_reference(reference_data, :ref => ref) }
57
+ self
58
+ end
59
+
60
+ # @return [Hash{String => InventoryRefresh::InventoryCollection::Reference}] Hash of indexed Reference objects
61
+ def primary_references
62
+ references[primary_index_ref]
63
+ end
64
+
65
+ # Builds a Reference object
66
+ #
67
+ # @param reference_data [InventoryRefresh::InventoryCollection::References, Hash, Object] Either existing Reference
68
+ # object, or data we will build the reference object from. For InventoryCollection with :manager_ref size
69
+ # bigger than 1, it's required to pass a Hash.
70
+ def build_reference(reference_data, ref = nil)
71
+ ref ||= primary_index_ref
72
+ return reference_data if reference_data.kind_of?(::InventoryRefresh::InventoryCollection::Reference)
73
+
74
+ ::InventoryRefresh::InventoryCollection::Reference.new(reference_data, ref, named_ref(ref))
75
+ end
76
+
77
+ # Builds string uuid from passed Hash and keys
78
+ #
79
+ # @param hash [Hash] Hash data
80
+ # @param keys [Array<Symbol>] Indexes into the Hash data
81
+ # @return [String] Concatenated values on keys from data
82
+ def build_stringified_reference(hash, keys)
83
+ ::InventoryRefresh::InventoryCollection::Reference.build_stringified_reference(hash, keys)
84
+ end
85
+
86
+ # Builds string uuid from passed Object and keys
87
+ #
88
+ # @param record [ApplicationRecord] ActiveRecord record
89
+ # @param keys [Array<Symbol>] Indexes into the Hash data
90
+ # @return [String] Concatenated values on keys from data
91
+ def build_stringified_reference_for_record(record, keys)
92
+ ::InventoryRefresh::InventoryCollection::Reference.build_stringified_reference_for_record(record, keys)
93
+ end
94
+
95
+ private
96
+
97
+ # @return [InventoryRefresh::InventoryCollection::Index::Proxy] Index::Proxy object associated to this reference
98
+ # storage
99
+ attr_reader :index_proxy
100
+
101
+ delegate :named_ref,
102
+ :primary_index_ref,
103
+ :to => :index_proxy
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,117 @@
1
+ require "active_support/core_ext/module/delegation"
2
+
3
+ module InventoryRefresh
4
+ class InventoryCollection
5
+ class Scanner
6
+ class << self
7
+ # Scanning inventory_collections for dependencies and references, storing the results in the inventory_collections
8
+ # themselves. Dependencies are needed for building a graph, references are needed for effective DB querying, where
9
+ # we can load all referenced objects of some InventoryCollection by one DB query.
10
+ #
11
+ # @param inventory_collections [Array<InventoryRefresh::InventoryCollection>] Array of InventoryCollection objects
12
+ def scan!(inventory_collections)
13
+ indexed_inventory_collections = inventory_collections.index_by(&:name)
14
+
15
+ inventory_collections.each do |inventory_collection|
16
+ new(inventory_collection, indexed_inventory_collections).scan!
17
+ end
18
+
19
+ inventory_collections.each do |inventory_collection|
20
+ inventory_collection.dependencies.each do |dependency|
21
+ dependency.dependees << inventory_collection
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ attr_reader :inventory_collection, :indexed_inventory_collections
28
+
29
+ # Boolean helpers the scanner uses from the :inventory_collection
30
+ delegate :inventory_object_lazy?,
31
+ :inventory_object?,
32
+ :targeted?,
33
+ :to => :inventory_collection
34
+
35
+ # Methods the scanner uses from the :inventory_collection
36
+ delegate :data,
37
+ :find_or_build,
38
+ :manager_ref,
39
+ :saver_strategy,
40
+ :to => :inventory_collection
41
+
42
+ # The data scanner modifies inside of the :inventory_collection
43
+ delegate :attribute_references,
44
+ :data_collection_finalized=,
45
+ :dependency_attributes,
46
+ :targeted_scope,
47
+ :parent_inventory_collections,
48
+ :parent_inventory_collections=,
49
+ :references,
50
+ :transitive_dependency_attributes,
51
+ :to => :inventory_collection
52
+
53
+ def initialize(inventory_collection, indexed_inventory_collections)
54
+ @inventory_collection = inventory_collection
55
+ @indexed_inventory_collections = indexed_inventory_collections
56
+ end
57
+
58
+ def scan!
59
+ # Scan InventoryCollection InventoryObjects and store the results inside of the InventoryCollection
60
+ data.each do |inventory_object|
61
+ scan_inventory_object!(inventory_object)
62
+
63
+ if targeted? && parent_inventory_collections.blank?
64
+ # We want to track what manager_uuids we should query from a db, for the targeted refresh
65
+ targeted_scope << inventory_object.reference
66
+ end
67
+ end
68
+
69
+ # Transform :parent_inventory_collections symbols to InventoryCollection objects
70
+ if parent_inventory_collections.present?
71
+ self.parent_inventory_collections = parent_inventory_collections.map do |inventory_collection_index|
72
+ ic = indexed_inventory_collections[inventory_collection_index]
73
+ if ic.nil?
74
+ raise "Can't find InventoryCollection #{inventory_collection_index} from #{inventory_collection}" if targeted?
75
+ else
76
+ # Add parent_inventory_collection as a dependency, so e.g. disconnect is done in a right order
77
+ (dependency_attributes[:__parent_inventory_collections] ||= Set.new) << ic
78
+ ic
79
+ end
80
+ end.compact
81
+ end
82
+
83
+ # Mark InventoryCollection as finalized aka. scanned
84
+ self.data_collection_finalized = true
85
+ end
86
+
87
+ private
88
+
89
+ def scan_inventory_object!(inventory_object)
90
+ inventory_object.data.each do |key, value|
91
+ if value.kind_of?(Array)
92
+ value.each { |val| scan_inventory_object_attribute!(key, val) }
93
+ else
94
+ scan_inventory_object_attribute!(key, value)
95
+ end
96
+ end
97
+ end
98
+
99
+ def scan_inventory_object_attribute!(key, value)
100
+ return if !inventory_object_lazy?(value) && !inventory_object?(value)
101
+ value_inventory_collection = value.inventory_collection
102
+
103
+ # Storing attributes and their dependencies
104
+ (dependency_attributes[key] ||= Set.new) << value_inventory_collection if value.dependency?
105
+
106
+ # Storing a reference in the target inventory_collection, then each IC knows about all the references and can
107
+ # e.g. load all the referenced uuids from a DB
108
+ value_inventory_collection.add_reference(value.reference, :key => value.key)
109
+
110
+ if inventory_object_lazy?(value)
111
+ # Storing if attribute is a transitive dependency, so a lazy_find :key results in dependency
112
+ transitive_dependency_attributes << key if value.transitive_dependency?
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,140 @@
1
+ require "active_support/core_ext/module/delegation"
2
+
3
+ module InventoryRefresh
4
+ class InventoryCollection
5
+ class Serialization
6
+ delegate :all_manager_uuids,
7
+ :build,
8
+ :targeted_scope,
9
+ :data,
10
+ :inventory_object_lazy?,
11
+ :inventory_object?,
12
+ :name,
13
+ :skeletal_primary_index,
14
+ :to => :inventory_collection
15
+
16
+ attr_reader :inventory_collection
17
+
18
+ # @param inventory_collection [InventoryRefresh::InventoryCollection] InventoryCollection object we want the storage
19
+ # for
20
+ def initialize(inventory_collection)
21
+ @inventory_collection = inventory_collection
22
+ end
23
+
24
+ # Loads InventoryCollection data from it's serialized form into existing InventoryCollection object
25
+ #
26
+ # @param inventory_objects_data [Hash] Serialized InventoryCollection as Hash
27
+ # @param available_inventory_collections [Array<InventoryRefresh::InventoryCollection>] List of available
28
+ # InventoryCollection objects
29
+ def from_hash(inventory_objects_data, available_inventory_collections)
30
+ targeted_scope.merge!(inventory_objects_data["manager_uuids"].map(&:symbolize_keys!))
31
+
32
+ inventory_objects_data['data'].each do |inventory_object_data|
33
+ build(hash_to_data(inventory_object_data, available_inventory_collections).symbolize_keys!)
34
+ end
35
+
36
+ inventory_objects_data['partial_data'].each do |inventory_object_data|
37
+ skeletal_primary_index.build(hash_to_data(inventory_object_data, available_inventory_collections).symbolize_keys!)
38
+ end
39
+
40
+ # TODO(lsmola) add support for all_manager_uuids serialization
41
+ # self.all_manager_uuids = inventory_objects_data['all_manager_uuids']
42
+ end
43
+
44
+ # Serializes InventoryCollection's data storage into Array of Hashes
45
+ #
46
+ # @return [Hash] Serialized InventoryCollection object into Hash
47
+ def to_hash
48
+ {
49
+ :name => name,
50
+ # TODO(lsmola) we do not support nested references here, should we?
51
+ :manager_uuids => targeted_scope.primary_references.values.map(&:full_reference),
52
+ :all_manager_uuids => all_manager_uuids,
53
+ :data => data.map { |x| data_to_hash(x.data) },
54
+ :partial_data => skeletal_primary_index.index_data.map { |x| data_to_hash(x.data) },
55
+ }
56
+ end
57
+
58
+ private
59
+
60
+ # Converts InventoryRefresh::InventoryObject or InventoryRefresh::InventoryObjectLazy into Hash
61
+ #
62
+ # @param value [InventoryRefresh::InventoryObject, InventoryRefresh::InventoryObjectLazy] InventoryObject or a lazy link
63
+ # @param depth [Integer] Depth of nesting for nested lazy link
64
+ # @return [Hash] Serialized InventoryRefresh::InventoryObjectLazy
65
+ def lazy_relation_to_hash(value, depth = 0)
66
+ {
67
+ :type => "InventoryRefresh::InventoryObjectLazy",
68
+ :inventory_collection_name => value.inventory_collection.name,
69
+ :reference => data_to_hash(value.reference.full_reference, depth + 1),
70
+ :ref => value.reference.ref,
71
+ :key => value.try(:key),
72
+ :default => value.try(:default),
73
+ :transform_nested_lazy_finds => value.try(:transform_nested_lazy_finds)
74
+ }
75
+ end
76
+
77
+ # Converts Hash to InventoryRefresh::InventoryObjectLazy
78
+ #
79
+ # @param value [Hash] Serialized InventoryObject or a lazy link
80
+ # @param available_inventory_collections [Array<InventoryRefresh::InventoryCollection>] List of available
81
+ # InventoryCollection objects
82
+ # @param depth [Integer] Depth of nesting for nested lazy link
83
+ # @return [InventoryRefresh::InventoryObjectLazy] Lazy link created from hash
84
+ def hash_to_lazy_relation(value, available_inventory_collections, depth = 0)
85
+ inventory_collection = available_inventory_collections[value['inventory_collection_name'].try(:to_sym)]
86
+ raise "Couldn't build lazy_link #{value} the inventory_collection_name was not found" if inventory_collection.blank?
87
+
88
+ inventory_collection.lazy_find(
89
+ hash_to_data(value['reference'], available_inventory_collections, depth + 1).symbolize_keys!,
90
+ :ref => value['ref'].try(:to_sym),
91
+ :key => value['key'].try(:to_sym),
92
+ :default => value['default'],
93
+ :transform_nested_lazy_finds => value['transform_nested_lazy_finds']
94
+ )
95
+ end
96
+
97
+ # Converts Hash to attributes usable for building InventoryObject
98
+ #
99
+ # @param hash [Hash] Serialized InventoryObject data
100
+ # @param available_inventory_collections [Array<InventoryRefresh::InventoryCollection>] List of available
101
+ # InventoryCollection objects
102
+ # @param depth [Integer] Depth of nesting for nested lazy link
103
+ # @return [Hash] Hash with data usable for building InventoryObject
104
+ def hash_to_data(hash, available_inventory_collections, depth = 0)
105
+ raise "Nested lazy_relation of #{inventory_collection} is too deep, left processing: #{hash}" if depth > 20
106
+
107
+ hash.transform_values do |value|
108
+ if value.kind_of?(Hash) && value['type'] == "InventoryRefresh::InventoryObjectLazy"
109
+ hash_to_lazy_relation(value, available_inventory_collections, depth)
110
+ elsif value.kind_of?(Array) && value.first.kind_of?(Hash) && value.first['type'] == "InventoryRefresh::InventoryObjectLazy"
111
+ # TODO(lsmola) do we need to compact it sooner? What if first element is nil? On the other hand, we want to
112
+ # deprecate this Vm HABTM assignment because it's not effective
113
+ value.compact.map { |x| hash_to_lazy_relation(x, available_inventory_collections, depth) }
114
+ else
115
+ value
116
+ end
117
+ end
118
+ end
119
+
120
+ # Transforms data of the InventoryObject or Reference to InventoryObject into Hash
121
+ #
122
+ # @param data [Hash] Data of the InventoryObject or Reference to InventoryObject
123
+ # @param depth [Integer] Depth of nesting for nested lazy link
124
+ # @return [Hash] Serialized InventoryObject or Reference data into Hash
125
+ def data_to_hash(data, depth = 0)
126
+ raise "Nested lazy_relation of #{inventory_collection} is too deep, left processing: #{data}" if depth > 20
127
+
128
+ data.transform_values do |value|
129
+ if inventory_object_lazy?(value) || inventory_object?(value)
130
+ lazy_relation_to_hash(value, depth)
131
+ elsif value.kind_of?(Array) && (inventory_object_lazy?(value.compact.first) || inventory_object?(value.compact.first))
132
+ value.compact.map { |x| lazy_relation_to_hash(x, depth) }
133
+ else
134
+ value
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end