├── version ├── .hound.yml ├── alpha ├── unittest.yml ├── build_docker.yml ├── unittest.sh └── build_docker.sh ├── spec ├── lib │ ├── services │ │ ├── core-spec.js │ │ ├── log-publisher-spec.js │ │ ├── statsd-spec.js │ │ ├── waterline-spec.js │ │ ├── encryption-spec.js │ │ ├── environment-spec.js │ │ ├── configuration-spec.js │ │ ├── argument-handler-spec.js │ │ └── heartbeat-spec.js │ ├── protocol │ │ ├── http-spec.js │ │ ├── tftp-spec.js │ │ ├── waterline-spec.js │ │ └── scheduler-spec.js │ ├── extensions-spec.js │ ├── common │ │ ├── constants-spec.js │ │ ├── sanitizer-spec.js │ │ ├── system-uuid-spec.js │ │ ├── timer-spec.js │ │ ├── util-spec.js │ │ ├── graph-progress-spec.js │ │ ├── assert-spec.js │ │ ├── profile-spec.js │ │ ├── arp-spec.js │ │ ├── file-loader-spec.js │ │ ├── serializable-spec.js │ │ └── subscription-spec.js │ ├── workflow │ │ ├── stores │ │ │ └── store-spec.js │ │ └── messengers │ │ │ └── messenger-spec.js │ ├── models │ │ ├── template-spec.js │ │ ├── profile-spec.js │ │ ├── roles-spec.js │ │ ├── graph-object-spec.js │ │ ├── tags-spec.js │ │ ├── graph-definition-spec.js │ │ ├── log-spec.js │ │ └── localusers-spec.js │ └── serializables │ │ ├── result-spec.js │ │ ├── error-event-spec.js │ │ ├── ip-address-spec.js │ │ └── mac-address-spec.js ├── .jshintrc └── mocks │ └── logger.js ├── .dockerignore ├── Dockerfile ├── lib ├── protocol │ ├── http.js │ ├── tftp.js │ ├── scheduler.js │ └── waterline.js ├── services │ ├── hook.js │ ├── messenger.js │ ├── core.js │ ├── encryption.js │ ├── statsd.js │ ├── log-publisher.js │ ├── error-publisher.js │ ├── argument-handler.js │ ├── environment.js │ └── configuration.js ├── models │ ├── task-events.js │ ├── view.js │ ├── obms.js │ ├── firmware.js │ ├── profile.js │ ├── template.js │ ├── inband-mgmt.js │ ├── environment.js │ ├── roles.js │ ├── hooks.js │ ├── renderable.js │ ├── graph-definition.js │ ├── file.js │ ├── task-definition.js │ ├── graph-object.js │ ├── tags.js │ ├── task-dependency.js │ ├── log.js │ ├── local-user.js │ ├── sku.js │ └── catalog.js ├── workflow │ ├── stores │ │ └── store.js │ └── messengers │ │ ├── messenger.js │ │ └── messenger-AMQP.js ├── common │ ├── events.js │ ├── timer.js │ ├── sanitizer.js │ ├── system-uuid.js │ ├── template.js │ ├── util.js │ ├── assert.js │ ├── serializable.js │ ├── profile.js │ ├── promise-queue.js │ ├── message.js │ ├── validatable.js │ ├── logger.js │ ├── hook.js │ ├── subscription.js │ ├── arp.js │ ├── file-loader.js │ ├── encryption.js │ └── graph-progress.js ├── serializables │ ├── result.js │ ├── ip-address.js │ ├── mac-address.js │ └── error-event.js └── extensions.js ├── .gitignore ├── .jshintrc ├── .travis.yml ├── README.md ├── HWIMO-TEST ├── package.json └── index.js /version: -------------------------------------------------------------------------------- 1 | 2.60.7 -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | jshint: 2 | config_file: .jshintrc 3 | -------------------------------------------------------------------------------- /alpha/unittest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | 4 | inputs: 5 | - name: on-core 6 | 7 | run: 8 | path: "alpha/unittest.sh" 9 | dir: "on-core" 10 | -------------------------------------------------------------------------------- /spec/lib/services/core-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe(require('path').basename(__filename), function () { 7 | it('needs specs'); 8 | }); -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | .git 3 | .gitignore 4 | .hound.yml 5 | .jshintrc 6 | .travis.yml 7 | build_children.sh 8 | Dockerfile 9 | gruntfile.js 10 | HWIMO-* 11 | LICENSE 12 | node_modules/ 13 | README.md 14 | spec/ 15 | -------------------------------------------------------------------------------- /alpha/build_docker.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | 4 | inputs: 5 | - name: manifest-artifactory 6 | - name: on-core 7 | outputs: 8 | - name: build 9 | 10 | run: 11 | path: "alpha/build_docker.sh" 12 | dir: "on-core" 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2016, EMC, Inc. 2 | ARG repo=node 3 | ARG tag=8.11.1 4 | 5 | FROM ${repo}:${tag} 6 | 7 | COPY . /RackHD/on-core/ 8 | 9 | RUN cd /RackHD/on-core \ 10 | && npm install --production 11 | 12 | VOLUME /opt/monorail 13 | -------------------------------------------------------------------------------- /alpha/unittest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | ps -aux 3 | service rabbitmq-server start 4 | /usr/bin/mongod --fork --logpath /var/log/mongodb/monngod.log 5 | npm install 6 | ./node_modules/.bin/_mocha $(find spec -name '*-spec.js') --timeout 10000 --require spec/helper.js 7 | -------------------------------------------------------------------------------- /lib/protocol/http.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = httpProtocolFactory; 6 | 7 | httpProtocolFactory.$provide = 'Protocol.Http'; 8 | 9 | function httpProtocolFactory () { 10 | function HttpProtocol() { 11 | } 12 | 13 | return new HttpProtocol(); 14 | } 15 | -------------------------------------------------------------------------------- /lib/protocol/tftp.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = tftpProtocolFactory; 6 | 7 | tftpProtocolFactory.$provide = 'Protocol.Tftp'; 8 | 9 | function tftpProtocolFactory () { 10 | function TftpProtocol() { 11 | } 12 | 13 | return new TftpProtocol(); 14 | } 15 | -------------------------------------------------------------------------------- /lib/services/hook.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017, Dell EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = hookServiceFactory; 6 | 7 | hookServiceFactory.$provide = 'Services.Hook'; 8 | hookServiceFactory.$inject = [ 9 | 'Hook' 10 | ]; 11 | 12 | function hookServiceFactory( 13 | Hook 14 | ) { 15 | return new Hook(); 16 | } 17 | -------------------------------------------------------------------------------- /alpha/build_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | if [ "${VERIFY_DEP}" == "true" ]; then 4 | COMMIT=$(cat $(ls ../manifest-artifactory/manifest*.json) | jq -r .oncore.commit) 5 | git config --add remote.origin.fetch +refs/pull/*/head:refs/remotes/origin/pull/* 6 | git fetch 7 | git checkout $COMMIT 8 | fi 9 | ls 10 | cp -rf * ../build 11 | -------------------------------------------------------------------------------- /lib/services/messenger.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = messengerServiceFactory; 6 | 7 | messengerServiceFactory.$provide = 'Services.Messenger'; 8 | messengerServiceFactory.$inject = [ 9 | 'Messenger' 10 | ]; 11 | 12 | function messengerServiceFactory(Messenger) { 13 | return new Messenger(); 14 | } 15 | -------------------------------------------------------------------------------- /spec/lib/protocol/http-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe("Http Protocol functions", function () { 7 | helper.before(); 8 | 9 | before(function () { 10 | this.http = helper.injector.get('Protocol.Http'); 11 | }); 12 | 13 | helper.after(); 14 | 15 | it("should load", function() { 16 | expect(this.http).to.be.ok; 17 | }); 18 | 19 | }); -------------------------------------------------------------------------------- /spec/lib/protocol/tftp-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe("TFTP protocol", function () { 7 | 8 | helper.before(); 9 | 10 | before(function () { 11 | this.protocol = helper.injector.get('Protocol.Tftp'); 12 | }); 13 | 14 | helper.after(); 15 | 16 | it("should return a TFTP protocol", function() { 17 | expect(this.protocol).to.be.an('Object'); 18 | }); 19 | 20 | }); -------------------------------------------------------------------------------- /lib/models/task-events.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 'use strict'; 3 | 4 | module.exports = EventModelFactory; 5 | 6 | EventModelFactory.$provide = 'Models.TaskEvents'; 7 | EventModelFactory.$inject = [ 8 | 'Model', 9 | 'Services.Configuration' 10 | ]; 11 | 12 | function EventModelFactory(Model, configuration) { 13 | return Model.extend({ 14 | connection: configuration.get('taskgraph-store', 'mongo'), 15 | identity: 'taskevents' 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /spec/lib/extensions-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe('Extensions', function () { 7 | helper.before(); 8 | helper.after(); 9 | 10 | describe('String', function () { 11 | it('should format string literals', function () { 12 | "Hello %s".format('World').should.equal('Hello World'); 13 | }); 14 | 15 | it ('should format multiple string literals', function () { 16 | "%s %s".format('Hello', 'World').should.equal('Hello World'); 17 | }); 18 | }); 19 | }); -------------------------------------------------------------------------------- /lib/models/view.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = ViewModelFactory; 6 | 7 | ViewModelFactory.$provide = 'Models.View'; 8 | ViewModelFactory.$inject = [ 9 | 'Model', 10 | 'Renderable', 11 | '_', 12 | 'Services.Configuration' 13 | ]; 14 | 15 | function ViewModelFactory (Model, Renderable, _, configuration) { 16 | var viewModel = _.merge( 17 | {}, 18 | Renderable, 19 | {identity: 'views', connection: configuration.get('databaseType', 'mongo') } 20 | ); 21 | return Model.extend(viewModel); 22 | } 23 | -------------------------------------------------------------------------------- /lib/workflow/stores/store.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 'use strict'; 3 | 4 | module.exports = storeFactory; 5 | storeFactory.$provide = 'TaskGraph.Store'; 6 | storeFactory.$inject = [ 7 | 'Services.Configuration', 8 | '$injector' 9 | ]; 10 | 11 | function storeFactory(configuration, injector) { 12 | var mode = configuration.get('taskgraph-store', 'mongo'); 13 | switch(mode) { 14 | case 'mongo': 15 | return injector.get('TaskGraph.Stores.Mongo'); 16 | default: 17 | throw new Error('Unknown taskgraph store: ' + mode); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/models/obms.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = ObmsModelFactory; 6 | 7 | ObmsModelFactory.$provide = 'Models.Obms'; 8 | ObmsModelFactory.$inject = [ 9 | 'Model', 10 | 'NetworkManagement', 11 | '_', 12 | 'Services.Configuration' 13 | ]; 14 | 15 | function ObmsModelFactory (Model, NetworkManagement, _, configuration) { 16 | var obmModel = _.merge( 17 | {}, 18 | NetworkManagement, 19 | {identity: 'obms', connection: configuration.get('databaseType', 'mongo') } 20 | ); 21 | return Model.extend(obmModel); 22 | } 23 | -------------------------------------------------------------------------------- /lib/models/firmware.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = FirmwareModelFactory; 6 | 7 | FirmwareModelFactory.$provide = 'Models.Firmware'; 8 | FirmwareModelFactory.$inject = [ 9 | 'Model', 10 | 'NetworkManagement', 11 | '_', 12 | 'Services.Configuration' 13 | ]; 14 | 15 | function FirmwareModelFactory (Model, NetworkManagement, _, configuration) { 16 | var firmwareModel = _.merge( 17 | {}, 18 | {identity: 'firmware', connection: configuration.get('databaseType', 'mongo') } 19 | ); 20 | return Model.extend(firmwareModel); 21 | } 22 | -------------------------------------------------------------------------------- /lib/workflow/messengers/messenger.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 'use strict'; 3 | 4 | module.exports = MessengerFactory; 5 | MessengerFactory.$provide = 'Task.Messenger'; 6 | MessengerFactory.$inject = [ 7 | 'Services.Configuration', 8 | '$injector' 9 | ]; 10 | 11 | function MessengerFactory(config, injector) { 12 | var messenger = config.get('taskgraph-messenger', 'AMQP'); 13 | switch(messenger) { 14 | case 'AMQP': 15 | return injector.get('Task.Messengers.AMQP'); 16 | default: 17 | throw new Error('Unknown messenger: ' + messenger); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/models/profile.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = ProfileModelFactory; 6 | 7 | ProfileModelFactory.$provide = 'Models.Profile'; 8 | ProfileModelFactory.$inject = [ 9 | 'Model', 10 | 'Renderable', 11 | '_', 12 | 'Services.Configuration' 13 | ]; 14 | 15 | function ProfileModelFactory (Model, Renderable, _, configuration) { 16 | var profileModel = _.merge( 17 | {}, 18 | Renderable, 19 | {identity: 'profiles', connection: configuration.get('databaseType', 'mongo') } 20 | ); 21 | return Model.extend(profileModel); 22 | } 23 | -------------------------------------------------------------------------------- /lib/models/template.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = TemplateModelFactory; 6 | 7 | TemplateModelFactory.$provide = 'Models.Template'; 8 | TemplateModelFactory.$inject = [ 9 | 'Model', 10 | 'Renderable', 11 | '_', 12 | 'Services.Configuration' 13 | ]; 14 | 15 | function TemplateModelFactory (Model, Renderable, _, configuration) { 16 | var templateModel = _.merge( 17 | {}, 18 | Renderable, 19 | { identity: 'templates', connection: configuration.get('databaseType', 'mongo') } 20 | ); 21 | return Model.extend(templateModel); 22 | } 23 | -------------------------------------------------------------------------------- /spec/lib/common/constants-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe('Constants', function () { 7 | helper.before(); 8 | 9 | before(function () { 10 | this.subject = helper.injector.get('Constants'); 11 | }); 12 | 13 | helper.after(); 14 | 15 | it('should be an object', function () { 16 | expect(this.subject).to.be.an('object'); 17 | }); 18 | 19 | it('should be frozen', function () { 20 | var subject = this.subject; 21 | 22 | expect(function () { 23 | subject.foo = 'bar'; 24 | }).to.throw(TypeError); 25 | }); 26 | }); -------------------------------------------------------------------------------- /lib/models/inband-mgmt.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = InBandMgmtModelFactory; 6 | 7 | InBandMgmtModelFactory.$provide = 'Models.InBandMgmt'; 8 | InBandMgmtModelFactory.$inject = [ 9 | 'Model', 10 | 'NetworkManagement', 11 | '_', 12 | 'Services.Configuration' 13 | ]; 14 | 15 | function InBandMgmtModelFactory (Model, NetworkManagement, _, configuration) { 16 | var InBandMgmtModel = _.merge( 17 | {}, 18 | NetworkManagement, 19 | {identity: 'ibms', connection: configuration.get('databaseType', 'mongo') } 20 | ); 21 | return Model.extend(InBandMgmtModel); 22 | } 23 | -------------------------------------------------------------------------------- /lib/common/events.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = eventsFactory; 6 | 7 | eventsFactory.$provide = 'Events'; 8 | eventsFactory.$inject = [ 9 | 'EventEmitter', 10 | 'Constants', 11 | 'Util' 12 | ]; 13 | 14 | function eventsFactory(EventEmitter, Constants, util) { 15 | function Events() { 16 | EventEmitter.call(this); 17 | } 18 | 19 | util.inherits(Events, EventEmitter); 20 | 21 | Events.prototype.ignoreError = function (error) { 22 | this.emit(Constants.Events.Ignored, error); 23 | }; 24 | 25 | Events.prototype.log = function (data) { 26 | this.emit(Constants.Events.Log, data); 27 | }; 28 | 29 | return new Events(); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /spec/lib/workflow/stores/store-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | describe('Task Graph Store factory', function () { 6 | var mongo = {}; 7 | var configuration = { 8 | get: sinon.stub().withArgs('taskgraph-store').returns('mongo') 9 | }; 10 | 11 | before(function() { 12 | helper.setupInjector([ 13 | helper.require('/lib/workflow/stores/store'), 14 | helper.di.simpleWrapper(mongo, 'TaskGraph.Stores.Mongo'), 15 | helper.di.simpleWrapper(configuration, 'Services.Configuration') 16 | ]); 17 | }); 18 | 19 | it('should return the mongo store plugin', function() { 20 | expect(helper.injector.get('TaskGraph.Store')).to.equal(mongo); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /spec/lib/workflow/messengers/messenger-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | describe('Task/TaskGraph messenger factory', function () { 6 | var amqp = {}; 7 | var configuration = { 8 | get: sinon.stub().withArgs('taskgraph-messenger').returns('AMQP') 9 | }; 10 | 11 | before(function() { 12 | helper.setupInjector([ 13 | helper.require('/lib/workflow/messengers/messenger'), 14 | helper.di.simpleWrapper(amqp, 'Task.Messengers.AMQP'), 15 | helper.di.simpleWrapper(configuration, 'Services.Configuration') 16 | ]); 17 | }); 18 | 19 | it('should return the AMQP messenger plugin', function() { 20 | expect(helper.injector.get('Task.Messenger')).to.equal(amqp); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /lib/models/environment.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = EnvModelFactory; 6 | 7 | EnvModelFactory.$provide = 'Models.Environment'; 8 | EnvModelFactory.$inject = [ 9 | 'Model', 10 | 'Services.Configuration' 11 | ]; 12 | 13 | function EnvModelFactory (Model, configuration) { 14 | return Model.extend({ 15 | connection: configuration.get('databaseType', 'mongo'), 16 | identity: 'environment', 17 | attributes: { 18 | identifier: { 19 | type: 'string', 20 | required: true, 21 | primaryKey: true, 22 | unique: true 23 | }, 24 | data: { 25 | type: 'json', 26 | required: true, 27 | json: true 28 | } 29 | } 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /lib/models/roles.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = RolesModelFactory; 6 | 7 | RolesModelFactory.$provide = 'Models.Roles'; 8 | RolesModelFactory.$inject = [ 9 | 'Services.Waterline', 10 | 'Model', 11 | 'Services.Configuration' 12 | ]; 13 | 14 | function RolesModelFactory (waterline, Model, configuration) { 15 | var dbType = configuration.get('databaseType', 'mongo'); 16 | return Model.extend({ 17 | connection: dbType, 18 | identity: 'roles', 19 | attributes: { 20 | role: { 21 | type: 'string', 22 | required: true, 23 | primaryKey: true, 24 | unique: true 25 | }, 26 | privileges: { 27 | type: 'array', 28 | defaultsTo: [] 29 | } 30 | } 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /lib/serializables/result.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = ResultFactory; 6 | 7 | ResultFactory.$provide = 'Result'; 8 | ResultFactory.$inject = [ 9 | 'Assert', 10 | 'Serializable' 11 | ]; 12 | 13 | function ResultFactory (assert, Serializable) { 14 | function Result (defaults) { 15 | Serializable.call( 16 | this, 17 | Result.schema, 18 | defaults 19 | ); 20 | } 21 | 22 | Result.schema = { 23 | id: 'Result', 24 | type: 'object', 25 | properties: { 26 | value: { 27 | /* disabled because task and taskRunner protocol methods return undefined */ 28 | //required: true 29 | } 30 | } 31 | }; 32 | 33 | Serializable.register(ResultFactory, Result); 34 | 35 | return Result; 36 | } 37 | 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | xunit.xml 16 | checkstyle-result.xml 17 | 18 | #jsdoc 19 | docs 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | #Config overrride 28 | overrides.json 29 | 30 | # Dependency directory 31 | # Commenting this out is preferred by some people, see 32 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 33 | node_modules 34 | 35 | # Users Environment Variables 36 | .lock-wscript 37 | 38 | # IDE 39 | .idea 40 | .vscode/ 41 | .tmp/ 42 | .DS_Store 43 | -------------------------------------------------------------------------------- /lib/serializables/ip-address.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = IpAddressFactory; 6 | 7 | IpAddressFactory.$provide = 'IpAddress'; 8 | IpAddressFactory.$inject = [ 9 | 'Assert', 10 | 'Serializable' 11 | ]; 12 | 13 | function IpAddressFactory (assert, Serializable) { 14 | function IpAddress (defaults) { 15 | Serializable.call( 16 | this, 17 | IpAddress.schema, 18 | defaults 19 | ); 20 | } 21 | 22 | IpAddress.schema = { 23 | id: 'IpAddress', 24 | type: 'object', 25 | properties: { 26 | value: { 27 | type: 'string', 28 | format: 'ipv4' 29 | } 30 | }, 31 | required: [ 'value' ] 32 | }; 33 | 34 | Serializable.register(IpAddressFactory, IpAddress); 35 | 36 | return IpAddress; 37 | } 38 | -------------------------------------------------------------------------------- /lib/serializables/mac-address.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = MacAddressFactory; 6 | 7 | MacAddressFactory.$provide = 'MacAddress'; 8 | MacAddressFactory.$inject = [ 9 | 'Assert', 10 | 'Serializable' 11 | ]; 12 | 13 | function MacAddressFactory (assert, Serializable) { 14 | function MacAddress (defaults) { 15 | Serializable.call( 16 | this, 17 | MacAddress.schema, 18 | defaults 19 | ); 20 | } 21 | 22 | MacAddress.schema = { 23 | id: 'MacAddress', 24 | type: 'object', 25 | properties: { 26 | value: { 27 | type: 'string', 28 | pattern: '^([0-9a-fA-F][0-9a-fA-F]:){5}([0-9a-fA-F][0-9a-fA-F])$' 29 | } 30 | }, 31 | required: [ 'value' ] 32 | }; 33 | 34 | Serializable.register(MacAddressFactory, MacAddress); 35 | 36 | return MacAddress; 37 | } 38 | -------------------------------------------------------------------------------- /spec/lib/services/log-publisher-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe('LogPublisher', function () { 7 | var LogEvent, events; 8 | 9 | helper.before(); 10 | 11 | before(function () { 12 | LogEvent = helper.injector.get('LogEvent'); 13 | events = helper.injector.get('Events'); 14 | this.subject = helper.injector.get('Services.LogPublisher'); 15 | }); 16 | 17 | helper.after(); 18 | 19 | describe('handleLogEvent', function() { 20 | it('should not throw on errors', function() { 21 | var create = this.sandbox.stub(LogEvent, 'create').rejects(new Error('Test')), 22 | ignoreError = this.sandbox.stub(events, 'ignoreError'); 23 | 24 | return this.subject.handleLogEvent().then(function () { 25 | create.should.have.been.called; 26 | ignoreError.should.have.been.called; 27 | }); 28 | }); 29 | }); 30 | }); 31 | 32 | -------------------------------------------------------------------------------- /lib/common/timer.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = TimerFactory; 6 | 7 | TimerFactory.$provide = 'Timer'; 8 | 9 | function TimerFactory () { 10 | /** 11 | * Wrapper for process.hrtime. 12 | * @constructor 13 | */ 14 | function Timer () { 15 | } 16 | 17 | /** 18 | * Starts the timer. 19 | */ 20 | Timer.prototype.start = function() { 21 | this.time = process.hrtime(); 22 | }; 23 | 24 | /** 25 | * Clears the timer. 26 | */ 27 | Timer.prototype.clear = function() { 28 | this.time = undefined; 29 | }; 30 | 31 | /** 32 | * Stops the timer and returns the difference in milliseconds. 33 | * @return {Number} 34 | */ 35 | Timer.prototype.stop = function() { 36 | var diff = process.hrtime(this.time); 37 | //process.hrtime returns [, ] 38 | return diff[0] + (diff[1] / 1000000000); 39 | }; 40 | 41 | return Timer; 42 | } 43 | -------------------------------------------------------------------------------- /spec/lib/common/sanitizer-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | describe('Sanitizer', function () { 6 | var graphObj, sanitizer; 7 | helper.before(); 8 | 9 | helper.after(); 10 | 11 | before (function () { 12 | sanitizer = helper.injector.get('Sanitizer'); 13 | 14 | graphObj = {}; 15 | graphObj.id = 'testgraphid'; 16 | graphObj._status = 'pending'; 17 | graphObj.password = 'testPassword'; 18 | graphObj.definition = { 19 | options: { 20 | test_task: { 21 | password: "foo" 22 | } 23 | } 24 | }; 25 | }); 26 | 27 | it ('it removes passwords at first level', function () { 28 | sanitizer.scrub(graphObj); 29 | expect(graphObj.password).to.not.equal('testPassword'); 30 | }); 31 | 32 | it ('it removes passwords at nested levels', function () { 33 | sanitizer.scrub(graphObj); 34 | expect(graphObj.definition.options.test_task.password).to.not.equal('foo'); 35 | }); 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /lib/common/sanitizer.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = SanitizerFactory; 6 | 7 | SanitizerFactory.$provide = 'Sanitizer'; 8 | SanitizerFactory.$inject = [ 9 | 'Services.Encryption', 10 | 'Constants', 11 | 'Util', 12 | '_' 13 | ]; 14 | 15 | function SanitizerFactory ( 16 | encryption, 17 | Constants, 18 | util, 19 | _ 20 | ) { 21 | 22 | var secretsPattern = _.reduce(Constants.Logging.Redactions, function(pattern, regex) { 23 | return pattern ? util.format('%s|(?:%s)', pattern, regex.source) : 24 | util.format('(?:%s)', regex.source); 25 | }, ''); 26 | var secretsRegex = new RegExp(secretsPattern, 'i'); 27 | 28 | function sanitizer (obj) { 29 | _.forOwn(obj, function(value, key) { 30 | if (typeof(value) === 'object') { 31 | sanitizer(value); 32 | } else if ( key.match( secretsRegex )) { 33 | obj[key] = 'REDACTED'; 34 | } 35 | }); 36 | } 37 | 38 | return { 39 | scrub: sanitizer, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /spec/lib/protocol/waterline-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe('Protocol.Waterline', function() { 7 | var waterlineProtocol, 8 | collection = { identity: 'dummy' }, 9 | testSubscription, 10 | messenger; 11 | 12 | helper.before(); 13 | 14 | before(function () { 15 | waterlineProtocol = helper.injector.get('Protocol.Waterline'); 16 | messenger = helper.injector.get('Services.Messenger'); 17 | var Subscription = helper.injector.get('Subscription'); 18 | testSubscription = new Subscription({},{}); 19 | sinon.stub(testSubscription); 20 | }); 21 | 22 | helper.after(); 23 | 24 | it('should publish a created event', function() { 25 | return waterlineProtocol.publishRecord(collection, 'created', { id: 1 }); 26 | }); 27 | it('should publish an updated event', function() { 28 | return waterlineProtocol.publishRecord(collection, 'updated', { id: 1 }); 29 | }); 30 | 31 | it('should publish a destroyed event', function() { 32 | return waterlineProtocol.publishRecord(collection, 'destroyed', { id: 1 }); 33 | }); 34 | }); 35 | 36 | -------------------------------------------------------------------------------- /lib/models/hooks.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = HooksModelFactory; 6 | 7 | HooksModelFactory.$provide = 'Models.Hooks'; 8 | HooksModelFactory.$inject = [ 9 | 'Model', 10 | 'Services.Configuration', 11 | 'uuid' 12 | ]; 13 | 14 | function HooksModelFactory (Model, configuration, uuid) { 15 | 16 | return Model.extend({ 17 | connection: configuration.get('databaseType', 'mongo'), 18 | identity: 'hooks', 19 | attributes: { 20 | id: { 21 | type: 'string', 22 | uuidv4: true, 23 | primaryKey: true, 24 | unique: true, 25 | required: true, 26 | defaultsTo: function() { return uuid.v4(); } 27 | }, 28 | name: { 29 | type: 'string', 30 | required: false 31 | }, 32 | url: { 33 | type: 'string', 34 | unique: true, 35 | required: true 36 | }, 37 | filters: { 38 | type: 'array', 39 | required: false, 40 | defaultsTo: [] 41 | } 42 | } 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /lib/protocol/scheduler.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = schedulerProtocolFactory; 6 | 7 | schedulerProtocolFactory.$provide = 'Protocol.Scheduler'; 8 | schedulerProtocolFactory.$inject = [ 9 | 'Services.Messenger', 10 | 'Constants', 11 | 'Assert' 12 | ]; 13 | 14 | function schedulerProtocolFactory (messenger, Constants, assert) { 15 | function SchedulerProtocol() { 16 | } 17 | 18 | SchedulerProtocol.prototype.schedule = function(taskId, taskName, overrides) { 19 | assert.uuid(taskId); 20 | 21 | return messenger.publish( 22 | Constants.Protocol.Exchanges.Scheduler.Name, 23 | 'schedule', 24 | { taskId: taskId, taskName: taskName, overrides: overrides } 25 | ); 26 | }; 27 | 28 | SchedulerProtocol.prototype.subscribeSchedule = function(callback) { 29 | assert.func(callback); 30 | 31 | return messenger.subscribe( 32 | Constants.Protocol.Exchanges.Scheduler.Name, 33 | 'schedule', 34 | function(data) { 35 | callback(data.taskId, data.taskName, data.overrides); 36 | } 37 | ); 38 | }; 39 | 40 | return new SchedulerProtocol(); 41 | } 42 | -------------------------------------------------------------------------------- /lib/common/system-uuid.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = SystemUuidFactory; 6 | 7 | SystemUuidFactory.$provide = 'SystemUuid'; 8 | SystemUuidFactory.$inject = [ 9 | 'uuid', 10 | 'fs', 11 | 'Logger', 12 | 'Promise', 13 | 'Assert' 14 | ]; 15 | 16 | function SystemUuidFactory(uuid, nodeFs, Logger, Promise, assert) { 17 | var logger = Logger.initialize(SystemUuidFactory); 18 | var fs = Promise.promisifyAll(nodeFs); 19 | 20 | function SystemUuid() { 21 | } 22 | 23 | SystemUuid.prototype.getUuid = function() { 24 | // Attempt to get the system's UUID from sysfs otherwise generate one 25 | return fs.readFileAsync('/sys/class/dmi/id/product_uuid') 26 | .then(function(content) { 27 | return content.toString().toLowerCase().replace(/(\r\n|\n|\r)/gm,''); 28 | }) 29 | .then(function(uuid) { 30 | assert.uuid(uuid, 'System UUID'); 31 | return uuid; 32 | }) 33 | .catch(function(error) { 34 | logger.warning('Error getting system UUID, generating...', error); 35 | return uuid('v4'); 36 | }); 37 | }; 38 | 39 | return new SystemUuid(); 40 | } 41 | 42 | -------------------------------------------------------------------------------- /lib/common/template.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = templateServiceFactory; 6 | 7 | templateServiceFactory.$provide = 'Templates'; 8 | templateServiceFactory.$inject = [ 9 | 'Constants', 10 | 'Promise', 11 | '_', 12 | 'DbRenderableContent', 13 | 'Util' 14 | ]; 15 | 16 | /** 17 | * templateServiceFactory provides the template-service singleton object. 18 | * @private 19 | * @param {FileLoader} FileLoader A FileLoader class for extension. 20 | * @param {configuration} configuration An instance of the configuration configuration object. 21 | * @return {TemplateService} An instance of the TemplateService. 22 | */ 23 | function templateServiceFactory( 24 | Constants, 25 | Promise, 26 | _, 27 | DbRenderable, 28 | Util ) 29 | { 30 | Util.inherits(TemplateService, DbRenderable); 31 | 32 | /** 33 | * TemplateService is a singleton object which provides key/value store 34 | * access to template files loaded from disk via FileLoader. 35 | * @constructor 36 | * @extends {FileLoader} 37 | */ 38 | function TemplateService () { 39 | DbRenderable.call(this, { 40 | directory: Constants.Templates.Directory, 41 | collectionName: 'templates' 42 | }); 43 | } 44 | 45 | return new TemplateService(); 46 | } 47 | -------------------------------------------------------------------------------- /lib/models/renderable.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = RenderableModelFactory; 6 | 7 | RenderableModelFactory.$provide = 'Renderable'; 8 | RenderableModelFactory.$inject = ['uuid']; 9 | 10 | function RenderableModelFactory (uuid) { 11 | return { 12 | connection: 'mongo', 13 | attributes: { 14 | id: { 15 | type: 'string', 16 | uuidv4: true, 17 | primaryKey: true, 18 | unique: true, 19 | required: true, 20 | defaultsTo: function() { return uuid.v4(); } 21 | }, 22 | name: { 23 | type: 'string', 24 | required: true, 25 | unique: false //allow same file name but in different scope 26 | }, 27 | hash: { 28 | type: 'string', 29 | required: true 30 | }, 31 | path: { 32 | type: 'string', 33 | required: true 34 | }, 35 | scope: { 36 | type: 'string', 37 | defaultsTo: 'global' 38 | } 39 | }, 40 | 41 | $indexes: [ 42 | { 43 | keys: { name: 1, scope: 1 }, 44 | options: { unique: true } 45 | } 46 | ], 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /spec/lib/common/system-uuid-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 'use strict'; 3 | 4 | describe("SystemUuid", function() { 5 | var subject; 6 | var regEx = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/; 7 | var fs; 8 | var uuid = '0e2c320f-f29e-47c6-be18-0c833e0f080c'; 9 | 10 | before(function () { 11 | helper.setupInjector(); 12 | subject = helper.injector.get('SystemUuid'); 13 | fs = helper.injector.get('fs'); 14 | sinon.stub(fs, 'readFileAsync'); 15 | }); 16 | 17 | after(function() { 18 | fs.readFileAsync.restore(); 19 | }); 20 | 21 | beforeEach(function() { 22 | fs.readFileAsync.reset(); 23 | }); 24 | 25 | describe('getUuid', function () { 26 | it('should return system uuid', function () { 27 | fs.readFileAsync.resolves(uuid); 28 | return subject.getUuid() 29 | .then(function(data) { 30 | expect(regEx.test(data)).to.be.ok; 31 | expect(data).to.equal(uuid); 32 | }); 33 | }); 34 | 35 | it('should return generated uuid', function () { 36 | fs.readFileAsync.resolves('xyz'); 37 | return subject.getUuid() 38 | .then(function(data) { 39 | expect(regEx.test(data)).to.be.ok; 40 | }); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /lib/models/graph-definition.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = GraphModelFactory; 6 | 7 | GraphModelFactory.$provide = 'Models.GraphDefinition'; 8 | GraphModelFactory.$inject = [ 9 | 'Model', 10 | 'Services.Configuration' 11 | ]; 12 | 13 | function GraphModelFactory (Model, configuration) { 14 | return Model.extend({ 15 | connection: configuration.get('taskgraph-store', 'mongo'), 16 | identity: 'graphdefinitions', 17 | attributes: { 18 | friendlyName: { 19 | type: 'string', 20 | required: true 21 | }, 22 | injectableName: { 23 | type: 'string', 24 | required: true 25 | }, 26 | tasks: { 27 | type: 'array', 28 | required: true, 29 | json: true 30 | }, 31 | toJSON: function() { 32 | // Remove waterline keys that we don't want in our graph objects 33 | var obj = this.toObject(); 34 | delete obj.createdAt; 35 | delete obj.updatedAt; 36 | delete obj.id; 37 | return obj; 38 | } 39 | }, 40 | $indexes: [ 41 | { 42 | keys: { injectableName: 1 }, 43 | options: { unique: true } 44 | } 45 | ] 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /lib/extensions.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | var _ = require('lodash'), 6 | util = require('util'); 7 | 8 | // TODO: get rid of this 9 | /** 10 | * Provide default serialization for all Error objects. 11 | */ 12 | Object.defineProperty(Error.prototype, 'toJSON', { 13 | value: function () { 14 | console.error('Using monkey-patched Error.prototype.toJSON method, ' + 15 | 'which will be deprecated soon.'); 16 | return { 17 | name: this.name, 18 | message: this.message, 19 | stack: this.stack, 20 | context: _.merge( 21 | // Create a default context if none exists. 22 | this.context || {}, 23 | // Copy extra fields into context. 24 | _.omit( 25 | this, 26 | ['stack', 'arguments', 'name', 'message', 'context'] 27 | ) 28 | ) 29 | }; 30 | }, 31 | configurable: true, 32 | writable: true 33 | }); 34 | 35 | /** 36 | * Extension to String to allow for formatting of strings directly using 37 | * util.format. "Hello %s".format("World") returns "Hello World". 38 | */ 39 | String.prototype.format = function () { 40 | return util.format.apply( 41 | null, 42 | _.flattenDeep([ 43 | this, 44 | Array.prototype.slice.call(arguments) 45 | ]) 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /lib/services/core.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = coreServiceFactory; 6 | 7 | coreServiceFactory.$provide = 'Services.Core'; 8 | coreServiceFactory.$inject = [ 9 | 'Promise', 10 | '_', 11 | '$injector' 12 | ]; 13 | 14 | function coreServiceFactory(Promise, _, injector) { 15 | function sortByPriority(services) { 16 | return _.sortBy(services, function(service) { 17 | if (_.has(service, 'startupPriority')) { 18 | return service.startupPriority; 19 | } else { 20 | return Infinity; 21 | } 22 | }); 23 | } 24 | 25 | function CoreService () { 26 | } 27 | 28 | CoreService.prototype.start = function start() { 29 | var services = injector.getMatching(/^Services\.(?!Core).*/); 30 | 31 | services = sortByPriority(services); 32 | 33 | return Promise.reduce(services, function (accumulator, service) { 34 | return service.start(); 35 | }, {}); 36 | }; 37 | 38 | CoreService.prototype.stop = function stop() { 39 | var services = injector.getMatching(/^Services\.(?!Core).*/); 40 | 41 | services = sortByPriority(services).reverse(); 42 | 43 | var promises = _.map(services, function(service) { 44 | return service.stop(); 45 | }); 46 | 47 | return Promise.settle(promises); 48 | }; 49 | 50 | return new CoreService(); 51 | } 52 | -------------------------------------------------------------------------------- /spec/lib/common/timer-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe('Extensions', function () { 7 | var Timer; 8 | 9 | helper.before(); 10 | 11 | before(function () { 12 | Timer = helper.injector.get('Timer'); 13 | }); 14 | 15 | helper.after(); 16 | 17 | describe('Timer', function () { 18 | describe('start', function () { 19 | it('should store hrtime internally', function () { 20 | var subject = new Timer(); 21 | 22 | subject.start(); 23 | 24 | subject.time.should.be.ok; 25 | }); 26 | }); 27 | 28 | describe('clear', function () { 29 | it('should clear the time internally', function () { 30 | var subject = new Timer(); 31 | 32 | subject.start(); 33 | 34 | subject.time.should.be.ok; 35 | 36 | subject.clear(); 37 | 38 | expect(subject.time).to.be.undefined; 39 | }); 40 | }); 41 | 42 | describe('stop', function () { 43 | it('should diff the start time with the stop time', function () { 44 | var subject = new Timer(); 45 | 46 | subject.start(); 47 | 48 | var diff = subject.stop(); 49 | 50 | diff.should.be.a('number'); 51 | 52 | diff.should.be.gt(0.0); 53 | }); 54 | }); 55 | }); 56 | }); -------------------------------------------------------------------------------- /lib/common/util.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = UtilServiceFactory; 6 | 7 | UtilServiceFactory.$provide = 'Util'; 8 | UtilServiceFactory.$inject = [ 9 | 'util', 10 | '_' 11 | ]; 12 | 13 | function UtilServiceFactory(util, _) { 14 | function UtilService () { 15 | } 16 | 17 | _.forEach(_.methods(util), function(method) { 18 | UtilService.prototype[method] = util[method]; 19 | }); 20 | 21 | UtilService.prototype.inheritsStatic = function inheritsStatic(cls, _super) { 22 | _.each(_.methods(_super), function(method) { 23 | cls[method] = _super[method].bind(cls); 24 | }); 25 | }; 26 | 27 | UtilService.prototype.inheritsAll = function inheritsAll(cls, _super) { 28 | this.inherits(cls, _super); 29 | this.inheritsStatic(cls, _super); 30 | }; 31 | 32 | UtilService.prototype.provides = function (constructor) { 33 | if (_.isFunction(constructor)) { 34 | if (constructor.annotations && constructor.annotations.length) { 35 | var provides = _.detect(constructor.annotations, function (annotation) { 36 | return _.has(annotation, 'token'); 37 | }); 38 | 39 | if (provides) { 40 | return provides.token; 41 | } 42 | } 43 | } 44 | 45 | return undefined; 46 | }; 47 | 48 | return new UtilService(); 49 | } 50 | -------------------------------------------------------------------------------- /spec/lib/models/template-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | var base = require('./base-spec'); 7 | var sandbox = sinon.sandbox.create(); 8 | 9 | describe('Models.Template', function () { 10 | helper.before(function (context) { 11 | context.MessengerServices = function() { 12 | this.start= sandbox.stub().resolves(); 13 | this.stop = sandbox.stub().resolves(); 14 | this.publish = sandbox.stub().resolves(); 15 | }; 16 | return [ 17 | helper.di.simpleWrapper(context.MessengerServices, 'Messenger') 18 | ]; 19 | }); 20 | base.before(function (context) { 21 | context.model = helper.injector.get('Services.Waterline').templates; 22 | context.attributes = context.model._attributes; 23 | }); 24 | 25 | helper.after(); 26 | 27 | describe('Base', function () { 28 | base.examples(); 29 | }); 30 | 31 | describe('Attributes', function () { 32 | describe('name', function () { 33 | before(function () { 34 | this.subject = this.attributes.name; 35 | }); 36 | 37 | it('should be a string', function () { 38 | expect(this.subject.type).to.equal('string'); 39 | }); 40 | 41 | it('should be required', function () { 42 | expect(this.subject.required).to.equal(true); 43 | }); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /spec/lib/models/profile-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | var base = require('./base-spec'); 7 | var sandbox = sinon.sandbox.create(); 8 | 9 | describe('Models.Profile', function () { 10 | helper.before(function (context) { 11 | context.MessengerServices = function() { 12 | this.start= sandbox.stub().resolves(); 13 | this.stop = sandbox.stub().resolves(); 14 | this.publish = sandbox.stub().resolves(); 15 | }; 16 | return [ 17 | helper.di.simpleWrapper(context.MessengerServices, 'Messenger') 18 | ]; 19 | }); 20 | 21 | base.before(function (context) { 22 | context.model = helper.injector.get('Services.Waterline').profiles; 23 | context.attributes = context.model._attributes; 24 | }); 25 | 26 | helper.after(); 27 | 28 | describe('Base', function () { 29 | base.examples(); 30 | }); 31 | 32 | describe('Attributes', function () { 33 | describe('name', function () { 34 | before(function () { 35 | this.subject = this.attributes.name; 36 | }); 37 | 38 | it('should be a string', function () { 39 | expect(this.subject.type).to.equal('string'); 40 | }); 41 | 42 | it('should be required', function () { 43 | expect(this.subject.required).to.equal(true); 44 | }); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /spec/lib/serializables/result-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe('Result', function () { 7 | var Result, Serializable, Validatable; 8 | 9 | helper.before(); 10 | 11 | before(function () { 12 | Result = helper.injector.get('Result'); 13 | Serializable = helper.injector.get('Serializable'); 14 | Validatable = helper.injector.get('Validatable'); 15 | }); 16 | 17 | before(function () { 18 | this.subject = new Result({ value: 'random' }); 19 | }); 20 | 21 | helper.after(); 22 | 23 | describe('constructor', function () { 24 | it('should be Serializable', function () { 25 | this.subject.should.be.an.instanceof(Serializable); 26 | }); 27 | 28 | it('should be Validatable', function () { 29 | this.subject.should.be.an.instanceof(Validatable); 30 | }); 31 | 32 | it('should assign value to value', function () { 33 | this.subject.value.should.equal('random'); 34 | }); 35 | }); 36 | 37 | describe('validation', function () { 38 | describe('resolved', function () { 39 | it('should resolve if valid', function () { 40 | return this.subject.validate().should.be.resolved; 41 | }); 42 | 43 | it('should resolve if not valid since it has no rules', function () { 44 | return new Result().validate().should.be.resolved; 45 | }); 46 | }); 47 | }); 48 | }); -------------------------------------------------------------------------------- /lib/models/file.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = FileModelFactory; 6 | 7 | FileModelFactory.$provide = 'Models.File'; 8 | FileModelFactory.$inject = [ 9 | 'Model', 10 | 'Services.Configuration' 11 | ]; 12 | 13 | function FileModelFactory (Model, configuration) { 14 | return Model.extend({ 15 | connection: configuration.get('databaseType', 'mongo'), 16 | identity: 'files', 17 | tableName: 'files', 18 | attributes: { 19 | basename: { 20 | type: 'string', 21 | required: true, 22 | primaryKey: true 23 | }, 24 | 25 | filename: { 26 | type: 'string', 27 | required: true 28 | }, 29 | 30 | uuid: { 31 | type: 'string', 32 | required: true, 33 | uuidv4: true 34 | }, 35 | 36 | md5: { 37 | type: 'string', 38 | required: true 39 | }, 40 | 41 | sha256: { 42 | type: 'string', 43 | required: true 44 | }, 45 | 46 | version: { 47 | type: 'integer', 48 | defaultsTo: 0 49 | }, 50 | 51 | toJSON: function() { 52 | var obj = this.toObject(); 53 | delete obj.id; 54 | delete obj.createdAt; 55 | delete obj.updatedAt; 56 | return obj; 57 | } 58 | } 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /spec/lib/common/util-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | var di = require('di'); 7 | 8 | describe('Util', function () { 9 | var util; 10 | 11 | helper.before(); 12 | 13 | before(function () { 14 | util = helper.injector.get('Util'); 15 | }); 16 | 17 | helper.after(); 18 | 19 | describe('inheritsStatic', function () { 20 | it('needs tests'); 21 | }); 22 | 23 | describe('inheritsAll', function () { 24 | it('needs tests'); 25 | }); 26 | 27 | describe('provides', function () { 28 | it('should return the provides annotation on functions', function () { 29 | function Test () {} 30 | 31 | di.annotate(Test, new di.Provide('ProvidedByTest')); 32 | 33 | util.provides(Test).should.equal('ProvidedByTest'); 34 | }); 35 | 36 | it('should return undefined if only an inject annotation is present', function () { 37 | function Test () {} 38 | 39 | di.annotate(Test, new di.Inject(['ProvidedByTest'])); 40 | 41 | should.equal(util.provides(Test), undefined); 42 | }); 43 | 44 | it('should return undefined if no annotation is present', function () { 45 | function Test () {} 46 | 47 | should.equal(util.provides(Test), undefined); 48 | }); 49 | 50 | it('should return undefined if the provided argument is not a function', function () { 51 | should.equal(util.provides(), undefined); 52 | should.equal(util.provides('Not a Function'), undefined); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /lib/models/task-definition.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = TaskModelFactory; 6 | 7 | TaskModelFactory.$provide = 'Models.TaskDefinition'; 8 | TaskModelFactory.$inject = [ 9 | 'Model', 10 | 'Services.Configuration' 11 | ]; 12 | 13 | function TaskModelFactory (Model, configuration) { 14 | return Model.extend({ 15 | connection: configuration.get('taskgraph-store', 'mongo'), 16 | identity: 'taskdefinitions', 17 | attributes: { 18 | friendlyName: { 19 | type: 'string', 20 | required: true 21 | }, 22 | injectableName: { 23 | type: 'string', 24 | required: true, 25 | unique: true 26 | }, 27 | implementsTask: { 28 | type: 'string', 29 | required: true 30 | }, 31 | options: { 32 | type: 'json', 33 | required: true, 34 | json: true 35 | }, 36 | properties: { 37 | type: 'json', 38 | required: true, 39 | json: true 40 | }, 41 | optionsSchema: { 42 | type: 'json' 43 | }, 44 | toJSON: function() { 45 | // Remove waterline keys that we don't want in our graph objects 46 | var obj = this.toObject(); 47 | delete obj.createdAt; 48 | delete obj.updatedAt; 49 | delete obj.id; 50 | return obj; 51 | } 52 | } 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /spec/lib/serializables/error-event-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe('ErrorEvent', function () { 7 | var ErrorEvent, Serializable, Validatable; 8 | 9 | helper.before(); 10 | 11 | before(function () { 12 | ErrorEvent = helper.injector.get('ErrorEvent'); 13 | Serializable = helper.injector.get('Serializable'); 14 | Validatable = helper.injector.get('Validatable'); 15 | }); 16 | 17 | helper.after(); 18 | 19 | describe('constructor', function () { 20 | before(function () { 21 | var error = new Error('test'); 22 | 23 | error.context = { hello: 'world' }; 24 | 25 | this.subject = new ErrorEvent(error); 26 | }); 27 | 28 | it('should be Serializable', function () { 29 | this.subject.should.be.an.instanceof(Serializable); 30 | }); 31 | 32 | it('should be Validatable', function () { 33 | this.subject.should.be.an.instanceof(Validatable); 34 | }); 35 | 36 | it('should assign error.name to name', function () { 37 | this.subject.name.should.equal('Error'); 38 | }); 39 | 40 | it('should assign error.message to message', function () { 41 | this.subject.message.should.equal('test'); 42 | }); 43 | 44 | it('should assign error.stack to stack', function () { 45 | this.subject.stack.should.not.equal(undefined); 46 | }); 47 | 48 | it('should assign error.context to context', function () { 49 | this.subject.context.should.have.property('hello', 'world'); 50 | }); 51 | }); 52 | }); -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "maxerr" : 50, 3 | 4 | "bitwise" : true, 5 | "camelcase" : false, 6 | "curly" : true, 7 | "es3" : false, 8 | "eqeqeq" : true, 9 | "forin" : true, 10 | "freeze" : true, 11 | "immed" : false, 12 | "indent" : false, 13 | "latedef" : "nofunc", 14 | "maxcomplexity" : false, 15 | "maxdepth" : false, 16 | "maxlen" : false, 17 | "maxparams" : false, 18 | "maxstatements" : false, 19 | "newcap" : false, 20 | "noarg" : true, 21 | "noempty" : false, 22 | "nonbsp" : true, 23 | "nonew" : true, 24 | "plusplus" : true, 25 | "quotmark" : false, 26 | "strict" : true, 27 | "trailing" : true, 28 | "undef" : true, 29 | "unused" : true, 30 | 31 | "asi" : false, 32 | "boss" : false, 33 | "debug" : false, 34 | "eqnull" : false, 35 | "es5" : false, 36 | "esnext" : true, 37 | "moz" : false, 38 | "evil" : false, 39 | "expr" : true, 40 | "funcscope" : false, 41 | "iterator" : false, 42 | "lastsemic" : false, 43 | "laxbreak" : true, 44 | "laxcomma" : true, 45 | "loopfunc" : false, 46 | "multistr" : true, 47 | "noyield" : false, 48 | "notypeof" : false, 49 | "proto" : false, 50 | "scripturl" : false, 51 | "shadow" : false, 52 | "sub" : true, 53 | "supernew" : false, 54 | "validthis" : false, 55 | 56 | "node": true, 57 | "mocha": true, 58 | 59 | "predef" : [ 60 | "_", 61 | "chai", 62 | "expect", 63 | "helper", 64 | "Promise", 65 | "request", 66 | "should", 67 | "sinon", 68 | "sinonPromise" 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /lib/common/assert.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = AssertServiceFactory; 6 | 7 | AssertServiceFactory.$provide = 'Assert'; 8 | AssertServiceFactory.$inject = [ 9 | '_', 10 | 'assert-plus', 11 | 'validator', 12 | 'Constants' 13 | ]; 14 | 15 | function AssertServiceFactory(_, assert, validator, Constants) { 16 | validator.extend('isMac', function(mac) { 17 | return Constants.Regex.MacAddress.test(mac); 18 | }); 19 | 20 | function AssertService () { 21 | } 22 | 23 | /** 24 | * https://github.com/mcavage/node-assert-plus 25 | * 26 | * All methods exposed on assert plus are available on the assert service. 27 | */ 28 | _.each(_.methods(assert), function (method) { 29 | AssertService.prototype[method] = function () { 30 | assert[method].apply(assert, Array.prototype.slice.call(arguments)); 31 | }; 32 | }); 33 | 34 | /** 35 | * https://github.com/chriso/validator.js 36 | * 37 | * All methods exposed on the validator are avaialble on the assert service. 38 | */ 39 | _.each(_.methods(validator), function (method) { 40 | AssertService.prototype[method] = function () { 41 | var args = Array.prototype.slice.call(arguments); 42 | 43 | this.ok( 44 | validator[method].apply( 45 | validator, 46 | args 47 | ), 48 | 'Violated ' + method + ' constraint (' + args.join(',') + ').' 49 | ); 50 | }; 51 | }); 52 | 53 | // TODO: add assert.isObject(object, [type=object.constructor.name]) 54 | 55 | return new AssertService(); 56 | } 57 | -------------------------------------------------------------------------------- /lib/models/graph-object.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = GraphModelFactory; 6 | 7 | GraphModelFactory.$provide = 'Models.GraphObject'; 8 | GraphModelFactory.$inject = [ 9 | 'Model', 10 | 'Constants', 11 | 'Services.Configuration' 12 | ]; 13 | 14 | function GraphModelFactory ( 15 | Model, 16 | Constants, 17 | configuration 18 | ) { 19 | return Model.extend({ 20 | connection: configuration.get('taskgraph-store', 'mongo'), 21 | identity: 'graphobjects', 22 | attributes: { 23 | instanceId: { 24 | type: 'string', 25 | required: true, 26 | unique: true, 27 | uuidv4: true 28 | }, 29 | context: { 30 | type: 'json', 31 | required: true, 32 | json: true 33 | }, 34 | definition: { 35 | type: 'json', 36 | required: true, 37 | json: true 38 | }, 39 | tasks: { 40 | type: 'json', 41 | required: true, 42 | json: true 43 | }, 44 | node: { 45 | model: 'nodes' 46 | }, 47 | parentTaskId: { 48 | type: 'string', 49 | uuidv4: true 50 | }, 51 | parentGraphId: { 52 | type: 'string', 53 | uuidv4: true 54 | }, 55 | active: function() { 56 | var obj = this.toObject(); 57 | return Constants.Task.ActiveStates.indexOf(obj._status) > -1; 58 | } 59 | } 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /lib/common/serializable.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = SerializableFactory; 6 | 7 | SerializableFactory.$provide = 'Serializable'; 8 | SerializableFactory.$inject = [ 9 | 'Promise', 10 | 'Util', 11 | 'Validatable', 12 | '_' 13 | ]; 14 | 15 | function SerializableFactory (Promise, util, Validatable, _) { 16 | function Serializable(schema, defaults) { 17 | Validatable.call(this, schema); 18 | 19 | this.defaults(defaults); 20 | } 21 | 22 | util.inherits(Serializable, Validatable); 23 | 24 | Serializable.register = function (factory, constructor) { 25 | // Provide inheritance in registration to cut down on 26 | // boilerplate registrations and includes of util. 27 | util.inherits(constructor, Serializable); 28 | 29 | // Determine the constructor provides annotation to 30 | // make sure the constructor is usable later in 31 | // dynamic object creation. 32 | constructor.provides = util.provides(factory); 33 | 34 | // Register the constructor schema with the Validatable 35 | // object so they can be used as references to one 36 | // another. 37 | Validatable.register(constructor); 38 | }; 39 | 40 | Serializable.prototype.defaults = function (target) { 41 | _.defaults(this, target); 42 | }; 43 | 44 | Serializable.prototype.serialize = function (target) { 45 | target = target || this; 46 | return Promise.resolve(target); 47 | }; 48 | 49 | Serializable.prototype.deserialize = function (target) { 50 | this.defaults(target); 51 | return Promise.resolve(this); 52 | }; 53 | 54 | return Serializable; 55 | } 56 | 57 | -------------------------------------------------------------------------------- /lib/common/profile.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = profileServiceFactory; 6 | 7 | profileServiceFactory.$provide = 'Profiles'; 8 | profileServiceFactory.$inject = [ 9 | 'Constants', 10 | 'Promise', 11 | '_', 12 | 'DbRenderableContent', 13 | 'Util' 14 | ]; 15 | 16 | /** 17 | * profileServiceFactory provides the profile-service singleton object. 18 | * @private 19 | * @param {FileLoader} FileLoader A FileLoader class for extension. 20 | * @param {configuration} configuration An instance of the configuration configuration object. 21 | * @return {ProfileService} An instance of the ProfileService. 22 | */ 23 | function profileServiceFactory( 24 | Constants, 25 | Promise, 26 | _, 27 | DbRenderable, 28 | Util ) 29 | { 30 | Util.inherits(ProfileService, DbRenderable); 31 | 32 | /** 33 | * ProfileService is a singleton object which provides key/value store 34 | * access to profile files loaded from disk via FileLoader. 35 | * @constructor 36 | * @extends {FileLoader} 37 | */ 38 | function ProfileService () { 39 | DbRenderable.call(this, { 40 | directory: Constants.Profiles.Directory, 41 | collectionName: 'profiles' 42 | }); 43 | } 44 | 45 | ProfileService.prototype.get = function (name, raw, scope ) { 46 | return ProfileService.super_.prototype.get.call(this, name, scope) 47 | .then(function(profile) { 48 | if (profile && raw) { 49 | return profile.contents; 50 | } else { 51 | return profile; 52 | } 53 | }); 54 | }; 55 | 56 | return new ProfileService(); 57 | } 58 | -------------------------------------------------------------------------------- /lib/services/encryption.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = encryptionServiceFactory; 6 | 7 | encryptionServiceFactory.$provide = 'Services.Encryption'; 8 | encryptionServiceFactory.$inject = [ 9 | 'Promise', 10 | 'Services.Configuration', 11 | 'Encryption' 12 | ]; 13 | 14 | function encryptionServiceFactory(Promise, configuration, Encryption) { 15 | var shared = 'qxfO2D3tIJsZACu7UA6Fbw0avowo8r79ALzn+WeuC8M='; 16 | 17 | function EncryptionService () { 18 | this.encryption = new Encryption (); 19 | } 20 | 21 | EncryptionService.prototype.encrypt = function (data, iv) { 22 | if (this.encryption.isEncrypted(data)) { 23 | return data; 24 | } else { 25 | return this.encryption.encrypt( 26 | data, 27 | this.key, 28 | iv 29 | ); 30 | } 31 | }; 32 | 33 | EncryptionService.prototype.decrypt = function (data) { 34 | if (this.encryption.isEncrypted(data)) { 35 | return this.encryption.decrypt( 36 | data, 37 | this.key 38 | ); 39 | } else { 40 | return data; 41 | } 42 | }; 43 | 44 | EncryptionService.prototype.createHash = function(data, algorithm, salt) { 45 | return this.encryption.createHash(data, algorithm, salt); 46 | }; 47 | 48 | EncryptionService.prototype.start = function start() { 49 | this.key = configuration.get('sharedKey', shared); 50 | 51 | return Promise.resolve(); 52 | }; 53 | 54 | EncryptionService.prototype.stop = function stop() { 55 | return Promise.resolve(); 56 | }; 57 | 58 | return new EncryptionService(); 59 | } 60 | -------------------------------------------------------------------------------- /lib/serializables/error-event.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = ErrorEventFactory; 6 | 7 | ErrorEventFactory.$provide = 'ErrorEvent'; 8 | ErrorEventFactory.$inject = [ 9 | 'Assert', 10 | 'Serializable' 11 | ]; 12 | 13 | function ErrorEventFactory (assert, Serializable) { 14 | function ErrorEvent (_error) { 15 | var error = {}; 16 | if (_error !== undefined) { 17 | error.name = _error.name; 18 | error.message = _error.message; 19 | error.stack = _error.stack; 20 | error.context = _error.context || {}; 21 | if (_error.status) { 22 | error.status = _error.status; 23 | } 24 | } 25 | Serializable.call( 26 | this, 27 | ErrorEvent.schema, 28 | // Chai As Promised will instantiate an empty ErrorEvent 29 | // in checks against error types such as the following: 30 | // this.promise.should.be.rejectedWith(ErrorEvent) 31 | error 32 | ); 33 | } 34 | 35 | ErrorEvent.schema = { 36 | id: 'ErrorEvent', 37 | type: 'object', 38 | properties: { 39 | name: { 40 | type: 'string' 41 | }, 42 | message: { 43 | type: 'string' 44 | }, 45 | stack: { 46 | type: 'string' 47 | }, 48 | context: { 49 | type: 'object' 50 | }, 51 | status: { 52 | type: 'number' 53 | } 54 | }, 55 | required: [ 'name', 'message', 'stack', 'context' ] 56 | 57 | }; 58 | 59 | Serializable.register(ErrorEventFactory, ErrorEvent); 60 | 61 | return ErrorEvent; 62 | } 63 | 64 | -------------------------------------------------------------------------------- /lib/services/statsd.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = statsdServiceFactory; 6 | 7 | statsdServiceFactory.$provide = 'Services.StatsD'; 8 | statsdServiceFactory.$inject = [ 9 | 'Util', 10 | 'Promise', 11 | 'Services.Configuration', 12 | 'node-statsd', 13 | 'Assert', 14 | 'Constants' 15 | ]; 16 | 17 | function statsdServiceFactory( 18 | util, 19 | Promise, 20 | configuration, 21 | statsd, 22 | assert, 23 | Constants 24 | ) { 25 | var uuidv4 = /\.[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, 26 | bsonid = /\.[0-9A-F]{24}$/i; 27 | 28 | function StatsDService () {} 29 | 30 | util.inherits(StatsDService, statsd.StatsD); 31 | 32 | StatsDService.prototype.start = function () { 33 | var config = configuration.get('statsd', '127.0.0.1:8125'), 34 | host = config.split(/\:/)[0], 35 | port = parseInt(config.split(/\:/)[1] || 8125); 36 | 37 | statsd.StatsD.call(this, { 38 | host: host, 39 | port: port, 40 | prefix: '%s.%s.'.format(Constants.Host, Constants.Name), 41 | suffix: '' 42 | }); 43 | 44 | this.increment('process.started'); 45 | this.started = true; 46 | 47 | return Promise.resolve(); 48 | }; 49 | 50 | StatsDService.prototype.stop = function () { 51 | if(this.started) { 52 | this.increment('process.stopped'); 53 | this.started = false; 54 | this.close(); 55 | } 56 | return Promise.resolve(); 57 | }; 58 | 59 | StatsDService.prototype.sanitize = function (key) { 60 | assert.string(key, 'key'); 61 | 62 | return key.replace(uuidv4, '').replace(bsonid, ''); 63 | }; 64 | 65 | return new StatsDService(); 66 | } 67 | -------------------------------------------------------------------------------- /lib/models/tags.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = TagModelFactory; 6 | 7 | TagModelFactory.$provide = 'Models.Tag'; 8 | TagModelFactory.$inject = [ 9 | 'Model', 10 | '_', 11 | 'Assert', 12 | 'Validatable', 13 | 'anchor', 14 | 'Services.Configuration', 15 | 'uuid' 16 | ]; 17 | 18 | function TagModelFactory (Model, _, assert, Validatable, Anchor, configuration, uuid) { 19 | var allRules = _.keys(new Anchor().rules); 20 | 21 | return Model.extend({ 22 | types: { 23 | tagRules: function(rules) { 24 | assert.arrayOfObject(rules, 'rules'); 25 | _.forEach(rules, function (rule) { 26 | assert.string(rule.path, 'rule.path'); 27 | _(rule).omit('path').keys().forEach(function (key) { 28 | assert.isIn(key, allRules, 'rule.' + key); 29 | }).value(); 30 | }); 31 | return true; 32 | }, 33 | }, 34 | connection: configuration.get('databaseType', 'mongo'), 35 | identity: 'tags', 36 | attributes: { 37 | id: { 38 | type: 'string', 39 | uuidv4: true, 40 | primaryKey: true, 41 | unique: true, 42 | required: true, 43 | defaultsTo: function() { return uuid.v4(); } 44 | }, 45 | name: { 46 | type: 'string', 47 | required: true 48 | }, 49 | rules: { 50 | type: 'json', 51 | tagRules: true, 52 | required: true 53 | } 54 | }, 55 | $indexes: [ 56 | { 57 | keys: { name: 1 } 58 | } 59 | ] 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /lib/services/log-publisher.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = logPublisherFactory; 6 | 7 | logPublisherFactory.$provide = 'Services.LogPublisher'; 8 | logPublisherFactory.$inject = [ 9 | 'Constants', 10 | 'Events', 11 | 'LogEvent', 12 | 'Messenger', 13 | 'EventEmitter', 14 | 'Util' 15 | ]; 16 | 17 | function logPublisherFactory(Constants, events, LogEvent, Messenger, EventEmitter, util) { 18 | function LogPublisher() { 19 | EventEmitter.call(this); 20 | 21 | // Service Start Priority 22 | this.startupPriority = 1; 23 | 24 | // Embedded Messenger strictly for log publishing. 25 | this.messenger = new Messenger(); 26 | 27 | this.callback = this.handleLogEvent.bind(this); 28 | } 29 | 30 | util.inherits(LogPublisher, EventEmitter); 31 | 32 | LogPublisher.prototype.start = function() { 33 | var self = this; 34 | 35 | return this.messenger.start().then(function () { 36 | events.on(Constants.Events.Log, self.callback); 37 | }); 38 | }; 39 | 40 | LogPublisher.prototype.handleLogEvent = function(options) { 41 | var self = this; 42 | 43 | return LogEvent.create(options).then(function (e) { 44 | return e.print(); 45 | }).then(function (e) { 46 | return self.messenger.publish( 47 | Constants.Protocol.Exchanges.Logging.Name, 48 | e.level, 49 | e 50 | ); 51 | }).catch(function (error) { 52 | events.ignoreError(error); 53 | }); 54 | }; 55 | 56 | LogPublisher.prototype.stop = function() { 57 | var self = this; 58 | 59 | return this.messenger.stop().then(function () { 60 | events.off(Constants.Events.Log, self.callback); 61 | }); 62 | }; 63 | 64 | return new LogPublisher(); 65 | } 66 | 67 | -------------------------------------------------------------------------------- /spec/lib/serializables/ip-address-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe('IpAddress', function () { 7 | var IpAddress, Serializable, Validatable, Errors; 8 | 9 | helper.before(); 10 | 11 | before(function () { 12 | IpAddress = helper.injector.get('IpAddress'); 13 | Serializable = helper.injector.get('Serializable'); 14 | Validatable = helper.injector.get('Validatable'); 15 | Errors = helper.injector.get('Errors'); 16 | }); 17 | 18 | before(function () { 19 | this.subject = new IpAddress({ value: '10.1.1.1' }); 20 | }); 21 | 22 | helper.after(); 23 | 24 | describe('constructor', function () { 25 | it('should be Serializable', function () { 26 | this.subject.should.be.an.instanceof(Serializable); 27 | }); 28 | 29 | it('should be Validatable', function () { 30 | this.subject.should.be.an.instanceof(Validatable); 31 | }); 32 | 33 | it('should assign value to value', function () { 34 | this.subject.value.should.equal('10.1.1.1'); 35 | }); 36 | }); 37 | 38 | describe('validation', function () { 39 | describe('resolved', function () { 40 | it('should resolve if valid', function () { 41 | return this.subject.validate().should.be.resolved; 42 | }); 43 | }); 44 | 45 | describe('rejected', function () { 46 | it('should reject if value is not present', function () { 47 | return new IpAddress().validate().should.be.rejectedWith(Error); 48 | }); 49 | 50 | it('should reject if value is not an ip address', function () { 51 | return new IpAddress( 52 | { value: 'invalid' } 53 | ).validate().should.be.rejectedWith(Error); 54 | }); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /spec/lib/serializables/mac-address-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe('MacAddress', function () { 7 | var MacAddress, Serializable, Validatable, Errors; 8 | 9 | helper.before(); 10 | 11 | before(function () { 12 | MacAddress = helper.injector.get('MacAddress'); 13 | Serializable = helper.injector.get('Serializable'); 14 | Validatable = helper.injector.get('Validatable'); 15 | Errors = helper.injector.get('Errors'); 16 | }); 17 | 18 | before(function () { 19 | this.subject = new MacAddress({ value: '00:11:22:33:44:55' }); 20 | }); 21 | 22 | helper.after(); 23 | 24 | describe('constructor', function () { 25 | it('should be Serializable', function () { 26 | this.subject.should.be.an.instanceof(Serializable); 27 | }); 28 | 29 | it('should be Validatable', function () { 30 | this.subject.should.be.an.instanceof(Validatable); 31 | }); 32 | 33 | it('should assign value to value', function () { 34 | this.subject.value.should.equal('00:11:22:33:44:55'); 35 | }); 36 | }); 37 | 38 | describe('validation', function () { 39 | describe('resolved', function () { 40 | it('should resolve if valid', function () { 41 | return this.subject.validate(); 42 | }); 43 | }); 44 | 45 | describe('rejected', function () { 46 | it('should reject if value is not present', function () { 47 | return new MacAddress().validate().should.be.rejectedWith(Error); 48 | }); 49 | 50 | it('should reject if value is not a mac address', function () { 51 | return new MacAddress( 52 | { value: 'invalid' } 53 | ).validate().should.be.rejectedWith(Error); 54 | }); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /lib/protocol/waterline.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = waterlineProtocolFactory; 6 | 7 | waterlineProtocolFactory.$provide = 'Protocol.Waterline'; 8 | waterlineProtocolFactory.$inject = [ 9 | '_', 10 | 'Assert', 11 | 'Constants', 12 | 'Services.Messenger' 13 | ]; 14 | 15 | function waterlineProtocolFactory (_, assert, Constants, messenger) { 16 | function WaterlineProtocol() { 17 | } 18 | 19 | WaterlineProtocol.prototype.publishRecord = function(collection, event, record, id) { 20 | assert.object(collection, 'collection'); 21 | assert.string(collection.identity, 'collection.identity'); 22 | assert.string(event, 'event'); 23 | assert.object(record, 'record'); 24 | 25 | id = id || record.id; 26 | var routingKey = collection.identity + '.' + event + '.' + id; 27 | 28 | return messenger.publish( 29 | Constants.Protocol.Exchanges.Waterline.Name, 30 | routingKey, 31 | { 32 | event: event, 33 | record: record 34 | } 35 | ); 36 | }; 37 | 38 | WaterlineProtocol.prototype.subscribeGraphCollectionUpdates = function(callback) { 39 | assert.func(callback, 'graph collection updates callback'); 40 | return messenger.subscribe( 41 | Constants.Protocol.Exchanges.Waterline.Name, 42 | 'graphobjects.updated.*', 43 | function(record) { 44 | callback(record.instanceId); 45 | } 46 | ); 47 | }; 48 | 49 | WaterlineProtocol.prototype.subscribeContextCollectionUpdates = function(callback) { 50 | assert.func(callback, 'context collection updates callback'); 51 | return messenger.subscribe( 52 | Constants.Protocol.Exchanges.Waterline.Name, 53 | 'contexts.updated.*', 54 | function(record) { 55 | callback(record.id); 56 | } 57 | ); 58 | }; 59 | 60 | return new WaterlineProtocol(); 61 | } 62 | -------------------------------------------------------------------------------- /lib/services/error-publisher.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = ErrorPublisherFactory; 6 | 7 | ErrorPublisherFactory.$provide = 'Services.ErrorPublisher'; 8 | ErrorPublisherFactory.$inject = [ 9 | 'Constants', 10 | 'Promise', 11 | 'Events', 12 | 'Protocol.Events', 13 | 'Logger' 14 | ]; 15 | 16 | function ErrorPublisherFactory( 17 | Constants, 18 | Promise, 19 | events, 20 | eventsProtocol, 21 | Logger 22 | ) { 23 | var logger = Logger.initialize(ErrorPublisherFactory); 24 | 25 | function ErrorPublisher() { 26 | this.started = false; 27 | 28 | // These are emitted by the 'Events' EventEmitter. 29 | this.ignoredError = this.handleIgnoredError.bind(this); 30 | } 31 | 32 | ErrorPublisher.prototype.start = function() { 33 | var self = this; 34 | 35 | events.on(Constants.Events.Ignored, this.ignoredError); 36 | 37 | process.on('unhandledRejection', this.handleUnhandledRejection.bind(this)); 38 | 39 | return Promise.resolve().then(function () { 40 | self.started = true; 41 | }); 42 | }; 43 | 44 | ErrorPublisher.prototype.handleUnhandledRejection = function(error) { 45 | if (this.started) { 46 | eventsProtocol.publishUnhandledError(error); 47 | } 48 | 49 | logger.debug('Unhandled Rejection', { error: error }); 50 | }; 51 | 52 | ErrorPublisher.prototype.handleIgnoredError = function (error) { 53 | if (this.started) { 54 | eventsProtocol.publishIgnoredError(error); 55 | } 56 | 57 | logger.debug('Ignored Error', { error: error }); 58 | }; 59 | 60 | ErrorPublisher.prototype.stop = function() { 61 | var self = this; 62 | 63 | events.off(Constants.Events.Ignored, this.ignoredError); 64 | 65 | process.removeAllListeners('unhandledRejection'); 66 | 67 | return Promise.resolve().then(function () { 68 | self.started = false; 69 | }); 70 | }; 71 | 72 | return new ErrorPublisher(); 73 | } 74 | -------------------------------------------------------------------------------- /lib/common/promise-queue.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = PromiseQueueFactory; 6 | 7 | PromiseQueueFactory.$provide = 'PromiseQueue'; 8 | PromiseQueueFactory.$inject = [ 9 | 'Assert', 10 | 'EventEmitter', 11 | 'Util', 12 | 'Promise' 13 | ]; 14 | 15 | function PromiseQueueFactory(assert, EventEmitter, util, Promise) { 16 | function noop (timeout) { 17 | return Promise.delay(timeout); 18 | } 19 | 20 | function PromiseQueue(options) { 21 | EventEmitter.call(this); 22 | 23 | this.options = options || {}; 24 | this.callbacks = []; 25 | this.running = false; 26 | } 27 | 28 | util.inherits(PromiseQueue, EventEmitter); 29 | 30 | PromiseQueue.prototype.enqueue = function (callback) { 31 | assert.func(callback); 32 | 33 | this.callbacks.push(callback); 34 | }; 35 | 36 | PromiseQueue.prototype.dequeue = function () { 37 | return this.callbacks.shift(); 38 | }; 39 | 40 | PromiseQueue.prototype.start = function () { 41 | var self = this; 42 | 43 | if (!self.running) { 44 | self.running = true; 45 | 46 | setImmediate(self._process.bind(self)); 47 | 48 | self.emit('started'); 49 | } 50 | }; 51 | 52 | PromiseQueue.prototype._process = function () { 53 | var self = this, 54 | callback = self.dequeue() || noop.bind( 55 | undefined, self.options.timeout || 100 56 | ); 57 | 58 | Promise.resolve().then(function () { 59 | return callback(); 60 | }).catch(function (error) { 61 | self.emit('error', error); 62 | }).finally(function () { 63 | if (self.running) { 64 | setImmediate(self._process.bind(self)); 65 | } else { 66 | self.emit('stopped'); 67 | } 68 | }); 69 | }; 70 | 71 | PromiseQueue.prototype.stop = function () { 72 | this.running = false; 73 | this.callbacks = []; 74 | }; 75 | 76 | return PromiseQueue; 77 | } 78 | -------------------------------------------------------------------------------- /spec/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "maxerr" : 50, //Max errors before it fails the build 3 | 4 | //Enforcing 5 | "bitwise" : true, 6 | "camelcase" : true, 7 | "curly" : true, 8 | "es3" : false, 9 | "eqeqeq" : true, 10 | "forin" : true, 11 | "freeze" : true, 12 | "immed" : true, 13 | "indent" : 4, 14 | "latedef" : "nofunc", //allows function definitions to be ignored 15 | "maxcomplexity" : false, 16 | "maxdepth" : false, 17 | "maxlen" : 100, 18 | "maxparams" : false, 19 | "maxstatements" : false, 20 | "newcap" : true, 21 | "noarg" : true, 22 | "noempty" : true, 23 | "nonbsp" : true, 24 | "nonew" : true, 25 | "plusplus" : true, 26 | "quotmark" : false, 27 | "strict" : true, 28 | "trailing" : true, 29 | "undef" : true, 30 | "unused" : true, 31 | 32 | //Relaxing 33 | "asi" : false, 34 | "boss" : false, 35 | "debug" : false, 36 | "eqnull" : false, 37 | "es5" : false, 38 | "esnext" : false, 39 | "moz" : false, 40 | "evil" : false, 41 | "expr" : true, // true: Tolerate `ExpressionStatement` as Programs 42 | "funcscope" : false, 43 | "globalstrict" : false, 44 | "iterator" : false, 45 | "lastsemic" : false, 46 | "laxbreak" : false, 47 | "laxcomma" : false, 48 | "loopfunc" : false, 49 | "multistr" : false, 50 | "noyield" : false, 51 | "notypeof" : false, 52 | "proto" : false, 53 | "scripturl" : false, 54 | "shadow" : false, 55 | "sub" : false, 56 | "supernew" : false, 57 | "validthis" : false, 58 | 59 | // Environments 60 | "node": true, 61 | 62 | //Custom Globals 63 | "globals" : { 64 | "describe": false, 65 | "it": false, 66 | "before": false, 67 | "beforeEach": false, 68 | "after": false, 69 | "afterEach": false, 70 | "expect": false, 71 | "should": false, 72 | "request": false, 73 | "helper": false, 74 | "chai": false, 75 | "sinon": false, 76 | "_": false, 77 | "Q": false, 78 | "Promise": false 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2015-2018, Dell EMC, Inc. 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - "4" 7 | - "6" 8 | - "8" 9 | 10 | services: 11 | - mongodb 12 | - rabbitmq 13 | 14 | before_install: 15 | - npm i -g npm@5.6.0 16 | 17 | script: 18 | - mongo --version 19 | - npm test 20 | 21 | env: 22 | global: 23 | - secure: "sZxSeXt9D7v4Rer4DZ7Npa42KBenZWwNIRuNnRNHpJV99HUps+YlUpARmnViXKFmIHLDA5XhKkiCdzeZWsy3oBvp9DkuMf6l4Ib19I83LojEQY5BZb2pWXQh4tDrPpm6q/gHuo9K6cUNMP2dSUQDLGch+vkAGDn33ruHrYLq4d8QSYL/b1zDbNoo4J0wgVnfwDvtCYZDQv3eIuVSGLb64EJeRuYyhoB2oGmMApGL9zaxtic/XVB1tqmB/vmQWiQ8wPOenMbc9MRD9mtTFygywYo//0uNPR5kgsony56h+pfGCvmMW+ea8YPCyCl0oZVb9/zdjfg9ZyHsqCzSiJx2QNKVJkjx8KTZ/E+xwdXXgLqNQgPy3bXjdbalHeTFUPKd3ZS5i1pWkhNl7qScKGASbzBCT8oKNqiFE79bgBPBOXDoGjlFisiU/zRu5kbXgVQICSTkb8FLk8jwV5LHZ5Vzm+sQ7rXZUxIoMmW/lCQPs0LtR8zMc6/kM+k6ARN/ld88ox6QcjcRpYFPS+tmv6EdcrJLR+m3Xx0GsbwA7oPXkgs+M8rO8Ry+lidUv26BeD5MV18ilVs9Upx+37/qfn7TO5xENJsJazuxcLGW0fN0TarcgQDLhpAf7x0ZB2eSKmqrlNBlAPmlKFqEZ1nmRjJJl2IURmUbdhgb2nVICAr+js4=" 24 | 25 | 26 | after_success: 27 | - ./node_modules/.bin/istanbul cover -x "**/spec/**" ./node_modules/.bin/_mocha --report lcovonly -- $(find spec -name '*-spec.js') -R spec --require spec/helper.js 28 | - cat ./coverage/lcov.info | node_modules/.bin/coveralls 29 | - chmod 755 ./build_children.sh 30 | - ./build_children.sh 31 | 32 | notifications: 33 | slack: 34 | rooms: 35 | - secure: "DZ+r6cHd9kzKl0BoW2MmLnLhyPNivsjYWyRqVwjYtwNnsMZFSfQ13B6gojEw28PJmVqDu6am/bOhwD6gka8xuu5Fu7In0LMpdL4aNmV6bg/8B8CooYFG+izaa/9Cjsk9t9RG8oZggqXl/U1g4FGweczjU6UC/8WUU/QFsud1GXDZtdLm/80i+/CC1pmhKx73zcgXNWj/j4YDhTY1r3rsEM4nk5m2/CvSo9nYLY4VvTxlH1uA3OVKqDdoMPP8nX6N12qIBYJceDI8uXrBPKFfP+y6V7paq5VNIxAFYxEKkB9AUnNENgPSKvsTP0RWicstYbFosbJHw4o09e/+sui8Mj0UuK4eRfyBdTuj1/kSeUTIYCBffVLXtN5o/Hsm88Ak1Dd7gczQBXMlNQl7nXR9g38m150AMyinvyx52ZdOXiBhPRBbF9riD2fCgjxoCq8W3cVmRaU25lI/YyAMDUGaaVBVjRJCuMEMnFlXJdV2pGPIglRuPrB6llGnyCwTPNlPdsvVjWzQ6j9gV7jQb2mVvjo4c8Lvs0KI0UEAVrysWaslYd1Wgdh8HGSlPJua9Xpo7rQs5wsRqSqFwIZ038NCXWvXtZc828//KvWC+1e26puQfIjwMR/y6cqzF09h7+GkSePxYxoCqbNgSZ/u6KrOx0FOe+E4BrLl5PEtO3Y2iAg=" 36 | on_success: never 37 | on_failure: always 38 | on_start: never 39 | on_pull_requests: false 40 | -------------------------------------------------------------------------------- /spec/lib/services/statsd-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | var dgram = require('dgram'); 7 | 8 | describe("StatsD Service", function () { 9 | helper.before(); 10 | 11 | before(function () { 12 | this.subject = helper.injector.get('Services.StatsD'); 13 | }); 14 | 15 | helper.after(); 16 | 17 | describe('StatsD Methods', function () { 18 | [ 19 | 'timing', 20 | 'increment', 21 | 'decrement', 22 | 'histogram', 23 | 'gauge', 24 | 'set', 25 | 'unique' 26 | ].forEach(function (method) { 27 | it('should have a ' + method, function () { 28 | this.subject.should.respondTo(method); 29 | }); 30 | }); 31 | }); 32 | 33 | describe('sanitize', function () { 34 | it('should not alter a basic key', function () { 35 | this.subject.sanitize('test').should.equal('test'); 36 | }); 37 | 38 | it('should remove bson id from the end of the key', function () { 39 | this.subject.sanitize('test.551375513754f63fa62abc47').should.equal('test'); 40 | }); 41 | 42 | it('should remove uuidv4 from the end of the key', function () { 43 | this.subject.sanitize('test.de305d54-75b4-431b-adb2-eb6b9e546013').should.equal('test'); 44 | }); 45 | }); 46 | 47 | describe('Publishing', function () { 48 | before(function () { 49 | var self = this; 50 | 51 | return new Promise(function (resolve) { 52 | self.server = dgram.createSocket('udp4'); 53 | self.server.bind(8125); 54 | 55 | self.server.once('listening', function () { 56 | resolve(); 57 | }); 58 | }); 59 | }); 60 | 61 | it('should publish metrics to localhost:8125', function (done) { 62 | this.server.once('message', function () { 63 | done(); 64 | }); 65 | 66 | this.subject.gauge('test', 1); 67 | }); 68 | 69 | after(function () { 70 | this.server.close(); 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /lib/common/message.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = MessageFactory; 6 | 7 | MessageFactory.$provide = 'Message'; 8 | MessageFactory.$inject = [ 9 | 'Assert', 10 | 'ErrorEvent', 11 | '$injector' 12 | ]; 13 | 14 | function MessageFactory (assert, ErrorEvent, injector) { 15 | 16 | /** 17 | * Constructor for a message 18 | * 19 | * @param {*} messenger 20 | * @param {Object} data 21 | * @param {*} [options] 22 | * @constructor 23 | */ 24 | function Message (data, headers, deliveryInfo) { 25 | assert.object(data); 26 | assert.object(headers); 27 | assert.object(deliveryInfo); 28 | 29 | this.data = Message.factory(data, deliveryInfo.type); 30 | this.headers = headers; 31 | this.deliveryInfo = deliveryInfo; 32 | } 33 | 34 | Message.prototype.isRequest = function () { 35 | return this.deliveryInfo.replyTo !== undefined; 36 | }; 37 | 38 | /** 39 | * publishes a response for a given message using the replyTo property in 40 | * the message's 41 | * 42 | * @param {Object} data 43 | */ 44 | Message.prototype.respond = function (data) { 45 | assert.ok(this.deliveryInfo, 'deliveryInfo'); 46 | assert.ok(this.deliveryInfo.replyTo, 'replyTo'); 47 | 48 | var messenger = injector.get('Services.Messenger'); 49 | 50 | return messenger.publish( 51 | '', // default exchange used for direct queues 52 | this.deliveryInfo.replyTo, 53 | data 54 | ); 55 | }; 56 | 57 | Message.prototype.resolve = function (data) { 58 | return this.respond(data); 59 | }; 60 | 61 | Message.prototype.reject = function (error) { 62 | return this.respond(new ErrorEvent(error)); 63 | }; 64 | 65 | Message.factory = function (data, type) { 66 | var Type; 67 | 68 | // Attempt to derive the object for marshalling based on the type. 69 | try { 70 | Type = injector.get(type); 71 | } catch (error) { 72 | // noop 73 | } 74 | 75 | return Type !== undefined ? new Type(data): data; 76 | }; 77 | 78 | return Message; 79 | } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # on-core [![Build Status](http://travis-ci.org/RackHD/on-core.svg?branch=master)](https://travis-ci.org/RackHD/on-core) [![Code Climate](https://codeclimate.com/github/RackHD/on-core/badges/gpa.svg)](https://codeclimate.com/github/RackHD/on-core) [![Coverage Status](https://coveralls.io/repos/RackHD/on-core/badge.svg?branch=master&service=github)](https://coveralls.io/github/RackHD/on-core?branch=master) 2 | 3 | `on-core` provides common node.js libraries for applications in the RackHD project. 4 | 5 | Copyright 2015, EMC, Inc. 6 | 7 | ## CI/testing 8 | 9 | Prereqs: 10 | 11 | The tests included with this project are a combination of strictly unit 12 | and some functional tests, so to run to completion and correctly expect 13 | some services to be available and running locally: 14 | 15 | - rabbitmq 16 | - mongodb 17 | 18 | `./HWIMO-TEST` will run local tests, and was built for running on a jenkins build slave, and will run the tests, jshint, and code coverage all together. 19 | 20 | 21 | # Configuration 22 | 23 | The following configuration values can be overridden via the environment, command line, or via the global configuration file located at /opt/monorail/config.json. 24 | 25 | The global configuration file location can be overridden by setting a new configuration file value in the environment using the MONORAIL_CONFIG environment variable. 26 | 27 | ## amqp 28 | 29 | Defaults to `amqp://localhost`. 30 | 31 | ## mongo 32 | 33 | Defaults to `mongodb://localhost/pxe`. 34 | 35 | ## statsd 36 | 37 | Defaults to `localhost:8125`. 38 | 39 | ## sharedKey 40 | 41 | A 32 bit base64 encoded string, defaults to `qxfO2D3tIJsZACu7UA6Fbw0avowo8r79ALzn+WeuC8M=`. 42 | 43 | ## Licensing 44 | 45 | Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 46 | 47 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 48 | 49 | RackHD is a Trademark of Dell EMC 50 | -------------------------------------------------------------------------------- /HWIMO-TEST: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # Copyright 2015, EMC, Inc. 3 | 4 | checkDependencies(){ 5 | mongo_path=$( which mongod ) 6 | if [ -z "$mongo_path" ]; then 7 | echo "[ERROR]: the unit test requires mongodb service installed" 8 | exit 1 9 | fi 10 | rabbitmq_path=$( which rabbitmq-server ) 11 | if [ -z "$rabbitmq_path" ]; then 12 | echo "[ERROR]: the unit test requires rabbitmq service installed" 13 | exit 1 14 | fi 15 | 16 | # mongod works for version after 2.6 17 | # mongodb works for version before 2.6 18 | mongo_status=$(service mongodb status || service mongod status) 19 | export is_mongo_running=$(echo $mongo_status |grep running) 20 | export rabbitmq_status=$(sudo service rabbitmq-server status|grep pid) 21 | 22 | } 23 | 24 | cleanDatabase(){ 25 | mongo pxe --eval "db.dropDatabase()" 26 | mongo monorail-test --eval "db.dropDatabase()" 27 | } 28 | 29 | handleDependServices(){ 30 | action=$1 31 | if [ -z "$is_mongo_running" ]; then 32 | echo "[INFO]: $action mongodb service" 33 | sudo service mongodb $action || sudo service mongod $action 34 | fi 35 | 36 | if [ -z "$rabbitmq_status" ]; then 37 | echo "[INFO]: $action rabbitmq service" 38 | sudo service rabbitmq-server $action 39 | fi 40 | } 41 | 42 | prepareDependencies(){ 43 | handleDependServices start 44 | sleep 2 45 | cleanDatabase 46 | } 47 | 48 | restoreDependencies(){ 49 | cleanDatabase 50 | handleDependServices stop 51 | } 52 | 53 | build(){ 54 | rm -rf node_modules 55 | npm install 56 | } 57 | 58 | unitTest(){ 59 | # Checks the code against the jshint options 60 | ./node_modules/.bin/jshint -c .jshintrc --reporter=checkstyle lib index.js > checkstyle-result.xml || true 61 | ./node_modules/.bin/jshint -c .jshintrc lib index.js || true 62 | 63 | # Runs the mocha tests and reports the code coverage. 64 | ./node_modules/.bin/istanbul cover -x "**/spec/**" ./node_modules/.bin/_mocha -- $(find spec -name '*-spec.js') --timeout 10000 -R xunit-file --require spec/helper.js 65 | ./node_modules/.bin/istanbul report cobertura 66 | } 67 | checkDependencies 68 | prepareDependencies 69 | trap restoreDependencies SIGINT SIGTERM SIGKILL EXIT 70 | build 71 | unitTest 72 | -------------------------------------------------------------------------------- /lib/common/validatable.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = ValidatableFactory; 6 | 7 | ValidatableFactory.$provide = 'Validatable'; 8 | ValidatableFactory.$inject = [ 9 | 'Promise', 10 | '_', 11 | 'jsonschema', 12 | 'Errors', 13 | 'Assert' 14 | ]; 15 | 16 | function ValidatableFactory (Promise, _, jsonschema, Errors, assert) { 17 | var Validator = jsonschema.Validator, 18 | validator = new Validator(); 19 | 20 | function Validatable(schema) { 21 | assert.object(schema); 22 | 23 | Object.defineProperty(this, 'schema', { 24 | value: schema, 25 | enumerable: false 26 | }); 27 | } 28 | 29 | Validatable.register = function (constructor) { 30 | assert.func(constructor, 'Must be a constructor function.'); 31 | assert.object(constructor.schema, 'Must specify a schema property.'); 32 | assert.string(constructor.schema.id, 'Must specify a schema id.'); 33 | 34 | // Add the constructor schema to the available schemas for validation. 35 | validator.addSchema(constructor.schema, '/%s'.format(constructor.schema.id)); 36 | }; 37 | 38 | Validatable.prototype.validate = function (target, schema) { 39 | target = target || this; 40 | 41 | var outcome = validator.validate(target, schema || this.schema); 42 | 43 | if (_.isEmpty(outcome.errors)) { 44 | return Promise.resolve(target); 45 | } else { 46 | // Wrap with new Error because util.isError checks in swagger 47 | // fail on our custom errors. 48 | // Add context errors so the JSON schema violation is visibile to 49 | // the caller. 50 | var error = new Error(new Errors.SchemaError(outcome)); 51 | error.violations = outcome.errors; 52 | return Promise.reject(error); 53 | } 54 | }; 55 | 56 | Validatable.prototype.validatePartial = function (target) { 57 | target = target || this; 58 | 59 | return this.validate(target, _.omit(this.schema, 'required')); 60 | }; 61 | 62 | Validatable.prototype.validateAsModel = function (target) { 63 | target = target || this; 64 | 65 | return this.validate(target); 66 | }; 67 | 68 | return Validatable; 69 | } 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "//": "Copyright 2017, Dell EMC, Inc.", 3 | "name": "on-core", 4 | "version": "2.60.7", 5 | "description": "OnCore Library", 6 | "main": "index.js", 7 | "scripts": { 8 | "doc": "jsdoc lib -r -d docs", 9 | "test": "./node_modules/.bin/mocha $(find spec -name '*-spec.js') -R spec --require spec/helper.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/RackHD/on-core.git" 14 | }, 15 | "author": "", 16 | "license": "Apache-2.0", 17 | "bugs": { 18 | "url": "https://github.com/RackHD/on-core/issues" 19 | }, 20 | "homepage": "https://github.com/RackHD/on-core", 21 | "dependencies": { 22 | "ajv": "^4.1.0", 23 | "always-tail": "^0.2.0", 24 | "amqp": "0.2.3", 25 | "anchor": "^0.10.2", 26 | "assert-plus": "^1.0.0", 27 | "bluebird": "^2.8.0", 28 | "bson": "~0.4.22", 29 | "colors": "^1.0.3", 30 | "crypt3": "^1.0.0", 31 | "di": "git+https://github.com/RackHD/di.js.git", 32 | "ejs": "^2.0.8", 33 | "eventemitter2": "^0.4.14", 34 | "flat": "^1.6.0", 35 | "glob": "^4.3.2", 36 | "hogan.js": "^3.0.2", 37 | "jsonschema": "^1.0.0", 38 | "lodash": "^3.10.1", 39 | "lru-cache": "^3.2.0", 40 | "nanoid": "^1.0.2", 41 | "nconf": "^0.7.1", 42 | "node-statsd": "^0.1.1", 43 | "node-uuid": "^1.4.2", 44 | "pg": "^4.5.5", 45 | "pluralize": "~1.1.2", 46 | "prettyjson": "^1.0.0", 47 | "resolve": "^1.0.0", 48 | "rx": "4.0.6", 49 | "sails-mongo": "^0.11.5", 50 | "sails-postgresql": "^0.11.4", 51 | "stack-trace": "0.0.9", 52 | "validate.js": "^0.3.2", 53 | "validator": "^3.27.0", 54 | "waterline": "^0.10.21", 55 | "waterline-criteria": "^0.11.1" 56 | }, 57 | "devDependencies": { 58 | "chai": "^2.0.0", 59 | "chai-as-promised": "^4.2.0", 60 | "coveralls": "^2.11.4", 61 | "express": "^4.10.7", 62 | "grunt": "^0.4.5", 63 | "grunt-cli": "^0.1.13", 64 | "grunt-contrib-jshint": "^0.11.0", 65 | "grunt-contrib-watch": "^0.6.1", 66 | "grunt-mocha-istanbul": "^2.3.1", 67 | "grunt-mocha-test": "^0.12.7", 68 | "grunt-notify": "^0.4.1", 69 | "istanbul": "^0.3.5", 70 | "jsdoc": "^3.3.0-alpha13", 71 | "jshint": "^2.5.11", 72 | "mocha": "^4.0.1", 73 | "nock": "~9.0.22", 74 | "sinon": "1.16.1", 75 | "sinon-as-promised": "^2.0.3", 76 | "sinon-chai": "^2.7.0", 77 | "supertest": "^1.2.0", 78 | "xunit-file": "0.0.6" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/services/argument-handler.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = argumentHandlerServiceFactory; 6 | 7 | argumentHandlerServiceFactory.$provide = 'Services.ArgumentHandler'; 8 | argumentHandlerServiceFactory.$inject = [ 9 | 'Promise', 10 | '_', 11 | 'Services.Configuration', 12 | 'LogEvent' 13 | ]; 14 | 15 | function argumentHandlerServiceFactory( 16 | Promise, 17 | _, 18 | config, 19 | LogEvent 20 | ){ 21 | function ArgumentHandlerService() {} 22 | 23 | /** 24 | * Get the value from configuration by a collection of keys 25 | * 26 | * Sometimes we define different keys in configuration file and command line argument, because 27 | * configuration file prefers the full key name, but command line prefers the short and mnemonic 28 | * name. The argument handle can support the configuration from either file or command line, so 29 | * this function is a helper to enumerate all keys, any key exists in configuration will return 30 | * its value, if all keys don't exist, then return the default value. 31 | * The earlier key in the input keys collection will take precedence over latter. 32 | * 33 | * @param {Collection} keys - the collection of keys 34 | * @param {*} defaultValue - the default value that will be returned if all keys don't exist. 35 | * @returns {*} the value of those keys, if all keys don't exist, will return the defaultValue. 36 | */ 37 | ArgumentHandlerService.prototype._getValue = function(keys, defaultValue) { 38 | var val; 39 | _.forEach(keys, function(key) { 40 | var v = config.get(key); 41 | if (v !== undefined) { 42 | val = v; 43 | return false; 44 | } 45 | }); 46 | return (val === undefined ? defaultValue : val); 47 | }; 48 | 49 | /** 50 | * Start the argument handler service. 51 | */ 52 | ArgumentHandlerService.prototype.start = function() { 53 | var self = this; 54 | return Promise.try(function() { 55 | //Configure the log colorful output 56 | var colorEnable = self._getValue(['color', 'logColorEnable'], false); 57 | LogEvent.setColorEnable(colorEnable); 58 | }); 59 | }; 60 | 61 | ArgumentHandlerService.prototype.stop = function() { 62 | return Promise.resolve(); 63 | }; 64 | 65 | return new ArgumentHandlerService(); 66 | } 67 | -------------------------------------------------------------------------------- /lib/models/task-dependency.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = TaskDependencyFactory; 6 | 7 | TaskDependencyFactory.$provide = 'Models.TaskDependency'; 8 | TaskDependencyFactory.$inject = [ 9 | 'Model', 10 | 'Constants', 11 | 'Services.Configuration' 12 | ]; 13 | 14 | function TaskDependencyFactory (Model, Constants, configuration) { 15 | return Model.extend({ 16 | connection: configuration.get('taskgraph-store', 'mongo'), 17 | identity: 'taskdependencies', 18 | attributes: { 19 | domain: { 20 | type: 'string', 21 | defaultsTo: Constants.Task.DefaultDomain 22 | }, 23 | taskId: { 24 | type: 'string', 25 | required: true, 26 | unique: true, 27 | uuidv4: true 28 | }, 29 | graphId: { 30 | type: 'string', 31 | required: true 32 | }, 33 | state: { 34 | type: 'string', 35 | required: true 36 | }, 37 | evaluated: { 38 | type: 'boolean', 39 | defaultsTo: false 40 | }, 41 | reachable: { 42 | type: 'boolean', 43 | defaultsTo: true 44 | }, 45 | taskRunnerLease: { 46 | type: 'string', 47 | defaultsTo: null 48 | }, 49 | taskRunnerHeartbeat: { 50 | type: 'date', 51 | defaultsTo: null 52 | }, 53 | dependencies: { 54 | type: 'json', 55 | required: true 56 | }, 57 | terminalOnStates: { 58 | type: 'array', 59 | defaultsTo: [] 60 | }, 61 | context: { 62 | type: 'json', 63 | defaultsTo: null 64 | }, 65 | toJSON: function() { 66 | // Remove waterline keys that we don't want in our dependency object 67 | var obj = this.toObject(); 68 | delete obj.createdAt; 69 | delete obj.updatedAt; 70 | delete obj.id; 71 | return obj; 72 | } 73 | }, 74 | 75 | $indexes: [ 76 | { 77 | keys: { taskId: 1, graphId: 1 }, 78 | options: { unique: true } 79 | } 80 | ] 81 | }); 82 | } 83 | -------------------------------------------------------------------------------- /spec/lib/models/roles-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | var base = require('./base-spec'); 7 | var sandbox = sinon.sandbox.create(); 8 | 9 | describe('Models.Roles', function () { 10 | helper.before(function (context) { 11 | context.MessengerServices = function() { 12 | this.start= sandbox.stub().resolves(); 13 | this.stop = sandbox.stub().resolves(); 14 | this.publish = sandbox.stub().resolves(); 15 | }; 16 | return [ 17 | helper.di.simpleWrapper(context.MessengerServices, 'Messenger') 18 | ]; 19 | }); 20 | 21 | base.before(function (context) { 22 | context.model = helper.injector.get('Services.Waterline').roles; 23 | context.attributes = context.model._attributes; 24 | }); 25 | 26 | helper.after(); 27 | 28 | describe('Base', function () { 29 | describe('createdAt', function () { 30 | before(function () { 31 | this.subject = this.attributes.createdAt; 32 | }); 33 | 34 | it('should be a datetime', function () { 35 | expect(this.subject.type).to.equal('datetime'); 36 | }); 37 | }); 38 | 39 | describe('updatedAt', function () { 40 | before(function () { 41 | this.subject = this.attributes.updatedAt; 42 | }); 43 | 44 | it('should be a datetime', function () { 45 | expect(this.subject.type).to.equal('datetime'); 46 | }); 47 | }); 48 | }); 49 | 50 | describe('Attributes', function () { 51 | describe('role', function () { 52 | before(function () { 53 | this.subject = this.attributes.role; 54 | }); 55 | 56 | it('should be a string', function () { 57 | expect(this.subject.type).to.equal('string'); 58 | }); 59 | 60 | it('should be required', function () { 61 | expect(this.subject.required).to.equal(true); 62 | }); 63 | 64 | it('should be a primary key', function () { 65 | expect(this.subject.primaryKey).to.equal(true); 66 | }); 67 | }); 68 | 69 | describe('privileges', function () { 70 | before(function () { 71 | this.subject = this.attributes.privileges; 72 | }); 73 | 74 | it('should be a string', function () { 75 | expect(this.subject.type).to.equal('array'); 76 | }); 77 | }); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /spec/lib/protocol/scheduler-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe("Schedular protocol functions", function () { 7 | var testSubscription, 8 | testMessage, 9 | messenger, 10 | scheduler; 11 | 12 | helper.before(); 13 | 14 | before(function () { 15 | scheduler = helper.injector.get('Protocol.Scheduler'); 16 | messenger = helper.injector.get('Services.Messenger'); 17 | var Message = helper.injector.get('Message'); 18 | var Subscription = helper.injector.get('Subscription'); 19 | 20 | testSubscription = new Subscription({},{}); 21 | testMessage = new Message({},{},{}); 22 | sinon.stub(testMessage); 23 | }); 24 | 25 | beforeEach(function() { 26 | this.sandbox.stub(messenger, 'request'); 27 | }); 28 | 29 | helper.after(); 30 | 31 | describe("schedule", function () { 32 | 33 | it("should publish and receive a schedule request", function () { 34 | //NOTE: no matching internal code to listen for these events 35 | var self = this, 36 | Promise = helper.injector.get('Promise'), 37 | uuid = helper.injector.get('uuid'), 38 | deferred = Promise.defer(), 39 | data = { 40 | taskId:uuid.v4(), 41 | taskName:"testTaskName", 42 | overrides: { 43 | timeout: -1 44 | } 45 | }; 46 | messenger.subscribe = sinon.spy(function(a,b,callback) { 47 | callback(data,testMessage); 48 | return Promise.resolve(testSubscription); 49 | }); 50 | messenger.request.resolves(data); 51 | return scheduler.subscribeSchedule(function (cbTaskId, cbTaskName, cbOverrides) { 52 | try { 53 | expect(cbTaskId).to.equal(data.taskId); 54 | expect(cbTaskName).to.equal(data.taskName); 55 | expect(cbOverrides).to.deep.equal(data.overrides); 56 | deferred.resolve(); 57 | } catch (err) { 58 | deferred.reject(err); 59 | } 60 | }).then(function (subscription) { 61 | expect(subscription).to.be.ok; 62 | return scheduler.schedule(data.taskId, data.taskName, data.overrides); 63 | }).catch(function (err) { 64 | deferred.reject(err); 65 | }); 66 | 67 | return deferred.promise; 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /lib/models/log.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = LogModelFactory; 6 | 7 | LogModelFactory.$provide = 'Models.Log'; 8 | LogModelFactory.$inject = [ 9 | 'Model', 10 | 'Constants', 11 | '_', 12 | 'Services.Configuration' 13 | ]; 14 | 15 | function LogModelFactory (Model, constants, _, configuration) { 16 | return Model.extend({ 17 | connection: configuration.get('databaseType', 'mongo'), 18 | identity: 'logs', 19 | autoPK: true, 20 | attributes: { 21 | module: { 22 | type: 'string', 23 | required: false 24 | }, 25 | level: { 26 | type: 'string', 27 | required: true, 28 | in: _.keys(constants.Logging.Levels) 29 | }, 30 | message: { 31 | type: 'string', 32 | required: true 33 | }, 34 | context: { 35 | type: 'json', 36 | required: false, 37 | json: true 38 | }, 39 | trace: { 40 | type: 'string', 41 | uuidv4: true, 42 | required: true 43 | }, 44 | timestamp: { 45 | type: 'datetime', 46 | required: true 47 | }, 48 | caller: { 49 | type: 'string', 50 | required: true 51 | }, 52 | subject: { 53 | type: 'string', 54 | required: true 55 | }, 56 | host: { 57 | type: 'string', 58 | required: true 59 | } 60 | }, 61 | beforeCreate: function(values, cb) { 62 | if (values.context && values.context.data) { 63 | values.context.data = updateKeys(values.context.data, _); 64 | } 65 | cb(); 66 | } 67 | }); 68 | } 69 | 70 | /* 71 | * Change all key values containing '.' to '_', since the driver won't allow 72 | * keys with dots by default 73 | */ 74 | function updateKeys(obj, _) { 75 | var newObj = null; 76 | if (_.isArray(obj)) { 77 | newObj = []; 78 | } else if (_.isObject(obj)) { 79 | newObj = {}; 80 | } else { 81 | return obj; 82 | } 83 | _.forEach(obj, function(value, key) { 84 | if (key.replace) { 85 | var newKey = key.replace(/\$|\./g, '_'); 86 | newObj[newKey] = updateKeys(value, _); 87 | } else { 88 | newObj[key] = updateKeys(value, _); 89 | } 90 | }); 91 | return newObj; 92 | } 93 | -------------------------------------------------------------------------------- /spec/lib/common/graph-progress-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | 3 | 'use strict'; 4 | describe('GraphProgress', function() { 5 | var graph; 6 | var taskId; 7 | var graphId; 8 | var graphDescription; 9 | var taskProgress; 10 | var progressData; 11 | var GraphProgress; 12 | 13 | before(function() { 14 | helper.setupInjector([ 15 | helper.require('/lib/common/graph-progress') 16 | ]); 17 | GraphProgress = helper.injector.get('GraphProgress'); 18 | }); 19 | 20 | beforeEach(function() { 21 | taskId = 'taskId'; 22 | graphId = 'graphId'; 23 | graph = { 24 | instanceId: graphId, 25 | name: 'test graph name', 26 | node: 'nodeId', 27 | _status: 'failed', 28 | tasks: {} 29 | }; 30 | graphDescription = 'test graph description'; 31 | graph.tasks[taskId] = { 32 | friendlyName: 'test task name', 33 | state: 'pending', 34 | terminalOnStates: ['succeeded'] 35 | }; 36 | taskProgress = { 37 | value: 1, 38 | maximum: 4, 39 | description: 'test task description' 40 | }; 41 | progressData = { 42 | graphId: graphId, 43 | graphName: 'test graph name', 44 | nodeId: 'nodeId', 45 | status: graph._status, 46 | progress: { 47 | value: 0, 48 | maximum: 1, 49 | percentage: '0%', 50 | description: graphDescription 51 | }, 52 | taskProgress: { 53 | taskId: taskId, 54 | taskName: 'test task name', 55 | state: 'pending', 56 | progress: { 57 | value: 1, 58 | maximum: 4, 59 | percentage: '25%', 60 | description: 'test task description' 61 | }, 62 | } 63 | }; 64 | }); 65 | 66 | it('should get progress without taskProgress', function() { 67 | var progress = GraphProgress.create(graph, graphDescription); 68 | var data = progress.getProgressEventData(); 69 | delete progressData.taskProgress; 70 | expect(data).to.deep.equal(progressData); 71 | }); 72 | 73 | it('should get progress with taskProgress and updated task percentage', function() { 74 | var progress = GraphProgress.create(graph, graphDescription); 75 | progress.updateTaskProgress(taskId, taskProgress); 76 | var data = progress.getProgressEventData(); 77 | expect(data).to.deep.equal(progressData); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /spec/lib/services/waterline-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | describe('Services.Waterline', function () { 6 | var waterline, 7 | sandbox = sinon.sandbox.create(); 8 | 9 | helper.before(function(context) { 10 | context.Core = { 11 | start: sandbox.stub().resolves(), 12 | stop: sandbox.stub().resolves() 13 | }; 14 | return helper.di.simpleWrapper(context.Core, 'Services.Core' ); 15 | }); 16 | 17 | before(function() { 18 | waterline = helper.injector.get('Services.Waterline'); 19 | }); 20 | 21 | helper.after(); 22 | after(function() { 23 | sandbox.restore(); 24 | }); 25 | 26 | describe('start', function () { 27 | it('should start service and resolve', function() { 28 | var createIndexesStub = this.sandbox.stub().resolves(); 29 | waterline.service.initialize = this.sandbox.spy(function(cfg,callback) { 30 | var ontology = { 31 | collections:{ 32 | 'testModel': { 33 | identity: 'test', 34 | createIndexes: createIndexesStub 35 | } 36 | } 37 | }; 38 | callback(undefined, ontology); 39 | }); 40 | return waterline.start().then(function() { 41 | expect(createIndexesStub).to.have.been.calledOnce; 42 | }); 43 | }); 44 | 45 | it('should resolve itself if already initialized', function() { 46 | this.sandbox.stub(waterline, 'isInitialized').returns(true); 47 | return waterline.start().should.be.resolved; 48 | }); 49 | 50 | it('should reject if an error occurs when it is not initialized', function() { 51 | this.sandbox.stub(waterline, 'isInitialized').returns(false); 52 | waterline.service.initialize = this.sandbox.spy(function(cfg,callback) { 53 | callback(Error); 54 | }); 55 | return waterline.start().should.be.rejected; 56 | }); 57 | }); 58 | 59 | describe('stop', function () { 60 | it('should teardown and resolve when initialized', function() { 61 | waterline.service.teardown = this.sandbox.spy(function(callback) { 62 | callback(); 63 | }); 64 | this.sandbox.stub(waterline, 'isInitialized').returns(true); 65 | return waterline.stop(); 66 | }); 67 | 68 | it('should resolve when not initialized', function() { 69 | this.sandbox.stub(waterline, 'isInitialized').returns(false); 70 | return waterline.stop(); 71 | }); 72 | }); 73 | 74 | }); 75 | 76 | -------------------------------------------------------------------------------- /lib/models/local-user.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = LocalUserModelFactory; 6 | 7 | LocalUserModelFactory.$provide = 'Models.LocalUser'; 8 | LocalUserModelFactory.$inject = [ 9 | 'Model', 10 | 'Services.Configuration' 11 | ]; 12 | 13 | var crypto = require('crypto'); 14 | 15 | var hashConfig = { 16 | // number of bytes in the Hash 17 | hashBytes: 64, 18 | // number of bytes in the salt 19 | saltBytes: 64, 20 | // number of iterations to get the final hash, longer means more secure, 21 | // but also slower 22 | iterations: 10000 23 | }; 24 | 25 | 26 | function LocalUserModelFactory (Model, configuration) { 27 | return Model.extend({ 28 | connection: configuration.get('databaseType', 'mongo'), 29 | identity: 'localusers', 30 | attributes: { 31 | username: { 32 | type: 'string', 33 | required: true, 34 | primaryKey: true 35 | }, 36 | password:{ 37 | type: 'string', 38 | required: true, 39 | minLength: 6, 40 | maxLength: 50 41 | }, 42 | role: { 43 | type: 'string', 44 | defaultsTo: 'ReadOnly' // change to Constants 45 | }, 46 | comparePassword: function (password) { 47 | var combined = new Buffer(this.password, 'base64'); 48 | var saltBytes = hashConfig.saltBytes; 49 | var hashBytes = combined.length - hashConfig.saltBytes; 50 | var iterations = hashConfig.iterations; 51 | var salt = combined.slice(0, saltBytes); 52 | var hash = combined.toString('binary', saltBytes); 53 | var verify = crypto.pbkdf2Sync(password, salt, iterations, hashBytes, 'sha512'); 54 | return verify.toString('binary') === hash; 55 | } 56 | }, 57 | beforeCreate: serialize, 58 | beforeUpdate: serialize 59 | }); 60 | 61 | function serialize(obj, next) { 62 | crypto.randomBytes(hashConfig.saltBytes, function(err, salt) { 63 | if (err) { 64 | return next(err); 65 | } 66 | crypto.pbkdf2(obj.password, salt, hashConfig.iterations, hashConfig.hashBytes, "sha512", function(err, hash) { //jshint ignore: line 67 | if (err) { 68 | return next(err); 69 | } 70 | 71 | var combined = new Buffer(hash.length + salt.length); 72 | salt.copy(combined); 73 | hash.copy(combined, salt.length); 74 | obj.password = combined.toString('base64'); 75 | next(); 76 | }); 77 | }); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /spec/lib/common/assert-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 5 | 'use strict'; 6 | 7 | // Required because they are used pre-initialization of injector 8 | // for building out test cases. 9 | var assertPlus = require('assert-plus'); 10 | var validator = require('validator'); 11 | 12 | describe("AssertService", function() { 13 | var assert; 14 | 15 | helper.before(); 16 | 17 | before(function () { 18 | assert = helper.injector.get('Assert'); 19 | }); 20 | 21 | helper.after(); 22 | 23 | _.methods(assertPlus).forEach(function (method) { 24 | it('should have a ' + method + ' method', function () { 25 | expect(assert).to.respondTo(method); 26 | }); 27 | }); 28 | 29 | it('should work the way assert-plus intended', function () { 30 | expect(function () { 31 | assert.bool('not a bool'); 32 | }).to.throw(); 33 | 34 | expect(function () { 35 | assert.bool(true); 36 | }).to.not.throw(); 37 | 38 | expect(function () { 39 | assert.bool(false); 40 | }).to.not.throw(); 41 | 42 | expect(function () { 43 | assert.ok(undefined); 44 | }).to.throw(); 45 | }); 46 | 47 | _.methods(validator).forEach(function (method) { 48 | it('should have a ' + method + ' method', function () { 49 | expect(assert).to.respondTo(method); 50 | }); 51 | }); 52 | 53 | it('should work the way validator intended', function () { 54 | expect(function () { 55 | assert.isIP('not an ip'); 56 | }).to.throw(); 57 | 58 | expect(function () { 59 | assert.isIP('10.1.1.1'); 60 | }).to.not.throw(); 61 | }); 62 | 63 | describe('arguments', function () { 64 | it('should do throw if no arguments are present', function () { 65 | expect(function () { 66 | assert.isMac(); 67 | }).to.throw(); 68 | 69 | expect(function () { 70 | assert.ok(); 71 | }).to.throw(); 72 | }); 73 | }); 74 | 75 | describe('macaddress', function () { 76 | it('should throw on an invalid mac address', function () { 77 | expect(function () { 78 | assert.isMac('invalid'); 79 | }).to.throw(); 80 | 81 | expect(function () { 82 | assert.isMac('00:11:22:33:44:55:66'); 83 | }).to.throw(); 84 | 85 | expect(function () { 86 | assert.isMac('00:11:22:33:44'); 87 | }).to.throw(); 88 | }); 89 | 90 | it('should not throw on a valid mac address', function () { 91 | expect(function () { 92 | assert.isMac('00:11:22:33:44:55'); 93 | }); 94 | }); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /lib/models/sku.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = SkuModelFactory; 6 | 7 | SkuModelFactory.$provide = 'Models.Sku'; 8 | SkuModelFactory.$inject = [ 9 | 'Model', 10 | '_', 11 | 'Assert', 12 | 'Validatable', 13 | 'anchor', 14 | 'Services.Configuration', 15 | 'uuid' 16 | ]; 17 | 18 | function SkuModelFactory (Model, _, assert, Validatable, Anchor, configuration, uuid) { 19 | var allRules = _.keys(new Anchor().rules); 20 | return Model.extend({ 21 | types: { 22 | skuRules: function(rules) { 23 | assert.arrayOfObject(rules, 'rules'); 24 | _.forEach(rules, function (rule) { 25 | assert.string(rule.path, 'rule.path'); 26 | _(rule).omit('path').keys().forEach(function (key) { 27 | assert.isIn(key, allRules, 'rule.' + key); 28 | }).value(); 29 | }); 30 | return true; 31 | }, 32 | }, 33 | connection: configuration.get('databaseType', 'mongo'), 34 | identity: 'skus', 35 | attributes: { 36 | id: { 37 | type: 'string', 38 | uuidv4: true, 39 | primaryKey: true, 40 | unique: true, 41 | required: true, 42 | defaultsTo: function() { return uuid.v4(); } 43 | }, 44 | name: { 45 | type: 'string', 46 | required: true 47 | }, 48 | rules: { 49 | type: 'json', 50 | skuRules: true, 51 | required: true 52 | }, 53 | nodes: { 54 | collection: 'nodes', 55 | via: 'sku' 56 | }, 57 | discoveryGraphName: { 58 | type: 'string' 59 | }, 60 | discoveryGraphOptions: { 61 | type: 'json' 62 | }, 63 | httpStaticRoot : { 64 | type: 'string' 65 | }, 66 | httpTemplateRoot : { 67 | type: 'string' 68 | }, 69 | httpProfileRoot : { 70 | type: 'string' 71 | }, 72 | workflowRoot : { 73 | type: 'string' 74 | }, 75 | taskRoot : { 76 | type: 'string' 77 | }, 78 | skuConfig: { 79 | type: 'json' 80 | }, 81 | version : { 82 | type: 'string' 83 | }, 84 | description : { 85 | type: 'string' 86 | } 87 | }, 88 | $indexes: [ 89 | { 90 | keys: { name: 1 } 91 | } 92 | ] 93 | }); 94 | } 95 | -------------------------------------------------------------------------------- /lib/services/environment.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = environmentServiceFactory; 6 | 7 | environmentServiceFactory.$provide = 'Services.Environment'; 8 | environmentServiceFactory.$inject = [ 9 | 'Promise', 10 | 'Services.Waterline', 11 | '_', 12 | 'Assert' 13 | ]; 14 | 15 | function environmentServiceFactory( 16 | Promise, 17 | waterline, 18 | _, 19 | assert 20 | ) { 21 | function EnvironmentService() { 22 | } 23 | 24 | /** 25 | * Set the 'key' to the 'value' in the document identified by 'identifier' 26 | * @param {String} key 27 | * @param {Object} value 28 | * @param {String} identifier 29 | */ 30 | EnvironmentService.prototype.set = function set(key, value, identifier) { 31 | identifier = identifier || 'global'; 32 | return waterline.environment.findOrCreate( 33 | {identifier: identifier}, 34 | {identifier: identifier, data: {}} 35 | ).then(function(env) { 36 | _.set(env.data, key, value); 37 | return waterline.environment.update({identifier: identifier}, {data: env.data}); 38 | }); 39 | }; 40 | 41 | /** 42 | * Retrieve the 'key' using the hierarchy specified in identifiers. 43 | * The defaults value is returned if the key does not exist 44 | * @param {String} key 45 | * @param {Object} defaults 46 | * @param {Array} identifiers 47 | */ 48 | EnvironmentService.prototype.get = function get(key, defaults, identifiers) { 49 | return this.getAll(identifiers).then(function(envs) { 50 | return _.get(envs, key, defaults); 51 | }); 52 | }; 53 | 54 | /** 55 | * Retrieve the documents specified by identifiers and merge in order of priority 56 | * @param {Array} identifiers 57 | */ 58 | EnvironmentService.prototype.getAll = function (identifiers) { 59 | identifiers = identifiers || ['global']; 60 | assert.arrayOfString(identifiers, 'identifiers should be an array'); 61 | return Promise.all(Promise.map( identifiers, function(identifier) { 62 | return waterline.environment.findOne({identifier: identifier}); 63 | }).filter(function(env) { 64 | return env; 65 | })).then(function(envs) { 66 | var data = _.sortBy(envs, function(env) { 67 | return (envs.length - identifiers.indexOf(env.identifier)); 68 | }); 69 | var data2 = _.merge.apply(_, _.flatten([{}, data])); 70 | return data2.data; 71 | }); 72 | }; 73 | 74 | EnvironmentService.prototype.start = function start() { 75 | return Promise.resolve(); 76 | }; 77 | 78 | EnvironmentService.prototype.stop = function stop() { 79 | return Promise.resolve(); 80 | }; 81 | 82 | return new EnvironmentService(); 83 | } 84 | -------------------------------------------------------------------------------- /spec/lib/common/profile-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe('Profiles', function () { 7 | var waterline; 8 | var loader; 9 | var Logger; 10 | var crypto; 11 | 12 | before(function () { 13 | helper.setupInjector(); 14 | this.subject = helper.injector.get('Profiles'); 15 | this.sandbox = sinon.sandbox.create(); 16 | waterline = helper.injector.get('Services.Waterline'); 17 | loader = helper.injector.get('FileLoader'); 18 | Logger = helper.injector.get('Logger'); 19 | crypto = helper.injector.get('crypto'); 20 | Logger.prototype.log = sinon.spy(); 21 | }); 22 | 23 | beforeEach(function() { 24 | waterline.profiles = { 25 | findOne: sinon.stub().resolves(), 26 | find: sinon.stub().resolves(), 27 | create: sinon.stub().resolves(), 28 | update: sinon.stub().resolves(), 29 | destroy: sinon.stub().resolves() 30 | }; 31 | this.sandbox.stub(loader.prototype, 'get'); 32 | 33 | Logger.prototype.log.reset(); 34 | }); 35 | 36 | afterEach(function() { 37 | this.sandbox.restore(); 38 | }); 39 | 40 | describe('get', function() { 41 | it('should get a profile', function() { 42 | var profile = { 43 | name: 'test profile', 44 | scope: 'global', 45 | path: 'path', 46 | hash: crypto.createHash('md5').update('test contents').digest('base64') 47 | }; 48 | waterline.profiles.find.resolves([ profile ]); 49 | loader.prototype.get.resolves('test contents'); 50 | return this.subject.get('test profile') 51 | .then(function(out) { 52 | expect(out).to.have.property('contents'); 53 | _.forEach(_.keys(out), function(key) { 54 | expect(out[key]).to.equal(profile[key] || 'test contents'); 55 | }); 56 | expect(waterline.profiles.find) 57 | .to.have.been.calledWith({ name: 'test profile' }); 58 | }); 59 | }); 60 | 61 | it('should get a raw profile', function() { 62 | var profile = { 63 | name: 'test profile', 64 | scope: 'global', 65 | path: 'path', 66 | hash: crypto.createHash('md5').update('test contents').digest('base64') 67 | }; 68 | waterline.profiles.find.resolves([ profile ]); 69 | loader.prototype.get.resolves('test contents'); 70 | return this.subject.get('test profile', true) 71 | .then(function(out) { 72 | expect(out).to.equal('test contents'); 73 | expect(waterline.profiles.find) 74 | .to.have.been.calledWith({ name: 'test profile' }); 75 | }); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /spec/lib/services/encryption-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe('Encryption Service', function () { 7 | var EncryptionService, 8 | Encryption, 9 | Configuration; 10 | 11 | helper.before(); 12 | 13 | before(function () { 14 | Encryption = helper.injector.get('Encryption'); 15 | EncryptionService = helper.injector.get('Services.Encryption'); 16 | Configuration = helper.injector.get('Services.Configuration'); 17 | sinon.stub(Configuration); 18 | }); 19 | 20 | helper.after(); 21 | 22 | describe('start/stop', function () { 23 | it('should call Encryption.prototype.start', function () { 24 | Configuration.get.resolves('sharedKey'); 25 | return EncryptionService.start() 26 | .then(function() { 27 | expect(Promise.resolve()).to.be.ok; 28 | }); 29 | }); 30 | 31 | it('should call Encryption.prototype.stop', function () { 32 | return EncryptionService.stop() 33 | .then(function() { 34 | expect(Promise.resolve()).to.be.ok; 35 | }); 36 | }); 37 | }); 38 | 39 | describe('encrypt', function () { 40 | it('should call Encryption.prototype.encrypt', function () { 41 | var encrypt = this.sandbox.stub(Encryption.prototype, 'encrypt'); 42 | EncryptionService.encrypt('Hello World'); 43 | encrypt.should.have.been.calledWith('Hello World'); 44 | }); 45 | 46 | it('should return the data if already encrypted', function() { 47 | var isEncrypted = this.sandbox.stub(Encryption.prototype, 'isEncrypted'); 48 | isEncrypted.resolves(true); 49 | var target = EncryptionService.encrypt('NOOP'); 50 | EncryptionService.encrypt(target).should.equal(target); 51 | }); 52 | }); 53 | 54 | describe('decrypt', function () { 55 | it('should call Encryption.prototype.decrypt', function () { 56 | var isEncrypted = this.sandbox.stub(Encryption.prototype, 'isEncrypted'), 57 | decrypt = this.sandbox.stub(Encryption.prototype, 'decrypt'); 58 | isEncrypted.resolves(false); 59 | var target = EncryptionService.encrypt('Hello World'); 60 | EncryptionService.decrypt(target); 61 | decrypt.should.have.been.calledWith(target); 62 | }); 63 | 64 | it('should return the data if already decrypted', function() { 65 | EncryptionService.decrypt('NOOP').should.equal('NOOP'); 66 | }); 67 | }); 68 | 69 | describe('createHash', function () { 70 | it('should call Encryption.prototype.createHash', function () { 71 | var hash = this.sandbox.stub(Encryption.prototype, 'createHash'); 72 | EncryptionService.createHash('Hello World'); 73 | hash.should.have.been.calledWith('Hello World'); 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /lib/common/logger.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = loggerFactory; 6 | 7 | loggerFactory.$provide = 'Logger'; 8 | loggerFactory.$inject = [ 9 | 'Events', 10 | 'Constants', 11 | 'Assert', 12 | '_', 13 | 'Util', 14 | 'stack-trace', 15 | 'nconf' 16 | ]; 17 | 18 | /** 19 | * loggerFactory returns a Logger instance. 20 | * @private 21 | */ 22 | function loggerFactory( 23 | events, 24 | Constants, 25 | assert, 26 | _, 27 | util, 28 | stack, 29 | nconf 30 | ) { 31 | var levels = _.keys(Constants.Logging.Levels); 32 | 33 | function getCaller (depth) { 34 | var current = stack.get()[depth]; 35 | 36 | var file = current.getFileName().replace( 37 | Constants.WorkingDirectory, 38 | '' 39 | ) + ':' + current.getLineNumber(); 40 | 41 | return file.replace(/^node_modules/, ''); 42 | } 43 | 44 | function Logger (module) { 45 | var provides = util.provides(module); 46 | 47 | if (provides !== undefined) { 48 | this.module = provides; 49 | } else { 50 | if (_.isFunction(module)) { 51 | this.module = module.name; 52 | } else { 53 | this.module = module || 'No Module'; 54 | } 55 | } 56 | } 57 | 58 | Logger.prototype.log = function (level, message, context) { 59 | assert.isIn(level, levels); 60 | assert.string(message, 'message'); 61 | 62 | // Exit if the log level of this message is less than the minimum log level. 63 | var minLogLevel = nconf.get('minLogLevel'); 64 | if ((minLogLevel === undefined) || (typeof minLogLevel !== 'number')) { 65 | minLogLevel = 0; 66 | } 67 | if (Constants.Logging.Levels[level] < minLogLevel) { 68 | return; 69 | } 70 | 71 | events.log({ 72 | name: Constants.Name, 73 | host: Constants.Host, 74 | module: this.module, 75 | level: level, 76 | message: message, 77 | context: context, 78 | timestamp: new Date().toISOString(), 79 | caller: getCaller(3), 80 | subject: 'Server' 81 | }); 82 | }; 83 | 84 | _.forEach(levels, function(level) { 85 | Logger.prototype[level] = function (message, context) { 86 | this.log(level, message, context); 87 | }; 88 | }); 89 | 90 | Logger.prototype.deprecate = function (message, frames) { 91 | console.error([ 92 | 'DEPRECATION:', 93 | this.module, 94 | '-', 95 | message, 96 | getCaller(frames || 2) 97 | ].join(' ')); 98 | }; 99 | 100 | Logger.initialize = function (module) { 101 | return new Logger(module); 102 | }; 103 | 104 | return Logger; 105 | } 106 | -------------------------------------------------------------------------------- /lib/models/catalog.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = CatalogModelFactory; 6 | 7 | CatalogModelFactory.$provide = 'Models.Catalog'; 8 | CatalogModelFactory.$inject = [ 9 | 'Model', 10 | '_', 11 | 'Services.Configuration', 12 | 'uuid' 13 | ]; 14 | 15 | function CatalogModelFactory ( 16 | Model, 17 | _, 18 | configuration, 19 | uuid 20 | ) { 21 | return Model.extend({ 22 | connection: configuration.get('databaseType', 'mongo'), 23 | identity: 'catalogs', 24 | attributes: { 25 | id: { 26 | type: 'string', 27 | uuidv4: true, 28 | primaryKey: true, 29 | unique: true, 30 | required: true, 31 | defaultsTo: function() { return uuid.v4(); } 32 | }, 33 | node: { 34 | model: 'nodes', 35 | required: true 36 | }, 37 | source: { 38 | type: 'string', 39 | required: true 40 | }, 41 | data: { 42 | type: 'json', 43 | required: true, 44 | json: true 45 | } 46 | }, 47 | 48 | beforeCreate: function(values, cb) { 49 | values.data = updateKeys(values.data, _); 50 | cb(); 51 | }, 52 | 53 | $indexes: [ 54 | { 55 | keys: { node: 1 } 56 | }, 57 | { 58 | keys: { source: 1 } 59 | }, 60 | { 61 | keys: { node: 1, source: 1 }, 62 | options: { unique: false } //not unique index since we allow old catalogs exist 63 | } 64 | ], 65 | 66 | /** 67 | * Retrieves the most recent catalog of the source identified for the given node 68 | * 69 | * @param nodeId {string} 70 | * @param source {string} 71 | */ 72 | findLatestCatalogOfSource: function findLatestCatalog(nodeId, source) { 73 | return this.findMostRecent({ 74 | node: nodeId, 75 | source: source 76 | }); 77 | } 78 | }); 79 | } 80 | 81 | /* 82 | * Change all key values containing '.' to '_', since the driver won't allow 83 | * keys with dots by default 84 | */ 85 | function updateKeys(obj, _) { 86 | var newObj = null; 87 | if (_.isArray(obj)) { 88 | newObj = []; 89 | } else if (_.isObject(obj)) { 90 | newObj = {}; 91 | } else { 92 | return obj; 93 | } 94 | _.forEach(obj, function(value, key) { 95 | if (key.replace) { 96 | var newKey = key.replace(/\$|\./g, '_'); 97 | newObj[newKey] = updateKeys(value, _); 98 | } else { 99 | newObj[key] = updateKeys(value, _); 100 | } 101 | }); 102 | return newObj; 103 | } 104 | -------------------------------------------------------------------------------- /lib/common/hook.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017, Dell EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = hookFactory; 6 | 7 | hookFactory.$provide = 'Hook'; 8 | hookFactory.$inject = [ 9 | '_', 10 | 'Assert', 11 | 'Services.Waterline', 12 | 'HttpTool', 13 | 'Promise' 14 | ]; 15 | 16 | function hookFactory( 17 | _, 18 | assert, 19 | waterline, 20 | HttpTool, 21 | Promise 22 | ) { 23 | function Hook(){ 24 | } 25 | 26 | /** 27 | * Post data to RackHD-stored web hook list 28 | * 29 | * @param {Object} data 30 | * @returns {Promise} 31 | */ 32 | Hook.prototype.publish = function(data) { 33 | var self = this; 34 | return Promise.try(function(){ 35 | return waterline.hooks.find({}); 36 | }) 37 | .map(function(hook){ 38 | return self._publish(hook, data); 39 | }); 40 | }; 41 | 42 | /** 43 | * Post data to provided web hook 44 | * 45 | * @param {Array} hookList 46 | * @param {Object} data 47 | * @returns {Promise} 48 | */ 49 | Hook.prototype._publish = function(hook, data) { 50 | //Current filtering mechanism only filters against common event header 51 | //Payload data filtering in event messages is not supported 52 | var isQualified = _filter(data, hook.filters); 53 | if(!isQualified){ 54 | return; 55 | } 56 | 57 | var httpSettings = { 58 | method: 'POST', 59 | headers: {'Content-Type': 'application/json'}, 60 | data: data, 61 | url: hook.url 62 | }; 63 | var httpTool = new HttpTool(); 64 | return httpTool.setupRequest(httpSettings) 65 | .then(function(){ 66 | return httpTool.runRequest(); 67 | }); 68 | }; 69 | 70 | function _filter(data, filters) { 71 | if (_.isEmpty(filters)) { 72 | return true; 73 | } 74 | var isQualified = false; 75 | _.forEach(filters, function(filter){ 76 | isQualified = _conditionFilter(data, filter); 77 | return !isQualified; 78 | }); 79 | return isQualified; 80 | } 81 | 82 | function _conditionFilter(data, filter) { 83 | var isPassed = true; 84 | _.forEach(filter, function(value, key){ 85 | //TODO: if in the future we implemented caching for hooks, RE should be compiled 86 | // in advance instead of inside forEach loop. 87 | var pattern = new RegExp(value); 88 | isPassed = _.has(data, key) && pattern.test(data[key]); 89 | return isPassed; 90 | }); 91 | return isPassed; 92 | } 93 | 94 | Hook.prototype.start = function start() { 95 | return Promise.resolve(); 96 | }; 97 | 98 | Hook.prototype.stop = function stop() { 99 | return Promise.resolve(); 100 | }; 101 | return Hook; 102 | } 103 | -------------------------------------------------------------------------------- /spec/lib/services/environment-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe('Services.Environment', function () { 7 | var waterline; 8 | 9 | helper.before(); 10 | 11 | before(function () { 12 | this.subject = helper.injector.get('Services.Environment'); 13 | waterline = helper.injector.get('Services.Waterline'); 14 | }); 15 | 16 | beforeEach('set up mocks', function() { 17 | this.sandbox.stub(waterline.environment, "findOne"); 18 | }); 19 | 20 | helper.after(); 21 | 22 | var global = { 23 | identifier: 'global', 24 | data: { 25 | key1: { 26 | key2: 'key2-value', 27 | key3: 'key3-value' 28 | } 29 | } 30 | }; 31 | 32 | var sku = { 33 | identifier: 'sku', 34 | data: { 35 | key1: { 36 | key2: 'sku-value' 37 | } 38 | } 39 | }; 40 | 41 | it('should get a key-value', function() { 42 | waterline.environment.findOne.withArgs({identifier: 'global'}).resolves(global); 43 | return this.subject.get('key1.key2', 'value1').then(function(val) { 44 | val.should.equal('key2-value'); 45 | }); 46 | }); 47 | 48 | it('should get the highest priority key-value', function() { 49 | waterline.environment.findOne.withArgs({identifier: 'global'}).resolves(global); 50 | waterline.environment.findOne.withArgs({identifier: 'sku'}).resolves(sku); 51 | return this.subject.get('key1.key2', 'value1', ['sku', 'global']).then(function(val) { 52 | val.should.equal('sku-value'); 53 | }); 54 | }); 55 | 56 | it('should get the lower priority key-value when the high prioritiy is emtpy', function() { 57 | waterline.environment.findOne.withArgs({identifier: 'global'}).resolves(global); 58 | waterline.environment.findOne.withArgs({identifier: 'sku'}).resolves(sku); 59 | return this.subject.get('key1.key3', 'value1', ['sku', 'global']).then(function(val) { 60 | val.should.equal('key3-value'); 61 | }); 62 | }); 63 | 64 | it('should merge priorities', function() { 65 | waterline.environment.findOne.withArgs({identifier: 'global'}).resolves(global); 66 | waterline.environment.findOne.withArgs({identifier: 'sku'}).resolves(sku); 67 | return this.subject.getAll(['sku', 'global']) 68 | .should.eventually.deep.equal(_.merge({}, global, sku).data); 69 | }); 70 | 71 | it('should set a key-value', function() { 72 | var global = {}; 73 | this.sandbox.stub(waterline.environment, "update", function(where, data) { 74 | global = data.data; 75 | return [global]; 76 | }); 77 | waterline.environment.update.withArgs({identifier: 'global'}); 78 | return this.subject.set('key1.key2', 'value').then(function() { 79 | global.key1.key2.should.equal('value'); 80 | waterline.environment.update.restore(); 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /spec/lib/services/configuration-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe(require('path').basename(__filename), function () { 7 | var Constants, fs, nconf; 8 | 9 | helper.before(); 10 | 11 | before(function () { 12 | Constants = helper.injector.get('Constants'); 13 | fs = helper.injector.get('fs'); 14 | nconf = helper.injector.get('nconf'); 15 | this.subject = helper.injector.get('Services.Configuration'); 16 | }); 17 | 18 | helper.after(); 19 | 20 | describe('Instance Methods', function () { 21 | describe('set', function() { 22 | it('should chain', function() { 23 | this.subject.set('foo', 'bar').should.equal(this.subject); 24 | }); 25 | 26 | it('should set the key to the given value', function() { 27 | this.subject.set('foo', 'bar').get('foo').should.equal('bar'); 28 | }); 29 | }); 30 | 31 | describe('get', function() { 32 | it('should return the requested value', function() { 33 | this.subject.set('foo', 'bar').get('foo').should.equal('bar'); 34 | }); 35 | 36 | it('should use the default value provided if no value is defined', function() { 37 | this.subject.get('missing', 'override').should.be.equal('override'); 38 | }); 39 | }); 40 | 41 | describe('getAll', function() { 42 | it('should return all configuration values', function() { 43 | this.subject.getAll().should.be.an('object'); 44 | }); 45 | }); 46 | 47 | describe('start', function () { 48 | describe('defaults', function () { 49 | beforeEach(function () { 50 | sinon.stub(fs, 'existsSync'); 51 | sinon.stub(nconf, 'file').returns(); 52 | }); 53 | 54 | afterEach(function () { 55 | fs.existsSync.restore(); 56 | nconf.file.restore(); 57 | }); 58 | 59 | it('applies defaults from the global configuration file', function() { 60 | fs.existsSync.withArgs( 61 | Constants.Configuration.Files.Global 62 | ).returns(true); 63 | this.subject.load(); 64 | nconf.file.should.have.been.calledWith( 65 | 'global', Constants.Configuration.Files.Global 66 | ); 67 | }); 68 | it('applies defaults from the dell configuration file', function() { 69 | fs.existsSync.withArgs( 70 | Constants.Configuration.Files.Dell 71 | ).returns(true); 72 | this.subject.load(); 73 | nconf.file.should.have.been.calledWith( 74 | 'dell', Constants.Configuration.Files.Dell 75 | ); 76 | }); 77 | }); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /lib/common/subscription.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = SubscriptionFactory; 6 | 7 | SubscriptionFactory.$provide = 'Subscription'; 8 | SubscriptionFactory.$inject = [ 9 | 'Promise', 10 | 'Logger', 11 | 'Assert' 12 | ]; 13 | 14 | function SubscriptionFactory (Promise, Logger, assert) { 15 | var logger = Logger.initialize(SubscriptionFactory); 16 | 17 | /** 18 | * Creates a new subscription to a queue 19 | * @param queue {string} 20 | * @constructor 21 | */ 22 | function Subscription (queue, options) { 23 | assert.object(queue, 'queue'); 24 | assert.object(options, 'options'); 25 | 26 | this.MAX_DISPOSE_RETRIES = 3; 27 | this.retryDelay = 1000; 28 | this.queue = queue; 29 | this.options = options; 30 | this._disposed = false; 31 | } 32 | 33 | /** 34 | * Removes the subscription 35 | * 36 | * @returns {Promise} 37 | */ 38 | Subscription.prototype.dispose = function (attempt, retry) { 39 | var self = this; 40 | if (self._disposed && !retry) { 41 | logger.warning('Subscription dispose was called more than once.', { 42 | stack: new Error().stack, 43 | consumerTag: self.options.consumerTag 44 | }); 45 | return Promise.resolve(true); 46 | } else { 47 | self._disposed = true; 48 | } 49 | 50 | if (attempt === undefined) { 51 | attempt = 0; 52 | } else if (attempt >= self.MAX_DISPOSE_RETRIES) { 53 | logger.error('Subscription failed to dispose with maximum retries.', { 54 | consumerTag: self.options.consumerTag 55 | }); 56 | return Promise.reject(new Error('Subscription ' + self.options.consumerTag + 57 | ' failed to dispose with maximum retries')); 58 | } 59 | 60 | attempt += 1; 61 | 62 | if (this.queue.state === 'open') { 63 | return Promise.resolve().then(function () { 64 | return self.queue.unsubscribe(self.options.consumerTag); 65 | }).then(function () { 66 | return self.queue.destroy(); 67 | }).then(function () { 68 | return self.queue.close(); 69 | }).then(function () { 70 | return true; 71 | }).catch(function(err) { 72 | logger.error('Subscription failed to dispose, retrying attempt ' + attempt, { 73 | error: err 74 | }); 75 | Promise.delay(self.retryDelay).then(function() { 76 | self.dispose(attempt, true); 77 | }); 78 | throw err; 79 | }); 80 | } else { 81 | return Promise.reject( 82 | new Error('Attempted to dispose a subscription whose queue state is not open')); 83 | } 84 | }; 85 | 86 | Subscription.create = function (q, options) { 87 | return new Subscription(q, options); 88 | }; 89 | 90 | return Subscription; 91 | } 92 | 93 | -------------------------------------------------------------------------------- /lib/services/configuration.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = configurationServiceFactory; 6 | 7 | configurationServiceFactory.$provide = 'Services.Configuration'; 8 | configurationServiceFactory.$inject = [ 9 | 'Constants', 10 | 'Assert', 11 | 'nconf', 12 | 'Promise', 13 | 'fs', 14 | 'path', 15 | 'Logger' 16 | ]; 17 | 18 | function configurationServiceFactory( 19 | Constants, 20 | assert, 21 | nconf, 22 | Promise, 23 | fs, 24 | path, 25 | Logger 26 | ) { 27 | var logger = Logger.initialize(configurationServiceFactory); 28 | 29 | function ConfigurationService() { 30 | this.startupPriority = 0; 31 | this.load(); 32 | } 33 | 34 | ConfigurationService.prototype.load = function() { 35 | nconf.use('memory'); 36 | 37 | // TODO: Move the remaining code to start once deprecation warnings are fixed. 38 | 39 | nconf.argv().env(); 40 | 41 | // global config load 42 | try { 43 | if (fs.existsSync(Constants.Configuration.Files.Global)) { 44 | nconf.file('global', Constants.Configuration.Files.Global); 45 | } 46 | } 47 | catch(e) { 48 | console.error('Failed to load configuration file:', 49 | Constants.Configuration.Files.Global); 50 | console.error(e.message); 51 | } 52 | 53 | // dell config load 54 | try { 55 | if (fs.existsSync(Constants.Configuration.Files.Dell)) { 56 | nconf.file('dell', Constants.Configuration.Files.Dell); 57 | } 58 | } 59 | catch(e) { 60 | console.error('Failed to load configuration file:', 61 | Constants.Configuration.Files.Dell); 62 | console.error(e.message); 63 | } 64 | 65 | var baseDirectory = path.resolve(__dirname + '/../..'); 66 | 67 | this.set('baseDirectory', baseDirectory); 68 | 69 | logger.info('Setting base directory for loading configuration to %s.'.format( 70 | baseDirectory)); 71 | }; 72 | 73 | ConfigurationService.prototype.set = function set(key, value) { 74 | assert.string(key, 'key'); 75 | 76 | nconf.set(key, value); 77 | 78 | return this; 79 | }; 80 | 81 | ConfigurationService.prototype.get = function get(key, defaults) { 82 | assert.string(key, 'key'); 83 | 84 | var value = nconf.get(key); 85 | 86 | if (value === undefined) { 87 | logger.info('Configuration value is undefined, using default (%s => %s).'.format( 88 | key, 89 | defaults 90 | )); 91 | 92 | return defaults; 93 | } 94 | 95 | return value; 96 | }; 97 | 98 | ConfigurationService.prototype.getAll = function () { 99 | return nconf.get(); 100 | }; 101 | 102 | ConfigurationService.prototype.start = function start() { 103 | this.started = true; 104 | 105 | return Promise.resolve(); 106 | }; 107 | 108 | ConfigurationService.prototype.stop = function stop() { 109 | return Promise.resolve(); 110 | }; 111 | 112 | return new ConfigurationService(); 113 | } 114 | -------------------------------------------------------------------------------- /spec/mocks/logger.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = mockLoggerFactory; 6 | 7 | mockLoggerFactory.$provide = 'Logger'; 8 | mockLoggerFactory.$inject = [ 9 | 'Constants', 10 | 'Assert', 11 | 'LogEvent', 12 | '_', 13 | 'console' 14 | ]; 15 | 16 | function mockLoggerFactory(Constants, assert, LogEvent, _, console) { 17 | 18 | /** 19 | * Logger is a logger class which provides methods for logging based 20 | * on log levels with mesages & metadata provisions. This logger is a stub for the real 21 | * logger used that doesn't persist or publish logs over AMQP. 22 | * 23 | * Usage: 24 | * 25 | * var logger=Logger.initialize(yourFunctionOrInjectable) 26 | * logger.info('Your message here...', { hello: 'world', arbitrary: 'meta data object'}); 27 | * 28 | * @constructor 29 | */ 30 | function Logger (module) { 31 | // Set the intiial module to the provided string value if present. 32 | this.module = module !== undefined ? module.toString() : 'No Module'; 33 | 34 | // If the module is a function then we'll look for di.js annotations to get the 35 | // provide string. 36 | if (_.isFunction(module)) { 37 | if (module.annotations && module.annotations.length) { 38 | // Detect DI provides 39 | var provides = _.detect(module.annotations, function (annotation) { 40 | return _.has(annotation, 'token'); 41 | }); 42 | 43 | // If provides is present use that. 44 | if (provides) { 45 | this.module = provides.token; 46 | return; 47 | } 48 | } 49 | 50 | // If no provides then use the function. 51 | if (module.name) { 52 | this.module = module.name; 53 | } 54 | } 55 | } 56 | 57 | var singletonLogger = new Logger(mockLoggerFactory); 58 | 59 | /** 60 | * _log 61 | * @param {string} level Log Level 62 | * @param {string} message Log Message 63 | * @param {object} [context] Log Metadata 64 | * @private 65 | */ 66 | Logger.prototype.log = function (level, message, context) { 67 | assert.string(level, 'Must specifiy a level.'); 68 | assert.ok(_.has(Constants.Logging.Levels, level), 'Invalid level specified.'); 69 | 70 | assert.string(message, 'Must specify a message.'); 71 | 72 | if (context) { 73 | assert.object(context, 'Context must be an object if specified.'); 74 | } 75 | console.log("MOCKLOG: ["+level+"] "+message); 76 | }; 77 | 78 | // Iterate the available levels and create the appropriate prototype function. 79 | _.keys(Constants.Logging.Levels).forEach(function(level) { 80 | /** 81 | * level - Helper method to allow logging by using the specific level 82 | * as the method instead of calling log directly. 83 | * @param {string} message Log Message 84 | * @param {object} [context] Log Metadata 85 | */ 86 | Logger.prototype[level] = function (message, context) { 87 | this.log(level, message, context); 88 | }; 89 | }); 90 | 91 | Logger.initialize = function () { 92 | return singletonLogger; 93 | }; 94 | 95 | return Logger; 96 | } 97 | -------------------------------------------------------------------------------- /spec/lib/services/argument-handler-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | describe('Services.ArgumentHandler', function () { 6 | var config, LogEvent; 7 | 8 | helper.before(); 9 | 10 | before(function () { 11 | config = helper.injector.get('Services.Configuration'); 12 | LogEvent = helper.injector.get('LogEvent'); 13 | this.subject = helper.injector.get('Services.ArgumentHandler'); 14 | 15 | }); 16 | 17 | helper.after(); 18 | 19 | describe('_getValue', function () { 20 | var stubConfig; 21 | 22 | before(function() { 23 | stubConfig = sinon.stub(config, 'get', function(key, val) { 24 | var obj = { 25 | a: 1, 26 | b: 'test', 27 | c: true 28 | }; 29 | if (obj.hasOwnProperty(key)) { 30 | return obj[key]; 31 | } else { 32 | return val; 33 | } 34 | }); 35 | }); 36 | 37 | after(function() { 38 | stubConfig.restore(); 39 | }); 40 | 41 | it('should return the correct lookup value', function() { 42 | expect(this.subject._getValue(['a', 'b', 'c'], 'foo')).to.equal(1); 43 | expect(this.subject._getValue(['x', 'b', 'c'], 'foo')).to.equal('test'); 44 | expect(this.subject._getValue(['x', 'y', 'c'], 'foo')).to.equal(true); 45 | }); 46 | 47 | it('should return default value if all keys don\'t exist', function() { 48 | expect(this.subject._getValue(['x', 'y', 'z'], 'foo')).to.equal('foo'); 49 | }); 50 | 51 | it('should return default value if keys are empty', function() { 52 | expect(this.subject._getValue([], 'foo')).to.equal('foo'); 53 | }); 54 | 55 | it('should return undefined if no default value', function() { 56 | expect(this.subject._getValue(['x', 'y', 'z'])).to.equal(undefined); 57 | }); 58 | 59 | it('should return default value if keys are not collection', function() { 60 | var self = this; 61 | [null, 0, true, {}, undefined].forEach(function(key) { 62 | expect(self.subject._getValue(key, 'foo')).to.equal('foo'); 63 | }); 64 | }); 65 | }); 66 | 67 | describe('start', function() { 68 | var stubLogEvent; 69 | var stubGetValue; 70 | 71 | beforeEach(function() { 72 | stubLogEvent = sinon.stub(LogEvent, 'setColorEnable'); 73 | stubGetValue = sinon.stub(this.subject, '_getValue'); 74 | }); 75 | 76 | afterEach(function() { 77 | stubLogEvent.restore(); 78 | stubGetValue.restore(); 79 | }); 80 | 81 | it('should do enable color', function() { 82 | stubGetValue.withArgs(['color', 'logColorEnable'], false).returns(true); 83 | this.subject.start(); 84 | expect(stubLogEvent).to.have.callCount(1); 85 | expect(stubLogEvent).to.have.been.calledWith(true); 86 | }); 87 | 88 | it('should do disable color', function() { 89 | stubGetValue.withArgs(['color', 'logColorEnable'], false).returns(false); 90 | this.subject.start(); 91 | expect(stubLogEvent).to.have.callCount(1); 92 | expect(stubLogEvent).to.have.been.calledWith(false); 93 | }); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /lib/common/arp.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = arpCacheFactory; 6 | arpCacheFactory.$provide = 'ARPCache'; 7 | arpCacheFactory.$inject = [ 8 | 'Logger', 9 | 'Promise', 10 | 'Assert', 11 | 'Util', 12 | '_', 13 | 'fs' 14 | ]; 15 | 16 | function arpCacheFactory( 17 | Logger, 18 | Promise, 19 | assert, 20 | util, 21 | _, 22 | nodeFs 23 | ) { 24 | var logger = Logger.initialize(arpCacheFactory); 25 | var fs = Promise.promisifyAll(nodeFs); 26 | 27 | /** 28 | * 29 | * @param {Object} options 30 | * @param {Object} context 31 | * @param {String} taskId 32 | * @constructor 33 | */ 34 | function ARPCache() { 35 | this.last = []; 36 | this.current = []; 37 | } 38 | 39 | ARPCache.prototype.parseArpCache = function() { 40 | return fs.readFileAsync('/proc/net/arp') 41 | .then(function(data) { 42 | var cols, lines, entries = []; 43 | lines = data.toString().split('\n'); 44 | _.forEach(lines, function(line, index) { 45 | if(index !== 0) { 46 | cols = line.replace(/ [ ]*/g, ' ').split(' '); 47 | if((cols.length > 3) && 48 | (cols[0].length !== 0) && 49 | (cols[3].length !== 0)) { 50 | entries.push({ 51 | ip: cols[0], 52 | mac: cols[3], 53 | iface: cols[5], 54 | flag: cols[2] 55 | }); 56 | } 57 | } 58 | }); 59 | return entries; 60 | }) 61 | .catch(function(err) { 62 | logger.error('ARP Read Error', {error:err}); 63 | throw err; 64 | }); 65 | }; 66 | 67 | ARPCache.prototype.getCurrent = function() { 68 | var self = this; 69 | return self.parseArpCache() 70 | .then(function(data) { 71 | self.current = data; 72 | var updated = _.merge(_(self.last) 73 | .filter(function(e) { 74 | return _.isUndefined(_.find(self.current, e)); 75 | }) 76 | .value(), _(self.current) 77 | .filter(function(e) { 78 | return _.isUndefined(_.find(self.last, e)); 79 | }) 80 | .value()); 81 | 82 | if(updated.length) { 83 | return Promise.map(updated, function(entry) { 84 | if(entry.flag !== '0x0') { 85 | return { 86 | ip: entry.ip, 87 | mac: entry.mac 88 | }; 89 | } 90 | }); 91 | } 92 | }) 93 | .then(function(entries) { 94 | return _(entries).filter(function(entry) { 95 | return entry; 96 | }).value(); 97 | }) 98 | .catch(function(error) { 99 | logger.error('Error Handling ARP Entry', {error:error}); 100 | throw error; 101 | }) 102 | .finally(function() { 103 | self.last = self.current; 104 | }); 105 | }; 106 | 107 | return new ARPCache(); 108 | } 109 | -------------------------------------------------------------------------------- /spec/lib/common/arp-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe('ARPCache', function () { 7 | var arpCache; 8 | var fs; 9 | var procData = "IP address HW type Flags HW address Mask Device\n" + 10 | "1.2.3.4 0x1 0x2 52:54:be:ef:ff:12 * eth1\n" + 11 | "2.3.4.5 0x1 0x2 00:00:be:ef:ff:00 * eth0\n" + 12 | "2.3.4.6 0x1 0x2 00:00:be:ef:ff:01 * eth0\n"; 13 | 14 | var parsedData = [ 15 | { ip:'1.2.3.4', mac:'52:54:be:ef:ff:12', iface:'eth1', flag:'0x2' }, 16 | { ip:'2.3.4.5', mac:'00:00:be:ef:ff:00', iface:'eth0', flag:'0x2' }, 17 | { ip:'2.3.4.6', mac:'00:00:be:ef:ff:01', iface:'eth0', flag:'0x2' } 18 | ]; 19 | 20 | helper.before(); 21 | 22 | before(function () { 23 | arpCache = helper.injector.get('ARPCache'); 24 | fs = helper.injector.get('fs'); 25 | }); 26 | 27 | beforeEach(function() { 28 | this.sandbox.stub(fs, 'readFileAsync'); 29 | }); 30 | 31 | helper.after(); 32 | 33 | describe("Handle ARP Entry", function(){ 34 | it('should parse ARP data', function() { 35 | fs.readFileAsync.resolves(procData); 36 | return arpCache.parseArpCache() 37 | .then(function(parsed) { 38 | expect(parsed).to.deep.equal(parsedData); 39 | }); 40 | }); 41 | 42 | it('should parse ARP data with error', function() { 43 | fs.readFileAsync.rejects('error'); 44 | return expect(arpCache.parseArpCache()).to.be.rejectedWith('error'); 45 | }); 46 | 47 | it('should handle initial ARP data', function() { 48 | fs.readFileAsync.resolves(procData); 49 | arpCache.last = {ip:'1.2.3.4', mac:'52:54:be:ef:ff:12', iface:'eth1', flag:'0x2'}; 50 | return arpCache.getCurrent() 51 | .then(function() { 52 | expect(arpCache.last).to.deep.equal(parsedData); 53 | expect(arpCache.current).to.deep.equal(parsedData); 54 | }); 55 | }); 56 | 57 | it('should handle updated ARP data', function() { 58 | fs.readFileAsync.resolves( 59 | "IP address HW type Flags HW address Mask Device\n" + 60 | "1.2.3.4 0x1 0x1 52:54:be:ef:ff:12 * eth1\n" + 61 | "2.3.4.5 0x1 0x0 00:00:be:ef:ff:00 * eth0\n" + 62 | "2.3.4.9 0x1 0x2 00:00:be:ef:ff:01 * eth0\n" 63 | ); 64 | parsedData[0].flag = '0x1'; 65 | parsedData[1].flag = '0x0'; 66 | parsedData[2].ip = '2.3.4.9'; 67 | return arpCache.getCurrent() 68 | .then(function(data) { 69 | expect(arpCache.last).to.deep.equal(parsedData); 70 | expect(arpCache.current).to.deep.equal(parsedData); 71 | expect(data).to.deep.equal([ 72 | {ip:'1.2.3.4', mac:'52:54:be:ef:ff:12'}, 73 | {ip:'2.3.4.9', mac:'00:00:be:ef:ff:01'} 74 | ]); 75 | }); 76 | }); 77 | 78 | it('should handle ARP data with error', function() { 79 | fs.readFileAsync.rejects('error'); 80 | return expect(arpCache.getCurrent()).to.be.rejectedWith('error'); 81 | }); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /lib/common/file-loader.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = fileLoaderFactory; 6 | 7 | fileLoaderFactory.$provide = 'FileLoader'; 8 | fileLoaderFactory.$inject = [ 9 | 'Assert', 10 | 'Promise', 11 | '_', 12 | 'fs', 13 | 'path' 14 | ]; 15 | 16 | function fileLoaderFactory(assert, Promise, _, nodeFs, path) { 17 | var fs = Promise.promisifyAll(nodeFs); 18 | 19 | /** 20 | * FileLoad provides convenience methods around loading files from disk 21 | * into memory for use by consumers. 22 | */ 23 | function FileLoader() { 24 | } 25 | 26 | /** 27 | * The put function writes the contents to the given file. 28 | * @param {string} file File name. 29 | * @param {string|buffer} contents File Contents. 30 | * @return {Promise} Resolves on completion. 31 | */ 32 | FileLoader.prototype.put = function (file, contents) { 33 | assert.string(file); 34 | assert.ok(contents); 35 | 36 | return fs.writeFileAsync(file, contents); 37 | }; 38 | 39 | /** 40 | * The get function reads the contents of the given file and returns them 41 | * via a promise. 42 | * @param {string} file File to read. 43 | * @return {Promise} A promise fulfilled with the contents of the file. 44 | */ 45 | FileLoader.prototype.get = function (file) { 46 | assert.string(file); 47 | 48 | return fs.readFileAsync(file, 'utf-8'); 49 | }; 50 | 51 | /** 52 | * The getAll function reads the contents of all files in the given directory 53 | * and returns an object keyed by the file basename with a value of the file 54 | * contents. 55 | * @param {string} directory The directory to source files from. 56 | * @return {Promise} An object with key/value pairs representing the files located 57 | * in the given directory. 58 | */ 59 | FileLoader.prototype.getAll = function (directory, recursive) { 60 | var self = this; 61 | assert.string(directory); 62 | 63 | // List all files in the directory. 64 | var accumulator = {}; 65 | recursive = recursive || false; 66 | var readDir = function(dirName) { 67 | return fs.readdirAsync(dirName).map(function (fileName) { 68 | var pathName = dirName + '/' + fileName; 69 | return fs.statAsync(pathName).then(function(stat) { 70 | return stat.isDirectory() ? (recursive ? readDir(pathName) : null) : pathName; 71 | }); 72 | }) 73 | .reduce(function (a, b) { 74 | return a.concat(b); 75 | }, []); 76 | }; 77 | 78 | return readDir(directory).then(function(v) { 79 | return _(v).compact().value(); 80 | }) 81 | .map(function(filename) { 82 | return self.get(filename) 83 | .then(function(contents) { 84 | accumulator[path.basename(filename)] = { 85 | contents: contents, 86 | path: filename 87 | }; 88 | }); 89 | }) 90 | .then(function() { 91 | return accumulator; 92 | }); 93 | }; 94 | 95 | FileLoader.prototype.unlink = function(file) { 96 | assert.string(file); 97 | 98 | return fs.unlinkAsync(file); 99 | }; 100 | 101 | return FileLoader; 102 | } 103 | -------------------------------------------------------------------------------- /spec/lib/models/graph-object-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | var base = require('./base-spec'); 7 | var sandbox = sinon.sandbox.create(); 8 | 9 | describe('Models.GraphObject', function () { 10 | helper.before(function (context) { 11 | context.MessengerServices = function() { 12 | this.start= sandbox.stub().resolves(); 13 | this.stop = sandbox.stub().resolves(); 14 | this.publish = sandbox.stub().resolves(); 15 | }; 16 | return [ 17 | helper.di.simpleWrapper(context.MessengerServices, 'Messenger') 18 | ]; 19 | }); 20 | 21 | base.before(function (context) { 22 | context.model = helper.injector.get('Services.Waterline').graphobjects; 23 | context.attributes = context.model._attributes; 24 | }); 25 | 26 | helper.after(); 27 | 28 | describe('Base', function () { 29 | base.examples(); 30 | }); 31 | 32 | describe('Attributes', function () { 33 | describe('instanceId', function () { 34 | before(function () { 35 | this.subject = this.attributes.instanceId; 36 | }); 37 | 38 | it('should be a uuid', function () { 39 | expect(this.subject.type).to.equal('string'); 40 | expect(this.subject.uuidv4).to.be.true; 41 | }); 42 | 43 | it('should be required', function () { 44 | expect(this.subject.required).to.equal(true); 45 | }); 46 | }); 47 | 48 | describe('context', function () { 49 | before(function () { 50 | this.subject = this.attributes.context; 51 | }); 52 | 53 | it('should be required', function () { 54 | expect(this.subject.required).to.equal(true); 55 | }); 56 | 57 | it('should be json', function () { 58 | expect(this.subject.type).to.equal('json'); 59 | expect(this.subject.json).to.be.true; 60 | }); 61 | }); 62 | 63 | describe('definition', function () { 64 | before(function () { 65 | this.subject = this.attributes.definition; 66 | }); 67 | 68 | it('should be required', function () { 69 | expect(this.subject.required).to.equal(true); 70 | }); 71 | 72 | it('should be json', function () { 73 | expect(this.subject.type).to.equal('json'); 74 | expect(this.subject.json).to.be.true; 75 | }); 76 | }); 77 | 78 | describe('tasks', function () { 79 | before(function () { 80 | this.subject = this.attributes.tasks; 81 | }); 82 | 83 | it('should be required', function () { 84 | expect(this.subject.required).to.equal(true); 85 | }); 86 | 87 | it('should be json', function () { 88 | expect(this.subject.type).to.equal('json'); 89 | expect(this.subject.json).to.be.true; 90 | }); 91 | }); 92 | 93 | describe('node', function () { 94 | before(function () { 95 | this.subject = this.attributes.node; 96 | }); 97 | 98 | it('should be a relation to node via workflows', function () { 99 | expect(this.subject.model).to.equal('nodes'); 100 | }); 101 | }); 102 | 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /spec/lib/services/heartbeat-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 'use strict'; 4 | require('../../helper'); 5 | 6 | describe('Heartbeat', function () { 7 | var sandbox = sinon.sandbox.create(); 8 | var heartbeat; 9 | var constants; 10 | var subscription = { 11 | dispose: sandbox.stub().resolves() 12 | }; 13 | var rx = { 14 | Observable: { 15 | interval: sandbox.stub().returns({ 16 | takeWhile: sandbox.stub().returns({ 17 | subscribe: sandbox.spy(function(f1,f2) { 18 | f1(); 19 | f2({error:'error'}); 20 | return { 21 | dispose: sandbox.stub().resolves(subscription) 22 | }; 23 | }) 24 | }) 25 | }) 26 | } 27 | }; 28 | var events = { 29 | publishHeartbeatEvent: sandbox.stub().returns(Promise.resolve()), 30 | publishUnhandledError: sandbox.stub() 31 | }; 32 | var dns = { 33 | lookupServiceAsync: sandbox.stub(), 34 | lookupAsync: sandbox.stub() 35 | }; 36 | 37 | helper.before(function() { 38 | return [ 39 | helper.di.simpleWrapper(events, 'Protocol.Events'), 40 | helper.di.simpleWrapper(rx, 'Rx') 41 | ]; 42 | }); 43 | 44 | before(function () { 45 | heartbeat = helper.injector.get('Services.Heartbeat'); 46 | constants = helper.injector.get('Constants'); 47 | sandbox.stub(heartbeat, 'requireDns').returns(dns); 48 | }); 49 | 50 | beforeEach(function() { 51 | sandbox.reset(); 52 | }); 53 | 54 | helper.after(function() { 55 | sandbox.restore(); 56 | }); 57 | 58 | describe('heartbeatService', function() { 59 | it('should start', function() { 60 | return heartbeat.start() 61 | .then(function() { 62 | expect(rx.Observable.interval).to.be.calledOnce; 63 | expect(heartbeat.subscription).to.be.resolved; 64 | expect(heartbeat.running).to.be.true; 65 | }); 66 | }); 67 | 68 | it('should stop', function() { 69 | return heartbeat.start().then(function() { 70 | return heartbeat.stop().then(function() { 71 | expect(heartbeat.subscription.dispose).to.have.been.calledOnce; 72 | expect(heartbeat.running).to.be.false; 73 | }); 74 | }); 75 | }); 76 | 77 | it('should send heartbeat message', function() { 78 | return heartbeat.sendHeartbeat() 79 | .then(function() { 80 | expect(events.publishHeartbeatEvent).to.be.calledOnce; 81 | }); 82 | }); 83 | 84 | it('should resolve hostname', function() { 85 | dns.lookupServiceAsync.resolves(undefined); 86 | return heartbeat.getFqdn().then(function(hostname) { 87 | expect(hostname).to.equal(constants.Host); 88 | }); 89 | }); 90 | 91 | it('should resolve FQDN', function() { 92 | var testFqdn = constants.Host + '.example.com'; 93 | dns.lookupServiceAsync.resolves([testFqdn]); 94 | dns.lookupAsync.resolves(['1.2.3.4', 4]); 95 | return heartbeat.getFqdn().then(function(fqdn) { 96 | expect(fqdn).to.equal(testFqdn); 97 | }); 98 | }); 99 | }); 100 | }); 101 | 102 | -------------------------------------------------------------------------------- /spec/lib/models/tags-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | var base = require('./base-spec'); 7 | var sandbox = sinon.sandbox.create(); 8 | 9 | describe('Models.Tag', function () { 10 | helper.before(function (context) { 11 | context.MessengerServices = function() { 12 | this.start= sandbox.stub().resolves(); 13 | this.stop = sandbox.stub().resolves(); 14 | this.publish = sandbox.stub().resolves(); 15 | }; 16 | return [ 17 | helper.di.simpleWrapper(context.MessengerServices, 'Messenger') 18 | ]; 19 | }); 20 | 21 | base.before(function (context) { 22 | context.model = helper.injector.get('Services.Waterline').tags; 23 | context.attributes = context.model._attributes; 24 | }); 25 | 26 | helper.after(); 27 | 28 | describe('Base', function () { 29 | base.examples(); 30 | }); 31 | 32 | describe('Attributes', function () { 33 | describe('name', function () { 34 | before(function () { 35 | this.subject = this.attributes.name; 36 | }); 37 | 38 | it('should be a string', function () { 39 | expect(this.subject.type).to.equal('string'); 40 | }); 41 | 42 | it('should be required', function () { 43 | expect(this.subject.required).to.equal(true); 44 | }); 45 | }); 46 | 47 | describe('rules', function () { 48 | before(function () { 49 | this.subject = this.attributes.rules; 50 | }); 51 | 52 | it('should be required', function () { 53 | expect(this.subject.required).to.equal(true); 54 | }); 55 | 56 | it('should be json', function () { 57 | expect(this.subject.type).to.equal('json'); 58 | }); 59 | }); 60 | 61 | }); 62 | 63 | describe('Tag Rules', function () { 64 | beforeEach(function () { 65 | return helper.reset(); 66 | }); 67 | 68 | it('should validate tag with rules', function () { 69 | return this.model.create({ 70 | name: 'test1', 71 | rules: [ 72 | { 73 | path: 'dmi.memory.total', 74 | equals: '32946864kB' 75 | } 76 | ] 77 | }).should.be.fulfilled; 78 | }); 79 | 80 | it('should not validate tag rules with invalid values', function () { 81 | return this.model.create({ 82 | name: 'test2', 83 | rules: [1, 2, 3] 84 | }).should.be.rejectedWith(Error); 85 | }); 86 | 87 | it('should not validate tag rules with a missing path', function () { 88 | return this.model.create({ 89 | name: 'test3', 90 | rules: [ 91 | { 92 | path: null, 93 | } 94 | ] 95 | }).should.be.rejectedWith(Error); 96 | }); 97 | 98 | it('should not validate tag rules with an invalid validation rule', function () { 99 | return this.model.create({ 100 | name: 'test4', 101 | rules: [ 102 | { 103 | path: 'dmi.memory.free', 104 | badMatcher: 'asdf' 105 | } 106 | ] 107 | }).should.be.rejectedWith(Error); 108 | }); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /spec/lib/models/graph-definition-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | var base = require('./base-spec'); 7 | var sandbox = sinon.sandbox.create(); 8 | 9 | describe('Models.GraphDefinition', function () { 10 | helper.before(function (context) { 11 | context.MessengerServices = function() { 12 | this.start= sandbox.stub().resolves(); 13 | this.stop = sandbox.stub().resolves(); 14 | this.publish = sandbox.stub().resolves(); 15 | }; 16 | return [ 17 | helper.di.simpleWrapper(context.MessengerServices, 'Messenger') 18 | ]; 19 | }); 20 | 21 | base.before(function (context) { 22 | context.model = helper.injector.get('Services.Waterline').graphdefinitions; 23 | context.attributes = context.model._attributes; 24 | }); 25 | 26 | helper.after(); 27 | 28 | describe('Base', function () { 29 | base.examples(); 30 | }); 31 | 32 | describe('Attributes', function () { 33 | describe('friendlyName', function () { 34 | before(function () { 35 | this.subject = this.attributes.friendlyName; 36 | }); 37 | 38 | it('should be a string', function () { 39 | expect(this.subject.type).to.equal('string'); 40 | }); 41 | 42 | it('should be required', function () { 43 | expect(this.subject.required).to.equal(true); 44 | }); 45 | }); 46 | 47 | describe('injectableName', function () { 48 | before(function () { 49 | this.subject = this.attributes.injectableName; 50 | }); 51 | 52 | it('should be a string', function () { 53 | expect(this.subject.type).to.equal('string'); 54 | }); 55 | 56 | it('should be required', function () { 57 | expect(this.subject.required).to.equal(true); 58 | }); 59 | }); 60 | 61 | describe('tasks', function () { 62 | before(function () { 63 | this.subject = this.attributes.tasks; 64 | }); 65 | 66 | it('should be an array', function () { 67 | expect(this.subject.type).to.equal('array'); 68 | }); 69 | 70 | it('should be required', function () { 71 | expect(this.subject.required).to.equal(true); 72 | }); 73 | 74 | it('should be json', function () { 75 | expect(this.subject.json).to.equal(true); 76 | }); 77 | }); 78 | }); 79 | 80 | describe('object returned from toJSON()', function () { 81 | var graph; 82 | 83 | before('reset DB collections', function () { 84 | return helper.reset(); 85 | }); 86 | 87 | before('create record', function () { 88 | return this.model.create({ 89 | friendlyName: 'Dummy', 90 | injectableName: 'Graph.Dummy', 91 | tasks: [{ /* placeholder */ }] 92 | }).then(function (graph_) { 93 | graph = graph_.toJSON(); 94 | }); 95 | }); 96 | 97 | it('should not have createdAt', function () { 98 | expect(graph).to.not.have.property('createdAt'); 99 | }); 100 | 101 | it('should not have updatedAt', function () { 102 | expect(graph).to.not.have.property('updatedAt'); 103 | }); 104 | 105 | it('should not have id', function () { 106 | expect(graph).to.not.have.property('id'); 107 | }); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /lib/common/encryption.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | module.exports = encryptionFactory; 6 | 7 | encryptionFactory.$provide = 'Encryption'; 8 | encryptionFactory.$inject = [ 9 | 'crypto', 10 | 'Constants', 11 | 'crypt' 12 | ]; 13 | 14 | function encryptionFactory( 15 | crypto, 16 | Constants, 17 | crypt 18 | ) { 19 | function Encryption () { 20 | } 21 | 22 | Encryption.prototype.key = function () { 23 | return crypto.randomBytes(32).toString('base64'); 24 | }; 25 | 26 | Encryption.prototype.iv = function () { 27 | return crypto.randomBytes(16).toString('base64'); 28 | }; 29 | 30 | Encryption.prototype.encrypt = function (data, key, iv) { 31 | iv = iv || this.iv(); 32 | 33 | var cipher = crypto.createCipheriv( 34 | 'aes-256-cbc', 35 | new Buffer(key, 'base64'), 36 | new Buffer(iv, 'base64') 37 | ); 38 | 39 | var encrypted = Buffer.concat([ 40 | cipher.update(data), 41 | cipher.final() 42 | ]); 43 | 44 | return '%s.%s'.format( 45 | iv.toString('base64'), 46 | encrypted.toString('base64') 47 | ); 48 | }; 49 | 50 | Encryption.prototype.decrypt = function (data, key) { 51 | var parts = data.split('.'); 52 | 53 | var cipher = crypto.createDecipheriv( 54 | 'aes-256-cbc', 55 | new Buffer(key, 'base64'), 56 | new Buffer(parts[0], 'base64') 57 | ); 58 | 59 | var decrypted = Buffer.concat([ 60 | cipher.update(new Buffer(parts[1], 'base64')), 61 | cipher.final() 62 | ]); 63 | 64 | return decrypted.toString(); 65 | }; 66 | 67 | Encryption.prototype.isEncrypted = function (data) { 68 | return Constants.Regex.Encrypted.test(data); 69 | }; 70 | 71 | /** 72 | * Calculate the hash for input data 73 | * @param {String} data - The input data 74 | * @param {String} [algorithm='sha512'] - The hash algorithm, it should be one of 75 | * ['sha512', 'sha256','md5'], the default is 'sha512' 76 | * @param {String} [salt] - The salt feed to hash algorithm, if not specified, the function will 77 | * generate a random salt. 78 | * @return {String} the hashed data 79 | */ 80 | Encryption.prototype.createHash = function (data, algorithm, salt) { 81 | return crypt(data, this.createSalt(algorithm, salt)); 82 | }; 83 | 84 | /** 85 | * Create a salt to be used with crypt(). 86 | * @param {String} [algorithm='sha512'] - The hash algorithm, it should be one of 87 | * ['sha512', 'sha256','md5'], the default is 'sha512' 88 | * @param {String} [salt] - The salt feed to hash algorithm, if not specified, the function will 89 | * generate a random salt. 90 | * @return {String} the salt 91 | */ 92 | Encryption.prototype.createSalt = function (algorithm, salt) { 93 | algorithm = algorithm || 'sha512'; 94 | var signatures = { 95 | sha256: '$5$', 96 | sha512: '$6$' 97 | }; 98 | if(!signatures[algorithm]) { 99 | throw new TypeError('Unknown salt algorithm: ' + algorithm); 100 | } 101 | 102 | if (salt && salt.charAt(0) === '$') { 103 | salt = signatures[algorithm] + salt.split('$')[2]; 104 | } else if (salt) { 105 | salt = signatures[algorithm] + salt; 106 | } else{ 107 | salt = crypt.createSalt(algorithm); 108 | } 109 | 110 | return salt; 111 | }; 112 | 113 | return Encryption; 114 | } 115 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 'use strict'; 4 | 5 | var _ = require('lodash'), 6 | dependencyInjection = require('di'); 7 | 8 | require('./lib/extensions'); 9 | 10 | module.exports = function (di, directory) { 11 | di = di || dependencyInjection; 12 | directory = directory || __dirname; 13 | 14 | var helper = require('./lib/di')(di, directory); 15 | 16 | var injectables = _.flattenDeep( 17 | [ 18 | // NPM Packages 19 | helper.simpleWrapper(_, '_'), 20 | helper.requireWrapper('bluebird', 'Promise'), 21 | helper.requireWrapper('rx', 'Rx'), 22 | helper.requireWrapper('nconf'), 23 | helper.requireWrapper('waterline', 'Waterline'), 24 | helper.requireWrapper('waterline-criteria', 'WaterlineCriteria'), 25 | helper.requireWrapper('sails-mongo', 'MongoAdapter'), 26 | helper.requireWrapper('sails-postgresql', 'PostgreSQLAdapter'), 27 | helper.requireWrapper('amqp', 'amqp'), 28 | helper.requireWrapper('domain', 'domain'), 29 | helper.requireWrapper('node-uuid', 'uuid'), 30 | helper.requireWrapper('stack-trace', 'stack-trace'), 31 | helper.requireWrapper('colors/safe', 'colors'), 32 | helper.requireWrapper('prettyjson', 'prettyjson'), 33 | helper.requireWrapper('lru-cache', 'lru-cache'), 34 | helper.requireWrapper('node-statsd', 'node-statsd'), 35 | helper.requireWrapper('validate.js', 'validate'), 36 | helper.requireWrapper('validator', 'validator'), 37 | helper.requireWrapper('assert-plus', 'assert-plus'), 38 | helper.requireWrapper('ejs', 'ejs'), 39 | helper.requireWrapper('hogan.js', 'Hogan'), 40 | helper.requireWrapper('fs', 'fs'), 41 | helper.requireWrapper('path', 'path'), 42 | helper.requireWrapper('child_process', 'child_process'), 43 | helper.requireWrapper('anchor', 'anchor'), 44 | helper.requireWrapper('jsonschema', 'jsonschema'), 45 | helper.simpleWrapper(console, 'console'), 46 | helper.simpleWrapper(require('eventemitter2').EventEmitter2, 'EventEmitter'), 47 | helper.requireWrapper('nanoid', 'nanoid'), 48 | helper.requireWrapper('crypto', 'crypto'), 49 | helper.requireWrapper('crypt3/sync', 'crypt'), 50 | helper.requireWrapper('util', 'util'), 51 | helper.requireWrapper('pluralize', 'pluralize'), 52 | helper.requireWrapper('always-tail', 'Tail'), 53 | helper.requireWrapper('flat', 'flat'), 54 | helper.requireWrapper('ajv', 'Ajv'), 55 | helper.requireWrapper('url', 'url'), 56 | 57 | // Glob Requirables 58 | helper.requireGlob(__dirname + '/lib/common/*.js'), 59 | helper.requireGlob(__dirname + '/lib/models/*.js'), 60 | helper.requireGlob(__dirname + '/lib/protocol/*.js'), 61 | helper.requireGlob(__dirname + '/lib/serializables/*.js'), 62 | helper.requireGlob(__dirname + '/lib/services/*.js') 63 | ] 64 | ); 65 | 66 | var injector = new di.Injector(injectables); 67 | 68 | // Run the common arguments handler 69 | var argHandler = injector.get('Services.ArgumentHandler'); 70 | argHandler.start(); 71 | 72 | return { 73 | di: di, 74 | helper: helper, 75 | injectables: injectables, 76 | workflowInjectables: _.flatten([ 77 | helper.requireGlob(__dirname + '/lib/workflow/stores/*.js'), 78 | helper.requireGlob(__dirname + '/lib/workflow/messengers/*.js'), 79 | helper.requireGlob(__dirname + '/lib/workflow/*.js'), 80 | ]) 81 | }; 82 | }; 83 | -------------------------------------------------------------------------------- /spec/lib/common/file-loader-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | describe('FileLoader', function () { 7 | var FileLoader; 8 | var fs; 9 | 10 | helper.before(); 11 | 12 | before(function () { 13 | FileLoader = helper.injector.get('FileLoader'); 14 | fs = helper.injector.get('fs'); 15 | this.subject = new FileLoader(); 16 | }); 17 | 18 | beforeEach(function() { 19 | this.sandbox.stub(fs, 'writeFileAsync'); 20 | this.sandbox.stub(fs, 'readFileAsync'); 21 | this.sandbox.stub(fs, 'readdirAsync'); 22 | this.sandbox.stub(fs, 'statAsync'); 23 | }); 24 | 25 | helper.after(); 26 | 27 | describe('put', function () { 28 | it('should write the contents to the specified file', function () { 29 | fs.writeFileAsync.resolves('put'); 30 | 31 | return this.subject.put('filename', 'contents').then(function (contents) { 32 | fs.writeFileAsync.should.have.been.calledWith('filename', 'contents'); 33 | 34 | contents.should.equal('put'); 35 | }); 36 | }); 37 | }); 38 | 39 | describe('get', function () { 40 | it('should get the contents for the specified file', function () { 41 | fs.readFileAsync.resolves('get'); 42 | 43 | return this.subject.get('filename').then(function (contents) { 44 | fs.readFileAsync.should.have.been.calledWith('filename'); 45 | 46 | contents.should.equal('get'); 47 | }); 48 | }); 49 | }); 50 | 51 | describe('getAll', function () { 52 | it( 53 | 'should return a promise fulfilled with the file basename to contents in an object', 54 | function () { 55 | fs.readFileAsync.resolves('getAll'); 56 | fs.readdirAsync.resolves(['foo.txt']); 57 | fs.statAsync.resolves({ isDirectory: function() { return false; } }); 58 | return this.subject.getAll('/tmp').then(function (files) { 59 | fs.readdirAsync.should.have.been.calledWith('/tmp'); 60 | 61 | files['foo.txt'].should.have.property('path') 62 | .and.to.equal('/tmp/foo.txt'); 63 | files['foo.txt'].should.have.property('contents') 64 | .and.to.equal('getAll'); 65 | }); 66 | } 67 | ); 68 | it( 69 | 'should skip directories when recursive is disabled', 70 | function () { 71 | fs.readFileAsync.resolves('getAll'); 72 | fs.readdirAsync.resolves(['foo']); 73 | fs.statAsync.resolves({ isDirectory: function() { return true; } }); 74 | return this.subject.getAll('/tmp').then(function () { 75 | fs.readdirAsync.should.have.been.calledWith('/tmp'); 76 | fs.readFileAsync.should.not.have.been.called; 77 | }); 78 | } 79 | ); 80 | it( 81 | 'should not skip directories when recursive is enabled', 82 | function () { 83 | fs.readFileAsync.resolves('getAll'); 84 | fs.readdirAsync.withArgs('/tmp').resolves(['foo']); 85 | fs.readdirAsync.withArgs('/tmp/foo').resolves([]); 86 | fs.statAsync.resolves({ isDirectory: function() { return true; } }); 87 | return this.subject.getAll('/tmp', true).then(function () { 88 | fs.readdirAsync.should.have.been.calledWith('/tmp'); 89 | fs.readdirAsync.should.have.been.calledWith('/tmp/foo'); 90 | fs.readFileAsync.should.not.have.been.called; 91 | }); 92 | } 93 | ); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /spec/lib/common/serializable-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | var di = require('di'); 7 | 8 | describe('Serializable', function () { 9 | var Serializable, 10 | Validatable, 11 | Target; 12 | 13 | helper.before(); 14 | 15 | before(function () { 16 | Serializable = helper.injector.get('Serializable'); 17 | Validatable = helper.injector.get('Validatable'); 18 | 19 | di.annotate(factory, new di.Provide('Test.FactoryObject')); 20 | 21 | function factory () { 22 | function FactoryObject (defaults) { 23 | Serializable.call( 24 | this, 25 | 26 | // Schema 27 | FactoryObject.schema, 28 | 29 | // Default Values 30 | defaults 31 | ); 32 | } 33 | 34 | FactoryObject.schema = { 35 | id: '/FactoryObject', 36 | type: 'object', 37 | properties: { 38 | name: { 39 | type: 'string' 40 | } 41 | } 42 | }; 43 | 44 | Serializable.register(factory, FactoryObject); 45 | 46 | return FactoryObject; 47 | } 48 | 49 | // SHort circuiting the injector to simplify things. 50 | Target = factory(); 51 | }); 52 | 53 | helper.after(); 54 | 55 | describe('register', function () { 56 | it('should set constructor.provides to the factory provides', function () { 57 | Target.provides.should.equal('Test.FactoryObject'); 58 | }); 59 | }); 60 | 61 | describe('validatable', function () { 62 | it('should be validatable', function () { 63 | var subject = new Target(); 64 | 65 | return subject.should.be.an.instanceof(Validatable); 66 | }); 67 | 68 | it('should resolve on success', function () { 69 | var subject = new Target({ name: 'target' }); 70 | 71 | return subject.validate().should.be.fulfilled; 72 | }); 73 | 74 | it('should reject on failure', function () { 75 | var subject = new Target({ name: 123 }); 76 | 77 | return subject.validate().should.be.rejected; 78 | }); 79 | }); 80 | 81 | describe('defaults', function () { 82 | before(function () { 83 | this.subject = new Target({ 84 | one: 1, 85 | two: 2, 86 | // Not actually allowed 87 | rules: 'overriden' 88 | }); 89 | }); 90 | 91 | it('should assign default values', function () { 92 | this.subject.one.should.equal(1); 93 | this.subject.two.should.equal(2); 94 | }); 95 | 96 | it('should not overwrite existing values', function () { 97 | this.subject.rules.should.not.equal('overridden'); 98 | }); 99 | }); 100 | 101 | describe('serialize', function () { 102 | before(function () { 103 | this.subject = new Target({ 104 | one: 1 105 | }); 106 | }); 107 | 108 | it('should use the base serialize method', function() { 109 | return this.subject.serialize().should.eventually.have.property('one').that.equals(1); 110 | }); 111 | }); 112 | 113 | describe('deserialize', function () { 114 | it('should use the base deserialize method', function() { 115 | this.subject = new Target(); 116 | 117 | return this.subject.deserialize({ 118 | one: 1 119 | }).should.eventually.have.property('one').that.equals(1); 120 | }); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /lib/workflow/messengers/messenger-AMQP.js: -------------------------------------------------------------------------------- 1 | // Copyright © 2016-2017 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | 3 | 'use strict'; 4 | module.exports = amqpMessengerFactory; 5 | amqpMessengerFactory.$provide = 'Task.Messengers.AMQP'; 6 | amqpMessengerFactory.$inject = [ 7 | 'Constants', 8 | 'Protocol.Task', 9 | 'Protocol.Events', 10 | 'Protocol.TaskGraphRunner', 11 | 'Services.Waterline', 12 | 'Logger', 13 | 'Assert', 14 | '_', 15 | 'Promise' 16 | ]; 17 | 18 | function amqpMessengerFactory( 19 | Constants, 20 | taskProtocol, 21 | eventsProtocol, 22 | taskGraphRunnerProtocol, 23 | waterline, 24 | Logger, 25 | assert, 26 | _, 27 | Promise 28 | ) { 29 | var logger = Logger.initialize(amqpMessengerFactory); 30 | 31 | function AMQPMessenger() { 32 | } 33 | 34 | AMQPMessenger.prototype.subscribeRunTask = function(domain, callback) { 35 | return taskProtocol.subscribeRun(domain, callback); 36 | }; 37 | 38 | AMQPMessenger.prototype.publishRunTask = function(domain, taskId, graphId) { 39 | return taskProtocol.run(domain, { taskId: taskId, graphId: graphId}); 40 | }; 41 | 42 | AMQPMessenger.prototype.subscribeCancelTask = function(callback) { 43 | return taskProtocol.subscribeCancel(callback); 44 | }; 45 | 46 | AMQPMessenger.prototype.publishCancelTask = function(taskId, errName, errMessage) { 47 | return taskProtocol.cancel(taskId, errName, errMessage); 48 | }; 49 | 50 | AMQPMessenger.prototype.subscribeTaskFinished = function(domain, callback) { 51 | return eventsProtocol.subscribeTaskFinished(domain, callback); 52 | }; 53 | 54 | /** 55 | * Publishes a task finished event over AMQP 56 | * 57 | * @param {String} domain 58 | * @param {Object} task 59 | * @param {Boolean} swallowError 60 | * @returns {Promise} 61 | * @memberOf AMQPMessenger 62 | */ 63 | AMQPMessenger.prototype.publishTaskFinished = function(domain, task, swallowError) { 64 | var errorMsg; 65 | if (task.error && task.error.stack) { 66 | errorMsg = task.error.stack; 67 | } else if (task.error) { 68 | errorMsg = task.error.toString(); 69 | } 70 | 71 | return eventsProtocol.publishTaskFinished( 72 | domain, 73 | task.instanceId, 74 | task.definition.injectableName, 75 | task.context.graphId, 76 | task.context.graphName, 77 | task.state, 78 | errorMsg, 79 | task.context, 80 | task.definition.terminalOnStates 81 | ) 82 | .catch(function(error) { 83 | if(swallowError) { 84 | logger.error('Error publishing task finished event', { 85 | taskId: task.instanceId, 86 | graphId: task.context.graphId, 87 | state: task.state, 88 | error: error 89 | }); 90 | } else { 91 | throw error; 92 | } 93 | }); 94 | }; 95 | 96 | AMQPMessenger.prototype.subscribeRunTaskGraph = function(domain, callback) { 97 | return taskGraphRunnerProtocol.subscribeRunTaskGraph(domain, callback); 98 | }; 99 | 100 | AMQPMessenger.prototype.subscribeCancelGraph = function(callback) { 101 | return taskGraphRunnerProtocol.subscribeCancelTaskGraph(callback); 102 | }; 103 | 104 | AMQPMessenger.prototype.publishCancelGraph = function(graphId) { 105 | return taskGraphRunnerProtocol.cancelTaskGraph(graphId); 106 | }; 107 | 108 | AMQPMessenger.prototype.start = function() { 109 | return Promise.resolve(); 110 | }; 111 | 112 | return new AMQPMessenger(); 113 | } 114 | -------------------------------------------------------------------------------- /lib/common/graph-progress.js: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Dell Inc. or its subsidiaries. All Rights Reserved. 2 | 3 | 'use strict'; 4 | 5 | module.exports = graphProgressFactory; 6 | graphProgressFactory.$provide = 'GraphProgress'; 7 | graphProgressFactory.$inject = [ 8 | 'Constants', 9 | '_', 10 | 'Assert' 11 | ]; 12 | 13 | function graphProgressFactory( 14 | Constants, 15 | _, 16 | assert 17 | ) { 18 | /** 19 | * Creates a new GraphProgress and calculate the graph progress 20 | * @param graph {Object} 21 | * @param graphDescription {String} 22 | * @constructor 23 | */ 24 | function GraphProgress(graph, graphDescription) { 25 | assert.object(graph, 'graph'); 26 | assert.string(graphDescription, 'graphDescription'); 27 | 28 | this.graph = graph; 29 | this.data = { 30 | graphId: graph.instanceId, 31 | nodeId: graph.node, 32 | graphName: graph.name || 'Not available', 33 | status: graph._status, 34 | progress: { 35 | description: graphDescription || 'Not available', 36 | } 37 | }; 38 | this._calculateGraphProgress(); 39 | } 40 | 41 | /** 42 | * Get the graph progress 43 | * 44 | * @returns {Object} 45 | */ 46 | GraphProgress.prototype.getProgressEventData = function() { 47 | return this.data; 48 | }; 49 | 50 | GraphProgress.prototype._calculateGraphProgress = function() { 51 | var self = this; 52 | if (self.graph.tasks) { 53 | self.data.progress.value = self._countNonPengingTask(self.graph.tasks); 54 | self.data.progress.maximum = _.size(self.graph.tasks); 55 | } 56 | if (self.graph._status === Constants.Task.States.Succeeded) { 57 | self.data.progress.value = self.data.progress.maximum; 58 | } 59 | self.data.progress.percentage = self._calculateProgressPercentage(self.data.progress); 60 | }; 61 | 62 | /** 63 | * Update the task progress 64 | * @param taskId {String} 65 | * @param taskProgress {Object} 66 | */ 67 | GraphProgress.prototype.updateTaskProgress = function(taskId, taskProgress) { 68 | var self = this; 69 | self._setTaskProgress(taskId, taskProgress); 70 | self._calculateTaskProgressPercentage(); 71 | }; 72 | 73 | GraphProgress.prototype._setTaskProgress = function(taskId, taskProgress) { 74 | var self = this; 75 | var tasks = self.graph.tasks || {}; 76 | var task = tasks[taskId] || {friendlyName: 'Not available'}; 77 | self.data.taskProgress = { 78 | taskId: taskId, 79 | taskName: task.friendlyName || 'Not available', 80 | state: task.state, 81 | progress: taskProgress 82 | }; 83 | }; 84 | 85 | GraphProgress.prototype._calculateTaskProgressPercentage = function() { 86 | var self = this; 87 | self.data.taskProgress.progress.percentage = 88 | self._calculateProgressPercentage(self.data.taskProgress.progress); 89 | }; 90 | 91 | GraphProgress.prototype._calculateProgressPercentage = function(data) { 92 | var percentage = 100 * _.round(data.value / data.maximum, 2); 93 | if (percentage >= 0 && percentage <= 100) { 94 | percentage = percentage.toString() + '%'; 95 | } else { 96 | percentage = 'Not available'; 97 | } 98 | return percentage; 99 | }; 100 | 101 | GraphProgress.prototype._countNonPengingTask = function(tasks) { 102 | return _.size(_.filter(tasks, function(task) { 103 | return task.state !== Constants.Task.States.Pending; 104 | })); 105 | }; 106 | 107 | GraphProgress.create = function(graph, graphDescription) { 108 | return new GraphProgress(graph, graphDescription); 109 | }; 110 | 111 | return GraphProgress; 112 | } 113 | -------------------------------------------------------------------------------- /spec/lib/models/log-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | var base = require('./base-spec'); 7 | var sandbox = sinon.sandbox.create(); 8 | 9 | describe('Models.Log', function () { 10 | helper.before(function (context) { 11 | context.MessengerServices = function() { 12 | this.start= sandbox.stub().resolves(); 13 | this.stop = sandbox.stub().resolves(); 14 | this.publish = sandbox.stub().resolves(); 15 | }; 16 | return [ 17 | helper.di.simpleWrapper(context.MessengerServices, 'Messenger') 18 | ]; 19 | }); 20 | 21 | base.before(function (context) { 22 | context.model = helper.injector.get('Services.Waterline').logs; 23 | context.attributes = context.model._attributes; 24 | }); 25 | 26 | helper.after(); 27 | 28 | describe('Base', function () { 29 | base.examples(); 30 | }); 31 | 32 | describe('creation', function() { 33 | it('should create a log event', function() { 34 | return this.model.create({ 35 | node: "54d93422b492491333333333", 36 | level: 'info', 37 | message: 'hello', 38 | trace: '00000000-0000-4000-8000-000000000000', 39 | timestamp: new Date(), 40 | caller: 'dummy', 41 | subject: 'dummy', 42 | host: 'dummy' 43 | }); 44 | }); 45 | 46 | it('should create a log event with context', function() { 47 | return this.model.create({ 48 | node: "54d93422b492491333333333", 49 | level: 'info', 50 | message: 'hello', 51 | trace: '00000000-0000-4000-8000-000000000000', 52 | timestamp: new Date(), 53 | caller: 'dummy', 54 | subject: 'dummy', 55 | host: 'dummy', 56 | context: { 57 | data: { 58 | key1: 'valuevalue', 59 | array1: [ 60 | { 61 | key2: 'value3' 62 | } 63 | ] 64 | } 65 | } 66 | }); 67 | }); 68 | 69 | it('should replace dots in log context keys with underscores', function() { 70 | return this.model.create({ 71 | node: "54d93422b492491333333333", 72 | level: 'info', 73 | message: 'hello', 74 | trace: '00000000-0000-4000-8000-000000000000', 75 | timestamp: new Date(), 76 | caller: 'dummy', 77 | subject: 'dummy', 78 | host: 'dummy', 79 | context: { 80 | data: { 81 | 'testkey1.test': 'value1' 82 | } 83 | } 84 | }).then(function (logEvent) { 85 | /*jshint sub: true */ 86 | expect(logEvent.context.data['testkey1_test']).to.equal('value1'); 87 | }); 88 | }); 89 | 90 | it('should replace dollar signs in log keys with underscores', function() { 91 | return this.model.create({ 92 | node: "54d93422b492491333333333", 93 | level: 'info', 94 | message: 'hello', 95 | trace: '00000000-0000-4000-8000-000000000000', 96 | timestamp: new Date(), 97 | caller: 'dummy', 98 | subject: 'dummy', 99 | host: 'dummy', 100 | context: { 101 | data: { 102 | 'testkey1$test': 'value1' 103 | } 104 | } 105 | }).then(function (logEvent) { 106 | /*jshint sub: true */ 107 | expect(logEvent.context.data['testkey1_test']).to.equal('value1'); 108 | }); 109 | }); 110 | }); 111 | }); 112 | 113 | -------------------------------------------------------------------------------- /spec/lib/common/subscription-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2015, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | describe('Subscription', function () { 6 | var Subscription; 7 | var Logger; 8 | var sandbox = sinon.sandbox.create(); 9 | 10 | helper.before(function (context) { 11 | context.MessengerServices = function() { 12 | this.start= sandbox.stub().resolves(); 13 | this.stop = sandbox.stub().resolves(); 14 | this.publish = sandbox.stub().resolves(); 15 | }; 16 | return [ 17 | helper.di.simpleWrapper(context.MessengerServices, 'Messenger') 18 | ]; 19 | }); 20 | 21 | before(function () { 22 | Subscription = helper.injector.get('Subscription'); 23 | 24 | Logger = helper.injector.get('Logger'); 25 | 26 | Logger.prototype.log = sinon.spy(); 27 | 28 | this.options = { 29 | consumerTag: 'fake' 30 | }; 31 | }); 32 | 33 | 34 | beforeEach(function() { 35 | this.queue = { 36 | state: 'open', 37 | unsubscribe: sinon.stub().resolves(), 38 | destroy: sinon.stub().resolves(), 39 | close: sinon.stub().resolves() 40 | }; 41 | this.subject = new Subscription( 42 | this.queue, 43 | this.options 44 | ); 45 | 46 | }); 47 | 48 | helper.after(); 49 | 50 | describe('constructor', function () { 51 | it('assigns queue to queue', function () { 52 | this.subject.queue.should.deep.equal(this.queue); 53 | }); 54 | 55 | it('assigns options to options', function () { 56 | this.subject.options.should.deep.equal(this.options); 57 | }); 58 | }); 59 | 60 | describe('dispose', function () { 61 | it('should unsubscribe the queue when the state is open', function () { 62 | var self = this; 63 | 64 | return this.subject.dispose().then(function () { 65 | self.queue.unsubscribe.should.have.been.calledWith('fake'); 66 | self.queue.destroy.should.have.been.called; 67 | self.queue.close.should.have.been.called; 68 | }); 69 | }); 70 | 71 | it('should prevent closing the queue multiple times', function() { 72 | var self = this; 73 | return self.subject.dispose() 74 | .then(function() { 75 | return self.subject.dispose(); 76 | }) 77 | .then(function() { 78 | self.queue.unsubscribe.should.have.been.calledOnce; 79 | self.queue.destroy.should.have.been.calledOnce; 80 | self.queue.close.should.have.been.calledOnce; 81 | }); 82 | }); 83 | 84 | it('should retry closing the queue silently if it fails for the caller', function(done) { 85 | var self = this; 86 | sinon.spy(self.subject, 'dispose'); 87 | self.subject.retryDelay = 0; 88 | self.subject.MAX_DISPOSE_RETRIES = 1; 89 | self.queue.unsubscribe.rejects(new Error('test error')); 90 | 91 | expect(self.subject.dispose()).to.be.rejectedWith(/test error/) 92 | .then(function() { 93 | setTimeout(function() { 94 | try { 95 | expect(self.subject.dispose).to.have.been.calledTwice; 96 | done(); 97 | } catch (e) { 98 | done(e); 99 | } 100 | }, 50); 101 | }) 102 | .catch(function(err) { 103 | done(err); 104 | }); 105 | }); 106 | 107 | it('should reject with an error if the queue is already unsubscribed', function () { 108 | 109 | this.subject.queue.state = 'closing'; 110 | 111 | return this.subject.dispose().should.be.rejectedWith( 112 | Error, 113 | 'Attempted to dispose a subscription whose queue state is not open' 114 | ); 115 | }); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /spec/lib/models/localusers-spec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2016, EMC, Inc. 2 | 3 | 4 | 'use strict'; 5 | 6 | var base = require('./base-spec'); 7 | var sandbox = sinon.sandbox.create(); 8 | 9 | describe('Models.LocalUsers', function () { 10 | helper.before(function (context) { 11 | context.MessengerServices = function() { 12 | this.start= sandbox.stub().resolves(); 13 | this.stop = sandbox.stub().resolves(); 14 | this.publish = sandbox.stub().resolves(); 15 | }; 16 | return [ 17 | helper.di.simpleWrapper(context.MessengerServices, 'Messenger') 18 | ]; 19 | }); 20 | 21 | base.before(function (context) { 22 | context.model = helper.injector.get('Services.Waterline').localusers; 23 | context.attributes = context.model._attributes; 24 | }); 25 | 26 | helper.after(); 27 | 28 | describe('Base', function () { 29 | describe('createdAt', function () { 30 | before(function () { 31 | this.subject = this.attributes.createdAt; 32 | }); 33 | 34 | it('should be a datetime', function () { 35 | expect(this.subject.type).to.equal('datetime'); 36 | }); 37 | }); 38 | 39 | describe('updatedAt', function () { 40 | before(function () { 41 | this.subject = this.attributes.updatedAt; 42 | }); 43 | 44 | it('should be a datetime', function () { 45 | expect(this.subject.type).to.equal('datetime'); 46 | }); 47 | }); 48 | }); 49 | 50 | describe('Attributes', function () { 51 | describe('username', function () { 52 | before(function () { 53 | this.subject = this.attributes.username; 54 | }); 55 | 56 | it('should be a string', function () { 57 | expect(this.subject.type).to.equal('string'); 58 | }); 59 | 60 | it('should be required', function () { 61 | expect(this.subject.required).to.equal(true); 62 | }); 63 | 64 | it('should be a primary key', function () { 65 | expect(this.subject.primaryKey).to.equal(true); 66 | }); 67 | }); 68 | 69 | describe('password', function () { 70 | before(function () { 71 | this.subject = this.attributes.password; 72 | }); 73 | 74 | it('should be a string', function () { 75 | expect(this.subject.type).to.equal('string'); 76 | }); 77 | 78 | it('should be required', function () { 79 | expect(this.subject.required).to.equal(true); 80 | }); 81 | }); 82 | 83 | describe('role', function () { 84 | before(function () { 85 | this.subject = this.attributes.role; 86 | }); 87 | 88 | it('should be a string', function () { 89 | expect(this.subject.type).to.equal('string'); 90 | }); 91 | }); 92 | }); 93 | 94 | describe('Password functions', function () { 95 | beforeEach(function () { 96 | return helper.reset(); 97 | }); 98 | 99 | it('should serialize a password', function () { 100 | return this.model.create({ 101 | username: 'admin', 102 | password: 'admin123' 103 | }).then(function(user) { 104 | expect(user.username).to.equal('admin'); 105 | expect(user.password).to.not.equal('admin123'); 106 | }); 107 | }); 108 | 109 | it('should validate a password', function() { 110 | return this.model.create({ 111 | username: 'readonly', 112 | password: 'read123' 113 | }).then(function(user) { 114 | expect(user.password).to.not.equal('read123'); 115 | expect(user.comparePassword('read123')).to.be.true; 116 | expect(user.comparePassword('badread')).to.be.false; 117 | }); 118 | }); 119 | }); 120 | }); 121 | --------------------------------------------------------------------------------