120 lines
4.3 KiB
Erlang
120 lines
4.3 KiB
Erlang
%%%-------------------------------------------------------------------
|
|
%%% @doc One container deploy feedback stream from iot.
|
|
%%% @end
|
|
%%%-------------------------------------------------------------------
|
|
-module(efka_iot_deploy_stream).
|
|
|
|
-export([start_stream/1]).
|
|
-export([run/1]).
|
|
|
|
-define(REQUEST_TIMEOUT, 10000).
|
|
-define(MAX_REQUEST_BYTES, 16 * 1024 * 1024).
|
|
|
|
-type stream_id() :: pos_integer().
|
|
|
|
-spec start_stream(StreamId :: stream_id()) -> {ok, {pid(), reference()}}.
|
|
start_stream(StreamId) when is_integer(StreamId), StreamId > 0 ->
|
|
{ok, spawn_monitor(?MODULE, run, [StreamId])}.
|
|
|
|
-spec run(StreamId :: stream_id()) -> ok.
|
|
run(StreamId) when is_integer(StreamId), StreamId > 0 ->
|
|
try run0(StreamId) of
|
|
ok ->
|
|
ok
|
|
catch
|
|
Class:Reason:Stack ->
|
|
logger:warning("[efka_iot_deploy_stream] stream_id: ~p crashed, class: ~p, reason: ~p, stack: ~p",
|
|
[StreamId, Class, Reason, Stack]),
|
|
send_error_and_close(StreamId, iolist_to_binary(io_lib:format("~p:~p", [Class, Reason]))),
|
|
ok
|
|
after
|
|
efka_iot_client:stream_done(StreamId)
|
|
end.
|
|
|
|
-spec run0(stream_id()) -> ok.
|
|
run0(StreamId) ->
|
|
efka_iot_client:send_stream(StreamId, opened),
|
|
case receive_request(StreamId, <<>>) of
|
|
{ok, Request} ->
|
|
handle_request(StreamId, Request);
|
|
{error, reset} ->
|
|
ok;
|
|
{error, Reason} ->
|
|
send_error_and_close(StreamId, reason_to_binary(Reason))
|
|
end.
|
|
|
|
-spec receive_request(stream_id(), binary()) -> {ok, binary()} | {error, term()}.
|
|
receive_request(StreamId, Acc) ->
|
|
receive
|
|
{stream, StreamId, {data, Data}} when is_binary(Data) ->
|
|
NAcc = <<Acc/binary, Data/binary>>,
|
|
case byte_size(NAcc) =< ?MAX_REQUEST_BYTES of
|
|
true ->
|
|
receive_request(StreamId, NAcc);
|
|
false ->
|
|
{error, request_too_large}
|
|
end;
|
|
{stream, StreamId, fin} ->
|
|
{ok, Acc};
|
|
{stream, StreamId, {reset, _Reason}} ->
|
|
{error, reset};
|
|
Info ->
|
|
logger:debug("[efka_iot_deploy_stream] stream_id: ~p ignore unknown info: ~p", [StreamId, Info]),
|
|
receive_request(StreamId, Acc)
|
|
after ?REQUEST_TIMEOUT ->
|
|
{error, request_timeout}
|
|
end.
|
|
|
|
-spec handle_request(stream_id(), binary()) -> ok.
|
|
handle_request(StreamId, RequestBin) ->
|
|
case decode_request(RequestBin) of
|
|
{ok, TaskId, Params} ->
|
|
deploy(StreamId, TaskId, Params);
|
|
{error, Reason} ->
|
|
send_error_and_close(StreamId, reason_to_binary(Reason))
|
|
end.
|
|
|
|
-spec decode_request(binary()) -> {ok, non_neg_integer(), map()} | {error, term()}.
|
|
decode_request(RequestBin) ->
|
|
try json:decode(RequestBin) of
|
|
#{<<"task_id">> := TaskId, <<"params">> := Params}
|
|
when is_integer(TaskId), TaskId >= 0, is_map(Params) ->
|
|
{ok, TaskId, Params};
|
|
_ ->
|
|
{error, invalid_deploy_request}
|
|
catch
|
|
Class:Reason ->
|
|
{error, {bad_json, Class, Reason}}
|
|
end.
|
|
|
|
-spec deploy(stream_id(), non_neg_integer(), map()) -> ok.
|
|
deploy(StreamId, TaskId, Params) ->
|
|
try
|
|
ContainerName = maps:get(<<"container_name">>, Params),
|
|
{ok, RootDir} = docker_helper:root_dir(),
|
|
{ok, ContainerDir} = docker_helper:ensure_container_dir(RootDir, ContainerName),
|
|
docker_deployer:deploy(TaskId, ContainerDir, Params, {stream, StreamId})
|
|
catch
|
|
Class:Reason ->
|
|
Error = iolist_to_binary(io_lib:format("deploy stream failed: ~p:~p", [Class, Reason])),
|
|
send_error_and_close(StreamId, Error)
|
|
end.
|
|
|
|
-spec send_error_and_close(stream_id(), binary()) -> ok.
|
|
send_error_and_close(StreamId, Reason) ->
|
|
ok = efka_iot_client:send_stream(StreamId, {data, iolist_to_binary(json:encode(#{
|
|
<<"type">> => <<"error">>,
|
|
<<"message">> => Reason
|
|
}))}),
|
|
ok = efka_iot_client:send_stream(StreamId, {data, iolist_to_binary(json:encode(#{
|
|
<<"type">> => <<"close">>,
|
|
<<"reason">> => <<"fail">>
|
|
}))}),
|
|
efka_iot_client:send_stream(StreamId, fin).
|
|
|
|
-spec reason_to_binary(term()) -> binary().
|
|
reason_to_binary(Reason) when is_binary(Reason) ->
|
|
Reason;
|
|
reason_to_binary(Reason) ->
|
|
unicode:characters_to_binary(io_lib:format("~p", [Reason])).
|