[API] Retry request on galera deadlocks
It is possible in a multi-API situation for galera deadlocks to still happen. This patch will trigger a retry in these scenarios. Change-Id: I3c62a9a39f6dec921dee8106b7a3a98cf1095f6d
This commit is contained in:
		@@ -14,10 +14,10 @@
 | 
			
		||||
# under the License.
 | 
			
		||||
 | 
			
		||||
from pecan import response
 | 
			
		||||
from pecan.rest import RestController
 | 
			
		||||
from libra.api.library.libra_rest_controller import LibraController
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConnectionThrottleController(RestController):
 | 
			
		||||
class ConnectionThrottleController(LibraController):
 | 
			
		||||
    """functions for /loadbalancers/{loadBalancerId}/connectionthrottle/*
 | 
			
		||||
    routing"""
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,10 +14,10 @@
 | 
			
		||||
# under the License.
 | 
			
		||||
 | 
			
		||||
from pecan import response
 | 
			
		||||
from pecan.rest import RestController
 | 
			
		||||
from libra.api.library.libra_rest_controller import LibraController
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HealthMonitorController(RestController):
 | 
			
		||||
class HealthMonitorController(LibraController):
 | 
			
		||||
    """functions for /loadbalancers/{loadBalancerId}/healthmonitor/* routing"""
 | 
			
		||||
 | 
			
		||||
    def get(self, load_balancer_id):
 | 
			
		||||
 
 | 
			
		||||
@@ -14,11 +14,11 @@
 | 
			
		||||
# under the License.
 | 
			
		||||
 | 
			
		||||
from pecan import expose
 | 
			
		||||
from pecan.rest import RestController
 | 
			
		||||
from libra.api.library.libra_rest_controller import LibraController
 | 
			
		||||
from libra.api.model.lbaas import Limits, db_session
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LimitsController(RestController):
 | 
			
		||||
class LimitsController(LibraController):
 | 
			
		||||
    @expose('json')
 | 
			
		||||
    def get(self):
 | 
			
		||||
        resp = {}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,6 @@
 | 
			
		||||
import logging
 | 
			
		||||
# pecan imports
 | 
			
		||||
from pecan import expose, abort, response, request
 | 
			
		||||
from pecan.rest import RestController
 | 
			
		||||
import wsmeext.pecan as wsme_pecan
 | 
			
		||||
from wsme.exc import ClientSideError, InvalidInput
 | 
			
		||||
from wsme import Unset
 | 
			
		||||
@@ -32,11 +31,12 @@ from libra.api.library.gearman_client import submit_job
 | 
			
		||||
from libra.api.acl import get_limited_to_project
 | 
			
		||||
from libra.api.library.exp import OverLimit, IPOutOfRange
 | 
			
		||||
from libra.api.library.ip_filter import ipfilter
 | 
			
		||||
from libra.api.library.libra_rest_controller import LibraController
 | 
			
		||||
from pecan import conf
 | 
			
		||||
from sqlalchemy import func
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LoadBalancersController(RestController):
 | 
			
		||||
class LoadBalancersController(LibraController):
 | 
			
		||||
    def __init__(self, lbid=None):
 | 
			
		||||
        self.lbid = lbid
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,18 +14,18 @@
 | 
			
		||||
# under the License.
 | 
			
		||||
 | 
			
		||||
from pecan import request
 | 
			
		||||
from pecan.rest import RestController
 | 
			
		||||
from pecan import conf
 | 
			
		||||
import wsmeext.pecan as wsme_pecan
 | 
			
		||||
from wsme.exc import ClientSideError
 | 
			
		||||
from wsme import Unset
 | 
			
		||||
from libra.api.library.libra_rest_controller import LibraController
 | 
			
		||||
from libra.api.model.lbaas import LoadBalancer, Device, db_session
 | 
			
		||||
from libra.api.acl import get_limited_to_project
 | 
			
		||||
from libra.api.model.validators import LBLogsPost
 | 
			
		||||
from libra.api.library.gearman_client import submit_job
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LogsController(RestController):
 | 
			
		||||
class LogsController(LibraController):
 | 
			
		||||
    def __init__(self, load_balancer_id=None):
 | 
			
		||||
        self.lbid = load_balancer_id
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,11 +14,11 @@
 | 
			
		||||
# under the License.
 | 
			
		||||
 | 
			
		||||
from pecan import expose, response, request, abort
 | 
			
		||||
from pecan.rest import RestController
 | 
			
		||||
import wsmeext.pecan as wsme_pecan
 | 
			
		||||
