mirror of https://github.com/grpc/grpc.git
Merge pull request #22662 from lidizheng/aio-reflection
[Aio] Add AsyncIO support to grpcio-reflectionpull/22694/head
commit
b6a7d5505c
8 changed files with 450 additions and 93 deletions
@ -0,0 +1,57 @@ |
|||||||
|
# Copyright 2020 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. |
||||||
|
"""The AsyncIO version of the reflection servicer.""" |
||||||
|
|
||||||
|
from typing import AsyncIterable |
||||||
|
|
||||||
|
import grpc |
||||||
|
|
||||||
|
from grpc_reflection.v1alpha import reflection_pb2 as _reflection_pb2 |
||||||
|
from grpc_reflection.v1alpha._base import BaseReflectionServicer |
||||||
|
|
||||||
|
|
||||||
|
class ReflectionServicer(BaseReflectionServicer): |
||||||
|
"""Servicer handling RPCs for service statuses.""" |
||||||
|
|
||||||
|
async def ServerReflectionInfo( |
||||||
|
self, request_iterator: AsyncIterable[ |
||||||
|
_reflection_pb2.ServerReflectionRequest], unused_context |
||||||
|
) -> AsyncIterable[_reflection_pb2.ServerReflectionResponse]: |
||||||
|
async for request in request_iterator: |
||||||
|
if request.HasField('file_by_filename'): |
||||||
|
yield self._file_by_filename(request.file_by_filename) |
||||||
|
elif request.HasField('file_containing_symbol'): |
||||||
|
yield self._file_containing_symbol( |
||||||
|
request.file_containing_symbol) |
||||||
|
elif request.HasField('file_containing_extension'): |
||||||
|
yield self._file_containing_extension( |
||||||
|
request.file_containing_extension.containing_type, |
||||||
|
request.file_containing_extension.extension_number) |
||||||
|
elif request.HasField('all_extension_numbers_of_type'): |
||||||
|
yield self._all_extension_numbers_of_type( |
||||||
|
request.all_extension_numbers_of_type) |
||||||
|
elif request.HasField('list_services'): |
||||||
|
yield self._list_services() |
||||||
|
else: |
||||||
|
yield _reflection_pb2.ServerReflectionResponse( |
||||||
|
error_response=_reflection_pb2.ErrorResponse( |
||||||
|
error_code=grpc.StatusCode.INVALID_ARGUMENT.value[0], |
||||||
|
error_message=grpc.StatusCode.INVALID_ARGUMENT.value[1]. |
||||||
|
encode(), |
||||||
|
)) |
||||||
|
|
||||||
|
|
||||||
|
__all__ = [ |
||||||
|
"ReflectionServicer", |
||||||
|
] |
@ -0,0 +1,110 @@ |
|||||||
|
# Copyright 2020 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. |
||||||
|
"""Base implementation of reflection servicer.""" |
||||||
|
|
||||||
|
import grpc |
||||||
|
from google.protobuf import descriptor_pb2 |
||||||
|
from google.protobuf import descriptor_pool |
||||||
|
|
||||||
|
from grpc_reflection.v1alpha import reflection_pb2 as _reflection_pb2 |
||||||
|
from grpc_reflection.v1alpha import reflection_pb2_grpc as _reflection_pb2_grpc |
||||||
|
|
||||||
|
_POOL = descriptor_pool.Default() |
||||||
|
|
||||||
|
|
||||||
|
def _not_found_error(): |
||||||
|
return _reflection_pb2.ServerReflectionResponse( |
||||||
|
error_response=_reflection_pb2.ErrorResponse( |
||||||
|
error_code=grpc.StatusCode.NOT_FOUND.value[0], |
||||||
|
error_message=grpc.StatusCode.NOT_FOUND.value[1].encode(), |
||||||
|
)) |
||||||
|
|
||||||
|
|
||||||
|
def _file_descriptor_response(descriptor): |
||||||
|
proto = descriptor_pb2.FileDescriptorProto() |
||||||
|
descriptor.CopyToProto(proto) |
||||||
|
serialized_proto = proto.SerializeToString() |
||||||
|
return _reflection_pb2.ServerReflectionResponse( |
||||||
|
file_descriptor_response=_reflection_pb2.FileDescriptorResponse( |
||||||
|
file_descriptor_proto=(serialized_proto,)),) |
||||||
|
|
||||||
|
|
||||||
|
class BaseReflectionServicer(_reflection_pb2_grpc.ServerReflectionServicer): |
||||||
|
"""Base class for reflection servicer.""" |
||||||
|
|
||||||
|
def __init__(self, service_names, pool=None): |
||||||
|
"""Constructor. |
||||||
|
|
||||||
|
Args: |
||||||
|
service_names: Iterable of fully-qualified service names available. |
||||||
|
pool: An optional DescriptorPool instance. |
||||||
|
""" |
||||||
|
self._service_names = tuple(sorted(service_names)) |
||||||
|
self._pool = _POOL if pool is None else pool |
||||||
|
|
||||||
|
def _file_by_filename(self, filename): |
||||||
|
try: |
||||||
|
descriptor = self._pool.FindFileByName(filename) |
||||||
|
except KeyError: |
||||||
|
return _not_found_error() |
||||||
|
else: |
||||||
|
return _file_descriptor_response(descriptor) |
||||||
|
|
||||||
|
def _file_containing_symbol(self, fully_qualified_name): |
||||||
|
try: |
||||||
|
descriptor = self._pool.FindFileContainingSymbol( |
||||||
|
fully_qualified_name) |
||||||
|
except KeyError: |
||||||
|
return _not_found_error() |
||||||
|
else: |
||||||
|
return _file_descriptor_response(descriptor) |
||||||
|
|
||||||
|
def _file_containing_extension(self, containing_type, extension_number): |
||||||
|
try: |
||||||
|
message_descriptor = self._pool.FindMessageTypeByName( |
||||||
|
containing_type) |
||||||
|
extension_descriptor = self._pool.FindExtensionByNumber( |
||||||
|
message_descriptor, extension_number) |
||||||
|
descriptor = self._pool.FindFileContainingSymbol( |
||||||
|
extension_descriptor.full_name) |
||||||
|
except KeyError: |
||||||
|
return _not_found_error() |
||||||
|
else: |
||||||
|
return _file_descriptor_response(descriptor) |
||||||
|
|
||||||
|
def _all_extension_numbers_of_type(self, containing_type): |
||||||
|
try: |
||||||
|
message_descriptor = self._pool.FindMessageTypeByName( |
||||||
|
containing_type) |
||||||
|
extension_numbers = tuple( |
||||||
|
sorted(extension.number for extension in |
||||||
|
self._pool.FindAllExtensions(message_descriptor))) |
||||||
|
except KeyError: |
||||||
|
return _not_found_error() |
||||||
|
else: |
||||||
|
return _reflection_pb2.ServerReflectionResponse( |
||||||
|
all_extension_numbers_response=_reflection_pb2. |
||||||
|
ExtensionNumberResponse( |
||||||
|
base_type_name=message_descriptor.full_name, |
||||||
|
extension_number=extension_numbers)) |
||||||
|
|
||||||
|
def _list_services(self): |
||||||
|
return _reflection_pb2.ServerReflectionResponse( |
||||||
|
list_services_response=_reflection_pb2.ListServiceResponse(service=[ |
||||||
|
_reflection_pb2.ServiceResponse(name=service_name) |
||||||
|
for service_name in self._service_names |
||||||
|
])) |
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['BaseReflectionServicer'] |
@ -0,0 +1,30 @@ |
|||||||
|
# Copyright 2020 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. |
||||||
|
|
||||||
|
package(default_testonly = 1) |
||||||
|
|
||||||
|
py_test( |
||||||
|
name = "reflection_servicer_test", |
||||||
|
srcs = ["reflection_servicer_test.py"], |
||||||
|
imports = ["../../"], |
||||||
|
python_version = "PY3", |
||||||
|
deps = [ |
||||||
|
"//src/proto/grpc/testing:empty_py_pb2", |
||||||
|
"//src/proto/grpc/testing/proto2:empty2_extensions_proto", |
||||||
|
"//src/proto/grpc/testing/proto2:empty2_proto", |
||||||
|
"//src/python/grpcio/grpc:grpcio", |
||||||
|
"//src/python/grpcio_reflection/grpc_reflection/v1alpha:grpc_reflection", |
||||||
|
"//src/python/grpcio_tests/tests_aio/unit:_test_base", |
||||||
|
], |
||||||
|
) |
@ -0,0 +1,13 @@ |
|||||||
|
# Copyright 2016 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. |
@ -0,0 +1,193 @@ |
|||||||
|
# Copyright 2016 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 of grpc_reflection.v1alpha.reflection.""" |
||||||
|
|
||||||
|
import logging |
||||||
|
import unittest |
||||||
|
|
||||||
|
import grpc |
||||||
|
from google.protobuf import descriptor_pb2, descriptor_pool |
||||||
|
from grpc.experimental import aio |
||||||
|
|
||||||
|
from grpc_reflection.v1alpha import (reflection, reflection_pb2, |
||||||
|
reflection_pb2_grpc) |
||||||
|
from src.proto.grpc.testing import empty_pb2 |
||||||
|
from src.proto.grpc.testing.proto2 import empty2_extensions_pb2 |
||||||
|
from tests_aio.unit._test_base import AioTestBase |
||||||
|
|
||||||
|
_EMPTY_PROTO_FILE_NAME = 'src/proto/grpc/testing/empty.proto' |
||||||
|
_EMPTY_PROTO_SYMBOL_NAME = 'grpc.testing.Empty' |
||||||
|
_SERVICE_NAMES = ('Angstrom', 'Bohr', 'Curie', 'Dyson', 'Einstein', 'Feynman', |
||||||
|
'Galilei') |
||||||
|
_EMPTY_EXTENSIONS_SYMBOL_NAME = 'grpc.testing.proto2.EmptyWithExtensions' |
||||||
|
_EMPTY_EXTENSIONS_NUMBERS = ( |
||||||
|
124, |
||||||
|
125, |
||||||
|
126, |
||||||
|
127, |
||||||
|
128, |
||||||
|
) |
||||||
|
|
||||||
|
|
||||||
|
def _file_descriptor_to_proto(descriptor): |
||||||
|
proto = descriptor_pb2.FileDescriptorProto() |
||||||
|
descriptor.CopyToProto(proto) |
||||||
|
return proto.SerializeToString() |
||||||
|
|
||||||
|
|
||||||
|
class ReflectionServicerTest(AioTestBase): |
||||||
|
|
||||||
|
async def setUp(self): |
||||||
|
self._server = aio.server() |
||||||
|
reflection.enable_server_reflection(_SERVICE_NAMES, self._server) |
||||||
|
port = self._server.add_insecure_port('[::]:0') |
||||||
|
await self._server.start() |
||||||
|
|
||||||
|
self._channel = aio.insecure_channel('localhost:%d' % port) |
||||||
|
self._stub = reflection_pb2_grpc.ServerReflectionStub(self._channel) |
||||||
|
|
||||||
|
async def tearDown(self): |
||||||
|
await self._server.stop(None) |
||||||
|
await self._channel.close() |
||||||
|
|
||||||
|
async def test_file_by_name(self): |
||||||
|
requests = ( |
||||||
|
reflection_pb2.ServerReflectionRequest( |
||||||
|
file_by_filename=_EMPTY_PROTO_FILE_NAME), |
||||||
|
reflection_pb2.ServerReflectionRequest( |
||||||
|
file_by_filename='i-donut-exist'), |
||||||
|
) |
||||||
|
responses = [] |
||||||
|
async for response in self._stub.ServerReflectionInfo(iter(requests)): |
||||||
|
responses.append(response) |
||||||
|
expected_responses = ( |
||||||
|
reflection_pb2.ServerReflectionResponse( |
||||||
|
valid_host='', |
||||||
|
file_descriptor_response=reflection_pb2.FileDescriptorResponse( |
||||||
|
file_descriptor_proto=( |
||||||
|
_file_descriptor_to_proto(empty_pb2.DESCRIPTOR),))), |
||||||
|
reflection_pb2.ServerReflectionResponse( |
||||||
|
valid_host='', |
||||||
|
error_response=reflection_pb2.ErrorResponse( |
||||||
|
error_code=grpc.StatusCode.NOT_FOUND.value[0], |
||||||
|
error_message=grpc.StatusCode.NOT_FOUND.value[1].encode(), |
||||||
|
)), |
||||||
|
) |
||||||
|
self.assertSequenceEqual(expected_responses, responses) |
||||||
|
|
||||||
|
async def test_file_by_symbol(self): |
||||||
|
requests = ( |
||||||
|
reflection_pb2.ServerReflectionRequest( |
||||||
|
file_containing_symbol=_EMPTY_PROTO_SYMBOL_NAME), |
||||||
|
reflection_pb2.ServerReflectionRequest( |
||||||
|
file_containing_symbol='i.donut.exist.co.uk.org.net.me.name.foo' |
||||||
|
), |
||||||
|
) |
||||||
|
responses = [] |
||||||
|
async for response in self._stub.ServerReflectionInfo(iter(requests)): |
||||||
|
responses.append(response) |
||||||
|
expected_responses = ( |
||||||
|
reflection_pb2.ServerReflectionResponse( |
||||||
|
valid_host='', |
||||||
|
file_descriptor_response=reflection_pb2.FileDescriptorResponse( |
||||||
|
file_descriptor_proto=( |
||||||
|
_file_descriptor_to_proto(empty_pb2.DESCRIPTOR),))), |
||||||
|
reflection_pb2.ServerReflectionResponse( |
||||||
|
valid_host='', |
||||||
|
error_response=reflection_pb2.ErrorResponse( |
||||||
|
error_code=grpc.StatusCode.NOT_FOUND.value[0], |
||||||
|
error_message=grpc.StatusCode.NOT_FOUND.value[1].encode(), |
||||||
|
)), |
||||||
|
) |
||||||
|
self.assertSequenceEqual(expected_responses, responses) |
||||||
|
|
||||||
|
async def test_file_containing_extension(self): |
||||||
|
requests = ( |
||||||
|
reflection_pb2.ServerReflectionRequest( |
||||||
|
file_containing_extension=reflection_pb2.ExtensionRequest( |
||||||
|
containing_type=_EMPTY_EXTENSIONS_SYMBOL_NAME, |
||||||
|
extension_number=125, |
||||||
|
),), |
||||||
|
reflection_pb2.ServerReflectionRequest( |
||||||
|
file_containing_extension=reflection_pb2.ExtensionRequest( |
||||||
|
containing_type='i.donut.exist.co.uk.org.net.me.name.foo', |
||||||
|
extension_number=55, |
||||||
|
),), |
||||||
|
) |
||||||
|
responses = [] |
||||||
|
async for response in self._stub.ServerReflectionInfo(iter(requests)): |
||||||
|
responses.append(response) |
||||||
|
expected_responses = ( |
||||||
|
reflection_pb2.ServerReflectionResponse( |
||||||
|
valid_host='', |
||||||
|
file_descriptor_response=reflection_pb2.FileDescriptorResponse( |
||||||
|
file_descriptor_proto=(_file_descriptor_to_proto( |
||||||
|
empty2_extensions_pb2.DESCRIPTOR),))), |
||||||
|
reflection_pb2.ServerReflectionResponse( |
||||||
|
valid_host='', |
||||||
|
error_response=reflection_pb2.ErrorResponse( |
||||||
|
error_code=grpc.StatusCode.NOT_FOUND.value[0], |
||||||
|
error_message=grpc.StatusCode.NOT_FOUND.value[1].encode(), |
||||||
|
)), |
||||||
|
) |
||||||
|
self.assertSequenceEqual(expected_responses, responses) |
||||||
|
|
||||||
|
async def test_extension_numbers_of_type(self): |
||||||
|
requests = ( |
||||||
|
reflection_pb2.ServerReflectionRequest( |
||||||
|
all_extension_numbers_of_type=_EMPTY_EXTENSIONS_SYMBOL_NAME), |
||||||
|
reflection_pb2.ServerReflectionRequest( |
||||||
|
all_extension_numbers_of_type='i.donut.exist.co.uk.net.name.foo' |
||||||
|
), |
||||||
|
) |
||||||
|
responses = [] |
||||||
|
async for response in self._stub.ServerReflectionInfo(iter(requests)): |
||||||
|
responses.append(response) |
||||||
|
expected_responses = ( |
||||||
|
reflection_pb2.ServerReflectionResponse( |
||||||
|
valid_host='', |
||||||
|
all_extension_numbers_response=reflection_pb2. |
||||||
|
ExtensionNumberResponse( |
||||||
|
base_type_name=_EMPTY_EXTENSIONS_SYMBOL_NAME, |
||||||
|
extension_number=_EMPTY_EXTENSIONS_NUMBERS)), |
||||||
|
reflection_pb2.ServerReflectionResponse( |
||||||
|
valid_host='', |
||||||
|
error_response=reflection_pb2.ErrorResponse( |
||||||
|
error_code=grpc.StatusCode.NOT_FOUND.value[0], |
||||||
|
error_message=grpc.StatusCode.NOT_FOUND.value[1].encode(), |
||||||
|
)), |
||||||
|
) |
||||||
|
self.assertSequenceEqual(expected_responses, responses) |
||||||
|
|
||||||
|
async def test_list_services(self): |
||||||
|
requests = (reflection_pb2.ServerReflectionRequest(list_services='',),) |
||||||
|
responses = [] |
||||||
|
async for response in self._stub.ServerReflectionInfo(iter(requests)): |
||||||
|
responses.append(response) |
||||||
|
expected_responses = (reflection_pb2.ServerReflectionResponse( |
||||||
|
valid_host='', |
||||||
|
list_services_response=reflection_pb2.ListServiceResponse( |
||||||
|
service=tuple( |
||||||
|
reflection_pb2.ServiceResponse(name=name) |
||||||
|
for name in _SERVICE_NAMES))),) |
||||||
|
self.assertSequenceEqual(expected_responses, responses) |
||||||
|
|
||||||
|
def test_reflection_service_name(self): |
||||||
|
self.assertEqual(reflection.SERVICE_NAME, |
||||||
|
'grpc.reflection.v1alpha.ServerReflection') |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
logging.basicConfig(level=logging.DEBUG) |
||||||
|
unittest.main(verbosity=2) |
Loading…
Reference in new issue