diff --git a/db.js b/db.js index 0731fa0..bd196b3 100644 --- a/db.js +++ b/db.js @@ -9,7 +9,7 @@ let db = {}; */ function init () { try { - db = fs.readFileSync(filename); + db = JSON.parse(fs.readFileSync(filename)); } catch { fs.writeFileSync(filename, JSON.stringify(db)); @@ -23,12 +23,13 @@ function init () { * @returns {boolean} whether the user is approved to allocate requested resources */ function requestResources (user, resources) { - Object.keys(db[user]).forEach((element) => { - if (db[user][element] < resources[element]) { - return false; + let approved = true; + Object.keys(resources).forEach((element) => { + if (db[user][element] - resources[element] < 0) { + approved = false; } }); - return true; + return approved; } /** @@ -40,16 +41,16 @@ function requestResources (user, resources) { function allocateResources (user, resources) { let newdb = {}; Object.assign(newdb, db); - Object.keys(db[user]).forEach((element) => { - newdb[user][element] -= resource[element]; + Object.keys(resources).forEach((element) => { + newdb[user][element] -= resources[element]; }); try { - fs.writeFileSync(filename, newdb); + fs.writeFileSync(filename, JSON.stringify(newdb)); Object.assign(db, newdb); return true; } catch { - fs.writeFileSync(filename, db) + fs.writeFileSync(filename, JSON.stringify(db)) return false; } } @@ -63,18 +64,18 @@ function allocateResources (user, resources) { function releaseResources (user, resources) { let newdb = {}; Object.assign(newdb, db); - Object.keys(db[user]).forEach((element) => { - newdb[user][element] += resource[element]; + Object.keys(resources).forEach((element) => { + newdb[user][element] += resources[element]; }); try { - fs.writeFileSync(filename, newdb); + fs.writeFileSync(filename, JSON.stringify(newdb)); Object.assign(db, newdb); return true; } catch { - fs.writeFileSync(filename, db) + fs.writeFileSync(filename, JSON.stringify(db)) return false; } } -module.exports = {init, requestResources, releaseResources}; \ No newline at end of file +module.exports = {init, requestResources, allocateResources, releaseResources}; \ No newline at end of file diff --git a/main.js b/main.js index 5bb4902..86d592a 100644 --- a/main.js +++ b/main.js @@ -4,11 +4,11 @@ const cookieParser = require("cookie-parser") const cors = require("cors"); const helmet = require("helmet"); const morgan = require("morgan"); -const axios = require('axios'); var api = require("./package.json"); -const {pveAPI, pveAPIToken, listenPort, domain} = require("./vars.js"); -const {init, requestResources, releaseResources} = require("./db.js"); +const {pveAPIToken, listenPort, domain} = require("./vars.js"); +const {checkAuth, requestPVE, handleResponse, getUnusedDiskData, getDiskByConfig} = require("./pveutils.js"); +const {init, requestResources, allocateResources, releaseResources} = require("./db.js"); const app = express(); app.use(helmet()); @@ -48,7 +48,7 @@ app.post("/api/disk/detach", async (req, res) => { let auth = await checkAuth(req.cookies, vmpath); if (!auth) { - res.send({auth: auth}); + res.status(401).send({auth: auth}); return; } @@ -65,7 +65,7 @@ app.post("/api/disk/attach", async (req, res) => { let auth = await checkAuth(req.cookies, vmpath); if (!auth) { - res.send({auth: auth}); + res.status(401).send({auth: auth}); return; } @@ -84,7 +84,19 @@ app.post("/api/disk/resize", async (req, res) => { let auth = await checkAuth(req.cookies, vmpath); if (!auth) { - res.send({auth: auth}); + res.status(401).send({auth: auth}); + return; + } + + let diskConfig = await getDiskByConfig(req.body.node, req.body.type, req.body.vmid, req.body.disk); + if (!diskConfig) { + res.status(500).send({auth: auth, data:{error: `requested disk ${req.body.disk} does not exist`}}); + } + let storage = diskConfig.split(":")[0]; + let request = {}; + request[storage] = req.body.size; + if (!requestResources(req.cookies.username, request)) { + res.status(500).send({auth: auth, data:{request: request, error: `${storage} could not fulfill request`}}); return; } @@ -92,7 +104,10 @@ app.post("/api/disk/resize", async (req, res) => { let result = await requestPVE(`${vmpath}/resize`, "PUT", req.cookies, action, pveAPIToken); result = await handleResponse(req.body.node, result); - res.send({auth: auth, status: result.status, data: result.data}); + if (result.status === 200) { + allocateResources(req.cookies.username, request); + } + res.status(result.status).send({auth: auth, data: result.data}); }); app.post("/api/disk/move", async (req, res) => { @@ -116,7 +131,7 @@ app.post("/api/disk/move", async (req, res) => { let result = await requestPVE(`${vmpath}/${route}`, "POST", req.cookies, action, pveAPIToken); result = await handleResponse(req.body.node, result); - res.send({auth: auth, status: result.status, data: result.data}); + res.send({auth: auth, data: result.data}, result.status); }); app.post("/api/disk/delete", async (req, res) => { @@ -133,7 +148,7 @@ app.post("/api/disk/delete", async (req, res) => { 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.send({auth: auth, status: result.status, data: result.data}); + res.send({auth: auth, data: result.data}, result.status); }); app.post("/api/disk/create", async (req, res) => { @@ -160,7 +175,7 @@ app.post("/api/disk/create", async (req, res) => { 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.send({auth: auth, status: result.status, data: result.data}); + res.send({auth: auth, data: result.data}, result.status); }); app.post("/api/resources", async (req, res) => { @@ -176,7 +191,7 @@ app.post("/api/resources", async (req, res) => { action = JSON.stringify({cores: req.body.cores, memory: req.body.memory}); let result = await requestPVE(`${vmpath}/config`, method, req.cookies, action, pveAPIToken); result = await handleResponse(req.body.node, result); - res.send({auth: auth, status: result.status, data: result.data}); + res.send({auth: auth, data: result.data}, result.status); }); app.post("/api/instance", async (req, res) => { @@ -214,7 +229,7 @@ app.post("/api/instance", async (req, res) => { let result = await requestPVE(`/nodes/${req.body.node}/${req.body.type}`, "POST", req.cookies, action, pveAPIToken); result = await handleResponse(req.body.node, result); - res.send({auth: auth, status: result.status, data: result.data}); + res.send({auth: auth, data: result.data}, result.status); }); app.delete("/api/instance", async (req, res) => { @@ -228,74 +243,9 @@ app.delete("/api/instance", async (req, res) => { let result = await requestPVE(`${vmpath}`, "DELETE", req.cookies, null, pveAPIToken); result = await handleResponse(req.body.node, result); - res.send({auth: auth, status: result.status, data: result.data}); + res.send({auth: auth, data: result.data}, result.status); }); -async function checkAuth (cookies, vmpath = null) { - if (vmpath) { - let result = await requestPVE(`/${vmpath}/config`, "GET", cookies); - return 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; - } -} - -async function requestPVE (path, method, cookies, body = null, token = null) { - let url = `${pveAPI}${path}`; - let content = { - method: method, - mode: "cors", - credentials: "include", - headers: { - "Content-Type": "application/x-www-form-urlencoded" - }, - } - - if (token) { - content.headers.Authorization = `PVEAPIToken=${token.user}@${token.realm}!${token.id}=${token.uuid}`; - } - else if (cookies) { - content.headers.CSRFPreventionToken = cookies.CSRFPreventionToken; - content.headers.Cookie = `PVEAuthCookie=${cookies.PVEAuthCookie}; CSRFPreventionToken=${cookies.CSRFPreventionToken}`; - } - - if (body) { - content.data = JSON.parse(body); - } - - try { - let response = await axios.request(url, content); - return response; - } - catch (error) { - return error.response; - } -} - -async function handleResponse (node, response) { - const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay)); - if (response.data.data) { - let upid = response.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 taskStatus; - } - else if (taskStatus.data.data.status === "stopped") { - return taskStatus; - } - else { - await waitFor(1000); - } - } - } - else { - return response; - } -} - app.listen(listenPort, () => { init(); console.log(`proxmoxaas-api v${api.version} listening on port ${listenPort}`); diff --git a/pveutils.js b/pveutils.js new file mode 100644 index 0000000..e825f79 --- /dev/null +++ b/pveutils.js @@ -0,0 +1,86 @@ +const axios = require('axios'); +const {pveAPI, pveAPIToken} = require("./vars.js"); + +async function checkAuth (cookies, vmpath = null) { + if (vmpath) { + let result = await requestPVE(`/${vmpath}/config`, "GET", cookies); + return 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; + } +} + +async function requestPVE (path, method, cookies, body = null, token = null) { + let url = `${pveAPI}${path}`; + let content = { + method: method, + mode: "cors", + credentials: "include", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + } + + if (token) { + content.headers.Authorization = `PVEAPIToken=${token.user}@${token.realm}!${token.id}=${token.uuid}`; + } + else if (cookies) { + content.headers.CSRFPreventionToken = cookies.CSRFPreventionToken; + content.headers.Cookie = `PVEAuthCookie=${cookies.PVEAuthCookie}; CSRFPreventionToken=${cookies.CSRFPreventionToken}`; + } + + if (body) { + content.data = JSON.parse(body); + } + + try { + let response = await axios.request(url, content); + return response; + } + catch (error) { + return error.response; + } +} + +async function handleResponse (node, response) { + const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay)); + if (response.data.data) { + let upid = response.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}; + } + else if (taskStatus.data.data.status === "stopped") { + return {status: 500, data: taskStatus.data.data}; + } + else { + await waitFor(1000); + } + } + } + else { + return response; + } +} + +async function getUnusedDiskData (node, disk) { + let storageID = disk.split(":")[0]; + let storageData = await requestPVE(`/nodes/${node}/storage/${storageID}/content`, "GET", null, null, pveAPIToken); + storageData.data.forEach((element) => { + if (element.volid === disk) { + return element; + } + }); + return null; +} + +async function getDiskByConfig (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, getDiskByConfig}; \ No newline at end of file