|
|
05afe3 |
From 92da4155d881e9ac2dce3a51c6953817349d164a Mon Sep 17 00:00:00 2001
|
|
|
05afe3 |
From: Helen Koike <helen.koike@collabora.com>
|
|
|
05afe3 |
Date: Mon, 25 Jun 2018 11:03:51 -0300
|
|
|
05afe3 |
Subject: [PATCH 1/4] gcp-vpc-move-vip.in: manage ip alias
|
|
|
05afe3 |
|
|
|
05afe3 |
Add a resource agent to manage ip alias in the cluster.
|
|
|
05afe3 |
|
|
|
05afe3 |
start:
|
|
|
05afe3 |
Check if any machine in hostlist has the alias_ip assigned and
|
|
|
05afe3 |
disassociate it.
|
|
|
05afe3 |
Assign alias_ip to the current machine.
|
|
|
05afe3 |
|
|
|
05afe3 |
stop:
|
|
|
05afe3 |
Disassociate the alias_ip from the current machine.
|
|
|
05afe3 |
|
|
|
05afe3 |
status/monitor:
|
|
|
05afe3 |
Check if alias_ip is assigned with the current machine.
|
|
|
05afe3 |
|
|
|
05afe3 |
---
|
|
|
05afe3 |
|
|
|
05afe3 |
This is a port to the following bash script to python:
|
|
|
05afe3 |
https://storage.googleapis.com/sapdeploy/pacemaker-gcp/alias
|
|
|
05afe3 |
|
|
|
05afe3 |
The problem with the bash script is the use of gcloud whose command line
|
|
|
05afe3 |
API is not stable.
|
|
|
05afe3 |
|
|
|
05afe3 |
ocf-tester.in results:
|
|
|
05afe3 |
|
|
|
05afe3 |
> sudo ./tools/ocf-tester.in -o alias_ip='10.128.1.0/32' -o stackdriver_logging=yes -n gcp-vpc-move-vip.in heartbeat/gcp-vpc-move-vip.in
|
|
|
05afe3 |
Beginning tests for heartbeat/gcp-vpc-move-vip.in...
|
|
|
05afe3 |
./tools/ocf-tester.in: line 226: cd: @datadir@/resource-agents: No such file or directory
|
|
|
05afe3 |
close failed in file object destructor:
|
|
|
05afe3 |
sys.excepthook is missing
|
|
|
05afe3 |
lost sys.stderr
|
|
|
05afe3 |
* rc=1: Your agent produces meta-data which does not conform to ra-api-1.dtd
|
|
|
05afe3 |
Tests failed: heartbeat/gcp-vpc-move-vip.in failed 1 tests
|
|
|
05afe3 |
|
|
|
05afe3 |
The only test faillig is the meta-data, but all the agents that I tried
|
|
|
05afe3 |
also fails on this. If this is a concern, could you please point me out
|
|
|
05afe3 |
to a test which succeeds so I can check what I am doing differently?
|
|
|
05afe3 |
|
|
|
05afe3 |
This commit can also be viewed at:
|
|
|
05afe3 |
https://github.com/collabora-gce/resource-agents/tree/alias
|
|
|
05afe3 |
|
|
|
05afe3 |
Thanks
|
|
|
05afe3 |
---
|
|
|
05afe3 |
configure.ac | 1 +
|
|
|
05afe3 |
doc/man/Makefile.am | 1 +
|
|
|
05afe3 |
heartbeat/Makefile.am | 1 +
|
|
|
05afe3 |
heartbeat/gcp-vpc-move-vip.in | 299 ++++++++++++++++++++++++++++++++++++++++++
|
|
|
05afe3 |
4 files changed, 302 insertions(+)
|
|
|
05afe3 |
create mode 100755 heartbeat/gcp-vpc-move-vip.in
|
|
|
05afe3 |
|
|
|
05afe3 |
diff --git a/configure.ac b/configure.ac
|
|
|
05afe3 |
index bdf057d33..3d8f9ca74 100644
|
|
|
05afe3 |
--- a/configure.ac
|
|
|
05afe3 |
+++ b/configure.ac
|
|
|
05afe3 |
@@ -959,6 +959,7 @@ AC_CONFIG_FILES([heartbeat/dnsupdate], [chmod +x heartbeat/dnsupdate])
|
|
|
05afe3 |
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/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 c59126d13..e9eaf369f 100644
|
|
|
05afe3 |
--- a/doc/man/Makefile.am
|
|
|
05afe3 |
+++ b/doc/man/Makefile.am
|
|
|
05afe3 |
@@ -114,6 +114,7 @@ man_MANS = ocf_heartbeat_AoEtarget.7 \
|
|
|
05afe3 |
ocf_heartbeat_galera.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_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 4f5059e27..36b271956 100644
|
|
|
05afe3 |
--- a/heartbeat/Makefile.am
|
|
|
05afe3 |
+++ b/heartbeat/Makefile.am
|
|
|
05afe3 |
@@ -111,6 +111,7 @@ ocf_SCRIPTS = AoEtarget \
|
|
|
05afe3 |
galera \
|
|
|
05afe3 |
garbd \
|
|
|
05afe3 |
gcp-vpc-move-ip \
|
|
|
05afe3 |
+ gcp-vpc-move-vip \
|
|
|
05afe3 |
iSCSILogicalUnit \
|
|
|
05afe3 |
iSCSITarget \
|
|
|
05afe3 |
ids \
|
|
|
05afe3 |
diff --git a/heartbeat/gcp-vpc-move-vip.in b/heartbeat/gcp-vpc-move-vip.in
|
|
|
05afe3 |
new file mode 100755
|
|
|
05afe3 |
index 000000000..4954e11df
|
|
|
05afe3 |
--- /dev/null
|
|
|
05afe3 |
+++ b/heartbeat/gcp-vpc-move-vip.in
|
|
|
05afe3 |
@@ -0,0 +1,299 @@
|
|
|
05afe3 |
+#!/usr/bin/env python
|
|
|
05afe3 |
+# ---------------------------------------------------------------------
|
|
|
05afe3 |
+# Copyright 2016 Google Inc.
|
|
|
05afe3 |
+#
|
|
|
05afe3 |
+# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
05afe3 |
+# you may not use this file except in compliance with the License.
|
|
|
05afe3 |
+# You may obtain a copy of the License at
|
|
|
05afe3 |
+#
|
|
|
05afe3 |
+# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
05afe3 |
+# Unless required by applicable law or agreed to in writing, software
|
|
|
05afe3 |
+# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
05afe3 |
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
05afe3 |
+# See the License for the specific language governing permissions and
|
|
|
05afe3 |
+# limitations under the License.
|
|
|
05afe3 |
+# ---------------------------------------------------------------------
|
|
|
05afe3 |
+# Description: Google Cloud Platform - Floating IP Address (Alias)
|
|
|
05afe3 |
+# ---------------------------------------------------------------------
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+import json
|
|
|
05afe3 |
+import logging
|
|
|
05afe3 |
+import os
|
|
|
05afe3 |
+import sys
|
|
|
05afe3 |
+import time
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+import googleapiclient.discovery
|
|
|
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 |
+CONN = None
|
|
|
05afe3 |
+THIS_VM = None
|
|
|
05afe3 |
+OCF_SUCCESS = 0
|
|
|
05afe3 |
+OCF_ERR_GENERIC = 1
|
|
|
05afe3 |
+OCF_ERR_CONFIGURED = 6
|
|
|
05afe3 |
+OCF_NOT_RUNNING = 7
|
|
|
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-vip">
|
|
|
05afe3 |
+ <version>1.0</version>
|
|
|
05afe3 |
+ <longdesc lang="en">Floating IP Address on Google Cloud Platform - Using Alias IP address functionality to attach a secondary IP address to a running instance</longdesc>
|
|
|
05afe3 |
+ <shortdesc lang="en">Floating IP Address on Google Cloud Platform</shortdesc>
|
|
|
05afe3 |
+ <parameters>
|
|
|
05afe3 |
+ <parameter name="hostlist" unique="1" required="1">
|
|
|
05afe3 |
+ <longdesc lang="en">List of hosts in the cluster</longdesc>
|
|
|
05afe3 |
+ <shortdesc lang="en">Host list</shortdesc>
|
|
|
05afe3 |
+ <content type="string" default="" />
|
|
|
05afe3 |
+ </parameter>
|
|
|
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 |
+ <parameter name="alias_ip" unique="1" required="1">
|
|
|
05afe3 |
+ <longdesc lang="en">IP Address to be added including CIDR. E.g 192.168.0.1/32</longdesc>
|
|
|
05afe3 |
+ <shortdesc lang="en">IP Address to be added including CIDR. E.g 192.168.0.1/32</shortdesc>
|
|
|
05afe3 |
+ <content type="string" default="" />
|
|
|
05afe3 |
+ </parameter>
|
|
|
05afe3 |
+ <parameter name="alias_range_name" unique="1" required="0">
|
|
|
05afe3 |
+ <longdesc lang="en">Subnet name for the Alias IP2</longdesc>
|
|
|
05afe3 |
+ <shortdesc lang="en">Subnet name for the Alias IP</shortdesc>
|
|
|
05afe3 |
+ <content type="string" default="" />
|
|
|
05afe3 |
+ </parameter>
|
|
|
05afe3 |
+ </parameters>
|
|
|
05afe3 |
+ <actions>
|
|
|
05afe3 |
+ <action name="start" timeout="300" />
|
|
|
05afe3 |
+ <action name="stop" timeout="15" />
|
|
|
05afe3 |
+ <action name="monitor" timeout="15" interval="60" depth="0" />
|
|
|
05afe3 |
+ <action name="meta-data" timeout="15" />
|
|
|
05afe3 |
+ </actions>
|
|
|
05afe3 |
+</resource-agent>'''
|
|
|
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 get_instance(project, zone, instance):
|
|
|
05afe3 |
+ request = CONN.instances().get(
|
|
|
05afe3 |
+ project=project, zone=zone, instance=instance)
|
|
|
05afe3 |
+ return request.execute()
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def get_network_ifaces(project, zone, instance):
|
|
|
05afe3 |
+ return get_instance(project, zone, instance)['networkInterfaces']
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def wait_for_operation(project, zone, operation):
|
|
|
05afe3 |
+ while True:
|
|
|
05afe3 |
+ result = CONN.zoneOperations().get(
|
|
|
05afe3 |
+ project=project,
|
|
|
05afe3 |
+ zone=zone,
|
|
|
05afe3 |
+ operation=operation['name']).execute()
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ if result['status'] == 'DONE':
|
|
|
05afe3 |
+ if 'error' in result:
|
|
|
05afe3 |
+ raise Exception(result['error'])
|
|
|
05afe3 |
+ return
|
|
|
05afe3 |
+ time.sleep(1)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def set_alias(project, zone, instance, alias, alias_range_name=None):
|
|
|
05afe3 |
+ fingerprint = get_network_ifaces(project, zone, instance)[0]['fingerprint']
|
|
|
05afe3 |
+ body = {
|
|
|
05afe3 |
+ 'aliasIpRanges': [],
|
|
|
05afe3 |
+ 'fingerprint': fingerprint
|
|
|
05afe3 |
+ }
|
|
|
05afe3 |
+ if alias:
|
|
|
05afe3 |
+ obj = {'ipCidrRange': alias}
|
|
|
05afe3 |
+ if alias_range_name:
|
|
|
05afe3 |
+ obj['subnetworkRangeName'] = alias_range_name
|
|
|
05afe3 |
+ body['aliasIpRanges'].append(obj)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ request = CONN.instances().updateNetworkInterface(
|
|
|
05afe3 |
+ instance=instance, networkInterface='nic0', project=project, zone=zone,
|
|
|
05afe3 |
+ body=body)
|
|
|
05afe3 |
+ operation = request.execute()
|
|
|
05afe3 |
+ wait_for_operation(project, zone, operation)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def get_alias(project, zone, instance):
|
|
|
05afe3 |
+ iface = get_network_ifaces(project, zone, instance)
|
|
|
05afe3 |
+ try:
|
|
|
05afe3 |
+ return iface[0]['aliasIpRanges'][0]['ipCidrRange']
|
|
|
05afe3 |
+ except KeyError:
|
|
|
05afe3 |
+ return ''
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def get_localhost_alias():
|
|
|
05afe3 |
+ net_iface = get_metadata('instance/network-interfaces', {'recursive': True})
|
|
|
05afe3 |
+ net_iface = json.loads(net_iface.decode('utf-8'))
|
|
|
05afe3 |
+ try:
|
|
|
05afe3 |
+ return net_iface[0]['ipAliases'][0]
|
|
|
05afe3 |
+ except (KeyError, IndexError):
|
|
|
05afe3 |
+ return ''
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def get_zone(project, instance):
|
|
|
05afe3 |
+ request = CONN.instances().aggregatedList(project=project)
|
|
|
05afe3 |
+ while request is not None:
|
|
|
05afe3 |
+ response = request.execute()
|
|
|
05afe3 |
+ zones = response.get('items', {})
|
|
|
05afe3 |
+ for zone in zones.values():
|
|
|
05afe3 |
+ for inst in zone.get('instances', []):
|
|
|
05afe3 |
+ if inst['name'] == instance:
|
|
|
05afe3 |
+ return inst['zone'].split("/")[-1]
|
|
|
05afe3 |
+ request = CONN.instances().aggregatedList_next(
|
|
|
05afe3 |
+ previous_request=request, previous_response=response)
|
|
|
05afe3 |
+ raise Exception("Unable to find instance %s" % (instance))
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def gcp_alias_start(alias):
|
|
|
05afe3 |
+ if not alias:
|
|
|
05afe3 |
+ sys.exit(OCF_ERR_CONFIGURED)
|
|
|
05afe3 |
+ my_alias = get_localhost_alias()
|
|
|
05afe3 |
+ my_zone = get_metadata('instance/zone').split('/')[-1]
|
|
|
05afe3 |
+ project = get_metadata('project/project-id')
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ # If I already have the IP, exit. If it has an alias IP that isn't the VIP,
|
|
|
05afe3 |
+ # then remove it
|
|
|
05afe3 |
+ if my_alias == alias:
|
|
|
05afe3 |
+ logging.info(
|
|
|
05afe3 |
+ '%s already has %s attached. No action required' % (THIS_VM, alias))
|
|
|
05afe3 |
+ sys.exit(OCF_SUCCESS)
|
|
|
05afe3 |
+ elif my_alias:
|
|
|
05afe3 |
+ logging.info('Removing %s from %s' % (my_alias, THIS_VM))
|
|
|
05afe3 |
+ set_alias(project, my_zone, THIS_VM, '')
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ # Loops through all hosts & remove the alias IP from the host that has it
|
|
|
05afe3 |
+ hostlist = os.environ.get('OCF_RESKEY_hostlist', '')
|
|
|
05afe3 |
+ hostlist.replace(THIS_VM, '')
|
|
|
05afe3 |
+ for host in hostlist.split():
|
|
|
05afe3 |
+ host_zone = get_zone(project, host)
|
|
|
05afe3 |
+ host_alias = get_alias(project, host_zone, host)
|
|
|
05afe3 |
+ if alias == host_alias:
|
|
|
05afe3 |
+ logging.info(
|
|
|
05afe3 |
+ '%s is attached to %s - Removing all alias IP addresses from %s' %
|
|
|
05afe3 |
+ (alias, host, host))
|
|
|
05afe3 |
+ set_alias(project, host_zone, host, '')
|
|
|
05afe3 |
+ break
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ # add alias IP to localhost
|
|
|
05afe3 |
+ set_alias(
|
|
|
05afe3 |
+ project, my_zone, THIS_VM, alias,
|
|
|
05afe3 |
+ os.environ.get('OCF_RESKEY_alias_range_name'))
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ # Check the IP has been added
|
|
|
05afe3 |
+ my_alias = get_localhost_alias()
|
|
|
05afe3 |
+ if alias == my_alias:
|
|
|
05afe3 |
+ logging.info('Finished adding %s to %s' % (alias, THIS_VM))
|
|
|
05afe3 |
+ elif my_alias:
|
|
|
05afe3 |
+ logging.error(
|
|
|
05afe3 |
+ 'Failed to add IP. %s has an IP attached but it isn\'t %s' %
|
|
|
05afe3 |
+ (THIS_VM, alias))
|
|
|
05afe3 |
+ sys.exit(OCF_ERR_GENERIC)
|
|
|
05afe3 |
+ else:
|
|
|
05afe3 |
+ logging.error('Failed to add IP address %s to %s' % (alias, THIS_VM))
|
|
|
05afe3 |
+ sys.exit(OCF_ERR_GENERIC)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def gcp_alias_stop(alias):
|
|
|
05afe3 |
+ if not alias:
|
|
|
05afe3 |
+ sys.exit(OCF_ERR_CONFIGURED)
|
|
|
05afe3 |
+ my_alias = get_localhost_alias()
|
|
|
05afe3 |
+ my_zone = get_metadata('instance/zone').split('/')[-1]
|
|
|
05afe3 |
+ project = get_metadata('project/project-id')
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ if my_alias == alias:
|
|
|
05afe3 |
+ logging.info('Removing %s from %s' % (my_alias, THIS_VM))
|
|
|
05afe3 |
+ set_alias(project, my_zone, THIS_VM, '')
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def gcp_alias_status(alias):
|
|
|
05afe3 |
+ if not alias:
|
|
|
05afe3 |
+ sys.exit(OCF_ERR_CONFIGURED)
|
|
|
05afe3 |
+ my_alias = get_localhost_alias()
|
|
|
05afe3 |
+ if alias == my_alias:
|
|
|
05afe3 |
+ logging.info('%s has the correct IP address attached' % THIS_VM)
|
|
|
05afe3 |
+ else:
|
|
|
05afe3 |
+ sys.exit(OCF_NOT_RUNNING)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def configure():
|
|
|
05afe3 |
+ global CONN
|
|
|
05afe3 |
+ global THIS_VM
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ # Populate global vars
|
|
|
05afe3 |
+ CONN = googleapiclient.discovery.build('compute', 'v1')
|
|
|
05afe3 |
+ THIS_VM = get_metadata('instance/name')
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ # Prepare logging
|
|
|
05afe3 |
+ logging.basicConfig(
|
|
|
05afe3 |
+ format='gcp:alias - %(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=THIS_VM)
|
|
|
05afe3 |
+ handler.setLevel(logging.INFO)
|
|
|
05afe3 |
+ formatter = logging.Formatter('gcp:alias "%(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 |
+ configure()
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ alias = os.environ.get('OCF_RESKEY_alias_ip')
|
|
|
05afe3 |
+ if 'start' in sys.argv[1]:
|
|
|
05afe3 |
+ gcp_alias_start(alias)
|
|
|
05afe3 |
+ elif 'stop' in sys.argv[1]:
|
|
|
05afe3 |
+ gcp_alias_stop(alias)
|
|
|
05afe3 |
+ elif 'status' in sys.argv[1] or 'monitor' in sys.argv[1]:
|
|
|
05afe3 |
+ gcp_alias_status(alias)
|
|
|
05afe3 |
+ elif 'meta-data' in sys.argv[1]:
|
|
|
05afe3 |
+ print(METADATA)
|
|
|
05afe3 |
+ else:
|
|
|
05afe3 |
+ logging.error('gcp:alias - no such function %s' % str(sys.argv[1]))
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+if __name__ == "__main__":
|
|
|
05afe3 |
+ main()
|
|
|
05afe3 |
|
|
|
05afe3 |
From 0e6ba4894a748664ac1d8ff5b9e8c271f0b04d93 Mon Sep 17 00:00:00 2001
|
|
|
05afe3 |
From: Helen Koike <helen.koike@collabora.com>
|
|
|
05afe3 |
Date: Thu, 12 Jul 2018 09:01:22 -0300
|
|
|
05afe3 |
Subject: [PATCH 2/4] gcp-vpc-move-vip.in: minor fixes
|
|
|
05afe3 |
|
|
|
05afe3 |
- Get hostlist from the project if the parameter is not given
|
|
|
05afe3 |
- Verify if alias is present out of each action function
|
|
|
05afe3 |
- Don't call configure if 'meta-data' action is given
|
|
|
05afe3 |
---
|
|
|
05afe3 |
heartbeat/gcp-vpc-move-vip.in | 40 ++++++++++++++++++++++++++++------------
|
|
|
05afe3 |
1 file changed, 28 insertions(+), 12 deletions(-)
|
|
|
05afe3 |
|
|
|
05afe3 |
diff --git a/heartbeat/gcp-vpc-move-vip.in b/heartbeat/gcp-vpc-move-vip.in
|
|
|
05afe3 |
index 4954e11df..f3d117bda 100755
|
|
|
05afe3 |
--- a/heartbeat/gcp-vpc-move-vip.in
|
|
|
05afe3 |
+++ b/heartbeat/gcp-vpc-move-vip.in
|
|
|
05afe3 |
@@ -50,7 +50,7 @@ METADATA = \
|
|
|
05afe3 |
<longdesc lang="en">Floating IP Address on Google Cloud Platform - Using Alias IP address functionality to attach a secondary IP address to a running instance</longdesc>
|
|
|
05afe3 |
<shortdesc lang="en">Floating IP Address on Google Cloud Platform</shortdesc>
|
|
|
05afe3 |
<parameters>
|
|
|
05afe3 |
- <parameter name="hostlist" unique="1" required="1">
|
|
|
05afe3 |
+ <parameter name="hostlist" unique="1" required="0">
|
|
|
05afe3 |
<longdesc lang="en">List of hosts in the cluster</longdesc>
|
|
|
05afe3 |
<shortdesc lang="en">Host list</shortdesc>
|
|
|
05afe3 |
<content type="string" default="" />
|
|
|
05afe3 |
@@ -177,9 +177,22 @@ def get_zone(project, instance):
|
|
|
05afe3 |
raise Exception("Unable to find instance %s" % (instance))
|
|
|
05afe3 |
|
|
|
05afe3 |
|
|
|
05afe3 |
+def get_instances_list(project, exclude):
|
|
|
05afe3 |
+ hostlist = []
|
|
|
05afe3 |
+ request = CONN.instances().aggregatedList(project=project)
|
|
|
05afe3 |
+ while request is not None:
|
|
|
05afe3 |
+ response = request.execute()
|
|
|
05afe3 |
+ zones = response.get('items', {})
|
|
|
05afe3 |
+ for zone in zones.values():
|
|
|
05afe3 |
+ for inst in zone.get('instances', []):
|
|
|
05afe3 |
+ if inst['name'] != exclude:
|
|
|
05afe3 |
+ hostlist.append(inst['name'])
|
|
|
05afe3 |
+ request = CONN.instances().aggregatedList_next(
|
|
|
05afe3 |
+ previous_request=request, previous_response=response)
|
|
|
05afe3 |
+ return hostlist
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
def gcp_alias_start(alias):
|
|
|
05afe3 |
- if not alias:
|
|
|
05afe3 |
- sys.exit(OCF_ERR_CONFIGURED)
|
|
|
05afe3 |
my_alias = get_localhost_alias()
|
|
|
05afe3 |
my_zone = get_metadata('instance/zone').split('/')[-1]
|
|
|
05afe3 |
project = get_metadata('project/project-id')
|
|
|
05afe3 |
@@ -196,8 +209,11 @@ def gcp_alias_start(alias):
|
|
|
05afe3 |
|
|
|
05afe3 |
# Loops through all hosts & remove the alias IP from the host that has it
|
|
|
05afe3 |
hostlist = os.environ.get('OCF_RESKEY_hostlist', '')
|
|
|
05afe3 |
- hostlist.replace(THIS_VM, '')
|
|
|
05afe3 |
- for host in hostlist.split():
|
|
|
05afe3 |
+ if hostlist:
|
|
|
05afe3 |
+ hostlist.replace(THIS_VM, '').split()
|
|
|
05afe3 |
+ else:
|
|
|
05afe3 |
+ hostlist = get_instances_list(project, THIS_VM)
|
|
|
05afe3 |
+ for host in hostlist:
|
|
|
05afe3 |
host_zone = get_zone(project, host)
|
|
|
05afe3 |
host_alias = get_alias(project, host_zone, host)
|
|
|
05afe3 |
if alias == host_alias:
|
|
|
05afe3 |
@@ -227,8 +243,6 @@ def gcp_alias_start(alias):
|
|
|
05afe3 |
|
|
|
05afe3 |
|
|
|
05afe3 |
def gcp_alias_stop(alias):
|
|
|
05afe3 |
- if not alias:
|
|
|
05afe3 |
- sys.exit(OCF_ERR_CONFIGURED)
|
|
|
05afe3 |
my_alias = get_localhost_alias()
|
|
|
05afe3 |
my_zone = get_metadata('instance/zone').split('/')[-1]
|
|
|
05afe3 |
project = get_metadata('project/project-id')
|
|
|
05afe3 |
@@ -239,8 +253,6 @@ def gcp_alias_stop(alias):
|
|
|
05afe3 |
|
|
|
05afe3 |
|
|
|
05afe3 |
def gcp_alias_status(alias):
|
|
|
05afe3 |
- if not alias:
|
|
|
05afe3 |
- sys.exit(OCF_ERR_CONFIGURED)
|
|
|
05afe3 |
my_alias = get_localhost_alias()
|
|
|
05afe3 |
if alias == my_alias:
|
|
|
05afe3 |
logging.info('%s has the correct IP address attached' % THIS_VM)
|
|
|
05afe3 |
@@ -280,17 +292,21 @@ def configure():
|
|
|
05afe3 |
|
|
|
05afe3 |
|
|
|
05afe3 |
def main():
|
|
|
05afe3 |
- configure()
|
|
|
05afe3 |
+ if 'meta-data' in sys.argv[1]:
|
|
|
05afe3 |
+ print(METADATA)
|
|
|
05afe3 |
+ return
|
|
|
05afe3 |
|
|
|
05afe3 |
alias = os.environ.get('OCF_RESKEY_alias_ip')
|
|
|
05afe3 |
+ if not alias:
|
|
|
05afe3 |
+ sys.exit(OCF_ERR_CONFIGURED)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+ configure()
|
|
|
05afe3 |
if 'start' in sys.argv[1]:
|
|
|
05afe3 |
gcp_alias_start(alias)
|
|
|
05afe3 |
elif 'stop' in sys.argv[1]:
|
|
|
05afe3 |
gcp_alias_stop(alias)
|
|
|
05afe3 |
elif 'status' in sys.argv[1] or 'monitor' in sys.argv[1]:
|
|
|
05afe3 |
gcp_alias_status(alias)
|
|
|
05afe3 |
- elif 'meta-data' in sys.argv[1]:
|
|
|
05afe3 |
- print(METADATA)
|
|
|
05afe3 |
else:
|
|
|
05afe3 |
logging.error('gcp:alias - no such function %s' % str(sys.argv[1]))
|
|
|
05afe3 |
|
|
|
05afe3 |
|
|
|
05afe3 |
From 1f50c4bc80f23f561a8630c12076707366525899 Mon Sep 17 00:00:00 2001
|
|
|
05afe3 |
From: Helen Koike <helen.koike@collabora.com>
|
|
|
05afe3 |
Date: Thu, 12 Jul 2018 13:02:16 -0300
|
|
|
05afe3 |
Subject: [PATCH 3/4] gcp-vcp-move-vip.in: implement validate-all
|
|
|
05afe3 |
|
|
|
05afe3 |
Also fix some return errors
|
|
|
05afe3 |
---
|
|
|
05afe3 |
heartbeat/gcp-vpc-move-vip.in | 47 +++++++++++++++++++++++++++++++------------
|
|
|
05afe3 |
1 file changed, 34 insertions(+), 13 deletions(-)
|
|
|
05afe3 |
|
|
|
05afe3 |
diff --git a/heartbeat/gcp-vpc-move-vip.in b/heartbeat/gcp-vpc-move-vip.in
|
|
|
05afe3 |
index f3d117bda..a90c2de8d 100755
|
|
|
05afe3 |
--- a/heartbeat/gcp-vpc-move-vip.in
|
|
|
05afe3 |
+++ b/heartbeat/gcp-vpc-move-vip.in
|
|
|
05afe3 |
@@ -22,7 +22,10 @@ import os
|
|
|
05afe3 |
import sys
|
|
|
05afe3 |
import time
|
|
|
05afe3 |
|
|
|
05afe3 |
-import googleapiclient.discovery
|
|
|
05afe3 |
+try:
|
|
|
05afe3 |
+ import googleapiclient.discovery
|
|
|
05afe3 |
+except ImportError:
|
|
|
05afe3 |
+ pass
|
|
|
05afe3 |
|
|
|
05afe3 |
if sys.version_info >= (3, 0):
|
|
|
05afe3 |
# Python 3 imports.
|
|
|
05afe3 |
@@ -36,6 +39,7 @@ else:
|
|
|
05afe3 |
|
|
|
05afe3 |
CONN = None
|
|
|
05afe3 |
THIS_VM = None
|
|
|
05afe3 |
+ALIAS = None
|
|
|
05afe3 |
OCF_SUCCESS = 0
|
|
|
05afe3 |
OCF_ERR_GENERIC = 1
|
|
|
05afe3 |
OCF_ERR_CONFIGURED = 6
|
|
|
05afe3 |
@@ -210,7 +214,7 @@ def gcp_alias_start(alias):
|
|
|
05afe3 |
# Loops through all hosts & remove the alias IP from the host that has it
|
|
|
05afe3 |
hostlist = os.environ.get('OCF_RESKEY_hostlist', '')
|
|
|
05afe3 |
if hostlist:
|
|
|
05afe3 |
- hostlist.replace(THIS_VM, '').split()
|
|
|
05afe3 |
+ hostlist = hostlist.replace(THIS_VM, '').split()
|
|
|
05afe3 |
else:
|
|
|
05afe3 |
hostlist = get_instances_list(project, THIS_VM)
|
|
|
05afe3 |
for host in hostlist:
|
|
|
05afe3 |
@@ -260,14 +264,31 @@ def gcp_alias_status(alias):
|
|
|
05afe3 |
sys.exit(OCF_NOT_RUNNING)
|
|
|
05afe3 |
|
|
|
05afe3 |
|
|
|
05afe3 |
-def configure():
|
|
|
05afe3 |
+def validate():
|
|
|
05afe3 |
+ global ALIAS
|
|
|
05afe3 |
global CONN
|
|
|
05afe3 |
global THIS_VM
|
|
|
05afe3 |
|
|
|
05afe3 |
# Populate global vars
|
|
|
05afe3 |
- CONN = googleapiclient.discovery.build('compute', 'v1')
|
|
|
05afe3 |
- THIS_VM = get_metadata('instance/name')
|
|
|
05afe3 |
+ try:
|
|
|
05afe3 |
+ 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 |
+ try:
|
|
|
05afe3 |
+ THIS_VM = get_metadata('instance/name')
|
|
|
05afe3 |
+ except Exception as e:
|
|
|
05afe3 |
+ logging.error('Couldn\'t get instance name, is this running inside GCE?: ' + str(e))
|
|
|
05afe3 |
+ sys.exit(OCF_ERR_CONFIGURED)
|
|
|
05afe3 |
|
|
|
05afe3 |
+ ALIAS = os.environ.get('OCF_RESKEY_alias_ip')
|
|
|
05afe3 |
+ if not ALIAS:
|
|
|
05afe3 |
+ logging.error('Missing alias_ip parameter')
|
|
|
05afe3 |
+ sys.exit(OCF_ERR_CONFIGURED)
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+
|
|
|
05afe3 |
+def configure_logs():
|
|
|
05afe3 |
# Prepare logging
|
|
|
05afe3 |
logging.basicConfig(
|
|
|
05afe3 |
format='gcp:alias - %(levelname)s - %(message)s', level=logging.INFO)
|
|
|
05afe3 |
@@ -296,19 +317,19 @@ def main():
|
|
|
05afe3 |
print(METADATA)
|
|
|
05afe3 |
return
|
|
|
05afe3 |
|
|
|
05afe3 |
- alias = os.environ.get('OCF_RESKEY_alias_ip')
|
|
|
05afe3 |
- if not alias:
|
|
|
05afe3 |
- sys.exit(OCF_ERR_CONFIGURED)
|
|
|
05afe3 |
+ validate()
|
|
|
05afe3 |
+ if 'validate-all' in sys.argv[1]:
|
|
|
05afe3 |
+ return
|
|
|
05afe3 |
|
|
|
05afe3 |
- configure()
|
|
|
05afe3 |
+ configure_logs()
|
|
|
05afe3 |
if 'start' in sys.argv[1]:
|
|
|
05afe3 |
- gcp_alias_start(alias)
|
|
|
05afe3 |
+ gcp_alias_start(ALIAS)
|
|
|
05afe3 |
elif 'stop' in sys.argv[1]:
|
|
|
05afe3 |
- gcp_alias_stop(alias)
|
|
|
05afe3 |
+ gcp_alias_stop(ALIAS)
|
|
|
05afe3 |
elif 'status' in sys.argv[1] or 'monitor' in sys.argv[1]:
|
|
|
05afe3 |
- gcp_alias_status(alias)
|
|
|
05afe3 |
+ gcp_alias_status(ALIAS)
|
|
|
05afe3 |
else:
|
|
|
05afe3 |
- logging.error('gcp:alias - no such function %s' % str(sys.argv[1]))
|
|
|
05afe3 |
+ logging.error('no such function %s' % str(sys.argv[1]))
|
|
|
05afe3 |
|
|
|
05afe3 |
|
|
|
05afe3 |
if __name__ == "__main__":
|
|
|
05afe3 |
|
|
|
05afe3 |
From f11cb236bb348ebee74e962d0ded1cb2fc97bd5f Mon Sep 17 00:00:00 2001
|
|
|
05afe3 |
From: Helen Koike <helen.koike@collabora.com>
|
|
|
05afe3 |
Date: Fri, 13 Jul 2018 08:01:02 -0300
|
|
|
05afe3 |
Subject: [PATCH 4/4] gcp-vpc-move-vip.in: minor fixes
|
|
|
05afe3 |
|
|
|
05afe3 |
---
|
|
|
05afe3 |
heartbeat/gcp-vpc-move-vip.in | 5 +++--
|
|
|
05afe3 |
1 file changed, 3 insertions(+), 2 deletions(-)
|
|
|
05afe3 |
|
|
|
05afe3 |
diff --git a/heartbeat/gcp-vpc-move-vip.in b/heartbeat/gcp-vpc-move-vip.in
|
|
|
05afe3 |
index a90c2de8d..9fc87242f 100755
|
|
|
05afe3 |
--- a/heartbeat/gcp-vpc-move-vip.in
|
|
|
05afe3 |
+++ b/heartbeat/gcp-vpc-move-vip.in
|
|
|
05afe3 |
@@ -1,4 +1,4 @@
|
|
|
05afe3 |
-#!/usr/bin/env python
|
|
|
05afe3 |
+#!@PYTHON@ -tt
|
|
|
05afe3 |
# ---------------------------------------------------------------------
|
|
|
05afe3 |
# Copyright 2016 Google Inc.
|
|
|
05afe3 |
#
|
|
|
05afe3 |
@@ -59,7 +59,7 @@ METADATA = \
|
|
|
05afe3 |
<shortdesc lang="en">Host list</shortdesc>
|
|
|
05afe3 |
<content type="string" default="" />
|
|
|
05afe3 |
</parameter>
|
|
|
05afe3 |
- <parameter name="stackdriver-logging" unique="0" required="0">
|
|
|
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 |
@@ -80,6 +80,7 @@ METADATA = \
|
|
|
05afe3 |
<action name="stop" timeout="15" />
|
|
|
05afe3 |
<action name="monitor" timeout="15" interval="60" depth="0" />
|
|
|
05afe3 |
<action name="meta-data" timeout="15" />
|
|
|
05afe3 |
+ <action name="validate-all" timeout="15" />
|
|
|
05afe3 |
</actions>
|
|
|
05afe3 |
</resource-agent>'''
|
|
|
05afe3 |
|