From 5298e93a180e6db87a33f26eb6a2db06e8065dc7 Mon Sep 17 00:00:00 2001 From: joewilliams Date: Thu, 27 Jan 2011 18:15:25 +0100 Subject: [PATCH] Add 'generate-upgrade' command To support OTP release upgrades I have added support for building upgrade packages. Support for this is included in the rebar_upgrade module, specifically generate_upgrade/2. It requires one variable to be set on the command line 'previous_release' which is the absolute path or relative path from 'rel/' to the previous release one is upgrading from. Running an upgrade will create the needed files, including a relup and result in a tarball containing the upgrade being written to 'rel/'. When done it cleans up the temporary files systools created. Usage: $ rebar generate-upgrade previous_release=/path/to/old/version This also includes a dummy application that can be used to test upgrades as well as an example. Special thanks to Daniel Reverri, Jesper Louis Andersen and Richard Jones for comments and patches. --- ebin/rebar.app | 4 +- priv/templates/simplenode.runner | 10 +- src/rebar.erl | 6 +- src/rebar_upgrade.erl | 206 ++++++++++++++++++ test/upgrade_project/README.md | 38 ++++ .../apps/dummy/ebin/dummy.appup | 8 + .../apps/dummy/src/dummy.app.src | 9 + .../apps/dummy/src/dummy_app.erl | 9 + .../apps/dummy/src/dummy_server.erl | 56 +++++ .../apps/dummy/src/dummy_sup.erl | 15 ++ test/upgrade_project/rebar.config | 4 + test/upgrade_project/rel/files/app.config | 10 + test/upgrade_project/rel/files/dummy | 155 +++++++++++++ test/upgrade_project/rel/files/erl | 34 +++ test/upgrade_project/rel/files/nodetool | 116 ++++++++++ test/upgrade_project/rel/files/vm.args | 20 ++ test/upgrade_project/rel/reltool.config | 25 +++ 17 files changed, 720 insertions(+), 5 deletions(-) create mode 100644 src/rebar_upgrade.erl create mode 100644 test/upgrade_project/README.md create mode 100644 test/upgrade_project/apps/dummy/ebin/dummy.appup create mode 100644 test/upgrade_project/apps/dummy/src/dummy.app.src create mode 100644 test/upgrade_project/apps/dummy/src/dummy_app.erl create mode 100644 test/upgrade_project/apps/dummy/src/dummy_server.erl create mode 100644 test/upgrade_project/apps/dummy/src/dummy_sup.erl create mode 100644 test/upgrade_project/rebar.config create mode 100644 test/upgrade_project/rel/files/app.config create mode 100755 test/upgrade_project/rel/files/dummy create mode 100755 test/upgrade_project/rel/files/erl create mode 100755 test/upgrade_project/rel/files/nodetool create mode 100644 test/upgrade_project/rel/files/vm.args create mode 100644 test/upgrade_project/rel/reltool.config diff --git a/ebin/rebar.app b/ebin/rebar.app index ad37c66..1f7db21 100644 --- a/ebin/rebar.app +++ b/ebin/rebar.app @@ -32,6 +32,7 @@ rebar_require_vsn, rebar_subdirs, rebar_templater, + rebar_upgrade, rebar_utils, rebar_xref, getopt, @@ -79,7 +80,8 @@ ]}, {rel_dir, [ - rebar_reltool + rebar_reltool, + rebar_upgrade ]} ]} ]} diff --git a/priv/templates/simplenode.runner b/priv/templates/simplenode.runner index 10c233b..ec6d7ff 100755 --- a/priv/templates/simplenode.runner +++ b/priv/templates/simplenode.runner @@ -117,13 +117,19 @@ case "$1" in $ERTS_PATH/to_erl $PIPE_DIR ;; - console) + console|console_clean) + # .boot file typically just $SCRIPT (ie, the app name) + # however, for debugging, sometimes start_clean.boot is useful: + case "$1" in + console) BOOTFILE=$SCRIPT ;; + console_clean) BOOTFILE=start_clean ;; + esac # Setup beam-required vars ROOTDIR=$RUNNER_BASE_DIR BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin EMU=beam PROGNAME=`echo $0 | sed 's/.*\\///'` - CMD="$BINDIR/erlexec -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$SCRIPT -embedded -config $RUNNER_ETC_DIR/app.config -args_file $RUNNER_ETC_DIR/vm.args -- ${1+"$@"}" + CMD="$BINDIR/erlexec -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$BOOTFILE -embedded -config $RUNNER_ETC_DIR/app.config -args_file $RUNNER_ETC_DIR/vm.args -- ${1+"$@"}" export EMU export ROOTDIR export BINDIR diff --git a/src/rebar.erl b/src/rebar.erl index 1b502b1..6c99ab5 100644 --- a/src/rebar.erl +++ b/src/rebar.erl @@ -211,6 +211,8 @@ delete-deps Delete fetched dependencies generate [dump_spec=0/1] Build release with reltool +generate-upgrade [previous_release=path] Build an upgrade package + eunit [suite=foo] Run eunit [test/foo_tests.erl] tests ct [suite=] [case=] Run common_test suites in ./test @@ -264,8 +266,8 @@ filter_flags([Item | Rest], Commands) -> command_names() -> ["build-plt", "check-deps", "check-plt", "clean", "compile", "create", "create-app", "create-node", "ct", "delete-deps", "dialyze", "doc", - "eunit", "generate", "get-deps", "help", "list-templates", "update-deps", - "version", "xref"]. + "eunit", "generate", "generate-upgrade", "get-deps", "help", + "list-templates", "update-deps", "version", "xref"]. unabbreviate_command_names([]) -> []; diff --git a/src/rebar_upgrade.erl b/src/rebar_upgrade.erl new file mode 100644 index 0000000..4c0d2f7 --- /dev/null +++ b/src/rebar_upgrade.erl @@ -0,0 +1,206 @@ +%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2011 Joe Williams +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- + +-module(rebar_upgrade). + +-include("rebar.hrl"). +-include_lib("kernel/include/file.hrl"). + +-export(['generate-upgrade'/2]). + +%% public api + +'generate-upgrade'(_Config, ReltoolFile) -> + case rebar_config:get_global(previous_release, false) of + false -> + ?ABORT("previous_release=PATH is required to " + "create upgrade package~n", []); + OldVerPath -> + %% Run checks to make sure that building a package is possible + {NewName, NewVer} = run_checks(OldVerPath, ReltoolFile), + NameVer = NewName ++ "_" ++ NewVer, + + %% Save the code path prior to doing anything + OrigPath = code:get_path(), + + %% Prepare the environment for building the package + ok = setup(OldVerPath, NewName, NewVer, NameVer), + + %% Build the package + run_systools(NameVer, NewName), + + %% Boot file changes + boot_files(NewVer, NewName), + + %% Extract upgrade and tar it back up with changes + make_tar(NameVer), + + %% Clean up files that systools created + ok = cleanup(NameVer, NewName, NewVer), + + %% Restore original path + true = code:set_path(OrigPath), + + ok + end. + +%% internal api + +run_checks(OldVerPath, ReltoolFile) -> + true = prop_check(filelib:is_dir(OldVerPath), + "Release directory doesn't exist (~p)~n", [OldVerPath]), + + {Name, Ver} = get_release_name(ReltoolFile), + + NamePath = filename:join([".", Name]), + true = prop_check(filelib:is_dir(NamePath), + "Release directory doesn't exist (~p)~n", [NamePath]), + + {NewName, NewVer} = get_release_version(Name, NamePath), + {OldName, OldVer} = get_release_version(Name, OldVerPath), + + true = prop_check(NewName == OldName, + "New and old .rel release names do not match~n", []), + true = prop_check(Name == NewName, + "Reltool and .rel release names do not match~n", []), + true = prop_check(NewVer =/= OldVer, + "New and old .rel contain the same version~n", []), + true = prop_check(Ver == NewVer, + "Reltool and .rel versions do not match~n", []), + + {NewName, NewVer}. + +get_release_name(ReltoolFile) -> + %% expect sys to be the first proplist in reltool.config + case file:consult(ReltoolFile) of + {ok, [{sys, Config}| _]} -> + %% expect the first rel in the proplist to be the one you want + {rel, Name, Ver, _} = proplists:lookup(rel, Config), + {Name, Ver}; + _ -> + ?ABORT("Failed to parse ~s~n", [ReltoolFile]) + end. + +get_release_version(Name, Path) -> + [RelFile] = filelib:wildcard(filename:join([Path, "releases", "*", + Name ++ ".rel"])), + [BinDir|_] = re:replace(RelFile, Name ++ "\\.rel", ""), + {ok, [{release, {Name1, Ver}, _, _}]} = + file:consult(filename:join([binary_to_list(BinDir), + Name ++ ".rel"])), + {Name1, Ver}. + +prop_check(true, _, _) -> true; +prop_check(false, Msg, Args) -> ?ABORT(Msg, Args). + +setup(OldVerPath, NewName, NewVer, NameVer) -> + NewRelPath = filename:join([".", NewName]), + Src = filename:join([NewRelPath, "releases", + NewVer, NewName ++ ".rel"]), + Dst = filename:join([".", NameVer ++ ".rel"]), + {ok, _} = file:copy(Src, Dst), + ok = code:add_pathsa( + lists:append([ + filelib:wildcard(filename:join([OldVerPath, + "releases", "*"])), + filelib:wildcard(filename:join([OldVerPath, + "lib", "*", "ebin"])), + filelib:wildcard(filename:join([NewRelPath, + "lib", "*", "ebin"])), + filelib:wildcard(filename:join([NewRelPath, "*"])) + ])). + +run_systools(NewVer, Name) -> + Opts = [silent], + NameList = [Name], + case systools:make_relup(NewVer, NameList, NameList, Opts) of + {error, _, _Message} -> + ?ABORT("Systools aborted with: ~p~n", [_Message]); + _ -> + ?DEBUG("Relup created~n", []), + case systools:make_script(NewVer, Opts) of + {error, _, _Message1} -> + ?ABORT("Systools aborted with: ~p~n", [_Message1]); + _ -> + ?DEBUG("Script created~n", []), + case systools:make_tar(NewVer, Opts) of + {error, _, _Message2} -> + ?ABORT("Systools aborted with: ~p~n", [_Message2]); + _ -> + ok + end + end + end. + +boot_files(Ver, Name) -> + ok = file:make_dir(filename:join([".", "releases"])), + ok = file:make_dir(filename:join([".", "releases", Ver])), + ok = file:make_symlink(filename:join(["start.boot"]), + filename:join([".", "releases", Ver, Name ++ ".boot"])), + {ok, _} = file:copy(filename:join([".", Name, "releases", Ver, "start_clean.boot"]), + filename:join([".", "releases", Ver, "start_clean.boot"])). + +make_tar(NameVer) -> + Filename = NameVer ++ ".tar.gz", + ok = erl_tar:extract(Filename, [compressed]), + ok = file:delete(Filename), + {ok, Tar} = erl_tar:open(Filename, [write, compressed]), + ok = erl_tar:add(Tar, "lib", []), + ok = erl_tar:add(Tar, "releases", []), + ok = erl_tar:close(Tar), + ?CONSOLE("~s upgrade package created~n", [NameVer]). + +cleanup(NameVer, Name, Ver) -> + ?DEBUG("Removing files needed for building the upgrade~n", []), + Files = [ + filename:join([".", "releases", Ver, Name ++ ".boot"]), + filename:join([".", NameVer ++ ".rel"]), + filename:join([".", NameVer ++ ".boot"]), + filename:join([".", NameVer ++ ".script"]), + filename:join([".", "relup"]) + ], + [ok = file:delete(F) || F <- Files], + + ok = remove_dir_tree("releases"), + ok = remove_dir_tree("lib"). + +%% taken from http://www.erlang.org/doc/system_principles/create_target.html +remove_dir_tree(Dir) -> + remove_all_files(".", [Dir]). +remove_all_files(Dir, Files) -> + lists:foreach(fun(File) -> + FilePath = filename:join([Dir, File]), + {ok, FileInfo} = file:read_file_info(FilePath), + case FileInfo#file_info.type of + directory -> + {ok, DirFiles} = file:list_dir(FilePath), + remove_all_files(FilePath, DirFiles), + file:del_dir(FilePath); + _ -> + file:delete(FilePath) + end + end, Files). diff --git a/test/upgrade_project/README.md b/test/upgrade_project/README.md new file mode 100644 index 0000000..cf753e8 --- /dev/null +++ b/test/upgrade_project/README.md @@ -0,0 +1,38 @@ +#### Building version 0.1 + rebar compile + rebar generate + mv rel/dummy rel/dummy_0.1 + rebar clean + # start the release: + cd rel/dummy_0.1 + bin/dummy console + + erl> dummy_server:get_state(). + erl> dummy_server:set_state(123). + erl> dummy_server:get_state(). + +#### Building version 0.2 + + # Now, in another terminal we prepare an upgrade.. + + # change release version numbers from 0.1 to 0.2 in + $EDITOR apps/dummy/src/dummy.app.src + $EDITOR rel/reltool.config + + rebar compile + rebar generate + rebar generate-upgrade previous_release=dummy_0.1 + tar -zvtf rel/dummy_0.2.tar.gz + + +#### Deploying with release_handler + mv rel/dummy_0.2.tar.gz rel/dummy_0.1/releases/ + + # Now use release_handler in the running erlang console for the deploy: + + erl> release_handler:unpack_release("dummy_0.2"). + erl> release_handler:install_release("0.2"). + erl> release_handler:make_permanent("0.2"). + + erl> release_handler:which_releases(). + erl> dummy_server:get_state(). diff --git a/test/upgrade_project/apps/dummy/ebin/dummy.appup b/test/upgrade_project/apps/dummy/ebin/dummy.appup new file mode 100644 index 0000000..e77f66f --- /dev/null +++ b/test/upgrade_project/apps/dummy/ebin/dummy.appup @@ -0,0 +1,8 @@ +{"0.2", + [{"0.1",[ + {update, dummy_server, {advanced, [foo]}} + ]}], + [{"0.1",[ + {update, dummy_server, {advanced, [foo]}} + ]}] +}. diff --git a/test/upgrade_project/apps/dummy/src/dummy.app.src b/test/upgrade_project/apps/dummy/src/dummy.app.src new file mode 100644 index 0000000..dd06752 --- /dev/null +++ b/test/upgrade_project/apps/dummy/src/dummy.app.src @@ -0,0 +1,9 @@ +{application, dummy, [ + {description, "a dummy app"}, + {vsn, "0.1"}, + {registered, [ + dummy_app + ]}, + {mod, {dummy_app, []}}, + {applications, [kernel, stdlib, sasl]} +]}. diff --git a/test/upgrade_project/apps/dummy/src/dummy_app.erl b/test/upgrade_project/apps/dummy/src/dummy_app.erl new file mode 100644 index 0000000..51363b3 --- /dev/null +++ b/test/upgrade_project/apps/dummy/src/dummy_app.erl @@ -0,0 +1,9 @@ +-module(dummy_app). +-behaviour(application). + +-export([start/2, stop/1]). + +start(_,_) -> + dummy_sup:start_link(). + +stop(_) -> ok. diff --git a/test/upgrade_project/apps/dummy/src/dummy_server.erl b/test/upgrade_project/apps/dummy/src/dummy_server.erl new file mode 100644 index 0000000..382251e --- /dev/null +++ b/test/upgrade_project/apps/dummy/src/dummy_server.erl @@ -0,0 +1,56 @@ +-module(dummy_server). +-behaviour(gen_server). + +-export([start_link/0, set_state/1, get_state/0]). + +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3]). + +%% + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +set_state(What) -> + gen_server:call(?MODULE, {set_state, What}). + +get_state() -> + gen_server:call(?MODULE, get_state). + + +%% + +init([]) -> + say("init, setting state to 0", []), + {ok, 0}. + + +handle_call({set_state, NewState}, _From, _State) -> + {reply, {ok, NewState}, NewState}; + +handle_call(get_state, _From, State) -> + {reply, State, State}. + +handle_cast('__not_implemented', State) -> + {noreply, State}. + +handle_info(_Info, State) -> + say("info ~p, ~p.", [_Info, State]), + {noreply, State}. + +terminate(_Reason, _State) -> + say("terminate ~p, ~p", [_Reason, _State]), + ok. + +code_change(_OldVsn, State, _Extra) -> + say("code_change ~p, ~p, ~p", [_OldVsn, State, _Extra]), + {ok, State}. + +%% Internal + +say(Format, Data) -> + io:format("~p:~p: ~s~n", [?MODULE, self(), io_lib:format(Format, Data)]). diff --git a/test/upgrade_project/apps/dummy/src/dummy_sup.erl b/test/upgrade_project/apps/dummy/src/dummy_sup.erl new file mode 100644 index 0000000..b5617c7 --- /dev/null +++ b/test/upgrade_project/apps/dummy/src/dummy_sup.erl @@ -0,0 +1,15 @@ +-module(dummy_sup). +-behaviour(supervisor). + +-export([start_link/0]). +-export([init/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +init([]) -> + Dummy = {dummy_server, + {dummy_server, start_link, []}, + permanent, 5000, worker, [dummy_server]}, + + {ok, {{one_for_one, 10, 10}, [Dummy]}}. diff --git a/test/upgrade_project/rebar.config b/test/upgrade_project/rebar.config new file mode 100644 index 0000000..11b5b9a --- /dev/null +++ b/test/upgrade_project/rebar.config @@ -0,0 +1,4 @@ +{sub_dirs, [ + "apps/dummy", + "rel" +]}. diff --git a/test/upgrade_project/rel/files/app.config b/test/upgrade_project/rel/files/app.config new file mode 100644 index 0000000..25a3bb3 --- /dev/null +++ b/test/upgrade_project/rel/files/app.config @@ -0,0 +1,10 @@ +[ + %% SASL config + {sasl, [ + {sasl_error_logger, {file, "log/sasl-error.log"}}, + {errlog_type, error}, + {error_logger_mf_dir, "log/sasl"}, % Log directory + {error_logger_mf_maxbytes, 10485760}, % 10 MB max file size + {error_logger_mf_maxfiles, 5} % 5 files max + ]} +]. diff --git a/test/upgrade_project/rel/files/dummy b/test/upgrade_project/rel/files/dummy new file mode 100755 index 0000000..ec6d7ff --- /dev/null +++ b/test/upgrade_project/rel/files/dummy @@ -0,0 +1,155 @@ +#!/bin/bash +# -*- tab-width:4;indent-tabs-mode:nil -*- +# ex: ts=4 sw=4 et + +RUNNER_SCRIPT_DIR=$(cd ${0%/*} && pwd) + +RUNNER_BASE_DIR=${RUNNER_SCRIPT_DIR%/*} +RUNNER_ETC_DIR=$RUNNER_BASE_DIR/etc +RUNNER_LOG_DIR=$RUNNER_BASE_DIR/log +PIPE_DIR=/tmp/$RUNNER_BASE_DIR/ +RUNNER_USER= + +# Make sure this script is running as the appropriate user +if [ ! -z "$RUNNER_USER" ] && [ `whoami` != "$RUNNER_USER" ]; then + exec sudo -u $RUNNER_USER -i $0 $@ +fi + +# Make sure CWD is set to runner base dir +cd $RUNNER_BASE_DIR + +# Make sure log directory exists +mkdir -p $RUNNER_LOG_DIR + +# Extract the target node name from node.args +NAME_ARG=`grep -e '-[s]*name' $RUNNER_ETC_DIR/vm.args` +if [ -z "$NAME_ARG" ]; then + echo "vm.args needs to have either -name or -sname parameter." + exit 1 +fi + +# Extract the target cookie +COOKIE_ARG=`grep -e '-setcookie' $RUNNER_ETC_DIR/vm.args` +if [ -z "$COOKIE_ARG" ]; then + echo "vm.args needs to have a -setcookie parameter." + exit 1 +fi + +# Identify the script name +SCRIPT=`basename $0` + +# Parse out release and erts info +START_ERL=`cat $RUNNER_BASE_DIR/releases/start_erl.data` +ERTS_VSN=${START_ERL% *} +APP_VSN=${START_ERL#* } + +# Add ERTS bin dir to our path +ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin + +# Setup command to control the node +NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG" + +# Check the first argument for instructions +case "$1" in + start) + # Make sure there is not already a node running + RES=`$NODETOOL ping` + if [ "$RES" = "pong" ]; then + echo "Node is already running!" + exit 1 + fi + HEART_COMMAND="$RUNNER_BASE_DIR/bin/$SCRIPT start" + export HEART_COMMAND + mkdir -p $PIPE_DIR + # Note the trailing slash on $PIPE_DIR/ + $ERTS_PATH/run_erl -daemon $PIPE_DIR/ $RUNNER_LOG_DIR "exec $RUNNER_BASE_DIR/bin/$SCRIPT console" 2>&1 + ;; + + stop) + # Wait for the node to completely stop... + case `uname -s` in + Linux|Darwin|FreeBSD|DragonFly|NetBSD|OpenBSD) + # PID COMMAND + PID=`ps ax -o pid= -o command=|\ + grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $1}'` + ;; + SunOS) + # PID COMMAND + PID=`ps -ef -o pid= -o args=|\ + grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $1}'` + ;; + CYGWIN*) + # UID PID PPID TTY STIME COMMAND + PID=`ps -efW|grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $2}'` + ;; + esac + $NODETOOL stop + while `kill -0 $PID 2>/dev/null`; + do + sleep 1 + done + ;; + + restart) + ## Restart the VM without exiting the process + $NODETOOL restart + ;; + + reboot) + ## Restart the VM completely (uses heart to restart it) + $NODETOOL reboot + ;; + + ping) + ## See if the VM is alive + $NODETOOL ping + ;; + + attach) + # Make sure a node IS running + RES=`$NODETOOL ping` + if [ "$RES" != "pong" ]; then + echo "Node is not running!" + exit 1 + fi + + shift + $ERTS_PATH/to_erl $PIPE_DIR + ;; + + console|console_clean) + # .boot file typically just $SCRIPT (ie, the app name) + # however, for debugging, sometimes start_clean.boot is useful: + case "$1" in + console) BOOTFILE=$SCRIPT ;; + console_clean) BOOTFILE=start_clean ;; + esac + # Setup beam-required vars + ROOTDIR=$RUNNER_BASE_DIR + BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin + EMU=beam + PROGNAME=`echo $0 | sed 's/.*\\///'` + CMD="$BINDIR/erlexec -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$BOOTFILE -embedded -config $RUNNER_ETC_DIR/app.config -args_file $RUNNER_ETC_DIR/vm.args -- ${1+"$@"}" + export EMU + export ROOTDIR + export BINDIR + export PROGNAME + + # Dump environment info for logging purposes + echo "Exec: $CMD" + echo "Root: $ROOTDIR" + + # Log the startup + logger -t "$SCRIPT[$$]" "Starting up" + + # Start the VM + exec $CMD + ;; + + *) + echo "Usage: $SCRIPT {start|stop|restart|reboot|ping|console|attach}" + exit 1 + ;; +esac + +exit 0 diff --git a/test/upgrade_project/rel/files/erl b/test/upgrade_project/rel/files/erl new file mode 100755 index 0000000..b985f23 --- /dev/null +++ b/test/upgrade_project/rel/files/erl @@ -0,0 +1,34 @@ +#!/bin/bash + +## This script replaces the default "erl" in erts-VSN/bin. This is necessary +## as escript depends on erl and in turn, erl depends on having access to a +## bootscript (start.boot). Note that this script is ONLY invoked as a side-effect +## of running escript -- the embedded node bypasses erl and uses erlexec directly +## (as it should). +## +## Note that this script makes the assumption that there is a start_clean.boot +## file available in $ROOTDIR/release/VSN. + +# Determine the abspath of where this script is executing from. +ERTS_BIN_DIR=$(cd ${0%/*} && pwd) + +# Now determine the root directory -- this script runs from erts-VSN/bin, +# so we simply need to strip off two dirs from the end of the ERTS_BIN_DIR +# path. +ROOTDIR=${ERTS_BIN_DIR%/*/*} + +# Parse out release and erts info +START_ERL=`cat $ROOTDIR/releases/start_erl.data` +ERTS_VSN=${START_ERL% *} +APP_VSN=${START_ERL#* } + +BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin +EMU=beam +PROGNAME=`echo $0 | sed 's/.*\\///'` +CMD="$BINDIR/erlexec" +export EMU +export ROOTDIR +export BINDIR +export PROGNAME + +exec $CMD -boot $ROOTDIR/releases/$APP_VSN/start_clean ${1+"$@"} diff --git a/test/upgrade_project/rel/files/nodetool b/test/upgrade_project/rel/files/nodetool new file mode 100755 index 0000000..cb524ce --- /dev/null +++ b/test/upgrade_project/rel/files/nodetool @@ -0,0 +1,116 @@ +%% -*- mode:erlang;tab-width:4;erlang-indent-level:4;indent-tabs-mode:nil -*- +%% ex: ft=erlang ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% nodetool: Helper Script for interacting with live nodes +%% +%% ------------------------------------------------------------------- + +main(Args) -> + %% Extract the args + {RestArgs, TargetNode} = process_args(Args, [], undefined), + + %% See if the node is currently running -- if it's not, we'll bail + case {net_kernel:hidden_connect_node(TargetNode), net_adm:ping(TargetNode)} of + {true, pong} -> + ok; + {_, pang} -> + io:format("Node ~p not responding to pings.\n", [TargetNode]), + halt(1) + end, + + case RestArgs of + ["ping"] -> + %% If we got this far, the node already responsed to a ping, so just dump + %% a "pong" + io:format("pong\n"); + ["stop"] -> + io:format("~p\n", [rpc:call(TargetNode, init, stop, [], 60000)]); + ["restart"] -> + io:format("~p\n", [rpc:call(TargetNode, init, restart, [], 60000)]); + ["reboot"] -> + io:format("~p\n", [rpc:call(TargetNode, init, reboot, [], 60000)]); + ["rpc", Module, Function | RpcArgs] -> + case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), + [RpcArgs], 60000) of + ok -> + ok; + {badrpc, Reason} -> + io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), + halt(1); + _ -> + halt(1) + end; + ["rpcterms", Module, Function, ArgsAsString] -> + case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), + consult(ArgsAsString), 60000) of + {badrpc, Reason} -> + io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), + halt(1); + Other -> + io:format("~p\n", [Other]) + end; + Other -> + io:format("Other: ~p\n", [Other]), + io:format("Usage: nodetool {ping|stop|restart|reboot}\n") + end, + net_kernel:stop(). + +process_args([], Acc, TargetNode) -> + {lists:reverse(Acc), TargetNode}; +process_args(["-setcookie", Cookie | Rest], Acc, TargetNode) -> + erlang:set_cookie(node(), list_to_atom(Cookie)), + process_args(Rest, Acc, TargetNode); +process_args(["-name", TargetName | Rest], Acc, _) -> + ThisNode = append_node_suffix(TargetName, "_maint_"), + {ok, _} = net_kernel:start([ThisNode, longnames]), + process_args(Rest, Acc, nodename(TargetName)); +process_args(["-sname", TargetName | Rest], Acc, _) -> + ThisNode = append_node_suffix(TargetName, "_maint_"), + {ok, _} = net_kernel:start([ThisNode, shortnames]), + process_args(Rest, Acc, nodename(TargetName)); +process_args([Arg | Rest], Acc, Opts) -> + process_args(Rest, [Arg | Acc], Opts). + + +nodename(Name) -> + case string:tokens(Name, "@") of + [_Node, _Host] -> + list_to_atom(Name); + [Node] -> + [_, Host] = string:tokens(atom_to_list(node()), "@"), + list_to_atom(lists:concat([Node, "@", Host])) + end. + +append_node_suffix(Name, Suffix) -> + case string:tokens(Name, "@") of + [Node, Host] -> + list_to_atom(lists:concat([Node, Suffix, os:getpid(), "@", Host])); + [Node] -> + list_to_atom(lists:concat([Node, Suffix, os:getpid()])) + end. + + +%% +%% Given a string or binary, parse it into a list of terms, ala file:consult/0 +%% +consult(Str) when is_list(Str) -> + consult([], Str, []); +consult(Bin) when is_binary(Bin)-> + consult([], binary_to_list(Bin), []). + +consult(Cont, Str, Acc) -> + case erl_scan:tokens(Cont, Str, 0) of + {done, Result, Remaining} -> + case Result of + {ok, Tokens, _} -> + {ok, Term} = erl_parse:parse_term(Tokens), + consult([], Remaining, [Term | Acc]); + {eof, _Other} -> + lists:reverse(Acc); + {error, Info, _} -> + {error, Info} + end; + {more, Cont1} -> + consult(Cont1, eof, Acc) + end. diff --git a/test/upgrade_project/rel/files/vm.args b/test/upgrade_project/rel/files/vm.args new file mode 100644 index 0000000..7448fb1 --- /dev/null +++ b/test/upgrade_project/rel/files/vm.args @@ -0,0 +1,20 @@ + +## Name of the node +-name dummy@127.0.0.1 + +## Cookie for distributed erlang +-setcookie dummy + +## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive +## (Disabled by default..use with caution!) +##-heart + +## Enable kernel poll and a few async threads ++K true ++A 5 + +## Increase number of concurrent ports/sockets +-env ERL_MAX_PORTS 4096 + +## Tweak GC to run more often +-env ERL_FULLSWEEP_AFTER 10 diff --git a/test/upgrade_project/rel/reltool.config b/test/upgrade_project/rel/reltool.config new file mode 100644 index 0000000..22cec5e --- /dev/null +++ b/test/upgrade_project/rel/reltool.config @@ -0,0 +1,25 @@ +{sys, [ + {lib_dirs, ["../apps"]}, + {rel, "dummy", "0.1", [ + kernel, + stdlib, + sasl, + dummy + ]}, + {rel, "start_clean", "", [kernel, stdlib]}, + {boot_rel, "dummy"}, + {profile, embedded}, + {excl_sys_filters, ["^bin/.*", "^erts.*/bin/(dialyzer|typer)"]}, + {excl_archive_filters, [".*"]}, + + {app, dummy, [{incl_cond, include}]} +]}. + +{overlay, [ + {mkdir, "log/sasl"}, + {copy, "files/erl", "{{erts_vsn}}/bin/erl"}, + {copy, "files/nodetool", "{{erts_vsn}}/bin/nodetool"}, + {copy, "files/dummy", "bin/dummy"}, + {copy, "files/app.config", "etc/app.config"}, + {copy, "files/vm.args", "etc/vm.args"} + ]}.