AgentXcpp  Version:0.3
Internals Documentation
 All Classes Namespaces Files Functions Variables Enumerations Enumerator Friends Pages
How to implement a writeable variable
Note
This is a follow-up tutorial to How to write a Subagent.

In the previous tutorial we wrote a simple read-only counter variable. Here we will continue this work and add write support to the simpleCounter variable. Writing to the variable will set it to a new value, and it will continue to count from there.

The updated MIB looks like that:

SIMPLE-MIB DEFINITIONS ::= BEGIN

IMPORTS
    OBJECT-TYPE FROM SNMPv2-SMI
    enterprises FROM SNMPv2-SMI;

simpleCounter OBJECT-TYPE
    SYNTAX INTEGER
    ACCESS read-write
    STATUS current
    DESCRIPTION "A simple counter which is incremented each time
                 it is queried. Starts with 0."
    ::={ enterprises 42 1 }

END

This is the same as before, but with "ACCESS read-write".

The anatomy of a Set request

A single SNMP Set request may set multiple variables at once (possibly distributed over multiple subagents), and SNMP guarantees an atomic operation in that case. This means that, should any variable fail to perform the Set operation, no other variable is allowed to perform the Set operation either. Therefore, the SET operation is carried out in multiple distinct steps:

  1. TestSet checks whether the variable could be set at all. This step is performed for all variables (in all subagents) affected by the single SNMP Set request. If any variable is not able to accept its new value, the Set operation is aborted, and the next step "CommitSet" is skipped (and no variable gets updated). During this step subagents shall allocate the resources they need to carry out the Set operation later, but they shall not update their value yet.
  2. CommitSet actually sets the new value. This step may fail for a variable, even if the TestSet step announced that the CommitSet would work. If that happens, all other variables which already performed the CommitSet must undo their action (which is done in the UndoSet step). It is strongly recommended to implement variables in such a way that CommitSet does not fail, when possible.
  3. The third step is one of the following:
    • CleanupSet is usually the last step, performed after CommitSet was successful for all variables. In the CleanupSet step, the subagent shall release any resources which where allocated in the TestSet step.
    • UndoSet may be performed if CommitSet failed for any variable. UndoSet is then called for all variables which already performed CommitSet and therefore must undo their action. For variables which didn't perform CommitSet yet, CleanupSet is called instead. UndoSet shall undo the actions performed by CommitSet and release all resources which were allocated in the TestSet step.

To implement these actions, the Variable classes provides the methods perform_testset(), perform_commitset(), perform_cleanupset() and perform_undoset(), which can be overridden by a concrete implementation.

Extending the SimpleCounter class

To implement write support for the SimpleCounter class, we implement all four methods. The perform_testset() implementation allocates an quint32 value, which is deallocated by perform_cleanupset() respectively perform_undoset(). This is not very realistic, but it demonstrates the usage of the individual steps. The perform_commitset() method stores the old value (in case an undo needs to be carried out by perform_undoset()) and sets the new value.

TestSet

The perform_testset() method must be overridden by any variable which should be writeable. If it is not overridden, agentXcpp assumes that the variable is read-only (which we exploited in the first, read-only version). This also means that an e.g. IntegerVariable object is read-only by default and needs to be subclassed to add write support. Hence, we implement it for our SimpleCounter class by adding the following code to it:

private:
quint32* old_value;
public:
virtual testset_result_t perform_testset(qint32 new_value)
{
try
{
// Allocate space to store the old value
old_value = new quint32;
}
catch(std::bad_alloc)
{
// Resource allocation failed.
return resourceUnavailable;
}
// setting the variable will succeed
return noError;
}

The perform_testset() method receives the value and can check whether setting the variable to this value would work. Here, we don't check the value, but we allocate an quint32 to store the old value, which is needed for perform_undoset(). If allocation fails, we return the error resourceUnavailable.

CommitSet

The perform_commitset() method must also be implemented for writeable variables. In the SimpleCounter class, this method stores the old value and sets the internal value:

virtual bool perform_commitset(qint32 new_value)
{
// Remember old value for rollback
(*old_value) = v;
// Set new value
v = new_value;
// Operation succeeded
return true;
}

CleanupSet

The perform_cleanupset() method releases old_value again:

virtual void perform_cleanupset(qint32 new_value)
{
// Release old value
delete old_value;
}

If old_value were an quint32 (instead of a pointer to quint32), no allocation and deallocation would have been necessary. The perform_cleanupset() method would not be implemented then. In fact, this method is the only one which is optional for writeable variables, while the other methods are mandatory.

UndoSet

The perform_undoset() method must be implemented by each writeable variable and shall perform a rollback. It is technically possible to omit this method; the compiler has no way to detect a missing implementation. However, it is an error to omit it, and an error will be reported to the master agent if it is missing. For simpleCounter implementation is easy:

virtual bool perform_undoset(qint32 new_value)
{
// Restore old value
v = *old_value;
// Release old value
delete old_value;
// Rollback succeeded
return true;
}

It's important to note that the method releases old_value.

Testing the extended subagent

Again, we compile and run the subagent. This is exactly the same as in the first tutorial (it is assumed that your system is already setup accordingly):

g++ simpleagent.cpp -o simpleagent -lagentxcpp `pkg-config --cflags --libs QtNetwork QtCore`

Next, we start the subagent again:

./simpleagent

Now, we can query the variable:

snmpget -v1 -c rw localhost SIMPLE-MIB::simpleCounter.0

Each time the variable is queried, the counter increments. Now, let's set the counter to, say, 42:

snmpset -v1 -c rw localhost SIMPLE-MIB::simpleCounter.0 i 42

Now, we continue to query it again several times and the counter should continue at 43, further incrementing.

Et voilá: a read-write variable. Now go on and learn how to send notifications!