1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2014-2018. 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%% EXPERIMENTAL support for testing of upgrade. 22%% 23%% This is a library module containing support for test of release 24%% related activities in one or more applications. Currenty it 25%% supports upgrade only. 26%% 27%% == Configuration == 28%% 29%% In order to find version numbers of applications to upgrade from, 30%% ct_release_test needs to access and start old OTP 31%% releases. A `common_test' configuration file can be used for 32%% specifying the location of such releases, for example: 33%% 34%% ``` 35%% %% old-rels.cfg 36%% {otp_releases,[{r16b,"/path/to/R16B03-1/bin/erl"}, 37%% {'17',"/path/to/17.3/bin/erl"}]}.''' 38%% 39%% The configuration file should preferably point out the latest patch 40%% level on each major release. 41%% 42%% If no such configuration file is given, init/1 will return 43%% {skip,Reason} and any attempt at running upgrade/4 44%% will fail. 45%% 46%% == Callback functions == 47%% 48%% The following functions should be exported from a ct_release_test 49%% callback module. 50%% 51%% All callback functions are called on the node where the upgrade is 52%% executed. 53%% 54%% Module:upgrade_init(CtData,State) -> NewState 55%% Types: 56%% 57%% CtData = ct_data() 58%% State = NewState = cb_state() 59%% 60%% Initialyze system before upgrade test starts. 61%% 62%% This function is called before the upgrade is started. All 63%% applications given in upgrade/4 are already started by 64%% the boot script, so this callback is intended for additional 65%% initialization, if necessary. 66%% 67%% CtData is an opaque data structure which shall be used 68%% in any call to ct_release_test inside the callback. 69%% 70%% Example: 71%% 72%% upgrade_init(CtData,State) -> 73%% {ok,{FromVsn,ToVsn}} = ct_release_test:get_app_vsns(CtData,myapp), 74%% open_connection(State). 75%% 76%% Module:upgrade_upgraded(CtData,State) -> NewState 77%% Types: 78%% 79%% CtData = ct_data() 80%% State = NewState = cb_state() 81%% 82%% Check that upgrade was successful. 83%% 84%% This function is called after the release_handler has 85%% successfully unpacked and installed the new release, and it has 86%% been made permanent. It allows application specific checks to 87%% ensure that the upgrade was successful. 88%% 89%% CtData is an opaque data structure which shall be used 90%% in any call to ct_release_test inside the callback. 91%% 92%% Example: 93%% 94%% upgrade_upgraded(CtData,State) -> 95%% check_connection_still_open(State). 96%% 97%% Module:upgrade_downgraded(CtData,State) -> NewState 98%% Types: 99%% 100%% CtData = ct_data() 101%% State = NewState = cb_state() 102%% 103%% Check that downgrade was successful. 104%% 105%% This function is called after the release_handler has 106%% successfully re-installed the original release, and it has been 107%% made permanent. It allows application specific checks to ensure 108%% that the downgrade was successful. 109%% 110%% CtData is an opaque data structure which shall be used 111%% in any call to ct_release_test inside the callback. 112%% 113%% Example: 114%% 115%% upgrade_downgraded(CtData,State) -> 116%% check_connection_closed(State). 117%%----------------------------------------------------------------- 118-module(ct_release_test). 119 120-export([init/1, upgrade/4, cleanup/1, get_app_vsns/2, get_appup/2]). 121 122-include_lib("kernel/include/file.hrl"). 123 124%%----------------------------------------------------------------- 125-define(testnode, 'ct_release_test-upgrade'). 126-define(exclude_apps, [hipe, dialyzer]). % never include these apps 127 128%%----------------------------------------------------------------- 129-record(ct_data, {from,to}). 130 131%%----------------------------------------------------------------- 132-type config() :: [{atom(),term()}]. 133-type cb_state() :: term(). 134-opaque ct_data() :: #ct_data{}. 135-export_type([ct_data/0]). 136 137-callback upgrade_init(ct_data(),cb_state()) -> cb_state(). 138-callback upgrade_upgraded(ct_data(),cb_state()) -> cb_state(). 139-callback upgrade_downgraded(ct_data(),cb_state()) -> cb_state(). 140 141%%----------------------------------------------------------------- 142-spec init(Config) -> Result when 143 Config :: config(), 144 Result :: config() | SkipOrFail, 145 SkipOrFail :: {skip,Reason} | {fail,Reason}. 146%% Initialize ct_release_test. 147%% 148%% This function can be called from any of the 149%% `init_per_*' functions in the test suite. It updates 150%% the given `Config' with data that will be 151%% used by future calls to other functions in this module. The 152%% returned configuration must therefore also be returned from 153%% the calling `init_per_*'. 154%% 155%% If the initialization fails, e.g. if a required release can 156%% not be found, the function returns `{skip,Reason}'. In 157%% this case the other test support functions in this mudule 158%% can not be used. 159%% 160%% Example: 161%% 162%% init_per_suite(Config) -> 163%% ct_release_test:init(Config). 164%% 165init(Config) -> 166 try init_upgrade_test() of 167 {Major,Minor} -> 168 [{release_test,[{major,Major},{minor,Minor}]} | Config] 169 catch throw:Thrown -> 170 Thrown 171 end. 172 173%%----------------------------------------------------------------- 174-spec upgrade(App,Level,Callback,Config) -> any() when 175 App :: atom(), 176 Level :: minor | major, 177 Callback :: {module(),InitState}, 178 InitState :: cb_state(), 179 Config :: config(); 180 (Apps,Level,Callback,Config) -> any() when 181 Apps :: [App], 182 App :: atom(), 183 Level :: minor | major, 184 Callback :: {module(),InitState}, 185 InitState :: cb_state(), 186 Config :: config(). 187%% Test upgrade of the given application(s). 188%% 189%% This function can be called from a test case. It requires that 190%% `Config' has been initialized by calling 191%% init/1 prior to this, for example from `init_per_suite/1'. 192%% 193%% Upgrade tests are performed as follows: 194%% 195%% Figure out which OTP release to test upgrade 196%% from. Start a node running that release and find the 197%% application versions on that node. Terminate the 198%% node. 199%% Figure out all dependencies for the applications under 200%% test. 201%% Create a release containing the core 202%% applications `kernel', `stdlib' and `sasl' 203%% in addition to the application(s) under test and all 204%% dependencies of these. The versions of the applications 205%% under test will be the ones found on the OTP release to 206%% upgrade from. The versions of all other applications will 207%% be those found on the current node, i.e. the common_test 208%% node. This is the "From"-release. 209%% Create another release containing the same 210%% applications as in the previous step, but with all 211%% application versions taken from the current node. This is 212%% the "To"-release. 213%% Install the "From"-release and start a new node 214%% running this release. 215%% Perform the upgrade test and allow customized 216%% control by using callbacks: 217%% Callback: `upgrade_init/2' 218%% Unpack the new release 219%% Install the new release 220%% Callback: `upgrade_upgraded/2' 221%% Install the original release 222%% Callback: `upgrade_downgraded/2' 223%% 224%% `App' or `Apps' 225%% specifies the applications under test, i.e. the applications 226%% which shall be upgraded. All other applications that are 227%% included have the same releases in the "From"- and 228%% "To"-releases and will therefore not be upgraded. 229%% 230%% `Level' specifies which OTP release to 231%% pick the "From" versions from. 232%% major 233%% From verions are picked from the previous major 234%% release. For example, if the test is run on an OTP-17 235%% node, ct_release_test will pick the application 236%% "From" versions from an OTP installation running OTP 237%% R16B. 238%% 239%% minor 240%% From verions are picked from the current major 241%% release. For example, if the test is run on an OTP-17 242%% node, ct_release_test will pick the application 243%% "From" versions from an OTP installation running an 244%% earlier patch level of OTP-17. 245%% 246%% The application "To" versions are allways picked from the 247%% current node, i.e. the common_test node. 248%% 249%% `Callback' specifies the module (normally the 250%% test suite) which implements the Callback functions, and 251%% the initial value of the `State' variable used in these 252%% functions. 253%% 254%% `Config' is the input argument received 255%% in the test case function. 256%% 257%% Example: 258%% 259%% minor_upgrade(Config) -> 260%% ct_release_test:upgrade(ssl,minor,{?MODULE,[]},Config). 261%% 262upgrade(App,Level,Callback,Config) when is_atom(App) -> 263 upgrade([App],Level,Callback,Config); 264upgrade(Apps,Level,Callback,Config) -> 265 Dir = proplists:get_value(priv_dir,Config), 266 CreateDir = filename:join([Dir,Level,create]), 267 InstallDir = filename:join([Dir,Level,install]), 268 ok = filelib:ensure_dir(filename:join(CreateDir,"*")), 269 ok = filelib:ensure_dir(filename:join(InstallDir,"*")), 270 try upgrade(Apps,Level,Callback,CreateDir,InstallDir,Config) of 271 ok -> 272 %%rm_rf(CreateDir), 273 Tars = filelib:wildcard(filename:join(CreateDir,"*.tar.gz")), 274 _ = [file:delete(Tar) || Tar <- Tars], 275 rm_rf(InstallDir), 276 ok 277 catch throw:{fail,Reason} -> 278 ct:fail(Reason); 279 throw:{skip,Reason} -> 280 rm_rf(CreateDir), 281 rm_rf(InstallDir), 282 {skip,Reason} 283 after 284 %% Brutally kill all nodes that erroneously survived the test. 285 %% Note, we will not reach this if the test fails with a 286 %% timetrap timeout in the test suite! Thus we can have 287 %% hanging nodes... 288 Nodes = lists:filter(fun(Node) -> 289 case atom_to_list(Node) of 290 "ct_release_test-" ++_ -> true; 291 _ -> false 292 end 293 end, 294 nodes()), 295 [rpc:call(Node,erlang,halt,[]) || Node <- Nodes] 296 end. 297 298%%----------------------------------------------------------------- 299-spec cleanup(Config) -> Result when 300 Config :: config(), 301 Result :: config(). 302%% Clean up after tests. 303%% 304%% This function shall be called from the `end_per_*' function 305%% complementing the `init_per_*' function where init/1 306%% is called. 307%% 308%% It cleans up after the test, for example kills hanging 309%% nodes. 310%% 311%% Example: 312%% 313%% end_per_suite(Config) -> 314%% ct_release_test:cleanup(Config). 315%% 316cleanup(Config) -> 317 AllNodes = [node_name(?testnode)|nodes()], 318 Nodes = lists:filter(fun(Node) -> 319 case atom_to_list(Node) of 320 "ct_release_test-" ++_ -> true; 321 _ -> false 322 end 323 end, 324 AllNodes), 325 _ = [rpc:call(Node,erlang,halt,[]) || Node <- Nodes], 326 Config. 327 328%%----------------------------------------------------------------- 329-spec get_app_vsns(CtData,App) -> {ok,{From,To}} | {error,Reason} when 330 CtData :: ct_data(), 331 App :: atom(), 332 From :: string(), 333 To :: string(), 334 Reason :: {app_not_found,App}. 335%% Get versions involved in this upgrade for the given application. 336%% 337%% This function can be called from inside any of the callback 338%% functions. It returns the old (From) and new (To) versions involved 339%% in the upgrade/downgrade test for the given application. 340%% 341%% CtData must be the first argument received in the 342%% calling callback function - an opaque data structure set by 343%% ct_release_tests. 344get_app_vsns(#ct_data{from=FromApps,to=ToApps},App) -> 345 case {lists:keyfind(App,1,FromApps),lists:keyfind(App,1,ToApps)} of 346 {{App,FromVsn,_},{App,ToVsn,_}} -> 347 {ok,{FromVsn,ToVsn}}; 348 _ -> 349 {error,{app_not_found,App}} 350 end. 351 352%%----------------------------------------------------------------- 353-spec get_appup(CtData,App) -> {ok,Appup} | {error,Reason} when 354 CtData :: ct_data(), 355 App :: atom(), 356 Appup :: {From,To,Up,Down}, 357 From :: string(), 358 To :: string(), 359 Up :: [Instr], 360 Down :: [Instr], 361 Instr :: term(), 362 Reason :: {app_not_found,App} | {vsn_not_found,{App,From}}. 363%% Get appup instructions for the given application. 364%% 365%% This function can be called from inside any of the callback 366%% functions. It reads the appup file for the given application and 367%% returns the instructions for upgrade and downgrade for the versions 368%% in the test. 369%% 370%% CtData must be the first argument received in the 371%% calling callback function - an opaque data structure set by 372%% ct_release_tests. 373%% 374%% See reference manual for appup files for types definitions for the 375%% instructions. 376get_appup(#ct_data{from=FromApps,to=ToApps},App) -> 377 case lists:keyfind(App,1,ToApps) of 378 {App,ToVsn,ToDir} -> 379 Appup = filename:join([ToDir, "ebin", atom_to_list(App)++".appup"]), 380 {ok, [{ToVsn, Ups, Downs}]} = file:consult(Appup), 381 {App,FromVsn,_} = lists:keyfind(App,1,FromApps), 382 case {systools_relup:appup_search_for_version(FromVsn,Ups), 383 systools_relup:appup_search_for_version(FromVsn,Downs)} of 384 {{ok,Up},{ok,Down}} -> 385 {ok,{FromVsn,ToVsn,Up,Down}}; 386 _ -> 387 {error,{vsn_not_found,{App,FromVsn}}} 388 end; 389 false -> 390 {error,{app_not_found,App}} 391 end. 392 393%%----------------------------------------------------------------- 394init_upgrade_test() -> 395 %% Check that a real release is running, not e.g. cerl 396 ok = application:ensure_started(sasl), 397 case release_handler:which_releases() of 398 [{_,_,[],_}] -> 399 %% Fake release, no applications 400 throw({skip, "Need a real release running to create other releases"}); 401 _ -> 402 Major = init_upgrade_test(major), 403 Minor = init_upgrade_test(minor), 404 {Major,Minor} 405 end. 406 407init_upgrade_test(Level) -> 408 {FromVsn,ToVsn} = get_rels(Level), 409 OldRel = 410 case test_server:is_release_available(FromVsn) of 411 true -> 412 {release,FromVsn}; 413 false -> 414 case ct:get_config({otp_releases,list_to_atom(FromVsn)}) of 415 undefined -> 416 false; 417 Prog0 -> 418 case os:find_executable(Prog0) of 419 false -> 420 false; 421 Prog -> 422 {prog,Prog} 423 end 424 end 425 end, 426 case OldRel of 427 false -> 428 ct:log("Release ~tp is not available." 429 " Upgrade on '~p' level can not be tested.", 430 [FromVsn,Level]), 431 undefined; 432 _ -> 433 init_upgrade_test(FromVsn,ToVsn,OldRel) 434 end. 435 436get_rels(major) -> 437 %% Given that the current major release is X, then this is an 438 %% upgrade from major release X-1 to the current release. 439 Current = erlang:system_info(otp_release), 440 PreviousMajor = previous_major(Current), 441 {PreviousMajor,Current}; 442get_rels(minor) -> 443 %% Given that this is a (possibly) patched version of major 444 %% release X, then this is an upgrade from major release X to the 445 %% current release. 446 CurrentMajor = erlang:system_info(otp_release), 447 Current = CurrentMajor++"_patched", 448 {CurrentMajor,Current}. 449 450init_upgrade_test(FromVsn,ToVsn,OldRel) -> 451 Name = list_to_atom("ct_release_test-otp-"++FromVsn), 452 ct:log("Starting node to fetch application versions to upgrade from"), 453 {ok,Node} = test_server:start_node(Name,peer,[{erl,[OldRel]}]), 454 {Apps,Path} = fetch_all_apps(Node), 455 test_server:stop_node(Node), 456 {FromVsn,ToVsn,Apps,Path}. 457 458fetch_all_apps(Node) -> 459 Paths = rpc:call(Node,code,get_path,[]), 460 %% Find all possible applications in the path 461 AppFiles = 462 lists:flatmap( 463 fun(P) -> 464 filelib:wildcard(filename:join(P,"*.app")) 465 end, 466 Paths), 467 %% Figure out which version of each application is running on this 468 %% node. Using application:load and application:get_key instead of 469 %% reading the .app files since there might be multiple versions 470 %% of a .app file and we only want the one that is actually 471 %% running. 472 AppVsns = 473 lists:flatmap( 474 fun(F) -> 475 A = list_to_atom(filename:basename(filename:rootname(F))), 476 _ = rpc:call(Node,application,load,[A]), 477 case rpc:call(Node,application,get_key,[A,vsn]) of 478 {ok,V} -> [{A,V}]; 479 _ -> [] 480 end 481 end, 482 AppFiles), 483 ErtsVsn = rpc:call(Node, erlang, system_info, [version]), 484 {[{erts,ErtsVsn}|AppVsns], Paths}. 485 486 487%%----------------------------------------------------------------- 488upgrade(Apps,Level,Callback,CreateDir,InstallDir,Config) -> 489 ct:log("Test upgrade of the following applications: ~p",[Apps]), 490 ct:log(".rel files and start scripts are created in:~n~ts",[CreateDir]), 491 ct:log("The release is installed in:~n~ts",[InstallDir]), 492 case proplists:get_value(release_test,Config) of 493 undefined -> 494 throw({fail,"ct_release_test:init/1 not run"}); 495 RTConfig -> 496 case proplists:get_value(Level,RTConfig) of 497 undefined -> 498 throw({skip,"Old release not available"}); 499 Data -> 500 {FromVsn,FromRel,FromAppsVsns} = 501 target_system(Apps, CreateDir, InstallDir, Data), 502 {ToVsn,ToRel,ToAppsVsns} = 503 upgrade_system(Apps, FromRel, CreateDir, 504 InstallDir, Data), 505 ct:log("Upgrade from: OTP-~ts, ~tp",[FromVsn, FromAppsVsns]), 506 ct:log("Upgrade to: OTP-~ts, ~tp",[ToVsn, ToAppsVsns]), 507 do_upgrade(Callback, FromVsn, FromAppsVsns, ToRel, 508 ToAppsVsns, InstallDir) 509 end 510 end. 511 512%%% This is similar to sasl/examples/src/target_system.erl, but with 513%%% the following adjustments: 514%%% - add a log directory 515%%% - use an own 'start' script 516%%% - chmod 'start' and 'start_erl' 517target_system(Apps,CreateDir,InstallDir,{FromVsn,_,AllAppsVsns,Path}) -> 518 RelName0 = "otp-"++FromVsn, 519 520 AppsVsns = [{A,V} || {A,V} <- AllAppsVsns, lists:member(A,Apps)], 521 {RelName,ErtsVsn} = create_relfile(AppsVsns,CreateDir,RelName0,FromVsn), 522 523 %% Create .script and .boot 524 ok = systools(make_script,[RelName,[{path,Path}]]), 525 526 %% Create base tar file - i.e. erts and all apps 527 ok = systools(make_tar,[RelName,[{erts,code:root_dir()}, 528 {path,Path}]]), 529 530 %% Unpack the tar to complete the installation 531 erl_tar:extract(RelName ++ ".tar.gz", [{cwd, InstallDir}, compressed]), 532 533 %% Add bin and log dirs 534 BinDir = filename:join([InstallDir, "bin"]), 535 ok = make_dir(BinDir), 536 ok = make_dir(filename:join(InstallDir,"log")), 537 538 %% Delete start scripts - they will be added later 539 ErtsBinDir = filename:join([InstallDir, "erts-" ++ ErtsVsn, "bin"]), 540 ok = delete_file(filename:join([ErtsBinDir, "erl"])), 541 ok = delete_file(filename:join([ErtsBinDir, "start"])), 542 ok = delete_file(filename:join([ErtsBinDir, "start_erl"])), 543 544 %% Copy .boot to bin/start.boot 545 copy_file(RelName++".boot",filename:join([BinDir, "start.boot"])), 546 547 %% Copy scripts from erts-xxx/bin to bin 548 copy_file(filename:join([ErtsBinDir, "epmd"]), 549 filename:join([BinDir, "epmd"]), [preserve]), 550 copy_file(filename:join([ErtsBinDir, "run_erl"]), 551 filename:join([BinDir, "run_erl"]), [preserve]), 552 copy_file(filename:join([ErtsBinDir, "to_erl"]), 553 filename:join([BinDir, "to_erl"]), [preserve]), 554 555 %% create start_erl.data, sys.config and start.src 556 StartErlData = filename:join([InstallDir, "releases", "start_erl.data"]), 557 write_file(StartErlData, io_lib:fwrite("~s ~s~n", [ErtsVsn, FromVsn])), 558 SysConfig = filename:join([InstallDir, "releases", FromVsn, "sys.config"]), 559 write_file(SysConfig, "[]."), 560 StartSrc = filename:join(ErtsBinDir,"start.src"), 561 write_file(StartSrc,start_script()), 562 ok = file:change_mode(StartSrc,8#0755), 563 564 %% Make start_erl executable 565 %% (this has been fixed in OTP 17 - it is now installed with 566 %% $INSTALL_SCRIPT instead of $INSTALL_DATA and should therefore 567 %% be executable from the start) 568 ok = file:change_mode(filename:join(ErtsBinDir,"start_erl.src"),8#0755), 569 570 %% Substitute variables in erl.src, start.src and start_erl.src 571 %% (.src found in erts-xxx/bin - result stored in bin) 572 subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir, 573 [{"FINAL_ROOTDIR", InstallDir}, {"EMU", "beam"}], 574 [preserve]), 575 576 %% Create RELEASES 577 RelFile = filename:join([InstallDir, "releases", 578 filename:basename(RelName) ++ ".rel"]), 579 release_handler:create_RELEASES(InstallDir, RelFile), 580 581 {FromVsn, RelName,AppsVsns}. 582 583systools(Func,Args) -> 584 case apply(systools,Func,Args) of 585 ok -> 586 ok; 587 error -> 588 throw({fail,{systools,Func,Args}}) 589 end. 590 591%%% This is a copy of $ROOT/erts-xxx/bin/start.src, modified to add 592%%% sname and heart 593start_script() -> 594 ["#!/bin/sh\n" 595 "ROOTDIR=%FINAL_ROOTDIR%\n" 596 "\n" 597 "if [ -z \"$RELDIR\" ]\n" 598 "then\n" 599 " RELDIR=$ROOTDIR/releases\n" 600 "fi\n" 601 "\n" 602 "START_ERL_DATA=${1:-$RELDIR/start_erl.data}\n" 603 "\n" 604 "$ROOTDIR/bin/run_erl -daemon /tmp/ $ROOTDIR/log \"exec $ROOTDIR/bin/start_erl $ROOTDIR $RELDIR $START_ERL_DATA -sname ",atom_to_list(?testnode)," -heart\"\n"]. 605 606%%% Create a release containing the current (the test node) OTP 607%%% release, including relup to allow upgrade from an earlier OTP 608%%% release. 609upgrade_system(Apps, FromRel, CreateDir, InstallDir, {_,ToVsn,_,_}) -> 610 ct:log("Generating release to upgrade to."), 611 612 RelName0 = "otp-"++ToVsn, 613 614 AppsVsns = get_vsns(Apps), 615 {RelName,_} = create_relfile(AppsVsns,CreateDir,RelName0,ToVsn), 616 FromPath = filename:join([InstallDir,lib,"*",ebin]), 617 618 ok = systools(make_script,[RelName]), 619 ok = systools(make_relup,[RelName,[FromRel],[FromRel], 620 [{path,[FromPath]}, 621 {outdir,CreateDir}]]), 622 SysConfig = filename:join([CreateDir, "sys.config"]), 623 write_file(SysConfig, "[]."), 624 625 ok = systools(make_tar,[RelName,[{erts,code:root_dir()}]]), 626 627 {ToVsn, RelName,AppsVsns}. 628 629%%% Start a new node running the release from target_system/6 630%%% above. Then upgrade to the system from upgrade_system/6. 631do_upgrade({Cb,InitState},FromVsn,FromAppsVsns,ToRel,ToAppsVsns,InstallDir) -> 632 ct:log("Upgrade test attempting to start node.~n" 633 "If test fails, logs can be found in:~n~ts", 634 [filename:join(InstallDir,log)]), 635 Start = filename:join([InstallDir,bin,start]), 636 {ok,Node} = start_node(Start,FromVsn,FromAppsVsns), 637 638 ct:log("Node started: ~p",[Node]), 639 CtData = #ct_data{from = [{A,V,code:lib_dir(A)} || {A,V} <- FromAppsVsns], 640 to=[{A,V,code:lib_dir(A)} || {A,V} <- ToAppsVsns]}, 641 State1 = do_callback(Node,Cb,upgrade_init,[CtData,InitState]), 642 643 [{"OTP upgrade test",FromVsn,_,permanent}] = 644 rpc:call(Node,release_handler,which_releases,[]), 645 ToRelName = filename:basename(ToRel), 646 copy_file(ToRel++".tar.gz", 647 filename:join([InstallDir,releases,ToRelName++".tar.gz"])), 648 ct:log("Unpacking new release"), 649 {ok,ToVsn} = rpc:call(Node,release_handler,unpack_release,[ToRelName]), 650 [{"OTP upgrade test",ToVsn,_,unpacked}, 651 {"OTP upgrade test",FromVsn,_,permanent}] = 652 rpc:call(Node,release_handler,which_releases,[]), 653 ct:log("Installing new release"), 654 case rpc:call(Node,release_handler,install_release,[ToVsn]) of 655 {ok,FromVsn,_} -> 656 ok; 657 {continue_after_restart,FromVsn,_} -> 658 ct:log("Waiting for node restart") 659 end, 660 %% even if install_release returned {ok,...} there might be an 661 %% emulator restart (instruction restart_emulator), so we must 662 %% always make sure the node is running. 663 {ok, _} = wait_node_up(current,ToVsn,ToAppsVsns), 664 665 [{"OTP upgrade test",ToVsn,_,current}, 666 {"OTP upgrade test",FromVsn,_,permanent}] = 667 rpc:call(Node,release_handler,which_releases,[]), 668 ct:log("Permanenting new release"), 669 ok = rpc:call(Node,release_handler,make_permanent,[ToVsn]), 670 [{"OTP upgrade test",ToVsn,_,permanent}, 671 {"OTP upgrade test",FromVsn,_,old}] = 672 rpc:call(Node,release_handler,which_releases,[]), 673 674 State2 = do_callback(Node,Cb,upgrade_upgraded,[CtData,State1]), 675 676 ct:log("Re-installing old release"), 677 case rpc:call(Node,release_handler,install_release,[FromVsn]) of 678 {ok,FromVsn,_} -> 679 ok; 680 {continue_after_restart,FromVsn,_} -> 681 ct:log("Waiting for node restart") 682 end, 683 %% even if install_release returned {ok,...} there might be an 684 %% emulator restart (instruction restart_emulator), so we must 685 %% always make sure the node is running. 686 {ok, _} = wait_node_up(current,FromVsn,FromAppsVsns), 687 688 [{"OTP upgrade test",ToVsn,_,permanent}, 689 {"OTP upgrade test",FromVsn,_,current}] = 690 rpc:call(Node,release_handler,which_releases,[]), 691 ct:log("Permanenting old release"), 692 ok = rpc:call(Node,release_handler,make_permanent,[FromVsn]), 693 [{"OTP upgrade test",ToVsn,_,old}, 694 {"OTP upgrade test",FromVsn,_,permanent}] = 695 rpc:call(Node,release_handler,which_releases,[]), 696 697 _State3 = do_callback(Node,Cb,upgrade_downgraded,[CtData,State2]), 698 699 ct:log("Terminating node ~p",[Node]), 700 erlang:monitor_node(Node,true), 701 _ = rpc:call(Node,init,stop,[]), 702 receive {nodedown,Node} -> ok end, 703 ct:log("Node terminated"), 704 705 ok. 706 707do_callback(Node,Mod,Func,Args) -> 708 Dir = filename:dirname(code:which(Mod)), 709 _ = rpc:call(Node,code,add_path,[Dir]), 710 ct:log("Calling ~p:~tp/1",[Mod,Func]), 711 R = rpc:call(Node,Mod,Func,Args), 712 ct:log("~p:~tp/~w returned: ~tp",[Mod,Func,length(Args),R]), 713 case R of 714 {badrpc,Error} -> 715 throw({fail,{test_upgrade_callback,Mod,Func,Args,Error}}); 716 NewState -> 717 NewState 718 end. 719 720%%% Library functions 721previous_major("17") -> 722 "r16b"; 723previous_major(Rel) -> 724 integer_to_list(list_to_integer(Rel)-1). 725 726create_relfile(AppsVsns,CreateDir,RelName0,RelVsn) -> 727 UpgradeAppsVsns = [{A,V,restart_type(A)} || {A,V} <- AppsVsns], 728 729 CoreAppVsns0 = get_vsns([kernel,stdlib,sasl]), 730 CoreAppVsns = 731 [{A,V,restart_type(A)} || {A,V} <- CoreAppVsns0, 732 false == lists:keymember(A,1,AppsVsns)], 733 734 Apps = [App || {App,_} <- AppsVsns], 735 StartDepsVsns = get_start_deps(Apps,CoreAppVsns), 736 StartApps = [StartApp || {StartApp,_,_} <- StartDepsVsns] ++ Apps, 737 738 {RuntimeDepsVsns,_} = get_runtime_deps(StartApps,StartApps,[],[]), 739 740 AllAppsVsns0 = StartDepsVsns ++ UpgradeAppsVsns ++ RuntimeDepsVsns, 741 742 %% Should test tools really be included? Some library functions 743 %% here could be used by callback, but not everything since 744 %% processes of these applications will not be running. 745 TestToolAppsVsns0 = get_vsns([common_test]), 746 TestToolAppsVsns = 747 [{A,V,none} || {A,V} <- TestToolAppsVsns0, 748 false == lists:keymember(A,1,AllAppsVsns0)], 749 750 AllAppsVsns1 = AllAppsVsns0 ++ TestToolAppsVsns, 751 AllAppsVsns = [AV || AV={A,_,_} <- AllAppsVsns1, 752 false == lists:member(A,?exclude_apps)], 753 754 ErtsVsn = erlang:system_info(version), 755 756 %% Create the .rel file 757 RelContent = {release,{"OTP upgrade test",RelVsn},{erts,ErtsVsn},AllAppsVsns}, 758 RelName = filename:join(CreateDir,RelName0), 759 RelFile = RelName++".rel", 760 {ok,Fd} = file:open(RelFile,[write,{encoding,utf8}]), 761 io:format(Fd,"~tp.~n",[RelContent]), 762 ok = file:close(Fd), 763 {RelName,ErtsVsn}. 764 765get_vsns(Apps) -> 766 [begin 767 _ = application:load(A), 768 {ok,V} = application:get_key(A,vsn), 769 {A,V} 770 end || A <- Apps]. 771 772get_start_deps([App|Apps],Acc) -> 773 _ = application:load(App), 774 {ok,StartDeps} = application:get_key(App,applications), 775 StartDepsVsns = 776 [begin 777 _ = application:load(StartApp), 778 {ok,StartVsn} = application:get_key(StartApp,vsn), 779 {StartApp,StartVsn,restart_type(StartApp)} 780 end || StartApp <- StartDeps, 781 false == lists:keymember(StartApp,1,Acc)], 782 DepsStartDeps = get_start_deps(StartDeps,Acc ++ StartDepsVsns), 783 get_start_deps(Apps,DepsStartDeps); 784get_start_deps([],Acc) -> 785 Acc. 786 787get_runtime_deps([App|Apps],StartApps,Acc,Visited) -> 788 case lists:member(App,Visited) of 789 true -> 790 get_runtime_deps(Apps,StartApps,Acc,Visited); 791 false -> 792 %% runtime_dependencies should be possible to read with 793 %% application:get_key/2, but still isn't so we need to 794 %% read the .app file... 795 AppFile = code:where_is_file(atom_to_list(App) ++ ".app"), 796 {ok,[{application,App,Attrs}]} = file:consult(AppFile), 797 RuntimeDeps = 798 lists:flatmap( 799 fun(Str) -> 800 [RuntimeAppStr,_] = string:lexemes(Str,"-"), 801 RuntimeApp = list_to_atom(RuntimeAppStr), 802 case {lists:keymember(RuntimeApp,1,Acc), 803 lists:member(RuntimeApp,StartApps)} of 804 {false,false} when RuntimeApp=/=erts -> 805 [RuntimeApp]; 806 _ -> 807 [] 808 end 809 end, 810 proplists:get_value(runtime_dependencies,Attrs,[])), 811 RuntimeDepsVsns = 812 [begin 813 _ = application:load(RuntimeApp), 814 {ok,RuntimeVsn} = application:get_key(RuntimeApp,vsn), 815 {RuntimeApp,RuntimeVsn,none} 816 end || RuntimeApp <- RuntimeDeps], 817 {DepsRuntimeDeps,NewVisited} = 818 get_runtime_deps(RuntimeDeps,StartApps,Acc++RuntimeDepsVsns,[App|Visited]), 819 get_runtime_deps(Apps,StartApps,DepsRuntimeDeps,NewVisited) 820 end; 821get_runtime_deps([],_,Acc,Visited) -> 822 {Acc,Visited}. 823 824restart_type(App) when App==kernel; App==stdlib; App==sasl -> 825 permanent; 826restart_type(_) -> 827 temporary. 828 829copy_file(Src, Dest) -> 830 copy_file(Src, Dest, []). 831 832copy_file(Src, Dest, Opts) -> 833 {ok,_} = file:copy(Src, Dest), 834 case lists:member(preserve, Opts) of 835 true -> 836 {ok, FileInfo} = file:read_file_info(Src), 837 ok = file:write_file_info(Dest, FileInfo); 838 false -> 839 ok 840 end. 841 842write_file(FName, Conts) -> 843 file:write_file(FName, unicode:characters_to_binary(Conts)). 844 845%% Substitute all occurrences of %Var% for Val in the given scripts 846subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) -> 847 lists:foreach(fun(Script) -> 848 subst_src_script(Script, SrcDir, DestDir, 849 Vars, Opts) 850 end, Scripts). 851 852subst_src_script(Script, SrcDir, DestDir, Vars, Opts) -> 853 subst_file(filename:join([SrcDir, Script ++ ".src"]), 854 filename:join([DestDir, Script]), 855 Vars, Opts). 856 857subst_file(Src, Dest, Vars, Opts) -> 858 {ok, Bin} = file:read_file(Src), 859 Conts = unicode:characters_to_list(Bin), 860 NConts = subst(Conts, Vars), 861 write_file(Dest, NConts), 862 case lists:member(preserve, Opts) of 863 true -> 864 {ok, FileInfo} = file:read_file_info(Src), 865 file:write_file_info(Dest, FileInfo); 866 false -> 867 ok 868 end. 869 870subst(Str, [{Var,Val}|Vars]) -> 871 subst(re:replace(Str,"%"++Var++"%",Val,[{return,list},unicode]),Vars); 872subst(Str, []) -> 873 Str. 874 875%%% Start a node by executing the given start command. This node will 876%%% be used for upgrade. 877start_node(Start,ExpVsn,ExpAppsVsns) -> 878 Port = open_port({spawn_executable, Start}, []), 879 unlink(Port), 880 erlang:port_close(Port), 881 wait_node_up(permanent,ExpVsn,ExpAppsVsns). 882 883wait_node_up(ExpStatus,ExpVsn,ExpAppsVsns) -> 884 Node = node_name(?testnode), 885 wait_node_up(Node,ExpStatus,ExpVsn,lists:keysort(1,ExpAppsVsns),60). 886 887wait_node_up(Node,ExpStatus,ExpVsn,ExpAppsVsns,0) -> 888 test_server:fail({node_not_started,app_check_failed,ExpVsn,ExpAppsVsns, 889 rpc:call(Node,release_handler,which_releases,[ExpStatus]), 890 rpc:call(Node,application,which_applications,[])}); 891wait_node_up(Node,ExpStatus,ExpVsn,ExpAppsVsns,N) -> 892 case {rpc:call(Node,release_handler,which_releases,[ExpStatus]), 893 rpc:call(Node, application, which_applications, [])} of 894 {[{_,ExpVsn,_,_}],Apps} when is_list(Apps) -> 895 case [{A,V} || {A,_,V} <- lists:keysort(1,Apps), 896 lists:keymember(A,1,ExpAppsVsns)] of 897 ExpAppsVsns -> 898 {ok,Node}; 899 _ -> 900 timer:sleep(2000), 901 wait_node_up(Node,ExpStatus,ExpVsn,ExpAppsVsns,N-1) 902 end; 903 _ -> 904 timer:sleep(2000), 905 wait_node_up(Node,ExpStatus,ExpVsn,ExpAppsVsns,N-1) 906 end. 907 908node_name(Sname) -> 909 {ok,Host} = inet:gethostname(), 910 list_to_atom(atom_to_list(Sname) ++ "@" ++ Host). 911 912rm_rf(Dir) -> 913 case file:read_file_info(Dir) of 914 {ok, #file_info{type = directory}} -> 915 {ok, Content} = file:list_dir_all(Dir), 916 [rm_rf(filename:join(Dir,C)) || C <- Content], 917 ok=file:del_dir(Dir), 918 ok; 919 {ok, #file_info{}} -> 920 ok=file:delete(Dir); 921 _ -> 922 ok 923 end. 924 925delete_file(FileName) -> 926 case file:delete(FileName) of 927 {error, enoent} -> 928 ok; 929 Else -> 930 Else 931 end. 932 933make_dir(Dir) -> 934 case file:make_dir(Dir) of 935 {error, eexist} -> 936 ok; 937 Else -> 938 Else 939 end. 940