Imported Upstream version 1.37.1 upstream/1.37.1
authorJinWang An <jinwang.an@samsung.com>
Wed, 1 Dec 2021 08:53:19 +0000 (17:53 +0900)
committerJinWang An <jinwang.an@samsung.com>
Wed, 1 Dec 2021 08:53:19 +0000 (17:53 +0900)
73 files changed:
BUILD
CMakeLists.txt
Makefile
build_autogenerated.yaml
build_handwritten.yaml
config.w32
gRPC-C++.podspec
gRPC-Core.podspec
gRPC-ProtoRPC.podspec
gRPC-RxLibrary.podspec
gRPC.podspec
package.xml
src/core/ext/filters/fault_injection/fault_injection_filter.cc
src/core/lib/security/transport/server_auth_filter.cc
src/cpp/common/version_cc.cc
src/csharp/Grpc.Core.Api/VersionInfo.cs
src/csharp/build/dependencies.props
src/objective-c/!ProtoCompiler-gRPCCppPlugin.podspec
src/objective-c/!ProtoCompiler-gRPCPlugin.podspec
src/objective-c/GRPCClient/version.h
src/objective-c/tests/version.h
src/php/bin/run_xds_client.sh [new file with mode: 0755]
src/php/bin/xds_manager.py [new file with mode: 0755]
src/php/composer.json
src/php/ext/grpc/call.c
src/php/ext/grpc/channel_credentials.c
src/php/ext/grpc/version.h
src/php/lib/Grpc/RpcServer.php
src/php/tests/interop/Grpc/Testing/XdsUpdateClientConfigureServiceStub.php [new file with mode: 0644]
src/php/tests/interop/xds_client.php
src/php/tests/interop/xds_empty_call.php [new file with mode: 0644]
src/php/tests/interop/xds_unary_call.php [new file with mode: 0644]
src/php/tests/unit_tests/ChannelCredentialsTest.php
src/php/tests/unit_tests/ChannelTest.php
src/python/grpcio/grpc/_grpcio_metadata.py
src/python/grpcio/grpc_version.py
src/python/grpcio_channelz/grpc_version.py
src/python/grpcio_health_checking/grpc_version.py
src/python/grpcio_reflection/grpc_version.py
src/python/grpcio_status/grpc_version.py
src/python/grpcio_testing/grpc_version.py
src/python/grpcio_tests/grpc_version.py
src/ruby/lib/grpc/version.rb
src/ruby/pb/test/xds_client.rb
src/ruby/tools/version.rb
templates/config.w32.template
test/cpp/interop/BUILD
test/cpp/interop/xds_interop_client.cc
test/cpp/interop/xds_interop_server.cc
tools/distrib/python/grpc_version.py
tools/distrib/python/grpcio_tools/grpc_version.py
tools/dockerfile/interoptest/grpc_interop_cxx_xds/Dockerfile.xds_client [new file with mode: 0644]
tools/dockerfile/interoptest/grpc_interop_cxx_xds/Dockerfile.xds_server [new file with mode: 0644]
tools/doxygen/Doxyfile.c++
tools/doxygen/Doxyfile.c++.internal
tools/doxygen/Doxyfile.objc
tools/doxygen/Doxyfile.objc.internal
tools/doxygen/Doxyfile.php
tools/internal_ci/linux/grpc_xds_bazel_python_test_in_docker.sh
tools/internal_ci/linux/grpc_xds_bazel_test_in_docker.sh
tools/internal_ci/linux/grpc_xds_csharp_test_in_docker.sh
tools/internal_ci/linux/grpc_xds_k8s.cfg [new file with mode: 0644]
tools/internal_ci/linux/grpc_xds_k8s.sh [new file with mode: 0755]
tools/internal_ci/linux/grpc_xds_k8s_install_test_driver.sh [new file with mode: 0644]
tools/internal_ci/linux/grpc_xds_php_test_in_docker.sh
tools/internal_ci/linux/grpc_xds_ruby_test_in_docker.sh
tools/run_tests/artifacts/artifact_targets.py
tools/run_tests/helper_scripts/prep_xds.sh
tools/run_tests/run_xds_tests.py
tools/run_tests/xds_k8s_test_driver/config/common.cfg
tools/run_tests/xds_k8s_test_driver/framework/infrastructure/gcp/api.py
tools/run_tests/xds_k8s_test_driver/kubernetes-manifests/client-secure.deployment.yaml
tools/run_tests/xds_k8s_test_driver/kubernetes-manifests/server-secure.deployment.yaml

diff --git a/BUILD b/BUILD
index 554046c..4d35ffb 100644 (file)
--- a/BUILD
+++ b/BUILD
@@ -90,7 +90,7 @@ g_stands_for = "gilded"  # @unused
 
 core_version = "15.0.0"  # @unused
 
