topological_inventory-providers-common 1.0.5 → 1.0.10

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e289728fe42a0bdf12142485a444c851948e9db8ff215c6444f8a7b7bd98c409
4
- data.tar.gz: 54241e05b5d38f449a905b4edecd725b5110eaba96c213098ade88643601de76
3
+ metadata.gz: cd89d895ea401fd0692588fb0f7dc86d6df4a4ec189e73ec4453eafc2be13c7e
4
+ data.tar.gz: 933d448f102d927a62153b75eadd2fb835d1f6fa5a72496ddbaf830a040ff1cc
5
5
  SHA512:
6
- metadata.gz: 14d64a1f0c4b208a43692905a3e843f6c255145cda9cc1a928b8db0a1da02a387cca940146d8044cdcdfbad15bee694e71660d318a2184cf7b1f920f1d6e86fe
7
- data.tar.gz: 5f6d8af6dff4fe080e223a9fb2e0ae81ac8d4c3789f5ef832043ad3b08f52ba2d72590963096a02eb9efc9ce31d6a0e36929a1d89c0ad54a2985aa7871921a74
6
+ metadata.gz: 012ad9a5e0f75a7a9970c31b269fdd7f0ef740ec7626c585ff003ba7cc784ba32995208ebef39de05666e1e442b2104aa44685d2b0d2855c012744e7553c7ea1
7
+ data.tar.gz: 73590bf64ce08ae60a3693f0214a60f31a2f5b82780542180bf7d6a5e8b32d156b2e4fa2e85cea8088b8ef0178737ca365b5e932117b52becf8777cc5916acd1
@@ -4,7 +4,26 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## [1.0.5]
7
+ ## [1.0.10]
8
+ Add HealthCheck class for operations workers #48
9
+ Set the LOG_LEVEL if present #50
10
+
11
+ ## [1.0.9]
12
+ Added refresh-type to save and sweep inventory #45
13
+
14
+ ## [1.0.8] - 2020-08-12
15
+ Add => to error messages that rubocop missed #44
16
+
17
+ ## [1.0.7] - 2020-07-27
18
+ Update operations/source model for receptor-enabled availability checks #36
19
+ Add check for Application subresource under a Source during Availability check #40
20
+ Remove infinite loop in error messages #43
21
+
22
+ ## [1.0.6] - 2020-07-06
23
+ Add some error handling if Sources does not have endpoints/authentications for a source #38
24
+ Specs for Collector #35
25
+
26
+ ## [1.0.5] - 2020-06-18
8
27
  Change release workflow to do everything manually #32
9
28
  Add specs to released files #33
10
29
 
@@ -34,7 +53,12 @@ manageiq-loggers to >= 0.4.2 #20
34
53
  ## [1.0.0] - 2020-03-19
35
54
  ### Initial release to rubygems.org
36
55
 
37
- [Unreleased]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.5...HEAD
56
+ [Unreleased]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.10...HEAD
57
+ [1.0.10]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.9...v1.0.10
58
+ [1.0.9]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.8...v1.0.9
59
+ [1.0.8]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.7...v1.0.8
60
+ [1.0.7]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.6...v1.0.7
61
+ [1.0.6]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.5...v1.0.6
38
62
  [1.0.5]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.4...v1.0.5
39
63
  [1.0.4]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.3...v1.0.4
40
64
  [1.0.3]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.2...v1.0.3
@@ -2,6 +2,7 @@ require "topological_inventory/providers/common/version"
2
2
  require "topological_inventory/providers/common/logging"
3
3
  require "topological_inventory/providers/common/operations/processor"
4
4
  require "topological_inventory/providers/common/operations/endpoint_client"
5
+ require "topological_inventory/providers/common/operations/health_check"
5
6
  require "topological_inventory/providers/common/collectors_pool"
6
7
  require "topological_inventory/providers/common/collector"
7
8
 
@@ -104,7 +104,8 @@ module TopologicalInventory
104
104
  refresh_state_uuid = nil,
