topological_inventory-providers-common 1.0.2 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/gem-push.yml +49 -0
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +3 -0
  5. data/.rubocop_cc.yml +4 -0
  6. data/.rubocop_local.yml +2 -0
  7. data/.travis.yml +8 -0
  8. data/CHANGELOG.md +29 -1
  9. data/Gemfile +0 -3
  10. data/lib/topological_inventory/providers/common/logging.rb +8 -0
  11. data/lib/topological_inventory/providers/common/operations/endpoint_client.rb +3 -0
  12. data/lib/topological_inventory/providers/common/operations/source.rb +191 -0
  13. data/lib/topological_inventory/providers/common/operations/sources_api_client.rb +15 -6
  14. data/lib/topological_inventory/providers/common/save_inventory/saver.rb +13 -4
  15. data/lib/topological_inventory/providers/common/version.rb +1 -1
  16. data/spec/spec_helper.rb +22 -0
  17. data/spec/support/inventory_helper.rb +14 -0
  18. data/spec/support/shared/availability_check.rb +236 -0
  19. data/spec/topological_inventory/providers/common/collector_spec.rb +171 -0
  20. data/spec/topological_inventory/providers/common/collectors/inventory_collection_storage_spec.rb +44 -0
  21. data/spec/topological_inventory/providers/common/collectors/inventory_collection_wrapper_spec.rb +9 -0
  22. data/spec/topological_inventory/providers/common/collectors_pool_spec.rb +150 -0
  23. data/spec/topological_inventory/providers/common/logger_spec.rb +38 -0
  24. data/spec/topological_inventory/providers/common/operations/processor_spec.rb +102 -0
  25. data/spec/topological_inventory/providers/common/operations/source_spec.rb +5 -0
  26. data/spec/topological_inventory/providers/common/save_inventory/saver_spec.rb +65 -0
  27. data/spec/topological_inventory/providers/common_spec.rb +3 -0
  28. data/topological_inventory-providers-common.gemspec +7 -1
  29. metadata +104 -3
@@ -1,7 +1,7 @@
1
1
  module TopologicalInventory
2
2
  module Providers
3
3
  module Common
