import { QueueClient, QueueServiceClient } from "@azure/storage-queue" import {TableClient, TableTransaction} from '@azure/data-tables' //import uuidv1 from "uuid/v1"; import { createRequire } from "module"; const require = createRequire(import.meta.url); const connects = require("./AzureConnects.json"); /* description: file to house classes/interfaces that will handle many of the details for interacting with Azure services, currently Table and Queue */ /* Interface class to handle processing messages from Azure Queue. Provides an extra layer of abstraction so the back-end only has to call simple functions. */ export class QueueInterface { /* Message formats: v1: "vmID:currentTest:status", v2(TBD): "versionNum:vmID:currentTest:status" */ constructor(queueName) { this.connection = QueueServiceClient .fromConnectionString(connects.azureConnect) .getQueueClient(queueName); } /* desc: gets the next message in the queue, dequeues it and returns the message text. params: N/A returns: String message: message text from the dequeued message */ async getNextMessage() { const response = await this.connection.receiveMessages(); if (response.receivedMessageItems.length == 1) { const dequeuedMessage = response.receivedMessageItems[0]; let message = dequeuedMessage.messageText; console.log(`Processing & deleting message with content: ${message}`); // do processing here const deletedMessage = await this.connection.deleteMessage( dequeuedMessage.messageId, dequeuedMessage.popReceipt ); console.log( `Delete message successfully, service assigned request Id: ${deletedMessage.requestId}` ); return message; } return null; } /* Desc: Adds a given message to the queue params: String message : Message to add to the queue returns: Success or failure */ async addMessage(message) { const response = await this.connection.sendMessage(message); console.log( `Sent message successfully, message Id: ${response.messageId}, request Id: ${response.requestId}` ); return true; } /* Desc: checks if there are messages in the queue, logs and returns amount of messages and up to 5 messages at the front params: N/A returns: Promise Object : {length: int, headMessages: array} */ async checkMessages() { const messages = await this.connection.peekMessages({numberOfMessages: 5}); let len = messages.peekedMessageItems.length; let headMessages = []; console.log("Head messages: "); for(let i = 0; i < 5 && i < len; i++) { headMessages.push(messages.peekedMessageItems[i].messageText); console.log(messages.peekedMessageItems[i].messageText); } return {"headMessages": headMessages}; } /* Desc: clears all messages in the queue, none are saved or cached. params: N/A returns: N/A TODO: Use the literal clear messages function provided by azure */ async clearMessages() { this.connection.clearMessages(); } } /* */ export class TableInterface { /* Entity formats: JSON string data should be stored in each entry using "data" as the indentifier. Ex: { partitionKey: rowKey: ... data: "{dataObj}" } Issues Current (v2): { "id":"VG-11", "desc":null, "vm": "wi11", // vm row key "fw": "4798123" // fw row key } Issues To Be Added (v2.1): { "version": "2.1" "id":"VG-11", "desc":null, "vm": "wi11", // vm row key "fw": "4798123" // fw row key } VM Data Example (v1): { "vm":"wi11", "vmlinkWin": "", // rdp link "vmlinkLin": "", // other link "vmstatus":"In-Progress", "owner":"John Deer", "vmlocation":"Madison", "vmversion":"Windows" } FW Data Example (v1): { "product":"WD Red", "status":"Passed", "fwversion":"2.3.2", "fwlink": "", // ffu link "commitid":111727, "form":"7.5in", "fwlocation":"San Francisco", "serial":128648 } */ constructor(tableName) { this.connection = TableClient.fromConnectionString(connects.azureConnect, tableName); } /* Desc: params: Keys for each partition you want to pull from returns: Query String */ getQueryString() { var stringArr = arguments[0].split(" "); var length = stringArr.length; if( length === 0 ){ console.log("Zero arguments given to \'getQueryString()\'"); throw "Zero arguments given to \'getQueryString()\'"; } let utilStr = "PartitionKey eq "; let returnStr = ""; for (var index = 0, length; index < length; index++) { console.log(stringArr[index]) if(index == length - 1) { returnStr = returnStr + utilStr + stringArr[index]; } else { returnStr = returnStr + utilStr + stringArr[index] + " or "; } console.log("returnStr:\t" + returnStr); } return returnStr; } /* desc: A general function to handle getting data from the database using specific types. This will provide a quick way of getting the data in a less friendly way, the other specific data functions will use this one to provide a friendly interface params: Int type : the type of data we want, like the partition key String ?role : if the user is requesting issue data, a role must be provided String ?id : the row key of the requested data, if not provided, all data from that partition will be fetched returns: A object containing the requested data. */ async getData(type, role = -1, id = -1) { try { var partition = ""; var filter = ""; var list = []; var data = null; switch (type) { // Issues case 0: if(id === -1) { filter = this.getQueryString("\'FWIssues\' \'DevIssues\' \'ExeIssues\'"); } else if(role === "FW" || role === "Dev" || role === "Exe") { partition = role + "Issues" } else if (role === -1) { return {result: "Failed", desc: "invalid role type"}; } break; // VMs case 1: if(id === -1) { filter = this.getQueryString("\'VM\'"); } else { partition = "VM"; } break; // FW case 2: if(id === -1) { filter = this.getQueryString("\'FW\'"); } else { partition = "FW"; } break; // System case 3: if(id === -1) { filter = this.getQueryString("\'System\'"); } else { partition = "System"; } break; // User case 4: if(id === -1) { filter = this.getQueryString("\'User\'"); } else { partition = "User"; } break; default: filter = "" } if(id === -1) { data = this.connection.listEntities({queryOptions: {filter: filter}}); for await (const entity of data) { list.unshift(entity); } return {result: "Success", data: list}; } else { data = await this.connection.getEntity(partition, id); return {result: "Success", data: data}; } } catch(RestError) { console.log("failed to get data with id: " + id); return {result: "Failed", data: RestError.message}; } } /* desc: A general function to handle adding data to the database using specific types. This will provide a quick way of adding the data in a less friendly way, the other specific data functions will use this one to provide a friendly interface params: Int type : the type of data we want to add, like the partition key String id : the row key of the data to be added Object data : the data to be added to the database String ?role : if the user is adding issue data, a role must be provided returns: A object containing the result of the operation */ async addData(type, id, data, role = -1) { try { var partition = ""; var result = null; switch (type) { // Issues case 0: if(role === "FW" || role === "Dev" || role === "Exe") { partition = role + "Issues" } else { return {result: "Failed", desc: "invalid role type"}; } break; // VMs case 1: partition = "VM"; break; // FW case 2: partition = "FW"; break; // System case 3: partition = "System"; break; // User case 4: partition = "User"; break; default: return {result: "Failed", desc: "invalid type"}; } //let dataStr = JSON.stringify(data); result = await this.connection.createEntity({partitionKey: partition, rowKey: id, ...data}); console.log("Added data to " + partition + " with id: " + id + "\n"); return {result: "Success", data: result}; } catch(RestError) { if(RestError.details.odataError.code === "EntityAlreadyExists") { console.log("Failed to add issue to " + partition + " with id: " + id); console.log("entity already exists\n"); } else { console.log("Failed to add issue to " + partition + " with id: " + id); console.log(RestError.details.odataError.message.value + "\n"); } return {result: "Failed", data: RestError.message}; } } /* desc: A general function to handle modifying data in the database using specific types. This will provide a quick way of modifying the data in a less friendly way, the other specific data functions will use this one to provide a friendly interface params: Int type : the type of data we want, like the partition key String id : the row key of the requested data, if not provided, all data from that partition will be fetched Int op? : the operation to perform, 0 for deletion, 1 for merge update, 2 for replace update String ?role : if the user is requesting issue data, a role must be provided Object ?mods : the modifications to perform in the DB if op is 1 or 2 returns: A object containing the result of the operation */ async modData(type, id, op = 0, role = -1, mods = null) { try { var partition = ""; var result = null; var entity = {}; switch (type) { // Issues case 0: if(role === "FW" || role === "Dev" || role === "Exe") { partition = role + "Issues" } else { return {result: "Failed", desc: "invalid role type"}; } break; // VMs case 1: partition = "VM"; break; // FW case 2: partition = "FW"; break; // System case 3: partition = "System"; break; // User case 4: partition = "User"; break; default: return {result: "Failed", data: "invalid type"}; } if(op === 0) { result = await this.connection.deleteEntity(partition, id); console.log("Removed issue in " + partition + " with id: " + id + "\n", result); return {result: "Success", data: result}; } else if(op === 1) { entity = {partitionKey: partition, rowKey: id, ...mods}; result = await this.connection.updateEntity(entity); return {result: "Success", data: result}; } else if(op === 2) { entity = {partitionKey: partition, rowKey: id, ...mods}; result = await this.connection.updateEntity(entity, "Replace"); return {result: "Success", data: result}; } else { console.log("Failed to modify issue in " + partition + " with id: " + id + "\n"); return {result: "Failed", data: "Invalid Operation"}; } } catch(RestError) { console.log("Failed to modify issue in " + partition + " with id: " + id + "\n"); return {result: "Failed", data: RestError.message}; } } /* Desc: requests all data about a given issue from the database using the issue id as a key. params: String role: user role, FW, Dev or Exe String id: the ID of the issue we want. returns: Promise Object: { "vm": "vmstatus": "owner": "vmlocation": "vmversion": "product": "status": "fwversion": "commitid": "form": "fwlocation": "serial": } */ async getIssueByID(role, id) { return this.getData(0, role, id); } /* Desc: requests all data about all issues params: N/A returns: Promise Object: [List of issue objects] */ async getAllIssues() { return this.getData(0); } /* Desc: Adds a new issue to the database. params: String role: user role, FW, Dev or Exe Object issue: { "id": "desc": "data": { "vm": "vmstatus": "owner": "vmlocation": "vmversion": "product": "status": "fwversion": "commitid": "form": "fwlocation": "serial": } } returns: Promise Object: { "result": "data": } */ async addIssue(role, issue) { return this.addData(0, "" + issue.id, issue, role); } /* desc: Removes a issue from the database params: String role: user role for the issue (FW, Dev or Exe) String id: Jira id of the issue to remove returns: result of the deletion */ async removeIssue(role, id) { return this.modData(0, "" + id, 0, role); } /* Desc: Gets data about a VM by using its ID as a key params: String id: ID of the VM we want returns: Promise Object: { TODO } */ async getVMByID(id) { return this.getData(1, "", id); } /* Desc: Gets data about all VMs params: N/A returns: Promise Object: { [List of VM object] } */ async getAllVMs() { return this.getData(1); } /* Desc: Adds a VM to the database params: Object vm: { TODO } returns: Promise Object: { "result": "desc": } */ async addVM(vm) { return this.addData(1, vm.vm, vm); } /* */ async removeVM(vm) { return this.modData(1, vm); } /* Desc: Gets data about a VM by using its ID as a key params: String id: ID of the VM we want returns: Promise Object: { TODO } */ async getFWByID(id) { return this.getData(2, "", id + ""); } /* Desc: Gets data about all VMs params: N/A returns: Promise Object: { [List of VM object] } */ async getAllFWs() { return this.getData(2); } /* Desc: Adds a VM to the database params: Object vm: { TODO } returns: Promise Object: { "result": "desc": } */ async addFW(fw) { return this.addData(2, fw.serial + "", fw); } /* */ async removeFW(fw) { return this.modData(2, fw + ""); } /* Desc: Adds a new reservation to the calendar, checks for conflicts params: Object: { "id": "vm": "startTime": "runTime": } returns: Promise Object: { "result": "desc": } */ async addReservation(system, reservation) { let systemData = await this.getSystemByID(system); // console.log(systemData); let schedule = JSON.parse(systemData.data.schedule); let newSchedule = []; let startDate = (new Date(reservation.start)).getTime(); let endDate = (new Date(reservation.end)).getTime(); let resStart = {}; let resEnd = {}; for(let res of schedule) { if(res.vm == reservation.vm) { resStart = (new Date(res.start)).getTime(); resEnd = (new Date(res.end)).getTime(); if(((startDate >= resStart && startDate <= resEnd) || (endDate >= resStart && endDate <= resEnd) || (resStart >= startDate && resStart <= endDate) || (resEnd >= startDate && resEnd <= endDate))) { console.log("Conflict\nnew res: " + startDate + "-" + endDate + "\n\ old res: " + resStart + "-" + resEnd); } else { newSchedule.push(res); } } else { newSchedule.push(res); } } newSchedule.push(reservation); schedule = JSON.stringify(newSchedule); let result = await this.modData(3, system, 1, "", {schedule: schedule}); return result; } /* Desc: Adds a new reservation to the calendar, checks for conflicts params: Object: { "id": "vm": "startTime": "runTime": } returns: Promise Object: { "result": "desc": } */ async removeOldReservations(system) { let systemData = await this.getData(3, "", system); let schedule = JSON.parse(systemData.data.schedule); let newSchedule = []; for(const res of schedule) { if(res != null) { console.log(new Date(res.end).getTime(), new Date().getTime()); if(new Date(res.end).getTime() > new Date().getTime()) { newSchedule.push(res); } else { console.log("Removing old res that ended on: " + res.end); } } } newSchedule = JSON.stringify(newSchedule); let result = await this.modData(3, system, 1, "", {schedule: newSchedule}); return result; } /* */ async getSystemByID(id) { return this.getData(3, "", id); } /* */ async getAllSystems() { return this.getData(3); } /* Desc: Adds a System to the database params: Object system: { id: "VICE1", schedule: [] } returns: Promise Object: { "result": "data": } */ async addSystem(system) { return this.addData(3, system.id, system); } /* */ async removeSystem(id) { return this.modData(3, id); } /* */ async getUserByID(id) { return this.getData(4, "", id); } /* */ async addUser(user) { return this.addData(4, user.id, user); } /* */ async removeUser(id) { return this.modData(4, id); } }