+ # test_get_timeout and test_put_timeout test that
+ # KeepClient.get and KeepClient.put use the appropriate timeouts
+ # when connected directly to a Keep server (i.e. non-proxy timeout)
+
+ def test_get_timeout(self):
+ api_client = self.mock_keep_services(count=1)
+ force_timeout = [socket.timeout("timed out")]
+ 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_session.return_value.get.called)
+ self.assertEqual(
+ arvados.KeepClient.DEFAULT_TIMEOUT,
+ mock_session.return_value.get.call_args[1]['timeout'])
+
+ def test_put_timeout(self):
+ api_client = self.mock_keep_services(count=1)
+ force_timeout = [socket.timeout("timed out")]
+ 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_session.return_value.put.called)
+ self.assertEqual(
+ arvados.KeepClient.DEFAULT_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(service_type='proxy', count=1)
+ force_timeout = [socket.timeout("timed out")]
+ 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_session.return_value.get.called)
+ self.assertEqual(
+ arvados.KeepClient.DEFAULT_PROXY_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(service_type='proxy', count=1)
+ force_timeout = [socket.timeout("timed out")]
+ 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_session.return_value.put.called)
+ self.assertEqual(
+ arvados.KeepClient.DEFAULT_PROXY_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
+ # hash=md5(sprintf("%064x",i)) where there are 16 services
+ # with uuid sprintf("anything-%015x",j) with j in 0..15. E.g.,
+ # the first probe for the block consisting of 64 "0"
+ # characters is the service whose uuid is
+ # "zzzzz-bi6l4-000000000000003", so expected_order[0][0]=='3'.
+ expected_order = [
+ list('3eab2d5fc9681074'),
+ list('097dba52e648f1c3'),
+ list('c5b4e023f8a7d691'),
+ list('9d81c02e76a3bf54'),
+ ]
+ hashes = [
+ hashlib.md5("{:064x}".format(x)).hexdigest()
+ for x in range(len(expected_order))]
+ api_client = self.mock_keep_services(count=16)
+ keep_client = arvados.KeepClient(api_client=api_client)
+ for i, hash in enumerate(hashes):
+ roots = keep_client.weighted_service_roots(hash)
+ got_order = [
+ re.search(r'//\[?keep0x([0-9a-f]+)', root).group(1)
+ for root in roots]
+ self.assertEqual(expected_order[i], got_order)
+
+ def test_probe_waste_adding_one_server(self):
+ hashes = [
+ hashlib.md5("{:064x}".format(x)).hexdigest() for x in range(100)]
+ initial_services = 12
+ api_client = self.mock_keep_services(count=initial_services)
+ keep_client = arvados.KeepClient(api_client=api_client)
+ probes_before = [
+ keep_client.weighted_service_roots(hash) for hash in hashes]
+ for added_services in range(1, 12):
+ api_client = self.mock_keep_services(count=initial_services+added_services)
+ keep_client = arvados.KeepClient(api_client=api_client)
+ total_penalty = 0
+ for hash_index in range(len(hashes)):
+ probe_after = keep_client.weighted_service_roots(
+ hashes[hash_index])
+ penalty = probe_after.index(probes_before[hash_index][0])
+ self.assertLessEqual(penalty, added_services)
+ total_penalty += penalty
+ # Average penalty per block should not exceed
+ # N(added)/N(orig) by more than 20%, and should get closer
+ # to the ideal as we add data points.
+ expect_penalty = (
+ added_services *
+ len(hashes) / initial_services)
+ max_penalty = (
+ expect_penalty *
+ (120 - added_services)/100)
+ min_penalty = (
+ expect_penalty * 8/10)
+ self.assertTrue(
+ min_penalty <= total_penalty <= max_penalty,
+ "With {}+{} services, {} blocks, penalty {} but expected {}..{}".format(
+ initial_services,
+ added_services,
+ len(hashes),
+ total_penalty,
+ 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'
+ # Arbitrary port number:
+ aport = random.randint(1024,65535)
+ api_client = self.mock_keep_services(service_port=aport, count=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.request_errors()]
+ self.assertEqual([('keep0x' + c, aport) 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.request_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_keep_services(count=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.request_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_keep_services(count=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.request_errors()))
+