cygrpc_test.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. # Copyright 2015, Google Inc.
  2. # All rights reserved.
  3. #
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions are
  6. # met:
  7. #
  8. # * Redistributions of source code must retain the above copyright
  9. # notice, this list of conditions and the following disclaimer.
  10. # * Redistributions in binary form must reproduce the above
  11. # copyright notice, this list of conditions and the following disclaimer
  12. # in the documentation and/or other materials provided with the
  13. # distribution.
  14. # * Neither the name of Google Inc. nor the names of its
  15. # contributors may be used to endorse or promote products derived from
  16. # this software without specific prior written permission.
  17. #
  18. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. import time
  30. import threading
  31. import unittest
  32. from grpc._cython import cygrpc
  33. from tests.unit._cython import test_utilities
  34. from tests.unit import test_common
  35. from tests.unit import resources
  36. _SSL_HOST_OVERRIDE = b'foo.test.google.fr'
  37. _CALL_CREDENTIALS_METADATA_KEY = 'call-creds-key'
  38. _CALL_CREDENTIALS_METADATA_VALUE = 'call-creds-value'
  39. _EMPTY_FLAGS = 0
  40. def _metadata_plugin_callback(context, callback):
  41. callback(cygrpc.Metadata(
  42. [cygrpc.Metadatum(_CALL_CREDENTIALS_METADATA_KEY,
  43. _CALL_CREDENTIALS_METADATA_VALUE)]),
  44. cygrpc.StatusCode.ok, b'')
  45. class TypeSmokeTest(unittest.TestCase):
  46. def testStringsInUtilitiesUpDown(self):
  47. self.assertEqual(0, cygrpc.StatusCode.ok)
  48. metadatum = cygrpc.Metadatum(b'a', b'b')
  49. self.assertEqual(b'a', metadatum.key)
  50. self.assertEqual(b'b', metadatum.value)
  51. metadata = cygrpc.Metadata([metadatum])
  52. self.assertEqual(1, len(metadata))
  53. self.assertEqual(metadatum.key, metadata[0].key)
  54. def testMetadataIteration(self):
  55. metadata = cygrpc.Metadata([
  56. cygrpc.Metadatum(b'a', b'b'), cygrpc.Metadatum(b'c', b'd')])
  57. iterator = iter(metadata)
  58. metadatum = next(iterator)
  59. self.assertIsInstance(metadatum, cygrpc.Metadatum)
  60. self.assertEqual(metadatum.key, b'a')
  61. self.assertEqual(metadatum.value, b'b')
  62. metadatum = next(iterator)
  63. self.assertIsInstance(metadatum, cygrpc.Metadatum)
  64. self.assertEqual(metadatum.key, b'c')
  65. self.assertEqual(metadatum.value, b'd')
  66. with self.assertRaises(StopIteration):
  67. next(iterator)
  68. def testOperationsIteration(self):
  69. operations = cygrpc.Operations([
  70. cygrpc.operation_send_message(b'asdf', _EMPTY_FLAGS)])
  71. iterator = iter(operations)
  72. operation = next(iterator)
  73. self.assertIsInstance(operation, cygrpc.Operation)
  74. # `Operation`s are write-only structures; can't directly debug anything out
  75. # of them. Just check that we stop iterating.
  76. with self.assertRaises(StopIteration):
  77. next(iterator)
  78. def testOperationFlags(self):
  79. operation = cygrpc.operation_send_message(b'asdf',
  80. cygrpc.WriteFlag.no_compress)
  81. self.assertEqual(cygrpc.WriteFlag.no_compress, operation.flags)
  82. def testTimespec(self):
  83. now = time.time()
  84. timespec = cygrpc.Timespec(now)
  85. self.assertAlmostEqual(now, float(timespec), places=8)
  86. def testCompletionQueueUpDown(self):
  87. completion_queue = cygrpc.CompletionQueue()
  88. del completion_queue
  89. def testServerUpDown(self):
  90. server = cygrpc.Server(cygrpc.ChannelArgs([]))
  91. del server
  92. def testChannelUpDown(self):
  93. channel = cygrpc.Channel(b'[::]:0', cygrpc.ChannelArgs([]))
  94. del channel
  95. def testCredentialsMetadataPluginUpDown(self):
  96. plugin = cygrpc.CredentialsMetadataPlugin(
  97. lambda ignored_a, ignored_b: None, b'')
  98. del plugin
  99. def testCallCredentialsFromPluginUpDown(self):
  100. plugin = cygrpc.CredentialsMetadataPlugin(_metadata_plugin_callback, b'')
  101. call_credentials = cygrpc.call_credentials_metadata_plugin(plugin)
  102. del plugin
  103. del call_credentials
  104. def testServerStartNoExplicitShutdown(self):
  105. server = cygrpc.Server()
  106. completion_queue = cygrpc.CompletionQueue()
  107. server.register_completion_queue(completion_queue)
  108. port = server.add_http2_port(b'[::]:0')
  109. self.assertIsInstance(port, int)
  110. server.start()
  111. del server
  112. def testServerStartShutdown(self):
  113. completion_queue = cygrpc.CompletionQueue()
  114. server = cygrpc.Server()
  115. server.add_http2_port(b'[::]:0')
  116. server.register_completion_queue(completion_queue)
  117. server.start()
  118. shutdown_tag = object()
  119. server.shutdown(completion_queue, shutdown_tag)
  120. event = completion_queue.poll()
  121. self.assertEqual(cygrpc.CompletionType.operation_complete, event.type)
  122. self.assertIs(shutdown_tag, event.tag)
  123. del server
  124. del completion_queue
  125. class ServerClientMixin(object):
  126. def setUpMixin(self, server_credentials, client_credentials, host_override):
  127. self.server_completion_queue = cygrpc.CompletionQueue()
  128. self.server = cygrpc.Server()
  129. self.server.register_completion_queue(self.server_completion_queue)
  130. if server_credentials:
  131. self.port = self.server.add_http2_port(b'[::]:0', server_credentials)
  132. else:
  133. self.port = self.server.add_http2_port(b'[::]:0')
  134. self.server.start()
  135. self.client_completion_queue = cygrpc.CompletionQueue()
  136. if client_credentials:
  137. client_channel_arguments = cygrpc.ChannelArgs([
  138. cygrpc.ChannelArg(cygrpc.ChannelArgKey.ssl_target_name_override,
  139. host_override)])
  140. self.client_channel = cygrpc.Channel(
  141. 'localhost:{}'.format(self.port).encode(), client_channel_arguments,
  142. client_credentials)
  143. else:
  144. self.client_channel = cygrpc.Channel('localhost:{}'.format(self.port).encode())
  145. if host_override:
  146. self.host_argument = None # default host
  147. self.expected_host = host_override
  148. else:
  149. # arbitrary host name necessitating no further identification
  150. self.host_argument = b'hostess'
  151. self.expected_host = self.host_argument
  152. def tearDownMixin(self):
  153. del self.server
  154. del self.client_completion_queue
  155. del self.server_completion_queue
  156. def _perform_operations(self, operations, call, queue, deadline, description):
  157. """Perform the list of operations with given call, queue, and deadline.
  158. Invocation errors are reported with as an exception with `description` in
  159. the message. Performs the operations asynchronously, returning a future.
  160. """
  161. def performer():
  162. tag = object()
  163. try:
  164. call_result = call.start_client_batch(
  165. cygrpc.Operations(operations), tag)
  166. self.assertEqual(cygrpc.CallError.ok, call_result)
  167. event = queue.poll(deadline)
  168. self.assertEqual(cygrpc.CompletionType.operation_complete, event.type)
  169. self.assertTrue(event.success)
  170. self.assertIs(tag, event.tag)
  171. except Exception as error:
  172. raise Exception("Error in '{}': {}".format(description, error.message))
  173. return event
  174. return test_utilities.SimpleFuture(performer)
  175. def testEcho(self):
  176. DEADLINE = time.time()+5
  177. DEADLINE_TOLERANCE = 0.25
  178. CLIENT_METADATA_ASCII_KEY = b'key'
  179. CLIENT_METADATA_ASCII_VALUE = b'val'
  180. CLIENT_METADATA_BIN_KEY = b'key-bin'
  181. CLIENT_METADATA_BIN_VALUE = b'\0'*1000
  182. SERVER_INITIAL_METADATA_KEY = b'init_me_me_me'
  183. SERVER_INITIAL_METADATA_VALUE = b'whodawha?'
  184. SERVER_TRAILING_METADATA_KEY = b'california_is_in_a_drought'
  185. SERVER_TRAILING_METADATA_VALUE = b'zomg it is'
  186. SERVER_STATUS_CODE = cygrpc.StatusCode.ok
  187. SERVER_STATUS_DETAILS = b'our work is never over'
  188. REQUEST = b'in death a member of project mayhem has a name'
  189. RESPONSE = b'his name is robert paulson'
  190. METHOD = b'twinkies'
  191. cygrpc_deadline = cygrpc.Timespec(DEADLINE)
  192. server_request_tag = object()
  193. request_call_result = self.server.request_call(
  194. self.server_completion_queue, self.server_completion_queue,
  195. server_request_tag)
  196. self.assertEqual(cygrpc.CallError.ok, request_call_result)
  197. client_call_tag = object()
  198. client_call = self.client_channel.create_call(
  199. None, 0, self.client_completion_queue, METHOD, self.host_argument,
  200. cygrpc_deadline)
  201. client_initial_metadata = cygrpc.Metadata([
  202. cygrpc.Metadatum(CLIENT_METADATA_ASCII_KEY,
  203. CLIENT_METADATA_ASCII_VALUE),
  204. cygrpc.Metadatum(CLIENT_METADATA_BIN_KEY, CLIENT_METADATA_BIN_VALUE)])
  205. client_start_batch_result = client_call.start_client_batch([
  206. cygrpc.operation_send_initial_metadata(client_initial_metadata,
  207. _EMPTY_FLAGS),
  208. cygrpc.operation_send_message(REQUEST, _EMPTY_FLAGS),
  209. cygrpc.operation_send_close_from_client(_EMPTY_FLAGS),
  210. cygrpc.operation_receive_initial_metadata(_EMPTY_FLAGS),
  211. cygrpc.operation_receive_message(_EMPTY_FLAGS),
  212. cygrpc.operation_receive_status_on_client(_EMPTY_FLAGS)
  213. ], client_call_tag)
  214. self.assertEqual(cygrpc.CallError.ok, client_start_batch_result)
  215. client_event_future = test_utilities.CompletionQueuePollFuture(
  216. self.client_completion_queue, cygrpc_deadline)
  217. request_event = self.server_completion_queue.poll(cygrpc_deadline)
  218. self.assertEqual(cygrpc.CompletionType.operation_complete,
  219. request_event.type)
  220. self.assertIsInstance(request_event.operation_call, cygrpc.Call)
  221. self.assertIs(server_request_tag, request_event.tag)
  222. self.assertEqual(0, len(request_event.batch_operations))
  223. self.assertTrue(
  224. test_common.metadata_transmitted(client_initial_metadata,
  225. request_event.request_metadata))
  226. self.assertEqual(METHOD, request_event.request_call_details.method)
  227. self.assertEqual(self.expected_host,
  228. request_event.request_call_details.host)
  229. self.assertLess(
  230. abs(DEADLINE - float(request_event.request_call_details.deadline)),
  231. DEADLINE_TOLERANCE)
  232. server_call_tag = object()
  233. server_call = request_event.operation_call
  234. server_initial_metadata = cygrpc.Metadata([
  235. cygrpc.Metadatum(SERVER_INITIAL_METADATA_KEY,
  236. SERVER_INITIAL_METADATA_VALUE)])
  237. server_trailing_metadata = cygrpc.Metadata([
  238. cygrpc.Metadatum(SERVER_TRAILING_METADATA_KEY,
  239. SERVER_TRAILING_METADATA_VALUE)])
  240. server_start_batch_result = server_call.start_server_batch([
  241. cygrpc.operation_send_initial_metadata(server_initial_metadata,
  242. _EMPTY_FLAGS),
  243. cygrpc.operation_receive_message(_EMPTY_FLAGS),
  244. cygrpc.operation_send_message(RESPONSE, _EMPTY_FLAGS),
  245. cygrpc.operation_receive_close_on_server(_EMPTY_FLAGS),
  246. cygrpc.operation_send_status_from_server(
  247. server_trailing_metadata, SERVER_STATUS_CODE,
  248. SERVER_STATUS_DETAILS, _EMPTY_FLAGS)
  249. ], server_call_tag)
  250. self.assertEqual(cygrpc.CallError.ok, server_start_batch_result)
  251. server_event = self.server_completion_queue.poll(cygrpc_deadline)
  252. client_event = client_event_future.result()
  253. self.assertEqual(6, len(client_event.batch_operations))
  254. found_client_op_types = set()
  255. for client_result in client_event.batch_operations:
  256. # we expect each op type to be unique
  257. self.assertNotIn(client_result.type, found_client_op_types)
  258. found_client_op_types.add(client_result.type)
  259. if client_result.type == cygrpc.OperationType.receive_initial_metadata:
  260. self.assertTrue(
  261. test_common.metadata_transmitted(server_initial_metadata,
  262. client_result.received_metadata))
  263. elif client_result.type == cygrpc.OperationType.receive_message:
  264. self.assertEqual(RESPONSE, client_result.received_message.bytes())
  265. elif client_result.type == cygrpc.OperationType.receive_status_on_client:
  266. self.assertTrue(
  267. test_common.metadata_transmitted(server_trailing_metadata,
  268. client_result.received_metadata))
  269. self.assertEqual(SERVER_STATUS_DETAILS,
  270. client_result.received_status_details)
  271. self.assertEqual(SERVER_STATUS_CODE, client_result.received_status_code)
  272. self.assertEqual(set([
  273. cygrpc.OperationType.send_initial_metadata,
  274. cygrpc.OperationType.send_message,
  275. cygrpc.OperationType.send_close_from_client,
  276. cygrpc.OperationType.receive_initial_metadata,
  277. cygrpc.OperationType.receive_message,
  278. cygrpc.OperationType.receive_status_on_client
  279. ]), found_client_op_types)
  280. self.assertEqual(5, len(server_event.batch_operations))
  281. found_server_op_types = set()
  282. for server_result in server_event.batch_operations:
  283. self.assertNotIn(client_result.type, found_server_op_types)
  284. found_server_op_types.add(server_result.type)
  285. if server_result.type == cygrpc.OperationType.receive_message:
  286. self.assertEqual(REQUEST, server_result.received_message.bytes())
  287. elif server_result.type == cygrpc.OperationType.receive_close_on_server:
  288. self.assertFalse(server_result.received_cancelled)
  289. self.assertEqual(set([
  290. cygrpc.OperationType.send_initial_metadata,
  291. cygrpc.OperationType.receive_message,
  292. cygrpc.OperationType.send_message,
  293. cygrpc.OperationType.receive_close_on_server,
  294. cygrpc.OperationType.send_status_from_server
  295. ]), found_server_op_types)
  296. del client_call
  297. del server_call
  298. def test6522(self):
  299. DEADLINE = time.time()+5
  300. DEADLINE_TOLERANCE = 0.25
  301. METHOD = b'twinkies'
  302. cygrpc_deadline = cygrpc.Timespec(DEADLINE)
  303. empty_metadata = cygrpc.Metadata([])
  304. server_request_tag = object()
  305. self.server.request_call(
  306. self.server_completion_queue, self.server_completion_queue,
  307. server_request_tag)
  308. client_call = self.client_channel.create_call(
  309. None, 0, self.client_completion_queue, METHOD, self.host_argument,
  310. cygrpc_deadline)
  311. # Prologue
  312. def perform_client_operations(operations, description):
  313. return self._perform_operations(
  314. operations, client_call,
  315. self.client_completion_queue, cygrpc_deadline, description)
  316. client_event_future = perform_client_operations([
  317. cygrpc.operation_send_initial_metadata(empty_metadata,
  318. _EMPTY_FLAGS),
  319. cygrpc.operation_receive_initial_metadata(_EMPTY_FLAGS),
  320. ], "Client prologue")
  321. request_event = self.server_completion_queue.poll(cygrpc_deadline)
  322. server_call = request_event.operation_call
  323. def perform_server_operations(operations, description):
  324. return self._perform_operations(
  325. operations, server_call,
  326. self.server_completion_queue, cygrpc_deadline, description)
  327. server_event_future = perform_server_operations([
  328. cygrpc.operation_send_initial_metadata(empty_metadata,
  329. _EMPTY_FLAGS),
  330. ], "Server prologue")
  331. client_event_future.result() # force completion
  332. server_event_future.result()
  333. # Messaging
  334. for _ in range(10):
  335. client_event_future = perform_client_operations([
  336. cygrpc.operation_send_message(b'', _EMPTY_FLAGS),
  337. cygrpc.operation_receive_message(_EMPTY_FLAGS),
  338. ], "Client message")
  339. server_event_future = perform_server_operations([
  340. cygrpc.operation_send_message(b'', _EMPTY_FLAGS),
  341. cygrpc.operation_receive_message(_EMPTY_FLAGS),
  342. ], "Server receive")
  343. client_event_future.result() # force completion
  344. server_event_future.result()
  345. # Epilogue
  346. client_event_future = perform_client_operations([
  347. cygrpc.operation_send_close_from_client(_EMPTY_FLAGS),
  348. cygrpc.operation_receive_status_on_client(_EMPTY_FLAGS)
  349. ], "Client epilogue")
  350. server_event_future = perform_server_operations([
  351. cygrpc.operation_receive_close_on_server(_EMPTY_FLAGS),
  352. cygrpc.operation_send_status_from_server(
  353. empty_metadata, cygrpc.StatusCode.ok, b'', _EMPTY_FLAGS)
  354. ], "Server epilogue")
  355. client_event_future.result() # force completion
  356. server_event_future.result()
  357. class InsecureServerInsecureClient(unittest.TestCase, ServerClientMixin):
  358. def setUp(self):
  359. self.setUpMixin(None, None, None)
  360. def tearDown(self):
  361. self.tearDownMixin()
  362. class SecureServerSecureClient(unittest.TestCase, ServerClientMixin):
  363. def setUp(self):
  364. server_credentials = cygrpc.server_credentials_ssl(
  365. None, [cygrpc.SslPemKeyCertPair(resources.private_key(),
  366. resources.certificate_chain())], False)
  367. client_credentials = cygrpc.channel_credentials_ssl(
  368. resources.test_root_certificates(), None)
  369. self.setUpMixin(server_credentials, client_credentials, _SSL_HOST_OVERRIDE)
  370. def tearDown(self):
  371. self.tearDownMixin()
  372. if __name__ == '__main__':
  373. unittest.main(verbosity=2)