From 1ed0b8e3d72abcc4788e89cab4caa4e8c0083985 Mon Sep 17 00:00:00 2001
From: Makarand Dharmapurikar <makarandd@google.com>
Date: Wed, 14 Sep 2016 15:01:16 -0700
Subject: [PATCH] Add interop test for Cacheable Unary Calls

modified interop test spec doc
added CacheableUnaryCall to test.proto
modified server and client implmenentations to support new method
---
 doc/interop-test-descriptions.md   | 21 +++++++++++++++
 src/proto/grpc/testing/test.proto  |  5 ++++
 test/cpp/interop/client.cc         |  4 +++
 test/cpp/interop/interop_client.cc | 43 ++++++++++++++++++++++++++++++
 test/cpp/interop/interop_client.h  |  1 +
 test/cpp/interop/interop_server.cc | 11 ++++++++
 6 files changed, 85 insertions(+)

diff --git a/doc/interop-test-descriptions.md b/doc/interop-test-descriptions.md
index 1e04966380..5b3ad2335c 100644
--- a/doc/interop-test-descriptions.md
+++ b/doc/interop-test-descriptions.md
@@ -60,6 +60,27 @@ Client asserts:
 *It may be possible to use UnaryCall instead of EmptyCall, but it is harder to
 ensure that the proto serialized to zero bytes.*
 
+### cacheable_unary
+
+This test verifies that gRPC requests marked as cacheable use GET verb instead
+of POST, and that server sets appropriate cache control headers for the response
+to be cached by a proxy. This interop test requires that the server is behind
+a caching proxy. It is NOT expected to pass if client is accessing the server
+directly.
+
+Server features:
+* [CacheableUnaryCall][]
+
+Procedure:
+ 1. Client calls CacheableUnaryCall twice, and compares the two responses.
+ The server generates a unique response (timestamp) for each request.
+ If the second response was delivered from cache, then both responses will
+ be the same.
+
+Client asserts:
+* call was successful
+* responses are the same.
+
 ### large_unary
 
 This test verifies unary calls succeed in sending messages, and touches on flow
