33 Commits

Author SHA1 Message Date
Michele Mancioppi
a363a2485b [WIP] Mongo init 2020-06-07 12:54:58 +02:00
Michele Mancioppi
f3e849e90b Add programmable error rate to catalogue and user 2020-06-07 12:54:58 +02:00
Michele Mancioppi
4470ebfa1c Add error rate to messaging queue 2020-06-07 12:54:58 +02:00
Michele Mancioppi
2d8e8e92ed Make payment app crash if CF_INSTANCE_INDEX is odd 2020-06-07 12:54:58 +02:00
Michele Mancioppi
2d1aa58274 Update README.md 2020-01-31 17:14:03 +01:00
Michele Mancioppi
bdce77fbd0 Update README.md 2020-01-31 14:58:25 +01:00
Michele Mancioppi
ab51044e65 Update README.md 2020-01-31 14:57:21 +01:00
Michele Mancioppi
480da51039 Update README.md 2020-01-31 14:54:32 +01:00
Michele Mancioppi
656054e928 Update README.md 2020-01-31 14:49:02 +01:00
Michele Mancioppi
136a67166f Update README.md 2020-01-31 10:43:05 +01:00
Michele Mancioppi
bf06964db3 Update README.md 2020-01-31 10:35:45 +01:00
Michele Mancioppi
68fe665eea Add maven wrapper to Shipping app 2020-01-31 10:33:23 +01:00
Michele Mancioppi
0f12ad30d7 Update README.md 2020-01-31 10:30:19 +01:00
Dusan Ugarkovic
f389523bbf fixed reconnection to rabbitmq in payment app 2019-10-28 08:28:30 +01:00
Michele Mancioppi
497bfe6acc Configure NGINX to group all static resources into one endpoint 2019-09-10 18:44:46 +02:00
Michele Mancioppi
3b7b948f96 Add EUM pageload support via nginx.conf 2019-09-10 13:17:40 +02:00
Michele Mancioppi
c51acba9ab Ensure order history update failures are marked as errors 2019-09-09 13:34:53 +02:00
Michele Mancioppi
934f95deb0 Make paypal failures visible as errors in Instana 2019-09-09 13:28:36 +02:00
Michele Mancioppi
7ef5c22ba3 Leverage support for 'messaging_bus.address' in dispatch component 2019-09-08 15:23:48 +02:00
Michele Mancioppi
632d2a85b3 Improve MongoDB init
1) Use promises properly because it's 2019 already
2) Get rid of 'Unhandled promise exceptions'
3) Terminate task successfully when all work is done
4) Purge collections before re-adding data (import is now idempotent)
2019-09-08 15:23:48 +02:00
Michele Mancioppi
00401f6b80 Add CF diagnostic logs for CF to payment app 2019-09-08 14:32:12 +02:00
Michele Mancioppi
e6cbae39a2 Add Procfile to payment app 2019-09-08 14:32:12 +02:00
Michele Mancioppi
bcfd10d9be Improve setup info 2019-09-08 14:32:12 +02:00
Michele Mancioppi
733c943108 Improve bootstrap of MySQL databases 2019-09-08 14:32:12 +02:00
Michele Mancioppi
0db4344a24 Simplify load-gen setup 2019-09-08 14:32:11 +02:00
Michele Mancioppi
da3cde42f5 Update README.md 2019-09-06 09:49:13 +02:00
Goran Ceko
43040713a7 Rename Artifical Inteligence category to AI to remove space. Fix docker file broken copy command 2019-09-06 08:22:25 +02:00
Michele Mancioppi
33235306ae Add load generation
TODO: Cleanup the configuration: we should use py-cfenv to automatically
calculate the URL of the web shop
2019-09-05 15:56:38 +02:00
Michele Mancioppi
d931967c1f Pin Python sensor to 1.15.3
See https://github.com/instana/python-sensor/issues/187
2019-09-05 06:11:28 +02:00
Michele Mancioppi
e76fd26775 Fix lookup of RabbitMQ for dispatch in CF 2019-09-05 06:09:09 +02:00
Michele Mancioppi
e44ddda513 Update manifest.yml 2019-09-04 14:42:40 +02:00
Michele Mancioppi
eefe45a645 Pin web manifest to Nginx 1.17.2 2019-09-03 13:20:05 +02:00
Michele Mancioppi
48032a561b [WIP] Port RobotShop to CF 2019-08-24 06:10:08 +02:00
62 changed files with 2295 additions and 215 deletions

