Using ONTAPI™ programming interfaces from C/C++

          

This document talks about the ONTAPI API mechanism and shows you how to use it from C and C++.
 

Introduction


 
The ONTAPI API mechanism satisfies a number of requirements for Network Appliance. First, it allows for remote management of Netapp appliances when port 80 is the only available port (this is sometimes the case for NetCache machines). Second, it is transport-independent; either DCE/RPC or HTTP may be used as the transport for any call. Third, it is simple. It consists of a small set of primitives that can be ported relatively easily from one language to another. 
This paper is concerned solely with using ONTAPI APIs from C and C++. 


API concepts and conventions


  Both the client and server side use similar API functions and concepts to manipulate elements, the values used for both requests and results. Elements are so named because a subset of XML is used to package up the query and return results. An element has a name, child elements, and contents (which can be a string, integer, or array of elements). API functions that manipulate these things come in several categories, identified by the prefix of their name. The main ones are: 
 
na_elem_* These functions manage elements, get and set contents, free memory associated with them and so on.
na_child_* These functions manipulate element children, which are just other elements nested within an element. Elements can be arbitrarily nested, but in practice two levels of them is enough of a headache to keep one's wilder ambitions in check.
na_server_*
These functions manage server connections, including the transport style and the user associated with them.
na_results_*
These functions manipulate results elements (which are children of an element returned by a ONTAPI call) to get error numbers and messages.

Several opaque types are manipulated by these functions
 

na_elem_t*  The representation of an RPC invocation or response. The na_elem_* routines add input parameters and extract output parameters from an instance of an na_elem_t.
na_server_t*  Server context for a NetApp filer or NetCache.
na_elem_iter_t  A structure managing iteration over possibly unbounded data.

The core routines are presented next. Following that is a recap, in C only, of the getting-started tutorial.



The ONTAPI core APIs

 
The core APIs are those necessary to actually make ONTAPI API calls to a filer. They are, in essence, the binding glue, and perform the same function that RPC stub code does, but in an explicit way that doesn't require IDL files.

The first group of core APIs deals with startup and memory management


void        na_elem_free(na_elem_t* elt);
Free up all resources used by the I/O element elt.
na_elem_t*  na_elem_new(const char* name);
Allocate resources for a new I/O element. Returns NULL if unsuccessful.
void        na_free(const void* ptr);
Free memory for a given chunk of memory allocated by the system.
void        na_shutdown(void);
Cleanup of system resources.
int         na_startup(char* errbuff,
                       int errbuffsize);
One-time startup to prepare for API and network things. Returns TRUE on success.


The second group manages I/O elements. These have names and values, and can be arbitrarily nested.


na_elem_t*  na_child_add(na_elem_t* elt,
                         na_elem_t* childelt);

Add the nested element childelt to elt (nested elements are technically unordered and accessed by name, but this routine adds it to the end of the nested element list). Return childelt , or NULL on failure.
na_elem_t*  na_child_add_int(na_elem_t * e, const             char * name, int value);
Adds the value value to name of element e. Returns 0 if there is a memory allocation or argument error.
na_elem_t*  na_child_add_string(na_elem_t* elt
                                const char* name,
                                const char* value);

Create a new element with key name and value value, and add it to the nested element list of elt .
na_elem_t* na_child_add_string_encrypted(
                                na_elem_t* elt,
                                const char* name,
                                const char* value,
                                const char* key);

Create a new element with key name and value value, encrypt the data contained in the value with encryption key key, and add the new element as a nested element of elt .
NULL is the only value that should be passed as fourth argument.
na_elem_t* na_child_add_uint32(na_elem_t* elt,
                               const char* name,
                               uint32_t value);

Create a new element with key name and integer value value, and add it to the nested element list of elt .  Like na_child_int(), this is a convenience routine. On the wire, all data travels as strings.
bool_t      na_child_get_bool(na_elem_t * e,
                   const char * name, bool_t deflt);

Returns the boolean value of the contents of the element.
int         na_child_get_int(na_elem_t *,
                           const char *, int);

Return the integer value of the contents of an element.
const char* na_child_get_string(na_elem_t* elt,
                                const char* s);

Return a pointer to the data in the nested element indexed by s. The data is located in a static data area, so you must copy it to allocated memory if you need to keep it around longer than the scope of one ONTAPI API call.
char*       na_child_get_string_encrypted(
                                na_elem_t* elt,
                                const char* name,
                                const char* key);

