From 57dab01d7be477adb3f509fda432c53e1d877af8 Mon Sep 17 00:00:00 2001 From: Arthur Lu Date: Tue, 1 Aug 2023 19:07:45 +0000 Subject: [PATCH] finish moving routes to files --- src/main.js | 290 ++---------------------------------------- src/routes/cluster.js | 250 ++++++++++++++++++++++++++++++++++++ src/routes/global.js | 29 +++++ 3 files changed, 287 insertions(+), 282 deletions(-) create mode 100644 src/routes/global.js diff --git a/src/main.js b/src/main.js index 7bdb7af..56d3e9c 100644 --- a/src/main.js +++ b/src/main.js @@ -21,15 +21,6 @@ app.use(cookieParser()); app.use(cors({ origin: db.hostname })); app.use(morgan("combined")); -// legacy defs -const requestPVE = global.pve.requestPVE; -const handleResponse = global.pve.handleResponse; -const getNodeAvailDevices = global.pve.getNodeAvailDevices; -const checkAuth = global.utils.checkAuth; -const approveResources = global.utils.approveResources; -const getUserResources = global.utils.getUserResources; -const pveAPIToken = global.db.pveAPIToken; - global.server = app.listen(db.listenPort, () => { console.log(`proxmoxaas-api v${api.version} listening on port ${db.listenPort}`); }); @@ -38,6 +29,14 @@ import("./routes/auth.js").then((module) => { app.use("/api/auth", module.router); }); +import("./routes/cluster.js").then((module) => { + app.use("/api", module.router); +}); + +import("./routes/global.js").then((module) => { + app.use("/api/global", module.router); +}); + import("./routes/proxmox.js").then((module) => { app.use("/api/proxmox", module.router); }); @@ -50,14 +49,6 @@ import("./routes/user.js").then((module) => { app.use("/api/user", module.router); }); -import("./routes/cluster.js").then((module) => { - app.use("/api", module.router); -}); - -const nodeRegexP = "[\\w-]+"; -const typeRegexP = "qemu|lxc"; -const vmidRegexP = "\\d+"; - /** * GET - get API version * responses: @@ -75,268 +66,3 @@ app.get("/api/version", (req, res) => { app.get("/api/echo", (req, res) => { res.status(200).send({ body: req.body, cookies: req.cookies }); }); - -/** - * GET - get db global resource configuration - * responses: - * - 200: Object - */ -app.get("/api/global/config/:key", async (req, res) => { - const params = { - key: req.params.key - }; - // check auth - const auth = await checkAuth(req.cookies, res); - if (!auth) { - return; - } - const allowKeys = ["resources"]; - if (allowKeys.includes(params.key)) { - const config = db.getGlobalConfig(); - res.status(200).send(config[params.key]); - } - else { - res.status(401).send({ auth: false, error: `User is not authorized to access /global/config/${params.key}.` }); - } -}); - -/** - * GET - get available pcie devices given node and user - * request: - * - node: string - vm host node id - * responses: - * - 200: PVE PCI Device Object - * - 401: {auth: false} - * - 401: {auth: false, path: string} - * - 500: {error: string} - */ -app.get(`/api/:node(${nodeRegexP})/pci`, async (req, res) => { - const params = { - node: req.params.node - }; - // check auth - const auth = await checkAuth(req.cookies, res); - if (!auth) { - return; - } - const userNodes = db.getUserConfig(req.cookies.username).nodes; - if (!userNodes.includes(params.node)) { - res.status(401).send({ auth: false, path: params.node }); - res.end(); - return; - } - // get remaining user resources - const userAvailPci = (await getUserResources(req, req.cookies.username)).avail.pci; - // get node avail devices - let nodeAvailPci = await getNodeAvailDevices(params.node, req.cookies); - nodeAvailPci = nodeAvailPci.filter(nodeAvail => userAvailPci.some((userAvail) => { - return nodeAvail.device_name && nodeAvail.device_name.includes(userAvail); - })); - res.status(200).send(nodeAvailPci); - res.end(); -}); - -/** - * POST - set basic resources for vm - * request: - * - node: string - vm host node id - * - type: string - vm type (lxc, qemu) - * - vmid: number - vm id number - * - proctype: string - vm processor type - * - cores: number, optional - number of processor cores for instance - * - memory: number - amount of memory for instance - * - swap: number, optional - new amount of swap for instance - * responses: - * - 200: PVE Task Object - * - 401: {auth: false, path: string} - * - 500: {request: Object, error: string} - * - 500: PVE Task Object - */ -app.post(`/api/:node(${nodeRegexP})/:type(${typeRegexP})/:vmid(${vmidRegexP})/resources`, async (req, res) => { - const params = { - node: req.params.node, - type: req.params.type, - vmid: req.params.vmid, - proctype: req.body.proctype, - cores: req.body.cores, - memory: req.body.memory, - swap: req.body.swap - }; - // check auth for specific instance - const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; - const auth = await checkAuth(req.cookies, res, vmpath); - if (!auth) { - return; - } - // get current config - const currentConfig = await requestPVE(`/nodes/${params.node}/${params.type}/${params.vmid}/config`, "GET", null, null, pveAPIToken); - const request = { - cores: Number(params.cores) - Number(currentConfig.data.data.cores), - memory: Number(params.memory) - Number(currentConfig.data.data.memory) - }; - if (params.type === "lxc") { - request.swap = Number(params.swap) - Number(currentConfig.data.data.swap); - } - else if (params.type === "qemu") { - request.cpu = params.proctype; - } - // check resource approval - if (!await approveResources(req, req.cookies.username, request)) { - res.status(500).send({ request, error: "Could not fulfil request." }); - res.end(); - return; - } - // setup action - let action = { cores: params.cores, memory: params.memory }; - if (params.type === "lxc") { - action.swap = Number(params.swap); - } - else if (params.type === "qemu") { - action.cpu = params.proctype; - } - action = JSON.stringify(action); - const method = params.type === "qemu" ? "POST" : "PUT"; - // commit action - const result = await requestPVE(`${vmpath}/config`, method, req.cookies, action, pveAPIToken); - await handleResponse(params.node, result, res); -}); - -/** - * POST - create new instance - * request: - * - node: string - vm host node id - * - type: string - vm type (lxc, qemu) - * - vmid: number - vm id number for instance - * - hostname: string, optional- hostname for lxc instance - * - name: string, optional - hostname for qemu instance - * - cores: number - number of cores for instance - * - memory: number - amount of memory for instance - * - swap: number, optional - amount of swap for lxc instance - * - password: string, optional - password for lxc instance - * - ostemplate: string, optional - os template name for lxc instance - * - rootfslocation: string, optional - storage name for lxc instance rootfs - * - rootfssize: number, optional, - size of lxc instance rootfs - * responses: - * - 200: PVE Task Object - * - 401: {auth: false, path: string} - * - 500: {error: string} - * - 500: {request: Object, error: string} - * - 500: PVE Task Object - */ -app.post(`/api/:node(${nodeRegexP})/:type(${typeRegexP})/:vmid(${vmidRegexP})/create`, async (req, res) => { - const params = { - node: req.params.node, - type: req.params.type, - vmid: req.params.vmid, - hostname: req.body.hostname, - name: req.body.name, - cores: req.body.cores, - memory: req.body.memory, - swap: req.body.swap, - password: req.body.password, - ostemplate: req.body.ostemplate, - rootfslocation: req.body.rootfslocation, - rootfssize: req.body.rootfssize - }; - // check auth - const auth = await checkAuth(req.cookies, res); - if (!auth) { - return; - } - // get user db config - const user = await db.getUserConfig(req.cookies.username); - const vmid = Number.parseInt(params.vmid); - const vmidMin = user.cluster.vmid.min; - const vmidMax = user.cluster.vmid.max; - // check vmid is within allowed range - if (vmid < vmidMin || vmid > vmidMax) { - res.status(500).send({ error: `Requested vmid ${vmid} is out of allowed range [${vmidMin},${vmidMax}].` }); - res.end(); - return; - } - // check node is within allowed list - if (!user.nodes.includes(params.node)) { - res.status(500).send({ error: `Requested node ${params.node} is not in allowed nodes [${user.nodes}].` }); - res.end(); - return; - } - // setup request - const request = { - cores: Number(params.cores), - memory: Number(params.memory) - }; - if (params.type === "lxc") { - request.swap = params.swap; - request[params.rootfslocation] = params.rootfssize; - } - for (const key of Object.keys(user.templates.instances[params.type])) { - const item = user.templates.instances[params.type][key]; - if (item.resource) { - if (request[item.resource.name]) { - request[item.resource.name] += item.resource.amount; - } - else { - request[item.resource.name] = item.resource.amount; - } - } - } - // check resource approval - if (!await approveResources(req, req.cookies.username, request)) { // check resource approval - res.status(500).send({ request, error: "Not enough resources to satisfy request." }); - res.end(); - return; - } - // setup action by adding non resource values - let action = { - vmid: params.vmid, - cores: Number(params.cores), - memory: Number(params.memory), - pool: user.cluster.pool - }; - for (const key of Object.keys(user.templates.instances[params.type])) { - action[key] = user.templates.instances[params.type][key].value; - } - if (params.type === "lxc") { - action.hostname = params.name; - action.unprivileged = 1; - action.features = "nesting=1"; - action.password = params.password; - action.ostemplate = params.ostemplate; - action.rootfs = `${params.rootfslocation}:${params.rootfssize}`; - } - else { - action.name = params.name; - } - action = JSON.stringify(action); - // commit action - const result = await requestPVE(`/nodes/${params.node}/${params.type}`, "POST", req.cookies, action, pveAPIToken); - await handleResponse(params.node, result, res); -}); - -/** - * DELETE - destroy existing instance - * request: - * - node: string - vm host node id - * - type: string - vm type (lxc, qemu) - * - vmid: number - vm id number to destroy - * responses: - * - 200: PVE Task Object - * - 401: {auth: false, path: string} - * - 500: PVE Task Object - */ -app.delete(`/api/:node(${nodeRegexP})/:type(${typeRegexP})/:vmid(${vmidRegexP})/delete`, async (req, res) => { - const params = { - node: req.params.node, - type: req.params.type, - vmid: req.params.vmid - }; - // check auth for specific instance - const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; - const auth = await checkAuth(req.cookies, res, vmpath); - if (!auth) { - return; - } - // commit action - const result = await requestPVE(vmpath, "DELETE", req.cookies, null, pveAPIToken); - await handleResponse(params.node, result, res); -}); diff --git a/src/routes/cluster.js b/src/routes/cluster.js index b379774..04ac679 100644 --- a/src/routes/cluster.js +++ b/src/routes/cluster.js @@ -1,6 +1,15 @@ import { Router } from "express"; export const router = Router({ mergeParams: true }); +const db = global.db; +const requestPVE = global.pve.requestPVE; +const handleResponse = global.pve.handleResponse; +const checkAuth = global.utils.checkAuth; +const approveResources = global.utils.approveResources; +const pveAPIToken = global.db.pveAPIToken; +const getNodeAvailDevices = global.pve.getNodeAvailDevices; +const getUserResources = global.utils.getUserResources; + const nodeRegexP = "[\\w-]+"; const typeRegexP = "qemu|lxc"; const vmidRegexP = "\\d+"; @@ -18,3 +27,244 @@ import("./cluster/net.js").then((module) => { import("./cluster/pci.js").then((module) => { router.use(`${basePath}/pci`, module.router); }); + +/** + * GET - get available pcie devices given node and user + * request: + * - node: string - vm host node id + * responses: + * - 200: PVE PCI Device Object + * - 401: {auth: false} + * - 401: {auth: false, path: string} + * - 500: {error: string} + */ +router.get(`/:node(${nodeRegexP})/pci`, async (req, res) => { + const params = { + node: req.params.node + }; + // check auth + const auth = await checkAuth(req.cookies, res); + if (!auth) { + return; + } + const userNodes = db.getUserConfig(req.cookies.username).nodes; + if (!userNodes.includes(params.node)) { + res.status(401).send({ auth: false, path: params.node }); + res.end(); + return; + } + // get remaining user resources + const userAvailPci = (await getUserResources(req, req.cookies.username)).avail.pci; + // get node avail devices + let nodeAvailPci = await getNodeAvailDevices(params.node, req.cookies); + nodeAvailPci = nodeAvailPci.filter(nodeAvail => userAvailPci.some((userAvail) => { + return nodeAvail.device_name && nodeAvail.device_name.includes(userAvail); + })); + res.status(200).send(nodeAvailPci); + res.end(); +}); + +/** + * POST - set basic resources for vm + * request: + * - node: string - vm host node id + * - type: string - vm type (lxc, qemu) + * - vmid: number - vm id number + * - proctype: string - vm processor type + * - cores: number, optional - number of processor cores for instance + * - memory: number - amount of memory for instance + * - swap: number, optional - new amount of swap for instance + * responses: + * - 200: PVE Task Object + * - 401: {auth: false, path: string} + * - 500: {request: Object, error: string} + * - 500: PVE Task Object + */ +router.post(`${basePath}/resources`, async (req, res) => { + const params = { + node: req.params.node, + type: req.params.type, + vmid: req.params.vmid, + proctype: req.body.proctype, + cores: req.body.cores, + memory: req.body.memory, + swap: req.body.swap + }; + // check auth for specific instance + const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; + const auth = await checkAuth(req.cookies, res, vmpath); + if (!auth) { + return; + } + // get current config + const currentConfig = await requestPVE(`/nodes/${params.node}/${params.type}/${params.vmid}/config`, "GET", null, null, pveAPIToken); + const request = { + cores: Number(params.cores) - Number(currentConfig.data.data.cores), + memory: Number(params.memory) - Number(currentConfig.data.data.memory) + }; + if (params.type === "lxc") { + request.swap = Number(params.swap) - Number(currentConfig.data.data.swap); + } + else if (params.type === "qemu") { + request.cpu = params.proctype; + } + // check resource approval + if (!await approveResources(req, req.cookies.username, request)) { + res.status(500).send({ request, error: "Could not fulfil request." }); + res.end(); + return; + } + // setup action + let action = { cores: params.cores, memory: params.memory }; + if (params.type === "lxc") { + action.swap = Number(params.swap); + } + else if (params.type === "qemu") { + action.cpu = params.proctype; + } + action = JSON.stringify(action); + const method = params.type === "qemu" ? "POST" : "PUT"; + // commit action + const result = await requestPVE(`${vmpath}/config`, method, req.cookies, action, pveAPIToken); + await handleResponse(params.node, result, res); +}); + +/** + * POST - create new instance + * request: + * - node: string - vm host node id + * - type: string - vm type (lxc, qemu) + * - vmid: number - vm id number for instance + * - hostname: string, optional- hostname for lxc instance + * - name: string, optional - hostname for qemu instance + * - cores: number - number of cores for instance + * - memory: number - amount of memory for instance + * - swap: number, optional - amount of swap for lxc instance + * - password: string, optional - password for lxc instance + * - ostemplate: string, optional - os template name for lxc instance + * - rootfslocation: string, optional - storage name for lxc instance rootfs + * - rootfssize: number, optional, - size of lxc instance rootfs + * responses: + * - 200: PVE Task Object + * - 401: {auth: false, path: string} + * - 500: {error: string} + * - 500: {request: Object, error: string} + * - 500: PVE Task Object + */ +router.post(`${basePath}/create`, async (req, res) => { + const params = { + node: req.params.node, + type: req.params.type, + vmid: req.params.vmid, + hostname: req.body.hostname, + name: req.body.name, + cores: req.body.cores, + memory: req.body.memory, + swap: req.body.swap, + password: req.body.password, + ostemplate: req.body.ostemplate, + rootfslocation: req.body.rootfslocation, + rootfssize: req.body.rootfssize + }; + // check auth + const auth = await checkAuth(req.cookies, res); + if (!auth) { + return; + } + // get user db config + const user = await db.getUserConfig(req.cookies.username); + const vmid = Number.parseInt(params.vmid); + const vmidMin = user.cluster.vmid.min; + const vmidMax = user.cluster.vmid.max; + // check vmid is within allowed range + if (vmid < vmidMin || vmid > vmidMax) { + res.status(500).send({ error: `Requested vmid ${vmid} is out of allowed range [${vmidMin},${vmidMax}].` }); + res.end(); + return; + } + // check node is within allowed list + if (!user.nodes.includes(params.node)) { + res.status(500).send({ error: `Requested node ${params.node} is not in allowed nodes [${user.nodes}].` }); + res.end(); + return; + } + // setup request + const request = { + cores: Number(params.cores), + memory: Number(params.memory) + }; + if (params.type === "lxc") { + request.swap = params.swap; + request[params.rootfslocation] = params.rootfssize; + } + for (const key of Object.keys(user.templates.instances[params.type])) { + const item = user.templates.instances[params.type][key]; + if (item.resource) { + if (request[item.resource.name]) { + request[item.resource.name] += item.resource.amount; + } + else { + request[item.resource.name] = item.resource.amount; + } + } + } + // check resource approval + if (!await approveResources(req, req.cookies.username, request)) { // check resource approval + res.status(500).send({ request, error: "Not enough resources to satisfy request." }); + res.end(); + return; + } + // setup action by adding non resource values + let action = { + vmid: params.vmid, + cores: Number(params.cores), + memory: Number(params.memory), + pool: user.cluster.pool + }; + for (const key of Object.keys(user.templates.instances[params.type])) { + action[key] = user.templates.instances[params.type][key].value; + } + if (params.type === "lxc") { + action.hostname = params.name; + action.unprivileged = 1; + action.features = "nesting=1"; + action.password = params.password; + action.ostemplate = params.ostemplate; + action.rootfs = `${params.rootfslocation}:${params.rootfssize}`; + } + else { + action.name = params.name; + } + action = JSON.stringify(action); + // commit action + const result = await requestPVE(`/nodes/${params.node}/${params.type}`, "POST", req.cookies, action, pveAPIToken); + await handleResponse(params.node, result, res); +}); + +/** + * DELETE - destroy existing instance + * request: + * - node: string - vm host node id + * - type: string - vm type (lxc, qemu) + * - vmid: number - vm id number to destroy + * responses: + * - 200: PVE Task Object + * - 401: {auth: false, path: string} + * - 500: PVE Task Object + */ +router.delete(`${basePath}/delete`, async (req, res) => { + const params = { + node: req.params.node, + type: req.params.type, + vmid: req.params.vmid + }; + // check auth for specific instance + const vmpath = `/nodes/${params.node}/${params.type}/${params.vmid}`; + const auth = await checkAuth(req.cookies, res, vmpath); + if (!auth) { + return; + } + // commit action + const result = await requestPVE(vmpath, "DELETE", req.cookies, null, pveAPIToken); + await handleResponse(params.node, result, res); +}); diff --git a/src/routes/global.js b/src/routes/global.js new file mode 100644 index 0000000..2f2b6da --- /dev/null +++ b/src/routes/global.js @@ -0,0 +1,29 @@ +import { Router } from "express"; +export const router = Router({ mergeParams: true }); + +const db = global.db; +const checkAuth = global.utils.checkAuth; + +/** + * GET - get db global resource configuration + * responses: + * - 200: Object + */ +router.get("/config/:key", async (req, res) => { + const params = { + key: req.params.key + }; + // check auth + const auth = await checkAuth(req.cookies, res); + if (!auth) { + return; + } + const allowKeys = ["resources"]; + if (allowKeys.includes(params.key)) { + const config = db.getGlobalConfig(); + res.status(200).send(config[params.key]); + } + else { + res.status(401).send({ auth: false, error: `User is not authorized to access /global/config/${params.key}.` }); + } +});