3
.gitignore vendored
View File

@@ -2,4 +2,5 @@
.DS_Store
*-private.*
vendor/
Gopkg.lock
Gopkg.lock
.idea

134
CF/README.md Normal file
View File

@@ -0,0 +1,134 @@
# 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 Pivotal Platform](https://network.pivotal.io/products/p-rabbitmq/)
- [Redis for Pivotal Platform](https://network.pivotal.io/products/p-redis/)
- [MySQL for Pivotal Platform](https://network.pivotal.io/products/pivotal-mysql/)
- [MongoDB Enterprise Service for Pivotal Platform](https://network.pivotal.io/products/mongodb-enterprise-service/)
- [Instana Microservices Application Monitoring for Pivotal Platform](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
```
## Build applications
Build the Java apps, from the repository root:
```sh
pushd shipping && ./mvnw clean package && popd
```
## 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
```
## Create the services
```sh
cf cs p.mysql db-small mysql-cities
cf cs p.mysql db-small mysql-ratings
cf cs mongodb-odb replica_set_small mongodb
cf cs p.redis cache-small redis
cf cs p.rabbitmq single-node-3.7 rabbitmq
```
## Init MongoDB
```sh
cf bind-service mongo-init mongodb --binding-name catalogue_database
cf run-task mongo-init 'node init-db.js' --name "Init MongoDB"
```
## Init MySQL databases
### Ratings database
```sh
cf bind-service mysql-init mysql-ratings --binding-name ratings_database
cf run-task mysql-init 'node init-db.js init-ratings.sql ratings_database' --name "Init Ratings database"
```
### Shipping database
This one is not automated yet, as the `mysql-init` task app chokes on the large SQL init file.
Something that works is to:
1) `bosh ssh` into the MySQL virtual machine
2) Find out admin password from `/var/vcap/jobs/mysql/config/mylogin.cnf`
3) Download the ["cities" init sql file](https://github.com/mmanciop/robot-shop/raw/master/mysql/scripts/10-dump.sql.gz) and `gunzip` it
4) `/var/vcap/packages/percona-server/bin/mysql -u admin -p<adminpwd> -P 3306 -D service_instance_db < <sql_file>`
## Bind the services
Now that we have both apps and services, we can bind the former to the latter:
```sh
cf bind-service mysql-init mysql-ratings --binding-name ratings_database
cf bind-service ratings mysql-ratings --binding-name ratings_database
cf bind-service catalogue mongodb --binding-name catalogue_database
cf bind-service cart redis --binding-name cart_cache
cf bind-service shipping mysql-cities --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
```
## 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.
Push again the `web` application (or the entire manifest, whatever is easier for you):
```sh
cf push -f CF/manifest.yml
```
## Spin up the containers
**Note:** Feel free to replace the value after `-i` with how many container of any one kind you want.
```sh
cf scale -i 1 web
cf scale -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
```

139
CF/manifest.yml Normal file
View File

@@ -0,0 +1,139 @@
---
applications:
###
### "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
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: 0
- name: web
path: ../web/
memory: 128M
buildpacks:
- https://github.com/mmanciop/nginx-buildpack.git#nginx-1.17.2
instances: 0
- name: loadgeneration
path: ../load-gen
buildpacks:
- https://github.com/cloudfoundry/python-buildpack.git#v1.6.36
no-route: true
health-check-type: process
memory: 1GB
env:
HOST: http://web.apps.pcf264.qainfra.instana.io
CLIENTS: 5
instances: 5
# cf cs p.mysql db-small mysql-cities
# cf cs p.mysql db-small mysql-ratings
# 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"
# cf bind-service mysql-init mysql-ratings --binding-name ratings_database
# cf run-task mysql-init 'node init-db.js init-ratings.sql ratings_database' --name "Init Ratings database"
###
### DOES NOT WORK DUE TO THE SIZE OF THE DATABASE TO BE IMPORTED
### MUST IMPORT DATA MANUALLY:
### 1) BOSH SSH into the MySQL VM
### 2) Find out admin password from /var/vcap/jobs/mysql/config/mylogin.cnf
### 3) Download init sql from https://github.com/mmanciop/robot-shop/raw/master/mysql/scripts/10-dump.sql.gz and gunzip it
### 4) /var/vcap/packages/percona-server/bin/mysql -u admin -p<adminpwd> -P 3306 -D service_instance_db < init.sql
# cf bind-service mysql-init mysql-ratings --binding-name shipping_database
# cf run-task mysql-init 'node init-db.js init-cities.sql shipping_database' --name "Init Shipping database"
# cf bind-service ratings mysql-ratings --binding-name ratings_database
# cf scale -i 1 ratings
# cf restage ratings
# cf bind-service catalogue mongodb --binding-name catalogue_database
# cf scale -i 1 catalogue
# cf restage catalogue
# cf bind-service cart redis --binding-name cart_cache
# cf scale -i 1 cart
# cf restage cart
# cf bind-service shipping mysql --binding-name shipping_database
# cf scale -i 1 shipping
# cf restage 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 restage user
# cf bind-service payment rabbitmq --binding-name dispatch_queue
# cf scale -i 1 payment
# cf restage payment
# cf bind-service dispatch rabbitmq --binding-name dispatch_queue
# cf scale -i 1 dispatch
# cf restage dispatch
# cf scale -i 1 web

90
CF/mongo-init/init-db.js Normal file
View File

@@ -0,0 +1,90 @@
/*jshint esversion: 8 */
const mongoClient = require('mongodb').MongoClient;
new Promise((resolve, reject) => {
if (process.env.VCAP_SERVICES) {
const bindingName = 'catalogue_database';
connectionDetails = null;
for (let [key, value] of Object.entries(JSON.parse(process.env.VCAP_SERVICES))) {
connectionDetails = value.find(function(binding) {
return bindingName == binding.binding_name && binding.credentials;
}).credentials;
if (!connectionDetails) {
reject(`Cannot find a service binding with name '${bindingName}'`);
}
const username = connectionDetails.username;
const password = connectionDetails.password;
const normalizedAuthDetails = `${encodeURIComponent(username)}:${encodeURIComponent(password)}`;
const mongoUrl = connectionDetails.uri.replace(`${username}:${password}`, normalizedAuthDetails);
resolve(mongoUrl);
}
} else if (process.env.MONGO_URL) {
resolve(process.env.MONGO_URL);
} else {
reject('MongoDB connection data missing');
}
}).then(function (mongoUrl) {
console.log(`Connecting to ${mongoUrl}`);
return mongoClient.connect(mongoUrl);
}).then(function(db) {
const products = db.collection('products');
const users = db.collection('users');
return products
.deleteMany({})
.then(products.insertMany([
{sku: 'HAL-1', name: 'HAL', description: 'Sorry Dave, I cant do that', price: 2001, instock: 2, categories: ['AI']},
{sku: 'PB-1', name: 'Positronic Brain', description: 'Highly advanced sentient processing unit with the laws of robotics burned in', price: 200, instock: 0, categories: ['AI']},
{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', 'AI']},
{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
.then(products.createIndex({
name: 'text',
description: 'text'
}))
// unique index for product sku
.then(products.createIndex(
{ sku: 1 },
{ unique: true }
))
.then(function() {
console.log('Populated \'products\' collection');
})
.then(users.deleteMany({}))
.then(users.insertMany([
{name: 'user', password: 'password', email: 'user@me.com'},
{name: 'stan', password: 'bigbrain', email: 'stan@instana.com'}
]))
.then(users.createIndex(
{name: 1},
{unique: true}
))
.then(function () {
console.log('Populated \'users\' collection');
})
})
.then(function() {
console.log('All done');
process.exit(0);
})
.catch(function(err) {
console.log(`Error while importing data into the MongoDB database: ${err}`);
process.exit(42);
});

View 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"
}
}

44
CF/mysql-init/init-db.js Normal file
View File

@@ -0,0 +1,44 @@
/*jshint esversion: 6 */
(function () {
'use strict';
const fs = require('fs');
const initArgv = process.argv.slice(2);
if (initArgv.length != 2) {
console.error(`Usage: ${process.argv[0]} ${process.argv[1]} <sql-file> <mysql-service-binding-name>\n\nProgram arguments: ${process.argv.join(' ')}`);
process.exit(1);
}
const sqlFile = initArgv[0];
const bindingName = initArgv[1];
const appEnv = require('cfenv').getAppEnv();
if (appEnv.isLocal) {
throw new Error('Not running in Cloud Foundry (\'VCAP_SERVICES\' env var not found)');
}
var connectionDetails = appEnv.getServiceCreds(bindingName);
if (!connectionDetails) {
throw new Error(`No service binding with name '${bindingName}' found`);
}
if (fs.existsSync(sqlFile)) {
console.log('Starting database import');
require('mysql-import').config({
host: connectionDetails.hostname,
user: connectionDetails.username,
password: connectionDetails.password,
database: connectionDetails.name,
onerror: err=>console.log(err.message)
}).import(sqlFile)
.then(() => console.log('Database imported'));
} else {
throw new Error(`File '${sqlFile}' not found!`);
}
}());

View File

@@ -0,0 +1,8 @@
DROP TABLE IF EXISTS 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;

View File

@@ -0,0 +1,15 @@
{
"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",
"cfenv": "1.2.2"
}
}

View File

@@ -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*.

View File

@@ -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"
}
}

View File

@@ -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);
});

