From 682e8bd035cf750766b9785c8d2f59c0297f453e Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Tue, 2 Jun 2020 16:32:16 +0200 Subject: [PATCH 1/4] add C# xds example --- examples/csharp/Xds/Greeter.sln | 34 +++++++++ examples/csharp/Xds/Greeter/Greeter.csproj | 19 +++++ .../Xds/GreeterClient/GreeterClient.csproj | 12 ++++ examples/csharp/Xds/GreeterClient/Program.cs | 40 +++++++++++ .../Xds/GreeterServer/GreeterServer.csproj | 12 ++++ examples/csharp/Xds/GreeterServer/Program.cs | 71 +++++++++++++++++++ examples/csharp/Xds/README.md | 29 ++++++++ 7 files changed, 217 insertions(+) create mode 100644 examples/csharp/Xds/Greeter.sln create mode 100644 examples/csharp/Xds/Greeter/Greeter.csproj create mode 100644 examples/csharp/Xds/GreeterClient/GreeterClient.csproj create mode 100644 examples/csharp/Xds/GreeterClient/Program.cs create mode 100644 examples/csharp/Xds/GreeterServer/GreeterServer.csproj create mode 100644 examples/csharp/Xds/GreeterServer/Program.cs create mode 100644 examples/csharp/Xds/README.md diff --git a/examples/csharp/Xds/Greeter.sln b/examples/csharp/Xds/Greeter.sln new file mode 100644 index 00000000000..a5ba98d0bed --- /dev/null +++ b/examples/csharp/Xds/Greeter.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26228.4 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Greeter", "Greeter\Greeter.csproj", "{13B6DFC8-F5F6-4CC2-99DF-57A7CF042033}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GreeterClient", "GreeterClient\GreeterClient.csproj", "{B754FB02-D501-4308-8B89-33AB7119C80D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GreeterServer", "GreeterServer\GreeterServer.csproj", "{DDBFF994-E076-43AD-B18D-049DFC1B670C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {13B6DFC8-F5F6-4CC2-99DF-57A7CF042033}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {13B6DFC8-F5F6-4CC2-99DF-57A7CF042033}.Debug|Any CPU.Build.0 = Debug|Any CPU + {13B6DFC8-F5F6-4CC2-99DF-57A7CF042033}.Release|Any CPU.ActiveCfg = Release|Any CPU + {13B6DFC8-F5F6-4CC2-99DF-57A7CF042033}.Release|Any CPU.Build.0 = Release|Any CPU + {B754FB02-D501-4308-8B89-33AB7119C80D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B754FB02-D501-4308-8B89-33AB7119C80D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B754FB02-D501-4308-8B89-33AB7119C80D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B754FB02-D501-4308-8B89-33AB7119C80D}.Release|Any CPU.Build.0 = Release|Any CPU + {DDBFF994-E076-43AD-B18D-049DFC1B670C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DDBFF994-E076-43AD-B18D-049DFC1B670C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DDBFF994-E076-43AD-B18D-049DFC1B670C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DDBFF994-E076-43AD-B18D-049DFC1B670C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/examples/csharp/Xds/Greeter/Greeter.csproj b/examples/csharp/Xds/Greeter/Greeter.csproj new file mode 100644 index 00000000000..232cf560edf --- /dev/null +++ b/examples/csharp/Xds/Greeter/Greeter.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.0 + + + + + + + + + + + + + + + diff --git a/examples/csharp/Xds/GreeterClient/GreeterClient.csproj b/examples/csharp/Xds/GreeterClient/GreeterClient.csproj new file mode 100644 index 00000000000..ac10d854972 --- /dev/null +++ b/examples/csharp/Xds/GreeterClient/GreeterClient.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp2.1 + Exe + + + + + + + diff --git a/examples/csharp/Xds/GreeterClient/Program.cs b/examples/csharp/Xds/GreeterClient/Program.cs new file mode 100644 index 00000000000..0b2b002b1fd --- /dev/null +++ b/examples/csharp/Xds/GreeterClient/Program.cs @@ -0,0 +1,40 @@ +// Copyright 2015 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. + +using System; +using Grpc.Core; +using Helloworld; + +namespace GreeterClient +{ + class Program + { + public static void Main(string[] args) + { + // TODO: specify server address.. + + Channel channel = new Channel("127.0.0.1:50051", ChannelCredentials.Insecure); + + var client = new Greeter.GreeterClient(channel); + String user = "you"; + + var reply = client.SayHello(new HelloRequest { Name = user }); + Console.WriteLine("Greeter client received: " + reply.Message); + + channel.ShutdownAsync().Wait(); + Console.WriteLine("Press any key to exit..."); + Console.ReadKey(); + } + } +} diff --git a/examples/csharp/Xds/GreeterServer/GreeterServer.csproj b/examples/csharp/Xds/GreeterServer/GreeterServer.csproj new file mode 100644 index 00000000000..ac10d854972 --- /dev/null +++ b/examples/csharp/Xds/GreeterServer/GreeterServer.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp2.1 + Exe + + + + + + + diff --git a/examples/csharp/Xds/GreeterServer/Program.cs b/examples/csharp/Xds/GreeterServer/Program.cs new file mode 100644 index 00000000000..030dd61c8b2 --- /dev/null +++ b/examples/csharp/Xds/GreeterServer/Program.cs @@ -0,0 +1,71 @@ +// 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. + +using System; +using System.Net; +using System.Threading.Tasks; +using Grpc.Core; +using Grpc.HealthCheck; +using Helloworld; +using Grpc.Health; +using Grpc.Health.V1; +using Grpc.Reflection; +using Grpc.Reflection.V1Alpha; + +namespace GreeterServer +{ + class GreeterImpl : Greeter.GreeterBase + { + // Server side handler of the SayHello RPC + public override Task SayHello(HelloRequest request, ServerCallContext context) + { + String hostName = Dns.GetHostName(); + return Task.FromResult(new HelloReply { Message = $"Hello {request.Name} from {hostName}!"}); + } + } + + class Program + { + const int Port = 50051; + + public static void Main(string[] args) + { + var serviceDescriptors = new [] {Greeter.Descriptor, Health.Descriptor, ServerReflection.Descriptor}; + var greeterImpl = new GreeterImpl(); + var healthServiceImpl = new HealthServiceImpl(); + var reflectionImpl = new ReflectionServiceImpl(serviceDescriptors); + + Server server = new Server + { + Services = { Greeter.BindService(greeterImpl), Health.BindService(healthServiceImpl), ServerReflection.BindService(reflectionImpl) }, + Ports = { new ServerPort("localhost", Port, ServerCredentials.Insecure) } + }; + server.Start(); + + // Mark all services as healthy. + foreach (var serviceDescriptor in serviceDescriptors) + { + healthServiceImpl.SetStatus(serviceDescriptor.FullName, HealthCheckResponse.Types.ServingStatus.Serving); + } + // Mark overall server status as healthy. + healthServiceImpl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Serving); + + Console.WriteLine("Greeter server listening on port " + Port); + Console.WriteLine("Press any key to stop the server..."); + Console.ReadKey(); + + server.ShutdownAsync().Wait(); + } + } +} diff --git a/examples/csharp/Xds/README.md b/examples/csharp/Xds/README.md new file mode 100644 index 00000000000..7c09f307896 --- /dev/null +++ b/examples/csharp/Xds/README.md @@ -0,0 +1,29 @@ +gRPC Hostname example (C#) +======================== + +BACKGROUND +------------- +This is a version of the helloworld example with a 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. + +PREREQUISITES +------------- + +- The [.NET Core SDK 2.1+](https://www.microsoft.com/net/core) + +You can also build the solution `Greeter.sln` using Visual Studio 2019, +but it's not a requirement. + +BUILD AND RUN +------------- + +- Build and run the server + + ``` + > dotnet run -p GreeterServer + ``` + +- Build and run the client + + ``` + > dotnet run -p GreeterClient + ``` From 8049266e642e3a6a0782ab9eee5fee439f0acfcc Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Tue, 2 Jun 2020 17:31:56 +0200 Subject: [PATCH 2/4] improvements --- examples/csharp/Xds/GreeterServer/Program.cs | 6 +- examples/csharp/Xds/README.md | 90 +++++++++++++++++--- 2 files changed, 83 insertions(+), 13 deletions(-) diff --git a/examples/csharp/Xds/GreeterServer/Program.cs b/examples/csharp/Xds/GreeterServer/Program.cs index 030dd61c8b2..e7a2965739f 100644 --- a/examples/csharp/Xds/GreeterServer/Program.cs +++ b/examples/csharp/Xds/GreeterServer/Program.cs @@ -30,14 +30,14 @@ namespace GreeterServer // Server side handler of the SayHello RPC public override Task SayHello(HelloRequest request, ServerCallContext context) { - String hostName = Dns.GetHostName(); + String hostName = Dns.GetHostName(); // TODO: make hostname configurable return Task.FromResult(new HelloReply { Message = $"Hello {request.Name} from {hostName}!"}); } } class Program { - const int Port = 50051; + const int Port = 50051; // TODO: make port configurable public static void Main(string[] args) { @@ -49,7 +49,7 @@ namespace GreeterServer Server server = new Server { Services = { Greeter.BindService(greeterImpl), Health.BindService(healthServiceImpl), ServerReflection.BindService(reflectionImpl) }, - Ports = { new ServerPort("localhost", Port, ServerCredentials.Insecure) } + Ports = { new ServerPort("localhost", Port, ServerCredentials.Insecure) } // TODO: don't listen on just localhost }; server.Start(); diff --git a/examples/csharp/Xds/README.md b/examples/csharp/Xds/README.md index 7c09f307896..9b3f939dcf0 100644 --- a/examples/csharp/Xds/README.md +++ b/examples/csharp/Xds/README.md @@ -3,7 +3,7 @@ gRPC Hostname example (C#) BACKGROUND ------------- -This is a version of the helloworld example with a 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. +This is a version of the helloworld example with a server whose response includes its hostname. It also supports health and reflection services. This makes it a good server to test infrastructure, such as XDS load balancing. PREREQUISITES ------------- @@ -13,17 +13,87 @@ PREREQUISITES You can also build the solution `Greeter.sln` using Visual Studio 2019, but it's not a requirement. -BUILD AND RUN +RUN THE EXAMPLE ------------- -- Build and run the server +First, build and run the server, then verify the server is running and +check the server is behaving as expected (more on that below). - ``` - > dotnet run -p GreeterServer - ``` +``` +cd GreeterServer +dotnet run +``` -- Build and run the client +After configuring your xDS server to track the gRPC server we just started, +create a bootstrap file as desribed in [gRFC A27](https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md): - ``` - > dotnet run -p GreeterClient - ``` +``` +{ + xds_servers": [ + { + "server_uri": , + "channel_creds": [ + { + "type": , + "config": + } + ] + } + ], + "node": +} +``` + +Then point the `GRPC_XDS_BOOTSTRAP` environment variable at the bootstrap file: + +``` +export GRPC_XDS_BOOTSTRAP=/etc/xds-bootstrap.json +``` + +Finally, run your client: + +``` +cd GreeterClient +dotnet run -- xds-experimental:///my-backend +``` + +VERIFYING THE SERVER +------------- + +`grpcurl` can be used to test your server. 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 jtatt.muc.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" +} +``` From f5ecc0adc89c388a1a0614fe9b7e16eb1427a389 Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Tue, 2 Jun 2020 17:58:17 +0200 Subject: [PATCH 3/4] add commandline parsing --- examples/csharp/Xds/Greeter/Greeter.csproj | 1 + examples/csharp/Xds/GreeterClient/Program.cs | 15 ++++++++++++-- examples/csharp/Xds/GreeterServer/Program.cs | 21 ++++++++++++++++---- examples/csharp/Xds/README.md | 2 +- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/examples/csharp/Xds/Greeter/Greeter.csproj b/examples/csharp/Xds/Greeter/Greeter.csproj index 232cf560edf..9351cbcb53a 100644 --- a/examples/csharp/Xds/Greeter/Greeter.csproj +++ b/examples/csharp/Xds/Greeter/Greeter.csproj @@ -9,6 +9,7 @@ + diff --git a/examples/csharp/Xds/GreeterClient/Program.cs b/examples/csharp/Xds/GreeterClient/Program.cs index 0b2b002b1fd..c6820a8f8d7 100644 --- a/examples/csharp/Xds/GreeterClient/Program.cs +++ b/examples/csharp/Xds/GreeterClient/Program.cs @@ -15,16 +15,27 @@ using System; using Grpc.Core; using Helloworld; +using CommandLine; namespace GreeterClient { class Program { + private class Options + { + [Option("server", Default = "localhost:50051", HelpText = "The address of the server")] + public string Server { get; set; } + } + public static void Main(string[] args) { - // TODO: specify server address.. + Parser.Default.ParseArguments(args) + .WithParsed(options => RunClient(options)); + } - Channel channel = new Channel("127.0.0.1:50051", ChannelCredentials.Insecure); + private static void RunClient(Options options) + { + Channel channel = new Channel(options.Server, ChannelCredentials.Insecure); var client = new Greeter.GreeterClient(channel); String user = "you"; diff --git a/examples/csharp/Xds/GreeterServer/Program.cs b/examples/csharp/Xds/GreeterServer/Program.cs index e7a2965739f..4c3e46158b3 100644 --- a/examples/csharp/Xds/GreeterServer/Program.cs +++ b/examples/csharp/Xds/GreeterServer/Program.cs @@ -22,6 +22,7 @@ using Grpc.Health; using Grpc.Health.V1; using Grpc.Reflection; using Grpc.Reflection.V1Alpha; +using CommandLine; namespace GreeterServer { @@ -30,16 +31,28 @@ namespace GreeterServer // Server side handler of the SayHello RPC public override Task SayHello(HelloRequest request, ServerCallContext context) { - String hostName = Dns.GetHostName(); // TODO: make hostname configurable + String hostName = Dns.GetHostName(); return Task.FromResult(new HelloReply { Message = $"Hello {request.Name} from {hostName}!"}); } } class Program { - const int Port = 50051; // TODO: make port configurable + class Options + { + [Option("port", Default = 50051, HelpText = "The port to listen on.")] + public int Port { get; set; } + + // TODO: make hostname configurable + } public static void Main(string[] args) + { + Parser.Default.ParseArguments(args) + .WithParsed(options => RunServer(options)); + } + + private static void RunServer(Options options) { var serviceDescriptors = new [] {Greeter.Descriptor, Health.Descriptor, ServerReflection.Descriptor}; var greeterImpl = new GreeterImpl(); @@ -49,7 +62,7 @@ namespace GreeterServer Server server = new Server { Services = { Greeter.BindService(greeterImpl), Health.BindService(healthServiceImpl), ServerReflection.BindService(reflectionImpl) }, - Ports = { new ServerPort("localhost", Port, ServerCredentials.Insecure) } // TODO: don't listen on just localhost + Ports = { new ServerPort("[::]", options.Port, ServerCredentials.Insecure) } }; server.Start(); @@ -61,7 +74,7 @@ namespace GreeterServer // Mark overall server status as healthy. healthServiceImpl.SetStatus("", HealthCheckResponse.Types.ServingStatus.Serving); - Console.WriteLine("Greeter server listening on port " + Port); + Console.WriteLine("Greeter server listening on port " + options.Port); Console.WriteLine("Press any key to stop the server..."); Console.ReadKey(); diff --git a/examples/csharp/Xds/README.md b/examples/csharp/Xds/README.md index 9b3f939dcf0..ce0aa9f7443 100644 --- a/examples/csharp/Xds/README.md +++ b/examples/csharp/Xds/README.md @@ -54,7 +54,7 @@ Finally, run your client: ``` cd GreeterClient -dotnet run -- xds-experimental:///my-backend +dotnet run --server xds-experimental:///my-backend ``` VERIFYING THE SERVER From 171eeb552ab268a3129fa27c927c22fb9d522deb Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Tue, 2 Jun 2020 21:08:37 +0200 Subject: [PATCH 4/4] make hostname configurable --- examples/csharp/Xds/GreeterServer/Program.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/examples/csharp/Xds/GreeterServer/Program.cs b/examples/csharp/Xds/GreeterServer/Program.cs index 4c3e46158b3..e10b1af9f07 100644 --- a/examples/csharp/Xds/GreeterServer/Program.cs +++ b/examples/csharp/Xds/GreeterServer/Program.cs @@ -28,11 +28,17 @@ namespace GreeterServer { class GreeterImpl : Greeter.GreeterBase { + private string hostname; + + public GreeterImpl(string hostname) + { + this.hostname = hostname; + } + // Server side handler of the SayHello RPC public override Task SayHello(HelloRequest request, ServerCallContext context) { - String hostName = Dns.GetHostName(); - return Task.FromResult(new HelloReply { Message = $"Hello {request.Name} from {hostName}!"}); + return Task.FromResult(new HelloReply { Message = $"Hello {request.Name} from {hostname}!"}); } } @@ -43,7 +49,8 @@ namespace GreeterServer [Option("port", Default = 50051, HelpText = "The port to listen on.")] public int Port { get; set; } - // TODO: make hostname configurable + [Option("hostname", Required = false, HelpText = "The name clients will see in responses. If not specified, machine's hostname will obtain automatically.")] + public string Hostname { get; set; } } public static void Main(string[] args) @@ -54,8 +61,10 @@ namespace GreeterServer private static void RunServer(Options options) { + var hostName = options.Hostname ?? Dns.GetHostName(); + var serviceDescriptors = new [] {Greeter.Descriptor, Health.Descriptor, ServerReflection.Descriptor}; - var greeterImpl = new GreeterImpl(); + var greeterImpl = new GreeterImpl(hostName); var healthServiceImpl = new HealthServiceImpl(); var reflectionImpl = new ReflectionServiceImpl(serviceDescriptors);