prepare pve backend and utils for paas fabric
This commit is contained in:
parent
d080e71601
commit
cfcf08b373
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
188
src/utils.js
188
src/utils.js
@ -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,62 +144,74 @@ 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;
|
||||
// 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;
|
||||
}
|
||||
// otherwise add the resource to the global pool
|
||||
else {
|
||||
userResources[storage].global.used += val;
|
||||
userResources[storage].global.avail -= val;
|
||||
}
|
||||
userResources[storage].total.used += val;
|
||||
userResources[storage].total.avail -= val;
|
||||
}
|
||||
}
|
||||
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 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.network.nodes) {
|
||||
userResources.network.nodes[nodeName].used += val;
|
||||
userResources.network.nodes[nodeName].avail -= val;
|
||||
if (nodeName in userResources[storage].nodes) {
|
||||
userResources[storage].nodes[nodeName].used += size;
|
||||
userResources[storage].nodes[nodeName].avail -= size;
|
||||
}
|
||||
// otherwise add the resource to the global pool
|
||||
else {
|
||||
userResources.network.global.used += val;
|
||||
userResources.network.global.avail -= val;
|
||||
userResources[storage].global.used += size;
|
||||
userResources[storage].global.avail -= size;
|
||||
}
|
||||
userResources.network.total.used += val;
|
||||
userResources.network.total.avail -= val;
|
||||
userResources[storage].total.used += size;
|
||||
userResources[storage].total.avail -= size;
|
||||
}
|
||||
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
|
||||
if (nodeName in userResources.pci.nodes) {
|
||||
const index = userResources.pci.nodes[nodeName].findIndex((availEelement) => deviceName.includes(availEelement.match));
|
||||
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));
|
||||
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));
|
||||
userResources.pci.total[index].used++;
|
||||
userResources.pci.total[index].avail--;
|
||||
}
|
||||
// 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 += rate;
|
||||
userResources.network.nodes[nodeName].avail -= rate;
|
||||
}
|
||||
// otherwise add the resource to the global pool
|
||||
else {
|
||||
userResources.network.global.used += rate;
|
||||
userResources.network.global.avail -= rate;
|
||||
}
|
||||
userResources.network.total.used += rate;
|
||||
userResources.network.total.avail -= rate;
|
||||
}
|
||||
}
|
||||
// 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) => 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) => 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) => name.includes(availEelement.match));
|
||||
if (index >= 0) {
|
||||
userResources.pci.total[index].used++;
|
||||
userResources.pci.total[index].avail--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user