Skip to content

Commit

Permalink
Merge pull request #445 from esl/riak-private
Browse files Browse the repository at this point in the history
Riak backend for private storage
  • Loading branch information
ppikula committed Aug 21, 2015
2 parents c0f4c7d + f247165 commit ac9ff99
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 9 deletions.
4 changes: 2 additions & 2 deletions apps/ejabberd/src/mod_private.erl
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
NS :: binary(),
XML :: #xmlel{},
Reason :: term(),
Result :: {atomic, ok} | {aborted, Reason} | {error, Reason}.
Result :: ok | {aborted, Reason} | {error, Reason}.

-callback multi_get_data(LUser, LServer, NS2Def) -> [XML | Default] when
LUser :: binary(),
Expand Down Expand Up @@ -109,7 +109,7 @@ process_sm_iq(
NS2XML = to_map(Elems),
Result = ?BACKEND:multi_set_data(LUser, LServer, NS2XML),
case Result of
{atomic, ok} ->
ok ->
IQ#iq{type = result, sub_el = [SubElem]};
{error, Reason} ->
?ERROR_MSG("~p:multi_set_data failed ~p for ~ts@~ts.",
Expand Down
5 changes: 4 additions & 1 deletion apps/ejabberd/src/mod_private_mnesia.erl
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ init(_Host, _Opts) ->

multi_set_data(LUser, LServer, NS2XML) ->
F = fun() -> multi_set_data_t(LUser, LServer, NS2XML) end,
mnesia:transaction(F).
case mnesia:transaction(F) of
{atomic, ok} -> ok;
{aborted, Reason} -> {aborted, Reason}
end.

multi_set_data_t(LUser, LServer, NS2XML) ->
[set_data_t(LUser, LServer, NS, XML) || {NS, XML} <- NS2XML],
Expand Down
2 changes: 1 addition & 1 deletion apps/ejabberd/src/mod_private_mysql.erl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ multi_set_data(LUser, LServer, NS2XML) ->
replace_like_insert_result(
odbc_queries:multi_set_private_data(LServer, SLUser, Rows)).

replace_like_insert_result({updated, _}) -> {atomic, ok};
replace_like_insert_result({updated, _}) -> ok;
replace_like_insert_result({error, Reason}) -> {error, Reason};
replace_like_insert_result({aborted, Reason}) -> {aborted, Reason}.

Expand Down
12 changes: 8 additions & 4 deletions apps/ejabberd/src/mod_private_odbc.erl
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,18 @@ init(_Host, _Opts) ->

multi_set_data(LUser, LServer, NS2XML) ->
F = fun() -> multi_set_data_t(LUser, LServer, NS2XML) end,
odbc_queries:sql_transaction(LServer, F).
case odbc_queries:sql_transaction(LServer, F) of
{atomic, ok} -> ok;
{aborted, Reason} -> {aborted, Reason};
{error, Reason} -> {error, Reason}
end.

multi_set_data_t(LUser, LServer, NS2XML) ->
[set_data_t(LUser, LServer, NS, XML) || {NS, XML} <- NS2XML],
SLUser = ejabberd_odbc:escape(LUser),
[set_data_t(SLUser, LServer, NS, XML) || {NS, XML} <- NS2XML],
ok.

set_data_t(LUser, LServer, NS, XML) ->
SLUser = ejabberd_odbc:escape(LUser),
set_data_t(SLUser, LServer, NS, XML) ->
SNS = ejabberd_odbc:escape(NS),
SData = ejabberd_odbc:escape(xml:element_to_binary(XML)),
odbc_queries:set_private_data(LServer, SLUser, SNS, SData).
Expand Down
79 changes: 79 additions & 0 deletions apps/ejabberd/src/mod_private_riak.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
%%==============================================================================
%% Copyright 2015 Erlang Solutions Ltd.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%==============================================================================
-module(mod_private_riak).

-behaviour(mod_private).

%% API
-export([init/2,
multi_set_data/3,
multi_get_data/3,
remove_user/2]).

-include("ejabberd.hrl").
-include("jlib.hrl").

-spec init(ejabberd:lserver(), list()) -> ok.
init(_Host, _Opts) ->
ok.

-spec multi_set_data(ejabberd:luser(), ejabberd:lserver(),
{binary(), ejabberd:xmlterm()}) -> ok.
multi_set_data(LUser, LServer, NS2XML) ->
R = [set_private_data(LUser, LServer, NS, XML) || {NS, XML} <- NS2XML],
%% check if something returned with error msg
case lists:keyfind(error, 1, R) of
{error, Reason} -> {error, Reason};
false -> ok
end.

-spec multi_get_data(ejabberd:luser(), ejabberd:lserver(),
{binary(), term()}) -> ok.
multi_get_data(LUser, LServer, NS2Def) ->
[get_private_data(LUser, LServer, NS, Default) || {NS, Default} <- NS2Def].

-spec remove_user(ejabberd:luser(), ejabberd:lserver()) -> ok.
remove_user(LUser, LServer) ->
KeyFilter = [[<<"starts_with">>,LUser]],
Bucket = bucket_type(LServer),
case mongoose_riak:mapred({Bucket, KeyFilter}, []) of
{ok, []} ->
ok;
{ok, [{0, BucketKeys} | _]} ->
[mongoose_riak:delete(Bucket1, Key) || {{Bucket1, Key}, _} <- BucketKeys];
Error ->
?WARNING_MSG("Error reading keys to remove: ~p", [Error]),
{error, Error}
end.

set_private_data(LUser, LServer, NS, XML) ->
Obj = riakc_obj:new(bucket_type(LServer), key(LUser, NS), xml:element_to_binary(XML)),
mongoose_riak:put(Obj).

get_private_data(LUser, LServer, NS, Default) ->
case mongoose_riak:get(bucket_type(LServer), key(LUser, NS)) of
{ok, Obj} ->
Value = riakc_obj:get_value(Obj),
#xmlel{} = xml_stream:parse_element(Value);
_ ->
Default
end.

bucket_type(LServer) ->
{<<"private">>, LServer}.

key(LUser, NS) ->
<<LUser/binary, "/", NS/binary>>.
10 changes: 10 additions & 0 deletions apps/ejabberd/src/mongoose_riak.erl
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@
-export([update_type/3, update_type/4]).
-export([fetch_type/2, fetch_type/3]).
-export([list_keys/1]).
-export([list_buckets/1]).
-export([get_worker/0]).
-export([create_new_map/1]).
-export([update_map/2]).
-export([mapred/2]).

-export([pool_name/0]).

Expand Down Expand Up @@ -121,6 +123,9 @@ fetch_type(Bucket, Key, Opts) ->
list_keys(Bucket) ->
?CALL(list_keys, [Bucket]).

-spec list_buckets(binary()) -> list().
list_buckets(Type) ->
?CALL(list_buckets, [Type]).

-spec create_new_map([riakc_map_op()]) -> riakc_map:crdt_map().
create_new_map(Ops) ->
Expand All @@ -130,6 +135,11 @@ create_new_map(Ops) ->
update_map(Map, Ops) ->
lists:foldl(fun update_map_op/2, Map, Ops).

-spec mapred(mapred_inputs(), [mapred_queryterm()]) ->
{ok, mapred_result()} | {error, term()}.
mapred(KeyFileters, MapRed) ->
?CALL(mapred, [KeyFileters, MapRed]).

-spec get_worker() -> pid() | undefined.
get_worker() ->
case catch cuesport:get_worker(pool_name()) of
Expand Down
4 changes: 4 additions & 0 deletions doc/advanced-configuration/database-backends-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ psql -h localhost -U user -q -d mongooseim -f pg.sql
**Can be used for:**

* users (credentials)
* private storage

**Setup**

Expand All @@ -80,6 +81,9 @@ be able to store **user credentials** one have to run the following command:
```bash
riak-admin bucket-type create users '{"props":{"datatype":"map"}}'
riak-admin bucket-type activate users

riak-admin bucket-type create private '{"props":{"last_write_wins":true}}'
riak-admin bucket-type activate private
```

This will create a bucket type required for storing **users credentials** and it will
Expand Down
6 changes: 5 additions & 1 deletion doc/modules/mod_private.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ This module implements [XEP-0049 (Private XML Storage)](http://xmpp.org/extensio

### Options
* **iqdisc**
* **backend** (atom, default: `mnesia`) - Storage backend. Currently `mnesia`, `odbc` and `mysql` are supported . `mysql` uses MySQL-specific queries so in some cases it is more efficient than generic `odbc`.
* **backend** (atom, default: `mnesia`) - Storage backend. Currently `mnesia`, `odbc`, `riak` and `mysql` are supported . `mysql` uses MySQL-specific queries so in some cases it is more efficient than generic `odbc`.

**CAUTION:** Riak backend doesn't support transactions(rollbacks), so please avoid inserting more
than one value in one set request, otherwise you may end up with partially save data, backend returns
first error.

### Example Configuration
```
Expand Down
3 changes: 3 additions & 0 deletions tools/setup_riak
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ service riak start

${1}riak-admin bucket-type create users '{"props":{"datatype":"map"}}'
${1}riak-admin bucket-type activate users

${1}riak-admin bucket-type create private '{"props":{"last_write_wins":true}}'
${1}riak-admin bucket-type activate private

0 comments on commit ac9ff99

Please sign in to comment.