diff --git a/heartbeat/aws-vpc-move-ip b/heartbeat/aws-vpc-move-ip index 6115e5ba8..fbeb2ee64 100755 --- a/heartbeat/aws-vpc-move-ip +++ b/heartbeat/aws-vpc-move-ip @@ -1,541 +1,541 @@ #!/bin/sh # # # OCF resource agent to move an IP address within a VPC in the AWS # # Copyright (c) 2017 Markus Guertler (SUSE) # Based on code of Adam Gandelman (GitHub ec2-resource-agents/elasticip) # 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. # ####################################################################### # Initialization: : ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat} . ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs # Defaults OCF_RESKEY_awscli_default="/usr/bin/aws" OCF_RESKEY_auth_type_default="key" OCF_RESKEY_profile_default="default" OCF_RESKEY_region_default="" OCF_RESKEY_ip_default="" OCF_RESKEY_address_default="" OCF_RESKEY_routing_table_default="" OCF_RESKEY_routing_table_role_default="" OCF_RESKEY_interface_default="eth0" OCF_RESKEY_iflabel_default="" OCF_RESKEY_monapi_default="false" OCF_RESKEY_lookup_type_default="InstanceId" OCF_RESKEY_curl_retries_default="3" OCF_RESKEY_curl_sleep_default="1" : ${OCF_RESKEY_awscli=${OCF_RESKEY_awscli_default}} : ${OCF_RESKEY_auth_type=${OCF_RESKEY_auth_type_default}} : ${OCF_RESKEY_profile=${OCF_RESKEY_profile_default}} : ${OCF_RESKEY_region=${OCF_RESKEY_region_default}} : ${OCF_RESKEY_ip=${OCF_RESKEY_ip_default}} : ${OCF_RESKEY_address=${OCF_RESKEY_address_default}} : ${OCF_RESKEY_routing_table=${OCF_RESKEY_routing_table_default}} : ${OCF_RESKEY_routing_table_role=${OCF_RESKEY_routing_table_role_default}} : ${OCF_RESKEY_interface=${OCF_RESKEY_interface_default}} : ${OCF_RESKEY_iflabel=${OCF_RESKEY_iflabel_default}} : ${OCF_RESKEY_monapi=${OCF_RESKEY_monapi_default}} : ${OCF_RESKEY_lookup_type=${OCF_RESKEY_lookup_type_default}} : ${OCF_RESKEY_curl_retries=${OCF_RESKEY_curl_retries_default}} : ${OCF_RESKEY_curl_sleep=${OCF_RESKEY_curl_sleep_default}} ####################################################################### USAGE="usage: $0 {start|stop|status|meta-data}"; ############################################################################### ############################################################################### # # Functions # ############################################################################### metadata() { cat < 1.0 Resource Agent to move IP addresses within a VPC of the Amazon Webservices EC2 by changing an entry in an specific routing table Credentials needs to be setup by running "aws configure", or by using AWS Policies. See https://aws.amazon.com/cli/ for more information about awscli. Move IP within a VPC of the AWS EC2 Path to command line tools for AWS Path to AWS CLI tools Authentication type "key" for AccessKey and SecretAccessKey set via "aws configure", or "role" to use AWS Policies. Authentication type Valid AWS CLI profile name (see ~/.aws/config and 'aws configure') profile name Valid AWS region name (e.g., 'us-west-2') region name VPC private IP address VPC private IP Deprecated IP address param. Use the ip param instead. Deprecated VPC private IP Address Name of the routing table(s), where the route for the IP address should be changed. If declaring multiple routing tables they should be separated by comma. Example: rtb-XXXXXXXX,rtb-YYYYYYYYY routing table name(s) Role to use to query/update the route table route table query/update role Name of the network interface, i.e. eth0 network interface name You can specify an additional label for your IP address here. This label is appended to your interface name. The kernel allows alphanumeric labels up to a maximum length of 15 characters including the interface name and colon (e.g. eth0:foobar1234) Interface label Enable enhanced monitoring using AWS API calls to check route table entry Enhanced Monitoring Name of resource type to lookup in route table. "InstanceId" : EC2 instance ID. (default) "NetworkInterfaceId" : ENI ID. (useful in shared VPC setups). lookup type for route table resource curl retries before failing curl retries curl sleep between tries curl sleep END } execute_cmd_as_role(){ cmd=$1 role=$2 output="$($AWSCLI_CMD sts assume-role --role-arn $role --role-session-name AWSCLI-RouteTableUpdate --output=text)" export AWS_ACCESS_KEY_ID="$(echo $output | awk -F" " '$4=="CREDENTIALS" {print $5}')" export AWS_SECRET_ACCESS_KEY="$(echo $output | awk -F" " '$4=="CREDENTIALS" {print $7}')" export AWS_SESSION_TOKEN="$(echo $output | awk -F" " '$4=="CREDENTIALS" {print $8}')" #Execute command ocf_log debug "Assumed Role ${role}" ocf_log debug "$($OCF_RESKEY_awscli sts get-caller-identity)" ocf_log debug "executing command: $cmd" response="$($cmd)" unset output AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN echo $response } ec2ip_set_address_param_compat(){ # Include backward compatibility for the deprecated address parameter if [ -z "$OCF_RESKEY_ip" ] && [ -n "$OCF_RESKEY_address" ]; then OCF_RESKEY_ip="$OCF_RESKEY_address" fi } ec2ip_validate() { for cmd in "$OCF_RESKEY_awscli" ip curl; do check_binary "$cmd" done if [ "x${OCF_RESKEY_auth_type}" = "xkey" ] && [ -z "$OCF_RESKEY_profile" ]; then ocf_exit_reason "profile parameter not set" return $OCF_ERR_CONFIGURED fi if [ -n "$OCF_RESKEY_iflabel" ]; then label=${OCF_RESKEY_interface}:${OFC_RESKEY_iflabel} if [ ${#label} -gt 15 ]; then ocf_exit_reason "Interface label [$label] exceeds maximum character limit of 15" exit $OCF_ERR_CONFIGURED fi fi - TOKEN=$(curl_retry "$OCF_RESKEY_curl_retries" "$OCF_RESKEY_curl_sleep" "--show-error -sX PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 21600'" "http://169.254.169.254/latest/api/token") + TOKEN=$(get_token) [ $? -ne 0 ] && exit $OCF_ERR_GENERIC EC2_INSTANCE_ID=$(curl_retry "$OCF_RESKEY_curl_retries" "$OCF_RESKEY_curl_sleep" "--show-error -s -H 'X-aws-ec2-metadata-token: $TOKEN'" "http://169.254.169.254/latest/meta-data/instance-id") [ $? -ne 0 ] && exit $OCF_ERR_GENERIC if [ -z "${EC2_INSTANCE_ID}" ]; then ocf_exit_reason "Instance ID not found. Is this a EC2 instance?" return $OCF_ERR_GENERIC fi return $OCF_SUCCESS } ec2ip_monitor() { MON_RES="" if [ "${OCF_RESKEY_lookup_type}" = "NetworkInterfaceId" ]; then EC2_ID="$(ec2ip_get_instance_eni)" RESOURCE_TYPE="interface" else EC2_ID="$EC2_INSTANCE_ID" RESOURCE_TYPE="instance" fi if ocf_is_true ${OCF_RESKEY_monapi} || [ "$__OCF_ACTION" = "start" ] || ocf_is_probe; then for rtb in $(echo $OCF_RESKEY_routing_table | sed -e 's/,/ /g'); do ocf_log info "monitor: check routing table (API call) - $rtb" if [ -z "${OCF_RESKEY_routing_table_role}" ]; then cmd="$AWSCLI_CMD --output text ec2 describe-route-tables --route-table-ids $rtb --query RouteTables[*].Routes[?DestinationCidrBlock=='$OCF_RESKEY_ip/32'].$OCF_RESKEY_lookup_type" ocf_log debug "executing command: $cmd" ROUTE_TO_INSTANCE="$($cmd)" else cmd="$OCF_RESKEY_awscli $region_opt --output text ec2 describe-route-tables --route-table-ids $rtb --query RouteTables[*].Routes[?DestinationCidrBlock=='$OCF_RESKEY_ip/32'].$OCF_RESKEY_lookup_type" ROUTE_TO_INSTANCE="$(execute_cmd_as_role "$cmd" $OCF_RESKEY_routing_table_role)" fi ocf_log debug "Overlay IP is currently routed to ${ROUTE_TO_INSTANCE}" if [ -z "$ROUTE_TO_INSTANCE" ]; then ROUTE_TO_INSTANCE="" fi if [ "$EC2_ID" != "$ROUTE_TO_INSTANCE" ]; then ocf_log warn "not routed to this $RESOURCE_TYPE ($EC2_ID) but to $RESOURCE_TYPE $ROUTE_TO_INSTANCE on $rtb" MON_RES="$MON_RES $rtb" fi sleep 1 done if [ ! -z "$MON_RES" ]; then return $OCF_NOT_RUNNING fi else ocf_log debug "monitor: Enhanced Monitoring disabled - omitting API call" fi cmd="ip addr show to $OCF_RESKEY_ip up" ocf_log debug "executing command: $cmd" RESULT=$($cmd | grep "$OCF_RESKEY_ip") if [ -z "$RESULT" ]; then if [ "$__OCF_ACTION" = "monitor" ] && ! ocf_is_probe; then level="error" else level="info" fi ocf_log "$level" "IP $OCF_RESKEY_ip not assigned to running interface" return $OCF_NOT_RUNNING fi ocf_log debug "route in VPC and address assigned" return $OCF_SUCCESS } ec2ip_drop() { cmd="ip addr delete ${OCF_RESKEY_ip}/32 dev $OCF_RESKEY_interface" ocf_log debug "executing command: $cmd" output=$($cmd 2>&1) rc=$? if [ "$rc" -gt 0 ]; then if [ "$__OCF_ACTION" = "start" ]; then # expected to fail during start level="debug" else level="warn" fi ocf_log "$level" "command failed, rc $rc" ocf_log "$level" "output/error: $output" return $OCF_ERR_GENERIC else ocf_log debug "output/error: $output" fi # delete remaining route-entries if any ip route show to exact ${OCF_RESKEY_ip}/32 dev $OCF_RESKEY_interface | xargs -r ip route delete ip route show table local to exact ${OCF_RESKEY_ip}/32 dev $OCF_RESKEY_interface | xargs -r ip route delete return $OCF_SUCCESS } ec2ip_get_instance_eni() { MAC_FILE="/sys/class/net/${OCF_RESKEY_interface}/address" if [ -f $MAC_FILE ]; then cmd="cat ${MAC_FILE}" else cmd="ip -br link show dev ${OCF_RESKEY_interface} | tr -s ' ' | cut -d' ' -f3" fi ocf_log debug "executing command: $cmd" MAC_ADDR="$(eval $cmd)" rc=$? if [ $rc != 0 ]; then ocf_log warn "command failed, rc: $rc" return $OCF_ERR_GENERIC fi ocf_log debug "MAC address associated with interface ${OCF_RESKEY_interface}: ${MAC_ADDR}" cmd="curl_retry \"$OCF_RESKEY_curl_retries\" \"$OCF_RESKEY_curl_sleep\" \"--show-error -s -H 'X-aws-ec2-metadata-token: $TOKEN'\" \"http://169.254.169.254/latest/meta-data/network/interfaces/macs/${MAC_ADDR}/interface-id\"" EC2_NETWORK_INTERFACE_ID="$(eval $cmd)" [ $? -ne 0 ] && exit $OCF_ERR_GENERIC ocf_log debug "network interface id associated MAC address ${MAC_ADDR}: ${EC2_NETWORK_INTERFACE_ID}" echo $EC2_NETWORK_INTERFACE_ID } ec2ip_get_and_configure() { EC2_NETWORK_INTERFACE_ID="$(ec2ip_get_instance_eni)" for rtb in $(echo $OCF_RESKEY_routing_table | sed -e 's/,/ /g'); do if [ -z "${OCF_RESKEY_routing_table_role}" ]; then cmd="$AWSCLI_CMD --output text ec2 replace-route --route-table-id $rtb --destination-cidr-block ${OCF_RESKEY_ip}/32 --network-interface-id $EC2_NETWORK_INTERFACE_ID" ocf_log debug "executing command: $cmd" $cmd else cmd="$OCF_RESKEY_awscli $region_opt --output text ec2 replace-route --route-table-id $rtb --destination-cidr-block ${OCF_RESKEY_ip}/32 --network-interface-id $EC2_NETWORK_INTERFACE_ID" update_response="$(execute_cmd_as_role "$cmd" $OCF_RESKEY_routing_table_role)" fi rc=$? if [ "$rc" != 0 ]; then ocf_log warn "command failed, rc: $rc" return $OCF_ERR_GENERIC fi sleep 1 done # Reconfigure the local ip address ec2ip_drop extra_opts="" if [ -n "$OCF_RESKEY_iflabel" ]; then extra_opts="$extra_opts label $OCF_RESKEY_interface:$OCF_RESKEY_iflabel" fi cmd="ip addr add ${OCF_RESKEY_ip}/32 dev $OCF_RESKEY_interface $extra_opts" ocf_log debug "executing command: $cmd" $cmd rc=$? if [ $rc != 0 ]; then ocf_log warn "command failed, rc: $rc" return $OCF_ERR_GENERIC fi return $OCF_SUCCESS } ec2ip_stop() { ocf_log info "EC2: Bringing down IP address $OCF_RESKEY_ip" ec2ip_monitor if [ $? = $OCF_NOT_RUNNING ]; then ocf_log info "EC2: Address $OCF_RESKEY_ip already down" return $OCF_SUCCESS fi ec2ip_drop if [ $? != $OCF_SUCCESS ]; then return $OCF_ERR_GENERIC fi ec2ip_monitor if [ $? != $OCF_NOT_RUNNING ]; then ocf_log error "EC2: Couldn't bring down IP address $OCF_RESKEY_ip on interface $OCF_RESKEY_interface." return $OCF_ERR_GENERIC fi ocf_log info "EC2: Successfully brought down $OCF_RESKEY_ip" return $OCF_SUCCESS } ec2ip_start() { ocf_log info "EC2: Moving IP address $OCF_RESKEY_ip to this host by adjusting routing table $OCF_RESKEY_routing_table" ec2ip_monitor if [ $? = $OCF_SUCCESS ]; then ocf_log info "EC2: $OCF_RESKEY_ip already started" return $OCF_SUCCESS fi ocf_log info "EC2: Adjusting routing table and locally configuring IP address" ec2ip_get_and_configure rc=$? if [ $rc != $OCF_SUCCESS ]; then ocf_log error "Received $rc from 'aws'" return $OCF_ERR_GENERIC fi ec2ip_monitor if [ $? != $OCF_SUCCESS ]; then ocf_log error "EC2: IP address couldn't be configured on this host (IP: $OCF_RESKEY_ip, Interface: $OCF_RESKEY_interface)" return $OCF_ERR_GENERIC fi return $OCF_SUCCESS } ############################################################################### # # MAIN # ############################################################################### case $__OCF_ACTION in meta-data) metadata exit $OCF_SUCCESS ;; usage|help) echo $USAGE exit $OCF_SUCCESS ;; esac if ! ocf_is_root; then ocf_log err "You must be root for $__OCF_ACTION operation." exit $OCF_ERR_PERM fi AWSCLI_CMD="${OCF_RESKEY_awscli}" if [ "x${OCF_RESKEY_auth_type}" = "xkey" ]; then AWSCLI_CMD="$AWSCLI_CMD --profile ${OCF_RESKEY_profile}" elif [ "x${OCF_RESKEY_auth_type}" = "xrole" ]; then if [ -z "${OCF_RESKEY_region}" ]; then ocf_exit_reason "region needs to be set when using role-based authentication" exit $OCF_ERR_CONFIGURED fi else ocf_exit_reason "Incorrect auth_type: ${OCF_RESKEY_auth_type}" exit $OCF_ERR_CONFIGURED fi if [ -n "${OCF_RESKEY_region}" ]; then AWSCLI_CMD="$AWSCLI_CMD --region ${OCF_RESKEY_region}" fi ec2ip_set_address_param_compat ec2ip_validate case $__OCF_ACTION in start) ec2ip_start;; stop) ec2ip_stop;; monitor) ec2ip_monitor;; validate-all) exit $?;; *) echo $USAGE exit $OCF_ERR_UNIMPLEMENTED ;; esac diff --git a/heartbeat/awsvip b/heartbeat/awsvip index f2b238a0f..ca19ac086 100755 --- a/heartbeat/awsvip +++ b/heartbeat/awsvip @@ -1,311 +1,311 @@ #!/bin/sh # # # Manage Secondary Private IP with Pacemaker # # # Copyright 2016-2018 guessi # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # # # Prerequisites: # # - preconfigured AWS CLI running environment (AccessKey, SecretAccessKey, etc.) or # (AWSRole) Setup up relevant AWS Policies to allow agent related functions to be executed. # - a reserved secondary private IP address for EC2 instances high availablity # - IAM user role with the following permissions: # * DescribeInstances # * AssignPrivateIpAddresses # * UnassignPrivateIpAddresses # ####################################################################### # Initialization: : ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat} . ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs ####################################################################### # # Defaults # OCF_RESKEY_awscli_default="/usr/bin/aws" OCF_RESKEY_auth_type_default="key" OCF_RESKEY_profile_default="default" OCF_RESKEY_region_default="" OCF_RESKEY_api_delay_default="3" OCF_RESKEY_curl_retries_default="3" OCF_RESKEY_curl_sleep_default="1" : ${OCF_RESKEY_awscli=${OCF_RESKEY_awscli_default}} : ${OCF_RESKEY_auth_type=${OCF_RESKEY_auth_type_default}} : ${OCF_RESKEY_profile=${OCF_RESKEY_profile_default}} : ${OCF_RESKEY_region=${OCF_RESKEY_region_default}} : ${OCF_RESKEY_api_delay=${OCF_RESKEY_api_delay_default}} : ${OCF_RESKEY_curl_retries=${OCF_RESKEY_curl_retries_default}} : ${OCF_RESKEY_curl_sleep=${OCF_RESKEY_curl_sleep_default}} meta_data() { cat < 1.0 Resource Agent for Amazon AWS Secondary Private IP Addresses. It manages AWS Secondary Private IP Addresses with awscli. Credentials needs to be setup by running "aws configure", or by using AWS Policies. See https://aws.amazon.com/cli/ for more information about awscli. Amazon AWS Secondary Private IP Address Resource Agent command line tools for aws services aws cli tools Authentication type "key" for AccessKey and SecretAccessKey set via "aws configure", or "role" to use AWS Policies. Authentication type Valid AWS CLI profile name (see ~/.aws/config and 'aws configure') profile name reserved secondary private ip for ec2 instance reserved secondary private ip for ec2 instance Region for AWS resource (required for role-based authentication) Region a short delay between API calls, to avoid sending API too quick a short delay between API calls curl retries before failing curl retries curl sleep between tries curl sleep END } ####################################################################### awsvip_usage() { cat </dev/null; then if [ "x$HA_debug" = "x0" -a "x$loglevel" = xdebug ] ; then return 0 elif [ "$ignore_stderr" = "true" ]; then # something already printed this error to stderr, so ignore return 0 fi if [ "$HA_LOGTAG" ]; then echo "$HA_LOGTAG: $*" else echo "$*" fi >&2 return 0 fi set_logtag if [ "x${HA_LOGD}" = "xyes" ] ; then ha_logger -t "${HA_LOGTAG}" "$@" if [ "$?" -eq "0" ] ; then return 0 fi fi if [ -n "$HA_LOGFACILITY" ] then : logging through syslog # loglevel is unknown, use 'notice' for now loglevel=notice case "${*}" in *ERROR*) loglevel=err;; *WARN*) loglevel=warning;; *INFO*|info) loglevel=info;; esac logger -t "$HA_LOGTAG" -p ${HA_LOGFACILITY}.${loglevel} "${*}" fi if [ -n "$HA_LOGFILE" ] then : appending to $HA_LOGFILE echo `hadate`" $HA_LOGTAG: ${*}" >> $HA_LOGFILE fi if [ -z "$HA_LOGFACILITY" -a -z "$HA_LOGFILE" ] && ! [ "$ignore_stderr" = "true" ] then : appending to stderr echo `hadate`"${*}" >&2 fi if [ -n "$HA_DEBUGLOG" ] then : appending to $HA_DEBUGLOG if [ "$HA_LOGFILE"x != "$HA_DEBUGLOG"x ]; then echo "$HA_LOGTAG: "`hadate`"${*}" >> $HA_DEBUGLOG fi fi } ha_log() { __ha_log "$@" } ha_debug() { if [ "x${HA_debug}" = "x0" ] || [ -z "${HA_debug}" ] ; then return 0 fi if tty >/dev/null; then if [ "$HA_LOGTAG" ]; then echo "$HA_LOGTAG: $*" else echo "$*" fi >&2 return 0 fi set_logtag if [ "x${HA_LOGD}" = "xyes" ] ; then ha_logger -t "${HA_LOGTAG}" -D "ha-debug" "$@" if [ "$?" -eq "0" ] ; then return 0 fi fi [ none = "$HA_LOGFACILITY" ] && HA_LOGFACILITY="" if [ -n "$HA_LOGFACILITY" ] then : logging through syslog logger -t "$HA_LOGTAG" -p "${HA_LOGFACILITY}.debug" "${*}" fi if [ -n "$HA_DEBUGLOG" ] then : appending to $HA_DEBUGLOG echo "$HA_LOGTAG: "`hadate`"${*}" >> $HA_DEBUGLOG fi if [ -z "$HA_LOGFACILITY" -a -z "$HA_DEBUGLOG" ] then : appending to stderr echo "$HA_LOGTAG: `hadate`${*}: ${HA_LOGFACILITY}" >&2 fi } ha_parameter() { local VALUE VALUE=`sed -e 's%[ ][ ]*% %' -e 's%^ %%' -e 's%#.*%%' $HA_CF | grep -i "^$1 " | sed 's%[^ ]* %%'` if [ "X$VALUE" = X ] then case $1 in keepalive) VALUE=2;; deadtime) ka=`ha_parameter keepalive` VALUE=`expr $ka '*' 2 '+' 1`;; esac fi echo $VALUE } ocf_log() { # TODO: Revisit and implement internally. if [ $# -lt 2 ] then ocf_log err "Not enough arguments [$#] to ocf_log." fi __OCF_PRIO="$1" shift __OCF_MSG="$*" case "${__OCF_PRIO}" in crit) __OCF_PRIO="CRIT";; err) __OCF_PRIO="ERROR";; warn) __OCF_PRIO="WARNING";; info) __OCF_PRIO="INFO";; debug)__OCF_PRIO="DEBUG";; *) __OCF_PRIO=`echo ${__OCF_PRIO}| tr '[a-z]' '[A-Z]'`;; esac if [ "${__OCF_PRIO}" = "DEBUG" ]; then ha_debug "${__OCF_PRIO}: $__OCF_MSG" else ha_log "${__OCF_PRIO}: $__OCF_MSG" fi } # # ocf_exit_reason: print exit error string to stderr # Usage: Allows the OCF script to provide a string # describing why the exit code was returned. # Arguments: reason - required, The string that represents why the error # occured. # ocf_exit_reason() { local cookie="$OCF_EXIT_REASON_PREFIX" local fmt local msg # No argument is likely not intentional. # Just one argument implies a printf format string of just "%s". # "Least surprise" in case some interpolated string from variable # expansion or other contains a percent sign. # More than one argument: first argument is going to be the format string. case $# in 0) ocf_log err "Not enough arguments to ocf_log_exit_msg." ;; 1) fmt="%s" ;; *) fmt=$1 shift case $fmt in *%*) : ;; # ok, does look like a format string *) ocf_log warn "Does not look like format string: [$fmt]" ;; esac ;; esac if [ -z "$cookie" ]; then # use a default prefix cookie="ocf-exit-reason:" fi msg=$(printf "${fmt}" "$@") printf >&2 "%s%s\n" "$cookie" "$msg" __ha_log --ignore-stderr "ERROR: $msg" } # # ocf_deprecated: Log a deprecation warning # Usage: ocf_deprecated [param-name] # Arguments: param-name optional, name of a boolean resource # parameter that can be used to suppress # the warning (default # "ignore_deprecation") ocf_deprecated() { local param param=${1:-ignore_deprecation} # don't use ${!param} here, it's a bashism if ! ocf_is_true $(eval echo \$OCF_RESKEY_$param); then ocf_log warn "This resource agent is deprecated" \ "and may be removed in a future release." \ "See the man page for details." \ "To suppress this warning, set the \"${param}\"" \ "resource parameter to true." fi } # # Ocf_run: Run a script, and log its output. # Usage: ocf_run [-q] [-info|-warn|-err] # -q: don't log the output of the command if it succeeds # -info|-warn|-err: log the output of the command at given # severity if it fails (defaults to err) # ocf_run() { local rc local output local verbose=1 local loglevel=err local var for var in 1 2 do case "$1" in "-q") verbose="" shift 1;; "-info"|"-warn"|"-err"|"-debug") loglevel=${1#-} shift 1;; *) ;; esac done output=`"$@" 2>&1` rc=$? [ -n "$output" ] && output="$(echo "$output" | tr -s ' \t\r\n' ' ')" if [ $rc -eq 0 ]; then if [ "$verbose" -a ! -z "$output" ]; then ocf_log info "$output" fi else if [ ! -z "$output" ]; then ocf_log $loglevel "$output" else ocf_log $loglevel "command failed: $*" fi fi return $rc } ocf_pidfile_status() { local pid pidfile="$1" if [ ! -e "$pidfile" ]; then # Not exists return 2 fi pid=$(cat "$pidfile") kill -0 "$pid" > /dev/null 2>&1 if [ $? = 0 ]; then return 0 fi # Stale return 1 } # mkdir(1) based locking # first the directory is created with the name given as $1 # then a file named "pid" is created within that directory with # the process PID # stale locks are handled carefully, the inode of a directory # needs to match before and after test if the process is running # empty directories are also handled appropriately # we relax (sleep) occasionally to allow for other processes to # finish managing the lock in case they are in the middle of the # business relax() { sleep 0.5; } ocf_get_stale_pid() { local piddir pid dir_inode piddir="$1" [ -z "$piddir" ] && return 2 dir_inode="`ls -di $piddir 2>/dev/null`" [ -z "$dir_inode" ] && return 1 pid=`cat $piddir/pid 2>/dev/null` if [ -z "$pid" ]; then # empty directory? relax if [ "$dir_inode" = "`ls -di $piddir 2>/dev/null`" ]; then echo $dir_inode else return 1 fi elif kill -0 $pid >/dev/null 2>&1; then return 1 elif relax && [ -e "$piddir/pid" ] && [ "$dir_inode" = "`ls -di $piddir 2>/dev/null`" ]; then echo $pid else return 1 fi } # There is a race when the following two functions to manage the # lock file (mk and rm) are invoked in parallel by different # instances. It is up to the caller to reduce probability of that # taking place (see ocf_take_lock() below). ocf_mk_pid() { mkdir $1 2>/dev/null && echo $$ > $1/pid } ocf_rm_pid() { rm -f $1/pid rmdir $1 2>/dev/null } # Testing and subsequently removing a stale lock (containing the # process pid) is inherently difficult to do in such a way as to # prevent a race between creating a pid file and removing it and # its directory. We reduce the probability of that happening by # checking if the stale lock persists over a random period of # time. ocf_take_lock() { local lockdir=$1 local rnd local stale_pid # we don't want it too short, so strip leading zeros rnd=$(ocf_maybe_random | sed 's/^0*//') stale_pid=`ocf_get_stale_pid $lockdir` if [ -n "$stale_pid" ]; then sleep 0.$rnd # remove "stale pid" only if it persists [ "$stale_pid" = "`ocf_get_stale_pid $lockdir`" ] && ocf_rm_pid $lockdir fi while ! ocf_mk_pid $lockdir; do ocf_log info "Sleeping until $lockdir is released..." sleep 0.$rnd done } ocf_release_lock_on_exit() { trap "ocf_rm_pid $1" EXIT } # returns true if the CRM is currently running a probe. A probe is # defined as a monitor operation with a monitoring interval of zero. ocf_is_probe() { [ "$__OCF_ACTION" = "monitor" -a "$OCF_RESKEY_CRM_meta_interval" = 0 ] } # returns true if the resource is configured as a clone. This is # defined as a resource where the clone-max meta attribute is present. ocf_is_clone() { [ ! -z "${OCF_RESKEY_CRM_meta_clone_max}" ] } # returns true if the resource is configured as a multistate # (master/slave) resource. This is defined as a resource where the # master-max meta attribute is present, and set to greater than zero. ocf_is_ms() { [ "${OCF_RESKEY_CRM_meta_promotable}" = "true" ] || { [ ! -z "${OCF_RESKEY_CRM_meta_master_max}" ] && [ "${OCF_RESKEY_CRM_meta_master_max}" -gt 0 ]; } } # version check functions # allow . and - to delimit version numbers # max version number is 999 # ocf_is_ver() { echo $1 | grep '^[0-9][0-9.-]*[0-9A-Za-z.\+-]*$' >/dev/null 2>&1 } # usage: ocf_version_cmp VER1 VER2 # version strings can contain digits, dots, and dashes # must start and end with a digit # returns: # 0: VER1 smaller (older) than VER2 # 1: versions equal # 2: VER1 greater (newer) than VER2 # 3: bad format ocf_version_cmp() { ocf_is_ver "$1" || return 3 ocf_is_ver "$2" || return 3 local v1=$1 local v2=$2 sort_version="sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n" older=$( (echo "$v1"; echo "$v2") | $sort_version | head -1 ) if [ "$v1" = "$v2" ]; then return 1 elif [ "$v1" = "$older" ]; then return 0 else return 2 # -1 would look funny in shell ;-) fi } ocf_local_nodename() { # use crm_node -n for pacemaker > 1.1.8 which pacemakerd > /dev/null 2>&1 if [ $? -eq 0 ]; then local version=$(pacemakerd -$ | awk '/^Pacemaker /{ print $2 }') version=$(echo $version | awk -F- '{ print $1 }') ocf_version_cmp "$version" "1.1.8" if [ $? -eq 2 ]; then which crm_node > /dev/null 2>&1 if [ $? -eq 0 ]; then crm_node -n return fi fi fi # otherwise use uname -n uname -n } # usage: dirname DIR dirname() { local a local b [ $# = 1 ] || return 1 a="$1" while [ 1 ]; do b="${a%/}" [ "$a" = "$b" ] && break a="$b" done b=${a%/*} [ -z "$b" -o "$a" = "$b" ] && b="." echo "$b" return 0 } # usage: systemd_is_running # returns: # 0 PID 1 is systemd # 1 otherwise systemd_is_running() { [ "$(cat /proc/1/comm 2>/dev/null)" = "systemd" ] } # usage: systemd_drop_in systemd_drop_in() { local conf_file if [ $# -ne 3 ]; then ocf_log err "Incorrect number of arguments [$#] for systemd_drop_in." fi systemdrundir="/run/systemd/system/resource-agents-deps.target.d" mkdir -p "$systemdrundir" conf_file="$systemdrundir/$1.conf" conf_line="$2=$3" if ! { [ -f "$conf_file" ] && grep -q "^$conf_line$" "$conf_file" ; } ; then cat > "$conf_file" <<-EOF [Unit] $conf_line EOF # The information is accessible through systemd API and systemd would # complain about improper permissions. chmod o+r "$conf_file" systemctl daemon-reload fi } # usage: curl_retry RETRIES SLEEP ARGS URL # # Use --show-error in ARGS to log HTTP error code # # returns: # 0 success # exit: # 1 fail curl_retry() { local retries=$1 sleep=$2 opts=$3 url=$4 local tries=$(($retries + 1)) local args="--fail $opts $url" local result rc for try in $(seq $tries); do ocf_log debug "curl $args try $try of $tries" result=$(echo "$args" | xargs curl 2>&1) rc=$? ocf_log debug "result: $result" [ $rc -eq 0 ] && break sleep $sleep done if [ $rc -ne 0 ]; then ocf_exit_reason "curl $args failed $tries tries" exit $OCF_ERR_GENERIC fi echo "$result" return $rc } # move process to root cgroup if realtime scheduling is enabled ocf_move_to_root_cgroup_if_rt_enabled() { if [ -e "/sys/fs/cgroup/cpu/cpu.rt_runtime_us" ]; then echo $$ >> /sys/fs/cgroup/cpu/tasks if [ "$?" -ne "0" ]; then ocf_log warn "Unable to move PID $$ to the root cgroup" fi fi } # usage: crm_mon_no_validation args... # run crm_mon without any cib schema validation # This is useful when an agent runs in a bundle to avoid potential # schema validation errors when host and bundle are not perfectly aligned # To be used, your shell must support on process substitution (e.g. bash) # returns: # crm_mon_no_validation() { # The subshell prevents parsing error with incompatible shells ocf_version_cmp "$OCF_RESKEY_crm_feature_set" "3.19.7" if [ $res -eq 2 ] || [ $res -eq 1 ]; then "$SHELL" -c "CIB_file=<(${HA_SBIN_DIR}/cibadmin -Q \ ${HA_SBIN_DIR}/crm_mon \$*" -- $* else "$SHELL" -c "CIB_file=<(${HA_SBIN_DIR}/cibadmin -Q | sed 's/validate-with=\"[^\"]*\"/validate-with=\"none\"/') \ ${HA_SBIN_DIR}/crm_mon \$*" -- $* fi } # # pseudo_resource status tracking function... # # This allows pseudo resources to give correct status information. As we add # resource monitoring, and better resource tracking in general, this will # become essential. # # These scripts work because ${HA_RSCTMP} is cleaned on node reboot. # # We create "resource-string" tracking files under ${HA_RSCTMP} in a # very simple way: # # Existence of "${HA_RSCTMP}/resource-string" means that we consider # the resource named by "resource-string" to be running. # # Note that "resource-string" needs to be unique. Using the resource type # plus the resource instance arguments to make up the resource string # is probably sufficient... # # usage: ha_pseudo_resource resource-string op [tracking_file] # where op is {start|stop|monitor|status|restart|reload|print} # print is a special op which just prints the tracking file location # user can override our choice of the tracking file location by # specifying it as the third arg # Note that all operations are silent... # ha_pseudo_resource() { local ha_resource_tracking_file="${3:-${HA_RSCTMP}/$1}" case $2 in start|restart|reload) touch "$ha_resource_tracking_file";; stop) rm -f "$ha_resource_tracking_file";; status|monitor) if [ -f "$ha_resource_tracking_file" ] then return 0 else case $2 in status) return 3;; *) return 7;; esac fi;; print) echo "$ha_resource_tracking_file";; *) return 3;; esac } # usage: rmtempdir TMPDIR rmtempdir() { [ $# = 1 ] || return 1 if [ -e "$1" ]; then rmdir "$1" || return 1 fi return 0 } # usage: maketempfile [-d] maketempfile() { if [ $# = 1 -a "$1" = "-d" ]; then mktemp -d return 0 elif [ $# != 0 ]; then return 1 fi mktemp return 0 } # usage: rmtempfile TMPFILE rmtempfile () { [ $# = 1 ] || return 1 if [ -e "$1" ]; then rm "$1" || return 1 fi return 0 } # echo the first lower supported check level # pass set of levels supported by the agent # (in increasing order, 0 is optional) ocf_check_level() { local lvl prev lvl=0 prev=0 if ocf_is_decimal "$OCF_CHECK_LEVEL"; then # the level list should be very short for lvl; do if [ "$lvl" -eq "$OCF_CHECK_LEVEL" ]; then break elif [ "$lvl" -gt "$OCF_CHECK_LEVEL" ]; then lvl=$prev # the previous one break fi prev=$lvl done fi echo $lvl } # usage: ocf_stop_processes SIGNALS WAIT_TIME PIDS # # we send signals (use quotes for more than one!) in the order # given; if one or more processes are still running we try KILL; # the wait_time is the _total_ time we'll spend in this function # this time may be slightly exceeded if the processes won't leave # # returns: # 0: all processes left # 1: some processes still running # # example: # # ocf_stop_processes TERM 5 $pids # ocf_stop_processes() { local signals="$1" local wait_time="$(($2/`echo $signals|wc -w`))" shift 2 local pids="$*" local sig i test -z "$pids" && return 0 for sig in $signals KILL; do kill -s $sig $pids 2>/dev/null # try to leave early, and yet leave processes time to exit sleep 0.2 for i in `seq $wait_time`; do kill -s 0 $pids 2>/dev/null || return 0 sleep 1 done done return 1 } # # create a given status directory # if the directory path doesn't start with $HA_VARRUN, then # we return with error (most of the calls would be with the user # supplied configuration, hence we need to do necessary # protection) # used mostly for PID files # # usage: ocf_mkstatedir owner permissions path # # owner: user.group # permissions: permissions # path: directory path # # example: # ocf_mkstatedir named 755 `dirname $pidfile` # ocf_mkstatedir() { local owner local perms local path owner=$1 perms=$2 path=$3 test -d $path && return 0 [ $(id -u) = 0 ] || return 1 case $path in ${HA_VARRUN%/}/*) : this path is ok ;; *) ocf_log err "cannot create $path (does not start with $HA_VARRUN)" return 1 ;; esac mkdir -p $path && chown $owner $path && chmod $perms $path } # # create a unique status directory in $HA_VARRUN # used mostly for PID files # the directory is by default set to # $HA_VARRUN/$OCF_RESOURCE_INSTANCE # the directory name is printed to stdout # # usage: ocf_unique_rundir owner permissions name # # owner: user.group (default: "root") # permissions: permissions (default: "755") # name: some unique string (default: "$OCF_RESOURCE_INSTANCE") # # to use the default either don't set the parameter or set it to # empty string ("") # example: # # STATEDIR=`ocf_unique_rundir named "" myownstatedir` # ocf_unique_rundir() { local path local owner local perms local name owner=${1:-"root"} perms=${2:-"755"} name=${3:-"$OCF_RESOURCE_INSTANCE"} path=$HA_VARRUN/$name if [ ! -d $path ]; then [ $(id -u) = 0 ] || return 1 mkdir -p $path && chown $owner $path && chmod $perms $path || return 1 fi echo $path } # # RA tracing may be turned on by setting OCF_TRACE_RA # the trace output will be saved to OCF_TRACE_FILE, if set, or # by default to # $HA_VARLIB/trace_ra//.. # e.g. $HA_VARLIB/trace_ra/oracle/db.start.2012-11-27.08:37:08 # # OCF_TRACE_FILE: # - FD (small integer [3-9]) in that case it is up to the callers # to capture output; the FD _must_ be open for writing # - absolute path # # NB: FD 9 may be used for tracing with bash >= v4 in case # OCF_TRACE_FILE is set to a path. # ocf_bash_has_xtracefd() { [ -n "$BASH_VERSION" ] && [ ${BASH_VERSINFO[0]} -ge 4 ] } # for backwards compatibility ocf_is_bash4() { ocf_bash_has_xtracefd } ocf_trace_redirect_to_file() { local dest=$1 if ocf_bash_has_xtracefd; then exec 9>$dest BASH_XTRACEFD=9 else exec 2>$dest fi } ocf_trace_redirect_to_fd() { local fd=$1 if ocf_bash_has_xtracefd; then BASH_XTRACEFD=$fd else exec 2>&$fd fi } __ocf_test_trc_dest() { local dest=$1 if ! touch $dest; then ocf_log warn "$dest not writable, trace not going to happen" __OCF_TRC_DEST="" __OCF_TRC_MANAGE="" return 1 fi return 0 } ocf_default_trace_dest() { tty >/dev/null && return if [ -n "$OCF_RESOURCE_TYPE" -a \ -n "$OCF_RESOURCE_INSTANCE" -a -n "$__OCF_ACTION" ]; then local ts=`date +%F.%T` __OCF_TRC_DEST=${OCF_RESKEY_trace_dir}/${OCF_RESOURCE_TYPE}/${OCF_RESOURCE_INSTANCE}.${__OCF_ACTION}.$ts __OCF_TRC_MANAGE="1" fi } ocf_start_trace() { export __OCF_TRC_DEST="" __OCF_TRC_MANAGE="" case "$OCF_TRACE_FILE" in [3-9]) ocf_trace_redirect_to_fd "$OCF_TRACE_FILE" ;; /*/*) __OCF_TRC_DEST=$OCF_TRACE_FILE ;; "") ocf_default_trace_dest ;; *) ocf_log warn "OCF_TRACE_FILE must be set to either FD (open for writing) or absolute file path" ocf_default_trace_dest ;; esac if [ "$__OCF_TRC_DEST" ]; then mkdir -p `dirname $__OCF_TRC_DEST` __ocf_test_trc_dest $__OCF_TRC_DEST || return ocf_trace_redirect_to_file "$__OCF_TRC_DEST" fi if [ -n "$BASH_VERSION" ]; then PS4='+ `date +"%T"`: ${FUNCNAME[0]:+${FUNCNAME[0]}:}${LINENO}: ' fi set -x env=$( echo; printenv | sort ) } ocf_stop_trace() { set +x } # Helper functions to map from nodename/bundle-name and physical hostname # list_index_for_word "node0 node1 node2 node3 node4 node5" node4 --> 5 # list_word_at_index "NA host1 host2 host3 host4 host5" 3 --> host2 # list_index_for_word "node1 node2 node3 node4 node5" node7 --> "" # list_word_at_index "host1 host2 host3 host4 host5" 8 --> "" # attribute_target node1 --> host1 list_index_for_word() { echo $1 | tr ' ' '\n' | awk -v x="$2" '$0~x {print NR}' } list_word_at_index() { echo $1 | tr ' ' '\n' | awk -v n="$2" 'n == NR' } ocf_attribute_target() { if [ x$1 = x ]; then if [ x$OCF_RESKEY_CRM_meta_container_attribute_target = xhost -a x$OCF_RESKEY_CRM_meta_physical_host != x ]; then echo $OCF_RESKEY_CRM_meta_physical_host else if [ x$OCF_RESKEY_CRM_meta_on_node != x ]; then echo $OCF_RESKEY_CRM_meta_on_node else ocf_local_nodename fi fi return elif [ x"$OCF_RESKEY_CRM_meta_notify_all_uname" != x ]; then index=$(list_index_for_word "$OCF_RESKEY_CRM_meta_notify_all_uname" $1) mapping="" if [ x$index != x ]; then mapping=$(list_word_at_index "$OCF_RESKEY_CRM_meta_notify_all_hosts" $index) fi if [ x$mapping != x -a x$mapping != xNA ]; then echo $mapping return fi fi echo $1 } ocf_promotion_score() { ocf_version_cmp "$OCF_RESKEY_crm_feature_set" "3.10.0" res=$? if [ $res -eq 2 ] || [ $res -eq 1 ] || ! have_binary "crm_master"; then ${HA_SBIN_DIR}/crm_attribute -p ${OCF_RESOURCE_INSTANCE} $@ else ${HA_SBIN_DIR}/crm_master -l reboot $@ fi } __ocf_set_defaults "$@" : ${OCF_TRACE_RA:=$OCF_RESKEY_trace_ra} : ${OCF_RESKEY_trace_dir:="$HA_VARLIB/trace_ra"} ocf_is_true "$OCF_TRACE_RA" && ocf_start_trace # pacemaker sets HA_use_logd, some others use HA_LOGD :/ if ocf_is_true "$HA_use_logd"; then : ${HA_LOGD:=yes} fi + +# File to store the token and timestamp +TOKEN_FILE="/tmp/.imds_token" +TOKEN_LIFETIME=21600 # Token lifetime in seconds (6 hours) +TOKEN_EXPIRY_THRESHOLD=3600 # Renew token if less than 60 minutes (1 hour) remaining + +# Function to fetch a new token +fetch_new_token() { + TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: $TOKEN_LIFETIME") + echo "$TOKEN $(date +%s)" > "$TOKEN_FILE" + echo "$TOKEN" +} + +# Function to retrieve or renew the token +get_token() { + if [[ -f "$TOKEN_FILE" ]]; then + read -r STORED_TOKEN STORED_TIMESTAMP < "$TOKEN_FILE" + CURRENT_TIME=$(date +%s) + ELAPSED_TIME=$((CURRENT_TIME - STORED_TIMESTAMP)) + + if (( ELAPSED_TIME < (TOKEN_LIFETIME - TOKEN_EXPIRY_THRESHOLD) )); then + # Token is still valid + echo "$STORED_TOKEN" + return + fi + fi + # Fetch a new token if not valid + fetch_new_token +} + +