Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/heartbeat/Makefile.am b/heartbeat/Makefile.am
index 6adc6bc3c..e7a3a4fac 100644
--- a/heartbeat/Makefile.am
+++ b/heartbeat/Makefile.am
@@ -1,198 +1,199 @@
# Makefile.am for OCF RAs
#
# Author: Sun Jing Dong
# Copyright (C) 2004 IBM
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
MAINTAINERCLEANFILES = Makefile.in
EXTRA_DIST = $(ocf_SCRIPTS) $(ocfcommon_DATA) \
$(common_DATA) $(hb_DATA) $(dtd_DATA) \
README
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/linux-ha
halibdir = $(libexecdir)/heartbeat
ocfdir = $(OCF_RA_DIR_PREFIX)/heartbeat
dtddir = $(datadir)/$(PACKAGE_NAME)
dtd_DATA = ra-api-1.dtd metadata.rng
if USE_IPV6ADDR_AGENT
ocf_PROGRAMS = IPv6addr
else
ocf_PROGRAMS =
endif
if IPV6ADDR_COMPATIBLE
halib_PROGRAMS = send_ua
else
halib_PROGRAMS =
endif
IPv6addr_SOURCES = IPv6addr.c IPv6addr_utils.c
send_ua_SOURCES = send_ua.c IPv6addr_utils.c
IPv6addr_LDADD = -lplumb $(LIBNETLIBS)
send_ua_LDADD = $(LIBNETLIBS)
ocf_SCRIPTS = AoEtarget \
AudibleAlarm \
ClusterMon \
CTDB \
Delay \
Dummy \
EvmsSCC \
Evmsd \
Filesystem \
ICP \
IPaddr \
IPaddr2 \
IPsrcaddr \
LVM \
LinuxSCSI \
lvmlockd \
LVM-activate \
MailTo \
ManageRAID \
ManageVE \
NodeUtilization \
Pure-FTPd \
Raid1 \
Route \
SAPDatabase \
SAPInstance \
SendArp \
ServeRAID \
SphinxSearchDaemon \
Squid \
Stateful \
SysInfo \
VIPArip \
VirtualDomain \
WAS \
WAS6 \
WinPopup \
Xen \
Xinetd \
ZFS \
aliyun-vpc-move-ip \
anything \
apache \
asterisk \
aws-vpc-move-ip \
aws-vpc-route53 \
awseip \
awsvip \
azure-lb \
clvm \
conntrackd \
db2 \
dhcpd \
dnsupdate \
docker \
eDir88 \
ethmonitor \
exportfs \
fio \
galera \
garbd \
gcp-vpc-move-ip \
gcp-vpc-move-vip \
gcp-vpc-move-route \
iSCSILogicalUnit \
iSCSITarget \
ids \
iface-bridge \
iface-vlan \
ipsec \
iscsi \
jboss \
jira \
kamailio \
lxc \
lxd-info \
machine-info \
mariadb \
minio \
mysql \
mysql-proxy \
nagios \
named \
nfsnotify \
nfsserver \
nginx \
openstack-cinder-volume \
openstack-floating-ip \
openstack-info \
oraasm \
oracle \
oralsnr \
ovsmonitor \
pgagent \
pgsql \
pingd \
portblock \
postfix \
pound \
proftpd \
rabbitmq-cluster \
redis \
rkt \
rsyncd \
rsyslog \
scsi2reservation \
sfex \
sg_persist \
mpathpersist \
slapd \
sybaseASE \
symlink \
syslog-ng \
tomcat \
varnish \
vmware \
vsftpd \
zabbixserver
ocfcommondir = $(OCF_LIB_DIR_PREFIX)/heartbeat
ocfcommon_DATA = ocf-shellfuncs \
ocf-binaries \
ocf-directories \
ocf-returncodes \
ocf-rarun \
ocf-distro \
apache-conf.sh \
http-mon.sh \
sapdb-nosha.sh \
sapdb.sh \
lvm-clvm.sh \
lvm-plain.sh \
lvm-tag.sh \
ora-common.sh \
mysql-common.sh \
nfsserver-redhat.sh \
- findif.sh
+ findif.sh \
+ ocf.py
# Legacy locations
hbdir = $(sysconfdir)/ha.d
hb_DATA = shellfuncs
check: $(ocf_SCRIPTS:=.check)
%.check: %
OCF_ROOT=$(abs_srcdir) OCF_FUNCTIONS_DIR=$(abs_srcdir) ./$< meta-data | xmllint --path $(abs_srcdir) --noout --relaxng $(abs_srcdir)/metadata.rng -
diff --git a/heartbeat/gcp-vpc-move-route.in b/heartbeat/gcp-vpc-move-route.in
index 566a70f86..125289d86 100644
--- a/heartbeat/gcp-vpc-move-route.in
+++ b/heartbeat/gcp-vpc-move-route.in
@@ -1,442 +1,440 @@
#!@PYTHON@ -tt
# - *- coding: utf- 8 - *-
#
#
# OCF resource agent to move an IP address within a VPC in GCP
#
# License: GNU General Public License (GPL)
# Copyright (c) 2018 Hervé Werner (MFG Labs)
# Copyright 2018 Google Inc.
# Based on code from Markus Guertler (aws-vpc-move-ip)
# 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.
#
#######################################################################
import atexit
import logging
import os
import sys
import time
+OCF_FUNCTIONS_DIR="%s/lib/heartbeat" % os.environ.get("OCF_ROOT")
+sys.path.append(OCF_FUNCTIONS_DIR)
+
+from ocf import *
+
try:
import googleapiclient.discovery
import pyroute2
except ImportError:
pass
if sys.version_info >= (3, 0):
# Python 3 imports.
import urllib.parse as urlparse
import urllib.request as urlrequest
else:
# Python 2 imports.
import urllib as urlparse
import urllib2 as urlrequest
-OCF_SUCCESS = 0
-OCF_ERR_GENERIC = 1
-OCF_ERR_UNIMPLEMENTED = 3
-OCF_ERR_PERM = 4
-OCF_ERR_CONFIGURED = 6
-OCF_NOT_RUNNING = 7
GCP_API_URL_PREFIX = 'https://www.googleapis.com/compute/v1'
METADATA_SERVER = 'http://metadata.google.internal/computeMetadata/v1/'
METADATA_HEADERS = {'Metadata-Flavor': 'Google'}
METADATA = \
'''<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="gcp-vpc-move-route">
<version>1.0</version>
<longdesc lang="en">
Resource Agent that can move a floating IP addresse within a GCP VPC by changing an
entry in the routing table. This agent also configures the floating IP locally
on the instance OS.
Requirements :
- IP forwarding must be enabled on all instances in order to be able to
terminate the route
- The floating IP address must be choosen so that it is outside all existing
subnets in the VPC network
- IAM permissions
(see https://cloud.google.com/compute/docs/access/iam-permissions) :
1) compute.routes.delete, compute.routes.get and compute.routes.update on the
route
2) compute.networks.updatePolicy on the network (to add a new route)
3) compute.networks.get on the network (to check the VPC network existence)
4) compute.routes.list on the project (to check conflicting routes)
</longdesc>
<shortdesc lang="en">Move IP within a GCP VPC</shortdesc>
<parameters>
<parameter name="ip" unique="1" required="1">
<longdesc lang="en">
Floating IP address. Note that this IP must be chosen outside of all existing
subnet ranges
</longdesc>
<shortdesc lang="en">Floating IP</shortdesc>
<content type="string" />
</parameter>
<parameter name="vpc_network" required="1">
<longdesc lang="en">
Name of the VPC network
</longdesc>
<shortdesc lang="en">VPC network</shortdesc>
<content type="string" default="default" />
</parameter>
<parameter name="interface">
<longdesc lang="en">
Name of the network interface
</longdesc>
<shortdesc lang="en">Network interface name</shortdesc>
<content type="string" default="eth0" />
</parameter>
<parameter name="route_name" unique="1">
<longdesc lang="en">
Route name
</longdesc>
<shortdesc lang="en">Route name</shortdesc>
<content type="string" default="ra-%s" />
</parameter>
<parameter name="stackdriver_logging" unique="0" required="0">
<longdesc lang="en">If enabled (set to true), IP failover logs will be posted to stackdriver logging</longdesc>
<shortdesc lang="en">Stackdriver-logging support</shortdesc>
<content type="boolean" default="" />
</parameter>
</parameters>
<actions>
<action name="start" timeout="180s" />
<action name="stop" timeout="180s" />
<action name="monitor" depth="0" timeout="30s" interval="60s" />
<action name="validate-all" timeout="5s" />
<action name="meta-data" timeout="5s" />
</actions>
</resource-agent>
''' % os.path.basename(sys.argv[0])
class Context(object):
__slots__ = 'conn', 'iface_idx', 'instance', 'instance_url', 'interface', \
'ip', 'iproute', 'project', 'route_name', 'vpc_network', \
'vpc_network_url', 'zone'
def wait_for_operation(ctx, response):
"""Blocks until operation completes.
Code from GitHub's GoogleCloudPlatform/python-docs-samples
Args:
response: dict, a request's response
"""
def _OperationGetter(response):
operation = response[u'name']
if response.get(u'zone'):
return ctx.conn.zoneOperations().get(
project=ctx.project, zone=ctx.zone, operation=operation)
else:
return ctx.conn.globalOperations().get(
project=ctx.project, operation=operation)
while True:
result = _OperationGetter(response).execute()
if result['status'] == 'DONE':
if 'error' in result:
raise Exception(result['error'])
return result
time.sleep(1)
def get_metadata(metadata_key, params=None, timeout=None):
"""Performs a GET request with the metadata headers.
Args:
metadata_key: string, the metadata to perform a GET request on.
params: dictionary, the query parameters in the GET request.
timeout: int, timeout in seconds for metadata requests.
Returns:
HTTP response from the GET request.
Raises:
urlerror.HTTPError: raises when the GET request fails.
"""
timeout = timeout or 60
metadata_url = os.path.join(METADATA_SERVER, metadata_key)
params = urlparse.urlencode(params or {})
url = '%s?%s' % (metadata_url, params)
request = urlrequest.Request(url, headers=METADATA_HEADERS)
request_opener = urlrequest.build_opener(urlrequest.ProxyHandler({}))
return request_opener.open(request, timeout=timeout * 1.1).read()
def validate(ctx):
if os.geteuid() != 0:
- logging.error('You must run this agent as root')
+ logger.error('You must run this agent as root')
sys.exit(OCF_ERR_PERM)
try:
ctx.conn = googleapiclient.discovery.build('compute', 'v1')
except Exception as e:
- logging.error('Couldn\'t connect with google api: ' + str(e))
+ logger.error('Couldn\'t connect with google api: ' + str(e))
sys.exit(OCF_ERR_CONFIGURED)
ctx.ip = os.environ.get('OCF_RESKEY_ip')
if not ctx.ip:
- logging.error('Missing ip parameter')
+ logger.error('Missing ip parameter')
sys.exit(OCF_ERR_CONFIGURED)
try:
ctx.instance = get_metadata('instance/name')
ctx.zone = get_metadata('instance/zone').split('/')[-1]
ctx.project = get_metadata('project/project-id')
except Exception as e:
- logging.error(
+ logger.error(
'Instance information not found. Is this a GCE instance ?: %s', str(e))
sys.exit(OCF_ERR_CONFIGURED)
ctx.instance_url = '%s/projects/%s/zones/%s/instances/%s' % (
GCP_API_URL_PREFIX, ctx.project, ctx.zone, ctx.instance)
ctx.vpc_network = os.environ.get('OCF_RESKEY_vpc_network', 'default')
ctx.vpc_network_url = '%s/projects/%s/global/networks/%s' % (
GCP_API_URL_PREFIX, ctx.project, ctx.vpc_network)
ctx.interface = os.environ.get('OCF_RESKEY_interface', 'eth0')
ctx.route_name = os.environ.get(
'OCF_RESKEY_route_name', 'ra-%s' % os.path.basename(sys.argv[0]))
ctx.iproute = pyroute2.IPRoute()
atexit.register(ctx.iproute.close)
idxs = ctx.iproute.link_lookup(ifname=ctx.interface)
if not idxs:
- logging.error('Network interface not found')
+ logger.error('Network interface not found')
sys.exit(OCF_ERR_CONFIGURED)
ctx.iface_idx = idxs[0]
def check_conflicting_routes(ctx):
fl = '(destRange = "%s*") AND (network = "%s") AND (name != "%s")' % (
ctx.ip, ctx.vpc_network_url, ctx.route_name)
request = ctx.conn.routes().list(project=ctx.project, filter=fl)
response = request.execute()
route_list = response.get('items', None)
if route_list:
- logging.error(
+ logger.error(
'Conflicting unnmanaged routes for destination %s/32 in VPC %s found : %s',
ctx.ip, ctx.vpc_network, str(route_list))
sys.exit(OCF_ERR_CONFIGURED)
def route_release(ctx):
request = ctx.conn.routes().delete(project=ctx.project, route=ctx.route_name)
wait_for_operation(ctx, request.execute())
def ip_monitor(ctx):
- logging.info('IP monitor: checking local network configuration')
+ logger.info('IP monitor: checking local network configuration')
def address_filter(addr):
for attr in addr['attrs']:
if attr[0] == 'IFA_LOCAL':
if attr[1] == ctx.ip:
return True
else:
return False
route = ctx.iproute.get_addr(
index=ctx.iface_idx, match=address_filter)
if not route:
- logging.warn(
+ logger.warning(
'The floating IP %s is not locally configured on this instance (%s)',
ctx.ip, ctx.instance)
return OCF_NOT_RUNNING
- logging.debug(
+ logger.debug(
'The floating IP %s is correctly configured on this instance (%s)',
ctx.ip, ctx.instance)
return OCF_SUCCESS
def ip_release(ctx):
ctx.iproute.addr('del', index=ctx.iface_idx, address=ctx.ip, mask=32)
def ip_and_route_start(ctx):
- logging.info('Bringing up the floating IP %s', ctx.ip)
+ logger.info('Bringing up the floating IP %s', ctx.ip)
# Add a new entry in the routing table
# If the route entry exists and is pointing to another instance, take it over
# Ensure that there is no route that we are not aware of that is also handling our IP
check_conflicting_routes(ctx)
# There is no replace API, We need to first delete the existing route if any
try:
request = ctx.conn.routes().get(project=ctx.project, route=ctx.route_name)
request.execute()
# TODO: check specific exception for 404
except googleapiclient.errors.HttpError as e:
if e.resp.status != 404:
raise
else:
route_release(ctx)
route_body = {
'name': ctx.route_name,
'network': ctx.vpc_network_url,
'destRange': '%s/32' % ctx.ip,
'nextHopInstance': ctx.instance_url,
}
try:
request = ctx.conn.routes().insert(project=ctx.project, body=route_body)
wait_for_operation(ctx, request.execute())
except googleapiclient.errors.HttpError:
try:
request = ctx.conn.networks().get(
project=ctx.project, network=ctx.vpc_network)
request.execute()
except googleapiclient.errors.HttpError as e:
if e.resp.status == 404:
- logging.error('VPC network not found')
+ logger.error('VPC network not found')
sys.exit(OCF_ERR_CONFIGURED)
else:
raise
else:
raise
# Configure the IP address locally
# We need to release the IP first
if ip_monitor(ctx) == OCF_SUCCESS:
ip_release(ctx)
ctx.iproute.addr('add', index=ctx.iface_idx, address=ctx.ip, mask=32)
ctx.iproute.link('set', index=ctx.iface_idx, state='up')
- logging.info('Successfully brought up the floating IP %s', ctx.ip)
+ logger.info('Successfully brought up the floating IP %s', ctx.ip)
def route_monitor(ctx):
- logging.info('GCP route monitor: checking route table')
+ logger.info('GCP route monitor: checking route table')
# Ensure that there is no route that we are not aware of that is also handling our IP
check_conflicting_routes
try:
request = ctx.conn.routes().get(project=ctx.project, route=ctx.route_name)
response = request.execute()
except googleapiclient.errors.HttpError as e:
if 'Insufficient Permission' in e.content:
return OCF_ERR_PERM
elif e.resp.status == 404:
return OCF_NOT_RUNNING
else:
raise
routed_to_instance = response.get('nextHopInstance', '<unknown>')
instance_url = '%s/projects/%s/zones/%s/instances/%s' % (
GCP_API_URL_PREFIX, ctx.project, ctx.zone, ctx.instance)
if routed_to_instance != instance_url:
- logging.warn(
+ logger.warning(
'The floating IP %s is not routed to this instance (%s) but to instance %s',
ctx.ip, ctx.instance, routed_to_instance.split('/')[-1])
return OCF_NOT_RUNNING
- logging.debug(
+ logger.debug(
'The floating IP %s is correctly routed to this instance (%s)',
ctx.ip, ctx.instance)
return OCF_SUCCESS
def ip_and_route_stop(ctx):
- logging.info('Bringing down the floating IP %s', ctx.ip)
+ logger.info('Bringing down the floating IP %s', ctx.ip)
# Delete the route entry
# If the route entry exists and is pointing to another instance, don't touch it
if route_monitor(ctx) == OCF_NOT_RUNNING:
- logging.info(
+ logger.info(
'The floating IP %s is already not routed to this instance (%s)',
ctx.ip, ctx.instance)
else:
route_release(ctx)
if ip_monitor(ctx) == OCF_NOT_RUNNING:
- logging.info('The floating IP %s is already down', ctx.ip)
+ logger.info('The floating IP %s is already down', ctx.ip)
else:
ip_release(ctx)
def configure_logs(ctx):
# Prepare logging
- logging.basicConfig(
- format='gcp:route - %(levelname)s - %(message)s', level=logging.INFO)
+ global logger
logging.getLogger('googleapiclient').setLevel(logging.WARN)
logging_env = os.environ.get('OCF_RESKEY_stackdriver_logging')
if logging_env:
logging_env = logging_env.lower()
if any(x in logging_env for x in ['yes', 'true', 'enabled']):
try:
import google.cloud.logging.handlers
client = google.cloud.logging.Client()
handler = google.cloud.logging.handlers.CloudLoggingHandler(
client, name=ctx.instance)
handler.setLevel(logging.INFO)
formatter = logging.Formatter('gcp:route "%(message)s"')
handler.setFormatter(formatter)
- root_logger = logging.getLogger()
- root_logger.addHandler(handler)
+ log.addHandler(handler)
+ logger = logging.LoggerAdapter(log, {'OCF_RESOURCE_INSTANCE': OCF_RESOURCE_INSTANCE})
except ImportError:
- logging.error('Couldn\'t import google.cloud.logging, '
+ logger.error('Couldn\'t import google.cloud.logging, '
'disabling Stackdriver-logging support')
def main():
if 'meta-data' in sys.argv[1]:
print(METADATA)
return
ctx = Context()
validate(ctx)
if 'validate-all' in sys.argv[1]:
return
configure_logs(ctx)
if 'start' in sys.argv[1]:
ip_and_route_start(ctx)
elif 'stop' in sys.argv[1]:
ip_and_route_stop(ctx)
elif 'status' in sys.argv[1] or 'monitor' in sys.argv[1]:
sys.exit(ip_monitor(ctx))
else:
usage = 'usage: %s {start|stop|monitor|status|meta-data|validate-all}' % \
os.path.basename(sys.argv[0])
- logging.error(usage)
+ logger.error(usage)
sys.exit(OCF_ERR_UNIMPLEMENTED)
if __name__ == "__main__":
main()
diff --git a/heartbeat/gcp-vpc-move-vip.in b/heartbeat/gcp-vpc-move-vip.in
index af2080502..ba61193b6 100755
--- a/heartbeat/gcp-vpc-move-vip.in
+++ b/heartbeat/gcp-vpc-move-vip.in
@@ -1,338 +1,338 @@
#!@PYTHON@ -tt
# ---------------------------------------------------------------------
# Copyright 2016 Google Inc.
#
# 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.
# ---------------------------------------------------------------------
# Description: Google Cloud Platform - Floating IP Address (Alias)
# ---------------------------------------------------------------------
import json
import logging
import os
import sys
import time
+OCF_FUNCTIONS_DIR="%s/lib/heartbeat" % os.environ.get("OCF_ROOT")
+sys.path.append(OCF_FUNCTIONS_DIR)
+
+from ocf import *
+
try:
import googleapiclient.discovery
except ImportError:
pass
if sys.version_info >= (3, 0):
# Python 3 imports.
import urllib.parse as urlparse
import urllib.request as urlrequest
else:
# Python 2 imports.
import urllib as urlparse
import urllib2 as urlrequest
CONN = None
THIS_VM = None
ALIAS = None
-OCF_SUCCESS = 0
-OCF_ERR_GENERIC = 1
-OCF_ERR_CONFIGURED = 6
-OCF_NOT_RUNNING = 7
METADATA_SERVER = 'http://metadata.google.internal/computeMetadata/v1/'
METADATA_HEADERS = {'Metadata-Flavor': 'Google'}
METADATA = \
'''<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="gcp-vpc-move-vip">
<version>1.0</version>
<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>
<shortdesc lang="en">Floating IP Address on Google Cloud Platform</shortdesc>
<parameters>
+ <parameter name="alias_ip" unique="1" required="1">
+ <longdesc lang="en">IP Address to be added including CIDR. E.g 192.168.0.1/32</longdesc>
+ <shortdesc lang="en">IP Address to be added including CIDR. E.g 192.168.0.1/32</shortdesc>
+ <content type="string" default="" />
+ </parameter>
+ <parameter name="alias_range_name" unique="1" required="0">
+ <longdesc lang="en">Subnet name for the Alias IP</longdesc>
+ <shortdesc lang="en">Subnet name for the Alias IP</shortdesc>
+ <content type="string" default="" />
+ </parameter>
<parameter name="hostlist" unique="1" required="0">
<longdesc lang="en">List of hosts in the cluster</longdesc>
<shortdesc lang="en">Host list</shortdesc>
<content type="string" default="" />
</parameter>
<parameter name="stackdriver_logging" unique="0" required="0">
<longdesc lang="en">If enabled (set to true), IP failover logs will be posted to stackdriver logging</longdesc>
<shortdesc lang="en">Stackdriver-logging support</shortdesc>
<content type="boolean" default="" />
</parameter>
- <parameter name="alias_ip" unique="1" required="1">
- <longdesc lang="en">IP Address to be added including CIDR. E.g 192.168.0.1/32</longdesc>
- <shortdesc lang="en">IP Address to be added including CIDR. E.g 192.168.0.1/32</shortdesc>
- <content type="string" default="" />
- </parameter>
- <parameter name="alias_range_name" unique="1" required="0">
- <longdesc lang="en">Subnet name for the Alias IP2</longdesc>
- <shortdesc lang="en">Subnet name for the Alias IP</shortdesc>
- <content type="string" default="" />
- </parameter>
</parameters>
<actions>
<action name="start" timeout="300" />
<action name="stop" timeout="15" />
<action name="monitor" timeout="15" interval="60" depth="0" />
<action name="meta-data" timeout="15" />
<action name="validate-all" timeout="15" />
</actions>
</resource-agent>'''
def get_metadata(metadata_key, params=None, timeout=None):
"""Performs a GET request with the metadata headers.
Args:
metadata_key: string, the metadata to perform a GET request on.
params: dictionary, the query parameters in the GET request.
timeout: int, timeout in seconds for metadata requests.
Returns:
HTTP response from the GET request.
Raises:
urlerror.HTTPError: raises when the GET request fails.
"""
timeout = timeout or 60
metadata_url = os.path.join(METADATA_SERVER, metadata_key)
params = urlparse.urlencode(params or {})
url = '%s?%s' % (metadata_url, params)
request = urlrequest.Request(url, headers=METADATA_HEADERS)
request_opener = urlrequest.build_opener(urlrequest.ProxyHandler({}))
return request_opener.open(request, timeout=timeout * 1.1).read()
def get_instance(project, zone, instance):
request = CONN.instances().get(
project=project, zone=zone, instance=instance)
return request.execute()
def get_network_ifaces(project, zone, instance):
return get_instance(project, zone, instance)['networkInterfaces']
def wait_for_operation(project, zone, operation):
while True:
result = CONN.zoneOperations().get(
project=project,
zone=zone,
operation=operation['name']).execute()
if result['status'] == 'DONE':
if 'error' in result:
raise Exception(result['error'])
return
time.sleep(1)
def set_alias(project, zone, instance, alias, alias_range_name=None):
fingerprint = get_network_ifaces(project, zone, instance)[0]['fingerprint']
body = {
'aliasIpRanges': [],
'fingerprint': fingerprint
}
if alias:
obj = {'ipCidrRange': alias}
if alias_range_name:
obj['subnetworkRangeName'] = alias_range_name
body['aliasIpRanges'].append(obj)
request = CONN.instances().updateNetworkInterface(
instance=instance, networkInterface='nic0', project=project, zone=zone,
body=body)
operation = request.execute()
wait_for_operation(project, zone, operation)
def get_alias(project, zone, instance):
iface = get_network_ifaces(project, zone, instance)
try:
return iface[0]['aliasIpRanges'][0]['ipCidrRange']
except KeyError:
return ''
def get_localhost_alias():
net_iface = get_metadata('instance/network-interfaces', {'recursive': True})
net_iface = json.loads(net_iface.decode('utf-8'))
try:
return net_iface[0]['ipAliases'][0]
except (KeyError, IndexError):
return ''
def get_zone(project, instance):
fl = 'name="%s"' % instance
request = CONN.instances().aggregatedList(project=project, filter=fl)
while request is not None:
response = request.execute()
zones = response.get('items', {})
for zone in zones.values():
for inst in zone.get('instances', []):
if inst['name'] == instance:
return inst['zone'].split("/")[-1]
request = CONN.instances().aggregatedList_next(
previous_request=request, previous_response=response)
raise Exception("Unable to find instance %s" % (instance))
def get_instances_list(project, exclude):
hostlist = []
request = CONN.instances().aggregatedList(project=project)
while request is not None:
response = request.execute()
zones = response.get('items', {})
for zone in zones.values():
for inst in zone.get('instances', []):
if inst['name'] != exclude:
hostlist.append(inst['name'])
request = CONN.instances().aggregatedList_next(
previous_request=request, previous_response=response)
return hostlist
def gcp_alias_start(alias):
my_alias = get_localhost_alias()
my_zone = get_metadata('instance/zone').split('/')[-1]
project = get_metadata('project/project-id')
# If I already have the IP, exit. If it has an alias IP that isn't the VIP,
# then remove it
if my_alias == alias:
- logging.info(
+ logger.info(
'%s already has %s attached. No action required' % (THIS_VM, alias))
sys.exit(OCF_SUCCESS)
elif my_alias:
- logging.info('Removing %s from %s' % (my_alias, THIS_VM))
+ logger.info('Removing %s from %s' % (my_alias, THIS_VM))
set_alias(project, my_zone, THIS_VM, '')
# Loops through all hosts & remove the alias IP from the host that has it
hostlist = os.environ.get('OCF_RESKEY_hostlist', '')
if hostlist:
hostlist = hostlist.replace(THIS_VM, '').split()
else:
hostlist = get_instances_list(project, THIS_VM)
for host in hostlist:
host_zone = get_zone(project, host)
host_alias = get_alias(project, host_zone, host)
if alias == host_alias:
- logging.info(
+ logger.info(
'%s is attached to %s - Removing all alias IP addresses from %s' %
(alias, host, host))
set_alias(project, host_zone, host, '')
break
# add alias IP to localhost
set_alias(
project, my_zone, THIS_VM, alias,
os.environ.get('OCF_RESKEY_alias_range_name'))
# Check the IP has been added
my_alias = get_localhost_alias()
if alias == my_alias:
- logging.info('Finished adding %s to %s' % (alias, THIS_VM))
+ logger.info('Finished adding %s to %s' % (alias, THIS_VM))
elif my_alias:
- logging.error(
+ logger.error(
'Failed to add IP. %s has an IP attached but it isn\'t %s' %
(THIS_VM, alias))
sys.exit(OCF_ERR_GENERIC)
else:
- logging.error('Failed to add IP address %s to %s' % (alias, THIS_VM))
+ logger.error('Failed to add IP address %s to %s' % (alias, THIS_VM))
sys.exit(OCF_ERR_GENERIC)
def gcp_alias_stop(alias):
my_alias = get_localhost_alias()
my_zone = get_metadata('instance/zone').split('/')[-1]
project = get_metadata('project/project-id')
if my_alias == alias:
- logging.info('Removing %s from %s' % (my_alias, THIS_VM))
+ logger.info('Removing %s from %s' % (my_alias, THIS_VM))
set_alias(project, my_zone, THIS_VM, '')
def gcp_alias_status(alias):
my_alias = get_localhost_alias()
if alias == my_alias:
- logging.info('%s has the correct IP address attached' % THIS_VM)
+ logger.info('%s has the correct IP address attached' % THIS_VM)
else:
sys.exit(OCF_NOT_RUNNING)
def validate():
global ALIAS
global CONN
global THIS_VM
# Populate global vars
try:
CONN = googleapiclient.discovery.build('compute', 'v1')
except Exception as e:
- logging.error('Couldn\'t connect with google api: ' + str(e))
+ logger.error('Couldn\'t connect with google api: ' + str(e))
sys.exit(OCF_ERR_CONFIGURED)
try:
THIS_VM = get_metadata('instance/name')
except Exception as e:
- logging.error('Couldn\'t get instance name, is this running inside GCE?: ' + str(e))
+ logger.error('Couldn\'t get instance name, is this running inside GCE?: ' + str(e))
sys.exit(OCF_ERR_CONFIGURED)
ALIAS = os.environ.get('OCF_RESKEY_alias_ip')
if not ALIAS:
- logging.error('Missing alias_ip parameter')
+ logger.error('Missing alias_ip parameter')
sys.exit(OCF_ERR_CONFIGURED)
def configure_logs():
# Prepare logging
- logging.basicConfig(
- format='gcp:alias - %(levelname)s - %(message)s', level=logging.INFO)
+ global logger
logging.getLogger('googleapiclient').setLevel(logging.WARN)
logging_env = os.environ.get('OCF_RESKEY_stackdriver_logging')
if logging_env:
logging_env = logging_env.lower()
if any(x in logging_env for x in ['yes', 'true', 'enabled']):
try:
import google.cloud.logging.handlers
client = google.cloud.logging.Client()
handler = google.cloud.logging.handlers.CloudLoggingHandler(
client, name=THIS_VM)
handler.setLevel(logging.INFO)
formatter = logging.Formatter('gcp:alias "%(message)s"')
handler.setFormatter(formatter)
- root_logger = logging.getLogger()
- root_logger.addHandler(handler)
+ log.addHandler(handler)
+ logger = logging.LoggerAdapter(log, {'OCF_RESOURCE_INSTANCE': OCF_RESOURCE_INSTANCE})
except ImportError:
- logging.error('Couldn\'t import google.cloud.logging, '
+ logger.error('Couldn\'t import google.cloud.logging, '
'disabling Stackdriver-logging support')
def main():
if 'meta-data' in sys.argv[1]:
print(METADATA)
return
validate()
if 'validate-all' in sys.argv[1]:
return
configure_logs()
if 'start' in sys.argv[1]:
gcp_alias_start(ALIAS)
elif 'stop' in sys.argv[1]:
gcp_alias_stop(ALIAS)
elif 'status' in sys.argv[1] or 'monitor' in sys.argv[1]:
gcp_alias_status(ALIAS)
else:
- logging.error('no such function %s' % str(sys.argv[1]))
+ logger.error('no such function %s' % str(sys.argv[1]))
if __name__ == "__main__":
main()
diff --git a/heartbeat/ocf.py b/heartbeat/ocf.py
new file mode 100644
index 000000000..36e7ccccd
--- /dev/null
+++ b/heartbeat/ocf.py
@@ -0,0 +1,137 @@
+#
+# Copyright (c) 2016 Red Hat, Inc, Oyvind Albrigtsen
+# All Rights Reserved.
+#
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+import sys, os, logging, syslog
+
+argv=sys.argv
+env=os.environ
+
+#
+# Common variables for the OCF Resource Agents supplied by
+# heartbeat.
+#
+
+OCF_SUCCESS=0
+OCF_ERR_GENERIC=1
+OCF_ERR_ARGS=2
+OCF_ERR_UNIMPLEMENTED=3
+OCF_ERR_PERM=4
+OCF_ERR_INSTALLED=5
+OCF_ERR_CONFIGURED=6
+OCF_NOT_RUNNING=7
+
+# Non-standard values.
+#
+# OCF does not include the concept of master/slave resources so we
+# need to extend it so we can discover a resource's complete state.
+#
+# OCF_RUNNING_MASTER:
+# The resource is in "master" mode and fully operational
+# OCF_FAILED_MASTER:
+# The resource is in "master" mode but in a failed state
+#
+# The extra two values should only be used during a probe.
+#
+# Probes are used to discover resources that were started outside of
+# the CRM and/or left behind if the LRM fails.
+#
+# They can be identified in RA scripts by checking for:
+# [ "${__OCF_ACTION}" = "monitor" -a "${OCF_RESKEY_CRM_meta_interval}" = "0" ]
+#
+# Failed "slaves" should continue to use: OCF_ERR_GENERIC
+# Fully operational "slaves" should continue to use: OCF_SUCCESS
+#
+OCF_RUNNING_MASTER=8
+OCF_FAILED_MASTER=9
+
+
+## Own logger handler that uses old-style syslog handler as otherwise
+## everything is sourced from /dev/syslog
+class SyslogLibHandler(logging.StreamHandler):
+ """
+ A handler class that correctly push messages into syslog
+ """
+ def emit(self, record):
+ syslog_level = {
+ logging.CRITICAL:syslog.LOG_CRIT,
+ logging.ERROR:syslog.LOG_ERR,
+ logging.WARNING:syslog.LOG_WARNING,
+ logging.INFO:syslog.LOG_INFO,
+ logging.DEBUG:syslog.LOG_DEBUG,
+ logging.NOTSET:syslog.LOG_DEBUG,
+ }[record.levelno]
+
+ msg = self.format(record)
+
+ # syslog.syslog can not have 0x00 character inside or exception
+ # is thrown
+ syslog.syslog(syslog_level, msg.replace("\x00","\n"))
+ return
+
+
+OCF_RESOURCE_INSTANCE = env.get("OCF_RESOURCE_INSTANCE")
+
+HA_DEBUG = env.get("HA_debug", 0)
+HA_DATEFMT = env.get("HA_DATEFMT", "%b %d %T ")
+HA_LOGFACILITY = env.get("HA_LOGFACILITY")
+HA_LOGFILE = env.get("HA_LOGFILE")
+HA_DEBUGLOG = env.get("HA_DEBUGLOG")
+
+logging.basicConfig()
+log = logging.getLogger(os.path.basename(argv[0]))
+log.setLevel(logging.DEBUG)
+
+## add logging to stderr
+if sys.stdout.isatty():
+ seh = logging.StreamHandler(stream=sys.stderr)
+ if HA_DEBUG == 0:
+ seh.setLevel(logging.WARNING)
+ sehformatter = logging.Formatter('%(filename)s(%(OCF_RESOURCE_INSTANCE)s)[%(process)s]:\t%(asctime)s%(levelname)s: %(message)s', datefmt=HA_DATEFMT)
+ seh.setFormatter(sehformatter)
+ log.addHandler(seh)
+
+## add logging to syslog
+if HA_LOGFACILITY:
+ slh = SyslogLibHandler()
+ if HA_DEBUG == 0:
+ slh.setLevel(logging.WARNING)
+ slhformatter = logging.Formatter('%(levelname)s: %(message)s')
+ slh.setFormatter(slhformatter)
+ log.addHandler(slh)
+
+## add logging to file
+if HA_LOGFILE:
+ lfh = logging.FileHandler(HA_LOGFILE)
+ if HA_DEBUG == 0:
+ lfh.setLevel(logging.WARNING)
+ lfhformatter = logging.Formatter('%(filename)s(%(OCF_RESOURCE_INSTANCE)s)[%(process)s]:\t%(asctime)s%(levelname)s: %(message)s', datefmt=HA_DATEFMT)
+ lfh.setFormatter(lfhformatter)
+ log.addHandler(lfh)
+
+## add debug logging to file
+if HA_DEBUGLOG and HA_LOGFILE != HA_DEBUGLOG:
+ dfh = logging.FileHandler(HA_DEBUGLOG)
+ if HA_DEBUG == 0:
+ dfh.setLevel(logging.WARNING)
+ dfhformatter = logging.Formatter('%(filename)s(%(OCF_RESOURCE_INSTANCE)s)[%(process)s]:\t%(asctime)s%(levelname)s: %(message)s', datefmt=HA_DATEFMT)
+ dfh.setFormatter(dfhformatter)
+ log.addHandler(dfh)
+
+logger = logging.LoggerAdapter(log, {'OCF_RESOURCE_INSTANCE': OCF_RESOURCE_INSTANCE})

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jul 8, 6:02 PM (1 d, 2 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2002435
Default Alt Text
(37 KB)

Event Timeline