4823: Add root_collection() to make it easier to find the root. Adjusted
[arvados.git] / sdk / python / tests / test_keep_client.py
index 108b74cdcd28ca4a06226c0c16f64091d0b1db60..885ae7c1d0bd66acf25be72a1fd623607db9892d 100644 (file)
@@ -278,57 +278,57 @@ class KeepClientServiceTestCase(unittest.TestCase):
 
     def test_get_timeout(self):
         api_client = self.mock_keep_services(('keep', 10, False, 'disk'))
-        keep_client = arvados.KeepClient(api_client=api_client)
         force_timeout = [socket.timeout("timed out")]
-        with mock.patch('requests.get', side_effect=force_timeout) as mock_request:
+        with tutil.mock_get(force_timeout) as mock_session:
+            keep_client = arvados.KeepClient(api_client=api_client)
             with self.assertRaises(arvados.errors.KeepReadError):
                 keep_client.get('ffffffffffffffffffffffffffffffff')
-            self.assertTrue(mock_request.called)
+            self.assertTrue(mock_session.return_value.get.called)
             self.assertEqual(
                 arvados.KeepClient.DEFAULT_TIMEOUT,
-                mock_request.call_args[1]['timeout'])
+                mock_session.return_value.get.call_args[1]['timeout'])
 
     def test_put_timeout(self):
         api_client = self.mock_keep_services(('keep', 10, False, 'disk'))
-        keep_client = arvados.KeepClient(api_client=api_client)
         force_timeout = [socket.timeout("timed out")]
-        with mock.patch('requests.put', side_effect=force_timeout) as mock_request:
+        with tutil.mock_put(force_timeout) as mock_session:
+            keep_client = arvados.KeepClient(api_client=api_client)
             with self.assertRaises(arvados.errors.KeepWriteError):
                 keep_client.put('foo')
-            self.assertTrue(mock_request.called)
+            self.assertTrue(mock_session.return_value.put.called)
             self.assertEqual(
                 arvados.KeepClient.DEFAULT_TIMEOUT,
-                mock_request.call_args[1]['timeout'])
+                mock_session.return_value.put.call_args[1]['timeout'])
 
     def test_proxy_get_timeout(self):
         # Force a timeout, verifying that the requests.get or
         # requests.put method was called with the proxy_timeout
         # setting rather than the default timeout.
         api_client = self.mock_keep_services(('keep', 10, False, 'proxy'))
-        keep_client = arvados.KeepClient(api_client=api_client)
         force_timeout = [socket.timeout("timed out")]
-        with mock.patch('requests.get', side_effect=force_timeout) as mock_request:
+        with tutil.mock_get(force_timeout) as mock_session:
+            keep_client = arvados.KeepClient(api_client=api_client)
             with self.assertRaises(arvados.errors.KeepReadError):
                 keep_client.get('ffffffffffffffffffffffffffffffff')
-            self.assertTrue(mock_request.called)
+            self.assertTrue(mock_session.return_value.get.called)
             self.assertEqual(
                 arvados.KeepClient.DEFAULT_PROXY_TIMEOUT,
-                mock_request.call_args[1]['timeout'])
+                mock_session.return_value.get.call_args[1]['timeout'])
 
     def test_proxy_put_timeout(self):
         # Force a timeout, verifying that the requests.get or
         # requests.put method was called with the proxy_timeout
         # setting rather than the default timeout.
         api_client = self.mock_keep_services(('keep', 10, False, 'proxy'))
-        keep_client = arvados.KeepClient(api_client=api_client)
         force_timeout = [socket.timeout("timed out")]
-        with mock.patch('requests.put', side_effect=force_timeout) as mock_request:
+        with tutil.mock_put(force_timeout) as mock_session:
+            keep_client = arvados.KeepClient(api_client=api_client)
             with self.assertRaises(arvados.errors.KeepWriteError):
                 keep_client.put('foo')
