Skip to content
This repository was archived by the owner on Apr 19, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions lib/storage/providers/redis.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,41 @@ StorageRedis.prototype.getServiceOutagesSince = function (service, timestamp, ca
});
};

/**
* Get outage history for all services
* @param services
* @param options
* @param callback
*/

StorageRedis.prototype.getServicesOutagesSince = function (services, options, callback) {
// Creating a multi request to get each service outages.
var multi = this.redis.multi();
for (var i = 0; i < services.length; i++) {
multi.zrevrangebyscore(
services[i].id + ':' + OUTAGES_KEY_SUFIX,
'+inf',
options.since,
'LIMIT',
0,
options.maxItemsPerService
);
}

multi.exec(function (err, replies) {
if (err) {
return callback(err, null);
}
if(options.grouping) {
var servicesOutages = parseOutagesGroupedByServiceFromMulti(services, replies);
} else {
servicesOutages = parseOutagesFromMulti(services, replies).slice(0, options.maxItems);
}

callback(null, servicesOutages);
})
};

/**
* Records ping latency
* @param service
Expand Down Expand Up @@ -312,3 +347,54 @@ function parseLatencyDataFromZset(zset) {
mean: Math.round(accLatency / list.length)
};
}

/**
* Parses an array of serialized outages from a multi request into a timestamp sorted list of parsed outages.
*
* @param {Array} services The list of services requested.
* @param {Array} replies The outages replies for each requested services.
* @returns {Array} The completed list of outages for requested services sorted by timestamp in descending order.
*/
function parseOutagesFromMulti(services, replies) {
var servicesOutages = [];
for (var i = 0; i < services.length; i++) {
var svcOutages = replies[i].map(function (outage) {
outage = JSON.parse(outage);
outage['service'] = services[i].id;
return outage;
});

servicesOutages = servicesOutages.concat(svcOutages);
}

// Sorting by timestamp
servicesOutages.sort(function (a, b) {
if (a.timestamp < b.timestamp)
return -1;
if (a.timestamp < b.timestamp)
return 1;
return 0;
});
return servicesOutages;
}

/**
* Parses an array of serialized outages from a multi request into a list of outages sorted by timestamp in descending
* order and grouped by service ID. Services without outages won't be returned.
*
* @param {Array} services The list of services requested.
* @param {Array} replies The outages replies for each requested services.
* @returns {Array} The completed list of outages for requested services sorted by timestamp in descending order and grouped by service ID.
*/
function parseOutagesGroupedByServiceFromMulti(services, replies) {
var servicesOutages = {};

for(var i = 0; i < services.length; i++) {
var outages = replies[i].map(JSON.parse);
if(outages.length > 0) {
servicesOutages[services[i].id] = outages;
}
}

return servicesOutages;
}
149 changes: 149 additions & 0 deletions test/test-api-report-route.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ var passport = require('passport');
var mockPassport = require('passport-mock');
var superAgentAssertions = require('./lib/util/super-agent-assertions');
var storageFactory = require('../lib/storage/storage-factory');
var async = require('async');
var sinon = require('sinon');

var storage = storageFactory.getStorageInstance('test');
var app = require('../webserver/app')(storage);
Expand All @@ -15,6 +17,12 @@ describe('report route', function () {
var PORT = 3355;
var validService;

var SECOND = 1000;
var MINUTE = SECOND * 60; //ms
var HOUR = MINUTE * 60; //ms
var INITIAL_TIME = 946684800000;
var clock;

var USERS = [
{id: 1, email: 'admin@domain.com', isAdmin: true},
{id: 2, email: 'user@domain.com', isAdmin: false}
Expand All @@ -24,6 +32,50 @@ describe('report route', function () {

var agent = request.agent(app);

function addOutageRecords(service, outageData, outageDuration, outageInterval, callback) {
function addOutage(outage, cb) {
outage.timestamp = +new Date();
storage.startOutage(service, outage, function (err) {
assert.ifError(err);
clock.tick(outageDuration);
storage.archiveCurrentOutageIfExists(service, function (err) {
clock.tick(outageInterval);
cb();
});
});
}

async.eachSeries(outageData, addOutage, callback);
}

function testOnServiceWithOutages(url, done, callback) {
storage.addService(validService, function (err, id) {
assert.ifError(err);
validService.id = id;
var outageData = [];
for (var i = 0; i < 12; i++) {
outageData.push({error: 'my error'});
}

var outageDuration = 4 * MINUTE;
var outageInterval = HOUR;

addOutageRecords(validService, outageData, outageDuration, outageInterval, function () {
agent
.get(url)
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200)
.send()
.end(function (err, res) {
assert.ifError(err);
callback(res);
done(err);
});
});
});
}

before(function (done) {

var mock = mockPassport(passport, USERS);
Expand Down Expand Up @@ -55,6 +107,9 @@ describe('report route', function () {
failureInterval: 30000,
warningThreshold: 30000
};

clock = sinon.useFakeTimers(INITIAL_TIME);

storage.flush_database(done);
});

Expand Down Expand Up @@ -306,4 +361,98 @@ describe('report route', function () {
});
});

describe('loading all outages', function () {

describe('with an anonymous user', function () {
before(function (done) {
agent.get('/logout').expect(302, done);
});

it('should not require auth', function (done) {
testOnServiceWithOutages(
API_ROOT + '/services/outages?since=0',
done,
function (res) {
assert.equal(res.body.length, 10);
assert.equal(res.body[0].service, validService.id);
});
});
});

describe('with an authenticated normal user', function () {
beforeEach(function (done) {
agent.get('/login/test/2').expect(200, done);
});

it('should have not access if restrictions are applied but user is not included', function (done) {
validService.restrictedTo = "other@domain.com";

testOnServiceWithOutages(
API_ROOT + '/services/outages?since=0',
done,
function (res) {
assert.equal(res.body.length, 0);
}
);
});

it('should have access if restrictions include the current user', function (done) {
validService.restrictedTo = "user@domain.com";
testOnServiceWithOutages(
API_ROOT + '/services/outages?since=0',
done,
function (res) {
assert.equal(res.body.length, 10);
}
);
});

it('should respect the max-items parameter', function (done) {
testOnServiceWithOutages(
API_ROOT + '/services/outages?since=0&max-items=5',
done,
function (res) {
assert.equal(res.body.length, 5);
}
);
});

it('should respect the max-items-per-service parameter', function (done) {
testOnServiceWithOutages(
API_ROOT + '/services/outages?since=0&max-items-per-service=4',
done,
function (res) {
assert.equal(res.body.length, 4);
}
);
});

it('should respect the grouping parameter', function (done) {
testOnServiceWithOutages(
API_ROOT + '/services/outages?since=0&grouping=1',
done,
function (res) {
assert.ok(Array.isArray(res.body[validService.id]));
assert.equal(res.body[validService.id].length, 10);
}
);
});
});

describe('with an authenticated admin user', function () {
beforeEach(function (done) {
agent.get('/login/test/1').expect(200, done);
});

it('should have access to all services', function (done) {
testOnServiceWithOutages(
API_ROOT + '/services/outages?since=0',
done,
function (res) {
assert.equal(res.body.length, 10);
}
);
});
});
});
});
46 changes: 24 additions & 22 deletions webserver/public/build/scripts.js

Large diffs are not rendered by default.

Loading