telemetry-snmp 0.2.0 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b1cf78bbce21aa906630c1e4430ed67805d3fa1332664c0048976ea12c92746
4
- data.tar.gz: 0365001cb5dd2dd1767c76234e485b18e0f4c5727743489459b69e1832a478bc
3
+ metadata.gz: ed97642826f1f7eae99d08d99d6e98a587a1f2427b7fb35c1fe3bf58a1056118
4
+ data.tar.gz: 5cbf336cb9c75b0e89b578f85a89c2874a6ce4cbaa6662d48edfeb34058ee4a0
5
5
  SHA512:
6
- metadata.gz: 7dbd63ecd438e885bc22080aed5cf5044a7344671600134d93d77d07c89d6a2065b3d1558cfd1b30e84b44e8a718d581ad08d6436ed4af852c16ec43f14715d3
7
- data.tar.gz: f3377fc61d505f6b165f8d85709d2ea7471933f4d3c83ecb2f50d4360c2e992dbc547114d722f344b65a72b1f94db1f633ba445df3bd82780bd9563b6d11b0cd
6
+ metadata.gz: e1692d91324dfa8eb117c81c78d908dc8018e4ccd7e5682feb6bc45a0dd43c884974db9900fddf54a9e9ae5100d11e41f49c20a0ff70b76bb435fd1447708348
7
+ data.tar.gz: 3e9d11ad581fb35eba0e068d61f4d18dfbc8ddcce599392a903ac645f35647fa300148ebaaaa7ac0ff23a22c7efba176630c11db654329587648df132f1e29d1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Telemetry::SNMP
2
2
 
3
+ ## [0.3.0]
4
+ * Updating locking mechanism
5
+ * Allow for parallel collections
6
+ * Adding measurement prefixes
7
+
8
+ ## [0.2.2]
9
+ * Updating logic to use the IP address of the device first and then hostname if ip is null
10
+
11
+ ## [0.2.1]
12
+ Updating logic so it updates the last_polled column at the beginning of the loop
13
+
3
14
  ## [0.2.0]
4
15
  * Fixing ENV variables to not use .
5
16
  * Adding new device lock table for future reference
