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()
|