snmp4em 0.2.1 → 0.3

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/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ # Use `bundle install` in order to install these gems
2
+ # Use `bundle exec rake` in order to run the specs using the bundle
3
+
4
+ gem "eventmachine", "0.12.10"
5
+ gem "snmp", ">= 1.0.2"
6
+
7
+ gem "rspec", "1.3.0"
data/Manifest CHANGED
@@ -1,16 +1,29 @@
1
- README
1
+ Gemfile
2
+ README.rdoc
2
3
  Rakefile
3
4
  lib/snmp4em.rb
4
- lib/snmp4em/common.rb
5
+ lib/snmp4em/common_requests.rb
6
+ lib/snmp4em/extensions.rb
7
+ lib/snmp4em/extensions/snmp/integer.rb
8
+ lib/snmp4em/extensions/snmp/ip_address.rb
9
+ lib/snmp4em/extensions/snmp/null.rb
10
+ lib/snmp4em/extensions/snmp/object_id.rb
11
+ lib/snmp4em/extensions/snmp/octet_string.rb
12
+ lib/snmp4em/extensions/snmp/response_error.rb
5
13
  lib/snmp4em/handler.rb
14
+ lib/snmp4em/manager.rb
6
15
  lib/snmp4em/requests/snmp_get_request.rb
7
16
  lib/snmp4em/requests/snmp_getbulk_request.rb
8
17
  lib/snmp4em/requests/snmp_getnext_request.rb
9
18
  lib/snmp4em/requests/snmp_set_request.rb
10
19
  lib/snmp4em/requests/snmp_walk_request.rb
11
- lib/snmp4em/snmp_connection.rb
12
20
  lib/snmp4em/snmp_request.rb
13
- lib/snmp4em/snmp_v1.rb
14
- lib/snmp4em/snmp_v2.rb
15
- snmp4em.gemspec
21
+ lib/snmp4em/snmp_v2c_requests.rb
22
+ spec/models/test_message.rb
23
+ spec/models/test_request.rb
24
+ spec/models/test_response.rb
25
+ spec/spec.opts
26
+ spec/spec_helper.rb
27
+ spec/unit/handler_spec.rb
28
+ spec/unit/manager_spec.rb
16
29
  Manifest
@@ -2,12 +2,12 @@
2
2
 
3
3
  == Summary
4
4
 
