From 3fd000148125e5b381086dccaf6276c47e23ef81 Mon Sep 17 00:00:00 2001 From: Richard Belleville Date: Mon, 23 Mar 2020 16:05:41 -0700 Subject: [PATCH 1/4] Add Python xDS example server --- examples/python/xds/README.md | 65 ++++++++++ examples/python/xds/helloworld_pb2.py | 134 +++++++++++++++++++++ examples/python/xds/helloworld_pb2_grpc.py | 46 +++++++ examples/python/xds/server.py | 78 ++++++++++++ 4 files changed, 323 insertions(+) create mode 100644 examples/python/xds/README.md create mode 100644 examples/python/xds/helloworld_pb2.py create mode 100644 examples/python/xds/helloworld_pb2_grpc.py create mode 100644 examples/python/xds/server.py diff --git a/examples/python/xds/README.md b/examples/python/xds/README.md new file mode 100644 index 00000000000..b5c7152f09e --- /dev/null +++ b/examples/python/xds/README.md @@ -0,0 +1,65 @@ +gRPC Hostname Example +===================== + +The hostname example is a Hello World server whose response includes its +hostname. It also supports health and reflection services. This makes it a good +server to test infrastructure, like load balancing. + +The example requires grpc to already be built. You are strongly encouraged +to check out a git release tag, since there will already be a build of gRPC +available. + +### Run the example + +1. Navigate to this directory: + +```sh +cd grpc/examples/python/xds +``` + +2. Run the server + +```sh +python server.py +``` + +3. Verify the Server + +This step is not strictly necessary, but you can use it as a sanity check if +you'd like. If you don't have it, install +[`grpcurl`](https://github.com/fullstorydev/grpcurl/releases). This will allow +you to manually test the service. + +Exercise your server's application-layer service: + +```sh +> grpcurl --plaintext -d '{"name": "you"}' localhost:50051 +{ + "message": "Hello you from rbell.svl.corp.google.com!" +} +``` + +Make sure that all of your server's services are available via reflection: + +```sh +> grpcurl --plaintext localhost:50051 list +grpc.health.v1.Health +grpc.reflection.v1alpha.ServerReflection +helloworld.Greeter +``` + +Make sure that your services are reporting healthy: + +```sh +> grpcurl --plaintext -d '{"service": "helloworld.Greeter"}' localhost:50051 +grpc.health.v1.Health/Check +{ + "status": "SERVING" +} + +> grpcurl --plaintext -d '{"service": ""}' localhost:50051 +grpc.health.v1.Health/Check +{ + "status": "SERVING" +} +``` diff --git a/examples/python/xds/helloworld_pb2.py b/examples/python/xds/helloworld_pb2.py new file mode 100644 index 00000000000..e18ab9acc7a --- /dev/null +++ b/examples/python/xds/helloworld_pb2.py @@ -0,0 +1,134 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: helloworld.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='helloworld.proto', + package='helloworld', + syntax='proto3', + serialized_pb=_b('\n\x10helloworld.proto\x12\nhelloworld\"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x1d\n\nHelloReply\x12\x0f\n\x07message\x18\x01 \x01(\t2I\n\x07Greeter\x12>\n\x08SayHello\x12\x18.helloworld.HelloRequest\x1a\x16.helloworld.HelloReply\"\x00\x42\x36\n\x1bio.grpc.examples.helloworldB\x0fHelloWorldProtoP\x01\xa2\x02\x03HLWb\x06proto3') +) + + + + +_HELLOREQUEST = _descriptor.Descriptor( + name='HelloRequest', + full_name='helloworld.HelloRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='helloworld.HelloRequest.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=32, + serialized_end=60, +) + + +_HELLOREPLY = _descriptor.Descriptor( + name='HelloReply', + full_name='helloworld.HelloReply', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='message', full_name='helloworld.HelloReply.message', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=62, + serialized_end=91, +) + +DESCRIPTOR.message_types_by_name['HelloRequest'] = _HELLOREQUEST +DESCRIPTOR.message_types_by_name['HelloReply'] = _HELLOREPLY +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +HelloRequest = _reflection.GeneratedProtocolMessageType('HelloRequest', (_message.Message,), dict( + DESCRIPTOR = _HELLOREQUEST, + __module__ = 'helloworld_pb2' + # @@protoc_insertion_point(class_scope:helloworld.HelloRequest) + )) +_sym_db.RegisterMessage(HelloRequest) + +HelloReply = _reflection.GeneratedProtocolMessageType('HelloReply', (_message.Message,), dict( + DESCRIPTOR = _HELLOREPLY, + __module__ = 'helloworld_pb2' + # @@protoc_insertion_point(class_scope:helloworld.HelloReply) + )) +_sym_db.RegisterMessage(HelloReply) + + +DESCRIPTOR.has_options = True +DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n\033io.grpc.examples.helloworldB\017HelloWorldProtoP\001\242\002\003HLW')) + +_GREETER = _descriptor.ServiceDescriptor( + name='Greeter', + full_name='helloworld.Greeter', + file=DESCRIPTOR, + index=0, + options=None, + serialized_start=93, + serialized_end=166, + methods=[ + _descriptor.MethodDescriptor( + name='SayHello', + full_name='helloworld.Greeter.SayHello', + index=0, + containing_service=None, + input_type=_HELLOREQUEST, + output_type=_HELLOREPLY, + options=None, + ), +]) +_sym_db.RegisterServiceDescriptor(_GREETER) + +DESCRIPTOR.services_by_name['Greeter'] = _GREETER + +# @@protoc_insertion_point(module_scope) diff --git a/examples/python/xds/helloworld_pb2_grpc.py b/examples/python/xds/helloworld_pb2_grpc.py new file mode 100644 index 00000000000..18e07d16797 --- /dev/null +++ b/examples/python/xds/helloworld_pb2_grpc.py @@ -0,0 +1,46 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +import grpc + +import helloworld_pb2 as helloworld__pb2 + + +class GreeterStub(object): + """The greeting service definition. + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.SayHello = channel.unary_unary( + '/helloworld.Greeter/SayHello', + request_serializer=helloworld__pb2.HelloRequest.SerializeToString, + response_deserializer=helloworld__pb2.HelloReply.FromString, + ) + + +class GreeterServicer(object): + """The greeting service definition. + """ + + def SayHello(self, request, context): + """Sends a greeting + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_GreeterServicer_to_server(servicer, server): + rpc_method_handlers = { + 'SayHello': grpc.unary_unary_rpc_method_handler( + servicer.SayHello, + request_deserializer=helloworld__pb2.HelloRequest.FromString, + response_serializer=helloworld__pb2.HelloReply.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'helloworld.Greeter', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) diff --git a/examples/python/xds/server.py b/examples/python/xds/server.py new file mode 100644 index 00000000000..2b459eb39f3 --- /dev/null +++ b/examples/python/xds/server.py @@ -0,0 +1,78 @@ +# Copyright 2020 The 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 Python implementation of the GRPC helloworld.Greeter server.""" + +from concurrent import futures +import argparse +import logging +import socket + +import grpc + +import helloworld_pb2 +import helloworld_pb2_grpc + +from grpc_reflection.v1alpha import reflection +from grpc_health.v1 import health +from grpc_health.v1 import health_pb2 +from grpc_health.v1 import health_pb2_grpc + +_DESCRIPTION = "A general purpose dummy server." + + +class Greeter(helloworld_pb2_grpc.GreeterServicer): + + def __init__(self, hostname): + self._hostname = hostname if hostname else socket.gethostname() + + def SayHello(self, request, context): + return helloworld_pb2.HelloReply( + message=f"Hello {request.name} from {self._hostname}!") + + +def serve(port, hostname): + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(hostname), server) + health_servicer = health.HealthServicer( + experimental_non_blocking=True, + experimental_thread_pool=futures.ThreadPoolExecutor(max_workers=4)) + health_pb2_grpc.add_HealthServicer_to_server(health_servicer, server) + services = tuple( + service.full_name + for service in helloworld_pb2.DESCRIPTOR.services_by_name.values()) + ( + reflection.SERVICE_NAME, health.SERVICE_NAME) + reflection.enable_server_reflection(services, server) + server.add_insecure_port(f"[::]:{port}") + server.start() + overall_server_health = "" + for service in services + (overall_server_health,): + health_servicer.set(service, health_pb2.HealthCheckResponse.SERVING) + server.wait_for_termination() + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=_DESCRIPTION) + parser.add_argument("port", + default=50051, + type=int, + nargs="?", + help="The port on which to listen.") + parser.add_argument("hostname", + type=str, + default=None, + nargs="?", + help="The name clients will see in responses.") + args = parser.parse_args() + logging.basicConfig() + serve(args.port, args.hostname) From 0c28a9beaf78e96f280a4a367bb16202ac71af86 Mon Sep 17 00:00:00 2001 From: Richard Belleville Date: Tue, 24 Mar 2020 14:11:17 -0700 Subject: [PATCH 2/4] Specify the Python version --- examples/python/xds/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/python/xds/README.md b/examples/python/xds/README.md index b5c7152f09e..5dacfc4371e 100644 --- a/examples/python/xds/README.md +++ b/examples/python/xds/README.md @@ -20,6 +20,9 @@ cd grpc/examples/python/xds 2. Run the server ```sh +virtualenv venv -p python3 +source venv/bin/activate +pip install grpcio protobuf python server.py ``` From 571a564f5111c13c812bbfce397c5793131c8da4 Mon Sep 17 00:00:00 2001 From: Richard Belleville Date: Tue, 24 Mar 2020 14:23:25 -0700 Subject: [PATCH 3/4] Add type annotations and comments --- examples/python/xds/README.md | 2 +- examples/python/xds/server.py | 24 ++++++++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/examples/python/xds/README.md b/examples/python/xds/README.md index 5dacfc4371e..51bfe715583 100644 --- a/examples/python/xds/README.md +++ b/examples/python/xds/README.md @@ -22,7 +22,7 @@ cd grpc/examples/python/xds ```sh virtualenv venv -p python3 source venv/bin/activate -pip install grpcio protobuf +pip install grpcio protobuf grpcio-reflection grpcio-health-checking python server.py ``` diff --git a/examples/python/xds/server.py b/examples/python/xds/server.py index 2b459eb39f3..3cff567667f 100644 --- a/examples/python/xds/server.py +++ b/examples/python/xds/server.py @@ -16,6 +16,7 @@ from concurrent import futures import argparse import logging +import multiprocessing import socket import grpc @@ -33,31 +34,46 @@ _DESCRIPTION = "A general purpose dummy server." class Greeter(helloworld_pb2_grpc.GreeterServicer): - def __init__(self, hostname): + def __init__(self, hostname: str): self._hostname = hostname if hostname else socket.gethostname() - def SayHello(self, request, context): + def SayHello(self, request: helloworld_pb2.HelloRequest, + context: grpc.ServicerContext) -> helloworld_pb2.HelloReply: return helloworld_pb2.HelloReply( message=f"Hello {request.name} from {self._hostname}!") def serve(port, hostname): - server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + server = grpc.server( + futures.ThreadPoolExecutor(max_workers=multiprocessing.cpu_count())) + + # Add the application servicer to the server. helloworld_pb2_grpc.add_GreeterServicer_to_server(Greeter(hostname), server) + + # Create a health check servicer. We use the non-blocking implementation + # to avoid thread starvation. health_servicer = health.HealthServicer( experimental_non_blocking=True, - experimental_thread_pool=futures.ThreadPoolExecutor(max_workers=4)) + experimental_thread_pool=futures.ThreadPoolExecutor(max_workers=1)) health_pb2_grpc.add_HealthServicer_to_server(health_servicer, server) + + # Create a tuple of all of the services we want to export via reflection. services = tuple( service.full_name for service in helloworld_pb2.DESCRIPTOR.services_by_name.values()) + ( reflection.SERVICE_NAME, health.SERVICE_NAME) + + # Add the reflection service to the server. reflection.enable_server_reflection(services, server) server.add_insecure_port(f"[::]:{port}") server.start() + + # Mark all services as healthy. overall_server_health = "" for service in services + (overall_server_health,): health_servicer.set(service, health_pb2.HealthCheckResponse.SERVING) + + # Park the main application thread. server.wait_for_termination() From f64e7af7cd70c2c260151445d7f26add74ff331b Mon Sep 17 00:00:00 2001 From: Richard Belleville Date: Wed, 25 Mar 2020 10:35:45 -0700 Subject: [PATCH 4/4] Add requirements.txt --- examples/python/xds/README.md | 2 +- examples/python/xds/requirements.txt | 5 +++++ examples/python/xds/server.py | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 examples/python/xds/requirements.txt diff --git a/examples/python/xds/README.md b/examples/python/xds/README.md index 51bfe715583..bf7809e814d 100644 --- a/examples/python/xds/README.md +++ b/examples/python/xds/README.md @@ -22,7 +22,7 @@ cd grpc/examples/python/xds ```sh virtualenv venv -p python3 source venv/bin/activate -pip install grpcio protobuf grpcio-reflection grpcio-health-checking +pip install -r requirements.txt python server.py ``` diff --git a/examples/python/xds/requirements.txt b/examples/python/xds/requirements.txt new file mode 100644 index 00000000000..a7ac2e7c8b3 --- /dev/null +++ b/examples/python/xds/requirements.txt @@ -0,0 +1,5 @@ +grpcio>=1.28.0 +protobuf +grpcio-reflection +grpcio-health-checking + diff --git a/examples/python/xds/server.py b/examples/python/xds/server.py index 3cff567667f..75043857f3b 100644 --- a/examples/python/xds/server.py +++ b/examples/python/xds/server.py @@ -43,7 +43,7 @@ class Greeter(helloworld_pb2_grpc.GreeterServicer): message=f"Hello {request.name} from {self._hostname}!") -def serve(port, hostname): +def serve(port: int, hostname: str): server = grpc.server( futures.ThreadPoolExecutor(max_workers=multiprocessing.cpu_count()))