From 3a0c5487db9f795f52f416556c5b01cf4f2b2f9c Mon Sep 17 00:00:00 2001 From: darthmaim Date: Fri, 11 May 2018 00:17:47 +0200 Subject: [PATCH 1/3] Query both api datacenters --- frontend/src/ResultsRow.js | 126 ++++++++++++++++++++++++----------- frontend/src/ResultsTable.js | 25 +++---- frontend/src/sideEffects.js | 36 ++++++---- worker/src/requestApi.js | 35 +++++++++- worker/src/runTests.js | 11 ++- worker/src/testEndpoint.js | 27 +++----- 6 files changed, 173 insertions(+), 87 deletions(-) diff --git a/frontend/src/ResultsRow.js b/frontend/src/ResultsRow.js index b14116f..9b4146a 100644 --- a/frontend/src/ResultsRow.js +++ b/frontend/src/ResultsRow.js @@ -1,5 +1,85 @@ import {h, Component} from 'preact' +function renderStatus(endpoint) { + const servers = Object.entries(endpoint.servers) + const working = servers.filter(([,{status}]) => status < 400) + const broken = servers.filter(([,{status}]) => status >= 400) + + if(broken.length === 0) { + return () + } + + return servers.map( + ([server, {status, error}]) => ( + status < 400 ? (
+ {server.toUpperCase()}
+ ) : ( +
+ {server.toUpperCase()} +
{status}
+
{error || 'Unknown error'}
+
+ ) + ) + ) +} + +function renderSchema(endpoint) { + const servers = Object.entries(endpoint.servers) + + if(servers.every(([, {schemaValid}]) => schemaValid === undefined)) { + return ('—') + } + + const working = servers.filter(([,{schemaValid}]) => schemaValid) + const broken = servers.filter(([,{schemaValid}]) => !schemaValid) + + if(broken.length === 0) { + return () + } + + return servers.map( + ([server, {schemaValid, schemaChanges}]) => ( + schemaValid ? (
+ {server.toUpperCase()}
+ ) : ( +
+ {server.toUpperCase()} +
{schemaChanges}
+
+ ) + ) + ) +} + +function renderSnapshot(endpoint) { + const servers = Object.entries(endpoint.servers) + + if(servers.every(([, {snapshotValid}]) => snapshotValid === undefined)) { + return ('—') + } + + const working = servers.filter(([,{snapshotValid}]) => snapshotValid) + const broken = servers.filter(([,{snapshotValid}]) => !snapshotValid) + + if(broken.length === 0) { + return () + } + + return servers.map( + ([server, {snapshotValid, snapshotChanges}]) => ( + snapshotValid ? (
+ {server.toUpperCase()}
+ ) : ( +
+ {server.toUpperCase()} +
{snapshotChanges}
+
+ ) + ) + ) +} + class ResultsRow extends Component { render () { const {data, className, onClick} = this.props @@ -10,61 +90,29 @@ class ResultsRow extends Component { {data.name} - {data.status < 400 && ( - - )} - - {data.status >= 400 && ( -
- -
{data.status}
-
{data.error || 'Unknown error'}
-
- )} + {renderStatus(data)} - {data.schemaValid === undefined && '—'} - - {data.schemaValid !== undefined && data.schemaValid && ( - - )} - - {data.schemaValid !== undefined && !data.schemaValid && ( -
- -
{data.schemaChanges}
-
- )} + {renderSchema(data)} - {data.snapshotValid === undefined && '—'} - - {data.snapshotValid !== undefined && data.snapshotValid && ( - - )} - - {data.snapshotValid !== undefined && !data.snapshotValid && ( -
- -
{data.snapshotChanges}
-
- )} + {renderSnapshot(data)}
- {data.duration <= 1500 && ( + {data.servers.eu.duration <= 1500 && ( )} - {data.duration > 1500 && data.duration <= 3000 && ( + {data.servers.eu.duration > 1500 && data.servers.eu.duration <= 3000 && ( )} - {data.duration > 3000 && ( + {data.servers.eu.duration > 3000 && ( )} -
{data.duration !== 0 ? `${data.duration.toLocaleString()} ms` : ''}
+
{data.servers.eu.duration !== 0 ? `${data.servers.eu.duration.toLocaleString()} ms` : ''}
diff --git a/frontend/src/ResultsTable.js b/frontend/src/ResultsTable.js index 52c00eb..edb9e7e 100644 --- a/frontend/src/ResultsTable.js +++ b/frontend/src/ResultsTable.js @@ -52,22 +52,19 @@ class ResultsTable extends Component { const count = workingCount === results.data.length ? 'All' : workingCount const text = `${prefix} ${count} endpoints fully operational 🎉` - const averageDuration = Math.round(average(workingResults.map(x => x.duration))) - - const data = { - name: text, - status: 200, - schemaValid: true, - snapshotValid: true, - duration: averageDuration - } + const averageDuration = Math.round(average(workingResults.map(x => x.servers.eu.duration))) return ( - this.setState({expanded: !this.state.expanded})} - className='result-row--summary' - /> + this.setState({expanded: !this.state.expanded})} className="result-row--summary"> + {text} + + + +
+ +
{averageDuration.toLocaleString()}ms
+
+ ) } } diff --git a/frontend/src/sideEffects.js b/frontend/src/sideEffects.js index 3dbbf0f..88a1ec0 100644 --- a/frontend/src/sideEffects.js +++ b/frontend/src/sideEffects.js @@ -9,7 +9,7 @@ export async function getLatestTest () { return endpoint }) - result.data.sort((a, b) => b.duration - a.duration) + result.data.sort((a, b) => b.servers.eu.duration - a.servers.eu.duration) result.data.sort((a, b) => b.severity - a.severity) result.updated_at = new Date(result.updated_at) @@ -18,21 +18,29 @@ export async function getLatestTest () { } function calculateSeverity (endpoint) { - if (endpoint.status >= 400) { - return 3 + const severity = Object.values(endpoint.servers).map( + (server) => { + if (server.status >= 400) { + return 3 + } + + if (server.schemaValid === false || server.snapshotValid === false) { + return 2 + } + + return 0 + } + ).reduce( + (total, severity) => total + severity, 0 + ) + + if (endpoint.servers.eu.duration > 3000) { + return severity + 1 } - if (endpoint.schemaValid === false || endpoint.snapshotValid === false) { - return 2 + if (endpoint.servers.eu.duration > 1500) { + return severity + 0.5 } - if (endpoint.duration > 3000) { - return 1 - } - - if (endpoint.duration > 1500) { - return 0.5 - } - - return 0 + return severity } diff --git a/worker/src/requestApi.js b/worker/src/requestApi.js index af86440..4c2c916 100644 --- a/worker/src/requestApi.js +++ b/worker/src/requestApi.js @@ -1,11 +1,44 @@ const fetch = require('node-fetch') +const { Agent } = require('https'); + +const server = { + us: createApiAgent('34.226.105.80'), + eu: createApiAgent('52.58.154.210') +}; + +function createApiAgent (ip) { + // create new https agent + const agent = new Agent() + + // save original createConnection function + const createConnection = agent.createConnection + + // override createConnection + agent.createConnection = function(opts, callback) { + // set custom dns lookup resolving to either us or eu datacenter + opts.lookup = (_1, _2, cb) => cb(null, ip, 4) + + // call original create connection call + return createConnection.call(agent, opts, callback) + } + + return agent +} async function requestApi (url) { + return Promise.all([server.us, server.eu].map( + (agent) => requestApiInternal(url, agent) + )).then( + ([us, eu]) => ({us, eu}) + ) +} + +async function requestApiInternal (url, agent) { const start = new Date().getTime() let response = null try { - response = await fetch(url) + response = await fetch(url, { agent }) } catch (err) {} const end = new Date().getTime() diff --git a/worker/src/runTests.js b/worker/src/runTests.js index 9b4049c..2e345eb 100644 --- a/worker/src/runTests.js +++ b/worker/src/runTests.js @@ -12,8 +12,15 @@ async function runTests () { results.push(await testEndpoint(endpoints[i])) } - let possiblyBroken = results.filter(x => x.schemaValid === false || x.snapshotValid === false) - let definitelyBroken = results.filter(x => x.status !== 200) + let possiblyBroken = results.filter( + (endpoint) => Object.values(endpoint.servers).some( + (server) => server.schemaValid === false || server.snapshotValid === false + ) + ) + + let definitelyBroken = results.filter( + (endpoint) => Object.values(endpoint.servers).some((server) => server.status !== 200) + ) if (definitelyBroken.length > 0) { console.log(`❗ ERROR: ${definitelyBroken.length} API endpoint(s)`) diff --git a/worker/src/testEndpoint.js b/worker/src/testEndpoint.js index f47afbe..216bfea 100644 --- a/worker/src/testEndpoint.js +++ b/worker/src/testEndpoint.js @@ -5,27 +5,20 @@ const matchSnapshot = require('./matchSnapshot') async function testEndpoint (endpoint) { const url = generateUrl(endpoint.url) - const {duration, response} = await requestApi(url) + const requests = await requestApi(url) - const error = response.status >= 400 && response.content && response.content.text - - let result = { + return { url, name: endpoint.name, - status: response.status, - duration, - error - } - - if (result.status !== 200) { - return result + servers: Object.entries(requests) + .reduce((a, [server, {response: {status, content}, duration }]) => ({ + ...a, [server]: { + status, duration, + ...(endpoint.matchSchema ? matchSchema(endpoint, content) : {}), + ...(endpoint.matchSnapshot ? matchSnapshot(endpoint, content) : {}) + } + }), []) } - - return Object.assign( - result, - endpoint.matchSchema ? matchSchema(endpoint, response.content) : {}, - endpoint.matchSnapshot ? matchSnapshot(endpoint, response.content) : {} - ) } module.exports = testEndpoint From 31f89f39933fffc6c27665d296728deca11c645d Mon Sep 17 00:00:00 2001 From: darthmaim Date: Fri, 11 May 2018 12:58:37 +0200 Subject: [PATCH 2/3] Remove hardcoded server list --- worker/src/requestApi.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/worker/src/requestApi.js b/worker/src/requestApi.js index 4c2c916..0a64e39 100644 --- a/worker/src/requestApi.js +++ b/worker/src/requestApi.js @@ -26,10 +26,12 @@ function createApiAgent (ip) { } async function requestApi (url) { - return Promise.all([server.us, server.eu].map( - (agent) => requestApiInternal(url, agent) + return Promise.all(Object.entries(servers).map( + ([server, agent]) => [server, requestApiInternal(url, agent)] )).then( - ([us, eu]) => ({us, eu}) + (servers) => servers.reduce( + (servers, [server, result]) => ({...servers, [server]: result}), {} + ) ) } From 25c5ced4c6ba123c088191f63691686f02e5508c Mon Sep 17 00:00:00 2001 From: darthmaim Date: Fri, 11 May 2018 13:50:55 +0200 Subject: [PATCH 3/3] Collapse errors when all servers have the same error --- frontend/src/ResultsRow.js | 77 +++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/frontend/src/ResultsRow.js b/frontend/src/ResultsRow.js index 9b4146a..969cc34 100644 --- a/frontend/src/ResultsRow.js +++ b/frontend/src/ResultsRow.js @@ -1,25 +1,34 @@ import {h, Component} from 'preact' +function renderSuccess(server = '') { + return ( +
+ {server.toUpperCase()} +
+ ) +} + +function renderError(server = '', text, details) { + return ( +
+ {server.toUpperCase()} + {text && (
{text}
)} +
{details}
+
+ ) +} + function renderStatus(endpoint) { const servers = Object.entries(endpoint.servers) - const working = servers.filter(([,{status}]) => status < 400) const broken = servers.filter(([,{status}]) => status >= 400) if(broken.length === 0) { - return () + return renderSuccess(); } return servers.map( ([server, {status, error}]) => ( - status < 400 ? (
- {server.toUpperCase()}
- ) : ( -
- {server.toUpperCase()} -
{status}
-
{error || 'Unknown error'}
-
- ) + status < 400 ? renderSuccess(server) : renderError(server, status, error || 'Unknown error') ) ) } @@ -31,23 +40,26 @@ function renderSchema(endpoint) { return ('—') } - const working = servers.filter(([,{schemaValid}]) => schemaValid) const broken = servers.filter(([,{schemaValid}]) => !schemaValid) if(broken.length === 0) { - return () + return renderSuccess() } + const firstServer = servers[0][1]; + const sameError = servers.every( + ([, {schemaValid, schemaChanges}]) => + schemaValid === firstServer.schemaValid && + schemaChanges === firstServer.schemaChanges + ) + + if(sameError) { + return renderError('', null, (
{firstServer.schemaChanges}
)) + } + return servers.map( ([server, {schemaValid, schemaChanges}]) => ( - schemaValid ? (
- {server.toUpperCase()}
- ) : ( -
- {server.toUpperCase()} -
{schemaChanges}
-
- ) + schemaValid ? renderSuccess(server) : renderError(server, null, (
{schemaChanges}
)) ) ) } @@ -59,23 +71,26 @@ function renderSnapshot(endpoint) { return ('—') } - const working = servers.filter(([,{snapshotValid}]) => snapshotValid) const broken = servers.filter(([,{snapshotValid}]) => !snapshotValid) if(broken.length === 0) { - return () + return renderSuccess(); + } + + const firstServer = servers[0][1]; + const sameError = servers.every( + ([, {snapshotValid, snapshotChanges}]) => + snapshotValid === firstServer.snapshotValid && + snapshotChanges === firstServer.snapshotChanges + ) + + if(sameError) { + return renderError('', null, (
{firstServer.snapshotChanges}
)) } return servers.map( ([server, {snapshotValid, snapshotChanges}]) => ( - snapshotValid ? (
- {server.toUpperCase()}
- ) : ( -
- {server.toUpperCase()} -
{snapshotChanges}
-
- ) + snapshotValid ? renderSuccess(server) : renderError(server, null, (
{snapshotChanges}
)) ) ) }