data/README.md CHANGED
@@ -11,6 +11,16 @@ bundle exec exe/snmp_collector
11
11
  ```
12
12
 
13
13
 
14
+ 1.3.6.1.2.1.47
15
+ 1.3.6.1.2.1.2.2.1.8 = Interface Up/Down
16
+ 1.3.6.1.2.1.2.2.1.10.1 = Interface in counter
17
+ 1.3.6.1.2.1.2.2.1.14 = Interface In Errors
18
+ 1.3.6.1.2.1.2.2.1.16 = Interface out counters
19
+ 1.3.6.1.2.1.2.2.1.20 = Inteface Out Errors
20
+ 1.3.6.1.2.1.31.1.1.1
21
+
22
+ https://knowledgebase.paloaltonetworks.com/KCSArticleDetail?id=kA10g000000ClaSCAS
23
+
14
24
  ### Telemetry::Snmp::API
15
25
  The API allows for you to remotely CRUD devices, oids, users, device credentials, etc
16
26
  The Routes are available via a [postman json](https://github.com/Optum/telemetry-snmp/blob/main/telemetry-snmp.json)
@@ -62,6 +62,10 @@ module Telemetry
62
62
  Telemetry::Snmp::Collector.loop_devices
63
63
  end
64
64
 
65
+ get '/poll_next_device' do
66
+ Telemetry::Snmp::Collector.poll_next_device
67
+ end
68
+
65
69
  namespace('/users') { register Telemetry::Snmp::Controller::Users }
66
70
  namespace('/devices/creds') { register Telemetry::Snmp::Controller::DeviceCreds }
67
71
  namespace('/devices') { register Telemetry::Snmp::Controller::Devices }
@@ -21,10 +21,10 @@ module Telemetry
21
21
  def connection(host)
22
22
  return @connections[host.to_sym] if @connections.key? host.to_sym
23
23
 
24
- dataset = Telemetry::Snmp::Data::Model::Device[hostname: host]
24
+ dataset = Telemetry::Snmp::Data::Model::Device.where(hostname: host).or(ip_address: host).first
25
25
 
26
26
  @connections[host.to_sym] = NETSNMP::Client.new(
27
- host: dataset.values[:ip_address],
27
+ host: dataset.values[:hostname],
28
28
  port: dataset.values[:port],
29
29
  username: dataset.device_cred.values[:username],
30
30
  auth_password: dataset.device_cred.values[:auth_password],
@@ -59,7 +59,7 @@ module Telemetry
59
59
  end
60
60
 
61
61
  def grab_oid_metrics(hostname)
62
- device = Telemetry::Snmp::Data::Model::Device[hostname: hostname]
62
+ device = Telemetry::Snmp::Data::Model::Device.where(hostname: hostname).or(ip_address: hostname).first
63
63
  @lines = []
64
64
  Telemetry::Snmp::Data::Model::OIDWalks.where(:active).each do |row|
65
65
  index = {}
@@ -94,6 +94,8 @@ module Telemetry
94
94
 
95
95
  @lines.push line
96
96
  end
97
+ rescue StandardError => e
98
+ Telemetry::Logger.error "#{e.class}: #{hostname}, #{e.message}"
97
99
  end
98
100
 
99
101
  @lines
@@ -2,16 +2,30 @@ module Telemetry
2
2
  module Snmp
3
3
  module Collector
4
4
  class << self
5
- def worker_name
6
- "#{::Socket.gethostname.tr('.', '_')}.#{::Process.pid}.#{Thread.current.object_id}"
5
+ def loop_devices
6
+ count = 0
7
+ Telemetry::Snmp::Data::Model::Device.where(:active).order(:last_polled).each do |row|
8
+ break if count >= 10
9
+ next if row.values[:last_polled].to_i + row.values[:frequency] > Time.now.to_i
10
+ next if device_locked?(row.values[:id])
11
+
12
+ Telemetry::Logger.info "Grabbing metrics for #{row.values[:hostname]}"
13
+ device = Telemetry::Snmp::DeviceCollector.new(row.values[:hostname])
14
+ device.async.collect
15
+ count += 1
16
+ end
7
17
  end
8
18
 
9
- def loop_devices
10
- Telemetry::Snmp::Data::Model::Device.where(:active).each do |row|
19
+ def poll_next_device
20
+ Telemetry::Snmp::Data::Model::Device.where(:active).order(:last_polled).each do |row|
11
21
  next if row.values[:last_polled].to_i + row.values[:frequency] > Time.now.to_i
12
22
  next if device_locked?(row.values[:id])
13
23
 
24
+ Telemetry::Logger.info "Grabbing metrics for #{row.values[:hostname]}"
14
25
  collect(row.values[:id])
26
+ break
27
+ rescue StandardError => e
28
+ Telemetry::Logger.exception(e, level: 'error')
15
29
  end
16
30
  end
17
31
 
@@ -19,81 +33,17 @@ module Telemetry
19
33
  Telemetry::Snmp::Data::Model::DeviceLock.each do |row|
20
34
  next if row.values[:expires] < Sequel::CURRENT_TIMESTAMP
21
35
 
36
+ Telemetry::Logger.warn "removing lock for #{row.values[:hostname]}"
22
37
  row.delete
23
38
  end
24
39
  end
25
40
 
26
41
  def device_locked?(device_id)
27
- !Telemetry::Snmp::Data::Model::DeviceLock[device_id: device_id].nil?
28
- end
29
-
30
- def lock_device(device_id)
31
- return false unless Telemetry::Snmp::Data::Model::DeviceLock[device_id: device_id].nil?
32
-
33
- Telemetry::Snmp::Data::Model::DeviceLock.insert(
34
- worker_name: worker_name,
35
- device_id: device_id,
36
- created: Sequel::CURRENT_TIMESTAMP,
37
- expires: Sequel::CURRENT_TIMESTAMP
38
- )
39
- true
40
- end
41
-
42
- # noinspection RubyArgCount
43
- def unlock_device(device_id)
44
- device = Telemetry::Snmp::Data::Model::DeviceLock[device_id: device_id]
45
- return true if device.nil?
46
-
47
- device.delete
42
+ Telemetry::Snmp::Data::Model::DeviceLock.where(device_id: device_id).count.positive?
48
43
  end
49
44
 
50
- def collect(device_id)
51
- lock_device(device_id)
52
- row = Telemetry::Snmp::Data::Model::Device[device_id]
53
- lines = []
54
- fields = {}
55
- tags = {
56
- hostname: row.values[:hostname],
57
- ip_address: row.values[:ip_address],
58
- env: row.values[:environment],
59
- dc: row.values[:datacenter],
60
- zone: row.values[:zone],
61
- influxdb_node_group: 'snmp',
62
- influxdb_database: 'snmp'
63
- }
64
-
65
- Telemetry::Snmp::Data::Model::OID.each do |oid_row|
66
- break if @quit
67
-
68
- oid_value = Telemetry::Snmp::Client.oid_value(row[:hostname], oid_row.values[:oid])
69
- next if oid_value.nil?
70
- next unless oid_value.is_a?(Integer) || oid_value.is_a?(Float)
71
-
72
- fields[oid_row.values[:name]] =
73
- "#{Telemetry::Snmp::Client.oid_value(row[:hostname], oid_row.values[:oid])}i"
74
- rescue StandardError => e
75
- Telemetry::Logger.error "#{e.class}: #{e.message}"
76
- end
77
-
78
- lines.push Telemetry::Metrics::Parser.to_line_protocol(
79
- measurement: 'palo_alto',
80
- fields: fields,
81
- tags: tags,
82
- timestamp: (DateTime.now.strftime('%Q').to_i * 1000 * 1000)
83
- )
84
-
85
- walker = Telemetry::Snmp::Client.grab_oid_metrics(row.values[:hostname])
86
- Telemetry::Logger.info "Pushing #{walker.count} lines for #{row.values[:hostname]}" unless walker.empty?
87
- Telemetry::Snmp::Publisher.push_lines(walker) unless walker.empty?
88
-
89
- row.update(last_polled: Sequel::CURRENT_TIMESTAMP)
90
- row.save
91
-
92
- Telemetry::Snmp::Publisher.push_lines(lines) unless lines.empty?
93
- unlock_device(device_id)
94
- rescue StandardError => e
95
- Telemetry::Logger.exception(e, level: 'error')
96
- unlock_device(device_id)
45
+ def device_unlocked?(device_id)
46
+ !device_locked?(device_id)
97
47
  end
98
48
  end
99
49
  end
@@ -0,0 +1,8 @@
1
+ Sequel.migration do
2
+ change do
3
+ alter_table(:oids) do
4
+ add_column :active, Integer, limit: 1, null: false, default: 1, index: true
5
+ add_column :measurement_name, String, null: false, default: 'snmp'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ Sequel.migration do
2
+ change do
3
+ alter_table(:oids) do
4
+ add_column :brand, String
5
+ add_column :type, String
6
+ add_column :role, String
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,186 @@
1
+ module Telemetry
2
+ module Snmp
3
+ class DeviceCollector
4
+ include Concurrent::Async
5
+
6
+ def initialize(host)
7
+ @device = Telemetry::Snmp::Data::Model::Device.where(hostname: host).or(ip_address: host).first
8
+ return if @device.nil?
9
+
10
+ @id = @device.values[:id]
11
+ @hostname = @device.values[:hostname]
12
+ end
13
+
14
+ def connection
15
+ @connection ||= NETSNMP::Client.new(
16
+ host: @device.values[:hostname],
17
+ port: @device.values[:port],
18
+ username: @device.device_cred.values[:username],
19
+ auth_password: @device.device_cred.values[:auth_password],
20
+ auth_protocol: @device.device_cred.values[:auth_protocol].to_sym,
21
+ priv_password: @device.device_cred.values[:priv_password],
22
+ priv_protocol: @device.device_cred.values[:priv_protocol].to_sym,
23
+ security_level: @device.device_cred.values[:security_level].to_sym
24
+ )
25
+ end
26
+
27
+ def collect
28
+ return false unless lock_device
29
+
30
+ @collection_start = Time.now
31
+
32
+ @device.update(last_polled: Sequel::CURRENT_TIMESTAMP)
33
+ @device.save
34
+ @lines = []
35
+ @fields = {}
36
+
37
+ Telemetry::Snmp::Data::Model::OID.where(:active).each do |oid_row|
38
+ oid_value = oid_value(oid_row.values[:oid])
39
+ oid_value = oid_value.to_i if oid_value.is_a?(NETSNMP::Timetick)
40
+
41
+ if oid_value.nil?
42
+ Telemetry::Logger.warn "#{@hostname} nil result for #{oid_row.values[:oid]}"
43
+ next
44
+ end
45
+
46
+ unless oid_value.is_a?(Integer) || oid_value.is_a?(Float)
47
+ Telemetry::Logger.error "#{@hostname} nil result for #{oid_row.values[:oid]} class: #{oid_value.class}, #{oid_value}" # rubocop:disable Layout/LineLength
48
+ next
49
+ end
50
+
51
+ @fields[oid_row.values[:name]] = "#{oid_value}i"
52
+ rescue StandardError => e
53
+ Telemetry::Logger.error "#{e.class}: #{e.message}"
54
+ end
55
+
56
+ @lines.push Telemetry::Metrics::Parser.to_line_protocol(
57
+ measurement: 'palo_alto',
58
+ fields: @fields,
59
+ tags: tags,
60
+ timestamp: (DateTime.now.strftime('%Q').to_i * 1000 * 1000)
61
+ )
62
+
63
+ walker = grab_oid_metrics
64
+ unless walker.empty?
65
+ Telemetry::Logger.info "Pushing #{walker.count} lines for #{@hostname} in #{((Time.now - @collection_start) * 1000).round}ms" # rubocop:disable Layout/LineLength
66
+ end
67
+ Telemetry::Snmp::Publisher.push_lines(walker) unless walker.empty?
68
+ Telemetry::Snmp::Publisher.push_lines(@lines) unless @lines.empty?
69
+ unlock_device
70
+ rescue StandardError => e
71
+ Telemetry::Logger.exception(e, level: 'error')
72
+ unlock_device
73
+ end
74
+
75
+ def oid_value(oid)
76
+ connection.get(oid: oid)
77
+ rescue StandardError
78
+ nil
79
+ end
80
+
81
+ def oid_walk(oid)
82
+ results = []
83
+ connection.walk(oid: oid).each do |oid_code, value|
84
+ hash = { oid_code: oid_code, value: value }
85
+ begin
86
+ ident = NETSNMP::MIB.identifier(oid_code)
87
+
88
+ hash[:identifier] = ident.first
89
+ rescue StandardError
90
+ # literally do nothing
91
+ end
92
+ results.push hash
93
+ end
94
+
95
+ results
96
+ end
97
+
98
+ def grab_oid_metrics
99
+ @lines = []
100
+ Telemetry::Snmp::Data::Model::OIDWalks.where(:active).each do |row|
101
+ index = {}
102
+ oid_walk(row.values[:oid_index]).each do |hash|
103
+ index[hash[:oid_code].delete_prefix("#{row.values[:oid_index]}.")] = hash[:value].gsub(%r{\\/}, '.')
104
+ end
105
+
106
+ timestamp = DateTime.now.strftime('%Q').to_i * 1000 * 1000
107
+ oid_walk(row.values[:oid_walk]).each do |walk|
108
+ key = walk[:oid_code].split('.').last
109
+ next if walk[:value].is_a? String
110
+ next if walk[:value].nil?
111
+
112
+ fields = {}
113
+ fields[walk[:identifier]] = "#{walk[:value]}i"
114
+ tags = {
115
+ hostname: @hostname,
116
+ interface: index[key],
117
+ ip_address: @device.values[:ip_address],
118
+ zone: @device.values[:zone],
119
+ env: @device.values[:environment],
120
+ dc: @device.values[:datacenter],
121
+ influxdb_node_group: 'snmp',
122
+ influxdb_database: 'snmp'
123
+ }
124
+
125
+ line = Telemetry::Metrics::Parser.to_line_protocol(
126
+ measurement: "snmp_#{row.values[:measurement_name]}",
127
+ fields: fields,
128
+ tags: tags,
129
+ timestamp: timestamp
130
+ )
131
+
132
+ @lines.push line
133
+ end
134
+ rescue StandardError => e
135
+ Telemetry::Logger.error "#{e.class}: #{@hostname}, #{e.message}"
136
+ end
137
+
138
+ @lines
139
+ end
140
+
141
+ def device_locked?
142
+ Telemetry::Snmp::Data::Model::DeviceLock.where(device_id: @id).count.positive?
143
+ end
144
+
145
+ def device_unlocked?
146
+ Telemetry::Snmp::Data::Model::DeviceLock.where(device_id: @id).count.zero?
147
+ end
148
+
149
+ def lock_device
150
+ Telemetry::Snmp::Data::Model::DeviceLock.insert(
151
+ worker_name: worker_name,
152
+ device_id: @id,
153
+ created: Sequel::CURRENT_TIMESTAMP,
154
+ expires: Sequel::CURRENT_TIMESTAMP
155
+ )
156
+ true
157
+ end
158
+
159
+ def tags
160
+ tags = {
161
+ hostname: @device.values[:hostname],
162
+ ip_address: @device.values[:ip_address],
163
+ env: @device.values[:environment],
164
+ dc: @device.values[:datacenter],
165
+ zone: @device.values[:zone],
166
+ influxdb_node_group: 'snmp',
167
+ influxdb_database: 'snmp'
168
+ }
169
+ tags.delete_if { |_k, v| v.nil? }
170
+
171
+ tags
172
+ end
173
+
174
+ def unlock_device
175
+ device = Telemetry::Snmp::Data::Model::DeviceLock[device_id: @id]
176
+ return true if device.nil?
177
+
178
+ device.delete
179
+ end
180
+
181
+ def worker_name
182
+ "#{::Socket.gethostname.tr('.', '_')}.#{::Process.pid}.#{Thread.current.object_id}"
183
+ end
184
+ end
185
+ end
186
+ end
@@ -118,7 +118,7 @@ module Telemetry
118
118
  routing_key: 'snmp',
119
119
  persistent: true,
120
120
  mandatory: false,
121
- timstamp: Time.now.to_i,
121
+ timestamp: Time.now.to_i,
122
122
  type: 'metric',
123
123
  content_type: 'application/json',
124
124
  content_encoding: 'identity'
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Telemetry
4
4
  module Snmp
5
- VERSION = '0.2.0'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
@@ -7,6 +7,7 @@ require 'telemetry/snmp/data'
7
7
  require 'telemetry/snmp/client'
8
8
  require 'telemetry/snmp/publisher'
9
9
  require 'telemetry/snmp/collector'
10
+ require 'telemetry/snmp/device_collector'
10
11
 
11
12
  module Telemetry
12
13
  module Snmp
@@ -18,6 +19,30 @@ module Telemetry
18
19
  Telemetry::Snmp::Client.load_mibs
19
20
  Telemetry::Snmp::Publisher.start!
20
21
  Telemetry::Logger.info 'Telemetry::Snmp bootstrapped!'
22
+ start_expire_devices
23
+ start_collection
24
+ end
25
+
26
+ def start_expire_devices
27
+ @expire_devices_task = Concurrent::TimerTask.new(execution_interval: 300, timeout_interval: 10) do
28
+ Telemetry::Snmp::Collector.unlock_expired_devices
29
+ end
30
+ @expire_devices_task.execute
31
+ end
32
+
33
+ def stop_expire_devices
34
+ @expire_devices_task.stop
35
+ end
36
+
37
+ def start_collection
38
+ @collection_task = Concurrent::TimerTask.new(execution_interval: 10, timeout_interval: 300) do
39
+ Telemetry::Snmp::Collector.loop_devices
40
+ end
41
+ @collection_task.execute
42
+ end
43
+
44
+ def stop_collection
45
+ @collection_task.stop
21
46
  end
22
47
  end
23
48
  end
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.add_dependency 'concurrent-ruby', '>= 1.1.7'
28
28
  spec.add_dependency 'concurrent-ruby-ext', '>= 1.1.7'
29
29
  spec.add_dependency 'connection_pool', '>= 2.2.3'
30
+ spec.add_dependency 'elastic-apm'
30
31
  spec.add_dependency 'faraday'
31
32
  spec.add_dependency 'faraday_middleware'
32
33
  spec.add_dependency 'faraday-request-timer'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: telemetry-snmp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-23 00:00:00.000000000 Z
11
+ date: 2021-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: 2.2.3
55
+ - !ruby/object:Gem::Dependency
56
+ name: elastic-apm
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: faraday
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -339,6 +353,8 @@ files:
339
353
  - lib/telemetry/snmp/data/migrations/009_create_tag_name_column.rb
340
354
  - lib/telemetry/snmp/data/migrations/010_create_user_audit_table.rb
341
355
  - lib/telemetry/snmp/data/migrations/011_device_locks.rb
356
+ - lib/telemetry/snmp/data/migrations/012_add_measurement_prefix.rb
357
+ - lib/telemetry/snmp/data/migrations/013_add_fields_to_devices.rb
342
358
  - lib/telemetry/snmp/data/models/device.rb
343
359
  - lib/telemetry/snmp/data/models/device_cred.rb
344
360
  - lib/telemetry/snmp/data/models/device_lock.rb
@@ -348,6 +364,7 @@ files:
348
364
  - lib/telemetry/snmp/data/models/oid_walk.rb
349
365
  - lib/telemetry/snmp/data/models/user.rb
350
366
  - lib/telemetry/snmp/data/models/user_audit_log.rb
367
+ - lib/telemetry/snmp/device_collector.rb
351
368
  - lib/telemetry/snmp/mibs/AGENTX-MIB.txt
352
369
  - lib/telemetry/snmp/mibs/AIRPORT-BASESTATION-3-MIB.txt
353
370
  - lib/telemetry/snmp/mibs/BRIDGE-MIB.txt