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.
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