topological_inventory-providers-common 1.0.10 → 2.1.1

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.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +31 -0
  3. data/.rubocop.yml +1 -1
  4. data/.yamllint +8 -0
  5. data/CHANGELOG.md +25 -3
  6. data/lib/topological_inventory/providers/common.rb +1 -2
  7. data/lib/topological_inventory/providers/common/collectors_pool.rb +2 -1
  8. data/lib/topological_inventory/providers/common/logging.rb +6 -3
  9. data/lib/topological_inventory/providers/common/messaging_client.rb +40 -0
  10. data/lib/topological_inventory/providers/common/metrics.rb +84 -0
  11. data/lib/topological_inventory/providers/common/mixins/sources_api.rb +61 -0
  12. data/lib/topological_inventory/providers/common/mixins/statuses.rb +19 -0
  13. data/lib/topological_inventory/providers/common/mixins/topology_api.rb +26 -0
  14. data/lib/topological_inventory/providers/common/mixins/x_rh_headers.rb +24 -0
  15. data/lib/topological_inventory/providers/common/operations/async_worker.rb +56 -0
  16. data/lib/topological_inventory/providers/common/operations/processor.rb +46 -104
  17. data/lib/topological_inventory/providers/common/operations/source.rb +183 -144
  18. data/lib/topological_inventory/providers/common/sources_api_client.rb +92 -0
  19. data/lib/topological_inventory/providers/common/topology_api_client.rb +43 -0
  20. data/lib/topological_inventory/providers/common/version.rb +1 -1
  21. data/spec/support/shared/availability_check.rb +254 -90
  22. data/spec/topological_inventory/providers/common/operations/async_worker_spec.rb +36 -0
  23. data/spec/topological_inventory/providers/common/operations/processor_spec.rb +52 -83
  24. data/topological_inventory-providers-common.gemspec +14 -10
  25. metadata +74 -9
  26. data/lib/topological_inventory/providers/common/operations/endpoint_client.rb +0 -65
  27. data/lib/topological_inventory/providers/common/operations/sources_api_client.rb +0 -94
  28. data/lib/topological_inventory/providers/common/operations/topology_api_client.rb +0 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd89d895ea401fd0692588fb0f7dc86d6df4a4ec189e73ec4453eafc2be13c7e
4
- data.tar.gz: 933d448f102d927a62153b75eadd2fb835d1f6fa5a72496ddbaf830a040ff1cc
3
+ metadata.gz: faf7ab98d098c6dfed7cac60aa40dca8e84a26721eac35ed8a487c84c57d4375
4
+ data.tar.gz: 122a119549a916b7d908c7655a94ed855af4ddb49220e05cedd11e89972abcd3
5
5
  SHA512:
6
- metadata.gz: 012ad9a5e0f75a7a9970c31b269fdd7f0ef740ec7626c585ff003ba7cc784ba32995208ebef39de05666e1e442b2104aa44685d2b0d2855c012744e7553c7ea1
7
- data.tar.gz: 73590bf64ce08ae60a3693f0214a60f31a2f5b82780542180bf7d6a5e8b32d156b2e4fa2e85cea8088b8ef0178737ca365b5e932117b52becf8777cc5916acd1
6
+ metadata.gz: 90d69186763762d8d62c41282f27b926b6aee15fbd2b916d2f911eab519f586650e97e804ea07e3c2c1121b953a305f949912648e05ffe016ef27cd4273b0747
7
+ data.tar.gz: f8864ca924b2e5b494d3e0fe2403bcd0f1c03450fe9ce755fb6bf80c832e8a1dafa0c788a9998ae50c2e63eb414852bdeadd593ca2ebfd8389ae1858e6517129
@@ -0,0 +1,31 @@
1
+ ---
2
+ version: '2'
3
+ prepare:
4
+ fetch:
5
+ - url: https://raw.githubusercontent.com/RedHatInsights/insights-api-common-rails/master/.rubocop_base.yml
6
+ path: ".rubocop_base.yml"
7
+ - url: https://raw.githubusercontent.com/RedHatInsights/insights-api-common-rails/master/.rubocop_cc_base.yml
8
+ path: ".rubocop_cc_base.yml"
9
+ checks:
10
+ argument-count:
11
+ enabled: false
12
+ complex-logic:
13
+ enabled: false
14
+ file-lines:
15
+ enabled: false
16
+ method-complexity:
17
+ config:
18
+ threshold: 11
19
+ method-count:
20
+ enabled: false
21
+ method-lines:
22
+ enabled: false
23
+ nested-control-flow:
24
+ enabled: false
25
+ return-statements:
26
+ enabled: false
27
+ plugins:
28
+ rubocop:
29
+ enabled: true
30
+ config: ".rubocop_cc.yml"
31
+ channel: rubocop-1-0
@@ -1,3 +1,3 @@
1
1
  inherit_from:
