Moved existing version of sample application to jenkins repo
diff --git a/auto-scale/README.md b/auto-scale/README.md
new file mode 100644
index 0000000..cb11620
--- /dev/null
+++ b/auto-scale/README.md
@@ -0,0 +1,84 @@
+# Autoscaling Application
+The auto-scaling app uses monitoring data to decide when to scale a service up/down.
+
+It is treated as an application rather than yet another
+service because it offers only a GUI front-end; it is not
+modelled as a service that other services can build upon.
+
+#How to
+
+Ensure that the CORD config is installed and then run:
+
+`python xos_auto_scaling_app.py`
+
+This command will start the autoscaling application and start REST server on 9991 port.
+
+## To verify the autoscaling application:
+1) Login to cloudlab compute nodes <br/>
+2) On each compute node, open /etc/ceilometer/pipeline.yaml file<br/>
+3) Change the polling interval for "cpu_source" meters from 600 to poll interval period that u wish (eg. 60) as shown below.<br/>
+From:
+```
+ - name: cpu_source
+ interval: 600
+ meters:
+ - "cpu"
+ sinks:
+ - cpu_sink
+```
+
+To:
+```
+ - name: cpu_source
+ interval: 60
+ meters:
+ - "cpu"
+ sinks:
+ - cpu_sink
+```
+3b) Also ensure the publisher in "cpu_sink" contains the following URL "udp://"IP of Ceilometer PUB-SUB":5004" as shown below.<br/>
+```
+ - name: cpu_sink
+ transformers:
+ - name: "rate_of_change"
+ parameters:
+ target:
+ name: "cpu_util"
+ unit: "%"
+ type: "gauge"
+ scale: "100.0 / (10**9 * (resource_metadata.cpu_number or 1))"
+ publishers:
+ - notifier://
+```
+
+To:
+```
+ - name: cpu_sink
+ transformers:
+ - name: "rate_of_change"
+ parameters:
+ target:
+ name: "cpu_util"
+ unit: "%"
+ type: "gauge"
+ scale: "100.0 / (10**9 * (resource_metadata.cpu_number or 1))"
+ publishers:
+ - notifier://
+ - udp://10.11.10.1:5004
+```
+4) sudo service ceilometer-agent-compute restart<br/>
+5) With this change, the autoscaling application should start receiving the CPU utilization samples every 60 seconds<br/>
+6) The REST API to retrieve the cpu utilization samples from autoscaling application: http://<app_ip>:9991/autoscaledata
+
+## GUI (early development stage)
+
+To run this sample you need to have `nodejs >= 4.0` installed on your local system. You can get it [here](https://nodejs.org/en/)
+
+- Clone the repository locally
+- Open `xos-apps/auto-scale/gui/env/default` and change it to:
+```
+module.exports = {
+ host: 'http://<your.cord.installation.ip>:9991'
+}
+```
+- From `xos-apps/auto-scale/gui` run `npm start`
diff --git a/auto-scale/gui/.bowerrc b/auto-scale/gui/.bowerrc
new file mode 100644
index 0000000..e491038
--- /dev/null
+++ b/auto-scale/gui/.bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory": "src/vendor/"
+}
\ No newline at end of file
diff --git a/auto-scale/gui/.editorconfig b/auto-scale/gui/.editorconfig
new file mode 100644
index 0000000..e717f5e
--- /dev/null
+++ b/auto-scale/gui/.editorconfig
@@ -0,0 +1,13 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/auto-scale/gui/.eslintrc b/auto-scale/gui/.eslintrc
new file mode 100644
index 0000000..868fd4b
--- /dev/null
+++ b/auto-scale/gui/.eslintrc
@@ -0,0 +1,44 @@
+{
+ "ecmaFeatures": {
+ "blockBindings": true,
+ "forOf": true,
+ "destructuring": true,
+ "arrowFunctions": true,
+ "templateStrings": true
+ },
+ "env": {
+ "browser": true,
+ "node": true,
+ "es6": true
+ },
+ "plugins": [
+ //"angular"
+ ],
+ "rules": {
+ "quotes": [2, "single"],
+ "camelcase": [0, {"properties": "always"}],
+ "no-underscore-dangle": 1,
+ "eqeqeq": [2, "smart"],
+ "no-alert": 1,
+ "key-spacing": [1, { "beforeColon": false, "afterColon": true }],
+ "indent": [2, 2],
+ "no-irregular-whitespace": 1,
+ "eol-last": 0,
+ "max-nested-callbacks": [2, 4],
+ "comma-spacing": [1, {"before": false, "after": true}],
+ "no-trailing-spaces": [1, { skipBlankLines: true }],
+ "no-unused-vars": [1, {"vars": "all", "args": "after-used"}],
+ "new-cap": 0,
+ "no-undef": 2,
+
+ //"angular/ng_module_name": [2, '/^xos\.*[a-z]*$/'],
+ //"angular/ng_controller_name": [2, '/^[a-z].*Ctrl$/'],
+ //"angular/ng_service_name": [2, '/^[A-Z].*Service$/'],
+ //"angular/ng_directive_name": [2, '/^[a-z]+[[A-Z].*]*$/'],
+ //"angular/ng_di": [0, "function or array"]
+ },
+ "globals" :{
+ "angular": true,
+ "Chart": true
+ }
+}
\ No newline at end of file
diff --git a/auto-scale/gui/.gitignore b/auto-scale/gui/.gitignore
new file mode 100644
index 0000000..429d79a
--- /dev/null
+++ b/auto-scale/gui/.gitignore
@@ -0,0 +1,9 @@
+node_modules/
+bower_components/
+coverage/
+.sass-cache/
+.idea/
+.tmp/
+dist/
+src/vendor
+src/.tmp
diff --git a/auto-scale/gui/bower.json b/auto-scale/gui/bower.json
new file mode 100644
index 0000000..3286dd3
--- /dev/null
+++ b/auto-scale/gui/bower.json
@@ -0,0 +1,43 @@
+{
+ "name": "xos-ceilometerDashboard",
+ "version": "0.0.0",
+ "authors": [
+ "Matteo Scandolo <teo@onlab.us>"
+ ],
+ "description": "The ceilometerDashboard view",
+ "license": "MIT",
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "bower_components",
+ "static/js/vendor/",
+ "test",
+ "tests"
+ ],
+ "dependencies": {
+ "angular-chart.js": "~0.10.2",
+ "angular-animate": "~1.4.8",
+ "ui.bootstrap": "~0.14.3",
+ "jquery": "~2.2.3",
+ "angular": "~1.4.7",
+ "angular-ui-router": "~0.2.15",
+ "ng-lodash": "~0.3.0",
+ "bootstrap-css": "3.3.4"
+ },
+ "overrides": {
+ "ui.bootstrap": {
+ "main": [
+ "src/accordion/accordion.js",
+ "src/collapse/collapse.js"
+ ]
+ }
+ },
+ "devDependencies": {
+ "angular-mocks": "~1.4.8"
+ },
+ "resolutions": {
+ "jquery": "~2.1.4",
+ "angular-chart.js": "~0.8.7",
+ "Chart.js": "~1.0.1"
+ }
+}
diff --git a/auto-scale/gui/env/default.js b/auto-scale/gui/env/default.js
new file mode 100644
index 0000000..2153a31
--- /dev/null
+++ b/auto-scale/gui/env/default.js
@@ -0,0 +1,3 @@
+module.exports = {
+ host: 'http://130.127.133.58:9991/'
+}
diff --git a/auto-scale/gui/env/srikanth.js b/auto-scale/gui/env/srikanth.js
new file mode 100644
index 0000000..0489fb9
--- /dev/null
+++ b/auto-scale/gui/env/srikanth.js
@@ -0,0 +1,3 @@
+module.exports = {
+ host: 'http://128.104.222.16:9991'
+}
\ No newline at end of file
diff --git a/auto-scale/gui/gulp/build.js b/auto-scale/gui/gulp/build.js
new file mode 100644
index 0000000..9f9bfb8
--- /dev/null
+++ b/auto-scale/gui/gulp/build.js
@@ -0,0 +1,149 @@
+'use strict';
+
+// BUILD
+//
+// The only purpose of this gulpfile is to build a XOS view and copy the correct files into
+// .html => dashboards
+// .js (minified and concat) => static/js
+//
+// The template are parsed and added to js with angular $templateCache
+
+var gulp = require('gulp');
+var ngAnnotate = require('gulp-ng-annotate');
+var uglify = require('gulp-uglify');
+var templateCache = require('gulp-angular-templatecache');
+var runSequence = require('run-sequence');
+var concat = require('gulp-concat');
+var del = require('del');
+var wiredep = require('wiredep');
+var angularFilesort = require('gulp-angular-filesort');
+var _ = require('lodash');
+var eslint = require('gulp-eslint');
+var inject = require('gulp-inject');
+var rename = require('gulp-rename');
+var replace = require('gulp-replace');
+var postcss = require('gulp-postcss');
+var autoprefixer = require('autoprefixer');
+var mqpacker = require('css-mqpacker');
+var csswring = require('csswring');
+
+var TEMPLATE_FOOTER = `}]);
+angular.module('xos.ceilometerDashboard').run(function($location){$location.path('/')});
+angular.bootstrap(angular.element('#xosCeilometerDashboard'), ['xos.ceilometerDashboard']);`;
+
+module.exports = function(options){
+
+ // delete previous builded file
+ gulp.task('clean', function(){
+ return del(
+ [options.dashboards + 'xosCeilometerDashboard.html'],
+ {force: true}
+ );
+ });
+
+ // minify css
+ gulp.task('css', function () {
+ var processors = [
+ autoprefixer({browsers: ['last 1 version']}),
+ mqpacker,
+ csswring
+ ];
+
+ gulp.src([
+ `${options.css}**/*.css`,
+ `!${options.css}dev.css`
+ ])
+ .pipe(postcss(processors))
+ .pipe(gulp.dest(options.tmp + '/css/'));
+ });
+
+ gulp.task('copyCss', ['css'], function(){
+ return gulp.src([`${options.tmp}/css/*.css`])
+ .pipe(concat('xosCeilometerDashboard.css'))
+ .pipe(gulp.dest(options.static + 'css/'))
+ });
+
+ // compile and minify scripts
+ gulp.task('scripts', function() {
+ return gulp.src([
+ options.tmp + '**/*.js'
+ ])
+ .pipe(ngAnnotate())
+ .pipe(angularFilesort())
+ .pipe(concat('xosCeilometerDashboard.js'))
+ .pipe(uglify())
+ .pipe(gulp.dest(options.static + 'js/'));
+ });
+
+ // set templates in cache
+ gulp.task('templates', function(){
+ return gulp.src('./src/templates/*.html')
+ .pipe(templateCache({
+ module: 'xos.ceilometerDashboard',
+ root: 'templates/',
+ templateFooter: TEMPLATE_FOOTER
+ }))
+ .pipe(gulp.dest(options.tmp));
+ });
+
+ // copy html index to Django Folder
+ gulp.task('copyHtml', ['clean'], function(){
+ return gulp.src(options.src + 'index.html')
+ // remove dev dependencies from html
+ .pipe(replace(/<!-- bower:css -->(\n.*)*\n<!-- endbower --><!-- endcss -->/, ''))
+ .pipe(replace(/<!-- bower:js -->(\n.*)*\n<!-- endbower --><!-- endjs -->/, ''))
+ .pipe(replace(/ng-app=".*"\s/, ''))
+ //rewriting css path
+ // .pipe(replace(/(<link.*">)/, ''))
+ // injecting minified files
+ .pipe(
+ inject(
+ gulp.src([
+ options.static + 'js/vendor/xosCeilometerDashboardVendor.js',
+ options.static + 'js/xosCeilometerDashboard.js',
+ options.static + 'css/xosCeilometerDashboard.css'
+ ])
+ )
+ )
+ .pipe(rename('xosCeilometerDashboard.html'))
+ .pipe(gulp.dest(options.dashboards));
+ });
+
+ // minify vendor js files
+ gulp.task('wiredep', function(){
+ var bowerDeps = wiredep().js;
+ if(!bowerDeps){
+ return;
+ }
+
+ // remove angular (it's already loaded)
+ _.remove(bowerDeps, function(dep){
+ return dep.indexOf('angular/angular.js') !== -1;
+ });
+
+ return gulp.src(bowerDeps)
+ .pipe(concat('xosCeilometerDashboardVendor.js'))
+ .pipe(uglify())
+ .pipe(gulp.dest(options.static + 'js/vendor/'));
+ });
+
+ gulp.task('lint', function () {
+ return gulp.src(['src/js/**/*.js'])
+ .pipe(eslint())
+ .pipe(eslint.format())
+ .pipe(eslint.failAfterError());
+ });
+
+ gulp.task('build', function() {
+ runSequence(
+ 'lint',
+ 'templates',
+ 'babel',
+ 'scripts',
+ 'wiredep',
+ 'copyHtml',
+ 'copyCss',
+ 'cleanTmp'
+ );
+ });
+};
\ No newline at end of file
diff --git a/auto-scale/gui/gulp/server.js b/auto-scale/gui/gulp/server.js
new file mode 100644
index 0000000..0f69064
--- /dev/null
+++ b/auto-scale/gui/gulp/server.js
@@ -0,0 +1,149 @@
+'use strict';
+
+var gulp = require('gulp');
+var browserSync = require('browser-sync').create();
+var inject = require('gulp-inject');
+var runSequence = require('run-sequence');
+var angularFilesort = require('gulp-angular-filesort');
+var babel = require('gulp-babel');
+var wiredep = require('wiredep').stream;
+var del = require('del');
+var httpProxy = require('http-proxy');
+
+const environment = process.env.NODE_ENV;
+
+if (environment){
+ var conf = require(`../env/${environment}.js`);
+}
+else{
+ var conf = require('../env/default.js')
+}
+
+console.log(conf);
+
+var proxy = httpProxy.createProxyServer({
+ target: conf.host || 'http://0.0.0.0:9999'
+});
+
+proxy.on('error', function(error, req, res) {
+ res.writeHead(500, {
+ 'Content-Type': 'text/plain'
+ });
+
+ console.error('[Proxy]', error);
+});
+
+module.exports = function(options){
+
+ // open in browser with sync and proxy to 0.0.0.0
+ gulp.task('browser', function() {
+ browserSync.init({
+ // reloadDelay: 500,
+ // logLevel: 'debug',
+ // logConnections: true,
+ // directory: true,
+ startPath: '#/',
+ snippetOptions: {
+ rule: {
+ match: /<!-- browserSync -->/i
+ }
+ },
+ server: {
+ baseDir: options.src,
+ middleware: function(req, res, next){
+ if(
+ req.url.indexOf('autoscaledata') !== -1
+ ){
+ proxy.web(req, res);
+ }
+ else{
+ next();
+ }
+ }
+ }
+ });
+
+ gulp.watch(options.src + 'js/**/*.js', ['js-watch']);
+
+ gulp.watch(options.src + 'vendor/**/*.js', ['bower'], function(){
+ browserSync.reload();
+ });
+ gulp.watch(options.src + '**/*.html', function(){
+ browserSync.reload();
+ });
+ gulp.watch(options.src + '**/*.css', function(){
+ browserSync.reload();
+ });
+ });
+
+ // transpile js with sourceMaps
+ gulp.task('babel', function(){
+ return gulp.src([options.scripts + '**/*.js'])
+ .pipe(babel({sourceMaps: true}))
+ .pipe(gulp.dest(options.tmp));
+ });
+
+ // inject scripts
+ gulp.task('injectScript', function(){
+ runSequence(
+ 'cleanTmp',
+ 'babel',
+ function() {
+ return gulp.src(options.src + 'index.html')
+ .pipe(
+ inject(
+ gulp.src([
+ options.tmp + '**/*.js',
+ options.api + '*.js',
+ options.helpers + '**/*.js'
+ ])
+ // .pipe(debug({title: 'unicorn:'}))
+ .pipe(angularFilesort()),
+ {
+ ignorePath: [options.src, '/../../ngXosLib']
+ }
+ )
+ )
+ .pipe(gulp.dest(options.src));
+ }
+ );
+ });
+
+ // inject CSS
+ gulp.task('injectCss', function(){
+ return gulp.src(options.src + 'index.html')
+ .pipe(
+ inject(
+ gulp.src(options.src + 'css/*.css'),
+ {
+ ignorePath: [options.src]
+ }
+ )
+ )
+ .pipe(gulp.dest(options.src));
+ });
+
+ // inject bower dependencies with wiredep
+ gulp.task('bower', function () {
+ return gulp.src(options.src + 'index.html')
+ .pipe(wiredep({devDependencies: true}))
+ .pipe(gulp.dest(options.src));
+ });
+
+ gulp.task('js-watch', ['injectScript'], function(){
+ browserSync.reload();
+ });
+
+ gulp.task('cleanTmp', function(){
+ return del([options.tmp + '**/*']);
+ });
+
+ gulp.task('serve', function() {
+ runSequence(
+ 'bower',
+ 'injectScript',
+ 'injectCss',
+ ['browser']
+ );
+ });
+};
diff --git a/auto-scale/gui/gulpfile.js b/auto-scale/gui/gulpfile.js
new file mode 100644
index 0000000..de911e9
--- /dev/null
+++ b/auto-scale/gui/gulpfile.js
@@ -0,0 +1,25 @@
+'use strict';
+
+var gulp = require('gulp');
+var wrench = require('wrench');
+
+var options = {
+ src: 'src/',
+ css: 'src/css/',
+ scripts: 'src/js/',
+ tmp: 'src/.tmp',
+ dist: 'dist/',
+ api: '../../ngXosLib/api/',
+ helpers: '../../ngXosLib/xosHelpers/src/',
+ static: '../../static/', // this is the django static folder
+ dashboards: '../../dashboards/' // this is the django html folder
+};
+
+wrench.readdirSyncRecursive('./gulp')
+.map(function(file) {
+ require('./gulp/' + file)(options);
+});
+
+gulp.task('default', function () {
+ gulp.start('build');
+});
diff --git a/auto-scale/gui/karma.conf.js b/auto-scale/gui/karma.conf.js
new file mode 100644
index 0000000..b67c208
--- /dev/null
+++ b/auto-scale/gui/karma.conf.js
@@ -0,0 +1,94 @@
+// Karma configuration
+// Generated on Tue Oct 06 2015 09:27:10 GMT+0000 (UTC)
+
+/* eslint indent: [2,2], quotes: [2, "single"]*/
+
+/*eslint-disable*/
+var wiredep = require('wiredep');
+var path = require('path');
+
+var bowerComponents = wiredep( {devDependencies: true} )[ 'js' ].map(function( file ){
+ return path.relative(process.cwd(), file);
+});
+/*eslint-enable*/
+module.exports = function(config) {
+ config.set({
+
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: '',
+
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['jasmine'],
+
+
+ // list of files / patterns to load in the browser
+ files: bowerComponents.concat([
+ 'src/css/**/*.css',
+ 'src/js/main.js',
+ 'src/js/**/*.js',
+ 'spec/**/*.mock.js',
+ 'spec/**/*.test.js',
+ 'src/**/*.html'
+ ]),
+
+
+ // list of files to exclude
+ exclude: [
+ ],
+
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+ 'src/js/**/*.js': ['babel'],
+ 'spec/**/*.js': ['babel'],
+ 'src/**/*.html': ['ng-html2js']
+ },
+
+ babelPreprocessor: {
+ options: {
+ presets: ['es2015'],
+ sourceMap: 'inline'
+ }
+ },
+
+ ngHtml2JsPreprocessor: {
+ stripPrefix: 'src/', //strip the src path from template url (http://stackoverflow.com/questions/22869668/karma-unexpected-request-when-testing-angular-directive-even-with-ng-html2js)
+ moduleName: 'templates' // define the template module name
+ },
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['mocha'],
+
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: true,
+
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['PhantomJS'],
+
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: false
+ });
+};
diff --git a/auto-scale/gui/package.json b/auto-scale/gui/package.json
new file mode 100644
index 0000000..0a9ccae
--- /dev/null
+++ b/auto-scale/gui/package.json
@@ -0,0 +1,62 @@
+{
+ "name": "xos-ceilometerDashboard",
+ "version": "1.0.0",
+ "description": "Angular Application for XOS, created with generator-xos",
+ "scripts": {
+ "prestart": "npm install && bower install",
+ "start": "gulp serve",
+ "prebuild": "npm install && bower install",
+ "build": "gulp",
+ "pretest": "npm install",
+ "test": "karma start",
+ "lint": "eslint src/js/"
+ },
+ "keywords": [
+ "XOS",
+ "Angular",
+ "XOSlib"
+ ],
+ "author": "Matteo Scandolo",
+ "license": "MIT",
+ "dependencies": {},
+ "devDependencies": {
+ "autoprefixer": "^6.1.2",
+ "babel": "^6.3.13",
+ "babel-preset-es2015": "^6.3.13",
+ "browser-sync": "^2.9.11",
+ "css-mqpacker": "^4.0.0",
+ "csswring": "^4.1.1",
+ "del": "^2.0.2",
+ "eslint": "^1.8.0",
+ "eslint-plugin-angular": "linkmesrl/eslint-plugin-angular",
+ "gulp": "^3.9.0",
+ "gulp-angular-filesort": "^1.1.1",
+ "gulp-angular-templatecache": "^1.8.0",
+ "gulp-babel": "^5.3.0",
+ "gulp-concat": "^2.6.0",
+ "gulp-debug": "^2.1.2",
+ "gulp-eslint": "^1.0.0",
+ "gulp-inject": "^3.0.0",
+ "gulp-minify-html": "^1.0.4",
+ "gulp-ng-annotate": "^1.1.0",
+ "gulp-postcss": "^6.0.1",
+ "gulp-rename": "^1.2.2",
+ "gulp-replace": "^0.5.4",
+ "gulp-uglify": "^1.4.2",
+ "http-proxy": "^1.12.0",
+ "jasmine-core": "^2.4.1",
+ "karma": "^0.13.15",
+ "karma-babel-preprocessor": "^6.0.1",
+ "karma-jasmine": "^0.3.6",
+ "karma-mocha-reporter": "^1.1.3",
+ "karma-ng-html2js-preprocessor": "^0.2.0",
+ "karma-phantomjs-launcher": "^0.2.1",
+ "lodash": "^3.10.1",
+ "mocha": "^2.3.4",
+ "phantomjs": "^1.9.19",
+ "proxy-middleware": "^0.15.0",
+ "run-sequence": "^1.1.4",
+ "wiredep": "^3.0.0-beta",
+ "wrench": "^1.5.8"
+ }
+}
diff --git a/auto-scale/gui/spec/.eslintrc b/auto-scale/gui/spec/.eslintrc
new file mode 100644
index 0000000..ad4bc2d
--- /dev/null
+++ b/auto-scale/gui/spec/.eslintrc
@@ -0,0 +1,9 @@
+{
+ "globals" :{
+ "describe": true,
+ "beforeEach": true,
+ "it": true,
+ "inject": true,
+ "expect": true
+ }
+}
diff --git a/auto-scale/gui/spec/autoscaling.test.js b/auto-scale/gui/spec/autoscaling.test.js
new file mode 100644
index 0000000..54caf0e
--- /dev/null
+++ b/auto-scale/gui/spec/autoscaling.test.js
@@ -0,0 +1,48 @@
+'use strict';
+
+describe('In autoscaling app', () => {
+ var scope, element, vm, httpBackend, Service;
+
+
+
+ beforeEach(module('autoscaling'));
+ beforeEach(module('templates'));
+
+ beforeEach(inject(($httpBackend, $rootScope, Autoscaling) => {
+ httpBackend = $httpBackend;
+ scope = $rootScope.$new();
+ Service = Autoscaling;
+ }));
+
+ describe('the serviceContainer', () => {
+ beforeEach(inject(function($httpBackend, $compile){
+
+ httpBackend.whenGET('/autoscaledata').respond(200, autoscalingMock);
+
+ element = angular.element('<service-container></service-container>');
+ $compile(element)(scope);
+
+ scope.$digest();
+ vm = element.isolateScope().vm;
+ httpBackend.flush();
+ }));
+
+ it('should correctly format data', () => {
+ expect(vm.services['service1']).toBeDefined();
+ expect(vm.services['service1']['slice1']).toBeDefined();
+ expect(vm.services['service1']['slice1']['instance1']).toBeDefined();
+
+ expect(vm.services['service1']['slice1']['instance1'][0].counter_volume).toBe(10);
+ expect(vm.services['service1']['slice1']['instance1'][1].counter_volume).toBe(11);
+
+ // triggering the function with 2 resources
+ vm.printData(Service.formatData(autoscalingMock2instances));
+
+ const keys = Object.keys(vm.services['service1']['slice1']);
+
+ expect(vm.services['service1']['slice1'][keys[0]][0].counter_volume).toBe(10);
+ expect(vm.services['service1']['slice1'][keys[0]][1].counter_volume).toBe(11);
+
+ });
+ });
+});
\ No newline at end of file
diff --git a/auto-scale/gui/spec/autoscaling_data.mock.js b/auto-scale/gui/spec/autoscaling_data.mock.js
new file mode 100644
index 0000000..e9f637a
--- /dev/null
+++ b/auto-scale/gui/spec/autoscaling_data.mock.js
@@ -0,0 +1,64 @@
+var autoscalingMock =[
+ {
+ 'slice': 'slice1',
+ 'service': 'service1',
+ 'project_id': 'project1',
+ 'resources': {
+ 'resource1': {
+ 'queue': [
+ {
+ 'timestamp': '2015-12-17T22:55:36Z',
+ 'counter_volume': 10,
+ },
+ {
+ 'timestamp': '2015-12-17T22:55:46Z',
+ 'counter_volume': 11,
+ }
+ ],
+ 'xos_instance_info': {
+ 'instance_name': 'instance1'
+ }
+ }
+ }
+ }
+];
+
+var autoscalingMock2instances =[
+ {
+ 'slice': 'slice1',
+ 'service': 'service1',
+ 'project_id': 'project1',
+ 'resources': {
+ 'resource2': {
+ 'queue': [
+ {
+ 'timestamp': '2015-12-17T22:55:36Z',
+ 'counter_volume': 20,
+ },
+ {
+ 'timestamp': '2015-12-17T22:55:46Z',
+ 'counter_volume': 21,
+ }
+ ],
+ 'xos_instance_info': {
+ 'instance_name': 'instance2'
+ }
+ },
+ 'resource1': {
+ 'queue': [
+ {
+ 'timestamp': '2015-12-17T22:55:36Z',
+ 'counter_volume': 10,
+ },
+ {
+ 'timestamp': '2015-12-17T22:55:46Z',
+ 'counter_volume': 11,
+ }
+ ],
+ 'xos_instance_info': {
+ 'instance_name': 'instance1'
+ }
+ },
+ }
+ }
+];
\ No newline at end of file
diff --git a/auto-scale/gui/src/css/style.css b/auto-scale/gui/src/css/style.css
new file mode 100644
index 0000000..260b6df
--- /dev/null
+++ b/auto-scale/gui/src/css/style.css
@@ -0,0 +1,134 @@
+body {
+ padding-top: 50px;
+}
+
+.list-group-item.active > a {
+ color: white;
+}
+
+/* ANIMATION */
+.animate-repeat{
+ /*background: red;*/
+}
+
+.animate-repeat.ng-move,
+.animate-repeat.ng-enter,
+.animate-repeat.ng-leave {
+ transition:all linear 0.5s;
+}
+
+/* Element Exit */
+.animate-repeat.ng-leave.ng-leave-active,
+.animate-repeat.ng-move,
+.animate-repeat.ng-enter {
+ /*opacity:0;*/
+ /*animation:1.5s bounceOutDown ease;*/
+}
+
+/* Element Enter */
+.animate-repeat.ng-leave,
+.animate-repeat.ng-move.ng-move-active,
+.animate-repeat.ng-enter.ng-enter-active {
+ /*opacity:1;*/
+ /*animation:1.5s bounceOutUp ease;*/
+}
+
+@keyframes bounceInUp {
+ from, 60%, 75%, 90%, to {
+ animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+ }
+
+ from {
+ opacity: 0;
+ transform: translate3d(0, 3000px, 0);
+ }
+
+ 60% {
+ opacity: 1;
+ transform: translate3d(0, -20px, 0);
+ }
+
+ 75% {
+ transform: translate3d(0, 10px, 0);
+ }
+
+ 90% {
+ transform: translate3d(0, -5px, 0);
+ }
+
+ to {
+ transform: translate3d(0, 0, 0);
+ }
+}
+
+@keyframes bounceOutUp {
+ 20% {
+ transform: translate3d(0, -10px, 0);
+ }
+
+ 40%, 45% {
+ opacity: 1;
+ transform: translate3d(0, 20px, 0);
+ }
+
+ to {
+ opacity: 0;
+ transform: translate3d(0, -2000px, 0);
+ }
+}
+
+/* LOADER */
+.loader {
+ font-size: 10px;
+ margin: 150px auto;
+ text-indent: -9999em;
+ width: 11em;
+ height: 11em;
+ border-radius: 50%;
+ background: #ffffff;
+ background: -moz-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
+ background: -webkit-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
+ background: -o-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
+ background: -ms-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
+ background: linear-gradient(to right, #ffffff 10%, rgba(255, 255, 255, 0) 42%);
+ position: relative;
+ -webkit-animation: load3 1.4s infinite linear;
+ animation: load3 1.4s infinite linear;
+ -webkit-transform: translateZ(0);
+ -ms-transform: translateZ(0);
+ transform: translateZ(0);
+}
+.loader:before {
+ width: 50%;
+ height: 50%;
+ background: #105E9E;
+ border-radius: 100% 0 0 0;
+ position: absolute;
+ top: 0;
+ left: 0;
+ content: '';
+}
+.loader:after {
+ background: #fff;
+ width: 75%;
+ height: 75%;
+ border-radius: 50%;
+ content: '';
+ margin: auto;
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+}
+
+@keyframes load3 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
\ No newline at end of file
diff --git a/auto-scale/gui/src/index.html b/auto-scale/gui/src/index.html
new file mode 100644
index 0000000..51bad93
--- /dev/null
+++ b/auto-scale/gui/src/index.html
@@ -0,0 +1,72 @@
+<!-- browserSync -->
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>XOS - Autoscaling Framework</title>
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1, max-scale=1">
+ <!-- bower:css -->
+ <link rel="stylesheet" href="vendor/angular-chart.js/dist/angular-chart.css" />
+ <link rel="stylesheet" href="vendor/bootstrap-css/css/bootstrap.min.css" />
+ <!-- endbower --><!-- endcss -->
+ <!-- inject:css -->
+ <link rel="stylesheet" href="/css/style.css">
+ <!-- endinject -->
+</head>
+<body>
+
+</body>
+</html>
+
+<div ng-app="autoscaling">
+
+ <nav class="navbar navbar-inverse navbar-fixed-top">
+ <div class="container">
+ <div class="navbar-header">
+ <!-- <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button> -->
+ <a class="navbar-brand" href="#">XOS - Autoscaling Framework</a>
+ </div>
+ <!-- <div id="navbar" class="collapse navbar-collapse">
+ <ul class="nav navbar-nav">
+ <li class="active"><a href="#">Home</a></li>
+ <li><a href="#about">About</a></li>
+ <li><a href="#contact">Contact</a></li>
+ </ul>
+ </div><!--/.nav-collapse --> -->
+ </div>
+ </nav>
+
+ <div class="container">
+ <div class="row">
+ <div class="col-xs-12">
+ <div ui-view ng-class="stateName"></div>
+ </div>
+ </div>
+ </div>
+ <!-- bower:js -->
+ <script src="vendor/angular/angular.js"></script>
+ <script src="vendor/Chart.js/Chart.js"></script>
+ <script src="vendor/angular-chart.js/dist/angular-chart.js"></script>
+ <script src="vendor/angular-animate/angular-animate.js"></script>
+ <script src="vendor/ui.bootstrap/src/accordion/accordion.js"></script>
+ <script src="vendor/ui.bootstrap/src/collapse/collapse.js"></script>
+ <script src="vendor/jquery/dist/jquery.js"></script>
+ <script src="vendor/angular-ui-router/release/angular-ui-router.js"></script>
+ <script src="vendor/ng-lodash/build/ng-lodash.js"></script>
+ <script src="vendor/bootstrap-css/js/bootstrap.min.js"></script>
+ <script src="vendor/angular-mocks/angular-mocks.js"></script>
+ <!-- endbower --><!-- endjs -->
+
+ <!-- inject:js -->
+ <script src="/.tmp/main.js"></script>
+ <script src="/.tmp/autoscaling_details.directive.js"></script>
+ <script src="/.tmp/autoscaling.service.js"></script>
+ <!-- endinject -->
+</div>
+
diff --git a/auto-scale/gui/src/js/autoscaling.service.js b/auto-scale/gui/src/js/autoscaling.service.js
new file mode 100644
index 0000000..2762fe7
--- /dev/null
+++ b/auto-scale/gui/src/js/autoscaling.service.js
@@ -0,0 +1,71 @@
+'use strict';
+
+angular.module('autoscaling')
+.service('Autoscaling', function($http, $interval, $rootScope, lodash, $q){
+
+ const pollingFrequency = 10;
+ var pollinginterval;
+
+ /**
+ * Convert data to a flat array of resources
+ */
+
+ this.formatData = (data) => {
+ const list = [];
+ // cicle trough all slices
+ lodash.map(data, (item) => {
+ // cicle trough every resource
+ item.resources = lodash.forEach(
+ Object.keys(item.resources),
+ (resource) => {
+ const tmp = item.resources[resource];
+ tmp.service = item.service;
+ tmp.slice = item.slice;
+ tmp.project_id = item.project_id;
+ tmp.instance_name = tmp.xos_instance_info.instance_name;
+ delete tmp.xos_instance_info;
+ list.push(tmp);
+ }
+ )
+ });
+ return list;
+ };
+
+ function requestData(url){
+
+ const deferred = $q.defer();
+
+ $http.get(url)
+ .success((res) => {
+ deferred.resolve(res);
+ })
+ .error((e) => {
+ deferred.reject(e);
+ });
+
+ return deferred.promise;
+ };
+
+
+ // TODO Move to Websocket
+ this.getAutoscalingData = () => {
+
+ requestData('/autoscaledata')
+ .then((res) => {
+ $rootScope.$emit('autoscaling.update', this.formatData(res));
+ })
+ .catch((e) => {
+ $rootScope.$emit('autoscaling.error', this.formatData(e));
+ });
+
+ pollinginterval = $interval(() => {
+ requestData('/autoscaledata')
+ .then((res) => {
+ $rootScope.$emit('autoscaling.update', this.formatData(res));
+ })
+ .catch((e) => {
+ $rootScope.$emit('autoscaling.error', this.formatData(e));
+ });
+ }, pollingFrequency * 1000)
+ };
+});
\ No newline at end of file
diff --git a/auto-scale/gui/src/js/autoscaling_details.directive.js b/auto-scale/gui/src/js/autoscaling_details.directive.js
new file mode 100644
index 0000000..76cce2d
--- /dev/null
+++ b/auto-scale/gui/src/js/autoscaling_details.directive.js
@@ -0,0 +1,197 @@
+angular.module('autoscaling')
+.directive('serviceContainer', function(lodash, Autoscaling){
+ return {
+ restrict: 'E',
+ scope: {},
+ bindToController: true,
+ controllerAs: 'vm',
+ templateUrl: 'templates/service-container.tpl.html',
+ controller: function($rootScope) {
+
+ this.loader = true;
+
+ // set to true when a service is manually selected
+ this.manualSelect = false;
+
+ // start polling
+ Autoscaling.getAutoscalingData();
+
+ // list to polling events
+ $rootScope.$on('autoscaling.update', (evt, data) => {
+
+ if (data.length > 0) {
+ this.loader = false;
+ };
+ this.printData(data);
+ });
+
+ // handle errors
+ $rootScope.$on('autoscaling.error', (evt, err) => {
+ this.loader = false;
+ this.error = err.data.message;
+ });
+
+ /**
+ * Group resources by service and slice
+ */
+ this.printData = (data) => {
+ this.services = lodash.groupBy(data, 'service');
+ lodash.forEach(Object.keys(this.services), (service) => {
+ this.services[service] = lodash.groupBy(this.services[service], 'slice');
+ lodash.forEach(Object.keys(this.services[service]), (slice) => {
+ // grouping instance by name
+ this.services[service][slice] = lodash.groupBy(this.services[service][slice], 'instance_name');
+ // instance can't have the same name,
+ // so take them out of an array
+ // and keep only the sample data
+ lodash.forEach(Object.keys(this.services[service][slice]), (instance) => {
+ // TODO maintain the instance order
+ this.services[service][slice][instance] = this.services[service][slice][instance][0].queue;
+ });
+
+ })
+ });
+ // arbitrary set the first service in the list as the selected one
+ if(!this.manualSelect){
+ this.serviceName = Object.keys(this.services)[0];
+ this.selectedService = this.services[Object.keys(this.services)[0]];
+ }
+ else{
+ this.selectedService = this.services[this.serviceName]
+ }
+ };
+
+ /**
+ * Change the current selected service
+ */
+
+ this.selectService = (serviceName) => {
+ this.serviceName = serviceName;
+ this.selectedService = this.services[serviceName];
+ this.manualSelect = true;
+ };
+ }
+ };
+})
+.directive('serviceDetail', function(lodash){
+ return {
+ restrict: 'E',
+ scope: {
+ service: '=service'
+ },
+ bindToController: true,
+ controllerAs: 'vm',
+ templateUrl: 'templates/service-detail.tpl.html',
+ controller: function($scope) {
+
+ }
+ };
+})
+.directive('sliceDetail', function(lodash){
+ return {
+ restrict: 'E',
+ scope: {
+ instances: '=instances'
+ },
+ bindToController: true,
+ controllerAs: 'vm',
+ templateUrl: 'templates/slice-detail.tpl.html',
+ controller: function($scope, $timeout) {
+
+ this.chart = {
+ options: {
+ datasetFill: false,
+ animation: true,
+ // animationEasing: 'easeInBack'
+ }
+ };
+
+ this.chartColors = [
+ '#286090',
+ '#F7464A',
+ '#46BFBD',
+ '#FDB45C',
+ '#97BBCD',
+ '#4D5360',
+ '#8c4f9f'
+ ];
+
+ Chart.defaults.global.colours = this.chartColors;
+
+ /**
+ * Goes trough the array and format date to be used as labels
+ *
+ * @param Array data
+ * @returns Array a list of labels
+ */
+
+ this.getLabels = (data) => {
+ // we should compare the labels and get the last available
+ return this.prependValues(
+ data.reduce((list, item) => {
+ let date = new Date(item.timestamp);
+ list.push(`${date.getHours()}:${(date.getMinutes()<10?'0':'') + date.getMinutes()}:${date.getSeconds()}`);
+ return list;
+ }, [])
+ , '');
+ };
+
+ /**
+ * Prepend value if the array is less than 10 element
+ */
+ this.prependValues = (list, value) => {
+ if(list.length < 10){
+ list.unshift(value);
+ // call itself to check again
+ return this.prependValues(list, value);
+ }
+ return list;
+ }
+
+ /**
+ * Convert an object of array,
+ * in an array of arrays of values
+ */
+ this.getData = (data, instanceNames) => {
+ return lodash.map(instanceNames, (item) => {
+ return this.prependValues(lodash.reduce(data[item], (list, sample) => {
+ // console.log(data[item], sample);
+ list.push(sample.counter_volume);
+ return list;
+ }, []), null);
+ });
+ };
+
+ this.getMostRecentSeries = (instances) => {
+ // console.log(instances);
+ const newestValues = [];
+ instances = lodash.toArray(instances)
+ lodash.forEach(instances, (values) => {
+ newestValues.push(lodash.max(values, item => new Date(item.timestamp)));
+ });
+
+ var highestValue = 0;
+ var newestInstanceIndex = lodash.findIndex(newestValues, (val) => {
+ return new Date(val.timestamp) > highestValue;
+ });
+
+ return instances[newestInstanceIndex]
+ }
+
+ this.drawChart = (data) => {
+
+ const instanceNames = Object.keys(data);
+
+ this.chart.labels = this.getLabels(this.getMostRecentSeries(data));
+ this.chart.series = instanceNames;
+ this.chart.data = this.getData(data, instanceNames);
+ }
+
+ $scope.$watch(() => this.instances, (val) => {
+ $timeout(()=>{this.chart.options.animation = false}, 1000);
+ this.drawChart(val)
+ });
+
+ }
+ };
+});
\ No newline at end of file
diff --git a/auto-scale/gui/src/js/main.js b/auto-scale/gui/src/js/main.js
new file mode 100644
index 0000000..468f355
--- /dev/null
+++ b/auto-scale/gui/src/js/main.js
@@ -0,0 +1,17 @@
+'use strict';
+
+angular.module('autoscaling', [
+ 'ngLodash',
+ 'ui.router',
+ 'ngAnimate',
+ 'chart.js'
+])
+.config(($stateProvider, $urlRouterProvider) => {
+ $stateProvider
+ .state('ceilometerDashboard', {
+ url: '/',
+ template: '<service-container></service-container>'
+ });
+
+ $urlRouterProvider.otherwise('/');
+});
diff --git a/auto-scale/gui/src/mocks/mock.json b/auto-scale/gui/src/mocks/mock.json
new file mode 100644
index 0000000..aa91dd2
--- /dev/null
+++ b/auto-scale/gui/src/mocks/mock.json
@@ -0,0 +1 @@
+[{"slice": "mysite_onos_volt", "service": "service_ONOS_vOLT", "lthreadshold_count": 1, "alarm": "scale_down_eval", "uthreadshold_count": 0, "project_id": "bfef8341327245d682c7bada50aceecb", "resources": {"71863bc9-1840-4014-9a7c-fee8d94d3f77": {"queue": [{"counter_name": "cpu_util", "resource_id": "71863bc9-1840-4014-9a7c-fee8d94d3f77", "timestamp": "2015-12-16T23:44:02Z", "counter_unit": "%", "counter_volume": 10.6, "project_id": "bfef8341327245d682c7bada50aceecb"}, {"counter_name": "cpu_util", "resource_id": "71863bc9-1840-4014-9a7c-fee8d94d3f77", "timestamp": "2015-12-16T23:44:12Z", "counter_unit": "%", "counter_volume": 11.1, "project_id": "bfef8341327245d682c7bada50aceecb"}, {"counter_name": "cpu_util", "resource_id": "71863bc9-1840-4014-9a7c-fee8d94d3f77", "timestamp": "2015-12-16T23:44:22Z", "counter_unit": "%", "counter_volume": 10.799999999999999, "project_id": "bfef8341327245d682c7bada50aceecb"}, {"counter_name": "cpu_util", "resource_id": "71863bc9-1840-4014-9a7c-fee8d94d3f77", "timestamp": "2015-12-16T23:44:32Z", "counter_unit": "%", "counter_volume": 9.9, "project_id": "bfef8341327245d682c7bada50aceecb"}, {"counter_name": "cpu_util", "resource_id": "71863bc9-1840-4014-9a7c-fee8d94d3f77", "timestamp": "2015-12-16T23:44:42Z", "counter_unit": "%", "counter_volume": 11.2, "project_id": "bfef8341327245d682c7bada50aceecb"}, {"counter_name": "cpu_util", "resource_id": "71863bc9-1840-4014-9a7c-fee8d94d3f77", "timestamp": "2015-12-16T23:44:52Z", "counter_unit": "%", "counter_volume": 11.1, "project_id": "bfef8341327245d682c7bada50aceecb"}], "xos_instance_info": {"instance_name": "mysite_onos_volt-4"}}}}, {"slice": "mysite_onos_vbng", "service": "service_ONOS_vBNG", "lthreadshold_count": 1, "alarm": "scale_down_eval", "uthreadshold_count": 0, "project_id": "7c918e6765c24ee281f3ee8692fd102c", "resources": {"4065c7d0-62bc-486a-ad3e-87603d2ab33b": {"queue": [{"counter_name": "cpu_util", "resource_id": "4065c7d0-62bc-486a-ad3e-87603d2ab33b", "timestamp": "2015-12-16T23:44:02Z", "counter_unit": "%", "counter_volume": 9.7, "project_id": "7c918e6765c24ee281f3ee8692fd102c"}, {"counter_name": "cpu_util", "resource_id": "4065c7d0-62bc-486a-ad3e-87603d2ab33b", "timestamp": "2015-12-16T23:44:12Z", "counter_unit": "%", "counter_volume": 11.5, "project_id": "7c918e6765c24ee281f3ee8692fd102c"}, {"counter_name": "cpu_util", "resource_id": "4065c7d0-62bc-486a-ad3e-87603d2ab33b", "timestamp": "2015-12-16T23:44:22Z", "counter_unit": "%", "counter_volume": 12.399999999999999, "project_id": "7c918e6765c24ee281f3ee8692fd102c"}, {"counter_name": "cpu_util", "resource_id": "4065c7d0-62bc-486a-ad3e-87603d2ab33b", "timestamp": "2015-12-16T23:44:32Z", "counter_unit": "%", "counter_volume": 11.299999999999999, "project_id": "7c918e6765c24ee281f3ee8692fd102c"}, {"counter_name": "cpu_util", "resource_id": "4065c7d0-62bc-486a-ad3e-87603d2ab33b", "timestamp": "2015-12-16T23:44:42Z", "counter_unit": "%", "counter_volume": 11.799999999999999, "project_id": "7c918e6765c24ee281f3ee8692fd102c"}, {"counter_name": "cpu_util", "resource_id": "4065c7d0-62bc-486a-ad3e-87603d2ab33b", "timestamp": "2015-12-16T23:44:52Z", "counter_unit": "%", "counter_volume": 12.299999999999999, "project_id": "7c918e6765c24ee281f3ee8692fd102c"}], "xos_instance_info": {"instance_name": "mysite_onos_vbng-2"}}}}, {"slice": "mysite_vcpe", "service": "service_vcpe", "lthreadshold_count": 1, "alarm": "scale_down_eval", "uthreadshold_count": 0, "project_id": "61a5d4b37b5d43718854916079760c0d", "resources": {"41625531-8243-4471-b435-574618df0484": {"queue": [{"counter_name": "cpu_util", "resource_id": "41625531-8243-4471-b435-574618df0484", "timestamp": "2015-12-16T23:44:02Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "61a5d4b37b5d43718854916079760c0d"}, {"counter_name": "cpu_util", "resource_id": "41625531-8243-4471-b435-574618df0484", "timestamp": "2015-12-16T23:44:12Z", "counter_unit": "%", "counter_volume": 0.0, "project_id": "61a5d4b37b5d43718854916079760c0d"}, {"counter_name": "cpu_util", "resource_id": "41625531-8243-4471-b435-574618df0484", "timestamp": "2015-12-16T23:44:22Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "61a5d4b37b5d43718854916079760c0d"}, {"counter_name": "cpu_util", "resource_id": "41625531-8243-4471-b435-574618df0484", "timestamp": "2015-12-16T23:44:32Z", "counter_unit": "%", "counter_volume": 0.0, "project_id": "61a5d4b37b5d43718854916079760c0d"}, {"counter_name": "cpu_util", "resource_id": "41625531-8243-4471-b435-574618df0484", "timestamp": "2015-12-16T23:44:42Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "61a5d4b37b5d43718854916079760c0d"}, {"counter_name": "cpu_util", "resource_id": "41625531-8243-4471-b435-574618df0484", "timestamp": "2015-12-16T23:44:52Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "61a5d4b37b5d43718854916079760c0d"}], "xos_instance_info": {"instance_name": "mysite_vcpe-7"}}}}, {"slice": "mysite_volt", "service": null, "lthreadshold_count": 1, "alarm": "scale_down_eval", "uthreadshold_count": 0, "project_id": "f506380a49a24389ae0d8469274e3279", "resources": {"9452c9e2-e358-4302-ba79-0d19b7b9be84": {"queue": [{"counter_name": "cpu_util", "resource_id": "9452c9e2-e358-4302-ba79-0d19b7b9be84", "timestamp": "2015-12-16T23:44:02Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "f506380a49a24389ae0d8469274e3279"}, {"counter_name": "cpu_util", "resource_id": "9452c9e2-e358-4302-ba79-0d19b7b9be84", "timestamp": "2015-12-16T23:44:12Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "f506380a49a24389ae0d8469274e3279"}, {"counter_name": "cpu_util", "resource_id": "9452c9e2-e358-4302-ba79-0d19b7b9be84", "timestamp": "2015-12-16T23:44:22Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "f506380a49a24389ae0d8469274e3279"}, {"counter_name": "cpu_util", "resource_id": "9452c9e2-e358-4302-ba79-0d19b7b9be84", "timestamp": "2015-12-16T23:44:32Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "f506380a49a24389ae0d8469274e3279"}, {"counter_name": "cpu_util", "resource_id": "9452c9e2-e358-4302-ba79-0d19b7b9be84", "timestamp": "2015-12-16T23:44:42Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "f506380a49a24389ae0d8469274e3279"}, {"counter_name": "cpu_util", "resource_id": "9452c9e2-e358-4302-ba79-0d19b7b9be84", "timestamp": "2015-12-16T23:44:52Z", "counter_unit": "%", "counter_volume": 0.0, "project_id": "f506380a49a24389ae0d8469274e3279"}], "xos_instance_info": {"instance_name": "mysite_volt-5"}}}}, {"slice": "mysite_vbng", "service": null, "lthreadshold_count": 1, "alarm": "scale_down_eval", "uthreadshold_count": 0, "project_id": "c92cf450df4640d6952c0276730e5048", "resources": {"40aec721-b7bc-49b2-9e1f-1f2a9005cb41": {"queue": [{"counter_name": "cpu_util", "resource_id": "40aec721-b7bc-49b2-9e1f-1f2a9005cb41", "timestamp": "2015-12-16T23:44:02Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "c92cf450df4640d6952c0276730e5048"}, {"counter_name": "cpu_util", "resource_id": "40aec721-b7bc-49b2-9e1f-1f2a9005cb41", "timestamp": "2015-12-16T23:44:12Z", "counter_unit": "%", "counter_volume": 0.19999999999999998, "project_id": "c92cf450df4640d6952c0276730e5048"}, {"counter_name": "cpu_util", "resource_id": "40aec721-b7bc-49b2-9e1f-1f2a9005cb41", "timestamp": "2015-12-16T23:44:22Z", "counter_unit": "%", "counter_volume": 0.0, "project_id": "c92cf450df4640d6952c0276730e5048"}, {"counter_name": "cpu_util", "resource_id": "40aec721-b7bc-49b2-9e1f-1f2a9005cb41", "timestamp": "2015-12-16T23:44:32Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "c92cf450df4640d6952c0276730e5048"}, {"counter_name": "cpu_util", "resource_id": "40aec721-b7bc-49b2-9e1f-1f2a9005cb41", "timestamp": "2015-12-16T23:44:42Z", "counter_unit": "%", "counter_volume": 0.0, "project_id": "c92cf450df4640d6952c0276730e5048"}, {"counter_name": "cpu_util", "resource_id": "40aec721-b7bc-49b2-9e1f-1f2a9005cb41", "timestamp": "2015-12-16T23:44:52Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "c92cf450df4640d6952c0276730e5048"}], "xos_instance_info": {"instance_name": "mysite_vbng-6"}}}}, {"slice": "mysite_clients", "service": null, "lthreadshold_count": 1, "alarm": "scale_down_eval", "uthreadshold_count": 0, "project_id": "f884c440474b40808a8adcdece5f45ef", "resources": {"036742d1-ff67-4c5b-8bde-58d0fc752b1d": {"queue": [{"counter_name": "cpu_util", "resource_id": "036742d1-ff67-4c5b-8bde-58d0fc752b1d", "timestamp": "2015-12-16T23:44:02Z", "counter_unit": "%", "counter_volume": 0.19999999999999998, "project_id": "f884c440474b40808a8adcdece5f45ef"}, {"counter_name": "cpu_util", "resource_id": "036742d1-ff67-4c5b-8bde-58d0fc752b1d", "timestamp": "2015-12-16T23:44:12Z", "counter_unit": "%", "counter_volume": 0.0, "project_id": "f884c440474b40808a8adcdece5f45ef"}, {"counter_name": "cpu_util", "resource_id": "036742d1-ff67-4c5b-8bde-58d0fc752b1d", "timestamp": "2015-12-16T23:44:22Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "f884c440474b40808a8adcdece5f45ef"}, {"counter_name": "cpu_util", "resource_id": "036742d1-ff67-4c5b-8bde-58d0fc752b1d", "timestamp": "2015-12-16T23:44:32Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "f884c440474b40808a8adcdece5f45ef"}, {"counter_name": "cpu_util", "resource_id": "036742d1-ff67-4c5b-8bde-58d0fc752b1d", "timestamp": "2015-12-16T23:44:42Z", "counter_unit": "%", "counter_volume": 0.0, "project_id": "f884c440474b40808a8adcdece5f45ef"}, {"counter_name": "cpu_util", "resource_id": "036742d1-ff67-4c5b-8bde-58d0fc752b1d", "timestamp": "2015-12-16T23:44:52Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "f884c440474b40808a8adcdece5f45ef"}], "xos_instance_info": {"instance_name": "mysite_clients-3"}}}}, {"slice": "mysite_ceilometer", "service": "service_ceilometer", "lthreadshold_count": 1, "alarm": "scale_down_eval", "uthreadshold_count": 0, "project_id": "920d70bc63574552bbb3c3fb262ee1bc", "resources": {"627f7de4-634a-4205-8eb4-93c40a34e862": {"queue": [{"counter_name": "cpu_util", "resource_id": "627f7de4-634a-4205-8eb4-93c40a34e862", "timestamp": "2015-12-16T23:44:02Z", "counter_unit": "%", "counter_volume": 0.19999999999999998, "project_id": "920d70bc63574552bbb3c3fb262ee1bc"}, {"counter_name": "cpu_util", "resource_id": "627f7de4-634a-4205-8eb4-93c40a34e862", "timestamp": "2015-12-16T23:44:12Z", "counter_unit": "%", "counter_volume": 0.19999999999999998, "project_id": "920d70bc63574552bbb3c3fb262ee1bc"}, {"counter_name": "cpu_util", "resource_id": "627f7de4-634a-4205-8eb4-93c40a34e862", "timestamp": "2015-12-16T23:44:22Z", "counter_unit": "%", "counter_volume": 0.19999999999999998, "project_id": "920d70bc63574552bbb3c3fb262ee1bc"}, {"counter_name": "cpu_util", "resource_id": "627f7de4-634a-4205-8eb4-93c40a34e862", "timestamp": "2015-12-16T23:44:32Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "920d70bc63574552bbb3c3fb262ee1bc"}, {"counter_name": "cpu_util", "resource_id": "627f7de4-634a-4205-8eb4-93c40a34e862", "timestamp": "2015-12-16T23:44:42Z", "counter_unit": "%", "counter_volume": 0.09999999999999999, "project_id": "920d70bc63574552bbb3c3fb262ee1bc"}, {"counter_name": "cpu_util", "resource_id": "627f7de4-634a-4205-8eb4-93c40a34e862", "timestamp": "2015-12-16T23:44:52Z", "counter_unit": "%", "counter_volume": 0.19999999999999998, "project_id": "920d70bc63574552bbb3c3fb262ee1bc"}], "xos_instance_info": {"instance_name": "mysite_ceilometer-8"}}}}]
\ No newline at end of file
diff --git a/auto-scale/gui/src/templates/service-container.tpl.html b/auto-scale/gui/src/templates/service-container.tpl.html
new file mode 100644
index 0000000..34cdcca
--- /dev/null
+++ b/auto-scale/gui/src/templates/service-container.tpl.html
@@ -0,0 +1,26 @@
+<div class="row">
+ <div class="col-xs-12">
+ <h1>Service detail</h1>
+ </div>
+</div>
+<div class="row">
+ <div class="col-sm-12" ng-show="vm.loader">
+ <div class="loader"></div>
+ </div>
+ <div class="col-sm-3">
+ <ul class="list-group">
+ <li class="list-group-item" ng-class="{active: vm.serviceName == service}" ng-repeat="(service, slices) in vm.services">
+ <a href="" ng-click="vm.selectService(service)">{{service}}</a>
+ </li>
+ </ul>
+ </div>
+
+ <div class="col-sm-9 repeat-container" ng-show="!vm.error">
+ <service-detail service="vm.selectedService"></service-detail>
+ </div>
+ <div class="col-sm-9" ng-show="vm.error">
+ <div class="alert alert-danger">
+ {{vm.error}}
+ </div>
+ </div>
+</div>
\ No newline at end of file
diff --git a/auto-scale/gui/src/templates/service-detail.tpl.html b/auto-scale/gui/src/templates/service-detail.tpl.html
new file mode 100644
index 0000000..f0e3636
--- /dev/null
+++ b/auto-scale/gui/src/templates/service-detail.tpl.html
@@ -0,0 +1,8 @@
+<div class="animate-repeat panel panel-default" ng-repeat="(slice, instances) in vm.service">
+ <div class="panel-heading">
+ <h3 class="panel-title">{{slice}}</h3>
+ </div>
+ <div class="panel-body">
+ <slice-detail instances="instances"></slice-detail>
+ </div>
+</div>
\ No newline at end of file
diff --git a/auto-scale/gui/src/templates/slice-detail.tpl.html b/auto-scale/gui/src/templates/slice-detail.tpl.html
new file mode 100644
index 0000000..6e63d5b
--- /dev/null
+++ b/auto-scale/gui/src/templates/slice-detail.tpl.html
@@ -0,0 +1,4 @@
+<canvas id="line" class="chart chart-line" chart-data="vm.chart.data"
+ chart-labels="vm.chart.labels" chart-legend="true" chart-series="vm.chart.series"
+ chart-options="vm.chart.options">
+</canvas>
\ No newline at end of file
diff --git a/auto-scale/xos_auto_scaling_app.py b/auto-scale/xos_auto_scaling_app.py
new file mode 100644
index 0000000..848ccc0
--- /dev/null
+++ b/auto-scale/xos_auto_scaling_app.py
@@ -0,0 +1,333 @@
+import socket
+import requests
+import urllib2
+import json
+import msgpack
+import collections
+import time, thread, threading
+
+from flask import request, Request, jsonify
+from flask import Flask
+from flask import make_response
+app = Flask(__name__)
+
+projects_map = {}
+xos_tenant_info_map = {}
+xos_instances_info_map = {}
+
+use_kafka = True
+
+if use_kafka:
+ import kafka
+ from kafka import TopicPartition
+else:
+ UDP_IP = "0.0.0.0"
+ UDP_PORT = 12346
+
+@app.route('/autoscaledata',methods=['GET'])
+def autoscaledata():
+ response = app.make_response(json.dumps(projects_map.values()))
+ response.mimetype="application/json"
+ return response
+
+def acquire_xos_monitoring_channel():
+ url = "http://ctl:9999/xoslib/monitoringchannel/"
+ admin_auth=("padmin@vicci.org", "letmein") # use your XOS username and password
+ monitoring_channels = requests.get(url, auth=admin_auth).json()
+ ceilometer_url = None
+ if not monitoring_channels:
+ print 'SRIKANTH: No monitoring channels for this user...'
+ return None
+ else:
+ monitoring_channel = monitoring_channels[0]
+ while not monitoring_channel['ceilometer_url']:
+ print 'SRIKANTH: Waiting for monitoring channel create'
+ sleep(0.5)
+ monitoring_channel = requests.get(url, auth=admin_auth).json()[0]
+ #TODO: Wait until URL is completely UP
+ while True:
+ print 'SRIKANTH: Waiting for ceilometer proxy URL %s is available' % monitoring_channel['ceilometer_url']
+ try:
+ response = urllib2.urlopen(monitoring_channel['ceilometer_url'],timeout=1)
+ break
+ except urllib2.HTTPError, e:
+ print 'SRIKANTH: HTTP error %s' % e.reason
+ break
+ except urllib2.URLError, e:
+ print 'SRIKANTH: URL error %s' % e.reason
+ pass
+ return monitoring_channel
+
+def print_samples():
+ print ""
+ print ""
+ for project in projects_map.keys():
+ print "service=%s slice=%s, alarm_state=%s" % (projects_map[project]['service'], projects_map[project]['slice'] if projects_map[project]['slice'] else project, projects_map[project]['alarm'])
+ for resource in projects_map[project]['resources'].keys():
+ print "resource=%s" % (projects_map[project]['resources'][resource]['xos_instance_info']['instance_name'] if projects_map[project]['resources'][resource]['xos_instance_info'] else resource)
+ for i in projects_map[project]['resources'][resource]['queue']:
+ print " time=%s val=%s" % ( i['timestamp'],i['counter_volume'])
+
+def periodic_print():
+ print_samples()
+ #Print every 1minute
+ threading.Timer(20, periodic_print).start()
+
+
+CPU_UPPER_THRESHOLD = 80 #80%
+CPU_LOWER_THRESHOLD = 30 #30%
+CPU_THRESHOLD_REPEAT = 3
+INITIAL_STATE = 'normal_config'
+SCALE_UP_EVALUATION = 'scale_up_eval'
+SCALE_DOWN_EVALUATION = 'scale_down_eval'
+SCALE_UP_ALARM = 'scale_up'
+SCALE_DOWN_ALARM = 'scale_down'
+
+def loadAllXosTenantInfo():
+ print "SRIKANTH: Loading all XOS tenant info"
+ url = "http://ctl:9999/xos/controllerslices/"
+ admin_auth=("padmin@vicci.org", "letmein") # use your XOS username and password
+ controller_slices = requests.get(url, auth=admin_auth).json()
+ for cslice in controller_slices:
+ slice = requests.get(cslice['slice'], auth=admin_auth).json()
+ slice_name = slice['humanReadableName']
+ if slice['service']:
+ service = requests.get(slice['service'], auth=admin_auth).json()
+ service_name = service['humanReadableName']
+ else:
+ service_name = None
+ xos_tenant_info_map[cslice['tenant_id']] = {'service':service_name, 'slice':slice_name}
+ print "SRIKANTH: Project: %s Service:%s Slice:%s" % (cslice['tenant_id'],service_name,slice_name)
+
+def loadAllXosInstanceInfo():
+ print "SRIKANTH: Loading all XOS instance info"
+ url = "http://ctl:9999/xos/instances/"
+ admin_auth=("padmin@vicci.org", "letmein") # use your XOS username and password
+ xos_instances = requests.get(url, auth=admin_auth).json()
+ for instance in xos_instances:
+ xos_instances_info_map[instance['instance_uuid']] = {'instance_name':instance['instance_name']}
+
+def getXosTenantInfo(project):
+ xos_tenant_info = xos_tenant_info_map.get(project, None)
+ if xos_tenant_info:
+ return xos_tenant_info
+ else:
+ loadAllXosTenantInfo()
+ xos_tenant_info = xos_tenant_info_map.get(project, None)
+ if not xos_tenant_info:
+ print "SRIKANTH: Project %s has no associated XOS slice" % project
+ return xos_tenant_info
+
+def getXosInstanceInfo(resource):
+ xos_instance_info = xos_instances_info_map.get(resource, None)
+ if xos_instance_info:
+ return xos_instance_info
+ else:
+ loadAllXosInstanceInfo()
+ xos_instance_info = xos_instances_info_map.get(resource, None)
+ if not xos_instance_info:
+ print "SRIKANTH: Resource %s has no associated XOS instance" % project
+ return xos_instance_info
+
+def handle_adjust_scale(project, adjust):
+ if (adjust != 'up') and (adjust != 'down'):
+ print "SRIKANTH: Invalid adjust value %s " % adjust
+ return
+ current_instances = len(projects_map[project]['resources'].keys())
+ if (current_instances <=1 and adjust == 'down'):
+ print "SRIKANTH: %s is running with already minimum instances and can not scale down further " % project
+ return
+ if (current_instances >=2 and adjust == 'up'):
+ print "SRIKANTH: %s is running with already maximum instances and can not scale up further " % project
+ return
+ #xos_tenant = getXosTenantInfo(project)
+ xos_service = projects_map[project]['service']
+ xos_slice = projects_map[project]['slice']
+ if not xos_service or not xos_slice:
+ print "SRIKANTH: Can not handle adjust_scale for Project %s because not associated with any service or slice" % project
+ return
+ print "SRIKANTH: SCALE %s for Project %s, Slice=%s, Service=%s from current=%d to new=%d" % (adjust, project, xos_slice, xos_service, current_instances, current_instances+1 if (adjust=='up') else current_instances-1)
+ query_params = {'service':xos_service, 'slice_hint':xos_slice, 'scale':current_instances+1 if (adjust=='up') else current_instances-1}
+ url = "http://ctl:9999/xoslib/serviceadjustscale/"
+ admin_auth=("padmin@vicci.org", "letmein") # use your XOS username and password
+ response = requests.get(url, params=query_params, auth=admin_auth).json()
+ print "SRIKANTH: XOS adjust_scale response: %s" % response
+
+def periodic_cpu_threshold_evaluator():
+ for project in projects_map.keys():
+ aggregate_cpu_util = sum([resource['queue'][-1]['counter_volume'] \
+ for resource in projects_map[project]['resources'].values()]) \
+ /len(projects_map[project]['resources'].keys())
+
+ if (projects_map[project]['alarm'] == INITIAL_STATE or
+ projects_map[project]['alarm'] == SCALE_UP_ALARM or
+ projects_map[project]['alarm'] == SCALE_DOWN_ALARM):
+ if aggregate_cpu_util > CPU_UPPER_THRESHOLD:
+ projects_map[project]['uthreadshold_count'] = 1
+ projects_map[project]['alarm'] = SCALE_UP_EVALUATION
+ if projects_map[project]['uthreadshold_count'] >= CPU_THRESHOLD_REPEAT:
+ projects_map[project]['alarm'] = SCALE_UP_ALARM
+ handle_adjust_scale(project, 'up')
+ elif aggregate_cpu_util < CPU_LOWER_THRESHOLD:
+ projects_map[project]['lthreadshold_count'] = 1
+ projects_map[project]['alarm'] = SCALE_DOWN_EVALUATION
+ if projects_map[project]['lthreadshold_count'] >= CPU_THRESHOLD_REPEAT:
+ projects_map[project]['alarm'] = SCALE_DOWN_ALARM
+ handle_adjust_scale(project, 'down')
+ else:
+ projects_map[project]['uthreadshold_count'] = 0
+ projects_map[project]['lthreadshold_count'] = 0
+ projects_map[project]['alarm'] = INITIAL_STATE
+ elif projects_map[project]['alarm'] == SCALE_UP_EVALUATION:
+ if aggregate_cpu_util > CPU_UPPER_THRESHOLD:
+ projects_map[project]['uthreadshold_count'] += 1
+ if projects_map[project]['uthreadshold_count'] >= CPU_THRESHOLD_REPEAT:
+ projects_map[project]['alarm'] = SCALE_UP_ALARM
+ handle_adjust_scale(project, 'up')
+ elif aggregate_cpu_util < CPU_LOWER_THRESHOLD:
+ projects_map[project]['lthreadshold_count'] += 1
+ projects_map[project]['alarm'] = SCALE_DOWN_EVALUATION
+ else:
+ projects_map[project]['uthreadshold_count'] = 0
+ projects_map[project]['alarm'] = INITIAL_STATE
+ elif projects_map[project]['alarm'] == SCALE_DOWN_EVALUATION:
+ if aggregate_cpu_util < CPU_LOWER_THRESHOLD:
+ projects_map[project]['lthreadshold_count'] += 1
+ if projects_map[project]['lthreadshold_count'] >= CPU_THRESHOLD_REPEAT:
+ projects_map[project]['alarm'] = SCALE_DOWN_ALARM
+ handle_adjust_scale(project, 'down')
+ elif aggregate_cpu_util > CPU_UPPER_THRESHOLD:
+ projects_map[project]['uthreadshold_count'] += 1
+ projects_map[project]['alarm'] = SCALE_UP_EVALUATION
+ else:
+ projects_map[project]['lthreadshold_count'] = 0
+ projects_map[project]['alarm'] = INITIAL_STATE
+ threading.Timer(20, periodic_cpu_threshold_evaluator).start()
+
+def read_notification_from_ceilometer_over_kafka(host,port,topic):
+ print "Kafka target" , host, port, topic
+ try :
+ consumer=kafka.KafkaConsumer(bootstrap_servers=["%s:%s" % (host,port)])
+ consumer.assign([TopicPartition(topic,0)])
+ consumer.seek_to_end()
+ for message in consumer:
+ #print message.value
+ #logging.debug("%s",message.value)
+ process_notification_from_ceilometer(json.loads(message.value))
+ #status = process_ceilometer_message(json.loads(message.value),message.value)
+ #print status
+ except Exception as e:
+ print "AUTO_SCALE Exception:",e
+
+def read_notification_from_ceilometer(host,port):
+ udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
+ udp.bind((host, port))
+
+ while True:
+ data, source = udp.recvfrom(64000)
+ try:
+ sample = msgpack.loads(data, encoding='utf-8')
+ process_notification_from_ceilometer(sample)
+ except Exception as e:
+ print e
+
+def process_notification_from_ceilometer(sample):
+ if sample['counter_name'] == 'instance':
+ if 'delete' in sample['resource_metadata']['event_type']:
+ xosTenantInfo = getXosTenantInfo(sample['project_id'])
+ xosResourceInfo = getXosInstanceInfo(sample['resource_id'])
+ print "SRIKANTH: Project %s Instance %s is getting deleted" % (xosTenantInfo['slice'] if xosTenantInfo['slice'] else sample['project_id'],xosResourceInfo)
+ if sample['project_id'] not in projects_map.keys():
+ return
+ if sample['resource_id'] not in projects_map[sample['project_id']]['resources'].keys():
+ return
+ projects_map[sample['project_id']]['resources'].pop(sample['resource_id'], None)
+ return
+ elif sample['counter_name'] != 'cpu_util':
+ return
+ if sample['project_id'] not in projects_map.keys():
+ projects_map[sample['project_id']] = {}
+ xosTenantInfo = getXosTenantInfo(sample['project_id'])
+ projects_map[sample['project_id']]['project_id'] = sample['project_id']
+ projects_map[sample['project_id']]['slice'] = xosTenantInfo['slice']
+ projects_map[sample['project_id']]['service'] = xosTenantInfo['service']
+ projects_map[sample['project_id']]['resources'] = {}
+ projects_map[sample['project_id']]['uthreadshold_count'] = 0
+ projects_map[sample['project_id']]['lthreadshold_count'] = 0
+ projects_map[sample['project_id']]['alarm'] = INITIAL_STATE
+ resource_map = projects_map[sample['project_id']]['resources']
+ if sample['resource_id'] not in resource_map.keys():
+ resource_map[sample['resource_id']] = {}
+ resource_map[sample['resource_id']]['xos_instance_info'] = getXosInstanceInfo(sample['resource_id'])
+ resource_map[sample['resource_id']]['queue'] = []
+ samples_queue = resource_map[sample['resource_id']]['queue']
+ sample = {'counter_name':sample['counter_name'],
+ 'project_id':sample['project_id'],
+ 'resource_id':sample['resource_id'],
+ 'timestamp':sample['timestamp'],
+ 'counter_unit':sample['counter_unit'],
+ 'counter_volume':sample['counter_volume']}
+ deque = collections.deque(samples_queue, maxlen=10)
+ deque.append(sample)
+ resource_map[sample['resource_id']]['queue'] = list(deque)
+
+def setup_webserver():
+ try:
+ #config = ConfigParser.ConfigParser()
+ #config.read('pub_sub.conf')
+ #webserver_host = config.get('WEB_SERVER','webserver_host')
+ #webserver_port = int (config.get('WEB_SERVER','webserver_port'))
+ #client_host = config.get('CLIENT','client_host')
+ #client_port = int (config.get('CLIENT','client_port'))
+
+ #log_level = config.get('LOGGING','level')
+ #log_file = config.get('LOGGING','filename')
+
+ #level = LEVELS.get(log_level, logging.NOTSET)
+ #logging.basicConfig(filename=log_file,format='%(asctime)s %(levelname)s %(message)s',\
+ # datefmt=_DEFAULT_LOG_DATE_FORMAT,level=level)
+ webserver_host = '0.0.0.0'
+ webserver_port = 9991
+
+ except Exception as e:
+ print("* Error in config file:",e.__str__())
+ logging.error("* Error in confing file:%s",e.__str__())
+ else:
+ app.run(host=webserver_host,port=webserver_port,debug=True, use_reloader=False)
+
+
+def main():
+ monitoring_channel = acquire_xos_monitoring_channel()
+ if not monitoring_channel:
+ print 'SRIKANTH: XOS monitoring_channel is not created... Create it before using this app'
+ return
+ loadAllXosTenantInfo()
+ loadAllXosInstanceInfo()
+ ceilometer_url = monitoring_channel['ceilometer_url']
+ if use_kafka:
+ thread.start_new(read_notification_from_ceilometer_over_kafka, ("10.11.10.1","9092","auto-scale",))
+ subscribe_data = {"sub_info":"cpu_util","app_id":"xos_auto_scale","target":"kafka://10.11.10.1:9092?topic=auto-scale"}
+ else:
+ thread.start_new(read_notification_from_ceilometer,(UDP_IP,UDP_PORT,))
+ subscribe_data = {"sub_info":"cpu_util","app_id":"xos_auto_scale","target":"udp://10.11.10.1:12346"}
+ subscribe_url = ceilometer_url + 'v2/subscribe'
+ response = requests.post(subscribe_url, data=json.dumps(subscribe_data))
+ print 'SRIKANTH: Ceilometer meter "cpu_util" Subscription status:%s' % response.text
+ #TODO: Fix the typo in 'sucess'
+ if (not 'sucess' in response.text) and (not 'already exists' in response.text):
+ print 'SRIKANTH: Ceilometer meter "cpu_util" Subscription unsuccessful...Exiting'
+ return
+ subscribe_data = {"sub_info":"instance","app_id":"xos_auto_scale2","target":"udp://10.11.10.1:12346"}
+ subscribe_url = ceilometer_url + 'v2/subscribe'
+ response = requests.post(subscribe_url, data=json.dumps(subscribe_data))
+ print 'SRIKANTH: Ceilometer meter "instance" Subscription status:%s' % response.text
+ #TODO: Fix the typo in 'sucess'
+ if (not 'sucess' in response.text) and (not 'already exists' in response.text):
+ print 'SRIKANTH: Ceilometer meter "instance"Subscription unsuccessful...Exiting'
+ return
+ periodic_cpu_threshold_evaluator()
+ periodic_print()
+ setup_webserver()
+
+if __name__ == "__main__":
+ main()