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.pull/3/head
@ -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 <joe@joetify.com> | |||
%% | |||
%% 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). |
@ -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(). |
@ -0,0 +1,8 @@ | |||
{"0.2", | |||
[{"0.1",[ | |||
{update, dummy_server, {advanced, [foo]}} | |||
]}], | |||
[{"0.1",[ | |||
{update, dummy_server, {advanced, [foo]}} | |||
]}] | |||
}. |
@ -0,0 +1,9 @@ | |||
{application, dummy, [ | |||
{description, "a dummy app"}, | |||
{vsn, "0.1"}, | |||
{registered, [ | |||
dummy_app | |||
]}, | |||
{mod, {dummy_app, []}}, | |||
{applications, [kernel, stdlib, sasl]} | |||
]}. |
@ -0,0 +1,9 @@ | |||
-module(dummy_app). | |||
-behaviour(application). | |||
-export([start/2, stop/1]). | |||
start(_,_) -> | |||
dummy_sup:start_link(). | |||
stop(_) -> ok. |
@ -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)]). |
@ -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]}}. |
@ -0,0 +1,4 @@ | |||
{sub_dirs, [ | |||
"apps/dummy", | |||
"rel" | |||
]}. |
@ -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 | |||
]} | |||
]. |
@ -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 |
@ -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+"$@"} |
@ -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. |
@ -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 |
@ -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"} | |||
]}. |