├── .bonsai.yml ├── .editorconfig ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .kitchen.yml ├── .rubocop.yml ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bin ├── check-es-circuit-breakers.rb ├── check-es-cluster-health.rb ├── check-es-cluster-status.rb ├── check-es-file-descriptors.rb ├── check-es-heap.rb ├── check-es-indexes.rb ├── check-es-indices-field-count.rb ├── check-es-indices-sizes.rb ├── check-es-node-status.rb ├── check-es-query-average.rb ├── check-es-query-count.rb ├── check-es-query-exists.rb ├── check-es-query-ratio.rb ├── check-es-shard-allocation-status.rb ├── handler-es-delete-indices.rb ├── metrics-es-cluster.rb ├── metrics-es-node-graphite.rb └── metrics-es-node.rb ├── lib ├── sensu-plugins-elasticsearch.rb └── sensu-plugins-elasticsearch │ ├── elasticsearch-common.rb │ ├── elasticsearch-query.rb │ └── version.rb ├── sensu-plugins-elasticsearch.gemspec └── test ├── fixtures └── bootstrap.sh ├── integration ├── helpers │ └── serverspec │ │ ├── check-es-indices-field-count-shared_spec.rb │ │ ├── check-es-query-count-shared_spec.rb │ │ ├── shared_spec.rb │ │ └── spec_helper.rb ├── ruby-20 │ └── serverspec │ │ └── default_spec.rb ├── ruby-21 │ └── serverspec │ │ └── default_spec.rb ├── ruby-22 │ └── serverspec │ │ └── default_spec.rb ├── ruby-230 │ └── serverspec │ │ └── default_spec.rb └── ruby-241 │ └── serverspec │ └── default_spec.rb └── spec_helper.rb /.bonsai.yml: -------------------------------------------------------------------------------- 1 | --- 2 | description: "#{repo}" 3 | builds: 4 | - platform: "alpine" 5 | arch: "amd64" 6 | asset_filename: "#{repo}_#{version}_alpine_linux_amd64.tar.gz" 7 | sha_filename: "#{repo}_#{version}_sha512-checksums.txt" 8 | filter: 9 | - "entity.system.os == 'linux'" 10 | - "entity.system.arch == 'amd64'" 11 | - "entity.system.platform == 'alpine'" 12 | - "entity.system.platform_version.split('.')[0] == '3'" 13 | - platform: "alpine3.8" 14 | arch: "amd64" 15 | asset_filename: "#{repo}_#{version}_alpine3.8_linux_amd64.tar.gz" 16 | sha_filename: "#{repo}_#{version}_sha512-checksums.txt" 17 | filter: 18 | - "entity.system.os == 'linux'" 19 | - "entity.system.arch == 'amd64'" 20 | - "entity.system.platform == 'alpine'" 21 | - platform: "centos6" 22 | arch: "amd64" 23 | asset_filename: "#{repo}_#{version}_centos8_linux_amd64.tar.gz" 24 | sha_filename: "#{repo}_#{version}_sha512-checksums.txt" 25 | filter: 26 | - "entity.system.os == 'linux'" 27 | - "entity.system.arch == 'amd64'" 28 | - "entity.system.platform_family == 'rhel'" 29 | - "entity.system.platform_version.split('.')[0] == '8'" 30 | - platform: "centos7" 31 | arch: "amd64" 32 | asset_filename: "#{repo}_#{version}_centos7_linux_amd64.tar.gz" 33 | sha_filename: "#{repo}_#{version}_sha512-checksums.txt" 34 | filter: 35 | - "entity.system.os == 'linux'" 36 | - "entity.system.arch == 'amd64'" 37 | - "entity.system.platform_family == 'rhel'" 38 | - "entity.system.platform_version.split('.')[0] == '7'" 39 | - platform: "debian" 40 | arch: "amd64" 41 | asset_filename: "#{repo}_#{version}_debian_linux_amd64.tar.gz" 42 | sha_filename: "#{repo}_#{version}_sha512-checksums.txt" 43 | filter: 44 | - "entity.system.os == 'linux'" 45 | - "entity.system.arch == 'amd64'" 46 | - "entity.system.platform_family == 'debian'" 47 | - platform: "debian9" 48 | arch: "amd64" 49 | asset_filename: "#{repo}_#{version}_debian9_linux_amd64.tar.gz" 50 | sha_filename: "#{repo}_#{version}_sha512-checksums.txt" 51 | filter: 52 | - "entity.system.os == 'linux'" 53 | - "entity.system.arch == 'amd64'" 54 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 2 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Pull Request Checklist 2 | 3 | **Is this in reference to an existing issue?** 4 | 5 | #### General 6 | 7 | - [ ] Update Changelog following the conventions laid out [here](https://github.com/sensu-plugins/community/blob/master/HOW_WE_CHANGELOG.md) 8 | 9 | - [ ] Update README with any necessary configuration snippets 10 | 11 | - [ ] Binstubs are created if needed 12 | 13 | - [ ] RuboCop passes 14 | 15 | - [ ] Existing tests pass 16 | 17 | #### New Plugins 18 | 19 | - [ ] Tests 20 | 21 | - [ ] Add the plugin to the README 22 | 23 | - [ ] Does it have a complete header as outlined [here](http://sensu-plugins.io/docs/developer_guidelines.html#coding-style) 24 | 25 | #### Purpose 26 | 27 | #### Known Compatibility Issues 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | .vagrant/* 16 | .DS_Store 17 | .idea/* 18 | *.gem 19 | .kitchen/ 20 | vendor/bundle 21 | -------------------------------------------------------------------------------- /.kitchen.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: 3 | name: docker 4 | use_sudo: false 5 | links: 6 | # TODO: multiple versions at some point 7 | - sensu-elasticsearch-6:sensu-elasticsearch-6 8 | 9 | provisioner: 10 | name: shell 11 | data_path: . 12 | script: test/fixtures/bootstrap.sh 13 | 14 | # verifier: 15 | # ruby_bindir: <%= ENV['MY_RUBY_HOME'] || '/opt/sensu/embedded' %>/bin 16 | verifier: 17 | ruby_bindir: /usr/local/bin 18 | 19 | platforms: 20 | - name: debian-8 21 | 22 | suites: 23 | - name: ruby-20 24 | driver: 25 | image: ruby:2.0-slim 26 | - name: ruby-21 27 | driver: 28 | image: ruby:2.1-slim 29 | - name: ruby-22 30 | driver: 31 | image: ruby:2.2-slim 32 | - name: ruby-230 33 | driver: 34 | image: ruby:2.3.0-slim 35 | - name: ruby-241 36 | driver: 37 | image: ruby:2.4.1-slim 38 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | 2 | MethodLength: 3 | Max: 200 4 | 5 | LineLength: 6 | Max: 160 7 | 8 | AbcSize: 9 | Max: 250 10 | 11 | FileName: 12 | Enabled: false 13 | 14 | PerceivedComplexity: 15 | Enabled: false 16 | 17 | CyclomaticComplexity: 18 | Enabled: false 19 | 20 | ClassLength: 21 | Enabled: false 22 | 23 | IfUnlessModifier: 24 | Enabled: false 25 | 26 | RegexpLiteral: 27 | Enabled: false 28 | 29 | Style/Documentation: 30 | Enabled: false 31 | 32 | Style/Next: 33 | Enabled: false 34 | 35 | Style/FormatString: 36 | Enabled: false 37 | 38 | Style/NestedParenthesizedCalls: 39 | Enabled: false 40 | 41 | Style/GuardClause: 42 | Enabled: false 43 | 44 | Metrics/ModuleLength: 45 | Exclude: 46 | - 'lib/sensu-plugins-elasticsearch/elasticsearch-query.rb' 47 | 48 | # TODO: come cleanup the code that uses this once we figure out what we want to use 49 | Lint/UriEscapeUnescape: 50 | Enabled: false 51 | 52 | # TODO: switch from `DateTime` to `Time` or `Date` 53 | Style/DateTime: 54 | Enabled: false 55 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: true 2 | service: docker 3 | language: ruby 4 | cache: 5 | - bundler 6 | before_install: 7 | - sudo iptables -L DOCKER || ( echo "DOCKER iptables chain missing" ; sudo iptables -N DOCKER ) 8 | - gem install bundler -v 1.15 9 | install: 10 | - bundle install 11 | rvm: 12 | - 2.3.0 13 | - 2.4.1 14 | notifications: 15 | email: 16 | recipients: 17 | - sensu-plugin@sensu-plugins.io 18 | on_success: change 19 | on_failure: always 20 | 21 | before_script: 22 | - docker run --name sensu-elasticsearch-6 -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -d docker.elastic.co/elasticsearch/elasticsearch-oss:6.2.2 23 | - bash -c "[ ! -d bonsai/ ] && git clone https://github.com/sensu/sensu-go-bonsai-asset.git bonsai || echo 'bonsai/ exists, skipping git clone'" 24 | 25 | script: 26 | - gem build sensu-plugins-elasticsearch.gemspec 27 | - gem install sensu-plugins-elasticsearch-*.gem 28 | - bundle exec rake quick 29 | #- bundle exec rake kitchen:ruby-`echo $TRAVIS_RUBY_VERSION | sed -e "s/\.//g"`-debian-8 30 | deploy: 31 | - provider: rubygems 32 | api_key: 33 | secure: lF7CO6Vfmd9+PR4vDdv5E4iL+NuOUacQR19DhBY+TNrz/BSD+ehAlaGMshbFICpZ6BtUKVj+lMlmjiKHEfcKrPuovO4AUE4wRQRiD/LSorBdgM06iJL6mGblrMiyCMfBs0FLkXVdrQRa5AHGY8Assw+JGS58Xrnh1pvttgP6Tyw= 34 | gem: sensu-plugins-elasticsearch 35 | on: 36 | tags: true 37 | all_branches: true 38 | rvm: 2.4.1 39 | repo: sensu-plugins/sensu-plugins-elasticsearch 40 | - provider: script 41 | script: bonsai/ruby-runtime/travis-build-ruby-plugin-assets.sh sensu-plugins-elasticsearch 42 | skip_cleanup: true 43 | on: 44 | tags: true 45 | all_branches: true 46 | rvm: 2.4.1 47 | 48 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | This project adheres to [Semantic Versioning](http://semver.org/). 3 | 4 | This CHANGELOG follows the format listed [here](https://github.com/sensu-plugins/community/blob/master/HOW_WE_CHANGELOG.md) 5 | 6 | ## [Unreleased] 7 | 8 | ## [4.2.2] 2020-12-09 9 | ### Changed 10 | - Replaced CentOS 6 in Bonsai with CentOS 8 11 | 12 | ## [4.2.1] 2020-12-09 13 | ### Fixed 14 | - metrics-es-node-graphite.rb: request URL now constructed correctly when `--cert-file` flag is used 15 | 16 | ## [4.2.0] 2020-05-11 17 | ### Added 18 | - Updated asset build targets to support centos6 19 | - Removed centos from bonsai asset definition 20 | - Updated bundler requirement from '~> 1.7' to '~> 2.1' 21 | 22 | ## [4.1.0] 2020-01-09 23 | ### Changed 24 | - Update rest-client requirement from `= 1.8.0` to `= 2.1.0` (@dependabot-preview) 25 | 26 | ## [4.0.1] - 2019-06-21 27 | ### Fixed 28 | - Fix for JSON.parse in metrics checks commands that inheret from Sensu::Plugin::Metric::CLI::JSON:Class 29 | 30 | ## [4.0.0] - 2019-05-07 31 | ### Breaking Changes 32 | - Bump `sensu-plugin` dependency from `~> 3.0` to `~> 4.0` you can read the changelog entries for [4.0](https://github.com/sensu-plugins/sensu-plugin/blob/master/CHANGELOG.md#400---2018-02-17), [3.0](https://github.com/sensu-plugins/sensu-plugin/blob/master/CHANGELOG.md#300---2018-12-04), and [2.0](https://github.com/sensu-plugins/sensu-plugin/blob/master/CHANGELOG.md#v200---2017-03-29) 33 | 34 | ### Added 35 | - Travis build automation to generate Sensu Asset tarballs that can be used n conjunction with Sensu provided ruby runtime assets and the Bonsai Asset Index 36 | - Require latest sensu-plugin for [Sensu Go support](https://github.com/sensu-plugins/sensu-plugin#sensu-go-enablement) 37 | 38 | ## [3.0.0] - 2018-12-17 39 | ### Breaking Changes 40 | - removed ruby support for versions `< 2.3` (@majormoses) 41 | 42 | ## [2.1.0] - 2018-05-23 43 | ### Added 44 | - check-es-shard-allocation-status.rb, check-es-file-descriptors.rb, check-es-heap.rb: added `--cert-file` option which allows you to specify a ca-cert to be used to verify TLS (@vin01) 45 | 46 | ## [2.0.1] - 2018-03-27 47 | ### Security 48 | - updated yard dependency to `~> 0.9.11` per: https://nvd.nist.gov/vuln/detail/CVE-2017-17042 (@majormoses) 49 | 50 | ## [2.0.0] - 2018-03-07 51 | ### Security 52 | - updated rubocop dependency to `~> 0.51.0` per: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-8418. (@majormoses) 53 | 54 | ### Breaking Changes 55 | - removed ruby `< 2.1` support @majormoses 56 | 57 | ### Changes 58 | - added explicit dependency on `mixlib-shellout` to keep compatibility with ruby `2.1` (@majormoses) 59 | - appeased the cops and created TODOs (@majormoses) 60 | 61 | ## [1.12.0] - 2018-03-01 62 | ### Added 63 | - check-es-indices-field-number.rb: check if the number of fields in index(es) is approaching limit (default to 1000 in ES) (@huynt1979) 64 | ### Changed 65 | - Address a couple of rubocop style violations (@huynt1979) 66 | 67 | ## [1.11.1] - 2018-02-27 68 | ### Changed 69 | - this is a no-op release to test the CI pipeline's ability to publish gems 70 | 71 | ## [1.11.0] - 2018-02-23 72 | ### Added 73 | - bin/check-es-query-average.rb: added flag of '--headers' so that headers can be passed to the elasticsearch http client (@alexandrustaetu) 74 | - bin/check-es-query-average.rb: added flag of '--headers' so that headers can be passed to the elasticsearch http client (@alexandrustaetu) 75 | - bin/check-es-query-average.rb: added flag of '--headers' so that headers can be passed to the elasticsearch http client (@alexandrustaetu) 76 | - bin/check-es-query-average.rb: added flag of '--headers' so that headers can be passed to the elasticsearch http client (@alexandrustaetu) 77 | - lib/sensu-plugins-elasticsearch/elasticsearch-common.rb: collect headers originating from query commands and pass them to the elasticsearch client (@alexandrustaetu) 78 | - integration testing with es6 (@alexandrustaetu) (@majormoses) 79 | 80 | ## [1.10.0] - 2018-02-19 81 | ### Added 82 | - check-es-circuit-breakers.rb, check-es-cluster-status.rb, check-es-indices-sizes.rb, check-es-node-status.rb, metrics-es-cluster.rb, metrics-es-node-graphite.rb, metrics-es-node.rb: added `--cert-file` option which allows you to specify a ca-cert to be used to verify TLS (@csoleimani) (@majormoses) 83 | 84 | ## [1.9.0] - 2018-02-19 85 | ### Changed 86 | - bin/check-es-node-status.rb: updated to check the status of all nodes in a cluster (@cihangirbesiktas) 87 | 88 | ## [1.8.1] - 2018-02-15 89 | ### Fixed 90 | - metrics-es-cluster.rb: Don't try to fetch percolator stats on ES 5+, those stats have been removed (@eheydrick) 91 | 92 | ## [1.8.0] - 2017-11-21 93 | ### Added 94 | - bin/check-es-heap.rb: added support to check heap usage of all nodes in a cluster (@cihangirbesiktas) 95 | - update changelog location guidelines (@majormoses) 96 | 97 | ## [1.7.1] - 2017-09-18 98 | ### Fixed 99 | - bin/check-es-cluster-health.rb and bin/check-es-cluster-status.rb fixed --alert-status failing to alert and allow absent value to alert on any status (@rwky) 100 | 101 | ## [1.7.0] - 2017-09-16 102 | ### Changed 103 | - check-es-cluster-health.rb: changed `--alert_status` to `--alert-status` to be more consistent with conventions, restrict `--alert-status` to the actual acceptable options. (@majormoses) 104 | - check-es-cluster-status.rb: changed `--alert_status` to `--alert-status` to be more consistent with conventions, restrict `--alert-status` to the actual acceptable options. (@majormoses) 105 | 106 | ### Added 107 | - bin/check-es-cluster-health.rb: added option to alert only during a yellow state or only during a red state (@barrebre) 108 | - bin/check-es-cluster-status.rb: added option to alert only during a yellow state or only during a red state (@barrebre) 109 | 110 | ## [1.6.1] - 2017-08-24 111 | ### Fixed 112 | - bin/check-es-query-ratio.rb: added support to define float thresholds (@cgarciaarano) 113 | 114 | ## [1.6.0] - 2017-08-18 115 | ### Added 116 | - bin/check-es-query-ratio.rb: added option to avoid triggering alert if divisor is 0 (@cgarciaarano) 117 | 118 | ## [1.5.3] - 2017-08-17 119 | ### Fixed 120 | - bin/check-es-query-ratio.rb: ratio is performed by a float division, instead of integer division (@cgarciaarano) 121 | 122 | ## [1.5.2] - 2017-08-12 123 | ### Fixed 124 | - check-es-query-ratio.rb: Fix when divisor = 0 (@cgarciaarano) 125 | ## [1.5.1] - 2017-08-03 126 | ### Fixed 127 | - bin/metrics-es-cluster.rb: missing data no longer causes invalid metrics by defaulting to 0 (@TheKevJames) 128 | 129 | ## [1.5.0] - 2017-07-26 130 | ### Added 131 | - check-es-query-average.rb: check of average result by field (@ilavender) 132 | 133 | ## [1.4.1] - 2017-07-13 134 | ### Fixed 135 | - use timestamp_field from config for sorting in Kibana (@osgida) 136 | 137 | ## [1.4.0] - 2017-07-04 138 | ### Added 139 | - added ruby 2.4 testing (@majormoses) 140 | - check-es-shard-allocation-status.rb: HTTP Basic Auth support added (@cihangirbesiktas) 141 | - check-es-shard-allocation-status.rb: timeout option for rest calls (@cihangirbesiktas) 142 | 143 | ### Fixed 144 | - PR template spell "compatibility" correctly. (@majormoses) 145 | 146 | ## [1.3.1] - 2017-05-22 147 | ### Fixed 148 | - Conversion of previous_months option to Seconds (@guptaishabh) 149 | 150 | ## [1.3.0] - 2017-05-08 151 | ### Fixed 152 | - Use strict Base64 encoding to fix base64 encoding/netty issue (@msblum) 153 | 154 | ## [1.2.0] - 2017-05-03 155 | ### Fixed 156 | - metrics-es-cluster.rb: Check to see if cluster key exists in transient_settings before trying to use it. (@RAR) 157 | ### Added 158 | - Add option to run check-es-shard-allocation-status.rb on non master nodes (@Evesy) 159 | - Fixed check-es-shard-allocation-status.rb for Elasticsearch 5.x compatibility (@Evesy) 160 | 161 | ## [1.1.3] - 2017-01-04 162 | ### Fixed 163 | - metrics-es-cluster/metrics-es-node-graphite.rb: Fix Elasticsearch 5.0 compatability (@terjesannum) 164 | 165 | ## [1.1.2] - 2016-12-29 166 | ### Fixed 167 | - Fixed metrics-es-node-graphite.rb was not compatible with Elasticsearch 5.0 (@woqer) 168 | - Make query lib compatible with ES 5+ (@jackfengji) 169 | 170 | ## [1.1.1] - 2016-11-26 171 | ### Fixed 172 | - Fixed check-es-file-descriptors.rb was not compatible with Elasticsearch 5.0 (@woqer) 173 | 174 | ## [1.1.0] - 2016-11-14 175 | ### Changed 176 | - Changed check-es-heap.rb to be compatible with Elasticsearch 5.0 (@christianherro) 177 | 178 | ### Added 179 | - Added check-es-query-ratio.tb to support ratio-type checks (@alcasim) 180 | - Added direct support to check-es-indices-size.rb to delete indicies without the handler-es-delete-indices.rb 181 | 182 | ### Fixed 183 | - aws-sdk 2.5.x breaks aws-es-transport (@sstarcher) 184 | - check-es-indicies-size - fix array mapping by (@nyxcharon) 185 | 186 | ## [1.0.0] - 2016-07-29 187 | ### Added 188 | - Added AWS transport gem and configuration for check-es-query-* sensu calls to use --transport=AWS (@brendangibat) 189 | - Added a rescue for 503 on several checks: (@majormoses) 190 | - check-es-circuit-breakers.rb 191 | - check-es-cluster-status.rb 192 | - check-es-file-descriptors.rb 193 | - check-es-heap.rb 194 | - Added option --localhost for check-es-circuit-breakers.rb to only check its local node for broken circuit (@majormoses) 195 | - Add Ruby 2.3.0 support (@eheydrick) 196 | - Allow using newer patch versions of elasticsearch gem within the same minor (@majormoses) 197 | - Add check-es-cluster-health to check Elasticsearch cluster health and status (@brendangibat) 198 | - Add check-es-indices-size to check if indicies grow above a certain size (@brendangibat) 199 | - Add handler-es-delete-indices handler to delete indicies (@brendangibat) 200 | 201 | ### Removed 202 | - Ruby 1.9.3 support (@eheydrick) 203 | 204 | ### Changed 205 | - Update to Rubocop 0.40 and cleanup (@eheydrick) 206 | 207 | ### Fixed 208 | - check-es-indicies-size.rb - broken for newer updates 209 | 210 | ## [0.5.3] - 2016-04-02 211 | ### Added 212 | - check-es-indexes (check for dup indexes) (Yieldbot) 213 | - check-es-shard-allocation (check ElasticSearch shard allocation persistent and transient settings) (Yieldbot) 214 | - Adding offset flag to allow specifying of a end time offset 215 | - Adding custom timestamp field feature to check-es-query-count and check-es-query-exists 216 | - Added support for https requests (OrbotixInc) 217 | 218 | 219 | ## [0.4.3] - 2016-02-22 220 | ### Fixed 221 | - metrics-es-heap.rb: Assignment of node from the stats variable happened before stats was assigned. Moved node assignment to be after stats assignment. 222 | 223 | ## [0.4.2] - 2016-01-27 224 | ### Added 225 | - metrics-es-cluster.rb: Added i/o cluster stats 226 | 227 | ## [0.4.1] - 2016-01-26 228 | ### Fixed 229 | - metrics-es-cluster.rb: Allow metrics to be gathered even if the cluster has zero documents. Also updated cache name for Elasticsearch 2.0+ 230 | - metrics-es-node-graphite.rb: Update node stats for Elasticsearch 2.0+ 231 | 232 | ## [0.4.0] - 2016-01-22 233 | ### Added 234 | - metrics-es-node-graphite.rb: Added file system and cpu stats 235 | - metrics-es-cluster.rb: Added cluster metrics including optional percolator metrics, allocation status, and option to run on non-master nodes 236 | 237 | ## [0.3.2] - 2015-12-29 238 | ### Changed 239 | - Update metrics-es-node.rb for Elasticsearch 2.0 240 | 241 | ## [0.3.1] - 2015-12-29 242 | ### Changed 243 | - Update metrics-es-node.rb to use version checks consistent with other metrics 244 | - Update metrics-es-cluster.rb to use `_stats` api instead of `/_count?q=*:*` see [Unbound wildcard range query cripples es on larger installs #20](https://github.com/sensu-plugins/sensu-plugins-elasticsearch/issues/20) 245 | 246 | ## [0.3.0] - 2015-11-18 247 | ### Changed 248 | - Update metrics-es-node-graphite.rb, check-es-node-status.rb, and check-es-file-descriptors.rb for Elasticsearch 2.0 249 | - Update elasticsearch gem to 1.0.14 250 | 251 | ### Added 252 | - Add check-es-cluster-health that checks health status with elasticsearch gem and can use AWS transport for checks. 253 | - Add check-es-circuit-breakers.rb, to alert when circuit breakers have been tripped 254 | 255 | ## [0.2.0] - 2015-10-15 256 | ### Changed 257 | - cluster-status check: added a new `status_timeout` option that will use elasticsearch's [`wait_for_status` parameter](https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-health.html#request-params) and wait up to the given number of seconds for the cluster to be green. This pervents false alerting during normal elasticsearch operations. 258 | 259 | ## [0.1.2] - 2015-08-11 260 | ### Added 261 | - add parameters for elasticsearch auth 262 | 263 | ## [0.1.1] - 2015-07-14 264 | ### Changed 265 | - updated sensu-plugin gem to 1.2.0 266 | 267 | ## [0.1.0] - 2015-07-06 268 | ### Added 269 | - `check-es-node-status` node status check 270 | 271 | ### Fixed 272 | - uri resource path for `get_es_resource` method 273 | 274 | ### Changed 275 | - `get_es_resource` URI path needs to start with `/` 276 | - clean cruft from Rakefile 277 | - put deps in alpha order in gemspec 278 | - update documentation links in README and CONTRIBUTING 279 | 280 | ## [0.0.2] - 2015-06-02 281 | ### Fixed 282 | - added binstubs 283 | 284 | ### Changed 285 | - removed cruft from /lib 286 | 287 | ## 0.0.1 - 2015-05-21 288 | ### Added 289 | - initial release 290 | 291 | 292 | [Unreleased]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/4.2.1...HEAD 293 | [4.2.2]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/4.2.1...4.2.2 294 | [4.2.1]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/4.2.0...4.2.1 295 | [4.2.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/4.1.0...4.2.0 296 | [4.1.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/4.0.1...4.1.0 297 | [4.0.1]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/4.0.0...4.0.1 298 | [4.0.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/3.0.0...4.0.0 299 | [3.0.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/2.1.0...3.0.0 300 | [2.1.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/2.0.1...2.1.0 301 | [2.0.1]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/2.0.0...2.0.1 302 | [2.0.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.12.0...2.0.0 303 | [1.12.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.11.1...1.12.0 304 | [1.11.1]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.11.0...1.11.1 305 | [1.11.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.10.0...1.11.0 306 | [1.10.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.9.0...1.10.0 307 | [1.9.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.8.1...1.9.0 308 | [1.8.1]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.8.0...1.8.1 309 | [1.8.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.7.1...1.8.0 310 | [1.7.1]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.7.0...1.7.1 311 | [1.7.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.6.1...1.7.0 312 | [1.6.1]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.6.0...1.6.1 313 | [1.6.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.5.3...1.6.0 314 | [1.5.3]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.5.2...1.5.3 315 | [1.5.2]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.5.1...1.5.2 316 | [1.5.1]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.5.0...1.5.1 317 | [1.5.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.4.1...1.5.0 318 | [1.4.1]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.4.0...1.4.1 319 | [1.4.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.3.1...1.4.0 320 | [1.3.1]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.3.0...1.3.1 321 | [1.3.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.2.0...1.3.0 322 | [1.2.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.1.3...1.2.0 323 | [1.1.3]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.1.2...1.1.3 324 | [1.1.2]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.1.1...1.1.2 325 | [1.1.1]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.1.0...1.1.1 326 | [1.1.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/1.0.0...1.1.0 327 | [1.0.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/0.5.3...1.0.0 328 | [0.5.3]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/0.4.3...0.5.3 329 | [0.4.3]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/0.4.2...0.4.3 330 | [0.4.2]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/0.4.1...0.4.2 331 | [0.4.1]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/0.4.0...0.4.1 332 | [0.4.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/0.3.2...0.4.0 333 | [0.3.2]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/0.3.1...0.3.2 334 | [0.3.1]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/0.3.0...0.3.1 335 | [0.3.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/0.2.0...0.3.0 336 | [0.2.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/0.1.2...0.2.0 337 | [0.1.2]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/0.1.1...0.1.2 338 | [0.1.1]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/0.1.0...0.1.1 339 | [0.1.0]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/0.0.2...0.1.0 340 | [0.0.2]: https://github.com/sensu-plugins/sensu-plugins-elasticsearch/compare/0.0.1...0.0.2 341 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | [Development Documentation](http://sensu-plugins.io/docs/developer_guidelines.html) 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in sensu-plugins-elasticsearch.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Sensu-Plugins 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Sensu-Plugins-elasticsearch 2 | 3 | [![Build Status](https://travis-ci.org/sensu-plugins/sensu-plugins-elasticsearch.svg?branch=master)](https://travis-ci.org/sensu-plugins/sensu-plugins-elasticsearch) 4 | [![Gem Version](https://badge.fury.io/rb/sensu-plugins-elasticsearch.svg)](http://badge.fury.io/rb/sensu-plugins-elasticsearch) 5 | [![Code Climate](https://codeclimate.com/github/sensu-plugins/sensu-plugins-elasticsearch/badges/gpa.svg)](https://codeclimate.com/github/sensu-plugins/sensu-plugins-elasticsearch) 6 | [![Test Coverage](https://codeclimate.com/github/sensu-plugins/sensu-plugins-elasticsearch/badges/coverage.svg)](https://codeclimate.com/github/sensu-plugins/sensu-plugins-elasticsearch) 7 | [![Dependency Status](https://gemnasium.com/sensu-plugins/sensu-plugins-elasticsearch.svg)](https://gemnasium.com/sensu-plugins/sensu-plugins-elasticsearch) 8 | [![Sensu Bonsai Asset](https://img.shields.io/badge/Bonsai-Download%20Me-brightgreen.svg?colorB=89C967&logo=sensu)](https://bonsai.sensu.io/assets/sensu-plugins/sensu-plugins-elasticsearch) 9 | 10 | ## Sensu Asset 11 | The Sensu assets packaged from this repository are built against the Sensu ruby runtime environment. When using these assets as part of a Sensu Go resource (check, mutator or handler), make sure you include the corresponding Sensu ruby runtime asset in the list of assets needed by the resource. The current ruby-runtime assets can be found [here](https://bonsai.sensu.io/assets/sensu/sensu-ruby-runtime) in the [Bonsai Asset Index](bonsai.sensu.io). 12 | 13 | ## Functionality 14 | 15 | ## Files 16 | * /bin/check-es-circuit-breakers.rb 17 | * /bin/check-es-cluster-health.rb 18 | * /bin/check-es-cluster-status.rb 19 | * /bin/check-es-file-descriptors.rb 20 | * /bin/check-es-heap.rb 21 | * /bin/check-es-indices-field-count.rb 22 | * /bin/check-es-indexes.rb 23 | * /bin/check-es-indicies-sizes.rb 24 | * /bin/check-es-node-status.rb 25 | * /bin/check-es-query-count.rb 26 | * /bin/check-es-query-exists.rb 27 | * /bin/check-es-query-ratio.rb 28 | * /bin/check-es-shard-allocation-status.rb 29 | * /bin/handler-es-delete-indices.rb 30 | * /bin/metrics-es-cluster.rb 31 | * /bin/metrics-es-node.rb 32 | * /bin/metrics-es-node-graphite.rb 33 | 34 | ## Usage 35 | 36 | ## Installation 37 | 38 | [Installation and Setup](http://sensu-plugins.io/docs/installation_instructions.html) 39 | 40 | ## Notes 41 | When using `handler-es-delete-indices.rb` with Sensu Go, you will need to use the event mapping commandline option, see `handler-es-delete-indices.rb --help` for details. And please read [the sensu-plugin README](https://github.com/sensu-plugins/sensu-plugin#sensu-go-enablement) for more information on the event mapping functionality. 42 | 43 | ## Testing 44 | 45 | This repository uses the [Kitchen](https://kitchen.ci/) suite for it's tests. 46 | 47 | Note: The test suite uses an elasticsearch instance in order to have passing tests. Execute the following command to create a mock elasticsearch 6 instance: 48 | 49 | ```bash 50 | docker run -d --name sensu-elasticsearch-6 docker.elastic.co/elasticsearch/elasticsearch:6.2.2 51 | ``` 52 | 53 | Running the tests: 54 | 55 | ```bash 56 | bundle install --path vendor/bundle 57 | bundle exec kitchen test 58 | ``` 59 | 60 | You can find sample output for all tests running successfully in [this gist](https://gist.github.com/alexandrustaetu/d19feea1296d2ce7e367542265252d7a). 61 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'github/markup' 3 | require 'redcarpet' 4 | require 'rspec/core/rake_task' 5 | require 'rubocop/rake_task' 6 | require 'yard' 7 | require 'yard/rake/yardoc_task' 8 | require 'English' 9 | require 'kitchen/rake_tasks' 10 | 11 | YARD::Rake::YardocTask.new do |t| 12 | OTHER_PATHS = %w[].freeze 13 | t.files = ['lib/**/*.rb', 'bin/**/*.rb', OTHER_PATHS] 14 | t.options = %w[--markup-provider=redcarpet --markup=markdown --main=README.md --files CHANGELOG.md] 15 | end 16 | 17 | RuboCop::RakeTask.new 18 | 19 | RSpec::Core::RakeTask.new(:spec) do |r| 20 | r.pattern = FileList['**/**/*_spec.rb'] 21 | end 22 | 23 | desc 'Make all plugins executable' 24 | task :make_bin_executable do 25 | `chmod -R +x bin/*` 26 | end 27 | 28 | desc 'Test for binstubs' 29 | task :check_binstubs do 30 | bin_list = Gem::Specification.load('sensu-plugins-elasticsearch.gemspec').executables 31 | bin_list.each do |b| 32 | `which #{ b }` 33 | unless $CHILD_STATUS.success? 34 | puts "#{b} was not a binstub" 35 | exit 36 | end 37 | end 38 | end 39 | 40 | Kitchen::RakeTasks.new 41 | 42 | desc 'Alias for kitchen:all' 43 | task integration: 'kitchen:all' 44 | 45 | task default: %i[make_bin_executable yard rubocop check_binstubs integration] 46 | task quick: %i[make_bin_executable yard rubocop check_binstubs] 47 | -------------------------------------------------------------------------------- /bin/check-es-circuit-breakers.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # check-es-circuit-breakers 4 | # 5 | # DESCRIPTION: 6 | # This plugin checks whether the ElasticSearch circuit breakers have been tripped, 7 | # using the node stats API. 8 | # Works with ES 0.9x and ES 1.x 9 | # 10 | # OUTPUT: 11 | # plain text 12 | # 13 | # PLATFORMS: 14 | # Linux 15 | # 16 | # DEPENDENCIES: 17 | # gem: sensu-plugin 18 | # gem: rest-client 19 | # 20 | # USAGE: 21 | # check-es-circuit-breakers --help 22 | # 23 | # NOTES: 24 | # 25 | # LICENSE: 26 | # Copyright 2012 Sonian, Inc 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 'rest-client' 33 | require 'json' 34 | require 'base64' 35 | 36 | class ESCircuitBreaker < Sensu::Plugin::Check::CLI 37 | option :host, 38 | description: 'Elasticsearch host', 39 | short: '-h HOST', 40 | long: '--host HOST', 41 | default: 'localhost' 42 | 43 | option :port, 44 | description: 'Elasticsearch port', 45 | short: '-p PORT', 46 | long: '--port PORT', 47 | proc: proc(&:to_i), 48 | default: 9200 49 | 50 | option :timeout, 51 | description: 'Sets the connection timeout for REST client', 52 | short: '-t SECS', 53 | long: '--timeout SECS', 54 | proc: proc(&:to_i), 55 | default: 30 56 | 57 | option :user, 58 | description: 'Elasticsearch User', 59 | short: '-u USER', 60 | long: '--user USER' 61 | 62 | option :password, 63 | description: 'Elasticsearch Password', 64 | short: '-P PASS', 65 | long: '--password PASS' 66 | 67 | option :https, 68 | description: 'Enables HTTPS', 69 | short: '-e', 70 | long: '--https' 71 | 72 | option :cert_file, 73 | description: 'Cert file to use', 74 | long: '--cert-file CERT' 75 | 76 | option :localhost, 77 | description: 'only check local node', 78 | short: '-l', 79 | long: '--localhost', 80 | boolean: true, 81 | default: false 82 | 83 | def get_es_resource(resource) 84 | headers = {} 85 | if config[:user] && config[:password] 86 | auth = 'Basic ' + Base64.strict_encode64("#{config[:user]}:#{config[:password]}").chomp 87 | headers = { 'Authorization' => auth } 88 | end 89 | 90 | protocol = if config[:https] 91 | 'https' 92 | else 93 | 'http' 94 | end 95 | 96 | r = if config[:cert_file] 97 | RestClient::Resource.new("#{protocol}://#{config[:host]}:#{config[:port]}#{resource}", 98 | ssl_ca_file: config[:cert_file].to_s, 99 | timeout: config[:timeout], 100 | headers: headers) 101 | else 102 | RestClient::Resource.new("#{protocol}://#{config[:host]}:#{config[:port]}#{resource}", 103 | timeout: config[:timeout], 104 | headers: headers) 105 | end 106 | JSON.parse(r.get) 107 | rescue Errno::ECONNREFUSED 108 | critical 'Connection refused' 109 | rescue RestClient::RequestTimeout 110 | critical 'Connection timed out' 111 | rescue RestClient::ServiceUnavailable 112 | warning 'Service is unavailable' 113 | rescue Errno::ECONNRESET 114 | critical 'Connection reset by peer' 115 | end 116 | 117 | def breaker_status 118 | breakers = {} 119 | status = if config[:localhost] 120 | get_es_resource('/_nodes/_local/stats/breaker') 121 | else 122 | get_es_resource('/_nodes/stats/breaker') 123 | end 124 | status['nodes'].each_pair do |_node, stat| 125 | host = stat['host'] 126 | breakers[host] = {} 127 | breakers[host]['breakers'] = [] 128 | stat.each_pair do |key, val| 129 | if key == 'breakers' 130 | val.each_pair do |bk, bv| 131 | if bv['tripped'] != 0 132 | breakers[host]['breakers'] << bk 133 | end 134 | end 135 | end 136 | end 137 | end 138 | breakers 139 | end 140 | 141 | def run 142 | breakers = breaker_status 143 | tripped = false 144 | breakers.each_pair { |_k, v| tripped = true unless v['breakers'].empty? } 145 | if tripped 146 | critical "Circuit Breakers: #{breakers.each_pair { |k, _v| k }} trippped!" 147 | else 148 | ok 'All circuit breakers okay' 149 | end 150 | end 151 | end 152 | -------------------------------------------------------------------------------- /bin/check-es-cluster-health.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # check-es-cluster-health 4 | # 5 | # DESCRIPTION: 6 | # This plugin checks the ElasticSearch cluster health and status. 7 | # 8 | # OUTPUT: 9 | # plain text 10 | # 11 | # PLATFORMS: 12 | # Linux 13 | # 14 | # DEPENDENCIES: 15 | # gem: sensu-plugin 16 | # gem: elasticsearch 17 | # gem: aws_es_transport 18 | # 19 | # USAGE: 20 | # Checks against the ElasticSearch api for cluster health using the 21 | # elasticsearch gem 22 | # 23 | # NOTES: 24 | # 25 | # LICENSE: 26 | # Brendan Gibat 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 | # 37 | # ES Cluster Health 38 | # 39 | class ESClusterHealth < Sensu::Plugin::Check::CLI 40 | include ElasticsearchCommon 41 | 42 | option :transport, 43 | long: '--transport TRANSPORT', 44 | description: 'Transport to use to communicate with ES. Use "AWS" for signed AWS transports.' 45 | 46 | option :region, 47 | long: '--region REGION', 48 | description: 'Region (necessary for AWS Transport)' 49 | 50 | option :host, 51 | description: 'Elasticsearch host', 52 | short: '-h HOST', 53 | long: '--host HOST', 54 | default: 'localhost' 55 | 56 | option :level, 57 | description: 'Level of detail to check returend information ("cluster", "indices", "shards").', 58 | short: '-l LEVEL', 59 | long: '--level LEVEL' 60 | 61 | option :local, 62 | description: 'Return local information, do not retrieve the state from master node.', 63 | long: '--local', 64 | boolean: true 65 | 66 | option :port, 67 | description: 'Elasticsearch port', 68 | short: '-p PORT', 69 | long: '--port PORT', 70 | proc: proc(&:to_i), 71 | default: 9200 72 | 73 | option :scheme, 74 | description: 'Elasticsearch connection scheme, defaults to https for authenticated connections', 75 | short: '-s SCHEME', 76 | long: '--scheme SCHEME' 77 | 78 | option :password, 79 | description: 'Elasticsearch connection password', 80 | short: '-P PASSWORD', 81 | long: '--password PASSWORD' 82 | 83 | option :user, 84 | description: 'Elasticsearch connection user', 85 | short: '-u USER', 86 | long: '--user USER' 87 | 88 | option :timeout, 89 | description: 'Elasticsearch query timeout in seconds', 90 | short: '-t TIMEOUT', 91 | long: '--timeout TIMEOUT', 92 | proc: proc(&:to_i), 93 | default: 30 94 | 95 | option :alert_status, 96 | description: 'Only alert when status matches given RED/YELLOW/GREEN or if blank all statuses', 97 | long: '--alert-status STATUS', 98 | default: '', 99 | in: ['RED', 'YELLOW', 'GREEN', ''] 100 | 101 | def run 102 | options = {} 103 | unless config[:level].nil? 104 | options[:level] = config[:level] 105 | end 106 | unless config[:local].nil? 107 | options[:local] = config[:local] 108 | end 109 | unless config[:index].nil? 110 | options[:index] = config[:index] 111 | end 112 | health = client.cluster.health options 113 | case health['status'] 114 | when 'yellow' 115 | if ['YELLOW', ''].include? config[:alert_status] 116 | warning 'Cluster state is Yellow' 117 | else 118 | ok 'Not alerting on yellow' 119 | end 120 | when 'red' 121 | if ['RED', ''].include? config[:alert_status] 122 | critical 'Cluster state is Red' 123 | else 124 | ok 'Not alerting on red' 125 | end 126 | when 'green' 127 | ok 128 | else 129 | unknown "Cluster state is in an unknown health: #{health['status']}" 130 | end 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /bin/check-es-cluster-status.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # check-es-cluster-status 4 | # 5 | # DESCRIPTION: 6 | # This plugin checks the ElasticSearch cluster status, using its API. 7 | # Works with ES 0.9x and ES 1.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 | # #YELLOW 21 | # 22 | # NOTES: 23 | # 24 | # LICENSE: 25 | # Copyright 2012 Sonian, Inc 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 | # 36 | # ES Cluster Status 37 | # 38 | class ESClusterStatus < Sensu::Plugin::Check::CLI 39 | option :host, 40 | description: 'Elasticsearch host', 41 | short: '-h HOST', 42 | long: '--host HOST', 43 | default: 'localhost' 44 | 45 | option :port, 46 | description: 'Elasticsearch port', 47 | short: '-p PORT', 48 | long: '--port PORT', 49 | proc: proc(&:to_i), 50 | default: 9200 51 | 52 | option :master_only, 53 | description: 'Use master Elasticsearch server only', 54 | short: '-m', 55 | long: '--master-only', 56 | default: false 57 | 58 | option :timeout, 59 | description: 'Sets the connection timeout for REST client', 60 | short: '-t SECS', 61 | long: '--timeout SECS', 62 | proc: proc(&:to_i), 63 | default: 30 64 | 65 | option :status_timeout, 66 | description: 'Sets the time to wait for the cluster status to be green', 67 | short: '-T SECS', 68 | long: '--status_timeout SECS', 69 | proc: proc(&:to_i) 70 | 71 | option :user, 72 | description: 'Elasticsearch User', 73 | short: '-u USER', 74 | long: '--user USER' 75 | 76 | option :password, 77 | description: 'Elasticsearch Password', 78 | short: '-P PASS', 79 | long: '--password PASS' 80 | 81 | option :https, 82 | description: 'Enables HTTPS', 83 | short: '-e', 84 | long: '--https' 85 | 86 | option :cert_file, 87 | description: 'Cert file to use', 88 | long: '--cert-file CERT_FILE' 89 | 90 | option :alert_status, 91 | description: 'Only alert when status matches given RED/YELLOW/GREEN or if blank all statuses', 92 | long: '--alert-status STATUS', 93 | default: '', 94 | in: ['RED', 'YELLOW', 'GREEN', ''] 95 | 96 | def get_es_resource(resource) 97 | headers = {} 98 | if config[:user] && config[:password] 99 | auth = 'Basic ' + Base64.strict_encode64("#{config[:user]}:#{config[:password]}").chomp 100 | headers = { 'Authorization' => auth } 101 | end 102 | 103 | protocol = if config[:https] 104 | 'https' 105 | else 106 | 'http' 107 | end 108 | 109 | r = if config[:cert_file] 110 | RestClient::Resource.new("#{protocol}://#{config[:host]}:#{config[:port]}#{resource}", 111 | ssl_ca_file: config[:cert_file].to_s, 112 | timeout: config[:timeout], 113 | headers: headers) 114 | else 115 | RestClient::Resource.new("#{protocol}://#{config[:host]}:#{config[:port]}#{resource}", 116 | timeout: config[:timeout], 117 | headers: headers) 118 | end 119 | JSON.parse(r.get) 120 | rescue Errno::ECONNREFUSED 121 | critical 'Connection refused' 122 | rescue RestClient::RequestTimeout 123 | critical 'Connection timed out' 124 | rescue RestClient::ServiceUnavailable 125 | critical 'Service is unavailable' 126 | rescue Errno::ECONNRESET 127 | critical 'Connection reset by peer' 128 | end 129 | 130 | def acquire_es_version 131 | info = get_es_resource('/') 132 | info['version']['number'] 133 | end 134 | 135 | def master? 136 | if Gem::Version.new(acquire_es_version) >= Gem::Version.new('1.0.0') 137 | master = get_es_resource('/_cluster/state/master_node')['master_node'] 138 | local = get_es_resource('/_nodes/_local') 139 | else 140 | master = get_es_resource('/_cluster/state?filter_routing_table=true&filter_metadata=true&filter_indices=true')['master_node'] 141 | local = get_es_resource('/_cluster/nodes/_local') 142 | end 143 | local['nodes'].keys.first == master 144 | end 145 | 146 | def acquire_status 147 | health = if config[:status_timeout] 148 | get_es_resource("/_cluster/health?wait_for_status=green&timeout=#{config[:status_timeout]}s") 149 | else 150 | get_es_resource('/_cluster/health') 151 | end 152 | health['status'].downcase 153 | end 154 | 155 | def run 156 | if !config[:master_only] || master? 157 | case acquire_status 158 | when 'green' 159 | ok 'Cluster is green' 160 | when 'yellow' 161 | if ['YELLOW', ''].include? config[:alert_status] 162 | warning 'Cluster state is Yellow' 163 | else 164 | ok 'Not alerting on yellow' 165 | end 166 | when 'red' 167 | if ['RED', ''].include? config[:alert_status] 168 | critical 'Cluster state is Red' 169 | else 170 | ok 'Not alerting on red' 171 | end 172 | end 173 | else 174 | ok 'Not the master' 175 | end 176 | end 177 | end 178 | -------------------------------------------------------------------------------- /bin/check-es-file-descriptors.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # check-es-file-descriptors 4 | # 5 | # DESCRIPTION: 6 | # This plugin checks the ElasticSearch file descriptor usage, using its API. 7 | # 8 | # OUTPUT: 9 | # plain text 10 | # 11 | # PLATFORMS: 12 | # Linux 13 | # 14 | # DEPENDENCIES: 15 | # gem: sensu-plugin 16 | # gem: rest-client 17 | # 18 | # USAGE: 19 | # #YELLOW 20 | # 21 | # NOTES: 22 | # 23 | # LICENSE: 24 | # Author: S. Zachariah Sprackett 25 | # Released under the same terms as Sensu (the MIT license); see LICENSE 26 | # for details. 27 | # 28 | 29 | require 'sensu-plugin/check/cli' 30 | require 'rest-client' 31 | require 'json' 32 | require 'base64' 33 | 34 | # 35 | # ES File Descriptiors 36 | # 37 | class ESFileDescriptors < Sensu::Plugin::Check::CLI 38 | option :host, 39 | description: 'Elasticsearch host', 40 | short: '-h HOST', 41 | long: '--host HOST', 42 | default: 'localhost' 43 | 44 | option :port, 45 | description: 'Elasticsearch port', 46 | short: '-p PORT', 47 | long: '--port PORT', 48 | proc: proc(&:to_i), 49 | default: 9200 50 | 51 | option :timeout, 52 | description: 'Sets the connection timeout for REST client', 53 | short: '-t SECS', 54 | long: '--timeout SECS', 55 | proc: proc(&:to_i), 56 | default: 30 57 | 58 | option :critical, 59 | description: 'Critical percentage of FD usage', 60 | short: '-c PERCENTAGE', 61 | proc: proc(&:to_i), 62 | default: 90 63 | 64 | option :warning, 65 | description: 'Warning percentage of FD usage', 66 | short: '-w PERCENTAGE', 67 | proc: proc(&:to_i), 68 | default: 80 69 | 70 | option :user, 71 | description: 'Elasticsearch User', 72 | short: '-u USER', 73 | long: '--user USER' 74 | 75 | option :password, 76 | description: 'Elasticsearch Password', 77 | short: '-P PASS', 78 | long: '--password PASS' 79 | 80 | option :https, 81 | description: 'Enables HTTPS', 82 | short: '-e', 83 | long: '--https' 84 | 85 | option :cert_file, 86 | description: 'Cert file to use', 87 | long: '--cert-file CERT' 88 | 89 | def get_es_resource(resource) 90 | headers = {} 91 | if config[:user] && config[:password] 92 | auth = 'Basic ' + Base64.strict_encode64("#{config[:user]}:#{config[:password]}").chomp 93 | headers = { 'Authorization' => auth } 94 | end 95 | 96 | protocol = if config[:https] 97 | 'https' 98 | else 99 | 'http' 100 | end 101 | 102 | r = if config[:cert_file] 103 | RestClient::Resource.new("#{protocol}://#{config[:host]}:#{config[:port]}#{resource}", 104 | ssl_ca_file: config[:cert_file].to_s, 105 | timeout: config[:timeout], 106 | headers: headers) 107 | else 108 | RestClient::Resource.new("#{protocol}://#{config[:host]}:#{config[:port]}#{resource}", 109 | timeout: config[:timeout], 110 | headers: headers) 111 | end 112 | JSON.parse(r.get) 113 | rescue Errno::ECONNREFUSED 114 | warning 'Connection refused' 115 | rescue RestClient::RequestTimeout 116 | warning 'Connection timed out' 117 | rescue RestClient::ServiceUnavailable 118 | warning 'Service is unavailable' 119 | end 120 | 121 | def acquire_es_version 122 | info = get_es_resource('/') 123 | info['version']['number'] 124 | end 125 | 126 | def es_version 127 | @es_version ||= Gem::Version.new(acquire_es_version) 128 | end 129 | 130 | def acquire_open_fds 131 | stats = if es_version < Gem::Version.new('5.0.0') 132 | get_es_resource('/_nodes/_local/stats?process=true') 133 | else 134 | get_es_resource('/_nodes/_local/stats/process') 135 | end 136 | begin 137 | keys = stats['nodes'].keys 138 | stats['nodes'][keys[0]]['process']['open_file_descriptors'].to_i 139 | rescue NoMethodError 140 | warning 'Failed to retrieve open_file_descriptors' 141 | end 142 | end 143 | 144 | def acquire_max_fds 145 | info = if es_version < Gem::Version.new('2.0.0') 146 | get_es_resource('/_nodes/_local?process=true') 147 | elsif es_version < Gem::Version.new('5.0.0') 148 | get_es_resource('/_nodes/_local/stats?process=true') 149 | else 150 | get_es_resource('/_nodes/_local/stats/process') 151 | end 152 | begin 153 | keys = info['nodes'].keys 154 | info['nodes'][keys[0]]['process']['max_file_descriptors'].to_i 155 | rescue NoMethodError 156 | warning 'Failed to retrieve max_file_descriptors' 157 | end 158 | end 159 | 160 | def run 161 | open = acquire_open_fds 162 | max = acquire_max_fds 163 | used_percent = ((open.to_f / max.to_f) * 100).to_i 164 | 165 | if used_percent >= config[:critical] 166 | critical "fd usage #{used_percent}% exceeds #{config[:critical]}% (#{open}/#{max})" 167 | elsif used_percent >= config[:warning] 168 | warning "fd usage #{used_percent}% exceeds #{config[:warning]}% (#{open}/#{max})" 169 | else 170 | ok "fd usage at #{used_percent}% (#{open}/#{max})" 171 | end 172 | end 173 | end 174 | -------------------------------------------------------------------------------- /bin/check-es-heap.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # check-es-heap 4 | # 5 | # DESCRIPTION: 6 | # This plugin checks ElasticSearch's Java heap usage using its API. 7 | # 8 | # OUTPUT: 9 | # plain text 10 | # 11 | # PLATFORMS: 12 | # Linux 13 | # 14 | # DEPENDENCIES: 15 | # gem: sensu-plugin 16 | # gem: rest-client 17 | # 18 | # USAGE: 19 | # example commands 20 | # 21 | # NOTES: 22 | # 23 | # LICENSE: 24 | # Copyright 2012 Sonian, Inc 25 | # Released under the same terms as Sensu (the MIT license); see LICENSE 26 | # for details. 27 | # 28 | 29 | require 'sensu-plugin/check/cli' 30 | require 'rest-client' 31 | require 'json' 32 | require 'base64' 33 | 34 | # 35 | # ES Heap 36 | # 37 | class ESHeap < Sensu::Plugin::Check::CLI 38 | option :host, 39 | description: 'Elasticsearch host', 40 | short: '-h HOST', 41 | long: '--host HOST', 42 | default: 'localhost' 43 | 44 | option :port, 45 | description: 'Elasticsearch port', 46 | short: '-p PORT', 47 | long: '--port PORT', 48 | proc: proc(&:to_i), 49 | default: 9200 50 | 51 | option :warn, 52 | short: '-w N', 53 | long: '--warn N', 54 | description: 'Heap used in bytes WARNING threshold', 55 | proc: proc(&:to_i), 56 | default: 0 57 | 58 | option :timeout, 59 | description: 'Sets the connection timeout for REST client', 60 | short: '-t SECS', 61 | long: '--timeout SECS', 62 | proc: proc(&:to_i), 63 | default: 30 64 | 65 | option :crit, 66 | short: '-c N', 67 | long: '--crit N', 68 | description: 'Heap used in bytes CRITICAL threshold', 69 | proc: proc(&:to_i), 70 | default: 0 71 | 72 | option :percentage, 73 | short: '-P', 74 | long: '--percentage', 75 | description: 'Use the WARNING and CRITICAL threshold numbers as percentage indicators of the total heap available', 76 | default: false 77 | 78 | option :user, 79 | description: 'Elasticsearch User', 80 | short: '-u USER', 81 | long: '--user USER' 82 | 83 | option :password, 84 | description: 'Elasticsearch Password', 85 | short: '-W PASS', 86 | long: '--password PASS' 87 | 88 | option :https, 89 | description: 'Enables HTTPS', 90 | short: '-e', 91 | long: '--https' 92 | 93 | option :cert_file, 94 | description: 'Cert file to use', 95 | long: '--cert-file CERT' 96 | 97 | option :all, 98 | description: 'Check all nodes in the ES cluster', 99 | short: '-a', 100 | long: '--all', 101 | default: false 102 | 103 | def acquire_es_version 104 | info = acquire_es_resource('/') 105 | info['version']['number'] 106 | end 107 | 108 | def acquire_es_resource(resource) 109 | headers = {} 110 | if config[:user] && config[:password] 111 | auth = 'Basic ' + Base64.strict_encode64("#{config[:user]}:#{config[:password]}").chomp 112 | headers = { 'Authorization' => auth } 113 | end 114 | 115 | protocol = if config[:https] 116 | 'https' 117 | else 118 | 'http' 119 | end 120 | 121 | r = if config[:cert_file] 122 | RestClient::Resource.new("#{protocol}://#{config[:host]}:#{config[:port]}#{resource}", 123 | ssl_ca_file: config[:cert_file].to_s, 124 | timeout: config[:timeout], 125 | headers: headers) 126 | else 127 | RestClient::Resource.new("#{protocol}://#{config[:host]}:#{config[:port]}#{resource}", 128 | timeout: config[:timeout], 129 | headers: headers) 130 | end 131 | JSON.parse(r.get) 132 | rescue Errno::ECONNREFUSED 133 | warning 'Connection refused' 134 | rescue RestClient::RequestTimeout 135 | warning 'Connection timed out' 136 | rescue RestClient::ServiceUnavailable 137 | warning 'Service is unavailable' 138 | rescue JSON::ParserError 139 | warning 'Elasticsearch API returned invalid JSON' 140 | end 141 | 142 | def acquire_stats 143 | if Gem::Version.new(acquire_es_version) >= Gem::Version.new('1.0.0') 144 | if config[:all] 145 | acquire_es_resource('/_nodes/stats') 146 | else 147 | acquire_es_resource('/_nodes/_local/stats') 148 | end 149 | elsif config[:all] 150 | acquire_es_resource('/_cluster/nodes/stats') 151 | else 152 | acquire_es_resource('/_cluster/nodes/_local/stats') 153 | end 154 | end 155 | 156 | def acquire_heap_data(node) 157 | return node['jvm']['mem']['heap_used_in_bytes'], node['jvm']['mem']['heap_max_in_bytes'] 158 | rescue StandardError 159 | warning 'Failed to obtain heap used in bytes' 160 | end 161 | 162 | def acquire_heap_usage(heap_used, heap_max, node_name) 163 | if config[:percentage] 164 | heap_usage = ((100 * heap_used) / heap_max).to_i 165 | output = if config[:all] 166 | "Node #{node_name}: Heap used in bytes #{heap_used} (#{heap_usage}% full)\n" 167 | else 168 | "Heap used in bytes #{heap_used} (#{heap_usage}% full)" 169 | end 170 | else 171 | heap_usage = heap_used 172 | output = config[:all] ? "Node #{node_name}: Heap used in bytes #{heap_used}\n" : "Heap used in bytes #{heap_used}" 173 | end 174 | [heap_usage, output] 175 | end 176 | 177 | def run 178 | stats = acquire_stats 179 | status = { crit: '', warn: '', ok: '' } 180 | 181 | # Check all the nodes in the cluster, alert if any of the nodes have heap usage above thresholds 182 | stats['nodes'].each_value do |node| 183 | heap_used, heap_max = acquire_heap_data(node) 184 | heap_usage, output = acquire_heap_usage(heap_used, heap_max, node['name']) 185 | if heap_usage >= config[:crit] 186 | status[:crit] += output 187 | elsif heap_usage >= config[:warn] 188 | status[:warn] += output 189 | elsif !config[:all] 190 | status[:ok] += output 191 | end 192 | end 193 | 194 | if !status[:crit].empty? 195 | message status[:crit] 196 | critical 197 | elsif !status[:warn].empty? 198 | message status[:warn] 199 | warning 200 | else 201 | message status[:ok] 202 | ok 203 | end 204 | end 205 | end 206 | -------------------------------------------------------------------------------- /bin/check-es-indexes.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # Check Elastic Search Indexes 4 | # === 5 | # 6 | # DESCRIPTION: 7 | # This plugin will check a a node for dupe indexes 8 | # 9 | # OUTPUT: 10 | # plain-text 11 | # 12 | # PLATFORMS: 13 | # Linux 14 | # 15 | # DEPENDENCIES: 16 | # gem: sensu-plugin 17 | # 18 | # needs usage 19 | # USAGE: 20 | # 21 | # NOTES: 22 | # 23 | # LICENSE: 24 | # Copyright 2014 Yieldbot, Inc 25 | # Released under the same terms as Sensu (the MIT license); see LICENSE 26 | # for details. 27 | # 28 | 29 | require 'sensu-plugin/check/cli' 30 | 31 | # 32 | # == Check Elastic Search Cluster Index 33 | # 34 | class CheckESClusterIndex < Sensu::Plugin::Check::CLI 35 | option :cluster, 36 | description: 'Array of clusters to check', 37 | short: '-C CLUSTER[,CLUSTER]', 38 | long: '--cluster CLUSTER[,CLUSTER]', 39 | proc: proc { |a| a.split(',') } 40 | 41 | option :ignore, 42 | description: 'Comma separated list of indexes to ignore', 43 | short: '-i INDEX[,INDEX]', 44 | long: '--ignore INDEX[,INDEX]', 45 | proc: proc { |a| a.split(',') } 46 | 47 | option :debug, 48 | description: 'Debug', 49 | short: '-d', 50 | long: '--debug' 51 | 52 | def run 53 | # If only one cluster is given, no need to check the indexes 54 | ok 'All indexes are unique' if config[:cluster].length == 1 55 | 56 | port = ':9200' 57 | cmd = '/_cat/indices?v | tail -n +2' 58 | 59 | valid_index = {} 60 | dupe_index = {} 61 | config[:cluster].each do |u| 62 | index_arr = `curl -s #{ u }#{ port }#{ cmd }`.split("\n") 63 | index_arr.each do |t| 64 | t = t.split[1] 65 | 66 | # If the index is in the ignore list, go to the next one 67 | next if config[:ignore].include? t 68 | 69 | if valid_index.key?(t) 70 | dupe_index[t] = [] unless dupe_index[t].is_a?(Array) 71 | dupe_index[t] << u 72 | dupe_index[t] << valid_index[t] unless dupe_index[t] 73 | .include?(valid_index[t]) 74 | else 75 | valid_index[t] = [] unless valid_index[t].is_a?(Array) 76 | valid_index[t] << u 77 | end 78 | end 79 | end 80 | 81 | if dupe_index.count > 0 82 | dupe_index.each do |k, v| 83 | critical "#{k} is on #{v}" 84 | end 85 | else 86 | ok 'All indexes are unique' 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /bin/check-es-indices-field-count.rb: -------------------------------------------------------------------------------- 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 -p 9200 26 | # -i , --types -w -c 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 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 specied 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 195 | -------------------------------------------------------------------------------- /bin/check-es-indices-sizes.rb: -------------------------------------------------------------------------------- 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 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: '^(?.*)-(?\d\d\d\d)\.(?\d\d?).(?\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 200 | -------------------------------------------------------------------------------- /bin/check-es-node-status.rb: -------------------------------------------------------------------------------- 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 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 138 | -------------------------------------------------------------------------------- /bin/check-es-query-average.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # check-es-query 4 | # 5 | # DESCRIPTION: 6 | # This plugin checks an ElasticSearch query. 7 | # 8 | # OUTPUT: 9 | # plain text 10 | # 11 | # PLATFORMS: 12 | # Linux 13 | # 14 | # DEPENDENCIES: 15 | # gem: sensu-plugin 16 | # gem: elasticsearch 17 | # gem: aws_es_transport 18 | # 19 | # USAGE: 20 | # This example checks that the average of the field "serve_time" in logs matching a query of 21 | # anything (*) at the host elasticsearch.service.consul for the past 90 minutes 22 | # will warn if above 120ms and go critical if the result average is above 250ms 23 | # check-es-query-average.rb -h elasticsearch.service.consul -q "*" 24 | # --types special_type -d 'logging-%Y.%m.%d' --minutes-previous 90 -p 9200 -c 0.250 -w 0.120 -a serve_time 25 | # 26 | # NOTES: 27 | # 28 | # LICENSE: 29 | # Itamar Lavender 30 | # Released under the same terms as Sensu (the MIT license); see LICENSE 31 | # for details. 32 | # 33 | 34 | require 'sensu-plugin/check/cli' 35 | require 'elasticsearch' 36 | require 'time' 37 | require 'uri' 38 | require 'aws_es_transport' 39 | require 'sensu-plugins-elasticsearch' 40 | 41 | # 42 | # ES Query Count 43 | # 44 | class ESQueryAverage < Sensu::Plugin::Check::CLI 45 | include ElasticsearchCommon 46 | 47 | option :index, 48 | description: 'Elasticsearch indices to query. 49 | Comma-separated list of index names to search. 50 | Use `_all` or empty string to perform the operation on all indices. 51 | Accepts wildcards', 52 | short: '-i INDEX', 53 | long: '--indices INDEX' 54 | 55 | option :transport, 56 | long: '--transport TRANSPORT', 57 | description: 'Transport to use to communicate with ES. Use "AWS" for signed AWS transports.' 58 | 59 | option :region, 60 | long: '--region REGION', 61 | description: 'Region (necessary for AWS Transport)' 62 | 63 | option :types, 64 | description: 'Elasticsearch types to limit searches to, comma separated list.', 65 | long: '--types TYPES' 66 | 67 | option :timestamp_field, 68 | description: 'Field to use instead of @timestamp for query.', 69 | long: '--timestamp-field FIELD_NAME', 70 | default: '@timestamp' 71 | 72 | option :offset, 73 | description: 'Seconds before offset to end @timestamp against query.', 74 | long: '--offset OFFSET', 75 | proc: proc(&:to_i), 76 | default: 0 77 | 78 | option :ignore_unavailable, 79 | description: 'Ignore unavailable indices.', 80 | long: '--ignore-unavailable', 81 | boolean: true, 82 | default: true 83 | 84 | option :minutes_previous, 85 | description: 'Minutes before offset to check @timestamp against query.', 86 | long: '--minutes-previous MINUTES_PREVIOUS', 87 | proc: proc(&:to_i), 88 | default: 0 89 | 90 | option :hours_previous, 91 | description: 'Hours before offset to check @timestamp against query.', 92 | long: '--hours-previous HOURS_PREVIOUS', 93 | proc: proc(&:to_i), 94 | default: 0 95 | 96 | option :days_previous, 97 | description: 'Days before offset to check @timestamp against query.', 98 | long: '--days-previous DAYS_PREVIOUS', 99 | proc: proc(&:to_i), 100 | default: 0 101 | 102 | option :weeks_previous, 103 | description: 'Weeks before offset to check @timestamp against query.', 104 | long: '--weeks-previous WEEKS_PREVIOUS', 105 | proc: proc(&:to_i), 106 | default: 0 107 | 108 | option :months_previous, 109 | description: 'Months before offset to check @timestamp against query.', 110 | long: '--months-previous MONTHS_PREVIOUS', 111 | proc: proc(&:to_i), 112 | default: 0 113 | 114 | option :date_index, 115 | description: 'Elasticsearch time based index. 116 | Accepts format from http://ruby-doc.org/core-2.2.0/Time.html#method-i-strftime', 117 | short: '-d DATE_INDEX', 118 | long: '--date-index DATE_INDEX' 119 | 120 | option :date_repeat_daily, 121 | description: 'Elasticsearch date based index repeats daily.', 122 | long: '--repeat-daily', 123 | boolean: true, 124 | default: true 125 | 126 | option :date_repeat_hourly, 127 | description: 'Elasticsearch date based index repeats hourly.', 128 | long: '--repeat-hourly', 129 | boolean: true, 130 | default: false 131 | 132 | option :search_field, 133 | description: 'The Elasticsearch document field to search for your query string.', 134 | short: '-f FIELD', 135 | long: '--field FIELD', 136 | required: false, 137 | default: 'message' 138 | 139 | option :query, 140 | description: 'Elasticsearch query', 141 | short: '-q QUERY', 142 | long: '--query QUERY', 143 | required: false 144 | 145 | option :aggr, 146 | description: 'Elasticsearch query aggr', 147 | long: '--aggr', 148 | boolean: true, 149 | required: false, 150 | default: true 151 | 152 | option :aggr_field, 153 | description: 'Elasticsearch query field to aggregate and average from', 154 | short: '-a FIELD', 155 | long: '--aggr-field FIELD', 156 | required: true 157 | 158 | option :host, 159 | description: 'Elasticsearch host', 160 | short: '-h HOST', 161 | long: '--host HOST', 162 | default: 'localhost' 163 | 164 | option :port, 165 | description: 'Elasticsearch port', 166 | short: '-p PORT', 167 | long: '--port PORT', 168 | proc: proc(&:to_i), 169 | default: 9200 170 | 171 | option :scheme, 172 | description: 'Elasticsearch connection scheme, defaults to https for authenticated connections', 173 | short: '-s SCHEME', 174 | long: '--scheme SCHEME' 175 | 176 | option :password, 177 | description: 'Elasticsearch connection password', 178 | short: '-P PASSWORD', 179 | long: '--password PASSWORD' 180 | 181 | option :user, 182 | description: 'Elasticsearch connection user', 183 | short: '-u USER', 184 | long: '--user USER' 185 | 186 | option :headers, 187 | description: 'A comma separated list of headers to pass to elasticsearch http client', 188 | short: '-H headers', 189 | long: '--headers headers', 190 | default: 'Content-Type: application/json' 191 | 192 | option :timeout, 193 | description: 'Elasticsearch query timeout in seconds', 194 | short: '-t TIMEOUT', 195 | long: '--timeout TIMEOUT', 196 | proc: proc(&:to_i), 197 | default: 30 198 | 199 | option :warn, 200 | short: '-w N', 201 | long: '--warn N', 202 | description: 'Result average WARNING threshold', 203 | proc: proc(&:to_f), 204 | default: 0 205 | 206 | option :crit, 207 | short: '-c N', 208 | long: '--crit N', 209 | description: 'Result average CRITICAL threshold', 210 | proc: proc(&:to_f), 211 | default: 0 212 | 213 | option :invert, 214 | long: '--invert', 215 | description: 'Invert thresholds', 216 | boolean: true 217 | 218 | option :kibana_url, 219 | long: '--kibana-url KIBANA_URL', 220 | description: 'Kibana URL query prefix that will be in critical / warning response output.' 221 | 222 | def kibana_info 223 | kibana_date_format = '%Y-%m-%dT%H:%M:%S.%LZ' 224 | unless config[:kibana_url].nil? 225 | index = config[:index] 226 | unless config[:date_index].nil? 227 | date_index_partition = config[:date_index].split('%') 228 | index = "[#{date_index_partition.first}]" \ 229 | "#{date_index_partition[1..-1].join.sub('Y', 'YYYY').sub('y', 'YY').sub('m', 'MM').sub('d', 'DD').sub('j', 'DDDD').sub('H', 'hh')}" 230 | end 231 | end_time = Time.now.utc.to_i 232 | start_time = end_time 233 | if config[:minutes_previous] != 0 234 | start_time -= (config[:minutes_previous] * 60) 235 | end 236 | if config[:hours_previous] != 0 237 | start_time -= (config[:hours_previous] * 60 * 60) 238 | end 239 | if config[:days_previous] != 0 240 | start_time -= (config[:days_previous] * 60 * 60 * 24) 241 | end 242 | if config[:weeks_previous] != 0 243 | start_time -= (config[:weeks_previous] * 60 * 60 * 24 * 7) 244 | end 245 | if config[:months_previous] != 0 246 | start_time -= (config[:months_previous] * 60 * 60 * 24 * 31) 247 | end 248 | "Kibana logs: #{config[:kibana_url]}/#/discover?_g=" \ 249 | "(refreshInterval:(display:Off,section:0,value:0),time:(from:'" \ 250 | "#{URI.escape(Time.at(start_time).utc.strftime kibana_date_format)}',mode:absolute,to:'" \ 251 | "#{URI.escape(Time.at(end_time).utc.strftime kibana_date_format)}'))&_a=(columns:!(_source),index:" \ 252 | "#{URI.escape(index)},interval:auto,query:(query_string:(analyze_wildcard:!t,query:'" \ 253 | "#{URI.escape(config[:query])}')),sort:!('@timestamp',desc))&dummy" 254 | end 255 | end 256 | 257 | def run 258 | response = client.search(build_request_options) 259 | if config[:invert] 260 | if response['aggregations']['average']['value'] < config[:crit] 261 | critical "Query average (#{response['aggregations']['average']['value']}) was below critical threshold. #{kibana_info}" 262 | elsif response['aggregations']['average']['value'] < config[:warn] 263 | warning "Query average (#{response['aggregations']['average']['value']}) was below warning threshold. #{kibana_info}" 264 | else 265 | ok "Query average (#{response['aggregations']['average']['value']}) was ok" 266 | end 267 | elsif response['aggregations']['average']['value'] > config[:crit] 268 | critical "Query average (#{response['aggregations']['average']['value']}) was above critical threshold. #{kibana_info}" 269 | elsif response['aggregations']['average']['value'] > config[:warn] 270 | warning "Query average (#{response['aggregations']['average']['value']}) was above warning threshold. #{kibana_info}" 271 | else 272 | ok "Query average (#{response['aggregations']['average']['value']}) was ok" 273 | end 274 | rescue Elasticsearch::Transport::Transport::Errors::NotFound 275 | if config[:invert] 276 | if response['aggregations']['average']['value'] < config[:crit] 277 | critical "Query average (#{response['aggregations']['average']['value']}) was below critical threshold. #{kibana_info}" 278 | elsif response['aggregations']['average']['value'] < config[:warn] 279 | warning "Query average (#{response['aggregations']['average']['value']}) was below warning threshold. #{kibana_info}" 280 | else 281 | ok "Query average (#{response['aggregations']['average']['value']}) was ok" 282 | end 283 | else 284 | ok 'No results found, average was below thresholds' 285 | end 286 | end 287 | end 288 | -------------------------------------------------------------------------------- /bin/check-es-query-count.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # check-es-query 4 | # 5 | # DESCRIPTION: 6 | # This plugin checks an ElasticSearch query. 7 | # 8 | # OUTPUT: 9 | # plain text 10 | # 11 | # PLATFORMS: 12 | # Linux 13 | # 14 | # DEPENDENCIES: 15 | # gem: sensu-plugin 16 | # gem: elasticsearch 17 | # gem: aws_es_transport 18 | # 19 | # USAGE: 20 | # This example checks that the count of special_type logs matching a query of 21 | # anything (*) at the host elasticsearch.service.consul for the past 90 minutes 22 | # will warn if there are under 100 and go critical if the result count is below 1 23 | # (The invert flag warns if counts are _below_ the critical and warning values) 24 | # check-es-query-count.rb -h elasticsearch.service.consul -q "*" --invert 25 | # --types special_type -d 'logging-%Y.%m.%d' --minutes-previous 90 -p 9200 -c 1 -w 100 26 | # 27 | # NOTES: 28 | # 29 | # LICENSE: 30 | # Brendan Gibat 31 | # Released under the same terms as Sensu (the MIT license); see LICENSE 32 | # for details. 33 | # 34 | 35 | require 'sensu-plugin/check/cli' 36 | require 'elasticsearch' 37 | require 'time' 38 | require 'uri' 39 | require 'aws_es_transport' 40 | require 'sensu-plugins-elasticsearch' 41 | 42 | # 43 | # ES Query Count 44 | # 45 | class ESQueryCount < Sensu::Plugin::Check::CLI 46 | include ElasticsearchCommon 47 | 48 | option :index, 49 | description: 'Elasticsearch indices to query. 50 | Comma-separated list of index names to search. 51 | Use `_all` or empty string to perform the operation on all indices. 52 | Accepts wildcards', 53 | short: '-i INDEX', 54 | long: '--indices INDEX' 55 | 56 | option :transport, 57 | long: '--transport TRANSPORT', 58 | description: 'Transport to use to communicate with ES. Use "AWS" for signed AWS transports.' 59 | 60 | option :region, 61 | long: '--region REGION', 62 | description: 'Region (necessary for AWS Transport)' 63 | 64 | option :types, 65 | description: 'Elasticsearch types to limit searches to, comma separated list.', 66 | long: '--types TYPES' 67 | 68 | option :timestamp_field, 69 | description: 'Field to use instead of @timestamp for query.', 70 | long: '--timestamp-field FIELD_NAME', 71 | default: '@timestamp' 72 | 73 | option :offset, 74 | description: 'Seconds before offset to end @timestamp against query.', 75 | long: '--offset OFFSET', 76 | proc: proc(&:to_i), 77 | default: 0 78 | 79 | option :ignore_unavailable, 80 | description: 'Ignore unavailable indices.', 81 | long: '--ignore-unavailable', 82 | boolean: true, 83 | default: true 84 | 85 | option :minutes_previous, 86 | description: 'Minutes before offset to check @timestamp against query.', 87 | long: '--minutes-previous MINUTES_PREVIOUS', 88 | proc: proc(&:to_i), 89 | default: 0 90 | 91 | option :hours_previous, 92 | description: 'Hours before offset to check @timestamp against query.', 93 | long: '--hours-previous HOURS_PREVIOUS', 94 | proc: proc(&:to_i), 95 | default: 0 96 | 97 | option :days_previous, 98 | description: 'Days before offset to check @timestamp against query.', 99 | long: '--days-previous DAYS_PREVIOUS', 100 | proc: proc(&:to_i), 101 | default: 0 102 | 103 | option :weeks_previous, 104 | description: 'Weeks before offset to check @timestamp against query.', 105 | long: '--weeks-previous WEEKS_PREVIOUS', 106 | proc: proc(&:to_i), 107 | default: 0 108 | 109 | option :months_previous, 110 | description: 'Months before offset to check @timestamp against query.', 111 | long: '--months-previous MONTHS_PREVIOUS', 112 | proc: proc(&:to_i), 113 | default: 0 114 | 115 | option :date_index, 116 | description: 'Elasticsearch time based index. 117 | Accepts format from http://ruby-doc.org/core-2.2.0/Time.html#method-i-strftime', 118 | short: '-d DATE_INDEX', 119 | long: '--date-index DATE_INDEX' 120 | 121 | option :date_repeat_daily, 122 | description: 'Elasticsearch date based index repeats daily.', 123 | long: '--repeat-daily', 124 | boolean: true, 125 | default: true 126 | 127 | option :date_repeat_hourly, 128 | description: 'Elasticsearch date based index repeats hourly.', 129 | long: '--repeat-hourly', 130 | boolean: true, 131 | default: false 132 | 133 | option :search_field, 134 | description: 'The Elasticsearch document field to search for your query string.', 135 | short: '-f FIELD', 136 | long: '--field FIELD', 137 | required: false, 138 | default: 'message' 139 | 140 | option :query, 141 | description: 'Elasticsearch query', 142 | short: '-q QUERY', 143 | long: '--query QUERY', 144 | required: true 145 | 146 | option :host, 147 | description: 'Elasticsearch host', 148 | short: '-h HOST', 149 | long: '--host HOST', 150 | default: 'localhost' 151 | 152 | option :port, 153 | description: 'Elasticsearch port', 154 | short: '-p PORT', 155 | long: '--port PORT', 156 | proc: proc(&:to_i), 157 | default: 9200 158 | 159 | option :scheme, 160 | description: 'Elasticsearch connection scheme, defaults to https for authenticated connections', 161 | short: '-s SCHEME', 162 | long: '--scheme SCHEME' 163 | 164 | option :password, 165 | description: 'Elasticsearch connection password', 166 | short: '-P PASSWORD', 167 | long: '--password PASSWORD' 168 | 169 | option :user, 170 | description: 'Elasticsearch connection user', 171 | short: '-u USER', 172 | long: '--user USER' 173 | 174 | option :headers, 175 | description: 'A comma separated list of headers to pass to elasticsearch http client', 176 | short: '-H headers', 177 | long: '--headers headers', 178 | default: 'Content-Type: application/json' 179 | 180 | option :timeout, 181 | description: 'Elasticsearch query timeout in seconds', 182 | short: '-t TIMEOUT', 183 | long: '--timeout TIMEOUT', 184 | proc: proc(&:to_i), 185 | default: 30 186 | 187 | option :warn, 188 | short: '-w N', 189 | long: '--warn N', 190 | description: 'Result count WARNING threshold', 191 | proc: proc(&:to_i), 192 | default: 0 193 | 194 | option :crit, 195 | short: '-c N', 196 | long: '--crit N', 197 | description: 'Result count CRITICAL threshold', 198 | proc: proc(&:to_i), 199 | default: 0 200 | 201 | option :invert, 202 | long: '--invert', 203 | description: 'Invert thresholds', 204 | boolean: true 205 | 206 | option :kibana_url, 207 | long: '--kibana-url KIBANA_URL', 208 | description: 'Kibana URL query prefix that will be in critical / warning response output.' 209 | 210 | def kibana_info 211 | kibana_date_format = '%Y-%m-%dT%H:%M:%S.%LZ' 212 | unless config[:kibana_url].nil? 213 | index = config[:index] 214 | unless config[:date_index].nil? 215 | date_index_partition = config[:date_index].split('%') 216 | index = "[#{date_index_partition.first}]" \ 217 | "#{date_index_partition[1..-1].join.sub('Y', 'YYYY').sub('y', 'YY').sub('m', 'MM').sub('d', 'DD').sub('j', 'DDDD').sub('H', 'hh')}" 218 | end 219 | end_time = Time.now.utc.to_i 220 | start_time = end_time 221 | if config[:minutes_previous] != 0 222 | start_time -= (config[:minutes_previous] * 60) 223 | end 224 | if config[:hours_previous] != 0 225 | start_time -= (config[:hours_previous] * 60 * 60) 226 | end 227 | if config[:days_previous] != 0 228 | start_time -= (config[:days_previous] * 60 * 60 * 24) 229 | end 230 | if config[:weeks_previous] != 0 231 | start_time -= (config[:weeks_previous] * 60 * 60 * 24 * 7) 232 | end 233 | if config[:months_previous] != 0 234 | start_time -= (config[:months_previous] * 60 * 60 * 24 * 31) 235 | end 236 | "Kibana logs: #{config[:kibana_url]}/#/discover?_g=" \ 237 | "(refreshInterval:(display:Off,section:0,value:0),time:(from:'" \ 238 | "#{URI.escape(Time.at(start_time).utc.strftime kibana_date_format)}',mode:absolute,to:'" \ 239 | "#{URI.escape(Time.at(end_time).utc.strftime kibana_date_format)}'))&_a=(columns:!(_source),index:" \ 240 | "#{URI.escape(index)},interval:auto,query:(query_string:(analyze_wildcard:!t,query:'" \ 241 | "#{URI.escape(config[:query])}')),sort:!('@timestamp',desc))&dummy" 242 | end 243 | end 244 | 245 | def run 246 | response = client.count(build_request_options) 247 | if config[:invert] 248 | if response['count'] < config[:crit] 249 | critical "Query count (#{response['count']}) was below critical threshold. #{kibana_info}" 250 | elsif response['count'] < config[:warn] 251 | warning "Query count (#{response['count']}) was below warning threshold. #{kibana_info}" 252 | else 253 | ok "Query count (#{response['count']}) was ok" 254 | end 255 | elsif response['count'] > config[:crit] 256 | critical "Query count (#{response['count']}) was above critical threshold. #{kibana_info}" 257 | elsif response['count'] > config[:warn] 258 | warning "Query count (#{response['count']}) was above warning threshold. #{kibana_info}" 259 | else 260 | ok "Query count (#{response['count']}) was ok" 261 | end 262 | rescue Elasticsearch::Transport::Transport::Errors::NotFound 263 | if config[:invert] 264 | if response['count'] < config[:crit] 265 | critical "Query count (#{response['count']}) was below critical threshold. #{kibana_info}" 266 | elsif response['count'] < config[:warn] 267 | warning "Query count (#{response['count']}) was below warning threshold. #{kibana_info}" 268 | else 269 | ok "Query count (#{response['count']}) was ok" 270 | end 271 | else 272 | ok 'No results found, count was below thresholds' 273 | end 274 | end 275 | end 276 | -------------------------------------------------------------------------------- /bin/check-es-query-exists.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # check-es-query-exists 4 | # 5 | # DESCRIPTION: 6 | # This plugin checks an ElasticSearch query that documents exist. 7 | # 8 | # OUTPUT: 9 | # plain text 10 | # 11 | # PLATFORMS: 12 | # Linux 13 | # 14 | # DEPENDENCIES: 15 | # gem: sensu-plugin 16 | # gem: elasticsearch 17 | # 18 | # USAGE: 19 | # This example checks that the count of special_type logs matching a query of 20 | # "docker.args:special AND *specialstring* AND _exists_:key.name" 21 | # at the host elasticsearch.service.consul and port 9200 for the past 3 minutes 22 | # will go critical if there are NO results for that period. 23 | # This check is to ensure that events are happening at all. 24 | # check-es-query-exists.rb -h elasticsearch.service.consul 25 | # -q "docker.args:special AND *specialstring* AND _exists_:key.name" --invert 26 | # --types special_type -d 'logging-%Y.%m.%d' --minutes-previous 3 -p 9200 27 | # 28 | # NOTES: 29 | # 30 | # LICENSE: 31 | # Brendan Gibat 32 | # Released under the same terms as Sensu (the MIT license); see LICENSE 33 | # for details. 34 | # 35 | 36 | require 'sensu-plugin/check/cli' 37 | require 'elasticsearch' 38 | require 'time' 39 | require 'aws_es_transport' 40 | require 'sensu-plugins-elasticsearch' 41 | 42 | # 43 | # ES Query Exists 44 | # 45 | class ESQueryExists < Sensu::Plugin::Check::CLI 46 | include ElasticsearchCommon 47 | 48 | option :index, 49 | description: 'Elasticsearch indices to query. 50 | Comma-separated list of index names to search. 51 | Use `_all` or empty string to perform the operation on all indices. 52 | Accepts wildcards', 53 | short: '-i INDEX', 54 | long: '--indices INDEX' 55 | 56 | option :transport, 57 | long: '--transport TRANSPORT', 58 | description: 'Transport to use to communicate with ES. Use "AWS" for signed AWS transports.' 59 | 60 | option :region, 61 | long: '--region REGION', 62 | description: 'Region (necessary for AWS Transport)' 63 | 64 | option :types, 65 | description: 'Elasticsearch types to limit searches to, comma separated list.', 66 | long: '--types TYPES' 67 | 68 | option :timestamp_field, 69 | description: 'Field to use instead of @timestamp for query.', 70 | long: '--timestamp_field FIELD_NAME', 71 | default: '@timestamp' 72 | 73 | option :offset, 74 | description: 'Seconds before offset to end @timestamp against query.', 75 | long: '--offset OFFSET', 76 | proc: proc(&:to_i), 77 | default: 0 78 | 79 | option :minutes_previous, 80 | description: 'Minutes before offset to check @timestamp against query.', 81 | long: '--minutes-previous MINUTES_PREVIOUS', 82 | proc: proc(&:to_i), 83 | default: 0 84 | 85 | option :hours_previous, 86 | description: 'Hours before offset to check @timestamp against query.', 87 | long: '--hours-previous DAYS_PREVIOUS', 88 | proc: proc(&:to_i), 89 | default: 0 90 | 91 | option :days_previous, 92 | description: 'Days before offset to check @timestamp against query.', 93 | long: '--days-previous DAYS_PREVIOUS', 94 | proc: proc(&:to_i), 95 | default: 0 96 | 97 | option :weeks_previous, 98 | description: 'Weeks before offset to check @timestamp against query.', 99 | long: '--weeks-previous WEEKS_PREVIOUS', 100 | proc: proc(&:to_i), 101 | default: 0 102 | 103 | option :months_previous, 104 | description: 'Months before offset to check @timestamp against query.', 105 | long: '--months-previous MONTHS_PREVIOUS', 106 | proc: proc(&:to_i), 107 | default: 0 108 | 109 | option :date_index, 110 | description: 'Elasticsearch time based index. 111 | Accepts format from http://ruby-doc.org/core-2.2.0/Time.html#method-i-strftime', 112 | short: '-d DATE_INDEX', 113 | long: '--date-index DATE_INDEX' 114 | 115 | option :date_repeat_daily, 116 | description: 'Elasticsearch date based index repeats daily.', 117 | long: '--repeat-daily', 118 | boolean: true, 119 | default: true 120 | 121 | option :date_repeat_hourly, 122 | description: 'Elasticsearch date based index repeats hourly.', 123 | long: '--repeat-hourly', 124 | boolean: true, 125 | default: false 126 | 127 | option :id, 128 | description: 'ID of the ElasticSearch document to check for existence', 129 | long: '--id ID', 130 | required: true 131 | 132 | option :host, 133 | description: 'Elasticsearch host', 134 | short: '-h HOST', 135 | long: '--host HOST', 136 | default: 'localhost' 137 | 138 | option :port, 139 | description: 'Elasticsearch port', 140 | short: '-p PORT', 141 | long: '--port PORT', 142 | proc: proc(&:to_i), 143 | default: 9200 144 | 145 | option :scheme, 146 | description: 'Elasticsearch connection scheme, defaults to https for authenticated connections', 147 | short: '-s SCHEME', 148 | long: '--scheme SCHEME' 149 | 150 | option :password, 151 | description: 'Elasticsearch connection password', 152 | short: '-P PASSWORD', 153 | long: '--password PASSWORD' 154 | 155 | option :user, 156 | description: 'Elasticsearch connection user', 157 | short: '-u USER', 158 | long: '--user USER' 159 | 160 | option :headers, 161 | description: 'A comma separated list of headers to pass to elasticsearch http client', 162 | short: '-H headers', 163 | long: '--headers headers', 164 | default: 'Content-Type: application/json' 165 | 166 | option :timeout, 167 | description: 'Elasticsearch query timeout in seconds', 168 | short: '-t TIMEOUT', 169 | long: '--timeout TIMEOUT', 170 | proc: proc(&:to_i), 171 | default: 30 172 | 173 | option :warn, 174 | short: '-w', 175 | long: '--warn', 176 | description: 'Warn instead of critical', 177 | boolean: true, 178 | default: false 179 | 180 | option :invert, 181 | long: '--invert', 182 | description: 'Invert status', 183 | boolean: true, 184 | default: false 185 | 186 | def run 187 | if client.exists?(build_request_options) 188 | if config[:invert] 189 | if config[:warn] 190 | warning 191 | else 192 | critical 193 | end 194 | else 195 | ok 196 | end 197 | elsif config[:invert] 198 | ok 199 | elsif config[:warn] 200 | warning 201 | else 202 | critical 203 | end 204 | end 205 | end 206 | -------------------------------------------------------------------------------- /bin/check-es-query-ratio.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # check-es-query 4 | # 5 | # DESCRIPTION: 6 | # This plugin checks ratio between results of two Elasticsearch queries 7 | # 8 | # OUTPUT: 9 | # plain text 10 | # 11 | # PLATFORMS: 12 | # Linux 13 | # 14 | # DEPENDENCIES: 15 | # gem: sensu-plugin 16 | # gem: elasticsearch 17 | # gem: aws_es_transport 18 | # 19 | # USAGE: 20 | # This example checks the ratio from the count of two different queries 21 | # as dividend and divisor at the host elasticsearch.service.consul for the past 90 minutes 22 | # will warn if percentage is lower than 10 and critical if percentage is lower than 5 23 | # (The invert flag warns if results are _below_ the critical and warning values) 24 | # check-es-query-ratio.rb -h elasticsearch.service.consul -Q "orders:*" 25 | # -q "orders:OK" --invert --types special_type -d 'logging-%Y.%m.%d' 26 | # --minutes-previous 90 -p 9200 -c 5 -w 10 27 | # 28 | # 29 | # NOTES: 30 | # 31 | # LICENSE: 32 | # 33 | # Released under the same terms as Sensu (the MIT license); see LICENSE 34 | # for details. 35 | # 36 | 37 | require 'sensu-plugin/check/cli' 38 | require 'elasticsearch' 39 | require 'time' 40 | require 'uri' 41 | require 'aws_es_transport' 42 | require 'sensu-plugins-elasticsearch' 43 | 44 | # 45 | # ES Query Count 46 | # 47 | class ESQueryRatio < Sensu::Plugin::Check::CLI 48 | include ElasticsearchCommon 49 | 50 | option :index, 51 | description: 'Elasticsearch indices to query. 52 | Comma-separated list of index names to search. 53 | Use `_all` or empty string to perform the operation on all indices. 54 | Accepts wildcards', 55 | short: '-i INDEX', 56 | long: '--indices INDEX' 57 | 58 | option :transport, 59 | long: '--transport TRANSPORT', 60 | description: 'Transport to use to communicate with ES. Use "AWS" for signed AWS transports.' 61 | 62 | option :region, 63 | long: '--region REGION', 64 | description: 'Region (necessary for AWS Transport)' 65 | 66 | option :types, 67 | description: 'Elasticsearch types to limit searches to, comma separated list.', 68 | long: '--types TYPES' 69 | 70 | option :timestamp_field, 71 | description: 'Field to use instead of @timestamp for query.', 72 | long: '--timestamp-field FIELD_NAME', 73 | default: '@timestamp' 74 | 75 | option :offset, 76 | description: 'Seconds before offset to end @timestamp against query.', 77 | long: '--offset OFFSET', 78 | proc: proc(&:to_i), 79 | default: 0 80 | 81 | option :ignore_unavailable, 82 | description: 'Ignore unavailable indices.', 83 | long: '--ignore-unavailable', 84 | boolean: true, 85 | default: true 86 | 87 | option :minutes_previous, 88 | description: 'Minutes before offset to check @timestamp against query.', 89 | long: '--minutes-previous MINUTES_PREVIOUS', 90 | proc: proc(&:to_i), 91 | default: 0 92 | 93 | option :hours_previous, 94 | description: 'Hours before offset to check @timestamp against query.', 95 | long: '--hours-previous HOURS_PREVIOUS', 96 | proc: proc(&:to_i), 97 | default: 0 98 | 99 | option :days_previous, 100 | description: 'Days before offset to check @timestamp against query.', 101 | long: '--days-previous DAYS_PREVIOUS', 102 | proc: proc(&:to_i), 103 | default: 0 104 | 105 | option :weeks_previous, 106 | description: 'Weeks before offset to check @timestamp against query.', 107 | long: '--weeks-previous WEEKS_PREVIOUS', 108 | proc: proc(&:to_i), 109 | default: 0 110 | 111 | option :months_previous, 112 | description: 'Months before offset to check @timestamp against query.', 113 | long: '--months-previous MONTHS_PREVIOUS', 114 | proc: proc(&:to_i), 115 | default: 0 116 | 117 | option :date_index, 118 | description: 'Elasticsearch time based index. 119 | Accepts format from http://ruby-doc.org/core-2.2.0/Time.html#method-i-strftime', 120 | short: '-d DATE_INDEX', 121 | long: '--date-index DATE_INDEX' 122 | 123 | option :date_repeat_daily, 124 | description: 'Elasticsearch date based index repeats daily.', 125 | long: '--repeat-daily', 126 | boolean: true, 127 | default: true 128 | 129 | option :date_repeat_hourly, 130 | description: 'Elasticsearch date based index repeats hourly.', 131 | long: '--repeat-hourly', 132 | boolean: true, 133 | default: false 134 | 135 | option :search_field, 136 | description: 'The Elasticsearch document field to search for your query string.', 137 | short: '-f FIELD', 138 | long: '--field FIELD', 139 | required: false, 140 | default: 'message' 141 | 142 | option :dividend, 143 | description: 'Elasticsearch query where percentage is calculated for', 144 | short: '-Q QUERY', 145 | long: '--dividend QUERY', 146 | required: true 147 | 148 | option :divisor, 149 | description: 'Elasticsearch query where percentage is calculated from', 150 | short: '-q QUERY', 151 | long: '--divisor QUERY', 152 | required: true 153 | 154 | option :host, 155 | description: 'Elasticsearch host', 156 | short: '-h HOST', 157 | long: '--host HOST', 158 | default: 'localhost' 159 | 160 | option :port, 161 | description: 'Elasticsearch port', 162 | short: '-p PORT', 163 | long: '--port PORT', 164 | proc: proc(&:to_i), 165 | default: 9200 166 | 167 | option :scheme, 168 | description: 'Elasticsearch connection scheme, defaults to https for authenticated connections', 169 | short: '-s SCHEME', 170 | long: '--scheme SCHEME' 171 | 172 | option :password, 173 | description: 'Elasticsearch connection password', 174 | short: '-P PASSWORD', 175 | long: '--password PASSWORD' 176 | 177 | option :user, 178 | description: 'Elasticsearch connection user', 179 | short: '-u USER', 180 | long: '--user USER' 181 | 182 | option :headers, 183 | description: 'A comma separated list of headers to pass to elasticsearch http client', 184 | short: '-H headers', 185 | long: '--headers headers', 186 | default: 'Content-Type: application/json' 187 | 188 | option :timeout, 189 | description: 'Elasticsearch query timeout in seconds', 190 | short: '-t TIMEOUT', 191 | long: '--timeout TIMEOUT', 192 | proc: proc(&:to_i), 193 | default: 30 194 | 195 | option :warn, 196 | short: '-w N', 197 | long: '--warn N', 198 | description: 'Result count WARNING threshold', 199 | proc: proc(&:to_f), 200 | default: 0 201 | 202 | option :crit, 203 | short: '-c N', 204 | long: '--crit N', 205 | description: 'Result count CRITICAL threshold', 206 | proc: proc(&:to_f), 207 | default: 0 208 | 209 | option :invert, 210 | long: '--invert', 211 | description: 'Invert thresholds', 212 | boolean: true 213 | 214 | option :divisor_zero_ok, 215 | short: '-z', 216 | long: '--zero', 217 | description: 'Division by 0 returns OK', 218 | boolean: true, 219 | default: false 220 | 221 | option :kibana_url, 222 | long: '--kibana-url KIBANA_URL', 223 | description: 'Kibana URL query prefix that will be in critical / warning response output.' 224 | 225 | def kibana_info 226 | kibana_date_format = '%Y-%m-%dT%H:%M:%S.%LZ' 227 | unless config[:kibana_url].nil? 228 | index = config[:index] 229 | unless config[:date_index].nil? 230 | date_index_partition = config[:date_index].split('%') 231 | index = "[#{date_index_partition.first}]" \ 232 | "#{date_index_partition[1..-1].join.sub('Y', 'YYYY').sub('y', 'YY').sub('m', 'MM').sub('d', 'DD').sub('j', 'DDDD').sub('H', 'hh')}" 233 | end 234 | end_time = Time.now.utc.to_i 235 | start_time = end_time 236 | if config[:minutes_previous] != 0 237 | start_time -= (config[:minutes_previous] * 60) 238 | end 239 | if config[:hours_previous] != 0 240 | start_time -= (config[:hours_previous] * 60 * 60) 241 | end 242 | if config[:days_previous] != 0 243 | start_time -= (config[:days_previous] * 60 * 60 * 24) 244 | end 245 | if config[:weeks_previous] != 0 246 | start_time -= (config[:weeks_previous] * 60 * 60 * 24 * 7) 247 | end 248 | if config[:months_previous] != 0 249 | start_time -= (config[:months_previous] * 60 * 60 * 24 * 31) 250 | end 251 | "Kibana logs: #{config[:kibana_url]}/#/discover?_g=" \ 252 | "(refreshInterval:(display:Off,section:0,value:0),time:(from:'" \ 253 | "#{URI.escape(Time.at(start_time).utc.strftime kibana_date_format)}',mode:absolute,to:'" \ 254 | "#{URI.escape(Time.at(end_time).utc.strftime kibana_date_format)}'))&_a=(columns:!(_source),index:" \ 255 | "#{URI.escape(index)},interval:auto,query:(query_string:(analyze_wildcard:!t,query:'" \ 256 | "#{URI.escape(config[:query])}')),sort:!('#{config[:timestamp_field]}',desc))&dummy" 257 | end 258 | end 259 | 260 | def run 261 | dividend_query = config[:dividend] 262 | divisor_query = config[:divisor] 263 | config.delete(:dividend) 264 | config.delete(:divisor) 265 | config[:query] = dividend_query 266 | dividend = client.count(build_request_options) 267 | config[:query] = divisor_query 268 | divisor = client.count(build_request_options) 269 | divisor_zero_ok = config[:divisor_zero_ok] 270 | if divisor_zero_ok && divisor['count'].zero? 271 | ok 'Divisor is 0, ratio check cannot be performed, failing safe with ok' 272 | elsif divisor['count'].zero? 273 | critical 'Divisor is 0, ratio check cannot be performed, raising an alert' 274 | else 275 | response = {} 276 | response['count'] = (dividend['count'].to_f / divisor['count']) 277 | end 278 | if config[:invert] 279 | if response['count'] < config[:crit] 280 | critical "Query count (#{response['count']}) was below critical threshold. #{kibana_info}" 281 | elsif response['count'] < config[:warn] 282 | warning "Query count (#{response['count']}) was below warning threshold. #{kibana_info}" 283 | else 284 | ok "Query count (#{response['count']}) was ok" 285 | end 286 | elsif response['count'] > config[:crit] 287 | critical "Query count (#{response['count']}) was above critical threshold. #{kibana_info}" 288 | elsif response['count'] > config[:warn] 289 | warning "Query count (#{response['count']}) was above warning threshold. #{kibana_info}" 290 | else 291 | ok "Query count (#{response['count']}) was ok" 292 | end 293 | rescue Elasticsearch::Transport::Transport::Errors::NotFound 294 | if config[:invert] 295 | if response['count'] < config[:crit] 296 | critical "Query count (#{response['count']}) was below critical threshold. #{kibana_info}" 297 | elsif response['count'] < config[:warn] 298 | warning "Query count (#{response['count']}) was below warning threshold. #{kibana_info}" 299 | else 300 | ok "Query count (#{response['count']}) was ok" 301 | end 302 | else 303 | ok 'No results found, count was below thresholds' 304 | end 305 | end 306 | end 307 | -------------------------------------------------------------------------------- /bin/check-es-shard-allocation-status.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # Checks ElasticSearch shard allocation setting status 4 | # === 5 | # 6 | # DESCRIPTION: 7 | # Checks the ElasticSearch shard allocation persistent and transient settings 8 | # and will return status based on a difference in those settings. 9 | # 10 | # OUTPUT: 11 | # plain-text 12 | # 13 | # PLATFORMS: 14 | # Linux 15 | # 16 | # DEPENDENCIES: 17 | # gem: sensu-plugin 18 | # gem: rest-client 19 | # 20 | # USAGE: 21 | # 22 | # NOTES: 23 | # 24 | # LICENSE: 25 | # Copyright 2014 Yieldbot, Inc 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 | # 36 | # == Elastic Search Shard Allocation Status 37 | # 38 | class ESShardAllocationStatus < Sensu::Plugin::Check::CLI 39 | option :scheme, 40 | description: 'URI scheme', 41 | long: '--scheme SCHEME', 42 | default: 'http' 43 | 44 | option :server, 45 | description: 'Elasticsearch server', 46 | short: '-s SERVER', 47 | long: '--server SERVER', 48 | default: 'localhost' 49 | 50 | option :port, 51 | description: 'Port', 52 | short: '-p PORT', 53 | long: '--port PORT', 54 | default: '9200' 55 | 56 | option :allow_non_master, 57 | description: 'Allow check to run on non-master nodes', 58 | short: '-a', 59 | long: '--allow-non-master', 60 | default: false 61 | 62 | option :timeout, 63 | description: 'Sets the connection timeout for REST client', 64 | short: '-t SECS', 65 | long: '--timeout SECS', 66 | proc: proc(&:to_i), 67 | default: 45 68 | 69 | option :user, 70 | description: 'Elasticsearch User', 71 | short: '-u USER', 72 | long: '--user USER' 73 | 74 | option :password, 75 | description: 'Elasticsearch Password', 76 | short: '-P PASS', 77 | long: '--password PASS' 78 | 79 | option :cert_file, 80 | description: 'Cert file to use', 81 | long: '--cert-file CERT' 82 | 83 | def get_es_resource(resource) 84 | headers = {} 85 | if config[:user] && config[:password] 86 | auth = 'Basic ' + Base64.strict_encode64("#{config[:user]}:#{config[:password]}").chomp 87 | headers = { 'Authorization' => auth } 88 | end 89 | 90 | r = if config[:cert_file] 91 | RestClient::Resource.new("#{config[:scheme]}://#{config[:server]}:#{config[:port]}#{resource}", 92 | ssl_ca_file: config[:cert_file].to_s, 93 | timeout: config[:timeout], 94 | headers: headers) 95 | else 96 | RestClient::Resource.new("#{config[:scheme]}://#{config[:server]}:#{config[:port]}#{resource}", 97 | timeout: config[:timeout], 98 | headers: headers) 99 | end 100 | JSON.parse(r.get) 101 | rescue Errno::ECONNREFUSED 102 | warning 'Connection refused' 103 | rescue RestClient::RequestTimeout 104 | critical 'Connection timed out' 105 | rescue RestClient::ServiceUnavailable 106 | critical 'Service is unavailable' 107 | rescue Errno::ECONNRESET 108 | critical 'Connection reset by peer' 109 | end 110 | 111 | def master? 112 | state = get_es_resource('/_cluster/state/master_node') 113 | local = get_es_resource('/_nodes/_local') 114 | local['nodes'].keys.first == state['master_node'] 115 | end 116 | 117 | def get_status(type) 118 | settings = get_es_resource('/_cluster/settings') 119 | # Get the status for the given type, or default to 'all' 120 | # which is the ES default 121 | begin 122 | settings[type]['cluster']['routing']['allocation']['enable'].downcase 123 | rescue StandardError 124 | 'all' 125 | end 126 | end 127 | 128 | def run 129 | if config[:allow_non_master] || master? 130 | transient = get_status('transient') 131 | persistent = get_status('persistent') 132 | 133 | if transient == persistent 134 | ok "Persistent and transient allocation match: #{persistent}" 135 | else 136 | critical "Persistent(#{persistent}) and transient(#{transient}) \ 137 | shard allocation do not match." 138 | end 139 | else 140 | ok 'Not the master' 141 | end 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /bin/handler-es-delete-indices.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # handler-es-delete-indices.rb 4 | # 5 | # DESCRIPTION: 6 | # This handler deletes indices. 7 | # 8 | # OUTPUT: 9 | # plain text 10 | # 11 | # PLATFORMS: 12 | # Linux 13 | # 14 | # DEPENDENCIES: 15 | # gem: sensu-plugin 16 | # gem: elasticsearch 17 | # gem: aws_es_transport 18 | # 19 | # USAGE: 20 | # Deletes the indices given to it from a check output and a configured 21 | # regex, and then deletes the indices matched. 22 | # 23 | # NOTES: 24 | # 25 | # LICENSE: 26 | # Brendan Leon Gibat 27 | # Released under the same terms as Sensu (the MIT license); see LICENSE 28 | # for details. 29 | # 30 | 31 | require 'sensu-handler' 32 | require 'elasticsearch' 33 | require 'aws_es_transport' 34 | require 'sensu-plugins-elasticsearch' 35 | 36 | class ESIndexCleanup < Sensu::Handler 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: 60 81 | 82 | option :event_regex, 83 | description: 'Elasticsearch connection user', 84 | short: '-e EVENT_REGEX', 85 | long: '--event-regex EVENT_REGEX', 86 | default: 'INDEX\[([^\]]+)\]' 87 | 88 | def handle 89 | event_regex = Regexp.new(config[:event_regex]) 90 | indices_to_delete = @event['check']['output'].scan(event_regex).flatten 91 | if !indices_to_delete.nil? && !indices_to_delete.empty? 92 | 93 | puts("Deleting indices: [ #{indices_to_delete.sort.join(', ')} ]") 94 | client.indices.delete index: indices_to_delete 95 | else 96 | puts('No indices matched pattern to delete.') 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /bin/metrics-es-cluster.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # es-cluster-metrics 4 | # 5 | # DESCRIPTION: 6 | # This plugin uses the ES API to collect metrics, producing a JSON 7 | # document which is outputted to STDOUT. An exit status of 0 indicates 8 | # the plugin has successfully collected and produced metrics. 9 | # 10 | # OUTPUT: 11 | # metric data 12 | # 13 | # PLATFORMS: 14 | # Linux 15 | # 16 | # DEPENDENCIES: 17 | # gem: sensu-plugin 18 | # gem: rest-client 19 | # 20 | # USAGE: 21 | # #YELLOW 22 | # 23 | # NOTES: 24 | # 25 | # LICENSE: 26 | # Copyright 2011 Sonian, Inc 27 | # Released under the same terms as Sensu (the MIT license); see LICENSE 28 | # for details. 29 | # 30 | 31 | require 'sensu-plugin/metric/cli' 32 | require 'rest-client' 33 | require 'json' 34 | require 'base64' 35 | 36 | # 37 | # ES Cluster Metrics 38 | # 39 | class ESClusterMetrics < Sensu::Plugin::Metric::CLI::Graphite 40 | option :scheme, 41 | description: 'Metric naming scheme, text to prepend to metric', 42 | short: '-s SCHEME', 43 | long: '--scheme SCHEME', 44 | default: "#{Socket.gethostname}.elasticsearch.cluster" 45 | 46 | option :host, 47 | description: 'Elasticsearch host', 48 | short: '-h HOST', 49 | long: '--host HOST', 50 | default: 'localhost' 51 | 52 | option :port, 53 | description: 'Elasticsearch port', 54 | short: '-p PORT', 55 | long: '--port PORT', 56 | proc: proc(&:to_i), 57 | default: 9200 58 | 59 | option :timeout, 60 | description: 'Sets the connection timeout for REST client', 61 | short: '-t SECS', 62 | long: '--timeout SECS', 63 | proc: proc(&:to_i), 64 | default: 30 65 | 66 | option :allow_non_master, 67 | description: 'Allow check to run on non-master nodes', 68 | short: '-a', 69 | long: '--allow-non-master', 70 | default: false 71 | 72 | option :enable_percolate, 73 | description: 'Enables percolator stats (ES 2 and older only)', 74 | short: '-o', 75 | long: '--enable-percolate', 76 | default: false 77 | 78 | option :user, 79 | description: 'Elasticsearch User', 80 | short: '-u USER', 81 | long: '--user USER' 82 | 83 | option :password, 84 | description: 'Elasticsearch Password', 85 | short: '-P PASS', 86 | long: '--password PASS' 87 | 88 | option :https, 89 | description: 'Enables HTTPS', 90 | short: '-e', 91 | long: '--https' 92 | 93 | option :cert_file, 94 | description: 'Cert file to use', 95 | long: '--cert-file CERT_FILE' 96 | 97 | def acquire_es_version 98 | info = get_es_resource('/') 99 | info['version']['number'] 100 | end 101 | 102 | def get_es_resource(resource) 103 | headers = {} 104 | if config[:user] && config[:password] 105 | auth = 'Basic ' + Base64.strict_encode64("#{config[:user]}:#{config[:password]}").chomp 106 | headers = { 'Authorization' => auth } 107 | end 108 | 109 | protocol = if config[:https] 110 | 'https' 111 | else 112 | 'http' 113 | end 114 | 115 | r = if config[:cert_file] 116 | RestClient::Resource.new("#{protocol}://#{config[:host]}:#{config[:port]}#{resource}", 117 | ssl_ca_file: config[:cert_file].to_s, 118 | timeout: config[:timeout], 119 | headers: headers) 120 | else 121 | RestClient::Resource.new("#{protocol}://#{config[:host]}:#{config[:port]}#{resource}", 122 | timeout: config[:timeout], 123 | headers: headers) 124 | end 125 | ::JSON.parse(r.get) 126 | rescue Errno::ECONNREFUSED 127 | warning 'Connection refused' 128 | rescue RestClient::RequestTimeout 129 | warning 'Connection timed out' 130 | end 131 | 132 | def master? 133 | state = if Gem::Version.new(acquire_es_version) >= Gem::Version.new('3.0.0') 134 | get_es_resource('/_cluster/state/master_node') 135 | else 136 | get_es_resource('/_cluster/state?filter_routing_table=true&filter_metadata=true&filter_indices=true') 137 | end 138 | local = if Gem::Version.new(acquire_es_version) >= Gem::Version.new('1.0.0') 139 | get_es_resource('/_nodes/_local') 140 | else 141 | get_es_resource('/_cluster/nodes/_local') 142 | end 143 | local['nodes'].keys.first == state['master_node'] 144 | end 145 | 146 | def acquire_health 147 | health = get_es_resource('/_cluster/health').reject { |k, _v| %w[cluster_name timed_out].include?(k) } 148 | health['status'] = %w[red yellow green].index(health['status']) 149 | health 150 | end 151 | 152 | def acquire_document_count 153 | document_count = get_es_resource('/_stats/docs') 154 | count = document_count['_all']['total'] 155 | if count.empty? 156 | return 0 157 | else 158 | return count['docs']['count'] 159 | end 160 | end 161 | 162 | def acquire_cluster_metrics 163 | cluster_stats = get_es_resource('/_cluster/stats') 164 | cluster_metrics = Hash.new { |h, k| h[k] = {} } 165 | cluster_metrics['fs']['total_in_bytes'] = cluster_stats['nodes']['fs']['total_in_bytes'] 166 | cluster_metrics['fs']['free_in_bytes'] = cluster_stats['nodes']['fs']['free_in_bytes'] 167 | cluster_metrics['fs']['store_in_bytes'] = cluster_stats['indices']['store']['size_in_bytes'] 168 | cluster_metrics['fs']['disk_reads'] = cluster_stats['nodes']['fs']['disk_reads'] 169 | cluster_metrics['fs']['disk_writes'] = cluster_stats['nodes']['fs']['disk_writes'] 170 | cluster_metrics['fs']['disk_read_size_in_bytes'] = cluster_stats['nodes']['fs']['disk_read_size_in_bytes'] 171 | cluster_metrics['fs']['disk_write_size_in_bytes'] = cluster_stats['nodes']['fs']['disk_write_size_in_bytes'] 172 | cluster_metrics['fielddata']['memory_size_in_bytes'] = cluster_stats['indices']['fielddata']['memory_size_in_bytes'] 173 | cluster_metrics['fielddata']['evictions'] = cluster_stats['indices']['fielddata']['evictions'] 174 | 175 | # Elasticsearch changed the name filter_cache to query_cache in 2.0+ 176 | cache_name = Gem::Version.new(acquire_es_version) < Gem::Version.new('2.0.0') ? 'filter_cache' : 'query_cache' 177 | 178 | cluster_metrics[cache_name]['memory_size_in_bytes'] = cluster_stats['indices'][cache_name]['memory_size_in_bytes'] 179 | cluster_metrics[cache_name]['evictions'] = cluster_stats['indices'][cache_name]['evictions'] 180 | cluster_metrics['mem'] = cluster_stats['nodes']['jvm']['mem'] 181 | 182 | if config[:enable_percolate] && Gem::Version.new(acquire_es_version) < Gem::Version.new('5.0.0') 183 | cluster_metrics['percolate']['total'] = cluster_stats['indices']['percolate']['total'] 184 | cluster_metrics['percolate']['time_in_millis'] = cluster_stats['indices']['percolate']['time_in_millis'] 185 | cluster_metrics['percolate']['queries'] = cluster_stats['indices']['percolate']['queries'] 186 | end 187 | cluster_metrics 188 | end 189 | 190 | def acquire_allocation_status 191 | cluster_config = get_es_resource('/_cluster/settings') 192 | transient_settings = cluster_config['transient'] 193 | if transient_settings.key?('cluster') 194 | return %w[none new_primaries primaries all].index(transient_settings['cluster']['routing']['allocation']['enable']) 195 | else 196 | return nil 197 | end 198 | end 199 | 200 | def run 201 | if config[:allow_non_master] || master? 202 | acquire_health.each do |k, v| 203 | output(config[:scheme] + '.' + k, v) 204 | end 205 | acquire_cluster_metrics.each do |cluster_metric| 206 | cluster_metric[1].each do |k, v| 207 | output(config[:scheme] + '.' + cluster_metric[0] + '.' + k, v || 0) 208 | end 209 | end 210 | output(config[:scheme] + '.document_count', acquire_document_count) 211 | output(config[:scheme] + '.allocation_status', acquire_allocation_status) unless acquire_allocation_status.nil? 212 | end 213 | ok 214 | end 215 | end 216 | -------------------------------------------------------------------------------- /bin/metrics-es-node-graphite.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # es-node-graphite 4 | # 5 | # DESCRIPTION: 6 | # This check creates node metrics from the elasticsearch API 7 | # 8 | # OUTPUT: 9 | # metric data 10 | # 11 | # PLATFORMS: 12 | # Linux, Windows, BSD, Solaris, etc 13 | # 14 | # DEPENDENCIES: 15 | # gem: sensu-plugin 16 | # gem: rest-client 17 | # 18 | # USAGE: 19 | # #YELLOW 20 | # 21 | # NOTES: 22 | # 2014/04 23 | # Modifid by Vincent Janelle @randomfrequency http://github.com/vjanelle 24 | # Add more metrics, fix es 1.x URLs, translate graphite stats from 25 | # names directly 26 | # 27 | # 2012/12 - Modified by Zach Dunn @SillySophist http://github.com/zadunn 28 | # To add more metrics, and correct for new versins of ES. Tested on 29 | # ES Version 0.19.8 30 | # 31 | # LICENSE: 32 | # Copyright 2013 Vincent Janelle 33 | # Copyright 2012 Sonian, Inc 34 | # Released under the same terms as Sensu (the MIT license); see LICENSE 35 | # for details. 36 | # 37 | 38 | require 'sensu-plugin/metric/cli' 39 | require 'rest-client' 40 | require 'json' 41 | require 'base64' 42 | 43 | # 44 | # ES Node Graphite Metrics 45 | # 46 | class ESNodeGraphiteMetrics < Sensu::Plugin::Metric::CLI::Graphite 47 | option :scheme, 48 | description: 'Metric naming scheme, text to prepend to queue_name.metric', 49 | short: '-s SCHEME', 50 | long: '--scheme SCHEME', 51 | default: "#{Socket.gethostname}.elasticsearch" 52 | 53 | option :server, 54 | description: 'Elasticsearch server host.', 55 | short: '-h HOST', 56 | long: '--host HOST', 57 | default: 'localhost' 58 | 59 | option :port, 60 | description: 'Elasticsearch port.', 61 | short: '-p PORT', 62 | long: '--port PORT', 63 | proc: proc(&:to_i), 64 | default: 9200 65 | 66 | option :timeout, 67 | description: 'Request timeout to elasticsearch', 68 | short: '-t TIMEOUT', 69 | long: '--timeout TIMEOUT', 70 | proc: proc(&:to_i), 71 | default: 30 72 | 73 | option :disable_jvm_stats, 74 | description: 'Disable JVM statistics', 75 | long: '--disable-jvm-stats', 76 | boolean: true, 77 | default: false 78 | 79 | option :disable_os_stats, 80 | description: 'Disable OS Stats', 81 | long: '--disable-os-stat', 82 | boolean: true, 83 | default: false 84 | 85 | option :disable_process_stats, 86 | description: 'Disable process statistics', 87 | long: '--disable-process-stats', 88 | boolean: true, 89 | default: false 90 | 91 | option :disable_thread_pool_stats, 92 | description: 'Disable thread-pool statistics', 93 | long: '--disable-thread-pool-stats', 94 | boolean: true, 95 | default: false 96 | 97 | option :disable_fs_stats, 98 | description: 'Disable filesystem statistics', 99 | long: '--disable-fs-stats', 100 | boolean: true, 101 | default: false 102 | 103 | option :user, 104 | description: 'Elasticsearch User', 105 | short: '-u USER', 106 | long: '--user USER' 107 | 108 | option :password, 109 | description: 'Elasticsearch Password', 110 | short: '-P PASS', 111 | long: '--password PASS' 112 | 113 | option :https, 114 | description: 'Enables HTTPS', 115 | short: '-e', 116 | long: '--https' 117 | 118 | option :cert_file, 119 | description: 'Cert file to use', 120 | long: '--cert-file CERT_FILE' 121 | 122 | def get_es_resource(resource) 123 | headers = {} 124 | if config[:user] && config[:password] 125 | auth = 'Basic ' + Base64.strict_encode64("#{config[:user]}:#{config[:password]}").chomp 126 | headers = { 'Authorization' => auth } 127 | end 128 | 129 | protocol = if config[:https] 130 | 'https' 131 | else 132 | 'http' 133 | end 134 | 135 | r = if config[:cert_file] 136 | RestClient::Resource.new("#{protocol}://#{config[:server]}:#{config[:port]}#{resource}?pretty", 137 | ssl_ca_file: config[:cert_file].to_s, 138 | timeout: config[:timeout], 139 | headers: headers) 140 | else 141 | RestClient::Resource.new("#{protocol}://#{config[:server]}:#{config[:port]}#{resource}?pretty", 142 | timeout: config[:timeout], 143 | headers: headers) 144 | end 145 | ::JSON.parse(r.get) 146 | rescue Errno::ECONNREFUSED 147 | warning 'Connection refused' 148 | rescue RestClient::RequestTimeout 149 | warning 'Connection timed out' 150 | end 151 | 152 | def acquire_es_version 153 | info = get_es_resource('/') 154 | info['version']['number'] 155 | end 156 | 157 | def run 158 | # invert various stats depending on if some flags are set 159 | os_stat = !config[:disable_os_stats] 160 | process_stats = !config[:disable_process_stats] 161 | jvm_stats = !config[:disable_jvm_stats] 162 | tp_stats = !config[:disable_thread_pool_stats] 163 | fs_stats = !config[:disable_fs_stats] 164 | 165 | es_version = Gem::Version.new(acquire_es_version) 166 | 167 | if es_version >= Gem::Version.new('3.0.0') 168 | stats_query_array = %w[indices http transport] 169 | stats_query_array.push('jvm') if jvm_stats == true 170 | stats_query_array.push('os') if os_stat == true 171 | stats_query_array.push('process') if process_stats == true 172 | stats_query_array.push('thread_pool') if tp_stats == true 173 | stats_query_array.push('fs') if fs_stats == true 174 | stats_query_string = stats_query_array.join(',') 175 | elsif es_version >= Gem::Version.new('1.0.0') 176 | stats_query_array = %w[indices http network transport thread_pool] 177 | stats_query_array.push('jvm') if jvm_stats == true 178 | stats_query_array.push('os') if os_stat == true 179 | stats_query_array.push('process') if process_stats == true 180 | stats_query_array.push('tp_stats') if tp_stats == true 181 | stats_query_array.push('fs_stats') if fs_stats == true 182 | stats_query_string = stats_query_array.join(',') 183 | else 184 | stats_query_string = [ 185 | 'clear=true', 186 | 'indices=true', 187 | 'http=true', 188 | "jvm=#{jvm_stats}", 189 | 'network=true', 190 | "os=#{os_stat}", 191 | "process=#{process_stats}", 192 | "thread_pool=#{tp_stats}", 193 | 'transport=true', 194 | 'thread_pool=true', 195 | "fs=#{fs_stats}" 196 | ].join('&') 197 | end 198 | 199 | stats = if es_version >= Gem::Version.new('3.0.0') 200 | get_es_resource("/_nodes/_local/stats/#{stats_query_string}") 201 | elsif es_version >= Gem::Version.new('1.0.0') 202 | get_es_resource("/_nodes/_local/stats?#{stats_query_string}") 203 | else 204 | get_es_resource("/_cluster/nodes/_local/stats?#{stats_query_string}") 205 | end 206 | 207 | timestamp = Time.now.to_i 208 | node = stats['nodes'].values.first 209 | 210 | metrics = {} 211 | 212 | if os_stat 213 | if es_version >= Gem::Version.new('2.0.0') 214 | metrics['os.load_average'] = node['os']['load_average'] 215 | else 216 | metrics['os.load_average'] = node['os']['load_average'][0] 217 | metrics['os.load_average.1'] = node['os']['load_average'][0] 218 | metrics['os.load_average.5'] = node['os']['load_average'][1] 219 | metrics['os.load_average.15'] = node['os']['load_average'][2] 220 | metrics['os.cpu.sys'] = node['os']['cpu']['sys'] 221 | metrics['os.cpu.user'] = node['os']['cpu']['user'] 222 | metrics['os.cpu.idle'] = node['os']['cpu']['idle'] 223 | metrics['os.cpu.usage'] = node['os']['cpu']['usage'] 224 | metrics['os.cpu.stolen'] = node['os']['cpu']['stolen'] 225 | metrics['os.uptime'] = node['os']['uptime_in_millis'] 226 | end 227 | metrics['os.mem.free_in_bytes'] = node['os']['mem']['free_in_bytes'] 228 | end 229 | 230 | if process_stats 231 | metrics['process.cpu.percent'] = node['process']['cpu']['percent'] 232 | metrics['process.mem.resident_in_bytes'] = node['process']['mem']['resident_in_bytes'] if node['process']['mem']['resident_in_bytes'] 233 | end 234 | 235 | if jvm_stats 236 | metrics['jvm.mem.heap_used_in_bytes'] = node['jvm']['mem']['heap_used_in_bytes'] 237 | metrics['jvm.mem.non_heap_used_in_bytes'] = node['jvm']['mem']['non_heap_used_in_bytes'] 238 | metrics['jvm.mem.max_heap_size_in_bytes'] = 0 239 | 240 | node['jvm']['mem']['pools'].each do |k, v| 241 | metrics["jvm.mem.#{k.tr(' ', '_')}.max_in_bytes"] = v['max_in_bytes'] 242 | metrics['jvm.mem.max_heap_size_in_bytes'] += v['max_in_bytes'] 243 | end 244 | 245 | # This makes absolutely no sense - not sure what it's trying to measure - @vjanelle 246 | # metrics['jvm.gc.collection_time_in_millis'] = node['jvm']['gc']['collection_time_in_millis'] + \ 247 | # node['jvm']['mem']['pools']['CMS Old Gen']['max_in_bytes'] 248 | 249 | node['jvm']['gc']['collectors'].each do |gc, gc_value| 250 | gc_value.each do |k, v| 251 | # this contains stupid things like '28ms' and '2s', and there's already 252 | # something that counts in millis, which makes more sense 253 | unless k.end_with? 'collection_time' 254 | metrics["jvm.gc.collectors.#{gc}.#{k}"] = v 255 | end 256 | end 257 | end 258 | 259 | metrics['jvm.threads.count'] = node['jvm']['threads']['count'] 260 | metrics['jvm.threads.peak_count'] = node['jvm']['threads']['peak_count'] 261 | metrics['jvm.uptime'] = node['jvm']['uptime_in_millis'] 262 | end 263 | 264 | node['indices'].each do |type, index| 265 | index.each do |k, v| 266 | # #YELLOW 267 | unless k =~ /(_time$)/ || v =~ /\d+/ 268 | metrics["indices.#{type}.#{k}"] = v 269 | end 270 | end 271 | end 272 | 273 | node['transport'].each do |k, v| 274 | # #YELLOW 275 | unless k =~ /(_size$)/ 276 | metrics["transport.#{k}"] = v 277 | end 278 | end 279 | 280 | metrics['http.current_open'] = node['http']['current_open'] 281 | metrics['http.total_opened'] = node['http']['total_opened'] 282 | 283 | if node['network'] 284 | metrics['network.tcp.active_opens'] = node['network']['tcp']['active_opens'] 285 | metrics['network.tcp.passive_opens'] = node['network']['tcp']['passive_opens'] 286 | 287 | metrics['network.tcp.in_segs'] = node['network']['tcp']['in_segs'] 288 | metrics['network.tcp.out_segs'] = node['network']['tcp']['out_segs'] 289 | metrics['network.tcp.retrans_segs'] = node['network']['tcp']['retrans_segs'] 290 | metrics['network.tcp.attempt_fails'] = node['network']['tcp']['attempt_fails'] 291 | metrics['network.tcp.in_errs'] = node['network']['tcp']['in_errs'] 292 | metrics['network.tcp.out_rsts'] = node['network']['tcp']['out_rsts'] 293 | 294 | metrics['network.tcp.curr_estab'] = node['network']['tcp']['curr_estab'] 295 | metrics['network.tcp.estab_resets'] = node['network']['tcp']['estab_resets'] 296 | end 297 | 298 | if tp_stats 299 | node['thread_pool'].each do |pool, stat| 300 | stat.each do |k, v| 301 | metrics["thread_pool.#{pool}.#{k}"] = v 302 | end 303 | end 304 | end 305 | 306 | if fs_stats 307 | node['fs'].each do |fs, fs_value| 308 | unless fs =~ /(timestamp|data)/ 309 | fs_value.each do |k, v| 310 | metrics["fs.#{fs}.#{k}"] = v 311 | end 312 | end 313 | end 314 | end 315 | 316 | metrics.each do |k, v| 317 | output([config[:scheme], k].join('.'), v, timestamp) 318 | end 319 | ok 320 | end 321 | end 322 | -------------------------------------------------------------------------------- /bin/metrics-es-node.rb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | # 3 | # es-node-metrics 4 | # 5 | # DESCRIPTION: 6 | # This plugin uses the ES API to collect metrics, producing a JSON 7 | # document which is outputted to STDOUT. An exit status of 0 indicates 8 | # the plugin has successfully collected and produced. 9 | # 10 | # OUTPUT: 11 | # metric data 12 | # 13 | # PLATFORMS: 14 | # Linux 15 | # 16 | # DEPENDENCIES: 17 | # gem: sensu-plugin 18 | # gem: rest-client 19 | # 20 | # USAGE: 21 | # #YELLOW 22 | # 23 | # NOTES: 24 | # 25 | # LICENSE: 26 | # Copyright 2011 Sonian, Inc 27 | # Released under the same terms as Sensu (the MIT license); see LICENSE 28 | # for details. 29 | # 30 | 31 | require 'sensu-plugin/metric/cli' 32 | require 'rest-client' 33 | require 'json' 34 | require 'base64' 35 | 36 | # 37 | # ES Node Metrics 38 | # 39 | class ESMetrics < Sensu::Plugin::Metric::CLI::Graphite 40 | option :scheme, 41 | description: 'Metric naming scheme, text to prepend to queue_name.metric', 42 | short: '-s SCHEME', 43 | long: '--scheme SCHEME', 44 | default: "#{Socket.gethostname}.elasticsearch" 45 | 46 | option :host, 47 | description: 'Elasticsearch server host.', 48 | short: '-h HOST', 49 | long: '--host HOST', 50 | default: 'localhost' 51 | 52 | option :port, 53 | description: 'Elasticsearch port', 54 | short: '-p PORT', 55 | long: '--port PORT', 56 | proc: proc(&:to_i), 57 | default: 9200 58 | 59 | option :user, 60 | description: 'Elasticsearch User', 61 | short: '-u USER', 62 | long: '--user USER' 63 | 64 | option :password, 65 | description: 'Elasticsearch Password', 66 | short: '-P PASS', 67 | long: '--password PASS' 68 | 69 | option :https, 70 | description: 'Enables HTTPS', 71 | short: '-e', 72 | long: '--https' 73 | 74 | option :cert_file, 75 | description: 'Cert file to use', 76 | long: '--cert CERT_FILE' 77 | 78 | def acquire_es_version 79 | info = get_es_resource('/') 80 | info['version']['number'] 81 | end 82 | 83 | def get_es_resource(resource) 84 | headers = {} 85 | if config[:user] && config[:password] 86 | auth = 'Basic ' + Base64.strict_encode64("#{config[:user]}:#{config[:password]}").chomp 87 | headers = { 'Authorization' => auth } 88 | end 89 | 90 | protocol = if config[:https] 91 | 'https' 92 | else 93 | 'http' 94 | end 95 | 96 | r = if config[:cert_file] 97 | RestClient::Resource.new("#{protocol}://#{config[:host]}:#{config[:port]}#{resource}", 98 | ssl_ca_file: config[:cert_file].to_s, 99 | timeout: config[:timeout], 100 | headers: headers) 101 | else 102 | RestClient::Resource.new("#{protocol}://#{config[:host]}:#{config[:port]}#{resource}", 103 | timeout: config[:timeout], 104 | headers: headers) 105 | end 106 | ::JSON.parse(r.get) 107 | rescue Errno::ECONNREFUSED 108 | warning 'Connection refused' 109 | rescue RestClient::RequestTimeout 110 | warning 'Connection timed out' 111 | end 112 | 113 | def run 114 | es_version = Gem::Version.new(acquire_es_version) 115 | 116 | if es_version >= Gem::Version.new('1.0.0') 117 | ln = get_es_resource('/_nodes/_local') 118 | stats = get_es_resource('/_nodes/_local/stats') 119 | else 120 | ln = get_es_resource('/_cluster/nodes/_local') 121 | stats = get_es_resource('/_cluster/nodes/_local/stats') 122 | end 123 | 124 | timestamp = Time.now.to_i 125 | node = stats['nodes'].values.first 126 | node['jvm']['mem']['heap_max_in_bytes'] = ln['nodes'].values.first['jvm']['mem']['heap_max_in_bytes'] 127 | metrics = {} 128 | metrics['os.load_average'] = if es_version >= Gem::Version.new('2.0.0') 129 | node['os']['load_average'] 130 | else 131 | node['os']['load_average'][0] 132 | end 133 | metrics['os.mem.free_in_bytes'] = node['os']['mem']['free_in_bytes'] 134 | metrics['process.mem.resident_in_bytes'] = node['process']['mem']['resident_in_bytes'] 135 | metrics['jvm.mem.heap_used_in_bytes'] = node['jvm']['mem']['heap_used_in_bytes'] 136 | metrics['jvm.mem.non_heap_used_in_bytes'] = node['jvm']['mem']['non_heap_used_in_bytes'] 137 | metrics['jvm.gc.collection_time_in_millis'] = node['jvm']['gc']['collection_time_in_millis'] 138 | metrics.each do |k, v| 139 | output([config[:scheme], k].join('.'), v, timestamp) 140 | end 141 | ok 142 | end 143 | end 144 | -------------------------------------------------------------------------------- /lib/sensu-plugins-elasticsearch.rb: -------------------------------------------------------------------------------- 1 | require 'sensu-plugins-elasticsearch/version' 2 | require 'sensu-plugins-elasticsearch/elasticsearch-common' 3 | require 'sensu-plugins-elasticsearch/elasticsearch-query' 4 | -------------------------------------------------------------------------------- /lib/sensu-plugins-elasticsearch/elasticsearch-common.rb: -------------------------------------------------------------------------------- 1 | # 2 | # DESCRIPTION: 3 | # Common helper methods 4 | # 5 | # DEPENDENCIES: 6 | # gem: elasticsearch 7 | # gem: sensu-plugin 8 | # 9 | # USAGE: 10 | # 11 | # NOTES: 12 | # 13 | # LICENSE: 14 | # Brendan Gibat 15 | # Released under the same terms as Sensu (the MIT license); see LICENSE 16 | # for details. 17 | # 18 | 19 | require_relative 'elasticsearch-query.rb' 20 | 21 | module ElasticsearchCommon 22 | include ElasticsearchQuery 23 | def initialize 24 | super() 25 | end 26 | 27 | def client 28 | transport_class = nil 29 | if config[:transport] == 'AWS' 30 | transport_class = Elasticsearch::Transport::Transport::HTTP::AWS 31 | end 32 | 33 | host = { 34 | host: config[:host], 35 | port: config[:port], 36 | request_timeout: config[:timeout], 37 | scheme: config[:scheme] 38 | } 39 | 40 | if !config[:user].nil? && !config[:password].nil? 41 | host[:user] = config[:user] 42 | host[:password] = config[:password] 43 | host[:scheme] = 'https' unless config[:scheme] 44 | end 45 | 46 | transport_options = {} 47 | 48 | if config[:headers] 49 | 50 | headers = {} 51 | 52 | config[:headers].split(',').each do |header| 53 | h, v = header.split(':', 2) 54 | headers[h.strip] = v.strip 55 | end 56 | 57 | transport_options[:headers] = headers 58 | 59 | end 60 | 61 | @client ||= Elasticsearch::Client.new(transport_class: transport_class, hosts: [host], region: config[:region], transport_options: transport_options) 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/sensu-plugins-elasticsearch/elasticsearch-query.rb: -------------------------------------------------------------------------------- 1 | # 2 | # DESCRIPTION: 3 | # Common search helper methods 4 | # 5 | # DEPENDENCIES: 6 | # gem: elasticsearch 7 | # gem: sensu-plugin 8 | # 9 | # USAGE: 10 | # 11 | # NOTES: 12 | # 13 | # LICENSE: 14 | # Brendan Gibat 15 | # Released under the same terms as Sensu (the MIT license); see LICENSE 16 | # for details. 17 | # 18 | 19 | module ElasticsearchQuery 20 | def initialize 21 | super() 22 | end 23 | 24 | def indices(end_time) 25 | if !config[:index].nil? 26 | return config[:index] 27 | elsif !config[:date_index].nil? 28 | indices = [] 29 | 30 | curr = end_time.to_i 31 | start = curr 32 | 33 | if config[:minutes_previous] != 0 34 | start -= (config[:minutes_previous] * 60) 35 | end 36 | if config[:hours_previous] != 0 37 | start -= (config[:hours_previous] * 60 * 60) 38 | end 39 | if config[:days_previous] != 0 40 | start -= (config[:days_previous] * 60 * 60 * 24) 41 | end 42 | if config[:weeks_previous] != 0 43 | start -= (config[:weeks_previous] * 60 * 60 * 24 * 7) 44 | end 45 | if config[:months_previous] != 0 46 | start -= (config[:months_previous] * 60 * 60 * 24 * 7 * 31) 47 | end 48 | total = 60 * 60 * 24 49 | if config[:date_repeat_hourly] 50 | total = 60 * 60 51 | end 52 | (start.to_i..curr.to_i).step(total) do |step| 53 | indices.push(Time.at(step).utc.strftime config[:date_index]) 54 | end 55 | unless indices.include?(Time.at(curr).utc.strftime config[:date_index]) 56 | indices.push(Time.at(curr).utc.strftime config[:date_index]) 57 | end 58 | return indices.join(',') 59 | end 60 | ['_all'] 61 | end 62 | 63 | def build_request_options 64 | end_time = (Time.now.utc - config[:offset]) 65 | options = { 66 | 67 | index: indices(end_time), 68 | ignore_unavailable: true 69 | } 70 | 71 | unless config[:ignore_unavailable].nil? 72 | options[:ignore_unavailable] = config[:ignore_unavailable] 73 | end 74 | 75 | unless config[:id].nil? 76 | options[:id] = config[:id] 77 | end 78 | 79 | if !config[:body].nil? 80 | options[:body] = config[:body] 81 | elsif config[:aggr] == true 82 | es_date_start = es_date_math_string end_time 83 | options[:size] = 0 84 | options[:body] = { 85 | 'query' => { 86 | 'bool' => { 87 | 'must' => [{ 88 | 'query_string' => { 89 | 'default_field' => config[:search_field], 90 | 'query' => config[:query] 91 | } 92 | }, { 93 | 'range' => { 94 | config[:timestamp_field] => { 95 | 'gt' => es_date_start, 96 | 'lt' => end_time.strftime('%Y-%m-%dT%H:%M:%S') 97 | } 98 | } 99 | }] 100 | } 101 | }, 102 | 'aggregations' => { 103 | 'average' => { 'avg' => { 'field' => config[:aggr_field] } } 104 | } 105 | } 106 | else 107 | es_date_start = es_date_math_string end_time 108 | unless es_date_start.nil? 109 | options[:body] = { 110 | 'query' => { 111 | 'bool' => { 112 | 'must' => [{ 113 | 'query_string' => { 114 | 'default_field' => config[:search_field], 115 | 'query' => config[:query] 116 | } 117 | }, { 118 | 'range' => { 119 | config[:timestamp_field] => { 120 | 'gt' => es_date_start, 121 | 'lt' => end_time.strftime('%Y-%m-%dT%H:%M:%S') 122 | } 123 | } 124 | }] 125 | } 126 | } 127 | } 128 | end 129 | end 130 | unless config[:types].nil? 131 | options[:type] = config[:types] 132 | end 133 | options 134 | end 135 | 136 | def es_date_math_string(end_time) 137 | if config[:minutes_previous].zero? && \ 138 | config[:hours_previous].zero? && \ 139 | config[:days_previous].zero? && \ 140 | config[:weeks_previous].zero? && \ 141 | config[:months_previous].zero? 142 | nil 143 | else 144 | es_math = "#{end_time.strftime '%Y-%m-%dT%H:%M:%S'}||" 145 | es_math += "-#{config[:minutes_previous]}m" if config[:minutes_previous] != 0 146 | es_math += "-#{config[:hours_previous]}h" if config[:hours_previous] != 0 147 | es_math += "-#{config[:days_previous]}d" if config[:days_previous] != 0 148 | es_math += "-#{config[:weeks_previous]}w" if config[:weeks_previous] != 0 149 | es_math += "-#{config[:months_previous]}M" if config[:months_previous] != 0 150 | es_math 151 | end 152 | end 153 | end 154 | -------------------------------------------------------------------------------- /lib/sensu-plugins-elasticsearch/version.rb: -------------------------------------------------------------------------------- 1 | module SensuPluginsElasticsearch 2 | module Version 3 | MAJOR = 4 4 | MINOR = 2 5 | PATCH = 2 6 | 7 | VER_STRING = [MAJOR, MINOR, PATCH].compact.join('.') 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /sensu-plugins-elasticsearch.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('../lib', __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | 4 | require 'date' 5 | require_relative 'lib/sensu-plugins-elasticsearch' 6 | 7 | # pvt_key = '~/.ssh/gem-private_key.pem' 8 | Gem::Specification.new do |s| # rubocop:disable Metrics/BlockLength 9 | s.authors = ['Sensu Plugins and contributors'] 10 | # s.cert_chain = ['certs/sensu-plugins.pem'] 11 | s.date = Date.today.to_s 12 | s.description = 'This plugin provides native ElasticSearch instrumentation 13 | for monitoring and metrics collection, including: 14 | service health and metrics for cluster, node, and more.' 15 | s.email = '' 16 | s.executables = Dir.glob('bin/**/*.rb').map { |file| File.basename(file) } 17 | s.files = Dir.glob('{bin,lib}/**/*') + %w[LICENSE README.md CHANGELOG.md] 18 | s.homepage = 'https://github.com/sensu-plugins/sensu-plugins-elasticsearch' 19 | s.license = 'MIT' 20 | s.metadata = { 'maintainer' => 'sensu-plugin', 21 | 'development_status' => 'active', 22 | 'production_status' => 'unstable - testing recommended', 23 | 'release_draft' => 'false', 24 | 'release_prerelease' => 'false' } 25 | s.name = 'sensu-plugins-elasticsearch' 26 | s.platform = Gem::Platform::RUBY 27 | s.post_install_message = 'You can use the embedded Ruby by setting EMBEDDED_RUBY=true in /etc/default/sensu' 28 | s.require_paths = ['lib'] 29 | s.required_ruby_version = '>= 2.3.0' 30 | # s.signing_key = File.expand_path(pvt_key) if $PROGRAM_NAME =~ /gem\z/ 31 | s.summary = 'Sensu plugins for elasticsearch' 32 | s.test_files = s.files.grep(%r{^(test|spec|features)/}) 33 | s.version = SensuPluginsElasticsearch::Version::VER_STRING 34 | 35 | s.add_runtime_dependency 'aws-es-transport', '~> 0.1' 36 | s.add_runtime_dependency 'aws-sdk', ['>= 2.1.14', '< 2.5', '~> 2.1'] 37 | s.add_runtime_dependency 'elasticsearch', '~> 1.0.14' 38 | s.add_runtime_dependency 'rest-client', '2.1.0' 39 | s.add_runtime_dependency 'sensu-plugin', '~> 4.0' 40 | 41 | s.add_development_dependency 'bundler', '~> 2.1' 42 | s.add_development_dependency 'codeclimate-test-reporter', '~> 0.4' 43 | s.add_development_dependency 'github-markup', '~> 4.0' 44 | s.add_development_dependency 'kitchen-docker', '~> 2.6' 45 | # locked to keep ruby 2.1 support, this is pulled in by test-kitchen 46 | s.add_development_dependency 'mixlib-shellout', ['< 2.3.0', '~> 2.2'] 47 | s.add_development_dependency 'pry', '~> 0.10' 48 | s.add_development_dependency 'rake', '~> 13.0' 49 | s.add_development_dependency 'redcarpet', '~> 3.2' 50 | s.add_development_dependency 'rspec', '~> 3.1' 51 | s.add_development_dependency 'rubocop', '~> 0.51.0' 52 | # 0.17 requires ruby 2.3+ 53 | s.add_development_dependency 'test-kitchen', '~> 1.16.0' 54 | s.add_development_dependency 'yard', '~> 0.9.11' 55 | end 56 | -------------------------------------------------------------------------------- /test/fixtures/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | apt-get update 5 | apt-get install -y build-essential 6 | 7 | source /etc/profile 8 | DATA_DIR=/tmp/kitchen/data 9 | RUBY_HOME=${MY_RUBY_HOME} 10 | 11 | 12 | cd $DATA_DIR 13 | SIGN_GEM=false gem build sensu-plugins-elasticsearch.gemspec 14 | gem install sensu-plugins-elasticsearch-*.gem 15 | 16 | # hostnamectl 17 | if [ $(curl -sI -XGET sensu-elasticsearch-6:9200/field_count_index | grep -c '200 OK') -ne 0 ]; then 18 | # clean up existing index 19 | echo "Clean up existing index field_count_index" 20 | curl -XDELETE sensu-elasticsearch-6:9200/field_count_index 21 | echo 22 | fi 23 | 24 | echo 25 | echo "Create index field_count_index" 26 | echo 27 | curl -XPUT sensu-elasticsearch-6:9200/field_count_index 28 | 29 | echo 30 | echo "Create mapping for index field_count_index" 31 | echo 32 | curl --header 'Content-Type: application/json' -XPUT sensu-elasticsearch-6:9200/field_count_index/_mapping/test -d @- <<'EOF' 33 | { 34 | "properties": { 35 | "field1": { 36 | "type": "boolean" 37 | }, 38 | "field2": { 39 | "type": "boolean" 40 | }, 41 | "field3": { 42 | "type": "boolean" 43 | }, 44 | "field4": { 45 | "type": "boolean" 46 | }, 47 | "field5": { 48 | "type": "boolean" 49 | }, 50 | "field6": { 51 | "type": "boolean" 52 | }, 53 | "field7": { 54 | "type": "boolean" 55 | }, 56 | "field8": { 57 | "type": "boolean" 58 | } 59 | } 60 | } 61 | EOF 62 | -------------------------------------------------------------------------------- /test/integration/helpers/serverspec/check-es-indices-field-count-shared_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'shared_spec' 5 | 6 | gem_path = '/usr/local/bin' 7 | check_name = 'check-es-indices-field-count.rb' 8 | check = "#{gem_path}/#{check_name}" 9 | host = 'sensu-elasticsearch-6' 10 | 11 | describe 'ruby environment' do 12 | it_behaves_like 'ruby checks', check 13 | end 14 | 15 | describe command("#{check} --host #{host} -i field_count_index -l 10 -w 85 -c 95") do 16 | its(:exit_status) { should eq 0 } 17 | its(:stdout) { should match(/ESIndicesFieldCount OK/) } 18 | end 19 | 20 | describe command("#{check} --host #{host} -l 10 -w 85 -c 95") do 21 | its(:exit_status) { should eq 0 } 22 | its(:stdout) { should match(/ESIndicesFieldCount OK/) } 23 | end 24 | 25 | describe command("#{check} --host #{host} -i field_count_index -l 10 -w 70 -c 90") do 26 | its(:exit_status) { should eq 1 } 27 | end 28 | 29 | describe command("#{check} --host #{host} -i field_count_index -l 10 -w 70 -c 80") do 30 | its(:exit_status) { should eq 2 } 31 | end 32 | -------------------------------------------------------------------------------- /test/integration/helpers/serverspec/check-es-query-count-shared_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'shared_spec' 5 | 6 | gem_path = '/usr/local/bin' 7 | check_name = 'check-es-query-count.rb' 8 | check = "#{gem_path}/#{check_name}" 9 | host = 'sensu-elasticsearch-6' 10 | 11 | describe 'ruby environment' do 12 | it_behaves_like 'ruby checks', check 13 | end 14 | 15 | describe command("#{check} --host #{host} -q '*' --minutes-previous 1") do 16 | its(:exit_status) { should eq 0 } 17 | its(:stdout) { should match(/ESQueryCount OK: Query count \(\d+\) was ok/) } 18 | end 19 | 20 | describe command("#{check} --host #{host} -q '*' --headers='Content-Type: application/x-www-form-urlencoded' --minutes-previous 1") do 21 | its(:exit_status) { should eq 2 } 22 | end 23 | -------------------------------------------------------------------------------- /test/integration/helpers/serverspec/shared_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | shared_examples_for 'ruby checks' do |check| 6 | describe command('which ruby') do 7 | its(:exit_status) { should eq 0 } 8 | its(:stdout) { should match(/\/usr\/local\/bin\/ruby/) } 9 | end 10 | 11 | describe command('which gem') do 12 | its(:exit_status) { should eq 0 } 13 | its(:stdout) { should match(/\/usr\/local\/bin\/gem/) } 14 | end 15 | 16 | describe command("which #{check}") do 17 | its(:exit_status) { should eq 0 } 18 | its(:stdout) { should match(Regexp.new(Regexp.escape(check))) } 19 | end 20 | 21 | describe file(check) do 22 | it { should be_file } 23 | it { should be_executable } 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/integration/helpers/serverspec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'serverspec' 4 | 5 | set :backend, :exec 6 | -------------------------------------------------------------------------------- /test/integration/ruby-20/serverspec/default_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | -------------------------------------------------------------------------------- /test/integration/ruby-21/serverspec/default_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | -------------------------------------------------------------------------------- /test/integration/ruby-22/serverspec/default_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | -------------------------------------------------------------------------------- /test/integration/ruby-230/serverspec/default_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | -------------------------------------------------------------------------------- /test/integration/ruby-241/serverspec/default_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | -------------------------------------------------------------------------------- /test/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'codeclimate-test-reporter' 2 | CodeClimate::TestReporter.start 3 | 4 | RSpec.configure do |c| 5 | c.formatter = :documentation 6 | c.color = true 7 | end 8 | --------------------------------------------------------------------------------