Quellcode durchsuchen

db_cluster: new module for generic database clustering

- the module allow to group several DB connections and perform commands
  over them, based on policies such as round robin, serial try or
  parallel execution
- any kind of db connection can make part from a cluster
- example use case: write in parallel to two databases and do round
  robin read from them
- the module is a middle layer between modules and databases, reusing
  existing db connectors. A module that wants to use such cluster, just
  have to set the db_url to "cluster://clusterid"
- the module allow definition of multiple clusters
- see README for more details
- todo: enable/disable connections at runtime and via rpc
Daniel-Constantin Mierla vor 13 Jahren
Ursprung
Commit
201fc2d600

+ 15 - 0
modules_k/db_cluster/Makefile

@@ -0,0 +1,15 @@
+#
+# db_cluster module makefile
+#
+
+include ../../Makefile.defs
+auto_gen=
+NAME=db_cluster.so
+LIBS=
+
+DEFS+=-DOPENSER_MOD_INTERFACE
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/srdb1/srdb1
+
+include ../../Makefile.modules

+ 162 - 0
modules_k/db_cluster/README

@@ -0,0 +1,162 @@
+DB_CLUSTER Module
+
+Daniel-Constantin Mierla
+
+   <[email protected]>
+
+Edited by
+
+Daniel-Constantin Mierla
+
+   <[email protected]>
+
+   Copyright © 2012 asipto.com
+     __________________________________________________________________
+
+   Table of Contents
+
+   1. Admin Guide
+
+        1. Overview
+        2. Dependencies
+
+              2.1. Kamailio Modules
+              2.2. External Libraries or Applications
+
+        3. Parameters
+
+              3.1. connection (int)
+              3.2. cluster (int)
+
+        4. Usage
+
+   List of Examples
+
+   1.1. Set connection parameter
+   1.2. Set cluster parameter
+   1.3. Set cluster parameter
+
+Chapter 1. Admin Guide
+
+   Table of Contents
+
+   1. Overview
+   2. Dependencies
+
+        2.1. Kamailio Modules
+        2.2. External Libraries or Applications
+
+   3. Parameters
+
+        3.1. connection (int)
+        3.2. cluster (int)
+
+   4. Usage
+
+1. Overview
+
+   This module provides a generic database clustering system. It can be
+   used as a middle layer between modules and database connectors.
+
+   Via clustering, database operations can be executed across multiple
+   servers, based on policies such as paraller write, serial or round
+   robin write and read.
+
+   Following database commands are considered to be write operations:
+   INSERT, DELETE, UPDATE, REPLACE, INSERT-DELAYED, INSERT-UPDATE. The
+   read operations are done for database commands: QUERY and RAW-QUERY.
+
+2. Dependencies
+
+   2.1. Kamailio Modules
+   2.2. External Libraries or Applications
+
+2.1. Kamailio Modules
+
+   The following modules must be loaded before this module:
+     * db connector - database connectors.
+
+2.2. External Libraries or Applications
+
+   The following libraries or applications must be installed before
+   running Kamailio with this module loaded:
+     * None
+
+3. Parameters
+
+   3.1. connection (int)
+   3.2. cluster (int)
+
+3.1. connection (int)
+
+   Specify the connection to a real database system. The format is
+   'conid=>DBURL' - providing a connection id and the database URL.
+
+   Default value is NULL.
+
+   Example 1.1. Set connection parameter
+...
+modparam("db_cluster", "connection",
+             "con1=>mysql://openser:openser@localhost/kamailio1")
+modparam("db_cluster", "connection",
+             "con2=>mysql://openser:openser@localhost/kamailio2")
+...
+
+3.2. cluster (int)
+
+   Specify the cluster definition. The format is
+   'clsid=>conid1=def1;conid2=def2' - providing a cluster id and the list
+   of database connections to be used. For each connection you have to
+   provide a usage definition. The usage definition is a 4-char long
+   string, specifying priority and command mode for read an write
+   operations to be done on that connection.
+
+   The priority is a digit between 0 and 9, higher value is higher
+   priority. Priority 0 means that connection is not going to be used in
+   that cluster.
+
+   Command mode is a character among s, r and p. s is for doing serial
+   operations (try first and if fails, try next); r is for doing round
+   robin operations; p - is for doing parallel operations (this is valid
+   only for write operations).
+
+   Default value is NULL.
+
+   Example 1.2. Set cluster parameter
+...
+modparam("db_cluster", "cluster", "cls1=>con1=9s8p;con2=9s8p")
+...
+
+4. Usage
+
+   Practically, all the modules that want to use a cluster, have to set
+   their db_url parameter to "cluster://clusterid".
+
+   Following rules apply when doing DB commands: the connecions with
+   highest priority are chosen first and the operations are performed
+   according to the command mode. Note that for same priority, only one
+   command mode is used (the one from the first connection with that
+   priority found in the definition of the cluster). If the DB command is
+   not successful, next set of connections based on priority is selected
+   and the command is tried again. When the command is successful, no
+   other try is made.
+
+   For parallel operations, a command is considered successful if it
+   succeeded on one connection from a group with same priority.
+
+   Next example shows how to set a cluster with two connections to MySQL
+   to be used for parallel writing from acc and round-robin reading by
+   sqlops.
+
+   Example 1.3. Set cluster parameter
+...
+modparam("db_cluster", "connection",
+             "c1=>mysql://openser:openserrw@localhost/kamailio1")
+modparam("db_cluster", "connection",
+             "c2=>mysql://openser:openserrw@localhost/kamailio2")
+modparam("db_cluster", "cluster", "k1=>c1=9r9p;c2=9r9p")
+
+modparam("acc", "db_url", "cluster://k1")
+
+modparam("sqlops", "sqlcon", "ca=>cluster://k1")
+...

+ 112 - 0
modules_k/db_cluster/db_cluster_mod.c