2
- - https://raw.githubusercontent.com/ManageIQ/guides/master/.rubocop_base.yml
2
+ - https://raw.githubusercontent.com/RedHatInsights/insights-api-common-rails/master/.rubocop_base.yml
3
3
  - .rubocop_local.yml
@@ -0,0 +1,8 @@
1
+ ---
2
+ ignore: |
3
+
4
+ extends: relaxed
5
+
6
+ rules:
7
+ line-length:
8
+ max: 120
@@ -4,11 +4,29 @@ 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.10]
7
+ ## [2.1.1] - 2020-11-04
8
+ MessagingClient and Source fix #61
9
+ Fix availability_check in app check #62
10
+
11
+ ## [2.1.0] - 2020-11-02
12
+ Add Availability checks for sources via Kafka #54
13
+ Common Metrics exporter #57
14
+ Rubocop rules from insights-api-common + yamllint #59
15
+
16
+ ## [2.0.0] - 2020-10-20
17
+ Operations/API clients refactoring
18
+
19
+ ## [1.0.12] - 2020-10-05
20
+ Add Operations Async Worker class #55
21
+
22
+ ## [1.0.11] - 2020-09-04
23
+ Make Collector Poll Time a parameter so we can tweak the collection interval #51
24
+
25
+ ## [1.0.10] - 2020-08-26
8
26
  Add HealthCheck class for operations workers #48
9
27
  Set the LOG_LEVEL if present #50
10
28
 
11
- ## [1.0.9]
29
+ ## [1.0.9] - 2020-08-17
12
30
  Added refresh-type to save and sweep inventory #45
13
31
 
14
32
  ## [1.0.8] - 2020-08-12
@@ -53,7 +71,11 @@ manageiq-loggers to >= 0.4.2 #20
53
71
  ## [1.0.0] - 2020-03-19
54
72
  ### Initial release to rubygems.org
55
73
 
56
- [Unreleased]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.10...HEAD
74
+ [Unreleased]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v2.1.0...HEAD
75
+ [2.1.0]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v2.0.0...v2.1.0
76
+ [2.0.0]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.12...v2.0.0
77
+ [1.0.12]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.11...v1.0.12
78
+ [1.0.11]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.10...v1.0.11
57
79
  [1.0.10]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.9...v1.0.10
58
80
  [1.0.9]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.8...v1.0.9
59
81
  [1.0.8]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.7...v1.0.8
@@ -1,10 +1,9 @@
1
1
  require "topological_inventory/providers/common/version"
2
2
  require "topological_inventory/providers/common/logging"
3
- require "topological_inventory/providers/common/operations/processor"
4
- require "topological_inventory/providers/common/operations/endpoint_client"
5
3
  require "topological_inventory/providers/common/operations/health_check"
6
4
  require "topological_inventory/providers/common/collectors_pool"
7
5
  require "topological_inventory/providers/common/collector"
6
+ require "topological_inventory/providers/common/metrics"
8
7
 
9
8
  module TopologicalInventory
10
9
  module Providers
@@ -5,8 +5,9 @@ module TopologicalInventory
5
5
  module Common
6
6
  class CollectorsPool
7
7
  SECRET_FILENAME = "credentials".freeze
8
+ COLLECTOR_POLL_TIME = ENV['COLLECTOR_POLL_TIME']&.to_i || 300
8
9
 
9
- def initialize(config_name, metrics, collector_poll_time: 60, thread_pool_size: 2)
10
+ def initialize(config_name, metrics, collector_poll_time: COLLECTOR_POLL_TIME, thread_pool_size: 2)
10
11
  self.config_name = config_name
11
12
  self.collector_status = Concurrent::Map.new
12
13
  self.metrics = metrics
@@ -23,11 +23,14 @@ module TopologicalInventory
23
23
  end
24
24
 
25
25
  def availability_check(message, severity = :info)
26
- log_with_prefix("Source#availability_check", message, severity)
26
+ send("#{severity}_ext", "Source#availability_check", message)
27
27
  end
28
28
 
29
- def log_with_prefix(prefix, message, severity)
30
- send(severity, "#{prefix} - #{message}") if respond_to?(severity)
29
+ %w[debug info warn error fatal].each do |severity|
30
+ define_method("#{severity}_ext".to_sym) do |prefix, message|
31
+ ext_message = [prefix, message].compact.join(' - ')
32
+ send(severity, ext_message)
33
+ end
31
34
  end
32
35
  end
33
36
 
