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