rubix 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Dhruv Bansal
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,262 @@
1
+ = Rubix
2
+
3
+ Rubix is a Ruby client for Zabbix[http://www.zabbix.com/]. It has a
4
+ few goals:
5
+
6
+ - Provide a +Connection+ and +Response+ class that abstract away the
7
+ complexity[http://www.zabbix.com/documentation/1.8/api/getting_started]
8
+ of authenticating with and sending API requests to Zabbix.
9
+
10
+ - Provide an ORM for Zabbix resources like Hosts, HostGroups,
11
+ Templates, &c. using the {Zabbix
12
+ API}[http://www.zabbix.com/documentation/1.8/api].
13
+
14
+ - Provide a command-line script that wraps the
15
+ {+zabbix_sender+}[http://www.zabbix.com/documentation/1.8/manpages/zabbix_sender]
16
+ utility, allowing it to consume JSON data and to 'auto-vivify'
17
+ hosts, hostgroups, applications, and items.
18
+
19
+ - Provide some +Monitor+ classes that make it easy to write scripts
20
+ that periodically measure something and push it to Zabbix.
21
+
22
+ - Have as few dependencies as possible: the core classes only depend
23
+ on Ruby 1.8 standard library & +JSON+ and scripts additionally
24
+ depend on +Configliere+[http://github.com/mrflip/configliere].
25
+
26
+ There are a lot of other projects out there that connect Ruby to
27
+ Zabbix. Here's a quick list:
28
+
29
+ zabbix[http://github.com/lorieri/zabbix]::
30
+ zabbix aws templates, scripts, chef automations
31
+
32
+ zabbixapi[http://github.com/verm666/zabbixapi]::
33
+ Ruby module for work with zabbix api
34
+
35
+ zabbix-rb[http://github.com/mhat/zabbix-rb]::
36
+ send data to zabbix from ruby
37
+
38
+ zabbix_pusher[http://github.com/iteh/zabbix_pusher]::
39
+ zabbix_pusher is a gem to parse zabbix templates and push the data
40
+ to the corresponding zabbix server
41
+
42
+ zabbix-trappers[http://github.com/vzctl/zabbix-trappers]::
43
+ Collection of ruby scripts for zabbix trappers
44
+
45
+ rzabbix[http://github.com/neerfri/rzabbix]::
46
+ Zabbix API client for Ruby
47
+
48
+ zabboard[http://github.com/yammer/zabboard]::
49
+ zabbix analytics
50
+
51
+ zabbix-web[http://github.com/legiar/zabbix-web]::
52
+ Zabbix frontend
53
+
54
+ zabcon[http://trac.red-tux.net/]::
55
+ Zabcon is a command line interface for Zabbix written in Ruby
56
+
57
+ None of these projects was satisfactory for our purposes so I decided
58
+ to write Rubix. The name is terrible but the code is better. Enjoy!
59
+
60
+ == Connections, Requests, & Responses
61
+
62
+ Getting connected is easy
63
+
64
+ require 'rubix'
65
+
66
+ # Provide API URL & credentials. These are the defaults.
67
+ Rubix.connect('http://localhost/api_jsonrpc.php', 'admin', 'zabbix')
68
+
69
+ As per the {Zabbixi API
70
+ documentation}[http://www.zabbix.com/documentation/1.8/api], each
71
+ request to the Zabbix API needs four values:
72
+
73
+ +id+::
74
+ an integer identifying the request ID.
75
+
76
+ +auth+::
77
+ a string confirming that the API request is authenticated.
78
+
79
+ +method+::
80
+ the name of the API method you're calling, e.g. - <tt>host.get</tt>, <tt>template.delete</tt>, &c.
81
+
82
+ +params+::
83
+ parameters for the invocation of the +method+.
84
+
85
+ When you send a request, Rubix only requires you to specify the
86
+ +method+ and the +params+, handling the +id+ and authentication
87
+ quietly for you:
88
+
89
+ response = Rubix.connection.request('host.get', 'filter' => { 'host' => 'My Zabbix Host' })
90
+
91
+ # We can check for errors, whether they are non-200 responses or 200
92
+ responses with a Zabbix API error.
93
+ puts response.error_message unless response.success?
94
+
95
+ # See the result
96
+ puts response.result
97
+ #=> [{"hostid"=>"10017"}]
98
+
99
+ === On the command line
100
+
101
+ Rubix comes with a command line utility +zabbix_api+ which lets you
102
+ issue these sorts of requests directly on the command line.
103
+
104
+ $ zabbix_api host.get '{"filter": {"host": "My Zabbix Host"}}'
105
+ [{"hostid"=>"10017"}]
106
+
107
+ +zabbix_api+ lets you specify the credentials and will pretty-print
108
+ responses for you.
109
+
110
+ == ORM
111
+
112
+ If you don't want to deal with the particulars of the Zabbix API
113
+ itself, Rubix provides a set of classes that you can use instead.
114
+
115
+ The following example goes through setting up an item on a host
116
+ complete with host groups, templates, applications, and so on.
117
+
118
+ require 'rubix'
119
+ Rubix.connect('http://localhost/api_jsonrpc.php', 'admin', 'zabbix')
120
+
121
+ # Ensure the host group we want exists.
122
+ host_group = Rubix::HostGroup.new(:name => "My Zabbix Hosts")
123
+ host_group.create unless host_group.exists?
124
+
125
+ # Now the template -- created templates are empty by default!
126
+ template = Rubix::Template.new(:name => "Template_Some_Service")
127
+ template.create unless template.exists?
128
+
129
+ # Now the host.
130
+ host = Rubix::Host.new(:name => "My Host", :ip => '123.123.123.123', :templates => [template], :host_groups => [host_group])
131
+ # 'register' means the same as 'create unless exist' above...
132
+ host.register
133
+
134
+ # Now for the application
135
+ app = Rubix::Application.new(:name => 'Some App', :host => host)
136
+ app.register
137
+
138
+ # Now the item
139
+ item = Rubix::Item.new(:host => host, :key => 'foo.bar.baz', :description => "Some Item", :value_type => :unsigned_int, :applications => [app])
140
+ item.register
141
+
142
+ You can also +update+, +destroy+ or +unregister+ ('destroy if exists')
143
+ resources.
144
+
145
+ Only host groups, templates, hosts, applications, and items are
146
+ available at present. Other Zabbix resources (actions, alerts,
147
+ events, maps, users, &c.) can be similarly wrapped, they just haven't
148
+ been because we don't use them as much as we use these core resources.
149
+
150
+ == Sender
151
+
152
+ Rubix comes with a +zabbix_pipe+ script which, coupled with the
153
+ {+zabbix_sender+}[http://www.zabbix.com/documentation/1.8/manpages/zabbix_sender]
154
+ utility, allows for writing data to Zabbix.
155
+
156
+ By the design of Zabbix, all data written via +zabbix_sender+ (and
157
+ therefore +zabbix_pipe+) must be written to items with type "Zabbix
158
+ trapper". This type instructs Zabbix to accept values written by
159
+ +zabbix_sender+.
160
+
161
+ +zabbix_pipe+ can consume data from +STDIN+, a file on disk, or a
162
+ {named pipe}[http://en.wikipedia.org/wiki/Named_pipe]. It consumes
163
+ data one line at a time from any of these sources and uses the
164
+ +zabbix_sender+ utility to send this data to a Zabbix server.
165
+
166
+ Here's an example of starting +zabbix_pipe+ and sending some data onto
167
+ Zabbix. (Credentials and addresses for the Zabbix server and the
168
+ Zabbix API can all be customized; try the <tt>--help</tt> option.)
169
+
170
+ # Send one data point
171
+ $ echo "foo.bar.baz 123" | zabbix_pipe --host='My Zabbix Host'
172
+ # Send a bunch of data points in a file
173
+ $ cat my_data.tsv | zabbix_pipe --host='My Zabbix Host'
174
+
175
+ You can also pass the file directly:
176
+
177
+ # Send a bunch of data points in a file
178
+ $ zabbix_pipe --host='My Zabbix Host' my_data.tsv
179
+
180
+ You can also listen from a named pipe. This is useful on a
181
+ "production" system in which many processes may want to simply and
182
+ easily write somewhere without worrying about what happens.
183
+
184
+ # In the first terminal
185
+ $ mkfifo /dev/zabbix
186
+ $ zabbix_pipe --pipe=/dev/zabbix --host='My Zabbix Host'
187
+
188
+ # In another terminal
189
+ $ echo "foo.bar.baz 123" > /dev/zabbix
190
+ $ cat my_data > /dev/zabbix
191
+
192
+ In any of these modes, you can also send JSON data directly.
193
+
194
+ $ echo '{"data": [{"key": "foo.bar.baz", "value": 123}, {"key": "foo.bar.baz", "value": 101}]}' > /dev/zabbix
195
+
196
+ This simple block of JSON doesn't really add any power to
197
+ +zabbix_pipe+. What becomes more interesting is that we can wedge in
198
+ data for *diffferent* hosts, items, &c. when using JSON input:
199
+
200
+ $ echo '{"host": "My first host", "data": [{"key": "foo.bar.baz", "value": 123}, {"key": "foo.bar.baz", "value": 101}]}' > /dev/zabbix
201
+ $ echo '{"host": "My second host", "data": [{"key": "foo.bar.baz", "value": 123}, {"key": "foo.bar.baz", "value": 101}]}' > /dev/zabbix
202
+ $ echo '{"host": "My third host", "data": [{"key": "foo.bar.baz", "value": 123}, {"key": "foo.bar.baz", "value": 101}]}' > /dev/zabbix
203
+
204
+ Rubix will switch hosts on the fly.
205
+
206
+ === Auto-vivification
207
+
208
+ By default, for every item written into +zabbix_pipe+, Rubix will
209
+ first check, using the Zabbix API, whether an item with the given key
210
+ exists for the given host. If not, Rubix will create the host and
211
+ the item. You can pass a few details along with your data (when
212
+ writing in JSON format) or at startup of the +zabbix_pipe+ to tune
213
+ some of the properites of newly created hosts and items:
214
+
215
+ $ echo '{"host": "My host", "hostgroups": "Host Group 1,Host Group 2", "templates": "Template 1,Template 2", "applications": "App 1, App2", "data": [{"key": "foo.bar.baz", "value": 123}, {"key": "foo.bar.baz", "value": 101}]}' > /dev/zabbix
216
+
217
+ If the host 'My host' does not exist, Rubix will create it, putting in
218
+ each of the host groups 'Host Group 1' and 'Host Group 2' (which will
219
+ also be auto-vivified), and attach it to templates 'Template 1' and
220
+ 'Template 2' (auto-vivified). If the item does not exist for the host
221
+ 'My host', then it will be created and put inside applications 'App1'
222
+ and 'App2' (auto-vivified). The value type of the item (unsigned int,
223
+ float, character, text, &c.) will be chosen dynamically based on the
224
+ value being written. The created items will all be of the type "Zabbix
225
+ trapper" so that they can be written to.
226
+
227
+ Auto-vivification is intended to make it easy to register a lot of
228
+ dynamic hosts and items in Zabbix. This is perfect for cloud
229
+ deployments where resources to be monitored are often dynamic.
230
+
231
+ ==== Failed writes
232
+
233
+ By the design of Zabbix, a newly created item of type 'Zabbix trapper'
234
+ will not accept data for some interval, typically a minute or so,
235
+ after being created. This means that when writing a series of values
236
+ for some non-existent item 'foo.bar.baz', the first write will cause
237
+ the item to be created (auto-vivified), the next several writes will
238
+ fail as the Zabbix server is not accepting writes for this item yet,
239
+ and then all writes will begin to succeed as the Zabbix server catches
240
+ up. If it is absolutely essential for all writes to succeed,
241
+ including the first, then +zabbix_pipe+ needs to go to sleep for a
242
+ while after creating a new item in order to give the Zabbix server
243
+ time to catch up. This can be configured with the
244
+ <tt>--create_item_sleep</tt> option. By default this is set to 0.
245
+
246
+ ==== Skipping auto-vivification
247
+
248
+ Attempting to auto-vivify keys on every single write is expensive and
249
+ does not scale. It's recommended, therefore, to run +zabbix_pipe+
250
+ with the <tt>--fast</tt> flag in production settings. In this mode,
251
+ +zabbix_pipe+ will not attempt to auto-vivify anything -- if items do
252
+ not exist, writes will just fail, as they do with +zabbix_sender+
253
+ itself.
254
+
255
+ A good pattern is to set up a production pipe (in <tt>--fast</tt>)
256
+ mode at <tt>/dev/zabbix</tt> and to do all development/deployment with
257
+ a separate copy of +zabbix_pipe+. Once development/deployment is
258
+ complete and all hosts, groups, templates, applications, and items
259
+ have been created, switch to using production mode.
260
+
261
+ == Monitors
262
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ RUBIX_ROOT = File.expand_path('../../lib', __FILE__)
4
+ $: << RUBIX_ROOT unless $:.include?(RUBIX_ROOT)
5
+
6
+ require 'rubix'
7
+ require 'configliere'
8
+
9
+ Settings.use :commandline
10
+ Settings.use :env_var
11
+
12
+ Settings.define :pretty, :description => "Pretty print output", :required => true, :type => :boolean, :default => false
13
+ Settings.define :url, :description => "URL for the Zabbix API", :required => true, :default => 'http://localhost/api_jsonrpc.php'
14
+ Settings.define :username, :description => "Username for the Zabbix API", :required => true, :env_var => "ZABBIX_API_USERNAME", :default => 'admin'
15
+ Settings.define :password, :description => "Password for the Zabbix API", :required => true, :env_var => "ZABBIX_API_PASSWORD", :default => 'zabbix'
16
+ Settings.define :server_error_sleep, :description => "Seconds to sleep after a 50x error from the server", :required => true, :type => Integer, :default => 1
17
+
18
+ def Settings.usage
19
+ %Q{usage: #{raw_script_name} [...--param=val...] METHOD PARAMS}
20
+ end
21
+
22
+ if $0 == __FILE__
23
+
24
+ begin
25
+ Settings.resolve!
26
+ rescue RuntimeError => e
27
+ puts e.message
28
+ Settings.dump_help
29
+ exit(1)
30
+ end
31
+
32
+ if Settings.rest.size < 2
33
+ Settings.dump_help
34
+ exit(1)
35
+ end
36
+
37
+ method = Settings.rest[0]
38
+ json_params = Settings.rest[1]
39
+
40
+ begin
41
+ Rubix.connect(Settings[:url], Settings[:username], Settings[:password])
42
+
43
+ params = JSON.parse(json_params)
44
+ response = Rubix.connection.request(method, params)
45
+
46
+ if Settings[:pretty]
47
+ puts JSON.pretty_generate(response.parsed)
48
+ else
49
+ puts response.parsed.to_json
50
+ end
51
+
52
+ rescue JSON::ParserError => e
53
+ puts "Invalid JSON -- #{e.message}"
54
+ exit(1)
55
+ rescue Rubix::Error => e
56
+ puts e.message
57
+ exit(1)
58
+ end
59
+
60
+ end
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ RUBIX_ROOT = File.expand_path('../../lib', __FILE__)
4
+ $: << RUBIX_ROOT unless $:.include?(RUBIX_ROOT)
5
+
6
+ require 'rubix'
7
+ require 'configliere'
8
+
9
+ Settings.use :commandline
10
+ Settings.use :env_var
11
+
12
+ Settings.define 'verbose', :description => "Be verbose", :required => false, :type => :boolean, :default => false
13
+
14
+ #
15
+ # Configuration for talking with the Zabbix server
16
+ #
17
+ Settings.define 'server', :description => "Hostname/IP of the Zabbix server", :required => true
18
+ Settings.define 'host', :description => "Name of the (possibly new) Zabbix host to write data to", :required => true
19
+
20
+ #
21
+ # Configuration for talking with the Zabbix API
22
+ #
23
+ Settings.define 'url', :description => "URL of the Zabbix API", :required => true, :default => 'localhost/api_jsonrpc.php'
24
+ Settings.define 'username', :description => "Username for the Zabbix API", :required => false, :env_var => "ZABBIX_API_USERNAME", :default => 'admin'
25
+ Settings.define 'password', :description => "Password for the Zabbix API", :required => false, :env_var => "ZABBIX_API_PASSWORD", :default => 'zabbix'
26
+ Settings.define 'server_error_sleep', :description => "Seconds to sleep after a 50x error from the server", :required => true, :type => Integer, :default => 1
27
+ Settings.define 'fast', :description => "Run *fast* by not bothering to auto-vivify objects via the API", :type => :boolean, :default => false
28
+
29
+ #
30
+ # Define parameters for dynamically creating a new host.
31
+ #
32
+ Settings.define 'host_groups', :description => "Comma-separated hostgroup names to use when creating a new host", :required => false
33
+ Settings.define 'templates', :description => "Comma-separated template names to use when creating a new host", :required => false
34
+
35
+ #
36
+ # Define parameters for dynamically creating a new item.
37
+ #
38
+ Settings.define 'create_item_sleep', :description => "Number of seconds to sleep after creating a new Item", :required => true, :default => 0, :type => Integer
39
+ Settings.define 'application', :description => "Name of the Application created Items will be scoped by", :required => false
40
+
41
+ #
42
+ # Define local paths will be used to read and send data.
43
+ #
44
+ Settings.define 'sender', :description => "Path to the zabbix_sender program", :required => true, :default => 'zabbix_sender'
45
+ Settings.define 'pipe', :description => "Path to the named pipe to read from", :required => false
46
+ Settings.define 'pipe_read_sleep', :description => "Seconds to sleep between reads from pipe", :required => true, :type => Float, :default => 0.1
47
+
48
+ if $0 == __FILE__
49
+ begin
50
+ Settings.resolve!
51
+ rescue RuntimeError => e
52
+ $stderr.puts e.message
53
+ exit(1)
54
+ end
55
+
56
+ begin
57
+ Rubix.connect((Settings[:url] || File.join(Settings[:server], 'api_jsonrpc.php')), Settings[:username], Settings[:password])
58
+
59
+ rescue Rubix::Error => e
60
+ $stderr.puts e.message
61
+ exit(1)
62
+ end
63
+
64
+ begin
65
+ sender = Rubix::Sender.new(Settings)
66
+ rescue Rubix::Error => e
67
+ $stderr.puts e.message
68
+ exit(1)
69
+ end
70
+
71
+ begin
72
+ sender.run
73
+ rescue => e
74
+ $stderr.puts "#{e.class} -- #{e.message}"
75
+ end
76
+ end
77
+
@@ -0,0 +1,42 @@
1
+ require 'rubygems'
2
+ require 'rubix/log'
3
+ module Rubix
4
+
5
+ def self.connect server, username=nil, password=nil
6
+ self.connection = Connection.new(server, username, password)
7
+ end
8
+
9
+ def self.connection= connection
10
+ @connection = connection
11
+ end
12
+
13
+ def self.connection
14
+ @connection ||= Connection.new('http://localhost/api_jsonrpc.php', 'admin', 'zabbix')
15
+ return @connection if @connection.authorized?
16
+ raise ConnectionError.new("Could not authorize with Zabbix API at #{@connection.uri}") unless @connection.authorize!
17
+ @connection
18
+ end
19
+
20
+ autoload :Connection, 'rubix/connection'
21
+ autoload :Response, 'rubix/response'
22
+
23
+ autoload :Model, 'rubix/model'
24
+ autoload :HostGroup, 'rubix/models/host_group'
25
+ autoload :Template, 'rubix/models/template'
26
+ autoload :Host, 'rubix/models/host'
27
+ autoload :Item, 'rubix/models/item'
28
+ autoload :Application, 'rubix/models/application'
29
+
30
+ autoload :Monitor, 'rubix/monitor'
31
+ autoload :ChefMonitor, 'rubix/monitors/chef_monitor'
32
+ autoload :ClusterMonitor, 'rubix/monitors/cluster_monitor'
33
+
34
+ autoload :Sender, 'rubix/sender'
35
+
36
+ Error = Class.new(RuntimeError)
37
+ ConnectionError = Class.new(Error)
38
+ AuthenticationError = Class.new(Error)
39
+ RequestError = Class.new(Error)
40
+ ValidationError = Class.new(Error)
41
+
42
+ end