zcollective 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/bin/zcollective ADDED
@@ -0,0 +1,659 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright (c) 2012, 2013, The Scale Factory Ltd.
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are met:
8
+ # * Redistributions of source code must retain the above copyright
9
+ # notice, this list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright
11
+ # notice, this list of conditions and the following disclaimer in the
12
+ # documentation and/or other materials provided with the distribution.
13
+ # * Neither the name of the The Scale Factory Ltd nor the
14
+ # names of its contributors may be used to endorse or promote products
15
+ # derived from this software without specific prior written permission.
16
+ #
17
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE SCALE FACTORY LTD BE LIABLE FOR ANY
21
+ # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26
+ # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+
28
+ require 'rubygems'
29
+ require 'optparse'
30
+ require 'logger'
31
+ require 'json'
32
+ require 'netaddr'
33
+ require 'zcollective/zabbixclient'
34
+
35
+ begin
36
+ require 'mcollective'
37
+ rescue LoadError => e
38
+ raise unless e.message =~ /mcollective/
39
+ STDERR.puts "ZCollective requires that you install mcollective. " <<
40
+ "This is not supplied as a gem - install the OS packages from puppetlabs"
41
+ exit 2
42
+ end
43
+
44
+ options = {}
45
+ optparse = OptionParser.new do |opts|
46
+
47
+ options[:zabbix_api_url] = 'http://localhost/zabbix/api_jsonrpc.php'
48
+ opts.on('-z', '--zabbix-api-url url', 'JSON-RPC endpoint for zabbix server') do |u|
49
+ options[:zabbix_api_url] = u
50
+ end
51
+
52
+ options[:zabbix_user] = 'Admin'
53
+ opts.on('-u', '--zabbix-user user', 'Zabbix API username') do |u|
54
+ options[:zabbix_user] = u
55
+ end
56
+
57
+ options[:zabbix_pass] = 'zabbix'
58
+ opts.on('-p', '--zabbix-pass pass', 'Zabbix API password') do |p|
59
+ options[:zabbix_pass] = p
60
+ end
61
+
62
+ options[:debug] = false
63
+ opts.on('-d', '--debug', 'Enable debugging') do
64
+ options[:debug] = true
65
+ end
66
+
67
+ options[:noop] = false
68
+ opts.on('-n', '--noop', 'Don\'t make changes') do
69
+ options[:noop] = true
70
+ end
71
+
72
+ options[:interface_cidr] = '0.0.0.0/0'
73
+ opts.on('-c', '--interface-cidr CIDR', 'Only consider interfaces matching the given CIDR') do |c|
74
+ options[:interface_cidr] = c
75
+ end
76
+
77
+ options[:connect_by_ip] = 0
78
+ opts.on('--connect-by-ip','Configure newly added hosts to connect by IP address instead of DNS') do
79
+ options[:connect_by_ip] = 1
80
+ end
81
+
82
+ options[:lockfile] = "/tmp/zcollective.lock"
83
+ opts.on('--lockfile=f', 'Use alternative lock file') do |f|
84
+ options[:lockfile] = f
85
+ end
86
+
87
+ options[:timeout] = 60
88
+ opts.on('--timeout=t', 'Time out after number of seconds') do |t|
89
+ options[:timeout] = t.to_i
90
+ end
91
+
92
+ end
93
+
94
+ begin
95
+
96
+ optparse.parse!
97
+
98
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument
99
+
100
+ $stderr.puts $!.to_s
101
+ $stderr.puts optparse
102
+ exit 2
103
+
104
+ end
105
+
106
+ log = Logger.new(STDERR)
107
+
108
+ if options[:debug]
109
+ log.level = Logger::DEBUG
110
+ else
111
+ log.level = Logger::INFO
112
+ end
113
+
114
+ trap("ALRM") do
115
+ log.info("Timeout after #{options[:timeout]}s")
116
+ exit 4
117
+ end
118
+
119
+ Thread.new {
120
+ sleep options[:timeout]
121
+ Process.kill( "ALRM", Process.pid )
122
+ }
123
+
124
+ if File.exist?(options[:lockfile])
125
+
126
+ other_pid = File.read(options[:lockfile]).to_i
127
+
128
+ begin
129
+ Process.getpgid( other_pid )
130
+ log.info("Another zcollective process (#{other_pid}) holds the lock")
131
+ exit 3
132
+ rescue Errno::ESRCH
133
+ log.info("Deleting stale lock file")
134
+ FileUtils.remove_file(options[:lockfile], true)
135
+ end
136
+
137
+ end
138
+
139
+ File.open( options[:lockfile], File::CREAT | File::EXCL | File::WRONLY ) do |o|
140
+ o.write(Process.pid)
141
+ end
142
+
143
+ log.debug( "Connecting to Zabbix RPC service" )
144
+
145
+ zabbix_client = ZCollective::ZabbixClient.new(
146
+ :url => options[:zabbix_api_url],
147
+ :user => options[:zabbix_user],
148
+ :password => options[:zabbix_pass],
149
+ :debug => options[:debug]
150
+ )
151
+
152
+ log.debug( "Connected and authenticated" )
153
+
154
+
155
+
156
+ ############################################################################
157
+ # Fetch list of zabbix templates
158
+
159
+ log.debug( "Fetching list of zabbix templates" )
160
+
161
+ zabbix_templates = {}
162
+
163
+ zabbix_client.request( 'template.get',
164
+ 'search' => '',
165
+ 'output' => 'extend'
166
+ ).each do |template|
167
+
168
+ log.debug( "\tName: #{template['name']} ID: #{template['templateid']}" )
169
+ zabbix_templates[ template['name'] ] = template['templateid']
170
+
171
+ end
172
+
173
+ # We're going to build a big nasty hash of zabbix and mcollective data
174
+ # because apparently I still think like a Perl engineer. It seems this
175
+ # dirty bit of Ruby is necessary to allow them to be anonymously built.
176
+ #
177
+ nested_hash = lambda {|hash, key| hash[key] = Hash.new(&nested_hash)}
178
+ hosts = Hash.new(&nested_hash)
179
+
180
+ # An array of collectives to create hostgroups of:
181
+ collectives = Array.new
182
+
183
+
184
+ ############################################################################
185
+ # Fetch list of zabbix groups
186
+ # Create "ZCollective discovered hosts" group if it doesn't exist
187
+
188
+ log.debug( "Fetching list of zabbix hostgroups" )
189
+
190
+ zcollective_hostgroup_name = 'ZCollective discovered hosts'
191
+ zcollective_hostgroup = nil
192
+
193
+ zabbix_client.request( 'hostgroup.get',
194
+ 'search' => '',
195
+ 'output' => 'extend'
196
+ ).each do |hostgroup|
197
+
198
+ log.debug("\tName: #{hostgroup['name']} ID: #{hostgroup['groupid']}")
199
+
200
+ if hostgroup['name'] == zcollective_hostgroup_name
201
+ zcollective_hostgroup = hostgroup['groupid']
202
+ end
203
+
204
+ end
205
+
206
+ if zcollective_hostgroup.nil?
207
+
208
+ if options[:noop]
209
+
210
+ log.debug("No zcollective hostgroup, but not creating as " <<
211
+ "we're in --noop mode")
212
+
213
+ else
214
+
215
+ log.debug("No zcollective hostgroup: creating")
216
+
217
+ resp = zabbix_client.request( 'hostgroup.create',
218
+ 'name' => zcollective_hostgroup_name
219
+ )
220
+
221
+ zcollective_hostgroup = resp['groupids'].first
222
+
223
+ end
224
+
225
+ end
226
+
227
+ log.debug("ZCollective hostgroup: #{zcollective_hostgroup}")
228
+
229
+
230
+
231
+ ############################################################################
232
+ # Iterate through zabbix hosts
233
+
234
+ zabbix_client.request( 'host.get',
235
+ 'search' => '',
236
+ 'output' => 'extend'
237
+ ).each do |host|
238
+
239
+ log.debug( "Host: #{host['name']}, ID: #{host['hostid']}" )
240
+
241
+ # Iterate through hostinterfaces, looking for zabbix agent type
242
+ # interfaces.
243
+ #
244
+ # I'm not sure how we should handle multiple interfaces here
245
+ # but it seems a safe assumption that there will only be one
246
+ # agent type interface per machine.
247
+
248
+ zabbix_client.request( 'hostinterface.get',
249
+ 'hostids' => host['hostid'],
250
+ 'output' => 'extend'
251
+ ).each do |interface|
252
+
253
+ next unless interface['type'] == "1" # skip non-Zabbix agent interfaces
254
+
255
+ log.debug( "\tIP: #{interface['ip']}" )
256
+ hosts[ host['name'] ][:zabbix][:ip] = interface['ip']
257
+
258
+ end
259
+
260
+ hosts[ host['name'] ][:zabbix][:hostid] = host['hostid']
261
+ hosts[ host['name'] ][:zabbix][:templates] = []
262
+
263
+ # Iterate through this host's templates
264
+
265
+ zabbix_client.request(
266
+ 'template.get',
267
+ 'search' => '',
268
+ 'output' => 'extend',
269
+ 'hostids' => host['hostid']
270
+ ).each do |template|
271
+
272
+ log.debug( "\tTemplate: #{template['name']}" )
273
+ hosts[ host['name'] ][:zabbix][:templates].push( template['name'] )
274
+
275
+ end
276
+
277
+ end
278
+
279
+
280
+
281
+ ############################################################################
282
+ # Iterate through MCollective hosts
283
+
284
+ include MCollective::RPC
285
+
286
+ mc = rpcclient("rpcutil", :debug => true)
287
+ begin
288
+ zt_mc = rpcclient("zabbix_template", :exit_on_failure => false)
289
+ rescue Exception
290
+ log.warn("No zabbix_template mcollective rpc agent found")
291
+ end
292
+ if (!zt_mc.nil?)
293
+ zt_mc.progress = false
294
+ end
295
+ mc.progress = false
296
+ mc.discover.sort.each do |host|
297
+
298
+ # MCollective returns FQDN name, and we probably want to use the short
299
+ # form name in zabbix.
300
+
301
+ short_hostname = host.split('.').first
302
+
303
+ log.debug("Host: #{short_hostname} (#{host})")
304
+
305
+ # Get inventory details for each host
306
+ inventory = mc.custom_request( "inventory", {}, host,
307
+ { "identity" => host }
308
+ ).first
309
+
310
+ # Work through network interfaces reported by Facter and find the first
311
+ # which matches the CIDR passed on the commandline. Use that to talk
312
+ # zabbix to.
313
+
314
+ cidr_to_match = NetAddr::CIDR.create( options[:interface_cidr] )
315
+ ip = nil
316
+
317
+ inventory[:data][:facts].sort.each do |key,value|
318
+
319
+ next unless key.match(/^ipaddress_/)
320
+
321
+ log.debug("Potential IP interface #{key} with IP #{value}")
322
+
323
+ ip_cidr = NetAddr::CIDR.create( value )
324
+ if ip_cidr.is_contained?( cidr_to_match)
325
+
326
+ log.debug("IP matches CIDR #{options[:interface_cidr]}")
327
+
328
+ ip = value
329
+ break
330
+
331
+ else
332
+ log.debug("IP doesn't match CIDR")
333
+ end
334
+
335
+ end
336
+
337
+ unless ip
338
+ raise "Host #{host} has no IP matching the target CIDR #{options[:interface_cidr]}"
339
+ end
340
+
341
+ log.debug("\tIP #{ip}")
342
+
343
+ # Find whether we have to use different or any extra templates in Zabbix
344
+ # for any of the modules on this host. Only do this if we were
345
+ # successfully able to create the zabbix_template mcollective rpcclient.
346
+ if (!zt_mc.nil?)
347
+ host_template_info = zt_mc.custom_request( "templates", {},
348
+ host,
349
+ {"identity" => host}
350
+ ).first
351
+ end
352
+
353
+ if (!host_template_info.nil?)
354
+ hosts[ short_hostname ][:aliases] = host_template_info[:data][:aliases]
355
+ hosts[ short_hostname ][:extras] = host_template_info[:data][:extras]
356
+ end
357
+
358
+ hosts[ short_hostname ][:mcollective][:ip] = ip
359
+ hosts[ short_hostname ][:mcollective][:classes] = inventory[:data][:classes]
360
+ hosts[ short_hostname ][:mcollective][:collectives] = inventory[:data][:collectives]
361
+
362
+ collectives << hosts[ short_hostname ][:mcollective][:collectives]
363
+
364
+ end
365
+
366
+ mc.disconnect
367
+
368
+ ###########################################################################
369
+ # Fetch list of zabbix groups & Create hosts groups of discoverred
370
+ # collectives, to add hosts to later
371
+
372
+ log.debug("collectives: #{collectives.flatten.inspect}")
373
+ log.debug("uniq collectives: #{collectives.flatten.uniq.inspect}")
374
+
375
+ collectives_to_hostgroups = collectives.flatten.uniq
376
+
377
+ collectives_to_hostgroups.each do | collective |
378
+
379
+ log.debug( "Fetching list of zabbix hostgroups" )
380
+
381
+ collective_hostgroup_name = "#{collective}"
382
+ collective_hostgroup_id = nil
383
+
384
+ zabbix_client.request( 'hostgroup.get',
385
+ 'search' => '',
386
+ 'output' => 'extend'
387
+ ).each do |hostgroup|
388
+
389
+ log.debug("\tName: #{hostgroup['name']} ID: #{hostgroup['groupid']}")
390
+
391
+ if hostgroup['name'] == collective_hostgroup_name
392
+ collective_hostgroup_id = hostgroup['groupid']
393
+ end
394
+
395
+ end
396
+
397
+ if collective_hostgroup_id.nil?
398
+
399
+ if options[:noop]
400
+
401
+ log.debug("No #{collective} hostgroup, but not creating as " <<
402
+ "we're in --noop mode")
403
+ else
404
+
405
+ log.debug("No #{collective} hostgroup: creating")
406
+
407
+ resp = zabbix_client.request( 'hostgroup.create',
408
+ 'name' => collective
409
+ )
410
+
411
+ collective_hostgroup_id = resp['groupids'].first
412
+
413
+ end
414
+
415
+ end
416
+
417
+ log.info("New hostgroup: #{collective}")
418
+ log.info("New hostgroup's ID: #{collective_hostgroup_id}")
419
+
420
+ end
421
+
422
+
423
+ ############################################################################
424
+ # Rationalise the two datasets, iterating over the hosts and carrying out
425
+ # the appropriate actions
426
+
427
+ hosts.each do |host,data|
428
+
429
+
430
+
431
+ ###### Condition 1 #############################################
432
+ #
433
+ # Hosts that are found by mcollective but that aren't in zabbix
434
+ # should be added.
435
+
436
+ if data.has_key?(:mcollective) and !data.has_key?(:zabbix)
437
+
438
+ log.info( "Host #{host} found by mcollective but not in zabbix" )
439
+
440
+ # If mcollective finds a host, but zabbix doesn't list one by
441
+ # that name, we're going to add it.
442
+
443
+ # Iterate through the classes reported by mcollective for this
444
+ # host. If the class name matches the name of a zabbix template,
445
+ # get the ID and add an object to the templates_to_add array.
446
+ # This will be passed to the zabbix API call.
447
+
448
+ templates_to_add = []
449
+ data[:mcollective][:classes].each do |template|
450
+ # if the class name has a :: in it, replace it with an underscore
451
+ # for the purposes of finding a template with that name
452
+ template = template.sub(/::/, '_')
453
+ classname = template
454
+
455
+ # if we have an alias for this template, use that instead
456
+ if ( ! data[:aliases].nil? and data[:aliases].has_key?( template ) )
457
+ template = data[:aliases][ template ]
458
+ log.debug("\tUsing alias #{template} for #{classname}")
459
+ end
460
+
461
+ next unless zabbix_templates.has_key?( template )
462
+ template_id = zabbix_templates[ template ]
463
+ log.debug("\tWill be adding template #{template} ID #{template_id}")
464
+ templates_to_add.push( { 'templateid' => template_id } )
465
+
466
+ # if we have any extra zabbix templates to add for this class
467
+ # then add them to the array of templates to add for this host
468
+ if ( ! data[:extras].nil? and data[:extras].has_key?( classname ) )
469
+
470
+ extra_templates = data[:extras][ classname ].split(/,/)
471
+
472
+ extra_templates.each do |extra_template|
473
+ next unless zabbix_templates.has_key?( extra_template )
474
+ template_id = zabbix_templates[ extra_template ]
475
+ log.debug("\tWill be adding template #{extra_template} ID #{template_id} for #{classname}")
476
+ templates_to_add.push( { 'templateid' => template_id } )
477
+ end
478
+
479
+ end
480
+ end
481
+
482
+ ### get list of current hostgroup, so we can add to the correct groups by ID
483
+ groups_by_id = []
484
+ zabbix_client.request( 'hostgroup.get',
485
+ 'search' => '',
486
+ 'output' => 'extend'
487
+ ).each do |hostgroup|
488
+
489
+ host_group_hash = {}
490
+
491
+ log.debug("\tName: #{hostgroup['name']} ID: #{hostgroup['groupid']}")
492
+
493
+ if data[:mcollective][:collectives].include? hostgroup['name']
494
+ host_group_hash['groupid'] = hostgroup['groupid']
495
+ groups_by_id << host_group_hash
496
+ end
497
+
498
+ end
499
+
500
+ if options[:noop]
501
+
502
+ log.info("--noop passed - not making changes")
503
+
504
+ else
505
+
506
+ # If we're not in --noop mode, create the host with the
507
+ # zabbix API. Hosts need at least one interface (for now
508
+ # we're just adding a Zabbix agent interface), and need
509
+ # to be in a group.
510
+
511
+ resp = zabbix_client.request( 'host.create',
512
+ 'host' => host,
513
+ 'interfaces' => [
514
+ {
515
+ 'type' => 1,
516
+ 'main' => 1,
517
+ 'useip' => options[:connect_by_ip],
518
+ 'ip' => data[:mcollective][:ip],
519
+ 'dns' => host,
520
+ 'port' => '10050'
521
+ }
522
+ ],
523
+ 'groups' => groups_by_id,
524
+ 'templates' => templates_to_add
525
+ )
526
+
527
+ # This call returns the created host id
528
+
529
+ new_hostid = resp['hostids'].first
530
+
531
+ log.info("Host #{host} added as ID #{new_hostid} " <<
532
+ "with #{templates_to_add.count} templates")
533
+
534
+ end
535
+
536
+ end
537
+
538
+
539
+
540
+ ###### Condition 2 #############################################
541
+ # If zabbix contains a host that mcollective knows nothing about
542
+ # we leave it alone but report it.
543
+
544
+ if data.has_key?(:zabbix) and !data.has_key?(:mcollective)
545
+
546
+ log.warn( "Host #{host} found in zabbix but not by mcollective" )
547
+
548
+ end
549
+
550
+
551
+
552
+ ###### Condition 3 #############################################
553
+ # Hosts in zabbix and mcollective are checked to ensure that
554
+ # they are linked with at least the templates they should be
555
+
556
+ if data.has_key?(:zabbix) and data.has_key?(:mcollective)
557
+
558
+ log.debug( "Host #{host} in both zabbix and mcollective" )
559
+
560
+ # Compare interface addresses and warn if mismatched
561
+
562
+ if data[:mcollective][:ip] != data[:zabbix][:ip]
563
+ log.warn("Host #{host} monitored, but IP mismatch " <<
564
+ "M:#{data[:mcollective][:ip]} " <<
565
+ "Z:#{data[:zabbix][:ip]}"
566
+ )
567
+ end
568
+
569
+ templates_need_adding = []
570
+
571
+ # Iterate through the classes mcollective lists for the host
572
+
573
+ data[:mcollective][:classes].each do |template|
574
+
575
+ # templates_should_have is a list of templates that zabbix should have
576
+ # for this particular host, for this class.
577
+ templates_should_have = []
578
+
579
+ # Ignore any that don't match the name of a zabbix template
580
+ # Again, replace :: with _ to match template names
581
+ template = template.sub(/::/, '_')
582
+ classname = template
583
+
584
+ # if we have an alias for this template, use that instead
585
+ if ( ! data[:aliases].nil? and data[:aliases].has_key?( template ) )
586
+ template = data[:aliases][ template ]
587
+ log.debug("\tUsing alias #{template} for #{classname}")
588
+ end
589
+
590
+ next unless zabbix_templates.has_key?( template )
591
+ templates_should_have.push( template )
592
+ log.debug("\tHas mcollective class #{classname} matching a zabbix template")
593
+
594
+ # if we have any extra zabbix templates to add for this class
595
+ # then add them to the array of templates to add for this host
596
+ if ( ! data[:extras].nil? and data[:extras].has_key?( classname ) )
597
+
598
+ extra_templates = data[:extras][ classname ].split(/,/)
599
+
600
+ extra_templates.each do |extra_template|
601
+ next unless zabbix_templates.has_key?( extra_template )
602
+ template_id = zabbix_templates[ extra_template ]
603
+ log.debug("\tHas extra template #{extra_template} defined for #{classname}")
604
+ templates_should_have.push( extra_template )
605
+ end
606
+
607
+ end
608
+
609
+ # iterate over the templates that we should have, and if they're
610
+ # not already linked, add them to the list of templates which
611
+ # need adding for this host.
612
+ templates_should_have.each do |tpl_should_have|
613
+
614
+ if data[:zabbix][:templates].index( tpl_should_have )
615
+
616
+ # The host in zabbix is already linked to this template
617
+ log.debug("\tZabbix host already linked to template #{tpl_should_have}")
618
+
619
+ else
620
+
621
+ # Zabbix shows that although it knows about this template
622
+ # the host in question isn't linked to it. We add this
623
+ # template to a list of those that are missing in zabbix.
624
+ log.info("\tZabbix #{host} not linked to template #{tpl_should_have}")
625
+ templates_need_adding.push( { 'templateid' => zabbix_templates[ tpl_should_have ] } )
626
+
627
+ end
628
+
629
+ end
630
+
631
+ end
632
+
633
+ if templates_need_adding.count > 0
634
+
635
+ if options[:noop]
636
+
637
+ log.info("--noop passed - not making changes")
638
+
639
+ else
640
+
641
+ # If we're not running --noop and we found missing templates,
642
+ # link the zabbix host with these.
643
+
644
+ zabbix_client.request( 'template.massadd',
645
+ 'templates' => templates_need_adding,
646
+ 'hosts' => { 'hostid' => data[:zabbix][:hostid] }
647
+ )
648
+
649
+ log.info("Added missing templates to #{host}")
650
+
651
+ end
652
+
653
+ end
654
+
655
+ end
656
+
657
+ end
658
+
659
+ FileUtils.remove_file(options[:lockfile], true)
@@ -0,0 +1,117 @@
1
+ # Copyright (c) 2012, 2013, The Scale Factory Ltd.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ # * Redistributions of source code must retain the above copyright
7
+ # notice, this list of conditions and the following disclaimer.
8
+ # * Redistributions in binary form must reproduce the above copyright
9
+ # notice, this list of conditions and the following disclaimer in the
10
+ # documentation and/or other materials provided with the distribution.
11
+ # * Neither the name of the The Scale Factory Ltd nor the
12
+ # names of its contributors may be used to endorse or promote products
13
+ # derived from this software without specific prior written permission.
14
+ #
15
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE SCALE FACTORY LTD BE LIABLE FOR ANY
19
+ # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24
+ # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
+
26
+ require 'json'
27
+ require 'net/http'
28
+ require 'logger'
29
+ require 'ostruct'
30
+
31
+ module ZCollective
32
+
33
+ class ZabbixClient
34
+
35
+ @restclient_options = { :content_type => 'application/json' }
36
+
37
+ @url
38
+ @auth_hash
39
+ @options
40
+ @log
41
+
42
+ def initialize ( options = {} )
43
+ @options = options
44
+
45
+ @log = Logger.new(STDERR)
46
+ if( @options[:debug] )
47
+ @log.level = Logger::DEBUG
48
+ else
49
+ @log.level = Logger::WARN
50
+ end
51
+
52
+ @auth_hash = authenticate
53
+
54
+ end
55
+
56
+ def authenticate ( )
57
+
58
+ response = request( 'user.authenticate',
59
+ :user => @options[:user],
60
+ :password => @options[:password]
61
+ )
62
+
63
+ end
64
+
65
+ def request_json( method, *args )
66
+
67
+ req = {
68
+ :jsonrpc => '2.0',
69
+ :method => method,
70
+ :params => Hash[*args.flatten],
71
+ :id => rand( 100000 )
72
+ }
73
+
74
+ if @auth_hash
75
+ req[:auth] = @auth_hash
76
+ end
77
+
78
+ JSON.generate( req )
79
+
80
+ end
81
+
82
+ def request( method, *args )
83
+
84
+ json = request_json( method, *args )
85
+
86
+ uri = URI.parse( @options[:url] )
87
+ proxy = ENV['http_proxy'] ? URI.parse(ENV['http_proxy']) : OpenStruct.new
88
+ http = Net::HTTP::Proxy(proxy.host, proxy.port).new( uri.host, uri.port )
89
+
90
+ request = Net::HTTP::Post.new( uri.request_uri )
91
+ request.add_field( 'Content-Type', 'application/json-rpc' )
92
+ request.body = json
93
+
94
+ @log.debug( "HTTP Request: #{uri} #{json}" )
95
+
96
+ response = http.request( request )
97
+
98
+ unless response.code == "200"
99
+ raise "HTTP Error: #{response.code}"
100
+ end
101
+
102
+ @log.debug( "HTTP Response: #{response.body}" )
103
+
104
+ result = JSON.parse( response.body )
105
+
106
+ if result['error']
107
+ raise "JSON-RPC error: #{result['error']['message']} (#{result['error']['data']})"
108
+ end
109
+
110
+ result['result']
111
+
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zcollective
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.9
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jon Topper
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-12-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: netaddr
16
+ requirement: &70263403228180 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.5.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70263403228180
25
+ description: ZCollective is a tool used to configure Zabbix using data discovered
26
+ using MCollective.
27
+ email: jon@scalefactory.com
28
+ executables:
29
+ - zcollective
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/zcollective/zabbixclient.rb
34
+ - bin/zcollective
35
+ homepage: http://github.com/scalefactory/zcollective
36
+ licenses: []
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubyforge_project:
55
+ rubygems_version: 1.8.11
56
+ signing_key:
57
+ specification_version: 3
58
+ summary: Zabbix/MCollective integration
59
+ test_files: []
60
+ has_rdoc: