8 import arvados_testutil as tutil
11 class KeepTestCase(run_test_server.TestCaseWithServers):
17 super(KeepTestCase, cls).setUpClass()
18 run_test_server.authorize_with("admin")
19 cls.api_client = arvados.api('v1')
20 cls.keep_client = arvados.KeepClient(api_client=cls.api_client,
21 proxy='', local_store='')
23 def test_KeepBasicRWTest(self):
24 foo_locator = self.keep_client.put('foo')
25 self.assertRegexpMatches(
27 '^acbd18db4cc2f85cedef654fccc4a4d8\+3',
28 'wrong md5 hash from Keep.put("foo"): ' + foo_locator)
29 self.assertEqual(self.keep_client.get(foo_locator),
31 'wrong content from Keep.get(md5("foo"))')
33 def test_KeepBinaryRWTest(self):
34 blob_str = '\xff\xfe\xf7\x00\x01\x02'
35 blob_locator = self.keep_client.put(blob_str)
36 self.assertRegexpMatches(
38 '^7fc7c53b45e53926ba52821140fef396\+6',
39 ('wrong locator from Keep.put(<binarydata>):' + blob_locator))
40 self.assertEqual(self.keep_client.get(blob_locator),
42 'wrong content from Keep.get(md5(<binarydata>))')
44 def test_KeepLongBinaryRWTest(self):
45 blob_str = '\xff\xfe\xfd\xfc\x00\x01\x02\x03'
47 blob_str = blob_str + blob_str
48 blob_locator = self.keep_client.put(blob_str)
49 self.assertRegexpMatches(
51 '^84d90fc0d8175dd5dcfab04b999bc956\+67108864',
52 ('wrong locator from Keep.put(<binarydata>): ' + blob_locator))
53 self.assertEqual(self.keep_client.get(blob_locator),
55 'wrong content from Keep.get(md5(<binarydata>))')
57 def test_KeepSingleCopyRWTest(self):
58 blob_str = '\xff\xfe\xfd\xfc\x00\x01\x02\x03'
59 blob_locator = self.keep_client.put(blob_str, copies=1)
60 self.assertRegexpMatches(
62 '^c902006bc98a3eb4a3663b65ab4a6fab\+8',
63 ('wrong locator from Keep.put(<binarydata>): ' + blob_locator))
64 self.assertEqual(self.keep_client.get(blob_locator),
66 'wrong content from Keep.get(md5(<binarydata>))')
69 class KeepPermissionTestCase(run_test_server.TestCaseWithServers):
71 KEEP_SERVER = {'blob_signing_key': 'abcdefghijk0123456789',
72 'enforce_permissions': True}
74 def test_KeepBasicRWTest(self):
75 run_test_server.authorize_with('active')
76 keep_client = arvados.KeepClient()
77 foo_locator = keep_client.put('foo')
78 self.assertRegexpMatches(
80 r'^acbd18db4cc2f85cedef654fccc4a4d8\+3\+A[a-f0-9]+@[a-f0-9]+$',
81 'invalid locator from Keep.put("foo"): ' + foo_locator)
82 self.assertEqual(keep_client.get(foo_locator),
84 'wrong content from Keep.get(md5("foo"))')
86 # GET with an unsigned locator => NotFound
87 bar_locator = keep_client.put('bar')
88 unsigned_bar_locator = "37b51d194a7513e45b56f6524f2d51f2+3"
89 self.assertRegexpMatches(
91 r'^37b51d194a7513e45b56f6524f2d51f2\+3\+A[a-f0-9]+@[a-f0-9]+$',
92 'invalid locator from Keep.put("bar"): ' + bar_locator)
93 self.assertRaises(arvados.errors.NotFoundError,
97 # GET from a different user => NotFound
98 run_test_server.authorize_with('spectator')
99 self.assertRaises(arvados.errors.NotFoundError,
103 # Unauthenticated GET for a signed locator => NotFound
104 # Unauthenticated GET for an unsigned locator => NotFound
105 keep_client.api_token = ''
106 self.assertRaises(arvados.errors.NotFoundError,
109 self.assertRaises(arvados.errors.NotFoundError,
111 unsigned_bar_locator)
114 # KeepOptionalPermission: starts Keep with --permission-key-file
115 # but not --enforce-permissions (i.e. generate signatures on PUT
116 # requests, but do not require them for GET requests)
118 # All of these requests should succeed when permissions are optional:
119 # * authenticated request, signed locator
120 # * authenticated request, unsigned locator
121 # * unauthenticated request, signed locator
122 # * unauthenticated request, unsigned locator
123 class KeepOptionalPermission(run_test_server.TestCaseWithServers):
125 KEEP_SERVER = {'blob_signing_key': 'abcdefghijk0123456789',
126 'enforce_permissions': False}
130 super(KeepOptionalPermission, cls).setUpClass()
131 run_test_server.authorize_with("admin")
132 cls.api_client = arvados.api('v1')
135 super(KeepOptionalPermission, self).setUp()
136 self.keep_client = arvados.KeepClient(api_client=self.api_client,
137 proxy='', local_store='')
139 def _put_foo_and_check(self):
140 signed_locator = self.keep_client.put('foo')
141 self.assertRegexpMatches(
143 r'^acbd18db4cc2f85cedef654fccc4a4d8\+3\+A[a-f0-9]+@[a-f0-9]+$',
144 'invalid locator from Keep.put("foo"): ' + signed_locator)
145 return signed_locator
147 def test_KeepAuthenticatedSignedTest(self):
148 signed_locator = self._put_foo_and_check()
149 self.assertEqual(self.keep_client.get(signed_locator),
151 'wrong content from Keep.get(md5("foo"))')
153 def test_KeepAuthenticatedUnsignedTest(self):
154 signed_locator = self._put_foo_and_check()
155 self.assertEqual(self.keep_client.get("acbd18db4cc2f85cedef654fccc4a4d8"),
157 'wrong content from Keep.get(md5("foo"))')
159 def test_KeepUnauthenticatedSignedTest(self):
160 # Check that signed GET requests work even when permissions
161 # enforcement is off.
162 signed_locator = self._put_foo_and_check()
163 self.keep_client.api_token = ''
164 self.assertEqual(self.keep_client.get(signed_locator),
166 'wrong content from Keep.get(md5("foo"))')
168 def test_KeepUnauthenticatedUnsignedTest(self):
169 # Since --enforce-permissions is not in effect, GET requests
170 # need not be authenticated.
171 signed_locator = self._put_foo_and_check()
172 self.keep_client.api_token = ''
173 self.assertEqual(self.keep_client.get("acbd18db4cc2f85cedef654fccc4a4d8"),
175 'wrong content from Keep.get(md5("foo"))')
178 class KeepProxyTestCase(run_test_server.TestCaseWithServers):
181 KEEP_PROXY_SERVER = {'auth': 'admin'}
185 super(KeepProxyTestCase, cls).setUpClass()
186 cls.api_client = arvados.api('v1')
189 arvados.config.settings().pop('ARVADOS_EXTERNAL_CLIENT', None)
190 super(KeepProxyTestCase, self).tearDown()
192 def test_KeepProxyTest1(self):
193 # Will use ARVADOS_KEEP_PROXY environment variable that is set by
195 keep_client = arvados.KeepClient(api_client=self.api_client,
197 baz_locator = keep_client.put('baz')
198 self.assertRegexpMatches(
200 '^73feffa4b7f6bb68e44cf984c85f6e88\+3',
201 'wrong md5 hash from Keep.put("baz"): ' + baz_locator)
202 self.assertEqual(keep_client.get(baz_locator),
204 'wrong content from Keep.get(md5("baz"))')
205 self.assertTrue(keep_client.using_proxy)
207 def test_KeepProxyTest2(self):
208 # Don't instantiate the proxy directly, but set the X-External-Client
209 # header. The API server should direct us to the proxy.
210 arvados.config.settings()['ARVADOS_EXTERNAL_CLIENT'] = 'true'
211 keep_client = arvados.KeepClient(api_client=self.api_client,
212 proxy='', local_store='')
213 baz_locator = keep_client.put('baz2')
214 self.assertRegexpMatches(
216 '^91f372a266fe2bf2823cb8ec7fda31ce\+4',
217 'wrong md5 hash from Keep.put("baz2"): ' + baz_locator)
218 self.assertEqual(keep_client.get(baz_locator),
220 'wrong content from Keep.get(md5("baz2"))')
221 self.assertTrue(keep_client.using_proxy)
224 class KeepClientRetryTestMixin(object):
225 # Testing with a local Keep store won't exercise the retry behavior.
226 # Instead, our strategy is:
227 # * Create a client with one proxy specified (pointed at a black
228 # hole), so there's no need to instantiate an API client, and
229 # all HTTP requests come from one place.
230 # * Mock httplib's request method to provide simulated responses.
231 # This lets us test the retry logic extensively without relying on any
232 # supporting servers, and prevents side effects in case something hiccups.
233 # To use this mixin, define DEFAULT_EXPECT, DEFAULT_EXCEPTION, and
235 PROXY_ADDR = 'http://[%s]:65535/' % (tutil.TEST_HOST,)
236 TEST_DATA = 'testdata'
237 TEST_LOCATOR = 'ef654c40ab4f1747fc699915d4f70902+8'
240 self.client_kwargs = {'proxy': self.PROXY_ADDR, 'local_store': ''}
242 def new_client(self, **caller_kwargs):
243 kwargs = self.client_kwargs.copy()
244 kwargs.update(caller_kwargs)
245 return arvados.KeepClient(**kwargs)
247 def run_method(self, *args, **kwargs):
248 raise NotImplementedError("test subclasses must define run_method")
250 def check_success(self, expected=None, *args, **kwargs):
252 expected = self.DEFAULT_EXPECT
253 self.assertEqual(expected, self.run_method(*args, **kwargs))
255 def check_exception(self, error_class=None, *args, **kwargs):
256 if error_class is None:
257 error_class = self.DEFAULT_EXCEPTION
258 self.assertRaises(error_class, self.run_method, *args, **kwargs)
260 def test_immediate_success(self):
261 with tutil.mock_responses(self.DEFAULT_EXPECT, 200):
264 def test_retry_then_success(self):
265 with tutil.mock_responses(self.DEFAULT_EXPECT, 500, 200):
266 self.check_success(num_retries=3)
268 def test_no_default_retry(self):
269 with tutil.mock_responses(self.DEFAULT_EXPECT, 500, 200):
270 self.check_exception()
272 def test_no_retry_after_permanent_error(self):
273 with tutil.mock_responses(self.DEFAULT_EXPECT, 403, 200):
274 self.check_exception(num_retries=3)
276 def test_error_after_retries_exhausted(self):
277 with tutil.mock_responses(self.DEFAULT_EXPECT, 500, 500, 200):
278 self.check_exception(num_retries=1)
280 def test_num_retries_instance_fallback(self):
281 self.client_kwargs['num_retries'] = 3
282 with tutil.mock_responses(self.DEFAULT_EXPECT, 500, 200):
287 class KeepClientRetryGetTestCase(KeepClientRetryTestMixin, unittest.TestCase):
288 DEFAULT_EXPECT = KeepClientRetryTestMixin.TEST_DATA
289 DEFAULT_EXCEPTION = arvados.errors.KeepReadError
290 HINTED_LOCATOR = KeepClientRetryTestMixin.TEST_LOCATOR + '+K@xyzzy'
292 def run_method(self, locator=KeepClientRetryTestMixin.TEST_LOCATOR,
294 return self.new_client().get(locator, *args, **kwargs)
296 def test_specific_exception_when_not_found(self):
297 with tutil.mock_responses(self.DEFAULT_EXPECT, 404, 200):
298 self.check_exception(arvados.errors.NotFoundError, num_retries=3)
300 def test_general_exception_with_mixed_errors(self):
301 # get should raise a NotFoundError if no server returns the block,
302 # and a high threshold of servers report that it's not found.
303 # This test rigs up 50/50 disagreement between two servers, and
304 # checks that it does not become a NotFoundError.
305 client = self.new_client()
306 with tutil.mock_responses(self.DEFAULT_EXPECT, 404, 500):
307 with self.assertRaises(arvados.errors.KeepReadError) as exc_check:
308 client.get(self.HINTED_LOCATOR)
309 self.assertNotIsInstance(
310 exc_check.exception, arvados.errors.NotFoundError,
311 "mixed errors raised NotFoundError")
313 def test_hint_server_can_succeed_without_retries(self):
314 with tutil.mock_responses(self.DEFAULT_EXPECT, 404, 200, 500):
315 self.check_success(locator=self.HINTED_LOCATOR)
317 def test_try_next_server_after_timeout(self):
319 socket.timeout("timed out"),
320 (tutil.fake_httplib2_response(200), self.DEFAULT_EXPECT)]
321 with mock.patch('httplib2.Http.request',
322 side_effect=iter(side_effects)):
323 self.check_success(locator=self.HINTED_LOCATOR)
325 def test_retry_data_with_wrong_checksum(self):
326 side_effects = ((tutil.fake_httplib2_response(200), s)
327 for s in ['baddata', self.TEST_DATA])
328 with mock.patch('httplib2.Http.request', side_effect=side_effects):
329 self.check_success(locator=self.HINTED_LOCATOR)
333 class KeepClientRetryPutTestCase(KeepClientRetryTestMixin, unittest.TestCase):
334 DEFAULT_EXPECT = KeepClientRetryTestMixin.TEST_LOCATOR
335 DEFAULT_EXCEPTION = arvados.errors.KeepWriteError
337 def run_method(self, data=KeepClientRetryTestMixin.TEST_DATA,
338 copies=1, *args, **kwargs):
339 return self.new_client().put(data, copies, *args, **kwargs)
341 def test_do_not_send_multiple_copies_to_same_server(self):
342 with tutil.mock_responses(self.DEFAULT_EXPECT, 200):
343 self.check_exception(copies=2, num_retries=3)