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/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
- # A Hash of options.
28
+ # @return [Hash] settings
10
29
  attr_accessor :settings
11
30
 
12
- # The host the Sender will send data for.
31
+ # @return [Rubix::Host] the host the Sender will send data for
13
32
  attr_accessor :host
14
33
 
15
- # The hostgroups used to create this host.
34
+ # @return [Array<Rubix::HostGroup>] the hostgroups used to create this host
16
35
  attr_accessor :host_groups
17
36
 
18
- # The templates used to create this host.
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
- initialize_hostgroups
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
- def initialize_hostgroups
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
- # Actions
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 the file at +path+.
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 +f+.
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
- send(key, value, timestamp)
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
- def send key, value, timestamp
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 "CRUD for hosts" do
3
+ describe "Applications" do
4
4
 
5
5
  before do
6
- @hg = Rubix::HostGroup.new(:name => 'rubix_spec_host_group_1')
7
- ensure_save(@hg)
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
- ensure_destroy(@h1, @h2, @hg)
12
+ truncate_all_tables
18
13
  end
19
-
20
- it "should be able to create, update, and destroy a host" do
21
- integration_test
22
-
23
- Rubix::Application.find(:name => 'rubix_spec_app_1', :host_id => @h1.id).should be_nil
24
-
25
- app1 = Rubix::Application.new(:name => 'rubix_spec_app_1', :host_id => @h1.id)
26
- app1.save.should be_true
27
- id = app1.id
28
- id.should_not be_nil
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 "CRUD for host groups" do
3
+ describe "HostGroups" do
4
4
 
5
- it "should be able to create, update, and destroy a host group" do
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
- Rubix::HostGroup.find(:name => 'rubix_spec_host_group_1').should be_nil
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
- hg1 = Rubix::HostGroup.new(:name => 'rubix_spec_host_group_1')
11
- hg1.save.should be_true
12
- id = hg1.id
13
- id.should_not be_nil
14
-
15
- ensure_destroy(hg1) do
16
- hg2 = Rubix::HostGroup.find(:name => 'rubix_spec_host_group_1')
17
- hg2.should_not be_nil
18
- hg2.name.should == 'rubix_spec_host_group_1'
19
-
20
- hg1.name = 'rubix_spec_host_group_2'
21
- hg1.save.should be_true
22
-
23
- hg2 = Rubix::HostGroup.find(:name => 'rubix_spec_host_group_2')
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