%%%------------------------------------------------------------------- %%% @author licheng5 %%% @copyright (C) 2020, %%% @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.