From d6038fadfcb2a107eaba6fa67c2c6fe6dbbc7d03 Mon Sep 17 00:00:00 2001 From: James Norton Date: Wed, 22 Jan 2025 16:23:24 -0500 Subject: [PATCH 1/9] HARMONY-1996: Add EMPTY_RESULT to WorkItemStatus --- db/db.sql | 2 +- .../work-item-updates.ts | 3 +- .../harmony/app/models/work-item-interface.ts | 2 + .../harmony/test/work-items/work-backends.ts | 65 ++++++++++++++++++- 4 files changed, 69 insertions(+), 3 deletions(-) diff --git a/db/db.sql b/db/db.sql index 417d26737..ab3f1c95c 100644 --- a/db/db.sql +++ b/db/db.sql @@ -80,7 +80,7 @@ CREATE TABLE `work_items` ( `workflowStepIndex` integer not null, `scrollID` varchar(4096), `serviceID` varchar(255) not null, - `status` text check (`status` in ('ready', 'queued', 'running', 'successful', 'failed', 'canceled')) not null, + `status` text check (`status` in ('ready', 'queued', 'running', 'successful', 'failed', 'canceled', 'empty-result')) not null, `stacCatalogLocation` varchar(255), `totalItemsSize` double precision not null default 0, `outputItemSizesJson` text, diff --git a/services/harmony/app/backends/workflow-orchestration/work-item-updates.ts b/services/harmony/app/backends/workflow-orchestration/work-item-updates.ts index 91a9ccdb7..6a17d52b1 100644 --- a/services/harmony/app/backends/workflow-orchestration/work-item-updates.ts +++ b/services/harmony/app/backends/workflow-orchestration/work-item-updates.ts @@ -552,7 +552,8 @@ export async function preprocessWorkItem( let outputItemSizes; let catalogItems; try { - if (status === WorkItemStatus.SUCCESSFUL && !nextWorkflowStep) { + // TODO fix this in HARMONY-1995 + if ([WorkItemStatus.SUCCESSFUL, WorkItemStatus.EMPTY_RESULT].includes(status) && !nextWorkflowStep) { // if we are CREATING STAC CATALOGS for the last step in the chain we should read the catalog items // since they are needed for generating the output links we will save catalogItems = await readCatalogsItems(results); diff --git a/services/harmony/app/models/work-item-interface.ts b/services/harmony/app/models/work-item-interface.ts index 0d9cc531e..caafd600b 100644 --- a/services/harmony/app/models/work-item-interface.ts +++ b/services/harmony/app/models/work-item-interface.ts @@ -9,12 +9,14 @@ export enum WorkItemStatus { SUCCESSFUL = 'successful', FAILED = 'failed', CANCELED = 'canceled', + EMPTY_RESULT = 'empty-result', } export const COMPLETED_WORK_ITEM_STATUSES = [ WorkItemStatus.SUCCESSFUL, WorkItemStatus.FAILED, WorkItemStatus.CANCELED, + WorkItemStatus.EMPTY_RESULT, ]; export interface WorkItemRecord { diff --git a/services/harmony/test/work-items/work-backends.ts b/services/harmony/test/work-items/work-backends.ts index 34c123d42..82db6a15a 100644 --- a/services/harmony/test/work-items/work-backends.ts +++ b/services/harmony/test/work-items/work-backends.ts @@ -476,6 +476,69 @@ describe('Work Backends', function () { }); }); + describe('when the work item completes with an empty result', async function () { + hookJobCreation(jobRecord); + hookWorkflowStepCreation(workflowStepRecord); + const runningWorkItemRecord = { + ...workItemRecord, + ...{ + status: WorkItemStatus.RUNNING, + startedAt: new Date(), + }, + }; + hookWorkItemCreation(runningWorkItemRecord); + const emptyResultWorkItemRecord = { + ...workItemRecord, + ...{ + status: WorkItemStatus.EMPTY_RESULT, + results: [getStacLocation({ id: workItemRecord.id, jobID: workItemRecord.jobID }, 'catalog.json')], + outputItemSizes: [], + duration: 0, + }, + }; + before(async () => { + await fakeServiceStacOutput(emptyResultWorkItemRecord.jobID, emptyResultWorkItemRecord.id); + }); + hookWorkItemUpdate((r) => r.send(emptyResultWorkItemRecord)); + + it('sets the work item status to empty result', async function () { + const updatedWorkItem = await getWorkItemById(db, this.workItem.id); + expect(updatedWorkItem.status).to.equal(WorkItemStatus.EMPTY_RESULT); + }); + + describe('and the worker computed duration is less than the harmony computed duration', async function () { + it('sets the work item duration to the harmony computed duration', async function () { + const updatedWorkItem = await getWorkItemById(db, this.workItem.id); + expect(updatedWorkItem.duration).to.be.greaterThan(emptyResultWorkItemRecord.duration); + }); + }); + + describe('and the work item is the last in the chain', async function () { + it('sets the job updatedAt field to the current time', async function () { + const { job: updatedJob } = await Job.byJobID(db, this.job.jobID); + expect(updatedJob.updatedAt.valueOf()).to.greaterThan(this.job.updatedAt.valueOf()); + }); + + // TODO this will change with HARMONY-1995 + it('adds a link for the work results to the job', async function () { + const { job: updatedJob } = await Job.byJobID(db, this.job.jobID, true); + expect(updatedJob.links.filter( + (jobLink) => jobLink.href === expectedLink, + ).length).to.equal(1); + }); + + it('sets the job status to complete', async function () { + const { job: updatedJob } = await Job.byJobID(db, this.job.jobID); + expect(updatedJob.status === JobStatus.SUCCESSFUL); + }); + + it('sets the job progress to 100', async function () { + const { job: updatedJob } = await Job.byJobID(db, this.job.jobID); + expect(updatedJob.progress).to.equal(100); + }); + }); + }); + describe('when a retried work item succeeds on the original worker before the retry finishes', async function () { hookJobCreation(jobRecord); hookWorkflowStepCreation(workflowStepRecord); @@ -539,7 +602,7 @@ describe('Work Backends', function () { } // tests to make sure work-items cannot be updated once they are in a terminal state - for (const terminalState of [WorkItemStatus.CANCELED, WorkItemStatus.FAILED, WorkItemStatus.SUCCESSFUL]) { + for (const terminalState of [WorkItemStatus.CANCELED, WorkItemStatus.FAILED, WorkItemStatus.SUCCESSFUL, WorkItemStatus.EMPTY_RESULT]) { describe(`When the work-item is already in state "${terminalState}"`, async function () { const newWorkItemRecord = { ...workItemRecord, ...{ status: terminalState }, From 8ccb9d3cfca66d45284d8b4ec1c3b3d0e1b71594 Mon Sep 17 00:00:00 2001 From: James Norton Date: Thu, 23 Jan 2025 10:26:23 -0500 Subject: [PATCH 2/9] HARMONY-1996: Add new db migration file missed on previous commit --- ...8_add_empty_result_status_to_work_items.js | 25 +++++++++++++++++++ services/harmony/app/models/work-item.ts | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 db/migrations/20250122190038_add_empty_result_status_to_work_items.js diff --git a/db/migrations/20250122190038_add_empty_result_status_to_work_items.js b/db/migrations/20250122190038_add_empty_result_status_to_work_items.js new file mode 100644 index 000000000..d9796bba4 --- /dev/null +++ b/db/migrations/20250122190038_add_empty_result_status_to_work_items.js @@ -0,0 +1,25 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function (knex, Promise) { + return knex.schema.raw(` + ALTER TABLE "work_items" + DROP CONSTRAINT "work_items_status_check", + ADD CONSTRAINT "work_items_status_check" + CHECK (status IN ('ready', 'queued', 'running', 'successful', 'failed', 'canceled', 'empty-result')) + `); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function (knex) { + return knex.schema.raw(` + ALTER TABLE "work_items" + DROP CONSTRAINT "work_items_status_check", + ADD CONSTRAINT "work_items_status_check" + CHECK (status IN ('ready', 'queued', 'running', 'successful', 'failed', 'canceled')) + `); +}; \ No newline at end of file diff --git a/services/harmony/app/models/work-item.ts b/services/harmony/app/models/work-item.ts index 4dca4a22d..0f483366d 100644 --- a/services/harmony/app/models/work-item.ts +++ b/services/harmony/app/models/work-item.ts @@ -256,7 +256,7 @@ export async function getNextWorkItems( .whereIn('id', workItemData.map((w) => w.id)); } } catch (e) { - logger.error(`Error getting next work item for service [${serviceID}] and job [${jobID}]`); + logger.error(`Error getting next work items for service [${serviceID}] and job [${jobID}]`); logger.error(e); throw e; } From 6ad13b8fdfe2b50e0a10d2bdb115dcc351d54610 Mon Sep 17 00:00:00 2001 From: James Norton Date: Thu, 23 Jan 2025 14:38:15 -0500 Subject: [PATCH 3/9] HARMONY-1996: Change empty-result status to no-data --- db/db.sql | 2 +- ...8_add_empty_result_status_to_work_items.js | 2 +- .../work-item-updates.ts | 2 +- .../harmony/app/models/work-item-interface.ts | 4 ++-- services/harmony/package-lock.json | 23 +++++++++++++++++++ .../harmony/test/work-items/work-backends.ts | 18 +++++++-------- 6 files changed, 37 insertions(+), 14 deletions(-) diff --git a/db/db.sql b/db/db.sql index ab3f1c95c..ea66870e0 100644 --- a/db/db.sql +++ b/db/db.sql @@ -80,7 +80,7 @@ CREATE TABLE `work_items` ( `workflowStepIndex` integer not null, `scrollID` varchar(4096), `serviceID` varchar(255) not null, - `status` text check (`status` in ('ready', 'queued', 'running', 'successful', 'failed', 'canceled', 'empty-result')) not null, + `status` text check (`status` in ('ready', 'queued', 'running', 'successful', 'failed', 'canceled', 'no-data')) not null, `stacCatalogLocation` varchar(255), `totalItemsSize` double precision not null default 0, `outputItemSizesJson` text, diff --git a/db/migrations/20250122190038_add_empty_result_status_to_work_items.js b/db/migrations/20250122190038_add_empty_result_status_to_work_items.js index d9796bba4..5ceb92bff 100644 --- a/db/migrations/20250122190038_add_empty_result_status_to_work_items.js +++ b/db/migrations/20250122190038_add_empty_result_status_to_work_items.js @@ -7,7 +7,7 @@ exports.up = function (knex, Promise) { ALTER TABLE "work_items" DROP CONSTRAINT "work_items_status_check", ADD CONSTRAINT "work_items_status_check" - CHECK (status IN ('ready', 'queued', 'running', 'successful', 'failed', 'canceled', 'empty-result')) + CHECK (status IN ('ready', 'queued', 'running', 'successful', 'failed', 'canceled', 'no-data')) `); }; diff --git a/services/harmony/app/backends/workflow-orchestration/work-item-updates.ts b/services/harmony/app/backends/workflow-orchestration/work-item-updates.ts index 6a17d52b1..ac2e79cab 100644 --- a/services/harmony/app/backends/workflow-orchestration/work-item-updates.ts +++ b/services/harmony/app/backends/workflow-orchestration/work-item-updates.ts @@ -553,7 +553,7 @@ export async function preprocessWorkItem( let catalogItems; try { // TODO fix this in HARMONY-1995 - if ([WorkItemStatus.SUCCESSFUL, WorkItemStatus.EMPTY_RESULT].includes(status) && !nextWorkflowStep) { + if ([WorkItemStatus.SUCCESSFUL, WorkItemStatus.NO_DATA].includes(status) && !nextWorkflowStep) { // if we are CREATING STAC CATALOGS for the last step in the chain we should read the catalog items // since they are needed for generating the output links we will save catalogItems = await readCatalogsItems(results); diff --git a/services/harmony/app/models/work-item-interface.ts b/services/harmony/app/models/work-item-interface.ts index caafd600b..04bac370d 100644 --- a/services/harmony/app/models/work-item-interface.ts +++ b/services/harmony/app/models/work-item-interface.ts @@ -9,14 +9,14 @@ export enum WorkItemStatus { SUCCESSFUL = 'successful', FAILED = 'failed', CANCELED = 'canceled', - EMPTY_RESULT = 'empty-result', + NO_DATA = 'no-data', } export const COMPLETED_WORK_ITEM_STATUSES = [ WorkItemStatus.SUCCESSFUL, WorkItemStatus.FAILED, WorkItemStatus.CANCELED, - WorkItemStatus.EMPTY_RESULT, + WorkItemStatus.NO_DATA, ]; export interface WorkItemRecord { diff --git a/services/harmony/package-lock.json b/services/harmony/package-lock.json index b529fb021..58fa06945 100644 --- a/services/harmony/package-lock.json +++ b/services/harmony/package-lock.json @@ -5791,6 +5791,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "detect-libc": "^2.0.0", @@ -5811,6 +5812,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -5820,6 +5822,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -5832,6 +5835,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -5844,6 +5848,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, "license": "ISC", "engines": { "node": ">=8" @@ -5853,6 +5858,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, "license": "ISC", "dependencies": { "chownr": "^2.0.0", @@ -9384,6 +9390,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "devOptional": true, "license": "ISC" }, "node_modules/accepts": { @@ -9444,6 +9451,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "devOptional": true, "license": "MIT", "dependencies": { "debug": "4" @@ -9642,6 +9650,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "devOptional": true, "license": "ISC" }, "node_modules/archy": { @@ -9656,6 +9665,7 @@ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", "deprecated": "This package is no longer supported.", + "dev": true, "license": "ISC", "dependencies": { "delegates": "^1.0.0", @@ -10950,6 +10960,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "devOptional": true, "license": "ISC", "bin": { "color-support": "bin.js" @@ -11090,6 +11101,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "devOptional": true, "license": "ISC" }, "node_modules/content-disposition": { @@ -11845,6 +11857,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "devOptional": true, "license": "MIT" }, "node_modules/depd": { @@ -13867,6 +13880,7 @@ "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", "deprecated": "This package is no longer supported.", + "dev": true, "license": "ISC", "dependencies": { "aproba": "^1.0.3 || ^2.0.0", @@ -15062,6 +15076,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "devOptional": true, "license": "ISC" }, "node_modules/hasha": { @@ -15298,6 +15313,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "devOptional": true, "license": "MIT", "dependencies": { "agent-base": "6", @@ -17925,6 +17941,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, "license": "MIT", "dependencies": { "semver": "^6.0.0" @@ -19546,6 +19563,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "devOptional": true, "license": "ISC", "dependencies": { "abbrev": "1" @@ -19741,6 +19759,7 @@ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", "deprecated": "This package is no longer supported.", + "dev": true, "license": "ISC", "dependencies": { "are-we-there-yet": "^2.0.0", @@ -22389,6 +22408,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", + "devOptional": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -22730,6 +22750,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "devOptional": true, "license": "ISC" }, "node_modules/set-cookie-parser": { @@ -22931,6 +22952,7 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "devOptional": true, "license": "ISC" }, "node_modules/sigstore": { @@ -25729,6 +25751,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "devOptional": true, "license": "ISC", "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" diff --git a/services/harmony/test/work-items/work-backends.ts b/services/harmony/test/work-items/work-backends.ts index 82db6a15a..cea836111 100644 --- a/services/harmony/test/work-items/work-backends.ts +++ b/services/harmony/test/work-items/work-backends.ts @@ -476,7 +476,7 @@ describe('Work Backends', function () { }); }); - describe('when the work item completes with an empty result', async function () { + describe('when the work item completes with no data', async function () { hookJobCreation(jobRecord); hookWorkflowStepCreation(workflowStepRecord); const runningWorkItemRecord = { @@ -487,29 +487,29 @@ describe('Work Backends', function () { }, }; hookWorkItemCreation(runningWorkItemRecord); - const emptyResultWorkItemRecord = { + const noDataWorkItemRecord = { ...workItemRecord, ...{ - status: WorkItemStatus.EMPTY_RESULT, + status: WorkItemStatus.NO_DATA, results: [getStacLocation({ id: workItemRecord.id, jobID: workItemRecord.jobID }, 'catalog.json')], outputItemSizes: [], duration: 0, }, }; before(async () => { - await fakeServiceStacOutput(emptyResultWorkItemRecord.jobID, emptyResultWorkItemRecord.id); + await fakeServiceStacOutput(noDataWorkItemRecord.jobID, noDataWorkItemRecord.id); }); - hookWorkItemUpdate((r) => r.send(emptyResultWorkItemRecord)); + hookWorkItemUpdate((r) => r.send(noDataWorkItemRecord)); - it('sets the work item status to empty result', async function () { + it('sets the work item status to no-data', async function () { const updatedWorkItem = await getWorkItemById(db, this.workItem.id); - expect(updatedWorkItem.status).to.equal(WorkItemStatus.EMPTY_RESULT); + expect(updatedWorkItem.status).to.equal(WorkItemStatus.NO_DATA); }); describe('and the worker computed duration is less than the harmony computed duration', async function () { it('sets the work item duration to the harmony computed duration', async function () { const updatedWorkItem = await getWorkItemById(db, this.workItem.id); - expect(updatedWorkItem.duration).to.be.greaterThan(emptyResultWorkItemRecord.duration); + expect(updatedWorkItem.duration).to.be.greaterThan(noDataWorkItemRecord.duration); }); }); @@ -602,7 +602,7 @@ describe('Work Backends', function () { } // tests to make sure work-items cannot be updated once they are in a terminal state - for (const terminalState of [WorkItemStatus.CANCELED, WorkItemStatus.FAILED, WorkItemStatus.SUCCESSFUL, WorkItemStatus.EMPTY_RESULT]) { + for (const terminalState of [WorkItemStatus.CANCELED, WorkItemStatus.FAILED, WorkItemStatus.SUCCESSFUL, WorkItemStatus.NO_DATA]) { describe(`When the work-item is already in state "${terminalState}"`, async function () { const newWorkItemRecord = { ...workItemRecord, ...{ status: terminalState }, From 4fe4a8465cc3f5097734572d4d82cc08d25fe67e Mon Sep 17 00:00:00 2001 From: James Norton Date: Thu, 23 Jan 2025 15:55:40 -0500 Subject: [PATCH 4/9] HARMONY-1996: Changes to tsconfig.json files to fix tsc errors --- packages/util/tsconfig.build.json | 5 ++++- packages/util/tsconfig.json | 5 ++++- services/work-failer/tsconfig.base.json | 3 +++ services/work-reaper/tsconfig.base.json | 3 +++ services/work-scheduler/tsconfig.json | 5 ++++- services/work-updater/tsconfig.json | 5 ++++- tsconfig.base.json | 5 ++++- 7 files changed, 26 insertions(+), 5 deletions(-) diff --git a/packages/util/tsconfig.build.json b/packages/util/tsconfig.build.json index 7708760ca..2d48607d4 100644 --- a/packages/util/tsconfig.build.json +++ b/packages/util/tsconfig.build.json @@ -10,7 +10,10 @@ "allowJs": true, "noImplicitAny": false, "sourceMap": true, - "outDir": "built" + "outDir": "built", + "typeRoots": [ + "node_modules/@types" + ] }, "include": ["."], "exclude": ["./node_modules", "coverage"] diff --git a/packages/util/tsconfig.json b/packages/util/tsconfig.json index 7708760ca..e51a9ea73 100644 --- a/packages/util/tsconfig.json +++ b/packages/util/tsconfig.json @@ -10,7 +10,10 @@ "allowJs": true, "noImplicitAny": false, "sourceMap": true, - "outDir": "built" + "outDir": "built", + "typeRoots": [ + "node_modules/@types" + ], }, "include": ["."], "exclude": ["./node_modules", "coverage"] diff --git a/services/work-failer/tsconfig.base.json b/services/work-failer/tsconfig.base.json index 6724fa829..4fd6aca36 100644 --- a/services/work-failer/tsconfig.base.json +++ b/services/work-failer/tsconfig.base.json @@ -12,6 +12,9 @@ "noImplicitAny": false, "sourceMap": true, "outDir": "built", + "typeRoots": [ + "node_modules/@types" + ], }, "include": [ "./app", diff --git a/services/work-reaper/tsconfig.base.json b/services/work-reaper/tsconfig.base.json index 26cc487b6..6f8a87103 100644 --- a/services/work-reaper/tsconfig.base.json +++ b/services/work-reaper/tsconfig.base.json @@ -13,6 +13,9 @@ "noImplicitAny": false, "sourceMap": true, "outDir": "built", + "typeRoots": [ + "node_modules/@types" + ], }, "include": [ "./app", diff --git a/services/work-scheduler/tsconfig.json b/services/work-scheduler/tsconfig.json index 9e893df92..92fd19558 100644 --- a/services/work-scheduler/tsconfig.json +++ b/services/work-scheduler/tsconfig.json @@ -11,7 +11,10 @@ "noImplicitAny": false, "resolveJsonModule": true, "sourceMap": true, - "outDir": "built" + "outDir": "built", + "typeRoots": [ + "node_modules/@types" + ] }, "include": ["./app", "./test"] } diff --git a/services/work-updater/tsconfig.json b/services/work-updater/tsconfig.json index b9f22471e..1022d778c 100644 --- a/services/work-updater/tsconfig.json +++ b/services/work-updater/tsconfig.json @@ -10,7 +10,10 @@ "allowJs": true, "noImplicitAny": false, "sourceMap": true, - "outDir": "built" + "outDir": "built", + "typeRoots": [ + "node_modules/@types" + ], }, "include": ["./app", "./test"] } diff --git a/tsconfig.base.json b/tsconfig.base.json index 64555fe13..20e72594c 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -10,6 +10,9 @@ "allowJs": true, "noImplicitAny": false, "sourceMap": true, - "outDir": "built" + "outDir": "built", + "typeRoots": [ + "node_modules/@types" + ] } } \ No newline at end of file From 8551b21100bf7ff42ee5c7f3c67b1c0ea1a1a025 Mon Sep 17 00:00:00 2001 From: James Norton Date: Fri, 24 Jan 2025 14:44:30 -0500 Subject: [PATCH 5/9] HARMONY-1996: Add work-item status 'warning' and substatus work items and level/category to job_errors to support warnings as well as errors --- db/db.sql | 18 +++++++++- ...ng_status_and_sub_status_to_work_items.js} | 9 ++++- ...44_add_level_and_category_to_job_errors.js | 34 +++++++++++++++++++ .../work-item-updates.ts | 10 +++--- .../workflow-orchestration.ts | 2 ++ .../harmony/app/models/work-item-interface.ts | 12 +++++-- .../harmony/app/models/work-item-update.ts | 5 ++- services/harmony/app/models/work-item.ts | 17 +++++++--- .../harmony/test/work-items/work-backends.ts | 12 ++++--- 9 files changed, 100 insertions(+), 19 deletions(-) rename db/migrations/{20250122190038_add_empty_result_status_to_work_items.js => 20250122190038_add_warning_status_and_sub_status_to_work_items.js} (68%) create mode 100644 db/migrations/20250124155544_add_level_and_category_to_job_errors.js diff --git a/db/db.sql b/db/db.sql index ea66870e0..1c19e70b5 100644 --- a/db/db.sql +++ b/db/db.sql @@ -41,6 +41,18 @@ CREATE TABLE `job_errors` ( `jobID` char(36) not null, `url` varchar(4096) not null, `message` varchar(4096) not null, + `level` varchar(255) check ( + `level` in ( + 'error', + 'warning' + ) + ) not null default 'error', + `category` varchar(255) check ( + `category` in ( + 'generic', + 'no-data' + ) + ) not null default 'generic', `createdAt` datetime not null, `updatedAt` datetime not null, FOREIGN KEY(jobID) REFERENCES jobs(jobID) @@ -80,7 +92,8 @@ CREATE TABLE `work_items` ( `workflowStepIndex` integer not null, `scrollID` varchar(4096), `serviceID` varchar(255) not null, - `status` text check (`status` in ('ready', 'queued', 'running', 'successful', 'failed', 'canceled', 'no-data')) not null, + `status` varchar(255) check (`status` in ('ready', 'queued', 'running', 'successful', 'failed', 'canceled', 'warning')) not null, + `subStatus` varchar(255) check (`subStatus` in ('no-data')), `stacCatalogLocation` varchar(255), `totalItemsSize` double precision not null default 0, `outputItemSizesJson` text, @@ -183,9 +196,12 @@ CREATE INDEX jobs_username_idx ON jobs(jobID, username); CREATE INDEX job_links_jobID_idx ON job_links(jobID); CREATE INDEX job_links_jobID_id_idx ON job_links(jobID, id); CREATE INDEX job_errors_jobID_idx ON job_errors(jobID); +CREATE INDEX job_errors_level_idx ON job_errors(level); +CREATE INDEX job_errors_category_idx ON job_errors(category); CREATE INDEX work_items_jobID_idx ON work_items(jobID); CREATE INDEX work_items_serviceID_idx ON work_items(serviceID); CREATE INDEX work_items_status_idx ON work_items(status); +CREATE INDEX work_items_subStatus_idx ON work_items(subStatus); CREATE INDEX workflow_steps_jobID_idx ON workflow_steps(jobID); CREATE INDEX workflow_steps_jobID_StepIndex_idx ON workflow_steps(jobID, stepIndex); CREATE INDEX workflow_steps_serviceID_idx ON workflow_steps(serviceID); diff --git a/db/migrations/20250122190038_add_empty_result_status_to_work_items.js b/db/migrations/20250122190038_add_warning_status_and_sub_status_to_work_items.js similarity index 68% rename from db/migrations/20250122190038_add_empty_result_status_to_work_items.js rename to db/migrations/20250122190038_add_warning_status_and_sub_status_to_work_items.js index 5ceb92bff..fe96b7da3 100644 --- a/db/migrations/20250122190038_add_empty_result_status_to_work_items.js +++ b/db/migrations/20250122190038_add_warning_status_and_sub_status_to_work_items.js @@ -7,7 +7,12 @@ exports.up = function (knex, Promise) { ALTER TABLE "work_items" DROP CONSTRAINT "work_items_status_check", ADD CONSTRAINT "work_items_status_check" - CHECK (status IN ('ready', 'queued', 'running', 'successful', 'failed', 'canceled', 'no-data')) + CHECK (status IN ('ready', 'queued', 'running', 'successful', 'failed', 'canceled', 'warning')), + ADD COLUMN "sub_status" VARCHAR(255), + ADD CONSTRAINT "work_items_sub_status_check" + CHECK (sub_status IN (null, 'no-data')); + + CREATE INDEX work_items_sub_status ON work_items (sub_status) `); }; @@ -18,6 +23,8 @@ exports.up = function (knex, Promise) { exports.down = function (knex) { return knex.schema.raw(` ALTER TABLE "work_items" + DROP CONSTRAINT "work_items_sub_status_check", + DROP COLUMN "sub_status"), DROP CONSTRAINT "work_items_status_check", ADD CONSTRAINT "work_items_status_check" CHECK (status IN ('ready', 'queued', 'running', 'successful', 'failed', 'canceled')) diff --git a/db/migrations/20250124155544_add_level_and_category_to_job_errors.js b/db/migrations/20250124155544_add_level_and_category_to_job_errors.js new file mode 100644 index 000000000..7ffcbc981 --- /dev/null +++ b/db/migrations/20250124155544_add_level_and_category_to_job_errors.js @@ -0,0 +1,34 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function (knex, Promise) { + return knex.schema.raw(` + ALTER TABLE "job_errors" + ADD COLUMN "level" VARCHAR(255) DEFAULT 'error' NOT NULL, + ADD CONSTRAINT "job_errors_level_check" + CHECK (level IN ('error', 'warning')), + ADD COLUMN "category" VARCHAR(255) DEFAULT 'generic' NOT NULL, + ADD CONSTRAINT "job_errors_category_check" + CHECK (category IN ('generic', 'no-data')); + + CREATE INDEX job_errors_level ON job_errors (level); + CREATE INDEX job_errors_category ON job_errors (category) + `); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function (knex) { + return knex.schema.raw(` + DROP INDEX job_errors_category; + DROP_INDEX job_errors_level; + ALTER TABLE "job_errors" + DROP CONSTRAINT "job_errors_category_check", + DROP COLUMN "category", + DROP CONSTRAINT "job_errors_level_check", + DROP COLUMN "level" + `); +}; \ No newline at end of file diff --git a/services/harmony/app/backends/workflow-orchestration/work-item-updates.ts b/services/harmony/app/backends/workflow-orchestration/work-item-updates.ts index ac2e79cab..423fbf455 100644 --- a/services/harmony/app/backends/workflow-orchestration/work-item-updates.ts +++ b/services/harmony/app/backends/workflow-orchestration/work-item-updates.ts @@ -553,7 +553,7 @@ export async function preprocessWorkItem( let catalogItems; try { // TODO fix this in HARMONY-1995 - if ([WorkItemStatus.SUCCESSFUL, WorkItemStatus.NO_DATA].includes(status) && !nextWorkflowStep) { + if ([WorkItemStatus.SUCCESSFUL, WorkItemStatus.WARNING].includes(status) && !nextWorkflowStep) { // if we are CREATING STAC CATALOGS for the last step in the chain we should read the catalog items // since they are needed for generating the output links we will save catalogItems = await readCatalogsItems(results); @@ -609,13 +609,13 @@ export async function processWorkItem( ): Promise { const { jobID } = job; const { status, errorMessage, catalogItems, outputItemSizes } = preprocessResult; - const { workItemID, hits, results, scrollID } = update; + const { workItemID, hits, results, scrollID, subStatus } = update; const startTime = new Date().getTime(); let durationMs; let jobSaveStartTime; let didCreateWorkItem = false; - if (status === WorkItemStatus.SUCCESSFUL) { - logger.info(`Updating work item ${workItemID} to ${status}`); + if (status === WorkItemStatus.SUCCESSFUL || status === WorkItemStatus.WARNING) { + logger.info(`Updating work item ${workItemID} to ${status} | ${subStatus}`); } try { @@ -701,6 +701,7 @@ export async function processWorkItem( tx, workItemID, status, + subStatus, duration, totalItemsSize, outputItemSizes); @@ -712,6 +713,7 @@ export async function processWorkItem( logger.info(`Updated work item. Duration (ms) was: ${duration}`); workItem.status = status; + workItem.subStatus = subStatus; let allWorkItemsForStepComplete = false; diff --git a/services/harmony/app/backends/workflow-orchestration/workflow-orchestration.ts b/services/harmony/app/backends/workflow-orchestration/workflow-orchestration.ts index fdc522dd3..a8e50161f 100644 --- a/services/harmony/app/backends/workflow-orchestration/workflow-orchestration.ts +++ b/services/harmony/app/backends/workflow-orchestration/workflow-orchestration.ts @@ -96,6 +96,7 @@ export async function updateWorkItem(req: HarmonyRequest, res: Response): Promis const { id } = req.params; const { status, + subStatus, hits, results, scrollID, @@ -116,6 +117,7 @@ export async function updateWorkItem(req: HarmonyRequest, res: Response): Promis const update = { workItemID: parseInt(id), status, + subStatus, hits, results, scrollID, diff --git a/services/harmony/app/models/work-item-interface.ts b/services/harmony/app/models/work-item-interface.ts index 04bac370d..1a6303f86 100644 --- a/services/harmony/app/models/work-item-interface.ts +++ b/services/harmony/app/models/work-item-interface.ts @@ -9,14 +9,19 @@ export enum WorkItemStatus { SUCCESSFUL = 'successful', FAILED = 'failed', CANCELED = 'canceled', - NO_DATA = 'no-data', + WARNING = 'warning', +}; + +// additional information about the status - currently only relevant for WARNING status +export enum WorkItemSubStatus { + NO_DATA = 'no-data', // the service responded with no data } export const COMPLETED_WORK_ITEM_STATUSES = [ WorkItemStatus.SUCCESSFUL, WorkItemStatus.FAILED, WorkItemStatus.CANCELED, - WorkItemStatus.NO_DATA, + WorkItemStatus.WARNING, ]; export interface WorkItemRecord { @@ -38,6 +43,9 @@ export interface WorkItemRecord { // The status of the operation - see WorkItemStatus status?: WorkItemStatus; + // The sub-status of the operation - see WorkItemSubStatus + subStatus?: WorkItemSubStatus; + // error message if status === FAILED errorMessage?: string; diff --git a/services/harmony/app/models/work-item-update.ts b/services/harmony/app/models/work-item-update.ts index 7d8f1fd5a..a3e256736 100644 --- a/services/harmony/app/models/work-item-update.ts +++ b/services/harmony/app/models/work-item-update.ts @@ -1,5 +1,5 @@ import _ from 'lodash'; -import { WorkItemStatus } from './work-item-interface'; +import { WorkItemStatus, WorkItemSubStatus } from './work-item-interface'; /** * @@ -14,6 +14,9 @@ export default interface WorkItemUpdate { // The status of the operation - see WorkItemStatus status?: WorkItemStatus; + // The sub-status of the operation - see WorkItemSubStatus + subStatus?: WorkItemSubStatus; + // The ID of the scroll session (only used for the query cmr service) scrollID?: string; diff --git a/services/harmony/app/models/work-item.ts b/services/harmony/app/models/work-item.ts index 0f483366d..ea25b95f3 100644 --- a/services/harmony/app/models/work-item.ts +++ b/services/harmony/app/models/work-item.ts @@ -9,7 +9,7 @@ import env from '../util/env'; import { Job, JobStatus } from './job'; import Record from './record'; import WorkflowStep from './workflow-steps'; -import { WorkItemRecord, WorkItemStatus, getStacLocation, WorkItemQuery } from './work-item-interface'; +import { WorkItemRecord, WorkItemStatus, getStacLocation, WorkItemQuery, WorkItemSubStatus } from './work-item-interface'; import { eventEmitter } from '../events'; import { getWorkSchedulerQueue } from '../../app/util/queue/queue-factory'; @@ -51,6 +51,9 @@ export default class WorkItem extends Record implements WorkItemRecord { // The status of the operation - see WorkItemStatus status?: WorkItemStatus; + // The sub-status of the operation - see WorkItemSubStatus + subStatus?: WorkItemSubStatus; + // error message if status === FAILED errorMessage?: string; @@ -297,6 +300,7 @@ export async function getWorkItemStatus( * @param tx - the transaction to use for querying * @param id - the id of the WorkItem * @param status - the status to set for the WorkItem + * @param subStatus - the sub-status to set for the WorkItem * @param duration - how long the work item took to process * @param totalItemsSize - the combined sizes of all the input granules for this work item * @param outputItemSizes - the separate size of each granule in the output for this work item @@ -305,6 +309,7 @@ export async function updateWorkItemStatus( tx: Transaction, id: number, status: WorkItemStatus, + subStatus: WorkItemSubStatus, duration: number, totalItemsSize: number, outputItemSizes: number[], @@ -313,11 +318,11 @@ export async function updateWorkItemStatus( const outputItemSizesJson = JSON.stringify(outputItemSizes); try { await tx(WorkItem.table) - .update({ status, duration, totalItemsSize, outputItemSizesJson: outputItemSizesJson, updatedAt: new Date() }) + .update({ status, subStatus, duration, totalItemsSize, outputItemSizesJson: outputItemSizesJson, updatedAt: new Date() }) .where({ id }); - logger.debug(`Status for work item ${id} set to ${status}`); + logger.debug(`Status for work item ${id} set to ${status} | ${subStatus}`); } catch (e) { - logger.error(`Failed to update work item ${id} status to ${status}`); + logger.error(`Failed to update work item ${id} status to ${status} | ${subStatus}`); logger.error(e); throw e; } @@ -328,14 +333,16 @@ export async function updateWorkItemStatus( * @param tx - the transaction to use for querying * @param ids - the ids of the WorkItems * @param status - the status to set for the WorkItems + * @param subStatus - the sub-status to set for the WorkItems */ export async function updateWorkItemStatuses( tx: Transaction, ids: number[], status: WorkItemStatus, + subStatus?: WorkItemSubStatus, ): Promise { const now = new Date(); - let update = { status, updatedAt: now }; + let update = { status, subStatus, updatedAt: now }; // if we are setting the status to running, also set the startedAt time if (status === WorkItemStatus.RUNNING) { update = { ...update, ...{ startedAt: now } }; diff --git a/services/harmony/test/work-items/work-backends.ts b/services/harmony/test/work-items/work-backends.ts index cea836111..beb0e08ca 100644 --- a/services/harmony/test/work-items/work-backends.ts +++ b/services/harmony/test/work-items/work-backends.ts @@ -1,4 +1,4 @@ -import { WorkItemStatus, getStacLocation, WorkItemRecord } from './../../app/models/work-item-interface'; +import { WorkItemStatus, getStacLocation, WorkItemRecord, WorkItemSubStatus } from './../../app/models/work-item-interface'; import { Job, JobRecord, JobStatus, terminalStates } from './../../app/models/job'; import { describe, it } from 'mocha'; import * as sinon from 'sinon'; @@ -490,7 +490,8 @@ describe('Work Backends', function () { const noDataWorkItemRecord = { ...workItemRecord, ...{ - status: WorkItemStatus.NO_DATA, + status: WorkItemStatus.WARNING, + subStatus: WorkItemSubStatus.NO_DATA, results: [getStacLocation({ id: workItemRecord.id, jobID: workItemRecord.jobID }, 'catalog.json')], outputItemSizes: [], duration: 0, @@ -501,9 +502,10 @@ describe('Work Backends', function () { }); hookWorkItemUpdate((r) => r.send(noDataWorkItemRecord)); - it('sets the work item status to no-data', async function () { + it('sets the work item status to warning with no-data', async function () { const updatedWorkItem = await getWorkItemById(db, this.workItem.id); - expect(updatedWorkItem.status).to.equal(WorkItemStatus.NO_DATA); + expect(updatedWorkItem.status).to.equal(WorkItemStatus.WARNING); + expect(updatedWorkItem.subStatus).to.equal(WorkItemSubStatus.NO_DATA); }); describe('and the worker computed duration is less than the harmony computed duration', async function () { @@ -602,7 +604,7 @@ describe('Work Backends', function () { } // tests to make sure work-items cannot be updated once they are in a terminal state - for (const terminalState of [WorkItemStatus.CANCELED, WorkItemStatus.FAILED, WorkItemStatus.SUCCESSFUL, WorkItemStatus.NO_DATA]) { + for (const terminalState of [WorkItemStatus.CANCELED, WorkItemStatus.FAILED, WorkItemStatus.SUCCESSFUL, WorkItemStatus.WARNING]) { describe(`When the work-item is already in state "${terminalState}"`, async function () { const newWorkItemRecord = { ...workItemRecord, ...{ status: terminalState }, From 57ad87c2dc058dc5d09c8c596e0225f270949a84 Mon Sep 17 00:00:00 2001 From: James Norton Date: Fri, 24 Jan 2025 14:56:19 -0500 Subject: [PATCH 6/9] HARMONY-1996: Remove unnecessary semicolon --- services/harmony/app/models/work-item-interface.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/harmony/app/models/work-item-interface.ts b/services/harmony/app/models/work-item-interface.ts index 1a6303f86..f91e28089 100644 --- a/services/harmony/app/models/work-item-interface.ts +++ b/services/harmony/app/models/work-item-interface.ts @@ -10,7 +10,7 @@ export enum WorkItemStatus { FAILED = 'failed', CANCELED = 'canceled', WARNING = 'warning', -}; +} // additional information about the status - currently only relevant for WARNING status export enum WorkItemSubStatus { From 48b513b9cf09474e3db9dd3fec83367187d25659 Mon Sep 17 00:00:00 2001 From: James Norton Date: Fri, 24 Jan 2025 16:47:09 -0500 Subject: [PATCH 7/9] HARMONY-1996: Remove unneeded dB constraint and fix casing on model sub_status field to be consistent --- db/db.sql | 11 +++-------- ...544_add_level_and_category_to_job_errors.js | 4 +--- .../work-item-updates.ts | 8 ++++---- .../workflow-orchestration.ts | 4 ++-- .../harmony/app/models/work-item-interface.ts | 2 +- .../harmony/app/models/work-item-update.ts | 2 +- services/harmony/app/models/work-item.ts | 18 +++++++++--------- .../harmony/test/work-items/work-backends.ts | 4 ++-- 8 files changed, 23 insertions(+), 30 deletions(-) diff --git a/db/db.sql b/db/db.sql index 1c19e70b5..1f07d22ef 100644 --- a/db/db.sql +++ b/db/db.sql @@ -47,12 +47,7 @@ CREATE TABLE `job_errors` ( 'warning' ) ) not null default 'error', - `category` varchar(255) check ( - `category` in ( - 'generic', - 'no-data' - ) - ) not null default 'generic', + `category` varchar(255) not null default 'generic', `createdAt` datetime not null, `updatedAt` datetime not null, FOREIGN KEY(jobID) REFERENCES jobs(jobID) @@ -93,7 +88,7 @@ CREATE TABLE `work_items` ( `scrollID` varchar(4096), `serviceID` varchar(255) not null, `status` varchar(255) check (`status` in ('ready', 'queued', 'running', 'successful', 'failed', 'canceled', 'warning')) not null, - `subStatus` varchar(255) check (`subStatus` in ('no-data')), + `sub_status` varchar(255) check (`sub_status` in ('no-data')), `stacCatalogLocation` varchar(255), `totalItemsSize` double precision not null default 0, `outputItemSizesJson` text, @@ -201,7 +196,7 @@ CREATE INDEX job_errors_category_idx ON job_errors(category); CREATE INDEX work_items_jobID_idx ON work_items(jobID); CREATE INDEX work_items_serviceID_idx ON work_items(serviceID); CREATE INDEX work_items_status_idx ON work_items(status); -CREATE INDEX work_items_subStatus_idx ON work_items(subStatus); +CREATE INDEX work_items_sub_status_idx ON work_items(sub_status); CREATE INDEX workflow_steps_jobID_idx ON workflow_steps(jobID); CREATE INDEX workflow_steps_jobID_StepIndex_idx ON workflow_steps(jobID, stepIndex); CREATE INDEX workflow_steps_serviceID_idx ON workflow_steps(serviceID); diff --git a/db/migrations/20250124155544_add_level_and_category_to_job_errors.js b/db/migrations/20250124155544_add_level_and_category_to_job_errors.js index 7ffcbc981..df0b7e376 100644 --- a/db/migrations/20250124155544_add_level_and_category_to_job_errors.js +++ b/db/migrations/20250124155544_add_level_and_category_to_job_errors.js @@ -8,9 +8,7 @@ exports.up = function (knex, Promise) { ADD COLUMN "level" VARCHAR(255) DEFAULT 'error' NOT NULL, ADD CONSTRAINT "job_errors_level_check" CHECK (level IN ('error', 'warning')), - ADD COLUMN "category" VARCHAR(255) DEFAULT 'generic' NOT NULL, - ADD CONSTRAINT "job_errors_category_check" - CHECK (category IN ('generic', 'no-data')); + ADD COLUMN "category" VARCHAR(255) DEFAULT 'generic' NOT NULL; CREATE INDEX job_errors_level ON job_errors (level); CREATE INDEX job_errors_category ON job_errors (category) diff --git a/services/harmony/app/backends/workflow-orchestration/work-item-updates.ts b/services/harmony/app/backends/workflow-orchestration/work-item-updates.ts index 423fbf455..67e4d2771 100644 --- a/services/harmony/app/backends/workflow-orchestration/work-item-updates.ts +++ b/services/harmony/app/backends/workflow-orchestration/work-item-updates.ts @@ -609,13 +609,13 @@ export async function processWorkItem( ): Promise { const { jobID } = job; const { status, errorMessage, catalogItems, outputItemSizes } = preprocessResult; - const { workItemID, hits, results, scrollID, subStatus } = update; + const { workItemID, hits, results, scrollID, sub_status } = update; const startTime = new Date().getTime(); let durationMs; let jobSaveStartTime; let didCreateWorkItem = false; if (status === WorkItemStatus.SUCCESSFUL || status === WorkItemStatus.WARNING) { - logger.info(`Updating work item ${workItemID} to ${status} | ${subStatus}`); + logger.info(`Updating work item ${workItemID} to ${status} | ${sub_status}`); } try { @@ -701,7 +701,7 @@ export async function processWorkItem( tx, workItemID, status, - subStatus, + sub_status, duration, totalItemsSize, outputItemSizes); @@ -713,7 +713,7 @@ export async function processWorkItem( logger.info(`Updated work item. Duration (ms) was: ${duration}`); workItem.status = status; - workItem.subStatus = subStatus; + workItem.sub_status = sub_status; let allWorkItemsForStepComplete = false; diff --git a/services/harmony/app/backends/workflow-orchestration/workflow-orchestration.ts b/services/harmony/app/backends/workflow-orchestration/workflow-orchestration.ts index a8e50161f..2c4e4e6c8 100644 --- a/services/harmony/app/backends/workflow-orchestration/workflow-orchestration.ts +++ b/services/harmony/app/backends/workflow-orchestration/workflow-orchestration.ts @@ -96,7 +96,7 @@ export async function updateWorkItem(req: HarmonyRequest, res: Response): Promis const { id } = req.params; const { status, - subStatus, + sub_status, hits, results, scrollID, @@ -117,7 +117,7 @@ export async function updateWorkItem(req: HarmonyRequest, res: Response): Promis const update = { workItemID: parseInt(id), status, - subStatus, + sub_status, hits, results, scrollID, diff --git a/services/harmony/app/models/work-item-interface.ts b/services/harmony/app/models/work-item-interface.ts index f91e28089..e14023bd5 100644 --- a/services/harmony/app/models/work-item-interface.ts +++ b/services/harmony/app/models/work-item-interface.ts @@ -44,7 +44,7 @@ export interface WorkItemRecord { status?: WorkItemStatus; // The sub-status of the operation - see WorkItemSubStatus - subStatus?: WorkItemSubStatus; + sub_status?: WorkItemSubStatus; // error message if status === FAILED errorMessage?: string; diff --git a/services/harmony/app/models/work-item-update.ts b/services/harmony/app/models/work-item-update.ts index a3e256736..938589535 100644 --- a/services/harmony/app/models/work-item-update.ts +++ b/services/harmony/app/models/work-item-update.ts @@ -15,7 +15,7 @@ export default interface WorkItemUpdate { status?: WorkItemStatus; // The sub-status of the operation - see WorkItemSubStatus - subStatus?: WorkItemSubStatus; + sub_status?: WorkItemSubStatus; // The ID of the scroll session (only used for the query cmr service) scrollID?: string; diff --git a/services/harmony/app/models/work-item.ts b/services/harmony/app/models/work-item.ts index ea25b95f3..d1bf10882 100644 --- a/services/harmony/app/models/work-item.ts +++ b/services/harmony/app/models/work-item.ts @@ -52,7 +52,7 @@ export default class WorkItem extends Record implements WorkItemRecord { status?: WorkItemStatus; // The sub-status of the operation - see WorkItemSubStatus - subStatus?: WorkItemSubStatus; + sub_status?: WorkItemSubStatus; // error message if status === FAILED errorMessage?: string; @@ -300,7 +300,7 @@ export async function getWorkItemStatus( * @param tx - the transaction to use for querying * @param id - the id of the WorkItem * @param status - the status to set for the WorkItem - * @param subStatus - the sub-status to set for the WorkItem + * @param sub_status - the sub-status to set for the WorkItem * @param duration - how long the work item took to process * @param totalItemsSize - the combined sizes of all the input granules for this work item * @param outputItemSizes - the separate size of each granule in the output for this work item @@ -309,7 +309,7 @@ export async function updateWorkItemStatus( tx: Transaction, id: number, status: WorkItemStatus, - subStatus: WorkItemSubStatus, + sub_status: WorkItemSubStatus, duration: number, totalItemsSize: number, outputItemSizes: number[], @@ -318,11 +318,11 @@ export async function updateWorkItemStatus( const outputItemSizesJson = JSON.stringify(outputItemSizes); try { await tx(WorkItem.table) - .update({ status, subStatus, duration, totalItemsSize, outputItemSizesJson: outputItemSizesJson, updatedAt: new Date() }) + .update({ status, sub_status, duration, totalItemsSize, outputItemSizesJson: outputItemSizesJson, updatedAt: new Date() }) .where({ id }); - logger.debug(`Status for work item ${id} set to ${status} | ${subStatus}`); + logger.debug(`Status for work item ${id} set to ${status} | ${sub_status}`); } catch (e) { - logger.error(`Failed to update work item ${id} status to ${status} | ${subStatus}`); + logger.error(`Failed to update work item ${id} status to ${status} | ${sub_status}`); logger.error(e); throw e; } @@ -333,16 +333,16 @@ export async function updateWorkItemStatus( * @param tx - the transaction to use for querying * @param ids - the ids of the WorkItems * @param status - the status to set for the WorkItems - * @param subStatus - the sub-status to set for the WorkItems + * @param sub_status - the sub-status to set for the WorkItems */ export async function updateWorkItemStatuses( tx: Transaction, ids: number[], status: WorkItemStatus, - subStatus?: WorkItemSubStatus, + sub_status?: WorkItemSubStatus, ): Promise { const now = new Date(); - let update = { status, subStatus, updatedAt: now }; + let update = { status, sub_status, updatedAt: now }; // if we are setting the status to running, also set the startedAt time if (status === WorkItemStatus.RUNNING) { update = { ...update, ...{ startedAt: now } }; diff --git a/services/harmony/test/work-items/work-backends.ts b/services/harmony/test/work-items/work-backends.ts index beb0e08ca..0f5d770f0 100644 --- a/services/harmony/test/work-items/work-backends.ts +++ b/services/harmony/test/work-items/work-backends.ts @@ -491,7 +491,7 @@ describe('Work Backends', function () { ...workItemRecord, ...{ status: WorkItemStatus.WARNING, - subStatus: WorkItemSubStatus.NO_DATA, + sub_status: WorkItemSubStatus.NO_DATA, results: [getStacLocation({ id: workItemRecord.id, jobID: workItemRecord.jobID }, 'catalog.json')], outputItemSizes: [], duration: 0, @@ -505,7 +505,7 @@ describe('Work Backends', function () { it('sets the work item status to warning with no-data', async function () { const updatedWorkItem = await getWorkItemById(db, this.workItem.id); expect(updatedWorkItem.status).to.equal(WorkItemStatus.WARNING); - expect(updatedWorkItem.subStatus).to.equal(WorkItemSubStatus.NO_DATA); + expect(updatedWorkItem.sub_status).to.equal(WorkItemSubStatus.NO_DATA); }); describe('and the worker computed duration is less than the harmony computed duration', async function () { From a67ca1bafdbcb2e71a571338d69776fa52933d4a Mon Sep 17 00:00:00 2001 From: James Norton Date: Mon, 27 Jan 2025 10:15:49 -0500 Subject: [PATCH 8/9] HARMONY-1996: Remove unneeded constrains on dB tables --- db/db.sql | 4 ++-- ...038_add_warning_status_and_sub_status_to_work_items.js | 8 +++----- ...20250124155544_add_level_and_category_to_job_errors.js | 7 +++---- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/db/db.sql b/db/db.sql index 1f07d22ef..08064b8da 100644 --- a/db/db.sql +++ b/db/db.sql @@ -47,7 +47,7 @@ CREATE TABLE `job_errors` ( 'warning' ) ) not null default 'error', - `category` varchar(255) not null default 'generic', + `category` varchar(255), `createdAt` datetime not null, `updatedAt` datetime not null, FOREIGN KEY(jobID) REFERENCES jobs(jobID) @@ -88,7 +88,7 @@ CREATE TABLE `work_items` ( `scrollID` varchar(4096), `serviceID` varchar(255) not null, `status` varchar(255) check (`status` in ('ready', 'queued', 'running', 'successful', 'failed', 'canceled', 'warning')) not null, - `sub_status` varchar(255) check (`sub_status` in ('no-data')), + `sub_status` varchar(255), `stacCatalogLocation` varchar(255), `totalItemsSize` double precision not null default 0, `outputItemSizesJson` text, diff --git a/db/migrations/20250122190038_add_warning_status_and_sub_status_to_work_items.js b/db/migrations/20250122190038_add_warning_status_and_sub_status_to_work_items.js index fe96b7da3..d62959e09 100644 --- a/db/migrations/20250122190038_add_warning_status_and_sub_status_to_work_items.js +++ b/db/migrations/20250122190038_add_warning_status_and_sub_status_to_work_items.js @@ -8,11 +8,9 @@ exports.up = function (knex, Promise) { DROP CONSTRAINT "work_items_status_check", ADD CONSTRAINT "work_items_status_check" CHECK (status IN ('ready', 'queued', 'running', 'successful', 'failed', 'canceled', 'warning')), - ADD COLUMN "sub_status" VARCHAR(255), - ADD CONSTRAINT "work_items_sub_status_check" - CHECK (sub_status IN (null, 'no-data')); + ADD COLUMN "sub_status" VARCHAR(255); - CREATE INDEX work_items_sub_status ON work_items (sub_status) + CREATE INDEX work_items_sub_status ON work_items (sub_status); `); }; @@ -22,8 +20,8 @@ exports.up = function (knex, Promise) { */ exports.down = function (knex) { return knex.schema.raw(` + DROP_INDEX work_items_sub_status; ALTER TABLE "work_items" - DROP CONSTRAINT "work_items_sub_status_check", DROP COLUMN "sub_status"), DROP CONSTRAINT "work_items_status_check", ADD CONSTRAINT "work_items_status_check" diff --git a/db/migrations/20250124155544_add_level_and_category_to_job_errors.js b/db/migrations/20250124155544_add_level_and_category_to_job_errors.js index df0b7e376..fdfe3879c 100644 --- a/db/migrations/20250124155544_add_level_and_category_to_job_errors.js +++ b/db/migrations/20250124155544_add_level_and_category_to_job_errors.js @@ -8,10 +8,10 @@ exports.up = function (knex, Promise) { ADD COLUMN "level" VARCHAR(255) DEFAULT 'error' NOT NULL, ADD CONSTRAINT "job_errors_level_check" CHECK (level IN ('error', 'warning')), - ADD COLUMN "category" VARCHAR(255) DEFAULT 'generic' NOT NULL; + ADD COLUMN "category" VARCHAR(255); CREATE INDEX job_errors_level ON job_errors (level); - CREATE INDEX job_errors_category ON job_errors (category) + CREATE INDEX job_errors_category ON job_errors (category); `); }; @@ -24,9 +24,8 @@ exports.down = function (knex) { DROP INDEX job_errors_category; DROP_INDEX job_errors_level; ALTER TABLE "job_errors" - DROP CONSTRAINT "job_errors_category_check", DROP COLUMN "category", DROP CONSTRAINT "job_errors_level_check", - DROP COLUMN "level" + DROP COLUMN "level;" `); }; \ No newline at end of file From d105361b8d2b7e48bbf1936c3dc15d0b7ac4b6c8 Mon Sep 17 00:00:00 2001 From: James Norton Date: Mon, 27 Jan 2025 12:00:56 -0500 Subject: [PATCH 9/9] HARMONY-1996: Fix down migrations --- ..._add_warning_status_and_sub_status_to_work_items.js | 8 ++++---- ...50124155544_add_level_and_category_to_job_errors.js | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/db/migrations/20250122190038_add_warning_status_and_sub_status_to_work_items.js b/db/migrations/20250122190038_add_warning_status_and_sub_status_to_work_items.js index d62959e09..c095f597e 100644 --- a/db/migrations/20250122190038_add_warning_status_and_sub_status_to_work_items.js +++ b/db/migrations/20250122190038_add_warning_status_and_sub_status_to_work_items.js @@ -10,7 +10,7 @@ exports.up = function (knex, Promise) { CHECK (status IN ('ready', 'queued', 'running', 'successful', 'failed', 'canceled', 'warning')), ADD COLUMN "sub_status" VARCHAR(255); - CREATE INDEX work_items_sub_status ON work_items (sub_status); + CREATE INDEX work_items_sub_status_index ON work_items (sub_status); `); }; @@ -20,11 +20,11 @@ exports.up = function (knex, Promise) { */ exports.down = function (knex) { return knex.schema.raw(` - DROP_INDEX work_items_sub_status; + DROP INDEX work_items_sub_status_index; ALTER TABLE "work_items" - DROP COLUMN "sub_status"), + DROP COLUMN "sub_status", DROP CONSTRAINT "work_items_status_check", ADD CONSTRAINT "work_items_status_check" - CHECK (status IN ('ready', 'queued', 'running', 'successful', 'failed', 'canceled')) + CHECK (status IN ('ready', 'queued', 'running', 'successful', 'failed', 'canceled')); `); }; \ No newline at end of file diff --git a/db/migrations/20250124155544_add_level_and_category_to_job_errors.js b/db/migrations/20250124155544_add_level_and_category_to_job_errors.js index fdfe3879c..614b36f2c 100644 --- a/db/migrations/20250124155544_add_level_and_category_to_job_errors.js +++ b/db/migrations/20250124155544_add_level_and_category_to_job_errors.js @@ -10,8 +10,8 @@ exports.up = function (knex, Promise) { CHECK (level IN ('error', 'warning')), ADD COLUMN "category" VARCHAR(255); - CREATE INDEX job_errors_level ON job_errors (level); - CREATE INDEX job_errors_category ON job_errors (category); + CREATE INDEX job_errors_level_index ON job_errors (level); + CREATE INDEX job_errors_category_index ON job_errors (category); `); }; @@ -21,11 +21,11 @@ exports.up = function (knex, Promise) { */ exports.down = function (knex) { return knex.schema.raw(` - DROP INDEX job_errors_category; - DROP_INDEX job_errors_level; + DROP INDEX job_errors_category_index; + DROP INDEX job_errors_level_index; ALTER TABLE "job_errors" DROP COLUMN "category", DROP CONSTRAINT "job_errors_level_check", - DROP COLUMN "level;" + DROP COLUMN "level"; `); }; \ No newline at end of file