diff --git a/kubernetes-manifests/currencyservice.yaml b/kubernetes-manifests/currencyservice.yaml index b08121c..b344b1a 100644 --- a/kubernetes-manifests/currencyservice.yaml +++ b/kubernetes-manifests/currencyservice.yaml @@ -30,13 +30,11 @@ spec: - name: grpc containerPort: 7000 readinessProbe: - periodSeconds: 5 - tcpSocket: - port: 7000 + exec: + command: ["/bin/grpc_health_probe", "-addr=:7000"] livenessProbe: - periodSeconds: 5 - tcpSocket: - port: 7000 + exec: + command: ["/bin/grpc_health_probe", "-addr=:7000"] resources: requests: cpu: 100m diff --git a/kubernetes-manifests/paymentservice.yaml b/kubernetes-manifests/paymentservice.yaml index 8664d3c..8f28410 100644 --- a/kubernetes-manifests/paymentservice.yaml +++ b/kubernetes-manifests/paymentservice.yaml @@ -29,13 +29,11 @@ spec: ports: - containerPort: 50051 readinessProbe: - periodSeconds: 5 - tcpSocket: - port: 50051 + exec: + command: ["/bin/grpc_health_probe", "-addr=:50051"] livenessProbe: - periodSeconds: 5 - tcpSocket: - port: 50051 + exec: + command: ["/bin/grpc_health_probe", "-addr=:50051"] resources: requests: cpu: 100m diff --git a/pb/grpc/health/v1/health.proto b/pb/grpc/health/v1/health.proto new file mode 100644 index 0000000..4b4677b --- /dev/null +++ b/pb/grpc/health/v1/health.proto @@ -0,0 +1,43 @@ +// Copyright 2015 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The canonical version of this proto can be found at +// https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto + +syntax = "proto3"; + +package grpc.health.v1; + +option csharp_namespace = "Grpc.Health.V1"; +option go_package = "google.golang.org/grpc/health/grpc_health_v1"; +option java_multiple_files = true; +option java_outer_classname = "HealthProto"; +option java_package = "io.grpc.health.v1"; + +message HealthCheckRequest { + string service = 1; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + } + ServingStatus status = 1; +} + +service Health { + rpc Check(HealthCheckRequest) returns (HealthCheckResponse); +} diff --git a/src/currencyservice/Dockerfile b/src/currencyservice/Dockerfile index a87d34f..769e464 100644 --- a/src/currencyservice/Dockerfile +++ b/src/currencyservice/Dockerfile @@ -1,4 +1,7 @@ FROM node:8 +RUN GRPC_HEALTH_PROBE_VERSION=v0.1.0-alpha.1 && \ + wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ + chmod +x /bin/grpc_health_probe WORKDIR /usr/src/app COPY package*.json ./ RUN npm install --only=production diff --git a/src/currencyservice/client.js b/src/currencyservice/client.js index 7d2f059..2d367e0 100644 --- a/src/currencyservice/client.js +++ b/src/currencyservice/client.js @@ -22,7 +22,7 @@ const grpc = require('grpc'); const leftPad = require('left-pad'); const PROTO_PATH = path.join(__dirname, './proto/demo.proto'); -const PORT = 31337; +const PORT = 7000; const shopProto = grpc.load(PROTO_PATH).hipstershop; const client = new shopProto.CurrencyService(`localhost:${PORT}`, @@ -49,7 +49,7 @@ client.getSupportedCurrencies({}, (err, response) => { } }); -client.convert(request, function (err, response) { +client.convert(request, (err, response) => { if (err) { console.error(`Error in convert: ${err}`); } else { diff --git a/src/currencyservice/genproto.sh b/src/currencyservice/genproto.sh index ba89804..a9609fd 100755 --- a/src/currencyservice/genproto.sh +++ b/src/currencyservice/genproto.sh @@ -14,7 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/bin/bash -e # protos are loaded dynamically for node, simply copies over the proto. -mkdir -p proto && \ -cp ../../pb/demo.proto proto \ No newline at end of file +mkdir -p proto +cp -r ../../pb/* ./proto diff --git a/src/currencyservice/package.json b/src/currencyservice/package.json index 5b2f859..1d89148 100644 --- a/src/currencyservice/package.json +++ b/src/currencyservice/package.json @@ -2,17 +2,25 @@ "name": "grpc-currency-service", "version": "0.1.0", "description": "A gRPC currency conversion microservice", - "repository": "TODO", + "repository": "https://github.com/GoogleCloudPlatform/microservices-demo", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "lint": "semistandard *.js" + }, "license": "Apache-2.0", "dependencies": { "@google-cloud/debug-agent": "^2.6.0", "@google-cloud/profiler": "^0.1.14", "@google-cloud/trace-agent": "^2.11.0", + "@grpc/proto-loader": "^0.3.0", "async": "^1.5.2", "google-protobuf": "^3.0.0", "grpc": "^1.0.0", "left-pad": "^1.3.0", "request": "^2.87.0", "xml2js": "^0.4.19" + }, + "devDependencies": { + "semistandard": "^12.0.1" } } diff --git a/src/currencyservice/proto/demo.proto b/src/currencyservice/proto/demo.proto index 0c3fdf2..11b8c29 100644 --- a/src/currencyservice/proto/demo.proto +++ b/src/currencyservice/proto/demo.proto @@ -108,9 +108,9 @@ message ShipOrderResponse { } message Address { - string street_address_1 = 1; - string street_address_2 = 2; - string city= 3; + string street_address = 1; + string city = 2; + string state = 3; string country = 4; int32 zip_code = 5; } @@ -202,21 +202,9 @@ message SendOrderConfirmationRequest { // -------------Checkout service----------------- service CheckoutService { - rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse) {} rpc PlaceOrder(PlaceOrderRequest) returns (PlaceOrderResponse) {} } -message CreateOrderRequest { - string user_id = 1; - string user_currency = 2; - Address address = 3; -} - -message CreateOrderResponse { - repeated OrderItem items = 1; - Money shipping_cost = 2; -} - message PlaceOrderRequest { string user_id = 1; string user_currency = 2; @@ -229,3 +217,26 @@ message PlaceOrderRequest { message PlaceOrderResponse { OrderResult order = 1; } + +// ------------Ads service------------------ + +service AdsService { + rpc GetAds(AdsRequest) returns (AdsResponse) {} +} + +message AdsRequest { + // List of important key words from the current page describing the context. + repeated string context_keys = 1; +} + +message AdsResponse { + repeated Ad ads = 1; +} + +message Ad { + // url to redirect to when an ad is clicked. + string redirect_url = 1; + + // short advertisement text to display. + string text = 2; +} diff --git a/src/currencyservice/proto/grpc/health/v1/health.proto b/src/currencyservice/proto/grpc/health/v1/health.proto new file mode 100644 index 0000000..4b4677b --- /dev/null +++ b/src/currencyservice/proto/grpc/health/v1/health.proto @@ -0,0 +1,43 @@ +// Copyright 2015 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The canonical version of this proto can be found at +// https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto + +syntax = "proto3"; + +package grpc.health.v1; + +option csharp_namespace = "Grpc.Health.V1"; +option go_package = "google.golang.org/grpc/health/grpc_health_v1"; +option java_multiple_files = true; +option java_outer_classname = "HealthProto"; +option java_package = "io.grpc.health.v1"; + +message HealthCheckRequest { + string service = 1; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + } + ServingStatus status = 1; +} + +service Health { + rpc Check(HealthCheckRequest) returns (HealthCheckResponse); +} diff --git a/src/currencyservice/server.js b/src/currencyservice/server.js index e6b07c8..69f7c3c 100644 --- a/src/currencyservice/server.js +++ b/src/currencyservice/server.js @@ -16,27 +16,49 @@ require('@google-cloud/profiler').start({ serviceContext: { - service: 'currencyservice', - version: '1.0.0' + service: 'currencyservice', + version: '1.0.0' } }); - require('@google-cloud/trace-agent').start(); - require('@google-cloud/debug-agent').start({ +require('@google-cloud/trace-agent').start(); +require('@google-cloud/debug-agent').start({ serviceContext: { service: 'currencyservice', version: 'VERSION' } -}) +}); const path = require('path'); const grpc = require('grpc'); const request = require('request'); const xml2js = require('xml2js'); +const protoLoader = require('@grpc/proto-loader'); + +const MAIN_PROTO_PATH = path.join(__dirname, './proto/demo.proto'); +const HEALTH_PROTO_PATH = path.join(__dirname, './proto/grpc/health/v1/health.proto'); -const PROTO_PATH = path.join(__dirname, './proto/demo.proto'); const PORT = 7000; const DATA_URL = 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'; -const shopProto = grpc.load(PROTO_PATH).hipstershop; + +const shopProto = _loadProto(MAIN_PROTO_PATH).hipstershop; +const healthProto = _loadProto(HEALTH_PROTO_PATH).grpc.health.v1; + +/** + * Helper function that loads a protobuf file. + */ +function _loadProto (path) { + const packageDefinition = protoLoader.loadSync( + path, + { + keepCase: true, + longs: String, + enums: String, + defaults: true, + oneofs: true + } + ); + return grpc.loadPackageDefinition(packageDefinition); +} /** * Helper function that gets currency data from an XML webpage @@ -116,8 +138,8 @@ function convert (call, callback) { nanos: euros.nanos * data[request.to_code] }); - result.units = Math.floor(result.units) - result.nanos = Math.floor(result.nanos) + result.units = Math.floor(result.units); + result.nanos = Math.floor(result.nanos); result.currency_code = request.to_code; console.log(`conversion request successful`); @@ -130,6 +152,13 @@ function convert (call, callback) { } } +/** + * Endpoint for health checks + */ +function check (call, callback) { + callback(null, { status: 'SERVING' }); +} + /** * Starts an RPC server that receives requests for the * CurrencyConverter service at the sample server port @@ -138,6 +167,7 @@ function main () { console.log(`Starting gRPC server on port ${PORT}...`); const server = new grpc.Server(); server.addService(shopProto.CurrencyService.service, {getSupportedCurrencies, convert}); + server.addService(healthProto.Health.service, {check}); server.bind(`0.0.0.0:${PORT}`, grpc.ServerCredentials.createInsecure()); server.start(); } diff --git a/src/paymentservice/Dockerfile b/src/paymentservice/Dockerfile index 79ee243..abe0eba 100644 --- a/src/paymentservice/Dockerfile +++ b/src/paymentservice/Dockerfile @@ -1,4 +1,7 @@ FROM node:8 +RUN GRPC_HEALTH_PROBE_VERSION=v0.1.0-alpha.1 && \ + wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ + chmod +x /bin/grpc_health_probe WORKDIR /usr/src/app diff --git a/src/paymentservice/charge.js b/src/paymentservice/charge.js index e11d0c1..a0151ed 100644 --- a/src/paymentservice/charge.js +++ b/src/paymentservice/charge.js @@ -16,26 +16,26 @@ const cardValidator = require('simple-card-validator'); const uuid = require('uuid/v4'); class CreditCardError extends Error { - constructor(message) { + constructor (message) { super(message); this.code = 400; // Invalid argument error } } class InvalidCreditCard extends CreditCardError { - constructor(cardType) { + constructor (cardType) { super(`Credit card info is invalid`); } } class UnacceptedCreditCard extends CreditCardError { - constructor(cardType) { + constructor (cardType) { super(`Sorry, we cannot process ${cardType} credit cards. Only VISA or MasterCard is accepted.`); } } class ExpiredCreditCard extends CreditCardError { - constructor(number, month, year) { + constructor (number, month, year) { super(`Your credit card (ending ${number.substr(-4)}) expired on ${month}/${year}`); } } @@ -46,34 +46,29 @@ class ExpiredCreditCard extends CreditCardError { * @param {*} request * @return transaction_id - a random uuid v4. */ -module.exports = function charge(request) { +module.exports = function charge (request) { const { amount, credit_card: creditCard } = request; const cardNumber = creditCard.credit_card_number; const cardInfo = cardValidator(cardNumber); const { card_type: cardType, - valid, - cvv_length: cvvLength, + valid } = cardInfo.getCardDetails(); - if (!valid) - throw new InvalidCreditCard(); + if (!valid) { throw new InvalidCreditCard(); } // Only VISA and mastercard is accepted, other card types (AMEX, dinersclub) will // throw UnacceptedCreditCard error. - if (!(cardType === 'visa' || cardType == 'mastercard')) - throw new UnacceptedCreditCard(cardType); + if (!(cardType === 'visa' || cardType === 'mastercard')) { throw new UnacceptedCreditCard(cardType); } // Also validate expiration is > today. const currentMonth = new Date().getMonth() + 1; const currentYear = new Date().getFullYear(); const { credit_card_expiration_year: year, credit_card_expiration_month: month } = creditCard; - if ((currentYear * 12 + currentMonth) > (year * 12 + month)) - throw new ExpiredCreditCard(cardNumber.replace('-', ''), month, year); + if ((currentYear * 12 + currentMonth) > (year * 12 + month)) { throw new ExpiredCreditCard(cardNumber.replace('-', ''), month, year); } console.log(`Transaction processed: ${cardType} ending ${cardNumber.substr(-4)} \ - Amount: ${amount.currency_code}${amount.units}.${amount.nanos}`) - - return { transaction_id: uuid() } -} + Amount: ${amount.currency_code}${amount.units}.${amount.nanos}`); + return { transaction_id: uuid() }; +}; diff --git a/src/paymentservice/genproto.sh b/src/paymentservice/genproto.sh index 2529fd9..a9609fd 100755 --- a/src/paymentservice/genproto.sh +++ b/src/paymentservice/genproto.sh @@ -14,9 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/bin/bash -e - # protos are loaded dynamically for node, simply copies over the proto. - -mkdir -p proto && \ -cp ../../pb/demo.proto proto +mkdir -p proto +cp -r ../../pb/* ./proto diff --git a/src/paymentservice/index.js b/src/paymentservice/index.js index 6f32be8..661bc82 100644 --- a/src/paymentservice/index.js +++ b/src/paymentservice/index.js @@ -17,25 +17,25 @@ 'use strict'; require('@google-cloud/profiler').start({ - serviceContext: { - service: 'paymentservice', - version: '1.0.0' - } - }); + serviceContext: { + service: 'paymentservice', + version: '1.0.0' + } +}); require('@google-cloud/trace-agent').start(); require('@google-cloud/debug-agent').start({ - serviceContext: { - service: 'paymentservice', - version: 'VERSION' - } - }) + serviceContext: { + service: 'paymentservice', + version: 'VERSION' + } +}); +const path = require('path'); const HipsterShopServer = require('./server'); const PORT = process.env['PORT']; -const PROTO_PATH = __dirname + '/proto/demo.proto'; +const PROTO_PATH = path.join(__dirname, '/proto/'); const server = new HipsterShopServer(PROTO_PATH, PORT); server.listen(); - diff --git a/src/paymentservice/package.json b/src/paymentservice/package.json index 4aa6c29..c62033b 100644 --- a/src/paymentservice/package.json +++ b/src/paymentservice/package.json @@ -2,9 +2,11 @@ "name": "paymentservice", "version": "0.0.1", "description": "Payment Microservice demo", + "repository": "https://github.com/GoogleCloudPlatform/microservices-demo", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "lint": "semistandard *.js" }, "author": "Jonathan Lui", "license": "ISC", @@ -16,5 +18,8 @@ "grpc": "^1.12.3", "simple-card-validator": "^1.1.0", "uuid": "^3.2.1" + }, + "devDependencies": { + "semistandard": "^12.0.1" } } diff --git a/src/paymentservice/proto/demo.proto b/src/paymentservice/proto/demo.proto index 0c3fdf2..11b8c29 100644 --- a/src/paymentservice/proto/demo.proto +++ b/src/paymentservice/proto/demo.proto @@ -108,9 +108,9 @@ message ShipOrderResponse { } message Address { - string street_address_1 = 1; - string street_address_2 = 2; - string city= 3; + string street_address = 1; + string city = 2; + string state = 3; string country = 4; int32 zip_code = 5; } @@ -202,21 +202,9 @@ message SendOrderConfirmationRequest { // -------------Checkout service----------------- service CheckoutService { - rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse) {} rpc PlaceOrder(PlaceOrderRequest) returns (PlaceOrderResponse) {} } -message CreateOrderRequest { - string user_id = 1; - string user_currency = 2; - Address address = 3; -} - -message CreateOrderResponse { - repeated OrderItem items = 1; - Money shipping_cost = 2; -} - message PlaceOrderRequest { string user_id = 1; string user_currency = 2; @@ -229,3 +217,26 @@ message PlaceOrderRequest { message PlaceOrderResponse { OrderResult order = 1; } + +// ------------Ads service------------------ + +service AdsService { + rpc GetAds(AdsRequest) returns (AdsResponse) {} +} + +message AdsRequest { + // List of important key words from the current page describing the context. + repeated string context_keys = 1; +} + +message AdsResponse { + repeated Ad ads = 1; +} + +message Ad { + // url to redirect to when an ad is clicked. + string redirect_url = 1; + + // short advertisement text to display. + string text = 2; +} diff --git a/src/paymentservice/proto/grpc/health/v1/health.proto b/src/paymentservice/proto/grpc/health/v1/health.proto new file mode 100644 index 0000000..4b4677b --- /dev/null +++ b/src/paymentservice/proto/grpc/health/v1/health.proto @@ -0,0 +1,43 @@ +// Copyright 2015 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The canonical version of this proto can be found at +// https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto + +syntax = "proto3"; + +package grpc.health.v1; + +option csharp_namespace = "Grpc.Health.V1"; +option go_package = "google.golang.org/grpc/health/grpc_health_v1"; +option java_multiple_files = true; +option java_outer_classname = "HealthProto"; +option java_package = "io.grpc.health.v1"; + +message HealthCheckRequest { + string service = 1; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + } + ServingStatus status = 1; +} + +service Health { + rpc Check(HealthCheckRequest) returns (HealthCheckResponse); +} diff --git a/src/paymentservice/server.js b/src/paymentservice/server.js index 410757c..ebce323 100644 --- a/src/paymentservice/server.js +++ b/src/paymentservice/server.js @@ -12,17 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. +const path = require('path'); const grpc = require('grpc'); const protoLoader = require('@grpc/proto-loader'); const charge = require('./charge'); class HipsterShopServer { - constructor(protoFile, port = HipsterShopServer.DEFAULT_PORT) { + constructor (protoRoot, port = HipsterShopServer.DEFAULT_PORT) { this.port = port; + this.packages = { + hipsterShop: this.loadProto(path.join(protoRoot, 'demo.proto')), + health: this.loadProto(path.join(protoRoot, 'grpc/health/v1/health.proto')) + }; + this.server = new grpc.Server(); - this.loadProto(protoFile); + this.loadAllProtos(protoRoot); } /** @@ -30,10 +36,10 @@ class HipsterShopServer { * @param {*} call { ChargeRequest } * @param {*} callback fn(err, ChargeResponse) */ - static ChargeServiceHandler(call, callback) { + static ChargeServiceHandler (call, callback) { try { - console.log(`PaymentService#Charge invoked with request ${JSON.stringify(call.request)}`) - const response = charge(call.request) + console.log(`PaymentService#Charge invoked with request ${JSON.stringify(call.request)}`); + const response = charge(call.request); callback(null, response); } catch (err) { console.warn(err); @@ -41,13 +47,17 @@ class HipsterShopServer { } } - listen() { + static CheckHandler (call, callback) { + callback(null, { status: 'SERVING' }); + } + + listen () { this.server.bind(`0.0.0.0:${this.port}`, grpc.ServerCredentials.createInsecure()); console.log(`PaymentService grpc server listening on ${this.port}`); this.server.start(); } - loadProto(path) { + loadProto (path) { const packageDefinition = protoLoader.loadSync( path, { @@ -55,21 +65,28 @@ class HipsterShopServer { longs: String, enums: String, defaults: true, - oneofs: true, - }, + oneofs: true + } ); - const protoDescriptor = grpc.loadPackageDefinition(packageDefinition); - const hipsterShopPackage = protoDescriptor.hipstershop; - - this.addProtoService(hipsterShopPackage.PaymentService.service); + return grpc.loadPackageDefinition(packageDefinition); } - addProtoService(service) { + loadAllProtos (protoRoot) { + const hipsterShopPackage = this.packages.hipsterShop.hipstershop; + const healthPackage = this.packages.health.grpc.health.v1; + this.server.addService( - service, + hipsterShopPackage.PaymentService.service, { - charge: HipsterShopServer.ChargeServiceHandler.bind(this), - }, + charge: HipsterShopServer.ChargeServiceHandler.bind(this) + } + ); + + this.server.addService( + healthPackage.Health.service, + { + check: HipsterShopServer.CheckHandler.bind(this) + } ); } }