105
105
  refresh_state_part_uuid = nil,
106
106
  refresh_state_part_collected_at = nil,
107
- refresh_state_part_sent_at = Time.now.utc)
107
+ refresh_state_part_sent_at = Time.now.utc,
108
+ refresh_type = default_refresh_type)
108
109
  return 0 if collections.empty?
109
110
 
110
111
  SaveInventory::Saver.new(:client => ingress_api_client, :logger => logger).save(
@@ -116,7 +117,8 @@ module TopologicalInventory
116
117
  :refresh_state_uuid => refresh_state_uuid,
117
118
  :refresh_state_part_uuid => refresh_state_part_uuid,
118
119
  :refresh_state_part_collected_at => refresh_state_part_collected_at,
119
- :refresh_state_part_sent_at => refresh_state_part_sent_at
120
+ :refresh_state_part_sent_at => refresh_state_part_sent_at,
121
+ :refresh_type => refresh_type
120
122
  )
121
123
  )
122
124
  rescue => e
@@ -134,7 +136,8 @@ module TopologicalInventory
134
136
  total_parts,
135
137
  sweep_scope,
136
138
  refresh_state_started_at = nil,
137
- refresh_state_sent_at = Time.now.utc)
139
+ refresh_state_sent_at = Time.now.utc,
140
+ refresh_type = default_refresh_type)
138
141
  return if !total_parts || sweep_scope.empty?
139
142
 
140
143
  SaveInventory::Saver.new(:client => ingress_api_client, :logger => logger).save(
@@ -147,7 +150,8 @@ module TopologicalInventory
147
150
  :total_parts => total_parts,
148
151
  :sweep_scope => sweep_scope,
149
152
  :refresh_state_started_at => refresh_state_started_at,
150
- :refresh_state_sent_at => refresh_state_sent_at
153
+ :refresh_state_sent_at => refresh_state_sent_at,
154
+ :refresh_type => refresh_type
151
155
  )
152
156
  )
153
157
  rescue => e
@@ -165,6 +169,10 @@ module TopologicalInventory
165
169
  "Default"
166
170
  end
167
171
 
172
+ def default_refresh_type
173
+ 'full-refresh'
174
+ end
175
+
168
176
  def ingress_api_client
169
177
  TopologicalInventoryIngressApiClient::DefaultApi.new
170
178
  end
@@ -33,7 +33,10 @@ module TopologicalInventory
33
33
 
34
34
  class Logger < ManageIQ::Loggers::CloudWatch
35
35
  def self.new(*args)
36
- super.tap { |logger| logger.extend(TopologicalInventory::Providers::Common::LoggingFunctions) }
36
+ super.tap do |logger|
37
+ logger.extend(TopologicalInventory::Providers::Common::LoggingFunctions)
38
+ logger.level = ENV['LOG_LEVEL'] if ENV['LOG_LEVEL']
39
+ end
37
40
  end
38
41
  end
39
42
 
@@ -0,0 +1,15 @@
1
+ module TopologicalInventory
2
+ module Providers
3
+ module Common
4
+ module Operations
5
+ class HealthCheck
6
+ HEARTBEAT_FILE = '/tmp/healthy'.freeze
7
+
8
+ def self.touch_file
9
+ FileUtils.touch(HEARTBEAT_FILE)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -12,18 +12,20 @@ module TopologicalInventory
12
12
  STATUS_AVAILABLE, STATUS_UNAVAILABLE = %w[available unavailable].freeze
13
13
 
14
14
  ERROR_MESSAGES = {
15
- :authentication_not_found => "Authentication not found in Sources API",
16
- :endpoint_not_found => "Endpoint not found in Sources API",
15
+ :authentication_not_found => "Authentication not found in Sources API",
16
+ :endpoint_or_application_not_found => "Endpoint or Application not found in Sources API",
17
17
  }.freeze
18
18
 
19
19
  LAST_CHECKED_AT_THRESHOLD = 5.minutes.freeze
