zabbix_sender_api 1.0.5 → 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.gitlab-ci.yml +12 -0
- data/CHANGELOG.md +9 -0
- data/README.md +24 -27
- data/bin/bundle +114 -0
- data/bin/byebug +29 -0
- data/bin/coderay +29 -0
- data/bin/example.rb +29 -0
- data/bin/pry +29 -0
- data/bin/rake +29 -0
- data/lib/zabbix_sender_api/api.rb +179 -32
- data/lib/zabbix_sender_api/version.rb +1 -1
- data/lib/zabbix_sender_api.rb +3 -0
- data/public/Zabbix/AgentConfiguration.html +413 -0
- data/public/Zabbix/Sender/Batch.html +660 -0
- data/public/Zabbix/Sender/Connection.html +650 -0
- data/public/Zabbix/Sender/Discovery.html +597 -0
- data/public/Zabbix/Sender/ItemData.html +678 -0
- data/public/Zabbix/Sender/Pipe.html +461 -0
- data/public/Zabbix/Sender/Socket.html +528 -0
- data/public/Zabbix/Sender.html +115 -0
- data/public/Zabbix.html +117 -0
- data/public/ZabbixSenderApi/Error.html +124 -0
- data/public/ZabbixSenderApi.html +133 -0
- data/public/_index.html +242 -0
- data/public/class_list.html +51 -0
- data/public/css/common.css +1 -0
- data/public/css/full_list.css +58 -0
- data/public/css/style.css +497 -0
- data/public/file.README.html +174 -0
- data/public/file_list.html +56 -0
- data/public/frames.html +17 -0
- data/public/index.html +174 -0
- data/public/js/app.js +314 -0
- data/public/js/full_list.js +216 -0
- data/public/js/jquery.js +4 -0
- data/public/method_list.html +363 -0
- data/public/top-level-namespace.html +110 -0
- data/zabbix_sender_api.gemspec +5 -3
- metadata +43 -9
- data/Gemfile.lock +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1bb42ed344a48f3aa83f04739a41ad388eb59ca3beb2819636255d3a5cd6ce7
|
4
|
+
data.tar.gz: 9039d00b328b41b2ad5462a3632a6834319fe027f672084352a707d8c1d3e5a5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7df9cceb6e1a5d0836c6bc410a7ab444cd6cab38b9948ba688aaf5daac971c164a88a8d21a6fd629f3841492c9c8f8257c802c25bf8219929421eb3ff31fcdc8
|
7
|
+
data.tar.gz: 9a7b38fc6ae72efabcfa7bfda7b2c503aee772ebf0f3442c8e63ab5391c812a47572dd5ef2143ba33c3c6f591191749b8b34ffb051567d2023240272b4d03d6b
|
data/.gitignore
CHANGED
data/.gitlab-ci.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# 1.1.0
|
2
|
+
- added native (socket based) sender logic (Zabbix::Sender::Socket).
|
3
|
+
- changed Zabbix::Sender::Pipe's open method from popen to Open3::popen3 to allow capture of stdout stderf and exitA
|
4
|
+
- the Socket and Pipe .flush method returns status, sendBatchAtomic relays it from its internal call to flush
|
5
|
+
# 1.1.1
|
6
|
+
- added ability to override Zabbix::AgentConfiguration search path
|
7
|
+
- cleaned up a few issues + the docs
|
8
|
+
# 1.1.2
|
9
|
+
- This is just a documentation fix. rubydoc.info wasn't generating docs from the gem properly so docs are now in a gitlab "pages" site.
|
data/README.md
CHANGED
@@ -1,39 +1,17 @@
|
|
1
1
|
# zabbix_sender_api
|
2
2
|
|
3
|
-
This gem
|
3
|
+
This gem provides a model for assembling and sending data to Zabbix via its sender/trapper mechanism. It works both with the zabbix_sender command line utility, or by itself using a socket connection to a trapper port direct from Ruby.
|
4
4
|
|
5
|
-
**Detailed documentation for this library is
|
5
|
+
**Detailed documentation for this library is [>>HERE<<](https://svdasein.gitlab.io/zabbix_sender_api)**
|
6
6
|
|
7
7
|
Look at [the source for the zfstozab gem](https://gitlab.com/svdasein/zfstozab/-/blob/master/exe/zabbix-zfs.rb) for a practical example how to use this api.
|
8
8
|
|
9
|
-
|
9
|
+
zabbix_sender_api can send data to zabbix via one of two included connection types:
|
10
|
+
* zabbix_sender (this was the original and for quite some time only mode supported)
|
11
|
+
* TCP socket connection directly to a Zabbix "trapper" port (as of vers 1.1.0)
|
10
12
|
|
11
|
-
* [Zabbix sender manpage](https://www.zabbix.com/documentation/4.0/manpages/zabbix_sender)
|
12
|
-
* [Zabbix sender CLI help](zabbix-sender-help.md)
|
13
13
|
|
14
14
|
|
15
|
-
zabbix_sender_api's function is to generate data to send to a zabbix_sender instance via stdin where zabbix_sender was invoked with these options:
|
16
|
-
|
17
|
-
```
|
18
|
-
-i --input-file input-file Load values from input file. Specify - for
|
19
|
-
standard input. Each line of file contains
|
20
|
-
whitespace delimited: <host> <key> <value>.
|
21
|
-
Specify - in <host> to use hostname from
|
22
|
-
configuration file or --host argument
|
23
|
-
|
24
|
-
-T --with-timestamps Each line of file contains whitespace delimited:
|
25
|
-
<host> <key> <timestamp> <value>. This can be used
|
26
|
-
with --input-file option. Timestamp should be
|
27
|
-
specified in Unix timestamp format
|
28
|
-
```
|
29
|
-
|
30
|
-
It is analogous to:
|
31
|
-
|
32
|
-
\<program that generates sender input\> | zabbix-sender -z \<zabbix-server-or-proxy-ip\> -T -i -
|
33
|
-
|
34
|
-
(You can use this api to just generate output that you then pipe to zabbix_sender yourself if you prefer)
|
35
|
-
|
36
|
-
|
37
15
|
## Installation
|
38
16
|
|
39
17
|
$ gem install zabbix_sender_api
|
@@ -81,6 +59,7 @@ data.addDiscovery(disco)
|
|
81
59
|
|
82
60
|
### Under the hood
|
83
61
|
|
62
|
+
#### Zabbix::Sender::Pipe method:
|
84
63
|
The zabbix-sender cli utility provides a number of methods by which to insert data into zabbix.
|
85
64
|
|
86
65
|
* zabbix-sender ... -s zabbixHostName -k keyName -o value (one k-v pair at a time)
|
@@ -120,3 +99,21 @@ If you wished to use the above lld to actually do some discovery, you'd set thin
|
|
120
99
|
|
121
100
|
![Discovery rule configuration](images/Spectacle.Z29721.png)
|
122
101
|
![Item prototype configuration](images/Spectacle.l29721.png )
|
102
|
+
|
103
|
+
#### Zabbix::Sender::Socket method:
|
104
|
+
|
105
|
+
You can switch between using the Pipe(zabbix_sender) method and the Socket(direct to zabbix via tcp socket) method very simply. Just change:
|
106
|
+
|
107
|
+
```
|
108
|
+
sender = Zabbix::Sender::Pipe.new
|
109
|
+
```
|
110
|
+
to
|
111
|
+
```
|
112
|
+
sender = Zabbix::Sender::Socket.new
|
113
|
+
```
|
114
|
+
|
115
|
+
If you were specifiying a path to zabbix_sender using the Pipe method, remove that. If you're using a socket other than 10051 on your server/proxy, you'll want to add the port: option
|
116
|
+
|
117
|
+
The socket method doesn't support sending multiple batches between flushes, so you should just use sendBatchAtomic(aBatch) with the Socket method.
|
118
|
+
|
119
|
+
You don't have to change anything else - the rest of zabbix_sender_api works exactly the same with the Socket method, save for that it's arguably more efficient with system resources. The only drawback might be if zabbix changes the sender protocol. At that point until this library is update to support the change you can revert to the Pipe/zabbix_sender method if needed (if they change the command line switches radically there, changes will be required in this library as well).
|
data/bin/bundle
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'bundle' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "rubygems"
|
12
|
+
|
13
|
+
m = Module.new do
|
14
|
+
module_function
|
15
|
+
|
16
|
+
def invoked_as_script?
|
17
|
+
File.expand_path($0) == File.expand_path(__FILE__)
|
18
|
+
end
|
19
|
+
|
20
|
+
def env_var_version
|
21
|
+
ENV["BUNDLER_VERSION"]
|
22
|
+
end
|
23
|
+
|
24
|
+
def cli_arg_version
|
25
|
+
return unless invoked_as_script? # don't want to hijack other binstubs
|
26
|
+
return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
|
27
|
+
bundler_version = nil
|
28
|
+
update_index = nil
|
29
|
+
ARGV.each_with_index do |a, i|
|
30
|
+
if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
|
31
|
+
bundler_version = a
|
32
|
+
end
|
33
|
+
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
|
34
|
+
bundler_version = $1
|
35
|
+
update_index = i
|
36
|
+
end
|
37
|
+
bundler_version
|
38
|
+
end
|
39
|
+
|
40
|
+
def gemfile
|
41
|
+
gemfile = ENV["BUNDLE_GEMFILE"]
|
42
|
+
return gemfile if gemfile && !gemfile.empty?
|
43
|
+
|
44
|
+
File.expand_path("../../Gemfile", __FILE__)
|
45
|
+
end
|
46
|
+
|
47
|
+
def lockfile
|
48
|
+
lockfile =
|
49
|
+
case File.basename(gemfile)
|
50
|
+
when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
|
51
|
+
else "#{gemfile}.lock"
|
52
|
+
end
|
53
|
+
File.expand_path(lockfile)
|
54
|
+
end
|
55
|
+
|
56
|
+
def lockfile_version
|
57
|
+
return unless File.file?(lockfile)
|
58
|
+
lockfile_contents = File.read(lockfile)
|
59
|
+
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
|
60
|
+
Regexp.last_match(1)
|
61
|
+
end
|
62
|
+
|
63
|
+
def bundler_version
|
64
|
+
@bundler_version ||=
|
65
|
+
env_var_version || cli_arg_version ||
|
66
|
+
lockfile_version
|
67
|
+
end
|
68
|
+
|
69
|
+
def bundler_requirement
|
70
|
+
return "#{Gem::Requirement.default}.a" unless bundler_version
|
71
|
+
|
72
|
+
bundler_gem_version = Gem::Version.new(bundler_version)
|
73
|
+
|
74
|
+
requirement = bundler_gem_version.approximate_recommendation
|
75
|
+
|
76
|
+
return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
|
77
|
+
|
78
|
+
requirement += ".a" if bundler_gem_version.prerelease?
|
79
|
+
|
80
|
+
requirement
|
81
|
+
end
|
82
|
+
|
83
|
+
def load_bundler!
|
84
|
+
ENV["BUNDLE_GEMFILE"] ||= gemfile
|
85
|
+
|
86
|
+
activate_bundler
|
87
|
+
end
|
88
|
+
|
89
|
+
def activate_bundler
|
90
|
+
gem_error = activation_error_handling do
|
91
|
+
gem "bundler", bundler_requirement
|
92
|
+
end
|
93
|
+
return if gem_error.nil?
|
94
|
+
require_error = activation_error_handling do
|
95
|
+
require "bundler/version"
|
96
|
+
end
|
97
|
+
return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
|
98
|
+
warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
|
99
|
+
exit 42
|
100
|
+
end
|
101
|
+
|
102
|
+
def activation_error_handling
|
103
|
+
yield
|
104
|
+
nil
|
105
|
+
rescue StandardError, LoadError => e
|
106
|
+
e
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
m.load_bundler!
|
111
|
+
|
112
|
+
if m.invoked_as_script?
|
113
|
+
load Gem.bin_path("bundler", "bundle")
|
114
|
+
end
|
data/bin/byebug
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'byebug' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("byebug", "byebug")
|
data/bin/coderay
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'coderay' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("coderay", "coderay")
|
data/bin/example.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'example.rb' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("zabbix_sender_api", "example.rb")
|
data/bin/pry
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'pry' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("pry", "pry")
|
data/bin/rake
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rake", "rake")
|
@@ -1,20 +1,26 @@
|
|
1
1
|
module Zabbix
|
2
2
|
require 'socket'
|
3
3
|
require 'json'
|
4
|
+
require 'open3'
|
4
5
|
|
5
6
|
##
|
6
7
|
# AgentConfiguration holds data that's scraped from a zabbix_agentd config file. It's
|
7
|
-
# initialized when the gem is required.
|
8
|
+
# initialized when the gem is required. You may optionally re-initialize the
|
9
|
+
# class with your own list of paths to search. If it finds configuration you can
|
8
10
|
# access it with class methods. This is not meant to be instantiated.
|
9
|
-
|
10
11
|
class AgentConfiguration
|
11
|
-
|
12
|
-
|
12
|
+
##
|
13
|
+
# You may optionally pass an array of full paths to agent conf files to look for
|
14
|
+
# during initialization. By default some common places are checked, but
|
15
|
+
# you can specify your own. If you call this you'll re-initialize the class, which
|
16
|
+
# will scan for values in any of the listed files it happens to find.
|
17
|
+
def self.initialize(paths: [
|
13
18
|
'/etc/zabbix/zabbix_agentd.conf',
|
14
19
|
'/usr/local/etc/zabbix/zabbix_agentd.conf',
|
15
20
|
'/opt/zabbix/etc/zabbix_agentd.conf',
|
16
21
|
'/opt/zabbix/conf/zabbix_agentd.conf'
|
17
|
-
]
|
22
|
+
])
|
23
|
+
@agentConfPaths = paths
|
18
24
|
|
19
25
|
@proxy = nil
|
20
26
|
@hostname = nil
|
@@ -63,50 +69,155 @@ module Sender
|
|
63
69
|
require 'set'
|
64
70
|
|
65
71
|
##
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
|
70
|
-
#
|
71
|
-
class Pipe
|
72
|
-
##
|
73
|
-
# pipe file handle object
|
74
|
-
attr_reader :pipe
|
72
|
+
# Connection is an abstract class that defines the basis of specific
|
73
|
+
# connection types (Pipe, Socket). It is not meant to be instantiated
|
74
|
+
# on its own.
|
75
|
+
class Connection
|
75
76
|
##
|
76
77
|
# target host (server or proxy) name or ip
|
77
78
|
attr_reader :targetHost
|
79
|
+
attr_reader :pipe
|
80
|
+
##
|
81
|
+
# Initialize a new Connector object. Proxy is optional.
|
82
|
+
#
|
83
|
+
#
|
84
|
+
# An attempt is made to provide sane default values. If you have a zabbix_agentd.conf
|
85
|
+
# file in one of the usual places and zabbix_sender is on your path, it'll probably
|
86
|
+
# just work
|
87
|
+
#
|
88
|
+
def initialize(proxy: Zabbix::AgentConfiguration.zabbixProxy)
|
89
|
+
@targetHost = proxy
|
90
|
+
@pipe = nil
|
91
|
+
end
|
92
|
+
##
|
93
|
+
# Aborts execution if directly called. Subclasses must override.
|
94
|
+
#
|
95
|
+
def open
|
96
|
+
abort("Call to abstract method Connection::open")
|
97
|
+
end
|
98
|
+
##
|
99
|
+
# Aborts execution if directly called. Subclasses must override.
|
100
|
+
#
|
101
|
+
def sendBatch(aBatch)
|
102
|
+
abort("Call to abstract method Connection::sendBatch(aBatch)")
|
103
|
+
end
|
104
|
+
##
|
105
|
+
# Send a Batch instance to zabbix (via zabbix_sender). This opens the pipe,
|
106
|
+
# writes the data to the pipe, and closes the pipe all in one go.
|
107
|
+
#
|
108
|
+
def sendBatchAtomic(aBatch)
|
109
|
+
self.open
|
110
|
+
self.sendBatch(aBatch)
|
111
|
+
return self.flush
|
112
|
+
end
|
113
|
+
##
|
114
|
+
# Closes the zabbix_sender pipe if it's open
|
115
|
+
#
|
116
|
+
def flush
|
117
|
+
if @pipe and not @pipe.closed?
|
118
|
+
@pipe.close
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Socket instances enable TCPSocket based communication with a zabbix trapper server instance
|
125
|
+
#
|
126
|
+
class Socket < Connection
|
127
|
+
attr_reader :port
|
128
|
+
##
|
129
|
+
# Create a new socket connection object. Both proxy and port are optional.
|
130
|
+
#
|
131
|
+
# An attempt is made to provide sane default values. If you have a zabbix_agentd.conf
|
132
|
+
# file in one of the usual places and zabbix_sender is on your path, it'll probably
|
133
|
+
# just work
|
134
|
+
#
|
135
|
+
def initialize(proxy: Zabbix::AgentConfiguration.zabbixProxy, port: 10051)
|
136
|
+
super(proxy: proxy)
|
137
|
+
@port = port
|
138
|
+
@lastres = nil
|
139
|
+
end
|
140
|
+
##
|
141
|
+
# Open tcp socket to target proxy/server
|
142
|
+
#
|
143
|
+
def open
|
144
|
+
@pipe = TCPSocket.new(@targetHost, @port)
|
145
|
+
end
|
78
146
|
##
|
79
|
-
#
|
80
|
-
|
147
|
+
# Send aBatch to zabbix via the socket. Note that zabbix will close
|
148
|
+
# the connection after you send a complete message, so you
|
149
|
+
# *can't* do this:
|
150
|
+
#
|
151
|
+
# socket.open
|
152
|
+
# socket.sendBatch(a)
|
153
|
+
# socket.sendBatch(b) <-- this will blow up
|
154
|
+
# socket.flush
|
155
|
+
#
|
156
|
+
# ...as you can with Zabbix::Sender::Pipe. You're really best off
|
157
|
+
# just using sendBatchAtomic for sockets.
|
158
|
+
#
|
159
|
+
# This assumes that the socket is already open.
|
160
|
+
#
|
161
|
+
def sendBatch(aBatch)
|
162
|
+
header = %Q(ZBXD\x01)
|
163
|
+
data = aBatch.to_senderstruct.to_json
|
164
|
+
blob = %Q(#{header}#{[data.bytesize].pack("Q").force_encoding("UTF-8")}#{data})
|
165
|
+
@pipe.write(blob)
|
166
|
+
respHeader = @pipe.read(header.bytesize + 8)
|
167
|
+
datalen = respHeader[header.bytesize, 8].unpack("Q")[0]
|
168
|
+
@lastres = JSON.parse(@pipe.read(datalen))
|
169
|
+
end
|
81
170
|
|
171
|
+
def flush
|
172
|
+
super
|
173
|
+
return @lastres
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# Pipe instances utilize communication to a running instance of zabbix_sender
|
179
|
+
# via a pipe to STDIN. If you want your program to send data itself (as opposed
|
180
|
+
# to say printing stdin lines that you can then pipe to zabbix_sender yourself),
|
181
|
+
# you'll want to make an instance of this
|
182
|
+
#
|
183
|
+
class Pipe < Connection
|
82
184
|
|
83
185
|
##
|
84
|
-
#
|
186
|
+
# Create a new Pipe object. Both proxy: and path: are optional.
|
85
187
|
#
|
86
188
|
# An attempt is made to provide sane default values. If you have a zabbix_agentd.conf
|
87
189
|
# file in one of the usual places and zabbix_sender is on your path, it'll probably
|
88
190
|
# just work
|
89
191
|
#
|
90
192
|
def initialize(proxy: Zabbix::AgentConfiguration.zabbixProxy, path: 'zabbix_sender')
|
91
|
-
|
193
|
+
super(proxy: proxy)
|
194
|
+
@wait_thr = @stdout = @stderr = nil
|
92
195
|
@exePath = path
|
93
|
-
@pipe = nil
|
94
196
|
end
|
95
197
|
|
96
198
|
##
|
97
199
|
# Opens a pipe to the zabbix_sender command with appropriate options specified.
|
98
200
|
# If the pipe is already opened when this command is called, it is first closed
|
99
201
|
# via a call to flush
|
202
|
+
#
|
100
203
|
def open
|
101
204
|
self.flush
|
102
|
-
|
205
|
+
#@pipe = IO.popen(%Q(#{@exePath} -z #{@targetHost} -vv -T -i-),'w')
|
206
|
+
cmd = %Q(#{@exePath} -z #{@targetHost} -vv -T -i-)
|
207
|
+
@pipe,@stdout,@stderr,@wait_thr = Open3.popen3(cmd)
|
103
208
|
end
|
104
209
|
|
105
210
|
##
|
106
|
-
# Closes the
|
211
|
+
# Closes the open3 pipe stuff. We need this override method as
|
212
|
+
# closing an open3 pipe requires some extra work.
|
107
213
|
def flush
|
108
214
|
if @pipe and not @pipe.closed?
|
109
215
|
@pipe.close
|
216
|
+
stdout = @stdout.read
|
217
|
+
stderr = @stderr.read
|
218
|
+
@stdout.close
|
219
|
+
@stderr.close
|
220
|
+
return {stdout: stdout, stderr: stderr, success: @wait_thr.value.success?}
|
110
221
|
end
|
111
222
|
end
|
112
223
|
|
@@ -118,14 +229,6 @@ class Pipe
|
|
118
229
|
@pipe.puts(aBatch.to_senderline)
|
119
230
|
end
|
120
231
|
|
121
|
-
##
|
122
|
-
# Send a Batch instance to zabbix (via zabbix_sender). This opens the pipe,
|
123
|
-
# writes the data to the pipe, and closes the pipe all in one go.
|
124
|
-
def sendBatchAtomic(aBatch)
|
125
|
-
self.open
|
126
|
-
self.sendBatch(aBatch)
|
127
|
-
self.flush
|
128
|
-
end
|
129
232
|
|
130
233
|
end
|
131
234
|
|
@@ -165,6 +268,21 @@ class ItemData
|
|
165
268
|
end
|
166
269
|
return %Q("#{@hostname}" #{@key} #{@timestamp.to_i} #{@value}\n)
|
167
270
|
end
|
271
|
+
##
|
272
|
+
# Render the ItemData instance as an object suitable for conversion to json, for socket transmission
|
273
|
+
def to_senderstruct
|
274
|
+
if @timestamp.to_i == 0
|
275
|
+
puts %Q("#{@hostname}" #{@key} #{@timestamp.to_i} #{@value}\n)
|
276
|
+
abort("Attempt was made to render a timestamp of zero. You DO NOT want this - it can kill db performance. Fix it.")
|
277
|
+
else
|
278
|
+
return item = {
|
279
|
+
host: @hostname,
|
280
|
+
key: @key,
|
281
|
+
value: @value,
|
282
|
+
clock: @timestamp.to_i
|
283
|
+
}
|
284
|
+
end
|
285
|
+
end
|
168
286
|
end
|
169
287
|
|
170
288
|
##
|
@@ -198,12 +316,25 @@ class Discovery < ItemData
|
|
198
316
|
@entities.add(zabbified)
|
199
317
|
end
|
200
318
|
##
|
201
|
-
# Render this discovery
|
319
|
+
# Render this discovery as the structure an external discovery script should return. You can use this
|
320
|
+
# if you're writing custom external discovery logic
|
202
321
|
#
|
203
|
-
def
|
322
|
+
def to_discodata
|
204
323
|
disco = { 'data'=>Array.new }
|
205
324
|
disco['data'] = @entities.to_a
|
206
|
-
|
325
|
+
return disco
|
326
|
+
end
|
327
|
+
##
|
328
|
+
# Render this discovery instance as a zabbix_sender line.
|
329
|
+
#
|
330
|
+
def to_senderline
|
331
|
+
@value = self.to_discodata.to_json
|
332
|
+
super
|
333
|
+
end
|
334
|
+
##
|
335
|
+
# Render this discovery instance as an object suitable for conversion to json for socket transmission
|
336
|
+
def to_senderstruct
|
337
|
+
@value = self.to_discodata
|
207
338
|
super
|
208
339
|
end
|
209
340
|
end
|
@@ -262,6 +393,12 @@ class Batch
|
|
262
393
|
@data.unshift(aDiscovery)
|
263
394
|
end
|
264
395
|
|
396
|
+
##
|
397
|
+
# Append another batch's data into this one.
|
398
|
+
def appendBatch(aBatch)
|
399
|
+
@data.append(*aBatch.data)
|
400
|
+
end
|
401
|
+
|
265
402
|
##
|
266
403
|
# Render this batch of data as a sequence of lines of text appropriate
|
267
404
|
# for sending into zabbix_sender
|
@@ -269,6 +406,16 @@ class Batch
|
|
269
406
|
def to_senderline
|
270
407
|
@data.collect {|line| line.to_senderline}.join
|
271
408
|
end
|
409
|
+
##
|
410
|
+
# Render this batch as a json object
|
411
|
+
#
|
412
|
+
def to_senderstruct
|
413
|
+
return batch = {
|
414
|
+
request: "sender data",
|
415
|
+
data: @data.collect {|item| item.to_senderstruct},
|
416
|
+
clock: @time.to_i
|
417
|
+
}
|
418
|
+
end
|
272
419
|
end
|
273
420
|
|
274
421
|
end # module Sender
|