portal/db/MongoDBImpl.js

203 lines
6.7 KiB
JavaScript

/* EdgeVPNio
* Copyright 2021, University of Florida
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
const mongoose = require("mongoose");
const { DataBaseInterface } = require("./DatabaseInterface");
const { overlayModel, topologyModel } = require("./Model");
const { DataTransformer } = require("../controllers/DataTransformer");
class MongoDBImpl extends DataBaseInterface {
// Stores the url and dbname and is available for every instance
constructor(url, dbname) {
super(url);
// Once an instance is created the db connection is kept until the instance is alive.
this.connection = mongoose.connect(url, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
});
this.dbname = dbname;
this.db = mongoose.connection.client;
}
/**
* Getter function for the db attribute
*/
getDb() {
return this.db;
}
/**
* Function to insert the PUT data sent by the EVIO nodes into
* collections: 1) Overlays
* 2) Topology
*
* @param {Json} data Aggregated data Object
* @param {Number} timestamp End timestamp at which object was taken.
*/
async insertInto(data, timestamp) {
var dataTrasformer = new DataTransformer();
try {
// The data is transformed to the required form and returned as an array of arrays.
var transformedData = dataTrasformer.transformData(data);
//console.log("transformedData: ", JSON.stringify(transformedData));
var overlaySaveData = new overlayModel({
_id: timestamp,
Overlays: transformedData[0], // Overlays array
});
// Overlay data is put into the db with the below call.
overlaySaveData.save(function (err) {
if (err) {
console.log(err.stack);
}
//console.log("Saved Overlay data for timestamp: " + timestamp);
});
var topologySaveData = new topologyModel({
_id: timestamp,
Topology: transformedData[1], // Topology Array
});
// Topology data is put into the db with the below call.
topologySaveData.save(function (err) {
if (err) {
console.log(err.stack);
}
//console.log("Saved Topology data for timestamp:" + timestamp);
});
} catch (err) {
console.error(err);
}
}
/**
* Database call to get the intervals stored.
*
* @param {String} tableName Model Name to use to find the intervals.
*/
async getIntervals(tableName) {
return tableName.find({}, { Overlays: 0 });
}
/**
* Database call to get the high level Overlay information.
*
* @param {String} tableName Model to use to get the overlay.
* @param {Float} intervalId Interval identifier to query.
*/
async findOverlays(tableName, intervalId) {
if (intervalId) {
//Find the next available interval, greater than the previous one from client
return tableName
.find({ _id: { $gt: intervalId } })
.sort({ _id: 1 })
.limit(1);
}
//Most recent entry - intervalId not passed
return tableName.find().sort({ _id: -1 }).limit(1);
}
/**
* Database call to get the detailed topology information.
*
* @param {String} tableName
* @param {Float} intervalId
* @param {String} overlayId
*/
async findTopology(tableName, intervalId, overlayId) {
if (intervalId) {
//Find the next available interval, greater than the previous one from client
return tableName
.find(
{
Topology: { $elemMatch: { OverlayId: overlayId } },
_id: { $gt: intervalId },
},
{ "Topology.$": 1 }
)
.sort({ _id: 1 })
.limit(1);
}
//Most recent entry - intervalId not passed
return tableName
.find(
{ Topology: { $elemMatch: { OverlayId: overlayId } } },
{ "Topology.$": 1 }
)
.sort({ _id: -1 })
.limit(1);
}
async getOverlays(tableName, intervalId) {
var overlayData = await this.findOverlays(tableName, intervalId);
if (Object.keys(overlayData).length === 0) {
const pipeline = [{ $match: { operationType: "insert" } }]; //watch for insert operation
const overlayChangeStream = this.db
.db("Evio")
.collection("Overlays")
.watch(pipeline);
await Promise.resolve(overlayChangeStream.hasNext());
let newData = await Promise.resolve(overlayChangeStream.next());
overlayData = [newData.fullDocument];
overlayChangeStream.close();
}
return overlayData;
}
/**
* Function to query Topology collection, watch the collection for insert op every 1 second
* @param {String} tableName
* @param {Float} intervalId
* @param {String} overlayId
* @returns inserted data to topology collection
*/
async getTopology(tableName, intervalId, overlayId) {
var topologyData = await this.findTopology(
tableName,
intervalId,
overlayId
);
if (Object.keys(topologyData).length === 0) {
const pipeline = [{ $match: { operationType: "insert" } }];
const topologyChangeStream = this.db
.db("Evio")
.collection("Topology")
.watch(pipeline);
await Promise.resolve(topologyChangeStream.hasNext());
topologyChangeStream.close();
topologyData = await this.findTopology(tableName, intervalId, overlayId);
}
return topologyData;
}
/**
* Database call to get the intervals stored.
*
* @param {mogoose.Model} tableName Model Name to use to find the intervals.
* @param {String} nodeId
*/
async getEvioControl(tableName, nodeId) {
return tableName.find(
{ "EvioControl.NodeId": { $eq: nodeId } }
).sort({ _id: -1 }).limit(1);
}
}
module.exports = { MongoDBImpl };