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/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