View File

@@ -11,10 +11,11 @@
"dependencies": {
"body-parser": "^1.18.1",
"express": "^4.15.4",
"mongodb": "^2.2.33",
"mongodb": "2.2.33",
"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",
"mathjs": "^6.6.4"
}
}

View File

@@ -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,9 +25,81 @@ const expLogger = expPino({
});
// MongoDB
var db;
var collection;
var mongoConnected = false;
var productsCollection;
const port = process.env.CATALOGUE_SERVER_PORT || '8080';
// set up Mongo
function mongoConnectionPromise() {
return new Promise(function() {
var mongoURL;
if (process.env.VCAP_SERVICES) {
const bindingName = 'catalogue_database';
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 {
connectionDetails = value.find(function(binding) {
return bindingName == binding.binding_name && binding.credentials;
}).credentials;
if (!connectionDetails) {
throw new Error(`Cannot find a service binding with name '${bindingName}'`);
}
const username = connectionDetails.username;
const password = connectionDetails.password;
const normalizedAuthDetails = `${encodeURIComponent(username)}:${encodeURIComponent(password)}`;
mongoURL = connectionDetails.uri.replace(`${username}:${password}`, normalizedAuthDetails);
} 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');
}
return mongoClient.connect(mongoURL);
})
.then(function(db) {
productsCollection = db.collection('products');
db.on('close', function() {
logger.error('Disconnected from the MongoDB database, reconnecting...', e);
mongoConnect();
})
})
.then(function() {
logger.info('MongoDB connected');
})
.catch(function(err) {
console.log(`Cannot connect to MongoDB: ${err}`);
});
};
async function mongoConnect() {
await mongoConnectionPromise();
}
mongoConnect();
const app = express();
@@ -51,120 +124,64 @@ app.get('/health', (req, res) => {
// all products
app.get('/products', (req, res) => {
if(mongoConnected) {
collection.find({}).toArray().then((products) => {
res.json(products);
}).catch((e) => {
req.log.error('ERROR', e);
res.status(500).send(e);
});
} else {
req.log.error('database not available');
res.status(500).send('database not avaiable');
}
productsCollection.find({}).toArray().then((products) => {
res.json(products);
}).catch((e) => {
req.log.error('ERROR', e);
res.status(500).send(e);
});
});
// product by SKU
app.get('/product/:sku', (req, res) => {
if(mongoConnected) {
collection.findOne({sku: req.params.sku}).then((product) => {
req.log.info('product', product);
if(product) {
res.json(product);
} else {
res.status(404).send('SKU not found');
}
}).catch((e) => {
req.log.error('ERROR', e);
res.status(500).send(e);
});
} else {
req.log.error('database not available');
res.status(500).send('database not available');
}
productsCollection.findOne({sku: req.params.sku}).then((product) => {
req.log.info('product', product);
if(product) {
res.json(product);
} else {
res.status(404).send('SKU not found');
}
}).catch((e) => {
req.log.error('ERROR', e);
res.status(500).send(e);
});
});
// products in a category
app.get('/products/:cat', (req, res) => {
if(mongoConnected) {
collection.find({ categories: req.params.cat }).sort({ name: 1 }).toArray().then((products) => {
if(products) {
res.json(products);
} else {
res.status(404).send('No products for ' + req.params.cat);
}
}).catch((e) => {
req.log.error('ERROR', e);
res.status(500).send(e);
});
} else {
req.log.error('database not available');
res.status(500).send('database not avaiable');
}
productsCollection.find({ categories: req.params.cat }).sort({ name: 1 }).toArray().then((products) => {
if(products) {
res.json(products);
} else {
res.status(404).send('No products for ' + req.params.cat);
}
}).catch((e) => {
req.log.error('ERROR', e);
res.status(500).send(e);
});
});
// all categories
app.get('/categories', (req, res) => {
if(mongoConnected) {
collection.distinct('categories').then((categories) => {
res.json(categories);
}).catch((e) => {
req.log.error('ERROR', e);
res.status(500).send(e);
});
} else {
req.log.error('database not available');
res.status(500).send('database not available');
}
productsCollection.distinct('categories').then((categories) => {
res.json(categories);
}).catch((e) => {
req.log.error('ERROR', e);
res.status(500).send(e);
});
});
// search name and description
app.get('/search/:text', (req, res) => {
if(mongoConnected) {
collection.find({ '$text': { '$search': req.params.text }}).toArray().then((hits) => {
res.json(hits);
}).catch((e) => {
req.log.error('ERROR', e);
res.status(500).send(e);
});
} else {
req.log.error('database not available');
res.status(500).send('database not available');
}
productsCollection.find({ '$text': { '$search': req.params.text }}).toArray().then((hits) => {
res.json(hits);
}).catch((e) => {
req.log.error('ERROR', e);
res.status(500).send(e);
});
});
// 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) => {
if(error) {
reject(error);
} else {
db = _db;
collection = db.collection('products');
resolve('connected');
}
});
});
}
// mongodb connection retry loop
function mongoLoop() {
mongoConnect().then((r) => {
mongoConnected = true;
logger.info('MongoDB connected');
}).catch((e) => {
logger.error('ERROR', e);
setTimeout(mongoLoop, 2000);
});
}
mongoLoop();
// fire it up!
const port = process.env.CATALOGUE_SERVER_PORT || '8080';
// Fire up
app.listen(port, () => {
logger.info('Started on port', port);
});

