Add programmable error rate to catalogue and user

This commit is contained in:
Michele Mancioppi
2020-06-07 12:53:50 +02:00
parent 4470ebfa1c
commit f3e849e90b
5 changed files with 223 additions and 150 deletions

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/collector": "^1.28.0"
"@instana/collector": "^1.28.0",
"mathjs": "^6.6.4"
}
}

View File

@@ -26,7 +26,80 @@ const expLogger = expPino({
// MongoDB
var productsCollection;
var mongoConnected = false;
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,163 +124,64 @@ app.get('/health', (req, res) => {
// all products
app.get('/products', (req, res) => {
if(mongoConnected) {
productsCollection.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) {
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);
});
} 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) {
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);
});
} 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) {
productsCollection.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) {
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);
});
} 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;
if (process.env.VCAP_SERVICES) {
connectionDetails = null;
console.log('Env var \'VCAP_SERVICES\' found, scanning for \'catalogue_database\' service binding');
for (let [key, value] of Object.entries(JSON.parse(process.env.VCAP_SERVICES))) {
try {
binding = value.find(function(binding) {
return 'catalogue_database' == binding.binding_name && binding.credentials;
});
if (!binding) {
continue;
}
connectionDetails = binding.credentials;
if (connectionDetails) {
mongoURL = connectionDetails.uri;
console.log('MongoDB URI for \'catalogue_database\' service binding found in \'VCAP_SERVICES\'');
break;
}
} catch (err) {
console.log('Cannot process key \'' + key + '\' of \'VCAP_SERVICES\'', err);
throw err;
}
}
} else if (process.env.MONGO_URL) {
mongoURL = process.env.MONGO_URL;
console.log('MongoDB URI found in \'MONGO_URL\': ' + mongoURL);
} else {
mongoURL = 'mongodb://mongodb:27017/catalogue';
console.log('Using default MongoDB URI');
}
if (!mongoURL) {
throw new Error('MongoDB connection data missing');
}
mongoClient.connect(mongoURL, (error, db) => {
if(error) {
reject(error);
} else {
productsCollection = db.collection('products');
resolve('connected');
}
});
});
}
// mongodb connection retry loop
function mongoLoop() {
mongoConnect().then((r) => {
mongoConnected = true;
logger.info('MongoDB connected');
}).catch((e) => {
logger.error('An error occurred', 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);
});

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

@@ -15,6 +15,10 @@ 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;
@@ -55,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) => {
@@ -71,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) {
@@ -104,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');
@@ -134,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');
@@ -169,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) {
@@ -223,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