|
|
05afe3 |
From 0ee4c62105ee8f90a43fe0bf8a65bc9b9da2e7e0 Mon Sep 17 00:00:00 2001
|
|
|
05afe3 |
From: Helen Koike <helen.koike@collabora.com>
|
|
|
05afe3 |
Date: Wed, 18 Jul 2018 11:54:40 -0300
|
|
|
05afe3 |
Subject: [PATCH 1/4] gcp-vpc-move-route.in: python implementation of
|
|
|
05afe3 |
gcp-vpc-move-ip.in
|
|
|
05afe3 |
|
|
|
05afe3 |
gcloud api is not reliable and it is slow, add a python version of
|
|
|
05afe3 |
gcp-vpc-move-ip.in
|
|
|
05afe3 |
---
|
|
|
05afe3 |
configure.ac | 1 +
|
|
|
05afe3 |
doc/man/Makefile.am | 1 +
|
|
|
05afe3 |
heartbeat/Makefile.am | 1 +
|
|
|
05afe3 |
heartbeat/gcp-vpc-move-route.in | 441 ++++++++++++++++++++++++++++++++++++++++
|
|
|
05afe3 |
4 files changed, 444 insertions(+)
|
|
|
05afe3 |
create mode 100644 heartbeat/gcp-vpc-move-route.in
|
|
|
05afe3 |
|
|
|
05afe3 |
diff --git a/configure.ac b/configure.ac
|
|
|
05afe3 |
index 3d8f9ca74..039b4942c 100644
|
|
|
05afe3 |
--- a/configure.ac
|
|
|
05afe3 |
+++ b/configure.ac
|
|
|
05afe3 |
@@ -960,6 +960,7 @@ AC_CONFIG_FILES([heartbeat/eDir88], [chmod +x heartbeat/eDir88])
|
|
|
05afe3 |
AC_CONFIG_FILES([heartbeat/fio], [chmod +x heartbeat/fio])
|
|
|
05afe3 |
AC_CONFIG_FILES([heartbeat/gcp-vpc-move-ip], [chmod +x heartbeat/gcp-vpc-move-ip])
|
|
|
05afe3 |
AC_CONFIG_FILES([heartbeat/gcp-vpc-move-vip], [chmod +x heartbeat/gcp-vpc-move-vip])
|
|
|
05afe3 |
+AC_CONFIG_FILES([heartbeat/gcp-vpc-move-route], [chmod +x heartbeat/gcp-vpc-move-route])
|
|
|
05afe3 |
AC_CONFIG_FILES([heartbeat/iSCSILogicalUnit], [chmod +x heartbeat/iSCSILogicalUnit])
|
|
|
05afe3 |
AC_CONFIG_FILES([heartbeat/iSCSITarget], [chmod +x heartbeat/iSCSITarget])
|
|
|
05afe3 |
AC_CONFIG_FILES([heartbeat/jira], [chmod +x heartbeat/jira])
|
|
|
05afe3 |
diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am
|
|
|
05afe3 |
index e9eaf369f..3ac0569de 100644
|
|
|
05afe3 |
--- a/doc/man/Makefile.am
|
|
|
05afe3 |
+++ b/doc/man/Makefile.am
|
|
|
05afe3 |
@@ -115,6 +115,7 @@ man_MANS = ocf_heartbeat_AoEtarget.7 \
|
|
|
05afe3 |
ocf_heartbeat_garbd.7 \
|
|
|
05afe3 |
ocf_heartbeat_gcp-vpc-move-ip.7 \
|
|
|
05afe3 |
ocf_heartbeat_gcp-vpc-move-vip.7 \
|
|
|
05afe3 |
+ ocf_heartbeat_gcp-vpc-move-route.7 \
|
|
|
05afe3 |
ocf_heartbeat_iSCSILogicalUnit.7 \
|
|
|
05afe3 |
ocf_heartbeat_iSCSITarget.7 \
|
|
|
05afe3 |
ocf_heartbeat_iface-bridge.7 \
|
|
|
05afe3 |
diff --git a/heartbeat/Makefile.am b/heartbeat/Makefile.am
|
|
|
05afe3 |
index 36b271956..d4750bf09 100644
|
|
|
05afe3 |
--- a/heartbeat/Makefile.am
|
|
|
05afe3 |
+++ b/heartbeat/Makefile.am
|
|
|
05afe3 |
@@ -112,6 +112,7 @@ ocf_SCRIPTS = AoEtarget \
|
|
|
05afe3 |
garbd \
|
|
|
05afe3 |
gcp-vpc-move-ip \
|
|
|
05afe3 |
gcp-vpc-move-vip \
|
|
|
05afe3 |
+ gcp-vpc-move-route \
|
|
|
05afe3 |
iSCSILogicalUnit \
|
|
|
05afe3 |
iSCSITarget \
|
|
|
05afe3 |
ids \
|
|
|
05afe3 |
diff --git a/heartbeat/gcp-vpc-move-route.in b/heartbeat/gcp-vpc-move-route.in
|
|
|
05afe3 |
new file mode 100644
|
|
|
05afe3 |
index 000000000..5f4569baa
|
|
|
05afe3 |
--- /dev/null
|
|
|
05afe3 |
+++ b/heartbeat/gcp-vpc-move-route.in
|
|
|
05afe3 |
@@ -0,0 +1,441 @@
|
|
|
05afe3 |
+#!@PYTHON@ -tt
|
|
|
05afe3 |
+# - *- coding: utf- 8 - *-
|
|
|
05afe3 |
+#
|
|
|
05afe3 |
+#
|
|
|
05afe3 |
+# OCF resource agent to move an IP address within a VPC in GCP
|
|
|
05afe3 |
+#
|
|
|
05afe3 |
+# License: GNU General Public License (GPL)
|
|
|
05afe3 |
+# Copyright (c) 2018 Hervé Werner (MFG Labs)
|
|
|
05afe3 |
+# Copyright 2018 Google Inc.
|
|
|
05afe3 |
+# Based on code from Markus Guertler (aws-vpc-move-ip)
|
|
|
05afe3 |
+# All Rights Reserved.
|
|
|
05afe3 |
+#
|
|
|
05afe3 |
+# This program is free software; you can redistribute it and/or modify
|
|
|
05afe3 |
+# it under the terms of version 2 of the GNU General Public License as
|
|
|
05afe3 |
+# published by the Free Software Foundation.
|
|
|
05afe3 |
+#
|
|
|
05afe3 |
+# This program is distributed in the hope that it would be useful, but
|
|
|
05afe3 |
+# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
05afe3 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
05afe3 |
+#
|
|
|
05afe3 |
+# Further, this software is distributed without any warranty that it is
|
|
|
05afe3 |
+# free of the rightful claim of any third person regarding infringement
|
|
|
05afe3 |
+# or the like. Any license provided herein, whether implied or
|
|
|
05afe3 |
+# otherwise, applies only to this software file. Patent licenses, if
|
|
|
05afe3 |
+# any, provided herein do not apply to combinations of this program with
|
|
|
05afe3 |
+# other software, or any other product whatsoever.
|
|
|
05afe3 |
+#
|
|
|
05afe3 |
+# You should have received a copy of the GNU General Public License
|
|
|
05afe3 |
+# along with this program; if not, write the Free Software Foundation,
|
|
|
05afe3 |
+# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
|
|
|
05afe3 |
+#
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+#######################################################################
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+import atexit
|
|
|
05afe3 |
+import logging
|
|
|
05afe3 |
+import os
|
|
|
05afe3 |
+import sys
|
|
|
05afe3 |
+import time
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+try:
|
|
|
05afe3 |
+ import googleapiclient.discovery
|
|
|
05afe3 |
+ import pyroute2
|
|
|
05afe3 |
+except ImportError:
|
|
|
05afe3 |
+ pass
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+if sys.version_info >= (3, 0):
|
|
|
05afe3 |
+ # Python 3 imports.
|
|
|
05afe3 |
+ import urllib.parse as urlparse
|
|
|
05afe3 |
+ import urllib.request as urlrequest
|
|
|
05afe3 |
+else:
|
|
|
05afe3 |
+ # Python 2 imports.
|
|
|
05afe3 |
+ import urllib as urlparse
|
|
|
05afe3 |
+ import urllib2 as urlrequest
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+OCF_SUCCESS = 0
|
|
|
05afe3 |
+OCF_ERR_GENERIC = 1
|
|
|
05afe3 |
+OCF_ERR_UNIMPLEMENTED = 3
|
|
|
05afe3 |
+OCF_ERR_PERM = 4
|
|
|
05afe3 |
+OCF_ERR_CONFIGURED = 6
|
|
|
05afe3 |
+OCF_NOT_RUNNING = 7
|
|
|
05afe3 |
+GCP_API_URL_PREFIX = 'https://www.googleapis.com/compute/v1'
|
|
|
05afe3 |
+METADATA_SERVER = 'http://metadata.google.internal/computeMetadata/v1/'
|
|
|
05afe3 |
+METADATA_HEADERS = {'Metadata-Flavor': 'Google'}
|
|
|
05afe3 |
+METADATA = \
|
|
|
05afe3 |
+'''
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+<resource-agent name="gcp-vpc-move-route">
|
|
|
05afe3 |
+<version>1.0</version>
|
|
|
05afe3 |
+<longdesc lang="en">
|
|
|
05afe3 |
+Resource Agent that can move a floating IP addresse within a GCP VPC by changing an
|
|
|
05afe3 |
+entry in the routing table. This agent also configures the floating IP locally
|
|
|
05afe3 |
+on the instance OS.
|
|
|
05afe3 |
+Requirements :
|
|
|
05afe3 |
+- IP forwarding must be enabled on all instances in order to be able to
|
|
|
05afe3 |
+terminate the route
|
|
|
05afe3 |
+- The floating IP address must be choosen so that it is outside all existing
|
|
|
05afe3 |
+subnets in the VPC network
|
|
|
05afe3 |
+- IAM permissions
|
|
|
05afe3 |
+(see https://cloud.google.com/compute/docs/access/iam-permissions) :
|
|
|
05afe3 |
+1) compute.routes.delete, compute.routes.get and compute.routes.update on the
|
|
|
05afe3 |
+route
|
|
|
05afe3 |
+2) compute.networks.updatePolicy on the network (to add a new route)
|
|
|
05afe3 |
+3) compute.networks.get on the network (to check the VPC network existence)
|
|
|
05afe3 |
+4) compute.routes.list on the project (to check conflicting routes)
|
|
|
05afe3 |
+</longdesc>
|
|
|
05afe3 |
+<shortdesc lang="en">Move IP within a GCP VPC</shortdesc>
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+<parameters>
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+<parameter name="stackdriver_logging" unique="0" required="0">
|
|
|
05afe3 |
+<longdesc lang="en">If enabled (set to true), IP failover logs will be posted to stackdriver logging</longdesc>
|
|
|
05afe3 |
+<shortdesc lang="en">Stackdriver-logging support</shortdesc>
|
|
|
05afe3 |
+<content type="boolean" default="" />
|
|
|
05afe3 |
+</parameter>
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+<parameter name="ip" unique="1" required="1">
|
|
|
05afe3 |
+<longdesc lang="en">
|
|
|
05afe3 |
+Floating IP address. Note that this IP must be chosen outside of all existing
|
|
|
05afe3 |
+subnet ranges
|
|
|
05afe3 |
+</longdesc>
|
|
|
05afe3 |
+<shortdesc lang="en">Floating IP</shortdesc>
|
|
|
05afe3 |
+<content type="string" />
|
|
|
05afe3 |
+</parameter>
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+<parameter name="vpc_network" required="1">
|
|
|
05afe3 |
+<longdesc lang="en">
|
|
|
05afe3 |
+Name of the VPC network
|
|
|
05afe3 |
+</longdesc>
|
|
|
05afe3 |
+<shortdesc lang="en">VPC network</shortdesc>
|
|
|
05afe3 |
+<content type="string" default="${OCF_RESKEY_vpc_network_default}" />
|
|
|
05afe3 |
+</parameter>
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+<parameter name="interface">
|
|
|
05afe3 |
+<longdesc lang="en">
|
|
|
05afe3 |
+Name of the network interface
|
|
|
05afe3 |
+</longdesc>
|
|
|
05afe3 |
+<shortdesc lang="en">Network interface name</shortdesc>
|
|
|
05afe3 |
+<content type="string" default="${OCF_RESKEY_interface_default}" />
|
|
|
05afe3 |
+</parameter>
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+<parameter name="route_name" unique="1">
|
|
|
05afe3 |
+<longdesc lang="en">
|
|
|
05afe3 |
+Route name
|
|
|
05afe3 |
+</longdesc>
|
|
|
05afe3 |
+<shortdesc lang="en">Route name</shortdesc>
|
|
|
05afe3 |
+<content type="string" default="${OCF_RESKEY_route_name_default}" />
|
|
|
05afe3 |
+</parameter>
|
|
|
05afe3 |
+</parameters>
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+<actions>
|
|
|
05afe3 |
+<action name="start" timeout="180s" />
|
|
|
05afe3 |
+<action name="stop" timeout="180s" />
|
|
|
05afe3 |
+<action name="monitor" depth="0" timeout="30s" interval="60s" />
|
|
|
05afe3 |
+<action name="validate-all" timeout="5s" />
|
|
|
05afe3 |
+<action name="meta-data" timeout="5s" />
|
|
|
05afe3 |
+</actions>
|
|
|
05afe3 |
+</resource-agent>
|
|
|
05afe3 |
+'''
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+class Context(object):
|
|
|
05afe3 |
+ __slots__ = 'conn', 'iface_idx', 'instance', 'instance_url', 'interface', \
|
|
|
05afe3 |
+ 'ip', 'iproute', 'project', 'route_name', 'vpc_network', \
|
|
|
05afe3 |
+ 'vpc_network_url', 'zone'
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def wait_for_operation(ctx, response):
|
|
|
05afe3 |
+ """Blocks until operation completes.
|
|
|
05afe3 |
+ Code from GitHub's GoogleCloudPlatform/python-docs-samples
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ Args:
|
|
|
05afe3 |
+ response: dict, a request's response
|
|
|
05afe3 |
+ """
|
|
|
05afe3 |
+ def _OperationGetter(response):
|
|
|
05afe3 |
+ operation = response[u'name']
|
|
|
05afe3 |
+ if response.get(u'zone'):
|
|
|
05afe3 |
+ return ctx.conn.zoneOperations().get(
|
|
|
05afe3 |
+ project=ctx.project, zone=ctx.zone, operation=operation)
|
|
|
05afe3 |
+ else:
|
|
|
05afe3 |
+ return ctx.conn.globalOperations().get(
|
|
|
05afe3 |
+ project=ctx.project, operation=operation)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ while True:
|
|
|
05afe3 |
+ result = _OperationGetter(response).execute()
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ if result['status'] == 'DONE':
|
|
|
05afe3 |
+ if 'error' in result:
|
|
|
05afe3 |
+ raise Exception(result['error'])
|
|
|
05afe3 |
+ return result
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ time.sleep(1)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def get_metadata(metadata_key, params=None, timeout=None):
|
|
|
05afe3 |
+ """Performs a GET request with the metadata headers.
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ Args:
|
|
|
05afe3 |
+ metadata_key: string, the metadata to perform a GET request on.
|
|
|
05afe3 |
+ params: dictionary, the query parameters in the GET request.
|
|
|
05afe3 |
+ timeout: int, timeout in seconds for metadata requests.
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ Returns:
|
|
|
05afe3 |
+ HTTP response from the GET request.
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ Raises:
|
|
|
05afe3 |
+ urlerror.HTTPError: raises when the GET request fails.
|
|
|
05afe3 |
+ """
|
|
|
05afe3 |
+ timeout = timeout or 60
|
|
|
05afe3 |
+ metadata_url = os.path.join(METADATA_SERVER, metadata_key)
|
|
|
05afe3 |
+ params = urlparse.urlencode(params or {})
|
|
|
05afe3 |
+ url = '%s?%s' % (metadata_url, params)
|
|
|
05afe3 |
+ request = urlrequest.Request(url, headers=METADATA_HEADERS)
|
|
|
05afe3 |
+ request_opener = urlrequest.build_opener(urlrequest.ProxyHandler({}))
|
|
|
05afe3 |
+ return request_opener.open(request, timeout=timeout * 1.1).read()
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def validate(ctx):
|
|
|
05afe3 |
+ if os.geteuid() != 0:
|
|
|
05afe3 |
+ logging.error('You must run this agent as root')
|
|
|
05afe3 |
+ sys.exit(OCF_ERR_PERM)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ try:
|
|
|
05afe3 |
+ ctx.conn = googleapiclient.discovery.build('compute', 'v1')
|
|
|
05afe3 |
+ except Exception as e:
|
|
|
05afe3 |
+ logging.error('Couldn\'t connect with google api: ' + str(e))
|
|
|
05afe3 |
+ sys.exit(OCF_ERR_CONFIGURED)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ ctx.ip = os.environ.get('OCF_RESKEY_ip')
|
|
|
05afe3 |
+ if not ctx.ip:
|
|
|
05afe3 |
+ logging.error('Missing ip parameter')
|
|
|
05afe3 |
+ sys.exit(OCF_ERR_CONFIGURED)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ try:
|
|
|
05afe3 |
+ ctx.instance = get_metadata('instance/name')
|
|
|
05afe3 |
+ ctx.zone = get_metadata('instance/zone').split('/')[-1]
|
|
|
05afe3 |
+ ctx.project = get_metadata('project/project-id')
|
|
|
05afe3 |
+ except Exception as e:
|
|
|
05afe3 |
+ logging.error(
|
|
|
05afe3 |
+ 'Instance information not found. Is this a GCE instance ?: %s', str(e))
|
|
|
05afe3 |
+ sys.exit(OCF_ERR_CONFIGURED)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ ctx.instance_url = '%s/projects/%s/zones/%s/instances/%s' % (
|
|
|
05afe3 |
+ GCP_API_URL_PREFIX, ctx.project, ctx.zone, ctx.instance)
|
|
|
05afe3 |
+ ctx.vpc_network = os.environ.get('OCF_RESKEY_vpc_network', 'default')
|
|
|
05afe3 |
+ ctx.vpc_network_url = '%s/projects/%s/global/networks/%s' % (
|
|
|
05afe3 |
+ GCP_API_URL_PREFIX, ctx.project, ctx.vpc_network)
|
|
|
05afe3 |
+ ctx.interface = os.environ.get('OCF_RESKEY_interface', 'eth0')
|
|
|
05afe3 |
+ ctx.route_name = os.environ.get(
|
|
|
05afe3 |
+ 'OCF_RESKEY_route_name', 'ra-%s' % os.environ['__SCRIPT_NAME'])
|
|
|
05afe3 |
+ ctx.iproute = pyroute2.IPRoute()
|
|
|
05afe3 |
+ atexit.register(ctx.iproute.close)
|
|
|
05afe3 |
+ idxs = ctx.iproute.link_lookup(ifname=ctx.interface)
|
|
|
05afe3 |
+ if not idxs:
|
|
|
05afe3 |
+ logging.error('Network interface not found')
|
|
|
05afe3 |
+ sys.exit(OCF_ERR_CONFIGURED)
|
|
|
05afe3 |
+ ctx.iface_idx = idxs[0]
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def check_conflicting_routes(ctx):
|
|
|
05afe3 |
+ fl = '(destRange = "%s*") AND (network = "%s") AND (name != "%s")' % (
|
|
|
05afe3 |
+ ctx.ip, ctx.vpc_network_url, ctx.route_name)
|
|
|
05afe3 |
+ request = ctx.conn.routes().list(project=ctx.project, filter=fl)
|
|
|
05afe3 |
+ response = request.execute()
|
|
|
05afe3 |
+ route_list = response.get('items', None)
|
|
|
05afe3 |
+ if route_list:
|
|
|
05afe3 |
+ logging.error(
|
|
|
05afe3 |
+ 'Conflicting unnmanaged routes for destination %s/32 in VPC %s found : %s',
|
|
|
05afe3 |
+ ctx.ip, ctx.vpc_network, str(route_list))
|
|
|
05afe3 |
+ sys.exit(OCF_ERR_CONFIGURED)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def route_release(ctx):
|
|
|
05afe3 |
+ request = ctx.conn.routes().delete(project=ctx.project, route=ctx.route_name)
|
|
|
05afe3 |
+ wait_for_operation(ctx, request.execute())
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def ip_monitor(ctx):
|
|
|
05afe3 |
+ logging.info('IP monitor: checking local network configuration')
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ def address_filter(addr):
|
|
|
05afe3 |
+ for attr in addr['attrs']:
|
|
|
05afe3 |
+ if attr[0] == 'IFA_LOCAL':
|
|
|
05afe3 |
+ if attr[1] == ctx.ip:
|
|
|
05afe3 |
+ return True
|
|
|
05afe3 |
+ else:
|
|
|
05afe3 |
+ return False
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ route = ctx.iproute.get_addr(
|
|
|
05afe3 |
+ index=ctx.iface_idx, match=address_filter)
|
|
|
05afe3 |
+ if not route:
|
|
|
05afe3 |
+ logging.warn(
|
|
|
05afe3 |
+ 'The floating IP %s is not locally configured on this instance (%s)',
|
|
|
05afe3 |
+ ctx.ip, ctx.instance)
|
|
|
05afe3 |
+ return OCF_NOT_RUNNING
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ logging.debug(
|
|
|
05afe3 |
+ 'The floating IP %s is correctly configured on this instance (%s)',
|
|
|
05afe3 |
+ ctx.ip, ctx.instance)
|
|
|
05afe3 |
+ return OCF_SUCCESS
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def ip_release(ctx):
|
|
|
05afe3 |
+ ctx.iproute.addr('del', index=ctx.iface_idx, address=ctx.ip, mask=32)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def ip_and_route_start(ctx):
|
|
|
05afe3 |
+ logging.info('Bringing up the floating IP %s', ctx.ip)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ # Add a new entry in the routing table
|
|
|
05afe3 |
+ # If the route entry exists and is pointing to another instance, take it over
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ # Ensure that there is no route that we are not aware of that is also handling our IP
|
|
|
05afe3 |
+ check_conflicting_routes(ctx)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ # There is no replace API, We need to first delete the existing route if any
|
|
|
05afe3 |
+ try:
|
|
|
05afe3 |
+ request = ctx.conn.routes().get(project=ctx.project, route=ctx.route_name)
|
|
|
05afe3 |
+ request.execute()
|
|
|
05afe3 |
+ # TODO: check specific exception for 404
|
|
|
05afe3 |
+ except googleapiclient.errors.HttpError as e:
|
|
|
05afe3 |
+ if e.resp.status != 404:
|
|
|
05afe3 |
+ raise
|
|
|
05afe3 |
+ else:
|
|
|
05afe3 |
+ route_release(ctx)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ route_body = {
|
|
|
05afe3 |
+ 'name': ctx.route_name,
|
|
|
05afe3 |
+ 'network': ctx.vpc_network_url,
|
|
|
05afe3 |
+ 'destRange': '%s/32' % ctx.ip,
|
|
|
05afe3 |
+ 'nextHopInstance': ctx.instance_url,
|
|
|
05afe3 |
+ }
|
|
|
05afe3 |
+ try:
|
|
|
05afe3 |
+ request = ctx.conn.routes().insert(project=ctx.project, body=route_body)
|
|
|
05afe3 |
+ wait_for_operation(ctx, request.execute())
|
|
|
05afe3 |
+ except googleapiclient.errors.HttpError:
|
|
|
05afe3 |
+ try:
|
|
|
05afe3 |
+ request = ctx.conn.networks().get(
|
|
|
05afe3 |
+ project=ctx.project, network=ctx.vpc_network)
|
|
|
05afe3 |
+ request.execute()
|
|
|
05afe3 |
+ except googleapiclient.errors.HttpError as e:
|
|
|
05afe3 |
+ if e.resp.status == 404:
|
|
|
05afe3 |
+ logging.error('VPC network not found')
|
|
|
05afe3 |
+ sys.exit(OCF_ERR_CONFIGURED)
|
|
|
05afe3 |
+ else:
|
|
|
05afe3 |
+ raise
|
|
|
05afe3 |
+ else:
|
|
|
05afe3 |
+ raise
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ # Configure the IP address locally
|
|
|
05afe3 |
+ # We need to release the IP first
|
|
|
05afe3 |
+ if ip_monitor(ctx) == OCF_SUCCESS:
|
|
|
05afe3 |
+ ip_release(ctx)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ ctx.iproute.addr('add', index=ctx.iface_idx, address=ctx.ip, mask=32)
|
|
|
05afe3 |
+ ctx.iproute.link('set', index=ctx.iface_idx, state='up')
|
|
|
05afe3 |
+ logging.info('Successfully brought up the floating IP %s', ctx.ip)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def route_monitor(ctx):
|
|
|
05afe3 |
+ logging.info('GCP route monitor: checking route table')
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ # Ensure that there is no route that we are not aware of that is also handling our IP
|
|
|
05afe3 |
+ check_conflicting_routes
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ try:
|
|
|
05afe3 |
+ request = ctx.conn.routes().get(project=ctx.project, route=ctx.route_name)
|
|
|
05afe3 |
+ response = request.execute()
|
|
|
05afe3 |
+ except googleapiclient.errors.HttpError as e:
|
|
|
05afe3 |
+ if 'Insufficient Permission' in e.content:
|
|
|
05afe3 |
+ return OCF_ERR_PERM
|
|
|
05afe3 |
+ elif e.resp.status == 404:
|
|
|
05afe3 |
+ return OCF_NOT_RUNNING
|
|
|
05afe3 |
+ else:
|
|
|
05afe3 |
+ raise
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ routed_to_instance = response.get('nextHopInstance', '<unknown>')
|
|
|
05afe3 |
+ instance_url = '%s/projects/%s/zones/%s/instances/%s' % (
|
|
|
05afe3 |
+ GCP_API_URL_PREFIX, ctx.project, ctx.zone, ctx.instance)
|
|
|
05afe3 |
+ if routed_to_instance != instance_url:
|
|
|
05afe3 |
+ logging.warn(
|
|
|
05afe3 |
+ 'The floating IP %s is not routed to this instance (%s) but to instance %s',
|
|
|
05afe3 |
+ ctx.ip, ctx.instance, routed_to_instance.split('/')[-1])
|
|
|
05afe3 |
+ return OCF_NOT_RUNNING
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ logging.debug(
|
|
|
05afe3 |
+ 'The floating IP %s is correctly routed to this instance (%s)',
|
|
|
05afe3 |
+ ctx.ip, ctx.instance)
|
|
|
05afe3 |
+ return OCF_SUCCESS
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def ip_and_route_stop(ctx):
|
|
|
05afe3 |
+ logging.info('Bringing down the floating IP %s', ctx.ip)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ # Delete the route entry
|
|
|
05afe3 |
+ # If the route entry exists and is pointing to another instance, don't touch it
|
|
|
05afe3 |
+ if route_monitor(ctx) == OCF_NOT_RUNNING:
|
|
|
05afe3 |
+ logging.info(
|
|
|
05afe3 |
+ 'The floating IP %s is already not routed to this instance (%s)',
|
|
|
05afe3 |
+ ctx.ip, ctx.instance)
|
|
|
05afe3 |
+ else:
|
|
|
05afe3 |
+ route_release(ctx)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ if ip_monitor(ctx) == OCF_NOT_RUNNING:
|
|
|
05afe3 |
+ logging.info('The floating IP %s is already down', ctx.ip)
|
|
|
05afe3 |
+ else:
|
|
|
05afe3 |
+ ip_release(ctx)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def configure_logs(ctx):
|
|
|
05afe3 |
+ # Prepare logging
|
|
|
05afe3 |
+ logging.basicConfig(
|
|
|
05afe3 |
+ format='gcp:route - %(levelname)s - %(message)s', level=logging.INFO)
|
|
|
05afe3 |
+ logging.getLogger('googleapiclient').setLevel(logging.WARN)
|
|
|
05afe3 |
+ logging_env = os.environ.get('OCF_RESKEY_stackdriver_logging')
|
|
|
05afe3 |
+ if logging_env:
|
|
|
05afe3 |
+ logging_env = logging_env.lower()
|
|
|
05afe3 |
+ if any(x in logging_env for x in ['yes', 'true', 'enabled']):
|
|
|
05afe3 |
+ try:
|
|
|
05afe3 |
+ import google.cloud.logging.handlers
|
|
|
05afe3 |
+ client = google.cloud.logging.Client()
|
|
|
05afe3 |
+ handler = google.cloud.logging.handlers.CloudLoggingHandler(
|
|
|
05afe3 |
+ client, name=ctx.instance)
|
|
|
05afe3 |
+ handler.setLevel(logging.INFO)
|
|
|
05afe3 |
+ formatter = logging.Formatter('gcp:route "%(message)s"')
|
|
|
05afe3 |
+ handler.setFormatter(formatter)
|
|
|
05afe3 |
+ root_logger = logging.getLogger()
|
|
|
05afe3 |
+ root_logger.addHandler(handler)
|
|
|
05afe3 |
+ except ImportError:
|
|
|
05afe3 |
+ logging.error('Couldn\'t import google.cloud.logging, '
|
|
|
05afe3 |
+ 'disabling Stackdriver-logging support')
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def main():
|
|
|
05afe3 |
+ if 'meta-data' in sys.argv[1]:
|
|
|
05afe3 |
+ print(METADATA)
|
|
|
05afe3 |
+ return
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ ctx = Context()
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ validate(ctx)
|
|
|
05afe3 |
+ if 'validate-all' in sys.argv[1]:
|
|
|
05afe3 |
+ return
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ configure_logs(ctx)
|
|
|
05afe3 |
+ if 'start' in sys.argv[1]:
|
|
|
05afe3 |
+ ip_and_route_start(ctx)
|
|
|
05afe3 |
+ elif 'stop' in sys.argv[1]:
|
|
|
05afe3 |
+ ip_and_route_stop(ctx)
|
|
|
05afe3 |
+ elif 'status' in sys.argv[1] or 'monitor' in sys.argv[1]:
|
|
|
05afe3 |
+ sys.exit(ip_monitor(ctx))
|
|
|
05afe3 |
+ else:
|
|
|
05afe3 |
+ usage = 'usage: $0 {start|stop|monitor|status|meta-data|validate-all}'
|
|
|
05afe3 |
+ logging.error(usage)
|
|
|
05afe3 |
+ sys.exit(OCF_ERR_UNIMPLEMENTED)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+if __name__ == "__main__":
|
|
|
05afe3 |
+ main()
|
|
|
05afe3 |
|
|
|
05afe3 |
From 6590c99f462403808854114ec1031755e5ce6b36 Mon Sep 17 00:00:00 2001
|
|
|
05afe3 |
From: Helen Koike <helen.koike@collabora.com>
|
|
|
05afe3 |
Date: Thu, 19 Jul 2018 12:33:44 -0300
|
|
|
05afe3 |
Subject: [PATCH 2/4] gcp-vpc-move-ip.in: add deprecation message
|
|
|
05afe3 |
|
|
|
05afe3 |
---
|
|
|
05afe3 |
heartbeat/gcp-vpc-move-ip.in | 2 ++
|
|
|
05afe3 |
1 file changed, 2 insertions(+)
|
|
|
05afe3 |
|
|
|
05afe3 |
diff --git a/heartbeat/gcp-vpc-move-ip.in b/heartbeat/gcp-vpc-move-ip.in
|
|
|
05afe3 |
index 4a6c343a8..3b8d998b3 100755
|
|
|
05afe3 |
--- a/heartbeat/gcp-vpc-move-ip.in
|
|
|
05afe3 |
+++ b/heartbeat/gcp-vpc-move-ip.in
|
|
|
05afe3 |
@@ -348,6 +348,8 @@ ip_and_route_stop() {
|
|
|
05afe3 |
#
|
|
|
05afe3 |
###############################################################################
|
|
|
05afe3 |
|
|
|
05afe3 |
+ocf_log warn "gcp-vpc-move-ip is deprecated, prefer to use gcp-vpc-move-route instead"
|
|
|
05afe3 |
+
|
|
|
05afe3 |
case $__OCF_ACTION in
|
|
|
05afe3 |
meta-data) metadata
|
|
|
05afe3 |
exit $OCF_SUCCESS
|
|
|
05afe3 |
|
|
|
05afe3 |
From 73608196d21068c6c2d5fb9f77e3d40179c85fee Mon Sep 17 00:00:00 2001
|
|
|
05afe3 |
From: Helen Koike <helen.koike@collabora.com>
|
|
|
05afe3 |
Date: Fri, 20 Jul 2018 08:26:17 -0300
|
|
|
05afe3 |
Subject: [PATCH 3/4] gcp-vpc-move-route.in: move stackdriver parameter
|
|
|
05afe3 |
|
|
|
05afe3 |
Move stackdriver parameter to the bottom of metadata list
|
|
|
05afe3 |
---
|
|
|
05afe3 |
heartbeat/gcp-vpc-move-route.in | 12 ++++++------
|
|
|
05afe3 |
1 file changed, 6 insertions(+), 6 deletions(-)
|
|
|
05afe3 |
|
|
|
05afe3 |
diff --git a/heartbeat/gcp-vpc-move-route.in b/heartbeat/gcp-vpc-move-route.in
|
|
|
05afe3 |
index 5f4569baa..8d5bfff36 100644
|
|
|
05afe3 |
--- a/heartbeat/gcp-vpc-move-route.in
|
|
|
05afe3 |
+++ b/heartbeat/gcp-vpc-move-route.in
|
|
|
05afe3 |
@@ -90,12 +90,6 @@ route
|
|
|
05afe3 |
|
|
|
05afe3 |
<parameters>
|
|
|
05afe3 |
|
|
|
05afe3 |
-<parameter name="stackdriver_logging" unique="0" required="0">
|
|
|
05afe3 |
-<longdesc lang="en">If enabled (set to true), IP failover logs will be posted to stackdriver logging</longdesc>
|
|
|
05afe3 |
-<shortdesc lang="en">Stackdriver-logging support</shortdesc>
|
|
|
05afe3 |
-<content type="boolean" default="" />
|
|
|
05afe3 |
-</parameter>
|
|
|
05afe3 |
-
|
|
|
05afe3 |
<parameter name="ip" unique="1" required="1">
|
|
|
05afe3 |
<longdesc lang="en">
|
|
|
05afe3 |
Floating IP address. Note that this IP must be chosen outside of all existing
|
|
|
05afe3 |
@@ -128,6 +122,12 @@ Route name
|
|
|
05afe3 |
<shortdesc lang="en">Route name</shortdesc>
|
|
|
05afe3 |
<content type="string" default="${OCF_RESKEY_route_name_default}" />
|
|
|
05afe3 |
</parameter>
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+<parameter name="stackdriver_logging" unique="0" required="0">
|
|
|
05afe3 |
+<longdesc lang="en">If enabled (set to true), IP failover logs will be posted to stackdriver logging</longdesc>
|
|
|
05afe3 |
+<shortdesc lang="en">Stackdriver-logging support</shortdesc>
|
|
|
05afe3 |
+<content type="boolean" default="" />
|
|
|
05afe3 |
+</parameter>
|
|
|
05afe3 |
</parameters>
|
|
|
05afe3 |
|
|
|
05afe3 |
<actions>
|
|
|
05afe3 |
|
|
|
05afe3 |
From e54565ec69f809b28337c0471ad0a9b26a64f8bf Mon Sep 17 00:00:00 2001
|
|
|
05afe3 |
From: Helen Koike <helen.koike@collabora.com>
|
|
|
05afe3 |
Date: Fri, 20 Jul 2018 08:45:53 -0300
|
|
|
05afe3 |
Subject: [PATCH 4/4] gcp-vpc-move-route.in: minor fixes
|
|
|
05afe3 |
|
|
|
05afe3 |
---
|
|
|
05afe3 |
heartbeat/gcp-vpc-move-route.in | 13 +++++++------
|
|
|
05afe3 |
1 file changed, 7 insertions(+), 6 deletions(-)
|
|
|
05afe3 |
|
|
|
05afe3 |
diff --git a/heartbeat/gcp-vpc-move-route.in b/heartbeat/gcp-vpc-move-route.in
|
|
|
05afe3 |
index 8d5bfff36..566a70f86 100644
|
|
|
05afe3 |
--- a/heartbeat/gcp-vpc-move-route.in
|
|
|
05afe3 |
+++ b/heartbeat/gcp-vpc-move-route.in
|
|
|
05afe3 |
@@ -104,7 +104,7 @@ subnet ranges
|
|
|
05afe3 |
Name of the VPC network
|
|
|
05afe3 |
</longdesc>
|
|
|
05afe3 |
<shortdesc lang="en">VPC network</shortdesc>
|
|
|
05afe3 |
-<content type="string" default="${OCF_RESKEY_vpc_network_default}" />
|
|
|
05afe3 |
+<content type="string" default="default" />
|
|
|
05afe3 |
</parameter>
|
|
|
05afe3 |
|
|
|
05afe3 |
<parameter name="interface">
|
|
|
05afe3 |
@@ -112,7 +112,7 @@ Name of the VPC network
|
|
|
05afe3 |
Name of the network interface
|
|
|
05afe3 |
</longdesc>
|
|
|
05afe3 |
<shortdesc lang="en">Network interface name</shortdesc>
|
|
|
05afe3 |
-<content type="string" default="${OCF_RESKEY_interface_default}" />
|
|
|
05afe3 |
+<content type="string" default="eth0" />
|
|
|
05afe3 |
</parameter>
|
|
|
05afe3 |
|
|
|
05afe3 |
<parameter name="route_name" unique="1">
|
|
|
05afe3 |
@@ -120,7 +120,7 @@ Name of the network interface
|
|
|
05afe3 |
Route name
|
|
|
05afe3 |
</longdesc>
|
|
|
05afe3 |
<shortdesc lang="en">Route name</shortdesc>
|
|
|
05afe3 |
-<content type="string" default="${OCF_RESKEY_route_name_default}" />
|
|
|
05afe3 |
+<content type="string" default="ra-%s" />
|
|
|
05afe3 |
</parameter>
|
|
|
05afe3 |
|
|
|
05afe3 |
<parameter name="stackdriver_logging" unique="0" required="0">
|
|
|
05afe3 |
@@ -138,7 +138,7 @@ Route name
|
|
|
05afe3 |
<action name="meta-data" timeout="5s" />
|
|
|
05afe3 |
</actions>
|
|
|
05afe3 |
</resource-agent>
|
|
|
05afe3 |
-'''
|
|
|
05afe3 |
+''' % os.path.basename(sys.argv[0])
|
|
|
05afe3 |
|
|
|
05afe3 |
|
|
|
05afe3 |
class Context(object):
|
|
|
05afe3 |
@@ -229,7 +229,7 @@ def validate(ctx):
|
|
|
05afe3 |
GCP_API_URL_PREFIX, ctx.project, ctx.vpc_network)
|
|
|
05afe3 |
ctx.interface = os.environ.get('OCF_RESKEY_interface', 'eth0')
|
|
|
05afe3 |
ctx.route_name = os.environ.get(
|
|
|
05afe3 |
- 'OCF_RESKEY_route_name', 'ra-%s' % os.environ['__SCRIPT_NAME'])
|
|
|
05afe3 |
+ 'OCF_RESKEY_route_name', 'ra-%s' % os.path.basename(sys.argv[0]))
|
|
|
05afe3 |
ctx.iproute = pyroute2.IPRoute()
|
|
|
05afe3 |
atexit.register(ctx.iproute.close)
|
|
|
05afe3 |
idxs = ctx.iproute.link_lookup(ifname=ctx.interface)
|
|
|
05afe3 |
@@ -432,7 +432,8 @@ def main():
|
|
|
05afe3 |
elif 'status' in sys.argv[1] or 'monitor' in sys.argv[1]:
|
|
|
05afe3 |
sys.exit(ip_monitor(ctx))
|
|
|
05afe3 |
else:
|
|
|
05afe3 |
- usage = 'usage: $0 {start|stop|monitor|status|meta-data|validate-all}'
|
|
|
05afe3 |
+ usage = 'usage: %s {start|stop|monitor|status|meta-data|validate-all}' % \
|
|
|
05afe3 |
+ os.path.basename(sys.argv[0])
|
|
|
05afe3 |
logging.error(usage)
|
|
|
05afe3 |
sys.exit(OCF_ERR_UNIMPLEMENTED)
|
|
|
05afe3 |
|