View File

@@ -8,12 +8,16 @@ import (
"math/rand"
"strconv"
"encoding/json"
"strings"
"errors"
"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 (
@@ -94,7 +98,7 @@ func getOrderId(order []byte) string {
return id
}
func createSpan(headers map[string]interface{}, order string) {
func createSpan(headers map[string]interface{}, amqpUri string, order string) {
// headers is map[string]interface{}
// carrier is map[string]string
carrier := make(ot.TextMapCarrier)
@@ -116,9 +120,10 @@ func createSpan(headers map[string]interface{}, order string) {
span = tracer.StartSpan("getOrder", ot.ChildOf(spanContext))
span.SetTag(string(ext.SpanKind), ext.SpanKindConsumerEnum)
span.SetTag(string(ext.MessageBusDestination), "robot-shop")
span.SetTag("messaging_bus.address", amqpUri)
span.SetTag("exchange", "robot-shop")
span.SetTag("sort", "consume")
span.SetTag("address", "rabbitmq")
//span.SetTag("address", "rabbitmq")
span.SetTag("key", "orders")
span.LogFields(otlog.String("orderid", order))
defer span.Finish()
@@ -160,7 +165,50 @@ 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)
}
log.Printf("Cloud Foundry detected")
bindingFound := false
services := appEnv.Services
for _, service := range services {
for _, serviceBinding := range service {
bindingName := serviceBinding.Name
if strings.Compare(bindingName, "dispatch_queue") == 0 {
bindingFound = true
log.Printf("RabbitMQ service binding '%s' found", bindingName)
amqpUri, _ = serviceBinding.CredentialString("uri")
}
}
}
if !bindingFound {
panic(errors.New("Service binding 'dispatch_queue' not found!"))
}
}
// get error threshold from environment
errorPercent = 0
@@ -203,7 +251,7 @@ func main() {
log.Printf("Order %s\n", d.Body)
log.Printf("Headers %v\n", d.Headers)
id := getOrderId(d.Body)
go createSpan(d.Headers, id)
go createSpan(d.Headers, amqpUri, id)
}
}
}()

