8 from arvados_testutil import fake_httplib2_response
10 class KeepTestCase(run_test_server.TestCaseWithServers):
16 super(KeepTestCase, cls).setUpClass()
17 run_test_server.authorize_with("admin")
18 cls.api_client = arvados.api('v1')
19 cls.keep_client = arvados.KeepClient(api_client=cls.api_client,
20 proxy='', local_store='')
22 def test_KeepBasicRWTest(self):
23 foo_locator = self.keep_client.put('foo')
24 self.assertRegexpMatches(
26 '^acbd18db4cc2f85cedef654fccc4a4d8\+3',
27 'wrong md5 hash from Keep.put("foo"): ' + foo_locator)
28 self.assertEqual(self.keep_client.get(foo_locator),
30 'wrong content from Keep.get(md5("foo"))')
32 def test_KeepBinaryRWTest(self):
33 blob_str = '\xff\xfe\xf7\x00\x01\x02'
34 blob_locator = self.keep_client.put(blob_str)
35 self.assertRegexpMatches(
37 '^7fc7c53b45e53926ba52821140fef396\+6',
38 ('wrong locator from Keep.put(<binarydata>):' + blob_locator))
39 self.assertEqual(self.keep_client.get(blob_locator),
41 'wrong content from Keep.get(md5(<binarydata>))')
43 def test_KeepLongBinaryRWTest(self):
44 blob_str = '\xff\xfe\xfd\xfc\x00\x01\x02\x03'
46 blob_str = blob_str + blob_str
47 blob_locator = self.keep_client.put(blob_str)
48 self.assertRegexpMatches(
50 '^84d90fc0d8175dd5dcfab04b999bc956\+67108864',
51 ('wrong locator from Keep.put(<binarydata>): ' + blob_locator))
52 self.assertEqual(self.keep_client.get(blob_locator),
54 'wrong content from Keep.get(md5(<binarydata>))')
56 def test_KeepSingleCopyRWTest(self):
57 blob_str = '\xff\xfe\xfd\xfc\x00\x01\x02\x03'
58 blob_locator = self.keep_client.put(blob_str, copies=1)
59 self.assertRegexpMatches(
61 '^c902006bc98a3eb4a3663b65ab4a6fab\+8',
62 ('wrong locator from Keep.put(<binarydata>): ' + blob_locator))
63 self.assertEqual(self.keep_client.get(blob_locator),
65 'wrong content from Keep.get(md5(<binarydata>))')
68 class KeepPermissionTestCase(run_test_server.TestCaseWithServers):
70 KEEP_SERVER = {'blob_signing_key': 'abcdefghijk0123456789',
71 'enforce_permissions': True}
73 def test_KeepBasicRWTest(self):
74 run_test_server.authorize_with('active')
75 keep_client = arvados.KeepClient()
76 foo_locator = keep_client.put('foo')
77 self.assertRegexpMatches(
79 r'^acbd18db4cc2f85cedef654fccc4a4d8\+3\+A[a-f0-9]+@[a-f0-9]+$',
80 'invalid locator from Keep.put("foo"): ' + foo_locator)
81 self.assertEqual(keep_client.get(foo_locator),
83 'wrong content from Keep.get(md5("foo"))')
85 # GET with an unsigned locator => NotFound
86 bar_locator = keep_client.put('bar')
87 unsigned_bar_locator = "37b51d194a7513e45b56f6524f2d51f2+3"
88 self.assertRegexpMatches(
90 r'^37b51d194a7513e45b56f6524f2d51f2\+3\+A[a-f0-9]+@[a-f0-9]+$',
91 'invalid locator from Keep.put("bar"): ' + bar_locator)
92 self.assertRaises(arvados.errors.NotFoundError,
96 # GET from a different user => NotFound
97 run_test_server.authorize_with('spectator')
98 self.assertRaises(arvados.errors.NotFoundError,
102 # Unauthenticated GET for a signed locator => NotFound
103 # Unauthenticated GET for an unsigned locator => NotFound
104 keep_client.api_token = ''
105 self.assertRaises(arvados.errors.NotFoundError,
108 self.assertRaises(arvados.errors.NotFoundError,
110 unsigned_bar_locator)
113 # KeepOptionalPermission: starts Keep with --permission-key-file
114 # but not --enforce-permissions (i.e. generate signatures on PUT
115 # requests, but do not require them for GET requests)
117 # All of these requests should succeed when permissions are optional:
118 # * authenticated request, signed locator
119 # * authenticated request, unsigned locator
120 # * unauthenticated request, signed locator
121 # * unauthenticated request, unsigned locator
122 class KeepOptionalPermission(run_test_server.TestCaseWithServers):
124 KEEP_SERVER = {'blob_signing_key': 'abcdefghijk0123456789',
125 'enforce_permissions': False}
129 super(KeepOptionalPermission, cls).setUpClass()
130 run_test_server.authorize_with("admin")
131 cls.api_client = arvados.api('v1')
134 super(KeepOptionalPermission, self).setUp()
135 self.keep_client = arvados.KeepClient(api_client=self.api_client,
136 proxy='', local_store='')
138 def _put_foo_and_check(self):
139 signed_locator = self.keep_client.put('foo')
140 self.assertRegexpMatches(
142 r'^acbd18db4cc2f85cedef654fccc4a4d8\+3\+A[a-f0-9]+@[a-f0-9]+$',
143 'invalid locator from Keep.put("foo"): ' + signed_locator)
144 return signed_locator
146 def test_KeepAuthenticatedSignedTest(self):
147 signed_locator = self._put_foo_and_check()
148 self.assertEqual(self.keep_client.get(signed_locator),
150 'wrong content from Keep.get(md5("foo"))')
152 def test_KeepAuthenticatedUnsignedTest(self):
153 signed_locator = self._put_foo_and_check()
154 self.assertEqual(self.keep_client.get("acbd18db4cc2f85cedef654fccc4a4d8"),
156 'wrong content from Keep.get(md5("foo"))')
158 def test_KeepUnauthenticatedSignedTest(self):
159 # Check that signed GET requests work even when permissions
160 # enforcement is off.
161 signed_locator = self._put_foo_and_check()
162 self.keep_client.api_token = ''
163 self.assertEqual(self.keep_client.get(signed_locator),
165 'wrong content from Keep.get(md5("foo"))')
167 def test_KeepUnauthenticatedUnsignedTest(self):
168 # Since --enforce-permissions is not in effect, GET requests
169 # need not be authenticated.
170 signed_locator = self._put_foo_and_check()
171 self.keep_client.api_token = ''
172 self.assertEqual(self.keep_client.get("acbd18db4cc2f85cedef654fccc4a4d8"),
174 'wrong content from Keep.get(md5("foo"))')
177 class KeepProxyTestCase(run_test_server.TestCaseWithServers):
180 KEEP_PROXY_SERVER = {'auth': 'admin'}
184 super(KeepProxyTestCase, cls).setUpClass()
185 cls.api_client = arvados.api('v1')
188 arvados.config.settings().pop('ARVADOS_EXTERNAL_CLIENT', None)
189 super(KeepProxyTestCase, self).tearDown()
191 def test_KeepProxyTest1(self):
192 # Will use ARVADOS_KEEP_PROXY environment variable that is set by
194 keep_client = arvados.KeepClient(api_client=self.api_client,
196 baz_locator = keep_client.put('baz')
197 self.assertRegexpMatches(
199 '^73feffa4b7f6bb68e44cf984c85f6e88\+3',
200 'wrong md5 hash from Keep.put("baz"): ' + baz_locator)
201 self.assertEqual(keep_client.get(baz_locator),
203 'wrong content from Keep.get(md5("baz"))')
204 self.assertTrue(keep_client.using_proxy)
206 def test_KeepProxyTest2(self):
207 # Don't instantiate the proxy directly, but set the X-External-Client
208 # header. The API server should direct us to the proxy.
209 arvados.config.settings()['ARVADOS_EXTERNAL_CLIENT'] = 'true'
210 keep_client = arvados.KeepClient(api_client=self.api_client,
211 proxy='', local_store='')
212 baz_locator = keep_client.put('baz2')
213 self.assertRegexpMatches(
215 '^91f372a266fe2bf2823cb8ec7fda31ce\+4',
216 'wrong md5 hash from Keep.put("baz2"): ' + baz_locator)
217 self.assertEqual(keep_client.get(baz_locator),
219 'wrong content from Keep.get(md5("baz2"))')
220 self.assertTrue(keep_client.using_proxy)
223 class KeepClientRetryTestMixin(object):
224 # Testing with a local Keep store won't exercise the retry behavior.
225 # Instead, our strategy is:
226 # * Create a client with one proxy specified (pointed at a black
227 # hole), so there's no need to instantiate an API client, and
228 # all HTTP requests come from one place.
229 # * Mock httplib's request method to provide simulated responses.
230 # This lets us test the retry logic extensively without relying on any
231 # supporting servers, and prevents side effects in case something hiccups.
232 # To use this mixin, define DEFAULT_EXPECT, DEFAULT_EXCEPTION, and
234 PROXY_ADDR = 'http://[100::]/'
235 TEST_DATA = 'testdata'
236 TEST_LOCATOR = 'ef654c40ab4f1747fc699915d4f70902+8'
239 def mock_responses(body, *codes):
240 return mock.patch('httplib2.Http.request', side_effect=(
241 (fake_httplib2_response(code), body) for code in codes))
243 def new_client(self):
244 return arvados.KeepClient(proxy=self.PROXY_ADDR, local_store='')
246 def run_method(self, *args, **kwargs):
247 raise NotImplementedError("test subclasses must define run_method")
249 def check_success(self, expected=None, *args, **kwargs):
251 expected = self.DEFAULT_EXPECT
252 self.assertEqual(expected, self.run_method(*args, **kwargs))
254 def check_exception(self, error_class=None, *args, **kwargs):
255 if error_class is None:
256 error_class = self.DEFAULT_EXCEPTION
257 self.assertRaises(error_class, self.run_method, *args, **kwargs)
259 def test_immediate_success(self):
260 with self.mock_responses(self.DEFAULT_EXPECT, 200):
263 def test_retry_then_success(self):
264 with self.mock_responses(self.DEFAULT_EXPECT, 500, 200):
265 self.check_success(num_retries=3)
267 def test_no_default_retry(self):
268 with self.mock_responses(self.DEFAULT_EXPECT, 500, 200):
269 self.check_exception()
271 def test_no_retry_after_permanent_error(self):
272 with self.mock_responses(self.DEFAULT_EXPECT, 403, 200):
273 self.check_exception(num_retries=3)
275 def test_error_after_retries_exhausted(self):
276 with self.mock_responses(self.DEFAULT_EXPECT, 500, 500, 200):
277 self.check_exception(num_retries=1)
280 # Don't delay from HTTPRetryLoop's exponential backoff.
281 no_backoff = mock.patch('time.sleep', lambda n: None)
283 class KeepClientRetryGetTestCase(unittest.TestCase, KeepClientRetryTestMixin):
284 DEFAULT_EXPECT = KeepClientRetryTestMixin.TEST_DATA
285 DEFAULT_EXCEPTION = arvados.errors.KeepReadError
286 HINTED_LOCATOR = KeepClientRetryTestMixin.TEST_LOCATOR + '+K@xyzzy'
288 def run_method(self, locator=KeepClientRetryTestMixin.TEST_LOCATOR,
290 return self.new_client().get(locator, *args, **kwargs)
292 def test_specific_exception_when_not_found(self):
293 with self.mock_responses(self.DEFAULT_EXPECT, 404, 200):
294 self.check_exception(arvados.errors.NotFoundError, num_retries=3)
296 def test_general_exception_with_mixed_errors(self):
297 # get should raise a NotFoundError if no server returns the block,
298 # and a high threshold of servers report that it's not found.
299 # This test rigs up 50/50 disagreement between two servers, and
300 # checks that it does not become a NotFoundError.
301 client = self.new_client()
302 with self.mock_responses(self.DEFAULT_EXPECT, 404, 500):
303 with self.assertRaises(arvados.errors.KeepReadError) as exc_check:
304 client.get(self.HINTED_LOCATOR)
305 self.assertNotIsInstance(
306 exc_check.exception, arvados.errors.NotFoundError,
307 "mixed errors raised NotFoundError")
309 def test_hint_server_can_succeed_without_retries(self):
310 with self.mock_responses(self.DEFAULT_EXPECT, 404, 200, 500):
311 self.check_success(locator=self.HINTED_LOCATOR)
315 class KeepClientRetryPutTestCase(unittest.TestCase, KeepClientRetryTestMixin):
316 DEFAULT_EXPECT = KeepClientRetryTestMixin.TEST_LOCATOR
317 DEFAULT_EXCEPTION = arvados.errors.KeepWriteError
319 def run_method(self, data=KeepClientRetryTestMixin.TEST_DATA,
320 copies=1, *args, **kwargs):
321 return self.new_client().put(data, copies, *args, **kwargs)
323 def test_do_not_send_multiple_copies_to_same_server(self):
324 with self.mock_responses(self.DEFAULT_EXPECT, 200):
325 self.check_exception(copies=2, num_retries=3)