diff options
Diffstat (limited to 'lib/common_test/src/ct_gen_conn.erl')
-rw-r--r-- | lib/common_test/src/ct_gen_conn.erl | 286 |
1 files changed, 286 insertions, 0 deletions
diff --git a/lib/common_test/src/ct_gen_conn.erl b/lib/common_test/src/ct_gen_conn.erl new file mode 100644 index 0000000000..a31e57c7ea --- /dev/null +++ b/lib/common_test/src/ct_gen_conn.erl @@ -0,0 +1,286 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%%% @doc Generic connection owner process. +%%% +%%% @type handle() = pid(). A handle for using a connection implemented +%%% with ct_gen_conn.erl. + +-module(ct_gen_conn). + +-compile(export_all). + +-export([start/4, stop/1]). +-export([call/2, do_within_time/2]). + +-ifdef(debug). +-define(dbg,true). +-else. +-define(dbg,false). +-endif. + +-record(gen_opts,{callback, + name, + address, + init_data, + conn_pid, + cb_state, + ct_util_server}). + +%%%----------------------------------------------------------------- +%%% @spec start(Name,Address,InitData,CallbackMod) -> +%%% {ok,Handle} | {error,Reason} +%%% Name = term() +%%% CallbackMod = atom() +%%% InitData = term() +%%% Address = term() +%%% +%%% @doc Open a connection and start the generic connection owner process. +%%% +%%% <p>The <code>CallbackMod</code> is a specific callback module for +%%% each type of connection (e.g. telnet, ftp,...). It must export the +%%% function <code>init/3</code> which takes the arguments +%%% <code>Name</code>, <code>Addresse</code>) and +%%% <code>InitData</code> and returna +%%% <code>{ok,ConnectionPid,State}</code> or +%%% <code>{error,Reason}</code>.</p> +start(Name,Address,InitData,CallbackMod) -> + case ct_util:does_connection_exist(Name,Address,CallbackMod) of + {ok,Pid} -> + log("ct_gen_conn:start","Using existing connection!\n",[]), + {ok,Pid}; + false -> + Self = self(), + Pid = spawn(fun() -> + init_gen(Self, #gen_opts{callback=CallbackMod, + name=Name, + address=Address, + init_data=InitData}) + end), + MRef = erlang:monitor(process,Pid), + receive + {connected,Pid} -> + erlang:demonitor(MRef), + ct_util:register_connection(Name,Address,CallbackMod,Pid), + {ok,Pid}; + {Error,Pid} -> + receive {'DOWN',MRef,process,_,_} -> ok end, + Error; + {'DOWN',MRef,process,_,Reason} -> + log("ct_gen_conn:start", + "Connection process died: ~p\n", + [Reason]), + {error,{connection_process_died,Reason}} + end + end. + + +%%%----------------------------------------------------------------- +%%% @spec stop(Handle) -> ok +%%% Handle = handle() +%%% +%%% @doc Close the telnet connection and stop the process managing it. +stop(Pid) -> + call(Pid,stop). + +%%%----------------------------------------------------------------- +%%% @spec log(Heading,Format,Args) -> ok +%%% +%%% @doc Log activities on the current connection (tool-internal use only). +%%% @see ct_logs:log/3 +log(Heading,Format,Args) -> + log(log,[Heading,Format,Args]). + +%%%----------------------------------------------------------------- +%%% @spec start_log(Heading) -> ok +%%% +%%% @doc Log activities on the current connection (tool-internal use only). +%%% @see ct_logs:start_log/1 +start_log(Heading) -> + log(start_log,[Heading]). + +%%%----------------------------------------------------------------- +%%% @spec cont_log(Format,Args) -> ok +%%% +%%% @doc Log activities on the current connection (tool-internal use only). +%%% @see ct_logs:cont_log/2 +cont_log(Format,Args) -> + log(cont_log,[Format,Args]). + +%%%----------------------------------------------------------------- +%%% @spec end_log() -> ok +%%% +%%% @doc Log activities on the current connection (tool-internal use only). +%%% @see ct_logs:end_log/0 +end_log() -> + log(end_log,[]). + +%%%----------------------------------------------------------------- +%%% @spec do_within_time(Fun,Timeout) -> FunResult | {error,Reason} +%%% Fun = function() +%%% Timeout = integer() +%%% +%%% @doc Execute a function within a limited time (tool-internal use only). +%%% +%%% <p>Execute the given <code>Fun</code>, but interrupt if it takes +%%% more than <code>Timeout</code> milliseconds.</p> +%%% +%%% <p>The execution is also interrupted if the connection is +%%% closed.</p> +do_within_time(Fun,Timeout) -> + Self = self(), + Silent = get(silent), + TmpPid = spawn_link(fun() -> put(silent,Silent), + R = Fun(), + Self ! {self(),R} + end), + ConnPid = get(conn_pid), + receive + {TmpPid,Result} -> + Result; + {'EXIT',ConnPid,_Reason}=M -> + unlink(TmpPid), + exit(TmpPid,kill), + self() ! M, + {error,connection_closed} + after + Timeout -> + exit(TmpPid,kill), + receive + {TmpPid,Result} -> + %% TmpPid just managed to send the result at the same time + %% as the timeout expired. + receive {'EXIT',TmpPid,_reason} -> ok end, + Result; + {'EXIT',TmpPid,killed} -> + %% TmpPid did not send the result before the timeout expired. + {error,timeout} + end + end. + +%%%================================================================= +%%% Internal functions +call(Pid,Msg) -> + MRef = erlang:monitor(process,Pid), + Ref = make_ref(), + Pid ! {Msg,{self(),Ref}}, + receive + {Ref, Result} -> + erlang:demonitor(MRef), + case Result of + {retry,_Data} -> + call(Pid,Result); + Other -> + Other + end; + {'DOWN',MRef,process,_,Reason} -> + {error,{process_down,Pid,Reason}} + end. + +return({To,Ref},Result) -> + To ! {Ref, Result}. + +init_gen(Parent,Opts) -> + process_flag(trap_exit,true), + CtUtilServer = whereis(ct_util_server), + link(CtUtilServer), + put(silent,false), + case catch (Opts#gen_opts.callback):init(Opts#gen_opts.name, + Opts#gen_opts.address, + Opts#gen_opts.init_data) of + {ok,ConnPid,State} when is_pid(ConnPid) -> + link(ConnPid), + put(conn_pid,ConnPid), + Parent ! {connected,self()}, + loop(Opts#gen_opts{conn_pid=ConnPid, + cb_state=State, + ct_util_server=CtUtilServer}); + {error,Reason} -> + Parent ! {{error,Reason},self()} + end. + +loop(Opts) -> + receive + {'EXIT',Pid,Reason} when Pid==Opts#gen_opts.conn_pid -> + log("Connection down!\nOpening new!","Reason: ~p\nAddress: ~p\n", + [Reason,Opts#gen_opts.address]), + case reconnect(Opts) of + {ok, NewPid, NewState} -> + link(NewPid), + put(conn_pid,NewPid), + loop(Opts#gen_opts{conn_pid=NewPid,cb_state=NewState}); + Error -> + ct_util:unregister_connection(self()), + log("Reconnect failed. Giving up!","Reason: ~p\n",[Error]) + end; + {'EXIT',Pid,Reason} -> + case Opts#gen_opts.ct_util_server of + Pid -> + exit(Reason); + _ -> + loop(Opts) + end; + {stop, From} -> + ct_util:unregister_connection(self()), + (Opts#gen_opts.callback):terminate(Opts#gen_opts.conn_pid, + Opts#gen_opts.cb_state), + return(From,ok), + ok; + {{retry,{Error,_Name,CPid,_Msg}}, From} when CPid == Opts#gen_opts.conn_pid -> + %% only retry if failure is because of a reconnection + Return = case Error of + {error,_} -> Error; + Reason -> {error,Reason} + end, + return(From, Return), + loop(Opts); + {{retry,{_Error,_Name,_CPid,Msg}}, From} -> + log("Rerunning command","Connection reestablished. Rerunning command...",[]), + {Return,NewState} = + (Opts#gen_opts.callback):handle_msg(Msg,Opts#gen_opts.cb_state), + return(From, Return), + loop(Opts#gen_opts{cb_state=NewState}); + {Msg,From={Pid,_Ref}} when is_pid(Pid) -> + {Return,NewState} = + (Opts#gen_opts.callback):handle_msg(Msg,Opts#gen_opts.cb_state), + return(From, Return), + loop(Opts#gen_opts{cb_state=NewState}) + end. + +nozero({ok,S}) when is_list(S) -> + {ok,[C || C <- S, + C=/=0, + C=/=13]}; +nozero(M) -> + M. + +reconnect(Opts) -> + (Opts#gen_opts.callback):reconnect(Opts#gen_opts.address, + Opts#gen_opts.cb_state). + + +log(Func,Args) -> + case get(silent) of + true when not ?dbg-> + ok; + _ -> + apply(ct_logs,Func,Args) + end. + + |