diff options
author | Jan Lehnardt <jan@apache.org> | 2018-09-15 09:50:37 +0200 |
---|---|---|
committer | Jan Lehnardt <jan@apache.org> | 2018-11-09 14:51:33 +0100 |
commit | 67f4da6e0bfbf31f1f72d9d9431f03a5d870d561 (patch) | |
tree | b05d30c61791ec4346706394af95c9f540af1109 | |
parent | 36e2323044dee3cadf5f766462000d7be89fc4dc (diff) | |
download | couchdb-67f4da6e0bfbf31f1f72d9d9431f03a5d870d561.tar.gz |
feat: remove deprecated os_daemons
-rw-r--r-- | src/couch/src/couch_os_daemons.erl | 405 | ||||
-rw-r--r-- | src/couch/src/couch_secondary_sup.erl | 1 | ||||
-rw-r--r-- | src/couch/test/couchdb_os_daemons_tests.erl | 259 |
3 files changed, 0 insertions, 665 deletions
diff --git a/src/couch/src/couch_os_daemons.erl b/src/couch/src/couch_os_daemons.erl deleted file mode 100644 index 2427d67ab..000000000 --- a/src/couch/src/couch_os_daemons.erl +++ /dev/null @@ -1,405 +0,0 @@ -% Licensed under the Apache License, Version 2.0 (the "License"); you may not -% use this file except in compliance with the License. You may obtain a copy of -% the License at -% -% http://www.apache.org/licenses/LICENSE-2.0 -% -% Unless required by applicable law or agreed to in writing, software -% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -% License for the specific language governing permissions and limitations under -% the License. --module(couch_os_daemons). --behaviour(gen_server). --vsn(1). --behaviour(config_listener). - --export([start_link/0, info/0, info/1]). - --export([init/1, terminate/2, code_change/3]). --export([handle_call/3, handle_cast/2, handle_info/2]). - -% config_listener api --export([handle_config_change/5, handle_config_terminate/3]). - --include_lib("couch/include/couch_db.hrl"). - --record(daemon, { - port, - name, - cmd, - kill, - status=running, - cfg_patterns=[], - errors=[], - buf=[] -}). - --define(PORT_OPTIONS, [stream, {line, 1024}, binary, exit_status, hide]). --define(RELISTEN_DELAY, 5000). - -start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). - -info() -> - info([]). - -info(Options) -> - gen_server:call(?MODULE, {daemon_info, Options}). - -init(_) -> - process_flag(trap_exit, true), - ok = config:listen_for_changes(?MODULE, nil), - Table = ets:new(?MODULE, [protected, set, {keypos, #daemon.port}]), - reload_daemons(Table), - {ok, Table}. - -terminate(_Reason, Table) -> - [stop_port(D) || D <- ets:tab2list(Table)], - ok. - -handle_call({daemon_info, Options}, _From, Table) when is_list(Options) -> - case lists:member(table, Options) of - true -> - {reply, {ok, ets:tab2list(Table)}, Table}; - _ -> - {reply, {ok, Table}, Table} - end; -handle_call(Msg, From, Table) -> - couch_log:error("Unknown call message to ~p from ~p: ~p", - [?MODULE, From, Msg]), - {stop, error, Table}. - -handle_cast({config_change, Sect, Key}, Table) -> - restart_daemons(Table, Sect, Key), - {noreply, Table}; -handle_cast(stop, Table) -> - {stop, normal, Table}; -handle_cast(Msg, Table) -> - couch_log:error("Unknown cast message to ~p: ~p", [?MODULE, Msg]), - {stop, error, Table}. - -handle_info({'EXIT', Port, Reason}, Table) -> - case ets:lookup(Table, Port) of - [] -> - couch_log:info("Port ~p exited after stopping: ~p~n", - [Port, Reason]); - [#daemon{status=stopping}] -> - true = ets:delete(Table, Port); - [#daemon{name=Name, status=restarting}=D] -> - couch_log:info("Daemon ~p restarting after config change.", [Name]), - true = ets:delete(Table, Port), - {ok, Port2} = start_port(D#daemon.cmd), - true = ets:insert(Table, D#daemon{ - port=Port2, status=running, kill=undefined, buf=[] - }); - [#daemon{name=Name, status=halted}] -> - couch_log:error("Halted daemon process: ~p", [Name]); - [D] -> - couch_log:error("Invalid port state at exit: ~p", [D]) - end, - {noreply, Table}; -handle_info({Port, closed}, Table) -> - handle_info({Port, {exit_status, closed}}, Table); -handle_info({Port, {exit_status, Status}}, Table) -> - case ets:lookup(Table, Port) of - [] -> - couch_log:error("Unknown port ~p exiting ~p", [Port, Status]), - {stop, {error, unknown_port_died, Status}, Table}; - [#daemon{name=Name, status=restarting}=D] -> - couch_log:info("Daemon ~p restarting after config change.", [Name]), - true = ets:delete(Table, Port), - {ok, Port2} = start_port(D#daemon.cmd), - true = ets:insert(Table, D#daemon{ - port=Port2, status=running, kill=undefined, buf=[] - }), - {noreply, Table}; - [#daemon{status=stopping}=D] -> - % The configuration changed and this daemon is no - % longer needed. - couch_log:debug("Port ~p shut down.", [D#daemon.name]), - true = ets:delete(Table, Port), - {noreply, Table}; - [D] -> - % Port died for unknown reason. Check to see if it's - % died too many times or if we should boot it back up. - case should_halt([os:timestamp() | D#daemon.errors]) of - {true, _} -> - % Halting the process. We won't try and reboot - % until the configuration changes. - Fmt = "Daemon ~p halted with exit_status ~p", - couch_log:error(Fmt, [D#daemon.name, Status]), - D2 = D#daemon{status=halted, errors=nil, buf=nil}, - true = ets:insert(Table, D2), - {noreply, Table}; - {false, Errors} -> - % We're guessing it was a random error, this daemon - % has behaved so we'll give it another chance. - Fmt = "Daemon ~p is being rebooted after exit_status ~p", - couch_log:info(Fmt, [D#daemon.name, Status]), - true = ets:delete(Table, Port), - {ok, Port2} = start_port(D#daemon.cmd), - true = ets:insert(Table, D#daemon{ - port=Port2, status=running, kill=undefined, - errors=Errors, buf=[] - }), - {noreply, Table} - end; - _Else -> - throw(error) - end; -handle_info({Port, {data, {noeol, Data}}}, Table) -> - [#daemon{buf=Buf}=D] = ets:lookup(Table, Port), - true = ets:insert(Table, D#daemon{buf=[Data | Buf]}), - {noreply, Table}; -handle_info({Port, {data, {eol, Data}}}, Table) -> - [#daemon{buf=Buf}=D] = ets:lookup(Table, Port), - Line = lists:reverse(Buf, Data), - % The first line echoed back is the kill command - % for when we go to get rid of the port. Lines after - % that are considered part of the stdio API. - case D#daemon.kill of - undefined -> - true = ets:insert(Table, D#daemon{kill=?b2l(Line), buf=[]}); - _Else -> - D2 = case (catch ?JSON_DECODE(Line)) of - {invalid_json, Rejected} -> - couch_log:error("Ignoring OS daemon request: ~p", - [Rejected]), - D; - JSON -> - {ok, D3} = handle_port_message(D, JSON), - D3 - end, - true = ets:insert(Table, D2#daemon{buf=[]}) - end, - {noreply, Table}; -handle_info({Port, Error}, Table) -> - couch_log:error("Unexpectd message from port ~p: ~p", [Port, Error]), - stop_port(Port), - [D] = ets:lookup(Table, Port), - true = ets:insert(Table, D#daemon{status=restarting, buf=nil}), - {noreply, Table}; -handle_info(restart_config_listener, State) -> - ok = config:listen_for_changes(?MODULE, nil), - {noreply, State}; -handle_info(Msg, Table) -> - couch_log:error("Unexpected info message to ~p: ~p", [?MODULE, Msg]), - {stop, error, Table}. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - - -handle_config_change(Section, Key, _, _, _) -> - gen_server:cast(?MODULE, {config_change, Section, Key}), - {ok, nil}. - -handle_config_terminate(_, stop, _) -> - ok; -handle_config_terminate(_Server, _Reason, _State) -> - erlang:send_after(?RELISTEN_DELAY, whereis(?MODULE), restart_config_listener). - - -% Internal API - -% -% Port management helpers -% - -start_port(Command) -> - start_port(Command, []). - -start_port(Command, EnvPairs) -> - PrivDir = couch_util:priv_dir(), - Spawnkiller = "\"" ++ filename:join(PrivDir, "couchspawnkillable") ++ "\"", - Opts = case lists:keytake(env, 1, ?PORT_OPTIONS) of - false -> - ?PORT_OPTIONS ++ [ {env,EnvPairs} ]; - {value, {env,OldPairs}, SubOpts} -> - AllPairs = lists:keymerge(1, EnvPairs, OldPairs), - SubOpts ++ [ {env,AllPairs} ] - end, - Port = open_port({spawn, Spawnkiller ++ " " ++ Command}, Opts), - {ok, Port}. - - -stop_port(#daemon{port=Port, kill=undefined}=D) -> - couch_log:error("Stopping daemon without a kill command: ~p", - [D#daemon.name]), - catch port_close(Port); -stop_port(#daemon{port=Port}=D) -> - couch_log:debug("Stopping daemon: ~p", [D#daemon.name]), - os:cmd(D#daemon.kill), - catch port_close(Port). - - -handle_port_message(#daemon{port=Port}=Daemon, [<<"get">>, Section]) -> - KVs = config:get(Section), - Data = lists:map(fun({K, V}) -> {?l2b(K), ?l2b(V)} end, KVs), - Json = iolist_to_binary(?JSON_ENCODE({Data})), - port_command(Port, <<Json/binary, "\n">>), - {ok, Daemon}; -handle_port_message(#daemon{port=Port}=Daemon, [<<"get">>, Section, Key]) -> - Value = case config:get(Section, Key, undefined) of - undefined -> null; - String -> ?l2b(String) - end, - Json = iolist_to_binary(?JSON_ENCODE(Value)), - port_command(Port, <<Json/binary, "\n">>), - {ok, Daemon}; -handle_port_message(Daemon, [<<"register">>, Sec]) when is_binary(Sec) -> - Patterns = lists:usort(Daemon#daemon.cfg_patterns ++ [{?b2l(Sec)}]), - {ok, Daemon#daemon{cfg_patterns=Patterns}}; -handle_port_message(Daemon, [<<"register">>, Sec, Key]) - when is_binary(Sec) andalso is_binary(Key) -> - Pattern = {?b2l(Sec), ?b2l(Key)}, - Patterns = lists:usort(Daemon#daemon.cfg_patterns ++ [Pattern]), - {ok, Daemon#daemon{cfg_patterns=Patterns}}; -handle_port_message(#daemon{name=Name}=Daemon, [<<"log">>, Msg]) -> - handle_log_message(Name, Msg, <<"info">>), - {ok, Daemon}; -handle_port_message(#daemon{name=Name}=Daemon, [<<"log">>, Msg, {Opts}]) -> - Level = couch_util:get_value(<<"level">>, Opts, <<"info">>), - handle_log_message(Name, Msg, Level), - {ok, Daemon}; -handle_port_message(#daemon{name=Name}=Daemon, Else) -> - couch_log:error("Daemon ~p made invalid request: ~p", [Name, Else]), - {ok, Daemon}. - - -handle_log_message(Name, Msg, _Level) when not is_binary(Msg) -> - couch_log:error("Invalid log message from daemon ~p: ~p", [Name, Msg]); -handle_log_message(Name, Msg, <<"debug">>) -> - couch_log:debug("Daemon ~p :: ~s", [Name, ?b2l(Msg)]); -handle_log_message(Name, Msg, <<"info">>) -> - couch_log:info("Daemon ~p :: ~s", [Name, ?b2l(Msg)]); -handle_log_message(Name, Msg, <<"error">>) -> - couch_log:error("Daemon: ~p :: ~s", [Name, ?b2l(Msg)]); -handle_log_message(Name, Msg, Level) -> - couch_log:error("Invalid log level from daemon: ~p", [Level]), - couch_log:info("Daemon: ~p :: ~s", [Name, ?b2l(Msg)]). - -% -% Daemon management helpers -% - -get_daemons() -> - % map all COUCHDB_OS_DAEMON_* vars out of os:getenv - lists:filtermap(fun(Var) -> - case string:split(Var, "=") of - [VarName, Cmd] -> - case string:find(VarName, "COUCHDB_OS_DAEMON_") of - nomatch -> - false; - "COUCHDB_OS_DAEMON_" ++ Name -> - {true, {string:lowercase(Name), Cmd}} - end; - _Else -> false - end - end, os:getenv()). - -reload_daemons(Table) -> - % List of daemons we want to have running. - Configured = lists:sort(get_daemons()), - - % Remove records for daemons that were halted. - MSpecHalted = #daemon{name='$1', cmd='$2', status=halted, _='_'}, - Halted = lists:sort([{N, C} || [N, C] <- ets:match(Table, MSpecHalted)]), - ok = stop_os_daemons(Table, find_to_stop(Configured, Halted, [])), - - % Stop daemons that are running - % Start newly configured daemons - MSpecRunning = #daemon{name='$1', cmd='$2', status=running, _='_'}, - Running = lists:sort([{N, C} || [N, C] <- ets:match(Table, MSpecRunning)]), - ok = stop_os_daemons(Table, find_to_stop(Configured, Running, [])), - ok = boot_os_daemons(Table, find_to_boot(Configured, Running, [])), - ok. - - -restart_daemons(Table, Sect, Key) -> - restart_daemons(Table, Sect, Key, ets:first(Table)). - -restart_daemons(_, _, _, '$end_of_table') -> - ok; -restart_daemons(Table, Sect, Key, Port) -> - [D] = ets:lookup(Table, Port), - HasSect = lists:member({Sect}, D#daemon.cfg_patterns), - HasKey = lists:member({Sect, Key}, D#daemon.cfg_patterns), - case HasSect or HasKey of - true -> - stop_port(D), - D2 = D#daemon{status=restarting, buf=nil}, - true = ets:insert(Table, D2); - _ -> - ok - end, - restart_daemons(Table, Sect, Key, ets:next(Table, Port)). - - -stop_os_daemons(_Table, []) -> - ok; -stop_os_daemons(Table, [{Name, Cmd} | Rest]) -> - [[Port]] = ets:match(Table, #daemon{port='$1', name=Name, cmd=Cmd, _='_'}), - [D] = ets:lookup(Table, Port), - case D#daemon.status of - halted -> - ets:delete(Table, Port); - _ -> - stop_port(D), - D2 = D#daemon{status=stopping, errors=nil, buf=nil}, - true = ets:insert(Table, D2) - end, - stop_os_daemons(Table, Rest). - -boot_os_daemons(_Table, []) -> - ok; -boot_os_daemons(Table, [{Name, Cmd} | Rest]) -> - {ok, Port} = start_port(Cmd), - true = ets:insert(Table, #daemon{port=Port, name=Name, cmd=Cmd}), - boot_os_daemons(Table, Rest). - -% Elements unique to the configured set need to be booted. -find_to_boot([], _Rest, Acc) -> - % Nothing else configured. - Acc; -find_to_boot([D | R1], [D | R2], Acc) -> - % Elements are equal, daemon already running. - find_to_boot(R1, R2, Acc); -find_to_boot([D1 | R1], [D2 | _]=A2, Acc) when D1 < D2 -> - find_to_boot(R1, A2, [D1 | Acc]); -find_to_boot(A1, [_ | R2], Acc) -> - find_to_boot(A1, R2, Acc); -find_to_boot(Rest, [], Acc) -> - % No more candidates for already running. Boot all. - Rest ++ Acc. - -% Elements unique to the running set need to be killed. -find_to_stop([], Rest, Acc) -> - % The rest haven't been found, so they must all - % be ready to die. - Rest ++ Acc; -find_to_stop([D | R1], [D | R2], Acc) -> - % Elements are equal, daemon already running. - find_to_stop(R1, R2, Acc); -find_to_stop([D1 | R1], [D2 | _]=A2, Acc) when D1 < D2 -> - find_to_stop(R1, A2, Acc); -find_to_stop(A1, [D2 | R2], Acc) -> - find_to_stop(A1, R2, [D2 | Acc]); -find_to_stop(_, [], Acc) -> - % No more running daemons to worry about. - Acc. - -should_halt(Errors) -> - RetryTimeCfg = config:get("os_daemon_settings", "retry_time", "5"), - RetryTime = list_to_integer(RetryTimeCfg), - - Now = os:timestamp(), - RecentErrors = lists:filter(fun(Time) -> - timer:now_diff(Now, Time) =< RetryTime * 1000000 - end, Errors), - - RetryCfg = config:get("os_daemon_settings", "max_retries", "3"), - Retries = list_to_integer(RetryCfg), - - {length(RecentErrors) >= Retries, RecentErrors}. diff --git a/src/couch/src/couch_secondary_sup.erl b/src/couch/src/couch_secondary_sup.erl index cfbce6a9a..d78b5b15b 100644 --- a/src/couch/src/couch_secondary_sup.erl +++ b/src/couch/src/couch_secondary_sup.erl @@ -33,7 +33,6 @@ init([]) -> {"httpd", "{couch_httpd, start_link, []}"}, {"uuids", "{couch_uuids, start, []}"}, {"auth_cache", "{couch_auth_cache, start_link, []}"}, - {"os_daemons", "{couch_os_daemons, start_link, []}"}, {"compaction_daemon", "{couch_compaction_daemon, start_link, []}"} ], diff --git a/src/couch/test/couchdb_os_daemons_tests.erl b/src/couch/test/couchdb_os_daemons_tests.erl deleted file mode 100644 index 1728314bb..000000000 --- a/src/couch/test/couchdb_os_daemons_tests.erl +++ /dev/null @@ -1,259 +0,0 @@ -% Licensed under the Apache License, Version 2.0 (the "License"); you may not -% use this file except in compliance with the License. You may obtain a copy of -% the License at -% -% http://www.apache.org/licenses/LICENSE-2.0 -% -% Unless required by applicable law or agreed to in writing, software -% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -% License for the specific language governing permissions and limitations under -% the License. - --module(couchdb_os_daemons_tests). - -%% tests are UNIX-specific, will not function under Windows --ifdef(WINDOWS). --undef(TEST). --define(NOTEST, 1). --endif. - --include_lib("couch/include/couch_eunit.hrl"). - -%% keep in sync with couchdb/couch_os_daemons.erl --record(daemon, { - port, - name, - cmd, - kill, - status=running, - cfg_patterns=[], - errors=[], - buf=[] -}). - --define(DAEMON_CONFIGER, "os_daemon_configer.escript"). --define(DAEMON_LOOPER, "os_daemon_looper.escript"). --define(DAEMON_BAD_PERM, "os_daemon_bad_perm.sh"). --define(DAEMON_CAN_REBOOT, "os_daemon_can_reboot.sh"). --define(DAEMON_DIE_ON_BOOT, "os_daemon_die_on_boot.sh"). --define(DAEMON_DIE_QUICKLY, "os_daemon_die_quickly.sh"). --define(TRIES, 40). --define(TRY_DELAY_MS, 100). --define(TIMEOUT, 10000). --define(CONFIG_TIMEOUT, 1000). - - -setup(DName) -> - Ctx = test_util:start(?MODULE, [couch_log], [{dont_mock, [config]}]), - {ok, OsDPid} = couch_os_daemons:start_link(), - config:set("os_daemons", DName, - filename:join([?FIXTURESDIR, DName]), false), - % Set configuration option to be used by configuration_reader_test_ - % This will be used in os_daemon_configer.escript:test_get_cfg2 - config:set("uuids", "algorithm","sequential", false), - config:set("os_daemon_settings", "max_retries", "2", false), - ensure_n_daemons_are_alive(1), - {Ctx, OsDPid}. - -teardown(_, {Ctx, OsDPid}) -> - try - test_util:stop_sync_throw(OsDPid, fun() -> - exit(OsDPid, shutdown) - end, {timeout, os_daemon_stop}, ?TIMEOUT) - catch - {timeout, os_daemon_stop} -> - Msg = "~nWARNING: OS daemons test stop ~p msec timeout exceeded~n", - io:format(standard_error, Msg, [?TIMEOUT]), - exit(OsDPid, kill) - end, - test_util:stop(Ctx). - - -os_daemons_test_() -> - { - "OS Daemons tests", - { - foreachx, - fun setup/1, fun teardown/2, - [{?DAEMON_LOOPER, Fun} || Fun <- [ - fun should_check_daemon/2, - fun should_check_daemon_table_form/2, - fun should_clean_tables_on_daemon_remove/2, - fun should_spawn_multiple_daemons/2, - fun should_keep_alive_one_daemon_on_killing_other/2 - ]] - } - }. - -configuration_reader_test_() -> - { - "OS Daemon requests CouchDB configuration", - { - foreachx, - fun setup/1, fun teardown/2, - [{?DAEMON_CONFIGER, - fun should_read_write_config_settings_by_daemon/2}] - - } - }. - -error_test_() -> - { - "OS Daemon process error tests", - { - foreachx, - fun setup/1, fun teardown/2, - [{?DAEMON_BAD_PERM, fun should_fail_due_to_lack_of_permissions/2}, - {?DAEMON_DIE_ON_BOOT, fun should_die_on_boot/2}, - {?DAEMON_DIE_QUICKLY, fun should_die_quickly/2}, - {?DAEMON_CAN_REBOOT, fun should_not_being_halted/2}] - } - }. - - -should_check_daemon(DName, _) -> - ?_test(begin - {ok, [D]} = couch_os_daemons:info([table]), - check_daemon(D, DName) - end). - -should_check_daemon_table_form(DName, _) -> - ?_test(begin - {ok, Tab} = couch_os_daemons:info(), - [D] = ets:tab2list(Tab), - check_daemon(D, DName) - end). - -should_clean_tables_on_daemon_remove(DName, _) -> - ?_test(begin - config:delete("os_daemons", DName, false), - {ok, Tab2} = couch_os_daemons:info(), - ?_assertEqual([], ets:tab2list(Tab2)) - end). - -should_spawn_multiple_daemons(DName, _) -> - ?_test(begin - config:set("os_daemons", "bar", - filename:join([?FIXTURESDIR, DName]), false), - config:set("os_daemons", "baz", - filename:join([?FIXTURESDIR, DName]), false), - ensure_n_daemons_are_alive(3), % DName, "bar" and "baz" - {ok, Daemons} = couch_os_daemons:info([table]), - lists:foreach(fun(D) -> - check_daemon(D) - end, Daemons), - {ok, Tab} = couch_os_daemons:info(), - lists:foreach(fun(D) -> - check_daemon(D) - end, ets:tab2list(Tab)) - end). - -should_keep_alive_one_daemon_on_killing_other(DName, _) -> - ?_test(begin - config:set("os_daemons", "bar", - filename:join([?FIXTURESDIR, DName]), false), - ensure_n_daemons_are_alive(2), % DName and "bar" - {ok, Daemons} = couch_os_daemons:info([table]), - lists:foreach(fun(D) -> - check_daemon(D) - end, Daemons), - - config:delete("os_daemons", "bar", false), - ensure_n_daemons_are_alive(1), % Dname only, "bar" should be dead - {ok, [D2]} = couch_os_daemons:info([table]), - check_daemon(D2, DName), - - {ok, Tab} = couch_os_daemons:info(), - [T] = ets:tab2list(Tab), - check_daemon(T, DName) - end). - -should_read_write_config_settings_by_daemon(DName, _) -> - ?_test(begin - % have to wait till daemon run all his tests - % see daemon's script for more info - timer:sleep(?CONFIG_TIMEOUT), - {ok, [D]} = couch_os_daemons:info([table]), - check_daemon(D, DName) - end). - -should_fail_due_to_lack_of_permissions(DName, _) -> - ?_test(should_halts(DName, 1000)). - -should_die_on_boot(DName, _) -> - ?_test(should_halts(DName, 2000)). - -should_die_quickly(DName, _) -> - ?_test(should_halts(DName, 4000)). - -should_not_being_halted(DName, _) -> - ?_test(begin - timer:sleep(1000), - {ok, [D1]} = couch_os_daemons:info([table]), - check_daemon(D1, DName, 0), - - % Should reboot every two seconds. We're at 1s, so wait - % until 3s to be in the middle of the next invocation's - % life span. - - timer:sleep(2000), - {ok, [D2]} = couch_os_daemons:info([table]), - check_daemon(D2, DName, 1), - - % If the kill command changed, that means we rebooted the process. - ?assertNotEqual(D1#daemon.kill, D2#daemon.kill) - end). - -should_halts(DName, Time) -> - timer:sleep(Time), - {ok, [D]} = couch_os_daemons:info([table]), - check_dead(D, DName), - config:delete("os_daemons", DName, false). - -check_daemon(D) -> - check_daemon(D, D#daemon.name). - -check_daemon(D, Name) -> - check_daemon(D, Name, 0). - -check_daemon(D, Name, Errs) -> - ?assert(is_port(D#daemon.port)), - ?assertEqual(Name, D#daemon.name), - ?assertNotEqual(undefined, D#daemon.kill), - ?assertEqual(running, D#daemon.status), - ?assertEqual(Errs, length(D#daemon.errors)), - ?assertEqual([], D#daemon.buf). - -check_dead(D, Name) -> - ?assert(is_port(D#daemon.port)), - ?assertEqual(Name, D#daemon.name), - ?assertNotEqual(undefined, D#daemon.kill), - ?assertEqual(halted, D#daemon.status), - ?assertEqual(nil, D#daemon.errors), - ?assertEqual(nil, D#daemon.buf). - -daemons() -> - {ok, Daemons} = couch_os_daemons:info([table]), - Daemons. - -ensure_n_daemons_are_alive(NumDaemons) -> - retry(fun() -> length(daemons()) == NumDaemons end, "spawning"), - retry(fun() -> - lists:all(fun(D) -> D#daemon.kill =/= undefined end, daemons()) - end, "waiting for kill flag"). - -retry(Pred, FailReason) -> - retry(Pred, ?TRIES, FailReason). - -retry(_Pred, 0, FailReason) -> - erlang:error({assertion_failed,[{module, ?MODULE}, {line, ?LINE}, - {reason, "Timed out: " ++ FailReason}]}); -retry(Pred, N, FailReason) -> - case Pred() of - true -> - ok; - false -> - timer:sleep(?TRY_DELAY_MS), - retry(Pred, N - 1, FailReason) - end. |