├── .eslintignore ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── support_request.md ├── PULL_REQUEST_TEMPLATE.md ├── release-please.yml ├── trusted-contribution.yml └── workflows │ ├── ci.yaml │ ├── codeql-analysis.yml │ └── release.yaml ├── .gitignore ├── .gitmodules ├── .nycrc ├── .prettierignore ├── .prettierrc.js ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── cloudbuild.yaml ├── cloudprober ├── cloudprober.cfg ├── codegen.sh ├── grpc_gcp_prober │ ├── firestore_probes.js │ ├── prober.js │ ├── spanner_probes.js │ └── stackdriver_util.js ├── package.json ├── setup.sh └── test_prober.sh ├── codegen.sh ├── doc ├── cloudprober-guide.md └── gRPC-client-user-guide.md ├── package.json ├── protos ├── grpc_gcp.proto └── test_service.proto ├── renovate.json ├── src ├── channel_ref.ts ├── gcp_channel_factory.ts ├── generated │ ├── grpc_gcp.d.ts │ └── grpc_gcp.js ├── grpc_interface.ts └── index.ts ├── test ├── .eslintrc.yaml ├── integration │ ├── local_service_test.js │ ├── spanner.grpc.config │ └── spanner_test.js └── unit │ └── channel_factory_test.js └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | test/google 3 | test/googleapis 4 | src/generated 5 | build/ 6 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/gts", 3 | "overrides": [ 4 | { 5 | "files": ["test/**/*.js"], 6 | "rules": { 7 | "no-unused-vars": "off" 8 | } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | Thanks for stopping by to let us know something could be better! 8 | 9 | **PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response. 10 | 11 | Please run down the following list and make sure you've tried the usual "quick fixes": 12 | 13 | - Search the issues already opened: https://github.com/GoogleCloudPlatform/grpc-gcp-node/issues 14 | - Search the issues on our "catch-all" repository: https://github.com/googleapis/google-cloud-node 15 | - Search StackOverflow: http://stackoverflow.com/questions/tagged/google-cloud-platform+node.js 16 | 17 | If you are still having issues, please be sure to include as much information as possible: 18 | 19 | #### Environment details 20 | 21 | - OS: 22 | - Node.js version: 23 | - npm version: 24 | - `grpc-gcp` version: 25 | 26 | #### Steps to reproduce 27 | 28 | 1. ? 29 | 2. ? 30 | 31 | Making sure to follow these steps will guarantee the quickest resolution possible. 32 | 33 | Thanks! 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this library 4 | 5 | --- 6 | 7 | Thanks for stopping by to let us know something could be better! 8 | 9 | **PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response. 10 | 11 | **Is your feature request related to a problem? Please describe.** 12 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | **Additional context** 18 | Add any other context or screenshots about the feature request here. 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support request 3 | about: If you have a support contract with Google, please create an issue in the Google Cloud Support console. 4 | 5 | --- 6 | 7 | **PLEASE READ**: If you have a support contract with Google, please create an issue in the [support console](https://cloud.google.com/support/) instead of filing on GitHub. This will ensure a timely response. 8 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: 2 | - [ ] Make sure to open an issue as a [bug/issue](https://github.com//issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea 3 | - [ ] Ensure the tests and linter pass 4 | - [ ] Code coverage does not decrease (if any source code was changed) 5 | - [ ] Appropriate docs were updated (if necessary) 6 | 7 | Fixes # 🦕 8 | -------------------------------------------------------------------------------- /.github/release-please.yml: -------------------------------------------------------------------------------- 1 | releaseType: node 2 | handleGHRelease: true 3 | -------------------------------------------------------------------------------- /.github/trusted-contribution.yml: -------------------------------------------------------------------------------- 1 | annotations: 2 | - type: comment 3 | text: "/gcbrun" 4 | trustedContributors: 5 | - gcf-merge-on-green[bot] 6 | - gcf-owl-bot[bot] 7 | - owlbot-bootstrapper[bot] 8 | - release-please[bot] 9 | - renovate-bot 10 | - yoshi-code-bot -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [$default-branch] 4 | pull_request: 5 | name: ci 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | node: [10, 12, 14, 16] 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version: ${{ matrix.node }} 17 | - run: node --version 18 | - run: npm install 19 | - run: npm test 20 | windows: 21 | runs-on: windows-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: actions/setup-node@v3 25 | with: 26 | node-version: 14 27 | - run: npm install 28 | - run: npm test 29 | lint: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v3 33 | - uses: actions/setup-node@v3 34 | with: 35 | node-version: 10 36 | - run: npm install 37 | - run: npm run lint 38 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '34 12 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v3 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v2 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v2 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v2 72 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [published] 4 | name: release 5 | jobs: 6 | release-please: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: actions/setup-node@v3 11 | with: 12 | node-version: 14 13 | registry-url: 'https://wombat-dressing-room.appspot.com' 14 | - run: npm install 15 | - run: npm publish --access public 16 | env: 17 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | build/ 4 | node_modules/ 5 | package-lock.json 6 | npm-debug.log 7 | .nyc_output 8 | coverage 9 | 10 | # generated code 11 | test/google 12 | cloudprober/google 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/googleapis"] 2 | path = third_party/googleapis 3 | url = https://github.com/googleapis/googleapis.git 4 | branch = master 5 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "report-dir": "./coverage", 3 | "reporter": ["text", "lcov"], 4 | "exclude": [ 5 | "**/*-test", 6 | "**/.coverage", 7 | "**/apis", 8 | "**/benchmark", 9 | "**/conformance", 10 | "**/docs", 11 | "**/samples", 12 | "**/generated/**/*.js", 13 | "**/scripts", 14 | "**/protos", 15 | "**/test", 16 | "**/*.d.ts", 17 | ".jsdoc.js", 18 | "**/.jsdoc.js", 19 | "karma.conf.js", 20 | "webpack-tests.config.js", 21 | "webpack.config.js" 22 | ], 23 | "exclude-after-remap": false, 24 | "all": true 25 | } 26 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | test/google 3 | test/googleapis 4 | src/generated 5 | build 6 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module.exports = { 16 | ...require('gts/.prettierrc.json') 17 | } 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.0.2](https://github.com/GoogleCloudPlatform/grpc-gcp-node/compare/v1.0.1...v1.0.2) (2025-05-30) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * Fix missing runtime dependency on protobufjs ([8cc212f](https://github.com/GoogleCloudPlatform/grpc-gcp-node/commit/8cc212f908f1fe5b26d4deece3296335e633e3e5)) 9 | 10 | ## [1.0.1](https://github.com/GoogleCloudPlatform/grpc-gcp-node/compare/v1.0.0...v1.0.1) (2023-01-23) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * Define a generic interface to allow flexibility ([7e12a66](https://github.com/GoogleCloudPlatform/grpc-gcp-node/commit/7e12a662a3a02b0ee057a69dae872fd86c4bd1fc)) 16 | 17 | ## [1.0.0](https://github.com/GoogleCloudPlatform/grpc-gcp-node/compare/v0.4.2...v1.0.0) (2022-09-14) 18 | 19 | 20 | ### ⚠ BREAKING CHANGES 21 | 22 | * require Node.js 12+ (#156) 23 | 24 | ### Bug Fixes 25 | 26 | * **deps:** update dependency @google-cloud/error-reporting to v3 ([#150](https://github.com/GoogleCloudPlatform/grpc-gcp-node/issues/150)) ([4fc086f](https://github.com/GoogleCloudPlatform/grpc-gcp-node/commit/4fc086ffa8ce014b4f46e9b4a2810c0daa6c6737)) 27 | * **deps:** update dependency @grpc/grpc-js to ~1.7.0 ([#155](https://github.com/GoogleCloudPlatform/grpc-gcp-node/issues/155)) ([8dc6d31](https://github.com/GoogleCloudPlatform/grpc-gcp-node/commit/8dc6d31d0ad096af3f9a62d8ae74bd864d289bd4)) 28 | * **grpc:** loosen version range on @grpc/grpc-js ([#153](https://github.com/GoogleCloudPlatform/grpc-gcp-node/issues/153)) ([ad35ce7](https://github.com/GoogleCloudPlatform/grpc-gcp-node/commit/ad35ce7f582b206884fc556f533edc02269ee064)) 29 | 30 | 31 | ### Build System 32 | 33 | * require Node.js 12+ ([#156](https://github.com/GoogleCloudPlatform/grpc-gcp-node/issues/156)) ([4a71e84](https://github.com/GoogleCloudPlatform/grpc-gcp-node/commit/4a71e841db041e957b66a20596347c9aca0e0807)) 34 | 35 | ### [0.4.2](https://github.com/GoogleCloudPlatform/grpc-gcp-node/compare/v0.4.1...v0.4.2) (2022-04-18) 36 | 37 | 38 | ### Bug Fixes 39 | 40 | * **deps:** update dependency @grpc/grpc-js to ~1.6.0 ([#143](https://github.com/GoogleCloudPlatform/grpc-gcp-node/issues/143)) ([9e448f0](https://github.com/GoogleCloudPlatform/grpc-gcp-node/commit/9e448f0a99468fa570846b790a26233ef49a003e)) 41 | 42 | ### [0.4.1](https://github.com/GoogleCloudPlatform/grpc-gcp-node/compare/v0.4.0...v0.4.1) (2022-04-06) 43 | 44 | 45 | ### Bug Fixes 46 | 47 | * match grpc version to gax ([#141](https://github.com/GoogleCloudPlatform/grpc-gcp-node/issues/141)) ([5b86ac8](https://github.com/GoogleCloudPlatform/grpc-gcp-node/commit/5b86ac895a69d583f43d02c9ff45683167b36c35)) 48 | 49 | ## [0.4.0](https://github.com/GoogleCloudPlatform/grpc-gcp-node/compare/v0.3.3...v0.4.0) (2022-04-06) 50 | 51 | 52 | ### Features 53 | 54 | * add ability to periodically request debug headers ([#139](https://github.com/GoogleCloudPlatform/grpc-gcp-node/issues/139)) ([e551d92](https://github.com/GoogleCloudPlatform/grpc-gcp-node/commit/e551d92cc73909fadd3df3f541be6870137bd24c)) 55 | * add configuration for minimum number of channels ([#136](https://github.com/GoogleCloudPlatform/grpc-gcp-node/issues/136)) ([2140577](https://github.com/GoogleCloudPlatform/grpc-gcp-node/commit/2140577e897391712215766567dd85226945b56a)) 56 | 57 | 58 | ### Bug Fixes 59 | 60 | * add a stub implementation of channelz in the channel pool to fix compilation ([#137](https://github.com/GoogleCloudPlatform/grpc-gcp-node/issues/137)) ([a05c3dd](https://github.com/GoogleCloudPlatform/grpc-gcp-node/commit/a05c3dddeda6a31981d4124f2279f27f818c5c5a)) 61 | * **deps:** update dependency argparse to v2 ([#93](https://github.com/GoogleCloudPlatform/grpc-gcp-node/issues/93)) ([fd9ed4a](https://github.com/GoogleCloudPlatform/grpc-gcp-node/commit/fd9ed4a9a5f50d87c77da6e98227f580bc70595e)) 62 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, 4 | and in the interest of fostering an open and welcoming community, 5 | we pledge to respect all people who contribute through reporting issues, 6 | posting feature requests, updating documentation, 7 | submitting pull requests or patches, and other activities. 8 | 9 | We are committed to making participation in this project 10 | a harassment-free experience for everyone, 11 | regardless of level of experience, gender, gender identity and expression, 12 | sexual orientation, disability, personal appearance, 13 | body size, race, ethnicity, age, religion, or nationality. 14 | 15 | Examples of unacceptable behavior by participants include: 16 | 17 | * The use of sexualized language or imagery 18 | * Personal attacks 19 | * Trolling or insulting/derogatory comments 20 | * Public or private harassment 21 | * Publishing other's private information, 22 | such as physical or electronic 23 | addresses, without explicit permission 24 | * Other unethical or unprofessional conduct. 25 | 26 | Project maintainers have the right and responsibility to remove, edit, or reject 27 | comments, commits, code, wiki edits, issues, and other contributions 28 | that are not aligned to this Code of Conduct. 29 | By adopting this Code of Conduct, 30 | project maintainers commit themselves to fairly and consistently 31 | applying these principles to every aspect of managing this project. 32 | Project maintainers who do not follow or enforce the Code of Conduct 33 | may be permanently removed from the project team. 34 | 35 | This code of conduct applies both within project spaces and in public spaces 36 | when an individual is representing the project or its community. 37 | 38 | Instances of abusive, harassing, or otherwise unacceptable behavior 39 | may be reported by opening an issue 40 | or contacting one or more of the project maintainers. 41 | 42 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, 43 | available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) 44 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | **Table of contents** 4 | 5 | * [Contributor License Agreements](#contributor-license-agreements) 6 | * [Contributing a patch](#contributing-a-patch) 7 | * [Running the tests](#running-the-tests) 8 | * [Releasing the library](#releasing-the-library) 9 | 10 | ## Contributor License Agreements 11 | 12 | We'd love to accept your sample apps and patches! Before we can take them, we 13 | have to jump a couple of legal hurdles. 14 | 15 | Please fill out either the individual or corporate Contributor License Agreement 16 | (CLA). 17 | 18 | * If you are an individual writing original source code and you're sure you 19 | own the intellectual property, then you'll need to sign an [individual CLA](https://developers.google.com/open-source/cla/individual). 20 | * If you work for a company that wants to allow you to contribute your work, 21 | then you'll need to sign a [corporate CLA](https://developers.google.com/open-source/cla/corporate). 22 | 23 | Follow either of the two links above to access the appropriate CLA and 24 | instructions for how to sign and return it. Once we receive it, we'll be able to 25 | accept your pull requests. 26 | 27 | ## Contributing A Patch 28 | 29 | 1. Submit an issue describing your proposed change to the repo in question. 30 | 1. The repo owner will respond to your issue promptly. 31 | 1. If your proposed change is accepted, and you haven't already done so, sign a 32 | Contributor License Agreement (see details above). 33 | 1. Fork the desired repo, develop and test your code changes. 34 | 1. Ensure that your code adheres to the existing style in the code to which 35 | you are contributing. 36 | 1. Ensure that your code has an appropriate set of tests which all pass. 37 | 1. Title your pull request following [Conventional Commits](https://www.conventionalcommits.org/) styling. 38 | 1. Submit a pull request. 39 | 40 | ## Running the tests 41 | 42 | 1. [Prepare your environment for Node.js setup][setup]. 43 | 44 | 1. Install dependencies: 45 | 46 | npm install 47 | 48 | 1. Run the tests: 49 | 50 | # Run unit tests. 51 | npm test 52 | 53 | # Run sample integration tests. 54 | gcloud auth application-default login 55 | npm run samples-test 56 | 57 | # Run all system tests. 58 | gcloud auth application-default login 59 | npm run system-test 60 | 61 | 1. Lint (and maybe fix) any changes: 62 | 63 | npm run fix 64 | 65 | [setup]: https://cloud.google.com/nodejs/docs/setup 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gRPC-GCP for Node.js 2 | 3 | A Node.js module providing grpc supports for Google Cloud APIs. 4 | 5 | ## Installation 6 | 7 | ```sh 8 | npm install grpc-gcp --save 9 | ``` 10 | 11 | ## Usage 12 | 13 | Let's use Spanner API as an example. 14 | 15 | First, Create a json file defining API configuration, with ChannelPoolConfig and MethodConfig. 16 | 17 | ```json 18 | { 19 | "channelPool": { 20 | "maxSize": 10, 21 | "maxConcurrentStreamsLowWatermark": 1 22 | }, 23 | "method": [ 24 | { 25 | "name": [ "/google.spanner.v1.Spanner/CreateSession" ], 26 | "affinity": { 27 | "command": "BIND", 28 | "affinityKey": "name" 29 | } 30 | }, 31 | { 32 | "name": [ "/google.spanner.v1.Spanner/GetSession" ], 33 | "affinity": { 34 | "command": "BOUND", 35 | "affinityKey": "name" 36 | } 37 | }, 38 | { 39 | "name": [ "/google.spanner.v1.Spanner/DeleteSession" ], 40 | "affinity": { 41 | "command": "UNBIND", 42 | "affinityKey": "name" 43 | } 44 | } 45 | ] 46 | } 47 | ``` 48 | 49 | Load configuration to ApiConfig. 50 | 51 | ```javascript 52 | // @grpc/grpc-js can be used in place of grpc with no changes 53 | var grpc = require('grpc'); 54 | var grpcGcp = require('grpc-gcp')(grpc); 55 | var fs = require('fs'); 56 | 57 | var apiDefinition = JSON.parse(fs.readFileSync('your_api_config_json_file')); 58 | var apiConfig = grpcGcp.createGcpApiConfig(apiDefinition); 59 | ``` 60 | 61 | Pass `gcpChannelFactoryOverride` and `gcpCallInvocationTransformer` to channel options when initializing api client. 62 | 63 | ```javascript 64 | var channelOptions = { 65 | channelFactoryOverride: grpcGcp.gcpChannelFactoryOverride, 66 | callInvocationTransformer: grpcGcp.gcpCallInvocationTransformer, 67 | gcpApiConfig: apiConfig, 68 | }; 69 | 70 | var client = new SpannerClient( 71 | 'spanner.googleapis.com:443', 72 | channelCreds, 73 | channelOptions 74 | ); 75 | ``` 76 | 77 | ## Build from source 78 | 79 | Download source. 80 | 81 | ```sh 82 | git clone https://github.com/GoogleCloudPlatform/grpc-gcp-node.git && cd grpc-gcp-node 83 | ``` 84 | 85 | ```sh 86 | git submodule update --init --recursive 87 | ``` 88 | 89 | Build grpc-gcp. 90 | 91 | ```sh 92 | npm install 93 | ``` 94 | 95 | ## Test 96 | 97 | Setup credentials. See [Getting Started With Authentication](https://cloud.google.com/docs/authentication/getting-started) for more details. 98 | 99 | ```sh 100 | export GOOGLE_APPLICATION_CREDENTIALS=path/to/key.json 101 | ``` 102 | 103 | Run unit tests. 104 | 105 | ```sh 106 | npm test 107 | ``` 108 | 109 | Run system tests. 110 | 111 | ```sh 112 | npm run system-test 113 | ``` 114 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | To report a security issue, please use [g.co/vulnz](https://g.co/vulnz). 4 | 5 | The Google Security Team will respond within 5 working days of your report on g.co/vulnz. 6 | 7 | We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue. 8 | -------------------------------------------------------------------------------- /cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: node:14 3 | entrypoint: git 4 | args: ['submodule', 'update', '--init', '--recursive'] 5 | - name: node:14 6 | entrypoint: npm 7 | args: ['install', '--unsafe-perm'] 8 | - name: node:14 9 | entrypoint: npm 10 | args: ['run', 'system-test'] 11 | -------------------------------------------------------------------------------- /cloudprober/cloudprober.cfg: -------------------------------------------------------------------------------- 1 | probe { 2 | type: EXTERNAL 3 | name: "spanner" 4 | interval_msec: 1800000 5 | timeout_msec: 30000 6 | targets { dummy_targets {} } # No targets for external probe 7 | external_probe { 8 | mode: ONCE 9 | command: "node /grpc-gcp-node/cloudprober/grpc_gcp_prober/prober.js --api=spanner" 10 | } 11 | } 12 | 13 | probe { 14 | type: EXTERNAL 15 | name: "firestore" 16 | interval_msec: 1800000 17 | timeout_msec: 30000 18 | targets { dummy_targets {} } # No targets for external probe 19 | external_probe { 20 | mode: ONCE 21 | command: "node /grpc-gcp-node/cloudprober/grpc_gcp_prober/prober.js --api=firestore" 22 | } 23 | } 24 | 25 | surfacer { 26 | type: STACKDRIVER 27 | name: "stackdriver" 28 | stackdriver_surfacer { 29 | monitoring_url: "custom.googleapis.com/cloudprober/" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cloudprober/codegen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd "$(dirname "$0")" 3 | 4 | rm -rf google 5 | 6 | PROTOC=./node_modules/grpc-tools/bin/protoc.js 7 | 8 | for p in $(find ../third_party/googleapis/google -type f -name *.proto); do 9 | $PROTOC \ 10 | --proto_path=../third_party/googleapis \ 11 | --js_out=import_style=commonjs,binary:./ \ 12 | --grpc_out=./ \ 13 | "$p" 14 | done 15 | -------------------------------------------------------------------------------- /cloudprober/grpc_gcp_prober/firestore_probes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Firestore probes for Node.js that cloudprober will execute. 3 | * 4 | * Each method implements grpc client calls to Firestore API. The latency for 5 | * each client call will be output to stackdriver as metrics. 6 | */ 7 | 8 | const firestore = require('../google/firestore/v1beta1/firestore_pb.js'); 9 | const _PARENT_RESOURCE = 10 | 'projects/grpc-prober-testing/databases/(default)/documents'; 11 | 12 | /** 13 | * Probes to test session related grpc calls from Spanner client. 14 | * @param {spanner_grpc_pb.SpannerClient} client An instance of FirestoreClient. 15 | * @param {Object} metrics A map of metrics. 16 | * @return {Promise} A promise after a sequence of client calls. 17 | */ 18 | function documents(client, metrics) { 19 | return new Promise((resolve, reject) => { 20 | var listDocsRequest = new firestore.ListDocumentsRequest(); 21 | listDocsRequest.setParent(_PARENT_RESOURCE); 22 | var start = new Date(); 23 | client.listDocuments(listDocsRequest, (error, response) => { 24 | if (error) { 25 | reject(error); 26 | } else { 27 | var latency = (new Date() - start); 28 | metrics['list_documents_latency_ms'] = latency; 29 | var docArray = response.getDocumentsList(); 30 | if (!docArray || !docArray.length) { 31 | reject(new Error( 32 | 'ListDocumentsResponse should have more than 1 document')); 33 | } 34 | resolve(); 35 | } 36 | }); 37 | }); 38 | } 39 | 40 | 41 | // exports.documents = documents; 42 | exports.probeFunctions = { 43 | 'documents': documents 44 | }; 45 | -------------------------------------------------------------------------------- /cloudprober/grpc_gcp_prober/prober.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Main entrypoint to execute probes for different cloud APIs. 3 | */ 4 | 5 | const firestore_grpc = 6 | require('../google/firestore/v1beta1/firestore_grpc_pb.js'); 7 | const spanner_grpc = require('../google/spanner/v1/spanner_grpc_pb.js'); 8 | const {GoogleAuth} = require('google-auth-library'); 9 | const grpc = require('grpc'); 10 | const argparse = require('argparse'); 11 | const firestore_probes = require('./firestore_probes'); 12 | const spanner_probes = require('./spanner_probes'); 13 | const stackdriver_util = require('./stackdriver_util'); 14 | 15 | const _OAUTH_SCOPE = 'https://www.googleapis.com/auth/cloud-platform'; 16 | const _FIRESTORE_TARGET = 'firestore.googleapis.com:443'; 17 | const _SPANNER_TARGET = 'spanner.googleapis.com:443'; 18 | 19 | 20 | /** 21 | * Retrieves arguments before executing probes. 22 | * @return {Object} An object containing all the args parsed in. 23 | */ 24 | function getArgs() { 25 | var parser = new argparse.ArgumentParser({ 26 | version: '0.0.1', 27 | addHelp: true, 28 | description: 'Argument parser for grpc gcp prober.' 29 | }); 30 | parser.addArgument('--api', {help: 'foo bar'}); 31 | return parser.parseArgs(); 32 | } 33 | 34 | /** 35 | * Execute a probe function given certain Cloud api. 36 | * @param {string} api The name of the api provider, e.g. "spanner", "firestore". 37 | */ 38 | function executeProbes(api) { 39 | var authFactory = new GoogleAuth(); 40 | var util = new stackdriver_util.StackdriverUtil(api); 41 | 42 | authFactory.getApplicationDefault((err, auth) => { 43 | if (err) { 44 | console.log('Authentication failed because of ', err); 45 | return; 46 | } 47 | if (auth.createScopedRequired && auth.createScopedRequired()) { 48 | var scopes = [_OAUTH_SCOPE]; 49 | auth = auth.createScoped(scopes); 50 | } 51 | var sslCreds = grpc.credentials.createSsl(); 52 | var callCreds = grpc.credentials.createFromGoogleCredential(auth); 53 | var channelCreds = 54 | grpc.credentials.combineChannelCredentials(sslCreds, callCreds); 55 | if (api === 'firestore') { 56 | var client = 57 | new firestore_grpc.FirestoreClient(_FIRESTORE_TARGET, channelCreds); 58 | var probeFunctions = firestore_probes.probeFunctions; 59 | } else if (api === 'spanner') { 60 | var client = 61 | new spanner_grpc.SpannerClient(_SPANNER_TARGET, channelCreds); 62 | var probeFunctions = spanner_probes.probeFunctions; 63 | } else { 64 | throw new Error('gRPC prober is not implemented for ' + api + ' !'); 65 | } 66 | 67 | var metrics = {}; 68 | var probeNames = Object.keys(probeFunctions); 69 | var promises = probeNames.map((probeName) => { 70 | probe_function = probeFunctions[probeName]; 71 | return probe_function(client, metrics); 72 | }); 73 | 74 | Promise.all(promises) 75 | .then(() => { 76 | util.setSuccess(true); 77 | }) 78 | .catch((err) => { 79 | util.setSuccess(false); 80 | util.reportError(err); 81 | }) 82 | .then(() => { 83 | util.addMetrics(metrics); 84 | util.outputMetrics(); 85 | }); 86 | 87 | // TODO: if fail, exit probe. 88 | }); 89 | } 90 | 91 | var args = getArgs(); 92 | executeProbes(args.api); 93 | -------------------------------------------------------------------------------- /cloudprober/grpc_gcp_prober/spanner_probes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Spanner probes for Node.js that cloudprober will execute. 3 | * 4 | * Each method implements grpc client calls to Spanner API. The latency for 5 | * each client call will be output to stackdriver as metrics. 6 | */ 7 | 8 | const spanner = require('../google/spanner/v1/spanner_pb.js'); 9 | const _DATABASE = 10 | 'projects/grpc-prober-testing/instances/weiranf-instance/databases/test-db'; 11 | const _TEST_USERNAME = 'test_username'; 12 | const Promise = require('bluebird'); 13 | 14 | /** 15 | * Probes to test session related grpc calls from Spanner client. 16 | * @param {spanner_grpc_pb.SpannerClient} client An instance of SpannerClient. 17 | * @param {Object} metrics A map of metrics. 18 | * @return {Promise} A promise after a sequence of client calls. 19 | */ 20 | function sessionManagement(client, metrics) { 21 | var sessionName; 22 | 23 | let createSession = () => { 24 | return new Promise((resolve, reject) => { 25 | var createSessionRequest = new spanner.CreateSessionRequest(); 26 | createSessionRequest.setDatabase(_DATABASE); 27 | var start = new Date(); 28 | client.createSession(createSessionRequest, (error, session) => { 29 | if (error) { 30 | reject(error); 31 | } else { 32 | var latency = (new Date() - start); 33 | metrics['create_session_latency_ms'] = latency; 34 | sessionName = session.getName(); 35 | resolve(); 36 | } 37 | }); 38 | }); 39 | }; 40 | 41 | let getSession = () => { 42 | return new Promise((resolve, reject) => { 43 | var getSessionRequest = new spanner.GetSessionRequest(); 44 | getSessionRequest.setName(sessionName); 45 | start = new Date(); 46 | client.getSession(getSessionRequest, (error, sessionResult) => { 47 | if (error) { 48 | reject(error); 49 | } else { 50 | var latency = (new Date() - start); 51 | metrics['get_session_latency_ms'] = latency; 52 | if (sessionResult.getName() != sessionName) { 53 | reject(new Error( 54 | 'client.getSession has incorrect result: ' + 55 | sessionResult.getName())); 56 | } 57 | resolve(); 58 | } 59 | }); 60 | }); 61 | }; 62 | 63 | let deleteSession = () => { 64 | return new Promise((resolve, reject) => { 65 | var deleteSessionRequest = new spanner.DeleteSessionRequest(); 66 | deleteSessionRequest.setName(sessionName); 67 | start = new Date(); 68 | client.deleteSession(deleteSessionRequest, (error) => { 69 | if (error) { 70 | reject(error); 71 | } else { 72 | var latency = (new Date() - start); 73 | metrics['delete_session_latency_ms'] = latency; 74 | resolve(); 75 | } 76 | }); 77 | }); 78 | }; 79 | 80 | return createSession() 81 | .then(() => { 82 | return getSession(); 83 | }) 84 | .finally(() => { 85 | if (sessionName) { 86 | return deleteSession(); 87 | } 88 | }); 89 | } 90 | 91 | exports.probeFunctions = { 92 | 'sessionManagement': sessionManagement 93 | }; 94 | -------------------------------------------------------------------------------- /cloudprober/grpc_gcp_prober/stackdriver_util.js: -------------------------------------------------------------------------------- 1 | const {ErrorReporting} = require('@google-cloud/error-reporting'); 2 | 3 | /** Utility class for collection Stackdriver metrics and errors. */ 4 | class StackdriverUtil { 5 | /** 6 | * Constructor of StackdriverUtil 7 | * @param {string} api The cloud api name of the metrics being generated. 8 | */ 9 | constructor(api) { 10 | this.api_ = api; 11 | this.metircs_ = {}; 12 | this.success_ = false; 13 | this.errClient_ = new ErrorReporting(); 14 | } 15 | 16 | /** 17 | * Add a map of metrics to the existing metrics. 18 | * @param {Object} metrics A map of metrics. 19 | */ 20 | addMetrics(metrics) { 21 | this.metrics_ = Object.assign({}, this.metrics_, metrics); 22 | } 23 | 24 | /** 25 | * Set the result of the probe. 26 | * @param {boolean} result 27 | */ 28 | setSuccess(result) { 29 | this.success_ = result; 30 | } 31 | 32 | /** 33 | * Format output before they can be made to Stackdriver metrics. 34 | * 35 | * Formatted output like 'metricvalue' will be scraped by Stackdriver 36 | * as custom metrics. 37 | */ 38 | outputMetrics() { 39 | if (this.success_) { 40 | console.log(this.api_ + '_success 1'); 41 | } else { 42 | console.log(this.api_ + '_success 0'); 43 | } 44 | 45 | for (var metric_name in this.metrics_) { 46 | console.log(metric_name + ' ' + this.metrics_[metric_name]); 47 | } 48 | } 49 | 50 | /** 51 | * Format error message to output to error reporting. 52 | */ 53 | reportError(err) { 54 | console.error(err); 55 | this.errClient_.report( 56 | 'NodeProberFailure: gRPC(v=x.x.x) fails on ' + this.api_ + 57 | ' API. Details: ' + err.toString()); 58 | } 59 | } 60 | 61 | exports.StackdriverUtil = StackdriverUtil; 62 | -------------------------------------------------------------------------------- /cloudprober/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grpc-gcp-prober", 3 | "version": "0.0.1", 4 | "description": "grpc cloudprober for Node.js", 5 | "main": "index.js", 6 | "dependencies": { 7 | "@google-cloud/error-reporting": "^3.0.0", 8 | "argparse": "^2.0.0", 9 | "bluebird": "^3.5.2", 10 | "google-auth-library": "^8.0.0", 11 | "google-protobuf": "^3.6.1", 12 | "grpc": "^1.15.1", 13 | "grpc-tools": "^1.6.6" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cloudprober/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd "$(dirname "$0")" 3 | 4 | npm install 5 | 6 | ./codegen.sh 7 | -------------------------------------------------------------------------------- /cloudprober/test_prober.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd "$(dirname "$0")" 3 | 4 | ./setup.sh 5 | node grpc_gcp_prober/prober.js --api=spanner 6 | -------------------------------------------------------------------------------- /codegen.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd "$(dirname "$0")" 3 | 4 | PBJS=./node_modules/protobufjs/bin/pbjs 5 | PBTS=./node_modules/protobufjs/bin/pbts 6 | OUTDIR=./src/generated 7 | 8 | rm -r $OUTDIR 9 | mkdir $OUTDIR 10 | 11 | $PBJS -t static-module -w commonjs -o $OUTDIR/grpc_gcp.js protos/grpc_gcp.proto 12 | echo "Generated src/generated/grpc_gcp.js" 13 | 14 | $PBTS -o $OUTDIR/grpc_gcp.d.ts $OUTDIR/grpc_gcp.js 15 | echo "Generated src/generated/grpc_gcp.d.ts" 16 | -------------------------------------------------------------------------------- /doc/cloudprober-guide.md: -------------------------------------------------------------------------------- 1 | # How To Add NodeJs Probers for Cloud APIs 2 | 3 | The gRPC Cloudprober supports NodeJs probers. Following steps 4 | shows how to add probes for a new Cloud API in NodeJs. For this 5 | instruction, we take Firestore API as an example and walk through the process of 6 | adding NodeJs probes for Firestore. 7 | 8 | ## Add Cloud API Probes 9 | 10 | The source code of the probes lives in [grpc_gcp_prober](../cloudprober/grpc_gcp_prober), 11 | you will need to modify this folder to add probes for new APIs. 12 | 13 | ### Implement new probes 14 | 15 | Create a new module named `firestore_probes.js` inside the source folder, and 16 | implement NodeJs probes for the new cloud API. For example, if you want to test the `ListDocuments` call from firestore stub: 17 | 18 | ```javascript 19 | function documents(client, metrics) { 20 | return new Promise((resolve, reject) => { 21 | var listDocsRequest = new firestore.ListDocumentsRequest(); 22 | listDocsRequest.setParent(_PARENT_RESOURCE); 23 | var start = new Date(); 24 | client.listDocuments(listDocsRequest, (error, response) => { 25 | if (error) { 26 | reject(error); 27 | } else { 28 | var latency = (new Date() - start); 29 | metrics['list_documents_latency_ms'] = latency; 30 | var docArray = response.getDocumentsList(); 31 | if (!docArray || !docArray.length) { 32 | reject(new Error( 33 | 'ListDocumentsResponse should have more than 1 document')); 34 | } 35 | resolve(); 36 | } 37 | }); 38 | }); 39 | } 40 | ``` 41 | 42 | Use a dict to map the probe name and the probe method. 43 | 44 | ```javascript 45 | exports.probeFunctions = { 46 | 'documents': documents 47 | }; 48 | ``` 49 | 50 | Notice that `client` and `metrics` objects are initialized in `prober.js`. We will 51 | discuss them in later sections. For complete code, check [firestore_probes.js](../cloudprober/grpc_gcp_prober/firestore_probes.js). 52 | 53 | ### Register new API stub 54 | 55 | Register the new cloud API in [prober.js](../cloudprober/grpc_gcp_prober/prober.js). `prober.js` is an entrypoint for all the probes of different cloud APIs. It creates 56 | the stub for the api and executes the probe functions defined for the specific cloud api. 57 | 58 | ```javascript 59 | const {GoogleAuth} = require('google-auth-library'); 60 | 61 | function executeProbes(api) { 62 | // Some other code 63 | if (api === 'firestore') { 64 | var client = 65 | new firestore_grpc.FirestoreClient(_FIRESTORE_TARGET, channelCreds); 66 | var probeFunctions = firestore_probes.probeFunctions; 67 | } else if (api === 'spanner') { 68 | var client = 69 | new spanner_grpc.SpannerClient(_SPANNER_TARGET, channelCreds); 70 | var probeFunctions = spanner_probes.probeFunctions; 71 | } else { 72 | throw new Error('gRPC prober is not implemented for ' + api + ' !'); 73 | } 74 | // Some other code 75 | } 76 | ``` 77 | 78 | ### Register probe in cloudprober 79 | 80 | Add the new probe you just implemented to [cloudprober.cfg](../cloudprober/cloudprober.cfg), so that when cloudprober is running, it will executes the probe and forward all metrics to 81 | Stackdriver. Use the template just like the other probes. 82 | 83 | ``` 84 | probe { 85 | type: EXTERNAL 86 | name: "firestore" 87 | interval_msec: 1800000 88 | timeout_msec: 30000 89 | targets { dummy_targets {} } # No targets for external probe 90 | external_probe { 91 | mode: ONCE 92 | command: "node /grpc-gcp-node/cloudprober/grpc_gcp_prober/prober.js --api=firestore" 93 | } 94 | } 95 | ``` 96 | 97 | ## Stackdriver Mornitoring 98 | 99 | Use the [StackdriverUtil](../cloudprober/grpc_gcp_prober/stackdriver_util.js) 100 | to add custom metrics. 101 | 102 | ```javascript 103 | var util = new stackdriver_util.StackdriverUtil(api); 104 | util.addMetrics(metrics); 105 | ``` 106 | 107 | The StackdriverUtil will format the output (e.g. "read_latency_ms 100") so they 108 | can be scraped by cloudprober and then metrics will be automatically created and 109 | forwarded to Stackdriver as [Custom Metrics](https://cloud.google.com/monitoring/custom-metrics/). Later on, the metrics can be retrieved via [Metric Explore](https://app.google.stackdriver.com/metrics-explorer). 110 | The full name of the metric will be in the following format: 111 | 112 | ``` 113 | custom.googleapis.com/cloudprober/external// 114 | ``` 115 | 116 | ## Stackdriver Error Reporting 117 | [StackdriverUtil](../cloudprober/grpc_gcp_prober/stackdriver_util.py) also helps setting up 118 | [Error Reporting](https://cloud.google.com/error-reporting/docs/setup/python) 119 | to report any Error occurred during probes. In this way, if anything unusual 120 | occurs, it can be reported immediately. 121 | 122 | By default, all exceptions thrown by any probe will be reported to Error 123 | Reporting by StackdriverUtil. 124 | 125 | ## Alerting Notification 126 | 127 | There are two ways you can be notified for alerts: 128 | 129 | 1. Add [Alerting Policy](https://cloud.google.com/monitoring/alerts/) in 130 | Stackdriver Monitoring. And set up notification when certain metircs are absent 131 | or beyond/below a certain threshold. 132 | 133 | 2. Set up [Email Notification](https://cloud.google.com/error-reporting/docs/notifications) 134 | in Error Reporting. The alert will be triggered whenever an Error/Exception is 135 | reported by google-cloud-error-reporting client. Note that this option does not 136 | support email alias, you need to use the email that is associated with the 137 | Google Account and with necessary IAM roles. 138 | -------------------------------------------------------------------------------- /doc/gRPC-client-user-guide.md: -------------------------------------------------------------------------------- 1 | # Instructions for create a gRPC client for google cloud services 2 | 3 | ## Overview 4 | 5 | This instruction includes a step by step guide for creating a gRPC 6 | client to test the google cloud service from an empty linux 7 | VM, using GCE ubuntu 16.04 TLS instance. 8 | 9 | The main steps are followed as steps below: 10 | 11 | - Environment prerequisite 12 | - Install gRPC-nodejs and plugin 13 | - Generate client API from .proto files 14 | - Create the client and send/receive RPC. 15 | 16 | ## Environment Prerequisite 17 | 18 | **Nodejs** 19 | ```sh 20 | $ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.25.4/install.sh | bash 21 | $ source ~/.bashrc 22 | $ nvm install 8 && npm config set cache /tmp/npm-cache && npm install -g npm 23 | $ nvm alias default 8 24 | ``` 25 | 26 | ## Install gRPC-nodejs and plugin 27 | ```sh 28 | $ npm install -g grpc 29 | $ npm install -g grpc-tools 30 | ``` 31 | 32 | ## Generate client API from .proto files 33 | The plugin is installed with grpc-tools. 34 | The command using plugin looks like 35 | ```sh 36 | $ mkdir $HOME/project-node && cd $HOME/project-node 37 | $ grpc_tools_node_protoc --proto_path=./ \ 38 | --js_out=import_style=commonjs,binary:=./ \ 39 | --grpc_out=./ \ 40 | --plugin=protoc-gen-grpc=which grpc_tools_node_protoc_plugin \ 41 | path/to/your/proto_dependency_directory1/*.proto \ 42 | path/to/your/proto_dependency_directory2/*.proto \ 43 | path/to/your/proto_directory/*.proto 44 | ``` 45 | 46 | Take `Firestore` service under [googleapis github repo](https://github.com/googleapis/googleapis) 47 | for example. 48 | The proto files required are 49 | ``` 50 | google/api/annotations.proto 51 | google/api/http.proto 52 | google/api/httpbody.proto 53 | google/longrunning/operations.proto 54 | google/rpc/code.proto 55 | google/rpc/error_details.proto 56 | google/rpc/status.proto 57 | google/type/latlng.proto 58 | google/firestore/v1beta1/firestore.proto 59 | google/firestore/v1beta1/common.proto 60 | google/firestore/v1beta1/query.proto 61 | google/firestore/v1beta1/write.proto 62 | google/firestore/v1beta1/document.proto 63 | ``` 64 | Thus the command generating client API is 65 | ```sh 66 | $ grpc_tools_node_protoc --proto_path=googleapis --js_out=i 67 | mport_style=commonjs,binary:./ --grpc_out=./ \ 68 | google/api/annotations.proto google/api/http.proto google/api/httpbody.proto \ 69 | google/longrunning/operations.proto google/rpc/code.proto google/rpc/error_details.proto \ 70 | google/rpc/status.proto google/type/latlng.proto google/firestore/v1beta1/firestore.proto \ 71 | google/firestore/v1beta1/common.proto google/firestore/v1beta1/query.proto \ 72 | google/firestore/v1beta1/write.proto google/firestore/v1beta1/document.proto 73 | ``` 74 | 75 | Since most of cloud services already publish proto files under 76 | [googleapis github repo](https://github.com/googleapis/googleapis), 77 | you can generate the client API by using it's Makefile. 78 | The `Makefile` will help you generate the client API as 79 | well as all the dependencies. The command will simply be: 80 | ```sh 81 | $ cd $HOME 82 | $ mkdir project-node 83 | $ git clone https://github.com/googleapis/googleapis.git 84 | $ cd googleapis 85 | $ make LANGUAGE=node OUTPUT=$HOME/project-node PROTOC="grpc_tools_node_protoc --proto_path=./ --js_out=import_style=commonjs,binary:$HOME/project-node --grpc_out=$HOME/project-node --plugin=protoc-gen-grpc=`which grpc_tools_node_protoc_plugin`" \ 86 | FLAGS="" GRPCPLUGIN="" 87 | ``` 88 | 89 | The client API library is generated under `$HOME/project-node`. 90 | Take [`Firestore`](https://github.com/googleapis/googleapis/blob/master/google/firestore/v1beta1/firestore.proto) 91 | as example, the Client API is under 92 | `$HOME/project-node/google/firestore/v1beta1` depends on your 93 | package namespace inside .proto file. An easy way to find your client is 94 | ```sh 95 | $ cd $HOME/project-node 96 | $ find ./ -name [service_name: eg, firestore, cluster_service]* 97 | ``` 98 | 99 | ## Create the client and send/receive RPC. 100 | Now it's time to use the client API to send and receive RPCs. 101 | 102 | **Install related packages** 103 | ``` sh 104 | $ cd $HOME/project-node 105 | $ vim package.json 106 | ##### paste 107 | { 108 | "name": "firestore-grpc-test-client", 109 | "version": "0.1.0", 110 | "dependencies": { 111 | "@google-cloud/firestore": "^0.10.0", 112 | "async": "^1.5.2", 113 | "ca-store": "^1.1.1", 114 | "colors": "1.1.2", 115 | "eslint": "4.12.1", 116 | "firebase-admin": "^5.5.1", 117 | "fs": "0.0.1-security", 118 | "google-auth-library": "^0.12.0", 119 | "google-protobuf": "^3.0.0", 120 | "googleapis": "^23.0.0", 121 | "grpc": "1.7.3", 122 | "lodash": "^4.6.1", 123 | "minimist": "^1.2.0", 124 | "prompt": "1.0.0" 125 | } 126 | } 127 | ##### 128 | $ npm install 129 | ``` 130 | 131 | 132 | **Set credentials file** 133 | 134 | This is important otherwise your RPC response will be a permission error. 135 | ``` sh 136 | $ vim $HOME/key.json 137 | ## Paste you credential file downloaded from your cloud project 138 | ## which you can find in APIs&Services => credentials => create credentials 139 | ## => Service account key => your credentials 140 | $ export GOOGLE_APPLICATION_CREDENTIALS=$HOME/key.json 141 | ``` 142 | 143 | **Implement Service Client** 144 | 145 | Take a unary-unary RPC `listDocument` from `FirestoreClient` as example. 146 | Create a file name `$HONE/project-node/list_document_client.js`. 147 | - Import library 148 | ``` 149 | var firestore = require('./google/firestore/v1beta1/firestore_pb.js'); 150 | var services = require('./google/firestore/v1beta1/firestore_grpc_pb.js'); 151 | var google = require('googleapis'); 152 | var googleAuth = require('google-auth-library'); 153 | var grpc = require('grpc'); 154 | var colors = require('colors'); 155 | ``` 156 | - Set Google Auth. Please see the referece for 157 | [authenticate with Google using an Oauth2 token](https://grpc.io/docs/guides/auth.html#authenticate-with-google-using-oauth2-token-legacy-approach) 158 | for the use of 'googleauth' library. 159 | ``` 160 | var authFactory = new googleAuth(); 161 | var dns = google.dns("v1"); 162 | authFactory.getApplicationDefault(function (err, authClient) { 163 | if (err) { 164 | console.log('Authentication failed because of ', err); 165 | return; 166 | } 167 | if (authClient.createScopedRequired && authClient.createScopedRequired()) { 168 | var scopes = ['https://www.googleapis.com/auth/datastore']; 169 | authClient = authClient.createScoped(scopes); 170 | } 171 | var ssl_creds = grpc.credentials.createSsl(); 172 | call_creds = grpc.credentials.createFromGoogleCredential(authClient); 173 | var channel_creds = grpc.credentials.combineCallCredentials(ssl_creds, call_creds); 174 | ``` 175 | 176 | - Create Client 177 | ``` 178 | client = new services.FirestoreClient("firestore.googleapis.com:443", channel_creds); 179 | ``` 180 | - Make and receive RPC call 181 | ``` 182 | var listDocsRequest = new firestore.ListDocumentsRequest(); 183 | listDocsRequest.setParent('projects/{project_name}/databases/(default)/documents'); 184 | client.listDocuments(listDocsRequest, listDocsCallback); 185 | ``` 186 | - Print RPC response 187 | ``` 188 | function listDocsCallback(error, response) { 189 | if (error) { 190 | console.log(colors.red.bold(error.toString())); 191 | menu.drawMenu(); 192 | return; 193 | } 194 | 195 | var docArray = response.getDocumentsList(); 196 | var i = 0; 197 | docArray.forEach(function (doc) { 198 | i++; 199 | var docName = doc.array[0]; 200 | console.log(colors.white.bold("Document " + i + ": ") + colors.yellow(docName)); 201 | }); 202 | menu.drawMenu(); 203 | return; 204 | } 205 | }); 206 | ``` 207 | - Run the script 208 | ```sh 209 | $ node list_document_client.js 210 | ``` 211 | 212 | For different kinds of RPC(unary-unary, unary-stream, stream-unary, stream-stream), 213 | please check [grpc.io Node part](https://grpc.io/docs/tutorials/basic/node.html#simple-rpc) 214 | for reference. 215 | 216 | 217 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grpc-gcp", 3 | "version": "1.0.2", 4 | "description": "Extension for supporting Google Cloud Platform specific features for gRPC.", 5 | "main": "build/src/index.js", 6 | "scripts": { 7 | "build": "tsc && cp -r src/generated build/src/", 8 | "system-test": "c8 mocha test/integration/*.js --reporter spec --timeout 10000 --grpclib grpc && c8 mocha test/integration/*.js --reporter spec --timeout 10000 --grpclib @grpc/grpc-js", 9 | "test": "c8 mocha test/unit/*.js --reporter spec", 10 | "lint": "gts check src/**/*.ts", 11 | "fix": "gts fix", 12 | "prepare": "npm run build" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/GoogleCloudPlatform/grpc-gcp-node.git" 17 | }, 18 | "keywords": [ 19 | "google", 20 | "grpc", 21 | "cloud", 22 | "gcp" 23 | ], 24 | "author": "Google Inc.", 25 | "license": "Apache-2.0", 26 | "types": "build/src/index.d.ts", 27 | "bugs": { 28 | "url": "https://github.com/GoogleCloudPlatform/grpc-gcp-node/issues" 29 | }, 30 | "homepage": "https://github.com/GoogleCloudPlatform/grpc-gcp-node#readme", 31 | "engines": { 32 | "node": ">=12" 33 | }, 34 | "dependencies": { 35 | "@grpc/grpc-js": "^1.7.0", 36 | "protobufjs": "7.4.0" 37 | }, 38 | "devDependencies": { 39 | "@grpc/proto-loader": "^0.7.0", 40 | "@google-cloud/spanner": "^6.0.0", 41 | "c8": "^7.7.2", 42 | "google-auth-library": "^8.0.0", 43 | "google-gax": "^3.0.0", 44 | "google-protobuf": "^3.17.1", 45 | "grpc": "^1.24.10", 46 | "grpc-tools": "^1.11.1", 47 | "gts": "^3.1.0", 48 | "mocha": "^9.2.2", 49 | "typescript": "^4.3.2" 50 | }, 51 | "files": [ 52 | "build/src" 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /protos/grpc_gcp.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 gRPC authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package grpc.gcp; 18 | 19 | message ApiConfig { 20 | // The channel pool configurations. 21 | ChannelPoolConfig channel_pool = 2; 22 | 23 | // The method configurations. 24 | repeated MethodConfig method = 1001; 25 | } 26 | 27 | message ChannelPoolConfig { 28 | // The max number of channels in the pool. 29 | uint32 max_size = 1; 30 | // The min number of channels in the pool. 31 | uint32 min_size = 4; 32 | // The idle timeout (seconds) of channels without bound affinity sessions. 33 | uint64 idle_timeout = 2; 34 | // The low watermark of max number of concurrent streams in a channel. 35 | // New channel will be created once it get hit, until we reach the max size 36 | // of the channel pool. 37 | uint32 max_concurrent_streams_low_watermark = 3; 38 | 39 | // This will request debug response headers from the load balancer. 40 | // The headers are meant to help diagnose issues when connecting to GCP 41 | // services. The headers are primarily useful to support engineers that will 42 | // be able to decrypt them. The headers have a fairly large payload (~1kib), 43 | // so will be requested at most once per this period. A negative number will 44 | // request the headers for every request, 0 will never request headers. 45 | uint32 debug_header_interval_secs = 5; 46 | } 47 | 48 | message MethodConfig { 49 | // A fully qualified name of a gRPC method, or a wildcard pattern ending 50 | // with .*, such as foo.bar.A, foo.bar.*. Method configs are evaluated 51 | // sequentially, and the first one takes precedence. 52 | repeated string name = 1; 53 | 54 | // The channel affinity configurations. 55 | AffinityConfig affinity = 1001; 56 | } 57 | 58 | message AffinityConfig { 59 | enum Command { 60 | // The annotated method will be required to be bound to an existing session 61 | // to execute the RPC. The corresponding will be 62 | // used to find the affinity key from the request message. 63 | BOUND = 0; 64 | // The annotated method will establish the channel affinity with the channel 65 | // which is used to execute the RPC. The corresponding 66 | // will be used to find the affinity key from the 67 | // response message. 68 | BIND = 1; 69 | // The annotated method will remove the channel affinity with the channel 70 | // which is used to execute the RPC. The corresponding 71 | // will be used to find the affinity key from the 72 | // request message. 73 | UNBIND = 2; 74 | } 75 | // The affinity command applies on the selected gRPC methods. 76 | Command command = 2; 77 | // The field path of the affinity key in the request/response message. 78 | // For example: "f.a", "f.b.d", etc. 79 | string affinity_key = 3; 80 | } 81 | -------------------------------------------------------------------------------- /protos/test_service.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 gRPC authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | message Request { 18 | bool error = 1; 19 | string message = 2; 20 | } 21 | 22 | message Response { 23 | int32 count = 1; 24 | } 25 | 26 | service TestService { 27 | rpc Unary (Request) returns (Response) {} 28 | 29 | rpc ClientStream (stream Request) returns (Response) {} 30 | 31 | rpc ServerStream (Request) returns (stream Response) {} 32 | 33 | rpc BidiStream (stream Request) returns (stream Response) {} 34 | } 35 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "pinVersions": false, 6 | "rebaseStalePrs": true 7 | } 8 | -------------------------------------------------------------------------------- /src/channel_ref.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2018 gRPC authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | import * as grpc from '@grpc/grpc-js'; 20 | 21 | /** 22 | * A wrapper of real grpc channel. Also provides helper functions to 23 | * calculate affinity counts and active streams count. 24 | */ 25 | export class ChannelRef { 26 | private readonly channel: grpc.ChannelInterface; 27 | private readonly channelId: number; 28 | private affinityCount: number; 29 | private activeStreamsCount: number; 30 | private debugHeadersRequestedAt: Date | null; 31 | private shouldForceDebugHeadersOnNextRequest: boolean; 32 | private closed: boolean; 33 | 34 | /** 35 | * @param channel The underlying grpc channel. 36 | * @param channelId Id for creating unique channel. 37 | * @param affinityCount Initial affinity count. 38 | * @param activeStreamsCount Initial streams count. 39 | */ 40 | constructor( 41 | channel: grpc.ChannelInterface, 42 | channelId: number, 43 | affinityCount?: number, 44 | activeStreamsCount?: number 45 | ) { 46 | this.channel = channel; 47 | this.channelId = channelId; 48 | this.affinityCount = affinityCount ? affinityCount : 0; 49 | this.activeStreamsCount = activeStreamsCount ? activeStreamsCount : 0; 50 | this.debugHeadersRequestedAt = null; 51 | this.shouldForceDebugHeadersOnNextRequest = false; 52 | this.closed = false; 53 | } 54 | 55 | close() { 56 | this.closed = true; 57 | this.channel.close(); 58 | } 59 | 60 | isClosed() { 61 | return this.closed; 62 | } 63 | 64 | affinityCountIncr() { 65 | this.affinityCount++; 66 | } 67 | 68 | activeStreamsCountIncr() { 69 | this.activeStreamsCount++; 70 | } 71 | 72 | affinityCountDecr() { 73 | this.affinityCount--; 74 | } 75 | 76 | activeStreamsCountDecr() { 77 | this.activeStreamsCount--; 78 | } 79 | 80 | getAffinityCount() { 81 | return this.affinityCount; 82 | } 83 | 84 | getActiveStreamsCount() { 85 | return this.activeStreamsCount; 86 | } 87 | 88 | forceDebugHeadersOnNextRequest() { 89 | this.shouldForceDebugHeadersOnNextRequest = true; 90 | } 91 | notifyDebugHeadersRequested() { 92 | this.debugHeadersRequestedAt = new Date(); 93 | this.shouldForceDebugHeadersOnNextRequest = false; 94 | } 95 | 96 | getDebugHeadersRequestedAt(): Date | null { 97 | return this.debugHeadersRequestedAt; 98 | } 99 | 100 | getChannel() { 101 | return this.channel; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/gcp_channel_factory.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2018 gRPC authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | import * as grpcType from '@grpc/grpc-js'; 20 | import {promisify} from 'util'; 21 | 22 | import {ChannelRef} from './channel_ref'; 23 | import * as protoRoot from './generated/grpc_gcp'; 24 | import {connectivityState} from '@grpc/grpc-js'; 25 | import ApiConfig = protoRoot.grpc.gcp.ApiConfig; 26 | import IAffinityConfig = protoRoot.grpc.gcp.IAffinityConfig; 27 | 28 | const CLIENT_CHANNEL_ID = 'grpc_gcp.client_channel.id'; 29 | 30 | export type GrpcModule = typeof grpcType; 31 | 32 | export interface GcpChannelFactoryInterface extends grpcType.ChannelInterface { 33 | getChannelRef(affinityKey?: string): ChannelRef; 34 | getAffinityConfig(methodName: string): IAffinityConfig; 35 | bind(channelRef: ChannelRef, affinityKey: string): void; 36 | unbind(boundKey?: string): void; 37 | shouldRequestDebugHeaders(lastRequested: Date | null) : boolean; 38 | 39 | } 40 | 41 | export interface GcpChannelFactoryConstructor { 42 | new ( 43 | address: string, 44 | credentials: grpcType.ChannelCredentials, 45 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 46 | options: any 47 | ): GcpChannelFactoryInterface; 48 | } 49 | 50 | export function getGcpChannelFactoryClass( 51 | grpc: GrpcModule 52 | ): GcpChannelFactoryConstructor { 53 | /** 54 | * A channel management factory that implements grpc.Channel APIs. 55 | */ 56 | return class GcpChannelFactory implements GcpChannelFactoryInterface { 57 | private minSize: number; 58 | private maxSize: number; 59 | private maxConcurrentStreamsLowWatermark: number; 60 | private options: {}; 61 | private methodToAffinity: {[key: string]: IAffinityConfig} = {}; 62 | private affinityKeyToChannelRef: {[affinityKey: string]: ChannelRef} = {}; 63 | private channelRefs: ChannelRef[] = []; 64 | private target: string; 65 | private credentials: grpcType.ChannelCredentials; 66 | private debugHeaderIntervalSecs: number; 67 | 68 | /** 69 | * @param address The address of the server to connect to. 70 | * @param credentials Channel credentials to use when connecting 71 | * @param options A map of channel options. 72 | */ 73 | constructor( 74 | address: string, 75 | credentials: grpcType.ChannelCredentials, 76 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 77 | options: any 78 | ) { 79 | if (!options) { 80 | options = {}; 81 | } 82 | if (typeof options !== 'object') { 83 | throw new TypeError( 84 | 'Channel options must be an object with string keys and integer or string values' 85 | ); 86 | } 87 | this.minSize = 1; 88 | this.maxSize = 10; 89 | this.maxConcurrentStreamsLowWatermark = 100; 90 | this.debugHeaderIntervalSecs = 0; 91 | const gcpApiConfig = options.gcpApiConfig; 92 | if (gcpApiConfig) { 93 | if (gcpApiConfig.channelPool) { 94 | const channelPool = gcpApiConfig.channelPool; 95 | if (channelPool.minSize) this.minSize = channelPool.minSize; 96 | if (channelPool.maxSize) this.maxSize = channelPool.maxSize; 97 | if (channelPool.maxConcurrentStreamsLowWatermark) { 98 | this.maxConcurrentStreamsLowWatermark = 99 | channelPool.maxConcurrentStreamsLowWatermark; 100 | } 101 | 102 | if (this.maxSize < this.minSize) { 103 | throw new Error('Invalid channelPool config: minSize must <= maxSize') 104 | } 105 | this.debugHeaderIntervalSecs = channelPool.debugHeaderIntervalSecs || 0; 106 | } 107 | this.initMethodToAffinityMap(gcpApiConfig); 108 | } 109 | 110 | delete options.gcpApiConfig; 111 | this.options = options; 112 | this.target = address; 113 | this.credentials = credentials; 114 | 115 | // Create initial channels 116 | for (let i = 0; i < this.minSize; i++) { 117 | this.addChannel(); 118 | } 119 | } 120 | 121 | getChannelzRef() { 122 | return this.channelRefs[0].getChannel().getChannelzRef(); 123 | } 124 | 125 | private initMethodToAffinityMap(gcpApiConfig: ApiConfig): void { 126 | const methodList = gcpApiConfig.method; 127 | if (methodList) { 128 | for (let i = 0; i < methodList.length; i++) { 129 | const method = methodList[i]; 130 | const nameList = method.name; 131 | if (nameList) { 132 | for (let j = 0; j < nameList.length; j++) { 133 | const methodName = nameList[j]; 134 | if (method.affinity) { 135 | this.methodToAffinity[methodName] = method.affinity; 136 | } 137 | } 138 | } 139 | } 140 | } 141 | } 142 | 143 | /** 144 | * Picks a grpc channel from the pool and wraps it with ChannelRef. 145 | * @param affinityKey Affinity key to get the bound channel. 146 | * @return Wrapper containing the grpc channel. 147 | */ 148 | getChannelRef(affinityKey?: string): ChannelRef { 149 | if (affinityKey && this.affinityKeyToChannelRef[affinityKey]) { 150 | // Chose an bound channel if affinityKey is specified. 151 | return this.affinityKeyToChannelRef[affinityKey]; 152 | } 153 | 154 | // Sort channel refs by active streams count. 155 | this.channelRefs.sort((ref1, ref2) => { 156 | return ref1.getActiveStreamsCount() - ref2.getActiveStreamsCount(); 157 | }); 158 | 159 | const size = this.channelRefs.length; 160 | // Chose the channelRef that has the least busy channel. 161 | if ( 162 | size > 0 && 163 | this.channelRefs[0].getActiveStreamsCount() < 164 | this.maxConcurrentStreamsLowWatermark 165 | ) { 166 | return this.channelRefs[0]; 167 | } 168 | 169 | // If all existing channels are busy, and channel pool still has capacity, 170 | // create a new channel in the pool. 171 | if (size < this.maxSize) { 172 | return this.addChannel(); 173 | } else { 174 | return this.channelRefs[0]; 175 | } 176 | } 177 | 178 | /** 179 | * Create a new channel and add it to the pool. 180 | * @private 181 | */ 182 | private addChannel() : ChannelRef { 183 | const size = this.channelRefs.length; 184 | const channelOptions = Object.assign( 185 | {[CLIENT_CHANNEL_ID]: size}, 186 | this.options 187 | ); 188 | const grpcChannel = new grpc.Channel( 189 | this.target, 190 | this.credentials, 191 | channelOptions 192 | ); 193 | const channelRef = new ChannelRef(grpcChannel, size); 194 | this.channelRefs.push(channelRef); 195 | 196 | if (this.debugHeaderIntervalSecs) { 197 | this.setupDebugHeadersOnChannelTransition(channelRef); 198 | } 199 | 200 | return channelRef; 201 | } 202 | 203 | private setupDebugHeadersOnChannelTransition(channel: ChannelRef) { 204 | const self = this; 205 | 206 | if (channel.isClosed()) { 207 | return; 208 | } 209 | 210 | let currentState = channel.getChannel().getConnectivityState(false); 211 | if (currentState == connectivityState.SHUTDOWN) { 212 | return; 213 | } 214 | 215 | channel.getChannel().watchConnectivityState(currentState, Infinity, (e) => { 216 | channel.forceDebugHeadersOnNextRequest(); 217 | self.setupDebugHeadersOnChannelTransition(channel); 218 | }); 219 | } 220 | 221 | 222 | /** 223 | * Get AffinityConfig associated with a certain method. 224 | * @param methodName Method name of the request. 225 | */ 226 | getAffinityConfig(methodName: string): IAffinityConfig { 227 | return this.methodToAffinity[methodName]; 228 | } 229 | 230 | shouldRequestDebugHeaders(lastRequested: Date | null) : boolean { 231 | if (this.debugHeaderIntervalSecs < 0) return true; 232 | else if (this.debugHeaderIntervalSecs == 0) return false; 233 | else if (!lastRequested) return true; 234 | 235 | return new Date().getTime() - lastRequested.getTime() > this.debugHeaderIntervalSecs * 1000; 236 | } 237 | 238 | /** 239 | * Bind channel with affinity key. 240 | * @param channelRef ChannelRef instance that contains the grpc channel. 241 | * @param affinityKey The affinity key used for binding the channel. 242 | */ 243 | bind(channelRef: ChannelRef, affinityKey: string): void { 244 | if (!affinityKey || !channelRef) return; 245 | const existingChannelRef = this.affinityKeyToChannelRef[affinityKey]; 246 | if (!existingChannelRef) { 247 | this.affinityKeyToChannelRef[affinityKey] = channelRef; 248 | } 249 | this.affinityKeyToChannelRef[affinityKey].affinityCountIncr(); 250 | } 251 | 252 | /** 253 | * Unbind channel with affinity key. 254 | * @param boundKey Affinity key bound to a channel. 255 | */ 256 | unbind(boundKey?: string): void { 257 | if (!boundKey) return; 258 | const boundChannelRef = this.affinityKeyToChannelRef[boundKey]; 259 | if (boundChannelRef) { 260 | boundChannelRef.affinityCountDecr(); 261 | if (boundChannelRef.getAffinityCount() <= 0) { 262 | delete this.affinityKeyToChannelRef[boundKey]; 263 | } 264 | } 265 | } 266 | 267 | /** 268 | * Close all channels in the channel pool. 269 | */ 270 | close(): void { 271 | this.channelRefs.forEach(ref => { 272 | ref.close(); 273 | }); 274 | } 275 | 276 | getTarget(): string { 277 | return this.target; 278 | } 279 | 280 | /** 281 | * Get the current connectivity state of the channel pool. 282 | * @param tryToConnect If true, the channel will start connecting if it is 283 | * idle. Otherwise, idle channels will only start connecting when a 284 | * call starts. 285 | * @return connectivity state of channel pool. 286 | */ 287 | getConnectivityState(tryToConnect: boolean): grpcType.connectivityState { 288 | let ready = 0; 289 | let idle = 0; 290 | let connecting = 0; 291 | let transientFailure = 0; 292 | let shutdown = 0; 293 | 294 | for (let i = 0; i < this.channelRefs.length; i++) { 295 | const grpcChannel = this.channelRefs[i].getChannel(); 296 | const state = grpcChannel.getConnectivityState(tryToConnect); 297 | switch (state) { 298 | case grpc.connectivityState.READY: 299 | ready++; 300 | break; 301 | case grpc.connectivityState.SHUTDOWN: 302 | shutdown++; 303 | break; 304 | case grpc.connectivityState.TRANSIENT_FAILURE: 305 | transientFailure++; 306 | break; 307 | case grpc.connectivityState.CONNECTING: 308 | connecting++; 309 | break; 310 | case grpc.connectivityState.IDLE: 311 | idle++; 312 | break; 313 | default: 314 | break; 315 | } 316 | } 317 | 318 | if (ready > 0) { 319 | return grpc.connectivityState.READY; 320 | } else if (connecting > 0) { 321 | return grpc.connectivityState.CONNECTING; 322 | } else if (transientFailure > 0) { 323 | return grpc.connectivityState.TRANSIENT_FAILURE; 324 | } else if (idle > 0) { 325 | return grpc.connectivityState.IDLE; 326 | } else if (shutdown > 0) { 327 | return grpc.connectivityState.SHUTDOWN; 328 | } 329 | 330 | throw new Error( 331 | 'Cannot get connectivity state because no channel provides valid state.' 332 | ); 333 | } 334 | 335 | /** 336 | * Watch for connectivity state changes. 337 | * @param currentState The state to watch for transitions from. This should 338 | * always be populated by calling getConnectivityState immediately 339 | * before. 340 | * @param deadline A deadline for waiting for a state change 341 | * @param callback Called with no error when the state changes, or with an 342 | * error if the deadline passes without a state change 343 | */ 344 | watchConnectivityState( 345 | currentState: grpcType.connectivityState, 346 | deadline: grpcType.Deadline, 347 | callback: (error?: Error) => void 348 | ): void { 349 | if (!this.channelRefs.length) { 350 | callback( 351 | new Error( 352 | 'Cannot watch connectivity state because there are no channels.' 353 | ) 354 | ); 355 | return; 356 | } 357 | 358 | const connectivityState = this.getConnectivityState(false); 359 | 360 | if (connectivityState !== currentState) { 361 | setImmediate(() => callback()); 362 | return; 363 | } 364 | 365 | const watchState = async (channelRef: ChannelRef): Promise => { 366 | const channel = channelRef.getChannel(); 367 | const startingState = channel.getConnectivityState(false); 368 | 369 | await promisify(channel.watchConnectivityState).call( 370 | channel, 371 | startingState, 372 | deadline 373 | ); 374 | 375 | const state = this.getConnectivityState(false); 376 | 377 | if (state === currentState) { 378 | return watchState(channelRef); 379 | } 380 | }; 381 | 382 | const watchers = this.channelRefs.map(watchState); 383 | Promise.race(watchers).then(() => callback(), callback); 384 | } 385 | 386 | /** 387 | * Create a call object. This function will not be called when using 388 | * grpc.Client class. But since it's a public function of grpc.Channel, 389 | * It needs to be implemented for potential use cases. 390 | * @param method The full method string to request. 391 | * @param deadline The call deadline. 392 | * @param host A host string override for making the request. 393 | * @param parentCall A server call to propagate some information from. 394 | * @param propagateFlags A bitwise combination of elements of 395 | * {@link grpc.propagate} that indicates what information to propagate 396 | * from parentCall. 397 | * @return a grpc call object. 398 | */ 399 | createCall( 400 | method: string, 401 | deadline: grpcType.Deadline, 402 | host: string | null, 403 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 404 | parentCall: any, 405 | propagateFlags: number | null 406 | ) { 407 | const grpcChannel = this.getChannelRef().getChannel(); 408 | return grpcChannel.createCall( 409 | method, 410 | deadline, 411 | host, 412 | parentCall, 413 | propagateFlags 414 | ); 415 | } 416 | }; 417 | } 418 | -------------------------------------------------------------------------------- /src/generated/grpc_gcp.d.ts: -------------------------------------------------------------------------------- 1 | import * as $protobuf from "protobufjs"; 2 | /** Namespace grpc. */ 3 | export namespace grpc { 4 | 5 | /** Namespace gcp. */ 6 | namespace gcp { 7 | 8 | /** Properties of an ApiConfig. */ 9 | interface IApiConfig { 10 | 11 | /** ApiConfig channelPool */ 12 | channelPool?: (grpc.gcp.IChannelPoolConfig|null); 13 | 14 | /** ApiConfig method */ 15 | method?: (grpc.gcp.IMethodConfig[]|null); 16 | } 17 | 18 | /** Represents an ApiConfig. */ 19 | class ApiConfig implements IApiConfig { 20 | 21 | /** 22 | * Constructs a new ApiConfig. 23 | * @param [properties] Properties to set 24 | */ 25 | constructor(properties?: grpc.gcp.IApiConfig); 26 | 27 | /** ApiConfig channelPool. */ 28 | public channelPool?: (grpc.gcp.IChannelPoolConfig|null); 29 | 30 | /** ApiConfig method. */ 31 | public method: grpc.gcp.IMethodConfig[]; 32 | 33 | /** 34 | * Creates a new ApiConfig instance using the specified properties. 35 | * @param [properties] Properties to set 36 | * @returns ApiConfig instance 37 | */ 38 | public static create(properties?: grpc.gcp.IApiConfig): grpc.gcp.ApiConfig; 39 | 40 | /** 41 | * Encodes the specified ApiConfig message. Does not implicitly {@link grpc.gcp.ApiConfig.verify|verify} messages. 42 | * @param message ApiConfig message or plain object to encode 43 | * @param [writer] Writer to encode to 44 | * @returns Writer 45 | */ 46 | public static encode(message: grpc.gcp.IApiConfig, writer?: $protobuf.Writer): $protobuf.Writer; 47 | 48 | /** 49 | * Encodes the specified ApiConfig message, length delimited. Does not implicitly {@link grpc.gcp.ApiConfig.verify|verify} messages. 50 | * @param message ApiConfig message or plain object to encode 51 | * @param [writer] Writer to encode to 52 | * @returns Writer 53 | */ 54 | public static encodeDelimited(message: grpc.gcp.IApiConfig, writer?: $protobuf.Writer): $protobuf.Writer; 55 | 56 | /** 57 | * Decodes an ApiConfig message from the specified reader or buffer. 58 | * @param reader Reader or buffer to decode from 59 | * @param [length] Message length if known beforehand 60 | * @returns ApiConfig 61 | * @throws {Error} If the payload is not a reader or valid buffer 62 | * @throws {$protobuf.util.ProtocolError} If required fields are missing 63 | */ 64 | public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): grpc.gcp.ApiConfig; 65 | 66 | /** 67 | * Decodes an ApiConfig message from the specified reader or buffer, length delimited. 68 | * @param reader Reader or buffer to decode from 69 | * @returns ApiConfig 70 | * @throws {Error} If the payload is not a reader or valid buffer 71 | * @throws {$protobuf.util.ProtocolError} If required fields are missing 72 | */ 73 | public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): grpc.gcp.ApiConfig; 74 | 75 | /** 76 | * Verifies an ApiConfig message. 77 | * @param message Plain object to verify 78 | * @returns `null` if valid, otherwise the reason why it is not 79 | */ 80 | public static verify(message: { [k: string]: any }): (string|null); 81 | 82 | /** 83 | * Creates an ApiConfig message from a plain object. Also converts values to their respective internal types. 84 | * @param object Plain object 85 | * @returns ApiConfig 86 | */ 87 | public static fromObject(object: { [k: string]: any }): grpc.gcp.ApiConfig; 88 | 89 | /** 90 | * Creates a plain object from an ApiConfig message. Also converts values to other types if specified. 91 | * @param message ApiConfig 92 | * @param [options] Conversion options 93 | * @returns Plain object 94 | */ 95 | public static toObject(message: grpc.gcp.ApiConfig, options?: $protobuf.IConversionOptions): { [k: string]: any }; 96 | 97 | /** 98 | * Converts this ApiConfig to JSON. 99 | * @returns JSON object 100 | */ 101 | public toJSON(): { [k: string]: any }; 102 | } 103 | 104 | /** Properties of a ChannelPoolConfig. */ 105 | interface IChannelPoolConfig { 106 | 107 | /** ChannelPoolConfig maxSize */ 108 | maxSize?: (number|null); 109 | 110 | /** ChannelPoolConfig minSize */ 111 | minSize?: (number|null); 112 | 113 | /** ChannelPoolConfig idleTimeout */ 114 | idleTimeout?: (number|Long|null); 115 | 116 | /** ChannelPoolConfig maxConcurrentStreamsLowWatermark */ 117 | maxConcurrentStreamsLowWatermark?: (number|null); 118 | 119 | /** ChannelPoolConfig debugHeaderIntervalSecs */ 120 | debugHeaderIntervalSecs?: (number|null); 121 | } 122 | 123 | /** Represents a ChannelPoolConfig. */ 124 | class ChannelPoolConfig implements IChannelPoolConfig { 125 | 126 | /** 127 | * Constructs a new ChannelPoolConfig. 128 | * @param [properties] Properties to set 129 | */ 130 | constructor(properties?: grpc.gcp.IChannelPoolConfig); 131 | 132 | /** ChannelPoolConfig maxSize. */ 133 | public maxSize: number; 134 | 135 | /** ChannelPoolConfig minSize. */ 136 | public minSize: number; 137 | 138 | /** ChannelPoolConfig idleTimeout. */ 139 | public idleTimeout: (number|Long); 140 | 141 | /** ChannelPoolConfig maxConcurrentStreamsLowWatermark. */ 142 | public maxConcurrentStreamsLowWatermark: number; 143 | 144 | /** ChannelPoolConfig debugHeaderIntervalSecs. */ 145 | public debugHeaderIntervalSecs: number; 146 | 147 | /** 148 | * Creates a new ChannelPoolConfig instance using the specified properties. 149 | * @param [properties] Properties to set 150 | * @returns ChannelPoolConfig instance 151 | */ 152 | public static create(properties?: grpc.gcp.IChannelPoolConfig): grpc.gcp.ChannelPoolConfig; 153 | 154 | /** 155 | * Encodes the specified ChannelPoolConfig message. Does not implicitly {@link grpc.gcp.ChannelPoolConfig.verify|verify} messages. 156 | * @param message ChannelPoolConfig message or plain object to encode 157 | * @param [writer] Writer to encode to 158 | * @returns Writer 159 | */ 160 | public static encode(message: grpc.gcp.IChannelPoolConfig, writer?: $protobuf.Writer): $protobuf.Writer; 161 | 162 | /** 163 | * Encodes the specified ChannelPoolConfig message, length delimited. Does not implicitly {@link grpc.gcp.ChannelPoolConfig.verify|verify} messages. 164 | * @param message ChannelPoolConfig message or plain object to encode 165 | * @param [writer] Writer to encode to 166 | * @returns Writer 167 | */ 168 | public static encodeDelimited(message: grpc.gcp.IChannelPoolConfig, writer?: $protobuf.Writer): $protobuf.Writer; 169 | 170 | /** 171 | * Decodes a ChannelPoolConfig message from the specified reader or buffer. 172 | * @param reader Reader or buffer to decode from 173 | * @param [length] Message length if known beforehand 174 | * @returns ChannelPoolConfig 175 | * @throws {Error} If the payload is not a reader or valid buffer 176 | * @throws {$protobuf.util.ProtocolError} If required fields are missing 177 | */ 178 | public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): grpc.gcp.ChannelPoolConfig; 179 | 180 | /** 181 | * Decodes a ChannelPoolConfig message from the specified reader or buffer, length delimited. 182 | * @param reader Reader or buffer to decode from 183 | * @returns ChannelPoolConfig 184 | * @throws {Error} If the payload is not a reader or valid buffer 185 | * @throws {$protobuf.util.ProtocolError} If required fields are missing 186 | */ 187 | public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): grpc.gcp.ChannelPoolConfig; 188 | 189 | /** 190 | * Verifies a ChannelPoolConfig message. 191 | * @param message Plain object to verify 192 | * @returns `null` if valid, otherwise the reason why it is not 193 | */ 194 | public static verify(message: { [k: string]: any }): (string|null); 195 | 196 | /** 197 | * Creates a ChannelPoolConfig message from a plain object. Also converts values to their respective internal types. 198 | * @param object Plain object 199 | * @returns ChannelPoolConfig 200 | */ 201 | public static fromObject(object: { [k: string]: any }): grpc.gcp.ChannelPoolConfig; 202 | 203 | /** 204 | * Creates a plain object from a ChannelPoolConfig message. Also converts values to other types if specified. 205 | * @param message ChannelPoolConfig 206 | * @param [options] Conversion options 207 | * @returns Plain object 208 | */ 209 | public static toObject(message: grpc.gcp.ChannelPoolConfig, options?: $protobuf.IConversionOptions): { [k: string]: any }; 210 | 211 | /** 212 | * Converts this ChannelPoolConfig to JSON. 213 | * @returns JSON object 214 | */ 215 | public toJSON(): { [k: string]: any }; 216 | } 217 | 218 | /** Properties of a MethodConfig. */ 219 | interface IMethodConfig { 220 | 221 | /** MethodConfig name */ 222 | name?: (string[]|null); 223 | 224 | /** MethodConfig affinity */ 225 | affinity?: (grpc.gcp.IAffinityConfig|null); 226 | } 227 | 228 | /** Represents a MethodConfig. */ 229 | class MethodConfig implements IMethodConfig { 230 | 231 | /** 232 | * Constructs a new MethodConfig. 233 | * @param [properties] Properties to set 234 | */ 235 | constructor(properties?: grpc.gcp.IMethodConfig); 236 | 237 | /** MethodConfig name. */ 238 | public name: string[]; 239 | 240 | /** MethodConfig affinity. */ 241 | public affinity?: (grpc.gcp.IAffinityConfig|null); 242 | 243 | /** 244 | * Creates a new MethodConfig instance using the specified properties. 245 | * @param [properties] Properties to set 246 | * @returns MethodConfig instance 247 | */ 248 | public static create(properties?: grpc.gcp.IMethodConfig): grpc.gcp.MethodConfig; 249 | 250 | /** 251 | * Encodes the specified MethodConfig message. Does not implicitly {@link grpc.gcp.MethodConfig.verify|verify} messages. 252 | * @param message MethodConfig message or plain object to encode 253 | * @param [writer] Writer to encode to 254 | * @returns Writer 255 | */ 256 | public static encode(message: grpc.gcp.IMethodConfig, writer?: $protobuf.Writer): $protobuf.Writer; 257 | 258 | /** 259 | * Encodes the specified MethodConfig message, length delimited. Does not implicitly {@link grpc.gcp.MethodConfig.verify|verify} messages. 260 | * @param message MethodConfig message or plain object to encode 261 | * @param [writer] Writer to encode to 262 | * @returns Writer 263 | */ 264 | public static encodeDelimited(message: grpc.gcp.IMethodConfig, writer?: $protobuf.Writer): $protobuf.Writer; 265 | 266 | /** 267 | * Decodes a MethodConfig message from the specified reader or buffer. 268 | * @param reader Reader or buffer to decode from 269 | * @param [length] Message length if known beforehand 270 | * @returns MethodConfig 271 | * @throws {Error} If the payload is not a reader or valid buffer 272 | * @throws {$protobuf.util.ProtocolError} If required fields are missing 273 | */ 274 | public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): grpc.gcp.MethodConfig; 275 | 276 | /** 277 | * Decodes a MethodConfig message from the specified reader or buffer, length delimited. 278 | * @param reader Reader or buffer to decode from 279 | * @returns MethodConfig 280 | * @throws {Error} If the payload is not a reader or valid buffer 281 | * @throws {$protobuf.util.ProtocolError} If required fields are missing 282 | */ 283 | public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): grpc.gcp.MethodConfig; 284 | 285 | /** 286 | * Verifies a MethodConfig message. 287 | * @param message Plain object to verify 288 | * @returns `null` if valid, otherwise the reason why it is not 289 | */ 290 | public static verify(message: { [k: string]: any }): (string|null); 291 | 292 | /** 293 | * Creates a MethodConfig message from a plain object. Also converts values to their respective internal types. 294 | * @param object Plain object 295 | * @returns MethodConfig 296 | */ 297 | public static fromObject(object: { [k: string]: any }): grpc.gcp.MethodConfig; 298 | 299 | /** 300 | * Creates a plain object from a MethodConfig message. Also converts values to other types if specified. 301 | * @param message MethodConfig 302 | * @param [options] Conversion options 303 | * @returns Plain object 304 | */ 305 | public static toObject(message: grpc.gcp.MethodConfig, options?: $protobuf.IConversionOptions): { [k: string]: any }; 306 | 307 | /** 308 | * Converts this MethodConfig to JSON. 309 | * @returns JSON object 310 | */ 311 | public toJSON(): { [k: string]: any }; 312 | } 313 | 314 | /** Properties of an AffinityConfig. */ 315 | interface IAffinityConfig { 316 | 317 | /** AffinityConfig command */ 318 | command?: (grpc.gcp.AffinityConfig.Command|null); 319 | 320 | /** AffinityConfig affinityKey */ 321 | affinityKey?: (string|null); 322 | } 323 | 324 | /** Represents an AffinityConfig. */ 325 | class AffinityConfig implements IAffinityConfig { 326 | 327 | /** 328 | * Constructs a new AffinityConfig. 329 | * @param [properties] Properties to set 330 | */ 331 | constructor(properties?: grpc.gcp.IAffinityConfig); 332 | 333 | /** AffinityConfig command. */ 334 | public command: grpc.gcp.AffinityConfig.Command; 335 | 336 | /** AffinityConfig affinityKey. */ 337 | public affinityKey: string; 338 | 339 | /** 340 | * Creates a new AffinityConfig instance using the specified properties. 341 | * @param [properties] Properties to set 342 | * @returns AffinityConfig instance 343 | */ 344 | public static create(properties?: grpc.gcp.IAffinityConfig): grpc.gcp.AffinityConfig; 345 | 346 | /** 347 | * Encodes the specified AffinityConfig message. Does not implicitly {@link grpc.gcp.AffinityConfig.verify|verify} messages. 348 | * @param message AffinityConfig message or plain object to encode 349 | * @param [writer] Writer to encode to 350 | * @returns Writer 351 | */ 352 | public static encode(message: grpc.gcp.IAffinityConfig, writer?: $protobuf.Writer): $protobuf.Writer; 353 | 354 | /** 355 | * Encodes the specified AffinityConfig message, length delimited. Does not implicitly {@link grpc.gcp.AffinityConfig.verify|verify} messages. 356 | * @param message AffinityConfig message or plain object to encode 357 | * @param [writer] Writer to encode to 358 | * @returns Writer 359 | */ 360 | public static encodeDelimited(message: grpc.gcp.IAffinityConfig, writer?: $protobuf.Writer): $protobuf.Writer; 361 | 362 | /** 363 | * Decodes an AffinityConfig message from the specified reader or buffer. 364 | * @param reader Reader or buffer to decode from 365 | * @param [length] Message length if known beforehand 366 | * @returns AffinityConfig 367 | * @throws {Error} If the payload is not a reader or valid buffer 368 | * @throws {$protobuf.util.ProtocolError} If required fields are missing 369 | */ 370 | public static decode(reader: ($protobuf.Reader|Uint8Array), length?: number): grpc.gcp.AffinityConfig; 371 | 372 | /** 373 | * Decodes an AffinityConfig message from the specified reader or buffer, length delimited. 374 | * @param reader Reader or buffer to decode from 375 | * @returns AffinityConfig 376 | * @throws {Error} If the payload is not a reader or valid buffer 377 | * @throws {$protobuf.util.ProtocolError} If required fields are missing 378 | */ 379 | public static decodeDelimited(reader: ($protobuf.Reader|Uint8Array)): grpc.gcp.AffinityConfig; 380 | 381 | /** 382 | * Verifies an AffinityConfig message. 383 | * @param message Plain object to verify 384 | * @returns `null` if valid, otherwise the reason why it is not 385 | */ 386 | public static verify(message: { [k: string]: any }): (string|null); 387 | 388 | /** 389 | * Creates an AffinityConfig message from a plain object. Also converts values to their respective internal types. 390 | * @param object Plain object 391 | * @returns AffinityConfig 392 | */ 393 | public static fromObject(object: { [k: string]: any }): grpc.gcp.AffinityConfig; 394 | 395 | /** 396 | * Creates a plain object from an AffinityConfig message. Also converts values to other types if specified. 397 | * @param message AffinityConfig 398 | * @param [options] Conversion options 399 | * @returns Plain object 400 | */ 401 | public static toObject(message: grpc.gcp.AffinityConfig, options?: $protobuf.IConversionOptions): { [k: string]: any }; 402 | 403 | /** 404 | * Converts this AffinityConfig to JSON. 405 | * @returns JSON object 406 | */ 407 | public toJSON(): { [k: string]: any }; 408 | } 409 | 410 | namespace AffinityConfig { 411 | 412 | /** Command enum. */ 413 | enum Command { 414 | BOUND = 0, 415 | BIND = 1, 416 | UNBIND = 2 417 | } 418 | } 419 | } 420 | } 421 | -------------------------------------------------------------------------------- /src/generated/grpc_gcp.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars*/ 2 | "use strict"; 3 | 4 | var $protobuf = require("protobufjs/minimal"); 5 | 6 | // Common aliases 7 | var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; 8 | 9 | // Exported root namespace 10 | var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); 11 | 12 | $root.grpc = (function() { 13 | 14 | /** 15 | * Namespace grpc. 16 | * @exports grpc 17 | * @namespace 18 | */ 19 | var grpc = {}; 20 | 21 | grpc.gcp = (function() { 22 | 23 | /** 24 | * Namespace gcp. 25 | * @memberof grpc 26 | * @namespace 27 | */ 28 | var gcp = {}; 29 | 30 | gcp.ApiConfig = (function() { 31 | 32 | /** 33 | * Properties of an ApiConfig. 34 | * @memberof grpc.gcp 35 | * @interface IApiConfig 36 | * @property {grpc.gcp.IChannelPoolConfig|null} [channelPool] ApiConfig channelPool 37 | * @property {Array.|null} [method] ApiConfig method 38 | */ 39 | 40 | /** 41 | * Constructs a new ApiConfig. 42 | * @memberof grpc.gcp 43 | * @classdesc Represents an ApiConfig. 44 | * @implements IApiConfig 45 | * @constructor 46 | * @param {grpc.gcp.IApiConfig=} [properties] Properties to set 47 | */ 48 | function ApiConfig(properties) { 49 | this.method = []; 50 | if (properties) 51 | for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) 52 | if (properties[keys[i]] != null) 53 | this[keys[i]] = properties[keys[i]]; 54 | } 55 | 56 | /** 57 | * ApiConfig channelPool. 58 | * @member {grpc.gcp.IChannelPoolConfig|null|undefined} channelPool 59 | * @memberof grpc.gcp.ApiConfig 60 | * @instance 61 | */ 62 | ApiConfig.prototype.channelPool = null; 63 | 64 | /** 65 | * ApiConfig method. 66 | * @member {Array.} method 67 | * @memberof grpc.gcp.ApiConfig 68 | * @instance 69 | */ 70 | ApiConfig.prototype.method = $util.emptyArray; 71 | 72 | /** 73 | * Creates a new ApiConfig instance using the specified properties. 74 | * @function create 75 | * @memberof grpc.gcp.ApiConfig 76 | * @static 77 | * @param {grpc.gcp.IApiConfig=} [properties] Properties to set 78 | * @returns {grpc.gcp.ApiConfig} ApiConfig instance 79 | */ 80 | ApiConfig.create = function create(properties) { 81 | return new ApiConfig(properties); 82 | }; 83 | 84 | /** 85 | * Encodes the specified ApiConfig message. Does not implicitly {@link grpc.gcp.ApiConfig.verify|verify} messages. 86 | * @function encode 87 | * @memberof grpc.gcp.ApiConfig 88 | * @static 89 | * @param {grpc.gcp.IApiConfig} message ApiConfig message or plain object to encode 90 | * @param {$protobuf.Writer} [writer] Writer to encode to 91 | * @returns {$protobuf.Writer} Writer 92 | */ 93 | ApiConfig.encode = function encode(message, writer) { 94 | if (!writer) 95 | writer = $Writer.create(); 96 | if (message.channelPool != null && Object.hasOwnProperty.call(message, "channelPool")) 97 | $root.grpc.gcp.ChannelPoolConfig.encode(message.channelPool, writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim(); 98 | if (message.method != null && message.method.length) 99 | for (var i = 0; i < message.method.length; ++i) 100 | $root.grpc.gcp.MethodConfig.encode(message.method[i], writer.uint32(/* id 1001, wireType 2 =*/8010).fork()).ldelim(); 101 | return writer; 102 | }; 103 | 104 | /** 105 | * Encodes the specified ApiConfig message, length delimited. Does not implicitly {@link grpc.gcp.ApiConfig.verify|verify} messages. 106 | * @function encodeDelimited 107 | * @memberof grpc.gcp.ApiConfig 108 | * @static 109 | * @param {grpc.gcp.IApiConfig} message ApiConfig message or plain object to encode 110 | * @param {$protobuf.Writer} [writer] Writer to encode to 111 | * @returns {$protobuf.Writer} Writer 112 | */ 113 | ApiConfig.encodeDelimited = function encodeDelimited(message, writer) { 114 | return this.encode(message, writer).ldelim(); 115 | }; 116 | 117 | /** 118 | * Decodes an ApiConfig message from the specified reader or buffer. 119 | * @function decode 120 | * @memberof grpc.gcp.ApiConfig 121 | * @static 122 | * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from 123 | * @param {number} [length] Message length if known beforehand 124 | * @returns {grpc.gcp.ApiConfig} ApiConfig 125 | * @throws {Error} If the payload is not a reader or valid buffer 126 | * @throws {$protobuf.util.ProtocolError} If required fields are missing 127 | */ 128 | ApiConfig.decode = function decode(reader, length) { 129 | if (!(reader instanceof $Reader)) 130 | reader = $Reader.create(reader); 131 | var end = length === undefined ? reader.len : reader.pos + length, message = new $root.grpc.gcp.ApiConfig(); 132 | while (reader.pos < end) { 133 | var tag = reader.uint32(); 134 | switch (tag >>> 3) { 135 | case 2: 136 | message.channelPool = $root.grpc.gcp.ChannelPoolConfig.decode(reader, reader.uint32()); 137 | break; 138 | case 1001: 139 | if (!(message.method && message.method.length)) 140 | message.method = []; 141 | message.method.push($root.grpc.gcp.MethodConfig.decode(reader, reader.uint32())); 142 | break; 143 | default: 144 | reader.skipType(tag & 7); 145 | break; 146 | } 147 | } 148 | return message; 149 | }; 150 | 151 | /** 152 | * Decodes an ApiConfig message from the specified reader or buffer, length delimited. 153 | * @function decodeDelimited 154 | * @memberof grpc.gcp.ApiConfig 155 | * @static 156 | * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from 157 | * @returns {grpc.gcp.ApiConfig} ApiConfig 158 | * @throws {Error} If the payload is not a reader or valid buffer 159 | * @throws {$protobuf.util.ProtocolError} If required fields are missing 160 | */ 161 | ApiConfig.decodeDelimited = function decodeDelimited(reader) { 162 | if (!(reader instanceof $Reader)) 163 | reader = new $Reader(reader); 164 | return this.decode(reader, reader.uint32()); 165 | }; 166 | 167 | /** 168 | * Verifies an ApiConfig message. 169 | * @function verify 170 | * @memberof grpc.gcp.ApiConfig 171 | * @static 172 | * @param {Object.} message Plain object to verify 173 | * @returns {string|null} `null` if valid, otherwise the reason why it is not 174 | */ 175 | ApiConfig.verify = function verify(message) { 176 | if (typeof message !== "object" || message === null) 177 | return "object expected"; 178 | if (message.channelPool != null && message.hasOwnProperty("channelPool")) { 179 | var error = $root.grpc.gcp.ChannelPoolConfig.verify(message.channelPool); 180 | if (error) 181 | return "channelPool." + error; 182 | } 183 | if (message.method != null && message.hasOwnProperty("method")) { 184 | if (!Array.isArray(message.method)) 185 | return "method: array expected"; 186 | for (var i = 0; i < message.method.length; ++i) { 187 | var error = $root.grpc.gcp.MethodConfig.verify(message.method[i]); 188 | if (error) 189 | return "method." + error; 190 | } 191 | } 192 | return null; 193 | }; 194 | 195 | /** 196 | * Creates an ApiConfig message from a plain object. Also converts values to their respective internal types. 197 | * @function fromObject 198 | * @memberof grpc.gcp.ApiConfig 199 | * @static 200 | * @param {Object.} object Plain object 201 | * @returns {grpc.gcp.ApiConfig} ApiConfig 202 | */ 203 | ApiConfig.fromObject = function fromObject(object) { 204 | if (object instanceof $root.grpc.gcp.ApiConfig) 205 | return object; 206 | var message = new $root.grpc.gcp.ApiConfig(); 207 | if (object.channelPool != null) { 208 | if (typeof object.channelPool !== "object") 209 | throw TypeError(".grpc.gcp.ApiConfig.channelPool: object expected"); 210 | message.channelPool = $root.grpc.gcp.ChannelPoolConfig.fromObject(object.channelPool); 211 | } 212 | if (object.method) { 213 | if (!Array.isArray(object.method)) 214 | throw TypeError(".grpc.gcp.ApiConfig.method: array expected"); 215 | message.method = []; 216 | for (var i = 0; i < object.method.length; ++i) { 217 | if (typeof object.method[i] !== "object") 218 | throw TypeError(".grpc.gcp.ApiConfig.method: object expected"); 219 | message.method[i] = $root.grpc.gcp.MethodConfig.fromObject(object.method[i]); 220 | } 221 | } 222 | return message; 223 | }; 224 | 225 | /** 226 | * Creates a plain object from an ApiConfig message. Also converts values to other types if specified. 227 | * @function toObject 228 | * @memberof grpc.gcp.ApiConfig 229 | * @static 230 | * @param {grpc.gcp.ApiConfig} message ApiConfig 231 | * @param {$protobuf.IConversionOptions} [options] Conversion options 232 | * @returns {Object.} Plain object 233 | */ 234 | ApiConfig.toObject = function toObject(message, options) { 235 | if (!options) 236 | options = {}; 237 | var object = {}; 238 | if (options.arrays || options.defaults) 239 | object.method = []; 240 | if (options.defaults) 241 | object.channelPool = null; 242 | if (message.channelPool != null && message.hasOwnProperty("channelPool")) 243 | object.channelPool = $root.grpc.gcp.ChannelPoolConfig.toObject(message.channelPool, options); 244 | if (message.method && message.method.length) { 245 | object.method = []; 246 | for (var j = 0; j < message.method.length; ++j) 247 | object.method[j] = $root.grpc.gcp.MethodConfig.toObject(message.method[j], options); 248 | } 249 | return object; 250 | }; 251 | 252 | /** 253 | * Converts this ApiConfig to JSON. 254 | * @function toJSON 255 | * @memberof grpc.gcp.ApiConfig 256 | * @instance 257 | * @returns {Object.} JSON object 258 | */ 259 | ApiConfig.prototype.toJSON = function toJSON() { 260 | return this.constructor.toObject(this, $protobuf.util.toJSONOptions); 261 | }; 262 | 263 | return ApiConfig; 264 | })(); 265 | 266 | gcp.ChannelPoolConfig = (function() { 267 | 268 | /** 269 | * Properties of a ChannelPoolConfig. 270 | * @memberof grpc.gcp 271 | * @interface IChannelPoolConfig 272 | * @property {number|null} [maxSize] ChannelPoolConfig maxSize 273 | * @property {number|null} [minSize] ChannelPoolConfig minSize 274 | * @property {number|Long|null} [idleTimeout] ChannelPoolConfig idleTimeout 275 | * @property {number|null} [maxConcurrentStreamsLowWatermark] ChannelPoolConfig maxConcurrentStreamsLowWatermark 276 | * @property {number|null} [debugHeaderIntervalSecs] ChannelPoolConfig debugHeaderIntervalSecs 277 | */ 278 | 279 | /** 280 | * Constructs a new ChannelPoolConfig. 281 | * @memberof grpc.gcp 282 | * @classdesc Represents a ChannelPoolConfig. 283 | * @implements IChannelPoolConfig 284 | * @constructor 285 | * @param {grpc.gcp.IChannelPoolConfig=} [properties] Properties to set 286 | */ 287 | function ChannelPoolConfig(properties) { 288 | if (properties) 289 | for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) 290 | if (properties[keys[i]] != null) 291 | this[keys[i]] = properties[keys[i]]; 292 | } 293 | 294 | /** 295 | * ChannelPoolConfig maxSize. 296 | * @member {number} maxSize 297 | * @memberof grpc.gcp.ChannelPoolConfig 298 | * @instance 299 | */ 300 | ChannelPoolConfig.prototype.maxSize = 0; 301 | 302 | /** 303 | * ChannelPoolConfig minSize. 304 | * @member {number} minSize 305 | * @memberof grpc.gcp.ChannelPoolConfig 306 | * @instance 307 | */ 308 | ChannelPoolConfig.prototype.minSize = 0; 309 | 310 | /** 311 | * ChannelPoolConfig idleTimeout. 312 | * @member {number|Long} idleTimeout 313 | * @memberof grpc.gcp.ChannelPoolConfig 314 | * @instance 315 | */ 316 | ChannelPoolConfig.prototype.idleTimeout = $util.Long ? $util.Long.fromBits(0,0,true) : 0; 317 | 318 | /** 319 | * ChannelPoolConfig maxConcurrentStreamsLowWatermark. 320 | * @member {number} maxConcurrentStreamsLowWatermark 321 | * @memberof grpc.gcp.ChannelPoolConfig 322 | * @instance 323 | */ 324 | ChannelPoolConfig.prototype.maxConcurrentStreamsLowWatermark = 0; 325 | 326 | /** 327 | * ChannelPoolConfig debugHeaderIntervalSecs. 328 | * @member {number} debugHeaderIntervalSecs 329 | * @memberof grpc.gcp.ChannelPoolConfig 330 | * @instance 331 | */ 332 | ChannelPoolConfig.prototype.debugHeaderIntervalSecs = 0; 333 | 334 | /** 335 | * Creates a new ChannelPoolConfig instance using the specified properties. 336 | * @function create 337 | * @memberof grpc.gcp.ChannelPoolConfig 338 | * @static 339 | * @param {grpc.gcp.IChannelPoolConfig=} [properties] Properties to set 340 | * @returns {grpc.gcp.ChannelPoolConfig} ChannelPoolConfig instance 341 | */ 342 | ChannelPoolConfig.create = function create(properties) { 343 | return new ChannelPoolConfig(properties); 344 | }; 345 | 346 | /** 347 | * Encodes the specified ChannelPoolConfig message. Does not implicitly {@link grpc.gcp.ChannelPoolConfig.verify|verify} messages. 348 | * @function encode 349 | * @memberof grpc.gcp.ChannelPoolConfig 350 | * @static 351 | * @param {grpc.gcp.IChannelPoolConfig} message ChannelPoolConfig message or plain object to encode 352 | * @param {$protobuf.Writer} [writer] Writer to encode to 353 | * @returns {$protobuf.Writer} Writer 354 | */ 355 | ChannelPoolConfig.encode = function encode(message, writer) { 356 | if (!writer) 357 | writer = $Writer.create(); 358 | if (message.maxSize != null && Object.hasOwnProperty.call(message, "maxSize")) 359 | writer.uint32(/* id 1, wireType 0 =*/8).uint32(message.maxSize); 360 | if (message.idleTimeout != null && Object.hasOwnProperty.call(message, "idleTimeout")) 361 | writer.uint32(/* id 2, wireType 0 =*/16).uint64(message.idleTimeout); 362 | if (message.maxConcurrentStreamsLowWatermark != null && Object.hasOwnProperty.call(message, "maxConcurrentStreamsLowWatermark")) 363 | writer.uint32(/* id 3, wireType 0 =*/24).uint32(message.maxConcurrentStreamsLowWatermark); 364 | if (message.minSize != null && Object.hasOwnProperty.call(message, "minSize")) 365 | writer.uint32(/* id 4, wireType 0 =*/32).uint32(message.minSize); 366 | if (message.debugHeaderIntervalSecs != null && Object.hasOwnProperty.call(message, "debugHeaderIntervalSecs")) 367 | writer.uint32(/* id 5, wireType 0 =*/40).uint32(message.debugHeaderIntervalSecs); 368 | return writer; 369 | }; 370 | 371 | /** 372 | * Encodes the specified ChannelPoolConfig message, length delimited. Does not implicitly {@link grpc.gcp.ChannelPoolConfig.verify|verify} messages. 373 | * @function encodeDelimited 374 | * @memberof grpc.gcp.ChannelPoolConfig 375 | * @static 376 | * @param {grpc.gcp.IChannelPoolConfig} message ChannelPoolConfig message or plain object to encode 377 | * @param {$protobuf.Writer} [writer] Writer to encode to 378 | * @returns {$protobuf.Writer} Writer 379 | */ 380 | ChannelPoolConfig.encodeDelimited = function encodeDelimited(message, writer) { 381 | return this.encode(message, writer).ldelim(); 382 | }; 383 | 384 | /** 385 | * Decodes a ChannelPoolConfig message from the specified reader or buffer. 386 | * @function decode 387 | * @memberof grpc.gcp.ChannelPoolConfig 388 | * @static 389 | * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from 390 | * @param {number} [length] Message length if known beforehand 391 | * @returns {grpc.gcp.ChannelPoolConfig} ChannelPoolConfig 392 | * @throws {Error} If the payload is not a reader or valid buffer 393 | * @throws {$protobuf.util.ProtocolError} If required fields are missing 394 | */ 395 | ChannelPoolConfig.decode = function decode(reader, length) { 396 | if (!(reader instanceof $Reader)) 397 | reader = $Reader.create(reader); 398 | var end = length === undefined ? reader.len : reader.pos + length, message = new $root.grpc.gcp.ChannelPoolConfig(); 399 | while (reader.pos < end) { 400 | var tag = reader.uint32(); 401 | switch (tag >>> 3) { 402 | case 1: 403 | message.maxSize = reader.uint32(); 404 | break; 405 | case 4: 406 | message.minSize = reader.uint32(); 407 | break; 408 | case 2: 409 | message.idleTimeout = reader.uint64(); 410 | break; 411 | case 3: 412 | message.maxConcurrentStreamsLowWatermark = reader.uint32(); 413 | break; 414 | case 5: 415 | message.debugHeaderIntervalSecs = reader.uint32(); 416 | break; 417 | default: 418 | reader.skipType(tag & 7); 419 | break; 420 | } 421 | } 422 | return message; 423 | }; 424 | 425 | /** 426 | * Decodes a ChannelPoolConfig message from the specified reader or buffer, length delimited. 427 | * @function decodeDelimited 428 | * @memberof grpc.gcp.ChannelPoolConfig 429 | * @static 430 | * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from 431 | * @returns {grpc.gcp.ChannelPoolConfig} ChannelPoolConfig 432 | * @throws {Error} If the payload is not a reader or valid buffer 433 | * @throws {$protobuf.util.ProtocolError} If required fields are missing 434 | */ 435 | ChannelPoolConfig.decodeDelimited = function decodeDelimited(reader) { 436 | if (!(reader instanceof $Reader)) 437 | reader = new $Reader(reader); 438 | return this.decode(reader, reader.uint32()); 439 | }; 440 | 441 | /** 442 | * Verifies a ChannelPoolConfig message. 443 | * @function verify 444 | * @memberof grpc.gcp.ChannelPoolConfig 445 | * @static 446 | * @param {Object.} message Plain object to verify 447 | * @returns {string|null} `null` if valid, otherwise the reason why it is not 448 | */ 449 | ChannelPoolConfig.verify = function verify(message) { 450 | if (typeof message !== "object" || message === null) 451 | return "object expected"; 452 | if (message.maxSize != null && message.hasOwnProperty("maxSize")) 453 | if (!$util.isInteger(message.maxSize)) 454 | return "maxSize: integer expected"; 455 | if (message.minSize != null && message.hasOwnProperty("minSize")) 456 | if (!$util.isInteger(message.minSize)) 457 | return "minSize: integer expected"; 458 | if (message.idleTimeout != null && message.hasOwnProperty("idleTimeout")) 459 | if (!$util.isInteger(message.idleTimeout) && !(message.idleTimeout && $util.isInteger(message.idleTimeout.low) && $util.isInteger(message.idleTimeout.high))) 460 | return "idleTimeout: integer|Long expected"; 461 | if (message.maxConcurrentStreamsLowWatermark != null && message.hasOwnProperty("maxConcurrentStreamsLowWatermark")) 462 | if (!$util.isInteger(message.maxConcurrentStreamsLowWatermark)) 463 | return "maxConcurrentStreamsLowWatermark: integer expected"; 464 | if (message.debugHeaderIntervalSecs != null && message.hasOwnProperty("debugHeaderIntervalSecs")) 465 | if (!$util.isInteger(message.debugHeaderIntervalSecs)) 466 | return "debugHeaderIntervalSecs: integer expected"; 467 | return null; 468 | }; 469 | 470 | /** 471 | * Creates a ChannelPoolConfig message from a plain object. Also converts values to their respective internal types. 472 | * @function fromObject 473 | * @memberof grpc.gcp.ChannelPoolConfig 474 | * @static 475 | * @param {Object.} object Plain object 476 | * @returns {grpc.gcp.ChannelPoolConfig} ChannelPoolConfig 477 | */ 478 | ChannelPoolConfig.fromObject = function fromObject(object) { 479 | if (object instanceof $root.grpc.gcp.ChannelPoolConfig) 480 | return object; 481 | var message = new $root.grpc.gcp.ChannelPoolConfig(); 482 | if (object.maxSize != null) 483 | message.maxSize = object.maxSize >>> 0; 484 | if (object.minSize != null) 485 | message.minSize = object.minSize >>> 0; 486 | if (object.idleTimeout != null) 487 | if ($util.Long) 488 | (message.idleTimeout = $util.Long.fromValue(object.idleTimeout)).unsigned = true; 489 | else if (typeof object.idleTimeout === "string") 490 | message.idleTimeout = parseInt(object.idleTimeout, 10); 491 | else if (typeof object.idleTimeout === "number") 492 | message.idleTimeout = object.idleTimeout; 493 | else if (typeof object.idleTimeout === "object") 494 | message.idleTimeout = new $util.LongBits(object.idleTimeout.low >>> 0, object.idleTimeout.high >>> 0).toNumber(true); 495 | if (object.maxConcurrentStreamsLowWatermark != null) 496 | message.maxConcurrentStreamsLowWatermark = object.maxConcurrentStreamsLowWatermark >>> 0; 497 | if (object.debugHeaderIntervalSecs != null) 498 | message.debugHeaderIntervalSecs = object.debugHeaderIntervalSecs >>> 0; 499 | return message; 500 | }; 501 | 502 | /** 503 | * Creates a plain object from a ChannelPoolConfig message. Also converts values to other types if specified. 504 | * @function toObject 505 | * @memberof grpc.gcp.ChannelPoolConfig 506 | * @static 507 | * @param {grpc.gcp.ChannelPoolConfig} message ChannelPoolConfig 508 | * @param {$protobuf.IConversionOptions} [options] Conversion options 509 | * @returns {Object.} Plain object 510 | */ 511 | ChannelPoolConfig.toObject = function toObject(message, options) { 512 | if (!options) 513 | options = {}; 514 | var object = {}; 515 | if (options.defaults) { 516 | object.maxSize = 0; 517 | if ($util.Long) { 518 | var long = new $util.Long(0, 0, true); 519 | object.idleTimeout = options.longs === String ? long.toString() : options.longs === Number ? long.toNumber() : long; 520 | } else 521 | object.idleTimeout = options.longs === String ? "0" : 0; 522 | object.maxConcurrentStreamsLowWatermark = 0; 523 | object.minSize = 0; 524 | object.debugHeaderIntervalSecs = 0; 525 | } 526 | if (message.maxSize != null && message.hasOwnProperty("maxSize")) 527 | object.maxSize = message.maxSize; 528 | if (message.idleTimeout != null && message.hasOwnProperty("idleTimeout")) 529 | if (typeof message.idleTimeout === "number") 530 | object.idleTimeout = options.longs === String ? String(message.idleTimeout) : message.idleTimeout; 531 | else 532 | object.idleTimeout = options.longs === String ? $util.Long.prototype.toString.call(message.idleTimeout) : options.longs === Number ? new $util.LongBits(message.idleTimeout.low >>> 0, message.idleTimeout.high >>> 0).toNumber(true) : message.idleTimeout; 533 | if (message.maxConcurrentStreamsLowWatermark != null && message.hasOwnProperty("maxConcurrentStreamsLowWatermark")) 534 | object.maxConcurrentStreamsLowWatermark = message.maxConcurrentStreamsLowWatermark; 535 | if (message.minSize != null && message.hasOwnProperty("minSize")) 536 | object.minSize = message.minSize; 537 | if (message.debugHeaderIntervalSecs != null && message.hasOwnProperty("debugHeaderIntervalSecs")) 538 | object.debugHeaderIntervalSecs = message.debugHeaderIntervalSecs; 539 | return object; 540 | }; 541 | 542 | /** 543 | * Converts this ChannelPoolConfig to JSON. 544 | * @function toJSON 545 | * @memberof grpc.gcp.ChannelPoolConfig 546 | * @instance 547 | * @returns {Object.} JSON object 548 | */ 549 | ChannelPoolConfig.prototype.toJSON = function toJSON() { 550 | return this.constructor.toObject(this, $protobuf.util.toJSONOptions); 551 | }; 552 | 553 | return ChannelPoolConfig; 554 | })(); 555 | 556 | gcp.MethodConfig = (function() { 557 | 558 | /** 559 | * Properties of a MethodConfig. 560 | * @memberof grpc.gcp 561 | * @interface IMethodConfig 562 | * @property {Array.|null} [name] MethodConfig name 563 | * @property {grpc.gcp.IAffinityConfig|null} [affinity] MethodConfig affinity 564 | */ 565 | 566 | /** 567 | * Constructs a new MethodConfig. 568 | * @memberof grpc.gcp 569 | * @classdesc Represents a MethodConfig. 570 | * @implements IMethodConfig 571 | * @constructor 572 | * @param {grpc.gcp.IMethodConfig=} [properties] Properties to set 573 | */ 574 | function MethodConfig(properties) { 575 | this.name = []; 576 | if (properties) 577 | for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) 578 | if (properties[keys[i]] != null) 579 | this[keys[i]] = properties[keys[i]]; 580 | } 581 | 582 | /** 583 | * MethodConfig name. 584 | * @member {Array.} name 585 | * @memberof grpc.gcp.MethodConfig 586 | * @instance 587 | */ 588 | MethodConfig.prototype.name = $util.emptyArray; 589 | 590 | /** 591 | * MethodConfig affinity. 592 | * @member {grpc.gcp.IAffinityConfig|null|undefined} affinity 593 | * @memberof grpc.gcp.MethodConfig 594 | * @instance 595 | */ 596 | MethodConfig.prototype.affinity = null; 597 | 598 | /** 599 | * Creates a new MethodConfig instance using the specified properties. 600 | * @function create 601 | * @memberof grpc.gcp.MethodConfig 602 | * @static 603 | * @param {grpc.gcp.IMethodConfig=} [properties] Properties to set 604 | * @returns {grpc.gcp.MethodConfig} MethodConfig instance 605 | */ 606 | MethodConfig.create = function create(properties) { 607 | return new MethodConfig(properties); 608 | }; 609 | 610 | /** 611 | * Encodes the specified MethodConfig message. Does not implicitly {@link grpc.gcp.MethodConfig.verify|verify} messages. 612 | * @function encode 613 | * @memberof grpc.gcp.MethodConfig 614 | * @static 615 | * @param {grpc.gcp.IMethodConfig} message MethodConfig message or plain object to encode 616 | * @param {$protobuf.Writer} [writer] Writer to encode to 617 | * @returns {$protobuf.Writer} Writer 618 | */ 619 | MethodConfig.encode = function encode(message, writer) { 620 | if (!writer) 621 | writer = $Writer.create(); 622 | if (message.name != null && message.name.length) 623 | for (var i = 0; i < message.name.length; ++i) 624 | writer.uint32(/* id 1, wireType 2 =*/10).string(message.name[i]); 625 | if (message.affinity != null && Object.hasOwnProperty.call(message, "affinity")) 626 | $root.grpc.gcp.AffinityConfig.encode(message.affinity, writer.uint32(/* id 1001, wireType 2 =*/8010).fork()).ldelim(); 627 | return writer; 628 | }; 629 | 630 | /** 631 | * Encodes the specified MethodConfig message, length delimited. Does not implicitly {@link grpc.gcp.MethodConfig.verify|verify} messages. 632 | * @function encodeDelimited 633 | * @memberof grpc.gcp.MethodConfig 634 | * @static 635 | * @param {grpc.gcp.IMethodConfig} message MethodConfig message or plain object to encode 636 | * @param {$protobuf.Writer} [writer] Writer to encode to 637 | * @returns {$protobuf.Writer} Writer 638 | */ 639 | MethodConfig.encodeDelimited = function encodeDelimited(message, writer) { 640 | return this.encode(message, writer).ldelim(); 641 | }; 642 | 643 | /** 644 | * Decodes a MethodConfig message from the specified reader or buffer. 645 | * @function decode 646 | * @memberof grpc.gcp.MethodConfig 647 | * @static 648 | * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from 649 | * @param {number} [length] Message length if known beforehand 650 | * @returns {grpc.gcp.MethodConfig} MethodConfig 651 | * @throws {Error} If the payload is not a reader or valid buffer 652 | * @throws {$protobuf.util.ProtocolError} If required fields are missing 653 | */ 654 | MethodConfig.decode = function decode(reader, length) { 655 | if (!(reader instanceof $Reader)) 656 | reader = $Reader.create(reader); 657 | var end = length === undefined ? reader.len : reader.pos + length, message = new $root.grpc.gcp.MethodConfig(); 658 | while (reader.pos < end) { 659 | var tag = reader.uint32(); 660 | switch (tag >>> 3) { 661 | case 1: 662 | if (!(message.name && message.name.length)) 663 | message.name = []; 664 | message.name.push(reader.string()); 665 | break; 666 | case 1001: 667 | message.affinity = $root.grpc.gcp.AffinityConfig.decode(reader, reader.uint32()); 668 | break; 669 | default: 670 | reader.skipType(tag & 7); 671 | break; 672 | } 673 | } 674 | return message; 675 | }; 676 | 677 | /** 678 | * Decodes a MethodConfig message from the specified reader or buffer, length delimited. 679 | * @function decodeDelimited 680 | * @memberof grpc.gcp.MethodConfig 681 | * @static 682 | * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from 683 | * @returns {grpc.gcp.MethodConfig} MethodConfig 684 | * @throws {Error} If the payload is not a reader or valid buffer 685 | * @throws {$protobuf.util.ProtocolError} If required fields are missing 686 | */ 687 | MethodConfig.decodeDelimited = function decodeDelimited(reader) { 688 | if (!(reader instanceof $Reader)) 689 | reader = new $Reader(reader); 690 | return this.decode(reader, reader.uint32()); 691 | }; 692 | 693 | /** 694 | * Verifies a MethodConfig message. 695 | * @function verify 696 | * @memberof grpc.gcp.MethodConfig 697 | * @static 698 | * @param {Object.} message Plain object to verify 699 | * @returns {string|null} `null` if valid, otherwise the reason why it is not 700 | */ 701 | MethodConfig.verify = function verify(message) { 702 | if (typeof message !== "object" || message === null) 703 | return "object expected"; 704 | if (message.name != null && message.hasOwnProperty("name")) { 705 | if (!Array.isArray(message.name)) 706 | return "name: array expected"; 707 | for (var i = 0; i < message.name.length; ++i) 708 | if (!$util.isString(message.name[i])) 709 | return "name: string[] expected"; 710 | } 711 | if (message.affinity != null && message.hasOwnProperty("affinity")) { 712 | var error = $root.grpc.gcp.AffinityConfig.verify(message.affinity); 713 | if (error) 714 | return "affinity." + error; 715 | } 716 | return null; 717 | }; 718 | 719 | /** 720 | * Creates a MethodConfig message from a plain object. Also converts values to their respective internal types. 721 | * @function fromObject 722 | * @memberof grpc.gcp.MethodConfig 723 | * @static 724 | * @param {Object.} object Plain object 725 | * @returns {grpc.gcp.MethodConfig} MethodConfig 726 | */ 727 | MethodConfig.fromObject = function fromObject(object) { 728 | if (object instanceof $root.grpc.gcp.MethodConfig) 729 | return object; 730 | var message = new $root.grpc.gcp.MethodConfig(); 731 | if (object.name) { 732 | if (!Array.isArray(object.name)) 733 | throw TypeError(".grpc.gcp.MethodConfig.name: array expected"); 734 | message.name = []; 735 | for (var i = 0; i < object.name.length; ++i) 736 | message.name[i] = String(object.name[i]); 737 | } 738 | if (object.affinity != null) { 739 | if (typeof object.affinity !== "object") 740 | throw TypeError(".grpc.gcp.MethodConfig.affinity: object expected"); 741 | message.affinity = $root.grpc.gcp.AffinityConfig.fromObject(object.affinity); 742 | } 743 | return message; 744 | }; 745 | 746 | /** 747 | * Creates a plain object from a MethodConfig message. Also converts values to other types if specified. 748 | * @function toObject 749 | * @memberof grpc.gcp.MethodConfig 750 | * @static 751 | * @param {grpc.gcp.MethodConfig} message MethodConfig 752 | * @param {$protobuf.IConversionOptions} [options] Conversion options 753 | * @returns {Object.} Plain object 754 | */ 755 | MethodConfig.toObject = function toObject(message, options) { 756 | if (!options) 757 | options = {}; 758 | var object = {}; 759 | if (options.arrays || options.defaults) 760 | object.name = []; 761 | if (options.defaults) 762 | object.affinity = null; 763 | if (message.name && message.name.length) { 764 | object.name = []; 765 | for (var j = 0; j < message.name.length; ++j) 766 | object.name[j] = message.name[j]; 767 | } 768 | if (message.affinity != null && message.hasOwnProperty("affinity")) 769 | object.affinity = $root.grpc.gcp.AffinityConfig.toObject(message.affinity, options); 770 | return object; 771 | }; 772 | 773 | /** 774 | * Converts this MethodConfig to JSON. 775 | * @function toJSON 776 | * @memberof grpc.gcp.MethodConfig 777 | * @instance 778 | * @returns {Object.} JSON object 779 | */ 780 | MethodConfig.prototype.toJSON = function toJSON() { 781 | return this.constructor.toObject(this, $protobuf.util.toJSONOptions); 782 | }; 783 | 784 | return MethodConfig; 785 | })(); 786 | 787 | gcp.AffinityConfig = (function() { 788 | 789 | /** 790 | * Properties of an AffinityConfig. 791 | * @memberof grpc.gcp 792 | * @interface IAffinityConfig 793 | * @property {grpc.gcp.AffinityConfig.Command|null} [command] AffinityConfig command 794 | * @property {string|null} [affinityKey] AffinityConfig affinityKey 795 | */ 796 | 797 | /** 798 | * Constructs a new AffinityConfig. 799 | * @memberof grpc.gcp 800 | * @classdesc Represents an AffinityConfig. 801 | * @implements IAffinityConfig 802 | * @constructor 803 | * @param {grpc.gcp.IAffinityConfig=} [properties] Properties to set 804 | */ 805 | function AffinityConfig(properties) { 806 | if (properties) 807 | for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) 808 | if (properties[keys[i]] != null) 809 | this[keys[i]] = properties[keys[i]]; 810 | } 811 | 812 | /** 813 | * AffinityConfig command. 814 | * @member {grpc.gcp.AffinityConfig.Command} command 815 | * @memberof grpc.gcp.AffinityConfig 816 | * @instance 817 | */ 818 | AffinityConfig.prototype.command = 0; 819 | 820 | /** 821 | * AffinityConfig affinityKey. 822 | * @member {string} affinityKey 823 | * @memberof grpc.gcp.AffinityConfig 824 | * @instance 825 | */ 826 | AffinityConfig.prototype.affinityKey = ""; 827 | 828 | /** 829 | * Creates a new AffinityConfig instance using the specified properties. 830 | * @function create 831 | * @memberof grpc.gcp.AffinityConfig 832 | * @static 833 | * @param {grpc.gcp.IAffinityConfig=} [properties] Properties to set 834 | * @returns {grpc.gcp.AffinityConfig} AffinityConfig instance 835 | */ 836 | AffinityConfig.create = function create(properties) { 837 | return new AffinityConfig(properties); 838 | }; 839 | 840 | /** 841 | * Encodes the specified AffinityConfig message. Does not implicitly {@link grpc.gcp.AffinityConfig.verify|verify} messages. 842 | * @function encode 843 | * @memberof grpc.gcp.AffinityConfig 844 | * @static 845 | * @param {grpc.gcp.IAffinityConfig} message AffinityConfig message or plain object to encode 846 | * @param {$protobuf.Writer} [writer] Writer to encode to 847 | * @returns {$protobuf.Writer} Writer 848 | */ 849 | AffinityConfig.encode = function encode(message, writer) { 850 | if (!writer) 851 | writer = $Writer.create(); 852 | if (message.command != null && Object.hasOwnProperty.call(message, "command")) 853 | writer.uint32(/* id 2, wireType 0 =*/16).int32(message.command); 854 | if (message.affinityKey != null && Object.hasOwnProperty.call(message, "affinityKey")) 855 | writer.uint32(/* id 3, wireType 2 =*/26).string(message.affinityKey); 856 | return writer; 857 | }; 858 | 859 | /** 860 | * Encodes the specified AffinityConfig message, length delimited. Does not implicitly {@link grpc.gcp.AffinityConfig.verify|verify} messages. 861 | * @function encodeDelimited 862 | * @memberof grpc.gcp.AffinityConfig 863 | * @static 864 | * @param {grpc.gcp.IAffinityConfig} message AffinityConfig message or plain object to encode 865 | * @param {$protobuf.Writer} [writer] Writer to encode to 866 | * @returns {$protobuf.Writer} Writer 867 | */ 868 | AffinityConfig.encodeDelimited = function encodeDelimited(message, writer) { 869 | return this.encode(message, writer).ldelim(); 870 | }; 871 | 872 | /** 873 | * Decodes an AffinityConfig message from the specified reader or buffer. 874 | * @function decode 875 | * @memberof grpc.gcp.AffinityConfig 876 | * @static 877 | * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from 878 | * @param {number} [length] Message length if known beforehand 879 | * @returns {grpc.gcp.AffinityConfig} AffinityConfig 880 | * @throws {Error} If the payload is not a reader or valid buffer 881 | * @throws {$protobuf.util.ProtocolError} If required fields are missing 882 | */ 883 | AffinityConfig.decode = function decode(reader, length) { 884 | if (!(reader instanceof $Reader)) 885 | reader = $Reader.create(reader); 886 | var end = length === undefined ? reader.len : reader.pos + length, message = new $root.grpc.gcp.AffinityConfig(); 887 | while (reader.pos < end) { 888 | var tag = reader.uint32(); 889 | switch (tag >>> 3) { 890 | case 2: 891 | message.command = reader.int32(); 892 | break; 893 | case 3: 894 | message.affinityKey = reader.string(); 895 | break; 896 | default: 897 | reader.skipType(tag & 7); 898 | break; 899 | } 900 | } 901 | return message; 902 | }; 903 | 904 | /** 905 | * Decodes an AffinityConfig message from the specified reader or buffer, length delimited. 906 | * @function decodeDelimited 907 | * @memberof grpc.gcp.AffinityConfig 908 | * @static 909 | * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from 910 | * @returns {grpc.gcp.AffinityConfig} AffinityConfig 911 | * @throws {Error} If the payload is not a reader or valid buffer 912 | * @throws {$protobuf.util.ProtocolError} If required fields are missing 913 | */ 914 | AffinityConfig.decodeDelimited = function decodeDelimited(reader) { 915 | if (!(reader instanceof $Reader)) 916 | reader = new $Reader(reader); 917 | return this.decode(reader, reader.uint32()); 918 | }; 919 | 920 | /** 921 | * Verifies an AffinityConfig message. 922 | * @function verify 923 | * @memberof grpc.gcp.AffinityConfig 924 | * @static 925 | * @param {Object.} message Plain object to verify 926 | * @returns {string|null} `null` if valid, otherwise the reason why it is not 927 | */ 928 | AffinityConfig.verify = function verify(message) { 929 | if (typeof message !== "object" || message === null) 930 | return "object expected"; 931 | if (message.command != null && message.hasOwnProperty("command")) 932 | switch (message.command) { 933 | default: 934 | return "command: enum value expected"; 935 | case 0: 936 | case 1: 937 | case 2: 938 | break; 939 | } 940 | if (message.affinityKey != null && message.hasOwnProperty("affinityKey")) 941 | if (!$util.isString(message.affinityKey)) 942 | return "affinityKey: string expected"; 943 | return null; 944 | }; 945 | 946 | /** 947 | * Creates an AffinityConfig message from a plain object. Also converts values to their respective internal types. 948 | * @function fromObject 949 | * @memberof grpc.gcp.AffinityConfig 950 | * @static 951 | * @param {Object.} object Plain object 952 | * @returns {grpc.gcp.AffinityConfig} AffinityConfig 953 | */ 954 | AffinityConfig.fromObject = function fromObject(object) { 955 | if (object instanceof $root.grpc.gcp.AffinityConfig) 956 | return object; 957 | var message = new $root.grpc.gcp.AffinityConfig(); 958 | switch (object.command) { 959 | case "BOUND": 960 | case 0: 961 | message.command = 0; 962 | break; 963 | case "BIND": 964 | case 1: 965 | message.command = 1; 966 | break; 967 | case "UNBIND": 968 | case 2: 969 | message.command = 2; 970 | break; 971 | } 972 | if (object.affinityKey != null) 973 | message.affinityKey = String(object.affinityKey); 974 | return message; 975 | }; 976 | 977 | /** 978 | * Creates a plain object from an AffinityConfig message. Also converts values to other types if specified. 979 | * @function toObject 980 | * @memberof grpc.gcp.AffinityConfig 981 | * @static 982 | * @param {grpc.gcp.AffinityConfig} message AffinityConfig 983 | * @param {$protobuf.IConversionOptions} [options] Conversion options 984 | * @returns {Object.} Plain object 985 | */ 986 | AffinityConfig.toObject = function toObject(message, options) { 987 | if (!options) 988 | options = {}; 989 | var object = {}; 990 | if (options.defaults) { 991 | object.command = options.enums === String ? "BOUND" : 0; 992 | object.affinityKey = ""; 993 | } 994 | if (message.command != null && message.hasOwnProperty("command")) 995 | object.command = options.enums === String ? $root.grpc.gcp.AffinityConfig.Command[message.command] : message.command; 996 | if (message.affinityKey != null && message.hasOwnProperty("affinityKey")) 997 | object.affinityKey = message.affinityKey; 998 | return object; 999 | }; 1000 | 1001 | /** 1002 | * Converts this AffinityConfig to JSON. 1003 | * @function toJSON 1004 | * @memberof grpc.gcp.AffinityConfig 1005 | * @instance 1006 | * @returns {Object.} JSON object 1007 | */ 1008 | AffinityConfig.prototype.toJSON = function toJSON() { 1009 | return this.constructor.toObject(this, $protobuf.util.toJSONOptions); 1010 | }; 1011 | 1012 | /** 1013 | * Command enum. 1014 | * @name grpc.gcp.AffinityConfig.Command 1015 | * @enum {number} 1016 | * @property {number} BOUND=0 BOUND value 1017 | * @property {number} BIND=1 BIND value 1018 | * @property {number} UNBIND=2 UNBIND value 1019 | */ 1020 | AffinityConfig.Command = (function() { 1021 | var valuesById = {}, values = Object.create(valuesById); 1022 | values[valuesById[0] = "BOUND"] = 0; 1023 | values[valuesById[1] = "BIND"] = 1; 1024 | values[valuesById[2] = "UNBIND"] = 2; 1025 | return values; 1026 | })(); 1027 | 1028 | return AffinityConfig; 1029 | })(); 1030 | 1031 | return gcp; 1032 | })(); 1033 | 1034 | return grpc; 1035 | })(); 1036 | 1037 | module.exports = $root; 1038 | -------------------------------------------------------------------------------- /src/grpc_interface.ts: -------------------------------------------------------------------------------- 1 | export interface GrpcInterface { 2 | Channel: any; 3 | connectivityState: any; 4 | status: any; 5 | InterceptingCall: any; 6 | }; 7 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2018 gRPC authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | import * as grpcType from '@grpc/grpc-js'; 19 | import {GrpcInterface} from './grpc_interface'; 20 | import * as util from 'util'; 21 | 22 | import {ChannelRef} from './channel_ref'; 23 | import { 24 | GcpChannelFactoryInterface, 25 | getGcpChannelFactoryClass, 26 | } from './gcp_channel_factory'; 27 | import * as protoRoot from './generated/grpc_gcp'; 28 | 29 | import ApiConfig = protoRoot.grpc.gcp.ApiConfig; 30 | import AffinityConfig = protoRoot.grpc.gcp.AffinityConfig; 31 | 32 | type GrpcModule = typeof grpcType; 33 | 34 | export = (grpc: GrpcInterface) => setup(grpc as GrpcModule); 35 | const setup = (grpc: GrpcModule) => { 36 | const GcpChannelFactory = getGcpChannelFactoryClass(grpc); 37 | /** 38 | * Create ApiConfig proto message from config object. 39 | * @param apiDefinition Api object that specifies channel pool configuation. 40 | * @return A protobuf message type. 41 | */ 42 | function createGcpApiConfig(apiDefinition: {}): ApiConfig { 43 | return ApiConfig.fromObject(apiDefinition); 44 | } 45 | 46 | /** 47 | * Function for creating a gcp channel factory. 48 | * @memberof grpc-gcp 49 | * @param address The address of the server to connect to. 50 | * @param credentials Channel credentials to use when connecting 51 | * @param options A map of channel options that will be passed to the core. 52 | * @return {GcpChannelFactory} A GcpChannelFactory instance. 53 | */ 54 | function gcpChannelFactoryOverride( 55 | address: string, 56 | credentials: grpcType.ChannelCredentials, 57 | options: {} 58 | ) { 59 | return new GcpChannelFactory(address, credentials, options); 60 | } 61 | 62 | /** 63 | * Pass in call properties and return a new object with modified values. 64 | * This function will be used together with gcpChannelFactoryOverride 65 | * when constructing a grpc Client. 66 | * @memberof grpc-gcp 67 | * @param callProperties Call properties with channel factory object. 68 | * @return Modified call properties with selected grpc channel object. 69 | */ 70 | function gcpCallInvocationTransformer( 71 | callProperties: grpcType.CallProperties 72 | ): grpcType.CallProperties { 73 | if (!callProperties.channel || !(callProperties.channel instanceof GcpChannelFactory)) { 74 | // The gcpCallInvocationTransformer needs to use gcp channel factory. 75 | return callProperties; 76 | } 77 | 78 | const channelFactory = callProperties.channel as GcpChannelFactoryInterface; 79 | 80 | const argument = callProperties.argument; 81 | const metadata = callProperties.metadata; 82 | const call = callProperties.call; 83 | const methodDefinition = callProperties.methodDefinition; 84 | const path = methodDefinition.path; 85 | const callOptions = callProperties.callOptions; 86 | const callback = callProperties.callback; 87 | 88 | const preProcessResult = preProcess(channelFactory, path, argument); 89 | const channelRef = preProcessResult.channelRef; 90 | 91 | const boundKey = preProcessResult.boundKey; 92 | 93 | const postProcessInterceptor = ( 94 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 95 | options: any, 96 | nextCall: Function 97 | ): grpcType.InterceptingCall => { 98 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 99 | let firstMessage: any; 100 | 101 | const requester = { 102 | start: ( 103 | metadata: grpcType.Metadata, 104 | listener: grpcType.Listener, 105 | next: Function 106 | ): void => { 107 | const newListener = { 108 | onReceiveMetadata: ( 109 | metadata: grpcType.Metadata, 110 | next: Function 111 | ) => { 112 | next(metadata); 113 | }, 114 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 115 | onReceiveMessage: (message: any, next: Function) => { 116 | if (!firstMessage) firstMessage = message; 117 | next(message); 118 | }, 119 | onReceiveStatus: ( 120 | status: grpcType.StatusObject, 121 | next: Function 122 | ) => { 123 | if (status.code === grpc.status.OK) { 124 | postProcess( 125 | channelFactory, 126 | channelRef, 127 | path, 128 | boundKey, 129 | firstMessage 130 | ); 131 | } 132 | next(status); 133 | }, 134 | }; 135 | next(metadata, newListener); 136 | }, 137 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 138 | sendMessage: (message: any, next: Function): void => { 139 | next(message); 140 | }, 141 | halfClose: (next: Function): void => { 142 | next(); 143 | }, 144 | cancel: (next: Function): void => { 145 | next(); 146 | }, 147 | }; 148 | return new grpc.InterceptingCall(nextCall(options), requester); 149 | }; 150 | 151 | // Append interceptor to existing interceptors list. 152 | const newCallOptions = Object.assign({}, callOptions); 153 | const interceptors = callOptions.interceptors 154 | ? callOptions.interceptors 155 | : []; 156 | newCallOptions.interceptors = interceptors.concat([postProcessInterceptor]); 157 | 158 | if (channelFactory.shouldRequestDebugHeaders(channelRef.getDebugHeadersRequestedAt())) { 159 | metadata.set('x-return-encrypted-headers', 'all_response'); 160 | channelRef.notifyDebugHeadersRequested(); 161 | } 162 | 163 | return { 164 | argument, 165 | metadata, 166 | call, 167 | channel: channelRef.getChannel(), 168 | methodDefinition, 169 | callOptions: newCallOptions, 170 | callback, 171 | }; 172 | } 173 | 174 | /** 175 | * Handle channel affinity and pick a channel before call starts. 176 | * @param channelFactory The channel management factory. 177 | * @param path Method path. 178 | * @param argument The request arguments object. 179 | * @return Result containing bound affinity key and the chosen channel ref 180 | * object. 181 | */ 182 | function preProcess( 183 | channelFactory: GcpChannelFactoryInterface, 184 | path: string, 185 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 186 | argument?: any 187 | ): {boundKey: string | undefined; channelRef: ChannelRef} { 188 | const affinityConfig = channelFactory.getAffinityConfig(path); 189 | let boundKey; 190 | if (argument && affinityConfig) { 191 | const command = affinityConfig.command; 192 | if ( 193 | command === AffinityConfig.Command.BOUND || 194 | command === AffinityConfig.Command.UNBIND 195 | ) { 196 | boundKey = getAffinityKeyFromMessage( 197 | affinityConfig.affinityKey, 198 | argument 199 | ); 200 | } 201 | } 202 | const channelRef = channelFactory.getChannelRef(boundKey); 203 | channelRef.activeStreamsCountIncr(); 204 | return { 205 | boundKey, 206 | channelRef, 207 | }; 208 | } 209 | 210 | /** 211 | * Handle channel affinity and streams count after call is done. 212 | * @param channelFactory The channel management factory. 213 | * @param channelRef ChannelRef instance that contains a real grpc channel. 214 | * @param path Method path. 215 | * @param boundKey Affinity key bound to a channel. 216 | * @param responseMsg Response proto message. 217 | */ 218 | function postProcess( 219 | channelFactory: GcpChannelFactoryInterface, 220 | channelRef: ChannelRef, 221 | path: string, 222 | boundKey?: string, 223 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 224 | responseMsg?: any 225 | ) { 226 | if (!channelFactory || !responseMsg) return; 227 | const affinityConfig = channelFactory.getAffinityConfig(path); 228 | if (affinityConfig && affinityConfig.command) { 229 | const command = affinityConfig.command; 230 | if (command === AffinityConfig.Command.BIND) { 231 | const affinityKey = getAffinityKeyFromMessage( 232 | affinityConfig.affinityKey, 233 | responseMsg 234 | ); 235 | channelFactory.bind(channelRef, affinityKey); 236 | } else if (command === AffinityConfig.Command.UNBIND) { 237 | channelFactory.unbind(boundKey); 238 | } 239 | } 240 | channelRef.activeStreamsCountDecr(); 241 | } 242 | 243 | /** 244 | * Retrieve affinity key specified in the proto message. 245 | * @param affinityKeyName affinity key locator. 246 | * @param message proto message that contains affinity info. 247 | * @return Affinity key string. 248 | */ 249 | function getAffinityKeyFromMessage( 250 | affinityKeyName: string | null | undefined, 251 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 252 | message: any 253 | ): string { 254 | if (affinityKeyName) { 255 | let currMessage = message; 256 | const names = affinityKeyName.split('.'); 257 | let i = 0; 258 | for (; i < names.length; i++) { 259 | if (currMessage[names[i]]) { 260 | // check if the proto message is generated by protobufjs. 261 | currMessage = currMessage[names[i]]; 262 | } else { 263 | // otherwise use jspb format. 264 | const getter = 265 | 'get' + names[i].charAt(0).toUpperCase() + names[i].substr(1); 266 | if (!currMessage || typeof currMessage[getter] !== 'function') break; 267 | currMessage = currMessage[getter](); 268 | } 269 | } 270 | if (i !== 0 && i === names.length) return currMessage; 271 | } 272 | console.error( 273 | util.format( 274 | 'Cannot find affinity value from proto message using affinity_key: %s.', 275 | affinityKeyName 276 | ) 277 | ); 278 | return ''; 279 | } 280 | 281 | return { 282 | createGcpApiConfig, 283 | gcpChannelFactoryOverride, 284 | gcpCallInvocationTransformer, 285 | GcpChannelFactory, 286 | }; 287 | }; 288 | -------------------------------------------------------------------------------- /test/.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | env: 3 | mocha: true 4 | rules: 5 | node/no-unpublished-require: off -------------------------------------------------------------------------------- /test/integration/local_service_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2018 gRPC authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | /** 20 | * @fileoverview Integration tests for spanner grpc requests. 21 | */ 22 | 23 | 'use strict'; 24 | 25 | const protoLoader = require('@grpc/proto-loader'); 26 | const assert = require('assert'); 27 | const getGrpcGcpObjects = require('../../build/src'); 28 | const { promisify } = require('util') 29 | 30 | const PROTO_PATH = __dirname + '/../../protos/test_service.proto'; 31 | const packageDef = protoLoader.loadSync(PROTO_PATH); 32 | 33 | for (const grpcLibName of ['grpc', '@grpc/grpc-js']) { 34 | describe('Using ' + grpcLibName, () => { 35 | const grpc = require(grpcLibName); 36 | const grpcGcp = getGrpcGcpObjects(grpc); 37 | 38 | const server_insecure_creds = grpc.ServerCredentials.createInsecure(); 39 | const Client = grpc.loadPackageDefinition(packageDef).TestService; 40 | 41 | const initApiConfig = function () { 42 | return grpcGcp.createGcpApiConfig({ 43 | channelPool: { 44 | maxSize: 10, 45 | maxConcurrentStreamsLowWatermark: 1, 46 | }, 47 | }); 48 | }; 49 | 50 | describe('Local service integration tests', () => { 51 | let server; 52 | let port; 53 | before(done => { 54 | // var packageDef = protoLoader.loadSync(PROTO_PATH); 55 | // Client = grpc.loadPackageDefinition(packageDef).TestService; 56 | server = new grpc.Server(); 57 | server.addService(Client.service, { 58 | unary: function (call, cb) { 59 | call.sendMetadata(call.metadata); 60 | cb(null, {}); 61 | }, 62 | clientStream: function (stream, cb) { 63 | stream.on('data', data => {}); 64 | stream.on('end', () => { 65 | stream.sendMetadata(stream.metadata); 66 | cb(null, {}); 67 | }); 68 | }, 69 | serverStream: function (stream) { 70 | stream.sendMetadata(stream.metadata); 71 | stream.end(); 72 | }, 73 | bidiStream: function (stream) { 74 | stream.on('data', data => {}); 75 | stream.on('end', () => { 76 | stream.sendMetadata(stream.metadata); 77 | stream.end(); 78 | }); 79 | }, 80 | }); 81 | server.bindAsync( 82 | 'localhost:0', 83 | server_insecure_creds, 84 | (error, boundPort) => { 85 | if (error) { 86 | done(error); 87 | } 88 | port = boundPort; 89 | server.start(); 90 | done(); 91 | } 92 | ); 93 | }); 94 | after(() => { 95 | server.forceShutdown(); 96 | }); 97 | 98 | describe('Different channel options', () => { 99 | it('no api config', done => { 100 | const channelOptions = { 101 | channelFactoryOverride: grpcGcp.gcpChannelFactoryOverride, 102 | callInvocationTransformer: grpcGcp.gcpCallInvocationTransformer, 103 | }; 104 | const client = new Client( 105 | 'localhost:' + port, 106 | grpc.credentials.createInsecure(), 107 | channelOptions 108 | ); 109 | const channelFactory = client.getChannel(); 110 | assert(channelFactory instanceof grpcGcp.GcpChannelFactory); 111 | assert.strictEqual(channelFactory.maxSize, 10); 112 | assert.strictEqual( 113 | channelFactory.maxConcurrentStreamsLowWatermark, 114 | 100 115 | ); 116 | done(); 117 | }); 118 | it('no override function', done => { 119 | const channelOptions = { 120 | callInvocationTransformer: grpcGcp.gcpCallInvocationTransformer, 121 | }; 122 | const client = new Client( 123 | 'localhost:' + port, 124 | grpc.credentials.createInsecure(), 125 | channelOptions 126 | ); 127 | const channel = client.getChannel(); 128 | assert(channel instanceof grpc.Channel); 129 | done(); 130 | }); 131 | }); 132 | 133 | // describe('Debug headers', () => { 134 | // let client; 135 | // beforeEach(() => { 136 | // const channelOptions = { 137 | // channelFactoryOverride: grpcGcp.gcpChannelFactoryOverride, 138 | // callInvocationTransformer: grpcGcp.gcpCallInvocationTransformer, 139 | // gcpApiConfig: grpcGcp.createGcpApiConfig({ 140 | // channelPool: { 141 | // maxSize: 1, 142 | // maxConcurrentStreamsLowWatermark: 1, 143 | // debugHeaderIntervalSecs: 1 144 | // }, 145 | // }), 146 | // }; 147 | 148 | // client = new Client( 149 | // 'localhost:' + port, 150 | // grpc.credentials.createInsecure(), 151 | // channelOptions 152 | // ); 153 | // }); 154 | // afterEach(() => { 155 | // client.close(); 156 | // }); 157 | // it('with unary call', async () => { 158 | // function makeCallAndReturnMeta() { 159 | // return new Promise((resolve, reject) => { 160 | // let lastMeta = null; 161 | 162 | // const call = client.unary({}, new grpc.Metadata(), (err, data) => { 163 | // if (err) reject(err); 164 | // else resolve(lastMeta); 165 | // }); 166 | 167 | // call.on('metadata', meta => lastMeta = meta); 168 | // }); 169 | // } 170 | // let m1 = await makeCallAndReturnMeta(); 171 | // assert.deepStrictEqual(m1.get('x-return-encrypted-headers'), ['all_response']); 172 | 173 | // let m2 = await makeCallAndReturnMeta(); 174 | // assert.deepStrictEqual(m2.get('x-return-encrypted-headers'), []); 175 | 176 | // await promisify(setTimeout)(1100); 177 | 178 | // let m3 = await makeCallAndReturnMeta(); 179 | // assert.deepStrictEqual(m3.get('x-return-encrypted-headers'), ['all_response']); 180 | // }); 181 | // it('with server streaming call', async () => { 182 | // function makeCallAndReturnMeta() { 183 | // return new Promise((resolve, reject) => { 184 | // const call = client.serverStream({}, new grpc.Metadata()); 185 | // call.on('metadata', meta => resolve(meta)); 186 | // call.on('data', (d) => {}) 187 | // }); 188 | // } 189 | // let m1 = await makeCallAndReturnMeta(); 190 | // assert.deepStrictEqual(m1.get('x-return-encrypted-headers'), ['all_response']); 191 | 192 | // let m2 = await makeCallAndReturnMeta(); 193 | // assert.deepStrictEqual(m2.get('x-return-encrypted-headers'), []); 194 | 195 | // await promisify(setTimeout)(1100); 196 | 197 | // let m3 = await makeCallAndReturnMeta(); 198 | // assert.deepStrictEqual(m3.get('x-return-encrypted-headers'), ['all_response']); 199 | // }); 200 | // }); 201 | describe('Echo metadata', () => { 202 | let metadata; 203 | let client; 204 | before(() => { 205 | const apiConfig = initApiConfig(); 206 | const channelOptions = { 207 | channelFactoryOverride: grpcGcp.gcpChannelFactoryOverride, 208 | callInvocationTransformer: grpcGcp.gcpCallInvocationTransformer, 209 | gcpApiConfig: apiConfig, 210 | }; 211 | 212 | client = new Client( 213 | 'localhost:' + port, 214 | grpc.credentials.createInsecure(), 215 | channelOptions 216 | ); 217 | 218 | metadata = new grpc.Metadata(); 219 | metadata.set('key', 'value'); 220 | }); 221 | it('with unary call', done => { 222 | const call = client.unary({}, metadata, (err, data) => { 223 | assert.ifError(err); 224 | }); 225 | call.on('metadata', metadata => { 226 | assert.deepStrictEqual(metadata.get('key'), ['value']); 227 | done(); 228 | }); 229 | }); 230 | it('with client stream call', done => { 231 | const call = client.clientStream(metadata, (err, data) => { 232 | assert.ifError(err); 233 | }); 234 | call.on('metadata', metadata => { 235 | assert.deepStrictEqual(metadata.get('key'), ['value']); 236 | done(); 237 | }); 238 | call.end(); 239 | }); 240 | it('with server stream call', done => { 241 | const call = client.serverStream({}, metadata); 242 | call.on('data', () => {}); 243 | call.on('metadata', metadata => { 244 | assert.deepStrictEqual(metadata.get('key'), ['value']); 245 | done(); 246 | }); 247 | }); 248 | it('with bidi stream call', done => { 249 | const call = client.bidiStream(metadata); 250 | call.on('data', () => {}); 251 | call.on('metadata', metadata => { 252 | assert.deepStrictEqual(metadata.get('key'), ['value']); 253 | done(); 254 | }); 255 | call.end(); 256 | }); 257 | it('properly handles duplicate values', done => { 258 | const dup_metadata = metadata.clone(); 259 | dup_metadata.add('key', 'value2'); 260 | const call = client.unary({}, dup_metadata, (err, data) => { 261 | assert.ifError(err); 262 | }); 263 | call.on('metadata', resp_metadata => { 264 | // Two arrays are equal iff their symmetric difference is empty 265 | const actual_values = resp_metadata.get('key'); 266 | if (actual_values.length === 1) { 267 | assert.deepStrictEqual(actual_values, ['value, value2']); 268 | } else { 269 | assert.deepStrictEqual(actual_values, ['value', 'value2']); 270 | } 271 | done(); 272 | }); 273 | }); 274 | describe('Call argument handling', () => { 275 | describe('Unary call', () => { 276 | it('Should handle missing options', done => { 277 | const call = client.unary({}, metadata, (err, data) => { 278 | assert.ifError(err); 279 | }); 280 | call.on('metadata', metadata => { 281 | assert.deepStrictEqual(metadata.get('key'), ['value']); 282 | done(); 283 | }); 284 | }); 285 | it('Should handle missing metadata and options', done => { 286 | const call = client.unary({}, (err, data) => { 287 | assert.ifError(err); 288 | }); 289 | call.on('metadata', metadata => { 290 | done(); 291 | }); 292 | }); 293 | }); 294 | describe('Client stream call', () => { 295 | it('Should handle missing options', done => { 296 | const call = client.clientStream(metadata, (err, data) => { 297 | assert.ifError(err); 298 | }); 299 | call.on('metadata', metadata => { 300 | assert.deepStrictEqual(metadata.get('key'), ['value']); 301 | done(); 302 | }); 303 | call.end(); 304 | }); 305 | it('Should handle missing metadata and options', done => { 306 | const call = client.clientStream((err, data) => { 307 | assert.ifError(err); 308 | }); 309 | call.on('metadata', metadata => { 310 | done(); 311 | }); 312 | call.end(); 313 | }); 314 | }); 315 | describe('Server stream call', () => { 316 | it('Should handle missing options', done => { 317 | const call = client.serverStream({}, metadata); 318 | call.on('data', () => {}); 319 | call.on('metadata', metadata => { 320 | assert.deepStrictEqual(metadata.get('key'), ['value']); 321 | done(); 322 | }); 323 | }); 324 | it('Should handle missing metadata and options', done => { 325 | const call = client.serverStream({}); 326 | call.on('data', () => {}); 327 | call.on('metadata', metadata => { 328 | done(); 329 | }); 330 | }); 331 | }); 332 | describe('Bidi stream call', () => { 333 | it('Should handle missing options', done => { 334 | const call = client.bidiStream(metadata); 335 | call.on('data', () => {}); 336 | call.on('metadata', metadata => { 337 | assert.deepStrictEqual(metadata.get('key'), ['value']); 338 | done(); 339 | }); 340 | call.end(); 341 | }); 342 | it('Should handle missing metadata and options', done => { 343 | const call = client.bidiStream(); 344 | call.on('data', () => {}); 345 | call.on('metadata', metadata => { 346 | done(); 347 | }); 348 | call.end(); 349 | }); 350 | }); 351 | }); 352 | }); 353 | }); 354 | }); 355 | } 356 | -------------------------------------------------------------------------------- /test/integration/spanner.grpc.config: -------------------------------------------------------------------------------- 1 | { 2 | "channelPool": { 3 | "maxSize": 10, 4 | "maxConcurrentStreamsLowWatermark": 1 5 | }, 6 | "method": [ 7 | { 8 | "name": [ "/google.spanner.v1.Spanner/CreateSession" ], 9 | "affinity": { 10 | "command": "BIND", 11 | "affinityKey": "name" 12 | } 13 | }, 14 | { 15 | "name": [ "/google.spanner.v1.Spanner/GetSession" ], 16 | "affinity": { 17 | "command": "BOUND", 18 | "affinityKey": "name" 19 | } 20 | }, 21 | { 22 | "name": [ "/google.spanner.v1.Spanner/DeleteSession" ], 23 | "affinity": { 24 | "command": "UNBIND", 25 | "affinityKey": "name" 26 | } 27 | }, 28 | { 29 | "name": [ "/google.spanner.v1.Spanner/ExecuteSql" ], 30 | "affinity": { 31 | "command": "BOUND", 32 | "affinityKey": "session" 33 | } 34 | }, 35 | { 36 | "name": [ "/google.spanner.v1.Spanner/ExecuteStreamingSql" ], 37 | "affinity": { 38 | "command": "BOUND", 39 | "affinityKey": "session" 40 | } 41 | }, 42 | { 43 | "name": [ "/google.spanner.v1.Spanner/Read" ], 44 | "affinity": { 45 | "command": "BOUND", 46 | "affinityKey": "session" 47 | } 48 | }, 49 | { 50 | "name": [ "/google.spanner.v1.Spanner/StreamingRead" ], 51 | "affinity": { 52 | "command": "BOUND", 53 | "affinityKey": "session" 54 | } 55 | }, 56 | { 57 | "name": [ "/google.spanner.v1.Spanner/BeginTransaction" ], 58 | "affinity": { 59 | "command": "BOUND", 60 | "affinityKey": "session" 61 | } 62 | }, 63 | { 64 | "name": [ "/google.spanner.v1.Spanner/Commit" ], 65 | "affinity": { 66 | "command": "BOUND", 67 | "affinityKey": "session" 68 | } 69 | }, 70 | { 71 | "name": [ "/google.spanner.v1.Spanner/Rollback" ], 72 | "affinity": { 73 | "command": "BOUND", 74 | "affinityKey": "session" 75 | } 76 | }, 77 | { 78 | "name": [ "/google.spanner.v1.Spanner/PartitionQuery" ], 79 | "affinity": { 80 | "command": "BOUND", 81 | "affinityKey": "session" 82 | } 83 | }, 84 | { 85 | "name": [ "/google.spanner.v1.Spanner/PartitionRead" ], 86 | "affinity": { 87 | "command": "BOUND", 88 | "affinityKey": "session" 89 | } 90 | } 91 | ] 92 | } -------------------------------------------------------------------------------- /test/integration/spanner_test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2018 gRPC authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | /** 20 | * @fileoverview Integration tests for spanner grpc requests. 21 | */ 22 | 23 | 'use strict'; 24 | 25 | const PROTO_DIR = __dirname + '/../../third_party/googleapis'; 26 | 27 | const protoLoader = require('@grpc/proto-loader'); 28 | const assert = require('assert'); 29 | const {GoogleAuth} = require('google-auth-library'); 30 | const fs = require('fs'); 31 | const gax = require('google-gax'); 32 | const {Spanner} = require('@google-cloud/spanner'); 33 | 34 | const _TARGET = 'spanner.googleapis.com:443'; 35 | const _OAUTH_SCOPE = 'https://www.googleapis.com/auth/cloud-platform'; 36 | const _MAX_RAND_ID = 1000000; 37 | const _PROJECT_ID = 'long-door-651'; 38 | const _INSTANCE_ID = 39 | 'test-instance-' + Math.floor(Math.random() * _MAX_RAND_ID); 40 | const _DATABASE_ID = 'test-db-' + Math.floor(Math.random() * _MAX_RAND_ID); 41 | const _TABLE_NAME = 'storage'; 42 | const _DATABASE = 43 | `projects/${_PROJECT_ID}/instances/${_INSTANCE_ID}/databases/${_DATABASE_ID}`; 44 | const _TEST_SQL = `select id from ${_TABLE_NAME}`; 45 | const _CONFIG_FILE = `${__dirname}/spanner.grpc.config`; 46 | 47 | const getGrpcGcpObjects = require('../../build/src'); 48 | 49 | const spanner = new Spanner({projectId: _PROJECT_ID}); 50 | const instance = spanner.instance(_INSTANCE_ID); 51 | 52 | const argIndex = process.argv.findIndex(value => value === '--grpclib'); 53 | assert.notStrictEqual(argIndex, -1, 'Please, specify grpc lib with --grpclib'); 54 | const grpcLibName = process.argv[argIndex + 1]; 55 | 56 | describe('Using ' + grpcLibName, () => { 57 | before(async function () { 58 | this.timeout(120000); 59 | // Create test instance. 60 | console.log(`Creating instance ${instance.formattedName_}.`); 61 | const [, instOp] = await instance.create({ 62 | config: 'regional-us-central1', 63 | nodes: 1, 64 | displayName: 'grpc-gcp-node tests', 65 | labels: { 66 | ['grpc_gcp_node_tests']: 'true', 67 | created: Math.round(Date.now() / 1000).toString(), // current time 68 | }, 69 | }); 70 | 71 | console.log(`Waiting for operation on ${instance.id} to complete...`); 72 | await instOp.promise(); 73 | console.log(`Created instance ${_INSTANCE_ID}.`); 74 | 75 | // Create test database. 76 | const [database, dbOp] = await instance.createDatabase(_DATABASE_ID); 77 | 78 | console.log(`Waiting for operation on ${database.id} to complete...`); 79 | await dbOp.promise(); 80 | 81 | console.log(`Created database ${_DATABASE_ID} on instance ${_INSTANCE_ID}.`); 82 | 83 | // Create test table. 84 | const table = database.table(_TABLE_NAME); 85 | const schema = 86 | `CREATE TABLE ${_TABLE_NAME} (id STRING(1024)) PRIMARY KEY(id)`; 87 | 88 | const [, tableOp] = await table.create(schema); 89 | console.log(`Waiting for operation on ${table.id} to complete...`); 90 | await tableOp.promise(); 91 | console.log(`Created table "${_TABLE_NAME}".`); 92 | 93 | // Populate test table. 94 | const row = { 95 | id: 'payload', 96 | }; 97 | 98 | console.log(`Inserting row to the table...`); 99 | await table.insert(row); 100 | console.log(`Row inserted into table "${_TABLE_NAME}".`); 101 | }); 102 | 103 | after(async function () { 104 | this.timeout(120000); 105 | // Delete test instance. 106 | console.log(`Deleting instance ${instance.id}...`); 107 | await instance.delete(); 108 | console.log(`Deleted instance ${_INSTANCE_ID}.`); 109 | }); 110 | 111 | const grpc = require(grpcLibName); 112 | const grpcGcp = getGrpcGcpObjects(grpc); 113 | describe('Spanner integration tests', () => { 114 | describe('SpannerClient generated by jspb', () => { 115 | const spannerPackageDef = protoLoader.loadSync( 116 | PROTO_DIR + '/google/spanner/v1/spanner.proto', 117 | {includeDirs: [PROTO_DIR]} 118 | ); 119 | const spannerGrpc = grpc.loadPackageDefinition(spannerPackageDef); 120 | 121 | let client; 122 | let pool; 123 | 124 | beforeEach(async () => { 125 | const authFactory = new GoogleAuth(); 126 | const auth = await authFactory.getClient(); 127 | 128 | const sslCreds = grpc.credentials.createSsl(); 129 | const callCreds = grpc.credentials.createFromGoogleCredential(auth); 130 | const channelCreds = grpc.credentials.combineChannelCredentials( 131 | sslCreds, 132 | callCreds 133 | ); 134 | 135 | const apiDefinition = JSON.parse(fs.readFileSync(_CONFIG_FILE)); 136 | const apiConfig = grpcGcp.createGcpApiConfig(apiDefinition); 137 | 138 | const channelOptions = { 139 | channelFactoryOverride: grpcGcp.gcpChannelFactoryOverride, 140 | callInvocationTransformer: grpcGcp.gcpCallInvocationTransformer, 141 | gcpApiConfig: apiConfig, 142 | }; 143 | 144 | client = new spannerGrpc.google.spanner.v1.Spanner( 145 | _TARGET, 146 | channelCreds, 147 | channelOptions 148 | ); 149 | 150 | pool = client.getChannel(); 151 | }); 152 | 153 | it('Test session operations', done => { 154 | const createSessionRequest = {database: _DATABASE}; 155 | client.createSession(createSessionRequest, (err, session) => { 156 | assert.ifError(err); 157 | const sessionName = session.name; 158 | 159 | const getSessionRequest = {name: sessionName}; 160 | client.getSession(getSessionRequest, (err, sessionResult) => { 161 | assert.ifError(err); 162 | assert.strictEqual(sessionResult.name, sessionName); 163 | 164 | const listSessionsRequest = {database: _DATABASE}; 165 | client.listSessions(listSessionsRequest, (err, response) => { 166 | assert.ifError(err); 167 | const sessionsList = response.sessions; 168 | const sessionNames = sessionsList.map(session => session.name); 169 | assert(sessionNames.includes(sessionName)); 170 | 171 | const deleteSessionRequest = {name: sessionName}; 172 | client.deleteSession(deleteSessionRequest, err => { 173 | assert.ifError(err); 174 | done(); 175 | }); 176 | }); 177 | }); 178 | }); 179 | }); 180 | 181 | it('Test executeSql', done => { 182 | const createSessionRequest = {database: _DATABASE}; 183 | client.createSession(createSessionRequest, (err, session) => { 184 | assert.ifError(err); 185 | const sessionName = session.name; 186 | 187 | assert.strictEqual(pool.channelRefs.length, 1); 188 | assert.strictEqual(pool.channelRefs[0].affinityCount, 1); 189 | assert.strictEqual(pool.channelRefs[0].activeStreamsCount, 0); 190 | 191 | const executeSqlRequest = { 192 | session: sessionName, 193 | sql: _TEST_SQL, 194 | }; 195 | client.executeSql(executeSqlRequest, (err, resultSet) => { 196 | assert.ifError(err); 197 | assert.notStrictEqual(resultSet, null); 198 | const rowsList = resultSet.rows; 199 | const value = rowsList[0].values[0].stringValue; 200 | 201 | assert.strictEqual(value, 'payload'); 202 | assert.strictEqual(pool.channelRefs.length, 1); 203 | assert.strictEqual(pool.channelRefs[0].affinityCount, 1); 204 | assert.strictEqual(pool.channelRefs[0].activeStreamsCount, 0); 205 | 206 | const deleteSessionRequest = {name: sessionName}; 207 | client.deleteSession(deleteSessionRequest, err => { 208 | assert.ifError(err); 209 | assert.strictEqual(pool.channelRefs.length, 1); 210 | assert.strictEqual(pool.channelRefs[0].affinityCount, 0); 211 | assert.strictEqual(pool.channelRefs[0].activeStreamsCount, 0); 212 | done(); 213 | }); 214 | }); 215 | }); 216 | }); 217 | 218 | it('Test executeStreamingSql', done => { 219 | const createSessionRequest = {database: _DATABASE}; 220 | client.createSession(createSessionRequest, (err, session) => { 221 | assert.ifError(err); 222 | const sessionName = session.name; 223 | 224 | assert.strictEqual(pool.channelRefs.length, 1); 225 | assert.strictEqual(pool.channelRefs[0].affinityCount, 1); 226 | assert.strictEqual(pool.channelRefs[0].activeStreamsCount, 0); 227 | 228 | const executeSqlRequest = { 229 | session: sessionName, 230 | sql: _TEST_SQL, 231 | }; 232 | const call = client.executeStreamingSql(executeSqlRequest); 233 | 234 | assert.strictEqual(pool.channelRefs.length, 1); 235 | assert.strictEqual(pool.channelRefs[0].affinityCount, 1); 236 | assert.strictEqual(pool.channelRefs[0].activeStreamsCount, 1); 237 | 238 | call.on('data', partialResultSet => { 239 | const value = partialResultSet.values[0].stringValue; 240 | assert.strictEqual(value, 'payload'); 241 | }); 242 | 243 | call.on('status', status => { 244 | assert.strictEqual(status.code, grpc.status.OK); 245 | assert.strictEqual(pool.channelRefs.length, 1); 246 | assert.strictEqual(pool.channelRefs[0].affinityCount, 1); 247 | assert.strictEqual(pool.channelRefs[0].activeStreamsCount, 0); 248 | }); 249 | 250 | call.on('end', () => { 251 | const deleteSessionRequest = {name: sessionName}; 252 | client.deleteSession(deleteSessionRequest, err => { 253 | assert.ifError(err); 254 | assert.strictEqual(pool.channelRefs.length, 1); 255 | assert.strictEqual(pool.channelRefs[0].affinityCount, 0); 256 | assert.strictEqual(pool.channelRefs[0].activeStreamsCount, 0); 257 | done(); 258 | }); 259 | }); 260 | }); 261 | }); 262 | 263 | it('Test concurrent streams watermark', done => { 264 | const watermark = 5; 265 | pool.maxConcurrentStreamsLowWatermark = watermark; 266 | 267 | const expectedNumChannels = 3; 268 | 269 | const createCallPromises = []; 270 | 271 | for (let i = 0; i < watermark * expectedNumChannels; i++) { 272 | const promise = new Promise((resolve, reject) => { 273 | const createSessionRequest = {database: _DATABASE}; 274 | client.createSession(createSessionRequest, (err, session) => { 275 | if (err) { 276 | reject(err); 277 | } else { 278 | const executeSqlRequest = { 279 | session: session.name, 280 | sql: _TEST_SQL, 281 | }; 282 | const call = client.executeStreamingSql(executeSqlRequest); 283 | 284 | resolve({ 285 | call: call, 286 | sessionName: session.name, 287 | }); 288 | } 289 | }); 290 | }); 291 | createCallPromises.push(promise); 292 | } 293 | 294 | Promise.all(createCallPromises) 295 | .then( 296 | results => { 297 | assert.strictEqual( 298 | pool.channelRefs.length, 299 | expectedNumChannels 300 | ); 301 | assert.strictEqual( 302 | pool.channelRefs[0].affinityCount, 303 | watermark 304 | ); 305 | assert.strictEqual( 306 | pool.channelRefs[0].activeStreamsCount, 307 | watermark 308 | ); 309 | 310 | // Consume streaming calls. 311 | const emitterPromises = results.map( 312 | result => 313 | new Promise(resolve => { 314 | result.call.on('data', partialResultSet => { 315 | const value = partialResultSet.values[0].stringValue; 316 | assert.strictEqual(value, 'payload'); 317 | }); 318 | result.call.on('end', () => { 319 | const deleteSessionRequest = {name: result.sessionName}; 320 | client.deleteSession(deleteSessionRequest, err => { 321 | assert.ifError(err); 322 | resolve(); 323 | }); 324 | }); 325 | }) 326 | ); 327 | 328 | // Make sure all sessions get cleaned. 329 | return Promise.all(emitterPromises); 330 | }, 331 | error => { 332 | done(error); 333 | } 334 | ) 335 | .then( 336 | () => { 337 | assert.strictEqual( 338 | pool.channelRefs.length, 339 | expectedNumChannels 340 | ); 341 | assert.strictEqual(pool.channelRefs[0].affinityCount, 0); 342 | assert.strictEqual(pool.channelRefs[0].activeStreamsCount, 0); 343 | done(); 344 | }, 345 | error => { 346 | done(error); 347 | } 348 | ); 349 | }); 350 | 351 | it('Test invalid BOUND affinity', done => { 352 | const getSessionRequest = {name: 'wrong_name'}; 353 | client.getSession(getSessionRequest, err => { 354 | assert(err); 355 | assert.strictEqual( 356 | err.message, 357 | '3 INVALID_ARGUMENT: Invalid GetSession request.' 358 | ); 359 | done(); 360 | }); 361 | }); 362 | 363 | it('Test invalid UNBIND affinity', done => { 364 | const deleteSessionRequest = {name: 'wrong_name'}; 365 | client.deleteSession(deleteSessionRequest, err => { 366 | assert(err); 367 | assert.strictEqual( 368 | err.message, 369 | '3 INVALID_ARGUMENT: Invalid DeleteSession request.' 370 | ); 371 | done(); 372 | }); 373 | }); 374 | }); 375 | 376 | describe('SpannerClient generated by google-gax', () => { 377 | const gaxGrpc = new gax.GrpcClient({grpc}); 378 | const protos = gaxGrpc.loadProto( 379 | PROTO_DIR, 380 | 'google/spanner/v1/spanner.proto' 381 | ); 382 | const SpannerClient = protos.google.spanner.v1.Spanner; 383 | 384 | let client; 385 | let pool; 386 | 387 | beforeEach(async () => { 388 | const authFactory = new GoogleAuth({ 389 | scopes: [_OAUTH_SCOPE], 390 | }); 391 | const auth = await authFactory.getClient(); 392 | 393 | const sslCreds = grpc.credentials.createSsl(); 394 | const callCreds = grpc.credentials.createFromGoogleCredential(auth); 395 | const channelCreds = grpc.credentials.combineChannelCredentials( 396 | sslCreds, 397 | callCreds 398 | ); 399 | 400 | const apiDefinition = JSON.parse(fs.readFileSync(_CONFIG_FILE)); 401 | const apiConfig = grpcGcp.createGcpApiConfig(apiDefinition); 402 | 403 | const channelOptions = { 404 | channelFactoryOverride: grpcGcp.gcpChannelFactoryOverride, 405 | callInvocationTransformer: grpcGcp.gcpCallInvocationTransformer, 406 | gcpApiConfig: apiConfig, 407 | }; 408 | 409 | client = new SpannerClient(_TARGET, channelCreds, channelOptions); 410 | pool = client.getChannel(); 411 | }); 412 | 413 | it('Test session operations', done => { 414 | client.createSession({database: _DATABASE}, (err, session) => { 415 | assert.ifError(err); 416 | const sessionName = session.name; 417 | 418 | client.getSession({name: sessionName}, (err, sessionResult) => { 419 | assert.ifError(err); 420 | assert.strictEqual(sessionResult.name, sessionName); 421 | 422 | client.listSessions({database: _DATABASE}, (err, response) => { 423 | assert.ifError(err); 424 | const sessionsList = response.sessions; 425 | const sessionNames = sessionsList.map(session => session.name); 426 | assert(sessionNames.includes(sessionName)); 427 | 428 | client.deleteSession({name: sessionName}, err => { 429 | assert.ifError(err); 430 | done(); 431 | }); 432 | }); 433 | }); 434 | }); 435 | }); 436 | 437 | it('Test executeSql', done => { 438 | client.createSession({database: _DATABASE}, (err, session) => { 439 | assert.ifError(err); 440 | const sessionName = session.name; 441 | 442 | assert.strictEqual(pool.channelRefs.length, 1); 443 | assert.strictEqual(pool.channelRefs[0].affinityCount, 1); 444 | assert.strictEqual(pool.channelRefs[0].activeStreamsCount, 0); 445 | 446 | const executeSqlRequest = { 447 | session: sessionName, 448 | sql: _TEST_SQL, 449 | }; 450 | client.executeSql(executeSqlRequest, (err, resultSet) => { 451 | assert.ifError(err); 452 | assert.notStrictEqual(resultSet, null); 453 | const rowsList = resultSet.rows; 454 | const value = rowsList[0].values[0].stringValue; 455 | 456 | assert.strictEqual(value, 'payload'); 457 | assert.strictEqual(pool.channelRefs.length, 1); 458 | assert.strictEqual(pool.channelRefs[0].affinityCount, 1); 459 | assert.strictEqual(pool.channelRefs[0].activeStreamsCount, 0); 460 | 461 | const deleteSessionRequest = {name: sessionName}; 462 | client.deleteSession(deleteSessionRequest, err => { 463 | assert.ifError(err); 464 | assert.strictEqual(pool.channelRefs.length, 1); 465 | assert.strictEqual(pool.channelRefs[0].affinityCount, 0); 466 | assert.strictEqual(pool.channelRefs[0].activeStreamsCount, 0); 467 | done(); 468 | }); 469 | }); 470 | }); 471 | }); 472 | 473 | it('Test executeStreamingSql', done => { 474 | client.createSession({database: _DATABASE}, (err, session) => { 475 | assert.ifError(err); 476 | const sessionName = session.name; 477 | 478 | assert.strictEqual(pool.channelRefs.length, 1); 479 | assert.strictEqual(pool.channelRefs[0].affinityCount, 1); 480 | assert.strictEqual(pool.channelRefs[0].activeStreamsCount, 0); 481 | 482 | const executeSqlRequest = { 483 | session: sessionName, 484 | sql: _TEST_SQL, 485 | }; 486 | const call = client.executeStreamingSql(executeSqlRequest); 487 | 488 | assert.strictEqual(pool.channelRefs.length, 1); 489 | assert.strictEqual(pool.channelRefs[0].affinityCount, 1); 490 | assert.strictEqual(pool.channelRefs[0].activeStreamsCount, 1); 491 | 492 | call.on('data', partialResultSet => { 493 | const value = partialResultSet.values[0].stringValue; 494 | assert.strictEqual(value, 'payload'); 495 | }); 496 | 497 | call.on('status', status => { 498 | assert.strictEqual(status.code, grpc.status.OK); 499 | assert.strictEqual(pool.channelRefs.length, 1); 500 | assert.strictEqual(pool.channelRefs[0].affinityCount, 1); 501 | assert.strictEqual(pool.channelRefs[0].activeStreamsCount, 0); 502 | }); 503 | 504 | call.on('end', () => { 505 | client.deleteSession({name: sessionName}, err => { 506 | assert.ifError(err); 507 | assert.strictEqual(pool.channelRefs.length, 1); 508 | assert.strictEqual(pool.channelRefs[0].affinityCount, 0); 509 | assert.strictEqual(pool.channelRefs[0].activeStreamsCount, 0); 510 | done(); 511 | }); 512 | }); 513 | }); 514 | }); 515 | }); 516 | }); 517 | }); 518 | -------------------------------------------------------------------------------- /test/unit/channel_factory_test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Copyright 2018 gRPC authors. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | 'use strict'; 20 | 21 | const assert = require('assert'); 22 | const getGrpcGcpObjects = require('../../build/src'); 23 | 24 | /** 25 | * This is used for testing functions with multiple asynchronous calls that 26 | * can happen in different orders. This should be passed the number of async 27 | * function invocations that can occur last, and each of those should call this 28 | * function's return value 29 | * @param {function()} done The function that should be called when a test is 30 | * complete. 31 | * @param {number} count The number of calls to the resulting function if the 32 | * test passes. 33 | * @return {function()} The function that should be called at the end of each 34 | * sequence of asynchronous functions. 35 | */ 36 | function multiDone(done, count) { 37 | return function () { 38 | count -= 1; 39 | if (count <= 0) { 40 | done(); 41 | } 42 | }; 43 | } 44 | 45 | for (const grpcLibName of ['grpc', '@grpc/grpc-js']) { 46 | describe('Using ' + grpcLibName, () => { 47 | const grpc = require(grpcLibName); 48 | const grpcGcp = getGrpcGcpObjects(grpc); 49 | 50 | const insecureCreds = grpc.credentials.createInsecure(); 51 | 52 | describe('grpc-gcp channel factory tests', () => { 53 | describe('constructor', () => { 54 | it('should require a string for the first argument', () => { 55 | assert.doesNotThrow(() => { 56 | new grpcGcp.GcpChannelFactory('hostname', insecureCreds); 57 | }); 58 | assert.throws(() => { 59 | new grpcGcp.GcpChannelFactory(); 60 | }, TypeError); 61 | assert.throws(() => { 62 | new grpcGcp.GcpChannelFactory(5); 63 | }); 64 | }); 65 | it('should require a credential for the second argument', () => { 66 | assert.doesNotThrow(() => { 67 | new grpcGcp.GcpChannelFactory('hostname', insecureCreds); 68 | }); 69 | assert.throws(() => { 70 | new grpcGcp.GcpChannelFactory('hostname', 5); 71 | }); 72 | assert.throws(() => { 73 | new grpcGcp.GcpChannelFactory('hostname'); 74 | }); 75 | }); 76 | it('should accept an object for the third argument', () => { 77 | assert.doesNotThrow(() => { 78 | new grpcGcp.GcpChannelFactory('hostname', insecureCreds, {}); 79 | }); 80 | assert.throws(() => { 81 | new grpcGcp.GcpChannelFactory('hostname', insecureCreds, 'abc'); 82 | }); 83 | }); 84 | it('should only accept objects with string or int values', () => { 85 | assert.doesNotThrow(() => { 86 | new grpcGcp.GcpChannelFactory('hostname', insecureCreds, { 87 | key: 'value', 88 | }); 89 | }); 90 | assert.doesNotThrow(() => { 91 | new grpcGcp.GcpChannelFactory('hostname', insecureCreds, {key: 5}); 92 | }); 93 | }); 94 | it('should succeed without the new keyword', () => { 95 | assert.doesNotThrow(() => { 96 | const channel = new grpcGcp.GcpChannelFactory( 97 | 'hostname', 98 | insecureCreds 99 | ); 100 | assert(channel instanceof grpcGcp.GcpChannelFactory); 101 | }); 102 | }); 103 | 104 | it('should build min channels', () => { 105 | const channel = new grpcGcp.GcpChannelFactory( 106 | 'hostname', 107 | insecureCreds, 108 | { 109 | gcpApiConfig: grpcGcp.createGcpApiConfig({ 110 | "channelPool": { 111 | "minSize": 3 112 | } 113 | }) 114 | } 115 | ); 116 | assert.equal(channel.channelRefs.length, 3); 117 | }) 118 | }); 119 | describe('close', () => { 120 | let channel; 121 | beforeEach(() => { 122 | channel = new grpcGcp.GcpChannelFactory( 123 | 'hostname', 124 | insecureCreds, 125 | {} 126 | ); 127 | }); 128 | it('should succeed silently', () => { 129 | assert.doesNotThrow(() => { 130 | channel.close(); 131 | }); 132 | }); 133 | it('should be idempotent', () => { 134 | assert.doesNotThrow(() => { 135 | channel.close(); 136 | channel.close(); 137 | }); 138 | }); 139 | }); 140 | describe('getTarget', () => { 141 | let channel; 142 | beforeEach(() => { 143 | channel = new grpcGcp.GcpChannelFactory( 144 | 'hostname', 145 | insecureCreds, 146 | {} 147 | ); 148 | }); 149 | it('should return a string', () => { 150 | assert.strictEqual(typeof channel.getTarget(), 'string'); 151 | }); 152 | }); 153 | describe('getConnectivityState', () => { 154 | let channel; 155 | beforeEach(() => { 156 | channel = new grpcGcp.GcpChannelFactory( 157 | 'hostname', 158 | insecureCreds, 159 | {} 160 | ); 161 | }); 162 | it('should return IDLE for a new channel', () => { 163 | assert.strictEqual( 164 | channel.getConnectivityState(), 165 | grpc.connectivityState.IDLE 166 | ); 167 | }); 168 | }); 169 | describe('watchConnectivityState', () => { 170 | let channel; 171 | beforeEach(() => { 172 | channel = new grpcGcp.GcpChannelFactory( 173 | 'localhost', 174 | insecureCreds, 175 | {} 176 | ); 177 | }); 178 | afterEach(() => { 179 | channel.close(); 180 | }); 181 | it('should throw an error if no channels are available', done => { 182 | channel.channelRefs = []; 183 | channel.watchConnectivityState(0, new Date(), err => { 184 | assert(err instanceof Error); 185 | assert.strictEqual( 186 | err.message, 187 | 'Cannot watch connectivity state because there are no channels.' 188 | ); 189 | done(); 190 | }); 191 | }); 192 | it('should resolve immediately if the state is different', done => { 193 | const fakeState = grpc.connectivityState.READY; 194 | channel.getConnectivityState = function () { 195 | return grpc.connectivityState.IDLE; 196 | }; 197 | channel.watchConnectivityState(fakeState, 1000, err => { 198 | assert.ifError(err); 199 | done(); 200 | }); 201 | }); 202 | it('should call channel.watchConnectivityState', done => { 203 | const fakeState = grpc.connectivityState.READY; 204 | channel.getConnectivityState = function () { 205 | return fakeState; 206 | }; 207 | channel.channelRefs.forEach(channelRef => { 208 | channelRef.channel.getConnectivityState = function (connect) { 209 | assert.strictEqual(connect, false); 210 | return fakeState; 211 | }; 212 | channelRef.channel.watchConnectivityState = function (s, d, cb) { 213 | assert.strictEqual(s, fakeState); 214 | assert.strictEqual(d, 1000); 215 | channel.getConnectivityState = function () { 216 | return grpc.connectivityState.IDLE; 217 | }; 218 | setImmediate(cb); 219 | }; 220 | }); 221 | channel.watchConnectivityState(fakeState, 1000, done); 222 | }); 223 | }); 224 | describe('createCall', () => { 225 | let channel; 226 | beforeEach(() => { 227 | channel = new grpcGcp.GcpChannelFactory( 228 | 'localhost', 229 | insecureCreds, 230 | {} 231 | ); 232 | }); 233 | afterEach(() => { 234 | channel.close(); 235 | }); 236 | it('should return grpc.Call', () => { 237 | assert.throws(() => { 238 | channel.createCall(); 239 | }, TypeError); 240 | assert.throws(() => { 241 | channel.createCall('method'); 242 | }, TypeError); 243 | assert.doesNotThrow(() => { 244 | channel.createCall('method', new Date()); 245 | }); 246 | assert.doesNotThrow(() => { 247 | channel.createCall('method', 0); 248 | }); 249 | assert.doesNotThrow(() => { 250 | channel.createCall('method', new Date(), 'host_override'); 251 | }); 252 | }); 253 | }); 254 | }); 255 | }); 256 | } 257 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/gts/tsconfig-google.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "outDir": "build", 6 | "target": "es6" 7 | }, 8 | "include": [ 9 | "src" 10 | ] 11 | } 12 | --------------------------------------------------------------------------------