Collect input from user

HI

I am using command from confd cli “show cmts linecard 1” ( 1 is slotnum as a input from user).

i wrote yang like this:
container cmts {
config false;
tailf:callpoint cmts;
leaf linecard {
type string;
}
please gide me.

Regards,
Sankar

Hi Sankar,

Guessing what your issue is here. Is typing “show cmts linecard 1” resulting in something like:

> show cmts linecard 1
----------------------------------------^
syntax error: unknown argument

and

> show cmts linecard  
Error: application communication failure

If this is your issue, you need to register an application with ConfD that handle the “cmts” callpoint in your YANG model.

> show confd-state internal callpoints 
ID    ID  NAME  ERROR  PATH  FILE  ERROR           LOWER  UPPER  DEFAULT  ID  NAME  ERROR  
-------------------------------------------------------------------------------------------
cmts  -   -     -      -     -     NOT-REGISTERED   

Regarding “config false” status / operational data and how to register to handle callpoints, see examples.confd/intro/5-c_stat example and ConfD UG Chapter “Operational Data” for more.

Hi, cohult

I also have the same question, I think @sankar wants to get the parameter of linecard from user input. As the yang model given, the confd will render the cli to

  1. show cmts
  2. show cmts linecard
    It means the the command’s format is fixed buy confd, we could input as: show cmts linecard xxx. Because the cmts is a container, and linecard is not a key.

suppose the callback of cmts is:

get_elem()
{
        char *s = get_parameter_of('linecard');
        confd_value_t ret = format("you_give_me_%s", s); // you_give_me_1
        confd_value_reply(ret);
        return confd_ok;
}

I think @sankar want the following result:

# show cmts linecard 1
# cmts linecard you_give_me_1

# show cmts linecard 2
# cmts linecard you_give_me_2

I want I could input any other data beyond the data model, and then I could get it in callpoint callback.

Either you make it into a YANG action / rpc or you get the parameter from a configuration leaf.
Example:

  leaf linecard {
    type string;
  }
  container cmts {
    config false;
    tailf:callpoint cmts;
    leaf linecard_data {
      type string;
    }
  }

In your c-application:

static int get_elem(struct confd_trans_ctx *tctx,
                    confd_hkeypath_t *keypath)
{
  ...
  cdb_get_str(rsock, buf, BUFSIZ, "/linecard");
  ...
  CONFD_SET_STR(&v, buf);
  confd_data_reply_value(tctx, &v);
  return CONFD_OK;
}

The YANG action/rpc could be provided parameters as they are designed, but some times user wanna the show command can also be given parameters, so that we can show something filtered by the parameters. I think ConfD only support the list key as the filter parameter which passed to daemon, if we need to filter by other leaf value, the User Guide said use:

 # show some_list | select some_leaf some_value

But this is not what I want, I think it is only a display filter, not the data provider filter. For example, if we have a hundred million items in certain list, The ConfD will get all the data from daemon, and the filter the data by select and then show the match ones.
Instead, we need to get the show parameters, and then only provide the filtered data to ConfD, and then ConfD show them.

list students
{
    key id;
    leaf id {type uint32;}
    leaf name {type string;}
    leaf age {type uint8;}
    leaf sex {type gender;}
    leaf others {}
}

and then

show students age 20 sex Male ...

our daemon only provides students(age = 20, sex = Male), we do data filter in our daemon.

hope I describe myself clearly. Thank you.

If you do something like this, ConfD will filter for you on the fly.

# show students                                           
ID  NAME  AGE  SEX     
-----------------------
1   a     1    female  
2   b     2    female  
3   a     2    female  
4   b     1    female  
5   a     1    male    
6   a     1    male    
7   a     1    male    
8   b     1    male 

# show students | select age 1 | select sex female | match-all
ID  NAME  AGE  SEX     
-----------------------
1   a     1    female  
4   b     1    female  

# show students | select age 1 | select sex male | match-all 
ID  NAME  AGE  SEX   
---------------------
5   a     1    male  
8   b     1    male

I think ConfD will first get all the 8 members from the daemon, and then show them by filter, no matter how many items it shows, ConfD must get the full sets of all items. Is that right?

it doesn’t matter if we have a small amount of items, but how about if we have a large amount data?

Does ConfD provides the select callback (instead of supporting parameters), so that we could feed the final data sets filtered in daemon side instead of ConfD side.

It depends on how you implemented your data provider callbacks.

Let’s take the use examples.confd/intro/5-c_stats as an example:

# show arpentries arpe
IP             IFNAME  HWADDR             PERMANENT  PUBLISHED  
----------------------------------------------------------------
172.16.171.2   eth1    00:50:56:f7:9d:e2  false      false      
172.16.171.98  eth1    bb:bb:bb:bb:bb:bb  true       false      
172.16.171.99  eth1    aa:aa:aa:aa:aa:aa  true       false 

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_elem(thandle=6,/arpentries/arpe{172.16.171.2 eth1}/hwaddr) ("00:50:56:f7:9d:e2")  --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.2 eth1}/permanent) (false)  --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.2 eth1}/published) (false)  --> CONFD_OK
TRACE CALL data get_next(thandle=6, /arpentries/arpe, 13923968) --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.98 eth1}/hwaddr) ("bb:bb:bb:bb:bb:bb")  --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.98 eth1}/permanent) (true)  --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.98 eth1}/published) (false)  --> CONFD_OK
TRACE CALL data get_next(thandle=6, /arpentries/arpe, 13947088) --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.99 eth1}/hwaddr) ("aa:aa:aa:aa:aa:aa")  --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.99 eth1}/permanent) (true)  --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.99 eth1}/published) (false)  --> CONFD_OK
TRACE CALL data get_next(thandle=6, /arpentries/arpe, 0) --> CONFD_OK

# show arpentries arpe | select permanent true | select published false | match-all
IP             IFNAME  HWADDR             PERMANENT  PUBLISHED  
----------------------------------------------------------------
172.16.171.98  eth1    bb:bb:bb:bb:bb:bb  true       false      
172.16.171.99  eth1    aa:aa:aa:aa:aa:aa  true       false 

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_elem(thandle=6,/arpentries/arpe{172.16.171.2 eth1}/permanent) (false)  --> CONFD_OK
TRACE CALL data get_next(thandle=6, /arpentries/arpe, 13947088) --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.98 eth1}/permanent) (true)  --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.98 eth1}/published) (false)  --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.98 eth1}/hwaddr) ("bb:bb:bb:bb:bb:bb")  --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.98 eth1}/permanent) (true)  --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.98 eth1}/published) (false)  --> CONFD_OK
TRACE CALL data get_next(thandle=6, /arpentries/arpe, 13949312) --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.99 eth1}/permanent) (true)  --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.99 eth1}/published) (false)  --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.99 eth1}/hwaddr) ("aa:aa:aa:aa:aa:aa")  --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.99 eth1}/permanent) (true)  --> CONFD_OK
TRACE CALL data get_elem(thandle=6,/arpentries/arpe{172.16.171.99 eth1}/published) (false)  --> CONFD_OK
TRACE CALL data get_next(thandle=6, /arpentries/arpe, 0) --> CONFD_OK

As described by How to add the support of the get_next_object() callback to the 5-c_stats example? with the get_next_object() callback implemented ConfD will ask for the entire list. (you can chunk it in your get_next_object() callback if it is a large list):

# show arpentries
IP             IFNAME  HWADDR             PERMANENT  PUBLISHED  
----------------------------------------------------------------
172.16.171.2   eth1    00:50:56:f7:9d:e2  false      false      
172.16.171.98  eth1    bb:bb:bb:bb:bb:bb  true       false      
172.16.171.99  eth1    aa:aa:aa:aa:aa:aa  true       false   

TRACE CALL data get_next(thandle=16, /arpentries/arpe, -1) --> CONFD_OK
TRACE CALL data get_next_object(thandle=16, /arpentries/arpe, -1) --> CONFD_OK

# show arpentries arpe | select permanent true | select published false | match-all
IP             IFNAME  HWADDR             PERMANENT  PUBLISHED  
----------------------------------------------------------------
172.16.171.98  eth1    bb:bb:bb:bb:bb:bb  true       false      
172.16.171.99  eth1    aa:aa:aa:aa:aa:aa  true       false 

TRACE CALL data get_next(thandle=16, /arpentries/arpe, -1) --> CONFD_OK
TRACE CALL data get_next_object(thandle=16, /arpentries/arpe, -1) --> CONFD_OK

Hi cohult, thanks for your patience.

# show arpentries arpe | select permanent true | select published false | match-all

In your get_elem() demo, when ConfD founds permanent of arpe{172.16.171.2 eth1} if false, which should be filtered by ‘select permanent true’, so other get_elem() of other leaf of this key is skipped. Suppose we have many leaves in this list, and we only filter the last one, will ConfD call get_elem() for each leaf before the filtered one? To say the least, ConfD is smart enough, filtered leaves take priority over all others by get_elem(). But at least one leaf must be passed back to ConfD to judge whether to continue getting other leaves.

The get_object() is suitable for retrieving the entire large list without any filter, actually this is what we use. But how do we filter data without get the filter parameters?

I mean how do we filter data in get_next_object()

static int get_next_object(struct confd_trans_ctx *tctx,
			     confd_hkeypath_t *keypath, long next)
{
  // get all items in effective functions
  // aes = binary_search_by(dp->arp_entries, get_filter(hardware)

  // simple compare in daemon side
  ae = dp->arp_entries;
  while(next(ae)) {
    if (ae->permanent != get_filter(permanent))
          continue;
    if (ae->published != get_filter(published))
          continue;
    .......
  }
  return CONFD_OK;
}

As you can see in the output I provided above for the get_elem() with filter (select) scenario ConfD will check the filter before getting the list instance. I.e. only the leafs included in the filter. And as demonstrated by the example above too, ConfD will not get any more leafs if the first leaf ConfD get does not match the filter. Above it was the permanent leaf that was set to false.

ConfD will use what ConfD thinks is the most effective way to execute the filter depending on what callbacks you implement. In the get_next_object() example I provided above ConfD decides it is more efficient to get the complete list and parse the list inside ConfD instead of calling multiple get_next() get_elem() to find out which list entries to retrieve.

Though it save the cost of calling the other leaves, It has to call get_elem() for each filtered leaf as well as call get_next() for each entry.

I modified examples.confd/intro/5-c_stats let is read data from file:

116     //if ((fp = popen("arp -an", "r")) == NULL)
117     if ((fp = fopen("arp.txt", "r")) == NULL)
118         return CONFD_ERR;

the arp.txt contains the following:

? (10.0.0.1) at ff:ff:ff:ff:ff:00 [ether] on eno1234567
? (10.0.0.2) at ff:ff:ff:ff:ff:00 [ether] on eno1234567
? (10.0.0.3) at ff:ff:ff:ff:ff:00 [ether] on eno1234567
? (10.0.0.4) at ff:ff:ff:ff:ff:00 [ether] on eno1234567
? (10.0.0.5) at ff:ff:ff:ff:ff:00 [ether] on eno1234567
? (10.0.0.6) at ff:ff:ff:ff:ff:00 [ether] on eno1234567
? (10.0.0.7) at ff:ff:ff:ff:ff:00 [ether] on eno1234567
? (10.0.0.8) at ff:ff:ff:ff:ff:00 [ether] on eno1234567
? (10.0.0.9) at ff:ff:ff:ff:ff:00 [ether] on eno1234567 permanent
? (10.0.0.10) at ff:ff:ff:ff:ff:00 [ether] on eno1234567 published

We start confd and arpstat and show by filter:

localhost# show arpentries arpe | select published true
IP         IFNAME      HWADDR             PERMANENT  PUBLISHED  
----------------------------------------------------------------
10.0.0.10  eno1234567  ff:ff:ff:ff:ff:00  false      true       


Here is the log:

TRACE Connected (maapi) to ConfD
TRACE MAAPI_LOAD_ALL_NS
TRACE MAAPI_LOAD_HASH_DB
TRACE Connected (dp) to ConfD
TRACE Received daemon id 9
TRACE Connected (dp) to ConfD
TRACE Picked up old user session: 14 for user:admin ctx:cli
TRACE Picked up old user session: 10 for user:system ctx:system
TRACE Picked up old user session: 1 for user:system ctx:system
TRACE CALL trans init(thandle=9,mode="r",db=running) --> CONFD_OK
TRACE CALL data get_next(thandle=9, /arpentries/arpe, -1) --> CONFD_OK
TRACE CALL data get_next(thandle=9, /arpentries/arpe, -1) --> CONFD_OK
TRACE CALL data get_elem(thandle=9,/arpentries/arpe{10.0.0.1 eno1234567}/published) (false)  --> CONFD_OK
TRACE CALL data get_next(thandle=9, /arpentries/arpe, 21334528) --> CONFD_OK
TRACE CALL data get_elem(thandle=9,/arpentries/arpe{10.0.0.2 eno1234567}/published) (false)  --> CONFD_OK
TRACE CALL data get_next(thandle=9, /arpentries/arpe, 21608496) --> CONFD_OK
TRACE CALL data get_elem(thandle=9,/arpentries/arpe{10.0.0.3 eno1234567}/published) (false)  --> CONFD_OK
TRACE CALL data get_next(thandle=9, /arpentries/arpe, 21608048) --> CONFD_OK
TRACE CALL data get_elem(thandle=9,/arpentries/arpe{10.0.0.4 eno1234567}/published) (false)  --> CONFD_OK
TRACE CALL data get_next(thandle=9, /arpentries/arpe, 21608160) --> CONFD_OK
TRACE CALL data get_elem(thandle=9,/arpentries/arpe{10.0.0.5 eno1234567}/published) (false)  --> CONFD_OK
TRACE CALL data get_next(thandle=9, /arpentries/arpe, 21608272) --> CONFD_OK
TRACE CALL data get_elem(thandle=9,/arpentries/arpe{10.0.0.6 eno1234567}/published) (false)  --> CONFD_OK
TRACE CALL data get_next(thandle=9, /arpentries/arpe, 21614336) --> CONFD_OK
TRACE CALL data get_elem(thandle=9,/arpentries/arpe{10.0.0.7 eno1234567}/published) (false)  --> CONFD_OK
TRACE CALL data get_next(thandle=9, /arpentries/arpe, 21614448) --> CONFD_OK
TRACE CALL data get_elem(thandle=9,/arpentries/arpe{10.0.0.8 eno1234567}/published) (false)  --> CONFD_OK
TRACE CALL data get_next(thandle=9, /arpentries/arpe, 21614560) --> CONFD_OK
TRACE CALL data get_elem(thandle=9,/arpentries/arpe{10.0.0.9 eno1234567}/published) (false)  --> CONFD_OK
TRACE CALL data get_next(thandle=9, /arpentries/arpe, 21614672) --> CONFD_OK
TRACE CALL data get_elem(thandle=9,/arpentries/arpe{10.0.0.10 eno1234567}/published) (true)  --> CONFD_OK
TRACE CALL data get_elem(thandle=9,/arpentries/arpe{10.0.0.10 eno1234567}/hwaddr) ("ff:ff:ff:ff:ff:00")  --> CONFD_OK
TRACE CALL data get_elem(thandle=9,/arpentries/arpe{10.0.0.10 eno1234567}/permanent) (false)  --> CONFD_OK
TRACE CALL data get_elem(thandle=9,/arpentries/arpe{10.0.0.10 eno1234567}/published) (true)  --> CONFD_OK
TRACE CALL data get_next(thandle=9, /arpentries/arpe, 0) --> CONFD_OK

