xymonclient 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +60 -0
- data/LICENSE +13 -0
- data/README.md +85 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/xymonclient/discovery.rb +23 -0
- data/lib/xymonclient/exception.rb +22 -0
- data/lib/xymonclient/helpers.rb +43 -0
- data/lib/xymonclient/service.rb +133 -0
- data/lib/xymonclient/serviceitem.rb +133 -0
- data/lib/xymonclient/version.rb +3 -0
- data/lib/xymonclient.rb +105 -0
- data/spec/service_spec.rb +49 -0
- data/spec/serviceitem_spec.rb +94 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/xymonclient_spec.rb +17 -0
- metadata +147 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 53c615d0c431bab59303f6e27d944dc89b2f8f3c
|
|
4
|
+
data.tar.gz: d279ec888695080dd90901dd5c3a2230c37b95f6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: bed8acb18cdbb7b3c1dc5ce9c381433c3138fc142637e4a2456b14f1fb15f0a25e8b8f4505a257a2c35ce17b7dca38e2561b8a78ee634cbbe72d820fc748b33e
|
|
7
|
+
data.tar.gz: 24a02a76ebaff377408a6423f127050a27ec416bcf2c3c6d7c638a31e421833ec4321c79d530b4120946251201100e5c361e25e168f03a6c637d3a88cca36a38
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
xymonclient (0.1.0)
|
|
5
|
+
|
|
6
|
+
GEM
|
|
7
|
+
remote: https://rubygems.org/
|
|
8
|
+
specs:
|
|
9
|
+
ast (2.3.0)
|
|
10
|
+
codeclimate-test-reporter (1.0.3)
|
|
11
|
+
simplecov
|
|
12
|
+
diff-lcs (1.2.5)
|
|
13
|
+
docile (1.1.5)
|
|
14
|
+
json (2.0.2-java)
|
|
15
|
+
parser (2.3.1.4)
|
|
16
|
+
ast (~> 2.2)
|
|
17
|
+
powerpack (0.1.1)
|
|
18
|
+
rainbow (2.1.0)
|
|
19
|
+
rake (10.5.0)
|
|
20
|
+
rspec (3.5.0)
|
|
21
|
+
rspec-core (~> 3.5.0)
|
|
22
|
+
rspec-expectations (~> 3.5.0)
|
|
23
|
+
rspec-mocks (~> 3.5.0)
|
|
24
|
+
rspec-core (3.5.4)
|
|
25
|
+
rspec-support (~> 3.5.0)
|
|
26
|
+
rspec-expectations (3.5.0)
|
|
27
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
28
|
+
rspec-support (~> 3.5.0)
|
|
29
|
+
rspec-mocks (3.5.0)
|
|
30
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
31
|
+
rspec-support (~> 3.5.0)
|
|
32
|
+
rspec-support (3.5.0)
|
|
33
|
+
rubocop (0.40.0)
|
|
34
|
+
parser (>= 2.3.1.0, < 3.0)
|
|
35
|
+
powerpack (~> 0.1)
|
|
36
|
+
rainbow (>= 1.99.1, < 3.0)
|
|
37
|
+
ruby-progressbar (~> 1.7)
|
|
38
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
|
39
|
+
ruby-progressbar (1.8.1)
|
|
40
|
+
simplecov (0.12.0)
|
|
41
|
+
docile (~> 1.1.0)
|
|
42
|
+
json (>= 1.8, < 3)
|
|
43
|
+
simplecov-html (~> 0.10.0)
|
|
44
|
+
simplecov-html (0.10.0)
|
|
45
|
+
unicode-display_width (1.1.1)
|
|
46
|
+
|
|
47
|
+
PLATFORMS
|
|
48
|
+
java
|
|
49
|
+
|
|
50
|
+
DEPENDENCIES
|
|
51
|
+
bundler (~> 1.13)
|
|
52
|
+
codeclimate-test-reporter (~> 1.0)
|
|
53
|
+
rake (~> 10.0)
|
|
54
|
+
rspec (~> 3.0)
|
|
55
|
+
rubocop (= 0.40)
|
|
56
|
+
simplecov (~> 0.12.0)
|
|
57
|
+
xymonclient!
|
|
58
|
+
|
|
59
|
+
BUNDLED WITH
|
|
60
|
+
1.13.6
|
data/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2016 Orange <http://orange.com>
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[](https://badge.fury.io/rb/xymonclient)
|
|
2
|
+
[](https://travis-ci.org/dchauviere/ruby-xymonclient)
|
|
3
|
+
[](https://codeclimate.com/github/dchauviere/ruby-xymonclient)
|
|
4
|
+
<a href="https://codeclimate.com/github/dchauviere/ruby-xymonclient/coverage"><img src="https://codeclimate.com/github/dchauviere/ruby-xymonclient/badges/coverage.svg" /></a>
|
|
5
|
+
# XymonClient
|
|
6
|
+
|
|
7
|
+
XymonClient is a ruby library for interacting with Xymon
|
|
8
|
+
|
|
9
|
+
Features:
|
|
10
|
+
* Send status
|
|
11
|
+
* Ack
|
|
12
|
+
* Enable/Disable
|
|
13
|
+
* Helper Class 'Service' for building sensors easily
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Add this line to your application's Gemfile:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
gem 'xymonclient'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
And then execute:
|
|
24
|
+
|
|
25
|
+
$ bundle
|
|
26
|
+
|
|
27
|
+
Or install it yourself as:
|
|
28
|
+
|
|
29
|
+
$ gem install xymonclient
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
### Basic usage
|
|
34
|
+
```ruby
|
|
35
|
+
require 'xymonclient'
|
|
36
|
+
|
|
37
|
+
client = XymonClient::Client.new(['localhost:1984'])
|
|
38
|
+
client.status('myhost', 'service1', 'green', 'additional data')
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Service wrapper
|
|
43
|
+
```ruby
|
|
44
|
+
require 'xymonclient/service'
|
|
45
|
+
|
|
46
|
+
service = XymonClient::Service.new(['localhost:1984'],
|
|
47
|
+
'name' => 'service1',
|
|
48
|
+
'host' => 'myhost',
|
|
49
|
+
'header' => 'A sample header',
|
|
50
|
+
'footer' => 'A sample footer',
|
|
51
|
+
'items' => {
|
|
52
|
+
'ITEM1' => {
|
|
53
|
+
'label' => 'Gauge Item 1',
|
|
54
|
+
'type' => 'gauge',
|
|
55
|
+
'threshold' => {
|
|
56
|
+
'order' => '<',
|
|
57
|
+
'critical' => 5,
|
|
58
|
+
'warning' => 10,
|
|
59
|
+
'nan_status' => 'red'
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
'ITEM2' => {
|
|
63
|
+
'label' => 'String Item 2',
|
|
64
|
+
'type' => 'string',
|
|
65
|
+
'threshold' => {
|
|
66
|
+
'inclusive' => false,
|
|
67
|
+
'critical' => ['all is Ok !']
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
service.update_item('ITEM1', 3)
|
|
73
|
+
service.update_item('ITEM2', 'all is Ok !')
|
|
74
|
+
service.status
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Development
|
|
78
|
+
|
|
79
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
80
|
+
|
|
81
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
82
|
+
|
|
83
|
+
## Contributing
|
|
84
|
+
|
|
85
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/dchauviere/xymonclient.
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'xymonclient'
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require 'irb'
|
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'xymonclient/exception'
|
|
2
|
+
|
|
3
|
+
module XymonClient
|
|
4
|
+
##
|
|
5
|
+
# static methods of servers discovery
|
|
6
|
+
class ServerDiscovery
|
|
7
|
+
def find_from_file(file = '/etc/xymon/xymonclient.cfg')
|
|
8
|
+
result = []
|
|
9
|
+
open(file, 'r').read.each_line do |line|
|
|
10
|
+
next unless line =~ /^XYMSRV=/ || line =~ /^XYMSERVERS=/
|
|
11
|
+
ip = line.scan(/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/)
|
|
12
|
+
if ip[0] != '0.0.0.0' && line =~ /^XYMSRV=/
|
|
13
|
+
result << xymsrv_ip
|
|
14
|
+
break
|
|
15
|
+
else
|
|
16
|
+
result = ip
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
raise NoXymonServerDefined if result.empty?
|
|
20
|
+
result.map { |ip| { host: ip, port: 1984 } }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module XymonClient
|
|
2
|
+
class NoXymonServerDefined < StandardError
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
class InvalidStatus < StandardError
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class InvalidServer < StandardError
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class InvalidDuration < StandardError
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class InvalidService < StandardError
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class InvalidHost < StandardError
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class InvalidServiceItem < StandardError
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module XymonClient
|
|
2
|
+
TIMESTRING_DEFINITION = {
|
|
3
|
+
'' => 60,
|
|
4
|
+
'm' => 60,
|
|
5
|
+
'h' => 60 * 60,
|
|
6
|
+
'd' => 60 * 60 * 24,
|
|
7
|
+
'w' => 60 * 60 * 24 * 7
|
|
8
|
+
}.freeze
|
|
9
|
+
|
|
10
|
+
def self.valid_status?(status)
|
|
11
|
+
%w(green yellow red purple blue clear).include?(status)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.valid_duration?(duration)
|
|
15
|
+
duration =~ /^[0-9]+[hmwd]?$/
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.timestring_to_time(timestring)
|
|
19
|
+
time_matched = /^([0-9]+)([hmdw]{0,1})$/.match(timestring)
|
|
20
|
+
raise InvalidTimeString unless time_matched
|
|
21
|
+
time_matched[1].to_i * TIMESTRING_DEFINITION[time_matched[2]]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.hostsvc(host, service)
|
|
25
|
+
raise XymonClient::InvalidHost, host if host == ''
|
|
26
|
+
raise XymonClient::InvalidService, service if service == ''
|
|
27
|
+
host.tr('.', ',') + '.' + service
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# Class container for isolating context for ERB templating
|
|
32
|
+
class ERBContext
|
|
33
|
+
def initialize(hash)
|
|
34
|
+
hash.each_pair do |key, value|
|
|
35
|
+
instance_variable_set('@' + key.to_s, value)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def context
|
|
40
|
+
binding
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
require 'erb'
|
|
2
|
+
require 'xymonclient'
|
|
3
|
+
require 'xymonclient/helpers'
|
|
4
|
+
require 'xymonclient/serviceitem'
|
|
5
|
+
|
|
6
|
+
module XymonClient
|
|
7
|
+
##
|
|
8
|
+
# Manage a Service, can contains multiple items to monitor
|
|
9
|
+
class Service < XymonClient::Client
|
|
10
|
+
attr_reader :name
|
|
11
|
+
attr_accessor :status
|
|
12
|
+
attr_reader :details
|
|
13
|
+
DEFAULT_DETAILS_TEMPLATE = 'Generated at <%= @timestamp %> ' \
|
|
14
|
+
"for <%= @lifetime %> \n<%= @header %>\n" \
|
|
15
|
+
'<% @items.each do |item| %>' \
|
|
16
|
+
"&<%= item['status'] %> <%= item['label'] %>: <%= item['value'] %>\n" \
|
|
17
|
+
"<% end %>\n" \
|
|
18
|
+
"<%= @footer %>\n".freeze
|
|
19
|
+
|
|
20
|
+
def initialize(servers, config)
|
|
21
|
+
super(servers)
|
|
22
|
+
@info = { 'items' => {} }
|
|
23
|
+
update_config(config)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def update_config(config)
|
|
27
|
+
raise InvalidService if config.fetch('name', '') == ''
|
|
28
|
+
raise InvalidService if config.fetch('host', '') == ''
|
|
29
|
+
@info['name'] = config['name']
|
|
30
|
+
@info['host'] = config['host']
|
|
31
|
+
@info['details_template'] = config.fetch(
|
|
32
|
+
'details_template',
|
|
33
|
+
DEFAULT_DETAILS_TEMPLATE
|
|
34
|
+
)
|
|
35
|
+
@info['lifetime'] = config.fetch('lifetime', '30m')
|
|
36
|
+
@info['enabled'] = config.fetch('enabled', true)
|
|
37
|
+
@info['header'] = config.fetch('header', '')
|
|
38
|
+
@info['footer'] = config.fetch('footer', '')
|
|
39
|
+
if config.fetch('items', {}).empty?
|
|
40
|
+
@info['items'] = {}
|
|
41
|
+
else
|
|
42
|
+
_update_items_config(config)
|
|
43
|
+
end
|
|
44
|
+
@info['purple_item_status'] = config.fetch('purple_item_status', 'red')
|
|
45
|
+
@info['status'] = @info.fetch('status', config.fetch('status', 'purple'))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def update_item(name, value)
|
|
49
|
+
raise InvalidServiceItem unless @info['items'].include?(name)
|
|
50
|
+
@info['items'][name].value = value
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def status
|
|
54
|
+
return 'clear' unless @info['enabled']
|
|
55
|
+
items_status = @info['items'].map do |_key, value|
|
|
56
|
+
if value.info['status'] == 'purple'
|
|
57
|
+
@info['purple_item_status']
|
|
58
|
+
else
|
|
59
|
+
value.info['status']
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
@info['status'] = unless items_status.empty?
|
|
63
|
+
if items_status.include?('red')
|
|
64
|
+
'red'
|
|
65
|
+
elsif items_status.include?('yellow') && \
|
|
66
|
+
@status != 'red'
|
|
67
|
+
'yellow'
|
|
68
|
+
else
|
|
69
|
+
'green'
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
details = _details
|
|
73
|
+
super(
|
|
74
|
+
@info['host'],
|
|
75
|
+
@info['name'],
|
|
76
|
+
@info['status'],
|
|
77
|
+
details,
|
|
78
|
+
@info['lifetime']
|
|
79
|
+
)
|
|
80
|
+
[@info['status'], details]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def enable
|
|
84
|
+
super(@info['host'], @info['name'])
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def disable(duration, message)
|
|
88
|
+
super(@info['host'], @info['name'], duration, message)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def board(fields = [])
|
|
92
|
+
super(@info['host'], @info['name'], fields)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def ack(duration, message)
|
|
96
|
+
super(@info['host'], @info['name'], duration, message)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def _details
|
|
102
|
+
@info['timestamp'] = Time.now
|
|
103
|
+
context = @info.reject { |key, _value| key == 'items' }
|
|
104
|
+
context['items'] = @info['items'].map { |_key, value| value.info }
|
|
105
|
+
ERB.new(@info['details_template']).result(
|
|
106
|
+
XymonClient::ERBContext.new(context).context
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def _create_serviceitem(config)
|
|
111
|
+
case config.fetch('type', '')
|
|
112
|
+
when 'gauge'
|
|
113
|
+
ServiceItemGauge.new(config)
|
|
114
|
+
when 'string'
|
|
115
|
+
ServiceItemString.new(config)
|
|
116
|
+
else
|
|
117
|
+
ServiceItem.new(config)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def _update_items_config(config)
|
|
122
|
+
# cleanup old items and update old items
|
|
123
|
+
@info['items'].keep_if { |key, _value| config.fetch('items').key?(key) }
|
|
124
|
+
@info['items'].each do |item_name, item_value|
|
|
125
|
+
item_value.update_config(config['items'][item_name])
|
|
126
|
+
end
|
|
127
|
+
# add new items
|
|
128
|
+
config['items'].each do |item_name, item_config|
|
|
129
|
+
@info['items'][item_name] = _create_serviceitem(item_config)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
require 'xymonclient'
|
|
2
|
+
require 'xymonclient/helpers'
|
|
3
|
+
|
|
4
|
+
module XymonClient
|
|
5
|
+
##
|
|
6
|
+
# Manage an item to monitor
|
|
7
|
+
class ServiceItem
|
|
8
|
+
attr_accessor :value
|
|
9
|
+
attr_reader :info
|
|
10
|
+
|
|
11
|
+
def initialize(config)
|
|
12
|
+
raise InvalidServiceItemName if config.fetch('label', '') == ''
|
|
13
|
+
@info = {
|
|
14
|
+
'label' => config['label'],
|
|
15
|
+
'type' => config['type'],
|
|
16
|
+
'description' => config.fetch('description', ''),
|
|
17
|
+
'enabled' => config.fetch('enabled', true),
|
|
18
|
+
'status' => 'purple',
|
|
19
|
+
'lifetime' => config.fetch('lifetime', '30m'),
|
|
20
|
+
'time' => Time.at(0)
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def value=(value)
|
|
25
|
+
@info['value'] = value
|
|
26
|
+
@info['time'] = Time.now
|
|
27
|
+
status
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def value
|
|
31
|
+
@info['value']
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def status
|
|
35
|
+
@info['status'] = \
|
|
36
|
+
if !@info['enabled']
|
|
37
|
+
'clear'
|
|
38
|
+
elsif Time.now - @info['time'] > \
|
|
39
|
+
XymonClient.timestring_to_time(@info['lifetime'])
|
|
40
|
+
'purple'
|
|
41
|
+
elsif XymonClient.valid_status?(@info['value'])
|
|
42
|
+
@info['value']
|
|
43
|
+
else
|
|
44
|
+
'red'
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
##
|
|
50
|
+
class ServiceItemGauge < ServiceItem
|
|
51
|
+
def initialize(config)
|
|
52
|
+
super(config)
|
|
53
|
+
@info['threshold'] = config.fetch('threshold', {})
|
|
54
|
+
@info['nan_status'] = config.fetch('nan_status', 'green')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def status
|
|
58
|
+
@info['status'] = \
|
|
59
|
+
if !@info['enabled']
|
|
60
|
+
'clear'
|
|
61
|
+
elsif Time.now - @info['time'] > \
|
|
62
|
+
XymonClient.timestring_to_time(@info['lifetime'])
|
|
63
|
+
'purple'
|
|
64
|
+
elsif value.instance_of?(Float) && value.nan?
|
|
65
|
+
@info['threshold'].fetch('nan_status', 'red')
|
|
66
|
+
elsif @info['threshold'].key?('critical') && \
|
|
67
|
+
_threshold_reached?('critical')
|
|
68
|
+
'red'
|
|
69
|
+
elsif @info['threshold'].key?('warning') && \
|
|
70
|
+
_threshold_reached?('warning')
|
|
71
|
+
'yellow'
|
|
72
|
+
else
|
|
73
|
+
'green'
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def _threshold_reached?(threshold)
|
|
80
|
+
case @info['threshold'].fetch('order', '<')
|
|
81
|
+
when '<'
|
|
82
|
+
@info['value'] < @info['threshold'][threshold]
|
|
83
|
+
when '>'
|
|
84
|
+
@info['value'] > @info['threshold'][threshold]
|
|
85
|
+
when '<='
|
|
86
|
+
@info['value'] <= @info['threshold'][threshold]
|
|
87
|
+
when '>='
|
|
88
|
+
@info['value'] >= @info['threshold'][threshold]
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
##
|
|
94
|
+
class ServiceItemString < ServiceItem
|
|
95
|
+
def initialize(config)
|
|
96
|
+
super(config)
|
|
97
|
+
@info['threshold'] = config.fetch('threshold', {})
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def status
|
|
101
|
+
@info['status'] = \
|
|
102
|
+
if !@info['enabled']
|
|
103
|
+
'clear'
|
|
104
|
+
elsif Time.now - @info['time'] > \
|
|
105
|
+
XymonClient.timestring_to_time(@info['lifetime'])
|
|
106
|
+
'purple'
|
|
107
|
+
elsif @info['threshold'].key?('critical') && \
|
|
108
|
+
_threshold_reached?('critical')
|
|
109
|
+
'red'
|
|
110
|
+
elsif @info['threshold'].key?('warning') && \
|
|
111
|
+
_threshold_reached?('warning')
|
|
112
|
+
'yellow'
|
|
113
|
+
else
|
|
114
|
+
'green'
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
def _threshold_reached?(threshold)
|
|
121
|
+
inclusive = @info['threshold'].fetch('inclusive', true)
|
|
122
|
+
values = @info['value']
|
|
123
|
+
if values.instance_of?(Array)
|
|
124
|
+
value_is_included = values.any? do |value|
|
|
125
|
+
@info['threshold'][threshold].include?(value)
|
|
126
|
+
end
|
|
127
|
+
else
|
|
128
|
+
value_is_included = @info['threshold'][threshold].include?(values)
|
|
129
|
+
end
|
|
130
|
+
(inclusive && value_is_included) || (!inclusive && !value_is_included)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
data/lib/xymonclient.rb
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
require 'xymonclient/version'
|
|
2
|
+
require 'xymonclient/exception'
|
|
3
|
+
require 'xymonclient/discovery'
|
|
4
|
+
require 'xymonclient/helpers'
|
|
5
|
+
require 'socket'
|
|
6
|
+
|
|
7
|
+
module XymonClient
|
|
8
|
+
##
|
|
9
|
+
# Client object for interacting with Xymon server(s)
|
|
10
|
+
# Params:
|
|
11
|
+
# - servers: array of string 'hostname' or 'hostname:port'
|
|
12
|
+
# (port default to 1984)
|
|
13
|
+
class Client
|
|
14
|
+
attr_reader :servers
|
|
15
|
+
|
|
16
|
+
def initialize(servers = [])
|
|
17
|
+
@servers = \
|
|
18
|
+
if servers.empty?
|
|
19
|
+
XymonClient::ServerDiscovery.find_from_file
|
|
20
|
+
else
|
|
21
|
+
_parse_servers(servers)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def status(host, service, status, message, lifetime = '30m')
|
|
26
|
+
raise XymonClient::InvalidDuration, lifetime \
|
|
27
|
+
unless XymonClient.valid_duration?(lifetime)
|
|
28
|
+
raise XymonClient::InvalidStatus, status \
|
|
29
|
+
unless XymonClient.valid_status?(status)
|
|
30
|
+
_send_to_all(
|
|
31
|
+
"status+#{lifetime} " \
|
|
32
|
+
"#{XymonClient.hostsvc(host, service)} #{status} #{message}"
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def disable(host, service, duration, message)
|
|
37
|
+
raise XymonClient::InvalidDuration, duration \
|
|
38
|
+
unless XymonClient.valid_duration?(duration)
|
|
39
|
+
_send_to_all(
|
|
40
|
+
"disable #{XymonClient.hostsvc(host, service)} #{duration} #{message}"
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def enable(host, service)
|
|
45
|
+
_send_to_all("enable #{XymonClient.hostsvc(host, service)}")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def drop(host, service = '')
|
|
49
|
+
_send_to_all("drop #{host} #{service}")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def board(host, service, fields = [])
|
|
53
|
+
response = {}
|
|
54
|
+
@servers.each do |server|
|
|
55
|
+
response[server] = _send(
|
|
56
|
+
server,
|
|
57
|
+
"xymondboard host=#{host} test=#{service} fields=#{fields.join(';')}"
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
response
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def ack(host, service, duration, message)
|
|
64
|
+
raise XymonClient::InvalidDuration, duration \
|
|
65
|
+
unless XymonClient.valid_duration?(duration)
|
|
66
|
+
cookies = board(host, service, ['cookie'])
|
|
67
|
+
@servers.each do |server|
|
|
68
|
+
_send(
|
|
69
|
+
server,
|
|
70
|
+
"xymondack #{cookies[server].to_i} #{duration} #{message}"
|
|
71
|
+
) if cookies[server].to_i != -1
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def _send_to_all(message)
|
|
78
|
+
@servers.each { |server| _send(server, message) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def _send(server, message)
|
|
82
|
+
# TODO: validate response from all servers ( and retry ?)
|
|
83
|
+
socket = TCPSocket.open(server[:host], server[:port])
|
|
84
|
+
socket.puts message
|
|
85
|
+
socket.close_write
|
|
86
|
+
socket.gets
|
|
87
|
+
ensure
|
|
88
|
+
socket.close if socket
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def _parse_servers(servers = [])
|
|
92
|
+
raise XymonClient::NoXymonServerDefined if servers.empty?
|
|
93
|
+
servers.map do |server|
|
|
94
|
+
case server
|
|
95
|
+
when /[^:]+:[0-9]*/
|
|
96
|
+
{ host: server.split(':')[0], port: server.split(':')[1].to_i }
|
|
97
|
+
when /[^:]*/
|
|
98
|
+
{ host: server, port: 1984 }
|
|
99
|
+
else
|
|
100
|
+
raise XymonClient::InvalidServer, server
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'xymonclient/service'
|
|
3
|
+
require 'socket'
|
|
4
|
+
|
|
5
|
+
describe XymonClient do
|
|
6
|
+
describe XymonClient::Service do
|
|
7
|
+
let(:service) do
|
|
8
|
+
XymonClient::Service.new(
|
|
9
|
+
['localhost'],
|
|
10
|
+
'name' => 'service1',
|
|
11
|
+
'host' => 'myhost',
|
|
12
|
+
'header' => 'A sample header',
|
|
13
|
+
'footer' => 'A sample footer',
|
|
14
|
+
'items' => {
|
|
15
|
+
'ITEM1' => {
|
|
16
|
+
'label' => 'Gauge Item 1',
|
|
17
|
+
'type' => 'gauge',
|
|
18
|
+
'threshold' => {
|
|
19
|
+
'order' => '<',
|
|
20
|
+
'critical' => 5,
|
|
21
|
+
'warning' => 10,
|
|
22
|
+
'nan_status' => 'red'
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
'ITEM2' => {
|
|
26
|
+
'label' => 'String Item 2',
|
|
27
|
+
'type' => 'string',
|
|
28
|
+
'threshold' => {
|
|
29
|
+
'inclusive' => false,
|
|
30
|
+
'critical' => ['all is Ok !']
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'should send a valid status and details' do
|
|
38
|
+
service.update_item('ITEM1', 3)
|
|
39
|
+
service.update_item('ITEM2', 'all is Ok !')
|
|
40
|
+
allow(service).to receive(:_send) { '' }
|
|
41
|
+
# rubocop:disable LineLength
|
|
42
|
+
expect(service.status[0]).to eq('red')
|
|
43
|
+
expect(service.status[1]).to match(
|
|
44
|
+
/Generated at .* for 30m \nA sample header\n&red Gauge Item 1: 3\n&green String Item 2: all is Ok !\n\nA sample footer/
|
|
45
|
+
)
|
|
46
|
+
# rubocop:enable LineLength
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'xymonclient'
|
|
3
|
+
require 'xymonclient/serviceitem'
|
|
4
|
+
|
|
5
|
+
describe XymonClient do
|
|
6
|
+
describe XymonClient::ServiceItem do
|
|
7
|
+
let(:config) { { 'label' => 'Item 1' } }
|
|
8
|
+
|
|
9
|
+
it 'should return purple status at start' do
|
|
10
|
+
expect(described_class.new(config).status).to eq('purple')
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'should return red status on bad value' do
|
|
14
|
+
baditem = described_class.new(config)
|
|
15
|
+
baditem.value = 'badstatus'
|
|
16
|
+
expect(baditem.status).to eq('red')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'should return clear status if disabled' do
|
|
20
|
+
config['enabled'] = false
|
|
21
|
+
expect(described_class.new(config).status).to eq('clear')
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe XymonClient::ServiceItemGauge do
|
|
26
|
+
let(:config) { { 'label' => 'Item 1' } }
|
|
27
|
+
|
|
28
|
+
it 'should return red status when value is above critical threshold' do
|
|
29
|
+
config['threshold'] = { 'order' => '>', 'critical' => 10, 'warning' => 5 }
|
|
30
|
+
item = described_class.new(config)
|
|
31
|
+
item.value = 11
|
|
32
|
+
expect(item.status).to eq('red')
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'should return yellow status when value is between warning and ' \
|
|
36
|
+
'critical threshold' do
|
|
37
|
+
config['threshold'] = { 'order' => '>', 'critical' => 10, 'warning' => 5 }
|
|
38
|
+
item = described_class.new(config)
|
|
39
|
+
item.value = 7
|
|
40
|
+
expect(item.status).to eq('yellow')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'should return green status when value is under warning threshold' do
|
|
44
|
+
config['threshold'] = { 'order' => '>', 'critical' => 10, 'warning' => 5 }
|
|
45
|
+
item = described_class.new(config)
|
|
46
|
+
item.value = 3
|
|
47
|
+
expect(item.status).to eq('green')
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe XymonClient::ServiceItemString do
|
|
52
|
+
describe 'inclusive' do
|
|
53
|
+
let(:config) { { 'label' => 'Item 1', 'threshold' => {'inclusive' => true, 'critical' => ['foo'], 'warning' => ['bar']}} }
|
|
54
|
+
it 'should return red status when value is included in critical threshold' do
|
|
55
|
+
item = described_class.new(config)
|
|
56
|
+
item.value = 'foo'
|
|
57
|
+
expect(item.status).to eq('red')
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'should return yellow status when value is included in warning threshold' do
|
|
61
|
+
item = described_class.new(config)
|
|
62
|
+
item.value = 'bar'
|
|
63
|
+
expect(item.status).to eq('yellow')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'should return green status when values is not included in critical/warning threshold' do
|
|
67
|
+
item = described_class.new(config)
|
|
68
|
+
item.value = 'other'
|
|
69
|
+
expect(item.status).to eq('green')
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe 'exclusive' do
|
|
74
|
+
let(:config) { { 'label' => 'Item 1', 'threshold' => {'inclusive' => false, 'critical' => ['foo'], 'warning' => ['bar']}} }
|
|
75
|
+
it 'should return red status when "foo" is not included in critical threshold' do
|
|
76
|
+
item = described_class.new(config)
|
|
77
|
+
item.value = ['bar']
|
|
78
|
+
expect(item.status).to eq('red')
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'should return yellow status when "bar" is not included in warning threshold' do
|
|
82
|
+
item = described_class.new(config)
|
|
83
|
+
item.value = ['foo']
|
|
84
|
+
expect(item.status).to eq('yellow')
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'should return green status when all values are included in critical/warning threshold' do
|
|
88
|
+
item = described_class.new(config)
|
|
89
|
+
item.value = ['foo', 'bar']
|
|
90
|
+
expect(item.status).to eq('green')
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'xymonclient/helpers'
|
|
3
|
+
|
|
4
|
+
describe XymonClient do
|
|
5
|
+
describe XymonClient::Client do
|
|
6
|
+
let(:client) { XymonClient::Client.new(['foo', 'bar:19840']) }
|
|
7
|
+
|
|
8
|
+
it 'should send a status with default lifetime (30m)' do
|
|
9
|
+
allow(client).to receive(:_send) { '' }
|
|
10
|
+
expect(client).to receive(:_send).with(
|
|
11
|
+
kind_of(Hash),
|
|
12
|
+
'status+30m my,host.myservice green all is good'
|
|
13
|
+
)
|
|
14
|
+
client.status('my.host', 'myservice', 'green', 'all is good')
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: xymonclient
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- David Chauviere
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2016-11-14 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.13'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.13'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '10.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '10.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rubocop
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - '='
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0.40'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - '='
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0.40'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: simplecov
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 0.12.0
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: 0.12.0
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: codeclimate-test-reporter
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '1.0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '1.0'
|
|
97
|
+
description: Interact with Xymon server, send status, ack, enable/disable
|
|
98
|
+
email:
|
|
99
|
+
- david.chauviere@orange.com
|
|
100
|
+
executables: []
|
|
101
|
+
extensions: []
|
|
102
|
+
extra_rdoc_files: []
|
|
103
|
+
files:
|
|
104
|
+
- Gemfile
|
|
105
|
+
- Gemfile.lock
|
|
106
|
+
- LICENSE
|
|
107
|
+
- README.md
|
|
108
|
+
- Rakefile
|
|
109
|
+
- bin/console
|
|
110
|
+
- bin/setup
|
|
111
|
+
- lib/xymonclient.rb
|
|
112
|
+
- lib/xymonclient/discovery.rb
|
|
113
|
+
- lib/xymonclient/exception.rb
|
|
114
|
+
- lib/xymonclient/helpers.rb
|
|
115
|
+
- lib/xymonclient/service.rb
|
|
116
|
+
- lib/xymonclient/serviceitem.rb
|
|
117
|
+
- lib/xymonclient/version.rb
|
|
118
|
+
- spec/service_spec.rb
|
|
119
|
+
- spec/serviceitem_spec.rb
|
|
120
|
+
- spec/spec_helper.rb
|
|
121
|
+
- spec/xymonclient_spec.rb
|
|
122
|
+
homepage: https://github.com/dchauviere/ruby-xymonclient
|
|
123
|
+
licenses:
|
|
124
|
+
- Apache-2.0
|
|
125
|
+
metadata: {}
|
|
126
|
+
post_install_message:
|
|
127
|
+
rdoc_options: []
|
|
128
|
+
require_paths:
|
|
129
|
+
- lib
|
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
131
|
+
requirements:
|
|
132
|
+
- - ">="
|
|
133
|
+
- !ruby/object:Gem::Version
|
|
134
|
+
version: '0'
|
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
136
|
+
requirements:
|
|
137
|
+
- - ">="
|
|
138
|
+
- !ruby/object:Gem::Version
|
|
139
|
+
version: '0'
|
|
140
|
+
requirements: []
|
|
141
|
+
rubyforge_project:
|
|
142
|
+
rubygems_version: 2.6.6
|
|
143
|
+
signing_key:
|
|
144
|
+
specification_version: 4
|
|
145
|
+
summary: Xymon client library
|
|
146
|
+
test_files: []
|
|
147
|
+
has_rdoc:
|