various code cleanup and commenting

This commit is contained in:
2026-05-26 22:35:56 +00:00
parent 46295fabde
commit af2194a8b3
14 changed files with 229 additions and 123 deletions
+1 -1
View File
@@ -17,5 +17,5 @@ GET {{baseUrl}}/access/pools/
### Get pool ### Get pool
GET {{baseUrl}}/access/pools/{{poolname}} GET {{baseUrl}}/access/pools/{{poolname}}
### Get all pools ### Get all nodes
GET {{baseUrl}}/cluster/nodes GET {{baseUrl}}/cluster/nodes
+3
View File
@@ -8,6 +8,9 @@ username={{username}}
### Get instance resources ### Get instance resources
GET {{baseUrl}}/cluster/{{testvmpath}} GET {{baseUrl}}/cluster/{{testvmpath}}
### Get instance backups
GET {{baseUrl}}/cluster/{{testvmpath}}/backup
### Test create instance ### Test create instance
POST {{baseUrl}}/cluster/{{testvmpath}}/create POST {{baseUrl}}/cluster/{{testvmpath}}/create
+15 -1
View File
@@ -62,9 +62,11 @@ router.post("/ticket", async (req, res) => {
password: req.body.password password: req.body.password
}; };
const domain = global.config.application.domain; // get user and user backends from config
const userObj = global.utils.getUserObjFromUsername(params.username); const userObj = global.utils.getUserObjFromUsername(params.username);
const backends = [global.config.handlers.users, global.config.handlers.instance]; const backends = [global.config.handlers.users, global.config.handlers.instance];
// fetch cookies using cookie fetcher
const cm = new CookieFetcher(); const cm = new CookieFetcher();
const error = await cm.fetchBackends(backends, userObj, params.password); const error = await cm.fetchBackends(backends, userObj, params.password);
if (error) { if (error) {
@@ -72,6 +74,11 @@ router.post("/ticket", async (req, res) => {
return; return;
} }
const cookies = cm.exportCookies(); const cookies = cm.exportCookies();
// get global config domain name
const domain = global.config.application.domain;
// for each cookie, add the cookie to response and also compute the minimum across all cookies
let minimumExpires = Infinity; let minimumExpires = Infinity;
for (const cookie of cookies) { for (const cookie of cookies) {
const expiresDate = new Date(Date.now() + cookie.expiresMSFromNow); const expiresDate = new Date(Date.now() + cookie.expiresMSFromNow);
@@ -80,6 +87,8 @@ router.post("/ticket", async (req, res) => {
minimumExpires = cookie.expiresMSFromNow; minimumExpires = cookie.expiresMSFromNow;
} }
} }
// set username and auth cookie with the minimum cookie length
const expiresDate = new Date(Date.now() + minimumExpires); const expiresDate = new Date(Date.now() + minimumExpires);
res.cookie("username", params.username, { domain, path: "/", secure: true, expires: expiresDate, sameSite: "none" }); res.cookie("username", params.username, { domain, path: "/", secure: true, expires: expiresDate, sameSite: "none" });
res.cookie("auth", 1, { domain, path: "/", secure: true, expires: expiresDate, sameSite: "none" }); res.cookie("auth", 1, { domain, path: "/", secure: true, expires: expiresDate, sameSite: "none" });
@@ -92,15 +101,20 @@ router.post("/ticket", async (req, res) => {
* - 200: {auth: false} * - 200: {auth: false}
*/ */
router.delete("/ticket", async (req, res) => { router.delete("/ticket", async (req, res) => {
// must have cookies to delete, otherwise just return ok
if (Object.keys(req.cookies).length === 0) { if (Object.keys(req.cookies).length === 0) {
res.status(200).send({ auth: false }); res.status(200).send({ auth: false });
return; return;
} }
// for each cookie, set the expire date to 0
const domain = global.config.application.domain; const domain = global.config.application.domain;
const expire = new Date(0); const expire = new Date(0);
for (const cookie in req.cookies) { for (const cookie in req.cookies) {
res.cookie(cookie, "", { domain, path: "/", expires: expire, secure: true, sameSite: "none" }); res.cookie(cookie, "", { domain, path: "/", expires: expire, secure: true, sameSite: "none" });
} }
// call close session on each backend, even if was not used
await global.pve.closeSession(req.cookies); await global.pve.closeSession(req.cookies);
await global.access.closeSession(req.cookies); await global.access.closeSession(req.cookies);
res.status(200).send({ auth: false }); res.status(200).send({ auth: false });
+9 -5
View File
@@ -1,8 +1,6 @@
import { Router } from "express"; import { Router } from "express";
export const router = Router({ mergeParams: true }); export const router = Router({ mergeParams: true });
const checkAuth = global.utils.checkAuth;
/** /**
* GET - get specific group * GET - get specific group
* request: * request:
@@ -16,18 +14,24 @@ router.get("/:groupname", async (req, res) => {
groupname: req.params.groupname groupname: req.params.groupname
}; };
// check auth // check auth
const auth = await checkAuth(req.cookies, res); const auth = await global.utils.checkAuth(req.cookies, res);
if (!auth) { if (!auth) {
return; return;
} }
// attempt to parse group from groupname
const groupObj = global.utils.getGroupObjFromGroupname(params.groupname); const groupObj = global.utils.getGroupObjFromGroupname(params.groupname);
if (groupObj == null) {
res.status(400).send({ auth: true, error:`Groupname ${params.groupname} does not match format gid-realm or gid.` });
}
// get group
const g = await global.access.getGroup(groupObj, req.cookies); const g = await global.access.getGroup(groupObj, req.cookies);
if (g.ok !== true) { if (g.ok !== true) {
res.status(g.status).send(g); res.status(g.status).send({ auth:true, error:g });
return; return;
} }
const group = g.group; const group = g.group;
res.status(200).send({ group }); res.status(200).send({ auth:true, group });
}); });
+15 -13
View File
@@ -1,9 +1,6 @@
import { Router } from "express"; import { Router } from "express";
export const router = Router({ mergeParams: true }); export const router = Router({ mergeParams: true });
const checkAuth = global.utils.checkAuth;
const checkUserInPool = global.utils.checkUserInPool;
/** /**
* GET - get all available cluster pools * GET - get all available cluster pools
* returns only pool IDs * returns only pool IDs
@@ -13,27 +10,30 @@ const checkUserInPool = global.utils.checkUserInPool;
*/ */
router.get("/", async (req, res) => { router.get("/", async (req, res) => {
// check auth // check auth
const auth = await checkAuth(req.cookies, res); const auth = await global.utils.checkAuth(req.cookies, res);
if (!auth) { if (!auth) {
return; return;
} }
// get user object
const userObj = global.utils.getUserObjFromUsername(req.cookies.username); const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
const pools = {}; // get all pool names using api token
const poolnames = await global.pve.requestPVE("/pools", "GET", { token: true }); const poolnames = await global.pve.requestPVE("/pools", "GET", { token: true });
// setup pools (return value)
const pools = {};
// for each poolname
for (const poolpartial of poolnames.data) { for (const poolpartial of poolnames.data) {
const poolname = poolpartial.poolid; const poolname = poolpartial.poolid;
// get the pool
const p = await global.access.getPool(poolname, req.cookies); const p = await global.access.getPool(poolname, req.cookies);
if (p.ok !== true) { if (p.ok !== true) {
continue; continue;
} }
const pool = p.pool; const pool = p.pool;
// if user is in the pool, add it to pools (return value)
if (checkUserInPool(pool, userObj)) { if (global.utils.checkUserInPool(pool, userObj)) {
const resources = await global.utils.getPoolResources(req, poolname); const resources = await global.utils.getPoolResources(req, poolname);
pool.resources = resources; pool.resources = resources;
pools[poolname] = pool; pools[poolname] = pool;
@@ -57,20 +57,22 @@ router.get("/:poolname", async (req, res) => {
poolname: req.params.poolname poolname: req.params.poolname
}; };
// check auth // check auth
const auth = await checkAuth(req.cookies, res); const auth = await global.utils.checkAuth(req.cookies, res);
if (!auth) { if (!auth) {
return; return;
} }
// get pool
const p = await global.access.getPool(params.poolname, req.cookies); const p = await global.access.getPool(params.poolname, req.cookies);
if (p.ok !== true) { if (p.ok !== true) {
res.status(p.status).send(p); res.status(p.status).send({ auth:true, error: p });
return; return;
} }
const pool = p.pool; const pool = p.pool;
// get resources
const resources = await global.utils.getPoolResources(req, params.poolname); const resources = await global.utils.getPoolResources(req, params.poolname);
// append resources to pool
pool.resources = resources; pool.resources = resources;
res.status(200).send({ pool }); res.status(200).send({ auth: true, pool });
}); });
+8 -4
View File
@@ -1,8 +1,6 @@
import { Router } from "express"; import { Router } from "express";
export const router = Router({ mergeParams: true }); export const router = Router({ mergeParams: true });
const checkAuth = global.utils.checkAuth;
/** /**
* GET - get specific user * GET - get specific user
* request: * request:
@@ -16,15 +14,21 @@ router.get("/:username", async (req, res) => {
username: req.params.username username: req.params.username
}; };
// check auth // check auth
const auth = await checkAuth(req.cookies, res); const auth = await global.utils.checkAuth(req.cookies, res);
if (!auth) { if (!auth) {
return; return;
} }
// attempt to parse user from username
const userObj = global.utils.getUserObjFromUsername(params.username); const userObj = global.utils.getUserObjFromUsername(params.username);
if (userObj == null) {
res.status(400).send({ auth:true, error:`username ${params.username} does not match format uid@realm.` });
}
// get user
const u = await global.access.getUser(userObj, req.cookies); const u = await global.access.getUser(userObj, req.cookies);
if (u.ok !== true) { if (u.ok !== true) {
res.status(u.status).send(u); res.status(u.status).send({ auth: true, error: u });
return; return;
} }
const user = u.user; const user = u.user;
+24 -18
View File
@@ -1,10 +1,6 @@
import { Router } from "express"; import { Router } from "express";
export const router = Router({ mergeParams: true }); export const router = Router({ mergeParams: true });
const checkAuth = global.utils.checkAuth;
const approveResources = global.utils.approveResources;
const checkUserInPool = global.utils.checkUserInPool;
const nodeRegexP = "[\\w-]+"; const nodeRegexP = "[\\w-]+";
const typeRegexP = "qemu|lxc"; const typeRegexP = "qemu|lxc";
const vmidRegexP = "\\d+"; const vmidRegexP = "\\d+";
@@ -23,13 +19,13 @@ global.utils.recursiveImportRoutes(router, basePath, "cluster", import.meta.url)
*/ */
router.get("/nodes", async (req, res) => { router.get("/nodes", async (req, res) => {
// check auth // check auth
const auth = await checkAuth(req.cookies, res); const auth = await global.utils.checkAuth(req.cookies, res);
if (!auth) { if (!auth) {
return; return;
} }
// get all nodes
const allNodes = await global.pve.requestPVE("/nodes", "GET", { cookies: req.cookies }); const allNodes = await global.pve.requestPVE("/nodes", "GET", { cookies: req.cookies });
if (allNodes.status === 200) { if (allNodes.status === 200) {
const allNodesIDs = Array.from(allNodes.data, (x) => x.node); const allNodesIDs = Array.from(allNodes.data, (x) => x.node);
res.status(allNodes.status).send({ nodes: allNodesIDs }); res.status(allNodes.status).send({ nodes: allNodesIDs });
@@ -60,14 +56,13 @@ router.get(`${basePath}`, async (req, res) => {
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
// get current config // get current config
const instance = await global.pve.getInstance(params.node, params.vmid); const instance = await global.pve.getInstance(params.node, params.vmid);
res.status(200).send(instance); res.status(200).send(instance);
}); });
@@ -99,11 +94,9 @@ router.post(`${basePath}/resources`, async (req, res) => {
boot: req.body.boot boot: req.body.boot
}; };
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
@@ -119,13 +112,16 @@ router.post(`${basePath}/resources`, async (req, res) => {
else if (params.type === "qemu") { else if (params.type === "qemu") {
request.cpu = params.proctype; request.cpu = params.proctype;
} }
// check resource approval // check resource approval
const { approved, reason } = await approveResources(req, userObj, params.node, instance.pool, request); const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
const { approved, reason } = await global.utils.approveResources(req, userObj, params.node, instance.pool, request);
if (!approved) { if (!approved) {
res.status(400).send({ request, error: "Not enough resources to satisfy request.", reason }); res.status(400).send({ request, error: "Not enough resources to satisfy request.", reason });
res.end(); res.end();
return; return;
} }
// setup action // setup action
const action = { cores: params.cores, memory: params.memory }; const action = { cores: params.cores, memory: params.memory };
if (params.type === "lxc") { if (params.type === "lxc") {
@@ -136,6 +132,7 @@ router.post(`${basePath}/resources`, async (req, res) => {
action.boot = `order=${params.boot.toString().replaceAll(",", ";")};`; action.boot = `order=${params.boot.toString().replaceAll(",", ";")};`;
} }
const method = params.type === "qemu" ? "POST" : "PUT"; const method = params.type === "qemu" ? "POST" : "PUT";
// commit action // commit action
const result = await global.pve.requestPVE(`${vmpath}/config`, method, { token: true }, action); const result = await global.pve.requestPVE(`${vmpath}/config`, method, { token: true }, action);
await global.pve.handleResponse(params.node, result, res); await global.pve.handleResponse(params.node, result, res);
@@ -181,36 +178,40 @@ router.post(`${basePath}/create`, async (req, res) => {
rootfssize: req.body.rootfssize rootfssize: req.body.rootfssize
}; };
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
// check auth // check auth
const auth = await checkAuth(req.cookies, res); const auth = await global.utils.checkAuth(req.cookies, res);
if (!auth) { if (!auth) {
return; return;
} }
// get pool config // get pool config
const pool = (await global.access.getPool(params.pool, req.cookies)).pool; const pool = (await global.access.getPool(params.pool, req.cookies)).pool;
const vmid = Number.parseInt(params.vmid); const vmid = Number.parseInt(params.vmid);
const vmidMin = pool["vmid-allowed"].min; const vmidMin = pool["vmid-allowed"].min;
const vmidMax = pool["vmid-allowed"].max; const vmidMax = pool["vmid-allowed"].max;
// check vmid is within allowed range // check vmid is within allowed range
if (vmid < vmidMin || vmid > vmidMax) { if (vmid < vmidMin || vmid > vmidMax) {
res.status(500).send({ error: `Requested vmid ${vmid} is out of allowed range [${vmidMin},${vmidMax}].` }); res.status(500).send({ error: `Requested vmid ${vmid} is out of allowed range [${vmidMin},${vmidMax}].` });
res.end(); res.end();
return; return;
} }
// check node is within allowed list // check node is within allowed list
if (pool["nodes-allowed"][params.node] !== true) { if (pool["nodes-allowed"][params.node] !== true) {
res.status(500).send({ error: `Requested node ${params.node} is not in allowed nodes [${pool["nodes-allowed"]}].` }); res.status(500).send({ error: `Requested node ${params.node} is not in allowed nodes [${pool["nodes-allowed"]}].` });
res.end(); res.end();
return; return;
} }
// check if user is in pool // check if user is in pool
if(checkUserInPool(pool, userObj) !== true) { const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
if(global.utils.checkUserInPool(pool, userObj) !== true) {
res.status(500).send({ error: `Requested pool ${params.pool} does not contain user ${req.cookies.username}]` }); res.status(500).send({ error: `Requested pool ${params.pool} does not contain user ${req.cookies.username}]` });
res.end(); res.end();
return; return;
} }
// setup request // setup request
const request = { const request = {
cores: Number(params.cores), cores: Number(params.cores),
@@ -231,13 +232,15 @@ router.post(`${basePath}/create`, async (req, res) => {
} }
} }
} }
// check resource approval // check resource approval
const { approved, reason } = await await approveResources(req, userObj, params.node, params.pool, request); const { approved, reason } = await await global.utils.approveResources(req, userObj, params.node, params.pool, request);
if (!approved) { if (!approved) {
res.status(400).send({ request, error: "Not enough resources to satisfy request.", reason }); res.status(400).send({ request, error: "Not enough resources to satisfy request.", reason });
res.end(); res.end();
return; return;
} }
// setup action by adding non resource values // setup action by adding non resource values
const action = { const action = {
vmid: params.vmid, vmid: params.vmid,
@@ -260,6 +263,7 @@ router.post(`${basePath}/create`, async (req, res) => {
else { else {
action.name = params.name; action.name = params.name;
} }
// commit action // commit action
const result = await global.pve.requestPVE(`/nodes/${params.node}/${params.type}`, "POST", { token: true }, action); const result = await global.pve.requestPVE(`/nodes/${params.node}/${params.type}`, "POST", { token: true }, action);
await global.pve.handleResponse(params.node, result, res); await global.pve.handleResponse(params.node, result, res);
@@ -283,12 +287,14 @@ router.delete(`${basePath}/delete`, async (req, res) => {
type: req.params.type, type: req.params.type,
vmid: req.params.vmid vmid: req.params.vmid
}; };
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
// commit action // commit action
const result = await global.pve.requestPVE(vmpath, "DELETE", { token: true }); const result = await global.pve.requestPVE(vmpath, "DELETE", { token: true });
await global.pve.handleResponse(params.node, result, res); await global.pve.handleResponse(params.node, result, res);
+28 -14
View File
@@ -1,8 +1,6 @@
import { Router } from "express"; import { Router } from "express";
export const router = Router({ mergeParams: true }); ; export const router = Router({ mergeParams: true }); ;
const checkAuth = global.utils.checkAuth;
/** /**
* GET - get backups for an instance * GET - get backups for an instance
* request: * request:
@@ -24,7 +22,7 @@ router.get("/", async (req, res) => {
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
@@ -32,11 +30,12 @@ router.get("/", async (req, res) => {
// get vm backups // get vm backups
const storage = global.config.backups.storage; const storage = global.config.backups.storage;
const backups = await global.pve.requestPVE(`/nodes/${params.node}/storage/${storage}/content?content=backup&vmid=${params.vmid}`, "GET", { token: true }); const backups = await global.pve.requestPVE(`/nodes/${params.node}/storage/${storage}/content?content=backup&vmid=${params.vmid}`, "GET", { token: true });
if (backups.status === 200) { if (backups.status === 200) {
res.status(backups.status).send(backups.data); res.status(backups.status).send(backups.data);
} }
else { else {
res.status(backups.status).send({ error: backups.statusText }); res.status(backups.status).send({ auth: true, error: backups.statusText });
} }
}); });
@@ -64,22 +63,37 @@ router.post("/", async (req, res) => {
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
// check if number of backups is less than the allowed number // get number of currently backups used
const storage = global.config.backups.storage; const storage = global.config.backups.storage;
const backups = await global.pve.requestPVE(`/nodes/${params.node}/storage/${storage}/content?content=backup&vmid=${params.vmid}`, "GET", { token: true }); const backups = await global.pve.requestPVE(`/nodes/${params.node}/storage/${storage}/content?content=backup&vmid=${params.vmid}`, "GET", { token: true });
const numBackups = backups.data.length;
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
const maxAllowed = (await global.access.getUser(userObj, req.cookies)).cluster.backups.max;
if (backups.status !== 200) { if (backups.status !== 200) {
res.status(backups.status).send({ error: backups.statusText }); res.status(backups.status).send({ error: backups.statusText });
return; return;
} }
else if (numBackups >= maxAllowed) { const numBackups = backups.data.length;
// get instance
const instance = await global.pve.getInstance(params.node, params.vmid);
if (instance === null) {
res.status(400).send({ error: `failed to get instance ${params.node}/${params.vmid}` });
return;
}
// get pool and pool allowed nodes
const pool = await global.access.getPool(instance.pool, req.cookies);
if (!pool.ok) {
res.status(pool.status).send({ error: `failed to get pool ${pool}` });
return;
}
const maxAllowed = pool.pool["backups-allowed"].max;
// check if used backups is more than maximum allowed, if so exit
if (numBackups >= maxAllowed) {
res.status(backups.status).send({ error: `${params.vmid} already has ${numBackups} >= ${maxAllowed} max backups allowed` }); res.status(backups.status).send({ error: `${params.vmid} already has ${numBackups} >= ${maxAllowed} max backups allowed` });
return; return;
} }
@@ -122,7 +136,7 @@ router.post("/notes", async (req, res) => {
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
@@ -146,7 +160,7 @@ router.post("/notes", async (req, res) => {
return; return;
} }
// create backup using vzdump path // modify backup notes
const body = { const body = {
notes: params.notes notes: params.notes
}; };
@@ -182,7 +196,7 @@ router.delete("/", async (req, res) => {
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
@@ -234,7 +248,7 @@ router.post("/restore", async (req, res) => {
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
+48 -22
View File
@@ -1,9 +1,6 @@
import { Router } from "express"; import { Router } from "express";
export const router = Router({ mergeParams: true }); export const router = Router({ mergeParams: true });
const checkAuth = global.utils.checkAuth;
const approveResources = global.utils.approveResources;
/** /**
* POST - detach mounted disk from instance * POST - detach mounted disk from instance
* request: * request:
@@ -25,12 +22,14 @@ router.post("/:disk/detach", async (req, res) => {
vmid: req.params.vmid, vmid: req.params.vmid,
disk: req.params.disk disk: req.params.disk
}; };
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
// disk must exist // disk must exist
const disk = await global.pve.getDisk(params.node, params.vmid, params.disk); const disk = await global.pve.getDisk(params.node, params.vmid, params.disk);
if (!disk) { if (!disk) {
@@ -38,14 +37,19 @@ router.post("/:disk/detach", async (req, res) => {
res.end(); res.end();
return; return;
} }
// disk cannot be unused // disk cannot be unused
if (params.disk.includes("unused")) { if (params.disk.includes("unused")) {
res.status(500).send({ error: `Requested disk ${params.disk} cannot be unused. Use /disk/delete to permanently delete unused disks.` }); res.status(500).send({ error: `Requested disk ${params.disk} cannot be unused. Use /disk/delete to permanently delete unused disks.` });
res.end(); res.end();
return; return;
} }
// setup detach action
const action = { delete: params.disk }; const action = { delete: params.disk };
const method = params.type === "qemu" ? "POST" : "PUT"; const method = params.type === "qemu" ? "POST" : "PUT";
// commit action
const result = await global.pve.requestPVE(`${vmpath}/config`, method, { token: true }, action); const result = await global.pve.requestPVE(`${vmpath}/config`, method, { token: true }, action);
await global.pve.handleResponse(params.node, result, res); await global.pve.handleResponse(params.node, result, res);
await global.pve.syncInstance(params.node, params.vmid); await global.pve.syncInstance(params.node, params.vmid);
@@ -75,9 +79,10 @@ router.post("/:disk/attach", async (req, res) => {
source: req.body.source, source: req.body.source,
mp: req.body.mp mp: req.body.mp
}; };
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
@@ -89,6 +94,7 @@ router.post("/:disk/attach", async (req, res) => {
res.end(); res.end();
return; return;
} }
// target disk must be allowed according to source disk's storage options // target disk must be allowed according to source disk's storage options
const resourceConfig = global.config.resources; const resourceConfig = global.config.resources;
if (!resourceConfig[disk.storage].disks.some(diskPrefix => params.disk.startsWith(diskPrefix))) { if (!resourceConfig[disk.storage].disks.some(diskPrefix => params.disk.startsWith(diskPrefix))) {
@@ -96,14 +102,10 @@ router.post("/:disk/attach", async (req, res) => {
res.end(); res.end();
return; return;
} }
// setup action using source disk info from vm config // setup action using source disk info from vm config
const action = {}; const action = {};
if (params.type === "qemu") { action[params.disk] = params.type === "qemu" ? `${disk.file}` : `${disk.file},mp=${params.mp},backup=1`;
action[params.disk] = `${disk.file}`;
}
else if (params.type === "lxc") {
action[params.disk] = `${disk.file},mp=${params.mp},backup=1`;
}
const method = params.type === "qemu" ? "POST" : "PUT"; const method = params.type === "qemu" ? "POST" : "PUT";
// commit action // commit action
@@ -137,16 +139,22 @@ router.post("/:disk/resize", async (req, res) => {
size: req.body.size size: req.body.size
}; };
const userObj = global.utils.getUserObjFromUsername(req.cookies.username); // attempt to parse user from username
const userObj = global.utils.getUserObjFromUsername(params.username);
if (userObj == null) {
res.status(400).send({ auth:true, error:`username ${params.username} does not match format uid@realm.` });
}
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
// get instance config for pool membership // get instance config for pool membership
const instance = await global.pve.getInstance(params.node, params.vmid); const instance = await global.pve.getInstance(params.node, params.vmid);
// check disk existence // check disk existence
const disk = await global.pve.getDisk(params.node, params.vmid, params.disk); // get target disk const disk = await global.pve.getDisk(params.node, params.vmid, params.disk); // get target disk
if (!disk) { // exit if disk does not exist if (!disk) { // exit if disk does not exist
@@ -154,17 +162,20 @@ router.post("/:disk/resize", async (req, res) => {
res.end(); res.end();
return; return;
} }
// setup request // setup request
const storage = disk.storage; // get the storage const storage = disk.storage; // get the storage
const request = {}; const request = {};
request[storage] = Number(params.size * 1024 ** 3); // setup request object request[storage] = Number(params.size * 1024 ** 3); // setup request object
// check request approval // check request approval
const { approved } = await approveResources(req, userObj, params.node, instance.pool, request); const { approved } = await global.utils.approveResources(req, userObj, params.node, instance.pool, request);
if (!approved) { if (!approved) {
res.status(500).send({ request, error: `Storage ${storage} could not fulfill request of size ${params.size}G.` }); res.status(500).send({ request, error: `Storage ${storage} could not fulfill request of size ${params.size}G.` });
res.end(); res.end();
return; return;
} }
// action approved, commit to action // action approved, commit to action
const action = { disk: params.disk, size: `+${params.size}G` }; const action = { disk: params.disk, size: `+${params.size}G` };
const result = await global.pve.requestPVE(`${vmpath}/resize`, "PUT", { token: true }, action); const result = await global.pve.requestPVE(`${vmpath}/resize`, "PUT", { token: true }, action);
@@ -199,11 +210,15 @@ router.post("/:disk/move", async (req, res) => {
delete: req.body.delete delete: req.body.delete
}; };
const userObj = global.utils.getUserObjFromUsername(req.cookies.username); // attempt to parse user from username
const userObj = global.utils.getUserObjFromUsername(params.username);
if (userObj == null) {
res.status(400).send({ auth:true, error:`username ${params.username} does not match format uid@realm.` });
}
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
@@ -224,7 +239,7 @@ router.post("/:disk/move", async (req, res) => {
request[dstStorage] = Number(size); // always decrease destination storage by size request[dstStorage] = Number(size); // always decrease destination storage by size
} }
// check request approval // check request approval
const { approved } = await approveResources(req, userObj, params.node, instance.pool, request); const { approved } = await global.utils.approveResources(req, userObj, params.node, instance.pool, request);
if (!approved) { if (!approved) {
res.status(500).send({ request, error: `Storage ${params.storage} could not fulfill request of size ${params.size}G.` }); res.status(500).send({ request, error: `Storage ${params.storage} could not fulfill request of size ${params.size}G.` });
res.end(); res.end();
@@ -268,7 +283,7 @@ router.delete("/:disk/delete", async (req, res) => {
}; };
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
@@ -321,15 +336,23 @@ router.post("/:disk/create", async (req, res) => {
size: req.body.size, size: req.body.size,
iso: req.body.iso iso: req.body.iso
}; };
const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
// attempt to parse user from username
const userObj = global.utils.getUserObjFromUsername(params.username);
if (userObj == null) {
res.status(400).send({ auth:true, error:`username ${params.username} does not match format uid@realm.` });
}
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
// get instance config for pool membership // get instance config for pool membership
const instance = await global.pve.getInstance(params.node, params.vmid); const instance = await global.pve.getInstance(params.node, params.vmid);
// disk must not exist // disk must not exist
const disk = await global.pve.getDisk(params.node, params.vmid, params.disk); const disk = await global.pve.getDisk(params.node, params.vmid, params.disk);
if (disk) { if (disk) {
@@ -337,13 +360,14 @@ router.post("/:disk/create", async (req, res) => {
res.end(); res.end();
return; return;
} }
// setup request // setup request
const request = {}; const request = {};
if (!params.disk.includes("ide")) { if (!params.disk.includes("ide")) { // ignore resource request if the type is ide (iso file)
// setup request // setup request
request[params.storage] = Number(params.size * 1024 ** 3); request[params.storage] = Number(params.size * 1024 ** 3);
// check request approval // check request approval
const { approved } = await approveResources(req, userObj, params.node, instance.pool, request); const { approved } = await global.utils.approveResources(req, userObj, params.node, instance.pool, request);
if (!approved) { if (!approved) {
res.status(500).send({ request, error: `Storage ${params.storage} could not fulfill request of size ${params.size}G.` }); res.status(500).send({ request, error: `Storage ${params.storage} could not fulfill request of size ${params.size}G.` });
res.end(); res.end();
@@ -357,6 +381,7 @@ router.post("/:disk/create", async (req, res) => {
return; return;
} }
} }
// setup action // setup action
const action = {}; const action = {};
if (params.disk.includes("ide") && params.iso) { if (params.disk.includes("ide") && params.iso) {
@@ -369,6 +394,7 @@ router.post("/:disk/create", async (req, res) => {
action[params.disk] = `${params.storage}:${params.size},mp=/${params.disk}/,backup=1`; action[params.disk] = `${params.storage}:${params.size},mp=/${params.disk}/,backup=1`;
} }
const method = params.type === "qemu" ? "POST" : "PUT"; const method = params.type === "qemu" ? "POST" : "PUT";
// commit action // commit action
const result = await global.pve.requestPVE(`${vmpath}/config`, method, { token: true }, action); const result = await global.pve.requestPVE(`${vmpath}/config`, method, { token: true }, action);
await global.pve.handleResponse(params.node, result, res); await global.pve.handleResponse(params.node, result, res);
+27 -9
View File
@@ -1,9 +1,6 @@
import { Router } from "express"; import { Router } from "express";
export const router = Router({ mergeParams: true }); ; export const router = Router({ mergeParams: true }); ;
const checkAuth = global.utils.checkAuth;
const approveResources = global.utils.approveResources;
/** /**
* POST - create new virtual network interface * POST - create new virtual network interface
* request: * request:
@@ -30,14 +27,17 @@ router.post("/:netid/create", async (req, res) => {
rate: req.body.rate, rate: req.body.rate,
name: req.body.name name: req.body.name
}; };
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
// get instance config for pool membership // get instance config for pool membership
const instance = await global.pve.getInstance(params.node, params.vmid); const instance = await global.pve.getInstance(params.node, params.vmid);
// net interface must not exist // net interface must not exist
const net = await global.pve.getNet(params.node, params.vmid, params.netid); const net = await global.pve.getNet(params.node, params.vmid, params.netid);
if (net) { if (net) {
@@ -50,17 +50,21 @@ router.post("/:netid/create", async (req, res) => {
res.end(); res.end();
return; return;
} }
// setup request
const request = { const request = {
network: Number(params.rate) network: Number(params.rate)
}; };
// check resource approval // check resource approval
const userObj = global.utils.getUserObjFromUsername(req.cookies.username); const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
const { approved } = await approveResources(req, userObj, params.node, instance.pool, request); const { approved } = await global.utils.approveResources(req, userObj, params.node, instance.pool, request);
if (!approved) { if (!approved) {
res.status(500).send({ request, error: `Could not fulfil network request of ${params.rate}MB/s.` }); res.status(500).send({ request, error: `Could not fulfil network request of ${params.rate}MB/s.` });
res.end(); res.end();
return; return;
} }
// setup action // setup action
const nc = (await global.access.getUser(userObj, req.cookies)).templates.network[params.type]; const nc = (await global.access.getUser(userObj, req.cookies)).templates.network[params.type];
const action = {}; const action = {};
@@ -71,6 +75,7 @@ router.post("/:netid/create", async (req, res) => {
action[`${params.netid}`] = `${nc.type},bridge=${nc.bridge},tag=${nc.vlan},rate=${params.rate}`; action[`${params.netid}`] = `${nc.type},bridge=${nc.bridge},tag=${nc.vlan},rate=${params.rate}`;
} }
const method = params.type === "qemu" ? "POST" : "PUT"; const method = params.type === "qemu" ? "POST" : "PUT";
// commit action // commit action
const result = await global.pve.requestPVE(`${vmpath}/config`, method, { token: true }, action); const result = await global.pve.requestPVE(`${vmpath}/config`, method, { token: true }, action);
await global.pve.handleResponse(params.node, result, res); await global.pve.handleResponse(params.node, result, res);
@@ -101,14 +106,17 @@ router.post("/:netid/modify", async (req, res) => {
netid: req.params.netid, netid: req.params.netid,
rate: req.body.rate rate: req.body.rate
}; };
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
// get instance config for pool membership // get instance config for pool membership
const instance = await global.pve.getInstance(params.node, params.vmid); const instance = await global.pve.getInstance(params.node, params.vmid);
// net interface must already exist // net interface must already exist
const net = await global.pve.getNet(params.node, params.vmid, params.netid); const net = await global.pve.getNet(params.node, params.vmid, params.netid);
if (!net) { if (!net) {
@@ -116,21 +124,26 @@ router.post("/:netid/modify", async (req, res) => {
res.end(); res.end();
return; return;
} }
// setup request
const request = { const request = {
network: Number(params.rate) - Number(net.rate) network: Number(params.rate) - Number(net.rate)
}; };
// check resource approval // check resource approval
const userObj = global.utils.getUserObjFromUsername(req.cookies.username); const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
const { approved } = await approveResources(req, userObj, params.node, instance.pool, request); const { approved } = await global.utils.approveResources(req, userObj, params.node, instance.pool, request);
if (!approved) { if (!approved) {
res.status(500).send({ request, error: `Could not fulfil network request of ${params.rate}MB/s.` }); res.status(500).send({ request, error: `Could not fulfil network request of ${params.rate}MB/s.` });
res.end(); res.end();
return; return;
} }
// setup action // setup action
const action = {}; const action = {};
action[`${params.netid}`] = net.value.replace(`rate=${net.rate}`, `rate=${params.rate}`); action[`${params.netid}`] = net.value.replace(`rate=${net.rate}`, `rate=${params.rate}`);
const method = params.type === "qemu" ? "POST" : "PUT"; const method = params.type === "qemu" ? "POST" : "PUT";
// commit action // commit action
const result = await global.pve.requestPVE(`${vmpath}/config`, method, { token: true }, action); const result = await global.pve.requestPVE(`${vmpath}/config`, method, { token: true }, action);
await global.pve.handleResponse(params.node, result, res); await global.pve.handleResponse(params.node, result, res);
@@ -158,12 +171,14 @@ router.delete("/:netid/delete", async (req, res) => {
vmid: req.params.vmid, vmid: req.params.vmid,
netid: req.params.netid netid: req.params.netid
}; };
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
// net interface must already exist // net interface must already exist
const net = await global.pve.getNet(params.node, params.vmid, params.netid); const net = await global.pve.getNet(params.node, params.vmid, params.netid);
if (!net) { if (!net) {
@@ -171,10 +186,13 @@ router.delete("/:netid/delete", async (req, res) => {
res.end(); res.end();
return; return;
} }
// setup action // setup action
const action = { delete: `${params.netid}` };
const method = params.type === "qemu" ? "POST" : "PUT"; const method = params.type === "qemu" ? "POST" : "PUT";
// commit action // commit action
const result = await global.pve.requestPVE(`${vmpath}/config`, method, { token: true }, { delete: `${params.netid}` }); const result = await global.pve.requestPVE(`${vmpath}/config`, method, { token: true }, action);
await global.pve.handleResponse(params.node, result, res); await global.pve.handleResponse(params.node, result, res);
await global.pve.syncInstance(params.node, params.vmid); await global.pve.syncInstance(params.node, params.vmid);
}); });
+34 -12
View File
@@ -1,10 +1,6 @@
import { Router } from "express"; import { Router } from "express";
export const router = Router({ mergeParams: true }); ; export const router = Router({ mergeParams: true }); ;
const checkAuth = global.utils.checkAuth;
const getPoolResources = global.utils.getPoolResources;
const approveResources = global.utils.approveResources;
/** /**
* GET - get available pcie devices for the given node and user * GET - get available pcie devices for the given node and user
* request: * request:
@@ -21,8 +17,9 @@ router.get("/", async (req, res) => {
type: req.params.type, type: req.params.type,
vmid: req.params.vmid, vmid: req.params.vmid,
}; };
// check auth // check auth
const auth = await checkAuth(req.cookies, res); const auth = await global.utils.checkAuth(req.cookies, res);
if (!auth) { if (!auth) {
return; return;
} }
@@ -48,7 +45,7 @@ router.get("/", async (req, res) => {
} }
// get remaining user resources // get remaining user resources
const poolAvailPci = (await getPoolResources(req, instance.pool)).pci.nodes[params.node]; // we assume that the node list is used. TODO support global lists const poolAvailPci = (await global.utils.getPoolResources(req, instance.pool)).pci.nodes[params.node]; // we assume that the node list is used. TODO support global lists
if (poolAvailPci === undefined) { // user has no available devices on this node, so send an empty list if (poolAvailPci === undefined) { // user has no available devices on this node, so send an empty list
res.status(200).send([]); res.status(200).send([]);
res.end(); res.end();
@@ -93,12 +90,14 @@ router.get("/:hostpci", async (req, res) => {
vmid: req.params.vmid, vmid: req.params.vmid,
hostpci: req.params.hostpci hostpci: req.params.hostpci
}; };
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
// get device // get device
const device = await global.pve.getDevice(params.node, params.vmid, params.hostpci); const device = await global.pve.getDevice(params.node, params.vmid, params.hostpci);
if (!device) { if (!device) {
@@ -135,22 +134,27 @@ router.post("/:hostpci/modify", async (req, res) => {
device: req.body.device, device: req.body.device,
pcie: req.body.pcie pcie: req.body.pcie
}; };
// check if type is qemu // check if type is qemu
if (params.type !== "qemu") { if (params.type !== "qemu") {
res.status(500).send({ error: "Type must be qemu (vm)." }); res.status(500).send({ error: "Type must be qemu (vm)." });
res.end(); res.end();
return; return;
} }
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
// get instance config for pool membership // get instance config for pool membership
const instance = await global.pve.getInstance(params.node, params.vmid); const instance = await global.pve.getInstance(params.node, params.vmid);
// force all functions // force all functions
params.device = params.device.split(".")[0]; params.device = params.device.split(".")[0];
// device must exist to be modified // device must exist to be modified
const existingDevice = await global.pve.getDevice(params.node, params.vmid, params.hostpci); const existingDevice = await global.pve.getDevice(params.node, params.vmid, params.hostpci);
if (!existingDevice) { if (!existingDevice) {
@@ -158,6 +162,7 @@ router.post("/:hostpci/modify", async (req, res) => {
res.end(); res.end();
return; return;
} }
// only check user and node availability if base id is different, we do the split in case of existing partial-function hostpci // only check user and node availability if base id is different, we do the split in case of existing partial-function hostpci
const userObj = global.utils.getUserObjFromUsername(req.cookies.username); const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
if (existingDevice.device_bus.split(".")[0] !== params.device) { if (existingDevice.device_bus.split(".")[0] !== params.device) {
@@ -171,7 +176,7 @@ router.post("/:hostpci/modify", async (req, res) => {
return; return;
} }
// check resource approval // check resource approval
const { approved } = await approveResources(req, userObj, params.node, instance.pool, request); const { approved } = await global.utils.approveResources(req, userObj, params.node, instance.pool, request);
if (!approved) { if (!approved) {
res.status(500).send({ request, error: `Could not fulfil request for ${requestedDevice.device_name}.` }); res.status(500).send({ request, error: `Could not fulfil request for ${requestedDevice.device_name}.` });
res.end(); res.end();
@@ -184,9 +189,11 @@ router.post("/:hostpci/modify", async (req, res) => {
return; return;
} }
} }
// setup action // setup action
const action = {}; const action = {};
action[`${params.hostpci}`] = `${params.device},pcie=${params.pcie}`; action[`${params.hostpci}`] = `${params.device},pcie=${params.pcie}`;
// commit action // commit action
const result = await global.pve.requestPVE(`${vmpath}/config`, "POST", { root: true }, action); const result = await global.pve.requestPVE(`${vmpath}/config`, "POST", { root: true }, action);
await global.pve.handleResponse(params.node, result, res); await global.pve.handleResponse(params.node, result, res);
@@ -217,22 +224,27 @@ router.post("/:hostpci/create", async (req, res) => {
device: req.body.device, device: req.body.device,
pcie: req.body.pcie pcie: req.body.pcie
}; };
// check if type is qemu // check if type is qemu
if (params.type !== "qemu") { if (params.type !== "qemu") {
res.status(500).send({ error: "Type must be qemu (vm)." }); res.status(500).send({ error: "Type must be qemu (vm)." });
res.end(); res.end();
return; return;
} }
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
// get instance config for pool membership // get instance config for pool membership
const instance = await global.pve.getInstance(params.node, params.vmid); const instance = await global.pve.getInstance(params.node, params.vmid);
// force all functions // force all functions
params.device = params.device.split(".")[0]; params.device = params.device.split(".")[0];
// device must not exist to be added // device must not exist to be added
const existingDevice = await global.pve.getDevice(params.node, params.vmid, params.hostpci); const existingDevice = await global.pve.getDevice(params.node, params.vmid, params.hostpci);
if (existingDevice) { if (existingDevice) {
@@ -240,27 +252,32 @@ router.post("/:hostpci/create", async (req, res) => {
res.end(); res.end();
return; return;
} }
// setup request // setup request
const node = await global.pve.getNode(params.node); const node = await global.pve.getNode(params.node);
const requestedDevice = node.devices[`${params.device}`]; const requestedDevice = node.devices[`${params.device}`];
const request = { pci: requestedDevice.device_name }; const request = { pci: requestedDevice.device_name };
// check resource approval // check resource approval
const userObj = global.utils.getUserObjFromUsername(req.cookies.username); const userObj = global.utils.getUserObjFromUsername(req.cookies.username);
const { approved } = await approveResources(req, userObj, params.node, instance.pool, request); const { approved } = await global.utils.approveResources(req, userObj, params.node, instance.pool, request);
if (!approved) { if (!approved) {
res.status(500).send({ request, error: `Could not fulfil request for ${requestedDevice.device_name}.` }); res.status(500).send({ request, error: `Could not fulfil request for ${requestedDevice.device_name}.` });
res.end(); res.end();
return; return;
} }
// check node availability // check node availability
if (!Object.values(node.devices).some(element => element.device_bus.split(".")[0] === params.device && element.reserved === false)) { if (!Object.values(node.devices).some(element => element.device_bus.split(".")[0] === params.device && element.reserved === false)) {
res.status(500).send({ error: `Device ${params.device} is already in use on ${params.node}.` }); res.status(500).send({ error: `Device ${params.device} is already in use on ${params.node}.` });
res.end(); res.end();
return; return;
} }
// setup action // setup action
const action = {}; const action = {};
action[`${params.hostpci}`] = `${params.device},pcie=${params.pcie}`; action[`${params.hostpci}`] = `${params.device},pcie=${params.pcie}`;
// commit action // commit action
const result = await global.pve.requestPVE(`${vmpath}/config`, "POST", { root: true }, action); const result = await global.pve.requestPVE(`${vmpath}/config`, "POST", { root: true }, action);
await global.pve.handleResponse(params.node, result, res); await global.pve.handleResponse(params.node, result, res);
@@ -288,18 +305,21 @@ router.delete("/:hostpci/delete", async (req, res) => {
vmid: req.params.vmid, vmid: req.params.vmid,
hostpci: req.params.hostpci hostpci: req.params.hostpci
}; };
// check if type is qemu // check if type is qemu
if (params.type !== "qemu") { if (params.type !== "qemu") {
res.status(500).send({ error: "Type must be qemu (vm)." }); res.status(500).send({ error: "Type must be qemu (vm)." });
res.end(); res.end();
return; return;
} }
// check auth for specific instance // check auth for specific instance
const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`;
const auth = await checkAuth(req.cookies, res, vmpath); const auth = await global.utils.checkAuth(req.cookies, res, vmpath);
if (!auth) { if (!auth) {
return; return;
} }
// check device is in instance config // check device is in instance config
const device = global.pve.getDevice(params.node, params.vmid, params.hostpci); const device = global.pve.getDevice(params.node, params.vmid, params.hostpci);
if (!device) { if (!device) {
@@ -307,8 +327,10 @@ router.delete("/:hostpci/delete", async (req, res) => {
res.end(); res.end();
return; return;
} }
// setup action // setup action
const action = { delete: `${params.hostpci}` }; const action = { delete: `${params.hostpci}` };
// commit action, need to use root user here because proxmox api only allows root to modify hostpci for whatever reason // commit action, need to use root user here because proxmox api only allows root to modify hostpci for whatever reason
const result = await global.pve.requestPVE(`${vmpath}/config`, "POST", { root: true }, action); const result = await global.pve.requestPVE(`${vmpath}/config`, "POST", { root: true }, action);
await global.pve.handleResponse(params.node, result, res); await global.pve.handleResponse(params.node, result, res);
+4
View File
@@ -12,11 +12,15 @@ router.get("/config/:key", async (req, res) => {
const params = { const params = {
key: req.params.key key: req.params.key
}; };
// check auth // check auth
const auth = await checkAuth(req.cookies, res); const auth = await checkAuth(req.cookies, res);
if (!auth) { if (!auth) {
return; return;
} }
// check if users are allowed to get the config value
// return the value if so, otherwise send unauthorized
const allowKeys = ["resources"]; const allowKeys = ["resources"];
if (allowKeys.includes(params.key)) { if (allowKeys.includes(params.key)) {
const config = global.config; const config = global.config;
+5 -9
View File
@@ -2,11 +2,7 @@ import { WebSocketServer } from "ws";
import * as cookie from "cookie"; import * as cookie from "cookie";
import { Router } from "express"; import { Router } from "express";
export const router = Router({ mergeParams: true }); ; export const router = Router({ mergeParams: true });
const checkAuth = global.utils.checkAuth;
const getObjectHash = global.utils.getObjectHash;
const getTimeLeft = global.utils.getTimeLeft;
// maps usernames to socket object(s) // maps usernames to socket object(s)
const userSocketMap = {}; const userSocketMap = {};
@@ -47,7 +43,7 @@ if (schemes.hash.enabled) {
*/ */
router.get("/hash", async (req, res) => { router.get("/hash", async (req, res) => {
// check auth // check auth
const auth = await checkAuth(req.cookies, res); const auth = await global.utils.checkAuth(req.cookies, res);
if (!auth) { if (!auth) {
return; return;
} }
@@ -55,7 +51,7 @@ if (schemes.hash.enabled) {
const status = (await global.pve.requestPVE("/cluster/resources", "GET", { cookies: req.cookies })).data; const status = (await global.pve.requestPVE("/cluster/resources", "GET", { cookies: req.cookies })).data;
// filter out just state information of resources that are needed // filter out just state information of resources that are needed
const state = extractClusterState(status, resourceTypes); const state = extractClusterState(status, resourceTypes);
res.status(200).send(getObjectHash(state)); res.status(200).send(global.utils.getObjectHash(state));
}); });
console.log("clientsync: enabled hash sync"); console.log("clientsync: enabled hash sync");
} }
@@ -135,7 +131,7 @@ if (schemes.interrupt.enabled) {
// AND if the next event trigger is more than the new rate in the future, // AND if the next event trigger is more than the new rate in the future,
// restart the timer with the new rate // restart the timer with the new rate
// avoids a large requested rate preventing a faster rate from being fulfilled // avoids a large requested rate preventing a faster rate from being fulfilled
else if (rate < Math.min.apply(null, Object.values(requestedRates)) && getTimeLeft(timer) > rate) { else if (rate < Math.min.apply(null, Object.values(requestedRates)) && global.utils.getTimeLeft(timer) > rate) {
clearTimeout(timer); clearTimeout(timer);
timer = setTimeout(handleInterruptSync, rate); timer = setTimeout(handleInterruptSync, rate);
const time = global.process.uptime(); const time = global.process.uptime();
@@ -259,7 +255,7 @@ function extractClusterState (status, resourceTypes, hashIndividual = false) {
pool: resource.pool || null pool: resource.pool || null
}; };
if (hashIndividual) { if (hashIndividual) {
const hash = getObjectHash(state[resource.id]); const hash = global.utils.getObjectHash(state[resource.id]);
state[resource.id].hash = hash; state[resource.id].hash = hash;
} }
} }
+7 -14
View File
@@ -230,16 +230,13 @@ export async function getPoolResources (req, pool) {
export async function approveResources (req, user, node, pool, request) { export async function approveResources (req, user, node, pool, request) {
const configResources = global.config.resources; const configResources = global.config.resources;
const poolResources = await getPoolResources(req, pool); const poolResources = await getPoolResources(req, pool);
// let approved = true;
const reason = {}; const reason = {};
for (const key in request) { for (const key in request) {
// if requested resource is not specified in user resources, assume it's not allowed // if requested resource is not specified in user resources, assume it's not allowed
if (!(key in poolResources)) { if (!(key in poolResources)) {
// approved = false;
reason[key] = { approved: false, reason: `${key} not allowed` }; reason[key] = { approved: false, reason: `${key} not allowed` };
continue; continue;
// return;
} }
// use node specific quota if there is one available, otherwise use the global resource quota // use node specific quota if there is one available, otherwise use the global resource quota
@@ -252,25 +249,21 @@ export async function approveResources (req, user, node, pool, request) {
// if no matching resource when index == -1, then remaining is -1 otherwise use the remaining value // if no matching resource when index == -1, then remaining is -1 otherwise use the remaining value
const avail = index === -1 ? false : resourceData[index].avail > 0; const avail = index === -1 ? false : resourceData[index].avail > 0;
if (avail !== configResources[key].whitelist) { if (avail !== configResources[key].whitelist) {
// approved = false;
reason[key] = { approved: false, reason: `${key} ${configResources[key].whitelist ? "not in whitelist" : "in blacklist"}` }; reason[key] = { approved: false, reason: `${key} ${configResources[key].whitelist ? "not in whitelist" : "in blacklist"}` };
// return;
continue; continue;
} }
} }
// if either the requested or avail resource is not strictly a number, block // if either the requested or avail resource is not strictly a number, block
else if (typeof (resourceData.avail) !== "number" || typeof (request[key]) !== "number") { if (typeof (resourceData.avail) !== "number" || typeof (request[key]) !== "number") {
// approved = false;
reason[key] = { approved: false, reason: `expected ${key} to be a number but got ${request[key]}` }; reason[key] = { approved: false, reason: `expected ${key} to be a number but got ${request[key]}` };
continue; continue;
// return;
} }
// if the avail resources is less than the requested resources, block // if the avail resources is less than the requested resources, block
else if (resourceData.avail - request[key] < 0) { if (resourceData.avail - request[key] < 0) {
// approved = false;
reason[key] = { approved: false, reason: `${key} requested ${request[key]} which is more than ${resourceData.avail} available` }; reason[key] = { approved: false, reason: `${key} requested ${request[key]} which is more than ${resourceData.avail} available` };
continue; continue;
// return;
} }
reason[key] = { approved: true, reason: "ok" }; reason[key] = { approved: true, reason: "ok" };
@@ -340,7 +333,7 @@ export function readJSONFile (path) {
}; };
/** /**
* * Parse username into user object using the uid@realm format.
* @param {*} username * @param {*} username
* @returns {Object | null} user object containing userid and realm or null if username format was invalid * @returns {Object | null} user object containing userid and realm or null if username format was invalid
*/ */
@@ -357,7 +350,7 @@ export function getUserObjFromUsername (username) {
} }
/** /**
* * Parse groupname into group object using the gid-realm format.
* @param {*} groupname * @param {*} groupname
* @returns {Object | null} user object containing groupid and realm or null if groupname format was invalid * @returns {Object | null} user object containing groupid and realm or null if groupname format was invalid
*/ */
@@ -382,7 +375,7 @@ export function getGroupObjFromGroupname (groupname) {
} }
/** /**
* * Inspect pool object and return true if pool contains any groups which contain the user object.
* @param {Object} poolObj pool data object * @param {Object} poolObj pool data object
* @param {Object} userObj user object containing id and realm * @param {Object} userObj user object containing id and realm
* @returns {boolean} true if userObj in poolObj * @returns {boolean} true if userObj in poolObj