rubix 0.0.8 → 0.0.9
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/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
|