Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'feature/riak_core' into dev

Conflicts:
	apps/snarl/src/snarl.app.src
	rebar.config
	rel/reltool.config
  • Loading branch information...
commit 350c2cc3e83104affa48fcce0d9d9f711256278d 2 parents 981f452 + c21c032
@Licenser Licenser authored
Showing with 3,515 additions and 260 deletions.
  1. +2 −0  .gitignore
  2. +5 −0 .travis.yml
  3. +81 −41 Makefile
  4. +67 −0 README.md
  5. +5 −6 apps/snarl/src/snarl.app.src
  6. +25 −0 apps/snarl/src/snarl.erl
  7. +30 −0 apps/snarl/src/snarl.hrl
  8. +21 −14 apps/snarl/src/snarl_app.erl
  9. +70 −0 apps/snarl/src/snarl_console.erl
  10. +89 −0 apps/snarl/src/snarl_group.erl
  11. +233 −0 apps/snarl/src/snarl_group_read_fsm.erl
  12. +20 −0 apps/snarl/src/snarl_group_read_fsm_sup.erl
  13. +29 −0 apps/snarl/src/snarl_group_state.erl
  14. +238 −0 apps/snarl/src/snarl_group_vnode.erl
  15. +136 −0 apps/snarl/src/snarl_group_write_fsm.erl
  16. +21 −0 apps/snarl/src/snarl_group_write_fsm_sup.erl
  17. +42 −0 apps/snarl/src/snarl_node_event_handler.erl
  18. +113 −0 apps/snarl/src/snarl_obj.erl
  19. +68 −0 apps/snarl/src/snarl_permissions_vnode.erl
  20. +42 −0 apps/snarl/src/snarl_ring_event_handler.erl
  21. +34 −9 apps/snarl/src/snarl_sup.erl
  22. +245 −0 apps/snarl/src/snarl_user.erl
  23. +234 −0 apps/snarl/src/snarl_user_read_fsm.erl
  24. +20 −0 apps/snarl/src/snarl_user_read_fsm_sup.erl
  25. +49 −0 apps/snarl/src/snarl_user_state.erl
  26. +272 −0 apps/snarl/src/snarl_user_vnode.erl
  27. +136 −0 apps/snarl/src/snarl_user_write_fsm.erl
  28. +21 −0 apps/snarl/src/snarl_user_write_fsm_sup.erl
  29. +67 −0 apps/snarl/src/snarl_vnode.erl
  30. +100 −0 apps/snarl/src/snarl_zmq_handler.erl
  31. +49 −0 old/Makefile
  32. +18 −0 old/apps/snarl/src/snarl.app.src
  33. +28 −0 old/apps/snarl/src/snarl_app.erl
  34. 0  { → old}/apps/snarl/src/snarl_srv.erl
  35. +30 −0 old/apps/snarl/src/snarl_sup.erl
  36. +12 −0 old/rebar.config
  37. 0  { → old}/rel/files/epmd.xml
  38. +34 −0 old/rel/files/erl
  39. 0  { → old}/rel/files/install_upgrade.escript
  40. +138 −0 old/rel/files/nodetool
  41. +258 −0 old/rel/files/snarl
  42. 0  { → old}/rel/files/snarl.cmd
  43. 0  { → old}/rel/files/snarl.xml
  44. 0  { → old}/rel/files/start_erl.cmd
  45. 0  { → old}/rel/files/sys.config
  46. +22 −0 old/rel/files/vm.args
  47. +68 −0 old/rel/reltool.config
  48. 0  { → old}/standalone.config.example
  49. BIN  rebar
  50. +13 −11 rebar.config
  51. +42 −0 rel/files/app.config
  52. +1 −1  rel/files/erl
  53. +21 −123 rel/files/snarl
  54. +172 −0 rel/files/snarl-admin
  55. +7 −9 rel/files/vm.args
  56. +17 −46 rel/reltool.config
  57. +14 −0 rel/vars.config
  58. +14 −0 rel/vars/dev1.config
  59. +14 −0 rel/vars/dev2.config
  60. +14 −0 rel/vars/dev3.config
  61. +14 −0 rel/vars/dev4.config
