mirror of
https://github.com/valitydev/dominant.git
synced 2024-11-06 02:25:17 +00:00
[HG-27] new concept (#2)
* new concept * add tests * refactor things * Add dmt_storage_mgun * update damsel * remove dmt_server * update damsel * Migrate to jenkins and make tests api-involved * add dmt_poller * drop wercker * pr fixes * remove obsolete case * drop woody dependencie for dmt * Revert "drop woody dependencie for dmt" This reverts commit 99db9bc77904ab77237cd9356cf04c82b6e77bd1.
This commit is contained in:
parent
11cb1934ca
commit
ba644bcce2
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
# general
|
||||
log
|
||||
/_build/
|
||||
*~
|
||||
erl_crash.dump
|
||||
.tags*
|
||||
*.sublime-workspace
|
||||
.DS_Store
|
||||
|
||||
# wercker
|
||||
/_builds/
|
||||
/_cache/
|
||||
/_projects/
|
||||
/_steps/
|
||||
/_temp/
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "apps/dmt_proto/damsel"]
|
||||
path = apps/dmt_proto/damsel
|
||||
url = git@github.com:rbkmoney/damsel.git
|
6
Dockerfile
Normal file
6
Dockerfile
Normal file
@ -0,0 +1,6 @@
|
||||
FROM rbkmoney/service_erlang:latest
|
||||
MAINTAINER Igor Savchuk <i.savchuk@rbkmoney.com>
|
||||
COPY _build/prod/rel/dmt /opt/dominant
|
||||
CMD ["/opt/dominant/bin/dmt", "foreground"]
|
||||
LABEL service_version="semver"
|
||||
WORKDIR /opt/dominant
|
34
Jenkinsfile
vendored
Normal file
34
Jenkinsfile
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
#!groovy
|
||||
|
||||
// Args:
|
||||
// GitHub repo name
|
||||
// Jenkins agent label
|
||||
// Tracing artifacts to be stored alongside build logs
|
||||
// Optional: artifacts to cache between the builds
|
||||
pipeline("dmt", 'docker-host', "_build/") {
|
||||
|
||||
// ToDo: Uncomment the stage as soon as Elvis is in the build image!
|
||||
// runStage('lint') {
|
||||
// sh 'make w_container_lint'
|
||||
// }
|
||||
|
||||
runStage('compile') {
|
||||
sh 'make w_container_compile'
|
||||
}
|
||||
|
||||
runStage('xref') {
|
||||
sh 'make w_container_xref'
|
||||
}
|
||||
|
||||
runStage('test_api') {
|
||||
sh "make w_container_test_api"
|
||||
}
|
||||
|
||||
runStage('test_client') {
|
||||
sh "make w_container_test_client"
|
||||
}
|
||||
|
||||
runStage('dialyze') {
|
||||
sh 'make w_container_dialyze'
|
||||
}
|
||||
}
|
83
Makefile
Normal file
83
Makefile
Normal file
@ -0,0 +1,83 @@
|
||||
REBAR := $(shell which rebar3 2>/dev/null || which ./rebar3)
|
||||
SUBMODULES = apps/dmt_proto/damsel
|
||||
SUBTARGETS = $(patsubst %,%/.git,$(SUBMODULES))
|
||||
|
||||
ORG_NAME := rbkmoney
|
||||
BASE_IMAGE := "$(ORG_NAME)/build:latest"
|
||||
RELNAME := dominant
|
||||
|
||||
TAG = latest
|
||||
IMAGE_NAME = "$(ORG_NAME)/$(RELNAME):$(TAG)"
|
||||
|
||||
CALL_ANYWHERE := submodules rebar-update compile xref lint dialyze start devrel release clean distclean
|
||||
|
||||
CALL_W_CONTAINER := $(CALL_ANYWHERE) test_api
|
||||
|
||||
include utils.mk
|
||||
|
||||
.PHONY: $(CALL_W_CONTAINER) all containerize push $(UTIL_TARGETS)
|
||||
|
||||
# CALL_ANYWHERE
|
||||
$(SUBTARGETS): %/.git: %
|
||||
git submodule update --init $<
|
||||
touch $@
|
||||
|
||||
submodules: $(SUBTARGETS)
|
||||
|
||||
rebar-update:
|
||||
$(REBAR) update
|
||||
|
||||
compile: submodules rebar-update
|
||||
$(REBAR) compile
|
||||
|
||||
xref: submodules
|
||||
$(REBAR) xref
|
||||
|
||||
lint: compile
|
||||
elvis rock
|
||||
|
||||
dialyze:
|
||||
$(REBAR) dialyzer
|
||||
|
||||
start: submodules
|
||||
$(REBAR) run
|
||||
|
||||
devrel: submodules
|
||||
$(REBAR) release
|
||||
|
||||
release: distclean
|
||||
$(REBAR) as prod release
|
||||
|
||||
clean:
|
||||
$(REBAR) clean
|
||||
|
||||
distclean:
|
||||
$(REBAR) clean -a
|
||||
rm -rfv _build _builds _cache _steps _temp
|
||||
|
||||
# CALL_W_CONTAINER
|
||||
test_api: submodules
|
||||
$(REBAR) ct --suite apps/dmt_api/test/dmt_api_tests_SUITE.erl
|
||||
|
||||
test_client: submodules
|
||||
$(REBAR) ct --suite apps/dmt_api/test/dmt_client_tests_SUITE.erl
|
||||
|
||||
w_container_test_client: submodules
|
||||
{ \
|
||||
$(DOCKER_COMPOSE) up -d ; \
|
||||
$(DOCKER_COMPOSE) exec -T -d dominant make start ; \
|
||||
$(DOCKER_COMPOSE) exec -T dmt_client make test_client ; \
|
||||
res=$$? ; \
|
||||
$(DOCKER_COMPOSE) down ; \
|
||||
exit $$res ; \
|
||||
}
|
||||
|
||||
|
||||
# OTHER
|
||||
all: compile
|
||||
|
||||
containerize: w_container_release
|
||||
$(DOCKER) build --force-rm --tag $(IMAGE_NAME) .
|
||||
|
||||
push: containerize
|
||||
$(DOCKER) push "$(IMAGE_NAME)"
|
@ -14,17 +14,12 @@
|
||||
|
||||
> _Хозяйке на заметку._ При этом используется стандартный Erlang релиз, собранный при помощи [relx][3] в режиме разработчика.
|
||||
|
||||
Рекомендуется вести разработку и сборку проекта в рамках локальной виртуальной среды, предоставляемой [wercker][1]. Настоятельно рекомендуется прогоны тестовых сценариев проводить только в этой среде.
|
||||
|
||||
$ wercker dev
|
||||
|
||||
> _Хозяйке на заметку._ В зависимости от вашего окружения и операционной системы вам может понадобиться [Docker Machine][4].
|
||||
|
||||
## Документация
|
||||
|
||||
Дальнейшую документацию можно почерпнуть, пройдясь по ссылкам в [соответствующем документе](doc/index.md).
|
||||
|
||||
[1]: http://devcenter.wercker.com/learn/basics/the-wercker-cli.html
|
||||
[2]: http://erlang.org/doc/man/shell.html
|
||||
[3]: https://github.com/erlware/relx
|
||||
[4]: https://docs.docker.com/machine/install-machine/
|
||||
|
1
apps/dmt/include/dmt_mg.hrl
Normal file
1
apps/dmt/include/dmt_mg.hrl
Normal file
@ -0,0 +1 @@
|
||||
-define(MG_TAG, <<"dmt_storage">>).
|
23
apps/dmt/src/dmt.app.src
Normal file
23
apps/dmt/src/dmt.app.src
Normal file
@ -0,0 +1,23 @@
|
||||
{application, dmt, [
|
||||
{description, "Domain config service"},
|
||||
{vsn, "0"},
|
||||
{registered, []},
|
||||
{mod, {dmt, []}},
|
||||
{applications, [
|
||||
kernel,
|
||||
stdlib,
|
||||
lager,
|
||||
genlib,
|
||||
woody,
|
||||
dmt_proto
|
||||
]},
|
||||
{env, [
|
||||
{mgun_automaton_url, "http://machinegun:8022/v1/automaton_service"}
|
||||
]},
|
||||
{modules, []},
|
||||
{maintainers, [
|
||||
"Andrey Mayorov <a.mayorov@rbkmoney.com>"
|
||||
]},
|
||||
{licenses, []},
|
||||
{links, ["https://github.com/rbkmoney/dominant"]}
|
||||
]}.
|
110
apps/dmt/src/dmt.erl
Normal file
110
apps/dmt/src/dmt.erl
Normal file
@ -0,0 +1,110 @@
|
||||
%%% @doc Public API, supervisor and application startup.
|
||||
%%% @end
|
||||
|
||||
-module(dmt).
|
||||
-behaviour(supervisor).
|
||||
-behaviour(application).
|
||||
|
||||
%% API
|
||||
-export([checkout/1]).
|
||||
-export([checkout_object/2]).
|
||||
-export([pull/1]).
|
||||
-export([commit/2]).
|
||||
-export([validate_commit/3]).
|
||||
|
||||
%% Supervisor callbacks
|
||||
-export([init/1]).
|
||||
|
||||
%% Application callbacks
|
||||
-export([start/2]).
|
||||
-export([stop/1]).
|
||||
|
||||
%% Type shortcuts
|
||||
-export_type([version/0]).
|
||||
-export_type([head/0]).
|
||||
-export_type([ref/0]).
|
||||
-export_type([snapshot/0]).
|
||||
-export_type([commit/0]).
|
||||
-export_type([operation/0]).
|
||||
-export_type([history/0]).
|
||||
-export_type([object_ref/0]).
|
||||
-export_type([domain/0]).
|
||||
-export_type([domain_object/0]).
|
||||
|
||||
-type version() :: dmt_domain_config_thrift:'Version'().
|
||||
-type head() :: dmt_domain_config_thrift:'Head'().
|
||||
-type ref() :: dmt_domain_config_thrift:'Reference'().
|
||||
-type snapshot() :: dmt_domain_config_thrift:'Snapshot'().
|
||||
-type commit() :: dmt_domain_config_thrift:'Commit'().
|
||||
-type operation() :: dmt_domain_config_thrift:'Operation'().
|
||||
-type history() :: dmt_domain_config_thrift:'History'().
|
||||
-type object_ref() :: dmt_domain_thrift:'Reference'().
|
||||
-type domain() :: dmt_domain_thrift:'Domain'().
|
||||
-type domain_object() :: dmt_domain_thrift:'DomainObject'().
|
||||
|
||||
-include_lib("dmt_proto/include/dmt_domain_config_thrift.hrl").
|
||||
|
||||
%% API
|
||||
|
||||
-spec checkout(ref()) -> snapshot().
|
||||
checkout(Reference) ->
|
||||
dmt_cache:checkout(Reference).
|
||||
|
||||
-spec checkout_object(ref(), object_ref()) ->
|
||||
dmt_domain_config_thrift:'VersionedObject'().
|
||||
checkout_object(Reference, ObjectReference) ->
|
||||
#'Snapshot'{version = Version, domain = Domain} = checkout(Reference),
|
||||
Object = dmt_domain:get_object(ObjectReference, Domain),
|
||||
#'VersionedObject'{version = Version, object = Object}.
|
||||
|
||||
-spec pull(version()) -> history().
|
||||
pull(Version) ->
|
||||
dmt_mg:get_history(Version).
|
||||
|
||||
-spec commit(version(), commit()) -> version().
|
||||
commit(Version, Commit) ->
|
||||
ok = dmt_mg:commit(Version, Commit),
|
||||
Version + 1.
|
||||
|
||||
-spec validate_commit(version(), commit(), history()) -> ok.
|
||||
validate_commit(Version, _Commit, History) ->
|
||||
%%TODO: actually validate commit
|
||||
LastVersion = case map_size(History) of
|
||||
0 ->
|
||||
0;
|
||||
_Size ->
|
||||
lists:max(maps:keys(History))
|
||||
end,
|
||||
case Version =:= LastVersion of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
throw(bad_version)
|
||||
end.
|
||||
|
||||
%% Supervisor callbacks
|
||||
|
||||
-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
|
||||
|
||||
init([]) ->
|
||||
Cache = #{id => dmt_cache, start => {dmt_cache, start_link, []}, restart => permanent},
|
||||
Poller = #{id => dmt_poller, start => {dmt_poller, start_link, []}, restart => permanent},
|
||||
Children = case application:get_env(dmt, slave_mode) of
|
||||
{ok, true} ->
|
||||
[Cache, Poller];
|
||||
_ ->
|
||||
[Cache]
|
||||
end,
|
||||
{ok, {#{strategy => rest_for_one, intensity => 10, period => 60}, Children}}.
|
||||
|
||||
%% Application callbacks
|
||||
|
||||
-spec start(normal, any()) -> {ok, pid()} | {error, any()}.
|
||||
|
||||
start(_StartType, _StartArgs) ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
|
||||
-spec stop(any()) -> ok.
|
||||
|
||||
stop(_State) ->
|
||||
ok.
|
148
apps/dmt/src/dmt_cache.erl
Normal file
148
apps/dmt/src/dmt_cache.erl
Normal file
@ -0,0 +1,148 @@
|
||||
-module(dmt_cache).
|
||||
-behaviour(gen_server).
|
||||
|
||||
%%
|
||||
|
||||
-export([start_link/0]).
|
||||
|
||||
-export([checkout_head/0]).
|
||||
-export([checkout/1]).
|
||||
-export([cache/1]).
|
||||
-export([cache_snapshot/1]).
|
||||
-export([commit/1]).
|
||||
|
||||
%%
|
||||
|
||||
-export([init/1]).
|
||||
-export([handle_call/3]).
|
||||
-export([handle_cast/2]).
|
||||
-export([handle_info/2]).
|
||||
-export([terminate/2]).
|
||||
-export([code_change/3]).
|
||||
|
||||
-define(TABLE, ?MODULE).
|
||||
-define(SERVER, ?MODULE).
|
||||
|
||||
-include_lib("dmt_proto/include/dmt_domain_config_thrift.hrl").
|
||||
-include_lib("stdlib/include/ms_transform.hrl").
|
||||
|
||||
%%
|
||||
|
||||
-spec start_link() -> {ok, pid()} | {error, term()}. % FIXME
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
-spec checkout_head() -> dmt:snapshot().
|
||||
checkout_head() ->
|
||||
checkout({head, #'Head'{}}).
|
||||
|
||||
-spec checkout(dmt:ref()) -> dmt:snapshot().
|
||||
checkout({head, #'Head'{}}) ->
|
||||
checkout({version, head()});
|
||||
checkout({version, Version}) ->
|
||||
case ets:lookup(?TABLE, Version) of
|
||||
[Snapshot] ->
|
||||
Snapshot;
|
||||
[] ->
|
||||
gen_server:call(?SERVER, {cache, Version})
|
||||
end.
|
||||
|
||||
-spec cache(dmt:version()) -> dmt:snapshot().
|
||||
cache(Version) ->
|
||||
gen_server:call(?SERVER, {cache, Version}).
|
||||
|
||||
-spec cache_snapshot(dmt:snapshot()) -> ok.
|
||||
cache_snapshot(Snapshot) ->
|
||||
gen_server:call(?SERVER, {cache_snapshot, Snapshot}).
|
||||
|
||||
-spec commit(dmt:commit()) -> ok.
|
||||
commit(Commit) ->
|
||||
gen_server:call(?SERVER, {commit, Commit}).
|
||||
|
||||
%%
|
||||
|
||||
-record(state, {
|
||||
}).
|
||||
|
||||
-type state() :: #state{}.
|
||||
|
||||
-spec init(_) -> {ok, state()}.
|
||||
|
||||
init(_) ->
|
||||
EtsOpts = [
|
||||
named_table,
|
||||
ordered_set,
|
||||
protected,
|
||||
{read_concurrency, true},
|
||||
{keypos, #'Snapshot'.version}
|
||||
],
|
||||
?TABLE = ets:new(?TABLE, EtsOpts),
|
||||
{ok, #state{}}.
|
||||
|
||||
-spec handle_call(term(), {pid(), term()}, state()) -> {reply, term(), state()}.
|
||||
handle_call({cache, Version}, _From, State) ->
|
||||
Closest = closest_snapshot(Version),
|
||||
Snapshot = dmt_history:travel(Version, dmt_mg:get_history(), Closest),
|
||||
true = ets:insert(?TABLE, Snapshot),
|
||||
{reply, Snapshot, State};
|
||||
handle_call({cache_snapshot, Snapshot}, _From, State) ->
|
||||
true = ets:insert(?TABLE, Snapshot),
|
||||
{reply, ok, State};
|
||||
handle_call({commit, #'Commit'{ops = Ops}}, _From, State) ->
|
||||
#'Snapshot'{version = Version, domain = Domain} = checkout({head, #'Head'{}}),
|
||||
NewSnapshot = #'Snapshot'{
|
||||
version = Version + 1,
|
||||
domain = dmt_domain:apply_operations(Ops, Domain)
|
||||
},
|
||||
true = ets:insert(?TABLE, NewSnapshot),
|
||||
{reply, NewSnapshot, State};
|
||||
handle_call(_Msg, _From, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
-spec handle_cast(term(), state()) -> {noreply, state()}.
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
-spec handle_info(term(), state()) -> {noreply, state()}.
|
||||
handle_info(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
-spec terminate(term(), state()) -> ok.
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
-spec code_change(term(), state(), term()) -> {error, noimpl}.
|
||||
code_change(_OldVsn, _State, _Extra) ->
|
||||
{error, noimpl}.
|
||||
|
||||
%% internal
|
||||
|
||||
-spec head() -> dmt:version().
|
||||
head() ->
|
||||
case ets:last(?TABLE) of
|
||||
'$end_of_table' ->
|
||||
% #'Snapshot'{version = Version} = dmt_history:head(dmt_mg:get_history()),
|
||||
% Version;
|
||||
0;
|
||||
Version ->
|
||||
Version
|
||||
end.
|
||||
|
||||
-spec closest_snapshot(dmt:version()) -> dmt:snapshot().
|
||||
closest_snapshot(Version) ->
|
||||
CachedVersions = ets:select(?TABLE, ets:fun2ms(fun (#'Snapshot'{version = V}) -> V end)),
|
||||
Closest = lists:foldl(fun (V, Acc) ->
|
||||
case abs(V - Version) =< abs(Acc - Version) of
|
||||
true ->
|
||||
V;
|
||||
false ->
|
||||
Acc
|
||||
end
|
||||
end, 0, CachedVersions),
|
||||
case Closest of
|
||||
0 ->
|
||||
#'Snapshot'{version = 0, domain = dmt_domain:new()};
|
||||
Closest ->
|
||||
ets:lookup(?TABLE, Closest)
|
||||
end.
|
85
apps/dmt/src/dmt_domain.erl
Normal file
85
apps/dmt/src/dmt_domain.erl
Normal file
@ -0,0 +1,85 @@
|
||||
-module(dmt_domain).
|
||||
-include_lib("dmt_proto/include/dmt_domain_config_thrift.hrl").
|
||||
|
||||
%%
|
||||
|
||||
-export([new/0]).
|
||||
-export([get_object/2]).
|
||||
-export([apply_operations/2]).
|
||||
-export([revert_operations/2]).
|
||||
|
||||
%%
|
||||
|
||||
-spec new() ->
|
||||
dmt:domain().
|
||||
|
||||
new() ->
|
||||
#{}.
|
||||
|
||||
-spec get_object(dmt:object_ref(), dmt:domain()) ->
|
||||
dmt:domain_object().
|
||||
|
||||
get_object(ObjectReference, Domain) ->
|
||||
case maps:find(ObjectReference, Domain) of
|
||||
{ok, Object} ->
|
||||
Object;
|
||||
error ->
|
||||
throw(object_not_found)
|
||||
end.
|
||||
|
||||
|
||||
-spec apply_operations([dmt:operation()], dmt:domain()) -> dmt:domain().
|
||||
apply_operations([], Domain) ->
|
||||
Domain;
|
||||
apply_operations([{insert, #'InsertOp'{object = Object}} | Rest], Domain) ->
|
||||
apply_operations(Rest, insert(Object, Domain));
|
||||
apply_operations([{update, #'UpdateOp'{old_object = OldObject, new_object = NewObject}} | Rest], Domain) ->
|
||||
apply_operations(Rest, update(OldObject, NewObject, Domain));
|
||||
apply_operations([{remove, #'RemoveOp'{object = Object}} | Rest], Domain) ->
|
||||
apply_operations(Rest, delete(Object, Domain)).
|
||||
|
||||
-spec revert_operations([dmt:operation()], dmt:domain()) -> dmt:domain().
|
||||
revert_operations([], Domain) ->
|
||||
Domain;
|
||||
revert_operations([{insert, #'InsertOp'{object = Object}} | Rest], Domain) ->
|
||||
revert_operations(Rest, delete(Object, Domain));
|
||||
revert_operations([{update, #'UpdateOp'{old_object = OldObject, new_object = NewObject}} | Rest], Domain) ->
|
||||
revert_operations(Rest, update(NewObject, OldObject, Domain));
|
||||
revert_operations([{remove, #'RemoveOp'{object = Object}} | Rest], Domain) ->
|
||||
revert_operations(Rest, insert(Object, Domain)).
|
||||
|
||||
-spec insert(dmt:domain_object(), dmt:domain()) -> dmt:domain().
|
||||
insert(Object, Domain) ->
|
||||
ObjectReference = get_ref(Object),
|
||||
case maps:is_key(ObjectReference, Domain) of
|
||||
false ->
|
||||
maps:put(ObjectReference, Object, Domain);
|
||||
true ->
|
||||
throw(object_already_exists)
|
||||
end.
|
||||
|
||||
-spec update(dmt:domain_object(), dmt:domain_object(), dmt:domain()) -> dmt:domain().
|
||||
update(OldObject, NewObject, Domain) ->
|
||||
ObjectReference = get_ref(OldObject),
|
||||
ObjectReference = get_ref(NewObject),
|
||||
case maps:find(ObjectReference, Domain) of
|
||||
{ok, OldObject} ->
|
||||
maps:put(ObjectReference, NewObject, Domain);
|
||||
error ->
|
||||
throw(object_not_found)
|
||||
end.
|
||||
|
||||
-spec delete(dmt:domain_object(), dmt:domain()) -> dmt:domain().
|
||||
delete(Object, Domain) ->
|
||||
ObjectReference = get_ref(Object),
|
||||
case maps:find(ObjectReference, Domain) of
|
||||
{ok, Object} ->
|
||||
maps:remove(ObjectReference, Domain);
|
||||
error ->
|
||||
throw(object_not_found)
|
||||
end.
|
||||
|
||||
%%TODO:elaborate
|
||||
-spec get_ref(dmt:domain_object()) -> dmt:object_ref().
|
||||
get_ref({Tag, {_Type, Ref, _Data}}) ->
|
||||
{Tag, Ref}.
|
39
apps/dmt/src/dmt_history.erl
Normal file
39
apps/dmt/src/dmt_history.erl
Normal file
@ -0,0 +1,39 @@
|
||||
-module(dmt_history).
|
||||
|
||||
-export([head/1]).
|
||||
-export([head/2]).
|
||||
-export([travel/3]).
|
||||
|
||||
-include_lib("dmt_proto/include/dmt_domain_config_thrift.hrl").
|
||||
|
||||
-spec head(dmt:history()) -> dmt:snapshot().
|
||||
head(History) when map_size(History) =:= 0 ->
|
||||
#'Snapshot'{version = 0, domain = dmt_domain:new()};
|
||||
head(History) ->
|
||||
head(History, #'Snapshot'{version = 0, domain = dmt_domain:new()}).
|
||||
|
||||
-spec head(dmt:history(), dmt:snapshot()) -> dmt:snapshot().
|
||||
head(History, Snapshot) ->
|
||||
Head = lists:max(maps:keys(History)),
|
||||
travel(Head, History, Snapshot).
|
||||
|
||||
-spec travel(dmt:version(), dmt:history(), dmt:snapshot()) -> dmt:snapshot().
|
||||
travel(To, _History, #'Snapshot'{version = From} = Snapshot)
|
||||
when To =:= From ->
|
||||
Snapshot;
|
||||
travel(To, History, #'Snapshot'{version = From, domain = Domain})
|
||||
when To > From ->
|
||||
#'Commit'{ops = Ops} = maps:get(From + 1, History),
|
||||
NextSnapshot = #'Snapshot'{
|
||||
version = From + 1,
|
||||
domain = dmt_domain:apply_operations(Ops, Domain)
|
||||
},
|
||||
travel(To, History, NextSnapshot);
|
||||
travel(To, History, #'Snapshot'{version = From, domain = Domain})
|
||||
when To < From ->
|
||||
#'Commit'{ops = Ops} = maps:get(From, History),
|
||||
PreviousSnapshot = #'Snapshot'{
|
||||
version = From - 1,
|
||||
domain = dmt_domain:revert_operations(Ops, Domain)
|
||||
},
|
||||
travel(To, History, PreviousSnapshot).
|
62
apps/dmt/src/dmt_mg.erl
Normal file
62
apps/dmt/src/dmt_mg.erl
Normal file
@ -0,0 +1,62 @@
|
||||
-module(dmt_mg).
|
||||
|
||||
-export([call/2]).
|
||||
-export([start/0]).
|
||||
-export([get_commit/1]).
|
||||
-export([get_history/0]).
|
||||
-export([get_history/1]).
|
||||
-export([commit/2]).
|
||||
-export([read_history/1]).
|
||||
|
||||
-include_lib("dmt_proto/include/dmt_state_processing_thrift.hrl").
|
||||
-include_lib("dmt/include/dmt_mg.hrl").
|
||||
|
||||
-spec call(atom(), list(term())) ->
|
||||
{ok, term()} | ok | no_return().
|
||||
call(Method, Args) ->
|
||||
Request = {{dmt_state_processing_thrift, 'Automaton'}, Method, Args},
|
||||
Context = woody_client:new_context(
|
||||
woody_client:make_id(<<"dmt">>),
|
||||
dmt_api_woody_event_handler
|
||||
),
|
||||
{ok, MgunAutomatonUrl} = application:get_env(dmt, mgun_automaton_url),
|
||||
woody_client:call(Context, Request, #{url => MgunAutomatonUrl}).
|
||||
|
||||
-spec start() -> ok.
|
||||
start() ->
|
||||
{{ok, {_, _}}, _} = call(start, {'Args', <<>>}),
|
||||
ok.
|
||||
|
||||
-spec get_commit(dmt:version()) -> dmt:commit().
|
||||
get_commit(Id) ->
|
||||
#{Id := Commit} = get_history(),
|
||||
Commit.
|
||||
|
||||
%% TODO: add range requests after they are fixed in mg
|
||||
-spec get_history() -> dmt:history().
|
||||
get_history() ->
|
||||
get_history(undefined).
|
||||
|
||||
-spec get_history(dmt:version() | undefined) -> dmt:history().
|
||||
get_history(After) ->
|
||||
{{ok, History}, _Context} = call(getHistory, [{tag, ?MG_TAG}, #'HistoryRange'{'after' = After}]),
|
||||
read_history(History).
|
||||
|
||||
-spec commit(dmt:version(), dmt:commit()) -> ok.
|
||||
commit(Version, Commit) ->
|
||||
Call = <<"commit", (term_to_binary({Version, Commit}))/binary>>,
|
||||
{{ok, <<"ok">>}, _Context} = call(call, [{tag, ?MG_TAG}, Call]),
|
||||
ok.
|
||||
|
||||
%% utils
|
||||
|
||||
-spec read_history([dmt_state_processing_thrift:'Event'()]) -> dmt:history().
|
||||
read_history(Events) ->
|
||||
read_history(Events, #{}).
|
||||
|
||||
-spec read_history([dmt_state_processing_thrift:'Event'()], dmt:history()) ->
|
||||
dmt:history().
|
||||
read_history([], History) ->
|
||||
History;
|
||||
read_history([#'Event'{id = Id, event_payload = BinaryPayload} | Rest], History) ->
|
||||
read_history(Rest, History#{Id => binary_to_term(BinaryPayload)}).
|
74
apps/dmt/src/dmt_poller.erl
Normal file
74
apps/dmt/src/dmt_poller.erl
Normal file
@ -0,0 +1,74 @@
|
||||
-module(dmt_poller).
|
||||
-behaviour(gen_server).
|
||||
|
||||
-export([start_link/0]).
|
||||
-export([poll/0]).
|
||||
|
||||
-export([init/1]).
|
||||
-export([handle_call/3]).
|
||||
-export([handle_cast/2]).
|
||||
-export([handle_info/2]).
|
||||
-export([terminate/2]).
|
||||
-export([code_change/3]).
|
||||
|
||||
-define(SERVER, ?MODULE).
|
||||
-define(INTERVAL, 5000).
|
||||
|
||||
-include_lib("dmt_proto/include/dmt_domain_config_thrift.hrl").
|
||||
|
||||
-spec start_link() -> {ok, pid()} | {error, term()}.
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||
|
||||
-spec poll() -> ok.
|
||||
poll() ->
|
||||
gen_server:call(?SERVER, poll).
|
||||
|
||||
-record(state, {
|
||||
timer :: reference(),
|
||||
last_version = 0 :: dmt:version()
|
||||
}).
|
||||
|
||||
-type state() :: #state{}.
|
||||
|
||||
-spec init(_) -> {ok, state()}.
|
||||
|
||||
init(_) ->
|
||||
Timer = erlang:send_after(?INTERVAL, self(), poll),
|
||||
{ok, #state{timer = Timer}}.
|
||||
|
||||
-spec handle_call(term(), {pid(), term()}, state()) -> {reply, term(), state()}.
|
||||
handle_call(poll, _From, #state{last_version = LastVersion, timer = Timer} = State) ->
|
||||
_ = erlang:cancel_timer(Timer),
|
||||
NewLastVersion = pull(LastVersion),
|
||||
NewTimer = erlang:send_after(?INTERVAL, self(), poll),
|
||||
{reply, ok, State#state{timer = NewTimer, last_version = NewLastVersion}}.
|
||||
|
||||
-spec handle_cast(term(), state()) -> {noreply, state()}.
|
||||
handle_cast(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
-spec handle_info(term(), state()) -> {noreply, state()}.
|
||||
handle_info(timer, #state{last_version = LastVersion} = State) ->
|
||||
NewLastVersion = pull(LastVersion),
|
||||
Timer = erlang:send_after(?INTERVAL, self(), poll),
|
||||
{noreply, State#state{last_version = NewLastVersion, timer = Timer}};
|
||||
handle_info(_Msg, State) ->
|
||||
{noreply, State}.
|
||||
|
||||
-spec terminate(term(), state()) -> ok.
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
-spec code_change(term(), state(), term()) -> {error, noimpl}.
|
||||
code_change(_OldVsn, _State, _Extra) ->
|
||||
{error, noimpl}.
|
||||
|
||||
%% Internal
|
||||
-spec pull(dmt:version()) -> dmt:version().
|
||||
pull(LastVersion) ->
|
||||
FreshHistory = dmt_api_client:pull(LastVersion),
|
||||
OldHead = dmt_cache:checkout_head(),
|
||||
#'Snapshot'{version = NewLastVersion} = NewHead = dmt_history:head(FreshHistory, OldHead),
|
||||
ok = dmt_cache:cache_snapshot(NewHead),
|
||||
NewLastVersion.
|
3
apps/dmt_api/rebar.config
Normal file
3
apps/dmt_api/rebar.config
Normal file
@ -0,0 +1,3 @@
|
||||
{erl_opts, [
|
||||
{parse_transform, lager_transform}
|
||||
]}.
|
14
apps/dmt_api/src/dmt_api.app.src
Normal file
14
apps/dmt_api/src/dmt_api.app.src
Normal file
@ -0,0 +1,14 @@
|
||||
{application, dmt_api, [
|
||||
{description, "Domain config service interfaces"},
|
||||
{vsn, "0"},
|
||||
{registered, []},
|
||||
{applications, [
|
||||
kernel,
|
||||
stdlib,
|
||||
lager,
|
||||
dmt,
|
||||
woody
|
||||
]},
|
||||
{mod, {dmt_api, []}},
|
||||
{env, []}
|
||||
]}.
|
65
apps/dmt_api/src/dmt_api.erl
Normal file
65
apps/dmt_api/src/dmt_api.erl
Normal file
@ -0,0 +1,65 @@
|
||||
-module(dmt_api).
|
||||
-behaviour(application).
|
||||
-behaviour(supervisor).
|
||||
|
||||
-export([get_handler_spec/1]).
|
||||
|
||||
-export([start/2]).
|
||||
-export([stop/1]).
|
||||
-export([init/1]).
|
||||
|
||||
%%
|
||||
|
||||
-spec start(application:start_type(), term()) -> {ok, pid()} | {error, term()}.
|
||||
|
||||
start(_StartType, _Args) ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
||||
|
||||
-spec stop(term()) -> ok.
|
||||
|
||||
stop(_State) ->
|
||||
ok.
|
||||
|
||||
%%
|
||||
|
||||
-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec(), ...]}}.
|
||||
|
||||
init([]) ->
|
||||
WoodyChildSpec = woody_server:child_spec(
|
||||
?MODULE,
|
||||
#{
|
||||
ip => dmt_api_utils:get_hostname_ip(genlib_app:env(?MODULE, host, "dominant")),
|
||||
port => genlib_app:env(?MODULE, port, 8800),
|
||||
net_opts => [],
|
||||
event_handler => dmt_api_woody_event_handler,
|
||||
handlers => [
|
||||
get_handler_spec(repository),
|
||||
get_handler_spec(repository_client),
|
||||
get_handler_spec(mg_processor)
|
||||
]
|
||||
}
|
||||
),
|
||||
{ok, {#{strategy => one_for_one, intensity => 0, period => 1}, [WoodyChildSpec]}}.
|
||||
|
||||
-spec get_handler_spec(Which) -> {Path, {woody_t:service(), module(), term()}} when
|
||||
Which :: repository | repository_client | mg_processor,
|
||||
Path :: iodata().
|
||||
|
||||
get_handler_spec(repository) ->
|
||||
{"/v1/domain/repository", {
|
||||
{dmt_domain_config_thrift, 'Repository'},
|
||||
dmt_api_repository_handler,
|
||||
[]
|
||||
}};
|
||||
get_handler_spec(repository_client) ->
|
||||
{"/v1/domain/repository_client", {
|
||||
{dmt_domain_config_thrift, 'RepositoryClient'},
|
||||
dmt_api_repository_client_handler,
|
||||
[]
|
||||
}};
|
||||
get_handler_spec(mg_processor) ->
|
||||
{"/v1/domain/mgun_processor", {
|
||||
{dmt_state_processing_thrift, 'Processor'},
|
||||
dmt_api_mgun_handler,
|
||||
[]
|
||||
}}.
|
42
apps/dmt_api/src/dmt_api_client.erl
Normal file
42
apps/dmt_api/src/dmt_api_client.erl
Normal file
@ -0,0 +1,42 @@
|
||||
-module(dmt_api_client).
|
||||
|
||||
-export([commit/2]).
|
||||
-export([checkout/1]).
|
||||
-export([pull/1]).
|
||||
-export([checkout_object/2]).
|
||||
|
||||
|
||||
-spec commit(dmt:version(), dmt:commit()) -> dmt:version().
|
||||
commit(Version, Commit) ->
|
||||
call(repository, 'Commit', [Version, Commit]).
|
||||
|
||||
-spec checkout(dmt:ref()) -> dmt:snapshot().
|
||||
checkout(Reference) ->
|
||||
call(repository, 'Checkout', [Reference]).
|
||||
|
||||
-spec pull(dmt:version()) -> dmt:history().
|
||||
pull(Version) ->
|
||||
call(repository, 'Pull', [Version]).
|
||||
|
||||
-spec checkout_object(dmt:ref(), dmt:object_ref()) -> dmt:domain_object().
|
||||
checkout_object(Reference, ObjectReference) ->
|
||||
call(repository_client, 'checkoutObject', [Reference, ObjectReference]).
|
||||
|
||||
|
||||
call(ServiceName, Function, Args) ->
|
||||
Host = application:get_env(dmt, client_host, "dominant"),
|
||||
Port = integer_to_list(application:get_env(dmt, client_port, 8800)),
|
||||
{Path, {Service, _Handler, _Opts}} = dmt_api:get_handler_spec(ServiceName),
|
||||
Call = {Service, Function, Args},
|
||||
Server = #{url => Host ++ ":" ++ Port ++ Path},
|
||||
Context = woody_client:new_context(woody_client:make_id(<<"dmt_client">>), dmt_api_woody_event_handler),
|
||||
case woody_client:call_safe(Context, Call, Server) of
|
||||
{ok, _Context} ->
|
||||
ok;
|
||||
{{ok, Response}, _Context} ->
|
||||
Response;
|
||||
{{exception, Exception}, _Context} ->
|
||||
throw(Exception);
|
||||
{{error, Error}, _Context} ->
|
||||
error(Error)
|
||||
end.
|
32
apps/dmt_api/src/dmt_api_mgun_handler.erl
Normal file
32
apps/dmt_api/src/dmt_api_mgun_handler.erl
Normal file
@ -0,0 +1,32 @@
|
||||
-module(dmt_api_mgun_handler).
|
||||
-behaviour(woody_server_thrift_handler).
|
||||
|
||||
-export([handle_function/4]).
|
||||
|
||||
%%
|
||||
|
||||
-include_lib("dmt_proto/include/dmt_state_processing_thrift.hrl").
|
||||
-include_lib("dmt/include/dmt_mg.hrl").
|
||||
|
||||
|
||||
-spec handle_function(
|
||||
woody_t:func(),
|
||||
woody_server_thrift_handler:args(),
|
||||
woody_client:context(),
|
||||
woody_server_thrift_handler:handler_opts()
|
||||
) -> {ok | {ok, woody_server_thrift_handler:result()}, woody_client:context()} | no_return().
|
||||
handle_function(processCall, {#'CallArgs'{call = <<"commit", Data/binary>>, history = History}}, Context, _Opts) ->
|
||||
{Version, Commit} = binary_to_term(Data),
|
||||
ok = dmt:validate_commit(Version, Commit, dmt_mg:read_history(History)),
|
||||
_Snapshot = dmt_cache:commit(Commit),
|
||||
{
|
||||
{ok, #'CallResult'{
|
||||
events = [term_to_binary(Commit)],
|
||||
action = #'ComplexAction'{},
|
||||
response = <<"ok">>
|
||||
}},
|
||||
Context
|
||||
};
|
||||
handle_function(processSignal, {#'SignalArgs'{signal = {init, #'InitSignal'{}}}}, Context, _Opts) ->
|
||||
CA = #'ComplexAction'{tag = #'TagAction'{tag = ?MG_TAG}},
|
||||
{{ok, #'SignalResult'{events = [], action = CA}}, Context}.
|
25
apps/dmt_api/src/dmt_api_repository_client_handler.erl
Normal file
25
apps/dmt_api/src/dmt_api_repository_client_handler.erl
Normal file
@ -0,0 +1,25 @@
|
||||
-module(dmt_api_repository_client_handler).
|
||||
-behaviour(woody_server_thrift_handler).
|
||||
|
||||
-export([handle_function/4]).
|
||||
|
||||
%%
|
||||
|
||||
-include_lib("dmt_proto/include/dmt_domain_config_thrift.hrl").
|
||||
|
||||
-spec handle_function(
|
||||
woody_t:func(),
|
||||
woody_server_thrift_handler:args(),
|
||||
woody_client:context(),
|
||||
woody_server_thrift_handler:handler_opts()
|
||||
) -> {ok | {ok, woody_server_thrift_handler:result()}, woody_client:context()} | no_return().
|
||||
handle_function('checkoutObject', {Reference, ObjectReference}, Context, _Opts) ->
|
||||
try
|
||||
Object = dmt:checkout_object(Reference, ObjectReference),
|
||||
{{ok, Object}, Context}
|
||||
catch
|
||||
object_not_found ->
|
||||
throw({#'ObjectNotFound'{}, Context});
|
||||
version_not_found ->
|
||||
throw({#'VersionNotFound'{}, Context})
|
||||
end.
|
41
apps/dmt_api/src/dmt_api_repository_handler.erl
Normal file
41
apps/dmt_api/src/dmt_api_repository_handler.erl
Normal file
@ -0,0 +1,41 @@
|
||||
-module(dmt_api_repository_handler).
|
||||
-behaviour(woody_server_thrift_handler).
|
||||
|
||||
-export([handle_function/4]).
|
||||
|
||||
%%
|
||||
|
||||
-include_lib("dmt_proto/include/dmt_domain_config_thrift.hrl").
|
||||
|
||||
-spec handle_function(
|
||||
woody_t:func(),
|
||||
woody_server_thrift_handler:args(),
|
||||
woody_client:context(),
|
||||
woody_server_thrift_handler:handler_opts()
|
||||
) -> {ok | {ok, woody_server_thrift_handler:result()}, woody_client:context()} | no_return().
|
||||
handle_function('Commit', {Version, Commit}, Context, _Opts) ->
|
||||
try
|
||||
NewVersion = dmt:commit(Version, Commit),
|
||||
{{ok, NewVersion}, Context}
|
||||
catch
|
||||
operation_conflict ->
|
||||
throw({#'OperationConflict'{}, Context});
|
||||
version_not_found ->
|
||||
throw({#'VersionNotFound'{}, Context})
|
||||
end;
|
||||
handle_function('Checkout', {Reference}, Context, _Opts) ->
|
||||
try
|
||||
Snapshot = dmt:checkout(Reference),
|
||||
{{ok, Snapshot}, Context}
|
||||
catch
|
||||
version_not_found ->
|
||||
throw({#'VersionNotFound'{}, Context})
|
||||
end;
|
||||
handle_function('Pull', {Version}, Context, _Opts) ->
|
||||
try
|
||||
History = dmt:pull(Version),
|
||||
{{ok, History}, Context}
|
||||
catch
|
||||
version_not_found ->
|
||||
throw({#'VersionNotFound'{}, Context})
|
||||
end.
|
22
apps/dmt_api/src/dmt_api_utils.erl
Normal file
22
apps/dmt_api/src/dmt_api_utils.erl
Normal file
@ -0,0 +1,22 @@
|
||||
-module(dmt_api_utils).
|
||||
|
||||
-export([get_hostname_ip/1]).
|
||||
|
||||
%%
|
||||
|
||||
-include_lib("kernel/include/inet.hrl").
|
||||
|
||||
-spec get_hostname_ip(Hostname | IP) -> IP when
|
||||
Hostname :: string(),
|
||||
IP :: inet:ip_address().
|
||||
|
||||
get_hostname_ip(IP) when tuple_size(IP) == 4 orelse tuple_size(IP) == 8 ->
|
||||
IP;
|
||||
|
||||
get_hostname_ip(Host) ->
|
||||
case inet:gethostbyname(Host) of
|
||||
{ok, #hostent{h_addr_list = [IP | _]}} ->
|
||||
IP;
|
||||
{error, Error} ->
|
||||
exit(Error)
|
||||
end.
|
21
apps/dmt_api/src/dmt_api_woody_event_handler.erl
Normal file
21
apps/dmt_api/src/dmt_api_woody_event_handler.erl
Normal file
@ -0,0 +1,21 @@
|
||||
-module(dmt_api_woody_event_handler).
|
||||
|
||||
-behaviour(woody_event_handler).
|
||||
|
||||
-export([handle_event/3]).
|
||||
|
||||
-spec handle_event(EventType, RpcID, EventMeta)
|
||||
-> _ when
|
||||
EventType :: woody_event_handler:event_type(),
|
||||
RpcID :: woody_t:rpc_id(),
|
||||
EventMeta :: woody_event_handler:event_meta_type().
|
||||
|
||||
handle_event(EventType, RpcID, #{status := error, class := Class, reason := Reason, stack := Stack}) ->
|
||||
lager:error(
|
||||
maps:to_list(RpcID),
|
||||
"[server] ~s with ~s:~p at ~s",
|
||||
[EventType, Class, Reason, genlib_format:format_stacktrace(Stack, [newlines])]
|
||||
);
|
||||
|
||||
handle_event(EventType, RpcID, EventMeta) ->
|
||||
lager:debug(maps:to_list(RpcID), "[server] ~s: ~p", [EventType, EventMeta]).
|
94
apps/dmt_api/test/dmt_api_tests_SUITE.erl
Normal file
94
apps/dmt_api/test/dmt_api_tests_SUITE.erl
Normal file
@ -0,0 +1,94 @@
|
||||
-module(dmt_api_tests_SUITE).
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
-export([all/0]).
|
||||
-export([groups/0]).
|
||||
-export([init_per_suite/1]).
|
||||
-export([end_per_suite/1]).
|
||||
-export([application_stop/1]).
|
||||
-export([insert/1]).
|
||||
-export([update/1]).
|
||||
-export([delete/1]).
|
||||
|
||||
-include_lib("dmt_proto/include/dmt_domain_config_thrift.hrl").
|
||||
|
||||
%%
|
||||
%% tests descriptions
|
||||
%%
|
||||
-spec all() -> [term()].
|
||||
all() ->
|
||||
[
|
||||
{group, basic_lifecycle}
|
||||
].
|
||||
|
||||
-spec groups() -> [term()].
|
||||
groups() ->
|
||||
[
|
||||
{basic_lifecycle, [sequence], [
|
||||
insert,
|
||||
update,
|
||||
delete
|
||||
]}
|
||||
].
|
||||
|
||||
%%
|
||||
%% starting/stopping
|
||||
-spec init_per_suite(term()) -> term().
|
||||
init_per_suite(C) ->
|
||||
{ok, Apps} = application:ensure_all_started(dmt_api),
|
||||
ok = dmt_mg:start(),
|
||||
[{apps, Apps}|C].
|
||||
|
||||
-spec end_per_suite(term()) -> term().
|
||||
end_per_suite(C) ->
|
||||
[application_stop(App) || App <- proplists:get_value(apps, C)].
|
||||
|
||||
-spec application_stop(term()) -> term().
|
||||
application_stop(App) ->
|
||||
application:stop(App).
|
||||
|
||||
%%
|
||||
%% tests
|
||||
-spec insert(term()) -> term().
|
||||
insert(_C) ->
|
||||
Object = fixture_domain_object(1, <<"InsertFixture">>),
|
||||
Ref = fixture_object_ref(1),
|
||||
#'ObjectNotFound'{} = (catch dmt_api_client:checkout_object({head, #'Head'{}}, Ref)),
|
||||
#'Snapshot'{version = Version1} = dmt_api_client:checkout({head, #'Head'{}}),
|
||||
Version2 = dmt_api_client:commit(Version1, #'Commit'{ops = [{insert, #'InsertOp'{object = Object}}]}),
|
||||
#'VersionedObject'{object = Object} = dmt_api_client:checkout_object({head, #'Head'{}}, Ref),
|
||||
#'ObjectNotFound'{} = (catch dmt_api_client:checkout_object({version, Version1}, Ref)),
|
||||
#'VersionedObject'{object = Object} = dmt_api_client:checkout_object({version, Version2}, Ref).
|
||||
|
||||
-spec update(term()) -> term().
|
||||
update(_C) ->
|
||||
Object1 = fixture_domain_object(2, <<"UpdateFixture1">>),
|
||||
Object2 = fixture_domain_object(2, <<"UpdateFixture2">>),
|
||||
Ref = fixture_object_ref(2),
|
||||
#'Snapshot'{version = Version0} = dmt_api_client:checkout({head, #'Head'{}}),
|
||||
Version1 = dmt_api_client:commit(Version0, #'Commit'{ops = [{insert, #'InsertOp'{object = Object1}}]}),
|
||||
Version2 = dmt_api_client:commit(
|
||||
Version1,
|
||||
#'Commit'{ops = [{update, #'UpdateOp'{old_object = Object1, new_object = Object2}}]}
|
||||
),
|
||||
#'VersionedObject'{object = Object1} = dmt_api_client:checkout_object({version, Version1}, Ref),
|
||||
#'VersionedObject'{object = Object2} = dmt_api_client:checkout_object({version, Version2}, Ref).
|
||||
|
||||
-spec delete(term()) -> term().
|
||||
delete(_C) ->
|
||||
Object = fixture_domain_object(3, <<"DeleteFixture">>),
|
||||
Ref = fixture_object_ref(3),
|
||||
#'Snapshot'{version = Version0} = dmt_api_client:checkout({head, #'Head'{}}),
|
||||
Version1 = dmt_api_client:commit(Version0, #'Commit'{ops = [{insert, #'InsertOp'{object = Object}}]}),
|
||||
Version2 = dmt_api_client:commit(Version1, #'Commit'{ops = [{remove, #'RemoveOp'{object = Object}}]}),
|
||||
#'VersionedObject'{object = Object} = dmt_api_client:checkout_object({version, Version1}, Ref),
|
||||
#'ObjectNotFound'{} = (catch dmt_api_client:checkout_object({version, Version2}, Ref)).
|
||||
|
||||
fixture_domain_object(Ref, Data) ->
|
||||
{category, #'CategoryObject'{
|
||||
ref = #'CategoryRef'{id = Ref},
|
||||
data = #'Category'{name = Data, description = Data}
|
||||
}}.
|
||||
|
||||
fixture_object_ref(Ref) ->
|
||||
{category, #'CategoryRef'{id = Ref}}.
|
71
apps/dmt_api/test/dmt_client_tests_SUITE.erl
Normal file
71
apps/dmt_api/test/dmt_client_tests_SUITE.erl
Normal file
@ -0,0 +1,71 @@
|
||||
-module(dmt_client_tests_SUITE).
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
-export([all/0]).
|
||||
-export([groups/0]).
|
||||
-export([init_per_suite/1]).
|
||||
-export([end_per_suite/1]).
|
||||
-export([application_stop/1]).
|
||||
-export([poll/1]).
|
||||
|
||||
-include_lib("dmt_proto/include/dmt_domain_config_thrift.hrl").
|
||||
|
||||
%%
|
||||
%% tests descriptions
|
||||
%%
|
||||
-spec all() -> [term()].
|
||||
all() ->
|
||||
[
|
||||
{group, basic_lifecycle}
|
||||
].
|
||||
|
||||
-spec groups() -> [term()].
|
||||
groups() ->
|
||||
[
|
||||
{basic_lifecycle, [sequence], [
|
||||
poll
|
||||
]}
|
||||
].
|
||||
|
||||
%%
|
||||
%% starting/stopping
|
||||
-spec init_per_suite(term()) -> term().
|
||||
init_per_suite(C) ->
|
||||
{ok, Apps} = application:ensure_all_started(dmt),
|
||||
{ok, _PollerPid} = supervisor:start_child(dmt, #
|
||||
{id => dmt_poller, start => {dmt_poller, start_link, []}, restart => permanent}
|
||||
),
|
||||
ok = dmt_mg:start(),
|
||||
[{apps, Apps}|C].
|
||||
|
||||
-spec end_per_suite(term()) -> term().
|
||||
end_per_suite(C) ->
|
||||
[application_stop(App) || App <- proplists:get_value(apps, C)].
|
||||
|
||||
-spec application_stop(term()) -> term().
|
||||
application_stop(App) ->
|
||||
application:stop(App).
|
||||
|
||||
%%
|
||||
%% tests
|
||||
-spec poll(term()) -> term().
|
||||
poll(_C) ->
|
||||
Object = fixture_domain_object(1, <<"InsertFixture">>),
|
||||
Ref = fixture_object_ref(1),
|
||||
{'ObjectNotFound'} = (catch dmt_api_client:checkout_object({head, #'Head'{}}, Ref)),
|
||||
#'Snapshot'{version = Version1} = dmt_api_client:checkout({head, #'Head'{}}),
|
||||
Version2 = dmt_api_client:commit(Version1, #'Commit'{ops = [{insert, #'InsertOp'{object = Object}}]}),
|
||||
#'Snapshot'{version = Version1} = dmt:checkout({head, #'Head'{}}),
|
||||
object_not_found = (catch dmt:checkout_object({head, #'Head'{}}, Ref)),
|
||||
ok = dmt_poller:poll(),
|
||||
#'Snapshot'{version = Version2} = dmt:checkout({head, #'Head'{}}),
|
||||
#'VersionedObject'{object = Object} = dmt:checkout_object({head, #'Head'{}}, Ref).
|
||||
|
||||
fixture_domain_object(Ref, Data) ->
|
||||
{category, #'CategoryObject'{
|
||||
ref = #'CategoryRef'{id = Ref},
|
||||
data = #'Category'{name = Data, description = Data}
|
||||
}}.
|
||||
|
||||
fixture_object_ref(Ref) ->
|
||||
{category, #'CategoryRef'{id = Ref}}.
|
2
apps/dmt_proto/.gitignore
vendored
Normal file
2
apps/dmt_proto/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/include/dmt_*_thrift.hrl
|
||||
/src/dmt_*_thrift.erl
|
1
apps/dmt_proto/damsel
Submodule
1
apps/dmt_proto/damsel
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 7eda08c2a54c617d7fad2b14e6c185ba9f3fdc27
|
17
apps/dmt_proto/rebar.config
Normal file
17
apps/dmt_proto/rebar.config
Normal file
@ -0,0 +1,17 @@
|
||||
{plugins, [
|
||||
{rebar3_thrift_compiler,
|
||||
{git, "https://github.com/rbkmoney/rebar3_thrift_compiler.git", {tag, "0.2"}}}
|
||||
]}.
|
||||
|
||||
{provider_hooks, [
|
||||
{pre, [
|
||||
{compile, {thrift, compile}},
|
||||
{clean, {thrift, clean}}
|
||||
]}
|
||||
]}.
|
||||
|
||||
{thrift_compiler_opts, [
|
||||
{in_dir, "damsel/proto"},
|
||||
{in_files, ["domain_config.thrift", "state_processing.thrift"]},
|
||||
{gen, "erlang:app_prefix=dmt"}
|
||||
]}.
|
9
apps/dmt_proto/src/dmt_proto.app.src
Normal file
9
apps/dmt_proto/src/dmt_proto.app.src
Normal file
@ -0,0 +1,9 @@
|
||||
{application, dmt_proto, [
|
||||
{description, "Domain config protocol definitions"},
|
||||
{vsn, "0"},
|
||||
{registered, []},
|
||||
{applications, [
|
||||
kernel,
|
||||
stdlib
|
||||
]}
|
||||
]}.
|
4
config/sys.config
Normal file
4
config/sys.config
Normal file
@ -0,0 +1,4 @@
|
||||
[
|
||||
{dmt, [
|
||||
]}
|
||||
].
|
6
config/vm.args
Normal file
6
config/vm.args
Normal file
@ -0,0 +1,6 @@
|
||||
-sname dominant
|
||||
|
||||
-setcookie dominant_cookie
|
||||
|
||||
+K true
|
||||
+A 10
|
5
doc/index.md
Normal file
5
doc/index.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Документация
|
||||
|
||||
1. [Общее описание](overview.md)
|
||||
1. [Установка](install.md)
|
||||
1. [Первоначалная настройка](configuration.md)
|
21
docker-compose.yml
Normal file
21
docker-compose.yml
Normal file
@ -0,0 +1,21 @@
|
||||
version: '2'
|
||||
services:
|
||||
dominant:
|
||||
image: rbkmoney/build:latest
|
||||
volumes:
|
||||
- .:/code
|
||||
working_dir: /code
|
||||
command: /sbin/init
|
||||
links:
|
||||
- machinegun
|
||||
dmt_client:
|
||||
image: rbkmoney/build:latest
|
||||
volumes:
|
||||
- .:/code
|
||||
working_dir: /code
|
||||
command: /sbin/init
|
||||
links:
|
||||
- dominant
|
||||
machinegun:
|
||||
image: rbkmoney/mg:dmt
|
||||
command: /opt/mgun/bin/mgun foreground
|
68
elvis.config
Normal file
68
elvis.config
Normal file
@ -0,0 +1,68 @@
|
||||
[
|
||||
{elvis, [
|
||||
{config, [
|
||||
#{
|
||||
dirs => ["apps/*/src"],
|
||||
filter => "*.erl",
|
||||
ignore => ["_thrift.erl$"],
|
||||
rules => [
|
||||
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
|
||||
{elvis_style, no_tabs},
|
||||
{elvis_style, no_trailing_whitespace},
|
||||
{elvis_style, macro_names},
|
||||
{elvis_style, macro_module_names},
|
||||
{elvis_style, operator_spaces, #{rules => [{right, ","}, {right, "++"}, {left, "++"}]}},
|
||||
{elvis_style, nesting_level, #{level => 3}},
|
||||
{elvis_style, god_modules, #{limit => 25}},
|
||||
{elvis_style, no_if_expression},
|
||||
{elvis_style, invalid_dynamic_call, #{ignore => [elvis]}},
|
||||
{elvis_style, used_ignored_variable},
|
||||
{elvis_style, no_behavior_info},
|
||||
{elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$"}},
|
||||
{elvis_style, function_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*$"}},
|
||||
{elvis_style, state_record_and_type},
|
||||
{elvis_style, no_spec_with_records},
|
||||
{elvis_style, dont_repeat_yourself, #{min_complexity => 10}},
|
||||
{elvis_style, no_debug_call, #{ignore => [elvis, elvis_utils]}}
|
||||
]
|
||||
},
|
||||
#{
|
||||
dirs => ["."],
|
||||
filter => "Makefile",
|
||||
ruleset => makefiles
|
||||
},
|
||||
#{
|
||||
dirs => ["."],
|
||||
filter => "elvis.config",
|
||||
ruleset => elvis_config
|
||||
},
|
||||
#{
|
||||
dirs => ["apps", "apps/*"],
|
||||
filter => "rebar.config",
|
||||
rules => [
|
||||
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
|
||||
{elvis_style, no_tabs},
|
||||
{elvis_style, no_trailing_whitespace}
|
||||
]
|
||||
},
|
||||
#{
|
||||
dirs => ["."],
|
||||
filter => "rebar.config",
|
||||
rules => [
|
||||
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
|
||||
{elvis_style, no_tabs},
|
||||
{elvis_style, no_trailing_whitespace}
|
||||
]
|
||||
},
|
||||
#{
|
||||
dirs => ["apps/*/src"],
|
||||
filter => "*.app.src",
|
||||
rules => [
|
||||
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
|
||||
{elvis_style, no_tabs},
|
||||
{elvis_style, no_trailing_whitespace}
|
||||
]
|
||||
}
|
||||
]}
|
||||
]}
|
||||
].
|
85
rebar.config
Normal file
85
rebar.config
Normal file
@ -0,0 +1,85 @@
|
||||
%% Common project erlang options.
|
||||
{erl_opts, [
|
||||
|
||||
% mandatory
|
||||
debug_info,
|
||||
warnings_as_errors,
|
||||
warn_export_all,
|
||||
warn_missing_spec,
|
||||
warn_untyped_record,
|
||||
warn_export_vars,
|
||||
|
||||
% by default
|
||||
warn_unused_record,
|
||||
warn_bif_clash,
|
||||
warn_obsolete_guard,
|
||||
warn_unused_vars,
|
||||
warn_shadow_vars,
|
||||
warn_unused_import,
|
||||
warn_unused_function,
|
||||
warn_deprecated_function
|
||||
|
||||
% at will
|
||||
% bin_opt_info
|
||||
% no_auto_import
|
||||
% warn_missing_spec_all
|
||||
]}.
|
||||
|
||||
%% Common project dependencies.
|
||||
{deps, [
|
||||
{genlib , {git, "https://github.com/rbkmoney/genlib.git", {branch, "master"}}},
|
||||
{woody , {git, "git@github.com:rbkmoney/woody_erlang.git", {branch, "master"}}},
|
||||
{lager , "3.0.2"}
|
||||
]}.
|
||||
|
||||
%% XRef checks
|
||||
{xref_checks, [
|
||||
undefined_function_calls,
|
||||
undefined_functions,
|
||||
deprecated_functions_calls,
|
||||
deprecated_functions
|
||||
]}.
|
||||
|
||||
% at will
|
||||
% {xref_warnings, true}.
|
||||
|
||||
%% Tests
|
||||
{cover_enabled, true}.
|
||||
|
||||
%% Relx configuration
|
||||
{relx, [
|
||||
{release, {dominant, "0.1"}, [
|
||||
dmt_api,
|
||||
sasl
|
||||
]},
|
||||
{sys_config, "./config/sys.config"},
|
||||
{vm_args, "./config/vm.args"},
|
||||
{dev_mode, true},
|
||||
{include_erts, false},
|
||||
{extended_start_script, true}
|
||||
]}.
|
||||
|
||||
%% Dialyzer static analyzing
|
||||
{dialyzer, [
|
||||
{warnings, [
|
||||
% mandatory
|
||||
unmatched_returns,
|
||||
error_handling,
|
||||
race_conditions,
|
||||
unknown
|
||||
]},
|
||||
{plt_apps, all_deps}
|
||||
]}.
|
||||
|
||||
{profiles, [
|
||||
{prod, [
|
||||
{relx, [
|
||||
{dev_mode, false},
|
||||
{include_erts, true}
|
||||
]}
|
||||
]}
|
||||
]}.
|
||||
|
||||
{plugins, [
|
||||
rebar3_run
|
||||
]}.
|
27
rebar.lock
Normal file
27
rebar.lock
Normal file
@ -0,0 +1,27 @@
|
||||
[{<<"certifi">>,{pkg,<<"certifi">>,<<"0.4.0">>},2},
|
||||
{<<"cowboy">>,{pkg,<<"cowboy">>,<<"1.0.4">>},1},
|
||||
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"1.0.2">>},2},
|
||||
{<<"genlib">>,
|
||||
{git,"https://github.com/rbkmoney/genlib.git",
|
||||
{ref,"66db7fe296465a875b6894eb5ac944c90f82f913"}},
|
||||
0},
|
||||
{<<"goldrush">>,{pkg,<<"goldrush">>,<<"0.1.7">>},1},
|
||||
{<<"hackney">>,{pkg,<<"hackney">>,<<"1.5.7">>},1},
|
||||
{<<"idna">>,{pkg,<<"idna">>,<<"1.2.0">>},2},
|
||||
{<<"lager">>,{pkg,<<"lager">>,<<"3.0.2">>},0},
|
||||
{<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2},
|
||||
{<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.0.2">>},2},
|
||||
{<<"ranch">>,{pkg,<<"ranch">>,<<"1.2.1">>},2},
|
||||
{<<"snowflake">>,
|
||||
{git,"https://github.com/tel/snowflake.git",
|
||||
{ref,"7a8eab0f12757133623b2151a7913b6d2707b629"}},
|
||||
1},
|
||||
{<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.0">>},2},
|
||||
{<<"thrift">>,
|
||||
{git,"https://github.com/rbkmoney/thrift_erlang.git",
|
||||
{ref,"1978d5a1e350694bfa3d2dc3a762176bc60c6e64"}},
|
||||
1},
|
||||
{<<"woody">>,
|
||||
{git,"git@github.com:rbkmoney/woody_erlang.git",
|
||||
{ref,"dbe03ca171087f2649a7057a379cdb10c5dce90c"}},
|
||||
0}].
|
37
utils.mk
Normal file
37
utils.mk
Normal file
@ -0,0 +1,37 @@
|
||||
SHELL := /bin/bash
|
||||
|
||||
which = $(if $(shell which $(1) 2>/dev/null),\
|
||||
$(shell which $(1) 2>/dev/null),\
|
||||
$(error "Error: could not locate $(1)!"))
|
||||
|
||||
DOCKER = $(call which,docker)
|
||||
DOCKER_COMPOSE = $(call which,docker-compose)
|
||||
|
||||
UTIL_TARGETS := to_dev_container w_container_% run_w_container_% check_w_container_%
|
||||
|
||||
ifndef RELNAME
|
||||
$(error RELNAME is not set)
|
||||
endif
|
||||
|
||||
ifndef CALL_W_CONTAINER
|
||||
$(error CALL_W_CONTAINER is not set)
|
||||
endif
|
||||
|
||||
to_dev_container:
|
||||
$(DOCKER) run -it --rm -v $$PWD:$$PWD --workdir $$PWD $(BASE_IMAGE) /bin/bash
|
||||
|
||||
w_container_%:
|
||||
$(MAKE) -s run_w_container_$*
|
||||
|
||||
run_w_container_%: check_w_container_%
|
||||
{ \
|
||||
$(DOCKER_COMPOSE) up -d ; \
|
||||
$(DOCKER_COMPOSE) exec -T $(RELNAME) make $* ; \
|
||||
res=$$? ; \
|
||||
$(DOCKER_COMPOSE) down ; \
|
||||
exit $$res ; \
|
||||
}
|
||||
|
||||
check_w_container_%:
|
||||
$(if $(filter $*,$(CALL_W_CONTAINER)),,\
|
||||
$(error "Error: target '$*' cannot be called w_container_"))
|
Loading…
Reference in New Issue
Block a user