Cpp Lesson 11

Tutorial for Visual xAgentBuilder for C++

Lesson 11: Dynamic Row Creation

In this lesson handling of the dynamic row creation (row creation initialized by manager) will be presented. This lesson is extension to the previous lesson and represents correct implementation of the ND-GARAGE-MIB. Note that the agent built in the previous lesson does not implement dynamic row creation.

While, user can decide to make his/her own implementation for the dynamic row creation, there is a ready solution for handling tables with RowStatus (v2 tables) or EntryStatus (v1 tables). This helper is BMibTableImplEx class.

The first thing we have to do is to change base class for the VehicleTable class. No change is necessary in the constructor, since BMibTableImplEx class has the same parameters for the constructor as BmibTableImpl class.

The biggest change is implementation of the OnSet_VehicleStatus method. The first change is prototype. This method now has const ERowStatus& eVal as the forth parameter instead of const tASN_INT& eVal.
Accordingly, instead of

COLUMN_INT(VehiclesTable, 4, “vehicleStatus”, eAccess_ReadCreate,

               OnGet_VehicleStatus, OnSet_VehicleStatus)

 
There is

COLUMN_ROWSTATUS(VehiclesTable, 4, “vehicleStatus”, eAccess_ReadCreate,

                     OnGet_VehicleStatus, OnSet_VehicleStatus)

in the request handler map.

Let’s take a closer look in the steps involved in dynamic row creation.

Before OnSet_VehicleStatus method is called, command responder calls OnGet_VehicleStatus method. Command responder needs to know the current value of row status in order to do the proper test.  How do you now that OnGet is called as the
prelude to OnSet and not because of SNMP Get request? This OnGet method has extra parameter (comparing to all other OnGet-s) bool bSetTest. When OnGet is called as part of handling SNMP set request, bSetTest is true, otherwise it is false.
Here is the code for OnGet:

SNMP_ERRSTAT VehiclesTable::OnGet_VehicleStatus(

                        const BObjectIdentifier& oidIx,

                        tUint32 transactionId,

                        ERowStatus& eVal,

                        bool bSetTest)

{

   Entry* pRow = GetRow(oidIx);

   if (pRow)

   {

      eVal = pRow->m_eVehicleStatus;

      return eSNMP_ERRSTAT_NO_ERROR;

   }

   else

      return eSNMP_ERRSTAT_NO_SUCH_NAME;

}

 
As you can see, the code is not different than for any other OnGet. If row exist, value of row status is retrieved and noError status us returned, otherwise noSuchName error is returned. noSuchName error status indicates to command responder that the row does not exist and that it can proceed with test if this is row creation request. If any other error status is returned, command responder will return that error response to the sender of request without ever calling OnSet.

After OnGet returns, command responder checks the validity of the row status value depending on returned status and value, using following table:
 

ACTION Column
STATE Columns
A
status column
does not exist
B
status column
is notReady
C
status column
is notInService
D
status column
is active
set status column to
createAndGo
noError
->D

or inconsistent-
Value

inconsistent-
Value
inconsistent-
Value
inconsistent-
Value
set status column to
createAndWait
noError

-see #1

inconsistent-
Value
inconsistent-
Value
inconsistent-
Value
set status column to
active
inconsistent-Value
inconsistent-Value

or see 2

->D
noError

 

->D
noError

 

->D
set status column to
notInService
inconsistent-
Value
inconsistent-Value

or see 3

->C
noError
->C
noError
->C

or
wrongValue