Get the nested element indexed by name , decrypt it with key, and return a pointer to the decrypted data. The system allocates memory for the decrypted data, so you must explicitly free it with na_free() when you are done with it.
int        na_child_int(na_elem_t* elt,
                        const char* name,
                        int value);

Create a new element with key name and integer value value, and add it to the nested element list of elt .  This is a convenience routine. On the wire, all data travels as strings.
na_elem_iter_t  na_child_iterator(na_elem_t* elt);
Set up an iterator for iterating over the children of element elt. You'll call na_iterator_next() on this iterator to walk through the nested element list.
na_elem_t*  na_elem_child(na_elem_t* elt,
                          const char* eltname);

Returns the element named eltname that is nested in the element elt, or NULL if one is not found.
const char* na_elem_get_content(na_elem_t * e);
Gets the contents of an element. If the contents of element is NULL, it returns "".
na_elem_t*  na_zapi_get_elem_from_raw_xmlinput(
                                  char * val);

Retrieves api element from raw XML input passed.
const char* na_elem_get_name(na_elem_t * e);
Gets the name of an element. If the name of element is NULL, it returns "".
int         na_elem_has_children(na_elem_t* elt);
True if the element elthas any nested elements within it
int         na_elem_set_content(na_elem_t* elt,
                                const char* value);

Directly set the contents of an element. This low-level routine is mostly used internally.
void        na_encrypt_basic(const char *key,
                             const char* input,
                             char * output,
                             int nbytes);

Takes an input value (a series of bytes) and produces an encrypted version of it.
ASSUMPTIONS:
- key must be 16 chars
- output space provided is at least as long as the input.
na_elem_t*  na_iterator_next(na_elem_iter_t* tag);
Given a tag returned by na_child_iterator(elt), call this function repeatedly to iterate through all children of elt


The third group of core APIs manages server connections, authentication, and sending and receiving of API messages
int          na_server_adminuser(na_server_t* srv,
                                 const char* login,
                                 const char* pwd);

Set the username and password for a NA_STYLE_LOGIN_PASSWORD connection srv. The user you specify must have admin rights on the filer.
int          na_server_close(na_server_t* svr);
Close a connection opened by na_server_open() . You can have multiple connections open at once, but must match each open with a close.
na_debug_style_t  na_server_get_debugstyle(
                            na_server_t * s);

Get debug style on the server.
int          na_server_get_host_lookup(
                            na_server_t *s);

The API returns TRUE or FALSE, depending on whether the given ZAPI server object is set to do user lookups on the host name.
int          na_server_get_port(na_server_t * s);
The API returns the port number on which the server is listening.
int          na_server_get_raw_xml_output(na_server_t * s);
The API returns the raw XML output from the server. The debug style on the server should be set to NA_PRINT_DONT_PARSE before using this routine.
na_style_t   na_server_get_style(na_server_t* srv);
Get the style of the connection stored in srv.
int          na_server_get_timeout(na_server_t *s);
The API returns the time out value of the server.
na_server_transport_t  na_server_get_transport_type(
                          na_server_t* srv)

Returns transport type used for this connection.
na_elem_t*   na_server_invoke(na_server_t* srv,
                              const char* api, ...);

Syntactic sugar for na_server_invoke_elem(), below. Pass in name/value pairs, and terminate the list with NULL (there must always be an even number of arguments to this function). Only char data type can be passed in name/value pairs. The needed elements are all assembled and sent to the server. The return value is an element containing the server's reply. The sample code in the SDK has several examples of using this routine.
na_elem_t*   na_server_invoke_elem(na_server_t* srv,
                                   na_elem_t* elt);

Sometimes, e.g. when processing optional inputs from a dialog box, it makes the most sense to construct an element chain by yourself instead of using na_server_invoke(). Use this routine to send the completed element chain to the server and get the response. After the call, free elt with na_elem_free().
na_server_t* na_server_open(const char* server,
                            int major,
                            int minor);

Open a connection to the server named server , and return a pointer to an opaque type for managing the connection.
void         na_server_set_debugstyle(
                            na_server_t * s,
                            na_debug_style_t style);

Set debug style on the server for low-level debugging.
int          na_server_set_host_lookup(
                            na_server_t *s,
                            int host_lookup_choice);

The API sets the host lookup behavior for the given ZAPI server object to the given value, but only if it is using NA_STYLE_ZSM. If successful, it returns TRUE Otherwise, it returns FALSE.
int          na_server_set_port(na_server_t* svr,
                            int port);

Overrides default port for this server.     By default, na_server_set_transport_type() sets the port used. na_server_set_port() is required only when default port used needs to be overridden.
int          na_server_set_timeout(na_server_t* srv,
                              int timeout);

