topological_inventory-providers-common 1.0.9 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) 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 -2
  6. data/lib/topological_inventory/providers/common.rb +2 -2
  7. data/lib/topological_inventory/providers/common/collectors_pool.rb +2 -1
  8. data/lib/topological_inventory/providers/common/logging.rb +10 -4
  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/health_check.rb +15 -0
  17. data/lib/topological_inventory/providers/common/operations/processor.rb +46 -104
  18. data/lib/topological_inventory/providers/common/operations/source.rb +183 -144
  19. data/lib/topological_inventory/providers/common/sources_api_client.rb +92 -0
  20. data/lib/topological_inventory/providers/common/topology_api_client.rb +43 -0
  21. data/lib/topological_inventory/providers/common/version.rb +1 -1
  22. data/spec/support/shared/availability_check.rb +254 -90
  23. data/spec/topological_inventory/providers/common/operations/async_worker_spec.rb +36 -0
  24. data/spec/topological_inventory/providers/common/operations/processor_spec.rb +52 -83
  25. data/topological_inventory-providers-common.gemspec +14 -10
  26. metadata +75 -9
  27. data/lib/topological_inventory/providers/common/operations/endpoint_client.rb +0 -65
  28. data/lib/topological_inventory/providers/common/operations/sources_api_client.rb +0 -94
  29. 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: 880ff945277cf130a2617a20278ff29298e8a7644b92e254e7c4dd32002a58bc
4
- data.tar.gz: b3e65e3652f156fc0de8e2f6d97c1a9eac095d015035ae08d787c86f1da3b597
3
+ metadata.gz: aad209b97efea9bb19ce488782adb1dabfa8d650c12f8637e2b348ed4ec95a15
4
+ data.tar.gz: 69b9bbed6a2d725990206ab3bfc7b13ec25b10f7e0c1a979b48c61b67e53fd0d
5
5
  SHA512:
6
- metadata.gz: 7ad7a27bd9ae06e1161917aa5fa55f20f87ed9a69d9e1936968e21c7c4bab634924317e544afeaaa8530de2998bd156f912435eef9c3151758e82f8f3f0f9c08
7
- data.tar.gz: 37e3bdc21b29c71927a0c797a6cc56267a8be9ab1031a702ed6d6fa8f38c76c38ddb13baf32f1eeaccf3306328daed21edbcedeb0a9f1669c0df3ee9a685c029
6
+ metadata.gz: 45722b70f009a764d8cd804424157cc98b1a225b72326b8b8971fecd093fa6260446777db76bc3502e487d27dc82b2b4325959302f4a1abe13fbbf8c015c1d54
7
+ data.tar.gz: b1a6de8e2eb93b2b26d4b609f17a1a518cf4b318dbcf117c6225fe297bb3ec97f870b944e151786977ad90f42c16746eeeadf6a3d113b648921e278cb831aadd
@@ -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,7 +4,25 @@ 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.9]
7
+ ## [2.1.0]
8
+ Add Availability checks for sources via Kafka #54
9
+ Common Metrics exporter #57
10
+ Rubocop rules from insights-api-common + yamllint #59
11
+
12
+ ## [2.0.0] - 2020-10-20
13
+ Operations/API clients refactoring
14
+
15
+ ## [1.0.12] - 2020-10-01
16
+ Add Operations Async Worker class #55
17
+
18
+ ## [1.0.11] - 2020-09-04
19
+ Make Collector Poll Time a parameter so we can tweak the collection interval #51
20
+
21
+ ## [1.0.10] - 2020-08-26
22
+ Add HealthCheck class for operations workers #48
23
+ Set the LOG_LEVEL if present #50
24
+
25
+ ## [1.0.9] - 2020-08-17
8
26
  Added refresh-type to save and sweep inventory #45
9
27
 
10
28
  ## [1.0.8] - 2020-08-12
@@ -49,7 +67,12 @@ manageiq-loggers to >= 0.4.2 #20
49
67
  ## [1.0.0] - 2020-03-19
50
68
  ### Initial release to rubygems.org
51
69
 
52
- [Unreleased]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.9...HEAD
70
+ [Unreleased]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v2.1.0...HEAD
71
+ [2.1.0]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v2.0.0...v2.1.0
72
+ [2.0.0]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.12...v2.0.0
73
+ [1.0.12]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.11...v1.0.12
74
+ [1.0.11]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.10...v1.0.11
75
+ [1.0.10]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.9...v1.0.10
53
76
  [1.0.9]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.8...v1.0.9
54
77
  [1.0.8]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.7...v1.0.8
55
78
  [1.0.7]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.6...v1.0.7
@@ -1,9 +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"
3
+ require "topological_inventory/providers/common/operations/health_check"
5
4
  require "topological_inventory/providers/common/collectors_pool"
6
5
  require "topological_inventory/providers/common/collector"
6
+ require "topological_inventory/providers/common/metrics"
7
7
 
8
8
  module TopologicalInventory
9
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,17 +23,23 @@ 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
 
34
37
  class Logger < ManageIQ::Loggers::CloudWatch
35
38
  def self.new(*args)
36
- super.tap { |logger| logger.extend(TopologicalInventory::Providers::Common::LoggingFunctions) }
39
+ super.tap do |logger|
40
+ logger.extend(TopologicalInventory::Providers::Common::LoggingFunctions)
41
+ logger.level = ENV['LOG_LEVEL'] if ENV['LOG_LEVEL']
42
+ end
37
43
  end
38
44
  end
39
45
 
@@ -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
+ @queue_host = ENV['QUEUE_HOST'] || 'localhost'
15
+ @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 => @queue_host, :port => @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