Writing modules

Download:

This document is available for download in PDF format:

Module data flow

Testing module (a.k.a tester) exports functions called by surealived

according to the following scheme:
 

 

Module information

Module has to present itself nicely to surealived, to do so it has to declare
and fill mod_operations structure:

typedef struct mod_operations {
    gpointer        (*m_alloc_args)(void);
    void            (*m_free)(CfgReal *); /* free all real's memory */
    void            (*m_prepare)(CfgReal *);
    void            (*m_cleanup)(CfgReal *);
    REQUEST         (*m_process_event)(CfgReal *);
    void            (*m_check)(CfgReal *); /* exec */
    void            (*m_start)(CfgReal *); /* exec */
    gchar           *m_name;
    mod_args        *m_args;
    SDTestProtocol  m_test_protocol;
} mod_operations;

Where:

  • m_alloc_args - pointer to function returning real's private structure.
  • m_free - pointer to function free'ing memory when real is removed
    from configuration (ie when shutting down server)
  • m_prepare - pointer to function preparing real test
  • m_cleanup - pointer to function cleaning up after test
  • m_process_event - pointer to function handling all events
  • m_start/m_check - pointers to functions used by mod_exec
  • m_name - module name
  • m_args - pointer to array of mod_args structures
  • m_test_protocol - type of communication protocol,

    possible values are SD_PROTO_(TCP|UDP|EXEC|NO_TEST)

Initialization

When loading module SureAliveD calls it's init function, which returns
mod_args structure. Module's initializing function MUST be called

__init_module.

Example implementation of this function:

static mod_operations mops;   /* Module operations struct */

mod_operations __init_module(void) {
    mops.m_name             = "http";
    mops.m_test_protocol    = SD_PROTO_TCP;
    mops.m_args             = m_args;

    mops.m_alloc_args       = module_alloc_args;
    mops.m_prepare          = module_test_prepare;
    mops.m_process_event    = module_process_event;
    mops.m_cleanup          = module_cleanup;
    return mops;
}

Module's private parameters (per real)

SureAliveD services configuration is held in XML file.
In that file you may define additional parameters that can control testing modules.
Therefore module must somehow inform surealived which parameters it expects
during configuration. First of all it must create structure that will define possible
variables ie.:

typedef struct {
    char        url[BUFSIZ];
    char        host[256];
    char        *request;
    int         req_len;
    bool        naive;
} TestHTTP;

Besides that module must create NULL-terminated array of mod_args structures,
which will describe variables used by module. That way surealived will know
how to parse XML configuration.

mod_args structure:

typedef struct {
    gchar           *name;        /* attribute name */
    SD_MODARG_TYPE  type;         /* type (STRING/INT/...) */
    guint           param;        /* optional parameter (default value or length) */
    SD_ATTR_TYPE    attr_type;    /* BASIC_ATTR (necessary) or EXTRA_ATTR */
    unsigned long   offset;       /* Use SDOFF makro to define offset */
} mod_args;

Where:

  • name - defines attribute name in XML config
  • type - argument type, possible types are: STRING, PORT, UINT, INT, BOOL
  • param - defines maximum length for STRING or default value for other types
  • attr_type - defines whether argument is essential (BASIC_ATTR) or optional (EXTRA_ATTR)
  • offset - variable offset from beggining of the structure, it's best to use
    SDOFF makro passing it two arguments - first is struct name and second is variable name

Example:

static mod_args m_args[]={
    { "url",     STRING,    BUFSIZ, BASIC_ATTR, SDOFF(TestHTTP, url)     },
    { "host",    STRING,    256,    BASIC_ATTR, SDOFF(TestHTTP, host)    },
    { "naive",   BOOL,      -1,     EXTRA_ATTR, SDOFF(TestHTTP, naive)   },
    { "retcode", INT,       200,    EXTRA_ATTR, SDOFF(TestHTTP, retcode) },
    { NULL, 0,  0,  0,  0 },
};

