Merge branch 'master' into xos_auto_scaling_app_changes
diff --git a/.gitignore b/.gitignore
index cc49c01..451dd79 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@
xos/core/xoslib/docs
xos/core/xoslib/coverage
node_modules
+xos/xos_configuration/*
diff --git a/containers/synchronizer/Dockerfile b/containers/synchronizer/Dockerfile
index f9d79ae..7170e1c 100644
--- a/containers/synchronizer/Dockerfile
+++ b/containers/synchronizer/Dockerfile
@@ -10,6 +10,9 @@
python-httplib2 \
supervisor
+RUN pip install -U \
+ jinja2
+
RUN \
git clone -b release1.8.2 git://github.com/ansible/ansible.git /opt/ansible && \
git clone -b release1.8.2 git://github.com/ansible/ansible-modules-extras.git /opt/ansible/lib/ansible/modules/extras && \
@@ -41,4 +44,4 @@
# Supervisor
RUN cp /tmp/xos/containers/synchronizer/conf/synchronizer.conf /etc/supervisor/conf.d/
-CMD /usr/bin/supervisord -c /etc/supervisor/conf.d/synchronizer.conf
+CMD update-ca-certificates && /usr/bin/supervisord -c /etc/supervisor/conf.d/synchronizer.conf
diff --git a/containers/synchronizer/Makefile b/containers/synchronizer/Makefile
index 8620438..3b10312 100644
--- a/containers/synchronizer/Makefile
+++ b/containers/synchronizer/Makefile
@@ -6,7 +6,7 @@
build: ; docker build --no-cache=${NO_DOCKER_CACHE} --rm -t ${IMAGE_NAME} .
.PHONY: run
-run: ; docker run -d --name ${CONTAINER_NAME} ${IMAGE_NAME}
+run: ; docker run -d --name ${CONTAINER_NAME} -v /usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro ${IMAGE_NAME}
.PHONY: stop
stop: ; docker stop ${CONTAINER_NAME}
diff --git a/containers/xos/Dockerfile b/containers/xos/Dockerfile
index 38e08a1..639cbd5 100644
--- a/containers/xos/Dockerfile
+++ b/containers/xos/Dockerfile
@@ -1,4 +1,4 @@
-FROM ubuntu:14.04.3
+FROM python:2.7.11
# XXX Workaround for docker bug:
# https://github.com/docker/docker/issues/6345
@@ -36,12 +36,11 @@
python-glanceclient \
python-ceilometerclient
-RUN pip install -U \
+RUN pip install \
django==1.7 \
django-bitfield \
django-crispy-forms \
django-encrypted-fields \
- django_evolution \
django-extensions \
django-filter \
django-geoposition \
@@ -51,20 +50,27 @@
django-timezones \
djangorestframework==2.4.4 \
dnslib \
- google_api_python_client \
- httplib2 \
- httplib2.ca_certs_locater \
lxml \
markdown \
netaddr \
+ pyOpenSSL \
+ psycopg2 \
+ python-ceilometerclient \
python-dateutil \
- python_gflags \
python-keyczar \
pygraphviz \
pytz \
pyyaml \
requests
+RUN easy_install --upgrade httplib2
+
+RUN easy_install \
+ django_evolution \
+ python_gflags \
+ google_api_python_client \
+ httplib2.ca_certs_locater
+
ADD http://code.jquery.com/jquery-1.9.1.min.js /usr/local/lib/python2.7/dist-packages/suit/static/suit/js/
# Install XOS
@@ -74,6 +80,7 @@
/opt/xos/scripts/opencloud genkeys
# install Tosca engine
+RUN chmod +x /opt/xos/tosca/run.py
RUN bash /opt/xos/tosca/install_tosca.sh
EXPOSE 8000
@@ -82,7 +89,7 @@
ENV HOME /root
# Define working directory.
-WORKDIR /root
+WORKDIR /opt/xos
# Define default command.
-CMD python /opt/xos/manage.py runserver 0.0.0.0:8000 --insecure
+CMD update-ca-certificates && python manage.py runserver 0.0.0.0:8000 --insecure
diff --git a/containers/xos/Makefile b/containers/xos/Makefile
index 367f0ec..833046b 100644
--- a/containers/xos/Makefile
+++ b/containers/xos/Makefile
@@ -6,13 +6,13 @@
NO_DOCKER_CACHE?=false
.PHONY: build
-build: ; docker build --no-cache=${NO_DOCKER_CACHE} --rm -t ${IMAGE_NAME} .
+build: ; make -C ../../xos/configurations/common -f Makefile.cloudlab; docker build --no-cache=${NO_DOCKER_CACHE} --rm -t ${IMAGE_NAME} .
.PHONY: custom
custom: ; cat Dockerfile.templ | sed -e "s|XOS_GIT_REPO|${XOS_GIT_REPO}|g" -e "s|XOS_GIT_BRANCH|${XOS_GIT_BRANCH}|g" | docker build --no-cache=${NO_DOCKER_CACHE} -
.PHONY: run
-run: ; docker run -d --name ${CONTAINER_NAME} -p 80:8000 ${IMAGE_NAME}
+run: ; docker run -d --name ${CONTAINER_NAME} -p 80:8000 -v /usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro ${IMAGE_NAME}
.PHONY: runtosca
runtosca: ; docker exec -it ${CONTAINER_NAME} /usr/bin/python /opt/xos/tosca/run.py padmin@vicci.org ${TOSCA_CONFIG_PATH}
diff --git a/containers/xos/initdb b/containers/xos/initdb
index 41e0a9a..3252747 100755
--- a/containers/xos/initdb
+++ b/containers/xos/initdb
@@ -5,13 +5,12 @@
DB_HOST=$(wget http://ipinfo.io/ip -qO -)
# configure db host
-docker run -it --name=$CONTAINER_NAME $IMAGE_NAME sed -i '0,/host/{s/host=localhost/host='$DB_HOST'/}' /opt/xos/xos_config
+docker run -it --name=$CONTAINER_NAME $IMAGE_NAME sed -i '0,/host/{s/host=localhost/host='$DB_HOST'/}' /opt/xos/xos_configuration/xos_common_config
docker commit $CONTAINER_NAME $IMAGE_NAME
docker rm $CONTAINER_NAME
# init db schema
docker run -it --name=$CONTAINER_NAME $IMAGE_NAME /opt/xos/scripts/opencloud makemigrations
# run overrides the CMD specifed in the Dockerfile, so we re-set the CMD in the final commit"
-echo docker commit --change="CMD python /opt/xos/manage.py runserver 0.0.0.0:8000 --insecure" $CONTAINER_NAME $IMAGE_NAME
-docker commit --change="CMD python /opt/xos/manage.py runserver 0.0.0.0:8000 --insecure" $CONTAINER_NAME $IMAGE_NAME
+docker commit --change="CMD update-ca-certificates && python /opt/xos/manage.py runserver 0.0.0.0:8000 --insecure" $CONTAINER_NAME $IMAGE_NAME
docker rm $CONTAINER_NAME
diff --git a/xos/configurations/cord/Makefile b/xos/configurations/cord/Makefile
index ef823cd..80b71e0 100644
--- a/xos/configurations/cord/Makefile
+++ b/xos/configurations/cord/Makefile
@@ -2,7 +2,7 @@
RUNNING_CONTAINER:=$(shell sudo docker ps|grep "xos"|awk '{print $$NF}')
LAST_CONTAINER=$(shell sudo docker ps -l -q)
-cord: common_cloudlab ceilometer_dashboard virtualbng_json
+cord: common_cloudlab ceilometer_dashboard virtualbng_json vtn_network_cfg_json
rm ../../xos_configuration/*
echo "# Autogenerated -- do not edit" > Dockerfile
cat ../common/Dockerfile.common Dockerfile.cord >> Dockerfile
@@ -24,6 +24,9 @@
virtualbng_json:
bash make-virtualbng-json.sh
+vtn_network_cfg_json:
+ bash make-vtn-networkconfig-json.sh
+
stop:
sudo docker stop $(RUNNING_CONTAINER)
@@ -32,3 +35,27 @@
enter:
sudo docker exec -t -i $(RUNNING_CONTAINER) bash
+
+dataplane: etc_hosts
+ cd dataplane; ./gen-inventory.sh > hosts
+ cd dataplane; ansible-playbook -i hosts dataplane.yaml
+
+dataplane_bm: dataplane
+ cd dataplane; bash -c "./generate-bm.sh > hosts-bm"
+ cd dataplane; sudo bash -c "ansible-playbook -i hosts-bm dataplane-bm.yaml"
+
+etc_hosts:
+ sudo bash -c "sed -i '/^10.11.10/ d' /etc/hosts"
+ cd dataplane; sudo bash -c "./gen-etc-hosts.sh >> /etc/hosts"
+
+setup_client:
+ # add subscriber to vOLT? Is there a REST API?
+ echo "Don't forget: add-subscriber-access of:0000000000000001 1 432"
+ cd dataplane; ansible -i hosts client -m shell -s -a "route del default gw 10.11.10.5; dhclient br-sub"
+ # reboot the vBNG ONOS
+ cd dataplane; ansible -i hosts onos_vbng -m shell -s -a "docker restart ONOS"
+
+cleanup: stop
+ cd dataplane; ./cleanup.sh
+ bash -c "source ../common/admin-openrc.sh; nova list --all-tenants; neutron net-list"
+ echo "Don't forget to clean up containers"
diff --git a/xos/configurations/cord/cord.yaml b/xos/configurations/cord/cord.yaml
index 5d95d7a..8aa4676 100644
--- a/xos/configurations/cord/cord.yaml
+++ b/xos/configurations/cord/cord.yaml
@@ -437,6 +437,7 @@
container_format: na
disk_format: na
path: andybavier/docker-vcpe
+ tag: develop
# A subscriber
My House:
@@ -451,7 +452,7 @@
Mom's PC:
type: tosca.nodes.CORDUser
properties:
- mac: 010203040506
+ mac: 01:02:03:04:05:06
level: PG_13
requirements:
- household:
@@ -461,7 +462,7 @@
Dad's PC:
type: tosca.nodes.CORDUser
properties:
- mac: 90E2Ba82F975
+ mac: 90:E2:BA:82:F9:75
level: PG_13
requirements:
- household:
@@ -471,7 +472,7 @@
Jack's Laptop:
type: tosca.nodes.CORDUser
properties:
- mac: 685B359D91D5
+ mac: 68:5B:35:9D:91:D5
level: PG_13
requirements:
- household:
@@ -481,7 +482,7 @@
Jill's Laptop:
type: tosca.nodes.CORDUser
properties:
- mac: 34363BC9B6A6
+ mac: 34:36:3B:C9:B6:A6
level: PG_13
requirements:
- household:
diff --git a/xos/configurations/cord/dataplane/cleanup.sh b/xos/configurations/cord/dataplane/cleanup.sh
new file mode 100755
index 0000000..721f054
--- /dev/null
+++ b/xos/configurations/cord/dataplane/cleanup.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+function cleanup_network {
+ NETWORK=$1
+ SUBNETS=`neutron net-show $NETWORK | grep -i subnets | awk '{print $4}'`
+ if [[ $SUBNETS != "" ]]; then
+ PORTS=`neutron port-list | grep -i $SUBNETS | awk '{print $2}'`
+ for PORT in $PORTS; do
+ echo "Deleting port $PORT"
+ neutron port-delete $PORT
+ done
+ fi
+ neutron net-delete $NETWORK
+}
+
+source ../../common/admin-openrc.sh
+
+echo "Deleting VMs"
+# Delete all VMs
+VMS=$( nova list --all-tenants|grep mysite|awk '{print $2}' )
+for VM in $VMS
+do
+ nova delete $VM
+done
+
+echo "Waiting 5 seconds..."
+sleep 5
+
+cleanup_network wan_network
+cleanup_network mysite_vcpe-private
+
+echo "Deleting networks"
+# Delete all networks beginning with mysite_
+NETS=$( neutron net-list --all-tenants|grep mysite|awk '{print $2}' )
+for NET in $NETS
+do
+ neutron net-delete $NET
+done
+
+neutron net-delete lan_network || true
+neutron net-delete subscriber_network || true
+neutron net-delete public_network || true
+neutron net-delete hpc_client_network || true
+neutron net-delete ceilometer_network || true
diff --git a/xos/configurations/cord/dataplane/dataplane.yaml b/xos/configurations/cord/dataplane/dataplane.yaml
index a0950be..3ca3bbe 100644
--- a/xos/configurations/cord/dataplane/dataplane.yaml
+++ b/xos/configurations/cord/dataplane/dataplane.yaml
@@ -49,6 +49,7 @@
- name: Remove IP address on public_network
command: /sbin/ifconfig {{ public_net.stdout }} 0.0.0.0
+ when: public_net.stdout
- name: Change datapath ID of bridge to match config file
command: /usr/bin/ovs-vsctl set bridge br-vbng other-config:datapath-id={{ ovs_dpid }}
diff --git a/xos/configurations/cord/make-vtn-networkconfig-json.sh b/xos/configurations/cord/make-vtn-networkconfig-json.sh
new file mode 100644
index 0000000..df94597
--- /dev/null
+++ b/xos/configurations/cord/make-vtn-networkconfig-json.sh
@@ -0,0 +1,58 @@
+FN=vtn-network-cfg.json
+
+rm -f $FN
+
+cat >> $FN <<EOF
+{
+ "apps" : {
+ "org.onosproject.cordvtn" : {
+ "cordvtn" : {
+ "nodes" : [
+EOF
+
+NODES=$( sudo bash -c "source /root/setup/admin-openrc.sh ; nova hypervisor-list" |grep cloudlab|awk '{print $4}' )
+
+NODECOUNT=0
+for NODE in $NODES; do
+ ((NODECOUNT++))
+done
+
+I=0
+for NODE in $NODES; do
+ echo $NODE
+ NODEIP=`getent hosts $NODE | awk '{ print $1 }'`
+
+ ((I++))
+ cat >> $FN <<EOF
+ {
+ "hostname": "$NODE",
+ "ovsdbIp": "$NODEIP",
+ "ovsdbPort": "6640",
+ "bridgeId": "of:000000000000000$I"
+EOF
+ if [[ "$I" -lt "$NODECOUNT" ]]; then
+ echo " }," >> $FN
+ else
+ echo " }" >> $FN
+ fi
+done
+
+NEUTRONIP="127.0.0.1"
+KEYSTONEIP="127.0.0.1"
+
+cat >> $FN <<EOF
+ ]
+ }
+ },
+ "org.onosproject.openstackswitching" : {
+ "openstackswitching" : {
+ "do_not_push_flows" : "true",
+ "neutron_server" : "http://$NEUTRONIP:9696/v2.0/",
+ "keystone_server" : "http://$KEYSTONEIP:5000/v2.0/",
+ "user_name" : "admin",
+ "password" : "passwd"
+ }
+ }
+ }
+}
+EOF
diff --git a/xos/cord/models.py b/xos/cord/models.py
index daac40c..1a81419 100644
--- a/xos/cord/models.py
+++ b/xos/cord/models.py
@@ -689,7 +689,7 @@
def save_instance(self, instance):
with transaction.atomic():
- instance.volumes = "/etc/dnsmasq.d"
+ instance.volumes = "/etc/dnsmasq.d,/etc/ufw"
super(VCPETenant, self).save_instance(instance)
if instance.isolation in ["container", "container_vm"]:
diff --git a/xos/core/static/xos.css b/xos/core/static/xos.css
index 40cfcc3..685361e 100644
--- a/xos/core/static/xos.css
+++ b/xos/core/static/xos.css
@@ -62,6 +62,7 @@
-moz-transition: all 0.5s ease;
-o-transition: all 0.5s ease;
transition: all 0.5s ease;
+ background: white;
}
#wrapper.toggled #sidebar-wrapper {
diff --git a/xos/core/xoslib/dashboards/xosCeilometerDashboard.html b/xos/core/xoslib/dashboards/xosCeilometerDashboard.html
index 1db309a..22511f4 100644
--- a/xos/core/xoslib/dashboards/xosCeilometerDashboard.html
+++ b/xos/core/xoslib/dashboards/xosCeilometerDashboard.html
@@ -3,8 +3,9 @@
<!-- browserSync -->
<!-- inject:css -->
-<link rel="stylesheet" href="/static/css/ceilometerDashboard.css">
+<link rel="stylesheet" href="/../../static/css/xosCeilometerDashboard.css">
<!-- endinject -->
+<link rel="stylesheet" href="css/ceilometerDashboard.css">
<div id="xosCeilometerDashboard">
<div ui-view ng-class="stateName"></div>
</div>
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/.eslintrc b/xos/core/xoslib/ngXosViews/ceilometerDashboard/.eslintrc
index f495e79..868fd4b 100644
--- a/xos/core/xoslib/ngXosViews/ceilometerDashboard/.eslintrc
+++ b/xos/core/xoslib/ngXosViews/ceilometerDashboard/.eslintrc
@@ -16,7 +16,7 @@
],
"rules": {
"quotes": [2, "single"],
- "camelcase": [1, {"properties": "always"}],
+ "camelcase": [0, {"properties": "always"}],
"no-underscore-dangle": 1,
"eqeqeq": [2, "smart"],
"no-alert": 1,
@@ -38,6 +38,7 @@
//"angular/ng_di": [0, "function or array"]
},
"globals" :{
- "angular": true
+ "angular": true,
+ "Chart": true
}
}
\ No newline at end of file
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/env/default.js b/xos/core/xoslib/ngXosViews/ceilometerDashboard/env/default.js
index 26dd953..8fc0bd5 100644
--- a/xos/core/xoslib/ngXosViews/ceilometerDashboard/env/default.js
+++ b/xos/core/xoslib/ngXosViews/ceilometerDashboard/env/default.js
@@ -8,6 +8,6 @@
module.exports = {
host: 'http://clnode015.clemson.cloudlab.us:9999/',
- xoscsrftoken: 'kL0QrqJ5Y3TbTR6mLgPBGVCMNv2JksQv',
- xossessionid: '8ygydigw52qgc715fgznj15k7nfe1139'
+ xoscsrftoken: 'w0qMPZCk8nByQh9Zy86nPs5OM5G5lKdb',
+ xossessionid: 'kvv2aa1il6u4i7j6yu2399r4tdpdeoxz'
};
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/gulp/build.js b/xos/core/xoslib/ngXosViews/ceilometerDashboard/gulp/build.js
index d0d38f9..9f9bfb8 100644
--- a/xos/core/xoslib/ngXosViews/ceilometerDashboard/gulp/build.js
+++ b/xos/core/xoslib/ngXosViews/ceilometerDashboard/gulp/build.js
@@ -22,6 +22,10 @@
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('/')});
@@ -37,6 +41,28 @@
);
});
+ // 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([
@@ -67,12 +93,15 @@
.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 + 'js/xosCeilometerDashboard.js',
+ options.static + 'css/xosCeilometerDashboard.css'
])
)
)
@@ -107,11 +136,13 @@
gulp.task('build', function() {
runSequence(
+ 'lint',
'templates',
'babel',
'scripts',
'wiredep',
'copyHtml',
+ 'copyCss',
'cleanTmp'
);
});
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/gulp/server.js b/xos/core/xoslib/ngXosViews/ceilometerDashboard/gulp/server.js
index d176950..7268b13 100644
--- a/xos/core/xoslib/ngXosViews/ceilometerDashboard/gulp/server.js
+++ b/xos/core/xoslib/ngXosViews/ceilometerDashboard/gulp/server.js
@@ -107,7 +107,7 @@
options.api + '*.js',
options.helpers + '**/*.js'
])
- .pipe(debug({title: 'unicorn:'}))
+ // .pipe(debug({title: 'unicorn:'}))
.pipe(angularFilesort()),
{
ignorePath: [options.src, '/../../ngXosLib']
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/gulpfile.js b/xos/core/xoslib/ngXosViews/ceilometerDashboard/gulpfile.js
index f114774..de911e9 100644
--- a/xos/core/xoslib/ngXosViews/ceilometerDashboard/gulpfile.js
+++ b/xos/core/xoslib/ngXosViews/ceilometerDashboard/gulpfile.js
@@ -5,6 +5,7 @@
var options = {
src: 'src/',
+ css: 'src/css/',
scripts: 'src/js/',
tmp: 'src/.tmp',
dist: 'dist/',
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/karma.conf.js b/xos/core/xoslib/ngXosViews/ceilometerDashboard/karma.conf.js
index dbd344a..12d0697 100644
--- a/xos/core/xoslib/ngXosViews/ceilometerDashboard/karma.conf.js
+++ b/xos/core/xoslib/ngXosViews/ceilometerDashboard/karma.conf.js
@@ -10,9 +10,8 @@
var bowerComponents = wiredep( {devDependencies: true} )[ 'js' ].map(function( file ){
return path.relative(process.cwd(), file);
});
-
-module.exports = function(config) {
/*eslint-enable*/
+module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
@@ -45,10 +44,17 @@
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'src/js/**/*.js': ['babel'],
- 'spec/**/*.test.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
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/package.json b/xos/core/xoslib/ngXosViews/ceilometerDashboard/package.json
index ba3a8fd..0a9ccae 100644
--- a/xos/core/xoslib/ngXosViews/ceilometerDashboard/package.json
+++ b/xos/core/xoslib/ngXosViews/ceilometerDashboard/package.json
@@ -7,6 +7,7 @@
"start": "gulp serve",
"prebuild": "npm install && bower install",
"build": "gulp",
+ "pretest": "npm install",
"test": "karma start",
"lint": "eslint src/js/"
},
@@ -19,7 +20,12 @@
"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",
@@ -33,12 +39,21 @@
"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",
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/spec/.eslintrc b/xos/core/xoslib/ngXosViews/ceilometerDashboard/spec/.eslintrc
new file mode 100644
index 0000000..ad4bc2d
--- /dev/null
+++ b/xos/core/xoslib/ngXosViews/ceilometerDashboard/spec/.eslintrc
@@ -0,0 +1,9 @@
+{
+ "globals" :{
+ "describe": true,
+ "beforeEach": true,
+ "it": true,
+ "inject": true,
+ "expect": true
+ }
+}
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/spec/backend.mock.js b/xos/core/xoslib/ngXosViews/ceilometerDashboard/spec/backend.mock.js
new file mode 100644
index 0000000..c7642bb
--- /dev/null
+++ b/xos/core/xoslib/ngXosViews/ceilometerDashboard/spec/backend.mock.js
@@ -0,0 +1,98 @@
+'use strict';
+(function () {
+
+ const meters = [
+ // service_1
+ // - slice_1
+ // - resource_1
+ // service_2
+ // - slice_2
+ // - resource_2
+ // - resource_3
+ // - slice_3
+ // - resource_4
+ {
+ service: 'service_1',
+ slice: 'slice_1',
+ resource_name: 'resource_1',
+ resource_id: 'resource_id_1',
+ name: 'instance_1',
+ unit: 'instance'
+ },
+ {
+ service: 'service_2',
+ slice: 'slice_2',
+ resource_name: 'resource_2',
+ resource_id: 'resource_id_2',
+ name: 'instance_2',
+ unit: 'instance'
+ },
+ {
+ service: 'service_2',
+ slice: 'slice_2',
+ resource_name: 'resource_3',
+ resource_id: 'resource_id_3',
+ name: 'instance_2',
+ unit: 'instance'
+ },
+ {
+ service: 'service_2',
+ slice: 'slice_3',
+ resource_name: 'resource_4',
+ resource_id: 'resource_id_4',
+ name: 'instance_3',
+ unit: 'instance'
+ }
+ ];
+
+ const samples = [
+ {
+ meter: 'cpu',
+ resource_name: 'fakeName',
+ project_id: 'fakeTenant',
+ timestamp: '2015-12-15T00:34:08',
+ volume: 110
+ },
+ {
+ meter: 'cpu',
+ resource_name: 'fakeName',
+ project_id: 'fakeTenant',
+ timestamp: '2015-12-15T00:44:08',
+ volume: 120
+ },
+ {
+ meter: 'cpu',
+ resource_name: 'anotherName',
+ project_id: 'anotherTenant',
+ timestamp: '2015-12-15T00:24:08',
+ volume: 210
+ },
+ {
+ meter: 'cpu',
+ resource_name: 'anotherName',
+ project_id: 'anotherTenant',
+ timestamp: '2015-12-15T00:34:08',
+ volume: 220
+ },
+ {
+ meter: 'cpu',
+ resource_name: 'anotherName',
+ project_id: 'anotherTenant',
+ timestamp: '2015-12-15T00:44:08',
+ volume: 230
+ },
+ {
+ meter: 'cpu',
+ resource_name: 'thirdName',
+ project_id: 'thirdTenant',
+ timestamp: '2015-12-15T00:44:08',
+ volume: 310
+ }
+ ];
+
+ angular.module('xos.ceilometerDashboard')
+ .run(($httpBackend) => {
+ $httpBackend.whenGET(/metersamples/).respond(samples);
+ $httpBackend.whenGET(/meters/).respond(meters);
+ });
+})();
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/spec/ceilometer.test.js b/xos/core/xoslib/ngXosViews/ceilometerDashboard/spec/ceilometer.test.js
new file mode 100644
index 0000000..c5bb850
--- /dev/null
+++ b/xos/core/xoslib/ngXosViews/ceilometerDashboard/spec/ceilometer.test.js
@@ -0,0 +1,137 @@
+'use strict';
+
+describe('In Ceilometer View', () => {
+
+ var scope, element, vm, httpBackend;
+
+ beforeEach(module('xos.ceilometerDashboard'));
+ beforeEach(module('templates'));
+
+ beforeEach(inject(($httpBackend, $rootScope) => {
+ httpBackend = $httpBackend;
+ scope = $rootScope.$new();
+ }))
+
+ describe('The dashboard', () => {
+ beforeEach(inject(function($httpBackend, $compile){
+ element = angular.element('<ceilometer-dashboard></ceilometer-dashboard>');
+ $compile(element)(scope);
+ scope.$digest();
+ vm = element.isolateScope().vm;
+ httpBackend.flush();
+ }));
+
+ describe('when loading meters', () => {
+ it('should group meters by services', () => {
+ expect(Object.keys(vm.projects).length).toBe(2);
+ });
+
+ it('should group services by slices', () => {
+ expect(Object.keys(vm.projects.service_2).length).toBe(2);
+ });
+
+ it('should group slices by resources', () => {
+ expect(Object.keys(vm.projects.service_2.slice_2).length).toBe(2);
+ });
+ });
+ });
+
+ describe('the sample view', () => {
+ beforeEach(inject(function($httpBackend, $compile, $stateParams){
+
+ $stateParams.name = 'fakeName';
+ $stateParams.tenant = 'fakeTenant';
+
+ element = angular.element('<ceilometer-samples></ceilometer-samples>');
+ $compile(element)(scope);
+ scope.$digest();
+ vm = element.isolateScope().vm;
+ httpBackend.flush();
+ }));
+
+ it('should group samples by resource_id', () => {
+ expect(Object.keys(vm.samplesList.fakeTenant).length).toBe(2)
+ expect(Object.keys(vm.samplesList.anotherTenant).length).toBe(3)
+ expect(Object.keys(vm.samplesList.thirdTenant).length).toBe(1)
+ });
+
+ it('should add the comparable samples to the dropdown list', () => {
+ expect(vm.sampleLabels[0].id).toEqual('anotherTenant')
+ expect(vm.sampleLabels[1].id).toEqual('thirdTenant')
+ });
+
+ it('should add the selected meter to the chart', () => {
+ expect(vm.chart.labels.length).toBe(2);
+ expect(vm.chart.series[0]).toBe('fakeTenant');
+ expect(vm.chart.data[0].length).toBe(2);
+ expect(vm.chart.data[0][0]).toBe(110);
+ expect(vm.chart.data[0][1]).toBe(120);
+ expect(vm.chartMeters[0].project_id).toBe('fakeTenant')
+ expect(vm.chartMeters[0].resource_name).toBe('fakeName')
+ });
+
+ it('should add a sample to the chart', () => {
+ vm.addMeterToChart('anotherTenant');
+ expect(vm.chart.labels.length).toBe(3);
+ expect(vm.chart.data[1].length).toBe(3);
+ expect(vm.chart.data[1][0]).toBe(210);
+ expect(vm.chart.data[1][1]).toBe(220);
+ expect(vm.chart.data[1][2]).toBe(230);
+ expect(vm.chartMeters[1].project_id).toBe('anotherTenant')
+ expect(vm.chartMeters[1].resource_name).toBe('anotherName')
+ });
+
+ it('should remove a sample from the chart', () => {
+ // for simplyvity add a tenant (it's tested)
+ vm.addMeterToChart('anotherTenant');
+ vm.removeFromChart(vm.chartMeters[0]);
+ expect(vm.chart.data[0].length).toBe(3);
+ expect(vm.chart.data[0][0]).toBe(210);
+ expect(vm.chart.data[0][1]).toBe(220);
+ expect(vm.chart.data[0][2]).toBe(230);
+ expect(vm.chartMeters[0].project_id).toBe('anotherTenant')
+ expect(vm.chartMeters[0].resource_name).toBe('anotherName')
+ });
+
+ describe('The format sample labels method', () => {
+ it('should create an array of unique labels', () => {
+ // unique because every resource has multiple samples (time-series)
+ const samples = [
+ {project_id: 1, resource_name: 'fakeName'},
+ {project_id: 1, resource_name: 'fakeName'},
+ {project_id: 2, resource_name: 'anotherName'},
+ {project_id: 2, resource_name: 'anotherName'}
+ ];
+
+ const result = vm.formatSamplesLabels(samples);
+
+ expect(result.length).toBe(2);
+ expect(result[0]).toEqual({id: 1, name: 'fakeName'});
+ expect(result[1]).toEqual({id: 2, name: 'anotherName'});
+ });
+ });
+ });
+});
+
+describe('The orderObjectByKey filter', () => {
+ var $filter;
+
+ beforeEach(function () {
+ module('xos.ceilometerDashboard');
+
+ inject(function (_$filter_) {
+ $filter = _$filter_;
+ });
+ });
+
+ it('should order an object by the key value', function () {
+ // Arrange.
+ const list = {c: 3, b: 2, a: 1};
+
+ // call the filter function
+ const result = $filter('orderObjectByKey')(list);
+
+ // Assert.
+ expect(result).toEqual({a: 1, b: 2, c: 3});
+ });
+});
\ No newline at end of file
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/spec/sample.test.js b/xos/core/xoslib/ngXosViews/ceilometerDashboard/spec/sample.test.js
deleted file mode 100644
index a224095..0000000
--- a/xos/core/xoslib/ngXosViews/ceilometerDashboard/spec/sample.test.js
+++ /dev/null
@@ -1,34 +0,0 @@
-'use strict';
-
-describe('The User List', () => {
-
- var scope, element, isolatedScope, httpBackend;
-
- beforeEach(module('xos.ceilometerDashboard'));
- beforeEach(module('templates'));
-
- beforeEach(inject(function($httpBackend, $compile, $rootScope){
-
- httpBackend = $httpBackend;
- // Setting up mock request
- $httpBackend.expectGET(/meters/).respond([
- {
- email: 'teo@onlab.us',
- firstname: 'Matteo',
- lastname: 'Scandolo'
- }
- ]);
-
- scope = $rootScope.$new();
- element = angular.element('<ceilometer-dashboard></ceilometer-dashboard>');
- $compile(element)(scope);
- scope.$digest();
- isolatedScope = element.isolateScope().vm;
- }));
-
- it('should load 1 users', () => {
- httpBackend.flush();
- expect(isolatedScope.meters.length).toBe(1);
- });
-
-});
\ No newline at end of file
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/css/ceilometerDashboard.css b/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/css/ceilometerDashboard.css
index 9b53d98..879d7b4 100644
--- a/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/css/ceilometerDashboard.css
+++ b/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/css/ceilometerDashboard.css
@@ -1,267 +1,263 @@
-/*ceilometer-dashboard{*/
+#xosCeilometerDashboard{
+ position: relative;
+}
- #xosCeilometerDashboard{
- position: relative;
+/* Panel Layout */
+.panel {
+ margin-top: 10px;
+}
+
+.panel-body:not(:first-child) {
+ border-top: 1px solid #e3e3e3;
+}
+
+.panel-body .row {
+ margin-top: 10px;
+}
+
+/* Chart details */
+.chart {
+ width: 100%;
+ height: 300px;
+}
+
+.btn-chart, .btn-chart:hover {
+ color: #fff;
+}
+
+.side-container {
+ position: relative;
+}
+
+.service-list {
+ margin-top: -10px;
+}
+
+.service-list h3 {
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+.service-list a {
+ text-decoration: none;
+ color: #333;
+}
+
+.meters, .stats {
+ margin-top: 25px;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+}
+
+/* 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;
+}
+@-webkit-keyframes load3 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+@keyframes load3 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+
+/* VIEW ANIMATION */
+
+[ui-view] {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 100%;
+}
+
+/* FROM DASHBOARD TO DETAIL */
+
+/* dash out */
+[ui-view].ceilometerDashboard.ng-leave {
+ animation:1s bounceOutLeft ease;
+}
+/* samples in */
+[ui-view].samples.ng-enter {
+ animation:1s bounceInRight ease;
+}
+
+/* FROM DETAIL TO DASHBOARD */
+
+/* samples out */
+[ui-view].samples.ng-leave {
+ animation:1s bounceOutRight ease;
+}
+/* dash in */
+[ui-view].ceilometerDashboard.ng-enter {
+ animation:1s bounceInLeft ease;
+}
+
+/* COLUMS ANIMATION */
+/* when showing the thing */
+.animate .animate-slide-left.ng-hide-remove {
+ animation:0.5s bounceInRight ease;
+}
+
+/* when hiding the picture */
+.animate .animate-slide-left.ng-hide-add {
+ animation:0.5s bounceOutRight ease;
+}
+
+/* ANIMATIONS */
+
+@keyframes bounceInRight {
+ from, 60%, 75%, 90%, to {
+ animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
}
- /* Panel Layout */
- .panel {
- margin-top: 10px;
+ from {
+ opacity: 0;
+ transform: translate3d(3000px, 0, 0);
}
- .panel-body:not(:first-child) {
- border-top: 1px solid #e3e3e3;
+ 60% {
+ opacity: 1;
+ transform: translate3d(-25px, 0, 0);
}
- .panel-body .row {
- margin-top: 10px;
+ 75% {
+ transform: translate3d(10px, 0, 0);
}
- /* Chart details */
- .chart {
- width: 100%;
- height: 300px;
+ 90% {
+ transform: translate3d(-5px, 0, 0);
}
- .btn-chart, .btn-chart:hover {
- color: #fff;
+ to {
+ transform: none;
+ }
+}
+
+@keyframes bounceInLeft {
+ from, 60%, 75%, 90%, to {
+ animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
}
- .side-container {
- position: relative;
+ 0% {
+ opacity: 0;
+ transform: translate3d(-3000px, 0, 0);
}
- .service-list {
- margin-top: -10px;
+ 60% {
+ opacity: 1;
+ transform: translate3d(25px, 0, 0);
}
- .service-list h3 {
- margin-top: 0px;
- margin-bottom: 0px;
+ 75% {
+ transform: translate3d(-10px, 0, 0);
}
- .service-list a {
- text-decoration: none;
- color: #333;
+ 90% {
+ transform: translate3d(5px, 0, 0);
}
- .meters, .stats {
- margin-top: 25px;
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
+ to {
+ transform: none;
+ }
+}
+
+@keyframes slideInUp {
+ from {
+ transform: translate3d(0, 100%, 0);
+ visibility: visible;
}
- /* 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);
+ to {
+ transform: translate3d(0, 0, 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;
- }
- @-webkit-keyframes load3 {
- 0% {
- -webkit-transform: rotate(0deg);
- transform: rotate(0deg);
- }
- 100% {
- -webkit-transform: rotate(360deg);
- transform: rotate(360deg);
- }
- }
- @keyframes load3 {
- 0% {
- -webkit-transform: rotate(0deg);
- transform: rotate(0deg);
- }
- 100% {
- -webkit-transform: rotate(360deg);
- transform: rotate(360deg);
- }
+}
+
+@keyframes bounceOutRight {
+ 20% {
+ opacity: 1;
+ transform: translate3d(-20px, 0, 0);
}
- /* VIEW ANIMATION */
+ to {
+ opacity: 0;
+ transform: translate3d(2000px, 0, 0);
+ }
+}
- [ui-view] {
- position: absolute;
- top: 0;
- bottom: 0;
- width: 100%;
+@keyframes bounceOutLeft {
+ 20% {
+ opacity: 1;
+ transform: translate3d(20px, 0, 0);
}
- /* FROM DASHBOARD TO DETAIL */
-
- /* dash out */
- [ui-view].ceilometerDashboard.ng-leave {
- animation:1s bounceOutLeft ease;
+ to {
+ opacity: 0;
+ transform: translate3d(-2000px, 0, 0);
}
- /* samples in */
- [ui-view].samples.ng-enter {
- animation:1s bounceInRight ease;
+}
+
+@keyframes slideOutDown {
+ from {
+ transform: translate3d(0, 0, 0);
}
- /* FROM DETAIL TO DASHBOARD */
-
- /* samples out */
- [ui-view].samples.ng-leave {
- animation:1s bounceOutRight ease;
+ to {
+ visibility: hidden;
+ transform: translate3d(0, 100%, 0);
}
- /* dash in */
- [ui-view].ceilometerDashboard.ng-enter {
- animation:1s bounceInLeft ease;
- }
-
- /* COLUMS ANIMATION */
- /* when showing the thing */
- .animate .animate-slide-left.ng-hide-remove {
- animation:0.5s bounceInRight ease;
- }
-
- /* when hiding the picture */
- .animate .animate-slide-left.ng-hide-add {
- animation:0.5s bounceOutRight ease;
- }
-
- /* ANIMATIONS */
-
- @keyframes bounceInRight {
- from, 60%, 75%, 90%, to {
- animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
- }
-
- from {
- opacity: 0;
- transform: translate3d(3000px, 0, 0);
- }
-
- 60% {
- opacity: 1;
- transform: translate3d(-25px, 0, 0);
- }
-
- 75% {
- transform: translate3d(10px, 0, 0);
- }
-
- 90% {
- transform: translate3d(-5px, 0, 0);
- }
-
- to {
- transform: none;
- }
- }
-
- @keyframes bounceInLeft {
- from, 60%, 75%, 90%, to {
- animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
- }
-
- 0% {
- opacity: 0;
- transform: translate3d(-3000px, 0, 0);
- }
-
- 60% {
- opacity: 1;
- transform: translate3d(25px, 0, 0);
- }
-
- 75% {
- transform: translate3d(-10px, 0, 0);
- }
-
- 90% {
- transform: translate3d(5px, 0, 0);
- }
-
- to {
- transform: none;
- }
- }
-
- @keyframes slideInUp {
- from {
- transform: translate3d(0, 100%, 0);
- visibility: visible;
- }
-
- to {
- transform: translate3d(0, 0, 0);
- }
- }
-
- @keyframes bounceOutRight {
- 20% {
- opacity: 1;
- transform: translate3d(-20px, 0, 0);
- }
-
- to {
- opacity: 0;
- transform: translate3d(2000px, 0, 0);
- }
- }
-
- @keyframes bounceOutLeft {
- 20% {
- opacity: 1;
- transform: translate3d(20px, 0, 0);
- }
-
- to {
- opacity: 0;
- transform: translate3d(-2000px, 0, 0);
- }
- }
-
- @keyframes slideOutDown {
- from {
- transform: translate3d(0, 0, 0);
- }
-
- to {
- visibility: hidden;
- transform: translate3d(0, 100%, 0);
- }
- }
-
-/*}
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/css/dev.css b/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/css/dev.css
index b72bfdc..228d394 100644
--- a/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/css/dev.css
+++ b/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/css/dev.css
@@ -9,6 +9,6 @@
@media (min-width: 768px) {
#xosCeilometerDashboard {
- margin: 10px:;
+ margin: 10px
}
}
\ No newline at end of file
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/css/grips/vertical.png b/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/css/grips/vertical.png
deleted file mode 100644
index 0ac8fa1..0000000
--- a/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/css/grips/vertical.png
+++ /dev/null
Binary files differ
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/js/main.js b/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/js/main.js
index e85a26e..a20d563 100644
--- a/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/js/main.js
+++ b/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/js/main.js
@@ -33,24 +33,12 @@
})
.service('Ceilometer', function($http, $q, lodash){
- this.resourceMap = {};
-
this.getMeters = () => {
let deferred = $q.defer();
$http.get('/xoslib/meters/', {cache: true})
// $http.get('../meters_mock.json', {cache: true})
.then((res) => {
-
- // saving resources name and ids for later user,
- // {resource_id: resource_name}
- // NOTE REMOVE IF NOT ANYMORE NEEDED
- const resourceObj = lodash.groupBy(res.data, 'resource_id');
- this.resourceMap = lodash.reduce(Object.keys(resourceObj), (map, item) => {
- map[item] = resourceObj[item][0].resource_name;
- return map;
- }, {});
-
deferred.resolve(res.data)
})
.catch((e) => {
@@ -88,6 +76,11 @@
return deferred.promise;
};
+
+ // hold dashboard status (opened service, slice, resource)
+ this.selectedService = null;
+ this.selectedSlice = null;
+ this.selectedResource = null;
})
.directive('ceilometerDashboard', function(lodash){
return {
@@ -98,9 +91,34 @@
templateUrl: 'templates/ceilometer-dashboard.tpl.html',
controller: function(Ceilometer){
+ console.log(Ceilometer.selectedService, Ceilometer.selectedSlice, Ceilometer.selectedResource);
+
+ // this open the accordion
+ this.accordion = {
+ open: {}
+ }
+
+ /**
+ * Open the active panel base on the service stored values
+ */
+ this.openPanels = () => {
+ if(Ceilometer.selectedService){
+ this.accordion.open[Ceilometer.selectedService] = true;
+ if(Ceilometer.selectedSlice){
+ this.selectedSlice = Ceilometer.selectedSlice;
+ this.selectedResources = this.projects[Ceilometer.selectedService][Ceilometer.selectedSlice]
+ if(Ceilometer.selectedResource){
+ this.selectedResource = Ceilometer.selectedResource;
+ this.selectedMeters = this.selectedResources[Ceilometer.selectedResource];
+ }
+ }
+ }
+ }
+
this.loadMeters = () => {
this.loader = true;
+ // TODO rename projects in meters
Ceilometer.getMeters()
.then(meters => {
//group project by service
@@ -113,6 +131,9 @@
this.projects[project][slice] = lodash.groupBy(this.projects[project][slice], 'resource_name');
});
});
+
+ // open selected panels
+ this.openPanels();
})
.catch(err => {
this.error = err.data.detail;
@@ -125,33 +146,26 @@
this.loadMeters();
/**
- * Select the current service
- */
-
- this.selectService = (service) => {
- //cleaning
- this.selectedResources = null;
- this.selectedResource = null;
- this.selectedMeters = null;
-
- this.selectedService = service;
- };
-
- /**
* Select Resources for a slice
*
* @param Array resources The list of selected resources
* @returns void
*/
this.selectedResources = null;
- this.selectResources = (resources, slice) => {
+ this.selectResources = (resources, slice, service) => {
//cleaning
this.selectedResources = null;
this.selectedResource = null;
this.selectedMeters = null;
+ // hold the resource list for the current slice
this.selectedResources = resources;
this.selectedSlice = slice;
+ this.selectedService = service;
+
+ // store the status
+ Ceilometer.selectedSlice = slice;
+ Ceilometer.selectedService = service;
}
/**
@@ -163,6 +177,8 @@
this.selectedMeters = null;
this.selectMeters = (meters, resource) => {
this.selectedMeters = meters;
+
+ Ceilometer.selectedResource = resource;
this.selectedResource = resource;
}
@@ -181,6 +197,8 @@
templateUrl: 'templates/ceilometer-samples.tpl.html',
controller: function(Ceilometer) {
+ // console.log(Ceilometer.selectResource);
+
this.chartColors = [
'#286090',
'#F7464A',
@@ -204,9 +222,11 @@
if($stateParams.name && $stateParams.tenant){
this.name = $stateParams.name;
this.tenant = $stateParams.tenant;
+ // TODO rename tenant in project_id
}
-
- // Mock
+ else{
+ throw new Error('Missing Name and Tenant Params!');
+ }
/**
* Goes trough the array and format date to be used as labels
@@ -243,22 +263,21 @@
* @param string resource_id
*/
this.chartMeters = [];
- this.addMeterToChart = (resource_id) => {
- this.chart['labels'] = this.getLabels(lodash.sortBy(this.samplesList[resource_id], 'timestamp'));
- this.chart['series'].push(resource_id);
- this.chart['data'].push(this.getData(lodash.sortBy(this.samplesList[resource_id], 'timestamp')));
- console.log(this.samplesList[resource_id]);
- this.chartMeters.push(this.samplesList[resource_id][0]); //use the 0 as are all samples for the same resource and I need the name
- lodash.remove(this.sampleLabels, {id: resource_id});
+ this.addMeterToChart = (project_id) => {
+ this.chart['labels'] = this.getLabels(lodash.sortBy(this.samplesList[project_id], 'timestamp'));
+ this.chart['series'].push(project_id);
+ this.chart['data'].push(this.getData(lodash.sortBy(this.samplesList[project_id], 'timestamp')));
+ this.chartMeters.push(this.samplesList[project_id][0]); //use the 0 as are all samples for the same resource and I need the name
+ lodash.remove(this.sampleLabels, {id: project_id});
}
this.removeFromChart = (meter) => {
- this.chart.data.splice(this.chart.series.indexOf(meter.resource_id), 1);
- this.chart.series.splice(this.chart.series.indexOf(meter.resource_id), 1);
- this.chartMeters.splice(this.chartMeters.indexOf(meter.resource_id), 1);
+ this.chart.data.splice(this.chart.series.indexOf(meter.project_id), 1);
+ this.chart.series.splice(this.chart.series.indexOf(meter.project_id), 1);
+ this.chartMeters.splice(lodash.findIndex(this.chartMeters, {project_id: meter.project_id}), 1);
this.sampleLabels.push({
- id: meter.resource_id,
- name: meter.resource_name
+ id: meter.project_id,
+ name: meter.resource_name || meter.project_id
})
};
@@ -268,13 +287,14 @@
this.formatSamplesLabels = (samples) => {
- return lodash.uniq(samples.reduce((labels, item) => {
+ return lodash.uniq(samples, 'project_id')
+ .reduce((labels, item) => {
labels.push({
- id: item.resource_id,
- name: item.resource_name
+ id: item.project_id,
+ name: item.resource_name || item.project_id
});
return labels;
- }, []), item => item.id);
+ }, []);
}
@@ -289,7 +309,7 @@
.then(res => {
// setup data for visualization
- this.samplesList = lodash.groupBy(res, 'resource_id');
+ this.samplesList = lodash.groupBy(res, 'project_id');
this.sampleLabels = this.formatSamplesLabels(res);
// add current meter to chart
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/templates/ceilometer-dashboard.tpl.html b/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/templates/ceilometer-dashboard.tpl.html
index 2d880b5..f04a974 100644
--- a/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/templates/ceilometer-dashboard.tpl.html
+++ b/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/templates/ceilometer-dashboard.tpl.html
@@ -30,11 +30,12 @@
<uib-accordion-group
ng-repeat="(service, slices) in vm.projects | orderObjectByKey"
template-url="templates/accordion-group.html"
+ is-open="vm.accordion.open[service]"
heading="{{service}}">
<h4>Slices:</h4>
<a ng-repeat="(slice, resources) in slices"
ng-class="{active: slice === vm.selectedSlice}"
- ng-click="vm.selectResources(resources, slice)"
+ ng-click="vm.selectResources(resources, slice, service)"
href="#" class="list-group-item" >
{{slice}} <i class="glyphicon glyphicon-chevron-right pull-right"></i>
</a>
@@ -85,7 +86,7 @@
</div>
<div class="col-xs-3"></div>
</div>
- <div class="row" ng-click="vm.selectMeters(meters)" ng-repeat="meter in vm.selectedMeters" style="margin-bottom: 10px;">
+ <div class="row" ng-repeat="meter in vm.selectedMeters" style="margin-bottom: 10px;">
<div class="col-xs-6">
{{meter.name}}
</div>
@@ -93,7 +94,7 @@
{{meter.unit}}
</div>
<div class="col-xs-3">
- <a ui-sref="samples({name: meter.name, tenant: meter.resource_id})" class="btn btn-primary">
+ <a ui-sref="samples({name: meter.name, tenant: meter.project_id})" class="btn btn-primary">
<i class="glyphicon glyphicon-search"></i>
</a>
</div>
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/templates/ceilometer-samples.tpl.html b/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/templates/ceilometer-samples.tpl.html
index f323802e..b279123 100644
--- a/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/templates/ceilometer-samples.tpl.html
+++ b/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/templates/ceilometer-samples.tpl.html
@@ -42,7 +42,7 @@
<div class="row" ng-if="!vm.loader">
<div class="col-xs-12">
<a ng-click="vm.removeFromChart(meter)" class="btn btn-chart" ng-style="{'background-color': vm.chartColors[$index]}" ng-repeat="meter in vm.chartMeters">
- {{meter.resource_name}}
+ {{meter.resource_name || meter.resource_id}}
</a>
</div>
</div>
diff --git a/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/templates/ceilometer-stats.tpl.html b/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/templates/ceilometer-stats.tpl.html
index fabce0a..1f5ff11 100644
--- a/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/templates/ceilometer-stats.tpl.html
+++ b/xos/core/xoslib/ngXosViews/ceilometerDashboard/src/templates/ceilometer-stats.tpl.html
@@ -14,7 +14,7 @@
<th>Value:</th>
</tr>
<tr ng-repeat="item in vm.stats">
- <td>{{item.type}}</td>
+ <td>{{item.category}}</td>
<td>{{item.meter}}</td>
<td>{{item.unit}}</td>
<td>{{item.value}}</td>
diff --git a/xos/core/xoslib/static/css/xosCeilometerDashboard.css b/xos/core/xoslib/static/css/xosCeilometerDashboard.css
new file mode 100644
index 0000000..8aad3b2
--- /dev/null
+++ b/xos/core/xoslib/static/css/xosCeilometerDashboard.css
@@ -0,0 +1 @@
+#xosCeilometerDashboard{position:relative}.panel{margin-top:10px}.panel-body:not(:first-child){border-top:1px solid #e3e3e3}.panel-body .row{margin-top:10px}.chart{width:100%;height:300px}.btn-chart,.btn-chart:hover{color:#fff}.side-container{position:relative}.service-list{margin-top:-10px}.service-list h3{margin-top:0;margin-bottom:0}.service-list a{text-decoration:none;color:#333}.meters,.stats{margin-top:25px;position:absolute;top:0;left:0;width:100%}.loader{font-size:10px;margin:150px auto;text-indent:-9999em;width:11em;height:11em;border-radius:50%;background:#fff;background:linear-gradient(to right,#fff 10%,rgba(255,255,255,0) 42%);position:relative;animation:load3 1.4s infinite linear;transform:translateZ(0)}.loader:before{width:50%;height:50%;background:#105e9e;border-radius:100% 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%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}[ui-view]{position:absolute;top:0;bottom:0;width:100%}[ui-view].ceilometerDashboard.ng-leave{animation:1s bounceOutLeft ease}[ui-view].samples.ng-enter{animation:1s bounceInRight ease}[ui-view].samples.ng-leave{animation:1s bounceOutRight ease}[ui-view].ceilometerDashboard.ng-enter{animation:1s bounceInLeft ease}.animate .animate-slide-left.ng-hide-remove{animation:.5s bounceInRight ease}.animate .animate-slide-left.ng-hide-add{animation:.5s bounceOutRight ease}@keyframes bounceInRight{from,60%,75%,90%,to{animation-timing-function:cubic-bezier(.215,.61,.355,1.000)}from{opacity:0;transform:translate3d(3000px,0,0)}60%{opacity:1;transform:translate3d(-25px,0,0)}75%{transform:translate3d(10px,0,0)}90%{transform:translate3d(-5px,0,0)}to{transform:none}}@keyframes bounceInLeft{from,60%,75%,90%,to{animation-timing-function:cubic-bezier(.215,.61,.355,1.000)}0%{opacity:0;transform:translate3d(-3000px,0,0)}60%{opacity:1;transform:translate3d(25px,0,0)}75%{transform:translate3d(-10px,0,0)}90%{transform:translate3d(5px,0,0)}to{transform:none}}@keyframes slideInUp{from{transform:translate3d(0,100%,0);visibility:visible}to{transform:translate3d(0,0,0)}}@keyframes bounceOutRight{20%{opacity:1;transform:translate3d(-20px,0,0)}to{opacity:0;transform:translate3d(2000px,0,0)}}@keyframes bounceOutLeft{20%{opacity:1;transform:translate3d(20px,0,0)}to{opacity:0;transform:translate3d(-2000px,0,0)}}@keyframes slideOutDown{from{transform:translate3d(0,0,0)}to{visibility:hidden;transform:translate3d(0,100%,0)}}
\ No newline at end of file
diff --git a/xos/core/xoslib/static/js/xosCeilometerDashboard.js b/xos/core/xoslib/static/js/xosCeilometerDashboard.js
index 9e314d4..5aa7234 100644
--- a/xos/core/xoslib/static/js/xosCeilometerDashboard.js
+++ b/xos/core/xoslib/static/js/xosCeilometerDashboard.js
@@ -1 +1 @@
-"use strict";angular.module("xos.ceilometerDashboard",["ngResource","ngCookies","ngLodash","ui.router","xos.helpers","ngAnimate","chart.js","ui.bootstrap.accordion"]).config(["$stateProvider","$urlRouterProvider",function(e,t){e.state("ceilometerDashboard",{url:"/",template:"<ceilometer-dashboard></ceilometer-dashboard>"}).state("samples",{url:"/:name/:tenant/samples",template:"<ceilometer-samples></ceilometer-samples>"}),t.otherwise("/")}]).config(["$httpProvider",function(e){e.interceptors.push("NoHyperlinks")}]).run(["$rootScope",function(e){e.stateName="ceilometerDashboard",e.$on("$stateChangeStart",function(t,s){e.stateName=s.name})}]).service("Ceilometer",["$http","$q","lodash",function(e,t,s){var n=this;this.resourceMap={},this.getMeters=function(){var r=t.defer();return e.get("/xoslib/meters/",{cache:!0}).then(function(e){var t=s.groupBy(e.data,"resource_id");n.resourceMap=s.reduce(Object.keys(t),function(e,s){return e[s]=t[s][0].resource_name,e},{}),r.resolve(e.data)})["catch"](function(e){r.reject(e)}),r.promise},this.getSamples=function(s,n){var r=t.defer();return e.get("/xoslib/metersamples/",{params:{meter:s,tenant:n}}).then(function(e){r.resolve(e.data)})["catch"](function(e){r.reject(e)}),r.promise},this.getStats=function(n){var r=t.defer();return e.get("/xoslib/meterstatistics/",{cache:!0}).then(function(e){r.resolve(s.filter(e.data,{slice:n}))})["catch"](function(e){r.reject(e)}),r.promise}}]).directive("ceilometerDashboard",["lodash",function(e){return{restrict:"E",scope:{},bindToController:!0,controllerAs:"vm",templateUrl:"templates/ceilometer-dashboard.tpl.html",controller:["Ceilometer",function(t){var s=this;this.loadMeters=function(){s.loader=!0,t.getMeters().then(function(t){s.projects=e.groupBy(t,"service"),e.forEach(Object.keys(s.projects),function(t){s.projects[t]=e.groupBy(s.projects[t],"slice"),e.forEach(Object.keys(s.projects[t]),function(n){s.projects[t][n]=e.groupBy(s.projects[t][n],"resource_name")})})})["catch"](function(e){s.error=e.data.detail})["finally"](function(){s.loader=!1})},this.loadMeters(),this.selectService=function(e){s.selectedResources=null,s.selectedResource=null,s.selectedMeters=null,s.selectedService=e},this.selectedResources=null,this.selectResources=function(e,t){s.selectedResources=null,s.selectedResource=null,s.selectedMeters=null,s.selectedResources=e,s.selectedSlice=t},this.selectedMeters=null,this.selectMeters=function(e,t){s.selectedMeters=e,s.selectedResource=t}}]}}]).directive("ceilometerSamples",["lodash","$stateParams",function(e,t){return{restrict:"E",scope:{name:"=name",tenant:"=tenant"},bindToController:!0,controllerAs:"vm",templateUrl:"templates/ceilometer-samples.tpl.html",controller:["Ceilometer",function(s){var n=this;this.chartColors=["#286090","#F7464A","#46BFBD","#FDB45C","#97BBCD","#4D5360","#8c4f9f"],this.chart={series:[],labels:[],data:[]},Chart.defaults.global.colours=this.chartColors,this.chartType="line",t.name&&t.tenant&&(this.name=t.name,this.tenant=t.tenant),this.getLabels=function(e){return e.reduce(function(e,t){var s=new Date(t.timestamp);return e.push(s.getHours()+":"+((s.getMinutes()<10?"0":"")+s.getMinutes())+":"+s.getSeconds()),e},[])},this.getData=function(e){return e.reduce(function(e,t){return e.push(t.volume),e},[])},this.chartMeters=[],this.addMeterToChart=function(t){n.chart.labels=n.getLabels(e.sortBy(n.samplesList[t],"timestamp")),n.chart.series.push(t),n.chart.data.push(n.getData(e.sortBy(n.samplesList[t],"timestamp"))),console.log(n.samplesList[t]),n.chartMeters.push(n.samplesList[t][0]),e.remove(n.sampleLabels,{id:t})},this.removeFromChart=function(e){n.chart.data.splice(n.chart.series.indexOf(e.resource_id),1),n.chart.series.splice(n.chart.series.indexOf(e.resource_id),1),n.chartMeters.splice(n.chartMeters.indexOf(e.resource_id),1),n.sampleLabels.push({id:e.resource_id,name:e.resource_name})},this.formatSamplesLabels=function(t){return e.uniq(t.reduce(function(e,t){return e.push({id:t.resource_id,name:t.resource_name}),e},[]),function(e){return e.id})},this.showSamples=function(){n.loader=!0,s.getSamples(n.name).then(function(t){n.samplesList=e.groupBy(t,"resource_id"),n.sampleLabels=n.formatSamplesLabels(t),n.addMeterToChart(n.tenant)})["catch"](function(e){n.error=e.data.detail})["finally"](function(){n.loader=!1})},this.showSamples()}]}}]).directive("ceilometerStats",function(){return{restrict:"E",scope:{name:"=name"},bindToController:!0,controllerAs:"vm",templateUrl:"templates/ceilometer-stats.tpl.html",controller:["$scope","Ceilometer",function(e,t){var s=this;this.getStats=function(){s.loader=!0,t.getStats(s.name).then(function(e){s.stats=e})["catch"](function(e){s.error=e.data})["finally"](function(){s.loader=!1})},this.getStats(),e.$watch(function(){return s.name},function(){s.getStats()})}]}}).filter("orderObjectByKey",["lodash",function(e){return function(t){return t?e.reduce(Object.keys(t).reverse(),function(e,s){return e[s]=t[s],e},{}):void 0}}]),angular.module("xos.ceilometerDashboard").run(["$templateCache",function(e){e.put("templates/accordion-group.html",'<div class="panel {{panelClass || \'panel-default\'}}">\n <div class="panel-heading" ng-keypress="toggleOpen($event)">\n <h3>\n <a href tabindex="0" class="accordion-toggle" ng-click="toggleOpen()" uib-accordion-transclude="heading"><span ng-class="{\'text-muted\': isDisabled}">{{heading}}</span></a>\n </h3>\n </div>\n <div class="panel-collapse collapse" uib-collapse="!isOpen">\n <div class="panel-body" ng-transclude></div>\n </div>\n</div>\n'),e.put("templates/accordion.html",'<div class="panel-group" ng-transclude></div>'),e.put("templates/ceilometer-dashboard.tpl.html",'<div class="row">\n <div class="col-sm-10">\n <h1>XOS Monitoring Statistics</h1>\n </div>\n <div class="col-xs-2 text-right">\n <a href="" class="btn btn-default" \n ng-show="vm.selectedResources && !vm.showStats"\n ng-click="vm.showStats = true">\n <i class="glyphicon glyphicon-transfer"></i>\n </a>\n <a href="" class="btn btn-default" \n ng-show="vm.selectedResources && vm.showStats"\n ng-click="vm.showStats = false">\n <i class="glyphicon glyphicon-transfer"></i>\n </a>\n </div>\n</div>\n\n<div class="row" ng-show="vm.loader">\n <div class="col-xs-12">\n <div class="loader">Loading</div>\n </div>\n</div>\n\n<section ng-hide="vm.loader" ng-class="{animate: !vm.loader}">\n <div class="row">\n <div class="col-sm-3 service-list">\n <h3>XOS Service: </h3>\n <uib-accordion close-others="true" template-url="templates/accordion.html">\n <uib-accordion-group\n ng-repeat="(service, slices) in vm.projects | orderObjectByKey"\n template-url="templates/accordion-group.html"\n heading="{{service}}">\n <h4>Slices:</h4>\n <a ng-repeat="(slice, resources) in slices" \n ng-class="{active: slice === vm.selectedSlice}"\n ng-click="vm.selectResources(resources, slice)"\n href="#" class="list-group-item" >\n {{slice}} <i class="glyphicon glyphicon-chevron-right pull-right"></i>\n </a>\n </uib-accordion-group>\n </uib-accordion>\n </div>\n <section class="side-container col-sm-9">\n <div class="row">\n <article ng-hide="!vm.showStats" class="stats animate-slide-left">\n <div class="col-xs-12">\n <div class="list-group">\n <div class="list-group-item">\n <h3>Stats</h3>\n </div>\n <div class="list-group-item">\n <ceilometer-stats ng-if="vm.selectedSlice" name="vm.selectedSlice"></ceilometer-stats>\n </div>\n </div>\n </div>\n </article>\n <article ng-hide="vm.showStats" class="meters animate-slide-left">\n <div class="col-sm-4 animate-slide-left" ng-hide="!vm.selectedResources">\n <div class="list-group">\n <div class="list-group-item">\n <h3>Resources</h3>\n </div>\n <a href="#" \n ng-click="vm.selectMeters(meters, resource)" \n class="list-group-item" \n ng-repeat="(resource, meters) in vm.selectedResources" \n ng-class="{active: resource === vm.selectedResource}">\n {{resource}} <i class="glyphicon glyphicon-chevron-right pull-right"></i>\n </a>\n </div>\n </div>\n <div class="col-sm-8 animate-slide-left" ng-hide="!vm.selectedMeters">\n <div class="list-group">\n <div class="list-group-item">\n <h3>Meters</h3>\n </div>\n <div class="list-group-item">\n <div class="row">\n <div class="col-xs-6">\n <label>Name:</label>\n </div>\n <div class="col-xs-3">\n <label>Unit:</label>\n </div>\n <div class="col-xs-3"></div>\n </div>\n <div class="row" ng-click="vm.selectMeters(meters)" ng-repeat="meter in vm.selectedMeters" style="margin-bottom: 10px;">\n <div class="col-xs-6">\n {{meter.name}}\n </div>\n <div class="col-xs-3">\n {{meter.unit}}\n </div>\n <div class="col-xs-3">\n <a ui-sref="samples({name: meter.name, tenant: meter.resource_id})" class="btn btn-primary">\n <i class="glyphicon glyphicon-search"></i>\n </a>\n </div>\n </div>\n </div>\n </div>\n </div>\n </article>\n </div>\n </section>\n </div>\n</section>\n<section ng-if="!vm.loader && vm.error">\n <div class="alert alert-danger">\n {{vm.error}}\n </div>\n</section>\n'),e.put("templates/ceilometer-samples.tpl.html",'<!-- <pre>{{ vm | json}}</pre> -->\n\n<div class="row">\n <div class="col-xs-10">\n <h1>{{vm.name | uppercase}}</h1>\n </div>\n <div class="col-xs-2">\n <a ui-sref="ceilometerDashboard" class="btn btn-primary pull-right">\n <i class="glyphicon glyphicon-arrow-left"></i> Back to list\n </a>\n </div>\n</div>\n<div class="row" ng-show="vm.loader">\n <div class="col-xs-12">\n <div class="loader">Loading</div>\n </div>\n</div>\n<section ng-if="!vm.loader && !vm.error">\n <div class="row">\n <form class="form-inline col-xs-8" ng-submit="vm.addMeterToChart(vm.addMeterValue)">\n <select ng-model="vm.addMeterValue" class="form-control" ng-options="resource.id as resource.name for resource in vm.sampleLabels"></select>\n <button class="btn btn-success"> \n <i class="glyphicon glyphicon-plus"></i> Add\n </button>\n </form>\n <div class="col-xs-4 text-right">\n <a ng-click="vm.chartType = \'line\'" class="btn" ng-class="{\'btn-default\': vm.chartType != \'bar\', \'btn-primary\': vm.chartType == \'line\'}">Lines</a>\n <a ng-click="vm.chartType = \'bar\'" class="btn" ng-class="{\'btn-default\': vm.chartType != \'line\', \'btn-primary\': vm.chartType == \'bar\'}">Bars</a>\n </div>\n </div>\n <div class="row" ng-if="!vm.loader">\n <div class="col-xs-12">\n <canvas ng-if="vm.chartType === \'line\'" id="line" class="chart chart-line" chart-data="vm.chart.data" chart-options="{datasetFill: false}"\n chart-labels="vm.chart.labels" chart-legend="false" chart-series="vm.chart.series">\n </canvas>\n <canvas ng-if="vm.chartType === \'bar\'" id="bar" class="chart chart-bar" chart-data="vm.chart.data"\n chart-labels="vm.chart.labels" chart-legend="false" chart-series="vm.chart.series">\n </canvas>\n <!-- <pre>{{vm.chartMeters | json}}</pre> -->\n </div>\n </div>\n <div class="row" ng-if="!vm.loader">\n <div class="col-xs-12">\n <a ng-click="vm.removeFromChart(meter)" class="btn btn-chart" ng-style="{\'background-color\': vm.chartColors[$index]}" ng-repeat="meter in vm.chartMeters">\n {{meter.resource_name}}\n </a>\n </div>\n </div>\n</section>\n<section ng-if="!vm.loader && vm.error">\n <div class="alert alert-danger">\n {{vm.error}}\n </div>\n</section>'),e.put("templates/ceilometer-stats.tpl.html",'<div ng-show="vm.loader" class="loader">Loading</div>\n\n<section ng-if="!vm.loader && !vm.error">\n\n <div class="alert alert-danger" ng-if="vm.stats.length == 0">\n No result\n </div> \n\n <table class="table" ng-if="vm.stats.length > 0">\n <tr>\n <th>Type:</th>\n <th>Meter:</th>\n <th>Unit:</th>\n <th>Value:</th>\n </tr>\n <tr ng-repeat="item in vm.stats">\n <td>{{item.type}}</td>\n <td>{{item.meter}}</td>\n <td>{{item.unit}}</td>\n <td>{{item.value}}</td>\n </tr>\n </table>\n</section>\n\n<section ng-if="!vm.loader && vm.error">\n <div class="alert alert-danger">\n {{vm.error}}\n </div>\n</section>\n')}]),angular.module("xos.ceilometerDashboard").run(["$location",function(e){e.path("/")}]),angular.bootstrap(angular.element("#xosCeilometerDashboard"),["xos.ceilometerDashboard"]);
\ No newline at end of file
+"use strict";angular.module("xos.ceilometerDashboard",["ngResource","ngCookies","ngLodash","ui.router","xos.helpers","ngAnimate","chart.js","ui.bootstrap.accordion"]).config(["$stateProvider","$urlRouterProvider",function(e,t){e.state("ceilometerDashboard",{url:"/",template:"<ceilometer-dashboard></ceilometer-dashboard>"}).state("samples",{url:"/:name/:tenant/samples",template:"<ceilometer-samples></ceilometer-samples>"}),t.otherwise("/")}]).config(["$httpProvider",function(e){e.interceptors.push("NoHyperlinks")}]).run(["$rootScope",function(e){e.stateName="ceilometerDashboard",e.$on("$stateChangeStart",function(t,s){e.stateName=s.name})}]).service("Ceilometer",["$http","$q","lodash",function(e,t,s){var n=this;this.resourceMap={},this.getMeters=function(){var r=t.defer();return e.get("/xoslib/meters/",{cache:!0}).then(function(e){var t=s.groupBy(e.data,"resource_id");n.resourceMap=s.reduce(Object.keys(t),function(e,s){return e[s]=t[s][0].resource_name,e},{}),r.resolve(e.data)})["catch"](function(e){r.reject(e)}),r.promise},this.getSamples=function(s,n){var r=t.defer();return e.get("/xoslib/metersamples/",{params:{meter:s,tenant:n}}).then(function(e){r.resolve(e.data)})["catch"](function(e){r.reject(e)}),r.promise},this.getStats=function(n){var r=t.defer();return e.get("/xoslib/meterstatistics/",{cache:!0}).then(function(e){r.resolve(s.filter(e.data,{slice:n}))})["catch"](function(e){r.reject(e)}),r.promise}}]).directive("ceilometerDashboard",["lodash",function(e){return{restrict:"E",scope:{},bindToController:!0,controllerAs:"vm",templateUrl:"templates/ceilometer-dashboard.tpl.html",controller:["Ceilometer",function(t){var s=this;this.loadMeters=function(){s.loader=!0,t.getMeters().then(function(t){s.projects=e.groupBy(t,"service"),e.forEach(Object.keys(s.projects),function(t){s.projects[t]=e.groupBy(s.projects[t],"slice"),e.forEach(Object.keys(s.projects[t]),function(n){s.projects[t][n]=e.groupBy(s.projects[t][n],"resource_name")})})})["catch"](function(e){s.error=e.data.detail})["finally"](function(){s.loader=!1})},this.loadMeters(),this.selectService=function(e){s.selectedResources=null,s.selectedResource=null,s.selectedMeters=null,s.selectedService=e},this.selectedResources=null,this.selectResources=function(e,t){s.selectedResources=null,s.selectedResource=null,s.selectedMeters=null,s.selectedResources=e,s.selectedSlice=t},this.selectedMeters=null,this.selectMeters=function(e,t){s.selectedMeters=e,s.selectedResource=t}}]}}]).directive("ceilometerSamples",["lodash","$stateParams",function(e,t){return{restrict:"E",scope:{name:"=name",tenant:"=tenant"},bindToController:!0,controllerAs:"vm",templateUrl:"templates/ceilometer-samples.tpl.html",controller:["Ceilometer",function(s){var n=this;if(this.chartColors=["#286090","#F7464A","#46BFBD","#FDB45C","#97BBCD","#4D5360","#8c4f9f"],this.chart={series:[],labels:[],data:[]},Chart.defaults.global.colours=this.chartColors,this.chartType="line",!t.name||!t.tenant)throw new Error("Missing Name and Tenant Params!");this.name=t.name,this.tenant=t.tenant,this.getLabels=function(e){return e.reduce(function(e,t){var s=new Date(t.timestamp);return e.push(s.getHours()+":"+((s.getMinutes()<10?"0":"")+s.getMinutes())+":"+s.getSeconds()),e},[])},this.getData=function(e){return e.reduce(function(e,t){return e.push(t.volume),e},[])},this.chartMeters=[],this.addMeterToChart=function(t){n.chart.labels=n.getLabels(e.sortBy(n.samplesList[t],"timestamp")),n.chart.series.push(t),n.chart.data.push(n.getData(e.sortBy(n.samplesList[t],"timestamp"))),n.chartMeters.push(n.samplesList[t][0]),e.remove(n.sampleLabels,{id:t})},this.removeFromChart=function(t){n.chart.data.splice(n.chart.series.indexOf(t.resource_id),1),n.chart.series.splice(n.chart.series.indexOf(t.resource_id),1),n.chartMeters.splice(e.findIndex(n.chartMeters,{resource_id:t.resource_id}),1),n.sampleLabels.push({id:t.resource_id,name:t.resource_name})},this.formatSamplesLabels=function(t){return e.uniq(t,"resource_id").reduce(function(e,t){return e.push({id:t.resource_id,name:t.resource_name}),e},[])},this.showSamples=function(){n.loader=!0,s.getSamples(n.name).then(function(t){n.samplesList=e.groupBy(t,"resource_id"),n.sampleLabels=n.formatSamplesLabels(t),n.addMeterToChart(n.tenant)})["catch"](function(e){n.error=e.data.detail})["finally"](function(){n.loader=!1})},this.showSamples()}]}}]).directive("ceilometerStats",function(){return{restrict:"E",scope:{name:"=name"},bindToController:!0,controllerAs:"vm",templateUrl:"templates/ceilometer-stats.tpl.html",controller:["$scope","Ceilometer",function(e,t){var s=this;this.getStats=function(){s.loader=!0,t.getStats(s.name).then(function(e){s.stats=e})["catch"](function(e){s.error=e.data})["finally"](function(){s.loader=!1})},this.getStats(),e.$watch(function(){return s.name},function(){s.getStats()})}]}}).filter("orderObjectByKey",["lodash",function(e){return function(t){return t?e.reduce(Object.keys(t).reverse(),function(e,s){return e[s]=t[s],e},{}):void 0}}]),angular.module("xos.ceilometerDashboard").run(["$templateCache",function(e){e.put("templates/accordion-group.html",'<div class="panel {{panelClass || \'panel-default\'}}">\n <div class="panel-heading" ng-keypress="toggleOpen($event)">\n <h3>\n <a href tabindex="0" class="accordion-toggle" ng-click="toggleOpen()" uib-accordion-transclude="heading"><span ng-class="{\'text-muted\': isDisabled}">{{heading}}</span></a>\n </h3>\n </div>\n <div class="panel-collapse collapse" uib-collapse="!isOpen">\n <div class="panel-body" ng-transclude></div>\n </div>\n</div>\n'),e.put("templates/accordion.html",'<div class="panel-group" ng-transclude></div>'),e.put("templates/ceilometer-dashboard.tpl.html",'<div class="row">\n <div class="col-sm-10">\n <h1>XOS Monitoring Statistics</h1>\n </div>\n <div class="col-xs-2 text-right">\n <a href="" class="btn btn-default" \n ng-show="vm.selectedResources && !vm.showStats"\n ng-click="vm.showStats = true">\n <i class="glyphicon glyphicon-transfer"></i>\n </a>\n <a href="" class="btn btn-default" \n ng-show="vm.selectedResources && vm.showStats"\n ng-click="vm.showStats = false">\n <i class="glyphicon glyphicon-transfer"></i>\n </a>\n </div>\n</div>\n\n<div class="row" ng-show="vm.loader">\n <div class="col-xs-12">\n <div class="loader">Loading</div>\n </div>\n</div>\n\n<section ng-hide="vm.loader" ng-class="{animate: !vm.loader}">\n <div class="row">\n <div class="col-sm-3 service-list">\n <h3>XOS Service: </h3>\n <uib-accordion close-others="true" template-url="templates/accordion.html">\n <uib-accordion-group\n ng-repeat="(service, slices) in vm.projects | orderObjectByKey"\n template-url="templates/accordion-group.html"\n heading="{{service}}">\n <h4>Slices:</h4>\n <a ng-repeat="(slice, resources) in slices" \n ng-class="{active: slice === vm.selectedSlice}"\n ng-click="vm.selectResources(resources, slice)"\n href="#" class="list-group-item" >\n {{slice}} <i class="glyphicon glyphicon-chevron-right pull-right"></i>\n </a>\n </uib-accordion-group>\n </uib-accordion>\n </div>\n <section class="side-container col-sm-9">\n <div class="row">\n <article ng-hide="!vm.showStats" class="stats animate-slide-left">\n <div class="col-xs-12">\n <div class="list-group">\n <div class="list-group-item">\n <h3>Stats</h3>\n </div>\n <div class="list-group-item">\n <ceilometer-stats ng-if="vm.selectedSlice" name="vm.selectedSlice"></ceilometer-stats>\n </div>\n </div>\n </div>\n </article>\n <article ng-hide="vm.showStats" class="meters animate-slide-left">\n <div class="col-sm-4 animate-slide-left" ng-hide="!vm.selectedResources">\n <div class="list-group">\n <div class="list-group-item">\n <h3>Resources</h3>\n </div>\n <a href="#" \n ng-click="vm.selectMeters(meters, resource)" \n class="list-group-item" \n ng-repeat="(resource, meters) in vm.selectedResources" \n ng-class="{active: resource === vm.selectedResource}">\n {{resource}} <i class="glyphicon glyphicon-chevron-right pull-right"></i>\n </a>\n </div>\n </div>\n <div class="col-sm-8 animate-slide-left" ng-hide="!vm.selectedMeters">\n <div class="list-group">\n <div class="list-group-item">\n <h3>Meters</h3>\n </div>\n <div class="list-group-item">\n <div class="row">\n <div class="col-xs-6">\n <label>Name:</label>\n </div>\n <div class="col-xs-3">\n <label>Unit:</label>\n </div>\n <div class="col-xs-3"></div>\n </div>\n <div class="row" ng-click="vm.selectMeters(meters)" ng-repeat="meter in vm.selectedMeters" style="margin-bottom: 10px;">\n <div class="col-xs-6">\n {{meter.name}}\n </div>\n <div class="col-xs-3">\n {{meter.unit}}\n </div>\n <div class="col-xs-3">\n <a ui-sref="samples({name: meter.name, tenant: meter.resource_id})" class="btn btn-primary">\n <i class="glyphicon glyphicon-search"></i>\n </a>\n </div>\n </div>\n </div>\n </div>\n </div>\n </article>\n </div>\n </section>\n </div>\n</section>\n<section ng-if="!vm.loader && vm.error">\n <div class="alert alert-danger">\n {{vm.error}}\n </div>\n</section>\n'),e.put("templates/ceilometer-samples.tpl.html",'<!-- <pre>{{ vm | json}}</pre> -->\n\n<div class="row">\n <div class="col-xs-10">\n <h1>{{vm.name | uppercase}}</h1>\n </div>\n <div class="col-xs-2">\n <a ui-sref="ceilometerDashboard" class="btn btn-primary pull-right">\n <i class="glyphicon glyphicon-arrow-left"></i> Back to list\n </a>\n </div>\n</div>\n<div class="row" ng-show="vm.loader">\n <div class="col-xs-12">\n <div class="loader">Loading</div>\n </div>\n</div>\n<section ng-if="!vm.loader && !vm.error">\n <div class="row">\n <form class="form-inline col-xs-8" ng-submit="vm.addMeterToChart(vm.addMeterValue)">\n <select ng-model="vm.addMeterValue" class="form-control" ng-options="resource.id as resource.name for resource in vm.sampleLabels"></select>\n <button class="btn btn-success"> \n <i class="glyphicon glyphicon-plus"></i> Add\n </button>\n </form>\n <div class="col-xs-4 text-right">\n <a ng-click="vm.chartType = \'line\'" class="btn" ng-class="{\'btn-default\': vm.chartType != \'bar\', \'btn-primary\': vm.chartType == \'line\'}">Lines</a>\n <a ng-click="vm.chartType = \'bar\'" class="btn" ng-class="{\'btn-default\': vm.chartType != \'line\', \'btn-primary\': vm.chartType == \'bar\'}">Bars</a>\n </div>\n </div>\n <div class="row" ng-if="!vm.loader">\n <div class="col-xs-12">\n <canvas ng-if="vm.chartType === \'line\'" id="line" class="chart chart-line" chart-data="vm.chart.data" chart-options="{datasetFill: false}"\n chart-labels="vm.chart.labels" chart-legend="false" chart-series="vm.chart.series">\n </canvas>\n <canvas ng-if="vm.chartType === \'bar\'" id="bar" class="chart chart-bar" chart-data="vm.chart.data"\n chart-labels="vm.chart.labels" chart-legend="false" chart-series="vm.chart.series">\n </canvas>\n <!-- <pre>{{vm.chartMeters | json}}</pre> -->\n </div>\n </div>\n <div class="row" ng-if="!vm.loader">\n <div class="col-xs-12">\n <a ng-click="vm.removeFromChart(meter)" class="btn btn-chart" ng-style="{\'background-color\': vm.chartColors[$index]}" ng-repeat="meter in vm.chartMeters">\n {{meter.resource_name}}\n </a>\n </div>\n </div>\n</section>\n<section ng-if="!vm.loader && vm.error">\n <div class="alert alert-danger">\n {{vm.error}}\n </div>\n</section>'),e.put("templates/ceilometer-stats.tpl.html",'<div ng-show="vm.loader" class="loader">Loading</div>\n\n<section ng-if="!vm.loader && !vm.error">\n\n <div class="alert alert-danger" ng-if="vm.stats.length == 0">\n No result\n </div> \n\n <table class="table" ng-if="vm.stats.length > 0">\n <tr>\n <th>Type:</th>\n <th>Meter:</th>\n <th>Unit:</th>\n <th>Value:</th>\n </tr>\n <tr ng-repeat="item in vm.stats">\n <td>{{item.type}}</td>\n <td>{{item.meter}}</td>\n <td>{{item.unit}}</td>\n <td>{{item.value}}</td>\n </tr>\n </table>\n</section>\n\n<section ng-if="!vm.loader && vm.error">\n <div class="alert alert-danger">\n {{vm.error}}\n </div>\n</section>\n')}]),angular.module("xos.ceilometerDashboard").run(["$location",function(e){e.path("/")}]),angular.bootstrap(angular.element("#xosCeilometerDashboard"),["xos.ceilometerDashboard"]);
\ No newline at end of file
diff --git a/xos/observers/vcpe/files/etc/rc.local b/xos/observers/vcpe/files/etc/rc.local
new file mode 100755
index 0000000..24c8f8d
--- /dev/null
+++ b/xos/observers/vcpe/files/etc/rc.local
@@ -0,0 +1,22 @@
+#!/bin/sh -e
+#
+# rc.local
+#
+# This script is executed at the end of each multiuser runlevel.
+# Make sure that the script will "exit 0" on success or any other
+# value on error.
+#
+# In order to enable or disable this script just change the execution
+# bits.
+#
+# By default this script does nothing.
+
+ufw enable
+ufw allow bootps
+ufw allow from 192.168.0.0/24
+ufw route allow in on eth1 out on eth0
+ufw route allow in on eth1 out on eth2
+
+# service dnsmasq start
+
+exit 0
diff --git a/xos/observers/vcpe/files/etc/ufw/after.init b/xos/observers/vcpe/files/etc/ufw/after.init
new file mode 100644
index 0000000..e89217d
--- /dev/null
+++ b/xos/observers/vcpe/files/etc/ufw/after.init
@@ -0,0 +1,40 @@
+#!/bin/sh
+#
+# after.init: if executable, called by ufw-init. See 'man ufw-framework' for
+# details. Note that output from these scripts is not seen via the
+# the ufw command, but instead via ufw-init.
+#
+# Copyright 2013 Canonical Ltd.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+set -e
+
+case "$1" in
+start)
+ # typically required
+ ;;
+stop)
+ # typically required
+ ;;
+status)
+ # optional
+ ;;
+flush-all)
+ # optional
+ ;;
+*)
+ echo "'$1' not supported"
+ echo "Usage: after.init {start|stop|flush-all|status}"
+ ;;
+esac
diff --git a/xos/observers/vcpe/files/etc/ufw/after.rules b/xos/observers/vcpe/files/etc/ufw/after.rules
new file mode 100644
index 0000000..0d6c646
--- /dev/null
+++ b/xos/observers/vcpe/files/etc/ufw/after.rules
@@ -0,0 +1,30 @@
+#
+# rules.input-after
+#
+# Rules that should be run after the ufw command line added rules. Custom
+# rules should be added to one of these chains:
+# ufw-after-input
+# ufw-after-output
+# ufw-after-forward
+#
+
+# Don't delete these required lines, otherwise there will be errors
+*filter
+:ufw-after-input - [0:0]
+:ufw-after-output - [0:0]
+:ufw-after-forward - [0:0]
+# End required lines
+
+# don't log noisy services by default
+-A ufw-after-input -p udp --dport 137 -j ufw-skip-to-policy-input
+-A ufw-after-input -p udp --dport 138 -j ufw-skip-to-policy-input
+-A ufw-after-input -p tcp --dport 139 -j ufw-skip-to-policy-input
+-A ufw-after-input -p tcp --dport 445 -j ufw-skip-to-policy-input
+-A ufw-after-input -p udp --dport 67 -j ufw-skip-to-policy-input
+-A ufw-after-input -p udp --dport 68 -j ufw-skip-to-policy-input
+
+# don't log noisy broadcast
+-A ufw-after-input -m addrtype --dst-type BROADCAST -j ufw-skip-to-policy-input
+
+# don't delete the 'COMMIT' line or these rules won't be processed
+COMMIT
diff --git a/xos/observers/vcpe/files/etc/ufw/after6.rules b/xos/observers/vcpe/files/etc/ufw/after6.rules
new file mode 100644
index 0000000..0d99672
--- /dev/null
+++ b/xos/observers/vcpe/files/etc/ufw/after6.rules
@@ -0,0 +1,27 @@
+#
+# rules.input-after
+#
+# Rules that should be run after the ufw command line added rules. Custom
+# rules should be added to one of these chains:
+# ufw6-after-input
+# ufw6-after-output
+# ufw6-after-forward
+#
+
+# Don't delete these required lines, otherwise there will be errors
+*filter
+:ufw6-after-input - [0:0]
+:ufw6-after-output - [0:0]
+:ufw6-after-forward - [0:0]
+# End required lines
+
+# don't log noisy services by default
+-A ufw6-after-input -p udp --dport 137 -j ufw6-skip-to-policy-input
+-A ufw6-after-input -p udp --dport 138 -j ufw6-skip-to-policy-input
+-A ufw6-after-input -p tcp --dport 139 -j ufw6-skip-to-policy-input
+-A ufw6-after-input -p tcp --dport 445 -j ufw6-skip-to-policy-input
+-A ufw6-after-input -p udp --dport 546 -j ufw6-skip-to-policy-input
+-A ufw6-after-input -p udp --dport 547 -j ufw6-skip-to-policy-input
+
+# don't delete the 'COMMIT' line or these rules won't be processed
+COMMIT
diff --git a/xos/observers/vcpe/files/etc/ufw/before.init b/xos/observers/vcpe/files/etc/ufw/before.init
new file mode 100644
index 0000000..1348cb1
--- /dev/null
+++ b/xos/observers/vcpe/files/etc/ufw/before.init
@@ -0,0 +1,40 @@
+#!/bin/sh
+#
+# before.init: if executable, called by ufw-init. See 'man ufw-framework' for
+# details. Note that output from these scripts is not seen via the
+# the ufw command, but instead via ufw-init.
+#
+# Copyright 2013 Canonical Ltd.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3,
+# as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+set -e
+
+case "$1" in
+start)
+ # typically required
+ ;;
+stop)
+ # typically required
+ ;;
+status)
+ # optional
+ ;;
+flush-all)
+ # optional
+ ;;
+*)
+ echo "'$1' not supported"
+ echo "Usage: before.init {start|stop|flush-all|status}"
+ ;;
+esac
diff --git a/xos/observers/vcpe/files/etc/ufw/before6.rules b/xos/observers/vcpe/files/etc/ufw/before6.rules
new file mode 100644
index 0000000..0b26ed8
--- /dev/null
+++ b/xos/observers/vcpe/files/etc/ufw/before6.rules
@@ -0,0 +1,73 @@
+#
+# rules.before
+#
+# Rules that should be run before the ufw command line added rules. Custom
+# rules should be added to one of these chains:
+# ufw6-before-input
+# ufw6-before-output
+# ufw6-before-forward
+#
+
+# Don't delete these required lines, otherwise there will be errors
+*filter
+:ufw6-before-input - [0:0]
+:ufw6-before-output - [0:0]
+:ufw6-before-forward - [0:0]
+# End required lines
+
+
+# allow all on loopback
+-A ufw6-before-input -i lo -j ACCEPT
+-A ufw6-before-output -o lo -j ACCEPT
+
+# drop packets with RH0 headers
+-A ufw6-before-input -m rt --rt-type 0 -j DROP
+-A ufw6-before-forward -m rt --rt-type 0 -j DROP
+-A ufw6-before-output -m rt --rt-type 0 -j DROP
+
+# for stateless autoconfiguration (restrict NDP messages to hop limit of 255)
+-A ufw6-before-input -p icmpv6 --icmpv6-type neighbor-solicitation -m hl --hl-eq 255 -j ACCEPT
+-A ufw6-before-output -p icmpv6 --icmpv6-type neighbor-solicitation -m hl --hl-eq 255 -j ACCEPT
+-A ufw6-before-input -p icmpv6 --icmpv6-type neighbor-advertisement -m hl --hl-eq 255 -j ACCEPT
+-A ufw6-before-output -p icmpv6 --icmpv6-type neighbor-advertisement -m hl --hl-eq 255 -j ACCEPT
+-A ufw6-before-input -p icmpv6 --icmpv6-type router-solicitation -m hl --hl-eq 255 -j ACCEPT
+-A ufw6-before-input -p icmpv6 --icmpv6-type router-advertisement -m hl --hl-eq 255 -j ACCEPT
+
+# quickly process packets for which we already have a connection
+-A ufw6-before-input -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-A ufw6-before-output -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-A ufw6-before-forward -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+
+# for multicast ping replies from link-local addresses (these don't have an
+# associated connection and would otherwise be marked INVALID)
+-A ufw6-before-input -p icmpv6 --icmpv6-type echo-reply -s fe80::/10 -j ACCEPT
+
+# drop INVALID packets (logs these in loglevel medium and higher)
+-A ufw6-before-input -m conntrack --ctstate INVALID -j ufw6-logging-deny
+-A ufw6-before-input -m conntrack --ctstate INVALID -j DROP
+
+# ok icmp codes for INPUT
+-A ufw6-before-input -p icmpv6 --icmpv6-type destination-unreachable -j ACCEPT
+-A ufw6-before-input -p icmpv6 --icmpv6-type packet-too-big -j ACCEPT
+-A ufw6-before-input -p icmpv6 --icmpv6-type time-exceeded -j ACCEPT
+-A ufw6-before-input -p icmpv6 --icmpv6-type parameter-problem -j ACCEPT
+-A ufw6-before-input -p icmpv6 --icmpv6-type echo-request -j ACCEPT
+
+# ok icmp code for FORWARD
+-A ufw6-before-forward -p icmpv6 --icmpv6-type destination-unreachable -j ACCEPT
+-A ufw6-before-forward -p icmpv6 --icmpv6-type packet-too-big -j ACCEPT
+-A ufw6-before-forward -p icmpv6 --icmpv6-type time-exceeded -j ACCEPT
+-A ufw6-before-forward -p icmpv6 --icmpv6-type parameter-problem -j ACCEPT
+-A ufw6-before-forward -p icmpv6 --icmpv6-type echo-request -j ACCEPT
+
+# allow dhcp client to work
+-A ufw6-before-input -p udp -s fe80::/10 --sport 547 -d fe80::/10 --dport 546 -j ACCEPT
+
+# allow MULTICAST mDNS for service discovery
+-A ufw6-before-input -p udp -d ff02::fb --dport 5353 -j ACCEPT
+
+# allow MULTICAST UPnP for service discovery
+-A ufw6-before-input -p udp -d ff02::f --dport 1900 -j ACCEPT
+
+# don't delete the 'COMMIT' line or these rules won't be processed
+COMMIT
diff --git a/xos/observers/vcpe/files/etc/ufw/sysctl.conf b/xos/observers/vcpe/files/etc/ufw/sysctl.conf
new file mode 100644
index 0000000..8707032
--- /dev/null
+++ b/xos/observers/vcpe/files/etc/ufw/sysctl.conf
@@ -0,0 +1,57 @@
+#
+# Configuration file for setting network variables. Please note these settings
+# override /etc/sysctl.conf. If you prefer to use /etc/sysctl.conf, please
+# adjust IPT_SYSCTL in /etc/default/ufw.
+#
+
+# Uncomment this to allow this host to route packets between interfaces
+#net/ipv4/ip_forward=1
+#net/ipv6/conf/default/forwarding=1
+#net/ipv6/conf/all/forwarding=1
+
+# Turn on Source Address Verification in all interfaces to prevent some
+# spoofing attacks
+net/ipv4/conf/default/rp_filter=1
+net/ipv4/conf/all/rp_filter=1
+
+# Do not accept IP source route packets (we are not a router)
+net/ipv4/conf/default/accept_source_route=0
+net/ipv4/conf/all/accept_source_route=0
+net/ipv6/conf/default/accept_source_route=0
+net/ipv6/conf/all/accept_source_route=0
+
+# Disable ICMP redirects. ICMP redirects are rarely used but can be used in
+# MITM (man-in-the-middle) attacks. Disabling ICMP may disrupt legitimate
+# traffic to those sites.
+net/ipv4/conf/default/accept_redirects=0
+net/ipv4/conf/all/accept_redirects=0
+net/ipv6/conf/default/accept_redirects=0
+net/ipv6/conf/all/accept_redirects=0
+
+# Ignore bogus ICMP errors
+net/ipv4/icmp_echo_ignore_broadcasts=1
+net/ipv4/icmp_ignore_bogus_error_responses=1
+net/ipv4/icmp_echo_ignore_all=0
+
+# Don't log Martian Packets (impossible packets)
+net/ipv4/conf/default/log_martians=0
+net/ipv4/conf/all/log_martians=0
+
+# Change to '1' to enable TCP/IP SYN cookies This disables TCP Window Scaling
+# (http://lkml.org/lkml/2008/2/5/167)
+net/ipv4/tcp_syncookies=0
+
+#net/ipv4/tcp_fin_timeout=30
+#net/ipv4/tcp_keepalive_intvl=1800
+
+# normally allowing tcp_sack is ok, but if going through OpenBSD 3.8 RELEASE or
+# earlier pf firewall, should set this to 0
+net/ipv4/tcp_sack=1
+
+# Uncomment this to turn off ipv6 autoconfiguration
+#net/ipv6/conf/default/autoconf=0
+#net/ipv6/conf/all/autoconf=0
+
+# Uncomment this to enable ipv6 privacy addressing
+#net/ipv6/conf/default/use_tempaddr=2
+#net/ipv6/conf/all/use_tempaddr=2
diff --git a/xos/observers/vcpe/files/etc/ufw/ufw.conf b/xos/observers/vcpe/files/etc/ufw/ufw.conf
new file mode 100644
index 0000000..28fe534
--- /dev/null
+++ b/xos/observers/vcpe/files/etc/ufw/ufw.conf
@@ -0,0 +1,10 @@
+# /etc/ufw/ufw.conf
+#
+
+# Set to yes to start on boot. If setting this remotely, be sure to add a rule
+# to allow your remote connection before starting ufw. Eg: 'ufw allow 22/tcp'
+ENABLED=yes
+
+# Please use the 'ufw' command to set the loglevel. Eg: 'ufw logging medium'.
+# See 'man ufw' for details.
+LOGLEVEL=low
diff --git a/xos/observers/vcpe/steps/sync_vcpetenant_new.yaml b/xos/observers/vcpe/steps/sync_vcpetenant_new.yaml
index 960b480..eb647b6 100644
--- a/xos/observers/vcpe/steps/sync_vcpetenant_new.yaml
+++ b/xos/observers/vcpe/steps/sync_vcpetenant_new.yaml
@@ -67,8 +67,39 @@
notify:
- restart dnsmasq
+ - name: create directory for "safe" config
+ file: path=/var/container_volumes/{{ container_name }}/etc/dnsmasq.d/safe state=directory
+
+ - name: dnsmasq "safe" config
+ template: src=/opt/xos/observers/vcpe/templates/dnsmasq_safe_servers.j2 dest=/var/container_volumes/{{ container_name }}/etc/dnsmasq.d/safe/servers.conf owner=root group=root
+ notify:
+ - restart dnsmasq
+
+ - name: copy base ufw files
+ synchronize: src=/opt/xos/observers/vcpe/files/etc/ufw/ dest=/var/container_volumes/{{ container_name }}/etc/ufw/
+ notify:
+ - reload ufw
+
+ - name: redirection rules for safe DNS
+ template: src=/opt/xos/observers/vcpe/templates/before.rules.j2 dest=/var/container_volumes/{{ container_name }}/etc/ufw/before.rules owner=root group=root
+ notify:
+ - reload ufw
+
+ - name: base ufw setup uses /etc/rc.local
+ copy: src=/opt/xos/observers/vcpe/files/etc/rc.local dest=/var/container_volumes/{{ container_name }}/etc/ owner=root group=root
+ notify:
+ - copy in /etc/rc.local
+
handlers:
# Dnsmasq is automatically restarted in the container
- name: restart dnsmasq
shell: docker exec {{ container_name }} /usr/bin/killall dnsmasq
+ - name: reload ufw
+ shell: docker exec {{ container_name }} bash -c "/sbin/iptables -t nat -F PREROUTING; /usr/sbin/ufw reload"
+
+ # Use docker cp instead of single-file volume
+ # The reason is that changes to external file volume don't show up inside the container
+ # Probably Ansible deletes and then recreates the external file, and container has old version
+ - name: copy in /etc/rc.local
+ shell: docker cp /var/container_volumes/{{ container_name }}/etc/rc.local {{ container_name }}:/etc/
diff --git a/xos/observers/vcpe/templates/before.rules.j2 b/xos/observers/vcpe/templates/before.rules.j2
new file mode 100644
index 0000000..e6f7d4a
--- /dev/null
+++ b/xos/observers/vcpe/templates/before.rules.j2
@@ -0,0 +1,97 @@
+#
+# rules.before
+#
+# Rules that should be run before the ufw command line added rules. Custom
+# rules should be added to one of these chains:
+# ufw-before-input
+# ufw-before-output
+# ufw-before-forward
+#
+
+# nat Table rules
+*nat
+:POSTROUTING ACCEPT [0:0]
+
+# Forward traffic from eth1 through eth0.
+-A POSTROUTING -o eth0 -j MASQUERADE
+
+# Set up NAT for CDN services
+-A POSTROUTING -o eth2 -j MASQUERADE
+
+# DNS safe browsing
+{% if safe_browsing %}
+{% for mac in safe_browsing %}
+-A PREROUTING -i eth1 -m mac --mac-source {{ mac }} -p udp --dport 53 -j REDIRECT --to-port 5353
+-A PREROUTING -i eth1 -m mac --mac-source {{ mac }} -p tcp --dport 53 -j REDIRECT --to-port 5353
+{% endfor %}
+{% endif %}
+
+# don't delete the 'COMMIT' line or these nat table rules won't be processed
+COMMIT
+
+# Don't delete these required lines, otherwise there will be errors
+*filter
+:ufw-before-input - [0:0]
+:ufw-before-output - [0:0]
+:ufw-before-forward - [0:0]
+:ufw-not-local - [0:0]
+# End required lines
+
+# allow all on loopback
+-A ufw-before-input -i lo -j ACCEPT
+-A ufw-before-output -o lo -j ACCEPT
+
+# quickly process packets for which we already have a connection
+-A ufw-before-input -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-A ufw-before-output -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-A ufw-before-forward -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+
+# drop INVALID packets (logs these in loglevel medium and higher)
+-A ufw-before-input -m conntrack --ctstate INVALID -j ufw-logging-deny
+-A ufw-before-input -m conntrack --ctstate INVALID -j DROP
+
+# ok icmp codes for INPUT
+-A ufw-before-input -p icmp --icmp-type destination-unreachable -j ACCEPT
+-A ufw-before-input -p icmp --icmp-type source-quench -j ACCEPT
+-A ufw-before-input -p icmp --icmp-type time-exceeded -j ACCEPT
+-A ufw-before-input -p icmp --icmp-type parameter-problem -j ACCEPT
+-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT
+
+# ok icmp code for FORWARD
+-A ufw-before-forward -p icmp --icmp-type destination-unreachable -j ACCEPT
+-A ufw-before-forward -p icmp --icmp-type source-quench -j ACCEPT
+-A ufw-before-forward -p icmp --icmp-type time-exceeded -j ACCEPT
+-A ufw-before-forward -p icmp --icmp-type parameter-problem -j ACCEPT
+-A ufw-before-forward -p icmp --icmp-type echo-request -j ACCEPT
+
+# allow dhcp client to work
+-A ufw-before-input -p udp --sport 67 --dport 68 -j ACCEPT
+
+#
+# ufw-not-local
+#
+-A ufw-before-input -j ufw-not-local
+
+# if LOCAL, RETURN
+-A ufw-not-local -m addrtype --dst-type LOCAL -j RETURN
+
+# if MULTICAST, RETURN
+-A ufw-not-local -m addrtype --dst-type MULTICAST -j RETURN
+
+# if BROADCAST, RETURN
+-A ufw-not-local -m addrtype --dst-type BROADCAST -j RETURN
+
+# all other non-local packets are dropped
+-A ufw-not-local -m limit --limit 3/min --limit-burst 10 -j ufw-logging-deny
+-A ufw-not-local -j DROP
+
+# allow MULTICAST mDNS for service discovery (be sure the MULTICAST line above
+# is uncommented)
+-A ufw-before-input -p udp -d 224.0.0.251 --dport 5353 -j ACCEPT
+
+# allow MULTICAST UPnP for service discovery (be sure the MULTICAST line above
+# is uncommented)
+-A ufw-before-input -p udp -d 239.255.255.250 --dport 1900 -j ACCEPT
+
+# don't delete the 'COMMIT' line or these rules won't be processed
+COMMIT
diff --git a/xos/observers/vcpe/templates/dnsmasq_safe_servers.j2 b/xos/observers/vcpe/templates/dnsmasq_safe_servers.j2
new file mode 100644
index 0000000..2f93777
--- /dev/null
+++ b/xos/observers/vcpe/templates/dnsmasq_safe_servers.j2
@@ -0,0 +1,14 @@
+# This file autogenerated by vCPE observer
+# It contains a list of DNS servers for dnsmasq to use.
+no-resolv
+
+{% if cdn_enable %}
+# CDN
+{% for prefix in cdn_prefixes %}
+server=/{{ prefix }}/{{ dnsdemux_ip }}
+{% endfor %}
+{% endif %}
+
+# use OpenDNS service
+server=208.67.222.123
+server=208.67.220.123
diff --git a/xos/observers/vcpe/templates/dnsmasq_servers.j2 b/xos/observers/vcpe/templates/dnsmasq_servers.j2
index 88f523d..c89c762 100644
--- a/xos/observers/vcpe/templates/dnsmasq_servers.j2
+++ b/xos/observers/vcpe/templates/dnsmasq_servers.j2
@@ -3,21 +3,12 @@
no-resolv
{% if cdn_enable %}
-# CDN
+# CDN
{% for prefix in cdn_prefixes %}
server=/{{ prefix }}/{{ dnsdemux_ip }}
{% endfor %}
{% endif %}
-{% if url_filter_enable %}
-# Point to BroadbandShield service, disable cache, and enable eDNS MAC passing
-add-mac
-cache-size=0
-{% for addr in bbs_addrs %}
-server={{ addr }}
-{% endfor %}
-{% else %}
# use google's DNS service
server=8.8.8.8
server=8.8.4.4
-{% endif %}
diff --git a/xos/tosca/custom_types/xos.m4 b/xos/tosca/custom_types/xos.m4
index dc23c84..30798d5 100644
--- a/xos/tosca/custom_types/xos.m4
+++ b/xos/tosca/custom_types/xos.m4
@@ -166,6 +166,19 @@
type: string
required: false
+ tosca.nodes.ONOSVTNApp:
+ derived_from: tosca.nodes.Root
+ description: >
+ An ONOS VTN Application.
+ properties:
+ xos_base_tenant_props
+ dependencies:
+ type: string
+ required: false
+ rest_onos/v1/network/configuration/:
+ type: string
+ required: false
+
tosca.nodes.VCPEService:
description: >
CORD: The vCPE Service.
diff --git a/xos/tosca/custom_types/xos.yaml b/xos/tosca/custom_types/xos.yaml
index 1256c3b..63d7395 100644
--- a/xos/tosca/custom_types/xos.yaml
+++ b/xos/tosca/custom_types/xos.yaml
@@ -195,6 +195,26 @@
type: string
required: false
+ tosca.nodes.ONOSVTNApp:
+ derived_from: tosca.nodes.Root
+ description: >
+ An ONOS VTN Application.
+ properties:
+ kind:
+ type: string
+ default: generic
+ description: Kind of tenant
+ service_specific_id:
+ type: string
+ required: false
+ description: Service specific ID opaque to XOS but meaningful to service
+ dependencies:
+ type: string
+ required: false
+ rest_onos/v1/network/configuration/:
+ type: string
+ required: false
+
tosca.nodes.VCPEService:
description: >
CORD: The vCPE Service.
diff --git a/xos/tosca/resources/onosapp.py b/xos/tosca/resources/onosapp.py
index 7ed47d7..42cb0bf 100644
--- a/xos/tosca/resources/onosapp.py
+++ b/xos/tosca/resources/onosapp.py
@@ -12,7 +12,7 @@
from xosresource import XOSResource
class XOSONOSApp(XOSResource):
- provides = ["tosca.nodes.ONOSApp", "tosca.nodes.ONOSvBNGApp", "tosca.nodes.ONOSvOLTApp"]
+ provides = ["tosca.nodes.ONOSApp", "tosca.nodes.ONOSvBNGApp", "tosca.nodes.ONOSvOLTApp", "tosca.nodes.ONOSVTNApp"]
xos_model = ONOSApp
copyin_props = ["service_specific_id", "dependencies"]
diff --git a/xos/tosca/samples/vtn.yaml b/xos/tosca/samples/vtn.yaml
index 9cb7c95..5ce4670 100644
--- a/xos/tosca/samples/vtn.yaml
+++ b/xos/tosca/samples/vtn.yaml
@@ -18,13 +18,16 @@
pubkey: /opt/xos/observers/onos/onos_key.pub
VTN_ONOS_app:
- type: tosca.nodes.ONOSvBNGApp
+ type: tosca.nodes.ONOSVTNApp
requirements:
- onos_tenant:
node: service_ONOS_VTN
relationship: tosca.relationships.TenantOfService
properties:
-# dependencies: org.onosproject.proxyarp, org.onosproject.virtualbng, org.onosproject.openflow, org.onosproject.fwd
+ dependencies: org.onosproject.openstackswitching, org.onosproject.cordvtn
+ rest_onos/v1/network/configuration/: { get_artifact: [ SELF, vtn_network_cfg_json, LOCAL_FILE ] }
+ artifacts:
+ vtn_network_cfg_json: /root/setup/vtn-network-cfg.json
# docker image for vcpe containers
docker-onos:
diff --git a/xos/xos_configuration/xos_frontend_config b/xos/xos_configuration/xos_frontend_config
deleted file mode 100755
index 13fe53b..0000000
--- a/xos/xos_configuration/xos_frontend_config
+++ /dev/null
@@ -1,4 +0,0 @@
-[gui]
-branding_name=CORD
-branding_css=/static/cord.css
-branding_icon=/static/onos-logo.png