diff --git a/heartbeat/gcp-pd-move.in b/heartbeat/gcp-pd-move.in index e99cc71f8..cbe703c3c 100644 --- a/heartbeat/gcp-pd-move.in +++ b/heartbeat/gcp-pd-move.in @@ -1,382 +1,382 @@ #!@PYTHON@ -tt # - *- coding: utf- 8 - *- # # --------------------------------------------------------------------- # Copyright 2018 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 - Disk attach # --------------------------------------------------------------------- import json import logging import os import re import sys import time OCF_FUNCTIONS_DIR = os.environ.get("OCF_FUNCTIONS_DIR", "%s/lib/heartbeat" % os.environ.get("OCF_ROOT")) sys.path.append(OCF_FUNCTIONS_DIR) import ocf from ocf import logger 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 PROJECT = None ZONE = None REGION = None LIST_DISK_ATTACHED_INSTANCES = None INSTANCE_NAME = None PARAMETERS = { 'disk_name': '', 'disk_scope': 'detect', 'disk_csek_file': '', 'mode': "READ_WRITE", 'device_name': '', 'stackdriver_logging': 'no', } MANDATORY_PARAMETERS = ['disk_name', 'disk_scope'] 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-pd-move"> <version>1.0</version> <longdesc lang="en"> Resource Agent that can attach or detach a regional/zonal disk on current GCP instance. Requirements : - Disk has to be properly created as regional/zonal in order to be used correctly. </longdesc> <shortdesc lang="en">Attach/Detach a persistent disk on current GCP instance</shortdesc> <parameters> <parameter name="disk_name" unique="1" required="1"> <longdesc lang="en">The name of the GCP disk.</longdesc> <shortdesc lang="en">Disk name</shortdesc> <content type="string" default="{}" /> </parameter> <parameter name="disk_scope"> <longdesc lang="en">Disk scope</longdesc> <shortdesc lang="en">Network name</shortdesc> <content type="string" default="{}" /> </parameter> <parameter name="disk_csek_file"> <longdesc lang="en">Path to a Customer-Supplied Encryption Key (CSEK) key file</longdesc> <shortdesc lang="en">Customer-Supplied Encryption Key file</shortdesc> <content type="string" default="{}" /> </parameter> <parameter name="mode"> <longdesc lang="en">Attachment mode (READ_WRITE, READ_ONLY)</longdesc> <shortdesc lang="en">Attachment mode</shortdesc> <content type="string" default="{}" /> </parameter> <parameter name="device_name"> <longdesc lang="en">An optional name that indicates the disk name the guest operating system will see.</longdesc> <shortdesc lang="en">Optional device name</shortdesc> <content type="boolean" default="{}" /> </parameter> <parameter name="stackdriver_logging"> <longdesc lang="en">Use stackdriver_logging output to global resource (yes, true, enabled)</longdesc> <shortdesc lang="en">Use stackdriver_logging</shortdesc> <content type="string" default="{}" /> </parameter> </parameters> <actions> <action name="start" timeout="300s" /> <action name="stop" timeout="15s" /> <action name="monitor" timeout="15s" interval="10s" depth="0" /> <action name="meta-data" timeout="5s" /> </actions> </resource-agent>'''.format(PARAMETERS['disk_name'], PARAMETERS['disk_scope'], PARAMETERS['disk_csek_file'], PARAMETERS['mode'], PARAMETERS['device_name'], PARAMETERS['stackdriver_logging']) 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().decode("utf-8") def populate_vars(): global CONN global INSTANCE_NAME global PROJECT global ZONE global REGION global LIST_DISK_ATTACHED_INSTANCES # Populate global vars try: CONN = googleapiclient.discovery.build('compute', 'v1') except Exception as e: logger.error('Couldn\'t connect with google api: ' + str(e)) - sys.exit(ocf.OCF_ERR_CONFIGURED) + sys.exit(ocf.OCF_ERR_GENERIC) for param in PARAMETERS: value = os.environ.get('OCF_RESKEY_%s' % param, PARAMETERS[param]) if not value and param in MANDATORY_PARAMETERS: logger.error('Missing %s mandatory parameter' % param) sys.exit(ocf.OCF_ERR_CONFIGURED) elif value: PARAMETERS[param] = value try: INSTANCE_NAME = get_metadata('instance/name') except Exception as e: logger.error( 'Couldn\'t get instance name, is this running inside GCE?: ' + str(e)) - sys.exit(ocf.OCF_ERR_CONFIGURED) + sys.exit(ocf.OCF_ERR_GENERIC) PROJECT = get_metadata('project/project-id') if PARAMETERS['disk_scope'] in ['detect', 'regional']: ZONE = get_metadata('instance/zone').split('/')[-1] REGION = ZONE[:-2] else: ZONE = PARAMETERS['disk_scope'] LIST_DISK_ATTACHED_INSTANCES = get_disk_attached_instances( PARAMETERS['disk_name']) def configure_logs(): # Prepare logging 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=INSTANCE_NAME) handler.setLevel(logging.INFO) formatter = logging.Formatter('gcp:alias "%(message)s"') handler.setFormatter(formatter) ocf.log.addHandler(handler) logger = logging.LoggerAdapter( ocf.log, {'OCF_RESOURCE_INSTANCE': ocf.OCF_RESOURCE_INSTANCE}) except ImportError: logger.error('Couldn\'t import google.cloud.logging, ' 'disabling Stackdriver-logging support') def wait_for_operation(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 get_disk_attached_instances(disk): def get_users_list(): fl = 'name="%s"' % disk request = CONN.disks().aggregatedList(project=PROJECT, filter=fl) while request is not None: response = request.execute() locations = response.get('items', {}) for location in locations.values(): for d in location.get('disks', []): if d['name'] == disk: return d.get('users', []) request = CONN.instances().aggregatedList_next( previous_request=request, previous_response=response) raise Exception("Unable to find disk %s" % disk) def get_only_instance_name(user): return re.sub('.*/instances/', '', user) return map(get_only_instance_name, get_users_list()) def is_disk_attached(instance): return instance in LIST_DISK_ATTACHED_INSTANCES def detach_disk(instance, disk_name): # Python API misses disk-scope argument. # Detaching a disk is only possible by using deviceName, which is retrieved # as a disk parameter when listing the instance information request = CONN.instances().get( project=PROJECT, zone=ZONE, instance=instance) response = request.execute() device_name = None for disk in response['disks']: if disk_name == re.sub('.*disks/',"",disk['source']): device_name = disk['deviceName'] break if not device_name: logger.error("Didn't find %(d)s deviceName attached to %(i)s" % { 'd': disk_name, 'i': instance, }) return request = CONN.instances().detachDisk( project=PROJECT, zone=ZONE, instance=instance, deviceName=device_name) wait_for_operation(request.execute()) def attach_disk(instance, disk_name): location = 'zones/%s' % ZONE if PARAMETERS['disk_scope'] == 'regional': location = 'regions/%s' % REGION prefix = 'https://www.googleapis.com/compute/v1' body = { 'source': '%(prefix)s/projects/%(project)s/%(location)s/disks/%(disk)s' % { 'prefix': prefix, 'project': PROJECT, 'location': location, 'disk': disk_name, }, } # Customer-Supplied Encryption Key (CSEK) if PARAMETERS['disk_csek_file']: with open(PARAMETERS['disk_csek_file']) as csek_file: body['diskEncryptionKey'] = { 'rawKey': csek_file.read(), } if PARAMETERS['device_name']: body['deviceName'] = PARAMETERS['device_name'] if PARAMETERS['mode']: body['mode'] = PARAMETERS['mode'] force_attach = None if PARAMETERS['disk_scope'] == 'regional': # Python API misses disk-scope argument. force_attach = True else: # If this disk is attached to some instance, detach it first. for other_instance in LIST_DISK_ATTACHED_INSTANCES: logger.info("Detaching disk %(disk_name)s from other instance %(i)s" % { 'disk_name': PARAMETERS['disk_name'], 'i': other_instance, }) detach_disk(other_instance, PARAMETERS['disk_name']) request = CONN.instances().attachDisk( project=PROJECT, zone=ZONE, instance=instance, body=body, forceAttach=force_attach) wait_for_operation(request.execute()) def fetch_data(): configure_logs() populate_vars() def gcp_pd_move_start(): fetch_data() if not is_disk_attached(INSTANCE_NAME): logger.info("Attaching disk %(disk_name)s to %(instance)s" % { 'disk_name': PARAMETERS['disk_name'], 'instance': INSTANCE_NAME, }) attach_disk(INSTANCE_NAME, PARAMETERS['disk_name']) def gcp_pd_move_stop(): fetch_data() if is_disk_attached(INSTANCE_NAME): logger.info("Detaching disk %(disk_name)s to %(instance)s" % { 'disk_name': PARAMETERS['disk_name'], 'instance': INSTANCE_NAME, }) detach_disk(INSTANCE_NAME, PARAMETERS['disk_name']) def gcp_pd_move_status(): fetch_data() if is_disk_attached(INSTANCE_NAME): logger.debug("Disk %(disk_name)s is correctly attached to %(instance)s" % { 'disk_name': PARAMETERS['disk_name'], 'instance': INSTANCE_NAME, }) else: sys.exit(ocf.OCF_NOT_RUNNING) def main(): if len(sys.argv) < 2: logger.error('Missing argument') return command = sys.argv[1] if 'meta-data' in command: print(METADATA) return if command in 'start': gcp_pd_move_start() elif command in 'stop': gcp_pd_move_stop() elif command in ('monitor', 'status'): gcp_pd_move_status() else: configure_logs() logger.error('no such function %s' % str(command)) if __name__ == "__main__": main() diff --git a/heartbeat/gcp-vpc-move-route.in b/heartbeat/gcp-vpc-move-route.in index dac6e4ea8..6b240c04d 100644 --- a/heartbeat/gcp-vpc-move-route.in +++ b/heartbeat/gcp-vpc-move-route.in @@ -1,490 +1,490 @@ #!@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 = os.environ.get("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 try: from google.oauth2.service_account import Credentials as ServiceAccountCredentials except ImportError: from oauth2client.service_account import ServiceAccountCredentials 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 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 chosen 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="0"> <longdesc lang="en"> Name of the VPC network </longdesc> <shortdesc lang="en">VPC network</shortdesc> <content type="string" default="default" /> </parameter> <parameter name="project"> <longdesc lang="en"> Project ID of the instance. It can be useful to set this attribute if the instance is in a shared service project. Otherwise, the agent should be able to determine the project ID automatically. </longdesc> <shortdesc lang="en">Project ID</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="serviceaccount"> <longdesc lang="en">Path to Service account JSON file</longdesc> <shortdesc lang="en">Service account JSONfile</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> </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().decode("utf-8") def validate(ctx): if os.geteuid() != 0: logger.error('You must run this agent as root') sys.exit(OCF_ERR_PERM) try: serviceaccount = os.environ.get("OCF_RESKEY_serviceaccount") if not serviceaccount: try: from googleapiclient import _auth credentials = _auth.default_credentials(); except: credentials = GoogleCredentials.get_application_default() logging.debug("using application default credentials") else: scope = ['https://www.googleapis.com/auth/cloud-platform'] logging.debug("using credentials from service account") try: credentials = ServiceAccountCredentials.from_service_account_file(filename=serviceaccount, scopes=scope) except AttributeError: credentials = ServiceAccountCredentials.from_json_keyfile_name(serviceaccount, scope) except Exception as e: logging.error(str(e)) sys.exit(OCF_ERR_GENERIC) ctx.conn = googleapiclient.discovery.build('compute', 'v1', credentials=credentials, cache_discovery=False) except Exception as e: logger.error('Couldn\'t connect with google api: ' + str(e)) - sys.exit(OCF_ERR_CONFIGURED) + sys.exit(OCF_ERR_GENERIC) ctx.ip = os.environ.get('OCF_RESKEY_ip') if not ctx.ip: 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 = os.environ.get( 'OCF_RESKEY_project', get_metadata('project/project-id')) except Exception as e: logger.error( 'Instance information not found. Is this a GCE instance ?: %s', str(e)) - sys.exit(OCF_ERR_CONFIGURED) + sys.exit(OCF_ERR_GENERIC) 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: logger.error('Network interface not found') - sys.exit(OCF_ERR_CONFIGURED) + sys.exit(OCF_ERR_GENERIC) 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) try: request = ctx.conn.routes().list(project=ctx.project, filter=fl) response = request.execute() except googleapiclient.errors.HttpError as e: if e.resp.status == 404: logger.error('VPC network not found') if 'stop' in sys.argv[1]: sys.exit(OCF_SUCCESS) else: sys.exit(OCF_ERR_CONFIGURED) else: raise route_list = response.get('items', None) if route_list: 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): 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: logger.warning( 'The floating IP %s is not locally configured on this instance (%s)', ctx.ip, ctx.instance) return OCF_NOT_RUNNING 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): 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: 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') logger.info('Successfully brought up the floating IP %s', ctx.ip) def route_monitor(ctx): 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(ctx) try: request = ctx.conn.routes().get(project=ctx.project, route=ctx.route_name) response = request.execute() except googleapiclient.errors.HttpError as e: if e.resp.status == 404: return OCF_NOT_RUNNING elif 'Insufficient Permission' in e.content: return OCF_ERR_PERM 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: 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 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): 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: 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: logger.info('The floating IP %s is already down', ctx.ip) else: ip_release(ctx) def configure_logs(ctx): # Prepare logging 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) log.addHandler(handler) logger = logging.LoggerAdapter(log, {'OCF_RESOURCE_INSTANCE': OCF_RESOURCE_INSTANCE}) except ImportError: 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]) logger.error(usage) sys.exit(OCF_ERR_UNIMPLEMENTED) if __name__ == "__main__": main()