fix测试用例
This commit is contained in:
parent
835aceb060
commit
72030d55d6
@ -4,8 +4,6 @@
|
||||
|
||||
{dets_dir, "/usr/local/code/tmp/dets/"},
|
||||
|
||||
{upload_dir, "/usr/local/code/tmp/upload/"},
|
||||
|
||||
{tcp_server, [
|
||||
{port, 18088}
|
||||
]},
|
||||
|
||||
50
docs/todo.md
Normal file
50
docs/todo.md
Normal file
@ -0,0 +1,50 @@
|
||||
# TODO
|
||||
|
||||
## 协议层
|
||||
|
||||
### 高优先级
|
||||
|
||||
- 将 `message.proto` 收敛为 `efka` 与 `iot` 共用的一份单一事实来源,避免协议定义漂移和双端重复维护。
|
||||
|
||||
### 中优先级
|
||||
|
||||
- 增强 protobuf 映射和 Docker JSON 生成相关的自动化契约测试。
|
||||
|
||||
## Docker 层
|
||||
|
||||
### 高优先级
|
||||
|
||||
- 将 `docker_commands` 拆分为更清晰的层次:Docker HTTP 客户端、Docker JSON 构造层、以及 efka 本地补丁逻辑。
|
||||
- 明确 `docker_task_reporter` 的投递语义;当前只有内存缓冲,还缺少持久化能力和队列上限控制。
|
||||
|
||||
### 中优先级
|
||||
|
||||
- 丰富 `docker_deploy_manager` 的任务跟踪状态,把部署元数据、耗时、阶段、失败原因等信息纳入统一管理,避免后续再回头重构。
|
||||
- 统一 `docker_container_service` 的返回结构,避免调用方分支处理 `ok | {ok, binary()} | {error, binary()}` 这种混合形式。
|
||||
- 消除 `docker_commands` 中重复的 Docker HTTP 响应解析逻辑。
|
||||
|
||||
### 低优先级
|
||||
|
||||
- 对于像 `docker_events.erl` 这样当前未启用的模块,要么清理掉,要么补充说明其保留原因。
|
||||
|
||||
## 订阅与通道层
|
||||
|
||||
### 高优先级
|
||||
|
||||
- 将 `efka_subscription` 当前基于列表扫描的匹配方式替换为更可扩展的索引结构,例如 trie 或基于 ETS 的索引。
|
||||
|
||||
### 中优先级
|
||||
|
||||
- 继续收敛 `efka_client` 的职责,将传输层状态管理与 request/cast 协议处理进一步拆开。
|
||||
- 评估并落实 `service_channel` 中 `subscribed_topics` 集合的用途;如果只是被动保存状态,则应简化。
|
||||
|
||||
### 低优先级
|
||||
|
||||
- 如果 `efka_client` 的缓存指标量继续增长,可以为缓存刷出增加批量发送或节流机制。
|
||||
|
||||
## 基础设施层
|
||||
|
||||
### 低优先级
|
||||
|
||||
- 如果上传相关能力已经不再属于当前运行时,移除或补充说明残留的启动逻辑,例如 `ensure_upload_dir/0`。
|
||||
- 评估是否将 `efka_logger` 与 OTP `logger` 统一,避免长期维护两套日志链路。
|
||||
@ -11,7 +11,6 @@
|
||||
|
||||
start(_StartType, _StartArgs) ->
|
||||
io:setopts([{encoding, unicode}]),
|
||||
ensure_upload_dir(),
|
||||
%% 加速内存的回收
|
||||
erlang:system_flag(fullsweep_after, 16),
|
||||
start_http_server(),
|
||||
@ -41,13 +40,4 @@ start_http_server() ->
|
||||
],
|
||||
{ok, Pid} = cowboy:start_clear(ws_listener, TransOpts, #{env => #{dispatch => Dispatcher}}),
|
||||
|
||||
logger:debug("[efka_app] websocket server start at: ~p, pid is: ~p", [Port, Pid]).
|
||||
|
||||
ensure_upload_dir() ->
|
||||
{ok, UploadDir} = application:get_env(efka, upload_dir),
|
||||
case filelib:is_dir(UploadDir) of
|
||||
true ->
|
||||
ok;
|
||||
false ->
|
||||
ok = file:make_dir(UploadDir)
|
||||
end.
|
||||
logger:debug("[efka_app] websocket server start at: ~p, pid is: ~p", [Port, Pid]).
|
||||
@ -11,101 +11,285 @@
|
||||
-include("message_pb.hrl").
|
||||
|
||||
%% API
|
||||
-export([test_pull/0, test_commands/0, test_create_container/0]).
|
||||
-export([
|
||||
test_all/0,
|
||||
test_pull/0,
|
||||
test_check_image_exist/0,
|
||||
test_check_image_not_exist/0,
|
||||
test_create_container/0,
|
||||
test_create_container_without_create_options/0,
|
||||
test_create_container_patches_options/0,
|
||||
test_check_container_exist/0,
|
||||
test_check_container_not_exist/0,
|
||||
test_is_container_running/0,
|
||||
test_start_container/0,
|
||||
test_stop_container/0,
|
||||
test_stop_container_with_timeout/0,
|
||||
test_kill_container/0,
|
||||
test_kill_container_with_signal/0,
|
||||
test_remove_container/0,
|
||||
test_remove_container_with_options/0,
|
||||
test_get_containers/0
|
||||
]).
|
||||
|
||||
-define(TEST_IMAGE, <<"docker.1ms.run/library/busybox:latest">>).
|
||||
|
||||
test_all() ->
|
||||
ok = test_pull(),
|
||||
ok = test_check_image_exist(),
|
||||
ok = test_check_image_not_exist(),
|
||||
ok = test_create_container(),
|
||||
ok = test_create_container_without_create_options(),
|
||||
ok = test_create_container_patches_options(),
|
||||
ok = test_check_container_exist(),
|
||||
ok = test_check_container_not_exist(),
|
||||
ok = test_is_container_running(),
|
||||
ok = test_start_container(),
|
||||
ok = test_stop_container(),
|
||||
ok = test_stop_container_with_timeout(),
|
||||
ok = test_kill_container(),
|
||||
ok = test_kill_container_with_signal(),
|
||||
ok = test_remove_container(),
|
||||
ok = test_remove_container_with_options(),
|
||||
ok = test_get_containers().
|
||||
|
||||
test_pull() ->
|
||||
Image = <<"docker.1ms.run/library/nginx:latest">>,
|
||||
docker_commands:pull_image(Image, fun(Msg) -> logger:debug("msg is: ~p", [Msg]) end).
|
||||
ok = docker_commands:pull_image(?TEST_IMAGE, fun(Msg) -> logger:debug("msg is: ~p", [Msg]) end).
|
||||
|
||||
test_commands() ->
|
||||
Id = <<"redpanda-console">>,
|
||||
StopRes = docker_commands:stop_container(Id),
|
||||
logger:debug("stop res: ~p", [StopRes]),
|
||||
StartRes = docker_commands:start_container(Id),
|
||||
logger:debug("start res: ~p", [StartRes]).
|
||||
test_check_image_exist() ->
|
||||
ok = test_pull(),
|
||||
true = docker_commands:check_image_exist(?TEST_IMAGE),
|
||||
ok.
|
||||
|
||||
test_check_image_not_exist() ->
|
||||
false = docker_commands:check_image_exist(<<"docker.1ms.run/library/not-exists-for-efka-tests:latest">>),
|
||||
ok.
|
||||
|
||||
test_create_container() ->
|
||||
Name = test_container_name(<<"create">>),
|
||||
ContainerDir = prepare_container_dir(Name),
|
||||
try
|
||||
ok = test_pull(),
|
||||
{ok, ContainerId} = docker_commands:create_container(ContainerDir, minimal_params(Name)),
|
||||
true = is_binary(ContainerId),
|
||||
ok
|
||||
after
|
||||
cleanup_container(Name)
|
||||
end.
|
||||
|
||||
test_create_container_without_create_options() ->
|
||||
Name = test_container_name(<<"create-default">>),
|
||||
ContainerDir = prepare_container_dir(Name),
|
||||
try
|
||||
ok = test_pull(),
|
||||
{ok, ContainerId} = docker_commands:create_container(ContainerDir, #'ContainerDeployParams'{
|
||||
container_name = Name
|
||||
}),
|
||||
true = is_binary(ContainerId),
|
||||
Inspect = inspect_container_json(Name),
|
||||
assert_patched_defaults(Name, ContainerDir, Inspect),
|
||||
ok
|
||||
after
|
||||
cleanup_container(Name)
|
||||
end.
|
||||
|
||||
test_create_container_patches_options() ->
|
||||
Name = test_container_name(<<"create-patch">>),
|
||||
ContainerDir = prepare_container_dir(Name),
|
||||
Params = #'ContainerDeployParams'{
|
||||
container_name = <<"my_nginx_new1">>,
|
||||
container_name = Name,
|
||||
create = #'DockerCreateOptions'{
|
||||
config = #'DockerContainerConfig'{
|
||||
image = <<"docker.1ms.run/library/nginx:latest">>,
|
||||
cmd = [
|
||||
<<"nginx">>,
|
||||
<<"-g">>,
|
||||
<<"daemon off;">>
|
||||
],
|
||||
entrypoint = [
|
||||
<<"/docker-entrypoint.sh">>
|
||||
],
|
||||
env = [
|
||||
<<"ENV1=val1">>,
|
||||
<<"ENV2=val2">>
|
||||
],
|
||||
volumes = [
|
||||
<<"/data">>,
|
||||
<<"/var/log">>
|
||||
],
|
||||
labels = [
|
||||
{<<"role">>, <<"web">>},
|
||||
{<<"env">>, <<"prod">>}
|
||||
],
|
||||
user = <<"www-data">>,
|
||||
working_dir = <<"/app">>,
|
||||
hostname = <<"myhost">>,
|
||||
exposed_ports = [
|
||||
#'DockerExposedPort'{container_port = 80, protocol = <<"tcp">>},
|
||||
#'DockerExposedPort'{container_port = 443, protocol = <<"tcp">>}
|
||||
],
|
||||
healthcheck = #'Healthcheck'{
|
||||
test = [
|
||||
<<"CMD-SHELL">>,
|
||||
<<"curl -f http://localhost || exit 1">>
|
||||
],
|
||||
interval_ns = 30000000000,
|
||||
timeout_ns = 10000000000,
|
||||
retries = 3
|
||||
}
|
||||
image = ?TEST_IMAGE,
|
||||
cmd = [<<"sleep">>, <<"300">>],
|
||||
env = [<<"EXISTING_ENV=1">>],
|
||||
volumes = [<<"/data">>]
|
||||
},
|
||||
host_config = #'DockerHostConfig'{
|
||||
binds = [
|
||||
<<"/host/data:/data">>,
|
||||
<<"/host/log:/var/log">>
|
||||
],
|
||||
network_mode = <<"bridge">>,
|
||||
restart_policy = #'RestartPolicy'{name = <<"always">>},
|
||||
privileged = true,
|
||||
cap_add = [
|
||||
<<"NET_ADMIN">>
|
||||
],
|
||||
cap_drop = [
|
||||
<<"MKNOD">>
|
||||
],
|
||||
devices = [
|
||||
#'DeviceMapping'{host_path = <<"/dev/snd">>, container_path = <<"/dev/snd">>}
|
||||
],
|
||||
memory = 512 * 1024 * 1024,
|
||||
memory_reservation = 256 * 1024 * 1024,
|
||||
cpu_shares = 512,
|
||||
nano_cpus = 1500000000,
|
||||
ulimits = [
|
||||
#'Ulimit'{name = <<"nofile">>, soft = 1024, hard = 2048}
|
||||
],
|
||||
sysctls = [
|
||||
{<<"net.ipv4.ip_forward">>, <<"1">>}
|
||||
],
|
||||
tmpfs = [
|
||||
{<<"/tmp">>, <<>>}
|
||||
],
|
||||
extra_hosts = [
|
||||
<<"host1:192.168.0.1">>
|
||||
]
|
||||
},
|
||||
networking_config = #'DockerNetworkingConfig'{
|
||||
endpoints = [
|
||||
#'DockerNetworkEndpoint'{name = <<"mynet">>}
|
||||
]
|
||||
binds = [<<"/tmp:/tmp">>]
|
||||
}
|
||||
}
|
||||
},
|
||||
docker_commands:create_container("/usr/local/code/efka/", Params).
|
||||
try
|
||||
ok = test_pull(),
|
||||
{ok, _ContainerId} = docker_commands:create_container(ContainerDir, Params),
|
||||
Inspect = inspect_container_json(Name),
|
||||
assert_patched_defaults(Name, ContainerDir, Inspect),
|
||||
#{<<"Config">> := #{<<"Env">> := Env, <<"Volumes">> := Volumes},
|
||||
<<"HostConfig">> := #{<<"Binds">> := Binds}} = Inspect,
|
||||
true = lists:member(<<"EXISTING_ENV=1">>, Env),
|
||||
true = maps:is_key(<<"/data">>, Volumes),
|
||||
true = lists:member(<<"/tmp:/tmp">>, Binds),
|
||||
ok
|
||||
after
|
||||
cleanup_container(Name)
|
||||
end.
|
||||
|
||||
test_check_container_exist() ->
|
||||
Name = test_container_name(<<"exist">>),
|
||||
with_created_container(Name, fun(_ContainerDir, _ContainerId) ->
|
||||
true = docker_commands:check_container_exist(Name),
|
||||
ok
|
||||
end).
|
||||
|
||||
test_check_container_not_exist() ->
|
||||
false = docker_commands:check_container_exist(test_container_name(<<"missing">>)),
|
||||
ok.
|
||||
|
||||
test_is_container_running() ->
|
||||
Name = test_container_name(<<"running">>),
|
||||
with_created_container(Name, fun(_ContainerDir, ContainerId) ->
|
||||
false = docker_commands:is_container_running(ContainerId),
|
||||
ok = docker_commands:start_container(Name),
|
||||
true = docker_commands:is_container_running(ContainerId),
|
||||
ok = docker_commands:stop_container(Name),
|
||||
false = docker_commands:is_container_running(ContainerId),
|
||||
ok
|
||||
end).
|
||||
|
||||
test_start_container() ->
|
||||
Name = test_container_name(<<"start">>),
|
||||
with_created_container(Name, fun(_ContainerDir, ContainerId) ->
|
||||
ok = docker_commands:start_container(Name),
|
||||
true = docker_commands:is_container_running(ContainerId),
|
||||
ok
|
||||
end).
|
||||
|
||||
test_stop_container() ->
|
||||
Name = test_container_name(<<"stop">>),
|
||||
with_started_container(Name, fun(_ContainerDir, ContainerId) ->
|
||||
ok = docker_commands:stop_container(Name),
|
||||
false = docker_commands:is_container_running(ContainerId),
|
||||
ok
|
||||
end).
|
||||
|
||||
test_stop_container_with_timeout() ->
|
||||
Name = test_container_name(<<"stop-timeout">>),
|
||||
with_started_container(Name, fun(_ContainerDir, ContainerId) ->
|
||||
ok = docker_commands:stop_container(Name, 1),
|
||||
false = docker_commands:is_container_running(ContainerId),
|
||||
ok
|
||||
end).
|
||||
|
||||
test_kill_container() ->
|
||||
Name = test_container_name(<<"kill">>),
|
||||
with_started_container(Name, fun(_ContainerDir, ContainerId) ->
|
||||
ok = docker_commands:kill_container(Name),
|
||||
timer:sleep(200),
|
||||
false = docker_commands:is_container_running(ContainerId),
|
||||
ok
|
||||
end).
|
||||
|
||||
test_kill_container_with_signal() ->
|
||||
Name = test_container_name(<<"kill-signal">>),
|
||||
with_started_container(Name, fun(_ContainerDir, ContainerId) ->
|
||||
ok = docker_commands:kill_container(Name, <<"SIGKILL">>),
|
||||
timer:sleep(200),
|
||||
false = docker_commands:is_container_running(ContainerId),
|
||||
ok
|
||||
end).
|
||||
|
||||
test_remove_container() ->
|
||||
Name = test_container_name(<<"remove">>),
|
||||
with_created_container(Name, fun(_ContainerDir, _ContainerId) ->
|
||||
ok = docker_commands:remove_container(Name),
|
||||
false = docker_commands:check_container_exist(Name),
|
||||
ok
|
||||
end, false).
|
||||
|
||||
test_remove_container_with_options() ->
|
||||
Name = test_container_name(<<"remove-opts">>),
|
||||
with_started_container(Name, fun(_ContainerDir, _ContainerId) ->
|
||||
ok = docker_commands:remove_container(Name, true, false),
|
||||
false = docker_commands:check_container_exist(Name),
|
||||
ok
|
||||
end, false).
|
||||
|
||||
test_get_containers() ->
|
||||
Name = test_container_name(<<"list">>),
|
||||
with_created_container(Name, fun(_ContainerDir, ContainerId) ->
|
||||
{ok, Containers} = docker_commands:get_containers(),
|
||||
true = is_list(Containers),
|
||||
true = contains_container(Name, ContainerId, Containers),
|
||||
ok
|
||||
end).
|
||||
|
||||
with_created_container(Name, Fun) ->
|
||||
with_created_container(Name, Fun, true).
|
||||
|
||||
with_created_container(Name, Fun, Cleanup) when is_binary(Name), is_function(Fun, 2), is_boolean(Cleanup) ->
|
||||
ContainerDir = prepare_container_dir(Name),
|
||||
try
|
||||
ok = test_pull(),
|
||||
{ok, ContainerId} = docker_commands:create_container(ContainerDir, minimal_params(Name)),
|
||||
ok = Fun(ContainerDir, ContainerId)
|
||||
after
|
||||
case Cleanup of
|
||||
true ->
|
||||
cleanup_container(Name);
|
||||
false ->
|
||||
ok
|
||||
end
|
||||
end.
|
||||
|
||||
with_started_container(Name, Fun) ->
|
||||
with_started_container(Name, Fun, true).
|
||||
|
||||
with_started_container(Name, Fun, Cleanup) when is_binary(Name), is_function(Fun, 2), is_boolean(Cleanup) ->
|
||||
with_created_container(Name, fun(ContainerDir, ContainerId) ->
|
||||
ok = docker_commands:start_container(Name),
|
||||
ok = Fun(ContainerDir, ContainerId)
|
||||
end, Cleanup).
|
||||
|
||||
minimal_params(Name) when is_binary(Name) ->
|
||||
#'ContainerDeployParams'{
|
||||
container_name = Name,
|
||||
create = #'DockerCreateOptions'{
|
||||
config = #'DockerContainerConfig'{
|
||||
image = ?TEST_IMAGE,
|
||||
cmd = [<<"sleep">>, <<"300">>]
|
||||
}
|
||||
}
|
||||
}.
|
||||
|
||||
prepare_container_dir(Name) when is_binary(Name) ->
|
||||
Dir = lists:flatten(io_lib:format("/tmp/efka_docker_tests/~ts/", [Name])),
|
||||
ok = filelib:ensure_dir(Dir ++ "placeholder"),
|
||||
ok = file:write_file(Dir ++ "service.conf", <<>>, [write]),
|
||||
Dir.
|
||||
|
||||
cleanup_container(Name) when is_binary(Name) ->
|
||||
_ = docker_commands:remove_container(Name, true, false),
|
||||
ok.
|
||||
|
||||
inspect_container_json(Name) when is_binary(Name) ->
|
||||
Url = lists:flatten(io_lib:format("/containers/~s/json", [binary_to_list(Name)])),
|
||||
{ok, 200, _Headers, Resp} = docker_http:request("GET", Url, <<>>, []),
|
||||
jiffy:decode(Resp, [return_maps]).
|
||||
|
||||
assert_patched_defaults(Name, ContainerDir, Inspect)
|
||||
when is_binary(Name), is_list(ContainerDir), is_map(Inspect) ->
|
||||
ConfigFile = list_to_binary(docker_helper:get_config_file(ContainerDir)),
|
||||
ExpectedBind = <<ConfigFile/binary, ":/usr/local/etc/service.conf">>,
|
||||
#{<<"Config">> := #{<<"Env">> := Env, <<"Volumes">> := Volumes},
|
||||
<<"HostConfig">> := #{<<"Binds">> := Binds}} = Inspect,
|
||||
true = lists:member(<<"CONTAINER_NAME=", Name/binary>>, Env),
|
||||
true = maps:is_key(<<"/usr/local/etc/service.conf">>, Volumes),
|
||||
true = lists:member(ExpectedBind, Binds),
|
||||
ok.
|
||||
|
||||
contains_container(Name, ContainerId, Containers) when is_binary(Name), is_binary(ContainerId), is_list(Containers) ->
|
||||
lists:any(fun(Container) -> container_matches(Name, ContainerId, Container) end, Containers).
|
||||
|
||||
container_matches(Name, ContainerId, #{<<"Id">> := Id, <<"Names">> := Names}) when is_binary(Id), is_list(Names) ->
|
||||
lists:member(<<"/", Name/binary>>, Names) orelse has_id_prefix(ContainerId, Id);
|
||||
container_matches(_Name, _ContainerId, _Container) ->
|
||||
false.
|
||||
|
||||
has_id_prefix(ExpectedId, ActualId) when is_binary(ExpectedId), is_binary(ActualId) ->
|
||||
PrefixLen = erlang:min(byte_size(ExpectedId), byte_size(ActualId)),
|
||||
binary:part(ExpectedId, 0, PrefixLen) =:= binary:part(ActualId, 0, PrefixLen).
|
||||
|
||||
test_container_name(Prefix) when is_binary(Prefix) ->
|
||||
Suffix = integer_to_binary(erlang:unique_integer([positive])),
|
||||
<<"efka-test-", Prefix/binary, "-", Suffix/binary>>.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user