from wsme.exc import ClientSideError
 | 
			
		||||
from wsme import Unset
 | 
			
		||||
#default response objects
 | 
			
		||||
from libra.api.library.libra_rest_controller import LibraController
 | 
			
		||||
from libra.api.model.lbaas import LoadBalancer, Node, db_session, Limits
 | 
			
		||||
from libra.api.model.lbaas import Device
 | 
			
		||||
from libra.api.acl import get_limited_to_project
 | 
			
		||||
@@ -30,7 +30,7 @@ from libra.api.library.ip_filter import ipfilter
 | 
			
		||||
from pecan import conf
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NodesController(RestController):
 | 
			
		||||
class NodesController(LibraController):
 | 
			
		||||
    """Functions for /loadbalancers/{load_balancer_id}/nodes/* routing"""
 | 
			
		||||
    def __init__(self, lbid, nodeid=None):
 | 
			
		||||
        self.lbid = lbid
 | 
			
		||||
 
 | 
			
		||||
@@ -14,10 +14,10 @@
 | 
			
		||||
# under the License.
 | 
			
		||||
 | 
			
		||||
from pecan import response
 | 
			
		||||
from pecan.rest import RestController
 | 
			
		||||
from libra.api.library.libra_rest_controller import LibraController
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SessionPersistenceController(RestController):
 | 
			
		||||
class SessionPersistenceController(LibraController):
 | 
			
		||||
    """SessionPersistenceController
 | 
			
		||||
    functions for /loadbalancers/{loadBalancerId}/sessionpersistence/* routing
 | 
			
		||||
    """
 | 
			
		||||
 
 | 
			
		||||
@@ -14,12 +14,12 @@
 | 
			
		||||
# under the License.
 | 
			
		||||
 | 
			
		||||
from pecan import response, expose, request
 | 
			
		||||
from pecan.rest import RestController
 | 
			
		||||
from libra.api.library.libra_rest_controller import LibraController
 | 
			
		||||
from libra.api.model.lbaas import LoadBalancer, Device, db_session
 | 
			
		||||
from libra.api.acl import get_limited_to_project
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class VipsController(RestController):
 | 
			
		||||
class VipsController(LibraController):
 | 
			
		||||
    def __init__(self, load_balancer_id=None):
 | 
			
		||||
        self.lbid = load_balancer_id
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ import logging
 | 
			
		||||
from libra.common.json_gearman import JSONGearmanClient
 | 
			
		||||
from libra.api.model.lbaas import LoadBalancer, db_session, Device
 | 
			
		||||
from libra.api.model.lbaas import loadbalancers_devices
 | 
			
		||||
from sqlalchemy.exc import OperationalError
 | 
			
		||||
