rubix 0.0.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.
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