prepare pve backend and utils for paas fabric

This commit is contained in:
Arthur Lu 2025-02-06 00:53:25 +00:00
parent d080e71601
commit cfcf08b373
3 changed files with 167 additions and 146 deletions

View File

@ -4,6 +4,7 @@
"import": "pve.js",
"config": {
"url": "https://pve.mydomain.example/api2/json",
"fabric": "http://localhost:8082",
"token": {
"user": "proxmoxaas-api",
"realm": "pam",
@ -77,7 +78,7 @@
"memory": {
"name": "RAM",
"type": "numeric",
"multiplier": 1048576,
"multiplier": 1,
"base": 1024,
"compact": true,
"unit": "B",
@ -86,7 +87,7 @@
"swap": {
"name": "SWAP",
"type": "numeric",
"multiplier": 1048576,
"multiplier": 1,
"base": 1024,
"compact": true,
"unit": "B",

View File

@ -5,12 +5,14 @@ export default class PVE extends PVE_BACKEND {
#pveAPIURL = null;
#pveAPIToken = null;
#pveRoot = null;
#paasFabric = null;
constructor (config) {
super();
this.#pveAPIURL = config.url;
this.#pveAPIToken = config.token;
this.#pveRoot = config.root;
this.#paasFabric = config.fabric;
}
async openSession (user, password) {
@ -99,7 +101,7 @@ export default class PVE extends PVE_BACKEND {
const upid = result.data.data;
let taskStatus = await this.requestPVE(`/nodes/${node}/tasks/${upid}/status`, "GET", { token: true });
while (taskStatus.data.data.status !== "stopped") {
await waitFor(1000);
await waitFor(100);
taskStatus = await this.requestPVE(`/nodes/${node}/tasks/${upid}/status`, "GET", { token: true });
}
if (taskStatus.data.data.exitstatus === "OK") {
@ -123,26 +125,6 @@ export default class PVE extends PVE_BACKEND {
}
}
/**
* 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} 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.
*/
async getDiskInfo (node, config, disk) {
try {
const storageID = config[disk].split(":")[0];
const volID = config[disk].split(",")[0];
const volInfo = await this.requestPVE(`/nodes/${node}/storage/${storageID}/content/${volID}`, "GET", { token: true });
volInfo.data.data.storage = storageID;
return volInfo.data.data;
}
catch {
return null;
}
}
/**
* 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.
@ -202,4 +184,100 @@ export default class PVE extends PVE_BACKEND {
}
return nodeAvailPci;
}
/**
* Send HTTP request to PAAS Fabric
* @param {string} path HTTP path, prepended with the proxmox API base url.
* @param {string} method HTTP method.
* @param {Object} auth authentication method. Set auth.cookies with user cookies or auth.token with PVE API Token. Optional.
* @param {string} body body parameters and data to be sent. Optional.
* @returns {Object} HTTP response object or HTTP error object.
*/
async requestFabric (path, method, body = null) {
const url = `${this.#paasFabric}${path}`;
const content = {
method,
mode: "cors",
credentials: "include",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
data: body
};
try {
return await axios.request(url, content);
}
catch (error) {
return error;
}
}
async getNode (node) {
const res = await this.requestFabric(`/nodes/${node}`, "GET");
if (res.status !== 200) {
console.error(res);
return null;
}
return res.data.instance;
}
async syncNode (node) {
this.requestFabric(`/nodes/${node}/sync`, "POST");
}
async getInstance (node, instance) {
const res = await this.requestFabric(`/nodes/${node}/instances/${instance}`, "GET");
if (res.status !== 200) {
console.error(res);
return null;
}
return res.data.instance;
}
async syncInstance (node, vmid) {
this.requestFabric(`/nodes/${node}/instances/${vmid}/sync`, "POST");
}
/**
* 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} 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.
*/
async getDisk (node, instance, disk) {
const config = await this.getInstance(node, instance);
if (config != null && config.volumes[disk] != null) {
return config.volumes[disk];
}
else {
return null;
}
}
async getUserResources (user, cookies) {
// get user resources with vm filter
const res = await this.requestPVE("/cluster/resources?type=vm", "GET", { cookies });
if (res.status !== 200) {
return null;
}
const userPVEResources = res.data.data;
const resources = {};
// for each resource, add to the object
for (const resource of userPVEResources) {
const instance = await this.getInstance(resource.node, resource.vmid);
if (instance) {
instance.node = resource.node;
resources[resource.vmid] = instance;
}
}
return resources;
}
}

View File

@ -59,69 +59,6 @@ export async function checkAuth (cookies, res, vmpath = null) {
return auth;
}
/**
* 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 global.pve.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(global.pve.getDiskInfo(instance.node, config, key));
mappings.push(key);
}
else if (key.startsWith("hostpci")) {
promises.push(global.pve.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;
});
config.node = instance.node;
return config;
}
/**
* Get all configs for every instance owned by the user. Uses the expanded config data from getFullInstanceConfig.
* @param {Object} req ProxmoxAAS API request object.
* @param {Object} dbResources data about application resources, to indicate which resources are tracked.
* @returns {Object} k-v pairs of resource name and used amounts
*/
async function getAllInstanceConfigs (req, diskprefixes) {
// get the basic resources list
const resources = (await global.pve.requestPVE("/cluster/resources", "GET", { cookies: req.cookies })).data.data;
// 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);
}
}
// get all instance configs, also include detailed disk and device info
const promises = [];
const mappings = [];
for (let i = 0; i < instances.length; i++) {
const instance = instances[i];
const config = getFullInstanceConfig(req, instance, diskprefixes);
promises.push(config);
mappings.push(i);
}
const configs = await Promise.all(promises);
return configs;
}
/**
* Get user resource data including used, available, and maximum resources.
* @param {Object} req ProxmoxAAS API request object.
@ -131,15 +68,6 @@ async function getAllInstanceConfigs (req, diskprefixes) {
export async function getUserResources (req, user) {
const dbResources = global.config.resources;
const userResources = (await global.userManager.getUser(user, req.cookies)).resources;
// setup disk prefixes object
const diskprefixes = [];
for (const resourceName of Object.keys(dbResources)) {
if (dbResources[resourceName].type === "storage") {
for (const diskPrefix of dbResources[resourceName].disks) {
diskprefixes.push(diskPrefix);
}
}
}
// setup the user resource object with used and avail for each resource and each resource pool
// also add a total counter for each resource (only used for display, not used to check requests)
@ -193,10 +121,12 @@ export async function getUserResources (req, user) {
}
}
const configs = await getAllInstanceConfigs(req, diskprefixes);
const configs = await global.pve.getUserResources(user, req.cookies);
for (const config of configs) {
for (const vmid in configs) {
const config = configs[vmid];
const nodeName = config.node;
// count basic numeric resources
for (const resourceName of Object.keys(config)) {
// numeric resource type
if (resourceName in dbResources && dbResources[resourceName].type === "numeric") {
@ -214,65 +144,77 @@ export async function getUserResources (req, user) {
userResources[resourceName].total.used += val;
userResources[resourceName].total.avail -= val;
}
else if (diskprefixes.some(prefix => resourceName.startsWith(prefix))) {
const diskInfo = config[resourceName];
if (diskInfo) { // only count if disk exists
const val = Number(diskInfo.size);
const storage = diskInfo.storage;
}
// count disk resources in volumes
for (const diskid in config.volumes) {
const disk = config.volumes[diskid];
const storage = disk.storage;
const size = disk.size;
// only process disk if its storage is in the user resources to be counted
if (storage in userResources) {
// if the instance's node is restricted by this resource, add it to the instance's used value
if (nodeName in userResources[storage].nodes) {
userResources[storage].nodes[nodeName].used += val;
userResources[storage].nodes[nodeName].avail -= val;
userResources[storage].nodes[nodeName].used += size;
userResources[storage].nodes[nodeName].avail -= size;
}
// otherwise add the resource to the global pool
else {
userResources[storage].global.used += val;
userResources[storage].global.avail -= val;
userResources[storage].global.used += size;
userResources[storage].global.avail -= size;
}
userResources[storage].total.used += val;
userResources[storage].total.avail -= val;
userResources[storage].total.used += size;
userResources[storage].total.avail -= size;
}
}
else if (resourceName.startsWith("net") && config[resourceName].includes("rate=")) { // only count net instances with a rate limit
const val = Number(config[resourceName].split("rate=")[1].split(",")[0]);
// count net resources in nets
for (const netid in config.nets) {
const net = config.nets[netid];
const rate = net.rate;
if (userResources.network) {
// if the instance's node is restricted by this resource, add it to the instance's used value
if (nodeName in userResources.network.nodes) {
userResources.network.nodes[nodeName].used += val;
userResources.network.nodes[nodeName].avail -= val;
userResources.network.nodes[nodeName].used += rate;
userResources.network.nodes[nodeName].avail -= rate;
}
// otherwise add the resource to the global pool
else {
userResources.network.global.used += val;
userResources.network.global.avail -= val;
userResources.network.global.used += rate;
userResources.network.global.avail -= rate;
}
userResources.network.total.used += val;
userResources.network.total.avail -= val;
userResources.network.total.used += rate;
userResources.network.total.avail -= rate;
}
else if (resourceName.startsWith("hostpci")) {
const deviceInfo = config[resourceName];
if (deviceInfo) { // only count if device exists
const deviceName = deviceInfo.device_name;
// if the instance's node is restricted by this resource, add it to the instance's used value
}
// count pci device resources in devices
for (const deviceid in config.devices) {
const device = config.devices[deviceid];
let name = "";
for (const subsystems of device) {
name += `${subsystems.device_name}:${subsystems.subsystem_device_name},`;
}
if (nodeName in userResources.pci.nodes) {
const index = userResources.pci.nodes[nodeName].findIndex((availEelement) => deviceName.includes(availEelement.match));
const index = userResources.pci.nodes[nodeName].findIndex((availEelement) => name.includes(availEelement.match));
if (index >= 0) {
userResources.pci.nodes[nodeName][index].used++;
userResources.pci.nodes[nodeName][index].avail--;
}
}
// otherwise try to add the resource to the global pool
else {
const index = userResources.pci.global.findIndex((availEelement) => deviceName.includes(availEelement.match));
const index = userResources.pci.global.findIndex((availEelement) => name.includes(availEelement.match));
if (index >= 0) { // device resource is in the user's global list then increment it by 1
userResources.pci.global[index].used++;
userResources.pci.global[index].avail--;
}
}
const index = userResources.pci.total.findIndex((availEelement) => deviceName.includes(availEelement.match));
const index = userResources.pci.total.findIndex((availEelement) => name.includes(availEelement.match));
if (index >= 0) {
userResources.pci.total[index].used++;
userResources.pci.total[index].avail--;
}
}
}
}
return userResources;
}