sensu-plugins-elasticsearch-boutetnico 1.0.4

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.
@@ -0,0 +1,194 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # check-es-indices-field-count
4
+ #
5
+ # DESCRIPTION:
6
+ # This plugin checks if the number of fields in ES index(es) is approaching limit. ES by default
7
+ # puts this limit at 1000 fields per index.
8
+ #
9
+ # OUTPUT:
10
+ # plain text
11
+ #
12
+ # PLATFORMS:
13
+ # Linux
14
+ #
15
+ # DEPENDENCIES:
16
+ # gem: sensu-plugin
17
+ #
18
+ # USAGE:
19
+ # This example checks if the number of fields for an index is reaching its limit set in
20
+ # index.mapping.total_fields.limit. This check takes as paramater the index name or
21
+ # comma-separated list of indices, and optionally the type to check on. If type is not specified,
22
+ # all types in index are examined.
23
+ # You can also specify an optional value for the limit. When omitted, this will default to 1000.
24
+ #
25
+ # check-es-indices-field-count.rb -h <hostname or ip> -p 9200
26
+ # -i <index1>,<index2> --types <type_in_index> -w <pct> -c <pct>
27
+ #
28
+ # If any indices crossing the specified thresholds (warning/critical), beside the appropriate return code
29
+ # this check will also output a list of indices with the violated percentage for further troubleshooting.
30
+ # NOTES:
31
+ #
32
+ # LICENSE:
33
+ # CloudCruiser <devops@hpe.com>
34
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
35
+ # for details.
36
+ #
37
+
38
+ require 'sensu-plugin/check/cli'
39
+ require 'elasticsearch'
40
+ require 'sensu-plugins-elasticsearch'
41
+ require 'json'
42
+
43
+ #
44
+ # ES Indices Field Count
45
+ #
46
+ class ESIndicesFieldCount < Sensu::Plugin::Check::CLI
47
+ include ElasticsearchCommon
48
+
49
+ option :host,
50
+ description: 'Elasticsearch host',
51
+ short: '-h HOST',
52
+ long: '--host HOST',
53
+ default: 'localhost'
54
+
55
+ option :port,
56
+ description: 'Elasticsearch port',
57
+ short: '-p PORT',
58
+ long: '--port PORT',
59
+ proc: proc(&:to_i),
60
+ default: 9200
61
+
62
+ option :scheme,
63
+ description: 'Elasticsearch connection scheme, defaults to https for authenticated connections',
64
+ short: '-s SCHEME',
65
+ long: '--scheme SCHEME'
66
+
67
+ option :password,
68
+ description: 'Elasticsearch connection password',
69
+ short: '-P PASSWORD',
70
+ long: '--password PASSWORD'
71
+
72
+ option :user,
73
+ description: 'Elasticsearch connection user',
74
+ short: '-u USER',
75
+ long: '--user USER'
76
+
77
+ option :timeout,
78
+ description: 'Elasticsearch query timeout in seconds',
79
+ short: '-t TIMEOUT',
80
+ long: '--timeout TIMEOUT',
81
+ proc: proc(&:to_i),
82
+ default: 30
83
+
84
+ option :index,
85
+ description: 'Elasticsearch indices to check against.
86
+ Comma-separated list of index names to search.
87
+ Default to `_all` if omitted. Accepts wildcards',
88
+ short: '-i INDEX',
89
+ long: '--indices INDEX',
90
+ default: '_all'
91
+
92
+ option :types,
93
+ description: 'Elasticsearch types of index to check against.
94
+ Comma-separated list of types. When omitted, all types are checked against.',
95
+ short: '-T TYPES',
96
+ long: '--types TYPES'
97
+
98
+ option :limit,
99
+ description: 'Default number of fields limit to compare against.
100
+ Elasticsearch defaults this to 1000 if none is specified in index setting.',
101
+ short: '-l',
102
+ long: '--limit LIMIT',
103
+ proc: proc(&:to_i),
104
+ default: 1000
105
+
106
+ option :warn,
107
+ short: '-w PCT',
108
+ long: '--warn PCT',
109
+ description: 'WARNING threshold in percentage',
110
+ proc: proc(&:to_f),
111
+ default: 85.0
112
+
113
+ option :crit,
114
+ short: '-c N',
115
+ long: '--crit N',
116
+ description: 'CRITICAL threshold in percentage',
117
+ proc: proc(&:to_f),
118
+ default: 95.0
119
+
120
+ def indexfieldcount
121
+ index_field_count = {}
122
+ mappings = client.indices.get_mapping index: config[:index], type: config[:types]
123
+ mappings.each do |index, index_mapping|
124
+ unless index_mapping['mappings'].nil?
125
+ type_field_count = {}
126
+ index_mapping['mappings'].each do |type, type_mapping|
127
+ fieldcount = if type_mapping['properties'].nil?
128
+ 0
129
+ else
130
+ type_mapping['properties'].length
131
+ end
132
+ type_field_count[type] = fieldcount
133
+ end
134
+
135
+ index_field_count[index] = type_field_count
136
+ end
137
+ end
138
+
139
+ index_field_count
140
+ end
141
+
142
+ def fieldlimitsetting
143
+ field_limit_setting = {}
144
+ settings = client.indices.get_settings index: config[:index]
145
+ settings.each do |index, index_setting|
146
+ index_field_limit = index_setting['settings']['index.mapping.total_fields.limit']
147
+ # when no index.mapping.total_fields.limit, use value of the limit parameter, which defaults to 1000.
148
+ index_field_limit = config[:limit] if index_field_limit.nil?
149
+ field_limit_setting[index] = { 'limit' => index_field_limit }
150
+ end
151
+
152
+ field_limit_setting
153
+ end
154
+
155
+ def run
156
+ fieldcounts = indexfieldcount
157
+ limits = fieldlimitsetting
158
+
159
+ warnings = {}
160
+ criticals = {}
161
+
162
+ if fieldcounts.empty?
163
+ unknown "Can't find any indices."
164
+ end
165
+
166
+ fieldcounts.each do |index, counts|
167
+ counts.each do |type, count|
168
+ pct = count.to_f / limits[index]['limit'] * 100
169
+
170
+ if config[:warn] <= pct && pct < config[:crit]
171
+ warnings[index] = {} if warnings[index].nil?
172
+ warnings[index][type] = pct.round(2)
173
+ end
174
+
175
+ if config[:crit] <= pct
176
+ criticals[index] = {} if criticals[index].nil?
177
+ criticals[index][type] = pct.round(2)
178
+ end
179
+ end
180
+ end
181
+
182
+ unless criticals.empty?
183
+ critical "Number of fields in indices is at critical level.
184
+ #{JSON.pretty_generate(criticals)}"
185
+ end
186
+
187
+ unless warnings.empty?
188
+ warning "Number of fields in indices is at warning level.
189
+ #{JSON.pretty_generate(warnings)}"
190
+ end
191
+
192
+ ok
193
+ end
194
+ end
@@ -0,0 +1,199 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # check-es-indices-sizes.rb
4
+ #
5
+ # DESCRIPTION:
6
+ # This check sends a critical event when the indices mathing the date pattern
7
+ # are above a MB value.
8
+ #
9
+ # OUTPUT:
10
+ # plain text
11
+ #
12
+ # PLATFORMS:
13
+ # Linux
14
+ #
15
+ # DEPENDENCIES:
16
+ # gem: sensu-plugin
17
+ # gem: elasticsearch
18
+ # gem: aws_es_transport
19
+ #
20
+ # USAGE:
21
+ # ./check-es-indices-sizes.rb -h localhost -p 9200 -m 155000
22
+ #
23
+ # NOTES:
24
+ #
25
+ # LICENSE:
26
+ # Brendan Leon Gibat <brendan.gibat@gmail.com>
27
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
28
+ # for details.
29
+ #
30
+
31
+ require 'sensu-plugin/check/cli'
32
+ require 'elasticsearch'
33
+ require 'aws_es_transport'
34
+ require 'sensu-plugins-elasticsearch'
35
+
36
+ class ESCheckIndicesSizes < Sensu::Plugin::Check::CLI
37
+ include ElasticsearchCommon
38
+
39
+ option :transport,
40
+ long: '--transport TRANSPORT',
41
+ description: 'Transport to use to communicate with ES. Use "AWS" for signed AWS transports.'
42
+
43
+ option :region,
44
+ long: '--region REGION',
45
+ description: 'Region (necessary for AWS Transport)'
46
+
47
+ option :host,
48
+ description: 'Elasticsearch host',
49
+ short: '-h HOST',
50
+ long: '--host HOST',
51
+ default: 'localhost'
52
+
53
+ option :port,
54
+ description: 'Elasticsearch port',
55
+ short: '-p PORT',
56
+ long: '--port PORT',
57
+ proc: proc(&:to_i),
58
+ default: 9200
59
+
60
+ option :scheme,
61
+ description: 'Elasticsearch connection scheme, defaults to https for authenticated connections',
62
+ short: '-s SCHEME',
63
+ long: '--scheme SCHEME'
64
+
65
+ option :password,
66
+ description: 'Elasticsearch connection password',
67
+ short: '-P PASSWORD',
68
+ long: '--password PASSWORD'
69
+
70
+ option :user,
71
+ description: 'Elasticsearch connection user',
72
+ short: '-u USER',
73
+ long: '--user USER'
74
+
75
+ option :timeout,
76
+ description: 'Elasticsearch query timeout in seconds',
77
+ short: '-t TIMEOUT',
78
+ long: '--timeout TIMEOUT',
79
+ proc: proc(&:to_i),
80
+ default: 30
81
+
82
+ option :used_percent,
83
+ description: 'Percentage of bytes to use for indices matching pattern.',
84
+ short: '-a USED_PERCENTAGE',
85
+ long: '--used-percentage USED_PERCENTAGE',
86
+ proc: proc(&:to_i),
87
+ default: 80
88
+
89
+ option :maximum_megabytes,
90
+ description: 'Maximum number megabytes for date based indices to use.',
91
+ short: '-m MAXIMUM_MEGABYTES',
92
+ long: '--maximum-megabytes MAXIMUM_MEGABYTES',
93
+ proc: proc(&:to_i),
94
+ default: 0
95
+
96
+ option :pattern_regex,
97
+ description: 'Regular expression to use for matching date based indices. Four named groups are matched, pattern, year, month, day.',
98
+ short: '-x PATTERN_REGEX',
99
+ long: '--pattern-regex PATTERN_REGEX',
100
+ default: '^(?<pattern>.*)-(?<year>\d\d\d\d)\.(?<month>\d\d?).(?<day>\d\d?)$'
101
+
102
+ option :delete,
103
+ description: 'Instead of alerting deletes the indicies',
104
+ short: '-d',
105
+ long: '--delete',
106
+ boolean: true,
107
+ default: false
108
+
109
+ def get_indices_to_delete(starting_date, total_bytes_to_delete, indices_with_sizes)
110
+ total_bytes_deleted = 0
111
+
112
+ # TODO: switch from `DateTime` to `Time` or `Date`
113
+ curr_date = DateTime.now
114
+
115
+ indices_to_delete = []
116
+
117
+ # We don't delete the current day, as it is most likely being used.
118
+ while total_bytes_deleted < total_bytes_to_delete && starting_date < curr_date
119
+ same_day_indices = indices_with_sizes.values.map do |pattern|
120
+ pattern.select do |index|
121
+ index[:date] == starting_date
122
+ end
123
+ end.flatten
124
+ same_day_indices.each do |index|
125
+ if total_bytes_deleted < total_bytes_to_delete
126
+ indices_to_delete.push(index[:index])
127
+ total_bytes_deleted += index[:size]
128
+ end
129
+ end
130
+ starting_date += 1
131
+ end
132
+
133
+ indices_to_delete
134
+ end
135
+
136
+ def build_indices_with_sizes
137
+ indices_fs_stats = client.indices.stats store: true
138
+ pattern_regex = Regexp.new(config[:pattern_regex])
139
+
140
+ index_with_sizes = indices_fs_stats['indices'].keys.each_with_object({}) do |key, hash|
141
+ matching_index = pattern_regex.match(key)
142
+ unless matching_index.nil?
143
+ base_pattern = matching_index[:pattern]
144
+ unless base_pattern.nil?
145
+ unless hash.include?(base_pattern)
146
+ hash[base_pattern] = []
147
+ end
148
+ index_date = DateTime.new(matching_index[:year].to_i, matching_index[:month].to_i, matching_index[:day].to_i)
149
+ hash[base_pattern].push(
150
+ size: indices_fs_stats['indices'][key]['total']['store']['size_in_bytes'].to_i,
151
+ date: index_date,
152
+ index: key
153
+ )
154
+ end
155
+ end
156
+ end
157
+
158
+ index_with_sizes
159
+ end
160
+
161
+ def run
162
+ node_fs_stats = client.nodes.stats metric: 'fs,indices'
163
+ nodes_being_used = node_fs_stats['nodes'].values.select { |node| node['indices']['store']['size_in_bytes'] > 0 }
164
+
165
+ # TODO: come back and cleanup all these rubocop disables with a little refactor
166
+ # rubocop:disable Metrics/LineLength
167
+ used_in_bytes = nodes_being_used.map { |node| node['fs']['data'].map { |data| data['total_in_bytes'] - data['available_in_bytes'] }.flatten }.flatten.inject { |sum, x| sum + x }
168
+ total_in_bytes = nodes_being_used.map { |node| node['fs']['data'].map { |data| data['total_in_bytes'] }.flatten }.flatten.inject { |sum, x| sum + x }
169
+ # rubocop:enable Metrics/LineLength
170
+
171
+ if config[:maximum_megabytes] > 0
172
+ target_bytes_used = config[:maximum_megabytes] * 1_000_000
173
+ else
174
+ if config[:used_percent] > 100 || config[:used_percent] < 0
175
+ critical 'You can not make used-percentages greater than 100 or less than 0.'
176
+ end
177
+ target_bytes_used = (total_in_bytes.to_f * (config[:used_percent].to_f / 100.0)).to_i
178
+ end
179
+
180
+ total_bytes_to_delete = used_in_bytes - target_bytes_used
181
+ if total_bytes_to_delete <= 0
182
+ ok "Used space in bytes: #{used_in_bytes}, Total in bytes: #{total_in_bytes}"
183
+ end
184
+
185
+ indices_with_sizes = build_indices_with_sizes
186
+
187
+ oldest = indices_with_sizes.values.flatten.map { |index| index[:date] }.min
188
+ indices_to_delete = get_indices_to_delete(oldest, total_bytes_to_delete, indices_with_sizes)
189
+
190
+ if config[:delete]
191
+ client.indices.delete index: indices_to_delete
192
+ ok "Cleaned up space: #{total_bytes_to_delete}"
193
+ else
194
+ critical "Not enough space, #{total_bytes_to_delete} bytes need to be deleted. Used space in bytes: " \
195
+ "#{used_in_bytes}, Total in bytes: #{total_in_bytes}. Indices to delete: " \
196
+ "#{indices_to_delete.sort.map { |i| "INDEX[#{i}]" }.join(', ')}"
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,137 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # check-es-node-status
4
+ #
5
+ # DESCRIPTION:
6
+ # This plugin checks the ElasticSearch node status, using its API.
7
+ # Works with ES 0.9x, ES 1.x, and ES 2.x
8
+ #
9
+ # OUTPUT:
10
+ # plain text
11
+ #
12
+ # PLATFORMS:
13
+ # Linux
14
+ #
15
+ # DEPENDENCIES:
16
+ # gem: sensu-plugin
17
+ # gem: rest-client
18
+ #
19
+ # USAGE:
20
+ # check-es-node-status --help
21
+ #
22
+ # NOTES:
23
+ #
24
+ # LICENSE:
25
+ # Copyright 2012 Sonian, Inc <chefs@sonian.net>
26
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
27
+ # for details.
28
+ #
29
+
30
+ require 'sensu-plugin/check/cli'
31
+ require 'rest-client'
32
+ require 'json'
33
+ require 'base64'
34
+
35
+ class ESNodeStatus < Sensu::Plugin::Check::CLI
36
+ option :host,
37
+ description: 'Elasticsearch host',
38
+ short: '-h HOST',
39
+ long: '--host HOST',
40
+ default: 'localhost'
41
+
42
+ option :port,
43
+ description: 'Elasticsearch port',
44
+ short: '-p PORT',
45
+ long: '--port PORT',
46
+ proc: proc(&:to_i),
47
+ default: 9200
48
+
49
+ option :timeout,
50
+ description: 'Sets the connection timeout for REST client',
51
+ short: '-t SECS',
52
+ long: '--timeout SECS',
53
+ proc: proc(&:to_i),
54
+ default: 30
55
+
56
+ option :user,
57
+ description: 'Elasticsearch User',
58
+ short: '-u USER',
59
+ long: '--user USER'
60
+
61
+ option :password,
62
+ description: 'Elasticsearch Password',
63
+ short: '-P PASS',
64
+ long: '--password PASS'
65
+
66
+ option :https,
67
+ description: 'Enables HTTPS',
68
+ short: '-e',
69
+ long: '--https'
70
+
71
+ option :cert_file,
72
+ description: 'Cert file to use',
73
+ long: '--cert-file CERT_FILE'
74
+
75
+ option :all,
76
+ description: 'Check all nodes in the ES cluster',
77
+ short: '-a',
78
+ long: '--all',
79
+ default: false
80
+
81
+ def get_es_resource(resource)
82
+ headers = {}
83
+ if config[:user] && config[:password]
84
+ auth = 'Basic ' + Base64.strict_encode64("#{config[:user]}:#{config[:password]}").chomp
85
+ headers = { 'Authorization' => auth }
86
+ end
87
+
88
+ protocol = if config[:https]
89
+ 'https'
90
+ else
91
+ 'http'
92
+ end
93
+
94
+ r = if config[:cert_file]
95
+ RestClient::Resource.new("#{protocol}://#{config[:host]}:#{config[:port]}#{resource}",
96
+ ssl_ca_file: config[:cert_file].to_s,
97
+ timeout: config[:timeout],
98
+ headers: headers)
99
+ else
100
+ RestClient::Resource.new("#{protocol}://#{config[:host]}:#{config[:port]}#{resource}",
101
+ timeout: config[:timeout],
102
+ headers: headers)
103
+ end
104
+ r.get
105
+ rescue Errno::ECONNREFUSED
106
+ critical 'Connection refused'
107
+ rescue RestClient::RequestTimeout
108
+ critical 'Connection timed out'
109
+ rescue Errno::ECONNRESET
110
+ critical 'Connection reset by peer'
111
+ end
112
+
113
+ def acquire_status
114
+ status = get_es_resource('/_nodes/stats')
115
+ status
116
+ end
117
+
118
+ def run
119
+ stats = acquire_status
120
+
121
+ if stats.code == 200
122
+ if config[:all]
123
+ total = stats['_nodes']['total']
124
+ successful = stats['_nodes']['successful']
125
+ if total == successful
126
+ ok 'Alive - all nodes'
127
+ else
128
+ critical 'Dead - one or more nodes'
129
+ end
130
+ else
131
+ ok "Alive #{stats.code}"
132
+ end
133
+ else
134
+ critical "Dead (#{stats.code})"
135
+ end
136
+ end
137
+ end