/* * Copyright 2018 Google LLC. * * 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. */ // Tracing code const { NodeTracerProvider } = require('@opentelemetry/node'); const { SimpleSpanProcessor } = require('@opentelemetry/tracing'); const { JaegerExporter } = require('@opentelemetry/exporter-jaeger'); const options = { serviceName: process.env.SERVICE_NAME ||'currencyservice', tags: [{key: 'ip', value: process.env.POD_IP}, {key: 'name', value: process.env.POD_NAME}, {key: 'node_name', value: process.env.NODE_NAME}], // optional host: process.env.JAEGER_HOST, port: process.env.JAEGER_PORT, // optional maxPacketSize: 65000 // optional } const exporter = new JaegerExporter(options); const provider = new NodeTracerProvider(); provider.addSpanProcessor(new SimpleSpanProcessor(exporter)); provider.register(); console.log("jaeger tracing initialized"); const path = require('path'); const grpc = require('@grpc/grpc-js'); const pino = require('pino'); 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 PORT = process.env.PORT; const shopProto = _loadProto(MAIN_PROTO_PATH).hipstershop; const healthProto = _loadProto(HEALTH_PROTO_PATH).grpc.health.v1; const logger = pino({ name: 'currencyservice-server', messageKey: 'message', changeLevelName: 'severity', useLevelLabels: true }); /** * 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 a stored JSON file * Uses public data from European Central Bank */ function _getCurrencyData (callback) { const data = require('./data/currency_conversion.json'); callback(data); } /** * Helper function that handles decimal/fractional carrying */ function _carry (amount) { const fractionSize = Math.pow(10, 9); amount.nanos += (amount.units % 1) * fractionSize; amount.units = Math.floor(amount.units) + Math.floor(amount.nanos / fractionSize); amount.nanos = amount.nanos % fractionSize; return amount; } /** * Lists the supported currencies */ function getSupportedCurrencies (call, callback) { logger.info('Getting supported currencies...'); _getCurrencyData((data) => { callback(null, {currency_codes: Object.keys(data)}); }); } /** * Converts between currencies */ function convert (call, callback) { logger.info('received conversion request'); try { _getCurrencyData((data) => { const request = call.request; // Convert: from_currency --> EUR const from = request.from; const euros = _carry({ units: from.units / data[from.currency_code], nanos: from.nanos / data[from.currency_code] }); euros.nanos = Math.round(euros.nanos); // Convert: EUR --> to_currency const result = _carry({ units: euros.units * data[request.to_code], nanos: euros.nanos * data[request.to_code] }); result.units = Math.floor(result.units); result.nanos = Math.floor(result.nanos); result.currency_code = request.to_code; logger.info(`conversion request successful`); callback(null, result); }); } catch (err) { logger.error(`conversion request failed: ${err}`); callback(err.message); } } /** * 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 */ function main () { logger.info(`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.bindAsync( `0.0.0.0:${PORT}`, grpc.ServerCredentials.createInsecure(), (err, result) => { if (err) { logger.error(`err: ${err}`) } else { logger.info(`CurrencyService gRPC server started on port ${PORT}`); server.start(); } }, ); } main();