Closed SalemHarrache closed 10 years ago
Hi,
yes, using a separate thread is the way to go. It's what I'm doing in my productive agent. Actually Python makes it quite easy with the Threading module.
I'll update the example agent with this as soon as I get around to it.
I have found a solution which works and is not that complicated.
The idea is to use two threads : one thread is used for the updates and the other for the request processing. To synchronize both threads, we need to use a lock. Ok !
If we want subagents to answer quickly to requests, we need to launch check_and_process
in blocking mode. (and avoid time.sleep(x) in main loop)
lock.acquire()
agent.check_and_request(block=True)
lock.release()
Of course, we should avoid doing this, since the update thread can never get the lock.
However, we can think of doing something like this:
agent.wait_for_request() # block until receiv a new request
lock.acquire()
agent.check_and_process(block=False) # Don't block, just process
lock.release()
wait_for_request
calls select
and allows us to be aware of new requests.
I think such a method should figure in the API.
What do you think?
PS : you can see the code of the function wait_for_request
on this gist
I'm not sure whether you always really need a lock. In my productive agent, what I do is basically this:
def UpdateSNMPObjs():
global aTable, bTable
logger.debug("Beginning data update...")
data = []
# Do whatever time-consuming work is necessary to let code populate data, not the tables themselves
[...]
# At this point data has the data, now we update the tables themselves
logger.debug("Data update now, now clearing and (re)populating SNMP tables...")
aTable.clear()
cnt = 1
for (desc, value) in data["atable"]:
row = aTable.addRow([agent.Unsigned32(initval=cnt)])
row.setRowCell(2, agent.DisplayString(initval=desc))
row.setRowCell(3, agent.Integer32(initval=value))
cnt = cnt + 1
# similar for bTable
logger.debug("Data update done")
def UpdateSNMPObjsAsync():
if threading.active_count() == 1:
t = threading.Thread(target=UpdateSNMPObjs, name="UpdateSNMPObjs")
t.daemon = True
t.start()
else:
logger.warning("Data update still active, consider increasing the data update interval.")
# Create the supported SNMP objects (not shown here)
CreateSNMPObjs()
agent.start()
# Trigger initial data update.
UpdateSNMPObjsAsync()
# Install a signal handler that terminates dfshwagent 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
if loop:
timer_triggered = True
UpdateSNMPObjsAsync()
signal.signal(signal.SIGALRM, AlarmHandler)
signal.setitimer(signal.ITIMER_REAL, float(options.interval))
signal.signal(signal.SIGALRM, AlarmHandler)
signal.setitimer(signal.ITIMER_REAL, float(options.interval))
logger.info("Now serving SNMP requests.")
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
logger.error("Error {0} in SNMP packet processing!".format(res))
logger.info("Exiting.")
So the main thread never writes to aTable, bTable, only UpdateSNMPObjs() does. Also it uses data[] for the time-intensive work and only when that is available, it does a quick clear and repopulates, table-by-table. In this short time window, of course, SNMP request might see empty tables or only partially filled tables.
However, this could be considered valid as well seeing that the tables often are of dynamic nature anyway. In my example, I export eg. information about hard disks. If a hard disk were removed from a server, the table entry would disappear as well. Meaning that my SNMP clients need to be aware of tables that can at any time have disappearing or reappearing entries.
Of course, this might not be a useful use case for you?
In that case, your approach could work, but I'm yet having some pain as so far I just wrap the Net-SNMP API quite transparently. With this change, I'm afraid I could rely too much on Net-SNMP internals, so while basically positive about such splitted calls, I have to look at it in more detail and think it over.
Point is, your concern is absolutely valid, but I wonder how one would approach it when writing an agent in C using the Net-SNMP API. What the "official" approach by the Net-SNMP guys would be.
Hi Pieter, I'm really happy someone is working on this. I've wanted to avoid the process of coding an agent in C for multiple reasons. I have done that previously using Net-SNMP's mib2c program which produces basic C code for a branch of a MIB. Concerning SalemHarrache's comments and your responses I agree that there needs to be a different approach, and what seems to come out of the mib2c code is two routines for each parameter:
What would be most useful for me would be that the loop in your example agent blocked until a request came in, then returned a pointer to the oid or oidstr that was requested. My code would then determine via the handler a. if the current value was valid or stale and update if needed, then b. determine if a set was in bounds and prepare to send back an error if out of bounds and finally c. update the value if a set operation or get the current value if a get operation.
At least in my case the process of updating only those values requested makes sense. This is because there may be hundreds of parameters but very few are requested on a regular basis. Plus many are fixed values that never change after initialization.
Thanks again for all your work on this. Mike
Hi again Pieter, I went back and looked at some code produced by the Net-SNMP mib2c command it very closely matches your structure in Python. I now see that my problem in updating variables is that I do not know how to register a handler for a parameter during initialization. Your example code shows registering the oidstr, an initial value and context, but not a handler routine. I'll do some experimenting but it would be very useful to show an example of registering a handler for a parameter. Thanks, Mike
python-netsnmpagent does not yet support registering custom handlers, only scalar variables and tables for which the "default" net-snmp handlers will be used. Care to submit a patch? :)
I have just pushed a new example agent, threading_agent.py, that implements my threading approach outlined above. While it does not address any locking issues yet, it might help as a basis for future work.
Pieter, I would like to be able to help, and I will do some experimenting when I get a chance. But, I'm afraid that I'm mainly a hardware person and only play at programming, so my code may not be up to the best standards.
Mike
On 07/07/2013 03:15 AM, Pieter Hollants wrote:
python-netsnmpagent does not yet support registering custom handlers, only scalar variables and tables for which the "default" net-snmp handlers will be used. Care to submit a patch? :)
— Reply to this email directly or view it on GitHub https://github.com/pief/python-netsnmpagent/issues/6#issuecomment-20568632.
Closing this now, as I didn't get any recent feedback. If anyone is willing to implement the handler feature, always feel free to submit a patch.
Hi,
First of all, I wanted to thank you. It's really hard to find AgentX implementations written in Python that actually work.
But I have to admit I'm having trouble with the update process.
In your example, you launch the updates when
agent.check_and_process()
unlocks (when receiving a request).However in my case, I need the data to be ready sooner. I can't afford to lose time to get the new values.
I am thinking about using another thread, which will deal with the updates, but it implies to use locks almost everywhere, and I want to avoid that as much as possible.
Do you know if it is possible to launch the updates BEFORE (and independently from) a request reception?
Thank you in advance for your answer.
Best regards, Salem