topological_inventory-providers-common 1.0.5 → 1.0.10

Sign up to get free protection for your applications and to get access to all the features.
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