add pci device resource to config,

implement endpoints for getting available devices,
update used resource calculation to include pci devices
This commit is contained in:
Arthur Lu 2023-06-21 05:06:38 +00:00
parent 83a03bfd7b
commit 49192daac6
5 changed files with 98 additions and 17 deletions

View File

@ -48,4 +48,4 @@ server {
2. Start nginx with the new configurations by running `systemctl reload nginx` 2. Start nginx with the new configurations by running `systemctl reload nginx`
## Result ## Result
After these steps, the ProxmoxAAS Client should be avaliable and fully functional at `client.<FQDN>`. After these steps, the ProxmoxAAS Client should be available and fully functional at `client.<FQDN>`.

View File

@ -76,6 +76,11 @@
"compact": true, "compact": true,
"unit": "B/s", "unit": "B/s",
"display": true "display": true
},
"pci": {
"type": "list",
"whitelist": true,
"display": true
} }
}, },
"users": { "users": {

View File

@ -5,7 +5,7 @@ import cors from "cors";
import morgan from "morgan"; import morgan from "morgan";
import api from "../package.json" assert {type: "json"}; 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 { checkAuth, approveResources, getUserResources } from "./utils.js";
import { db, pveAPIToken, listenPort, hostname, domain } from "./db.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 * - vmid: Number - vm id number to destroy
* - hostpci: String - hostpci number * - hostpci: String - hostpci number
* responses: * responses:
* - 200: PVE PCI Device Object * - 200: {device_name: PVE PCI Device Object}
* - 401: {auth: false, path: String} * - 401: {auth: false, path: String}
* - 500: {error: 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]; let device = config[`hostpci${req.query.hostpci}`].split(",")[0];
// get node's pci devices // get node's pci devices
let result = (await requestPVE(`/nodes/${req.query.node}/hardware/pci`, "GET", req.cookies, null, pveAPIToken)).data.data; let deviceData = await getDeviceInfo(req.query.node, req.query.type, req.query.vmid, device);
let deviceData = []; if (!deviceData) {
result.forEach((element) => { res.status(500).send({ error: `Could not find hostpci${req.query.hostpci}=${device} in ${req.query.node}.` });
if (element.id.startsWith(device)) { res.end();
deviceData.push(element); return;
} }
}); res.status(200).send({ device_name: deviceData });
res.status(200).send(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(); res.end();
return; return;
}); });

View File

@ -71,12 +71,18 @@ export async function getUsedResources(req, resourceMeta) {
let used = {}; let used = {};
let diskprefixes = []; let diskprefixes = [];
for (let resourceName of Object.keys(resourceMeta)) { for (let resourceName of Object.keys(resourceMeta)) {
used[resourceName] = 0;
if (resourceMeta[resourceName].type === "storage") { if (resourceMeta[resourceName].type === "storage") {
used[resourceName] = 0;
for (let diskPrefix of resourceMeta[resourceName].disks) { for (let diskPrefix of resourceMeta[resourceName].disks) {
diskprefixes.push(diskPrefix); diskprefixes.push(diskPrefix);
} }
} }
else if (resourceMeta[resourceName].type === "list") {
used[resourceName] = [];
}
else {
used[resourceName] = 0;
}
} }
for (let instance of response.data.data) { for (let instance of response.data.data) {
if (instance.type === "lxc" || instance.type === "qemu") { if (instance.type === "lxc" || instance.type === "qemu") {
@ -92,9 +98,13 @@ export async function getUsedResources(req, resourceMeta) {
used[diskInfo.storage] += Number(diskInfo.size); used[diskInfo.storage] += Number(diskInfo.size);
} }
} }
else if (key.startsWith("net")) { else if (key.startsWith("net") && config[key].includes("rate=")) { // only count net instances with a rate limit
if (config[key].includes("rate=")) { // only count instances with a rate limit used.network += Number(config[key].split("rate=")[1].split(",")[0]);
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 { catch {
return null; 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;
}
} }

View File

@ -33,7 +33,16 @@ export async function getUserResources(req, username) {
let max = db.getUserConfig(username).resources.max; let max = db.getUserConfig(username).resources.max;
let avail = {}; let avail = {};
Object.keys(max).forEach((k) => { 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 }; return { used: used, max: max, avail: avail, resources: dbResources };
} }
@ -49,7 +58,7 @@ export async function approveResources(req, username, request) {
approved = false; approved = false;
} }
else if (resources[key].type === "list") { 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; approved = false;
} }
} }