├── examples ├── analytics │ ├── 5SecondNoOp.R │ ├── rtScore.R │ └── insurModel.rData ├── config.json ├── tutorial │ ├── pooled │ │ ├── pooled-simulation.js │ │ ├── pooled-simulation.html │ │ ├── pooled-basics.js │ │ └── pooled-basics.html │ ├── background │ │ ├── background-basics.js │ │ └── background-basics.html │ ├── discrete │ │ ├── discrete-basics.js │ │ └── discrete-basics.html │ └── simulations │ │ ├── browser │ │ └── scoring-engine-simulation.js │ │ └── nodejs │ │ └── scoring-engine-simulation.js └── utils │ └── rbroker-stats-helper.js ├── test ├── config.json ├── README.md ├── fixtures │ ├── pool.js │ ├── discrete.js │ ├── common.js │ └── job.js └── specs │ ├── discrete-task-broker-test.js │ └── background-task-broker-test.js ├── .gitignore ├── LICENSE.md ├── gulpfile.js ├── gulp ├── util │ ├── errors.js │ └── script-filter.js ├── tasks │ ├── default.js │ ├── build.js │ ├── start.js │ ├── jshint.js │ ├── uglifyjs.js │ ├── connect.js │ ├── browserify.js │ └── header.js ├── config.js └── index.js ├── lib ├── task │ ├── pooled-task.js │ ├── background-task.js │ ├── discreate-task.js │ └── rtask.js ├── rtask-type.js ├── worker │ ├── rbroker-worker.js │ ├── background-task-worker.js │ ├── pooled-task-worker.js │ └── discrete-task-worker.js ├── util │ └── rtask-queue.js ├── engine │ ├── discrete-task-broker.js │ ├── background-task-broker.js │ ├── pooled-task-broker.js │ └── rbroker-engine.js └── rtask-token.js ├── package.json ├── SECURITY.md ├── README.md └── rbroker.js /examples/analytics/5SecondNoOp.R: -------------------------------------------------------------------------------- 1 | Sys.sleep(5) 2 | -------------------------------------------------------------------------------- /examples/analytics/rtScore.R: -------------------------------------------------------------------------------- 1 | x 2 | customerid 3 | Sys.sleep(1) 4 | score <- customerid * 10 -------------------------------------------------------------------------------- /examples/analytics/insurModel.rData: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/js-rbroker-framework/HEAD/examples/analytics/insurModel.rData -------------------------------------------------------------------------------- /test/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "endpoint": "http://localhost:8050", 3 | "credentials": { 4 | "username": "testuser", 5 | "password": "YOUR_TESTUSER_PASSWORD" 6 | }, 7 | "cors": true, 8 | "allowSelfSignedSSLCert": true 9 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore build 2 | dist/ 3 | 4 | # Ignore extensions 5 | *.diff 6 | *.err 7 | *.log 8 | *.orig 9 | *.rej 10 | *.swo 11 | *.swp 12 | *.vi 13 | *.zip 14 | *~ 15 | 16 | # Ignore OS or Editor folders 17 | ._* 18 | .cache 19 | .DS_Store 20 | .project 21 | .settings 22 | .idea 23 | .tmproj 24 | *.esproj 25 | *.sublime-workspace 26 | *.sublime-project 27 | 28 | # Ignore node 29 | node_modules/ 30 | npm-debug.log -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2010-2016, Microsoft Corporation 2 | 3 | This program is licensed to you under the terms of Version 2.0 of the 4 | Apache License. This program is distributed WITHOUT 5 | ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 6 | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 7 | Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 8 | details. -------------------------------------------------------------------------------- /examples/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "http://localhost:8050", 3 | "cors": true, 4 | "credentials": { 5 | "username": "testuser", 6 | "password": "YOUR_TESTUSER_PASSWORD" 7 | }, 8 | "constants" : { 9 | "TUTORIAL_REPO_OWNER": "testuser", 10 | "TUTORIAL_REPO_DIRECTORY": "tutorial-rbroker", 11 | "TUTORIAL_NOOP_SCRIPT": "5SecondNoOp.R", 12 | "TUTORIAL_RTSCORE_SCRIPT": "rtScore.R", 13 | "TUTORIAL_INSURANCE_MODEL": "insurModel.rData" 14 | } 15 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | require('./gulp'); -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | DeployR JavaScript RBroker Framework Unit Tests 2 | =============================================== 3 | 4 | The DeployR JavaScript RBroker Framework ships with a set of unit tests. 5 | 6 | ``` 7 | $ npm test 8 | ``` 9 | 10 | By default, the build configuration assumes an instance of the DeployR server 11 | is running on `localhost`. If your instance of DeployR is running at some 12 | other IP address then please udpate the `endpoint` property in the 13 | config.json configuration file as appropriate. 14 | -------------------------------------------------------------------------------- /test/fixtures/pool.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | module.exports = require('./common'); -------------------------------------------------------------------------------- /test/fixtures/discrete.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | module.exports = require('./common'); -------------------------------------------------------------------------------- /gulp/util/errors.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var gutil = require('gulp-util'); 13 | 14 | module.exports = function(err) { 15 | gutil.log(gutil.colors.green(err)); 16 | }; -------------------------------------------------------------------------------- /gulp/util/script-filter.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var path = require('path'); 13 | 14 | module.exports = function(name) { 15 | return /(\.(js)$)/i.test(path.extname(name)); 16 | }; -------------------------------------------------------------------------------- /gulp/tasks/default.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var gulp = require('gulp'); 13 | 14 | /* 15 | * Task: default 16 | * 17 | * The default gulp task for deployr. 18 | */ 19 | gulp.task('default', ['build']); -------------------------------------------------------------------------------- /gulp/tasks/build.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var gulp = require('gulp'); 13 | 14 | /* 15 | * Task: build 16 | * 17 | * The main build task to prepare the deployr source for browser environments. 18 | */ 19 | gulp.task('build', ['jshint', 'header']); -------------------------------------------------------------------------------- /gulp/config.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var path = require('path'), 13 | pkg = require('../package.json'); 14 | 15 | module.exports = { 16 | port: '8080', 17 | root: path.resolve('./'), 18 | dist: './browser', 19 | name: pkg.name, 20 | pkg: pkg 21 | }; -------------------------------------------------------------------------------- /gulp/index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var fs = require('fs'), 13 | whitelist = require('./util/script-filter'), 14 | tasks = fs.readdirSync('./gulp/tasks/').filter(whitelist); 15 | 16 | tasks.forEach(function(task) { 17 | require('./tasks/' + task); 18 | }); -------------------------------------------------------------------------------- /gulp/tasks/start.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var gulp = require('gulp'); 13 | 14 | /* 15 | * Task: start 16 | * 17 | * Runs the build and starts the `Connect` HTTP server for viewing all the 18 | * ./examples HTML samples. 19 | */ 20 | gulp.task('start', ['connect']); 21 | -------------------------------------------------------------------------------- /gulp/tasks/jshint.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var gulp = require('gulp'), 13 | jshint = require("gulp-jshint"), 14 | config = require('../config'); 15 | 16 | /* 17 | * Task: jshist 18 | * 19 | * Lint's the entire `deployr` source. 20 | */ 21 | gulp.task('jshint', function () { 22 | return gulp.src([config.name + '.js', './lib*.js']) 23 | .pipe(jshint({lookup: true})) 24 | .pipe(jshint.reporter('default')); 25 | }); 26 | -------------------------------------------------------------------------------- /lib/task/pooled-task.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var Base = require('selfish').Base, 13 | RTaskType = require('../rtask-type'), 14 | RTask = require('./rtask'); 15 | 16 | module.exports = Base.extend(RTask, { 17 | 18 | initialize: function(props) { 19 | RTask.initialize.call(this, props, RTaskType.POOLED); 20 | }, 21 | 22 | toString: function() { 23 | return 'PooledTask: ' + RTask.toString.call(this); 24 | } 25 | }); -------------------------------------------------------------------------------- /lib/task/background-task.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var Base = require('selfish').Base, 13 | RTaskType = require('../rtask-type'), 14 | RTask = require('./rtask'); 15 | 16 | module.exports = Base.extend(RTask, { 17 | 18 | initialize: function(props) { 19 | RTask.initialize.call(this, props, RTaskType.BACKGROUND); 20 | }, 21 | 22 | toString: function() { 23 | return 'BackgroundTask: ' + RTask.toString.call(this); 24 | } 25 | }); -------------------------------------------------------------------------------- /lib/task/discreate-task.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * CCopyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var Base = require('selfish').Base, 13 | RTaskType = require('../rtask-type'), 14 | RTask = require('./rtask'); 15 | 16 | module.exports = Base.extend(RTask, { 17 | 18 | initialize: function(props) { 19 | RTask.initialize.call(this, props, RTaskType.DISCRETE); 20 | }, 21 | 22 | toString: function() { 23 | return 'DiscreteTask: ' + RTask.toString.call(this); 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /lib/rtask-type.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var Base = require('selfish').Base, 13 | Enum = require('enum'), 14 | taskEnum = new Enum(['DISCRETE', 'POOLED', 'BACKGROUND'], 'RTaskType'); 15 | 16 | /** 17 | * Defines the currently supported set of `RTask`. 18 | * 19 | * @module rtask-type 20 | * @for rbroker 21 | */ 22 | module.exports = Base.extend({ 23 | /** 24 | * Discrete task. 25 | */ 26 | DISCRETE: taskEnum.DISCRETE, 27 | 28 | /** 29 | * Pooled task. 30 | */ 31 | POOLED: taskEnum.POOLED, 32 | 33 | /** 34 | * Background task. 35 | */ 36 | BACKGROUND: taskEnum.BACKGROUND 37 | }); -------------------------------------------------------------------------------- /gulp/tasks/uglifyjs.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var gulp = require('gulp'), 13 | uglify = require('gulp-uglify'), 14 | plumber = require('gulp-plumber'), 15 | rename = require("gulp-rename"), 16 | onError = require('../util/errors'), 17 | config = require('../config'); 18 | 19 | /* 20 | * Task: uglifyjs 21 | */ 22 | gulp.task('uglify', ['browserify'], function() { 23 | return gulp.src(['./browser/' + config.name + '.js']) 24 | .pipe(plumber({ errorHandler: onError })) 25 | .pipe(uglify()) 26 | .pipe(rename({ 27 | extname: '.min.js' 28 | })) 29 | .pipe(gulp.dest('./browser/')); 30 | }); -------------------------------------------------------------------------------- /lib/worker/rbroker-worker.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var Base = require('selfish').Base, 13 | D = require('d.js'); 14 | 15 | module.exports = Base.extend({ 16 | initialize: function initialize(task) { 17 | this.task = task; 18 | this.defer = D(); 19 | }, 20 | 21 | work: function(resourceToken) { /* override */ }, 22 | 23 | terminate: function(interrupt) { /* override */ }, 24 | 25 | isPending: function() { 26 | return this.defer.promise.isPending(); 27 | }, 28 | 29 | resolve: function(result) { 30 | this.defer.resolve(result); 31 | }, 32 | 33 | reject: function(err) { 34 | this.defer.reject(err); 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /gulp/tasks/connect.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var gulp = require('gulp'), 13 | connect = require('gulp-connect'), 14 | config = require('../config'); 15 | 16 | /* 17 | * Task: connect 18 | * 19 | * Run a webserver for viewing ./examples HTML samples (with LiveReload) 20 | */ 21 | gulp.task('connect', ['build'], function() { 22 | connect.server({ 23 | root: [config.root + '/examples/', config.dist], 24 | port: config.port, 25 | livereload: true 26 | }); 27 | 28 | // Watch JS 29 | gulp.watch([config.name + '.js', './lib/**/*.js'], ['build']); 30 | 31 | // Watch Examples that use HTML and livereload 32 | gulp.watch('./examples/**/*.html', ['build']); 33 | }); -------------------------------------------------------------------------------- /gulp/tasks/browserify.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var browserify = require('browserify'), 13 | gulp = require('gulp'), 14 | source = require('vinyl-source-stream'), 15 | plumber = require('gulp-plumber'), 16 | onError = require('../util/errors'), 17 | config = require('../config'); 18 | 19 | /* 20 | * Task: browserify 21 | * 22 | * Runs `browserify` on the `deployr` source. 23 | */ 24 | gulp.task('browserify', function() { 25 | return browserify({ debug: true, standalone: config.name, entries: ['./' + config.name + '.js'] }) 26 | .ignore('http') 27 | .bundle() 28 | .pipe(plumber({ errorHandler: onError})) 29 | .pipe(source(config.name + '.js')) 30 | .pipe(gulp.dest(config.dist)); 31 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rbroker", 3 | "version": "8.0.5", 4 | "description": "JavaScript RBroker Framework for DeployR", 5 | "keywords": [ 6 | "deployr", 7 | "deployr.io", 8 | "rbroker", 9 | "ajax", 10 | "R", 11 | "api", 12 | "simple" 13 | ], 14 | "author": "DeployR - Microsoft Corporation", 15 | "contributors": [{ 16 | "name": "Sean Wells" 17 | }], 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/microsoft/js-rbroker-framework.git" 21 | }, 22 | "homepage": "http://go.microsoft.com/fwlink/?LinkID=698842", 23 | "scripts": { 24 | "start": "gulp start", 25 | "build": "gulp", 26 | "test": "tape test/specs/*.js", 27 | "test-browser": "browserify test/specs/*.js | testlingify" 28 | }, 29 | "devDependencies": { 30 | "browserify": "^13.0.1", 31 | "gulp": "^3.9.1", 32 | "gulp-connect": "^4.1.0", 33 | "gulp-header": "^1.8.7", 34 | "gulp-jshint": "^2.0.1", 35 | "gulp-livereload": "^3.8.1", 36 | "gulp-plumber": "^1.1.0", 37 | "gulp-rename": "^1.2.2", 38 | "gulp-uglify": "^1.5.4", 39 | "gulp-util": "^3.0.7", 40 | "tape": "^4.6.0", 41 | "vinyl-source-stream": "^1.1.0", 42 | "when": "^3.7.2" 43 | }, 44 | "dependencies": { 45 | "d.js": "^0.6.0", 46 | "deployr": "8.0.5", 47 | "enum": "^0.2.6", 48 | "merge": "^1.1.3", 49 | "selfish": "^0.3.2" 50 | }, 51 | "main": "rbroker.js", 52 | "engines": { 53 | "node": ">= 0.10.0" 54 | }, 55 | "license": "Apache-2.0", 56 | "bugs": { 57 | "url": "https://github.com/microsoft/js-rbroker-framework/issues" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/util/rtask-queue.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var Base = require('selfish').Base; 13 | 14 | var RTaskQueue = Base.extend({ 15 | initialize: function (capacity) { 16 | this.capacity = capacity; 17 | this.q = []; 18 | }, 19 | 20 | /** 21 | * Inserts the specified element at the tail of this queue if it is possible 22 | * to do so immediately without exceeding the queue's capacity, returning 23 | * ```true``` upon success and ```false``` if this queue is full. 24 | */ 25 | offer: function (rtask) { 26 | var accepting = this.size() < this.capacity; 27 | 28 | if (accepting) { 29 | this.q.push(rtask); 30 | } 31 | 32 | return accepting; // True if added False otherwise 33 | }, 34 | 35 | /** 36 | * Retrieves and removes the head of this queue. 37 | */ 38 | take: function () { 39 | return this.q.shift(); 40 | }, 41 | 42 | /** 43 | * Retrieves, but does not remove, the head of this queue, or returns ` 44 | * ``null`` if this queue is empty. 45 | */ 46 | peek: function () { 47 | return this.q[0]; 48 | }, 49 | 50 | /** 51 | * Returns the number of elements in this queue. 52 | */ 53 | size: function () { 54 | return this.q.length; 55 | }, 56 | 57 | /** 58 | * Returns ```true``` if this collection contains no elements. 59 | * This implementation returns size() === 0. 60 | */ 61 | isEmpty: function () { 62 | return this.size() === 0; 63 | }, 64 | 65 | clear: function () { 66 | this.q.length = 0; 67 | }, 68 | 69 | iter: function (fn) { 70 | this.q.forEach(fn); 71 | } 72 | }); 73 | 74 | module.exports = RTaskQueue; -------------------------------------------------------------------------------- /lib/task/rtask.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var Base = require('selfish').Base, 13 | merge = require('merge'); 14 | 15 | /** 16 | * Represents any R Analytics task for execution on an `RBroker`. 17 | * 18 | * @module rtask 19 | * @for rbroker 20 | */ 21 | module.exports = Base.extend({ 22 | 23 | /** 24 | * Represents any R Analytics task for execution on an `RBroker`. 25 | * 26 | * @class 27 | * @constructs 28 | * @param {Object} props - The task properties object hash. 29 | */ 30 | initialize: function initialize(props, type) { 31 | this.type = type; 32 | this.token = null; 33 | 34 | props = props || {}; 35 | 36 | for (var index in props) { 37 | Object.defineProperty(this, index, { 38 | value: props[index], 39 | enumerable: true 40 | }); 41 | } 42 | }, 43 | 44 | /** 45 | * A clone of the `RTask` DeployR API properties as a flat object. 46 | * 47 | * @returns {Object} A clone of the `RTask` properties. 48 | */ 49 | serialize: function() { 50 | var task = {}; 51 | 52 | Object.keys(this).filter(function(key) { // blacklist 53 | return (['type', 'token'].indexOf(key) === -1); 54 | }) 55 | .forEach(function(key) { 56 | task[key] = this[key]; 57 | }.bind(this)); 58 | 59 | return merge(true, task); 60 | }, 61 | 62 | toString: function() { 63 | var out = '', 64 | keys = Object.keys(this); 65 | 66 | for (var o in keys) { 67 | out += ' [ ' + keys[o] + ' = "' + this[keys[o]] + '" ]'; 68 | } 69 | 70 | return out; 71 | } 72 | }); -------------------------------------------------------------------------------- /test/fixtures/common.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var merge = require('merge'), 13 | deployr = require('deployr'), 14 | config = require('../config'); 15 | 16 | if (config.allowSelfSignedSSLCert) { 17 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; 18 | } 19 | 20 | function createOptions(good) { 21 | // 22 | // Example-Fraud-Score constants. 23 | // 24 | var EFS_DIRECTORY = "example-fraud-score", 25 | EFS_BAD_DIRECTORY = "bad-example-fraud-score", 26 | EFS_MODEL = "fraudModel.rData", 27 | EFS_FUNCTION = "ccFraudScore.R", 28 | EFS_USERNAME = "testuser"; 29 | 30 | return { 31 | // 32 | // Loading all files found in EFS_DIRECTORY. 33 | // 34 | preloadbydirectory: good ? EFS_DIRECTORY : EFS_BAD_DIRECTORY, 35 | // 36 | // Loading EFS_FILE found in EFS_DIRECTORY. 37 | // Note, setting this option is redundant here in practical terms as 38 | // the preloadByDirectory would have already loaded the EFS_FUNCTION 39 | // file from the EFS_DIRECTORY. Howvever, this method is testing the 40 | // preload directory mechanism. 41 | // 42 | preloadfilename: EFS_FUNCTION, 43 | preloadfiledirectory: good ? EFS_DIRECTORY : EFS_BAD_DIRECTORY, 44 | preloadfileauthor: EFS_USERNAME, 45 | // 46 | // Loading EFS_MODEL found in EFS_DIRECTORY. 47 | // 48 | preloadobjectname: EFS_MODEL, 49 | preloadobjectdirectory: good ? EFS_DIRECTORY : EFS_BAD_DIRECTORY, 50 | preloadobjectauthor: EFS_USERNAME, 51 | // 52 | // Loading aribtrary R object. 53 | // 54 | rinputs: [deployr.RInput.character('arbitrary', 'testme')] 55 | }; 56 | } 57 | 58 | module.exports = merge({ 59 | BAD_ENDPOINT: 'http://bad.end.point:999', 60 | BAD_CREDENTIALS: { 61 | username: 'bad-username-12321', 62 | password: 'bad-password-12321' 63 | }, 64 | BAD_SCRIPT_NAME: 'DoesNotExist.R', 65 | BAD_DIR_NAME: 'not-a-dir', 66 | BAD_AUTHOR_NAME: 'notauser', 67 | 68 | good: function() { 69 | return createOptions(true); 70 | }, 71 | 72 | bad: function() { 73 | return createOptions(false); 74 | } 75 | }, config); 76 | -------------------------------------------------------------------------------- /lib/engine/discrete-task-broker.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | var D = require('d.js'), 12 | RBrokerEngine = require('./rbroker-engine'), 13 | DiscreteTaskWorker = require('../worker/discrete-task-worker'); 14 | 15 | /** 16 | * The Discrete Task Runtime acquires DeployR grid resources per `RTask` 17 | * on-demand. 18 | * 19 | * @module discrete-broker 20 | * @for rbroker 21 | */ 22 | module.exports = RBrokerEngine.extend({ 23 | /** 24 | * The Discrete Task Runtime acquires DeployR grid resources per `RTask` 25 | * on-demand. 26 | * 27 | * @class 28 | * @constructor 29 | * @param {Object} [options] Configuration options. 30 | */ 31 | initialize: function (config) { 32 | RBrokerEngine.initialize.call(this, config); 33 | 34 | // Initialize the resourceTokenPool with Integer based resourceTokens. 35 | for (var i = 0; i < this.parallelTaskLimit; i++) { 36 | this.resourceTokenPool.offer(i); 37 | } 38 | 39 | if (config.credentials) { 40 | this.ruser = this.validateEndpoint().io('/r/user/login') 41 | .data(config.credentials) 42 | .ctx(this) 43 | .end(function(res) { this.emit('ready'); }); 44 | } else { 45 | this.validateEndpoint(function() { 46 | this.ruser = null; 47 | this.emit('ready'); 48 | }); 49 | } 50 | }, 51 | 52 | /** 53 | * Not supported. Support for `refresh` is only available on the 54 | * `PooledTaskBroker` runtime. 55 | * 56 | * @method refresh 57 | * @override 58 | * @api public 59 | */ 60 | refresh: function (config) { 61 | throw new Error('DiscreteTaskBroker refresh not supported.'); 62 | }, 63 | 64 | /** 65 | * @Override 66 | */ 67 | createWorker: function (task) { 68 | return DiscreteTaskWorker.new(task, this.ruser); 69 | }, 70 | 71 | /** 72 | * Release all client-side and server-side resources maintained by or on 73 | * behalf of an instance of `RBroker`. 74 | * 75 | * @method shutdown 76 | * @override 77 | * @return {Promise} A promise wrapping the resolution of either "resolve" or 78 | * "reject" callback. 79 | * @api public 80 | */ 81 | shutdown: function () { 82 | this.flush(); 83 | 84 | return this.ruser ? this.ruser.release() : D.promisify(true); 85 | } 86 | }); -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /lib/worker/background-task-worker.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var Base = require('selfish').Base, 13 | deployr = require('deployr'), 14 | D = require('d.js'), 15 | RTaskType = require('../rtask-type'), 16 | RBrokerWorker = require('./rbroker-worker'); 17 | 18 | module.exports = RBrokerWorker.extend({ 19 | 20 | initialize: function (task, ruser) { 21 | RBrokerWorker.initialize.call(this, task); 22 | this.ruser = ruser; 23 | this.job = null; 24 | }, 25 | 26 | work: function(resourceToken) { 27 | var self = this, 28 | startTime = new Date().getTime(), 29 | data = this.task.serialize(), 30 | rinputs = data.rinputs || [], 31 | routputs = data.routputs || []; 32 | 33 | delete data.rinputs; 34 | delete data.routputs; 35 | 36 | this.resourceToken = resourceToken; 37 | 38 | return deployr.io('/r/job/submit') 39 | .share(this.ruser.getCookies()) 40 | .data(data) 41 | .global(false) // supress global error events for this `io` 42 | .rinputs(rinputs) 43 | .routputs(routputs) 44 | .promise() 45 | .then(function(res) { 46 | var about = res.get('job'); 47 | 48 | self.job = about.job; // job-id 49 | 50 | // resolve the promise which kicks-off the callback 51 | return { 52 | task: self.task, 53 | result: { 54 | id: about.job, 55 | type: RTaskType.BACKGROUND, 56 | timeOnCode: 0, 57 | timeOnServer: 0, 58 | timeOnCall: (new Date().getTime() - startTime), 59 | success: true, 60 | failure: null 61 | } 62 | }; 63 | }); 64 | }, 65 | 66 | terminate: function(interrupt) { 67 | if (interrupt && this.job) { 68 | this.ruser.io('/r/job/cancel') 69 | .data({ job: this.job }) 70 | .end(); 71 | 72 | return true; 73 | } else { 74 | // 75 | // RTask still pending confirmation from RBroker if there is no 76 | // `resourceToken` hence can not be interrupted [or] the task is 77 | // being worked on and a forced `interrupt` was not given 78 | // 79 | return false; 80 | } 81 | } 82 | }); 83 | -------------------------------------------------------------------------------- /lib/engine/background-task-broker.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var merge = require('merge'), 13 | RBrokerEngine = require('./rbroker-engine'), 14 | BackgroundTaskWorker = require('../worker/background-task-worker'); 15 | 16 | /** 17 | * The Background Task Runtime acquires DeployR grid resources per `RTask` 18 | * based on the server-side management of asynchronous grid resources. 19 | * 20 | * @module brackground-broker 21 | * @for rbroker 22 | */ 23 | module.exports = RBrokerEngine.extend({ 24 | /** 25 | * The Background Task Runtime acquires DeployR grid resources per `RTask` 26 | * based on the server-side management of asynchronous grid resources. 27 | * 28 | * @class 29 | * @constructor 30 | * @param {Object} [options] Configuration options. 31 | */ 32 | initialize: function (config) { 33 | /* 34 | * This limit is set simply to ensure the BackgroundTaskBroker does not 35 | * swamp the server which too many concurrent HTTP requests when submitting 36 | * RTask. The real queueing of RTask is handled by the server, this broker 37 | * simply pushed the RTask into the server-managed queue. 38 | */ 39 | var PARALLEL_TASK_LIMIT = 10; 40 | 41 | RBrokerEngine.initialize.call(this, merge(config, { 42 | maxConcurrentTaskLimit: PARALLEL_TASK_LIMIT 43 | })); 44 | 45 | // Initialize the resourceTokenPool with Integer based resourceTokens. 46 | for(var i = 0; i < this.parallelTaskLimit; i++) { 47 | this.resourceTokenPool.offer(i); 48 | } 49 | 50 | this.ruser = this.validateEndpoint().io('/r/user/login') 51 | .data(config.credentials) 52 | .ctx(this) 53 | .end(function(res) { this.emit('ready'); }); 54 | }, 55 | 56 | /** 57 | * Not supported. Support for `refresh` is only available on the 58 | * `PooledTaskBroker` runtime. 59 | * 60 | * @method refresh 61 | * @override 62 | * @api public 63 | */ 64 | refresh: function (config) { 65 | throw new Error('BackgroundTaskBroker refresh not supported.'); 66 | }, 67 | 68 | /** 69 | * Release all client-side and server-side resources maintained by or on 70 | * behalf of an instance of `RBroker`. 71 | * 72 | * @method shutdown 73 | * @override 74 | * @return {Promise} A promise wrapping the resolution of either "resolve" or 75 | * "reject" callback. 76 | * @api public 77 | */ 78 | shutdown: function () { 79 | this.flush(); 80 | 81 | return this.ruser.release(); 82 | }, 83 | 84 | /** 85 | * @override 86 | * @api private 87 | */ 88 | createWorker: function (task) { 89 | return BackgroundTaskWorker.new(task, this.ruser); 90 | } 91 | }); 92 | -------------------------------------------------------------------------------- /examples/tutorial/pooled/pooled-simulation.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Copyright (C) 2010-2016, Microsoft Corporation 5 | * 6 | * This program is licensed to you under the terms of Version 2.0 of the 7 | * Apache License. This program is distributed WITHOUT 8 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 9 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 10 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 11 | * details. 12 | */ 13 | 14 | /*! 15 | * usage: $ node pooled-simulation.js 16 | * output: Task execution lifecycle logged to stdout 17 | * 18 | * Example using a PooledTaskBroker. 19 | * 20 | * 1. Prepare 'Pooled Broker Configuration' instance. 21 | * 22 | * This example creates an authenticated `PooledTaskBroker` 23 | * to act as our scoring engine. 24 | * 25 | * The following steps initialize the pool of 26 | * R sessions dedicated to our scoring engine. 27 | * 28 | * In this example, we pre-initialize the workspace 29 | * for each R session in the pool to contain the 30 | * R model used when scoring customer requests. 31 | * 32 | * The R model, insurModel.rData, is loaded from 33 | * the DeployR repository into each workspace at 34 | * startup time. Preloading the R model at startup 35 | * reduces runtime overhead and improves overall 36 | * response times in the application. 37 | */ 38 | 39 | var deployr = require('deployr'), 40 | rbroker = require('../../../rbroker'), 41 | config = require('../../config'), 42 | ScoringEngineSimulation = require('../simulations/nodejs/scoring-engine-simulation'); 43 | 44 | var DEMO6_TARGET_POOL_SIZE = 10; 45 | 46 | var brokerConfig = { 47 | maxConcurrentTaskLimit: DEMO6_TARGET_POOL_SIZE, 48 | host: config.host, 49 | cors: config.cors, 50 | credentials: config.credentials, 51 | releaseGridResources: false, 52 | logging: false, 53 | // --- pool options --- 54 | // For all `pool` option parameters: 55 | // @see https://microsoft.github.io/deployr-api-docs/#r-project-pool 56 | pool: { 57 | preloadobjectname: config.constants.TUTORIAL_INSURANCE_MODEL, 58 | preloadobjectauthor: config.constants.TUTORIAL_REPO_OWNER, 59 | preloadobjectdirectory: config.constants.TUTORIAL_REPO_DIRECTORY 60 | } 61 | }; 62 | 63 | /* 64 | * 2. Create RBroker instance. 65 | * 66 | * The application designer, in conjunction with the 67 | * DeployR administrator who provisions and sets 68 | * limits on DeployR grid resources, need to decide how 69 | * many R sessions will be reserved for the `PooledRBroker` 70 | * used by our scoring engine application. 71 | */ 72 | 73 | console.log('About to create pooledTaskBroker.'); 74 | 75 | var pBroker = rbroker.pooledTaskBroker(brokerConfig); 76 | 77 | console.log('R session pool size, requested: ' + 78 | DEMO6_TARGET_POOL_SIZE + ' , actual: ' + 79 | pBroker.maxConcurrency() + '\n'); 80 | 81 | /* 82 | * 3. Create an `RTaskAppSimulator`. It will drive 83 | * sample customer data scoring requests through the 84 | * RBroker. 85 | */ 86 | 87 | var simulation = new ScoringEngineSimulation(config); 88 | 89 | /* 90 | * 4. Launch RTaskAppSimulator simulation. 91 | */ 92 | pBroker.simulateApp(simulation); 93 | -------------------------------------------------------------------------------- /examples/tutorial/background/background-basics.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Copyright (C) 2010-2016, Microsoft Corporation 5 | * 6 | * This program is licensed to you under the terms of Version 2.0 of the 7 | * Apache License. This program is distributed WITHOUT 8 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 9 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 10 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 11 | * details. 12 | */ 13 | 14 | /* 15 | * usage: $ node background-basics.js 16 | * output: Task execution lifecycle logged to stdout 17 | * 18 | * Example using a BackgroundTaskBroker. 19 | */ 20 | 21 | var rbroker = require('../../../rbroker'), 22 | config = require('../../config'); 23 | 24 | /* 25 | * 1. Create RBroker instance using RBrokerFactory. 26 | * 27 | * This example creates a BackgroundTaskBroker. 28 | */ 29 | var brokerConfig = { 30 | host: config.host, 31 | credentials: config.credentials 32 | }; 33 | 34 | /* 35 | * Register a `listener` for asynchronous notifications on RTask completion. 36 | * 37 | * .complete|.error|.progress|.idel are aliases for: 38 | * ------------------------------------------------------- 39 | * .on('complete', fn) 40 | * .on('error', fn); 41 | * .on('progress', fn) 42 | * .on('idel'), fn) 43 | */ 44 | var bgBroker = rbroker.backgroundTaskBroker(brokerConfig) 45 | .complete(function (rTask, rTaskResult) { 46 | console.log('[completed]-----------------------------------------'); 47 | console.log(rTask); 48 | console.log(rTaskResult); 49 | console.log('----------------------------------------------------'); 50 | }) 51 | .error(function (err) { 52 | console.log('[error]---------------------------------------------'); 53 | console.log(err); 54 | console.log('----------------------------------------------------'); 55 | }) 56 | .progress(function (status) { 57 | console.log('[progress]------------------------------------------'); 58 | console.log(status); 59 | console.log('----------------------------------------------------'); 60 | }) 61 | .idle(function () { // nothing pending 62 | bgBroker.shutdown() 63 | .then(function () { 64 | console.log('Background Task: RBroker shutdown `successful`.'); 65 | }, function () { 66 | console.log('Background Task: RBroker has shutdown `failure`.'); 67 | }); 68 | }); 69 | 70 | /* 71 | * 2. Define RTask 72 | * 73 | * This example creates a BackgroundTask that will execute an artibrary block 74 | * of R code. 75 | */ 76 | var props = { 77 | name: 'Example Background RTask', 78 | descr: 'Example Background RTask.', 79 | code: 'x <- rnorm(100)' 80 | }; 81 | 82 | var rTask = rbroker.backgroundTask(props); 83 | 84 | /* 85 | * 3. Submit RTask to RBroker for execution. 86 | * 87 | * The RTaskToken is returned immediately. You can use the token to track the 88 | * progress of RTask and/or block while waiting for a result. However, in this 89 | * example we are going to allow the RTaskListener handle the result so there is 90 | * nothing further for us to do here after we submit the RTask. 91 | */ 92 | bgBroker.submit(rTask); 93 | 94 | console.log('Submitted ' + rTask + ' for execution on RBroker.'); 95 | -------------------------------------------------------------------------------- /lib/rtask-token.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | var Base = require('selfish').Base; 12 | 13 | /** 14 | * Represents a handle to an `RTask` live on an `RBroker`. 15 | * 16 | * @module rtask-token 17 | * @for rbroker 18 | */ 19 | module.exports = Base.extend({ 20 | 21 | /** 22 | * Represents a handle to an `RTask` live on an `RBroker`. 23 | * 24 | * @class 25 | * @constructor 26 | * @param {RTask} The task worker. 27 | */ 28 | initialize: function initialize(worker) { 29 | this.worker = worker; 30 | this.cancelled = false; 31 | }, 32 | 33 | /** 34 | * Terminates `this` running task. 35 | * 36 | * @method cancel 37 | * @param {Boolean} Permission to interrupt task if it is running. 38 | * @return {RTaskToken} for chaining. 39 | * @api public 40 | */ 41 | cancel: function(interrupt) { 42 | // RTask completed (resolved|rejected), can not be cancelled. 43 | this.cancelled = this.worker.terminate(interrupt); 44 | return this.cancelled; 45 | }, 46 | 47 | /** 48 | * Returns the `RTask` associated with this `RTaskToken`. 49 | * 50 | * @method getTask 51 | * @return {RTaskToken} for chaining. 52 | * @api public 53 | */ 54 | getTask: function() { 55 | return this.worker.task; 56 | }, 57 | 58 | /** 59 | * Returns `true` if this task completed. Completion may be due to normal 60 | * termination, an exception, or cancellation -- in all of these cases, 61 | * this method will return `true`. 62 | * 63 | * @deprecated 64 | * @method isDone 65 | * @return {Boolean} If `this` task is completed. 66 | * @api public 67 | */ 68 | isDone: function() { 69 | return !this.isPending(); 70 | }, 71 | 72 | /** 73 | * Returns `false` if this task completed. Completion may be due to normal 74 | * termination, an exception, or cancellation -- in all of these cases, 75 | * this method will return `true`. 76 | * 77 | * @method isPending 78 | * @return {Boolean} `true` if this task has not yet been completed. 79 | * @api public 80 | */ 81 | isPending: function() { 82 | return this.worker.isPending(); 83 | }, 84 | 85 | /** 86 | * Returns `true` if this task was cancelled before it completed normally. 87 | * 88 | * @method isCancelled 89 | * @return {Boolean} `true` if this task was cancelled before it completed. 90 | * @api public 91 | */ 92 | isCancelled: function() { 93 | return this.cancelled; 94 | }, 95 | 96 | /** 97 | * The `.promise()` method returns a dynamically generated Promise that is 98 | * resolved once this task has completed. 99 | * 100 | * @method promise 101 | * @return {Promise} A promise wrapping the resolution of either "resolve" 102 | * or "reject" callback. 103 | * @api public 104 | */ 105 | promise: function() { 106 | return this.worker.defer.promise; 107 | } 108 | }); -------------------------------------------------------------------------------- /lib/worker/pooled-task-worker.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var Base = require('selfish').Base, 13 | D = require('d.js'), 14 | merge = require('merge'), 15 | deployr = require('deployr'), 16 | RTaskType = require('../rtask-type'), 17 | RBrokerWorker = require('./rbroker-worker'); 18 | 19 | module.exports = RBrokerWorker.extend({ 20 | 21 | initialize: function (task, ruser) { 22 | RBrokerWorker.initialize.call(this, task); 23 | this.ruser = ruser; 24 | }, 25 | 26 | work: function(resourceToken) { 27 | var task = this.task, 28 | data = merge({ 29 | project: resourceToken, 30 | phantom: true 31 | }, this.task.serialize()), 32 | api = '/r/project/execute/' + (data.code ? 'code' : 'script'), 33 | rinputs = data.rinputs || [], 34 | routputs = data.routputs || [], 35 | startTime = new Date().getTime(); 36 | 37 | delete data.rinputs; 38 | delete data.routputs; 39 | 40 | this.resourceToken = resourceToken; 41 | 42 | return deployr.io(api) 43 | .share(this.ruser.getCookies()) 44 | .data(data) 45 | .rinputs(rinputs) 46 | .routputs(routputs) 47 | .global(false) // supress global error events for this `io` 48 | .promise() 49 | .then(function(res) { 50 | res = res.deployr.response; 51 | 52 | var generatedConsole = res.execution.console, 53 | generatedPlots = res.execution.results, 54 | generatedFiles = res.execution.artifacts, 55 | generatedObjects = res.workspace.objects, 56 | storedFiles = res.repository.files; 57 | 58 | // resolve the promise which kicks-off the callback 59 | return { 60 | task: task, 61 | result: { 62 | id: res.project.project, 63 | type: RTaskType.POOLED, 64 | success: true, 65 | timeOnCode: res.execution.timeCode, 66 | timeOnServer: res.execution.timeTotal, 67 | timeOnCall: (new Date().getTime() - startTime), 68 | failure: null, 69 | interrupted: false, 70 | generatedConsole: generatedConsole, 71 | generatedPlots: generatedPlots, 72 | generatedFiles: generatedFiles, 73 | generatedObjects: generatedObjects, 74 | storedFiles: storedFiles 75 | } 76 | }; 77 | }) 78 | }, 79 | 80 | terminate: function(interrupt) { 81 | // work has started and a forced interrupt given 82 | if (interrupt && this.resourceToken && this.isPending()) { 83 | this.ruser.io('/r/project/execute/interrupt') 84 | .data({ project: this.resourceToken }) 85 | .end(); 86 | return true; 87 | } else { 88 | // 89 | // RTask still pending confirmation from RBroker if there is no 90 | // `resourceToken` hence can not be interrupted [or] the task is 91 | // being worked on and a forced `interrupt` was not given 92 | // 93 | return false; 94 | } 95 | } 96 | 97 | }); 98 | -------------------------------------------------------------------------------- /lib/worker/discrete-task-worker.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var Base = require('selfish').Base, 13 | deployr = require('deployr'), 14 | D = require('d.js'), 15 | RTaskType = require('../rtask-type'), 16 | RBrokerWorker = require('./rbroker-worker'); 17 | 18 | module.exports = RBrokerWorker.extend({ 19 | 20 | initialize: function(task, ruser) { 21 | RBrokerWorker.initialize.call(this, task); 22 | this.ruser = ruser; 23 | this.io = null; // `io` request for 'termination' if called 24 | }, 25 | 26 | work: function(resourceToken) { 27 | var task = this.task, 28 | startTime = new Date().getTime(), 29 | data = this.task.serialize(), 30 | rinputs = data.rinputs || [], 31 | routputs = data.routputs || []; 32 | 33 | delete data.rinputs; 34 | delete data.routputs; 35 | 36 | this.resourceToken = resourceToken; 37 | 38 | // save `io` request for 'termination' purposes 39 | this.io = deployr.io('/r/repository/script/execute') 40 | // make parallel http req. using the same session 41 | .share(this.ruser ? this.ruser.getCookies() : null) 42 | .data(data) 43 | .rinputs(rinputs) 44 | .routputs(routputs) 45 | .global(false); // supress global error events for this `io` 46 | 47 | // send the script execution request and return a promise 48 | return this.io.promise() 49 | .then(function(res) { 50 | res = res.deployr.response; 51 | 52 | var timeOnServer = res.execution.timeTotal, 53 | id = res.project.project, 54 | generatedConsole = res.execution.console, 55 | generatedPlots = res.execution.results, 56 | generatedFiles = res.execution.artifacts, 57 | generatedObjects = res.workspace.objects, 58 | storedFiles = res.repository.files; 59 | 60 | // resolve the promise which kicks-off the callback values 61 | return { 62 | task: task, 63 | result: { 64 | id: id, 65 | type: RTaskType.DISCRETE, 66 | success: true, 67 | timeOnCode: res.execution.timeCode, 68 | timeOnServer: timeOnServer, 69 | timeOnCall: (new Date().getTime() - startTime), 70 | failure: null, 71 | interrupted: false, 72 | generatedConsole: generatedConsole, 73 | generatedPlots: generatedPlots, 74 | generatedFiles: generatedFiles, 75 | generatedObjects: generatedObjects, 76 | storedFiles: storedFiles 77 | } 78 | }; 79 | }); 80 | }, 81 | 82 | terminate: function(interrupt) { 83 | // 84 | // @NOTE: 85 | // There is no way to obtain DeployR reference, such as a projectId, for 86 | // an stateless execution in-progress, so aborting the current RTask 87 | // operation is not possible. At best we can do here is free-up the 88 | // client connection and abort. 89 | // 90 | if (interrupt && this.io && this.isPending()) { 91 | this.io.abort(); 92 | this.io.destroy(); 93 | this.io = null; 94 | 95 | return true; 96 | } else { 97 | // 98 | // RTask still pending confirmation from RBroker if there is no 99 | // `resourceToken` hence can not be interrupted [or] the task is 100 | // being worked on and a forced `interrupt` was not given 101 | // 102 | return false; 103 | } 104 | } 105 | }); 106 | -------------------------------------------------------------------------------- /examples/tutorial/pooled/pooled-simulation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Example using a Pooled Simulation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Example using a Pooled Simulation

