Getting ConfD to invoke get_object() instead of get_elem()

In the context of interfacing an external database with ConfD, if I implement both get_elem() and get_object(), how do I tell ConfD to use get_object()?

The user guide implies that, to handle list entries, ConfD will choose get_object() on its own (without and external control). However, in my experience, get_object() is used only if get_elem() is not present.

My model has a top container, which has two child lists. The top container does not have any child leafs.

Thanks,
Helen

Hi Helen,

get_object( ) works in practice according to the way it is described in the ConfD User Guide. ConfD will decide the most efficient way to retrieve the data from the external data provider on its own. If the get_object( ) call has been registered with ConfD, ConfD will invoke the get_object( ) call to retrieve the entire row of data from a list entry instead of the individual get_elem( ) calls to retrieve one data element at a time when list entries are being requested.

As is described in this post, when you invoke cli and perform the show arpentries operation, the get_object( ) call will be invoked instead of get_elem( ) as shown below:

admin connected from 127.0.0.1 using console on WAITAI-M-K092
WAITAI-M-K092# show arpentries 
IP        IFNAME  HWADDR             PERMANENT  PUBLISHED  
-----------------------------------------------------------
10.0.0.1  en0     58:23:8c:bf:2b:86  true       false      

TRACE New user session: 11 for user:admin ctx:cli --> CONFD_OK
TRACE CALL trans init(thandle=6,mode="r",db=running) --> CONFD_OK
TRACE CALL data get_next(thandle=6, /arpentries/arpe, -1) --> CONFD_OK
TRACE CALL data get_next(thandle=6, /arpentries/arpe, -1) --> CONFD_OK
TRACE CALL data get_object(thandle=6,/arpentries/arpe{10.0.0.1 en0}) --> CONFD_OK
TRACE CALL data get_next(thandle=6, /arpentries/arpe, 0) --> CONFD_OK

However, if you run the original example and perform the “show arpentries” operation from cli, the get_elem( ) calls will be invoked instead as shown in the following TRACE statements:

admin connected from 127.0.0.1 using console on WAITAI-M-K092
WAITAI-M-K092# show arpentries 
IP        IFNAME  HWADDR             PERMANENT  PUBLISHED  
-----------------------------------------------------------
10.0.0.1  en0     58:23:8c:bf:2b:86  true       false      

TRACE New user session: 12 for user:admin ctx:cli --> CONFD_OK
TRACE CALL trans init(thandle=12,mode="r",db=running) --> CONFD_OK
TRACE CALL data get_next(thandle=12, /arpentries/arpe, -1) --> CONFD_OK
TRACE CALL data get_next(thandle=12, /arpentries/arpe, -1) --> CONFD_OK
TRACE CALL data get_elem(thandle=12,/arpentries/arpe{10.0.0.1 en0}/hwaddr) ("58:23:8c:bf:2b:86")  --> CONFD_OK
TRACE CALL data get_elem(thandle=12,/arpentries/arpe{10.0.0.1 en0}/permanent) (true)  --> CONFD_OK
TRACE CALL data get_elem(thandle=12,/arpentries/arpe{10.0.0.1 en0}/published) (false)  --> CONFD_OK
TRACE CALL data get_next(thandle=12, /arpentries/arpe, 0) --> CONFD_OK

Please feel free to list your specific example if it works differently.

Regards,

Wai

What if the get_object() and get_elem() were used to get configuration data? Would ConfD automatically use get_object() instead of get_elem()? My confusion is that when my adapter has both get_object() and get_elem(), then I see that ConfD uses get_elem(). However, when my adapter only has get_object(), then I see that ConfD uses get_object().

ConfD will use what is most efficient depending on the data that is being retrieved and the available callbacks that have been registered.

Let’s go with an example for 3 types of NETCONF queries using the same example project with implementation of both the get_elem( ) and get_object( ) callbacks, 1st one is to retrieve the entire list, 2nd one is to retrieve a single type key elements, and the 3rd one is to retrieve a single type of leaf elements from a list. The NETCONF query with the interactive mode of netconf-console will look like the following:

$ netconf-console -i

* Enter a NETCONF operation, end with an empty line
  <get>
    <filter xmlns="http://tail-f.com/ns/example/arpe">
      <arpentries>
         <arpe/>
     </arpentries>
    </filter>
  </get>

<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="2">
  <data>
    <arpentries xmlns="http://tail-f.com/ns/example/arpe">
      <arpe>
        <ip>10.0.0.1</ip>
        <ifname>en0</ifname>
        <hwaddr>58:23:8c:bf:2b:86</hwaddr>
        <permanent>true</permanent>
        <published>false</published>
      </arpe>
    </arpentries>
  </data>
</rpc-reply>

