]> granicus.if.org Git - ejabberd/commitdiff
Make it possible to use SQL as an SM backend
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Mon, 9 Mar 2015 13:41:13 +0000 (16:41 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Mon, 9 Mar 2015 13:41:13 +0000 (16:41 +0300)
sql/mysql.sql
src/ejabberd_app.erl
src/ejabberd_sm.erl
src/ejabberd_sm_mnesia.erl
src/ejabberd_sm_odbc.erl [new file with mode: 0644]
src/ejabberd_sup.erl
src/odbc_queries.erl

index 7f96905c0e370adb89ba2560a941091b9f353187..64f54ba70c15191d616e1721e09dbba1f27d98d5 100644 (file)
@@ -278,3 +278,17 @@ CREATE TABLE caps_features (
 ) ENGINE=InnoDB CHARACTER SET utf8;
 
 CREATE INDEX i_caps_features_node_subnode ON caps_features(node(75), subnode(75));
+
+CREATE TABLE sm (
+    usec bigint NOT NULL,
+    pid text NOT NULL,
+    node text NOT NULL,
+    username varchar(250) NOT NULL,
+    resource varchar(250) NOT NULL,
+    priority text NOT NULL,
+    info text NOT NULL
+) ENGINE=InnoDB CHARACTER SET utf8;
+
+CREATE UNIQUE INDEX i_sid ON sm(usec, pid(75));
+CREATE INDEX i_node ON sm(node(75));
+CREATE INDEX i_username ON sm(username);
index 957aa5d462caf6c5e5fb5f989e4e107e82e59d34..15170daeeaf1dcf05dcf62c1035ae9e50a79b144 100644 (file)
@@ -58,6 +58,7 @@ start(normal, _Args) ->
     Sup = ejabberd_sup:start_link(),
     ejabberd_rdbms:start(),
     ejabberd_riak_sup:start(),
+    ejabberd_sm:start(),
     ejabberd_auth:start(),
     cyrsasl:start(),
     % Profiling
index 9925d26dd3e9d5a0084651eb31e804c474d7b517..67a82d024c04097846d4e3054332aaa3cd783051 100644 (file)
@@ -30,7 +30,8 @@
 -behaviour(gen_server).
 
 %% API
--export([start_link/0,
+-export([start/0,
+        start_link/0,
         route/3,
         open_session/5,
         open_session/6,
@@ -77,9 +78,9 @@
 -include("ejabberd_sm.hrl").
 
 -callback init() -> ok | {error, any()}.
--callback get_session(sid()) -> {ok, #session{}} | {error, notfound}.
+-callback get_session(binary(), sid()) -> {ok, #session{}} | {error, notfound}.
 -callback set_session(#session{}) -> ok.
--callback delete_session(sid()) -> ok.
+-callback delete_session(binary(), sid()) -> ok.
 -callback get_sessions() -> [#session{}].
 -callback get_sessions(binary()) -> [#session{}].
 -callback get_sessions(binary(), binary()) -> [#session{}].
 %%--------------------------------------------------------------------
 -export_type([sid/0]).
 
+start() ->
+    ChildSpec = {?MODULE, {?MODULE, start_link, []},
+                transient, 1000, worker, [?MODULE]},
+    supervisor:start_child(ejabberd_sup, ChildSpec).
+
 start_link() ->
     gen_server:start_link({local, ?MODULE}, ?MODULE, [],
                          []).
@@ -131,11 +137,12 @@ open_session(SID, User, Server, Resource, Info) ->
 
 close_session(SID, User, Server, Resource) ->
     Mod = get_sm_backend(),
-    Info = case Mod:get_session(SID) of
+    LServer = jlib:nameprep(Server),
+    Info = case Mod:get_session(LServer, SID) of
               {ok, #session{info = I}} -> I;
               {error, notfound} -> []
           end,
-    Mod:delete_session(SID),
+    Mod:delete_session(LServer, SID),
     JID = jlib:make_jid(User, Server, Resource),
     ejabberd_hooks:run(sm_remove_connection_hook,
                       JID#jid.lserver, [SID, JID, Info]).
@@ -723,7 +730,8 @@ force_update_presence({LUser, LServer}) ->
 get_sm_backend() ->
     DBType = ejabberd_config:get_option(sm_db_type,
                                        fun(mnesia) -> mnesia;
-                                          (internal) -> mnesia
+                                          (internal) -> mnesia;
+                                          (odbc) -> odbc
                                        end, mnesia),
     list_to_atom("ejabberd_sm_" ++ atom_to_list(DBType)).
 
index 504fa98420ba9429644ef671894411683ce97d60..59a6c64f69875d6233ffec495e4e186b72ca2ba1 100644 (file)
@@ -13,9 +13,9 @@
 
 %% API
 -export([init/0,
-        get_session/1,
+        get_session/2,
         set_session/1,
-        delete_session/1,
+        delete_session/2,
         get_sessions/0,
         get_sessions/1,
         get_sessions/2,
@@ -44,8 +44,8 @@ init() ->
            Err
     end.
 
--spec get_session(sid()) -> {ok, #session{}} | {error, notfound}.
-get_session(SID) ->
+-spec get_session(binary(), sid()) -> {ok, #session{}} | {error, notfound}.
+get_session(_LServer, SID) ->
     case mnesia:dirty_read(session, SID) of
        [] ->
            {error, notfound};
@@ -57,8 +57,8 @@ get_session(SID) ->
 set_session(Session) ->
     mnesia:dirty_write(Session).
 
--spec delete_session(sid()) -> ok.
-delete_session(SID) ->
+-spec delete_session(binary(), sid()) -> ok.
+delete_session(_LServer, SID) ->
     mnesia:dirty_delete(session, SID).
 
 -spec get_sessions() -> [#session{}].
diff --git a/src/ejabberd_sm_odbc.erl b/src/ejabberd_sm_odbc.erl
new file mode 100644 (file)
index 0000000..ef24fa5
--- /dev/null
@@ -0,0 +1,179 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2015, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created :  9 Mar 2015 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-module(ejabberd_sm_odbc).
+
+-behaviour(ejabberd_sm).
+
+%% API
+-export([init/0,
+        get_session/2,
+        set_session/1,
+        delete_session/2,
+        get_sessions/0,
+        get_sessions/1,
+        get_sessions/2,
+        get_sessions/3]).
+
+-include("ejabberd.hrl").
+-include("ejabberd_sm.hrl").
+-include("logger.hrl").
+-include("jlib.hrl").
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+-spec init() -> ok | {error, any()}.
+init() ->
+    Node = ejabberd_odbc:escape(erlang:atom_to_binary(node(), utf8)),
+    lists:foldl(
+      fun(Host, ok) ->
+             case ejabberd_odbc:sql_query(
+                    Host, [<<"delete from sm where node='">>, Node, <<"'">>]) of
+                 {updated, _} ->
+                     ok;
+                 Err ->
+                     ?ERROR_MSG("failed to clean 'sm' table: ~p", [Err]),
+                     Err
+             end;
+        (_, Err) ->
+             Err
+      end, ok, ?MYHOSTS).
+
+-spec get_session(binary(), sid()) -> {ok, #session{}} | {error, notfound}.
+get_session(LServer, {Now, Pid} = SID) ->
+    Host = ejabberd_odbc:escape(LServer),
+    PidS = list_to_binary(erlang:pid_to_list(Pid)),
+    TS = now_to_timestamp(Now),
+    case ejabberd_odbc:sql_query(
+          Host, [<<"select username, resource, priority, info from sm ">>,
+                 <<"where usec='">>, TS, <<"' and pid='">>, PidS, <<"'">>]) of
+       {selected, _, [[User, Resource, Priority, Info]|_]} ->
+           {ok, #session{sid = SID, us = {User, Resource},
+                         usr = {User, Resource, LServer},
+                         priority = dec_priority(Priority),
+                         info = ejabberd_odbc:decode_term(Info)}};
+       {selected, _, []} ->
+           {error, notfound}
+    end.
+
+set_session(#session{sid = {Now, Pid}, usr = {U, LServer, R},
+                    priority = Priority, info = Info}) ->
+    Username = ejabberd_odbc:escape(U),
+    Resource = ejabberd_odbc:escape(R),
+    InfoS = ejabberd_odbc:encode_term(Info),
+    PrioS = enc_priority(Priority),
+    TS = now_to_timestamp(Now),
+    PidS = list_to_binary(erlang:pid_to_list(Pid)),
+    Node = ejabberd_odbc:escape(erlang:atom_to_binary(node(Pid), utf8)),
+    case odbc_queries:update(
+          LServer,
+          <<"sm">>,
+          [<<"usec">>, <<"pid">>, <<"node">>, <<"username">>,
+           <<"resource">>, <<"priority">>, <<"info">>],
+          [TS, PidS, Node, Username, Resource, PrioS, InfoS],
+          [<<"usec='">>, TS, <<"' and pid='">>, PidS, <<"'">>]) of
+       ok ->
+           ok;
+       Err ->
+           ?ERROR_MSG("failed to update 'sm' table: ~p", [Err])
+    end.
+
+delete_session(LServer, {Now, Pid}) ->
+    TS = now_to_timestamp(Now),
+    PidS = list_to_binary(erlang:pid_to_list(Pid)),
+    case ejabberd_odbc:sql_query(
+          LServer, [<<"delete from sm where usec='">>,
+                    TS, <<"' and pid='">>, PidS, <<"'">>]) of
+       {updated, _} ->
+           ok;
+       Err ->
+           ?ERROR_MSG("failed to delete from 'sm' table: ~p", [Err])
+    end.
+
+get_sessions() ->
+    lists:flatmap(
+      fun(LServer) ->
+             get_sessions(LServer)
+      end, ?MYHOSTS).
+
+get_sessions(LServer) ->
+    case ejabberd_odbc:sql_query(
+          LServer, [<<"select usec, pid, username, ">>,
+                    <<"resource, priority, info from sm">>]) of
+       {selected, _, Rows} ->
+           [row_to_session(LServer, Row) || Row <- Rows];
+       Err ->
+           ?ERROR_MSG("failed to select from 'sm' table: ~p", [Err]),
+           []
+    end.
+
+get_sessions(LUser, LServer) ->
+    Username = ejabberd_odbc:escape(LUser),
+    case ejabberd_odbc:sql_query(
+          LServer, [<<"select usec, pid, username, ">>,
+                    <<"resource, priority, info from sm where ">>,
+                    <<"username='">>, Username, <<"'">>]) of
+       {selected, _, Rows} ->
+           [row_to_session(LServer, Row) || Row <- Rows];
+       Err ->
+           ?ERROR_MSG("failed to select from 'sm' table: ~p", [Err]),
+           []
+    end.
+
+get_sessions(LUser, LServer, LResource) ->
+    Username = ejabberd_odbc:escape(LUser),
+    Resource = ejabberd_odbc:escape(LResource),
+    case ejabberd_odbc:sql_query(
+          LServer, [<<"select usec, pid, username, ">>,
+                    <<"resource, priority, info from sm where ">>,
+                    <<"username='">>, Username, <<"' and resource='">>,
+                    Resource, <<"'">>]) of
+       {selected, _, Rows} ->
+           [row_to_session(LServer, Row) || Row <- Rows];
+       Err ->
+           ?ERROR_MSG("failed to select from 'sm' table: ~p", [Err]),
+           []
+    end.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+now_to_timestamp({MSec, Sec, USec}) ->
+    erlang:integer_to_binary((MSec * 1000000 + Sec) * 1000000 + USec).
+
+timestamp_to_now(TS) ->
+    I = erlang:binary_to_integer(TS),
+    Head = I div 1000000,
+    USec = I rem 1000000,
+    MSec = Head div 1000000,
+    Sec = Head div 1000000,
+    {MSec, Sec, USec}.
+
+dec_priority(Prio) ->
+    case catch erlang:binary_to_integer(Prio) of
+       {'EXIT', _} ->
+           undefined;
+       Int ->
+           Int
+    end.
+
+enc_priority(undefined) ->
+    <<"">>;
+enc_priority(Int) when is_integer(Int) ->
+    erlang:integer_to_binary(Int).
+
+row_to_session(LServer, [USec, PidS, User, Resource, PrioS, InfoS]) ->
+    Now = timestamp_to_now(USec),
+    Pid = erlang:list_to_pid(binary_to_list(PidS)),
+    Priority = dec_priority(PrioS),
+    Info = ejabberd_odbc:decode_term(InfoS),
+    #session{sid = {Now, Pid}, us = {User, LServer},
+            usr = {User, LServer, Resource},
+            priority = Priority,
+            info = Info}.
index 35c79f429665b9bf7f7d2a43519d068e038c4046..ecedfa50283408e141e1feb6277024049a95f813 100644 (file)
@@ -62,13 +62,6 @@ init([]) ->
         brutal_kill,
         worker,
         [ejabberd_router]},
-    SM =
-       {ejabberd_sm,
-        {ejabberd_sm, start_link, []},
-        permanent,
-        brutal_kill,
-        worker,
-        [ejabberd_sm]},
     S2S =
        {ejabberd_s2s,
         {ejabberd_s2s, start_link, []},
@@ -173,7 +166,6 @@ init([]) ->
           NodeGroups,
           SystemMonitor,
           Router,
-          SM,
           S2S,
           Local,
           Captcha,
index 1fa16b896073261050964ab48e6c9f70a9fb4049..f2771e52f31fb2e9f64e870a44f9e76465370a94 100644 (file)
@@ -27,7 +27,7 @@
 
 -author("mremond@process-one.net").
 
--export([get_db_type/0, update_t/4, sql_transaction/2,
+-export([get_db_type/0, update/5, update_t/4, sql_transaction/2,
         get_last/2, set_last_t/4, del_last/2, get_password/2,
         set_password_t/3, add_user/3, del_user/2,
         del_user_return_password/3, list_users/1, list_users/2,