[WIP] Port RobotShop to CF
This commit is contained in:
119
CF/README.md
Normal file
119
CF/README.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# RobotShop on PCF [WIP]
|
||||
|
||||
## Requirements
|
||||
|
||||
The following tiles up and running in the PCF foundation:
|
||||
|
||||
- [Pivotal Application Service](https://network.pivotal.io/products/elastic-runtime)
|
||||
- [RabbitMQ for PCF](https://network.pivotal.io/products/p-rabbitmq/)
|
||||
- [Redis for PCF](https://network.pivotal.io/products/p-redis/)
|
||||
- [MySQL for PCF](https://network.pivotal.io/products/pivotal-mysql/)
|
||||
- [MongoDB Enterprise Service for PCF](https://network.pivotal.io/products/mongodb-enterprise-service/)
|
||||
- [Instana Microservices Application Monitoring for PCF](https://network.pivotal.io/products/instana-microservices-application-monitoring/)
|
||||
|
||||
## Create an organization and a space
|
||||
|
||||
```sh
|
||||
cf create-org stan
|
||||
cf create-space -o stan robotshop
|
||||
```
|
||||
|
||||
## Ensure routes are available
|
||||
|
||||
The following routes must be available for use:
|
||||
|
||||
- `web.<domain>`
|
||||
- `ratings.<domain>`
|
||||
- `cart.<domain>`
|
||||
- `catalogue.<domain>`
|
||||
- `shipping.<domain>`
|
||||
- `user.<domain>`
|
||||
- `payment.<domain>`
|
||||
- `dispatch.<domain>`
|
||||
|
||||
## Set up the services
|
||||
|
||||
```sh
|
||||
cf target -o stan -s robotshop
|
||||
|
||||
cf cs p.mysql db-small mysql
|
||||
cf cs mongodb-odb replica_set_small mongodb
|
||||
cf cs p.redis cache-small redis
|
||||
cf cs p.rabbitmq single-node-3.7 rabbitmq
|
||||
```
|
||||
|
||||
## First app push
|
||||
|
||||
RobotShop relies on specific binding names between services and apps, so we first push the apps without creating instances (all instance counts in the manifest are `0`).
|
||||
|
||||
From the root of the repo:
|
||||
|
||||
```sh
|
||||
cf push -f CF/manifest.yml
|
||||
```
|
||||
|
||||
## Bind services
|
||||
|
||||
```sh
|
||||
cf bind-service mongo-init mongodb --binding-name catalogue_database
|
||||
cf bind-service ratings mysql --binding-name ratings_database
|
||||
|
||||
cf bind-service catalogue mongodb --binding-name catalog_database
|
||||
|
||||
cf bind-service cart redis --binding-name cart_cache
|
||||
|
||||
cf bind-service shipping mysql --binding-name shipping_database
|
||||
|
||||
cf bind-service user redis --binding-name users_cache
|
||||
cf bind-service user mongodb --binding-name users_database
|
||||
|
||||
cf bind-service payment rabbitmq --binding-name dispatch_queue
|
||||
|
||||
cf bind-service dispatch rabbitmq --binding-name dispatch_queue
|
||||
```
|
||||
|
||||
## Init MongoDB
|
||||
|
||||
```sh
|
||||
cf run-task mongo-init 'node init-db.js' --name "Init MongoDB"
|
||||
```
|
||||
|
||||
## Init MySQL
|
||||
|
||||
This one is not automated yet, as the `mysql-init` task app chokes on the `init.sql`.
|
||||
Something that "works" is to `bosh ssh` on the MySQL and run the database init via commandline (`mysql` is on the path) using the credentials one finds by doing `cf env shipping`.
|
||||
First import `init.sql` and then the following:
|
||||
|
||||
```sql
|
||||
CREATE DATABASE ratings
|
||||
DEFAULT CHARACTER SET 'utf8';
|
||||
|
||||
USE ratings;
|
||||
|
||||
CREATE TABLE ratings (
|
||||
sku varchar(80) NOT NULL,
|
||||
avg_rating DECIMAL(3, 2) NOT NULL,
|
||||
rating_count INT NOT NULL,
|
||||
PRIMARY KEY (sku)
|
||||
) ENGINE=InnoDB;
|
||||
```
|
||||
|
||||
The above is the content of the [ratings sql init script](../mysql/20-ratings.sql) minus the unnecessary user privs.
|
||||
|
||||
## Configure EUM
|
||||
|
||||
Create a website in Instana.
|
||||
Edit the `web/static/eum.html` file accordingly, specifically replacing the values of the `reportingUrl` and `key` ienums.
|
||||
|
||||
## Spin up the containers
|
||||
|
||||
```sh
|
||||
cf cart -i 1 web
|
||||
cf cart -i 1 ratings
|
||||
cf scale -i 1 cart
|
||||
cf scale -i 1 catalogue
|
||||
cf scale -i 1 shipping
|
||||
cf scale -i 1 user
|
||||
cf scale -i 1 payment
|
||||
cf scale -i 1 dispatch
|
||||
```
|
113
CF/manifest.yml
Normal file
113
CF/manifest.yml
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
applications:
|
||||
|
||||
# cf cs p.mysql db-small mysql
|
||||
# cf cs mongodb-odb replica_set_small mongodb
|
||||
# cf cs p.redis cache-small redis
|
||||
# cf cs p.rabbitmq single-node-3.7 rabbitmq
|
||||
|
||||
# cf bind-service mongo-init mongodb --binding-name catalogue_database
|
||||
# cf run-task mongo-init 'node init-db.js' --name "Init MongoDB"
|
||||
|
||||
### DOES NOT WORK, IMPORTED MYSEL BY HAND LOGGING INTO MYSQL INSTANCE CUZ YOLO
|
||||
### cf bind-service mysql-init mysql --binding-name shipping_database
|
||||
### cf run-task mysql-init 'node init-db.js' --name "Init MySQL"
|
||||
|
||||
# cf bind-service ratings mysql --binding-name ratings_database
|
||||
# cf scale -i 1 ratings
|
||||
|
||||
# cf bind-service catalogue mongodb --binding-name catalog_database
|
||||
# cf scale -i 1 catalogue
|
||||
|
||||
# cf bind-service cart redis --binding-name cart_cache
|
||||
# cf scale -i 1 cart
|
||||
|
||||
# cf bind-service shipping mysql --binding-name shipping_database
|
||||
# cf scale -i 1 shipping
|
||||
|
||||
# cf bind-service user redis --binding-name users_cache
|
||||
# cf bind-service user mongodb --binding-name users_database
|
||||
# cf scale -i 1 user
|
||||
|
||||
# cf bind-service payment rabbitmq --binding-name dispatch_queue
|
||||
# cf scale -i 1 payment
|
||||
|
||||
# cf bind-service dispatch rabbitmq --binding-name dispatch_queue
|
||||
# cf scale -i 1 dispatch
|
||||
|
||||
|
||||
###
|
||||
### "Task" applications to setup services, see https://docs.cloudfoundry.org/devguide/using-tasks.html
|
||||
###
|
||||
|
||||
- name: mongo-init
|
||||
path: ../CF/mongo-init
|
||||
memory: 128M
|
||||
no-route: true # It's a CF Task, does not need a route
|
||||
health-check-type: process
|
||||
instances: 0
|
||||
|
||||
# - name: mysql-init
|
||||
# path: ../CF/mysql-init
|
||||
# memory: 512M
|
||||
# no-route: true # It's a CF Task, does not need a route
|
||||
# health-check-type: process
|
||||
# instances: 0
|
||||
|
||||
###
|
||||
### Productive apps
|
||||
###
|
||||
|
||||
- name: cart
|
||||
path: ../cart/
|
||||
memory: 128M
|
||||
env:
|
||||
CATALOGUE_HTTPS: true
|
||||
instances: 0
|
||||
|
||||
- name: catalogue
|
||||
path: ../catalogue/
|
||||
instances: 0
|
||||
|
||||
- name: dispatch
|
||||
path: ../dispatch/src
|
||||
memory: 128M
|
||||
health-check-type: process
|
||||
env:
|
||||
GOPACKAGENAME: github.com/instana/robot-shop/dispatch
|
||||
instances: 0
|
||||
|
||||
- name: payment
|
||||
path: ../payment/
|
||||
memory: 256M
|
||||
command: python payment.py
|
||||
env:
|
||||
AUTOWRAPT_BOOTSTRAP: instana
|
||||
instances: 0
|
||||
|
||||
- name: ratings
|
||||
path: ../ratings/app
|
||||
memory: 128MB
|
||||
env:
|
||||
CATALOGUE_USE_HTTPS: true
|
||||
instances: 0
|
||||
|
||||
- name: shipping
|
||||
path: ../shipping/target/shipping-1.0-jar-with-dependencies.jar
|
||||
memory: 1G
|
||||
instances: 0
|
||||
|
||||
- name: user
|
||||
path: ../user/
|
||||
memory: 128MB
|
||||
instances: 1
|
||||
|
||||
- name: web
|
||||
path: ../web/
|
||||
memory: 128M
|
||||
buildpacks:
|
||||
- https://github.com/cloudfoundry/nginx-buildpack.git
|
||||
env:
|
||||
INSTANA_EUM_KEY: OT8yjQ_eSPm84a4owlq5Hw
|
||||
INSTANA_EUM_REPORTING_URL: https://eum-release-fullstack-0-us-west-2.instana.io
|
||||
instances: 0
|
109
CF/mongo-init/init-db.js
Normal file
109
CF/mongo-init/init-db.js
Normal file
@@ -0,0 +1,109 @@
|
||||
/*jshint esversion: 6 */
|
||||
|
||||
const mongoClient = require('mongodb').MongoClient;
|
||||
|
||||
var mongoURL = 'mongodb://mongodb:27017/catalogue';
|
||||
|
||||
if (process.env.VCAP_SERVICES) {
|
||||
connectionDetails = null;
|
||||
|
||||
for (let [key, value] of Object.entries(JSON.parse(process.env.VCAP_SERVICES))) {
|
||||
connectionDetails = value.find(function(binding) {
|
||||
return 'catalogue_database' == binding.binding_name && binding.credentials;
|
||||
}).credentials;
|
||||
|
||||
if (connectionDetails) {
|
||||
mongoURL = connectionDetails.uri;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (process.env.MONGO_URL) {
|
||||
mongoURL = process.env.MONGO_URL;
|
||||
}
|
||||
|
||||
if (!mongoURL) {
|
||||
throw new Error('MongoDB connection data missing');
|
||||
}
|
||||
|
||||
mongoClient.connect(mongoURL, (error, db) => {
|
||||
if(error) {
|
||||
console.log('Cannot connect to MongoDB', error);
|
||||
process.exit(42);
|
||||
} else {
|
||||
console.log('Creating products collection');
|
||||
|
||||
products = db.collection('products');
|
||||
|
||||
products.insertMany([
|
||||
{sku: 'HAL-1', name: 'HAL', description: 'Sorry Dave, I cant do that', price: 2001, instock: 2, categories: ['Artificial Intelligence']},
|
||||
{sku: 'PB-1', name: 'Positronic Brain', description: 'Highly advanced sentient processing unit with the laws of robotics burned in', price: 200, instock: 0, categories: ['Artificial Intelligence']},
|
||||
{sku: 'ROB-1', name: 'Robbie', description: 'Large mechanical workhorse, crude but effective. Comes in handy when you are lost in space', price: 1200, instock: 12, categories: ['Robot']},
|
||||
{sku: 'EVE-1', name: 'Eve', description: 'Extraterrestrial Vegetation Evaluator', price: 5000, instock: 10, categories: ['Robot']},
|
||||
{sku: 'C3P0', name: 'C3P0', description: 'Protocol android', price: 700, instock: 1, categories: ['Robot']},
|
||||
{sku: 'R2D2', name: 'R2D2', description: 'R2 maintenance robot and secret messenger. Help me Obi Wan', price: 1400, instock: 1, categories: ['Robot']},
|
||||
{sku: 'K9', name: 'K9', description: 'Time travelling companion at heel', price: 300, instock: 12, categories: ['Robot']},
|
||||
{sku: 'RD-10', name: 'Kryten', description: 'Red Drawf crew member', price: 700, instock: 5, categories: ['Robot']},
|
||||
{sku: 'HHGTTG', name: 'Marvin', description: 'Marvin, your paranoid android. Brain the size of a planet', price: 42, instock: 48, categories: ['Robot']},
|
||||
{sku: 'STAN-1', name: 'Stan', description: 'APM guru', price: 50, instock: 1000, categories: ['Robot', 'Artificial Intelligence']},
|
||||
{sku: 'STNG', name: 'Mr Data', description: 'Could be R. Daneel Olivaw? Protype positronic brain android', price: 1000, instock: 0, categories: ['Robot']}
|
||||
]);
|
||||
|
||||
//
|
||||
// Users
|
||||
//
|
||||
console.log('Creating users collection');
|
||||
|
||||
const users = db.collection('users');
|
||||
|
||||
console.log('Starting users import');
|
||||
|
||||
users.insertMany([
|
||||
{name: 'user', password: 'password', email: 'user@me.com'},
|
||||
{name: 'stan', password: 'bigbrain', email: 'stan@instana.com'}
|
||||
]);
|
||||
|
||||
// unique index on the name
|
||||
users.createIndex(
|
||||
{name: 1},
|
||||
{unique: true}
|
||||
);
|
||||
|
||||
console.log('Users imported');
|
||||
|
||||
console.log('Creating catalogue collection');
|
||||
|
||||
const catalogue = db.collection('catalogue');
|
||||
|
||||
console.log('Starting catalogue import');
|
||||
|
||||
catalogue.insertMany([
|
||||
{sku: 'HAL-1', name: 'HAL', description: 'Sorry Dave, I cant do that', price: 2001, instock: 2, categories: ['Artificial Intelligence']},
|
||||
{sku: 'PB-1', name: 'Positronic Brain', description: 'Highly advanced sentient processing unit with the laws of robotics burned in', price: 200, instock: 0, categories: ['Artificial Intelligence']},
|
||||
{sku: 'ROB-1', name: 'Robbie', description: 'Large mechanical workhorse, crude but effective. Comes in handy when you are lost in space', price: 1200, instock: 12, categories: ['Robot']},
|
||||
{sku: 'EVE-1', name: 'Eve', description: 'Extraterrestrial Vegetation Evaluator', price: 5000, instock: 10, categories: ['Robot']},
|
||||
{sku: 'C3P0', name: 'C3P0', description: 'Protocol android', price: 700, instock: 1, categories: ['Robot']},
|
||||
{sku: 'R2D2', name: 'R2D2', description: 'R2 maintenance robot and secret messenger. Help me Obi Wan', price: 1400, instock: 1, categories: ['Robot']},
|
||||
{sku: 'K9', name: 'K9', description: 'Time travelling companion at heel', price: 300, instock: 12, categories: ['Robot']},
|
||||
{sku: 'RD-10', name: 'Kryten', description: 'Red Drawf crew member', price: 700, instock: 5, categories: ['Robot']},
|
||||
{sku: 'HHGTTG', name: 'Marvin', description: 'Marvin, your paranoid android. Brain the size of a planet', price: 42, instock: 48, categories: ['Robot']},
|
||||
{sku: 'STAN-1', name: 'Stan', description: 'APM guru', price: 50, instock: 1000, categories: ['Robot', 'Artificial Intelligence']},
|
||||
{sku: 'STNG', name: 'Mr Data', description: 'Could be R. Daneel Olivaw? Protype positronic brain android', price: 1000, instock: 0, categories: ['Robot']}
|
||||
]);
|
||||
|
||||
// full text index for searching
|
||||
catalogue.createIndex({
|
||||
name: "text",
|
||||
description: "text"
|
||||
});
|
||||
|
||||
// unique index for product sku
|
||||
catalogue.createIndex(
|
||||
{ sku: 1 },
|
||||
{ unique: true }
|
||||
);
|
||||
|
||||
console.log('Products imported');
|
||||
|
||||
console.log('All done');
|
||||
}
|
||||
});
|
14
CF/mongo-init/package.json
Normal file
14
CF/mongo-init/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "mongodb-init",
|
||||
"version": "1.0.0",
|
||||
"description": "Init RobotShop's MongoDB database",
|
||||
"main": "init-db.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "SteveW",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"mongodb": "^2.2.33"
|
||||
}
|
||||
}
|
34
CF/mysql-init/init-db.js
Normal file
34
CF/mysql-init/init-db.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/*jshint esversion: 6 */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
if (!process.env.VCAP_SERVICES) {
|
||||
throw new Error('No services bound (VCAP_SERVICES env var not found)');
|
||||
}
|
||||
|
||||
var connectionDetails;
|
||||
|
||||
for (let [key, value] of Object.entries(JSON.parse(process.env.VCAP_SERVICES))) {
|
||||
connectionDetails = value.find(function(binding) {
|
||||
return 'shipping_database' == binding.binding_name && binding.credentials;
|
||||
}).credentials;
|
||||
|
||||
if (connectionDetails) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!connectionDetails) {
|
||||
throw new Error('Cannot extract MySQL connection details: ' + process.env.VCAP_SERVICES);
|
||||
}
|
||||
|
||||
require('mysql-import').config({
|
||||
host: connectionDetails.hostname,
|
||||
user: connectionDetails.username,
|
||||
password: connectionDetails.password,
|
||||
database: connectionDetails.name,
|
||||
onerror: err=>console.log(err.message)
|
||||
}).import('init.sql')
|
||||
.then(() => console.log('Database imported'));
|
||||
|
||||
}());
|
147
CF/mysql-init/init.sql
Normal file
147
CF/mysql-init/init.sql
Normal file
File diff suppressed because one or more lines are too long
14
CF/mysql-init/package.json
Normal file
14
CF/mysql-init/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "mysql-init",
|
||||
"version": "1.0.0",
|
||||
"description": "Init RobotShop's MySQL database",
|
||||
"main": "init-db.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "SteveW",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"mysql-import": "^2.0.2"
|
||||
}
|
||||
}
|
@@ -69,7 +69,7 @@ To deploy the Instana agent to Kubernetes, just use the [helm](https://github.co
|
||||
|
||||
If you are having difficulties get helm running with your K8s install it is most likely due to RBAC, most K8s now have RBAC enabled by default. Therefore helm requires a [service account](https://github.com/helm/helm/blob/master/docs/rbac.md) to have permission to do stuff.
|
||||
|
||||
## Acessing the Store
|
||||
## Accessing the Store
|
||||
If you are running the store locally via *docker-compose up* then, the store front is available on localhost port 8080 [http://localhost:8080](http://localhost:8080/)
|
||||
|
||||
If you are running the store on Kubernetes via minikube then, to make the store front accessible edit the *web* service definition and change the type to *NodePort* and add a port entry *nodePort: 30080*.
|
||||
|
@@ -16,6 +16,6 @@
|
||||
"pino": "^5.10.8",
|
||||
"express-pino-logger": "^4.0.0",
|
||||
"pino-pretty": "^2.5.0",
|
||||
"instana-nodejs-sensor": "^1.28.0"
|
||||
"@instana/collector": "^1.28.0"
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,6 @@
|
||||
const instana = require('instana-nodejs-sensor');
|
||||
/*jshint esversion: 6 */
|
||||
|
||||
const instana = require('@instana/collector');
|
||||
// init tracing
|
||||
// MUST be done before loading anything else!
|
||||
instana({
|
||||
@@ -16,8 +18,63 @@ const expPino = require('express-pino-logger');
|
||||
|
||||
var redisConnected = false;
|
||||
|
||||
var redisHost = process.env.REDIS_HOST || 'redis'
|
||||
var catalogueHost = process.env.CATALOGUE_HOST || 'catalogue'
|
||||
var redisHost;
|
||||
var redisPassword;
|
||||
var redisPort;
|
||||
|
||||
if (process.env.VCAP_SERVICES) {
|
||||
connectionDetails = null;
|
||||
|
||||
console.log('Env var \'VCAP_SERVICES\' found, scanning for \'cart_cache\' service binding');
|
||||
|
||||
for (let [key, value] of Object.entries(JSON.parse(process.env.VCAP_SERVICES))) {
|
||||
try {
|
||||
binding = value.find(function(binding) {
|
||||
return 'cart_cache' == binding.binding_name && binding.credentials;
|
||||
});
|
||||
|
||||
if (!binding) {
|
||||
continue;
|
||||
}
|
||||
|
||||
connectionDetails = binding.credentials;
|
||||
|
||||
if (connectionDetails) {
|
||||
redisHost = connectionDetails.host;
|
||||
redisPassword = connectionDetails.password;
|
||||
redisPort = connectionDetails.port;
|
||||
|
||||
console.log('Redis URI for \'cart_cache\' service binding found in \'VCAP_SERVICES\'');
|
||||
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Cannot process key \'' + key + '\' of \'VCAP_SERVICES\'', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
} else if (process.env.REDIS_HOST) {
|
||||
redisHost = process.env.REDIS_HOST;
|
||||
redisPort = 6379;
|
||||
|
||||
console.log('Redis host found in \'REDIS_HOST\': ' + redisHost);
|
||||
} else {
|
||||
redisHost = 'redis';
|
||||
redisPort = 6379;
|
||||
|
||||
console.log('Using default Redis host and port');
|
||||
}
|
||||
|
||||
var catalogueHost = process.env.CATALOGUE_HOST || 'catalogue';
|
||||
if (process.env.VCAP_APPLICATION) {
|
||||
vcapApplication = JSON.parse(process.env.VCAP_APPLICATION);
|
||||
|
||||
applicationName = vcapApplication.application_name;
|
||||
applicationUri = vcapApplication.application_uris[0];
|
||||
catalogueHost = 'catalogue' + applicationUri.substring(applicationUri.indexOf('.'));
|
||||
}
|
||||
|
||||
const catalogueUrl = (process.env.CATALOGUE_HTTPS ? 'https' : 'http') + '://' + catalogueHost;
|
||||
|
||||
const logger = pino({
|
||||
level: 'info',
|
||||
@@ -183,7 +240,7 @@ app.get('/update/:id/:sku/:qty', (req, res) => {
|
||||
// check quantity
|
||||
var qty = parseInt(req.params.qty);
|
||||
if(isNaN(qty)) {
|
||||
req.log.warn('quanity not a number');
|
||||
req.log.warn('quantity not a number');
|
||||
res.status(400).send('quantity must be a number');
|
||||
return;
|
||||
} else if(qty < 0) {
|
||||
@@ -328,7 +385,7 @@ function calcTax(total) {
|
||||
|
||||
function getProduct(sku) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request('http://' + catalogueHost + ':8080/product/' + sku, (err, res, body) => {
|
||||
request(catalogueUrl + '/product/' + sku, (err, res, body) => {
|
||||
if(err) {
|
||||
reject(err);
|
||||
} else if(res.statusCode != 200) {
|
||||
@@ -355,9 +412,15 @@ function saveCart(id, cart) {
|
||||
});
|
||||
}
|
||||
|
||||
if (!redisHost) {
|
||||
throw new Error('Redis connection data missing');
|
||||
}
|
||||
|
||||
// connect to Redis
|
||||
var redisClient = redis.createClient({
|
||||
host: redisHost
|
||||
host: redisHost,
|
||||
password: redisPassword,
|
||||
port: redisPort
|
||||
});
|
||||
|
||||
redisClient.on('error', (e) => {
|
||||
@@ -369,7 +432,7 @@ redisClient.on('ready', (r) => {
|
||||
});
|
||||
|
||||
// fire it up!
|
||||
const port = process.env.CART_SERVER_PORT || '8080';
|
||||
const port = process.env.SERVER_PORT || '8080';
|
||||
app.listen(port, () => {
|
||||
logger.info('Started on port', port);
|
||||
});
|
||||
|
@@ -15,6 +15,6 @@
|
||||
"pino": "^5.10.8",
|
||||
"express-pino-logger": "^4.0.0",
|
||||
"pino-pretty": "^2.5.0",
|
||||
"instana-nodejs-sensor": "^1.28.0"
|
||||
"@instana/collector": "^1.28.0"
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,6 @@
|
||||
const instana = require('instana-nodejs-sensor');
|
||||
/*jshint esversion: 6 */
|
||||
|
||||
const instana = require('@instana/collector');
|
||||
// init tracing
|
||||
// MUST be done before loading anything else!
|
||||
instana({
|
||||
@@ -8,7 +10,6 @@ instana({
|
||||
});
|
||||
|
||||
const mongoClient = require('mongodb').MongoClient;
|
||||
const mongoObjectID = require('mongodb').ObjectID;
|
||||
const bodyParser = require('body-parser');
|
||||
const express = require('express');
|
||||
const pino = require('pino');
|
||||
@@ -24,8 +25,7 @@ const expLogger = expPino({
|
||||
});
|
||||
|
||||
// MongoDB
|
||||
var db;
|
||||
var collection;
|
||||
var productsCollection;
|
||||
var mongoConnected = false;
|
||||
|
||||
const app = express();
|
||||
@@ -52,7 +52,7 @@ app.get('/health', (req, res) => {
|
||||
// all products
|
||||
app.get('/products', (req, res) => {
|
||||
if(mongoConnected) {
|
||||
collection.find({}).toArray().then((products) => {
|
||||
productsCollection.find({}).toArray().then((products) => {
|
||||
res.json(products);
|
||||
}).catch((e) => {
|
||||
req.log.error('ERROR', e);
|
||||
@@ -67,7 +67,7 @@ app.get('/products', (req, res) => {
|
||||
// product by SKU
|
||||
app.get('/product/:sku', (req, res) => {
|
||||
if(mongoConnected) {
|
||||
collection.findOne({sku: req.params.sku}).then((product) => {
|
||||
productsCollection.findOne({sku: req.params.sku}).then((product) => {
|
||||
req.log.info('product', product);
|
||||
if(product) {
|
||||
res.json(product);
|
||||
@@ -87,7 +87,7 @@ app.get('/product/:sku', (req, res) => {
|
||||
// products in a category
|
||||
app.get('/products/:cat', (req, res) => {
|
||||
if(mongoConnected) {
|
||||
collection.find({ categories: req.params.cat }).sort({ name: 1 }).toArray().then((products) => {
|
||||
productsCollection.find({ categories: req.params.cat }).sort({ name: 1 }).toArray().then((products) => {
|
||||
if(products) {
|
||||
res.json(products);
|
||||
} else {
|
||||
@@ -106,7 +106,7 @@ app.get('/products/:cat', (req, res) => {
|
||||
// all categories
|
||||
app.get('/categories', (req, res) => {
|
||||
if(mongoConnected) {
|
||||
collection.distinct('categories').then((categories) => {
|
||||
productsCollection.distinct('categories').then((categories) => {
|
||||
res.json(categories);
|
||||
}).catch((e) => {
|
||||
req.log.error('ERROR', e);
|
||||
@@ -121,7 +121,7 @@ app.get('/categories', (req, res) => {
|
||||
// search name and description
|
||||
app.get('/search/:text', (req, res) => {
|
||||
if(mongoConnected) {
|
||||
collection.find({ '$text': { '$search': req.params.text }}).toArray().then((hits) => {
|
||||
productsCollection.find({ '$text': { '$search': req.params.text }}).toArray().then((hits) => {
|
||||
res.json(hits);
|
||||
}).catch((e) => {
|
||||
req.log.error('ERROR', e);
|
||||
@@ -136,13 +136,56 @@ app.get('/search/:text', (req, res) => {
|
||||
// set up Mongo
|
||||
function mongoConnect() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var mongoURL = process.env.MONGO_URL || 'mongodb://mongodb:27017/catalogue';
|
||||
mongoClient.connect(mongoURL, (error, _db) => {
|
||||
var mongoURL;
|
||||
|
||||
if (process.env.VCAP_SERVICES) {
|
||||
connectionDetails = null;
|
||||
|
||||
console.log('Env var \'VCAP_SERVICES\' found, scanning for \'catalogue_database\' service binding');
|
||||
|
||||
for (let [key, value] of Object.entries(JSON.parse(process.env.VCAP_SERVICES))) {
|
||||
try {
|
||||
binding = value.find(function(binding) {
|
||||
return 'catalogue_database' == binding.binding_name && binding.credentials;
|
||||
});
|
||||
|
||||
if (!binding) {
|
||||
continue;
|
||||
}
|
||||
|
||||
connectionDetails = binding.credentials;
|
||||
|
||||
if (connectionDetails) {
|
||||
mongoURL = connectionDetails.uri;
|
||||
|
||||
console.log('MongoDB URI for \'catalogue_database\' service binding found in \'VCAP_SERVICES\'');
|
||||
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Cannot process key \'' + key + '\' of \'VCAP_SERVICES\'', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
} else if (process.env.MONGO_URL) {
|
||||
mongoURL = process.env.MONGO_URL;
|
||||
|
||||
console.log('MongoDB URI found in \'MONGO_URL\': ' + mongoURL);
|
||||
} else {
|
||||
mongoURL = 'mongodb://mongodb:27017/catalogue';
|
||||
|
||||
console.log('Using default MongoDB URI');
|
||||
}
|
||||
|
||||
if (!mongoURL) {
|
||||
throw new Error('MongoDB connection data missing');
|
||||
}
|
||||
|
||||
mongoClient.connect(mongoURL, (error, db) => {
|
||||
if(error) {
|
||||
reject(error);
|
||||
} else {
|
||||
db = _db;
|
||||
collection = db.collection('products');
|
||||
productsCollection = db.collection('products');
|
||||
resolve('connected');
|
||||
}
|
||||
});
|
||||
@@ -155,7 +198,7 @@ function mongoLoop() {
|
||||
mongoConnected = true;
|
||||
logger.info('MongoDB connected');
|
||||
}).catch((e) => {
|
||||
logger.error('ERROR', e);
|
||||
logger.error('An error occurred', e);
|
||||
setTimeout(mongoLoop, 2000);
|
||||
});
|
||||
}
|
||||
|
@@ -8,12 +8,15 @@ import (
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/streadway/amqp"
|
||||
"github.com/instana/go-sensor"
|
||||
ot "github.com/opentracing/opentracing-go"
|
||||
ext "github.com/opentracing/opentracing-go/ext"
|
||||
otlog "github.com/opentracing/opentracing-go/log"
|
||||
|
||||
"github.com/cloudfoundry-community/go-cfenv"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -160,7 +163,40 @@ func main() {
|
||||
if !ok {
|
||||
amqpHost = "rabbitmq"
|
||||
}
|
||||
amqpUri = fmt.Sprintf("amqp://guest:guest@%s:5672/", amqpHost)
|
||||
amqpUser, ok := os.LookupEnv("AMQP_USER")
|
||||
if !ok {
|
||||
amqpHost = "guest"
|
||||
}
|
||||
amqpPwd, ok := os.LookupEnv("AMQP_PWD")
|
||||
if !ok {
|
||||
amqpPwd = "guest"
|
||||
}
|
||||
amqpPort, ok := os.LookupEnv("AMQP_PORT")
|
||||
if !ok {
|
||||
amqpPort = "5672"
|
||||
}
|
||||
|
||||
amqpUri = fmt.Sprintf("amqp://%s:%s@%s:%s/", amqpUser, amqpPwd , amqpHost, amqpPort)
|
||||
|
||||
if cfenv.IsRunningOnCF() {
|
||||
appEnv, err := cfenv.Current()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
services := appEnv.Services
|
||||
for _, service := range services {
|
||||
for _, serviceBinding := range service {
|
||||
bindingName, ok := serviceBinding.CredentialString("binding_name")
|
||||
|
||||
if ok {
|
||||
if strings.Compare(bindingName, "dispatch_queue") == 0 {
|
||||
amqpUri, _ = serviceBinding.CredentialString("uri")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get error threshold from environment
|
||||
errorPercent = 0
|
||||
|
@@ -6,12 +6,14 @@ import uuid
|
||||
import json
|
||||
import requests
|
||||
import traceback
|
||||
import re
|
||||
import opentracing as ot
|
||||
import opentracing.ext.tags as tags
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
from flask import jsonify
|
||||
from rabbitmq import Publisher
|
||||
from cfenv import AppEnv
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@@ -41,9 +43,20 @@ def pay(id):
|
||||
span.log_kv({'id': id})
|
||||
span.log_kv({'cart': cart})
|
||||
|
||||
user_service_uri = 'http://user:8080'
|
||||
cart_service_uri = 'http://cart:8080'
|
||||
if 'VCAP_SERVICES' in os.environ:
|
||||
env = AppEnv()
|
||||
application_name = env.app['application_name']
|
||||
application_uri = env.app['application_uris'][0]
|
||||
hostname = re.sub(r'^\w+\.', '', application_uri)
|
||||
|
||||
user_service_uri = 'https://user.{hostname}'.format(hostname=hostname)
|
||||
cart_service_uri = 'https://cart.{hostname}'.format(hostname=hostname)
|
||||
|
||||
# check user exists
|
||||
try:
|
||||
req = requests.get('http://{user}:8080/check/{id}'.format(user=USER, id=id))
|
||||
req = requests.get('{uri}/check/{id}'.format(uri=user_service_uri, id=id), verify=False)
|
||||
except requests.exceptions.RequestException as err:
|
||||
app.logger.error(err)
|
||||
return str(err), 500
|
||||
@@ -78,9 +91,10 @@ def pay(id):
|
||||
# add to order history
|
||||
if not anonymous_user:
|
||||
try:
|
||||
req = requests.post('http://{user}:8080/order/{id}'.format(user=USER, id=id),
|
||||
req = requests.post('{user_uri}/order/{id}'.format(user_uri=user_service_uri, id=id),
|
||||
data=json.dumps({'orderid': orderid, 'cart': cart}),
|
||||
headers={'Content-Type': 'application/json'})
|
||||
headers={'Content-Type': 'application/json'},
|
||||
verify=False)
|
||||
app.logger.info('order history returned {}'.format(req.status_code))
|
||||
except requests.exceptions.RequestException as err:
|
||||
app.logger.error(err)
|
||||
@@ -88,7 +102,7 @@ def pay(id):
|
||||
|
||||
# delete cart
|
||||
try:
|
||||
req = requests.delete('http://{cart}:8080/cart/{id}'.format(cart=CART, id=id));
|
||||
req = requests.delete('{cart_uri}/cart/{id}'.format(cart_uri=cart_service_uri, id=id), verify=False);
|
||||
app.logger.info('cart delete returned {}'.format(req.status_code))
|
||||
except requests.exceptions.RequestException as err:
|
||||
app.logger.error(err)
|
||||
@@ -115,9 +129,10 @@ def queueOrder(order):
|
||||
tscope.span.log_kv({'orderid': order.get('orderid')})
|
||||
with ot.tracer.start_active_span('rabbitmq', child_of=tscope.span,
|
||||
tags={
|
||||
# 'address': Publisher.HOST,
|
||||
'messaging_bus.address': publisher._uri,
|
||||
'exchange': Publisher.EXCHANGE,
|
||||
'sort': 'publish',
|
||||
'address': Publisher.HOST,
|
||||
'key': Publisher.ROUTING_KEY
|
||||
}
|
||||
) as scope:
|
||||
|
@@ -2,19 +2,30 @@ import json
|
||||
import pika
|
||||
import os
|
||||
|
||||
from cfenv import AppEnv
|
||||
|
||||
class Publisher:
|
||||
HOST = os.getenv('AMQP_HOST', 'rabbitmq')
|
||||
VIRTUAL_HOST = '/'
|
||||
EXCHANGE='robot-shop'
|
||||
TYPE='direct'
|
||||
ROUTING_KEY = 'orders'
|
||||
|
||||
def __init__(self, logger):
|
||||
self._logger = logger
|
||||
self._params = pika.connection.ConnectionParameters(
|
||||
host=self.HOST,
|
||||
virtual_host=self.VIRTUAL_HOST,
|
||||
credentials=pika.credentials.PlainCredentials('guest', 'guest'))
|
||||
|
||||
if 'VCAP_SERVICES' in os.environ:
|
||||
env = AppEnv()
|
||||
amqp_service = env.get_service(binding_name='dispatch_queue')
|
||||
|
||||
self._uri = amqp_service.credentials.get('uri')
|
||||
else:
|
||||
self._uri = 'ampq://{user}:{pwd}@{host}:{port}/{vhost}'.format(
|
||||
host=os.getenv('AMQP_HOST', 'rabbitmq'),
|
||||
port=os.getenv('AMQP_PORT', '5672'),
|
||||
vhost='/',
|
||||
user=os.getenv('AMQP_USER', 'guest'),
|
||||
pwd=os.getenv('AMQP_PWD', 'guest'))
|
||||
|
||||
self._params = pika.URLParameters(self._uri)
|
||||
self._conn = None
|
||||
self._channel = None
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
Flask
|
||||
requests
|
||||
pika
|
||||
cfenv
|
||||
instana
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Use composer to install dependencies
|
||||
FROM composer AS build
|
||||
|
||||
COPY composer.json /app/
|
||||
COPY app/composer.json /app/
|
||||
|
||||
RUN composer install
|
||||
|
||||
@@ -13,7 +13,7 @@ FROM php:7.2-apache
|
||||
RUN docker-php-ext-install pdo_mysql
|
||||
|
||||
# relax permissions on status
|
||||
COPY status.conf /etc/apache2/mods-available/status.conf
|
||||
COPY app/status.conf /etc/apache2/mods-available/status.conf
|
||||
# Enable Apache mod_rewrite and status
|
||||
RUN a2enmod rewrite && a2enmod status
|
||||
|
||||
@@ -22,5 +22,4 @@ WORKDIR /var/www/html
|
||||
# copy dependencies from previous step
|
||||
COPY --from=build /app/vendor/ /var/www/html/vendor/
|
||||
|
||||
COPY html/ /var/www/html
|
||||
|
||||
COPY app/html/*.php /var/www/html
|
5
ratings/app/.bp-config/options.json
Normal file
5
ratings/app/.bp-config/options.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"PHP_VERSION": "{PHP_72_LATEST}",
|
||||
"LIBDIR": "htdocs/vendor",
|
||||
"COMPOSER_VENDOR_DIR": "htdocs/vendor"
|
||||
}
|
2
ratings/app/.bp-config/php/php.ini.d/pdo.ini
Normal file
2
ratings/app/.bp-config/php/php.ini.d/pdo.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
extension=pdo.so
|
||||
extension=pdo_mysql.so
|
2
ratings/app/.bp-config/php/php.ini.d/zzz-instana.ini
Normal file
2
ratings/app/.bp-config/php/php.ini.d/zzz-instana.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
#extension=instana-x64-1.3.2.so
|
||||
instana.socket=10.0.4.8:16816
|
@@ -2,5 +2,5 @@
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule api/(.*)$ api.php?request=$1 [QSA,NC,L]
|
||||
RewriteRule (.*)$ api.php?request=$1 [QSA,NC,L]
|
||||
</IfModule>
|
220
ratings/app/api.php
Normal file
220
ratings/app/api.php
Normal file
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
require_once 'API.class.php';
|
||||
|
||||
use Monolog\Logger;
|
||||
|
||||
class RatingsAPI extends API {
|
||||
public function __construct($request, $origin) {
|
||||
parent::__construct($request);
|
||||
// Logging
|
||||
$this->logger = new Logger('RatingsAPI');
|
||||
$this->logger->pushHandler($this->logHandler);
|
||||
}
|
||||
|
||||
protected function health() {
|
||||
$this->logger->info('health OK');
|
||||
return 'OK';
|
||||
}
|
||||
|
||||
protected function dump() {
|
||||
$data = array();
|
||||
$data['method'] = $this->method;
|
||||
$data['verb'] = $this->verb;
|
||||
$data = array_merge($data, array('args' => $this->args));
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
// GET ratings/fetch/sku
|
||||
protected function fetch() {
|
||||
if($this->method == 'GET' && isset($this->verb) && count($this->args) == 0) {
|
||||
$sku = $this->verb;
|
||||
if(! $this->_checkSku($sku)) {
|
||||
throw new Exception("$sku not found", 404);
|
||||
}
|
||||
$data = $this->_getRating($sku);
|
||||
return $data;
|
||||
} else {
|
||||
$this->logger->warn('fetch rating - bad request');
|
||||
throw new Exception('Bad request: fetch', 400);
|
||||
}
|
||||
}
|
||||
|
||||
// POST ratings/rate/sku/score
|
||||
protected function rate() {
|
||||
if($this->method == 'POST' && isset($this->verb) && count($this->args) == 1) {
|
||||
$sku = $this->verb;
|
||||
$score = intval($this->args[0]);
|
||||
$score = min(max(1, $score), 5);
|
||||
|
||||
if(! $this->_checkSku($sku)) {
|
||||
throw new Exception("$sku not found", 404);
|
||||
}
|
||||
|
||||
$rating = $this->_getRating($sku);
|
||||
if($rating['avg_rating'] == 0) {
|
||||
// not rated yet
|
||||
$this->_insertRating($sku, $score);
|
||||
} else {
|
||||
// iffy maths
|
||||
$newAvg = (($rating['avg_rating'] * $rating['rating_count']) + $score) / ($rating['rating_count'] + 1);
|
||||
$this->_updateRating($sku, $newAvg, $rating['rating_count'] + 1);
|
||||
}
|
||||
} else {
|
||||
$this->logger->warn('set rating - bad request');
|
||||
throw new Exception('Bad request: ' . implode($this->args), 400);
|
||||
}
|
||||
|
||||
return 'OK';
|
||||
}
|
||||
|
||||
private function _getRating($sku) {
|
||||
$db = $this->_dbConnect();
|
||||
if($db) {
|
||||
$stmt = $db->prepare('select avg_rating, rating_count from ratings where sku = ?');
|
||||
if($stmt->execute(array($sku))) {
|
||||
$data = $stmt->fetch();
|
||||
if($data) {
|
||||
// for some reason avg_rating is return as a string
|
||||
$data['avg_rating'] = floatval($data['avg_rating']);
|
||||
return $data;
|
||||
} else {
|
||||
// nicer to return an empty record than throw 404
|
||||
return array('avg_rating' => 0, 'rating_count' => 0);
|
||||
}
|
||||
} else {
|
||||
$this->logger->error('failed to query data');
|
||||
throw new Exception('Failed to query data', 500);
|
||||
}
|
||||
} else {
|
||||
$this->logger->error('database connection error');
|
||||
throw new Exception('Database connection error', 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function _updateRating($sku, $score, $count) {
|
||||
$db = $this->_dbConnect();
|
||||
if($db) {
|
||||
$stmt = $db->prepare('update ratings set avg_rating = ?, rating_count = ? where sku = ?');
|
||||
if(! $stmt->execute(array($score, $count, $sku))) {
|
||||
$this->logger->error('failed to update rating');
|
||||
throw new Exception('Failed to update data', 500);
|
||||
}
|
||||
} else {
|
||||
$this->logger->error('database connection error');
|
||||
throw new Exception('Database connection error', 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function _insertRating($sku, $score) {
|
||||
$db = $this->_dbConnect();
|
||||
if($db) {
|
||||
$stmt = $db->prepare('insert into ratings(sku, avg_rating, rating_count) values(?, ?, ?)');
|
||||
if(! $stmt->execute(array($sku, $score, 1))) {
|
||||
$this->logger->error('failed to insert data');
|
||||
throw new Exception('Failed to insert data', 500);
|
||||
}
|
||||
} else {
|
||||
$this->logger->error('database connection error');
|
||||
throw new Exception('Database connection error', 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function _dbConnect() {
|
||||
$dsn = getenv('PDO_URL') ? getenv('PDO_URL') : 'mysql:host=mysql;dbname=ratings;charset=utf8mb4';
|
||||
$username = 'ratings';
|
||||
$password = 'iloveit';
|
||||
|
||||
if($vcapServicesJson = getenv('VCAP_SERVICES')) {
|
||||
$vcapServices = json_decode($vcapServicesJson, true);
|
||||
|
||||
foreach ($vcapServices as $key => $value) {
|
||||
foreach ($value as $serviceBinding){
|
||||
if ($serviceBinding['binding_name'] == 'ratings_database') {
|
||||
$credentials = $serviceBinding['credentials'];
|
||||
$dsn = 'mysql:host=' . $credentials['hostname'] . ';port=' . $credentials['port'] . ';dbname=' . $credentials['name'];
|
||||
$username = $credentials['username'];
|
||||
$password = $credentials['password'];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$opt = array(
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false
|
||||
);
|
||||
|
||||
$db = false;
|
||||
try {
|
||||
$db = new PDO($dsn, $username, $password, $opt);
|
||||
} catch (PDOException $e) {
|
||||
$msg = $e->getMessage();
|
||||
$this->logger->error("Database error $msg");
|
||||
$db = false;
|
||||
}
|
||||
|
||||
return $db;
|
||||
}
|
||||
|
||||
private function _endsWith($subject, $token) {
|
||||
$tokenLength = strlen($token);
|
||||
if ($tokenLength == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (substr($subject, -$tokenLength) === $token);
|
||||
}
|
||||
|
||||
// check sku exists in product catalogue
|
||||
private function _checkSku($sku) {
|
||||
$url = getenv('CATALOGUE_URL') ? getenv('CATALOGUE_URL') : 'http://catalogue:8080/';
|
||||
|
||||
if ($vcapApplicationJson = getenv('VCAP_APPLICATION')) {
|
||||
$vcapApplication = json_decode($vcapApplicationJson, true);
|
||||
$applicationName = $vcapApplication['application_name'];
|
||||
$applicationUri = $vcapApplication['application_uris'][0];
|
||||
|
||||
$count = 1;
|
||||
$url = str_replace($applicationName, "catalogue", $applicationUri, $count);
|
||||
if (!$this->_endsWith($url, '/')) {
|
||||
$url = (getenv('CATALOGUE_USE_HTTPS') ? 'https' : 'http') . '://' . $url . '/';
|
||||
}
|
||||
}
|
||||
|
||||
$url = $url . 'product/' . $sku;
|
||||
|
||||
$opt = array(
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
);
|
||||
$curl = curl_init($url);
|
||||
curl_setopt_array($curl, $opt);
|
||||
|
||||
$data = curl_exec($curl);
|
||||
if(! $data) {
|
||||
$this->logger->error('failed to connect to catalogue');
|
||||
throw new Exception('Failed to connect to catalogue at: ' . $url, 500);
|
||||
}
|
||||
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
|
||||
$this->logger->info("catalogue status $status");
|
||||
|
||||
curl_close($curl);
|
||||
|
||||
return $status == 200;
|
||||
}
|
||||
}
|
||||
|
||||
if(!array_key_exists('HTTP_ORIGIN', $_SERVER)) {
|
||||
$_SERVER['HTTP_ORIGIN'] = $_SERVER['SERVER_NAME'];
|
||||
}
|
||||
|
||||
try {
|
||||
$API = new RatingsAPI($_REQUEST['request'], $_SERVER['HTTP_ORIGIN']);
|
||||
echo $API->processAPI();
|
||||
} catch(Exception $e) {
|
||||
echo json_encode(Array('error' => $e->getMessage()));
|
||||
}
|
||||
?>
|
143
ratings/app/composer.lock
generated
Normal file
143
ratings/app/composer.lock
generated
Normal file
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "8511edda340e04039f72f12feb7f3171",
|
||||
"packages": [
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "1.24.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Seldaek/monolog.git",
|
||||
"reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266",
|
||||
"reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"psr/log": "~1.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/log-implementation": "1.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
|
||||
"doctrine/couchdb": "~1.0@dev",
|
||||
"graylog2/gelf-php": "~1.0",
|
||||
"jakub-onderka/php-parallel-lint": "0.9",
|
||||
"php-amqplib/php-amqplib": "~2.4",
|
||||
"php-console/php-console": "^3.1.3",
|
||||
"phpunit/phpunit": "~4.5",
|
||||
"phpunit/phpunit-mock-objects": "2.3.0",
|
||||
"ruflin/elastica": ">=0.90 <3.0",
|
||||
"sentry/sentry": "^0.13",
|
||||
"swiftmailer/swiftmailer": "^5.3|^6.0"
|
||||
},
|
||||
"suggest": {
|
||||
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
|
||||
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
|
||||
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
|
||||
"ext-mongo": "Allow sending log messages to a MongoDB server",
|
||||
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
|
||||
"mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
|
||||
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
|
||||
"php-console/php-console": "Allow sending log messages to Google Chrome",
|
||||
"rollbar/rollbar": "Allow sending log messages to Rollbar",
|
||||
"ruflin/elastica": "Allow sending log messages to an Elastic Search server",
|
||||
"sentry/sentry": "Allow sending log messages to a Sentry server"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Monolog\\": "src/Monolog"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jordi Boggiano",
|
||||
"email": "j.boggiano@seld.be",
|
||||
"homepage": "http://seld.be"
|
||||
}
|
||||
],
|
||||
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
|
||||
"homepage": "http://github.com/Seldaek/monolog",
|
||||
"keywords": [
|
||||
"log",
|
||||
"logging",
|
||||
"psr-3"
|
||||
],
|
||||
"time": "2018-11-05T09:00:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/log",
|
||||
"version": "1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/log.git",
|
||||
"reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
|
||||
"reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Log\\": "Psr/Log/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "http://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for logging libraries",
|
||||
"homepage": "https://github.com/php-fig/log",
|
||||
"keywords": [
|
||||
"log",
|
||||
"psr",
|
||||
"psr-3"
|
||||
],
|
||||
"time": "2018-11-20T15:27:04+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": []
|
||||
}
|
BIN
ratings/app/php/lib/php/extensions/instana-x64-1.3.2.so
Normal file
BIN
ratings/app/php/lib/php/extensions/instana-x64-1.3.2.so
Normal file
Binary file not shown.
39
shipping/shipping.iml
Normal file
39
shipping/shipping.iml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
|
||||
<output url="file://$MODULE_DIR$/target/classes" />
|
||||
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Maven: com.sparkjava:spark-core:2.7.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.13" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-server:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:3.1.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-http:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-util:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-io:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-webapp:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-xml:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-servlet:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-security:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-server:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-common:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-client:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-client:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-servlet:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-api:9.4.8.v20171121" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.slf4j:slf4j-simple:1.7.25" level="project" />
|
||||
<orderEntry type="library" name="Maven: c3p0:c3p0:0.9.1.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: mysql:mysql-connector-java:5.1.45" level="project" />
|
||||
<orderEntry type="library" name="Maven: commons-dbutils:commons-dbutils:1.7" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.google.code.gson:gson:2.8.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpclient:4.5.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.httpcomponents:httpcore:4.4.9" level="project" />
|
||||
<orderEntry type="library" name="Maven: commons-logging:commons-logging:1.2" level="project" />
|
||||
<orderEntry type="library" name="Maven: commons-codec:commons-codec:1.10" level="project" />
|
||||
</component>
|
||||
</module>
|
@@ -1,5 +1,6 @@
|
||||
package org.steveww.spark;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.mchange.v2.c3p0.ComboPooledDataSource;
|
||||
import spark.Spark;
|
||||
|
||||
@@ -16,48 +17,75 @@ import org.apache.http.params.HttpParams;
|
||||
import org.apache.commons.dbutils.QueryRunner;
|
||||
import org.apache.commons.dbutils.handlers.MapListHandler;
|
||||
import org.apache.commons.dbutils.DbUtils;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.Types;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Main {
|
||||
private static String CART_URL = null;
|
||||
private static String JDBC_URL = null;
|
||||
private static Logger logger = LoggerFactory.getLogger(Main.class);
|
||||
private static ComboPooledDataSource cpds = null;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
|
||||
private static final ComboPooledDataSource CPDS = new ComboPooledDataSource();
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Get ENV configuration values
|
||||
CART_URL = String.format("http://%s/shipping/", System.getenv("CART_ENDPOINT") != null ? System.getenv("CART_ENDPOINT") : "cart");
|
||||
JDBC_URL = String.format("jdbc:mysql://%s/cities?useSSL=false&autoReconnect=true", System.getenv("DB_HOST") != null ? System.getenv("DB_HOST") : "mysql");
|
||||
final String cartUrl;
|
||||
final String jdbcUrl;
|
||||
|
||||
final boolean isCloudFoundry = System.getenv("VCAP_APPLICATION") != null;
|
||||
|
||||
if (isCloudFoundry) {
|
||||
// Cloud Foundry
|
||||
final JsonParser jsonParser = new JsonParser();
|
||||
|
||||
jdbcUrl = getJdbcURLFromVCAPServices(jsonParser);
|
||||
|
||||
if (System.getenv("CART_ENDPOINT") != null) {
|
||||
cartUrl = System.getenv("CART_ENDPOINT");
|
||||
} else {
|
||||
final JsonObject vcapApplication = jsonParser.parse(System.getenv("VCAP_APPLICATION")).getAsJsonObject();
|
||||
|
||||
final JsonArray appUris = vcapApplication.get("application_uris").getAsJsonArray();
|
||||
if (appUris == null || appUris.size() == 0) {
|
||||
throw new IllegalStateException("Cannot retrieve application URIs from 'VCAP_APPLICATION'");
|
||||
}
|
||||
|
||||
final String applicationName = vcapApplication.get("application_name").getAsString();
|
||||
final String applicationURI = appUris.get(0).getAsString();
|
||||
|
||||
cartUrl = "https://" + applicationURI.replace(applicationName, "cart") + "/shipping/";
|
||||
}
|
||||
} else {
|
||||
cartUrl = String.format("http://%s/shipping/", System.getenv("CART_ENDPOINT") != null ? System.getenv("CART_ENDPOINT") : "cart");
|
||||
jdbcUrl = String.format("jdbc:mysql://%s/cities?useSSL=false&autoReconnect=true", System.getenv("DB_HOST") != null ? System.getenv("DB_HOST") : "mysql");
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Create database connector
|
||||
// TODO - might need a retry loop here
|
||||
//
|
||||
try {
|
||||
cpds = new ComboPooledDataSource();
|
||||
cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc driver
|
||||
cpds.setJdbcUrl( JDBC_URL );
|
||||
cpds.setUser("shipping");
|
||||
cpds.setPassword("secret");
|
||||
CPDS.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc driver
|
||||
CPDS.setJdbcUrl( jdbcUrl );
|
||||
|
||||
if (!isCloudFoundry) {
|
||||
CPDS.setUser("shipping");
|
||||
CPDS.setPassword("secret");
|
||||
}
|
||||
|
||||
// some config
|
||||
cpds.setMinPoolSize(5);
|
||||
cpds.setAcquireIncrement(5);
|
||||
cpds.setMaxPoolSize(20);
|
||||
cpds.setMaxStatements(180);
|
||||
CPDS.setMinPoolSize(5);
|
||||
CPDS.setAcquireIncrement(5);
|
||||
CPDS.setMaxPoolSize(20);
|
||||
CPDS.setMaxStatements(180);
|
||||
}
|
||||
catch(Exception e) {
|
||||
logger.error("Database Exception", e);
|
||||
LOGGER.error("Database Exception", e);
|
||||
}
|
||||
|
||||
// Spark
|
||||
@@ -71,7 +99,7 @@ public class Main {
|
||||
data = queryToJson("select count(*) as count from cities");
|
||||
res.header("Content-Type", "application/json");
|
||||
} catch(Exception e) {
|
||||
logger.error("count", e);
|
||||
LOGGER.error("count", e);
|
||||
res.status(500);
|
||||
data = "ERROR";
|
||||
}
|
||||
@@ -86,7 +114,7 @@ public class Main {
|
||||
data = queryToJson(query);
|
||||
res.header("Content-Type", "application/json");
|
||||
} catch(Exception e) {
|
||||
logger.error("codes", e);
|
||||
LOGGER.error("codes", e);
|
||||
res.status(500);
|
||||
data = "ERROR";
|
||||
}
|
||||
@@ -99,11 +127,11 @@ public class Main {
|
||||
String data;
|
||||
try {
|
||||
String query = "select uuid, name from cities where country_code = ?";
|
||||
logger.info("Query " + query);
|
||||
LOGGER.info("Query " + query);
|
||||
data = queryToJson(query, req.params(":code"));
|
||||
res.header("Content-Type", "application/json");
|
||||
} catch(Exception e) {
|
||||
logger.error("cities", e);
|
||||
LOGGER.error("cities", e);
|
||||
res.status(500);
|
||||
data = "ERROR";
|
||||
}
|
||||
@@ -115,11 +143,11 @@ public class Main {
|
||||
String data;
|
||||
try {
|
||||
String query = "select uuid, name from cities where country_code = ? and city like ? order by name asc limit 10";
|
||||
logger.info("Query " + query);
|
||||
LOGGER.info("Query " + query);
|
||||
data = queryToJson(query, req.params(":code"), req.params(":text") + "%");
|
||||
res.header("Content-Type", "application/json");
|
||||
} catch(Exception e) {
|
||||
logger.error("match", e);
|
||||
LOGGER.error("match", e);
|
||||
res.status(500);
|
||||
data = "ERROR";
|
||||
}
|
||||
@@ -145,7 +173,7 @@ public class Main {
|
||||
data = new Gson().toJson(ship);
|
||||
} else {
|
||||
data = "no location";
|
||||
logger.warn(data);
|
||||
LOGGER.warn(data);
|
||||
res.status(400);
|
||||
}
|
||||
|
||||
@@ -153,9 +181,9 @@ public class Main {
|
||||
});
|
||||
|
||||
Spark.post("/confirm/:id", (req, res) -> {
|
||||
logger.info("confirm " + req.params(":id") + " - " + req.body());
|
||||
String cart = addToCart(req.params(":id"), req.body());
|
||||
logger.info("new cart " + cart);
|
||||
LOGGER.info("confirm " + req.params(":id") + " - " + req.body());
|
||||
String cart = addToCart(cartUrl, req.params(":id"), req.body());
|
||||
LOGGER.info("new cart " + cart);
|
||||
|
||||
if(cart.equals("")) {
|
||||
res.status(404);
|
||||
@@ -166,18 +194,35 @@ public class Main {
|
||||
return cart;
|
||||
});
|
||||
|
||||
logger.info("Ready");
|
||||
LOGGER.info("Ready");
|
||||
}
|
||||
|
||||
private static String getJdbcURLFromVCAPServices(final JsonParser jsonParser) {
|
||||
final JsonObject vcapServices = jsonParser.parse(System.getenv("VCAP_SERVICES")).getAsJsonObject();
|
||||
|
||||
for (Map.Entry<String, JsonElement> boundServices : vcapServices.entrySet()) {
|
||||
final JsonArray bindings = boundServices.getValue().getAsJsonArray();
|
||||
|
||||
for (JsonElement boundService : bindings) {
|
||||
final JsonObject bindingDetails = boundService.getAsJsonObject();
|
||||
if (bindingDetails.has("binding_name") && "shipping_database".equals(bindingDetails.get("binding_name").getAsString())) {
|
||||
final JsonObject credentials = bindingDetails.get("credentials").getAsJsonObject();
|
||||
return credentials.get("jdbcUrl").getAsString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Cannot retrieve the 'shipping_database' service binding.");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Query to Json - QED
|
||||
**/
|
||||
private static String queryToJson(String query, Object ... args) {
|
||||
List<Map<String, Object>> listOfMaps = null;
|
||||
List<Map<String, Object>> listOfMaps;
|
||||
try {
|
||||
QueryRunner queryRunner = new QueryRunner(cpds);
|
||||
QueryRunner queryRunner = new QueryRunner(CPDS);
|
||||
listOfMaps = queryRunner.query(query, new MapListHandler(), args);
|
||||
} catch (SQLException se) {
|
||||
throw new RuntimeException("Couldn't query the database.", se);
|
||||
@@ -197,7 +242,7 @@ public class Main {
|
||||
String query = "select latitude, longitude from cities where uuid = ?";
|
||||
|
||||
try {
|
||||
conn = cpds.getConnection();
|
||||
conn = CPDS.getConnection();
|
||||
stmt = conn.prepareStatement(query);
|
||||
stmt.setInt(1, Integer.parseInt(uuid));
|
||||
rs = stmt.executeQuery();
|
||||
@@ -206,7 +251,7 @@ public class Main {
|
||||
break;
|
||||
}
|
||||
} catch(Exception e) {
|
||||
logger.error("Location exception", e);
|
||||
LOGGER.error("Location exception", e);
|
||||
} finally {
|
||||
DbUtils.closeQuietly(conn, stmt, rs);
|
||||
}
|
||||
@@ -214,7 +259,7 @@ public class Main {
|
||||
return location;
|
||||
}
|
||||
|
||||
private static String addToCart(String id, String data) {
|
||||
private static String addToCart(String cartUrl, String id, String data) {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
DefaultHttpClient httpClient = null;
|
||||
@@ -224,7 +269,7 @@ public class Main {
|
||||
HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
|
||||
|
||||
httpClient = new DefaultHttpClient(httpParams);
|
||||
HttpPost postRequest = new HttpPost(CART_URL + id);
|
||||
HttpPost postRequest = new HttpPost(cartUrl + id);
|
||||
StringEntity payload = new StringEntity(data);
|
||||
payload.setContentType("application/json");
|
||||
postRequest.setEntity(payload);
|
||||
@@ -237,10 +282,10 @@ public class Main {
|
||||
buffer.append(line);
|
||||
}
|
||||
} else {
|
||||
logger.warn("Failed with code: " + res.getStatusLine().getStatusCode());
|
||||
LOGGER.warn("Failed with code: " + res.getStatusLine().getStatusCode());
|
||||
}
|
||||
} catch(Exception e) {
|
||||
logger.error("http client exception", e);
|
||||
LOGGER.error("http client exception", e);
|
||||
} finally {
|
||||
if(httpClient != null) {
|
||||
httpClient.getConnectionManager().shutdown();
|
||||
|
BIN
shipping/target/classes/org/steveww/spark/Location.class
Normal file
BIN
shipping/target/classes/org/steveww/spark/Location.class
Normal file
Binary file not shown.
BIN
shipping/target/classes/org/steveww/spark/Main.class
Normal file
BIN
shipping/target/classes/org/steveww/spark/Main.class
Normal file
Binary file not shown.
BIN
shipping/target/classes/org/steveww/spark/Ship.class
Normal file
BIN
shipping/target/classes/org/steveww/spark/Ship.class
Normal file
Binary file not shown.
5
shipping/target/maven-archiver/pom.properties
Normal file
5
shipping/target/maven-archiver/pom.properties
Normal file
@@ -0,0 +1,5 @@
|
||||
#Generated by Maven
|
||||
#Mon Aug 12 12:33:39 CEST 2019
|
||||
groupId=steveww
|
||||
artifactId=shipping
|
||||
version=1.0
|
@@ -0,0 +1,3 @@
|
||||
org/steveww/spark/Ship.class
|
||||
org/steveww/spark/Main.class
|
||||
org/steveww/spark/Location.class
|
@@ -0,0 +1,3 @@
|
||||
/Users/michele/git/instana/robot-shop/shipping/src/main/java/org/steveww/spark/Ship.java
|
||||
/Users/michele/git/instana/robot-shop/shipping/src/main/java/org/steveww/spark/Main.java
|
||||
/Users/michele/git/instana/robot-shop/shipping/src/main/java/org/steveww/spark/Location.java
|
BIN
shipping/target/shipping-1.0-jar-with-dependencies.jar
Normal file
BIN
shipping/target/shipping-1.0-jar-with-dependencies.jar
Normal file
Binary file not shown.
BIN
shipping/target/shipping-1.0.jar
Normal file
BIN
shipping/target/shipping-1.0.jar
Normal file
Binary file not shown.
123
user/server.js
123
user/server.js
@@ -1,4 +1,6 @@
|
||||
const instana = require('instana-nodejs-sensor');
|
||||
/*jshint esversion: 6 */
|
||||
|
||||
const instana = require('@instana/collector');
|
||||
// init tracing
|
||||
// MUST be done before loading anything else!
|
||||
instana({
|
||||
@@ -8,7 +10,6 @@ instana({
|
||||
});
|
||||
|
||||
const mongoClient = require('mongodb').MongoClient;
|
||||
const mongoObjectID = require('mongodb').ObjectID;
|
||||
const redis = require('redis');
|
||||
const bodyParser = require('body-parser');
|
||||
const express = require('express');
|
||||
@@ -241,9 +242,62 @@ app.get('/history/:id', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
var redisHost;
|
||||
var redisPassword;
|
||||
var redisPort;
|
||||
|
||||
if (process.env.VCAP_SERVICES) {
|
||||
connectionDetails = null;
|
||||
|
||||
console.log('Env var \'VCAP_SERVICES\' found, scanning for \'users_cache\' service binding');
|
||||
|
||||
for (let [key, value] of Object.entries(JSON.parse(process.env.VCAP_SERVICES))) {
|
||||
try {
|
||||
binding = value.find(function(binding) {
|
||||
return 'users_cache' == binding.binding_name && binding.credentials;
|
||||
});
|
||||
|
||||
if (!binding) {
|
||||
continue;
|
||||
}
|
||||
|
||||
connectionDetails = binding.credentials;
|
||||
|
||||
if (connectionDetails) {
|
||||
redisHost = connectionDetails.host;
|
||||
redisPassword = connectionDetails.password;
|
||||
redisPort = connectionDetails.port;
|
||||
|
||||
console.log('Redis URI for \'users_cache\' service binding found in \'VCAP_SERVICES\'');
|
||||
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Cannot process key \'' + key + '\' of \'VCAP_SERVICES\'', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
} else if (process.env.REDIS_HOST) {
|
||||
redisHost = process.env.REDIS_HOST;
|
||||
redisPort = 6379;
|
||||
|
||||
console.log('Redis host found in \'REDIS_HOST\': ' + redisHost);
|
||||
} else {
|
||||
redisHost = 'redis';
|
||||
redisPort = 6379;
|
||||
|
||||
console.log('Using default Redis host and port');
|
||||
}
|
||||
|
||||
if (!redisHost) {
|
||||
throw new Error('Redis connection data missing');
|
||||
}
|
||||
|
||||
// connect to Redis
|
||||
var redisClient = redis.createClient({
|
||||
host: process.env.REDIS_HOST || 'redis'
|
||||
host: redisHost,
|
||||
password: redisPassword,
|
||||
port: redisPort
|
||||
});
|
||||
|
||||
redisClient.on('error', (e) => {
|
||||
@@ -256,15 +310,66 @@ redisClient.on('ready', (r) => {
|
||||
// set up Mongo
|
||||
function mongoConnect() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var mongoURL = process.env.MONGO_URL || 'mongodb://mongodb:27017/users';
|
||||
mongoClient.connect(mongoURL, (error, _db) => {
|
||||
if(error) {
|
||||
reject(error);
|
||||
} else {
|
||||
db = _db;
|
||||
var mongoURL;
|
||||
|
||||
if (process.env.VCAP_SERVICES) {
|
||||
connectionDetails = null;
|
||||
|
||||
console.log('Env var \'VCAP_SERVICES\' found, scanning for \'users_database\' service binding');
|
||||
|
||||
for (let [key, value] of Object.entries(JSON.parse(process.env.VCAP_SERVICES))) {
|
||||
try {
|
||||
binding = value.find(function(binding) {
|
||||
return 'users_database' == binding.binding_name && binding.credentials;
|
||||
});
|
||||
|
||||
if (!binding) {
|
||||
continue;
|
||||
}
|
||||
|
||||
connectionDetails = binding.credentials;
|
||||
|
||||
if (connectionDetails && connectionDetails.uri) {
|
||||
mongoURL = connectionDetails.uri;
|
||||
|
||||
console.log('MongoDB URI for \'users_database\' service binding found in \'VCAP_SERVICES\'');
|
||||
|
||||
break;
|
||||
} else {
|
||||
throw new Error('Service binding \'users_database\' found, but cannot retrieve the URI from the credentials');
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Cannot process key \'' + key + '\' of \'VCAP_SERVICES\'', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
} else if (process.env.MONGO_URL) {
|
||||
mongoURL = process.env.MONGO_URL;
|
||||
|
||||
console.log('MongoDB URI found in \'MONGO_URL\': ' + mongoURL);
|
||||
} else {
|
||||
mongoURL = 'mongodb://mongodb:27017/catalogue';
|
||||
|
||||
console.log('Using default MongoDB URI');
|
||||
}
|
||||
|
||||
if (!mongoURL) {
|
||||
throw new Error('MongoDB connection data missing');
|
||||
}
|
||||
|
||||
mongoClient.connect(mongoURL, (error, db) => {
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
usersCollection = db.collection('users');
|
||||
ordersCollection = db.collection('orders');
|
||||
|
||||
resolve('connected');
|
||||
} catch (err) {
|
||||
console.log('Cannot connecto to MongoDB databases', err);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
3
web/buildpack.yml
Normal file
3
web/buildpack.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
nginx:
|
||||
version: 1.17.2
|
5
web/instana/instana-config.json
Normal file
5
web/instana/instana-config.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"service": "web",
|
||||
"agent_host": "instana-agent",
|
||||
"agent_port": 42699
|
||||
}
|
BIN
web/instana/libinstana_sensor.so
Normal file
BIN
web/instana/libinstana_sensor.so
Normal file
Binary file not shown.
78
web/mime.types
Normal file
78
web/mime.types
Normal file
@@ -0,0 +1,78 @@
|
||||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
application/x-javascript js;
|
||||
application/atom+xml atom;
|
||||
application/rss+xml rss;
|
||||
font/ttf ttf;
|
||||
font/woff woff;
|
||||
font/woff2 woff2;
|
||||
text/mathml mml;
|
||||
text/plain txt;
|
||||
text/vnd.sun.j2me.app-descriptor jad;
|
||||
text/vnd.wap.wml wml;
|
||||
text/x-component htc;
|
||||
text/cache-manifest manifest;
|
||||
image/png png;
|
||||
image/tiff tif tiff;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/x-icon ico;
|
||||
image/x-jng jng;
|
||||
image/x-ms-bmp bmp;
|
||||
image/svg+xml svg svgz;
|
||||
image/webp webp;
|
||||
application/java-archive jar war ear;
|
||||
application/mac-binhex40 hqx;
|
||||
application/msword doc;
|
||||
application/pdf pdf;
|
||||
application/postscript ps eps ai;
|
||||
application/rtf rtf;
|
||||
application/vnd.ms-excel xls;
|
||||
application/vnd.ms-powerpoint ppt;
|
||||
application/vnd.wap.wmlc wmlc;
|
||||
application/vnd.google-earth.kml+xml kml;
|
||||
application/vnd.google-earth.kmz kmz;
|
||||
application/x-7z-compressed 7z;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot prc pdb;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert der pem crt;
|
||||
application/x-xpinstall xpi;
|
||||
application/xhtml+xml xhtml;
|
||||
application/zip zip;
|
||||
application/octet-stream bin exe dll;
|
||||
application/octet-stream deb;
|
||||
application/octet-stream dmg;
|
||||
application/octet-stream eot;
|
||||
application/octet-stream iso img;
|
||||
application/octet-stream msi msp msm;
|
||||
application/json json;
|
||||
audio/midi mid midi kar;
|
||||
audio/mpeg mp3;
|
||||
audio/ogg ogg;
|
||||
audio/x-m4a m4a;
|
||||
audio/x-realaudio ra;
|
||||
video/3gpp 3gpp 3gp;
|
||||
video/mp4 mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-m4v m4v;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
}
|
BIN
web/modules/ngx_http_module.so
Normal file
BIN
web/modules/ngx_http_module.so
Normal file
Binary file not shown.
73
web/nginx.conf
Normal file
73
web/nginx.conf
Normal file
@@ -0,0 +1,73 @@
|
||||
load_module modules/ngx_http_module.so;
|
||||
|
||||
worker_processes 1;
|
||||
daemon off;
|
||||
|
||||
error_log stderr;
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
pid /tmp/nginx.pid;
|
||||
|
||||
http {
|
||||
opentracing on;
|
||||
|
||||
opentracing_load_tracer instana/libinstana_sensor.so instana/instana-config.json;
|
||||
|
||||
charset utf-8;
|
||||
log_format cloudfoundry 'NginxLog "$request" $status $body_bytes_sent';
|
||||
access_log /dev/stdout cloudfoundry;
|
||||
default_type application/octet-stream;
|
||||
sendfile on;
|
||||
|
||||
tcp_nopush on;
|
||||
keepalive_timeout 30;
|
||||
port_in_redirect off;
|
||||
|
||||
# Calculate CF app names reusing the CF apps domain as seen by the client
|
||||
map $server_name $domain {
|
||||
~^[^\.]*(.*)$ $1;
|
||||
}
|
||||
|
||||
server {
|
||||
listen {{port}};
|
||||
server_name ~^(?<name>\w+)\.(?<domain>.*)$;
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
# location = /50x.html {
|
||||
# root /usr/share/nginx/html;
|
||||
# }
|
||||
|
||||
location ~ ^/api/([a-zA-Z]+)/(.+)$ {
|
||||
# We need a resolver, since we need to use variables, so we need to specify a DNS server.
|
||||
# Google, HALP!
|
||||
resolver 8.8.8.8;
|
||||
|
||||
proxy_pass https://$1.$domain/$2;
|
||||
|
||||
opentracing_trace_locations off;
|
||||
opentracing_propagate_context;
|
||||
opentracing_tag "resource.name" $1/$2;
|
||||
opentracing_tag "endpoint" $1;
|
||||
}
|
||||
|
||||
location /nginx_status {
|
||||
stub_status on;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location / {
|
||||
root static;
|
||||
index index.html;
|
||||
ssi on;
|
||||
|
||||
include mime.types;
|
||||
|
||||
opentracing_trace_locations off;
|
||||
opentracing_propagate_context;
|
||||
opentracing_tag "resource.name" "static_resources";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
<!-- EUM include -->
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['InstanaEumObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//eum.instana.io/eum.min.js','ineum');
|
||||
ineum('apiKey', 'INSTANA_EUM_KEY');
|
||||
ineum('page', 'Shop');
|
||||
</script>
|
||||
<!-- EUM include end -->
|
||||
|
10
web/static/eum.html
Normal file
10
web/static/eum.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<script>
|
||||
(function(c,e,f,k,g,h,b,a,d){c[g]||(c[g]=h,b=c[h]=function(){
|
||||
b.q.push(arguments)},b.q=[],b.l=1*new Date,a=e.createElement(f),a.async=1,
|
||||
a.src=k,a.setAttribute("crossorigin", "anonymous"),d=e.getElementsByTagName(f)[0],
|
||||
d.parentNode.insertBefore(a,d))})(window,document,"script",
|
||||
"//eum.instana.io/eum.min.js","InstanaEumObject","ineum");
|
||||
ineum('reportingUrl', 'TODO EDIT ME');
|
||||
ineum('key', 'TODO EDIT ME');
|
||||
ineum('page', 'RobotShop');
|
||||
</script>
|
@@ -1,7 +1,9 @@
|
||||
/*jshint esversion: 6 */
|
||||
|
||||
(function(angular) {
|
||||
'use strict';
|
||||
|
||||
var robotshop = angular.module('robotshop', ['ngRoute'])
|
||||
var robotshop = angular.module('robotshop', ['ngRoute']);
|
||||
|
||||
// Share user between controllers
|
||||
robotshop.factory('currentUser', function() {
|
||||
@@ -211,10 +213,10 @@
|
||||
|
||||
$scope.rateProduct = function(score) {
|
||||
console.log('rate product', $scope.data.product.sku, score);
|
||||
var url = '/api/ratings/api/rate/' + $scope.data.product.sku + '/' + score;
|
||||
var url = '/api/ratings/rate/' + $scope.data.product.sku + '/' + score;
|
||||
$http({
|
||||
url: url,
|
||||
method: 'PUT'
|
||||
method: 'POST'
|
||||
}).then((res) => {
|
||||
$scope.data.message = 'Thankyou for your feedback';
|
||||
$timeout(clearMessage, 3000);
|
||||
@@ -246,7 +248,7 @@
|
||||
|
||||
function loadRating(sku) {
|
||||
$http({
|
||||
url: '/api/ratings/api/fetch/' + sku,
|
||||
url: '/api/ratings/fetch/' + sku,
|
||||
method: 'GET'
|
||||
}).then((res) => {
|
||||
$scope.data.rating = res.data;
|
||||
|
Reference in New Issue
Block a user