View
2  .gitignore
@@ -1,3 +1,5 @@
+.eunit
+dev
ebin
standalone.config
db
View
5 .travis.yml
@@ -0,0 +1,5 @@
+language: erlang
+otp_release:
+ - R15B01
+notifications:
+ irc: "irc.freenode.org#project-FiFo"
View
122 Makefile
@@ -1,49 +1,89 @@
-APP_NAME=snarl
-APP_DIR=apps/$(APP_NAME)
-OBJ=$(shell ls $(APP_DIR)/src/*.erl | sed -e 's/\.erl$$/.beam/' | sed -e 's;^$(APP_DIR)/src;$(APP_DIR)/ebin;g') $(shell ls $(APP_DIR)/src/*.app.src | sed -e 's/\.src$$//g' | sed -e 's;^$(APP_DIR)/src;$(APP_DIR)/ebin;g')
-DEPS=$(shell cat rebar.config |sed -e 's/%.*//'| sed -e '/{\(\w\+\), [^,]\+, {\w\+, [^,]\+, {[^,]\+, [^}]\+}}},\?/!d' | sed -e 's;{\(\w\+\), [^,]\+, {\w\+, [^,]\+, {[^,]\+, [^}]\+}}},\?;deps/\1/rebar.config;')
-ERL=erl
-PA=$(shell pwd)/$(APP_DIR)/ebin
-ERL_LIBS=`pwd`/deps/
-REBAR=./rebar
-
-all: $(DEPS) $(OBJ)
-
-rel: all remove_trash FORCE
- -rm -r rel/$(APP_NAME)
- cd rel; ../rebar generate
-echo:
- echo $(DEPS)
-
-tar: rel
- cd rel; tar jcvf $(APP_NAME).tar.bz2 $(APP_NAME)
-
-clean: FORCE
- $(REBAR) clean
- -rm *.beam erl_crash.dump
- -rm -r rel/$(APP_NAME)
- -rm rel/$(APP_NAME).tar.bz2
+REBAR = $(shell pwd)/rebar
-$(DEPS):
- $(REBAR) get-deps
- $(REBAR) compile
+.PHONY: deps rel stagedevrel
-$(APP_DIR)/ebin/%.app: $(APP_DIR)/src/%.app.src
- $(REBAR) compile
+all: deps compile
-$(APP_DIR)/ebin/%.beam: $(APP_DIR)/src/%.erl
+compile:
$(REBAR) compile
-shell: all
- ERL_LIBS="$(ERL_LIBS)" $(ERL) -pa $(PA) -config standalone -sname $(APP_NAME)
- rm *.beam || true
- [ -f erl_crash.dump ] && rm erl_crash.dump || true
+deps:
+ $(REBAR) get-deps
+
+clean:
+ $(REBAR) clean
+
+distclean: clean devclean relclean
+ $(REBAR) delete-deps
+
+test:
+ $(REBAR) skip_deps=true eunit
+
+rel: all
+ $(REBAR) generate
+
+relclean:
+ rm -rf rel/snarl
+
+devrel: dev1 dev2 dev3
+
+###
+### Docs
+###
+docs:
+ $(REBAR) skip_deps=true doc
+
+##
+## Developer targets
+##
+
+stage : rel
+ $(foreach dep,$(wildcard deps/* wildcard apps/*), rm -rf rel/snarl/lib/$(shell basename $(dep))-* && ln -sf $(abspath $(dep)) rel/snarl/lib;)
+
+
+stagedevrel: dev1 dev2 dev3
+ $(foreach dev,$^,\
+ $(foreach dep,$(wildcard deps/* wildcard apps/*), rm -rf dev/$(dev)/lib/$(shell basename $(dep))-* && ln -sf $(abspath $(dep)) dev/$(dev)/lib;))
+
+devrel: dev1 dev2 dev3 dev4
+
+
+devclean:
+ rm -rf dev
+
+dev1 dev2 dev3 dev4: all
+ mkdir -p dev
+ (cd rel && $(REBAR) generate target_dir=../dev/$@ overlay_vars=vars/$@.config)
+
+
+##
+## Dialyzer
+##
+APPS = kernel stdlib sasl erts ssl tools os_mon runtime_tools crypto inets \
+ xmerl webtool snmp public_key mnesia eunit syntax_tools compiler
+COMBO_PLT = $(HOME)/.snarl_combo_dialyzer_plt
+
+check_plt: deps compile
+ dialyzer --check_plt --plt $(COMBO_PLT) --apps $(APPS) \
+ deps/*/ebin apps/*/ebin
+
+build_plt: deps compile
+ dialyzer --build_plt --output_plt $(COMBO_PLT) --apps $(APPS) \
+ deps/*/ebin apps/*/ebin
-FORCE:
+dialyzer: deps compile
+ @echo
+ @echo Use "'make check_plt'" to check PLT prior to using this target.
+ @echo Use "'make build_plt'" to build PLT prior to using this target.
+ @echo
+ @sleep 1
+ dialyzer -Wno_return --plt $(COMBO_PLT) deps/*/ebin apps/*/ebin
-manifest: rel
- ./tools/mkmanifest > manifest
-remove_trash:
- -find . -name "*~" -exec rm {} \;.
- -rm *.beam erl_crash.dump || true
+cleanplt:
+ @echo
+ @echo "Are you sure? It takes about 1/2 hour to re-build."
+ @echo Deleting $(COMBO_PLT) in 5 seconds.
+ @echo
+ sleep 5
+ rm $(COMBO_PLT)
View
67 README.md
@@ -0,0 +1,67 @@
+Snarl
+=====
+
+Build status (master): [![Build Status](https://secure.travis-ci.org/project-fifo/snarl.png?branch=master)](http://travis-ci.org/project-fifo/snarl)
+
+Build status (dev): [![Build Status](https://secure.travis-ci.org/project-fifo/snarl.png?branch=dev)](http://travis-ci.org/project-fifo/snarl)
+
+Snarl is a right management server build on top of [riak_core](https://github.com/basho/riak_core/). The permission architecture is as following:
+
+Each permission consists of a list of values, where the values '...' and '_' (both Erlang atoms) have a special meaning.
+
+* '...' matches one, more or no values.
+* '_' matches exactly one value.
+* everything else just matches itself.
+
+Examples
+--------
+
+[some, cool, permission] matches:
+
+* [some, cool, permission]
+* [some, '_', permission]
+* ['_', '_', permission]
+* ['...', permission]
+* [some, '...', permission]
+* [some, '...']
+
+Interface
+---------
+
+
+Snarl publishes it's servers via mDNS as
+
+```
+_snarl._zmq._tcp.<domain>
+```
+
+the txt record of the annoucements contains:
+
+* *server*: ip of the server
+* *port*: port of ZMQ
+
+Message
+-------
+
+
+User Rleated
+* {user, list} -> [user()]
+* {user, get, User} -> {ok, {user, Name, Password, Permissions, Groups}} | not_found
+* {user, add, User} -> ok
+* {user, auth, User, Pass} -> true | false
+* {user, allowed, User, Permission} -> true/false
+* {user, delete, User} -> ok
+* {user, passwd, User, Pass} -> ok | not_found
+* {user, join, User, Group} -> ok | not_found
+* {user, leave, User, Group} -> ok | not_found
+* {user, grant, User, Permission} -> ok | not_found
+* {user, revoke, User, Permission} -> ok | not_found
+
+
+Group Functions
+* {group, list} -> [user()]
+* {group, get, Group} -> {ok, {group, Name, Permissions}} | not_found
+* {group, add, Group} -> ok | not_found
+* {group, delete, Group} -> ok | not_found
+* {group, grant, Group, Permission} -> ok | not_found
+* {group, revoke, Group, Permission} -> ok | not_found
View
11 apps/snarl/src/snarl.app.src
@@ -1,3 +1,4 @@
+%% -*- erlang -*-
{application, snarl,
[
{description, "RBAC server"},
@@ -6,12 +7,10 @@
{applications, [
kernel,
stdlib,
- backyard,
- lager,
- redo,
- statsderl,
- vmstats,
- uuid
+ riak_core,
+ eleveldb,
+ zmq_mdns_server,
+ lager
]},
{mod, { snarl_app, []}},
{env, []}
View
25 apps/snarl/src/snarl.erl
@@ -0,0 +1,25 @@
+-module(snarl).
+-include("snarl.hrl").
+-include_lib("riak_core/include/riak_core_vnode.hrl").
+
+-export([
+ ping/0
+ ]).
+
+-define(TIMEOUT, 5000).
+
+%% Public API
+
+%% @doc Pings a random vnode to make sure communication is functional
+ping() ->
+ DocIdx = riak_core_util:chash_key({<<"ping">>, term_to_binary(now())}),
+ PrefList = riak_core_apl:get_primary_apl(DocIdx, 1, snarl),
+ [{IndexNode, _Type}] = PrefList,
+ riak_core_vnode_master:sync_spawn_command(IndexNode, ping, snarl_vnode_master).
+
+
+
+%%%===================================================================
+%%% Internal Functions
+%%%===================================================================
+
View
30 apps/snarl/src/snarl.hrl
@@ -0,0 +1,30 @@
+-define(PRINT(Var), io:format("DEBUG: ~p:~p - ~p~n~n ~p~n~n", [?MODULE, ?LINE, ??Var, Var])).
+
+-define(N, 4).
+-define(R, 3).
+-define(W, 3).
+-define(STATEBOX_EXPIRE, 60000).
+-define(DEFAULT_TIMEOUT, 10000).
+
+-record(user, {name :: binary(),
+ passwd :: binary(),
+ permissions = [] :: [permission()],
+ groups = [] :: [binary()]}).
+
+-record(group, {name :: binary(),
+ permissions = [] :: [permission()],
+ users = [] :: [binary()]}).
+
+-type permission() :: [binary() | atom()].
+
+-type val() :: statebox:statebox().
+
+-record(snarl_obj, {val :: val(),
+ vclock :: vclock:vclock()}).
+
+-type snarl_obj() :: #snarl_obj{} | not_found.
+
+-type idx_node() :: {integer(), node()}.
+
+-type vnode_reply() :: {idx_node(), snarl_obj() | not_found}.
+
View
35 apps/snarl/src/snarl_app.erl
@@ -3,26 +3,33 @@
-behaviour(application).
%% Application callbacks
--export([start/2, stop/1, load/0]).
-
-load() ->
- application:start(sasl),
- application:start(lager),
- application:start(crypto),
- application:start(mdns),
- application:start(backyard),
- application:start(statsderl),
- application:start(vmstats),
- application:start(redo),
- application:start(uuid),
- application:start(snarl).
+-export([start/2, stop/1]).
%% ===================================================================
%% Application callbacks
%% ===================================================================
start(_StartType, _StartArgs) ->
- snarl_sup:start_link().
+ case snarl_sup:start_link() of
+ {ok, Pid} ->
+ ok = riak_core:register([{vnode_module, snarl_vnode}]),
+ ok = riak_core_node_watcher:service_up(snarl, self()),
+
+ ok = riak_core:register([{vnode_module, snarl_user_vnode}]),
+ ok = riak_core_node_watcher:service_up(snarl_user, self()),
+
+ ok = riak_core:register([{vnode_module, snarl_group_vnode}]),
+ ok = riak_core_node_watcher:service_up(snarl_group, self()),
+
+ ok = riak_core:register([{vnode_module, snarl_permissions_vnode}]),
+ ok = riak_core_node_watcher:service_up(snarl_permissions, self()),
+
+ ok = riak_core_ring_events:add_guarded_handler(snarl_ring_event_handler, []),
+ ok = riak_core_node_watcher_events:add_guarded_handler(snarl_node_event_handler, []),
+ {ok, Pid};
+ {error, Reason} ->
+ {error, Reason}
+ end.
stop(_State) ->
ok.
View
70 apps/snarl/src/snarl_console.erl
@@ -0,0 +1,70 @@
+%% @doc Interface for snarl-admin commands.
+-module(snarl_console).
+-export([join/1,
+ leave/1,
+ remove/1,
+ ringready/1]).
+
+join([NodeStr]) ->
+ try riak_core:join(NodeStr) of
+ ok ->
+ io:format("Sent join request to ~s\n", [NodeStr]),
+ ok;
+ {error, not_reachable} ->
+ io:format("Node ~s is not reachable!\n", [NodeStr]),
+ error;
+ {error, different_ring_sizes} ->
+ io:format("Failed: ~s has a different ring_creation_size~n",
+ [NodeStr]),
+ error
+ catch
+ Exception:Reason ->
+ lager:error("Join failed ~p:~p", [Exception, Reason]),
+ io:format("Join failed, see log for details~n"),
+ error
+ end.
+
+leave([]) ->
+ remove_node(node()).
+
+remove([Node]) ->
+ remove_node(list_to_atom(Node)).
+
+remove_node(Node) when is_atom(Node) ->
+ try catch(riak_core:remove_from_cluster(Node)) of
+ {'EXIT', {badarg, [{erlang, hd, [[]]}|_]}} ->
+ %% This is a workaround because
+ %% riak_core_gossip:remove_from_cluster doesn't check if
+ %% the result of subtracting the current node from the
+ %% cluster member list results in the empty list. When
+ %% that code gets refactored this can probably go away.
+ io:format("Leave failed, this node is the only member.~n"),
+ error;
+ Res ->
+ io:format(" ~p\n", [Res])
+ catch
+ Exception:Reason ->
+ lager:error("Leave failed ~p:~p", [Exception, Reason]),
+ io:format("Leave failed, see log for details~n"),
+ error
+ end.
+
+-spec(ringready([]) -> ok | error).
+ringready([]) ->
+ try riak_core_status:ringready() of
+ {ok, Nodes} ->
+ io:format("TRUE All nodes agree on the ring ~p\n", [Nodes]);
+ {error, {different_owners, N1, N2}} ->
+ io:format("FALSE Node ~p and ~p list different partition owners\n",
+ [N1, N2]),
+ error;
+ {error, {nodes_down, Down}} ->
+ io:format("FALSE ~p down. All nodes need to be up to check.\n",
+ [Down]),
+ error
+ catch
+ Exception:Reason ->
+ lager:error("Ringready failed ~p:~p", [Exception, Reason]),
+ io:format("Ringready failed, see log for details~n"),
+ error
+ end.
View
89 apps/snarl/src/snarl_group.erl
@@ -0,0 +1,89 @@
+-module(snarl_group).
+-include("snarl.hrl").
+-include_lib("riak_core/include/riak_core_vnode.hrl").
+
+-export([
+ ping/0,
+ list/0,
+ get/1,
+ add/1,
+ delete/1,
+ grant/2,
+ revoke/2
+ ]).
+
+-define(TIMEOUT, 5000).
+
+
+%% Public API
+
+%% @doc Pings a random vnode to make sure communication is functional
+ping() ->
+ DocIdx = riak_core_util:chash_key({<<"ping">>, term_to_binary(now())}),
+ PrefList = riak_core_apl:get_primary_apl(DocIdx, 1, snarl_group),
+ [{IndexNode, _Type}] = PrefList,
+ riak_core_vnode_master:sync_spawn_command(IndexNode, ping, snarl_group_vnode_master).
+
+get(Group) ->
+ {ok, ReqID} = snarl_group_read_fsm:get(Group),
+ wait_for_reqid(ReqID, ?TIMEOUT).
+
+list() ->
+ {ok, ReqID} = snarl_group_read_fsm:list(),
+ wait_for_reqid(ReqID, ?TIMEOUT).
+
+add(Group) ->
+ do_write(Group, add).
+
+delete(Group) ->
+ do_update(Group, delete).
+
+grant(Group, Permission) ->
+ do_update(Group, grant, Permission).
+
+revoke(Group, Permission) ->
+ do_update(Group, revoke, Permission).
+
+
+
+%%%===================================================================
+%%% Internal Functions
+%%%===================================================================
+do_update(User, Op) ->
+ {ok, ReqID} = snarl_group_read_fsm:get(User),
+ case wait_for_reqid(ReqID, ?TIMEOUT) of
+ {ok, not_found} ->
+ not_found;
+ {ok, _UserObj} ->
+ do_write(User, Op)
+ end.
+
+do_update(User, Op, Val) ->
+ {ok, ReqID} = snarl_group_read_fsm:get(User),
+ case wait_for_reqid(ReqID, ?TIMEOUT) of
+ {ok, not_found} ->
+ not_found;
+ {ok, _UserObj} ->
+ do_write(User, Op, Val)
+ end.
+
+do_write(Group, Op) ->
+ {ok, ReqID} = snarl_group_write_fsm:write(Group, Op),
+ wait_for_reqid(ReqID, ?TIMEOUT).
+
+do_write(Group, Op, Val) ->
+ {ok, ReqID} = snarl_group_write_fsm:write(Group, Op, Val),
+ wait_for_reqid(ReqID, ?TIMEOUT).
+
+wait_for_reqid(ReqID, Timeout) ->
+ ?PRINT({waiting_for, ReqID}),
+ receive
+ {ReqID, ok} ->
+ ok;
+ {ReqID, ok, Val} ->
+ {ok, Val};
+ Other ->
+ ?PRINT({yuck, Other})
+ after Timeout ->
+ {error, timeout}
+ end.
View
233 apps/snarl/src/snarl_group_read_fsm.erl
@@ -0,0 +1,233 @@
+%% @doc The coordinator for stat get operations. The key here is to
+%% generate the preflist just like in wrtie_fsm and then query each
+%% replica and wait until a quorum is met.
+-module(snarl_group_read_fsm).
+-behavior(gen_fsm).
+-include("snarl.hrl").
+
+%% API
+-export([start_link/5, get/1, list/0, auth/2]).
+
+
+-export([reconcile/1, different/1, needs_repair/2, repair/3, unique/1]).
+
+%% Callbacks
+-export([init/1, code_change/4, handle_event/3, handle_info/3,
+ handle_sync_event/4, terminate/3]).
+
+%% States
+-export([prepare/2, execute/2, waiting/2, wait_for_n/2, finalize/2]).
+
+-record(state, {req_id,
+ from,
+ group,
+ op,
+ r=?R,
+ preflist,
+ num_r=0,
+ timeout=?DEFAULT_TIMEOUT,
+ val,
+ replies=[]}).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+start_link(Op, ReqID, From, Group, Val) ->
+ gen_fsm:start_link(?MODULE, [Op, ReqID, From, Group, Val], []).
+
+auth(Group, Passwd) ->
+ ReqID = mk_reqid(),
+ snarl_group_read_fsm_sup:start_read_fsm([auth, ReqID, self(), Group, Passwd]),
+ {ok, ReqID}.
+
+get(Group) ->
+ ReqID = mk_reqid(),
+ snarl_group_read_fsm_sup:start_read_fsm([get, ReqID, self(), Group, undefined]),
+ {ok, ReqID}.
+
+list() ->
+ ReqID = mk_reqid(),
+ snarl_group_read_fsm_sup:start_read_fsm([list, ReqID, self(), undefined, undefined]),
+ {ok, ReqID}.
+
+%%%===================================================================
+%%% States
+%%%===================================================================
+
+%% Intiailize state data.
+init([Op, ReqId, From, Group, Val]) ->
+ ?PRINT({init, [Op, ReqId, From, Group, Val]}),
+ SD = #state{req_id=ReqId,
+ from=From,
+ op=Op,
+ val=Val,
+ group=Group},
+ {ok, prepare, SD, 0};
+
+init([Op, ReqId, From, Group]) ->
+ ?PRINT({init, [Op, ReqId, From, Group]}),
+ SD = #state{req_id=ReqId,
+ from=From,
+ op=Op,
+ group=Group},
+ {ok, prepare, SD, 0};
+
+init([Op, ReqId, From]) ->
+ ?PRINT({init, [Op, ReqId, From]}),
+ SD = #state{req_id=ReqId,
+ from=From,
+ op=Op},
+ {ok, prepare, SD, 0}.
+
+%% @doc Calculate the Preflist.
+prepare(timeout, SD0=#state{group=Group}) ->
+ ?PRINT({prepare, Group}),
+ DocIdx = riak_core_util:chash_key({<<"group">>, term_to_binary(Group)}),
+ Prelist = riak_core_apl:get_apl(DocIdx, ?N, snarl_group),
+ SD = SD0#state{preflist=Prelist},
+ {next_state, execute, SD, 0}.
+
+%% @doc Execute the get reqs.
+execute(timeout, SD0=#state{req_id=ReqId,
+ group=Group,
+ op=Op,
+ val=Val,
+ preflist=Prelist}) ->
+ ?PRINT({execute, Group, Val}),
+ case Group of
+ undefined ->
+ snarl_group_vnode:Op(Prelist, ReqId);
+ _ ->
+ case Val of
+ undefined ->
+ snarl_group_vnode:Op(Prelist, ReqId, Group);
+ _ ->
+
+ snarl_group_vnode:Op(Prelist, ReqId, Group, Val)
+ end
+ end,
+ {next_state, waiting, SD0}.
+
+%% @doc Wait for R replies and then respond to From (original client
+%% that called `snarl:get/2').
+%% TODO: read repair...or another blog post?
+
+waiting({ok, ReqID, IdxNode, Obj},
+ SD0=#state{from=From, num_r=NumR0, replies=Replies0,
+ r=R, timeout=Timeout}) ->
+ NumR = NumR0 + 1,
+ Replies = [{IdxNode, Obj}|Replies0],
+ SD = SD0#state{num_r=NumR,replies=Replies},
+ if
+ NumR =:= R ->
+ case merge(Replies) of
+ not_found ->
+ From ! {ReqID, ok, not_found};
+ Merged ->
+ Reply = snarl_obj:val(Merged),
+ From ! {ReqID, ok, statebox:value(Reply)},
+ if NumR =:= ?N -> {next_state, finalize, SD, 0};
+ true -> {next_state, wait_for_n, SD, Timeout}
+ end
+ end;
+ true -> {next_state, waiting, SD}
+ end.
+
+wait_for_n({ok, _ReqID, IdxNode, Obj},
+ SD0=#state{num_r=?N - 1, replies=Replies0}) ->
+ Replies = [{IdxNode, Obj}|Replies0],
+ {next_state, finalize, SD0#state{num_r=?N, replies=Replies}, 0};
+
+wait_for_n({ok, _ReqID, IdxNode, Obj},
+ SD0=#state{num_r=NumR0, replies=Replies0, timeout=Timeout}) ->
+ NumR = NumR0 + 1,
+ Replies = [{IdxNode, Obj}|Replies0],
+ {next_state, wait_for_n, SD0#state{num_r=NumR, replies=Replies}, Timeout};
+
+%% TODO partial repair?
+wait_for_n(timeout, SD) ->
+ {stop, timeout, SD}.
+
+finalize(timeout, SD=#state{replies=Replies, group=Group}) ->
+ MObj = merge(Replies),
+ case needs_repair(MObj, Replies) of
+ true ->
+ repair(Group, MObj, Replies),
+ {stop, normal, SD};
+ false ->
+ {stop, normal, SD}
+ end.
+
+
+
+handle_info(_Info, _StateName, StateData) ->
+ {stop,badmsg,StateData}.
+
+handle_event(_Event, _StateName, StateData) ->
+ {stop,badmsg,StateData}.
+
+handle_sync_event(_Event, _From, _StateName, StateData) ->
+ {stop,badmsg,StateData}.
+
+code_change(_OldVsn, StateName, State, _Extra) -> {ok, StateName, State}.
+
+terminate(_Reason, _SN, _SD) ->
+ ok.
+
+%%%===================================================================
+%%% Internal Functions
+%%%===================================================================
+
+mk_reqid() -> erlang:phash2(erlang:now()).
+
+
+%% @pure
+%%
+%% @doc Given a list of `Replies' return the merged value.
+-spec merge([vnode_reply()]) -> snarl_obj() | not_found.
+merge(Replies) ->
+ Objs = [Obj || {_,Obj} <- Replies],
+ snarl_obj:merge(snarl_group_read_fsm, Objs).
+
+%% @pure
+%%
+%% @doc Reconcile conflicts among conflicting values.
+-spec reconcile([A :: statebox:statebox()]) -> A :: statebox:statebox().
+
+reconcile(Vals) ->
+ statebox:merge(Vals).
+
+
+%% @pure
+%%
+%% @doc Given the merged object `MObj' and a list of `Replies'
+%% determine if repair is needed.
+-spec needs_repair(any(), [vnode_reply()]) -> boolean().
+needs_repair(MObj, Replies) ->
+ Objs = [Obj || {_,Obj} <- Replies],
+ lists:any(different(MObj), Objs).
+
+%% @pure
+different(A) -> fun(B) -> not snarl_obj:equal(A,B) end.
+
+%% @impure
+%%
+%% @doc Repair any vnodes that do not have the correct object.
+-spec repair(string(), snarl_obj(), [vnode_reply()]) -> io.
+repair(_, _, []) -> io;
+
+repair(StatName, MObj, [{IdxNode,Obj}|T]) ->
+ case snarl_obj:equal(MObj, Obj) of
+ true -> repair(StatName, MObj, T);
+ false ->
+ snarl_group_vnode:repair(IdxNode, StatName, MObj),
+ repair(StatName, MObj, T)
+ end.
+
+%% pure
+%%
+%% @doc Given a list return the set of unique values.
+-spec unique([A::any()]) -> [A::any()].
+unique(L) ->
+ sets:to_list(sets:from_list(L)).
View
20 apps/snarl/src/snarl_group_read_fsm_sup.erl
@@ -0,0 +1,20 @@
+%% @doc Supervise the rts_write FSM.
+-module(snarl_group_read_fsm_sup).
+-behavior(supervisor).
+
+-export([start_read_fsm/1,
+ start_link/0]).
+
+-export([init/1]).
+
+start_read_fsm(Args) ->
+ supervisor:start_child(?MODULE, Args).
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+init([]) ->
+ ReadFsm = {undefined,
+ {snarl_group_read_fsm, start_link, []},
+ temporary, 5000, worker, [snarl_group_read_fsm]},
+ {ok, {{simple_one_for_one, 10, 10}, [ReadFsm]}}.
View
29 apps/snarl/src/snarl_group_state.erl
@@ -0,0 +1,29 @@
+%%% @author Heinz Nikolaus Gies <heinz@licenser.net>
+%%% @copyright (C) 2012, Heinz Nikolaus Gies
+%%% @doc
+%%%
+%%% @end
+%%% Created : 23 Aug 2012 by Heinz Nikolaus Gies <heinz@licenser.net>
+
+-module(snarl_group_state).
+
+-include("snarl.hrl").
+
+-export([
+ new/0,
+ name/2,
+ grant/2,
+ revoke/2
+ ]).
+
+new() ->
+ #group{}.
+
+name(Name, Group) ->
+ Group#group{name = Name}.
+
+grant(Permission, Group) ->
+ Group#group{permissions = ordsets:add_element(Permission, Group#group.permissions)}.
+
+revoke(Permission, Group) ->
+ Group#group{permissions = ordsets:del_element(Permission, Group#group.permissions)}.
View
238 apps/snarl/src/snarl_group_vnode.erl
@@ -0,0 +1,238 @@
+-module(snarl_group_vnode).
+-behaviour(riak_core_vnode).
+-include("snarl.hrl").
+-include_lib("riak_core/include/riak_core_vnode.hrl").
+
+
+-export([start_vnode/1,
+ init/1,
+ terminate/2,
+ handle_command/3,
+ is_empty/1,
+ delete/1,
+ handle_handoff_command/3,
+ handoff_starting/2,
+ handoff_cancelled/1,
+ handoff_finished/2,
+ handle_handoff_data/2,
+ encode_handoff_item/2,
+ handle_coverage/4,
+ handle_exit/3]).
+
+% Reads
+-export([list/2,
+ get/3]).
+
+% Writes
+-export([add/3,
+ delete/3,
+ grant/4,
+ repair/3,
+ revoke/4]).
+
+-record(state, {partition, groups=[], dbref, node}).
+
+-define(MASTER, snarl_group_vnode_master).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+start_vnode(I) ->
+ riak_core_vnode_master:get_vnode_pid(I, ?MODULE).
+
+
+repair(IdxNode, Group, Obj) ->
+ riak_core_vnode_master:command(IdxNode,
+ {repair, undefined, Group, Obj},
+ ignore,
+ ?MASTER).
+
+%%%===================================================================
+%%% API - reads
+%%%===================================================================
+
+get(Preflist, ReqID, Group) ->
+ riak_core_vnode_master:command(Preflist,
+ {get, ReqID, Group},
+ {fsm, undefined, self()},
+ ?MASTER).
+
+list(Preflist, ReqID) ->
+ riak_core_vnode_master:command(Preflist,
+ {list, ReqID},
+ {fsm, undefined, self()},
+ ?MASTER).
+
+
+%%%===================================================================
+%%% API - writes
+%%%===================================================================
+
+add(Preflist, ReqID, Group) ->
+ riak_core_vnode_master:command(Preflist,
+ {add, ReqID, Group},
+ {fsm, undefined, self()},
+ ?MASTER).
+
+delete(Preflist, ReqID, Group) ->
+ riak_core_vnode_master:command(Preflist,
+ {delete, ReqID, Group},
+ {fsm, undefined, self()},
+ ?MASTER).
+
+grant(Preflist, ReqID, Group, Val) ->
+ riak_core_vnode_master:command(Preflist,
+ {grant, ReqID, Group, Val},
+ {fsm, undefined, self()},
+ ?MASTER).
+
+revoke(Preflist, ReqID, Group, Val) ->
+ riak_core_vnode_master:command(Preflist,
+ {revoke, ReqID, Group, Val},
+ {fsm, undefined, self()},
+ ?MASTER).
+
+%%%===================================================================
+%%% VNode
+%%%===================================================================
+
+init([Partition]) ->
+ {ok, DBRef} = eleveldb:open("groups."++integer_to_list(Partition)++".ldb", [{create_if_missing, true}]),
+ Groups = read_groups(DBRef),
+ {ok, #state {
+ node=node(),
+ groups=Groups,
+ partition=Partition,
+ dbref=DBRef}}.
+
+%% Sample command: respond to a ping
+handle_command(ping, _Sender, State) ->
+ {reply, {pong, State#state.partition}, State};
+
+handle_command({repair, undefined, Group, Obj}, _Sender, #state{groups=Groups0}=State) ->
+ lager:warning("repair performed ~p~n", [Obj]),
+ Groups1 = dict:store(Group, Obj, Groups0),
+ {noreply, State#state{groups=Groups1}};
+
+handle_command({add, {ReqID, Coordinator}, Group}, _Sender, #state{groups=Groups, dbref=DBRef} = State) ->
+ Group0 = statebox:new({snarl_group_state, new}),
+ Group1 = statebox:modify({snarl_group_state, name, [Group]}, Group0),
+ VC0 = vclock:fresh(),
+ VC = vclock:increment(Coordinator, VC0),
+ GroupObj = #snarl_obj{val=Group1, vclock=VC},
+ {ok, Groups1} = add_group(Group, GroupObj, Groups, DBRef),
+ {reply, {ok, ReqID}, State#state{groups=Groups1}};
+
+handle_command({delete, {ReqID, _Coordinator}, Group}, _Sender, #state{groups=Groups, dbref=DBRef} = State) ->
+ Groups1 = dict:erase(Group, Groups),
+ eleveldb:put(DBRef, <<"#groups">>, term_to_binary(dict:fetch_keys(Groups1)), []),
+ eleveldb:delete(DBRef, list_to_binary(Group), []),
+ {reply, {ok, ReqID}, State#state{groups=Groups1}};
+
+handle_command({list, ReqID}, _Sender, #state{groups=Groups} = State) ->
+ {reply, {ok, ReqID, dict:fetch_keys(Groups)}, State};
+
+handle_command({get, ReqID, Group}, _Sender, #state{groups=Groups, partition=Partition, node=Node} = State) ->
+ Res = case dict:find(Group, Groups) of
+ error ->
+ {ok, ReqID, {Partition,Node}, not_found};
+ {ok, V} ->
+ {ok, ReqID, {Partition,Node}, V}
+ end,
+ {reply, Res, State};
+
+handle_command({Action, {ReqID, Coordinator}, Group, Passwd}, _Sender,
+ #state{groups=Groups, dbref=DBRef} = State) ->
+ Groups1 = change_group_callback(Group, Action, Passwd, Coordinator, Groups, DBRef),
+ {reply, {ok, ReqID}, State#state{groups=Groups1}};
+
+handle_command(_Message, _Sender, State) ->
+ {noreply, State}.
+
+handle_handoff_command(?FOLD_REQ{foldfun=Fun, acc0=Acc0}, _Sender, State) ->
+ Acc = dict:fold(Fun, Acc0, State#state.groups),
+ {reply, Acc, State};
+
+handle_handoff_command(_Message, _Sender, State) ->
+ {noreply, State}.
+
+handoff_starting(TargetNode, State) ->
+ lager:warning("Starting handof to: ~p", [TargetNode]),
+ {true, State}.
+
+handoff_cancelled(State) ->
+ Groups = read_groups(State#state.dbref),
+ {ok, State#state{groups=Groups}}.
+
+handoff_finished(_TargetNode, State) ->
+ {ok, State}.
+
+handle_handoff_data(Data, State) ->
+ {Group, Data} = binary_to_term(Data),
+ {ok, Groups1} = add_group(Group, Data, State#state.groups, State#state.dbref),
+ {reply, ok, State#state{groups = Groups1}}.
+
+encode_handoff_item(Group, Data) ->
+ term_to_binary({Group, Data}).
+
+is_empty(State) ->
+ case dict:size(State#state.groups) of
+ 0 ->
+ {true, State};
+ _ ->
+ {true, State}
+ end.
+
+delete(#state{dbref=DBRef} = State) ->
+ eleveldb:close(DBRef),
+ eleveldb:destroy("groups."++integer_to_list(State#state.partition)++".ldb",[]),
+ {ok, DBRef1} = eleveldb:open("groups."++integer_to_list(State#state.partition)++".ldb", [{create_if_missing, true}]),
+ {ok, State#state{dbref=DBRef1}}.
+
+handle_coverage(_Req, _KeySpaces, _Sender, State) ->
+ {stop, not_implemented, State}.
+
+handle_exit(_Pid, _Reason, State) ->
+ {noreply, State}.
+
+
+terminate(_Reason, #state{dbref=undefined} = _State) ->
+ ok;
+
+terminate(_Reason, #state{dbref=DBRef} = _State) ->
+ eleveldb:close(DBRef),
+ ok.
+
+read_groups(DBRef) ->
+ case eleveldb:get(DBRef, <<"#groups">>, []) of
+ not_found ->
+ dict:new();
+ {ok, Bin} ->
+ lists:foldl(fun (Group, Groups0) ->
+ {ok, GrBin} = eleveldb:get(DBRef, list_to_binary(Group), []),
+ dict:store(Group, binary_to_term(GrBin), Groups0)
+ end, dict:new(), binary_to_term(Bin))
+ end.
+
+add_group(Group, GroupData, Groups, DBRef) ->
+ Groups1 = dict:store(Group, GroupData, Groups),
+ eleveldb:put(DBRef, <<"#groups">>, term_to_binary(dict:fetch_keys(Groups1)), []),
+ eleveldb:put(DBRef, list_to_binary(Group), term_to_binary(GroupData), []),
+ {ok, Groups1}.
+
+
+
+change_group_callback(Group, Action, Val, Coordinator, Groups, DBRef) ->
+ Groups1 = dict:update(Group, update_group(Coordinator, Val, Action), Groups),
+ {ok, GroupData} = dict:find(Group, Groups1),
+ eleveldb:put(DBRef, list_to_binary(Group), term_to_binary(GroupData), []),
+ Groups1.
+
+
+update_group(Coordinator, Val, Action) ->
+ fun (#snarl_obj{val=Group0}=O) ->
+ Group1 = statebox:modify({snarl_group_state, Action, [Val]}, Group0),
+ Group2 = statebox:expire(?STATEBOX_EXPIRE, Group1),
+ snarl_obj:update(Group2, Coordinator, O)
+ end.
View
136 apps/snarl/src/snarl_group_write_fsm.erl
@@ -0,0 +1,136 @@
+%% @doc The coordinator for stat write opeartions. This example will
+%% show how to properly replicate your data in Riak Core by making use
+%% of the _preflist_.
+-module(snarl_group_write_fsm).
+-behavior(gen_fsm).
+-include("snarl.hrl").
+
+%% API
+-export([start_link/4, start_link/5, write/2, write/3]).
+
+%% Callbacks
+-export([init/1, code_change/4, handle_event/3, handle_info/3,
+ handle_sync_event/4, terminate/3]).
+
+%% States
+-export([prepare/2, execute/2, waiting/2]).
+
+%% req_id: The request id so the caller can verify the response.
+%%
+%% from: The pid of the sender so a reply can be made.
+%%
+%% group: The group.
+%%
+%% op: The stat op, one of [add, delete, grant, revoke].
+%%
+%% val: Additional arguments passed.
+%%
+%% prelist: The preflist for the given {Client, StatName} pair.
+%%
+%% num_w: The number of successful write replies.
+-record(state, {req_id :: pos_integer(),
+ from :: pid(),
+ group :: string(),
+ op :: atom(),
+ cordinator :: node(),
+ val = undefined :: term() | undefined,
+ preflist :: riak_core_apl:preflist2(),
+ num_w = 0 :: non_neg_integer()}).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+start_link(ReqID, From, Group, Op) ->
+ start_link(ReqID, From, Group, Op, undefined).
+
+start_link(ReqID, From, Group, Op, Val) ->
+ gen_fsm:start_link(?MODULE, [ReqID, From, Group, Op, Val], []).
+
+write(Group, Op) ->
+ write(Group, Op, undefined).
+
+write(Group, Op, Val) ->
+ ReqID = mk_reqid(),
+ snarl_group_write_fsm_sup:start_write_fsm([ReqID, self(), Group, Op, Val]),
+ {ok, ReqID}.
+
+%%%===================================================================
+%%% States
+%%%===================================================================
+
+%% @doc Initialize the state data.
+init([ReqID, From, Group, Op, Val]) ->
+ SD = #state{req_id=ReqID,
+ from=From,
+ group=Group,
+ op=Op,
+ cordinator=node(),
+ val=Val},
+ {ok, prepare, SD, 0}.
+
+%% @doc Prepare the write by calculating the _preference list_.
+prepare(timeout, SD0=#state{group=Group}) ->
+ DocIdx = riak_core_util:chash_key({<<"group">>, term_to_binary(Group)}),
+ Preflist = riak_core_apl:get_apl(DocIdx, ?N, snarl_group),
+ SD = SD0#state{preflist=Preflist},
+ {next_state, execute, SD, 0}.
+
+%% @doc Execute the write request and then go into waiting state to
+%% verify it has meets consistency requirements.
+execute(timeout, SD0=#state{req_id=ReqID,
+ group=Group,
+ op=Op,
+ val=Val,
+ cordinator=Cordinator,
+ preflist=Preflist}) ->
+ case Val of
+ undefined ->
+ snarl_group_vnode:Op(Preflist, {ReqID, Cordinator}, Group);
+ _ ->
+ snarl_group_vnode:Op(Preflist, {ReqID, Cordinator}, Group, Val)
+ end,
+ {next_state, waiting, SD0}.
+
+%% @doc Wait for W write reqs to respond.
+waiting({ok, ReqID}, SD0=#state{from=From, num_w=NumW0, req_id=ReqID}) ->
+ NumW = NumW0 + 1,
+ SD = SD0#state{num_w=NumW},
+ lager:warning("Write(~p) ok", [NumW]),
+ if
+ NumW =:= ?W ->
+ From ! {ReqID, ok},
+ {stop, normal, SD};
+ true -> {next_state, waiting, SD}
+ end;
+waiting({ok, ReqID, Reply}, SD0=#state{from=From, num_w=NumW0, req_id=ReqID}) ->
+ NumW = NumW0 + 1,
+ SD = SD0#state{num_w=NumW},
+ lager:warning("Write(~p) reply: ~p", [NumW, Reply]),
+ if
+ NumW =:= ?W ->
+ From ! {ReqID, ok, Reply},
+ {stop, normal, SD};
+ true -> {next_state, waiting, SD}
+ end.
+
+handle_info(_Info, _StateName, StateData) ->
+ {stop,badmsg,StateData}.
+
+handle_event(_Event, _StateName, StateData) ->
+ {stop,badmsg,StateData}.
+
+handle_sync_event(_Event, _From, _StateName, StateData) ->
+ {stop,badmsg,StateData}.
+
+code_change(_OldVsn, StateName, State, _Extra) ->
+ {ok, StateName, State}.
+
+terminate(_Reason, _SN, _SD) ->
+ ok.
+
+%%%===================================================================
+%%% Internal Functions
+%%%===================================================================
+
+mk_reqid() -> erlang:phash2(erlang:now()).
View
21 apps/snarl/src/snarl_group_write_fsm_sup.erl
@@ -0,0 +1,21 @@
+%% @doc Supervise the rts_write FSM.
+-module(snarl_group_write_fsm_sup).
+-behavior(supervisor).
+
+-export([start_write_fsm/1,
+ start_link/0]).
+
+-export([init/1]).
+
+start_write_fsm(Args) ->
+ supervisor:start_child(?MODULE, Args).
+
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+init([]) ->
+ WriteFsm = {undefined,
+ {snarl_group_write_fsm, start_link, []},
+ temporary, 5000, worker, [snarl_group_write_fsm]},
+ {ok, {{simple_one_for_one, 10, 10}, [WriteFsm]}}.
View
42 apps/snarl/src/snarl_node_event_handler.erl
@@ -0,0 +1,42 @@
+%% This file is provided to you 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.
+
+%% Copyright (c) 2007-2011 Basho Technologies, Inc. All Rights Reserved.
+
+-module(snarl_node_event_handler).
+-behaviour(gen_event).
+
+%% gen_event callbacks
+-export([init/1, handle_event/2, handle_call/2,
+ handle_info/2, terminate/2, code_change/3]).
+-record(state, {}).
+
+init([]) ->
+ {ok, #state{}}.
+
+handle_event({service_update, _Services}, State) ->
+ {ok, State}.
+
+handle_call(_Event, State) ->
+ {ok, ok, State}.
+
+handle_info(_Info, State) ->
+ {ok, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
View
113 apps/snarl/src/snarl_obj.erl
@@ -0,0 +1,113 @@
+%% @doc A suite of functions that operate on the algebraic data type
+%% `snarl_obj'.
+%%
+%% TODO Possibly move type/record defs in there and use accessor funs
+%% and opaque types.
+%%
+%% Taken form https://github.com/Licenser/try-try-try/blob/master/2011/riak-core-conflict-resolution/rts/src/rts_obj.erl
+
+-module(snarl_obj).
+-export([ancestors/1, children/1, equal/1, equal/2, merge/2, unique/1,
+ update/3]).
+-export([val/1, vclock/1]).
+
+-include("snarl.hrl").
+
+%% @pure
+%%
+%% @doc Given a list of `snarl_obj()' return a list of all the
+%% ancestors. Ancestors are objects that all the other objects in the
+%% list have descent from.
+-spec ancestors([snarl_obj()]) -> [snarl_obj()].
+ancestors(Objs0) ->
+ Objs = [O || O <- Objs0, O /= not_found],
+ As = [[O2 || O2 <- Objs,
+ ancestor(O2#snarl_obj.vclock,
+ O1#snarl_obj.vclock)] || O1 <- Objs],
+ unique(lists:flatten(As)).
+
+%% @pure
+%%
+%% @doc Predicate to determine if `Va' is ancestor of `Vb'.
+-spec ancestor(vclock:vclock(), vclock:vclock()) -> boolean().
+ancestor(Va, Vb) ->
+ vclock:descends(Vb, Va) andalso (vclock:descends(Va, Vb) == false).
+
+%% @pure
+%%
+%% @doc Given a list of `snarl_obj()' return a list of the children
+%% objects. Children are the descendants of all others objects.
+children(Objs) ->
+ unique(Objs) -- ancestors(Objs).
+
+%% @pure
+%%
+%% @doc Predeicate to determine if `ObjA' and `ObjB' are equal.
+-spec equal(ObjA::snarl_obj(), ObjB::snarl_obj()) -> boolean().
+equal(#snarl_obj{vclock=A}, #snarl_obj{vclock=B}) -> vclock:equal(A,B);
+equal(not_found, not_found) -> true;
+equal(_, _) -> false.
+
+%% @pure
+%%
+%% @doc Closure around `equal/2' for use with HOFs (damn verbose
+%% Erlang).
+-spec equal(ObjA::snarl_obj()) -> fun((ObjB::snarl_obj()) -> boolean()).
+equal(ObjA) ->
+ fun(ObjB) -> equal(ObjA, ObjB) end.
+
+%% @pure
+%%
+%% @doc Merge the list of `Objs', calling the appropriate reconcile
+%% fun if there are siblings.
+-spec merge(atom(),[snarl_obj()]) -> snarl_obj().
+merge(FSM, [not_found|_]=Objs) ->
+ P = fun(X) -> X == not_found end,
+ case lists:all(P, Objs) of
+ true -> not_found;
+ false -> merge(FSM, lists:dropwhile(P, Objs))
+ end;
+
+merge(FSM, [#snarl_obj{}|_]=Objs) ->
+ case snarl_obj:children(Objs) of
+ [] -> not_found;
+ [Child] -> Child;
+ Chldrn ->
+ Val = FSM:reconcile(lists:map(fun val/1, Chldrn)),
+ MergedVC = vclock:merge(lists:map(fun vclock/1, Chldrn)),
+ #snarl_obj{val=Val, vclock=MergedVC}
+ end.
+
+%% @pure
+%%
+%% @doc Given a list of `Objs' return the list of uniques.
+-spec unique([snarl_obj()]) -> [snarl_obj()].
+unique(Objs) ->
+ F = fun(not_found, Acc) ->
+ Acc;
+ (Obj, Acc) ->
+ case lists:any(equal(Obj), Acc) of
+ true -> Acc;
+ false -> [Obj|Acc]
+ end
+ end,
+ lists:foldl(F, [], Objs).
+
+%% @pure
+%%
+%% @doc Given a `Val' update the `Obj'. The `Updater' is the name of
+%% the entity performing the update.
+-spec update(val(), node(), snarl_obj()) -> snarl_obj().
+update(Val, Updater, #snarl_obj{vclock=VClock0}=Obj0) ->
+ VClock = vclock:increment(Updater, VClock0),
+ Obj0#snarl_obj{val=Val, vclock=VClock}.
+
+-spec val(snarl_obj()) -> any().
+val(#snarl_obj{val=Val}) -> Val;
+val(not_found) -> not_found.
+
+%% @pure
+%%
+%% @doc Given a vclock type `Obj' retrieve the vclock.
+-spec vclock(snarl_obj()) -> vclock:vclock().
+vclock(#snarl_obj{vclock=VC}) -> VC.
View
68 apps/snarl/src/snarl_permissions_vnode.erl
@@ -0,0 +1,68 @@
+-module(snarl_permissions_vnode).
+-behaviour(riak_core_vnode).
+-include("snarl.hrl").
+
+-export([start_vnode/1,
+ init/1,
+ terminate/2,
+ handle_command/3,
+ is_empty/1,
+ delete/1,
+ handle_handoff_command/3,
+ handoff_starting/2,
+ handoff_cancelled/1,
+ handoff_finished/2,
+ handle_handoff_data/2,
+ encode_handoff_item/2,
+ handle_coverage/4,
+ handle_exit/3]).
+
+-record(state, {partition}).
+
+%% API
+start_vnode(I) ->
+ riak_core_vnode_master:get_vnode_pid(I, ?MODULE).
+
+init([Partition]) ->
+ {ok, #state { partition=Partition }}.
+
+%% Sample command: respond to a ping
+handle_command(ping, _Sender, State) ->
+ {reply, {pong, State#state.partition}, State};
+
+handle_command(Message, _Sender, State) ->
+ ?PRINT({unhandled_command, Message}),
+ {noreply, State}.
+
+handle_handoff_command(_Message, _Sender, State) ->
+ {noreply, State}.
+
+handoff_starting(_TargetNode, State) ->
+ {true, State}.
+
+handoff_cancelled(State) ->
+ {ok, State}.
+
+handoff_finished(_TargetNode, State) ->
+ {ok, State}.
+
+handle_handoff_data(_Data, State) ->
+ {reply, ok, State}.
+
+encode_handoff_item(_ObjectName, _ObjectValue) ->
+ <<>>.
+
+is_empty(State) ->
+ {true, State}.
+
+delete(State) ->
+ {ok, State}.
+
+handle_coverage(_Req, _KeySpaces, _Sender, State) ->
+ {stop, not_implemented, State}.
+
+handle_exit(_Pid, _Reason, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
View
42 apps/snarl/src/snarl_ring_event_handler.erl
@@ -0,0 +1,42 @@
+%% This file is provided to you 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.
+
+%% Copyright (c) 2007-2011 Basho Technologies, Inc. All Rights Reserved.
+
+-module(snarl_ring_event_handler).
+-behaviour(gen_event).
+
+%% gen_event callbacks
+-export([init/1, handle_event/2, handle_call/2,
+ handle_info/2, terminate/2, code_change/3]).
+-record(state, {}).
+
+init([]) ->
+ {ok, #state{}}.
+
+handle_event({ring_update, _Ring}, State) ->
+ {ok, State}.
+
+handle_call(_Event, State) ->
+ {ok, ok, State}.
+
+handle_info(_Info, State) ->
+ {ok, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
View
43 apps/snarl/src/snarl_sup.erl
@@ -1,4 +1,3 @@
-
-module(snarl_sup).
-behaviour(supervisor).
@@ -9,9 +8,6 @@
%% Supervisor callbacks
-export([init/1]).
-%% Helper macro for declaring children of supervisor
--define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
-
%% ===================================================================
%% API functions
%% ===================================================================
@@ -23,8 +19,37 @@ start_link() ->
%% Supervisor callbacks
%% ===================================================================
-init([]) ->
- {ok, { {one_for_one, 5, 10}, [?CHILD(redo, worker),
- ?CHILD(snarl_srv, worker)
- ]} }.
-
+init(_Args) ->
+ VMaster = { snarl_vnode_master,
+ {riak_core_vnode_master, start_link, [snarl_vnode]},
+ permanent, 5000, worker, [riak_core_vnode_master]},
+
+ GroupVMaster = { snarl_group_vnode_master,
+ {riak_core_vnode_master, start_link, [snarl_group_vnode]},
+ permanent, 5000, worker, [riak_core_vnode_master]},
+
+ GroupWFSMs = {snarl_group_write_fsm_sup,
+ {snarl_group_write_fsm_sup, start_link, []},
+ permanent, infinity, supervisor, [snarl_group_write_fsm_sup]},
+
+ GroupRFSMs = {snarl_group_read_fsm_sup,
+ {snarl_group_read_fsm_sup, start_link, []},
+ permanent, infinity, supervisor, [snarl_group_read_fsm_sup]},
+
+ UserVMaster = {snarl_user_vnode_master,
+ {riak_core_vnode_master, start_link, [snarl_user_vnode]},
+ permanent, 5000, worker, [riak_core_vnode_master]},
+
+ UserWFSMs = {snarl_user_write_fsm_sup,
+ {snarl_user_write_fsm_sup, start_link, []},
+ permanent, infinity, supervisor, [snarl_user_write_fsm_sup]},
+
+ UserRFSMs = {snarl_user_read_fsm_sup,
+ {snarl_user_read_fsm_sup, start_link, []},
+ permanent, infinity, supervisor, [snarl_user_read_fsm_sup]},
+
+ { ok,
+ { {one_for_one, 5, 10},
+ [VMaster,
+ GroupVMaster, GroupWFSMs, GroupRFSMs,
+ UserVMaster, UserWFSMs, UserRFSMs]}}.
View
245 apps/snarl/src/snarl_user.erl
@@ -0,0 +1,245 @@
+-module(snarl_user).
+-include("snarl.hrl").
+-include_lib("riak_core/include/riak_core_vnode.hrl").
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
+-export([
+ ping/0,
+ list/0,
+ auth/2,
+ get/1,
+ add/1,
+ delete/1,
+ passwd/2,
+ join/2,
+ leave/2,
+ grant/2,
+ revoke/2,
+ allowed/2
+ ]).
+
+-define(TIMEOUT, 5000).
+
+%% Public API
+
+%% @doc Pings a random vnode to make sure communication is functional
+ping() ->
+ DocIdx = riak_core_util:chash_key({<<"ping">>, term_to_binary(now())}),
+ PrefList = riak_core_apl:get_primary_apl(DocIdx, 1, snarl_user),
+ [{IndexNode, _Type}] = PrefList,
+ riak_core_vnode_master:sync_spawn_command(IndexNode, ping, snarl_user_vnode_master).
+
+auth(User, Passwd) ->
+ {ok, ReqID} = snarl_user_read_fsm:get(User),
+ case wait_for_reqid(ReqID, ?TIMEOUT) of
+ {ok, not_found} ->
+ not_found;
+ {ok, UserObj} ->
+ CurrentHash = UserObj#user.passwd,
+ case crypto:sha([User, Passwd]) of
+ CurrentHash ->
+ true;
+ _ ->
+ false
+ end
+ end.
+
+allowed(User, Permission) ->
+ {ok, ReqID} = snarl_user_read_fsm:get(User),
+ case wait_for_reqid(ReqID, ?TIMEOUT) of
+ {ok, not_found} ->
+ not_found;
+ {ok, UserObj} ->
+ test_user(UserObj, Permission)
+ end.
+
+get(User) ->
+ {ok, ReqID} = snarl_user_read_fsm:get(User),
+ wait_for_reqid(ReqID, ?TIMEOUT).
+
+list() ->
+ {ok, ReqID} = snarl_user_read_fsm:list(),
+ wait_for_reqid(ReqID, ?TIMEOUT).
+
+add(User) ->
+ do_update(User, add).
+
+passwd(User, Passwd) ->
+ do_update(User, passwd, Passwd).
+
+join(User, Group) ->
+ do_update(User, join, Group).
+
+leave(User, Group) ->
+ do_update(User, leave, Group).
+
+delete(User) ->
+ do_update(User, delete).
+
+grant(User, Permission) ->
+ do_update(User, grant, Permission).
+
+revoke(User, Permission) ->
+ do_update(User, revoke, Permission).
+
+%%%===================================================================
+%%% Internal Functions
+%%%===================================================================
+
+do_update(User, Op) ->
+ {ok, ReqID} = snarl_user_read_fsm:get(User),
+ case wait_for_reqid(ReqID, ?TIMEOUT) of
+ {ok, not_found} ->
+ not_found;
+ {ok, _UserObj} ->
+ do_write(User, Op)
+ end.
+
+do_update(User, Op, Val) ->
+ {ok, ReqID} = snarl_user_read_fsm:get(User),
+ case wait_for_reqid(ReqID, ?TIMEOUT) of
+ {ok, not_found} ->
+ not_found;
+ {ok, _UserObj} ->
+ do_write(User, Op, Val)
+ end.
+
+do_write(User, Op) ->
+ {ok, ReqID} = snarl_user_write_fsm:write(User, Op),
+ wait_for_reqid(ReqID, ?TIMEOUT).
+
+do_write(User, Op, Val) ->
+ {ok, ReqID} = snarl_user_write_fsm:write(User, Op, Val),
+ wait_for_reqid(ReqID, ?TIMEOUT).
+
+wait_for_reqid(ReqID, Timeout) ->
+ ?PRINT({waiting_for, ReqID}),
+ receive
+ {ReqID, ok} ->
+ ok;
+ {ReqID, ok, Val} ->
+ {ok, Val};
+ Other ->
+ ?PRINT({yuck, Other})
+ after Timeout ->
+ {error, timeout}
+ end.
+
+match([], []) ->
+ lager:info("snarl:match - Direct match"),
+ true;
+
+match(P, ['...']) ->
+ lager:info("snarl:match - Matched ~p by '...'", [P]),
+ true;
+
+match([], ['...'|_Rest]) ->
+ false;
+
+match([], [_X|_R]) ->
+ false;
+
+match([X | InRest], ['...', X|TestRest] = Test) ->
+ match(InRest, TestRest) orelse match(InRest, Test);
+
+match([_,X|InRest], ['...', X|TestRest] = Test) ->
+ match(InRest, TestRest) orelse match([X| InRest], Test);
+
+match([_ | InRest], ['...'|_TestRest] = Test) ->
+ match(InRest, Test);
+
+match([X|InRest], [X|TestRest]) ->
+ match(InRest, TestRest);
+
+match([_|InRest], ['_'|TestRest]) ->
+ match(InRest, TestRest);
+
+match(_, _) ->
+ false.
+
+test_perms(_Perm, []) ->
+ false;
+
+test_perms(Perm, [Test|Tests]) ->
+ match(Perm, Test) orelse test_perms(Perm, Tests).
+
+test_groups(_Permission, []) ->
+ false;
+
+test_groups(Permission, [Group|Groups]) ->
+ case snarl_group:get(Group) of
+ {ok, GroupObj} ->
+ case test_perms(Permission, GroupObj#group.permissions) of
+ true ->
+ true;
+ false ->
+ test_groups(Permission, Groups)
+ end;
+ _ ->
+ test_groups(Permission, Groups)
+ end.
+
+test_user(UserObj, Permission) ->
+ case test_perms(Permission, UserObj#user.permissions) of
+ true ->
+ true;
+ false ->
+ test_groups(Permission, UserObj#user.groups)
+ end.
+
+-ifdef(TEST).
+
+match_direct_test() ->
+ ?assert(true == match([some_permission], [some_permission])).
+
+nomatch_direct_test() ->
+ ?assert(false == match([some_permission], [some_other_permission])).
+
+match_direct_list_test() ->
+ ?assert(true == match([some, permission], [some, permission])).
+
+nomatch_direct_list_test() ->
+ ?assert(false == match([some, permission], [some, other, permission])),
+ ?assert(false == match([some, permission], [some, other_permission])).
+
+nomatch_short_list_test() ->
+ ?assert(false == match([some, permission], [some])).
+
+nomatch_long_list_test() ->
+ ?assert(false == match([some, permission], [some, permission, yap])).
+
+match_tripoint_test() ->
+ ?assert(true == match([some, permission], ['...'])).
+
+match_tripoint_at_end_test() ->
+ ?assert(true == match([some, permission], [some, permission, '...'])).
+
+
+match_tripoint_start_test() ->
+ ?assert(true == match([some, cool, permission], ['...', permission])).
+
+match_tripoint_end_test() ->
+ ?assert(true == match([some, cool, permission], [some, '...'])).
+
+match_tripoint_middle_test() ->
+ ?assert(true == match([some, really, cool, permission], [some, '...', permission])).
+
+match_underscore_test() ->
+ ?assert(true == match([some], ['_'])).
+
+match_underscore_start_test() ->
+ ?assert(true == match([some, permission], ['_', permission])).
+
+match_underscore_end_test() ->
+ ?assert(true == match([some, permission], [some, '_'])).
+
+match_underscore_middle_test() ->
+ ?assert(true == match([some, cool, permission], [some, '_', permission])).
+
+nomatch_underscore_double_test() ->
+ ?assert(false == match([some, really, cool, permission], [some, '_', permission])).
+
+-endif.
View
234 apps/snarl/src/snarl_user_read_fsm.erl
@@ -0,0 +1,234 @@
+%% @doc The coordinator for stat get operations. The key here is to
+%% generate the preflist just like in wrtie_fsm and then query each
+%% replica and wait until a quorum is met.
+-module(snarl_user_read_fsm).
+-behavior(gen_fsm).
+-include("snarl.hrl").
+
+%% API
+-export([start_link/5, get/1, list/0, auth/2]).
+
+
+-export([reconcile/1, different/1, needs_repair/2, repair/3, unique/1]).
+
+%% Callbacks
+-export([init/1, code_change/4, handle_event/3, handle_info/3,
+ handle_sync_event/4, terminate/3]).
+
+%% States
+-export([prepare/2, execute/2, waiting/2, wait_for_n/2, finalize/2]).
+
+-record(state, {req_id,
+ from,
+ user,
+ op,
+ r=?R,
+ preflist,
+ num_r=0,
+ timeout=?DEFAULT_TIMEOUT,
+ val,
+ replies=[]}).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+start_link(Op, ReqID, From, User, Val) ->
+ gen_fsm:start_link(?MODULE, [Op, ReqID, From, User, Val], []).
+
+auth(User, Passwd) ->
+ ReqID = mk_reqid(),
+ snarl_user_read_fsm_sup:start_read_fsm([auth, ReqID, self(), User, Passwd]),
+ {ok, ReqID}.
+
+get(User) ->
+ ReqID = mk_reqid(),
+ snarl_user_read_fsm_sup:start_read_fsm([get, ReqID, self(), User, undefined]),
+ {ok, ReqID}.
+
+list() ->
+ ReqID = mk_reqid(),
+ snarl_user_read_fsm_sup:start_read_fsm([list, ReqID, self(), undefined, undefined]),
+ {ok, ReqID}.
+
+%%%===================================================================
+%%% States
+%%%===================================================================
+
+%% Intiailize state data.
+init([Op, ReqId, From, User, Val]) ->
+ ?PRINT({init, [Op, ReqId, From, User, Val]}),
+ SD = #state{req_id=ReqId,
+ from=From,
+ op=Op,
+ val=Val,
+ user=User},
+ {ok, prepare, SD, 0};
+
+init([Op, ReqId, From, User]) ->
+ ?PRINT({init, [Op, ReqId, From, User]}),
+ SD = #state{req_id=ReqId,
+ from=From,
+ op=Op,
+ user=User},
+ {ok, prepare, SD, 0};
+
+init([Op, ReqId, From]) ->
+ ?PRINT({init, [Op, ReqId, From]}),
+ SD = #state{req_id=ReqId,
+ from=From,
+ op=Op},
+ {ok, prepare, SD, 0}.
+
+%% @doc Calculate the Preflist.
+prepare(timeout, SD0=#state{user=User}) ->
+ ?PRINT({prepare, User}),
+ DocIdx = riak_core_util:chash_key({<<"user">>, term_to_binary(User)}),
+ Prelist = riak_core_apl:get_apl(DocIdx, ?N, snarl_user),
+ SD = SD0#state{preflist=Prelist},
+ {next_state, execute, SD, 0}.
+
+%% @doc Execute the get reqs.
+execute(timeout, SD0=#state{req_id=ReqId,
+ user=User,
+ op=Op,
+ val=Val,
+ preflist=Prelist}) ->
+ ?PRINT({execute, User, Val}),
+ case User of
+ undefined ->
+ snarl_user_vnode:Op(Prelist, ReqId);
+ _ ->
+ case Val of
+ undefined ->
+ snarl_user_vnode:Op(Prelist, ReqId, User);
+ _ ->
+
+ snarl_user_vnode:Op(Prelist, ReqId, User, Val)
+ end
+ end,
+ {next_state, waiting, SD0}.
+
+%% @doc Wait for R replies and then respond to From (original client
+%% that called `snarl:get/2').
+%% TODO: read repair...or another blog post?
+
+waiting({ok, ReqID, IdxNode, Obj},
+ SD0=#state{from=From, num_r=NumR0, replies=Replies0,
+ r=R, timeout=Timeout}) ->
+ NumR = NumR0 + 1,
+ Replies = [{IdxNode, Obj}|Replies0],
+ SD = SD0#state{num_r=NumR,replies=Replies},
+
+ if
+ NumR =:= R ->
+ case merge(Replies) of
+ not_found ->
+ From ! {ReqID, ok, not_found};
+ Merged ->
+ Reply = snarl_obj:val(Merged),
+ From ! {ReqID, ok, statebox:value(Reply)},
+ if NumR =:= ?N -> {next_state, finalize, SD, 0};
+ true -> {next_state, wait_for_n, SD, Timeout}
+ end
+ end;
+ true -> {next_state, waiting, SD}
+ end.
+
+wait_for_n({ok, _ReqID, IdxNode, Obj},
+ SD0=#state{num_r=?N - 1, replies=Replies0}) ->
+ Replies = [{IdxNode, Obj}|Replies0],
+ {next_state, finalize, SD0#state{num_r=?N, replies=Replies}, 0};
+
+wait_for_n({ok, _ReqID, IdxNode, Obj},
+ SD0=#state{num_r=NumR0, replies=Replies0, timeout=Timeout}) ->
+ NumR = NumR0 + 1,
+ Replies = [{IdxNode, Obj}|Replies0],
+ {next_state, wait_for_n, SD0#state{num_r=NumR, replies=Replies}, Timeout};
+
+%% TODO partial repair?
+wait_for_n(timeout, SD) ->
+ {stop, timeout, SD}.
+
+finalize(timeout, SD=#state{replies=Replies, user=User}) ->
+ MObj = merge(Replies),
+ case needs_repair(MObj, Replies) of
+ true ->
+ repair(User, MObj, Replies),
+ {stop, normal, SD};
+ false ->
+ {stop, normal, SD}
+ end.
+
+
+
+handle_info(_Info, _StateName, StateData) ->
+ {stop,badmsg,StateData}.
+
+handle_event(_Event, _StateName, StateData) ->
+ {stop,badmsg,StateData}.
+
+handle_sync_event(_Event, _From, _StateName, StateData) ->
+ {stop,badmsg,StateData}.
+
+code_change(_OldVsn, StateName, State, _Extra) -> {ok, StateName, State}.
+
+terminate(_Reason, _SN, _SD) ->
+ ok.
+
+%%%===================================================================
+%%% Internal Functions
+%%%===================================================================
+
+mk_reqid() -> erlang:phash2(erlang:now()).
+
+
+%% @pure
+%%
+%% @doc Given a list of `Replies' return the merged value.
+-spec merge([vnode_reply()]) -> snarl_obj() | not_found.
+merge(Replies) ->
+ Objs = [Obj || {_,Obj} <- Replies],
+ snarl_obj:merge(snarl_user_read_fsm, Objs).
+
+%% @pure
+%%
+%% @doc Reconcile conflicts among conflicting values.
+-spec reconcile([A :: statebox:statebox()]) -> A :: statebox:statebox().
+
+reconcile(Vals) ->
+ statebox:merge(Vals).
+
+
+%% @pure
+%%
+%% @doc Given the merged object `MObj' and a list of `Replies'
+%% determine if repair is needed.
+-spec needs_repair(any(), [vnode_reply()]) -> boolean().
+needs_repair(MObj, Replies) ->
+ Objs = [Obj || {_,Obj} <- Replies],
+ lists:any(different(MObj), Objs).
+
+%% @pure
+different(A) -> fun(B) -> not snarl_obj:equal(A,B) end.
+
+%% @impure
+%%
+%% @doc Repair any vnodes that do not have the correct object.
+-spec repair(string(), snarl_obj(), [vnode_reply()]) -> io.
+repair(_, _, []) -> io;
+
+repair(StatName, MObj, [{IdxNode,Obj}|T]) ->
+ case snarl_obj:equal(MObj, Obj) of
+ true -> repair(StatName, MObj, T);
+ false ->
+ snarl_user_vnode:repair(IdxNode, StatName, MObj),
+ repair(StatName, MObj, T)
+ end.
+
+%% pure
+%%
+%% @doc Given a list return the set of unique values.
+-spec unique([A::any()]) -> [A::any()].
+unique(L) ->
+ sets:to_list(sets:from_list(L)).
View
20 apps/snarl/src/snarl_user_read_fsm_sup.erl
@@ -0,0 +1,20 @@
+%% @doc Supervise the rts_write FSM.
+-module(snarl_user_read_fsm_sup).
+-behavior(supervisor).
+
+-export([start_read_fsm/1,
+ start_link/0]).
+
+-export([init/1]).
+
+start_read_fsm(Args) ->
+ supervisor:start_child(?MODULE, Args).
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+init([]) ->
+ ReadFsm = {undefined,
+ {snarl_user_read_fsm, start_link, []},
+ temporary, 5000, worker, [snarl_user_read_fsm]},
+ {ok, {{simple_one_for_one, 10, 10}, [ReadFsm]}}.
View
49 apps/snarl/src/snarl_user_state.erl
@@ -0,0 +1,49 @@
+%%% @author Heinz Nikolaus Gies <heinz@licenser.net>
+%%% @copyright (C) 2012, Heinz Nikolaus Gies
+%%% @doc
+%%%
+%%% @end
+%%% Created : 23 Aug 2012 by Heinz Nikolaus Gies <heinz@licenser.net>
+
+-module(snarl_user_state).
+
+-include("snarl.hrl").
+
+-export([
+ new/0,
+ name/2,
+ passwd/2,
+ grant/2,
+ revoke/2,
+ join/2,
+ leave/2
+ ]).
+
+
+new() ->
+ #user{}.
+
+name(Name, User) ->
+ User#user{name = Name}.
+
+passwd(Passwd, User) ->
+ User#user{passwd = crypto:sha([User#user.name, Passwd])}.
+
+grant(Permission, User) ->
+ User#user{permissions = ordsets:add_element(Permission, User#user.permissions)}.
+
+revoke(Permission, User) ->
+ User#user{permissions = ordsets:del_element(Permission, User#user.permissions)}.
+
+join(Group, User) ->
+ User#user{groups = ordsets:add_element(Group, User#user.groups)}.
+
+leave(Group, User) ->
+ User#user{groups = ordsets:del_element(Group, User#user.groups)}.
+
+
+
+
+
+
+
View
272 apps/snarl/src/snarl_user_vnode.erl
@@ -0,0 +1,272 @@
+-module(snarl_user_vnode).
+-behaviour(riak_core_vnode).
+-include("snarl.hrl").
+-include_lib("riak_core/include/riak_core_vnode.hrl").
+
+
+-export([start_vnode/1,
+ init/1,
+ terminate/2,
+ handle_command/3,
+ is_empty/1,
+ delete/1,
+ handle_handoff_command/3,
+ handoff_starting/2,
+ handoff_cancelled/1,
+ handoff_finished/2,
+ handle_handoff_data/2,
+ encode_handoff_item/2,
+ handle_coverage/4,
+ handle_exit/3]).
+
+% Reads
+-export([list/2,
+ get/3]).
+
+% Writes
+-export([add/3,
+ delete/3,
+ passwd/4,
+ join/4,
+ leave/4,
+ grant/4,
+ repair/3,
+ revoke/4]).
+
+-record(state, {partition, users=[], dbref, node}).
+
+-define(MASTER, snarl_user_vnode_master).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+start_vnode(I) ->
+ riak_core_vnode_master:get_vnode_pid(I, ?MODULE).
+
+
+repair(IdxNode, User, Obj) ->
+ riak_core_vnode_master:command(IdxNode,
+ {repair, undefined, User, Obj},
+ ignore,
+ ?MASTER).
+
+%%%===================================================================
+%%% API - reads
+%%%===================================================================
+
+get(Preflist, ReqID, User) ->
+ riak_core_vnode_master:command(Preflist,
+ {get, ReqID, User},
+ {fsm, undefined, self()},
+ ?MASTER).
+
+list(Preflist, ReqID) ->
+ riak_core_vnode_master:command(Preflist,
+ {list, ReqID},
+ {fsm, undefined, self()},
+ ?MASTER).
+
+
+%%%===================================================================
+%%% API - writes
+%%%===================================================================
+
+add(Preflist, ReqID, User) ->
+ riak_core_vnode_master:command(Preflist,
+ {add, ReqID, User},
+ {fsm, undefined, self()},
+ ?MASTER).
+
+delete(Preflist, ReqID, User) ->
+ riak_core_vnode_master:command(Preflist,
+ {delete, ReqID, User},
+ {fsm, undefined, self()},
+ ?MASTER).
+
+passwd(Preflist, ReqID, User, Val) ->
+ riak_core_vnode_master:command(Preflist,
+ {passwd, ReqID, User, Val},
+ {fsm, undefined, self()},
+ ?MASTER).
+
+
+join(Preflist, ReqID, User, Val) ->
+ riak_core_vnode_master:command(Preflist,
+ {join, ReqID, User, Val},
+ {fsm, undefined, self()},
+ ?MASTER).
+
+leave(Preflist, ReqID, User, Val) ->
+ riak_core_vnode_master:command(Preflist,
+ {leave, ReqID, User, Val},
+ {fsm, undefined, self()},
+ ?MASTER).
+
+grant(Preflist, ReqID, User, Val) ->
+ riak_core_vnode_master:command(Preflist,
+ {grant, ReqID, User, Val},