ekfa/src/tests/docker_commands_tests.erl
2026-04-23 16:45:59 +08:00

331 lines
12 KiB
Erlang

%%%-------------------------------------------------------------------
%%% @author anlicheng
%%% @copyright (C) 2025, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 23. 9月 2025 17:23
%%%-------------------------------------------------------------------
-module(docker_commands_tests).
-author("anlicheng").
-include("message_pb.hrl").
%% API
-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
]).
-export([test/0]).
-define(TEST_IMAGE, <<"docker.1ms.run/library/nginx:latest">>).
-define(TEST_CMD, [<<"nginx">>, <<"-g">>, <<"daemon off;">>]).
test() ->
try
test_all()
catch _:_:Stack ->
logger:debug("statck is: ~p", [Stack])
end.
-spec test_all() -> ok.
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(),
ok.
-spec test_pull() -> ok.
test_pull() ->
ok = docker_commands:pull_image(?TEST_IMAGE, fun(Msg) -> logger:debug("msg is: ~p", [Msg]) end).
-spec test_check_image_exist() -> ok.
test_check_image_exist() ->
ok = test_pull(),
true = docker_commands:check_image_exist(?TEST_IMAGE),
ok.
-spec test_check_image_not_exist() -> ok.
test_check_image_not_exist() ->
false = docker_commands:check_image_exist(<<"docker.1ms.run/library/not-exists-for-efka-tests:latest">>),
ok.
-spec test_create_container() -> 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.
-spec test_create_container_without_create_options() -> ok.
test_create_container_without_create_options() ->
Name = test_container_name(<<"create-default">>),
ContainerDir = prepare_container_dir(Name),
Options = docker_container_builder:build_options(Name, ContainerDir, undefined),
assert_patched_default_options(Name, ContainerDir, Options),
ok.
-spec test_create_container_patches_options() -> ok.
test_create_container_patches_options() ->
Name = test_container_name(<<"create-patch">>),
ContainerDir = prepare_container_dir(Name),
Create = #'DockerCreateOptions'{
config = #'DockerContainerConfig'{
image = ?TEST_IMAGE,
cmd = ?TEST_CMD,
env = [<<"EXISTING_ENV=1">>],
volumes = [<<"/data">>]
},
host_config = #'DockerHostConfig'{
binds = [<<"/tmp:/tmp">>]
}
},
Params = #'ContainerDeployParams'{
container_name = Name,
create = Create
},
Options = docker_container_builder:build_options(Name, ContainerDir, Create),
assert_patched_default_options(Name, ContainerDir, Options),
assert_existing_options_preserved(Options),
try
ok = test_pull(),
{ok, _ContainerId} = docker_commands:create_container(ContainerDir, Params),
ok
after
cleanup_container(Name)
end.
-spec test_check_container_exist() -> ok.
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).
-spec test_check_container_not_exist() -> ok.
test_check_container_not_exist() ->
false = docker_commands:check_container_exist(test_container_name(<<"missing">>)),
ok.
-spec test_is_container_running() -> 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).
-spec test_start_container() -> ok.
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).
-spec test_stop_container() -> ok.
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).
-spec test_stop_container_with_timeout() -> ok.
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).
-spec test_kill_container() -> ok.
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).
-spec test_kill_container_with_signal() -> ok.
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).
-spec test_remove_container() -> ok.
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).
-spec test_remove_container_with_options() -> ok.
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).
-spec test_get_containers() -> ok.
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).
-spec with_created_container(binary(), fun((string(), binary()) -> ok)) -> ok.
with_created_container(Name, Fun) ->
with_created_container(Name, Fun, true).
-spec with_created_container(binary(), fun((string(), binary()) -> ok), boolean()) -> ok.
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.
-spec with_started_container(binary(), fun((string(), binary()) -> ok)) -> ok.
with_started_container(Name, Fun) ->
with_started_container(Name, Fun, true).
-spec with_started_container(binary(), fun((string(), binary()) -> ok), boolean()) -> ok.
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).
-spec minimal_params(binary()) -> message_pb:'ContainerDeployParams'().
minimal_params(Name) when is_binary(Name) ->
#'ContainerDeployParams'{
container_name = Name,
create = #'DockerCreateOptions'{
config = #'DockerContainerConfig'{
image = ?TEST_IMAGE,
cmd = ?TEST_CMD
}
}
}.
-spec prepare_container_dir(binary()) -> string().
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.
-spec cleanup_container(binary()) -> ok.
cleanup_container(Name) when is_binary(Name) ->
_ = docker_commands:remove_container(Name, true, false),
ok.
-spec assert_patched_default_options(binary(), string(), map()) -> ok.
assert_patched_default_options(Name, ContainerDir, Options)
when is_binary(Name), is_list(ContainerDir), is_map(Options) ->
ConfigFile = list_to_binary(docker_helper:get_config_file(ContainerDir)),
ExpectedBind = <<ConfigFile/binary, ":/usr/local/etc/service.conf">>,
#{<<"Env">> := Env,
<<"Volumes">> := Volumes,
<<"HostConfig">> := #{<<"Binds">> := Binds}} = Options,
true = lists:member(<<"CONTAINER_NAME=", Name/binary>>, Env),
true = maps:is_key(<<"/usr/local/etc/service.conf">>, Volumes),
true = lists:member(ExpectedBind, Binds),
ok.
-spec assert_existing_options_preserved(map()) -> ok.
assert_existing_options_preserved(Options) when is_map(Options) ->
#{<<"Env">> := Env,
<<"Volumes">> := Volumes,
<<"HostConfig">> := #{<<"Binds">> := Binds}} = Options,
true = lists:member(<<"EXISTING_ENV=1">>, Env),
true = maps:is_key(<<"/data">>, Volumes),
true = lists:member(<<"/tmp:/tmp">>, Binds),
ok.
-spec contains_container(binary(), binary(), [map()]) -> boolean().
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).
-spec container_matches(binary(), binary(), map()) -> boolean().
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.
-spec has_id_prefix(binary(), binary()) -> boolean().
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).
-spec test_container_name(binary()) -> binary().
test_container_name(Prefix) when is_binary(Prefix) ->
Suffix = integer_to_binary(erlang:unique_integer([positive])),
<<"efka-test-", Prefix/binary, "-", Suffix/binary>>.