From c06486dd619f3bd42371b5c6117af94ed67d546d Mon Sep 17 00:00:00 2001 From: Ranko Orlic Date: Wed, 3 Apr 2024 09:51:22 +0200 Subject: [PATCH] chore: add cancellation --- README.md | 21 +++++++++++---------- src/controller.ts | 37 +++++++++++++++++++++++++------------ src/server.ts | 38 +++++++++++++++++++++++++------------- 3 files changed, 61 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 6fbf62e..2669cc7 100644 --- a/README.md +++ b/README.md @@ -135,25 +135,26 @@ curl "http://localhost:9000/test/c47a3487-2f9f-433c-ab5a-82b196fff7e1/results" curl "http://localhost:9000/test/c47a3487-2f9f-433c-ab5a-82b196fff7e1/jmeter.log" ``` -### `DELETE /test/` -- Remove Test Run -Removes the test run with the given ID and its related data including resuls, so use with caution. -```bash -curl -X DELETE http://localhost:9000/test/c47a3487-2f9f-433c-ab5a-82b196fff7e1 -``` -> **Note** that if the test is still running, you need to confirm the deletion by adding `?confirm=true`, e.g. +### `DELETE /test/[?confirm=true]` -- Cancel Test Run or Remove Test And Results +If confirmed (`?confirm=true`), removes the test run with the given ID and its related data including results, so use with caution. If a test is running it is first cancelled. E.g.: ```bash curl -X DELETE http://localhost:9000/test/c47a3487-2f9f-433c-ab5a-82b196fff7e1?confirm=true ``` -### `DELETE /` -- Remove All Test Runs -Removes all tests and their related data including resuls, so use with extreme caution. +If not confirmed, a running test is simply cancelled. E.g.: ```bash -curl -X DELETE http://localhost:9000/test +curl -X DELETE http://localhost:9000/test/c47a3487-2f9f-433c-ab5a-82b196fff7e1 ``` -> **Note** that if a test is still running, you need to confirm the deletion by adding `?confirm=true`, e.g. +### `DELETE /test[?confirm=true]` -- Cancel All Test Runs or Delete All Tests +If confirmed (`?confirm=true`), removes all tests and their related data including results, so use with extreme caution. All running tests are cancelled before deletion. E.g.: ```bash curl -X DELETE http://localhost:9000/test?confirm=true ``` +If not confirmed, all running tests are simply cancelled. E.g. +```bash +curl -X DELETE http://localhost:9000/test +``` + ### `GET /prometheus` -- Get Metrics Exposes the metrics using [Prometheus](https://prometheus.io/) format. diff --git a/src/controller.ts b/src/controller.ts index 8851208..4149bdc 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -89,19 +89,19 @@ export class Controller { } } - private _exportRun(id: string) { - const run = this._getTest(id)!.run; - this._writeMetadata({ ...run, status: TestRunStatus.cancelled }); - this._moveToResults(id); + private _exportTestRun(run: TestRun) { + this._writeMetadata(run); + this._moveToResults(run.id); } - private _cancelTest(id: string) { - const test = this._getTest(id)!; + private _cancelTest(test: Test) { + const id = test.run.id; console.warn(`[WARN] Test ${id} is running...`); const process = test.process; console.warn(`[WARN] Killing pid ${process?.pid}...`); const killed = process?.kill; console.warn(killed ? `[WARN] Test ${id} was cancelled.` : `Failed to kill test ${id} (pid: ${process?.pid}).`); + return this._upsertTest({run: {...test.run, status: TestRunStatus.cancelled} as TestRun, process: undefined} as Test); } private _writeMetadata(run: TestRun) { @@ -149,10 +149,7 @@ export class Controller { public async exportTestRuns() { const runs = await this._getSubDirectories(this._config.tempFolder); - runs.forEach(id => { - this._cancelTest(id); - this._exportRun(id); - }); + runs.forEach(id => this.cancelTest(id)); } public testExists(id: string): boolean { @@ -164,6 +161,13 @@ export class Controller { return !!test && test.run.status === TestRunStatus.running; } + public cancelTest(id: string) { + const test = this._getTest(id); + if (test) { + this._exportTestRun(this._cancelTest(test).run); + } + } + public deleteTest(id: string) { const testData = path.join(this._config.testFolder, id); const runData = path.join(this._config.tempFolder, id); @@ -171,7 +175,8 @@ export class Controller { const exists = this.testExists(id); if (exists) { if (this.testRunning(id)) { - this._cancelTest(id); + const test = this._getTest(id)!; + this._cancelTest(test); } delete this._testsById[id]; } else { @@ -195,10 +200,18 @@ export class Controller { return exists || testDataExists || runDataExists; } - public deleteAllTestRuns() { + public deleteAllTests() { this._tests.map(x => this.deleteTest(x.run.id)); } + public cancelAllRunningTests() { + this._tests.map(x => { + if (x.process && !x.process.exitCode) { + this.cancelTest(x.run.id) + } + }); + } + public async getTestRunStatus(id: string, limit: number = 1000) { const test = this._getTest(id); if (!test) throw new Error(`Test ${id} does not exist.`); diff --git a/src/server.ts b/src/server.ts index 4321a26..ce49477 100644 --- a/src/server.ts +++ b/src/server.ts @@ -157,14 +157,23 @@ server.delete('/test', { schema: { querystring: { confirm: { type: 'boolean' } } } const parameters = request.query as { confirm?: boolean }; + const cancelOnly = !parameters.confirm; try { - if (controller.runningCount > 0 && !parameters.confirm) { - return reply.status(405).send("Cannot delete all tests as some are still running.\nHint:pass query parameter '?confirm=true'.\n"); - } else { - controller.deleteAllTestRuns(); + const anyTestRunning = controller.runningCount > 0; + if (anyTestRunning) { + controller.cancelAllRunningTests(); + } + + if (!cancelOnly) { + controller.deleteAllTests(); return reply.send('All tests deleted\n'); } + + return anyTestRunning + ? reply.send('All running tests cancelled\n') + : reply.status(405).send("No tests cancelled nor deleted.\nHint:pass query parameter '?confirm=true' to actually delete all tests.\n"); + } catch (error) { return reply.send({ msg: 'Cannot delete all tests\n', error: error }); } @@ -177,18 +186,21 @@ server.delete('/test/:id', { schema: { querystring: { confirm: { type: 'boolean' const { id } = request.params as { id: string }; const parameters = request.query as { confirm?: boolean }; + const cancelOnly = !parameters.confirm; try { - if (controller.testRunning(id) && !parameters.confirm) { - return reply.status(405).send(`Test ${id} is still running.\nHint:pass query parameter '?confirm=true'.\n`); - } else { - const deleted = controller.deleteTest(id); - if (deleted) { - return reply.send(`Test ${id} deleted\n`); - } else { - return reply.status(404).send(`Test ${id} not found\n`); - } + if (controller.testRunning(id)) { + controller.cancelTest(id); + } + + if (cancelOnly) { + return reply.send(`Test ${id} cancelled\n`); } + + return controller.deleteTest(id) + ? reply.send(`Test ${id} deleted\n`) + : reply.status(404).send(`Test ${id} not found\n`); + } catch (error) { return reply.send({ msg: `Cannot delete test ${id}\n`, error: error }); }