Merge "proxy: refactor error limiter to a class"
This commit is contained in:
		
							
								
								
									
										91
									
								
								swift/common/error_limiter.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								swift/common/error_limiter.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
			
		||||
# Copyright (c) 2021 NVIDIA
 | 
			
		||||
#
 | 
			
		||||
# 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 collections
 | 
			
		||||
from time import time
 | 
			
		||||
 | 
			
		||||
from swift.common.utils import node_to_string
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ErrorLimiter(object):
 | 
			
		||||
    """
 | 
			
		||||
    Tracks the number of errors that have occurred for nodes. A node will be
 | 
			
		||||
    considered to be error-limited for a given interval of time after it has
 | 
			
		||||
    accumulated more errors than a given limit.
 | 
			
		||||
 | 
			
		||||
    :param suppression_interval: The number of seconds for which a node is
 | 
			
		||||
        error-limited once it has accumulated more than ``suppression_limit``
 | 
			
		||||
        errors. Should be a float value.
 | 
			
		||||
    :param suppression_limit: The number of errors that a node must accumulate
 | 
			
		||||
        before it is considered to be error-limited. Should be an int value.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, suppression_interval, suppression_limit):
 | 
			
		||||
        self.suppression_interval = float(suppression_interval)
 | 
			
		||||
        self.suppression_limit = int(suppression_limit)
 | 
			
		||||
        self.stats = collections.defaultdict(dict)
 | 
			
		||||
 | 
			
		||||
    def node_key(self, node):
 | 
			
		||||
        """
 | 
			
		||||
        Get the key under which a node's error stats will be stored.
 | 
			
		||||
 | 
			
		||||
        :param node: dictionary describing a node.
 | 
			
		||||
        :return: string key.
 | 
			
		||||
        """
 | 
			
		||||
        return node_to_string(node)
 | 
			
		||||
 | 
			
		||||
    def is_limited(self, node):
 | 
			
		||||
        """
 | 
			
		||||
        Check if the node is currently error limited.
 | 
			
		||||
 | 
			
		||||
        :param node: dictionary of node to check
 | 
			
		||||
        :returns: True if error limited, False otherwise
 | 
			
		||||
        """
 | 
			
		||||
        now = time()
 | 
			
		||||
        node_key = self.node_key(node)
 | 
			
		||||
        error_stats = self.stats.get(node_key)
 | 
			
		||||
 | 
			
		||||
        if error_stats is None or 'errors' not in error_stats:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        if 'last_error' in error_stats and error_stats['last_error'] < \
 | 
			
		||||
                now - self.suppression_interval:
 | 
			
		||||
            self.stats.pop(node_key)
 | 
			
		||||
            return False
 | 
			
		||||
        return error_stats['errors'] > self.suppression_limit
 | 
			
		||||
 | 
			
		||||
    def limit(self, node):
 | 
			
		||||
        """
 | 
			
		||||
        Mark a node as error limited. This immediately pretends the
 | 
			
		||||
        node received enough errors to trigger error suppression. Use
 | 
			
		||||
        this for errors like Insufficient Storage. For other errors
 | 
			
		||||
        use :func:`increment`.
 | 
			
		||||
 | 
			
		||||
        :param node: dictionary of node to error limit
 | 
			
		||||
        """
 | 
			
		||||
        node_key = self.node_key(node)
 | 
			
		||||
        error_stats = self.stats[node_key]
 | 
			
		||||
        error_stats['errors'] = self.suppression_limit + 1
 | 
			
		||||
        error_stats['last_error'] = time()
 | 
			
		||||
 | 
			
		||||
    def increment(self, node):
 | 
			
		||||
        """
 | 
			
		||||
        Increment the error count and update the time of the last error for
 | 
			
		||||
        the given ``node``.
 | 
			
		||||
 | 
			
		||||
        :param node: dictionary describing a node.
 | 
			
		||||
        """
 | 
			
		||||
        node_key = self.node_key(node)
 | 
			
		||||
        error_stats = self.stats[node_key]
 | 
			
		||||
        error_stats['errors'] = error_stats.get('errors', 0) + 1
 | 
			
		||||
        error_stats['last_error'] = time()
 | 
			
		||||
@@ -31,6 +31,7 @@ from swift.common import constraints
 | 
			
		||||
from swift.common.http import is_server_error
 | 
			
		||||
from swift.common.storage_policy import POLICIES
 | 
			
		||||
from swift.common.ring import Ring
 | 
			
		||||
from swift.common.error_limiter import ErrorLimiter
 | 
			
		||||
from swift.common.utils import Watchdog, get_logger, \
 | 
			
		||||
    get_remote_client, split_path, config_true_value, generate_trans_id, \
 | 
			
		||||
    affinity_key_function, affinity_locality_predicate, list_from_csv, \
 | 
			
		||||
@@ -204,7 +205,6 @@ class Application(object):
 | 
			
		||||
                                     statsd_tail_prefix='proxy-server')
 | 
			
		||||
        else:
 | 
			
		||||
            self.logger = logger
 | 
			
		||||
        self._error_limiting = {}
 | 
			
		||||
        self.backend_user_agent = 'proxy-server %s' % os.getpid()
 | 
			
		||||
 | 
			
		||||
        swift_dir = conf.get('swift_dir', '/etc/swift')
 | 
			
		||||
@@ -218,10 +218,12 @@ class Application(object):
 | 
			
		||||
        self.client_chunk_size = int(conf.get('client_chunk_size', 65536))
 | 
			
		||||
        self.trans_id_suffix = conf.get('trans_id_suffix', '')
 | 
			
		||||
        self.post_quorum_timeout = float(conf.get('post_quorum_timeout', 0.5))
 | 
			
		||||
        self.error_suppression_interval = \
 | 
			
		||||
        error_suppression_interval = \
 | 
			
		||||
            float(conf.get('error_suppression_interval', 60))
 | 
			
		||||
        self.error_suppression_limit = \
 | 
			
		||||
        error_suppression_limit = \
 | 
			
		||||
            int(conf.get('error_suppression_limit', 10))
 | 
			
		||||
        self.error_limiter = ErrorLimiter(error_suppression_interval,
 | 
			
		||||
                                          error_suppression_limit)
 | 
			
		||||
        self.recheck_container_existence = \
 | 
			
		||||
            int(conf.get('recheck_container_existence',
 | 
			
		||||
                         DEFAULT_RECHECK_CONTAINER_EXISTENCE))
 | 
			
		||||
@@ -646,9 +648,6 @@ class Application(object):
 | 
			
		||||
        timing = round(timing, 3)  # sort timings to the millisecond
 | 
			
		||||
        self.node_timings[node['ip']] = (timing, now + self.timing_expiry)
 | 
			
		||||
 | 
			
		||||
    def _error_limit_node_key(self, node):
 | 
			
		||||
        return node_to_string(node)
 | 
			
		||||
 | 
			
		||||
    def error_limited(self, node):
 | 
			
		||||
        """
 | 
			
		||||
        Check if the node is currently error limited.
 | 
			
		||||
