Skip to content

Commit

Permalink
Add EC2 tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Serhii Popov committed Jun 16, 2020
1 parent 87c9f60 commit c0f3899
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 31 deletions.
14 changes: 5 additions & 9 deletions package/src/autoscaling-scheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ class AutoScalingScheduler {
*
* @param {String} action Perform an action name
* @param {Array} resourceTags "{tag:value}" pairs to use for filter resources
* @param callback
* @returns {Promise<void>}
*/
async run(action, resourceTags, callback) {
async run(action, resourceTags) {
if (!resourceTags) {
throw new Error('"resourceTags" must be specified, otherwise you will shoutdown all instances');
}
Expand All @@ -30,12 +29,10 @@ class AutoScalingScheduler {
let asgData = await this.autoScaling.describeAutoScalingGroups().promise();
for (const asg of asgData.AutoScalingGroups) {
if (asg.Tags.length && Utils.matchTags(resourceTags, asg.Tags)) {
let data = await this[action](asg);
//callback(null, data);
await this[action](asg);
}
}
} catch (e) {
//callback(e, null);
console.error(e.stack);
}
}
Expand All @@ -51,9 +48,8 @@ class AutoScalingScheduler {
AutoScalingGroupName: asg.AutoScalingGroupName,
};
let data = await this.autoScaling.suspendProcesses(params).promise();
console.log(`Suspend AutoScaling group ${asg.AutoScalingGroupName}`, JSON.stringify(data));

//let instanceIds = asg.Instances.forEach((instance) => { instance.InstanceId });
console.log(`Suspend AutoScaling group ${asg.AutoScalingGroupName}`, JSON.stringify(data));

for (const instance of asg.Instances) {
let params = {
Expand All @@ -68,6 +64,7 @@ class AutoScalingScheduler {
data = await this.ec2.stopInstances(params).promise();
} catch (e) {
// Otherwise try to terminate it (in most cases for EC2 Spot instances)
//console.log("I'm going to terminate instance");
data = await this.ec2.terminateInstances(params).promise();
}
console.log(`Stop EC2 instance ${instance.InstanceId}`, JSON.stringify(data));
Expand Down Expand Up @@ -99,9 +96,8 @@ class AutoScalingScheduler {
// @todo Start only instances which can be started: "Values": ["pending", "stopping", "stopped"],
let data = await this.ec2.startInstances(params).promise();

console.log(`Stop EC2 instance ${instance.InstanceId}`, JSON.stringify(data));
console.log(`Start EC2 instance ${instance.InstanceId}`, JSON.stringify(data));
}
return data;
}
}

Expand Down
7 changes: 3 additions & 4 deletions package/src/ec2-scheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ class Ec2Scheduler {
*
* @param action String
* @param resourceTags Array {key:value} pairs to use for filter resources
* @param callback
* @returns {Promise<void>}
*/
async run(action, resourceTags, callback) {
async run(action, resourceTags) {
if (!resourceTags) {
throw new Error('Resource tags must be specified otherwise you will shoutdown all instances');
}
Expand Down Expand Up @@ -49,16 +48,16 @@ class Ec2Scheduler {
};

let autoScaling = await this.autoScaling.describeAutoScalingInstances(asParams).promise();
console.log('autoScaling', autoScaling);

if (!autoScaling.AutoScalingInstances.length) {
let data = await this[action](instance.InstanceId);

//callback(null, data);
console.log(`${Utils.ucFirst(action)} EC2 instance ${instance.InstanceId}`, JSON.stringify(data));
}
}
}
} catch (e) {
//callback(e, null);
console.error(e.stack);
}
}
Expand Down
87 changes: 87 additions & 0 deletions package/test/unit/autoscaling-scheduler.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Great tutorial how to use Sinon @link https://semaphoreci.com/community/tutorials/best-practices-for-spies-stubs-and-mocks-in-sinon-js
// Use sinon assertions and matchers where it is possible https://sinonjs.org/releases/v9.0.2/matchers/
require('app-module-path').addPath(process.cwd() + '/src');

const AWSMock = require('aws-sdk-mock');
const AWS = require('aws-sdk');
const chai = require('chai');
const sinon = require('sinon');
const AutoScalingScheduler = require('autoscaling-scheduler');

describe('AWS AutoScaling Group Lambda Scheduler', () => {
let consoleLogStub = null;
beforeEach(() => {
AWSMock.setSDKInstance(AWS);
// Ignore console.log() output
consoleLogStub = sinon.stub(console, 'log');
});

afterEach(() => {
AWSMock.restore();
consoleLogStub.restore();
});

[
{ action: 'stop', method: 'stopInstances', processMethod: 'suspendProcesses', responseKey: 'StoppingInstances' },
{ action: 'start', method: 'startInstances', processMethod: 'resumeProcesses', responseKey: 'StartingInstances' },
].forEach(function (run) {
it(`run: "${run.method}" should be called once`, async () => {
let tags = [{ "Key": "ToStop", "Value": "true" }, { "Key": "Environment", "Value": "test" }];

// Important creating the spy/sub in such way, because there are several calls to AWS under the hood
let actionInstancesSpy = sinon.spy((params, callback) => {
callback(null, { [run.responseKey]: [{ InstanceId: "TEST-EC2-ID-123" }] });
})

// Mock successful execution
AWSMock.mock('EC2', run.method, actionInstancesSpy);

AWSMock.mock('AutoScaling', 'describeAutoScalingGroups', async (callback) => {
callback(null, { AutoScalingGroups: [
{
Tags: tags,
AutoScalingGroupName: "TEST-AUTO-SCALING-GROUP-NAME",
Instances: [{ InstanceId: "TEST-EC2-ID-123" }]
}
]});
});
AWSMock.mock('AutoScaling', run.processMethod, async (params, callback) => {
callback(null, { });
});

let spotScheduler = new AutoScalingScheduler('eu-central-1');
await spotScheduler.run(run.action, tags);

// Assert on your Sinon spy as normal
sinon.assert.calledOnce(actionInstancesSpy);
sinon.assert.calledWith(actionInstancesSpy, { InstanceIds: ['TEST-EC2-ID-123'] });
});
});

it(`stop: "terminateInstances should be called if stopInstances throw Error`, async () => {
// Important creating the spy/sub in such way, because there are several calls to AWS under the hood
let stopInstancesSpy = sinon.spy((params, callback) => {
throw Error();
});
AWSMock.mock('EC2', 'stopInstances', stopInstancesSpy);

let terminateInstancesSpy = sinon.spy((params, callback) => {
callback(null, { 'TerminateInstances': [{ InstanceId: "TEST-EC2-ID-123" }] });
})
AWSMock.mock('EC2', 'terminateInstances', terminateInstancesSpy);

AWSMock.mock('AutoScaling', 'suspendProcesses', async (params, callback) => {
callback(null, { });
});

let spotScheduler = new AutoScalingScheduler('eu-central-1');
await spotScheduler.stop({
AutoScalingGroupName: "TEST-AUTO-SCALING-GROUP-NAME",
Instances: [{ InstanceId: "TEST-EC2-ID-123" }]
});

// Assert on your Sinon spy as normal
sinon.assert.threw(stopInstancesSpy);
sinon.assert.calledWith(terminateInstancesSpy, { InstanceIds: ['TEST-EC2-ID-123'] });
});
});
59 changes: 59 additions & 0 deletions package/test/unit/ec2-scheduler.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Great tutorial how to use Sinon @link https://semaphoreci.com/community/tutorials/best-practices-for-spies-stubs-and-mocks-in-sinon-js
// Use sinon assertions and matchers where it is possible https://sinonjs.org/releases/v9.0.2/matchers/
require('app-module-path').addPath(process.cwd() + '/src');

const AWSMock = require('aws-sdk-mock');
const AWS = require('aws-sdk');
const chai = require('chai');
const sinon = require('sinon');
const Ec2Scheduler = require('ec2-scheduler');

describe('AWS EC2 Lambda Scheduler', () => {
let consoleLogStub = null;
beforeEach(() => {
AWSMock.setSDKInstance(AWS);
// Ignore console.log() output
consoleLogStub = sinon.stub(console, 'log');
});

afterEach(() => {
AWSMock.restore();
consoleLogStub.restore();
});

[
{ action: 'stop', method: 'stopInstances', responseKey: 'StoppingInstances' },
{ action: 'start', method: 'startInstances', responseKey: 'StartingInstances' },
].forEach(function (run) {
it(`run: "${run.method}" should be called once`, async () => {
let tags = [{ "Key": "ToStop", "Value": "true" }, { "Key": "Environment", "Value": "test" }];

// Important creating the spy/sub in such way, because there are several calls to AWS under the hood
let actionInstancesSpy = sinon.spy((params, callback) => {
callback(null, { [run.responseKey]: [{ InstanceId: "TEST-EC2-ID-123" }] });
})

// Mock successful execution
AWSMock.mock('EC2', run.method, actionInstancesSpy);

AWSMock.mock('EC2', 'describeInstances', async (params, callback) => {
callback(null, { Reservations: [
{
Instances: [{ InstanceId: "TEST-EC2-ID-123" }]
}
]});
});

AWSMock.mock('AutoScaling', 'describeAutoScalingInstances', async (params, callback) => {
callback(null, { AutoScalingInstances: []});
});

let spotScheduler = new Ec2Scheduler('eu-central-1');
await spotScheduler.run(run.action, tags);

// Assert on your Sinon spy as normal
sinon.assert.calledOnce(actionInstancesSpy);
sinon.assert.calledWith(actionInstancesSpy, { InstanceIds: ['TEST-EC2-ID-123'] });
});
});
});
28 changes: 13 additions & 15 deletions package/test/unit/rds-scheduler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,20 @@ chai.use(chaiAsPromised);
let expect = chai.expect;
let assert = chai.assert;

let consoleLogStub = null;

describe('AWS RDS Lambda Scheduler', () => {
beforeEach(function() {
AWSMock.setSDKInstance(AWS);
consoleLogStub = sinon.stub(console, 'log');
});

afterEach(function() {
// Important! Restore AWS SDK
AWSMock.restore();

// Ignore console.log() output
consoleLogStub.restore();
});

it('run: resourceTags cannot be empty', async() => {
Expand All @@ -26,8 +37,7 @@ describe('AWS RDS Lambda Scheduler', () => {
});

it('run: "stop" action should be called once', async() => {
// Ignore console.log() output
let consoleLogSpy = sinon.stub(console, 'log');
//let consoleLogSpy = sinon.stub(console, 'log');

let tags = [{ "Key": "ToStop", "Value": "true" }, { "Key": "Environment", "Value": "stage" }];

Expand All @@ -45,10 +55,6 @@ describe('AWS RDS Lambda Scheduler', () => {

sinon.assert.calledOnce(stopStub);
sinon.assert.calledWith(stopStub, 'DB-INSTANCE-TEST-ID');

// Important! Restore AWS SDK
AWSMock.restore('RDS');
consoleLogSpy.restore();
});

it('run: "stop" action should not be called according to mismatching of tags', async() => {
Expand All @@ -69,14 +75,11 @@ describe('AWS RDS Lambda Scheduler', () => {

sinon.assert.notCalled(stopStub);
//sinon.assert.calledWith(stopStub, 'DB-INSTANCE-TEST-ID');

// Important! Restore AWS SDK
AWSMock.restore('RDS');
});

it('stop: should stop instance by certain ID', async() => {
// Ignore console.log() output
let consoleLogSpy = sinon.stub(console, 'log');
//let consoleLogSpy = sinon.stub(console, 'log');

let stopDBInstanceSpy = sinon.spy((params, callback) => {
callback(null, { 'StoppingInstances': [{ DBInstanceIdentifier: "TEST-RDS-ID-123" }] });
Expand All @@ -99,11 +102,6 @@ describe('AWS RDS Lambda Scheduler', () => {
assert.isTrue(stopDBInstanceSpy.calledWith(expectedParams), 'should pass correct parameters');
// Expect passed JSON parameters have required 'DBInstanceIdentifier' property
expect(stopDBInstanceSpy.getCall(0).args[0]).to.have.property('DBInstanceIdentifier');

// Important! Restore AWS SDK
AWSMock.restore('RDS');

consoleLogSpy.restore();
});

});
17 changes: 14 additions & 3 deletions package/test/unit/spot-scheduler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,19 @@ const chai = require('chai');
const sinon = require('sinon');
const SpotScheduler = require('spot-scheduler');

describe('AWS EC2 Spot Instances Lambda Scheduler', () => {
let consoleLogStub = null;

describe('AWS EC2 Spot Instances Lambda Scheduler', async () => {
beforeEach(function() {
AWSMock.setSDKInstance(AWS);
consoleLogStub = sinon.stub(console, 'log');
});

afterEach(function() {
AWSMock.restore();

// Ignore console.log() output
consoleLogStub.restore();
});

[
Expand All @@ -22,7 +32,7 @@ describe('AWS EC2 Spot Instances Lambda Scheduler', () => {
let tags = [{ "Key": "ToStop", "Value": "true" }, { "Key": "Environment", "Value": "stage" }];

// Ignore console.log() output
let consoleLogSpy = sinon.stub(console, 'log');
//let consoleLogSpy = sandbox.stub(console, 'log');
// Important creating the spy/sub in such way there are several calls to AWS under the hood
let actionInstancesSpy = sinon.spy((params, callback) => {
callback(null, { [run.responseKey]: [{ InstanceId: "TEST-SPOT-ID-123" }] });
Expand All @@ -49,7 +59,8 @@ describe('AWS EC2 Spot Instances Lambda Scheduler', () => {
AWSMock.restore('EC2');
AWSMock.restore('AutoScaling');

consoleLogSpy.restore();
//actionInstancesSpy.restore();
//consoleLogSpy.restore();
});
});
});

0 comments on commit c0f3899

Please sign in to comment.