@@ -0,0 +1,112 @@
+/* 
+ * $Id$ 
+ *
+ * Generic db cluster module interface
+ *
+ * Copyright (C) 2012 Daniel-Constantin Mierla (asipto.com)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "../../sr_module.h"
+#include "../../dprint.h"
+#include "../../lib/srdb1/db.h"
+#include "dbcl_data.h"
+#include "dbcl_api.h"
+
+MODULE_VERSION
+
+int mod_init(void);
+int db_cluster_bind_api(db_func_t *dbb);
+
+int dbcl_con_param(modparam_t type, void *val);
+int dbcl_cls_param(modparam_t type, void *val);
+
+/*! \brief
+ * DB Cluster module interface
+ */
+static cmd_export_t cmds[] = {
+	{"db_bind_api",         (cmd_function)db_cluster_bind_api,      0, 0, 0, 0},
+	{0, 0, 0, 0, 0, 0}
+};
+
+/*! \brief
+ * Exported parameters
+ */
+static param_export_t params[] = {
+	{"connection",  STR_PARAM|USE_FUNC_PARAM, (void*)dbcl_con_param},
+	{"cluster",     STR_PARAM|USE_FUNC_PARAM, (void*)dbcl_cls_param},
+	{0, 0, 0}
+};
+
+struct module_exports exports = {	
+	"db_cluster",
+	DEFAULT_DLFLAGS, /* dlopen flags */
+	cmds,
+	params,          /*  module parameters */
+	0,               /* exported statistics */
+	0,               /* exported MI functions */
+	0,               /* exported pseudo-variables */
+	0,               /* extra processes */
+	mod_init,        /* module initialization function */
+	0,               /* response function*/
+	0,               /* destroy function */
+	0                /* per-child init function */
+};
+
+
+int mod_init(void)
+{
+	LM_DBG("Setting up DB cluster\n");
+	return 0;
+}
+
+int db_cluster_bind_api(db_func_t *dbb)
+{
+	if(dbb==NULL)
+		return -1;
+
+	memset(dbb, 0, sizeof(db_func_t));
+
+	dbb->use_table        = db_cluster_use_table;
+	dbb->init             = db_cluster_init;
+	dbb->close            = db_cluster_close;
+	dbb->query            = db_cluster_query;
+	dbb->fetch_result     = db_cluster_fetch_result;
+	dbb->raw_query        = db_cluster_raw_query;
+	dbb->free_result      = db_cluster_free_result;
+	dbb->insert           = db_cluster_insert;
+	dbb->delete           = db_cluster_delete;
+	dbb->update           = db_cluster_update;
+	dbb->replace          = db_cluster_replace;
+	dbb->last_inserted_id = db_cluster_last_inserted_id;
+	dbb->insert_update    = db_cluster_insert_update;
+	dbb->insert_delayed   = db_cluster_insert_delayed;
+	dbb->affected_rows    = db_cluster_affected_rows;
+
+	return 0;
+}
+
+int dbcl_con_param(modparam_t type, void *val)
+{
+	return dbcl_parse_con_param((char*)val);
+}
+
+int dbcl_cls_param(modparam_t type, void *val)
+{
+	return dbcl_parse_cls_param((char*)val);
+}

+ 437 - 0
modules_k/db_cluster/dbcl_api.c

