diff --git a/apps/roster/src/processes/job.erl b/apps/roster/src/processes/job.erl index 6418d14de4cb407113f435bb2acc899182f9c4cf..aa83e085ede4a7f9ed965675deaa37a4068de6e6 100644 --- a/apps/roster/src/processes/job.erl +++ b/apps/roster/src/processes/job.erl @@ -2,6 +2,7 @@ %-include_lib("kvs/include/feed.hrl"). -include("roster.hrl"). -include_lib("kvs/include/user.hrl"). +-include_lib("kernel/include/logger.hrl"). -compile(export_all). -define(QUANT, 500). @@ -11,6 +12,7 @@ def(J) -> job_process:definition(J). action({request,'Init'}, Proc) -> + ?LOG_INFO("Action Init"), io:format("Action Init~n"), {reply,Proc}; @@ -22,10 +24,12 @@ action({request,'Init'}, Proc) -> %% false -> {reply,'Update',Proc} end; action({request,'Stop'}, Proc) -> - io:format("Stop Process~n"), - {reply,'Action',Proc}; + ?LOG_INFO("Stop Process"), + io:format("Stop Process~n"), + {reply,'Action',Proc}; action({request,'Timeout'}, #process{id=_Id}=Proc) -> + ?LOG_INFO("Timeout Process"), io:format("Timeout Process~n"), %#process{options=Opts}=bpe:load(Id), case bpe:doc(#'Schedule'{},Proc) of @@ -41,6 +45,7 @@ action({request,'Timeout'}, #process{id=_Id}=Proc) -> %% {reply,Proc}; action({request,'Action'}, #process{}=Proc) -> + ?LOG_INFO("Action Process"), io:format("Action Process ~n"), %C= proplists:get_value(send_pid,Opts,undefined), CTimeout= case lists:keytake(timeoutEvent,1,bpe:events(Proc)) of diff --git a/apps/roster/src/rest/rest_cowboy_handler.erl b/apps/roster/src/rest/rest_cowboy_handler.erl index 942a84ecf7e701f4d6aa7be63934841db0d8fc39..7668463d18ccc5e2b1d2d78786ff9ab479a92da2 100644 --- a/apps/roster/src/rest/rest_cowboy_handler.erl +++ b/apps/roster/src/rest/rest_cowboy_handler.erl @@ -55,6 +55,7 @@ routes() -> , {"/cri/bubbles", rest_cowboy_cri_handler, basic_state(cri_bubble)} , {"/test/", rest_cowboy_test_handler, basic_state(test_api)} , {"/link/:link_id/room_id", rest_cowboy_link_handler, basic_state(link_room)} + , {"/health/", rest_cowboy_health_handler, basic_state(health)} , {"/api/v1/groups/:room_id/messages/csv", rest_cowboy_csv_handler, token_state(groups_csv)} , {"/api/v1/chats/:phone_id/messages/csv", diff --git a/apps/roster/src/rest/rest_cowboy_health_handler.erl b/apps/roster/src/rest/rest_cowboy_health_handler.erl new file mode 100644 index 0000000000000000000000000000000000000000..a53335a3216b1920d8398ed0a8615f2e8ad11576 --- /dev/null +++ b/apps/roster/src/rest/rest_cowboy_health_handler.erl @@ -0,0 +1,111 @@ +%%%------------------------------------------------------------------- +%%% @doc Cowboy handler for /health/ endpoint +%%% +%%% @end +%%%------------------------------------------------------------------- +-module(rest_cowboy_health_handler). +-include_lib("kernel/include/logger.hrl"). + +%% Cowboy callbacks +-export([ allowed_methods/2 + , content_types_provided/2 + , init/2 + , is_authorized/2 + ]). + +%% Custom callbacks +-export([ health_to_json/2 + ]). + + +%%%=================================================================== +%%% API +%%%=================================================================== + +init(#{path := Path, method := Method, qs := QS} = Req, #{endpoint := health} = State) -> + Headers = cowboy_req:headers(Req), + CT = maps:get(<<"content-type">>, Headers, <<"no-content-type">>), + ?LOG_INFO("~s:~s:~s ~p", [Method, Path, CT, QS]), + {cowboy_rest, Req, State}. + +is_authorized(Req, State) -> + rest_cowboy_handler:is_authorized(Req, State). + +allowed_methods(Req, State) -> + {[<<"GET">>], Req, State}. + +content_types_provided(Req, State) -> + {[{<<"application/json">>, health_to_json} + ], Req, State}. + +health_to_json(Req, State) -> + Status = #{ db_state => db_state() + , emq_state => emq_state() + , roster_state => roster_state() + , release_version => release_version() + }, + Content = jsx:encode(Status#{accept_traffic => accept_traffic(Status)}), + {Content, Req, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + +db_state() -> + case mnesia:system_info(is_running) of + yes -> + case mnesia:system_info(local_tables) of + [] -> + not_ready; + Tables -> + case mnesia:wait_for_tables(Tables, 0) of + ok -> ready; + {timeout, _} -> not_ready + end + end; + starting -> + not_ready; + stopping -> + not_ready; + no -> + not_ready + end. + +roster_state() -> + %%TODO: Should probably be a deeper check. + case lists:keymember(roster, 1, application:which_applications()) of + false -> + not_ready; + true -> + ready + end. + +emq_state() -> + %%TODO: Should probably be a deeper check. + case emqttd:is_running(node()) of + false -> not_ready; + true -> ready + end. + +accept_traffic(#{ db_state := ready + , emq_state := ready + , roster_state := ready + }) -> + case application:get_env(roster, health_endpoint_accept_traffic, undefined) of + undefined -> true; + false -> false; + true -> true + end; +accept_traffic(#{ db_state := _ + , emq_state := _ + , roster_state := _ + }) -> + false. + +release_version() -> + case release_handler:which_releases(permanent) of + [{"server", VsnString, _Libs, permanent}] -> + iolist_to_binary(VsnString); + _ -> + <<"unknown">> + end. diff --git a/spec/nynja_rest.yaml b/spec/nynja_rest.yaml index 85b079defdd5380cfbfb4236d0928b0098789c5c..5f2123dce8d8e0064a2f2784ce47aadb8adccac5 100644 --- a/spec/nynja_rest.yaml +++ b/spec/nynja_rest.yaml @@ -49,6 +49,8 @@ paths: /sessions: get: operationId: 'GetSessions' + security: + - basicAuth:[] parameters: - in: query name: phone @@ -747,7 +749,8 @@ paths: description: "Not found" content: text/plain: - content: + schema: + type: string application/json: schema: type: object @@ -961,6 +964,8 @@ paths: get: operationId: 'ChatsCSV' description: 'Get csv of all chats to user with phone_id (Authentication gives from is)' + security: + - bearerAuth: [] parameters: - in: path name: phone_id @@ -1052,6 +1057,38 @@ paths: content: application/json: + /health: + get: + operationId: 'GetHealth' + description: 'Get health parameters' + parameters: + responses: + '200': + content: + application/json: + schema: + type: object + properties: + accept_traffic: + type: boolean + description: 'Ready to accept traffic' + db_state: + type: string + description: 'ready | not_ready' + emq_state: + type: string + description: 'ready | not_ready' + roster_state: + type: string + description: 'ready | not_ready' + release_version: + type: string + '401': + description: 'Unauhorized' + content: + text/plain: + schema: + type: string components: schemas: @@ -1075,7 +1112,7 @@ components: properties: created: type: string - formst: date-time + format: date-time phone: type: integer @@ -1095,3 +1132,34 @@ components: RoomId: type: object + + responses: + NotFound: + description: "Not found" + content: + text/plain: + schema: + type: string + application/json: + schema: + type: object + properties: + status: + type: string + data: + type: string + Unauthorized: + description: Unauthorized + content: + test/plain: + schema: + type: string + + securitySchemes: + bearerAuth: # arbitrary name for the security scheme + type: http + scheme: bearer + bearerFormat: JWT + basicAuth: # <-- arbitrary name for the security scheme + type: http + scheme: basic diff --git a/sys.config b/sys.config index ac21587ec62332b27064b6b7175d270759b4f80d..a50aeb27c92ce4a2d6559cc2484d00c6289f330c 100644 --- a/sys.config +++ b/sys.config @@ -41,6 +41,7 @@ {review,[{host,"ns.synrc.com"}]}, {roster, [ + {health_endpoint_accept_traffic, true}, {freeze_time, 1000}, {upload,"./storage"}, {db_backup, "./backup"}, diff --git a/test/api_nynja.erl b/test/api_nynja.erl index bf858c12122fbe45448d89370ecec4f64c62f51e..a153e8b3b11f61dafdad9810f9b0a696a587a02a 100644 --- a/test/api_nynja.erl +++ b/test/api_nynja.erl @@ -1,6 +1,6 @@ %% coding: latin-1 %% This code is generated from ../server/spec/nynja_rest.yaml -%% on 2020-05-11 8:39:07 UTC +%% on 2020-05-13 8:31:46 UTC %% Using openapi rebar3 plugin version: #6ad56be %% Do not manually change this code! %% @@ -32,7 +32,7 @@ definitions() -> {"/components/schemas/PhoneEntry", #{<<"properties">> => #{<<"created">> => - #{<<"formst">> => <<"date-time">>,<<"type">> => <<"string">>}, + #{<<"format">> => <<"date-time">>,<<"type">> => <<"string">>}, <<"phone">> => #{<<"type">> => <<"integer">>}}, <<"type">> => <<"object">>}}, {"/components/schemas/Session", @@ -156,7 +156,15 @@ operations() -> <<"status">> => #{<<"type">> => <<"integer">>}}, <<"type">> => <<"object">>}}}], - 400 => [{"text/plain",undefined}], + 400 => + [{"application/json", + #{schema => + #{<<"properties">> => + #{<<"data">> => #{<<"type">> => <<"string">>}, + <<"status">> => + #{<<"type">> => <<"string">>}}, + <<"type">> => <<"object">>}}}, + {"text/plain",#{schema => #{<<"type">> => <<"string">>}}}], 401 => [{"text/plain",undefined}], 403 => [{"text/plain",undefined}]}, tags => []}, @@ -191,6 +199,35 @@ operations() -> #{method => get,parameters => [],path => <<"/fake_numbers">>, responses => #{200 => [{"text/html",undefined}]}, tags => []}, + 'GetHealth' => + #{method => get,parameters => [],path => <<"/health">>, + responses => + #{200 => + [{"application/json", + #{schema => + #{<<"properties">> => + #{<<"accept_traffic">> => + #{<<"description">> => + <<"Ready to accept traffic">>, + <<"type">> => <<"boolean">>}, + <<"db_state">> => + #{<<"description">> => + <<"ready | not_ready">>, + <<"type">> => <<"string">>}, + <<"emq_state">> => + #{<<"description">> => + <<"ready | not_ready">>, + <<"type">> => <<"string">>}, + <<"release_version">> => + #{<<"type">> => <<"string">>}, + <<"roster_state">> => + #{<<"description">> => + <<"ready | not_ready">>, + <<"type">> => <<"string">>}}, + <<"type">> => <<"object">>}}}], + 401 => + [{"text/plain",#{schema => #{<<"type">> => <<"string">>}}}]}, + tags => []}, 'GetMembersCRIRoom' => #{method => get, parameters => diff --git a/test/server_SUITE.erl b/test/server_SUITE.erl index c9cb0e21a8c8debd75d2ac2db3d1409b82828ecc..52c6bf23fda4242444dbc1b5dc366a3b97b1119e 100644 --- a/test/server_SUITE.erl +++ b/test/server_SUITE.erl @@ -32,6 +32,7 @@ , room_chat_message_errors/1 , call_bubble_message/1 , double_link_profile/1 + , health_endpoint_test/1 ]). all() -> @@ -45,7 +46,8 @@ groups() -> p2p_chat_message_get, p2p_chat_message_get_errors, p2p_chat_message_post, p2p_chat_message_post_errors, room_chat_message, room_chat_message_errors, - create_and_join_room_cri, call_bubble_message + create_and_join_room_cri, call_bubble_message, + health_endpoint_test ]}, {regression, [double_link_profile]} ]. @@ -847,6 +849,25 @@ double_link_profile(Cfg) -> ok. +health_endpoint_test(Cfg) -> + %% No basic auth + {ok, 401, _} + = request('GetHealth', + #{}, Cfg), + + %% Correct response + {ok, 200, #{ db_state := _ + , emq_state := _ + , roster_state := _ + , accept_traffic := _ + , release_version := _ + }} + = request('GetHealth', + #{}, [basic_auth()|Cfg]), + + ok. + + %%% ------------- helpers --------------------- register_fresh_user() ->