4
- VERSION = "1.0.2"
4
+ VERSION = "1.0.7"
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,22 @@
1
+ if ENV['CI']
2
+ require 'simplecov'
3
+ SimpleCov.start
4
+ end
5
+
6
+ require "bundler/setup"
7
+ require "topological_inventory/providers/common"
8
+ require "webmock/rspec"
9
+
10
+ Dir["./spec/support/**/*.rb"].each {|f| require f}
11
+
12
+ spec_path = File.dirname(__FILE__)
13
+ Dir[File.join(spec_path, "support/**/*.rb")].each { |f| require f }
14
+
15
+ RSpec.configure do |config|
16
+ # Enable flags like --only-failures and --next-failure
17
+ config.example_status_persistence_file_path = ".rspec_status"
18
+
19
+ config.expect_with :rspec do |c|
20
+ c.syntax = :expect
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ module InventorySpecHelper
2
+ def self.big_inventory(size, chunk_size)
3
+ {
4
+ :collections => [
5
+ :name => SecureRandom.uuid,
6
+ :data => data_chunks(size, chunk_size)
7
+ ]
8
+ }
9
+ end
10
+
11
+ def self.data_chunks(size, chunk)
12
+ Array.new(size / chunk) { "a" * chunk }
13
+ end
14
+ end
@@ -0,0 +1,236 @@
1
+ require "topological_inventory/providers/common/operations/source"
2
+
3
+ RSpec.shared_examples "availability_check" do
4
+ let(:host_url) { 'https://cloud.redhat.com' }
5
+ let(:sources_api_path) { '/api/sources/v3.0' }
6
+ let(:sources_internal_api_path) { '/internal/v1.0' }
7
+ let(:sources_api_url) { "#{host_url}#{sources_api_path}" }
8
+
9
+ let(:external_tenant) { '11001' }
10
+ let(:identity) { {'x-rh-identity' => Base64.strict_encode64({'identity' => {'account_number' => external_tenant, 'user' => {'is_org_admin' => true}}}.to_json)} }
11
+ let(:headers) { {'Content-Type' => 'application/json'}.merge(identity) }
12
+ let(:source_id) { '123' }
13
+ let(:endpoint_id) { '234' }
14
+ let(:application_id) { '345' }
15
+ let(:authentication_id) { '345' }
16
+ let(:payload) do
17
+ {
18
+ 'params' => {
19
+ 'source_id' => source_id,
20
+ 'external_tenant' => external_tenant,
21
+ 'timestamp' => Time.now.utc
22
+ }
23
+ }
24
+ end
25
+
26
+ let(:list_endpoints_response) { "{\"data\":[{\"default\":true,\"host\":\"10.0.0.1\",\"id\":\"#{endpoint_id}\",\"path\":\"/\",\"role\":\"ansible\",\"scheme\":\"https\",\"source_id\":\"#{source_id}\",\"tenant\":\"#{external_tenant}\"}]}" }
27
+ let(:list_endpoint_authentications_response) { "{\"data\":[{\"authtype\":\"username_password\",\"id\":\"#{authentication_id}\",\"resource_id\":\"#{endpoint_id}\",\"resource_type\":\"Endpoint\",\"username\":\"admin\",\"tenant\":\"#{external_tenant}\"}]}" }
28
+ let(:list_endpoint_authentications_response_empty) { "{\"data\":[]}" }
29
+ let(:internal_api_authentication_response) { "{\"authtype\":\"username_password\",\"id\":\"#{authentication_id}\",\"resource_id\":\"#{endpoint_id}\",\"resource_type\":\"Endpoint\",\"username\":\"admin\",\"tenant\":\"#{external_tenant}\",\"password\":\"xxx\"}" }
30
+ let(:list_applications_response) { {:data => [{:id => "345", :availability_status => "available"}]}.to_json }
31
+ let(:list_applications_unavailable_response) { {:data => [{:id => "345", :availability_status => "unavailable"}]}.to_json }
32
+
33
+ subject { described_class.new(payload["params"]) }
34
+
35
+ context "when not checked recently" do
36
+ before do
37
+ allow(subject).to receive(:checked_recently?).and_return(false)
38
+ end
39
+
40
+ it "updates Source and Endpoint when available" do
41
+ # GET
42
+ stub_get(:endpoint, list_endpoints_response)
43
+ stub_get(:authentication, list_endpoint_authentications_response)
44
+ stub_get(:password, internal_api_authentication_response)
45
+ stub_get(:application, "[]")
46
+
47
+ # PATCH
48
+ source_patch_body = {'availability_status' => described_class::STATUS_AVAILABLE, 'last_available_at' => subject.send(:check_time), 'last_checked_at' => subject.send(:check_time)}.to_json
49
+ endpoint_patch_body = {'availability_status' => described_class::STATUS_AVAILABLE, 'availability_status_error' => '', 'last_available_at' => subject.send(:check_time), 'last_checked_at' => subject.send(:check_time)}.to_json
50
+
51
+ stub_patch(:source, source_patch_body)
52
+ stub_patch(:endpoint, endpoint_patch_body)
53
+
54
+ # Check ---
55
+ expect(subject).to receive(:connection_check).and_return([described_class::STATUS_AVAILABLE, nil])
56
+
57
+ subject.availability_check
58
+
59
+ assert_patch(:source, source_patch_body)
60
+ assert_patch(:endpoint, endpoint_patch_body)
61
+ end
62
+
63
+ it "updates Source and Endpoint when unavailable" do
64
+ # GET
65
+ stub_get(:endpoint, list_endpoints_response)
66
+ stub_get(:authentication, list_endpoint_authentications_response)
67
+ stub_get(:password, internal_api_authentication_response)
68
+ stub_get(:application, "[]")
69
+
70
+ # PATCH
71
+ connection_error_message = "Some connection error"
72
+ source_patch_body = {'availability_status' => described_class::STATUS_UNAVAILABLE, 'last_checked_at' => subject.send(:check_time)}.to_json
73
+ endpoint_patch_body = {'availability_status' => described_class::STATUS_UNAVAILABLE, 'availability_status_error' => connection_error_message, 'last_checked_at' => subject.send(:check_time)}.to_json
74
+
75
+ stub_patch(:source, source_patch_body)
76
+ stub_patch(:endpoint, endpoint_patch_body)
77
+
78
+ # Check ---
79
+ expect(subject).to receive(:connection_check).and_return([described_class::STATUS_UNAVAILABLE, connection_error_message])
80
+
81
+ subject.availability_check
82
+
83
+ assert_patch(:source, source_patch_body)
84
+ assert_patch(:endpoint, endpoint_patch_body)
85
+ end
86
+
87
+ it "updates only Source to 'unavailable' status if Endpoint not found" do
88
+ # GET
89
+ stub_get(:endpoint, '')
90
+ stub_get(:application, "[]")
91
+
92
+ # PATCH
93
+ source_patch_body = {'availability_status' => described_class::STATUS_UNAVAILABLE, 'last_checked_at' => subject.send(:check_time)}.to_json
94
+ stub_patch(:source, source_patch_body)
95
+
96
+ # Check
97
+ api_client = subject.send(:api_client)
98
+ expect(api_client).not_to receive(:update_endpoint)
99
+
100
+ subject.availability_check
101
+
102
+ assert_patch(:source, source_patch_body)
103
+ end
104
+
105
+ it "updates Source and Endpoint to 'unavailable' if Authentication not found" do
106
+ # GET
107
+ stub_get(:endpoint, list_endpoints_response)
108
+ stub_get(:authentication, list_endpoint_authentications_response_empty)
109
+ stub_get(:application, "[]")
110
+
111
+ # PATCH
112
+ source_patch_body = {'availability_status' => described_class::STATUS_UNAVAILABLE, 'last_checked_at' => subject.send(:check_time)}.to_json
113
+ endpoint_patch_body = {'availability_status' => described_class::STATUS_UNAVAILABLE, 'availability_status_error' => described_class::ERROR_MESSAGES[:authentication_not_found], 'last_checked_at' => subject.send(:check_time)}.to_json
114
+
115
+ stub_patch(:source, source_patch_body)
116
+ stub_patch(:endpoint, endpoint_patch_body)
117
+
118
+ # Check
119
+ expect(subject).not_to receive(:connection_check)
120
+ subject.availability_check
121
+
122
+ assert_patch(:source, source_patch_body)
123
+ assert_patch(:endpoint, endpoint_patch_body)
124
+ end
125
+ end
126
+
127
+ context "when checked recently" do
128
+ before do
129
+ allow(subject).to receive(:checked_recently?).and_return(true)
130
+ end
131
+
132
+ it "doesn't do connection check" do
133
+ expect(subject).not_to receive(:connection_check)
134
+ expect(WebMock).not_to have_requested(:patch, "#{sources_api_url}/sources/#{source_id}")
135
+ expect(WebMock).not_to have_requested(:patch, "#{sources_api_url}/endpoints/#{endpoint_id}")
136
+
137
+ subject.availability_check
138
+ end
139
+ end
140
+
141
+ context "when there is an application" do
142
+ context "when it is available" do
143
+ it "updates the availability status to available" do
144
+ # GET
145
+ stub_get(:endpoint, "[]")
146
+ stub_get(:application, list_applications_response)
147
+ # PATCH
148
+ application_patch_body = {'last_available_at' => subject.send(:check_time), 'last_checked_at' => subject.send(:check_time)}.to_json
149
+ source_patch_body = {'availability_status' => described_class::STATUS_AVAILABLE, 'last_available_at' => subject.send(:check_time), 'last_checked_at' => subject.send(:check_time)}.to_json
150
+
151
+ stub_patch(:source, source_patch_body)
152
+ stub_patch(:application, application_patch_body)
153
+
154
+ # Check
155
+ expect(subject).not_to receive(:connection_check)
156
+ subject.availability_check
157
+
158
+ assert_patch(:source, source_patch_body)
159
+ assert_patch(:application, application_patch_body)
160
+ end
161
+ end
162
+
163
+ context "when it is unavailable" do
164
+ it "updates the availability status to unavailable" do
165
+ # GET
166
+ stub_get(:endpoint, "[]")
167
+ stub_get(:application, list_applications_unavailable_response)
168
+ # PATCH
169
+ application_patch_body = {'last_checked_at' => subject.send(:check_time)}.to_json
170
+ source_patch_body = {'availability_status' => described_class::STATUS_UNAVAILABLE, 'last_checked_at' => subject.send(:check_time)}.to_json
171
+
172
+ stub_patch(:source, source_patch_body)
173
+ stub_patch(:application, application_patch_body)
174
+
175
+ # Check
176
+ expect(subject).not_to receive(:connection_check)
177
+ subject.availability_check
178
+
179
+ assert_patch(:source, source_patch_body)
180
+ assert_patch(:application, application_patch_body)
181
+ end
182
+ end
183
+ end
184
+
185
+ def stub_get(object_type, response)
186
+ case object_type
187
+ when :endpoint
188
+ stub_request(:get, "#{sources_api_url}/sources/#{source_id}/endpoints")
189
+ .with(:headers => headers)
190
+ .to_return(:status => 200, :body => response, :headers => {})
191
+ when :authentication
192
+ stub_request(:get, "#{sources_api_url}/endpoints/#{endpoint_id}/authentications")
193
+ .with(:headers => headers)
194
+ .to_return(:status => 200, :body => response, :headers => {})
195
+ when :password
196
+ stub_request(:get, "#{host_url}#{sources_internal_api_path}/authentications/#{authentication_id}?expose_encrypted_attribute%5B%5D=password")
197
+ .with(:headers => headers)
198
+ .to_return(:status => 200, :body => response, :headers => {})
199
+ when :application
200
+ stub_request(:get, "#{sources_api_url}/sources/#{source_id}/applications")
201
+ .with(:headers => headers)
202
+ .to_return(:status => 200, :body => response, :headers => {})
203
+ end
204
+ end
205
+
206
+ def stub_patch(object_type, data)
207
+ case object_type
208
+ when :source
209
+ stub_request(:patch, "#{sources_api_url}/sources/#{source_id}")
210
+ .with(:body => data, :headers => headers)
211
+ .to_return(:status => 200, :body => "", :headers => {})
212
+ when :endpoint
213
+ stub_request(:patch, "#{sources_api_url}/endpoints/#{endpoint_id}")
214
+ .with(:body => data, :headers => headers)
215
+ .to_return(:status => 200, :body => "", :headers => {})
216
+ when :application
217
+ stub_request(:patch, "#{sources_api_url}/applications/#{application_id}")
218
+ .with(:body => data, :headers => headers)
219
+ .to_return(:status => 200, :body => "", :headers => {})
220
+ end
221
+ end
222
+
223
+ def assert_patch(object_type, data)
224
+ case object_type
225
+ when :source
226
+ expect(WebMock).to have_requested(:patch, "#{sources_api_url}/sources/#{source_id}")
227
+ .with(:body => data, :headers => headers).once
228
+ when :endpoint
229
+ expect(WebMock).to have_requested(:patch, "#{sources_api_url}/endpoints/#{endpoint_id}")
230
+ .with(:body => data, :headers => headers).once
231
+ when :application
232
+ expect(WebMock).to have_requested(:patch, "#{sources_api_url}/applications/#{application_id}")
233
+ .with(:body => data, :headers => headers).once
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,171 @@
1
+ RSpec.describe TopologicalInventory::Providers::Common::Collector do
2
+ let(:collector) do
3
+ collector = described_class.new(source)
4
+
5
+ allow(collector).to receive(:ingress_api_client).and_return(client)
6
+ allow(collector).to receive(:logger).and_return(logger)
7
+ allow(logger).to receive(:error)
8
+
9
+ collector
10
+ end
11
+
12
+ let(:parser) { TopologicalInventory::Providers::Common::Collector::Parser.new }
13
+
14
+ let(:source) { "source_uid" }
15
+ let(:client) { double }
16
+ let(:logger) { double }
17
+ let(:refresh_state_uuid) { SecureRandom.uuid }
18
+ let(:refresh_state_part_uuid) { SecureRandom.uuid }
19
+ # based on the default, we can tell how many chunks the saver will break the payload up into
20
+ let(:max_size) { TopologicalInventory::Providers::Common::SaveInventory::Saver::KAFKA_PAYLOAD_MAX_BYTES_DEFAULT }
21
+ let(:multiplier) { 0.75 }
22
+
23
+ context "#save_inventory" do
24
+ it "does nothing with empty collections" do
25
+ parts = collector.send(:save_inventory, [], collector.send(:inventory_name), collector.send(:schema_name), refresh_state_uuid, refresh_state_part_uuid)
26
+
27
+ expect(parts).to eq 0
28
+ end
29
+
30
+ it "saves 1 part if it fits" do
31
+ (multiplier * 1000).floor.times { parser.collections.container_groups.build(:source_ref => "a" * 950) }
32
+
33
+ expect(inventory_size(parser.collections.values) / max_size).to eq(0)
34
+
35
+ expect(client).to receive(:save_inventory_with_http_info).exactly(1).times
36
+ parts = collector.send(:save_inventory, parser.collections.values, collector.send(:inventory_name), collector.send(:schema_name), refresh_state_uuid, refresh_state_part_uuid)
37
+ expect(parts).to eq 1
38
+ end
39
+
40
+ it "saves 2 parts if over limit with 1 collection" do
41
+ (multiplier * 2000).floor.times { parser.collections.container_groups.build(:source_ref => "a" * 950) }
42
+
43
+ expect(inventory_size(parser.collections.values) / max_size).to eq(1)
44
+
45
+ expect(client).to receive(:save_inventory_with_http_info).exactly(2).times
46
+ parts = collector.send(:save_inventory, parser.collections.values, collector.send(:inventory_name), collector.send(:schema_name), refresh_state_uuid, refresh_state_part_uuid)
47
+ expect(parts).to eq 2
48
+ end
49
+
50
+ it "saves 2 parts if over limit with 2 collections" do
51
+ (multiplier * 1000).floor.times { parser.collections.container_groups.build(:source_ref => "a" * 950) }
52
+ (multiplier * 1000).floor.times { parser.collections.container_nodes.build(:source_ref => "a" * 950) }
53
+
54
+ expect(inventory_size(parser.collections.values) / max_size).to eq(1)
55
+
56
+ expect(client).to receive(:save_inventory_with_http_info).exactly(2).times
57
+ parts = collector.send(:save_inventory, parser.collections.values, collector.send(:inventory_name), collector.send(:schema_name), refresh_state_uuid, refresh_state_part_uuid)
58
+ expect(parts).to eq 2
59
+ end
60
+
61
+ it "saves many parts" do
62
+ (multiplier * 1500).floor.times { parser.collections.container_groups.build(:source_ref => "a" * 950) }
63
+ (multiplier * 2000).floor.times { parser.collections.container_nodes.build(:source_ref => "a" * 950) }
64
+
65
+ expect(client).to receive(:save_inventory_with_http_info).exactly(4).times
66
+ parts = collector.send(:save_inventory, parser.collections.values, collector.send(:inventory_name), collector.send(:schema_name), refresh_state_uuid, refresh_state_part_uuid)
67
+ expect(parts).to eq 4
68
+ end
69
+
70
+ it 'raises exception when entity to save is too big' do
71
+ parser.collections.container_groups.build(:source_ref => "a" * (1_000_000 * multiplier))
72
+
73
+ expect(inventory_size(parser.collections.values) / max_size).to eq(1)
74
+ # in this case, we first save empty inventory, then the size check fails saving the rest of data
75
+ expect(client).to receive(:save_inventory_with_http_info).exactly(1).times
76
+
77
+ expect { collector.send(:save_inventory, parser.collections.values, collector.send(:inventory_name), collector.send(:schema_name), refresh_state_uuid, refresh_state_part_uuid) }.to(
78
+ raise_error(TopologicalInventory::Providers::Common::SaveInventory::Exception::EntityTooLarge)
79
+ )
80
+ end
81
+
82
+ it 'raises exception when entity of second collection is too big' do
83
+ (multiplier * 1000).floor.times { parser.collections.container_groups.build(:source_ref => "a" * 950) }
84
+ parser.collections.container_nodes.build(:source_ref => "a" * (1_000_000 * multiplier))
85
+
86
+ expect(inventory_size(parser.collections.values) / max_size).to eq(1)
87
+ # We save the first collection then it fails on saving the second collection
88
+ expect(client).to receive(:save_inventory_with_http_info).exactly(1).times
89
+
90
+ expect { collector.send(:save_inventory, parser.collections.values, collector.send(:inventory_name), collector.send(:schema_name), refresh_state_uuid, refresh_state_part_uuid) }.to(
91
+ raise_error(TopologicalInventory::Providers::Common::SaveInventory::Exception::EntityTooLarge)
92
+ )
93
+ end
94
+
95
+ it 'raises exception when entity of second collection is too big then continues with smaller' do
96
+ (multiplier * 1000).floor.times { parser.collections.container_groups.build(:source_ref => "a" * 950) }
97
+ parser.collections.container_nodes.build(:source_ref => "a" * (1_000_000 * multiplier))
98
+ (multiplier * 1000).floor.times { parser.collections.container_nodes.build(:source_ref => "a" * 950) }
99
+
100
+ expect(inventory_size(parser.collections.values) / max_size).to eq(2)
101
+ # We save the first collection then it fails on saving the second collection
102
+ expect(client).to receive(:save_inventory_with_http_info).exactly(1).times
103
+
104
+ expect { collector.send(:save_inventory, parser.collections.values, collector.send(:inventory_name), collector.send(:schema_name), refresh_state_uuid, refresh_state_part_uuid) }.to(
105
+ raise_error(TopologicalInventory::Providers::Common::SaveInventory::Exception::EntityTooLarge)
106
+ )
107
+ end
108
+ end
109
+
110
+ context "#sweep_inventory" do
111
+ it "with nil total parts" do
112
+ expect(client).to receive(:save_inventory_with_http_info).exactly(0).times
113
+
114
+ collector.send(:sweep_inventory, collector.send(:inventory_name), collector.send(:schema_name), refresh_state_uuid, nil, [])
115
+ end
116
+
117
+ it "with empty scope " do
118
+ expect(client).to receive(:save_inventory_with_http_info).exactly(0).times
119
+
120
+ collector.send(:sweep_inventory, collector.send(:inventory_name), collector.send(:schema_name), refresh_state_uuid, 1, [])
121
+ end
122
+
123
+ it "with normal scope " do
124
+ expect(client).to receive(:save_inventory_with_http_info).exactly(1).times
125
+
126
+ collector.send(:sweep_inventory, collector.send(:inventory_name), collector.send(:schema_name), refresh_state_uuid, 1, [:container_groups])
127
+ end
128
+
129
+ it "with normal targeted scope " do
130
+ expect(client).to receive(:save_inventory_with_http_info).exactly(1).times
131
+
132
+ collector.send(:sweep_inventory, collector.send(:inventory_name), collector.send(:schema_name), refresh_state_uuid, 1, {:container_groups => [{:source_ref => "a"}]})
133
+ end
134
+
135
+ it "fails with scope entity too large " do
136
+ expect(client).to receive(:save_inventory_with_http_info).exactly(0).times
137
+
138
+ sweep_scope = {:container_groups => [{:source_ref => "a" * (1_000_002 * multiplier)}]}
139
+
140
+ expect { collector.send(:sweep_inventory, collector.send(:inventory_name), collector.send(:schema_name), refresh_state_uuid, 1, sweep_scope) }.to(
141
+ raise_error(TopologicalInventory::Providers::Common::SaveInventory::Exception::EntityTooLarge)
142
+ )
143
+ end
144
+
145
+ it "fails when scope is too big " do
146
+ # We should have also sweep scope chunking, that is if we'll do big targeted refresh and sweeping
147
+ expect(client).to receive(:save_inventory_with_http_info).exactly(0).times
148
+
149
+ sweep_scope = {:container_groups => (0..1001 * multiplier).map { {:source_ref => "a" * 1_000} } }
150
+
151
+ expect { collector.send(:sweep_inventory, collector.send(:inventory_name), collector.send(:schema_name), refresh_state_uuid, 1, sweep_scope) }.to(
152
+ raise_error(TopologicalInventory::Providers::Common::SaveInventory::Exception::EntityTooLarge)
153
+ )
154
+ end
155
+ end
156
+
157
+ def build_inventory(collections)
158
+ TopologicalInventoryIngressApiClient::Inventory.new(
159
+ :name => collector.send(:inventory_name),
160
+ :schema => TopologicalInventoryIngressApiClient::Schema.new(:name => collector.send(:schema_name)),
161
+ :source => source,
162
+ :collections => collections,
163
+ :refresh_state_uuid => refresh_state_uuid,
164
+ :refresh_state_part_uuid => refresh_state_part_uuid,
165
+ )
166
+ end
167
+
168
+ def inventory_size(collections)
169
+ JSON.generate(build_inventory(collections).to_hash).size
170
+ end
171
+ end