From 82a7ed710f0b4aad2d9871c5be9858927b649d58 Mon Sep 17 00:00:00 2001 From: Zhongxin Guo Date: Wed, 22 Oct 2025 04:46:50 -0700 Subject: [PATCH 1/9] update --- src/rest-server/src/config/v2/protocol.js | 5 +++++ src/rest-server/src/controllers/v2/job.js | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/rest-server/src/config/v2/protocol.js b/src/rest-server/src/config/v2/protocol.js index 163172f9..4f86c569 100644 --- a/src/rest-server/src/config/v2/protocol.js +++ b/src/rest-server/src/config/v2/protocol.js @@ -173,6 +173,11 @@ const protocolSchema = { }, minItems: 1, }, + jobType: { + type: 'string', + enum: ['inference', 'training', 'others'], + default: 'others', + }, parameters: { type: 'object', additionalProperties: true, diff --git a/src/rest-server/src/controllers/v2/job.js b/src/rest-server/src/controllers/v2/job.js index 6f59d3d3..07638ed7 100644 --- a/src/rest-server/src/controllers/v2/job.js +++ b/src/rest-server/src/controllers/v2/job.js @@ -200,6 +200,8 @@ const update = asyncHandler(async (req, res) => { const userName = req.user.username; const frameworkName = `${userName}~${jobName}`; + const jobType = res.locals.protocol.jobType || 'others'; + // check duplicate job try { const data = await job.get(frameworkName); @@ -216,6 +218,7 @@ const update = asyncHandler(async (req, res) => { } } await job.put(frameworkName, res.locals.protocol, req.body); + await job.addTag(frameworkName, jobType); res.status(status('Accepted')).json({ status: status('Accepted'), message: `Update job ${jobName} for user ${userName} successfully.`, From 4dbca923b64ef83aacbd8b6fc9db26eb7f0e9a43 Mon Sep 17 00:00:00 2001 From: Zhongxin Guo Date: Wed, 22 Oct 2025 04:56:02 -0700 Subject: [PATCH 2/9] check job protocol for inference job --- .../src/middlewares/v2/protocol.js | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/rest-server/src/middlewares/v2/protocol.js b/src/rest-server/src/middlewares/v2/protocol.js index 4e505be4..546a8ca0 100644 --- a/src/rest-server/src/middlewares/v2/protocol.js +++ b/src/rest-server/src/middlewares/v2/protocol.js @@ -120,6 +120,40 @@ const protocolValidate = (protocolYAML) => { } } } + + // check jobType + if ("jobType" in protocolObj) { + const validJobTypes = ['inference', 'training', 'others']; + if (!validJobTypes.includes(protocolObj.jobType)) { + throw createError( + 'Bad Request', + 'InvalidProtocolError', + `Job type ${protocolObj.jobType} is not valid.`, + ); + } + + if (protocolObj.jobType === 'inference') { + // check parameters for inference job + if (!('parameters' in protocolObj)) { + throw createError( + 'Bad Request', + 'InvalidProtocolError', + `Parameters (INTERNAL_SERVER_IP, INTERNAL_SERVER_PORT, API_KEY) must be specified for inference job.`, + ); + } + const requiredParams = ['INTERNAL_SERVER_IP', 'INTERNAL_SERVER_PORT', 'API_KEY']; + for (const param of requiredParams) { + if (!(param in protocolObj.parameters)) { + throw createError( + 'Bad Request', + 'InvalidProtocolError', + `Parameter ${param} must be specified for inference job.`, + ); + } + } + } + } + for (const taskRole of Object.keys(protocolObj.taskRoles)) { for (const field of prerequisiteFields) { if ( From 05488626c973f1caadce85a96368c21bcdfa8156 Mon Sep 17 00:00:00 2001 From: Zhongxin Guo Date: Thu, 30 Oct 2025 00:22:53 -0700 Subject: [PATCH 3/9] support jobType when query jobs --- src/rest-server/src/controllers/v2/job.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/rest-server/src/controllers/v2/job.js b/src/rest-server/src/controllers/v2/job.js index 07638ed7..ec670380 100644 --- a/src/rest-server/src/controllers/v2/job.js +++ b/src/rest-server/src/controllers/v2/job.js @@ -25,6 +25,7 @@ const logger = require('@pai/config/logger'); const { Op } = require('sequelize'); const { userProperty } = require('@pai/config/token'); const userController = require('@pai/controllers/v2/user'); +const { filter } = require('lodash'); const list = asyncHandler(async (req, res) => { // ?keyword=&username=,&vc=, @@ -86,6 +87,9 @@ const list = asyncHandler(async (req, res) => { if ('tagsNotContain' in req.query) { tagsNotContainFilter.name = req.query.tagsNotContain.split(','); } + if ('jobType' in req.query){ + tagsContainFilter.name += req.query.jobType.split(','); + } if ('keyword' in req.query) { // match text in username, jobname, or vc filters[Op.or] = [ From 3a4371ebff52bd7cde5c34bd7f1c25da6589c36c Mon Sep 17 00:00:00 2001 From: Zhongxin Guo Date: Fri, 31 Oct 2025 00:05:42 -0700 Subject: [PATCH 4/9] update --- src/rest-server/src/middlewares/v2/protocol.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/rest-server/src/middlewares/v2/protocol.js b/src/rest-server/src/middlewares/v2/protocol.js index 546a8ca0..c828461e 100644 --- a/src/rest-server/src/middlewares/v2/protocol.js +++ b/src/rest-server/src/middlewares/v2/protocol.js @@ -138,7 +138,10 @@ const protocolValidate = (protocolYAML) => { throw createError( 'Bad Request', 'InvalidProtocolError', - `Parameters (INTERNAL_SERVER_IP, INTERNAL_SERVER_PORT, API_KEY) must be specified for inference job.`, + `The following parameters must be specified for inference job: +INTERNAL_SERVER_IP=$PAI_HOST_IP_taskrole_0 +INTERNAL_SERVER_PORT=$PAI_PORT_LIST_taskrole_0_http +API_KEY=[any string]`, ); } const requiredParams = ['INTERNAL_SERVER_IP', 'INTERNAL_SERVER_PORT', 'API_KEY']; @@ -224,11 +227,11 @@ const protocolRender = (protocolObj) => { $parameters: protocolObj.parameters, $script: protocolObj.prerequisites.script[ - protocolObj.taskRoles[taskRole].script + protocolObj.taskRoles[taskRole].script ], $output: protocolObj.prerequisites.output[ - protocolObj.taskRoles[taskRole].output + protocolObj.taskRoles[taskRole].output ], $data: protocolObj.prerequisites.data[protocolObj.taskRoles[taskRole].data], From 666aee71f27acaca6668c4be332218b29bab8122 Mon Sep 17 00:00:00 2001 From: Zhongxin Guo Date: Fri, 31 Oct 2025 06:38:16 -0700 Subject: [PATCH 5/9] fix tag filter bug --- src/rest-server/src/controllers/v2/job.js | 6 ++- src/rest-server/src/models/v2/job/k8s.js | 56 +++++++++++++---------- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/rest-server/src/controllers/v2/job.js b/src/rest-server/src/controllers/v2/job.js index ec670380..1f33b752 100644 --- a/src/rest-server/src/controllers/v2/job.js +++ b/src/rest-server/src/controllers/v2/job.js @@ -88,7 +88,11 @@ const list = asyncHandler(async (req, res) => { tagsNotContainFilter.name = req.query.tagsNotContain.split(','); } if ('jobType' in req.query){ - tagsContainFilter.name += req.query.jobType.split(','); + if (Array.isArray(tagsContainFilter.name)) { + tagsContainFilter.name.push(...req.query.jobType.split(',')); + } else { + tagsContainFilter.name = req.query.jobType.split(','); + } } if ('keyword' in req.query) { // match text in username, jobname, or vc diff --git a/src/rest-server/src/models/v2/job/k8s.js b/src/rest-server/src/models/v2/job/k8s.js index 86ad75f8..5a8ba976 100644 --- a/src/rest-server/src/models/v2/job/k8s.js +++ b/src/rest-server/src/models/v2/job/k8s.js @@ -1000,33 +1000,43 @@ const list = async ( Object.keys(tagsContainFilter).length !== 0 || Object.keys(tagsNotContainFilter).length !== 0 ) { - filters.name = {}; - // tagsContain + // Build name filters by querying Tag table directly to avoid using QueryGenerator + const nameFilter = {}; + + // tagsContain -> include frameworks whose name appears in Tag rows matched by tagsContainFilter if (Object.keys(tagsContainFilter).length !== 0) { - const queryContainFrameworkName = databaseModel.sequelize.dialect.QueryGenerator.selectQuery( - 'tags', - { - attributes: ['frameworkName'], - where: tagsContainFilter, - }, - ); - filters.name[Sequelize.Op.in] = Sequelize.literal(` - (${queryContainFrameworkName.slice(0, -1)}) - `); + const containRows = await databaseModel.Tag.findAll({ + attributes: ['frameworkName'], + where: tagsContainFilter, + raw: true, + }); + const containNames = [...new Set(containRows.map((r) => r.frameworkName))]; + // if no tags match, result is empty + if (containNames.length === 0) { + if (withTotalCount) { + return { totalCount: 0, data: [] }; + } else { + return []; + } + } + nameFilter[Sequelize.Op.in] = containNames; } - // tagsNotContain + + // tagsNotContain -> exclude frameworks whose name appears in Tag rows matched by tagsNotContainFilter if (Object.keys(tagsNotContainFilter).length !== 0) { - const queryNotContainFrameworkName = databaseModel.sequelize.dialect.QueryGenerator.selectQuery( - 'tags', - { - attributes: ['frameworkName'], - where: tagsNotContainFilter, - }, - ); - filters.name[Sequelize.Op.notIn] = Sequelize.literal(` - (${queryNotContainFrameworkName.slice(0, -1)}) - `); + const notContainRows = await databaseModel.Tag.findAll({ + attributes: ['frameworkName'], + where: tagsNotContainFilter, + raw: true, + }); + const notContainNames = [...new Set(notContainRows.map((r) => r.frameworkName))]; + if (notContainNames.length > 0) { + nameFilter[Sequelize.Op.notIn] = notContainNames; + } } + + // merge with any existing name filter + filters.name = Object.assign({}, filters.name || {}, nameFilter); } frameworks = await databaseModel.Framework.findAll({ From c884cdecc56314b066f02fcef4dc4332f9143bff Mon Sep 17 00:00:00 2001 From: Zhongxin Guo Date: Thu, 6 Nov 2025 22:39:10 -0800 Subject: [PATCH 6/9] update --- src/rest-server/src/controllers/v2/job.js | 12 ++++++------ src/rest-server/src/middlewares/v2/protocol.js | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/rest-server/src/controllers/v2/job.js b/src/rest-server/src/controllers/v2/job.js index 1f33b752..b9c6c3e1 100644 --- a/src/rest-server/src/controllers/v2/job.js +++ b/src/rest-server/src/controllers/v2/job.js @@ -87,7 +87,7 @@ const list = asyncHandler(async (req, res) => { if ('tagsNotContain' in req.query) { tagsNotContainFilter.name = req.query.tagsNotContain.split(','); } - if ('jobType' in req.query){ + if ('jobType' in req.query) { if (Array.isArray(tagsContainFilter.name)) { tagsContainFilter.name.push(...req.query.jobType.split(',')); } else { @@ -209,7 +209,7 @@ const update = asyncHandler(async (req, res) => { const frameworkName = `${userName}~${jobName}`; const jobType = res.locals.protocol.jobType || 'others'; - + // check duplicate job try { const data = await job.get(frameworkName); @@ -379,10 +379,10 @@ const getLogs = asyncHandler(async (req, res) => { throw error.code === 'NoTaskLogError' ? error : createError( - 'Internal Server Error', - 'UnknownError', - 'Failed to get log list', - ); + 'Internal Server Error', + 'UnknownError', + 'Failed to get log list', + ); } }); diff --git a/src/rest-server/src/middlewares/v2/protocol.js b/src/rest-server/src/middlewares/v2/protocol.js index c828461e..fcb1e13f 100644 --- a/src/rest-server/src/middlewares/v2/protocol.js +++ b/src/rest-server/src/middlewares/v2/protocol.js @@ -121,7 +121,7 @@ const protocolValidate = (protocolYAML) => { } } - // check jobType + // check jobType if ("jobType" in protocolObj) { const validJobTypes = ['inference', 'training', 'others']; if (!validJobTypes.includes(protocolObj.jobType)) { From 43ed85cbd75b42744911b61158c38c8b62a0ceb7 Mon Sep 17 00:00:00 2001 From: Zhongxin Guo Date: Thu, 6 Nov 2025 22:44:31 -0800 Subject: [PATCH 7/9] update --- src/rest-server/src/controllers/v2/job.js | 1 - src/rest-server/src/middlewares/v2/protocol.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rest-server/src/controllers/v2/job.js b/src/rest-server/src/controllers/v2/job.js index b9c6c3e1..e79bce5a 100644 --- a/src/rest-server/src/controllers/v2/job.js +++ b/src/rest-server/src/controllers/v2/job.js @@ -25,7 +25,6 @@ const logger = require('@pai/config/logger'); const { Op } = require('sequelize'); const { userProperty } = require('@pai/config/token'); const userController = require('@pai/controllers/v2/user'); -const { filter } = require('lodash'); const list = asyncHandler(async (req, res) => { // ?keyword=&username=,&vc=, diff --git a/src/rest-server/src/middlewares/v2/protocol.js b/src/rest-server/src/middlewares/v2/protocol.js index fcb1e13f..da86c346 100644 --- a/src/rest-server/src/middlewares/v2/protocol.js +++ b/src/rest-server/src/middlewares/v2/protocol.js @@ -227,7 +227,7 @@ const protocolRender = (protocolObj) => { $parameters: protocolObj.parameters, $script: protocolObj.prerequisites.script[ - protocolObj.taskRoles[taskRole].script + protocolObj.taskRoles[taskRole].script ], $output: protocolObj.prerequisites.output[ From 7627a35da7ae7c719b2e4a5190b735c4e401a559 Mon Sep 17 00:00:00 2001 From: Zhongxin Guo Date: Thu, 6 Nov 2025 22:50:06 -0800 Subject: [PATCH 8/9] update --- src/rest-server/src/controllers/v2/job.js | 16 +++++++++++++--- src/rest-server/src/middlewares/v2/protocol.js | 12 +----------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/rest-server/src/controllers/v2/job.js b/src/rest-server/src/controllers/v2/job.js index e79bce5a..339bac7c 100644 --- a/src/rest-server/src/controllers/v2/job.js +++ b/src/rest-server/src/controllers/v2/job.js @@ -87,10 +87,21 @@ const list = asyncHandler(async (req, res) => { tagsNotContainFilter.name = req.query.tagsNotContain.split(','); } if ('jobType' in req.query) { + // validate jobType values + const validJobTypes = ['inference', 'training', 'others']; + const requestedTypes = req.query.jobType.split(','); + const invalidTypes = requestedTypes.filter(type => !validJobTypes.includes(type)); + if (invalidTypes.length > 0) { + throw createError( + 'Bad Request', + 'InvalidParametersError', + `Invalid job type(s): ${invalidTypes.join(', ')}` + ); + } if (Array.isArray(tagsContainFilter.name)) { - tagsContainFilter.name.push(...req.query.jobType.split(',')); + tagsContainFilter.name.push(...requestedTypes); } else { - tagsContainFilter.name = req.query.jobType.split(','); + tagsContainFilter.name = requestedTypes; } } if ('keyword' in req.query) { @@ -206,7 +217,6 @@ const update = asyncHandler(async (req, res) => { const jobName = res.locals.protocol.name; const userName = req.user.username; const frameworkName = `${userName}~${jobName}`; - const jobType = res.locals.protocol.jobType || 'others'; // check duplicate job diff --git a/src/rest-server/src/middlewares/v2/protocol.js b/src/rest-server/src/middlewares/v2/protocol.js index da86c346..8a07ee75 100644 --- a/src/rest-server/src/middlewares/v2/protocol.js +++ b/src/rest-server/src/middlewares/v2/protocol.js @@ -121,17 +121,7 @@ const protocolValidate = (protocolYAML) => { } } - // check jobType - if ("jobType" in protocolObj) { - const validJobTypes = ['inference', 'training', 'others']; - if (!validJobTypes.includes(protocolObj.jobType)) { - throw createError( - 'Bad Request', - 'InvalidProtocolError', - `Job type ${protocolObj.jobType} is not valid.`, - ); - } - + // check Inference job parameters if (protocolObj.jobType === 'inference') { // check parameters for inference job if (!('parameters' in protocolObj)) { From 5d370186f7137c12952ecc624aa69aa565b1be3a Mon Sep 17 00:00:00 2001 From: Zhongxin Guo Date: Thu, 6 Nov 2025 23:32:58 -0800 Subject: [PATCH 9/9] update --- src/rest-server/src/middlewares/v2/protocol.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rest-server/src/middlewares/v2/protocol.js b/src/rest-server/src/middlewares/v2/protocol.js index 8a07ee75..b989665e 100644 --- a/src/rest-server/src/middlewares/v2/protocol.js +++ b/src/rest-server/src/middlewares/v2/protocol.js @@ -121,7 +121,8 @@ const protocolValidate = (protocolYAML) => { } } - // check Inference job parameters + // check jobType + if ('jobType' in protocolObj) { if (protocolObj.jobType === 'inference') { // check parameters for inference job if (!('parameters' in protocolObj)) {