From dac8d013ca3ec0ffcd200168eaeab236dacaf506 Mon Sep 17 00:00:00 2001 From: Paulius Date: Mon, 11 Apr 2022 11:13:25 +0100 Subject: [PATCH 01/40] IN-154: clean up of server.js file. --- app/server.js | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/app/server.js b/app/server.js index f7cd51e..b2f9052 100644 --- a/app/server.js +++ b/app/server.js @@ -18,7 +18,7 @@ async function createHttpServer() { }, }) - await app.use((req, res, next) => { + app.use((req, res, next) => { if (req.path !== '/health') requestLogger(req, res) next() }) @@ -47,26 +47,13 @@ async function startServer() { const { app, ipfs } = await createHttpServer() const server = await new Promise((resolve, reject) => { - let resolved = false const server = app.listen(PORT, (err) => { - if (err) { - if (!resolved) { - resolved = true - reject(err) - } - } + if (err) return reject(err) logger.info(`Listening on port ${PORT} `) - if (!resolved) { - resolved = true - resolve(server) - } - }) - server.on('error', (err) => { - if (!resolved) { - resolved = true - reject(err) - } + return resolve(server) }) + + server.on('error', (err) => reject(err)) }) const closeHandler = (exitCode) => async () => { From 4283a34bfcd74d04f02775a429554a9be7a35ad6 Mon Sep 17 00:00:00 2001 From: Paulius Date: Mon, 11 Apr 2022 16:44:14 +0100 Subject: [PATCH 02/40] IN-154: new class for dependant services health monitoring. --- app/env.js | 2 ++ app/utils/ServiceWatcher.js | 63 +++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 app/utils/ServiceWatcher.js diff --git a/app/env.js b/app/env.js index 1c03037..7734a98 100644 --- a/app/env.js +++ b/app/env.js @@ -33,6 +33,8 @@ const vars = envalid.cleanEnv( METADATA_KEY_LENGTH: envalid.num({ default: 32 }), METADATA_VALUE_LITERAL_LENGTH: envalid.num({ default: 32 }), PROCESS_IDENTIFIER_LENGTH: envalid.num({ default: 32 }), + SUBSTRATE_STATUS_POLL_PERIOD_MS: envalid.num({ default: 30 * 1000 }), + SUBSTRATE_STATUS_TIMEOUT_MS: envalid.num({ default: 2 * 1000 }), }, { strict: true, diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js new file mode 100644 index 0000000..36678ea --- /dev/null +++ b/app/utils/ServiceWatcher.js @@ -0,0 +1,63 @@ +const { createNodeApi } = require('../keyWatcher/api'); +const { + SUBSTRATE_STATUS_POLL_PERIOD_MS, + SUBSTRATE_STATUS_TIMEOUT_MS +} = require('../env') + +class ServiceWatcher { + #delay(ms, result) { + return new Promise(r => setTimeout(r, ms, result)) + } + + async #substrateStatus() { + const api = (await createNodeApi())._api + await api.isReady // wait for api to be ready + const [chain, runtime] = await Promise.all([api.runtimeChain, api.runtimeVersion]) + + return { + name: 'substrate', + status: 'status-up', // TODO repl with constant/symbol + detail: { + chain, + runtime: { + name: runtime.specName, + versions: { + spec: runtime.specVersion.toNumber(), + impl: runtime.implVersion.toNumber(), + authoring: runtime.authoringVersion.toNumber(), + transaction: runtime.transactionVersion.toNumber(), + }, + }, + } + } + } + + constructor() { + this.pollPeriod = SUBSTRATE_STATUS_POLL_PERIOD_MS + this.timeout = SUBSTRATE_STATUS_TIMEOUT_MS + } + + init() { + // return initial state for all services + return [this.#substrateStatus] + } + + start(self = this) { + return { + [Symbol.asyncIterator]: async function*() { + while(true) { + for (const getStatus of self.init()) { + await self.#delay(1000) + yield Promise.race([ + getStatus(), + self.#delay(self.timeout, { status: 'status-down' }), + ]) + } + } + } + } + } +} + +// return a new instance of StatusWatcher +module.exports = new ServiceWatcher() From d374ce351c401866c700d608f4f3700f24421ce0 Mon Sep 17 00:00:00 2001 From: Paulius Date: Mon, 11 Apr 2022 16:47:19 +0100 Subject: [PATCH 03/40] IN-154: a bit of clean up before commiting unit tests. --- app/utils/ServiceWatcher.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index 36678ea..206ad2e 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -42,7 +42,7 @@ class ServiceWatcher { return [this.#substrateStatus] } - start(self = this) { + getStatus(self = this) { return { [Symbol.asyncIterator]: async function*() { while(true) { @@ -57,6 +57,13 @@ class ServiceWatcher { } } } + + async start() { + console.log('wait wait wait....') + for await (const service of this.getStatus()) { + console.log({ service }) + } + } } // return a new instance of StatusWatcher From d43d922b896d79d23c5ed7cda412b98d79c2fa9a Mon Sep 17 00:00:00 2001 From: Paulius Date: Tue, 12 Apr 2022 01:53:24 +0100 Subject: [PATCH 04/40] IN-154: some comments and unit test setup. --- app/utils/ServiceWatcher.js | 15 ++++-- .../substrate-create-node-api.json | 54 +++++++++++++++++++ test/unit/apiStatus.test.js | 29 ++++++++++ 3 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 test/__fixtures__/substrate-create-node-api.json create mode 100644 test/unit/apiStatus.test.js diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index 206ad2e..649af48 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -22,10 +22,10 @@ class ServiceWatcher { runtime: { name: runtime.specName, versions: { - spec: runtime.specVersion.toNumber(), - impl: runtime.implVersion.toNumber(), - authoring: runtime.authoringVersion.toNumber(), - transaction: runtime.transactionVersion.toNumber(), + spec: runtime.specVersion, //.toNumber(), + impl: runtime.implVersion, //.toNumber(), + authoring: runtime.authoringVersion, //.toNumber(), + transaction: runtime.transactionVersion //.toNumber(), }, }, } @@ -53,13 +53,18 @@ class ServiceWatcher { self.#delay(self.timeout, { status: 'status-down' }), ]) } + break } } } } + // handle disconnect + // handle error + // methood for stopping (update while val) + // . might want to stop after certain errors or... + async start() { - console.log('wait wait wait....') for await (const service of this.getStatus()) { console.log({ service }) } diff --git a/test/__fixtures__/substrate-create-node-api.json b/test/__fixtures__/substrate-create-node-api.json new file mode 100644 index 0000000..50627ca --- /dev/null +++ b/test/__fixtures__/substrate-create-node-api.json @@ -0,0 +1,54 @@ +{ + "isReady": true, + "chain": "Test", + "runtime": { + "specName": "dscp-node", + "implName": "dscp-node", + "authoringVersion": 1, + "specVersion": 300, + "implVersion": 1, + "apis": [ + [ + "0xdf6acb689907609b", + 3 + ], + [ + "0x37e397fc7c91f5e4", + 1 + ], + [ + "0x40fe3ad401f8959a", + 4 + ], + [ + "0xd2bc9897eed08f15", + 2 + ], + [ + "0xf78b278be53f454c", + 2 + ], + [ + "0xdd718d5cc53262d4", + 1 + ], + [ + "0xab3c0572291feb8b", + 1 + ], + [ + "0xed99c5acb25eedf5", + 2 + ], + [ + "0xbc9d89904f5b923f", + 1 + ], + [ + "0x37c8bb1350a9a2a8", + 1 + ] + ], + "transactionVersion": 1 + } +} \ No newline at end of file diff --git a/test/unit/apiStatus.test.js b/test/unit/apiStatus.test.js new file mode 100644 index 0000000..3c087d5 --- /dev/null +++ b/test/unit/apiStatus.test.js @@ -0,0 +1,29 @@ +const { describe, it } = require('mocha') +const { expect } = require('chai') +const { stub } = require('sinon') + +const mockNodeApi = require('../__fixtures__/substrate-create-node-api.json') +const NodeApi = require('../../app/keyWatcher/api'); + + +describe('substrate api status watcher', () => { + let SW + let createNodeApiMock + + before(() => { + createNodeApiMock = stub(NodeApi, "createNodeApi").callsFake(() => ({ + _api: { + isReaady: "true", + runtimeChain: mockNodeApi.chain, + runtimeVersion: mockNodeApi.runtime + } + })) + SW = require('../../app/utils/ServiceWatcher') + }) + + it('mmm... does something', async () => { + await SW.start() + expect(createNodeApiMock.called).to.equal(true) + expect(1).to.equal(1) + }) +}) From d14f33a7b2c8ab470db19153ff1ded4eaed44567 Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 13 Apr 2022 07:08:21 +0100 Subject: [PATCH 05/40] IN-154: added the report object and some error handling --- app/utils/ServiceWatcher.js | 105 ++++++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 40 deletions(-) diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index 649af48..d94086b 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -1,75 +1,100 @@ -const { createNodeApi } = require('../keyWatcher/api'); const { SUBSTRATE_STATUS_POLL_PERIOD_MS, SUBSTRATE_STATUS_TIMEOUT_MS } = require('../env') class ServiceWatcher { - #delay(ms, result) { - return new Promise(r => setTimeout(r, ms, result)) + constructor(createNodeApi) { + this.report = {} + this.pollPeriod = SUBSTRATE_STATUS_POLL_PERIOD_MS + this.timeout = SUBSTRATE_STATUS_TIMEOUT_MS + this.createNodeApi = createNodeApi } - async #substrateStatus() { - const api = (await createNodeApi())._api - await api.isReady // wait for api to be ready - const [chain, runtime] = await Promise.all([api.runtimeChain, api.runtimeVersion]) - - return { - name: 'substrate', - status: 'status-up', // TODO repl with constant/symbol - detail: { - chain, - runtime: { - name: runtime.specName, - versions: { - spec: runtime.specVersion, //.toNumber(), - impl: runtime.implVersion, //.toNumber(), - authoring: runtime.authoringVersion, //.toNumber(), - transaction: runtime.transactionVersion //.toNumber(), + // substrate polling function, each service should have their own + async #substratePoll({ createNodeApi }) { + try { + const api = (await createNodeApi())._api + if (!await api.isReady) throw new Error('service is not ready') + const [chain, runtime] = await Promise.all([api.runtimeChain, api.runtimeVersion]) + + return { + name: 'substrate', + status: 'up', // TODO repl with constant/symbol + detail: { + chain, + runtime: { + name: runtime.specName, + versions: { + spec: runtime.specVersion, //.toNumber(), // tmp commenting out for stubbing + impl: runtime.implVersion, //.toNumber(), + authoring: runtime.authoringVersion, //.toNumber(), + transaction: runtime.transactionVersion //.toNumber(), + }, }, - }, - } - } + } + } + } catch(error) { + return this.update('substrate', { status: 'error', error }) + } + } + + delay(ms, result) { + return new Promise(r => setTimeout(r, ms, result)) } - constructor() { - this.pollPeriod = SUBSTRATE_STATUS_POLL_PERIOD_MS - this.timeout = SUBSTRATE_STATUS_TIMEOUT_MS + + update(name, details) { + if (!name) return null // some handling + + this.report = { + ...this.report, + [name]: details, + } } + // services that we would like to monitor should be added here + // with [name] and [poll] properties, more can be added for enrichment init() { - // return initial state for all services - return [this.#substrateStatus] + return [{ + name: 'substrate', + poll: () => this.#substratePoll(this) + }] } - getStatus(self = this) { + // main generator function with infinate loop + getStatus() { return { [Symbol.asyncIterator]: async function*() { while(true) { - for (const getStatus of self.init()) { - await self.#delay(1000) + for (const service of this.init()) { + await this.delay(1000) yield Promise.race([ - getStatus(), - self.#delay(self.timeout, { status: 'status-down' }), + service.poll(), + this.delay(this.timeout, { + [service.name]: { + status: 'down', + error: `timeout, not response for ${this.timeout}ms`, + } // abstract to a helper method + }), ]) } break } - } + }.bind(this) } } - // handle disconnect - // handle error - // methood for stopping (update while val) + // TODO methood for stopping (update while val) // . might want to stop after certain errors or... + // something to do for later as it was not very straight forward due to scope async start() { for await (const service of this.getStatus()) { - console.log({ service }) + const [[ name, details ]] = Object.entries(service) + this.update(name, details) } } } -// return a new instance of StatusWatcher -module.exports = new ServiceWatcher() +module.exports = ServiceWatcher From c92dd1158ae83ba4ea63a05f4ffccccbc5f00aa1 Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 13 Apr 2022 11:27:08 +0100 Subject: [PATCH 06/40] IN-154: clean up and unit tests for substrate polling fn. --- app/utils/ServiceWatcher.js | 24 +++-- .../substrate-create-node-api.json | 54 ----------- test/unit/apiStatus.test.js | 94 ++++++++++++++++--- 3 files changed, 94 insertions(+), 78 deletions(-) delete mode 100644 test/__fixtures__/substrate-create-node-api.json diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index d94086b..9dc2f3a 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -4,6 +4,8 @@ const { } = require('../env') class ServiceWatcher { + // taking helper functions can be improved by taking an object + // { name: , method: createNodeApi } and use in init() constructor(createNodeApi) { this.report = {} this.pollPeriod = SUBSTRATE_STATUS_POLL_PERIOD_MS @@ -12,16 +14,16 @@ class ServiceWatcher { } // substrate polling function, each service should have their own - async #substratePoll({ createNodeApi }) { + async #substratePoll({ createNodeApi, name = 'substrate' }) { try { const api = (await createNodeApi())._api if (!await api.isReady) throw new Error('service is not ready') const [chain, runtime] = await Promise.all([api.runtimeChain, api.runtimeVersion]) return { - name: 'substrate', + name, status: 'up', // TODO repl with constant/symbol - detail: { + details: { chain, runtime: { name: runtime.specName, @@ -35,7 +37,8 @@ class ServiceWatcher { } } } catch(error) { - return this.update('substrate', { status: 'error', error }) + // TODO logging + return { name, status: 'error', error } } } @@ -72,10 +75,9 @@ class ServiceWatcher { yield Promise.race([ service.poll(), this.delay(this.timeout, { - [service.name]: { - status: 'down', - error: `timeout, not response for ${this.timeout}ms`, - } // abstract to a helper method + name: service.name, + status: 'down', + error: new Error(`timeout, no response for ${this.timeout}ms`), }), ]) } @@ -90,10 +92,12 @@ class ServiceWatcher { // something to do for later as it was not very straight forward due to scope async start() { - for await (const service of this.getStatus()) { - const [[ name, details ]] = Object.entries(service) + const generator = this.getStatus() + for await (const service of generator) { + const { name, ...details } = service this.update(name, details) } + return Promise.resolve() } } diff --git a/test/__fixtures__/substrate-create-node-api.json b/test/__fixtures__/substrate-create-node-api.json deleted file mode 100644 index 50627ca..0000000 --- a/test/__fixtures__/substrate-create-node-api.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "isReady": true, - "chain": "Test", - "runtime": { - "specName": "dscp-node", - "implName": "dscp-node", - "authoringVersion": 1, - "specVersion": 300, - "implVersion": 1, - "apis": [ - [ - "0xdf6acb689907609b", - 3 - ], - [ - "0x37e397fc7c91f5e4", - 1 - ], - [ - "0x40fe3ad401f8959a", - 4 - ], - [ - "0xd2bc9897eed08f15", - 2 - ], - [ - "0xf78b278be53f454c", - 2 - ], - [ - "0xdd718d5cc53262d4", - 1 - ], - [ - "0xab3c0572291feb8b", - 1 - ], - [ - "0xed99c5acb25eedf5", - 2 - ], - [ - "0xbc9d89904f5b923f", - 1 - ], - [ - "0x37c8bb1350a9a2a8", - 1 - ] - ], - "transactionVersion": 1 - } -} \ No newline at end of file diff --git a/test/unit/apiStatus.test.js b/test/unit/apiStatus.test.js index 3c087d5..b36909b 100644 --- a/test/unit/apiStatus.test.js +++ b/test/unit/apiStatus.test.js @@ -2,28 +2,94 @@ const { describe, it } = require('mocha') const { expect } = require('chai') const { stub } = require('sinon') -const mockNodeApi = require('../__fixtures__/substrate-create-node-api.json') +const mockNodeApi = require('../__fixtures__/create-node-api-fn') const NodeApi = require('../../app/keyWatcher/api'); +const ServiceWatcher = require('../../app/utils/ServiceWatcher') -describe('substrate api status watcher', () => { - let SW - let createNodeApiMock +const createNodeApiStub = stub(NodeApi, "createNodeApi") - before(() => { - createNodeApiMock = stub(NodeApi, "createNodeApi").callsFake(() => ({ - _api: { - isReaady: "true", - runtimeChain: mockNodeApi.chain, - runtimeVersion: mockNodeApi.runtime - } - })) - SW = require('../../app/utils/ServiceWatcher') +describe('ServiceWatcher', () => { + beforeEach(() => { + createNodeApiStub.callsFake(() => mockNodeApi.available) + SW = new ServiceWatcher(NodeApi.createNodeApi) }) - it('mmm... does something', async () => { + after(() => { + createNodeApiStub.restore() + }) + + describe('class methods', () => { + describe('ServiceWatcher.substratePoll', () => { + beforeEach(() => { + createNodeApiStub.callsFake(() => mockNodeApi.available) + SW = new ServiceWatcher(NodeApi.createNodeApi) + }) + + describe('when isReady is false', () => { + beforeEach(() => { + createNodeApiStub.callsFake(() => mockNodeApi.unavailable) + SW = new ServiceWatcher(NodeApi.createNodeApi) + }) + + it('throws and updates this.report', async () => { + await SW.start() + expect(SW.report) + .to.have.property('substrate') + .that.includes.all.keys('status', 'error') + const { status, error } = SW.report.substrate + expect(status).to.equal('error') + expect(error.message).to.equal('service is not ready') + }) + }) + + describe('if it fails to retrieve a chain\'s state', () => { + beforeEach(() => { + createNodeApiStub.restore() + SW = new ServiceWatcher(NodeApi.createNodeApi) + }) + + it('catches the error and updates this.report', async () => { + await SW.start() + expect(SW.report) + .to.have.property('substrate') + .that.includes.all.keys('status', 'error') + const { status, error } = SW.report.substrate + expect(status).to.equal('down') + expect(error.message).to.equal('timeout, no response for 2000ms') + }).timeout(5000) + }) + + it('persists substrate node status and details in this.report', async () => { + await SW.start() + expect(SW.report) + .to.have.property('substrate') + .that.includes.all.keys('status', 'details') + const { status, details } = SW.report.substrate + expect(status).to.equal('up') + expect(details).to.deep.equal({ + chain: "Test", + runtime: { + name: "dscp-node", + versions: { + authoring: 1, + impl: 1, + spec: 300, + transaction: 1 + } + } + }) + }).timeout(5000) + }) + }) + + + /* + it.skip('mmm... does something', async () => { await SW.start() + console.log(SW.report) expect(createNodeApiMock.called).to.equal(true) expect(1).to.equal(1) }) + */ }) From 2e14a1d6089b900f6c642e3e2f39d1ea4b2cfc8c Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 13 Apr 2022 12:58:08 +0100 Subject: [PATCH 07/40] IN-154: unit tests for the ServiceWatcher class. --- app/keyWatcher/api.js | 139 +++++++++--------- app/utils/ServiceWatcher.js | 8 +- test/__fixtures__/create-node-api-fn.js | 78 ++++++++++ test/unit/apiStatus.test.js | 184 ++++++++++++++++-------- 4 files changed, 280 insertions(+), 129 deletions(-) create mode 100644 test/__fixtures__/create-node-api-fn.js diff --git a/app/keyWatcher/api.js b/app/keyWatcher/api.js index e367e2b..0bd7133 100644 --- a/app/keyWatcher/api.js +++ b/app/keyWatcher/api.js @@ -7,78 +7,81 @@ const { PROCESS_IDENTIFIER_LENGTH, } = require('../env') -const provider = new WsProvider(`ws://${NODE_HOST}:${NODE_PORT}`) -const apiOptions = { - provider, - types: { - Address: 'MultiAddress', - LookupSource: 'MultiAddress', - PeerId: 'Vec', - Key: 'Vec', - TokenId: 'u128', - RoleKey: 'Role', - TokenMetadataKey: `[u8; ${METADATA_KEY_LENGTH}]`, - TokenMetadataValue: 'MetadataValue', - Token: { - id: 'TokenId', - original_id: 'TokenId', - roles: 'BTreeMap', - creator: 'AccountId', - created_at: 'BlockNumber', - destroyed_at: 'Option', - metadata: 'BTreeMap', - parents: 'Vec', - children: 'Option>', - }, - ProcessIO: { - roles: 'BTreeMap', - metadata: 'BTreeMap', - parent_index: 'Option', - }, - MetadataValue: { - _enum: { - File: 'Hash', - Literal: `[u8; ${METADATA_VALUE_LITERAL_LENGTH}]`, - TokenId: 'TokenId', - None: 'null', + +const createNodeApi = async () => { + // moved this one inside the function as it was causing some troubles when defined globally, + // might need to revert back TODO - confirm IPFS service + const provider = new WsProvider(`ws://${NODE_HOST}:${NODE_PORT}`) + const apiOptions = { + provider, + types: { + Address: 'MultiAddress', + LookupSource: 'MultiAddress', + PeerId: 'Vec', + Key: 'Vec', + TokenId: 'u128', + RoleKey: 'Role', + TokenMetadataKey: `[u8; ${METADATA_KEY_LENGTH}]`, + TokenMetadataValue: 'MetadataValue', + Token: { + id: 'TokenId', + original_id: 'TokenId', + roles: 'BTreeMap', + creator: 'AccountId', + created_at: 'BlockNumber', + destroyed_at: 'Option', + metadata: 'BTreeMap', + parents: 'Vec', + children: 'Option>', }, - }, - Role: { - _enum: ['Owner', 'Customer', 'AdditiveManufacturer', 'Laboratory', 'Buyer', 'Supplier', 'Reviewer'], - }, - ProcessIdentifier: `[u8; ${PROCESS_IDENTIFIER_LENGTH}]`, - ProcessVersion: 'u32', - ProcessId: { - id: 'ProcessIdentifier', - version: 'ProcessVersion', - }, - Process: { - status: 'ProcessStatus', - restrictions: 'Vec', - }, - ProcessStatus: { - _enum: ['Disabled', 'Enabled'], - }, - Restriction: { - _enum: { - None: '()', - SenderOwnsAllInputs: '()', - FixedNumberOfInputs: 'FixedNumberOfInputsRestriction', - FixedNumberOfOutputs: 'FixedNumberOfOutputsRestriction', + ProcessIO: { + roles: 'BTreeMap', + metadata: 'BTreeMap', + parent_index: 'Option', }, + MetadataValue: { + _enum: { + File: 'Hash', + Literal: `[u8; ${METADATA_VALUE_LITERAL_LENGTH}]`, + TokenId: 'TokenId', + None: 'null', + }, + }, + Role: { + _enum: ['Owner', 'Customer', 'AdditiveManufacturer', 'Laboratory', 'Buyer', 'Supplier', 'Reviewer'], + }, + ProcessIdentifier: `[u8; ${PROCESS_IDENTIFIER_LENGTH}]`, + ProcessVersion: 'u32', + ProcessId: { + id: 'ProcessIdentifier', + version: 'ProcessVersion', + }, + Process: { + status: 'ProcessStatus', + restrictions: 'Vec', + }, + ProcessStatus: { + _enum: ['Disabled', 'Enabled'], + }, + Restriction: { + _enum: { + None: '()', + SenderOwnsAllInputs: '()', + FixedNumberOfInputs: 'FixedNumberOfInputsRestriction', + FixedNumberOfOutputs: 'FixedNumberOfOutputsRestriction', + }, + }, + FixedNumberOfInputsRestriction: { + num_inputs: 'u32', + }, + FixedNumberOfOutputsRestriction: { + num_outputs: 'u32', + }, + IsNew: 'bool', + Restrictions: 'Vec', }, - FixedNumberOfInputsRestriction: { - num_inputs: 'u32', - }, - FixedNumberOfOutputsRestriction: { - num_outputs: 'u32', - }, - IsNew: 'bool', - Restrictions: 'Vec', - }, -} + } -const createNodeApi = async () => { const api = await ApiPromise.create(apiOptions) return { _api: api, diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index 9dc2f3a..9a19a51 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -47,8 +47,8 @@ class ServiceWatcher { } - update(name, details) { - if (!name) return null // some handling + update(name, details = 'unknown') { + if (!name || typeof name !== 'string') return null // some handling this.report = { ...this.report, @@ -57,7 +57,7 @@ class ServiceWatcher { } // services that we would like to monitor should be added here - // with [name] and [poll] properties, more can be added for enrichment + // with [name] and { poll, properties }, more can be added for enrichment init() { return [{ name: 'substrate', @@ -97,7 +97,7 @@ class ServiceWatcher { const { name, ...details } = service this.update(name, details) } - return Promise.resolve() + return Promise.resolve('done') } } diff --git a/test/__fixtures__/create-node-api-fn.js b/test/__fixtures__/create-node-api-fn.js new file mode 100644 index 0000000..4e7896d --- /dev/null +++ b/test/__fixtures__/create-node-api-fn.js @@ -0,0 +1,78 @@ +module.exports = { + timeout: { + _api: { + get isReady() { + return new Promise(r => setTimeout(r, 4000)) + } + } + }, + unavailable: { + _api: { + get isReady() { + return false + } + } + }, + available: { + _api: { + get isReady() { + return true + }, + get runtimeChain() { + return "Test" + }, + get runtimeVersion() { + return { + specName: "dscp-node", + implName: "dscp-node", + authoringVersion: 1, + specVersion: 300, + implVersion: 1, + apis: [ + [ + "0xdf6acb689907609b", + 3 + ], + [ + "0x37e397fc7c91f5e4", + 1 + ], + [ + "0x40fe3ad401f8959a", + 4 + ], + [ + "0xd2bc9897eed08f15", + 2 + ], + [ + "0xf78b278be53f454c", + 2 + ], + [ + "0xdd718d5cc53262d4", + 1 + ], + [ + "0xab3c0572291feb8b", + 1 + ], + [ + "0xed99c5acb25eedf5", + 2 + ], + [ + "0xbc9d89904f5b923f", + 1 + ], + [ + "0x37c8bb1350a9a2a8", + 1 + ] + ], + "transactionVersion": 1 + } + } + } + } +} \ No newline at end of file diff --git a/test/unit/apiStatus.test.js b/test/unit/apiStatus.test.js index b36909b..df0bffc 100644 --- a/test/unit/apiStatus.test.js +++ b/test/unit/apiStatus.test.js @@ -19,77 +19,147 @@ describe('ServiceWatcher', () => { createNodeApiStub.restore() }) - describe('class methods', () => { - describe('ServiceWatcher.substratePoll', () => { - beforeEach(() => { - createNodeApiStub.callsFake(() => mockNodeApi.available) - SW = new ServiceWatcher(NodeApi.createNodeApi) + describe('ServiceWatcher.delay', () => { + it('returns a desired result after delay', async () => { + const result = await SW.delay(10, { result: 'test' }) + expect(result).to.deep.equal({ + result: 'test', }) + }) - describe('when isReady is false', () => { - beforeEach(() => { - createNodeApiStub.callsFake(() => mockNodeApi.unavailable) - SW = new ServiceWatcher(NodeApi.createNodeApi) - }) + it('delays and resolves a promise without a result', async () => { + const result = await SW.delay(10) + expect(result).to.be.undefined + }) + }) - it('throws and updates this.report', async () => { - await SW.start() - expect(SW.report) - .to.have.property('substrate') - .that.includes.all.keys('status', 'error') - const { status, error } = SW.report.substrate - expect(status).to.equal('error') - expect(error.message).to.equal('service is not ready') + describe('ServiceWatcher.update', () => { + describe('if invalid arguments provided', () => { + const invalidTypes = [ [ 1, 2 ], 1, {} ] + + it('returns null if first argument is not supplied and does not update report', () => { + SW.update() + expect(SW.report).to.deep.equal({}) + }) + + invalidTypes.forEach((type) => { + const typeText = type instanceof Array ? 'array' : null || typeof type + it(`also if first arguments is of a type: ${typeText}`, () => { + SW.update(type) + expect(SW.report).to.deep.equals({}) }) }) + }) + + it('updates this.report with supplied details', () => { + const details = { a: 'a', b: 'b', c: [] } + SW.update('test', details) + expect(SW.report).to.deep.equal({ + test: details + }) + }) - describe('if it fails to retrieve a chain\'s state', () => { - beforeEach(() => { - createNodeApiStub.restore() - SW = new ServiceWatcher(NodeApi.createNodeApi) - }) + it('sets details - unknown if second argumnent is not provided', () => { + SW.update('test-no-details') + expect(SW.report).to.deep.equal({ + 'test-no-details': 'unknown' + }) + }) + }) + + describe('ServiceWatcher.init', () => { + it('returns an array of objects with polling functions', () => { + const array = SW.init() + expect(array.length).to.equal(1) + expect(array[0]).to.include({ + name: 'substrate', + }) + expect(array[0].poll).to.be.a('function') + }) + }) + + describe('ServiceWatcher.substratePoll', () => { + beforeEach(() => { + createNodeApiStub.callsFake(() => mockNodeApi.available) + SW = new ServiceWatcher(NodeApi.createNodeApi) + }) - it('catches the error and updates this.report', async () => { - await SW.start() - expect(SW.report) - .to.have.property('substrate') - .that.includes.all.keys('status', 'error') - const { status, error } = SW.report.substrate - expect(status).to.equal('down') - expect(error.message).to.equal('timeout, no response for 2000ms') - }).timeout(5000) + describe('when invalid argument supplied', () => { + beforeEach(() => { + SW = new ServiceWatcher(() => 'some-function') }) - it('persists substrate node status and details in this.report', async () => { + it('catches error and reports', async () => { await SW.start() expect(SW.report) .to.have.property('substrate') - .that.includes.all.keys('status', 'details') - const { status, details } = SW.report.substrate - expect(status).to.equal('up') - expect(details).to.deep.equal({ - chain: "Test", - runtime: { - name: "dscp-node", - versions: { - authoring: 1, - impl: 1, - spec: 300, - transaction: 1 - } - } - }) - }).timeout(5000) + .that.includes.all.keys(['status', 'error']) + .that.deep.contain({ + status: 'error', + }) + expect(SW.report.substrate.error.message) + .to.equal("Cannot read properties of undefined (reading 'isReady')") + }) + }) + + describe('when isReady is false', () => { + beforeEach(() => { + createNodeApiStub.callsFake(() => mockNodeApi.unavailable) + SW = new ServiceWatcher(NodeApi.createNodeApi) + }) + + it('throws and updates this.report', async () => { + await SW.start() + expect(SW.report) + .to.have.property('substrate') + .that.includes.all.keys('status', 'error') + .that.deep.contain({ + status: 'error' + }) + expect(SW.report.substrate.error.message) + .to.equal('service is not ready') + }) }) - }) + describe('if it hits timeout first', () => { + beforeEach(() => { + createNodeApiStub.callsFake(() => mockNodeApi.timeout) + SW = new ServiceWatcher(NodeApi.createNodeApi) + }) + + it('resolves timeout error and reflects in this.report', async () => { + await SW.start() + expect(SW.report) + .to.have.property('substrate') + .that.includes.all.keys('status', 'error') + .that.deep.contain({ + status: 'down', + }) + expect(SW.report.substrate.error.message) + .to.equal('timeout, no response for 2000ms') + }).timeout(5000) + }) - /* - it.skip('mmm... does something', async () => { - await SW.start() - console.log(SW.report) - expect(createNodeApiMock.called).to.equal(true) - expect(1).to.equal(1) + it('persists substrate node status and details in this.report', async () => { + await SW.start() + expect(SW.report) + .to.have.property('substrate') + .that.includes.all.keys('status', 'details') + .that.deep.equal({ + status: 'up', + details: { + chain: "Test", + runtime: { + name: "dscp-node", + versions: { + authoring: 1, + impl: 1, + spec: 300, + transaction: 1 + } + } + } + }) + }).timeout(5000) }) - */ }) From 709c2e6b32812cc1aee5443e90829fccf0d9bb07 Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 13 Apr 2022 13:00:50 +0100 Subject: [PATCH 08/40] IN-154: file rename. --- test/unit/{apiStatus.test.js => ServiceWatcher.test.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/unit/{apiStatus.test.js => ServiceWatcher.test.js} (100%) diff --git a/test/unit/apiStatus.test.js b/test/unit/ServiceWatcher.test.js similarity index 100% rename from test/unit/apiStatus.test.js rename to test/unit/ServiceWatcher.test.js From bbd1b7b47fa6ee6970578b081e8a71710b655c60 Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 13 Apr 2022 15:24:26 +0100 Subject: [PATCH 09/40] IN-154: veresion bump. --- .eslintrc | 2 + app/keyWatcher/api.js | 3 +- app/utils/ServiceWatcher.js | 76 ++++++++++++++++---------------- helm/dscp-ipfs/Chart.yaml | 4 +- helm/dscp-ipfs/values.yaml | 2 +- package-lock.json | 55 ++++++++++++++++++++++- package.json | 3 +- test/unit/ServiceWatcher.test.js | 64 +++++++++++++-------------- 8 files changed, 132 insertions(+), 77 deletions(-) diff --git a/.eslintrc b/.eslintrc index 8b3c6a3..b388f4f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,10 +5,12 @@ "es6": true, "node": true }, + "parser": "babel-eslint", "parserOptions": { "ecmaVersion": 9, "sourceType": "module" }, + "ignorePatterns": ["**/test/__fixtures__/**/*"], "rules": { "prettier/prettier": "error", "no-console": 2 diff --git a/app/keyWatcher/api.js b/app/keyWatcher/api.js index 0bd7133..4aa0029 100644 --- a/app/keyWatcher/api.js +++ b/app/keyWatcher/api.js @@ -7,9 +7,8 @@ const { PROCESS_IDENTIFIER_LENGTH, } = require('../env') - const createNodeApi = async () => { - // moved this one inside the function as it was causing some troubles when defined globally, + // moved this one inside the function as it was causing some troubles when defined globally, // might need to revert back TODO - confirm IPFS service const provider = new WsProvider(`ws://${NODE_HOST}:${NODE_PORT}`) const apiOptions = { diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index 9a19a51..d45afc2 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -1,55 +1,54 @@ -const { - SUBSTRATE_STATUS_POLL_PERIOD_MS, - SUBSTRATE_STATUS_TIMEOUT_MS -} = require('../env') +const { SUBSTRATE_STATUS_POLL_PERIOD_MS, SUBSTRATE_STATUS_TIMEOUT_MS } = require('../env') class ServiceWatcher { + #pollPeriod + #timeout + #createNodeApi // taking helper functions can be improved by taking an object // { name: , method: createNodeApi } and use in init() constructor(createNodeApi) { this.report = {} - this.pollPeriod = SUBSTRATE_STATUS_POLL_PERIOD_MS - this.timeout = SUBSTRATE_STATUS_TIMEOUT_MS - this.createNodeApi = createNodeApi + this.#createNodeApi = createNodeApi + this.#pollPeriod = SUBSTRATE_STATUS_POLL_PERIOD_MS + this.#timeout = SUBSTRATE_STATUS_TIMEOUT_MS } // substrate polling function, each service should have their own - async #substratePoll({ createNodeApi, name = 'substrate' }) { + async #substratePoll(createNodeApi, name = 'substrate') { try { const api = (await createNodeApi())._api - if (!await api.isReady) throw new Error('service is not ready') + if (!(await api.isReady)) throw new Error('service is not ready') const [chain, runtime] = await Promise.all([api.runtimeChain, api.runtimeVersion]) - + return { name, - status: 'up', // TODO repl with constant/symbol + status: 'up', details: { chain, runtime: { name: runtime.specName, versions: { - spec: runtime.specVersion, //.toNumber(), // tmp commenting out for stubbing - impl: runtime.implVersion, //.toNumber(), - authoring: runtime.authoringVersion, //.toNumber(), - transaction: runtime.transactionVersion //.toNumber(), + spec: runtime.specVersion.toNumber(), + impl: runtime.implVersion.toNumber(), + authoring: runtime.authoringVersion.toNumber(), + transaction: runtime.transactionVersion.toNumber(), }, }, - } - } - } catch(error) { + }, + } + } catch (error) { // TODO logging return { name, status: 'error', error } } } - + delay(ms, result) { - return new Promise(r => setTimeout(r, ms, result)) + return new Promise((r) => setTimeout(r, ms, result)) } - update(name, details = 'unknown') { if (!name || typeof name !== 'string') return null // some handling - + this.report = { ...this.report, [name]: details, @@ -59,45 +58,46 @@ class ServiceWatcher { // services that we would like to monitor should be added here // with [name] and { poll, properties }, more can be added for enrichment init() { - return [{ - name: 'substrate', - poll: () => this.#substratePoll(this) - }] + return [ + { + name: 'substrate', + poll: () => this.#substratePoll(this.#createNodeApi), + }, + ] } // main generator function with infinate loop - getStatus() { + generator(self = this) { return { - [Symbol.asyncIterator]: async function*() { - while(true) { - for (const service of this.init()) { - await this.delay(1000) + [Symbol.asyncIterator]: async function* () { + while (true) { + for (const service of self.init()) { + await self.delay(self.#pollPeriod) yield Promise.race([ service.poll(), - this.delay(this.timeout, { + self.delay(self.#timeout, { name: service.name, status: 'down', - error: new Error(`timeout, no response for ${this.timeout}ms`), + error: new Error(`timeout, no response for ${self.#timeout}ms`), }), ]) } break } - }.bind(this) + }, } } // TODO methood for stopping (update while val) // . might want to stop after certain errors or... // something to do for later as it was not very straight forward due to scope - async start() { - const generator = this.getStatus() - for await (const service of generator) { + const gen = this.generator() + for await (const service of gen) { const { name, ...details } = service this.update(name, details) } - return Promise.resolve('done') + return 'done' } } diff --git a/helm/dscp-ipfs/Chart.yaml b/helm/dscp-ipfs/Chart.yaml index 2e86b26..ecee30d 100644 --- a/helm/dscp-ipfs/Chart.yaml +++ b/helm/dscp-ipfs/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 name: dscp-ipfs -appVersion: '2.0.1' +appVersion: '2.0.2' description: A Helm chart for dscp-ipfs -version: '2.0.1' +version: '2.0.2' type: application dependencies: - name: dscp-node diff --git a/helm/dscp-ipfs/values.yaml b/helm/dscp-ipfs/values.yaml index d31c92d..dac94fb 100644 --- a/helm/dscp-ipfs/values.yaml +++ b/helm/dscp-ipfs/values.yaml @@ -33,7 +33,7 @@ config: image: repository: ghcr.io/digicatapult/dscp-ipfs pullPolicy: IfNotPresent - tag: 'v2.0.1' + tag: 'v2.0.2' storage: storageClass: "" diff --git a/package-lock.json b/package-lock.json index 97f2918..7b51051 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@digicatapult/dscp-ipfs", - "version": "2.0.1", + "version": "2.0.2", "lockfileVersion": 2, "requires": true, "packages": { @@ -17,6 +17,7 @@ "pino-http": "^5.5.0" }, "devDependencies": { + "babel-eslint": "^10.1.0", "chai": "^4.3.1", "delay": "^5.0.0", "depcheck": "^1.4.0", @@ -1282,6 +1283,36 @@ "node": ">=8.0.0" } }, + "node_modules/babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "deprecated": "babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "eslint": ">= 4.12.1" + } + }, + "node_modules/babel-eslint/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -8389,6 +8420,28 @@ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" }, + "babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", diff --git a/package.json b/package.json index 9bbc8c9..9716bdd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@digicatapult/dscp-ipfs", - "version": "2.0.1", + "version": "2.0.2", "description": "Service for WASP", "main": "app/index.js", "scripts": { @@ -39,6 +39,7 @@ "pino-http": "^5.5.0" }, "devDependencies": { + "babel-eslint": "^10.1.0", "chai": "^4.3.1", "delay": "^5.0.0", "depcheck": "^1.4.0", diff --git a/test/unit/ServiceWatcher.test.js b/test/unit/ServiceWatcher.test.js index df0bffc..112a3ad 100644 --- a/test/unit/ServiceWatcher.test.js +++ b/test/unit/ServiceWatcher.test.js @@ -3,13 +3,16 @@ const { expect } = require('chai') const { stub } = require('sinon') const mockNodeApi = require('../__fixtures__/create-node-api-fn') -const NodeApi = require('../../app/keyWatcher/api'); - +const NodeApi = require('../../app/keyWatcher/api') const ServiceWatcher = require('../../app/utils/ServiceWatcher') -const createNodeApiStub = stub(NodeApi, "createNodeApi") +const createNodeApiStub = stub(NodeApi, 'createNodeApi') describe('ServiceWatcher', () => { + let SW + Number.prototype.toNumber = function () { + return parseInt(this) + } beforeEach(() => { createNodeApiStub.callsFake(() => mockNodeApi.available) SW = new ServiceWatcher(NodeApi.createNodeApi) @@ -35,13 +38,13 @@ describe('ServiceWatcher', () => { describe('ServiceWatcher.update', () => { describe('if invalid arguments provided', () => { - const invalidTypes = [ [ 1, 2 ], 1, {} ] - + const invalidTypes = [[1, 2], 1, {}] + it('returns null if first argument is not supplied and does not update report', () => { SW.update() expect(SW.report).to.deep.equal({}) }) - + invalidTypes.forEach((type) => { const typeText = type instanceof Array ? 'array' : null || typeof type it(`also if first arguments is of a type: ${typeText}`, () => { @@ -50,19 +53,19 @@ describe('ServiceWatcher', () => { }) }) }) - + it('updates this.report with supplied details', () => { const details = { a: 'a', b: 'b', c: [] } SW.update('test', details) expect(SW.report).to.deep.equal({ - test: details + test: details, }) }) it('sets details - unknown if second argumnent is not provided', () => { SW.update('test-no-details') expect(SW.report).to.deep.equal({ - 'test-no-details': 'unknown' + 'test-no-details': 'unknown', }) }) }) @@ -91,14 +94,12 @@ describe('ServiceWatcher', () => { it('catches error and reports', async () => { await SW.start() - expect(SW.report) + + expect(SW.report) // prettier-ignore .to.have.property('substrate') .that.includes.all.keys(['status', 'error']) - .that.deep.contain({ - status: 'error', - }) - expect(SW.report.substrate.error.message) - .to.equal("Cannot read properties of undefined (reading 'isReady')") + .that.deep.contain({ status: 'error' }) + expect(SW.report.substrate.error.message).to.equal("Cannot read properties of undefined (reading 'isReady')") }) }) @@ -110,14 +111,13 @@ describe('ServiceWatcher', () => { it('throws and updates this.report', async () => { await SW.start() - expect(SW.report) + + expect(SW.report) // prettier-ignore .to.have.property('substrate') .that.includes.all.keys('status', 'error') - .that.deep.contain({ - status: 'error' - }) + .that.deep.contain({ status: 'error' }) expect(SW.report.substrate.error.message) - .to.equal('service is not ready') + .to.equal('service is not ready') // prettier-ignore }) }) @@ -129,36 +129,36 @@ describe('ServiceWatcher', () => { it('resolves timeout error and reflects in this.report', async () => { await SW.start() - expect(SW.report) + + expect(SW.report) // prettier-ignore .to.have.property('substrate') .that.includes.all.keys('status', 'error') - .that.deep.contain({ - status: 'down', - }) - expect(SW.report.substrate.error.message) + .that.deep.contain({ status: 'down' }) + expect(SW.report.substrate.error.message) // prettier-ignore .to.equal('timeout, no response for 2000ms') }).timeout(5000) }) it('persists substrate node status and details in this.report', async () => { await SW.start() - expect(SW.report) + + expect(SW.report) // prettier-ignore .to.have.property('substrate') .that.includes.all.keys('status', 'details') .that.deep.equal({ status: 'up', details: { - chain: "Test", + chain: 'Test', runtime: { - name: "dscp-node", + name: 'dscp-node', versions: { authoring: 1, impl: 1, spec: 300, - transaction: 1 - } - } - } + transaction: 1, + }, + }, + }, }) }).timeout(5000) }) From e4c63eca046641d9c0ee5b76dd59a04d067d2d29 Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 13 Apr 2022 16:17:28 +0100 Subject: [PATCH 10/40] IN-154: set polling interval in github workflows for tests so they do not timeout. --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 73484ea..f536774 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -167,6 +167,8 @@ jobs: time: '30s' - name: Run tests run: npm run test + env: + SUBSTRATE_STATUS_POLL_PERIOD_MS: 1000 check-version: name: 'Check version' From 238f3123fd795ab74dc3cf3d7c8ba6f50f79f21d Mon Sep 17 00:00:00 2001 From: Paulius Date: Sat, 16 Apr 2022 13:35:46 +0100 Subject: [PATCH 11/40] IN-154: integrating with service watcher --- README.md | 14 +++ app/ipfs.js | 1 + app/keyWatcher/index.js | 1 + app/server.js | 25 +++-- package-lock.json | 2 +- test/__fixtures__/create-node-api-fn.js | 124 +++++++++++------------- test/integration/helper/api.js | 1 + test/integration/helper/server.js | 3 +- 8 files changed, 97 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index c2fd7ae..1f383e7 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,20 @@ Manages a go-ipfs instance maintaining the private network swarm key based on the value from a `dscp-node` instance. +## Local development +> install dependencies +```sh +npm i +``` +> start substrate node using docker-compose +```sh +docker-compose up -d // -d for silent +``` +> start ipfs nodejs wrapper +```sh +npm run dev +``` + ## Environment Variables `dscp-ipfs` is configured primarily using environment variables as follows: diff --git a/app/ipfs.js b/app/ipfs.js index 40e3785..7a700f6 100644 --- a/app/ipfs.js +++ b/app/ipfs.js @@ -51,6 +51,7 @@ async function setupIpfs() { }) ipfs.on('close', unexpectedCloseListener) + that.ipfs = ipfs }, stop: async () => { logger.info('Stopping IPFS') diff --git a/app/keyWatcher/index.js b/app/keyWatcher/index.js index 1807eae..9f37dfe 100644 --- a/app/keyWatcher/index.js +++ b/app/keyWatcher/index.js @@ -5,5 +5,6 @@ module.exports = { setupKeyWatcher: async ({ onUpdate }) => { const api = await createNodeApi() await setupKeyWatcher(api)({ onUpdate }) + return api }, } diff --git a/app/server.js b/app/server.js index b2f9052..bd1f94d 100644 --- a/app/server.js +++ b/app/server.js @@ -5,26 +5,38 @@ const { PORT } = require('./env') const logger = require('./logger') const { setupKeyWatcher } = require('./keyWatcher') const { setupIpfs } = require('./ipfs') +const ServiceWatcher = require('./utils/ServiceWatcher') async function createHttpServer() { const app = express() const requestLogger = pinoHttp({ logger }) const ipfs = await setupIpfs() - await setupKeyWatcher({ + const nodeApi = await setupKeyWatcher({ onUpdate: async (value) => { await ipfs.stop() await ipfs.start({ swarmKey: value }) }, }) + // might be a good idea to have a dedicagted method for adding more api objects + const sw = new ServiceWatcher({ nodeApi: nodeApi._api, ipfsApi: ipfs }) + app.use((req, res, next) => { if (req.path !== '/health') requestLogger(req, res) next() }) app.get('/health', async (req, res) => { - res.status(200).send({ status: 'ok' }) + const statusCode = Object.values(sw.report) + .some((srv) => ['down', 'error'].includes(srv.status)) ? 503 : 200 + + res.status(statusCode).send({ + self: { + status: 'up', + ...sw.report + }, + }) }) // Sorry - app.use checks arity @@ -38,19 +50,20 @@ async function createHttpServer() { } }) - return { app, ipfs } + return { app, ipfs, sw } } /* istanbul ignore next */ async function startServer() { try { - const { app, ipfs } = await createHttpServer() - + const { app, ipfs, sw } = await createHttpServer() const server = await new Promise((resolve, reject) => { const server = app.listen(PORT, (err) => { if (err) return reject(err) + + sw.start()// include actual IPFS stats as well logger.info(`Listening on port ${PORT} `) - return resolve(server) + resolve(server) }) server.on('error', (err) => reject(err)) diff --git a/package-lock.json b/package-lock.json index 7b51051..6926895 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "@digicatapult/dscp-ipfs", - "version": "2.0.1", + "version": "2.0.2", "license": "Apache-2.0", "dependencies": { "@polkadot/api": "^5.9.1", diff --git a/test/__fixtures__/create-node-api-fn.js b/test/__fixtures__/create-node-api-fn.js index 4e7896d..49f2fd5 100644 --- a/test/__fixtures__/create-node-api-fn.js +++ b/test/__fixtures__/create-node-api-fn.js @@ -1,77 +1,71 @@ module.exports = { timeout: { - _api: { - get isReady() { - return new Promise(r => setTimeout(r, 4000)) - } + get isConnected() { + return new Promise(r => setTimeout(r, 4000)) } }, unavailable: { - _api: { - get isReady() { - return false - } + get isConnected() { + return false } }, available: { - _api: { - get isReady() { - return true - }, - get runtimeChain() { - return "Test" - }, - get runtimeVersion() { - return { - specName: "dscp-node", - implName: "dscp-node", - authoringVersion: 1, - specVersion: 300, - implVersion: 1, - apis: [ - [ - "0xdf6acb689907609b", - 3 - ], - [ - "0x37e397fc7c91f5e4", - 1 - ], - [ - "0x40fe3ad401f8959a", - 4 - ], - [ - "0xd2bc9897eed08f15", - 2 - ], - [ - "0xf78b278be53f454c", - 2 - ], - [ - "0xdd718d5cc53262d4", - 1 - ], - [ - "0xab3c0572291feb8b", - 1 - ], - [ - "0xed99c5acb25eedf5", - 2 - ], - [ - "0xbc9d89904f5b923f", - 1 - ], - [ - "0x37c8bb1350a9a2a8", - 1 - ] + get isConnected() { + return true + }, + get runtimeChain() { + return "Test" + }, + get runtimeVersion() { + return { + specName: "dscp-node", + implName: "dscp-node", + authoringVersion: 1, + specVersion: 300, + implVersion: 1, + apis: [ + [ + "0xdf6acb689907609b", + 3 + ], + [ + "0x37e397fc7c91f5e4", + 1 + ], + [ + "0x40fe3ad401f8959a", + 4 + ], + [ + "0xd2bc9897eed08f15", + 2 + ], + [ + "0xf78b278be53f454c", + 2 + ], + [ + "0xdd718d5cc53262d4", + 1 + ], + [ + "0xab3c0572291feb8b", + 1 + ], + [ + "0xed99c5acb25eedf5", + 2 + ], + [ + "0xbc9d89904f5b923f", + 1 ], - "transactionVersion": 1 - } + [ + "0x37c8bb1350a9a2a8", + 1 + ] + ], + "transactionVersion": 1 } } } diff --git a/test/integration/helper/api.js b/test/integration/helper/api.js index 9b0fbf6..39397ac 100644 --- a/test/integration/helper/api.js +++ b/test/integration/helper/api.js @@ -33,6 +33,7 @@ const setSwarmKey = async (swarmKey) => { }) .then((res) => { unsub = res + SW.start() }) }) } diff --git a/test/integration/helper/server.js b/test/integration/helper/server.js index c501c2f..e5e76e5 100644 --- a/test/integration/helper/server.js +++ b/test/integration/helper/server.js @@ -1,5 +1,4 @@ const { createHttpServer } = require('../../../app/server') - const logger = require('../../../app/logger') const { PORT } = require('../../../app/env') @@ -9,7 +8,7 @@ async function startServer(context) { context.ipfs = create.ipfs context.server = create.app.listen(PORT, (err) => { if (err) { - logger.error('Error starting app:', err) + logger.error('Error starting app:', err) reject(err) } else { logger.info(`Server is listening on port ${PORT}`) From 6eb2ed6a2baaf24825b62064fbd02e832036781b Mon Sep 17 00:00:00 2001 From: Paulius Date: Sat, 16 Apr 2022 13:36:20 +0100 Subject: [PATCH 12/40] IN-154: expanding on test coverage and addressing some minor changes. --- test/unit/ServiceWatcher.test.js | 174 +++++++++++++++++++++++-------- 1 file changed, 129 insertions(+), 45 deletions(-) diff --git a/test/unit/ServiceWatcher.test.js b/test/unit/ServiceWatcher.test.js index 112a3ad..8b5ca4f 100644 --- a/test/unit/ServiceWatcher.test.js +++ b/test/unit/ServiceWatcher.test.js @@ -1,33 +1,32 @@ const { describe, it } = require('mocha') const { expect } = require('chai') -const { stub } = require('sinon') +const { spy } = require('sinon') -const mockNodeApi = require('../__fixtures__/create-node-api-fn') -const NodeApi = require('../../app/keyWatcher/api') +const { available, unavailable, timeout } = require('../__fixtures__/create-node-api-fn') const ServiceWatcher = require('../../app/utils/ServiceWatcher') -const createNodeApiStub = stub(NodeApi, 'createNodeApi') +const connectionErrorMsg = 'Connection is not established, will retry during next polling cycle' + +describe('ServiceWatcher', function () { + this.timeout(5000) -describe('ServiceWatcher', () => { let SW Number.prototype.toNumber = function () { return parseInt(this) } beforeEach(() => { - createNodeApiStub.callsFake(() => mockNodeApi.available) - SW = new ServiceWatcher(NodeApi.createNodeApi) + SW = new ServiceWatcher({ substrate: available }) }) - after(() => { - createNodeApiStub.restore() + afterEach(() => { + SW.stop() }) describe('ServiceWatcher.delay', () => { - it('returns a desired result after delay', async () => { - const result = await SW.delay(10, { result: 'test' }) - expect(result).to.deep.equal({ - result: 'test', - }) + it('rejects with TimeoutError if second argument is supplied', () => { + return SW.delay(10, { name: 'test' }) + .then((res) => { throw new Error('was not supposed to succeed', res); }) + .catch((err) => { expect(err.message).to.be.equal('Timeout error, no response from a service') }) }) it('delays and resolves a promise without a result', async () => { @@ -57,6 +56,7 @@ describe('ServiceWatcher', () => { it('updates this.report with supplied details', () => { const details = { a: 'a', b: 'b', c: [] } SW.update('test', details) + expect(SW.report).to.deep.equal({ test: details, }) @@ -64,6 +64,7 @@ describe('ServiceWatcher', () => { it('sets details - unknown if second argumnent is not provided', () => { SW.update('test-no-details') + expect(SW.report).to.deep.equal({ 'test-no-details': 'unknown', }) @@ -71,76 +72,159 @@ describe('ServiceWatcher', () => { }) describe('ServiceWatcher.init', () => { - it('returns an array of objects with polling functions', () => { - const array = SW.init() - expect(array.length).to.equal(1) - expect(array[0]).to.include({ + it('returns an array of services with polling functions', () => { + SW = new ServiceWatcher({ substrate: available }) + expect(SW.services.length).to.equal(1) + expect(SW.services[0]).to.include({ name: 'substrate', }) - expect(array[0].poll).to.be.a('function') + expect(SW.services[0].poll).to.be.a('function') + }) + + it('does not include services that do not have a polling function', () => { + SW = new ServiceWatcher({ service1: {}, service2: {} }) + expect(SW.services.length).to.equal(0) }) }) - describe('ServiceWatcher.substratePoll', () => { + describe('substrate - service checks', () => { beforeEach(() => { - createNodeApiStub.callsFake(() => mockNodeApi.available) - SW = new ServiceWatcher(NodeApi.createNodeApi) + SW = new ServiceWatcher({ substrate: available }) }) - describe('when invalid argument supplied', () => { - beforeEach(() => { - SW = new ServiceWatcher(() => 'some-function') + describe('when invalid argument supplied to constructor', () => { + beforeEach(async () => { + SW = new ServiceWatcher('some-test-data') + SW.start() }) - it('catches error and reports', async () => { - await SW.start() + it('does not add to the services array', () => { + expect(SW.services).to.deep.equal([]) + }) + + it('does not create a new instance of generator', () => { + expect(SW.gen).to.be.undefined + }) - expect(SW.report) // prettier-ignore - .to.have.property('substrate') - .that.includes.all.keys(['status', 'error']) - .that.deep.contain({ status: 'error' }) - expect(SW.report.substrate.error.message).to.equal("Cannot read properties of undefined (reading 'isReady')") + it('sets stopped to true', () => { + expect(SW.stopped).to.equal(true) + }) + + it('and has nothing to report', () => { + expect(SW.report).to.deep.equal({}) }) }) - describe('when isReady is false', () => { - beforeEach(() => { - createNodeApiStub.callsFake(() => mockNodeApi.unavailable) - SW = new ServiceWatcher(NodeApi.createNodeApi) + describe('when service is unavailable', () => { + beforeEach(async () => { + SW = new ServiceWatcher({ substrate: unavailable }) + spy(SW, 'update') + SW.start() + await SW.delay(2100) }) - it('throws and updates this.report', async () => { - await SW.start() + afterEach(() => SW.stop()) + it('reflects status in this.report object with error message', () => { expect(SW.report) // prettier-ignore .to.have.property('substrate') .that.includes.all.keys('status', 'error') .that.deep.contain({ status: 'error' }) - expect(SW.report.substrate.error.message) - .to.equal('service is not ready') // prettier-ignore + expect(SW.report.substrate.error) + .to.have.all.keys('message', 'service') + .that.contains({ + message: connectionErrorMsg + }) + }) + + it('does not stop polling', () => { + expect(SW.stopped).to.equal(false) + expect(SW.update.callCount).to.equal(2) + expect(SW.update.getCall(0).args[0]).to.equal('substrate') + expect(SW.update.getCall(1).args[0]).to.equal('substrate') + expect(SW.update.getCall(1).args[1]) + .to.have.property('error') + .that.have.all.keys('message', 'service') + .that.contains({ + message: connectionErrorMsg, + service: 'substrate', + }) + }) + }) + + describe('and reports correctly when service status changes', () => { + beforeEach(async () => { + SW = new ServiceWatcher({ substrate: unavailable }) + spy(SW, 'update') + SW.start() + await SW.delay(1000) + SW.services = [{ + name: 'substrate', + poll: () => SW.substrate(available) + }] + await SW.delay(1000) + }) + + afterEach(() => { + SW.stop() + }) + + it('handles correctly unavalaible service', () => { + expect(SW.stopped).to.equal(false) + expect(SW.update.getCall(0).args[0]).to.equal('substrate') + expect(SW.update.getCall(0).args[1]) + .to.include.all.keys('error', 'status') + .that.property('error').contains({ + message: connectionErrorMsg, + service: 'substrate' + }) + }) + + it('updates this.report indicating that service is available', () => { + expect(SW.update.callCount).to.equal(2) + expect(SW.update.getCall(1).args).to.deep.equal([ + 'substrate', + { + status: 'up', + details: { + chain: 'Test', + runtime: { + name: 'dscp-node', + versions: { + authoring: 1, + impl: 1, + spec: 300, + transaction: 1, + }, + }, + }, + } + ]) + expect(SW.stopped).to.equal(false) }) }) describe('if it hits timeout first', () => { beforeEach(() => { - createNodeApiStub.callsFake(() => mockNodeApi.timeout) - SW = new ServiceWatcher(NodeApi.createNodeApi) + SW = new ServiceWatcher({ substrate: timeout }) }) it('resolves timeout error and reflects in this.report', async () => { - await SW.start() + await SW.start() // using await so it hits timeout expect(SW.report) // prettier-ignore .to.have.property('substrate') .that.includes.all.keys('status', 'error') .that.deep.contain({ status: 'down' }) expect(SW.report.substrate.error.message) // prettier-ignore - .to.equal('timeout, no response for 2000ms') + .to.equal('Timeout error, no response from a service') }).timeout(5000) }) it('persists substrate node status and details in this.report', async () => { - await SW.start() + SW.start() + await SW.delay(2000) + SW.stop() expect(SW.report) // prettier-ignore .to.have.property('substrate') From c29a7887e52fb6b746916b8c319b85b93dd4c5d2 Mon Sep 17 00:00:00 2001 From: Paulius Date: Sat, 16 Apr 2022 13:37:06 +0100 Subject: [PATCH 13/40] IN-154: better support for multiple services. --- app/utils/ServiceWatcher.js | 119 ++++++++++++++++++++++++++---------- 1 file changed, 87 insertions(+), 32 deletions(-) diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index d45afc2..035d9c7 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -1,23 +1,57 @@ const { SUBSTRATE_STATUS_POLL_PERIOD_MS, SUBSTRATE_STATUS_TIMEOUT_MS } = require('../env') +class TimeoutError extends Error { + constructor(service) { + super() + this.service = service.name + this.message = 'Timeout error, no response from a service' + } +} + +class ConnectionError extends Error { + constructor(service) { + super() + this.service = service.name + this.message = 'Connection is not established, will retry during next polling cycle' + } +} + class ServiceWatcher { #pollPeriod #timeout - #createNodeApi - // taking helper functions can be improved by taking an object - // { name: , method: createNodeApi } and use in init() - constructor(createNodeApi) { + + constructor(apis) { this.report = {} - this.#createNodeApi = createNodeApi + this.stopped = false this.#pollPeriod = SUBSTRATE_STATUS_POLL_PERIOD_MS this.#timeout = SUBSTRATE_STATUS_TIMEOUT_MS + this.services = this.#init(apis) + } + + async ipfs(api, name = 'ipfs') { + if (!api.ipfs || !api.ipfs.pid) return { + name, + status: 'error', + error: 'service has not started' + } + const { spawnfile, pid, killed } = api.ipfs + + return { + name, + status: 'up', + details: { + ...api, + spawnfile, + pid, + killed, + } + } } // substrate polling function, each service should have their own - async #substratePoll(createNodeApi, name = 'substrate') { + async substrate(api, name = 'substrate') { try { - const api = (await createNodeApi())._api - if (!(await api.isReady)) throw new Error('service is not ready') + if (!(await api.isConnected)) throw new ConnectionError({ name }) const [chain, runtime] = await Promise.all([api.runtimeChain, api.runtimeVersion]) return { @@ -42,12 +76,19 @@ class ServiceWatcher { } } - delay(ms, result) { - return new Promise((r) => setTimeout(r, ms, result)) + delay(ms, service = false) { + return new Promise((resolve, reject) => { + setTimeout(() => { + return service + ? reject(new TimeoutError(service)) + : resolve() + }, ms) + }) } update(name, details = 'unknown') { if (!name || typeof name !== 'string') return null // some handling + if (this.report[name] === details) return null // no need to update this.report = { ...this.report, @@ -57,45 +98,59 @@ class ServiceWatcher { // services that we would like to monitor should be added here // with [name] and { poll, properties }, more can be added for enrichment - init() { - return [ - { - name: 'substrate', - poll: () => this.#substratePoll(this.#createNodeApi), - }, - ] + #init(services) { + return Object.keys(services).map((service) => { + if (!this[service]) { + // TODO log that there are no polling functions for this service + return null + } + return { + name: service, + poll: () => this[service](services[service]) + } + }).filter(Boolean) } // main generator function with infinate loop generator(self = this) { + this.stopped = false return { [Symbol.asyncIterator]: async function* () { - while (true) { - for (const service of self.init()) { + try { + while (true) { await self.delay(self.#pollPeriod) - yield Promise.race([ - service.poll(), - self.delay(self.#timeout, { - name: service.name, - status: 'down', - error: new Error(`timeout, no response for ${self.#timeout}ms`), - }), - ]) + for (const service of self.services) { + yield Promise.race([ + service.poll(), + self.delay(self.#timeout, service) + ]) + } + } + } catch (error) { + yield { + status: 'down', + name: error.service, + error, } - break } }, } } + stop() { + // TODO return log.info and return an error + // take some args for error types + return this.stopped = true + } + // TODO methood for stopping (update while val) - // . might want to stop after certain errors or... - // something to do for later as it was not very straight forward due to scope async start() { - const gen = this.generator() - for await (const service of gen) { + if (this.services.length < 1) return this.stop() + this.gen = this.generator() + for await (const service of this.gen) { const { name, ...details } = service this.update(name, details) + if (this.stopped) break // TODO return log.info('watcher has stopped') } return 'done' } From 2ae0c39eb559bb9011058806b2ba752e32b77809 Mon Sep 17 00:00:00 2001 From: Paulius Date: Sat, 16 Apr 2022 14:59:40 +0100 Subject: [PATCH 14/40] IN-154: sorting unit tests and fixtures, added ipfs method. --- test/__fixtures__/ipfs-api-fn.js | 7 ++ ...ode-api-fn.js => substrate-node-api-fn.js} | 0 test/integration/helper/api.js | 1 - test/unit/ServiceWatcher.test.js | 117 ++++++++++++------ 4 files changed, 86 insertions(+), 39 deletions(-) create mode 100644 test/__fixtures__/ipfs-api-fn.js rename test/__fixtures__/{create-node-api-fn.js => substrate-node-api-fn.js} (100%) diff --git a/test/__fixtures__/ipfs-api-fn.js b/test/__fixtures__/ipfs-api-fn.js new file mode 100644 index 0000000..b672e10 --- /dev/null +++ b/test/__fixtures__/ipfs-api-fn.js @@ -0,0 +1,7 @@ +module.exports = { + available: { + pid: 10, + spawnfile: '/path/to/file/test/spawn.key', + killed: false + } +} \ No newline at end of file diff --git a/test/__fixtures__/create-node-api-fn.js b/test/__fixtures__/substrate-node-api-fn.js similarity index 100% rename from test/__fixtures__/create-node-api-fn.js rename to test/__fixtures__/substrate-node-api-fn.js diff --git a/test/integration/helper/api.js b/test/integration/helper/api.js index 39397ac..9b0fbf6 100644 --- a/test/integration/helper/api.js +++ b/test/integration/helper/api.js @@ -33,7 +33,6 @@ const setSwarmKey = async (swarmKey) => { }) .then((res) => { unsub = res - SW.start() }) }) } diff --git a/test/unit/ServiceWatcher.test.js b/test/unit/ServiceWatcher.test.js index 8b5ca4f..59aca17 100644 --- a/test/unit/ServiceWatcher.test.js +++ b/test/unit/ServiceWatcher.test.js @@ -2,8 +2,10 @@ const { describe, it } = require('mocha') const { expect } = require('chai') const { spy } = require('sinon') -const { available, unavailable, timeout } = require('../__fixtures__/create-node-api-fn') +const substrate = require('../__fixtures__/substrate-node-api-fn') +const ipfs = require('../__fixtures__/ipfs-api-fn') const ServiceWatcher = require('../../app/utils/ServiceWatcher') +const { TimeoutError } = require('../../app/utils/Errors') const connectionErrorMsg = 'Connection is not established, will retry during next polling cycle' @@ -15,14 +17,14 @@ describe('ServiceWatcher', function () { return parseInt(this) } beforeEach(() => { - SW = new ServiceWatcher({ substrate: available }) + SW = new ServiceWatcher({ substrate: substrate.available }) }) afterEach(() => { SW.stop() }) - describe('ServiceWatcher.delay', () => { + describe('delay method', () => { it('rejects with TimeoutError if second argument is supplied', () => { return SW.delay(10, { name: 'test' }) .then((res) => { throw new Error('was not supposed to succeed', res); }) @@ -35,7 +37,7 @@ describe('ServiceWatcher', function () { }) }) - describe('ServiceWatcher.update', () => { + describe('update method', () => { describe('if invalid arguments provided', () => { const invalidTypes = [[1, 2], 1, {}] @@ -71,9 +73,11 @@ describe('ServiceWatcher', function () { }) }) - describe('ServiceWatcher.init', () => { + // TODO teests for ServiceWatcher.stop() + + describe('init method', () => { it('returns an array of services with polling functions', () => { - SW = new ServiceWatcher({ substrate: available }) + SW = new ServiceWatcher({ substrate: substrate.available }) expect(SW.services.length).to.equal(1) expect(SW.services[0]).to.include({ name: 'substrate', @@ -87,37 +91,60 @@ describe('ServiceWatcher', function () { }) }) - describe('substrate - service checks', () => { - beforeEach(() => { - SW = new ServiceWatcher({ substrate: available }) + describe('if invalid argument supplied to constructor', () => { + beforeEach(async () => { + SW = new ServiceWatcher('some-test-data') + SW.start() }) - describe('when invalid argument supplied to constructor', () => { - beforeEach(async () => { - SW = new ServiceWatcher('some-test-data') - SW.start() - }) + it('does not add to the services array', () => { + expect(SW.services).to.deep.equal([]) + }) + + it('does not create a new instance of generator', () => { + expect(SW.gen).to.be.undefined + }) - it('does not add to the services array', () => { - expect(SW.services).to.deep.equal([]) - }) - - it('does not create a new instance of generator', () => { - expect(SW.gen).to.be.undefined - }) + it('sets stopped to true', () => { + expect(SW.stopped).to.equal(true) + }) + + it('and has nothing to report', () => { + expect(SW.report).to.deep.equal({}) + }) + }) - it('sets stopped to true', () => { - expect(SW.stopped).to.equal(true) - }) - - it('and has nothing to report', () => { - expect(SW.report).to.deep.equal({}) + describe('ipfs - service check', () => { + beforeEach(async () => { + SW = new ServiceWatcher({ ipfs: ipfs.available }) + SW.start() + await SW.delay(1000) + }) + + // TODO more coverage for ipfs + it('persist ipfs status and details in this.report object', () => { + expect(SW.report) // prettier-ignore + .to.have.property('ipfs') + .that.includes.all.keys('status', 'details') + .that.deep.equal({ + status: 'up', + details: { + killed: false, + pid: 10, + spawnfile: '/path/to/file/test/spawn.key', + }, }) }) + }) + + describe('substrate - service checks', () => { + beforeEach(() => { + SW = new ServiceWatcher({ substrate: substrate.available }) + }) describe('when service is unavailable', () => { beforeEach(async () => { - SW = new ServiceWatcher({ substrate: unavailable }) + SW = new ServiceWatcher({ substrate: substrate.unavailable }) spy(SW, 'update') SW.start() await SW.delay(2100) @@ -154,13 +181,13 @@ describe('ServiceWatcher', function () { describe('and reports correctly when service status changes', () => { beforeEach(async () => { - SW = new ServiceWatcher({ substrate: unavailable }) + SW = new ServiceWatcher({ substrate: substrate.unavailable }) spy(SW, 'update') SW.start() await SW.delay(1000) SW.services = [{ name: 'substrate', - poll: () => SW.substrate(available) + poll: () => SW.substrate(substrate.available) }] await SW.delay(1000) }) @@ -205,20 +232,34 @@ describe('ServiceWatcher', function () { }) describe('if it hits timeout first', () => { - beforeEach(() => { - SW = new ServiceWatcher({ substrate: timeout }) + beforeEach(async () => { + SW = new ServiceWatcher({ substrate: substrate.timeout }) + SW.start() // using await so it hits timeout + await SW.delay(3000) + }) + + afterEach(() => { + SW.stop() }) - it('resolves timeout error and reflects in this.report', async () => { - await SW.start() // using await so it hits timeout + it('creates an instace of timeout error', () => { + const { error } = SW.report.substrate; + expect(error).to.be.a.instanceOf(TimeoutError) + expect(error.name).to.equal('TimeoutError') + expect(error.message).to.equal('Timeout error, no response from a service') + }) + + it('updates this.report with new status and error object', () => { expect(SW.report) // prettier-ignore .to.have.property('substrate') .that.includes.all.keys('status', 'error') .that.deep.contain({ status: 'down' }) - expect(SW.report.substrate.error.message) // prettier-ignore - .to.equal('Timeout error, no response from a service') - }).timeout(5000) + }) + + it('continues polling', () => { + expect(SW.stopped).to.equal(false) + }) }) it('persists substrate node status and details in this.report', async () => { @@ -244,6 +285,6 @@ describe('ServiceWatcher', function () { }, }, }) - }).timeout(5000) + }) }) }) From 66e5039de71bb44dfd2aa5a0056d9aacc4048749 Mon Sep 17 00:00:00 2001 From: Paulius Date: Sat, 16 Apr 2022 15:00:01 +0100 Subject: [PATCH 15/40] IN-154: custom errors. --- app/utils/Errors.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 app/utils/Errors.js diff --git a/app/utils/Errors.js b/app/utils/Errors.js new file mode 100644 index 0000000..b162fbe --- /dev/null +++ b/app/utils/Errors.js @@ -0,0 +1,21 @@ +class TimeoutError extends Error { + constructor(service) { + super() + this.name = this.constructor.name + this.service = service.name + this.message = 'Timeout error, no response from a service' + } +} + +class ConnectionError extends Error { + constructor(service) { + super() + this.service = service.name + this.message = 'Connection is not established, will retry during next polling cycle' + } +} + +module.exports = { + TimeoutError, + ConnectionError, +} From 4f03f04ba758df5f1f33dcfea833dc8836375d4b Mon Sep 17 00:00:00 2001 From: Paulius Date: Sat, 16 Apr 2022 15:00:49 +0100 Subject: [PATCH 16/40] IN-154: minor tweaks to ServiceWatcher.: --- app/server.js | 11 +++------ app/utils/ServiceWatcher.js | 46 ++++++++++++------------------------- 2 files changed, 18 insertions(+), 39 deletions(-) diff --git a/app/server.js b/app/server.js index bd1f94d..e9a83af 100644 --- a/app/server.js +++ b/app/server.js @@ -20,7 +20,7 @@ async function createHttpServer() { }) // might be a good idea to have a dedicagted method for adding more api objects - const sw = new ServiceWatcher({ nodeApi: nodeApi._api, ipfsApi: ipfs }) + const sw = new ServiceWatcher({ substrate: nodeApi._api, ipfs }) app.use((req, res, next) => { if (req.path !== '/health') requestLogger(req, res) @@ -31,12 +31,7 @@ async function createHttpServer() { const statusCode = Object.values(sw.report) .some((srv) => ['down', 'error'].includes(srv.status)) ? 503 : 200 - res.status(statusCode).send({ - self: { - status: 'up', - ...sw.report - }, - }) + res.status(statusCode).send(sw.report) }) // Sorry - app.use checks arity @@ -61,9 +56,9 @@ async function startServer() { const server = app.listen(PORT, (err) => { if (err) return reject(err) - sw.start()// include actual IPFS stats as well logger.info(`Listening on port ${PORT} `) resolve(server) + sw.start() }) server.on('error', (err) => reject(err)) diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index 035d9c7..95a6eb6 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -1,21 +1,6 @@ +const { ConnectionError, TimeoutError } = require('./Errors') const { SUBSTRATE_STATUS_POLL_PERIOD_MS, SUBSTRATE_STATUS_TIMEOUT_MS } = require('../env') -class TimeoutError extends Error { - constructor(service) { - super() - this.service = service.name - this.message = 'Timeout error, no response from a service' - } -} - -class ConnectionError extends Error { - constructor(service) { - super() - this.service = service.name - this.message = 'Connection is not established, will retry during next polling cycle' - } -} - class ServiceWatcher { #pollPeriod #timeout @@ -29,22 +14,21 @@ class ServiceWatcher { } async ipfs(api, name = 'ipfs') { - if (!api.ipfs || !api.ipfs.pid) return { - name, - status: 'error', - error: 'service has not started' - } - const { spawnfile, pid, killed } = api.ipfs + try { + if (!api || !api.pid) throw new ConnectionError({ name }) + const { spawnfile, pid, killed } = api - return { - name, - status: 'up', - details: { - ...api, - spawnfile, - pid, - killed, + return { + name, + status: 'up', + details: { + spawnfile, + pid, + killed, + } } + } catch (error) { + return { name, status: 'error', error } } } @@ -71,7 +55,7 @@ class ServiceWatcher { }, } } catch (error) { - // TODO logging + // TODO logging and/or any other handling return { name, status: 'error', error } } } From e88077b8ea2a289afbda8fe4fa8c95c32f679928 Mon Sep 17 00:00:00 2001 From: Paulius Date: Sat, 16 Apr 2022 15:44:11 +0100 Subject: [PATCH 17/40] IN-154: linting errors and updated current health check tests (needs more coverage). --- app/server.js | 4 +- app/utils/Errors.js | 4 +- app/utils/ServiceWatcher.js | 41 ++++++++---------- test/integration/httpServer.test.js | 42 ++++++++++++++++--- test/unit/ServiceWatcher.test.js | 65 ++++++++++++++++------------- 5 files changed, 93 insertions(+), 63 deletions(-) diff --git a/app/server.js b/app/server.js index e9a83af..d9bd0c7 100644 --- a/app/server.js +++ b/app/server.js @@ -28,8 +28,7 @@ async function createHttpServer() { }) app.get('/health', async (req, res) => { - const statusCode = Object.values(sw.report) - .some((srv) => ['down', 'error'].includes(srv.status)) ? 503 : 200 + const statusCode = Object.values(sw.report).some((srv) => ['down', 'error'].includes(srv.status)) ? 503 : 200 res.status(statusCode).send(sw.report) }) @@ -55,7 +54,6 @@ async function startServer() { const server = await new Promise((resolve, reject) => { const server = app.listen(PORT, (err) => { if (err) return reject(err) - logger.info(`Listening on port ${PORT} `) resolve(server) sw.start() diff --git a/app/utils/Errors.js b/app/utils/Errors.js index b162fbe..a650118 100644 --- a/app/utils/Errors.js +++ b/app/utils/Errors.js @@ -3,7 +3,7 @@ class TimeoutError extends Error { super() this.name = this.constructor.name this.service = service.name - this.message = 'Timeout error, no response from a service' + this.message = 'Timeout error, no response from a service' } } @@ -11,7 +11,7 @@ class ConnectionError extends Error { constructor(service) { super() this.service = service.name - this.message = 'Connection is not established, will retry during next polling cycle' + this.message = 'Connection is not established, will retry during next polling cycle' } } diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index 95a6eb6..16399ae 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -15,7 +15,7 @@ class ServiceWatcher { async ipfs(api, name = 'ipfs') { try { - if (!api || !api.pid) throw new ConnectionError({ name }) + if (!api || !api.pid) throw new ConnectionError({ name }) const { spawnfile, pid, killed } = api return { @@ -25,7 +25,7 @@ class ServiceWatcher { spawnfile, pid, killed, - } + }, } } catch (error) { return { name, status: 'error', error } @@ -62,11 +62,7 @@ class ServiceWatcher { delay(ms, service = false) { return new Promise((resolve, reject) => { - setTimeout(() => { - return service - ? reject(new TimeoutError(service)) - : resolve() - }, ms) + setTimeout(() => (service ? reject(new TimeoutError(service)) : resolve()), ms) }) } @@ -83,16 +79,18 @@ class ServiceWatcher { // services that we would like to monitor should be added here // with [name] and { poll, properties }, more can be added for enrichment #init(services) { - return Object.keys(services).map((service) => { - if (!this[service]) { - // TODO log that there are no polling functions for this service - return null - } - return { - name: service, - poll: () => this[service](services[service]) - } - }).filter(Boolean) + return Object.keys(services) + .map((service) => { + if (!this[service]) { + // TODO log that there are no polling functions for this service + return null + } + return { + name: service, + poll: () => this[service](services[service]), + } + }) + .filter(Boolean) } // main generator function with infinate loop @@ -104,10 +102,7 @@ class ServiceWatcher { while (true) { await self.delay(self.#pollPeriod) for (const service of self.services) { - yield Promise.race([ - service.poll(), - self.delay(self.#timeout, service) - ]) + yield Promise.race([service.poll(), self.delay(self.#timeout, service)]) } } } catch (error) { @@ -124,12 +119,12 @@ class ServiceWatcher { stop() { // TODO return log.info and return an error // take some args for error types - return this.stopped = true + this.stopped = true } // TODO methood for stopping (update while val) async start() { - if (this.services.length < 1) return this.stop() + if (this.services.length < 1) return this.stop() this.gen = this.generator() for await (const service of this.gen) { const { name, ...details } = service diff --git a/test/integration/httpServer.test.js b/test/integration/httpServer.test.js index fbd98b0..850193a 100644 --- a/test/integration/httpServer.test.js +++ b/test/integration/httpServer.test.js @@ -4,7 +4,7 @@ const fetch = require('node-fetch') const { PORT } = require('../../app/env') -describe('health', function () { +describe('health checks', function () { const context = {} before(async function () { @@ -12,11 +12,41 @@ describe('health', function () { context.body = await context.response.json() }) - it('should return 200', function () { - expect(context.response.status).to.equal(200) - }) + describe('if any of the services status is down or error', () => { + it('should return 503', function () { + expect(context.response.status).to.equal(503) + }) + + it('and report contains IPFS status', function () { + expect(context.body) + .to.have.property('ipfs') + .that.deep.equal({ + error: { + message: 'Connection is not established, will retry during next polling cycle', + service: 'ipfs', + }, + status: 'error', + }) + }) - it('should return success', function () { - expect(context.body).to.deep.equal({ status: 'ok' }) + it('also contains substrate node status', () => { + expect(context.body) + .to.have.property('substrate') + .that.deep.equal({ + status: 'up', + details: { + chain: 'Development', + runtime: { + name: 'dscp-node', + versions: { + authoring: 1, + impl: 1, + spec: 300, + transaction: 1, + }, + }, + }, + }) + }) }) }) diff --git a/test/unit/ServiceWatcher.test.js b/test/unit/ServiceWatcher.test.js index 59aca17..e72a060 100644 --- a/test/unit/ServiceWatcher.test.js +++ b/test/unit/ServiceWatcher.test.js @@ -27,8 +27,12 @@ describe('ServiceWatcher', function () { describe('delay method', () => { it('rejects with TimeoutError if second argument is supplied', () => { return SW.delay(10, { name: 'test' }) - .then((res) => { throw new Error('was not supposed to succeed', res); }) - .catch((err) => { expect(err.message).to.be.equal('Timeout error, no response from a service') }) + .then((res) => { + throw new Error('was not supposed to succeed', res) + }) + .catch((err) => { + expect(err.message).to.be.equal('Timeout error, no response from a service') + }) }) it('delays and resolves a promise without a result', async () => { @@ -100,7 +104,7 @@ describe('ServiceWatcher', function () { it('does not add to the services array', () => { expect(SW.services).to.deep.equal([]) }) - + it('does not create a new instance of generator', () => { expect(SW.gen).to.be.undefined }) @@ -108,7 +112,7 @@ describe('ServiceWatcher', function () { it('sets stopped to true', () => { expect(SW.stopped).to.equal(true) }) - + it('and has nothing to report', () => { expect(SW.report).to.deep.equal({}) }) @@ -120,22 +124,22 @@ describe('ServiceWatcher', function () { SW.start() await SW.delay(1000) }) - + // TODO more coverage for ipfs it('persist ipfs status and details in this.report object', () => { expect(SW.report) // prettier-ignore - .to.have.property('ipfs') - .that.includes.all.keys('status', 'details') - .that.deep.equal({ - status: 'up', - details: { - killed: false, - pid: 10, - spawnfile: '/path/to/file/test/spawn.key', - }, - }) + .to.have.property('ipfs') + .that.includes.all.keys('status', 'details') + .that.deep.equal({ + status: 'up', + details: { + killed: false, + pid: 10, + spawnfile: '/path/to/file/test/spawn.key', + }, + }) }) - }) + }) describe('substrate - service checks', () => { beforeEach(() => { @@ -157,10 +161,10 @@ describe('ServiceWatcher', function () { .to.have.property('substrate') .that.includes.all.keys('status', 'error') .that.deep.contain({ status: 'error' }) - expect(SW.report.substrate.error) + expect(SW.report.substrate.error) // prettier-ignore .to.have.all.keys('message', 'service') .that.contains({ - message: connectionErrorMsg + message: connectionErrorMsg, }) }) @@ -185,10 +189,12 @@ describe('ServiceWatcher', function () { spy(SW, 'update') SW.start() await SW.delay(1000) - SW.services = [{ - name: 'substrate', - poll: () => SW.substrate(substrate.available) - }] + SW.services = [ + { + name: 'substrate', + poll: () => SW.substrate(substrate.available), + }, + ] await SW.delay(1000) }) @@ -199,14 +205,15 @@ describe('ServiceWatcher', function () { it('handles correctly unavalaible service', () => { expect(SW.stopped).to.equal(false) expect(SW.update.getCall(0).args[0]).to.equal('substrate') - expect(SW.update.getCall(0).args[1]) + expect(SW.update.getCall(0).args[1]) // prettier-ignore .to.include.all.keys('error', 'status') - .that.property('error').contains({ + .that.property('error') + .contains({ message: connectionErrorMsg, - service: 'substrate' + service: 'substrate', }) }) - + it('updates this.report indicating that service is available', () => { expect(SW.update.callCount).to.equal(2) expect(SW.update.getCall(1).args).to.deep.equal([ @@ -225,7 +232,7 @@ describe('ServiceWatcher', function () { }, }, }, - } + }, ]) expect(SW.stopped).to.equal(false) }) @@ -243,7 +250,7 @@ describe('ServiceWatcher', function () { }) it('creates an instace of timeout error', () => { - const { error } = SW.report.substrate; + const { error } = SW.report.substrate expect(error).to.be.a.instanceOf(TimeoutError) expect(error.name).to.equal('TimeoutError') @@ -271,7 +278,7 @@ describe('ServiceWatcher', function () { .to.have.property('substrate') .that.includes.all.keys('status', 'details') .that.deep.equal({ - status: 'up', + status: 'up', // TODO implement snapshot assertation for mocha details: { chain: 'Test', runtime: { From 152bbbc23cbb034e2e32ac92f4158358e550bf23 Mon Sep 17 00:00:00 2001 From: Paulius Date: Tue, 26 Apr 2022 10:46:49 +0100 Subject: [PATCH 18/40] IN-154: abstracting polling function to service models (ipfs, substrate). --- app/ipfs.js | 20 ++++++++ app/keyWatcher/index.js | 26 ++++++++++ app/server.js | 19 +++++-- app/utils/ServiceWatcher.js | 59 ++-------------------- test/__fixtures__/ipfs-api-fn.js | 5 +- test/__fixtures__/substrate-node-api-fn.js | 9 +++- test/unit/ServiceWatcher.test.js | 2 +- 7 files changed, 78 insertions(+), 62 deletions(-) diff --git a/app/ipfs.js b/app/ipfs.js index 7a700f6..ef337e0 100644 --- a/app/ipfs.js +++ b/app/ipfs.js @@ -83,6 +83,26 @@ async function setupIpfs() { return that } +async function ipfsHealthCheack(api, name = 'ipfs') { + try { + if (!api || !api.pid) throw new ConnectionError({ name }) + const { spawnfile, pid, killed } = api + + return { + name, + status: 'up', + details: { + spawnfile, + pid, + killed, + }, + } + } catch (error) { + return { name, status: 'error', error } + } +} + module.exports = { setupIpfs, + ipfsHealthCheack, } diff --git a/app/keyWatcher/index.js b/app/keyWatcher/index.js index 9f37dfe..e856c4a 100644 --- a/app/keyWatcher/index.js +++ b/app/keyWatcher/index.js @@ -1,5 +1,6 @@ const { createNodeApi } = require('./api') const { setupKeyWatcher } = require('./keyWatcher') +const { ConnectionError } = require('../utils/Errors') module.exports = { setupKeyWatcher: async ({ onUpdate }) => { @@ -7,4 +8,29 @@ module.exports = { await setupKeyWatcher(api)({ onUpdate }) return api }, + nodeHealthCheck: async (api, name = 'substrate') => { + try { + if (!(await api.isConnected)) throw new ConnectionError({ name }) + const [chain, runtime] = await Promise.all([api.runtimeChain, api.runtimeVersion]) + + return { + name, + status: 'up', + details: { + chain, + runtime: { + name: runtime.specName, + versions: { + spec: runtime.specVersion.toNumber(), + impl: runtime.implVersion.toNumber(), + authoring: runtime.authoringVersion.toNumber(), + transaction: runtime.transactionVersion.toNumber(), + }, + }, + }, + } + } catch (error) { + return { name, status: 'error', error } + } + } } diff --git a/app/server.js b/app/server.js index d9bd0c7..d9e1f55 100644 --- a/app/server.js +++ b/app/server.js @@ -3,8 +3,8 @@ const pinoHttp = require('pino-http') const { PORT } = require('./env') const logger = require('./logger') -const { setupKeyWatcher } = require('./keyWatcher') -const { setupIpfs } = require('./ipfs') +const { setupKeyWatcher, nodeHealthCheck } = require('./keyWatcher') +const { setupIpfs, ipfsHealthCheack } = require('./ipfs') const ServiceWatcher = require('./utils/ServiceWatcher') async function createHttpServer() { @@ -19,8 +19,19 @@ async function createHttpServer() { }, }) - // might be a good idea to have a dedicagted method for adding more api objects - const sw = new ServiceWatcher({ substrate: nodeApi._api, ipfs }) + // setup service watcher + // TODO add methdo foro addng service watcher so it can be done + // by calling sw.addService + const sw = new ServiceWatcher({ + substrate: { + ...nodeApi._api, + healthCheck: nodeHealthCheck, + }, + ipfs: { + ...ipfs, + healthCheck: ipfsHealthCheack, + }, + }) app.use((req, res, next) => { if (req.path !== '/health') requestLogger(req, res) diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index 16399ae..3028a5a 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -13,53 +13,6 @@ class ServiceWatcher { this.services = this.#init(apis) } - async ipfs(api, name = 'ipfs') { - try { - if (!api || !api.pid) throw new ConnectionError({ name }) - const { spawnfile, pid, killed } = api - - return { - name, - status: 'up', - details: { - spawnfile, - pid, - killed, - }, - } - } catch (error) { - return { name, status: 'error', error } - } - } - - // substrate polling function, each service should have their own - async substrate(api, name = 'substrate') { - try { - if (!(await api.isConnected)) throw new ConnectionError({ name }) - const [chain, runtime] = await Promise.all([api.runtimeChain, api.runtimeVersion]) - - return { - name, - status: 'up', - details: { - chain, - runtime: { - name: runtime.specName, - versions: { - spec: runtime.specVersion.toNumber(), - impl: runtime.implVersion.toNumber(), - authoring: runtime.authoringVersion.toNumber(), - transaction: runtime.transactionVersion.toNumber(), - }, - }, - }, - } - } catch (error) { - // TODO logging and/or any other handling - return { name, status: 'error', error } - } - } - delay(ms, service = false) { return new Promise((resolve, reject) => { setTimeout(() => (service ? reject(new TimeoutError(service)) : resolve()), ms) @@ -81,14 +34,12 @@ class ServiceWatcher { #init(services) { return Object.keys(services) .map((service) => { - if (!this[service]) { - // TODO log that there are no polling functions for this service - return null - } - return { + const { healthCheck, ...api } = services[service] + + return healthCheck ? { name: service, - poll: () => this[service](services[service]), - } + poll: () => healthCheck(api, service), + } : null }) .filter(Boolean) } diff --git a/test/__fixtures__/ipfs-api-fn.js b/test/__fixtures__/ipfs-api-fn.js index b672e10..9f42427 100644 --- a/test/__fixtures__/ipfs-api-fn.js +++ b/test/__fixtures__/ipfs-api-fn.js @@ -1,7 +1,10 @@ +const { ipfsHealthCheack } = require('../../app/ipfs'); + module.exports = { available: { pid: 10, spawnfile: '/path/to/file/test/spawn.key', - killed: false + killed: false, + healthCheck: ipfsHealthCheack, } } \ No newline at end of file diff --git a/test/__fixtures__/substrate-node-api-fn.js b/test/__fixtures__/substrate-node-api-fn.js index 49f2fd5..93b1d72 100644 --- a/test/__fixtures__/substrate-node-api-fn.js +++ b/test/__fixtures__/substrate-node-api-fn.js @@ -1,15 +1,20 @@ +const { nodeHealthCheck } = require("../../app/keyWatcher") + module.exports = { timeout: { get isConnected() { return new Promise(r => setTimeout(r, 4000)) - } + }, + healthCheck: nodeHealthCheck, }, unavailable: { get isConnected() { return false - } + }, + healthCheck: nodeHealthCheck, }, available: { + healthCheck: nodeHealthCheck, get isConnected() { return true }, diff --git a/test/unit/ServiceWatcher.test.js b/test/unit/ServiceWatcher.test.js index e72a060..8f99492 100644 --- a/test/unit/ServiceWatcher.test.js +++ b/test/unit/ServiceWatcher.test.js @@ -192,7 +192,7 @@ describe('ServiceWatcher', function () { SW.services = [ { name: 'substrate', - poll: () => SW.substrate(substrate.available), + poll: () => substrate.available.healthCheck(substrate.available, 'substrate'), }, ] await SW.delay(1000) From 5d7751c1e17d140e589b4a42fd907611f1f23a94 Mon Sep 17 00:00:00 2001 From: Paulius Date: Tue, 26 Apr 2022 11:48:30 +0100 Subject: [PATCH 19/40] IN-154: version bump 2.0.2 -> 2.0.3 --- helm/dscp-ipfs/Chart.yaml | 4 ++-- helm/dscp-ipfs/values.yaml | 2 +- package-lock.json | 2 +- package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/helm/dscp-ipfs/Chart.yaml b/helm/dscp-ipfs/Chart.yaml index ecee30d..55c9085 100644 --- a/helm/dscp-ipfs/Chart.yaml +++ b/helm/dscp-ipfs/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 name: dscp-ipfs -appVersion: '2.0.2' +appVersion: '2.0.3' description: A Helm chart for dscp-ipfs -version: '2.0.2' +version: '2.0.3' type: application dependencies: - name: dscp-node diff --git a/helm/dscp-ipfs/values.yaml b/helm/dscp-ipfs/values.yaml index dac94fb..569c45a 100644 --- a/helm/dscp-ipfs/values.yaml +++ b/helm/dscp-ipfs/values.yaml @@ -33,7 +33,7 @@ config: image: repository: ghcr.io/digicatapult/dscp-ipfs pullPolicy: IfNotPresent - tag: 'v2.0.2' + tag: 'v2.0.3' storage: storageClass: "" diff --git a/package-lock.json b/package-lock.json index 1b7e3ee..84c11d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@digicatapult/dscp-ipfs", - "version": "2.0.2", + "version": "2.0.3", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 9f0ccbe..e4f9471 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@digicatapult/dscp-ipfs", - "version": "2.0.2", + "version": "2.0.3", "description": "Service for WASP", "main": "app/index.js", "scripts": { From 99d30ca634ae014932c207654d43ea6d6b268151 Mon Sep 17 00:00:00 2001 From: Paulius Date: Tue, 26 Apr 2022 11:51:06 +0100 Subject: [PATCH 20/40] IN-154: use generic names for poll and timeout env vars. --- .github/workflows/tests.yml | 1 + app/env.js | 4 ++-- app/utils/ServiceWatcher.js | 6 +++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index aa69709..4db9cea 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -177,6 +177,7 @@ jobs: run: npm run test env: SUBSTRATE_STATUS_POLL_PERIOD_MS: 1000 + SUBSTRATE_STATUS_TIMEOUT_MS: 1000 check-version: name: 'Check version' diff --git a/app/env.js b/app/env.js index 7734a98..b63935f 100644 --- a/app/env.js +++ b/app/env.js @@ -33,8 +33,8 @@ const vars = envalid.cleanEnv( METADATA_KEY_LENGTH: envalid.num({ default: 32 }), METADATA_VALUE_LITERAL_LENGTH: envalid.num({ default: 32 }), PROCESS_IDENTIFIER_LENGTH: envalid.num({ default: 32 }), - SUBSTRATE_STATUS_POLL_PERIOD_MS: envalid.num({ default: 30 * 1000 }), - SUBSTRATE_STATUS_TIMEOUT_MS: envalid.num({ default: 2 * 1000 }), + HEALTHCHECK_POLL_PERIOD_MS: envalid.num({ default: 30 * 1000 }), + HEALTHCHECK_TIMEOUT_MS: envalid.num({ default: 2 * 1000 }), }, { strict: true, diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index e5af625..bbb3149 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -1,5 +1,5 @@ const { TimeoutError } = require('./Errors') -const { SUBSTRATE_STATUS_POLL_PERIOD_MS, SUBSTRATE_STATUS_TIMEOUT_MS } = require('../env') +const { HEALTHCHECK_POLL_PERIOD_MS, HEALTHCHECK_TIMEOUT_MS } = require('../env') class ServiceWatcher { #pollPeriod @@ -8,8 +8,8 @@ class ServiceWatcher { constructor(apis) { this.report = {} this.stopped = false - this.#pollPeriod = SUBSTRATE_STATUS_POLL_PERIOD_MS - this.#timeout = SUBSTRATE_STATUS_TIMEOUT_MS + this.#pollPeriod = HEALTHCHECK_POLL_PERIOD_MS + this.#timeout = HEALTHCHECK_TIMEOUT_MS this.services = this.#init(apis) } From 28161afe79882a35ae2d764f8b799809500e993d Mon Sep 17 00:00:00 2001 From: Paulius Date: Tue, 26 Apr 2022 11:52:24 +0100 Subject: [PATCH 21/40] IN-154: test.env update. --- test/test.env | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test.env b/test/test.env index dc4dbb3..b0cda9b 100644 --- a/test/test.env +++ b/test/test.env @@ -1,3 +1,5 @@ LOG_LEVEL=fatal IPFS_LOG_LEVEL=fatal NODE_HOST=localhost +HEALTHCHECK_POLL_PERIOD_MS=1000 +HEALTHCHECK_TIMEOUT_MS=1000 From 0760f4bdf7debdafeb89738fb631f3a753079971 Mon Sep 17 00:00:00 2001 From: Paulius Date: Tue, 26 Apr 2022 11:56:46 +0100 Subject: [PATCH 22/40] IN-154: test.yaml github action update. (left some env vars unstaged) --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4db9cea..bd17485 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -176,8 +176,8 @@ jobs: - name: Run tests run: npm run test env: - SUBSTRATE_STATUS_POLL_PERIOD_MS: 1000 - SUBSTRATE_STATUS_TIMEOUT_MS: 1000 + HEALTHCHECK_POLL_PERIOD_MS: 1000 + HEALTHCHECK_TIMEOUT_MS: 1000 check-version: name: 'Check version' From e4f339e01b48f3caab1068100f7339a2eb43be41 Mon Sep 17 00:00:00 2001 From: Paulius Date: Tue, 26 Apr 2022 12:35:46 +0100 Subject: [PATCH 23/40] IN-154: resolving integration tests. --- app/env.js | 4 ++-- test/integration/httpServer.test.js | 16 ++++------------ 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/app/env.js b/app/env.js index b63935f..67138b9 100644 --- a/app/env.js +++ b/app/env.js @@ -33,8 +33,8 @@ const vars = envalid.cleanEnv( METADATA_KEY_LENGTH: envalid.num({ default: 32 }), METADATA_VALUE_LITERAL_LENGTH: envalid.num({ default: 32 }), PROCESS_IDENTIFIER_LENGTH: envalid.num({ default: 32 }), - HEALTHCHECK_POLL_PERIOD_MS: envalid.num({ default: 30 * 1000 }), - HEALTHCHECK_TIMEOUT_MS: envalid.num({ default: 2 * 1000 }), + HEALTHCHECK_POLL_PERIOD_MS: envalid.num({ default: 30 * 1000, devDefault: 1000 }), + HEALTHCHECK_TIMEOUT_MS: envalid.num({ default: 2 * 1000, devDefault: 1000 }), }, { strict: true, diff --git a/test/integration/httpServer.test.js b/test/integration/httpServer.test.js index 850193a..59f7465 100644 --- a/test/integration/httpServer.test.js +++ b/test/integration/httpServer.test.js @@ -33,19 +33,11 @@ describe('health checks', function () { expect(context.body) .to.have.property('substrate') .that.deep.equal({ - status: 'up', - details: { - chain: 'Development', - runtime: { - name: 'dscp-node', - versions: { - authoring: 1, - impl: 1, - spec: 300, - transaction: 1, - }, - }, + error: { + message: 'Connection is not established, will retry during next polling cycle', + service: 'substrate', }, + status: 'error', }) }) }) From bd0b9bc386abaa5da0eea2e151fe0aa43ed40355 Mon Sep 17 00:00:00 2001 From: Paulius Date: Tue, 26 Apr 2022 12:45:29 +0100 Subject: [PATCH 24/40] IN-154: both services are up when running integration tests, reflect in assertations. --- .github/workflows/tests.yml | 1 - app/env.js | 4 +-- test/integration/httpServer.test.js | 51 ++++++++++++++--------------- 3 files changed, 27 insertions(+), 29 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bd17485..19238a0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -177,7 +177,6 @@ jobs: run: npm run test env: HEALTHCHECK_POLL_PERIOD_MS: 1000 - HEALTHCHECK_TIMEOUT_MS: 1000 check-version: name: 'Check version' diff --git a/app/env.js b/app/env.js index 67138b9..b63935f 100644 --- a/app/env.js +++ b/app/env.js @@ -33,8 +33,8 @@ const vars = envalid.cleanEnv( METADATA_KEY_LENGTH: envalid.num({ default: 32 }), METADATA_VALUE_LITERAL_LENGTH: envalid.num({ default: 32 }), PROCESS_IDENTIFIER_LENGTH: envalid.num({ default: 32 }), - HEALTHCHECK_POLL_PERIOD_MS: envalid.num({ default: 30 * 1000, devDefault: 1000 }), - HEALTHCHECK_TIMEOUT_MS: envalid.num({ default: 2 * 1000, devDefault: 1000 }), + HEALTHCHECK_POLL_PERIOD_MS: envalid.num({ default: 30 * 1000 }), + HEALTHCHECK_TIMEOUT_MS: envalid.num({ default: 2 * 1000 }), }, { strict: true, diff --git a/test/integration/httpServer.test.js b/test/integration/httpServer.test.js index 59f7465..b3214d2 100644 --- a/test/integration/httpServer.test.js +++ b/test/integration/httpServer.test.js @@ -12,33 +12,32 @@ describe('health checks', function () { context.body = await context.response.json() }) - describe('if any of the services status is down or error', () => { - it('should return 503', function () { - expect(context.response.status).to.equal(503) - }) + it('should returns 200', function () { + expect(context.response.status).to.equal(200) + }) - it('and report contains IPFS status', function () { - expect(context.body) - .to.have.property('ipfs') - .that.deep.equal({ - error: { - message: 'Connection is not established, will retry during next polling cycle', - service: 'ipfs', - }, - status: 'error', - }) - }) + it('and report contains IPFS status', function () { + expect(context.body) + .to.have.property('ipfs') + .that.deep.equal({ + error: { + message: 'Connection is not established, will retry during next polling cycle', + service: 'ipfs', + }, + status: 'error', + }) + }) - it('also contains substrate node status', () => { - expect(context.body) - .to.have.property('substrate') - .that.deep.equal({ - error: { - message: 'Connection is not established, will retry during next polling cycle', - service: 'substrate', - }, - status: 'error', - }) - }) + it('also contains substrate node status', () => { + expect(context.body) + .to.have.property('substrate') + .to.have.property('ipfs') + .that.deep.equal({ + error: { + message: 'Connection is not established, will retry during next polling cycle', + service: 'substrate', + }, + status: 'error', + }) }) }) From 4e730351d7543c653b03e9fbedec9d9239607639 Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 27 Apr 2022 08:56:28 +0100 Subject: [PATCH 25/40] IN-154: add console log. --- .github/workflows/tests.yml | 1 + test/integration/httpServer.test.js | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 19238a0..bd17485 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -177,6 +177,7 @@ jobs: run: npm run test env: HEALTHCHECK_POLL_PERIOD_MS: 1000 + HEALTHCHECK_TIMEOUT_MS: 1000 check-version: name: 'Check version' diff --git a/test/integration/httpServer.test.js b/test/integration/httpServer.test.js index b3214d2..b65bf48 100644 --- a/test/integration/httpServer.test.js +++ b/test/integration/httpServer.test.js @@ -10,6 +10,7 @@ describe('health checks', function () { before(async function () { context.response = await fetch(`http://localhost:${PORT}/health`) context.body = await context.response.json() + console.log(context) }) it('should returns 200', function () { From d8ff4e7b7523722983c45a22e2119d888cf7ab8a Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 27 Apr 2022 09:46:03 +0100 Subject: [PATCH 26/40] IN-154: updating property names for substrate api. --- app/env.js | 4 +- app/keyWatcher/index.js | 4 +- test/integration/httpServer.test.js | 60 ++++++++++++++++------------- 3 files changed, 38 insertions(+), 30 deletions(-) diff --git a/app/env.js b/app/env.js index b63935f..67138b9 100644 --- a/app/env.js +++ b/app/env.js @@ -33,8 +33,8 @@ const vars = envalid.cleanEnv( METADATA_KEY_LENGTH: envalid.num({ default: 32 }), METADATA_VALUE_LITERAL_LENGTH: envalid.num({ default: 32 }), PROCESS_IDENTIFIER_LENGTH: envalid.num({ default: 32 }), - HEALTHCHECK_POLL_PERIOD_MS: envalid.num({ default: 30 * 1000 }), - HEALTHCHECK_TIMEOUT_MS: envalid.num({ default: 2 * 1000 }), + HEALTHCHECK_POLL_PERIOD_MS: envalid.num({ default: 30 * 1000, devDefault: 1000 }), + HEALTHCHECK_TIMEOUT_MS: envalid.num({ default: 2 * 1000, devDefault: 1000 }), }, { strict: true, diff --git a/app/keyWatcher/index.js b/app/keyWatcher/index.js index 7b0e1fb..211a108 100644 --- a/app/keyWatcher/index.js +++ b/app/keyWatcher/index.js @@ -10,8 +10,8 @@ module.exports = { }, nodeHealthCheck: async (api, name = 'substrate') => { try { - if (!(await api.isConnected)) throw new ConnectionError({ name }) - const [chain, runtime] = await Promise.all([api.runtimeChain, api.runtimeVersion]) + if (!(await api._isConnected)) throw new ConnectionError({ name }) + const [chain, runtime] = await Promise.all([api._runtimeChain, api._runtimeVersion]) return { name, diff --git a/test/integration/httpServer.test.js b/test/integration/httpServer.test.js index b65bf48..850193a 100644 --- a/test/integration/httpServer.test.js +++ b/test/integration/httpServer.test.js @@ -10,35 +10,43 @@ describe('health checks', function () { before(async function () { context.response = await fetch(`http://localhost:${PORT}/health`) context.body = await context.response.json() - console.log(context) }) - it('should returns 200', function () { - expect(context.response.status).to.equal(200) - }) + describe('if any of the services status is down or error', () => { + it('should return 503', function () { + expect(context.response.status).to.equal(503) + }) - it('and report contains IPFS status', function () { - expect(context.body) - .to.have.property('ipfs') - .that.deep.equal({ - error: { - message: 'Connection is not established, will retry during next polling cycle', - service: 'ipfs', - }, - status: 'error', - }) - }) + it('and report contains IPFS status', function () { + expect(context.body) + .to.have.property('ipfs') + .that.deep.equal({ + error: { + message: 'Connection is not established, will retry during next polling cycle', + service: 'ipfs', + }, + status: 'error', + }) + }) - it('also contains substrate node status', () => { - expect(context.body) - .to.have.property('substrate') - .to.have.property('ipfs') - .that.deep.equal({ - error: { - message: 'Connection is not established, will retry during next polling cycle', - service: 'substrate', - }, - status: 'error', - }) + it('also contains substrate node status', () => { + expect(context.body) + .to.have.property('substrate') + .that.deep.equal({ + status: 'up', + details: { + chain: 'Development', + runtime: { + name: 'dscp-node', + versions: { + authoring: 1, + impl: 1, + spec: 300, + transaction: 1, + }, + }, + }, + }) + }) }) }) From 334f2336484b4cc2458b2bc5e065a99bc4e747b6 Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 27 Apr 2022 10:20:04 +0100 Subject: [PATCH 27/40] IN-154: fixtures update. --- test/__fixtures__/substrate-node-api-fn.js | 10 +++++----- test/integration/httpServer.test.js | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/__fixtures__/substrate-node-api-fn.js b/test/__fixtures__/substrate-node-api-fn.js index 93b1d72..7500a5b 100644 --- a/test/__fixtures__/substrate-node-api-fn.js +++ b/test/__fixtures__/substrate-node-api-fn.js @@ -2,26 +2,26 @@ const { nodeHealthCheck } = require("../../app/keyWatcher") module.exports = { timeout: { - get isConnected() { + get _isConnected() { return new Promise(r => setTimeout(r, 4000)) }, healthCheck: nodeHealthCheck, }, unavailable: { - get isConnected() { + get _isConnected() { return false }, healthCheck: nodeHealthCheck, }, available: { healthCheck: nodeHealthCheck, - get isConnected() { + get _isConnected() { return true }, - get runtimeChain() { + get _runtimeChain() { return "Test" }, - get runtimeVersion() { + get _runtimeVersion() { return { specName: "dscp-node", implName: "dscp-node", diff --git a/test/integration/httpServer.test.js b/test/integration/httpServer.test.js index 850193a..4065168 100644 --- a/test/integration/httpServer.test.js +++ b/test/integration/httpServer.test.js @@ -37,11 +37,11 @@ describe('health checks', function () { details: { chain: 'Development', runtime: { - name: 'dscp-node', + name: 'dscp', versions: { authoring: 1, impl: 1, - spec: 300, + spec: 310, transaction: 1, }, }, From 9d5a13f6fc77eb858f082c9d37394afafb1fc5f6 Mon Sep 17 00:00:00 2001 From: Paulius Date: Thu, 28 Apr 2022 13:10:58 +0100 Subject: [PATCH 28/40] IN0-154: git reset --hard to remove duplicate commits. --- .github/workflows/tests.yml | 3 --- test/integration/httpServer.test.js | 40 +++-------------------------- 2 files changed, 3 insertions(+), 40 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bd17485..69cca6e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -175,9 +175,6 @@ jobs: time: '30s' - name: Run tests run: npm run test - env: - HEALTHCHECK_POLL_PERIOD_MS: 1000 - HEALTHCHECK_TIMEOUT_MS: 1000 check-version: name: 'Check version' diff --git a/test/integration/httpServer.test.js b/test/integration/httpServer.test.js index 4065168..c957a5d 100644 --- a/test/integration/httpServer.test.js +++ b/test/integration/httpServer.test.js @@ -12,41 +12,7 @@ describe('health checks', function () { context.body = await context.response.json() }) - describe('if any of the services status is down or error', () => { - it('should return 503', function () { - expect(context.response.status).to.equal(503) - }) - - it('and report contains IPFS status', function () { - expect(context.body) - .to.have.property('ipfs') - .that.deep.equal({ - error: { - message: 'Connection is not established, will retry during next polling cycle', - service: 'ipfs', - }, - status: 'error', - }) - }) - - it('also contains substrate node status', () => { - expect(context.body) - .to.have.property('substrate') - .that.deep.equal({ - status: 'up', - details: { - chain: 'Development', - runtime: { - name: 'dscp', - versions: { - authoring: 1, - impl: 1, - spec: 310, - transaction: 1, - }, - }, - }, - }) - }) + it('returns 200 along with the report', () => { + expect(context.response.status).to.equal(200) }) -}) +}) \ No newline at end of file From 7dad68cc4bbddee6c8270b2820045424468a3974 Mon Sep 17 00:00:00 2001 From: Paulius Date: Thu, 28 Apr 2022 13:14:52 +0100 Subject: [PATCH 29/40] IN-154: prettier. --- test/integration/httpServer.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/httpServer.test.js b/test/integration/httpServer.test.js index c957a5d..12924ad 100644 --- a/test/integration/httpServer.test.js +++ b/test/integration/httpServer.test.js @@ -15,4 +15,4 @@ describe('health checks', function () { it('returns 200 along with the report', () => { expect(context.response.status).to.equal(200) }) -}) \ No newline at end of file +}) From b776ee9f3d77c1fdae895783e6d7d1fdcde653c9 Mon Sep 17 00:00:00 2001 From: Paulius Date: Thu, 28 Apr 2022 16:44:43 +0100 Subject: [PATCH 30/40] IN-154: removing some irelevant comments. --- app/utils/ServiceWatcher.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index bbb3149..859827e 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -5,6 +5,8 @@ class ServiceWatcher { #pollPeriod #timeout + // TODO add a method for adding a sertvice once + // this has been initialized already constructor(apis) { this.report = {} this.stopped = false @@ -29,8 +31,6 @@ class ServiceWatcher { } } - // services that we would like to monitor should be added here - // with [name] and { poll, properties }, more can be added for enrichment #init(services) { return Object.keys(services) .map((service) => { @@ -46,7 +46,7 @@ class ServiceWatcher { } // main generator function with infinate loop - generator(self = this) { + #generator(self = this) { this.stopped = false return { [Symbol.asyncIterator]: async function* () { @@ -72,10 +72,9 @@ class ServiceWatcher { this.stopped = true } - // TODO methood for stopping (update while val) async start() { if (this.services.length < 1) return this.stop() - this.gen = this.generator() + this.gen = this.#generator() for await (const service of this.gen) { const { name, ...details } = service this.update(name, details) From 33a64ca032118d37eb2e6339b70de5e15b972709 Mon Sep 17 00:00:00 2001 From: Paulius Date: Thu, 28 Apr 2022 16:51:07 +0100 Subject: [PATCH 31/40] IN-154: lint - whitespace. --- app/utils/ServiceWatcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index 859827e..a39e3a6 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -5,7 +5,7 @@ class ServiceWatcher { #pollPeriod #timeout - // TODO add a method for adding a sertvice once + // TODO add a method for adding a sertvice once // this has been initialized already constructor(apis) { this.report = {} From a2942aaabb991e1b2718e05472317d47d68b79b7 Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 4 May 2022 01:01:14 +0100 Subject: [PATCH 32/40] IN-154: simplification of generator --- .eslintrc | 1 - app/utils/Errors.js | 2 +- app/utils/ServiceWatcher.js | 60 ++++++++++------------ package-lock.json | 53 ------------------- package.json | 3 +- test/__fixtures__/substrate-node-api-fn.js | 2 +- test/unit/ServiceWatcher.test.js | 36 ++++--------- 7 files changed, 38 insertions(+), 119 deletions(-) diff --git a/.eslintrc b/.eslintrc index b388f4f..9267af4 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,7 +5,6 @@ "es6": true, "node": true }, - "parser": "babel-eslint", "parserOptions": { "ecmaVersion": 9, "sourceType": "module" diff --git a/app/utils/Errors.js b/app/utils/Errors.js index a650118..d68aedc 100644 --- a/app/utils/Errors.js +++ b/app/utils/Errors.js @@ -1,7 +1,7 @@ class TimeoutError extends Error { constructor(service) { super() - this.name = this.constructor.name + this.type = this.constructor.name this.service = service.name this.message = 'Timeout error, no response from a service' } diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index a39e3a6..c554641 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -9,7 +9,6 @@ class ServiceWatcher { // this has been initialized already constructor(apis) { this.report = {} - this.stopped = false this.#pollPeriod = HEALTHCHECK_POLL_PERIOD_MS this.#timeout = HEALTHCHECK_TIMEOUT_MS this.services = this.#init(apis) @@ -45,43 +44,36 @@ class ServiceWatcher { .filter(Boolean) } - // main generator function with infinate loop - #generator(self = this) { - this.stopped = false - return { - [Symbol.asyncIterator]: async function* () { - try { - while (true) { - await self.delay(self.#pollPeriod) - for (const service of self.services) { - yield Promise.race([service.poll(), self.delay(self.#timeout, service)]) - } - } - } catch (error) { - yield { - status: 'down', - name: error.service, - error, - } - } - }, - } - } - - stop() { - this.stopped = true + #poll() { + return Promise.all(this.services.map((service) => + Promise.race([ service.poll(), this.delay(this.#timeout, service)]) + )) } - async start() { - if (this.services.length < 1) return this.stop() + start() { + if (this.services.length < 1) return null this.gen = this.#generator() - for await (const service of this.gen) { - const { name, ...details } = service - this.update(name, details) - // TODO refactor this.stop() method - if (this.stopped) break + + const recursive = async (pollAll = Promise.resolve([])) => { + try { + const services = await pollAll + services.forEach(({ name, ...rest }) => this.update(name, rest)) + } catch (error) { + const name = error.service || 'server' + this.update(name, { error, status: 'error' }) + } + + await this.delay(this.#pollPeriod) + const { value } = this.gen.next() + recursive(value) } - return 'done' + + const { value } = this.gen.next() + recursive(value) + } + + * #generator() { + while(true) yield this.#poll() } } diff --git a/package-lock.json b/package-lock.json index 84c11d6..5aadd6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,6 @@ "pino-http": "^5.5.0" }, "devDependencies": { - "babel-eslint": "^10.1.0", "chai": "^4.3.1", "delay": "^5.0.0", "depcheck": "^1.4.0", @@ -1341,36 +1340,6 @@ "node": ">=8.0.0" } }, - "node_modules/babel-eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", - "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", - "deprecated": "babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.0", - "@babel/traverse": "^7.7.0", - "@babel/types": "^7.7.0", - "eslint-visitor-keys": "^1.0.0", - "resolve": "^1.12.0" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "eslint": ">= 4.12.1" - } - }, - "node_modules/babel-eslint/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -8506,28 +8475,6 @@ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" }, - "babel-eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", - "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.0", - "@babel/traverse": "^7.7.0", - "@babel/types": "^7.7.0", - "eslint-visitor-keys": "^1.0.0", - "resolve": "^1.12.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", diff --git a/package.json b/package.json index e4f9471..7205c6e 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "test": "NODE_ENV=test mocha --config ./test/mocharc.js ./test", "test:integration": "NODE_ENV=test mocha --config ./test/mocharc.js ./test/integration", - "test:unit": "NODE_ENV=test mocha --config ./test/mocharc-unit.js ./test", + "test:unit": "NODE_ENV=test mocha --config ./test/mocharc-unit.js ./test/unit", "lint": "eslint .", "depcheck": "depcheck", "start": "node app/index.js", @@ -39,7 +39,6 @@ "pino-http": "^5.5.0" }, "devDependencies": { - "babel-eslint": "^10.1.0", "chai": "^4.3.1", "delay": "^5.0.0", "depcheck": "^1.4.0", diff --git a/test/__fixtures__/substrate-node-api-fn.js b/test/__fixtures__/substrate-node-api-fn.js index 7500a5b..847592f 100644 --- a/test/__fixtures__/substrate-node-api-fn.js +++ b/test/__fixtures__/substrate-node-api-fn.js @@ -3,7 +3,7 @@ const { nodeHealthCheck } = require("../../app/keyWatcher") module.exports = { timeout: { get _isConnected() { - return new Promise(r => setTimeout(r, 4000)) + return new Promise(r => setTimeout(r, 5000)) }, healthCheck: nodeHealthCheck, }, diff --git a/test/unit/ServiceWatcher.test.js b/test/unit/ServiceWatcher.test.js index 8f99492..5104847 100644 --- a/test/unit/ServiceWatcher.test.js +++ b/test/unit/ServiceWatcher.test.js @@ -21,7 +21,7 @@ describe('ServiceWatcher', function () { }) afterEach(() => { - SW.stop() + SW.gen?.return() }) describe('delay method', () => { @@ -77,8 +77,6 @@ describe('ServiceWatcher', function () { }) }) - // TODO teests for ServiceWatcher.stop() - describe('init method', () => { it('returns an array of services with polling functions', () => { SW = new ServiceWatcher({ substrate: substrate.available }) @@ -109,10 +107,6 @@ describe('ServiceWatcher', function () { expect(SW.gen).to.be.undefined }) - it('sets stopped to true', () => { - expect(SW.stopped).to.equal(true) - }) - it('and has nothing to report', () => { expect(SW.report).to.deep.equal({}) }) @@ -151,11 +145,10 @@ describe('ServiceWatcher', function () { SW = new ServiceWatcher({ substrate: substrate.unavailable }) spy(SW, 'update') SW.start() - await SW.delay(2100) + await SW.delay(2000) + SW.gen.return() }) - afterEach(() => SW.stop()) - it('reflects status in this.report object with error message', () => { expect(SW.report) // prettier-ignore .to.have.property('substrate') @@ -169,7 +162,6 @@ describe('ServiceWatcher', function () { }) it('does not stop polling', () => { - expect(SW.stopped).to.equal(false) expect(SW.update.callCount).to.equal(2) expect(SW.update.getCall(0).args[0]).to.equal('substrate') expect(SW.update.getCall(1).args[0]).to.equal('substrate') @@ -198,12 +190,7 @@ describe('ServiceWatcher', function () { await SW.delay(1000) }) - afterEach(() => { - SW.stop() - }) - it('handles correctly unavalaible service', () => { - expect(SW.stopped).to.equal(false) expect(SW.update.getCall(0).args[0]).to.equal('substrate') expect(SW.update.getCall(0).args[1]) // prettier-ignore .to.include.all.keys('error', 'status') @@ -234,26 +221,21 @@ describe('ServiceWatcher', function () { }, }, ]) - expect(SW.stopped).to.equal(false) }) }) describe('if it hits timeout first', () => { beforeEach(async () => { SW = new ServiceWatcher({ substrate: substrate.timeout }) + spy(SW, 'update') SW.start() // using await so it hits timeout - await SW.delay(3000) - }) - - afterEach(() => { - SW.stop() + await SW.delay(4000) }) - it('creates an instace of timeout error', () => { + it('creates an instace of timeout error with error message', () => { const { error } = SW.report.substrate expect(error).to.be.a.instanceOf(TimeoutError) - expect(error.name).to.equal('TimeoutError') expect(error.message).to.equal('Timeout error, no response from a service') }) @@ -261,18 +243,18 @@ describe('ServiceWatcher', function () { expect(SW.report) // prettier-ignore .to.have.property('substrate') .that.includes.all.keys('status', 'error') - .that.deep.contain({ status: 'down' }) + .that.deep.contain({ status: 'error' }) }) it('continues polling', () => { - expect(SW.stopped).to.equal(false) + expect(SW.update.callCount).to.equal(2) }) }) it('persists substrate node status and details in this.report', async () => { SW.start() await SW.delay(2000) - SW.stop() + SW.gen.return() expect(SW.report) // prettier-ignore .to.have.property('substrate') From 8ba0a15786deae8d1c5d010e6a770b601083a0da Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 4 May 2022 01:15:27 +0100 Subject: [PATCH 33/40] IN-154: babel-eslint for linting ES6 feature like private properties and a?.b object conditions. --- .eslintrc | 1 + app/utils/ServiceWatcher.js | 12 ++++----- package-lock.json | 53 +++++++++++++++++++++++++++++++++++++ package.json | 1 + 4 files changed, 61 insertions(+), 6 deletions(-) diff --git a/.eslintrc b/.eslintrc index 9267af4..b388f4f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,6 +5,7 @@ "es6": true, "node": true }, + "parser": "babel-eslint", "parserOptions": { "ecmaVersion": 9, "sourceType": "module" diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index c554641..34997f5 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -45,9 +45,9 @@ class ServiceWatcher { } #poll() { - return Promise.all(this.services.map((service) => - Promise.race([ service.poll(), this.delay(this.#timeout, service)]) - )) + return Promise.all( + this.services.map((service) => Promise.race([service.poll(), this.delay(this.#timeout, service)])) + ) } start() { @@ -71,9 +71,9 @@ class ServiceWatcher { const { value } = this.gen.next() recursive(value) } - - * #generator() { - while(true) yield this.#poll() + + *#generator() { + while (true) yield this.#poll() } } diff --git a/package-lock.json b/package-lock.json index 5aadd6c..84c11d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "pino-http": "^5.5.0" }, "devDependencies": { + "babel-eslint": "^10.1.0", "chai": "^4.3.1", "delay": "^5.0.0", "depcheck": "^1.4.0", @@ -1340,6 +1341,36 @@ "node": ">=8.0.0" } }, + "node_modules/babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "deprecated": "babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "eslint": ">= 4.12.1" + } + }, + "node_modules/babel-eslint/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -8475,6 +8506,28 @@ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" }, + "babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", diff --git a/package.json b/package.json index 7205c6e..4693f4f 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "pino-http": "^5.5.0" }, "devDependencies": { + "babel-eslint": "^10.1.0", "chai": "^4.3.1", "delay": "^5.0.0", "depcheck": "^1.4.0", From 82069a9b5e5467f16ce82c0a6a9464b78bb3c295 Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 4 May 2022 01:18:14 +0100 Subject: [PATCH 34/40] IN-154: assert for instanceOf ConnectionError as well. --- test/unit/ServiceWatcher.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/ServiceWatcher.test.js b/test/unit/ServiceWatcher.test.js index 5104847..35d2031 100644 --- a/test/unit/ServiceWatcher.test.js +++ b/test/unit/ServiceWatcher.test.js @@ -5,7 +5,7 @@ const { spy } = require('sinon') const substrate = require('../__fixtures__/substrate-node-api-fn') const ipfs = require('../__fixtures__/ipfs-api-fn') const ServiceWatcher = require('../../app/utils/ServiceWatcher') -const { TimeoutError } = require('../../app/utils/Errors') +const { TimeoutError, ConnectionError } = require('../../app/utils/Errors') const connectionErrorMsg = 'Connection is not established, will retry during next polling cycle' @@ -156,6 +156,7 @@ describe('ServiceWatcher', function () { .that.deep.contain({ status: 'error' }) expect(SW.report.substrate.error) // prettier-ignore .to.have.all.keys('message', 'service') + .that.is.a.instanceOf(ConnectionError) .that.contains({ message: connectionErrorMsg, }) From 1c6da138f7d0ed067ff540e7e577e001d7e134e7 Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 4 May 2022 01:26:31 +0100 Subject: [PATCH 35/40] IN-154: clean up. --- test/unit/ServiceWatcher.test.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/unit/ServiceWatcher.test.js b/test/unit/ServiceWatcher.test.js index 35d2031..c646090 100644 --- a/test/unit/ServiceWatcher.test.js +++ b/test/unit/ServiceWatcher.test.js @@ -149,6 +149,12 @@ describe('ServiceWatcher', function () { SW.gen.return() }) + it('creates an instance of ConnectionError', () => { + expect(SW.report.substrate) // prettier-ignore + .to.have.all.keys('error') + .that.is.a.instanceOf(ConnectionError) + }) + it('reflects status in this.report object with error message', () => { expect(SW.report) // prettier-ignore .to.have.property('substrate') @@ -156,7 +162,6 @@ describe('ServiceWatcher', function () { .that.deep.contain({ status: 'error' }) expect(SW.report.substrate.error) // prettier-ignore .to.have.all.keys('message', 'service') - .that.is.a.instanceOf(ConnectionError) .that.contains({ message: connectionErrorMsg, }) From 9159614af6853d2181b3f2b74b8dafce8cdb0588 Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 4 May 2022 01:31:53 +0100 Subject: [PATCH 36/40] IN-154: wrong assertation for ConnectionError. --- test/unit/ServiceWatcher.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/ServiceWatcher.test.js b/test/unit/ServiceWatcher.test.js index c646090..b6350f3 100644 --- a/test/unit/ServiceWatcher.test.js +++ b/test/unit/ServiceWatcher.test.js @@ -151,7 +151,7 @@ describe('ServiceWatcher', function () { it('creates an instance of ConnectionError', () => { expect(SW.report.substrate) // prettier-ignore - .to.have.all.keys('error') + .to.have.property('error') .that.is.a.instanceOf(ConnectionError) }) From d75850bb79d79b59bf8ca0b1370d34646f8f3b65 Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 4 May 2022 01:36:39 +0100 Subject: [PATCH 37/40] IN-154: remove troublesome assertataion. --- test/unit/ServiceWatcher.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/unit/ServiceWatcher.test.js b/test/unit/ServiceWatcher.test.js index b6350f3..685365c 100644 --- a/test/unit/ServiceWatcher.test.js +++ b/test/unit/ServiceWatcher.test.js @@ -145,7 +145,7 @@ describe('ServiceWatcher', function () { SW = new ServiceWatcher({ substrate: substrate.unavailable }) spy(SW, 'update') SW.start() - await SW.delay(2000) + await SW.delay(1500) SW.gen.return() }) @@ -168,7 +168,6 @@ describe('ServiceWatcher', function () { }) it('does not stop polling', () => { - expect(SW.update.callCount).to.equal(2) expect(SW.update.getCall(0).args[0]).to.equal('substrate') expect(SW.update.getCall(1).args[0]).to.equal('substrate') expect(SW.update.getCall(1).args[1]) From 24d083755943a6fd34979f82307eb01a191c5ee9 Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 4 May 2022 08:38:03 +0100 Subject: [PATCH 38/40] IN-154: keep it simple, abstracting this.#poll method. --- app/utils/ServiceWatcher.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index 34997f5..c96e624 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -44,19 +44,13 @@ class ServiceWatcher { .filter(Boolean) } - #poll() { - return Promise.all( - this.services.map((service) => Promise.race([service.poll(), this.delay(this.#timeout, service)])) - ) - } - start() { if (this.services.length < 1) return null this.gen = this.#generator() - const recursive = async (pollAll = Promise.resolve([])) => { + const recursive = async (getAll = Promise.resolve([])) => { try { - const services = await pollAll + const services = await getAll services.forEach(({ name, ...rest }) => this.update(name, rest)) } catch (error) { const name = error.service || 'server' @@ -73,7 +67,9 @@ class ServiceWatcher { } *#generator() { - while (true) yield this.#poll() + while (true) yield Promise.all( + this.services.map((service) => Promise.race([service.poll(), this.delay(this.#timeout, service)])) + ) } } From 023b3275bea5860799c36ac50ad18e7fb436ea76 Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 4 May 2022 09:06:42 +0100 Subject: [PATCH 39/40] IN-154: linting error. --- app/utils/ServiceWatcher.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index c96e624..5319c21 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -67,9 +67,10 @@ class ServiceWatcher { } *#generator() { - while (true) yield Promise.all( - this.services.map((service) => Promise.race([service.poll(), this.delay(this.#timeout, service)])) - ) + while (true) + yield Promise.all( + this.services.map((service) => Promise.race([service.poll(), this.delay(this.#timeout, service)])) + ) } } From 1bb34d941b9cd62edf23271ea3037fcfe091138b Mon Sep 17 00:00:00 2001 From: Paulius Date: Wed, 4 May 2022 09:50:24 +0100 Subject: [PATCH 40/40] IN-154: comments. --- app/utils/ServiceWatcher.js | 10 +++++++--- test/unit/ServiceWatcher.test.js | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/utils/ServiceWatcher.js b/app/utils/ServiceWatcher.js index 5319c21..ab33fb5 100644 --- a/app/utils/ServiceWatcher.js +++ b/app/utils/ServiceWatcher.js @@ -5,8 +5,7 @@ class ServiceWatcher { #pollPeriod #timeout - // TODO add a method for adding a sertvice once - // this has been initialized already + // TODO add a method for updating this.services constructor(apis) { this.report = {} this.#pollPeriod = HEALTHCHECK_POLL_PERIOD_MS @@ -30,6 +29,7 @@ class ServiceWatcher { } } + // organize services and store in this.services #init(services) { return Object.keys(services) .map((service) => { @@ -44,6 +44,8 @@ class ServiceWatcher { .filter(Boolean) } + // fire and forget, cancel using ServiceWatcher.gen.return() + // or ServiceWatcher.gen.throw() start() { if (this.services.length < 1) return null this.gen = this.#generator() @@ -52,12 +54,13 @@ class ServiceWatcher { try { const services = await getAll services.forEach(({ name, ...rest }) => this.update(name, rest)) + await this.delay(this.#pollPeriod) } catch (error) { + // if no service assume that this is server error e.g. TypeError, Parse... const name = error.service || 'server' this.update(name, { error, status: 'error' }) } - await this.delay(this.#pollPeriod) const { value } = this.gen.next() recursive(value) } @@ -66,6 +69,7 @@ class ServiceWatcher { recursive(value) } + // a generator function that returns poll fn for each service *#generator() { while (true) yield Promise.all( diff --git a/test/unit/ServiceWatcher.test.js b/test/unit/ServiceWatcher.test.js index 685365c..046b126 100644 --- a/test/unit/ServiceWatcher.test.js +++ b/test/unit/ServiceWatcher.test.js @@ -234,7 +234,7 @@ describe('ServiceWatcher', function () { SW = new ServiceWatcher({ substrate: substrate.timeout }) spy(SW, 'update') SW.start() // using await so it hits timeout - await SW.delay(4000) + await SW.delay(3000) }) it('creates an instace of timeout error with error message', () => {