20
+ AUTH_NOT_NECESSARY = "n/a".freeze
20
21
 
21
- attr_accessor :params, :request_context, :source_id
22
+ attr_accessor :params, :request_context, :source_id, :account_number
22
23
 
23
24
  def initialize(params = {}, request_context = nil)
24
25
  self.params = params
25
26
  self.request_context = request_context
26
27
  self.source_id = params['source_id']
28
+ self.account_number = params['external_tenant']
27
29
  end
28
30
 
29
31
  def availability_check
@@ -33,7 +35,7 @@ module TopologicalInventory
33
35
 
34
36
  status, error_message = connection_status
35
37
 
36
- update_source_and_endpoint(status, error_message)
38
+ update_source_and_subresources(status, error_message)
37
39
 
38
40
  logger.availability_check("Completed: Source #{source_id} is #{status}")
39
41
  end
@@ -57,32 +59,57 @@ module TopologicalInventory
57
59
  end
58
60
 
59
61
  def checked_recently?
60
- return false if endpoint.nil?
62
+ checked_recently = if endpoint.present?
63
+ endpoint.last_checked_at.present? && endpoint.last_checked_at >= LAST_CHECKED_AT_THRESHOLD.ago
64
+ elsif application.present?
65
+ application.last_checked_at.present? && application.last_checked_at >= LAST_CHECKED_AT_THRESHOLD.ago
66
+ end
61
67
 
62
- checked_recently = endpoint.last_checked_at.present? && endpoint.last_checked_at >= LAST_CHECKED_AT_THRESHOLD.ago
63
- logger.availability_check("Skipping, last check at #{endpoint.last_checked_at} [Source ID: #{source_id}] ") if checked_recently
68
+ logger.availability_check("Skipping, last check at #{endpoint.last_checked_at || application.last_checked_at} [Source ID: #{source_id}] ") if checked_recently
64
69
 
65
70
  checked_recently
66
71
  end
67
72
 
68
73
  def connection_status
69
- return [STATUS_UNAVAILABLE, ERROR_MESSAGES[:endpoint_not_found]] unless endpoint
70
- return [STATUS_UNAVAILABLE, ERROR_MESSAGES[:authentication_not_found]] unless authentication
74
+ # we need either an endpoint or application to check the source.
75
+ return [STATUS_UNAVAILABLE, ERROR_MESSAGES[:endpoint_or_application_not_found]] unless endpoint || application
71
76
 
72
77
  check_time
78
+ if endpoint
79
+ endpoint_connection_check
80
+ elsif application
81
+ application_connection_check
82
+ end
83
+ end
84
+
85
+ def endpoint_connection_check
86
+ return [STATUS_UNAVAILABLE, ERROR_MESSAGES[:authentication_not_found]] unless authentication
87
+
88
+ # call down into the operations pod implementation of `Source#connection_check`
73
89
  connection_check
74
90
  end
75
91
 
92
+ def application_connection_check
93
+ case application.availability_status
94
+ when "available"
95
+ [STATUS_AVAILABLE, nil]
96
+ when "unavailable"
97
+ [STATUS_UNAVAILABLE, "Application id #{application.id} unavailable"]
98
+ end
99
+ end
100
+
76
101
  # @return [Array<String, String|nil] - STATUS_[UN]AVAILABLE, error message
77
102
  def connection_check
78
103
  raise NotImplementedError, "#{__method__} must be implemented in a subclass"
79
104
  end
80
105
 
81
- def update_source_and_endpoint(status, error_message = nil)
106
+ def update_source_and_subresources(status, error_message = nil)
82
107
  logger.availability_check("Updating source [#{source_id}] status [#{status}] message [#{error_message}]")
83
108
 
84
109
  update_source(status)
85
- update_endpoint(status, error_message)
110
+
111
+ update_endpoint(status, error_message) if endpoint
112
+ update_application(status) if application
86
113
  end
87
114
 
88
115
  def update_source(status)