1
load-gen/.profile Normal file
View File

@@ -0,0 +1 @@
export PATH=$PATH:/home/vcap/deps/0/python/bin

1
load-gen/Procfile Normal file
View File

@@ -0,0 +1 @@
web: locust -f robot-shop.py --no-web --host "$HOST" -c "$CLIENTS" -r 1

View File

@@ -1 +1 @@
locustio
locustio==0.11.0

1
load-gen/runtime.txt Normal file
View File

@@ -0,0 +1 @@
python-3.7.x

View File

@@ -3,8 +3,8 @@
//
db = db.getSiblingDB('catalogue');
db.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: 'HAL-1', name: 'HAL', description: 'Sorry Dave, I cant do that', price: 2001, instock: 2, categories: ['AI']},
{sku: 'PB-1', name: 'Positronic Brain', description: 'Highly advanced sentient processing unit with the laws of robotics burned in', price: 200, instock: 0, categories: ['AI']},
{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']},
@@ -12,7 +12,7 @@ db.products.insertMany([
{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: 'STAN-1', name: 'Stan', description: 'APM guru', price: 50, instock: 1000, categories: ['Robot', 'AI']},
{sku: 'STNG', name: 'Mr Data', description: 'Could be R. Daneel Olivaw? Protype positronic brain android', price: 1000, instock: 0, categories: ['Robot']}
]);

1
payment/Procfile Normal file
View File

@@ -0,0 +1 @@
web: python payment.py

View File

@@ -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
@@ -69,7 +82,7 @@ def pay(id):
app.logger.error(err)
return str(err), 500
if req.status_code != 200:
return 'payment error', req.status_code
return 'Payment error; \'{payment_gateway}\' answered with a status code \'{status_code}\''.format(payment_gateway=PAYMENT_GATEWAY, status_code=req.status_code), 503
# Generate order id
orderid = str(uuid.uuid4())
@@ -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,13 +102,13 @@ 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)
return str(err), 500
if req.status_code != 200:
return 'order history update error', req.status_code
return 'Cannot update order history', 500
return jsonify({ 'orderid': orderid })
@@ -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:

View File

@@ -1,25 +1,54 @@
import json
import pika
import os
import sys
import pika.exceptions as exceptions
from cfenv import AppEnv
from random import randrange
class Publisher:
HOST = os.getenv('AMQP_HOST', 'rabbitmq')
VIRTUAL_HOST = '/'
EXCHANGE='robot-shop'
TYPE='direct'
ROUTING_KEY = 'orders'
error_rate = 0
if 'ERROR_RATE' in os.environ:
error_rate = int(os.getenv('ERROR_RATE'))
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:
self._logger.info('Cloud Foundry detected')
if int(os.getenv('CF_INSTANCE_INDEX')) > 2:
# Crash horribly to show how we detect these scenarios
sys.exit(42)
env = AppEnv()
amqp_service = env.get_service(binding_name='dispatch_queue')
self._logger.info('Service binding \'{binding_name}\' found'.format(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
def _connect(self):
if not self._conn or self._conn.is_closed:
if not self._conn or self._conn.is_closed or self._channel is None or self._channel.is_closed:
self._conn = pika.BlockingConnection(self._params)
self._channel = self._conn.channel()
self._channel.exchange_declare(exchange=self.EXCHANGE, exchange_type=self.TYPE, durable=True)
@@ -34,14 +63,17 @@ class Publisher:
#Publish msg, reconnecting if necessary.
def publish(self, msg, headers):
if self._channel is None:
if self._channel is None or self._channel.is_closed or self._conn is None or self._conn.is_closed:
self._connect()
if error_rate > randrange(100):
raise exceptions.ConnectionWrongStateError(
'Connection is not open')
try:
self._publish(msg, headers)
except pika.exceptions.ConnectionClosed:
self._logger.info('reconnecting to queue')
self._connect()
self._publish(msg, headers)
self._logger.error('Connection to queue is closed')
def close(self):
if self._conn and self._conn.is_open:

View File

@@ -1,4 +1,5 @@
Flask
requests
pika
instana
cfenv
instana==1.15.3

View File

@@ -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 html/ /var/www/html

View File

@@ -0,0 +1,5 @@
{
"PHP_VERSION": "{PHP_72_LATEST}",
"LIBDIR": "htdocs/vendor",
"COMPOSER_VENDOR_DIR": "htdocs/vendor"
}

View File

@@ -0,0 +1,2 @@
extension=pdo.so
extension=pdo_mysql.so

View File

@@ -0,0 +1,2 @@
#extension=instana-x64-1.3.2.so
instana.socket=10.0.4.8:16816

View File

@@ -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
View 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
View 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": []
}

11
shipping/.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
.mvn/wrapper/maven-wrapper.jar

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2007-present the original author or 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.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}