* Enter a NETCONF operation, end with an empty line
  <get>
    <filter xmlns="http://tail-f.com/ns/example/arpe">
      <arpentries>
         <arpe>
            <ip/>
         </arpe>
      </arpentries>
    </filter>
  </get>

<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="2">
  <data>
    <arpentries xmlns="http://tail-f.com/ns/example/arpe">
      <arpe>
        <ip>10.0.0.1</ip>
      </arpe>
    </arpentries>
  </data>
</rpc-reply>

* Enter a NETCONF operation, end with an empty line
  <get>
    <filter xmlns="http://tail-f.com/ns/example/arpe">
      <arpentries>
         <arpe>
            <hwaddr/>
         </arpe>
      </arpentries>
    </filter>
  </get>

<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="2">
  <data>
    <arpentries xmlns="http://tail-f.com/ns/example/arpe">
      <arpe>
        <hwaddr>58:23:8c:bf:2b:86</hwaddr>
      </arpe>
    </arpentries>
  </data>
</rpc-reply>

The corresponding trace statements from the arpstat client application is as follows:

TRACE New user session: 17 for user:admin ctx:netconf --> CONFD_OK
TRACE CALL trans init(thandle=27,mode="r",db=running) --> CONFD_OK
TRACE CALL data get_next(thandle=27, /arpentries/arpe, -1) --> CONFD_OK
TRACE CALL data get_object(thandle=27,/arpentries/arpe{10.0.0.1 en0}) --> CONFD_OK
TRACE CALL data get_next(thandle=27, /arpentries/arpe, 0) --> CONFD_OK
TRACE CALL trans finish(thandle=27) --> CONFD_OK

TRACE CALL trans init(thandle=28,mode="r",db=running) --> CONFD_OK
TRACE CALL data get_next(thandle=28, /arpentries/arpe, -1) --> CONFD_OK
TRACE CALL data get_next(thandle=28, /arpentries/arpe, 0) --> CONFD_OK
TRACE CALL trans finish(thandle=28) --> CONFD_OK

TRACE CALL trans init(thandle=29,mode="r",db=running) --> CONFD_OK
TRACE CALL data get_next(thandle=29, /arpentries/arpe, -1) --> CONFD_OK
TRACE CALL data get_elem(thandle=29,/arpentries/arpe{10.0.0.1 en0}/hwaddr) ("58:23:8c:bf:2b:86")  --> CONFD_OK
TRACE CALL data get_next(thandle=29, /arpentries/arpe, 0) --> CONFD_OK
TRACE CALL trans finish(thandle=29) --> CONFD_OK

You will notice from the TRACE statements above that both get_next( ) and get_object( ) are being invoked for the retrieval of the entire list, only get_next( ) are being invoked for retrieval of key elements queries, and only get_next( ) and get_elem( ) are being invoked for retrieval of specific leaf element queries.

I’d like to use the example from a different question about module dependency. The modules and the edit-config RPC are as follows:

module test-citizens {
  namespace "http://www.example.com/yang/test-citizens";
  prefix "tc";

  organization "";
  contact "";

  container citizens {
    list citizen {
      key "name";
      leaf name {
        type string;
      }
    }
  }
}

module test-dependency {
  namespace "http://www.example.com/yang/test-dependency";
  prefix "td";

  import test-citizens {
    prefix "tc";
  }

  organization "";
  contact "";

  container cities {
    list city {
      key "name";
      leaf name {
        type string;
      }
      leaf mayor {
        type leafref {
          path "/tc:citizens/tc:citizen/tc:name";
        }
      }
    }
  }
}

module test-annotation {
  namespace "http://www.example.com/yang/test-annotation";
  prefix "ta";

  import test-citizens {
    prefix "tc";
  }

  import test-dependency {
    prefix "td";
  }

  import tailf-common {
    prefix "tailf";
  }

  organization "";
  contact "";

  tailf:annotate "/tc:citizens/tc:citizen" {
    tailf:callpoint citizen;
  }

  tailf:annotate "/td:cities/td:city" {
    tailf:callpoint city;
  }
}

<?xml version="1.0" encoding="UTF-8"?>
<rpc message-id="104"
  xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
  xmlns:tc="http://www.example.com/yang/test-citizens"
  xmlns:td="http://www.example.com/yang/test-dependency">
  <edit-config>
    <target>
      <running />
    </target>
    <config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
      <td:cities>
        <td:city xc:operation="create">
          <td:name>Seattle</td:name>
          <td:mayor>John</td:mayor>
        </td:city>
      </td:cities>
      <tc:citizens>
        <tc:citizen xc:operation="create">
          <tc:name>John</tc:name>
        </tc:citizen>
      </tc:citizens>
    </config>
  </edit-config>
</rpc>

When I send in the edit-config RPC, and when I implement both the get_elem and get_object callbacks for the “citizen” callpoint, the trace I get is as follows:

test_init(0) for root from 155.53.154.223  
 --> CONFD_OK
TRACE CALL data get_elem(thandle=7,/cities/city{Seattle}/name)
city_get_elem:176: kp_str /cities/city{Seattle}/name
 (NOEXISTS)  --> CONFD_OK
TRACE CALL data get_elem(thandle=7,/cities/city{Seattle}/mayor)
city_get_elem:176: kp_str /cities/city{Seattle}/mayor
 (NOEXISTS)  --> CONFD_OK
TRACE CALL data get_elem(thandle=7,/citizens/citizen{John}/name)
citizen_get_elem:61: kp_str /citizens/citizen{John}/name
 (NOEXISTS)  --> CONFD_OK
TRACE CALL data create(thandle=7,/citizens/citizen{John})
citizen_create:132: new citizen John
 --> CONFD_OK
 (0)  (0) TRACE CALL data create(thandle=7,/cities/city{Seattle})
city_create:212: kp_str /cities/city{Seattle}

city_create:221: new city Seattle
 --> CONFD_OK
 (0)  (0) TRACE CALL data set_elem(thandle=7, /cities/city{Seattle}/mayor, John) --> 
 CONFD_ACCUMULATE
 (0)  (0) TRACE CALL trans prepare(thandle=7)op: SET_ELEM, callpoint city
 --> CONFD_OK
TRACE CALL trans commit(thandle=7) --> CONFD_OK
TRACE CALL trans finish(thandle=7) --> CONFD_OK

However, if I implement only the get_object callback (by setting the get_elem callback to NULL), then my trace is as follows:

test_init(0) for root from 155.53.154.223
 --> CONFD_OK
TRACE CALL data get_object(thandle=9,/cities/city{Seattle})
city_get_object:241: kp_str /cities/city{Seattle}
 (NOEXISTS)  --> CONFD_OK
TRACE CALL data get_elem(thandle=9,/citizens/citizen{John}/name)
citizen_get_elem:61: kp_str /citizens/citizen{John}/name
 (NOEXISTS)  --> CONFD_OK
TRACE CALL data create(thandle=9,/citizens/citizen{John})
citizen_create:132: new citizen John
 --> CONFD_OK
 (0)  (0) TRACE CALL data create(thandle=9,/cities/city{Seattle})
city_create:212: kp_str /cities/city{Seattle}

city_create:221: new city Seattle
 --> CONFD_OK
 (0)  (0) TRACE CALL data set_elem(thandle=9, /cities/city{Seattle}/mayor, John) --> 
CONFD_ACCUMULATE
 (0)  (0) TRACE CALL trans prepare(thandle=9)op: SET_ELEM, callpoint city
 --> CONFD_OK
TRACE CALL trans commit(thandle=9) --> CONFD_OK
TRACE CALL trans finish(thandle=9) --> CONFD_OK

I don’t understand why there are two different traces. In the first trace, ConfD calls get_elem twice, rather than calling get_object once. (In the first trace, both get_elem and get_object callbacks are implemented.) Why does ConfD decide that calling the get_elem callback twice is more efficient than calling get_object once? (In the second trace, I set the get_elem callback to NULL, and see that ConfD uses get_object instead.)

Below are the get_elem calls from the first trace above:

TRACE CALL data get_elem(thandle=7,/cities/city{Seattle}/name)
city_get_elem:176: kp_str /cities/city{Seattle}/name
 (NOEXISTS)  --> CONFD_OK
TRACE CALL data get_elem(thandle=7,/cities/city{Seattle}/mayor)
city_get_elem:176: kp_str /cities/city{Seattle}/mayor
 (NOEXISTS)  --> CONFD_OK

Below is the get_object call from the second trace above:

TRACE CALL data get_object(thandle=9,/cities/city{Seattle})
city_get_object:241: kp_str /cities/city{Seattle}
 (NOEXISTS)  --> CONFD_OK

Hi Helen,

That’s a good question.

For your first trace, calling get_object( ) once is more efficient only for the case when Seattle hasn’t yet been created as a city in your city list. However, if Seattle already exists as a key element in your list, then your NETCONF create request will fail upon the first get_elem( ) request. A second get_elem( ) request is no longer necessary.

Regards,

Wai

Are you saying that the optimized callback only works for operational state data? For configuration data, the optimized callback will never be used?

Thanks,
Helen

No. I was pointing out the fact that invoking get_object( ) doesn’t always yield the most efficient scenario. It depends on the RPC operations being involved and the contents of the your external database.

Just to elaborate on my the point in my previous post, if I start with the following contents in my external database:

<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
  <data>
    <cities xmlns="http://www.example.com/yang/test-dependency">
      <city>
        <name>Seattle</name>
        <mayor/>
      </city>
    </cities>
  </data>
</rpc-reply>

and then issue the RPC request in your previous post, I will get the following trace statements:

TRACE New user session: 13 for user:admin ctx:netconf --> CONFD_OK
TRACE CALL trans init(thandle=21,mode="rw",db=running)s_init() for admin from 127.0.0.1
 --> CONFD_OK
TRACE CALL data get_elem(thandle=21,/cities/city{Seattle}/name) ("Seattle")  --> CONFD_OK
TRACE NULL trans finish(thandle=21)
TRACE Close user sess 13
 --> CONFD_OK

in which invoking a single get_elem( ) call is more efficient than invoking a get_object( ) call and the following RPC response:

<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:td="http://www.example.com/yang/test-dependency" xmlns:tc="http://www.example.com/yang/test-citizens" message-id="104">
  <rpc-error>
    <error-type>application</error-type>
    <error-tag>data-exists</error-tag>
    <error-severity>error</error-severity>
    <error-path xmlns:td="http://www.example.com/yang/test-dependency" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
    /nc:rpc/nc:edit-config/nc:config/td:cities/td:city[td:name='Seattle']
  </error-path>
    <error-info>
      <bad-element>city</bad-element>
    </error-info>
  </rpc-error>
</rpc-reply>

Going back to your scenario upon the successful completion of your RPC request, if I then issue the following RPC call:

<rpc message-id="1" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <get>
    <filter select=" cities" type="xpath"/>
  </get>
</rpc>

I will now get the following trace statements making use of the get_object( ) call:

TRACE New user session: 12 for user:admin ctx:netconf --> CONFD_OK
TRACE CALL trans init(thandle=8,mode="r",db=running)s_init() for admin from 127.0.0.1
 --> CONFD_OK
TRACE CALL data get_next(thandle=8, /cities/city, -1) --> CONFD_OK
TRACE CALL data get_object(thandle=8,/cities/city{Seattle}) --> CONFD_OK
TRACE CALL data get_next(thandle=8, /cities/city, 1) --> CONFD_OK
TRACE NULL trans finish(thandle=8)
TRACE Close user sess 12
 --> CONFD_OK

Hope this helps.

Ok, let me rephrase my question. (The distinction isn’t configuration data vs operational state data. The distinction is write operation vs read operation.)

Is there any operation on test-dependency that results in ConfD calling get_object instead of get_elem?

So far, all your examples of ConfD using get_object are to read data, whether it’s configuration data or operational state data. I don’t see examples of ConfD using get_object to write data.

Thanks,
Helen

If you perform a RPC edit-config request with the replace attribute as follows:

<edit-config>
  <target>
    <running />
  </target>
  <config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
    <td:cities>
      <td:city xc:operation="replace">
        <td:name>Seattle</td:name>
        <td:mayor>Dave</td:mayor>
      </td:city>
    </td:cities>
  </config>
</edit-config>

you will get the following trace statements:

TRACE New user session: 14 for user:admin ctx:netconf --> CONFD_OK
TRACE CALL trans init(thandle=23,mode="rw",db=running)s_init() for admin from 127.0.0.1
 --> CONFD_OK
TRACE CALL data get_elem(thandle=23,/cities/city{Seattle}/name) ("Seattle")  --> CONFD_OK
TRACE CALL data get_elem(thandle=23,/cities/city{Seattle}/mayor) ("John")  --> CONFD_OK
TRACE CALL trans init(thandle=24,mode="r",db=running)s_init() for admin from 127.0.0.1
 --> CONFD_OK
TRACE CALL data get_object(thandle=24,/cities/city{Seattle}) --> CONFD_OK
TRACE NULL trans finish(thandle=24)
TRACE CALL data set_elem(thandle=23, /cities/city{Seattle}/mayor, Dave) --> CONFD_OK
 (0)  (0) TRACE NULL trans finish(thandle=23)
TRACE Close user sess 14
 --> CONFD_OK

which does make use of the get_object( ) callback.

One optimization you can do in the case of your test-dependency data model is to make the leaf element called mayor a mandatory leaf.

If I repeat the same RPC request as in your earlier post in an empty datastore, I will now get the following trace statements:

TRACE New user session: 11 for user:admin ctx:netconf --> CONFD_OK
TRACE CALL trans init(thandle=6,mode="rw",db=running)s_init() for admin from 127.0.0.1
 --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/cities/city{Seattle}/name) (NOEXISTS)  --> CONFD_OK
TRACE CALL data create(thandle=6,/cities/city{Seattle}) --> CONFD_OK
 (0)  (0) TRACE CALL data set_elem(thandle=6, /cities/city{Seattle}/mayor, John) --> CONFD_OK
 (0)  (0) TRACE NULL trans finish(thandle=6)
TRACE Close user sess 11
 --> CONFD_OK

in this case, confd will now only invoke a single get_elem( ) call to find out the existence of the key element named Seattle prior to issuing the create( ) call.