mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-04-25 05:19:59 +00:00
Ensure we get input on parsing errors
We wrap JSON and date parsing code to ensure input data is logged in case of an error. Change-type: minor Signed-off-by: Roman Mazur <roman@balena.io>
This commit is contained in:
parent
7c4d8d7653
commit
645bc6c185
@ -159,6 +159,16 @@ export class LocalModeManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensures an error is thrown id timestamp string cannot be parsed.
|
||||||
|
// Date.parse may both throw or return NaN depending on a case.
|
||||||
|
private static parseTimestamp(input: string): Date {
|
||||||
|
const ms = Date.parse(input);
|
||||||
|
if (isNaN(ms)) {
|
||||||
|
throw new Error('bad date string - got Nan parsing it');
|
||||||
|
}
|
||||||
|
return new Date(ms);
|
||||||
|
}
|
||||||
|
|
||||||
// Read the latest stored snapshot from the database.
|
// Read the latest stored snapshot from the database.
|
||||||
public async retrieveLatestSnapshot(): Promise<EngineSnapshotRecord | null> {
|
public async retrieveLatestSnapshot(): Promise<EngineSnapshotRecord | null> {
|
||||||
const r = await this.db
|
const r = await this.db
|
||||||
@ -170,10 +180,18 @@ export class LocalModeManager {
|
|||||||
if (!r) {
|
if (!r) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
return new EngineSnapshotRecord(
|
return new EngineSnapshotRecord(
|
||||||
EngineSnapshot.fromJSON(r.snapshot),
|
EngineSnapshot.fromJSON(r.snapshot),
|
||||||
new Date(Date.parse(r.timestamp)),
|
LocalModeManager.parseTimestamp(r.timestamp),
|
||||||
);
|
);
|
||||||
|
} catch (e) {
|
||||||
|
// Some parsing error happened. Ensure we add data details to the error description.
|
||||||
|
throw new Error(
|
||||||
|
`Cannot parse snapshot data ${JSON.stringify(r)}.` +
|
||||||
|
`Original message: [${e.message}].`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async removeLocalModeArtifacts(objects: EngineSnapshot) {
|
private async removeLocalModeArtifacts(objects: EngineSnapshot) {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import * as assert from 'assert';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import * as Docker from 'dockerode';
|
import * as Docker from 'dockerode';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
@ -277,6 +278,24 @@ describe('LocalModeManager', () => {
|
|||||||
expect(dockerStub.getNetwork.callCount).to.be.equal(2);
|
expect(dockerStub.getNetwork.callCount).to.be.equal(2);
|
||||||
removeStubs.forEach(s => expect(s.remove.callCount).to.be.equal(2));
|
removeStubs.forEach(s => expect(s.remove.callCount).to.be.equal(2));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.only('skips cleanup in case of data corruption', async () => {
|
||||||
|
const removeStubs = stubRemoveMethods(false);
|
||||||
|
|
||||||
|
await db.models('engineSnapshot').insert({
|
||||||
|
snapshot: 'bad json',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
localMode.startLocalModeChangeHandling(false);
|
||||||
|
await localMode.switchCompletion();
|
||||||
|
|
||||||
|
expect(dockerStub.getImage.notCalled).to.be.true;
|
||||||
|
expect(dockerStub.getContainer.notCalled).to.be.true;
|
||||||
|
expect(dockerStub.getVolume.notCalled).to.be.true;
|
||||||
|
expect(dockerStub.getNetwork.notCalled).to.be.true;
|
||||||
|
removeStubs.forEach(s => expect(s.remove.notCalled).to.be.true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -310,6 +329,45 @@ describe('LocalModeManager', () => {
|
|||||||
await localMode.storeEngineSnapshot(recordSample);
|
await localMode.storeEngineSnapshot(recordSample);
|
||||||
expect(await recordsCount()).to.equal(1);
|
expect(await recordsCount()).to.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('in case of data corruption', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await db.models('engineSnapshot').delete();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deals with snapshot data corruption', async () => {
|
||||||
|
// Write bad data to simulate corruption.
|
||||||
|
await db.models('engineSnapshot').insert({
|
||||||
|
snapshot: 'bad json',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await localMode.retrieveLatestSnapshot();
|
||||||
|
assert.fail('Parsing error was expected');
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e.message);
|
||||||
|
expect(e.message).to.contain('bad json');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deals with snapshot timestamp corruption', async () => {
|
||||||
|
// Write bad data to simulate corruption.
|
||||||
|
await db.models('engineSnapshot').insert({
|
||||||
|
snapshot:
|
||||||
|
'{"containers": [], "images": [], "volumes": [], "networks": []}',
|
||||||
|
timestamp: 'bad timestamp',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await localMode.retrieveLatestSnapshot();
|
||||||
|
assert.fail('Parsing error was expected');
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e.message);
|
||||||
|
expect(e.message).to.contain('bad timestamp');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
after(async () => {
|
after(async () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user