It's not necessary to define all variables from struct, however they will not be set
while parsing (but you can use them as real's private data).

That array must be NULL-terminated.

Module is responsible for allocating memory for attribute structure, to do so
surealived calls m_alloc_args declared in mod_operations struct.

Example m_alloc_args:

static gpointer module_alloc_args(void) {
    TestHTTP    *ret = g_new0(TestHTTP, 1);
    memset(ret, 0, sizeof(TestHTTP));
    return ret;
}

From this moment surealived will handle parsing XML and setting attributes.
Module can be sure that parameters will be set exactly how it is defined in mod_args array.
 

CfgReal struct

In next steps surealived will pass pointer to CfgReal structure to module's function.
Here are the most important fields of this struct:

  • char *virt->name - virtual name to which this real is plugged
  • char *buf - pointer to buffer to/from which surealived will read/write data
  • gboolean test_state - test state TRUE/FALSE
  • char error - variable determining whether there was error in transmission
  • u_int32_t bytes_read - bytes read in last operation
  • u_int32_t intstate - real internal state (this variable is useful for modules)
  • char name[MAXNAME] - real name
  • char addrtxt[MAXIPTXT] - IP address (human readable)
  • char porttxt[MAXPORTTXT] - port (human readable)
  • gpointer moddata - pointer to structure allocated by module for specific real

 

Preparing test

Before starting the test SureAliveD calls m_prepare function defined
in module_operations struct, it's responsible for setting appropriate values
necessary to run the test (primarily it needs to set real state). This
function needs one parameter and it is pointer to real config struct - CfgReal.

 

static void m_prepare(CfgReal *real); /* function definition */

CfgReal struct contains pointer to real private struct defined by module
and allocated by m_alloc_args, it's name is moddata.
Example usage:

TestHTTP *t = (TestHTTP *)real->moddata;
 

Event handling

REQUEST m_process_event(CfgReal *real)

Function m_process_event is called when a new event for real is received.

Possible error (ie when connection was terminated) is indicated by setting real->error
to non-zero value. This function should implement event handling, it also must return
it's requests (REQUEST type) to surealived.

Possible requests are:

  • WANT_READ(n) - request to read EXACTLY n bytes
  • WANT_WRITE(n) - request to send EXACTLY n bytes
  • WANT_READ_AV(n) - request to read AT MOST n bytes, but
    module should be notified after first 'read()' from socket.
  • WANT_EOF - request to drain socket, module will be notified when EOF is reached

    all read data is lost
  • WANT_END - request to end test

WARNING! In case of functions requesting reading/writing all operations are
performed on buf pointer from CfgReal struct. Module must ensure that buf
pointer is set correctly and memory to which it points can hold that amount of data.
 

Cleaning up after test

void m_cleanup(CfgReal *real)

If module allocated any data which will not be reused, it should be free'd in this function.
Function m_cleanup is called after the test.

 

Example

In this subsection we will try to describe how mod_http.c, HTTP module works.

#include <stdio.h>
#include <glib.h>
#include <common.h>
#include <modloader.h>
#include <sd_defs.h>
#include <xmlparser.h>

#define REQUEST_TEMPLATE "GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: SureAliveD\r\n\r\n"

These are basic useful include's, but modloader.h and sd_defs.h are
necessary for module to work, they contain structures definitions.
REQUEST_TEMPLATE is (how the name suggests) HTTP request template. It will be filled
with correct host and req.

typedef struct {
    gchar       url[BUFSIZ];
    gchar       host[256];
    gboolean    naive;
    gchar      *request;
    gint        req_len;
    gchar       ans[64];
    gchar       retcode[4];
} TestHTTP;

enum {
    CONNECTED,
    REQUEST_SENT,
    REQUEST_RECEIVED,
    RECEIVING
} State;

TestHTTP struct contains information used when testing.
State describes real's internal state.

static mod_operations mops;

static mod_args m_args[]={
    { "url",    STRING,     BUFSIZ, BASIC_ATTR, SDOFF(TestHTTP, url)    },
    { "host",   STRING,     256,    BASIC_ATTR, SDOFF(TestHTTP, host)   },
    { "naive",  BOOL,       -1,     EXTRA_ATTR, SDOFF(TestHTTP, naive)  },
    { "retcode", STRING,    4, EXTRA_ATTR, SDOFF(TestHTTP, retcode) },
    { NULL, 0,  0,  0,  0 },
};

Here we declare mod_operations struct which will be filled when init
function is called.

Also m_args[] array is filled with valid mod_args structures.
As you can see module requests two basic attributes - url as STRING of maximal
BUSIZ length and host (max 256b). Other attributes are optional - naive

is a boolean value and retcode defines expected return code (from webserver).
Last element of ``m_args" array MUST be filled with zero's.

 

static gpointer module_alloc_args(void) {
    TestHTTP    *ret = g_new0(TestHTTP, 1);
    memset(ret, 0, sizeof(TestHTTP));
    return ret;
}

Function module_alloc_args(void) allocates and zeroes memory of TestHTTP
size and returns it.

 

static void module_test_prepare(CfgReal *real) {
    TestHTTP    *t = (TestHTTP *)real->moddata;
    LOGDETAIL("http module test prepare");
    real->intstate = CONNECTED;
    if (!t->retcode[0])
        strncpy(t->retcode, "200", 3);
}

Function preparing real to the test primarily sets it's internal state (real->intstate)
to CONNECTED (m_test_prepare is called AFTER connection is established), then module
checks if alternative returncode was supplied, if not ``HTTP 200 OK" is used.

Test handling

static REQUEST module_process_event(CfgReal *real) {
    TestHTTP    *t = (TestHTTP *)real->moddata;

    if (real->error)
        return WANT_END;

    if (real->intstate == CONNECTED) {
        real->intstate = REQUEST_SENT;     /* set next state */

        real->buf = t->request;
        if (t->request)
            return WANT_WRITE(t->req_len);

        t->request = g_strdup_printf(REQUEST_TEMPLATE, t->url, t->host);

        real->buf  = t->request;
        t->req_len = strlen(t->request); /* and try to remember the length of it */

        LOGDETAIL("MOD_HTTP: requesting to write %d(%d) bytes",
                     t->req_len, REQ_LEN(WANT_WRITE(t->req_len)));
        return WANT_WRITE(t->req_len);
    }

First, event handling function checks whether there was any error (connection reset)
if so it ends test immediately returning WANT_END request.

If there was no error then module inspects real internal state,
if it is just CONNECTED, then it changes it's state to REQUEST_SENT and
changes buf pointer to real's private request buffer. After that
module checks whether request was set for that real, if not
then it allocates and sets correct request (using url and host from XML).
And after that it returns request to send data (request).

 

    else if (real->intstate == REQUEST_SENT) {
        real->buf = t->ans;     /* drop answer to buf */
        memset(real->buf, 0, sizeof(t->ans));
        LOGDETAIL("MOD_HTTP: requesting to read %d(%d) bytes",
                     sizeof(t->ans), REQ_LEN(WANT_READ(sizeof(t->ans))));
        real->intstate = REQUEST_RECEIVED;
        return WANT_READ(sizeof(t->ans));
    }

If real is in REQUEST_SENT state, then module changes operation buffer
to real's private buf holding response and zeroes it.
Then it returns WANT_READ requesting reading of ``sizeof(t->ans)`` bytes.

 

    else if (real->intstate == REQUEST_RECEIVED) {
        if (!strncmp(real->buf+9, t->retcode, 3)) {
            LOGDETAIL("REAL ONLINE [virt:%s,%s]!", real->virt->name, real->name);
            real->test_state = TRUE;
        }
        else
            LOGDETAIL("REAL OFFLINE [virt:%s,%s]!", real->virt->name, real->name);

        if (t->naive)
            return WANT_END;

        real->intstate = EOF;
        return WANT_EOF;        /* notify me when you notice eof */
    }
    else {                      /* for statistics */
        LOGDETAIL("EOF on socket - statistics should be sent!");
    }

Last but one possible real state is REQUEST_RECEIVED,
in that state we need to check ``return code", if everything is OK then module
sets test state (real->tes_state) to TRUE.
After that module checks if naive attribute was set (in XML config), if so
then we need to end test, so module returns - WANT_END request.
Otherwise real state is set to EOF and WANT_EOF request is returned indicating
that module wants to be informed when socket is drained. In that case
last ``else" will handle it.

 

static void module_free(CfgReal *real) {
    TestHTTP    *t = (TestHTTP *)real->moddata;
    if (t->request)
        free(t->request);
    t->request = NULL;
}

Function module_free frees memory allocated by module. This function
is called when surealived wants to remove real from virtual.

 

mod_operations __init_module(void) {
    LOGINFO(" ** Init module: setting mod_operations for tester [http]");

    mops.m_name             = "http";
    mops.m_test_protocol    = SD_PROTO_TCP;
    mops.m_args             = m_args;

    mops.m_alloc_args       = module_alloc_args;
    mops.m_prepare          = module_test_prepare;
    mops.m_process_event    = module_process_event;
    mops.m_cleanup          = module_free;
    return mops;
}

Function __init_module initializes mod_operation structure, sets
module name, communication protocol and structure describing arguments passed
to module. It also sets function pointers to functions handling test.
In the end it returns that structure to surealived.
That function MUST be called __init_module.
 

Compilation

Module needs to be compiled to shared library. It can be done using this command:

gcc -fPIC -shared -Wl,-soname,mod_NAME.so -o mod_NAME.so mod_NAME.c \
    `pkg-config --libs --cflags glib-2.0` `xml2-config --libs --cflags`

Of course mod_NAME.so and mod_NAME.c must be renamed according to
module name. Remember that during compilation all header files should
be accessible.