@@ -0,0 +1,40 @@
1
+ require "more_core_extensions/core_ext/module/cache_with_timeout"
2
+ require "manageiq-messaging"
3
+
4
+ module TopologicalInventory
5
+ module Providers
6
+ module Common
7
+ class MessagingClient
8
+ # Kafka host name
9
+ attr_accessor :queue_host
10
+ # Kafka port
11
+ attr_accessor :queue_port
12
+
13
+ def initialize
14
+ self.queue_host = ENV['QUEUE_HOST'] || 'localhost'
15
+ self.queue_port = (ENV['QUEUE_PORT'] || 9092).to_i
16
+ end
17
+
18
+ def self.default
19
+ @@default ||= new
20
+ end
21
+
22
+ def self.configure
23
+ if block_given?
24
+ yield(default)
25
+ else
26
+ default
27
+ end
28
+ end
29
+
30
+ cache_with_timeout(:client) do
31
+ ManageIQ::Messaging::Client.open(:protocol => :Kafka, :host => default.queue_host, :port => default.queue_port)
32
+ end
33
+
34
+ def client
35
+ self.class.client
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,84 @@
1
+ require "benchmark"
2
+ require "prometheus_exporter"
3
+ require "prometheus_exporter/server"
4
+ require "prometheus_exporter/client"
5
+ require "prometheus_exporter/instrumentation"
6
+
7
+ module TopologicalInventory
8
+ module Providers
9
+ module Common
10
+ class Metrics
11
+ ERROR_COUNTER_MESSAGE = "total number of errors".freeze
12
+
13
+ def initialize(port = 9394)
14
+ return if port == 0
15
+
16
+ configure_server(port)
17
+ configure_metrics
18
+ end
19
+
20
+ def stop_server
21
+ @server&.stop
22
+ end
23
+
24
+ def record_error(type = :general)
25
+ @error_counter&.observe(1, :type => type.to_s)
26
+ end
27
+
28
+ def record_operation(name, labels = {})
29
+ @status_counter&.observe(1, (labels || {}).merge(:name => name))
30
+ end
31
+
32
+ def record_operation_time(name, labels = {}, &block)
33
+ record_time(@duration_seconds, (labels || {}).merge(:name => name), &block)
34
+ end
35
+
36
+ # Common method for gauge
37
+ def record_gauge(metric, opt, value: nil, labels: {})
38
+ case opt
39
+ when :set then
40
+ metric&.observe(value.to_i, labels)
41
+ when :add then
42
+ metric&.increment(labels)
43
+ when :remove then
44
+ metric&.decrement(labels)
45
+ end
46
+ end
47
+
48
+ # Common method for histogram
49
+ def record_time(metric, labels = {})
50
+ result = nil
51
+ time = Benchmark.realtime { result = yield }
52
+ metric&.observe(time, labels)
53
+ result
54
+ end
55
+
56
+ private
57
+
58
+ def configure_server(port)
59
+ @server = PrometheusExporter::Server::WebServer.new(:port => port)
60
+ @server.start
61
+
62
+ PrometheusExporter::Client.default = PrometheusExporter::LocalClient.new(:collector => @server.collector)
63
+ end
64
+
65
+ def configure_metrics
66
+ PrometheusExporter::Instrumentation::Process.start
67
+ PrometheusExporter::Metric::Base.default_prefix = default_prefix
68
+
69
+ @duration_seconds = PrometheusExporter::Metric::Histogram.new('duration_seconds', 'Duration of processed operation')
70
+ @error_counter = PrometheusExporter::Metric::Counter.new("error", ERROR_COUNTER_MESSAGE)
71
+ @status_counter = PrometheusExporter::Metric::Counter.new('status_counter', 'number of processed operations')
72
+
73
+ [@duration_seconds, @error_counter, @status_counter].each do |metric|
74
+ @server.collector.register_metric(metric)
75
+ end
76
+ end
77
+
78
+ def default_prefix
79
+ raise NotImplementedError, "#{__method__} must be implemented in a subclass"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,61 @@
1
+ require "topological_inventory/providers/common/sources_api_client"
2
+
3
+ module TopologicalInventory
4
+ module Providers
5
+ module Common
6
+ module Mixins
7
+ module SourcesApi
8
+ AUTH_NOT_NECESSARY = "n/a".freeze
9
+
10
+ def sources_api
11
+ @sources_api ||= TopologicalInventory::Providers::Common::SourcesApiClient.new(identity)
12
+ end
13
+
14
+ def endpoint
15
+ @endpoint ||= sources_api.fetch_default_endpoint(source_id)
16
+ rescue => e
17
+ metrics&.record_error(:sources_api)
18
+ logger.error_ext(operation, "Failed to fetch Endpoint for Source #{source_id}: #{e.message}")
19
+ nil
20
+ end
21
+
22
+ def authentication
23
+ @authentication ||= if endpoint.receptor_node.present?
24
+ AUTH_NOT_NECESSARY
25
+ else
26
+ sources_api.fetch_authentication(source_id, endpoint)
27
+ end
28
+ rescue => e
29
+ metrics&.record_error(:sources_api)
30
+ logger.error_ext(operation, "Failed to fetch Authentication for Source #{source_id}: #{e.message}")
31
+ nil
32
+ end
33
+
34
+ def application
35
+ @application ||= sources_api.fetch_application(source_id)
36
+ rescue => e
37
+ metrics&.record_error(:sources_api)
38
+ logger.error_ext(operation, "Failed to fetch Application for Source #{source_id}: #{e.message}")
39
+ nil
40
+ end
41
+
42
+ def on_premise?
43
+ @on_premise ||= endpoint&.receptor_node.to_s.strip.present?
44
+ end
45
+
46
+ def verify_ssl_mode
47
+ endpoint&.verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
48
+ end
49
+
50
+ def full_hostname(endpoint)
51
+ if on_premise?
52
+ "receptor://#{endpoint.receptor_node}"
53
+ else
54
+ endpoint.host.tap { |host| host << ":#{endpoint.port}" if endpoint.port }
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,19 @@
1
+ module TopologicalInventory
2
+ module Providers
3
+ module Common
4
+ module Mixins
5
+ module Statuses
6
+ def operation_status
7
+ return @statuses if @statuses.present?
8
+
9
+ @statuses = {}
10
+ %i[success error skipped not_implemented].each do |status|
11
+ @statuses[status] = status.to_s
12
+ end
13
+ @statuses
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ require "topological_inventory/providers/common/topology_api_client"
2
+
3
+ module TopologicalInventory
4
+ module Providers
5
+ module Common
6
+ module Mixins
7
+ module TopologyApi
8
+ # @identity attr_reader is expected
9
+ def topology_api
10
+ @topology_api ||= TopologicalInventory::Providers::Common::TopologyApiClient.new(identity)
11
+ end
12
+
13
+ def update_task(task_id, source_id: nil, state:, status:, target_type: nil, target_source_ref: nil, context: nil)
14
+ topology_api.update_task(task_id,
15
+ :source_id => source_id,
16
+ :state => state,
17
+ :status => status,
18
+ :target_type => target_type,
19
+ :target_source_ref => target_source_ref,
20
+ :context => context)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ module TopologicalInventory
2
+ module Providers
3
+ module Common
4
+ module Mixins
5
+ module XRhHeaders
6
+ def account_number_by_identity(identity)
7
+ return @account_number if @account_number
8
+ return if identity.try(:[], 'x-rh-identity').nil?
9
+
10
+ identity_hash = JSON.parse(Base64.decode64(identity['x-rh-identity']))
11
+ @account_number = identity_hash.dig('identity', 'account_number')
12
+ rescue JSON::ParserError => e
13
+ logger.error_ext(operation, "Failed to parse identity header: #{e.message}")
14
+ nil
15
+ end
16
+
17
+ def identity_by_account_number(account_number)
18
+ @identity ||= {"x-rh-identity" => Base64.strict_encode64({"identity" => {"account_number" => account_number, "user" => {"is_org_admin" => true}}}.to_json)}
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,56 @@
1
+ require "topological_inventory/providers/common/logging"
2
+ require "topological_inventory/providers/common/operations/health_check"
3
+
4
+ module TopologicalInventory
5
+ module Providers
6
+ module Common
7
+ module Operations
8
+ class AsyncWorker
9
+ include Logging
10
+
11
+ def initialize(processor, queue = nil)
12
+ @processor = processor
13
+ @queue = queue || Queue.new
14
+ end
15
+
16
+ def start
17
+ return if thread.present?
18
+
19
+ @thread = Thread.new { listen }
20
+ end
21
+
22
+ def stop
23
+ thread&.exit
24
+ end
25
+
26
+ def enqueue(msg)
27
+ queue << msg
28
+ end
29
+
30
+ def listen
31
+ loop do
32
+ # the queue thread waits for a message to come during `Queue#pop`
33
+ msg = queue.pop
34
+ process_message(msg)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :thread, :queue, :processor
41
+
42
+ def process_message(msg)
43
+ processor.process!(msg)
44
+ rescue => err
45
+ model, method = msg.message.to_s.split(".")
46
+ logger.error("#{model}##{method}: async worker failure: #{err.cause}\n#{err}\n#{err.backtrace.join("\n")}")
47
+ ensure
48
+ msg.ack
49
+ TopologicalInventory::Providers::Common::Operations::HealthCheck.touch_file
50
+ logger.debug("Operations::AsyncWorker queue length: #{queue.length}") if queue.length >= 20 && queue.length % 5 == 0
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end