16 |
17 |
    18 |
  1. Populate /examples/config.json with the proper values before running.
  2. 19 |
  3. Open the browser's debug window to view print logs for this example.
  4. 20 |
  5. View documentation for more information.
  6. 21 |
22 | 23 | 115 | 116 | -------------------------------------------------------------------------------- /examples/tutorial/discrete/discrete-basics.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Copyright (C) 2010-2016, Microsoft Corporation 5 | * 6 | * This program is licensed to you under the terms of Version 2.0 of the 7 | * Apache License. This program is distributed WITHOUT 8 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 9 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 10 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 11 | * details. 12 | */ 13 | 14 | /*! 15 | * usage: $ node discrete-basics.js 16 | * output: Task execution lifecycle logged to stdout 17 | * 18 | * Example using a DiscreteTaskBroker. 19 | */ 20 | 21 | var rbroker = require('../../../rbroker'), 22 | config = require('../../config'), 23 | consts = config.constants; 24 | 25 | /* 26 | * 1. Create RBroker instance using RBrokerFactory. 27 | * 28 | * This example creates a DiscreteTaskBroker. 29 | */ 30 | var brokerConfig = { 31 | maxConcurrentTaskLimit: 10, 32 | host: config.host, 33 | credentials: config.credentials 34 | }; 35 | 36 | // 37 | // .complete|.error|.progress|.idel are aliases for: 38 | // ------------------------------------------------------- 39 | // .on('complete', fn) 40 | // .on('error', fn); 41 | // .on('progress', fn) 42 | // .on('idel'), fn) 43 | // 44 | var dBroker = rbroker.discreteTaskBroker(brokerConfig) 45 | .complete(function (rTask, rTaskResult) { 46 | console.log('[completed]----------------------------------------'); 47 | console.log(rTask); 48 | console.log(rTaskResult); 49 | console.log('---------------------------------------------------'); 50 | }) 51 | .error(function (err) { 52 | console.log('[error]--------------------------------------------'); 53 | console.log(err); 54 | console.log('---------------------------------------------------'); 55 | }) 56 | .progress(function (status) { 57 | console.log('[progress]-----------------------------------------'); 58 | console.log(status); 59 | console.log('---------------------------------------------------'); 60 | }) 61 | .idle(function () { // nothing pending 62 | dBroker.shutdown() 63 | .then(function () { 64 | console.log('Discrete: RBroker shutdown `successful`.'); 65 | }, function () { 66 | console.log('Discrete: RBroker has shutdown `failure`.'); 67 | }); 68 | }); 69 | 70 | /* 71 | * 2. Define RTask(2) 72 | * 73 | * This example creates 7 DiscreteTasks that will execute an R script: 74 | * /testuser/root/DeployR - Hello World.R 75 | */ 76 | var props = { 77 | filename: consts.TUTORIAL_NOOP_SCRIPT, 78 | directory: consts.TUTORIAL_REPO_DIRECTORY, 79 | author: consts.TUTORIAL_REPO_OWNER 80 | }; 81 | 82 | var rTask1 = rbroker.discreteTask(props); 83 | var rTask2 = rbroker.discreteTask(props); 84 | var rTask3 = rbroker.discreteTask(props); 85 | var rTask4 = rbroker.discreteTask(props); 86 | var rTask5 = rbroker.discreteTask(props); 87 | var rTask6 = rbroker.discreteTask(props); 88 | var rTask7 = rbroker.discreteTask(props); 89 | 90 | /* 91 | * 92 | * 3. Submit RTasks to RBroker for execution. 93 | * 94 | * The RTaskToken is returned immediately. You can use the returned token to 95 | * bind to it's task lifecycle (complete|error|ensure), an example of this is 96 | * the rTask7 task. 97 | */ 98 | dBroker.submit(rTask1, false); 99 | dBroker.submit(rTask2, false); 100 | dBroker.submit(rTask3, false); 101 | dBroker.submit(rTask4, false); 102 | dBroker.submit(rTask5, false); 103 | dBroker.submit(rTask6, false); 104 | // -- Listen for indifidual task -- 105 | dBroker.submit(rTask7, false) 106 | .promise() 107 | .then(function (res) { 108 | var rTask = res.task, 109 | rTaskResult = res.result; 110 | 111 | console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~ Individual TASK completed'); 112 | console.log(rTask); 113 | console.log(rTaskResult); 114 | console.log('----------------------------------------------------'); 115 | }) 116 | .error(function (err) { 117 | console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Individual TASK error'); 118 | console.log(err); 119 | console.log('----------------------------------------------------'); 120 | }) 121 | .ensure(function () { 122 | console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Individual TASK ensure'); 123 | console.log(rTask7); 124 | console.log('----------------------------------------------------'); 125 | }); 126 | 127 | // Print 128 | console.log('Discrete Task: submitted ' + rTask1 + ' for execution on RBroker'); 129 | console.log('Discrete Task: submitted ' + rTask2 + ' for execution on RBroker'); 130 | console.log('Discrete Task: submitted ' + rTask3 + ' for execution on RBroker'); 131 | console.log('Discrete Task: submitted ' + rTask4 + ' for execution on RBroker'); 132 | console.log('Discrete Task: submitted ' + rTask5 + ' for execution on RBroker'); 133 | console.log('Discrete Task: submitted ' + rTask6 + ' for execution on RBroker'); 134 | console.log('Discrete Task: submitted ' + rTask7 + ' for execution on RBroker'); -------------------------------------------------------------------------------- /examples/utils/rbroker-stats-helper.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | (function(global) { 12 | 13 | var taskType = { 14 | DISCRETE: 'DISCRETE', 15 | POOLED: 'POOLED', 16 | BACKGROUND: 'BACKGROUND' 17 | }; 18 | 19 | /* 20 | * RBroker runtime summary statistics (print) helper class. 21 | */ 22 | var printHelper = { 23 | 24 | /** 25 | * Prints RBroker Runtime Stats to console output. 26 | */ 27 | stats: function(stats) { 28 | var displayAvgTimeOnCode = 0, 29 | displaySAvgTimeOnServer = 0, 30 | displayAvgTimeOnCall = 0, 31 | maxConcurrency = stats.maxConcurrency; 32 | 33 | console.log('[progress]-----------------------------------------'); 34 | console.log('RBroker Activity Summary'); 35 | console.log('RBroker: Max Concurrency [ ' + maxConcurrency + ' ]'); 36 | console.log('RBroker: Total Tasks Run [ ' + 37 | stats.totalTasksRun + ' ]'); 38 | console.log('RBroker: Tasks Ok [ ' + 39 | stats.totalTasksRunToSuccess + ' ] Fail [ ' + 40 | stats.totalTasksRunToFailure + ' ]'); 41 | 42 | if (stats.totalTasksRunToSuccess > 0) { 43 | displayAvgTimeOnCode = 44 | stats.totalTimeTasksOnCode / stats.totalTasksRunToSuccess; 45 | displayAvgTimeOnServer = 46 | stats.totalTimeTasksOnServer / stats.totalTasksRunToSuccess; 47 | displayAvgTimeOnCall = 48 | stats.totalTimeTasksOnCall / stats.totalTasksRunToSuccess; 49 | } 50 | 51 | console.log('RBroker: Task Average Time On Code [ ' + 52 | displayAvgTimeOnCode + ' ]'); 53 | console.log('RBroker: Task Average Time On Server [ ' + 54 | displayAvgTimeOnServer + ' ]'); 55 | console.log('RBroker: Task Average Time On Call [ ' + 56 | displayAvgTimeOnCall + ' ]\n'); 57 | 58 | console.log('---------------------------------------------------'); 59 | }, 60 | 61 | /** 62 | * Prints errors to console output. 63 | */ 64 | error: function(err) { 65 | console.log('[error]--------------------------------------------'); 66 | console.log('Status[fail]: cause= ' + err); 67 | console.log('---------------------------------------------------'); 68 | }, 69 | 70 | /** 71 | * Prints RTaskResult to console output. 72 | */ 73 | results: function(task, result) { 74 | console.log('[completed]----------------------------------------'); 75 | console.log('Task: ' + task); 76 | 77 | switch (result.type) { 78 | 79 | case taskType.DISCRETE: 80 | if (result.success) { 81 | console.log('Status[ok]: [ code : ' + 82 | result.timeOnCode + ' , server : ' + 83 | result.timeOnServer + ' , call : ' + 84 | result.timeOnCall + ' ]'); 85 | } else { 86 | console.log('Status[fail]: cause=' + result.failure); 87 | } 88 | break; 89 | 90 | case taskType.POOLED: 91 | if (result.success) { 92 | console.log('Status[ok]: [ code : ' + 93 | result.timeOnCode + ' , server : ' + 94 | result.timeOnServer + ' , call : ' + 95 | result.timeOnCall + ' ]'); 96 | } else { 97 | console.log('Status[fail]: cause=' + result.failure); 98 | } 99 | break; 100 | 101 | case taskType.BACKGROUND: 102 | if (result.success) { 103 | console.log('Status[ok]: [ server : ' + 104 | result.timeOnServer + ' , call : ' + 105 | result.timeOnCall + ' ]'); 106 | } else { 107 | console.log('Status[fail]: cause=' + result.failure); 108 | } 109 | break; 110 | } 111 | console.log('---------------------------------------------------'); 112 | } 113 | }; 114 | 115 | // -- export for both Browser|Node.js so we can reuse for both envs. -- 116 | if (typeof exports !== 'undefined') { 117 | if (typeof module !== 'undefined' && module.exports) { 118 | exports = module.exports = printHelper; 119 | } 120 | exports.printHelper = printHelper; 121 | } else { 122 | global.printHelper = printHelper; 123 | } 124 | 125 | })(typeof window === 'undefined' ? this : window); 126 | -------------------------------------------------------------------------------- /examples/tutorial/simulations/browser/scoring-engine-simulation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | /* 13 | * This ScoringEngineSimulation is a simple extension 14 | * to the SampleAppSimulation class used by PooledSimulation. 15 | * 16 | * The addition of the SIMULATE_TASK_RATE_PER_MINUTE parameter 17 | * allows the app simulation to mimick more "real world" 18 | * workloads by adjusting the rate at which RTask are 19 | * submitted to the RBroker for execution. 20 | * 21 | * Note, this simulation also demonstrates how input values 22 | * can be passed to each RTask and how output values can 23 | * be retrieved following the execution of each task. 24 | * 25 | * In this example, a "customerid" input value is passed on 26 | * each RTask. While this example passes a simple index 27 | * value for "customerid" a "real world" app would like pass 28 | * a database record id along with additional customer specific 29 | * data parameters. 30 | * 31 | * In this example, the value of the "score" is returned as a 32 | * DeployR-encoded R object and made available on the result 33 | * for the RTask. 34 | */ 35 | (function(global) { 36 | 37 | function ScoringEngineSimulation(config) { 38 | this.consts = config.constants; 39 | } 40 | 41 | ScoringEngineSimulation.prototype = { 42 | 43 | /** 44 | * @IMPORTANT - The `simulateApp(broker)` method must be implemented. 45 | */ 46 | simulateApp: function(pBroker) { 47 | var SIMULATE_TOTAL_TASK_COUNT = 10, 48 | SIMULATE_TASK_RATE_PER_MINUTE = 0, 49 | simulationStartTime = 0, 50 | sleep = 0, 51 | consts = this.consts, 52 | RIn = deployr.RInput; 53 | 54 | /* 55 | * The following logic controls the simulated rate of 56 | * RTasks being submitted to the RBroker, essentially 57 | * controlling the simulated workload. 58 | */ 59 | function staggeredLoad(tasksPushedToBroker, sleep) { 60 | /* 61 | * Prepare RTask for real-time scoring. 62 | * 63 | * In this example, we pass along a unique customer ID 64 | * with each RTask. In a real-world application, the input 65 | * parameters on each RTask will vary depending on need, 66 | * such as customer database record keys and supplementary 67 | * parameter data to facilitate the scoring. 68 | */ 69 | setTimeout(function() { 70 | 71 | var rTask = rbroker.pooledTask({ 72 | filename: consts.TUTORIAL_RTSCORE_SCRIPT, 73 | directory: consts.TUTORIAL_REPO_DIRECTORY, 74 | author: consts.TUTORIAL_REPO_OWNER, 75 | rinputs: [RIn.numeric('customerid', tasksPushedToBroker)], 76 | routputs: ['score'] 77 | }); 78 | 79 | pBroker.submit(rTask, false); 80 | console.log('Submitted task ' + rTask + '\n'); 81 | }, sleep); 82 | } 83 | 84 | /* 85 | * Listen for the RTask events. 86 | * 87 | * .complete|.error|.progress|.idel are aliases for: 88 | * ------------------------------------------------------- 89 | * .on('complete', fn) 90 | * .on('error', fn); 91 | * .on('progress', fn) 92 | * .on('idel'), fn) 93 | */ 94 | pBroker.complete(function(rTask, rTaskResult) { 95 | printHelper.results(rTask, rTaskResult); 96 | }) 97 | .error(function(err) { 98 | printHelper.error(err); 99 | }) 100 | .progress(function(status) { 101 | printHelper.stats(status); 102 | }) 103 | .idle(function() { 104 | pBroker.shutdown() 105 | .then(function() { 106 | console.log('Pooled: RBroker shutdown `successful`.'); 107 | }, function() { 108 | console.log('Pooled: RBroker has shutdown `failure`.'); 109 | }); 110 | }); 111 | 112 | /* 113 | * Submit task(s) to RBroker for execution. 114 | */ 115 | 116 | console.log('About to simulate ' + 117 | SIMULATE_TOTAL_TASK_COUNT + ' tasks at a rate of ' + 118 | SIMULATE_TASK_RATE_PER_MINUTE + ' tasks per minutes.'); 119 | 120 | simulationStartTime = new Date().getTime(); 121 | 122 | for (var tasksPushedToBroker = 0; 123 | tasksPushedToBroker < SIMULATE_TOTAL_TASK_COUNT; 124 | tasksPushedToBroker++) { 125 | 126 | /* 127 | * The following logic controls the simulated rate of 128 | * RTasks being submitted to the RBroker, essentially 129 | * controlling the simulated workload. 130 | */ 131 | if (tasksPushedToBroker < (SIMULATE_TOTAL_TASK_COUNT)) { 132 | if (SIMULATE_TASK_RATE_PER_MINUTE !== 0) { 133 | var staggerLoadInterval = 60 / SIMULATE_TASK_RATE_PER_MINUTE; 134 | sleep += (staggerLoadInterval * 1000); 135 | } 136 | staggeredLoad(tasksPushedToBroker, sleep); 137 | } 138 | } 139 | } 140 | }; 141 | 142 | global.ScoringEngineSimulation = ScoringEngineSimulation; 143 | 144 | })(window); -------------------------------------------------------------------------------- /examples/tutorial/simulations/nodejs/scoring-engine-simulation.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Copyright (C) 2010-2016, Microsoft Corporation 5 | * 6 | * This program is licensed to you under the terms of Version 2.0 of the 7 | * Apache License. This program is distributed WITHOUT 8 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 9 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 10 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 11 | * details. 12 | */ 13 | 14 | var deployr = require('deployr'), 15 | rbroker = require('../../../../rbroker'), 16 | printHelper = require('../../../utils/rbroker-stats-helper'); 17 | 18 | /* 19 | * This ScoringEngineSimulation is a simple extension 20 | * to the SampleAppSimulation class used by PooledSimulation. 21 | * 22 | * The addition of the SIMULATE_TASK_RATE_PER_MINUTE parameter 23 | * allows the app simulation to mimick more "real world" 24 | * workloads by adjusting the rate at which RTask are 25 | * submitted to the RBroker for execution. 26 | * 27 | * Note, this simulation also demonstrates how input values 28 | * can be passed to each RTask and how output values can 29 | * be retrieved following the execution of each task. 30 | * 31 | * In this example, a "customerid" input value is passed on 32 | * each RTask. While this example passes a simple index 33 | * value for "customerid" a "real world" app would like pass 34 | * a database record id along with additional customer specific 35 | * data parameters. 36 | * 37 | * In this example, the value of the "score" is returned as a 38 | * DeployR-encoded R object and made available on the result 39 | * for the RTask. 40 | */ 41 | function ScoringEngineSimulation(config) { 42 | this.consts = config.constants; 43 | } 44 | 45 | ScoringEngineSimulation.prototype = { 46 | 47 | /** 48 | * @IMPORTANT - The `simulateApp(broker)` method must be implemented. 49 | */ 50 | simulateApp: function(pBroker) { 51 | var SIMULATE_TOTAL_TASK_COUNT = 10, 52 | SIMULATE_TASK_RATE_PER_MINUTE = 0, 53 | simulationStartTime = 0, 54 | sleep = 0, 55 | consts = this.consts, 56 | RIn; 57 | 58 | if (typeof window === 'undefined') { 59 | RIn = require('deployr').RInput; 60 | } else { 61 | RIn = deployr.RInput; // `deployr` already in window for browsers 62 | } 63 | 64 | /* 65 | * The following logic controls the simulated rate of 66 | * RTasks being submitted to the RBroker, essentially 67 | * controlling the simulated workload. 68 | */ 69 | function staggeredLoad(tasksPushedToBroker, sleep) { 70 | /* 71 | * Prepare RTask for real-time scoring. 72 | * 73 | * In this example, we pass along a unique customer ID 74 | * with each RTask. In a real-world application, the input 75 | * parameters on each RTask will vary depending on need, 76 | * such as customer database record keys and supplementary 77 | * parameter data to facilitate the scoring. 78 | */ 79 | setTimeout(function() { 80 | 81 | var rTask = rbroker.pooledTask({ 82 | filename: consts.TUTORIAL_RTSCORE_SCRIPT, 83 | directory: consts.TUTORIAL_REPO_DIRECTORY, 84 | author: consts.TUTORIAL_REPO_OWNER, 85 | rinputs: [RIn.numeric('customerid', tasksPushedToBroker)], 86 | routputs: ['score'] 87 | }); 88 | 89 | pBroker.submit(rTask, false); 90 | console.log('Submitted task ' + rTask + '\n'); 91 | }, sleep); 92 | } 93 | 94 | /* 95 | * Listen for the RTask events. 96 | * 97 | * .complete|.error|.progress|.idel are aliases for: 98 | * ------------------------------------------------------- 99 | * .on('complete', fn) 100 | * .on('error', fn); 101 | * .on('progress', fn) 102 | * .on('idel'), fn) 103 | */ 104 | pBroker.complete(function(rTask, rTaskResult) { 105 | printHelper.results(rTask, rTaskResult); 106 | }) 107 | .error(function(err) { 108 | printHelper.error(err); 109 | }) 110 | .progress(function(status) { 111 | printHelper.stats(status); 112 | }) 113 | .idle(function() { 114 | pBroker.shutdown() 115 | .then(function() { 116 | console.log('Pooled: RBroker shutdown `successful`.'); 117 | }, function() { 118 | console.log('Pooled: RBroker has shutdown `failure`.'); 119 | }); 120 | }); 121 | 122 | /* 123 | * Submit task(s) to RBroker for execution. 124 | */ 125 | 126 | console.log('About to simulate ' + 127 | SIMULATE_TOTAL_TASK_COUNT + ' tasks at a rate of ' + 128 | SIMULATE_TASK_RATE_PER_MINUTE + ' tasks per minutes.'); 129 | 130 | simulationStartTime = new Date().getTime(); 131 | 132 | for (var tasksPushedToBroker = 0; tasksPushedToBroker < SIMULATE_TOTAL_TASK_COUNT; tasksPushedToBroker++) { 133 | 134 | /* 135 | * The following logic controls the simulated rate of 136 | * RTasks being submitted to the RBroker, essentially 137 | * controlling the simulated workload. 138 | */ 139 | if (tasksPushedToBroker < (SIMULATE_TOTAL_TASK_COUNT)) { 140 | if (SIMULATE_TASK_RATE_PER_MINUTE !== 0) { 141 | var staggerLoadInterval = 60 / SIMULATE_TASK_RATE_PER_MINUTE; 142 | sleep += (staggerLoadInterval * 1000); 143 | } 144 | staggeredLoad(tasksPushedToBroker, sleep); 145 | } 146 | } 147 | } 148 | }; 149 | 150 | module.exports = ScoringEngineSimulation; -------------------------------------------------------------------------------- /examples/tutorial/pooled/pooled-basics.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Copyright (C) 2010-2016, Microsoft Corporation 5 | * 6 | * This program is licensed to you under the terms of Version 2.0 of the 7 | * Apache License. This program is distributed WITHOUT 8 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 9 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 10 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 11 | * details. 12 | */ 13 | 14 | /*! 15 | * usage: $ node pooled-basics.js 16 | * output: Task execution lifecycle logged to stdout 17 | * 18 | * Example using a PooledTaskBroker. 19 | */ 20 | var rbroker = require('../../../rbroker'), 21 | config = require('../../config'), 22 | consts = config.constants; 23 | 24 | /* 25 | * 1. Create RBroker instance using RBrokerFactory. 26 | * 27 | * This example creates a PooledTaskBroker. 28 | */ 29 | var brokerConfig = { 30 | maxConcurrentTaskLimit: 10, 31 | host: config.host, 32 | credentials: config.credentials, 33 | releaseGridResources: false, 34 | logging: false, 35 | // --- pool options --- 36 | // For all `pool` option parameters: 37 | // @see https://microsoft.github.io/deployr-api-docs/#r-project-pool 38 | pool: { /* optional pooled parameters go here... */ } 39 | }; 40 | 41 | /* 42 | * .complete|.error|.progress|.idel are aliases for: 43 | * ------------------------------------------------------- 44 | * .on('complete', fn) 45 | * .on('error', fn); 46 | * .on('progress', fn) 47 | * .on('idel'), fn) 48 | */ 49 | var pBroker = rbroker.pooledTaskBroker(brokerConfig) 50 | .complete(function (rTask, rTaskResult) { 51 | console.log('[completed]----------------------------------------'); 52 | console.log(rTask); 53 | console.log(rTaskResult); 54 | console.log('---------------------------------------------------'); 55 | }) 56 | .error(function (err) { 57 | console.log('[error]--------------------------------------------'); 58 | console.log(err); 59 | console.log('---------------------------------------------------'); 60 | }) 61 | .progress(function (status) { 62 | console.log('[progress]-----------------------------------------'); 63 | console.log(status); 64 | console.log('---------------------------------------------------'); 65 | }) 66 | .idle(function () { // nothing pending 67 | 68 | var shutdown = { 69 | start: function () { 70 | console.log('Pooled Task: rBroker shutdown `started`.'); 71 | return pBroker.shutdown(); 72 | }, 73 | success: function () { 74 | console.log('Pooled Task: rBroker shutdown `successful`.'); 75 | }, 76 | failure: function () { 77 | console.log('Pooled Task: rBroker shutdown `failure`.'); 78 | } 79 | }; 80 | 81 | // -- refresh then shutdown then print shutdown status -- 82 | // 83 | // @NOTE - A `refresh` is not needed here it simply demonstrates a 84 | // the proper state to call `refresh` which is when we are idel 85 | pBroker.refresh() 86 | .then(shutdown.start) 87 | .then(shutdown.success, shutdown.failure); 88 | 89 | }); 90 | 91 | /* 92 | * 2. Define RTask 93 | * 94 | * This example creates 7 PooledTasks that will execute the same R script, 95 | * `testuser/root/DeployR - Hello World.R` 96 | */ 97 | var props = { 98 | // You can use `code` | `filename` for a script 99 | filename: consts.TUTORIAL_NOOP_SCRIPT, 100 | //code: 'x<-5 \n print(x)', 101 | directory: consts.TUTORIAL_REPO_DIRECTORY, 102 | author: consts.TUTORIAL_REPO_OWNER 103 | }; 104 | 105 | var rTask1 = rbroker.pooledTask(props); 106 | var rTask2 = rbroker.pooledTask(props); 107 | var rTask3 = rbroker.pooledTask(props); 108 | var rTask4 = rbroker.pooledTask(props); 109 | var rTask5 = rbroker.pooledTask(props); 110 | var rTask6 = rbroker.pooledTask(props); 111 | var rTask7 = rbroker.pooledTask(props); 112 | 113 | /* 114 | * 115 | * 3. Submit RTasks to RBroker for execution. 116 | * 117 | * The `Task Worker` is returned immediately. You can use the returned token to 118 | * bind to it's task lifecycle (complete|error|ensure), an example of this is 119 | * the rTask7 task submittal. 120 | */ 121 | pBroker.submit(rTask1, false); 122 | pBroker.submit(rTask2, false); 123 | pBroker.submit(rTask3, false); 124 | pBroker.submit(rTask4, false); 125 | pBroker.submit(rTask5, false); 126 | pBroker.submit(rTask6, false); 127 | pBroker.submit(rTask7, false) 128 | .promise() 129 | .then(function (res) { 130 | var rTask = res.task, 131 | rTaskResult = res.result; 132 | 133 | console.log('~~~~~~~~~~~~~~~~~~~~~~~~~ Individual TASK completed'); 134 | console.log(rTask); 135 | console.log(rTaskResult); 136 | console.log('---------------------------------------------------'); 137 | }) 138 | .error(function (err) { 139 | console.log(err); 140 | }) 141 | .ensure(function () { 142 | console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Individual TASK ensure'); 143 | console.log(rTask7); 144 | console.log('----------------------------------------------------'); 145 | }); 146 | 147 | // Print 148 | console.log('Pooled Task: submitted ' + rTask1 + ' for execution on RBroker.'); 149 | console.log('Pooled Task: submitted ' + rTask2 + ' for execution on RBroker.'); 150 | console.log('Pooled Task: submitted ' + rTask3 + ' for execution on RBroker.'); 151 | console.log('Pooled Task: submitted ' + rTask4 + ' for execution on RBroker.'); 152 | console.log('Pooled Task: submitted ' + rTask5 + ' for execution on RBroker.'); 153 | console.log('Pooled Task: submitted ' + rTask6 + ' for execution on RBroker.'); 154 | console.log('Pooled Task: submitted ' + rTask7 + ' for execution on RBroker.'); 155 | -------------------------------------------------------------------------------- /test/fixtures/job.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var deployr = require('deployr'), 13 | when = require('when'), 14 | merge = require('merge'), 15 | config = require('../config'); 16 | 17 | var JOB_DONE_STATES = { 18 | COMPLETED: 'Completed', 19 | CANCELLED: 'Cancelled', 20 | INTERRUPTED: 'Interrupted', 21 | FAILED: 'Failed', 22 | ABORTED: 'Aborted' 23 | }; 24 | 25 | function queryJob(ruser, jobID) { 26 | return deployr.io('/r/job/query') 27 | .data({ 28 | job: jobID 29 | }) 30 | .share(ruser.getCookies()) 31 | .promise(); 32 | }; 33 | 34 | module.exports = merge({ 35 | 36 | /* 37 | * Following a unit test all artifacts assocated with an 38 | * RJob should be removed from the DeployR database. This 39 | * implementation removes the RJob and the associated 40 | * RProject if one was generated as a result of the RJob. 41 | * 42 | * Note, this method will not throw any Exceptions. 43 | */ 44 | deleteJobArtifacts: function(ruser, jobID) { 45 | // 46 | // This runs in parrell to the below 47 | // 1. /r/job/query 48 | // 2. /r/project/delete 49 | // 50 | deployr.io('/r/job/query') 51 | .data({ 52 | job: jobID 53 | }) 54 | .share(ruser.getCookies()) 55 | .end(function(res) { 56 | return { 57 | project: res.get('project') 58 | } 59 | }) 60 | .io('/r/project/delete') 61 | .end(); 62 | 63 | // 64 | // This runs in parrell to the above 65 | // 1. /r/job/cancel 66 | // 2. /r/job/delete 67 | // They are slipt up to ensure `/r/job/cancel` and `/r/job/delete` 68 | // get invoked 69 | // 70 | return deployr.io('/r/job/cancel') 71 | .data({ 72 | job: jobID 73 | }) 74 | .share(ruser.getCookies()) 75 | .end() 76 | .io('/r/job/delete') 77 | .data({ 78 | job: jobID 79 | }) 80 | .promise(); 81 | }, 82 | 83 | /* 84 | * Test and verify that a Job has reached a given "status". 85 | * This implementation will busy-wait for up to 2 minutes 86 | * for the Job to complete, otherwise it returns failure. 87 | */ 88 | verifyJobExitStatus: function(ruser, jobID, status) { 89 | var statusCheck = { 90 | id: null, 91 | PING_INTERVAL: 1000, 92 | TWO_MINUTES: 120000, 93 | 94 | start: function() { 95 | var self = this, 96 | time = 0, 97 | TWO_MINUTES = this.TWO_MINUTES, 98 | PING_INTERVAL = this.PING_INTERVAL, 99 | stop = function(res) { 100 | self.stop(res); 101 | }; 102 | 103 | clearInterval(this.id); 104 | 105 | this.id = setInterval(function() { 106 | time = time + PING_INTERVAL; 107 | 108 | if (time >= TWO_MINUTES) { 109 | stop(); 110 | } 111 | 112 | queryJob(ruser, jobID).then(stop, stop); 113 | 114 | }, PING_INTERVAL); 115 | 116 | queryJob(ruser, jobID).then(stop, stop); 117 | 118 | this.defer = when.defer(); 119 | 120 | return this.defer.promise; 121 | }, 122 | 123 | stop: function(res) { 124 | if (res) { 125 | if (!res.get('success')) { 126 | clearInterval(this.id); 127 | this.defer.resolve({ 128 | status: false, 129 | cause: res.get('error') 130 | }); 131 | } else { 132 | if (res.get('status') === status) { 133 | // 134 | // status matches status to be 135 | // verified, break. 136 | // 137 | clearInterval(this.id); 138 | this.defer.resolve({ 139 | status: true, 140 | cause: status 141 | }); 142 | } else if (JOB_DONE_STATES[res.get('status').toUpperCase()]) { 143 | // 144 | // JobDetails.status matches a "done" 145 | // state that is not the state to be 146 | // verified, so break. 147 | // 148 | clearInterval(this.id); 149 | this.defer.resolve({ 150 | status: false, 151 | cause: 'status matches a "done" state ' + 152 | 'that is not the state to be verified.' 153 | }); 154 | //this.defer.reject('status matches a "done" state ' + 155 | // 'that is not the state to be verified.'); 156 | } else { /* continue job status checks */ } 157 | } 158 | } else { 159 | clearInterval(this.id); 160 | this.defer.resolve({ 161 | status: false, 162 | cause: 'timeout after 2 minutes.' 163 | }); 164 | } 165 | } 166 | }; 167 | 168 | return statusCheck.start(); 169 | } 170 | }, require('./common')); 171 | -------------------------------------------------------------------------------- /examples/tutorial/background/background-basics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Example using a Background Task Broker 8 | 9 | 10 | 11 | 12 | 13 |

