test_swifts3.py 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962
  1. # Copyright (c) 2011 OpenStack, LLC.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
  12. # implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import unittest
  16. from datetime import datetime
  17. import cgi
  18. import hashlib
  19. from webob import Request, Response
  20. from webob.exc import HTTPUnauthorized, HTTPCreated, HTTPNoContent,\
  21. HTTPAccepted, HTTPBadRequest, HTTPNotFound, HTTPConflict
  22. import xml.dom.minidom
  23. import simplejson
  24. import mock
  25. from swifts3 import middleware as swift3
  26. class FakeApp(object):
  27. def __init__(self):
  28. self.app = self
  29. self.response_args = []
  30. def __call__(self, env, start_response):
  31. return "FAKE APP"
  32. def do_start_response(self, *args):
  33. self.response_args.extend(args)
  34. class FakeAppService(FakeApp):
  35. def __init__(self, status=200):
  36. FakeApp.__init__(self)
  37. self.status = status
  38. self.buckets = (('apple', 1, 200), ('orange', 3, 430))
  39. def __call__(self, env, start_response):
  40. if self.status == 200:
  41. start_response(Response().status, [('Content-Type', 'text/xml')])
  42. json_pattern = ['"name":%s', '"count":%s', '"bytes":%s']
  43. json_pattern = '{' + ','.join(json_pattern) + '}'
  44. json_out = []
  45. for b in self.buckets:
  46. name = simplejson.dumps(b[0])
  47. json_out.append(json_pattern %
  48. (name, b[1], b[2]))
  49. account_list = '[' + ','.join(json_out) + ']'
  50. return account_list
  51. elif self.status == 401:
  52. start_response(HTTPUnauthorized().status, [])
  53. else:
  54. start_response(HTTPBadRequest().status, [])
  55. class FakeAppBucket(FakeApp):
  56. def __init__(self, status=200):
  57. FakeApp.__init__(self)
  58. self.status = status
  59. self.objects = (('rose', '2011-01-05T02:19:14.275290', 0, 303),
  60. ('viola', '2011-01-05T02:19:14.275290', 0, 3909),
  61. ('lily', '2011-01-05T02:19:14.275290', 0, 3909))
  62. def __call__(self, env, start_response):
  63. if env['REQUEST_METHOD'] == 'GET':
  64. if self.status == 200:
  65. start_response(Response().status,
  66. [('Content-Type', 'text/xml')])
  67. json_pattern = ['"name":%s', '"last_modified":%s', '"hash":%s',
  68. '"bytes":%s']
  69. json_pattern = '{' + ','.join(json_pattern) + '}'
  70. json_out = []
  71. for b in self.objects:
  72. name = simplejson.dumps(b[0])
  73. time = simplejson.dumps(b[1])
  74. json_out.append(json_pattern %
  75. (name, time, b[2], b[3]))
  76. account_list = '[' + ','.join(json_out) + ']'
  77. return account_list
  78. elif self.status == 401:
  79. start_response(HTTPUnauthorized().status, [])
  80. elif self.status == 404:
  81. start_response(HTTPNotFound().status, [])
  82. else:
  83. start_response(HTTPBadRequest().status, [])
  84. elif env['REQUEST_METHOD'] == 'PUT':
  85. if self.status == 201:
  86. start_response(HTTPCreated().status, [])
  87. elif self.status == 401:
  88. start_response(HTTPUnauthorized().status, [])
  89. elif self.status == 202:
  90. start_response(HTTPAccepted().status, [])
  91. else:
  92. start_response(HTTPBadRequest().status, [])
  93. elif env['REQUEST_METHOD'] == 'DELETE':
  94. if self.status == 204:
  95. start_response(HTTPNoContent().status, [])
  96. elif self.status == 401:
  97. start_response(HTTPUnauthorized().status, [])
  98. elif self.status == 404:
  99. start_response(HTTPNotFound().status, [])
  100. elif self.status == 409:
  101. start_response(HTTPConflict().status, [])
  102. else:
  103. start_response(HTTPBadRequest().status, [])
  104. class FakeAppBucketMPU(FakeAppBucket):
  105. def __init__(self, status=200):
  106. FakeApp.__init__(self)
  107. self.status = status
  108. self.objects = [('rose/id/meta', '2011-01-05T02:19:14.275290', 0, 303)]
  109. class FakeAppObject(FakeApp):
  110. def __init__(self, status=200):
  111. FakeApp.__init__(self)
  112. self.status = status
  113. str_body = {"object": {"name": "bucket_new2",
  114. "last_modified": "2012-08-27T14:55:42.963Z",
  115. "hash": "1b2cf535f27731c974343645a3985328",
  116. "bytes": 123,
  117. }}
  118. self.object_body = simplejson.dumps(str_body)
  119. self.response_headers = {'Content-Type': 'text/html',
  120. 'Content-Length': len(self.object_body),
  121. 'x-object-meta-test': 'swift',
  122. 'etag': '1b2cf535f27731c974343645a3985328',
  123. 'last-modified': '2011-01-05T02:19:14.275290'}
  124. def __call__(self, env, start_response):
  125. if env['REQUEST_METHOD'] == 'GET' or env['REQUEST_METHOD'] == 'HEAD':
  126. if self.status == 200:
  127. start_response(Response().status,
  128. self.response_headers.items())
  129. if env['REQUEST_METHOD'] == 'GET':
  130. return self.object_body
  131. elif self.status == 401:
  132. start_response(HTTPUnauthorized().status, [])
  133. elif self.status == 404:
  134. start_response(HTTPNotFound().status, [])
  135. else:
  136. start_response(HTTPBadRequest().status, [])
  137. elif env['REQUEST_METHOD'] == 'PUT':
  138. if self.status == 201:
  139. start_response(HTTPCreated().status,
  140. [('etag', self.response_headers['etag'])])
  141. elif self.status == 401:
  142. start_response(HTTPUnauthorized().status, [])
  143. elif self.status == 404:
  144. start_response(HTTPNotFound().status, [])
  145. else:
  146. start_response(HTTPBadRequest().status, [])
  147. elif env['REQUEST_METHOD'] == 'DELETE':
  148. if self.status == 204:
  149. start_response(HTTPNoContent().status, [])
  150. elif self.status == 401:
  151. start_response(HTTPUnauthorized().status, [])
  152. elif self.status == 404:
  153. start_response(HTTPNotFound().status, [])
  154. else:
  155. start_response(HTTPBadRequest().status, [])
  156. def start_response(*args):
  157. pass
  158. class TestSwift3(unittest.TestCase):
  159. def setUp(self):
  160. self.app = swift3.filter_factory({})(FakeApp())
  161. self.MULTIPART_UPLOAD_PREFIX = 'mpu.'
  162. def test_non_s3_request_passthrough(self):
  163. req = Request.blank('/something')
  164. resp = self.app(req.environ, start_response)
  165. self.assertEquals(resp, 'FAKE APP')
  166. def test_bad_format_authorization(self):
  167. req = Request.blank('/something',
  168. headers={'Authorization': 'hoge'})
  169. resp = self.app(req.environ, start_response)
  170. dom = xml.dom.minidom.parseString("".join(resp))
  171. self.assertEquals(dom.firstChild.nodeName, 'Error')
  172. code = dom.getElementsByTagName('Code')[0].childNodes[0].nodeValue
  173. self.assertEquals(code, 'InvalidArgument')
  174. def test_bad_method(self):
  175. req = Request.blank('/',
  176. environ={'REQUEST_METHOD': 'PUT'},
  177. headers={'Authorization': 'AWS test:tester:hmac'})
  178. resp = self.app(req.environ, start_response)
  179. dom = xml.dom.minidom.parseString("".join(resp))
  180. self.assertEquals(dom.firstChild.nodeName, 'Error')
  181. code = dom.getElementsByTagName('Code')[0].childNodes[0].nodeValue
  182. self.assertEquals(code, 'InvalidURI')
  183. def _test_method_error(self, cl, method, path, status):
  184. local_app = swift3.filter_factory({})(cl(status))
  185. req = Request.blank(path,
  186. environ={'REQUEST_METHOD': method},
  187. headers={'Authorization': 'AWS test:tester:hmac'})
  188. resp = local_app(req.environ, start_response)
  189. dom = xml.dom.minidom.parseString("".join(resp))
  190. self.assertEquals(dom.firstChild.nodeName, 'Error')
  191. return dom.getElementsByTagName('Code')[0].childNodes[0].nodeValue
  192. def test_service_GET_error(self):
  193. code = self._test_method_error(FakeAppService, 'GET', '/', 401)
  194. self.assertEquals(code, 'AccessDenied')
  195. code = self._test_method_error(FakeAppService, 'GET', '/', 0)
  196. self.assertEquals(code, 'InvalidURI')
  197. def test_service_GET(self):
  198. local_app = swift3.filter_factory({})(FakeAppService())
  199. req = Request.blank('/',
  200. environ={'REQUEST_METHOD': 'GET'},
  201. headers={'Authorization': 'AWS test:tester:hmac'})
  202. resp = local_app(req.environ, local_app.app.do_start_response)
  203. self.assertEquals(local_app.app.response_args[0].split()[0], '200')
  204. dom = xml.dom.minidom.parseString("".join(resp))
  205. self.assertEquals(dom.firstChild.nodeName, 'ListAllMyBucketsResult')
  206. buckets = [n for n in dom.getElementsByTagName('Bucket')]
  207. listing = [n for n in buckets[0].childNodes if n.nodeName != '#text']
  208. self.assertEquals(len(listing), 2)
  209. names = []
  210. for b in buckets:
  211. if b.childNodes[0].nodeName == 'Name':
  212. names.append(b.childNodes[0].childNodes[0].nodeValue)
  213. self.assertEquals(len(names), len(FakeAppService().buckets))
  214. for i in FakeAppService().buckets:
  215. self.assertTrue(i[0] in names)
  216. def test_bucket_GET_error(self):
  217. code = self._test_method_error(FakeAppBucket, 'GET', '/bucket', 401)
  218. self.assertEquals(code, 'AccessDenied')
  219. code = self._test_method_error(FakeAppBucket, 'GET', '/bucket', 404)
  220. self.assertEquals(code, 'InvalidBucketName')
  221. code = self._test_method_error(FakeAppBucket, 'GET', '/bucket', 0)
  222. self.assertEquals(code, 'InvalidURI')
  223. def test_bucket_GET_uploads(self):
  224. local_app = swift3.filter_factory({})(FakeAppBucketMPU())
  225. bucket_name = 'junk'
  226. req = Request.blank('/%s?uploads' % bucket_name,
  227. environ={'REQUEST_METHOD': 'GET'},
  228. headers={'Authorization': 'AWS test:tester:hmac'})
  229. resp = local_app(req.environ, local_app.app.do_start_response)
  230. self.assertEquals(local_app.app.response_args[0].split()[0], '200')
  231. dom = xml.dom.minidom.parseString("".join(resp))
  232. self.assertEquals(dom.firstChild.nodeName,
  233. 'ListMultipartUploadsResult')
  234. name = dom.getElementsByTagName('Bucket')[0].childNodes[0].nodeValue
  235. self.assertEquals(name, bucket_name)
  236. objects = [n for n in dom.getElementsByTagName('Upload')]
  237. names = []
  238. for o in objects:
  239. if o.childNodes[0].nodeName == 'Key':
  240. names.append(o.childNodes[0].childNodes[0].nodeValue)
  241. if o.childNodes[-1].nodeName == 'Initiated':
  242. self.assertTrue(
  243. o.childNodes[-1].childNodes[0].nodeValue.endswith('Z'))
  244. self.assertEquals(len(names), len(FakeAppBucketMPU().objects))
  245. for i in FakeAppBucketMPU().objects:
  246. for name in names:
  247. if name in i[0]:
  248. self.assertTrue(name in i[0])
  249. def test_bucket_GET(self):
  250. local_app = swift3.filter_factory({})(FakeAppBucket())
  251. bucket_name = 'junk'
  252. req = Request.blank('/%s' % bucket_name,
  253. environ={'REQUEST_METHOD': 'GET'},
  254. headers={'Authorization': 'AWS test:tester:hmac'})
  255. resp = local_app(req.environ, local_app.app.do_start_response)
  256. self.assertEquals(local_app.app.response_args[0].split()[0], '200')
  257. dom = xml.dom.minidom.parseString("".join(resp))
  258. self.assertEquals(dom.firstChild.nodeName, 'ListBucketResult')
  259. name = dom.getElementsByTagName('Name')[0].childNodes[0].nodeValue
  260. self.assertEquals(name, bucket_name)
  261. objects = [n for n in dom.getElementsByTagName('Contents')]
  262. listing = [n for n in objects[0].childNodes if n.nodeName != '#text']
  263. names = []
  264. for o in objects:
  265. if o.childNodes[0].nodeName == 'Key':
  266. names.append(o.childNodes[0].childNodes[0].nodeValue)
  267. if o.childNodes[1].nodeName == 'LastModified':
  268. self.assertTrue(
  269. o.childNodes[1].childNodes[0].nodeValue.endswith('Z'))
  270. self.assertEquals(len(names), len(FakeAppBucket().objects))
  271. for i in FakeAppBucket().objects:
  272. self.assertTrue(i[0] in names)
  273. def test_bucket_GET_is_truncated(self):
  274. local_app = swift3.filter_factory({})(FakeAppBucket())
  275. bucket_name = 'junk'
  276. req = Request.blank('/%s' % bucket_name,
  277. environ={'REQUEST_METHOD': 'GET',
  278. 'QUERY_STRING': 'max-keys=3'},
  279. headers={'Authorization': 'AWS test:tester:hmac'})
  280. resp = local_app(req.environ, local_app.app.do_start_response)
  281. dom = xml.dom.minidom.parseString("".join(resp))
  282. self.assertEquals(dom.getElementsByTagName('IsTruncated')[0].
  283. childNodes[0].nodeValue, 'false')
  284. req = Request.blank('/%s' % bucket_name,
  285. environ={'REQUEST_METHOD': 'GET',
  286. 'QUERY_STRING': 'max-keys=2'},
  287. headers={'Authorization': 'AWS test:tester:hmac'})
  288. resp = local_app(req.environ, local_app.app.do_start_response)
  289. dom = xml.dom.minidom.parseString("".join(resp))
  290. self.assertEquals(dom.getElementsByTagName('IsTruncated')[0].
  291. childNodes[0].nodeValue, 'true')
  292. def test_bucket_GET_max_keys(self):
  293. class FakeApp(object):
  294. def __call__(self, env, start_response):
  295. self.query_string = env['QUERY_STRING']
  296. start_response('200 OK', [])
  297. return '[]'
  298. fake_app = FakeApp()
  299. local_app = swift3.filter_factory({})(fake_app)
  300. bucket_name = 'junk'
  301. req = Request.blank('/%s' % bucket_name,
  302. environ={'REQUEST_METHOD': 'GET',
  303. 'QUERY_STRING': 'max-keys=5'},
  304. headers={'Authorization': 'AWS test:tester:hmac'})
  305. resp = local_app(req.environ, lambda *args: None)
  306. dom = xml.dom.minidom.parseString("".join(resp))
  307. self.assertEquals(dom.getElementsByTagName('MaxKeys')[0].
  308. childNodes[0].nodeValue, '5')
  309. args = dict(cgi.parse_qsl(fake_app.query_string))
  310. self.assert_(args['limit'] == '6')
  311. req = Request.blank('/%s' % bucket_name,
  312. environ={'REQUEST_METHOD': 'GET',
  313. 'QUERY_STRING': 'max-keys=5000'},
  314. headers={'Authorization': 'AWS test:tester:hmac'})
  315. resp = local_app(req.environ, lambda *args: None)
  316. dom = xml.dom.minidom.parseString("".join(resp))
  317. self.assertEquals(dom.getElementsByTagName('MaxKeys')[0].
  318. childNodes[0].nodeValue, '1000')
  319. args = dict(cgi.parse_qsl(fake_app.query_string))
  320. self.assertEquals(args['limit'], '1001')
  321. def test_bucket_GET_passthroughs(self):
  322. class FakeApp(object):
  323. def __call__(self, env, start_response):
  324. self.query_string = env['QUERY_STRING']
  325. start_response('200 OK', [])
  326. return '[]'
  327. fake_app = FakeApp()
  328. local_app = swift3.filter_factory({})(fake_app)
  329. bucket_name = 'junk'
  330. req = Request.blank('/%s' % bucket_name,
  331. environ={'REQUEST_METHOD': 'GET', 'QUERY_STRING':
  332. 'delimiter=a&marker=b&prefix=c'},
  333. headers={'Authorization': 'AWS test:tester:hmac'})
  334. resp = local_app(req.environ, lambda *args: None)
  335. dom = xml.dom.minidom.parseString("".join(resp))
  336. self.assertEquals(dom.getElementsByTagName('Prefix')[0].
  337. childNodes[0].nodeValue, 'c')
  338. self.assertEquals(dom.getElementsByTagName('Marker')[0].
  339. childNodes[0].nodeValue, 'b')
  340. self.assertEquals(dom.getElementsByTagName('Delimiter')[0].
  341. childNodes[0].nodeValue, 'a')
  342. args = dict(cgi.parse_qsl(fake_app.query_string))
  343. self.assertEquals(args['delimiter'], 'a')
  344. self.assertEquals(args['marker'], 'b')
  345. self.assertEquals(args['prefix'], 'c')
  346. def test_bucket_PUT_error(self):
  347. code = self._test_method_error(FakeAppBucket, 'PUT', '/bucket', 401)
  348. self.assertEquals(code, 'AccessDenied')
  349. code = self._test_method_error(FakeAppBucket, 'PUT', '/bucket', 202)
  350. self.assertEquals(code, 'BucketAlreadyExists')
  351. code = self._test_method_error(FakeAppBucket, 'PUT', '/bucket', 0)
  352. self.assertEquals(code, 'InvalidURI')
  353. def test_bucket_PUT(self):
  354. local_app = swift3.filter_factory({})(FakeAppBucket(201))
  355. req = Request.blank('/bucket',
  356. environ={'REQUEST_METHOD': 'PUT'},
  357. headers={'Authorization': 'AWS test:tester:hmac'})
  358. resp = local_app(req.environ, local_app.app.do_start_response)
  359. self.assertEquals(local_app.app.response_args[0].split()[0], '200')
  360. def test_bucket_DELETE_error(self):
  361. code = self._test_method_error(FakeAppBucket, 'DELETE', '/bucket', 401)
  362. self.assertEquals(code, 'AccessDenied')
  363. code = self._test_method_error(FakeAppBucket, 'DELETE', '/bucket', 404)
  364. self.assertEquals(code, 'InvalidBucketName')
  365. code = self._test_method_error(FakeAppBucket, 'DELETE', '/bucket', 409)
  366. self.assertEquals(code, 'BucketNotEmpty')
  367. code = self._test_method_error(FakeAppBucket, 'DELETE', '/bucket', 0)
  368. self.assertEquals(code, 'InvalidURI')
  369. def test_bucket_DELETE(self):
  370. local_app = swift3.filter_factory({})(FakeAppBucket(204))
  371. req = Request.blank('/bucket',
  372. environ={'REQUEST_METHOD': 'DELETE'},
  373. headers={'Authorization': 'AWS test:tester:hmac'})
  374. with mock.patch('swifts3.middleware.BucketController'
  375. '.mpu_bucket_deletion') as mocked:
  376. mocked.return_value = Response(status=204)
  377. resp = local_app(req.environ, local_app.app.do_start_response)
  378. self.assertEquals(local_app.app.response_args[0].split()[0], '204')
  379. def test_bucket_DELETE_mpu(self):
  380. class FakeObjects(object):
  381. status_int = 200
  382. body = simplejson.dumps([{'name': 'some_name/meta'},
  383. {'name': 'some_name'}])
  384. headers = {"test": "test"}
  385. local_app = swift3.filter_factory({})(FakeAppBucket(204))
  386. req = Request.blank('/bucket',
  387. environ={'REQUEST_METHOD': 'DELETE'},
  388. headers={'Authorization': 'AWS test:tester:hmac'})
  389. with mock.patch('webob.Request.get_response') as mocked:
  390. with mock.patch('swifts3.middleware.BucketController'
  391. '.mpu_bucket_deletion_list_request') as mocked_list_req:
  392. mocked.return_value = Response(status=204)
  393. mocked_list_req.return_value = FakeObjects()
  394. resp = local_app(req.environ, local_app.app.do_start_response)
  395. self.assertEquals(local_app.app.response_args[0].split()[0],
  396. '204')
  397. def _check_acl(self, owner, resp):
  398. dom = xml.dom.minidom.parseString("".join(resp))
  399. self.assertEquals(dom.firstChild.nodeName, 'AccessControlPolicy')
  400. name = dom.getElementsByTagName('Permission')[0].childNodes[0].\
  401. nodeValue
  402. self.assertEquals(name, 'FULL_CONTROL')
  403. name = dom.getElementsByTagName('ID')[0].childNodes[0].nodeValue
  404. self.assertEquals(name, owner)
  405. def test_bucket_acl_GET(self):
  406. local_app = swift3.filter_factory({})(FakeAppBucket())
  407. bucket_name = 'junk'
  408. req = Request.blank('/%s?acl' % bucket_name,
  409. environ={'REQUEST_METHOD': 'GET'},
  410. headers={'Authorization': 'AWS test:tester:hmac'})
  411. resp = local_app(req.environ, local_app.app.do_start_response)
  412. self._check_acl('test:tester', resp)
  413. def _test_object_GETorHEAD(self, method):
  414. local_app = swift3.filter_factory({})(FakeAppObject())
  415. req = Request.blank('/bucket/object',
  416. environ={'REQUEST_METHOD': method},
  417. headers={'Authorization': 'AWS test:tester:hmac'})
  418. resp = local_app(req.environ, local_app.app.do_start_response)
  419. self.assertEquals(local_app.app.response_args[0].split()[0], '200')
  420. headers = dict(local_app.app.response_args[1])
  421. for key, val in local_app.app.response_headers.iteritems():
  422. if key in ('Content-Length', 'Content-Type', 'Content-Encoding',
  423. 'etag', 'last-modified'):
  424. self.assertTrue(key in headers)
  425. if key == 'Content-Length' and method == 'HEAD':
  426. self.assertEquals(headers[key], '0')
  427. else:
  428. self.assertEquals(headers[key], val)
  429. elif key.startswith('x-object-meta-'):
  430. self.assertTrue('x-amz-meta-' + key[14:] in headers)
  431. self.assertEquals(headers['x-amz-meta-' + key[14:]], val)
  432. if method == 'GET':
  433. self.assertEquals(resp, local_app.app.object_body)
  434. def test_object_HEAD(self):
  435. self._test_object_GETorHEAD('HEAD')
  436. def test_object_GET_error(self):
  437. code = self._test_method_error(FakeAppObject, 'GET',
  438. '/bucket/object', 401)
  439. self.assertEquals(code, 'AccessDenied')
  440. code = self._test_method_error(FakeAppObject, 'GET',
  441. '/bucket/object', 404)
  442. self.assertEquals(code, 'NoSuchKey')
  443. code = self._test_method_error(FakeAppObject, 'GET',
  444. '/bucket/object', 0)
  445. self.assertEquals(code, 'InvalidURI')
  446. def test_object_GET(self):
  447. self._test_object_GETorHEAD('GET')
  448. def test_object_PUT_error(self):
  449. code = self._test_method_error(FakeAppObject, 'PUT',
  450. '/bucket/object', 401)
  451. self.assertEquals(code, 'AccessDenied')
  452. code = self._test_method_error(FakeAppObject, 'PUT',
  453. '/bucket/object', 404)
  454. self.assertEquals(code, 'InvalidBucketName')
  455. code = self._test_method_error(FakeAppObject, 'PUT',
  456. '/bucket/object', 0)
  457. self.assertEquals(code, 'InvalidURI')
  458. def test_object_PUT(self):
  459. local_app = swift3.filter_factory({})(FakeAppObject(201))
  460. req = Request.blank('/bucket/object',
  461. environ={'REQUEST_METHOD': 'PUT'},
  462. headers={
  463. 'Authorization': 'AWS test:tester:hmac',
  464. 'x-amz-storage-class': 'REDUCED_REDUNDANCY',
  465. 'Content-MD5': 'Gyz1NfJ3Mcl0NDZFo5hTKA==',
  466. })
  467. req.date = datetime.now()
  468. req.content_type = 'text/plain'
  469. resp = local_app(req.environ, local_app.app.do_start_response)
  470. self.assertEquals(local_app.app.response_args[0].split()[0], '200')
  471. headers = dict(local_app.app.response_args[1])
  472. self.assertEquals(headers['ETag'],
  473. "\"%s\"" % local_app.app.response_headers['etag'])
  474. def test_object_PUT_headers(self):
  475. class FakeApp(object):
  476. def __call__(self, env, start_response):
  477. self.req = Request(env)
  478. start_response('200 OK', [])
  479. app = FakeApp()
  480. local_app = swift3.filter_factory({})(app)
  481. req = Request.blank('/bucket/object',
  482. environ={'REQUEST_METHOD': 'PUT'},
  483. headers={'Authorization': 'AWS test:tester:hmac',
  484. 'X-Amz-Storage-Class': 'REDUCED_REDUNDANCY',
  485. 'X-Amz-Meta-Something': 'oh hai',
  486. 'X-Amz-Copy-Source': '/some/source',
  487. 'Content-MD5': 'ffoHqOWd280dyE1MT4KuoQ=='})
  488. req.date = datetime.now()
  489. req.content_type = 'text/plain'
  490. resp = local_app(req.environ, lambda *args: None)
  491. self.assertEquals(app.req.headers['ETag'],
  492. '7dfa07a8e59ddbcd1dc84d4c4f82aea1')
  493. self.assertEquals(app.req.headers['X-Object-Meta-Something'], 'oh hai')
  494. self.assertEquals(app.req.headers['X-Copy-From'], '/some/source')
  495. def test_object_DELETE_error(self):
  496. code = self._test_method_error(FakeAppObject, 'DELETE',
  497. '/bucket/object', 401)
  498. self.assertEquals(code, 'AccessDenied')
  499. code = self._test_method_error(FakeAppObject, 'DELETE',
  500. '/bucket/object', 404)
  501. self.assertEquals(code, 'NoSuchKey')
  502. code = self._test_method_error(FakeAppObject, 'DELETE',
  503. '/bucket/object', 0)
  504. self.assertEquals(code, 'InvalidURI')
  505. def test_object_DELETE(self):
  506. local_app = swift3.filter_factory({})(FakeAppObject(204))
  507. req = Request.blank('/bucket/object',
  508. environ={'REQUEST_METHOD': 'DELETE'},
  509. headers={'Authorization': 'AWS test:tester:hmac'})
  510. resp = local_app(req.environ, local_app.app.do_start_response)
  511. self.assertEquals(local_app.app.response_args[0].split()[0], '204')
  512. local_app = swift3.filter_factory({})(FakeAppObject(204))
  513. req = Request.blank('/bucket/object',
  514. environ={'REQUEST_METHOD': 'DELETE'},
  515. headers={'Authorization': 'AWS test:tester:hmac',
  516. 'X-Object-Manifest': '123'})
  517. class FakeResponse(object):
  518. status_int = 200
  519. headers = {'X-Object-Manifest': \
  520. 'somefile_new2/18dc9b42566848abb58d8bb1ccbaf37a/'}
  521. with mock.patch('webob.Request.get_response') as mocked:
  522. mocked.return_value = FakeResponse()
  523. resp = local_app(req.environ, local_app.app.do_start_response)
  524. self.assertEquals(local_app.app.response_args[0].split()[0], '204')
  525. def test_object_acl_GET(self):
  526. local_app = swift3.filter_factory({})(FakeAppObject())
  527. req = Request.blank('/bucket/object?acl',
  528. environ={'REQUEST_METHOD': 'GET'},
  529. headers={'Authorization': 'AWS test:tester:hmac'})
  530. resp = local_app(req.environ, local_app.app.do_start_response)
  531. self._check_acl('test:tester', resp)
  532. def test_multipart_GET(self):
  533. class FakeResponse(object):
  534. status_int = 200
  535. body = [{"name": "test_bucket",
  536. "last_modified": "2011-01-05T02:19:14.275290",
  537. "hash": 0,
  538. "bytes": 3909}]
  539. body = simplejson.dumps(body)
  540. local_app = swift3.filter_factory({})(FakeAppObject(200))
  541. bucket_name = 'bucket'
  542. req = Request.blank('/%s/object?uploadId=deadbeef' % bucket_name,
  543. environ={'REQUEST_METHOD': 'GET'},
  544. headers={'Authorization': 'AWS test:tester:hmac'})
  545. with mock.patch('webob.Request.get_response') as mocked:
  546. mocked.return_value = FakeResponse()
  547. resp = local_app(req.environ, local_app.app.do_start_response)
  548. self.assertEquals(local_app.app.response_args[0].split()[0], '200')
  549. def test_multipart_GET_error(self):
  550. code = self._test_method_error(
  551. FakeAppObject,
  552. 'GET',
  553. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  554. 'bucket/object?uploadId=deadbeef',
  555. 0
  556. )
  557. self.assertEquals(code, 'NoSuchUpload')
  558. code = self._test_method_error(
  559. FakeAppObject,
  560. 'GET',
  561. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  562. 'bucket/object?uploadId=123&part-number-marker=abc',
  563. 0
  564. )
  565. self.assertEquals(code, 'InvalidURI')
  566. code = self._test_method_error(
  567. FakeAppObject,
  568. 'GET',
  569. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  570. 'bucket/object?uploadId=!@#12',
  571. 0
  572. )
  573. self.assertEquals(code, 'InvalidURI')
  574. def test_multipart_POST(self):
  575. class FakeResponse201(object):
  576. status_int = 201
  577. headers = {"ETag": "1b2cf535f27731c974343645a3985328"}
  578. with mock.patch('webob.Request.get_response') as mocked:
  579. mocked.return_value = FakeResponse201()
  580. local_app = swift3.filter_factory({})(FakeAppObject(201))
  581. req = Request.blank('/' + self.MULTIPART_UPLOAD_PREFIX + \
  582. 'bucket/object?uploads=123',
  583. environ={'REQUEST_METHOD': 'POST'},
  584. headers={'Authorization': \
  585. 'AWS test:tester:hmac'})
  586. resp = local_app(req.environ, local_app.app.do_start_response)
  587. self.assertEquals(local_app.app.response_args[0].split()[0], '200')
  588. with mock.patch('swifts3.middleware.meta_request_head') as mocked:
  589. with mock.patch('webob.Request.get_response') as mocked_manifest:
  590. mocked.return_value = Response(status=200,
  591. headers={"test": "test"})
  592. mocked_manifest.return_value = FakeResponse201()
  593. local_app = swift3.filter_factory({})(FakeAppObject(201))
  594. req = Request.blank('/' + self.MULTIPART_UPLOAD_PREFIX + \
  595. 'bucket/object?uploadId=deadbeef',
  596. environ={'REQUEST_METHOD': 'POST'},
  597. headers={'Authorization': \
  598. 'AWS test:tester:hmac'})
  599. resp = local_app(req.environ, local_app.app.do_start_response)
  600. self.assertEquals(local_app.app.response_args[0].split()[0],
  601. '200')
  602. def test_multipart_POST_error(self):
  603. class FakeResponse204(object):
  604. status_int = 204
  605. with mock.patch('webob.Request.get_response') as mocked:
  606. mocked.return_value = FakeResponse204()
  607. code = self._test_method_error(
  608. FakeAppService,
  609. 'POST',
  610. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  611. 'bucket/object?uploads',
  612. 0
  613. )
  614. self.assertEquals(code, 'InvalidURI')
  615. class FakeResponse404(object):
  616. status_int = 404
  617. with mock.patch('webob.Request.get_response') as mocked:
  618. mocked.return_value = FakeResponse404()
  619. code = self._test_method_error(
  620. FakeAppService,
  621. 'POST',
  622. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  623. 'bucket/object?uploads',
  624. 404
  625. )
  626. self.assertEquals(code, 'InvalidBucketName')
  627. code = self._test_method_error(
  628. FakeAppService,
  629. 'POST',
  630. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  631. 'bucket/object?uploadId=123',
  632. 404
  633. )
  634. self.assertEquals(code, 'NoSuchUpload')
  635. code = self._test_method_error(
  636. FakeAppService,
  637. 'POST',
  638. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  639. 'bucket/object?uploads',
  640. 401
  641. )
  642. self.assertEquals(code, 'AccessDenied')
  643. code = self._test_method_error(
  644. FakeAppService,
  645. 'POST',
  646. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  647. 'bucket/object?uploads',
  648. 0
  649. )
  650. self.assertEquals(code, 'InvalidURI')
  651. code = self._test_method_error(
  652. FakeAppService,
  653. 'POST',
  654. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  655. 'bucket/object?uploadId=deadbeef',
  656. 401
  657. )
  658. self.assertEquals(code, 'AccessDenied')
  659. code = self._test_method_error(
  660. FakeAppService,
  661. 'POST',
  662. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  663. 'bucket/object?uploadId=deadbeef',
  664. 0
  665. )
  666. self.assertEquals(code, 'InvalidURI')
  667. code = self._test_method_error(
  668. FakeAppService,
  669. 'POST',
  670. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  671. 'bucket/object?uploadId=ad2@!',
  672. 0
  673. )
  674. self.assertEquals(code, 'InvalidURI')
  675. class FakeResponse200(object):
  676. status_int = 200
  677. headers = {"test": "test"}
  678. with mock.patch('webob.Request.get_response') as mocked:
  679. mocked.return_value = FakeResponse200()
  680. code = self._test_method_error(
  681. FakeAppObject,
  682. 'POST',
  683. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  684. 'bucket/object?uploadId=deadbeef',
  685. 0
  686. )
  687. self.assertEquals(code, 'InvalidURI')
  688. def test_multipart_PUT_error(self):
  689. class FakeResponse(object):
  690. status_int = 200
  691. headers = {"test": "test"}
  692. with mock.patch('webob.Request.get_response') as mocked:
  693. mocked.return_value = FakeResponse()
  694. code = self._test_method_error(
  695. FakeAppObject,
  696. 'PUT',
  697. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  698. 'bucket/object?uploadId=deadbeef&partNumber=1',
  699. 0
  700. )
  701. self.assertEquals(code, 'InvalidURI')
  702. code = self._test_method_error(
  703. FakeAppObject,
  704. 'PUT',
  705. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  706. 'bucket/object?uploadId=deadbeef&partNumber=1',
  707. 0
  708. )
  709. self.assertEquals(code, 'NoSuchUpload')
  710. code = self._test_method_error(
  711. FakeAppObject,
  712. 'PUT',
  713. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  714. 'bucket/object?uploadId=#@!$',
  715. 0
  716. )
  717. self.assertEquals(code, 'InvalidURI')
  718. code = self._test_method_error(
  719. FakeAppObject,
  720. 'PUT',
  721. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  722. 'bucket/object?uploadId=123',
  723. 0
  724. )
  725. self.assertEquals(code, 'InvalidURI')
  726. code = self._test_method_error(
  727. FakeAppObject,
  728. 'PUT',
  729. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  730. 'bucket/object?uploadId=deadbeef&partNumber=abc',
  731. 0
  732. )
  733. self.assertEquals(code, 'InvalidURI')
  734. def test_multipart_DELETE(self):
  735. class FakeResponse200(object):
  736. status_int = 200
  737. body = [{"name": "test"}]
  738. body = simplejson.dumps(body)
  739. local_app = swift3.filter_factory({})(FakeAppObject(200))
  740. with mock.patch('webob.Request.get_response') as mocked:
  741. mocked.return_value = FakeResponse200()
  742. req = Request.blank('/bucket/object?uploadId=deadbeef',
  743. environ={'REQUEST_METHOD': 'DELETE'},
  744. headers={
  745. 'Authorization': 'AWS test:tester:hmac',
  746. 'Content-MD5': 'Gyz1NfJ3Mcl0NDZFo5hTKA==',
  747. })
  748. resp = local_app(req.environ, local_app.app.do_start_response)
  749. self.assertEquals(local_app.app.response_args[0].split()[0], '204')
  750. def test_multipart_DELETE_error(self):
  751. code = self._test_method_error(
  752. FakeAppObject,
  753. 'DELETE',
  754. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  755. 'bucket/object?uploadId=#@!$',
  756. 0
  757. )
  758. self.assertEquals(code, 'InvalidURI')
  759. code = self._test_method_error(
  760. FakeAppObject,
  761. 'DELETE',
  762. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  763. 'bucket/object?uploadId=deadbeef',
  764. 401
  765. )
  766. self.assertEquals(code, 'AccessDenied')
  767. code = self._test_method_error(
  768. FakeAppObject,
  769. 'DELETE',
  770. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  771. 'bucket/object?uploadId=deadbeef',
  772. 404
  773. )
  774. self.assertEquals(code, 'InvalidBucketName')
  775. code = self._test_method_error(
  776. FakeAppObject,
  777. 'DELETE',
  778. '/' + self.MULTIPART_UPLOAD_PREFIX + \
  779. 'bucket/object?uploadId=deadbeef',
  780. 0
  781. )
  782. self.assertEquals(code, 'InvalidURI')
  783. def test_canonical_string(self):
  784. """
  785. The hashes here were generated by running the same requests against
  786. boto.utils.canonical_string
  787. """
  788. def verify(hash, path, headers):
  789. req = Request.blank(path, headers=headers)
  790. self.assertEquals(hash,
  791. hashlib.md5(swift3.canonical_string(req)).hexdigest())
  792. verify('6dd08c75e42190a1ce9468d1fd2eb787', '/bucket/object',
  793. {'Content-Type': 'text/plain', 'X-Amz-Something': 'test',
  794. 'Date': 'whatever'})
  795. verify('c8447135da232ae7517328f3429df481', '/bucket/object',
  796. {'Content-Type': 'text/plain', 'X-Amz-Something': 'test'})
  797. verify('bf49304103a4de5c325dce6384f2a4a2', '/bucket/object',
  798. {'content-type': 'text/plain'})
  799. verify('be01bd15d8d47f9fe5e2d9248cc6f180', '/bucket/object', {})
  800. verify('8d28cc4b8322211f6cc003256cd9439e', 'bucket/object',
  801. {'Content-MD5': 'somestuff'})
  802. verify('a822deb31213ad09af37b5a7fe59e55e', '/bucket/object?acl', {})
  803. verify('cce5dd1016595cb706c93f28d3eaa18f', '/bucket/object',
  804. {'Content-Type': 'text/plain', 'X-Amz-A': 'test',
  805. 'X-Amz-Z': 'whatever', 'X-Amz-B': 'lalala',
  806. 'X-Amz-Y': 'lalalalalalala'})
  807. verify('7506d97002c7d2de922cc0ec34af8846', '/bucket/object',
  808. {'Content-Type': None, 'X-Amz-Something': 'test'})
  809. verify('28f76d6162444a193b612cd6cb20e0be', '/bucket/object',
  810. {'Content-Type': None,
  811. 'X-Amz-Date': 'Mon, 11 Jul 2011 10:52:57 +0000',
  812. 'Date': 'Tue, 12 Jul 2011 10:52:57 +0000'})
  813. verify('ed6971e3eca5af4ee361f05d7c272e49', '/bucket/object',
  814. {'Content-Type': None,
  815. 'Date': 'Tue, 12 Jul 2011 10:52:57 +0000'})
  816. req1 = Request.blank('/', headers={'Content-Type': None,
  817. 'X-Amz-Something': 'test'})
  818. req2 = Request.blank('/', headers={'Content-Type': '',
  819. 'X-Amz-Something': 'test'})
  820. req3 = Request.blank('/', headers={'X-Amz-Something': 'test'})
  821. self.assertEquals(swift3.canonical_string(req1),
  822. swift3.canonical_string(req2))
  823. self.assertEquals(swift3.canonical_string(req2),
  824. swift3.canonical_string(req3))
  825. def test_signed_urls(self):
  826. class FakeApp(object):
  827. def __call__(self, env, start_response):
  828. self.req = Request(env)
  829. start_response('200 OK', [])
  830. app = FakeApp()
  831. local_app = swift3.filter_factory({})(app)
  832. req = Request.blank('/bucket/object?Signature=X&Expires=Y&'
  833. 'AWSAccessKeyId=Z', environ={'REQUEST_METHOD': 'GET'})
  834. req.date = datetime.now()
  835. req.content_type = 'text/plain'
  836. resp = local_app(req.environ, lambda *args: None)
  837. self.assertEquals(app.req.headers['Authorization'], 'AWS Z:X')
  838. self.assertEquals(app.req.headers['Date'], 'Y')