309 lines
12 KiB
Erlang
309 lines
12 KiB
Erlang
%%%-------------------------------------------------------------------
|
||
%%% @author licheng5
|
||
%%% @copyright (C) 2020, <COMPANY>
|
||
%%% @doc
|
||
%%%
|
||
%%% @end
|
||
%%% Created : 26. 4月 2020 3:36 下午
|
||
%%%-------------------------------------------------------------------
|
||
-module(container_handler).
|
||
-author("licheng5").
|
||
-include("iot.hrl").
|
||
|
||
-define(REQ_TIMEOUT, 10000).
|
||
|
||
%% API
|
||
-export([handle_request/4]).
|
||
|
||
handle_request("GET", "/container/get_all", #{<<"uuid">> := UUID}, _) when is_binary(UUID) ->
|
||
%% 检查ConfigJson是否是合法的json字符串
|
||
case iot_host:get_pid(UUID) of
|
||
undefined ->
|
||
{ok, 200, iot_util:json_error(-1, <<"host not found">>)};
|
||
Pid when is_pid(Pid) ->
|
||
case iot_host:get_containers(Pid) of
|
||
{ok, Ref} ->
|
||
case iot_host:await_reply(Ref, ?REQ_TIMEOUT) of
|
||
{ok, Result} ->
|
||
{ok, 200, iot_util:json_data(Result)};
|
||
{error, Reason} ->
|
||
{ok, 200, iot_util:json_error(-1, Reason)}
|
||
end;
|
||
{error, Reason} when is_binary(Reason) ->
|
||
{ok, 200, iot_util:json_error(-1, Reason)}
|
||
end
|
||
end;
|
||
|
||
%% 下发config.json, 微服务接受后,保存服务配置
|
||
handle_request("POST", "/container/push_config", _,
|
||
#{<<"uuid">> := UUID, <<"container_name">> := ContainerName, <<"config">> := Config, <<"timeout">> := Timeout0})
|
||
when is_binary(UUID), is_binary(ContainerName), is_binary(Config), is_integer(Timeout0) ->
|
||
|
||
%% 检查ConfigJson是否是合法的json字符串
|
||
true = iot_util:is_json(Config),
|
||
case iot_host:get_pid(UUID) of
|
||
undefined ->
|
||
{ok, 200, iot_util:json_error(-1, <<"host not found">>)};
|
||
Pid when is_pid(Pid) ->
|
||
Timeout = Timeout0 * 1000,
|
||
case iot_host:config_container(Pid, ContainerName, Config) of
|
||
{ok, Ref} ->
|
||
case iot_host:await_reply(Ref, Timeout) of
|
||
{ok, Result} ->
|
||
{ok, 200, iot_util:json_data(Result)};
|
||
{error, Reason} ->
|
||
{ok, 200, iot_util:json_error(-1, Reason)}
|
||
end;
|
||
{error, Reason} when is_binary(Reason) ->
|
||
{ok, 200, iot_util:json_error(-1, Reason)}
|
||
end
|
||
end;
|
||
|
||
%% 部署微服务
|
||
handle_request("POST", "/container/deploy", _, #{<<"uuid">> := UUID, <<"task_id">> := TaskId, <<"config">> := Config})
|
||
when is_binary(UUID), is_integer(TaskId), is_map(Config) ->
|
||
|
||
case validate_config(Config) of
|
||
ok ->
|
||
case iot_host:get_pid(UUID) of
|
||
undefined ->
|
||
{ok, 200, iot_util:json_error(404, <<"host not found">>)};
|
||
Pid when is_pid(Pid) ->
|
||
case iot_host:deploy_container(Pid, TaskId, Config) of
|
||
{ok, Ref} ->
|
||
case iot_host:await_reply(Ref, ?REQ_TIMEOUT) of
|
||
{ok, Result} ->
|
||
{ok, 200, iot_util:json_data(Result)};
|
||
{error, Reason} ->
|
||
{ok, 200, iot_util:json_error(400, Reason)}
|
||
end;
|
||
{error, Reason} when is_binary(Reason) ->
|
||
{ok, 200, iot_util:json_error(400, Reason)}
|
||
end
|
||
end;
|
||
{error, Errors} ->
|
||
Reason = iolist_to_binary(lists:join(<<"|||">>, Errors)),
|
||
{ok, 200, iot_util:json_error(400, Reason)}
|
||
end;
|
||
|
||
%% 启动服务
|
||
handle_request("POST", "/container/start", _, #{<<"uuid">> := UUID, <<"container_name">> := ContainerName}) when is_binary(UUID), is_binary(ContainerName) ->
|
||
case iot_host:get_pid(UUID) of
|
||
undefined ->
|
||
{ok, 200, iot_util:json_error(404, <<"host not found">>)};
|
||
Pid when is_pid(Pid) ->
|
||
case iot_host:start_container(Pid, ContainerName) of
|
||
{ok, Ref} ->
|
||
case iot_host:await_reply(Ref, ?REQ_TIMEOUT) of
|
||
{ok, Result} ->
|
||
{ok, 200, iot_util:json_data(Result)};
|
||
{error, Reason} ->
|
||
{ok, 200, iot_util:json_error(400, Reason)}
|
||
end;
|
||
{error, Reason} when is_binary(Reason) ->
|
||
{ok, 200, iot_util:json_error(400, Reason)}
|
||
end
|
||
end;
|
||
|
||
%% 停止服务
|
||
handle_request("POST", "/container/stop", _, #{<<"uuid">> := UUID, <<"container_name">> := ContainerName}) when is_binary(UUID), is_binary(ContainerName) ->
|
||
case iot_host:get_pid(UUID) of
|
||
undefined ->
|
||
{ok, 200, iot_util:json_error(404, <<"host not found">>)};
|
||
Pid when is_pid(Pid) ->
|
||
case iot_host:stop_container(Pid, ContainerName) of
|
||
{ok, Ref} ->
|
||
case iot_host:await_reply(Ref, ?REQ_TIMEOUT) of
|
||
{ok, Result} ->
|
||
{ok, 200, iot_util:json_data(Result)};
|
||
{error, Reason} ->
|
||
{ok, 200, iot_util:json_error(400, Reason)}
|
||
end;
|
||
{error, Reason} when is_binary(Reason) ->
|
||
{ok, 200, iot_util:json_error(400, Reason)}
|
||
end
|
||
end;
|
||
|
||
handle_request("POST", "/container/kill", _, #{<<"uuid">> := UUID, <<"container_name">> := ContainerName}) when is_binary(UUID), is_binary(ContainerName) ->
|
||
case iot_host:get_pid(UUID) of
|
||
undefined ->
|
||
{ok, 200, iot_util:json_error(404, <<"host not found">>)};
|
||
Pid when is_pid(Pid) ->
|
||
case iot_host:kill_container(Pid, ContainerName) of
|
||
{ok, Ref} ->
|
||
case iot_host:await_reply(Ref, ?REQ_TIMEOUT) of
|
||
{ok, Result} ->
|
||
{ok, 200, iot_util:json_data(Result)};
|
||
{error, Reason} ->
|
||
{ok, 200, iot_util:json_error(400, Reason)}
|
||
end;
|
||
{error, Reason} when is_binary(Reason) ->
|
||
{ok, 200, iot_util:json_error(400, Reason)}
|
||
end
|
||
end;
|
||
|
||
%% 删除容器
|
||
handle_request("POST", "/container/remove", _, #{<<"uuid">> := UUID, <<"container_name">> := ContainerName}) when is_binary(UUID), is_binary(ContainerName) ->
|
||
case iot_host:get_pid(UUID) of
|
||
undefined ->
|
||
{ok, 200, iot_util:json_error(404, <<"host not found">>)};
|
||
Pid when is_pid(Pid) ->
|
||
case iot_host:remove_container(Pid, ContainerName) of
|
||
{ok, Ref} ->
|
||
case iot_host:await_reply(Ref, ?REQ_TIMEOUT) of
|
||
{ok, Result} ->
|
||
{ok, 200, iot_util:json_data(Result)};
|
||
{error, Reason} ->
|
||
{ok, 200, iot_util:json_error(400, Reason)}
|
||
end;
|
||
{error, Reason} when is_binary(Reason) ->
|
||
{ok, 200, iot_util:json_error(400, Reason)}
|
||
end
|
||
end;
|
||
|
||
handle_request(_, Path, _, _) ->
|
||
Path1 = list_to_binary(Path),
|
||
{ok, 200, iot_util:json_error(-1, <<"url: ", Path1/binary, " not found">>)}.
|
||
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
%% helper methods
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
|
||
validate_config(Config) when is_map(Config) ->
|
||
%% 必选参数
|
||
Required = [
|
||
{<<"image">>, binary},
|
||
{<<"container_name">>, binary},
|
||
{<<"command">>, {list, binary}},
|
||
{<<"restart">>, binary}
|
||
],
|
||
|
||
%% 可选参数(附带默认值)
|
||
Optional = [
|
||
{<<"privileged">>, boolean},
|
||
{<<"envs">>, {list, binary}},
|
||
{<<"ports">>, {list, binary}},
|
||
{<<"expose">>, {list, binary}},
|
||
{<<"volumes">>, {list, binary}},
|
||
{<<"networks">>, {list, binary}},
|
||
{<<"labels">>, {map, {binary, binary}}},
|
||
{<<"user">>, binary},
|
||
{<<"working_dir">>, binary},
|
||
{<<"hostname">>, binary},
|
||
{<<"cap_add">>, {list, binary}},
|
||
{<<"cap_drop">>, {list, binary}},
|
||
{<<"devices">>, {list, binary}},
|
||
{<<"mem_limit">>, binary},
|
||
{<<"mem_reservation">>, binary},
|
||
{<<"cpu_shares">>, integer},
|
||
{<<"cpus">>, number},
|
||
{<<"ulimits">>, {map, {binary, binary}}},
|
||
{<<"sysctls">>, {map, {binary, binary}}},
|
||
{<<"tmpfs">>, {list, binary}},
|
||
{<<"extra_hosts">>, {list, binary}},
|
||
{<<"healthcheck">>, {map, {binary, any}}}
|
||
],
|
||
|
||
Errors1 = check_required(Config, Required),
|
||
Errors2 = check_optional(Config, Optional),
|
||
|
||
Errors = Errors1 ++ Errors2,
|
||
case Errors of
|
||
[] ->
|
||
ok;
|
||
_ ->
|
||
{error, lists:map(fun erlang:iolist_to_binary/1, Errors)}
|
||
end.
|
||
|
||
%%------------------------------------------------------------------------------
|
||
%% 校验必选项
|
||
%%------------------------------------------------------------------------------
|
||
check_required(Config, Fields) ->
|
||
lists:foldl(
|
||
fun({Key, Type}, ErrAcc) ->
|
||
case maps:get(Key, Config, undefined) of
|
||
undefined ->
|
||
[io_lib:format("miss requied parameter: ~p", [Key]) | ErrAcc];
|
||
Value ->
|
||
case check_type(Value, Type) of
|
||
true ->
|
||
ErrAcc;
|
||
false ->
|
||
[io_lib:format("required parameter: ~p, type must be: ~p", [Key, type_name(Type)]) | ErrAcc]
|
||
end
|
||
end
|
||
end,
|
||
[], Fields).
|
||
|
||
%%------------------------------------------------------------------------------
|
||
%% 校验可选项(支持默认值填充)
|
||
%%------------------------------------------------------------------------------
|
||
check_optional(Config, Fields) ->
|
||
lists:foldl(
|
||
fun({Key, Type}, ErrAcc) ->
|
||
case maps:get(Key, Config, undefined) of
|
||
undefined ->
|
||
ErrAcc;
|
||
Value ->
|
||
case check_type(Value, Type) of
|
||
true ->
|
||
ErrAcc;
|
||
false ->
|
||
[io_lib:format("optional parameter: ~p, type must be: ~p", [Key, type_name(Type)]) | ErrAcc]
|
||
end
|
||
end
|
||
end,
|
||
[], Fields).
|
||
|
||
%%------------------------------------------------------------------------------
|
||
%% 类型检查辅助函数(binary版)
|
||
%%------------------------------------------------------------------------------
|
||
-spec type_name(tuple() | atom()) -> binary().
|
||
type_name(binary) ->
|
||
<<"string">>;
|
||
type_name(integer) ->
|
||
<<"integer">>;
|
||
type_name(number) ->
|
||
<<"number">>;
|
||
type_name(list) ->
|
||
<<"list">>;
|
||
type_name({list, binary}) ->
|
||
<<"list of string">>;
|
||
type_name({list, number}) ->
|
||
<<"list of number">>;
|
||
type_name({list, integer}) ->
|
||
<<"list of integer">>;
|
||
type_name(map) ->
|
||
<<"map">>;
|
||
type_name({map, {binary, binary}}) ->
|
||
<<"map of string:string">>;
|
||
type_name({map, {binary, any}}) ->
|
||
<<"map of string:any">>;
|
||
type_name(boolean) ->
|
||
<<"boolean">>.
|
||
|
||
-spec check_type(Value :: any(), any()) -> boolean().
|
||
check_type(Value, binary) ->
|
||
is_binary(Value);
|
||
check_type(Value, integer) ->
|
||
is_integer(Value);
|
||
check_type(Value, number) ->
|
||
is_number(Value);
|
||
check_type(Value, list) when is_list(Value) ->
|
||
true;
|
||
check_type(Value, {list, binary}) when is_list(Value) ->
|
||
lists:all(fun(E) -> is_binary(E) end, Value);
|
||
check_type(Value, {list, number}) when is_list(Value) ->
|
||
lists:all(fun(E) -> is_number(E) end, Value);
|
||
check_type(Value, {list, integer}) when is_list(Value) ->
|
||
lists:all(fun(E) -> is_integer(E) end, Value);
|
||
check_type(Value, map) when is_map(Value) ->
|
||
true;
|
||
check_type(Value, {map, {binary, binary}}) when is_map(Value) ->
|
||
lists:all(fun({K, V}) -> is_binary(K) andalso is_binary(V) end, maps:to_list(Value));
|
||
check_type(Value, {map, {binary, any}}) when is_map(Value) ->
|
||
lists:all(fun({K, _}) -> is_binary(K) end, maps:to_list(Value));
|
||
check_type(Value, boolean) ->
|
||
is_boolean(Value);
|
||
check_type(_, _) ->
|
||
false. |