-            self.assertTrue(mock_request.called)
+            self.assertTrue(mock_session.return_value.put.called)
             self.assertEqual(
                 arvados.KeepClient.DEFAULT_PROXY_TIMEOUT,
-                mock_request.call_args[1]['timeout'])
+                mock_session.return_value.put.call_args[1]['timeout'])
 
     def test_probe_order_reference_set(self):
         # expected_order[i] is the probe order for
@@ -394,6 +394,71 @@ class KeepClientServiceTestCase(unittest.TestCase):
                     min_penalty,
                     max_penalty))
 
+    def check_64_zeros_error_order(self, verb, exc_class):
+        data = '0' * 64
+        if verb == 'get':
+            data = hashlib.md5(data).hexdigest() + '+1234'
+        api_client = self.mock_n_keep_disks(16)
+        keep_client = arvados.KeepClient(api_client=api_client)
+        with mock.patch('requests.' + verb,
+                        side_effect=socket.timeout) as req_mock, \
+                self.assertRaises(exc_class) as err_check:
+            getattr(keep_client, verb)(data)
+        urls = [urlparse.urlparse(url)
+                for url in err_check.exception.service_errors()]
+        self.assertEqual([('keep0x' + c, 80) for c in '3eab2d5fc9681074'],
+                         [(url.hostname, url.port) for url in urls])
+
+    def test_get_error_shows_probe_order(self):
+        self.check_64_zeros_error_order('get', arvados.errors.KeepReadError)
+
+    def test_put_error_shows_probe_order(self):
+        self.check_64_zeros_error_order('put', arvados.errors.KeepWriteError)
+
+    def check_no_services_error(self, verb, exc_class):
+        api_client = mock.MagicMock(name='api_client')
+        api_client.keep_services().accessible().execute.side_effect = (
+            arvados.errors.ApiError)
+        keep_client = arvados.KeepClient(api_client=api_client)
+        with self.assertRaises(exc_class) as err_check:
+            getattr(keep_client, verb)('d41d8cd98f00b204e9800998ecf8427e+0')
+        self.assertEqual(0, len(err_check.exception.service_errors()))
+
+    def test_get_error_with_no_services(self):
+        self.check_no_services_error('get', arvados.errors.KeepReadError)
+
+    def test_put_error_with_no_services(self):
+        self.check_no_services_error('put', arvados.errors.KeepWriteError)
+
+    def check_errors_from_last_retry(self, verb, exc_class):
+        api_client = self.mock_n_keep_disks(2)
+        req_mock = getattr(tutil, 'mock_{}_responses'.format(verb))(
+            "retry error reporting test", 500, 500, 403, 403)
+        with req_mock, tutil.skip_sleep, \
+                self.assertRaises(exc_class) as err_check:
+            keep_client = arvados.KeepClient(api_client=api_client)
+            getattr(keep_client, verb)('d41d8cd98f00b204e9800998ecf8427e+0',
+                                       num_retries=3)
+        self.assertEqual([403, 403], [
+                getattr(error, 'status_code', None)
+                for error in err_check.exception.service_errors().itervalues()])
+
+    def test_get_error_reflects_last_retry(self):
+        self.check_errors_from_last_retry('get', arvados.errors.KeepReadError)
+
+    def test_put_error_reflects_last_retry(self):
+        self.check_errors_from_last_retry('put', arvados.errors.KeepWriteError)
+
+    def test_put_error_does_not_include_successful_puts(self):
+        data = 'partial failure test'
+        data_loc = '{}+{}'.format(hashlib.md5(data).hexdigest(), len(data))
+        api_client = self.mock_n_keep_disks(3)
+        with tutil.mock_put_responses(data_loc, 200, 500, 500) as req_mock, \
+                self.assertRaises(arvados.errors.KeepWriteError) as exc_check:
+            keep_client = arvados.KeepClient(api_client=api_client)
+            keep_client.put(data)
+        self.assertEqual(2, len(exc_check.exception.service_errors()))
+
 
 class KeepClientRetryTestMixin(object):
     # Testing with a local Keep store won't exercise the retry behavior.
@@ -416,18 +481,11 @@ class KeepClientRetryTestMixin(object):
 
     def setUp(self):
         self.client_kwargs = {'proxy': self.PROXY_ADDR, 'local_store': ''}
-        self.mock_session = None
-
-    def mock_get_responses(self, body, *codes, **headers):
-        self.mock_session = tutil.MockSession(body, codes, headers)
-
-    def mock_put_responses(self, body, *codes, **headers):
-        self.mock_session = tutil.MockSession(body, codes, headers)
 
     def new_client(self, **caller_kwargs):
         kwargs = self.client_kwargs.copy()
         kwargs.update(caller_kwargs)
-        return arvados.KeepClient(session=self.mock_session, **kwargs)
+        return arvados.KeepClient(**kwargs)
 
     def run_method(self, *args, **kwargs):
         raise NotImplementedError("test subclasses must define run_method")
@@ -443,29 +501,29 @@ class KeepClientRetryTestMixin(object):
         self.assertRaises(error_class, self.run_method, *args, **kwargs)
 
     def test_immediate_success(self):
-        self.TEST_PATCHER(self.DEFAULT_EXPECT, 200)
-        self.check_success()
+        with self.TEST_PATCHER(self.DEFAULT_EXPECT, 200):
+            self.check_success()
 
     def test_retry_then_success(self):
-        self.TEST_PATCHER(self.DEFAULT_EXPECT, 500, 200)
-        self.check_success(num_retries=3)
+        with self.TEST_PATCHER(self.DEFAULT_EXPECT, 500, 200):
+            self.check_success(num_retries=3)
 
     def test_no_default_retry(self):
-        self.TEST_PATCHER(self.DEFAULT_EXPECT, 500, 200)
-        self.check_exception()
+        with self.TEST_PATCHER(self.DEFAULT_EXPECT, 500, 200):
+            self.check_exception()
 
     def test_no_retry_after_permanent_error(self):
-        self.TEST_PATCHER(self.DEFAULT_EXPECT, 403, 200)
-        self.check_exception(num_retries=3)
+        with self.TEST_PATCHER(self.DEFAULT_EXPECT, 403, 200):
+            self.check_exception(num_retries=3)
 
     def test_error_after_retries_exhausted(self):
-        self.TEST_PATCHER(self.DEFAULT_EXPECT, 500, 500, 200)
-        self.check_exception(num_retries=1)
+        with self.TEST_PATCHER(self.DEFAULT_EXPECT, 500, 500, 200):
+            self.check_exception(num_retries=1)
 
     def test_num_retries_instance_fallback(self):
         self.client_kwargs['num_retries'] = 3
-        self.TEST_PATCHER(self.DEFAULT_EXPECT, 500, 200)
-        self.check_success()
+        with self.TEST_PATCHER(self.DEFAULT_EXPECT, 500, 200):
+            self.check_success()
 
 
 @tutil.skip_sleep
@@ -473,15 +531,15 @@ class KeepClientRetryGetTestCase(KeepClientRetryTestMixin, unittest.TestCase):
     DEFAULT_EXPECT = KeepClientRetryTestMixin.TEST_DATA
     DEFAULT_EXCEPTION = arvados.errors.KeepReadError
     HINTED_LOCATOR = KeepClientRetryTestMixin.TEST_LOCATOR + '+K@xyzzy'
-    TEST_PATCHER = KeepClientRetryTestMixin.mock_get_responses
+    TEST_PATCHER = staticmethod(tutil.mock_get_responses)
 
     def run_method(self, locator=KeepClientRetryTestMixin.TEST_LOCATOR,
                    *args, **kwargs):
         return self.new_client().get(locator, *args, **kwargs)
 
     def test_specific_exception_when_not_found(self):
