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 +4 -4
- data/CHANGELOG.md +26 -2
- data/lib/topological_inventory/providers/common.rb +1 -0
- data/lib/topological_inventory/providers/common/collector.rb +12 -4
- data/lib/topological_inventory/providers/common/logging.rb +4 -1
- data/lib/topological_inventory/providers/common/operations/health_check.rb +15 -0
- data/lib/topological_inventory/providers/common/operations/source.rb +67 -13
- data/lib/topological_inventory/providers/common/operations/sources_api_client.rb +6 -1
- data/lib/topological_inventory/providers/common/version.rb +1 -1
- data/spec/support/shared/availability_check.rb +85 -3
- data/spec/topological_inventory/providers/common/collector_spec.rb +171 -0
- data/topological_inventory-providers-common.gemspec +1 -1
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd89d895ea401fd0692588fb0f7dc86d6df4a4ec189e73ec4453eafc2be13c7e
|
4
|
+
data.tar.gz: 933d448f102d927a62153b75eadd2fb835d1f6fa5a72496ddbaf830a040ff1cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 012ad9a5e0f75a7a9970c31b269fdd7f0ef740ec7626c585ff003ba7cc784ba32995208ebef39de05666e1e442b2104aa44685d2b0d2855c012744e7553c7ea1
|
7
|
+
data.tar.gz: 73590bf64ce08ae60a3693f0214a60f31a2f5b82780542180bf7d6a5e8b32d156b2e4fa2e85cea8088b8ef0178737ca365b5e932117b52becf8777cc5916acd1
|
data/CHANGELOG.md
CHANGED
@@ -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.
|
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.
|
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
|
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
|
|
@@ -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
|
16
|
-
:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
70
|
-
return [STATUS_UNAVAILABLE, ERROR_MESSAGES[:
|
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
|
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
|
-
|
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 ||=
|
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" =>
|
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?
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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-
|
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
|