Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove domain/async #3813

Merged
merged 6 commits into from
Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions big_tests/tests/domain_rest_helper.erl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
putt_domain_with_custom_body/2,
rest_select_domain/2,
rest_delete_domain/3,
request_delete_domain/3,
delete_custom/4,
patch_custom/4]).

Expand Down Expand Up @@ -73,6 +74,13 @@ rest_delete_domain(Config, Domain, HostType) ->
creds => make_creds(Config),
body => Params }).

request_delete_domain(Config, Domain, HostType) ->
Params = #{<<"host_type">> => HostType, <<"request">> => true},
rest_helper:make_request(#{ role => admin, method => <<"DELETE">>,
path => domain_path(Domain),
creds => make_creds(Config),
body => Params }).

delete_custom(Config, Role, Path, Body) ->
rest_helper:make_request(#{ role => Role, method => <<"DELETE">>,
path => Path, creds => make_creds(Config),
Expand Down
19 changes: 19 additions & 0 deletions big_tests/tests/graphql_domain_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ domain_tests() ->
get_domains_by_host_type,
get_domain_details,
delete_domain,
request_delete_domain,
get_domains_after_deletion,
set_domain_password,
set_nonexistent_domain_password,
Expand Down Expand Up @@ -177,6 +178,20 @@ delete_domain(Config) ->
<<"domain">> := #{<<"domain">> := ?SECOND_EXAMPLE_DOMAIN}},
ParsedResult2).