-        self.mock_get_responses(self.DEFAULT_EXPECT, 404, 200)
-        self.check_exception(arvados.errors.NotFoundError, num_retries=3)
+        with tutil.mock_get_responses(self.DEFAULT_EXPECT, 404, 200):
+            self.check_exception(arvados.errors.NotFoundError, num_retries=3)
 
     def test_general_exception_with_mixed_errors(self):
         # get should raise a NotFoundError if no server returns the block,
@@ -489,40 +547,38 @@ class KeepClientRetryGetTestCase(KeepClientRetryTestMixin, unittest.TestCase):
         # This test rigs up 50/50 disagreement between two servers, and
         # checks that it does not become a NotFoundError.
         client = self.new_client()
-        self.mock_get_responses(self.DEFAULT_EXPECT, 404, 500)
-        with self.assertRaises(arvados.errors.KeepReadError) as exc_check:
-            client.get(self.HINTED_LOCATOR)
-        self.assertNotIsInstance(
-            exc_check.exception, arvados.errors.NotFoundError,
-            "mixed errors raised NotFoundError")
+        with tutil.mock_get_responses(self.DEFAULT_EXPECT, 404, 500):
+            with self.assertRaises(arvados.errors.KeepReadError) as exc_check:
+                client.get(self.HINTED_LOCATOR)
+            self.assertNotIsInstance(
+                exc_check.exception, arvados.errors.NotFoundError,
+                "mixed errors raised NotFoundError")
 
     def test_hint_server_can_succeed_without_retries(self):
-        self.mock_get_responses(self.DEFAULT_EXPECT, 404, 200, 500)
-        self.check_success(locator=self.HINTED_LOCATOR)
+        with tutil.mock_get_responses(self.DEFAULT_EXPECT, 404, 200, 500):
+            self.check_success(locator=self.HINTED_LOCATOR)
 
     def test_try_next_server_after_timeout(self):
-        side_effects = [
-            socket.timeout("timed out"),
-            tutil.fake_requests_response(200, self.DEFAULT_EXPECT)]
-        with mock.patch('requests.get',
-                        side_effect=iter(side_effects)):
+        with tutil.mock_get([
+                socket.timeout("timed out"),
+                tutil.fake_requests_response(200, self.DEFAULT_EXPECT)]):
             self.check_success(locator=self.HINTED_LOCATOR)
 
     def test_retry_data_with_wrong_checksum(self):
-        self.mock_get_responses(['baddata', self.TEST_DATA], 200, 200)
-        self.check_success(locator=self.HINTED_LOCATOR)
+        with tutil.mock_get((tutil.fake_requests_response(200, s) for s in ['baddata', self.TEST_DATA])):
+            self.check_success(locator=self.HINTED_LOCATOR)
 
 
 @tutil.skip_sleep
 class KeepClientRetryPutTestCase(KeepClientRetryTestMixin, unittest.TestCase):
     DEFAULT_EXPECT = KeepClientRetryTestMixin.TEST_LOCATOR
     DEFAULT_EXCEPTION = arvados.errors.KeepWriteError
-    TEST_PATCHER = KeepClientRetryTestMixin.mock_put_responses
+    TEST_PATCHER = staticmethod(tutil.mock_put_responses)
 
     def run_method(self, data=KeepClientRetryTestMixin.TEST_DATA,
                    copies=1, *args, **kwargs):
         return self.new_client().put(data, copies, *args, **kwargs)
 
     def test_do_not_send_multiple_copies_to_same_server(self):
-        self.mock_put_responses(self.DEFAULT_EXPECT, 200)
-        self.check_exception(copies=2, num_retries=3)
+        with tutil.mock_put_responses(self.DEFAULT_EXPECT, 200):
+            self.check_exception(copies=2, num_retries=3)