5
- This library extends the Ruby-SNMP[http://snmplib.rubyforge.org] to use the asynchronous EventMachine[http://rubyeventmachine.com] library for added performance and scalability. This allows code to scale monitoring applications to access a very high number of devices without the need for complex asynchronous I/O handling.
5
+ This gem extends Ruby-SNMP[http://snmplib.rubyforge.org] to use the asynchronous EventMachine[http://rubyeventmachine.com] library for added performance and scalability. This allows code to scale monitoring applications to access a very high number of devices without the need for complex asynchronous I/O handling.
6
6
 
7
7
 
8
8
  == Features
9
9
 
10
- The initial version 0.1.0 of this software supports:
10
+ Version 0.2.1 supports:
11
11
 
12
12
  * SNMP v1 and v2 only
13
13
  * SNMP Get, GetNext, GetBulk (v2 only), Set, and Walk requests.
@@ -38,27 +38,27 @@ A few definitions:
38
38
 
39
39
  A simple SNMP-GET:
40
40
 
41
- EM::run do
42
- snmp = SNMP4EM::SNMPv1.new(:host => "192.168.1.1")
41
+ EM.run {
42
+ snmp = SNMP4EM::Manager.new(:host => "192.168.1.1", :version => :SNMPv1)
43
43
 
44
- request = snmp.get([OID_SYSNAME, OID_SYSLOCATION])
44
+ request = snmp.get([OID_SYSNAME, OID_SYSLOCATION])
45
45
 
46
- request.callback do |response|
47
- puts "System name = #{response[OID_SYSNAME]}"
48
- puts "System location = #{response[OID_SYSLOCATION]}"
49
- end
46
+ request.callback do |response|
47
+ puts "System name = #{response[OID_SYSNAME]}"
48
+ puts "System location = #{response[OID_SYSLOCATION]}"
49
+ end
50
50
 
51
- request.errback do |error|
52
- puts "GET got error #{error}"
53
- end
54
- end
51
+ request.errback do |error|
52
+ puts "GET got error #{error}"
53
+ end
54
+ }
55
55
 
56
56
  A simple SNMP-GETNEXT:
57
57
 
58
- EM::run do
59
- snmp = SNMP4EM::SNMPv1.new(:host => "192.168.1.1")
58
+ EM.run {
59
+ snmp = SNMP4EM::Manager.new(:host => "192.168.1.1")
60
60
 
61
- request = snmp.getnext([OID_SYSNAME])
61
+ request = snmp.getnext(OID_SYSNAME)
62
62
 
63
63
  request.callback do |response|
64
64
  r = response[OID_SYSNAME]
@@ -66,14 +66,14 @@ A simple SNMP-GETNEXT:
66
66
  end
67
67
 
68
68
  request.errback do |error|
69
- puts "GET got error #{error}"
69
+ puts "GETNEXT got error #{error}"
70
70
  end
71
- end
71
+ }
72
72
 
73
73
  A simple SNMP-SET:
74
74
 
75
- EM::run do
76
- snmp = SNMP4EM::SNMPv1.new(:host => "192.168.1.1")
75
+ EM.run {
76
+ snmp = SNMP4EM::Manager.new(:host => "192.168.1.1")
77
77
 
78
78
  request = snmp.set({OID_SYSNAME => "My System Name", OID_SYSLOCATION => "My System Location"})
79
79
 
@@ -92,16 +92,16 @@ A simple SNMP-SET:
92
92
  end
93
93
 
94
94
  request.errback do |error|
95
- puts "GET got error #{error}"
95
+ puts "SET got error #{error}"
96
96
  end
97
- end
97
+ }
98
98
 
99
99
  A simple SNMP-WALK:
100
100
 
101
- EM::run do
102
- snmp = SNMP4EM::SNMPv1.new(:host => "192.168.1.1")
101
+ EM.run {
102
+ snmp = SNMP4EM::Manager.new(:host => "192.168.1.1")
103
103
 
104
- request = snmp.walk([OID_SYSTEM])
104
+ request = snmp.walk(OID_SYSTEM)
105
105
 
106
106
  request.callback do |response|
107
107
  if (response[OID_SYSTEM].is_a? Array)
@@ -114,16 +114,16 @@ A simple SNMP-WALK:
114
114
  end
115
115
 
116
116
  request.errback do |error|
117
- puts "GET got error #{error}"
117
+ puts "WALK got error #{error}"
118
118
  end
119
- end
119
+ }
120
120
 
121
121
  A simple SNMP-GET-BULK:
122
122
 
123
- EM::run do
124
- snmp = SNMP4EM::SNMPv2.new(:host => "192.168.1.1")
123
+ EM.run {
124
+ snmp = SNMP4EM::Manager.new(:host => "192.168.1.1")
125
125
 
126
- request = snmp.getbulk([OID_SYSTEM])
126
+ request = snmp.getbulk(OID_SYSTEM)
127
127
 
128
128
  request.callback do |response|
129
129
  if (response[OID_SYSTEM].is_a? Array)
@@ -136,21 +136,24 @@ A simple SNMP-GET-BULK:
136
136
  end
137
137
 
138
138
  request.errback do |error|
139
- puts "GET got error #{error}"
139
+ puts "GET-BULK got error #{error}"
140
140
  end
141
- end
141
+ }
142
142
 
143
143
  == Change Log
144
144
 
145
- Version 0.1.0:
145
+ Version 0.2.1:
146
146
 
147
- * Initial deployment, ability to run get/getnext/set/walk requests in parallel
147
+ * Code cleanups, speed boosts
148
148
 
149
149
  Version 0.2.0:
150
150
 
151
151
  * Added support for SNMPv2, including GET-BULK operations
152
152
 
153
- == Developer Contact Information
153
+ Version 0.1.0:
154
+
155
+ * Initial deployment, ability to run get/getnext/set/walk requests in parallel
156
+
157
+ == Credits
154
158
 
155
- Norman Elton
156
- normelton@gmail.com
159
+ Author: Norman Elton normelton@gmail.com
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require "rubygems"
2
+ require "rake"
3
+ require "echoe"
4
+
5
+ gem "rspec", "1.3.0"
6
+
7
+ require "spec/rake/spectask"
8
+
9
+ Echoe.new("snmp4em", "0.2.1") do |p|
10
+ p.description = "A high-performance SNMP engine built on EventMachine and Ruby-SNMP"
11
+ p.url = "http://github.com/normelton/snmp4em"
12
+ p.author = "Norman Elton"
13
+ p.email = "normelton@gmail.com"
14
+ p.development_dependencies = []
15
+ p.rdoc_pattern = ["lib", "README.rdoc"]
16
+ p.dependencies = ["snmp"]
17
+ end
@@ -1,25 +1,7 @@
1
1
  # The SNMP4EM library
2
2
 
3
3
  module SNMP4EM
4
- class SNMPv1 < SnmpConnection
5
- attr_reader :community_ro, :community_rw
6
-
7
- # Creates a new object to communicate with SNMPv1 agents. Optionally pass in the following parameters:
8
- # * _host_ - IP/hostname of remote agent (default: 127.0.0.1)
9
- # * _port_ - UDP port on remote agent (default: 161)
10
- # * _community_ - Community string to use (default: public)
11
- # * _community_ro_ - Read-only community string to use for get/getnext/walk operations (default: public)
12
- # * _community_rw_ - Read-write community string to use for set operations (default: public)
13
- # * _timeout_ - Number of seconds to wait before a request times out (default: 1)
14
- # * _retries_ - Number of retries before failing (default: 3)
15
-
16
- def initialize(args = {})
17
- super(args)
18
-
19
- @community_ro = args[:community_ro] || args[:community] || "public"
20
- @community_rw = args[:community_rw] || args[:community] || "public"
21
- end
22
-
4
+ module CommonRequests
23
5
  # Sends an SNMP-GET request to the remote agent for all OIDs specified in the _oids_ array. Returns a SnmpGetRequest object,
24
6
  # which implements EM::Deferrable. From there, implement a callback/errback to fetch the result. On success, the result will be
25
7
  # a hash, mapping requested OID values to results.
@@ -28,9 +10,7 @@ module SNMP4EM
28
10
  # * _return_raw_ - Return objects and errors as their raw SNMP types, such as SNMP::Integer instead of native Ruby integers, SNMP::OctetString instead of native Ruby strings, etc. (default: false)
29
11
 
30
12
  def get(oids, args = {})
31
- request = SnmpGetRequest.new(self, oids, args.merge(:version => :SNMPv1))
32
- SnmpConnection.pending_requests << request
33
- return request
13
+ SnmpGetRequest.new(self, oids, args.merge(:version => @version))
34
14
  end
35
15
 
36
16
  # Sends an SNMP-GETNEXT request to the remote agent for all OIDs specified in the _oids_ array. Returns a SnmpGetRequest object,
@@ -42,9 +22,7 @@ module SNMP4EM
42
22
  # * _return_raw_ - Return objects and errors as their raw SNMP types, such as SNMP::Integer instead of native Ruby integers, SNMP::OctetString instead of native Ruby strings, etc. (default: false)
43
23
 
44
24
  def getnext(oids, args = {})
45
- request = SnmpGetNextRequest.new(self, oids, args.merge(:version => :SNMPv1))
46
- SnmpConnection.pending_requests << request
47
- return request
25
+ SnmpGetNextRequest.new(self, oids, args.merge(:version => @version))
48
26
  end
49
27
 
50
28
  # Sends an SNMP-SET request to the remote agent for all OIDs specified in the _oids_ hash. The hash must map OID values to requested
@@ -57,9 +35,7 @@ module SNMP4EM
57
35
  # * _return_raw_ - Return error objects as SNMP::ResponseError instead of a symbol
58
36
 
59
37
  def set(oids, args = {})
60
- request = SnmpSetRequest.new(self, oids, args.merge(:version => :SNMPv1))
61
- SnmpConnection.pending_requests << request
62
- return request
38
+ SnmpSetRequest.new(self, oids, args.merge(:version => @version))
63
39
  end
64
40
 
65
41
  # Sends a series of SNMP-GETNEXT requests to simulate an SNMP "walk" operation. Given an OID prefix, the library will keep requesting the
@@ -74,9 +50,7 @@ module SNMP4EM
74
50
  # * _max_results_ - Maximum number of results to be returned for any single OID prefix (default: nil = unlimited)
75
51
 
76
52
  def walk(oids, args = {})
77
- request = SnmpWalkRequest.new(self, oids, args.merge(:version => :SNMPv1))
78
- SnmpConnection.pending_requests << request
79
- return request
53
+ SnmpWalkRequest.new(self, oids, args.merge(:version => @version))
80
54
  end
81
55
  end
82
- end
56
+ end
@@ -0,0 +1,7 @@
1
+ module SNMP #:nodoc:
2
+
3
+ class Integer #:nodoc:
4
+ alias :rubify :to_i
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ module SNMP #:nodoc:
2
+
3
+ class IpAddress #:nodoc:
4
+ alias :rubify :to_s
5
+ end
6
+
7
+ end
@@ -0,0 +1,11 @@
1
+ module SNMP #:nodoc:
2
+
3
+ class Null #:nodoc:
4
+ class << self
5
+ def rubify
6
+ nil
7
+ end
8
+ end
9
+ end
10
+
11
+ end
@@ -0,0 +1,7 @@
1
+ module SNMP #:nodoc:
2
+
3
+ class ObjectId #:nodoc:
4
+ alias :rubify :to_s
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ module SNMP #:nodoc:
2
+
3
+ class OctetString #:nodoc:
4
+ alias :rubify :to_s
5
+ end
6
+
7
+ end
@@ -0,0 +1,17 @@
1
+ module SNMP #:nodoc:
2
+
3
+ class ResponseError
4
+ attr_reader :error_status
5
+ alias :rubify :error_status #:nodoc:
6
+
7
+ def initialize(error_status) #:nodoc:
8
+ @error_status = error_status
9
+ end
10
+
11
+ # String representation of this error
12
+ def to_s
13
+ @error_status.to_s
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,6 @@
1
+ require "snmp4em/extensions/snmp/integer.rb"
2
+ require "snmp4em/extensions/snmp/ip_address.rb"
3
+ require "snmp4em/extensions/snmp/null.rb"
4
+ require "snmp4em/extensions/snmp/object_id.rb"
5
+ require "snmp4em/extensions/snmp/octet_string.rb"
6
+ require "snmp4em/extensions/snmp/response_error.rb"
@@ -1,18 +1,27 @@
1
1
  module SNMP4EM
2
- module Handler #:nodoc:
2
+ class Handler < EventMachine::Connection #:nodoc:
3
3
  def receive_data(data)
4
4
  begin
5
5
  message = SNMP::Message.decode(data)
6
6
  rescue Exception => err
7
+ # the request that this malformed response corresponds to
8
+ # will timeout and retry
7
9
  return
8
10
  end
9
11
 
10
12
  response = message.pdu
11
- request_id = response.request_id
12
-
13
- if (request = SnmpConnection.pending_requests.select{|r| r.snmp_id == request_id}.first)
13
+ request = Manager.pending_requests[response.request_id]
14
+
15
+ #
16
+ # in the event of a timeout retry, the request will have been
17
+ # pruned from the Manager, so the response is to an expired
18
+ # request, ignore it.
19
+ #
20
+
21
+ if request
22
+ request.timeout_timer.cancel
14
23
  request.handle_response(response)
15
24
  end
16
25
  end
17
26
  end
18
- end
27
+ end
@@ -0,0 +1,69 @@
1
+ # The SNMP4EM library
2
+
3
+ module SNMP4EM
4
+ class Manager
5
+ include SNMP4EM::CommonRequests
6
+
7
+ #
8
+ # @pending_requests maps a request's id to its SnmpRequest
9
+ #
10
+ @pending_requests = {}
11
+ @socket = nil
12
+
13
+ class << self
14
+ attr_reader :pending_requests
15
+ attr_reader :socket
16
+
17
+ def init_socket #:nodoc:
18
+ # When the socket is in error state, close the socket and re-open a new one.
19
+ if !@socket.nil? && @socket.error?
20
+ @socket.close_connection
21
+ @socket = nil
22
+ end
23
+
24
+ @socket ||= EM::open_datagram_socket("0.0.0.0", 0, Handler)
25
+ end
26
+
27
+ def track_request(request)
28
+ @pending_requests.delete(request.snmp_id)
29
+
30
+ begin
31
+ request.snmp_id = rand(2**31) # Largest SNMP Signed INTEGER
32
+ end while @pending_requests[request.snmp_id]
33
+
34
+ @pending_requests[request.snmp_id] = request
35
+ end
36
+ end
37
+
38
+ attr_reader :host, :port, :timeout, :retries, :version, :community_ro, :community_rw
39
+
40
+ # Creates a new object to communicate with SNMPv1 agents. Optionally pass in the following parameters:
41
+ # * _host_ - IP/hostname of remote agent (default: 127.0.0.1)
42
+ # * _port_ - UDP port on remote agent (default: 161)
43
+ # * _community_ - Community string to use (default: public)
44
+ # * _community_ro_ - Read-only community string to use for get/getnext/walk operations (default: public)
45
+ # * _community_rw_ - Read-write community string to use for set operations (default: public)
46
+ # * _timeout_ - Number of seconds to wait before a request times out (default: 1)
47
+ # * _retries_ - Number of retries before failing (default: 3)
48
+
49
+ def initialize(args = {})
50
+ @host = args[:host] || "127.0.0.1"
51
+ @port = args[:port] || 161
52
+ @timeout = args[:timeout] || 1
53
+ @retries = args[:retries] || 3
54
+ @version = args[:version] || :SNMPv2c
55
+
56
+ self.extend SNMPv2cRequests if @version == :SNMPv2c
57
+
58
+ @community_ro = args[:community_ro] || args[:community] || "public"
59
+ @community_rw = args[:community_rw] || args[:community] || "public"
60
+
61
+ self.class.init_socket
62
+ end
63
+
64
+ def send(message) #:nodoc:
65
+ self.class.socket.send_datagram message.encode, @host, @port
66
+ end
67
+
68
+ end
69
+ end
@@ -1,38 +1,22 @@
1
1
  module SNMP4EM
2
2
 
3
- # Returned from SNMP4EM::SNMPv1.get(). This implements EM::Deferrable, so you can hang a callback()
4
- # or errback() to retrieve the results.
3
+ # This implements EM::Deferrable, so you can hang a callback() or errback() to retrieve the results.
5
4
 
6
5
  class SnmpGetRequest < SnmpRequest
7
- attr_reader :snmp_id
6
+ attr_accessor :snmp_id
8
7
 
9
8
  # For an SNMP-GET request, @pending_oids will be a ruby array of SNMP::ObjectNames that need to be fetched. As
10
9
  # responses come back from the agent, this array will be pruned of any error-producing OIDs. Once no errors
11
10
  # are returned, the @responses hash will be populated and returned.
12
11
 
13
- def initialize(sender, oids, args = {}) #:nodoc:
14
- @sender = sender
15
-
16
- @timeout_timer = nil
17
- @timeout_retries = @sender.retries
18
- @error_retries = oids.size
19
-
20
- @version = args[:version] || :SNMPv1
21
- @return_raw = args[:return_raw] || false
22
-
23
- @responses = Hash.new
24
- @pending_oids = SNMP::VarBindList.new(oids).collect{|r| r.name}
25
-
26
- init_callbacks
27
- send
28
- end
29
-
30
12
  def handle_response(response) #:nodoc:
31
- if (response.error_status == :noError)
13
+ if response.error_status == :noError
32
14
  # No errors, populate the @responses object so it can be returned
33
15
  response.each_varbind do |vb|
34
16
  request_oid = @pending_oids.shift
35
- @responses[request_oid.to_s] = vb.value
17
+ value = @return_raw || !vb.value.respond_to?(:rubify) ? vb.value : vb.value.rubify
18
+
19
+ @responses[request_oid.to_s] = value
36
20
  end
37
21
 
38
22
  else
@@ -40,17 +24,10 @@ module SNMP4EM
40
24
  error_oid = @pending_oids.delete_at(response.error_index - 1)
41
25
  @responses[error_oid.to_s] = SNMP::ResponseError.new(response.error_status)
42
26
  end
43
-
44
- if (@pending_oids.empty? || @error_retries.zero?)
45
- until @pending_oids.empty?
46
- error_oid = @pending_oids.shift
47
- @responses[error_oid.to_s] = SNMP::ResponseError.new(:genErr)
48
- end
49
-
50
- @responses.each_pair do |oid, value|
51
- @responses[oid] = value.rubify if (!@return_raw && value.respond_to?(:rubify))
52
- end
53
-
27
+
28
+ if @error_retries < 0
29
+ fail "exhausted all retries"
30
+ elsif @pending_oids.empty?
54
31
  # Send the @responses back to the requester, we're done!
55
32
  succeed @responses
56
33
  else
@@ -62,13 +39,13 @@ module SNMP4EM
62
39
  private
63
40
 
64
41
  def send
65
- # Send the contents of @pending_oids
42
+ Manager.track_request(self)
66
43
 
67
- @snmp_id = generate_snmp_id
44
+ # Send the contents of @pending_oids
68
45
 
69
46
  vb_list = SNMP::VarBindList.new(@pending_oids)
70
47
  request = SNMP::GetRequest.new(@snmp_id, vb_list)
71
- message = SNMP::Message.new(@version, @sender.community_ro, request)
48
+ message = SNMP::Message.new(@sender.version, @sender.community_ro, request)
72
49
 
73
50
  super(message)
74
51
  end
@@ -1,37 +1,23 @@
1
1
  module SNMP4EM
2
2
 
3
- # Returned from SNMP4EM::SNMPv1.get(). This implements EM::Deferrable, so you can hang a callback()
4
- # or errback() to retrieve the results.
3
+ # This implements EM::Deferrable, so you can hang a callback() or errback() to retrieve the results.
5
4
 
6
5
  class SnmpGetBulkRequest < SnmpRequest
7
- attr_reader :snmp_id
6
+ attr_accessor :snmp_id
8
7
 
9
8
  # For an SNMP-GETBULK request, @pending_oids will be a ruby array of SNMP::ObjectNames that need to be fetched. As
10
9
  # responses come back from the agent, this array will be pruned of any error-producing OIDs. Once no errors
11
10
  # are returned, the @responses hash will be populated and returned.
12
11
 
13
12
  def initialize(sender, oids, args = {}) #:nodoc:
14
- @sender = sender
15
-
16
- @timeout_timer = nil
17
- @timeout_retries = @sender.retries
18
- @error_retries = oids.size
19
-
20
- @version = args[:version] || :SNMPv2c
21
- @return_raw = args[:return_raw] || false
22
-
23
13
  @nonrepeaters = args[:nonrepeaters] || 0
24
14
  @maxrepetitions = args[:maxrepetitions] || 10
25
15
 
26
- @responses = Hash.new
27
- @pending_oids = SNMP::VarBindList.new(oids).collect{|r| r.name}
28
-
29
- init_callbacks
30
- send
16
+ super
31
17
  end
32
18
 
33
19
  def handle_response(response) #:nodoc:
34
- if (response.error_status == :noError)
20
+ if response.error_status == :noError
35
21
  # No errors, populate the @responses object so it can be returned
36
22
 
37
23
  @nonrepeaters.times do |i|
@@ -85,9 +71,9 @@ module SNMP4EM
85
71
  private
86
72
 
87
73
  def send
88
- # Send the contents of @pending_oids
74
+ Manager.track_request(self)
89
75
 
90
- @snmp_id = generate_snmp_id
76
+ # Send the contents of @pending_oids
91
77
 
92
78
  vb_list = SNMP::VarBindList.new(@pending_oids)
93
79
  request = SNMP::GetBulkRequest.new(@snmp_id, vb_list)
@@ -95,7 +81,7 @@ module SNMP4EM
95
81
  request.max_repetitions = @maxrepetitions
96
82
  request.non_repeaters = @nonrepeaters
97
83
 
98
- message = SNMP::Message.new(@version, @sender.community_ro, request)
84
+ message = SNMP::Message.new(@sender.version, @sender.community_ro, request)
99
85
 
100
86
  super(message)
101
87
  end