File: threading_agent.py

package info (click to toggle)
python-netsnmpagent 0.6.0-6
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 340 kB
  • sloc: python: 1,200; sh: 172; makefile: 6
file content (231 lines) | stat: -rwxr-xr-x 7,252 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#!/usr/bin/env python3
#
# python-netsnmpagent example agent with threading
#
# Copyright (c) 2013-2016 Pieter Hollants <pieter@hollants.com>
# Licensed under the GNU Lesser Public License (LGPL) version 3
#

#
# simple_agent.py demonstrates registering the various SNMP object types quite
# nicely but uses an inferior control flow logic: the main loop blocks in
# net-snmp's check_and_process() call until some event happens (eg. SNMP
# requests need processing). Only then will data be updated, not inbetween. And
# on the other hand, SNMP requests can not be handled while data is being
# updated, which might take longer periods of time.
#
# This example agent uses a more real life-suitable approach by outsourcing the
# data update process into a separate thread that gets woken up through an
# SIGALRM handler at an configurable interval. This does only ensure periodic
# data updates, it also makes sure that SNMP requests will always be replied to
# in time.
#
# Note that this implementation does not address possible locking issues: if
# a SNMP client's requests are processed while the data update thread is in the
# midst of refreshing the SNMP objects, the client might receive partially
# inconsistent data. 
#
# Use the included script run_threading_agent.sh to test this example.
#
# Alternatively, see the comment block in the head of simple_agent.py for
# adaptable instructions how to run this example against a system-wide snmpd
# instance.
#

import sys, os, signal, time
import optparse, threading, subprocess

# Make sure we use the local copy, not a system-wide one
sys.path.insert(0, os.path.dirname(os.getcwd()))
import netsnmpagent

prgname = sys.argv[0]

# Process command line arguments
parser = optparse.OptionParser()
parser.add_option(
	"-i",
	"--interval",
	dest="interval",
	help="Set interval in seconds between data updates",
	default=30
)
parser.add_option(
	"-m",
	"--mastersocket",
	dest="mastersocket",
	help="Sets the transport specification for the master agent's AgentX socket",
	default="/var/run/agentx/master"
)
parser.add_option(
	"-p",
	"--persistencedir",
	dest="persistencedir",
	help="Sets the path to the persistence directory",
	default="/var/lib/net-snmp"
)
(options, args) = parser.parse_args()

headerlogged = 0
def LogMsg(msg):
	""" Writes a formatted log message with a timestamp to stdout. """

	global headerlogged

	if headerlogged == 0:
		print("{0:<8} {1:<90} {2}".format(
			"Time",
			"MainThread",
			"UpdateSNMPObjsThread"
		))
		print("{0:-^120}".format("-"))
		headerlogged = 1

	threadname = threading.currentThread().name

	funcname = sys._getframe(1).f_code.co_name
	if funcname == "<module>":
		funcname = "Main code path"
	elif funcname == "LogNetSnmpMsg":
		funcname = "net-snmp code"
	else:
		funcname = "{0}()".format(funcname)

	if threadname == "MainThread":
		logmsg = "{0} {1:<112.112}".format(
			time.strftime("%T", time.localtime(time.time())),
			"{0}: {1}".format(funcname, msg)
		)
	else:
		logmsg = "{0} {1:>112.112}".format(
			time.strftime("%T", time.localtime(time.time())),
			"{0}: {1}".format(funcname, msg)
		)
	print(logmsg)

def LogNetSnmpMsg(priority, msg):
	""" Log handler for log messages generated by net-snmp code. """

	LogMsg("[{0}] {1}.".format(priority, msg))

# Create an instance of the netsnmpAgent class
try:
	agent = netsnmpagent.netsnmpAgent(
		AgentName      = "ThreadingAgent",
		MasterSocket   = options.mastersocket,
		PersistenceDir = options.persistencedir,
		MIBFiles       = [ os.path.abspath(os.path.dirname(sys.argv[0])) +
		                   "/THREADING-MIB.txt" ],
		LogHandler     = LogNetSnmpMsg,
	)