diff --git a/src/proto/grpc/testing/test.proto b/src/proto/grpc/testing/test.proto
index 84369db4b8..b52c4cbad6 100644
--- a/src/proto/grpc/testing/test.proto
+++ b/src/proto/grpc/testing/test.proto
@@ -47,6 +47,11 @@ service TestService {
   // One request followed by one response.
   rpc UnaryCall(SimpleRequest) returns (SimpleResponse);
 
+  // One request followed by one response. Response has cache control
+  // headers set such that a caching HTTP proxy (such as GFE) can
+  // satisfy subsequent requests.
+  rpc CacheableUnaryCall(SimpleRequest) returns (SimpleResponse);
+
   // One request followed by a sequence of responses (streamed download).
   // The server returns the payload with client desired type and sizes.
   rpc StreamingOutputCall(StreamingOutputCallRequest)
diff --git a/test/cpp/interop/client.cc b/test/cpp/interop/client.cc
index e8ae6ee572..8cbb1feeaf 100644
--- a/test/cpp/interop/client.cc
+++ b/test/cpp/interop/client.cc
@@ -148,6 +148,8 @@ int main(int argc, char** argv) {
     client.DoStatusWithMessage();
   } else if (FLAGS_test_case == "custom_metadata") {
     client.DoCustomMetadata();
+  } else if (FLAGS_test_case == "cacheable_unary") {
+    client.DoCacheableUnary();
   } else if (FLAGS_test_case == "all") {
     client.DoEmpty();
     client.DoLargeUnary();
@@ -165,6 +167,7 @@ int main(int argc, char** argv) {
     client.DoEmptyStream();
     client.DoStatusWithMessage();
     client.DoCustomMetadata();
+    client.DoCacheableUnary();
     // service_account_creds and jwt_token_creds can only run with ssl.
     if (FLAGS_use_tls) {
       grpc::string json_key = GetServiceAccountJsonKey();
@@ -176,6 +179,7 @@ int main(int argc, char** argv) {
     // compute_engine_creds only runs in GCE.
   } else {
     const char* testcases[] = {"all",
+                               "cacheable_unary",
                                "cancel_after_begin",
                                "cancel_after_first_response",
                                "client_compressed_streaming",
diff --git a/test/cpp/interop/interop_client.cc b/test/cpp/interop/interop_client.cc
index 8861bc1163..f2290adfc3 100644
--- a/test/cpp/interop/interop_client.cc
+++ b/test/cpp/interop/interop_client.cc
@@ -845,6 +845,49 @@ bool InteropClient::DoStatusWithMessage() {
   return true;
 }
 
+bool InteropClient::DoCacheableUnary() {
+  gpr_log(GPR_DEBUG, "Sending RPC with cacheable response");
+
+  SimpleRequest request;
+  request.set_response_size(16);
+  grpc::string payload(16, '\0');
+  request.mutable_payload()->set_body(payload.c_str(), 16);
+
+  // Request 1
+  ClientContext context1;
+  SimpleResponse response1;
+  context1.set_cacheable(true);
+  // Add fake user IP since some proxy's (GFE) won't cache requests from
+  // localhost.
+  context1.AddMetadata("x-user-ip", "1.2.3.4");
+  Status s1 =
+      serviceStub_.Get()->CacheableUnaryCall(&context1, request, &response1);
+  if (!AssertStatusOk(s1)) {
+    return false;
+  }
+  gpr_log(GPR_DEBUG, "response 1 payload: %s",
+          response1.payload().body().c_str());
+
+  // Request 2
+  ClientContext context2;
+  SimpleResponse response2;
+  context2.set_cacheable(true);
+  context2.AddMetadata("x-user-ip", "1.2.3.4");
+  Status s2 =
+      serviceStub_.Get()->CacheableUnaryCall(&context2, request, &response2);
+  if (!AssertStatusOk(s2)) {
+    return false;
+  }
+  gpr_log(GPR_DEBUG, "response 1 payload: %s",
+          response2.payload().body().c_str());
+
+  // Check that the body is same for both requests. It will be the same if the
+  // second response is a cached copy of the first response
+  GPR_ASSERT(response2.payload().body() == response1.payload().body());
+
+  return true;
+}
+
 bool InteropClient::DoCustomMetadata() {
   const grpc::string kEchoInitialMetadataKey("x-grpc-test-echo-initial");
   const grpc::string kInitialMetadataValue("test_initial_metadata_value");
diff --git a/test/cpp/interop/interop_client.h b/test/cpp/interop/interop_client.h
index eb886fcb7e..1e89f0987d 100644
--- a/test/cpp/interop/interop_client.h
+++ b/test/cpp/interop/interop_client.h
@@ -79,6 +79,7 @@ class InteropClient {
   bool DoEmptyStream();
   bool DoStatusWithMessage();
   bool DoCustomMetadata();
+  bool DoCacheableUnary();
   // Auth tests.
   // username is a string containing the user email
   bool DoJwtTokenCreds(const grpc::string& username);
diff --git a/test/cpp/interop/interop_server.cc b/test/cpp/interop/interop_server.cc
index e5878bb248..ac2567eba0 100644
--- a/test/cpp/interop/interop_server.cc
+++ b/test/cpp/interop/interop_server.cc
@@ -47,6 +47,7 @@
 #include <grpc/support/log.h>
 #include <grpc/support/useful.h>
 
+#include "src/core/lib/support/string.h"
 #include "src/core/lib/transport/byte_stream.h"
 #include "src/proto/grpc/testing/empty.grpc.pb.h"
 #include "src/proto/grpc/testing/messages.grpc.pb.h"
@@ -152,6 +153,16 @@ class TestServiceImpl : public TestService::Service {
     return Status::OK;
   }
 
+  // Response contains current timestamp. We ignore everything in the request.
+  Status CacheableUnaryCall(ServerContext* context, const SimpleRequest* request,
+                   SimpleResponse* response) {
+    gpr_timespec ts = gpr_now(GPR_CLOCK_REALTIME);
+    std::string timestamp = std::to_string(ts.tv_nsec);
+    response->mutable_payload()->set_body(timestamp.c_str(), timestamp.size());
+    context->AddInitialMetadata("Cache-Control", "max-age=100000, public");
+    return Status::OK;
+  }
+
   Status UnaryCall(ServerContext* context, const SimpleRequest* request,
                    SimpleResponse* response) {
     MaybeEchoMetadata(context);
-- 
GitLab