zabbix_agent 0.1
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/README +41 -0
- data/zabbix_agent.rb +238 -0
- metadata +67 -0
data/README
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
Zabbix Ruby agent readme
|
|
2
|
+
|
|
3
|
+
This is a pure Ruby implementation of the Zabbix agent. The agent supports
|
|
4
|
+
both the 1.1 and 1.4 (default) agent protocols. Presently the agent only
|
|
5
|
+
supports passive connections. The agent is multi threaded and will spawn a new
|
|
6
|
+
thread for each incoming connection. After a timeout period (default 30s) the
|
|
7
|
+
thread will be killed and the associated socket closed. Effort has been made
|
|
8
|
+
to make the agent itself thread safe, however the agent makes no effort to
|
|
9
|
+
ensure the functions it calls are thread safe. Thus it is important that the
|
|
10
|
+
end user be aware they are responsible for their functions being thread safe.
|
|
11
|
+
|
|
12
|
+
Required Gems:
|
|
13
|
+
bit-struct
|
|
14
|
+
|
|
15
|
+
Use:
|
|
16
|
+
Use of the agent is pretty straight forward.
|
|
17
|
+
|
|
18
|
+
agent = ZabbixAgent.new(nil,10060)
|
|
19
|
+
|
|
20
|
+
This will instantiate a new agent listening on all interfaces on port 10060.
|
|
21
|
+
The key thing to note is that this will not start the agent's main thread.
|
|
22
|
+
|
|
23
|
+
agent.register("myfunc",self.method(:myfunc))
|
|
24
|
+
|
|
25
|
+
This will register a new key/item with the agent. Behind the scenes all keys
|
|
26
|
+
are stored in a Hash which in turn is used to look up which function to call,
|
|
27
|
+
the resul from this function called is then passed back to the Zabbix server.
|
|
28
|
+
The return value must be in string format. The function will be passed
|
|
29
|
+
whatever arguments were received from the Zabbix server, for instance if the
|
|
30
|
+
agent received the following from the server:
|
|
31
|
+
myfunc[1,2,"str"]
|
|
32
|
+
A string of '1,2,3,"str"' would be passed to the function.
|
|
33
|
+
|
|
34
|
+
Starting the agent is as simple as:
|
|
35
|
+
agent.start
|
|
36
|
+
|
|
37
|
+
This method will return as it will spawn two threads, the main loop and the
|
|
38
|
+
watchdog.
|
|
39
|
+
|
|
40
|
+
After the agent has started it is possible to register more items dynamically.
|
|
41
|
+
The item zbx_available will return the current list of items the agent supports.
|
data/zabbix_agent.rb
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
|
|
3
|
+
#License:: GPL 2.0 http://www.gnu.org/licenses/gpl-2.0.html
|
|
4
|
+
#Copyright:: Copyright (C) 2009,2010 Andrew Nelson nelsonab(at)red-tux(dot)net
|
|
5
|
+
#
|
|
6
|
+
#This program is free software; you can redistribute it and/or
|
|
7
|
+
#modify it under the terms of the GNU General Public License
|
|
8
|
+
#as published by the Free Software Foundation; either version 2
|
|
9
|
+
#of the License, or (at your option) any later version.
|
|
10
|
+
#
|
|
11
|
+
#This program is distributed in the hope that it will be useful,
|
|
12
|
+
#but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
#GNU General Public License for more details.
|
|
15
|
+
#
|
|
16
|
+
#You should have received a copy of the GNU General Public License
|
|
17
|
+
#along with this program; if not, write to the Free Software
|
|
18
|
+
#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
19
|
+
|
|
20
|
+
version_header = <<EOF
|
|
21
|
+
##########################################
|
|
22
|
+
# Subversion information
|
|
23
|
+
# $Id: zabbix_agent.rb 228 2010-10-13 06:23:18Z nelsonab $
|
|
24
|
+
# $Revision: 228 $
|
|
25
|
+
##########################################
|
|
26
|
+
EOF
|
|
27
|
+
|
|
28
|
+
REVISION=version_header.split("\n")[3].split[2]
|
|
29
|
+
|
|
30
|
+
require 'rubygems'
|
|
31
|
+
require 'thread'
|
|
32
|
+
require 'socket'
|
|
33
|
+
|
|
34
|
+
require 'bit-struct'
|
|
35
|
+
|
|
36
|
+
#Class for holding the 1.4 protocol header
|
|
37
|
+
class ZBX_14 < BitStruct
|
|
38
|
+
char :header, 32, "Zabbix Header"
|
|
39
|
+
unsigned :version, 8, "Protocol Version"
|
|
40
|
+
unsigned :length, 64, :endian=>:little
|
|
41
|
+
rest :data, "Data"
|
|
42
|
+
|
|
43
|
+
initial_value.header='ZBXD'
|
|
44
|
+
initial_value.version=1
|
|
45
|
+
|
|
46
|
+
def set_data(data)
|
|
47
|
+
self.data=data
|
|
48
|
+
self.length=data.length
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
alias initialize_orig initialize
|
|
52
|
+
|
|
53
|
+
def initialize(data)
|
|
54
|
+
initialize_orig
|
|
55
|
+
set_data(data)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class WorkerThread < Thread
|
|
60
|
+
attr_reader :starttime
|
|
61
|
+
|
|
62
|
+
def initialize(*args)
|
|
63
|
+
@starttime=Time.now.to_i
|
|
64
|
+
@socket=nil
|
|
65
|
+
args.each {|item|
|
|
66
|
+
@socket = item if item.class==TCPSocket
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
raise "No TCP Socket Found!" if @socket.nil?
|
|
70
|
+
|
|
71
|
+
super(*args)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def kill
|
|
75
|
+
@socket.close #This will generate an IOError in the thread
|
|
76
|
+
super
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class ZabbixAgent
|
|
81
|
+
|
|
82
|
+
attr_accessor :thread_timeout, :watchdog_sleep_timer
|
|
83
|
+
|
|
84
|
+
#Intialize the port and address to listen on
|
|
85
|
+
#address is the address on the local host to listen to
|
|
86
|
+
#port is the port on the local host to listen to
|
|
87
|
+
#protocol_version is one of the following:
|
|
88
|
+
# :ver1_1, :ver1_4
|
|
89
|
+
# This denotes the protocol version to be used when sending the data back.
|
|
90
|
+
# Version 1.4 is the default
|
|
91
|
+
def initialize(address,port,protocol_version=:ver1_4)
|
|
92
|
+
@proto_ver=protocol_version
|
|
93
|
+
@socket = TCPServer.open(address,port)
|
|
94
|
+
|
|
95
|
+
@functions_mutex = Mutex.new
|
|
96
|
+
@workers_mutex = Mutex.new
|
|
97
|
+
|
|
98
|
+
@thread_timeout=30
|
|
99
|
+
@watchdog_sleep_timer=1
|
|
100
|
+
|
|
101
|
+
#setup internal variables
|
|
102
|
+
@functions={} #Hash of function callbacks key(str):function(method)
|
|
103
|
+
|
|
104
|
+
register("agent.version",self.method(:version))
|
|
105
|
+
register("zbx_available",self.method(:show_avail))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
#setup the main listener thread
|
|
109
|
+
#This function will return the thread created
|
|
110
|
+
def start
|
|
111
|
+
@workers=[]
|
|
112
|
+
@main_thread = Thread.new do
|
|
113
|
+
main
|
|
114
|
+
puts "Main thread quit"
|
|
115
|
+
end
|
|
116
|
+
@watchdog_thread = Thread.new do watchdog end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
#this is the main loop for the thread.
|
|
120
|
+
def main
|
|
121
|
+
#When a connection comes in, get the string and send it to the dispatcher
|
|
122
|
+
#which will then take care of the request and then go back to listening
|
|
123
|
+
#for a new connection
|
|
124
|
+
#This agent will not spawn new threads for each incoming connection.
|
|
125
|
+
loop do
|
|
126
|
+
t = WorkerThread.new(@socket.accept) {|session|
|
|
127
|
+
worker_method(session)
|
|
128
|
+
}
|
|
129
|
+
@workers_mutex.synchronize {
|
|
130
|
+
@workers+=[t]
|
|
131
|
+
}
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def watchdog
|
|
136
|
+
loop do
|
|
137
|
+
@workers_mutex.synchronize {
|
|
138
|
+
@workers.each {|thread|
|
|
139
|
+
if thread.status==false
|
|
140
|
+
@workers.delete(thread) # Thread died of natural causes, we don't need to track it anymore
|
|
141
|
+
elsif thread.starttime < Time.now.to_i-@thread_timeout
|
|
142
|
+
puts "Killing #{thread} due to timeout"
|
|
143
|
+
thread.kill
|
|
144
|
+
@workers.delete(thread)
|
|
145
|
+
end
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
sleep @watchdog_sleep_timer
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def worker_method(session)
|
|
153
|
+
puts "starting socket #{session}"
|
|
154
|
+
begin
|
|
155
|
+
str=session.gets
|
|
156
|
+
dispatch(str,session)
|
|
157
|
+
session.close
|
|
158
|
+
rescue IOError
|
|
159
|
+
#IO Errors are likely due to the thread being killed. If there was a better way to
|
|
160
|
+
#trap this I would rather use it.
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def output11(socket,data)
|
|
165
|
+
socket.print data
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def output14(socket,data)
|
|
169
|
+
zbx=ZBX_14.new(data)
|
|
170
|
+
socket.send(zbx,0)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
#Registers a method with the agent.
|
|
174
|
+
#key is of type string, function is of class method
|
|
175
|
+
#When the key is received, the function is called with the arguments from
|
|
176
|
+
#the Zabbix server and is expected to return the value to be sent
|
|
177
|
+
#The class initializer contains a reference to the use of this function
|
|
178
|
+
#The
|
|
179
|
+
def register(key,function)
|
|
180
|
+
@functions_mutex.synchronize {
|
|
181
|
+
@functions.merge!({key,function})
|
|
182
|
+
}
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def version(args)
|
|
186
|
+
return "Ruby Zabbix agent svn revision:#{REVISION}"
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def show_avail(args)
|
|
190
|
+
retval=""
|
|
191
|
+
p @functions
|
|
192
|
+
@functions.each_key do |key|
|
|
193
|
+
retval += "#{key}\n"
|
|
194
|
+
end
|
|
195
|
+
p retval
|
|
196
|
+
return retval
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def dispatch(str,socket)
|
|
200
|
+
matches = (str =~ /(\S+)\[(\S+)\]|(\S+)/)
|
|
201
|
+
if $1.nil?
|
|
202
|
+
func=$3
|
|
203
|
+
arg=nil
|
|
204
|
+
else
|
|
205
|
+
func=$1
|
|
206
|
+
arg=$2
|
|
207
|
+
end
|
|
208
|
+
if @functions.has_key?(func)
|
|
209
|
+
retval=nil
|
|
210
|
+
@functions_mutex.synchronize {
|
|
211
|
+
retval=@functions[func].call(arg)
|
|
212
|
+
}
|
|
213
|
+
else
|
|
214
|
+
socket.print "ZBX_NOTSUPPORTED"
|
|
215
|
+
return
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
case @proto_ver
|
|
219
|
+
when :ver1_1 then output11(socket,retval)
|
|
220
|
+
when :ver1_4 then output14(socket,retval)
|
|
221
|
+
else output11(socket,retval)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
if __FILE__ == $0
|
|
227
|
+
Thread.abort_on_exception=true
|
|
228
|
+
|
|
229
|
+
agent = ZabbixAgent.new(nil,10060)
|
|
230
|
+
# agent.thread_timeout=30
|
|
231
|
+
agent.start
|
|
232
|
+
|
|
233
|
+
puts "To quit press ctrl-c"
|
|
234
|
+
#we need to have an infinate loop for the thread to work.
|
|
235
|
+
loop do
|
|
236
|
+
sleep 100
|
|
237
|
+
end
|
|
238
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: zabbix_agent
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 9
|
|
5
|
+
prerelease: false
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 1
|
|
9
|
+
version: "0.1"
|
|
10
|
+
platform: ruby
|
|
11
|
+
authors:
|
|
12
|
+
- A. Nelson
|
|
13
|
+
autorequire:
|
|
14
|
+
bindir: bin
|
|
15
|
+
cert_chain: []
|
|
16
|
+
|
|
17
|
+
date: 2010-12-28 00:00:00 -05:00
|
|
18
|
+
default_executable:
|
|
19
|
+
dependencies: []
|
|
20
|
+
|
|
21
|
+
description: Class for creating Zabbix agents in Ruby
|
|
22
|
+
email: nelsonab@red-tux.net
|
|
23
|
+
executables: []
|
|
24
|
+
|
|
25
|
+
extensions: []
|
|
26
|
+
|
|
27
|
+
extra_rdoc_files: []
|
|
28
|
+
|
|
29
|
+
files:
|
|
30
|
+
- zabbix_agent.rb
|
|
31
|
+
- README
|
|
32
|
+
has_rdoc: true
|
|
33
|
+
homepage: http://trac.red-tux.net/
|
|
34
|
+
licenses:
|
|
35
|
+
- LGPL 2.1
|
|
36
|
+
post_install_message:
|
|
37
|
+
rdoc_options: []
|
|
38
|
+
|
|
39
|
+
require_paths:
|
|
40
|
+
- .
|
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
42
|
+
none: false
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
hash: 3
|
|
47
|
+
segments:
|
|
48
|
+
- 0
|
|
49
|
+
version: "0"
|
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
51
|
+
none: false
|
|
52
|
+
requirements:
|
|
53
|
+
- - ">="
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
hash: 3
|
|
56
|
+
segments:
|
|
57
|
+
- 0
|
|
58
|
+
version: "0"
|
|
59
|
+
requirements: []
|
|
60
|
+
|
|
61
|
+
rubyforge_project: zabbix_agent
|
|
62
|
+
rubygems_version: 1.3.7
|
|
63
|
+
signing_key:
|
|
64
|
+
specification_version: 3
|
|
65
|
+
summary: Class for creating Zabbix agents in Ruby
|
|
66
|
+
test_files: []
|
|
67
|
+
|