diff --git a/.gitignore b/.gitignore index 281f730458..b09c1ea37e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,6 @@ coverage/ dump.rdb logs/ *.iml +*.pb .idea/ .nyc_output diff --git a/endpoints/getting-started-grpc/.dockerignore b/endpoints/getting-started-grpc/.dockerignore new file mode 100644 index 0000000000..18bae67fd6 --- /dev/null +++ b/endpoints/getting-started-grpc/.dockerignore @@ -0,0 +1,4 @@ +*.md +*.yaml +node_modules/ +test/ diff --git a/endpoints/getting-started-grpc/Dockerfile b/endpoints/getting-started-grpc/Dockerfile new file mode 100644 index 0000000000..9caeabaee4 --- /dev/null +++ b/endpoints/getting-started-grpc/Dockerfile @@ -0,0 +1,15 @@ +# The Google App Engine Flexible Environment base Docker image can +# also be used on Google Container Engine, or any other Docker host. +# This image is based on Debian Jessie and includes nodejs and npm +# installed from nodejs.org. The source is located in +# https://github.com/GoogleCloudPlatform/nodejs-docker +FROM gcr.io/google_appengine/nodejs + +ADD . /app +WORKDIR /app + +RUN npm install +ENTRYPOINT [] + +EXPOSE 50051 +CMD ["npm", "start"] diff --git a/endpoints/getting-started-grpc/README.md b/endpoints/getting-started-grpc/README.md new file mode 100644 index 0000000000..fb02a819f2 --- /dev/null +++ b/endpoints/getting-started-grpc/README.md @@ -0,0 +1,122 @@ +# Google Cloud Endpoints sample for Node.js + +This sample demonstrates how to use Google Cloud Endpoints with Node.js. + +For a complete walkthrough showing how to run this sample in different +environments, see the [Google Cloud Endpoints Quickstarts](https://cloud.google.com/endpoints/docs/quickstarts). + +## Running locally + +### Start a local server +``` +$ node server.js -p 50051 +``` + +### Run the client +``` +$ node client.js -h localhost:50051 +``` + +## Running on Google Cloud Platform +### Setup +Make sure you have [gcloud](https://cloud.google.com/sdk/gcloud/) and [Node.js](https://nodejs.org/) installed. + +To update `gcloud`, use the `gcloud components update` command. + +### Deploying to Endpoints +1. Install [protoc](https://github.com/google/protobuf/#protocol-compiler-installation). + +1. Compile the proto file using protoc. +``` +$ protoc --include_imports --include_source_info protos/helloworld.proto --descriptor_set_out out.pb +``` + +1. In `api_config.yaml`, replace `MY_PROJECT_ID` with your Project ID. + +1. Deploy your service's configuration to Endpoints. Take note of your service's config ID and name once the deployment completes. +``` +$ gcloud service-management deploy out.pb api_config.yaml +... +Service Configuration [SERVICE_CONFIG_ID] uploaded for service [SERVICE_NAME] +``` + +1. Build a Docker image for later use using the following command. Make sure to replace `[YOUR_PROJECT_ID]` with your Project ID. +``` +$ gcloud container builds submit --tag gcr.io/[YOUR_PROJECT_ID]/endpoints-example:1.0 . +``` + +### Running your service +#### Compute Engine +1. [Create](https://console.cloud.google.com/compute/instancesAdd) a Compute Engine instance. Be sure to check **Allow HTTP traffic** and **Allow HTTPS traffic** when creating the instance. + +1. Once your instance is created, take note of its IP address. + +Note: this IP address is _ephemeral_ by default, and may change unexpectedly. If you plan to use this instance in the future, [reserve a static IP address](https://cloud.google.com/compute/docs/configure-ip-addresses#reserve_new_static) instead. + +1. SSH into your instance, and install Docker. +``` +$ sudo apt-get update +$ sudo apt-get install docker.io +``` + +1. Using the SSH connection to your instance, initialize the required Docker images in the order specified below. Replace `[YOUR_GCLOUD_PROJECT]`, `[YOUR_SERVICE_NAME]` and `[YOUR_SERVICE_CONFIG_ID]` with your GCloud Project ID, your service's name and your service's config ID respectively. +``` +$ sudo docker run -d --name=helloworld gcr.io/[YOUR_GCLOUD_PROJECT]/endpoints-example:1.0 +``` + +``` +$ sudo docker run --detach --name=esp \ + -p 80:9000 \ + --link=helloworld:helloworld \ + gcr.io/endpoints-release/endpoints-runtime:1 \ + -s [YOUR_SERVICE_NAME] \ + -v [YOUR_SERVICE_CONFIG_ID] \ + -P 9000 \ + -a grpc://helloworld:50051 +``` + +1. On your local machine, use the client to test your Endpoints deployment. Replace `[YOUR_INSTANCE_IP_ADDRESS]` with your instance's external IP address, and `[YOUR_API_KEY]` with a [valid Google Cloud Platform API key](https://support.google.com/cloud/answer/6158862?hl=en). +``` +$ node client.js -h [YOUR_INSTANCE_IP_ADDRESS]:80 -k [YOUR_API_KEY] +``` + +#### Container Engine +1. If you haven't already, install `kubectl`. +``` +$ gcloud components install kubectl +``` + +1. [Create](https://console.cloud.google.com/kubernetes/add) a container cluster with the default settings. Remember the cluster's name and zone, as you will need these later. + + +1. Configure `kubectl` to have access to the cluster. Replace `[YOUR_CLUSTER_NAME]` and `[YOUR_CLUSTER_ZONE]` with your cluster's name and zone respectively. +``` +$ gcloud container clusters get-credentials [YOUR_CLUSTER_NAME] --zone [YOUR_CLUSTER_ZONE] +``` + +1. Edit the `container_engine.yaml` file, and replace `GCLOUD_PROJECT`, `SERVICE_NAME`, and `SERVICE_CONFIG` with your Project ID and your Endpoints service's name and config ID respectively. + +1. Add a [Kubernetes service](https://kubernetes.io/docs/user-guide/services/) to the cluster you created. Note that Kubernetes services should not be confused with [Endpoints services](https://cloud.google.com/endpoints/docs/grpc). +``` +$ kubectl create -f container-engine.yaml +``` + +1. Get the external IP of your service. This may take a few minutes to be provisioned. +``` +$ kubectl get service +``` + +1. Use the client to test your Endpoints deployment. Replace `[YOUR_CLUSTER_IP_ADDRESS]` with your service's external IP address, and `[YOUR_API_KEY]` with a [valid Google Cloud Platform API key](https://support.google.com/cloud/answer/6158862?hl=en). +``` +$ node client.js -h [YOUR_CLUSTER_IP_ADDRESS]:80 -k [YOUR_API_KEY] +``` + +## Cleanup +If you do not intend to use the resources you created for this tutorial in the future, delete your [VM instances](https://console.cloud.google.com/compute/instances) and/or [container clusters](https://console.cloud.google.com/kubernetes/list) to prevent additional charges. + +## Troubleshooting +If you're having issues with this tutorial, here are some things to try: +- [Check](https://console.cloud.google.com/logs/viewer) your VM instance's/cluster's logs +- Make sure your Compute Engine instance's [firewall](https://console.cloud.google.com/networking/firewalls/list) permits TCP access to port 80 + +If those suggestions don't solve your problem, please [let us know](https://github.com/GoogleCloudPlatform/nodejs-docs-samples/issues) or [submit a PR](https://github.com/GoogleCloudPlatform/nodejs-docs-samples/pulls). \ No newline at end of file diff --git a/endpoints/getting-started-grpc/api_config.yaml b/endpoints/getting-started-grpc/api_config.yaml new file mode 100644 index 0000000000..143c0d0840 --- /dev/null +++ b/endpoints/getting-started-grpc/api_config.yaml @@ -0,0 +1,36 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# An example API configuration. +# +# Below, replace MY_PROJECT_ID with your Google Cloud Project ID. +# + +# The configuration schema is defined by service.proto file +# https://github.com/googleapis/googleapis/blob/master/google/api/service.proto +type: google.api.Service +config_version: 3 + +# +# Name of the service configuration. +# +name: hellogrpc.endpoints.MY_PROJECT_ID.cloud.goog + +# +# API title to appear in the user interface (Google Cloud Console). +# +title: Hello gRPC API +apis: +- name: helloworld.Greeter \ No newline at end of file diff --git a/endpoints/getting-started-grpc/client.js b/endpoints/getting-started-grpc/client.js new file mode 100644 index 0000000000..50c569a5b2 --- /dev/null +++ b/endpoints/getting-started-grpc/client.js @@ -0,0 +1,75 @@ +// Copyright 2017, Google, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +function makeGrpcRequest (API_KEY, HOST, GREETEE) { + // Uncomment these lines to set their values + // const API_KEY = 'YOUR_API_KEY'; + // const HOST = 'localhost:50051'; // The IP address of your endpoints host + // const GREETEE = 'world'; + + // Import required libraries + const grpc = require('grpc'); + const path = require('path'); + + // Load protobuf spec for an example API + const PROTO_PATH = path.join(__dirname, '/protos/helloworld.proto'); + const protoObj = grpc.load(PROTO_PATH).helloworld; + + // Create a client for the protobuf spec + const client = new protoObj.Greeter(HOST, grpc.credentials.createInsecure()); + + // Build gRPC request + const metadata = new grpc.Metadata(); + metadata.add('x-api-key', API_KEY); + + // Execute gRPC request + client.sayHello({ name: GREETEE }, metadata, (err, response) => { + if (err) { + console.error(err); + } + + if (response) { + console.log(response.message); + } + }); +} + +// The command-line program +const argv = require('yargs') + .usage('Usage: node $0 -k YOUR_API_KEY [-h YOUR_ENDPOINTS_HOST] [-g GREETEE_NAME]') + .option('apiKey', { + alias: 'k', + type: 'string', + global: true, + default: '' + }) + .option('host', { + alias: 'h', + type: 'string', + default: 'localhost:50051', + global: true + }) + .option('greetee', { + alias: 'g', + type: 'string', + default: 'world', + global: true + }) + .wrap(120) + .epilogue(`For more information, see https://cloud.google.com/endpoints/docs`) + .argv; + +makeGrpcRequest(argv.apiKey, argv.host, argv.greetee); diff --git a/endpoints/getting-started-grpc/container-engine.yaml b/endpoints/getting-started-grpc/container-engine.yaml new file mode 100644 index 0000000000..5c740545e4 --- /dev/null +++ b/endpoints/getting-started-grpc/container-engine.yaml @@ -0,0 +1,54 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: v1 +kind: Service +metadata: + name: grpc-hello +spec: + ports: + - port: 80 + targetPort: 9000 + protocol: TCP + name: http + selector: + app: grpc-hello + type: LoadBalancer +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: grpc-hello +spec: + replicas: 1 + template: + metadata: + labels: + app: grpc-hello + spec: + containers: + - name: esp + image: gcr.io/endpoints-release/endpoints-runtime:1 + args: [ + "-P", "9000", + "-a", "grpc://127.0.0.1:50051", + "-s", "SERVICE_NAME", + "-v", "SERVICE_CONFIG_ID", + ] + ports: + - containerPort: 9000 + - name: node-grpc-hello + image: gcr.io/GCLOUD_PROJECT/endpoints-example:1.0 + ports: + - containerPort: 50051 diff --git a/endpoints/getting-started-grpc/package.json b/endpoints/getting-started-grpc/package.json new file mode 100644 index 0000000000..64f4e0d589 --- /dev/null +++ b/endpoints/getting-started-grpc/package.json @@ -0,0 +1,20 @@ +{ + "name": "appengine-endpoints", + "description": "Endpoints Node.js gRPC sample for Google App Engine", + "version": "0.0.1", + "private": true, + "license": "Apache Version 2.0", + "author": "Google Inc.", + "engines": { + "node": ">=4.3.2" + }, + "scripts": { + "start": "node server.js", + "system-test": "ava system-test/*.test.js --timeout=30s" + }, + "dependencies": { + "body-parser": "1.16.0", + "express": "4.14.1", + "grpc": "^1.1.2" + } +} diff --git a/endpoints/getting-started-grpc/protos/helloworld.proto b/endpoints/getting-started-grpc/protos/helloworld.proto new file mode 100644 index 0000000000..0bee1fcfcf --- /dev/null +++ b/endpoints/getting-started-grpc/protos/helloworld.proto @@ -0,0 +1,53 @@ +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; +option objc_class_prefix = "HLW"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} diff --git a/endpoints/getting-started-grpc/server.js b/endpoints/getting-started-grpc/server.js new file mode 100644 index 0000000000..35bc11065f --- /dev/null +++ b/endpoints/getting-started-grpc/server.js @@ -0,0 +1,49 @@ +// Copyright 2017, Google, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const path = require('path'); +const PROTO_PATH = path.join(__dirname, '/protos/helloworld.proto'); + +const grpc = require('grpc'); +const helloProto = grpc.load(PROTO_PATH).helloworld; + +// Implement the SayHello RPC method. +function sayHello (call, callback) { + callback(null, { message: `Hello ${call.request.name}` }); +} + +// Start an RPC server to handle Greeter service requests +function startServer (PORT) { + const server = new grpc.Server(); + server.addProtoService(helloProto.Greeter.service, { sayHello: sayHello }); + server.bind(`0.0.0.0:${PORT}`, grpc.ServerCredentials.createInsecure()); + server.start(); +} + +// The command-line program +const argv = require('yargs') + .usage('Usage: node $0 [-p PORT]') + .option('port', { + alias: 'p', + type: 'number', + default: 50051, + global: true + }) + .wrap(120) + .epilogue(`For more information, see https://cloud.google.com/endpoints/docs`) + .argv; + +startServer(argv.port); diff --git a/endpoints/getting-started-grpc/system-test/endpoints.test.js b/endpoints/getting-started-grpc/system-test/endpoints.test.js new file mode 100644 index 0000000000..411ca7019c --- /dev/null +++ b/endpoints/getting-started-grpc/system-test/endpoints.test.js @@ -0,0 +1,53 @@ +// Copyright 2016, Google, Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +require(`../../../system-test/_setup`); + +const childProcess = require('child_process'); + +const clientCmd = `node client.js`; +const serverCmd = `node server.js`; + +const path = require('path'); +const cwd = path.join(__dirname, `..`); + +const API_KEY = process.env.ENDPOINTS_API_KEY; +const GCE_HOST = process.env.ENDPOINTS_GCE_HOST; +const GKE_HOST = process.env.ENDPOINTS_GKE_HOST; + +const delay = (mSec) => { + return new Promise((resolve) => setTimeout(resolve, mSec)); +}; + +test(`should request a greeting from a remote Compute Engine instance`, async (t) => { + const output = await runAsync(`${clientCmd} -h ${GCE_HOST} -k ${API_KEY}`, cwd); + t.regex(output, /Hello world/); +}); + +test(`should request a greeting from a remote Container Engine cluster`, async (t) => { + const output = await runAsync(`${clientCmd} -h ${GKE_HOST} -k ${API_KEY}`, cwd); + t.regex(output, /Hello world/); +}); + +test(`should request and handle a greeting locally`, async (t) => { + const PORT = 50051; + const server = childProcess.exec(`${serverCmd} -p ${PORT}`, { cwd: cwd }); + + await delay(1000); + console.log(`${clientCmd} -h localhost:${PORT} -k ${API_KEY}`); + const clientOutput = await runAsync(`${clientCmd} -h localhost:${PORT} -k ${API_KEY}`, cwd); + t.regex(clientOutput, /Hello world/); + server.kill(); +});