mirror of https://github.com/grpc/grpc.git
Merge pull request #16919 from lidizheng/wait-for-ready
Add wait-for-ready semanticspull/17141/head
commit
678ea08950
8 changed files with 517 additions and 75 deletions
@ -0,0 +1,251 @@ |
|||||||
|
# Copyright 2018 gRPC authors. |
||||||
|
# |
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
# you may not use this file except in compliance with the License. |
||||||
|
# You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, software |
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
# See the License for the specific language governing permissions and |
||||||
|
# limitations under the License. |
||||||
|
"""Tests metadata flags feature by testing wait-for-ready semantics""" |
||||||
|
|
||||||
|
import time |
||||||
|
import weakref |
||||||
|
import unittest |
||||||
|
import threading |
||||||
|
import socket |
||||||
|
from six.moves import queue |
||||||
|
|
||||||
|
import grpc |
||||||
|
|
||||||
|
from tests.unit import test_common |
||||||
|
from tests.unit.framework.common import test_constants |
||||||
|
|
||||||
|
_UNARY_UNARY = '/test/UnaryUnary' |
||||||
|
_UNARY_STREAM = '/test/UnaryStream' |
||||||
|
_STREAM_UNARY = '/test/StreamUnary' |
||||||
|
_STREAM_STREAM = '/test/StreamStream' |
||||||
|
|
||||||
|
_REQUEST = b'\x00\x00\x00' |
||||||
|
_RESPONSE = b'\x00\x00\x00' |
||||||
|
|
||||||
|
|
||||||
|
def handle_unary_unary(test, request, servicer_context): |
||||||
|
return _RESPONSE |
||||||
|
|
||||||
|
|
||||||
|
def handle_unary_stream(test, request, servicer_context): |
||||||
|
for _ in range(test_constants.STREAM_LENGTH): |
||||||
|
yield _RESPONSE |
||||||
|
|
||||||
|
|
||||||
|
def handle_stream_unary(test, request_iterator, servicer_context): |
||||||
|
for _ in request_iterator: |
||||||
|
pass |
||||||
|
return _RESPONSE |
||||||
|
|
||||||
|
|
||||||
|
def handle_stream_stream(test, request_iterator, servicer_context): |
||||||
|
for _ in request_iterator: |
||||||
|
yield _RESPONSE |
||||||
|
|
||||||
|
|
||||||
|
class _MethodHandler(grpc.RpcMethodHandler): |
||||||
|
|
||||||
|
def __init__(self, test, request_streaming, response_streaming): |
||||||
|
self.request_streaming = request_streaming |
||||||
|
self.response_streaming = response_streaming |
||||||
|
self.request_deserializer = None |
||||||
|
self.response_serializer = None |
||||||
|
self.unary_unary = None |
||||||
|
self.unary_stream = None |
||||||
|
self.stream_unary = None |
||||||
|
self.stream_stream = None |
||||||
|
if self.request_streaming and self.response_streaming: |
||||||
|
self.stream_stream = lambda req, ctx: handle_stream_stream(test, req, ctx) |
||||||
|
elif self.request_streaming: |
||||||
|
self.stream_unary = lambda req, ctx: handle_stream_unary(test, req, ctx) |
||||||
|
elif self.response_streaming: |
||||||
|
self.unary_stream = lambda req, ctx: handle_unary_stream(test, req, ctx) |
||||||
|
else: |
||||||
|
self.unary_unary = lambda req, ctx: handle_unary_unary(test, req, ctx) |
||||||
|
|
||||||
|
|
||||||
|
class _GenericHandler(grpc.GenericRpcHandler): |
||||||
|
|
||||||
|
def __init__(self, test): |
||||||
|
self._test = test |
||||||
|
|
||||||
|
def service(self, handler_call_details): |
||||||
|
if handler_call_details.method == _UNARY_UNARY: |
||||||
|
return _MethodHandler(self._test, False, False) |
||||||
|
elif handler_call_details.method == _UNARY_STREAM: |
||||||
|
return _MethodHandler(self._test, False, True) |
||||||
|
elif handler_call_details.method == _STREAM_UNARY: |
||||||
|
return _MethodHandler(self._test, True, False) |
||||||
|
elif handler_call_details.method == _STREAM_STREAM: |
||||||
|
return _MethodHandler(self._test, True, True) |
||||||
|
else: |
||||||
|
return None |
||||||
|
|
||||||
|
|
||||||
|
def get_free_loopback_tcp_port(): |
||||||
|
tcp = socket.socket(socket.AF_INET6) |
||||||
|
tcp.bind(('', 0)) |
||||||
|
address_tuple = tcp.getsockname() |
||||||
|
return tcp, "[::1]:%s" % (address_tuple[1]) |
||||||
|
|
||||||
|
|
||||||
|
def create_dummy_channel(): |
||||||
|
"""Creating dummy channels is a workaround for retries""" |
||||||
|
_, addr = get_free_loopback_tcp_port() |
||||||
|
return grpc.insecure_channel(addr) |
||||||
|
|
||||||
|
|
||||||
|
def perform_unary_unary_call(channel, wait_for_ready=None): |
||||||
|
channel.unary_unary(_UNARY_UNARY).__call__( |
||||||
|
_REQUEST, |
||||||
|
timeout=test_constants.LONG_TIMEOUT, |
||||||
|
wait_for_ready=wait_for_ready) |
||||||
|
|
||||||
|
|
||||||
|
def perform_unary_unary_with_call(channel, wait_for_ready=None): |
||||||
|
channel.unary_unary(_UNARY_UNARY).with_call( |
||||||
|
_REQUEST, |
||||||
|
timeout=test_constants.LONG_TIMEOUT, |
||||||
|
wait_for_ready=wait_for_ready) |
||||||
|
|
||||||
|
|
||||||
|
def perform_unary_unary_future(channel, wait_for_ready=None): |
||||||
|
channel.unary_unary(_UNARY_UNARY).future( |
||||||
|
_REQUEST, |
||||||
|
timeout=test_constants.LONG_TIMEOUT, |
||||||
|
wait_for_ready=wait_for_ready).result( |
||||||
|
timeout=test_constants.LONG_TIMEOUT) |
||||||
|
|
||||||
|
|
||||||
|
def perform_unary_stream_call(channel, wait_for_ready=None): |
||||||
|
response_iterator = channel.unary_stream(_UNARY_STREAM).__call__( |
||||||
|
_REQUEST, |
||||||
|
timeout=test_constants.LONG_TIMEOUT, |
||||||
|
wait_for_ready=wait_for_ready) |
||||||
|
for _ in response_iterator: |
||||||
|
pass |
||||||
|
|
||||||
|
|
||||||
|
def perform_stream_unary_call(channel, wait_for_ready=None): |
||||||
|
channel.stream_unary(_STREAM_UNARY).__call__( |
||||||
|
iter([_REQUEST] * test_constants.STREAM_LENGTH), |
||||||
|
timeout=test_constants.LONG_TIMEOUT, |
||||||
|
wait_for_ready=wait_for_ready) |
||||||
|
|
||||||
|
|
||||||
|
def perform_stream_unary_with_call(channel, wait_for_ready=None): |
||||||
|
channel.stream_unary(_STREAM_UNARY).with_call( |
||||||
|
iter([_REQUEST] * test_constants.STREAM_LENGTH), |
||||||
|
timeout=test_constants.LONG_TIMEOUT, |
||||||
|
wait_for_ready=wait_for_ready) |
||||||
|
|
||||||
|
|
||||||
|
def perform_stream_unary_future(channel, wait_for_ready=None): |
||||||
|
channel.stream_unary(_STREAM_UNARY).future( |
||||||
|
iter([_REQUEST] * test_constants.STREAM_LENGTH), |
||||||
|
timeout=test_constants.LONG_TIMEOUT, |
||||||
|
wait_for_ready=wait_for_ready).result( |
||||||
|
timeout=test_constants.LONG_TIMEOUT) |
||||||
|
|
||||||
|
|
||||||
|
def perform_stream_stream_call(channel, wait_for_ready=None): |
||||||
|
response_iterator = channel.stream_stream(_STREAM_STREAM).__call__( |
||||||
|
iter([_REQUEST] * test_constants.STREAM_LENGTH), |
||||||
|
timeout=test_constants.LONG_TIMEOUT, |
||||||
|
wait_for_ready=wait_for_ready) |
||||||
|
for _ in response_iterator: |
||||||
|
pass |
||||||
|
|
||||||
|
|
||||||
|
_ALL_CALL_CASES = [ |
||||||
|
perform_unary_unary_call, perform_unary_unary_with_call, |
||||||
|
perform_unary_unary_future, perform_unary_stream_call, |
||||||
|
perform_stream_unary_call, perform_stream_unary_with_call, |
||||||
|
perform_stream_unary_future, perform_stream_stream_call |
||||||
|
] |
||||||
|
|
||||||
|
|
||||||
|
class MetadataFlagsTest(unittest.TestCase): |
||||||
|
|
||||||
|
def check_connection_does_failfast(self, fn, channel, wait_for_ready=None): |
||||||
|
try: |
||||||
|
fn(channel, wait_for_ready) |
||||||
|
self.fail("The Call should fail") |
||||||
|
except BaseException as e: # pylint: disable=broad-except |
||||||
|
self.assertIn('StatusCode.UNAVAILABLE', str(e)) |
||||||
|
|
||||||
|
def test_call_wait_for_ready_default(self): |
||||||
|
for perform_call in _ALL_CALL_CASES: |
||||||
|
self.check_connection_does_failfast(perform_call, |
||||||
|
create_dummy_channel()) |
||||||
|
|
||||||
|
def test_call_wait_for_ready_disabled(self): |
||||||
|
for perform_call in _ALL_CALL_CASES: |
||||||
|
self.check_connection_does_failfast( |
||||||
|
perform_call, create_dummy_channel(), wait_for_ready=False) |
||||||
|
|
||||||
|
def test_call_wait_for_ready_enabled(self): |
||||||
|
# To test the wait mechanism, Python thread is required to make |
||||||
|
# client set up first without handling them case by case. |
||||||
|
# Also, Python thread don't pass the unhandled exceptions to |
||||||
|
# main thread. So, it need another method to store the |
||||||
|
# exceptions and raise them again in main thread. |
||||||
|
unhandled_exceptions = queue.Queue() |
||||||
|
tcp, addr = get_free_loopback_tcp_port() |
||||||
|
wg = test_common.WaitGroup(len(_ALL_CALL_CASES)) |
||||||
|
|
||||||
|
def wait_for_transient_failure(channel_connectivity): |
||||||
|
if channel_connectivity == grpc.ChannelConnectivity.TRANSIENT_FAILURE: |
||||||
|
wg.done() |
||||||
|
|
||||||
|
def test_call(perform_call): |
||||||
|
try: |
||||||
|
channel = grpc.insecure_channel(addr) |
||||||
|
channel.subscribe(wait_for_transient_failure) |
||||||
|
perform_call(channel, wait_for_ready=True) |
||||||
|
except BaseException as e: # pylint: disable=broad-except |
||||||
|
# If the call failed, the thread would be destroyed. The channel |
||||||
|
# object can be collected before calling the callback, which |
||||||
|
# will result in a deadlock. |
||||||
|
wg.done() |
||||||
|
unhandled_exceptions.put(e, True) |
||||||
|
|
||||||
|
test_threads = [] |
||||||
|
for perform_call in _ALL_CALL_CASES: |
||||||
|
test_thread = threading.Thread( |
||||||
|
target=test_call, args=(perform_call,)) |
||||||
|
test_thread.exception = None |
||||||
|
test_thread.start() |
||||||
|
test_threads.append(test_thread) |
||||||
|
|
||||||
|
# Start the server after the connections are waiting |
||||||
|
wg.wait() |
||||||
|
tcp.close() |
||||||
|
server = test_common.test_server() |
||||||
|
server.add_generic_rpc_handlers((_GenericHandler(weakref.proxy(self)),)) |
||||||
|
server.add_insecure_port(addr) |
||||||
|
server.start() |
||||||
|
|
||||||
|
for test_thread in test_threads: |
||||||
|
test_thread.join() |
||||||
|
|
||||||
|
# Stop the server to make test end properly |
||||||
|
server.stop(0) |
||||||
|
|
||||||
|
if not unhandled_exceptions.empty(): |
||||||
|
raise unhandled_exceptions.get(True) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
unittest.main(verbosity=2) |
Loading…
Reference in new issue