From 3f2df1fc4040e9ab4b46ac8a141527deb12eb816 Mon Sep 17 00:00:00 2001 From: Zeeshan Lakhani Date: Fri, 2 Oct 2015 17:49:10 -0400 Subject: [PATCH] - allow for removal of fuses - update fuse_eqc test with remove command - update fuse_SUITE with common test for remove --- src/fuse.erl | 9 +++++++ src/fuse_server.erl | 30 ++++++++++++++++++--- test/fuse_SUITE.erl | 42 +++++++++++++++++++++++------ test/fuse_eqc.erl | 65 ++++++++++++++++++++++++++++++++++----------- 4 files changed, 119 insertions(+), 27 deletions(-) diff --git a/src/fuse.erl b/src/fuse.erl index d273c02..432d6b0 100644 --- a/src/fuse.erl +++ b/src/fuse.erl @@ -10,6 +10,7 @@ ask/2, install/2, melt/1, + remove/1, reset/1, run/3 ]). @@ -69,6 +70,14 @@ reset(Name) -> melt(Name) -> fuse_server:melt(Name). +%% @doc remove/1 removs a fuse +%% Given `remove(N)' this removes the fuse under the name `N'. This fuse will no longer exist. +%% @end +-spec remove(Name) -> ok + when Name :: atom(). +remove(Name) -> + fuse_server:remove(Name). + %% Internal functions %% ----------------------- diff --git a/src/fuse_server.erl b/src/fuse_server.erl index bb47503..69f0676 100644 --- a/src/fuse_server.erl +++ b/src/fuse_server.erl @@ -16,6 +16,7 @@ ask/2, install/2, melt/1, + remove/1, reset/1, run/3]). @@ -96,7 +97,14 @@ reset(Name) -> when Name :: atom(). melt(Name) -> gen_server:call(?MODULE, {melt, Name}). - + +%% @doc remove/1 removes the fuse +%% The documentation is (@see fuse:remove/1) +%% @end +-spec remove(atom()) -> ok | {error, not_found}. +remove(Name) -> + gen_server:call(?MODULE, {remove, Name}). + %% sync/0 syncs the server. For internal use only in tests %% @private sync() -> @@ -147,6 +155,9 @@ handle_call({install, #fuse { name = Name } = Fuse}, _From, #state { fuses = Fs handle_call({reset, Name}, _From, State) -> {Reply, State2} = handle_reset(Name, State, reset), {reply, Reply, State2}; +handle_call({remove, Name}, _From, State) -> + {Reply, State2} = handle_remove(Name, State), + {reply, Reply, State2}; handle_call({melt, Name}, _From, State) -> Now = ?TIME:monotonic_time(), {Res, State2} = with_fuse(Name, State, fun(F) -> add_restart(Now, F) end), @@ -165,7 +176,7 @@ handle_call(q_melts, _From, #state { fuses = Fs } = State) -> {reply, [{N, Ms} || #fuse { name = N, melt_history = Ms } <- Fs], State}; handle_call(_M, _F, State) -> {reply, {error, unknown}, State}. - + %% @private handle_cast(_M, State) -> {noreply, State}. @@ -206,6 +217,14 @@ handle_reset(Name, State, ResetType) -> not_found -> {{error, not_found}, State2} end. +handle_remove(Name, #state { fuses = Fs } = State) -> + case lists:keytake(Name, #fuse.name, Fs) of + false -> {{error, not_found}, State}; + {value, F, OtherFs} -> + delete(F), + {ok, State#state { fuses = OtherFs }} + end. + init_state(Name, {{standard, MaxR, MaxT}, {reset, Reset}}) -> NativePeriod = ?TIME:convert_time_unit(MaxT, milli_seconds, native), #fuse { name = Name, intensity = MaxR, period = NativePeriod, heal_time = Reset }. @@ -237,7 +256,7 @@ add_restart_([R|Restarts], Now, Period) -> false -> [] end; add_restart_([], _, _) -> []. - + in_period(Time, Now, Period) when (Now - Time) > Period -> false; in_period(_, _, _) -> true. @@ -251,6 +270,11 @@ fix(#fuse { name = Name }) -> fuse_event:notify({Name, ok}), ok. +delete(#fuse { name = Name }) -> + ets:delete(?TAB, Name), + fuse_event:notify({Name, removed}), + ok. + install_metrics(#fuse { name = N }) -> StatsPlugin = application:get_env(fuse, stats_plugin, fuse_stats_ets), _ = StatsPlugin:init(N), diff --git a/test/fuse_SUITE.erl b/test/fuse_SUITE.erl index 979963b..c9cd138 100644 --- a/test/fuse_SUITE.erl +++ b/test/fuse_SUITE.erl @@ -12,7 +12,8 @@ %% Tests. -export([ simple_test/1, - reset_test/1 + reset_test/1, + remove_test/1 ]). -ifdef(EQC_TESTING). @@ -24,11 +25,12 @@ %% ct. all() -> [ simple_test, - reset_test + reset_test, + remove_test ]. - + groups() -> []. - + suite() -> [{timetrap, {minutes, 2}}]. @@ -45,15 +47,15 @@ init_per_suite(Config) -> true -> {skip, running_eqc} end. - -end_per_suite(_Config) -> + +end_per_suite(_Config) -> application:stop(fuse), application:stop(sasl), ok. - + init_per_group(_Group, Config) -> Config. - + end_per_group(_Group, _Config) -> ok. @@ -114,3 +116,27 @@ reset_test(_Config) -> 2 = proplists:get_value(ok, Stats), 1 = proplists:get_value(blown, Stats), ok. + +-define(FUSE_REMOVE, remove_fuse). +remove_test(_Config) -> + ct:log("Install a fuse, melt it, blow it, then remove it, then recreate it"), + ok = fuse:install(?FUSE_REMOVE, {{standard, 2, 60}, {reset, 5000}}), + ok = fuse:ask(?FUSE_REMOVE, sync), + ok = fuse:melt(?FUSE_REMOVE), + ok = fuse:melt(?FUSE_REMOVE), + ok = fuse:melt(?FUSE_REMOVE), + blown = fuse:ask(?FUSE_REMOVE, sync), + Stats = fuse_stats_ets:counters(?FUSE_REMOVE), + 3 = proplists:get_value(melt, Stats), + 1 = proplists:get_value(ok, Stats), + 1 = proplists:get_value(blown, Stats), + ok = fuse:remove(?FUSE_REMOVE), + {error, not_found} = fuse:ask(?FUSE_REMOVE, sync), + {error, not_found} = fuse:remove(?FUSE_REMOVE), + ok = fuse:install(?FUSE_REMOVE, {{standard, 2, 60}, {reset, 5000}}), + ok = fuse:ask(?FUSE_REMOVE, sync), + Stats2 = fuse_stats_ets:counters(?FUSE_REMOVE), + 0 = proplists:get_value(melt, Stats2), + 1 = proplists:get_value(ok, Stats2), + 0 = proplists:get_value(blown, Stats2), + ok. diff --git a/test/fuse_eqc.erl b/test/fuse_eqc.erl index ef316eb..6144cdf 100644 --- a/test/fuse_eqc.erl +++ b/test/fuse_eqc.erl @@ -61,7 +61,7 @@ g_strategy() -> %% g_refresh()/0 generates a refresh setting. g_refresh() -> {reset, 60000}. - + %% g_options() generates install options g_options() -> {g_strategy(), g_refresh()}. @@ -80,7 +80,7 @@ initial_state() -> #state{}. %% at a later point than normally. elapse_time(N) -> fuse_time_mock:elapse_time(N). - + elapse_time_args(_S) -> [g_time_inc()]. elapse_time_next(#state { time = T } = State, _V, [N]) -> @@ -202,7 +202,7 @@ reset_features(S, [Name], _V) -> %% Split into two variants ask_installed(Name) -> fuse:ask(Name, ?CONTEXT). - + ask_installed_pre(S) -> has_fuses_installed(S). ask_installed_args(_S) -> [g_name()]. @@ -219,11 +219,11 @@ ask_installed_return(S, [Name]) -> ask(Name) -> fuse:ask(Name, ?CONTEXT). - + ask_pre(S) -> has_fuses_installed(S). ask_args(_S) -> [g_name()]. - + ask_features(S, [Name], _V) -> case is_installed(Name, S) of true -> [{fuse_eqc, r15, ask_installed}]; @@ -245,7 +245,7 @@ ask_return(S, [Name]) -> %% --------------------------------------------------------------- run(Name, _Result, _Return, Fun) -> fuse:run(Name, Fun, ?CONTEXT). - + run_pre(S) -> has_fuses_installed(S). @@ -307,14 +307,14 @@ run_return(S, [Name, _Result, Return, _]) -> %% fuses which are installed, since we assume the interesting aspects affects these. melt_installed(Name) -> fuse:melt(Name). - + melt_installed_pre(S) -> has_fuses_installed(S). melt_installed_args(_S) -> [g_name()]. melt_installed_pre(S, [Name]) -> is_installed(Name, S). - + melt_installed_next(#state { time = Ts } = S, _V, [Name]) -> M = val(record_melt(Name, Ts, S)), {_, NewState} = @@ -365,9 +365,41 @@ melt_features(#state { time = Ts } = S, [Name], _V) -> [{fuse_eqc, r11, melt_installed_fuse}] ++ Features; false -> [{fuse_eqc, r12, melt_uninstalled_fuse}] end. - + melt_return(_S, _) -> ok. +%% remove/1 removes a fuse +%% --------------------------------------------------------------- +remove(Name) -> + fuse:remove(Name). + +%% Generate arguments to remove from a fuse that's actually installed vs +%% fuses that are not. +remove_args(#state { installed = Is } = _S) -> + frequency( + [ {20, ?LET(F, elements(Is), [element(1, F)])} || Is /= [] ] ++ + [ {1, ?SUCHTHAT([F], [g_name()], lists:keymember(F, 1, Is) == false)} ]). + +remove_return(S, [Name]) -> + case is_installed(Name, S) of + true -> ok; + false -> {error, not_found} + end. + +%% Removing a fuse, removes it from the list of installed fuses. +remove_next(#state{ installed = Is } = S, _V, [Name]) -> + case is_installed(Name, S) of + false -> S; + true -> + S#state { installed = lists:keydelete(Name, 1, Is) } + end. + +remove_features(S, [Name], _V) -> + case is_installed(Name, S) of + false -> [{fuse_eqc, r17, remove_uninstalled_fuse}]; + true -> [{fuse_eqc, r18, remove_installed_fuse}] + end. + %%% Command weight distribution %% --------------------------------------------------------------- weight(_, elapse_time) -> 5; @@ -378,7 +410,8 @@ weight(_, melt) -> 1; weight(_, melt_installed) -> 40; weight(_, fuse_reset) -> 100; weight(_, ask) -> 1; -weight(_, ask_installed) -> 30. +weight(_, ask_installed) -> 30; +weight(_, remove) -> 1. %%% PROPERTIES %% --------------------------------------------------------------- @@ -412,7 +445,7 @@ prop_model_par() -> fun() -> ok end end, fault_rate(1, 40, - ?LET(Shrinking, parameter(shrinking, false), + ?LET(Shrinking, parameter(shrinking, false), ?FORALL(Cmds, more_commands(2, parallel_commands(?MODULE)), ?ALWAYS(if not Shrinking -> 1; Shrinking -> 20 @@ -478,13 +511,13 @@ valid_opts({{standard, K, R}, {reset, T}}) true; valid_opts(_) -> false. - + melt_state(Name, S) -> count_state(fuse_intensity(Name, S) - count_melts(Name, S)). is_blown(Name, #state { blown = BlownFuses }) -> lists:member(Name, BlownFuses). - + fuse_intensity(Name, #state { installed = Inst }) -> {Name, Count, _} = lists:keyfind(Name, 1, Inst), Count. @@ -526,7 +559,7 @@ expire_melts(Period, Who, #state { time = Now, melts = Ms } = S) -> clear_blown(Name, #state { blown = Rs } = S) -> S#state { blown = [N || N <- Rs, N /= Name] }. - + clear_melts(Name, #state { melts = Ms } = S) -> S#state { melts = [{N, Ts} || {N, Ts} <- Ms, N /= Name] }. @@ -547,7 +580,7 @@ t(pulse, {T, Unit}) -> eqc:testing_time(eval_time(T, Unit), x_prop_model_pulse()); t(pulse, N) when is_integer(N) -> eqc:numtests(N, x_prop_model_pulse()). - + eval_time(N, h) -> eval_time(N*60, min); eval_time(N, min) -> eval_time(N*60, sec); @@ -562,7 +595,7 @@ rv(What, T) -> pulse_instrument() -> [ pulse_instrument(File) || File <- filelib:wildcard("../src/*.erl") ++ filelib:wildcard("../eqc_test/*.erl") ], load_sasl(). - + load_sasl() -> application:load(sasl), application:set_env(sasl, sasl_error_logger, false),