set status column to
destroy
noError
->A
noError
->A
noError
->A
noError
->A
set any other column to
some value
see 4
noError
see #1
noError
->C
see 5
->D

            (1) goto B or C, depending on information available to the

            agent.

            (2) if other variable bindings included in the same PDU,

            provide values for all columns which are missing but

            required, then return noError and goto D.

            (3) if other variable bindings included in the same PDU,

            provide values for all columns which are missing but

            required, then return noError and goto C.

            (4) at the discretion of the agent, the return value may be

            either:

                 inconsistentName:  because the agent does not choose to

                 create such an instance when the corresponding

                 RowStatus instance does not exist, or

                 inconsistentValue:  if the supplied value is

                 inconsistent with the state of some other MIB object’s

                 value, or

                 noError: because the agent chooses to create the

                 instance.

            If noError is returned, then the instance of the status

            column must also be created, and the new state is B or C,

            depending on the information available to the agent.  If

            inconsistentName or inconsistentValue is returned, the row

            remains in state A.

            (5) depending on the MIB definition for the column/table,

            either noError or inconsistentValue may be returned.

            NOTE: Other processing of the set request may result in a

            response other than noError being returned, e.g.,

            wrongValue, noCreation, etc.

 
If requested new value does not satisfies one of the transition condition listed above, an appropriate error response is returned (for example if request specifies createAndGo for the existing row an SNMP error status inconsistentValue will be returned in response). If new value is valid, then OnSet_VehicleStatus method is called passing eSNMP_SET_TEST as opcode.  This is the last step in the test phase. Here is the code:

   SNMP_ERRSTAT status = eSNMP_ERRSTAT_GENERAL_ERR;

   ERowStatus eAction = eVal;

   switch (eOpCode)

   {

   case eSNMP_SET_TEST:

      {

         // validity of eAction is done internally

         if ( IsRowCreation(oidIx) )

         {

            // check the index part; does it make sense?

             if ( !IsIndexValid(oidIx) )

               return eSNMP_ERRSTAT_INCONSISTENT_NAME;

            // create new row, but do not insert it yet

            *ppContextInfo = (void*)CreateNewRow(oidIx);

            if ( !*ppContextInfo )

               return eSNMP_ERRSTAT_RESOURCE_UNAVAIL;

         }

         status = eSNMP_ERRSTAT_NO_ERROR;

      }

      break;

 
As you can see, the only situation that we have to intervene is when request is row creation (row status value is createAndGo or createAndWait). This test is done in IsRowCreation method that is a member of BMibTableImplEx class. If it is row creation request, the next test performed is validity of the index: is oidIx valid for this table. Here is the implementation for IsIndexValid :

bool VehiclesTable::IsIndexValid(const BObjectIdentifier& oidIx)

{

   if (oidIx.Len() == 1)

     return true;

   return false;

}

 
The only requirement for the OID that is index in this table is, that it is composed of single element. Note that this test becomes more complicated for the multiple indexed tables, and when index is composed of octet strings, ip addresses etc.

Assuming that index is OK, the next step in the test phase for row creation request is actual row creation. Note that the row is not inserted into the table yet. Also pointer to this temporary row is stored in ppContextInfo variable. This ensures that in case test phase fails before the commit is executed, memory gets deallocated in the cleanup phase.

Let’s see the change in the other OnSet methods for this phase of set.  After checking of the value constraints, and before retrieving the row we should insert following lines:

         if (IsRowCreation(oidIx))

            return eSNMP_ERRSTAT_NO_ERROR;:

 
If row creation is started, then simply return OK.

Now back to the OnSet_VehicleStatus method. If test phase completes successfully, the commit phase is started by calling this method with passing eSNMP_SET_COMMIT as opcode.

Here is the code:

   case eSNMP_SET_COMMIT:

      {

         status = eSNMP_ERRSTAT_COMMIT_FAIL;

         if ( IsRowCreation(oidIx) )

         {

            // add row to the table

            if (*ppContextInfo)

            {

               Entry* pRow = (Entry*)(*ppContextInfo);

               // createAndGo/cretateAndWait are not valid states

               if (eAction == eRowStatus_createAndGo)

                  pRow->m_eVehicleStatus = eRowStatus_active;

               else

                  pRow->m_eVehicleStatus = eRowStatus_notInService;

               if ( m_tbl.Insert(oidIx, pRow) )

               {

                  *ppContextInfo = NULL; // prevent double freeing

                  status = eSNMP_ERRSTAT_NO_ERROR;

               }

            }

            else

            {

               // this is the case for 2nd, 3rd, … row/entry status

               // varbind in this transaction

               Entry* pRow = GetRow(oidIx);

               if (pRow)

               {

                  pRow->m_eVehicleStatus = eAction;

                  status = eSNMP_ERRSTAT_NO_ERROR;

               }

            }

         }

         else if ( IsRowDeletion(oidIx) )

         {

            //note that this will not affect not existent row

            //save pRow so we can restore it in case some

             //other commit in this transaction fail

            *ppContextInfo = (void*)RemoveRow(oidIx);

            status = eSNMP_ERRSTAT_NO_ERROR;

         }

         else

         {

            //validity of assignment is already done in the test phase

            Entry* pRow = GetRow(oidIx);

            if (pRow)

            {

               pRow->m_eVehicleStatus = eAction;

               status = eSNMP_ERRSTAT_NO_ERROR;

            }

         }

      }

      break;

 
There are three possible states of the row: it is either under creation, it is being deleted or simply the value for the row status is being changed.

In the first state, under creation, we have two cases.

The first one is when row is really under creation, row is created in the test phase and hence *ppContextInfo is not NULL. If this is the case, assign the appropriate row status value and add row to the table.

The other case (row is under creation but “not really”) is actually indication that more than one varbind in the same pdu refers the same row status object. This situation rarely occurs with tables with RowStatus, but very often when table has EntryStatus column. In that case SNMP managers tend to put createRequest in the first varbind, and as the last varbind in the same pdu assign the value for that row to valid.
That way, one round trip is saved in row creation process. What happens in that scenario is, the fist varbind sets the flag that the row is under creation, the second (third, 4th ,…) in the test phase simply assume that row is there (even though PreTestRowStatus returns false), and now in the commit phase row is already in the table. So, for the second case, retrieve row from the table and assign new value.

The second state is row deletion. If request is to destroy then row is removed from the table. Row is not deallocated here. This is left for the cleanup phase. We have to keep that row around in case commit phase fails and undo needs to be
done.

The third state is simple assignment. Normally GetRow should succeed since we already checked that such row exists.

If commit phase fails, undo will be executed.

   case eSNMP_SET_UNDO:

      {

         status = eSNMP_ERRSTAT_UNDO_FAIL;

         if ( IsRowCreation(oidIx) )

         {

            //  row is (possibly) added to the table in commit phase,

             //remove it

            Entry* pRow = RemoveRow(oidIx);

            if (pRow)

               delete pRow;

            status = eSNMP_ERRSTAT_NO_ERROR;

         }

         else if ( IsRowDeletion(oidIx) )

         {

            //restore row

            Entry* pRow = (Entry*)(*ppContextInfo);

            if (pRow && InsertRow(oidIx, pRow) )

            {

               *ppContextInfo = NULL; // to prevent freeing in cleanup

               status = eSNMP_ERRSTAT_NO_ERROR;

            }

         }

         else

         {

            //this is simply reassigning the old value

            Entry* pRow = GetRow(oidIx);

            if (pRow)

            {

               // restore variable

               pRow->m_eVehicleStatus = eAction;

               status = eSNMP_ERRSTAT_NO_ERROR;

            }

         }

      }

      break;

 
Again three states. If row is under creation, remove it. If request was to delete the row, restore row in the table. If it was simple assignment, restore previous value.

Finally, the cleanup phase.

 

   case eSNMP_SET_CLEANUP:

   {

      // this will free memory for the row removed in the commit phase

      // from this point row is lost, no recovery is possible

     if (*ppContextInfo)

        delete *ppContextInfo;

      status = eSNMP_ERRSTAT_NO_ERROR;

   }

   break;

 
For the complete listing please take a look at the Lesson11 project included in the setup.

 
The next lesson Lesson 12: Notifications shows how to send notifications/traps.

Previous    Next