from pecan import conf
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -38,21 +39,28 @@ def submit_job(job_type, host, data, lbid):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def client_job(logger, job_type, host, data, lbid):
 | 
			
		||||
    try:
 | 
			
		||||
        client = GearmanClientThread(logger, host, lbid)
 | 
			
		||||
        logger.info(
 | 
			
		||||
            "Sending Gearman job {0} to {1} for loadbalancer {2}".format(
 | 
			
		||||
                job_type, host, lbid
 | 
			
		||||
    for x in xrange(5):
 | 
			
		||||
        try:
 | 
			
		||||
            client = GearmanClientThread(logger, host, lbid)
 | 
			
		||||
            logger.info(
 | 
			
		||||
                "Sending Gearman job {0} to {1} for loadbalancer {2}".format(
 | 
			
		||||
                    job_type, host, lbid
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        if job_type == 'UPDATE':
 | 
			
		||||
            client.send_update(data)
 | 
			
		||||
        if job_type == 'DELETE':
 | 
			
		||||
            client.send_delete(data)
 | 
			
		||||
        if job_type == 'ARCHIVE':
 | 
			
		||||
            client.send_archive(data)
 | 
			
		||||
    except:
 | 
			
		||||
        logger.exception("Gearman thread unhandled exception")
 | 
			
		||||
            if job_type == 'UPDATE':
 | 
			
		||||
                client.send_update(data)
 | 
			
		||||
            if job_type == 'DELETE':
 | 
			
		||||
                client.send_delete(data)
 | 
			
		||||
            if job_type == 'ARCHIVE':
 | 
			
		||||
                client.send_archive(data)
 | 
			
		||||
            return
 | 
			
		||||
        except OperationalError as exc:
 | 
			
		||||
            # Auto retry on galera locking error
 | 
			
		||||
            logger.warning("Galera deadlock in gearman, retry {0}".format(x+1))
 | 
			
		||||
            if exc.args[0] != 1213:
 | 
			
		||||
                raise
 | 
			
		||||
        except:
 | 
			
		||||
            logger.exception("Gearman thread unhandled exception")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GearmanClientThread(object):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										94
									
								
								libra/api/library/libra_rest_controller.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								libra/api/library/libra_rest_controller.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
 | 
			
		||||
#
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
from pecan.rest import RestController
 | 
			
		||||
from pecan.core import request, abort
 | 
			
		||||
from pecan.decorators import expose
 | 
			
		||||
from inspect import getargspec
 | 
			
		||||
from webob import exc
 | 
			
		||||
from sqlalchemy.exc import OperationalError
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LibraController(RestController):
 | 
			
		||||
    routing_calls = 0
 | 
			
		||||
 | 
			
		||||
    @expose()
 | 
			
		||||
    def _route(self, args):
 | 
			
		||||
        '''
 | 
			
		||||
        Routes a request to the appropriate controller and returns its result.
 | 
			
		||||
 | 
			
		||||
        Performs a bit of validation - refuses to route delete and put actions
 | 
			
		||||
        via a GET request).
 | 
			
		||||
        '''
 | 
			
		||||
        # convention uses "_method" to handle browser-unsupported methods
 | 
			
		||||
        if request.environ.get('pecan.validation_redirected', False) is True:
 | 
			
		||||
            #
 | 
			
		||||
            # If the request has been internally redirected due to a validation
 | 
			
		||||
            # exception, we want the request method to be enforced as GET, not
 | 
			
		||||
            # the `_method` param which may have been passed for REST support.
 | 
			
		||||
            #
 | 
			
		||||
            method = request.method.lower()
 | 
			
		||||
        else:
 | 
			
		||||
            method = request.params.get('_method', request.method).lower()
 | 
			
		||||
 | 
			
		||||
        # make sure DELETE/PUT requests don't use GET
 | 
			
		||||
        if request.method == 'GET' and method in ('delete', 'put'):
 | 
			
		||||
            abort(405)
 | 
			
		||||
 | 
			
		||||
        # check for nested controllers
 | 
			
		||||
        result = self._find_sub_controllers(args)
 | 
			
		||||
        if result:
 | 
			
		||||
            return result
 | 
			
		||||
 | 
			
		||||
        # handle the request
 | 
			
		||||
        handler = getattr(self, '_handle_%s' % method, self._handle_custom)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            result = handler(method, args)
 | 
			
		||||
 | 
			
		||||
            #
 | 
			
		||||
            # If the signature of the handler does not match the number
 | 
			
		||||
            # of remaining positional arguments, attempt to handle
 | 
			
		||||
            # a _lookup method (if it exists)
 | 
			
		||||
            #
 | 
			
		||||
            argspec = getargspec(result[0])
 | 
			
		||||
            num_args = len(argspec[0][1:])
 | 
			
		||||
            if num_args < len(args):
 | 
			
		||||
                _lookup_result = self._handle_lookup(args)
 | 
			
		||||
                if _lookup_result:
 | 
			
		||||
                    return _lookup_result
 | 
			
		||||
        except exc.HTTPNotFound:
 | 
			
		||||
            #
 | 
			
		||||
            # If the matching handler results in a 404, attempt to handle
 | 
			
		||||
            # a _lookup method (if it exists)
 | 
			
		||||
            #
 | 
			
		||||
            _lookup_result = self._handle_lookup(args)
 | 
			
		||||
            if _lookup_result:
 | 
			
		||||
                return _lookup_result
 | 
			
		||||
        except OperationalError as sqlexc:
 | 
			
		||||
            logger = logging.getLogger(__name__)
 | 
			
		||||
            # if a galera transaction fails due to locking, retry the call
 | 
			
		||||
            if sqlexc.args[0] == 1213 and LibraController.routing_calls < 5:
 | 
			
		||||
                LibraController.routing_calls += 1
 | 
			
		||||
                logger.warning("Galera deadlock, retry: {0}".format(
 | 
			
		||||
                    LibraController.routing_calls)
 | 
			
		||||
                )
 | 
			
		||||
                result = self._route(args)
 | 
			
		||||
            else:
 | 
			
		||||
                raise
 | 
			
		||||
 | 
			
		||||
        LibraController.routing_calls = 0
 | 
			
		||||
        # return the result
 | 
			
		||||
        return result
 | 
			
		||||
		Reference in New Issue
	
	Block a user