This module implements a generic db driver for kamailio. It
requires a "schema" and "key" definition of "tables" and corresponding
keys for redis in the kamailio config file, otherwise it's supposed to
work with every module.
Implemented methods are query (w/o order-by), insert, update, delete.
Tested with usrloc and acc.
... | ... |
@@ -153,7 +153,7 @@ mod_list_jansson=jansson |
153 | 153 |
mod_list_jansson_event=janssonrpcc |
154 | 154 |
|
155 | 155 |
# - modules depending on redis library |
156 |
-mod_list_redis=ndb_redis topos_redis |
|
156 |
+mod_list_redis=db_redis ndb_redis topos_redis |
|
157 | 157 |
|
158 | 158 |
# - modules depending on mono library |
159 | 159 |
mod_list_mono=app_mono |
160 | 160 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,31 @@ |
1 |
+# |
|
2 |
+# WARNING: do not run this directly, it should be run by the master Makefile |
|
3 |
+ |
|
4 |
+include ../../Makefile.defs |
|
5 |
+auto_gen= |
|
6 |
+NAME=db_redis.so |
|
7 |
+ |
|
8 |
+ifeq ($(CROSS_COMPILE),) |
|
9 |
+HIREDIS_BUILDER = $(shell \ |
|
10 |
+ if pkg-config --exists hiredis; then \ |
|
11 |
+ echo 'pkg-config hiredis'; \ |
|
12 |
+ fi) |
|
13 |
+endif |
|
14 |
+ |
|
15 |
+ifeq ($(HIREDIS_BUILDER),) |
|
16 |
+ HIREDISDEFS=-I$(LOCALBASE)/include |
|
17 |
+ HIREDISLIBS=-L$(LOCALBASE)/lib -lhiredis |
|
18 |
+else |
|
19 |
+ HIREDISDEFS = $(shell $(HIREDIS_BUILDER) --cflags) |
|
20 |
+ HIREDISLIBS = $(shell $(HIREDIS_BUILDER) --libs) |
|
21 |
+endif |
|
22 |
+ |
|
23 |
+DEFS+=$(HIREDISDEFS) |
|
24 |
+LIBS=$(HIREDISLIBS) |
|
25 |
+ |
|
26 |
+DEFS+=-DKAMAILIO_MOD_INTERFACE |
|
27 |
+ |
|
28 |
+SERLIBPATH=../../lib |
|
29 |
+SER_LIBS=$(SERLIBPATH)/srdb2/srdb2 $(SERLIBPATH)/srdb1/srdb1 |
|
30 |
+ |
|
31 |
+include ../../Makefile.modules |
0 | 32 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,199 @@ |
1 |
+DB_REDIS Module |
|
2 |
+ |
|
3 |
+Andreas Granig |
|
4 |
+ |
|
5 |
+ <agranig@sipwise.com> |
|
6 |
+ |
|
7 |
+Edited by |
|
8 |
+ |
|
9 |
+Andreas Granig |
|
10 |
+ |
|
11 |
+ <agranig@sipwise.com> |
|
12 |
+ |
|
13 |
+ Copyright © 2018 sipwise.com |
|
14 |
+ __________________________________________________________________ |
|
15 |
+ |
|
16 |
+ Table of Contents |
|
17 |
+ |
|
18 |
+ 1. Admin Guide |
|
19 |
+ |
|
20 |
+ 1. Overview |
|
21 |
+ |
|
22 |
+ 1.1. Limitations |
|
23 |
+ |
|
24 |
+ 2. Dependencies |
|
25 |
+ |
|
26 |
+ 2.1. Kamailio Modules |
|
27 |
+ 2.2. Parameters |
|
28 |
+ |
|
29 |
+ 2.2.1. schema (string) |
|
30 |
+ 2.2.2. keys (string) |
|
31 |
+ |
|
32 |
+ 2.3. External Libraries or Applications |
|
33 |
+ |
|
34 |
+ 3. Usage |
|
35 |
+ |
|
36 |
+ List of Examples |
|
37 |
+ |
|
38 |
+ 1.1. Setting schema module parameter |
|
39 |
+ 1.2. Setting keys module parameter |
|
40 |
+ 1.3. Usage |
|
41 |
+ |
|
42 |
+Chapter 1. Admin Guide |
|
43 |
+ |
|
44 |
+ Table of Contents |
|
45 |
+ |
|
46 |
+ 1. Overview |
|
47 |
+ |
|
48 |
+ 1.1. Limitations |
|
49 |
+ |
|
50 |
+ 2. Dependencies |
|
51 |
+ |
|
52 |
+ 2.1. Kamailio Modules |
|
53 |
+ 2.2. Parameters |
|
54 |
+ |
|
55 |
+ 2.2.1. schema (string) |
|
56 |
+ 2.2.2. keys (string) |
|
57 |
+ |
|
58 |
+ 2.3. External Libraries or Applications |
|
59 |
+ |
|
60 |
+ 3. Usage |
|
61 |
+ |
|
62 |
+1. Overview |
|
63 |
+ |
|
64 |
+ 1.1. Limitations |
|
65 |
+ |
|
66 |
+ This module provides a DB APIv1 connector for Redis server. |
|
67 |
+ |
|
68 |
+ It can be used as a replacement for other database modules such as |
|
69 |
+ db_mysql and db_postgres. Not all the specs of DB APIv1 are |
|
70 |
+ implemented, thus the usage of this module might be restricted to |
|
71 |
+ specific modules. Also, for proper performance, the module needs |
|
72 |
+ particular configuration tailored to the using modules. |
|
73 |
+ |
|
74 |
+ Since Redis does not provide a schema, a schema has to be defined as |
|
75 |
+ module parameter "schema". The schema definition is composed of a |
|
76 |
+ semi-column separated list of table definitions in format |
|
77 |
+ <table-name>=<column-name>/<type>[<column-name>/<type> ...]. |
|
78 |
+ |
|
79 |
+ Example: |
|
80 |
+ version=table_name/string,table_version/int;location=username/string,dom |
|
81 |
+ain/string,contact/string,received/string,path/string,expires/timestamp,q/double |
|
82 |
+,callid/string,cseq/int,last_modified/timestamp,flags/int,cflags/int,user_agent/ |
|
83 |
+string,socket/string,methods/int,ruid/string,reg_id/int,instance/string,server_i |
|
84 |
+d/int,connection_id/int,keepalive/int,partition/int |
|
85 |
+ |
|
86 |
+ Also since Redis is a key-value store with keys having to be unique, |
|
87 |
+ tables and rows e.g. from MySQL can not be ported 1:1 to Redis. For |
|
88 |
+ instance, usrloc relies on a key "username@domain", but it must not be |
|
89 |
+ unique for being able to store multiple contacts per AoR. Thus, |
|
90 |
+ db_redis supports mapping sets in a way for example for usrloc to have |
|
91 |
+ a set with a key "username@domain", with its entries being unique keys |
|
92 |
+ per contact being the ruid of a contact. Thus, one contact in usrloc |
|
93 |
+ consists of a unique key "location:entry::example-ruid-1" being a hash |
|
94 |
+ with the columns like username, domain, contact, path etc. In addition, |
|
95 |
+ this unique key is stored in a set |
|
96 |
+ "location:usrdom::exampleuser:exampledomain.org". When usrloc does a |
|
97 |
+ lookup based on "username@domain", db_redis figures out via the |
|
98 |
+ keys/values the query is constructed by usrloc to look for the final |
|
99 |
+ entry key in the mapping set first, then querying the actual entries |
|
100 |
+ from there, avoiding full table scans. For usrloc, the same holds true |
|
101 |
+ for exipired contacts, requiring a different kind of mapping. There is |
|
102 |
+ a certain balance of read performance vs. write performance to |
|
103 |
+ consider, because inserts and deletes also have to maintain the |
|
104 |
+ mappings, in favor of much faster selects. The mappings can be freely |
|
105 |
+ defined, so even though other kamailio modules don't require a specific |
|
106 |
+ mapping to be in place for proper performance, mappings could be |
|
107 |
+ defined for external applications to read faster (for instance letting |
|
108 |
+ the acc module also write mappings besides the actual records for |
|
109 |
+ billing systems to correlate start and stop records faster). |
|
110 |
+ |
|
111 |
+ The mappings can be freely defined in the "keys" module parameter. It |
|
112 |
+ is composed of a semi-colon separated list of definitions in format |
|
113 |
+ <table-name>=<entry>:<column-name>[&<map-name>:<column-name>,<column-na |
|
114 |
+ me>...] |
|
115 |
+ |
|
116 |
+ Example: |
|
117 |
+ version=entry:table_name;location=entry:ruid&usrdom:username,domain&time |
|
118 |
+r:partition,keepalive;acc=entry:callid,time_hires&cid:callid |
|
119 |
+ |
|
120 |
+ Note that as of now, you have to have version information in your Redis |
|
121 |
+ db, similar to your MySQL schema. To insert table versions (e.g. for |
|
122 |
+ usrloc and acc), execute the following: |
|
123 |
+ # redis-cli -h $host -n $dbnumber HMSET version:entry::location table_ve |
|
124 |
+rsion 8 |
|
125 |
+ # redis-cli -h $host -n $dbnumber HMSET version:entry::acc table_version |
|
126 |
+ 5 |
|
127 |
+ |
|
128 |
+ You can read more about Redis at: https://www.redis.io. |
|
129 |
+ |
|
130 |
+1.1. Limitations |
|
131 |
+ |
|
132 |
+ * This module has implemented the equivalent operations for INSERT, |
|
133 |
+ UPDATE, DELETE and SELECT. The ORDER BY for SELECT is not |
|
134 |
+ implemented. Raw query is not implemented inside this module, use |
|
135 |
+ db_redis for sending any kind of command to a Redis server. |
|
136 |
+ |
|
137 |
+2. Dependencies |
|
138 |
+ |
|
139 |
+ 2.1. Kamailio Modules |
|
140 |
+ 2.2. Parameters |
|
141 |
+ |
|
142 |
+ 2.2.1. schema (string) |
|
143 |
+ 2.2.2. keys (string) |
|
144 |
+ |
|
145 |
+ 2.3. External Libraries or Applications |
|
146 |
+ |
|
147 |
+2.1. Kamailio Modules |
|
148 |
+ |
|
149 |
+ The following modules must be loaded before this module: |
|
150 |
+ * none. |
|
151 |
+ |
|
152 |
+2.2. Parameters |
|
153 |
+ |
|
154 |
+2.2.1. schema (string) |
|
155 |
+ |
|
156 |
+ The schema of your tables. |
|
157 |
+ |
|
158 |
+ Example 1.1. Setting schema module parameter |
|
159 |
+modparam("db_redis", "schema", "version=table_name/string,table_version/int;loca |
|
160 |
+tion=username/string,domain/string,contact/string,received/string,path/string,ex |
|
161 |
+pires/timestamp,q/double,callid/string,cseq/int,last_modified/timestamp,flags/in |
|
162 |
+t,cflags/int,user_agent/string,socket/string,methods/int,ruid/string,reg_id/int, |
|
163 |
+instance/string,server_id/int,connection_id/int,keepalive/int,partition/int") |
|
164 |
+ |
|
165 |
+2.2.2. keys (string) |
|
166 |
+ |
|
167 |
+ The lookup and mapping keys of your tables. |
|
168 |
+ |
|
169 |
+ Example 1.2. Setting keys module parameter |
|
170 |
+modparam("db_redis", "keys", "version=entry:table_name;location=entry:ruid&usrdo |
|
171 |
+m:username,domain&timer:partition,keepalive") |
|
172 |
+ |
|
173 |
+2.3. External Libraries or Applications |
|
174 |
+ |
|
175 |
+ The following libraries or applications must be installed before |
|
176 |
+ running Kamailio with this module loaded: |
|
177 |
+ * hiredis - available at https://github.com/redis/hiredis |
|
178 |
+ |
|
179 |
+3. Usage |
|
180 |
+ |
|
181 |
+ Load the module and set the the DB URL for specific modules to: |
|
182 |
+ redis://[username]@host:port/database. Username is optional. Database |
|
183 |
+ must be a valid redis database number. |
|
184 |
+ |
|
185 |
+ Example 1.3. Usage |
|
186 |
+... |
|
187 |
+loadmodule "db_redis.so" |
|
188 |
+... |
|
189 |
+#!define DBURL "redis://127.0.0.1:6379/5" |
|
190 |
+... |
|
191 |
+modparam("db_redis", "schema", "version=table_name/string,table_version/int;loca |
|
192 |
+tion=username/string,domain/string,contact/string,received/string,path/string,ex |
|
193 |
+pires/timestamp,q/double,callid/string,cseq/int,last_modified/timestamp,flags/in |
|
194 |
+t,cflags/int,user_agent/string,socket/string,methods/int,ruid/string,reg_id/int, |
|
195 |
+instance/string,server_id/int,connection_id/int,keepalive/int,partition/int") |
|
196 |
+modparam("db_redis", "keys", "version=entry:table_name;location=entry:ruid&usrdo |
|
197 |
+m:username,domain&timer:partition,keepalive") |
|
198 |
+modparam("usrloc", "db_url", DBURL) |
|
199 |
+... |
0 | 200 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,105 @@ |
1 |
+/** |
|
2 |
+ * Copyright (C) 2018 Andreas Granig (sipwise.com) |
|
3 |
+ * |
|
4 |
+ * This file is part of Kamailio, a free SIP server. |
|
5 |
+ * |
|
6 |
+ * Kamailio is free software; you can redistribute it and/or modify |
|
7 |
+ * it under the terms of the GNU General Public License as published by |
|
8 |
+ * the Free Software Foundation; either version 2 of the License, or |
|
9 |
+ * (at your option) any later version |
|
10 |
+ * |
|
11 |
+ * Kamailio is distributed in the hope that it will be useful, |
|
12 |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 |
+ * GNU General Public License for more details. |
|
15 |
+ * |
|
16 |
+ * You should have received a copy of the GNU General Public License |
|
17 |
+ * along with this program; if not, write to the Free Software |
|
18 |
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
19 |
+ * |
|
20 |
+ */ |
|
21 |
+ |
|
22 |
+#define DB_REDIS_DEBUG |
|
23 |
+ |
|
24 |
+#include <string.h> |
|
25 |
+#include <stdlib.h> |
|
26 |
+#include <stdio.h> |
|
27 |
+ |
|
28 |
+#include "db_redis_mod.h" |
|
29 |
+#include "redis_dbase.h" |
|
30 |
+ |
|
31 |
+MODULE_VERSION |
|
32 |
+ |
|
33 |
+str redis_keys = str_init(""); |
|
34 |
+str redis_schema = str_init(""); |
|
35 |
+ |
|
36 |
+static int db_redis_bind_api(db_func_t *dbb); |
|
37 |
+static int mod_init(void); |
|
38 |
+static void mod_destroy(void); |
|
39 |
+ |
|
40 |
+static cmd_export_t cmds[] = { |
|
41 |
+ {"db_bind_api", (cmd_function)db_redis_bind_api, 0, 0, 0, 0}, |
|
42 |
+ {0, 0, 0, 0, 0, 0} |
|
43 |
+}; |
|
44 |
+ |
|
45 |
+ |
|
46 |
+/* |
|
47 |
+ * Exported parameters |
|
48 |
+ */ |
|
49 |
+static param_export_t params[] = { |
|
50 |
+ {"keys", PARAM_STR, &redis_keys }, |
|
51 |
+ {"schema", PARAM_STR, &redis_schema }, |
|
52 |
+ {0, 0, 0} |
|
53 |
+}; |
|
54 |
+ |
|
55 |
+ |
|
56 |
+struct module_exports exports = { |
|
57 |
+ "db_redis", |
|
58 |
+ DEFAULT_DLFLAGS, /* dlopen flags */ |
|
59 |
+ cmds, |
|
60 |
+ params, /* module parameters */ |
|
61 |
+ 0, /* exported statistics */ |
|
62 |
+ 0, /* exported MI functions */ |
|
63 |
+ 0, /* exported pseudo-variables */ |
|
64 |
+ 0, /* extra processes */ |
|
65 |
+ mod_init, /* module initialization function */ |
|
66 |
+ 0, /* response function*/ |
|
67 |
+ mod_destroy, /* destroy function */ |
|
68 |
+ 0 /* per-child init function */ |
|
69 |
+}; |
|
70 |
+ |
|
71 |
+static int db_redis_bind_api(db_func_t *dbb) { |
|
72 |
+ if(dbb==NULL) |
|
73 |
+ return -1; |
|
74 |
+ |
|
75 |
+ memset(dbb, 0, sizeof(db_func_t)); |
|
76 |
+ |
|
77 |
+ dbb->use_table = db_redis_use_table; |
|
78 |
+ dbb->init = db_redis_init; |
|
79 |
+ dbb->close = db_redis_close; |
|
80 |
+ dbb->query = db_redis_query; |
|
81 |
+ dbb->fetch_result = 0; //db_redis_fetch_result; |
|
82 |
+ dbb->raw_query = 0; //db_redis_raw_query; |
|
83 |
+ dbb->free_result = db_redis_free_result; |
|
84 |
+ dbb->insert = db_redis_insert; |
|
85 |
+ dbb->delete = db_redis_delete; |
|
86 |
+ dbb->update = db_redis_update; |
|
87 |
+ dbb->replace = 0; //db_redis_replace; |
|
88 |
+ |
|
89 |
+ return 0; |
|
90 |
+} |
|
91 |
+ |
|
92 |
+int mod_register(char *path, int *dlflags, void *p1, void *p2) { |
|
93 |
+ if(db_api_init()<0) |
|
94 |
+ return -1; |
|
95 |
+ return 0; |
|
96 |
+} |
|
97 |
+ |
|
98 |
+static int mod_init(void) { |
|
99 |
+ LM_DBG("module initializing\n"); |
|
100 |
+ return 0; |
|
101 |
+} |
|
102 |
+ |
|
103 |
+static void mod_destroy(void) { |
|
104 |
+ LM_DBG("module destroying\n"); |
|
105 |
+} |
|
0 | 106 |
\ No newline at end of file |
1 | 107 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,53 @@ |
1 |
+/* |
|
2 |
+ * Copyright (C) 2018 Andreas Granig (sipwise.com) |
|
3 |
+ * |
|
4 |
+ * This file is part of Kamailio, a free SIP server. |
|
5 |
+ * |
|
6 |
+ * Kamailio is free software; you can redistribute it and/or modify |
|
7 |
+ * it under the terms of the GNU General Public License as published by |
|
8 |
+ * the Free Software Foundation; either version 2 of the License, or |
|
9 |
+ * (at your option) any later version |
|
10 |
+ * |
|
11 |
+ * Kamailio is distributed in the hope that it will be useful, |
|
12 |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 |
+ * GNU General Public License for more details. |
|
15 |
+ * |
|
16 |
+ * You should have received a copy of the GNU General Public License |
|
17 |
+ * along with this program; if not, write to the Free Software |
|
18 |
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
19 |
+ * |
|
20 |
+ */ |
|
21 |
+ |
|
22 |
+ |
|
23 |
+#ifndef _DB_REDIS_MOD_H |
|
24 |
+#define _DB_REDIS_MOD_H |
|
25 |
+ |
|
26 |
+#include "../../lib/srdb1/db.h" |
|
27 |
+#include "../../lib/srdb1/db_ut.h" |
|
28 |
+#include "../../lib/srdb1/db_query.h" |
|
29 |
+#include "../../lib/srdb1/db_pool.h" |
|
30 |
+#include "../../lib/srdb1/db_id.h" |
|
31 |
+#include "../../lib/srdb1/db_con.h" |
|
32 |
+#include "../../lib/srdb1/db_res.h" |
|
33 |
+#include "../../lib/srdb1/db_key.h" |
|
34 |
+#include "../../lib/srdb1/db_op.h" |
|
35 |
+#include "../../lib/srdb1/db_val.h" |
|
36 |
+ |
|
37 |
+#include "../../core/mem/mem.h" |
|
38 |
+ |
|
39 |
+#include "../../core/dprint.h" |
|
40 |
+#include "../../core/sr_module.h" |
|
41 |
+#include "../../core/str.h" |
|
42 |
+#include "../../core/str_hash.h" |
|
43 |
+#include "../../core/ut.h" |
|
44 |
+ |
|
45 |
+#define REDIS_DIRECT_PREFIX "entry" |
|
46 |
+#define REDIS_DIRECT_PREFIX_LEN 5 |
|
47 |
+ |
|
48 |
+#define REDIS_HT_SIZE 8 |
|
49 |
+ |
|
50 |
+extern str redis_keys; |
|
51 |
+extern str redis_schema; |
|
52 |
+ |
|
53 |
+#endif /* _DB_REDIS_MOD_H */ |
0 | 5 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,37 @@ |
1 |
+<?xml version="1.0" encoding='ISO-8859-1'?> |
|
2 |
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" |
|
3 |
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [ |
|
4 |
+ |
|
5 |
+<!-- Include general documentation entities --> |
|
6 |
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml"> |
|
7 |
+%docentities; |
|
8 |
+ |
|
9 |
+]> |
|
10 |
+ |
|
11 |
+<book xmlns:xi="http://www.w3.org/2001/XInclude"> |
|
12 |
+ <bookinfo> |
|
13 |
+ <title>DB_REDIS Module</title> |
|
14 |
+ <productname class="trade">kamailio.org</productname> |
|
15 |
+ <authorgroup> |
|
16 |
+ <author> |
|
17 |
+ <firstname>Andreas</firstname> |
|
18 |
+ <surname>Granig</surname> |
|
19 |
+ <email>agranig@sipwise.com</email> |
|
20 |
+ </author> |
|
21 |
+ <editor> |
|
22 |
+ <firstname>Andreas</firstname> |
|
23 |
+ <surname>Granig</surname> |
|
24 |
+ <email>agranig@sipwise.com</email> |
|
25 |
+ </editor> |
|
26 |
+ </authorgroup> |
|
27 |
+ <copyright> |
|
28 |
+ <year>2018</year> |
|
29 |
+ <holder>sipwise.com</holder> |
|
30 |
+ </copyright> |
|
31 |
+ </bookinfo> |
|
32 |
+ <toc></toc> |
|
33 |
+ |
|
34 |
+ <xi:include href="db_redis_admin.xml"/> |
|
35 |
+ |
|
36 |
+ |
|
37 |
+</book> |
0 | 38 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,190 @@ |
1 |
+<?xml version="1.0" encoding='ISO-8859-1'?> |
|
2 |
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" |
|
3 |
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [ |
|
4 |
+ |
|
5 |
+<!-- Include general documentation entities --> |
|
6 |
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml"> |
|
7 |
+%docentities; |
|
8 |
+ |
|
9 |
+]> |
|
10 |
+<!-- Module User's Guide --> |
|
11 |
+ |
|
12 |
+<chapter> |
|
13 |
+ |
|
14 |
+ <title>&adminguide;</title> |
|
15 |
+ |
|
16 |
+ <section> |
|
17 |
+ <title>Overview</title> |
|
18 |
+ <para> |
|
19 |
+ This module provides a DB APIv1 connector for Redis server. |
|
20 |
+ </para> |
|
21 |
+ <para> |
|
22 |
+ It can be used as a replacement for other database modules such as |
|
23 |
+ db_mysql and db_postgres. Not all the specs of DB APIv1 are |
|
24 |
+ implemented, thus the usage of this module might be restricted to |
|
25 |
+ specific modules. Also, for proper performance, the module needs |
|
26 |
+ particular configuration tailored to the using modules. |
|
27 |
+ </para> |
|
28 |
+ <para> |
|
29 |
+ Since Redis does not provide a schema, a schema has to be defined as |
|
30 |
+ module parameter "schema". The schema definition is composed of a |
|
31 |
+ semi-column separated list of table definitions in format |
|
32 |
+ <table-name>=<column-name>/<type>[,<column-name>/<type> ...]. |
|
33 |
+ </para> |
|
34 |
+ <para> |
|
35 |
+ Example: |
|
36 |
+ <programlisting format="linespecific"> |
|
37 |
+ version=table_name/string,table_version/int;location=username/string,domain/string,contact/string,received/string,path/string,expires/timestamp,q/double,callid/string,cseq/int,last_modified/timestamp,flags/int,cflags/int,user_agent/string,socket/string,methods/int,ruid/string,reg_id/int,instance/string,server_id/int,connection_id/int,keepalive/int,partition/int |
|
38 |
+ </programlisting> |
|
39 |
+ </para> |
|
40 |
+ <para> |
|
41 |
+ Also since Redis is a key-value store with keys having to be unique, |
|
42 |
+ tables and rows e.g. from MySQL can not be ported 1:1 to Redis. For |
|
43 |
+ instance, usrloc relies on a key "username@domain", but it must not be |
|
44 |
+ unique for being able to store multiple contacts per AoR. Thus, db_redis |
|
45 |
+ supports mapping sets in a way for example for usrloc to have a set with |
|
46 |
+ a key "username@domain", with its entries being unique keys per contact being |
|
47 |
+ the ruid of a contact. Thus, one contact in usrloc consists of a unique |
|
48 |
+ key "location:entry::example-ruid-1" being a hash with the columns like |
|
49 |
+ username, domain, contact, path etc. In addition, this unique key is stored |
|
50 |
+ in a set "location:usrdom::exampleuser:exampledomain.org". When usrloc does |
|
51 |
+ a lookup based on "username@domain", db_redis figures out via the keys/values |
|
52 |
+ the query is constructed by usrloc to look for the final entry key in the |
|
53 |
+ mapping set first, then querying the actual entries from there, avoiding full |
|
54 |
+ table scans. For usrloc, the same holds true for expired contacts, requiring |
|
55 |
+ a different kind of mapping. There is a certain balance of read performance |
|
56 |
+ vs. write performance to consider, because inserts and deletes also have to |
|
57 |
+ maintain the mappings, in favor of much faster selects. The mappings can be |
|
58 |
+ freely defined, so even though other kamailio modules don't require a specific |
|
59 |
+ mapping to be in place for proper performance, mappings could be defined |
|
60 |
+ for external applications to read faster (for instance letting the acc module |
|
61 |
+ also write mappings besides the actual records for billing systems to |
|
62 |
+ correlate start and stop records faster). |
|
63 |
+ </para> |
|
64 |
+ <para> |
|
65 |
+ The mappings can be freely defined in the "keys" module parameter. It is |
|
66 |
+ composed of a semi-colon separated list of definitions in format |
|
67 |
+ <table-name>=<entry>:<column-name>[&<map-name>:<column-name>,<column-name>...]. |
|
68 |
+ Each table must at least have an "entry" key for db_redis to be able to store data. |
|
69 |
+ </para> |
|
70 |
+ <para> |
|
71 |
+ Example: |
|
72 |
+ <programlisting format="linespecific"> |
|
73 |
+ version=entry:table_name;location=entry:ruid&usrdom:username,domain&timer:partition,keepalive;acc=entry:callid,time_hires&cid:callid |
|
74 |
+ </programlisting> |
|
75 |
+ </para> |
|
76 |
+ <para> |
|
77 |
+ Note that as of now, you have to have version information in your Redis db, |
|
78 |
+ similar to your MySQL schema. To insert table versions (e.g. for usrloc and acc), |
|
79 |
+ execute the following: |
|
80 |
+ </para> |
|
81 |
+ <programlisting format="linespecific"> |
|
82 |
+ # redis-cli -h $host -n $dbnumber HMSET version:entry::location table_version 8 |
|
83 |
+ # redis-cli -h $host -n $dbnumber HMSET version:entry::acc table_version 5 |
|
84 |
+ </programlisting> |
|
85 |
+ |
|
86 |
+ <para> |
|
87 |
+ You can read more about Redis at: |
|
88 |
+ <ulink url="https://www.redis.io">https://www.redis.io</ulink>. |
|
89 |
+ </para> |
|
90 |
+ |
|
91 |
+ <section> |
|
92 |
+ <title>Limitations</title> |
|
93 |
+ <itemizedlist> |
|
94 |
+ <listitem> |
|
95 |
+ <para> |
|
96 |
+ This module has implemented the equivalent operations for INSERT, |
|
97 |
+ UPDATE, DELETE and SELECT. The ORDER BY for SELECT is not implemented. |
|
98 |
+ Raw query is not implemented inside this module, use ndb_redis for sending any |
|
99 |
+ kind of command to a Redis server. |
|
100 |
+ </para> |
|
101 |
+ </listitem> |
|
102 |
+ </itemizedlist> |
|
103 |
+ </section> |
|
104 |
+ </section> |
|
105 |
+ |
|
106 |
+ <section> |
|
107 |
+ <title>Dependencies</title> |
|
108 |
+ <section> |
|
109 |
+ <title>&kamailio; Modules</title> |
|
110 |
+ <para> |
|
111 |
+ The following modules must be loaded before this module: |
|
112 |
+ <itemizedlist> |
|
113 |
+ <listitem> |
|
114 |
+ <para> |
|
115 |
+ <emphasis>none</emphasis>. |
|
116 |
+ </para> |
|
117 |
+ </listitem> |
|
118 |
+ </itemizedlist> |
|
119 |
+ </para> |
|
120 |
+ </section> |
|
121 |
+ |
|
122 |
+ <section> |
|
123 |
+ <title>Parameters</title> |
|
124 |
+ <section> |
|
125 |
+ <title><varname>schema</varname> (string)</title> |
|
126 |
+ <para> |
|
127 |
+ The schema of your tables. |
|
128 |
+ </para> |
|
129 |
+ <example> |
|
130 |
+ <title>Setting schema module parameter</title> |
|
131 |
+ <programlisting format="linespecific"> |
|
132 |
+modparam("db_redis", "schema", "version=table_name/string,table_version/int;location=username/string,domain/string,contact/string,received/string,path/string,expires/timestamp,q/double,callid/string,cseq/int,last_modified/timestamp,flags/int,cflags/int,user_agent/string,socket/string,methods/int,ruid/string,reg_id/int,instance/string,server_id/int,connection_id/int,keepalive/int,partition/int") |
|
133 |
+ </programlisting> |
|
134 |
+ </example> |
|
135 |
+ </section> |
|
136 |
+ <section> |
|
137 |
+ <title><varname>keys</varname> (string)</title> |
|
138 |
+ <para> |
|
139 |
+ The entry and mapping keys of your tables. |
|
140 |
+ </para> |
|
141 |
+ <example> |
|
142 |
+ <title>Setting keys module parameter</title> |
|
143 |
+ <programlisting format="linespecific"> |
|
144 |
+modparam("db_redis", "keys", "version=entry:table_name;location=entry:ruid&usrdom:username,domain&timer:partition,keepalive") |
|
145 |
+ </programlisting> |
|
146 |
+ </example> |
|
147 |
+ </section> |
|
148 |
+ </section> |
|
149 |
+ |
|
150 |
+ <section> |
|
151 |
+ <title>External Libraries or Applications</title> |
|
152 |
+ <para> |
|
153 |
+ The following libraries or applications must be installed before running |
|
154 |
+ &kamailio; with this module loaded: |
|
155 |
+ <itemizedlist> |
|
156 |
+ <listitem> |
|
157 |
+ <para> |
|
158 |
+ <emphasis>hiredis</emphasis> - available at |
|
159 |
+ <ulink url="https://github.com/redis/hiredis">https://github.com/redis/hiredis</ulink> |
|
160 |
+ </para> |
|
161 |
+ </listitem> |
|
162 |
+ </itemizedlist> |
|
163 |
+ </para> |
|
164 |
+ </section> |
|
165 |
+ </section> |
|
166 |
+ |
|
167 |
+ <section> |
|
168 |
+ <title>Usage</title> |
|
169 |
+ <para> |
|
170 |
+ Load the module and set the the DB URL for specific modules to: |
|
171 |
+ redis://[username]@host:port/database. Username is optional. |
|
172 |
+ Database must be a valid redis database number. |
|
173 |
+ </para> |
|
174 |
+ <example> |
|
175 |
+ <title>Usage</title> |
|
176 |
+ <programlisting format="linespecific"> |
|
177 |
+... |
|
178 |
+loadmodule "db_redis.so" |
|
179 |
+... |
|
180 |
+#!define DBURL "redis://127.0.0.1:6379/5" |
|
181 |
+... |
|
182 |
+modparam("db_redis", "schema", "version=table_name/string,table_version/int;location=username/string,domain/string,contact/string,received/string,path/string,expires/timestamp,q/double,callid/string,cseq/int,last_modified/timestamp,flags/int,cflags/int,user_agent/string,socket/string,methods/int,ruid/string,reg_id/int,instance/string,server_id/int,connection_id/int,keepalive/int,partition/int") |
|
183 |
+modparam("db_redis", "keys", "version=entry:table_name;location=entry:ruid&usrdom:username,domain&timer:partition,keepalive") |
|
184 |
+modparam("usrloc", "db_url", DBURL) |
|
185 |
+... |
|
186 |
+</programlisting> |
|
187 |
+ </example> |
|
188 |
+ </section> |
|
189 |
+</chapter> |
|
190 |
+ |
0 | 191 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,293 @@ |
1 |
+/* |
|
2 |
+ * Copyright (C) 2018 Andreas Granig (sipwise.com) |
|
3 |
+ * |
|
4 |
+ * This file is part of Kamailio, a free SIP server. |
|
5 |
+ * |
|
6 |
+ * Kamailio is free software; you can redistribute it and/or modify |
|
7 |
+ * it under the terms of the GNU General Public License as published by |
|
8 |
+ * the Free Software Foundation; either version 2 of the License, or |
|
9 |
+ * (at your option) any later version |
|
10 |
+ * |
|
11 |
+ * Kamailio is distributed in the hope that it will be useful, |
|
12 |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 |
+ * GNU General Public License for more details. |
|
15 |
+ * |
|
16 |
+ * You should have received a copy of the GNU General Public License |
|
17 |
+ * along with this program; if not, write to the Free Software |
|
18 |
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
19 |
+ */ |
|
20 |
+ |
|
21 |
+#include <stdlib.h> |
|
22 |
+ |
|
23 |
+#include "db_redis_mod.h" |
|
24 |
+#include "redis_connection.h" |
|
25 |
+#include "redis_table.h" |
|
26 |
+ |
|
27 |
+int db_redis_connect(km_redis_con_t *con) { |
|
28 |
+ struct timeval tv; |
|
29 |
+ redisReply *reply; |
|
30 |
+ int db; |
|
31 |
+ |
|
32 |
+ tv.tv_sec = 1; |
|
33 |
+ tv.tv_usec = 0; |
|
34 |
+ |
|
35 |
+ db = atoi(con->id->database); |
|
36 |
+ reply = NULL; |
|
37 |
+ |
|
38 |
+ // TODO: introduce require_master mod-param and check if we're indeed master |
|
39 |
+ // TODO: on carrier, if we have db fail-over, the currently connected |
|
40 |
+ // redis server will become slave without dropping connections? |
|
41 |
+ |
|
42 |
+ con->con = redisConnectWithTimeout(con->id->host, con->id->port, tv); |
|
43 |
+ |
|
44 |
+ if (!con->con) { |
|
45 |
+ LM_ERR("cannot open connection: %.*s\n", con->id->url.len, con->id->url.s); |
|
46 |
+ goto err; |
|
47 |
+ } |
|
48 |
+ if (con->con->err) { |
|
49 |
+ LM_ERR("cannot open connection to %.*s: %s\n", con->id->url.len, con->id->url.s, |
|
50 |
+ con->con->errstr); |
|
51 |
+ goto err; |
|
52 |
+ } |
|
53 |
+ |
|
54 |
+ if (con->id->password) { |
|
55 |
+ reply = redisCommand(con->con, "AUTH %s", con->id->password); |
|
56 |
+ if (!reply) { |
|
57 |
+ LM_ERR("cannot authenticate connection %.*s: %s\n", |
|
58 |
+ con->id->url.len, con->id->url.s, con->con->errstr); |
|
59 |
+ goto err; |
|
60 |
+ } |
|
61 |
+ if (reply->type == REDIS_REPLY_ERROR) { |
|
62 |
+ LM_ERR("cannot authenticate connection %.*s: %s\n", |
|
63 |
+ con->id->url.len, con->id->url.s, reply->str); |
|
64 |
+ goto err; |
|
65 |
+ } |
|
66 |
+ freeReplyObject(reply); reply = NULL; |
|
67 |
+ } |
|
68 |
+ |
|
69 |
+ reply = redisCommand(con->con, "PING"); |
|
70 |
+ if (!reply) { |
|
71 |
+ LM_ERR("cannot ping server on connection %.*s: %s\n", |
|
72 |
+ con->id->url.len, con->id->url.s, con->con->errstr); |
|
73 |
+ goto err; |
|
74 |
+ } |
|
75 |
+ if (reply->type == REDIS_REPLY_ERROR) { |
|
76 |
+ LM_ERR("cannot ping server on connection %.*s: %s\n", |
|
77 |
+ con->id->url.len, con->id->url.s, reply->str); |
|
78 |
+ goto err; |
|
79 |
+ } |
|
80 |
+ freeReplyObject(reply); reply = NULL; |
|
81 |
+ |
|
82 |
+ reply = redisCommand(con->con, "SELECT %i", db); |
|
83 |
+ if (!reply) { |
|
84 |
+ LM_ERR("cannot select db on connection %.*s: %s\n", |
|
85 |
+ con->id->url.len, con->id->url.s, con->con->errstr); |
|
86 |
+ goto err; |
|
87 |
+ } |
|
88 |
+ if (reply->type == REDIS_REPLY_ERROR) { |
|
89 |
+ LM_ERR("cannot select db on connection %.*s: %s\n", |
|
90 |
+ con->id->url.len, con->id->url.s, reply->str); |
|
91 |
+ goto err; |
|
92 |
+ } |
|
93 |
+ freeReplyObject(reply); reply = NULL; |
|
94 |
+ |
|
95 |
+ return 0; |
|
96 |
+ |
|
97 |
+err: |
|
98 |
+ if (reply) |
|
99 |
+ freeReplyObject(reply); |
|
100 |
+ if (con->con) { |
|
101 |
+ redisFree(con->con); |
|
102 |
+ con->con = NULL; |
|
103 |
+ } |
|
104 |
+ return -1; |
|
105 |
+} |
|
106 |
+ |
|
107 |
+/*! \brief |
|
108 |
+ * Create a new connection structure, |
|
109 |
+ * open the redis connection and set reference count to 1 |
|
110 |
+ */ |
|
111 |
+km_redis_con_t* db_redis_new_connection(const struct db_id* id) { |
|
112 |
+ km_redis_con_t *ptr = NULL; |
|
113 |
+ |
|
114 |
+ if (!id) { |
|
115 |
+ LM_ERR("invalid id parameter value\n"); |
|
116 |
+ return 0; |
|
117 |
+ } |
|
118 |
+ |
|
119 |
+ ptr = (km_redis_con_t*)pkg_malloc(sizeof(km_redis_con_t)); |
|
120 |
+ if (!ptr) { |
|
121 |
+ LM_ERR("no private memory left\n"); |
|
122 |
+ return 0; |
|
123 |
+ } |
|
124 |
+ memset(ptr, 0, sizeof(km_redis_con_t)); |
|
125 |
+ ptr->id = (struct db_id*)id; |
|
126 |
+ |
|
127 |
+ /* |
|
128 |
+ LM_DBG("trying to initialize connection to '%.*s' with schema '%.*s' and keys '%.*s'\n", |
|
129 |
+ id->url.len, id->url.s, |
|
130 |
+ redis_schema.len, redis_schema.s, |
|
131 |
+ redis_keys.len, redis_keys.s); |
|
132 |
+ */ |
|
133 |
+ LM_DBG("trying to initialize connection to '%.*s'\n", |
|
134 |
+ id->url.len, id->url.s); |
|
135 |
+ if (db_redis_parse_schema(ptr) != 0) { |
|
136 |
+ LM_ERR("failed to parse 'schema' module parameter\n"); |
|
137 |
+ goto err; |
|
138 |
+ } |
|
139 |
+ if (db_redis_parse_keys(ptr) != 0) { |
|
140 |
+ LM_ERR("failed to parse 'keys' module parameter\n"); |
|
141 |
+ goto err; |
|
142 |
+ } |
|
143 |
+ |
|
144 |
+ db_redis_print_all_tables(ptr); |
|
145 |
+ |
|
146 |
+ ptr->ref = 1; |
|
147 |
+ ptr->append_counter = 0; |
|
148 |
+ |
|
149 |
+ if (db_redis_connect(ptr) != 0) { |
|
150 |
+ LM_ERR("Failed to connect to redis db\n"); |
|
151 |
+ goto err; |
|
152 |
+ } |
|
153 |
+ |
|
154 |
+ LM_DBG("connection opened to %.*s\n", id->url.len, id->url.s); |
|
155 |
+ |
|
156 |
+ return ptr; |
|
157 |
+ |
|
158 |
+ err: |
|
159 |
+ if (ptr) { |
|
160 |
+ if (ptr->con) { |
|
161 |
+ redisFree(ptr->con); |
|
162 |
+ } |
|
163 |
+ pkg_free(ptr); |
|
164 |
+ } |
|
165 |
+ return 0; |
|
166 |
+} |
|
167 |
+ |
|
168 |
+ |
|
169 |
+/*! \brief |
|
170 |
+ * Close the connection and release memory |
|
171 |
+ */ |
|
172 |
+void db_redis_free_connection(struct pool_con* con) { |
|
173 |
+ km_redis_con_t * _c; |
|
174 |
+ |
|
175 |
+ LM_DBG("freeing db_redis connection\n"); |
|
176 |
+ |
|
177 |
+ if (!con) return; |
|
178 |
+ |
|
179 |
+ _c = (km_redis_con_t*) con; |
|
180 |
+ |
|
181 |
+ if (_c->id) free_db_id(_c->id); |
|
182 |
+ if (_c->con) { |
|
183 |
+ redisFree(_c->con); |
|
184 |
+ } |
|
185 |
+ |
|
186 |
+ db_redis_free_tables(_c); |
|
187 |
+ pkg_free(_c); |
|
188 |
+} |
|
189 |
+ |
|
190 |
+ |
|
191 |
+static void print_query(redis_key_t *query) { |
|
192 |
+ LM_DBG("Query dump:\n"); |
|
193 |
+ for (redis_key_t *k = query; k; k = k->next) { |
|
194 |
+ LM_DBG(" %s\n", k->key.s); |
|
195 |
+ } |
|
196 |
+} |
|
197 |
+ |
|
198 |
+void *db_redis_command_argv(km_redis_con_t *con, redis_key_t *query) { |
|
199 |
+ char **argv = NULL; |
|
200 |
+ int argc; |
|
201 |
+ |
|
202 |
+ print_query(query); |
|
203 |
+ |
|
204 |
+ argc = db_redis_key_list2arr(query, &argv); |
|
205 |
+ if (argc < 0) { |
|
206 |
+ LM_ERR("Failed to allocate memory for query array\n"); |
|
207 |
+ return NULL; |
|
208 |
+ } |
|
209 |
+ LM_DBG("query has %d args\n", argc); |
|
210 |
+ |
|
211 |
+ redisReply *reply = redisCommandArgv(con->con, argc, (const char**)argv, NULL); |
|
212 |
+ if (con->con->err == REDIS_ERR_EOF && |
|
213 |
+ strcmp(con->con->errstr,"Server closed the connection") == 0) { |
|
214 |
+ |
|
215 |
+ if (db_redis_connect(con) != 0) { |
|
216 |
+ LM_ERR("Failed to reconnect to redis db\n"); |
|
217 |
+ pkg_free(argv); |
|
218 |
+ return NULL; |
|
219 |
+ } |
|
220 |
+ reply = redisCommandArgv(con->con, argc, (const char**)argv, NULL); |
|
221 |
+ } |
|
222 |
+ pkg_free(argv); |
|
223 |
+ return reply; |
|
224 |
+} |
|
225 |
+ |
|
226 |
+int db_redis_append_command_argv(km_redis_con_t *con, redis_key_t *query) { |
|
227 |
+ char **argv = NULL; |
|
228 |
+ int ret, argc; |
|
229 |
+ |
|
230 |
+ print_query(query); |
|
231 |
+ |
|
232 |
+ argc = db_redis_key_list2arr(query, &argv); |
|
233 |
+ if (argc < 0) { |
|
234 |
+ LM_ERR("Failed to allocate memory for query array\n"); |
|
235 |
+ return -1; |
|
236 |
+ } |
|
237 |
+ LM_DBG("query has %d args\n", argc); |
|
238 |
+ |
|
239 |
+ ret = redisAppendCommandArgv(con->con, argc, (const char**)argv, NULL); |
|
240 |
+ if (con->con->err == REDIS_ERR_EOF && |
|
241 |
+ strcmp(con->con->errstr,"Server closed the connection") == 0) { |
|
242 |
+ |
|
243 |
+ if (db_redis_connect(con) != 0) { |
|
244 |
+ LM_ERR("Failed to reconnect to redis db\n"); |
|
245 |
+ pkg_free(argv); |
|
246 |
+ return ret; |
|
247 |
+ } |
|
248 |
+ ret = redisAppendCommandArgv(con->con, argc, (const char**)argv, NULL); |
|
249 |
+ } |
|
250 |
+ pkg_free(argv); |
|
251 |
+ if (!con->con->err) { |
|
252 |
+ con->append_counter++; |
|
253 |
+ } |
|
254 |
+ return ret; |
|
255 |
+} |
|
256 |
+ |
|
257 |
+int db_redis_get_reply(km_redis_con_t *con, void **reply) { |
|
258 |
+ int ret; |
|
259 |
+ |
|
260 |
+ *reply = NULL; |
|
261 |
+ ret = redisGetReply(con->con, reply); |
|
262 |
+ if (con->con->err == REDIS_ERR_EOF && |
|
263 |
+ strcmp(con->con->errstr,"Server closed the connection") == 0) { |
|
264 |
+ |
|
265 |
+ if (db_redis_connect(con) != 0) { |
|
266 |
+ LM_ERR("Failed to reconnect to redis db\n"); |
|
267 |
+ return ret; |
|
268 |
+ } |
|
269 |
+ ret = redisGetReply(con->con, reply); |
|
270 |
+ } |
|
271 |
+ if (!con->con->err) |
|
272 |
+ con->append_counter--; |
|
273 |
+ return ret; |
|
274 |
+} |
|
275 |
+ |
|
276 |
+void db_redis_free_reply(redisReply **reply) { |
|
277 |
+ if (reply && *reply) { |
|
278 |
+ freeReplyObject(*reply); |
|
279 |
+ *reply = NULL; |
|
280 |
+ } |
|
281 |
+} |
|
282 |
+ |
|
283 |
+void db_redis_consume_replies(km_redis_con_t *con) { |
|
284 |
+ redisReply *reply = NULL; |
|
285 |
+ while (con->append_counter > 0 && !con->con->err) { |
|
286 |
+ LM_DBG("consuming outstanding reply %u", con->append_counter); |
|
287 |
+ db_redis_get_reply(con, (void**)&reply); |
|
288 |
+ if (reply) { |
|
289 |
+ freeReplyObject(reply); |
|
290 |
+ reply = NULL; |
|
291 |
+ } |
|
292 |
+ } |
|
293 |
+} |
0 | 294 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,73 @@ |
1 |
+/** |
|
2 |
+ * Copyright (C) 2018 Andreas Granig (sipwise.com) |
|
3 |
+ * |
|
4 |
+ * This file is part of Kamailio, a free SIP server. |
|
5 |
+ * |
|
6 |
+ * Kamailio is free software; you can redistribute it and/or modify |
|
7 |
+ * it under the terms of the GNU General Public License as published by |
|
8 |
+ * the Free Software Foundation; either version 2 of the License, or |
|
9 |
+ * (at your option) any later version |
|
10 |
+ * |
|
11 |
+ * Kamailio is distributed in the hope that it will be useful, |
|
12 |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 |
+ * GNU General Public License for more details. |
|
15 |
+ * |
|
16 |
+ * You should have received a copy of the GNU General Public License |
|
17 |
+ * along with this program; if not, write to the Free Software |
|
18 |
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
19 |
+ * |
|
20 |
+ */ |
|
21 |
+ |
|
22 |
+ |
|
23 |
+#ifndef _REDIS_CONNECTION_H_ |
|
24 |
+#define _REDIS_CONNECTION_H_ |
|
25 |
+ |
|
26 |
+#include <hiredis.h> |
|
27 |
+ |
|
28 |
+#include "db_redis_mod.h" |
|
29 |
+ |
|
30 |
+#define db_redis_check_reply(con, reply, err) do { \ |
|
31 |
+ if (!(reply) && !(con)->con) { \ |
|
32 |
+ LM_ERR("Failed to fetch type entry: no connection to server\n"); \ |
|
33 |
+ goto err; \ |
|
34 |
+ } \ |
|
35 |
+ if (!(reply)) { \ |
|
36 |
+ LM_ERR("Failed to fetch type entry: %s\n", \ |
|
37 |
+ (con)->con->errstr); \ |
|
38 |
+ goto err; \ |
|
39 |
+ } \ |
|
40 |
+ if ((reply)->type == REDIS_REPLY_ERROR) { \ |
|
41 |
+ LM_ERR("Failed to fetch type entry: %s\n", \ |
|
42 |
+ (reply)->str); \ |
|
43 |
+ goto err; \ |
|
44 |
+ } \ |
|
45 |
+} while(0); |
|
46 |
+ |
|
47 |
+typedef struct km_redis_con { |
|
48 |
+ struct db_id* id; |
|
49 |
+ unsigned int ref; |
|
50 |
+ struct pool_con* next; |
|
51 |
+ |
|
52 |
+ redisContext *con; |
|
53 |
+ unsigned int append_counter; |
|
54 |
+ struct str_hash_table tables; |
|
55 |
+} km_redis_con_t; |
|
56 |
+ |
|
57 |
+ |
|
58 |
+struct redis_key; |
|
59 |
+typedef struct redis_key redis_key_t; |
|
60 |
+ |
|
61 |
+#define REDIS_CON(db_con) ((km_redis_con_t*)((db_con)->tail)) |
|
62 |
+ |
|
63 |
+km_redis_con_t* db_redis_new_connection(const struct db_id* id); |
|
64 |
+void db_redis_free_connection(struct pool_con* con); |
|
65 |
+ |
|
66 |
+int db_redis_connect(km_redis_con_t *con); |
|
67 |
+void *db_redis_command_argv(km_redis_con_t *con, redis_key_t *query); |
|
68 |
+int db_redis_append_command_argv(km_redis_con_t *con, redis_key_t *query); |
|
69 |
+int db_redis_get_reply(km_redis_con_t *con, void **reply); |
|
70 |
+void db_redis_consume_replies(km_redis_con_t *con); |
|
71 |
+void db_redis_free_reply(redisReply **reply); |
|
72 |
+ |
|
73 |
+#endif /* _REDIS_CONNECTION_H_ */ |
0 | 74 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,2058 @@ |
1 |
+/* |
|
2 |
+ * Copyright (C) 2018 Andreas Granig (sipwise.com) |
|
3 |
+ * |
|
4 |
+ * This file is part of Kamailio, a free SIP server. |
|
5 |
+ * |
|
6 |
+ * Kamailio is free software; you can redistribute it and/or modify |
|
7 |
+ * it under the terms of the GNU General Public License as published by |
|
8 |
+ * the Free Software Foundation; either version 2 of the License, or |
|
9 |
+ * (at your option) any later version |
|
10 |
+ * |
|
11 |
+ * Kamailio is distributed in the hope that it will be useful, |
|
12 |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
14 |
+ * GNU General Public License for more details. |
|
15 |
+ * |
|
16 |
+ * You should have received a copy of the GNU General Public License |
|
17 |
+ * along with this program; if not, write to the Free Software |
|
18 |
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
19 |
+ * |
|
20 |
+ */ |
|
21 |
+ |
|
22 |
+#include <stdlib.h> |
|
23 |
+#include <time.h> |
|
24 |
+ |
|
25 |
+#include "db_redis_mod.h" |
|
26 |
+#include "redis_connection.h" |
|
27 |
+#include "redis_dbase.h" |
|
28 |
+#include "redis_table.h" |
|
29 |
+ |
|
30 |
+static void db_redis_dump_reply(redisReply *reply) { |
|
31 |
+ if (reply->type == REDIS_REPLY_STRING) { |
|
32 |
+ LM_DBG("%s\n", reply->str); |
|
33 |
+ } else if (reply->type == REDIS_REPLY_INTEGER) { |
|
34 |
+ LM_DBG("%lld\n", reply->integer); |
|
35 |
+ } else if (reply->type == REDIS_REPLY_NIL) { |
|
36 |
+ LM_DBG("<null>\n"); |
|
37 |
+ } else if (reply->type == REDIS_REPLY_ARRAY) { |
|
38 |
+ LM_DBG("printing %lu elements in array reply\n", reply->elements); |
|
39 |
+ for(int i = 0; i < reply->elements; ++i) { |
|
40 |
+ db_redis_dump_reply(reply->element[i]); |
|
41 |
+ } |
|
42 |
+ } else { |
|
43 |
+ LM_DBG("not printing invalid reply type\n"); |
|
44 |
+ } |
|
45 |
+} |
|
46 |
+ |
|
47 |
+// TODO: utilize auto-expiry? on insert/update, also update expire value |
|
48 |
+// of mappings |
|
49 |
+ |
|
50 |
+/* |
|
51 |
+ * Initialize database module |
|
52 |
+ * No function should be called before this |
|
53 |
+ */ |
|
54 |
+db1_con_t* db_redis_init(const str* _url) { |
|
55 |
+ return db_do_init(_url, (void *)db_redis_new_connection); |
|
56 |
+} |
|
57 |
+ |
|
58 |
+/* |
|
59 |
+ * Shut down database module |
|
60 |
+ * No function should be called after this |
|
61 |
+ */ |
|
62 |
+void db_redis_close(db1_con_t* _h) { |
|
63 |
+ LM_DBG("closing redis db connection\n"); |
|
64 |
+ db_do_close(_h, db_redis_free_connection); |
|
65 |
+} |
|
66 |
+ |
|
67 |
+static int db_redis_val2str(const db_val_t *v, str *_str) { |
|
68 |
+ const char *s; |
|
69 |
+ const str *tmpstr; |
|
70 |
+ int vtype = VAL_TYPE(v); |
|
71 |
+ _str->s = NULL; |
|
72 |
+ _str->len = 32; // default for numbers |
|
73 |
+ |
|
74 |
+ if (VAL_NULL(v)) { |
|
75 |
+ LM_DBG("converting <null> value to str\n"); |
|
76 |
+ _str->len = 0; |
|
77 |
+ return 0; |
|
78 |
+ } |
|
79 |
+ |
|
80 |
+ switch (vtype) { |
|
81 |
+ case DB1_INT: |
|
82 |
+ LM_DBG("converting int value %d to str\n", VAL_INT(v)); |
|
83 |
+ _str->s = (char*)pkg_malloc(_str->len); |
|
84 |
+ if (!_str->s) goto memerr; |
|
85 |
+ snprintf(_str->s, _str->len, "%d", VAL_INT(v)); |
|
86 |
+ _str->len = strlen(_str->s); |
|
87 |
+ break; |
|
88 |
+ case DB1_BIGINT: |
|
89 |
+ LM_DBG("converting bigint value %lld to str\n", VAL_BIGINT(v)); |
|
90 |
+ _str->s = (char*)pkg_malloc(_str->len); |
|
91 |
+ if (!_str->s) goto memerr; |
|
92 |
+ snprintf(_str->s, _str->len, "%lld", VAL_BIGINT(v)); |
|
93 |
+ _str->len = strlen(_str->s); |
|
94 |
+ break; |
|
95 |
+ case DB1_STRING: |
|
96 |
+ s = VAL_STRING(v); |
|
97 |
+ _str->len = strlen(s); |
|
98 |
+ LM_DBG("converting string value '%s' with len %d to str\n", s, _str->len); |
|
99 |
+ _str->s = (char*)pkg_malloc(_str->len + 1); |
|
100 |
+ if (!_str->s) goto memerr; |
|
101 |
+ //memcpy(_str->s, s, _str->len); |
|
102 |
+ //_str->s[_str->len] = '\0'; |
|
103 |
+ memset(_str->s, 0, _str->len + 1); |
|
104 |
+ strncpy(_str->s, s, _str->len); |
|
105 |
+ break; |
|
106 |
+ case DB1_STR: |
|
107 |
+ tmpstr = &(VAL_STR(v)); |
|
108 |
+ LM_DBG("converting str value '%.*s' with len %d to str\n", tmpstr->len, tmpstr->s, tmpstr->len); |
|
109 |
+ // copy manually to add 0 termination |
|
110 |
+ _str->s = (char*)pkg_malloc(tmpstr->len + 1); |
|
111 |
+ if (!_str->s) goto memerr; |
|
112 |
+ _str->len = tmpstr->len; |
|
113 |
+ memcpy(_str->s, tmpstr->s, _str->len); |
|
114 |
+ _str->s[_str->len] = '\0'; |
|
115 |
+ break; |
|
116 |
+ case DB1_DATETIME: |
|
117 |
+ LM_DBG("converting datetime value %ld to str\n", VAL_TIME(v)); |
|
118 |
+ _str->s = (char*)pkg_malloc(_str->len); |
|
119 |
+ if (!_str->s) goto memerr; |
|
120 |
+ strftime(_str->s, _str->len, "%Y-%m-%d %H:%M:%S", localtime(&(VAL_TIME(v)))); |
|
121 |
+ _str->len = strlen(_str->s); |
|
122 |
+ break; |
|
123 |
+ case DB1_DOUBLE: |
|
124 |
+ LM_DBG("converting double value %f to str\n", VAL_DOUBLE(v)); |
|
125 |
+ _str->s = (char*)pkg_malloc(_str->len); |
|
126 |
+ if (!_str->s) goto memerr; |
|
127 |
+ snprintf(_str->s, _str->len, "%.6f", VAL_DOUBLE(v)); |
|
128 |
+ _str->len = strlen(_str->s); |
|
129 |
+ break; |
|
130 |
+ case DB1_BITMAP: |
|
131 |
+ LM_DBG("converting bitmap value %u to str\n", VAL_BITMAP(v)); |
|
132 |
+ _str->s = (char*)pkg_malloc(_str->len); |
|
133 |
+ if (!_str->s) goto memerr; |
|
134 |
+ snprintf(_str->s, _str->len, "%u", VAL_BITMAP(v)); |
|
135 |
+ _str->len = strlen(_str->s); |
|
136 |
+ break; |
|
137 |
+ case DB1_BLOB: |
|
138 |
+ default: |
|
139 |
+ LM_ERR("Unsupported val type %d\n", vtype); |
|
140 |
+ goto err; |
|
141 |
+ } |
|
142 |
+ |
|
143 |
+ return 0; |
|
144 |
+ |
|
145 |
+memerr: |
|
146 |
+ LM_ERR("Failed to allocate memory to convert value to string\n"); |
|
147 |
+err: |
|
148 |
+ return -1; |
|
149 |
+} |
|
150 |
+ |
|
151 |
+static int db_redis_build_entry_manual_keys(redis_table_t *table, const db_key_t *_k, const db_val_t *_v, const int _n, int **manual_keys, int *manual_key_count) { |
|
152 |
+ |
|
153 |
+ // TODO: we also put keys here which are already part of type mapping! |
|
154 |
+ // there must be removed for performance reasons |
|
155 |
+ |
|
156 |
+ redis_key_t *key = NULL; |
|
157 |
+ |
|
158 |
+ *manual_keys = (int*)pkg_malloc(_n * sizeof(int)); |
|
159 |
+ if (! *manual_keys) { |
|
160 |
+ LM_ERR("Failed to allocate memory for manual key indices\n"); |
|
161 |
+ goto err; |
|
162 |
+ } |
|
163 |
+ memset(*manual_keys, 0, _n * sizeof(int)); |
|
164 |
+ *manual_key_count = 0; |
|
165 |
+ |
|
166 |
+ for (key = table->entry_keys; key; key = key->next) { |
|
167 |
+ int subkey_found = 0; |
|
168 |
+ LM_DBG("checking for existence of entry key '%.*s' in query to get manual key\n", |
|
169 |
+ key->key.len, key->key.s); |
|
170 |
+ for (int i = 0; i < _n; ++i) { |
|
171 |
+ const db_key_t k = _k[i]; |
|
172 |
+ if (!str_strcmp(&key->key, (str*)k)) { |
|
173 |
+ LM_DBG("found key in entry key\n"); |
|
174 |
+ subkey_found = 1; |
|
175 |
+ break; |
|
176 |
+ } else { |
|
177 |
+ (*manual_keys)[*manual_key_count] = i; |
|
178 |
+ (*manual_key_count)++; |
|
179 |
+ } |
|
180 |
+ } |
|
181 |
+ if (!subkey_found) { |
|
182 |
+ break; |
|
183 |
+ } |
|
184 |
+ } |
|
185 |
+ return 0; |
|
186 |
+ |
|
187 |
+err: |
|
188 |
+ if (*manual_keys) { |
|
189 |
+ pkg_free(*manual_keys); |
|
190 |
+ *manual_keys = NULL; |
|
191 |
+ } |
|
192 |
+ return -1; |
|
193 |
+} |
|
194 |
+ |
|
195 |
+static int db_redis_find_query_key(redis_key_t *key, const str *table_name, str *type_name, const db_key_t *_k, const db_val_t *_v, const int _n, str *key_name, int *key_found) { |
|
196 |
+ |
|
197 |
+ unsigned int len; |
|
198 |
+ str val = {NULL, 0}; |
|
199 |
+ |
|
200 |
+ *key_found = 1; |
|
201 |
+ key_name->len = 0; |
|
202 |
+ key_name->s = NULL; |
|
203 |
+ |
|
204 |
+ for (; key; key = key->next) { |
|
205 |
+ int subkey_found = 0; |
|
206 |
+ LM_DBG("checking for existence of entry key '%.*s' in query\n", |
|
207 |
+ key->key.len, key->key.s); |
|
208 |
+ for (int i = 0; i < _n; ++i) { |
|
209 |
+ const db_key_t k = _k[i]; |
|
210 |
+ const db_val_t v = _v[i]; |
|
211 |
+ |
|
212 |
+ if (VAL_NULL(&v)) { |
|
213 |
+ LM_DBG("Skipping null value for given key '%.*s'\n", |
|
214 |
+ k->len, k->s); |
|
215 |
+ break; |
|
216 |
+ } else if (!str_strcmp(&key->key, (str*)k)) { |
|
217 |
+ LM_DBG("found key in entry key\n"); |
|
218 |
+ if (db_redis_val2str(&v, &val) != 0) goto err; |
|
219 |
+ if (val.s == NULL) { |
|
220 |
+ LM_DBG("key value in entry key is null, skip key\n"); |
|
221 |
+ subkey_found = 0; |
|
222 |
+ break; |
|
223 |
+ } |
|
224 |
+ if (!key_name->len) { |
|
225 |
+ // <table_name>:<type>::<val> |
|
226 |
+ len = table_name->len + 1 + type_name->len + 2 + val.len + 1; //snprintf writes term 0 char |
|
227 |
+ key_name->s = (char*)pkg_malloc(len); |
|
228 |
+ if (!key_name->s) { |
|
229 |
+ LM_ERR("Failed to allocate key memory\n"); |
|
230 |
+ goto err; |
|
231 |
+ } |
|
232 |
+ snprintf(key_name->s, len, "%.*s:%.*s::%.*s", |
|
233 |
+ table_name->len, table_name->s, |
|
234 |
+ type_name->len, type_name->s, |
|
235 |
+ val.len, val.s); |
|
236 |
+ key_name->len = len-1; // subtract the term 0 char |
|
237 |
+ |
|
238 |
+ } else { |
|
239 |
+ // :<val> |
|
240 |
+ key_name->s = (char*)pkg_realloc(key_name->s, key_name->len + val.len + 2); |
|
241 |
+ if (!key_name->s) { |
|
242 |
+ LM_ERR("Failed to allocate key memory\n"); |
|
243 |
+ goto err; |
|
244 |
+ } |
|
245 |
+ snprintf(key_name->s + key_name->len, 1 + val.len + 1, ":%.*s", |
|
246 |
+ val.len, val.s); |
|
247 |
+ key_name->len += (1 + val.len); |
|
248 |
+ } |
|
249 |
+ LM_DBG("entry key so far is '%.*s'\n", key_name->len, key_name->s); |
|
250 |
+ subkey_found = 1; |
|
251 |
+ pkg_free(val.s); |
|
252 |
+ val.s = NULL; |
|
253 |
+ break; |
|
254 |
+ } |
|
255 |
+ } |
|
256 |
+ if (!subkey_found) { |
|
257 |
+ LM_DBG("key '%.*s' for type '%.*s' not found, unable to use this type\n", |
|
258 |
+ key->key.len, key->key.s, type_name->len, type_name->s); |
|
259 |
+ if (key_name->s) { |
|
260 |
+ pkg_free(key_name->s); |
|
261 |
+ key_name->s = NULL; |
|
262 |
+ key_name->len = 0; |
|
263 |
+ } |
|
264 |
+ *key_found = 0; |
|
265 |
+ break; |
|
266 |
+ } |
|
267 |
+ } |
|
268 |
+ |
|
269 |
+ return 0; |
|
270 |
+ |
|
271 |
+err: |
|
272 |
+ if (val.s) |
|
273 |
+ pkg_free(val.s); |
|
274 |
+ if(key_name->s) { |
|
275 |
+ pkg_free(key_name->s); |
|
276 |
+ key_name->s = NULL; |
|
277 |
+ key_name->len = 0; |
|
278 |
+ } |
|
279 |
+ return -1; |
|
280 |
+} |
|
281 |
+ |
|
282 |
+static int db_redis_build_entry_keys(km_redis_con_t *con, const str *table_name, |
|
283 |
+ const db_key_t *_k, const db_val_t *_v, const int _n, |
|
284 |
+ redis_key_t **keys, int *keys_count) { |
|
285 |
+ |
|
286 |
+ struct str_hash_entry *table_e; |
|
287 |
+ redis_table_t *table; |
|
288 |
+ redis_key_t *key; |
|
289 |
+ int key_found; |
|
290 |
+ str type_name = str_init("entry"); |
|
291 |
+ str keyname = {NULL, 0}; |
|
292 |
+ |
|
293 |
+ LM_DBG("build entry keys\n"); |
|
294 |
+ |
|
295 |
+ table_e = str_hash_get(&con->tables, table_name->s, table_name->len); |
|
296 |
+ if (!table_e) { |
|
297 |
+ LM_ERR("query to undefined table '%.*s', define in db_redis keys parameter!", |
|
298 |
+ table_name->len, table_name->s); |
|
299 |
+ return -1; |
|
300 |
+ } |
|
301 |
+ table = (redis_table_t*)table_e->u.p; |
|
302 |
+ key = table->entry_keys; |
|
303 |
+ if (db_redis_find_query_key(key, table_name, &type_name, _k, _v, _n, &keyname, &key_found) != 0) { |
|
304 |
+ goto err; |
|
305 |
+ } |
|
306 |
+ if (key_found) { |
|
307 |
+ db_redis_key_add_str(keys, &keyname); |
|
308 |
+ |
|
309 |
+ if (db_redis_key_add_str(keys, &keyname) != 0) { |
|
310 |
+ LM_ERR("Failed to add key string\n"); |
|
311 |
+ goto err; |
|
312 |
+ } |
|
313 |
+ LM_DBG("found suitable entry key '%.*s' for query\n", |
|
314 |
+ (*keys)->key.len, (*keys)->key.s); |
|
315 |
+ *keys_count = 1; |
|
316 |
+ pkg_free(keyname.s); |
|
317 |
+ } else { |
|
318 |
+ LM_ERR("Failed to create direct entry key, no matching key definition\n"); |
|
319 |
+ goto err; |
|
320 |
+ } |
|
321 |
+ |
|
322 |
+ return 0; |
|
323 |
+ |
|
324 |
+err: |
|
325 |
+ db_redis_key_free(keys); |
|
326 |
+ if (keyname.s) |
|
327 |
+ pkg_free(keyname.s); |
|
328 |
+ return -1; |
|
329 |
+} |
|
330 |
+ |
|
331 |
+static int db_redis_get_keys_for_all_types(km_redis_con_t *con, const str *table_name, |
|
332 |
+ redis_key_t **keys, int *keys_count) { |
|
333 |
+ |
|
334 |
+ struct str_hash_entry *table_e; |
|
335 |
+ redis_table_t *table; |
|
336 |
+ redis_type_t *type; |
|
337 |
+ redis_key_t *key; |
|
338 |
+ |
|
339 |
+ *keys = NULL; |
|
340 |
+ *keys_count = 0; |
|
341 |
+ |
|
342 |
+ table_e = str_hash_get(&con->tables, table_name->s, table_name->len); |
|
343 |
+ if (!table_e) { |
|
344 |
+ LM_ERR("query to undefined table '%.*s', define in db_redis keys parameter!", |
|
345 |
+ table_name->len, table_name->s); |
|
346 |
+ return -1; |
|
347 |
+ } |
|
348 |
+ table = (redis_table_t*)table_e->u.p; |
|
349 |
+ |
|
350 |
+ for (type = table->types; type; type = type->next) { |
|
351 |
+ for (key = type->keys; key; key = key->next) { |
|
352 |
+ if (db_redis_key_add_str(keys, &key->key) != 0) { |
|
353 |
+ LM_ERR("Failed to add key string\n"); |
|
354 |
+ goto err; |
|
355 |
+ } |
|
356 |
+ (*keys_count)++; |
|
357 |
+ } |
|
358 |
+ } |
|
359 |
+ |
|
360 |
+ return 0; |
|
361 |
+ |
|
362 |
+err: |
|
363 |
+ db_redis_key_free(keys); |
|
364 |
+ return -1; |
|
365 |
+} |
|
366 |
+ |
|
367 |
+static int db_redis_build_type_keys(km_redis_con_t *con, const str *table_name, |
|
368 |
+ const db_key_t *_k, const db_val_t *_v, const int _n, |
|
369 |
+ redis_key_t **keys, int *keys_count) { |
|
370 |
+ |
|
371 |
+ struct str_hash_entry *table_e; |
|
372 |
+ redis_table_t *table; |
|
373 |
+ redis_type_t *type; |
|
374 |
+ redis_key_t *key; |
|
375 |
+ |
|
376 |
+ *keys = NULL; |
|
377 |
+ *keys_count = 0; |
|
378 |
+ |
|
379 |
+ LM_DBG("build type keys\n"); |
|
380 |
+ |
|
381 |
+ table_e = str_hash_get(&con->tables, table_name->s, table_name->len); |
|
382 |
+ if (!table_e) { |
|
383 |
+ LM_ERR("query to undefined table '%.*s', define in db_redis keys parameter!", |
|
384 |
+ table_name->len, table_name->s); |
|
385 |
+ return -1; |
|
386 |
+ } |
|
387 |