Tutorial for Visual xAgentBuilder for C++
Lesson 10: Implementing Table in the Agent
In this lesson MIB table implementation in the agent will be presented.
Here is the general structure of the subtree that represents MIB table:
fooTable (<some number>)
|
+ fooEntry (1)
|
+ fooColumn1 (1)
+ fooColumn2 (2)
. . .
+ fooColumnN (N)
As you can see, Entry is always defined as the child of the Table node with subID equal to 1. Columns in the table are children of the Entry node and normally (but not required) have subIDs from 1 to the number of columns.
Note that there is no operation in SNMP that are performed on the table or row objects (as they are in RDBMS and SQL). All operations in SNMP work on specific instance of the leaf object (either scalar or columnar). In many SNMP and MIB books tables and rows are usually qualified with the term conceptual. This is just to emphasize the fact of SNMP not having operations that can be executed on the whole table/row.
Here is the structure of the subtree that represents the table that we are going to implement in this lesson, i.e. vehiclesTable:
. . .
+- [ 3] vehiclesTable
|
+- [ 1] vehiclesEntry
|
+- [ 1] –NA INTEGER vehicleIndex
+- [ 2] -RC- DisplayString vehicleLicencePlate
+- [ 3] -RW- DisplayString vehicleModel
+- [ 4] -RC- RowStatus vehicleStatus
From the definition of vehiclesEntry
vehiclesEntry OBJECT-TYPE
SYNTAX VehiclesEntry
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION “Row in the vehiclesTable.”
INDEX { vehicleIndex }
::= { vehiclesTable 1 }
we can see that the table is indexed by vehicleIndex column. vehicleIndex column has MAX_ACCESS specified as not-accessible (as is requirement of SMIv2), and it has INTEGER syntax.
Generally speaking, MIB table is always indexed by OID (even if it is OID of length 1 as is the case when table is indexed by integer). Number of subIDs in the OID varies depending on the objects chosen as indices. Here is the example
(instance of the ipAdEntIfIndex column in the ipAddrTable):
1.3.6.1.2.1.4.20.1.2.127.0.0.1 = ipAdEntIfIndex.127.0.0.1
(instance of the ifType column in the ifTable):
1.3.6.1.2.1.2.2.1.3.4 = ifType.4
In the example OID = 127.0.0.1 and OID = 4 are indices.
The BMibTableImpl class handles the subtrees that are MIB tables. Our table is implemented in VehicleTable class derived from BmibTableImpl.
For this example we’ve chosen to use object of BMapOid2Ptr class to instrument table storage:
BMapOid2Ptr m_table;
To instrument this table, we could have chosen an array, or collection that maps integer to the row, or whatever appropriate instrumentation might be. BMapOid2Ptr maps BobjectIdentifier object (index) to pointer (to the table row).
Struct Entry nested in VehicleTable class
represents one row in the table. Here is the declaration:
struct Entry
{
Entry();
tASN_INT m_nVehicleIndex;
BString m_strVehicleLicencePlate;
BString m_strVehicleModel;
ERowStatus m_eVehicleStatus;
};
VehicleTable class defines methods for inserting, retrieving and deleting rows from the table. All this methods simply call corresponding method in BMapOid2Ptr. Please note that RemoveRow method returns pointer to the target row. It is user responsibility to free memory allocated fro that row. While one can argue that this can be done in the method’s body, the reason for that will be clear in the next lesson: Dynamic Row Creation.
Here is the interesting part:
bool NextInstanceInColumn(/*IN*/ tUint32 transactionId,
/*IN*/ tUint32 columnId,
/*IN*/ const BObjectIdentifier& oidFrom,
/*OUT*/ BObjectIdentifier& oidIx);
This method is abstract method declared in BMibTableImpl class, and it means that all classes that derive from BMibTableImpl must implement it. Command responder calls this method during processing of getNext request. The reason for this method is the fact that table can have zero, one or multiple rows. And command responder does not have this information in advance. This is opposed to BMibScalarGroupImpl where all members are scalars, and scalar object has exactly one instance.
transactionId is unique id associated with the request. This number can be used to cache results of row retrieval in case table storage is not implemented locally (for example we need to access DB for data, here is the good place to cache retrieved row).
Remember that the agent is multithreaded, and that two threads could be handling the request that involves this table. Life is not that complicated though. Each subtree handler is synchronized during execution of the user code. You can see intermixed calls for different transactionIds only when complex requests (each request involves vblist with objects from multiple subtrees) arrive in succession.
columnId is, as name says, the subID of the column in question, while oidFrom is an index part in the requested oid. Next OID should be assigned to oidIx and true retuned. If there is no next (oidFrom is equal or lexucograpbically greater than the index of the last row in the table) false should be returned.
In most cases, columnIn won’t be used at all. It is handy if you are going to implement sparse table. In our case we simply call BMapOid2Ptr’s Next.
Now, the request handling methods: OnGet and OnSet. Regarding mapping of table columns to OnGet and OnSet methods it is the same as in BMibScalarGroupImpl . All we have to do is to declare request handler map (macro DECLARE_SNMP_RQHNDLR_MAP ) and to implement map using BEGIN_SNMP_RQHNDLR_MAP and END_SNMP_RQHNDLR_MAP. The difference is that here we have to use COLUMN_xxx macros.
The OnGet and OnSet methods have one parameter more than the corresponding methods for scalars:
const BObjectIdentifier& oidIx
This parameter is index of the row for the particular column. Here is the implementation of the OnGet method for the second column in the table:
SNMP_ERRSTAT GarageObjects::OnGet_LicensePlate(
const BObjectIdentifier& oiIndex,
tUint32 transactionId,
string& strVal)
{
VehicleEntry* pRow = GetRow(oiIndex);
if (pRow)
{
strVal = pRow->m_strLicensePlate;
return eSNMP_ERRSTAT_NO_ERROR;
}
else
return eSNMP_ERRSTAT_NO_SUCH_NAME;
}
OnGet_LicensePlate is straightforward. Check the local storage for the row with index oiIndex.
If found assign appropriate value to the by reference passed strVal. If not found return error.
Now let’s look at the OnSet method:
SNMP_ERRSTAT GarageObjects::OnSet_LicensePlate(
const BObjectIdentifier& oiIndex,
ESnmpSetOpCode eOpCode,
tUINT transactionId,
const BString& strVal,
void** ppContextInfo)
{
switch (eOpCode)
{
case eSNMP_SET_TEST:
{
Entry* pRow = GetRow(oidIx);
if (pRow)
{
//store variable in the context varaible
*ppContextInfo = new BString (pRow->m_strLicensePlate);
return eSNMP_ERRSTAT_NO_ERROR;
}
}
case eSNMP_SET_COMMIT:
{
Entry* pRow = GetRow(oidIx);
if (pRow)
{
pRow->m_strLicensePlate = strVal;
return eSNMP_ERRSTAT_NO_ERROR;
}
return eSNMP_ERRSTAT_COMMIT_FAIL;
}
case eSNMP_SET_UNDO:
{
Entry* pRow = GetRow(oidIx);
if (pRow)
{
//restore variable from the the context varaible
pRow->m_strLicensePlate = *((BString *)*ppContextInfo);
return eSNMP_ERRSTAT_NO_ERROR;
}
return eSNMP_ERRSTAT_UNDO_FAIL;
}
case eSNMP_SET_CLEANUP:
{
if (*ppContextInfo)
delete ((BString *)*ppContextInfo);
return eSNMP_ERRSTAT_NO_ERROR;
}
}
return eSNMP_ERRSTAT_GENERAL_ERR;
}
Again, the only difference between scalar Set and columnar Set is in the index parameter (const BObjectIdentifier& oiIndex). The explanation of this method is basically the same as the one given in the Lesson 9: Read-write Scalar Object in the Agent.
It should be noted that handling of this table is not correct. This table has RowStatus column that should be used for dynamic row creation. In this lesson RowStatus is handled as any other column, i.e. user can not create/remove rows in this table remotely.
We’ll fix in the next lesson Lesson 11: Dynamic Row Creation.