@@ -114,12 +141,39 @@ module TopologicalInventory
114
141
  logger.availability_check("Failed to update Endpoint(ID: #{endpoint.id}) - #{e.message}", :error)
115
142
  end
116
143
 
144
+ def update_application(status)
145
+ application_update = ::SourcesApiClient::Application.new
146
+ application_update.last_checked_at = check_time
147
+ application_update.last_available_at = check_time if status == STATUS_AVAILABLE
148
+
149
+ api_client.update_application(application.id, application_update)
150
+ rescue ::SourcesApiClient::ApiError => e
151
+ logger.availability_check("Failed to update Application id: #{application.id} - #{e.message}", :error)
152
+ end
153
+
117
154
  def endpoint
118
155
  @endpoint ||= api_client.fetch_default_endpoint(source_id)
156
+ rescue => e
157
+ logger.availability_check("Failed to fetch Endpoint for Source #{source_id}: #{e.message}", :error)
158
+ nil
119
159
  end
120
160
 
121
161
  def authentication
122
- @authentication ||= api_client.fetch_authentication(source_id, endpoint)
162
+ @authentication ||= if endpoint.receptor_node.present?
163
+ AUTH_NOT_NECESSARY
164
+ else
165
+ api_client.fetch_authentication(source_id, endpoint)
166
+ end
167
+ rescue => e
168
+ logger.availability_check("Failed to fetch Authentication for Source #{source_id}: #{e.message}", :error)
169
+ nil
170
+ end
171
+
172
+ def application
173
+ @application ||= api_client.fetch_application(source_id)
174
+ rescue => e
175
+ logger.availability_check("Failed to fetch Application for Source #{source_id}: #{e.message}", :error)
176
+ nil
123
177
  end
124
178
 
125
179
  def check_time
@@ -127,7 +181,7 @@ module TopologicalInventory
127
181
  end
128
182
 
129
183
  def identity
130
- @identity ||= {"x-rh-identity" => Base64.strict_encode64({"identity" => {"account_number" => params["external_tenant"], "user" => {"is_org_admin" => true}}}.to_json)}
184
+ @identity ||= {"x-rh-identity" => Base64.strict_encode64({"identity" => {"account_number" => account_number, "user" => {"is_org_admin" => true}}}.to_json)}
131
185
  end
132
186
 
133
187
  def api_client
@@ -5,7 +5,7 @@ module TopologicalInventory
5
5
  module Common
6
6
  module Operations
7
7
  class SourcesApiClient < ::SourcesApiClient::ApiClient
8
- delegate :update_source, :update_endpoint, :to => :api
8
+ delegate :update_source, :update_endpoint, :update_application, :to => :api
9
9
 
10
10
  INTERNAL_API_PATH = '//internal/v1.0'.freeze
11
11
 
@@ -25,6 +25,11 @@ module TopologicalInventory
25
25
  endpoints.find(&:default)
26
26
  end
27
27
 
28
+ def fetch_application(source_id)
29
+ applications = api.list_source_applications(source_id)&.data || []
30
+ applications.first
31
+ end
32
+
28
33
  def fetch_authentication(source_id, default_endpoint = nil, authtype = nil)
29
34
  endpoint = default_endpoint || fetch_default_endpoint(source_id)
30
35
  return if endpoint.nil?
@@ -1,7 +1,7 @@
1
1
  module TopologicalInventory
2
2
  module Providers
3
3
  module Common
4
- VERSION = "1.0.5"
4
+ VERSION = "1.0.10"
5
5
  end
6
6
  end
7
7
  end
@@ -11,6 +11,7 @@ RSpec.shared_examples "availability_check" do
11
11
  let(:headers) { {'Content-Type' => 'application/json'}.merge(identity) }
12
12
  let(:source_id) { '123' }
13
13
  let(:endpoint_id) { '234' }
14
+ let(:application_id) { '345' }
14
15
  let(:authentication_id) { '345' }
15
16
  let(:payload) do
