implement new db strategy
Signed-off-by: Arthur Lu <learthurgo@gmail.com>
This commit is contained in:
parent
943d598b69
commit
2ef4bfd3d2
105
db.js
105
db.js
@ -16,92 +16,25 @@ function init () {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* user requests additional resources specified in k-v pairs
|
||||
* @param {string} user user's proxmox username in the form username@authrealm
|
||||
* @param {Object} resources k-v pairs with resource name as keys and resource ammount as values
|
||||
* @returns {boolean} whether the user is approved to allocate requested resources
|
||||
*/
|
||||
function requestResources (user, resources) {
|
||||
let approved = true;
|
||||
Object.keys(resources).forEach((element) => {
|
||||
if(!(element in db[user].available)) { // if the resource does not exist in the user's entry, assume the user is not allowed to use it
|
||||
approved = false;
|
||||
}
|
||||
else if (db[user].available[element] - resources[element] < 0) {
|
||||
approved = false;
|
||||
}
|
||||
function getResourceMeta () {
|
||||
return db.resources;
|
||||
}
|
||||
|
||||
function getUserMax (username) {
|
||||
return db.users[username].maximum;
|
||||
}
|
||||
|
||||
function getResourceUnits () {
|
||||
return db.units;
|
||||
}
|
||||
|
||||
function putUserResources (username, used) {
|
||||
let userEntry = db.users[username];
|
||||
userEntry.used = used;
|
||||
userEntry.avail = {};
|
||||
Object.keys(max).forEach((k) => {
|
||||
userEntry.avail[k] = max[k] - used[k];
|
||||
});
|
||||
return approved;
|
||||
}
|
||||
|
||||
/**
|
||||
* user allocates additional resources specified in k-v pairs
|
||||
* @param {string} user user's proxmox username in the form username@authrealm
|
||||
* @param {Object} resources k-v pairs with resource name as keys and resource ammount as values
|
||||
* @returns {boolean} true if resources were successfully allocated, false otherwise
|
||||
*/
|
||||
function allocateResources (user, resources) {
|
||||
let newdb = {};
|
||||
Object.assign(newdb, db);
|
||||
Object.keys(resources).forEach((element) => {
|
||||
if(typeof(resources[element]) === "number" && isFinite(resources[element])) {
|
||||
newdb[user].available[element] -= resources[element];
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
try {
|
||||
fs.writeFileSync(filename, JSON.stringify(newdb));
|
||||
Object.assign(db, newdb);
|
||||
return true;
|
||||
}
|
||||
catch {
|
||||
fs.writeFileSync(filename, JSON.stringify(db))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* user releases allocated resources specified in k-v pairs
|
||||
* @param {string} user user's proxmox username in the form username@authrealm
|
||||
* @param {Object} resources k-v pairs with resource name as keys and resource ammount as values
|
||||
* @returns {boolean} true if resources were successfully deallocated, false otherwise
|
||||
*/
|
||||
function releaseResources (user, resources) {
|
||||
let newdb = {};
|
||||
Object.assign(newdb, db);
|
||||
Object.keys(resources).forEach((element) => {
|
||||
if(typeof(resources[element]) === "number" && isFinite(resources[element]) && resources[element]) {
|
||||
newdb[user].available[element] += resources[element];
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
try {
|
||||
fs.writeFileSync(filename, JSON.stringify(newdb));
|
||||
Object.assign(db, newdb);
|
||||
return true;
|
||||
}
|
||||
catch {
|
||||
fs.writeFileSync(filename, JSON.stringify(db))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return a read only copy of the user resources
|
||||
* @param {string} user user's proxmox username in the form username@authrealm
|
||||
* @returns {Object} user's remaining resources as k-v pairs with resource name as keys and resource ammount as values
|
||||
*/
|
||||
function getResources (user) {
|
||||
let returnVal = {};
|
||||
if(user in db) {
|
||||
Object.assign(returnVal, db[user]);
|
||||
}
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
module.exports = {init, requestResources, allocateResources, releaseResources, getResources};
|
||||
module.exports = {init, getResourceMeta, getUserMax, getResourceUnits, putUserResources};
|
62
main.js
62
main.js
@ -7,8 +7,8 @@ const morgan = require("morgan");
|
||||
var api = require("./package.json");
|
||||
|
||||
const {pveAPIToken, listenPort, domain} = require("./vars.js");
|
||||
const {checkAuth, requestPVE, handleResponse, getUnusedDiskData, getDiskConfig} = require("./pveutils.js");
|
||||
const {init, requestResources, allocateResources, releaseResources, getResources} = require("./db.js");
|
||||
const {checkAuth, requestPVE, handleResponse, getUsedResources} = require("./pveutils.js");
|
||||
const {init, getResourceMeta, getUserMax, getResourceUnits} = require("./db.js");
|
||||
|
||||
const app = express();
|
||||
app.use(helmet());
|
||||
@ -19,82 +19,68 @@ app.use(morgan("combined"));
|
||||
|
||||
|
||||
app.get("/api/version", (req, res) => {
|
||||
res.send({version: api.version});
|
||||
res.status(200).send({version: api.version});
|
||||
});
|
||||
|
||||
app.get("/api/echo", (req, res) => {
|
||||
res.send({body: req.body, cookies: req.cookies});
|
||||
res.status(200).send({body: req.body, cookies: req.cookies});
|
||||
});
|
||||
|
||||
app.get("/api/auth", async (req, res) => {
|
||||
let result = await checkAuth(req.cookies);
|
||||
res.send({auth: result});
|
||||
await checkAuth(req.cookies);
|
||||
res.status(200).send({auth: true});
|
||||
});
|
||||
|
||||
app.get("/api/proxmox/*", async (req, res) => { // proxy endpoint for GET proxmox api with no token
|
||||
path = req.url.replace("/api/proxmox", "");
|
||||
let result = await requestPVE(path, "GET", req.cookies);
|
||||
res.send(result.data, result.status);
|
||||
res.status(result.status).send(result.data);
|
||||
});
|
||||
|
||||
app.post("/api/proxmox/*", async (req, res) => { // proxy endpoint for POST proxmox api with no token
|
||||
path = req.url.replace("/api/proxmox", "");
|
||||
let result = await requestPVE(path, "POST", req.cookies, JSON.stringify(req.body)); // need to stringify body because of other issues
|
||||
res.send(result.data, result.status);
|
||||
res.status(result.status).send(result.data);
|
||||
});
|
||||
|
||||
app.get("/api/user/resources", async(req, res) => {
|
||||
let auth = await checkAuth(req.cookies);
|
||||
if (!auth) {
|
||||
res.status(401).send({auth: auth});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).send({resources: getResources(req.cookies.username)});
|
||||
await checkAuth(req.cookies, res);
|
||||
let rm = getResourceMeta();
|
||||
let used = await getUsedResources(req, rm);
|
||||
let max = await getUserMax(req.cookies.username);
|
||||
avail = {};
|
||||
Object.keys(max).forEach((k) => {
|
||||
avail[k] = max[k] - used[k];
|
||||
});
|
||||
let units = getResourceUnits();
|
||||
res.status(200).send({used: used, maximum: max, available: avail, units: units});
|
||||
return;
|
||||
});
|
||||
|
||||
app.post("/api/disk/detach", async (req, res) => {
|
||||
let vmpath = `/nodes/${req.body.node}/${req.body.type}/${req.body.vmid}`;
|
||||
|
||||
// check auth
|
||||
let auth = await checkAuth(req.cookies, vmpath);
|
||||
if (!auth) {
|
||||
res.status(401).send({auth: auth});
|
||||
return;
|
||||
}
|
||||
|
||||
await checkAuth(req.cookies, res);
|
||||
if (req.body.disk.includes("unused")) {
|
||||
res.status(500).send({auth: auth, data:{error: `Requested disk ${req.body.disk} cannot be unused. Use /disk/delete to permanently delete unused disks.`}});
|
||||
return;
|
||||
}
|
||||
|
||||
let action = JSON.stringify({delete: req.body.disk});
|
||||
let method = req.body.type === "qemu" ? "POST" : "PUT";
|
||||
let result = await requestPVE(`${vmpath}/config`, method, req.cookies, action, pveAPIToken);
|
||||
result = await handleResponse(req.body.node, result);
|
||||
res.status(result.status).send({auth: auth, data: result.data});
|
||||
await handleResponse(req.body.node, result, res);
|
||||
});
|
||||
|
||||
app.post("/api/disk/attach", async (req, res) => {
|
||||
let vmpath = `/nodes/${req.body.node}/${req.body.type}/${req.body.vmid}`;
|
||||
|
||||
// check auth
|
||||
let auth = await checkAuth(req.cookies, vmpath);
|
||||
if (!auth) {
|
||||
res.status(401).send({auth: auth});
|
||||
return;
|
||||
}
|
||||
|
||||
await checkAuth(req.cookies, res);
|
||||
let action = {};
|
||||
action[req.body.disk] = req.body.data;
|
||||
action = JSON.stringify(action);
|
||||
let method = req.body.type === "qemu" ? "POST" : "PUT";
|
||||
let result = await requestPVE(`${vmpath}/config`, method, req.cookies, action, pveAPIToken);
|
||||
result = await handleResponse(req.body.node, result);
|
||||
res.status(result.status).send({auth: auth, data: result.data});
|
||||
await handleResponse(req.body.node, result, res);
|
||||
});
|
||||
|
||||
/*
|
||||
app.post("/api/disk/resize", async (req, res) => {
|
||||
let vmpath = `/nodes/${req.body.node}/${req.body.type}/${req.body.vmid}`;
|
||||
|
||||
@ -401,7 +387,7 @@ app.delete("/api/instance", async (req, res) => {
|
||||
}
|
||||
|
||||
res.status(result.status).send({auth: auth, data: result.data, deallocated: release});
|
||||
});
|
||||
});*/
|
||||
|
||||
app.listen(listenPort, () => {
|
||||
init();
|
||||
|
86
pveutils.js
86
pveutils.js
@ -1,14 +1,20 @@
|
||||
const axios = require('axios');
|
||||
const {pveAPI, pveAPIToken} = require("./vars.js");
|
||||
|
||||
async function checkAuth (cookies, vmpath = null) {
|
||||
async function checkAuth (cookies, res, vmpath = null) {
|
||||
let auth = false;
|
||||
if (vmpath) {
|
||||
let result = await requestPVE(`/${vmpath}/config`, "GET", cookies);
|
||||
return result.status === 200;
|
||||
auth = result.status === 200;
|
||||
}
|
||||
else { // if no path is specified, then do a simple authentication
|
||||
let result = await requestPVE("/version", "GET", cookies);
|
||||
return result.status === 200;
|
||||
auth = result.status === 200;
|
||||
}
|
||||
if (!auth) {
|
||||
res.status(401).send({auth: auth});
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,17 +50,21 @@ async function requestPVE (path, method, cookies, body = null, token = null) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleResponse (node, response) {
|
||||
async function handleResponse (node, result, res) {
|
||||
const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay));
|
||||
if (response.data.data) {
|
||||
let upid = response.data.data;
|
||||
if (result.data.data) {
|
||||
let upid = result.data.data;
|
||||
while (true) {
|
||||
let taskStatus = await requestPVE(`/nodes/${node}/tasks/${upid}/status`, "GET", null, null, pveAPIToken);
|
||||
if (taskStatus.data.data.status === "stopped" && taskStatus.data.data.exitstatus === "OK") {
|
||||
return {status: 200, data: taskStatus.data.data};
|
||||
res.status(200).send(taskStatus.data.data);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
else if (taskStatus.data.data.status === "stopped") {
|
||||
return {status: 500, data: taskStatus.data.data};
|
||||
res.status(500).send(taskStatus.data.data);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
await waitFor(1000);
|
||||
@ -62,28 +72,58 @@ async function handleResponse (node, response) {
|
||||
}
|
||||
}
|
||||
else {
|
||||
return response;
|
||||
res.status(result.status).send(result.data);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function getUnusedDiskData (node, type, vmid, disk) {
|
||||
let diskDataConfig = await getDiskConfig(node, type, vmid, disk);
|
||||
let storageID = diskDataConfig.split(":")[0];
|
||||
async function getUsedResources (req, resourceMeta) {
|
||||
let response = await requestPVE("/cluster/resources", "GET", req.cookies);
|
||||
let used = {};
|
||||
let diskprefixes = [];
|
||||
for (let resourceName of Object.keys(resourceMeta)) {
|
||||
if (resourceMeta[resourceName].type === "numeric") {
|
||||
used[resourceName] = 0;
|
||||
}
|
||||
else if (resourceMeta[resourceName].type === "disk") {
|
||||
resourceMeta[resourceName].storages.forEach((element) => {
|
||||
used[element] = 0;
|
||||
});
|
||||
diskprefixes.push(resourceName);
|
||||
}
|
||||
}
|
||||
for (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", req.cookies);
|
||||
config = config.data.data;
|
||||
for (key of Object.keys(config)) {
|
||||
if (Object.keys(used).includes(key) && resourceMeta[key].type === "numeric") {
|
||||
used[key] += config[key];
|
||||
}
|
||||
else if (diskprefixes.some(prefix => key.startsWith(prefix))) {
|
||||
let diskInfo = await getDiskInfo(instance.node, instance.type, instance.vmid, key);
|
||||
used[diskInfo.storage] += diskInfo.size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return used;
|
||||
}
|
||||
|
||||
async function getDiskInfo (node, type, vmid, disk) {
|
||||
let config = await requestPVE(`/nodes/${node}/${type}/${vmid}/config`, "GET", null, null, pveAPIToken);
|
||||
let storageID = config.data.data[disk].split(":")[0];
|
||||
let volIDTarget = config.data.data[disk].split(",")[0];
|
||||
let storageData = await requestPVE(`/nodes/${node}/storage/${storageID}/content`, "GET", null, null, pveAPIToken);
|
||||
let diskDataStorage = null;
|
||||
let diskInfo = null;
|
||||
storageData.data.data.forEach((element) => {
|
||||
if (element.volid === diskDataConfig) {
|
||||
if (element.volid === volIDTarget) {
|
||||
element.storage = storageID;
|
||||
diskDataStorage = element;
|
||||
diskInfo = element;
|
||||
}
|
||||
});
|
||||
return diskDataStorage;
|
||||
return diskInfo;
|
||||
}
|
||||
|
||||
async function getDiskConfig (node, type, vmid, disk) {
|
||||
let config = await requestPVE(`/nodes/${node}/${type}/${vmid}/config`, "GET", null, null, pveAPIToken);
|
||||
|
||||
return config.data.data[disk];
|
||||
}
|
||||
|
||||
module.exports = {checkAuth, requestPVE, handleResponse, getUnusedDiskData, getDiskConfig};
|
||||
module.exports = {checkAuth, requestPVE, handleResponse, getUsedResources};
|
Loading…
Reference in New Issue
Block a user