-
Notifications
You must be signed in to change notification settings - Fork 181
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
251 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
testsuite/features/step_definitions/system_monitoring_steps.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Copyright (c) 2024 SUSE LLC. | ||
# Licensed under the terms of the MIT license. | ||
|
||
### This file contains the definitions for the steps extracting and reporting information from the system | ||
|
||
When(/^I report the bootstrap duration for "([^"]*)"$/) do |host| | ||
next unless $quality_intelligence_mode | ||
|
||
duration = last_bootstrap_duration(host) | ||
$quality_intelligence.push_bootstrap_duration(host, duration) | ||
end | ||
|
||
When(/^I report the onboarding duration for "([^"]*)"$/) do |host| | ||
next unless $quality_intelligence_mode | ||
|
||
duration = last_onboarding_duration(host) | ||
$quality_intelligence.push_onboarding_duration(host, duration) | ||
end | ||
|
||
When(/^I report the synchronization duration for "([^"]*)"$/) do |product| | ||
next unless $quality_intelligence_mode | ||
|
||
duration = synchronization_duration(product) | ||
$quality_intelligence.push_synchronization_duration(product, duration) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# Copyright (c) 2024 SUSE LLC. | ||
# Licensed under the terms of the MIT license. | ||
require 'redis' | ||
|
||
# Database handler to interact with a Redis database | ||
class DatabaseHandler | ||
# Initialize a connection with a Redis database | ||
# | ||
# @param redis_host [String] The hostname of the Redis database. | ||
# @param redis_port [Integer] The port of the Redis database. | ||
# @param redis_username [String] The username to authenticate with the Redis database. | ||
# @param redis_password [String] The password to authenticate with the Redis database. | ||
# @return [Redis] A connection with the Redis database. | ||
def initialize(redis_host, redis_port, redis_username, redis_password) | ||
@database = Redis.new(host: redis_host, port: redis_port, username: redis_username, password: redis_password) | ||
end | ||
|
||
# Close the connection with the Redis database | ||
def close | ||
@database.close | ||
end | ||
|
||
# Add a key-value pair to a Set, optionally selecting a database | ||
# | ||
# @param key [String] The key to add the value to. | ||
# @param value [String] The value to add to the key. | ||
# @param database [Integer] The database to select (default: 0). | ||
# @return [String] `OK` | ||
def add(key, value, database = 0) | ||
begin | ||
@database.select(database) | ||
@database.sadd(key, value) | ||
rescue StandardError => e | ||
warn("#{e.backtrace} > #{key} : #{value}") | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# Copyright (c) 2024 SUSE LLC. | ||
# Licensed under the terms of the MIT license. | ||
|
||
require 'net/http' | ||
require 'prometheus/client' | ||
require 'prometheus/client/push' | ||
require 'uri' | ||
|
||
# Prometheus handler to push metrics to Prometheus | ||
class PrometheusHandler | ||
def initialize(push_gateway_url) | ||
@push_gateway_url = push_gateway_url | ||
@prometheus = Prometheus::Client::Registry.new | ||
end | ||
|
||
# Push a metric to Prometheus, raising an error if the Prometheus request fails. | ||
# | ||
# @param job_name [String] the job name to push the metric to | ||
# @param metric_name [String] the metric name to push | ||
# @param metric_value [Integer] the metric value to push | ||
# @param labels [Hash] the labels to add to the metric | ||
# @return [void] | ||
# @raise [SystemCallError] if the Prometheus request fails | ||
def push_metric_to_prometheus(job_name, metric_name, metric_value, labels = {}) | ||
begin | ||
gauge = @prometheus.get(metric_name.to_sym) || @prometheus.gauge(metric_name.to_sym, docstring: job_name, labels: labels.keys) | ||
gauge.set(metric_value, labels: labels) | ||
Prometheus::Client::Push.new(job: job_name, gateway: @push_gateway_url).add(@prometheus) | ||
rescue StandardError => e | ||
warn(e.full_message) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# Copyright (c) 2024 SUSE LLC. | ||
# Licensed under the terms of the MIT license. | ||
require_relative 'database_handler' | ||
require_relative 'prometheus_handler' | ||
|
||
# Quality Intelligence handler to produce, parse and report Quality Intelligence from the test suite | ||
class QualityIntelligence | ||
QI = 'quality_intelligence'.freeze | ||
private_constant :QI | ||
|
||
# Initialize the QualityIntelligence handler | ||
def initialize | ||
@db_handler = DatabaseHandler.new(ENV.fetch('REDIS_HOST', nil), ENV.fetch('REDIS_PORT', nil), ENV.fetch('REDIS_USERNAME', nil), ENV.fetch('REDIS_PASSWORD', nil)) | ||
@prometheus_handler = PrometheusHandler.new(ENV.fetch('PROMETHEUS_PUSH_GATEWAY_URL', 'http://nsa.mgr.suse.de:9091')) | ||
@environment = ENV.fetch('SERVER', nil) | ||
end | ||
|
||
# Report the time to complete a bootstrap of a system passed as parameter, | ||
# raising an error if the Prometheus request fails. | ||
# | ||
# @param system [String] the system to bootstrap | ||
# @param time [Integer] the time to complete the bootstrap in seconds | ||
# @return [void] | ||
def push_bootstrap_duration(system, time) | ||
@prometheus_handler.push_metric_to_prometheus(QI, 'system_bootstrap_duration_seconds', time, labels: { 'system' => system, 'environment' => @environment }) | ||
end | ||
|
||
# Report the time to complete the onboarding of a system passed as parameter, | ||
# raising an error if the Prometheus request fails. | ||
# | ||
# @param system [String] the system to be onboarded | ||
# @param time [Integer] the time to complete the onboarding in seconds | ||
# @return [void] | ||
def push_onboarding_duration(system, time) | ||
@prometheus_handler.push_metric_to_prometheus(QI, 'system_onboarding_duration_seconds', time, labels: { 'system' => system, 'environment' => @environment }) | ||
end | ||
|
||
# Report the time to complete a synchronization of a product passed as parameter, | ||
# raising an error if the Prometheus request fails. | ||
# | ||
# @param product [String] the product to synchronize | ||
# @param time [Integer] the time to complete the synchronization in seconds | ||
# @return [void] | ||
def push_synchronization_duration(product, time) | ||
@prometheus_handler.push_metric_to_prometheus(QI, 'product_synch_duration_seconds', time, labels: { 'product' => product, 'environment' => @environment }) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# Copyright (c) 2024 SUSE LLC. | ||
# Licensed under the terms of the MIT license. | ||
|
||
require 'time' | ||
|
||
# This method should return the last bootstrap duration for the given host | ||
# @param host [String] the hostname | ||
# @return [Float] the duration in seconds or nil if the duration could not be determined | ||
def last_bootstrap_duration(host) | ||
system_name = get_system_name(host) | ||
duration = nil | ||
lines, _code = get_target('server').run('tail -n100 /var/log/rhn/rhn_web_api.log') | ||
|
||
lines.each_line do |line| | ||
if line.include?(system_name) && line.include?('systems.bootstrap') | ||
match = line.match(/TIME: (\d+\.\d+) seconds/) | ||
duration = match[1].to_f if match | ||
end | ||
end | ||
|
||
raise ScriptError, "Boostrap duration not found for #{host}" if duration.nil? | ||
|
||
duration | ||
end | ||
|
||
# This method should return the last onboarding duration for the given host | ||
# @param host [String] the hostname | ||
# @return [Float] the duration in seconds or nil if the duration could not be determined | ||
def last_onboarding_duration(host) | ||
node = get_target(host) | ||
system_id = get_system_id(node) | ||
events = $api_test.system.get_event_history(system_id, 0, 10) | ||
onboarding_events = events.select { |event| event['summary'].include? 'certs, channels, packages' } | ||
last_event_id = onboarding_events.last['id'] | ||
event_details = $api_test.system.get_event_details(system_id, last_event_id) | ||
Time.parse(event_details['completed']) - Time.parse(event_details['picked_up']) | ||
end | ||
|
||
# This method should return the synchronization duration for the given product | ||
# @param os_product_version [String] the product name | ||
# @return [Float] the duration in seconds or nil if the duration could not be determined | ||
def synchronization_duration(os_product_version) | ||
channels_to_wait = CHANNEL_TO_SYNC_BY_OS_PRODUCT_VERSION.dig(product, os_product_version) | ||
channels_to_wait = filter_channels(channels_to_wait, ['beta']) unless $beta_enabled | ||
raise ScriptError, "Synchronization error, channels for #{os_product_version} in #{product} not found" if channels_to_wait.nil? | ||
|
||
duration = 0 | ||
channel_to_evaluate = false | ||
get_target('server').extract('/var/log/rhn/reposync.log', '/tmp/reposync.log') | ||
File.foreach('/tmp/reposync.log') do |line| | ||
if line.include?('Channel: ') | ||
channel_name = line.split('Channel: ')[1].strip | ||
channel_to_evaluate = channels_to_wait.include?(channel_name) | ||
end | ||
if line.include?('Total time: ') && channel_to_evaluate | ||
match = line.match(/Total time: (\d+):(\d+):(\d+)/) | ||
hours, minutes, seconds = match.captures.map(&:to_i) | ||
total_seconds = (hours * 3600) + (minutes * 60) + seconds | ||
duration += total_seconds | ||
channel_to_evaluate = false | ||
end | ||
end | ||
duration | ||
end |