@@ -0,0 +1,437 @@
+/*
+ * $Id$
+ *
+ * DB CLuster core functions
+ *
+ * Copyright (C) 2012 Daniel-Constantin Mierla (asipto.com)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*! \file
+ *  \brief DB_CLUSTER :: Core
+ *  \ingroup db_cluster
+ *  Module: \ref db_cluster
+ */
+
+#include "../../dprint.h"
+#include "../../hashes.h"
+#include "../../trim.h"
+#include "../../globals.h"
+#include "../../lib/srdb1/db.h"
+
+#include "dbcl_data.h"
+#include "dbcl_api.h"
+
+
+#define DBCL_READ(command) \
+	do {\
+	int ret;\
+	int i;\
+	int j;\
+	int k;\
+	db1_con_t  *dbh=NULL;\
+	dbcl_cls_t *cls=NULL;\
+	cls = (dbcl_cls_t*)_h->tail;\
+	ret = 0;\
+	for(i=DBCL_PRIO_SIZE-1; i>0; i--)\
+	{\
+		switch(cls->rlist[i].mode) {\
+			case 's':\
+			case 'S':\
+				for(j=0; j<cls->rlist[i].clen; j++)\
+				{\
+					if(cls->rlist[i].clist[j] != NULL && cls->rlist[i].clist[j]->flags!=0\
+						&& cls->rlist[i].clist[j]->dbh != NULL)\
+					{\
+						LM_DBG("serial operation - cluster [%.*s] (%d/%d)\n",\
+								cls->name.len, cls->name.s, i, j);\
+						dbh = cls->rlist[i].clist[j]->dbh;\
+						ret = cls->rlist[i].clist[j]->dbf.command;\
+						if (ret==0) {\
+							cls->usedcon = cls->rlist[i].clist[j];\
+							return 0;\
+						}\
+					}\
+				}\
+				break;\
+			case 'r':\
+			case 'R':\
+				for(k=0; k<cls->rlist[i].clen; k++)\
+				{\
+					j = (process_no + k + cls->rlist[i].crt) % cls->rlist[i].clen;\
+					if(cls->rlist[i].clist[j] != NULL && cls->rlist[i].clist[j]->flags!=0\
+						&& cls->rlist[i].clist[j]->dbh != NULL)\
+					{\
+						LM_DBG("round robin operation - cluster [%.*s] (%d/%d)\n",\
+								cls->name.len, cls->name.s, i, j);\
+						dbh = cls->rlist[i].clist[j]->dbh;\
+						ret = cls->rlist[i].clist[j]->dbf.command;\
+						if (ret==0)\
+						{\
+							cls->usedcon = cls->rlist[i].clist[j];\
+							cls->rlist[i].crt = (j+1) % cls->rlist[i].clen;\
+							return 0;\
+						}\
+					}\
+				}\
+				break;\
+			default:\
+				LM_ERR("invalid mode %c (%d)\n", cls->rlist[i].mode,\
+							cls->rlist[i].mode);\
+				return -1;\
+		}\
+	}\
+	return ret;\
+	} while(0)
+
+#define DBCL_WRITE(command) \
+	do {\
+	int ret;\
+	int rc;\
+	int rok;\
+	int i;\
+	int j;\
+	int k;\
+	db1_con_t  *dbh=NULL;\
+	dbcl_cls_t *cls=NULL;\
+	cls = (dbcl_cls_t*)_h->tail;\
+	ret = 0;\
+	rok = 0;\
+	rc = 0;\
+	for(i=DBCL_PRIO_SIZE-1; i>0; i--)\
+	{\
+		switch(cls->wlist[i].mode) {\
+			case 's':\
+			case 'S':\
+				for(j=0; j<cls->wlist[i].clen; j++)\
+				{\
+					if(cls->wlist[i].clist[j] != NULL && cls->wlist[i].clist[j]->flags!=0\
+						&& cls->wlist[i].clist[j]->dbh != NULL)\
+					{\
+						LM_DBG("serial operation - cluster [%.*s] (%d/%d)\n",\
+								cls->name.len, cls->name.s, i, j);\
+						dbh = cls->rlist[i].clist[j]->dbh;\
+						ret = cls->wlist[i].clist[j]->dbf.command;\
+						if (ret==0) {\
+							cls->usedcon = cls->wlist[i].clist[j];\
+							return 0;\
+						}\
+					}\
+				}\
+				break;\
+			case 'r':\
+			case 'R':\
+				for(k=0; k<cls->wlist[i].clen; k++)\
+				{\
+					j = (process_no + k + cls->wlist[i].crt) % cls->wlist[i].clen;\
+					if(cls->wlist[i].clist[j] != NULL && cls->wlist[i].clist[j]->flags!=0\
+						&& cls->wlist[i].clist[j]->dbh != NULL)\
+					{\
+						LM_DBG("round robin operation - cluster [%.*s] (%d/%d)\n",\
+								cls->name.len, cls->name.s, i, j);\
+						dbh = cls->rlist[i].clist[j]->dbh;\
+						ret = cls->wlist[i].clist[j]->dbf.command;\
+						if (ret==0)\
+						{\
+							cls->usedcon = cls->wlist[i].clist[j];\
+							cls->wlist[i].crt = (j+1) % cls->wlist[i].clen;\
+							return 0;\
+						}\
+					}\
+				}\
+				break;\
+			case 'p':\
+			case 'P':\
+				for(j=0; j<cls->wlist[i].clen; j++)\
+				{\
+					if(cls->wlist[i].clist[j] != NULL && cls->wlist[i].clist[j]->flags!=0\
+						&& cls->wlist[i].clist[j]->dbh != NULL)\
+					{\
+						LM_DBG("parallel operation - cluster [%.*s] (%d/%d)\n",\
+								cls->name.len, cls->name.s, i, j);\
+						dbh = cls->rlist[i].clist[j]->dbh;\
+						rc = cls->wlist[i].clist[j]->dbf.command;\
+						if(rc==0) {\
+							cls->usedcon = cls->wlist[i].clist[j];\
+							rok = 1;\
+						}\
+						ret |= rc;\
+					}\
+				}\
+				if (rok==1 && cls->wlist[i].clen>0)\
+					return 0;\
+				break;\
+			default:\
+				LM_ERR("invalid mode %c (%d)\n", cls->rlist[i].mode,\
+						cls->rlist[i].mode);\
+				return -1;\
+		}\
+	}\
+	return ret;\
+	} while(0)
+
+
+
+/*! \brief
+ * Initialize database connection
+ */
+db1_con_t* db_cluster_init(const str* _dburl)
+{
+	db1_con_t  *h=NULL;
+	dbcl_cls_t *cls=NULL;
+	str name;
+
+	LM_DBG("initializing with cluster [%.*s]\n", _dburl->len, _dburl->s);
+	if(_dburl->len<10 || strncmp(_dburl->s, "cluster://", 10)!=0)
+	{
+		LM_ERR("invlaid url for cluster module [%.*s]\n",
+				_dburl->len, _dburl->s);
+		return NULL;
+	}
+	name.s = _dburl->s + 10;
+	name.len = _dburl->len - 10;
+	trim(&name);
+	cls = dbcl_get_cluster(&name);
+	if(cls==NULL)
+	{
+		LM_ERR("cluster not found [%.*s]\n",
+				_dburl->len, _dburl->s);
+		return NULL;
+	}
+	if(dbcl_init_dbf(cls)<0)
+	{
+		LM_ERR("cluster [%.*s] - unable to bind to DB engines\n",
+				_dburl->len, _dburl->s);
+		return NULL;
+	}
+	dbcl_init_connections(cls);
+	cls->ref++;
+	h = (db1_con_t*)pkg_malloc(sizeof(db1_con_t));
+	if (h==NULL) {
+		LM_ERR("out of pkg\n");
+		return NULL;
+	}
+	memset(h, 0, sizeof(db1_con_t));
+	h->tail = (unsigned long)cls;
+	return h;
+}
+
+
+/*! \brief
+ * Close a database connection
+ */
+void db_cluster_close(db1_con_t* _h)
+{
+	dbcl_cls_t *cls=NULL;
+	LM_DBG("executing db cluster close command\n");
+	cls = (dbcl_cls_t*)_h->tail;
+ 	cls->ref--;
+ 	if(cls->ref > 0)
+		return;
+	/* close connections */
+	dbcl_close_connections(cls);
+	return;
+}
+
+
+/*! \brief
+ * Free all memory allocated by get_result
+ */
+int db_cluster_free_result(db1_con_t* _h, db1_res_t* _r)
+{
+	dbcl_cls_t *cls=NULL;
+	LM_DBG("executing db cluster free-result command\n");
+	cls = (dbcl_cls_t*)_h->tail;
+	if(cls->usedcon==NULL || cls->usedcon->dbh==NULL)
+		return -1;
+	return cls->usedcon->dbf.free_result(cls->usedcon->dbh, _r);
+}
+
+
+/*! \brief
+ * Do a query
+ */
+int db_cluster_query(const db1_con_t* _h, const db_key_t* _k, const db_op_t* _op,
+	     const db_val_t* _v, const db_key_t* _c, const int _n, const int _nc,
+	     const db_key_t _o, db1_res_t** _r)
+{
+	LM_DBG("executing db cluster query command\n");
+	DBCL_READ(query(dbh, _k, _op, _v, _c, _n, _nc, _o, _r));
+}
+
+
+/*! \brief
+ * fetch rows from a result
+ */
+int db_cluster_fetch_result(const db1_con_t* _h, db1_res_t** _r, const int nrows)
+{
+	dbcl_cls_t *cls=NULL;
+	LM_DBG("executing db cluster fetch-result command\n");
+	cls = (dbcl_cls_t*)_h->tail;
+	if(cls->usedcon==NULL || cls->usedcon->dbh==NULL)
+		return -1;
+	return cls->usedcon->dbf.fetch_result(cls->usedcon->dbh, _r, nrows);
+}
+
+
+/*! \brief
+ * Raw SQL query
+ */
+int db_cluster_raw_query(const db1_con_t* _h, const str* _s, db1_res_t** _r)
+{
+	LM_DBG("executing db cluster raw query command\n");
+	DBCL_READ(raw_query(dbh, _s, _r));
+}
+
+
+/*! \brief
+ * Insert a row into table
+ */
+int db_cluster_insert(const db1_con_t* _h, const db_key_t* _k, const db_val_t* _v, const int _n)
+{
+	LM_DBG("executing db cluster insert command\n");
+	DBCL_WRITE(insert(dbh, _k, _v, _n));
+}
+
+
+/*! \brief
+ * Delete a row from table
+ */
+int db_cluster_delete(const db1_con_t* _h, const db_key_t* _k, const 
+	db_op_t* _o, const db_val_t* _v, const int _n)
+{
+	LM_DBG("executing db cluster delete command\n");
+	DBCL_WRITE(delete(dbh, _k, _o, _v, _n));
+}
+
+
+/*! \brief
+ * Update a row in table
+ */
+int db_cluster_update(const db1_con_t* _h, const db_key_t* _k, const db_op_t* _o,
+	const db_val_t* _v, const db_key_t* _uk, const db_val_t* _uv, const int _n,
+	const int _un)
+{
+	LM_DBG("executing db cluster update command\n");
+	DBCL_WRITE(update(dbh, _k, _o, _v, _uk, _uv, _n, _un));
+}
+
+
+/*! \brief
+ * Just like insert, but replace the row if it exists
+ */
+int db_cluster_replace(const db1_con_t* _h, const db_key_t* _k,
+		const db_val_t* _v, const int _n, const int _un, const int _m)
+{
+	LM_DBG("executing db cluster replace command\n");
+	DBCL_WRITE(replace(dbh, _k, _v, _n, _un, _m));
+}
+
+/*! \brief
+ * Returns the last inserted ID
+ */
+int db_cluster_last_inserted_id(const db1_con_t* _h)
+{
+	dbcl_cls_t *cls=NULL;
+	LM_DBG("executing db cluster last inserted id command\n");
+	cls = (dbcl_cls_t*)_h->tail;
+	if(cls->usedcon==NULL || cls->usedcon->dbh==NULL)
+		return -1;
+	return cls->usedcon->dbf.last_inserted_id(cls->usedcon->dbh);
+}
+
+
+/*! \brief
+ * Returns number of affected rows for last query
+ */
+int db_cluster_affected_rows(const db1_con_t* _h)
+{
+	dbcl_cls_t *cls=NULL;
+	LM_DBG("executing db cluster affected-rows command\n");
+	cls = (dbcl_cls_t*)_h->tail;
+	if(cls->usedcon==NULL || cls->usedcon->dbh==NULL)
+		return -1;
+	return cls->usedcon->dbf.affected_rows(cls->usedcon->dbh);
+}
+
+
+/*! \brief
+ * Insert a row into table, update on duplicate key
+ */
+int db_cluster_insert_update(const db1_con_t* _h, const db_key_t* _k, const db_val_t* _v,
+	const int _n)
+{
+	LM_DBG("executing db cluster insert-update command\n");
+	DBCL_WRITE(insert_update(dbh, _k, _v, _n));
+}
+
+
+/*! \brief
+ * Insert a row into table
+ */
+int db_cluster_insert_delayed(const db1_con_t* _h, const db_key_t* _k,
+		const db_val_t* _v, const int _n)
+{
+	LM_DBG("executing db cluster insert delayed command\n");
+	DBCL_WRITE(insert_delayed(dbh, _k, _v, _n));
+}
+
+
+/*! \brief
+ * Store name of table that will be used by
+ * subsequent database functions
+ */
+int db_cluster_use_table(db1_con_t* _h, const str* _t)
+{
+	int i;
+	int j;
+	int ret;
+	dbcl_cls_t *cls=NULL;
+
+	cls = (dbcl_cls_t*)_h->tail;
+
+	ret = 0;
+	LM_DBG("use table (%.*s) - cluster [%.*s]\n",
+							_t->len, _t->s, cls->name.len, cls->name.s);
+	for(i=DBCL_PRIO_SIZE-1; i>0; i--)
+	{
+		for(j=0; j<cls->rlist[i].clen; j++)
+		{
+			if(cls->rlist[i].clist[j] != NULL && cls->rlist[i].clist[j]->flags!=0
+					&& cls->rlist[i].clist[j]->dbh != NULL)
+			{
+				LM_DBG("set read table (%.*s) - cluster [%.*s] (%d/%d)\n",
+							_t->len, _t->s, cls->name.len, cls->name.s, i, j);
+				ret |= cls->rlist[i].clist[j]->dbf.use_table(cls->rlist[i].clist[j]->dbh, _t);
+			}
+		}
+		for(j=0; j<cls->wlist[i].clen; j++)
+		{
+			if(cls->wlist[i].clist[j] != NULL && cls->wlist[i].clist[j]->flags!=0
+					&& cls->wlist[i].clist[j]->dbh != NULL)
+			{
+				LM_DBG("set write table (%.*s) - cluster [%.*s] (%d/%d)\n",
+							_t->len, _t->s, cls->name.len, cls->name.s, i, j);
+				ret |= cls->wlist[i].clist[j]->dbf.use_table(cls->wlist[i].clist[j]->dbh, _t);
+			}
+		}
+
+	}
+
+	return ret;
+}