View File

@@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

310
shipping/mvnw vendored Executable file
View File

@@ -0,0 +1,310 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

182
shipping/mvnw.cmd vendored Normal file
View File

@@ -0,0 +1,182 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

39
shipping/shipping.iml Normal file
View 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>

View File

@@ -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();

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,5 @@
#Generated by Maven
#Mon Aug 12 12:33:39 CEST 2019
groupId=steveww
artifactId=shipping
version=1.0

View File

@@ -0,0 +1,3 @@
org/steveww/spark/Ship.class
org/steveww/spark/Main.class
org/steveww/spark/Location.class

View File

@@ -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

Binary file not shown.

Binary file not shown.

63
user/package-lock.json generated Normal file
View File

@@ -0,0 +1,63 @@
{
"name": "user",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"complex.js": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.11.tgz",
"integrity": "sha512-6IArJLApNtdg1P1dFtn3dnyzoZBEF0MwMnrfF1exSBRpZYoy4yieMkpZhQDC0uwctw48vii0CFVyHfpgZ/DfGw=="
},
"decimal.js": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.0.tgz",
"integrity": "sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw=="
},
"escape-latex": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
"integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw=="
},
"fraction.js": {
"version": "4.0.12",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.12.tgz",
"integrity": "sha512-8Z1K0VTG4hzYY7kA/1sj4/r1/RWLBD3xwReT/RCrUCbzPszjNQCCsy3ktkU/eaEqX3MYa4pY37a52eiBlPMlhA=="
},
"javascript-natural-sort": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
"integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k="
},
"mathjs": {
"version": "6.6.4",
"resolved": "https://registry.npmjs.org/mathjs/-/mathjs-6.6.4.tgz",
"integrity": "sha512-fvmP89ujJbDAC8ths7FZh7PWdA71dfA5WJVAzJbQhSDCHK1aBk8WRf1XcTw51ERs+sKx9nYBGsRshqmb/oe8Ag==",
"requires": {
"complex.js": "^2.0.11",
"decimal.js": "^10.2.0",
"escape-latex": "^1.2.0",
"fraction.js": "^4.0.12",
"javascript-natural-sort": "^0.7.1",
"seed-random": "^2.2.0",
"tiny-emitter": "^2.1.0",
"typed-function": "^1.1.1"
}
},
"seed-random": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz",
"integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ="
},
"tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
},
"typed-function": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/typed-function/-/typed-function-1.1.1.tgz",
"integrity": "sha512-RbN7MaTQBZLJYzDENHPA0nUmWT0Ex80KHItprrgbTPufYhIlTePvCXZxyQK7wgn19FW5bnuaBIKcBb5mRWjB1Q=="
}
}
}

