From 384e38b76073babc23ca535614a472afd5b202e5 Mon Sep 17 00:00:00 2001 From: Arthur Lu Date: Sat, 21 Oct 2023 00:13:29 +0000 Subject: [PATCH] improve getUsedResources, remove unused params for some pve functions --- src/main.js | 4 +- src/pve.js | 128 ++++++++++++++++++++++++++----------- src/routes/cluster/disk.js | 10 ++- src/routes/cluster/pci.js | 8 +-- 4 files changed, 103 insertions(+), 47 deletions(-) diff --git a/src/main.js b/src/main.js index e0f65f8..831ca65 100644 --- a/src/main.js +++ b/src/main.js @@ -4,7 +4,7 @@ import cookieParser from "cookie-parser"; import cors from "cors"; import morgan from "morgan"; -import api from "./package.js"; +import _package from "./package.js"; import * as pve from "./pve.js"; import * as utils from "./utils.js"; import LocalDB from "./db.js"; @@ -17,7 +17,7 @@ global.argv = parseArgs(process.argv.slice(2), { } }); -global.api = api(global.argv.package); +global.api = _package(global.argv.package); global.pve = pve; global.utils = utils; global.db = new LocalDB(global.argv.localdb); diff --git a/src/pve.js b/src/pve.js index a24c341..f6382d0 100644 --- a/src/pve.js +++ b/src/pve.js @@ -33,8 +33,7 @@ export async function requestPVE (path, method, auth = null, body = null) { } try { - const response = await axios.request(url, content); - return response; + return await axios.request(url, content); } catch (error) { return error.response; @@ -80,6 +79,36 @@ export async function handleResponse (node, result, res) { } } +/** + * Get the full config of an instance, including searching disk information. + * @param {Object} req ProxmoxAAS API request object. + * @param {Object} instance to get config as object containing node, type, and id. + * @param {Array} diskprefixes Array containing prefixes for disks. + * @returns + */ +async function getFullInstanceConfig (req, instance, diskprefixes) { + const config = (await requestPVE(`/nodes/${instance.node}/${instance.type}/${instance.vmid}/config`, "GET", { cookies: req.cookies })).data.data; + // fetch all instance disk and device data concurrently + const promises = []; + const mappings = []; + for (const key in config) { + if (diskprefixes.some(prefix => key.startsWith(prefix))) { + promises.push(getDiskInfo(instance.node, config, key)); + mappings.push(key); + } + else if (key.startsWith("hostpci")) { + promises.push(getDeviceInfo(instance.node, config[key].split(",")[0])); + mappings.push(key); + } + } + const results = await Promise.all(promises); + results.forEach((e, i) => { + const key = mappings[i]; + config[key] = e; + }); + return config; +} + /** * Get the amount of resources used by specified user. * @param {Object} req ProxmoxAAS API request object. @@ -87,7 +116,10 @@ export async function handleResponse (node, result, res) { * @returns {Object} k-v pairs of resource name and used amounts */ export async function getUsedResources (req, resourceMeta) { - const response = await requestPVE("/cluster/resources", "GET", { cookies: req.cookies }); + // get the basic resources list + const resources = (await requestPVE("/cluster/resources", "GET", { cookies: req.cookies })).data.data; + + // setup the used object and diskPrefixes object const used = {}; const diskprefixes = []; for (const resourceName of Object.keys(resourceMeta)) { @@ -104,49 +136,63 @@ export async function getUsedResources (req, resourceMeta) { used[resourceName] = 0; } } - for (const instance of response.data.data) { - if (instance.type === "lxc" || instance.type === "qemu") { - let config = await requestPVE(`/nodes/${instance.node}/${instance.type}/${instance.vmid}/config`, "GET", { cookies: req.cookies }); - config = config.data.data; - for (const key of Object.keys(config)) { - if (Object.keys(used).includes(key) && resourceMeta[key].type === "numeric") { - used[key] += Number(config[key]); + + // filter resources by their type, we only want lxc and qemu + const instances = []; + for (const resource of resources) { + if (resource.type === "lxc" || resource.type === "qemu") { + instances.push(resource); + } + } + + const promises = []; + const mappings = []; + for (let i = 0; i < instances.length; i++) { + const instance = instances[i]; + promises.push(getFullInstanceConfig(req, instance, diskprefixes)); + mappings.push(i); + } + const configs = await Promise.all(promises); + + // for each instance, sum each resource + for (const config of configs) { + for (const key of Object.keys(config)) { + if (Object.keys(used).includes(key) && resourceMeta[key].type === "numeric") { + used[key] += Number(config[key]); + } + else if (diskprefixes.some(prefix => key.startsWith(prefix))) { + const diskInfo = config[key]; + if (diskInfo) { // only count if disk exists + used[diskInfo.storage] += Number(diskInfo.size); } - else if (diskprefixes.some(prefix => key.startsWith(prefix))) { - const diskInfo = await getDiskInfo(instance.node, instance.type, instance.vmid, key); - if (diskInfo) { // only count if disk exists - used[diskInfo.storage] += Number(diskInfo.size); - } - } - else if (key.startsWith("net") && config[key].includes("rate=")) { // only count net instances with a rate limit - used.network += Number(config[key].split("rate=")[1].split(",")[0]); - } - else if (key.startsWith("hostpci")) { - const deviceInfo = await getDeviceInfo(instance.node, instance.type, instance.vmid, config[key].split(",")[0]); - if (deviceInfo) { // only count if device exists - used.pci.push(deviceInfo.device_name); - } + } + else if (key.startsWith("net") && config[key].includes("rate=")) { // only count net instances with a rate limit + used.network += Number(config[key].split("rate=")[1].split(",")[0]); + } + else if (key.startsWith("hostpci")) { + const deviceInfo = config[key]; + if (deviceInfo) { // only count if device exists + used.pci.push(deviceInfo.device_name); } } } } + return used; } /** * Get meta data for a specific disk. Adds info that is not normally available in a instance's config. * @param {string} node containing the query disk. - * @param {string} type of instance with query disk. - * @param {string} vmid of instance with query disk + * @param {string} config of instance with query disk. * @param {string} disk name of the query disk, ie. sata0. * @returns {Objetc} k-v pairs of specific disk data, including storage and size of unused disks. */ -export async function getDiskInfo (node, type, vmid, disk) { +export async function getDiskInfo (node, config, disk) { const pveAPIToken = global.db.pveAPIToken; try { - const config = await requestPVE(`/nodes/${node}/${type}/${vmid}/config`, "GET", { token: pveAPIToken }); - const storageID = config.data.data[disk].split(":")[0]; - const volID = config.data.data[disk].split(",")[0]; + const storageID = config[disk].split(":")[0]; + const volID = config[disk].split(",")[0]; const volInfo = await requestPVE(`/nodes/${node}/storage/${storageID}/content/${volID}`, "GET", { token: pveAPIToken }); volInfo.data.data.storage = storageID; return volInfo.data.data; @@ -159,12 +205,10 @@ export async function getDiskInfo (node, type, vmid, disk) { /** * Get meta data for a specific pci device. Adds info that is not normally available in a instance's config. * @param {string} node containing the query device. - * @param {string} type of instance with query device. - * @param {string} vmid of instance with query device. * @param {string} qid pci bus id number of the query device, ie. 89ab:cd:ef.0. * @returns {Object} k-v pairs of specific device data, including device name and manufacturer. */ -export async function getDeviceInfo (node, type, vmid, qid) { +export async function getDeviceInfo (node, qid) { const pveAPIToken = global.db.pveAPIToken; try { const result = (await requestPVE(`/nodes/${node}/hardware/pci`, "GET", { token: pveAPIToken })).data.data; @@ -189,17 +233,25 @@ export async function getDeviceInfo (node, type, vmid, qid) { /** * Get available devices on specific node. * @param {string} node to get devices from. - * @param {Object} cookies user authentication, unused since the API token is required. * @returns {Array.} array of k-v pairs of specific device data, including device name and manufacturer, which are available on the specified node. */ -export async function getNodeAvailDevices (node, cookies) { +export async function getNodeAvailDevices (node) { const pveAPIToken = global.db.pveAPIToken; // get node pci devices - let nodeAvailPci = (await requestPVE(`/nodes/${node}/hardware/pci`, "GET", { token: pveAPIToken })).data.data; + let nodeAvailPci = requestPVE(`/nodes/${node}/hardware/pci`, "GET", { token: pveAPIToken }); // for each node container, get its config and remove devices which are already used - const vms = (await requestPVE(`/nodes/${node}/qemu`, "GET", { token: pveAPIToken })).data.data; + let vms = (await requestPVE(`/nodes/${node}/qemu`, "GET", { token: pveAPIToken })).data.data; + + const promises = []; for (const vm of vms) { - const config = (await requestPVE(`/nodes/${node}/qemu/${vm.vmid}/config`, "GET", { token: pveAPIToken })).data.data; + promises.push(requestPVE(`/nodes/${node}/qemu/${vm.vmid}/config`, "GET", { token: pveAPIToken })); + } + const configs = await Promise.all(promises); + configs.forEach((e,i) => {configs[i] = e.data.data}); + + nodeAvailPci = (await nodeAvailPci).data.data; + + for (const config of configs) { Object.keys(config).forEach((key) => { if (key.startsWith("hostpci")) { const deviceID = config[key].split(",")[0]; diff --git a/src/routes/cluster/disk.js b/src/routes/cluster/disk.js index 51d6ea1..e0f0a2f 100644 --- a/src/routes/cluster/disk.js +++ b/src/routes/cluster/disk.js @@ -94,7 +94,7 @@ router.post("/:disk/attach", async (req, res) => { return; } // target disk must be allowed according to source disk's storage options - const diskConfig = await getDiskInfo(params.node, params.type, params.vmid, `unused${params.source}`); // get target disk + const diskConfig = await getDiskInfo(params.node, config, `unused${params.source}`); // get target disk const resourceConfig = db.getGlobalConfig().resources; if (!resourceConfig[diskConfig.storage].disks.some(diskPrefix => params.disk.startsWith(diskPrefix))) { res.status(500).send({ error: `Requested target ${params.disk} is not in allowed list [${resourceConfig[diskConfig.storage].disks}].` }); @@ -141,8 +141,10 @@ router.post("/:disk/resize", async (req, res) => { if (!auth) { return; } + // get current config + const config = (await requestPVE(`${vmpath}/config`, "GET", { cookies: req.cookies })).data.data; // check disk existence - const diskConfig = await getDiskInfo(params.node, params.type, params.vmid, params.disk); // get target disk + const diskConfig = await getDiskInfo(params.node, config, params.disk); // get target disk if (!diskConfig) { // exit if disk does not exist res.status(500).send({ error: `requested disk ${params.disk} does not exist.` }); res.end(); @@ -196,8 +198,10 @@ router.post("/:disk/move", async (req, res) => { if (!auth) { return; } + // get current config + const config = (await requestPVE(`${vmpath}/config`, "GET", { cookies: req.cookies })).data.data; // check disk existence - const diskConfig = await getDiskInfo(params.node, params.type, params.vmid, params.disk); // get target disk + const diskConfig = await getDiskInfo(params.node, config, params.disk); // get target disk if (!diskConfig) { // exit if disk does not exist res.status(500).send({ error: `requested disk ${params.disk} does not exist.` }); res.end(); diff --git a/src/routes/cluster/pci.js b/src/routes/cluster/pci.js index 574e8a4..e41abfe 100644 --- a/src/routes/cluster/pci.js +++ b/src/routes/cluster/pci.js @@ -45,7 +45,7 @@ router.get("/:hostpci", async (req, res) => { } const device = config[`hostpci${params.hostpci}`].split(",")[0]; // get node's pci devices - const deviceData = await getDeviceInfo(params.node, params.type, params.vmid, device); + const deviceData = await getDeviceInfo(params.node, device); if (!deviceData) { res.status(500).send({ error: `Could not find hostpci${params.hostpci}=${device} in ${params.node}.` }); res.end(); @@ -96,7 +96,7 @@ router.post("/:hostpci/modify", async (req, res) => { params.device = params.device.split(".")[0]; // get instance config to check if device has not changed const config = (await requestPVE(`/nodes/${params.node}/${params.type}/${params.vmid}/config`, "GET", { token: pveAPIToken })).data.data; - const currentDeviceData = await getDeviceInfo(params.node, params.type, params.vmid, config[`hostpci${params.hostpci}`].split(",")[0]); + const currentDeviceData = await getDeviceInfo(params.node, config[`hostpci${params.hostpci}`].split(",")[0]); if (!currentDeviceData) { res.status(500).send({ error: `No device in hostpci${params.hostpci}.` }); res.end(); @@ -105,7 +105,7 @@ router.post("/:hostpci/modify", async (req, res) => { // only check user and node availability if base id is different if (currentDeviceData.id.split(".")[0] !== params.device) { // setup request - const deviceData = await getDeviceInfo(params.node, params.type, params.vmid, params.device); + const deviceData = await getDeviceInfo(params.node, params.device); const request = { pci: deviceData.device_name }; // check resource approval if (!await approveResources(req, req.cookies.username, request)) { @@ -184,7 +184,7 @@ router.post("/create", async (req, res) => { hostpci++; } // setup request - const deviceData = await getDeviceInfo(params.node, params.type, params.vmid, params.device); + const deviceData = await getDeviceInfo(params.node, params.device); const request = { pci: deviceData.device_name };