+ 142 - 0
modules_k/db_cluster/dbcl_api.h

@@ -0,0 +1,142 @@
+/*
+ * $Id$
+ *
+ * DB CLuster core functions
+ *
+ * Copyright (C) 2012 Daniel-Constantin Mierla (asipto.com)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*! \file
+ *  \brief DB_CLUSTER :: Core
+ *  \ingroup db_cluster
+ *  Module: \ref db_cluster
+ */
+
+
+
+#ifndef _DBCL_API_H_
+#define _DBCL_API_H_
+
+
+#include "../../lib/srdb1/db_con.h"
+#include "../../lib/srdb1/db_res.h"
+#include "../../lib/srdb1/db_key.h"
+#include "../../lib/srdb1/db_op.h"
+#include "../../lib/srdb1/db_val.h"
+#include "../../str.h"
+
+/*! \brief
+ * Initialize database connection
+ */
+db1_con_t* db_cluster_init(const str* _sqlurl);
+
+
+/*! \brief
+ * Close a database connection
+ */
+void db_cluster_close(db1_con_t* _h);
+
+
+/*! \brief
+ * Free all memory allocated by get_result
+ */
+int db_cluster_free_result(db1_con_t* _h, db1_res_t* _r);
+
+
+/*! \brief
+ * Do a query
+ */
+int db_cluster_query(const db1_con_t* _h, const db_key_t* _k, const db_op_t* _op,
+	     const db_val_t* _v, const db_key_t* _c, const int _n, const int _nc,
+	     const db_key_t _o, db1_res_t** _r);
+
+
+/*! \brief
+ * fetch rows from a result
+ */
+int db_cluster_fetch_result(const db1_con_t* _h, db1_res_t** _r, const int nrows);
+
+
+/*! \brief
+ * Raw SQL query
+ */
+int db_cluster_raw_query(const db1_con_t* _h, const str* _s, db1_res_t** _r);
+
+
+/*! \brief
+ * Insert a row into table
+ */
+int db_cluster_insert(const db1_con_t* _h, const db_key_t* _k, const db_val_t* _v, const int _n);
+
+
+/*! \brief
+ * Delete a row from table
+ */
+int db_cluster_delete(const db1_con_t* _h, const db_key_t* _k, const 
+	db_op_t* _o, const db_val_t* _v, const int _n);
+
+
+/*! \brief
+ * Update a row in table
+ */
+int db_cluster_update(const db1_con_t* _h, const db_key_t* _k, const db_op_t* _o,
+	const db_val_t* _v, const db_key_t* _uk, const db_val_t* _uv, const int _n,
+	const int _un);
+
+
+/*! \brief
+ * Just like insert, but replace the row if it exists
+ */
+int db_cluster_replace(const db1_con_t* handle, const db_key_t* keys,
+		const db_val_t* vals, const int n, const int _un, const int _m);
+
+/*! \brief
+ * Returns the last inserted ID
+ */
+int db_cluster_last_inserted_id(const db1_con_t* _h);
+
+
+/*! \brief
+ * Returns number of affected rows for last query
+ */
+int db_cluster_affected_rows(const db1_con_t* _h);
+
+
+/*! \brief
+ * Insert a row into table, update on duplicate key
+ */
+int db_cluster_insert_update(const db1_con_t* _h, const db_key_t* _k, const db_val_t* _v,
+	const int _n);
+
+
+/*! \brief
+ * Insert a row into table
+ */
+int db_cluster_insert_delayed(const db1_con_t* _h, const db_key_t* _k,
+		const db_val_t* _v, const int _n);
+
+
+/*! \brief
+ * Store name of table that will be used by
+ * subsequent database functions
+ */
+int db_cluster_use_table(db1_con_t* _h, const str* _t);
+
+
+#endif /* KM_DBASE_H */

