From 49192daac6adc6e625b6011f7a9a3c15be7c2272 Mon Sep 17 00:00:00 2001 From: Arthur Lu Date: Wed, 21 Jun 2023 05:06:38 +0000 Subject: [PATCH] add pci device resource to config, implement endpoints for getting available devices, update used resource calculation to include pci devices --- README.md | 2 +- config/localdb.json.template | 5 +++ src/main.js | 60 ++++++++++++++++++++++++++++++------ src/pve.js | 35 ++++++++++++++++++--- src/utils.js | 13 ++++++-- 5 files changed, 98 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 3403fbc..9351ef1 100644 --- a/README.md +++ b/README.md @@ -48,4 +48,4 @@ server { 2. Start nginx with the new configurations by running `systemctl reload nginx` ## Result -After these steps, the ProxmoxAAS Client should be avaliable and fully functional at `client.`. \ No newline at end of file +After these steps, the ProxmoxAAS Client should be available and fully functional at `client.`. \ No newline at end of file diff --git a/config/localdb.json.template b/config/localdb.json.template index a729b80..81a587e 100644 --- a/config/localdb.json.template +++ b/config/localdb.json.template @@ -76,6 +76,11 @@ "compact": true, "unit": "B/s", "display": true + }, + "pci": { + "type": "list", + "whitelist": true, + "display": true } }, "users": { diff --git a/src/main.js b/src/main.js index 0a2eb1a..b9ff474 100644 --- a/src/main.js +++ b/src/main.js @@ -5,7 +5,7 @@ import cors from "cors"; import morgan from "morgan"; import api from "../package.json" assert {type: "json"}; -import { requestPVE, handleResponse, getDiskInfo } from "./pve.js"; +import { requestPVE, handleResponse, getDiskInfo, getDeviceInfo, getUsedResources } from "./pve.js"; import { checkAuth, approveResources, getUserResources } from "./utils.js"; import { db, pveAPIToken, listenPort, hostname, domain } from "./db.js"; @@ -614,7 +614,7 @@ app.delete("/api/instance/network/delete", async (req, res) => { * - vmid: Number - vm id number to destroy * - hostpci: String - hostpci number * responses: - * - 200: PVE PCI Device Object + * - 200: {device_name: PVE PCI Device Object} * - 401: {auth: false, path: String} * - 500: {error: String} */ @@ -632,14 +632,54 @@ app.get("/api/instance/pci", async (req, res) => { } let device = config[`hostpci${req.query.hostpci}`].split(",")[0]; // get node's pci devices - let result = (await requestPVE(`/nodes/${req.query.node}/hardware/pci`, "GET", req.cookies, null, pveAPIToken)).data.data; - let deviceData = []; - result.forEach((element) => { - if (element.id.startsWith(device)) { - deviceData.push(element); - } - }); - res.status(200).send(deviceData); + let deviceData = await getDeviceInfo(req.query.node, req.query.type, req.query.vmid, device); + if (!deviceData) { + res.status(500).send({ error: `Could not find hostpci${req.query.hostpci}=${device} in ${req.query.node}.` }); + res.end(); + return; + } + res.status(200).send({ device_name: deviceData }); + res.end(); + return; +}); + +/** + * GET - get available pcie devices given node and user + * request: + * - node: String - vm host node id + * responses: + * - 200: [PVE PCI Device Object] + * - 401: {auth: false, path: String} + * - 500: {error: String} + */ +app.get("/api/nodes/pci", async (req, res) => { + // check auth + let auth = await checkAuth(req.cookies, res); + if (!auth) { return; } + // get remaining user resources + let userAvailPci = (await getUserResources(req, req.cookies.username)).avail.pci; + // get node pci devices + let nodeAvailPci = (await requestPVE(`/nodes/${req.query.node}/hardware/pci`, "GET", req.body.cookies, null, pveAPIToken)).data.data; + // for each node container, get its config and remove devices which are already used + let vms = (await requestPVE(`/nodes/${req.query.node}/qemu`, "GET", req.body.cookies, null, pveAPIToken)).data.data; + for (let vm of vms) { + let config = (await requestPVE(`/nodes/${req.query.node}/qemu/${vm.vmid}/config`, "GET", req.body.cookies, null, pveAPIToken)).data.data; + Object.keys(config).forEach((key) => { + if (key.startsWith("hostpci")) { + let device_id = config[key].split(",")[0]; + let allfn = !device_id.includes("."); + + if (allfn) { // if allfn, remove all devices which include the same id as already allocated device + nodeAvailPci = nodeAvailPci.filter(element => !element.id.includes(device_id)); + } + else { // if not allfn, remove only device with exact id match + nodeAvailPci = nodeAvailPci.filter(element => !element.id === device_id); + } + } + }); + } + nodeAvailPci = nodeAvailPci.filter(nodeAvail => userAvailPci.some((userAvail) => { return nodeAvail.device_name && nodeAvail.device_name.includes(userAvail) })); + res.status(200).send(nodeAvailPci); res.end(); return; }); diff --git a/src/pve.js b/src/pve.js index 363b00c..910adc9 100644 --- a/src/pve.js +++ b/src/pve.js @@ -71,12 +71,18 @@ export async function getUsedResources(req, resourceMeta) { let used = {}; let diskprefixes = []; for (let resourceName of Object.keys(resourceMeta)) { - used[resourceName] = 0; if (resourceMeta[resourceName].type === "storage") { + used[resourceName] = 0; for (let diskPrefix of resourceMeta[resourceName].disks) { diskprefixes.push(diskPrefix); } } + else if (resourceMeta[resourceName].type === "list") { + used[resourceName] = []; + } + else { + used[resourceName] = 0; + } } for (let instance of response.data.data) { if (instance.type === "lxc" || instance.type === "qemu") { @@ -92,9 +98,13 @@ export async function getUsedResources(req, resourceMeta) { used[diskInfo.storage] += Number(diskInfo.size); } } - else if (key.startsWith("net")) { - if (config[key].includes("rate=")) { // only count instances with a rate limit - used.network += Number(config[key].split("rate=")[1].split(",")[0]); + 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")) { + let deviceInfo = await getDeviceInfo(instance.node, instance.type, instance.vmid, config[key].split(",")[0]); + if (deviceInfo) { // only count if device exists + used.pci.push(deviceInfo); } } } @@ -115,4 +125,21 @@ export async function getDiskInfo(node, type, vmid, disk) { catch { return null; } +} + +export async function getDeviceInfo(node, type, vmid, qid) { + try { + let result = (await requestPVE(`/nodes/${node}/hardware/pci`, "GET", null, null, pveAPIToken)).data.data; + let deviceData = []; + result.forEach((element) => { + if (element.id.startsWith(qid)) { + deviceData.push(element); + } + }); + deviceData.sort((a, b) => { return a.id < b.id }) + return deviceData[0].device_name; + } + catch { + return null; + } } \ No newline at end of file diff --git a/src/utils.js b/src/utils.js index c6320fb..492cadc 100644 --- a/src/utils.js +++ b/src/utils.js @@ -33,7 +33,16 @@ export async function getUserResources(req, username) { let max = db.getUserConfig(username).resources.max; let avail = {}; Object.keys(max).forEach((k) => { - avail[k] = max[k] - used[k]; + if (dbResources[k] && dbResources[k].type === "list") { + avail[k] = structuredClone(max[k]); + used[k].forEach((usedDeviceName) => { + let index = avail[k].findIndex((maxElement) => usedDeviceName.includes(maxElement)); + avail[k].splice(index, 1); + }); + } + else { + avail[k] = max[k] - used[k]; + } }); return { used: used, max: max, avail: avail, resources: dbResources }; } @@ -49,7 +58,7 @@ export async function approveResources(req, username, request) { approved = false; } else if (resources[key].type === "list") { - if (max[key].includes(request[key]) != resources[key].whitelist) { + if (avail[key].includes(request[key]) != resources[key].whitelist) { approved = false; } }