Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 138 additions & 50 deletions lib/services/nodes-api-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ di.annotate(nodeApiServiceFactory,
'Errors',
'Logger',
'_',
'Promise'
'Promise',
'Constants'
)
);
function nodeApiServiceFactory(
Expand All @@ -22,89 +23,181 @@ function nodeApiServiceFactory(
Errors,
Logger,
_,
Promise
Promise,
Constants
) {
var logger = Logger.initialize(nodeApiServiceFactory);

function NodeApiService() {
}

/**
* Find Enclosure nodes which enclose compute node
* Find target nodes that relate with the removing node
* @param {Object} node
* @return {Promise}
* @param {String} type
* @return {Promise} array of target nodes
*/
NodeApiService.prototype._findEnclNodes = function(node) {
// Find the enclosure nodes who enclose this compute node
NodeApiService.prototype._findTargetNodes = function(node, type) {
if (!_.has(node, 'relations')) {
return Promise.resolve();
return Promise.resolve([]);
}

var relation = _.find(node.relations, { relationType: 'enclosedBy' });
var relation = _.find(node.relations, { relationType: type });
if (!relation || !_.has(relation, 'targets') ) {
return Promise.resolve();
return Promise.resolve([]);
}

return Promise.map(relation.targets, function (enclNodeId) {
return waterline.nodes.needByIdentifier(enclNodeId)
return Promise.map(relation.targets, function (targetNodeId) {
return waterline.nodes.needByIdentifier(targetNodeId)
.catch(function (err) {
logger.warning("Error Getting Enclosure Node", { error: err });
logger.warning("Error getting target node with type " + type,
{ error: err });
return;
});
});
};

/**
* Remove the relations between node and enclosure node, if enclosure node
* doesn't enclose any nodes, this enclosure node is also removed.
* @param {Object} node
* @param {Array} enclNodes
* @return {Promise}
* Remove the relations to original node in target node. If the node is invalid
* or doesn't have required relation, this function doesn't need to update the
* node info and ignore silently with Promise.resolve(). If the relation field
* is empty after removing, delete this field.
* @param {Object} node node whose relation needs to be updated
* @param {String} type relation type that needs to be updated
* @param {String} targetId id in relation type that needs to be deleted
* @return {Promise} node after removing relation
*/
NodeApiService.prototype._removeEnclRelations = function(node, enclNodes) {
if (!enclNodes) {
NodeApiService.prototype._removeRelation = function(node, type, targetId) {

if (!node) {
return Promise.resolve();
}

// Remove the relationship between enclosure and compute node
return Promise.map(enclNodes, function(enclNode) {
if (!_.has(enclNode, 'relations')) {
return;
}
// Remove the relationship between node and downstram node
if (!_.has(node, 'relations')) {
return Promise.resolve();
}

var index = _.findIndex(enclNode.relations, { relationType: 'encloses' });
if (index === -1 || !_.has(enclNode.relations[index], 'targets')) {
return;
}
var index = _.findIndex(node.relations, { relationType: type });
if (index === -1 || !_.has(node.relations[index], 'targets')) {
return Promise.resolve();
}

if (_.indexOf(enclNode.relations[index].targets, node.id) !== -1) {
_.pull(enclNode.relations[index].targets, node.id);
// Remove target node id in relation field
if (_.indexOf(node.relations[index].targets, targetId) !== -1) {
_.pull(node.relations[index].targets, targetId);
}

// Remove the type of relation if no targets in it
if (node.relations[index].targets.length === 0) {
_.pull(node.relations, node.relations[index]);
}

return waterline.nodes.updateByIdentifier(node.id,
{relations: node.relations});
};

/**
* Handle target nodes. Update relation type field and remove the target
* node if needed.
* @param {Array} targetNodes array of target nodes
* @param {String} type relation type in the removing node
* @param {String} nodeId removing node id
* @return {Promise}
*/
NodeApiService.prototype._handleTargetNodes = function(targetNodes, type, nodeId) {
var self = this;
var targetType = '';
var relationClass = null;
var needDel = false;

if (Constants.NodeRelations.hasOwnProperty(type)) {
targetType = Constants.NodeRelations[type].mapping;
relationClass = Constants.NodeRelations[type].relationClass;
}

if ((relationClass === 'component') &&
(type.indexOf('By') === -1)) {
// Its components need to be deleted
needDel = true;
}

return Promise.resolve()
.then(function () {
if (needDel === true) {
// Throw error when all target nodes need to be deleted
// but at least one of them have active workflow
return Promise.map(targetNodes, function(targetNode) {
return self._delValidityCheck(targetNode.id);
});
}
// If Enclosure node doesn't have enclosed nodes
// remove this enclosure node, else remove relationships
if (enclNode.relations[index].targets.length === 0) {
return waterline.nodes.destroy({ id: enclNode.id });
} else {
return waterline.nodes.updateByIdentifier(
enclNode.id, { relations : enclNode.relations });
})
.then(function () {
return Promise.map(targetNodes, function (targetNode) {
// Delete target nodes if it is required
if (needDel === true) {
return self.removeNode(targetNode, targetType);
}

// Update relations in the target node
return self._removeRelation(targetNode, targetType, nodeId)
.then(function (targetNode) {
if (targetNode) {
logger.debug('node updated', {id: targetNode.id, relation: targetType});
}
});
});
});
};

/**
* Check whether a node is valid to be deleted
* @param {String} nodeId
* @return {Promise}
*/
NodeApiService.prototype._delValidityCheck = function(nodeId) {
return taskGraphProtocol.getActiveTaskGraph( { target: nodeId })
.then(function (graph) {
if (graph) {
// If there is active workflow, the node cannot be deleted
return Promise.reject('Could not remove node ' + nodeId +
', active workflow is running');
}
return Promise.resolve();
});
};

/**
* Remove node related data and remove its relations with other nodes
* @param {Object} node
* @param {String} srcType
* @return {Promise}
*/
NodeApiService.prototype.removeNode = function(node) {
NodeApiService.prototype.removeNode = function(node, srcType) {
var self = this;

return taskGraphProtocol.getActiveTaskGraph( { target: node.id })
.then(function (graph) {
if (graph) {
throw new Errors.BadRequestError('Could not remove node ' + node.id +
', active workflow is running');
return self._delValidityCheck(node.id)
.then(function () {
if (!node.hasOwnProperty('relations')) {
return Promise.resolve();
}

return Promise.map(node.relations, function(relation) {

var type = relation.relationType;

// Skip handling relationType that comes from the upstream node
// to avoid deleting upstream nodes more than once
if (srcType && (srcType === type)) {
return Promise.resolve();
}

// Otherwise update targets node in its "relationType"
return self._findTargetNodes(node, type)
.then(function (targetNodes) {
return self._handleTargetNodes(targetNodes, type, node.id);
});
});
})
.then(function () {
return Promise.settle([
Expand All @@ -115,15 +208,10 @@ function nodeApiServiceFactory(
waterline.nodes.destroy({ id: node.id }),
waterline.catalogs.destroy({ node: node.id }),
waterline.workitems.destroy({ node: node.id })
]);
})
.then(function () {
return self._findEnclNodes(node);
})
.then(function (enclNodes) {
return self._removeEnclRelations(node, enclNodes);
]);
})
.then(function () {
logger.debug('node deleted', {id: node.id, type: node.type});
return node;
});
};
Expand Down
Loading