zcollective 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
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: