How to convert a ConfD tag value array to XML?

It is a small task to convert a tag value array to XML. Below is a quick implementation and demo where we extend the existing ConfD intro/1-2-3-start-query-model example by first getting the subscription configuration updates using a confd_get_modifications() call and then print the tag value array result in in pretty XML format.

Example code:

Let’s first do a diff to see what we added to the 1-2-3 intro example:

$ pwd
/Users/conny/tailf/confd-5.4.2/examples.confd/intro/1-2-3-start-query-model-mod 
$ diff ../1-2-3-start-query-model/dhcpd_conf.c dhcpd_conf.c 

In the 1-2-3 intro example main() function we want to first get the schema information at initialization:

162a230,232
>     if (confd_load_schemas((struct sockaddr*)&addr, 
>                             sizeof (struct sockaddr_in)) != CONFD_OK)
>         confd_fatal("Failed to load schemas from confd\n");

When we receive a subscription modification notification we want to first get the modifications and then we call a helper function to print the tag value array in pretty XML format:

220a291,306
> 	        confd_tag_value_t *values = NULL;
>                 int nvalues, i;
>                 if ((status = cdb_get_modifications(subsock, 
>          					    sub_points[0], 
> 						    CDB_GET_MODS_INCLUDE_LISTS, 
> 					            &values, 
> 						    &nvalues, 
> 						    "/dhcp")) == CONFD_OK) {
> 		    printf("Modifications in pretty printed XML:\n");
> 		    print_modifications_xml_pretty(values, nvalues, NULL, 0);
> 		    for (i = 0; i < nvalues; i++)
> 		        confd_free_value(CONFD_GET_TAG_VALUE(&values[i]));
>                     free(values); 
> 		} else {
> 		    fprintf(stderr, "failed to get modifications %d\n", status);
>                 }

Our helper function that print a tag value array in pretty XML format:

148a149,215
> static void print_modifications_xml_pretty(confd_tag_value_t *val, int nvals,
>                                 struct confd_cs_node *start_node,
>                                 int start_indent)
> {
>     int i, indent = start_indent;
>     struct confd_cs_node root, *pnode = start_node, *node;
>     char tmpbuf[BUFSIZ];
>     char *tmp;
> 
>     for (i=0; i<nvals; i++) {
>         if (indent == start_indent && start_node == NULL) {
>             node = confd_find_cs_root(CONFD_GET_TAG_NS(&val[i]));
>             root.children = node;
>             pnode = &root;
>         }
>         switch (CONFD_GET_TAG_VALUE(&val[i])->type) {
>         case C_XMLBEGIN:
> 	    tmp = ""; //begin;
>             if (pnode != NULL)
>                 pnode = confd_find_cs_node_child(pnode, val[i].tag);
>             break;
>         case C_XMLBEGINDEL:
> 	    tmp = ""; //begin-deleted
>             if (pnode != NULL)
>                 pnode = confd_find_cs_node_child(pnode, val[i].tag);
>             break;
>         case C_XMLEND:
> 	    tmp = ""; //end;
>             if (pnode != NULL)
>                 pnode = pnode->parent;
>             break;
>         case C_XMLTAG:
> 	  tmp = ""; //created;
>             break;
>         case C_NOEXISTS:
> 	    tmp = ""; //deleted;
>             break;
>         default:
>             if (pnode == NULL ||
>                 (node = confd_find_cs_node_child(pnode, val[i].tag)) == NULL ||
>                 confd_val2str(node->info.type, CONFD_GET_TAG_VALUE(&val[i]),
>                               tmpbuf, sizeof(tmpbuf)) == CONFD_ERR) {
>                 confd_pp_value(tmpbuf, sizeof(tmpbuf),
>                                CONFD_GET_TAG_VALUE(&val[i]));
>             }
>             tmp = tmpbuf; //merge
>         }
>         switch (CONFD_GET_TAG_VALUE(&val[i])->type) {
>         case C_XMLBEGIN:
>         case C_XMLBEGINDEL:
> 	    printf("%*s<%s> %s\n", indent, "",
>                confd_hash2str(CONFD_GET_TAG_TAG(&val[i])), tmp);
> 	    indent += 2;
>             break;
> 	case C_XMLEND:
> 	    indent -= 2;
>             printf("%*s</%s> %s\n", indent, "",
>                confd_hash2str(CONFD_GET_TAG_TAG(&val[i])), tmp);
> 	    break;
>         default:
> 	    printf("%*s<%s>%s</%s>\n", indent, "",
> 		   confd_hash2str(CONFD_GET_TAG_TAG(&val[i])), tmp, confd_hash2str(CONFD_GET_TAG_TAG(&val[i])));
>             break;
>         }
>     }
> }

Quick demo:

In terminal window1 we set some new configuration using the netconf-console by issuing a NETCONF <edit-config> request:

$ netconf-console -i

* Enter a NETCONF operation, end with an empty line
 <edit-config>
    <target>
      <running/>
    </target>
    <config>
      <dhcp xmlns="http://tail-f.com/ns/example/dhcpd"
            xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
        <defaultLeaseTime nc:operation="merge">
          PT30M
        </defaultLeaseTime>
        <SubNets>
          <subNet nc:operation="create">
            <net>192.168.128.0</net>
            <mask>255.255.255.0</mask>
            <range>
              <lowAddr>192.168.128.60</lowAddr>
              <hiAddr>192.168.128.89</hiAddr>
            </range>
          </subNet>
        </SubNets>
      </dhcp>
    </config>
  </edit-config>