In this situation, if we could get the filter ‘select published true’ in daemon side, we’ll could do some filter in get_next(), we tell ConfD there is only one entry, so ConfD don’t need to call get_next() on each entry.

The same as the get_next_object(), we filter the data, and send them to ConfD instead of we send all entry to ConfD filltered by ConfD.

Hi lynn,

A filter, e.g. a CLI show | select statement, a NETCONF, JSON-RPC, MAAPI query etc. is something that is transformed to a filter that the ConfD core engine understands.

You want to get this “filter” that ConfD use and pre-filter in your data provider application, but there is no API to pass the “filter” to your data provider in ConfD.

So if you absolutely cannot live with the ConfD core engine doing the filtering for you, but must pass a filter to your data provider application to do some more efficient pre-filtering depending on what the manager types in the CLI, you can for example implement your own CLI pipe command.
The pipe command implementation can then pass the filter parameters in some way of your choice to your data provider for “pre-filtering” before your data provider pass the data back.

E.g. create a commands-c.cli config spec file that looks something like this:

<clispec xmlns="http://tail-f.com/ns/clispec/1.0" style="c">
  <operationalMode>
    <modifications>
      <addPipeFlags src="show">filter</addPipeFlags>
    </modifications>
  </operationalMode>
  <configureMode/>
  <pipeCmds>
    <cmd name="filter">
      <info>filter</info>
      <help></help>
      <options>
        <pipeFlags>filter</pipeFlags>
      </options>
      <callback>
        <capi>
          <cmdpoint>showfilter</cmdpoint>
        </capi>
      </callback>
      <params>
        <param>
          <type>YOUR INPUT TYPE HERE</type>
          <info>&amp;lt;SOME HELP&amp;gt;</info>
          <help>SOME HELP</help>
          <name>PARAMETER NAME</name>
        </param>
      <params>
    </cmd>
  </pipeCmds>
</clispec>

The “cmdpoint” showfilter specifies the name of the C-API action to be called. For this to work, you need to register an actionpoint with the ConfD daemon at startup (besides the callpoint).

If you don’t want to implement this as an actionpoint you can run an executable (e.g. a shell script or a c-program) instead that pass the filter parameters to your data provider application.

<callback>
  <exec>
    <osCommand>SOME PATH/showfilter.sh</osCommand>
  </exec>
</callback>

See man/man5/clispec.5 and examples.confd/cli/show_templates/confd.cisco.cli for a somewhat related example.

Hi cohult, thank you very much, you help me a lot.

In our project, we’ve got more than one hundred million router items to show, but we don’t want to show them all in one swoop, we want to show different router items by different filter, so there is little data need to be transferred from data provider application to ConfD.

It really helps to filter data in CLI by implementing pipe command. But it only works for CLI rather than NETCONF, JSON-RPC, MAAPI query, right?

Anyway thank you very much again!

Hi lynn,

Right, I believe it would need to be your own special NETCONF RPC, your proprietary JSON-RPC API call. With MAAPI query you could for example implement a wrapper call that place the query information somewhere known to the data provider.