on-http-y1
Version:
OnRack Http Server
1,274 lines (1,137 loc) • 51.3 kB
JavaScript
// Copyright © 2017 Dell Inc. or its subsidiaries. All Rights Reserved.
"use strict";
describe("Http.Services.Api.Nodes", function () {
var nodeApiService;
var workflowApiService;
var Errors;
var waterline;
var updateByIdentifier;
var create;
var getNodeById;
var needByIdentifier;
var findActiveGraphForTarget;
var findAllByNode;
var upsertByNode;
var computeNode;
var enclosureNode;
var rackNode;
var _;
var eventsProtocol;
var Promise;
var findByNode;
var upsertByNodeIbm;
var removeListItemsByIdentifier;
var addFieldIfNotExistByIdentifier;
var addListItemsIfNotExistByIdentifier;
before("Http.Services.Api.Nodes before", function() {
helper.setupInjector([
onHttpContext.prerequisiteInjectables,
helper.require("/lib/services/nodes-api-service"),
helper.require("/lib/services/workflow-api-service"),
helper.require("/lib/services/taskgraph-api-service"),
dihelper.simpleWrapper({}, 'Task.Services.OBM'),
dihelper.simpleWrapper({}, 'ipmi-obm-service')
]);
nodeApiService = helper.injector.get("Http.Services.Api.Nodes");
workflowApiService = helper.injector.get("Http.Services.Api.Workflows");
Errors = helper.injector.get("Errors");
waterline = helper.injector.get('Services.Waterline');
_ = helper.injector.get('_');
eventsProtocol = helper.injector.get('Protocol.Events');
Promise = helper.injector.get('Promise');
waterline.nodes = {
create: function() {},
getNodeById: function() {},
needByIdentifier: function() {},
updateByIdentifier: function() {},
destroy: function() {},
removeListItemsByIdentifier: function() {},
addFieldIfNotExistByIdentifier: function() {},
addListItemsIfNotExistByIdentifier: function() {}
};
waterline.catalogs = {
destroy: function() {}
};
waterline.workitems = {
destroy: function() {}
};
waterline.lookups = {
update: function() {}
};
waterline.obms = {
findAllByNode: function() {},
upsertByNode: function() {}
};
waterline.ibms = {
findByNode: function() {},
upsertByNode: function() {}
};
this.sandbox = sinon.sandbox.create();
});
beforeEach("Http.Services.Api.Nodes beforeEach", function() {
computeNode = {
id: '1234abcd1234abcd1234abcd',
type: 'compute',
relations: [
{
"relationType": "enclosedBy",
"targets": [
"1234abcd1234abcd1234abcf"
]
}
]
};
enclosureNode = {
id: '1234abcd1234abcd1234abcf',
type: 'enclosure',
relations: [
{
"relationType": "encloses",
"targets": [
"1234abcd1234abcd1234abcd",
"1234abcd1234abcd1234abce"
]
}
]
};
rackNode = {
id: '1234abcd1234abcd1234abcc',
type: 'rack',
relations: []
};
create = this.sandbox.stub(waterline.nodes, 'create');
getNodeById = this.sandbox.stub(waterline.nodes, 'getNodeById');
needByIdentifier = this.sandbox.stub(waterline.nodes, 'needByIdentifier');
updateByIdentifier = this.sandbox.stub(waterline.nodes, 'updateByIdentifier');
removeListItemsByIdentifier = this.sandbox.stub(
waterline.nodes, 'removeListItemsByIdentifier');
addFieldIfNotExistByIdentifier = this.sandbox.stub(
waterline.nodes, 'addFieldIfNotExistByIdentifier');
addListItemsIfNotExistByIdentifier = this.sandbox.stub(
waterline.nodes, 'addListItemsIfNotExistByIdentifier');
findActiveGraphForTarget = this.sandbox.stub(
workflowApiService, 'findActiveGraphForTarget');
findAllByNode = this.sandbox.stub(waterline.obms, 'findAllByNode');
upsertByNode = this.sandbox.stub(waterline.obms, 'upsertByNode');
upsertByNodeIbm = this.sandbox.stub(waterline.ibms, 'upsertByNode');
findByNode = this.sandbox.stub(waterline.ibms, 'findByNode');
this.sandbox.stub(eventsProtocol, 'publishNodeEvent').resolves({});
});
afterEach("Http.Services.Api.Nodes afterEach", function() {
this.sandbox.restore();
});
describe("postNode", function() {
var postBody = {
name: 'name',
type: 'compute',
obms: [
{
service: 'ipmi-obm-service',
config: {
host: '1.2.3.4',
user: 'myuser',
password: 'mypass'
}
},
{
service: 'snmp-obm-service',
config: {
host: '1.2.3.4',
community: 'abcdef'
}
}
]
};
var node = {
id: '1234abcd1234abcd1234abcd',
name: 'name',
type: 'compute',
obms: []
};
it('should create a node & obms', function () {
waterline.nodes.create.resolves(node);
return nodeApiService.postNode(postBody)
.then(function() {
expect(waterline.nodes.create).to.have.been.calledOnce;
expect(
waterline.nodes.create.firstCall.args[0]
).to.deep.equal({ name: 'name', type: 'compute' });
expect(waterline.obms.upsertByNode).to.have.been.calledTwice;
expect(waterline.obms.upsertByNode)
.to.be.calledWith(node.id, postBody.obms[0]);
expect(waterline.obms.upsertByNode)
.to.be.calledWith(node.id, postBody.obms[1]);
});
});
it('should run discovery if the requested node is an autoDiscoverable switch', function() {
var switchNode = {
id: '1234abcd1234abcd1234abcd',
name: 'name',
ibms: [{
service: 'snmp-ibm-service',
config: {
host: '1.2.3.4',
community: 'community'
}
}],
autoDiscover: true,
type: 'switch'
};
waterline.nodes.create.resolves(switchNode);
waterline.ibms.upsertByNode.resolves({});
waterline.ibms.findByNode.resolves(switchNode.ibms);
this.sandbox.stub(workflowApiService, 'createAndRunGraph').resolves({});
return nodeApiService.postNode(switchNode)
.then(function() {
expect(waterline.ibms.upsertByNode).to.have.been.calledOnce;
expect(waterline.ibms.findByNode).to.have.been.calledOnce;
expect(workflowApiService.createAndRunGraph).to.have.been.calledOnce;
expect(workflowApiService.createAndRunGraph).to.have.been.calledWith(
{
name: 'Graph.Switch.Discovery',
options: { defaults: switchNode.ibms }
},
switchNode.id
);
});
});
it('should run discovery if the requested node is an autoDiscoverable mgmt server',
function() {
var mgmtNode = {
id: '1234abcd1234abcd1234abce',
name: 'mgmt server',
obms: [
{
config: {
host: '1.2.3.4',
user: 'user',
password: 'password'
},
service: 'ipmi-obm-service'
}
],
autoDiscover: true,
type: 'mgmt'
};
var options = {
defaults: {
graphOptions: {
target: mgmtNode.id
},
nodeId: mgmtNode.id
}
};
waterline.nodes.create.resolves(mgmtNode);
this.sandbox.stub(workflowApiService, 'createAndRunGraph').resolves({});
return nodeApiService.postNode(mgmtNode)
.then(function() {
expect(waterline.obms.upsertByNode).to.be.calledOnce;
expect(waterline.obms.upsertByNode)
.to.be.calledWith(mgmtNode.id, mgmtNode.obms[0]);
expect(workflowApiService.createAndRunGraph).to.have.been.calledOnce;
expect(workflowApiService.createAndRunGraph).to.have.been.calledWith(
{
name: 'Graph.MgmtSKU.Discovery',
options: options
}
);
});
});
it('should run discovery if the requested node is an autoDiscoverable PDU',
function() {
var pduNode = {
id: '1234abcd1234abcd1234abcd',
name: 'name',
ibms: [{
service: 'snmp-ibm-service',
config: {
host: '1.2.3.4',
community: 'community'
}
}],
autoDiscover: true,
type: 'pdu'
};
waterline.nodes.create.resolves(pduNode);
waterline.ibms.upsertByNode.resolves({});
waterline.ibms.findByNode.resolves(pduNode.ibms);
this.sandbox.stub(workflowApiService, 'createAndRunGraph').resolves({});
return nodeApiService.postNode(pduNode)
.then(function() {
expect(waterline.ibms.upsertByNode).to.have.been.calledOnce;
expect(workflowApiService.createAndRunGraph).to.have.been.calledOnce;
expect(workflowApiService.createAndRunGraph).to.have.been.calledWith(
{
name: 'Graph.PDU.Discovery',
options: { defaults: pduNode.ibms }
},
pduNode.id
);
});
});
it('should publish a node added event if the node is a rack', function() {
var rackNode = {
id: 'someNodeId',
type: 'rack',
name: 'rackNode'
};
waterline.nodes.create.resolves(rackNode);
return nodeApiService.postNode(rackNode)
.then(function() {
expect(eventsProtocol.publishNodeEvent).to.be.calledWithExactly(
rackNode, 'added'
);
});
});
});
describe('setNodeWorkflow/setNodeWorkflowById', function() {
it('should create a workflow', function () {
this.sandbox.stub(workflowApiService, 'createAndRunGraph').resolves();
return nodeApiService.setNodeWorkflow(
{ name: 'TestGraph.Dummy', domain: 'test' },
'testnodeid'
)
.then(function () {
expect(workflowApiService.createAndRunGraph).to.have.been.calledOnce;
expect(workflowApiService.createAndRunGraph).to.have.been.calledWith(
{ name: 'TestGraph.Dummy', domain: 'test' },
'testnodeid'
);
});
});
it('should create a workflow', function () {
this.sandbox.stub(workflowApiService, 'createAndRunGraph').resolves();
return nodeApiService.setNodeWorkflowById(
{ name: 'TestGraph.Dummy', domain: 'test' },
'testnodeid'
)
.then(function () {
expect(workflowApiService.createAndRunGraph).to.have.been.calledOnce;
expect(workflowApiService.createAndRunGraph).to.have.been.calledWith(
{ name: 'TestGraph.Dummy', domain: 'test' },
'testnodeid'
);
});
});
});
describe('getActiveNodeWorkflowById', function() {
it('should get the currently active workflow', function () {
var node = {
id: '123'
};
var graph = {
instanceId: '0987'
};
waterline.nodes.needByIdentifier.resolves(node);
findActiveGraphForTarget.resolves(graph);
return nodeApiService.getActiveNodeWorkflowById(node.id)
.then(function() {
expect(findActiveGraphForTarget).to.have.been.calledOnce;
expect(findActiveGraphForTarget)
.to.have.been.calledWith(node.id);
});
});
it('should throw a NotFoundError if the node has no active graph', function () {
waterline.nodes.needByIdentifier.resolves({ id: 'testid' });
findActiveGraphForTarget.resolves(null);
return expect(nodeApiService.getActiveNodeWorkflowById('test')).to.become(null);
});
});
describe('delActiveWorkflowById', function() {
it('should delete the currently active workflow', function () {
var node = {
id: '123'
};
var graph = {
instanceId: 'testgraphid'
};
waterline.nodes.needByIdentifier.resolves(node);
findActiveGraphForTarget.resolves(graph);
this.sandbox.stub(workflowApiService, 'cancelTaskGraph').resolves(graph.instanceId);
return nodeApiService.delActiveWorkflowById('testnodeid')
.then(function() {
expect(findActiveGraphForTarget).to.have.been.calledOnce;
expect(findActiveGraphForTarget)
.to.have.been.calledWith(node.id);
expect(workflowApiService.cancelTaskGraph).to.have.been.calledOnce;
expect(workflowApiService.cancelTaskGraph)
.to.have.been.calledWith(graph.instanceId);
});
});
it('should throw a NotFoundError if there is no active workflow', function () {
var node = {
id: '123'
};
waterline.nodes.needByIdentifier.resolves(node);
findActiveGraphForTarget.resolves(null);
return expect(nodeApiService.delActiveWorkflowById('testnodeid'))
.to.be.rejectedWith(Errors.NotFoundError);
});
it('should throw a NotFoundError if the workflow completes while processing the request',
function () {
var node = {
id: '123'
};
var graph = {
instanceId: 'testgraphid'
};
waterline.nodes.needByIdentifier.resolves(node);
findActiveGraphForTarget.resolves(graph);
this.sandbox.stub(workflowApiService, 'cancelTaskGraph').resolves(null);
return expect(nodeApiService.delActiveWorkflowById('testnodeid'))
.to.be.rejectedWith(Errors.NotFoundError);
});
});
describe("_findTargetNodes", function() {
before("_findTargetNodes before", function() {
});
it("_findTargetNodes should find related target nodes", function() {
waterline.nodes.needByIdentifier.resolves(enclosureNode);
return nodeApiService._findTargetNodes(computeNode.relations, 'enclosedBy')
.then(function (nodes) {
expect(waterline.nodes.needByIdentifier).to.have.been.calledOnce;
expect(nodes[0]).to.equal(enclosureNode);
});
});
it("_findTargetNodes should return nothing if cannot find target node", function() {
waterline.nodes.needByIdentifier.rejects(Errors.NotFoundError(''));
return nodeApiService._findTargetNodes(computeNode.relations, 'enclosedBy')
.then(function (nodes) {
expect(waterline.nodes.needByIdentifier).to.have.been.calledOnce;
expect(nodes[0]).to.equal(undefined);
});
});
it("_findTargetNodes should return nothing if don't have relations", function() {
var node = {
id: '1234abcd1234abcd1234abcd',
type: 'compute'
};
return nodeApiService._findTargetNodes(node, 'enclosedBy')
.then(function (nodes) {
expect(nodes).to.deep.equal([]);
});
});
it("_findTargetNodes should return nothing if node is null", function() {
return nodeApiService._findTargetNodes(null, 'enclosedBy')
.then(function (nodes) {
expect(nodes).to.deep.equal([]);
});
});
});
describe("_needTargetNodes", function() {
it("should fail if any target nodes cannot be found", function() {
var error = new Errors.NotFoundError();
waterline.nodes.needByIdentifier.rejects(error);
return expect(nodeApiService._needTargetNodes(computeNode.relations, 'enclosedBy')
).to.be.rejectedWith(error);
});
});
describe("removeRelation", function() {
beforeEach(function() {
updateByIdentifier.resolves();
});
it("removeRelation should return undefined if target node is null", function() {
expect(nodeApiService.removeRelation(null, 'encloses', computeNode))
.to.equal(undefined);
});
it("removeRelation should fail if relation type is null", function() {
var enclNode = {
id: '1234abcd1234abcd1234abcd',
type: 'enclNode'
};
expect(nodeApiService.removeRelation(enclNode, null, computeNode))
.to.equal(undefined);
});
it("removeRelation should fail if don't have targets", function() {
var enclNode = {
id: '1234abcd1234abcd1234abcd',
type: 'enclosure',
relations: [
{
"relationType": "encloses"
}
]
};
expect(nodeApiService.removeRelation(enclNode, 'encloses', computeNode))
.to.equal(undefined);
});
it("removeRelation should return undefined if don't have relations", function() {
var enclNode = {
id: '1234abcd1234abcd1234abcd',
type: 'enclosure'
};
expect(nodeApiService.removeRelation(enclNode, 'encloses', computeNode))
.to.equal(undefined);
});
it("removeRelation should fail if relationType is incorrect", function() {
var enclNode = {
id: '1234abcd1234abcd1234abcd',
type: 'enclosure',
relations: [
{
"relationType": "enclosedBy",
"targets": [computeNode.id]
}
]
};
expect(nodeApiService.removeRelation(enclNode, 'encloses', computeNode))
.to.equal(undefined);
});
it("removeRelation should remove one relation when blank targets", function() {
var enclNode = {
id: '1234abcd1234abcd1234abcf',
type: 'enclosure',
relations: [
{
"relationType": "encloses",
"targets": [
]
},
{
"relationType": "clusterBy",
"targets": [
"aaa",
]
}
]
};
var relationsToBeRemoved = { relations: [{ relationType: "encloses", targets: [] }] };
var enclAfter = _.cloneDeep(enclNode);
_.pull(enclAfter.relation, enclNode.relations[0]);
removeListItemsByIdentifier.resolves(enclAfter);
return nodeApiService.removeRelation(enclNode, 'encloses')
.then(function(node){
expect(removeListItemsByIdentifier).to.have.been.calledOnce;
expect(removeListItemsByIdentifier).to.have.been
.calledWith(enclNode.id, relationsToBeRemoved);
expect(node).to.deep.equal(enclAfter);
});
});
it("removeRelation should return node when passing targets to be removed is null" +
"but target node relations doesn't have blank targets", function() {
var enclNode = {
id: '1234abcd1234abcd1234abcf',
type: 'enclosure',
relations: [
{
"relationType": "encloses",
"targets": [
"bbb"
]
},
{
"relationType": "clusterBy",
"targets": [
"aaa",
]
}
]
};
return nodeApiService.removeRelation(enclNode, "encloses", null).then(function(node){
expect(node).to.deep.equal(enclNode);
});
});
it("removeRelation should remove related target and then remove", function() {
var enclNode = {
id: '1234abcd1234abcd1234abcf',
type: 'enclosure',
relations: [
{
"relationType": "encloses",
"targets": [
"1234abcd1234abcd1234abcd"
]
}
]
};
var targetsToBeRemoved = { "relations.0.targets": ["1234abcd1234abcd1234abcd"] };
var relationsToBeRemoved = {
"relations": [
{
"relationType": "encloses",
"targets": []
}
]
};
var enclNodeAfter = _.cloneDeep(enclNode);
var enclNodeAfter1 = _.cloneDeep(enclNode);
_.pull(enclNodeAfter.relations[0].targets, enclNodeAfter.relations[0].targets[0]);
_.pull(enclNodeAfter1.relations, enclNodeAfter1.relations[0]);
removeListItemsByIdentifier.withArgs(enclNode.id, targetsToBeRemoved)
.resolves(enclNodeAfter);
removeListItemsByIdentifier.withArgs(enclNode.id, relationsToBeRemoved)
.resolves(enclNodeAfter1);
return nodeApiService.removeRelation(enclNode, 'encloses', computeNode)
.then(function(node){
expect(node).to.deep.equal(enclNodeAfter1);
expect(removeListItemsByIdentifier).to.have.been.calledTwice;
expect(removeListItemsByIdentifier)
.to.have.been.calledWith(enclNode.id, targetsToBeRemoved);
expect(removeListItemsByIdentifier)
.to.have.been.calledWith(enclNode.id, relationsToBeRemoved);
});
});
});
describe("addRelation", function() {
var computeNode2;
beforeEach(function() {
computeNode2 = {
id: '1234abcd1234abcd1234abcx',
type: 'compute',
relations: [
{
"relationType": "enclosedBy",
"targets": [
"1234abcd1234abcd1234abcf"
]
}
]
};
updateByIdentifier.resolves();
});
it("should do nothing if arguments are missing", function() {
var argList = [rackNode, "contains", [computeNode]];
expect(_.map(argList, function(arg, index) {
var argsCopy = [].concat(argList);
argsCopy[index] = undefined;
return nodeApiService.addRelation(argsCopy[0], argsCopy[1], argsCopy[2]);
})
).to.deep.equal([undefined, undefined, undefined]);
});
it("should return the node updated with new relations", function() {
var rackNodeAfter = _.cloneDeep(rackNode);
rackNodeAfter.relations = [];
addFieldIfNotExistByIdentifier.withArgs(rackNode.id, "relations", []).
resolves(rackNodeAfter);
var relationToBeAdded = {relations: [{relationType: 'contains', targets: []}]};
var existSign = [ {relationType: 'contains'} ];
var rackNodeAfter1 = _.cloneDeep(rackNode);
rackNodeAfter1.relations = [{relationType: 'contains', targets: []}];
addListItemsIfNotExistByIdentifier.withArgs(rackNode.id, relationToBeAdded, existSign)
.resolves(rackNodeAfter1);
var targetsToBeAadded = {"relations.0.targets": [computeNode.id, computeNode2.id]};
var rackNodeAfter2 = _.cloneDeep(rackNode);
rackNodeAfter2.relations = [{relationType: 'contains', targets: [
computeNode.id,
computeNode2.id
]}];
addListItemsIfNotExistByIdentifier.withArgs(rackNode.id, targetsToBeAadded)
.resolves(rackNodeAfter2);
return Promise.all([
nodeApiService.addRelation(rackNode, 'contains', [computeNode, computeNode2])
.then(function(modifiedNode){
expect(modifiedNode.relations).to.deep.equal(
[{relationType: 'contains', targets: [computeNode.id, computeNode2.id]}]
);
}),
nodeApiService.addRelation(
rackNode, 'contains', [computeNode.id, computeNode2.id]
).then(function(modifiedNode){
expect(modifiedNode.relations).to.deep.equal(
[{relationType: 'contains', targets: [computeNode.id, computeNode2.id]}]
);
})
]);
});
it('shoud return node if node already has relations obj', function(){
this.sandbox.stub(_, "findIndex");
addFieldIfNotExistByIdentifier.resolves();
addListItemsIfNotExistByIdentifier.resolves();
return nodeApiService.addRelation(enclosureNode, 'encloses', [computeNode.id])
.then(function(){
expect(_.findIndex).to.have.been.calledWith(enclosureNode.relations);
});
});
it('should fail to relate a node to itself', function() {
addFieldIfNotExistByIdentifier.resolves();
addListItemsIfNotExistByIdentifier.resolves(rackNode);
expect(nodeApiService.addRelation(rackNode, 'contains', rackNode.id))
.to.be.rejectedWith(Error);
});
it('should fail to set a node as containedBy two other nodes', function() {
addFieldIfNotExistByIdentifier.resolves();
var rackNode2 = _.cloneDeep(rackNode);
rackNode2.id = 'secondRackNodeId';
computeNode.relations = [{relationType: 'containedBy', targets: [
rackNode.id,
]}];
addListItemsIfNotExistByIdentifier.resolves(rackNode2);
return nodeApiService.addRelation(
computeNode, 'containedBy', [rackNode.id, rackNode2.id]
)
.catch(function(e){
expect(e).to.deep
.equal(Error("Node "+computeNode.id+" can only be contained by one node"));
});
});
it('should replace old enclosure when add a new one to a computenode', function(){
addFieldIfNotExistByIdentifier.resolves();
addListItemsIfNotExistByIdentifier.resolves();
removeListItemsByIdentifier.resolves();
return nodeApiService.addRelation(computeNode, "enclosedBy", ["id"])
.then(function(){
expect(removeListItemsByIdentifier)
.to.have.been.calledWith(
computeNode.id,
{"relations.0.targets": [ computeNode.relations[0].targets[0] ]
});
});
});
});
describe("getNodeRelations", function() {
it("should return the relations field of the requested node", function() {
computeNode.relations = [{relationType: "enclosedBy", targets:[enclosureNode]}];
waterline.nodes.needByIdentifier.resolves(computeNode);
return nodeApiService.getNodeRelations(computeNode.id)
.then(function(relations) {
expect(relations).to.deep.equal(computeNode.relations);
expect(waterline.nodes.needByIdentifier).to.be.calledOnce;
});
});
});
describe("editNodeRelations", function() {
var body,
handler,
error,
computeNode2;
beforeEach(function() {
computeNode2 = {
id: '1234abcd1234abcd1234abcx',
type: 'compute',
relations: [
{
"relationType": "enclosedBy",
"targets": [
"1234abcd1234abcd1234abcf"
]
}
]
};
body = {
contains: [computeNode.id, computeNode2.id]
};
handler = this.sandbox.stub().resolves();
error = new Errors.NotFoundError();
this.sandbox.stub(nodeApiService, "_needTargetNodes");
waterline.nodes.needByIdentifier.resolves(rackNode);
waterline.nodes.updateByIdentifier.resolves();
});
afterEach(function() {
this.sandbox.restore();
});
it("should delegate node updates to a handler", function() {
nodeApiService._needTargetNodes.resolves([computeNode, computeNode2]);
return nodeApiService.editNodeRelations(rackNode.id, body, handler)
.then(function() {
expect(handler).to.be.calledWithExactly(
rackNode, 'contains', [computeNode, computeNode2]
);
});
});
it("should fail if any target nodes do not exist", function() {
nodeApiService._needTargetNodes.rejects(error);
return nodeApiService.editNodeRelations(rackNode.id, body, handler)
.then(function() {
throw new Error("expected function to fail");
}).catch(function(e) {
expect(e).to.equal(error);
});
});
it("should fail if the node given by id does not exist", function() {
waterline.nodes.needByIdentifier.rejects(error);
return nodeApiService.editNodeRelations(rackNode.id, body, handler)
.then(function() {
throw new Error("expected function to fail");
}).catch(function(e) {
expect(e).to.equal(error);
});
});
});
describe("removeNode", function() {
before("removeNode before", function() {
});
beforeEach(function() {
this.sandbox.stub(waterline.lookups, 'update').resolves();
this.sandbox.stub(waterline.nodes, 'destroy').resolves();
this.sandbox.stub(waterline.workitems, 'destroy').resolves();
this.sandbox.stub(waterline.catalogs, 'destroy').resolves();
});
it("removeNode should not delete target node when no constants defined", function() {
var noopNode = {
id: '1234abcd1234abcd1234abce',
type: 'noop',
relations: [
{
"relationType": "noops",
"targets": [
"1234abcd1234abcd1234abcd"
]
}
]
};
var computeNodeBefore = _.cloneDeep(computeNode);
computeNodeBefore.relations[0] = {
"relationType": "noopedBy",
"targets": ["1234abcd1234abcd1234abce"]
};
findActiveGraphForTarget.resolves('');
needByIdentifier.resolves(noopNode);
return nodeApiService.removeNode(computeNodeBefore)
.then(function (node){
expect(node).to.equal(computeNodeBefore);
expect(removeListItemsByIdentifier).to.not.have.been.called;
expect(waterline.nodes.destroy).to.have.been.calledOnce;
expect(waterline.nodes.destroy).to.have.been
.calledWith({id: computeNodeBefore.id});
expect(eventsProtocol.publishNodeEvent)
.to.have.been.calledWith(computeNodeBefore, "removed")
.to.have.been.calledOnce;
});
});
it("removeNode should only delete required node when cannot get target node", function() {
findActiveGraphForTarget.resolves('');
needByIdentifier.rejects();
return nodeApiService.removeNode(computeNode)
.then(function (node){
expect(node).to.equal(computeNode);
expect(removeListItemsByIdentifier).to.not.have.been.called;
expect(waterline.nodes.destroy).to.have.been.calledOnce;
expect(eventsProtocol.publishNodeEvent)
.to.have.been.calledWith(computeNode, "removed")
.to.have.been.calledOnce;
});
});
it("removeNode should only delete required node when no target node", function() {
var noopNode = {
id: '1234abcd1234abcd1234abcf',
type: 'enclosure',
relations: [
{
'relationType': 'encloses'
}
]
};
findActiveGraphForTarget.resolves('');
return nodeApiService.removeNode(noopNode)
.then(function (node){
expect(node).to.equal(noopNode);
expect(removeListItemsByIdentifier).to.not.have.been.called;
expect(waterline.nodes.destroy).to.have.been.calledOnce;
expect(eventsProtocol.publishNodeEvent)
.to.have.been.calledWith(noopNode, "removed")
.to.have.been.calledOnce;
});
});
it("removeNode should only delete compute node when no compute node target", function() {
var enclNode = {
id: '1234abcd1234abcd1234abcf',
type: 'enclosure',
relations: [
{
"relationType": "encloses",
"targets": [
"1234abcd1234abcd1234abcd"
]
}
]
};
var enclNodeAfter = _.cloneDeep(enclNode);
_.pull(enclNodeAfter.relations, enclNodeAfter.relations[0]);
findActiveGraphForTarget.resolves('');
needByIdentifier.resolves(enclNode);
this.sandbox.stub(nodeApiService, '_findTargetNodes').resolves([enclNodeAfter]);
return nodeApiService.removeNode(computeNode)
.then(function (node){
expect(removeListItemsByIdentifier).to.not.have.been.called;
expect(node).to.equal(computeNode);
expect(waterline.nodes.destroy).to.have.been.calledOnce;
expect(waterline.nodes.destroy).to.have.been.calledWith({id: computeNode.id});
expect(eventsProtocol.publishNodeEvent)
.to.have.been.calledWith(computeNode, "removed")
.to.have.been.calledOnce;
});
});
it("removeNode should delete compute node when deleting enclosure", function() {
var computeNode2 = {
id: '1234abcd1234abcd1234abce',
type: 'compute',
relations: [
{
"relationType": "enclosedBy",
"targets": [
"1234abcd1234abcd1234abcf"
]
}
]
};
this.sandbox.stub(nodeApiService, '_findTargetNodes')
.resolves([computeNode, computeNode2]);
findActiveGraphForTarget.resolves('');
needByIdentifier.withArgs(computeNode.id).resolves(computeNode);
needByIdentifier.withArgs(computeNode2.id).resolves(computeNode2);
return nodeApiService.removeNode(enclosureNode)
.then(function (node){
expect(node).to.equal(enclosureNode);
expect(waterline.nodes.destroy).to.have.been.calledThrice;
expect(waterline.nodes.destroy).to.have.been.calledWith({id: computeNode.id});
expect(waterline.nodes.destroy).to.have.been.calledWith({id: computeNode2.id});
expect(waterline.nodes.destroy).to.have.been.calledWith({id: enclosureNode.id});
expect(eventsProtocol.publishNodeEvent)
.to.have.been.calledWith(enclosureNode, "removed")
.to.have.callCount(3);
});
});
it("removeNode should update pdu node when deleting enclosure node", function() {
var pduNode = {
id: '1234abcd1234abcd1234abcg',
type: 'pdu',
relations: [
{
"relationType": "powers",
"targets": [
"1234abcd1234abcd1234abcd",
"aaa"
]
}
]
};
var computeNode2 = {
id: '1234abcd1234abcd1234abce',
type: 'compute',
relations: [
{
"relationType": "enclosedBy",
"targets": [
"1234abcd1234abcd1234abf",
]
}
]
};
var pduNodeAfter = _.cloneDeep(pduNode);
_.pull(pduNodeAfter.relations[0].targets, pduNodeAfter.relations[0].targets[0]);
var computeNodeBefore = _.cloneDeep(computeNode);
computeNodeBefore.relations[1] = {
"relationType": "poweredBy",
"targets": ["1234abcd1234abcd1234abcg"]
};
var targets = { "relations.0.targets": ["1234abcd1234abcd1234abcd"] };
findActiveGraphForTarget.resolves('');
needByIdentifier.withArgs(computeNode.id).resolves(computeNodeBefore);
needByIdentifier.withArgs(computeNode2.id).resolves(computeNode2);
needByIdentifier.withArgs(pduNode.id).resolves(pduNode);
removeListItemsByIdentifier.withArgs(pduNode.id, targets).resolves(pduNodeAfter);
return nodeApiService.removeNode(enclosureNode)
.then(function (node){
expect(node).to.equal(enclosureNode);
expect(waterline.nodes.destroy).to.have.been.calledTrice;
expect(removeListItemsByIdentifier).to.have.been
.calledWith(pduNode.id, targets);
expect(eventsProtocol.publishNodeEvent)
.to.have.been.calledWith(enclosureNode, "removed")
.to.have.callCount(3);
});
});
it("removeNode should remove relations with blank target list in compute node target node",
function() {
var enclNode = {
id: '1234abcd1234abcd1234abcf',
type: 'enclosure',
relations: [
{
"relationType": "encloses",
"targets": [
"1234abcd1234abcd1234abcd"
]
}
]
};
var enclNodeAfter = _.cloneDeep(enclNode);
_.pull(enclNodeAfter.relations[0].targets, enclNodeAfter.relations[0].targets[0]);
var targets = { "relations.0.targets": ["1234abcd1234abcd1234abcd"] };
removeListItemsByIdentifier.withArgs(enclNode.id, targets).resolves(enclNodeAfter);
var relations = { "relations": [{"relationType": "encloses", "targets": []}] };
findActiveGraphForTarget.resolves('');
needByIdentifier.resolves(enclNode);
this.sandbox.stub(nodeApiService, '_findTargetNodes').resolves([enclNode]);
return nodeApiService.removeNode(computeNode)
.then(function (node){
expect(removeListItemsByIdentifier).to.have.been.calledTwice;
expect(removeListItemsByIdentifier).to.have.been
.calledWith(enclNode.id, targets);
expect(removeListItemsByIdentifier).to.have.been
.calledWith(enclNode.id, relations);
expect(node).to.equal(computeNode);
expect(waterline.nodes.destroy).to.have.been.calledOnce;
expect(waterline.nodes.destroy).to.have.been.calledWith({id: computeNode.id});
expect(eventsProtocol.publishNodeEvent)
.to.have.been.calledWith(computeNode, "removed")
.to.have.been.calledOnce;
});
});
it("removeNode should not delete enlosure when active workflow", function(done) {
var computeNode2 = {
id: '1234abcd1234abcd1234abce',
type: 'compute',
relations: [
{
"relationType": "enclosedBy",
"targets": [
"1234abcd1234abcd1234abcf"
]
}
]
};
var computeNodeAfter = _.cloneDeep(computeNode);
var computeNode2After = _.cloneDeep(computeNode2);
delete computeNodeAfter.relations;
delete computeNode2After.relations;
findActiveGraphForTarget.resolves(null);
findActiveGraphForTarget.withArgs(computeNode2.id).resolves('1');
needByIdentifier.withArgs(computeNode.id).resolves(computeNode);
needByIdentifier.withArgs(computeNode2.id).resolves(computeNode2);
nodeApiService.removeNode(enclosureNode)
.then(function() {
done(new Error("Expected job to fail"));
})
.catch(function(e) {
try {
expect(e).to.equal('Could not remove node ' + computeNode2.id +
', active workflow is running');
expect(waterline.nodes.destroy).to.not.have.been.called;
expect(removeListItemsByIdentifier).to.not.have.been.called;
expect(eventsProtocol.publishNodeEvent).to.not.have.been.called;
done();
} catch (e) {
done(e);
}
});
});
});
describe('Tagging', function() {
var node = {
id: '1234abcd1234abcd1234abcd',
tags: ['name1'],
type: 'compute',
};
var node1 = {
id: '5678efgh5678efgh5678efgh',
tags: ['name1'],
type: 'compute',
};
before(function() {
waterline.nodes.addTags = sinon.stub().resolves();
waterline.nodes.remTags = sinon.stub().resolves();
waterline.nodes.findByTag = sinon.stub().resolves();
});
beforeEach(function() {
waterline.nodes.addTags.reset();
waterline.nodes.remTags.reset();
waterline.nodes.findByTag.reset();
});
after(function() {
delete waterline.nodes.addTags;
delete waterline.nodes.remTags;
delete waterline.nodes.findByTag;
});
it('should call waterline to add a tag array', function() {
var tags = ['tag'];
needByIdentifier.withArgs(node.id).resolves(node);
return nodeApiService.addTagsById(node.id, tags)
.then(function() {
expect(waterline.nodes.addTags).to.have.been.calledWith(node.id, tags);
expect(needByIdentifier).to.have.been.calledWith(node.id);
});
});
it('should reject an invalid tag array', function() {
return nodeApiService.addTagsById(node.id, 'tag')
.catch(function(e) {
expect(e).to.have.property('name').that.equals('AssertionError');
expect(waterline.nodes.addTags).to.not.be.called;
expect(needByIdentifier).to.not.be.called;
});
});
it('should call waterline to remove a tag', function() {
needByIdentifier.withArgs(node.id).resolves(node);
return nodeApiService.removeTagsById(node.id, 'tag')
.then(function() {
expect(waterline.nodes.remTags).to.have.been.calledWith(node.id, 'tag');
expect(needByIdentifier).to.have.been.calledWith(node.id);
});
});
it('should reject an invalid tag', function() {
return nodeApiService.removeTagsById(node.id, 1)
.catch(function(e) {
expect(e).to.have.property('name').that.equals('AssertionError');
expect(waterline.nodes.remTags).to.not.be.called;
expect(needByIdentifier).to.not.be.called;
});
});
it('should call waterline to get tags on a node', function() {
needByIdentifier.withArgs(node.id).resolves(node);
return nodeApiService.getTagsById(node.id)
.then(function() {
expect(needByIdentifier).to.have.been.calledWith(node.id);
});
});
it('should call waterline to get nodes with the tag', function() {
return nodeApiService.getNodesByTag('tag')
.then(function() {
expect(waterline.nodes.findByTag).to.have.been.calledWith('tag');
});
});
it('should reject an invalid tag', function() {
return nodeApiService.getNodesByTag(1)
.catch(function(e) {
expect(e).to.have.property('name').that.equals('AssertionError');
expect(waterline.nodes.findByTag).to.not.be.called;
expect(needByIdentifier).to.not.be.called;
});
});
it('should call waterline to get list of nodes and remove the specified' +
' tag', function() {
var tagName = 'name1';
waterline.nodes.findByTag.resolves([node, node1]);
return nodeApiService.masterDelTagById(tagName)
.then(function() {
expect(waterline.nodes.remTags).to.have.been.calledWith(node.id,tagName);
expect(waterline.nodes.remTags).to.have.been.calledWith(node1.id,tagName);
});
});
});
describe('Obms', function() {
var node = {
id: '1234abcd1234abcd1234abcd',
name: 'name',
type: 'compute',
obms: [
{
service: 'ipmi-obm-service',
config: {
host: '1.2.3.4',
user: 'myuser',
password: 'mypass'
}
}
]
};
var obm = {
id: '5678efgh5678efgh5678efgh',
node: '/api/2.0/nodes/1234abcd1234abcd1234abcd',
service: 'ipmi-obm-service',
config: {
host: '1.2.3.4',
user: 'myuser',
password: 'mypass'
}
};
it('should add an OBM to a node', function () {
waterline.nodes.getNodeById.resolves(node);
waterline.obms.upsertByNode.resolves();
return nodeApiService.putObmsByNodeId(node.id, obm)
.then(function() {
expect(waterline.nodes.getNodeById).to.have.been.calledOnce;
expect(
waterline.nodes.getNodeById.firstCall.args[0]
).to.equal(node.id);
expect(waterline.obms.upsertByNode).to.have.been.calledOnce;
expect(wate