Table of Contents
MAAPI is a C API which provides full access to the ConfD internal transaction engine. It is used in a number of different settings.
We use MAAPI if we want to write our own management application. Using the MAAPI interface, it is for example possible to implement a custom built command line interface (CLI) or Web UI. This usage is described below.
We use MAAPI to access ConfD data inside a not yet committed transaction when we wish to implement semantic validation in C. This is fully described in Chapter 9, Semantic validation.
We use MAAPI to access ConfD data inside a not yet committed transaction when we wish to implement CLI wizards in C. Here we can invoke an external C program which can read and write, both to the executing transaction, but also interact with the CLI user. This is fully described in Chapter 16, The CLI agent.
Finally MAAPI is also used during database upgrade to access and write data to a special upgrade transaction. This is fully described in Chapter 5, CDB - The ConfD XML Database.
Thus, MAAPI is an API which contains functions that are the northbound equivalents of all the callbacks described in Chapter 7, The external database API.
A typical sequence of API calls when using MAAPI to write a management application would be
Create a user session, this is the equivalent of an established SSH connection from a NETCONF manager. It is up to the MAAPI application to authenticate users. The TCP connection from the MAAPI application to ConfD is a clear text connection.
Establish a new ConfD transaction
Issue a series of read and write operations towards the ConfD transaction
Commit or abort the transaction
MAAPI also has support for several operations that do not work immediately towards a ConfD transaction. This includes users session management, locking, and candidate manipulation.
In this section we implement a small toy CLI towards a specific data model. We start with the following YANG module:
Example 23.1. scli.yang YANG module
module scli { namespace "http://tail-f.com/test/scli"; prefix scli; import ietf-inet-types { prefix inet; } leaf foo { type string; default "fooo"; } leaf bar { type int32; default 66; } list servers { key "name"; max-elements 64; leaf name { type string; } leaf ip { type inet:ipv4-address; mandatory true; } leaf port { type inet:port-number; mandatory true; } } }
We compile this file with confdc (1) and load it into ConfD as usual. Data is kept in CDB, since there are no callpoints defined. At this point, we can manipulate the configuration data with a NETCONF manager or the ConfD built-in CLI.
What we wish to do is to write a small
custom
CLI, that understands the structure
of the XML tree using the MAAPI.
All functions in MAAPI are described in the confd_lib_maapi(3) manual page. We will use a subset of those functions in this example.
We start off with some global variables, and a trivial
main()
function.
#include "confd_lib.h" #include "confd_maapi.h" /* include .h file generated by confdc */ #include "scli.h" static int sock, th; static char *user = "admin"; static const char *groups[] = {"admin"}; static int debuglevel = CONFD_DEBUG; static char *context = "maapi"; static enum confd_dbname dbname = CONFD_RUNNING; int main(int argc, char **argv) { int c; while ((c = getopt(argc, argv, "rc")) != -1) { switch(c) { case 'r': dbname = CONFD_RUNNING; break; case 'c': dbname = CONFD_CANDIDATE; break; } } confd_init("MYNAME", stderr, debuglevel); cnct(); runcli(); }
The code in main()
sets a global variable
dbname
which will be the database we are
opening, i.e. we can choose to run this CLI either towards
the running database or towards the candidate database.
The code first calls cnct()
which creates a
MAAPI socket and connects to ConfD, following that we
run the simple CLI.
The code to connect to ConfD looks like this:
static void cnct() { struct in_addr in; struct sockaddr_in addr; struct confd_ip ip; inet_aton("127.0.0.1", &in); addr.sin_addr.s_addr = in.s_addr; addr.sin_family = AF_INET; addr.sin_port = htons(4565); if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0 ) confd_fatal("Failed to open socket\n"); if (maapi_connect(sock, (struct sockaddr*)&addr, sizeof (struct sockaddr_in)) < 0) confd_fatal("Failed to confd_connect() to confd \n"); ip.af = AF_INET; inet_aton("66.55.44.33", &ip.ip.v4); maapi_start_user_session(sock, user, context, groups, 1, &ip, CONFD_PROTO_TCP); th = maapi_start_trans(sock, dbname, CONFD_READ_WRITE); maapi_set_namespace(sock, th, scli__ns); }
The code first creates a normal socket, and then
connects to ConfD using maapi_connect()
. Following
that the code calls maapi_start_user_session()
which
creates a user session
on the socket.
We must usually have an established user session before we can issue any of the MAAPI calls. There are some calls that can be performed on the socket without an already established user session.
A user session must be
authenticated by the agent before the agent connects to
ConfD. This authentication must be performed by the agent
code, and once the agent issues the call to
maapi_start_user_session()
, the authentication
must already be done. ConfD also has an authentication system built-in
to it. The behavior of the ConfD built-in authentication system
is controlled through the AAA section in confd.conf
.
Thus, when we're implementing a proprietary agent, either we
do the authentication ourselves, or we can ask ConfD to authenticate
the user through the API function maapi_authenticate()
.
Thus we could have written:
char *groups[10]; if (maapi_authenticate(sock, user, pass, &groups[0], 10) == 1) { int i = 0; while (groups[i]) i++; maapi_start_user_session(sock, user, context, groups, i, &srcip, CONFD_PROTO_TCP);
This way we only need to obtain the clear text password in the agent and we can let ConfD perform the actual authentication as well as the group assignment. The group assignment is important, since the authorization model in ConfD hinges entirely on group membership.
Once we have established a user session, we continue
to create a transaction towards either the running database
or the candidate. This is done through the call to
maapi_start_trans()
. The call returns a transaction
handle, a th
which is subsequently used
when we read and write data. Remember that we are creating a
transaction, thus nothing is written to actual storage until
we commit the transaction. This applies also when we access
the running database.
Finally we make our first call towards the transaction, and that
is to indicate the name of the namespace we are planning to
work against. This is a hashed integer value which can be found
in a header file generated from scli.fxs
. Read more about
how to generate a header (.h) file from a .fxs file in the
confdc
(1) compiler man page.
Once we have connected, established a user session and also
created our first transaction, we enter the CLI input loop
called runcli()
.
#define DELIM " \n\r" static void runcli() { char ibuf[BUFSIZ]; struct maapi_cursor mc; int ival; char *tok; printf("#cli "); fflush(stdout); while(fgets(ibuf, BUFSIZ, stdin) != NULL) { if ((tok = strtok(ibuf, DELIM)) == NULL) { printf("#cli "); fflush(stdout); continue; } if (strcmp(tok, "show") == 0) { if (maapi_get_str_elem(sock, th, ibuf, BUFSIZ, "/foo") ==CONFD_OK) printf ("foo: %s\n", ibuf); else if (confd_errno == CONFD_ERR_NOEXISTS) printf ("foo: \n"); else confd_fatal("What \n"); if (maapi_get_int32_elem(sock, th, &ival, "/bar") == CONFD_OK) printf ("bar: %d\n", ival); else if (confd_errno == CONFD_ERR_NOEXISTS) printf ("bar: "); else confd_fatal("What \n"); maapi_init_cursor(sock, th, &mc, "/servers/server"); maapi_get_next(&mc); while (mc.n != 0) { struct in_addr ip; uint16_t port; char tmpbuf[BUFSIZ]; maapi_get_ipv4_elem(sock, th, &ip, "/servers/server{%x}/ip", &mc.keys[0]); maapi_get_u_int16_elem(sock, th, &port, "/servers/server{%x}/port", &mc.keys[0]); confd_pp_value(tmpbuf,BUFSIZ,&mc.keys[0]); printf ("server name=%s ip=%s port=%d\n", tmpbuf, inet_ntoa(ip), port); maapi_get_next(&mc); } maapi_destroy_cursor(&mc); } else if (strcmp(tok, "abort") == 0) { maapi_abort_trans(sock, th); maapi_finish_trans(sock, th); th = maapi_start_trans(sock, dbname, CONFD_READ_WRITE); maapi_set_namespace(sock, th, scli__ns); } else if (strcmp(tok, "commit") == 0) { maapi_apply_trans(sock, th, 0); maapi_finish_trans(sock, th); th = maapi_start_trans(sock, dbname, CONFD_READ_WRITE); maapi_set_namespace(sock, th, scli__ns); } else if (strcmp(tok, "create") == 0) { char *name; char *ipstr; char *portstr; if (((name = strtok(NULL, DELIM)) == NULL) || ((ipstr = strtok(NULL, DELIM)) == NULL) || ((portstr = strtok(NULL, DELIM)) == NULL)) { printf ("input error \n"); goto err; } if (maapi_create(sock, th, "/servers/server{%s}", name) != CONFD_OK) { printf ("error: %d %s \n ", confd_errno,confd_lasterr()); goto err; } if (maapi_set_elem2(sock,th,ipstr,"/servers/server{%s}/ip",name) != CONFD_OK) { printf ("error: %d %s \n ", confd_errno, confd_lasterr()); goto err; } if (maapi_set_elem2(sock,th,portstr,"/servers/server{%s}/port", name) != CONFD_OK) { printf ("error: %d %s \n ", confd_errno, confd_lasterr()); goto err; } } else if (strcmp(tok, "delete-config") == 0) { if (maapi_delete_config(sock, dbname) != CONFD_OK) printf ("error: %d, %s\n", confd_errno, confd_lasterr()); } else if (strcmp(tok, "delete") == 0) { char *name; if ((name= strtok(NULL, DELIM)) == NULL) { printf ("input error"); goto err; } if (maapi_delete(sock, th, "/servers/server{%s}", name) != CONFD_OK) { printf ("error: %s \n ", confd_lasterr()); goto err; } } else if (strcmp(tok, "candidate-reset") == 0) { if (maapi_candidate_reset(sock) != CONFD_OK) printf ("error: %d, %s\n", confd_errno, confd_lasterr()); } else if (strcmp(tok, "validate-trans") == 0) { if (maapi_validate_trans(sock, th,1,1) == CONFD_OK) { printf ("ok \n"); } else { printf ("nok: %s\n", confd_lasterr()); } } else if (strcmp(tok, "candidate-confirmed-commit") == 0) { char *istr = strtok(NULL, DELIM); if (!istr) {printf("input error\n"); goto err;} if (maapi_candidate_confirmed_commit(sock, atoi(istr))!=CONFD_OK) { printf("error: %s\n", confd_lasterr()); } else { printf("ok\n"); } } else if (strcmp(tok, "candidate-commit") == 0) { if (maapi_candidate_commit(sock) != CONFD_OK) { printf("error: %s\n", confd_lasterr()); } else { printf("ok\n"); } } else { printf("commands \n"); printf(" show - show current conf\n"); printf(" help - show this text\n"); printf(" abort - abort current trans \n"); printf(" commit - commit current trans\n"); printf(" create name ip port - create new server\n"); printf(" delete name - delete server\n"); printf(" candidate-reset - copy running into cand"); printf(" validate - trans validate"); printf(" delete-config - delete config"); printf(" candidate-commit - copy cand to running | confirm"); printf(" candidate-confirmed-commit secs \n"); printf(" \n"); } err: printf("#cli "); fflush(stdout); } }
The code above reads lines from stdin, parses the line
and invokes different MAAPI calls. For example, if we wish to
use the show command to show
the contents of the opened database, we first read the
two leaf elements, called /foo
and
/bar
.
We must check the return values from those read calls. If
we look at the data model, the values are defined as:
leaf foo { type string; default "fooo"; } leaf bar { type int32; default 66; }
The above elements both have default values. However if we open an empty candidate, they do not exist there.
Following that, in order to show the database, we must traverse
all /servers/server
instances and display them on the
screen. We do this by using a maapi_cursor
. A cursor
must first be initialized, and then we can use the cursor to
find out the key value(s) of the next
element until
there are no more elements.
Another interesting call is the call to create a new
server. We create a new server instance through the call to
maapi_create(sock, th, "/servers/server{%s}", name)
.
Once we have created a new "server" instance we must
also set the values for the two leafs inside the
server instance, the ip
and the port
elements.
If we fail to do this, commit will fail.
There are two variants on the MAAPI function which sets a a value. One where the value is a regular string, and one where the value is of type confd_value_t. Usually when we implement a proprietary agent, somehow a user will enter values as strings. In our code above, we use the string variant.
This program can be found in the examples section of a ConfD
release, in the misc/maapi_cli
directory.
If we want to use MAAPI to implement a general purpose
agent which works on all data models, we can use the
confd_cs_node
tree that is generated when we call
maapi_load_schemas()
/confd_load_schemas()
,
see the section called “USING SCHEMA INFORMATION” in
the confd_types(3) manual page.
This is a representation of the complete data model in the
form of a tree of linked C structures.