Example using a Background Task Broker

14 |
15 |
    16 |
  1. Populate /examples/config.json with the proper values before running.
  2. 17 |
  3. Open the browser's debug window to view print logs for this example.
  4. 18 |
  5. View documentation for more information.
  6. 19 |
20 | 21 | 151 | 152 | -------------------------------------------------------------------------------- /lib/engine/pooled-task-broker.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var deployr = require('deployr'); 13 | 14 | var D = require('d.js'), 15 | merge = require('merge'), 16 | RBrokerEngine = require('./rbroker-engine'), 17 | PooledTaskWorker = require('../worker/pooled-task-worker'); 18 | 19 | /** 20 | * The Pooled Task Runtime acquires a dedicated pool of DeployR grid resources 21 | * at startup. 22 | * 23 | * @module pooled-task-broker 24 | * @for rbroker 25 | */ 26 | module.exports = RBrokerEngine.extend({ 27 | /** 28 | * The Pooled Task Runtime acquires a dedicated pool of DeployR grid resources 29 | * at startup. 30 | * 31 | * @class 32 | * @constructor 33 | * @param {Object} [options] Configuration options. 34 | */ 35 | initialize: function (config) { 36 | RBrokerEngine.initialize.call(this, config); 37 | 38 | if (!config.credentials) { 39 | throw new Error('Broker failed to initialize, user credentials required.'); 40 | } 41 | 42 | var self = this, 43 | poolSize = this.parallelTaskLimit, 44 | pool = merge({ poolsize: poolSize }, config.pool || {}); 45 | 46 | /* 47 | * Prevents authenticated HTTP session from timing out due to inactivity to 48 | * ensure pool of RProject remain live and available to PooledTaskBroker. 49 | */ 50 | this.httpKeepAlive = { 51 | id: null, 52 | PING_INTERVAL: 60000, 53 | 54 | start: function(ruser) { 55 | this.stop(); 56 | this.id = setInterval(function() { 57 | deployr.io('/r/user/about').share(ruser.getCookies()).end(); 58 | }, this.PING_INTERVAL); 59 | }, 60 | 61 | stop: function() { 62 | clearInterval(this.id); 63 | } 64 | }; 65 | 66 | /* 67 | * Initialize the resourceTokenPool with RProject. 68 | */ 69 | function load(res) { 70 | var projects = res.get('projects'); 71 | 72 | if (projects.length > 0) { 73 | // inform caller of any `Grid Notification` errors usch as: 74 | // 'Concurrent authenticated project limit (10) reached for user XYZ.' 75 | // These 'errors' are more like warnings than runtime throwable errors. 76 | var warn = res.get('error'); 77 | if (warn) { self.emit('warning', warn); } 78 | 79 | projects.forEach(function(project) { 80 | self.resourceTokenPool.offer(project.project); 81 | }); 82 | 83 | self.parallelTaskLimit = self.resourceTokenPool.size(); 84 | self.httpKeepAlive.start(self.ruser); 85 | self.emit('ready'); 86 | } else { // No projects were created (projects.length == 0) 87 | self.emit('error', res.get('error')); 88 | } 89 | } 90 | 91 | // 92 | // Validate DeployR server `endpoint` and authenticate. 93 | // 94 | this.ruser = this.validateEndpoint().io('/r/user/login') 95 | .data(config.credentials) 96 | .ctx(this) 97 | .end(function() { 98 | // 99 | // Build the project pool. 100 | // 101 | if (config.releaseGridResources) { 102 | this.ruser.io('/r/user/release') 103 | .end() 104 | .io('/r/project/pool') 105 | .data(pool) 106 | .end(load); 107 | } else { 108 | this.ruser.io('/r/project/pool').data(pool).end(load); 109 | } 110 | }); 111 | }, 112 | 113 | /** 114 | * Refresh the configuration for `PooledTaskRBroker`. 115 | * 116 | * A refresh causes all workspace objects and directory files in the 117 | * underlying R sessions within the pool to be cleared before new workspace 118 | * objects and/or directory files are loaded per the new config options. 119 | * 120 | * Only an idle RBroker instance can be refreshed. 121 | * 122 | * @method refresh 123 | * @param {Object} Startup options for a `PooledTaskRBroker`. 124 | * @override 125 | * @return {Promise} A promise wrapping the resolution of either "resolve" or 126 | * "reject" callback. 127 | * @api public 128 | */ 129 | refresh: function (config) { 130 | var calls = [], 131 | data = {}, 132 | ruser = this.ruser; 133 | 134 | if(!this.isIdle()) { 135 | //var defer = when.defer(); 136 | //defer.reject(new Error('RBroker is not idle, refresh not permitted.')); 137 | //return defer.promise; 138 | return D.rejected(new Error('RBroker is not idle, refresh not permitted.')); 139 | } 140 | 141 | // assert only the proper parameters are on the call 142 | config = config || {}; 143 | data = { 144 | code: '# Refresh project on PooledTaskBroker.', 145 | preloadfilename: config.preloadfilename, 146 | preloaddirectory: config.preloaddirectory, 147 | preloadfileauthor: config.preloadfileauthor, 148 | preloadfileversion: config.preloadfileversion 149 | }; 150 | 151 | // exe will fulfill only once all the inputs have fulfilled 152 | this.resourceTokenPool.q.forEach(function(project) { 153 | calls.push( 154 | deployr.io('/r/project/recycle') 155 | .share(ruser.getCookies()) 156 | .delay() 157 | .data( { project: project }) 158 | .end() 159 | .io('/r/project/execute/code') 160 | .share(ruser.getCookies()) 161 | .delay() 162 | .data(merge(data, { project: project })) 163 | .end() ); 164 | }); 165 | 166 | // execute a series of sequentially chained tasks in sequence batches 167 | // without overlap. Works in conjunction with .delay() + .end() or if no 168 | // .end() is used 169 | return deployr.pipeline(calls); 170 | }, 171 | 172 | /** 173 | * Release all client-side and server-side resources maintained by or on 174 | * behalf of an instance of `RBroker`. 175 | * 176 | * @method shutdown 177 | * @override 178 | * @return {Promise} A promise wrapping the resolution of either "resolve" or 179 | * "reject" callback. 180 | * @api public 181 | */ 182 | shutdown: function () { 183 | var projects = this.resourceTokenPool.q.slice(); 184 | 185 | this.resourceTokenPool.q = []; 186 | this.httpKeepAlive.stop(); 187 | this.flush(); 188 | 189 | return this.ruser.release(projects); 190 | }, 191 | 192 | /** 193 | * @override 194 | * @api private 195 | */ 196 | createWorker: function (task) { 197 | return PooledTaskWorker.new(task, this.ruser); 198 | } 199 | }); 200 | -------------------------------------------------------------------------------- /examples/tutorial/pooled/pooled-basics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Example using a Pooled Task Broker 8 | 9 | 10 | 11 | 12 | 13 |

Example using a Pooled Task Broker

14 |
15 |
    16 |
  1. Populate /examples/config.json with the proper values before running.
  2. 17 |
  3. Open the browser's debug window to view print logs for this example.
  4. 18 |
  5. View documentation for more information.
  6. 19 |
20 | 21 | 174 | 175 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JavaScript RBroker Framework for DeployR 2 | ======================================== 3 | 4 | The JavaScript RBroker Framework provides a simple yet powerful API that 5 | supports the rapid integration of R Analytics inside any browser or Node.js 6 | based application. Simply define an _RTask_, submit your task to an instance of 7 | _RBroker_ and then retrieve your task results. It really is that simple. 8 | 9 | Links 10 | ----- 11 | 12 | * [Download](#downloading) 13 | * [Tutorial](https://msdn.microsoft.com/en-us/microsoft-r/deployr-rbroker-framework) 14 | * [API Documentation](http://microsoft.github.io/js-rbroker-framework/) 15 | * [Simple examples](#examples) 16 | * [Gulp, for building](#building) 17 | * [Tests](#tests) 18 | * [License](#license) 19 | 20 | Downloading 21 | ============ 22 | 23 | There are a few diffrent ways to obtain the library depending on your 24 | environment and intended usage: 25 | 26 | - `$ npm install rbroker` dependent on [Node.js](http://nodejs.org/download/) 27 | - `$ git clone https://github.com/microsoft/js-rbroker-framework.git` 28 | - [Download Zip](https://github.com/microsoft/js-rbroker-framework/archive/master.zip) 29 | - Download specific [release](https://github.com/microsoft/js-rbroker-framework/releases) 30 | 31 | Environments 32 | ============ 33 | 34 | - Browser and Node.js 35 | 36 | We recommend you [download and install](http://nodejs.org/download/) Node.js. 37 | It is __not__ a requirement for the browser however using the JavaScript RBroker 38 | Framework from within a server or from the command line in addition to the 39 | browser can be powerful! 40 | 41 | ### Browser 42 | 43 | If your environment is the browser the JavaScript RBroker Framework can be found 44 | here: 45 | 46 | ``` 47 | ./rbroker/browser/rbroker.js 48 | ./rbroker/browser/rbroker.min.js 49 | ``` 50 | 51 | ### Node.js 52 | 53 | If your environment is Node then the entire root ```./rbroker/``` directory 54 | represents the JavaScript RBroker Framework as it uses the same source for both 55 | environments. 56 | 57 | Installing 58 | ========== 59 | 60 | ### Browser 61 | 62 | Using the browser version: 63 | 64 | ``` 65 | ./rbroker/browser/rbroker.js 66 | ./rbroker/browser/rbroker.min.js 67 | ``` 68 | 69 | Include either one on your page in the ` 74 | 75 | Or 76 | 77 | 78 | 79 | ``` 80 | 81 | ### Node.js 82 | 83 | 1. [Download and install](http://nodejs.org/download/) Node.js, which includes 84 | npm. npm, which stands for _node packaged modules_, is a way to manage 85 | development dependencies through Node.js. 86 | 87 | 2. `$npm install rbroker` 88 | 89 | 3. `require` the directory: 90 | 91 | ```js 92 | var rbroker = require('rbroker'); 93 | ``` 94 | 95 | Examples 96 | ======== 97 | 98 | The JavaScript RBroker Framework ships with a set of small examples under the 99 | __./rbroker/examples__ directory that run in both the browser and Node.js 100 | environments. The intention of the examples are to demonstrate the syntax and 101 | core areas of the JavaScript API. They are not intended to be a tutorial on how 102 | to write web applications. 103 | 104 | We encourage you to start here and customize these examples and adapt them to 105 | suit your needs as you explore the API. 106 | 107 | __./examples/tutorial:__ Introduces the three RBroker runtimes available. 108 | These runtimes are identified as: 109 | 110 | 1. Discrete Task Runtime 111 | 2. Pooled Task Runtime 112 | 3. Background Task Runtime 113 | 114 | R Analytics File Dependencies 115 | ----------------------------- 116 | 117 | The R scripts and data models used by these example applications are bundled by 118 | default within the DeployR repository within the tutorial-rbroker directory 119 | owned by testuser. 120 | 121 | However, if for any reason your DeployR repository does not contain 122 | these fiels you can add them using the DeployR Repository Manager as 123 | follows: 124 | 125 | 1. Log in as testuser into the Repository Manager 126 | 2. Create a new directory called tutorial-rbroker 127 | 3. Upload each of the files found in the `./examples/analytics` directory 128 | 4. Set the access control on 5SecondNoOp.R to Public. 129 | 130 | ### Running 131 | 132 | __Browser:__ 133 | 134 | - Copy the _.html_ files under `./examples` to your webserver 135 | - Copy the `./examples/config.json` file under `./examples` to your webserver 136 | - Set the DeployR endpoint and basic authentication credentials in 137 | `./examples/config.json` 138 | 139 | ***NOTE:*** The `testuser` user must first be enabled in DeployR and a password 140 | set. 141 | 142 | ```json 143 | { 144 | "endpoint": "http://dhost:port", 145 | "credentials": { 146 | "username": "testuser", 147 | "password": "YOUR_TESTUSER_PASSWORD" 148 | } 149 | } 150 | ``` 151 | 152 | - Open your browser and select an example `.html` file to run. All examples 153 | simply print to stdout viewable from your browser debug console. 154 | 155 | Alternatively, you can run the examples as is without moving them via the 156 | embedded web server if you have [Node.js](http://nodejs.org/download/) installed: 157 | 158 | `$ npm install rbroker` 159 | 160 | `$ cd ./rbroker` 161 | 162 | `$ npm install` 163 | 164 | `$ npm start` 165 | 166 | Open your browser to _http://localhost:8080_ and select a example 167 | `.html` file to run. All examples simply print to stdout viewable from your 168 | browser debug console. 169 | 170 | __Node.js:__ 171 | 172 | Set the DeployR endpoint and basic authentication credentials in 173 | `./examples/config.json` 174 | 175 | ***NOTE:*** The `testuser` user must first be enabled in DeployR and a password 176 | set. 177 | 178 | ```json 179 | { 180 | "endpoint": "http://dhost:port", 181 | "credentials": { 182 | "username": "testuser", 183 | "password": "YOUR_TESTUSER_PASSWORD" 184 | } 185 | } 186 | 187 | ``` 188 | 189 | From the command line run one of the Node.js examples: 190 | 191 | `$ node ./examples/PATH_TO_EXAMPLE_FILE.js` 192 | 193 | Building 194 | ======== 195 | 196 | This section only pertains to the _Browser_ environment. 197 | 198 | Our dev and release builds are handled by [gulp.js](http://gulpjs.com/). 199 | 200 | ### Installation 201 | 202 | 1. [Download and install](http://nodejs.org/download/) Node.js 203 | 2. `$ npm install rbroker` 204 | 3. `$ cd ./rbroker` 205 | 4. `$ npm install` This will install the development tools needed to build locally. 206 | 207 | ### Targets 208 | 209 | * `$ npm run build` - Runs a build. 210 | * `$ npm start` - Runs a build and starts a local webserver with LiveReload 211 | on `http://localhost:8080` rebuilding on file changes. 212 | 213 | ### Destination 214 | 215 | The browser build destination is located in the __./browser__ directory. 216 | 217 | Tests 218 | ===== 219 | 220 | The DeployR JavaScript RBroker Framework also ships with a set of unit tests. 221 | See [here](https://github.com/microsoft/js-rbroker-framework/tree/master/test) for 222 | details. 223 | 224 | License 225 | ======= 226 | 227 | Copyright (C) 2010-2016, Microsoft Corporation 228 | 229 | This program is licensed to you under the terms of Version 2.0 of the 230 | Apache License. This program is distributed WITHOUT 231 | ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 232 | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 233 | Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 234 | details. -------------------------------------------------------------------------------- /rbroker.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var deployr = require('deployr'), 13 | DiscreteTask = require('./lib/task/discreate-task'), 14 | PooledTask = require('./lib/task/pooled-task'), 15 | BackgroundTask = require('./lib/task/background-task'), 16 | DiscreteTaskBroker = require('./lib/engine/discrete-task-broker'), 17 | PooledTaskBroker = require('./lib/engine/pooled-task-broker'), 18 | BackgroundTaskBroker = require('./lib/engine/background-task-broker'); 19 | 20 | // -- expose `deployr` into the global namespace for browser convenience -- 21 | if (typeof window !== 'undefined' && !window.deployr) { 22 | window.deployr = deployr; 23 | } 24 | 25 | /** 26 | * The `rbroker` global namespace object. This is the public interface for the 27 | * DeployR `RBroker`. It is used as a factory to simplify the creation of new 28 | * instances of: 29 | * 30 | * - DiscreteTaskBroker 31 | * - PooledTaskBroker 32 | * - BackgroundTaskBroker 33 | * - DiscreteTask 34 | * - PooledTask 35 | * - BackgroundTask 36 | * 37 | * @module rbroker 38 | * @for rbroker 39 | */ 40 | module.exports = { 41 | /** 42 | * Expose `deployr` via the `rbroker` for convenience. 43 | * 44 | * @property deployr 45 | * @static 46 | * @api public 47 | */ 48 | deployr: deployr, 49 | 50 | /** 51 | * Defines the factory for creating a DeployR-specific encoded R object to be 52 | * sent as input parameters to an R script. 53 | * 54 | * Example: 55 | * ``` 56 | * var rinput = rbroker.RInput.logical('logical_name', true); 57 | * var rinput = rbroker.RInput.numeric('numeric_name', 10.5); 58 | * var rinput = rboker.RInput.integer('integer_name', 5); 59 | * var rinput = rboker.RInput.character('character_name', 'Hello'); 60 | * // ect... 61 | * ``` 62 | * @property RInput 63 | * @static 64 | * @api public 65 | */ 66 | RInput: deployr.RInput, 67 | 68 | /** 69 | * Create an instance of an `DiscreteTaskBroker` to manage the execution of 70 | * a `DiscreteTask`. 71 | * 72 | * @method discreteTaskBroker 73 | * @static 74 | * @param {Object} [options] Configuration options. 75 | * @return {RBroker} A new instance of a `DiscreteTaskBroker`. 76 | * @api public 77 | */ 78 | discreteTaskBroker: function(config) { 79 | return DiscreteTaskBroker.new(config); 80 | }, 81 | 82 | /** 83 | * Create an instance of an `PooledTaskBroker` to manage the execution of 84 | * a `PooledTask`. 85 | * 86 | * @method pooledTaskBroker 87 | * @static 88 | * @param {Object} [options] Configuration options. 89 | * @return {RBroker} A new instance of a `PooledTaskBroker`. 90 | * @api public 91 | */ 92 | pooledTaskBroker: function(config) { 93 | return PooledTaskBroker.new(config); 94 | }, 95 | 96 | /** 97 | * Create an instance of an `BackgroundTaskBroker` to manage the execution of 98 | * a `BackgroundTask`. 99 | * 100 | * @method backgroundTaskBroker 101 | * @static 102 | * @param {Object} [options] Configuration options. 103 | * @return {RBroker} A new instance of a `BackgroundTaskBroker`. 104 | * @api public 105 | */ 106 | backgroundTaskBroker: function(config) { 107 | return BackgroundTaskBroker.new(config); 108 | }, 109 | 110 | /** 111 | * Create an instance of a `DiscreteTask` for an analytics Web service based 112 | * on either: 113 | * 114 | * 1. Repository-managed R script 115 | * 116 | * ``` 117 | * var rtask = rbroker.discreteTask({ 118 | * filename: 'regression', 119 | * directory: 'demo', 120 | * author: 'george', 121 | * version: version 122 | * // Additional Discrete Task Options... 123 | * }); 124 | * ``` 125 | * 126 | * 2. URL-addressable R script 127 | * 128 | * ``` 129 | * var rtask = rbroker.discreteTask( { 130 | * externalsource: regressionURL 131 | * // Additional Discrete Task Options... 132 | * }); 133 | * ``` 134 | * 135 | * @method discreteTask 136 | * @static 137 | * @param {Object} [options] Configuration options. 138 | * @return {RTask} A new instance of a `DiscreteTask`. 139 | * @api public 140 | */ 141 | discreteTask: function(config) { 142 | return DiscreteTask.new(config); 143 | }, 144 | 145 | /** 146 | * Create an instance of a `PooledTask` for an analytics Web service based 147 | * on either: 148 | * 149 | * 1. Repository-managed R script 150 | * 151 | * ``` 152 | * var rtask = rbroker.pooledTask({ 153 | * filename: 'regression', 154 | * directory: 'demo', 155 | * author: 'george', 156 | * version: version 157 | * // Additional Pooled Task Options... 158 | * }); 159 | * ``` 160 | * 161 | * 2. Arbitrary block of R code 162 | * 163 | * ``` 164 | * var rtask = rbroker.pooledTask({ 165 | * code: codeBlock 166 | * // Additional Pooled Task Options... 167 | * }); 168 | * ``` 169 | * 170 | * 3. URL-addressable R script 171 | * 172 | * ``` 173 | * var rtask = rbroker.pooledTask( { 174 | * externalsource: regressionURL 175 | * // Additional Pooled Task Options... 176 | * }); 177 | * ``` 178 | * 179 | * @method pooledTask 180 | * @static 181 | * @param {Object} [options] Configuration options. 182 | * @return {RTask} A new instance of a `PooledTask`. 183 | * @api public 184 | */ 185 | pooledTask: function(config) { 186 | return PooledTask.new(config); 187 | }, 188 | 189 | /** 190 | * Create an instance of a `BackgroundTask` for an analytics Web service 191 | * based on either: 192 | * 193 | * 1. Repository-managed R script 194 | * 195 | * ``` 196 | * var rTask = rbroker.backgroundTask({ 197 | * name: 'Sample Task', 198 | * descr: 'Sample description', 199 | * rscriptname: 'regression', 200 | * rscriptdirectory: 'demo', 201 | * rscriptauthor: 'george', 202 | * rscriptversion: version 203 | * // Additional Background Task Options... 204 | * }); 205 | * ``` 206 | * 207 | * 2. Arbitrary block of R code 208 | * 209 | * ``` 210 | * var rtask = rbroker.backgroundTask({ 211 | * name: 'Sample Task', 212 | * descr: 'Sample description', 213 | * code: codeBlock 214 | * // Additional Background Task Options... 215 | * }); 216 | * ``` 217 | * 218 | * 3. URL-addressable R script 219 | * 220 | * ``` 221 | * var rtask = rbroker.backgroundTask( { 222 | * name: 'Sample Task', 223 | * descr: 'Sample description', 224 | * externalsource: regressionURL 225 | * // Additional Background Task Options... 226 | * }); 227 | * ``` 228 | * 229 | * @method backgroundTask 230 | * @static 231 | * @param {Object} [options] Configuration options. 232 | * @return {RTask} A new instance of an `BackgroundTask`. 233 | * @api public 234 | */ 235 | backgroundTask: function(config) { 236 | return BackgroundTask.new(config); 237 | } 238 | }; 239 | -------------------------------------------------------------------------------- /examples/tutorial/discrete/discrete-basics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Example using a Discrete Task Broker 8 | 9 | 10 | 11 | 12 | 13 |

Example using a Discrete Task Broker

14 |
15 |
    16 |
  1. Populate /examples/config.json with the proper values before running.
  2. 17 |
  3. Open the browser's debug window to view print logs for this example.
  4. 18 |
  5. View documentation for more information.
  6. 19 |
20 | 21 | 169 | 170 | -------------------------------------------------------------------------------- /gulp/tasks/header.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var gulp = require('gulp'), 13 | header = require('gulp-header'), 14 | config = require('../config.js'), 15 | pkg = config.pkg, 16 | banner = [ 17 | '/*!', 18 | ' * `<%= pkg.name %>` JavaScript Client Library v<%= pkg.version %>', 19 | ' * <%= pkg.homepage %>', 20 | ' *', 21 | ' * Copyright (C) 2010-2016, Microsoft Corporation', 22 | ' * Released under the Apache License 2.0', 23 | ' * http://www.apache.org/licenses/LICENSE-2.0', 24 | ' *', 25 | ' * Includes:', 26 | ' * - superagent: https://github.com/visionmedia/superagent', 27 | ' * - ws: https://github.com/einaros/ws', 28 | ' * - D.js: http://malko.github.io/D.js', 29 | ' * - yui-lang.js: https://github.com/yui/yui3 (DeployR port)', 30 | ' *', 31 | ' * superagent', 32 | ' *', 33 | ' * Copyright (c) 2014-2015 TJ Holowaychuk ', 34 | ' * Open Source Initiative OSI - The MIT License', 35 | ' *', 36 | ' * Permission is hereby granted, free of charge, to any person obtaining', 37 | ' * a copy of this software and associated documentation files (the,', 38 | ' * "Software"), to deal in the Software without restriction, including', 39 | ' * without limitation the rights to use, copy, modify, merge, publish,', 40 | ' * distribute, sublicense, and/or sell copies of the Software, and to', 41 | ' * permit persons to whom the Software is furnished to do so, subject to', 42 | ' * the following conditions:', 43 | ' *', 44 | ' * The above copyright notice and this permission notice shall be', 45 | ' * included in all copies or substantial portions of the Software.', 46 | ' *', 47 | ' * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND', 48 | ' * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF', 49 | ' * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND', 50 | ' * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE', 51 | ' * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION', 52 | ' * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION', 53 | ' * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.', 54 | ' *', 55 | ' * ws', 56 | ' *', 57 | ' * Copyright (c) 2011 Einar Otto Stangvik ', 58 | ' * Open Source Initiative OSI - The MIT License', 59 | ' *', 60 | ' * Permission is hereby granted, free of charge, to any person obtaining ', 61 | ' * a copy of this software and associated documentation files ', 62 | ' * (the "Software"), to deal in the Software without restriction, ', 63 | ' * including without limitation the rights to use, copy, modify, merge', 64 | ' * publish, distribute, sublicense, and/or sell copies of the Software, ', 65 | ' * and to permit persons to whom the Software is furnished to do so, ', 66 | ' * subject to the following conditions:', 67 | ' *', 68 | ' * The above copyright notice and this permission notice shall be ', 69 | ' * included in all copies or substantial portions of the Software.', 70 | ' *', 71 | ' * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ', 72 | ' * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ', 73 | ' * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.', 74 | ' * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY ', 75 | ' * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, ', 76 | ' * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE ', 77 | ' * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.', 78 | ' *', 79 | ' * D', 80 | ' *', 81 | ' * Copyright (C) 2013 Jonathan Gotti ', 82 | ' * Open Source Initiative OSI - The MIT License', 83 | ' *', 84 | ' * Permission is hereby granted, free of charge, to any person obtaining', 85 | ' * a copy of this software and associated documentation files (the,', 86 | ' * "Software"), to deal in the Software without restriction, including', 87 | ' * without limitation the rights to use, copy, modify, merge, publish,', 88 | ' * distribute, sublicense, and/or sell copies of the Software, and to', 89 | ' * permit persons to whom the Software is furnished to do so, subject to', 90 | ' * the following conditions:', 91 | ' *', 92 | ' * The above copyright notice and this permission notice shall be', 93 | ' * included in all copies or substantial portions of the Software.', 94 | ' *', 95 | ' * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND', 96 | ' * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF', 97 | ' * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND', 98 | ' * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE', 99 | ' * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION', 100 | ' * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION', 101 | ' * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.', 102 | ' *', 103 | ' * selfish', 104 | ' *', 105 | ' * Copyright 2011 Irakli Gozalishvili. All rights reserved', 106 | ' * Open Source Initiative OSI - The MIT License', 107 | ' *', 108 | ' * Permission is hereby granted, free of charge, to any person obtaining', 109 | ' * a copy of this software and associated documentation files (the,', 110 | ' * "Software"), to deal in the Software without restriction, including', 111 | ' * without limitation the rights to use, copy, modify, merge, publish,', 112 | ' * distribute, sublicense, and/or sell copies of the Software, and to', 113 | ' * permit persons to whom the Software is furnished to do so, subject to', 114 | ' * the following conditions:', 115 | ' *', 116 | ' * The above copyright notice and this permission notice shall be', 117 | ' * included in all copies or substantial portions of the Software.', 118 | ' *', 119 | ' * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND', 120 | ' * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF', 121 | ' * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND', 122 | ' * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE', 123 | ' * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION', 124 | ' * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION', 125 | ' * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.', 126 | ' *', 127 | ' * yui-lang', 128 | ' *', 129 | ' * The lang component is a DeployR port of yui-lang.js by Yahoo!', 130 | ' *', 131 | ' * Software License Agreement (BSD License)', 132 | ' * Copyright (c) 2013, Yahoo! Inc. All rights reserved.', 133 | ' *', 134 | ' * Redistribution and use of this software in source and binary forms, ', 135 | ' * with or without modification, are permitted provided that the ', 136 | ' * following conditions are met:', 137 | ' *', 138 | ' * Redistributions of source code must retain the above copyright notice,', 139 | ' * this list of conditions and the following disclaimer. Redistributions', 140 | ' * in binary form must reproduce the above copyright notice, this list of', 141 | ' * conditions and the following disclaimer in the documentation and/or ', 142 | ' * other materials provided with the distribution.', 143 | ' * ', 144 | ' * Neither the name of Yahoo! Inc. nor the names of YUI\'s contributors ', 145 | ' * may be used to endorse or promote products derived from this software ', 146 | ' * without specific prior written permission of Yahoo! Inc.', 147 | ' * ', 148 | ' * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS', 149 | ' * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ', 150 | ' * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ', 151 | ' * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ', 152 | ' * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ', 153 | ' * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ', 154 | ' * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ', 155 | ' * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ', 156 | ' * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT, ', 157 | ' * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ', 158 | ' * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.', 159 | ' *', 160 | ' * Date: <%= date.today %>', 161 | ' */', 162 | ''].join('\n'); 163 | 164 | function today() { 165 | var today = new Date(), 166 | dd = today.getDate(), 167 | mm = today.getMonth() + 1, // January is 0 168 | yyyy = today.getFullYear(); 169 | 170 | if(dd < 10) { dd = '0' + dd; } 171 | if(mm < 10) { mm = '0' + mm; } 172 | 173 | return yyyy + '-' + mm + '-' + dd; 174 | } 175 | 176 | /* 177 | * Task: header 178 | * 179 | * Prefix the copyright information at the top. 180 | */ 181 | gulp.task('header', ['uglify'], function() { 182 | return gulp.src(config.dist + '/*.js') 183 | .pipe(header(banner, { pkg : config.pkg, date: { today: today() } } )) 184 | .pipe(gulp.dest(config.dist)); 185 | }); -------------------------------------------------------------------------------- /lib/engine/rbroker-engine.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | var deployr = require('deployr'), 12 | merge = require('merge'), 13 | Base = require('selfish').Base, 14 | EventEmitter = require('deployr/lib/emitter'), 15 | RTaskToken = require('../rtask-token'), 16 | RTaskQueue = require('../util/rtask-queue'), 17 | MAX_TASK_QUEUE_SIZE = 999; 18 | 19 | /** 20 | * Represents a high-level programming model for building DeployR-enabled 21 | * client applications. By using RBroker an application developer can focus 22 | * entirely on integrating R Analytics, while offloading the complexity of 23 | * managing client-side API task queues and server-side R session lifecycles. 24 | * 25 | * The basic programming model for working with RBroker is as follows: 26 | * 27 | * 1. Decide if the R Analytics tasks for your application should execute as: 28 | * - Discrete tasks: authentication optional, grid resources allocated at 29 | * runtime, results returned immediately, no persistence. Good for 30 | * prototyping and public facing production deployments. 31 | * - Pooled tasks: authentication required, grid resources pre-allocated, 32 | * results returned immediately, optional persistence to repository. Good 33 | * for enterprise production deployments, consistent runtime, 34 | * high-throughput environments. 35 | * - Background tasks: authentication required, grid resources allocated at 36 | * runtime, results persisted for later retrieval. Good for periodic, 37 | * scheduled or batch processing. 38 | * 2. Use the factories defined in rbroker to create an appropriate instance of 39 | * `RBroke`r. 40 | * 3. Define the R Analytics tasks for your application as one or more `RTask`. 41 | * 4. Submit your `RTask` to `RBroker` for execution. 42 | * 5. Integrate the results of your `RTask` found within `.complete()` 43 | * 44 | * @module rbroker-engine 45 | * @for rbroker 46 | */ 47 | module.exports = Base.extend(EventEmitter, { 48 | 49 | /** 50 | * Represents a high-level programming model for building DeployR-enabled 51 | * client applications. 52 | * 53 | * @class 54 | * @constructor 55 | * @param {Object} [options] Configuration options. 56 | */ 57 | initialize: function (config) { 58 | EventEmitter.initialize.call(this); 59 | 60 | var self = this; 61 | this.config = config; 62 | this.scope = this.config.ctx || this; // EventEmitter context 63 | this.engineStarted = false; 64 | this.parallelTaskLimit = this.config.maxConcurrentTaskLimit || 1; 65 | this.pendingLow = RTaskQueue.new(MAX_TASK_QUEUE_SIZE); 66 | this.pendingHigh = RTaskQueue.new(MAX_TASK_QUEUE_SIZE); 67 | this.resourceTokenPool = RTaskQueue.new(MAX_TASK_QUEUE_SIZE); 68 | 69 | // 70 | // Configure `deployr` client with supplied runtime settings and catch 71 | // all `io` internal errors. 72 | // 73 | deployr.configure( { 74 | host: config.host, 75 | cors: config.cors, 76 | logging: config.logging, 77 | allowSelfSignedSSLCert: config.allowSelfSignedSSLCert, 78 | events: { 79 | error: function(api, res) { 80 | this.emit('error', res); 81 | }.bind(this) 82 | } 83 | }); 84 | 85 | /** 86 | * Correlate the running task counter and other runtime statistics. 87 | * 88 | * @private 89 | */ 90 | this.runtime = { 91 | runningTasks: 0, 92 | 93 | totals: { success: 0, failure: 0 }, 94 | 95 | stats: function(res) { 96 | return { 97 | maxConcurrency: self.maxConcurrency(), 98 | totalTasksRun: this.totals.success + this.totals.failure, 99 | totalTasksRunToSuccess: this.totals.success, 100 | totalTasksRunToFailure: this.totals.failure, 101 | totalTimeTasksOnCode: res.result.timeOnCode, 102 | totalTimeTasksOnServer: res.result.timeOnServer, 103 | totalTimeTasksOnCall: res.result.timeOnCall, 104 | task: res.task 105 | }; 106 | }, 107 | 108 | calibrate: function(direction) { 109 | this.runningTasks = this.runningTasks + direction; 110 | 111 | if (this.runningTasks < 0) { 112 | this.runningTasks = 0; 113 | } 114 | 115 | return this.runningTasks; 116 | } 117 | }; 118 | 119 | // -- emitted by subclasses indicating workers can begin work -- 120 | this.on('ready', function() { 121 | this.engineStarted = true; 122 | 123 | // Flush the queues to force work. 124 | for (var i = 0; i < this.parallelTaskLimit; i++) { 125 | if (!this.isIdle()) { 126 | this.run(); 127 | } else { 128 | break; 129 | } 130 | } 131 | }); 132 | }, 133 | 134 | /** 135 | * Test the `/r/server/info` endpoint, expect HTTP 200 othwrise 136 | * 137 | * @param {Function} The optional callback 138 | * @api public 139 | */ 140 | validateEndpoint: function(cb) { 141 | return deployr.io('/r/server/info') 142 | .ctx(this) 143 | .timeout(1000 * 10) 144 | .end(cb); 145 | }, 146 | 147 | owner: function() { 148 | return this.ruser; 149 | }, 150 | 151 | /** 152 | * Launch an `RTaskAppSimulator` simulation. The `RTask` defined by your 153 | * simulation will be automatically executed by the current instance of 154 | * `RBroker`. 155 | * 156 | * Make sure to register your `.complete()`, `.error()`, `.idle()`, and 157 | * `.progress()` listeners before starting your simulation in order to 158 | * receive asynchronous callbacks in your application when `RTask` complete 159 | * and/or to receive runtime summary statistics from `RBroker` as the 160 | * simulation proceeds. 161 | * 162 | * @method simulateApp 163 | * @param {Object} The Application Simulator that implements the 164 | * `simulateApp(RBroker)` method. 165 | * @api public 166 | */ 167 | simulateApp: function(appSimulator) { 168 | if (appSimulator) { 169 | if (typeof appSimulator.simulateApp === 'function') { 170 | appSimulator.simulateApp(this); 171 | } else { 172 | throw new Error('ReferenceError: "simulateApp" is not defined'); 173 | } 174 | } 175 | }, 176 | 177 | /** 178 | * Submit an `RTask` for execution under the control of `RBroker`. If 179 | * priority is indicated, priority tasks are automatically moved to the front 180 | * of the queue, ahead of all standard tasks that are already pending 181 | * execution by the broker. 182 | * 183 | * @method submit 184 | * @param {RTask} The `RTask` associated with this submission. 185 | * @param {Boolean} (optional) Mark this task as having priority. 186 | * @return {RTaskToken} The handle to an `RTask` live on an `RBroker`. 187 | * @api public 188 | */ 189 | submit: function(task, priority) { 190 | // `worker` is a defred object. It is either currently working or queued 191 | // up with an appointment to work in the future once `run()` is called 192 | var worker = this.createWorker(task), 193 | taskToken = RTaskToken.new(worker); 194 | 195 | if (priority) { 196 | this.pendingHigh.offer(worker); 197 | } else { 198 | this.pendingLow.offer(worker); 199 | } 200 | 201 | // start the task work 202 | this.run(); 203 | 204 | return taskToken; 205 | }, 206 | 207 | /** 208 | * Returns the task execution concurrency levels enforced for this instance 209 | * of `RBroker`. 210 | * 211 | * @method maxConcurrency 212 | * @return {Number} The task execution concurrency levels. 213 | * @api public 214 | */ 215 | maxConcurrency: function() { 216 | return this.parallelTaskLimit; 217 | }, 218 | 219 | /** 220 | * Flushes all pending `RTask` from queues maintained by `RBroker`. Flushing 221 | * `RTask` queues ensures that queued tasks will not be executed by 222 | * `RBroker`. 223 | * 224 | * @method flush 225 | * @return {Object} Object literal containing `RBroker` status indicating 226 | * the number of currently queued and executing `RTask`. 227 | * @api public 228 | */ 229 | flush: function() { 230 | this.pendingHigh.clear(); 231 | this.pendingLow.clear(); 232 | 233 | return this.status(); 234 | }, 235 | 236 | /** 237 | * Indicates if current `RBroker` instance is still connected to the DeployR 238 | * server. A connection may be lost for a number of reasons, for example, due 239 | * to a droppeed network connection between client and server or if the 240 | * DeployR server itself goes down. 241 | * 242 | * @method isConnected 243 | * @return {Promise} A promise with a `Boolean` value connection status. 244 | * @api public 245 | */ 246 | isConnected: function() { 247 | var test = this.ruser ? this.ruser.io('/r/user/about') : 248 | deployr.io('/r/server/info') 249 | 250 | // 251 | // (authenticated | anonymous) checks 252 | // authenticated - Test connection to authenticated HTTP session 253 | // anonymous - Test the that the server is up. 254 | // 255 | return test 256 | .global(false) // supress global error events for this `io` 257 | .promise() 258 | .success(function() { 259 | return true; 260 | }) 261 | .error(function() { 262 | return false; 263 | }); 264 | }, 265 | 266 | /** 267 | * Returns status indicating current `RTask` activity on `RBroker`. 268 | * 269 | * 270 | * @method status 271 | * @return {Object} Object literal containing `RBroker` status indicating the 272 | * number of currently queued and executing `RTask`. 273 | * @api public 274 | */ 275 | status: function() { 276 | // Pending tasks include all tasks on high and low priority queues. 277 | var pending = this.pendingHigh.size() + this.pendingLow.size(), 278 | executing = this.parallelTaskLimit - this.runtime.runningTasks, 279 | idle = this.isIdle(); 280 | 281 | return { 282 | pending: pending, 283 | executing: executing, 284 | idle: idle, 285 | toString: function() { 286 | return '\nRBrokerStatus: [ pending = "' + pending + '" ] ' + 287 | '[ executing = "' + executing + '" ] ' + 288 | '[ idle = "' + idle + '" ]\n'; 289 | } 290 | }; 291 | }, 292 | 293 | /** 294 | * Used to determine if an `RBroker` instance is idle which can be 295 | * particularly useful ahead of calls to `shutdown()`. Another options is to 296 | * bind a listener to the `.isIdle()` method. 297 | * 298 | * @method isIdle 299 | * @return {Boolean} If the `RBroker` instance is idle. 300 | * @api public 301 | */ 302 | isIdle: function() { 303 | return (this.runtime.runningTasks === 0 && 304 | this.pendingLow.isEmpty() && this.pendingHigh.isEmpty()); 305 | }, 306 | 307 | /** 308 | * An initialization lifecycle listener fired during construction. 309 | * 310 | * @method error 311 | * @param {Function} The callback function. 312 | * @return {RBroker} The refrence to `this` `RBroker` used for chaining. 313 | * @api public 314 | */ 315 | ready: function(fn) { 316 | this.on('ready', fn); 317 | 318 | return this; 319 | }, 320 | 321 | /** 322 | * A failure listener for all tasks submitted on behalf of `this` `RBroker`. 323 | * 324 | * @method error 325 | * @param {Function} The callback function. 326 | * @return {RBroker} The refrence to `this` `RBroker` used for chaining. 327 | * @api public 328 | */ 329 | error: function(fn) { 330 | this.on('error', fn); 331 | 332 | return this; 333 | }, 334 | 335 | /** 336 | * A notification listener indicating warnings about dubious runtime behavior 337 | * submitted on behalf of `this` `RBroker`. 338 | * 339 | * @method warning 340 | * @param {Function} The callback function. 341 | * @return {RBroker} The refrence to `this` `RBroker` used for chaining. 342 | * @api public 343 | */ 344 | warning: function(fn) { 345 | this.on('warning', fn); 346 | 347 | return this; 348 | }, 349 | 350 | /** 351 | * A completion listener for all tasks submitted on behalf of `this` 352 | * `RBroker`. 353 | * 354 | * @method complete 355 | * @param {Function} The callback function. 356 | * @return {RBroker} The refrence to `this` `RBroker` used for chaining. 357 | * @api public 358 | */ 359 | complete: function(fn) { 360 | this.on('complete', fn); 361 | 362 | return this; 363 | }, 364 | 365 | /** 366 | * A notification listener indicating that `this` `Rbroker` is still active 367 | * and there are currently no `RTasks` running or in the wait queue. 368 | * 369 | * @method idle 370 | * @param {Function} The callback function. 371 | * @return {RBroker} The refrence to `this` `RBroker` used for chaining. 372 | * @api public 373 | */ 374 | idle: function(fn) { 375 | this.on('idle', fn); 376 | 377 | return this; 378 | }, 379 | 380 | /** 381 | * A notification listener for `RBroker` runtime statistics of a `RTask`. 382 | * 383 | * @method progress 384 | * @param {Function} The callback function. 385 | * @return {RBroker} The refrence to `this` `RBroker` used for chaining. 386 | * @api public 387 | */ 388 | progress: function(fn) { 389 | this.on('progress', fn); 390 | 391 | return this; 392 | }, 393 | 394 | /** 395 | * A notification listener for `RBroker` runtime statistics of a `RTask`. 396 | * 397 | * @method progress 398 | * @param {Function} The callback function. 399 | * @return {RBroker} The refrence to `this` `RBroker` used for chaining. 400 | * @api public 401 | */ 402 | start: function(fn) { 403 | this.on('start', fn); 404 | 405 | return this; 406 | }, 407 | 408 | /** 409 | * Do the task work. 410 | * 411 | * @api private 412 | */ 413 | run: function() { 414 | var self = this, nextWorker, resourceToken; 415 | 416 | if (this.engineStarted && 417 | !this.resourceTokenPool.isEmpty() && 418 | (!this.pendingLow.isEmpty() || !this.pendingHigh.isEmpty())) { 419 | 420 | nextWorker = this.pendingHigh.take() || this.pendingLow.take(); 421 | 422 | this.runtime.calibrate(+1); 423 | 424 | this.emit('start', nextWorker.task); 425 | 426 | nextWorker.work(this.resourceTokenPool.take()) // start working.... 427 | .then(function(res) { 428 | self.runtime.totals.success++; 429 | self.emit('progress', self.runtime.stats(res)); 430 | self.emit('complete', res.task, res.result); 431 | 432 | // -- individual RTask notifications -- 433 | this.resolve(res); 434 | }.bind(nextWorker), function(err) { 435 | var res = { 436 | task: this.task, 437 | result: { // task error result 438 | id: null, 439 | type: this.task.type, 440 | success: false, 441 | timeOnCode: 0, 442 | timeOnServer: 0, 443 | timeOnCall: 0, 444 | failure: err 445 | } 446 | }; 447 | 448 | self.runtime.totals.failure++; 449 | self.emit('progress', self.runtime.stats(res)); 450 | self.emit('error', merge(err, { task: this.task })); 451 | self.emit('complete', res.task, res.result); 452 | 453 | // -- individual RTask notifications -- 454 | this.reject(merge(err, { task: this.task })); 455 | }.bind(nextWorker)) 456 | .ensure(function(v) { // finally 457 | self.runtime.calibrate(-1); 458 | self.resourceTokenPool.offer(this.resourceToken); 459 | self.run(); 460 | 461 | // -- notify all tasks submitted have came to completion -- 462 | if (self.isIdle()) { self.emit('idle'); } 463 | }.bind(nextWorker)); 464 | } 465 | } 466 | }); -------------------------------------------------------------------------------- /test/specs/discrete-task-broker-test.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var test = require('tape'), 13 | merge = require('merge'), 14 | rbroker = require('../../rbroker'), 15 | fixtures = require('../fixtures/discrete'); 16 | 17 | console.log('A DiscreteTaskBroker\n'); 18 | 19 | /** 20 | * Test "good" endpoint. 21 | */ 22 | test('should successfully initialize with good endpoint address', function(t) { 23 | t.plan(1); 24 | 25 | var dBroker, 26 | brokerConfig = { 27 | host: fixtures.endpoint, 28 | credentials: fixtures.credentials, 29 | logging: false 30 | }; 31 | 32 | dBroker = rbroker.discreteTaskBroker(brokerConfig) 33 | .error(function(err) { 34 | t.fail('dBroker initialization failed "' + err.get('error') + '"'); 35 | }) 36 | .ready(function() { 37 | t.pass('dBroker initialization success'); 38 | dBroker.shutdown().then(function() {}, function(err) { 39 | t.fail('dBroker.shutdown failed: ' + err.get('error')); 40 | }); 41 | }); 42 | }); 43 | 44 | /** 45 | * Test "bad" endpoint. 46 | */ 47 | test('should fail initialization with bad endpoint address', function(t) { 48 | t.plan(1); 49 | 50 | var dBroker, 51 | brokerConfig = { 52 | host: fixtures.BAD_ENDPOINT, 53 | credentials: fixtures.credentials, 54 | logging: false 55 | }; 56 | 57 | dBroker = rbroker.discreteTaskBroker(brokerConfig) 58 | .error(function(err) { 59 | t.pass('dBroker initialization failed "' + err.get('error') + '"'); 60 | }) 61 | .ready(function() { 62 | t.pass('dBroker initialization success but should have failed'); 63 | dBroker.shutdown().then(function() {}, function(err) { 64 | t.fail('dBroker.shutdown failed: ' + err.get('error')); 65 | }); 66 | }); 67 | }); 68 | 69 | /** 70 | * Test anonymous DiscreteBrokerfixtures. 71 | */ 72 | test('should successfully execute using an anonymous `Discrete Task Broker`', function(t) { 73 | t.plan(3); 74 | 75 | var dBroker, 76 | brokerConfig = { 77 | host: fixtures.endpoint, 78 | logging: false 79 | }; 80 | 81 | dBroker = rbroker.discreteTaskBroker(brokerConfig) 82 | .complete(function(rTask, rTaskResult) { 83 | t.notEqual(rTaskResult, null, 'rTaskResult'); 84 | t.ok(rTaskResult.success, 'rTaskResult.success'); 85 | }) 86 | .error(function(err) { 87 | var msg = err.task ? 'dBroker.submit(rTask) failed: ' : 88 | 'dBroker failed: '; 89 | 90 | t.fail(msg + err.get('error')); 91 | }) 92 | .idle(function() { 93 | dBroker.shutdown().then(function() { 94 | t.pass('dBroker.shutdown success'); 95 | }, function(err) { 96 | t.fail('dBroker.shutdown failed: ' + err.get('error')); 97 | }); 98 | }); 99 | 100 | dBroker.submit(rbroker.discreteTask({ 101 | filename: 'Histogram of Auto Sales', 102 | directory: 'root', 103 | author: 'testuser' 104 | })); 105 | }); 106 | 107 | /** 108 | * Test authenticated DiscreteBrokerfixtures. 109 | */ 110 | test('should successfully execute using authenticated `Discrete Task Broker`', function(t) { 111 | t.plan(3); 112 | 113 | var dBroker, 114 | brokerConfig = { 115 | host: fixtures.endpoint, 116 | credentials: fixtures.credentials, 117 | logging: false 118 | }; 119 | 120 | dBroker = rbroker.discreteTaskBroker(brokerConfig) 121 | .complete(function(rTask, rTaskResult) { 122 | t.notEqual(rTaskResult, null, 'rTaskResult'); 123 | t.ok(rTaskResult.success, 'rTaskResult.success'); 124 | }) 125 | .error(function(err) { 126 | var msg = err.task ? 'dBroker.submit(rTask) failed: ' : 127 | 'dBroker failed: '; 128 | 129 | t.notOk(true, msg + err.get('error')); 130 | }) 131 | .idle(function() { 132 | dBroker.shutdown().then(function() { 133 | t.pass('dBroker.shutdown success'); 134 | }, function(err) { 135 | t.fail('dBroker.shutdown failed: ' + err.get('error')); 136 | }); 137 | }); 138 | 139 | dBroker.submit(rbroker.discreteTask({ 140 | filename: 'Histogram of Auto Sales', 141 | directory: 'root', 142 | author: 'testuser' 143 | })); 144 | }); 145 | 146 | /** 147 | * Test authenticated DiscreteBrokerConfig using bad credentials. 148 | */ 149 | test('should not authenticate with bad credentials for `Discrete Task Broker`', function(t) { 150 | t.plan(1); 151 | 152 | var brokerConfig = { 153 | host: fixtures.endpoint, 154 | credentials: fixtures.BAD_CREDENTIALS, 155 | logging: false 156 | }, 157 | dBroker = rbroker.discreteTaskBroker(brokerConfig) 158 | .ready(function() { 159 | dBroker.shutdown().then(function() {}, function(err) { 160 | t.fail('dBroker.shutdown failed: ' + err.get('error')); 161 | }) 162 | .ensure(function() { 163 | t.fail('authentication successful but should not have been'); 164 | }); 165 | }) 166 | .error(function(err) { 167 | t.equal(err.get('errorCode'), 940, err.get('error')); 168 | }); 169 | }); 170 | 171 | /** 172 | * Test DiscreteBrokerConfig using non-default max concurrency. 173 | */ 174 | test('should successfully execute `Discrete Task Broker` using non-default max concurrency', function(t) { 175 | t.plan(3); 176 | 177 | var dBroker, 178 | MAX_CONCURRENCY = 10, 179 | brokerConfig = { 180 | host: fixtures.endpoint, 181 | credentials: fixtures.credentials, 182 | maxConcurrentTaskLimit: MAX_CONCURRENCY, 183 | logging: false 184 | }; 185 | 186 | dBroker = rbroker.discreteTaskBroker(brokerConfig) 187 | .complete(function(rTask, rTaskResult) { 188 | t.notEqual(rTaskResult, null, 'rTaskResult'); 189 | t.ok(rTaskResult.success, 'rTaskResult.success'); 190 | }) 191 | .error(function(err) { 192 | var msg = err.task ? 'dBroker.submit(rTask) failed: ' : 193 | 'dBroker failed: '; 194 | 195 | t.notOk(true, msg + err.get('error')); 196 | }) 197 | .idle(function() { 198 | dBroker.shutdown().then(function() { 199 | t.pass('dBroker.shutdown success'); 200 | }, function(err) { 201 | t.fail('dBroker.shutdown failed: ' + err.get('error')); 202 | }); 203 | }); 204 | 205 | dBroker.submit(rbroker.discreteTask({ 206 | filename: 'Histogram of Auto Sales', 207 | directory: 'root', 208 | author: 'testuser' 209 | })); 210 | }); 211 | 212 | /** 213 | * Test "good" RTask execution with execution-token. 214 | */ 215 | test('should successfully execute `RTask` using `execution-token`', function(t) { 216 | t.plan(4); 217 | 218 | var dBroker, rTask, 219 | executionToken = 'testTaskExecutionWithTokenStandardQueueGood', 220 | brokerConfig = { 221 | host: fixtures.endpoint, 222 | credentials: fixtures.credentials, 223 | logging: false 224 | }; 225 | 226 | dBroker = rbroker.discreteTaskBroker(brokerConfig) 227 | .complete(function(rTask, rTaskResult) { 228 | t.notEqual(rTaskResult, null, 'rTaskResult'); 229 | t.ok(rTaskResult.success, 'rTaskResult.success'); 230 | t.equal(rTask.token, executionToken, 'execution-token'); 231 | }) 232 | .error(function(err) { 233 | t.fail(err.task ? 'dBroker.submit(rTask) failed: ' : 234 | 'dBroker failed: '); 235 | }) 236 | .idle(function() { 237 | dBroker.shutdown().then(function() { 238 | t.pass('dBroker.shutdown success'); 239 | }, function(err) { 240 | t.fail('dBroker.shutdown failed: ' + err.get('error')); 241 | }); 242 | }); 243 | 244 | rTask = rbroker.discreteTask({ 245 | filename: 'Histogram of Auto Sales', 246 | directory: 'root', 247 | author: 'testuser' 248 | }); 249 | 250 | rTask.token = executionToken; 251 | 252 | dBroker.submit(rTask); 253 | }); 254 | 255 | /** 256 | * Test "good" RTask priority execution with execution-token. 257 | */ 258 | test('should successfully execute priority `RTask` using `execution-token`', function(t) { 259 | t.plan(4); 260 | 261 | var dBroker, rTask, 262 | executionToken = 'testTaskExecutionWithTokenPriorityQueueGood', 263 | brokerConfig = { 264 | host: fixtures.endpoint, 265 | credentials: fixtures.credentials, 266 | logging: false 267 | }; 268 | 269 | dBroker = rbroker.discreteTaskBroker(brokerConfig) 270 | .complete(function(rTask, rTaskResult) { 271 | t.notEqual(rTaskResult, null, 'rTaskResult'); 272 | t.ok(rTaskResult.success, 'rTaskResult.success'); 273 | t.equal(rTask.token, executionToken, 'execution-token'); 274 | }) 275 | .error(function(err) { 276 | t.fail(err.task ? 'dBroker.submit(rTask) failed: ' : 277 | 'dBroker failed: '); 278 | }) 279 | .idle(function() { 280 | dBroker.shutdown().then(function() { 281 | t.pass('dBroker.shutdown success'); 282 | }, function(err) { 283 | t.fail('dBroker.shutdown failed: ' + err.get('error')); 284 | }); 285 | }); 286 | 287 | rTask = rbroker.discreteTask({ 288 | filename: 'Histogram of Auto Sales', 289 | directory: 'root', 290 | author: 'testuser' 291 | }); 292 | 293 | rTask.token = executionToken; 294 | 295 | dBroker.submit(rTask, true); 296 | }); 297 | 298 | /** 299 | * Test multiple "good" RTask executions distributed across the standard and 300 | * priority task queues. 301 | */ 302 | 303 | test('should execute multiple "good" RTask executions distributed across standard and priority task queues', function(t) { 304 | var dBroker, 305 | executionToken = 'testMultipleTaskExecutionWithMixedPriority', 306 | rTasks = [], 307 | priorityTaskTokens = [], 308 | standardTaskTokens = [], 309 | rTaskResults = [], 310 | priorityTask = true, 311 | multipleTaskTestSize = 10, 312 | brokerConfig = { 313 | host: fixtures.endpoint, 314 | credentials: fixtures.credentials, 315 | maxConcurrentTaskLimit: 10, 316 | logging: false 317 | }; 318 | 319 | t.plan(4 + multipleTaskTestSize); 320 | 321 | dBroker = rbroker.discreteTaskBroker(brokerConfig) 322 | .complete(function(rTask, rTaskResult) { 323 | rTaskResults.push(rTaskResult); 324 | }) 325 | .error(function(err) { 326 | var msg = err.task ? 'dBroker.submit(rTask) failed: ' : 327 | 'dBroker failed: '; 328 | t.fail(msg + err.get('error')); 329 | }) 330 | .idle(function() { 331 | t.equal(rTasks.length, multipleTaskTestSize, 332 | 'rTasks and multipleTaskTestSize are the same size'); 333 | t.equal(standardTaskTokens.length + priorityTaskTokens.length, 334 | multipleTaskTestSize, 'standardTaskTokens and ' + 335 | 'priorityTaskTokens are the same size'); 336 | t.equal(rTaskResults.length, multipleTaskTestSize, 337 | 'rTaskResults and multipleTaskTestSize are the same size'); 338 | rTaskResults.forEach(function(result) { 339 | t.ok(result.success, 'rTask ran to success'); 340 | }); 341 | 342 | dBroker.shutdown().then(function() { 343 | t.pass('dBroker.shutdown success'); 344 | }, function(err) { 345 | t.fail('dBroker.shutdown failed: ' + err.get('error')); 346 | }); 347 | }); // ideal 348 | 349 | for (var i = 0; i < multipleTaskTestSize; i++) { 350 | var rTask = rbroker.discreteTask({ 351 | filename: 'Histogram of Auto Sales', 352 | directory: 'root', 353 | author: 'testuser' 354 | }), 355 | rTaskToken; 356 | 357 | rTasks.push(rTask); 358 | rTaskToken = dBroker.submit(rTask, priorityTask); 359 | 360 | if (priorityTask) { 361 | priorityTaskTokens.push(rTaskToken); 362 | } else { 363 | standardTaskTokens.push(rTaskToken); 364 | } 365 | 366 | // Flip priority flag to altenate task type. 367 | priorityTask = !priorityTask; 368 | } 369 | }); 370 | 371 | /** 372 | * Test "bad" RTask execution with execution-token. 373 | */ 374 | test('should unsuccessfully execute RTask with execution-token using "bad" values', function(t) { 375 | t.plan(4); 376 | 377 | var dBroker, rTask, 378 | executionToken = 'testTaskExecutionWithTokenBadScript', 379 | MAX_CONCURRENCY = 10, 380 | brokerConfig = { 381 | host: fixtures.endpoint, 382 | credentials: fixtures.credentials, 383 | maxConcurrentTaskLimit: MAX_CONCURRENCY, 384 | logging: false 385 | }; 386 | 387 | dBroker = rbroker.discreteTaskBroker(brokerConfig) 388 | .complete(function(rTask, rTaskResult) { 389 | t.notEqual(rTaskResult, null, 'rTaskResult'); 390 | t.notOk(rTaskResult.success, '!rTaskResult.success'); 391 | t.equal(rTask.token, executionToken, 'execution-token'); 392 | }) 393 | .error(function(err) { 394 | // 395 | // suppress valid task submit errors however non-task execution 396 | // errors should fail the test 397 | // 398 | if (!err.task) { 399 | t.fail('dBroker failed: ' + err.get('error')); 400 | } 401 | }) 402 | .idle(function() { 403 | dBroker.shutdown().then(function() { 404 | t.pass('dBroker.shutdown success'); 405 | }, function(err) { 406 | t.fail('dBroker.shutdown failed: ' + err.get('error')); 407 | }); 408 | }); 409 | 410 | rTask = rbroker.discreteTask({ 411 | filename: fixtures.BAD_SCRIPT_NAME, 412 | directory: fixtures.BAD_DIR_NAME, 413 | author: fixtures.BAD_AUTHOR_NAME 414 | }); 415 | 416 | rTask.token = executionToken; 417 | 418 | dBroker.submit(rTask); 419 | }); 420 | 421 | /** 422 | * Test RTask execution with "good" task execution options. 423 | */ 424 | test('should successfully execute RTask with "good" task execution options', function(t) { 425 | t.plan(4); 426 | 427 | var dBroker, rTask, 428 | executionToken = 'testTaskExecutionWithToken', 429 | brokerConfig = { 430 | host: fixtures.endpoint, 431 | credentials: fixtures.credentials 432 | }; 433 | 434 | dBroker = rbroker.discreteTaskBroker(brokerConfig) 435 | .complete(function(rTask, rTaskResult) { 436 | t.notEqual(rTaskResult, null, 'rTaskResult'); 437 | t.ok(rTaskResult.success, 'rTaskResult.success'); 438 | t.equal(rTask.token, executionToken, 'execution-token'); 439 | }) 440 | .error(function(err) { 441 | var msg = err.task ? 'dBroker.submit(rTask) failed: ' : 442 | 'dBroker failed: '; 443 | t.fail(msg + err.get('error')); 444 | }) 445 | .idle(function() { 446 | dBroker.shutdown().then(function() { 447 | t.pass('dBroker.shutdown success'); 448 | }, function(err) { 449 | t.fail('dBroker.shutdown failed: ' + err.get('error')); 450 | }); 451 | }); 452 | 453 | rTask = rbroker.discreteTask(merge({ 454 | filename: 'Histogram of Auto Sales', 455 | directory: 'root', 456 | author: 'testuser' 457 | }, fixtures.good())); 458 | 459 | rTask.token = executionToken; 460 | 461 | dBroker.submit(rTask); 462 | }); 463 | 464 | /** 465 | * Test RTask execution with "bad" task execution options. 466 | */ 467 | test('should unsuccessfully execute RTask with "bad" task execution options', function(t) { 468 | t.plan(3); 469 | 470 | var dBroker, 471 | brokerConfig = { 472 | host: fixtures.endpoint, 473 | credentials: fixtures.credentials 474 | }; 475 | 476 | dBroker = rbroker.discreteTaskBroker(brokerConfig) 477 | .complete(function(rTask, rTaskResult) { 478 | t.notEqual(rTaskResult, null, 'rTaskResult'); 479 | t.notOk(rTaskResult.success, '!rTaskResult.success'); 480 | }) 481 | .error(function(err) { 482 | // 483 | // suppress valid task submit errors however non-task execution 484 | // errors should fail the test 485 | // 486 | if (!err.task) { 487 | t.fail('dBroker failed: ' + err.get('error')); 488 | } 489 | }) 490 | .idle(function() { 491 | dBroker.shutdown().then(function() { 492 | t.pass('dBroker.shutdown success'); 493 | }, function(err) { 494 | t.fail('dBroker.shutdown failed: ' + err.get('error')); 495 | }); 496 | }); 497 | 498 | dBroker.submit(rbroker.discreteTask(merge({ 499 | filename: 'Histogram of Auto Sales', 500 | directory: 'root', 501 | author: 'testuser' 502 | }, fixtures.bad()))); 503 | }); 504 | -------------------------------------------------------------------------------- /test/specs/background-task-broker-test.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (C) 2010-2016, Microsoft Corporation 3 | * 4 | * This program is licensed to you under the terms of Version 2.0 of the 5 | * Apache License. This program is distributed WITHOUT 6 | * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 7 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 8 | * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more 9 | * details. 10 | */ 11 | 12 | var test = require('tape'), 13 | when = require('when'), 14 | merge = require('merge'), 15 | rbroker = require('../../rbroker'), 16 | fixtures = require('../fixtures/job'); 17 | 18 | console.log('A BackgroundTaskBroker\n'); 19 | 20 | /** 21 | * Teardown job tests 22 | */ 23 | function teardown(t, bgBroker, jobIDs) { 24 | var promises = []; 25 | 26 | jobIDs.forEach(function(jobID) { 27 | promises.push(fixtures.deleteJobArtifacts(bgBroker.owner(), jobID)); 28 | }); 29 | 30 | when.all(promises).then() 31 | .ensure(function() { 32 | bgBroker.shutdown().then(function() { 33 | t.pass('bgBroker.shutdown success'); 34 | }, function(err) { 35 | t.fail('bgBroker.shutdown failed: ' + err.get('error')); 36 | }); 37 | }); 38 | } 39 | 40 | /** 41 | * Test "good" endpoint. 42 | */ 43 | test('should successfully initialize with good endpoint address', function(t) { 44 | t.plan(1); 45 | 46 | var bgBroker = null, 47 | brokerConfig = { 48 | host: fixtures.endpoint, 49 | credentials: fixtures.credentials 50 | }; 51 | 52 | bgBroker = rbroker.backgroundTaskBroker(brokerConfig) 53 | .error(function(err) { 54 | t.fail('bgBroker initialization failed "' + err.get('error') + '"'); 55 | }) 56 | .ready(function() { 57 | t.pass('bgBroker initialization success'); 58 | bgBroker.shutdown().then(function() {}, function(err) { 59 | t.fail('bgBroker.shutdown failed: ' + err.get('error')); 60 | }); 61 | }); 62 | }); 63 | 64 | /** 65 | * Test "bad" endpoint. 66 | */ 67 | test('should fail initialization with bad endpoint address', function(t) { 68 | t.plan(1); 69 | 70 | var bgBroker = null, 71 | brokerConfig = { 72 | host: fixtures.BAD_ENDPOINT, 73 | credentials: fixtures.credentials 74 | }; 75 | 76 | bgBroker = rbroker.backgroundTaskBroker(brokerConfig) 77 | .error(function(err) { 78 | t.pass('bgBroker initialization failed "' + err.get('error') + '"'); 79 | }) 80 | .ready(function() { 81 | t.pass('bgBroker initialization success but should have failed'); 82 | bgBroker.shutdown().then(function() {}, function(err) { 83 | t.fail('bgBroker.shutdown failed: ' + err.get('error')); 84 | }); 85 | }); 86 | }); 87 | 88 | /** 89 | * Test authenticated BackgroundBrokerfixtures. 90 | */ 91 | test('should authenticate with `Background Broker Config`', function(t) { 92 | t.plan(5); 93 | 94 | var brokerConfig = { 95 | host: fixtures.endpoint, 96 | credentials: fixtures.credentials, 97 | logging: false 98 | }, 99 | jobID = null, 100 | bgBroker = null, 101 | rTask = null; 102 | 103 | bgBroker = rbroker.backgroundTaskBroker(brokerConfig) 104 | .complete(function(rTask, rTaskResult) { 105 | jobID = rTaskResult.id; 106 | 107 | t.notEqual(rTaskResult, null, 'rTaskResult'); 108 | t.ok(rTaskResult.success, 'rTaskResult.success'); 109 | t.notEqual(jobID, null, 'jobID'); 110 | 111 | fixtures.verifyJobExitStatus(bgBroker.owner(), jobID, 'Completed') 112 | .then(function() { 113 | t.pass('jobCompleted'); 114 | }, function(err) { 115 | t.fail('jobCompleted: ' + err); 116 | }) 117 | .ensure(function() { 118 | bgBroker.shutdown().then(function() { 119 | t.pass('bgBroker.shutdown success'); 120 | }, function(err) { 121 | t.fail('bgBroker.shutdown failed: ' + err.get('error')); 122 | }); 123 | }); 124 | }) 125 | .error(function(err) { 126 | var msg = err.task ? 'bgBroker.submit(rTask) failed: ' : 127 | 'bgBroker failed: '; 128 | 129 | t.notOk(false, msg + err.get('error')); 130 | }); 131 | 132 | bgBroker.submit(rbroker.backgroundTask({ 133 | name: 'testConfigAuthenticationGoodCredentials', 134 | descr: 'Background Task', 135 | rscriptname: 'Histogram of Auto Sales', 136 | rscriptdirectory: 'root', 137 | rscriptauthor: 'testuser' 138 | })); 139 | }); 140 | 141 | /** 142 | * Test authenticated BackgroundBrokerConfig using bad credentials. 143 | */ 144 | test('should not authenticate with `Background Broker Config`', function(t) { 145 | t.plan(1); 146 | 147 | var brokerConfig = { 148 | host: fixtures.endpoint, 149 | credentials: fixtures.BAD_CREDENTIALS, 150 | logging: false 151 | }, 152 | bgBroker = null; 153 | 154 | bgBroker = rbroker.backgroundTaskBroker(brokerConfig) 155 | .ready(function() { 156 | bgBroker.shutdown().then(function() {}, function(err) { 157 | t.fail('bgBroker.shutdown failed: ' + err.get('error')); 158 | }) 159 | .ensure(function() { 160 | t.fail('authentication successful but should not have been'); 161 | }); 162 | }) 163 | .error(function(err) { 164 | t.pass('rbroker.backgroundTaskBroker failed: ' + err.get('error')); 165 | }); 166 | }); 167 | 168 | /** 169 | * Test RTask execution with `execution-token`. 170 | */ 171 | test('should execute RTask successfully with `execution-token`', function(t) { 172 | t.plan(6); 173 | 174 | var brokerConfig = { 175 | host: fixtures.endpoint, 176 | credentials: fixtures.credentials, 177 | logging: false 178 | }, 179 | executionToken = 'testTaskExecutionWithTokenStandardQueueGood', 180 | jobID = null, 181 | bgBroker = null, 182 | rTask = null; 183 | 184 | bgBroker = rbroker.backgroundTaskBroker(brokerConfig) 185 | .complete(function(rTask, rTaskResult) { 186 | jobID = rTaskResult.id; 187 | 188 | t.notEqual(rTaskResult, null, 'rTaskResult'); 189 | t.ok(rTaskResult.success, 'rTaskResult.success'); 190 | t.equal(rTask.token, executionToken, 'execution-token'); 191 | t.notEqual(jobID, null, 'jobID'); 192 | 193 | fixtures.verifyJobExitStatus(bgBroker.owner(), jobID, 'Completed') 194 | .then(function() { 195 | t.pass('jobCompleted'); 196 | }, function(err) { 197 | t.fail('jobCompleted: ' + err); 198 | }) 199 | .ensure(function() { 200 | teardown(t, bgBroker, [jobID]); 201 | }); 202 | }) 203 | .error(function(err) { 204 | var msg = err.task ? 'bgBroker.submit(rTask) failed: ' : 205 | 'bgBroker failed: '; 206 | 207 | t.notOk(false, msg + err.get('error')); 208 | }); 209 | 210 | rTask = rbroker.backgroundTask({ 211 | name: executionToken, 212 | descr: 'Background Task', 213 | rscriptname: 'Histogram of Auto Sales', 214 | rscriptdirectory: 'root', 215 | rscriptauthor: 'testuser' 216 | }); 217 | 218 | rTask.token = executionToken; 219 | 220 | bgBroker.submit(rTask); 221 | }); 222 | 223 | /** 224 | * Test BackgroundBrokerConfig RTask priority execution with `execution-token`. 225 | */ 226 | test('should execute priority RTask successfully with `execution-token`', function(t) { 227 | t.plan(6); 228 | 229 | var brokerConfig = { 230 | host: fixtures.endpoint, 231 | credentials: fixtures.credentials, 232 | logging: false 233 | }, 234 | executionToken = 'testTaskExecutionWithTokenPriorityQueueGood', 235 | jobID = null, 236 | bgBroker = null, 237 | rTask = null; 238 | 239 | bgBroker = rbroker.backgroundTaskBroker(brokerConfig) 240 | .complete(function(rTask, rTaskResult) { 241 | jobID = rTaskResult.id; 242 | 243 | t.notEqual(rTaskResult, null, 'rTaskResult'); 244 | t.ok(rTaskResult.success, 'rTaskResult.success'); 245 | t.equal(rTask.token, executionToken, 'execution-token'); 246 | t.notEqual(jobID, null, 'jobID'); 247 | 248 | fixtures.verifyJobExitStatus(bgBroker.owner(), jobID, 'Completed') 249 | .then(function() { 250 | t.pass('jobCompleted'); 251 | }, function(err) { 252 | t.fail('jobCompleted: ' + err); 253 | }) 254 | .ensure(function() { 255 | teardown(t, bgBroker, [jobID]); 256 | }); 257 | }) 258 | .error(function(err) { 259 | if (err.task) { 260 | t.fail('bgBroker.submit(rTask) failed: ' + err.get('error')); 261 | } 262 | }); 263 | 264 | rTask = rbroker.backgroundTask({ 265 | name: executionToken, 266 | descr: 'Background Task', 267 | rscriptname: 'Histogram of Auto Sales', 268 | rscriptdirectory: 'root', 269 | rscriptauthor: 'testuser' 270 | }, true); 271 | 272 | rTask.token = executionToken; 273 | 274 | bgBroker.submit(rTask); 275 | }); 276 | 277 | /** 278 | * Test multiple "good" RTask executions distributed across the standard and 279 | * priority task queues. 280 | */ 281 | test('should execute multiple "good" RTask executions distributed across standard and priority task queues', function(t) { 282 | var brokerConfig = { 283 | host: fixtures.endpoint, 284 | credentials: fixtures.credentials, 285 | logging: false 286 | }, 287 | executionToken = 'testMultipleTaskExecutionWithMixedPriority', 288 | jobID = null, 289 | bgBroker = null, 290 | rTasks = [], 291 | priorityTaskTokens = [], 292 | standardTaskTokens = [], 293 | rTaskResults = [], 294 | jobResults = [], 295 | priorityTask = true, 296 | multipleTaskTestSize = 10; 297 | 298 | t.plan(4 + (multipleTaskTestSize * 2)); 299 | 300 | bgBroker = rbroker.backgroundTaskBroker(brokerConfig) 301 | .complete(function(rTask, rTaskResult) { 302 | rTaskResults.push(rTaskResult); 303 | }) 304 | .error(function(err, rTask) { 305 | if (rTask) { 306 | t.fail('bgBroker.submit(rTask) failed: ' + err.get('error')); 307 | } 308 | }) 309 | .idle(function() { 310 | var promises = [], 311 | jobIDs = []; 312 | 313 | rTaskResults.forEach(function(result, i) { 314 | var jobID = result.id; 315 | jobIDs.push(result.id); 316 | promises.push(fixtures.verifyJobExitStatus(bgBroker.owner(), 317 | jobID, 'Completed')); 318 | }); 319 | 320 | when.all(promises).then(function(results) { 321 | jobResults = jobResults.concat(results); 322 | }) 323 | .ensure(function() { 324 | t.equal(rTasks.length, multipleTaskTestSize, 325 | 'rTasks and multipleTaskTestSize are the same size'); 326 | t.equal(standardTaskTokens.length + priorityTaskTokens.length, 327 | multipleTaskTestSize, 'standardTaskTokens and ' + 328 | 'priorityTaskTokens are the same size'); 329 | t.equal(rTaskResults.length, multipleTaskTestSize, 330 | 'rTaskResults and multipleTaskTestSize are the same size'); 331 | rTaskResults.forEach(function(result) { 332 | t.ok(result.success, 'rTask ran to success'); 333 | }); 334 | jobResults.forEach(function(result) { 335 | t.ok(result.status, 'job exit status success'); 336 | }); 337 | 338 | teardown(t, bgBroker, jobIDs); 339 | }); 340 | }); // ideal 341 | 342 | for (var i = 0; i < multipleTaskTestSize; i++) { 343 | var rTask = rbroker.backgroundTask({ 344 | name: executionToken, 345 | descr: 'Background Task', 346 | rscriptname: 'Histogram of Auto Sales', 347 | rscriptdirectory: 'root', 348 | rscriptauthor: 'testuser' 349 | }), 350 | rTaskToken = null; 351 | 352 | rTasks.push(rTask); 353 | rTaskToken = bgBroker.submit(rTask, priorityTask); 354 | 355 | if (priorityTask) { 356 | priorityTaskTokens.push(rTaskToken); 357 | } else { 358 | standardTaskTokens.push(rTaskToken); 359 | } 360 | 361 | // Flip priority flag to altenate task type. 362 | priorityTask = !priorityTask; 363 | } 364 | }); 365 | 366 | /** 367 | * Test "bad" repository-managed script based RTask execution with an 368 | * `execution-token`. 369 | */ 370 | test('should execute "bad" repository-managed script based RTask execution unsuccessfully with `execution-token`', function(t) { 371 | t.plan(4); 372 | 373 | var brokerConfig = { 374 | host: fixtures.endpoint, 375 | credentials: fixtures.credentials, 376 | logging: false 377 | }, 378 | executionToken = 'testTaskExecutionWithTokenBadScript', 379 | jobID = null, 380 | bgBroker = null, 381 | rTask = null; 382 | 383 | bgBroker = rbroker.backgroundTaskBroker(brokerConfig) 384 | .complete(function(rTask, rTaskResult) { 385 | jobID = rTaskResult.id; 386 | 387 | t.notEqual(rTaskResult, null, 'rTaskResult'); 388 | t.notEqual(jobID, null, 'jobID'); 389 | 390 | fixtures.verifyJobExitStatus(bgBroker.owner(), jobID, 'Failed') 391 | .then(function(result) { 392 | t.ok(result.status, 'job should fail'); 393 | }) 394 | .ensure(function() { 395 | teardown(t, bgBroker, [jobID]); 396 | }); 397 | }) 398 | .error(function(err) { 399 | if (err.task) { 400 | t.fail('bgBroker.submit(rTask) failed: ' + err.get('error')); 401 | } 402 | }); 403 | 404 | rTask = rbroker.backgroundTask({ 405 | name: executionToken, 406 | descr: 'Background Task', 407 | rscriptname: 'This Script Does Not Exist', 408 | rscriptdirectory: 'root', 409 | rscriptauthor: 'testuser' 410 | }); 411 | 412 | rTask.token = executionToken; 413 | 414 | bgBroker.submit(rTask); 415 | }); 416 | 417 | /** 418 | * Test "bad" code-block based RTask execution with execution-token. 419 | */ 420 | test('should execute "bad" code-block based RTask execution unsuccessfully with `execution-token`', function(t) { 421 | t.plan(4); 422 | 423 | var brokerConfig = { 424 | host: fixtures.endpoint, 425 | credentials: fixtures.credentials, 426 | logging: false 427 | }, 428 | executionToken = 'testTaskExecutionWithTokenBadCode', 429 | jobID = null, 430 | bgBroker = null, 431 | rTask = null; 432 | 433 | bgBroker = rbroker.backgroundTaskBroker(brokerConfig) 434 | .complete(function(rTask, rTaskResult) { 435 | jobID = rTaskResult.id; 436 | 437 | t.notEqual(rTaskResult, null, 'rTaskResult'); 438 | t.notEqual(jobID, null, 'jobID'); 439 | 440 | fixtures.verifyJobExitStatus(bgBroker.owner(), jobID, 'Failed') 441 | .then(function(result) { 442 | t.ok(result.status, 'job should fail'); 443 | }) 444 | .ensure(function() { 445 | teardown(t, bgBroker, [jobID]); 446 | }); 447 | }) 448 | .error(function(err) { 449 | if (err.task) { 450 | t.fail('bgBroker.submit(rTask) failed: ' + err.get('error')); 451 | } 452 | }); 453 | 454 | rTask = rbroker.backgroundTask({ 455 | name: executionToken, 456 | descr: 'Background Task', 457 | code: 'x y' // invalid R code 458 | }); 459 | 460 | rTask.token = executionToken; 461 | 462 | bgBroker.submit(rTask); 463 | }); 464 | 465 | /** 466 | * Test RTask execution with "good" task execution options. 467 | */ 468 | test('should successfully execute RTask with "good" task execution options', function(t) { 469 | t.plan(5); 470 | 471 | var jobID, bgBroker, 472 | brokerConfig = { 473 | host: fixtures.endpoint, 474 | credentials: fixtures.credentials, 475 | logging: false 476 | }; 477 | 478 | bgBroker = rbroker.backgroundTaskBroker(brokerConfig) 479 | .complete(function(rTask, rTaskResult) { 480 | jobID = rTaskResult.id; 481 | 482 | t.notEqual(rTaskResult, null, 'rTaskResult'); 483 | t.ok(rTaskResult.success, 'rTaskResult.success'); 484 | t.notEqual(jobID, null, 'jobID'); 485 | 486 | fixtures.verifyJobExitStatus(bgBroker.owner(), jobID, 'Completed') 487 | .then(function(result) { 488 | t.ok(result.status, 'job should complete'); 489 | }) 490 | .ensure(function() { 491 | teardown(t, bgBroker, [jobID]); 492 | }); 493 | }) 494 | .error(function(err) { 495 | if (err.task) { 496 | t.fail('bgBroker.submit(rTask) failed: ' + err.get('error')); 497 | } 498 | }); 499 | 500 | bgBroker.submit(rbroker.backgroundTask(merge({ 501 | name: 'testTaskExecutionWithGoodOptions', 502 | descr: 'Background Task', 503 | rscriptname: 'Histogram of Auto Sales', 504 | rscriptdirectory: 'root', 505 | rscriptauthor: 'testuser' 506 | }, fixtures.good()))); 507 | }); 508 | 509 | /** 510 | * Test RTask execution with "bad" task execution options. 511 | */ 512 | test('should unsuccessfully execute RTask with "bad" task execution options', function(t) { 513 | t.plan(4); 514 | 515 | var bgBroker, jobID, 516 | brokerConfig = { 517 | host: fixtures.endpoint, 518 | credentials: fixtures.credentials, 519 | logging: false 520 | }; 521 | 522 | bgBroker = rbroker.backgroundTaskBroker(brokerConfig) 523 | .complete(function(rTask, rTaskResult) { 524 | jobID = rTaskResult.id; 525 | 526 | t.notEqual(rTaskResult, null, 'rTaskResult'); 527 | t.notEqual(jobID, null, 'jobID'); 528 | 529 | fixtures.verifyJobExitStatus(bgBroker.owner(), jobID, 'Aborted') 530 | .then(function(result) { 531 | t.notOk(result.status, 'job should fail'); 532 | }) 533 | .ensure(function() { 534 | teardown(t, bgBroker, [jobID]); 535 | }); 536 | }) 537 | .error(function(err) { 538 | // 539 | // Suppress all errors other than 400: Project param not specified 540 | // on projectDelete call. All other errors should fail the test. 541 | // 542 | if (!err.task && err.get('errorCode') !== 400) { 543 | t.fail('bgBroker failed: ' + err.get('error')); 544 | } 545 | }); 546 | 547 | bgBroker.submit(rbroker.backgroundTask(merge({ 548 | name: 'testTaskExecutionWithBadOptions', 549 | descr: 'Background Task', 550 | rscriptname: 'Histogram of Auto Sales', 551 | rscriptdirectory: 'root', 552 | rscriptauthor: 'testuser' 553 | }, fixtures.bad()))); 554 | }); 555 | 556 | /** 557 | * Cancel long running RTask. 558 | */ 559 | test('should successfully cancel long running RTask', function(t) { 560 | t.plan(5); 561 | 562 | var bgBroker, rTaskToken, 563 | brokerConfig = { 564 | host: fixtures.endpoint, 565 | credentials: fixtures.credentials 566 | }; 567 | 568 | bgBroker = rbroker.backgroundTaskBroker(brokerConfig) 569 | .complete(function(rTask, rTaskResult) { 570 | jobID = rTaskResult.id; 571 | 572 | t.notEqual(rTaskResult, null, 'rTaskResult'); 573 | t.ok(rTaskResult.success, 'rTaskResult.success'); 574 | t.notEqual(jobID, null, 'jobID'); 575 | 576 | rTaskToken.cancel(true); 577 | 578 | // wait 5 seconds for /r/job/cancel to take place 579 | setTimeout(function() { 580 | fixtures.verifyJobExitStatus(bgBroker.owner(), jobID, 'Cancelled') 581 | .then(function(result) { 582 | t.ok(result.status, 'jobCancelled'); 583 | }) 584 | .ensure(function() { 585 | bgBroker.shutdown().then(function() { 586 | t.pass('bgBroker.shutdown success'); 587 | }, function(err) { 588 | t.fail('bgBroker.shutdown failed: ' + err.get('error')); 589 | }); 590 | }); 591 | }, 5000); 592 | }) 593 | .error(function(err) { 594 | t.fail('bgBroker failed: ' + err.get('error')); 595 | }); 596 | 597 | rTaskToken = bgBroker.submit(rbroker.backgroundTask({ 598 | name: 'testJobCancel', 599 | descr: 'Background Task', 600 | code: 'Sys.sleep(10)' // sleep 10 seconds so we can cancel it 601 | })); 602 | }); --------------------------------------------------------------------------------