Initial release

Change-Id: I04faafd3e3a508d9359138f07e3a10fa2d84689f
This commit is contained in:
Fotis Paraskevopoulos 2024-04-25 13:41:05 +03:00 committed by Radosław Piliszek
parent 29ecb6b8b6
commit efc6c9f6da
9 changed files with 412 additions and 262 deletions

4
.env Normal file
View File

@ -0,0 +1,4 @@
ACTIVEMQ_PORT=
ACTIVEMQ_HOST=
ACTIVEMQ_USERNAME=
ACTIVEMQ_PASSWORD=

View File

@ -1,5 +1,5 @@
require('dotenv').config();
const container= require('rhea');
const connection_options={ const connection_options={
'port': process.env.ACTIVEMQ_PORT, 'port': process.env.ACTIVEMQ_PORT,
@ -11,13 +11,14 @@ const connection_options={
if (!connection_options.port || !connection_options.host) { if (!connection_options.port || !connection_options.host) {
console.error("No connection option provided for EXN skipping asynchronous messaging") console.error("No connection option provided for EXN skipping asynchronous messaging");
return
} else {
const connection = container.connect(connection_options);
} }
const container= require('rhea');
let connection;
let sender_sal_nodecandidate_get; let sender_sal_nodecandidate_get;
let sender_sal_cloud_get; let sender_sal_cloud_get;
let sender_sal_cloud_post; let sender_sal_cloud_post;
@ -37,8 +38,6 @@ let sender_ui_policies_model_upsert;
const correlations = {} const correlations = {}
container.on('message', (context)=>{ container.on('message', (context)=>{
// console.log("Received ",context.message)
if(context.message.correlation_id in correlations){ if(context.message.correlation_id in correlations){
if(context.message.body.metaData['status'] >= 400){ if(context.message.body.metaData['status'] >= 400){
correlations[context.message.correlation_id]['reject'](context.message.body['message']) correlations[context.message.correlation_id]['reject'](context.message.body['message'])
@ -53,16 +52,16 @@ container.on('connection_open', function (context) {
console.log("Connected ",context.container.id); console.log("Connected ",context.container.id);
context.connection.open_receiver('topic://eu.nebulouscloud.exn.sal.cloud.get.reply') context.connection.open_receiver('topic://eu.nebulouscloud.exn.sal.cloud.get.reply')
context.connection.open_receiver('topic://eu.nebulouscloud.exn.sal.cloud.post.reply') context.connection.open_receiver('topic://eu.nebulouscloud.exn.sal.cloud.create.reply')
context.connection.open_receiver('topic://eu.nebulouscloud.exn.sal.cloud.delete.reply') context.connection.open_receiver('topic://eu.nebulouscloud.exn.sal.cloud.delete.reply')
context.connection.open_receiver('topic://eu.nebulouscloud.exn.sal.nodecandidate.get.reply') context.connection.open_receiver('topic://eu.nebulouscloud.exn.sal.nodecandidate.get.reply')
context.connection.open_receiver('topic://eu.nebulouscloud.exn.sal.node.post.reply') context.connection.open_receiver('topic://eu.nebulouscloud.exn.sal.node.create.reply')
sender_sal_nodecandidate_get = context.connection.open_sender('topic://eu.nebulouscloud.exn.sal.nodecandidate.get'); sender_sal_nodecandidate_get = context.connection.open_sender('topic://eu.nebulouscloud.exn.sal.nodecandidate.get');
sender_sal_cloud_get = context.connection.open_sender('topic://eu.nebulouscloud.exn.sal.cloud.get'); sender_sal_cloud_get = context.connection.open_sender('topic://eu.nebulouscloud.exn.sal.cloud.get');
sender_sal_cloud_post = context.connection.open_sender('topic://eu.nebulouscloud.exn.sal.cloud.post'); sender_sal_cloud_post = context.connection.open_sender('topic://eu.nebulouscloud.exn.sal.cloud.create');
sender_sal_cloud_delete = context.connection.open_sender('topic://eu.nebulouscloud.exn.sal.cloud.delete'); sender_sal_cloud_delete = context.connection.open_sender('topic://eu.nebulouscloud.exn.sal.cloud.delete');
sender_sal_node_post = context.connection.open_sender('topic://eu.nebulouscloud.exn.sal.node.post'); sender_sal_node_post = context.connection.open_sender('topic://eu.nebulouscloud.exn.sal.node.create');
sender_ui_application_new = context.connection.open_sender('topic://eu.nebulouscloud.ui.application.new'); sender_ui_application_new = context.connection.open_sender('topic://eu.nebulouscloud.ui.application.new');
sender_ui_application_updated = context.connection.open_sender('topic://eu.nebulouscloud.ui.application.updated'); sender_ui_application_updated = context.connection.open_sender('topic://eu.nebulouscloud.ui.application.updated');
@ -77,7 +76,6 @@ container.on('connection_open', function (context) {
connection = container.connect();
const {v4: uuidv4} = require("uuid"); const {v4: uuidv4} = require("uuid");
@ -91,14 +89,15 @@ module.exports = {
'reject':reject, 'reject':reject,
}; };
const message = { const message = {
to: sender_ui_deploy_application_new.options.target.address, to: sender_ui_application_new.options.target.address,
correlation_id: correlation_id, correlation_id: correlation_id,
message_annotations: {application: uuid},
body:{ body:{
uuid: uuid uuid: uuid
} }
} }
console.log("Send ", message) console.log("Send ", message)
sender_ui_deploy_application_new.send(message) sender_ui_application_new.send(message)
}) })
}, },
@ -113,21 +112,28 @@ module.exports = {
const message = { const message = {
to: sender_ui_application_dsl_json.options.target.address, to: sender_ui_application_dsl_json.options.target.address,
correlation_id: correlation_id, correlation_id: correlation_id,
message_annotations: {application: uuid},
application_properties: {application: uuid},
body:json body:json
} }
sender_ui_application_dsl_json.send(message) sender_ui_application_dsl_json.send(message)
console.log("Sending ", sender_ui_application_dsl_metric.options.target.address, uuid,json) const metric_model_payload ={
const metrci_message = { 'application': uuid,
to: sender_ui_application_dsl_metric.options.target.address,
correlation_id: correlation_id,
body:{
'yaml': yaml 'yaml': yaml
} }
console.log("Sending ", sender_ui_application_dsl_metric.options.target.address, uuid, metric_model_payload)
const metric_message = {
to: sender_ui_application_dsl_metric.options.target.address,
correlation_id: correlation_id,
message_annotations: {application: uuid},
application_properties: {application: uuid},
body:metric_model_payload
} }
sender_ui_application_dsl_metric.send(message) sender_ui_application_dsl_metric.send(metric_message)
return resolve()
}) })
}, },
@ -139,18 +145,20 @@ module.exports = {
'reject':reject, 'reject':reject,
}; };
const message = { const message = {
to: sender_ui_deploy_application_new.options.target.address, to: sender_ui_application_updated.options.target.address,
correlation_id: correlation_id, correlation_id: correlation_id,
message_annotations: {application: uuid},
application_properties: {application: uuid},
body:{ body:{
uuid: uuid uuid: uuid
} }
} }
console.log("Send ", message) console.log("Send ", message)
sender_ui_deploy_application_new.send(message) sender_ui_application_updated.send(message)
}) })
}, },
register_cloud:( uuid, user ,secret ) =>{ register_cloud:(doc) =>{
return new Promise((resolve,reject)=>{ return new Promise((resolve,reject)=>{
const correlation_id = uuidv4() const correlation_id = uuidv4()
@ -159,6 +167,23 @@ module.exports = {
'reject':reject, 'reject':reject,
}; };
/**
*
* // Amazon Web Service Elastic Compute Cloud
* AWSEC2("aws-ec2"),
* // Azure VM
* AZUREVM("azure"),
* // Google CLoud Engine
* GCE("gce"),
* // OpenStack NOVA
* OPENSTACKNOVA("openstack"),
* // BYON, to be used for on-premise baremetal
* BYON("byon"),
* // EDGE nodes
* EDGE("edge");
*
*/
const message = { const message = {
to: sender_sal_cloud_post.options.target.address, to: sender_sal_cloud_post.options.target.address,
correlation_id: correlation_id, correlation_id: correlation_id,
@ -167,27 +192,27 @@ module.exports = {
userId: "admin" userId: "admin"
}, },
body: JSON.stringify([{ body: JSON.stringify([{
"cloudId": uuid, "cloudId": doc.uuid,
"cloudProviderName": "aws-ec2", "cloudProviderName": doc._platform[0].provider_name,
"cloudType": "PUBLIC", "cloudType": "PUBLIC",
"securityGroup": null, "securityGroup": doc.securityGroup || '',
"subnet": null, "subnet": doc.subnet || null,
"sshCredentials": { "sshCredentials": {
"username": null, "username": doc.sshCredentials.username,
"keyPairName": "mkl", "keyPairName": doc.sshCredentials.keyPairName,
"privateKey": null "privateKey": doc.sshCredentials.privateKey
}, },
"endpoint": null, "endpoint": doc.endpoint,
"scope": { "scope": {
"prefix": null, "prefix": null,
"value": null "value": null
}, },
"identityVersion": null, "identityVersion": doc.identityVersion,
"defaultNetwork": null, "defaultNetwork": doc.defaultNetwork,
"credentials": { "credentials": {
"user": user, "user": doc.credentials.user,
"secret": secret, "secret": doc.credentials.secret,
"domain": null "domain": doc.credentials.domain || null
}, },
"blacklist": null "blacklist": null
}]) }])
@ -196,10 +221,6 @@ module.exports = {
console.log("Send ", message) console.log("Send ", message)
sender_sal_cloud_post.send(message) sender_sal_cloud_post.send(message)
}) })
},
deploy_application: (uuid) => {
}, },
get_cloud_candidates: () => { get_cloud_candidates: () => {
return new Promise((resolve,reject)=> { return new Promise((resolve,reject)=> {
@ -213,7 +234,9 @@ module.exports = {
const message = { const message = {
to: sender_sal_nodecandidate_get.options.target.address, to: sender_sal_nodecandidate_get.options.target.address,
correlation_id: correlation_id, correlation_id: correlation_id,
body: {} body: {
body:[]
}
} }
sender_sal_nodecandidate_get.send(message) sender_sal_nodecandidate_get.send(message)
}) })

View File

@ -4,8 +4,7 @@ const math = require('mathjs');
module.exports = { module.exports = {
extractFromEquation: (equation)=>{ extractFromEquation: (equation)=>{
equation = equation || ''; equation = equation || '';
const lowerCaseEquation = equation.toLowerCase(); return math.parse(equation);
return math.parse(lowerCaseEquation);
}, },
extractVariableNames: (mathNode) => { extractVariableNames: (mathNode) => {
let variableNames = new Set(); let variableNames = new Set();

View File

@ -4,92 +4,107 @@ const yaml = require('yaml');
module.exports = { module.exports = {
yaml: (doc) => { yaml: (doc) => {
let object = _.clone(doc) let object = _.cloneDeep(doc);
let componentsForAppSpecComp = [];
let componentsForAppWideScope = [];
const protectedVariables = ["_id", "type", "metaType", "organization", "_edit", "_publish", "variables", "utilityFunctions", "resources", "parameters",]; const protectedVariables = ["_id", "type", "metaType", "organization", "_edit", "_publish", "variables", "utilityFunctions", "resources"];
protectedVariables.forEach(p => { protectedVariables.forEach(p => {
delete object[p]; delete object[p];
}); });
//Templates
if (object.templates) { if (object.templates) {
object.templates = object.templates.map(v => { object.templates = object.templates.map(v => ({
return {
id: v.id, id: v.id,
type: v.type, type: v.type,
range: [v.minValue, v.maxValue], range: [v.minValue, v.maxValue],
unit: v.unit unit: v.unit
} }));
});
} }
object.metrics_comp = []; object.metrics_comp = [];
object.metrics_global = []; object.metrics_global = [];
//Metrics
if (object.metrics) { if (object.metrics) {
object.metrics.forEach(v => { object.metrics.forEach(v => {
let metricsDetail = {}; let metricsDetail = {};
if (v.type === 'composite') { if (v.type === 'composite') {
const componentNames = v.components.map(component => component.componentName).join(', ');
let windowDetail = {}; let windowDetail = {};
if (v.isWindowInput && v.input.type && v.input.interval && v.input.unit) { if (v.isWindowInput && v.input.type && v.input.interval && v.input.unit) {
windowDetail.type = v.input.type; windowDetail = {
windowDetail.size = `${v.input.interval} ${v.input.unit}`; type: v.input.type,
size: `${v.input.interval} ${v.input.unit}`,
};
} }
if (v.isWindowOutput && v.output.type && v.output.interval && v.output.unit) {
windowDetail.output = `${v.output.type} ${v.output.interval} ${v.output.unit}`; // Only add windowDetail to metricsDetail if it's not empty
if (Object.keys(windowDetail).length > 0) {
metricsDetail.window = windowDetail;
}
let componentNames = v.components ? v.components.map(component => component.componentName) : [];
if (componentNames.length === 1) {
componentsForAppSpecComp.push(componentNames[0]);
} else if (componentNames.length > 1) {
componentsForAppWideScope = componentsForAppWideScope.concat(componentNames);
} }
metricsDetail = { metricsDetail = {
name: v.name, name: v.name,
type: v.type, type: v.type,
template: componentNames, template: v.template,
window: windowDetail formula: v.formula,
...metricsDetail,
}; };
if (v.isWindowOutput && v.output.type && v.output.interval && v.output.unit) {
metricsDetail.output = `${v.output.type} ${v.output.interval} ${v.output.unit}`;
}
} else if (v.type === 'raw') { } else if (v.type === 'raw') {
let configs = v.config.map(configItem => ({
[configItem.name]: configItem.value
}));
let windowDetailRaw = {}; let configObject = configs.reduce((acc, current) => ({ ...acc, ...current }), {});
if (v.isWindowInputRaw && v.inputRaw.type && v.inputRaw.interval && v.inputRaw.unit) {
windowDetailRaw.type = v.inputRaw.type;
windowDetailRaw.size = `${v.inputRaw.interval} ${v.inputRaw.unit}`;
}
if (v.isWindowOutputRaw && v.outputRaw.type && v.outputRaw.interval && v.outputRaw.unit) {
windowDetailRaw.output = `${v.outputRaw.type} ${v.outputRaw.interval} ${v.outputRaw.unit}`;
}
metricsDetail = { metricsDetail = {
name: v.name, name: v.name,
type: v.type, type: v.type,
sensor: { sensor: {
type: v.sensor type: v.sensor,
}, config: configObject,
window: windowDetailRaw }
}; };
if (v.outputRaw && v.outputRaw.type && v.outputRaw.interval && v.outputRaw.unit) {
metricsDetail.output = `${v.outputRaw.type} ${v.outputRaw.interval} ${v.outputRaw.unit}`;
}
if (v.template) {
metricsDetail.template = v.template;
} }
const metric = { let componentNames = v.components ? v.components.map(component => component.componentName) : [];
metrics: metricsDetail
};
if (v.type === 'composite' && v.components.length < 2) { if (componentNames.length === 1) {
object.metrics_global.push(metric); componentsForAppSpecComp.push(componentNames[0]);
} else if (v.type === 'composite' && v.components.length >= 2) { } else if (componentNames.length > 1) {
object.metrics_comp.push(metric); componentsForAppWideScope = componentsForAppWideScope.concat(componentNames);
} else if (v.type === 'raw') { }
object.metrics_global.push(metric); }
if (v.components && v.components.length === 1) {
object.metrics_comp.push({...metricsDetail});
} else {
object.metrics_global.push({...metricsDetail});
} }
}); });
} }
//SLO Violations
if (object.sloViolations) { if (object.sloViolations) {
const processSloViolations = (violations) => { const processSloViolations = (violations) => {
const buildConstraint = (v, parentCondition = '') => { const buildConstraint = (v, parentCondition = '') => {
@ -99,29 +114,58 @@ module.exports = {
} else { } else {
const childConstraints = v.children.map(child => buildConstraint(child, v.condition)).join(` ${v.condition} `); const childConstraints = v.children.map(child => buildConstraint(child, v.condition)).join(` ${v.condition} `);
if (v.not) { constraint = v.not ? `NOT (${childConstraints})` : `(${childConstraints})`;
constraint = `NOT (${childConstraints})`;
} else {
constraint = `(${childConstraints})`;
}
} }
return constraint; return constraint;
}; };
const combinedConstraint = buildConstraint(violations); const combinedConstraint = buildConstraint(JSON.parse(doc['sloViolations']));
const requirement = { return [{
name: 'Combined SLO', name: 'Combined SLO',
type: 'slo', type: 'slo',
constraint: combinedConstraint constraint: combinedConstraint
}];
}; };
return [requirement]; object.sloViolations = processSloViolations(object.sloViolations);
};
object.sloViolations = processSloViolations(JSON.parse(doc['sloViolations']));
} }
let specContent = {
components: [
...componentsForAppSpecComp,
{
name: "spec-comp",
metrics: object.metrics_comp
}
], scopes: [{
name: "app-wide-scope",
components: componentsForAppWideScope,
metrics: object.metrics_global,
requirements: object.sloViolations ? object.sloViolations : [],
}]
};
//Parameters + push to app wide scope
if (object.parameters) {
const parametersMetrics = object.parameters.map(v => ({
name: v.name,
type: "constant",
initialValue: v.initialValue,
template: v.template
}));
if (parametersMetrics.length > 0) {
specContent.scopes.push({
name: "parameters-scope",
components: [],
metrics: parametersMetrics,
});
}
}
//Construct and return the final version
const yamlDoc = { const yamlDoc = {
apiVersion: "nebulous/v1", apiVersion: "nebulous/v1",
kind: "MetricModel", kind: "MetricModel",
@ -131,19 +175,10 @@ module.exports = {
app: object.title, app: object.title,
} }
}, },
common: object.templates, templates: object.templates,
spec: { spec: specContent
components: object.metrics_comp
},
scopes: [
{
name: "app-wide-scope",
requirements: object.sloViolations,
components: object.metrics_global,
}
]
}; };
return yamlDoc; return yamlDoc;
} }
}; };

View File

@ -134,7 +134,11 @@ module.exports = {
template: { template: {
type: 'string', type: 'string',
label: 'Template' label: 'Template'
} },
initialValue: {
type: 'float',
label: 'Initial Value'
},
} }
} }
}, },
@ -188,8 +192,8 @@ module.exports = {
type: 'select', type: 'select',
label: 'Level', label: 'Level',
choices: [ choices: [
{ label: 'Global', value: 'Global' }, { label: 'Global', value: 'global' },
{ label: 'Components', value: 'Components' } { label: 'Components', value: 'components' }
], ],
def: 'global' def: 'global'
}, },
@ -197,7 +201,7 @@ module.exports = {
type: 'array', type: 'array',
label: 'Components', label: 'Components',
if: { if: {
level: 'Components' level: 'components'
}, },
fields: { fields: {
add: { add: {
@ -210,10 +214,12 @@ module.exports = {
}, },
name: { name: {
type: 'string', type: 'string',
label: 'Name', label: 'Name'
if: { },
type: ['composite', 'raw'] template: {
} type: 'string',
label: 'Template',
textarea: true,
}, },
formula: { formula: {
type: 'string', type: 'string',
@ -239,7 +245,7 @@ module.exports = {
type: 'select', type: 'select',
label: 'Type Input', label: 'Type Input',
choices: [ choices: [
{label: 'All', value: 'all'}, {label: 'Batch', value: 'batch'},
{label: 'Sliding', value: 'sliding'} {label: 'Sliding', value: 'sliding'}
], ],
}, },
@ -255,7 +261,8 @@ module.exports = {
{label: 'Sec', value: 'sec'}, {label: 'Sec', value: 'sec'},
{label: 'Min', value: 'min'}, {label: 'Min', value: 'min'},
{label: 'Hour', value: 'hour'}, {label: 'Hour', value: 'hour'},
{label: 'Day', value: 'day'} {label: 'Day', value: 'day'},
{label: 'Events', value: 'events'}
], ],
}, },
}, },
@ -280,7 +287,8 @@ module.exports = {
type: 'select', type: 'select',
choices: [ choices: [
{label: 'All', value: 'all'}, {label: 'All', value: 'all'},
{label: 'Sliding', value: 'sliding'} {label: 'First', value: 'first'},
{label: 'Last', value: 'last'}
], ],
}, },
interval: { interval: {
@ -330,47 +338,6 @@ module.exports = {
} }
} }
}, },
isWindowInputRaw: {
type: 'boolean',
label: 'Window Input',
if: {
type: 'raw'
}
},
inputRaw: {
type: 'object',
label: 'Input',
fields: {
add: {
type: {
type: 'select',
label: 'Type Input',
choices: [
{label: 'All', value: 'all'},
{label: 'Sliding', value: 'sliding'}
],
},
interval: {
type: 'integer',
label: 'Interval',
},
unit: {
type: 'select',
label: 'Unit',
choices: [
{label: 'Ms', value: 'ms'},
{label: 'Sec', value: 'sec'},
{label: 'Min', value: 'min'},
{label: 'Hour', value: 'hour'},
{label: 'Day', value: 'day'}
],
},
},
},
if: {
isWindowInputRaw: true
}
},
isWindowOutputRaw: { isWindowOutputRaw: {
type: 'boolean', type: 'boolean',
label: 'Window Output', label: 'Window Output',
@ -387,7 +354,8 @@ module.exports = {
type: 'select', type: 'select',
choices: [ choices: [
{label: 'All', value: 'all'}, {label: 'All', value: 'all'},
{label: 'Sliding', value: 'sliding'} {label: 'First', value: 'first'},
{label: 'Last', value: 'last'}
], ],
}, },
interval: { interval: {
@ -433,6 +401,7 @@ module.exports = {
label: 'Function Type', label: 'Function Type',
choices: [ choices: [
{label: 'Maximize', value: 'maximize'}, {label: 'Maximize', value: 'maximize'},
{label: 'Minimize', value: 'minimize'},
{label: 'Constant', value: 'constant'} {label: 'Constant', value: 'constant'}
] ]
}, },
@ -558,11 +527,11 @@ module.exports = {
application_update_sender.send({ application_update_sender.send({
"body":{"uuid":doc.uuid}, "body":{"uuid":doc.uuid},
"message_annotations":{ "message_annotations":{
"subject":doc.uuid "application":doc.uuid
} }
}); });
application_dsl_generic.send({body:{}, "message_annotations":{ application_dsl_generic.send({body:{}, "message_annotations":{
"subject":doc.uuid "application":doc.uuid
}}); }});
} }
} }
@ -585,11 +554,11 @@ module.exports = {
application_update_sender.send({ application_update_sender.send({
"body":{"uuid":doc.uuid}, "body":{"uuid":doc.uuid},
"message_annotations":{ "message_annotations":{
"subject":doc.uuid "application":doc.uuid
} }
}); });
application_dsl_generic.send({body:{}, "message_annotations":{ application_dsl_generic.send({body:{}, "message_annotations":{
"subject":doc.uuid "application":doc.uuid
}}); }});
} }
} }
@ -786,10 +755,10 @@ module.exports = {
'string.base': 'Function Name must be a string.', 'string.base': 'Function Name must be a string.',
'any.required': 'Function Name is required.' 'any.required': 'Function Name is required.'
}), }),
functionType: Joi.string().valid('maximize', 'constant').insensitive().required().messages({ functionType: Joi.string().valid('maximize', 'constant','minimize').insensitive().required().messages({
'string.base': 'Function Type must be a string.', 'string.base': 'Function Type must be a string.',
'any.required': 'Function Type is required.', 'any.required': 'Function Type is required.',
'any.only': 'Function Type must be either "Maximize" or "Constant".' 'any.only': 'Function Type must be either "Maximize" , "Constant" or "Minimize.'
}), }),
functionExpression: Joi.string().trim().required().messages({ functionExpression: Joi.string().trim().required().messages({
'string.base': 'Function Expression must be a string.', 'string.base': 'Function Expression must be a string.',
@ -982,10 +951,12 @@ module.exports = {
try { try {
const updatedApp = await self.find(req,{ uuid: uuid , organization:adminOrganization }).project(projection).toArray(); const updatedApp = await self.find(req,{ uuid: uuid , organization:adminOrganization }).project(projection).toArray();
const result = await exn.application_dsl(uuid, const updatedAppItem = updatedApp.pop();
kubevela.json(updatedApp.pop()),
"" await exn.application_dsl(uuid,
) kubevela.json(updatedAppItem), metric_model.yaml(updatedAppItem)
);
//TODO refactor to use apostrophe CMS ORM //TODO refactor to use apostrophe CMS ORM
await self.apos.doc.db.updateOne( await self.apos.doc.db.updateOne(
{ uuid: uuid }, { uuid: uuid },

View File

@ -8,9 +8,8 @@ module.exports = {
try { try {
let parsedEquation = mathutils.extractFromEquation(req.body.equation) let parsedEquation = mathutils.extractFromEquation(req.body.equation)
const variableNames = mathutils.extractVariableNames(parsedEquation); const variableNames = mathutils.extractVariableNames(parsedEquation);
const uppercaseVariableNames = variableNames.map(name => name.toUpperCase());
return { return {
variables: uppercaseVariableNames, variables: variableNames,
}; };
} catch (error) { } catch (error) {
throw error; throw error;

View File

@ -1,8 +1,13 @@
const {v4: uuidv4} = require("uuid"); const {v4: uuidv4} = require("uuid");
const _ = require('lodash');
module.exports = { module.exports = {
extend: '@apostrophecms/piece-type', extend: '@apostrophecms/piece-type',
options: { options: {
label: 'Platform', label: 'Platform',
sort:{
sortOrder:1,
}
}, },
fields: { fields: {
@ -11,12 +16,29 @@ module.exports = {
type: 'string', type: 'string',
label: 'UUID', label: 'UUID',
required: false required: false
},
provider_name: {
type: 'string',
label: 'Provider Name',
required: false
},
public: {
type: 'boolean',
label: 'Public',
required: true,
def: true
},
sortOrder: {
type: 'integer',
label: 'Sort Order',
required: true,
def: 0,
} }
}, },
group: { group: {
basics: { basics: {
label: 'Basics', label: 'Basics',
fields: ['uuid'] fields: ['uuid','public','sortOrder']
} }
} }
}, },
@ -38,10 +60,12 @@ module.exports = {
const projection = { const projection = {
title: 1, title: 1,
uuid: 1, uuid: 1,
sortOrder: 1,
}; };
try { try {
const platforms = await self.find(req).project(projection).toArray(); const platforms = await self.find(req).project(projection).toArray();
return platforms;
return _.map(platforms, (p)=> { return {'title': p.title, 'uuid': p.uuid} })
} catch (error) { } catch (error) {
throw self.apos.error('notfound', 'Platforms not found'); throw self.apos.error('notfound', 'Platforms not found');
} }

View File

@ -8,54 +8,130 @@ const projection = {
uuid: 1, uuid: 1,
organization: 1, organization: 1,
platform: 1, platform: 1,
appId: 1, subnet: 1,
appSecret: 1 endpoint: 1,
identityVersion: 1,
credentials: 1,
defaultNetwork: 1,
sshCredentials: 1,
_platform: 1
}; };
const resourcesSchema = Joi.object({ const resourcesSchema = Joi.object({
appId: Joi.string().required().messages({ _platform: Joi.required().messages({
'string.empty': 'App ID is required.',
'any.required': 'App ID is a required field.'
}),
appSecret: Joi.string().required().messages({
'string.empty': 'App Secret is required.',
'any.required': 'App Secret is a required field.'
}),
platform: Joi.string().required().messages({
'string.empty': 'Platform is required.',
'any.required': 'Platform is a required field.' 'any.required': 'Platform is a required field.'
})
}).unknown().options({ abortEarly: false });
const credentialsSchema = Joi.object({
user: Joi.string().required().messages({
'string.empty': 'Credentials: Users is required.',
'any.required': 'Credentials: Users is a required field.'
}),
secret: Joi.string().required().messages({
'string.empty': 'Credentials: Secret is required.',
'any.required': 'Credentials: Secret is a required field.'
}), }),
}).unknown().options({ abortEarly: false }); }).unknown().options({ abortEarly: false });
module.exports = { module.exports = {
extend: '@apostrophecms/piece-type', extend: '@apostrophecms/piece-type',
options: { options: {
label: 'Resource', label: 'Resource'
}, },
fields: { fields: {
add: { add: {
uuid: { uuid: {
type: 'string', type: 'string',
label: 'UUID', label: 'UUID'
readOnly: true
}, },
platform: { _platform: {
type: 'string', label: 'Platform',
label: 'Platform' type: 'relationship',
withType: "platforms",
max:1
}, },
appId: { securityGroup:{
type: 'string', type: 'string',
label: 'App ID', label: 'Security Group'
}, },
appSecret: { sshCredentials: {
type: 'object',
label: 'SSH Credentials',
fields: {
add: {
username: {
type: 'string', type: 'string',
label: 'App Secret', label: 'Username',
},
privateKey: {
type: 'string',
label: 'Private Key',
textarea: true,
},
keyPairName: {
type: 'string',
label: 'Key Pair Name',
},
}
}
},
subnet:{
type: 'string',
label: 'Subnet'
},
endpoint:{
type: 'string',
label: 'Endpoint'
},
identityVersion:{
type: 'select',
label: 'Identity Version',
choices: [
{'label':'v3', value:'3'}
],
def: '3'
},
defaultNetwork:{
type: 'string',
label: 'Default Network'
},
credentials:{
type: 'object',
label: 'Credentials',
fields: {
add: {
user: {
type: 'string',
label: 'Username',
},
secret: {
type: 'string',
label: 'Secret',
textarea: true,
},
domain: {
type: 'string',
label: 'Domain',
},
}
}
} }
}, },
group: { group: {
basics: { basics: {
label: 'Details', label: 'Details',
fields: ['title', 'uuid', 'platform', 'appId', 'appSecret'] fields: ['title', 'uuid', '_platform', 'identityVersion']
},
network: {
label: 'Network',
fields: ['defaultNetwork','endpoint', 'subnet']
},
security: {
label: 'Security',
fields: ['securityGroup','credentials','sshCredentials']
} }
} }
}, },
handlers(self) { handlers(self) {
@ -79,16 +155,7 @@ module.exports = {
await generateUuid(doc); await generateUuid(doc);
try{ try{
if(doc.aposMode === 'published'){ await self.updateWithPlatformInfo(req,doc);
const message = await exn.register_cloud(
doc.uuid,
doc.appId,
doc.appSecret,
)
console.log("Registered ",message);
}
await self.updateWithPlatformInfo(doc);
await assignOrganization(req, doc); await assignOrganization(req, doc);
}catch(e){ }catch(e){
@ -96,11 +163,10 @@ module.exports = {
} }
} }
}, },
beforeSave: { beforeSave: {
async handler(req, doc, options) { async handler(req, doc, options) {
try { try {
await self.updateWithPlatformInfo(req,doc)
self.validateDocument(doc); self.validateDocument(doc);
} catch (error) { } catch (error) {
if (error.name === 'required' && error.error && error.error.length > 0) { if (error.name === 'required' && error.error && error.error.length > 0) {
@ -113,28 +179,45 @@ module.exports = {
} }
} }
} }
} },
} }
}, },
methods(self) { methods(self) {
return { return {
async updateWithPlatformInfo(doc) { async updateWithPlatformInfo(req, doc) {
if (doc.platform && !doc.platformUpdated) {
const platformPiece = await self.apos.doc.db.findOne({
type: 'platforms',
uuid: doc.platform
});
if (platformPiece) { if(req.body.platform && req.body.platform.uuid){
doc.platform = platformPiece.title;
doc.platformUpdated = true; const platform = await self.apos.modules['platforms'].find(req,{'uuid':req.body.platform.uuid}).toObject();
} else { if(!platform){
throw self.apos.error('notfound', 'Platform not found'); throw self.apos.error('notfound', 'Platform not found or empty');
} }
doc.platformIds = [platform._id]
doc._platform = [platform]
delete req.body.platform
} }
return doc
}, },
cleanUp:function(self,req, resources){
_.each(resources,(r)=>{
r= self.removeForbiddenFields(req,r)
r['platform'] = null
if( r._platform.length > 0 ){
r['platform'] = {
'uuid': r._platform[0].uuid,
'title': r._platform[0].title,
}
}
delete r._platform
})
return resources
},
validateDocument(doc) { validateDocument(doc) {
const validateField = (data, schema) => { const validateField = (data, schema) => {
const {error} = schema.validate(data); const {error} = schema.validate(data);
@ -147,6 +230,7 @@ module.exports = {
} }
}; };
validateField(doc, resourcesSchema); validateField(doc, resourcesSchema);
validateField(doc.credentials, credentialsSchema);
} }
} }
}, },
@ -159,10 +243,24 @@ module.exports = {
const adminOrganization = currentUser.organization; const adminOrganization = currentUser.organization;
try { try {
const filters = { const filters = {
organization: adminOrganization organization: adminOrganization,
};
const page = req.query.page || 1
const pageSize = req.query.pageSize || 10
const count = await self.find(req, filters).toCount()
const resources = await self.find(req, filters).project(projection)
.withPublished(true)
.perPage(pageSize)
.page(page)
.toArray()
return {
"total":count,
"page": page,
"results":self.cleanUp(self,req,resources)
}; };
const resources = await self.find(req, filters).project(projection).toArray();
return resources;
} catch (error) { } catch (error) {
throw self.apos.error('notfound', 'Resource not found'); throw self.apos.error('notfound', 'Resource not found');
} }
@ -182,19 +280,31 @@ module.exports = {
throw self.apos.error('notfound', 'Resource not found'); throw self.apos.error('notfound', 'Resource not found');
} }
return doc; return self.cleanUp(self,req, [doc])[0]
} catch (error) { } catch (error) {
throw self.apos.error(error.name, error.message); throw self.apos.error(error.name, error.message);
} }
}, },
async 'candidates'(req) { async ':uuid/candidates'(req) {
const uuid = req.params.uuid;
if (!( req.user.organization)) { if (!( req.user.organization)) {
throw self.apos.error('forbidden', 'You do not have permission to perform this action'); throw self.apos.error('forbidden', 'You do not have permission to perform this action');
} }
const currentUser = req.user;
const adminOrganization = currentUser.organization;
try { try {
const doc = await self.find(req, { uuid: uuid , organization:adminOrganization}).project(projection).toObject();
if (!doc) {
throw self.apos.error('notfound', 'Resource not found');
}
await exn.register_cloud(doc)
await new Promise(resolve => setTimeout(resolve, 10000));
const message = await exn.get_cloud_candidates() const message = await exn.get_cloud_candidates()
return _.map(JSON.parse(message.body), (r)=>{ return _.map(JSON.parse(message.body), (r)=>{
return { return {
@ -262,7 +372,6 @@ module.exports = {
if (!(req.user.role === "admin" && req.user.organization)) { if (!(req.user.role === "admin" && req.user.organization)) {
throw self.apos.error('forbidden', 'You do not have permission to perform this action'); throw self.apos.error('forbidden', 'You do not have permission to perform this action');
} }
self.validateDocument(updateData);
const adminOrganization = req.user.organization; const adminOrganization = req.user.organization;
@ -271,33 +380,18 @@ module.exports = {
uuid: uuid, uuid: uuid,
organization: adminOrganization organization: adminOrganization
}; };
const resourcesToUpdate = await self.find(req, filters).project(projection).toArray(); const doc = await self.find(req, filters).toObject();
if (!doc) {
if (!resourcesToUpdate || resourcesToUpdate.length === 0) {
throw self.apos.error('notfound', 'Resource not found'); throw self.apos.error('notfound', 'Resource not found');
} }
await self.updateWithPlatformInfo(req,doc)
self.validateDocument(doc)
const doc = resourcesToUpdate[0]; let update = { ...doc, ...updateData };
await self.update(req,update)
return self.cleanUp(self,req,[update])[0]
if ('platform' in updateData) {
let docToUpdate = { ...doc, ...updateData };
await self.updateWithPlatformInfo(docToUpdate);
await self.apos.doc.db.updateOne(
{ uuid: uuid },
{ $set: docToUpdate }
);
} else {
await self.apos.doc.db.updateOne(
{ uuid: uuid },
{ $set: updateData }
);
}
const resourceUpdated = await self.find(req, filters).project(projection).toArray();
return resourceUpdated;
} catch (error) { } catch (error) {
throw self.apos.error(error.name, error.message); throw self.apos.error(error.name, error.message);
} }

View File

@ -42,6 +42,7 @@
"dependencies": { "dependencies": {
"@johmun/vue-tags-input": "^2.1.0", "@johmun/vue-tags-input": "^2.1.0",
"apostrophe": "^3.61.1", "apostrophe": "^3.61.1",
"dotenv": "^16.4.5",
"flat": "^5.0.2", "flat": "^5.0.2",
"joi": "^17.11.0", "joi": "^17.11.0",
"mathjs": "^12.2.1", "mathjs": "^12.2.1",