View File

@@ -9,13 +9,14 @@
"author": "SteveW",
"license": "Apache-2.0",
"dependencies": {
"body-parser": "^1.18.1",
"express": "^4.15.4",
"mongodb": "^2.2.33",
"redis": "^2.8.0",
"pino": "^5.10.8",
"express-pino-logger": "^4.0.0",
"pino-pretty": "^2.5.0",
"instana-nodejs-sensor": "^1.28.0"
"body-parser": "^1.18.1",
"express": "^4.15.4",
"express-pino-logger": "^4.0.0",
"instana-nodejs-sensor": "^1.28.0",
"mathjs": "^6.6.4",
"mongodb": "^2.2.33",
"pino": "^5.10.8",
"pino-pretty": "^2.5.0",
"redis": "^2.8.0"
}
}

View File

@@ -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,12 +10,15 @@ instana({
});
const mongoClient = require('mongodb').MongoClient;
const mongoObjectID = require('mongodb').ObjectID;
const redis = require('redis');
const bodyParser = require('body-parser');
const express = require('express');
const pino = require('pino');
const expPino = require('express-pino-logger');
const { randomInt } = require('mathjs');
const errorRate = parseInt(process.env.ERROR_RATE) || 0
// MongoDB
var db;
@@ -54,6 +59,11 @@ app.get('/health', (req, res) => {
// use REDIS INCR to track anonymous users
app.get('/uniqueid', (req, res) => {
if (errorRate > randomInt(1, 100)) {
res.status(500).send('Cannot process the uniqueid request');
return;
}
req.log.error('Unique ID test');
// get number from Redis
redisClient.incr('anonymous-counter', (err, r) => {
@@ -70,6 +80,11 @@ app.get('/uniqueid', (req, res) => {
// check user exists
app.get('/check/:id', (req, res) => {
if (errorRate > randomInt(1, 100)) {
res.status(500).send('Cannot process the check user request');
return;
}
if(mongoConnected) {
usersCollection.findOne({name: req.params.id}).then((user) => {
if(user) {
@@ -103,6 +118,11 @@ app.get('/users', (req, res) => {
});
app.post('/login', (req, res) => {
if (errorRate > randomInt(1, 100)) {
res.status(500).send('Cannot process the login request');
return;
}
req.log.info('login', req.body);
if(req.body.name === undefined || req.body.password === undefined) {
req.log.warn('credentails not complete');
@@ -133,6 +153,11 @@ app.post('/login', (req, res) => {
// TODO - validate email address format
app.post('/register', (req, res) => {
if (errorRate > randomInt(1, 100)) {
res.status(500).send('Cannot process the registration request');
return;
}
req.log.info('register', req.body);
if(req.body.name === undefined || req.body.password === undefined || req.body.email === undefined) {
req.log.warn('insufficient data');
@@ -168,6 +193,11 @@ app.post('/register', (req, res) => {
});
app.post('/order/:id', (req, res) => {
if (errorRate > randomInt(1, 100)) {
res.status(500).send('Cannot process the order request');
return;
}
req.log.info('order', req.body);
// only for registered users
if(mongoConnected) {
@@ -222,6 +252,11 @@ app.post('/order/:id', (req, res) => {
});
app.get('/history/:id', (req, res) => {
if (errorRate > randomInt(1, 100)) {
res.status(500).send('Cannot process the history request');
return;
}
if(mongoConnected) {
ordersCollection.findOne({
name: req.params.id
@@ -241,9 +276,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 +344,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
View File

@@ -0,0 +1,3 @@
---
nginx:
version: 1.17.2

View File

@@ -0,0 +1,5 @@
{
"service": "web",
"agent_host": "instana-agent",
"agent_port": 42699
}

Binary file not shown.

78
web/mime.types Normal file
View 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;
}

Binary file not shown.

76
web/nginx.conf Normal file
View File

@@ -0,0 +1,76 @@
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";
opentracing_tag "endpoint" "static_resources";
add_header "server-timing" "intid;desc=${opentracing_context_x_instana_t}";
}
}
}

View File

@@ -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
View 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>

View File

@@ -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;