Skip to content

Instantly share code, notes, and snippets.

@beekhof
Created April 26, 2011 10:32
Show Gist options
  • Save beekhof/942090 to your computer and use it in GitHub Desktop.
Save beekhof/942090 to your computer and use it in GitHub Desktop.
Fencing agent for Amazon EC2
#!/bin/bash
description="
fence_ec2 is an I/O Fencing agent which can be used with Amazon EC2 instances.
In order to function, the agent needs the private key and cert used by the Amazon EC2 API.
API functions used by this agent:
- ec2-describe-tags
- ec2-describe-instances
- ec2-stop-instances
- ec2-start-instances
- ec2-reboot-instances
If the uname used by the cluster node is any of:
- Public DNS name (or part there of),
- Private DNS name (or part there of),
- Instance ID (eg. i-4f15a839)
- Contents of tag associated with the instance
then the agent should be able to automatically discover the instances it can control.
If the tag containing the uname is not [Name], then it will need to be specified using the [tag] option.
"
#
# Copyright (c) 2011 Andrew Beekhof
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it would be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Further, this software is distributed without any warranty that it is
# free of the rightful claim of any third person regarding infringement
# or the like. Any license provided herein, whether implied or
# otherwise, applies only to this software file. Patent licenses, if
# any, provided herein do not apply to combinations of this program with
# other software, or any other product whatsoever.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
#
#######################################################################
quiet=0
port=""
instance_not_found=0
unknown_are_stopped=0
action="reset" # Default fence action
ec2_tag="Name" # EC2 Tag containing the instance's uname
ec2_key="" # EC2 Private Key
ec2_cert="" # EC2 Cert
ec2_region="us-east-1" # EC2 Region
if [ -z "$EC2_HOME" ]; then
EC2_HOME="$HOME/.ec2"
fi
function usage()
{
cat <<EOF
`basename $0` - A fencing agent for Amazon EC2 instances
$description
Usage: `basename $0` -o|--action [-n|--port] [options]
Options:
-h, --help This text
-V, --version Version information
-q, --quiet Reduced output mode
Commands:
-o, --action Action to perform: on|off|reboot|status|monitor
-n, --port The name of a machine/instance to control/check
Additional Options:
-e, --ec2-home Location of Amazon EC2 command line tools
-k, --private-key The private key to use when constructing requests to Amazon EC2
-c, --cert The X.509 certificate to use when constructing requests to Amazon EC2
-r, --region The Amazon region for which the device should control instances (defaults to us-east-1)
-t, --tag Name of the tag containing the instance's uname
Dangerous options:
-U, --unknown-are-stopped Assume any unknown instance is safely stopped
EOF
exit 0;
}
function metadata()
{
cat <<EOF
<?xml version="1.0" ?>
<resource-agent name="fence_ec2" shortdesc="Fencing agent for Amazon EC2 instances" >
<longdesc>
$description
</longdesc>
<parameters>
<parameter name="action" unique="1" required="1">
<getopt mixed="-o, --action=[action]" />
<content type="string" default="reboot" />
<shortdesc lang="en">Fencing Action</shortdesc>
</parameter>
<parameter name="port" unique="1" required="1">
<getopt mixed="-n, --port=[port]" />
<content type="string" />
<shortdesc lang="en">The name/id/tag of a instance to control/check</shortdesc>
</parameter>
<parameter name="ec2-home" unique="1" required="1">
<getopt mixed="-e, --ec2-home=[directory]" />
<content type="string" default="~/.ec2" />
<shortdesc lang="en">Location of Amazon EC2 command line tools</shortdesc>
</parameter>
<parameter name="private-key" unique="1" required="1">
<getopt mixed="-k, --private-key=[filename]" />
<content type="string" default="$ec2-home/pk-*.pem" />
<shortdesc lang="en">The private key to use when constructing requests to Amazon EC2</shortdesc>
</parameter>
<parameter name="cert" unique="1" required="1">
<getopt mixed="-c, --cert=[filename]" />
<content type="string" default="$ec2-home/cert-*.pem" />
<shortdesc lang="en">The X.509 certificate to use when constructing requests to Amazon EC2</shortdesc>
</parameter>
<parameter name="region" unique="1" required="1">
<getopt mixed="-r, --region=[region]" />
<content type="string" default="us-east-1" />
<shortdesc lang="en">The Amazon region for which the device should control instances</shortdesc>
</parameter>
<parameter name="tag" unique="1" required="1">
<getopt mixed="-t, --tag=[tag]" />
<content type="string" default="Name" />
<shortdesc lang="en">Name of the tag containing the instances uname</shortdesc>
</parameter>
<parameter name="unknown-are-stopped" unique="1" required="1">
<getopt mixed="-U, --unknown-are-stopped" />
<content type="string" default="false" />
<shortdesc lang="en">DANGER: Assume any unknown instance is safely stopped</shortdesc>
</parameter>
</parameters>
<actions>
<action name="on" />
<action name="off" />
<action name="reboot" />
<action name="status" />
<action name="list" />
<action name="monitor" />
<action name="metadata" />
</actions>
</resource-agent>
EOF
exit 0;
}
function instance_for_port()
{
port=$1; shift
# Look for port name -n in the INSTANCE data
instance=`ec2-describe-instances $* | grep INSTANCE.*$port | awk '{print $2}'`
if [ -z $instance ]; then
# Look for port name -n in the Name TAG
instance=`ec2-describe-tags $* | grep TAG.*$ec2_tag.*$port | awk '{print $3}'`
fi
if [ -z $instance ]; then
instance_not_found=1
instance=$port
fi
echo $instance
}
TEMP=`getopt -o qVo:e:k:c:r:n:t:U --long version,help,region:,action:,port:,option:,ec2-home:,private-key:,cert:,tag:,quiet,unknown-are-stopped \
-n 'fence_ec2' -- "$@"`
if [ $? != 0 ];then
usage
exit 1
fi
# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"
if [ -z $1 ]; then
# If there are no command line args, look for options from stdin
while read line; do
case $line in
option=*|action=*) action=`echo $line | sed s/.*=//`;;
port=*) port=`echo $line | sed s/.*=//`;;
ec2-home=*) EC2_HOME=`echo $line | sed s/.*=//`;;
private-key=*) ec2_key=`echo $line | sed s/.*=//`;;
cert=*) ec2_cert=`echo $line | sed s/.*=//`;;
region=*) ec2_region=`echo $line | sed s/.*=//`;;
tag=*) ec2_tag=`echo $line | sed s/.*=//`;;
quiet*) quiet=1;;
unknown-are-stopped*) unknown_are_stopped=1;;
--);;
*) echo "Invalid command: $line";;
esac
done
fi
while true ; do
case "$1" in
-o|--action|--option) action=$2; shift; shift;;
-n|--port) port=$2; shift; shift;;
-e|--ec2-home) EC2_HOME=$2; shift; shift;;
-k|--private-key) ec2_key=$2; shift; shift;;
-c|--cert) ec2_cert=$2; shift; shift;;
-r|--region) ec2_region=$2; shift; shift;;
-t|--tag) ec2_tag=$2; shift; shift;;
-U|--unknown-are-stopped) unknown_are_stopped=1; shift;;
-q|--quiet) quiet=1; shift;;
-V|--version) echo "1.0.0"; exit 0;;
--help|-h)
usage;
exit 0;;
--) shift ; break ;;
*) echo "Unknown option: $1. See --help for details."; exit 1;;
esac
done
export EC2_HOME
PATH=$PATH:$EC2_HOME/bin
if [ -z "$JAVA_HOME" ]; then
java=`which java`
while [ -L "$java" ]; do
java=`/bin/ls -l $java | awk '{print $11}'`
done
export JAVA_HOME=`dirname $java`/..
fi
if [ -z "$ec2_key" ]; then
ec2_key=`ls $EC2_HOME/pk-*.pem`;
fi
if [ -z "$ec2_cert" ]; then
ec2_cert=`ls $EC2_HOME/cert-*.pem`;
fi
options="--region $ec2_region --private-key $ec2_key --cert $ec2_cert"
action=`echo $action | tr 'A-Z' 'a-z'`
instance=""
if [ ! -z "$port" ]; then
instance=`instance_for_port $port $options`
fi
case $action in
reboot|reset)
if [ $unknown_are_stopped = 1 -a $instance_not_found ]; then
: nothing we _can_ do
echo "Assuming unknown instance $instance is already off, cannot restart"
else
ec2-reboot-instances $options $instance
fi
;;
poweron|on)
ec2-start-instances $options $instance
;;
poweroff|off)
if [ $unknown_are_stopped = 1 -a $instance_not_found ]; then
: nothing to do
echo "Assuming unknown instance $instance is already off"
else
ec2-stop-instances $options $instance
fi
;;
monitor)
# Is the device ok?
ec2-describe-instances | grep INSTANCE &> /dev/null
;;
hostlist|list)
# List of names we know about
ec2-describe-instances $options | awk -v tag_pat="^TAG\tinstance\t.*\t$ec2_tag" -F '\t' '{
if (/^INSTANCE.*pending/) { printf "%s\n", $2 }
else if (/^INSTANCE.*stopped/) { printf "%s\n", $2 }
else if (/^INSTANCE/) { printf "%s\n%s\n%s\n", $2, $4, $5 }
else if ( $1"\t"$2"\t"$3"\t"$4 ~ tag_pat ) { printf "%s\n", $5 }
}' | sort -u
;;
stat|status)
# List of instances and their current status
if [ $unknown_are_stopped = 1 -a $instance_not_found ]; then
echo "$instance stopped (unknown)"
else
ec2-describe-instances $options $instance | awk '{
if (/^INSTANCE.*pending/) { printf "%s %s\n", $2, $4 }
else if (/^INSTANCE.*stopped/) { printf "%s %s\n", $2, $4 }
else if (/^INSTANCE/) { printf "%s %s %s %s\n", $2, $6, $4, $5 }
}'
fi
;;
metadata)
metadata
;;
*) echo "Unknown action: $action"; exit 1;;
esac
status=$?
if [ $quiet -eq 1 ]; then
: nothing
elif [ $status -eq 0 ]; then
echo "Operation $action passed"
else
echo "Operation $action failed: $status"
fi
exit $status
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment