1#!/usr/local/bin/bash
2#
3# OCF resource agent to move an IP address within a VPC in the Aliyun
4# Based on code of Markus Guertler (GitHub AWS-VPC-move-IP)
5# Based on code of Adam Gandelman (GitHub ec2-resource-agents/elasticip)
6#
7
8#######################################################################
9# Initialization:
10: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
11. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs
12
13# Parameter defaults
14
15OCF_RESKEY_address_default=""
16OCF_RESKEY_routing_table_default=""
17OCF_RESKEY_interface_default="eth0"
18OCF_RESKEY_profile_default="default"
19OCF_RESKEY_endpoint_default="vpc.aliyuncs.com"
20OCF_RESKEY_aliyuncli_default="detect"
21
22
23: ${OCF_RESKEY_address=${OCF_RESKEY_address_default}}
24: ${OCF_RESKEY_routing_table=${OCF_RESKEY_routing_table_default}}
25: ${OCF_RESKEY_interface=${OCF_RESKEY_interface_default}}
26: ${OCF_RESKEY_profile=${OCF_RESKEY_profile_default}}
27: ${OCF_RESKEY_endpoint=${OCF_RESKEY_endpoint_default}}
28: ${OCF_RESKEY_aliyuncli=${OCF_RESKEY_aliyuncli_default}}
29
30#######################################################################
31
32# aliyun cli doesnt work without HOME parameter
33export HOME="/root"
34
35USAGE="usage: $0 {start|stop|status|meta-data}";
36
37if [ "${OCF_RESKEY_aliyuncli}" = "detect" ]; then
38	OCF_RESKEY_aliyuncli="$(which aliyuncli 2> /dev/null || which aliyun 2> /dev/null)"
39fi
40
41if [ "${OCF_RESKEY_aliyuncli##*/}" = 'aliyuncli' ]; then
42	OUTPUT="text"
43	EXECUTING='{ print $3 }'
44	IFS_=" "
45	ENDPOINT=""
46elif [ "${OCF_RESKEY_aliyuncli##*/}" = 'aliyun' ]; then
47	OUTPUT="table cols=InstanceId,DestinationCidrBlock rows=RouteTables.RouteTable[].RouteEntrys.RouteEntry[]"
48	EXECUTING='{ gsub (" ", "", $0); print $1 }'
49	IFS_="|"
50	ENDPOINT="--endpoint $OCF_RESKEY_endpoint"
51fi
52###############################################################################
53
54
55###############################################################################
56#
57# Functions
58#
59###############################################################################
60
61request_create_route_entry() {
62	cmd="${OCF_RESKEY_aliyuncli} vpc CreateRouteEntry --RouteTableId $OCF_RESKEY_routing_table --DestinationCidrBlock ${OCF_RESKEY_address}/32 --NextHopId $ECS_INSTANCE_ID --NextHopType Instance ${ENDPOINT}"
63	ocf_log debug "executing command: $cmd"
64	res=$($cmd  2>&1)
65	rc=$?
66	if [ $rc -eq 0 ]
67	then
68		ocf_log debug "result: $res; rc: $rc"
69	else
70		ocf_log err "result: $res; cmd: $cmd; rc: $rc"
71	fi
72	return $rc
73}
74
75request_delete_route_entry() {
76	cmd="${OCF_RESKEY_aliyuncli} vpc DeleteRouteEntry --RouteTableId $OCF_RESKEY_routing_table --DestinationCidrBlock ${OCF_RESKEY_address}/32 --NextHopId $ROUTE_TO_INSTANCE ${ENDPOINT}"
77	ocf_log debug "executing command: $cmd"
78	res=$($cmd)
79	rc=$?
80	if [ $rc -eq 0 ]
81	then
82		ocf_log debug "result: $res; rc: $rc"
83	else
84		ocf_log err "result: $res; cmd: $cmd; rc: $rc"
85	fi
86	return $rc
87}
88
89request_describe_route_tables() {
90	cmd="${OCF_RESKEY_aliyuncli} vpc DescribeRouteTables --RouteTableId $OCF_RESKEY_routing_table --output ${OUTPUT} ${ENDPOINT}"
91	ocf_log debug "executing command: $cmd"
92	res=$($cmd)
93	rc=$?
94	if [ $rc -eq 0 ]
95	then
96		ROUTE_TO_INSTANCE=$(echo "$res" |grep "\s${OCF_RESKEY_address}/" | awk -F "${IFS_}" "${EXECUTING}")
97		ocf_log debug "ROUTE_TO_INSTANCE: $ROUTE_TO_INSTANCE"
98	else
99		ocf_log err "result: $res; cmd: $cmd; rc: $rc"
100	fi
101}
102
103ip_get_and_configure() {
104	ocf_log debug "function: ip_get_and_configure"
105
106	request_describe_route_tables
107	if [ "$ECS_INSTANCE_ID" != "$ROUTE_TO_INSTANCE" ]; then
108		if [ -n "$ROUTE_TO_INSTANCE" ]; then
109			ip_drop
110		fi
111		request_create_route_entry
112		rc=$?
113		while [ $rc -ne 0 ]; do
114			sleep 1
115			request_create_route_entry
116			rc=$?
117		done
118		wait_for_started
119	fi
120
121
122	# Reconfigure the local ip address
123	ip addr add "${OCF_RESKEY_address}/32" dev $OCF_RESKEY_interface
124	rc=$?
125	if [ $rc -ne 0 ]; then
126		ocf_log err "command failed, rc: $rc"
127		return $OCF_ERR_GENERIC
128	fi
129
130	ocf_log debug "IP added"
131
132	return $OCF_SUCCESS
133}
134
135ip_drop() {
136	ocf_log debug "function: ip_drop"
137	cmd="ip addr delete ${OCF_RESKEY_address}/32 dev $OCF_RESKEY_interface"
138	ocf_log debug "executing command: $cmd"
139	res=$($cmd)
140	rc=$?
141	if [ $rc -ne 0 ] && [ $rc -ne 2 ]; then
142		ocf_log err "command failed, rc: $rc; cmd: $cmd; result: $res"
143		return $OCF_ERR_GENERIC
144	fi
145	request_delete_route_entry
146	rc=$?
147	if [ $rc -ne 0 ]; then
148		ocf_log err "command failed, rc: $rc"
149		return $OCF_ERR_GENERIC
150	fi
151	wait_for_deleted
152
153	ocf_log debug "IP dropped"
154
155	return $OCF_SUCCESS
156}
157
158wait_for_started() {
159	request_describe_route_tables
160	while [ "$ECS_INSTANCE_ID" != "$ROUTE_TO_INSTANCE" ]; do
161		sleep 3
162		request_describe_route_tables
163	done
164}
165
166wait_for_deleted() {
167	request_describe_route_tables
168	 while [ ! -z "$ROUTE_TO_INSTANCE" ]; do
169		sleep 1
170		request_describe_route_tables
171	 done
172}
173
174ecs_ip_metadata() {
175	cat <<END
176<?xml version="1.0"?>
177<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
178<resource-agent name="aliyun-vpc-move-ip">
179<version>2.0</version>
180<longdesc lang="en">
181Resource Agent to move IP addresses within a VPC of the Aliyun Webservices ECS
182by changing an entry in an specific routing table
183</longdesc>
184<shortdesc lang="en">Move IP within a VPC of the Aliyun ECS</shortdesc>
185
186<parameters>
187<parameter name="aliyuncli" required="0">
188<longdesc lang="en">
189Path to command line tools for Aliyun
190</longdesc>
191<shortdesc lang="en">Path to Aliyun CLI tools</shortdesc>
192<content type="string" default="${OCF_RESKEY_aliyuncli_default}" />
193</parameter>
194
195<parameter name="address" required="1">
196<longdesc lang="en">
197VPC private IP address
198</longdesc>
199<shortdesc lang="en">vpc ip</shortdesc>
200<content type="string" default="${OCF_RESKEY_address_default}" />
201</parameter>
202
203<parameter name="routing_table" required="1">
204<longdesc lang="en">
205Name of the routing table, where the route for the IP address should be changed, i.e. vtb-...
206</longdesc>
207<shortdesc lang="en">routing table name</shortdesc>
208<content type="string" default="${OCF_RESKEY_routing_table_default}" />
209</parameter>
210
211<parameter name="interface" required="1">
212<longdesc lang="en">
213Name of the network interface, i.e. eth0
214</longdesc>
215<shortdesc lang="en">network interface name</shortdesc>
216<content type="string" default="${OCF_RESKEY_interface_default}" />
217</parameter>
218
219<parameter name="endpoint" required="0">
220<longdesc lang="en">
221An endpoint is the service entry of an Alibaba Cloud service, i.e. vpc.cn-beijing.aliyuncs.com
222</longdesc>
223<shortdesc lang="en">service endpoint</shortdesc>
224<content type="string" default="${OCF_RESKEY_endpoint_default}" />
225</parameter>
226
227<parameter name="profile" required="0">
228<longdesc lang="en">
229Valid Aliyun CLI profile name (see 'aliyun cli configure').
230See https://www.alibabacloud.com/help/zh/product/29991.htm for more information about aliyun cli.
231</longdesc>
232<shortdesc lang="en">profile name</shortdesc>
233<content type="string" default="${OCF_RESKEY_profile_default}" />
234</parameter>
235</parameters>
236
237<actions>
238<action name="start" timeout="180s" />
239<action name="stop" timeout="180s" />
240<action name="monitor" depth="0" timeout="30s" interval="30s" />
241<action name="validate-all" timeout="5s" />
242<action name="meta-data" timeout="5s" />
243</actions>
244</resource-agent>
245END
246}
247
248ecs_ip_validate() {
249	ocf_log debug "function: validate"
250
251	if [ -z "${OCF_RESKEY_aliyuncli}" ]; then
252		ocf_exit_reason "unable to detect aliyuncli binary"
253		exit $OCF_ERR_INSTALLED
254	fi
255
256	# IP address
257	if [ -z "$OCF_RESKEY_address" ]; then
258		ocf_log err "IP address parameter not set $OCF_RESKEY_ADDRESS!"
259		exit $OCF_ERR_CONFIGURED
260	fi
261
262	# Network Interface
263	if [ -z "$OCF_RESKEY_interface" ]; then
264		ocf_log err "Network interface parameter not set $OCF_RESKEY_INTERFACE!"
265		exit $OCF_ERR_CONFIGURED
266	fi
267
268	# Routing Table
269	if [ -z "$OCF_RESKEY_routing_table" ]; then
270		ocf_log err "Routing table parameter not set $OCF_RESKEY_ROUTING_TABLE!"
271		exit $OCF_ERR_CONFIGURED
272	fi
273
274	if [ -z "${ECS_INSTANCE_ID}" ]; then
275		ocf_exit_reason "Instance ID not found. Is this a ECS instance?"
276		return $OCF_ERR_GENERIC
277	fi
278
279	return $OCF_SUCCESS
280}
281
282ecs_ip_start() {
283	ocf_log info "ECS: Moving IP address $OCF_RESKEY_address to this host by adjusting routing table $OCF_RESKEY_routing_table"
284
285	ecs_ip_monitor
286	if [ $? = $OCF_SUCCESS ]; then
287		ocf_log info "ECS: $OCF_RESKEY_address already started"
288		return $OCF_SUCCESS
289	fi
290
291	ocf_log info "ECS: Adjusting routing table and locally configuring IP address"
292	ip_get_and_configure
293	rc=$?
294	if [ $rc -ne 0 ]; then
295		ocf_log err "Received $rc from 'aliyun cli'"
296		return $OCF_ERR_GENERIC
297	fi
298
299	ecs_ip_monitor
300	rc=$?
301	if [ $rc -ne $OCF_SUCCESS ]; then
302		ocf_log err "IP address couldn't be configured on this host (IP: $OCF_RESKEY_address, Interface: $OCF_RESKEY_interface)"
303		return $rc
304	fi
305
306	return $OCF_SUCCESS
307}
308
309ecs_ip_stop() {
310	ocf_log info "ECS: Bringing down IP address $OCF_RESKEY_address"
311
312	ecs_ip_monitor
313	if [ $? = $OCF_NOT_RUNNING ]; then
314		ocf_log info "ECS: Address $OCF_RESKEY_address already down"
315		return $OCF_SUCCESS
316	fi
317
318	ip_drop
319	if [ $? -ne $OCF_SUCCESS ]; then
320		ocf_log err "ECS: Couldn't drop IP address $OCF_RESKEY_address on interface $OCF_RESKEY_interface."
321		return $OCF_ERR_GENERIC
322	fi
323
324	ecs_ip_monitor
325	if [ $? = $OCF_NOT_RUNNING ]; then
326		ocf_log info "ECS: Successfully brought down $OCF_RESKEY_address"
327		return $OCF_SUCCESS
328	fi
329
330	ocf_log err "ECS: Couldn't bring down IP address $OCF_RESKEY_address on interface $OCF_RESKEY_interface."
331	return $OCF_ERR_GENERIC
332}
333
334ecs_ip_monitor() {
335	ocf_log debug "function: ecsip_monitor: check routing table"
336	request_describe_route_tables
337
338	if [ "$ECS_INSTANCE_ID" != "$ROUTE_TO_INSTANCE" ]; then
339		ocf_log debug "not routed to this instance ($ECS_INSTANCE_ID) but to instance $ROUTE_TO_INSTANCE"
340		return $OCF_NOT_RUNNING
341	fi
342
343	cmd="ping -W 1 -c 1 $OCF_RESKEY_address"
344	ocf_log debug "executing command: $cmd"
345	$cmd > /dev/null
346	if [ $? -ne 0 ]; then
347		ocf_log debug "IP $OCF_RESKEY_address not locally reachable via ping on this system"
348		return $OCF_NOT_RUNNING
349	fi
350	ocf_log debug "routed in VPC and locally reachable"
351	return $OCF_SUCCESS
352}
353
354
355###############################################################################
356#
357# MAIN
358#
359###############################################################################
360
361case $__OCF_ACTION in
362	meta-data) ecs_ip_metadata
363		   exit $OCF_SUCCESS;;
364	validate-all) ecs_ip_validate;;
365esac
366
367ECS_INSTANCE_ID="$(curl -s http://100.100.100.200/latest/meta-data/instance-id)"
368
369case $__OCF_ACTION in
370	start)
371		ecs_ip_validate
372		ecs_ip_start;;
373	stop)
374		ecs_ip_stop;;
375	monitor)
376		ecs_ip_monitor;;
377	*)	exit $OCF_ERR_UNIMPLEMENTED;;
378esac
379