+ 479 - 0
modules_k/db_cluster/dbcl_data.c

@@ -0,0 +1,479 @@
+/*
+ * $Id$
+ *
+ * DB CLuster core functions
+ *
+ * Copyright (C) 2012 Daniel-Constantin Mierla (asipto.com)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*! \file
+ *  \brief DB_CLUSTER :: Core
+ *  \ingroup db_cluster
+ *  Module: \ref db_cluster
+ */
+
+#include "../../parser/parse_param.h"
+#include "../../dprint.h"
+#include "../../hashes.h"
+#include "../../trim.h"
+#include "../../mem/mem.h"
+#include "../../mem/shm_mem.h"
+
+#include "dbcl_data.h"
+
+
+static dbcl_con_t *_dbcl_con_root = NULL;
+static dbcl_cls_t *_dbcl_cls_root = NULL;
+
+dbcl_con_t *dbcl_get_connection(str *name)
+{
+	dbcl_con_t *sc;
+	unsigned int conid;
+
+	conid = core_case_hash(name, 0, 0);
+	sc = _dbcl_con_root;
+	while(sc)
+	{
+		if(conid==sc->conid && sc->name.len==name->len
+				&& strncmp(sc->name.s, name->s, name->len)==0)
+		{
+			LM_DBG("connection found [%.*s]\n", name->len, name->s);
+			return sc;
+		}
+		sc = sc->next;
+	}
+	return NULL;
+}
+
+dbcl_cls_t *dbcl_get_cluster(str *name)
+{
+	dbcl_cls_t *sc;
+	unsigned int clsid;
+
+	clsid = core_case_hash(name, 0, 0);
+	sc = _dbcl_cls_root;
+	while(sc)
+	{
+		if(clsid==sc->clsid && sc->name.len==name->len
+				&& strncmp(sc->name.s, name->s, name->len)==0)
+		{
+			LM_DBG("cluster found [%.*s]\n", name->len, name->s);
+			return sc;
+		}
+		sc = sc->next;
+	}
+	return NULL;
+}
+
+
+int dbcl_init_con(str *name, str *url)
+{
+	dbcl_con_t *sc;
+	unsigned int conid;
+
+	conid = core_case_hash(name, 0, 0);
+
+	sc = _dbcl_con_root;
+	while(sc)
+	{
+		if(conid==sc->conid && sc->name.len==name->len
+				&& strncmp(sc->name.s, name->s, name->len)==0)
+		{
+			LM_ERR("duplicate connection name\n");
+			return -1;
+		}
+		sc = sc->next;
+	}
+	sc = (dbcl_con_t*)pkg_malloc(sizeof(dbcl_con_t));
+	if(sc==NULL)
+	{
+		LM_ERR("no pkg memory\n");
+		return -1;
+	}
+	memset(sc, 0, sizeof(dbcl_con_t));
+	sc->conid = conid;
+	sc->name = *name;
+	sc->db_url = *url;
+	sc->sinfo = (dbcl_shared_t*)shm_malloc(sizeof(dbcl_shared_t));
+	if(sc->sinfo==NULL)
+	{
+		LM_ERR("no shm memory\n");
+		return -1;
+	}
+	memset(sc->sinfo, 0, sizeof(dbcl_shared_t));
+	sc->next = _dbcl_con_root;
+	_dbcl_con_root = sc;
+
+	return 0;
+}
+
+int dbcl_parse_con_param(char *val)
+{
+	str name;
+	str tok;
+	str in;
+	char *p;
+
+	/* parse: name=>db_url*/
+	in.s = val;
+	in.len = strlen(in.s);
+	p = in.s;
+
+	while(p<in.s+in.len && (*p==' ' || *p=='\t' || *p=='\n' || *p=='\r'))
+		p++;
+	if(p>in.s+in.len || *p=='\0')
+		goto error;
+	name.s = p;
+	while(p < in.s + in.len)
+	{
+		if(*p=='=' || *p==' ' || *p=='\t' || *p=='\n' || *p=='\r')
+			break;
+		p++;
+	}
+	if(p>in.s+in.len || *p=='\0')
+		goto error;
+	name.len = p - name.s;
+	if(*p!='=')
+	{
+		while(p<in.s+in.len && (*p==' ' || *p=='\t' || *p=='\n' || *p=='\r'))
+			p++;
+		if(p>in.s+in.len || *p=='\0' || *p!='=')
+			goto error;
+	}
+	p++;
+	if(*p!='>')
+		goto error;
+	p++;
+	while(p<in.s+in.len && (*p==' ' || *p=='\t' || *p=='\n' || *p=='\r'))
+		p++;
+	tok.s = p;
+	tok.len = in.len + (int)(in.s - p);
+
+	LM_DBG("connection: [%.*s] url: [%.*s]\n", name.len, name.s, tok.len, tok.s);
+
+	return dbcl_init_con(&name, &tok);
+error:
+	LM_ERR("invalid connection parameter [%.*s] at [%d]\n", in.len, in.s,
+			(int)(p-in.s));
+	return -1;
+}
+
+/**
+ * cons: conid1=1s1p;...
+ */
+int dbcl_cls_set_connections(dbcl_cls_t *cls, str *cons)
+{
+	param_t* params_list = NULL;
+	param_hooks_t phooks;
+	param_t *pit=NULL;
+	dbcl_con_t *sc;
+	str s;
+	int i;
+
+	if(cls==NULL || cons==NULL)
+		return -1;
+	s = *cons;
+	if(s.s[s.len-1]==';')
+		s.len--;
+	if (parse_params(&s, CLASS_ANY, &phooks, &params_list)<0)
+		return -1;
+	for (pit = params_list; pit; pit=pit->next)
+	{
+		sc = dbcl_get_connection(&pit->name);
+		if(sc==NULL)
+		{
+			LM_ERR("invalid connection id [%.*s]\n",
+					pit->name.len, pit->name.s);
+			goto error;
+		}
+		s = pit->body;
+		trim(&s);
+		if(s.len!=4)
+		{
+			LM_ERR("invalid parameter [%.*s] for connection id [%.*s]\n",
+					pit->body.len, pit->body.s,
+					pit->name.len, pit->name.s);
+			goto error;
+		}
+		if(s.s[0]<'0' || s.s[0]>'9')
+		{
+			LM_ERR("invalid parameter [%.*s] for connection id [%.*s]\n",
+					pit->body.len, pit->body.s,
+					pit->name.len, pit->name.s);
+			goto error;
+		}
+		i = s.s[0] - '0';
+		if(s.s[1]!='s' && s.s[1]!='S' && s.s[1]!='r' && s.s[1]!='r')
+		{
+			LM_ERR("invalid parameter [%.*s] for connection id [%.*s]\n",
+					pit->body.len, pit->body.s,
+					pit->name.len, pit->name.s);
+			goto error;
+		}
+		if(cls->rlist[i].clen<DBCL_CLIST_SIZE)
+		{
+			if(cls->rlist[i].mode==0)
+				cls->rlist[i].mode = s.s[1] | 32;
+			cls->rlist[i].prio = i;
+			cls->rlist[i].clist[cls->rlist[i].clen] = sc;
+			cls->rlist[i].clen++;
+		} else {
+			LM_WARN("too many read connections in cluster - con-id [%.*s]\n",
+					pit->name.len, pit->name.s);
+		}
+		if(s.s[2]<'0' || s.s[2]>'9')
+		{
+			LM_ERR("invalid parameter [%.*s] for connection id [%.*s]\n",
+					pit->body.len, pit->body.s,
+					pit->name.len, pit->name.s);
+			goto error;
+		}
+		i = s.s[2] - '0';
+		if(s.s[3]!='s' && s.s[3]!='S' && s.s[3]!='r' && s.s[3]!='r'
+				 && s.s[3]!='p' && s.s[3]!='P')
+		{
+			LM_ERR("invalid parameter [%.*s] for connection id [%.*s]\n",
+					pit->body.len, pit->body.s,
+					pit->name.len, pit->name.s);
+			goto error;
+		}
+		if(cls->wlist[i].clen<DBCL_CLIST_SIZE)
+		{
+			if(cls->wlist[i].mode==0)
+				cls->wlist[i].mode = s.s[3] | 32;
+			cls->wlist[i].prio = i;
+			cls->wlist[i].clist[cls->wlist[i].clen] = sc;
+			cls->wlist[i].clen++;
+		} else {
+			LM_WARN("too many write connections in cluster - con-id [%.*s]\n",
+					pit->name.len, pit->name.s);
+		}
+	}
+	return 0;
+error:
+	return -1;
+}
+
+int dbcl_init_cls(str *name, str *cons)
+{
+	dbcl_cls_t *sc;
+	unsigned int clsid;
+
+	clsid = core_case_hash(name, 0, 0);
+
+	sc = _dbcl_cls_root;
+	while(sc)
+	{
+		if(clsid==sc->clsid && sc->name.len==name->len
+				&& strncmp(sc->name.s, name->s, name->len)==0)
+		{
+			LM_ERR("duplicate cluster name\n");
+			return -1;
+		}
+		sc = sc->next;
+	}
+	sc = (dbcl_cls_t*)pkg_malloc(sizeof(dbcl_cls_t));
+	if(sc==NULL)
+	{
+		LM_ERR("no pkg memory\n");
+		return -1;
+	}
+	memset(sc, 0, sizeof(dbcl_cls_t));
+	sc->clsid = clsid;
+	sc->name = *name;
+	/* parse cls con list */
+	if(dbcl_cls_set_connections(sc, cons)<0)
+	{
+		LM_ERR("unable to add connections to cluster definition\n");
+		pkg_free(sc);
+		return -1;
+	}
+	sc->next = _dbcl_cls_root;
+	_dbcl_cls_root = sc;
+
+	return 0;
+}
+
+int dbcl_parse_cls_param(char *val)
+{
+	str name;
+	str tok;
+	str in;
+	char *p;
+
+	/* parse: name=>conlist*/
+	in.s = val;
+	in.len = strlen(in.s);
+	p = in.s;
+
+	while(p<in.s+in.len && (*p==' ' || *p=='\t' || *p=='\n' || *p=='\r'))
+		p++;
+	if(p>in.s+in.len || *p=='\0')
+		goto error;
+	name.s = p;
+	while(p < in.s + in.len)
+	{
+		if(*p=='=' || *p==' ' || *p=='\t' || *p=='\n' || *p=='\r')
+			break;
+		p++;
+	}
+	if(p>in.s+in.len || *p=='\0')
+		goto error;
+	name.len = p - name.s;
+	if(*p!='=')
+	{
+		while(p<in.s+in.len && (*p==' ' || *p=='\t' || *p=='\n' || *p=='\r'))
+			p++;
+		if(p>in.s+in.len || *p=='\0' || *p!='=')
+			goto error;
+	}
+	p++;
+	if(*p!='>')
+		goto error;
+	p++;
+	while(p<in.s+in.len && (*p==' ' || *p=='\t' || *p=='\n' || *p=='\r'))
+		p++;
+	tok.s = p;
+	tok.len = in.len + (int)(in.s - p);
+
+	LM_DBG("cluster: [%.*s] : con-list [%.*s]\n", name.len, name.s, tok.len, tok.s);
+
+	return dbcl_init_cls(&name, &tok);
+error:
+	LM_ERR("invalid cluster parameter [%.*s] at [%d]\n", in.len, in.s,
+			(int)(p-in.s));
+	return -1;
+}
+
+int dbcl_init_dbf(dbcl_cls_t *cls)
+{
+	int i;
+	int j;
+
+	for(i=1; i<DBCL_PRIO_SIZE; i++)
+	{
+		for(j=0; j<cls->rlist[i].clen; j++)
+		{
+			if(cls->rlist[i].clist[j] != NULL && cls->rlist[i].clist[j]->flags==0)
+			{
+				if(db_bind_mod(&cls->rlist[i].clist[j]->db_url,
+							&cls->rlist[i].clist[j]->dbf)<0)
+				{	
+					LM_ERR("unable to bind database module\n");
+					return -1;
+				}
+				cls->rlist[i].clist[j]->flags = 1;
+			}
+		}
+		for(j=0; j<cls->wlist[i].clen; j++)
+		{
+			if(cls->wlist[i].clist[j] != NULL && cls->wlist[i].clist[j]->flags==0)
+			{
+				if(db_bind_mod(&cls->wlist[i].clist[j]->db_url,
+							&cls->wlist[i].clist[j]->dbf)<0)
+				{	
+					LM_ERR("unable to bind database module\n");
+					return -1;
+				}
+				cls->wlist[i].clist[j]->flags = 1;
+			}
+		}
+
+	}
+	return 0;
+}
+
+int dbcl_init_connections(dbcl_cls_t *cls)
+{
+	int i;
+	int j;
+
+	for(i=1; i<DBCL_PRIO_SIZE; i++)
+	{
+		for(j=0; j<cls->rlist[i].clen; j++)
+		{
+			if(cls->rlist[i].clist[j] != NULL && cls->rlist[i].clist[j]->flags!=0)
+			{
+				LM_DBG("setting up read connection [%.*s]\n",
+							cls->rlist[i].clist[j]->name.len,
+							cls->rlist[i].clist[j]->name.s);
+				cls->rlist[i].clist[j]->dbh = 
+					cls->rlist[i].clist[j]->dbf.init(&cls->rlist[i].clist[j]->db_url);	
+				if(cls->rlist[i].clist[j]->dbh==NULL)
+				{
+					LM_WARN("cannot connect to database - connection [%.*s]\n",
+							cls->rlist[i].clist[j]->name.len,
+							cls->rlist[i].clist[j]->name.s);
+				}
+			}
+		}
+		for(j=0; j<cls->wlist[i].clen; j++)
+		{
+			if(cls->wlist[i].clist[j] != NULL && cls->wlist[i].clist[j]->flags!=0)
+			{
+				LM_DBG("setting up write connection [%.*s]\n",
+							cls->wlist[i].clist[j]->name.len,
+							cls->wlist[i].clist[j]->name.s);
+				cls->wlist[i].clist[j]->dbh = 
+					cls->wlist[i].clist[j]->dbf.init(&cls->wlist[i].clist[j]->db_url);	
+				if(cls->wlist[i].clist[j]->dbh==NULL)
+				{
+					LM_WARN("cannot connect to database - connection [%.*s]\n",
+							cls->wlist[i].clist[j]->name.len,
+							cls->wlist[i].clist[j]->name.s);
+				}
+			}
+		}
+
+	}
+	return 0;
+}
+
+int dbcl_close_connections(dbcl_cls_t *cls)
+{
+	int i;
+	int j;
+
+	if(cls->ref > 0)
+		return 0;
+	for(i=1; i<DBCL_PRIO_SIZE; i++)
+	{
+		for(j=0; j<cls->rlist[i].clen; j++)
+		{
+			if(cls->rlist[i].clist[j] != NULL && cls->rlist[i].clist[j]->flags!=0
+					&& cls->rlist[i].clist[j]->dbh != NULL)
+			{
+				
+				cls->rlist[i].clist[j]->dbf.close(cls->rlist[i].clist[j]->dbh);
+				cls->rlist[i].clist[j]->dbh = NULL;
+			}
+		}
+		for(j=0; j<cls->wlist[i].clen; j++)
+		{
+			if(cls->wlist[i].clist[j] != NULL && cls->wlist[i].clist[j]->flags!=0
+					&& cls->wlist[i].clist[j]->dbh != NULL)
+			{
+				cls->wlist[i].clist[j]->dbf.close(cls->wlist[i].clist[j]->dbh);
+				cls->wlist[i].clist[j]->dbh = NULL;
+			}
+		}
+
+	}
+	return 0;
+}

+ 89 - 0
modules_k/db_cluster/dbcl_data.h

@@ -0,0 +1,89 @@
+/*
+ * $Id$
+ *
+ * DB CLuster core functions
+ *
+ * Copyright (C) 2012 Daniel-Constantin Mierla (asipto.com)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*! \file
+ *  \brief DB_CLUSTER :: Core
+ *  \ingroup db_cluster
+ *  Module: \ref db_cluster
+ */
+
+
+
+#ifndef _DBCL_DATA_H_
+#define _DBCL_DATA_H_
+
+
+#include "../../lib/srdb1/db.h"
+#include "../../str.h"
+
+#define DBCL_PRIO_SIZE	10
+#define DBCL_CLIST_SIZE	5
+
+typedef struct dbcl_shared
+{
+	int state;
+	int count;
+} dbcl_shared_t;
+
+typedef struct dbcl_con
+{
+	str name;
+	unsigned int conid;
+	str db_url;
+	db1_con_t  *dbh;
+	db_func_t dbf;
+	int flags;
+	dbcl_shared_t *sinfo;
+	struct dbcl_con *next;
+} dbcl_con_t;
+
+typedef struct dbcl_cdata
+{
+	dbcl_con_t *clist[DBCL_CLIST_SIZE];
+	int clen;
+	int prio;
+	int mode;
+	int crt;
+} dbcl_cdata_t;
+
+typedef struct dbcl_cls
+{
+	str name;
+	unsigned int clsid;
+	unsigned int ref;
+	dbcl_cdata_t rlist[DBCL_PRIO_SIZE];
+	dbcl_cdata_t wlist[DBCL_PRIO_SIZE];
+	dbcl_con_t *usedcon;
+	struct dbcl_cls *next;
+} dbcl_cls_t;
+
+
+int dbcl_init_dbf(dbcl_cls_t *cls);
+int dbcl_init_connections(dbcl_cls_t *cls);
+int dbcl_close_connections(dbcl_cls_t *cls);
+dbcl_cls_t *dbcl_get_cluster(str *name);
+
+int dbcl_parse_con_param(char *val);
+int dbcl_parse_cls_param(char *val);
+#endif /* KM_DBASE_H */

