fix protocol
This commit is contained in:
parent
6a51ce8627
commit
0e7bee5188
@ -40,8 +40,8 @@
|
||||
socket :: undefined | ssl:sslsocket(),
|
||||
outbox :: efka_iot_outbox:outbox(),
|
||||
streams = #{},
|
||||
%% 保存当前auth请求的ref,用来建立auth请求和响应的对应关系
|
||||
auth_ref = undefined :: undefined | pos_integer(),
|
||||
%% 保存当前 auth 请求的 packet id,用来建立 auth 请求和响应的对应关系
|
||||
auth_pkt_id = undefined :: undefined | pos_integer(),
|
||||
next_pkt_id = 1 :: pos_integer(),
|
||||
ping_timer_ref = undefined :: undefined | reference(),
|
||||
dropped_message_count = 0 :: non_neg_integer()
|
||||
@ -195,7 +195,7 @@ handle_event(info, {timeout, _, create_transport}, ?STATE_DISCONNECTED, State =
|
||||
AuthPacket = auth_packet(PktId),
|
||||
ok = ssl:send(Socket, AuthPacket),
|
||||
logger:debug("[efka_iot_client] send auth request, packet_id: ~p", [PktId]),
|
||||
{next_state, ?STATE_AUTH, State#state{socket = Socket, auth_ref = PktId, next_pkt_id = PktId + 1}, [{state_timeout, 5000, auth_timeout}]};
|
||||
{next_state, ?STATE_AUTH, State#state{socket = Socket, auth_pkt_id = PktId, next_pkt_id = PktId + 1}, [{state_timeout, 5000, auth_timeout}]};
|
||||
{error, _Reason} ->
|
||||
schedule_reconnect(),
|
||||
{keep_state, State#state{socket = undefined}}
|
||||
@ -205,7 +205,7 @@ handle_event(state_timeout, auth_timeout, ?STATE_AUTH, State = #state{socket = S
|
||||
logger:debug("[efka_iot_client] auth request timeout"),
|
||||
disconnect(Socket),
|
||||
schedule_reconnect(),
|
||||
{next_state, ?STATE_DISCONNECTED, State#state{socket = undefined, auth_ref = undefined}};
|
||||
{next_state, ?STATE_DISCONNECTED, State#state{socket = undefined, auth_pkt_id = undefined}};
|
||||
|
||||
handle_event(info, {timeout, TimerRef, ssl_ping}, ?STATE_ACTIVATED, State = #state{socket = Socket, ping_timer_ref = TimerRef}) ->
|
||||
Packet = encode_message_frame(ping),
|
||||
@ -217,7 +217,7 @@ handle_event(info, {timeout, TimerRef, ssl_ping}, ?STATE_ACTIVATED, State = #sta
|
||||
disconnect(Socket),
|
||||
schedule_reconnect(),
|
||||
NState = close_all_streams({send_ping_failed, Reason}, State),
|
||||
{next_state, ?STATE_DISCONNECTED, NState#state{socket = undefined, auth_ref = undefined, ping_timer_ref = undefined}}
|
||||
{next_state, ?STATE_DISCONNECTED, NState#state{socket = undefined, auth_pkt_id = undefined, ping_timer_ref = undefined}}
|
||||
end;
|
||||
handle_event(info, {timeout, _TimerRef, ssl_ping}, _StateName, State) ->
|
||||
{keep_state, State};
|
||||
@ -254,7 +254,7 @@ handle_event(info, {ssl, Socket, PacketBin}, _, State = #state{socket = Socket})
|
||||
cancel_ssl_ping(State),
|
||||
schedule_reconnect(),
|
||||
NState = close_all_streams(bad_packet, State),
|
||||
{next_state, ?STATE_DISCONNECTED, NState#state{socket = undefined, auth_ref = undefined, ping_timer_ref = undefined}}
|
||||
{next_state, ?STATE_DISCONNECTED, NState#state{socket = undefined, auth_pkt_id = undefined, ping_timer_ref = undefined}}
|
||||
end;
|
||||
handle_event(info, {ssl_error, Socket, Reason}, _, State = #state{}) ->
|
||||
logger:debug("[efka_iot_client] ssl error: ~p", [Reason]),
|
||||
@ -262,14 +262,14 @@ handle_event(info, {ssl_error, Socket, Reason}, _, State = #state{}) ->
|
||||
cancel_ssl_ping(State),
|
||||
schedule_reconnect(),
|
||||
NState = close_all_streams(ssl_error, State),
|
||||
{next_state, ?STATE_DISCONNECTED, NState#state{socket = undefined, auth_ref = undefined, ping_timer_ref = undefined}};
|
||||
{next_state, ?STATE_DISCONNECTED, NState#state{socket = undefined, auth_pkt_id = undefined, ping_timer_ref = undefined}};
|
||||
handle_event(info, {ssl_closed, Socket}, _, State = #state{}) ->
|
||||
logger:debug("[efka_iot_client] ssl closed"),
|
||||
disconnect(Socket),
|
||||
cancel_ssl_ping(State),
|
||||
schedule_reconnect(),
|
||||
NState = close_all_streams(ssl_closed, State),
|
||||
{next_state, ?STATE_DISCONNECTED, NState#state{socket = undefined, auth_ref = undefined, ping_timer_ref = undefined}};
|
||||
{next_state, ?STATE_DISCONNECTED, NState#state{socket = undefined, auth_pkt_id = undefined, ping_timer_ref = undefined}};
|
||||
|
||||
handle_event(info, {'DOWN', MonitorRef, process, WorkerPid, Reason}, _StateName, State = #state{streams = Streams}) ->
|
||||
case take_stream_by_monitor(MonitorRef, WorkerPid, Streams) of
|
||||
@ -282,7 +282,7 @@ handle_event(info, {'DOWN', MonitorRef, process, WorkerPid, Reason}, _StateName,
|
||||
undefined ->
|
||||
{keep_state, State#state{streams = NStreams}};
|
||||
Socket ->
|
||||
Packet = encode_stream_frame(StreamId, {reset, safe_term({worker_down, Reason})}),
|
||||
Packet = encode_stream_frame(StreamId, {reset, {worker_down, Reason}}),
|
||||
ok = ssl:send(Socket, Packet),
|
||||
{keep_state, State#state{streams = NStreams}}
|
||||
end
|
||||
@ -291,25 +291,24 @@ handle_event(info, {'DOWN', MonitorRef, process, WorkerPid, Reason}, _StateName,
|
||||
%%% 处理内部消息。TLS 收到的数据先经过 protobuf 解码,再由这里按本地事件分发。
|
||||
|
||||
%% 容器管理命令由 iot 发起,使用 command/command_response 语义。
|
||||
handle_event(internal, #'Command'{packet_id = Ref, body = {container, ContainerCommand}}, ?STATE_ACTIVATED, State = #state{socket = Socket}) ->
|
||||
handle_container_command(Ref, ContainerCommand, Socket),
|
||||
handle_event(internal, #'Command'{packet_id = PktId, body = {container, ContainerCommand}}, ?STATE_ACTIVATED, State = #state{socket = Socket}) ->
|
||||
handle_container_command(PktId, ContainerCommand, Socket),
|
||||
{keep_state, State};
|
||||
handle_event(internal, #'Command'{packet_id = Ref, body = {container, ContainerCommand}}, _StateName, State = #state{socket = Socket}) ->
|
||||
Request = decode_container_command(ContainerCommand),
|
||||
logger:notice("[efka_iot_client] get an invalid command: ~p, agent invalid", [Request]),
|
||||
send_container_response(Socket, Ref, {error, <<"agent invalid">>}),
|
||||
handle_event(internal, #'Command'{packet_id = PktId, body = {container, ContainerCommand}}, _StateName, State = #state{socket = Socket}) ->
|
||||
logger:notice("[efka_iot_client] get an invalid command: ~p, agent invalid", [ContainerCommand]),
|
||||
send_container_response(Socket, PktId, {error, <<"agent invalid">>}),
|
||||
{keep_state, State};
|
||||
|
||||
%% 处理response
|
||||
handle_event(internal, #'Response'{packet_id = AuthRef, body = {auth_response, #'Response.AuthResponse'{}}}, ?STATE_AUTH, State = #state{auth_ref = AuthRef}) ->
|
||||
handle_event(internal, #'Response'{packet_id = AuthPktId, body = {auth_response, #'Response.AuthResponse'{}}}, ?STATE_AUTH, State = #state{auth_pkt_id = AuthPktId}) ->
|
||||
logger:debug("[efka_iot_client] auth success"),
|
||||
State1 = schedule_ssl_ping(State#state{auth_ref = undefined}),
|
||||
State1 = schedule_ssl_ping(State#state{auth_pkt_id = undefined}),
|
||||
{next_state, ?STATE_ACTIVATED, State1, [{next_event, info, flush_cache}]};
|
||||
handle_event(internal, #'Response'{packet_id = AuthRef, body = {error, #'Response.Error'{reason = Reason}}}, ?STATE_AUTH, State = #state{socket = Socket, auth_ref = AuthRef}) ->
|
||||
handle_event(internal, #'Response'{packet_id = AuthPktId, body = {error, #'Response.Error'{reason = Reason}}}, ?STATE_AUTH, State = #state{socket = Socket, auth_pkt_id = AuthPktId}) ->
|
||||
logger:debug("[efka_iot_client] auth failed, reason: ~p", [Reason]),
|
||||
disconnect(Socket),
|
||||
schedule_reconnect(),
|
||||
{next_state, ?STATE_DISCONNECTED, State#state{socket = undefined, auth_ref = undefined}};
|
||||
{next_state, ?STATE_DISCONNECTED, State#state{socket = undefined, auth_pkt_id = undefined}};
|
||||
handle_event(internal, #'Response'{} = Reply, StateName, State) ->
|
||||
logger:warning("[efka_iot_client] ignore unexpected response in state ~p: ~p", [StateName, Reply]),
|
||||
{keep_state, State};
|
||||
@ -372,14 +371,14 @@ code_change(_OldVsn, StateName, State = #state{}, _Extra) ->
|
||||
%%%===================================================================
|
||||
|
||||
-spec auth_packet(pos_integer()) -> binary().
|
||||
auth_packet(Ref) when is_integer(Ref), Ref > 0 ->
|
||||
auth_packet(PktId) when is_integer(PktId), PktId > 0 ->
|
||||
{ok, AuthInfo} = application:get_env(efka, auth),
|
||||
UUID = proplists:get_value(uuid, AuthInfo),
|
||||
Token = proplists:get_value(token, AuthInfo),
|
||||
|
||||
Timestamp = efka_util:timestamp(),
|
||||
encode_transport_frame(?CLASS_REQUEST, #'Request'{
|
||||
packet_id = Ref,
|
||||
packet_id = PktId,
|
||||
body = {auth_request, #'Request.AuthRequest'{
|
||||
uuid = list_to_binary(UUID),
|
||||
token = list_to_binary(Token),
|
||||
@ -492,7 +491,7 @@ cancel_timer(TimerRef) ->
|
||||
ok.
|
||||
|
||||
-spec send_container_response(ssl:sslsocket(), pos_integer(), term()) -> ok.
|
||||
send_container_response(Socket, Ref, Reply) ->
|
||||
send_container_response(Socket, PktId, Reply) ->
|
||||
Body = case Reply of
|
||||
ok ->
|
||||
{result, <<"ok">>};
|
||||
@ -504,73 +503,45 @@ send_container_response(Socket, Ref, Reply) ->
|
||||
{result, reply_to_binary(Other)}
|
||||
end,
|
||||
Packet = encode_transport_frame(?CLASS_COMMAND_RESPONSE, #'CommandResponse'{
|
||||
packet_id = Ref,
|
||||
packet_id = PktId,
|
||||
body = Body
|
||||
}),
|
||||
ok = ssl:send(Socket, Packet).
|
||||
|
||||
-spec handle_container_command(pos_integer(), message_pb:'Command.Container'(), ssl:sslsocket()) -> ok.
|
||||
handle_container_command(Ref, ContainerCommand, Socket) ->
|
||||
handle_container_request(Ref, decode_container_command(ContainerCommand), Socket).
|
||||
|
||||
-spec handle_container_request(pos_integer(), map(), ssl:sslsocket()) -> ok.
|
||||
handle_container_request(Ref, #{<<"action">> := <<"list">>}, Socket) ->
|
||||
handle_container_command(PktId, #'Command.Container'{action = {list, #'Command.Container.ContainerList'{}}}, Socket) ->
|
||||
Reply = docker_commands:get_containers(),
|
||||
send_container_response(Socket, Ref, Reply),
|
||||
send_container_response(Socket, PktId, Reply),
|
||||
ok;
|
||||
handle_container_request(Ref, #{<<"action">> := <<"deploy">>, <<"task_id">> := TaskId, <<"params">> := Params}, Socket) ->
|
||||
Reply = docker_deploy_manager:deploy(TaskId, Params),
|
||||
send_container_response(Socket, Ref, Reply),
|
||||
handle_container_command(PktId, #'Command.Container'{action = {deploy, #'Command.Container.ContainerDeploy'{task_id = TaskId, params = Params}}}, Socket) ->
|
||||
Reply = docker_deploy_manager:deploy(TaskId, decode_json_payload(Params)),
|
||||
send_container_response(Socket, PktId, Reply),
|
||||
ok;
|
||||
handle_container_request(Ref, #{<<"action">> := <<"start">>, <<"target">> := Target}, Socket) ->
|
||||
handle_container_command(PktId, #'Command.Container'{action = {start, #'Command.Container.ContainerStart'{target = Target}}}, Socket) ->
|
||||
Reply = docker_commands:start_container(container_target(Target)),
|
||||
send_container_response(Socket, Ref, Reply),
|
||||
send_container_response(Socket, PktId, Reply),
|
||||
ok;
|
||||
handle_container_request(Ref, #{<<"action">> := <<"stop">>, <<"target">> := Target, <<"timeout_seconds">> := TimeoutSeconds}, Socket) ->
|
||||
handle_container_command(PktId, #'Command.Container'{action = {stop, #'Command.Container.ContainerStop'{target = Target, timeout_seconds = TimeoutSeconds}}}, Socket) ->
|
||||
Reply = docker_commands:stop_container(container_target(Target), TimeoutSeconds),
|
||||
send_container_response(Socket, Ref, Reply),
|
||||
send_container_response(Socket, PktId, Reply),
|
||||
ok;
|
||||
handle_container_request(Ref, #{<<"action">> := <<"kill">>, <<"target">> := Target, <<"signal">> := Signal}, Socket) ->
|
||||
handle_container_command(PktId, #'Command.Container'{action = {kill, #'Command.Container.ContainerKill'{target = Target, signal = Signal}}}, Socket) ->
|
||||
Reply = docker_commands:kill_container(container_target(Target), to_binary(Signal)),
|
||||
send_container_response(Socket, Ref, Reply),
|
||||
send_container_response(Socket, PktId, Reply),
|
||||
ok;
|
||||
handle_container_request(Ref, #{<<"action">> := <<"remove">>, <<"target">> := Target, <<"force">> := Force, <<"remove_volumes">> := RemoveVolumes}, Socket) ->
|
||||
handle_container_command(PktId, #'Command.Container'{action = {remove, #'Command.Container.ContainerRemove'{target = Target, force = Force, remove_volumes = RemoveVolumes}}}, Socket) ->
|
||||
Reply = docker_commands:remove_container(container_target(Target), to_bool(Force), to_bool(RemoveVolumes)),
|
||||
send_container_response(Socket, Ref, Reply),
|
||||
send_container_response(Socket, PktId, Reply),
|
||||
ok;
|
||||
handle_container_request(Ref, #{<<"action">> := <<"config">>, <<"target">> := Target, <<"config">> := Config}, Socket) ->
|
||||
handle_container_command(PktId, #'Command.Container'{action = {config, #'Command.Container.ContainerConfig'{target = Target, config = Config}}}, Socket) ->
|
||||
Reply = docker_helper:update_container_config(container_target(Target), iolist_to_binary(Config)),
|
||||
send_container_response(Socket, Ref, Reply),
|
||||
send_container_response(Socket, PktId, Reply),
|
||||
ok;
|
||||
handle_container_request(Ref, Request, Socket) ->
|
||||
logger:notice("[efka_iot_client] get an invalid command: ~p, agent invalid", [Request]),
|
||||
send_container_response(Socket, Ref, {error, <<"agent invalid">>}),
|
||||
handle_container_command(PktId, ContainerCommand, Socket) ->
|
||||
logger:notice("[efka_iot_client] get an invalid command: ~p, agent invalid", [ContainerCommand]),
|
||||
send_container_response(Socket, PktId, {error, <<"agent invalid">>}),
|
||||
ok.
|
||||
|
||||
-spec decode_container_command(message_pb:'Command.Container'()) -> map().
|
||||
decode_container_command(#'Command.Container'{action = {list, #'Command.Container.ContainerList'{}}}) ->
|
||||
#{<<"action">> => <<"list">>};
|
||||
decode_container_command(#'Command.Container'{action = {deploy, #'Command.Container.ContainerDeploy'{task_id = TaskId, params = Params}}}) ->
|
||||
#{<<"action">> => <<"deploy">>, <<"task_id">> => TaskId, <<"params">> => decode_json_payload(Params)};
|
||||
decode_container_command(#'Command.Container'{action = {start, #'Command.Container.ContainerStart'{target = Target}}}) ->
|
||||
#{<<"action">> => <<"start">>, <<"target">> => decode_container_target(Target)};
|
||||
decode_container_command(#'Command.Container'{action = {stop, #'Command.Container.ContainerStop'{target = Target, timeout_seconds = TimeoutSeconds}}}) ->
|
||||
#{<<"action">> => <<"stop">>, <<"target">> => decode_container_target(Target), <<"timeout_seconds">> => TimeoutSeconds};
|
||||
decode_container_command(#'Command.Container'{action = {kill, #'Command.Container.ContainerKill'{target = Target, signal = Signal}}}) ->
|
||||
#{<<"action">> => <<"kill">>, <<"target">> => decode_container_target(Target), <<"signal">> => Signal};
|
||||
decode_container_command(#'Command.Container'{action = {remove, #'Command.Container.ContainerRemove'{target = Target, force = Force, remove_volumes = RemoveVolumes}}}) ->
|
||||
#{<<"action">> => <<"remove">>, <<"target">> => decode_container_target(Target), <<"force">> => Force, <<"remove_volumes">> => RemoveVolumes};
|
||||
decode_container_command(#'Command.Container'{action = {config, #'Command.Container.ContainerConfig'{target = Target, config = Config}}}) ->
|
||||
#{<<"action">> => <<"config">>, <<"target">> => decode_container_target(Target), <<"config">> => Config};
|
||||
decode_container_command(Other) ->
|
||||
#{<<"action">> => <<"invalid">>, <<"payload">> => safe_term(Other)}.
|
||||
|
||||
-spec decode_container_target(message_pb:'Command.Container.ContainerTarget'() | undefined) -> map().
|
||||
decode_container_target(#'Command.Container.ContainerTarget'{name = Name, id = Id}) ->
|
||||
#{<<"name">> => iolist_to_binary(Name), <<"id">> => iolist_to_binary(Id)};
|
||||
decode_container_target(undefined) ->
|
||||
#{<<"name">> => <<>>, <<"id">> => <<>>}.
|
||||
|
||||
-spec decode_json_payload(iodata()) -> term().
|
||||
decode_json_payload(Payload) ->
|
||||
Bin = iolist_to_binary(Payload),
|
||||
@ -661,58 +632,45 @@ demonitor_stream(#stream_state{monitor_ref = MonitorRef}) ->
|
||||
reply_to_binary(Value) when is_binary(Value) ->
|
||||
Value;
|
||||
reply_to_binary(Value) ->
|
||||
SafeValue = safe_term(Value),
|
||||
try iolist_to_binary(Value) of
|
||||
Bin ->
|
||||
Bin
|
||||
catch
|
||||
_:_ ->
|
||||
try iolist_to_binary(json:encode(SafeValue)) of
|
||||
try iolist_to_binary(json:encode(Value)) of
|
||||
JsonBin ->
|
||||
JsonBin
|
||||
catch
|
||||
_:_ ->
|
||||
term_to_binary(SafeValue)
|
||||
unicode:characters_to_binary(io_lib:format("~p", [Value]))
|
||||
end
|
||||
end.
|
||||
|
||||
-spec reason_to_binary(term()) -> binary().
|
||||
reason_to_binary(Reason) when is_binary(Reason) ->
|
||||
Reason;
|
||||
reason_to_binary(Reason) when is_list(Reason) ->
|
||||
iolist_to_binary(Reason);
|
||||
reason_to_binary(Reason) ->
|
||||
term_to_binary(safe_term(Reason)).
|
||||
try iolist_to_binary(Reason) of
|
||||
Bin ->
|
||||
Bin
|
||||
catch
|
||||
_:_ ->
|
||||
unicode:characters_to_binary(io_lib:format("~p", [Reason]))
|
||||
end.
|
||||
|
||||
-spec safe_term(term()) -> term().
|
||||
safe_term(true) ->
|
||||
true;
|
||||
safe_term(false) ->
|
||||
false;
|
||||
safe_term(undefined) ->
|
||||
undefined;
|
||||
safe_term(Value) when is_atom(Value) ->
|
||||
atom_to_binary(Value, utf8);
|
||||
safe_term(Value) when is_map(Value) ->
|
||||
maps:from_list([{safe_term(K), safe_term(V)} || {K, V} <- maps:to_list(Value)]);
|
||||
safe_term(Value) when is_list(Value) ->
|
||||
[safe_term(Item) || Item <- Value];
|
||||
safe_term(Value) when is_tuple(Value) ->
|
||||
list_to_tuple([safe_term(Item) || Item <- tuple_to_list(Value)]);
|
||||
safe_term(Value) ->
|
||||
Value.
|
||||
|
||||
-spec container_target(map()) -> binary().
|
||||
container_target(Target) when is_map(Target) ->
|
||||
NameBin = to_binary(maps:get(<<"name">>, Target, <<>>)),
|
||||
IdBin = to_binary(maps:get(<<"id">>, Target, <<>>)),
|
||||
-spec container_target(message_pb:'Command.Container.ContainerTarget'() | undefined) -> binary().
|
||||
container_target(#'Command.Container.ContainerTarget'{name = Name, id = Id}) ->
|
||||
NameBin = iolist_to_binary(Name),
|
||||
IdBin = iolist_to_binary(Id),
|
||||
case NameBin of
|
||||
<<>> ->
|
||||
true = IdBin =/= <<>>,
|
||||
IdBin;
|
||||
_ ->
|
||||
NameBin
|
||||
end.
|
||||
end;
|
||||
container_target(undefined) ->
|
||||
error(bad_container_target).
|
||||
|
||||
-spec to_binary(binary() | list()) -> binary().
|
||||
to_binary(Value) when is_binary(Value) ->
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user