diff --git a/examples/cpp/xds/BUILD b/examples/cpp/xds/BUILD new file mode 100644 index 00000000000..6657b1cbbf6 --- /dev/null +++ b/examples/cpp/xds/BUILD @@ -0,0 +1,41 @@ +# Copyright 2023 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. + +licenses(["notice"]) + +cc_binary( + name = "xds_greeter_client", + srcs = ["xds_greeter_client.cc"], + defines = ["BAZEL_BUILD"], + deps = [ + "//:grpc++", + "//examples/protos:helloworld_cc_grpc", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/flags:parse", + ], +) + +cc_binary( + name = "xds_greeter_server", + srcs = ["xds_greeter_server.cc"], + defines = ["BAZEL_BUILD"], + deps = [ + "//:grpc++", + "//:grpc++_reflection", + "//:grpcpp_admin", + "//examples/protos:helloworld_cc_grpc", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/flags:parse", + ], +) diff --git a/examples/cpp/xds/README.md b/examples/cpp/xds/README.md new file mode 100644 index 00000000000..aad6c5c0cb4 --- /dev/null +++ b/examples/cpp/xds/README.md @@ -0,0 +1,37 @@ +# gRPC C++ xDS Hello World Example + +This xDS example builds on the [Hello World Example](https://github.com/grpc/grpc/tree/master/examples/cpp/helloworld) and changes the gRPC client and server to accept configuration from an xDS control plane. + +## Configuration + +The client takes two command-line arguments - +* target - By default, the client tries to connect to the xDS "xds:///helloworld:50051" and gRPC would use xDS to resolve this target and connect to the server backend. This can be overriden to change the target. +* secure - Bool value, defaults to true. When this is set, [XdsCredentials](https://github.com/grpc/proposal/blob/master/A29-xds-tls-security.md) will be used with a fallback on `InsecureChannelCredentials`. If unset, `InsecureChannelCredentials` will be used. + +The server takes three command-line arguments - +* port - Port on which the Hello World service is run. Defaults to 50051. +* mantenance_port - If secure mode is used (see below), the [Admin](https://github.com/grpc/proposal/blob/master/A38-admin-interface-api.md) service is exposed on this port. If secure mode is not used, `maintenance_port` is unused, and the Admin service is just exposed on `port`. Defaults to 50052. +* secure - Bool value, defaults to true. When this is set, [XdsServerCredentials](https://github.com/grpc/proposal/blob/master/A29-xds-tls-security.md) will be used with a fallback on `InsecureServerCredentials`. If unset, `InsecureServerCredentials` will be used. + +## Running the example + +Currently, this example and some of the gRPC xDS libraries that it depends on only builds with bazel. CMake support will be introduced in the future. + +To use XDS, you should first deploy the XDS management server in your deployment environment and know its name. You need to set the `GRPC_XDS_BOOTSTRAP` environment variable to point to the gRPC XDS bootstrap file (see [gRFC A27](https://github.com/grpc/proposal/blob/master/A27-xds-global-load-balancing.md#xdsclient-and-bootstrap-file) for the bootstrap format). This is needed by both client and server. + +Please view [GCP instructions](https://cloud.google.com/traffic-director/docs/security-proxyless-setup) as an example. + +To run the server - + +``` +$ export GRPC_XDS_BOOTSTRAP=/path/to/bootstrap.json +$ tools/bazel run examples/cpp/xds:greeter_server +``` + +To run the client - + +``` +$ export GRPC_XDS_BOOTSTRAP=/path/to/bootstrap.json +$ tools/bazel run examples/cpp/xds:greeter_client +``` + diff --git a/examples/cpp/xds/xds_greeter_client.cc b/examples/cpp/xds/xds_greeter_client.cc new file mode 100644 index 00000000000..4bf47e1057c --- /dev/null +++ b/examples/cpp/xds/xds_greeter_client.cc @@ -0,0 +1,109 @@ +/* + * + * Copyright 2023 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. + * + */ + +#include +#include +#include +#include +#include + +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" + +#include + +#ifdef BAZEL_BUILD +#include "examples/protos/helloworld.grpc.pb.h" +#else +#include "helloworld.grpc.pb.h" +#endif + +ABSL_FLAG(std::string, target, "xds:///helloworld:50051", "Target string"); +ABSL_FLAG(bool, secure, true, "Secure mode"); + +using grpc::Channel; +using grpc::ClientContext; +using grpc::Status; +using helloworld::Greeter; +using helloworld::HelloReply; +using helloworld::HelloRequest; + +class GreeterClient { + public: + GreeterClient(std::shared_ptr channel) + : stub_(Greeter::NewStub(channel)) {} + + // Assembles the client's payload, sends it and presents the response back + // from the server. + std::string SayHello(const std::string& user) { + // Data we are sending to the server. + HelloRequest request; + request.set_name(user); + + // Container for the data we expect from the server. + HelloReply reply; + + // Context for the client. It could be used to convey extra information to + // the server and/or tweak certain RPC behaviors. + ClientContext context; + + // The actual RPC. + std::mutex mu; + std::condition_variable cv; + bool done = false; + Status status; + stub_->async()->SayHello(&context, &request, &reply, + [&mu, &cv, &done, &status](Status s) { + status = std::move(s); + std::lock_guard lock(mu); + done = true; + cv.notify_one(); + }); + + std::unique_lock lock(mu); + while (!done) { + cv.wait(lock); + } + + // Act upon its status. + if (status.ok()) { + return reply.message(); + } else { + std::cout << status.error_code() << ": " << status.error_message() + << std::endl; + return "RPC failed"; + } + } + + private: + std::unique_ptr stub_; +}; + +int main(int argc, char** argv) { + absl::ParseCommandLine(argc, argv); + GreeterClient greeter(grpc::CreateChannel( + absl::GetFlag(FLAGS_target), + absl::GetFlag(FLAGS_secure) + ? grpc::XdsCredentials(grpc::InsecureChannelCredentials()) + : grpc::InsecureChannelCredentials())); + std::string user("world"); + std::string reply = greeter.SayHello(user); + std::cout << "Greeter received: " << reply << std::endl; + + return 0; +} diff --git a/examples/cpp/xds/xds_greeter_server.cc b/examples/cpp/xds/xds_greeter_server.cc new file mode 100644 index 00000000000..bc4fe66c0f5 --- /dev/null +++ b/examples/cpp/xds/xds_greeter_server.cc @@ -0,0 +1,113 @@ +/* + * + * Copyright 2023 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. + * + */ + +#include +#include +#include + +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "absl/strings/str_cat.h" + +#include +#include +#include +#include +#include + +#ifdef BAZEL_BUILD +#include "examples/protos/helloworld.grpc.pb.h" +#else +#include "helloworld.grpc.pb.h" +#endif + +ABSL_FLAG(int32_t, port, 50051, "Server port for service."); +ABSL_FLAG(int32_t, maintenance_port, 50052, + "Server port for maintenance if --secure is used."); +ABSL_FLAG(bool, secure, true, "Secure mode"); + +using grpc::CallbackServerContext; +using grpc::Server; +using grpc::ServerBuilder; +using grpc::ServerUnaryReactor; +using grpc::Status; +using helloworld::Greeter; +using helloworld::HelloReply; +using helloworld::HelloRequest; + +// Logic and data behind the server's behavior. +class GreeterServiceImpl final : public Greeter::CallbackService { + ServerUnaryReactor* SayHello(CallbackServerContext* context, + const HelloRequest* request, + HelloReply* reply) override { + std::string prefix("Hello "); + reply->set_message(prefix + request->name()); + + ServerUnaryReactor* reactor = context->DefaultReactor(); + reactor->Finish(Status::OK); + return reactor; + } +}; + +void RunServer() { + grpc::EnableDefaultHealthCheckService(true); + grpc::reflection::InitProtoReflectionServerBuilderPlugin(); + int port = absl::GetFlag(FLAGS_port); + int maintenance_port = absl::GetFlag(FLAGS_maintenance_port); + grpc::XdsServerBuilder xds_builder; + ServerBuilder builder; + std::unique_ptr xds_enabled_server; + std::unique_ptr server; + GreeterServiceImpl service; + // Register "service" as the instance through which we'll communicate with + // clients. In this case it corresponds to an *synchronous* service. + xds_builder.RegisterService(&service); + if (absl::GetFlag(FLAGS_secure)) { + // Listen on the given address with XdsServerCredentials and a fallback of + // InsecureServerCredentials + xds_builder.AddListeningPort( + absl::StrCat("0.0.0.0:", port), + grpc::XdsServerCredentials(grpc::InsecureServerCredentials())); + xds_enabled_server = xds_builder.BuildAndStart(); + gpr_log(GPR_INFO, "Server starting on 0.0.0.0:%d", port); + grpc::AddAdminServices(&builder); + // For the maintenance server, do not use any authentication mechanism. + builder.AddListeningPort(absl::StrCat("0.0.0.0:", maintenance_port), + grpc::InsecureServerCredentials()); + server = builder.BuildAndStart(); + gpr_log(GPR_INFO, "Maintenance server listening on 0.0.0.0:%d", + maintenance_port); + } else { + grpc::AddAdminServices(&xds_builder); + // Listen on the given address without any authentication mechanism. + builder.AddListeningPort(absl::StrCat("0.0.0.0:", port), + grpc::InsecureServerCredentials()); + server = xds_builder.BuildAndStart(); + gpr_log(GPR_INFO, "Server listening on 0.0.0.0:%d", port); + } + + // Wait for the server to shutdown. Note that some other thread must be + // responsible for shutting down the server for this call to ever return. + server->Wait(); +} + +int main(int argc, char** argv) { + absl::ParseCommandLine(argc, argv); + RunServer(); + return 0; +}