@@ -656,17 +655,7 @@ class Application(object):
 | 
			
		||||
        :param node: dictionary of node to check
 | 
			
		||||
        :returns: True if error limited, False otherwise
 | 
			
		||||
        """
 | 
			
		||||
        now = time()
 | 
			
		||||
        node_key = self._error_limit_node_key(node)
 | 
			
		||||
        error_stats = self._error_limiting.get(node_key)
 | 
			
		||||
 | 
			
		||||
        if error_stats is None or 'errors' not in error_stats:
 | 
			
		||||
            return False
 | 
			
		||||
        if 'last_error' in error_stats and error_stats['last_error'] < \
 | 
			
		||||
                now - self.error_suppression_interval:
 | 
			
		||||
            self._error_limiting.pop(node_key, None)
 | 
			
		||||
            return False
 | 
			
		||||
        limited = error_stats['errors'] > self.error_suppression_limit
 | 
			
		||||
        limited = self.error_limiter.is_limited(node)
 | 
			
		||||
        if limited:
 | 
			
		||||
            self.logger.debug(
 | 
			
		||||
                'Node error limited: %s', node_to_string(node))
 | 
			
		||||
@@ -677,24 +666,15 @@ class Application(object):
 | 
			
		||||
        Mark a node as error limited. This immediately pretends the
 | 
			
		||||
        node received enough errors to trigger error suppression. Use
 | 
			
		||||
        this for errors like Insufficient Storage. For other errors
 | 
			
		||||
        use :func:`error_occurred`.
 | 
			
		||||
        use :func:`increment`.
 | 
			
		||||
 | 
			
		||||
        :param node: dictionary of node to error limit
 | 
			
		||||
        :param msg: error message
 | 
			
		||||
        """
 | 
			
		||||
        node_key = self._error_limit_node_key(node)
 | 
			
		||||
        error_stats = self._error_limiting.setdefault(node_key, {})
 | 
			
		||||
        error_stats['errors'] = self.error_suppression_limit + 1
 | 
			
		||||
        error_stats['last_error'] = time()
 | 
			
		||||
        self.error_limiter.limit(node)
 | 
			
		||||
        self.logger.error('%(msg)s %(node)s',
 | 
			
		||||
                          {'msg': msg, 'node': node_to_string(node)})
 | 
			
		||||
 | 
			
		||||
    def _incr_node_errors(self, node):
 | 
			
		||||
        node_key = self._error_limit_node_key(node)
 | 
			
		||||
        error_stats = self._error_limiting.setdefault(node_key, {})
 | 
			
		||||
        error_stats['errors'] = error_stats.get('errors', 0) + 1
 | 
			
		||||
        error_stats['last_error'] = time()
 | 
			
		||||
 | 
			
		||||
    def error_occurred(self, node, msg):
 | 
			
		||||
        """
 | 
			
		||||
        Handle logging, and handling of errors.
 | 
			
		||||
@@ -702,7 +682,7 @@ class Application(object):
 | 
			
		||||
        :param node: dictionary of node to handle errors for
 | 
			
		||||
        :param msg: error message
 | 
			
		||||
        """
 | 
			
		||||
        self._incr_node_errors(node)
 | 
			
		||||
        self.error_limiter.increment(node)
 | 
			
		||||
        if isinstance(msg, bytes):
 | 
			
		||||
            msg = msg.decode('utf-8')
 | 
			
		||||
        self.logger.error('%(msg)s %(node)s',
 | 
			
		||||
@@ -721,7 +701,7 @@ class Application(object):
 | 
			
		||||
        :param typ: server type
 | 
			
		||||
        :param additional_info: additional information to log
 | 
			
		||||
        """
 | 
			
		||||
        self._incr_node_errors(node)
 | 
			
		||||
        self.error_limiter.increment(node)
 | 
			
		||||
        if 'level' in kwargs:
 | 
			
		||||
            log = functools.partial(self.logger.log, kwargs.pop('level'))
 | 
			
		||||
            if 'exc_info' not in kwargs:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										102
									
								
								test/unit/common/test_error_limiter.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								test/unit/common/test_error_limiter.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
# Copyright (c) 2021 NVIDIA
 | 
			
		||||
#
 | 
			
		||||
# 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 unittest
 | 
			
		||||
import mock
 | 
			
		||||
from time import time
 | 
			
		||||
 | 
			
		||||
from swift.common.error_limiter import ErrorLimiter
 | 
			
		||||
from test.unit import FakeRing
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestErrorLimiter(unittest.TestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        self.ring = FakeRing()
 | 
			
		||||
 | 
			
		||||
    def test_init_config(self):
 | 
			
		||||
        config = {'suppression_interval': 100.9,
 | 
			
		||||
                  'suppression_limit': 5}
 | 
			
		||||
        limiter = ErrorLimiter(**config)
 | 
			
		||||
        self.assertEqual(limiter.suppression_interval, 100.9)
 | 
			
		||||
        self.assertEqual(limiter.suppression_limit, 5)
 | 
			
		||||
 | 
			
		||||
        config = {'suppression_interval': '100.9',
 | 
			
		||||
                  'suppression_limit': '5'}
 | 
			
		||||
        limiter = ErrorLimiter(**config)
 | 
			
		||||
        self.assertEqual(limiter.suppression_interval, 100.9)
 | 
			
		||||
        self.assertEqual(limiter.suppression_limit, 5)
 | 
			
		||||
 | 
			
		||||
    def test_init_bad_config(self):
 | 
			
		||||
        with self.assertRaises(ValueError):
 | 
			
		||||
            ErrorLimiter(suppression_interval='bad',
 | 
			
		||||
                         suppression_limit=1)
 | 
			
		||||
 | 
			
		||||
        with self.assertRaises(TypeError):
 | 
			
		||||
            ErrorLimiter(suppression_interval=None,
 | 
			
		||||
                         suppression_limit=1)
 | 
			
		||||
 | 
			
		||||
        with self.assertRaises(ValueError):
 | 
			
		||||
            ErrorLimiter(suppression_interval=0,
 | 
			
		||||
                         suppression_limit='bad')
 | 
			
		||||
 | 
			
		||||
        with self.assertRaises(TypeError):
 | 
			
		||||
            ErrorLimiter(suppression_interval=0,
 | 
			
		||||
                         suppression_limit=None)
 | 
			
		||||
 | 
			
		||||
    def test_is_limited(self):
 | 
			
		||||
        node = self.ring.devs[-1]
 | 
			
		||||
        limiter = ErrorLimiter(suppression_interval=60, suppression_limit=10)
 | 
			
		||||
 | 
			
		||||
        now = time()
 | 
			
		||||
        with mock.patch('swift.common.error_limiter.time', return_value=now):
 | 
			
		||||
            self.assertFalse(limiter.is_limited(node))
 | 
			
		||||
            limiter.limit(node)
 | 
			
		||||
            self.assertTrue(limiter.is_limited(node))
 | 
			
		||||
            node_key = limiter.node_key(node)
 | 
			
		||||
            self.assertEqual(limiter.stats.get(node_key),
 | 
			
		||||
                             {'errors': limiter.suppression_limit + 1,
 | 
			
		||||
                              'last_error': now})
 | 
			
		||||
 | 
			
		||||
    def test_increment(self):
 | 
			
		||||
        node = self.ring.devs[-1]
 | 
			
		||||
        limiter = ErrorLimiter(suppression_interval=60, suppression_limit=10)
 | 
			
		||||
        last_errors = 0
 | 
			
		||||
        node_key = limiter.node_key(node)
 | 
			
		||||
        for _ in range(limiter.suppression_limit):
 | 
			
		||||
            limiter.increment(node)
 | 
			
		||||
            node_errors = limiter.stats.get(node_key)
 | 
			
		||||
            self.assertGreater(node_errors['errors'], last_errors)
 | 
			
		||||
            self.assertFalse(limiter.is_limited(node))
 | 
			
		||||
            last_errors = node_errors['errors']
 | 
			
		||||
 | 
			
		||||
        # One more to make sure it is > suppression_limit
 | 
			
		||||
        limiter.increment(node)
 | 
			
		||||
        node_errors = limiter.stats.get(node_key)
 | 
			
		||||
        self.assertEqual(limiter.suppression_limit + 1,
 | 
			
		||||
                         node_errors['errors'])
 | 
			
		||||
        self.assertTrue(limiter.is_limited(node))
 | 
			
		||||
        last_time = node_errors['last_error']
 | 
			
		||||
 | 
			
		||||
        # Simulate time with no errors have gone by.
 | 
			
		||||
        now = last_time + limiter.suppression_interval + 1
 | 
			
		||||
        with mock.patch('swift.common.error_limiter.time',
 | 
			
		||||
                        return_value=now):
 | 
			
		||||
            self.assertFalse(limiter.is_limited(node))
 | 
			
		||||
            self.assertFalse(limiter.stats.get(node_key))
 | 
			
		||||
 | 
			
		||||
    def test_node_key(self):
 | 
			
		||||
        limiter = ErrorLimiter(suppression_interval=60, suppression_limit=10)
 | 
			
		||||
        node = self.ring.devs[0]
 | 
			
		||||
        expected = '%s:%s/%s' % (node['ip'], node['port'], node['device'])
 | 
			
		||||
        self.assertEqual(expected, limiter.node_key(node))
 | 
			
		||||
@@ -318,7 +318,7 @@ class TestContainerController(TestRingBase):
 | 
			
		||||
 | 
			
		||||
        for method in ('PUT', 'DELETE', 'POST'):
 | 
			
		||||
            def test_status_map(statuses, expected):
 | 
			
		||||
                self.app._error_limiting = {}
 | 
			
		||||
                self.app.error_limiter.stats.clear()
 | 
			
		||||
                req = Request.blank('/v1/a/c', method=method)
 | 
			
		||||
                with mocked_http_conn(*statuses) as fake_conn:
 | 
			
		||||
                    resp = req.get_response(self.app)
 | 
			
		||||
@@ -355,7 +355,7 @@ class TestContainerController(TestRingBase):
 | 
			
		||||
            test_status_map(base_status[:2] + [507] + base_status[2:], 201)
 | 
			
		||||
            self.assertEqual(node_error_count(
 | 
			
		||||
                self.app, self.container_ring.devs[2]),
 | 
			
		||||
                self.app.error_suppression_limit + 1)
 | 
			
		||||
                self.app.error_limiter.suppression_limit + 1)
 | 
			
		||||
 | 
			
		||||
    def test_response_codes_for_GET(self):
 | 
			
		||||
        nodes = self.app.container_ring.replicas
 | 
			
		||||
 
 | 
			
		||||
@@ -1214,7 +1214,7 @@ class TestReplicatedObjController(CommonObjectControllerMixin,
 | 
			
		||||
        self.app.sort_nodes = lambda n, *args, **kwargs: n  # disable shuffle
 | 
			
		||||
 | 
			
		||||
        def test_status_map(statuses, expected):
 | 
			
		||||
            self.app._error_limiting = {}
 | 
			
		||||
            self.app.error_limiter.stats.clear()
 | 
			
		||||
            req = swob.Request.blank('/v1/a/c/o.jpg', method='PUT',
 | 
			
		||||
                                     body=b'test body')
 | 
			
		||||
            with set_http_connect(*statuses):
 | 
			
		||||
@@ -1249,7 +1249,7 @@ class TestReplicatedObjController(CommonObjectControllerMixin,
 | 
			
		||||
        test_status_map(((507, None), 201, 201, 201), 201)
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            node_error_count(self.app, object_ring.devs[0]),
 | 
			
		||||
            self.app.error_suppression_limit + 1)
 | 
			
		||||
            self.app.error_limiter.suppression_limit + 1)
 | 
			
		||||
        # response errors
 | 
			
		||||
        test_status_map(((100, Timeout()), 201, 201), 201)
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
@@ -1260,7 +1260,7 @@ class TestReplicatedObjController(CommonObjectControllerMixin,
 | 
			
		||||
        test_status_map((201, (100, 507), 201), 201)
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            node_error_count(self.app, object_ring.devs[1]),
 | 
			
		||||
            self.app.error_suppression_limit + 1)
 | 
			
		||||
            self.app.error_limiter.suppression_limit + 1)
 | 
			
		||||
 | 
			
		||||
    def test_PUT_connect_exception_with_unicode_path(self):
 | 
			
		||||
        expected = 201
 | 
			
		||||
@@ -2385,7 +2385,13 @@ class TestECObjController(ECObjectControllerMixin, unittest.TestCase):
 | 
			
		||||
            }
 | 
			
		||||
            for r in log.requests[:4]
 | 
			
		||||
        }
 | 
			
		||||
        self.assertEqual(self.app._error_limiting, expected_error_limiting)
 | 
			
		||||
        actual = {}
 | 
			
		||||
        for n in self.app.get_object_ring(int(self.policy)).devs:
 | 
			
		||||
            node_key = self.app.error_limiter.node_key(n)
 | 
			
		||||
            stats = self.app.error_limiter.stats.get(node_key) or {}
 | 
			
		||||
            if stats:
 | 
			
		||||
                actual[self.app.error_limiter.node_key(n)] = stats
 | 
			
		||||
        self.assertEqual(actual, expected_error_limiting)
 | 
			
		||||
 | 
			
		||||
    def test_GET_not_found_when_404_newer(self):
 | 
			
		||||
        # if proxy receives a 404, it keeps waiting for other connections until
 | 
			
		||||
@@ -2410,14 +2416,14 @@ class TestECObjController(ECObjectControllerMixin, unittest.TestCase):
 | 
			
		||||
            with mocked_http_conn(*codes):
 | 
			
		||||
                resp = req.get_response(self.app)
 | 
			
		||||
            self.assertEqual(resp.status_int, 404)
 | 
			
		||||
            self.app._error_limiting = {}  # Reset error limiting
 | 
			
		||||
            self.app.error_limiter.stats.clear()  # Reset error limiting
 | 
			
		||||
 | 
			
		||||
        # one more timeout is past the tipping point
 | 
			
		||||
        codes[self.policy.object_ring.replica_count - 2] = Timeout()
 | 
			
		||||
        with mocked_http_conn(*codes):
 | 
			
		||||
            resp = req.get_response(self.app)
 | 
			
		||||
        self.assertEqual(resp.status_int, 503)
 | 
			
		||||
        self.app._error_limiting = {}  # Reset error limiting
 | 
			
		||||
        self.app.error_limiter.stats.clear()  # Reset error limiting
 | 
			
		||||
 | 
			
		||||
        # unless we have tombstones
 | 
			
		||||
        with mocked_http_conn(*codes, headers={'X-Backend-Timestamp': '1'}):
 | 
			
		||||
 
 | 
			
		||||
@@ -150,26 +150,29 @@ def parse_headers_string(headers_str):
 | 
			
		||||
    return headers_dict
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_node_error_stats(proxy_app, ring_node):
 | 
			
		||||
    node_key = proxy_app.error_limiter.node_key(ring_node)
 | 
			
		||||
    return proxy_app.error_limiter.stats.get(node_key) or {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def node_error_count(proxy_app, ring_node):
 | 
			
		||||
    # Reach into the proxy's internals to get the error count for a
 | 
			
		||||
    # particular node
 | 
			
		||||
    node_key = proxy_app._error_limit_node_key(ring_node)
 | 
			
		||||
    return proxy_app._error_limiting.get(node_key, {}).get('errors', 0)
 | 
			
		||||
    return get_node_error_stats(proxy_app, ring_node).get('errors', 0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def node_last_error(proxy_app, ring_node):
 | 
			
		||||
    # Reach into the proxy's internals to get the last error for a
 | 
			
		||||
    # particular node
 | 
			
		||||
    node_key = proxy_app._error_limit_node_key(ring_node)
 | 
			
		||||
    return proxy_app._error_limiting.get(node_key, {}).get('last_error')
 | 
			
		||||
    return get_node_error_stats(proxy_app, ring_node).get('last_error')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def set_node_errors(proxy_app, ring_node, value, last_error):
 | 
			
		||||
    # Set the node's error count to value
 | 
			
		||||
    node_key = proxy_app._error_limit_node_key(ring_node)
 | 
			
		||||
    stats = proxy_app._error_limiting.setdefault(node_key, {})
 | 
			
		||||
    stats['errors'] = value
 | 
			
		||||
    stats['last_error'] = last_error
 | 
			
		||||
    node_key = proxy_app.error_limiter.node_key(ring_node)
 | 
			
		||||
    stats = {'errors': value,
 | 
			
		||||
             'last_error': last_error}
 | 
			
		||||
    proxy_app.error_limiter.stats[node_key] = stats
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@contextmanager
 | 
			
		||||
@@ -239,7 +242,7 @@ class TestController(unittest.TestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        skip_if_no_xattrs()
 | 
			
		||||
        _test_servers[0]._error_limiting = {}  # clear out errors
 | 
			
		||||
        _test_servers[0].error_limiter.stats.clear()  # clear out errors
 | 
			
		||||
        self.account_ring = FakeRing()
 | 
			
		||||
        self.container_ring = FakeRing()
 | 
			
		||||
        self.memcache = FakeMemcache()
 | 
			
		||||
@@ -1169,15 +1172,15 @@ class TestProxyServer(unittest.TestCase):
 | 
			
		||||
                                           container_ring=FakeRing(),
 | 
			
		||||
                                           logger=logger)
 | 
			
		||||
            node = app.container_ring.get_part_nodes(0)[0]
 | 
			
		||||
            node_key = app._error_limit_node_key(node)
 | 
			
		||||
            self.assertNotIn(node_key, app._error_limiting)  # sanity
 | 
			
		||||
            node_key = app.error_limiter.node_key(node)
 | 
			
		||||
            self.assertNotIn(node_key, app.error_limiter.stats)  # sanity
 | 
			
		||||
            try:
 | 
			
		||||
                raise Exception('kaboom1!')
 | 
			
		||||
            except Exception as err:
 | 
			
		||||
                caught_exc = err
 | 
			
		||||
                app.exception_occurred(node, 'server-type', additional_info)
 | 
			
		||||
 | 
			
		||||
            self.assertEqual(1, app._error_limiting[node_key]['errors'])
 | 
			
		||||
            self.assertEqual(1, node_error_count(app, node))
 | 
			
		||||
            line = logger.get_lines_for_level('error')[-1]
 | 
			
		||||
            self.assertIn('server-type server', line)
 | 
			
		||||
            if six.PY2:
 | 
			
		||||
@@ -1203,12 +1206,12 @@ class TestProxyServer(unittest.TestCase):
 | 
			
		||||
                                           container_ring=FakeRing(),
 | 
			
		||||
                                           logger=logger)
 | 
			
		||||
            node = app.container_ring.get_part_nodes(0)[0]
 | 
			
		||||
            node_key = app._error_limit_node_key(node)
 | 
			
		||||
            self.assertNotIn(node_key, app._error_limiting)  # sanity
 | 
			
		||||
            node_key = app.error_limiter.node_key(node)
 | 
			
		||||
            self.assertNotIn(node_key, app.error_limiter.stats)  # sanity
 | 
			
		||||
 | 
			
		||||
            app.error_occurred(node, msg)
 | 
			
		||||
 | 
			
		||||
            self.assertEqual(1, app._error_limiting[node_key]['errors'])
 | 
			
		||||
            self.assertEqual(1, node_error_count(app, node))
 | 
			
		||||
            line = logger.get_lines_for_level('error')[-1]
 | 
			
		||||
            if six.PY2:
 | 
			
		||||
                self.assertIn(msg.decode('utf8'), line)
 | 
			
		||||
@@ -1501,7 +1504,7 @@ class TestProxyServerConfigLoading(unittest.TestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        skip_if_no_xattrs()
 | 
			
		||||
        _test_servers[0]._error_limiting = {}  # clear out errors
 | 
			
		||||
        _test_servers[0].error_limiter.stats.clear()  # clear out errors
 | 
			
		||||
        self.tempdir = mkdtemp()
 | 
			
		||||
        account_ring_path = os.path.join(self.tempdir, 'account.ring.gz')
 | 
			
		||||
        write_fake_ring(account_ring_path)
 | 
			
		||||
@@ -2362,7 +2365,7 @@ class TestReplicatedObjectController(
 | 
			
		||||
    """
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        skip_if_no_xattrs()
 | 
			
		||||
        _test_servers[0]._error_limiting = {}  # clear out errors
 | 
			
		||||
        _test_servers[0].error_limiter.stats.clear()  # clear out errors
 | 
			
		||||
        self.logger = debug_logger('proxy-ut')
 | 
			
		||||
        self.app = proxy_server.Application(
 | 
			
		||||
            None,
 | 
			
		||||
@@ -3022,7 +3025,7 @@ class TestReplicatedObjectController(
 | 
			
		||||
        bytes_before_timeout[0] = 700
 | 
			
		||||
        kaboomed[0] = 0
 | 
			
		||||
        sabotaged[0] = False
 | 
			
		||||
        prosrv._error_limiting = {}  # clear out errors
 | 
			
		||||
        prosrv.error_limiter.stats.clear()  # clear out errors
 | 
			
		||||
        with mock.patch.object(proxy_base,
 | 
			
		||||
                               'http_response_to_document_iters',
 | 
			
		||||
                               sabotaged_hrtdi):  # perma-broken
 | 
			
		||||
@@ -3062,7 +3065,7 @@ class TestReplicatedObjectController(
 | 
			
		||||
        bytes_before_timeout[0] = 300
 | 
			
		||||
        kaboomed[0] = 0
 | 
			
		||||
        sabotaged[0] = False
 | 
			
		||||
        prosrv._error_limiting = {}  # clear out errors
 | 
			
		||||
        prosrv.error_limiter.stats.clear()  # clear out errors
 | 
			
		||||
        with mock.patch.object(proxy_base,
 | 
			
		||||
                               'http_response_to_document_iters',
 | 
			
		||||
                               single_sabotage_hrtdi):
 | 
			
		||||
@@ -3102,7 +3105,7 @@ class TestReplicatedObjectController(
 | 
			
		||||
        bytes_before_timeout[0] = 501
 | 
			
		||||
        kaboomed[0] = 0
 | 
			
		||||
        sabotaged[0] = False
 | 
			
		||||
        prosrv._error_limiting = {}  # clear out errors
 | 
			
		||||
        prosrv.error_limiter.stats.clear()  # clear out errors
 | 
			
		||||
        with mock.patch.object(proxy_base,
 | 
			
		||||
                               'http_response_to_document_iters',
 | 
			
		||||
                               single_sabotage_hrtdi):
 | 
			
		||||
@@ -3142,7 +3145,7 @@ class TestReplicatedObjectController(
 | 
			
		||||
        bytes_before_timeout[0] = 750
 | 
			
		||||
        kaboomed[0] = 0
 | 
			
		||||
        sabotaged[0] = False
 | 
			
		||||
        prosrv._error_limiting = {}  # clear out errors
 | 
			
		||||
        prosrv.error_limiter.stats.clear()  # clear out errors
 | 
			
		||||
        with mock.patch.object(proxy_base,
 | 
			
		||||
                               'http_response_to_document_iters',
 | 
			
		||||
                               single_sabotage_hrtdi):
 | 
			
		||||
@@ -5081,7 +5084,7 @@ class TestReplicatedObjectController(
 | 
			
		||||
                self.app.log_handoffs = True
 | 
			
		||||
                self.app.logger.clear()  # clean capture state
 | 
			
		||||
                self.app.request_node_count = lambda r: 7
 | 
			
		||||
                self.app._error_limiting = {}  # clear out errors
 | 
			
		||||
                self.app.error_limiter.stats.clear()  # clear out errors
 | 
			
		||||
                set_node_errors(self.app, object_ring._devs[0], 999,
 | 
			
		||||
                                last_error=(2 ** 63 - 1))
 | 
			
		||||
 | 
			
		||||
@@ -5100,7 +5103,7 @@ class TestReplicatedObjectController(
 | 
			
		||||
                self.app.log_handoffs = True
 | 
			
		||||
                self.app.logger.clear()  # clean capture state
 | 
			
		||||
                self.app.request_node_count = lambda r: 7
 | 
			
		||||
                self.app._error_limiting = {}  # clear out errors
 | 
			
		||||
                self.app.error_limiter.stats.clear()  # clear out errors
 | 
			
		||||
                for i in range(2):
 | 
			
		||||
                    set_node_errors(self.app, object_ring._devs[i], 999,
 | 
			
		||||
                                    last_error=(2 ** 63 - 1))
 | 
			
		||||
@@ -5125,7 +5128,7 @@ class TestReplicatedObjectController(
 | 
			
		||||
                self.app.logger.clear()  # clean capture state
 | 
			
		||||
                self.app.request_node_count = lambda r: 10
 | 
			
		||||
                object_ring.set_replicas(4)  # otherwise we run out of handoffs
 | 
			
		||||
                self.app._error_limiting = {}  # clear out errors
 | 
			
		||||
                self.app.error_limiter.stats.clear()  # clear out errors
 | 
			
		||||
                for i in range(4):
 | 
			
		||||
                    set_node_errors(self.app, object_ring._devs[i], 999,
 | 
			
		||||
                                    last_error=(2 ** 63 - 1))
 | 
			
		||||
@@ -5290,12 +5293,12 @@ class TestReplicatedObjectController(
 | 
			
		||||
            self.assertTrue(
 | 
			
		||||
                node_last_error(controller.app, object_ring.devs[0])
 | 
			
		||||
                is not None)
 | 
			
		||||
            for _junk in range(self.app.error_suppression_limit):
 | 
			
		||||
            for _junk in range(self.app.error_limiter.suppression_limit):
 | 
			
		||||
                self.assert_status_map(controller.HEAD, (200, 200, 503, 503,
 | 
			
		||||
                                                         503), 503)
 | 
			
		||||
            self.assertEqual(
 | 
			
		||||
                node_error_count(controller.app, object_ring.devs[0]),
 | 
			
		||||
                self.app.error_suppression_limit + 1)
 | 
			
		||||
                self.app.error_limiter.suppression_limit + 1)
 | 
			
		||||
            self.assert_status_map(controller.HEAD, (200, 200, 200, 200, 200),
 | 
			
		||||
                                   503)
 | 
			
		||||
            self.assertTrue(
 | 
			
		||||
@@ -5308,7 +5311,7 @@ class TestReplicatedObjectController(
 | 
			
		||||
                                    202), 503)
 | 
			
		||||
            self.assert_status_map(controller.DELETE,
 | 
			
		||||
                                   (200, 200, 200, 204, 204, 204), 503)
 | 
			
		||||
            self.app.error_suppression_interval = -300
 | 
			
		||||
            self.app.error_limiter.suppression_interval = -300
 | 
			
		||||
            self.assert_status_map(controller.HEAD, (200, 200, 200, 200, 200),
 | 
			
		||||
                                   200)
 | 
			
		||||
            self.assertRaises(BaseException,
 | 
			
		||||
@@ -5329,12 +5332,12 @@ class TestReplicatedObjectController(
 | 
			
		||||
            self.assertTrue(
 | 
			
		||||
                node_last_error(controller.app, object_ring.devs[0])
 | 
			
		||||
                is not None)
 | 
			
		||||
            for _junk in range(self.app.error_suppression_limit):
 | 
			
		||||
            for _junk in range(self.app.error_limiter.suppression_limit):
 | 
			
		||||
                self.assert_status_map(controller.HEAD, (200, 200, 503, 503,
 | 
			
		||||
                                                         503), 503)
 | 
			
		||||
            self.assertEqual(
 | 
			
		||||
                node_error_count(controller.app, object_ring.devs[0]),
 | 
			
		||||
                self.app.error_suppression_limit + 1)
 | 
			
		||||
                self.app.error_limiter.suppression_limit + 1)
 | 
			
		||||
 | 
			
		||||
            # wipe out any state in the ring
 | 
			
		||||
            for policy in POLICIES:
 | 
			
		||||
@@ -5360,10 +5363,9 @@ class TestReplicatedObjectController(
 | 
			
		||||
            self.assertEqual(node_error_count(controller.app, odevs[0]), 2)
 | 
			
		||||
            self.assertEqual(node_error_count(controller.app, odevs[1]), 0)
 | 
			
		||||
            self.assertEqual(node_error_count(controller.app, odevs[2]), 0)
 | 
			
		||||
            self.assertTrue(
 | 
			
		||||
                node_last_error(controller.app, odevs[0]) is not None)
 | 
			
		||||
            self.assertTrue(node_last_error(controller.app, odevs[1]) is None)
 | 
			
		||||
            self.assertTrue(node_last_error(controller.app, odevs[2]) is None)
 | 
			
		||||
            self.assertIsNotNone(node_last_error(controller.app, odevs[0]))
 | 
			
		||||
            self.assertIsNone(node_last_error(controller.app, odevs[1]))
 | 
			
		||||
            self.assertIsNone(node_last_error(controller.app, odevs[2]))
 | 
			
		||||
 | 
			
		||||
    def test_PUT_error_limiting_last_node(self):
 | 
			
		||||
        with save_globals():
 | 
			
		||||
@@ -5380,14 +5382,13 @@ class TestReplicatedObjectController(
 | 
			
		||||
            self.assertEqual(node_error_count(controller.app, odevs[0]), 0)
 | 
			
		||||
            self.assertEqual(node_error_count(controller.app, odevs[1]), 0)
 | 
			
		||||
            self.assertEqual(node_error_count(controller.app, odevs[2]), 2)
 | 
			
		||||
            self.assertTrue(node_last_error(controller.app, odevs[0]) is None)
 | 
			
		||||
            self.assertTrue(node_last_error(controller.app, odevs[1]) is None)
 | 
			
		||||
            self.assertTrue(
 | 
			
		||||
                node_last_error(controller.app, odevs[2]) is not None)
 | 
			
		||||
            self.assertIsNone(node_last_error(controller.app, odevs[0]))
 | 
			
		||||
            self.assertIsNone(node_last_error(controller.app, odevs[1]))
 | 
			
		||||
            self.assertIsNotNone(node_last_error(controller.app, odevs[2]))
 | 
			
		||||
 | 
			
		||||
    def test_acc_or_con_missing_returns_404(self):
 | 
			
		||||
        with save_globals():
 | 
			
		||||
            self.app._error_limiting = {}
 | 
			
		||||
            self.app.error_limiter.stats.clear()
 | 
			
		||||
            controller = ReplicatedObjectController(
 | 
			
		||||
                self.app, 'account', 'container', 'object')
 | 
			
		||||
            set_http_connect(200, 200, 200, 200, 200, 200)
 | 
			
		||||
@@ -5455,7 +5456,8 @@ class TestReplicatedObjectController(
 | 
			
		||||
 | 
			
		||||
            for dev in self.app.account_ring.devs:
 | 
			
		||||
                set_node_errors(
 | 
			
		||||
                    self.app, dev, self.app.error_suppression_limit + 1,
 | 
			
		||||
                    self.app, dev,
 | 
			
		||||
                    self.app.error_limiter.suppression_limit + 1,
 | 
			
		||||
                    time.time())
 | 
			
		||||
            set_http_connect(200)
 | 
			
		||||
            #                acct [isn't actually called since everything
 | 
			
		||||
@@ -5469,9 +5471,10 @@ class TestReplicatedObjectController(
 | 
			
		||||
            for dev in self.app.account_ring.devs:
 | 
			
		||||
                set_node_errors(self.app, dev, 0, last_error=None)
 | 
			
		||||
            for dev in self.app.container_ring.devs:
 | 
			
		||||
                set_node_errors(self.app, dev,
 | 
			
		||||
                                self.app.error_suppression_limit + 1,
 | 
			
		||||
                                time.time())
 | 
			
		||||
                set_node_errors(
 | 
			
		||||
                    self.app, dev,
 | 
			
		||||
                    self.app.error_limiter.suppression_limit + 1,
 | 
			
		||||
                    time.time())
 | 
			
		||||
            set_http_connect(200, 200)
 | 
			
		||||
            #                acct cont [isn't actually called since
 | 
			
		||||
            #                           everything is error limited]
 | 
			
		||||
@@ -8039,7 +8042,7 @@ class TestECMismatchedFA(unittest.TestCase):
 | 
			
		||||
    def tearDown(self):
 | 
			
		||||
        prosrv = _test_servers[0]
 | 
			
		||||
        # don't leak error limits and poison other tests
 | 
			
		||||
        prosrv._error_limiting = {}
 | 
			
		||||
        prosrv.error_limiter.stats.clear()
 | 
			
		||||
 | 
			
		||||
    def test_mixing_different_objects_fragment_archives(self):
 | 
			
		||||
        (prosrv, acc1srv, acc2srv, con1srv, con2srv, obj1srv,
 | 
			
		||||
@@ -8077,7 +8080,7 @@ class TestECMismatchedFA(unittest.TestCase):
 | 
			
		||||
 | 
			
		||||
        # Server obj1 will have the first version of the object (obj2 also
 | 
			
		||||
        # gets it, but that gets stepped on later)
 | 
			
		||||
        prosrv._error_limiting = {}
 | 
			
		||||
        prosrv.error_limiter.stats.clear()
 | 
			
		||||
        with mock.patch.object(obj3srv, 'PUT', bad_disk), \
 | 
			
		||||
                mock.patch(
 | 
			
		||||
                    'swift.common.storage_policy.ECStoragePolicy.quorum'):
 | 
			
		||||
@@ -8086,7 +8089,7 @@ class TestECMismatchedFA(unittest.TestCase):
 | 
			
		||||
        self.assertEqual(resp.status_int, 201)
 | 
			
		||||
 | 
			
		||||
        # Servers obj2 and obj3 will have the second version of the object.
 | 
			
		||||
        prosrv._error_limiting = {}
 | 
			
		||||
        prosrv.error_limiter.stats.clear()
 | 
			
		||||
        with mock.patch.object(obj1srv, 'PUT', bad_disk), \
 | 
			
		||||
                mock.patch(
 | 
			
		||||
                    'swift.common.storage_policy.ECStoragePolicy.quorum'):
 | 
			
		||||
@@ -8098,7 +8101,7 @@ class TestECMismatchedFA(unittest.TestCase):
 | 
			
		||||
        get_req = Request.blank("/v1/a/ec-crazytown/obj",
 | 
			
		||||
                                environ={"REQUEST_METHOD": "GET"},
 | 
			
		||||
                                headers={"X-Auth-Token": "t"})
 | 
			
		||||
        prosrv._error_limiting = {}
 | 
			
		||||
        prosrv.error_limiter.stats.clear()
 | 
			
		||||
        with mock.patch.object(obj1srv, 'GET', bad_disk), \
 | 
			
		||||
                mock.patch.object(obj2srv, 'GET', bad_disk):
 | 
			
		||||
            resp = get_req.get_response(prosrv)
 | 
			
		||||
@@ -8108,7 +8111,7 @@ class TestECMismatchedFA(unittest.TestCase):
 | 
			
		||||
        get_req = Request.blank("/v1/a/ec-crazytown/obj",
 | 
			
		||||
                                environ={"REQUEST_METHOD": "GET"},
 | 
			
		||||
                                headers={"X-Auth-Token": "t"})
 | 
			
		||||
        prosrv._error_limiting = {}
 | 
			
		||||
        prosrv.error_limiter.stats.clear()
 | 
			
		||||
        with mock.patch.object(obj1srv, 'GET', bad_disk):
 | 
			
		||||
            resp = get_req.get_response(prosrv)
 | 
			
		||||
        self.assertEqual(resp.status_int, 200)
 | 
			
		||||
@@ -8118,7 +8121,7 @@ class TestECMismatchedFA(unittest.TestCase):
 | 
			
		||||
        get_req = Request.blank("/v1/a/ec-crazytown/obj",
 | 
			
		||||
                                environ={"REQUEST_METHOD": "GET"},
 | 
			
		||||
                                headers={"X-Auth-Token": "t"})
 | 
			
		||||
        prosrv._error_limiting = {}
 | 
			
		||||
        prosrv.error_limiter.stats.clear()
 | 
			
		||||
        with mock.patch.object(obj2srv, 'GET', bad_disk):
 | 
			
		||||
            resp = get_req.get_response(prosrv)
 | 
			
		||||
        self.assertEqual(resp.status_int, 503)
 | 
			
		||||
@@ -8159,7 +8162,7 @@ class TestECMismatchedFA(unittest.TestCase):
 | 
			
		||||
 | 
			
		||||
        # First subset of object server will have the first version of the
 | 
			
		||||
        # object
 | 
			
		||||
        prosrv._error_limiting = {}
 | 
			
		||||
        prosrv.error_limiter.stats.clear()
 | 
			
		||||
        with mock.patch.object(obj4srv, 'PUT', bad_disk), \
 | 
			
		||||
                mock.patch.object(obj5srv, 'PUT', bad_disk), \
 | 
			
		||||
                mock.patch.object(obj6srv, 'PUT', bad_disk), \
 | 
			
		||||
@@ -8170,7 +8173,7 @@ class TestECMismatchedFA(unittest.TestCase):
 | 
			
		||||
        self.assertEqual(resp.status_int, 201)
 | 
			
		||||
 | 
			
		||||
        # Second subset will have the second version of the object.
 | 
			
		||||
        prosrv._error_limiting = {}
 | 
			
		||||
        prosrv.error_limiter.stats.clear()
 | 
			
		||||
        with mock.patch.object(obj1srv, 'PUT', bad_disk), \
 | 
			
		||||
                mock.patch.object(obj2srv, 'PUT', bad_disk), \
 | 
			
		||||
                mock.patch.object(obj3srv, 'PUT', bad_disk), \
 | 
			
		||||
@@ -8184,7 +8187,7 @@ class TestECMismatchedFA(unittest.TestCase):
 | 
			
		||||
        get_req = Request.blank("/v1/a/ec-dup-crazytown/obj",
 | 
			
		||||
                                environ={"REQUEST_METHOD": "GET"},
 | 
			
		||||
                                headers={"X-Auth-Token": "t"})
 | 
			
		||||
        prosrv._error_limiting = {}
 | 
			
		||||
        prosrv.error_limiter.stats.clear()
 | 
			
		||||
        with mock.patch.object(obj2srv, 'GET', bad_disk), \
 | 
			
		||||
                mock.patch.object(obj3srv, 'GET', bad_disk), \
 | 
			
		||||
                mock.patch.object(obj4srv, 'GET', bad_disk), \
 | 
			
		||||
@@ -8197,7 +8200,7 @@ class TestECMismatchedFA(unittest.TestCase):
 | 
			
		||||
        get_req = Request.blank("/v1/a/ec-dup-crazytown/obj",
 | 
			
		||||
                                environ={"REQUEST_METHOD": "GET"},
 | 
			
		||||
                                headers={"X-Auth-Token": "t"})
 | 
			
		||||
        prosrv._error_limiting = {}
 | 
			
		||||
        prosrv.error_limiter.stats.clear()
 | 
			
		||||
        with mock.patch.object(obj1srv, 'GET', bad_disk), \
 | 
			
		||||
                mock.patch.object(obj2srv, 'GET', bad_disk), \
 | 
			
		||||
                mock.patch.object(obj3srv, 'GET', bad_disk), \
 | 
			
		||||
@@ -8210,7 +8213,7 @@ class TestECMismatchedFA(unittest.TestCase):
 | 
			
		||||
        get_req = Request.blank("/v1/a/ec-dup-crazytown/obj",
 | 
			
		||||
                                environ={"REQUEST_METHOD": "GET"},
 | 
			
		||||
                                headers={"X-Auth-Token": "t"})
 | 
			
		||||
        prosrv._error_limiting = {}
 | 
			
		||||
        prosrv.error_limiter.stats.clear()
 | 
			
		||||
        with mock.patch.object(obj2srv, 'GET', bad_disk), \
 | 
			
		||||
                mock.patch.object(obj3srv, 'GET', bad_disk), \
 | 
			
		||||
                mock.patch.object(obj4srv, 'GET', bad_disk), \
 | 
			
		||||
@@ -8229,7 +8232,7 @@ class TestECGets(unittest.TestCase):
 | 
			
		||||
        rmtree(self.tempdir, ignore_errors=True)
 | 
			
		||||
        prosrv = _test_servers[0]
 | 
			
		||||
        # don't leak error limits and poison other tests
 | 
			
		||||
        prosrv._error_limiting = {}
 | 
			
		||||
        prosrv.error_limiter.stats.clear()
 | 
			
		||||
        super(TestECGets, self).tearDown()
 | 
			
		||||
 | 
			
		||||
    def _setup_nodes_and_do_GET(self, objs, node_state):
 | 
			
		||||
@@ -8542,12 +8545,12 @@ class TestObjectDisconnectCleanup(unittest.TestCase):
 | 
			
		||||
        skip_if_no_xattrs()
 | 
			
		||||
        debug.hub_exceptions(False)
 | 
			
		||||
        self._cleanup_devices()
 | 
			
		||||
        _test_servers[0]._error_limiting = {}  # clear out errors
 | 
			
		||||
        _test_servers[0].error_limiter.stats.clear()  # clear out errors
 | 
			
		||||
 | 
			
		||||
    def tearDown(self):
 | 
			
		||||
        debug.hub_exceptions(True)
 | 
			
		||||
        self._cleanup_devices()
 | 
			
		||||
        _test_servers[0]._error_limiting = {}  # clear out errors
 | 
			
		||||
        _test_servers[0].error_limiter.stats.clear()  # clear out errors
 | 
			
		||||
 | 
			
		||||
    def _check_disconnect_cleans_up(self, policy_name, is_chunked=False):
 | 
			
		||||
        proxy_port = _test_sockets[0].getsockname()[1]
 | 
			
		||||
@@ -8640,7 +8643,7 @@ class TestObjectDisconnectCleanup(unittest.TestCase):
 | 
			
		||||
class TestObjectECRangedGET(unittest.TestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        _test_servers[0].logger._clear()
 | 
			
		||||
        _test_servers[0]._error_limiting = {}  # clear out errors
 | 
			
		||||
        _test_servers[0].error_limiter.stats.clear()  # clear out errors
 | 
			
		||||
        self.app = proxy_server.Application(
 | 
			
		||||
            None,
 | 
			
		||||
            logger=debug_logger('proxy-ut'),
 | 
			
		||||
@@ -8651,7 +8654,7 @@ class TestObjectECRangedGET(unittest.TestCase):
 | 
			
		||||
        prosrv = _test_servers[0]
 | 
			
		||||
        self.assertFalse(prosrv.logger.get_lines_for_level('error'))
 | 
			
		||||
        self.assertFalse(prosrv.logger.get_lines_for_level('warning'))
 | 
			
		||||
        prosrv._error_limiting = {}  # clear out errors
 | 
			
		||||
        prosrv.error_limiter.stats.clear()  # clear out errors
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def setUpClass(cls):
 | 
			
		||||
@@ -9672,7 +9675,7 @@ class TestContainerController(unittest.TestCase):
 | 
			
		||||
    def test_acc_missing_returns_404(self):
 | 
			
		||||
        for meth in ('DELETE', 'PUT'):
 | 
			
		||||
            with save_globals():
 | 
			
		||||
                self.app._error_limiting = {}
 | 
			
		||||
                self.app.error_limiter.stats.clear()
 | 
			
		||||
                controller = proxy_server.ContainerController(self.app,
 | 
			
		||||
                                                              'account',
 | 
			
		||||
                                                              'container')
 | 
			
		||||
@@ -9709,9 +9712,10 @@ class TestContainerController(unittest.TestCase):
 | 
			
		||||
                self.assertEqual(resp.status_int, 404)
 | 
			
		||||
 | 
			
		||||
                for dev in self.app.account_ring.devs:
 | 
			
		||||
                    set_node_errors(self.app, dev,
 | 
			
		||||
                                    self.app.error_suppression_limit + 1,
 | 
			
		||||
                                    time.time())
 | 
			
		||||
                    set_node_errors(
 | 
			
		||||
                        self.app, dev,
 | 
			
		||||
                        self.app.error_limiter.suppression_limit + 1,
 | 
			
		||||
                        time.time())
 | 
			
		||||
                set_http_connect(200, 200, 200, 200, 200, 200)
 | 
			
		||||
                # Make sure it is a blank request wthout env caching
 | 
			
		||||
                req = Request.blank('/v1/a/c',
 | 
			
		||||
@@ -9759,12 +9763,12 @@ class TestContainerController(unittest.TestCase):
 | 
			
		||||
            self.assertTrue(
 | 
			
		||||
                node_last_error(controller.app, container_ring.devs[0])
 | 
			
		||||
                is not None)
 | 
			
		||||
            for _junk in range(self.app.error_suppression_limit):
 | 
			
		||||
            for _junk in range(self.app.error_limiter.suppression_limit):
 | 
			
		||||
                self.assert_status_map(controller.HEAD,
 | 
			
		||||
                                       (200, 503, 503, 503), 503)
 | 
			
		||||
            self.assertEqual(
 | 
			
		||||
                node_error_count(controller.app, container_ring.devs[0]),
 | 
			
		||||
                self.app.error_suppression_limit + 1)
 | 
			
		||||
                self.app.error_limiter.suppression_limit + 1)
 | 
			
		||||
            self.assert_status_map(controller.HEAD, (200, 200, 200, 200), 503)
 | 
			
		||||
            self.assertTrue(
 | 
			
		||||
                node_last_error(controller.app, container_ring.devs[0])
 | 
			
		||||
@@ -9773,7 +9777,7 @@ class TestContainerController(unittest.TestCase):
 | 
			
		||||
                                   missing_container=True)
 | 
			
		||||
            self.assert_status_map(controller.DELETE,
 | 
			
		||||
                                   (200, 204, 204, 204), 503)
 | 
			
		||||
            self.app.error_suppression_interval = -300
 | 
			
		||||
            self.app.error_limiter.suppression_interval = -300
 | 
			
		||||
            self.assert_status_map(controller.HEAD, (200, 200, 200, 200), 200)
 | 
			
		||||
            self.assert_status_map(controller.DELETE, (200, 204, 204, 204),
 | 
			
		||||
                                   404, raise_exc=True)
 | 
			
		||||
@@ -11324,7 +11328,7 @@ class TestProxyObjectPerformance(unittest.TestCase):
 | 
			
		||||
        # various data paths between the proxy server and the object
 | 
			
		||||
        # server. Used as a play ground to debug buffer sizes for sockets.
 | 
			
		||||
        skip_if_no_xattrs()
 | 
			
		||||
        _test_servers[0]._error_limiting = {}  # clear out errors
 | 
			
		||||
        _test_servers[0].error_limiter.stats.clear()  # clear out errors
 | 
			
		||||
        prolis = _test_sockets[0]
 | 
			
		||||
        sock = connect_tcp(('localhost', prolis.getsockname()[1]))
 | 
			
		||||
        # Client is transmitting in 2 MB chunks
 | 
			
		||||
@@ -11445,7 +11449,7 @@ class TestSocketObjectVersions(unittest.TestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        global _test_sockets
 | 
			
		||||
        skip_if_no_xattrs()
 | 
			
		||||
        _test_servers[0]._error_limiting = {}  # clear out errors
 | 
			
		||||
        _test_servers[0].error_limiter.stats.clear()  # clear out errors
 | 
			
		||||
        self.prolis = prolis = listen_zero()
 | 
			
		||||
        self._orig_prolis = _test_sockets[0]
 | 
			
		||||
        allowed_headers = ', '.join([
 | 
			
		||||
@@ -11480,7 +11484,7 @@ class TestSocketObjectVersions(unittest.TestCase):
 | 
			
		||||
        global _test_sockets
 | 
			
		||||
        self.sockets[0] = self._orig_prolis
 | 
			
		||||
        _test_sockets = tuple(self.sockets)
 | 
			
		||||
        _test_servers[0]._error_limiting = {}  # clear out errors
 | 
			
		||||
        _test_servers[0].error_limiter.stats.clear()  # clear out errors
 | 
			
		||||
 | 
			
		||||
    def test_version_manifest(self, oc=b'versions', vc=b'vers', o=b'name'):
 | 
			
		||||
        versions_to_create = 3
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user