sensu-plugins-elasticsearch-boutetnico 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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