<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="2">
  <ok/>
</rpc-reply>

In terminal window2 the ConfD tag value array result from the cdb_get_modifications() is printed in XML pretty:

...
Modifications in pretty printed XML:
<defaultLeaseTime>PT30M0S.0</defaultLeaseTime>
<SubNets> 
  <subNet> 
    <net>192.168.128.0</net>
    <mask>255.255.255.0</mask>
    <maxLeaseTime>PT7200S.0</maxLeaseTime>
    <range> 
      <dynamicBootP>false</dynamicBootP>
      <lowAddr>192.168.128.60</lowAddr>
      <hiAddr>192.168.128.89</hiAddr>
    </range> 
  </subNet> 
</SubNets> 
…

If you for example want to add NETCONF xml tags like for example nc:operation, xmlns or xmlns:nc , just add them to the print function.
Python NETCONF example: ConfD-Demos/cdbl-sync-nc.py at master · ConfD-Developer/ConfD-Demos · GitHub

1 Like

Hello,
I am also looking something very similar. So can you please continue your previous answer and tell us how can we add nc:operation=“merge” vs “create” vs “replace” vs “modify”?
Is there a way to know which operation has happened on element/object using cdb_get_modification.

All I see is xmlbegin/xmlbegindel and end but no modify/merge/replace.
Please clarify what to do if the output we want is similar to the applied input so that we can apply this output to some remote cdb as edit-config.
Thanks,
-gopal

Hi Gopal,

Note that it is the new configuration vs the previous old configuration, i.e. the configuration changes that his presented in the tag value array returned by the cdb_get_modifications() API call. Not the NETCONF operations that was used to set the resulting new configuration.

Hence, in your case when you create a XML representation from the tag value array returned by cdb_get_modifications(), it would be sufficient if you use the “merge” operation for the changed/new configuration indicated by C_XMLBEGIN / C_XMLEND and the “remove” operation (or “delete” operation if you want an error message if you are deleting something that does not exist) on elements as indicated C_NOEXISTS and C_XMLBEGINDEL / C_XMLEND.

Hi Cohult,

I am using cdb_diff_iterate API for getting the difference of old and new data .
How i can convert the same to xml format ? Because for cdb_diff_iterate we will get value in confd_value_t struct not in confd_tag_value_t

How we can modify above print_modifications_xml_pretty to handle confd_value_t struct

Thanks,
Raja

Perhaps something like:

struct confd_cs_node *cs_node = confd_cs_node_cd(NULL, kp);
u_int32_t tag = cs_node->tag 

…and there’s your tag to go with the value.

thanks Cohult, we will try this and let you know update.

-Raja

1 Like

I would like to get the entire CDB state and generate XML out of it.
Right now, the only way I’m aware of is to have an empty CDB, run edit-config while having an active CDB subscriber that can run the above XML generation code and generate XML.
Is there a way to generate XML of the entire CDB state w/o any subscription and edit-config command?

thanks!
Kobi

Hi Kobi,

You can use the confd_load utility included in the ConfD distribution to do this.

To save all of CDB’s config data in XML format: confd_load -F x > config-data.xml

To save both config and oper data from CDB in XML format: confd_load -F x -o > all-data.xml

You can find detailed information on confd_load in ConfD man-pages, vol. 1, under confd_load.

Wai

Thank you Wai.

I’d like to run it as part of the C code.

In other words, my device comes up, the CDB is already populated with data, and I’d like to read all data and using the above XML function listed in the beginning of the threads. This way I have XML string that represent the entire CDB data.

Right now, the only way for me to use this function is when a notification is received and I can generate XML out of the modifications.
Can I have something like "confd_load into memory" and then call the XML string generate function with confd_tag_value_t ?

thank you again,

Take a look at the maapi_save_config( ) API call and see if it meets your needs.

The confd_load utility comes with source code under the $CONFD_DIR/src/confd/tools directory.

thank you Wai. This is useful. I think dp/generic_perf/src/maapi-save.c is what I’m looking for.
I can save to XML and read from the socket into a buf which will include my (pretty) XML.

I’ll try it out.
Thanks!
Kobi

Hi Wai,

It worked as expected. thank you.
Can you please point me to the documentation that explains the different between MAAPI_CONFIG_OPER_ONLY and other CONFID types? Not sure I completely understanding the differences.

Thank you again!
Kobi

The man page for maapi_save_config() can be found in vol. 3 of the ConfD man-pages under confd_lib_maapi which is at the back of the ConfD UG.

MAAPI_CONFIG_OPER_ONLY tells ConfD to include only operational data, and ancestors to operational data nodes, in the dump.

1 Like

Hi Wai,

I think I was looking for this:
http://66.218.245.39/doc/html/ch06.html

In this chapter, we show how to write instrumentation code for read-only operational and statistics data. … Operational data is typically not kept in a database but read at runtime by instrumentation functions.

The reason I was asking about what is exactly operational data is because in my investigation, I initially set the flags to be PRETTY_XML | OPERATIONAL_ONLY and did not see what I was looking for in the output XML.

Removing OPERATIONAL_ONLY provided me the expected output.

Now I realize that OPERATIONAL is for transient, KPIs and similar type of data.

Thanks!