OpenStack Image Management (Glance)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_wsgi.py 32KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2010-2011 OpenStack Foundation
  3. # Copyright 2014 IBM Corp.
  4. # All Rights Reserved.
  5. #
  6. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  7. # not use this file except in compliance with the License. You may obtain
  8. # a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing, software
  13. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  14. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  15. # License for the specific language governing permissions and limitations
  16. # under the License.
  17. import datetime
  18. import gettext
  19. import os
  20. import socket
  21. from babel import localedata
  22. import eventlet.patcher
  23. import fixtures
  24. import mock
  25. from oslo_concurrency import processutils
  26. from oslo_serialization import jsonutils
  27. import routes
  28. import six
  29. from six.moves import http_client as http
  30. import webob
  31. from glance.api.v2 import router as router_v2
  32. from glance.common import exception
  33. from glance.common import utils
  34. from glance.common import wsgi
  35. from glance import i18n
  36. from glance.tests import utils as test_utils
  37. class RequestTest(test_utils.BaseTestCase):
  38. def _set_expected_languages(self, all_locales=None, avail_locales=None):
  39. if all_locales is None:
  40. all_locales = []
  41. # Override localedata.locale_identifiers to return some locales.
  42. def returns_some_locales(*args, **kwargs):
  43. return all_locales
  44. self.stubs.Set(localedata, 'locale_identifiers', returns_some_locales)
  45. # Override gettext.find to return other than None for some languages.
  46. def fake_gettext_find(lang_id, *args, **kwargs):
  47. found_ret = '/glance/%s/LC_MESSAGES/glance.mo' % lang_id
  48. if avail_locales is None:
  49. # All locales are available.
  50. return found_ret
  51. languages = kwargs['languages']
  52. if languages[0] in avail_locales:
  53. return found_ret
  54. return None
  55. self.stubs.Set(gettext, 'find', fake_gettext_find)
  56. def test_content_range(self):
  57. request = wsgi.Request.blank('/tests/123')
  58. request.headers["Content-Range"] = 'bytes 10-99/*'
  59. range_ = request.get_range_from_request(120)
  60. self.assertEqual(10, range_.start)
  61. self.assertEqual(100, range_.stop) # non-inclusive
  62. self.assertIsNone(range_.length)
  63. def test_content_range_invalid(self):
  64. request = wsgi.Request.blank('/tests/123')
  65. request.headers["Content-Range"] = 'bytes=0-99'
  66. self.assertRaises(webob.exc.HTTPRequestRangeNotSatisfiable,
  67. request.get_range_from_request, 120)
  68. def test_range(self):
  69. request = wsgi.Request.blank('/tests/123')
  70. request.headers["Range"] = 'bytes=10-99'
  71. range_ = request.get_range_from_request(120)
  72. self.assertEqual(10, range_.start)
  73. self.assertEqual(100, range_.end) # non-inclusive
  74. def test_range_invalid(self):
  75. request = wsgi.Request.blank('/tests/123')
  76. request.headers["Range"] = 'bytes=150-'
  77. self.assertRaises(webob.exc.HTTPRequestRangeNotSatisfiable,
  78. request.get_range_from_request, 120)
  79. def test_content_type_missing(self):
  80. request = wsgi.Request.blank('/tests/123')
  81. self.assertRaises(exception.InvalidContentType,
  82. request.get_content_type, ('application/xml',))
  83. def test_content_type_unsupported(self):
  84. request = wsgi.Request.blank('/tests/123')
  85. request.headers["Content-Type"] = "text/html"
  86. self.assertRaises(exception.InvalidContentType,
  87. request.get_content_type, ('application/xml',))
  88. def test_content_type_with_charset(self):
  89. request = wsgi.Request.blank('/tests/123')
  90. request.headers["Content-Type"] = "application/json; charset=UTF-8"
  91. result = request.get_content_type(('application/json',))
  92. self.assertEqual("application/json", result)
  93. def test_params(self):
  94. if six.PY2:
  95. expected = webob.multidict.NestedMultiDict({
  96. 'limit': '20', 'name':
  97. '\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82',
  98. 'sort_key': 'name', 'sort_dir': 'asc'})
  99. else:
  100. expected = webob.multidict.NestedMultiDict({
  101. 'limit': '20', 'name': 'Привет', 'sort_key': 'name',
  102. 'sort_dir': 'asc'})
  103. request = wsgi.Request.blank("/?limit=20&name=%D0%9F%D1%80%D0%B8"
  104. "%D0%B2%D0%B5%D1%82&sort_key=name"
  105. "&sort_dir=asc")
  106. actual = request.params
  107. self.assertEqual(expected, actual)
  108. def test_content_type_from_accept_xml(self):
  109. request = wsgi.Request.blank('/tests/123')
  110. request.headers["Accept"] = "application/xml"
  111. result = request.best_match_content_type()
  112. self.assertEqual("application/json", result)
  113. def test_content_type_from_accept_json(self):
  114. request = wsgi.Request.blank('/tests/123')
  115. request.headers["Accept"] = "application/json"
  116. result = request.best_match_content_type()
  117. self.assertEqual("application/json", result)
  118. def test_content_type_from_accept_xml_json(self):
  119. request = wsgi.Request.blank('/tests/123')
  120. request.headers["Accept"] = "application/xml, application/json"
  121. result = request.best_match_content_type()
  122. self.assertEqual("application/json", result)
  123. def test_content_type_from_accept_json_xml_quality(self):
  124. request = wsgi.Request.blank('/tests/123')
  125. request.headers["Accept"] = ("application/json; q=0.3, "
  126. "application/xml; q=0.9")
  127. result = request.best_match_content_type()
  128. self.assertEqual("application/json", result)
  129. def test_content_type_accept_default(self):
  130. request = wsgi.Request.blank('/tests/123.unsupported')
  131. request.headers["Accept"] = "application/unsupported1"
  132. result = request.best_match_content_type()
  133. self.assertEqual("application/json", result)
  134. def test_language_accept_default(self):
  135. request = wsgi.Request.blank('/tests/123')
  136. request.headers["Accept-Language"] = "zz-ZZ,zz;q=0.8"
  137. result = request.best_match_language()
  138. self.assertIsNone(result)
  139. def test_language_accept_none(self):
  140. request = wsgi.Request.blank('/tests/123')
  141. result = request.best_match_language()
  142. self.assertIsNone(result)
  143. def test_best_match_language_expected(self):
  144. # If Accept-Language is a supported language, best_match_language()
  145. # returns it.
  146. self._set_expected_languages(all_locales=['it'])
  147. req = wsgi.Request.blank('/', headers={'Accept-Language': 'it'})
  148. self.assertEqual('it', req.best_match_language())
  149. def test_request_match_language_unexpected(self):
  150. # If Accept-Language is a language we do not support,
  151. # best_match_language() returns None.
  152. self._set_expected_languages(all_locales=['it'])
  153. req = wsgi.Request.blank('/', headers={'Accept-Language': 'unknown'})
  154. self.assertIsNone(req.best_match_language())
  155. def test_best_match_language_unknown(self):
  156. # Test that we are actually invoking language negotiation by webop
  157. request = wsgi.Request.blank('/')
  158. accepted = 'unknown-lang'
  159. request.headers = {'Accept-Language': accepted}
  160. # TODO(rosmaita): simplify when lower_constraints has webob >= 1.8.1
  161. try:
  162. from webob.acceptparse import AcceptLanguageValidHeader # noqa
  163. cls = webob.acceptparse.AcceptLanguageValidHeader
  164. funcname = 'lookup'
  165. # Bug #1765748: see comment in code in the function under test
  166. # to understand why this is the correct return value for the
  167. # webob 1.8.x mock
  168. retval = 'fake_LANG'
  169. except ImportError:
  170. cls = webob.acceptparse.AcceptLanguage
  171. funcname = 'best_match'
  172. retval = None
  173. with mock.patch.object(cls, funcname) as mocked_function:
  174. mocked_function.return_value = retval
  175. self.assertIsNone(request.best_match_language())
  176. mocked_function.assert_called_once()
  177. # If Accept-Language is missing or empty, match should be None
  178. request.headers = {'Accept-Language': ''}
  179. self.assertIsNone(request.best_match_language())
  180. request.headers.pop('Accept-Language')
  181. self.assertIsNone(request.best_match_language())
  182. def test_http_error_response_codes(self):
  183. sample_id, member_id, tag_val, task_id = 'abc', '123', '1', '2'
  184. """Makes sure v2 unallowed methods return 405"""
  185. unallowed_methods = [
  186. ('/schemas/image', ['POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']),
  187. ('/schemas/images', ['POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']),
  188. ('/schemas/member', ['POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']),
  189. ('/schemas/members', ['POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']),
  190. ('/schemas/task', ['POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']),
  191. ('/schemas/tasks', ['POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']),
  192. ('/images', ['PUT', 'DELETE', 'PATCH', 'HEAD']),
  193. ('/images/%s' % sample_id, ['POST', 'PUT', 'HEAD']),
  194. ('/images/%s/file' % sample_id,
  195. ['POST', 'DELETE', 'PATCH', 'HEAD']),
  196. ('/images/%s/tags/%s' % (sample_id, tag_val),
  197. ['GET', 'POST', 'PATCH', 'HEAD']),
  198. ('/images/%s/members' % sample_id,
  199. ['PUT', 'DELETE', 'PATCH', 'HEAD']),
  200. ('/images/%s/members/%s' % (sample_id, member_id),
  201. ['POST', 'PATCH', 'HEAD']),
  202. ('/tasks', ['PUT', 'DELETE', 'PATCH', 'HEAD']),
  203. ('/tasks/%s' % task_id, ['POST', 'PUT', 'PATCH', 'HEAD']),
  204. ]
  205. api = test_utils.FakeAuthMiddleware(router_v2.API(routes.Mapper()))
  206. for uri, methods in unallowed_methods:
  207. for method in methods:
  208. req = webob.Request.blank(uri)
  209. req.method = method
  210. res = req.get_response(api)
  211. self.assertEqual(http.METHOD_NOT_ALLOWED, res.status_int)
  212. # Makes sure not implemented methods return 405
  213. req = webob.Request.blank('/schemas/image')
  214. req.method = 'NonexistentMethod'
  215. res = req.get_response(api)
  216. self.assertEqual(http.METHOD_NOT_ALLOWED, res.status_int)
  217. class ResourceTest(test_utils.BaseTestCase):
  218. def test_get_action_args(self):
  219. env = {
  220. 'wsgiorg.routing_args': [
  221. None,
  222. {
  223. 'controller': None,
  224. 'format': None,
  225. 'action': 'update',
  226. 'id': 12,
  227. },
  228. ],
  229. }
  230. expected = {'action': 'update', 'id': 12}
  231. actual = wsgi.Resource(None, None, None).get_action_args(env)
  232. self.assertEqual(expected, actual)
  233. def test_get_action_args_invalid_index(self):
  234. env = {'wsgiorg.routing_args': []}
  235. expected = {}
  236. actual = wsgi.Resource(None, None, None).get_action_args(env)
  237. self.assertEqual(expected, actual)
  238. def test_get_action_args_del_controller_error(self):
  239. actions = {'format': None,
  240. 'action': 'update',
  241. 'id': 12}
  242. env = {'wsgiorg.routing_args': [None, actions]}
  243. expected = {'action': 'update', 'id': 12}
  244. actual = wsgi.Resource(None, None, None).get_action_args(env)
  245. self.assertEqual(expected, actual)
  246. def test_get_action_args_del_format_error(self):
  247. actions = {'action': 'update', 'id': 12}
  248. env = {'wsgiorg.routing_args': [None, actions]}
  249. expected = {'action': 'update', 'id': 12}
  250. actual = wsgi.Resource(None, None, None).get_action_args(env)
  251. self.assertEqual(expected, actual)
  252. def test_dispatch(self):
  253. class Controller(object):
  254. def index(self, shirt, pants=None):
  255. return (shirt, pants)
  256. resource = wsgi.Resource(None, None, None)
  257. actual = resource.dispatch(Controller(), 'index', 'on', pants='off')
  258. expected = ('on', 'off')
  259. self.assertEqual(expected, actual)
  260. def test_dispatch_default(self):
  261. class Controller(object):
  262. def default(self, shirt, pants=None):
  263. return (shirt, pants)
  264. resource = wsgi.Resource(None, None, None)
  265. actual = resource.dispatch(Controller(), 'index', 'on', pants='off')
  266. expected = ('on', 'off')
  267. self.assertEqual(expected, actual)
  268. def test_dispatch_no_default(self):
  269. class Controller(object):
  270. def show(self, shirt, pants=None):
  271. return (shirt, pants)
  272. resource = wsgi.Resource(None, None, None)
  273. self.assertRaises(AttributeError, resource.dispatch, Controller(),
  274. 'index', 'on', pants='off')
  275. def test_call(self):
  276. class FakeController(object):
  277. def index(self, shirt, pants=None):
  278. return (shirt, pants)
  279. resource = wsgi.Resource(FakeController(), None, None)
  280. def dispatch(self, obj, action, *args, **kwargs):
  281. if isinstance(obj, wsgi.JSONRequestDeserializer):
  282. return []
  283. if isinstance(obj, wsgi.JSONResponseSerializer):
  284. raise webob.exc.HTTPForbidden()
  285. self.stubs.Set(wsgi.Resource, 'dispatch', dispatch)
  286. request = wsgi.Request.blank('/')
  287. response = resource.__call__(request)
  288. self.assertIsInstance(response, webob.exc.HTTPForbidden)
  289. self.assertEqual(http.FORBIDDEN, response.status_code)
  290. def test_call_raises_exception(self):
  291. class FakeController(object):
  292. def index(self, shirt, pants=None):
  293. return (shirt, pants)
  294. resource = wsgi.Resource(FakeController(), None, None)
  295. def dispatch(self, obj, action, *args, **kwargs):
  296. raise Exception("test exception")
  297. self.stubs.Set(wsgi.Resource, 'dispatch', dispatch)
  298. request = wsgi.Request.blank('/')
  299. response = resource.__call__(request)
  300. self.assertIsInstance(response, webob.exc.HTTPInternalServerError)
  301. self.assertEqual(http.INTERNAL_SERVER_ERROR, response.status_code)
  302. @mock.patch.object(wsgi, 'translate_exception')
  303. def test_resource_call_error_handle_localized(self,
  304. mock_translate_exception):
  305. class Controller(object):
  306. def delete(self, req, identity):
  307. raise webob.exc.HTTPBadRequest(explanation='Not Found')
  308. actions = {'action': 'delete', 'identity': 12}
  309. env = {'wsgiorg.routing_args': [None, actions]}
  310. request = wsgi.Request.blank('/tests/123', environ=env)
  311. message_es = 'No Encontrado'
  312. resource = wsgi.Resource(Controller(),
  313. wsgi.JSONRequestDeserializer(),
  314. None)
  315. translated_exc = webob.exc.HTTPBadRequest(message_es)
  316. mock_translate_exception.return_value = translated_exc
  317. e = self.assertRaises(webob.exc.HTTPBadRequest,
  318. resource, request)
  319. self.assertEqual(message_es, str(e))
  320. @mock.patch.object(i18n, 'translate')
  321. def test_translate_exception(self, mock_translate):
  322. # TODO(rosmaita): simplify when lower_constraints has webob >= 1.8.1
  323. try:
  324. from webob.acceptparse import AcceptLanguageValidHeader # noqa
  325. cls = webob.acceptparse.AcceptLanguageValidHeader
  326. funcname = 'lookup'
  327. except ImportError:
  328. cls = webob.acceptparse.AcceptLanguage
  329. funcname = 'best_match'
  330. with mock.patch.object(cls, funcname) as mocked_function:
  331. mock_translate.return_value = 'No Encontrado'
  332. mocked_function.return_value = 'de'
  333. req = wsgi.Request.blank('/tests/123')
  334. req.headers["Accept-Language"] = "de"
  335. e = webob.exc.HTTPNotFound(explanation='Not Found')
  336. e = wsgi.translate_exception(req, e)
  337. self.assertEqual('No Encontrado', e.explanation)
  338. def test_response_headers_encoded(self):
  339. # prepare environment
  340. for_openstack_comrades = \
  341. u'\u0417\u0430 \u043e\u043f\u0435\u043d\u0441\u0442\u0435\u043a, ' \
  342. u'\u0442\u043e\u0432\u0430\u0440\u0438\u0449\u0438'
  343. class FakeController(object):
  344. def index(self, shirt, pants=None):
  345. return (shirt, pants)
  346. class FakeSerializer(object):
  347. def index(self, response, result):
  348. response.headers['unicode_test'] = for_openstack_comrades
  349. # make request
  350. resource = wsgi.Resource(FakeController(), None, FakeSerializer())
  351. actions = {'action': 'index'}
  352. env = {'wsgiorg.routing_args': [None, actions]}
  353. request = wsgi.Request.blank('/tests/123', environ=env)
  354. response = resource.__call__(request)
  355. # ensure it has been encoded correctly
  356. value = (response.headers['unicode_test'].decode('utf-8')
  357. if six.PY2 else response.headers['unicode_test'])
  358. self.assertEqual(for_openstack_comrades, value)
  359. class JSONResponseSerializerTest(test_utils.BaseTestCase):
  360. def test_to_json(self):
  361. fixture = {"key": "value"}
  362. expected = b'{"key": "value"}'
  363. actual = wsgi.JSONResponseSerializer().to_json(fixture)
  364. self.assertEqual(expected, actual)
  365. def test_to_json_with_date_format_value(self):
  366. fixture = {"date": datetime.datetime(1901, 3, 8, 2)}
  367. expected = b'{"date": "1901-03-08T02:00:00.000000"}'
  368. actual = wsgi.JSONResponseSerializer().to_json(fixture)
  369. self.assertEqual(expected, actual)
  370. def test_to_json_with_more_deep_format(self):
  371. fixture = {"is_public": True, "name": [{"name1": "test"}]}
  372. expected = {"is_public": True, "name": [{"name1": "test"}]}
  373. actual = wsgi.JSONResponseSerializer().to_json(fixture)
  374. actual = jsonutils.loads(actual)
  375. for k in expected:
  376. self.assertEqual(expected[k], actual[k])
  377. def test_to_json_with_set(self):
  378. fixture = set(["foo"])
  379. expected = b'["foo"]'
  380. actual = wsgi.JSONResponseSerializer().to_json(fixture)
  381. self.assertEqual(expected, actual)
  382. def test_default(self):
  383. fixture = {"key": "value"}
  384. response = webob.Response()
  385. wsgi.JSONResponseSerializer().default(response, fixture)
  386. self.assertEqual(http.OK, response.status_int)
  387. content_types = [h for h in response.headerlist
  388. if h[0] == 'Content-Type']
  389. self.assertEqual(1, len(content_types))
  390. self.assertEqual('application/json', response.content_type)
  391. self.assertEqual(b'{"key": "value"}', response.body)
  392. class JSONRequestDeserializerTest(test_utils.BaseTestCase):
  393. def test_has_body_no_content_length(self):
  394. request = wsgi.Request.blank('/')
  395. request.method = 'POST'
  396. request.body = b'asdf'
  397. request.headers.pop('Content-Length')
  398. self.assertFalse(wsgi.JSONRequestDeserializer().has_body(request))
  399. def test_has_body_zero_content_length(self):
  400. request = wsgi.Request.blank('/')
  401. request.method = 'POST'
  402. request.body = b'asdf'
  403. request.headers['Content-Length'] = 0
  404. self.assertFalse(wsgi.JSONRequestDeserializer().has_body(request))
  405. def test_has_body_has_content_length(self):
  406. request = wsgi.Request.blank('/')
  407. request.method = 'POST'
  408. request.body = b'asdf'
  409. self.assertIn('Content-Length', request.headers)
  410. self.assertTrue(wsgi.JSONRequestDeserializer().has_body(request))
  411. def test_no_body_no_content_length(self):
  412. request = wsgi.Request.blank('/')
  413. self.assertFalse(wsgi.JSONRequestDeserializer().has_body(request))
  414. def test_from_json(self):
  415. fixture = '{"key": "value"}'
  416. expected = {"key": "value"}
  417. actual = wsgi.JSONRequestDeserializer().from_json(fixture)
  418. self.assertEqual(expected, actual)
  419. def test_from_json_malformed(self):
  420. fixture = 'kjasdklfjsklajf'
  421. self.assertRaises(webob.exc.HTTPBadRequest,
  422. wsgi.JSONRequestDeserializer().from_json, fixture)
  423. def test_default_no_body(self):
  424. request = wsgi.Request.blank('/')
  425. actual = wsgi.JSONRequestDeserializer().default(request)
  426. expected = {}
  427. self.assertEqual(expected, actual)
  428. def test_default_with_body(self):
  429. request = wsgi.Request.blank('/')
  430. request.method = 'POST'
  431. request.body = b'{"key": "value"}'
  432. actual = wsgi.JSONRequestDeserializer().default(request)
  433. expected = {"body": {"key": "value"}}
  434. self.assertEqual(expected, actual)
  435. def test_has_body_has_transfer_encoding(self):
  436. self.assertTrue(self._check_transfer_encoding(
  437. transfer_encoding='chunked'))
  438. def test_has_body_multiple_transfer_encoding(self):
  439. self.assertTrue(self._check_transfer_encoding(
  440. transfer_encoding='chunked, gzip'))
  441. def test_has_body_invalid_transfer_encoding(self):
  442. self.assertFalse(self._check_transfer_encoding(
  443. transfer_encoding='invalid', content_length=0))
  444. def test_has_body_invalid_transfer_encoding_no_content_len_and_body(self):
  445. self.assertFalse(self._check_transfer_encoding(
  446. transfer_encoding='invalid', include_body=False))
  447. def test_has_body_invalid_transfer_encoding_no_content_len_but_body(self):
  448. self.assertTrue(self._check_transfer_encoding(
  449. transfer_encoding='invalid', include_body=True))
  450. def test_has_body_invalid_transfer_encoding_with_content_length(self):
  451. self.assertTrue(self._check_transfer_encoding(
  452. transfer_encoding='invalid', content_length=5))
  453. def test_has_body_valid_transfer_encoding_with_content_length(self):
  454. self.assertTrue(self._check_transfer_encoding(
  455. transfer_encoding='chunked', content_length=1))
  456. def test_has_body_valid_transfer_encoding_without_content_length(self):
  457. self.assertTrue(self._check_transfer_encoding(
  458. transfer_encoding='chunked'))
  459. def _check_transfer_encoding(self, transfer_encoding=None,
  460. content_length=None, include_body=True):
  461. request = wsgi.Request.blank('/')
  462. request.method = 'POST'
  463. if include_body:
  464. request.body = b'fake_body'
  465. request.headers['transfer-encoding'] = transfer_encoding
  466. if content_length is not None:
  467. request.headers['content-length'] = content_length
  468. return wsgi.JSONRequestDeserializer().has_body(request)
  469. def test_get_bind_addr_default_value(self):
  470. expected = ('0.0.0.0', '123456')
  471. actual = wsgi.get_bind_addr(default_port="123456")
  472. self.assertEqual(expected, actual)
  473. class ServerTest(test_utils.BaseTestCase):
  474. def test_create_pool(self):
  475. """Ensure the wsgi thread pool is an eventlet.greenpool.GreenPool."""
  476. actual = wsgi.Server(threads=1).create_pool()
  477. self.assertIsInstance(actual, eventlet.greenpool.GreenPool)
  478. @mock.patch.object(wsgi.Server, 'configure_socket')
  479. def test_http_keepalive(self, mock_configure_socket):
  480. self.config(http_keepalive=False)
  481. self.config(workers=0)
  482. server = wsgi.Server(threads=1)
  483. server.sock = 'fake_socket'
  484. # mocking eventlet.wsgi server method to check it is called with
  485. # configured 'http_keepalive' value.
  486. with mock.patch.object(eventlet.wsgi,
  487. 'server') as mock_server:
  488. fake_application = "fake-application"
  489. server.start(fake_application, 0)
  490. server.wait()
  491. mock_server.assert_called_once_with('fake_socket',
  492. fake_application,
  493. log=server._logger,
  494. debug=False,
  495. custom_pool=server.pool,
  496. keepalive=False,
  497. socket_timeout=900)
  498. def test_number_of_workers(self):
  499. """Ensure the number of workers matches num cpus limited to 8."""
  500. def pid():
  501. i = 1
  502. while True:
  503. i = i + 1
  504. yield i
  505. with mock.patch.object(os, 'fork') as mock_fork:
  506. with mock.patch('oslo_concurrency.processutils.get_worker_count',
  507. return_value=4):
  508. mock_fork.side_effect = pid
  509. server = wsgi.Server()
  510. server.configure = mock.Mock()
  511. fake_application = "fake-application"
  512. server.start(fake_application, None)
  513. self.assertEqual(4, len(server.children))
  514. with mock.patch('oslo_concurrency.processutils.get_worker_count',
  515. return_value=24):
  516. mock_fork.side_effect = pid
  517. server = wsgi.Server()
  518. server.configure = mock.Mock()
  519. fake_application = "fake-application"
  520. server.start(fake_application, None)
  521. self.assertEqual(8, len(server.children))
  522. mock_fork.side_effect = pid
  523. server = wsgi.Server()
  524. server.configure = mock.Mock()
  525. fake_application = "fake-application"
  526. server.start(fake_application, None)
  527. cpus = processutils.get_worker_count()
  528. expected_workers = cpus if cpus < 8 else 8
  529. self.assertEqual(expected_workers,
  530. len(server.children))
  531. class TestHelpers(test_utils.BaseTestCase):
  532. def test_headers_are_unicode(self):
  533. """
  534. Verifies that the headers returned by conversion code are unicode.
  535. Headers are passed via http in non-testing mode, which automatically
  536. converts them to unicode. Verifying that the method does the
  537. conversion proves that we aren't passing data that works in tests
  538. but will fail in production.
  539. """
  540. fixture = {'name': 'fake public image',
  541. 'is_public': True,
  542. 'size': 19,
  543. 'location': "file:///tmp/glance-tests/2",
  544. 'properties': {'distro': 'Ubuntu 10.04 LTS'}}
  545. headers = utils.image_meta_to_http_headers(fixture)
  546. for k, v in six.iteritems(headers):
  547. self.assertIsInstance(v, six.text_type)
  548. def test_data_passed_properly_through_headers(self):
  549. """
  550. Verifies that data is the same after being passed through headers
  551. """
  552. fixture = {'is_public': True,
  553. 'deleted': False,
  554. 'name': None,
  555. 'size': 19,
  556. 'location': "file:///tmp/glance-tests/2",
  557. 'properties': {'distro': 'Ubuntu 10.04 LTS'}}
  558. headers = utils.image_meta_to_http_headers(fixture)
  559. class FakeResponse(object):
  560. pass
  561. response = FakeResponse()
  562. response.headers = headers
  563. result = utils.get_image_meta_from_headers(response)
  564. for k, v in six.iteritems(fixture):
  565. if v is not None:
  566. self.assertEqual(v, result[k])
  567. else:
  568. self.assertNotIn(k, result)
  569. class GetSocketTestCase(test_utils.BaseTestCase):
  570. def setUp(self):
  571. super(GetSocketTestCase, self).setUp()
  572. self.useFixture(fixtures.MonkeyPatch(
  573. "glance.common.wsgi.get_bind_addr",
  574. lambda x: ('192.168.0.13', 1234)))
  575. addr_info_list = [(2, 1, 6, '', ('192.168.0.13', 80)),
  576. (2, 2, 17, '', ('192.168.0.13', 80)),
  577. (2, 3, 0, '', ('192.168.0.13', 80))]
  578. self.useFixture(fixtures.MonkeyPatch(
  579. "glance.common.wsgi.socket.getaddrinfo",
  580. lambda *x: addr_info_list))
  581. self.useFixture(fixtures.MonkeyPatch(
  582. "glance.common.wsgi.time.time",
  583. mock.Mock(side_effect=[0, 1, 5, 10, 20, 35])))
  584. self.useFixture(fixtures.MonkeyPatch(
  585. "glance.common.wsgi.utils.validate_key_cert",
  586. lambda *x: None))
  587. wsgi.CONF.cert_file = '/etc/ssl/cert'
  588. wsgi.CONF.key_file = '/etc/ssl/key'
  589. wsgi.CONF.ca_file = '/etc/ssl/ca_cert'
  590. wsgi.CONF.tcp_keepidle = 600
  591. def test_correct_configure_socket(self):
  592. mock_socket = mock.Mock()
  593. self.useFixture(fixtures.MonkeyPatch(
  594. 'glance.common.wsgi.ssl.wrap_socket',
  595. mock_socket))
  596. self.useFixture(fixtures.MonkeyPatch(
  597. 'glance.common.wsgi.eventlet.listen',
  598. lambda *x, **y: mock_socket))
  599. server = wsgi.Server()
  600. server.default_port = 1234
  601. server.configure_socket()
  602. self.assertIn(mock.call.setsockopt(
  603. socket.SOL_SOCKET,
  604. socket.SO_REUSEADDR,
  605. 1), mock_socket.mock_calls)
  606. self.assertIn(mock.call.setsockopt(
  607. socket.SOL_SOCKET,
  608. socket.SO_KEEPALIVE,
  609. 1), mock_socket.mock_calls)
  610. if hasattr(socket, 'TCP_KEEPIDLE'):
  611. self.assertIn(mock.call().setsockopt(
  612. socket.IPPROTO_TCP,
  613. socket.TCP_KEEPIDLE,
  614. wsgi.CONF.tcp_keepidle), mock_socket.mock_calls)
  615. def test_get_socket_without_all_ssl_reqs(self):
  616. wsgi.CONF.key_file = None
  617. self.assertRaises(RuntimeError, wsgi.get_socket, 1234)
  618. def test_get_socket_with_bind_problems(self):
  619. self.useFixture(fixtures.MonkeyPatch(
  620. 'glance.common.wsgi.eventlet.listen',
  621. mock.Mock(side_effect=(
  622. [wsgi.socket.error(socket.errno.EADDRINUSE)] * 3 + [None]))))
  623. self.useFixture(fixtures.MonkeyPatch(
  624. 'glance.common.wsgi.ssl.wrap_socket',
  625. lambda *x, **y: None))
  626. self.assertRaises(RuntimeError, wsgi.get_socket, 1234)
  627. def test_get_socket_with_unexpected_socket_errno(self):
  628. self.useFixture(fixtures.MonkeyPatch(
  629. 'glance.common.wsgi.eventlet.listen',
  630. mock.Mock(side_effect=wsgi.socket.error(socket.errno.ENOMEM))))
  631. self.useFixture(fixtures.MonkeyPatch(
  632. 'glance.common.wsgi.ssl.wrap_socket',
  633. lambda *x, **y: None))
  634. self.assertRaises(wsgi.socket.error, wsgi.get_socket, 1234)
  635. def _cleanup_uwsgi():
  636. wsgi.uwsgi = None
  637. class Test_UwsgiChunkedFile(test_utils.BaseTestCase):
  638. def test_read_no_data(self):
  639. reader = wsgi._UWSGIChunkFile()
  640. wsgi.uwsgi = mock.MagicMock()
  641. self.addCleanup(_cleanup_uwsgi)
  642. def fake_read():
  643. return None
  644. wsgi.uwsgi.chunked_read = fake_read
  645. out = reader.read()
  646. self.assertEqual(out, b'')
  647. def test_read_data_no_length(self):
  648. reader = wsgi._UWSGIChunkFile()
  649. wsgi.uwsgi = mock.MagicMock()
  650. self.addCleanup(_cleanup_uwsgi)
  651. values = iter([b'a', b'b', b'c', None])
  652. def fake_read():
  653. return next(values)
  654. wsgi.uwsgi.chunked_read = fake_read
  655. out = reader.read()
  656. self.assertEqual(out, b'abc')
  657. def test_read_zero_length(self):
  658. reader = wsgi._UWSGIChunkFile()
  659. self.assertEqual(b'', reader.read(length=0))
  660. def test_read_data_length(self):
  661. reader = wsgi._UWSGIChunkFile()
  662. wsgi.uwsgi = mock.MagicMock()
  663. self.addCleanup(_cleanup_uwsgi)
  664. values = iter([b'a', b'b', b'c', None])
  665. def fake_read():
  666. return next(values)
  667. wsgi.uwsgi.chunked_read = fake_read
  668. out = reader.read(length=2)
  669. self.assertEqual(out, b'ab')
  670. def test_read_data_negative_length(self):
  671. reader = wsgi._UWSGIChunkFile()
  672. wsgi.uwsgi = mock.MagicMock()
  673. self.addCleanup(_cleanup_uwsgi)
  674. values = iter([b'a', b'b', b'c', None])
  675. def fake_read():
  676. return next(values)
  677. wsgi.uwsgi.chunked_read = fake_read
  678. out = reader.read(length=-2)
  679. self.assertEqual(out, b'abc')