diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..412eeda --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9d6bd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,215 @@ +################# +## Eclipse +################# + +*.pydevproject +.project +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + + +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +############# +## Windows detritus +############# + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store + + +############# +## Python +############# + +*.py[co] + +# Packages +*.egg +*.egg-info +dist/ +build/ +eggs/ +parts/ +var/ +sdist/ +develop-eggs/ +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..3c6742f --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +deploy \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..217af47 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..3572571 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..22a5539 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..926bdd3 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..f756e81 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/rebar.xml b/.idea/rebar.xml new file mode 100644 index 0000000..c5a2371 --- /dev/null +++ b/.idea/rebar.xml @@ -0,0 +1,7 @@ + + + + C:\Users\zhaoxu-b\Documents\GitHub\deploy\rebar.cmd + + + diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 0000000..922003b --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..3b00020 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..9d32e50 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..dc9b534 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,794 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Abstraction issues + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + localhost + 5050 + + + + + + + + + 1383121281756 + 1383121281756 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Erlang (deploy)|Erlang + + + + + + + + + + + + + + + Erlang R15B03 + + + + + + + + deploy + + + + + + + + 1.6 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..b695be8 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Hot Upgrade Erlang Clusters, scp directory, file to clusters.( all in config file) \ No newline at end of file diff --git a/deploy.iml b/deploy.iml new file mode 100644 index 0000000..ef121a4 --- /dev/null +++ b/deploy.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/makefile b/makefile new file mode 100644 index 0000000..4ea3be7 --- /dev/null +++ b/makefile @@ -0,0 +1,35 @@ +.PHONY: rel deps + +PREFIX:=../ +DEST:=$(PREFIX)$(PROJECT) + +REBAR=./rebar + +all: + @$(REBAR) get-deps compile + +edoc: + @$(REBAR) doc + +test: + @rm -rf .eunit + @mkdir -p .eunit + @$(REBAR) skip_deps=true eunit + +rel: + @$(REBAR) generate +clean: + @$(REBAR) clean +clean_rel: + @rm -rf rel/deploy + +clean_all: + @$(REBAR) clean + @rm -rf rel/deploy + +build_plt: + @$(REBAR) build-plt + +dialyzer: + @$(REBAR) dialyze + diff --git a/rebar b/rebar new file mode 100644 index 0000000..49cb508 Binary files /dev/null and b/rebar differ diff --git a/rebar.cmd b/rebar.cmd new file mode 100644 index 0000000..6c7a1ca --- /dev/null +++ b/rebar.cmd @@ -0,0 +1,4 @@ +@echo off +setlocal +set rebarscript=%~f0 +escript.exe "%rebarscript:.cmd=%" %* diff --git a/rebar.config b/rebar.config new file mode 100644 index 0000000..2567b1a --- /dev/null +++ b/rebar.config @@ -0,0 +1,5 @@ +{sub_dirs, ["rel"]}. + +{cover_enabled, true}. + +{erl_opts, [warnings_as_errors, debug_info]}. \ No newline at end of file diff --git a/rebuild.sh b/rebuild.sh new file mode 100644 index 0000000..da41e7d --- /dev/null +++ b/rebuild.sh @@ -0,0 +1,9 @@ +#!/bin/bash +find . -name '._*' | xargs rm -f + +chmod +x rebar +chmod +x rel/files/* +chmod -x rel/files/*.config +chmod -x rel/files/*.args + +make clean && make && make rel diff --git a/rel/files/app.config b/rel/files/app.config new file mode 100644 index 0000000..f57fe81 --- /dev/null +++ b/rel/files/app.config @@ -0,0 +1,36 @@ +[ + {kernel, [{start_pg2, true}]}, + {sasl, [ + {sasl_error_logger, false} + ] + }, + + {deploy,[ + {apps,[ + {deploy, + %% ssh config of app servers. + {app_config, [ + {test_143, [ + {ssh, "your server ip"}, + {port, 22}, + {user, "user name"}, + {password, "your password"} + ]}, + {code_173, [ + {ssh, "your server ip"}, + {port, 22}, + {user, "user name"}, + {password, "your password"} + ]} + ], + %% beam directory of app. + "/home/zhaoxu-b/", + "/data/deploy/bin/deploy restart", + 'cookie_deploy_2013_by_denofiend', + ['deploy01@127.0.0.1', 'deploy02@127.0.0.1', 'deploy03@127.0.0.1'] + } + } + ]} + ] + } +]. diff --git a/rel/files/deploy b/rel/files/deploy new file mode 100644 index 0000000..ec6d7ff --- /dev/null +++ b/rel/files/deploy @@ -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/rel/files/env.sh b/rel/files/env.sh new file mode 100644 index 0000000..28a6799 --- /dev/null +++ b/rel/files/env.sh @@ -0,0 +1,229 @@ +#!/bin/sh +# -*- tab-width:4;indent-tabs-mode:nil -*- +# ex: ts=4 sw=4 et + +# installed by node_package (github.com/basho/node_package) + +# /bin/sh on Solaris is not a POSIX compatible shell, but /usr/bin/ksh is. +if [ `uname -s` = 'SunOS' -a "${POSIX_SHELL}" != "true" ]; then + POSIX_SHELL="true" + export POSIX_SHELL + # To support 'whoami' add /usr/ucb to path + PATH=/usr/ucb:$PATH + export PATH + exec /usr/bin/ksh $0 "$@" +fi +unset POSIX_SHELL # clear it so if we invoke other scripts, they run as ksh as well + +RUNNER_SCRIPT_DIR={{runner_script_dir}} +RUNNER_SCRIPT=${0##*/} + +RUNNER_BASE_DIR={{runner_base_dir}} +RUNNER_ETC_DIR={{runner_etc_dir}} +RUNNER_LOG_DIR={{runner_log_dir}} +RUNNER_LIB_DIR={{runner_lib_dir}} +RUNNER_PATCH_DIR={{runner_patch_dir}} +PIPE_DIR={{pipe_dir}} +RUNNER_USER={{runner_user}} +APP_VERSION={{app_version}} + +# Variables needed to support creation of .pid files +# PID directory and pid file name of this app +# ex: /var/run/riak & /var/run/riak/riak.pid +RUN_DIR="/var/run" # for now hard coded unless we find a platform that differs +PID_DIR=$RUN_DIR/$RUNNER_SCRIPT +PID_FILE=$PID_DIR/$RUNNER_SCRIPT.pid + +# Threshold where users will be warned of low ulimit file settings +# default it if it is not set +ULIMIT_WARN={{runner_ulimit_warn}} +if [ -z "$ULIMIT_WARN" ]; then + ULIMIT_WARN=4096 +fi + +# Registered process to wait for to consider start a success +WAIT_FOR_PROCESS={{runner_wait_process}} + +WHOAMI=$(whoami) + +# Echo to stderr on errors +echoerr() { echo "$@" 1>&2; } + +# Extract the target node name from node.args +NAME_ARG=`egrep '^\-s?name' $RUNNER_ETC_DIR/vm.args` +if [ -z "$NAME_ARG" ]; then + echoerr "vm.args needs to have either -name or -sname parameter." + exit 1 +fi + +# Learn how to specify node name for connection from remote nodes +echo "$NAME_ARG" | grep '^-sname' > /dev/null 2>&1 +if [ "X$?" = "X0" ]; then + NAME_PARAM="-sname" + NAME_HOST="" +else + NAME_PARAM="-name" + echo "$NAME_ARG" | grep '@.*' > /dev/null 2>&1 + if [ "X$?" = "X0" ]; then + NAME_HOST=`echo "${NAME_ARG}" | sed -e 's/.*\(@.*\)$/\1/'` + else + NAME_HOST="" + fi +fi + +# Extract the target cookie +COOKIE_ARG=`grep '^\-setcookie' $RUNNER_ETC_DIR/vm.args` +if [ -z "$COOKIE_ARG" ]; then + echoerr "vm.args needs to have a -setcookie parameter." + exit 1 +fi + +# 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" +NODETOOL_LITE="$ERTS_PATH/escript $ERTS_PATH/nodetool" + +# Ping node without stealing stdin +ping_node() { + $NODETOOL ping < /dev/null +} + +# Attempts to create a pid directory like /var/run/APPNAME and then +# changes the permissions on that directory so the $RUNNER_USER can +# read/write/delete .pid files during startup/shutdown +create_pid_dir() { + # Validate RUNNER_USER is set and they have permissions to write to /var/run + # Don't continue if we've already sudo'd to RUNNER_USER + if ([ "$RUNNER_USER" ] && [ "x$WHOAMI" != "x$RUNNER_USER" ]); then + if [ -w $RUN_DIR ]; then + mkdir -p $PID_DIR + ES=$? + if [ "$ES" -ne 0 ]; then + return 1 + else + # Change permissions on $PID_DIR + chown $RUNNER_USER $PID_DIR + ES=$? + if [ "$ES" -ne 0 ]; then + return 1 + else + return 0 + fi + fi + else + # If we don't have permissions, fail + return 1 + fi + fi + + # If RUNNER_USER is not set this is probably a test setup (devrel) and does + # not need a .pid file, so do not return error + return 0 +} + +# Attempt to create a pid file for the process +# This function assumes the process is already up and running and can +# respond to a getpid call. It also assumes that two processes +# with the same name will not be run on the machine +# Do not print any error messages as failure to create a pid file because +# pid files are strictly optional +# This function should really only be called in a "start" function +# you have been warned +create_pid_file() { + # Validate a pid directory even exists + if [ -w $PID_DIR ]; then + # Grab the proper pid from getpid + get_pid + ES=$? + if [ "$ES" -ne 0 ]; then + return $ES + else + # Remove pid file if it already exists since we do not + # plan for multiple identical runners on a single machine + rm -f $PID_FILE + echo $PID > $PID_FILE + return 0 + fi + else + return 1 + fi +} + +# Function to su into correct user +check_user() { + # Validate that the user running the script is the owner of the + # RUN_DIR. + if ([ "$RUNNER_USER" ] && [ "x$WHOAMI" != "x$RUNNER_USER" ]); then + type sudo > /dev/null 2>&1 + if [ "$?" -ne 0 ]; then + echoerr "sudo doesn't appear to be installed and your EUID isn't $RUNNER_USER" 1>&2 + exit 1 + fi + exec sudo -H -u $RUNNER_USER -i $RUNNER_SCRIPT_DIR/$RUNNER_SCRIPT $@ + fi +} + +# Function to validate the node is down +node_down_check() { + MUTE=`ping_node 2> /dev/null` + if [ "$?" -eq 0 ]; then + echoerr "Node is already running!" + exit 1 + fi +} + +# Function to validate the node is up +node_up_check() { + MUTE=`ping_node 2> /dev/null` + if [ "$?" -ne 0 ]; then + echoerr "Node is not running!" + exit 1 + fi +} + +# Function to check if the config file is valid +check_config() { + MUTE=`$NODETOOL_LITE chkconfig $RUNNER_ETC_DIR/app.config` + if [ "$?" -ne 0 ]; then + echoerr "Error reading $RUNNER_ETC_DIR/app.config" + exit 1 + fi + echo "config is OK" +} + +# Function to check if ulimit is properly set +check_ulimit() { + + # don't fail if this is unset + if [ ! -z "$ULIMIT_WARN" ]; then + ULIMIT_F=`ulimit -n` + if [ "$ULIMIT_F" -lt $ULIMIT_WARN ]; then + echo "!!!!" + echo "!!!! WARNING: ulimit -n is ${ULIMIT_F}; ${ULIMIT_WARN} is the recommended minimum." + echo "!!!!" + fi + fi +} + +# Set the PID global variable, return 1 on error +get_pid() { + PID=`$NODETOOL getpid < /dev/null` + if [ "$?" -ne 0 ]; then + echo "Node is not running!" + return 1 + fi + + # don't allow empty or init pid's + if [ -z $PID ] || [ "$PID" -le 1 ]; then + return 1 + fi + + return 0 +} diff --git a/rel/files/erl b/rel/files/erl new file mode 100644 index 0000000..e500626 --- /dev/null +++ b/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+"$@"} \ No newline at end of file diff --git a/rel/files/nodetool b/rel/files/nodetool new file mode 100644 index 0000000..eb08fa4 --- /dev/null +++ b/rel/files/nodetool @@ -0,0 +1,138 @@ +%% -*- mode: erlang;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) -> + ok = start_epmd(), + %% 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). + + +start_epmd() -> + [] = os:cmd(epmd_path() ++ " -daemon"), + ok. + +epmd_path() -> + ErtsBinDir = filename:dirname(escript:script_name()), + Name = "epmd", + case os:find_executable(Name, ErtsBinDir) of + false -> + case os:find_executable(Name) of + false -> + io:format("Could not find epmd.~n"), + halt(1); + GlobalEpmd -> + GlobalEpmd + end; + Epmd -> + Epmd + end. + + +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/rel/files/vm.args b/rel/files/vm.args new file mode 100644 index 0000000..c3f7193 --- /dev/null +++ b/rel/files/vm.args @@ -0,0 +1,29 @@ + +## Name of the node +-name deploy@127.0.0.1 + +## Cookie for distributed erlang +-setcookie cookie_deploy_2013_by_denofiend + +## 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 64 + ++P 250000 + ++zdbbl 32768 + ++S 12:12 +## Increase number of concurrent ports/sockets +-env ERL_MAX_PORTS 64000 +-env ERL_MAX_ETS_TABLES 256000 + +## Tweak GC to run more often +-env ERL_FULLSWEEP_AFTER 0 + +-env ERL_CRASH_DUMP_SECONDS 15 diff --git a/rel/reltool.config b/rel/reltool.config new file mode 100644 index 0000000..0a1e19a --- /dev/null +++ b/rel/reltool.config @@ -0,0 +1,35 @@ +{sys, [ + {lib_dirs, ["../../"]}, + {rel, "deploy", "1.0.0", + [ + %% deps system application + kernel, + stdlib, + deploy + ]}, + {app_file, strip}, + {rel, "start_clean", "", + [ + kernel, + stdlib + ]}, + {boot_rel, "deploy"}, + {profile, embedded}, + {excl_archive_filters, [".*"]}, %% Do not archive built libs + {excl_sys_filters, ["^bin/.*", + "^erts.*/bin/(dialyzer|typer)"]}, + {app, deploy, [{incl_cond, include}]} +]}. + +{target_dir, "deploy"}. + +{overlay_vars, "vars.config"}. + +{overlay, [ + {copy, "files/erl", "{{erts_vsn}}/bin/erl"}, + {copy, "files/nodetool", "{{erts_vsn}}/bin/nodetool"}, + {copy, "files/deploy", "bin/deploy"}, + {copy, "files/app.config", "etc/app.config"}, + {copy, "files/vm.args", "etc/vm.args"}, + {template, "files/env.sh", "lib/env.sh"} +]}. diff --git a/rel/vars.config b/rel/vars.config new file mode 100644 index 0000000..62cc56d --- /dev/null +++ b/rel/vars.config @@ -0,0 +1,31 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ft=erlang ts=4 sw=4 et + +%% Platform-specific installation paths +{platform_bin_dir, "./bin"}. +{platform_data_dir, "./data"}. +{platform_etc_dir, "./etc"}. +{platform_lib_dir, "./lib"}. +{platform_log_dir, "./log"}. + +%% +%% etc/app.config +%% +{ring_state_dir, "{{platform_data_dir}}/ring"}. +{bitcask_data_root, "{{platform_data_dir}}/bitcask"}. +{leveldb_data_root, "{{platform_data_dir}}/leveldb"}. +{sasl_error_log, "{{platform_log_dir}}/sasl-error.log"}. +{sasl_log_dir, "{{platform_log_dir}}/sasl"}. + + +%% +%% bin/riak +%% +{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"}. +{runner_lib_dir, "$RUNNER_BASE_DIR/lib"}. +{runner_patch_dir, "$RUNNER_BASE_DIR/lib/basho-patches"}. +{pipe_dir, "/tmp/$RUNNER_BASE_DIR/"}. +{runner_user, ""}. diff --git a/src/deploy.app.src b/src/deploy.app.src new file mode 100644 index 0000000..3886f4d --- /dev/null +++ b/src/deploy.app.src @@ -0,0 +1,20 @@ +%%%------------------------------------------------------------------- +%%% @author zhaoxu-b +%%% @copyright (C) 2013, +%%% @doc +%%% +%%% @end +%%% Created : 31. 十月 2013 上午10:31 +%%%------------------------------------------------------------------- +{application, deploy, [ + {description, ""}, + {vsn, "1.0.0"}, + {registered, []}, + {applications, [ + kernel, + stdlib + ]}, + {mod, {deploy, []}}, + {env, [ + ]} +]}. diff --git a/src/deploy.erl b/src/deploy.erl new file mode 100644 index 0000000..b2fe178 --- /dev/null +++ b/src/deploy.erl @@ -0,0 +1,255 @@ +%%%------------------------------------------------------------------- +%%% @author zhaoxu-b +%%% @copyright (C) 2013, +%%% @doc +%%% +%%% @end +%%% Created : 30. 十月 2013 下午4:21 +%%%------------------------------------------------------------------- +-module(deploy). +-author("zhaoxu-b"). + +-behaviour(application). + +-export([ + start/2, + stop/1, deploy_file/2, restart_server/1, deploy_dir/2, hot_upgrade/2 +]). + + +-record(app_config, { + server_list, + app_dir, + restart_command, + cookie, + nodes +}). + +%% -------------------------------------------------------------------- +%% Function: start_ssh_config/0 +%% Description: start ct_run, ct_config, ct_util +%% Returns: {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | (terminate/2 is called) +%% {stop, Reason, State} (terminate/2 is called) +%% -------------------------------------------------------------------- +start_ssh_config() -> + io:format("start_ssh_config"), + ct_run:install([]), + ct_config:start(interactive), + ct_util:start(), + + Configs = case application:get_env(deploy, apps) of + {ok, AppConfigs} -> + AppConfigs; + _ -> + [] + end, + io:format("Configs:~p~n", [Configs]), + + lists:foreach( + fun(Conf) -> + io:format("Conf:~p~n", [Conf]), + {_App, #app_config{server_list = ServerList}} = Conf, + lists:foreach( + fun(OneConfig) -> + io:format("Name:~p, OneConfig:~p~n", ['_UNDEF', [OneConfig]]), + ct_config:set_default_config('_UNDEF', [OneConfig], undefined) + end, ServerList) + end, Configs). + + +start(_Type, []) -> + start_ssh_config(), + deploy_sup:start_link(). + +%% @spec stop(State::term()) -> any() +%% +%% @doc This is callback after application shut down. +%% @private +stop(State) -> + ct_util:stop(State), + ok. + + +%% -------------------------------------------------------------------- +%% Function: get_config/1 +%% Description: get app config from application env. +%% Returns: {ok, Config} +%% -------------------------------------------------------------------- +get_config(App) -> + case application:get_env(deploy, apps) of + {ok, Configs} -> + case lists:keyfind(App, 1, Configs) of + false -> + {error, lists:connect([App, " is undefined"])}; + Config -> + {ok, Config} + end; + _ -> + {error, "apps is undfined"} + end. + +%% -------------------------------------------------------------------- +%% Function: send_dir/4 +%% Description: scp directory. +%% Returns: ok +%% -------------------------------------------------------------------- +send_dir(_AppDir, _CH, _Dir, []) -> + ok; + +send_dir(AppDir, CH, Dir, [File | T]) -> + FullName = filename:join([Dir, File]), + case filelib:is_file(FullName) of + true -> + %% sync file to server + {ok, FileData} = file:read_file(FullName), + ct_ssh:write_file(CH, lists:concat([AppDir, filename:basename(FullName)]), FileData); + false -> + send_dir(AppDir, CH, FullName, file:list_dir(FullName)) + end, + send_dir(AppDir, CH, Dir, T). + +%% -------------------------------------------------------------------- +%% Function:deploy_dir/2 +%% Description: deploy directory to remote servers for app. +%% Returns: ok +%% -------------------------------------------------------------------- +deploy_dir(App, Dir) -> + io:format("deploy dir ~p servers, Directory:~p~n", [App, Dir]), + + try + case get_config(App) of + {ok, {App, Config}} -> + #app_config{server_list = ServerList, app_dir = AppDir} = Config, + {ok, FileNames} = file:list_dir(Dir), + + lists:foreach( + fun(ServerConfig) -> + case ServerConfig of + {Name, _} -> + {ok, CH} = ct_ssh:connect(Name, ssh), + + send_dir(AppDir, CH, Dir, FileNames), + + ct_ssh:disconnect(CH); + _ -> + throw({error, "ssh config error"}) + end + end, ServerList), + ok; + _ -> + io:format("~p is undefined App in deploy.app file.~n", [App]), + throw({error, "app is undefined"}) + end + catch + Any -> + Any + end. + +%% -------------------------------------------------------------------- +%% Function:deploy_file/2 +%% Description: deploy file to remote servers for app. +%% Returns: ok +%% -------------------------------------------------------------------- +deploy_file(App, File) -> + io:format("deploy file ~p servers, file:~p~n", [App, File]), + try + case get_config(App) of + {ok, {App, Config}} -> + #app_config{server_list = ServerConfigList, app_dir = AppDir} = Config, + %% sync file to server + case file:read_file(File) of + {ok, FileData} -> + lists:foreach( + fun(ServerConfig) -> + case ServerConfig of + {Name, _} -> + %%io:format("Name:~p, AppDir:~p, RemoteRelPath:~p~n", [Name, AppDir, lists:concat([AppDir, filename:basename(File)])]), + {ok, CH} = ct_ssh:connect(Name, sftp), + ct_ssh:write_file(CH, lists:concat([AppDir, filename:basename(File)]), FileData), + ct_ssh:disconnect(CH); + _ -> + throw({error, "ssh config error"}) + end + end, ServerConfigList); + _ -> + throw({error, "file is not exists."}) + end, + ok; + Error -> + io:format("err:~p.~n", [Error]), + Error + end + catch + Any -> + Any + end. + +%% -------------------------------------------------------------------- +%% Function:restart_server/1 +%% Description: restart all servers of App. +%% Returns: ok +%% -------------------------------------------------------------------- +restart_server(App) -> + io:format("restart ~p servers~n", [App]), + try + case get_config(App) of + {ok, {App, Config}} -> + #app_config{server_list = ServerList, restart_command = RestartCommand} = Config, + lists:foreach( + fun(ServerConfig) -> + case ServerConfig of + {Name, _} -> + {ok, CH1} = ct_ssh:connect(Name, ssh), + ct_ssh:exec(CH1, RestartCommand), + ct_ssh:disconnect(CH1); + _ -> + throw({error, "ssh config error"}) + end + end, ServerList), + ok; + Error -> + io:format("err:~p.~n", [Error]), + Error + end + catch + Any -> + Any + end. + +%% -------------------------------------------------------------------- +%% Function:hot_upgrade/2 +%% Description: hot upgrade one Module in all servers of App. +%% Returns: ok +%% -------------------------------------------------------------------- +hot_upgrade(App, Module) -> + io:format("hot upgrade ~p servers, Module:~p~n", [App, Module]), + try + case get_config(App) of + {ok, {App, Config}} -> + #app_config{cookie = Cookie, nodes = Nodes} = Config, + + %% set cookie + erlang:set_cookie(node(), Cookie), + %% ping other nodes. + lists:foreach(fun(ServerNode) -> net_adm:ping(ServerNode) end, Nodes), + + io:format("server nodes:~p~n", [nodes()]), + + %% hot upgrade the Module + {Mod, Bin, File} = code:get_object_code(Module), + {ResL, BadNodes} = rpc:multicall(code, load_binary, [Mod, File, Bin]), + io:format("Res:~p, BadNodes:~p~n", [ResL, BadNodes]), + ok; + Error -> + io:format("err:~p.~n", [Error]), + Error + end + catch + Any -> + Any + end. + diff --git a/src/deploy_sup.erl b/src/deploy_sup.erl new file mode 100644 index 0000000..ff7e66f --- /dev/null +++ b/src/deploy_sup.erl @@ -0,0 +1,64 @@ +%%%------------------------------------------------------------------- +%%% @author zhaoxu-b +%%% @copyright (C) 2013, +%%% @doc +%%% +%%% @end +%%% Created : 01. 十一月 2013 下午3:56 +%%%------------------------------------------------------------------- +-module(deploy_sup). +-author("zhaoxu-b"). + +-behaviour(supervisor). + +%% API +-export([start_link/0]). + +%% Supervisor callbacks +-export([init/1]). + +-define(SERVER, ?MODULE). + +%%%=================================================================== +%%% API functions +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% @doc +%% Starts the supervisor +%% +%% @spec start_link() -> {ok, Pid} | ignore | {error, Error} +%% @end +%%-------------------------------------------------------------------- +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +%%%=================================================================== +%%% Supervisor callbacks +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Whenever a supervisor is started using supervisor:start_link/[2,3], +%% this function is called by the new process to find out about +%% restart strategy, maximum restart frequency and child +%% specifications. +%% +%% @spec init(Args) -> {ok, {SupFlags, [ChildSpec]}} | +%% ignore | +%% {error, Reason} +%% @end +%%-------------------------------------------------------------------- +init([]) -> + RestartStrategy = one_for_one, + MaxRestarts = 1000, + MaxSecondsBetweenRestarts = 3600, + + SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts}, + + {ok, {SupFlags, []}}. + +%%%=================================================================== +%%% Internal functions +%%%===================================================================