Skip to content

Commit d0e9b00

Browse files
committed
(SUP-3521) Remove legacy code
This commit removes legacy code and makes a few major changes: * Deprecate remote shipping to InfluxDB and Graphite * Refactor pe_metrics.rb into a mixin module * Use Puppet's HTTP client instead of net/http * Remove puma_metrics in favor of tk_metrics for all services * Remove version-exclusive code. Only currently supported PE and OSP Puppet releases are supported
1 parent 2a84a31 commit d0e9b00

20 files changed

+349
-918
lines changed

files/json2timeseriesdb

Lines changed: 0 additions & 421 deletions
This file was deleted.

files/pe_metrics.rb

100755100644
Lines changed: 115 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,165 +1,123 @@
1-
#!/opt/puppetlabs/puppet/bin/ruby
2-
3-
require 'net/https'
41
require 'json'
52
require 'uri'
63
require 'time'
74
require 'optparse'
85
require 'yaml'
9-
10-
options = {}
11-
12-
OptionParser.new { |opts|
13-
opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
14-
opts.on('-p', '--[no-]print', 'Print to STDOUT') { |p| options[:print] = p }
15-
opts.on('-m [TYPE]', '--metrics_type [TYPE]', 'Type of metrics to collect') { |v| options[:metrics_type] = v }
16-
opts.on('-o [DIR]', '--output_dir [DIR]', 'Directory to save output to') { |o| options[:output_dir] = o }
17-
opts.on('--metrics_port [PORT]', 'The port the metrics service runs on') { |port| options[:metrics_port] = port }
18-
opts.on('--[no-]ssl', 'Use SSL when collecting metrics') { |ssl| options[:ssl] = ssl }
19-
}.parse!
20-
21-
if options[:metrics_type].nil?
22-
STDERR.puts '--metrics_type (-m) is a required argument'
23-
exit 1
24-
end
25-
26-
# Allow scripts that require this script to access the options hash.
27-
OPTIONS = options
28-
29-
config_file = File.expand_path("../../config/#{options[:metrics_type]}.yaml", __FILE__)
30-
config = YAML.load_file(config_file)
31-
32-
def coalesce(higher_precedence, lower_precedence, default = nil)
33-
[higher_precedence, lower_precedence, default].find { |x| !x.nil? }
34-
end
35-
36-
METRICS_TYPE = options[:metrics_type]
37-
OUTPUT_DIR = options[:output_dir]
38-
PE_VERSION = config['pe_version']
39-
CERTNAME = config['clientcert']
40-
HOSTS = config['hosts']
41-
PORT = coalesce(options[:metrics_port], config['metrics_port'])
42-
USE_SSL = coalesce(options[:ssl], config['ssl'], true)
43-
EXCLUDES = config['excludes']
44-
ADDITIONAL_METRICS = config['additional_metrics']
45-
REMOTE_METRICS_ENABLED = config['remote_metrics_enabled']
46-
47-
# Metrics endpoints for our Puma services require a client certificate with SSL.
48-
# Metrics endpoints for our Trapper Keeper services do not require a client certificate.
49-
50-
if USE_CLIENTCERT
51-
SSLDIR = `/opt/puppetlabs/bin/puppet config print ssldir`.chomp
52-
end
53-
54-
$error_array = []
55-
56-
def generate_host_url(host, port, use_ssl)
57-
protocol = use_ssl ? 'https' : 'http'
58-
59-
host_url = "#{protocol}://#{host}:#{port}"
60-
end
61-
62-
def setup_connection(url, use_ssl)
63-
uri = URI.parse(url)
64-
http = Net::HTTP.new(uri.host, uri.port)
65-
66-
if use_ssl
67-
http.use_ssl = true
68-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
69-
70-
if USE_SSL && USE_CLIENTCERT
71-
# PE Puma services serve metrics from endpoints requiring a client certificate.
72-
# If https://github.com/puma/puma/pull/2098 is merged into the Puma used by PE,
73-
# we can collect metrics from /stats and /gc-stats without a client certificate.
74-
http.ca_path = "#{SSLDIR}/ca"
75-
http.ca_file = "#{SSLDIR}/certs/ca.pem"
76-
http.cert = OpenSSL::X509::Certificate.new(File.read("#{SSLDIR}/certs/#{CERTNAME}.pem"))
77-
http.key = OpenSSL::PKey::RSA.new(File.read("#{SSLDIR}/private_keys/#{CERTNAME}.pem"))
78-
end
79-
end
80-
81-
[http, uri]
82-
end
83-
84-
def get_endpoint(url, use_ssl)
85-
http, uri = setup_connection(url, use_ssl)
86-
87-
endpoint_data = JSON.parse(http.get(uri.request_uri).body)
88-
if endpoint_data.key?('status')
89-
if endpoint_data['status'] == 200
90-
endpoint_data = endpoint_data['value']
91-
else
92-
$error_array << "HTTP Error #{endpoint_data['status']} for #{url}"
93-
endpoint_data = {}
6+
require 'puppet'
7+
require 'puppet/http'
8+
9+
module PuppetX
10+
module Puppetlabs
11+
# Mixin module to provide instance variables and methods to the tk_metris script
12+
module PuppetMetricsCollector
13+
attr_accessor :client, :metrics_type, :output_dir, :certname, :hosts, :port, :excludes, :additional_metrics, :print, :errors
14+
15+
def metrics_collector_setup
16+
# The Puppet HTTP client takes care of connection pooling, client cert auth, and more for us
17+
@client ||= Puppet.runtime[:http]
18+
@errors ||= []
19+
20+
OptionParser.new { |opts|
21+
opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
22+
opts.on('-p', '--[no-]print', 'Print to STDOUT') { |p| @print = p }
23+
opts.on('-m [TYPE]', '--metrics_type [TYPE]', 'Type of metrics to collect') { |v| @metrics_type = v }
24+
opts.on('-o [DIR]', '--output_dir [DIR]', 'Directory to save output to') { |o| @output_dir = o }
25+
opts.on('--metrics_port [PORT]', 'The port the metrics service runs on') { |port| @metrics_port = port }
26+
}.parse!
27+
if @metrics_type.nil?
28+
STDERR.puts '--metrics_type (-m) is a required argument'
29+
exit 1
30+
end
31+
32+
config_file = File.expand_path("../../config/#{@metrics_type}.yaml", __FILE__)
33+
config = YAML.load_file(config_file)
34+
35+
@certname = config['certname']
36+
@hosts = config['hosts']
37+
@excludes = config['excludes']
38+
@additional_metrics = config['additional_metrics']
39+
@port = @metrics_port ? @metrics_port : config['metrics_port']
40+
rescue StandardError => e
41+
STDERR.puts "Failed to load config for #{@metrics_type}: #{e.message}"
42+
STDERR.puts e.backtrace
43+
nil
44+
end
45+
46+
def get_endpoint(url)
47+
response = @client.get(url)
48+
49+
if response.success?
50+
JSON.parse(response.body)
51+
else
52+
STDERR.puts "Received HTTP code '#{response.code}' with message '#{response.reason}' for #{url}"
53+
@errors << "HTTP Error #{response.code} for #{url}"
54+
{}
55+
end
56+
rescue StandardError => e
57+
STDERR.puts "Failed to query #{url}: #{e.message}"
58+
STDERR.puts e.backtrace
59+
60+
@errors << e.to_s
61+
{}
62+
end
63+
64+
def post_endpoint(url, body)
65+
response = @client.post(url, body, headers: { 'Content-Type' => 'application/json' })
66+
if response.success?
67+
JSON.parse(response.body)
68+
else
69+
STDERR.puts "Received HTTP code '#{response.code}' with message '#{response.reason}' for #{url}"
70+
@errors << "HTTP Error #{response.code} for #{url}"
71+
{}
72+
end
73+
rescue StandardError => e
74+
STDERR.puts "Failed to post to #{url}: #{e.message}"
75+
STDERR.puts e.backtrace
76+
77+
@errors << e.to_s
78+
{}
79+
end
80+
81+
def retrieve_additional_metrics(url, _metrics_type, metrics)
82+
metrics_output = post_endpoint(url, metrics.to_json)
83+
return [] if metrics_output.empty?
84+
85+
# For a status other than 200 or 404, add the HTTP code to the error array
86+
metrics_output.select { |m| m.key?('status') and ![200, 404].include?(m['status']) }.each do |m|
87+
@errors << "HTTP Error #{m['status']} for #{m['request']['mbean']}"
88+
end
89+
90+
# Select metrics output that has a 'status' key
91+
metrics_output.select { |m| m.key?('status') }.map do |m|
92+
# Then merge the corresponding 'metrics' hash
93+
# e.g. for a metrics_output entry of
94+
# {"request"=>{"mbean"=>"puppetlabs.puppetdb.mq:name=global.command-parse-time", "type"=>"read"}, "value" => ...}
95+
# and a metrics entry of
96+
# {"type"=>"read", "name"=>"global_command-parse-time", "mbean"=>"puppetlabs.puppetdb.mq:name=global.command-parse-time"}
97+
# the result is that 'name' entry is added to the metris_output hash
98+
m.merge!(metrics.find { |n| n['mbean'] == m['request']['mbean'] })
99+
100+
status = m['status']
101+
if status == 200
102+
{ 'name' => m['name'], 'data' => m['value'] }
103+
elsif status == 404
104+
{ 'name' => m['name'], 'data' => nil }
105+
end
106+
end
107+
end
108+
109+
def filter_metrics(dataset, filters)
110+
return dataset if filters.empty?
111+
112+
case dataset
113+
when Hash
114+
dataset = dataset.each_with_object({}) { |(k, v), m| m[k] = filter_metrics(v, filters) unless filters.include? k; }
115+
when Array
116+
dataset.map! { |e| filter_metrics(e, filters) }
117+
end
118+
119+
dataset
120+
end
94121
end
95122
end
96-
endpoint_data
97-
rescue Exception => e
98-
$error_array << e.to_s
99-
endpoint_data = {}
100-
end
101-
102-
def post_endpoint(url, use_ssl, post_data)
103-
http, uri = setup_connection(url, use_ssl)
104-
105-
request = Net::HTTP::Post.new(uri.request_uri)
106-
request.content_type = 'application/json'
107-
request.body = post_data
108-
109-
endpoint_data = JSON.parse(http.request(request).body)
110-
endpoint_data
111-
rescue Exception => e
112-
$error_array << e.to_s
113-
endpoint_data = {}
114-
end
115-
116-
def retrieve_additional_metrics(host, port, use_ssl, metrics_type, metrics)
117-
if metrics_type == 'puppetdb'
118-
host = '127.0.0.1' if host == CERTNAME && !REMOTE_METRICS_ENABLED
119-
unless REMOTE_METRICS_ENABLED || ['127.0.0.1', 'localhost'].include?(host)
120-
# Puppet services released between May, 2020 and Feb 2021 had
121-
# the /metrics API disabled due to:
122-
# https://puppet.com/security/cve/CVE-2020-7943/
123-
return []
124-
end
125-
end
126-
127-
host_url = generate_host_url(host, port, use_ssl)
128-
129-
metrics_array = []
130-
endpoint = "#{host_url}/metrics/v2/read"
131-
metrics_output = post_endpoint(endpoint, use_ssl, metrics.to_json)
132-
return metrics_array if metrics_output.empty?
133-
134-
metrics.each_index do |index|
135-
metric_name = metrics[index]['name']
136-
metric_data = metrics_output[index]
137-
138-
metric_status = metric_data ? metric_data.dig('status') : nil
139-
next unless metric_status
140-
141-
if metric_status == 200
142-
metrics_array << { 'name' => metric_name, 'data' => metric_data['value'] }
143-
elsif metric_status == 404
144-
metrics_array << { 'name' => metric_name, 'data' => nil }
145-
else
146-
metric_mbean = metrics[index]['mbean']
147-
$error_array << "HTTP Error #{metric_status} for #{metric_mbean}"
148-
end
149-
end
150-
151-
metrics_array
152-
end
153-
154-
def filter_metrics(dataset, filters)
155-
return dataset if filters.empty?
156-
157-
case dataset
158-
when Hash
159-
dataset = dataset.each_with_object({}) { |(k, v), m| m[k] = filter_metrics(v, filters) unless filters.include? k; }
160-
when Array
161-
dataset.map! { |e| filter_metrics(e, filters) }
162-
end
163-
164-
dataset
165123
end

files/puma_metrics

Lines changed: 0 additions & 59 deletions
This file was deleted.

0 commit comments

Comments
 (0)