rubix 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +10 -10
- data/VERSION +1 -1
- data/lib/rubix.rb +40 -1
- data/lib/rubix/associations/belongs_to_host.rb +4 -3
- data/lib/rubix/connection.rb +114 -34
- data/lib/rubix/log.rb +45 -1
- data/lib/rubix/models/model.rb +143 -9
- data/lib/rubix/response.rb +85 -13
- data/lib/rubix/sender.rb +122 -18
- data/spec/requests/application_request_spec.rb +36 -38
- data/spec/requests/host_group_request_spec.rb +40 -22
- data/spec/requests/host_request_spec.rb +55 -49
- data/spec/requests/item_request_spec.rb +49 -53
- data/spec/requests/template_request_spec.rb +34 -34
- data/spec/requests/user_macro_request_spec.rb +33 -27
- data/spec/spec_helper.rb +1 -13
- data/spec/support/integration_helper.rb +34 -1
- data/spec/test.yml +11 -3
- metadata +22 -8
data/lib/rubix/sender.rb
CHANGED
@@ -2,36 +2,69 @@ require 'rubix/log'
|
|
2
2
|
|
3
3
|
module Rubix
|
4
4
|
|
5
|
+
# A class used to send data to Zabbix.
|
6
|
+
#
|
7
|
+
# This sender is used to implement the logic for the +zabbix_pipe+
|
8
|
+
# utility. It is initialized with some metadata about a host, its
|
9
|
+
# host groups and templates, and applications into which items
|
10
|
+
# should be written, and it can then accept data and forward it to a
|
11
|
+
# Zabbix server using the +zabbix_sender+ utility that comes with
|
12
|
+
# Zabbix.
|
13
|
+
#
|
14
|
+
# A sender can be given data in either TSV or JSON formats. With
|
15
|
+
# the JSON format, it is possible to embed data for hosts, host
|
16
|
+
# groups, &c. distinct from that with which this sender was
|
17
|
+
# initialized. This is a useful way to send many different kinds of
|
18
|
+
# data through the same process.
|
19
|
+
#
|
20
|
+
# The sender will also auto-vivify any hosts, host gruops,
|
21
|
+
# templates, applications, and items it needs in order to be able to
|
22
|
+
# write data. This is expensive in terms of time so it can be
|
23
|
+
# turned off using the <tt>--fast</tt> option.
|
5
24
|
class Sender
|
6
25
|
|
7
26
|
include Logs
|
8
27
|
|
9
|
-
#
|
28
|
+
# @return [Hash] settings
|
10
29
|
attr_accessor :settings
|
11
30
|
|
12
|
-
#
|
31
|
+
# @return [Rubix::Host] the host the Sender will send data for
|
13
32
|
attr_accessor :host
|
14
33
|
|
15
|
-
#
|
34
|
+
# @return [Array<Rubix::HostGroup>] the hostgroups used to create this host
|
16
35
|
attr_accessor :host_groups
|
17
36
|
|
18
|
-
#
|
37
|
+
# @return [Array<Rubix::Template>] the templates used to create this host
|
19
38
|
attr_accessor :templates
|
20
39
|
|
21
|
-
# The applications used to create items
|
40
|
+
# @return [Array<Rubix::Application>] The applications used to create items
|
22
41
|
attr_accessor :applications
|
23
42
|
|
24
43
|
#
|
25
|
-
# Initialization
|
44
|
+
# == Initialization ==
|
26
45
|
#
|
27
46
|
|
47
|
+
# Create a new sender with the given +settings+.
|
48
|
+
#
|
49
|
+
# @param [Hash, Configliere::Param] settings
|
50
|
+
# @param settings [String] host the name of the Zabbix host to write data for
|
51
|
+
# @param settings [String] host_groups comma-separated names of Zabbix host groups the host should belong to
|
52
|
+
# @param settings [String] templates comma-separated names of Zabbix templates the host should belong to
|
53
|
+
# @param settings [String] applications comma-separated names of applications created items should be scoped under
|
54
|
+
# @param settings [String] server URL for the Zabbix server -- *not* the URL for the Zabbix API
|
55
|
+
# @param settings [String] configuration_file path to a local Zabbix configuration file as used by the +zabbix_sender+ utility
|
56
|
+
# @param settings [true, false] verbose be verbose during execution
|
57
|
+
# @param settings [true, false] fast auto-vivify (slow) or not (fast)
|
58
|
+
# @param settings [String] pipe path to a named pipe to be read from
|
59
|
+
# @param settings [Fixnum] pipe_read_sleep seconds to sleep after an empty read from the a named pipe
|
60
|
+
# @param settings [Fixnum] create_item_sleep seconds to sleep after creating a new item
|
28
61
|
def initialize settings
|
29
62
|
@settings = settings
|
30
63
|
confirm_settings
|
31
64
|
if fast?
|
32
65
|
info("Forwarding for #{settings['host']}...") if settings['verbose']
|
33
66
|
else
|
34
|
-
|
67
|
+
initialize_host_groups
|
35
68
|
initialize_templates
|
36
69
|
initialize_host
|
37
70
|
initialize_applications
|
@@ -39,29 +72,52 @@ module Rubix
|
|
39
72
|
end
|
40
73
|
end
|
41
74
|
|
75
|
+
# Is this sender running in 'fast' mode? If so, it will *not*
|
76
|
+
# auto-vivify any hosts, groups, items, &c.
|
77
|
+
#
|
78
|
+
# @return [true, false]
|
42
79
|
def fast?
|
43
80
|
settings['fast']
|
44
81
|
end
|
45
82
|
|
83
|
+
# Will this sender auto-vivify hosts, groups, items, &c.?
|
84
|
+
#
|
85
|
+
# @return [true, false]
|
46
86
|
def auto_vivify?
|
47
87
|
!fast?
|
48
88
|
end
|
49
|
-
|
50
|
-
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
# Find or create necessary host groups.
|
93
|
+
#
|
94
|
+
# @return [Array<Rubix::HostGroup>]
|
95
|
+
def initialize_host_groups
|
51
96
|
self.host_groups = settings['host_groups'].split(',').flatten.compact.map { |group_name | HostGroup.find_or_create(:name => group_name.strip) }
|
52
97
|
end
|
53
98
|
|
99
|
+
# Find necessary templates.
|
100
|
+
#
|
101
|
+
# @return [Array<Rubix::Template>]
|
54
102
|
def initialize_templates
|
55
103
|
self.templates = (settings['templates'] || '').split(',').flatten.compact.map { |template_name | Template.find(:name => template_name.strip) }.compact
|
56
104
|
end
|
57
105
|
|
106
|
+
# Find or create the host for this data. Host groups and
|
107
|
+
# templates will automatically be attached.
|
108
|
+
#
|
109
|
+
# @return [Rubix::Host]
|
58
110
|
def initialize_host
|
59
111
|
self.host = (Host.find(:name => settings['host']) || Host.new(:name => settings['host']))
|
60
|
-
host.host_groups = host_groups
|
61
|
-
host.templates = templates
|
112
|
+
host.host_groups = ((host.host_groups || []) + host_groups).flatten.compact.uniq
|
113
|
+
host.templates = ((host.templates || []) + templates).flatten.compact.uniq
|
62
114
|
host.save
|
115
|
+
host
|
63
116
|
end
|
64
117
|
|
118
|
+
# Find or create the applications for this data.
|
119
|
+
#
|
120
|
+
# @return [Array<Rubix::Application>]
|
65
121
|
def initialize_applications
|
66
122
|
application_names = (settings['applications'] || '').split(',').flatten.compact
|
67
123
|
self.applications = []
|
@@ -78,19 +134,28 @@ module Rubix
|
|
78
134
|
end
|
79
135
|
end
|
80
136
|
end
|
137
|
+
self.applications
|
81
138
|
end
|
82
139
|
|
140
|
+
# Check that all settings are correct in order to be able to
|
141
|
+
# successfully write data to Zabbix.
|
83
142
|
def confirm_settings
|
84
143
|
raise ConnectionError.new("Must specify a Zabbix server to send data to.") unless settings['server']
|
85
144
|
raise Error.new("Must specify the path to a local configuraiton file") unless settings['configuration_file'] && File.file?(settings['configuration_file'])
|
86
145
|
raise ConnectionError.new("Must specify the name of a host to send data for.") unless settings['host']
|
87
146
|
raise ValidationError.new("Must define at least one host group.") if auto_vivify? && (settings['host_groups'].nil? || settings['host_groups'].empty?)
|
88
147
|
end
|
148
|
+
|
149
|
+
public
|
89
150
|
|
90
151
|
#
|
91
|
-
#
|
152
|
+
# == Sending Data ==
|
92
153
|
#
|
93
154
|
|
155
|
+
# Run this sender.
|
156
|
+
#
|
157
|
+
# Will read from the correct source of data and exit the Ruby
|
158
|
+
# process once the source is consumed.
|
94
159
|
def run
|
95
160
|
case
|
96
161
|
when settings['pipe']
|
@@ -104,8 +169,12 @@ module Rubix
|
|
104
169
|
end
|
105
170
|
exit(0)
|
106
171
|
end
|
172
|
+
|
173
|
+
protected
|
107
174
|
|
108
|
-
# Process each line of
|
175
|
+
# Process each line of a file.
|
176
|
+
#
|
177
|
+
# @param [String] path the path to the file to process
|
109
178
|
def process_file path
|
110
179
|
f = File.new(path)
|
111
180
|
process_file_handle(f)
|
@@ -118,6 +187,10 @@ module Rubix
|
|
118
187
|
end
|
119
188
|
|
120
189
|
# Process each line read from the pipe.
|
190
|
+
#
|
191
|
+
# The pipe will be opened in a non-blocking read mode. This
|
192
|
+
# sender will wait 'pipe_read_sleep' seconds between successive
|
193
|
+
# empty reads.
|
121
194
|
def process_pipe
|
122
195
|
# We want to open this pipe in non-blocking read mode b/c
|
123
196
|
# otherwise this process becomes hard to kill.
|
@@ -133,7 +206,9 @@ module Rubix
|
|
133
206
|
f.close
|
134
207
|
end
|
135
208
|
|
136
|
-
# Process each line of a given file handle
|
209
|
+
# Process each line of a given file handle.
|
210
|
+
#
|
211
|
+
# @param [File] f the file to process
|
137
212
|
def process_file_handle f
|
138
213
|
begin
|
139
214
|
line = f.readline
|
@@ -152,7 +227,12 @@ module Rubix
|
|
152
227
|
end
|
153
228
|
end
|
154
229
|
end
|
230
|
+
|
231
|
+
public
|
155
232
|
|
233
|
+
# Process a single line of text.
|
234
|
+
#
|
235
|
+
# @param [String] line
|
156
236
|
def process_line line
|
157
237
|
if looks_like_json?(line)
|
158
238
|
process_line_of_json_in_new_pipe(line)
|
@@ -161,6 +241,8 @@ module Rubix
|
|
161
241
|
end
|
162
242
|
end
|
163
243
|
|
244
|
+
protected
|
245
|
+
|
164
246
|
# Parse and send a single +line+ of TSV input to the Zabbix server.
|
165
247
|
# The line will be split at tabs and expects either
|
166
248
|
#
|
@@ -168,6 +250,8 @@ module Rubix
|
|
168
250
|
# b) three columns: an item key, a value, and a timestamp
|
169
251
|
#
|
170
252
|
# Unexpected input will cause an error to be logged.
|
253
|
+
#
|
254
|
+
# @param [String] line a line of TSV data
|
171
255
|
def process_line_of_tsv_in_this_pipe line
|
172
256
|
parts = line.strip.split("\t")
|
173
257
|
case parts.size
|
@@ -181,7 +265,7 @@ module Rubix
|
|
181
265
|
error("Each line of input must be a tab separated row consisting of 2 columns (key, value) or 3 columns (timestamp, key, value)")
|
182
266
|
return
|
183
267
|
end
|
184
|
-
|
268
|
+
send_data(key, value, timestamp)
|
185
269
|
end
|
186
270
|
|
187
271
|
# Parse and send a single +line+ of JSON input to the Zabbix server.
|
@@ -214,6 +298,8 @@ module Rubix
|
|
214
298
|
# {'key': 'snap.crackle.pop', 'value': 8 }
|
215
299
|
# ]
|
216
300
|
# }
|
301
|
+
#
|
302
|
+
# @param [String] line a line of JSON data
|
217
303
|
def process_line_of_json_in_new_pipe line
|
218
304
|
begin
|
219
305
|
json = JSON.parse(line)
|
@@ -256,9 +342,12 @@ module Rubix
|
|
256
342
|
end
|
257
343
|
end
|
258
344
|
|
259
|
-
# Does the line look like it might be JSON?
|
345
|
+
# Does the +line+ look like it might be JSON?
|
346
|
+
#
|
347
|
+
# @param [String] line
|
348
|
+
# @return [true, false]
|
260
349
|
def looks_like_json? line
|
261
|
-
line =~ /^\s*\{/
|
350
|
+
!!(line =~ /^\s*\{/)
|
262
351
|
end
|
263
352
|
|
264
353
|
# Send the +value+ for +key+ at the given +timestamp+ to the Zabbix
|
@@ -266,7 +355,13 @@ module Rubix
|
|
266
355
|
#
|
267
356
|
# If the +key+ doesn't exist for this local agent's host, it will be
|
268
357
|
# added.
|
269
|
-
|
358
|
+
#
|
359
|
+
# FIXME passing +timestamp+ has no effect at present...
|
360
|
+
#
|
361
|
+
# @param [String] key
|
362
|
+
# @param [String, Fixnum, Float] value
|
363
|
+
# @param [Time] timestamp
|
364
|
+
def send_data key, value, timestamp
|
270
365
|
ensure_item_exists(key, value) unless fast?
|
271
366
|
command = "#{settings['sender']} --config #{settings['configuration_file']} --zabbix-server #{settings['server']} --host #{settings['host']} --key #{key} --value '#{value}'"
|
272
367
|
process_zabbix_sender_output(key, `#{command}`)
|
@@ -279,6 +374,10 @@ module Rubix
|
|
279
374
|
# end
|
280
375
|
end
|
281
376
|
|
377
|
+
# Create an item for the given +key+ if necessary.
|
378
|
+
#
|
379
|
+
# @param [String] key
|
380
|
+
# @param [String, Fixnum, Float] value
|
282
381
|
def ensure_item_exists key, value
|
283
382
|
item = Item.find(:key => key, :host_id => host.id)
|
284
383
|
unless item
|
@@ -301,6 +400,10 @@ module Rubix
|
|
301
400
|
end
|
302
401
|
|
303
402
|
# Parse the +text+ output by +zabbix_sender+.
|
403
|
+
#
|
404
|
+
# @param [String] key
|
405
|
+
# @param [String] text the output from +zabbix_sender+
|
406
|
+
# @return [Fixnum] the number of data points processed
|
304
407
|
def process_zabbix_sender_output key, text
|
305
408
|
return unless settings['verbose']
|
306
409
|
lines = text.strip.split("\n")
|
@@ -309,6 +412,7 @@ module Rubix
|
|
309
412
|
status_line =~ /Processed +(\d+) +Failed +(\d+) +Total +(\d+)/
|
310
413
|
processed, failed, total = $1, $2, $3
|
311
414
|
warn("Failed to write #{failed} values to key '#{key}'") if failed.to_i != 0
|
415
|
+
processed
|
312
416
|
end
|
313
417
|
|
314
418
|
end
|
@@ -1,49 +1,47 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe "
|
3
|
+
describe "Applications" do
|
4
4
|
|
5
5
|
before do
|
6
|
-
|
7
|
-
ensure_save(
|
8
|
-
|
9
|
-
@h1 = Rubix::Host.new(:name => 'rubix_spec_host_1', :host_groups => [@hg])
|
10
|
-
ensure_save(@h1)
|
11
|
-
|
12
|
-
@h2 = Rubix::Host.new(:name => 'rubix_spec_host_2', :host_groups => [@hg])
|
13
|
-
ensure_save(@h2)
|
6
|
+
integration_test
|
7
|
+
@host_group = ensure_save(Rubix::HostGroup.new(:name => 'rubix_spec_host_group_1'))
|
8
|
+
@host = ensure_save(Rubix::Host.new(:name => 'rubix_spec_host_1', :host_groups => [@host_group]))
|
14
9
|
end
|
15
10
|
|
16
11
|
after do
|
17
|
-
|
12
|
+
truncate_all_tables
|
18
13
|
end
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
ensure_destroy(app1) do
|
31
|
-
|
32
|
-
app2 = Rubix::Application.find(:name => 'rubix_spec_app_1', :host_id => @h1.id)
|
33
|
-
app2.should_not be_nil
|
34
|
-
app2.id.should == app1.id
|
35
|
-
app2.host_id.should == @h1.id
|
36
|
-
|
37
|
-
app1.name = 'rubix_spec_app_2'
|
38
|
-
app1.save.should be_true
|
39
|
-
|
40
|
-
app2 = Rubix::Application.find(:id => id, :name => 'rubix_spec_app_2', :host_id => @h1.id)
|
41
|
-
app2.should_not be_nil
|
42
|
-
app2.name.should == 'rubix_spec_app_2'
|
43
|
-
|
44
|
-
app1.destroy.should be_true
|
45
|
-
Rubix::Application.find(:id => id, :host_id => @h1.id).should be_nil
|
46
|
-
Rubix::Application.find(:id => id, :host_id => @h2.id).should be_nil
|
14
|
+
|
15
|
+
describe "when not existing" do
|
16
|
+
|
17
|
+
it "returns nil on find" do
|
18
|
+
Rubix::Application.find(:name => 'rubix_spec_app_1', :host_id => @host.id).should be_nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it "can be created" do
|
22
|
+
app = Rubix::Application.new(:name => 'rubix_spec_app_1', :host_id => @host.id)
|
23
|
+
app.save.should be_true
|
47
24
|
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "when existing" do
|
29
|
+
|
30
|
+
before do
|
31
|
+
@app = ensure_save(Rubix::Application.new(:name => 'rubix_spec_app_1', :host_id => @host.id))
|
32
|
+
end
|
33
|
+
|
34
|
+
it "can have its name changed" do
|
35
|
+
@app.name = 'rubix_spec_app_2'
|
36
|
+
@app.save
|
37
|
+
Rubix::Application.find(:name => 'rubix_spec_app_1', :host_id => @host.id).should be_nil
|
38
|
+
Rubix::Application.find(:name => 'rubix_spec_app_2', :host_id => @host.id).should_not be_nil
|
39
|
+
end
|
40
|
+
|
41
|
+
it "can be destroyed" do
|
42
|
+
@app.destroy
|
43
|
+
Rubix::Application.find(:name => 'rubix_spec_app_1', :host_id => @host.id).should be_nil
|
44
|
+
end
|
45
|
+
|
48
46
|
end
|
49
47
|
end
|
@@ -1,31 +1,49 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe "
|
3
|
+
describe "HostGroups" do
|
4
4
|
|
5
|
-
|
5
|
+
before do
|
6
6
|
integration_test
|
7
|
+
end
|
8
|
+
|
9
|
+
after do
|
10
|
+
truncate_all_tables
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "when not existing" do
|
14
|
+
|
15
|
+
it "returns nil on find" do
|
16
|
+
Rubix::HostGroup.find(:name => 'rubix_spec_host_group_1').should be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it "can be created" do
|
20
|
+
hg = Rubix::HostGroup.new(:name => 'rubix_spec_host_group_1')
|
21
|
+
hg.save.should be_true
|
22
|
+
end
|
7
23
|
|
8
|
-
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "when existing" do
|
27
|
+
|
28
|
+
before do
|
29
|
+
@hg = ensure_save(Rubix::HostGroup.new(:name => 'rubix_spec_host_group_1'))
|
30
|
+
end
|
9
31
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
hg2.should_not be_nil
|
25
|
-
hg2.name.should == 'rubix_spec_host_group_2'
|
26
|
-
|
27
|
-
hg1.destroy
|
28
|
-
Rubix::HostGroup.find(:id => id).should be_nil
|
32
|
+
it "can be found" do
|
33
|
+
Rubix::HostGroup.find(:name => 'rubix_spec_host_group_1').should_not be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it "can have its name changed" do
|
37
|
+
@hg.name = 'rubix_spec_host_group_2'
|
38
|
+
@hg.save
|
39
|
+
Rubix::HostGroup.find(:name => 'rubix_spec_host_group_1').should be_nil
|
40
|
+
Rubix::HostGroup.find(:name => 'rubix_spec_host_group_2').should_not be_nil
|
41
|
+
end
|
42
|
+
|
43
|
+
it "can be destroyed" do
|
44
|
+
@hg.destroy
|
45
|
+
Rubix::HostGroup.find(:name => 'rubix_spec_host_group_1').should be_nil
|
29
46
|
end
|
30
47
|
end
|
48
|
+
|
31
49
|
end
|