const express = require('express') const router = express.Router() const db = require('qmi-cloud-common/mongo'); const passport = require('../passport'); const moment = require('moment'); const fs = require('fs-extra'); const azurecli = require('qmi-cloud-common/azurecli'); import { queues, TF_APPLY_QUEUE, TF_APPLY_QSEOK_QUEUE, TF_DESTROY_QUEUE } from 'qmi-cloud-common/queues'; const RUNNING_PERIOD_ON_SCHEDULE = 4; function timeRunningOnSchedule(p) { let totalRunningTime = p.timeRunning*1000*60; if ( p.statusVms === 'Running' ) { let runningFromTime = p.runningFrom? new Date(p.runningFrom).getTime() : new Date(p.created).getTime(); let now = new Date(); totalRunningTime = totalRunningTime + Math.abs(now.getTime() - runningFromTime); } let duration = moment.duration(totalRunningTime); return Math.abs(duration.asHours()); } /** * @swagger * /users: * get: * description: Get all users * summary: Get all users * tags: * - admin * produces: * - application/json * responses: * 200: * description: User */ router.get('/', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => { try { const result = await db.user.get(); return res.json(result); } catch (error) { next(error); } }); /** * @swagger * /users/me: * get: * description: Get profile logged-in user * summary: Get logged-in user * produces: * - application/json * responses: * 200: * description: User */ router.get('/me', passport.ensureAuthenticated, async (req, res, next) => { try { const result = await db.user.getById(req.user._id); return res.json(result); } catch (error) { next(error); } }); /** * @swagger * /users/{userId}: * get: * description: Get profile for an user * summary: Get profile for an user * parameters: * - name: userId * in: path * type: string * required: true * produces: * - application/json * responses: * 200: * description: User */ router.get('/:userId', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => { try { const result = await db.user.getById(req.params.userId); return res.json(result); } catch (error) { next(error); } }); /** * @swagger * /users/{userId}: * put: * description: Update profile for an user * summary: Update profile for an user * tags: * - admin * parameters: * - name: userId * in: path * type: string * required: true * - in: body * name: body * description: User object * required: true * produces: * - application/json * responses: * 200: * description: User */ router.put('/:userId', passport.ensureAuthenticatedAndAdmin, async (req, res, next) => { try { const result = await db.user.update(req.params.userId, req.body); return res.json(result); } catch (error) { next(error); } }); /** * @swagger * /users/{userId}/provisions: * post: * description: Start a new Terraform provision * summary: Start a new Terraform provision * produces: * - application/json * parameters: * - name: userId * in: path * type: string * required: true * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * scenario: * type: string * description: * type: string * vmImage: * type: object * properties: * vm1: * type: object * properties: * vmType: * type: string * version: * type: object * properties: * name: * type: string * image: * type: string * responses: * 200: * description: Provision * 404: * description: Scenario not found * 400: * description: Invalid vmImage */ router.post('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => { try { req.body.user = req.params.userId; const scenarioSource = await db.scenario.getOne({name: req.body.scenario}); if (!scenarioSource) { return res.status(404).json({"msg": "Scenario not found "}); } if (!req.body.vmImage || !req.body.vmImage.vm1 || !req.body.vmImage.vm1.vmType ) { return res.status(400).json({"msg": "Invalid vmImage"}); } req.body.scenarioVersion = scenarioSource.version; if ( req.body.scheduleData && req.body.scheduleData.is24x7 !== undefined ) { const schedule = await db.schedule.add(req.body.scheduleData); req.body.schedule = schedule._id; } const provision = await db.provision.add(req.body); if ( provision.scenario === "azqmi-qseok" ){ queues[TF_APPLY_QSEOK_QUEUE].add("tf_apply_qseok_job", { scenario: req.body.scenario, vmType: req.body.vmType, nodeCount: req.body.nodeCount, id: provision._id, user: req.user, _scenario: scenarioSource }); } else { queues[TF_APPLY_QUEUE].add("tf_apply_job", { scenario: req.body.scenario, vmType: req.body.vmType, nodeCount: req.body.nodeCount, id: provision._id, user: req.user, _scenario: scenarioSource }); } return res.status(200).json(provision); } catch (error) { next(error); } }); /** * @swagger * /users/{userId}/provisions/{id}: * put: * description: Update Provision by ID * summary: Update Provision by ID * parameters: * - name: userId * in: path * type: string * required: true * - name: id * in: path * type: string * required: true * - in: body * name: body * description: Provision object * required: true * produces: * - application/json * responses: * 200: * description: Provision */ router.put('/:userId/provisions/:id', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => { let provision = await db.provision.getById(req.params.id); if (!provision){ return res.status(404).json({"msg": "Not found privision with id "+req.params.id}); } try { let schedule; if ( req.body.scheduleData ) { // -- HERE onScheduleRenewed???? var duration = timeRunningOnSchedule(provision); //hours var onScheduleRenewed = req.body.scheduleData.onScheduleRenewed? req.body.scheduleData.onScheduleRenewed : 1; if ( duration >= (24 * RUNNING_PERIOD_ON_SCHEDULE * onScheduleRenewed) ) { onScheduleRenewed = Math.ceil( duration/(24*RUNNING_PERIOD_ON_SCHEDULE) ) + 1; console.log("HEY!! onScheduleRenewed has been incremented!! -> ", onScheduleRenewed); } req.body.scheduleData["onScheduleRenewed"] = onScheduleRenewed; // -- if ( req.body.scheduleData._id ) { schedule = await db.schedule.update(req.body.scheduleData._id, req.body.scheduleData); } else { schedule = await db.schedule.add(req.body.scheduleData); } var tagsEdit = { "24x7": schedule.is24x7? " " : false, "StartupTime": (schedule.isStartupTimeEnable && !schedule.is24x7 && schedule.utcTagStartupTime)? schedule.utcTagStartupTime : false, "ShutdownTime": (!schedule.is24x7 && schedule.utcTagShutdownTime)? schedule.utcTagShutdownTime : false } azurecli.updateVmsTags(provision._id, tagsEdit); } let patch = {}; if ( req.body.user ) { patch.user = req.body.user; } if ( schedule ) { patch.schedule = schedule._id; } let result = { provision: await db.provision.update(provision._id, patch) } return res.json(result); } catch (error) { next(error); } }); /** * @swagger * /users/{userId}/provisions/{id}: * delete: * description: Delete Provision by ID * summary: Delete a Terraform Provision by ID * produces: * - application/json * parameters: * - name: userId * in: path * type: string * required: true * - name: id * in: path * type: string * required: true * responses: * 200: * description: Provision * 404: * description: Not found * */ router.delete('/:userId/provisions/:id', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => { try { const provision = await db.provision.getById(req.params.id); if (!provision){ return res.status(404).json({"msg": "Not found privision with id "+req.params.id}); } //var delDest = provision.destroy._id; //if ( provision.destroy ) { // delDest = await db.destroy.del(provision.destroy._id); //} const delProv = await db.provision.update(req.params.id, {"isDeleted": true}); //Move folder if (fs.existsSync(`/provisions/${provision.scenario}_${req.params.id}`)) { fs.moveSync(`/provisions/${provision.scenario}_${req.params.id}`, `/provisions/deleted/${provision.scenario}_${req.params.id}`, { overwrite: true }) } return res.json({"provision": delProv, "destroy": delProv.destroy}); } catch (error) { next(error); } }); /** * @swagger * /users/{userId}/provisions/{id}/deallocatevms: * post: * description: Stop all VMs for this provision * summary: Stop all VMs for this provision * produces: * - application/json * parameters: * - name: userId * in: path * type: string * required: true * - name: id * in: path * type: string * required: true * responses: * 200: * description: Provision * 404: * description: Not found * */ router.post('/:userId/provisions/:id/deallocatevms', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => { try { let provision = await db.provision.getById(req.params.id); if (!provision){ return res.status(404).json({"msg": "Not found provision with id "+req.params.id}); } //Set DivvyTags according to Schedule if ( provision.schedule && req.body.isStartupTimeEnable !== undefined ) { console.log("Set DivvyTags according to schedule"); var schedule = await db.schedule.update(provision.schedule._id, { "isStartupTimeEnable": req.body.isStartupTimeEnable }); var tagsEdit = { "24x7": schedule.is24x7? " " : false, "StartupTime": (schedule.isStartupTimeEnable && !schedule.is24x7 && schedule.utcTagStartupTime)? schedule.utcTagStartupTime : false, "ShutdownTime": (!schedule.is24x7 && schedule.utcTagShutdownTime)? schedule.utcTagShutdownTime : false } azurecli.updateVmsTags(provision._id, tagsEdit); } azurecli.deallocate(provision._id); return res.json({"statusVms": "Stopping"}); } catch (error) { db.provision.update(req.params.id, {"statusVms": "Error_Stopping"}); next(error); } }); /** * @swagger * /users/{userId}/provisions/{id}/startvms: * post: * description: Start all VMs for this provision * summary: Start all VMs for this provision * produces: * - application/json * parameters: * - name: userId * in: path * type: string * required: true * - name: id * in: path * type: string * required: true * responses: * 200: * description: Provision * 404: * description: Not found * */ router.post('/:userId/provisions/:id/startvms', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => { try { let provision = await db.provision.getById(req.params.id); if (!provision){ return res.status(404).json({"msg": "Not found privision with id "+req.params.id}); } azurecli.start(provision._id); //Re-enable DivvyTags according to schedule if ( provision.schedule ) { // -- onScheduleRenewed???? var duration = timeRunningOnSchedule(provision); //hours var onScheduleRenewed = provision.schedule.onScheduleRenewed? provision.schedule.onScheduleRenewed : 1; if ( duration >= (24 * RUNNING_PERIOD_ON_SCHEDULE * onScheduleRenewed) ) { onScheduleRenewed = Math.ceil( duration/(24*RUNNING_PERIOD_ON_SCHEDULE) ) + 1; console.log("HEY!! onScheduleRenewed has been incremented!! -> ", onScheduleRenewed); } //let schedule = await db.schedule.update(provision.schedule._id, {"isStartupTimeEnable": true, "onScheduleRenewed": onScheduleRenewed}); let schedule = await db.schedule.update(provision.schedule._id, {"onScheduleRenewed": onScheduleRenewed}); console.log("Re-enabling DivvyTags according to schedule"); var tagsEdit = { "24x7": schedule.is24x7? " " : false, "StartupTime": (schedule.isStartupTimeEnable && !schedule.is24x7 && schedule.utcTagStartupTime)? schedule.utcTagStartupTime : false, "ShutdownTime": (!schedule.is24x7 && schedule.utcTagShutdownTime)? schedule.utcTagShutdownTime : false } azurecli.updateVmsTags(provision._id, tagsEdit); } return res.json({"statusVms": "Starting"}); } catch (error) { db.provision.update(req.params.id, {"statusVms": "Error_Starting"}); next(error); } }); /** * @swagger * /users/{userId}/provisions/{id}/extend: * post: * description: Extend this provision Running more time * summary: Extend this provision Running more time * produces: * - application/json * parameters: * - name: userId * in: path * type: string * required: true * - name: id * in: path * type: string * required: true * responses: * 200: * description: Provision * 404: * description: Not found * */ router.post('/:userId/provisions/:id/extend', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => { try { let provision = await db.provision.getById(req.params.id); if (!provision){ return res.status(404).json({"msg": "Not found privision with id "+req.params.id}); } /*if ( provision.countExtend === 5 ) { return res.status(200).json({"msg": "You have reached the limit for the number of times to extend the Running VMs period."}); }*/ let timeRunning = db.utils.getNewTimeRunning(provision); let countExtend = db.utils.getNewCountExtend(provision); provision = await db.provision.update(req.params.id, {"runningFrom":new Date(), "timeRunning": timeRunning, "countExtend": countExtend, "pendingNextAction": undefined}); console.log(`Extending running period fo provision (${provision._id}), new total extends: ${countExtend}`); return res.json(provision); } catch (error) { next(error); } }); /** * @swagger * /users/{userId}/provisions/{id}/destroy: * post: * description: Destroy a Terraform Provision * summary: Destroy a Terraform Provision * produces: * - application/json * parameters: * - name: userId * in: path * type: string * required: true * - name: id * in: path * type: string * required: true * responses: * 200: * description: Provision * 404: * description: Not found * */ router.post('/:userId/provisions/:id/destroy', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => { try { let provision = await db.provision.getById(req.params.id); if (!provision){ return res.status(404).json({"msg": "Not found privision with id "+req.params.id}); } const destroyJob = await db.destroy.add({ "user": req.params.userId }); provision = await db.provision.update(req.params.id, {"destroy": destroyJob._id}); const scenarioSource = await db.scenario.getOne({name: provision.scenario}); queues[TF_DESTROY_QUEUE].add("tf_destroy_job", { scenario: provision.scenario, provId: provision._id, user: req.user, id: destroyJob._id, _scenario: scenarioSource }); return res.status(200).json(provision); } catch (error) { return res.status(error.output.statusCode).json({"err":error}); } }); /** * @swagger * /users/{userId}/provisions: * get: * description: Get all Provisions for an User * summary: Get all Provisions for an User * produces: * - application/json * parameters: * - name: userId * in: path * type: string * required: true * responses: * 200: * description: JSON Array */ router.get('/:userId/provisions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => { try { const filter = {"user": req.params.userId, "isDeleted": false}; const result = await db.provision.get(filter); return res.json(result); } catch (error) { next(error); } }); /** * @swagger * /users/{userId}/destroyprovisions: * get: * description: Get all Destroy Provisions for an User * summary: Get all Destroy Provisions for an User * produces: * - application/json * parameters: * - name: userId * in: path * type: string * required: true * responses: * 200: * description: JSON Array */ router.get('/:userId/destroyprovisions', passport.ensureAuthenticatedAndIsMe, async (req, res, next) => { try { const result = await db.destroy.get({"user": req.params.userId}); return res.json(result); } catch (error) { next(error); } }); module.exports = router;