OpenStack Identity Authentication Library
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_session.py 66KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738
  1. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  2. # not use this file except in compliance with the License. You may obtain
  3. # a copy of the License at
  4. #
  5. # http://www.apache.org/licenses/LICENSE-2.0
  6. #
  7. # Unless required by applicable law or agreed to in writing, software
  8. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  9. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  10. # License for the specific language governing permissions and limitations
  11. # under the License.
  12. import datetime
  13. import itertools
  14. import json
  15. import logging
  16. import sys
  17. import uuid
  18. import mock
  19. from oslo_utils import encodeutils
  20. import requests
  21. import requests.auth
  22. import six
  23. from testtools import matchers
  24. from keystoneauth1 import adapter
  25. from keystoneauth1 import discover
  26. from keystoneauth1 import exceptions
  27. from keystoneauth1 import plugin
  28. from keystoneauth1 import session as client_session
  29. from keystoneauth1.tests.unit import utils
  30. from keystoneauth1 import token_endpoint
  31. class RequestsAuth(requests.auth.AuthBase):
  32. def __init__(self, *args, **kwargs):
  33. super(RequestsAuth, self).__init__(*args, **kwargs)
  34. self.header_name = uuid.uuid4().hex
  35. self.header_val = uuid.uuid4().hex
  36. self.called = False
  37. def __call__(self, request):
  38. request.headers[self.header_name] = self.header_val
  39. self.called = True
  40. return request
  41. class SessionTests(utils.TestCase):
  42. TEST_URL = 'http://127.0.0.1:5000/'
  43. def test_get(self):
  44. session = client_session.Session()
  45. self.stub_url('GET', text='response')
  46. resp = session.get(self.TEST_URL)
  47. self.assertEqual('GET', self.requests_mock.last_request.method)
  48. self.assertEqual(resp.text, 'response')
  49. self.assertTrue(resp.ok)
  50. def test_post(self):
  51. session = client_session.Session()
  52. self.stub_url('POST', text='response')
  53. resp = session.post(self.TEST_URL, json={'hello': 'world'})
  54. self.assertEqual('POST', self.requests_mock.last_request.method)
  55. self.assertEqual(resp.text, 'response')
  56. self.assertTrue(resp.ok)
  57. self.assertRequestBodyIs(json={'hello': 'world'})
  58. def test_head(self):
  59. session = client_session.Session()
  60. self.stub_url('HEAD')
  61. resp = session.head(self.TEST_URL)
  62. self.assertEqual('HEAD', self.requests_mock.last_request.method)
  63. self.assertTrue(resp.ok)
  64. self.assertRequestBodyIs('')
  65. def test_put(self):
  66. session = client_session.Session()
  67. self.stub_url('PUT', text='response')
  68. resp = session.put(self.TEST_URL, json={'hello': 'world'})
  69. self.assertEqual('PUT', self.requests_mock.last_request.method)
  70. self.assertEqual(resp.text, 'response')
  71. self.assertTrue(resp.ok)
  72. self.assertRequestBodyIs(json={'hello': 'world'})
  73. def test_delete(self):
  74. session = client_session.Session()
  75. self.stub_url('DELETE', text='response')
  76. resp = session.delete(self.TEST_URL)
  77. self.assertEqual('DELETE', self.requests_mock.last_request.method)
  78. self.assertTrue(resp.ok)
  79. self.assertEqual(resp.text, 'response')
  80. def test_patch(self):
  81. session = client_session.Session()
  82. self.stub_url('PATCH', text='response')
  83. resp = session.patch(self.TEST_URL, json={'hello': 'world'})
  84. self.assertEqual('PATCH', self.requests_mock.last_request.method)
  85. self.assertTrue(resp.ok)
  86. self.assertEqual(resp.text, 'response')
  87. self.assertRequestBodyIs(json={'hello': 'world'})
  88. def test_set_microversion_headers(self):
  89. # String microversion, specified service type
  90. headers = {}
  91. client_session.Session._set_microversion_headers(
  92. headers, '2.30', 'compute', None)
  93. self.assertEqual(headers['OpenStack-API-Version'], 'compute 2.30')
  94. self.assertEqual(headers['X-OpenStack-Nova-API-Version'], '2.30')
  95. self.assertEqual(len(headers.keys()), 2)
  96. # Tuple microversion, service type via endpoint_filter
  97. headers = {}
  98. client_session.Session._set_microversion_headers(
  99. headers, (2, 30), None, {'service_type': 'compute'})
  100. self.assertEqual(headers['OpenStack-API-Version'], 'compute 2.30')
  101. self.assertEqual(headers['X-OpenStack-Nova-API-Version'], '2.30')
  102. self.assertEqual(len(headers.keys()), 2)
  103. # 'latest' (string) microversion
  104. headers = {}
  105. client_session.Session._set_microversion_headers(
  106. headers, 'latest', 'compute', None)
  107. self.assertEqual(headers['OpenStack-API-Version'], 'compute latest')
  108. self.assertEqual(headers['X-OpenStack-Nova-API-Version'], 'latest')
  109. self.assertEqual(len(headers.keys()), 2)
  110. # LATEST (tuple) microversion
  111. headers = {}
  112. client_session.Session._set_microversion_headers(
  113. headers, (discover.LATEST, discover.LATEST), 'compute', None)
  114. self.assertEqual(headers['OpenStack-API-Version'], 'compute latest')
  115. self.assertEqual(headers['X-OpenStack-Nova-API-Version'], 'latest')
  116. self.assertEqual(len(headers.keys()), 2)
  117. # ironic microversion, specified service type
  118. headers = {}
  119. client_session.Session._set_microversion_headers(
  120. headers, '2.30', 'baremetal', None)
  121. self.assertEqual(headers['OpenStack-API-Version'], 'baremetal 2.30')
  122. self.assertEqual(headers['X-OpenStack-Ironic-API-Version'], '2.30')
  123. self.assertEqual(len(headers.keys()), 2)
  124. # volumev2 service-type - volume microversion
  125. headers = {}
  126. client_session.Session._set_microversion_headers(
  127. headers, (2, 30), None, {'service_type': 'volumev2'})
  128. self.assertEqual(headers['OpenStack-API-Version'], 'volume 2.30')
  129. self.assertEqual(len(headers.keys()), 1)
  130. # block-storage service-type - volume microversion
  131. headers = {}
  132. client_session.Session._set_microversion_headers(
  133. headers, (2, 30), None, {'service_type': 'block-storage'})
  134. self.assertEqual(headers['OpenStack-API-Version'], 'volume 2.30')
  135. self.assertEqual(len(headers.keys()), 1)
  136. # Headers already exist - no change
  137. headers = {
  138. 'OpenStack-API-Version': 'compute 2.30',
  139. 'X-OpenStack-Nova-API-Version': '2.30',
  140. }
  141. client_session.Session._set_microversion_headers(
  142. headers, (2, 31), None, {'service_type': 'volume'})
  143. self.assertEqual(headers['OpenStack-API-Version'], 'compute 2.30')
  144. self.assertEqual(headers['X-OpenStack-Nova-API-Version'], '2.30')
  145. # Can't specify a 'M.latest' microversion
  146. self.assertRaises(TypeError,
  147. client_session.Session._set_microversion_headers,
  148. {}, '2.latest', 'service_type', None)
  149. self.assertRaises(TypeError,
  150. client_session.Session._set_microversion_headers,
  151. {}, (2, discover.LATEST), 'service_type', None)
  152. # Normalization error
  153. self.assertRaises(TypeError,
  154. client_session.Session._set_microversion_headers,
  155. {}, 'bogus', 'service_type', None)
  156. # No service type in param or endpoint filter
  157. self.assertRaises(TypeError,
  158. client_session.Session._set_microversion_headers,
  159. {}, (2, 30), None, None)
  160. self.assertRaises(TypeError,
  161. client_session.Session._set_microversion_headers,
  162. {}, (2, 30), None, {'no_service_type': 'here'})
  163. def test_microversion(self):
  164. # microversion not specified
  165. session = client_session.Session()
  166. self.stub_url('GET', text='response')
  167. resp = session.get(self.TEST_URL)
  168. self.assertTrue(resp.ok)
  169. self.assertRequestNotInHeader('OpenStack-API-Version')
  170. session = client_session.Session()
  171. self.stub_url('GET', text='response')
  172. resp = session.get(self.TEST_URL, microversion='2.30',
  173. microversion_service_type='compute',
  174. endpoint_filter={'endpoint': 'filter'})
  175. self.assertTrue(resp.ok)
  176. self.assertRequestHeaderEqual('OpenStack-API-Version', 'compute 2.30')
  177. self.assertRequestHeaderEqual('X-OpenStack-Nova-API-Version', '2.30')
  178. def test_user_agent(self):
  179. session = client_session.Session()
  180. self.stub_url('GET', text='response')
  181. resp = session.get(self.TEST_URL)
  182. self.assertTrue(resp.ok)
  183. self.assertRequestHeaderEqual(
  184. 'User-Agent',
  185. '%s %s' % ("run.py", client_session.DEFAULT_USER_AGENT))
  186. custom_agent = 'custom-agent/1.0'
  187. session = client_session.Session(user_agent=custom_agent)
  188. self.stub_url('GET', text='response')
  189. resp = session.get(self.TEST_URL)
  190. self.assertTrue(resp.ok)
  191. self.assertRequestHeaderEqual(
  192. 'User-Agent',
  193. '%s %s' % (custom_agent, client_session.DEFAULT_USER_AGENT))
  194. resp = session.get(self.TEST_URL, headers={'User-Agent': 'new-agent'})
  195. self.assertTrue(resp.ok)
  196. self.assertRequestHeaderEqual('User-Agent', 'new-agent')
  197. resp = session.get(self.TEST_URL, headers={'User-Agent': 'new-agent'},
  198. user_agent='overrides-agent')
  199. self.assertTrue(resp.ok)
  200. self.assertRequestHeaderEqual('User-Agent', 'overrides-agent')
  201. # If sys.argv is an empty list, then doesn't fail.
  202. with mock.patch.object(sys, 'argv', []):
  203. session = client_session.Session()
  204. resp = session.get(self.TEST_URL)
  205. self.assertTrue(resp.ok)
  206. self.assertRequestHeaderEqual(
  207. 'User-Agent',
  208. client_session.DEFAULT_USER_AGENT)
  209. # If sys.argv[0] is an empty string, then doesn't fail.
  210. with mock.patch.object(sys, 'argv', ['']):
  211. session = client_session.Session()
  212. resp = session.get(self.TEST_URL)
  213. self.assertTrue(resp.ok)
  214. self.assertRequestHeaderEqual(
  215. 'User-Agent',
  216. client_session.DEFAULT_USER_AGENT)
  217. def test_http_session_opts(self):
  218. session = client_session.Session(cert='cert.pem', timeout=5,
  219. verify='certs')
  220. FAKE_RESP = utils.TestResponse({'status_code': 200, 'text': 'resp'})
  221. RESP = mock.Mock(return_value=FAKE_RESP)
  222. with mock.patch.object(session.session, 'request', RESP) as mocked:
  223. session.post(self.TEST_URL, data='value')
  224. mock_args, mock_kwargs = mocked.call_args
  225. self.assertEqual(mock_args[0], 'POST')
  226. self.assertEqual(mock_args[1], self.TEST_URL)
  227. self.assertEqual(mock_kwargs['data'], 'value')
  228. self.assertEqual(mock_kwargs['cert'], 'cert.pem')
  229. self.assertEqual(mock_kwargs['verify'], 'certs')
  230. self.assertEqual(mock_kwargs['timeout'], 5)
  231. def test_not_found(self):
  232. session = client_session.Session()
  233. self.stub_url('GET', status_code=404)
  234. self.assertRaises(exceptions.NotFound, session.get, self.TEST_URL)
  235. def test_server_error(self):
  236. session = client_session.Session()
  237. self.stub_url('GET', status_code=500)
  238. self.assertRaises(exceptions.InternalServerError,
  239. session.get, self.TEST_URL)
  240. def test_session_debug_output(self):
  241. """Test request and response headers in debug logs.
  242. in order to redact secure headers while debug is true.
  243. """
  244. session = client_session.Session(verify=False)
  245. headers = {'HEADERA': 'HEADERVALB',
  246. 'Content-Type': 'application/json'}
  247. security_headers = {'Authorization': uuid.uuid4().hex,
  248. 'X-Auth-Token': uuid.uuid4().hex,
  249. 'X-Subject-Token': uuid.uuid4().hex,
  250. 'X-Service-Token': uuid.uuid4().hex}
  251. body = '{"a": "b"}'
  252. data = '{"c": "d"}'
  253. all_headers = dict(
  254. itertools.chain(headers.items(), security_headers.items()))
  255. self.stub_url('POST', text=body, headers=all_headers)
  256. resp = session.post(self.TEST_URL, headers=all_headers, data=data)
  257. self.assertEqual(resp.status_code, 200)
  258. self.assertIn('curl', self.logger.output)
  259. self.assertIn('POST', self.logger.output)
  260. self.assertIn('--insecure', self.logger.output)
  261. self.assertIn(body, self.logger.output)
  262. self.assertIn("'%s'" % data, self.logger.output)
  263. for k, v in headers.items():
  264. self.assertIn(k, self.logger.output)
  265. self.assertIn(v, self.logger.output)
  266. # Assert that response headers contains actual values and
  267. # only debug logs has been masked
  268. for k, v in security_headers.items():
  269. self.assertIn('%s: {SHA1}' % k, self.logger.output)
  270. self.assertEqual(v, resp.headers[k])
  271. self.assertNotIn(v, self.logger.output)
  272. def test_session_debug_output_logs_openstack_request_id(self):
  273. """Test x-openstack-request-id is logged in debug logs."""
  274. def get_response(log=True):
  275. session = client_session.Session(verify=False)
  276. endpoint_filter = {'service_name': 'Identity'}
  277. headers = {'X-OpenStack-Request-Id': 'req-1234'}
  278. body = 'BODYRESPONSE'
  279. data = 'BODYDATA'
  280. all_headers = dict(itertools.chain(headers.items()))
  281. self.stub_url('POST', text=body, headers=all_headers)
  282. resp = session.post(self.TEST_URL, endpoint_filter=endpoint_filter,
  283. headers=all_headers, data=data, log=log)
  284. return resp
  285. # if log is disabled then request-id is not logged in debug logs
  286. resp = get_response(log=False)
  287. self.assertEqual(resp.status_code, 200)
  288. expected_log = ('POST call to Identity for %s used request '
  289. 'id req-1234' % self.TEST_URL)
  290. self.assertNotIn(expected_log, self.logger.output)
  291. # if log is enabled then request-id is logged in debug logs
  292. resp = get_response()
  293. self.assertEqual(resp.status_code, 200)
  294. self.assertIn(expected_log, self.logger.output)
  295. def test_logs_failed_output(self):
  296. """Test that output is logged even for failed requests."""
  297. session = client_session.Session()
  298. body = {uuid.uuid4().hex: uuid.uuid4().hex}
  299. self.stub_url('GET', json=body, status_code=400,
  300. headers={'Content-Type': 'application/json'})
  301. resp = session.get(self.TEST_URL, raise_exc=False)
  302. self.assertEqual(resp.status_code, 400)
  303. self.assertIn(list(body.keys())[0], self.logger.output)
  304. self.assertIn(list(body.values())[0], self.logger.output)
  305. def test_logging_body_only_for_specified_content_types(self):
  306. """Verify response body is only logged in specific content types.
  307. Response bodies are logged only when the response's Content-Type header
  308. is set to application/json. This prevents us to get an unexpected
  309. MemoryError when reading arbitrary responses, such as streams.
  310. """
  311. OMITTED_BODY = ('Omitted, Content-Type is set to %s. Only '
  312. 'application/json responses have their bodies logged.')
  313. session = client_session.Session(verify=False)
  314. # Content-Type is not set
  315. body = json.dumps({'token': {'id': '...'}})
  316. self.stub_url('POST', text=body)
  317. session.post(self.TEST_URL)
  318. self.assertNotIn(body, self.logger.output)
  319. self.assertIn(OMITTED_BODY % None, self.logger.output)
  320. # Content-Type is set to text/xml
  321. body = '<token><id>...</id></token>'
  322. self.stub_url('POST', text=body, headers={'Content-Type': 'text/xml'})
  323. session.post(self.TEST_URL)
  324. self.assertNotIn(body, self.logger.output)
  325. self.assertIn(OMITTED_BODY % 'text/xml', self.logger.output)
  326. # Content-Type is set to application/json
  327. body = json.dumps({'token': {'id': '...'}})
  328. self.stub_url('POST', text=body,
  329. headers={'Content-Type': 'application/json'})
  330. session.post(self.TEST_URL)
  331. self.assertIn(body, self.logger.output)
  332. self.assertNotIn(OMITTED_BODY % 'application/json', self.logger.output)
  333. # Content-Type is set to application/json; charset=UTF-8
  334. body = json.dumps({'token': {'id': '...'}})
  335. self.stub_url(
  336. 'POST', text=body,
  337. headers={'Content-Type': 'application/json; charset=UTF-8'})
  338. session.post(self.TEST_URL)
  339. self.assertIn(body, self.logger.output)
  340. self.assertNotIn(OMITTED_BODY % 'application/json; charset=UTF-8',
  341. self.logger.output)
  342. def test_logging_cacerts(self):
  343. path_to_certs = '/path/to/certs'
  344. session = client_session.Session(verify=path_to_certs)
  345. self.stub_url('GET', text='text')
  346. session.get(self.TEST_URL)
  347. self.assertIn('--cacert', self.logger.output)
  348. self.assertIn(path_to_certs, self.logger.output)
  349. def test_connect_retries(self):
  350. self.stub_url('GET', exc=requests.exceptions.Timeout())
  351. session = client_session.Session()
  352. retries = 3
  353. with mock.patch('time.sleep') as m:
  354. self.assertRaises(exceptions.ConnectTimeout,
  355. session.get,
  356. self.TEST_URL, connect_retries=retries)
  357. self.assertEqual(retries, m.call_count)
  358. # 3 retries finishing with 2.0 means 0.5, 1.0 and 2.0
  359. m.assert_called_with(2.0)
  360. # we count retries so there will be one initial request + 3 retries
  361. self.assertThat(self.requests_mock.request_history,
  362. matchers.HasLength(retries + 1))
  363. def test_http_503_retries(self):
  364. self.stub_url('GET', status_code=503)
  365. session = client_session.Session()
  366. retries = 3
  367. with mock.patch('time.sleep') as m:
  368. self.assertRaises(exceptions.ServiceUnavailable,
  369. session.get,
  370. self.TEST_URL, status_code_retries=retries)
  371. self.assertEqual(retries, m.call_count)
  372. # 3 retries finishing with 2.0 means 0.5, 1.0 and 2.0
  373. m.assert_called_with(2.0)
  374. # we count retries so there will be one initial request + 3 retries
  375. self.assertThat(self.requests_mock.request_history,
  376. matchers.HasLength(retries + 1))
  377. def test_http_status_retries(self):
  378. self.stub_url('GET', status_code=409)
  379. session = client_session.Session()
  380. retries = 3
  381. with mock.patch('time.sleep') as m:
  382. self.assertRaises(exceptions.Conflict,
  383. session.get,
  384. self.TEST_URL, status_code_retries=retries,
  385. retriable_status_codes=[503, 409])
  386. self.assertEqual(retries, m.call_count)
  387. # 3 retries finishing with 2.0 means 0.5, 1.0 and 2.0
  388. m.assert_called_with(2.0)
  389. # we count retries so there will be one initial request + 3 retries
  390. self.assertThat(self.requests_mock.request_history,
  391. matchers.HasLength(retries + 1))
  392. def test_http_status_retries_another_code(self):
  393. self.stub_url('GET', status_code=404)
  394. session = client_session.Session()
  395. retries = 3
  396. with mock.patch('time.sleep') as m:
  397. self.assertRaises(exceptions.NotFound,
  398. session.get,
  399. self.TEST_URL, status_code_retries=retries,
  400. retriable_status_codes=[503, 409])
  401. self.assertFalse(m.called)
  402. self.assertThat(self.requests_mock.request_history,
  403. matchers.HasLength(1))
  404. def test_uses_tcp_keepalive_by_default(self):
  405. session = client_session.Session()
  406. requests_session = session.session
  407. self.assertIsInstance(requests_session.adapters['http://'],
  408. client_session.TCPKeepAliveAdapter)
  409. self.assertIsInstance(requests_session.adapters['https://'],
  410. client_session.TCPKeepAliveAdapter)
  411. def test_does_not_set_tcp_keepalive_on_custom_sessions(self):
  412. mock_session = mock.Mock()
  413. client_session.Session(session=mock_session)
  414. self.assertFalse(mock_session.mount.called)
  415. def test_ssl_error_message(self):
  416. error = uuid.uuid4().hex
  417. self.stub_url('GET', exc=requests.exceptions.SSLError(error))
  418. session = client_session.Session()
  419. # The exception should contain the URL and details about the SSL error
  420. msg = 'SSL exception connecting to %(url)s: %(error)s' % {
  421. 'url': self.TEST_URL, 'error': error}
  422. self.assertRaisesRegex(exceptions.SSLError,
  423. msg,
  424. session.get,
  425. self.TEST_URL)
  426. def test_json_content_type(self):
  427. session = client_session.Session()
  428. self.stub_url('POST', text='response')
  429. resp = session.post(
  430. self.TEST_URL,
  431. json=[{'op': 'replace',
  432. 'path': '/name',
  433. 'value': 'new_name'}],
  434. headers={'Content-Type': 'application/json-patch+json'})
  435. self.assertEqual('POST', self.requests_mock.last_request.method)
  436. self.assertEqual(resp.text, 'response')
  437. self.assertTrue(resp.ok)
  438. self.assertRequestBodyIs(
  439. json=[{'op': 'replace',
  440. 'path': '/name',
  441. 'value': 'new_name'}])
  442. self.assertContentTypeIs('application/json-patch+json')
  443. class RedirectTests(utils.TestCase):
  444. REDIRECT_CHAIN = ['http://myhost:3445/',
  445. 'http://anotherhost:6555/',
  446. 'http://thirdhost/',
  447. 'http://finaldestination:55/']
  448. DEFAULT_REDIRECT_BODY = 'Redirect'
  449. DEFAULT_RESP_BODY = 'Found'
  450. def setup_redirects(self, method='GET', status_code=305,
  451. redirect_kwargs={}, final_kwargs={}):
  452. redirect_kwargs.setdefault('text', self.DEFAULT_REDIRECT_BODY)
  453. for s, d in zip(self.REDIRECT_CHAIN, self.REDIRECT_CHAIN[1:]):
  454. self.requests_mock.register_uri(method, s, status_code=status_code,
  455. headers={'Location': d},
  456. **redirect_kwargs)
  457. final_kwargs.setdefault('status_code', 200)
  458. final_kwargs.setdefault('text', self.DEFAULT_RESP_BODY)
  459. self.requests_mock.register_uri(method, self.REDIRECT_CHAIN[-1],
  460. **final_kwargs)
  461. def assertResponse(self, resp):
  462. self.assertEqual(resp.status_code, 200)
  463. self.assertEqual(resp.text, self.DEFAULT_RESP_BODY)
  464. def test_basic_get(self):
  465. session = client_session.Session()
  466. self.setup_redirects()
  467. resp = session.get(self.REDIRECT_CHAIN[-2])
  468. self.assertResponse(resp)
  469. def test_basic_post_keeps_correct_method(self):
  470. session = client_session.Session()
  471. self.setup_redirects(method='POST', status_code=301)
  472. resp = session.post(self.REDIRECT_CHAIN[-2])
  473. self.assertResponse(resp)
  474. def test_redirect_forever(self):
  475. session = client_session.Session(redirect=True)
  476. self.setup_redirects()
  477. resp = session.get(self.REDIRECT_CHAIN[0])
  478. self.assertResponse(resp)
  479. self.assertTrue(len(resp.history), len(self.REDIRECT_CHAIN))
  480. def test_no_redirect(self):
  481. session = client_session.Session(redirect=False)
  482. self.setup_redirects()
  483. resp = session.get(self.REDIRECT_CHAIN[0])
  484. self.assertEqual(resp.status_code, 305)
  485. self.assertEqual(resp.url, self.REDIRECT_CHAIN[0])
  486. def test_redirect_limit(self):
  487. self.setup_redirects()
  488. for i in (1, 2):
  489. session = client_session.Session(redirect=i)
  490. resp = session.get(self.REDIRECT_CHAIN[0])
  491. self.assertEqual(resp.status_code, 305)
  492. self.assertEqual(resp.url, self.REDIRECT_CHAIN[i])
  493. self.assertEqual(resp.text, self.DEFAULT_REDIRECT_BODY)
  494. def test_history_matches_requests(self):
  495. self.setup_redirects(status_code=301)
  496. session = client_session.Session(redirect=True)
  497. req_resp = requests.get(self.REDIRECT_CHAIN[0],
  498. allow_redirects=True)
  499. ses_resp = session.get(self.REDIRECT_CHAIN[0])
  500. self.assertEqual(len(req_resp.history), len(ses_resp.history))
  501. for r, s in zip(req_resp.history, ses_resp.history):
  502. self.assertEqual(r.url, s.url)
  503. self.assertEqual(r.status_code, s.status_code)
  504. def test_permanent_redirect_308(self):
  505. session = client_session.Session()
  506. self.setup_redirects(status_code=308)
  507. resp = session.get(self.REDIRECT_CHAIN[-2])
  508. self.assertResponse(resp)
  509. class AuthPlugin(plugin.BaseAuthPlugin):
  510. """Very simple debug authentication plugin.
  511. Takes Parameters such that it can throw exceptions at the right times.
  512. """
  513. TEST_TOKEN = utils.TestCase.TEST_TOKEN
  514. TEST_USER_ID = 'aUser'
  515. TEST_PROJECT_ID = 'aProject'
  516. SERVICE_URLS = {
  517. 'identity': {'public': 'http://identity-public:1111/v2.0',
  518. 'admin': 'http://identity-admin:1111/v2.0'},
  519. 'compute': {'public': 'http://compute-public:2222/v1.0',
  520. 'admin': 'http://compute-admin:2222/v1.0'},
  521. 'image': {'public': 'http://image-public:3333/v2.0',
  522. 'admin': 'http://image-admin:3333/v2.0'}
  523. }
  524. def __init__(self, token=TEST_TOKEN, invalidate=True):
  525. self.token = token
  526. self._invalidate = invalidate
  527. def get_token(self, session):
  528. return self.token
  529. def get_endpoint(self, session, service_type=None, interface=None,
  530. **kwargs):
  531. try:
  532. return self.SERVICE_URLS[service_type][interface]
  533. except (KeyError, AttributeError):
  534. return None
  535. def invalidate(self):
  536. return self._invalidate
  537. def get_user_id(self, session):
  538. return self.TEST_USER_ID
  539. def get_project_id(self, session):
  540. return self.TEST_PROJECT_ID
  541. class CalledAuthPlugin(plugin.BaseAuthPlugin):
  542. ENDPOINT = 'http://fakeendpoint/'
  543. TOKEN = utils.TestCase.TEST_TOKEN
  544. USER_ID = uuid.uuid4().hex
  545. PROJECT_ID = uuid.uuid4().hex
  546. def __init__(self, invalidate=True):
  547. self.get_token_called = False
  548. self.get_endpoint_called = False
  549. self.endpoint_arguments = {}
  550. self.invalidate_called = False
  551. self.get_project_id_called = False
  552. self.get_user_id_called = False
  553. self._invalidate = invalidate
  554. def get_token(self, session):
  555. self.get_token_called = True
  556. return self.TOKEN
  557. def get_endpoint(self, session, **kwargs):
  558. self.get_endpoint_called = True
  559. self.endpoint_arguments = kwargs
  560. return self.ENDPOINT
  561. def invalidate(self):
  562. self.invalidate_called = True
  563. return self._invalidate
  564. def get_project_id(self, session, **kwargs):
  565. self.get_project_id_called = True
  566. return self.PROJECT_ID
  567. def get_user_id(self, session, **kwargs):
  568. self.get_user_id_called = True
  569. return self.USER_ID
  570. class SessionAuthTests(utils.TestCase):
  571. TEST_URL = 'http://127.0.0.1:5000/'
  572. TEST_JSON = {'hello': 'world'}
  573. def stub_service_url(self, service_type, interface, path,
  574. method='GET', **kwargs):
  575. base_url = AuthPlugin.SERVICE_URLS[service_type][interface]
  576. uri = "%s/%s" % (base_url.rstrip('/'), path.lstrip('/'))
  577. self.requests_mock.register_uri(method, uri, **kwargs)
  578. def test_auth_plugin_default_with_plugin(self):
  579. self.stub_url('GET', base_url=self.TEST_URL, json=self.TEST_JSON)
  580. # if there is an auth_plugin then it should default to authenticated
  581. auth = AuthPlugin()
  582. sess = client_session.Session(auth=auth)
  583. resp = sess.get(self.TEST_URL)
  584. self.assertEqual(resp.json(), self.TEST_JSON)
  585. self.assertRequestHeaderEqual('X-Auth-Token', AuthPlugin.TEST_TOKEN)
  586. def test_auth_plugin_disable(self):
  587. self.stub_url('GET', base_url=self.TEST_URL, json=self.TEST_JSON)
  588. auth = AuthPlugin()
  589. sess = client_session.Session(auth=auth)
  590. resp = sess.get(self.TEST_URL, authenticated=False)
  591. self.assertEqual(resp.json(), self.TEST_JSON)
  592. self.assertRequestHeaderEqual('X-Auth-Token', None)
  593. def test_service_type_urls(self):
  594. service_type = 'compute'
  595. interface = 'public'
  596. path = '/instances'
  597. status = 200
  598. body = 'SUCCESS'
  599. self.stub_service_url(service_type=service_type,
  600. interface=interface,
  601. path=path,
  602. status_code=status,
  603. text=body)
  604. sess = client_session.Session(auth=AuthPlugin())
  605. resp = sess.get(path,
  606. endpoint_filter={'service_type': service_type,
  607. 'interface': interface})
  608. self.assertEqual(self.requests_mock.last_request.url,
  609. AuthPlugin.SERVICE_URLS['compute']['public'] + path)
  610. self.assertEqual(resp.text, body)
  611. self.assertEqual(resp.status_code, status)
  612. def test_service_url_raises_if_no_auth_plugin(self):
  613. sess = client_session.Session()
  614. self.assertRaises(exceptions.MissingAuthPlugin,
  615. sess.get, '/path',
  616. endpoint_filter={'service_type': 'compute',
  617. 'interface': 'public'})
  618. def test_service_url_raises_if_no_url_returned(self):
  619. sess = client_session.Session(auth=AuthPlugin())
  620. self.assertRaises(exceptions.EndpointNotFound,
  621. sess.get, '/path',
  622. endpoint_filter={'service_type': 'unknown',
  623. 'interface': 'public'})
  624. def test_raises_exc_only_when_asked(self):
  625. # A request that returns a HTTP error should by default raise an
  626. # exception by default, if you specify raise_exc=False then it will not
  627. self.requests_mock.get(self.TEST_URL, status_code=401)
  628. sess = client_session.Session()
  629. self.assertRaises(exceptions.Unauthorized, sess.get, self.TEST_URL)
  630. resp = sess.get(self.TEST_URL, raise_exc=False)
  631. self.assertEqual(401, resp.status_code)
  632. def test_passed_auth_plugin(self):
  633. passed = CalledAuthPlugin()
  634. sess = client_session.Session()
  635. self.requests_mock.get(CalledAuthPlugin.ENDPOINT + 'path',
  636. status_code=200)
  637. endpoint_filter = {'service_type': 'identity'}
  638. # no plugin with authenticated won't work
  639. self.assertRaises(exceptions.MissingAuthPlugin, sess.get, 'path',
  640. authenticated=True)
  641. # no plugin with an endpoint filter won't work
  642. self.assertRaises(exceptions.MissingAuthPlugin, sess.get, 'path',
  643. authenticated=False, endpoint_filter=endpoint_filter)
  644. resp = sess.get('path', auth=passed, endpoint_filter=endpoint_filter)
  645. self.assertEqual(200, resp.status_code)
  646. self.assertTrue(passed.get_endpoint_called)
  647. self.assertTrue(passed.get_token_called)
  648. def test_passed_auth_plugin_overrides(self):
  649. fixed = CalledAuthPlugin()
  650. passed = CalledAuthPlugin()
  651. sess = client_session.Session(fixed)
  652. self.requests_mock.get(CalledAuthPlugin.ENDPOINT + 'path',
  653. status_code=200)
  654. resp = sess.get('path', auth=passed,
  655. endpoint_filter={'service_type': 'identity'})
  656. self.assertEqual(200, resp.status_code)
  657. self.assertTrue(passed.get_endpoint_called)
  658. self.assertTrue(passed.get_token_called)
  659. self.assertFalse(fixed.get_endpoint_called)
  660. self.assertFalse(fixed.get_token_called)
  661. def test_requests_auth_plugin(self):
  662. sess = client_session.Session()
  663. requests_auth = RequestsAuth()
  664. self.requests_mock.get(self.TEST_URL, text='resp')
  665. sess.get(self.TEST_URL, requests_auth=requests_auth)
  666. last = self.requests_mock.last_request
  667. self.assertEqual(requests_auth.header_val,
  668. last.headers[requests_auth.header_name])
  669. self.assertTrue(requests_auth.called)
  670. def test_reauth_called(self):
  671. auth = CalledAuthPlugin(invalidate=True)
  672. sess = client_session.Session(auth=auth)
  673. self.requests_mock.get(self.TEST_URL,
  674. [{'text': 'Failed', 'status_code': 401},
  675. {'text': 'Hello', 'status_code': 200}])
  676. # allow_reauth=True is the default
  677. resp = sess.get(self.TEST_URL, authenticated=True)
  678. self.assertEqual(200, resp.status_code)
  679. self.assertEqual('Hello', resp.text)
  680. self.assertTrue(auth.invalidate_called)
  681. def test_reauth_not_called(self):
  682. auth = CalledAuthPlugin(invalidate=True)
  683. sess = client_session.Session(auth=auth)
  684. self.requests_mock.get(self.TEST_URL,
  685. [{'text': 'Failed', 'status_code': 401},
  686. {'text': 'Hello', 'status_code': 200}])
  687. self.assertRaises(exceptions.Unauthorized, sess.get, self.TEST_URL,
  688. authenticated=True, allow_reauth=False)
  689. self.assertFalse(auth.invalidate_called)
  690. def test_endpoint_override_overrides_filter(self):
  691. auth = CalledAuthPlugin()
  692. sess = client_session.Session(auth=auth)
  693. override_base = 'http://mytest/'
  694. path = 'path'
  695. override_url = override_base + path
  696. resp_text = uuid.uuid4().hex
  697. self.requests_mock.get(override_url, text=resp_text)
  698. resp = sess.get(path,
  699. endpoint_override=override_base,
  700. endpoint_filter={'service_type': 'identity'})
  701. self.assertEqual(resp_text, resp.text)
  702. self.assertEqual(override_url, self.requests_mock.last_request.url)
  703. self.assertTrue(auth.get_token_called)
  704. self.assertFalse(auth.get_endpoint_called)
  705. self.assertFalse(auth.get_user_id_called)
  706. self.assertFalse(auth.get_project_id_called)
  707. def test_endpoint_override_ignore_full_url(self):
  708. auth = CalledAuthPlugin()
  709. sess = client_session.Session(auth=auth)
  710. path = 'path'
  711. url = self.TEST_URL + path
  712. resp_text = uuid.uuid4().hex
  713. self.requests_mock.get(url, text=resp_text)
  714. resp = sess.get(url,
  715. endpoint_override='http://someother.url',
  716. endpoint_filter={'service_type': 'identity'})
  717. self.assertEqual(resp_text, resp.text)
  718. self.assertEqual(url, self.requests_mock.last_request.url)
  719. self.assertTrue(auth.get_token_called)
  720. self.assertFalse(auth.get_endpoint_called)
  721. self.assertFalse(auth.get_user_id_called)
  722. self.assertFalse(auth.get_project_id_called)
  723. def test_endpoint_override_does_id_replacement(self):
  724. auth = CalledAuthPlugin()
  725. sess = client_session.Session(auth=auth)
  726. override_base = 'http://mytest/%(project_id)s/%(user_id)s'
  727. path = 'path'
  728. replacements = {'user_id': CalledAuthPlugin.USER_ID,
  729. 'project_id': CalledAuthPlugin.PROJECT_ID}
  730. override_url = override_base % replacements + '/' + path
  731. resp_text = uuid.uuid4().hex
  732. self.requests_mock.get(override_url, text=resp_text)
  733. resp = sess.get(path,
  734. endpoint_override=override_base,
  735. endpoint_filter={'service_type': 'identity'})
  736. self.assertEqual(resp_text, resp.text)
  737. self.assertEqual(override_url, self.requests_mock.last_request.url)
  738. self.assertTrue(auth.get_token_called)
  739. self.assertTrue(auth.get_user_id_called)
  740. self.assertTrue(auth.get_project_id_called)
  741. self.assertFalse(auth.get_endpoint_called)
  742. def test_endpoint_override_fails_to_replace_if_none(self):
  743. # The token_endpoint plugin doesn't know user_id or project_id
  744. auth = token_endpoint.Token(uuid.uuid4().hex, uuid.uuid4().hex)
  745. sess = client_session.Session(auth=auth)
  746. override_base = 'http://mytest/%(project_id)s'
  747. e = self.assertRaises(ValueError,
  748. sess.get,
  749. '/path',
  750. endpoint_override=override_base,
  751. endpoint_filter={'service_type': 'identity'})
  752. self.assertIn('project_id', str(e))
  753. override_base = 'http://mytest/%(user_id)s'
  754. e = self.assertRaises(ValueError,
  755. sess.get,
  756. '/path',
  757. endpoint_override=override_base,
  758. endpoint_filter={'service_type': 'identity'})
  759. self.assertIn('user_id', str(e))
  760. def test_endpoint_override_fails_to_do_unknown_replacement(self):
  761. auth = CalledAuthPlugin()
  762. sess = client_session.Session(auth=auth)
  763. override_base = 'http://mytest/%(unknown_id)s'
  764. e = self.assertRaises(AttributeError,
  765. sess.get,
  766. '/path',
  767. endpoint_override=override_base,
  768. endpoint_filter={'service_type': 'identity'})
  769. self.assertIn('unknown_id', str(e))
  770. def test_user_and_project_id(self):
  771. auth = AuthPlugin()
  772. sess = client_session.Session(auth=auth)
  773. self.assertEqual(auth.TEST_USER_ID, sess.get_user_id())
  774. self.assertEqual(auth.TEST_PROJECT_ID, sess.get_project_id())
  775. def test_logger_object_passed(self):
  776. logger = logging.getLogger(uuid.uuid4().hex)
  777. logger.setLevel(logging.DEBUG)
  778. logger.propagate = False
  779. io = six.StringIO()
  780. handler = logging.StreamHandler(io)
  781. logger.addHandler(handler)
  782. auth = AuthPlugin()
  783. sess = client_session.Session(auth=auth)
  784. response = {uuid.uuid4().hex: uuid.uuid4().hex}
  785. self.stub_url('GET',
  786. json=response,
  787. headers={'Content-Type': 'application/json'})
  788. resp = sess.get(self.TEST_URL, logger=logger)
  789. self.assertEqual(response, resp.json())
  790. output = io.getvalue()
  791. self.assertIn(self.TEST_URL, output)
  792. self.assertIn(list(response.keys())[0], output)
  793. self.assertIn(list(response.values())[0], output)
  794. self.assertNotIn(self.TEST_URL, self.logger.output)
  795. self.assertNotIn(list(response.keys())[0], self.logger.output)
  796. self.assertNotIn(list(response.values())[0], self.logger.output)
  797. def test_split_loggers(self):
  798. def get_logger_io(name):
  799. logger_name = 'keystoneauth.session.{name}'.format(name=name)
  800. logger = logging.getLogger(logger_name)
  801. logger.setLevel(logging.DEBUG)
  802. io = six.StringIO()
  803. handler = logging.StreamHandler(io)
  804. logger.addHandler(handler)
  805. return io
  806. io = {}
  807. for name in ('request', 'body', 'response', 'request-id'):
  808. io[name] = get_logger_io(name)
  809. auth = AuthPlugin()
  810. sess = client_session.Session(auth=auth, split_loggers=True)
  811. response_key = uuid.uuid4().hex
  812. response_val = uuid.uuid4().hex
  813. response = {response_key: response_val}
  814. request_id = uuid.uuid4().hex
  815. self.stub_url(
  816. 'GET',
  817. json=response,
  818. headers={
  819. 'Content-Type': 'application/json',
  820. 'X-OpenStack-Request-ID': request_id,
  821. })
  822. resp = sess.get(
  823. self.TEST_URL,
  824. headers={
  825. encodeutils.safe_encode('x-bytes-header'):
  826. encodeutils.safe_encode('bytes-value')
  827. })
  828. self.assertEqual(response, resp.json())
  829. request_output = io['request'].getvalue().strip()
  830. response_output = io['response'].getvalue().strip()
  831. body_output = io['body'].getvalue().strip()
  832. id_output = io['request-id'].getvalue().strip()
  833. self.assertIn('curl -g -i -X GET {url}'.format(url=self.TEST_URL),
  834. request_output)
  835. self.assertIn('-H "x-bytes-header: bytes-value"', request_output)
  836. self.assertEqual('[200] Content-Type: application/json '
  837. 'X-OpenStack-Request-ID: '
  838. '{id}'.format(id=request_id), response_output)
  839. self.assertEqual(
  840. 'GET call to {url} used request id {id}'.format(
  841. url=self.TEST_URL, id=request_id),
  842. id_output)
  843. self.assertEqual(
  844. '{{"{key}": "{val}"}}'.format(
  845. key=response_key, val=response_val),
  846. body_output)
  847. def test_collect_timing(self):
  848. auth = AuthPlugin()
  849. sess = client_session.Session(auth=auth, collect_timing=True)
  850. response = {uuid.uuid4().hex: uuid.uuid4().hex}
  851. self.stub_url('GET',
  852. json=response,
  853. headers={'Content-Type': 'application/json'})
  854. resp = sess.get(self.TEST_URL)
  855. self.assertEqual(response, resp.json())
  856. timings = sess.get_timings()
  857. self.assertEqual(timings[0].method, 'GET')
  858. self.assertEqual(timings[0].url, self.TEST_URL)
  859. self.assertIsInstance(timings[0].elapsed, datetime.timedelta)
  860. sess.reset_timings()
  861. timings = sess.get_timings()
  862. self.assertEqual(len(timings), 0)
  863. class AdapterTest(utils.TestCase):
  864. SERVICE_TYPE = uuid.uuid4().hex
  865. SERVICE_NAME = uuid.uuid4().hex
  866. INTERFACE = uuid.uuid4().hex
  867. REGION_NAME = uuid.uuid4().hex
  868. USER_AGENT = uuid.uuid4().hex
  869. VERSION = uuid.uuid4().hex
  870. ALLOW = {'allow_deprecated': False,
  871. 'allow_experimental': True,
  872. 'allow_unknown': True}
  873. TEST_URL = CalledAuthPlugin.ENDPOINT
  874. def _create_loaded_adapter(self, sess=None, auth=None):
  875. return adapter.Adapter(sess or client_session.Session(),
  876. auth=auth or CalledAuthPlugin(),
  877. service_type=self.SERVICE_TYPE,
  878. service_name=self.SERVICE_NAME,
  879. interface=self.INTERFACE,
  880. region_name=self.REGION_NAME,
  881. user_agent=self.USER_AGENT,
  882. version=self.VERSION,
  883. allow=self.ALLOW)
  884. def _verify_endpoint_called(self, adpt):
  885. self.assertEqual(self.SERVICE_TYPE,
  886. adpt.auth.endpoint_arguments['service_type'])
  887. self.assertEqual(self.SERVICE_NAME,
  888. adpt.auth.endpoint_arguments['service_name'])
  889. self.assertEqual(self.INTERFACE,
  890. adpt.auth.endpoint_arguments['interface'])
  891. self.assertEqual(self.REGION_NAME,
  892. adpt.auth.endpoint_arguments['region_name'])
  893. self.assertEqual(self.VERSION,
  894. adpt.auth.endpoint_arguments['version'])
  895. def test_setting_variables_on_request(self):
  896. response = uuid.uuid4().hex
  897. self.stub_url('GET', text=response)
  898. adpt = self._create_loaded_adapter()
  899. resp = adpt.get('/')
  900. self.assertEqual(resp.text, response)
  901. self._verify_endpoint_called(adpt)
  902. self.assertEqual(self.ALLOW,
  903. adpt.auth.endpoint_arguments['allow'])
  904. self.assertTrue(adpt.auth.get_token_called)
  905. self.assertRequestHeaderEqual('User-Agent', self.USER_AGENT)
  906. def test_setting_global_id_on_request(self):
  907. global_id = "req-%s" % uuid.uuid4()
  908. response = uuid.uuid4().hex
  909. self.stub_url('GET', text=response)
  910. adpt = adapter.Adapter(client_session.Session(),
  911. auth=CalledAuthPlugin(),
  912. service_type=self.SERVICE_TYPE,
  913. service_name=self.SERVICE_NAME,
  914. interface=self.INTERFACE,
  915. region_name=self.REGION_NAME,
  916. user_agent=self.USER_AGENT,
  917. version=self.VERSION,
  918. allow=self.ALLOW,
  919. global_request_id=global_id)
  920. resp = adpt.get('/')
  921. self.assertEqual(resp.text, response)
  922. self._verify_endpoint_called(adpt)
  923. self.assertEqual(self.ALLOW,
  924. adpt.auth.endpoint_arguments['allow'])
  925. self.assertTrue(adpt.auth.get_token_called)
  926. self.assertRequestHeaderEqual('X-OpenStack-Request-ID', global_id)
  927. def test_setting_variables_on_get_endpoint(self):
  928. adpt = self._create_loaded_adapter()
  929. url = adpt.get_endpoint()
  930. self.assertEqual(self.TEST_URL, url)
  931. self._verify_endpoint_called(adpt)
  932. def test_legacy_binding(self):
  933. key = uuid.uuid4().hex
  934. val = uuid.uuid4().hex
  935. response = json.dumps({key: val})
  936. self.stub_url('GET', text=response)
  937. auth = CalledAuthPlugin()
  938. sess = client_session.Session(auth=auth)
  939. adpt = adapter.LegacyJsonAdapter(sess,
  940. service_type=self.SERVICE_TYPE,
  941. user_agent=self.USER_AGENT)
  942. resp, body = adpt.get('/')
  943. self.assertEqual(self.SERVICE_TYPE,
  944. auth.endpoint_arguments['service_type'])
  945. self.assertEqual(resp.text, response)
  946. self.assertEqual(val, body[key])
  947. def test_legacy_binding_non_json_resp(self):
  948. response = uuid.uuid4().hex
  949. self.stub_url('GET', text=response,
  950. headers={'Content-Type': 'text/html'})
  951. auth = CalledAuthPlugin()
  952. sess = client_session.Session(auth=auth)
  953. adpt = adapter.LegacyJsonAdapter(sess,
  954. service_type=self.SERVICE_TYPE,
  955. user_agent=self.USER_AGENT)
  956. resp, body = adpt.get('/')
  957. self.assertEqual(self.SERVICE_TYPE,
  958. auth.endpoint_arguments['service_type'])
  959. self.assertEqual(resp.text, response)
  960. self.assertIsNone(body)
  961. def test_methods(self):
  962. sess = client_session.Session()
  963. adpt = adapter.Adapter(sess)
  964. url = 'http://url'
  965. for method in ['get', 'head', 'post', 'put', 'patch', 'delete']:
  966. with mock.patch.object(adpt, 'request') as m:
  967. getattr(adpt, method)(url)
  968. m.assert_called_once_with(url, method.upper())
  969. def test_setting_endpoint_override(self):
  970. endpoint_override = 'http://overrideurl'
  971. path = '/path'
  972. endpoint_url = endpoint_override + path
  973. auth = CalledAuthPlugin()
  974. sess = client_session.Session(auth=auth)
  975. adpt = adapter.Adapter(sess, endpoint_override=endpoint_override)
  976. response = uuid.uuid4().hex
  977. self.requests_mock.get(endpoint_url, text=response)
  978. resp = adpt.get(path)
  979. self.assertEqual(response, resp.text)
  980. self.assertEqual(endpoint_url, self.requests_mock.last_request.url)
  981. self.assertEqual(endpoint_override, adpt.get_endpoint())
  982. def test_adapter_invalidate(self):
  983. auth = CalledAuthPlugin()
  984. sess = client_session.Session()
  985. adpt = adapter.Adapter(sess, auth=auth)
  986. adpt.invalidate()
  987. self.assertTrue(auth.invalidate_called)
  988. def test_adapter_get_token(self):
  989. auth = CalledAuthPlugin()
  990. sess = client_session.Session()
  991. adpt = adapter.Adapter(sess, auth=auth)
  992. self.assertEqual(self.TEST_TOKEN, adpt.get_token())
  993. self.assertTrue(auth.get_token_called)
  994. def test_adapter_connect_retries(self):
  995. retries = 2
  996. sess = client_session.Session()
  997. adpt = adapter.Adapter(sess, connect_retries=retries)
  998. self.stub_url('GET', exc=requests.exceptions.ConnectionError())
  999. with mock.patch('time.sleep') as m:
  1000. self.assertRaises(exceptions.ConnectionError,
  1001. adpt.get, self.TEST_URL)
  1002. self.assertEqual(retries, m.call_count)
  1003. # we count retries so there will be one initial request + 2 retries
  1004. self.assertThat(self.requests_mock.request_history,
  1005. matchers.HasLength(retries + 1))
  1006. def test_adapter_http_503_retries(self):
  1007. retries = 2
  1008. sess = client_session.Session()
  1009. adpt = adapter.Adapter(sess, status_code_retries=retries)
  1010. self.stub_url('GET', status_code=503)
  1011. with mock.patch('time.sleep') as m:
  1012. self.assertRaises(exceptions.ServiceUnavailable,
  1013. adpt.get, self.TEST_URL)
  1014. self.assertEqual(retries, m.call_count)
  1015. # we count retries so there will be one initial request + 2 retries
  1016. self.assertThat(self.requests_mock.request_history,
  1017. matchers.HasLength(retries + 1))
  1018. def test_adapter_http_status_retries(self):
  1019. retries = 2
  1020. sess = client_session.Session()
  1021. adpt = adapter.Adapter(sess, status_code_retries=retries,
  1022. retriable_status_codes=[503, 409])
  1023. self.stub_url('GET', status_code=409)
  1024. with mock.patch('time.sleep') as m:
  1025. self.assertRaises(exceptions.Conflict,
  1026. adpt.get, self.TEST_URL)
  1027. self.assertEqual(retries, m.call_count)
  1028. # we count retries so there will be one initial request + 2 retries
  1029. self.assertThat(self.requests_mock.request_history,
  1030. matchers.HasLength(retries + 1))
  1031. def test_user_and_project_id(self):
  1032. auth = AuthPlugin()
  1033. sess = client_session.Session()
  1034. adpt = adapter.Adapter(sess, auth=auth)
  1035. self.assertEqual(auth.TEST_USER_ID, adpt.get_user_id())
  1036. self.assertEqual(auth.TEST_PROJECT_ID, adpt.get_project_id())
  1037. def test_logger_object_passed(self):
  1038. logger = logging.getLogger(uuid.uuid4().hex)
  1039. logger.setLevel(logging.DEBUG)
  1040. logger.propagate = False
  1041. io = six.StringIO()
  1042. handler = logging.StreamHandler(io)
  1043. logger.addHandler(handler)
  1044. auth = AuthPlugin()
  1045. sess = client_session.Session(auth=auth)
  1046. adpt = adapter.Adapter(sess, auth=auth, logger=logger)
  1047. response = {uuid.uuid4().hex: uuid.uuid4().hex}
  1048. self.stub_url('GET', json=response,
  1049. headers={'Content-Type': 'application/json'})
  1050. resp = adpt.get(self.TEST_URL, logger=logger)
  1051. self.assertEqual(response, resp.json())
  1052. output = io.getvalue()
  1053. self.assertIn(self.TEST_URL, output)
  1054. self.assertIn(list(response.keys())[0], output)
  1055. self.assertIn(list(response.values())[0], output)
  1056. self.assertNotIn(self.TEST_URL, self.logger.output)
  1057. self.assertNotIn(list(response.keys())[0], self.logger.output)
  1058. self.assertNotIn(list(response.values())[0], self.logger.output)
  1059. def test_unknown_connection_error(self):
  1060. self.stub_url('GET', exc=requests.exceptions.RequestException)
  1061. self.assertRaises(exceptions.UnknownConnectionError,
  1062. client_session.Session().request,
  1063. self.TEST_URL,
  1064. 'GET')
  1065. def test_additional_headers(self):
  1066. session_key = uuid.uuid4().hex
  1067. session_val = uuid.uuid4().hex
  1068. adapter_key = uuid.uuid4().hex
  1069. adapter_val = uuid.uuid4().hex
  1070. request_key = uuid.uuid4().hex
  1071. request_val = uuid.uuid4().hex
  1072. text = uuid.uuid4().hex
  1073. url = 'http://keystone.test.com'
  1074. self.requests_mock.get(url, text=text)
  1075. sess = client_session.Session(
  1076. additional_headers={session_key: session_val})
  1077. adap = adapter.Adapter(session=sess,
  1078. additional_headers={adapter_key: adapter_val})
  1079. resp = adap.get(url, headers={request_key: request_val})
  1080. request = self.requests_mock.last_request
  1081. self.assertEqual(resp.text, text)
  1082. self.assertEqual(session_val, request.headers[session_key])
  1083. self.assertEqual(adapter_val, request.headers[adapter_key])
  1084. self.assertEqual(request_val, request.headers[request_key])
  1085. def test_additional_headers_overrides(self):
  1086. header = uuid.uuid4().hex
  1087. session_val = uuid.uuid4().hex
  1088. adapter_val = uuid.uuid4().hex
  1089. request_val = uuid.uuid4().hex
  1090. url = 'http://keystone.test.com'
  1091. self.requests_mock.get(url)
  1092. sess = client_session.Session(additional_headers={header: session_val})
  1093. adap = adapter.Adapter(session=sess)
  1094. adap.get(url)
  1095. self.assertEqual(session_val,
  1096. self.requests_mock.last_request.headers[header])
  1097. adap.additional_headers[header] = adapter_val
  1098. adap.get(url)
  1099. self.assertEqual(adapter_val,
  1100. self.requests_mock.last_request.headers[header])
  1101. adap.get(url, headers={header: request_val})
  1102. self.assertEqual(request_val,
  1103. self.requests_mock.last_request.headers[header])
  1104. def test_adapter_user_agent_session_adapter(self):
  1105. sess = client_session.Session(app_name='ksatest', app_version='1.2.3')
  1106. adap = adapter.Adapter(client_name='testclient',
  1107. client_version='4.5.6',
  1108. session=sess)
  1109. url = 'http://keystone.test.com'
  1110. self.requests_mock.get(url)
  1111. adap.get(url)
  1112. agent = 'ksatest/1.2.3 testclient/4.5.6'
  1113. self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT,
  1114. self.requests_mock.last_request.headers['User-Agent'])
  1115. def test_adapter_user_agent_session_version_on_adapter(self):
  1116. class TestAdapter(adapter.Adapter):
  1117. client_name = 'testclient'
  1118. client_version = '4.5.6'
  1119. sess = client_session.Session(app_name='ksatest', app_version='1.2.3')
  1120. adap = TestAdapter(session=sess)
  1121. url = 'http://keystone.test.com'
  1122. self.requests_mock.get(url)
  1123. adap.get(url)
  1124. agent = 'ksatest/1.2.3 testclient/4.5.6'
  1125. self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT,
  1126. self.requests_mock.last_request.headers['User-Agent'])
  1127. def test_adapter_user_agent_session_adapter_no_app_version(self):
  1128. sess = client_session.Session(app_name='ksatest')
  1129. adap = adapter.Adapter(client_name='testclient',
  1130. client_version='4.5.6',
  1131. session=sess)
  1132. url = 'http://keystone.test.com'
  1133. self.requests_mock.get(url)
  1134. adap.get(url)
  1135. agent = 'ksatest testclient/4.5.6'
  1136. self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT,
  1137. self.requests_mock.last_request.headers['User-Agent'])
  1138. def test_adapter_user_agent_session_adapter_no_client_version(self):
  1139. sess = client_session.Session(app_name='ksatest', app_version='1.2.3')
  1140. adap = adapter.Adapter(client_name='testclient', session=sess)
  1141. url = 'http://keystone.test.com'
  1142. self.requests_mock.get(url)
  1143. adap.get(url)
  1144. agent = 'ksatest/1.2.3 testclient'
  1145. self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT,
  1146. self.requests_mock.last_request.headers['User-Agent'])
  1147. def test_adapter_user_agent_session_adapter_additional(self):
  1148. sess = client_session.Session(app_name='ksatest',
  1149. app_version='1.2.3',
  1150. additional_user_agent=[('one', '1.1.1'),
  1151. ('two', '2.2.2')])
  1152. adap = adapter.Adapter(client_name='testclient',
  1153. client_version='4.5.6',
  1154. session=sess)
  1155. url = 'http://keystone.test.com'
  1156. self.requests_mock.get(url)
  1157. adap.get(url)
  1158. agent = 'ksatest/1.2.3 testclient/4.5.6 one/1.1.1 two/2.2.2'
  1159. self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT,
  1160. self.requests_mock.last_request.headers['User-Agent'])
  1161. def test_adapter_user_agent_session(self):
  1162. sess = client_session.Session(app_name='ksatest', app_version='1.2.3')
  1163. adap = adapter.Adapter(session=sess)
  1164. url = 'http://keystone.test.com'
  1165. self.requests_mock.get(url)
  1166. adap.get(url)
  1167. agent = 'ksatest/1.2.3'
  1168. self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT,
  1169. self.requests_mock.last_request.headers['User-Agent'])
  1170. def test_adapter_user_agent_adapter(self):
  1171. sess = client_session.Session()
  1172. adap = adapter.Adapter(client_name='testclient',
  1173. client_version='4.5.6',
  1174. session=sess)
  1175. url = 'http://keystone.test.com'
  1176. self.requests_mock.get(url)
  1177. adap.get(url)
  1178. agent = 'testclient/4.5.6'
  1179. self.assertEqual(agent + ' ' + client_session.DEFAULT_USER_AGENT,
  1180. self.requests_mock.last_request.headers['User-Agent'])
  1181. def test_adapter_user_agent_session_override(self):
  1182. sess = client_session.Session(app_name='ksatest',
  1183. app_version='1.2.3',
  1184. additional_user_agent=[('one', '1.1.1'),
  1185. ('two', '2.2.2')])
  1186. adap = adapter.Adapter(client_name='testclient',
  1187. client_version='4.5.6',
  1188. session=sess)
  1189. url = 'http://keystone.test.com'
  1190. self.requests_mock.get(url)
  1191. override_user_agent = '%s/%s' % (uuid.uuid4().hex, uuid.uuid4().hex)
  1192. adap.get(url, user_agent=override_user_agent)
  1193. self.assertEqual(override_user_agent,
  1194. self.requests_mock.last_request.headers['User-Agent'])
  1195. def test_nested_adapters(self):
  1196. text = uuid.uuid4().hex
  1197. token = uuid.uuid4().hex
  1198. url = 'http://keystone.example.com/path'
  1199. sess = client_session.Session()
  1200. auth = CalledAuthPlugin()
  1201. auth.ENDPOINT = url
  1202. auth.TOKEN = token
  1203. adap1 = adapter.Adapter(session=sess,
  1204. interface='public')
  1205. adap2 = adapter.Adapter(session=adap1,
  1206. service_type='identity',
  1207. auth=auth)
  1208. self.requests_mock.get(url + '/test', text=text)
  1209. resp = adap2.get('/test')
  1210. self.assertEqual(text, resp.text)
  1211. self.assertTrue(auth.get_endpoint_called)
  1212. self.assertEqual('public', auth.endpoint_arguments['interface'])
  1213. self.assertEqual('identity', auth.endpoint_arguments['service_type'])
  1214. last_token = self.requests_mock.last_request.headers['X-Auth-Token']
  1215. self.assertEqual(token, last_token)
  1216. def test_default_microversion(self):
  1217. sess = client_session.Session()
  1218. url = 'http://url'
  1219. def validate(adap_kwargs, get_kwargs, exp_kwargs):
  1220. with mock.patch.object(sess, 'request') as m:
  1221. adapter.Adapter(sess, **adap_kwargs).get(url, **get_kwargs)
  1222. m.assert_called_once_with(url, 'GET', endpoint_filter={},
  1223. **exp_kwargs)
  1224. # No default_microversion in Adapter, no microversion in get()
  1225. validate({}, {}, {})
  1226. # default_microversion in Adapter, no microversion in get()
  1227. validate({'default_microversion': '1.2'}, {}, {'microversion': '1.2'})
  1228. # No default_microversion in Adapter, microversion specified in get()
  1229. validate({}, {'microversion': '1.2'}, {'microversion': '1.2'})
  1230. # microversion in get() overrides default_microversion in Adapter
  1231. validate({'default_microversion': '1.2'}, {'microversion': '1.5'},
  1232. {'microversion': '1.5'})
  1233. def test_raise_exc_override(self):
  1234. sess = client_session.Session()
  1235. url = 'http://url'
  1236. def validate(adap_kwargs, get_kwargs, exp_kwargs):
  1237. with mock.patch.object(sess, 'request') as m:
  1238. adapter.Adapter(sess, **adap_kwargs).get(url, **get_kwargs)
  1239. m.assert_called_once_with(url, 'GET', endpoint_filter={},
  1240. **exp_kwargs)
  1241. # No raise_exc in Adapter or get()
  1242. validate({}, {}, {})
  1243. # Set in Adapter, unset in get()
  1244. validate({'raise_exc': True}, {}, {'raise_exc': True})
  1245. validate({'raise_exc': False}, {}, {'raise_exc': False})
  1246. # Unset in Adapter, set in get()
  1247. validate({}, {'raise_exc': True}, {'raise_exc': True})
  1248. validate({}, {'raise_exc': False}, {'raise_exc': False})
  1249. # Setting in get() overrides the one in Adapter
  1250. validate({'raise_exc': True}, {'raise_exc': False},
  1251. {'raise_exc': False})
  1252. validate({'raise_exc': False}, {'raise_exc': True},
  1253. {'raise_exc': True})
  1254. class TCPKeepAliveAdapterTest(utils.TestCase):
  1255. def setUp(self):
  1256. super(TCPKeepAliveAdapterTest, self).setUp()
  1257. self.init_poolmanager = self.patch(
  1258. client_session.requests.adapters.HTTPAdapter,
  1259. 'init_poolmanager')
  1260. self.constructor = self.patch(
  1261. client_session.TCPKeepAliveAdapter, '__init__', lambda self: None)
  1262. def test_init_poolmanager_with_requests_lesser_than_2_4_1(self):
  1263. self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 0))
  1264. given_adapter = client_session.TCPKeepAliveAdapter()
  1265. # when pool manager is initialized
  1266. given_adapter.init_poolmanager(1, 2, 3)
  1267. # then no socket_options are given
  1268. self.init_poolmanager.assert_called_once_with(1, 2, 3)
  1269. def test_init_poolmanager_with_basic_options(self):
  1270. self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1))
  1271. socket = self.patch_socket_with_options(
  1272. ['IPPROTO_TCP', 'TCP_NODELAY', 'SOL_SOCKET', 'SO_KEEPALIVE'])
  1273. given_adapter = client_session.TCPKeepAliveAdapter()
  1274. # when pool manager is initialized
  1275. given_adapter.init_poolmanager(1, 2, 3)
  1276. # then no socket_options are given
  1277. self.init_poolmanager.assert_called_once_with(
  1278. 1, 2, 3, socket_options=[
  1279. (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),
  1280. (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)])
  1281. def test_init_poolmanager_with_tcp_keepidle(self):
  1282. self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1))
  1283. socket = self.patch_socket_with_options(
  1284. ['IPPROTO_TCP', 'TCP_NODELAY', 'SOL_SOCKET', 'SO_KEEPALIVE',
  1285. 'TCP_KEEPIDLE'])
  1286. given_adapter = client_session.TCPKeepAliveAdapter()
  1287. # when pool manager is initialized
  1288. given_adapter.init_poolmanager(1, 2, 3)
  1289. # then socket_options are given
  1290. self.init_poolmanager.assert_called_once_with(
  1291. 1, 2, 3, socket_options=[
  1292. (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),
  1293. (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
  1294. (socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60)])
  1295. def test_init_poolmanager_with_tcp_keepcnt(self):
  1296. self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1))
  1297. self.patch(client_session.utils, 'is_windows_linux_subsystem', False)
  1298. socket = self.patch_socket_with_options(
  1299. ['IPPROTO_TCP', 'TCP_NODELAY', 'SOL_SOCKET', 'SO_KEEPALIVE',
  1300. 'TCP_KEEPCNT'])
  1301. given_adapter = client_session.TCPKeepAliveAdapter()
  1302. # when pool manager is initialized
  1303. given_adapter.init_poolmanager(1, 2, 3)
  1304. # then socket_options are given
  1305. self.init_poolmanager.assert_called_once_with(
  1306. 1, 2, 3, socket_options=[
  1307. (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),
  1308. (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
  1309. (socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 4)])
  1310. def test_init_poolmanager_with_tcp_keepcnt_on_windows(self):
  1311. self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1))
  1312. self.patch(client_session.utils, 'is_windows_linux_subsystem', True)
  1313. socket = self.patch_socket_with_options(
  1314. ['IPPROTO_TCP', 'TCP_NODELAY', 'SOL_SOCKET', 'SO_KEEPALIVE',
  1315. 'TCP_KEEPCNT'])
  1316. given_adapter = client_session.TCPKeepAliveAdapter()
  1317. # when pool manager is initialized
  1318. given_adapter.init_poolmanager(1, 2, 3)
  1319. # then socket_options are given
  1320. self.init_poolmanager.assert_called_once_with(
  1321. 1, 2, 3, socket_options=[
  1322. (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),
  1323. (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)])
  1324. def test_init_poolmanager_with_tcp_keepintvl(self):
  1325. self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1))
  1326. socket = self.patch_socket_with_options(
  1327. ['IPPROTO_TCP', 'TCP_NODELAY', 'SOL_SOCKET', 'SO_KEEPALIVE',
  1328. 'TCP_KEEPINTVL'])
  1329. given_adapter = client_session.TCPKeepAliveAdapter()
  1330. # when pool manager is initialized
  1331. given_adapter.init_poolmanager(1, 2, 3)
  1332. # then socket_options are given
  1333. self.init_poolmanager.assert_called_once_with(
  1334. 1, 2, 3, socket_options=[
  1335. (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),
  1336. (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
  1337. (socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 15)])
  1338. def test_init_poolmanager_with_given_optionsl(self):
  1339. self.patch(client_session, 'REQUESTS_VERSION', (2, 4, 1))
  1340. given_adapter = client_session.TCPKeepAliveAdapter()
  1341. given_options = object()
  1342. # when pool manager is initialized
  1343. given_adapter.init_poolmanager(1, 2, 3, socket_options=given_options)
  1344. # then socket_options are given
  1345. self.init_poolmanager.assert_called_once_with(
  1346. 1, 2, 3, socket_options=given_options)
  1347. def patch_socket_with_options(self, option_names):
  1348. # to mock socket module with exactly the attributes I want I create
  1349. # a class with that attributes
  1350. socket = type('socket', (object,),
  1351. {name: 'socket.' + name for name in option_names})
  1352. return self.patch(client_session, 'socket', socket)
  1353. def patch(self, target, name, *args, **kwargs):
  1354. context = mock.patch.object(target, name, *args, **kwargs)
  1355. patch = context.start()
  1356. self.addCleanup(context.stop)
  1357. return patch