#!/bin/bash description=" fence_ec2 is an I/O Fencing agent which can be used with Amazon EC2 instances. API functions used by this agent: - aws ec2 describe-tags - aws ec2 describe-instances - aws ec2 stop-instances - aws ec2 start-instances - aws 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) 2018 Stefan Schneider # Copyright (c) 2018 Kristoffer Gronlund # Copyright (c) 2011-2013 Andrew Beekhof # Copyright (c) 2014 NIPPON TELEGRAPH AND TELEPHONE CORPORATION # 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, see . # ####################################################################### quiet=0 instance_not_found=0 unknown_are_stopped=0 action_default="reset" # Default fence action ec2_tag_default="Name" # EC2 Tag containing the instance's uname ec2_profile_default="default" # EC2 Profile containing the AWS's profile sleep_time="1" # Set the correct value for the tag [ -n "$tag" ] && ec2_tag="$tag" : ${ec2_tag=${ec2_tag_default}} # Set the correct value for the profile [ -n "$profile" ] && ec2_profile="$profile" : ${ec2_profile=${ec2_profile_default}} # Always invoke aws command with UTF-8 locale # to avoid issues when the tag contains non-ASCII # characters (bsc#1059171) LC_ALL=en_US.UTF-8 export LC_ALL function usage() { cat < Fencing Action The name/id/tag of a instance to control/check Use a specific profile from your credential file. Name of the tag containing the instances uname DANGER: Assume any unknown instance is safely stopped EOF exit 0; } function metadata() { cat < $description Fencing Action The name/id/tag of a instance to control/check Use a specific profile from your credential file. Name of the tag containing the instances uname DANGER: Assume any unknown instance is safely stopped EOF exit 0; } function is_instance_running() { local token local myinstance local mystatus # get session token, required for IMDSv2 token="$(curl -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" -X PUT http://169.254.169.254/latest/api/token)" # get my instance id myinstance="$(curl -H "X-aws-ec2-metadata-token: $token" http://169.254.169.254/latest/meta-data/instance-id)" # check my status. # When the EC2 instance be stopped by the "aws ec2 stop-instances" , the stop processing of the OS is executed. # While the OS stop processing, Pacemaker can execute the STONITH processing. # So, If my status is not "running", it determined that I was already fenced. And to prevent fencing each other # in split-brain, I don't fence other node. if [ -z "$myinstance" ]; then ha_log.sh err "Failed to get Instance ID. Unable to check instance status." return 1 fi mystatus="$(instance_status $myinstance)" if [ "$mystatus" != "running" ]; then #do not fence ha_log.sh warn "Already fenced (Instance status = $mystatus). Aborting fence attempt." return 1 fi return 0 } function instance_for_port() { local port=$1 local instance="" # Look for port name -n in the INSTANCE data instance=`aws ec2 describe-instances $options --filters "Name=tag-value,Values=${port}" "Name=tag-key,Values=${ec2_tag}" --query 'Reservations[*].Instances[*].InstanceId' ` if [ -z $instance ]; then instance_not_found=1 instance=$port fi echo $instance } function instance_on() { aws ec2 start-instances $options --instance-ids $instance } function instance_off() { if [ "$unknown_are_stopped" = 1 -a $instance_not_found ]; then # nothing to do ha_log.sh info "Assuming unknown instance $instance is already off" else aws ec2 stop-instances $options --instance-ids $instance --force fi } function instance_status() { local instance=$1 local status="unknown" local rc=1 # List of instances and their current status if [ "$unknown_are_stopped" = 1 -a $instance_not_found ]; then ha_log.sh info "$instance stopped (unknown)" else status=`aws ec2 describe-instances $options --instance-ids $instance --query 'Reservations[*].Instances[*].State.Name' ` rc=$? fi ha_log.sh info "status check for $instance is $status" echo $status return $rc } function monitor() { # Is the device ok? aws ec2 describe-instances $options --filters "Name=tag-key,Values=${ec2_tag}" | grep INSTANCES &> /dev/null } TEMP=`getopt -o qVho:e:p:n:t:U --long version,help,action:,port:,option:,profile:,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/.*=//`;; profile=*) ec2_profile=`echo $line | sed s/.*=//`;; tag=*) ec2_tag=`echo $line | sed s/.*=//`;; quiet*) quiet=1;; unknown-are-stopped*) unknown_are_stopped=1;; --);; *) ha_log.sh err "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;; -p|--profile) ec2_profile=$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 ;; *) ha_log.sh err "Unknown option: $1. See --help for details."; exit 1;; esac done [ -n "$1" ] && action=$1 [ -n "$2" ] && node_to_fence=$2 options="--output text --profile $ec2_profile" action=`echo $action | tr 'A-Z' 'a-z'` case $action in metadata) metadata ;; getinfo-xml) getinfo_xml ;; getconfignames) for i in profile port tag unknown_are_stopped do echo $i done exit 0 ;; getinfo-devid) echo "EC2 STONITH device" exit 0 ;; getinfo-devname) echo "EC2 STONITH external device" exit 0 ;; getinfo-devdescr) echo "ec2 is an I/O Fencing agent which can be used with Amazon EC2 instances." exit 0 ;; getinfo-devurl) echo "" exit 0 ;; esac if [ -z "$port" ]; then port="$node_to_fence" fi # get target's instance id instance="" if [ ! -z "$port" ]; then instance=`instance_for_port $port $options` fi is_instance_running || exit 1 case $action in reboot|reset) status=`instance_status $instance` if [ "$status" != "stopped" ]; then instance_off fi while true; do status=`instance_status $instance` if [ "$status" = "stopped" ]; then break fi sleep $sleep_time done instance_on while true; do status=`instance_status $instance` if [ "$status" = "running" ]; then break fi sleep $sleep_time done ;; poweron|on) instance_on while true; do status=`instance_status $instance` if [ "$status" = "running" ]; then break fi done ;; poweroff|off) instance_off while true; do status=`instance_status $instance` if [ "$status" = "stopped" ]; then break fi sleep $sleep_time done ;; monitor) monitor ;; gethosts|hostlist|list) # List of names we know about a=`aws ec2 describe-instances $options --filters "Name=tag-key,Values=${ec2_tag}" --query 'Reservations[*].Instances[*].Tags[?Key==\`'${ec2_tag}'\`].Value' | sort -u` echo $a ;; stat|status) monitor ;; *) ha_log.sh err "Unknown action: $action"; exit 1;; esac status=$? if [ $quiet -eq 1 ]; then : nothing elif [ $status -eq 0 ]; then ha_log.sh info "Operation $action passed" else ha_log.sh err "Operation $action failed: $status" fi exit $status