Sets a timeout on the server when making connection or when reading/writing data from a socket. The timeout parameter is in number of seconds. If it's non-zero the server will use non blocking socket I/O otherwise blocking I/O will be used. Blocking I/O is default behaviour.
int         na_server_set_transport_type(
               na_server_t* svr,
               na_server_transport_t transport,
               const union zfd_setopt *transportarg);

Overrides the default transport type. Valid transport values are : NA_SERVER_TRANSPORT_HTTP, NA_SERVER_TRANSPORT_HTTPS.         The default transport type is NA_SERVER_TRANSPORT_HTTP. If you want to use OpenSSL for a secure transport, just specify NA_SERVER_TRANSPORT_HTTPS as the transport value. transportarg is set to 0 for both HTTP, HTTPS transport.
int         na_server_set_vfiler(na_server_t *s,
                        const char* vfilerserver);

The API sets the vfiler name. Return value is TRUE in case of success and FALSE if the ontapi version supplied is lower than 1.7.
void        na_server_style(na_server_t* svr,
                             na_style_t style);

Set the communication style of the connection. If you specify NA_STYLE_LOGIN_PASSWORD, you must furnish an admin username and password with na_server_adminuser(). If you specify NA_STYLE_RPC, your code must be running on a Windows machine, so native Windows RPC-based authentication can be used.


Three routines suffice to determine the status of a request upon its return from the server:

int             na_results_errno(na_elem_t* elt);
The error number of the failure
const char*     na_results_reason(na_elem_t* elt);
A string containing [hopefully] useful information about the failure
int             na_results_status(na_elem_t* elt);
Either NA_OK or NA_FAILED, depending on the success of the requested operation


Finally, it's sometimes useful to print out an element string. This formatting routine helps with that.

char*  na_elem_sprintf(na_elem_t* elt);
Allocate a buffer, and pretty-print the elt chain into it. You must free the returned buffer using na_free() after you're done with it.


Client Setup


  The first thing a client program must do is initialize the server context:

#include <netapp_api.h>
...
        char    err[256];

        if (!na_startup(err, sizeof(err))) {
                fprintf(stderr, "Error in na_startup: %s\n", err);
                exit(-1);
        }
 




Next you identify which server (filer or netcache) you want to talk to, and the version of the API you expect. This setup gives you a pointer to a na_server_t*  value that is a "server context" used for subsequent API invocations:
 
        na_server_t*    s;
        na_elem_t*      out;

        /*
         * Set up the server you want to talk to, and 
         * which version of the API you want.  In this 
         * case we'd like version 1.1 (major.minor) or higher. 
         */ 
        s = na_server_open("jetfighter",1,1); 

        /*
         * The verification of the version is only done when 
         * an API is invoked, so if you want to have an explicit 
         * up-front check to make sure the version you've specified 
         * is available on the server, you need to invoke an 
         * innocuous API (e.g. get the ONTAP version string), 
         * and check for the "unsupported version" error. 
         */
        out = na_server_invoke(s, "system-get-version", NULL);
        if (na_results_status(out) != NA_OK)
                fprintf(stderr, "Version %d.%d is unsupported: %s\n", 1, 1, r);
                exit(-2);
        }
        na_free(out);

        /*
         * Call API functions that use the server context, 
         * and then terminate the context (freeing memory, etc). 
         */ 

        na_server_close(s);




Next you set the transport parameters. If you're writing a Windows application, you'll normally want to use Windows DCE/RPC, which furnishes native Windows authentication and authorization. If you need to use HTTP, on the other hand, you must furnish a username and password for use by the server context:

        bool            use_rpc = 1;            /* This should be the default for Windows clients */
        char            user[] = "root";        /* Change this to an actual admin user name */
        char            passwd[] = "passwd";    /* Change this to the actual admin password, or get
                                                   it from a prompt or dialog box.  */
        /*

         * Set the transport style.  Windows users will generally
         * want to use NA_STYLE_RPC, as that provides native Windows
         * authentication and authorization for ONTAPI calls.
         */
        if ( use_rpc) {
                na_server_style(s, NA_STYLE_RPC);
        } 
        else {
                /*
                 * This establishes the administrative user login name
                 * and password to be used to authenticate.
                 */
                na_server_style(s, NA_STYLE_LOGIN_PASSWORD);
                na_server_adminuser(s, user, passwd);
        }
      





Client Example #1 - The ONTAPI version of Hello World


  In the ONTAPI version of HelloWorld, we set up a session and transport, as above, then get the ONTAP version number and print it out.

When you make an API call you get back a pointer to an instance of na_elem_t , and retreive status and values from it with na_child_* primitives.  When you are done with the result, a single call to na_free() frees everything associated with the returned element. Onward... 

#include <stdio.h>
#include <netapp_api.h>

int main(int argc, char* argv[])
{
        na_server_t*    s;
        na_elem_t*      out;
        char            err[256];
        int             use_rpc = 0;            /* This should be the default for Windows clients */
        char            user[] = "root";        /* Change this to an actual admin user name */
        char            passwd[] = "passwd";    /* Change this to the actual admin password */
 
        if (!na_startup(err, sizeof(err))) {
                fprintf(stderr, "Error in na_startup: %s\n", err);
                return -1;
        }
        s = na_server_open("jetfighter", 1, 1);

        if ( use_rpc) {
                na_server_style(s, NA_STYLE_RPC);
        }
        else {
                na_server_style(s, NA_STYLE_LOGIN_PASSWORD);
                na_server_adminuser(s, user, passwd);
        }

        out = na_server_invoke(s, "system-get-version", NULL);
        if (na_results_status(out) != NA_OK) {
                printf("Error %d: %s\n", na_results_errno(out), na_results_reason(out));
                return -2;
        }     
        else {
                const char *v = na_child_string(out, "version");
                printf("Hello World!  The version of ONTAP is: %s\n", v);
        }
        na_elem_free(out);
        return 0;
}
   

Note: values pulled out of the result (as by na_child_string() in this example) point to memory that is freed when you call na_free() to free the result. You need to copy the value elsewhere if you need to make use of it later.

For this example to compile at present, you have to have the following things in place in your environment:

$ONTAPI_ROOT/include
In your include path. Visual Studio users should add this in the C/C++/Preprocessor pane. Solaris and cl users can specify it with a -I directive, or copy the include directory tree from here to a suitable location on your system.
$ONTAPI_ROOT/lib/<architecture>
In your library path. Visual Studio users should add this in the Link/Input pane. Solaris and cl users can specify it with a -L directive, or copy the libraries from here into a suitable location on your system.
libnetapp.lib, libxml.lib, libadt.lib, libpthread.lib, ntapadmin.lib (and in Windows only, ws2_32.lib) Linked into your application. Visual Studio users should add them in the Link/General options pane. Solaris users should specifiy -lnetapp -lxml ... on the command line
ntapadmin.dll, pthread.dll (Windows users only)
installed into the %systemroot%/system32 directory on the machine that will be running your application

The development team is working on simplifying these requirements, so they are likely to change in the official release of this SDK.



Client example #2  Taking a named snapshot


  This example takes filer, volume and snapshot parameters from the command line, uses them to create a named snapshot on the given volume, and does error checking to report the success or failure of the command.


  #include <stdio.h>
#include <netapp_api.h>

int main(int argc, char* argv[])
{
        na_server_t*     s;
        na_elem_t*       out;
        char             err[256];
        int              use_rpc = 0;            /* This should be the default for Windows clients */
        char             user[] = "root";        /* Change this to an actual admin user name */
        char             passwd[] = "passwd";    /* Change this to the actual admin password */
        char*            filername;
        char*            volname;
        char*            snapshotname;

        if (argc < 4) {
                fprintf(stderr, "Usage: %s <filername> <volname> <newsnapshotname>\n", argv[0]);
                return -1;
        }
        else {
                filername = argv[1];
                volname = argv[2];
                snapshotname = argv[3];
        }

        if (!na_startup(err, sizeof(err))) {
                fprintf(stderr, "Error in na_startup: %s\n", err);
                return -2;
        }
        s = na_server_open(filername, 1, 1);

        if ( use_rpc) {
                na_server_style(s, NA_STYLE_RPC);
        }
        else {
                na_server_style(s, NA_STYLE_LOGIN_PASSWORD);
                na_server_adminuser(s, user, passwd);
        }
        out = na_server_invoke(s, "system-get-version", NULL);
        if (na_results_status(out) != NA_OK) {
                printf("Error %d: %s\n", na_results_errno(out), na_results_reason(out));
                return -3;       
        }
        else {
                na_elem_free(out);
        }
      
        out = na_server_invoke(s, "snapshot-create",
                                "snapshot", snapshotname,
                                "volume", volname,
                                NULL);

        if (na_results_status(out) != NA_OK) {
                printf("Error %d: %s\n", na_results_errno(out), na_results_reason(out));
                return -4;      
        }
        else {
                na_elem_free(out);
        }
        return 0;
}