16
17
  {
@@ -26,6 +27,8 @@ RSpec.shared_examples "availability_check" do
26
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}\"}]}" }
27
28
  let(:list_endpoint_authentications_response_empty) { "{\"data\":[]}" }
28
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 }
29
32
 
30
33
  subject { described_class.new(payload["params"]) }
31
34
 
@@ -39,6 +42,7 @@ RSpec.shared_examples "availability_check" do
39
42
  stub_get(:endpoint, list_endpoints_response)
40
43
  stub_get(:authentication, list_endpoint_authentications_response)
41
44
  stub_get(:password, internal_api_authentication_response)
45
+ stub_not_found(:application)
42
46
 
43
47
  # PATCH
44
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
@@ -61,6 +65,7 @@ RSpec.shared_examples "availability_check" do
61
65
  stub_get(:endpoint, list_endpoints_response)
62
66
  stub_get(:authentication, list_endpoint_authentications_response)
63
67
  stub_get(:password, internal_api_authentication_response)
68
+ stub_not_found(:application)
64
69
 
65
70
  # PATCH
66
71
  connection_error_message = "Some connection error"
@@ -81,7 +86,8 @@ RSpec.shared_examples "availability_check" do
81
86
 
82
87
  it "updates only Source to 'unavailable' status if Endpoint not found" do
83
88
  # GET
84
- stub_get(:endpoint, '')
89
+ stub_not_found(:endpoint)
90
+ stub_not_found(:application)
85
91
 
86
92
  # PATCH
87
93
  source_patch_body = {'availability_status' => described_class::STATUS_UNAVAILABLE, 'last_checked_at' => subject.send(:check_time)}.to_json
@@ -100,6 +106,7 @@ RSpec.shared_examples "availability_check" do
100
106
  # GET
101
107
  stub_get(:endpoint, list_endpoints_response)
102
108
  stub_get(:authentication, list_endpoint_authentications_response_empty)
109
+ stub_not_found(:application)
103
110
 
104
111
  # PATCH
105
112
  source_patch_body = {'availability_status' => described_class::STATUS_UNAVAILABLE, 'last_checked_at' => subject.send(:check_time)}.to_json
@@ -131,6 +138,49 @@ RSpec.shared_examples "availability_check" do
131
138
  end
132
139
  end