-version = "1.37.0"  # @unused
+version = "1.37.1"  # @unused
 
 GPR_PUBLIC_HDRS = [
     "include/grpc/support/alloc.h",
index f4f1f6b..6729fc4 100644 (file)
 cmake_minimum_required(VERSION 3.5.1)
 
 set(PACKAGE_NAME          "grpc")
-set(PACKAGE_VERSION       "1.37.0")
+set(PACKAGE_VERSION       "1.37.1")
 set(gRPC_CORE_VERSION     "15.0.0")
 set(gRPC_CORE_SOVERSION   "15")
-set(gRPC_CPP_VERSION      "1.37.0")
+set(gRPC_CPP_VERSION      "1.37.1")
 set(gRPC_CPP_SOVERSION    "1.37")
-set(gRPC_CSHARP_VERSION   "2.37.0")
+set(gRPC_CSHARP_VERSION   "2.37.1")
 set(gRPC_CSHARP_SOVERSION "2.37")
 set(PACKAGE_STRING        "${PACKAGE_NAME} ${PACKAGE_VERSION}")
 set(PACKAGE_TARNAME       "${PACKAGE_NAME}-${PACKAGE_VERSION}")
@@ -14825,6 +14825,24 @@ add_executable(xds_interop_client
   ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/test.grpc.pb.cc
   ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/test.pb.h
   ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/test.grpc.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/base.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/base.grpc.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/base.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/base.grpc.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/config_dump.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/config_dump.grpc.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/config_dump.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/config_dump.grpc.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/csds.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/csds.grpc.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/csds.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/csds.grpc.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.grpc.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.grpc.pb.h
+  src/cpp/server/admin/admin_services.cc
+  src/cpp/server/csds/csds.cc
   test/cpp/interop/xds_interop_client.cc
   third_party/googletest/googletest/src/gtest-all.cc
   third_party/googletest/googlemock/src/gmock-all.cc
@@ -14853,7 +14871,8 @@ target_link_libraries(xds_interop_client
   ${_gRPC_PROTOBUF_LIBRARIES}
   ${_gRPC_ALLTARGETS_LIBRARIES}
   absl::flags
-  grpc++
+  grpc++_reflection
+  grpcpp_channelz
   grpc_test_util
   grpc++_test_config
 )
@@ -14879,6 +14898,24 @@ add_executable(xds_interop_server
   ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/test.grpc.pb.cc
   ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/test.pb.h
   ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/test.grpc.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/base.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/base.grpc.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/base.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/base.grpc.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/config_dump.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/config_dump.grpc.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/config_dump.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/config_dump.grpc.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/csds.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/csds.grpc.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/csds.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/csds.grpc.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.grpc.pb.cc
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.pb.h
+  ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.grpc.pb.h
+  src/cpp/server/admin/admin_services.cc
+  src/cpp/server/csds/csds.cc
   test/cpp/end2end/test_health_check_service_impl.cc
   test/cpp/interop/xds_interop_server.cc
   third_party/googletest/googletest/src/gtest-all.cc
@@ -14908,7 +14945,8 @@ target_link_libraries(xds_interop_server
   ${_gRPC_PROTOBUF_LIBRARIES}
   ${_gRPC_ALLTARGETS_LIBRARIES}
   absl::flags
-  grpc++
+  grpc++_reflection
+  grpcpp_channelz
   grpc_test_util
   grpc++_test_config
 )
index 223d466..55e65c8 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -455,8 +455,8 @@ Q = @
 endif
 
 CORE_VERSION = 15.0.0
-CPP_VERSION = 1.37.0
-CSHARP_VERSION = 2.37.0
+CPP_VERSION = 1.37.1
+CSHARP_VERSION = 2.37.1
 
 CPPFLAGS_NO_ARCH += $(addprefix -I, $(INCLUDES)) $(addprefix -D, $(DEFINES))
 CPPFLAGS += $(CPPFLAGS_NO_ARCH) $(ARCH_FLAGS)
index 5fd9112..558f424 100644 (file)
@@ -6630,15 +6630,23 @@ targets:
   build: test
   run: false
   language: c++
-  headers: []
+  headers:
+  - src/cpp/server/csds/csds.h
   src:
   - src/proto/grpc/testing/empty.proto
   - src/proto/grpc/testing/messages.proto
   - src/proto/grpc/testing/test.proto
+  - src/proto/grpc/testing/xds/v3/base.proto
+  - src/proto/grpc/testing/xds/v3/config_dump.proto
+  - src/proto/grpc/testing/xds/v3/csds.proto
+  - src/proto/grpc/testing/xds/v3/percent.proto
+  - src/cpp/server/admin/admin_services.cc
+  - src/cpp/server/csds/csds.cc
   - test/cpp/interop/xds_interop_client.cc
   deps:
   - absl/flags:flag
-  - grpc++
+  - grpc++_reflection
+  - grpcpp_channelz
   - grpc_test_util
   - grpc++_test_config
 - name: xds_interop_server
@@ -6646,17 +6654,25 @@ targets:
   run: false
   language: c++
   headers:
+  - src/cpp/server/csds/csds.h
   - test/cpp/end2end/test_health_check_service_impl.h
   src:
   - src/proto/grpc/health/v1/health.proto
   - src/proto/grpc/testing/empty.proto
   - src/proto/grpc/testing/messages.proto
   - src/proto/grpc/testing/test.proto
+  - src/proto/grpc/testing/xds/v3/base.proto
+  - src/proto/grpc/testing/xds/v3/config_dump.proto
+  - src/proto/grpc/testing/xds/v3/csds.proto
+  - src/proto/grpc/testing/xds/v3/percent.proto
+  - src/cpp/server/admin/admin_services.cc
+  - src/cpp/server/csds/csds.cc
   - test/cpp/end2end/test_health_check_service_impl.cc
   - test/cpp/interop/xds_interop_server.cc
   deps:
   - absl/flags:flag
-  - grpc++
+  - grpc++_reflection
+  - grpcpp_channelz
   - grpc_test_util
   - grpc++_test_config
 tests: []
index 2ccecb7..f1bf3ce 100644 (file)
@@ -16,7 +16,7 @@ settings:
   csharp_major_version: 2
   g_stands_for: gilded
   protobuf_version: 3.15.2
-  version: 1.37.0
+  version: 1.37.1
 targets:
 - name: check_epollexclusive
   build: tool
index f2745a6..ee6a8d5 100644 (file)
@@ -1022,6 +1022,7 @@ if (PHP_GRPC != "no") {
     "/I"+configure_module_dirname+"\\third_party\\boringssl-with-bazel\\src\\include "+
     "/I"+configure_module_dirname+"\\third_party\\re2 "+
     "/I"+configure_module_dirname+"\\third_party\\upb "+
+    "/I"+configure_module_dirname+"\\third_party\\xxhash "+
     "/I"+configure_module_dirname+"\\third_party\\zlib ");
 
   base_dir = get_define('BUILD_DIR');
index 06852b8..60ceae3 100644 (file)
@@ -22,7 +22,7 @@
 Pod::Spec.new do |s|
   s.name     = 'gRPC-C++'
   # TODO (mxyan): use version that match gRPC version when pod is stabilized
-  version = '1.37.0'
+  version = '1.37.1'
   s.version  = version
   s.summary  = 'gRPC C++ library'
   s.homepage = 'https://grpc.io'
index 621222e..1807b8a 100644 (file)
@@ -21,7 +21,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC-Core'
-  version = '1.37.0'
+  version = '1.37.1'
   s.version  = version
   s.summary  = 'Core cross-platform gRPC library, written in C'
   s.homepage = 'https://grpc.io'
index 8988e9a..df1d73a 100644 (file)
@@ -21,7 +21,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC-ProtoRPC'
-  version = '1.37.0'
+  version = '1.37.1'
   s.version  = version
   s.summary  = 'RPC library for Protocol Buffers, based on gRPC'
   s.homepage = 'https://grpc.io'
index 0befaca..fac08be 100644 (file)
@@ -21,7 +21,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC-RxLibrary'
-  version = '1.37.0'
+  version = '1.37.1'
   s.version  = version
   s.summary  = 'Reactive Extensions library for iOS/OSX.'
   s.homepage = 'https://grpc.io'
index ce9df12..4cdd9f2 100644 (file)
@@ -20,7 +20,7 @@
 
 Pod::Spec.new do |s|
   s.name     = 'gRPC'
-  version = '1.37.0'
+  version = '1.37.1'
   s.version  = version
   s.summary  = 'gRPC client library for iOS/OSX'
   s.homepage = 'https://grpc.io'
index 777a8c4..540616f 100644 (file)
@@ -13,8 +13,8 @@
  <date>2019-09-24</date>
  <time>16:06:07</time>
  <version>
-  <release>1.37.0</release>
-  <api>1.37.0</api>
+  <release>1.37.1</release>
+  <api>1.37.1</api>
  </version>
  <stability>
   <release>stable</release>
@@ -22,7 +22,7 @@
  </stability>
  <license>Apache 2.0</license>
  <notes>
-- gRPC Core 1.37.0 update
+- gRPC Core 1.37.1 update
  </notes>
  <contents>
   <dir baseinstalldir="/" name="/">
index 6cf21c5..a412488 100644 (file)
@@ -208,21 +208,24 @@ class CallData::ResumeBatchCanceller {
     auto* self = static_cast<ResumeBatchCanceller*>(arg);
     auto* chand = static_cast<ChannelData*>(self->elem_->channel_data);
     auto* calld = static_cast<CallData*>(self->elem_->call_data);
-    MutexLock lock(&calld->delay_mu_);
-    if (GRPC_TRACE_FLAG_ENABLED(grpc_fault_injection_filter_trace)) {
-      gpr_log(GPR_INFO,
-              "chand=%p calld=%p: cancelling schdueled pick: "
-              "error=%s self=%p calld->resume_batch_canceller_=%p",
-              chand, calld, grpc_error_string(error), self,
-              calld->resume_batch_canceller_);
-    }
-    if (error != GRPC_ERROR_NONE && calld->resume_batch_canceller_ == self) {
-      // Cancel the delayed pick.
-      calld->CancelDelayTimer();
-      calld->FaultInjectionFinished();
-      // Fail pending batches on the call.
-      grpc_transport_stream_op_batch_finish_with_failure(
-          calld->delayed_batch_, GRPC_ERROR_REF(error), calld->call_combiner_);
+    {
+      MutexLock lock(&calld->delay_mu_);
+      if (GRPC_TRACE_FLAG_ENABLED(grpc_fault_injection_filter_trace)) {
+        gpr_log(GPR_INFO,
+                "chand=%p calld=%p: cancelling schdueled pick: "
+                "error=%s self=%p calld->resume_batch_canceller_=%p",
+                chand, calld, grpc_error_string(error), self,
+                calld->resume_batch_canceller_);
+      }
+      if (error != GRPC_ERROR_NONE && calld->resume_batch_canceller_ == self) {
+        // Cancel the delayed pick.
+        calld->CancelDelayTimer();
+        calld->FaultInjectionFinished();
+        // Fail pending batches on the call.
+        grpc_transport_stream_op_batch_finish_with_failure(
+            calld->delayed_batch_, GRPC_ERROR_REF(error),
+            calld->call_combiner_);
+      }
     }
     GRPC_CALL_STACK_UNREF(calld->owning_call_, "ResumeBatchCanceller");
     delete self;
@@ -465,9 +468,11 @@ void CallData::HijackedRecvTrailingMetadataReady(void* arg, grpc_error* error) {
   if (calld->abort_error_ != GRPC_ERROR_NONE) {
     error = grpc_error_add_child(GRPC_ERROR_REF(error),
                                  GRPC_ERROR_REF(calld->abort_error_));
-    Closure::Run(DEBUG_LOCATION, calld->original_recv_trailing_metadata_ready_,
-                 error);
+  } else {
+    error = GRPC_ERROR_REF(error);
   }
+  Closure::Run(DEBUG_LOCATION, calld->original_recv_trailing_metadata_ready_,
+               error);
 }
 
 }  // namespace
index 6735189..925a70a 100644 (file)
@@ -300,6 +300,13 @@ static grpc_error* server_auth_init_channel_elem(
   GPR_ASSERT(!args->is_last);
   grpc_auth_context* auth_context =
       grpc_find_auth_context_in_args(args->channel_args);
+  if (auth_context == nullptr) {
+    grpc_error* error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
+        "No authorization context found. This might be a TRANSIENT failure due "
+        "to certificates not having been loaded yet.");
+    gpr_log(GPR_DEBUG, "%s", grpc_error_string(error));
+    return error;
+  }
   GPR_ASSERT(auth_context != nullptr);
   grpc_server_credentials* creds =
       grpc_find_server_credentials_in_args(args->channel_args);
index 27e7ebf..86a79d7 100644 (file)
@@ -22,5 +22,5 @@
 #include <grpcpp/grpcpp.h>
 
 namespace grpc {
-std::string Version() { return "1.37.0"; }
+std::string Version() { return "1.37.1"; }
 }  // namespace grpc
index a86f745..e28b5c5 100644 (file)
@@ -33,11 +33,11 @@ namespace Grpc.Core
         /// <summary>
         /// Current <c>AssemblyFileVersion</c> of gRPC C# assemblies
         /// </summary>
-        public const string CurrentAssemblyFileVersion = "2.37.0.0";
+        public const string CurrentAssemblyFileVersion = "2.37.1.0";
 
         /// <summary>
         /// Current version of gRPC C#
         /// </summary>
-        public const string CurrentVersion = "2.37.0";
+        public const string CurrentVersion = "2.37.1";
     }
 }
index bc66fad..35b92e2 100644 (file)
@@ -1,7 +1,7 @@
 <!-- This file is generated -->
 <Project>
   <PropertyGroup>
-    <GrpcCsharpVersion>2.37.0</GrpcCsharpVersion>
+    <GrpcCsharpVersion>2.37.1</GrpcCsharpVersion>
     <GoogleProtobufVersion>3.15.2</GoogleProtobufVersion>
   </PropertyGroup>
 </Project>
index e0f5a67..585796c 100644 (file)
@@ -42,7 +42,7 @@ Pod::Spec.new do |s|
   # exclamation mark ensures that other "regular" pods will be able to find it as it'll be installed
   # before them.
   s.name     = '!ProtoCompiler-gRPCCppPlugin'
-  v = '1.37.0'
+  v = '1.37.1'
   s.version  = v
   s.summary  = 'The gRPC ProtoC plugin generates C++ files from .proto services.'
   s.description = <<-DESC
index 3be6b9a..05408e5 100644 (file)
@@ -42,7 +42,7 @@ Pod::Spec.new do |s|
   # exclamation mark ensures that other "regular" pods will be able to find it as it'll be installed
   # before them.
   s.name     = '!ProtoCompiler-gRPCPlugin'
-  v = '1.37.0'
+  v = '1.37.1'
   s.version  = v
   s.summary  = 'The gRPC ProtoC plugin generates Objective-C files from .proto services.'
   s.description = <<-DESC
index 0ad3d5d..656a8aa 100644 (file)
@@ -22,4 +22,4 @@
 // instead. This file can be regenerated from the template by running
 // `tools/buildgen/generate_projects.sh`.
 
-#define GRPC_OBJC_VERSION_STRING @"1.37.0"
+#define GRPC_OBJC_VERSION_STRING @"1.37.1"
index d991b06..621123d 100644 (file)
@@ -22,5 +22,5 @@
 // instead. This file can be regenerated from the template by running
 // `tools/buildgen/generate_projects.sh`.
 
-#define GRPC_OBJC_VERSION_STRING @"1.37.0"
+#define GRPC_OBJC_VERSION_STRING @"1.37.1"
 #define GRPC_C_VERSION_STRING @"15.0.0"
diff --git a/src/php/bin/run_xds_client.sh b/src/php/bin/run_xds_client.sh
new file mode 100755 (executable)
index 0000000..82809a3
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/bash
+# Copyright 2021 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.
+
+
+# This script is being launched from the run_xds_tests.py runner and is
+# responsible for managing child PHP processes.
+
+cleanup () {
+    echo "Trapped SIGTERM. Cleaning up..."
+    set -x
+    kill -9 $PID1
+    kill -9 $PID2
+    running=false
+    set +x
+}
+
+trap cleanup SIGTERM
+
+set -e
+cd $(dirname $0)/../../..
+root=$(pwd)
+
+# tmp_file1 contains the list of RPCs (and their spec) the parent PHP
+# process want executed
+tmp_file1=$(mktemp)
+# tmp_file2 contains the RPC result of each key initiated
+tmp_file2=$(mktemp)
+
+set -x
+# This is the PHP parent process, who is primarily responding to the
+# run_xds_tests.py runner's stats requests
+php -d extension=grpc.so -d extension=pthreads.so \
+    src/php/tests/interop/xds_client.php $1 $2 $3 $4 $5 $6 \
+    --tmp_file1=$tmp_file1 --tmp_file2=$tmp_file2 &
+PID1=$!
+
+# This script watches RPCs written to tmp_file1, spawn off more PHP
+# child processes to execute them, and writes the result to tmp_file2
+python3 -u src/php/bin/xds_manager.py \
+        --tmp_file1=$tmp_file1 --tmp_file2=$tmp_file2 \
+        --bootstrap_path=$GRPC_XDS_BOOTSTRAP &
+PID2=$!
+set +x
+
+# This will be killed by a SIGTERM signal from the run_xds_tests.py
+# runner
+running=true
+while $running
+do
+    sleep 1
+done
diff --git a/src/php/bin/xds_manager.py b/src/php/bin/xds_manager.py
new file mode 100755 (executable)
index 0000000..da7ea66
--- /dev/null
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+# Copyright 2021 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.
+"""Manage PHP child processes for the main PHP xDS Interop client"""
+
+import argparse
+import fcntl
+import os
+import subprocess
+
+# This script is being launched from src/php/bin/run_xds_client.sh
+# to manage PHP child processes which will send 1 RPC each
+# asynchronously. This script keeps track of all those open
+# processes and reports back to the main PHP interop client each
+# of the child RPCs' status code.
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--tmp_file1', nargs='?', default='')
+    parser.add_argument('--tmp_file2', nargs='?', default='')
+    parser.add_argument('--bootstrap_path', nargs='?', default='')
+    args = parser.parse_args()
+    server_address = ''
+    rpcs_started = []
+    open_processes = {}
+    client_env = dict(os.environ)
+    client_env['GRPC_XDS_BOOTSTRAP'] = args.bootstrap_path
+    while True:
+        # tmp_file1 contains a list of RPCs (and their spec) the parent process
+        # wants executed
+        f1 = open(args.tmp_file1, 'r+')
+        fcntl.flock(f1, fcntl.LOCK_EX)
+        while True:
+            key = f1.readline()
+            if not key:
+                break
+            key = key.strip()
+            if key.startswith('server_address'):
+                if not server_address:
+                    server_address = key[15:]
+            elif not key in rpcs_started:
+                # format here needs to be in sync with
+                # src/php/tests/interop/xds_client.php
+                items = key.split('|')
+                num = items[0]
+                metadata = items[2]
+                timeout_sec = items[3]
+                if items[1] == 'UnaryCall':
+                    p = subprocess.Popen([
+                        'php', '-d', 'extension=grpc.so', '-d',
+                        'extension=pthreads.so',
+                        'src/php/tests/interop/xds_unary_call.php',
+                        '--server=' + server_address, '--num=' + str(num),
+                        '--metadata=' + metadata, '--timeout_sec=' + timeout_sec
+                    ],
+                                         env=client_env)
+                elif items[1] == 'EmptyCall':
+                    p = subprocess.Popen([
+                        'php', '-d', 'extension=grpc.so', '-d',
+                        'extension=pthreads.so',
+                        'src/php/tests/interop/xds_empty_call.php',
+                        '--server=' + server_address, '--num=' + str(num),
+                        '--metadata=' + metadata, '--timeout=' + timeout_sec
+                    ],
+                                         env=client_env)
+                else:
+                    continue
+                rpcs_started.append(key)
+                open_processes[key] = p
+        f1.truncate(0)
+        fcntl.flock(f1, fcntl.LOCK_UN)
+        f1.close()
+        # tmp_file2 contains the RPC result of each key received from tmp_file1
+        f2 = open(args.tmp_file2, 'a')
+        fcntl.flock(f2, fcntl.LOCK_EX)
+        keys_to_delete = []
+        for key, process in open_processes.items():
+            result = process.poll()
+            if result is not None:
+                # format here needs to be in sync with
+                # src/php/tests/interop/xds_client.php
+                f2.write(key + ',' + str(process.returncode) + "\n")
+                keys_to_delete.append(key)
+        for key in keys_to_delete:
+            del open_processes[key]
+        fcntl.flock(f2, fcntl.LOCK_UN)
+        f2.close()
index 511dbc8..a77c364 100644 (file)
@@ -2,7 +2,7 @@
   "name": "grpc/grpc-dev",
   "description": "gRPC library for PHP - for Development use only",
   "license": "Apache-2.0",
-  "version": "1.37.0",
+  "version": "1.37.1",
   "require": {
     "php": ">=7.0.0",
     "google/protobuf": "^v3.3.0"
index ef6b86e..1abc4a3 100644 (file)
@@ -314,6 +314,11 @@ PHP_METHOD(Call, startBatch) {
     goto cleanup;
   }
 
+  // c-core may call rand(). If we don't call srand() here, all the
+  // random numbers being returned would be the same.
+  gpr_timespec now = gpr_now(GPR_CLOCK_REALTIME);
+  srand(now.tv_nsec);
+
   array_hash = Z_ARRVAL_P(array);
 
   char *key = NULL;
index 4308066..34b4c82 100644 (file)
@@ -233,10 +233,7 @@ PHP_METHOD(ChannelCredentials, createComposite) {
  * @return null
  */
 PHP_METHOD(ChannelCredentials, createInsecure) {
-  grpc_channel_credentials* creds = grpc_insecure_credentials_create();
-  zval* creds_object = grpc_php_wrap_channel_credentials(
-      creds, strdup("INSECURE"), false TSRMLS_CC);
-  RETURN_DESTROY_ZVAL(creds_object);
+  RETURN_NULL();
 }
 
 /**
index ffc5a1e..76d5537 100644 (file)
@@ -20,6 +20,6 @@
 #ifndef VERSION_H
 #define VERSION_H
 
-#define PHP_GRPC_VERSION "1.37.0"
+#define PHP_GRPC_VERSION "1.37.1"
 
 #endif /* VERSION_H */
index 4c88b2e..ed3b986 100644 (file)
@@ -51,9 +51,7 @@ class RpcServer extends Server
         $event = $this->call->startBatch([
             OP_RECV_MESSAGE => true,
         ]);
-        if (!$event->message) {
-            throw new Exception("Did not receive a proper message");
-        }
+        $request->clear();
         $request->mergeFromString($event->message);
         return $request;
     }
diff --git a/src/php/tests/interop/Grpc/Testing/XdsUpdateClientConfigureServiceStub.php b/src/php/tests/interop/Grpc/Testing/XdsUpdateClientConfigureServiceStub.php
new file mode 100644 (file)
index 0000000..a167897
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+// DO NOT EDIT
+namespace Grpc\Testing;
+class XdsUpdateClientConfigureServiceStub {
+}
+
index 6927ce3..b814652 100644 (file)
 $autoload_path = realpath(dirname(__FILE__).'/../../vendor/autoload.php');
 require_once $autoload_path;
 
+class XdsUpdateClientConfigureService
+        extends \Grpc\Testing\XdsUpdateClientConfigureServiceStub
+{
+    function configure(\Grpc\Testing\ClientConfigureRequest $request) {
+        $rpc_types = $request->getTypes();
+        $all_metadata = $request->getMetadata();
+        $rpcs_to_send = [];
+        foreach ($rpc_types as $rpc_type) {
+            if ($rpc_type ==
+                \Grpc\Testing\ClientConfigureRequest\RpcType::EMPTY_CALL) {
+                $rpcs_to_send[] = 'EmptyCall';
+            } else if ($rpc_type ==
+                       \Grpc\Testing\ClientConfigureRequest\RpcType::UNARY_CALL) {
+                $rpcs_to_send[] = 'UnaryCall';
+            }
+        }
+        $metadata_to_send = [];
+        foreach ($all_metadata as $metadata) {
+            $rpc_type = $metadata->getType();
+            if ($rpc_type ==
+                \Grpc\Testing\ClientConfigureRequest\RpcType::EMPTY_CALL) {
+                $rpc_type_key = 'EmptyCall';
+            } else if ($rpc_type ==
+                       \Grpc\Testing\ClientConfigureRequest\RpcType::UNARY_CALL) {
+                $rpc_type_key = 'UnaryCall';
+            }
+            $key = $metadata->getKey();
+            $value = $metadata->getValue();
+            if (!isset($metadata_to_send[$rpc_type_key])) {
+                $metadata_to_send[$rpc_type_key] = [];
+            }
+            $metadata_to_send[$rpc_type_key][$key] = $value;
+        }
+        global $client_thread;
+        echo "PHP parent: Setting client_thread rpc_config to \n";
+        print_r($rpcs_to_send);
+        print_r($metadata_to_send);
+        echo "PHP parent: timeout_sec = ".$request->getTimeoutSec()."\n";
+        $client_thread->rpc_config->update($rpcs_to_send,
+                                           $metadata_to_send,
+                                           $request->getTimeoutSec());
+        return new Grpc\Testing\ClientConfigureResponse();
+    }
+}
+
 // The main xds interop test runner will ping this service to ask for
 // the stats of the distribution of the backends, for the next X rpcs.
 class LoadBalancerStatsService
@@ -69,7 +114,8 @@ class LoadBalancerStatsService
         //     '', 'hostname1', 'hostname2', '', 'hostname2', ...
         //   ],
         // ]
-        foreach ($client_thread->results as $rpc => $results) {
+        foreach ((array)$client_thread->rpc_config->rpcs_to_send as $rpc) {
+            $results = $client_thread->results[$rpc];
             // initialize, can always start from scratch here
             $rpcs_by_method[$rpc] = [];
             for ($i = $start_id; $i < $end_id; $i++) {
@@ -108,71 +154,204 @@ class LoadBalancerStatsService
         $response->setNumFailures($num_failures);
         return $response;
     }
+
+    function getClientAccumulatedStats(
+        \Grpc\Testing\LoadBalancerAccumulatedStatsRequest $request) {
+        global $client_thread;
+        $response = new Grpc\Testing\LoadBalancerAccumulatedStatsResponse();
+        $response->setNumRpcsStartedByMethod(
+            (array)$client_thread->num_rpcs_started_by_method);
+        $response->setNumRpcsSucceededByMethod(
+            (array)$client_thread->num_rpcs_succeeded_by_method);
+        $response->setNumRpcsFailedByMethod(
+            (array)$client_thread->num_rpcs_failed_by_method);
+        $accumulated_method_stats
+            = (array)$client_thread->accumulated_method_stats;
+        $stats_per_method = [];
+        foreach ($accumulated_method_stats as $rpc_name => $stats) {
+            $methodStats
+                = new Grpc\Testing\LoadBalancerAccumulatedStatsResponse\MethodStats();
+            $methodStats->setRpcsStarted($stats['rpcs_started']);
+            $methodStats->setResult((array)$stats['result']);
+            $stats_per_method[$rpc_name] = $methodStats;
+        }
+        $response->setStatsPerMethod($stats_per_method);
+        return $response;
+    }
+}
+
+class RpcConfig extends Volatile {
+    public $server_address;
+    public $qps;
+    public $fail_on_failed_rpcs;
+    public $rpcs_to_send;
+    public $metadata_to_send;
+    public $tmp_file1;
+    public $tmp_file2;
+    public $timeout_sec;
+    public function __construct($server_address,
+                                $qps,
+                                $fail_on_failed_rpcs,
+                                $rpcs_to_send,
+                                $metadata_to_send,
+                                $tmp_file1,
+                                $tmp_file2) {
+        $this->server_address = $server_address;
+        $this->qps = $qps;
+        $this->fail_on_failed_rpcs = $fail_on_failed_rpcs;
+        $this->rpcs_to_send = (array)$rpcs_to_send;
+        $this->metadata_to_send = (array)$metadata_to_send;
+        $this->tmp_file1 = $tmp_file1;
+        $this->tmp_file2 = $tmp_file2;
+        $this->timeout_sec = 30;
+    }
+    public function update($rpcs_to_send, $metadata_to_send, $timeout_sec) {
+        $this->rpcs_to_send = (array)$rpcs_to_send;
+        $this->metadata_to_send = (array)$metadata_to_send;
+        $this->timeout_sec = $timeout_sec;
+    }
 }
 
 // This client thread blindly sends a unary RPC to the server once
 // every 1 / qps seconds.
 class ClientThread extends Thread {
-    private $server_address_;
     private $target_seconds_between_rpcs_;
-    private $fail_on_failed_rpcs_;
     private $autoload_path_;
     private $TIMEOUT_US = 30 * 1e6; // 30 seconds
+    public $rpc_config;
     public $num_results = 0;
     public $results;
 
-    public function __construct($server_address, $qps, $fail_on_failed_rpcs,
-                                $rpcs_to_send, $metadata_to_send,
+    public $RPC_MAP = [
+        'UnaryCall' => 'UNARY_CALL',
+        'EmptyCall' => 'EMPTY_CALL',
+    ];
+
+    public $num_rpcs_started_by_method = [];
+    public $num_rpcs_succeeded_by_method = [];
+    public $num_rpcs_failed_by_method = [];
+    public $accumulated_method_stats = [];
+
+    public function __construct($rpc_config,
                                 $autoload_path) {
-        $this->server_address_ = $server_address;
-        $this->target_seconds_between_rpcs_ = 1.0 / $qps;
-        $this->fail_on_failed_rpcs_ = $fail_on_failed_rpcs;
-        $this->rpcs_to_send = explode(',', $rpcs_to_send);
-        // Convert input in the form of
-        //   rpc1:k1:v1,rpc2:k2:v2,rpc1:k3:v3
-        // into
-        //   [
-        //     'rpc1' => [
-        //       'k1' => 'v1',
-        //       'k3' => 'v3',
-        //     ],
-        //     'rpc2' => [
-        //       'k2' => 'v2'
-        //     ],
-        //   ]
-        $this->metadata_to_send = [];
-        if ($_all_metadata = explode(',', $metadata_to_send)) {
-            foreach ($_all_metadata as $one_metadata_pair) {
-                list($rpc,
-                     $metadata_key,
-                     $metadata_value) = explode(':', $one_metadata_pair);
-                // initialize in case we haven't seen this rpc before
-                if (!array_key_exists($rpc, $this->metadata_to_send)) {
-                    $this->metadata_to_send[$rpc] = [];
-                }
-                $this->metadata_to_send[$rpc][$metadata_key]
-                    = $metadata_value;
-            }
-        }
+        $this->rpc_config = $rpc_config;
+        $this->target_seconds_between_rpcs_ = 1.0 / $rpc_config->qps;
         $this->autoload_path_ = $autoload_path;
         $this->simple_request = new Grpc\Testing\SimpleRequest();
         $this->empty_request = new Grpc\Testing\EmptyMessage();
         $this->results = [];
-        foreach ($this->rpcs_to_send as $rpc) {
+        foreach (['UnaryCall', 'EmptyCall'] as $rpc) {
             $this->results[$rpc] = [];
         }
+        $this->outstanding_rpcs = [];
+        foreach (['UNARY_CALL', 'EMPTY_CALL'] as $rpc_stats_key) {
+            $this->num_rpcs_started_by_method[$rpc_stats_key] = 0;
+            $this->num_rpcs_succeeded_by_method[$rpc_stats_key] = 0;
+            $this->num_rpcs_failed_by_method[$rpc_stats_key] = 0;
+            $this->accumulated_method_stats[$rpc_stats_key] = [
+                'rpcs_started' => 0,
+                'result' => [],
+            ];
+        }
     }
 
     public function sendUnaryCall($stub, $metadata) {
+        $timeout = $this->rpc_config->timeout_sec ?
+                 $this->rpc_config->timeout_sec * 1e6 :
+                 $this->TIMEOUT_US;
         return $stub->UnaryCall($this->simple_request,
                                 $metadata,
-                                ['timeout' => $this->TIMEOUT_US]);
+                                ['timeout' => $timeout]);
     }
 
     public function sendEmptyCall($stub, $metadata) {
+        $timeout = $this->rpc_config->timeout_sec ?
+                 $this->rpc_config->timeout_sec * 1e6 :
+                 $this->TIMEOUT_US;
         return $stub->EmptyCall($this->empty_request,
                                 $metadata,
-                                ['timeout' => $this->TIMEOUT_US]);
+                                ['timeout' => $timeout]);
+    }
+
+    public function add_rpc_result($rpc, $status_code) {
+        // $rpc here needs to be in the format of 'UnaryCall', 'EmptyCall'
+        if (!isset($this->accumulated_method_stats[$this->RPC_MAP[$rpc]]
+                   ['result'][$status_code])) {
+            $this->accumulated_method_stats[$this->RPC_MAP[$rpc]]
+                ['result'][$status_code] = 0;
+        }
+        $this->accumulated_method_stats[$this->RPC_MAP[$rpc]]
+            ['result'][$status_code] += 1;
+    }
+
+    public function check_child_process_result() {
+        if (sizeof($this->outstanding_rpcs) > 0 &&
+            $this->rpc_config->tmp_file2) {
+            $keys_to_delete = [];
+            // tmp_file2 contains the RPC result of each RPC we
+            // originally wrote to tmp_file1
+            $f2 = fopen($this->rpc_config->tmp_file2, 'r+');
+            flock($f2, LOCK_EX);
+            while (true) {
+                $f2_line = fgets($f2);
+                if (!$f2_line) {
+                    break;
+                }
+                // format here needs to be in sync with
+                // src/php/bin/xds_manager.py
+                $parts = explode(',', trim($f2_line));
+                $key = $parts[0];
+                $returncode = $parts[1];
+                if (isset($this->outstanding_rpcs[$key])) {
+                    $parts2 = explode('|', $key);
+                    $result_num = $parts2[0];
+                    $rpc_name = $parts2[1];
+                    // Child processes can only communicate back the
+                    // status code for now.
+                    // Current interop test specs only call for
+                    // reporting back the status code in these scenarios.
+                    // If we ever need the hostname reported back from
+                    // child processes, we need to enhance this
+                    // communication framework through tmp files.
+                    $this->results[$rpc_name][$result_num] = "";
+                    if ($returncode) {
+                        $this->num_rpcs_failed_by_method
+                            [$this->RPC_MAP[$rpc_name]] += 1;
+                    } else {
+                        $this->num_rpcs_succeeded_by_method
+                            [$this->RPC_MAP[$rpc_name]] += 1;
+                    }
+                    $this->add_rpc_result($rpc_name, $returncode);
+                    $keys_to_delete[] = $key;
+                }
+            }
+            foreach ($keys_to_delete as $key) {
+                unset($this->outstanding_rpcs[$key]);
+            }
+            ftruncate($f2, 0);
+            flock($f2, LOCK_UN);
+            fclose($f2);
+        }
+    }
+
+    public function execute_rpc_in_child_process($rpc, $metadata_serialized) {
+        // tmp_file1 contains the list of RPCs (and their
+        // specs) we want executed. This will be picked up
+        // by src/php/bin/xds_manager.py
+        $f1 = fopen($this->rpc_config->tmp_file1, 'a');
+        $key = implode('|', [$this->num_results,
+                             $rpc,
+                             $metadata_serialized,
+                             $this->rpc_config->timeout_sec]);
+        flock($f1, LOCK_EX);
+        fwrite($f1, $key."\n");
+        fflush($f1);
+        flock($f1, LOCK_UN);
+        fclose($f1);
+        $this->outstanding_rpcs[$key] = 1;
+        $this->num_rpcs_started_by_method[$this->RPC_MAP[$rpc]] += 1;
+        $this->accumulated_method_stats[$this->RPC_MAP[$rpc]]
+            ['rpcs_started'] += 1;
     }
 
     public function run() {
@@ -180,10 +359,11 @@ class ClientThread extends Thread {
         // Hence we need to do this.
         require_once($this->autoload_path_);
 
-        $stub = new Grpc\Testing\TestServiceClient($this->server_address_, [
-            'credentials' => Grpc\ChannelCredentials::createInsecure()
+        $stub = new Grpc\Testing\TestServiceClient(
+            $this->rpc_config->server_address,
+            ['credentials' => Grpc\ChannelCredentials::createInsecure()
         ]);
-        # hrtime returns nanoseconds
+        // hrtime returns nanoseconds
         $target_next_start_us = hrtime(true) / 1000;
         while (true) {
             $now_us = hrtime(true) / 1000;
@@ -191,27 +371,36 @@ class ClientThread extends Thread {
             if ($sleep_us < 0) {
                 $target_next_start_us =
                         $now_us + ($this->target_seconds_between_rpcs_ * 1e6);
-                echo sprintf(
-                    "php xds: warning, rpc takes too long to finish. "
-                    . "Deficit %.1fms."
-                    . "If you consistently see this, the qps is too high.\n",
-                    round(abs($sleep_us / 1000), 1));
             } else {
                 $target_next_start_us +=
                         ($this->target_seconds_between_rpcs_ * 1e6);
                 usleep($sleep_us);
             }
-            foreach ($this->rpcs_to_send as $rpc) {
-                $metadata = array_key_exists(
-                    $rpc, $this->metadata_to_send) ?
-                          $this->metadata_to_send[$rpc] : [];
+            $this->check_child_process_result();
+            foreach ($this->rpc_config->rpcs_to_send as $rpc) {
+                $metadata_to_send_arr
+                    = (array)$this->rpc_config->metadata_to_send;
+                $metadata = array_key_exists($rpc, $metadata_to_send_arr) ?
+                          $metadata_to_send_arr[$rpc] : [];
                 // This copy is somehow necessary because
                 // $this->metadata_to_send[$rpc] somehow becomes a
                 // Volatile object, instead of an associative array.
                 $metadata_array = [];
+                $execute_in_child_process = false;
                 foreach ($metadata as $key => $value) {
                     $metadata_array[$key] = [$value];
+                    if ($key == 'rpc-behavior' || $key == 'fi_testcase') {
+                        $execute_in_child_process = true;
+                    }
                 }
+                if ($execute_in_child_process && $this->rpc_config->tmp_file1) {
+                    // if 'rpc-behavior' is set, we need to pawn off
+                    // the execution to some other child PHP processes
+                    $this->execute_rpc_in_child_process(
+                        $rpc, serialize($metadata_array));
+                    continue;
+                }
+                // Execute RPC within this script
                 $call = null;
                 if ($rpc == 'UnaryCall') {
                     $call = $this->sendUnaryCall($stub, $metadata_array);
@@ -220,19 +409,29 @@ class ClientThread extends Thread {
                 } else {
                     throw new Exception("Unhandled rpc $rpc");
                 }
+                $this->num_rpcs_started_by_method[$this->RPC_MAP[$rpc]] += 1;
+                $this->accumulated_method_stats[$this->RPC_MAP[$rpc]]
+                    ['rpcs_started'] += 1;
                 // the remote peer is being returned as part of the
                 // initial metadata, according to the test spec
                 $initial_metadata = $call->getMetadata();
                 list($response, $status) = $call->wait();
                 if ($status->code == Grpc\STATUS_OK &&
                     array_key_exists('hostname', $initial_metadata)) {
-                    $this->results[$rpc][] = $initial_metadata['hostname'][0];
+                    $this->results[$rpc][$this->num_results]
+                        = $initial_metadata['hostname'][0];
+                    $this->num_rpcs_succeeded_by_method
+                        [$this->RPC_MAP[$rpc]] += 1;
+                    $this->add_rpc_result($rpc, 0);
                 } else {
-                    if ($this->fail_on_failed_rpcs_) {
+                    if ($this->rpc_config->fail_on_failed_rpcs_) {
                         throw new Exception("$rpc failed with status "
                                             . $status->code);
                     }
-                    $this->results[$rpc][] = "";
+                    $this->results[$rpc][$this->num_results] = "";
+                    $this->num_rpcs_failed_by_method
+                        [$this->RPC_MAP[$rpc]] += 1;
+                    $this->add_rpc_result($rpc, $status->code);
                 }
             }
             // $num_results here is only incremented when the group of
@@ -250,18 +449,58 @@ class ClientThread extends Thread {
 
 // Note: num_channels are currently ignored for now
 $args = getopt('', ['fail_on_failed_rpcs:', 'num_channels:',
-                    'rpc:', 'metadata:',
+                    'rpc:', 'metadata:', 'tmp_file1:', 'tmp_file2:',
                     'server:', 'stats_port:', 'qps:']);
 
-$client_thread = new ClientThread($args['server'], $args['qps'],
-                                  $args['fail_on_failed_rpcs'],
-                                  (empty($args['rpc']) ? 'UnaryCall'
-                                   : $args['rpc']),
-                                  $args['metadata'],
+// Convert input in the form of
+//   rpc1:k1:v1,rpc2:k2:v2,rpc1:k3:v3
+// into
+//   [
+//     'rpc1' => [
+//       'k1' => 'v1',
+//       'k3' => 'v3',
+//     ],
+//     'rpc2' => [
+//       'k2' => 'v2'
+//     ],
+//   ]
+$metadata_to_send = [];
+if ($_all_metadata = explode(',', $args['metadata'])) {
+    foreach ($_all_metadata as $one_metadata_pair) {
+        list($rpc,
+             $metadata_key,
+             $metadata_value) = explode(':', $one_metadata_pair);
+        // initialize in case we haven't seen this rpc before
+        if (!array_key_exists($rpc, $metadata_to_send)) {
+            $metadata_to_send[$rpc] = [];
+        }
+        $metadata_to_send[$rpc][$metadata_key] = $metadata_value;
+    }
+}
+$rpcs_to_send = (empty($args['rpc']) ? 'UnaryCall' : $args['rpc']);
+
+// Need to communicate the xds server name to the async runner manager
+if ($args['tmp_file1']) {
+    $f1 = fopen($args['tmp_file1'], 'w');
+    fwrite($f1, 'server_address,'.$args['server']);
+    fclose($f1);
+}
+
+$rpc_config = new RpcConfig($args['server'],
+                            $args['qps'],
+                            $args['fail_on_failed_rpcs'],
+                            explode(',', $rpcs_to_send),
+                            $metadata_to_send,
+                            $args['tmp_file1'],
+                            $args['tmp_file2']);
+
+
+$client_thread = new ClientThread($rpc_config,
                                   $autoload_path);
 $client_thread->start();
 
 $server = new Grpc\RpcServer();
 $server->addHttp2Port('0.0.0.0:'.$args['stats_port']);
 $server->handle(new LoadBalancerStatsService());
+$server->handle(new XdsUpdateClientConfigureService());
 $server->run();
diff --git a/src/php/tests/interop/xds_empty_call.php b/src/php/tests/interop/xds_empty_call.php
new file mode 100644 (file)
index 0000000..e975d67
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+/*
+ *
+ * Copyright 2021 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.
+ *
+ */
+
+$autoload_path = realpath(dirname(__FILE__).'/../../vendor/autoload.php');
+require_once $autoload_path;
+
+// This script is used to launch 1 single EmptyCall RPC, most likely
+// for the purpose of starting such RPC asynchronously away from the
+// main PHP xDS interop client src/php/tests/interop/xds_client.php.
+
+// This script is launched from src/php/bin/xds_manager.py. The result
+// of this RPC will be aggregated and reported back to the main runner
+// from there.
+
+$args = getopt('', ['server:', 'num:',
+                    'metadata:', 'timeout_sec:']);
+$TIMEOUT_US = 30 * 1e6; // 30 seconds
+
+$server_address = $args['server'];
+$num = $args['num'];
+
+$stub = new Grpc\Testing\TestServiceClient($server_address, [
+    'credentials' => Grpc\ChannelCredentials::createInsecure()
+]);
+
+$empty_request = new Grpc\Testing\EmptyMessage();
+
+$timeout = $args['timeout_sec'] ? $args['timeout_sec'] * 1e6 : $TIMEOUT_US;
+$metadata = [];
+if ($args['metadata']) {
+    $metadata = unserialize($args['metadata']);
+}
+
+$call = $stub->EmptyCall($empty_request,
+                         $metadata,
+                         ['timeout' => $timeout]);
+list($response, $status) = $call->wait();
+exit($status->code);
diff --git a/src/php/tests/interop/xds_unary_call.php b/src/php/tests/interop/xds_unary_call.php
new file mode 100644 (file)
index 0000000..8997e0b
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+/*
+ *
+ * Copyright 2021 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.
+ *
+ */
+
+$autoload_path = realpath(dirname(__FILE__).'/../../vendor/autoload.php');
+require_once $autoload_path;
+
+// This script is used to launch 1 single EmptyCall RPC, most likely
+// for the purpose of starting such RPC asynchronously away from the
+// main PHP xDS interop client src/php/tests/interop/xds_client.php.
+
+// This script is launched from src/php/bin/xds_manager.py. The result
+// of this RPC will be aggregated and reported back to the main runner
+// from there.
+
+$args = getopt('', ['server:', 'num:',
+                    'metadata:', 'timeout_sec:']);
+$TIMEOUT_US = 30 * 1e6; // 30 seconds
+
+$server_address = $args['server'];
+$num = $args['num'];
+
+$stub = new Grpc\Testing\TestServiceClient($server_address, [
+    'credentials' => Grpc\ChannelCredentials::createInsecure()
+]);
+
+$simple_request = new Grpc\Testing\SimpleRequest();
+
+$timeout = $args['timeout_sec'] ? $args['timeout_sec'] * 1e6 : $TIMEOUT_US;
+$metadata = [];
+if ($args['metadata']) {
+    $metadata = unserialize($args['metadata']);
+}
+
+$call = $stub->UnaryCall($simple_request,
+                         $metadata,
+                         ['timeout' => $timeout]);
+list($response, $status) = $call->wait();
+exit($status->code);
index a10b5b8..ed61f3e 100644 (file)
@@ -43,7 +43,7 @@ class ChanellCredentialsTest extends \PHPUnit\Framework\TestCase
     public function testCreateInsecure()
     {
         $channel_credentials = Grpc\ChannelCredentials::createInsecure();
-        $this->assertNotNull($channel_credentials);
+        $this->assertNull($channel_credentials);
     }
 
     public function testDefaultRootsPem()
index a934106..b7df853 100644 (file)
@@ -52,7 +52,7 @@ class ChannelTest extends \PHPUnit\Framework\TestCase
         $this->assertNotNull($xdsCreds);
     }
 
-    public function testCreateXdsWithInsecure() {
+    public function disabled_testCreateXdsWithInsecure() {
         $xdsCreds = \Grpc\ChannelCredentials::createXds(
             \Grpc\ChannelCredentials::createInsecure()
         );
@@ -64,9 +64,21 @@ class ChannelTest extends \PHPUnit\Framework\TestCase
         $xdsCreds = \Grpc\ChannelCredentials::createXds(null);
     }
 
-    public function testCreateXdsWithInvalidType() {
-        $this->expectException(\TypeError::class);
-        $xdsCreds = \Grpc\ChannelCredentials::createXds("invalid-type");
+    public function testCreateXdsWithInvalidType()
+    {
+        $expected = $this->logicalOr(
+            // PHP8
+            new \PHPUnit\Framework\Constraint\Exception(\InvalidArgumentException::class),
+            // PHP7
+            new \PHPUnit\Framework\Constraint\Exception(\TypeError::class)
+        );
+        try {
+            $xdsCreds = \Grpc\ChannelCredentials::createXds("invalid-type");
+        } catch (\Throwable $exception) {
+            $this->assertThat($exception, $expected);
+            return;
+        }
+        $this->assertThat(null, $expected);
     }
 
     public function testGetConnectivityState()
@@ -353,6 +365,7 @@ class ChannelTest extends \PHPUnit\Framework\TestCase
                 ),
                 50306,
             ],
+            /*
             [
                 \Grpc\ChannelCredentials::createXds(
                     \Grpc\ChannelCredentials::createInSecure()
@@ -362,6 +375,7 @@ class ChannelTest extends \PHPUnit\Framework\TestCase
                 ),
                 50307,
             ],
+            */
         ];
     }
 
@@ -424,6 +438,7 @@ class ChannelTest extends \PHPUnit\Framework\TestCase
                 ),
                 50353,
             ],
+            /*
             [
                 \Grpc\ChannelCredentials::createXds(
                     \Grpc\ChannelCredentials::createSsl()
@@ -440,6 +455,7 @@ class ChannelTest extends \PHPUnit\Framework\TestCase
                 ),
                 50355,
             ],
+            */
             [
                 \Grpc\ChannelCredentials::createSsl(),
                 \Grpc\ChannelCredentials::createXds(
index b6638b3..d182b9b 100644 (file)
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio/grpc/_grpcio_metadata.py.template`!!!
 
-__version__ = """1.37.0"""
+__version__ = """1.37.1"""
index a06053e..ece1ae8 100644 (file)
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio/grpc_version.py.template`!!!
 
-VERSION = '1.37.0'
+VERSION = '1.37.1'
index 2c1abbc..197b529 100644 (file)
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_channelz/grpc_version.py.template`!!!
 
-VERSION = '1.37.0'
+VERSION = '1.37.1'
index 42713fd..3e60e12 100644 (file)
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_health_checking/grpc_version.py.template`!!!
 
-VERSION = '1.37.0'
+VERSION = '1.37.1'
index bfbcd8e..7b3e707 100644 (file)
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_reflection/grpc_version.py.template`!!!
 
-VERSION = '1.37.0'
+VERSION = '1.37.1'
index e8bb2fa..95a2a87 100644 (file)
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_status/grpc_version.py.template`!!!
 
-VERSION = '1.37.0'
+VERSION = '1.37.1'
index 527e922..899dae7 100644 (file)
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_testing/grpc_version.py.template`!!!
 
-VERSION = '1.37.0'
+VERSION = '1.37.1'
index ea7fec4..69d40de 100644 (file)
@@ -14,4 +14,4 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/src/python/grpcio_tests/grpc_version.py.template`!!!
 
-VERSION = '1.37.0'
+VERSION = '1.37.1'
index d620ac9..31425b1 100644 (file)
@@ -14,5 +14,5 @@
 
 # GRPC contains the General RPC module.
 module GRPC
-  VERSION = '1.37.0'
+  VERSION = '1.37.1'
 end
index 7f0ce44..ed111b0 100755 (executable)
@@ -283,9 +283,7 @@ def run_test_loop(stub, target_seconds_between_rpcs, fail_on_failed_rpcs)
         raise "Unsupported rpc #{rpc}"
       end
       rpc_stats_key = rpc.to_s
-      if metadata.key?('rpc-behavior') and
-        ((metadata['rpc-behavior'] == 'keep-open') or
-         (metadata['rpc-behavior'].start_with?('sleep')))
+      if metadata.key?('rpc-behavior') or metadata.key?('fi_testcase')
         keep_open_threads << execute_rpc_in_thread(op, rpc_stats_key)
       else
         results[rpc] = execute_rpc(op, fail_on_failed_rpcs, rpc_stats_key)
index a3bc3c7..f1e378e 100644 (file)
@@ -14,6 +14,6 @@
 
 module GRPC
   module Tools
-    VERSION = '1.37.0'
+    VERSION = '1.37.1'
   end
 end
index 4b5e04e..f4afa1d 100644 (file)
@@ -37,6 +37,7 @@
       "/I"+configure_module_dirname+"\\third_party\\boringssl-with-bazel\\src\\include "+
       "/I"+configure_module_dirname+"\\third_party\\re2 "+
       "/I"+configure_module_dirname+"\\third_party\\upb "+
+      "/I"+configure_module_dirname+"\\third_party\\xxhash "+
       "/I"+configure_module_dirname+"\\third_party\\zlib ");
   <%
     dirs = sorted(set(src[:src.rfind('/')] for src in srcs))
index e92c327..85a84a7 100644 (file)
@@ -229,6 +229,8 @@ grpc_cc_binary(
     ],
     deps = [
         "//:grpc++",
+        "//:grpc++_reflection",
+        "//:grpcpp_admin",
         "//src/proto/grpc/testing:empty_proto",
         "//src/proto/grpc/testing:messages_proto",
         "//src/proto/grpc/testing:test_proto",
@@ -248,6 +250,8 @@ grpc_cc_binary(
     ],
     deps = [
         "//:grpc++",
+        "//:grpc++_reflection",
+        "//:grpcpp_admin",
         "//src/proto/grpc/testing:empty_proto",
         "//src/proto/grpc/testing:messages_proto",
         "//src/proto/grpc/testing:test_proto",
index 7344905..f8ced82 100644 (file)
@@ -16,6 +16,8 @@
  *
  */
 
+#include <grpcpp/ext/admin_services.h>
+#include <grpcpp/ext/proto_server_reflection_plugin.h>
 #include <grpcpp/grpcpp.h>
 #include <grpcpp/server.h>
 #include <grpcpp/server_builder.h>
@@ -513,9 +515,11 @@ void RunServer(const int port, StatsWatchers* stats_watchers,
   LoadBalancerStatsServiceImpl stats_service(stats_watchers);
   XdsUpdateClientConfigureServiceImpl client_config_service(rpc_configs_queue);
 
+  grpc::reflection::InitProtoReflectionServerBuilderPlugin();
   ServerBuilder builder;
   builder.RegisterService(&stats_service);
   builder.RegisterService(&client_config_service);
+  grpc::AddAdminServices(&builder);
   builder.AddListeningPort(server_address.str(),
                            grpc::InsecureServerCredentials());
   std::unique_ptr<Server> server(builder.BuildAndStart());
index 0dfef79..ea7c6f2 100644 (file)
@@ -19,6 +19,8 @@
 #include <grpc/grpc.h>
 #include <grpc/support/log.h>
 #include <grpc/support/time.h>
+#include <grpcpp/ext/admin_services.h>
+#include <grpcpp/ext/proto_server_reflection_plugin.h>
 #include <grpcpp/health_check_service_interface.h>
 #include <grpcpp/server.h>
 #include <grpcpp/server_builder.h>
@@ -123,6 +125,7 @@ void RunServer(bool secure_mode, const int port, const int maintenance_port,
       grpc::health::v1::HealthCheckResponse::SERVING);
   XdsUpdateHealthServiceImpl update_health_service(&health_check_service);
 
+  grpc::reflection::InitProtoReflectionServerBuilderPlugin();
   ServerBuilder builder;
   if (secure_mode) {
     XdsServerBuilder xds_builder;
@@ -134,6 +137,7 @@ void RunServer(bool secure_mode, const int port, const int maintenance_port,
     gpr_log(GPR_INFO, "Server starting on 0.0.0.0:%d", port);
     builder.RegisterService(&health_check_service);
     builder.RegisterService(&update_health_service);
+    grpc::AddAdminServices(&builder);
     builder.AddListeningPort(absl::StrCat("0.0.0.0:", maintenance_port),
                              grpc::InsecureServerCredentials());
     server = builder.BuildAndStart();
@@ -143,6 +147,7 @@ void RunServer(bool secure_mode, const int port, const int maintenance_port,
     builder.RegisterService(&service);
     builder.RegisterService(&health_check_service);
     builder.RegisterService(&update_health_service);
+    grpc::AddAdminServices(&builder);
     builder.AddListeningPort(absl::StrCat("0.0.0.0:", port),
                              grpc::InsecureServerCredentials());
     server = builder.BuildAndStart();
@@ -155,7 +160,6 @@ void RunServer(bool secure_mode, const int port, const int maintenance_port,
 int main(int argc, char** argv) {
   grpc::testing::TestEnvironment env(argc, argv);
   grpc::testing::InitTest(&argc, &argv, true);
-
   char* hostname = grpc_gethostname();
   if (hostname == nullptr) {
     std::cout << "Failed to get hostname, terminating" << std::endl;
index ebff957..b351be4 100644 (file)
@@ -14,5 +14,5 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/tools/distrib/python/grpcio_tools/grpc_version.py.template`!!!
 
-VERSION = '1.37.0'
+VERSION = '1.37.1'
 PROTOBUF_VERSION = '3.15.2'
index 6947e6f..d608a5b 100644 (file)
@@ -14,5 +14,5 @@
 
 # AUTO-GENERATED FROM `$REPO_ROOT/templates/tools/distrib/python/grpcio_tools/grpc_version.py.template`!!!
 
-VERSION = '1.37.0'
+VERSION = '1.37.1'
 PROTOBUF_VERSION = '3.15.2'
diff --git a/tools/dockerfile/interoptest/grpc_interop_cxx_xds/Dockerfile.xds_client b/tools/dockerfile/interoptest/grpc_interop_cxx_xds/Dockerfile.xds_client
new file mode 100644 (file)
index 0000000..f0796a4
--- /dev/null
@@ -0,0 +1,38 @@
+# Copyright 2021 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.
+
+# Dockerfile for building //test/cpp/interop:xds_interop_client
+
+FROM phusion/baseimage:master@sha256:65ea10d5f757e5e86272625f8675d437dd83d8db64bdb429e2354d58f5462750
+
+RUN apt-get update -y && \
+        apt-get install -y \
+            build-essential \
+            clang \
+            python3 \
+            python3-dev
+
+WORKDIR /workdir
+
+RUN ln -s /usr/bin/python3 /usr/bin/python
+RUN mkdir /artifacts
+
+COPY . .
+RUN tools/bazel build //test/cpp/interop:xds_interop_client
+RUN cp -rL /workdir/bazel-bin/test/cpp/interop/xds_interop_client /artifacts/
+
+FROM phusion/baseimage:master@sha256:65ea10d5f757e5e86272625f8675d437dd83d8db64bdb429e2354d58f5462750
+COPY --from=0 /artifacts ./
+
+ENTRYPOINT ["/xds_interop_client"]
diff --git a/tools/dockerfile/interoptest/grpc_interop_cxx_xds/Dockerfile.xds_server b/tools/dockerfile/interoptest/grpc_interop_cxx_xds/Dockerfile.xds_server
new file mode 100644 (file)
index 0000000..cd081e1
--- /dev/null
@@ -0,0 +1,38 @@
+# Copyright 2021 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.
+
+# Dockerfile for building //test/cpp/interop:xds_interop_client
+
+FROM phusion/baseimage:master@sha256:65ea10d5f757e5e86272625f8675d437dd83d8db64bdb429e2354d58f5462750
+
+RUN apt-get update -y && \
+        apt-get install -y \
+            build-essential \
+            clang \
+            python3 \
+            python3-dev
+
+WORKDIR /workdir
+
+RUN ln -s /usr/bin/python3 /usr/bin/python
+RUN mkdir /artifacts
+
+COPY . .
+RUN tools/bazel build //test/cpp/interop:xds_interop_server
+RUN cp -rL /workdir/bazel-bin/test/cpp/interop/xds_interop_server /artifacts/
+
+FROM phusion/baseimage:master@sha256:65ea10d5f757e5e86272625f8675d437dd83d8db64bdb429e2354d58f5462750
+COPY --from=0 /artifacts ./
+
+ENTRYPOINT ["/xds_interop_server"]
index f2d87ea..e9b93f8 100644 (file)
@@ -40,7 +40,7 @@ PROJECT_NAME           = "GRPC C++"
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 1.37.0
+PROJECT_NUMBER         = 1.37.1
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
index b38fdba..c1e4ae1 100644 (file)
@@ -40,7 +40,7 @@ PROJECT_NAME           = "GRPC C++"
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 1.37.0
+PROJECT_NUMBER         = 1.37.1
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
index 9044ea6..d936870 100644 (file)
@@ -40,7 +40,7 @@ PROJECT_NAME           = "GRPC Objective-C"
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 1.37.0
+PROJECT_NUMBER         = 1.37.1
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
index 611639e..a9ece75 100644 (file)
@@ -40,7 +40,7 @@ PROJECT_NAME           = "GRPC Objective-C"
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 1.37.0
+PROJECT_NUMBER         = 1.37.1
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
index 65d4841..238762d 100644 (file)
@@ -40,7 +40,7 @@ PROJECT_NAME           = "GRPC PHP"
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = 1.37.0
+PROJECT_NUMBER         = 1.37.1
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
index b065ddb..6fb22b8 100755 (executable)
@@ -26,7 +26,7 @@ VIRTUAL_ENV=$(mktemp -d)
 virtualenv "$VIRTUAL_ENV" -p python3
 PYTHON="$VIRTUAL_ENV"/bin/python
 "$PYTHON" -m pip install --upgrade pip==19.3.1
-"$PYTHON" -m pip install --upgrade grpcio-tools google-api-python-client google-auth-httplib2 oauth2client
+"$PYTHON" -m pip install --upgrade grpcio-tools google-api-python-client google-auth-httplib2 oauth2client xds-protos
 
 # Prepare generated Python code.
 TOOLS_DIR=tools/run_tests
@@ -64,7 +64,7 @@ bazel build //src/python/grpcio_tests/tests_py3_only/interop:xds_interop_client
 # because not all interop clients in all languages support these new tests.
 GRPC_VERBOSITY=debug GRPC_TRACE=xds_client,xds_resolver,xds_cluster_manager_lb,cds_lb,xds_cluster_resolver_lb,priority_lb,xds_cluster_impl_lb,weighted_target_lb "$PYTHON" \
   tools/run_tests/run_xds_tests.py \
-    --test_case="all,path_matching,header_matching,circuit_breaking,timeout" \
+    --test_case="all,circuit_breaking,timeout,fault_injection" \
     --project_id=grpc-testing \
     --project_num=830293263384 \
     --source_image=projects/grpc-testing/global/images/xds-test-server-4 \
index d005405..8825719 100755 (executable)
@@ -26,7 +26,7 @@ VIRTUAL_ENV=$(mktemp -d)
 virtualenv "$VIRTUAL_ENV" -p python3
 PYTHON="$VIRTUAL_ENV"/bin/python
 "$PYTHON" -m pip install --upgrade pip==19.3.1
-"$PYTHON" -m pip install --upgrade grpcio grpcio-tools google-api-python-client google-auth-httplib2 oauth2client
+"$PYTHON" -m pip install --upgrade grpcio grpcio-tools google-api-python-client google-auth-httplib2 oauth2client xds-protos
 
 # Prepare generated Python code.
 TOOLS_DIR=tools/run_tests
@@ -67,7 +67,7 @@ bazel build test/cpp/interop:xds_interop_client
 # they are added into "all".
 GRPC_VERBOSITY=debug GRPC_TRACE=xds_client,xds_resolver,xds_cluster_manager_lb,cds_lb,xds_cluster_resolver_lb,priority_lb,xds_cluster_impl_lb,weighted_target_lb "$PYTHON" \
   tools/run_tests/run_xds_tests.py \
-    --test_case="all,path_matching,header_matching,circuit_breaking,timeout,fault_injection" \
+    --test_case="all,circuit_breaking,timeout,fault_injection,csds" \
     --project_id=grpc-testing \
     --project_num=830293263384 \
     --source_image=projects/grpc-testing/global/images/xds-test-server-4 \
index 9d45056..21b0d4e 100755 (executable)
@@ -26,7 +26,7 @@ VIRTUAL_ENV=$(mktemp -d)
 virtualenv "$VIRTUAL_ENV" -p python3
 PYTHON="$VIRTUAL_ENV"/bin/python
 "$PYTHON" -m pip install --upgrade pip==19.3.1
-"$PYTHON" -m pip install --upgrade grpcio grpcio-tools google-api-python-client google-auth-httplib2 oauth2client
+"$PYTHON" -m pip install --upgrade grpcio grpcio-tools google-api-python-client google-auth-httplib2 oauth2client xds-protos
 
 # Prepare generated Python code.
 TOOLS_DIR=tools/run_tests
diff --git a/tools/internal_ci/linux/grpc_xds_k8s.cfg b/tools/internal_ci/linux/grpc_xds_k8s.cfg
new file mode 100644 (file)
index 0000000..8de4c18
--- /dev/null
@@ -0,0 +1,26 @@
+# Copyright 2021 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.
+
+# Config file for the internal CI (in protobuf text format)
+
+# Location of the continuous shell script in repository.
+build_file: "grpc/tools/internal_ci/linux/grpc_xds_k8s.sh"
+timeout_mins: 180
+action {
+  define_artifacts {
+    regex: "artifacts/**/*sponge_log.xml"
+    regex: "artifacts/**/*sponge_log.log"
+    strip_prefix: "artifacts"
+  }
+}
diff --git a/tools/internal_ci/linux/grpc_xds_k8s.sh b/tools/internal_ci/linux/grpc_xds_k8s.sh
new file mode 100755 (executable)
index 0000000..b14c66f
--- /dev/null
@@ -0,0 +1,151 @@
+#!/usr/bin/env bash
+# Copyright 2021 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.
+
+set -ex -o igncr || set -ex
+
+# Constants
+readonly GITHUB_REPOSITORY_NAME="grpc"
+# GKE Cluster
+readonly GKE_CLUSTER_NAME="interop-test-psm-sec-testing-api"
+readonly GKE_CLUSTER_ZONE="us-west1-b"
+export CLOUDSDK_API_ENDPOINT_OVERRIDES_CONTAINER="https://test-container.sandbox.googleapis.com/"
+## xDS test server/client Docker images
+readonly SERVER_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/cpp-server"
+readonly CLIENT_IMAGE_NAME="gcr.io/grpc-testing/xds-interop/cpp-client"
+readonly FORCE_IMAGE_BUILD="${FORCE_IMAGE_BUILD:-0}"
+readonly BUILD_APP_PATH="interop-testing/build/install/grpc-interop-testing"
+readonly TEST_DRIVER_REPO_DIR_USE_EXISTING=1
+
+#######################################
+# Builds test app Docker images and pushes them to GCR
+# Globals:
+#   BUILD_APP_PATH
+#   SERVER_IMAGE_NAME: Test server Docker image name
+#   CLIENT_IMAGE_NAME: Test client Docker image name
+#   GIT_COMMIT: SHA-1 of git commit being built
+# Arguments:
+#   None
+# Outputs:
+#   Writes the output of `gcloud builds submit` to stdout, stderr
+#######################################
+build_test_app_docker_images() {
+  echo "Building C++ xDS interop test app Docker images"
+  docker build -f "${SRC_DIR}/tools/dockerfile/interoptest/grpc_interop_cxx_xds/Dockerfile.xds_client" -t "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" "${SRC_DIR}"
+  docker build -f "${SRC_DIR}/tools/dockerfile/interoptest/grpc_interop_cxx_xds/Dockerfile.xds_server" -t "${SERVER_IMAGE_NAME}:${GIT_COMMIT}" "${SRC_DIR}"
+  gcloud -q auth configure-docker
+  docker push "${CLIENT_IMAGE_NAME}:${GIT_COMMIT}"
+  docker push "${SERVER_IMAGE_NAME}:${GIT_COMMIT}"
+}
+
+#######################################
+# Builds test app and its docker images unless they already exist
+# Globals:
+#   SERVER_IMAGE_NAME: Test server Docker image name
+#   CLIENT_IMAGE_NAME: Test client Docker image name
+#   GIT_COMMIT: SHA-1 of git commit being built
+#   FORCE_IMAGE_BUILD
+# Arguments:
+#   None
+# Outputs:
+#   Writes the output to stdout, stderr
+#######################################
+build_docker_images_if_needed() {
+  # Check if images already exist
+  server_tags="$(gcloud_gcr_list_image_tags "${SERVER_IMAGE_NAME}" "${GIT_COMMIT}")"
+  printf "Server image: %s:%s\n" "${SERVER_IMAGE_NAME}" "${GIT_COMMIT}"
+  echo "${server_tags:-Server image not found}"
+
+  client_tags="$(gcloud_gcr_list_image_tags "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}")"
+  printf "Client image: %s:%s\n" "${CLIENT_IMAGE_NAME}" "${GIT_COMMIT}"
+  echo "${client_tags:-Client image not found}"
+
+  # Build if any of the images are missing, or FORCE_IMAGE_BUILD=1
+  if [[ "${FORCE_IMAGE_BUILD}" == "1" || -z "${server_tags}" || -z "${client_tags}" ]]; then
+    build_test_app_docker_images
+  else
+    echo "Skipping C++ test app build"
+  fi
+}
+
+#######################################
+# Executes the test case
+# Globals:
+#   TEST_DRIVER_FLAGFILE: Relative path to test driver flagfile
+#   KUBE_CONTEXT: The name of kubectl context with GKE cluster access
+#   TEST_XML_OUTPUT_DIR: Output directory for the test xUnit XML report
+#   SERVER_IMAGE_NAME: Test server Docker image name
+#   CLIENT_IMAGE_NAME: Test client Docker image name
+#   GIT_COMMIT: SHA-1 of git commit being built
+# Arguments:
+#   Test case name
+# Outputs:
+#   Writes the output of test execution to stdout, stderr
+#   Test xUnit report to ${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml
+#######################################
+run_test() {
+  # Test driver usage:
+  # https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#basic-usage
+  local test_name="${1:?Usage: run_test test_name}"
+  set -x
+  python -m "tests.${test_name}" \
+    --flagfile="${TEST_DRIVER_FLAGFILE}" \
+    --kube_context="${KUBE_CONTEXT}" \
+    --server_image="${SERVER_IMAGE_NAME}:${GIT_COMMIT}" \
+    --client_image="${CLIENT_IMAGE_NAME}:${GIT_COMMIT}" \
+    --xml_output_file="${TEST_XML_OUTPUT_DIR}/${test_name}/sponge_log.xml" \
+    --force_cleanup \
+    --nocheck_local_certs
+  set +x
+}
+
+#######################################
+# Main function: provision software necessary to execute tests, and run them
+# Globals:
+#   KOKORO_ARTIFACTS_DIR
+#   GITHUB_REPOSITORY_NAME
+#   SRC_DIR: Populated with absolute path to the source repo
+#   TEST_DRIVER_REPO_DIR: Populated with the path to the repo containing
+#                         the test driver
+#   TEST_DRIVER_FULL_DIR: Populated with the path to the test driver source code
+#   TEST_DRIVER_FLAGFILE: Populated with relative path to test driver flagfile
+#   TEST_XML_OUTPUT_DIR: Populated with the path to test xUnit XML report
+#   GIT_ORIGIN_URL: Populated with the origin URL of git repo used for the build
+#   GIT_COMMIT: Populated with the SHA-1 of git commit being built
+#   GIT_COMMIT_SHORT: Populated with the short SHA-1 of git commit being built
+#   KUBE_CONTEXT: Populated with name of kubectl context with GKE cluster access
+# Arguments:
+#   None
+# Outputs:
+#   Writes the output of test execution to stdout, stderr
+#######################################
+main() {
+  local script_dir
+  script_dir="$(dirname "$0")"
+  # shellcheck source=tools/internal_ci/linux/grpc_xds_k8s_install_test_driver.sh
+  source "${script_dir}/grpc_xds_k8s_install_test_driver.sh"
+  set -x
+  if [[ -n "${KOKORO_ARTIFACTS_DIR}" ]]; then
+    kokoro_setup_test_driver "${GITHUB_REPOSITORY_NAME}"
+  else
+    local_setup_test_driver "${script_dir}"
+  fi
+  build_docker_images_if_needed
+  # Run tests
+  cd "${TEST_DRIVER_FULL_DIR}"
+  run_test baseline_test
+  run_test security_test
+}
+
+main "$@"
diff --git a/tools/internal_ci/linux/grpc_xds_k8s_install_test_driver.sh b/tools/internal_ci/linux/grpc_xds_k8s_install_test_driver.sh
new file mode 100644 (file)
index 0000000..34979b0
--- /dev/null
@@ -0,0 +1,362 @@
+#!/usr/bin/env bash
+# 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.
+# TODO(sergiitk): move to grpc/grpc when implementing support of other languages
+set -eo pipefail
+
+# Constants
+readonly PYTHON_VERSION="3.6"
+# Test driver
+readonly TEST_DRIVER_REPO_NAME="grpc"
+readonly TEST_DRIVER_REPO_URL="https://github.com/grpc/grpc.git"
+readonly TEST_DRIVER_BRANCH="${TEST_DRIVER_BRANCH:-master}"
+readonly TEST_DRIVER_PATH="tools/run_tests/xds_k8s_test_driver"
+readonly TEST_DRIVER_PROTOS_PATH="src/proto/grpc/testing"
+
+#######################################
+# Run command end report its exit code. Doesn't exit on non-zero exit code.
+# Globals:
+#   None
+# Arguments:
+#   Command to execute
+# Outputs:
+#   Writes the output of given command to stdout, stderr
+#######################################
+run_ignore_exit_code() {
+  local exit_code=-1
+  "$@" || exit_code=$?
+  echo "Exit code: ${exit_code}"
+}
+
+#######################################
+# Parses information about git repository at given path to global variables.
+# Globals:
+#   GIT_ORIGIN_URL: Populated with the origin URL of git repo used for the build
+#   GIT_COMMIT: Populated with the SHA-1 of git commit being built
+#   GIT_COMMIT_SHORT: Populated with the short SHA-1 of git commit being built
+# Arguments:
+#   Git source dir
+#######################################
+parse_src_repo_git_info() {
+  local src_dir="${SRC_DIR:?SRC_DIR must be set}"
+  readonly GIT_ORIGIN_URL=$(git -C "${src_dir}" remote get-url origin)
+  readonly GIT_COMMIT=$(git -C "${src_dir}" rev-parse HEAD)
+  readonly GIT_COMMIT_SHORT=$(git -C "${src_dir}" rev-parse --short HEAD)
+}
+
+#######################################
+# List GCR image tags matching given tag name.
+# Arguments:
+#   Image name
+#   Tag name
+# Outputs:
+#   Writes the table with the list of found tags to stdout.
+#   If no tags found, the output is an empty string.
+#######################################
+gcloud_gcr_list_image_tags() {
+  gcloud container images list-tags --format="table[box](tags,digest,timestamp.date())" --filter="tags:$2" "$1"
+}
+
+#######################################
+# A helper to execute `gcloud -q components update`.
+# Arguments:
+#   None
+# Outputs:
+#   Writes the output of `gcloud` command to stdout, stderr
+#######################################
+gcloud_update() {
+  echo "Update gcloud components:"
+  gcloud -q components update
+}
+
+#######################################
+# Create kube context authenticated with GKE cluster, saves context name.
+# to KUBE_CONTEXT
+# Globals:
+#   GKE_CLUSTER_NAME
+#   GKE_CLUSTER_ZONE
+#   KUBE_CONTEXT: Populated with name of kubectl context with GKE cluster access
+# Arguments:
+#   None
+# Outputs:
+#   Writes the output of `gcloud` command to stdout, stderr
+#   Writes authorization info $HOME/.kube/config
+#######################################
+gcloud_get_cluster_credentials() {
+  gcloud container clusters get-credentials "${GKE_CLUSTER_NAME}" --zone "${GKE_CLUSTER_ZONE}"
+  readonly KUBE_CONTEXT="$(kubectl config current-context)"
+}
+
+#######################################
+# Clone the source code of the test driver to $TEST_DRIVER_REPO_DIR, unless
+# given folder exists.
+# Globals:
+#   TEST_DRIVER_REPO_URL
+#   TEST_DRIVER_BRANCH
+#   TEST_DRIVER_REPO_DIR: path to the repo containing the test driver
+#   TEST_DRIVER_REPO_DIR_USE_EXISTING: set non-empty value to use exiting
+#      clone of the driver repo located at $TEST_DRIVER_REPO_DIR.
+#      Useful for debugging the build script locally.
+# Arguments:
+#   None
+# Outputs:
+#   Writes the output of `git` command to stdout, stderr
+#   Writes driver source code to $TEST_DRIVER_REPO_DIR
+#######################################
+test_driver_get_source() {
+  if [[ -n "${TEST_DRIVER_REPO_DIR_USE_EXISTING}" && -d "${TEST_DRIVER_REPO_DIR}" ]]; then
+    echo "Using exiting driver directory: ${TEST_DRIVER_REPO_DIR}."
+  else
+    echo "Cloning driver to ${TEST_DRIVER_REPO_URL} branch ${TEST_DRIVER_BRANCH} to ${TEST_DRIVER_REPO_DIR}"
+    git clone -b "${TEST_DRIVER_BRANCH}" --depth=1 "${TEST_DRIVER_REPO_URL}" "${TEST_DRIVER_REPO_DIR}"
+  fi
+}
+
+#######################################
+# Install Python modules from required in $TEST_DRIVER_FULL_DIR/requirements.txt
+# to Python virtual environment. Creates and activates Python venv if necessary.
+# Globals:
+#   TEST_DRIVER_FULL_DIR
+#   PYTHON_VERSION
+# Arguments:
+#   None
+# Outputs:
+#   Writes the output of `python`, `pip` commands to stdout, stderr
+#   Writes the list of installed modules to stdout
+#######################################
+test_driver_pip_install() {
+  echo "Install python dependencies"
+  cd "${TEST_DRIVER_FULL_DIR}"
+
+  # Create and activate virtual environment unless already using one
+  if [[ -z "${VIRTUAL_ENV}" ]]; then
+    local venv_dir="${TEST_DRIVER_FULL_DIR}/venv"
+    if [[ -d "${venv_dir}" ]]; then
+      echo "Found python virtual environment directory: ${venv_dir}"
+    else
+      echo "Creating python virtual environment: ${venv_dir}"
+      "python${PYTHON_VERSION} -m venv ${venv_dir}"
+    fi
+    # Intentional: No need to check python venv activate script.
+    # shellcheck source=/dev/null
+    source "${venv_dir}/bin/activate"
+  fi
+
+  pip install -r requirements.txt
+  echo "Installed Python packages:"
+  pip list
+}
+
+#######################################
+# Compile proto-files needed for the test driver
+# Globals:
+#   TEST_DRIVER_REPO_DIR
+#   TEST_DRIVER_FULL_DIR
+#   TEST_DRIVER_PROTOS_PATH
+# Arguments:
+#   None
+# Outputs:
+#   Writes the output of `python -m grpc_tools.protoc` to stdout, stderr
+#   Writes the list if compiled python code to stdout
+#   Writes compiled python code with proto messages and grpc services to
+#   $TEST_DRIVER_FULL_DIR/src/proto
+#######################################
+test_driver_compile_protos() {
+  declare -a protos
+  protos=(
+    "${TEST_DRIVER_PROTOS_PATH}/test.proto"
+    "${TEST_DRIVER_PROTOS_PATH}/messages.proto"
+    "${TEST_DRIVER_PROTOS_PATH}/empty.proto"
+  )
+  echo "Generate python code from grpc.testing protos: ${protos[*]}"
+  cd "${TEST_DRIVER_REPO_DIR}"
+  python -m grpc_tools.protoc \
+    --proto_path=. \
+    --python_out="${TEST_DRIVER_FULL_DIR}" \
+    --grpc_python_out="${TEST_DRIVER_FULL_DIR}" \
+    "${protos[@]}"
+  local protos_out_dir="${TEST_DRIVER_FULL_DIR}/${TEST_DRIVER_PROTOS_PATH}"
+  echo "Generated files ${protos_out_dir}:"
+  ls -Fl "${protos_out_dir}"
+}
+
+#######################################
+# Installs the test driver and it's requirements.
+# https://github.com/grpc/grpc/tree/master/tools/run_tests/xds_k8s_test_driver#installation
+# Globals:
+#   TEST_DRIVER_REPO_DIR: Populated with the path to the repo containing
+#                         the test driver
+#   TEST_DRIVER_FULL_DIR: Populated with the path to the test driver source code
+# Arguments:
+#   The directory for test driver's source code
+# Outputs:
+#   Writes the output to stdout, stderr
+#######################################
+test_driver_install() {
+  readonly TEST_DRIVER_REPO_DIR="${1:?Usage test_driver_install TEST_DRIVER_REPO_DIR}"
+  readonly TEST_DRIVER_FULL_DIR="${TEST_DRIVER_REPO_DIR}/${TEST_DRIVER_PATH}"
+  test_driver_get_source
+  test_driver_pip_install
+  test_driver_compile_protos
+}
+
+#######################################
+# Outputs Kokoro image version and Ubuntu's lsb_release
+# Arguments:
+#   None
+# Outputs:
+#   Writes the output to stdout
+#######################################
+kokoro_print_version() {
+  echo "Kokoro VM version:"
+  if [[ -f /VERSION ]]; then
+    cat /VERSION
+  fi
+  run_ignore_exit_code lsb_release -a
+}
+
+#######################################
+# Report extra information about the job via sponge properties.
+# Globals:
+#   KOKORO_ARTIFACTS_DIR
+#   GIT_ORIGIN_URL
+#   GIT_COMMIT_SHORT
+#   TESTGRID_EXCLUDE
+# Arguments:
+#   None
+# Outputs:
+#   Writes the output to stdout
+#   Writes job properties to $KOKORO_ARTIFACTS_DIR/custom_sponge_config.csv
+#######################################
+kokoro_write_sponge_properties() {
+  # CSV format: "property_name","property_value"
+  # Bump TESTS_FORMAT_VERSION when reported test name changed enough to when it
+  # makes more sense to discard previous test results from a testgrid board.
+  # Use GIT_ORIGIN_URL to exclude test runs executed against repo forks from
+  # testgrid reports.
+  cat >"${KOKORO_ARTIFACTS_DIR}/custom_sponge_config.csv" <<EOF
+TESTS_FORMAT_VERSION,2
+TESTGRID_EXCLUDE,${TESTGRID_EXCLUDE:-0}
+GIT_ORIGIN_URL,${GIT_ORIGIN_URL:?GIT_ORIGIN_URL must be set}
+GIT_COMMIT_SHORT,${GIT_COMMIT_SHORT:?GIT_COMMIT_SHORT must be set}
+EOF
+  echo "Sponge properties:"
+  cat "${KOKORO_ARTIFACTS_DIR}/custom_sponge_config.csv"
+}
+
+#######################################
+# Configure Python virtual environment on Kokoro VM.
+# Arguments:
+#   None
+# Outputs:
+#   Writes the output of `pyenv` commands to stdout
+#######################################
+kokoro_setup_python_virtual_environment() {
+  # Kokoro provides pyenv, so use it instead of `python -m venv`
+  echo "Setup pyenv environment"
+  eval "$(pyenv init -)"
+  eval "$(pyenv virtualenv-init -)"
+  py_latest_patch="$(pyenv versions --bare --skip-aliases | grep -E "^${PYTHON_VERSION}\.[0-9]{1,2}$" | sort --version-sort | tail -n 1)"
+  echo "Activating python ${py_latest_patch} virtual environment"
+  pyenv virtualenv --no-pip "${py_latest_patch}" k8s_xds_test_runner
+  pyenv local k8s_xds_test_runner
+  pyenv activate k8s_xds_test_runner
+  python -m ensurepip
+  # pip is fixed to 21.0.1 due to issue https://github.com/pypa/pip/pull/9835
+  # internal details: b/186411224
+  # TODO(sergiitk): revert https://github.com/grpc/grpc/pull/26087 when 21.1.1 released
+  python -m pip install -U pip==21.0.1
+  pip --version
+}
+
+#######################################
+# Installs and configures the test driver on Kokoro VM.
+# Globals:
+#   KOKORO_ARTIFACTS_DIR
+#   TEST_DRIVER_REPO_NAME
+#   SRC_DIR: Populated with absolute path to the source repo on Kokoro VM
+#   TEST_DRIVER_REPO_DIR: Populated with the path to the repo containing
+#                         the test driver
+#   TEST_DRIVER_FULL_DIR: Populated with the path to the test driver source code
+#   TEST_DRIVER_FLAGFILE: Populated with relative path to test driver flagfile
+#   TEST_XML_OUTPUT_DIR: Populated with the path to test xUnit XML report
+#   KUBE_CONTEXT: Populated with name of kubectl context with GKE cluster access
+#   GIT_ORIGIN_URL: Populated with the origin URL of git repo used for the build
+#   GIT_COMMIT: Populated with the SHA-1 of git commit being built
+#   GIT_COMMIT_SHORT: Populated with the short SHA-1 of git commit being built
+# Arguments:
+#   The name of github repository being built
+# Outputs:
+#   Writes the output to stdout, stderr, files
+#######################################
+kokoro_setup_test_driver() {
+  local src_repository_name="${1:?Usage kokoro_setup_test_driver GITHUB_REPOSITORY_NAME}"
+  # Capture Kokoro VM version info in the log.
+  kokoro_print_version
+
+  # Kokoro clones repo to ${KOKORO_ARTIFACTS_DIR}/github/${GITHUB_REPOSITORY}
+  local github_root="${KOKORO_ARTIFACTS_DIR}/github"
+  readonly SRC_DIR="${github_root}/${src_repository_name}"
+  local test_driver_repo_dir="${github_root}/${TEST_DRIVER_REPO_NAME}"
+  parse_src_repo_git_info SRC_DIR
+  kokoro_write_sponge_properties
+  kokoro_setup_python_virtual_environment
+
+  # gcloud requires python, so this should be executed after pyenv setup
+  gcloud_update
+  gcloud_get_cluster_credentials
+  test_driver_install "${test_driver_repo_dir}"
+  # shellcheck disable=SC2034  # Used in the main script
+  readonly TEST_DRIVER_FLAGFILE="config/grpc-testing.cfg"
+  # Test artifacts dir: xml reports, logs, etc.
+  local artifacts_dir="${KOKORO_ARTIFACTS_DIR}/artifacts"
+  # Folders after $artifacts_dir reported as target name
+  readonly TEST_XML_OUTPUT_DIR="${artifacts_dir}/${KOKORO_JOB_NAME}"
+  mkdir -p "${artifacts_dir}" "${TEST_XML_OUTPUT_DIR}"
+}
+
+#######################################
+# Installs and configures the test driver for testing build script locally.
+# Globals:
+#   TEST_DRIVER_REPO_NAME
+#   TEST_DRIVER_REPO_DIR: Unless provided, populated with a temporary dir with
+#                         the path to the test driver repo
+#   SRC_DIR: Populated with absolute path to the source repo
+#   TEST_DRIVER_FULL_DIR: Populated with the path to the test driver source code
+#   TEST_DRIVER_FLAGFILE: Populated with relative path to test driver flagfile
+#   TEST_XML_OUTPUT_DIR: Populated with the path to test xUnit XML report
+#   GIT_ORIGIN_URL: Populated with the origin URL of git repo used for the build
+#   GIT_COMMIT: Populated with the SHA-1 of git commit being built
+#   GIT_COMMIT_SHORT: Populated with the short SHA-1 of git commit being built
+#   KUBE_CONTEXT: Populated with name of kubectl context with GKE cluster access
+# Arguments:
+#   The path to the folder containing the build script
+# Outputs:
+#   Writes the output to stdout, stderr, files
+#######################################
+local_setup_test_driver() {
+  local script_dir="${1:?Usage: local_setup_test_driver SCRIPT_DIR}"
+  readonly SRC_DIR="$(git -C "${script_dir}" rev-parse --show-toplevel)"
+  parse_src_repo_git_info SRC_DIR
+  readonly KUBE_CONTEXT="${KUBE_CONTEXT:-$(kubectl config current-context)}"
+  local test_driver_repo_dir
+  test_driver_repo_dir="${TEST_DRIVER_REPO_DIR:-$(mktemp -d)/${TEST_DRIVER_REPO_NAME}}"
+  test_driver_install "${test_driver_repo_dir}"
+  # shellcheck disable=SC2034  # Used in the main script
+  readonly TEST_DRIVER_FLAGFILE="config/local-dev.cfg"
+  # Test out
+  readonly TEST_XML_OUTPUT_DIR="${TEST_DRIVER_FULL_DIR}/out"
+  mkdir -p "${TEST_XML_OUTPUT_DIR}"
+}
+
index e518072..9d87920 100755 (executable)
@@ -26,7 +26,7 @@ VIRTUAL_ENV=$(mktemp -d)
 virtualenv "$VIRTUAL_ENV" -p python3
 PYTHON="$VIRTUAL_ENV"/bin/python
 "$PYTHON" -m pip install --upgrade pip==19.3.1
-"$PYTHON" -m pip install --upgrade grpcio-tools google-api-python-client google-auth-httplib2 oauth2client
+"$PYTHON" -m pip install --upgrade grpcio-tools google-api-python-client google-auth-httplib2 oauth2client xds-protos
 
 # Prepare generated Python code.
 TOOLS_DIR=tools/run_tests
@@ -72,10 +72,23 @@ export CC=/usr/bin/gcc
 
 GRPC_VERBOSITY=debug GRPC_TRACE=xds_client,xds_resolver,xds_cluster_manager_lb,cds_lb,xds_cluster_resolver_lb,priority_lb,xds_cluster_impl_lb,weighted_target_lb "$PYTHON" \
   tools/run_tests/run_xds_tests.py \
+  --test_case="timeout,fault_injection" \
+  --project_id=grpc-testing \
+  --project_num=830293263384 \
+  --source_image=projects/grpc-testing/global/images/xds-test-server-4 \
+  --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \
+  --gcp_suffix=$(date '+%s') \
+  --verbose \
+  --qps=20 \
+  ${XDS_V3_OPT-} \
+  --client_cmd='./src/php/bin/run_xds_client.sh --server=xds:///{server_uri} --stats_port={stats_port} --qps={qps} {fail_on_failed_rpc} {rpcs_to_send} {metadata_to_send}'
+
+GRPC_VERBOSITY=debug GRPC_TRACE=xds_client,xds_resolver,xds_cluster_manager_lb,cds_lb,xds_cluster_resolver_lb,priority_lb,xds_cluster_impl_lb,weighted_target_lb "$PYTHON" \
+  tools/run_tests/run_xds_tests.py \
   --test_case="all,path_matching,header_matching" \
   --project_id=grpc-testing \
   --project_num=830293263384 \
-  --source_image=projects/grpc-testing/global/images/xds-test-server-2 \
+  --source_image=projects/grpc-testing/global/images/xds-test-server-4 \
   --path_to_server_binary=/java_server/grpc-java/interop-testing/build/install/grpc-interop-testing/bin/xds-test-server \
   --gcp_suffix=$(date '+%s') \
   --verbose \
index 4105180..6c76f92 100755 (executable)
@@ -26,7 +26,7 @@ VIRTUAL_ENV=$(mktemp -d)
 virtualenv "$VIRTUAL_ENV" -p python3
 PYTHON="$VIRTUAL_ENV"/bin/python
 "$PYTHON" -m pip install --upgrade pip==19.3.1
-"$PYTHON" -m pip install --upgrade grpcio-tools google-api-python-client google-auth-httplib2 oauth2client
+"$PYTHON" -m pip install --upgrade grpcio-tools google-api-python-client google-auth-httplib2 oauth2client xds-protos
 
 # Prepare generated Python code.
 TOOLS_DIR=tools/run_tests
@@ -62,7 +62,7 @@ touch "$TOOLS_DIR"/src/proto/grpc/health/v1/__init__.py
 
 GRPC_VERBOSITY=debug GRPC_TRACE=xds_client,xds_resolver,xds_cluster_manager_lb,cds_lb,xds_cluster_resolver_lb,priority_lb,xds_cluster_impl_lb,weighted_target_lb "$PYTHON" \
   tools/run_tests/run_xds_tests.py \
-    --test_case="all,path_matching,header_matching,circuit_breaking,timeout" \
+    --test_case="all,circuit_breaking,timeout,fault_injection" \
     --project_id=grpc-testing \
     --project_num=830293263384 \
     --source_image=projects/grpc-testing/global/images/xds-test-server-4 \
index 7fb6c31..13e3d84 100644 (file)
@@ -387,6 +387,7 @@ def targets():
         PythonArtifact('manylinux2010', 'x86', 'cp37-cp37m'),
         PythonArtifact('manylinux2010', 'x86', 'cp38-cp38'),
         PythonArtifact('manylinux2010', 'x86', 'cp39-cp39'),
+        PythonArtifact('manylinux2014', 'aarch64', 'cp36-cp36m'),
         PythonArtifact('manylinux2014', 'aarch64', 'cp37-cp37m'),
         PythonArtifact('manylinux2014', 'aarch64', 'cp38-cp38'),
         PythonArtifact('manylinux2014', 'aarch64', 'cp39-cp39'),
index 5a65bc3..6187c88 100755 (executable)
@@ -20,7 +20,8 @@ cd "$(dirname "$0")/../../.."
 
 sudo apt-get install -y python3-pip
 sudo python3 -m pip install --upgrade pip
-sudo python3 -m pip install grpcio==1.31.0 grpcio-tools==1.31.0 google-api-python-client google-auth-httplib2 oauth2client
+sudo python3 -m pip install --upgrade setuptools
+sudo python3 -m pip install --upgrade grpcio==1.31.0 grpcio-tools==1.31.0 protobuf google-api-python-client google-auth-httplib2 oauth2client xds-protos
 
 # Prepare generated Python code.
 TOOLS_DIR=tools/run_tests
index e53557c..76b7ade 100755 (executable)
@@ -15,6 +15,7 @@
 """Run xDS integration tests on GCP using Traffic Director."""
 
 import argparse
+import datetime
 import googleapiclient.discovery
 import grpc
 import json
@@ -29,6 +30,7 @@ import tempfile
 import time
 import uuid
 
+from google.protobuf import json_format
 from oauth2client.client import GoogleCredentials
 
 import python_utils.jobset as jobset
@@ -40,6 +42,20 @@ from src.proto.grpc.testing import empty_pb2
 from src.proto.grpc.testing import messages_pb2
 from src.proto.grpc.testing import test_pb2_grpc
 
+# Envoy protos provided by PyPI package xds-protos
+# Needs to import the generated Python file to load descriptors
+try:
+    from envoy.service.status.v3 import csds_pb2
+    from envoy.service.status.v3 import csds_pb2_grpc
+    from envoy.extensions.filters.network.http_connection_manager.v3 import http_connection_manager_pb2
+    from envoy.extensions.filters.common.fault.v3 import fault_pb2
+    from envoy.extensions.filters.http.fault.v3 import fault_pb2
+    from envoy.extensions.filters.http.router.v3 import router_pb2
+except ImportError:
+    # These protos are required by CSDS test. We should not fail the entire
+    # script for one test case.
+    pass
+
 logger = logging.getLogger()
 console_handler = logging.StreamHandler()
 formatter = logging.Formatter(fmt='%(asctime)s: %(levelname)-8s %(message)s')
@@ -59,21 +75,23 @@ _TEST_CASES = [
     'secondary_locality_gets_no_requests_on_partial_primary_failure',
     'secondary_locality_gets_requests_on_primary_failure',
     'traffic_splitting',
+    'path_matching',
+    'header_matching',
 ]
+
 # Valid test cases, but not in all. So the tests can only run manually, and
 # aren't enabled automatically for all languages.
 #
 # TODO: Move them into _TEST_CASES when support is ready in all languages.
 _ADDITIONAL_TEST_CASES = [
-    'path_matching',
-    'header_matching',
     'circuit_breaking',
     'timeout',
     'fault_injection',
+    'csds',
 ]
 
 # Test cases that require the V3 API.  Skipped in older runs.
-_V3_TEST_CASES = frozenset(['timeout', 'fault_injection'])
+_V3_TEST_CASES = frozenset(['timeout', 'fault_injection', 'csds'])
 
 # Test cases that require the alpha API.  Skipped for stable API runs.
 _ALPHA_TEST_CASES = frozenset(['timeout'])
@@ -190,7 +208,7 @@ argp.add_argument('--network',
                   default='global/networks/default',
                   help='GCP network to use')
 argp.add_argument('--service_port_range',
-                  default='8080:8110',
+                  default='8080:8280',
                   type=parse_port_range,
                   help='Listening port for created gRPC backends. Specified as '
                   'either a single int or as a range in the format min:max, in '
@@ -353,6 +371,32 @@ def get_client_accumulated_stats():
             return response
 
 
+def get_client_xds_config_dump():
+    if CLIENT_HOSTS:
+        hosts = CLIENT_HOSTS
+    else:
+        hosts = ['localhost']
+    for host in hosts:
+        server_address = '%s:%d' % (host, args.stats_port)
+        with grpc.insecure_channel(server_address) as channel:
+            stub = csds_pb2_grpc.ClientStatusDiscoveryServiceStub(channel)
+            logger.debug('Fetching xDS config dump from %s', server_address)
+            response = stub.FetchClientStatus(csds_pb2.ClientStatusRequest(),
+                                              wait_for_ready=True,
+                                              timeout=_CONNECTION_TIMEOUT_SEC)
+            logger.debug('Fetched xDS config dump from %s', server_address)
+            if len(response.config) != 1:
+                logger.error('Unexpected number of ClientConfigs %d: %s',
+                             len(response.config), response)
+                return None
+            else:
+                # Converting the ClientStatusResponse into JSON, because many
+                # fields are packed in google.protobuf.Any. It will require many
+                # duplicated code to unpack proto message and inspect values.
+                return json_format.MessageToDict(
+                    response.config[0], preserving_proto_field_name=True)
+
+
 def configure_client(rpc_types, metadata=[], timeout_sec=None):
     if CLIENT_HOSTS:
         hosts = CLIENT_HOSTS
@@ -1768,6 +1812,93 @@ def test_fault_injection(gcp, original_backend_service, instance_group):
         set_validate_for_proxyless(gcp, True)
 
 
+def test_csds(gcp, original_backend_service, instance_group, server_uri):
+    test_csds_timeout_s = datetime.timedelta(minutes=5).total_seconds()
+    sleep_interval_between_attempts_s = datetime.timedelta(
+        seconds=2).total_seconds()
+    logger.info('Running test_csds')
+
+    logger.info('waiting for original backends to become healthy')
+    wait_for_healthy_backends(gcp, original_backend_service, instance_group)
+
+    # Test case timeout: 5 minutes
+    deadline = time.time() + test_csds_timeout_s
+    cnt = 0
+    while time.time() <= deadline:
+        client_config = get_client_xds_config_dump()
+        logger.info('test_csds attempt %d: received xDS config %s', cnt,
+                    json.dumps(client_config, indent=2))
+        if client_config is not None:
+            # Got the xDS config dump, now validate it
+            ok = True
+            try:
+                if client_config['node']['locality']['zone'] != args.zone:
+                    logger.info('Invalid zone %s != %s',
+                                client_config['node']['locality']['zone'],
+                                args.zone)
+                    ok = False
+                seen = set()
+                for xds_config in client_config['xds_config']:
+                    if 'listener_config' in xds_config:
+                        listener_name = xds_config['listener_config'][
+                            'dynamic_listeners'][0]['active_state']['listener'][
+                                'name']
+                        if listener_name != server_uri:
+                            logger.info('Invalid Listener name %s != %s',
+                                        listener_name, server_uri)
+                            ok = False
+                        else:
+                            seen.add('lds')
+                    elif 'route_config' in xds_config:
+                        num_vh = len(
+                            xds_config['route_config']['dynamic_route_configs']
+                            [0]['route_config']['virtual_hosts'])
+                        if num_vh <= 0:
+                            logger.info('Invalid number of VirtualHosts %s',
+                                        num_vh)
+                            ok = False
+                        else:
+                            seen.add('rds')
+                    elif 'cluster_config' in xds_config:
+                        cluster_type = xds_config['cluster_config'][
+                            'dynamic_active_clusters'][0]['cluster']['type']
+                        if cluster_type != 'EDS':
+                            logger.info('Invalid cluster type %s != EDS',
+                                        cluster_type)
+                            ok = False
+                        else:
+                            seen.add('cds')
+                    elif 'endpoint_config' in xds_config:
+                        sub_zone = xds_config["endpoint_config"][
+                            "dynamic_endpoint_configs"][0]["endpoint_config"][
+                                "endpoints"][0]["locality"]["sub_zone"]
+                        if args.zone not in sub_zone:
+                            logger.info('Invalid endpoint sub_zone %s',
+                                        sub_zone)
+                            ok = False
+                        else:
+                            seen.add('eds')
+                want = {'lds', 'rds', 'cds', 'eds'}
+                if seen != want:
+                    logger.info('Incomplete xDS config dump, seen=%s', seen)
+                    ok = False
+            except:
+                logger.exception('Error in xDS config dump:')
+                ok = False
+            finally:
+                if ok:
+                    # Successfully fetched xDS config, and they looks good.
+                    logger.info('success')
+                    return
+        logger.info('test_csds attempt %d failed', cnt)
+        # Give the client some time to fetch xDS resources
+        time.sleep(sleep_interval_between_attempts_s)
+        cnt += 1
+
+    raise RuntimeError('failed to receive a valid xDS config in %s seconds' %
+                       test_csds_timeout_s)
+
+
 def set_validate_for_proxyless(gcp, validate_for_proxyless):
     if not gcp.alpha_compute:
         logger.debug(
@@ -1824,7 +1955,7 @@ def is_primary_instance_group(gcp, instance_group):
 
 def get_startup_script(path_to_server_binary, service_port):
     if path_to_server_binary:
-        return "nohup %s --port=%d 1>/dev/null &" % (path_to_server_binary,
+        return 'nohup %s --port=%d 1>/dev/null &' % (path_to_server_binary,
                                                      service_port)
     else:
         return """#!/bin/bash
@@ -2719,6 +2850,8 @@ try:
                     test_timeout(gcp, backend_service, instance_group)
                 elif test_case == 'fault_injection':
                     test_fault_injection(gcp, backend_service, instance_group)
+                elif test_case == 'csds':
+                    test_csds(gcp, backend_service, instance_group, server_uri)
                 else:
                     logger.error('Unknown test case: %s', test_case)
                     sys.exit(1)
index 07bb8eb..e4c8090 100644 (file)
@@ -1,4 +1,4 @@
 --namespace=interop-psm-security
---td_bootstrap_image=gcr.io/grpc-testing/td-grpc-bootstrap:8af4336691ba23683ee3e280275894959dc47c4c
+--td_bootstrap_image=gcr.io/grpc-testing/td-grpc-bootstrap:2558ec79df06984ed0d37e9e69f34688ffe301bb
 --logger_levels=__main__:DEBUG,framework:INFO
 --verbosity=0
index 407d81f..25458bc 100644 (file)
@@ -137,7 +137,7 @@ class GcpApiManager:
         if version == 'v1':
             return secretmanager_v1.SecretManagerServiceClient()
 
-        raise NotImplementedError(f'Secrets Manager {version} not supported')
+        raise NotImplementedError(f'Secret Manager {version} not supported')
 
     def _build_from_discovery_v1(self, api_name, version):
         api = discovery.build(api_name,
index 302a410..6db4192 100644 (file)
@@ -17,6 +17,8 @@ spec:
       labels:
         app: ${deployment_name}
         owner: xds-k8s-interop-test
+      annotations:
+        security.cloud.google.com/use-workload-certificates: ""
     spec:
       serviceAccountName: ${service_account_name}
       containers:
@@ -43,9 +45,6 @@ spec:
           - mountPath: /tmp/grpc-xds/
             name: grpc-td-conf
             readOnly: true
-          - mountPath: /var/run/gke-spiffe/certs
-            name: gke-spiffe-certs-volume
-            readOnly: true
         resources:
           limits:
             cpu: 800m
@@ -79,7 +78,4 @@ spec:
         - name: grpc-td-conf
           emptyDir:
             medium: Memory
-        - name: gke-spiffe-certs-volume
-          csi:
-            driver: certs.spiffe.gke.io
 ...
index 7eb1c1c..151aede 100644 (file)
@@ -14,6 +14,8 @@ spec:
       app: ${deployment_name}
   template:
     metadata:
+      annotations:
+        security.cloud.google.com/use-workload-certificates: ""
       labels:
         app: ${deployment_name}
         owner: xds-k8s-interop-test
@@ -44,9 +46,6 @@ spec:
           - mountPath: /tmp/grpc-xds/
             name: grpc-td-conf
             readOnly: true
-          - mountPath: /var/run/gke-spiffe/certs
-            name: gke-spiffe-certs-volume
-            readOnly: true
         resources:
           limits:
             cpu: 800m
@@ -81,7 +80,4 @@ spec:
         - name: grpc-td-conf
           emptyDir:
             medium: Memory
-        - name: gke-spiffe-certs-volume
-          csi:
-            driver: certs.spiffe.gke.io
 ...