%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2014. 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% -module(upgrade_SUITE). -compile(export_all). -include_lib("common_test/include/ct.hrl"). -include_lib("kernel/include/file.hrl"). -define(upgr_sname,otp_upgrade). %% Applications that are excluded from this test because they can not %% just be started in a new node with out specific configuration. -define(start_exclude, [cosEvent,cosEventDomain,cosFileTransfer,cosNotification, cosProperty,cosTime,cosTransactions,erts,ic,netconf,orber, safe]). %% Applications that are excluded from this test because they don't %% have useful appup files. -define(appup_exclude, [asn1,common_test,compiler,crypto,dialyzer,edoc,eldap,eunit, hipe,inets,observer,odbc,os_mon,public_key,runtime_tools, ssh,ssl,syntax_tools,test_server,tools,typer,wx,xmerl]). init_per_suite(Config) -> %% Check that a real release is running, not e.g. cerl ok = application:ensure_started(sasl), case release_handler:which_releases() of [{_,_,[],_}] -> %% Fake release, no applications {skip, "Need a real release running to create other releases"}; _ -> rm_rf(filename:join([?config(data_dir,Config),priv_dir])), Config end. init_per_testcase(Case,Config) -> PrivDir = filename:join([?config(data_dir,Config),priv_dir,Case]), CreateDir = filename:join([PrivDir,create]), InstallDir = filename:join([PrivDir,install]), ok = filelib:ensure_dir(filename:join(CreateDir,"*")), ok = filelib:ensure_dir(filename:join(InstallDir,"*")), Config1 = lists:keyreplace(priv_dir,1,Config,{priv_dir,PrivDir}), [{create_dir,CreateDir},{install_dir,InstallDir}|Config1]. end_per_testcase(_Case,Config) -> Nodes = nodes(), [test_server:stop_node(Node) || Node <- Nodes], case ?config(tc_status,Config) of ok -> %% Note that priv_dir here is per test case! rm_rf(?config(priv_dir,Config)); _fail -> %% Test case data can be found under DataDir/priv_dir/Case ok end, ok. all() -> [minor,major]. %% If this is major release X, then this test performs an upgrade from %% major release X-1 to the current release. major(Config) -> Current = erlang:system_info(otp_release), PreviousMajor = previous_major(Current), upgrade_test(PreviousMajor,Current,Config). %% If this is a patched version of major release X, then this test %% performs an upgrade from major release X to the current release. minor(Config) -> CurrentMajor = erlang:system_info(otp_release), Current = list_to_atom(CurrentMajor++"_patched"), upgrade_test(CurrentMajor,Current,Config). %%%----------------------------------------------------------------- upgrade_test(FromVsn,ToVsn,Config) -> case test_server:is_release_available(FromVsn) of true -> upgrade_test1(FromVsn,ToVsn,Config); false -> %% Note that priv_dir here is per test case! rm_rf(?config(priv_dir,Config)), {skip, "no previous release available"} end. upgrade_test1(FromVsn,ToVsn,Config) -> CreateDir = ?config(create_dir,Config), InstallDir = ?config(install_dir,Config), FromRelName = "otp-"++FromVsn, ToRelName = "otp-"++ToVsn, {FromRel,FromApps} = target_system(FromRelName, FromVsn, CreateDir, InstallDir,Config), {ToRel,ToApps} = upgrade_system(FromRel, ToRelName, ToVsn, CreateDir, InstallDir), do_upgrade(FromVsn, FromApps, ToRel, ToApps, InstallDir). %%%----------------------------------------------------------------- %%% This is similar to sasl/examples/src/target_system.erl, but with %%% the following adjustments: %%% - add a log directory %%% - use an own 'start' script %%% - chmod 'start' and 'start_erl' target_system(RelName0,RelVsn,CreateDir,InstallDir,Config) -> {ok,Node} = test_server:start_node(list_to_atom(RelName0),peer, [{erl,[{release,RelVsn}]}]), {RelName,Apps,ErtsVsn} = create_relfile(Node,CreateDir,RelName0,RelVsn), %% Create .script and .boot ok = rpc:call(Node,systools,make_script,[RelName]), %% Create base tar file - i.e. erts and all apps ok = rpc:call(Node,systools,make_tar, [RelName,[{erts,rpc:call(Node,code,root_dir,[])}]]), %% Unpack the tar to complete the installation erl_tar:extract(RelName ++ ".tar.gz", [{cwd, InstallDir}, compressed]), %% Add bin and log dirs BinDir = filename:join([InstallDir, "bin"]), file:make_dir(BinDir), file:make_dir(filename:join(InstallDir,"log")), %% Delete start scripts - they will be added later ErtsBinDir = filename:join([InstallDir, "erts-" ++ ErtsVsn, "bin"]), file:delete(filename:join([ErtsBinDir, "erl"])), file:delete(filename:join([ErtsBinDir, "start"])), file:delete(filename:join([ErtsBinDir, "start_erl"])), %% Copy .boot to bin/start.boot copy_file(RelName++".boot",filename:join([BinDir, "start.boot"])), %% Copy scripts from erts-xxx/bin to bin copy_file(filename:join([ErtsBinDir, "epmd"]), filename:join([BinDir, "epmd"]), [preserve]), copy_file(filename:join([ErtsBinDir, "run_erl"]), filename:join([BinDir, "run_erl"]), [preserve]), copy_file(filename:join([ErtsBinDir, "to_erl"]), filename:join([BinDir, "to_erl"]), [preserve]), %% create start_erl.data and sys.config StartErlData = filename:join([InstallDir, "releases", "start_erl.data"]), write_file(StartErlData, io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn])), SysConfig = filename:join([InstallDir, "releases", RelVsn, "sys.config"]), write_file(SysConfig, "[]."), %% Insert 'start' script from data_dir - modified to add sname and heart copy_file(filename:join(?config(data_dir,Config),"start.src"), filename:join(ErtsBinDir,"start.src")), ok = file:change_mode(filename:join(ErtsBinDir,"start.src"),8#0755), %% Make start_erl executable %% (this has been fixed in OTP 17 - is is now installed with %% $INSTALL_SCRIPT instead of $INSTALL_DATA and should therefore %% be executable from the start) ok = file:change_mode(filename:join(ErtsBinDir,"start_erl.src"),8#0755), %% Substitute variables in erl.src, start.src and start_erl.src %% (.src found in erts-xxx/bin - result stored in bin) subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir, [{"FINAL_ROOTDIR", InstallDir}, {"EMU", "beam"}], [preserve]), %% Create RELEASES RelFile = filename:join([InstallDir, "releases", filename:basename(RelName) ++ ".rel"]), release_handler:create_RELEASES(InstallDir, RelFile), true = test_server:stop_node(Node), {RelName,Apps}. %%%----------------------------------------------------------------- %%% Create a release containing the current (the test node) OTP %%% release, including relup to allow upgrade from an earlier OTP %%% release. upgrade_system(FromRel, ToRelName0, ToVsn, CreateDir, InstallDir) -> {RelName,Apps,_} = create_relfile(node(),CreateDir,ToRelName0,ToVsn), FromPath = filename:join([InstallDir,lib,"*",ebin]), ok = systools:make_script(RelName), ok = systools:make_relup(RelName,[FromRel],[FromRel], [{path,[FromPath]}, {outdir,CreateDir}]), SysConfig = filename:join([CreateDir, "sys.config"]), write_file(SysConfig, "[]."), ok = systools:make_tar(RelName,[{erts,code:root_dir()}]), {RelName,Apps}. %%%----------------------------------------------------------------- %%% Start a new node running the release from target_system/5 %%% above. Then upgrade to the system from upgrade_system/5. do_upgrade(FromVsn,FromApps,ToRel,ToApps,InstallDir) -> Start = filename:join([InstallDir,bin,start]), {ok,Node} = start_node(Start,permanent,FromVsn,FromApps), [{"OTP upgrade test",FromVsn,_,permanent}] = rpc:call(Node,release_handler,which_releases,[]), {ok,ToVsn} = rpc:call(Node,release_handler,unpack_release,[ToRel]), [{"OTP upgrade test",ToVsn,_,unpacked}, {"OTP upgrade test",FromVsn,_,permanent}] = rpc:call(Node,release_handler,which_releases,[]), case rpc:call(Node,release_handler,install_release,[ToVsn]) of {ok,FromVsn,_} -> ok; {continue_after_restart,FromVsn,_} -> wait_node_up(current,ToVsn,ToApps) end, [{"OTP upgrade test",ToVsn,_,current}, {"OTP upgrade test",FromVsn,_,permanent}] = rpc:call(Node,release_handler,which_releases,[]), ok = rpc:call(Node,release_handler,make_permanent,[ToVsn]), [{"OTP upgrade test",ToVsn,_,permanent}, {"OTP upgrade test",FromVsn,_,old}] = rpc:call(Node,release_handler,which_releases,[]), true = test_server:stop_node(Node), ok. %%%----------------------------------------------------------------- %%% Library functions previous_major("17") -> "r16"; previous_major(Rel) -> integer_to_list(list_to_integer(Rel)-1). create_relfile(Node,CreateDir,RelName0,RelVsn) -> LibDir = rpc:call(Node,code,lib_dir,[]), SplitLibDir = filename:split(LibDir), Paths = rpc:call(Node,code,get_path,[]), Exclude = ?start_exclude ++ ?appup_exclude, Apps = lists:flatmap( fun(Path) -> case lists:prefix(LibDir,Path) of true -> case filename:split(Path) -- SplitLibDir of [AppVsn,"ebin"] -> case string:tokens(AppVsn,"-") of [AppStr,Vsn] -> App = list_to_atom(AppStr), case lists:member(App,Exclude) of true -> []; false -> [{App,Vsn}] end; _ -> [] end; _ -> [] end; false -> [] end end, Paths), ErtsVsn = rpc:call(Node, erlang, system_info, [version]), %% Create the .rel file RelContent = {release, {"OTP upgrade test", RelVsn}, {erts, ErtsVsn}, Apps}, RelName = filename:join(CreateDir,RelName0), RelFile = RelName++".rel", {ok,Fd} = file:open(RelFile,[write,{encoding,utf8}]), io:format(Fd,"~tp.~n",[RelContent]), ok = file:close(Fd), {RelName,Apps,ErtsVsn}. copy_file(Src, Dest) -> copy_file(Src, Dest, []). copy_file(Src, Dest, Opts) -> {ok,_} = file:copy(Src, Dest), case lists:member(preserve, Opts) of true -> {ok, FileInfo} = file:read_file_info(Src), file:write_file_info(Dest, FileInfo); false -> ok end. write_file(FName, Conts) -> Enc = file:native_name_encoding(), {ok, Fd} = file:open(FName, [write]), file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)), file:close(Fd). subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) -> lists:foreach(fun(Script) -> subst_src_script(Script, SrcDir, DestDir, Vars, Opts) end, Scripts). subst_src_script(Script, SrcDir, DestDir, Vars, Opts) -> subst_file(filename:join([SrcDir, Script ++ ".src"]), filename:join([DestDir, Script]), Vars, Opts). subst_file(Src, Dest, Vars, Opts) -> {ok, Bin} = file:read_file(Src), Conts = binary_to_list(Bin), NConts = subst(Conts, Vars), write_file(Dest, NConts), case lists:member(preserve, Opts) of true -> {ok, FileInfo} = file:read_file_info(Src), file:write_file_info(Dest, FileInfo); false -> ok end. %% subst(Str, Vars) %% Vars = [{Var, Val}] %% Var = Val = string() %% Substitute all occurrences of %Var% for Val in Str, using the list %% of variables in Vars. %% subst(Str, Vars) -> subst(Str, Vars, []). subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z -> subst_var([C| Rest], Vars, Result, []); subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z -> subst_var([C| Rest], Vars, Result, []); subst([$%, C| Rest], Vars, Result) when C == $_ -> subst_var([C| Rest], Vars, Result, []); subst([C| Rest], Vars, Result) -> subst(Rest, Vars, [C| Result]); subst([], _Vars, Result) -> lists:reverse(Result). subst_var([$%| Rest], Vars, Result, VarAcc) -> Key = lists:reverse(VarAcc), case lists:keysearch(Key, 1, Vars) of {value, {Key, Value}} -> subst(Rest, Vars, lists:reverse(Value, Result)); false -> subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]]) end; subst_var([C| Rest], Vars, Result, VarAcc) -> subst_var(Rest, Vars, Result, [C| VarAcc]); subst_var([], Vars, Result, VarAcc) -> subst([], Vars, [VarAcc ++ [$%| Result]]). %%%----------------------------------------------------------------- %%% start_node(Start,ExpStatus,ExpVsn,ExpApps) -> case open_port({spawn_executable, Start}, []) of Port when is_port(Port) -> unlink(Port), erlang:port_close(Port), wait_node_up(ExpStatus,ExpVsn,ExpApps); Error -> Error end. wait_node_up(ExpStatus,ExpVsn,ExpApps) -> Node = node_name(?upgr_sname), wait_node_up(Node,ExpStatus,ExpVsn,lists:keysort(1,ExpApps),60). wait_node_up(Node,ExpStatus,ExpVsn,ExpApps,0) -> ct:fail({app_check_failed,ExpVsn,ExpApps, rpc:call(Node,release_handler,which_releases,[ExpStatus]), rpc:call(Node,application,which_applications,[])}); wait_node_up(Node,ExpStatus,ExpVsn,ExpApps,N) -> timer:sleep(2000), case {rpc:call(Node,release_handler,which_releases,[ExpStatus]), rpc:call(Node, application, which_applications, [])} of {[{_,ExpVsn,_,_}],Apps} when is_list(Apps) -> case [{A,V} || {A,_,V} <- lists:keysort(1,Apps)] of ExpApps -> {ok,Node}; _ -> wait_node_up(Node,ExpStatus,ExpVsn,ExpApps,N-1) end; _ -> wait_node_up(Node,ExpStatus,ExpVsn,ExpApps,N-1) end. node_name(Sname) -> {ok,Host} = inet:gethostname(), list_to_atom(atom_to_list(Sname) ++ "@" ++ Host). rm_rf(Dir) -> case file:read_file_info(Dir) of {ok, #file_info{type = directory}} -> {ok, Content} = file:list_dir_all(Dir), [rm_rf(filename:join(Dir,C)) || C <- Content], ok=file:del_dir(Dir), ok; {ok, #file_info{}} -> ok=file:delete(Dir); _ -> ok end.