1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2003-2021. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20 21%% 22%% Generic connection owner process. 23%% 24 25-module(ct_gen_conn). 26 27-behaviour(gen_server). 28 29-export([start/4, 30 stop/1, 31 get_conn_pid/1]). 32 33-export([call/2, 34 call/3, 35 return/2, 36 do_within_time/2]). 37 38-export([log/3, 39 start_log/1, 40 cont_log/2, 41 cont_log_no_timestamp/2, 42 end_log/0]). 43 44%% gen_server callbacks 45-export([init/1, 46 handle_info/2, 47 handle_cast/2, 48 handle_call/3, 49 code_change/3, 50 terminate/2]). 51 52%% test 53-export([make_opts/1]). 54 55-ifdef(debug). 56-define(dbg,true). 57-else. 58-define(dbg,false). 59-endif. 60 61-type handle() :: pid(). %% connection-owning process spawned here 62 63-record(gen_opts, {callback :: module(), 64 name :: ct:target_name(), 65 address :: term(), 66 init_data :: term(), 67 reconnect = true :: boolean(), 68 forward = false :: boolean(), 69 use_existing = true :: boolean(), 70 old = false :: boolean(), 71 conn_pid :: pid() | undefined, 72 cb_state :: term(), 73 ct_util_server :: pid() | undefined}). 74 75%% --------------------------------------------------------------------- 76 77-spec start(Address, InitData, CallbackMod, [Opt]) 78 -> {ok, handle()} | {error, Reason :: term()} 79 when Address :: term(), 80 InitData :: term(), 81 CallbackMod :: module(), 82 Opt :: {name, Name :: ct:target_name()} 83 | {use_existing_connection, boolean()} 84 | {reconnect, boolean()} 85 | {forward_messages, boolean()} 86 ; (Name, Address, InitData, CallbackMod) 87 -> {ok, handle()} | {error, Reason :: term()} 88 when Name :: ct:target_name(), 89 Address :: term(), 90 InitData :: term(), 91 CallbackMod :: module(). 92 93%% Open a connection and start the generic connection owner process. 94%% 95%% CallbackMod is a specific callback module for 96%% each type of connection (e.g. telnet, ftp, netconf). It must export: 97%% 98%% init(Name, Address, InitData) -> {ok, ConnectionPid, State} 99%% | {error,Reason}. 100%% 101%% Name defaults to undefined if unspecified. 102%% 103%% The callback modules must also export: 104%% 105%% handle_msg(Msg, From, State) -> {reply, Reply, State} 106%% | {noreply, State} 107%% | {stop, Reply, State} 108%% terminate(ConnectionPid, State) -> term() 109%% close(Handle) -> term() 110%% 111%% A Reply of the form {retry, _} results in a new call to the server with 112%% the retry tuple as message. 113%% 114%% The close/1 callback function is actually a callback 115%% for ct_util, for closing registered connections when 116%% ct_util_server is terminated. Handle is the Pid of 117%% the ct_gen_conn process. 118%% 119%% If option reconnect is true, then the 120%% callback must also export: 121%% 122%% reconnect(Address, State) -> {ok, ConnectionPid, State} 123%% 124%% If option forward_messages is true then 125%% the callback must also export: 126%% 127%% handle_msg(Msg,State) -> {noreply,State} 128%% | {stop,State} 129%% 130%% An old interface still exists. This is used by ct_telnet, ct_ftp 131%% and ct_ssh. The start function then has an explicit 132%% Name argument, and no Opts argument. 133 134start(Address, InitData, CallbackMod, Opts) when is_list(Opts) -> 135 do_start(Address, InitData, CallbackMod, Opts); 136start(Name, Address, InitData, CallbackMod) -> 137 do_start(Address, InitData, CallbackMod, [{name, Name}, {old, true}]). 138 139%% --------------------------------------------------------------------- 140 141-spec stop(handle()) -> ok | {error, Reason :: term()}. 142 143%% Close the connection and stop the process managing it. 144 145stop(Handle) -> 146 call(Handle, stop, 5000). 147 148%% --------------------------------------------------------------------- 149 150-spec get_conn_pid(handle()) -> pid(). 151 152%% Return the connection pid associated with Handle 153 154get_conn_pid(Handle) -> 155 call(Handle, get_conn_pid). 156 157%% --------------------------------------------------------------------- 158 159-spec log(Heading, Format, Args) -> ok 160 when Heading :: iodata(), 161 Format :: io:format(), 162 Args :: [term()]. 163 164%% Log activities on the current connection. 165%% See ct_logs:log/3 166 167log(Heading, Format, Args) -> 168 log(log, [Heading, Format, Args]). 169 170%% --------------------------------------------------------------------- 171 172-spec start_log(Heading :: iodata()) -> ok. 173 174%% Log activities on the current connection. 175%% See ct_logs:start_log/1 176 177start_log(Heading) -> 178 log(start_log, [Heading]). 179 180%% --------------------------------------------------------------------- 181 182-spec cont_log(Format, Args) -> ok 183 when Format :: io:format(), 184 Args :: [term()]. 185 186%% Log activities on the current connection. 187%% See ct_logs:cont_log/2 188 189cont_log(Format,Args) -> 190 log(cont_log, [Format, Args]). 191 192%% --------------------------------------------------------------------- 193 194-spec cont_log_no_timestamp(Format, Args) -> ok 195 when Format :: io:format(), 196 Args :: [term()]. 197 198%% Log activities on the current connection. 199%% See ct_logs:cont_log/2 200 201cont_log_no_timestamp(Format, Args) -> 202 log(cont_log_no_timestamp, [Format, Args]). 203 204%% --------------------------------------------------------------------- 205 206-spec end_log() -> ok. 207 208%% Log activities on the current connection. 209%% See ct_logs:end_log/0 210 211end_log() -> 212 log(end_log, []). 213 214%% --------------------------------------------------------------------- 215 216-spec do_within_time(Fun, Tmo) 217 -> Result 218 when Fun :: fun(), 219 Tmo :: non_neg_integer(), 220 Result :: term(). 221 222%% Return the result of evaluating Fun, or interrupt after Tmo 223%% milliseconds or if the connection is closed. Assumes the caller 224%% is trapping exits. 225 226do_within_time(Fun, Tmo) -> 227 do_within_time(Fun, Tmo, get(silent), get(conn_pid)). 228 229%% Work around the fact that ct_telnet calls do_within_time/2 in its 230%% init callback, before it returns the connection pid for init/1 to 231%% write to the process dictionary. Monitoring on self() is pointless, 232%% but harmless. Should really be fixed by not using the process 233%% dictionary to pass arguments. 234do_within_time(Fun, Tmo, Silent, undefined) -> 235 do_within_time(Fun, Tmo, Silent, self()); 236 237do_within_time(Fun, Tmo, Silent, ConnPid) -> 238 MRef = monitor(process, ConnPid), 239 Pid = spawn_link(fun() -> 240 ct_util:mark_process(), 241 put(silent, Silent), 242 exit({MRef, Fun()}) 243 end), 244 down(Pid, MRef, Tmo, failure). 245 246down(Pid, MRef, Tmo, Reason) -> 247 receive 248 {'EXIT', Pid, T} -> 249 infinity == Tmo orelse demonitor(MRef, [flush]), 250 rc(MRef, T, Reason); 251 {'DOWN', MRef, process, _, _} -> 252 down(Pid, MRef, connection_closed) 253 after Tmo -> 254 demonitor(MRef, [flush]), 255 down(Pid, MRef, timeout) 256 end. 257 258down(Pid, MRef, Reason) -> 259 exit(Pid, kill), 260 down(Pid, MRef, infinity, Reason). 261 262rc(Ref, {Ref, RC}, _Reason) -> 263 RC; 264rc(_, Reason, failure) -> %% failure before timeout or lost connection 265 {error, Reason}; 266rc(_, _, Reason) -> 267 {error, Reason}. 268 269%% =========================================================================== 270 271do_start(Address, InitData, CallbackMod, OptsList) -> 272 #gen_opts{name = Name} 273 = Opts 274 = make_opts(OptsList, #gen_opts{callback = CallbackMod, 275 address = Address, 276 init_data = InitData}), 277 %% Testing for an existing connection is slightly broken as long 278 %% as calls to start aren't serialized: concurrent starts can both 279 %% end up in the final clause. 280 case ct_util:does_connection_exist(Name, Address, CallbackMod) of 281 {ok, _Pid} = Ok when Opts#gen_opts.use_existing -> 282 log("ct_gen_conn:start","Using existing connection!\n", []), 283 Ok; 284 {ok, Pid} when not Opts#gen_opts.use_existing -> 285 {error, {connection_exists, Pid}}; 286 false -> 287 do_start(Opts) 288 end. 289 290do_start(Opts) -> 291 try gen_server:start(?MODULE, Opts, []) of 292 {ok, _} = Ok -> Ok; 293 {error, Reason} -> {error, rc(Reason)} 294 catch 295 exit: Reason -> 296 log("ct_gen_conn:start", 297 "Connection process died: ~tp\n", 298 [Reason]), 299 {error, {connection_process_died, Reason}} 300 end. 301 302%% Unwrap a {shutdown, _} exit reason for backwards compatibility. 303rc({shutdown, Reason}) -> Reason; 304rc(T) -> T. 305 306make_opts(Opts) -> 307 make_opts(Opts, #gen_opts{}). 308 309make_opts(Opts, #gen_opts{} = Rec) -> 310 lists:foldl(fun opt/2, Rec, Opts). 311 312opt({name, Name}, Rec) -> Rec#gen_opts{name = Name}; 313opt({reconnect, Bool}, Rec) -> Rec#gen_opts{reconnect = Bool}; 314opt({forward_messages, Bool}, Rec) -> Rec#gen_opts{forward = Bool}; 315opt({use_existing_connection, Bool}, Rec) -> Rec#gen_opts{use_existing = Bool}; 316opt({old, Bool}, Rec) -> Rec#gen_opts{old = Bool}. 317 318%% =========================================================================== 319 320call(Pid, Msg) -> 321 call(Pid, Msg, infinity). 322 323-spec call(Handle, Msg, Tmo) 324 -> term() 325 when Handle :: handle(), 326 Msg :: term(), 327 Tmo :: non_neg_integer() 328 | infinity. 329 330call(Pid, Msg, infinity = Tmo) -> 331 gen_call(Pid, Msg, Tmo); 332 333%% Spawn a middleman process if the call can timeout to avoid the 334%% possibilty of a reply being left in the caller's mailbox after a 335%% timeout. 336call(Pid, Msg, Tmo) -> 337 {_, MRef} = spawn_monitor(fun() -> exit(gen_call(Pid, Msg, Tmo)) end), 338 receive {'DOWN', MRef, process, _, RC} -> RC end. 339 340gen_call(Pid, Msg, Tmo) -> 341 try gen_server:call(Pid, Msg, Tmo) of 342 T -> retry(Pid, T, Tmo) 343 catch 344 exit: Reason -> {error, {process_down, Pid, rc(Pid, Reason)}} 345 end. 346 347retry(Pid, {retry, _} = T, Tmo) -> gen_call(Pid, T, Tmo); 348retry(_, T, _) -> T. 349 350%% Unwrap the MFA gen_server puts into exit reasons. 351rc(Pid, {Reason, {gen_server, call, _}}) -> 352 rc(Pid, Reason); 353 354rc(Pid, timeout) -> 355 log("ct_gen_conn", 356 "Connection process ~w not responding. Killing now!", 357 [Pid]), 358 exit(Pid, kill), 359 forced_termination; 360 361rc(_, Reason) -> 362 rc(Reason). 363 364return(From, Result) -> 365 gen_server:reply(From, Result). 366 367%% =========================================================================== 368%% gen_server callbacks 369 370%% init/1 371 372init(#gen_opts{callback = Mod, 373 name = Name, 374 address = Addr, 375 init_data = InitData} 376 = Opts) -> 377 process_flag(trap_exit, true), 378 ct_util:mark_process(), 379 put(silent, false), 380 try Mod:init(Name, Addr, InitData) of 381 {ok, ConnPid, State} when is_pid(ConnPid) -> 382 link(ConnPid), 383 put(conn_pid, ConnPid), 384 SrvPid = whereis(ct_util_server), 385 link(SrvPid), 386 ct_util:register_connection(Name, Addr, Mod, self()), 387 {ok, Opts#gen_opts{conn_pid = ConnPid, 388 cb_state = State, 389 ct_util_server = SrvPid}}; 390 {error, Reason} -> 391 {stop, {shutdown, Reason}} 392 catch 393 C: Reason when C /= error -> 394 {stop, {shutdown, Reason}} 395 end. 396 397%% handle_call/2 398 399handle_call(get_conn_pid, _From, #gen_opts{conn_pid = Pid} = Opts) -> 400 {reply, Pid, Opts}; 401 402handle_call(stop, _From, Opts) -> 403 {stop, normal, ok, Opts}; 404 405%% Only retry if failure is because of a reconnection. 406handle_call({retry, {Error, _Name, ConnPid, _Msg}}, 407 _From, 408 #gen_opts{conn_pid = ConnPid} 409 = Opts) -> 410 {reply, error_rc(Error), Opts}; 411 412handle_call({retry, {_Error, _Name, _CPid, Msg}}, 413 _From, 414 #gen_opts{callback = Mod, 415 cb_state = State} 416 = Opts) -> 417 log("Rerunning command","Connection reestablished. " 418 "Rerunning command...", 419 []), 420 {Reply, NewState} = Mod:handle_msg(Msg, State), 421 {reply, Reply, Opts#gen_opts{cb_state = NewState}}; 422 423handle_call(Msg, _From, #gen_opts{old = true, 424 callback = Mod, 425 cb_state = State} 426 = Opts) -> 427 {Reply, NewState} = Mod:handle_msg(Msg, State), 428 {reply, Reply, Opts#gen_opts{cb_state = NewState}}; 429 430handle_call(Msg, From, #gen_opts{callback = Mod, 431 cb_state = State} 432 = Opts) -> 433 case Mod:handle_msg(Msg, From, State) of 434 {reply, Reply, NewState} -> 435 {reply, Reply, Opts#gen_opts{cb_state = NewState}}; 436 {noreply, NewState} -> 437 {noreply, Opts#gen_opts{cb_state = NewState}}; 438 {stop, Reply, NewState} -> 439 {stop, normal, Reply, Opts#gen_opts{cb_state = NewState}} 440 end. 441 442%% handle_cast/2 443 444handle_cast(_, Opts) -> 445 {noreply, Opts}. 446 447%% handle_info/2 448 449handle_info({'EXIT', Pid, Reason}, 450 #gen_opts{reconnect = true, 451 conn_pid = Pid, 452 address = Addr} 453 = Opts) -> 454 log("Connection down!\nOpening new!", 455 "Reason: ~tp\nAddress: ~tp\n", 456 [Reason, Addr]), 457 case reconnect(Opts) of 458 {ok, NewPid, NewState} -> 459 link(NewPid), 460 put(conn_pid, NewPid), 461 {noreply, Opts#gen_opts{conn_pid = NewPid, 462 cb_state = NewState}}; 463 Error -> 464 log("Reconnect failed. Giving up!", 465 "Reason: ~tp\n", 466 [Error]), 467 {stop, normal, Opts} 468 end; 469 470handle_info({'EXIT', Pid, Reason}, 471 #gen_opts{reconnect = false, 472 conn_pid = Pid} 473 = Opts) -> 474 log("Connection closed!", "Reason: ~tp\n", [Reason]), 475 {stop, normal, Opts}; 476 477handle_info({'EXIT', Pid, Reason}, 478 #gen_opts{ct_util_server = Pid} 479 = Opts) -> 480 {stop, {shutdown, Reason}, Opts}; 481 482handle_info(Msg, #gen_opts{forward = true, 483 callback = Mod, 484 cb_state = State} 485 = Opts) -> 486 case Mod:handle_msg(Msg, State) of 487 {noreply, NewState} -> 488 {noreply, Opts#gen_opts{cb_state = NewState}}; 489 {stop, NewState} -> 490 {stop, normal, Opts#gen_opts{cb_state = NewState}} 491 end; 492 493handle_info(_, #gen_opts{} = Opts) -> 494 {noreply, Opts}. 495 496%% code_change/2 497 498code_change(_Vsn, State, _Extra) -> 499 {ok, State}. 500 501%% terminate/2 502 503%% Cleanup is only in this case since ct_util also cleans up and 504%% expects us not to, which is at least an odd expectation. 505terminate(normal, #gen_opts{callback = Mod, 506 conn_pid = Pid, 507 cb_state = State}) -> 508 ct_util:unregister_connection(self()), 509 unlink(Pid), 510 Mod:terminate(Pid, State); 511 512terminate(_, #gen_opts{}) -> 513 ok. 514 515%% =========================================================================== 516 517error_rc({error, _} = T) -> T; 518error_rc(Reason) -> {error, Reason}. 519 520reconnect(#gen_opts{callback = Mod, 521 address = Addr, 522 cb_state = State}) -> 523 Mod:reconnect(Addr, State). 524 525log(Func, Args) -> 526 case get(silent) of 527 true when not ?dbg -> 528 ok; 529 _ -> 530 apply(ct_logs, Func, Args) 531 end. 532