+ 4 - 0
modules_k/db_cluster/doc/Makefile

@@ -0,0 +1,4 @@
+docs = db_cluster.xml
+
+docbook_dir = ../../../docbook
+include $(docbook_dir)/Makefile.module

+ 37 - 0
modules_k/db_cluster/doc/db_cluster.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+    <bookinfo>
+	<title>DB_CLUSTER Module</title>
+	<productname class="trade">sip-router.org</productname>
+	<authorgroup>
+	    <author>
+		<firstname>Daniel-Constantin</firstname>
+		<surname>Mierla</surname>
+		<email>[email protected]</email>
+	    </author>
+	    <editor>
+		<firstname>Daniel-Constantin</firstname>
+		<surname>Mierla</surname>
+		<email>[email protected]</email>
+	    </editor>
+	</authorgroup>
+	<copyright>
+	    <year>2012</year>
+	    <holder>asipto.com</holder>
+	</copyright>
+    </bookinfo>
+    <toc></toc>
+    
+    <xi:include href="db_cluster_admin.xml"/>
+    
+    
+</book>

+ 167 - 0
modules_k/db_cluster/doc/db_cluster_admin.xml

@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+<!-- Module User's Guide -->
+
+<chapter>
+	
+	<title>&adminguide;</title>
+	
+	<section>
+	<title>Overview</title>
+	<para>
+		This module provides a generic database clustering system. It can be
+		used as a middle layer between modules and database connectors.
+	</para>
+	<para>
+		Via clustering, database operations can be executed across multiple
+		servers, based on policies such as paraller write, serial or round
+		robin write and read.
+	</para>
+	<para>
+		Following database commands are considered to be write operations:
+		INSERT, DELETE, UPDATE, REPLACE, INSERT-DELAYED, INSERT-UPDATE. The
+		read operations are done for database commands: QUERY and RAW-QUERY.
+	</para>
+	</section>
+
+	<section>
+	<title>Dependencies</title>
+	<section>
+		<title>&kamailio; Modules</title>
+		<para>
+		The following modules must be loaded before this module:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>db connector</emphasis> - database connectors.
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	<section>
+		<title>External Libraries or Applications</title>
+		<para>
+		The following libraries or applications must be installed before running
+		&kamailio; with this module loaded:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>None</emphasis>
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	</section>
+	<section>
+	<title>Parameters</title>
+	<section>
+		<title><varname>connection</varname> (int)</title>
+		<para>
+			Specify the connection to a real database system. The format is
+			'conid=>DBURL' - providing a connection id and the database
+			URL.
+		</para>
+		<para>
+		<emphasis>
+			Default value is NULL.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>connection</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("db_cluster", "connection",
+             "con1=>mysql://openser:openser@localhost/kamailio1")
+modparam("db_cluster", "connection",
+             "con2=>mysql://openser:openser@localhost/kamailio2")
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>cluster</varname> (int)</title>
+		<para>
+			Specify the cluster definition. The format is
+			'clsid=>conid1=def1;conid2=def2' - providing a cluster id and the list of
+			database connections to be used. For each connection you have to provide
+			a usage definition. The usage definition is a 4-char long string,
+			specifying priority and command mode for read an write operations to be
+			done on that connection.
+		</para>
+		<para>
+			The priority is a digit between 0 and 9, higher value is higher priority.
+			Priority 0 means that connection is not going to be used in that cluster.
+		</para>
+		<para>
+			Command mode is a character among s, r and p. s is for doing serial
+			operations (try first and if fails, try next); r is for doing round
+			robin operations; p - is for doing parallel operations (this is valid
+			only for write operations).
+		</para>
+		<para>
+		<emphasis>
+			Default value is NULL.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>cluster</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("db_cluster", "cluster", "cls1=>con1=9s8p;con2=9s8p")
+...
+</programlisting>
+		</example>
+	</section>
+	</section>
+	<section>
+		<title>Usage</title>
+		<para>
+			Practically, all the modules that want to use a cluster, have to set
+			their db_url parameter to "cluster://clusterid".
+		</para>
+		<para>
+			Following rules apply when doing DB commands: the connecions with highest
+			priority are chosen first and the operations are performed according
+			to the command mode. Note that for same priority, only one command mode
+			is used (the one from the first connection with that priority found
+			in the definition of the cluster). If the DB command is not successful,
+			next set of connections based on priority is selected and the command is
+			tried again. When the command is successful, no other try is made.
+		</para>
+		<para>
+			For parallel operations, a command is considered successful if it
+			succeeded on one connection from a group with same priority.
+		</para>
+		<para>
+			Next example shows how to set a cluster with two connections to MySQL
+			to be used for parallel writing from acc and round-robin reading by
+			sqlops.
+		</para>
+		<example>
+		<title>Set <varname>cluster</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("db_cluster", "connection",
+             "c1=&gt;mysql://openser:openserrw@localhost/kamailio1")
+modparam("db_cluster", "connection",
+             "c2=&gt;mysql://openser:openserrw@localhost/kamailio2")
+modparam("db_cluster", "cluster", "k1=&gt;c1=9r9p;c2=9r9p")
+
+modparam("acc", "db_url", "cluster://k1")
+
+modparam("sqlops", "sqlcon", "ca=&gt;cluster://k1")
+...
+</programlisting>
+		</example>
+	</section>
+</chapter>
+