133
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_not_found(: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_not_found(: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
134
184
 
135
185
  def stub_get(object_type, response)
136
186
  case object_type
@@ -146,6 +196,31 @@ RSpec.shared_examples "availability_check" do
146
196
  stub_request(:get, "#{host_url}#{sources_internal_api_path}/authentications/#{authentication_id}?expose_encrypted_attribute%5B%5D=password")
147
197
  .with(:headers => headers)
148
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_not_found(object_type)
207
+ case object_type
208
+ when :endpoint
209
+ stub_request(:get, "#{sources_api_url}/sources/#{source_id}/endpoints")
210
+ .with(:headers => headers)
211
+ .to_return(:status => 404, :body => {}.to_json, :headers => {})
212
+ when :authentication
213
+ stub_request(:get, "#{sources_api_url}/endpoints/#{endpoint_id}/authentications")
214
+ .with(:headers => headers)
215
+ .to_return(:status => 404, :body => {}.to_json, :headers => {})
216
+ when :password
217
+ stub_request(:get, "#{host_url}#{sources_internal_api_path}/authentications/#{authentication_id}?expose_encrypted_attribute%5B%5D=password")
218
+ .with(:headers => headers)
219
+ .to_return(:status => 404, :body => {}.to_json, :headers => {})
220
+ when :application
221
+ stub_request(:get, "#{sources_api_url}/sources/#{source_id}/applications")
222
+ .with(:headers => headers)
223
+ .to_return(:status => 404, :body => {}.to_json, :headers => {})
149
224
  end
150
225
  end
151
226
 
@@ -159,6 +234,10 @@ RSpec.shared_examples "availability_check" do
159
234
  stub_request(:patch, "#{sources_api_url}/endpoints/#{endpoint_id}")
160
235
  .with(:body => data, :headers => headers)
161
236
  .to_return(:status => 200, :body => "", :headers => {})
237
+ when :application
238
+ stub_request(:patch, "#{sources_api_url}/applications/#{application_id}")
239
+ .with(:body => data, :headers => headers)
240
+ .to_return(:status => 200, :body => "", :headers => {})
162
241
  end
163
242
  end
164
243
 
@@ -166,10 +245,13 @@ RSpec.shared_examples "availability_check" do
166
245
  case object_type
167
246
  when :source
168
247
  expect(WebMock).to have_requested(:patch, "#{sources_api_url}/sources/#{source_id}")
169
- .with(:body => data, :headers => headers).once
248
+ .with(:body => data, :headers => headers).once
170
249
  when :endpoint
171
250
  expect(WebMock).to have_requested(:patch, "#{sources_api_url}/endpoints/#{endpoint_id}")
172
- .with(:body => data, :headers => headers).once
251
+ .with(:body => data, :headers => headers).once
252
+ when :application
253
+ expect(WebMock).to have_requested(:patch, "#{sources_api_url}/applications/#{application_id}")
254
+ .with(:body => data, :headers => headers).once
173
255
  end
174
256
  end
175
257
  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
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.add_runtime_dependency "manageiq-loggers", ">= 0.4.2"
30
30
  spec.add_runtime_dependency "sources-api-client", "~> 3.0"
31
31
  spec.add_runtime_dependency "topological_inventory-api-client", "~> 3.0", ">= 3.0.1"
32
- spec.add_runtime_dependency "topological_inventory-ingress_api-client", "~> 1.0"
32
+ spec.add_runtime_dependency "topological_inventory-ingress_api-client", "~> 1.0", ">= 1.0.3"
33
33
 
34
34
  spec.add_development_dependency "bundler", "~> 2.0"
35
35
  spec.add_development_dependency "rake", ">= 12.3.3"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: topological_inventory-providers-common
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.5
4
+ version: 1.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Slemr
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-06-18 00:00:00.000000000 Z
11
+ date: 2020-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -113,6 +113,9 @@ dependencies:
113
113
  - - "~>"
114
114
  - !ruby/object:Gem::Version
115
115
  version: '1.0'
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: 1.0.3
116
119
  type: :runtime
117
120
  prerelease: false
118
121
  version_requirements: !ruby/object:Gem::Requirement
@@ -120,6 +123,9 @@ dependencies:
120
123
  - - "~>"
121
124
  - !ruby/object:Gem::Version
122
125
  version: '1.0'
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: 1.0.3
123
129
  - !ruby/object:Gem::Dependency
124
130
  name: bundler
125
131
  requirement: !ruby/object:Gem::Requirement
@@ -247,6 +253,7 @@ files:
247
253
  - lib/topological_inventory/providers/common/collectors_pool.rb
248
254
  - lib/topological_inventory/providers/common/logging.rb
249
255
  - lib/topological_inventory/providers/common/operations/endpoint_client.rb
256
+ - lib/topological_inventory/providers/common/operations/health_check.rb
250
257
  - lib/topological_inventory/providers/common/operations/processor.rb
251
258
  - lib/topological_inventory/providers/common/operations/source.rb
252
259
  - lib/topological_inventory/providers/common/operations/sources_api_client.rb
@@ -257,6 +264,7 @@ files:
257
264
  - spec/spec_helper.rb
258
265
  - spec/support/inventory_helper.rb
259
266
  - spec/support/shared/availability_check.rb
267
+ - spec/topological_inventory/providers/common/collector_spec.rb
260
268
  - spec/topological_inventory/providers/common/collectors/inventory_collection_storage_spec.rb
261
269
  - spec/topological_inventory/providers/common/collectors/inventory_collection_wrapper_spec.rb
262
270
  - spec/topological_inventory/providers/common/collectors_pool_spec.rb