From 2d903e22dcedaa4b0592ba8426dc11c0c60c82f8 Mon Sep 17 00:00:00 2001 From: ZhouyihaiDing Date: Wed, 18 Apr 2018 21:49:44 -0700 Subject: [PATCH] Initial commit for Grpc Gcp library. --- .gitattributes | 5 + .php_cs.dist | 15 + LICENSE | 202 +++++ README.md | 14 + .../firestore}/examples/end2end/README.md | 0 .../examples/end2end/doc/.gitignore | 0 .../examples/end2end/doc/assets/image_0.png | Bin .../examples/end2end/doc/assets/image_1.png | Bin .../examples/end2end/doc/assets/image_2.png | Bin .../examples/end2end/doc/assets/image_3.png | Bin .../examples/end2end/doc/assets/image_4.png | Bin .../examples/end2end/doc/assets/image_5.png | Bin .../examples/end2end/doc/assets/image_6.png | Bin .../examples/end2end/doc/grpc-gcp-php.md | 0 .../.extra/google-grp-service-composer.tgz | Bin .../examples/end2end/src/.gitignore | 0 .../examples/end2end/src/Dockerfile | 0 .../examples/end2end/src/bin/interactive.php | 0 .../examples/end2end/src/composer.json | 0 .../examples/end2end/src/composer.lock | 0 .../examples/end2end/src/config/.gitignore | 0 .../end2end/src/config/application.config.php | 0 .../end2end/src/config/config.php.dist | 0 .../examples/end2end/src/firestore.php | 0 .../src/src/ApiMethods/Admin/CreateIndex.php | 0 .../src/src/ApiMethods/Admin/DeleteIndex.php | 0 .../src/src/ApiMethods/Admin/GetIndex.php | 0 .../src/src/ApiMethods/Admin/ListIndexes.php | 0 .../src/src/ApiMethods/BatchGetDocuments.php | 0 .../src/src/ApiMethods/BeginTransaction.php | 0 .../end2end/src/src/ApiMethods/Commit.php | 0 .../src/src/ApiMethods/CreateDocument.php | 0 .../src/src/ApiMethods/DeleteDocument.php | 0 .../src/src/ApiMethods/GetDocument.php | 0 .../src/src/ApiMethods/ListCollectionIds.php | 0 .../src/src/ApiMethods/ListDocuments.php | 0 .../end2end/src/src/ApiMethods/Rollback.php | 0 .../end2end/src/src/ApiMethods/RunQuery.php | 0 .../src/src/ApiMethods/UpdateDocument.php | 0 .../examples/end2end/src/src/Commands.php | 0 .../src/src/DatabaseRootNameBuilder.php | 0 .../end2end/src/src/DocumentNameBuilder.php | 0 .../src/src/ParentResourceNameBuilder.php | 0 .../src/src/StructuredQueryBuilder.php | 0 composer.json | 23 + doc/gRPC-client-user-guide.md | 217 ++++++ src/ChannelRef.php | 98 +++ src/Config.php | 122 +++ src/CreatedByDeserializeCheck.php | 68 ++ src/GCPBidiStreamingCall.php | 108 +++ src/GCPCallInvoker.php | 86 +++ src/GCPClientStreamCall.php | 76 ++ src/GCPServerStreamCall.php | 89 +++ src/GCPUnaryCall.php | 70 ++ src/GcpBaseCall.php | 222 ++++++ src/GcpExtensionChannel.php | 282 +++++++ src/generated/GPBMetadata/GrpcGcp.php | 39 + src/generated/Grpc/Gcp/AffinityConfig.php | 87 +++ .../Grpc/Gcp/AffinityConfig_Command.php | 38 + src/generated/Grpc/Gcp/ApiConfig.php | 84 +++ src/generated/Grpc/Gcp/ChannelPoolConfig.php | 122 +++ src/generated/Grpc/Gcp/MethodConfig.php | 90 +++ src/grpc_gcp.proto | 70 ++ tests/BasicTest.php | 416 +++++++++++ tests/ChannelManagementNoConfigTest.php | 411 +++++++++++ tests/code_gen.sh | 5 + tests/composer.json | 5 + tests/fpm/README.md | 11 + tests/fpm/php-fpm1.php | 84 +++ tests/fpm/php-fpm2.php | 78 ++ tests/grpc_unit_test/ChannelTest.php | 696 ++++++++++++++++++ tests/grpc_unit_test/data/README | 1 + tests/grpc_unit_test/data/ca.pem | 15 + tests/grpc_unit_test/data/server1.key | 16 + tests/grpc_unit_test/data/server1.pem | 16 + tests/spanner.grpc.config | 30 + 76 files changed, 4011 insertions(+) create mode 100644 .gitattributes create mode 100644 .php_cs.dist create mode 100644 LICENSE create mode 100644 README.md rename {firestore => cloud-apis/firestore}/examples/end2end/README.md (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/doc/.gitignore (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/doc/assets/image_0.png (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/doc/assets/image_1.png (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/doc/assets/image_2.png (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/doc/assets/image_3.png (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/doc/assets/image_4.png (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/doc/assets/image_5.png (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/doc/assets/image_6.png (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/doc/grpc-gcp-php.md (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/.extra/google-grp-service-composer.tgz (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/.gitignore (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/Dockerfile (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/bin/interactive.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/composer.json (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/composer.lock (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/config/.gitignore (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/config/application.config.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/config/config.php.dist (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/firestore.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/ApiMethods/Admin/CreateIndex.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/ApiMethods/Admin/DeleteIndex.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/ApiMethods/Admin/GetIndex.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/ApiMethods/Admin/ListIndexes.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/ApiMethods/BatchGetDocuments.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/ApiMethods/BeginTransaction.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/ApiMethods/Commit.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/ApiMethods/CreateDocument.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/ApiMethods/DeleteDocument.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/ApiMethods/GetDocument.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/ApiMethods/ListCollectionIds.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/ApiMethods/ListDocuments.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/ApiMethods/Rollback.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/ApiMethods/RunQuery.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/ApiMethods/UpdateDocument.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/Commands.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/DatabaseRootNameBuilder.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/DocumentNameBuilder.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/ParentResourceNameBuilder.php (100%) rename {firestore => cloud-apis/firestore}/examples/end2end/src/src/StructuredQueryBuilder.php (100%) create mode 100644 composer.json create mode 100644 doc/gRPC-client-user-guide.md create mode 100644 src/ChannelRef.php create mode 100644 src/Config.php create mode 100644 src/CreatedByDeserializeCheck.php create mode 100644 src/GCPBidiStreamingCall.php create mode 100644 src/GCPCallInvoker.php create mode 100644 src/GCPClientStreamCall.php create mode 100644 src/GCPServerStreamCall.php create mode 100644 src/GCPUnaryCall.php create mode 100644 src/GcpBaseCall.php create mode 100644 src/GcpExtensionChannel.php create mode 100644 src/generated/GPBMetadata/GrpcGcp.php create mode 100644 src/generated/Grpc/Gcp/AffinityConfig.php create mode 100644 src/generated/Grpc/Gcp/AffinityConfig_Command.php create mode 100644 src/generated/Grpc/Gcp/ApiConfig.php create mode 100644 src/generated/Grpc/Gcp/ChannelPoolConfig.php create mode 100644 src/generated/Grpc/Gcp/MethodConfig.php create mode 100644 src/grpc_gcp.proto create mode 100644 tests/BasicTest.php create mode 100644 tests/ChannelManagementNoConfigTest.php create mode 100644 tests/code_gen.sh create mode 100644 tests/composer.json create mode 100644 tests/fpm/README.md create mode 100644 tests/fpm/php-fpm1.php create mode 100644 tests/fpm/php-fpm2.php create mode 100644 tests/grpc_unit_test/ChannelTest.php create mode 100644 tests/grpc_unit_test/data/README create mode 100755 tests/grpc_unit_test/data/ca.pem create mode 100755 tests/grpc_unit_test/data/server1.key create mode 100755 tests/grpc_unit_test/data/server1.pem create mode 100644 tests/spanner.grpc.config diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..24bf1d4d37 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +/.gitattributes export-ignore +/*/.gitattributes export-ignore +/.gitignore export-ignore +/tests export-ignore +/cloud-apis export-ignore diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000000..56fbdd045a --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,15 @@ +setRules([ + '@PSR2' => true, + 'concat_space' => ['spacing' => 'one'], + 'no_unused_imports' => true, + 'method_argument_space' => false, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->notPath('firestore') + ->in(__DIR__) + ) +; + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..4d49b4e5de --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# gRPC for GCP extensions + +Copyright 2018 +[The gRPC Authors](https://github.com/grpc/grpc/blob/master/AUTHORS) + +## About This Repository + +This repo is created to support GCP specific extensions for gRPC. To use the extension features, please refer to [src](src). + +This repo also contains supporting infrastructures such as end2end tests and benchmarks for accessing cloud APIs with gRPC client libraries. + +## License + +Apache 2.0 - See [LICENSE](LICENSE) for more information. diff --git a/firestore/examples/end2end/README.md b/cloud-apis/firestore/examples/end2end/README.md similarity index 100% rename from firestore/examples/end2end/README.md rename to cloud-apis/firestore/examples/end2end/README.md diff --git a/firestore/examples/end2end/doc/.gitignore b/cloud-apis/firestore/examples/end2end/doc/.gitignore similarity index 100% rename from firestore/examples/end2end/doc/.gitignore rename to cloud-apis/firestore/examples/end2end/doc/.gitignore diff --git a/firestore/examples/end2end/doc/assets/image_0.png b/cloud-apis/firestore/examples/end2end/doc/assets/image_0.png similarity index 100% rename from firestore/examples/end2end/doc/assets/image_0.png rename to cloud-apis/firestore/examples/end2end/doc/assets/image_0.png diff --git a/firestore/examples/end2end/doc/assets/image_1.png b/cloud-apis/firestore/examples/end2end/doc/assets/image_1.png similarity index 100% rename from firestore/examples/end2end/doc/assets/image_1.png rename to cloud-apis/firestore/examples/end2end/doc/assets/image_1.png diff --git a/firestore/examples/end2end/doc/assets/image_2.png b/cloud-apis/firestore/examples/end2end/doc/assets/image_2.png similarity index 100% rename from firestore/examples/end2end/doc/assets/image_2.png rename to cloud-apis/firestore/examples/end2end/doc/assets/image_2.png diff --git a/firestore/examples/end2end/doc/assets/image_3.png b/cloud-apis/firestore/examples/end2end/doc/assets/image_3.png similarity index 100% rename from firestore/examples/end2end/doc/assets/image_3.png rename to cloud-apis/firestore/examples/end2end/doc/assets/image_3.png diff --git a/firestore/examples/end2end/doc/assets/image_4.png b/cloud-apis/firestore/examples/end2end/doc/assets/image_4.png similarity index 100% rename from firestore/examples/end2end/doc/assets/image_4.png rename to cloud-apis/firestore/examples/end2end/doc/assets/image_4.png diff --git a/firestore/examples/end2end/doc/assets/image_5.png b/cloud-apis/firestore/examples/end2end/doc/assets/image_5.png similarity index 100% rename from firestore/examples/end2end/doc/assets/image_5.png rename to cloud-apis/firestore/examples/end2end/doc/assets/image_5.png diff --git a/firestore/examples/end2end/doc/assets/image_6.png b/cloud-apis/firestore/examples/end2end/doc/assets/image_6.png similarity index 100% rename from firestore/examples/end2end/doc/assets/image_6.png rename to cloud-apis/firestore/examples/end2end/doc/assets/image_6.png diff --git a/firestore/examples/end2end/doc/grpc-gcp-php.md b/cloud-apis/firestore/examples/end2end/doc/grpc-gcp-php.md similarity index 100% rename from firestore/examples/end2end/doc/grpc-gcp-php.md rename to cloud-apis/firestore/examples/end2end/doc/grpc-gcp-php.md diff --git a/firestore/examples/end2end/src/.extra/google-grp-service-composer.tgz b/cloud-apis/firestore/examples/end2end/src/.extra/google-grp-service-composer.tgz similarity index 100% rename from firestore/examples/end2end/src/.extra/google-grp-service-composer.tgz rename to cloud-apis/firestore/examples/end2end/src/.extra/google-grp-service-composer.tgz diff --git a/firestore/examples/end2end/src/.gitignore b/cloud-apis/firestore/examples/end2end/src/.gitignore similarity index 100% rename from firestore/examples/end2end/src/.gitignore rename to cloud-apis/firestore/examples/end2end/src/.gitignore diff --git a/firestore/examples/end2end/src/Dockerfile b/cloud-apis/firestore/examples/end2end/src/Dockerfile similarity index 100% rename from firestore/examples/end2end/src/Dockerfile rename to cloud-apis/firestore/examples/end2end/src/Dockerfile diff --git a/firestore/examples/end2end/src/bin/interactive.php b/cloud-apis/firestore/examples/end2end/src/bin/interactive.php similarity index 100% rename from firestore/examples/end2end/src/bin/interactive.php rename to cloud-apis/firestore/examples/end2end/src/bin/interactive.php diff --git a/firestore/examples/end2end/src/composer.json b/cloud-apis/firestore/examples/end2end/src/composer.json similarity index 100% rename from firestore/examples/end2end/src/composer.json rename to cloud-apis/firestore/examples/end2end/src/composer.json diff --git a/firestore/examples/end2end/src/composer.lock b/cloud-apis/firestore/examples/end2end/src/composer.lock similarity index 100% rename from firestore/examples/end2end/src/composer.lock rename to cloud-apis/firestore/examples/end2end/src/composer.lock diff --git a/firestore/examples/end2end/src/config/.gitignore b/cloud-apis/firestore/examples/end2end/src/config/.gitignore similarity index 100% rename from firestore/examples/end2end/src/config/.gitignore rename to cloud-apis/firestore/examples/end2end/src/config/.gitignore diff --git a/firestore/examples/end2end/src/config/application.config.php b/cloud-apis/firestore/examples/end2end/src/config/application.config.php similarity index 100% rename from firestore/examples/end2end/src/config/application.config.php rename to cloud-apis/firestore/examples/end2end/src/config/application.config.php diff --git a/firestore/examples/end2end/src/config/config.php.dist b/cloud-apis/firestore/examples/end2end/src/config/config.php.dist similarity index 100% rename from firestore/examples/end2end/src/config/config.php.dist rename to cloud-apis/firestore/examples/end2end/src/config/config.php.dist diff --git a/firestore/examples/end2end/src/firestore.php b/cloud-apis/firestore/examples/end2end/src/firestore.php similarity index 100% rename from firestore/examples/end2end/src/firestore.php rename to cloud-apis/firestore/examples/end2end/src/firestore.php diff --git a/firestore/examples/end2end/src/src/ApiMethods/Admin/CreateIndex.php b/cloud-apis/firestore/examples/end2end/src/src/ApiMethods/Admin/CreateIndex.php similarity index 100% rename from firestore/examples/end2end/src/src/ApiMethods/Admin/CreateIndex.php rename to cloud-apis/firestore/examples/end2end/src/src/ApiMethods/Admin/CreateIndex.php diff --git a/firestore/examples/end2end/src/src/ApiMethods/Admin/DeleteIndex.php b/cloud-apis/firestore/examples/end2end/src/src/ApiMethods/Admin/DeleteIndex.php similarity index 100% rename from firestore/examples/end2end/src/src/ApiMethods/Admin/DeleteIndex.php rename to cloud-apis/firestore/examples/end2end/src/src/ApiMethods/Admin/DeleteIndex.php diff --git a/firestore/examples/end2end/src/src/ApiMethods/Admin/GetIndex.php b/cloud-apis/firestore/examples/end2end/src/src/ApiMethods/Admin/GetIndex.php similarity index 100% rename from firestore/examples/end2end/src/src/ApiMethods/Admin/GetIndex.php rename to cloud-apis/firestore/examples/end2end/src/src/ApiMethods/Admin/GetIndex.php diff --git a/firestore/examples/end2end/src/src/ApiMethods/Admin/ListIndexes.php b/cloud-apis/firestore/examples/end2end/src/src/ApiMethods/Admin/ListIndexes.php similarity index 100% rename from firestore/examples/end2end/src/src/ApiMethods/Admin/ListIndexes.php rename to cloud-apis/firestore/examples/end2end/src/src/ApiMethods/Admin/ListIndexes.php diff --git a/firestore/examples/end2end/src/src/ApiMethods/BatchGetDocuments.php b/cloud-apis/firestore/examples/end2end/src/src/ApiMethods/BatchGetDocuments.php similarity index 100% rename from firestore/examples/end2end/src/src/ApiMethods/BatchGetDocuments.php rename to cloud-apis/firestore/examples/end2end/src/src/ApiMethods/BatchGetDocuments.php diff --git a/firestore/examples/end2end/src/src/ApiMethods/BeginTransaction.php b/cloud-apis/firestore/examples/end2end/src/src/ApiMethods/BeginTransaction.php similarity index 100% rename from firestore/examples/end2end/src/src/ApiMethods/BeginTransaction.php rename to cloud-apis/firestore/examples/end2end/src/src/ApiMethods/BeginTransaction.php diff --git a/firestore/examples/end2end/src/src/ApiMethods/Commit.php b/cloud-apis/firestore/examples/end2end/src/src/ApiMethods/Commit.php similarity index 100% rename from firestore/examples/end2end/src/src/ApiMethods/Commit.php rename to cloud-apis/firestore/examples/end2end/src/src/ApiMethods/Commit.php diff --git a/firestore/examples/end2end/src/src/ApiMethods/CreateDocument.php b/cloud-apis/firestore/examples/end2end/src/src/ApiMethods/CreateDocument.php similarity index 100% rename from firestore/examples/end2end/src/src/ApiMethods/CreateDocument.php rename to cloud-apis/firestore/examples/end2end/src/src/ApiMethods/CreateDocument.php diff --git a/firestore/examples/end2end/src/src/ApiMethods/DeleteDocument.php b/cloud-apis/firestore/examples/end2end/src/src/ApiMethods/DeleteDocument.php similarity index 100% rename from firestore/examples/end2end/src/src/ApiMethods/DeleteDocument.php rename to cloud-apis/firestore/examples/end2end/src/src/ApiMethods/DeleteDocument.php diff --git a/firestore/examples/end2end/src/src/ApiMethods/GetDocument.php b/cloud-apis/firestore/examples/end2end/src/src/ApiMethods/GetDocument.php similarity index 100% rename from firestore/examples/end2end/src/src/ApiMethods/GetDocument.php rename to cloud-apis/firestore/examples/end2end/src/src/ApiMethods/GetDocument.php diff --git a/firestore/examples/end2end/src/src/ApiMethods/ListCollectionIds.php b/cloud-apis/firestore/examples/end2end/src/src/ApiMethods/ListCollectionIds.php similarity index 100% rename from firestore/examples/end2end/src/src/ApiMethods/ListCollectionIds.php rename to cloud-apis/firestore/examples/end2end/src/src/ApiMethods/ListCollectionIds.php diff --git a/firestore/examples/end2end/src/src/ApiMethods/ListDocuments.php b/cloud-apis/firestore/examples/end2end/src/src/ApiMethods/ListDocuments.php similarity index 100% rename from firestore/examples/end2end/src/src/ApiMethods/ListDocuments.php rename to cloud-apis/firestore/examples/end2end/src/src/ApiMethods/ListDocuments.php diff --git a/firestore/examples/end2end/src/src/ApiMethods/Rollback.php b/cloud-apis/firestore/examples/end2end/src/src/ApiMethods/Rollback.php similarity index 100% rename from firestore/examples/end2end/src/src/ApiMethods/Rollback.php rename to cloud-apis/firestore/examples/end2end/src/src/ApiMethods/Rollback.php diff --git a/firestore/examples/end2end/src/src/ApiMethods/RunQuery.php b/cloud-apis/firestore/examples/end2end/src/src/ApiMethods/RunQuery.php similarity index 100% rename from firestore/examples/end2end/src/src/ApiMethods/RunQuery.php rename to cloud-apis/firestore/examples/end2end/src/src/ApiMethods/RunQuery.php diff --git a/firestore/examples/end2end/src/src/ApiMethods/UpdateDocument.php b/cloud-apis/firestore/examples/end2end/src/src/ApiMethods/UpdateDocument.php similarity index 100% rename from firestore/examples/end2end/src/src/ApiMethods/UpdateDocument.php rename to cloud-apis/firestore/examples/end2end/src/src/ApiMethods/UpdateDocument.php diff --git a/firestore/examples/end2end/src/src/Commands.php b/cloud-apis/firestore/examples/end2end/src/src/Commands.php similarity index 100% rename from firestore/examples/end2end/src/src/Commands.php rename to cloud-apis/firestore/examples/end2end/src/src/Commands.php diff --git a/firestore/examples/end2end/src/src/DatabaseRootNameBuilder.php b/cloud-apis/firestore/examples/end2end/src/src/DatabaseRootNameBuilder.php similarity index 100% rename from firestore/examples/end2end/src/src/DatabaseRootNameBuilder.php rename to cloud-apis/firestore/examples/end2end/src/src/DatabaseRootNameBuilder.php diff --git a/firestore/examples/end2end/src/src/DocumentNameBuilder.php b/cloud-apis/firestore/examples/end2end/src/src/DocumentNameBuilder.php similarity index 100% rename from firestore/examples/end2end/src/src/DocumentNameBuilder.php rename to cloud-apis/firestore/examples/end2end/src/src/DocumentNameBuilder.php diff --git a/firestore/examples/end2end/src/src/ParentResourceNameBuilder.php b/cloud-apis/firestore/examples/end2end/src/src/ParentResourceNameBuilder.php similarity index 100% rename from firestore/examples/end2end/src/src/ParentResourceNameBuilder.php rename to cloud-apis/firestore/examples/end2end/src/src/ParentResourceNameBuilder.php diff --git a/firestore/examples/end2end/src/src/StructuredQueryBuilder.php b/cloud-apis/firestore/examples/end2end/src/src/StructuredQueryBuilder.php similarity index 100% rename from firestore/examples/end2end/src/src/StructuredQueryBuilder.php rename to cloud-apis/firestore/examples/end2end/src/src/StructuredQueryBuilder.php diff --git a/composer.json b/composer.json new file mode 100644 index 0000000000..d36f7f34bd --- /dev/null +++ b/composer.json @@ -0,0 +1,23 @@ +{ + "name": "google/cloud-grpc", + "description": "gRPC gcp library for channel management", + "license": "Apache-2.0", + "version": "0.0.1", + "config": { + "preferred-install": "source" + }, + "require": { + "php": ">=5.5.0", + "google/protobuf": "^v3.3.0", + "grpc/grpc": "dev-master", + "psr/cache": "^1.0.1" + }, + "require-dev": { + }, + "autoload": { + "psr-4": { + "Grpc\\Gcp": "src/", + "": ["src/generated/"] + } + } +} diff --git a/doc/gRPC-client-user-guide.md b/doc/gRPC-client-user-guide.md new file mode 100644 index 0000000000..a9848c87da --- /dev/null +++ b/doc/gRPC-client-user-guide.md @@ -0,0 +1,217 @@ +# Instructions for create a gRPC client for google cloud services + +## Overview + +This instruction includes a step by step guide for creating a gRPC +client to test the google cloud service from an empty linux +VM, using GCE ubuntu 16.04 TLS instance. + +The main steps are followed as steps below: + +- Environment prerequisite +- Install protobuf plugin and gRPC-PHP/protobuf extension +- Generate client API from .proto files +- Create the client and send/receive RPC. + +## Environment Prerequisite + +**Linux** +```sh +$ [sudo] apt-get install build-essential autoconf libtool pkg-config zip unzip zlib1g-dev +``` +**PHP** +* `php` 5.5 or above, 7.0 or above +* `pecl` +* `composer` +```sh +$ [sudo] apt-get install php php-dev +$ curl -sS https://getcomposer.org/installer | php +$ [sudo] mv composer.phar /usr/local/bin/composer +``` + +## Install protobuf plugin and gRPC-PHP/protobuf extension +`grpc_php_plugin` is used to generate client API from `*.proto `files. Currently, +The only way to install `grpc_php_plugin` is to build from the gRPC source. + +**Install protobuf, gRPC, which will install the plugin** +```sh +$ git clone -b $(curl -L https://grpc.io/release) https://github.com/grpc/grpc +$ cd grpc +$ git submodule update --init +# install protobuf +$ cd third_party/protobuf +$ ./autogen.sh && ./configure && make -j8 +$ [sudo] make install +$ [sudo] ldconfig +# install gRPC +$ cd ../.. +$ make -j8 +$ [sudo] make install +``` +It will generate `grpc_php_plugin` under `/usr/local/bin`. + + +**Install gRPC-PHP extension** +```sh +$ [sudo] pecl install protobuf +$ [sudo] pecl install grpc +``` +It will generate `protobuf.so` and `grpc.so` under PHP's extension directory. +Note gRPC-PHP extension installed by pecl doesn't work on RHEL6 system. + +## Generate client API from .proto files +The common way to generate the client API is to use `grpc_php_plugin` directly. +Since the plugin won't find the dependency by itself. It works if all your +service proto files and dependent proto files are inside one directory. The +command looks like: +```sh +$ mkdir $HOME/project +$ protoc --proto_path=./ --php_out=$HOME/project \ +--grpc_out=$HOME/project \ +--plugin=protoc-gen-grpc=./bins/opt/grpc_php_plugin \ +path/to/your/proto_dependency_directory1/*.proto \ +path/to/your/proto_dependency_directory2/*.proto \ +path/to/your/proto_directory/*.proto + +``` + +Take `Firestore` service under [googleapis github repo](https://github.com/googleapis/googleapis) +for example. The proto files required for generating client API are +``` +google/api/annotations.proto +google/api/http.proto +google/api/httpbody.proto +google/longrunning/operations.proto +google/rpc/code.proto +google/rpc/error_details.proto +google/rpc/status.proto +google/type/latlng.proto +google/firestore/v1beta1/firestore.proto +google/firestore/v1beta1/common.proto +google/firestore/v1beta1/query.proto +google/firestore/v1beta1/write.proto +google/firestore/v1beta1/document.proto +``` +Thus the command looks like: +```sh +$ protoc --proto_path=googleapis --plugin=protoc-gen-grpc=`which grpc_php_plugin` \ +--php_out=./ --grpc_out=./ google/api/annotations.proto google/api/http.proto \ +google/api/httpbody.proto google/longrunning/operations.proto google/rpc/code.proto \ +google/rpc/error_details.proto google/rpc/status.proto google/type/latlng.proto \ +google/firestore/v1beta1/firestore.proto google/firestore/v1beta1/common.proto \ +google/firestore/v1beta1/query.proto google/firestore/v1beta1/write.proto \ +google/firestore/v1beta1/document.proto +``` + +Since most of cloud services already publish proto files under +[googleapis github repo](https://github.com/googleapis/googleapis), +you can use it's Makefile to generate the client API. +The `Makefile` will help you generate the client API as +well as find the dependencies. The command will simply be: +```sh +$ cd $HOME +$ mkdir project +$ git clone https://github.com/googleapis/googleapis.git +$ cd googleapis +$ make LANGUAGE=php OUTPUT=$HOME/project +# (It's okay if you see error like Please add 'syntax = "proto3";' +# to the top of your .proto file.) +``` +The client API library is generated under `$HOME/project`. +Take [`Firestore`](https://github.com/googleapis/googleapis/blob/master/google/firestore/v1beta1/firestore.proto) +as example, the Client API is under +`project/Google/Cloud/Firestore/V1beta1/FirestoreClient.php` depends on your +package name inside .proto file. An easy way to find your client is +```sh +$ find ./ -name [service_name]Client.php +``` + +## Create the client and send/receive RPC. +Now it's time to use the client API to send and receive RPCs. +```sh +$ cd $HOME/project +``` +**Install gRPC-PHP composer library** +```sh +$ vim composer.json +######## you need to change the path and service namespace. +{ + "require": { + "google/cloud": "^0.52.1" + }, + "autoload": { + "psr-4": { + "FireStore\\": "src/", + "Google\\Cloud\\Firestore\\V1beta1\\": "Google/Cloud/Firestore/V1beta1/" + } + } +} +######## +$ composer install +``` +**Set credentials file** +``` sh +$ vim $HOME/key.json +## Paste you credential file downloaded from your cloud project +## which you can find in APIs&Services => credentials => create credentials +## => Service account key => your credentials +$ export GOOGLE_APPLICATION_CREDENTIALS=$HOME/key.json +``` + +**Implement Service Client** +Take a unary-unary RPC `listDocument` from `FirestoreClient` as example. +Create a file name `ListDocumentClient.php`. +- import library +``` +require_once __DIR__ . '/vendor/autoload.php'; +use Google\Cloud\Firestore\V1beta1\FirestoreClient; +use Google\Cloud\Firestore\V1beta1\ListDocumentsRequest; +use Google\Auth\ApplicationDefaultCredentials; +``` +- Google Auth +``` +$host = "firestore.googleapis.com"; +$credentials = \Grpc\ChannelCredentials::createSsl(); +// WARNING: the environment variable "GOOGLE_APPLICATION_CREDENTIALS" needs to be set +$auth = ApplicationDefaultCredentials::getCredentials(); +$opts = [ + 'credentials' => $credentials, + 'update_metadata' => $auth->getUpdateMetadataFunc(), +] +``` +- Create Client +``` +$firestoreClient = new FirestoreClient($host, $opts); +``` +- Make and receive RPC call +``` +$argument = new ListDocumentsRequest(); +$project_id = xxxxxxx; +$argument->setParent("projects/$project_id/databases/(default)/documents"); +list($Response, $error) = $firestoreClient->ListDocuments($argument)->wait(); +``` +- print RPC response +``` +$documents = $Response->getDocuments(); +$index = 0; +foreach($documents as $document) { + $index++; + $name = $document->getName(); + echo "=> Document $index: $name\n"; + $fields = $document->getFields(); + foreach ($fields as $name => $value) { + echo "$name => ".$value->getStringValue()."\n"; + } +} +``` + +- run the script +```sh +$ php -d extension=grpc.so -d extension=protobuf.so ListDocumentClient.php +``` + +For different kinds of RPC(unary-unary, unary-stream, stream-unary, stream-stream), +please check [grpc.io PHP part](https://grpc.io/docs/tutorials/basic/php.html#calling-service-methods) +for reference. + + diff --git a/src/ChannelRef.php b/src/ChannelRef.php new file mode 100644 index 0000000000..bc25836ef0 --- /dev/null +++ b/src/ChannelRef.php @@ -0,0 +1,98 @@ +target = $target; + $this->channel_id = $channel_id; + $this->affinity_ref = $affinity_ref; + $this->active_stream_ref = $active_stream_ref; + $this->opts = $opts; + $this->has_deserialized = new CreatedByDeserializeCheck(); + } + + public function getRealChannel($credentials) + { + // TODO(ddyihai): remove this check once the serialize handler for + // \Grpc\Channel is implemented(issue https://github.com/grpc/grpc/issues/15870). + if (!$this->has_deserialized->getData()) { + // $real_channel exists and is not created by the deserialization. + return $this->real_channel; + } + // If this ChannelRef is created by deserialization, $real_channel is invalid + // thus needs to be recreated becasue Grpc\Channel don't have serialize and + // deserialize handler. + // Since [target + augments + credentials] will be the same during the recreation, + // it will reuse the underline grpc channel in C extension without creating a + // new connection. + + // 'credentials' in the array $opts will be unset during creating the channel. + if (!array_key_exists('credentials', $this->opts)) { + $this->opts['credentials'] = $credentials; + } + $real_channel = new \Grpc\Channel($this->target, $this->opts); + $this->real_channel = $real_channel; + // Set deserialization to false so it won't be recreated within the same script. + $this->has_deserialized->setData(0); + return $real_channel; + } + + public function getAffinityRef() + { + return $this->affinity_ref; + } + public function getActiveStreamRef() + { + return $this->active_stream_ref; + } + public function affinityRefIncr() + { + $this->affinity_ref += 1; + } + public function affinityRefDecr() + { + $this->affinity_ref -= 1; + } + public function activeStreamRefIncr() + { + $this->active_stream_ref += 1; + } + public function activeStreamRefDecr() + { + $this->active_stream_ref -= 1; + } +} diff --git a/src/Config.php b/src/Config.php new file mode 100644 index 0000000000..356621b61d --- /dev/null +++ b/src/Config.php @@ -0,0 +1,122 @@ +gcp_call_invoker = new \Grpc\DefaultCallInvoker(); + return; + } + $gcp_channel = null; + $this->hostname = $hostname; + $channel_pool_key = $hostname . 'gcp_channel' . getmypid(); + if (!$cacheItemPool) { + // If there is no cacheItemPool, use shared memory for + // caching the configuration and channel pool. + $channel_pool_key = intval(base_convert(sha1($channel_pool_key), 16, 10)); + $shm_id = shm_attach(getmypid(), 200000, 0600); + $var1 = @shm_get_var($shm_id, $channel_pool_key); + if ($var1) { + $gcp_call_invoker = unserialize($var1); + } else { + $affinity_conf = $this->parseConfObject($conf); + $gcp_call_invoker = new GCPCallInvoker($affinity_conf); + } + $this->gcp_call_invoker = $gcp_call_invoker; + + register_shutdown_function(function ($gcp_call_invoker, $channel_pool_key, $shm_id) { + // Push the current gcp_channel back into the pool when the script finishes. + if (!shm_put_var($shm_id, $channel_pool_key, serialize($gcp_call_invoker))) { + echo "[warning]: failed to update the item pool\n"; + } + }, $gcp_call_invoker, $channel_pool_key, $shm_id); + } else { + $item = $cacheItemPool->getItem($channel_pool_key); + if ($item->isHit()) { + // Channel pool for the $hostname API has already created. + $gcp_call_invoker = unserialize($item->get()); + } else { + $affinity_conf = $this->parseConfObject($conf); + // Create GCP channel based on the information. + $gcp_call_invoker = new GCPCallInvoker($affinity_conf); + } + $this->gcp_call_invoker = $gcp_call_invoker; + register_shutdown_function(function ($gcp_call_invoker, $cacheItemPool, $item) { + // Push the current gcp_channel back into the pool when the script finishes. + $item->set(serialize($gcp_call_invoker)); + $cacheItemPool->save($item); + }, $gcp_call_invoker, $cacheItemPool, $item); + } + } + + /** + * @return \Grpc\CallInvoker The call invoker to be hooked into the gRPC + */ + public function callInvoker() + { + return $this->gcp_call_invoker; + } + + /** + * @return string The URI of the endpoint + */ + public function getTarget() + { + return $this->channel->getTarget(); + } + + private function parseConfObject($conf_object) + { + $config = json_decode($conf_object->serializeToJsonString(), true); + if (isset($config['channelPool'])) { + $affinity_conf['channelPool'] = $config['channelPool']; + } + $aff_by_method = array(); + if (isset($config['method'])) { + for ($i = 0; $i < count($config['method']); $i++) { + // In proto3, if the value is default, eg 0 for int, it won't be serialized. + // Thus serialized string may not have `command` if the value is default 0(BOUND). + if (!array_key_exists('command', $config['method'][$i]['affinity'])) { + $config['method'][$i]['affinity']['command'] = 'BOUND'; + } + $aff_by_method[$config['method'][$i]['name'][0]] = $config['method'][$i]['affinity']; + } + } + $affinity_conf['affinity_by_method'] = $aff_by_method; + return $affinity_conf; + } +} diff --git a/src/CreatedByDeserializeCheck.php b/src/CreatedByDeserializeCheck.php new file mode 100644 index 0000000000..debf07dbbb --- /dev/null +++ b/src/CreatedByDeserializeCheck.php @@ -0,0 +1,68 @@ +data = 1; + } + + /** + * @return string + */ + public function serialize() + { + return '0'; + } + + /** + * @param string $data + */ + public function unserialize($data) + { + $this->data = 1; + } + + /** + * @param $data + */ + public function setData($data) + { + $this->data = $data; + } + + /** + * @return int + */ + public function getData() + { + return $this->data; + } +} diff --git a/src/GCPBidiStreamingCall.php b/src/GCPBidiStreamingCall.php new file mode 100644 index 0000000000..992843f42e --- /dev/null +++ b/src/GCPBidiStreamingCall.php @@ -0,0 +1,108 @@ +_rpcPreProcess($data); + $this->real_call = new \Grpc\BidiStreamingCall($channel_ref->getRealChannel( + $this->gcp_channel->credentials), $this->method, $this->deserialize, $this->options); + $this->real_call->start($this->metadata_rpc); + return $this->real_call; + } + + /** + * Pick a channel and start the call. + * + * @param array $metadata Metadata to send with the call, if applicable + * (optional) + */ + public function start(array $metadata = []) + { + $this->metadata_rpc = $metadata; + } + + /** + * Reads the next value from the server. + * + * @return mixed The next value from the server, or null if there is none + */ + public function read() + { + if (!$this->has_real_call) { + $this->createRealCall(); + $this->has_real_call = true; + } + $response = $this->real_call->read(); + if ($response) { + $this->response = $response; + } + return $response; + } + + /** + * Write a single message to the server. This cannot be called after + * writesDone is called. + * + * @param ByteBuffer $data The data to write + * @param array $options An array of options, possible keys: + * 'flags' => a number (optional) + */ + public function write($data, array $options = []) + { + if (!$this->has_real_call) { + $this->createRealCall($data); + $this->has_real_call = true; + } + $this->real_call->write($data, $options); + } + + /** + * Indicate that no more writes will be sent. + */ + public function writesDone() + { + if (!$this->has_real_call) { + $this->createRealCall(); + $this->has_real_call = true; + } + $this->real_call->writesDone(); + } + + /** + * Wait for the server to send the status, and return it. + * + * @return \stdClass The status object, with integer $code, string + * $details, and array $metadata members + */ + public function getStatus() + { + $status = $this->real_call->getStatus(); + $this->_rpcPostProcess($status, $this->response); + return $status; + } +} diff --git a/src/GCPCallInvoker.php b/src/GCPCallInvoker.php new file mode 100644 index 0000000000..1e12fe59ae --- /dev/null +++ b/src/GCPCallInvoker.php @@ -0,0 +1,86 @@ +affinity_conf = $affinity_conf; + } + + /** + * @param string $hostname + * @param array $opts + * @return GcpExtensionChannel + */ + public function createChannelFactory($hostname, $opts) + { + if ($this->channel) { + // $call_invoker object has already created from previews PHP-FPM scripts. + // Only need to udpate the $opts including the credentials. + $this->channel->updateOpts($opts); + } else { + $opts['affinity_conf'] = $this->affinity_conf; + $channel = new GcpExtensionChannel($hostname, $opts); + $this->channel = $channel; + } + return $this->channel; + } + + // _getChannel is used for testing only. + public function GetChannel() + { + return $this->channel; + } + + public function UnaryCall($channel, $method, $deserialize, $options) + { + return new GCPUnaryCall($channel, $method, $deserialize, $options); + } + public function ClientStreamingCall($channel, $method, $deserialize, $options) + { + return new GCPClientStreamCall($channel, $method, $deserialize, $options); + } + public function ServerStreamingCall($channel, $method, $deserialize, $options) + { + return new GCPServerStreamCall($channel, $method, $deserialize, $options); + } + public function BidiStreamingCall($channel, $method, $deserialize, $options) + { + return new GCPBidiStreamingCall($channel, $method, $deserialize, $options); + } +} diff --git a/src/GCPClientStreamCall.php b/src/GCPClientStreamCall.php new file mode 100644 index 0000000000..c24a92b839 --- /dev/null +++ b/src/GCPClientStreamCall.php @@ -0,0 +1,76 @@ +_rpcPreProcess($data); + $this->real_call = new \Grpc\ClientStreamingCall($channel_ref->getRealChannel( + $this->gcp_channel->credentials), $this->method, $this->deserialize, $this->options); + $this->real_call->start($this->metadata_rpc); + return $this->real_call; + } + /** + * Pick a channel and start the call. + * + * @param array $metadata Metadata to send with the call, if applicable + * (optional) + */ + public function start(array $metadata = []) + { + // Postpone first rpc to write function(), where we can pick a channel + // from the channel pool. + $this->metadata_rpc = $metadata; + } + + /** + * Write a single message to the server. This cannot be called after + * wait is called. + * + * @param ByteBuffer $data The data to write + * @param array $options An array of options, possible keys: + * 'flags' => a number (optional) + */ + public function write($data, array $options = []) + { + if (!$this->has_real_call) { + $this->createRealCall($data); + $this->has_real_call = true; + } + $this->real_call->write($data, $options); + } + + /** + * Wait for the server to respond with data and a status. + * + * @return array [response data, status] + */ + public function wait() + { + list($response, $status) = $this->real_call->wait(); + $this->_rpcPostProcess($status, $response); + return [$response, $status]; + } +} diff --git a/src/GCPServerStreamCall.php b/src/GCPServerStreamCall.php new file mode 100644 index 0000000000..283cd61cb5 --- /dev/null +++ b/src/GCPServerStreamCall.php @@ -0,0 +1,89 @@ +real_call = new \Grpc\ServerStreamingCall($channel, $this->method, $this->deserialize, $this->options); + $this->has_real_call = true; + return $this->real_call; + } + + /** + * Pick a channel and start the call. + * + * @param mixed $data The data to send + * @param array $metadata Metadata to send with the call, if applicable + * (optional) + * @param array $options An array of options, possible keys: + * 'flags' => a number (optional) + */ + public function start($argument, $metadata, $options) + { + $channel_ref = $this->_rpcPreProcess($argument); + $this->createRealCall($channel_ref->getRealChannel( + $this->gcp_channel->credentials)); + $this->real_call->start($argument, $metadata, $options); + } + + /** + * @return mixed An iterator of response values + */ + public function responses() + { + $response = $this->real_call->responses(); + // Since the last response is empty for the server streaming RPC, + // the second last one is the last RPC response with payload. + // Use this one for searching the affinity key. + // The same as BidiStreaming. + if ($response) { + $this->response = $response; + } + return $response; + } + + /** + * Wait for the server to send the status, and return it. + * + * @return \stdClass The status object, with integer $code, string + * $details, and array $metadata members + */ + public function getStatus() + { + $status = $this->real_call->getStatus(); + $this->_rpcPostProcess($status, $this->response); + return $status; + } + + /** + * @return mixed The metadata sent by the server + */ + public function getMetadata() + { + return $this->real_call->getMetadata(); + } +} diff --git a/src/GCPUnaryCall.php b/src/GCPUnaryCall.php new file mode 100644 index 0000000000..da4a2096f8 --- /dev/null +++ b/src/GCPUnaryCall.php @@ -0,0 +1,70 @@ +real_call = new \Grpc\UnaryCall($channel, $this->method, $this->deserialize, $this->options); + $this->has_real_call = true; + return $this->real_call; + } + + /** + * Pick a channel and start the call. + * + * @param mixed $data The data to send + * @param array $metadata Metadata to send with the call, if applicable + * (optional) + * @param array $options An array of options, possible keys: + * 'flags' => a number (optional) + */ + public function start($argument, $metadata, $options) + { + $channel_ref = $this->_rpcPreProcess($argument); + $real_channel = $channel_ref->getRealChannel($this->gcp_channel->credentials); + $this->createRealCall($real_channel); + $this->real_call->start($argument, $metadata, $options); + } + + /** + * Wait for the server to respond with data and a status. + * + * @return array [response data, status] + */ + public function wait() + { + list($response, $status) = $this->real_call->wait(); + $this->_rpcPostProcess($status, $response); + return [$response, $status]; + } + + /** + * @return mixed The metadata sent by the server + */ + public function getMetadata() + { + return $this->real_call->getMetadata(); + } +} diff --git a/src/GcpBaseCall.php b/src/GcpBaseCall.php new file mode 100644 index 0000000000..56b42e0df7 --- /dev/null +++ b/src/GcpBaseCall.php @@ -0,0 +1,222 @@ +gcp_channel = $channel; + $this->method = $method; + $this->deserialize = $deserialize; + $this->options = $options; + $this->_affinity = null; + + if (isset($this->gcp_channel->affinity_conf['affinity_by_method'][$method])) { + $this->_affinity = $this->gcp_channel->affinity_conf['affinity_by_method'][$method]; + } + } + + /** + * Pick a ChannelRef from the channel pool based on the request and + * the affinity config. + * + * @param mixed $argument Requests. + * + * @return ChannelRef + */ + protected function _rpcPreProcess($argument) + { + $this->affinity_key = null; + if ($this->_affinity) { + $command = $this->_affinity['command']; + if ($command == self::BOUND || $command == self::UNBIND) { + $this->affinity_key = $this->getAffinityKeyFromProto($argument); + } + } + $this->channel_ref = $this->gcp_channel->getChannelRef($this->affinity_key); + $this->channel_ref->activeStreamRefIncr(); + return $this->channel_ref; + } + + /** + * Update ChannelRef when RPC finishes. + * + * @param \stdClass $status The status object, with integer $code, string + * $details, and array $metadata members + * @param mixed $response Response. + */ + protected function _rpcPostProcess($status, $response) + { + if ($this->_affinity) { + $command = $this->_affinity['command']; + if ($command == self::BIND) { + if ($status->code != \Grpc\STATUS_OK) { + return; + } + $affinity_key = $this->getAffinityKeyFromProto($response); + $this->gcp_channel->bind($this->channel_ref, $affinity_key); + } elseif ($command == self::UNBIND) { + $this->gcp_channel->unbind($this->affinity_key); + } + } + $this->channel_ref->activeStreamRefDecr(); + } + + /** + * Get the affinity key based on the affinity config. + * + * @param mixed $proto Objects may contain the affinity key. + * + * @return string Affinity key. + */ + protected function getAffinityKeyFromProto($proto) + { + if ($this->_affinity) { + $names = $this->_affinity['affinityKey']; + $names_arr = explode(".", $names); + foreach ($names_arr as $name) { + $getAttrMethod = 'get' . ucfirst($name); + $proto = call_user_func_array(array($proto, $getAttrMethod), array()); + } + return $proto; + } + echo "Cannot find the field in the proto\n"; + } + + /** + * @return mixed The metadata sent by the server + */ + public function getMetadata() + { + if (!$this->has_real_call) { + $this->createRealCall(); + $this->has_real_call = true; + } + return $this->real_call->getMetadata(); + } + + /** + * @return mixed The trailing metadata sent by the server + */ + public function getTrailingMetadata() + { + if (!$this->has_real_call) { + $this->createRealCall(); + $this->has_real_call = true; + } + return $this->real_call->getTrailingMetadata(); + } + + /** + * @return string The URI of the endpoint + */ + public function getPeer() + { + if (!$this->has_real_call) { + $this->createRealCall(); + $this->has_real_call = true; + } + return $this->real_call->getPeer(); + } + + /** + * Cancels the call. + */ + public function cancel() + { + if (!$this->has_real_call) { + $this->has_real_call = true; + $this->createRealCall(); + } + $this->real_call->cancel(); + } + + /** + * Serialize a message to the protobuf binary format. + * + * @param mixed $data The Protobuf message + * + * @return string The protobuf binary format + */ + protected function _serializeMessage($data) + { + return $this->real_call->_serializeMessage($data); + } + + /** + * Deserialize a response value to an object. + * + * @param string $value The binary value to deserialize + * + * @return mixed The deserialized value + */ + protected function _deserializeResponse($value) + { + return $this->real_call->_deserializeResponse($value); + } + + /** + * Set the CallCredentials for the underlying Call. + * + * @param CallCredentials $call_credentials The CallCredentials object + */ + public function setCallCredentials($call_credentials) + { + $this->call->setCredentials($call_credentials); + } +} diff --git a/src/GcpExtensionChannel.php b/src/GcpExtensionChannel.php new file mode 100644 index 0000000000..e05c554c8f --- /dev/null +++ b/src/GcpExtensionChannel.php @@ -0,0 +1,282 @@ +channel_refs; + } + + /** + * @param string $hostname + * @param array $opts Options to create a \Grpc\Channel and affinity config + */ + public function __construct($hostname = null, $opts = array()) + { + if ($hostname == null || !is_array($opts)) { + throw new \InvalidArgumentException("Expected hostname is empty"); + } + $this->max_size = 10; + $this->max_concurrent_streams_low_watermark = 100; + if (isset($opts['affinity_conf'])) { + if (isset($opts['affinity_conf']['channelPool'])) { + if (isset($opts['affinity_conf']['channelPool']['maxSize'])) { + $this->max_size = $opts['affinity_conf']['channelPool']['maxSize']; + } + if (isset($opts['affinity_conf']['channelPool']['maxConcurrentStreamsLowWatermark'])) { + $this->max_concurrent_streams_low_watermark = + $opts['affinity_conf']['channelPool']['maxConcurrentStreamsLowWatermark']; + } + } + $this->affinity_by_method = $opts['affinity_conf']['affinity_by_method']; + $this->affinity_conf = $opts['affinity_conf']; + } + $this->target = $hostname; + $this->affinity_key_to_channel_ref = array(); + $this->channel_refs = array(); + $this->updateOpts($opts); + // Initiate a Grpc\Channel at the beginning in order to keep the same + // behavior as the Grpc. + $channel_ref = $this->getChannelRef(); + $channel_ref->getRealChannel($this->credentials); + } + + /** + * @param array $opts Options to create a \Grpc\Channel + */ + public function updateOpts($opts) + { + if (isset($opts['credentials'])) { + $this->credentials = $opts['credentials']; + } + unset($opts['affinity_conf']); + unset($opts['credentials']); + $this->options = $opts; + $this->is_closed = false; + } + + /** + * Bind the ChannelRef with the affinity key. This is a private method. + * + * @param ChannelRef $channel_ref + * @param string $affinity_key + * + * @return ChannelRef + */ + public function bind($channel_ref, $affinity_key) + { + if (!array_key_exists($affinity_key, $this->affinity_key_to_channel_ref)) { + $this->affinity_key_to_channel_ref[$affinity_key] = $channel_ref; + } + $channel_ref->affinityRefIncr(); + return $channel_ref; + } + + /** + * Unbind the affinity key. This is a private method. + * + * @param string $affinity_key + * + * @return ChannelRef + */ + public function unbind($affinity_key) + { + $channel_ref = null; + if (array_key_exists($affinity_key, $this->affinity_key_to_channel_ref)) { + $channel_ref = $this->affinity_key_to_channel_ref[$affinity_key]; + $channel_ref->affinityRefDecr(); + } + unset($this->affinity_key_to_channel_ref[$affinity_key]); + return $channel_ref; + } + + + public function cmp_by_active_stream_ref($a, $b) + { + return $a->getActiveStreamRef() - $b->getActiveStreamRef(); + } + + /** + * Pick or create a ChannelRef from the pool by affinity key. + * + * @param string $affinity_key + * + * @return ChannelRef + */ + public function getChannelRef($affinity_key = null) + { + if ($affinity_key) { + if (array_key_exists($affinity_key, $this->affinity_key_to_channel_ref)) { + return $this->affinity_key_to_channel_ref[$affinity_key]; + } + return $this->getChannelRef(); + } + usort($this->channel_refs, array($this, 'cmp_by_active_stream_ref')); + + if (count($this->channel_refs) > 0 && $this->channel_refs[0]->getActiveStreamRef() < + $this->max_concurrent_streams_low_watermark) { + return $this->channel_refs[0]; + } + $num_channel_refs = count($this->channel_refs); + if ($num_channel_refs < $this->max_size) { + // grpc_target_persist_bound stands for how many channels can be persisted for + // the same target in the C extension. It is possible that the user use the pure + // gRPC and this GCP extension at the same time, which share the same target. In this case + // pure gRPC channel may occupy positions in C extension, which deletes some channels created + // by this GCP extension. + // If that happens, it won't cause the script failure because we saves all arguments for creating + // a channel instead of a channel itself. If we watch to fetch a GCP channel already deleted, + // it will create a new channel. The only cons is the latency of the first RPC will high because + // it will establish the connection again. + if (!isset($this->options['grpc_target_persist_bound']) || + $this->options['grpc_target_persist_bound'] < $this->max_size) { + $this->options['grpc_target_persist_bound'] = $this->max_size; + } + $cur_opts = array_merge($this->options, + ['grpc_gcp_channel_id' => $num_channel_refs]); + $channel_ref = new ChannelRef($this->target, $num_channel_refs, $cur_opts); + array_unshift($this->channel_refs, $channel_ref); + } + return $this->channel_refs[0]; + } + + /** + * Get the connectivity state of the channel + * + * @param bool $try_to_connect try to connect on the channel + * + * @return int The grpc connectivity state + * @throws \InvalidArgumentException + */ + public function getConnectivityState($try_to_connect = false) + { + // Since getRealChannel is creating a PHP Channel object. However in gRPC, when a Channel + // object is closed, we only mark this Object to be invalid. Thus, we need a global variable + // to mark whether this GCPExtensionChannel is close or not. + if ($this->is_closed) { + throw new \RuntimeException("Channel has already been closed"); + } + $ready = 0; + $idle = 0; + $connecting = 0; + $transient_failure = 0; + $shutdown = 0; + foreach ($this->channel_refs as $channel_ref) { + $state = $channel_ref->getRealChannel($this->credentials)->getConnectivityState($try_to_connect); + print_r($state); + switch ($state) { + case \Grpc\CHANNEL_READY: + $ready += 1; + break; + case \Grpc\CHANNEL_FATAL_FAILURE: + $shutdown += 1; + continue; + case \Grpc\CHANNEL_CONNECTING: + $connecting += 1; + continue; + case \Grpc\CHANNEL_TRANSIENT_FAILURE: + $transient_failure += 1; + continue; + case \Grpc\CHANNEL_IDLE: + $idle += 1; + continue; + } + } + if ($ready > 0) { + return \Grpc\CHANNEL_READY; + } elseif ($idle > 0) { + return \Grpc\CHANNEL_IDLE; + } elseif ($connecting > 0) { + return \Grpc\CHANNEL_CONNECTING; + } elseif ($transient_failure > 0) { + return \Grpc\CHANNEL_TRANSIENT_FAILURE; + } elseif ($shutdown > 0) { + return \Grpc\CHANNEL_SHUTDOWN; + } + } + + /** + * Watch the connectivity state of the channel until it changed + * + * @param int $last_state The previous connectivity state of the channel + * @param Timeval $deadline_obj The deadline this function should wait until + * + * @return bool If the connectivity state changes from last_state + * before deadline + * @throws \InvalidArgumentException + */ + public function watchConnectivityState($last_state, $deadline_obj = null) + { + if ($deadline_obj == null || !is_a($deadline_obj, '\Grpc\Timeval')) { + throw new \InvalidArgumentException(""); + } + // Since getRealChannel is creating a PHP Channel object. However in gRPC, when a Channel + // object is closed, we only mark this Object to be invalid. Thus, we need a global variable + // to mark whether this GCPExtensionChannel is close or not. + if ($this->is_closed) { + throw new \RuntimeException("Channel has already been closed"); + } + $state = 0; + foreach ($this->channel_refs as $channel_ref) { + $state = $channel_ref->getRealChannel($this->credentials)->watchConnectivityState($last_state, $deadline_obj); + } + return $state; + } + + /** + * Get the endpoint this call/stream is connected to + * + * @return string The URI of the endpoint + */ + public function getTarget() + { + return $this->target; + } + + /** + * Close the channel + */ + public function close() + { + foreach ($this->channel_refs as $channel_ref) { + $channel_ref->getRealChannel($this->credentials)->close(); + } + $this->is_closed = true; + } +} diff --git a/src/generated/GPBMetadata/GrpcGcp.php b/src/generated/GPBMetadata/GrpcGcp.php new file mode 100644 index 0000000000..4164ecb335 --- /dev/null +++ b/src/generated/GPBMetadata/GrpcGcp.php @@ -0,0 +1,39 @@ +internalAddGeneratedFile(hex2bin( + "0ac9030a0e677270635f6763702e70726f746f1208677270632e67637022" . + "670a09417069436f6e66696712310a0c6368616e6e656c5f706f6f6c1802" . + "2001280b321b2e677270632e6763702e4368616e6e656c506f6f6c436f6e" . + "66696712270a066d6574686f6418e9072003280b32162e677270632e6763" . + "702e4d6574686f64436f6e66696722690a114368616e6e656c506f6f6c43" . + "6f6e66696712100a086d61785f73697a6518012001280d12140a0c69646c" . + "655f74696d656f7574180220012804122c0a246d61785f636f6e63757272" . + "656e745f73747265616d735f6c6f775f77617465726d61726b1803200128" . + "0d22490a0c4d6574686f64436f6e666967120c0a046e616d651801200328" . + "09122b0a08616666696e69747918e9072001280b32182e677270632e6763" . + "702e416666696e697479436f6e6669672285010a0e416666696e69747943" . + "6f6e66696712310a07636f6d6d616e6418022001280e32202e677270632e" . + "6763702e416666696e697479436f6e6669672e436f6d6d616e6412140a0c" . + "616666696e6974795f6b6579180320012809222a0a07436f6d6d616e6412" . + "090a05424f554e44100012080a0442494e441001120a0a06554e42494e44" . + "1002620670726f746f33" + )); + + static::$is_initialized = true; + } +} diff --git a/src/generated/Grpc/Gcp/AffinityConfig.php b/src/generated/Grpc/Gcp/AffinityConfig.php new file mode 100644 index 0000000000..bad448eaca --- /dev/null +++ b/src/generated/Grpc/Gcp/AffinityConfig.php @@ -0,0 +1,87 @@ +grpc.gcp.AffinityConfig + */ +class AffinityConfig extends \Google\Protobuf\Internal\Message +{ + /** + * The affinity command applies on the selected gRPC methods. + * + * Generated from protobuf field .grpc.gcp.AffinityConfig.Command command = 2; + */ + private $command = 0; + /** + * The field path of the affinity key in the request/response message. + * For example: "f.a", "f.b.d", etc. + * + * Generated from protobuf field string affinity_key = 3; + */ + private $affinity_key = ''; + + public function __construct() + { + \GPBMetadata\GrpcGcp::initOnce(); + parent::__construct(); + } + + /** + * The affinity command applies on the selected gRPC methods. + * + * Generated from protobuf field .grpc.gcp.AffinityConfig.Command command = 2; + * @return int + */ + public function getCommand() + { + return $this->command; + } + + /** + * The affinity command applies on the selected gRPC methods. + * + * Generated from protobuf field .grpc.gcp.AffinityConfig.Command command = 2; + * @param int $var + * @return $this + */ + public function setCommand($var) + { + GPBUtil::checkEnum($var, \Grpc\Gcp\AffinityConfig_Command::class); + $this->command = $var; + + return $this; + } + + /** + * The field path of the affinity key in the request/response message. + * For example: "f.a", "f.b.d", etc. + * + * Generated from protobuf field string affinity_key = 3; + * @return string + */ + public function getAffinityKey() + { + return $this->affinity_key; + } + + /** + * The field path of the affinity key in the request/response message. + * For example: "f.a", "f.b.d", etc. + * + * Generated from protobuf field string affinity_key = 3; + * @param string $var + * @return $this + */ + public function setAffinityKey($var) + { + GPBUtil::checkString($var, true); + $this->affinity_key = $var; + + return $this; + } +} diff --git a/src/generated/Grpc/Gcp/AffinityConfig_Command.php b/src/generated/Grpc/Gcp/AffinityConfig_Command.php new file mode 100644 index 0000000000..2efcc7258a --- /dev/null +++ b/src/generated/Grpc/Gcp/AffinityConfig_Command.php @@ -0,0 +1,38 @@ +Grpc\Gcp\AffinityConfig\Command + */ +class AffinityConfig_Command +{ + /** + * The annotated method will be required to be bound to an existing session + * to execute the RPC. The corresponding will be + * used to find the affinity key from the request message. + * + * Generated from protobuf enum BOUND = 0; + */ + const BOUND = 0; + /** + * The annotated method will establish the channel affinity with the channel + * which is used to execute the RPC. The corresponding + * will be used to find the affinity key from the + * response message. + * + * Generated from protobuf enum BIND = 1; + */ + const BIND = 1; + /** + * The annotated method will remove the channel affinity with the channel + * which is used to execute the RPC. The corresponding + * will be used to find the affinity key from the + * request message. + * + * Generated from protobuf enum UNBIND = 2; + */ + const UNBIND = 2; +} diff --git a/src/generated/Grpc/Gcp/ApiConfig.php b/src/generated/Grpc/Gcp/ApiConfig.php new file mode 100644 index 0000000000..519e31a343 --- /dev/null +++ b/src/generated/Grpc/Gcp/ApiConfig.php @@ -0,0 +1,84 @@ +grpc.gcp.ApiConfig + */ +class ApiConfig extends \Google\Protobuf\Internal\Message +{ + /** + * The channel pool configurations. + * + * Generated from protobuf field .grpc.gcp.ChannelPoolConfig channel_pool = 2; + */ + private $channel_pool = null; + /** + * The method configurations. + * + * Generated from protobuf field repeated .grpc.gcp.MethodConfig method = 1001; + */ + private $method; + + public function __construct() + { + \GPBMetadata\GrpcGcp::initOnce(); + parent::__construct(); + } + + /** + * The channel pool configurations. + * + * Generated from protobuf field .grpc.gcp.ChannelPoolConfig channel_pool = 2; + * @return \Grpc\Gcp\ChannelPoolConfig + */ + public function getChannelPool() + { + return $this->channel_pool; + } + + /** + * The channel pool configurations. + * + * Generated from protobuf field .grpc.gcp.ChannelPoolConfig channel_pool = 2; + * @param \Grpc\Gcp\ChannelPoolConfig $var + * @return $this + */ + public function setChannelPool($var) + { + GPBUtil::checkMessage($var, \Grpc\Gcp\ChannelPoolConfig::class); + $this->channel_pool = $var; + + return $this; + } + + /** + * The method configurations. + * + * Generated from protobuf field repeated .grpc.gcp.MethodConfig method = 1001; + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getMethod() + { + return $this->method; + } + + /** + * The method configurations. + * + * Generated from protobuf field repeated .grpc.gcp.MethodConfig method = 1001; + * @param \Grpc\Gcp\MethodConfig[]|\Google\Protobuf\Internal\RepeatedField $var + * @return $this + */ + public function setMethod($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Grpc\Gcp\MethodConfig::class); + $this->method = $arr; + + return $this; + } +} diff --git a/src/generated/Grpc/Gcp/ChannelPoolConfig.php b/src/generated/Grpc/Gcp/ChannelPoolConfig.php new file mode 100644 index 0000000000..7c03076c62 --- /dev/null +++ b/src/generated/Grpc/Gcp/ChannelPoolConfig.php @@ -0,0 +1,122 @@ +grpc.gcp.ChannelPoolConfig + */ +class ChannelPoolConfig extends \Google\Protobuf\Internal\Message +{ + /** + * The max number of channels in the pool. + * + * Generated from protobuf field uint32 max_size = 1; + */ + private $max_size = 0; + /** + * The idle timeout (seconds) of channels without bound affinity sessions. + * + * Generated from protobuf field uint64 idle_timeout = 2; + */ + private $idle_timeout = 0; + /** + * The low watermark of max number of concurrent streams in a channel. + * New channel will be created once it get hit, until we reach the max size + * of the channel pool. + * + * Generated from protobuf field uint32 max_concurrent_streams_low_watermark = 3; + */ + private $max_concurrent_streams_low_watermark = 0; + + public function __construct() + { + \GPBMetadata\GrpcGcp::initOnce(); + parent::__construct(); + } + + /** + * The max number of channels in the pool. + * + * Generated from protobuf field uint32 max_size = 1; + * @return int + */ + public function getMaxSize() + { + return $this->max_size; + } + + /** + * The max number of channels in the pool. + * + * Generated from protobuf field uint32 max_size = 1; + * @param int $var + * @return $this + */ + public function setMaxSize($var) + { + GPBUtil::checkUint32($var); + $this->max_size = $var; + + return $this; + } + + /** + * The idle timeout (seconds) of channels without bound affinity sessions. + * + * Generated from protobuf field uint64 idle_timeout = 2; + * @return int|string + */ + public function getIdleTimeout() + { + return $this->idle_timeout; + } + + /** + * The idle timeout (seconds) of channels without bound affinity sessions. + * + * Generated from protobuf field uint64 idle_timeout = 2; + * @param int|string $var + * @return $this + */ + public function setIdleTimeout($var) + { + GPBUtil::checkUint64($var); + $this->idle_timeout = $var; + + return $this; + } + + /** + * The low watermark of max number of concurrent streams in a channel. + * New channel will be created once it get hit, until we reach the max size + * of the channel pool. + * + * Generated from protobuf field uint32 max_concurrent_streams_low_watermark = 3; + * @return int + */ + public function getMaxConcurrentStreamsLowWatermark() + { + return $this->max_concurrent_streams_low_watermark; + } + + /** + * The low watermark of max number of concurrent streams in a channel. + * New channel will be created once it get hit, until we reach the max size + * of the channel pool. + * + * Generated from protobuf field uint32 max_concurrent_streams_low_watermark = 3; + * @param int $var + * @return $this + */ + public function setMaxConcurrentStreamsLowWatermark($var) + { + GPBUtil::checkUint32($var); + $this->max_concurrent_streams_low_watermark = $var; + + return $this; + } +} diff --git a/src/generated/Grpc/Gcp/MethodConfig.php b/src/generated/Grpc/Gcp/MethodConfig.php new file mode 100644 index 0000000000..b60daf5d25 --- /dev/null +++ b/src/generated/Grpc/Gcp/MethodConfig.php @@ -0,0 +1,90 @@ +grpc.gcp.MethodConfig + */ +class MethodConfig extends \Google\Protobuf\Internal\Message +{ + /** + * A fully qualified name of a gRPC method, or a wildcard pattern ending + * with .*, such as foo.bar.A, foo.bar.*. Method configs are evaluated + * sequentially, and the first one takes precedence. + * + * Generated from protobuf field repeated string name = 1; + */ + private $name; + /** + * The channel affinity configurations. + * + * Generated from protobuf field .grpc.gcp.AffinityConfig affinity = 1001; + */ + private $affinity = null; + + public function __construct() + { + \GPBMetadata\GrpcGcp::initOnce(); + parent::__construct(); + } + + /** + * A fully qualified name of a gRPC method, or a wildcard pattern ending + * with .*, such as foo.bar.A, foo.bar.*. Method configs are evaluated + * sequentially, and the first one takes precedence. + * + * Generated from protobuf field repeated string name = 1; + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getName() + { + return $this->name; + } + + /** + * A fully qualified name of a gRPC method, or a wildcard pattern ending + * with .*, such as foo.bar.A, foo.bar.*. Method configs are evaluated + * sequentially, and the first one takes precedence. + * + * Generated from protobuf field repeated string name = 1; + * @param string[]|\Google\Protobuf\Internal\RepeatedField $var + * @return $this + */ + public function setName($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::STRING); + $this->name = $arr; + + return $this; + } + + /** + * The channel affinity configurations. + * + * Generated from protobuf field .grpc.gcp.AffinityConfig affinity = 1001; + * @return \Grpc\Gcp\AffinityConfig + */ + public function getAffinity() + { + return $this->affinity; + } + + /** + * The channel affinity configurations. + * + * Generated from protobuf field .grpc.gcp.AffinityConfig affinity = 1001; + * @param \Grpc\Gcp\AffinityConfig $var + * @return $this + */ + public function setAffinity($var) + { + GPBUtil::checkMessage($var, \Grpc\Gcp\AffinityConfig::class); + $this->affinity = $var; + + return $this; + } +} diff --git a/src/grpc_gcp.proto b/src/grpc_gcp.proto new file mode 100644 index 0000000000..ff52db2030 --- /dev/null +++ b/src/grpc_gcp.proto @@ -0,0 +1,70 @@ +// Copyright 2018 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. + +syntax = "proto3"; + +package grpc.gcp; + +message ApiConfig { + // The channel pool configurations. + ChannelPoolConfig channel_pool = 2; + + // The method configurations. + repeated MethodConfig method = 1001; +} + +message ChannelPoolConfig { + // The max number of channels in the pool. + uint32 max_size = 1; + // The idle timeout (seconds) of channels without bound affinity sessions. + uint64 idle_timeout = 2; + // The low watermark of max number of concurrent streams in a channel. + // New channel will be created once it get hit, until we reach the max size + // of the channel pool. + uint32 max_concurrent_streams_low_watermark = 3; +} + +message MethodConfig { + // A fully qualified name of a gRPC method, or a wildcard pattern ending + // with .*, such as foo.bar.A, foo.bar.*. Method configs are evaluated + // sequentially, and the first one takes precedence. + repeated string name = 1; + + // The channel affinity configurations. + AffinityConfig affinity = 1001; +} + +message AffinityConfig { + enum Command { + // The annotated method will be required to be bound to an existing session + // to execute the RPC. The corresponding will be + // used to find the affinity key from the request message. + BOUND = 0; + // The annotated method will establish the channel affinity with the channel + // which is used to execute the RPC. The corresponding + // will be used to find the affinity key from the + // response message. + BIND = 1; + // The annotated method will remove the channel affinity with the channel + // which is used to execute the RPC. The corresponding + // will be used to find the affinity key from the + // request message. + UNBIND = 2; + } + // The affinity command applies on the selected gRPC methods. + Command command = 2; + // The field path of the affinity key in the request/response message. + // For example: "f.a", "f.b.d", etc. + string affinity_key = 3; +} diff --git a/tests/BasicTest.php b/tests/BasicTest.php new file mode 100644 index 0000000000..3abdab0d14 --- /dev/null +++ b/tests/BasicTest.php @@ -0,0 +1,416 @@ +_DEFAULT_MAX_CHANNELS_PER_TARGET = $max_channels; + $this->_WATER_MARK = $max_streams; + $hostname = 'spanner.googleapis.com'; + $string = file_get_contents("spanner.grpc.config"); + + + $conf = new \Grpc\Gcp\ApiConfig(); + $conf->mergeFromJsonString($string); + $channel_pool = $conf->getChannelPool(); + $channel_pool->setMaxConcurrentStreamsLowWatermark($max_streams); + + $config = new \Grpc\Gcp\Config($hostname, $conf); + $credentials = \Grpc\ChannelCredentials::createSsl(); + $auth = ApplicationDefaultCredentials::getCredentials(); + $opts = [ + 'credentials' => $credentials, + 'update_metadata' => $auth->getUpdateMetadataFunc(), + 'grpc_call_invoker' => $config->callInvoker(), + ]; + + $this->stub = new SpannerGrpcClient($hostname, $opts); + $this->call_invoker = $config->callInvoker(); + + $this->database = 'projects/grpc-gcp/instances/sample/databases/benchmark'; + $this->table = 'storage'; + $this->data = 'payload'; + } + + public function assertStatusOk($status) + { + if ($status->code != \Grpc\STATUS_OK) { + var_dump($status); + throw new \Exception("gRPC status not OK: " . $status->code . "\n"); + } + } + + // Test CreateSession Reuse Channel + public function testCreateSessionReuseChannel() + { + $this->createStub(); + for ($i = 0; $i < $this->_DEFAULT_MAX_CHANNELS_PER_TARGET; $i++) { + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + list($session, $status) = $create_session_call->wait(); + $this->assertStatusOk($status); + $delete_session_request = new DeleteSessionRequest(); + $delete_session_request->setName($session->getName()); + list($session, $status) = $this->stub->DeleteSession($delete_session_request)->wait(); + $this->assertStatusOk($status); + $result = (count($this->call_invoker->getChannel()->getChannelRefs()) == 1); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + } + } + + // Test CreateSession New Channel + public function testCreateSessionNewChannel() + { + $this->createStub(); + $rpc_calls = array(); + for ($i = 0; $i < $this->_DEFAULT_MAX_CHANNELS_PER_TARGET; $i++) { + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + $result = (count($this->call_invoker->getChannel()->getChannelRefs()) == $i + 1); + $this->assertEquals($i + 1, count($this->call_invoker->getChannel()->getChannelRefs())); + array_push($rpc_calls, $create_session_call); + } + for ($i = 0; $i < $this->_DEFAULT_MAX_CHANNELS_PER_TARGET; $i++) { + list($session, $status) = $rpc_calls[$i]->wait(); + $this->assertStatusOk($status); + $delete_session_request = new DeleteSessionRequest(); + $delete_session_request->setName($session->getName()); + $delete_session_call = $this->stub->DeleteSession($delete_session_request); + list($session, $status) = $delete_session_call->wait(); + $this->assertStatusOk($status); + $result = (count($this->call_invoker->getChannel()->getChannelRefs()) == $this->_DEFAULT_MAX_CHANNELS_PER_TARGET); + $this->assertEquals($this->_DEFAULT_MAX_CHANNELS_PER_TARGET, + count($this->call_invoker->getChannel()->getChannelRefs())); + } + + $rpc_calls = array(); + for ($i = 0; $i < $this->_DEFAULT_MAX_CHANNELS_PER_TARGET; $i++) { + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + $result = (count($this->call_invoker->getChannel()->getChannelRefs()) == $this->_DEFAULT_MAX_CHANNELS_PER_TARGET); + $this->assertEquals($this->_DEFAULT_MAX_CHANNELS_PER_TARGET, + count($this->call_invoker->getChannel()->getChannelRefs())); + array_push($rpc_calls, $create_session_call); + } + for ($i = 0; $i < $this->_DEFAULT_MAX_CHANNELS_PER_TARGET; $i++) { + list($session, $status) = $rpc_calls[$i]->wait(); + $delete_session_request = new DeleteSessionRequest(); + $delete_session_request->setName($session->getName()); + list($session, $status) = $this->stub->DeleteSession($delete_session_request)->wait(); + $this->assertStatusOk($status); + } + } + + // Test Create List Delete Session + public function testCreateListDeleteSession() + { + $this->createStub(); + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + list($session, $status) = $create_session_call->wait(); + $this->assertStatusOk($status); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $list_session_request = new ListSessionsRequest(); + $list_session_request->setDatabase($this->database); + $list_session_call = $this->stub->ListSessions($list_session_request); + list($list_session_response, $status) = $list_session_call->wait(); + $this->assertStatusOk($status); + //foreach ($list_session_response->getSessions() as $session) { + // echo "session:\n"; + // echo "name - ". $session->getName(). PHP_EOL; + //} + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $delete_session_request = new DeleteSessionRequest(); + $delete_session_request->setName($session->getName()); + list($delete_session_response, $status) = $this->stub->DeleteSession($delete_session_request)->wait(); + $this->assertStatusOk($status); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $list_session_request = new ListSessionsRequest(); + $list_session_request->setDatabase($this->database); + $list_session_call = $this->stub->ListSessions($list_session_request); + list($list_session_response, $status) = $list_session_call->wait(); + $this->assertStatusOk($status); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + } + + // Test Execute Sql + public function testExecuteSql() + { + $this->createStub(); + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + list($session, $status) = $create_session_call->wait(); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $sql_cmd = "select id from $this->table"; + $exec_sql_request = new ExecuteSqlRequest(); + $exec_sql_request->setSession($session->getName()); + $exec_sql_request->setSql($sql_cmd); + $exec_sql_call = $this->stub->ExecuteSql($exec_sql_request); + list($exec_sql_reply, $status) = $exec_sql_call->wait(); + $this->assertStatusOk($status); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + $result = ['payload']; + $i = 0; + + foreach ($exec_sql_reply->getRows() as $row) { + foreach ($row->getValues() as $value) { + $this->assertEquals($value->getStringValue(), $result[$i]); + $i += 1; + } + } + + $delete_session_request = new DeleteSessionRequest(); + $delete_session_request->setName($session->getName()); + list($delete_session_response, $status) = $this->stub->DeleteSession($delete_session_request)->wait(); + $this->assertStatusOk($status); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + } + + // Test Execute Streaming Sql + public function testExecuteStreamingSql() + { + $this->createStub(); + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + list($session, $status) = $create_session_call->wait(); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $sql_cmd = "select id from $this->table"; + $stream_exec_sql_request = new ExecuteSqlRequest(); + $stream_exec_sql_request->setSession($session->getName()); + $stream_exec_sql_request->setSql($sql_cmd); + $stream_exec_sql_call = $this->stub->ExecuteStreamingSql($stream_exec_sql_request); + $features = $stream_exec_sql_call->responses(); + $result = ['payload']; + $i = 0; + foreach ($features as $feature) { + foreach ($feature->getValues() as $value) { + $this->assertEquals($value->getStringValue(), $result[$i]); + $i += 1; + } + } + $status = $stream_exec_sql_call->getStatus(); + $this->assertStatusOk($status); + } + + // Test Concurrent Streams Watermark + public function testConcurrentStreamsWatermark() + { + $this->createStub(10, 2); + $sql_cmd = "select id from $this->table"; + $result = ['payload']; + $exec_sql_calls = array(); + $sessions = array(); + for ($i=0; $i < $this->_WATER_MARK; $i++) { + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + list($session, $status) = $create_session_call->wait(); + $this->assertStatusOk($status); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals($i + 1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals($i, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $exec_sql_request = new ExecuteSqlRequest(); + $exec_sql_request->setSession($session->getName()); + $exec_sql_request->setSql($sql_cmd); + $exec_sql_call = $this->stub->ExecuteSql($exec_sql_request); + array_push($exec_sql_calls, $exec_sql_call); + array_push($sessions, $session); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals($i + 1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals($i + 1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + } + + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + list($session, $status) = $create_session_call->wait(); + $this->assertStatusOk($status); + $this->assertEquals(2, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(2, $this->call_invoker->getChannel()->getChannelRefs()[1]->getAffinityRef()); + $this->assertEquals(2, $this->call_invoker->getChannel()->getChannelRefs()[1]->getActiveStreamRef()); + $this->assertEquals(1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + // The new request uses the new session id. + $exec_sql_request = new ExecuteSqlRequest(); + $exec_sql_request->setSession($session->getName()); + $exec_sql_request->setSql($sql_cmd); + $exec_sql_call = $this->stub->ExecuteSql($exec_sql_request); + $this->assertEquals(2, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(2, $this->call_invoker->getChannel()->getChannelRefs()[1]->getAffinityRef()); + $this->assertEquals(2, $this->call_invoker->getChannel()->getChannelRefs()[1]->getActiveStreamRef()); + $this->assertEquals(1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + // Clear session and stream + list($exec_sql_reply, $status) = $exec_sql_call->wait(); + $this->assertStatusOk($status); + $this->assertEquals($exec_sql_reply->getRows()[0]->getValues()[0]->getStringValue(), $result[0]); + $this->assertEquals(2, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(2, $this->call_invoker->getChannel()->getChannelRefs()[1]->getAffinityRef()); + $this->assertEquals(2, $this->call_invoker->getChannel()->getChannelRefs()[1]->getActiveStreamRef()); + $this->assertEquals(1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $delete_session_request = new DeleteSessionRequest(); + $delete_session_request->setName($session->getName()); + list($session, $status) = $this->stub->DeleteSession($delete_session_request)->wait(); + $this->assertStatusOk($status); + $this->assertEquals(2, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(2, $this->call_invoker->getChannel()->getChannelRefs()[1]->getAffinityRef()); + $this->assertEquals(2, $this->call_invoker->getChannel()->getChannelRefs()[1]->getActiveStreamRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + for ($i=0; $i < $this->_WATER_MARK; $i++) { + list($exec_sql_reply, $status) = $exec_sql_calls[$i]->wait(); + $this->assertStatusOk($status); + $this->assertEquals($exec_sql_reply->getRows()[0]->getValues()[0]->getStringValue(), $result[0]); + $this->assertEquals(2, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(2 - $i, $this->call_invoker->getChannel()->getChannelRefs()[1]->getAffinityRef()); + $this->assertEquals(1 - $i, $this->call_invoker->getChannel()->getChannelRefs()[1]->getActiveStreamRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $delete_session_request = new DeleteSessionRequest(); + $delete_session_request->setName($sessions[$i]->getName()); + list($session, $status) = $this->stub->DeleteSession($delete_session_request)->wait(); + $this->assertEquals(2, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(1 - $i, $this->call_invoker->getChannel()->getChannelRefs()[1]->getAffinityRef()); + $this->assertEquals(1 - $i, $this->call_invoker->getChannel()->getChannelRefs()[1]->getActiveStreamRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + } + } + + // Test More Than 100 Concurrent Stream + public function testHundredConcurrentStream() + { + $this->createStub(10, 100); + $sql_cmd = "select id from $this->table"; + $result = ['payload']; + $exec_sql_calls = array(); + $sessions = array(); + $responses_result = array(); + for ($i=0; $i < $this->_WATER_MARK; $i++) { + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + list($session, $status) = $create_session_call->wait(); + $this->assertStatusOk($status); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals($i + 1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals($i, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $exec_sql_request = new ExecuteSqlRequest(); + $exec_sql_request->setSession($session->getName()); + $exec_sql_request->setSql($sql_cmd); + $exec_sql_call = $this->stub->ExecuteStreamingSql($exec_sql_request); + $features = $exec_sql_call->responses(); + array_push($responses_result, $features); + array_push($exec_sql_calls, $exec_sql_call); + array_push($sessions, $session); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals($i + 1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals($i + 1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + } + print_r($this->call_invoker->getChannel()->getChannelRefs()); + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + list($session, $status) = $create_session_call->wait(); + $this->assertStatusOk($status); + + $this->assertEquals(2, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(100, $this->call_invoker->getChannel()->getChannelRefs()[1]->getAffinityRef()); + $this->assertEquals(100, $this->call_invoker->getChannel()->getChannelRefs()[1]->getActiveStreamRef()); + $this->assertEquals(1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + // The new request uses the new session id. + $exec_sql_request = new ExecuteSqlRequest(); + $exec_sql_request->setSession($session->getName()); + $exec_sql_request->setSql($sql_cmd); + $exec_sql_call = $this->stub->ExecuteStreamingSql($exec_sql_request); + $features = $exec_sql_call->responses(); + array_push($exec_sql_calls, $exec_sql_call); + foreach ($features as $feature) { + $i = 0; + foreach ($feature->getValues() as $value) { + $this->assertEquals($value->getStringValue(), $result[$i]); + $i += 1; + } + } + $status = $exec_sql_call->getStatus(); + $this->assertStatusOk($status); + } +} diff --git a/tests/ChannelManagementNoConfigTest.php b/tests/ChannelManagementNoConfigTest.php new file mode 100644 index 0000000000..d8ed6d01bd --- /dev/null +++ b/tests/ChannelManagementNoConfigTest.php @@ -0,0 +1,411 @@ +_DEFAULT_MAX_CHANNELS_PER_TARGET = $max_channels; + $this->_WATER_MARK = $max_streams; + $hostname = 'spanner.googleapis.com'; + $conf = new \Grpc\Gcp\ApiConfig(); + $channel_pool = $conf->getChannelPool(); + if ($channel_pool == null) { + $channel_pool = new \Grpc\Gcp\ChannelPoolConfig(); + $conf->setChannelPool($channel_pool); + } + $channel_pool->setMaxConcurrentStreamsLowWatermark($max_streams); + $config = new \Google\Cloud\Grpc\Config($hostname, $conf); + $credentials = \Grpc\ChannelCredentials::createSsl(); + $auth = ApplicationDefaultCredentials::getCredentials(); + $opts = [ + 'credentials' => $credentials, + 'update_metadata' => $auth->getUpdateMetadataFunc(), + 'grpc_call_invoker' => $config->callInvoker(), + ]; + + $this->stub = new SpannerGrpcClient($hostname, $opts); + $this->call_invoker = $config->callInvoker(); + + $this->database = 'projects/grpc-gcp/instances/sample/databases/benchmark'; + $this->table = 'storage'; + $this->data = 'payload'; + } + + public function assertStatusOk($status) + { + if ($status->code != \Grpc\STATUS_OK) { + var_dump($status); + throw new \Exception("gRPC status not OK: " . $status->code . "\n"); + } + } + + // Test CreateSession Reuse Channel + public function testCreateSessionReuseChannel() + { + $this->createStub(); + for ($i = 0; $i < $this->_DEFAULT_MAX_CHANNELS_PER_TARGET; $i++) { + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + $this->assertEquals(1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + list($session, $status) = $create_session_call->wait(); + $this->assertStatusOk($status); + $delete_session_request = new DeleteSessionRequest(); + $delete_session_request->setName($session->getName()); + list($session, $status) = $this->stub->DeleteSession($delete_session_request)->wait(); + $this->assertStatusOk($status); + $result = (count($this->call_invoker->getChannel()->getChannelRefs()) == 1); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + } + } + + // Test CreateSession New Channel + public function testCreateSessionNewChannel() + { + $this->createStub(); + $rpc_calls = array(); + // All RPCs are sent by the first channel created. Because the numbers of streams + // are less than 100. + for ($i = 0; $i < $this->_DEFAULT_MAX_CHANNELS_PER_TARGET; $i++) { + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + $this->assertEquals(1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + $this->assertEquals($i + 1, count($this->call_invoker->getChannel()->getChannelRefs())); + array_push($rpc_calls, $create_session_call); + } + $sessions = array(); + for ($i = 0; $i < $this->_DEFAULT_MAX_CHANNELS_PER_TARGET; $i++) { + list($session, $status) = $rpc_calls[$i]->wait(); + array_push($sessions, $session); + $this->assertStatusOk($status); + } + for ($i = 0; $i < $this->_DEFAULT_MAX_CHANNELS_PER_TARGET; $i++) { + $result = $this->call_invoker->getChannel()->getChannelRefs()[$i]->getActiveStreamRef(); + } + for ($i = 0; $i < $this->_DEFAULT_MAX_CHANNELS_PER_TARGET; $i++) { + $delete_session_request = new DeleteSessionRequest(); + $delete_session_request->setName($sessions[$i]->getName()); + $delete_session_call = $this->stub->DeleteSession($delete_session_request); + list($session, $status) = $delete_session_call->wait(); + $this->assertStatusOk($status); + $result = $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef(); + $this->assertEquals(0, $result); + } + } + + // Test Create List Delete Session + public function testCreateListDeleteSession() + { + $this->createStub(); + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + list($session, $status) = $create_session_call->wait(); + $this->assertStatusOk($status); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $list_session_request = new ListSessionsRequest(); + $list_session_request->setDatabase($this->database); + $list_session_call = $this->stub->ListSessions($list_session_request); + list($list_session_response, $status) = $list_session_call->wait(); + $this->assertStatusOk($status); + //foreach ($list_session_response->getSessions() as $session) { + // echo "session:\n"; + // echo "name - ". $session->getName(). PHP_EOL; + //} + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $delete_session_request = new DeleteSessionRequest(); + $delete_session_request->setName($session->getName()); + list($delete_session_response, $status) = $this->stub->DeleteSession($delete_session_request)->wait(); + $this->assertStatusOk($status); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $list_session_request = new ListSessionsRequest(); + $list_session_request->setDatabase($this->database); + $list_session_call = $this->stub->ListSessions($list_session_request); + list($list_session_response, $status) = $list_session_call->wait(); + $this->assertStatusOk($status); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + } + + + // Test Execute Sql + public function testExecuteSql() + { + $this->createStub(); + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + list($session, $status) = $create_session_call->wait(); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $sql_cmd = "select id from $this->table"; + $exec_sql_request = new ExecuteSqlRequest(); + $exec_sql_request->setSession($session->getName()); + $exec_sql_request->setSql($sql_cmd); + $exec_sql_call = $this->stub->ExecuteSql($exec_sql_request); + list($exec_sql_reply, $status) = $exec_sql_call->wait(); + $this->assertStatusOk($status); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + $result = ['payload']; + $i = 0; + + foreach ($exec_sql_reply->getRows() as $row) { + foreach ($row->getValues() as $value) { + $this->assertEquals($value->getStringValue(), $result[$i]); + $i += 1; + } + } + + $delete_session_request = new DeleteSessionRequest(); + $delete_session_request->setName($session->getName()); + list($delete_session_response, $status) = $this->stub->DeleteSession($delete_session_request)->wait(); + $this->assertStatusOk($status); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + } + + + // Test Execute Streaming Sql + public function testExecuteStreamingSql() + { + $this->createStub(); + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + list($session, $status) = $create_session_call->wait(); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $sql_cmd = "select id from $this->table"; + $stream_exec_sql_request = new ExecuteSqlRequest(); + $stream_exec_sql_request->setSession($session->getName()); + $stream_exec_sql_request->setSql($sql_cmd); + $stream_exec_sql_call = $this->stub->ExecuteStreamingSql($stream_exec_sql_request); + $features = $stream_exec_sql_call->responses(); + $result = ['payload']; + $i = 0; + foreach ($features as $feature) { + foreach ($feature->getValues() as $value) { + $this->assertEquals($value->getStringValue(), $result[$i]); + $i += 1; + } + } + $status = $stream_exec_sql_call->getStatus(); + $this->assertStatusOk($status); + } + + // Test Concurrent Streams Watermark + public function testConcurrentStreamsWatermark() + { + $this->createStub(10, 2); + $sql_cmd = "select id from $this->table"; + $result = ['payload']; + $exec_sql_calls = array(); + $sessions = array(); + for ($i=0; $i < $this->_WATER_MARK; $i++) { + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + list($session, $status) = $create_session_call->wait(); + $this->assertStatusOk($status); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals($i, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $exec_sql_request = new ExecuteSqlRequest(); + $exec_sql_request->setSession($session->getName()); + $exec_sql_request->setSql($sql_cmd); + $exec_sql_call = $this->stub->ExecuteSql($exec_sql_request); + array_push($exec_sql_calls, $exec_sql_call); + array_push($sessions, $session); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals($i + 1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + } + + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + list($session, $status) = $create_session_call->wait(); + $this->assertStatusOk($status); + $this->assertEquals(2, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[1]->getAffinityRef()); + $this->assertEquals(2, $this->call_invoker->getChannel()->getChannelRefs()[1]->getActiveStreamRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + // The new request uses the new session id. + $exec_sql_request = new ExecuteSqlRequest(); + $exec_sql_request->setSession($session->getName()); + $exec_sql_request->setSql($sql_cmd); + $exec_sql_call = $this->stub->ExecuteSql($exec_sql_request); + $this->assertEquals(2, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[1]->getAffinityRef()); + $this->assertEquals(2, $this->call_invoker->getChannel()->getChannelRefs()[1]->getActiveStreamRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + // Clear session and stream + list($exec_sql_reply, $status) = $exec_sql_call->wait(); + $this->assertStatusOk($status); + $this->assertEquals($exec_sql_reply->getRows()[0]->getValues()[0]->getStringValue(), $result[0]); + $this->assertEquals(2, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[1]->getAffinityRef()); + $this->assertEquals(2, $this->call_invoker->getChannel()->getChannelRefs()[1]->getActiveStreamRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $delete_session_request = new DeleteSessionRequest(); + $delete_session_request->setName($session->getName()); + list($session, $status) = $this->stub->DeleteSession($delete_session_request)->wait(); + $this->assertStatusOk($status); + $this->assertEquals(2, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[1]->getAffinityRef()); + $this->assertEquals(2, $this->call_invoker->getChannel()->getChannelRefs()[1]->getActiveStreamRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + for ($i=0; $i < $this->_WATER_MARK; $i++) { + list($exec_sql_reply, $status) = $exec_sql_calls[$i]->wait(); + $this->assertStatusOk($status); + $this->assertEquals($exec_sql_reply->getRows()[0]->getValues()[0]->getStringValue(), $result[0]); + $this->assertEquals(2, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[1]->getAffinityRef()); + $this->assertEquals(1 - $i, $this->call_invoker->getChannel()->getChannelRefs()[1]->getActiveStreamRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $delete_session_request = new DeleteSessionRequest(); + $delete_session_request->setName($sessions[$i]->getName()); + list($session, $status) = $this->stub->DeleteSession($delete_session_request)->wait(); + $this->assertEquals(2, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[1]->getAffinityRef()); + $this->assertEquals(1 - $i, $this->call_invoker->getChannel()->getChannelRefs()[1]->getActiveStreamRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + } + } + + // Test More Than 100 Concurrent Stream + public function testHundredConcurrentStream() + { + $this->createStub(10, 100); + $sql_cmd = "select id from $this->table"; + $result = ['payload']; + $exec_sql_calls = array(); + $sessions = array(); + $responses_result = array(); + for ($i=0; $i < $this->_WATER_MARK; $i++) { + echo "$i "; + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + list($session, $status) = $create_session_call->wait(); + $this->assertStatusOk($status); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals($i, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + $exec_sql_request = new ExecuteSqlRequest(); + $exec_sql_request->setSession($session->getName()); + $exec_sql_request->setSql($sql_cmd); + $exec_sql_call = $this->stub->ExecuteStreamingSql($exec_sql_request); + $features = $exec_sql_call->responses(); + array_push($responses_result, $features); + array_push($exec_sql_calls, $exec_sql_call); + array_push($sessions, $session); + $this->assertEquals(1, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals($i + 1, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + } + + print_r($this->call_invoker->getChannel()->getChannelRefs()); + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($this->database); + $create_session_call = $this->stub->CreateSession($create_session_request); + list($session, $status) = $create_session_call->wait(); + $this->assertStatusOk($status); + + $this->assertEquals(2, count($this->call_invoker->getChannel()->getChannelRefs())); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[1]->getAffinityRef()); + $this->assertEquals(100, $this->call_invoker->getChannel()->getChannelRefs()[1]->getActiveStreamRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); + $this->assertEquals(0, $this->call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); + + // The new request uses the new session id. + $exec_sql_request = new ExecuteSqlRequest(); + $exec_sql_request->setSession($session->getName()); + $exec_sql_request->setSql($sql_cmd); + $exec_sql_call = $this->stub->ExecuteStreamingSql($exec_sql_request); + $features = $exec_sql_call->responses(); + array_push($exec_sql_calls, $exec_sql_call); + foreach ($features as $feature) { + $i = 0; + foreach ($feature->getValues() as $value) { + $this->assertEquals($value->getStringValue(), $result[$i]); + $i += 1; + } + } + $status = $exec_sql_call->getStatus(); + $this->assertStatusOk($status); + } +} diff --git a/tests/code_gen.sh b/tests/code_gen.sh new file mode 100644 index 0000000000..451f3c0966 --- /dev/null +++ b/tests/code_gen.sh @@ -0,0 +1,5 @@ +#!/bin/bash +protoc --proto_path=./../src/ --php_out=./generated/ \ + --grpc_out=./generated/ \ + --plugin=protoc-gen-grpc=/usr/local/bin/grpc_php_plugin \ + ./../src/grpc_gcp.proto diff --git a/tests/composer.json b/tests/composer.json new file mode 100644 index 0000000000..4f272631b3 --- /dev/null +++ b/tests/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "google/cloud": "^0.69.1" + } +} diff --git a/tests/fpm/README.md b/tests/fpm/README.md new file mode 100644 index 0000000000..0672f16c82 --- /dev/null +++ b/tests/fpm/README.md @@ -0,0 +1,11 @@ +PHP-FPM test is used to verify that channel created by script1 can be fetched by +script2. It can be run inside browser or by commands in terminal. +``` +curl -o - 'tests/fpm/session_unset.php' +curl -o - 'tests/fpm/php-fpm1.php' +curl -o - 'tests/fpm/php-fpm2.php' +``` +`session_unset.php` is clear all items inside current worker process. +`php-fpm1.php` creates 2 channels and update active streams with each channel. +`php-fpm2.php` can fetch 2 channels created by `php-fpm1.php` and use them. + diff --git a/tests/fpm/php-fpm1.php b/tests/fpm/php-fpm1.php new file mode 100644 index 0000000000..a80760e0a2 --- /dev/null +++ b/tests/fpm/php-fpm1.php @@ -0,0 +1,84 @@ +mergeFromJsonString($string); +$channel_pool = $conf->getChannelPool(); +$channel_pool->setMaxConcurrentStreamsLowWatermark($_WATER_MARK); +$config = new \Google\Cloud\Grpc\Config($hostname, $conf); + +$credentials = \Grpc\ChannelCredentials::createSsl(); +$auth = ApplicationDefaultCredentials::getCredentials(); +$opts = [ + 'credentials' => $credentials, + 'update_metadata' => $auth->getUpdateMetadataFunc(), + 'grpc_call_invoker' => $config->callInvoker(), +]; + +$stub = new SpannerGrpcClient($hostname, $opts); +$call_invoker = $config->callInvoker(); + +$database = 'projects/grpc-gcp/instances/sample/databases/benchmark'; +$table = 'storage'; +$data = 'payload'; + + +function assertEqual($var1, $var2, $str = "") +{ + if ($var1 != $var2) { + throw new \Exception("$str $var1 not matches to $var2.\n"); + } +} +function assertStatusOk($status) +{ + if ($status->code != \Grpc\STATUS_OK) { + var_dump($status); + throw new \Exception("gRPC status not OK: " . $status->code . "\n"); + } +} + +// Test Concurrent Streams Watermark +$sql_cmd = "select id from $table"; +$result = ['payload']; +$exec_sql_calls = array(); +$sessions = array(); +for ($i=0; $i < $_WATER_MARK + 1; $i++) { + $create_session_request = new CreateSessionRequest(); + $create_session_request->setDatabase($database); + $create_session_call = $stub->CreateSession($create_session_request); + list($session, $status) = $create_session_call->wait(); + assertStatusOk($status); + + $exec_sql_request = new ExecuteSqlRequest(); + $exec_sql_request->setSession($session->getName()); + $exec_sql_request->setSql($sql_cmd); + $exec_sql_call = $stub->ExecuteSql($exec_sql_request); + array_push($exec_sql_calls, $exec_sql_call); + array_push($sessions, $session->getName()); +} + +apcu_add('gcp_sessions', $sessions); + +for ($i=0; $i < $_WATER_MARK + 1; $i++) { + list($exec_sql_reply, $status) = $exec_sql_calls[$i]->wait(); + assertStatusOk($status); +} + +print_r($call_invoker->getChannel()->getChannelRefs()); diff --git a/tests/fpm/php-fpm2.php b/tests/fpm/php-fpm2.php new file mode 100644 index 0000000000..cae11405e5 --- /dev/null +++ b/tests/fpm/php-fpm2.php @@ -0,0 +1,78 @@ +mergeFromJsonString($string); +$channel_pool = $conf->getChannelPool(); +$channel_pool->setMaxConcurrentStreamsLowWatermark($_WATER_MARK); +$config = new \Google\Cloud\Grpc\Config($hostname, $conf); + +$credentials = \Grpc\ChannelCredentials::createSsl(); +$auth = ApplicationDefaultCredentials::getCredentials(); +$opts = [ + 'credentials' => $credentials, + 'update_metadata' => $auth->getUpdateMetadataFunc(), + 'grpc_call_invoker' => $config->callInvoker(), +]; + +$stub = new SpannerGrpcClient($hostname, $opts); +$call_invoker = $config->callInvoker(); + +print_r($call_invoker->getChannel()->getChannelRefs()); + +$database = 'projects/grpc-gcp/instances/sample/databases/benchmark'; +$table = 'storage'; +$data = 'payload'; + + +function assertEqual($var1, $var2, $str = "") +{ + if ($var1 != $var2) { + throw new \Exception("$str $var1 not matches to $var2.\n"); + } +} +function assertStatusOk($status) +{ + if ($status->code != \Grpc\STATUS_OK) { + var_dump($status); + throw new \Exception("gRPC status not OK: " . $status->code . "\n"); + } +} + +assertEqual(2, count($call_invoker->getChannel()->getChannelRefs())); +assertEqual(1, $call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); +assertEqual(0, $call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); +assertEqual(2, $call_invoker->getChannel()->getChannelRefs()[1]->getAffinityRef()); +assertEqual(0, $call_invoker->getChannel()->getChannelRefs()[1]->getActiveStreamRef()); + +$sessions = apcu_fetch('gcp_sessions'); +print_r($sessions); +for ($i = 0; $i < $_WATER_MARK + 1; $i++) { + $sessions_name = $sessions[$i]; + $delete_session_request = new DeleteSessionRequest(); + $delete_session_request->setName($sessions_name); + list($delete_session_response, $status) = $stub->DeleteSession($delete_session_request)->wait(); +} + +assertEqual(2, count($call_invoker->getChannel()->getChannelRefs())); +assertEqual(0, $call_invoker->getChannel()->getChannelRefs()[0]->getAffinityRef()); +assertEqual(0, $call_invoker->getChannel()->getChannelRefs()[0]->getActiveStreamRef()); +assertEqual(0, $call_invoker->getChannel()->getChannelRefs()[1]->getAffinityRef()); +assertEqual(0, $call_invoker->getChannel()->getChannelRefs()[1]->getActiveStreamRef()); diff --git a/tests/grpc_unit_test/ChannelTest.php b/tests/grpc_unit_test/ChannelTest.php new file mode 100644 index 0000000000..b4a0d510c3 --- /dev/null +++ b/tests/grpc_unit_test/ChannelTest.php @@ -0,0 +1,696 @@ +channel)) { + $this->channel->close(); + } + } + + public function testInsecureCredentials() + { + $this->channel = new Grpc\GCP\GcpExtensionChannel('localhost:50000', + ['credentials' => Grpc\ChannelCredentials::createInsecure()]); + $this->assertSame('Grpc\GCP\GcpExtensionChannel', get_class($this->channel)); + } + + public function testGetConnectivityState() + { + $this->channel = new Grpc\GCP\GcpExtensionChannel('localhost:50001', + ['credentials' => Grpc\ChannelCredentials::createInsecure()]); + $state = $this->channel->getConnectivityState(); + $this->assertEquals(0, $state); + } + + public function testGetConnectivityStateWithInt() + { + $this->channel = new Grpc\GCP\GcpExtensionChannel('localhost:50002', + ['credentials' => Grpc\ChannelCredentials::createInsecure()]); + $state = $this->channel->getConnectivityState(123); + $this->assertEquals(0, $state); + } + + public function testGetConnectivityStateWithString() + { + $this->channel = new Grpc\GCP\GcpExtensionChannel('localhost:50003', + ['credentials' => Grpc\ChannelCredentials::createInsecure()]); + $state = $this->channel->getConnectivityState('hello'); + $this->assertEquals(0, $state); + } + + public function testGetConnectivityStateWithBool() + { + $this->channel = new Grpc\GCP\GcpExtensionChannel('localhost:50004', + ['credentials' => Grpc\ChannelCredentials::createInsecure()]); + $state = $this->channel->getConnectivityState(true); + $this->assertEquals(0, $state); + } + + public function testGetTarget() + { + $this->channel = new Grpc\GCP\GcpExtensionChannel('localhost:50005', + ['credentials' => Grpc\ChannelCredentials::createInsecure()]); + $target = $this->channel->getTarget(); + $this->assertTrue(is_string($target)); + } + + public function testWatchConnectivityState() + { + $this->channel = new Grpc\GCP\GcpExtensionChannel('localhost:50006', + ['credentials' => Grpc\ChannelCredentials::createInsecure()]); + $now = Grpc\Timeval::now(); + $deadline = $now->add(new Grpc\Timeval(100*1000)); // 100ms + // we act as if 'CONNECTING'(=1) was the last state + // we saw, so the default state of 'IDLE' should be delivered instantly + $state = $this->channel->watchConnectivityState(3, $deadline); + print_r($state); + $this->assertTrue($state); + unset($now); + unset($deadline); + } + + public function testClose() + { + $this->channel = new Grpc\GCP\GcpExtensionChannel('localhost:50007', + ['credentials' => Grpc\ChannelCredentials::createInsecure()]); + $this->assertNotNull($this->channel); + $this->channel->close(); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidConstructorWithNull() + { + $this->channel = new Grpc\GCP\GcpExtensionChannel(); + $this->assertNull($this->channel); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidConstructorWith() + { + $this->channel = new Grpc\GCP\GcpExtensionChannel('localhost:50008', 'invalid'); + $this->assertNull($this->channel); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidCredentials() + { + $this->channel = new Grpc\GCP\GcpExtensionChannel('localhost:50009', + ['credentials' => new Grpc\Timeval(100)]); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidOptionsArray() + { + $this->channel = new Grpc\GCP\GcpExtensionChannel('localhost:50010', + ['abc' => []]); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidGetConnectivityStateWithArray() + { + $this->channel = new Grpc\GCP\GcpExtensionChannel('localhost:50011', + ['credentials' => Grpc\ChannelCredentials::createInsecure()]); + $this->channel->getConnectivityState([]); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidWatchConnectivityState() + { + $this->channel = new Grpc\GCP\GcpExtensionChannel('localhost:50012', + ['credentials' => Grpc\ChannelCredentials::createInsecure()]); + $this->channel->watchConnectivityState([]); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidWatchConnectivityState2() + { + $this->channel = new Grpc\GCP\GcpExtensionChannel('localhost:50013', + ['credentials' => Grpc\ChannelCredentials::createInsecure()]); + $this->channel->watchConnectivityState(1, 'hi'); + } + + + public function assertConnecting($state) + { + $this->assertTrue($state == GRPC\CHANNEL_CONNECTING || + $state == GRPC\CHANNEL_TRANSIENT_FAILURE); + } + + public function waitUntilNotIdle($channel) + { + for ($i = 0; $i < 10; $i++) { + $now = Grpc\Timeval::now(); + $deadline = $now->add(new Grpc\Timeval(1000)); + if ($channel->watchConnectivityState(GRPC\CHANNEL_IDLE, + $deadline)) { + return true; + } + } + $this->assertTrue(false); + } + + public function testPersistentChannelSameHost() + { + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50014', [ + "grpc_target_persist_bound" => 3, + ]); + // the underlying grpc channel is the same by default + // when connecting to the same host + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50014', []); + + // both channels should be IDLE + $state = $this->channel1->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + // both channels should now be in the CONNECTING state + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertConnecting($state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelDifferentHost() + { + // two different underlying channels because different hostname + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50015', [ + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50016', []); + + // both channels should be IDLE + $state = $this->channel1->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + // channel1 should now be in the CONNECTING state + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + // channel2 should still be in the IDLE state + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelSameArgs() + { + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50017', [ + "grpc_target_persist_bound" => 3, + "abc" => "def", + ]); + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50017', ["abc" => "def"]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertConnecting($state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelDifferentArgs() + { + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50018', [ + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50018', ["abc" => "def"]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelSameChannelCredentials() + { + $creds1 = Grpc\ChannelCredentials::createSsl(); + $creds2 = Grpc\ChannelCredentials::createSsl(); + + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50019', + ["credentials" => $creds1, + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50019', + ["credentials" => $creds2]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertConnecting($state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelDifferentChannelCredentials() + { + $creds1 = Grpc\ChannelCredentials::createSsl(); + $creds2 = Grpc\ChannelCredentials::createSsl( + file_get_contents(dirname(__FILE__) . '/data/ca.pem')); + + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50020', + ["credentials" => $creds1, + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50020', + ["credentials" => $creds2]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelSameChannelCredentialsRootCerts() + { + $creds1 = Grpc\ChannelCredentials::createSsl( + file_get_contents(dirname(__FILE__) . '/data/ca.pem')); + $creds2 = Grpc\ChannelCredentials::createSsl( + file_get_contents(dirname(__FILE__) . '/data/ca.pem')); + + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50021', + ["credentials" => $creds1, + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50021', + ["credentials" => $creds2]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertConnecting($state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelDifferentSecureChannelCredentials() + { + $creds1 = Grpc\ChannelCredentials::createSsl(); + $creds2 = Grpc\ChannelCredentials::createInsecure(); + + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50022', + ["credentials" => $creds1, + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50022', + ["credentials" => $creds2]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelSharedChannelClose1() + { + // same underlying channel + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50123', [ + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50123', []); + + // close channel1 + $this->channel1->close(); + + // channel2 can still be use. We need to exclude the possible that + // in testPersistentChannelSharedChannelClose2, the exception is thrown + // by channel1. + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + } + + /** + * @expectedException RuntimeException + */ + public function testPersistentChannelSharedChannelClose2() + { + // same underlying channel + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50223', [ + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50223', []); + + // close channel1 + $this->channel1->close(); + + // channel2 can still be use + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + // channel 1 is closed + $state = $this->channel1->getConnectivityState(); + } + + public function testPersistentChannelCreateAfterClose() + { + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50024', [ + "grpc_target_persist_bound" => 3, + ]); + + $this->channel1->close(); + + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50024', []); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $this->channel2->close(); + } + + public function testPersistentChannelSharedMoreThanTwo() + { + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50025', [ + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50025', []); + $this->channel3 = new Grpc\GCP\GcpExtensionChannel('localhost:50025', []); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + // all 3 channels should be in CONNECTING state + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel3->getConnectivityState(); + $this->assertConnecting($state); + + $this->channel1->close(); + } + + public function callbackFunc($context) + { + return []; + } + + public function callbackFunc2($context) + { + return ["k1" => "v1"]; + } + + public function testPersistentChannelWithCallCredentials() + { + $creds = Grpc\ChannelCredentials::createSsl(); + $callCreds = Grpc\CallCredentials::createFromPlugin( + [$this, 'callbackFunc']); + $credsWithCallCreds = Grpc\ChannelCredentials::createComposite( + $creds, $callCreds); + + // If a ChannelCredentials object is composed with a + // CallCredentials object, the underlying grpc channel will + // always be created new and NOT persisted. + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50026', + ["credentials" => + $credsWithCallCreds, + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50026', + ["credentials" => + $credsWithCallCreds]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelWithDifferentCallCredentials() + { + $callCreds1 = Grpc\CallCredentials::createFromPlugin( + [$this, 'callbackFunc']); + $callCreds2 = Grpc\CallCredentials::createFromPlugin( + [$this, 'callbackFunc2']); + + $creds1 = Grpc\ChannelCredentials::createSsl(); + $creds2 = Grpc\ChannelCredentials::createComposite( + $creds1, $callCreds1); + $creds3 = Grpc\ChannelCredentials::createComposite( + $creds1, $callCreds2); + + // Similar to the test above, anytime a ChannelCredentials + // object is composed with a CallCredentials object, the + // underlying grpc channel will always be separate and not + // persisted + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50027', + ["credentials" => $creds1, + "grpc_target_persist_bound" => 3, + ]); + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50027', + ["credentials" => $creds2]); + $this->channel3 = new Grpc\GCP\GcpExtensionChannel('localhost:50027', + ["credentials" => $creds3]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + $state = $this->channel3->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $this->channel1->close(); + $this->channel2->close(); + $this->channel3->close(); + } + + public function testPersistentChannelForceNew() + { + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50028', [ + "grpc_target_persist_bound" => 2, + ]); + // even though all the channel params are the same, channel2 + // has a new and different underlying channel + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50028', + ["force_new" => true]); + + // try to connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelForceNewOldChannelIdle1() + { + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50029', [ + "grpc_target_persist_bound" => 2, + ]); + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50029', + ["force_new" => true]); + // channel3 shares with channel1 + $this->channel3 = new Grpc\GCP\GcpExtensionChannel('localhost:50029', []); + + // try to connect on channel2 + $state = $this->channel2->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel2); + $state = $this->channel1->getConnectivityState(); + + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + $state = $this->channel2->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel3->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelForceNewOldChannelIdle2() + { + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50029', [ + "grpc_target_persist_bound" => 2, + ]); + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50029', []); + + // try to connect on channel2 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel2); + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + $state = $this->channel2->getConnectivityState(); + $this->assertConnecting($state); + + $this->channel1->close(); + $this->channel2->close(); + } + + public function testPersistentChannelForceNewOldChannelClose1() + { + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50130', [ + "grpc_target_persist_bound" => 2, + ]); + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50130', + ["force_new" => true]); + // channel3 shares with channel1 + $this->channel3 = new Grpc\GCP\GcpExtensionChannel('localhost:50130', []); + + $this->channel1->close(); + + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + // channel3 is still usable. We need to exclude the possibility that in + // testPersistentChannelForceNewOldChannelClose2, the exception is thrown + // by channel1 and channel2. + $state = $this->channel3->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + } + + /** + * @expectedException RuntimeException + */ + public function testPersistentChannelForceNewOldChannelClose2() + { + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50230', [ + "grpc_target_persist_bound" => 2, + ]); + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50230', + ["force_new" => true]); + // channel3 shares with channel1 + $this->channel3 = new Grpc\GCP\GcpExtensionChannel('localhost:50230', []); + + $this->channel1->close(); + + $state = $this->channel2->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + // channel3 is still usable + $state = $this->channel3->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + // channel 1 is closed + $this->channel1->getConnectivityState(); + } + + public function testPersistentChannelForceNewNewChannelClose() + { + $this->channel1 = new Grpc\GCP\GcpExtensionChannel('localhost:50031', [ + "grpc_target_persist_bound" => 2, + ]); + $this->channel2 = new Grpc\GCP\GcpExtensionChannel('localhost:50031', + ["force_new" => true]); + $this->channel3 = new Grpc\GCP\GcpExtensionChannel('localhost:50031', []); + + $this->channel2->close(); + + $state = $this->channel1->getConnectivityState(); + $this->assertEquals(GRPC\CHANNEL_IDLE, $state); + + // can still connect on channel1 + $state = $this->channel1->getConnectivityState(true); + $this->waitUntilNotIdle($this->channel1); + + $state = $this->channel1->getConnectivityState(); + $this->assertConnecting($state); + + $this->channel1->close(); + } +} diff --git a/tests/grpc_unit_test/data/README b/tests/grpc_unit_test/data/README new file mode 100644 index 0000000000..888d95b900 --- /dev/null +++ b/tests/grpc_unit_test/data/README @@ -0,0 +1 @@ +CONFIRMEDTESTKEY diff --git a/tests/grpc_unit_test/data/ca.pem b/tests/grpc_unit_test/data/ca.pem new file mode 100755 index 0000000000..6c8511a73c --- /dev/null +++ b/tests/grpc_unit_test/data/ca.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla +Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 +YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT +BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7 ++L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu +g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd +Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau +sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m +oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG +Dfcog5wrJytaQ6UA0wE= +-----END CERTIFICATE----- diff --git a/tests/grpc_unit_test/data/server1.key b/tests/grpc_unit_test/data/server1.key new file mode 100755 index 0000000000..143a5b8765 --- /dev/null +++ b/tests/grpc_unit_test/data/server1.key @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD +M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf +3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY +AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm +V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY +tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p +dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q +K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR +81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff +DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd +aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2 +ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3 +XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe +F98XJ7tIFfJq +-----END PRIVATE KEY----- diff --git a/tests/grpc_unit_test/data/server1.pem b/tests/grpc_unit_test/data/server1.pem new file mode 100755 index 0000000000..f3d43fcc5b --- /dev/null +++ b/tests/grpc_unit_test/data/server1.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET +MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ +dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx +MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV +BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50 +ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco +LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg +zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd +9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw +CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy +em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G +CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6 +hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh +y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8 +-----END CERTIFICATE----- diff --git a/tests/spanner.grpc.config b/tests/spanner.grpc.config new file mode 100644 index 0000000000..e419aef276 --- /dev/null +++ b/tests/spanner.grpc.config @@ -0,0 +1,30 @@ +{ + "channelPool":{"maxSize":10,"maxConcurrentStreamsLowWatermark":1}, + "method":[ + {"name":["/google.spanner.v1.Spanner/CreateSession"], + "affinity":{"command":"BIND", "affinityKey":"name"}}, + {"name":["/google.spanner.v1.Spanner/GetSession"], + "affinity":{"command":"BOUND", "affinityKey":"name"}}, + {"name":["/google.spanner.v1.Spanner/DeleteSession"], + "affinity":{"command":"UNBIND", "affinityKey":"name"}}, + {"name":["/google.spanner.v1.Spanner/ExecuteSql"], + "affinity":{"command":"BOUND", "affinityKey":"session"}}, + {"name":["/google.spanner.v1.Spanner/ExecuteStreamingSql"], + "affinity":{"command":"BOUND", "affinityKey":"session"}}, + {"name":["/google.spanner.v1.Spanner/Read"], + "affinity":{"command":"BOUND", "affinityKey":"session"}}, + {"name":["/google.spanner.v1.Spanner/StreamingRead"], + "affinity":{"command":"BOUND", "affinityKey":"session"}}, + {"name":["/google.spanner.v1.Spanner/BeginTransaction"], + "affinity":{"command":"BOUND", "affinityKey":"session"}}, + {"name":["/google.spanner.v1.Spanner/Commit"], + "affinity":{"command":"BOUND", "affinityKey":"session"}}, + {"name":["/google.spanner.v1.Spanner/Rollback"], + "affinity":{"command":"BOUND", "affinityKey":"session"}}, + {"name":["/google.spanner.v1.Spanner/PartitionQuery"], + "affinity":{"command":"BOUND", "affinityKey":"session"}}, + {"name":["/google.spanner.v1.Spanner/PartitionRead"], + "affinity":{"command":"BOUND", "affinityKey":"session"}} + ] +} +