request_delete_domain(Config) ->
Domain = <<"exampleDomain">>,
Result1 = request_remove_domain(Domain, ?HOST_TYPE, Config),
ParsedResult1 = get_ok_value([data, domain, requestRemoveDomain], Result1),
?assertMatch(#{<<"msg">> := <<"Domain disabled and enqueued for deletion">>,
<<"domain">> := #{<<"domain">> := Domain,
<<"status">> := <<"DELETING">>}},
ParsedResult1),
F = fun() ->
Result = get_domain_details(Domain, Config),
domain_not_found_error_formatting(Result)
end,
mongoose_helper:wait_until(F, ok, #{time_left => timer:seconds(5)}).

get_domains_after_deletion(Config) ->
Result = get_domains_by_host_type(?HOST_TYPE, Config),
ParsedResult = get_ok_value([data, domain, domainsByHostType], Result),
Expand Down Expand Up @@ -263,6 +278,10 @@ remove_domain(Domain, HostType, Config) ->
Vars = #{domain => Domain, hostType => HostType},
execute_command(<<"domain">>, <<"removeDomain">>, Vars, Config).

request_remove_domain(Domain, HostType, Config) ->
Vars = #{domain => Domain, hostType => HostType},
execute_command(<<"domain">>, <<"requestRemoveDomain">>, Vars, Config).

get_domains_by_host_type(HostType, Config) ->
Vars = #{hostType => HostType},
execute_command(<<"domain">>, <<"domainsByHostType">>, Vars, Config).
Expand Down
66 changes: 65 additions & 1 deletion big_tests/tests/service_domain_db_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ db_cases() -> [
db_deleted_domain_from_db,
db_deleted_domain_fails_with_wrong_host_type,
db_deleted_domain_from_core,
rest_cannot_enable_deleting,
db_disabled_domain_is_in_db,
db_disabled_domain_not_in_core,
db_reenabled_domain_is_in_db,
Expand Down Expand Up @@ -130,11 +131,11 @@ rest_auth_cases(true) ->
rest_cannot_enable_domain_without_auth,
rest_cannot_disable_domain_without_auth,
rest_cannot_select_domain_without_auth].

rest_cases() ->
[rest_can_insert_domain,
rest_can_disable_domain,
rest_can_delete_domain,
rest_request_can_delete_domain,
rest_cannot_delete_domain_without_correct_type,
rest_delete_missing_domain,
rest_cannot_insert_domain_twice_with_another_host_type,
Expand Down Expand Up @@ -180,6 +181,7 @@ init_per_suite(Config) ->
prepare_test_queries(mim2()),
erase_database(mim()),
Config2 = ejabberd_node_utils:init(mim(), Config1),
mongoose_helper:inject_module(?MODULE),
escalus:init_per_suite([{service_setup, per_testcase} | Config2]).

end_per_suite(Config) ->
Expand Down Expand Up @@ -220,6 +222,10 @@ end_per_group(_GroupName, Config) ->
per_testcase -> ok
end.

init_per_testcase(rest_cannot_enable_deleting, Config) ->
HostType = <<"type1">>,
Server = start_domain_removal_hook(HostType),
init_per_testcase(generic, [{server, Server}, {host_type, HostType} | Config]);
init_per_testcase(db_crash_on_initial_load_restarts_service, Config) ->
maybe_setup_meck(db_crash_on_initial_load_restarts_service),
restart_domain_core(mim(), [], []),
Expand All @@ -237,6 +243,11 @@ service_opts(db_events_table_gets_truncated) ->
service_opts(_) ->
#{}.

end_per_testcase(rest_cannot_enable_deleting, Config) ->
Server = ?config(server, Config),
HostType = ?config(host_type, Config),
stop_domain_removal_hook(HostType, Server),
exit(Server, normal);
end_per_testcase(TestcaseName, Config) ->
end_per_testcase2(TestcaseName, Config),
case TestcaseName of
Expand Down Expand Up @@ -366,6 +377,19 @@ db_inserted_domain_is_in_core(_) ->
sync(),
{ok, <<"type1">>} = get_host_type(mim(), <<"example.db">>).

rest_cannot_enable_deleting(Config) ->
HostType = ?config(host_type, Config),
Domain = <<"example.db">>,
ok = insert_domain(mim(), Domain, HostType),
{ok, #{status := enabled}} = select_domain(mim(), Domain),
ok = request_delete_domain(mim(), Domain, HostType),
{ok, #{status := deleting}} = select_domain(mim(), Domain),
{error, domain_deleted} = enable_domain(mim(), Domain),
Server = ?config(server, Config),
Server ! continue,
F = fun () -> select_domain(mim(), <<"example.db">>) end,
mongoose_helper:wait_until(F, {error, not_found}, #{time_left => timer:seconds(15)}).

db_deleted_domain_from_db(_) ->
ok = insert_domain(mim(), <<"example.db">>, <<"type1">>),
ok = delete_domain(mim(), <<"example.db">>, <<"type1">>),
Expand Down Expand Up @@ -821,6 +845,19 @@ rest_can_disable_domain(Config) ->
{ok, #{host_type := <<"type1">>, status := disabled}} =
select_domain(mim(), <<"example.db">>).

rest_request_can_delete_domain(Config) ->
%% Put a new domain to delete later
rest_put_domain(Config, <<"example.db">>, <<"type1">>),
%% Request delete domain
{{<<"202">>, _}, _} = domain_rest_helper:request_delete_domain(Config, <<"example.db">>, <<"type1">>),
%% Wait until it is not found anymore
Return = {{<<"404">>, <<"Not Found">>}, <<"Domain not found">>},
F1 = fun() -> rest_select_domain(Config, <<"example.db">>) end,
mongoose_helper:wait_until(F1, Return, #{time_left => timer:seconds(15)}),
%% Double-check
F2 = fun() -> select_domain(mim(), <<"example.db">>) end,
mongoose_helper:wait_until(F2, {error, not_found}, #{time_left => timer:seconds(5)}).

rest_can_delete_domain(Config) ->
rest_put_domain(Config, <<"example.db">>, <<"type1">>),
{{<<"204">>, _}, _} =
Expand Down Expand Up @@ -1078,6 +1115,9 @@ insert_domain(Node, Domain, HostType) ->
delete_domain(Node, Domain, HostType) ->
rpc(Node, mongoose_domain_api, delete_domain, [Domain, HostType]).

request_delete_domain(Node, Domain, HostType) ->
rpc(Node, mongoose_domain_api, request_delete_domain, [Domain, HostType]).

select_domain(Node, Domain) ->
rpc(Node, mongoose_domain_sql, select_domain, [Domain]).

Expand Down Expand Up @@ -1146,6 +1186,30 @@ disable_domain(Node, Domain) ->
enable_domain(Node, Domain) ->
rpc(Node, mongoose_domain_api, enable_domain, [Domain]).

start_domain_removal_hook(HostType) ->
Server = spawn(fun stopper/0),
rpc(mim(), gen_hook, add_handler,
[ remove_domain, HostType, fun ?MODULE:domain_removal_hook_fn/3,
#{server => Server}, 30]), %% Priority is so that it comes before muclight and mam
Server.

stop_domain_removal_hook(HostType, Server) ->
rpc(mim(), gen_hook, delete_handler,
[ remove_domain, HostType, fun ?MODULE:domain_removal_hook_fn/3,
#{server => Server}, 30]).

domain_removal_hook_fn(Acc, _Params, #{server := Server}) ->
Server ! {wait, self()},
receive continue -> ok end,
Acc.

stopper() ->
receive
{wait, From} ->
receive continue -> ok end,
From ! continue
end.

%% force_check_for_updates is already sent by insert or delete commands.
%% But it is async.
%% So, the only thing is left to sync is to call ping to the gen_server
Expand Down
13 changes: 13 additions & 0 deletions doc/rest-api/Administration-backend_swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,20 @@ paths:
host_type:
example: "type1"
type: string
- in: body
name: request
description: Flag to indicate if the request should be async.
required: false
schema:
title: request
type: object
properties:
request:
example: true
type: boolean
responses:
202:
description: "The domain has been disabled and is enqueued for removal."
204:
description: "The domain is removed or not found."
403:
Expand Down
3 changes: 3 additions & 0 deletions priv/graphql/schemas/admin/domain.gql
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ type DomainAdminMutation @protected{
"Remove domain. Only for global admin"
removeDomain(domain: String!, hostType: String!): RemoveDomainPayload
@protected(type: GLOBAL)
"Remove domain asynchronously. Only for global admin"
requestRemoveDomain(domain: String!, hostType: String!): RemoveDomainPayload
@protected(type: GLOBAL)
"Enable domain. Only for global admin"
enableDomain(domain: String!): Domain
@protected(type: GLOBAL)
Expand Down
19 changes: 15 additions & 4 deletions src/domain/mongoose_domain_api.erl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
%% domain API
-export([insert_domain/2,
delete_domain/2,
request_delete_domain/2,
disable_domain/1,
enable_domain/1,
get_domain_host_type/1,
Expand All @@ -30,7 +31,8 @@
get_all_subdomains_for_domain/1]).

%% Helper for remove_domain
-export([remove_domain_wrapper/3]).
-export([remove_domain_wrapper/3,
do_delete_domain_in_progress/3]).

%% For testing
-export([get_all_dynamic/0]).
Expand Down Expand Up @@ -82,13 +84,20 @@ insert_domain(Domain, HostType) ->
%% Domain should be nameprepped using `jid:nameprep'.
-spec delete_domain(domain(), host_type()) -> delete_domain_return().
delete_domain(Domain, HostType) ->
do_delete_domain(Domain, HostType, sync).

-spec request_delete_domain(domain(), host_type()) -> delete_domain_return().
request_delete_domain(Domain, HostType) ->
do_delete_domain(Domain, HostType, async).

do_delete_domain(Domain, HostType, RequestType) ->
case check_domain(Domain, HostType) of
ok ->
Res0 = check_db(mongoose_domain_sql:set_domain_for_deletion(Domain, HostType)),
case Res0 of
ok ->
delete_domain_password(Domain),
do_delete_domain_in_progress(Domain, HostType);
do_delete_domain_in_progress(Domain, HostType, RequestType);
Other ->
Other
end;
Expand All @@ -98,8 +107,10 @@ delete_domain(Domain, HostType) ->

%% This is ran only in the context of `do_delete_domain',
%% so it can already skip some checks
-spec do_delete_domain_in_progress(domain(), host_type()) -> delete_domain_return().
do_delete_domain_in_progress(Domain, HostType) ->
-spec do_delete_domain_in_progress(domain(), host_type(), sync | async) -> delete_domain_return().
do_delete_domain_in_progress(Domain, HostType, async) ->
kamilwaz marked this conversation as resolved.
Show resolved Hide resolved
mongoose_domain_db_cleaner:request_delete_domain(Domain, HostType);
do_delete_domain_in_progress(Domain, HostType, sync) ->
case mongoose_hooks:remove_domain(HostType, Domain) of
#{failed := []} ->
check_db(mongoose_domain_sql:delete_domain(Domain, HostType));
Expand Down
18 changes: 17 additions & 1 deletion src/domain/mongoose_domain_db_cleaner.erl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
-include("mongoose_logger.hrl").

-export([start/1, stop/0]).
-export([start_link/1]).
-export([start_link/1, request_delete_domain/2]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
Expand Down Expand Up @@ -37,6 +37,10 @@ stop() ->
start_link(Opts) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, Opts, []).

-spec request_delete_domain(jid:lserver(), mongooseim:host_type()) -> ok.
request_delete_domain(Domain, HostType) ->
gen_server:cast(?MODULE, {delete_domain, Domain, HostType}).

%% ---------------------------------------------------------------------------
%% Server callbacks

Expand All @@ -51,6 +55,8 @@ handle_call(Request, From, State) ->
?UNEXPECTED_CALL(Request, From),
{reply, ok, State}.

handle_cast({delete_domain, Domain, HostType}, State) ->
{noreply, handle_delete_domain(Domain, HostType, State)};
handle_cast(Msg, State) ->
?UNEXPECTED_CAST(Msg),
{noreply, State}.
Expand Down Expand Up @@ -90,3 +96,13 @@ schedule_removal(State = #{max_age := MaxAge}) ->
handle_timeout(_TimerRef, {do_removal, LastEventId}, State) ->
mongoose_domain_sql:delete_events_older_than(LastEventId),
State.

handle_delete_domain(Domain, HostType, State) ->
try
mongoose_domain_api:do_delete_domain_in_progress(Domain, HostType, sync)
catch Class:Reason:Stacktrace ->
?LOG_ERROR(#{what => domain_deletion_failed,
domain => Domain, host_type => HostType,
class => Class, reason => Reason, stacktrace => Stacktrace})
end,
State.
2 changes: 2 additions & 0 deletions src/domain/mongoose_domain_sql.erl
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ set_status(Domain, Status) ->
case mongoose_domain_core:is_host_type_allowed(HostType) of
false ->
{error, unknown_host_type};
true when deleting =:= CurrentStatus ->
{error, domain_deleted};
true when Status =:= CurrentStatus ->
ok;
true ->
Expand Down
8 changes: 8 additions & 0 deletions src/graphql/admin/mongoose_graphql_domain_admin_mutation.erl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ execute(_Ctx, admin, <<"removeDomain">>, #{<<"domain">> := Domain, <<"hostType">
{error, Error} ->
error_handler(Error, Domain, HostType)
end;
execute(_Ctx, admin, <<"requestRemoveDomain">>, #{<<"domain">> := Domain, <<"hostType">> := HostType}) ->
case mongoose_domain_api:request_delete_domain(Domain, HostType) of
ok ->
DomainObj = #domain{domain = Domain, host_type = HostType, status = deleting},
{ok, #{<<"domain">> => DomainObj, <<"msg">> => <<"Domain disabled and enqueued for deletion">>}};
{error, Error} ->
error_handler(Error, Domain, HostType)
end;
execute(_Ctx, admin, <<"enableDomain">>, #{<<"domain">> := Domain}) ->
case mongoose_domain_api:enable_domain(Domain) of
ok ->
Expand Down
24 changes: 22 additions & 2 deletions src/mongoose_admin_api/mongoose_admin_api_domain.erl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
allowed_methods/2,
to_json/2,
from_json/2,
delete_resource/2]).
delete_resource/2,
delete_completed/2]).

-ignore_xref([to_json/2, from_json/2]).

Expand Down Expand Up @@ -69,6 +70,12 @@ from_json(Req, State) ->
delete_resource(Req, State) ->
try_handle_request(Req, State, fun handle_delete/2).

-spec delete_completed(req(), state()) -> {boolean(), req(), state()}.
delete_completed(Req, #{deletion := in_process} = State) ->
{false, Req, State};
delete_completed(Req, State) ->
{true, Req, State}.

%% Internal functions

handle_get(Req, State) ->
Expand Down Expand Up @@ -128,7 +135,20 @@ handle_delete(Req, State) ->
Bindings = cowboy_req:bindings(Req),
Domain = get_domain(Bindings),
Args = parse_body(Req),
HostType = get_host_type(Args),
handle_delete(Req, State, Domain, Args).

handle_delete(Req, State, Domain, #{host_type := HostType, request := true}) ->
async_delete(Req, State, Domain, HostType);
handle_delete(Req, State, Domain, #{host_type := HostType}) ->
sync_delete(Req, State, Domain, HostType);
handle_delete(_Req, _State, _Domain, #{}) ->
throw_error(bad_request, <<"'host_type' field is missing">>).

async_delete(Req, State, Domain, HostType) ->
mongoose_domain_api:request_delete_domain(Domain, HostType),
{true, Req, State#{deletion => in_process}}.

sync_delete(Req, State, Domain, HostType) ->
case mongoose_domain_api:delete_domain(Domain, HostType) of
ok ->
{true, Req, State};
Expand Down