FORGE Course

Full Height |  Two columns |  Parts | 

transAPI framework

transAPI framework Apostolos Palladinos 12/05/2016 English
transAPI framework

In this course we are going to describe the transAPI framework which is useful for advanced NETCONF operations such as notifications and rpcs through the application which uses that framework. In addition, there is a potential of handling the state data of a NETCONF server through specific functions of this framework. The general frame of this framework is to react with a NETCONF server without using a NETCONF client, but through transAPI applications. Finally, via this protocol we can make function calls when:

  1. A specific element is affected in NETCONF's configuration file,i.e. modified , removed or added.
  2. A rpc is received by the NETCONF server.
  3. A custom file is affected, i.e. modified.
  4. We call <get> operation and then we can affect configuration's state data as mentioned before.

A deeper analysis about the above four cases can be seen below, but firstly there is a mention of the basic functions of the libnetconf library which is the library that our NETCONF server is builted on. Libnetconf is a c library and so the code of the applications that the network managers are going to develop are written in c. The basic functions are the following:

  1. int nc_init ( int flags): This function initializes libnetconf for a specific system usage. This initialization is common for all the processes.
  2. int nc_close(void): Releashing of libnetconf's resources.
  3. nc_rpc* nc_rpc_commit( void ): Creates a <commit> NETCONF rpc message.
  4. nc_rpc* nc_rpc_copyconfig (NC_DATASTORE source, NC_DATASTORE target): Creates a <copy-config> NETCONF rpc message.
  5. nc_rpc* nc_rpc_deleteconfig (NC_DATASTORE target): Creates a <delete-config> NETCONF rpc message.
  6. nc_rpc* nc_rpc_discardchanges ( void ): Creates  a <discard-changes> NETCONF rpc message.
  7. nc_rpc* nc_rpc_editconfig ( NC_DATASTORE target, NC_DATASTORE source, NC_EDIT_DEFOP_TYPE default_operation, NC_EDIT_ERROPT_TYPE error_option,NC_EDIT_TESTOPT_TYPE test_option): Creates a <edit-config> NETCONF rpc message.
  8. nc_rpc* nc_rpc_get( const struct nc_filter *  filter): Creates a <get> NETCONF rpc message.
  9. char* nc_rpc_get_op_content (  const nc_rpc* rpc): Gets the content of the operation specification from the given rpc.
  10. nc_rpc* nc_rpc_getconfig ( NC_DATASTORE source,  const struct nc_filter* filter): Creates a <get-config> NETCONF rpc message.
  11.  nc_rpc* nc_rpc_killsession( const char* kill_sid): Creates a <kill-session> NETCONF rpc message.
  12. nc_rpc* nc_rpc_lock (NC_DATASTORE target): Creates a <lock> NETCONF rpc message.
  13. nc_rpc* nc_rpc_unlock (NC_DATASTORE target): Creates an <unlock> NETCONF rpc message.
  14. nc_rpc* nc_rpc_validate (NC_DATASTORE source): Creates a <validate> NETCONF rpc message.
  15. NC_MSG_TYPE nc_session_send_recv(struct nc_session* session,nc_rpc* rpc,nc_reply** reply): Sends a <rpc> and receives a <rpc-reply> via the specified NETCONF session.
  16. nc_rpc* nc_rpc_subscribe( const char* stream, const struct nc_filter * filter, const time_t * start, const time_t * stop): Creates a <create-subsciption> NETCONF rpc message.
  17. int ncntf_event_new(time_t etime, NCNTF_EVENT event, specific_parameters: Stores a new event in the specified stream. Argument "specific_parameters" is specific for different events.
  18. int ncntf_stream_new ( const char* name ,const char* desc, int replay): Creates a new NETCONF event stream.

Returning to the function calls section we are going to analyze each and every of them:

  1. Those functions accept as an argument an XMLDIFF operation. This operation can be one of the below:
    • XMLDIFF_ADD = The node was added.
    • XMLDIFF_REM = The node was removed.
    • XMLDIFF_MOD = Node's content was changed.

    An example and an explanation is given below:

    int callback_some_leaf(void **data, XMLDIFF_OP op, xmlNodePtr old_node, xmlNodePtr new_node, struct nc_err **error) {
    if (op & XMLDIFF_MOD) { //changed value
    // code
    else if (op & XMLDIFF_REM) { // leaf removed
    // code
    else if (op & XMLDIFF_ADD) { //leaf added
    // code
    else {
    *error = nc_err_new(NC_ERR_OP_FAILED);
     nc_err_set(error, NC_PARAM_MSG, "Invalid event for leaf node /some/leaf.");
    return(EXIT_FAILURE);
    }
    return(EXIT_SUCCESS);
    }

    struct transapi_data_callbacks clbks =  {
        .callbacks_count = 1, //number of callback functions
        .data = NULL,
        .callbacks = {     //IMPORTANT->for more properties generally we put {} and comma seperated arguments.
            {.path = "/t:system/t:login/t:user/t:name", .func = callback_t_system_t_login_t_user_t_name} 
        }
    };
    Let's consider the configuration that was given to the Introduction course. The "some_leaf" in function's prototype above can be replaced by <name> in the following way: int callback_t_system_t_login_t_user_t_name( ... ). The t element before each element in xml hierarchy is the namespace of that element which is defined in YANG laguage as "prefix". Its usage before each element in hierarchy is mandatory. The "xmlNodePtr old_node" argument contains the configuration before the executed operation. On the other hand the "xmlNodePtr new_node" is the new configuration after the execution of the operation. Additionally, the "struct nc_err **error" includes  the errors that happened during the execution of that operation, if exist. We use the "struct transapi_data_callbacks clbks" for function declaration. More details can be seen at the comments at the code. Concluding, every time the above specific element of the NETCONF configuration accepts one of the previously mentioned operations this function is called and the network administrator can compose his specific functionality depending on the operation that happened.
  2. Using the same logic we can make function calls when a rpc is arriving at NETCONF server. Two function are available:
    • rpc_initialize: Makes an initialization.
    • rpc_run: Executes the desirable functionality.

    In addition, the "struct transapi_rpc_callbacks rpc_clbks" declares rpc callbck functions. More details are described at the comments of the example.This example can be seen below:

    nc_reply *rpc_initialize(xmlNodePtr input) {
    xmlNodePtr tape_content = get_rpc_node("tape-content", input);
    struct nc_err* e = NULL;
    free(tm_tape);
    tm_state = 0;
    tm_tape = tm_head = (char*)xmlNodeGetContent(tape_content);
    if (tm_tape == NULL) {
    tm_tape = tm_head = strdup("");
    }
    tm_tape_len = strlen(tm_tape) + 1;
    pthread_mutex_unlock(&tm_run_lock);
    return nc_reply_ok();
    }

    nc_reply *rpc_run(xmlNodePtr input) {
    pthread_t tm_run_thread;
    struct nc_err *e;
    char *emsg = NULL;
    int r;
    if ((r = pthread_create(&tm_run_thread, NULL, tm_run, NULL)) != 0) {
    e = nc_err_new(NC_ERR_OP_FAILED);
    asprintf(&emsg, "Unable to start turing machine thread (%s)", strerror(r));
    nc_err_set(e,NC_ERR_PARAM_MSG,emsg);
    free(emsg);
    return  nc_reply_error(e);
    }
    pthread_detach(tm_run_thread);
    return nc_reply_ok();
    }

    struct transapi_rpc_callbacks rpc_clbks = {
        .callbacks_count = 2, //number of callback functions
        .callbacks = {
            {.name="initialize", .func=rpc_initialize},
            {.name="run", .func=rpc_run}
        }
    };

  3. We can utilize callbacks, also, for files when they get modified. An example function and how to declare it as a callback can be seen below:

    int example_callback(const char *filepath, xmlDocPtr *running, int* execflag) {
    // do nothing
    *running = NULL;
    *execflag = 0;
    return EXIT_SUCCESS;
    }

    struct transapi_file_callbacks file_clbks = {
    .callbacks_count = 1, //number of callback functions
    .callbacks = {{.path = "/etc/turing.conf", .func = example_callback}}
    };
    The "example_callback" is the callback function for the file that exists in path "const char* filepath". Finally, the "xmlDocPtr *running" contains the configuration that exists in that file after the execution of the modification.
  4. Finally, we can call the "xmlDocPtr get_state_data(xmlDocPtr model, xmlDocPtr running, struct nc_err **err)" function in order to control the state data of the configuration. An example can be seen below:

    xmlDocPtr get_state_data(xmlDocPtr model, xmlDocPtr running, struct nc_err **err)
    {
        char *data = NULL, symbol[2];
        xmlDocPtr doc = NULL;
        xmlNodePtr root, tape, cell;
        xmlNsPtr ns;
        uint64_t i;

        /* create XML doc with root */
        doc = xmlNewDoc(BAD_CAST "1.0");
        xmlDocSetRootElement(doc, root = xmlNewDocNode(doc, NULL, BAD_CAST "turing-machine", NULL));
        ns = xmlNewNs(root, BAD_CAST "http://example.net/turing-machine", NULL);
        xmlSetNs(root, ns);

        /* lock internal structures */
        pthread_mutex_lock(&tm_data_lock);

        /* add leaf */
        asprintf(&data, "%d", tm_state);
        xmlNewChild(root, root->ns, BAD_CAST "state", BAD_CAST data);
        free(data);
        data = NULL;

        /* add leaf */
        asprintf(&data, "%ld", tm_head - tm_tape);
        xmlNewChild(root, root->ns, BAD_CAST "head-position", BAD_CAST data);
        free(data);
        data = NULL;

        /* add container */
        tape = xmlNewChild(root, root->ns, BAD_CAST "tape", NULL);
        if (tm_tape == NULL) {
            /* unlock internal structures */
            pthread_mutex_unlock(&tm_data_lock);
            return doc;
        }

        for (i = 0, symbol[1] = 'NULL'; i < tm_tape_len; i++) {
            /* skip cells with empty value */
            if (tm_tape[i] == 'NULL') {
                continue;
            }

            /* add list items */
            cell = xmlNewChild(tape, tape->ns, BAD_CAST "cell", NULL);

            asprintf(&data, "%ld", i);
            xmlNewChild(cell, cell->ns, BAD_CAST "coord", BAD_CAST data);
            free(data);
            data = NULL;

            symbol[0] = tm_tape[i];
            xmlNewChild(cell, cell->ns, BAD_CAST "symbol", BAD_CAST symbol);
        }

        /* unlock internal structures */
        pthread_mutex_unlock(&tm_data_lock);

        /* return turing machine state information */
        return doc;
    }
    In this example we use the libxml library in order to control the state data. Generally, the libxml library is proposed in order to control the xml configuration data.
    To create all the necessary files for validation, configuration and the transAPI module with a predifined structure from the desired YANG model we can use lnctool --model ./test.yang transapi --paths ./paths_file. In order to configure NETCONF server with the transAPI modules, network administrator must generate the path file, execute the proper commands in netopeer-manager module( netopeer is the NETCONF server that is used ) and finally execute some more necessary commands. All those can be seen below:
    Generate path file( paths_file ): 
        1.namespace's_prefix=namespace ( for example t=www.test.com, this is used also for the second command below ).
        2./t:system/t:login/t:user/t:name.
    Generate code:

    lnctool –model ./yang_model transapi –paths ./paths_file 

    Configure it with netopeer-server:

    1. Execution of the following commands in the same folder of the produced files:
      • sudo autoreconf --force --install.
      • sudo ./configure.
      • sudo make.
      • sudo make install.
    2. Execution of the following command in netopeer-manager:
      • sudo netopeer-manager add --name test –model path_to_yin_model --datastore path_to_configuration(it is custom) --transapi /usr/local/lib/test.so( name of the .so model's library which is stored at /usr/local/lib path-in this case is test.so)


    One of the most important features of the transAPI framework is the potential of sending notifications to the streams in which the NETCONF clients subscribe. By notifications NETCONF clients are warned by the NETCONF server that a change happened to the configuration or that he should execute the proper configuration commands to deal with a situation in the adminstrated network. Below there is an example for better understanding of the basic notification commands and also a concise explanation of some of the libxml functions in order to handle the xml based old and new configuration:

    int callback_t_system_t_login_t_message(void **data, XMLDIFF_OP op, xmlNodePtr old_node, xmlNodePtr new_node, struct nc_err **error) {
      char content[2048];
      char* oldcont;
      char* newcont;
      int cont;
      oldcont = (char*)xmlNodeGetContent(old_node);
      cont = atoi((char*)xmlNodeGetContent(new_node));
     if( cont == 100){
       ncntf_event_new(-1, NCNTF_GENERIC, "Content equals with 100");
      }
     if(cont > 100){
      ncntf_event_new(-1, NCNTF_GENERIC, "Content is bigger than 100");
     }
     if(strcmp("Welcome!",oldcont) == 0){
    ncntf_event_new(-1, NCNTF_GENERIC, "Node added");
     }
      sprintf(content,"%s %d",oldcont,cont);
      ncntf_event_new(-1, NCNTF_GENERIC, content);        
      if (op & XMLDIFF_MOD) {
        // change configured value
      } else if (op & XMLDIFF_REM) {
        // leaf removed (disable service, close port, ...)
      } else if (op & XMLDIFF_ADD) {
      } else {
        *error = nc_err_new(NC_ERR_OP_FAILED);
        nc_err_set(error, NC_ERR_PARAM_MSG, "Invalid event for leaf node /some/leaf.");
        return(EXIT_FAILURE);
      }
      return(EXIT_SUCCESS);
    }
    By using "ncntf_event_new(-1, NCNTF_GENERIC, "<sendn xmnls="urn:test:xml:ns:yang:1"><message>Node added</message></sendn>")" command for example we send at the default stream of the server, which is NETCONF, at current time( value -1 ), a generic notification that is the following message "<sendn xmnls="urn:test:xml:ns:yang:1"><message>Node added</message></sendn>". This notification must follow the rules of the YANG model otherwise the NETCONF server will reject it. In addition, if the network administrator created a new stream and sent a notification that belongs in that specific stream it will also be sent to the NETCONF stream as this stream is the global stream. In order to read the old or the new configuration value of the specific element for which the callback function is called, network administrator must use the "xmlNodeGetContent" function of the libxml library. For example to get the old configuration value of the element "message" we use the command "oldcont = (char*)xmlNodeGetContent(old_node);".

    Ending this course part we would like to mention that an authorized user that wants to access and modify directly the NETCONF configuration, there is a potential to do it without using either a NETCONF client or transAPI-libnetconf, through programming languages like Java which supports file opening and management.