except netsnmpagent.netsnmpAgentException as e:
	print("{0}: {1}".format(prgname, e))
	sys.exit(1)

# Register the only SNMP object we server, a DisplayString
threadingString = agent.DisplayString(
	oidstr  = "THREADING-MIB::threadingString",
	initval = "<No data available yet>"
)

def UpdateSNMPObjs():
	""" Function that does the actual data update. """

	global threadingString

	LogMsg("Beginning data update.")
	data = ""

	# Obtain the data by calling an external command. We don't use
	# subprocess.check_output() here for compatibility with Python versions
	# older than 2.7.
	LogMsg("Calling external command \"sleep 5; date\".")
	proc = subprocess.Popen(
		"sleep 5; date", shell=True, env={ "LANG": "C" },
		stdout=subprocess.PIPE, stderr=subprocess.STDOUT
	)
	output = proc.communicate()[0].splitlines()[0]
	rc = proc.poll()
	if rc != 0:
		LogMsg("An error occured executing the command: {0}".format(output))
		return

	msg = "Updating \"threadingString\" object with data \"{0}\"."
	LogMsg(msg.format(output))
	threadingString.update(output)

	LogMsg("Data update done, exiting thread.")

def UpdateSNMPObjsAsync():
	""" Starts UpdateSNMPObjs() in a separate thread. """

	# UpdateSNMPObjs() will be executed in a separate thread so that the main
	# thread can continue looping and processing SNMP requests while the data
	# update is still in progress. However we'll make sure only one update
	# thread is run at any time, even if the data update interval has been set
	# too low.
	if threading.active_count() == 1:
		LogMsg("Creating thread for UpdateSNMPObjs().")
		t = threading.Thread(target=UpdateSNMPObjs, name="UpdateSNMPObjsThread")
		t.daemon = True
		t.start()
	else:
		LogMsg("Data update still active, data update interval too low?")

# Start the agent (eg. connect to the master agent).
try:
	agent.start()
except netsnmpagent.netsnmpAgentException as e:
	LogMsg("{0}: {1}".format(prgname, e))
	sys.exit(1)

# Trigger initial data update.
LogMsg("Doing initial call to UpdateSNMPObjsAsync().")
UpdateSNMPObjsAsync()

# Install a signal handler that terminates our threading agent when CTRL-C is
# pressed or a KILL signal is received
def TermHandler(signum, frame):
	global loop
	loop = False
signal.signal(signal.SIGINT, TermHandler)
signal.signal(signal.SIGTERM, TermHandler)

# Define a signal handler that takes care of updating the data periodically
def AlarmHandler(signum, frame):
	global loop, timer_triggered

	LogMsg("Got triggered by SIGALRM.")

	if loop:
		timer_triggered = True

		UpdateSNMPObjsAsync()

		signal.signal(signal.SIGALRM, AlarmHandler)
		signal.setitimer(signal.ITIMER_REAL, float(options.interval))
msg = "Installing SIGALRM handler triggered every {0} seconds."
msg = msg.format(options.interval)
LogMsg(msg)
signal.signal(signal.SIGALRM, AlarmHandler)
signal.setitimer(signal.ITIMER_REAL, float(options.interval))

# The threading agent's main loop. We loop endlessly until our signal
# handler above changes the "loop" variable.
LogMsg("Now serving SNMP requests, press ^C to terminate.")

loop = True
while loop:
	# Block until something happened (signal arrived, SNMP packets processed)
	timer_triggered = False
	res = agent.check_and_process()
	if res == -1 and not timer_triggered and loop:
		loop = False
		LogMsg("Error {0} in SNMP packet processing!".format(res))
	elif loop and timer_triggered:
		LogMsg("net-snmp's check_and_process() returned due to SIGALRM (res={0}), doing another loop.".format(res))
	elif loop:
		LogMsg("net-snmp's check_and_process() returned (res={0}), doing another loop.".format(res))

LogMsg("Terminating.")
agent.shutdown()