improve return message when requests do not pass resource approval

This commit is contained in:
2025-10-17 02:45:59 +00:00
parent f2f4f45097
commit 07c48db808
5 changed files with 51 additions and 27 deletions

View File

@@ -98,7 +98,7 @@ router.get(`/:node(${nodeRegexP})/pci`, async (req, res) => {
// get remaining user resources
const userAvailPci = (await getUserResources(req, userObj)).pci.nodes[params.node]; // we assume that the node list is used. TODO support global lists
if (userAvailPci === undefined) { // user has no avaliable devices on this node, so send an empty list
if (userAvailPci === undefined) { // user has no available devices on this node, so send an empty list
res.status(200).send([]);
res.end();
}
@@ -164,8 +164,8 @@ router.get(`${basePath}`, async (req, res) => {
* - swap: number, optional - new amount of swap for instance
* responses:
* - 200: PVE Task Object
* - 400: {request; Object, error: string, reason: Object}
* - 401: {auth: false, path: string}
* - 500: {request: Object, error: string}
* - 500: PVE Task Object
*/
router.post(`${basePath}/resources`, async (req, res) => {
@@ -201,8 +201,9 @@ router.post(`${basePath}/resources`, async (req, res) => {
request.cpu = params.proctype;
}
// check resource approval
if (!await approveResources(req, userObj, request, params.node)) {
res.status(500).send({ request, error: "Could not fulfil request." });
const { approved, reason } = await approveResources(req, userObj, request, params.node);
if (!approved) {
res.status(400).send({ request, error: "Not enough resources to satisfy request.", reason });
res.end();
return;
}
@@ -239,9 +240,9 @@ router.post(`${basePath}/resources`, async (req, res) => {
* - rootfssize: number, optional, - size of lxc instance rootfs
* responses:
* - 200: PVE Task Object
* - 400: {request: Object, error: string, reason: Object}
* - 401: {auth: false, path: string}
* - 500: {error: string}
* - 500: {request: Object, error: string}
* - 500: PVE Task Object
*/
router.post(`${basePath}/create`, async (req, res) => {
@@ -312,8 +313,9 @@ router.post(`${basePath}/create`, async (req, res) => {
}
}
// check resource approval
if (!await approveResources(req, userObj, request, params.node)) { // check resource approval
res.status(500).send({ request, error: "Not enough resources to satisfy request." });
const { approved, reason } = await approveResources(req, userObj, request, params.node);
if (!approved) {
res.status(400).send({ request, error: "Not enough resources to satisfy request.", reason });
res.end();
return;
}

View File

@@ -157,7 +157,8 @@ router.post("/:disk/resize", async (req, res) => {
const request = {};
request[storage] = Number(params.size * 1024 ** 3); // setup request object
// check request approval
if (!await approveResources(req, userObj, request, params.node)) {
const { approved } = await approveResources(req, userObj, request, params.node);
if (!approved) {
res.status(500).send({ request, error: `Storage ${storage} could not fulfill request of size ${params.size}G.` });
res.end();
return;
@@ -219,7 +220,8 @@ router.post("/:disk/move", async (req, res) => {
request[dstStorage] = Number(size); // always decrease destination storage by size
}
// check request approval
if (!await approveResources(req, userObj, request, params.node)) {
const { approved } = await approveResources(req, userObj, request, params.node);
if (!approved) {
res.status(500).send({ request, error: `Storage ${params.storage} could not fulfill request of size ${params.size}G.` });
res.end();
return;
@@ -335,7 +337,8 @@ router.post("/:disk/create", async (req, res) => {
// setup request
request[params.storage] = Number(params.size * 1024 ** 3);
// check request approval
if (!await approveResources(req, userObj, request, params.node)) {
const { approved } = await approveResources(req, userObj, request, params.node);
if (!approved) {
res.status(500).send({ request, error: `Storage ${params.storage} could not fulfill request of size ${params.size}G.` });
res.end();
return;

View File

@@ -53,7 +53,8 @@ router.post("/:netid/create", async (req, res) => {
};
// check resource approval
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
if (!await approveResources(req, userObj, request, params.node)) {
const { approved } = await approveResources(req, userObj, request, params.node);
if (!approved) {
res.status(500).send({ request, error: `Could not fulfil network request of ${params.rate}MB/s.` });
res.end();
return;
@@ -116,7 +117,8 @@ router.post("/:netid/modify", async (req, res) => {
};
// check resource approval
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
if (!await approveResources(req, userObj, request, params.node)) {
const { approved } = await approveResources(req, userObj, request, params.node);
if (!approved) {
res.status(500).send({ request, error: `Could not fulfil network request of ${params.rate}MB/s.` });
res.end();
return;

View File

@@ -100,7 +100,8 @@ router.post("/:hostpci/modify", async (req, res) => {
return;
}
// check resource approval
if (!await approveResources(req, userObj, request, params.node)) {
const { approved } = await approveResources(req, userObj, request, params.node);
if (!approved) {
res.status(500).send({ request, error: `Could not fulfil request for ${requestedDevice.device_name}.` });
res.end();
return;
@@ -172,7 +173,8 @@ router.post("/:hostpci/create", async (req, res) => {
const request = { pci: requestedDevice.device_name };
// check resource approval
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
if (!await approveResources(req, userObj, request, params.node)) {
const { approved } = await approveResources(req, userObj, request, params.node);
if (!approved) {
res.status(500).send({ request, error: `Could not fulfil request for ${requestedDevice.device_name}.` });
res.end();
return;

View File

@@ -222,19 +222,24 @@ export async function getUserResources (req, user) {
* @param {Object} req ProxmoxAAS API request object.
* @param {{id: string, realm: string}} user object of user requesting additional resources.
* @param {Object} request k-v pairs of resources and requested amounts
* @returns {boolean} true if the available resources can fullfill the requested resources, false otherwise.
* @returns {boolean, Object} true if the available resources can fullfill the requested resources, false otherwise.
*/
export async function approveResources (req, user, request, node) {
const dbResources = global.config.resources;
const userResources = await getUserResources(req, user);
let approved = true;
Object.keys(request).every((key) => {
// let approved = true;
const reason = {};
for (const key in request) {
// if requested resource is not specified in user resources, assume it's not allowed
if (!(key in userResources)) {
approved = false;
return false;
// approved = false;
reason[key] = { approved: false, reason: `${key} not allowed` };
continue;
// return;
}
// use node specific quota if there is one available, otherwise use the global resource quota
const inNode = node in userResources[key].nodes;
const resourceData = inNode ? userResources[key].nodes[node] : userResources[key].global;
@@ -244,24 +249,34 @@ export async function approveResources (req, user, request, node) {
// if no matching resource when index == -1, then remaining is -1 otherwise use the remaining value
const avail = index === -1 ? false : resourceData[index].avail > 0;
if (avail !== dbResources[key].whitelist) {
approved = false;
return false;
// approved = false;
reason[key] = { approved: false, reason: `${key} ${dbResources[key].whitelist ? "not in whitelist" : "in blacklist"}` };
// return;
continue;
}
}
// if either the requested or avail resource is not strictly a number, block
else if (typeof (resourceData.avail) !== "number" || typeof (request[key]) !== "number") {
approved = false;
return false;
// approved = false;
reason[key] = { approved: false, reason: `expected ${key} to be a number but got ${request[key]}` };
continue;
// return;
}
// if the avail resources is less than the requested resources, block
else if (resourceData.avail - request[key] < 0) {
approved = false;
return false;
// approved = false;
reason[key] = { approved: false, reason: `${key} requested ${request[key]} which is more than ${resourceData.avail} available` };
continue;
// return;
}
return true;
reason[key] = { approved: true, reason: "ok" };
}
const approved = Object.values(reason).every((element) => {
return element.approved === true;
});
return approved; // if all requested resources pass, allow
return { approved, reason }; // if all requested resources pass, allow
}
/**