Merge branch 'master' into feature/fabric-synchronizer
diff --git a/applications/auto-scale/xos_auto_scaling_app.py b/applications/auto-scale/xos_auto_scaling_app.py
index 848ccc0..68ba4cf 100644
--- a/applications/auto-scale/xos_auto_scaling_app.py
+++ b/applications/auto-scale/xos_auto_scaling_app.py
@@ -17,6 +17,12 @@
use_kafka = True
+XOS_ENDPOINT = '130.127.133.58:9999'
+KAFKA_SERVER_IP = '130.127.133.58'
+KAFKA_SERVER_PORT = '9092'
+KAFKA_TOPIC = 'auto-scale'
+LOCAL_KAFKA_TARGET_URL = 'kafka://'+KAFKA_SERVER_IP+':'+KAFKA_SERVER_PORT+'?topic='+KAFKA_TOPIC
+
if use_kafka:
import kafka
from kafka import TopicPartition
@@ -31,7 +37,7 @@
return response
def acquire_xos_monitoring_channel():
- url = "http://ctl:9999/xoslib/monitoringchannel/"
+ url = "http://"+XOS_ENDPOINT+"/api/tenant/ceilometer/monitoringchannel/"
admin_auth=("padmin@vicci.org", "letmein") # use your XOS username and password
monitoring_channels = requests.get(url, auth=admin_auth).json()
ceilometer_url = None
@@ -85,7 +91,7 @@
def loadAllXosTenantInfo():
print "SRIKANTH: Loading all XOS tenant info"
- url = "http://ctl:9999/xos/controllerslices/"
+ url = "http://"+XOS_ENDPOINT+"/xos/controllerslices/"
admin_auth=("padmin@vicci.org", "letmein") # use your XOS username and password
controller_slices = requests.get(url, auth=admin_auth).json()
for cslice in controller_slices:
@@ -101,7 +107,7 @@
def loadAllXosInstanceInfo():
print "SRIKANTH: Loading all XOS instance info"
- url = "http://ctl:9999/xos/instances/"
+ url = "http://"+XOS_ENDPOINT+"/xos/instances/"
admin_auth=("padmin@vicci.org", "letmein") # use your XOS username and password
xos_instances = requests.get(url, auth=admin_auth).json()
for instance in xos_instances:
@@ -148,7 +154,7 @@
return
print "SRIKANTH: SCALE %s for Project %s, Slice=%s, Service=%s from current=%d to new=%d" % (adjust, project, xos_slice, xos_service, current_instances, current_instances+1 if (adjust=='up') else current_instances-1)
query_params = {'service':xos_service, 'slice_hint':xos_slice, 'scale':current_instances+1 if (adjust=='up') else current_instances-1}
- url = "http://ctl:9999/xoslib/serviceadjustscale/"
+ url = "http://"+XOS_ENDPOINT+"/xoslib/serviceadjustscale/"
admin_auth=("padmin@vicci.org", "letmein") # use your XOS username and password
response = requests.get(url, params=query_params, auth=admin_auth).json()
print "SRIKANTH: XOS adjust_scale response: %s" % response
@@ -233,7 +239,7 @@
def process_notification_from_ceilometer(sample):
if sample['counter_name'] == 'instance':
- if 'delete' in sample['resource_metadata']['event_type']:
+ if ('event_type' in sample['resource_metadata'].keys()) and ('delete' in sample['resource_metadata']['event_type']):
xosTenantInfo = getXosTenantInfo(sample['project_id'])
xosResourceInfo = getXosInstanceInfo(sample['resource_id'])
print "SRIKANTH: Project %s Instance %s is getting deleted" % (xosTenantInfo['slice'] if xosTenantInfo['slice'] else sample['project_id'],xosResourceInfo)
@@ -305,8 +311,8 @@
loadAllXosInstanceInfo()
ceilometer_url = monitoring_channel['ceilometer_url']
if use_kafka:
- thread.start_new(read_notification_from_ceilometer_over_kafka, ("10.11.10.1","9092","auto-scale",))
- subscribe_data = {"sub_info":"cpu_util","app_id":"xos_auto_scale","target":"kafka://10.11.10.1:9092?topic=auto-scale"}
+ thread.start_new(read_notification_from_ceilometer_over_kafka, (KAFKA_SERVER_IP,KAFKA_SERVER_PORT,KAFKA_TOPIC,))
+ subscribe_data = {"sub_info":"cpu_util","app_id":"xos_auto_scale","target":LOCAL_KAFKA_TARGET_URL}
else:
thread.start_new(read_notification_from_ceilometer,(UDP_IP,UDP_PORT,))
subscribe_data = {"sub_info":"cpu_util","app_id":"xos_auto_scale","target":"udp://10.11.10.1:12346"}
@@ -317,7 +323,10 @@
if (not 'sucess' in response.text) and (not 'already exists' in response.text):
print 'SRIKANTH: Ceilometer meter "cpu_util" Subscription unsuccessful...Exiting'
return
- subscribe_data = {"sub_info":"instance","app_id":"xos_auto_scale2","target":"udp://10.11.10.1:12346"}
+ if use_kafka:
+ subscribe_data = {"sub_info":"instance","app_id":"xos_auto_scale2","target":LOCAL_KAFKA_TARGET_URL}
+ else:
+ subscribe_data = {"sub_info":"instance","app_id":"xos_auto_scale2","target":"udp://10.11.10.1:12346"}
subscribe_url = ceilometer_url + 'v2/subscribe'
response = requests.post(subscribe_url, data=json.dumps(subscribe_data))
print 'SRIKANTH: Ceilometer meter "instance" Subscription status:%s' % response.text
diff --git a/containers/onboarding_synchronizer/Dockerfile b/containers/onboarding_synchronizer/Dockerfile
new file mode 100644
index 0000000..967e234
--- /dev/null
+++ b/containers/onboarding_synchronizer/Dockerfile
@@ -0,0 +1,40 @@
+FROM xosproject/xos-synchronizer-openstack
+
+# Install docker-in-docker (dind). See https://hub.docker.com/_/docker/. The docker git repo
+# currently only has 1.10 and 1.11, but it's possible to get the dockerfiles for earlier
+# versions by using:
+# docker pull centurylink/dockerfile-from-image
+# alias dfimage="docker run -v /var/run/docker.sock:/var/run/docker.sock --rm centurylink/dockerfile-from-image"
+# dgimage <name of image>
+
+# This container must be started in privileged mode.
+
+RUN apt-get install -y curl
+# iptables
+ENV DOCKER_BUCKET=get.docker.com
+ENV DOCKER_VERSION=1.8.3
+ENV DOCKER_SHA256=f024bc65c45a3778cf07213d26016075e8172de8f6e4b5702bedde06c241650f
+RUN curl -fSL "https://${DOCKER_BUCKET}/builds/Linux/x86_64/docker-$DOCKER_VERSION" -o /usr/local/bin/docker && echo "${DOCKER_SHA256} /usr/local/bin/docker" | sha256sum -c - && chmod +x /usr/local/bin/docker
+
+# XXX uncomment the following 6 lines to run docker-in-docker
+# comment them out if using the docker socket in a volume instead
+#ENV DIND_COMMIT=3b5fac462d21ca164b3778647420016315289034
+#RUN wget "https://raw.githubusercontent.com/docker/docker/${DIND_COMMIT}/hack/dind" -O /usr/local/bin/dind && chmod +x /usr/local/bin/dind
+#COPY start-dockerd.sh /usr/local/bin/
+#VOLUME /var/lib/docker
+#EXPOSE 2375
+#ENTRYPOINT ["start-dockerd.sh"]
+
+# Instead of using docker-in-docker, we can just attach ourselves
+# to the docker socket via a volume in the docker-compose:
+# - /var/run/docker.sock:/var/run/docker.sock
+# This is more convenient, allowing us to build directly into our
+# parent's docker build system, making the images available for
+# instantiation on the parent.
+
+# Now install docker-compose
+
+RUN bash -c "curl -L https://github.com/docker/compose/releases/download/1.5.2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose"
+RUN chmod +x /usr/local/bin/docker-compose
+
+CMD update-ca-certificates && /usr/bin/supervisord -c /etc/supervisor/conf.d/synchronizer.conf
diff --git a/containers/onboarding_synchronizer/Makefile b/containers/onboarding_synchronizer/Makefile
new file mode 100644
index 0000000..6532196
--- /dev/null
+++ b/containers/onboarding_synchronizer/Makefile
@@ -0,0 +1,15 @@
+IMAGE_NAME:=xosproject/xos-synchronizer-onboarding
+CONTAINER_NAME:=xos-synchronizer
+NO_DOCKER_CACHE?=false
+
+.PHONY: build
+build: ; sudo docker build --no-cache=${NO_DOCKER_CACHE} --rm -t ${IMAGE_NAME} .
+
+.PHONY: run
+run: ; sudo docker run -d --name ${CONTAINER_NAME} -v /usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro ${IMAGE_NAME}
+
+.PHONY: stop
+stop: ; sudo docker stop ${CONTAINER_NAME}
+
+.PHONY: rm
+rm: ; sudo docker rm ${CONTAINER_NAME}
diff --git a/containers/onboarding_synchronizer/start-dockerd.sh b/containers/onboarding_synchronizer/start-dockerd.sh
new file mode 100755
index 0000000..bb97341
--- /dev/null
+++ b/containers/onboarding_synchronizer/start-dockerd.sh
@@ -0,0 +1,3 @@
+#! /bin/bash
+
+docker daemon --host=unix:///var/run/docker.sock --host=tcp://0.0.0.0:2375 --storage-driver=aufs
diff --git a/containers/onos/Dockerfile b/containers/onos/Dockerfile
new file mode 100644
index 0000000..d00b8e2
--- /dev/null
+++ b/containers/onos/Dockerfile
@@ -0,0 +1,57 @@
+FROM debian:jessie
+MAINTAINER Zack Williams <zdw@cs.arizona.edu>
+
+# Add Java 8 repository
+ENV DEBIAN_FRONTEND noninteractive
+RUN echo debconf shared/accepted-oracle-license-v1-1 select true | debconf-set-selections && \
+ echo "deb http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main" | tee /etc/apt/sources.list.d/webupd8team-java.list && \
+ echo "deb-src http://ppa.launchpad.net/webupd8team/java/ubuntu trusty main" | tee -a /etc/apt/sources.list.d/webupd8team-java.list && \
+ apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys EEA14886
+
+# Set the environment variables
+ENV HOME /root
+ENV JAVA_HOME /usr/lib/jvm/java-8-oracle
+ENV ONOS_ROOT /src/onos
+ENV KARAF_VERSION 3.0.5
+ENV KARAF_ROOT /root/onos/apache-karaf-3.0.5
+ENV KARAF_LOG /root/onos/apache-karaf-3.0.5/data/log/karaf.log
+ENV BUILD_NUMBER docker
+ENV PATH $PATH:$KARAF_ROOT/bin
+
+#Download and Build ONOS
+WORKDIR /src
+RUN apt-get update && apt-get install -y python maven git curl oracle-java8-installer oracle-java8-set-default && \
+ git clone https://github.com/opennetworkinglab/onos.git && cd onos && \
+ git checkout f503a62372ffa55150936628689d1435109ffccb && \
+ mkdir -p /root/Downloads && \
+ mvn clean install && \
+ tools/build/onos-package && \
+ rm -rf /root/.m2 && cd .. && \
+ rm -rf onos && \
+ apt-get remove --purge -y `apt-mark showauto` && \
+ apt-get install oracle-java8-set-default -y && \
+ apt-get clean && apt-get purge -y && apt-get autoremove -y && \
+ rm -rf /var/lib/apt/lists/* && \
+ rm -rf /var/cache/oracle-jdk8-installer && \
+ rm -rf /root/Downloads
+
+# Change to /root directory
+WORKDIR /root
+
+#Install ONOS
+RUN mkdir onos && \
+ mv /tmp/onos-*.docker.tar.gz . && \
+ tar -xf onos-*.docker.tar.gz -C onos --strip-components=1 && \
+ rm -rf onos-*.docker.tar.gz
+
+# Ports
+# 6653 - OpenFlow
+# 8181 - GUI
+# 8101 - ONOS CLI
+# 9876 - ONOS CLUSTER COMMUNICATION
+EXPOSE 6653 8181 8101 9876
+
+# Get ready to run command
+WORKDIR /root/onos
+ENTRYPOINT ["./bin/onos-service"]
+
diff --git a/containers/onos/Makefile b/containers/onos/Makefile
new file mode 100644
index 0000000..4db1a9b
--- /dev/null
+++ b/containers/onos/Makefile
@@ -0,0 +1,15 @@
+IMAGE_NAME:=xosproject/onos-fork
+CONTAINER_NAME:=onos-fork
+NO_DOCKER_CACHE?=false
+
+.PHONY: build
+build: ; sudo docker build --no-cache=${NO_DOCKER_CACHE} --rm -t ${IMAGE_NAME} .
+
+.PHONY: run
+run: ; sudo docker run -d --name ${CONTAINER_NAME} ${IMAGE_NAME}
+
+.PHONY: stop
+stop: ; sudo docker stop ${CONTAINER_NAME}
+
+.PHONY: rm
+rm: ; sudo docker rm ${CONTAINER_NAME}
diff --git a/views/ngXosLib/package.json b/views/ngXosLib/package.json
index da9d27b..ba25551 100644
--- a/views/ngXosLib/package.json
+++ b/views/ngXosLib/package.json
@@ -12,7 +12,8 @@
"doc": "gulp docs; cd ./docs",
"doc:ci": "gulp makeDocs;",
"build": "gulp vendor && gulp helpers",
- "dev": "gulp dev"
+ "dev": "gulp dev",
+ "lint": "eslint xosHelpers/"
},
"author": "Matteo Scandolo",
"license": "ISC",
diff --git a/views/ngXosLib/xosHelpers/spec/services/helpers/form.helpers.test.js b/views/ngXosLib/xosHelpers/spec/services/helpers/form.helpers.test.js
index f6bfbb2..a126db5 100644
--- a/views/ngXosLib/xosHelpers/spec/services/helpers/form.helpers.test.js
+++ b/views/ngXosLib/xosHelpers/spec/services/helpers/form.helpers.test.js
@@ -17,8 +17,7 @@
'name',
'mail',
'active',
- 'created',
- 'custom'
+ 'created'
];
let modelField = {
@@ -26,8 +25,7 @@
name: {},
mail: {},
active: {},
- created: {},
- custom: {}
+ created: {}
};
let model = {
@@ -40,6 +38,14 @@
};
let customField = {
+ id: {
+ label: 'Id',
+ type: 'number',
+ validators: {
+ required: true
+ },
+ hint: ''
+ },
custom: {
label: 'Custom Label',
type: 'number',
@@ -52,7 +58,9 @@
id: {
label: 'Id:',
type: 'number',
- validators: {},
+ validators: {
+ required: true
+ },
hint: ''
},
name: {
@@ -152,7 +160,35 @@
describe('when modelField are provided', () => {
it('should combine modelField and customField in a form object', () => {
- expect(service.buildFormStructure(modelField, customField, model)).toEqual(formObject);
+ const form = service.buildFormStructure(modelField, customField, model);
+ expect(form).toEqual(formObject);
+ });
+
+ it('should override modelField properties whith customField properties', () => {
+ const customFieldOverride = {
+ id: {
+ hint: 'something',
+ type: 'select',
+ options: [
+ {id: 1, label: 'one'},
+ {id: 2, label: 'two'}
+ ],
+ validators: {
+ required: true
+ }
+ }
+ };
+ const form = service.buildFormStructure({id: {}}, customFieldOverride, model);
+
+ expect(form).toEqual({
+ id: {
+ label: 'Id:',
+ validators: {required: true},
+ hint: customFieldOverride.id.hint,
+ type: customFieldOverride.id.type,
+ options: customFieldOverride.id.options
+ }
+ });
});
});
@@ -185,6 +221,31 @@
label: 'Custom Label',
type: 'number',
hint: 'Test Hint'
+ },
+ select: {
+ label: 'Select Label',
+ type: 'select',
+ hint: 'Select Hint',
+ options: [
+ {id: 1, label: 'something'}
+ ]
+ },
+ object: {
+ label: 'Object Label',
+ type: 'object',
+ hint: 'Object Hint',
+ properties: {
+ foo: {
+ type: 'string',
+ label: 'FooLabel',
+ validators: {
+ required: true
+ }
+ },
+ bar: {
+ type: 'number'
+ }
+ }
}
};
@@ -224,19 +285,48 @@
type: 'number',
validators: {},
hint: 'Test Hint'
+ },
+ select: {
+ label: 'Select Label:',
+ type: 'select',
+ hint: 'Select Hint',
+ validators: {},
+ options: [
+ {id: 1, label: 'something'}
+ ]
+ },
+ object: {
+ label: 'Object Label:',
+ type: 'object',
+ hint: 'Object Hint',
+ validators: {},
+ properties: {
+ foo: {
+ type: 'string',
+ label: 'FooLabel',
+ validators: {
+ required: true
+ }
+ },
+ bar: {
+ type: 'number'
+ }
+ }
}
};
let empty_model = {5: 'Nan'}
it('should create a form object', () => {
- let res = service.buildFormStructure(empty_modelField, empty_customFields, empty_model)
+ let res = service.buildFormStructure(empty_modelField, empty_customFields, empty_model);
expect(res.id).toEqual(empty_formObject.id);
expect(res.name).toEqual(empty_formObject.name);
expect(res.mail).toEqual(empty_formObject.mail);
expect(res.active).toEqual(empty_formObject.active);
expect(res.created).toEqual(empty_formObject.created);
expect(res.custom).toEqual(empty_formObject.custom);
+ expect(res.select).toEqual(empty_formObject.select);
+ expect(res.object).toEqual(empty_formObject.object);
expect(res).toEqual(empty_formObject);
});
});
diff --git a/views/ngXosLib/xosHelpers/spec/ui/field.test.js b/views/ngXosLib/xosHelpers/spec/ui/field.test.js
index f933a01..62a41a7 100644
--- a/views/ngXosLib/xosHelpers/spec/ui/field.test.js
+++ b/views/ngXosLib/xosHelpers/spec/ui/field.test.js
@@ -59,6 +59,18 @@
expect(errorFunctionWrapper).toThrow(new Error('[xosField] Please provide a field definition'));
}));
+ it('should throw an error if no field type is passed', inject(($compile, $rootScope) => {
+ function errorFunctionWrapper(){
+ // setup the parent scope
+ scope = $rootScope.$new();
+ scope.name = 'label';
+ scope.ngModel = 1;
+ scope.field = {label: 'Label:'}
+ compileElement();
+ }
+ expect(errorFunctionWrapper).toThrow(new Error('[xosField] Please provide a type in the field definition'));
+ }));
+
it('should throw an error if no field model is passed', inject(($compile, $rootScope) => {
function errorFunctionWrapper(){
// setup the parent scope
@@ -92,6 +104,42 @@
});
});
+
+
+
+ describe('when a option is selected in dropdown', () => {
+ beforeEach(() => {
+ scope = rootScope.$new();
+ scope.name = 'label';
+ scope.field = {
+ label: 'Label',
+ type: 'select',
+ validators: {},
+ options: [
+ {
+ id: 0,
+ label: '---Site---'
+ },
+ {
+ id: 1,
+ label: '---Site1---'
+ }
+ ]
+ };
+ scope.ngModel = 'label';
+ compileElement();
+ });
+
+ it('No of select elements', () => {
+ expect($(element).find('select').children('option').length).toEqual(3);
+ });
+
+ it('should show a selected value', () => {
+ var elem = angular.element($(element).find('select').children('option')[1]);
+ expect(elem.text()).toEqual('---Site---');
+ });
+ });
+
describe('when a number input is passed', () => {
beforeEach(() => {
scope = rootScope.$new();
@@ -187,9 +235,36 @@
});
it('should not print the panel', () => {
- // console.log($(element).find('.panel.object-field'));
expect($(element).find('.panel.object-field')).not.toExist()
});
+
+ describe('but field is configured', () => {
+ beforeEach(() => {
+ scope.field.properties = {
+ foo: {
+ label: 'FooLabel:',
+ type: 'string',
+ validators: {
+ required: true
+ }
+ },
+ bar: {
+ type: 'number'
+ }
+ };
+ compileElement();
+ });
+ it('should render panel and configured fields', () => {
+ expect($(element).find('.panel.object-field')).toExist();
+ expect($(element).find('input[name="foo"]').parent().find('label').text()).toBe('FooLabel:');
+ expect($(element).find('input[name="foo"]')).toHaveAttr('type', 'string');
+ expect($(element).find('input[name="foo"]')).toHaveAttr('required');
+
+ expect($(element).find('input[name="bar"]').parent().find('label').text()).toBe('Bar:');
+ expect($(element).find('input[name="bar"]')).toHaveAttr('type', 'number');
+
+ });
+ });
});
});
});
diff --git a/views/ngXosLib/xosHelpers/spec/ui/form.test.js b/views/ngXosLib/xosHelpers/spec/ui/form.test.js
index cb9f9e8..eac10f5 100644
--- a/views/ngXosLib/xosHelpers/spec/ui/form.test.js
+++ b/views/ngXosLib/xosHelpers/spec/ui/form.test.js
@@ -33,21 +33,21 @@
compile = $compile;
}));
- it('should throw an error if no config is specified', inject(($compile, $rootScope) => {
+ it('should throw an error if no config is specified', () => {
function errorFunctionWrapper(){
compileElement();
}
expect(errorFunctionWrapper).toThrow(new Error('[xosForm] Please provide a configuration via the "config" attribute'));
- }));
+ });
- it('should throw an error if no actions is specified', inject(($compile, $rootScope) => {
+ it('should throw an error if no actions is specified', () => {
function errorFunctionWrapper(){
- scope = $rootScope.$new();
+ scope = rootScope.$new();
scope.config = 'green';
compileElement();
}
expect(errorFunctionWrapper).toThrow(new Error('[xosForm] Please provide an action list in the configuration'));
- }));
+ });
describe('when correctly configured', () => {
diff --git a/views/ngXosLib/xosHelpers/spec/ui/table.test.js b/views/ngXosLib/xosHelpers/spec/ui/table.test.js
index e94eb36..1535c6e 100644
--- a/views/ngXosLib/xosHelpers/spec/ui/table.test.js
+++ b/views/ngXosLib/xosHelpers/spec/ui/table.test.js
@@ -31,22 +31,22 @@
rootScope = $rootScope;
}));
- it('should throw an error if no config is specified', inject(($compile, $rootScope) => {
+ it('should throw an error if no config is specified', () => {
function errorFunctionWrapper(){
compileElement();
}
expect(errorFunctionWrapper).toThrow(new Error('[xosTable] Please provide a configuration via the "config" attribute'));
- }));
+ });
- it('should throw an error if no config columns are specified', inject(($compile, $rootScope) => {
+ it('should throw an error if no config columns are specified', () => {
function errorFunctionWrapper(){
// setup the parent scope
- scope = $rootScope.$new();
+ scope = rootScope.$new();
scope.config = 'green';
compileElement();
}
expect(errorFunctionWrapper).toThrow(new Error('[xosTable] Please provide a columns list in the configuration'));
- }));
+ });
describe('when basicly configured', function() {
diff --git a/views/ngXosLib/xosHelpers/spec/ui/validation.test.js b/views/ngXosLib/xosHelpers/spec/ui/validation.test.js
index bb663fa..f8350ed 100644
--- a/views/ngXosLib/xosHelpers/spec/ui/validation.test.js
+++ b/views/ngXosLib/xosHelpers/spec/ui/validation.test.js
@@ -7,7 +7,7 @@
(function () {
'use strict';
- let compile, element, scope, isolatedScope;
+ let compile, element, scope;
const compileElement = (el) => {
element = el;
@@ -20,7 +20,6 @@
}
compile(element)(scope);
scope.$digest();
- isolatedScope = element.isolateScope().vm;
}
describe('The xos.helper module', function(){
diff --git a/views/ngXosLib/xosHelpers/src/services/helpers/ui/form.helpers.js b/views/ngXosLib/xosHelpers/src/services/helpers/ui/form.helpers.js
index 04a383a..bc6a503 100644
--- a/views/ngXosLib/xosHelpers/src/services/helpers/ui/form.helpers.js
+++ b/views/ngXosLib/xosHelpers/src/services/helpers/ui/form.helpers.js
@@ -91,7 +91,7 @@
this.buildFormStructure = (modelField, customField, model) => {
- modelField = Object.keys(modelField).length > 0 ? modelField : customField; //if no model field are provided, check custom
+ modelField = angular.extend(modelField, customField);
customField = customField || {};
return _.reduce(Object.keys(modelField), (form, f) => {
@@ -100,9 +100,15 @@
label: (customField[f] && customField[f].label) ? `${customField[f].label}:` : LabelFormatter.format(f),
type: (customField[f] && customField[f].type) ? customField[f].type : this._getFieldFormat(model[f]),
validators: (customField[f] && customField[f].validators) ? customField[f].validators : {},
- hint: (customField[f] && customField[f].hint)? customField[f].hint : ''
+ hint: (customField[f] && customField[f].hint)? customField[f].hint : '',
};
+ if(customField[f] && customField[f].options){
+ form[f].options = customField[f].options;
+ }
+ if(customField[f] && customField[f].properties){
+ form[f].properties = customField[f].properties;
+ }
if(form[f].type === 'date'){
model[f] = new Date(model[f]);
}
diff --git a/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/field/field.component.js b/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/field/field.component.js
index 20b7707..686dd38 100644
--- a/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/field/field.component.js
+++ b/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/field/field.component.js
@@ -103,19 +103,41 @@
return $window._;
})
.controller('SampleCtrl', function(){
- this.name = 'input-name';
- this.field = {label: 'My Object Value:', type: 'object'};
- this.model = {
+ this.name1 = 'input-name';
+ this.field1 = {label: 'My Object Field:', type: 'object'};
+ this.model1 = {
name: 'Jhon',
age: '25',
email: 'jhon@thewall.ru',
active: true
};
+
+ this.name2 = 'another-name';
+ this.field2 = {
+ label: 'Empty Object Field',
+ type: 'object',
+ properties: {
+ foo: {
+ label: 'FooLabel:',
+ type: 'string',
+ validators: {
+ required: true
+ }
+ },
+ bar: {
+ type: 'number'
+ }
+ }
+ }
});
</file>
<file name="index.html">
<div ng-controller="SampleCtrl as vm">
- <xos-field ng-model="vm.model" name="vm.name" field="vm.field"></xos-field>
+ <h4>Autogenerated object field</h4>
+ <xos-field ng-model="vm.model1" name="vm.name1" field="vm.field1"></xos-field>
+
+ <h4>Configured object field</h4>
+ <xos-field ng-model="vm.model2" name="vm.name2" field="vm.field2"></xos-field>
</div>
</file>
</example>
@@ -131,7 +153,7 @@
template: `
<label ng-if="vm.field.type !== 'object'">{{vm.field.label}}</label>
<input
- ng-if="vm.field.type !== 'boolean' && vm.field.type !== 'object'"
+ ng-if="vm.field.type !== 'boolean' && vm.field.type !== 'object' && vm.field.type !== 'select'"
type="{{vm.field.type}}"
name="{{vm.name}}"
class="form-control"
@@ -139,6 +161,12 @@
ng-minlength="vm.field.validators.minlength || 0"
ng-maxlength="vm.field.validators.maxlength || 2000"
ng-required="vm.field.validators.required || false" />
+ <select class="form-control" ng-if ="vm.field.type === 'select'"
+ name = "{{vm.name}}"
+ ng-options="item.id as item.label for item in vm.field.options track by item.id"
+ ng-model="vm.ngModel"
+ ng-required="vm.field.validators.required || false">
+ </select>
<span class="boolean-field" ng-if="vm.field.type === 'boolean'">
<button
class="btn btn-success"
@@ -155,17 +183,28 @@
</span>
<div
class="panel panel-default object-field"
- ng-if="vm.field.type == 'object' && !vm.isEmptyObject(vm.ngModel)"
+ ng-if="vm.field.type == 'object' && (!vm.isEmptyObject(vm.ngModel) || !vm.isEmptyObject(vm.field.properties))"
>
<div class="panel-heading">{{vm.field.label}}</div>
<div class="panel-body">
- <div ng-repeat="(k, v) in vm.ngModel">
+ <div ng-if="!vm.field.properties" ng-repeat="(k, v) in vm.ngModel">
<xos-field
name="k"
field="{label: vm.formatLabel(k), type: vm.getType(v)}"
ng-model="v">
</xos-field>
</div>
+ <div ng-if="vm.field.properties" ng-repeat="(k, v) in vm.field.properties">
+ <xos-field
+ name="k"
+ field="{
+ label: v.label || vm.formatLabel(k),
+ type: v.type,
+ validators: v.validators
+ }"
+ ng-model="vm.ngModel[k]">
+ </xos-field>
+ </div>
</div>
</div>
`,
@@ -176,13 +215,16 @@
return RecursionHelper.compile(element);
},
controller: function($attrs, XosFormHelpers, LabelFormatter){
- // console.log('Field: ', this.name, this.field, this.ngModel);
+
if(!this.name){
throw new Error('[xosField] Please provide a field name');
}
if(!this.field){
throw new Error('[xosField] Please provide a field definition');
}
+ if(!this.field.type){
+ throw new Error('[xosField] Please provide a type in the field definition');
+ }
if(!$attrs.ngModel){
throw new Error('[xosField] Please provide an ng-model');
}
@@ -190,7 +232,7 @@
this.getType = XosFormHelpers._getFieldFormat;
this.formatLabel = LabelFormatter.format;
- this.isEmptyObject = o => Object.keys(o).length === 0;
+ this.isEmptyObject = o => o ? Object.keys(o).length === 0 : true;
}
}
});
diff --git a/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/form/form.component.js b/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/form/form.component.js
index 92843c2..2a9f00c 100644
--- a/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/form/form.component.js
+++ b/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/form/form.component.js
@@ -164,7 +164,7 @@
<ng-form name="vm.{{vm.config.formName || 'form'}}">
<div class="form-group" ng-repeat="(name, field) in vm.formField">
<xos-field name="name" field="field" ng-model="vm.ngModel[name]"></xos-field>
- <xos-validation errors="vm[vm.config.formName || 'form'][name].$error"></xos-validation>
+ <xos-validation field="vm[vm.config.formName || 'form'][name]" form="vm[vm.config.formName || 'form']"></xos-validation>
</div>
<div class="form-group" ng-if="vm.config.actions">
<button role="button" href=""
diff --git a/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/validation/validation.component.js b/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/validation/validation.component.js
index dbe4fbb..91610e1 100644
--- a/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/validation/validation.component.js
+++ b/views/ngXosLib/xosHelpers/src/ui_components/dumbComponents/validation/validation.component.js
@@ -29,41 +29,44 @@
</div>
<div class="col-xs-2">
<a class="btn"
- ng-click="vm.errors.required = !vm.errors.required"
- ng-class="{'btn-default': !vm.errors.required, 'btn-success': vm.errors.required}">
+ ng-click="vm.field.$error.required = !vm.field.$error.required"
+ ng-class="{'btn-default': !vm.field.$error.required, 'btn-success': vm.field.$error.required}">
Required
</a>
</div>
<div class="col-xs-2">
<a class="btn"
- ng-click="vm.errors.email = !vm.errors.email"
- ng-class="{'btn-default': !vm.errors.email, 'btn-success': vm.errors.email}">
+ ng-click="vm.field.$error.email = !vm.field.$error.email"
+ ng-class="{'btn-default': !vm.field.$error.email, 'btn-success': vm.field.$error.email}">
Email
</a>
</div>
<div class="col-xs-2">
<a class="btn"
- ng-click="vm.errors.minlength = !vm.errors.minlength"
- ng-class="{'btn-default': !vm.errors.minlength, 'btn-success': vm.errors.minlength}">
+ ng-click="vm.field.$error.minlength = !vm.field.$error.minlength"
+ ng-class="{'btn-default': !vm.field.$error.minlength, 'btn-success': vm.field.$error.minlength}">
Min Length
</a>
</div>
<div class="col-xs-2">
<a class="btn"
- ng-click="vm.errors.maxlength = !vm.errors.maxlength"
- ng-class="{'btn-default': !vm.errors.maxlength, 'btn-success': vm.errors.maxlength}">
+ ng-click="vm.field.$error.maxlength = !vm.field.$error.maxlength"
+ ng-class="{'btn-default': !vm.field.$error.maxlength, 'btn-success': vm.field.$error.maxlength}">
Max Length
</a>
</div>
</div>
- <xos-validation errors="vm.errors"></xos-validation>
+ <xos-validation field ="vm.field" form = "vm.form"></xos-validation>
</div>
</file>
<file name="script.js">
angular.module('sampleValidation', ['xos.uiComponents'])
.controller('SampleCtrl', function(){
- this.errors = {
- email: false
+ this.field = {
+ $error: {}
+ };
+ this.form= {
+ $submitted:true
}
});
</file>
@@ -82,16 +85,16 @@
<xos-alert config="vm.config" show="vm.field.$error.required !== undefined && vm.field.$error.required !== false && (vm.field.$touched || vm.form.$submitted)">
Field required
</xos-alert>
- <xos-alert config="vm.config" show="vm.field.$error.email !== undefined && vm.field.$error.email !== false && (vm.field.$touched || vm.form.$submitted)">
+ <xos-alert config="vm.config" show="vm.field.$error.email !== undefined && vm.field.$error.email !== false && (vm.field.$touched || vm.form.$submitted)">
This is not a valid email
</xos-alert>
- <xos-alert config="vm.config" show="vm.field.$error.minlength !== undefined && vm.field.$error.minlength !== false && (vm.field.$touched || vm.form.$submitted)">
+ <xos-alert config="vm.config" show="vm.field.$error.minlength !== undefined && vm.field.$error.minlength !== false && (vm.field.$touched || vm.form.$submitted)">
Too short
</xos-alert>
- <xos-alert config="vm.config" show="vm.field.$error.maxlength !== undefined && vm.field.$error.maxlength !== false && (vm.field.$touched || vm.form.$submitted)">
+ <xos-alert config="vm.config" show="vm.field.$error.maxlength !== undefined && vm.field.$error.maxlength !== false && (vm.field.$touched || vm.form.$submitted)">
Too long
</xos-alert>
- <xos-alert config="vm.config" show="vm.field.$error.custom !== undefined && vm.field.$error.custom !== false && (vm.field.$touched || vm.form.$submitted)">
+ <xos-alert config="vm.config" show="vm.field.$error.custom !== undefined && vm.field.$error.custom !== false && (vm.field.$touched || vm.form.$submitted)">
Field invalid
</xos-alert>
</div>
diff --git a/views/ngXosLib/xosHelpers/src/ui_components/smartComponents/smartPie/smartPie.component.js b/views/ngXosLib/xosHelpers/src/ui_components/smartComponents/smartPie/smartPie.component.js
index c1f08fa..f8a3985 100644
--- a/views/ngXosLib/xosHelpers/src/ui_components/smartComponents/smartPie/smartPie.component.js
+++ b/views/ngXosLib/xosHelpers/src/ui_components/smartComponents/smartPie/smartPie.component.js
@@ -198,13 +198,11 @@
const formatLabels = (data) => angular.isFunction(this.config.labelFormatter) ? this.config.labelFormatter(Object.keys(data)) : Object.keys(data);
const prepareData = (data) => {
- // $timeout(() => {
- // group data
- let grouped = groupData(data);
- this.data = formatData(grouped);
- // create labels
- this.labels = formatLabels(grouped);
- // }, 10);
+ // group data
+ let grouped = groupData(data);
+ this.data = formatData(grouped);
+ // create labels
+ this.labels = formatLabels(grouped);
}
if(this.config.resource){
diff --git a/views/ngXosLib/xosHelpers/src/ui_components/smartComponents/smartTable/smartTable.component.js b/views/ngXosLib/xosHelpers/src/ui_components/smartComponents/smartTable/smartTable.component.js
index b3a2eec..27b1ef6 100644
--- a/views/ngXosLib/xosHelpers/src/ui_components/smartComponents/smartTable/smartTable.component.js
+++ b/views/ngXosLib/xosHelpers/src/ui_components/smartComponents/smartTable/smartTable.component.js
@@ -225,7 +225,7 @@
let props = Object.keys(item);
_.remove(props, p => {
- return p == 'id' || p == 'validators'
+ return p === 'id' || p === 'validators'
});
// TODO move out cb, non sense triggering a lot of times
diff --git a/views/ngXosViews/synchronizerNotifier/src/js/main.js b/views/ngXosViews/synchronizerNotifier/src/js/main.js
index e761fad..f65c4d1 100644
--- a/views/ngXosViews/synchronizerNotifier/src/js/main.js
+++ b/views/ngXosViews/synchronizerNotifier/src/js/main.js
@@ -3,9 +3,13 @@
angular.module('xos.synchronizerNotifier', [
'ngResource',
'ngCookies',
- 'ui.router',
'xos.helpers'
])
+.run(function($rootScope){
+ $rootScope.$on('$locationChangeStart', function(event) {
+ event.preventDefault();
+ });
+})
.service('Diag', function($rootScope, $http, $q, $interval){
let isRunning = false;
diff --git a/xos/api/tenant/ceilometer/__init__.py b/xos/api/tenant/ceilometer/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/xos/api/tenant/ceilometer/__init__.py
@@ -0,0 +1 @@
+
diff --git a/xos/api/tenant/ceilometer/monitoringchannel.py b/xos/api/tenant/ceilometer/monitoringchannel.py
new file mode 100644
index 0000000..43e1636
--- /dev/null
+++ b/xos/api/tenant/ceilometer/monitoringchannel.py
@@ -0,0 +1,94 @@
+from rest_framework.decorators import api_view
+from rest_framework.response import Response
+from rest_framework.reverse import reverse
+from rest_framework import serializers
+from rest_framework import generics
+from rest_framework import status
+from core.models import *
+from django.forms import widgets
+from xos.apibase import XOSListCreateAPIView, XOSRetrieveUpdateDestroyAPIView, XOSPermissionDenied
+from api.xosapi_helpers import PlusModelSerializer, XOSViewSet, ReadOnlyField
+
+from services.ceilometer.models import MonitoringChannel, CeilometerService
+
+def get_default_ceilometer_service():
+ ceilometer_services = CeilometerService.get_service_objects().all()
+ if ceilometer_services:
+ return ceilometer_services[0].id
+ return None
+
+class MonitoringChannelForAPI(MonitoringChannel):
+ class Meta:
+ proxy = True
+ app_label = "ceilometer"
+
+ @property
+ def related(self):
+ related = {}
+ if self.creator:
+ related["creator"] = self.creator.username
+ if self.instance:
+ related["instance_id"] = self.instance.id
+ related["instance_name"] = self.instance.name
+ if self.instance.node:
+ related["compute_node_name"] = self.instance.node.name
+ return related
+
+class MonitoringChannelSerializer(PlusModelSerializer):
+ id = ReadOnlyField()
+ service_specific_attribute = ReadOnlyField()
+ ceilometer_url = ReadOnlyField()
+ tenant_list_str = ReadOnlyField()
+ #creator = ReadOnlyField()
+ #instance = ReadOnlyField()
+ provider_service = serializers.PrimaryKeyRelatedField(queryset=CeilometerService.get_service_objects().all(), default=get_default_ceilometer_service)
+
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ related = serializers.DictField(required=False)
+
+ #computeNodeName = serializers.SerializerMethodField("getComputeNodeName")
+
+ class Meta:
+ model = MonitoringChannelForAPI
+ fields = ('humanReadableName', 'id', 'provider_service', 'service_specific_attribute', 'ceilometer_url', 'tenant_list_str', 'related' )
+
+ def getHumanReadableName(self, obj):
+ return obj.__unicode__()
+
+ #def getComputeNodeName(self, obj):
+ # instance = obj.instance
+ # if not instance:
+ # return None
+ # return instance.node.name
+
+class MonitoringChannelSet(XOSViewSet):
+ base_name = "monitoringchannel"
+ method_name = "monitoringchannel"
+ method_kind = "viewset"
+ queryset = MonitoringChannelForAPI.get_tenant_objects().all()
+ serializer_class = MonitoringChannelSerializer
+
+ def get_queryset(self):
+ queryset = MonitoringChannelForAPI.get_tenant_objects().all()
+
+ current_user = self.request.user.username
+ if current_user is not None:
+ ids = [x.id for x in queryset if x.creator.username==current_user]
+ queryset = queryset.filter(id__in=ids)
+
+ return queryset
+
+ def create(self, request):
+ current_user = request.user.username
+ existing_obj = None
+ for obj in MonitoringChannelForAPI.get_tenant_objects().all():
+ if (obj.creator.username == current_user):
+ existing_obj = obj
+ break
+
+ if existing_obj:
+ serializer = MonitoringChannelSerializer(existing_obj)
+ headers = self.get_success_headers(serializer.data)
+ return Response( serializer.data, status=status.HTTP_200_OK )
+
+ return super(MonitoringChannelSet, self).create(request)
diff --git a/xos/api/utility/onboarding.py b/xos/api/utility/onboarding.py
new file mode 100644
index 0000000..dd66d6d
--- /dev/null
+++ b/xos/api/utility/onboarding.py
@@ -0,0 +1,97 @@
+import json
+from django.http import HttpResponse
+from rest_framework.decorators import api_view
+from rest_framework.response import Response
+from rest_framework.reverse import reverse
+from rest_framework import serializers
+from rest_framework import generics
+from rest_framework import status
+from core.models import *
+from xos.apibase import XOSListCreateAPIView, XOSRetrieveUpdateDestroyAPIView, XOSPermissionDenied
+from api.xosapi_helpers import PlusModelSerializer, XOSViewSet, ReadOnlyField
+
+class OnboardingViewSet(XOSViewSet):
+ base_name = "onboarding"
+ method_name = "onboarding"
+ method_kind = "viewset"
+
+ @classmethod
+ def get_urlpatterns(self, api_path="^"):
+ patterns = [] #super(CordSubscriberViewSet, self).get_urlpatterns(api_path=api_path)
+
+ patterns.append( self.list_url("xos/ready/$", {"get": "get_xos_ready"}, "xos_ready") )
+
+ patterns.append( self.list_url("summary/$", {"get": "get_summary"}, "summary") )
+
+ patterns.append( self.list_url("services/$", {"get": "get_service_list"}, "service_list") )
+ patterns.append( self.list_url("services/(?P<service>[a-zA-Z0-9\-_]+)/ready/$", {"get": "get_service_ready"}, "service_ready") )
+
+
+ return patterns
+
+ def is_ready(self, obj):
+ return (obj.enacted is not None) and (obj.updated is not None) and (obj.enacted>=obj.updated) and (obj.backend_status.startswith("1"))
+
+ def get_xos_ready(self, request):
+ xos = XOS.objects.all()
+ if not xos:
+ return Response(false)
+
+ xos=xos[0]
+
+ result = (xos.enacted is not None) and (xos.updated is not None) and (xos.enacted>=xos.updated) and (xos.backend_status.startswith("1"))
+ return HttpResponse( json.dumps(result), content_type="application/javascript" )
+
+ def get_summary(self, request):
+ result = []
+
+ xos = XOS.objects.all()
+ if not xos:
+ result.append( ("XOS", false) )
+ else:
+ xos=xos[0]
+
+ result.append( ("XOS", self.is_ready(xos)) )
+
+ for sc in xos.service_controllers.all():
+ result.append( (sc.name, self.is_ready(sc)) )
+
+ result = "\n".join( ["%s: %s" % (x[0], x[1]) for x in result] )
+ if result:
+ result = result + "\n"
+
+ return HttpResponse( result, content_type="text/ascii" )
+
+ def get_service_list(self, request):
+ xos = XOS.objects.all()
+ if not xos:
+ return Response([])
+
+ xos=xos[0]
+
+ result = []
+ for sc in xos.service_controllers.all():
+ result.append(sc.name)
+
+ return HttpResponse( json.dumps(result), content_type="application/javascript")
+
+ def get_service_ready(self, request, service):
+ xos = XOS.objects.all()
+ if not xos:
+ return Response([])
+
+ xos=xos[0]
+
+ sc=xos.service_controllers.filter(name=service)
+ if not sc:
+ return HttpResponse("Not Found", status_code=404)
+
+ sc=sc[0]
+ result = self.is_ready(sc)
+
+ return HttpResponse( json.dumps(result), content_type="application/javascript")
+
+
+
+
+
diff --git a/xos/configurations/acord/Makefile b/xos/configurations/acord/Makefile
index cfa04f8..e9b234c 100644
--- a/xos/configurations/acord/Makefile
+++ b/xos/configurations/acord/Makefile
@@ -13,9 +13,13 @@
sudo MYIP=$(MYIP) docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/common/cloudlab-openstack.yaml
sudo MYIP=$(MYIP) docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/nodes.yaml
-acord: cord
+acord: cord exampleservice
sudo MYIP=$(MYIP) docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/acord/ceilometer.yaml
+exampleservice:
+ #Ensure exampleservice is enabled in xos/tools/xos-manage and xos/settings.py file before uncommenting below lines
+ #sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/acord/acord-exampleservice.yaml
+
containers:
cd ../../../containers/xos; make devel
cd ../../../containers/synchronizer; make
diff --git a/xos/configurations/acord/acord-exampleservice.yaml b/xos/configurations/acord/acord-exampleservice.yaml
new file mode 100644
index 0000000..b6b23dd
--- /dev/null
+++ b/xos/configurations/acord/acord-exampleservice.yaml
@@ -0,0 +1,56 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Setup the ExampleService on the ACORD setup
+
+imports:
+ - custom_types/xos.yaml
+ - custom_types/exampleservice.yaml
+
+topology_template:
+ node_templates:
+
+ mysite:
+ type: tosca.nodes.Site
+
+ trusty-server-multi-nic:
+ type: tosca.nodes.Image
+
+ m1.small:
+ type: tosca.nodes.Flavor
+
+ mysite_exampleservice:
+ description: This slice holds the ExampleService
+ type: tosca.nodes.Slice
+ requirements:
+ - site:
+ node: mysite
+ relationship: tosca.relationships.MemberOfSite
+ - exmapleservice:
+ node: service_example
+ relationship: tosca.relationships.MemberOfService
+ - default_image:
+ node: trusty-server-multi-nic
+ relationship: tosca.relationships.DefaultImage
+ - m1.small:
+ node: m1.small
+ relationship: tosca.relationships.DefaultFlavor
+
+ service_example:
+ type: tosca.nodes.ExampleService
+ properties:
+ view_url: /admin/exampleservice/exampleservice/$id$/
+ kind: exampleservice
+ public_key: { get_artifact: [ SELF, pubkey, LOCAL_FILE] }
+ private_key_fn: /opt/xos/synchronizers/exampleservice/exampleservice_private_key
+ service_message: hello
+ artifacts:
+ pubkey: /opt/xos/synchronizers/exampleservice/exampleservice_public_key
+
+ exampletenant1:
+ type: tosca.nodes.ExampleTenant
+ properties:
+ tenant_message: world
+ requirements:
+ - tenant:
+ node: service_example
+ relationship: tosca.relationships.TenantOfService
diff --git a/xos/configurations/acord/ceilometer.yaml b/xos/configurations/acord/ceilometer.yaml
index 66d5d32..089837d 100644
--- a/xos/configurations/acord/ceilometer.yaml
+++ b/xos/configurations/acord/ceilometer.yaml
@@ -122,38 +122,38 @@
properties:
view_url: /admin/ceilometer/ceilometerservice/$id$/
kind: ceilometer
- ceilometer_pub_sub_url: http://10.11.10.1:4455/
+ ceilometer_pub_sub_url: http://130.127.133.58:4455/
public_key: { get_artifact: [ SELF, pubkey, LOCAL_FILE] }
artifacts:
pubkey: /opt/xos/synchronizers/monitoring_channel/monitoring_channel_public_key
-# service_sflow:
-# type: tosca.nodes.SFlowService
-# requirements:
-# properties:
-# view_url: /admin/ceilometer/sflowservice/$id$/
-# kind: sflow
-# sflow_port: 6343
-# sflow_api_port: 33333
+ service_sflow:
+ type: tosca.nodes.SFlowService
+ requirements:
+ properties:
+ view_url: /admin/ceilometer/sflowservice/$id$/
+ kind: sflow
+ sflow_port: 6343
+ sflow_api_port: 33333
Private:
type: tosca.nodes.NetworkTemplate
-
- ceilometer_network:
- type: tosca.nodes.network.Network.XOS
- properties:
- ip_version: 4
- labels: ceilometer_client_access
- requirements:
- - network_template:
- node: Private
- relationship: tosca.relationships.UsesNetworkTemplate
- - owner:
- node: mysite_ceilometer
- relationship: tosca.relationships.MemberOfSlice
- - connection:
- node: mysite_ceilometer
- relationship: tosca.relationships.ConnectsToSlice
+#
+# ceilometer_network:
+# type: tosca.nodes.network.Network.XOS
+# properties:
+# ip_version: 4
+# labels: ceilometer_client_access
+# requirements:
+# - network_template:
+# node: Private
+# relationship: tosca.relationships.UsesNetworkTemplate
+# - owner:
+# node: mysite_ceilometer
+# relationship: tosca.relationships.MemberOfSlice
+# - connection:
+# node: mysite_ceilometer
+# relationship: tosca.relationships.ConnectsToSlice
mysite:
type: tosca.nodes.Site
@@ -186,19 +186,24 @@
properties:
max_instances: 2
-# mysite_sflow:
-# description: Slice for sFlow service
-# type: tosca.nodes.Slice
-# requirements:
-# - sflow_service:
-# node: service_sflow
-# relationship: tosca.relationships.MemberOfService
-# - site:
-# node: mysite
-# relationship: tosca.relationships.MemberOfSite
-# properties:
-# default_flavor: m1.small
-# max_instances: 2
+ mysite_sflow:
+ description: Slice for sFlow service
+ type: tosca.nodes.Slice
+ requirements:
+ - sflow_service:
+ node: service_sflow
+ relationship: tosca.relationships.MemberOfService
+ - site:
+ node: mysite
+ relationship: tosca.relationships.MemberOfSite
+ - default_image:
+ node: trusty-server-multi-nic
+ relationship: tosca.relationships.DefaultImage
+ - m1.small:
+ node: m1.small
+ relationship: tosca.relationships.DefaultFlavor
+ properties:
+ max_instances: 2
my_ceilometer_tenant:
description: Ceilometer Service default Tenant
@@ -209,27 +214,27 @@
relationship: tosca.relationships.MemberOfService
# Virtual machines
-# sflow_service_instance:
-# type: tosca.nodes.Compute
-# capabilities:
-# # Host container properties
-# host:
-# properties:
-# num_cpus: 1
-# disk_size: 10 GB
-# mem_size: 4 MB
-# # Guest Operating System properties
-# os:
-# properties:
-# # host Operating System image properties
-# architecture: x86_64
-# type: linux
-# distribution: Ubuntu
-# version: 14.10
-# requirements:
-# - slice:
-# node: mysite_sflow
-# relationship: tosca.relationships.MemberOfSlice
+ sflow_service_instance:
+ type: tosca.nodes.Compute
+ capabilities:
+ # Host container properties
+ host:
+ properties:
+ num_cpus: 1
+ disk_size: 10 GB
+ mem_size: 4 MB
+ # Guest Operating System properties
+ os:
+ properties:
+ # host Operating System image properties
+ architecture: x86_64
+ type: linux
+ distribution: Ubuntu
+ version: 14.10
+ requirements:
+ - slice:
+ node: mysite_sflow
+ relationship: tosca.relationships.MemberOfSlice
Ceilometer:
type: tosca.nodes.DashboardView
diff --git a/xos/configurations/acord/ceilometer_pub_sub.tar.gz b/xos/configurations/acord/ceilometer_pub_sub.tar.gz
deleted file mode 100644
index eb88a2b..0000000
--- a/xos/configurations/acord/ceilometer_pub_sub.tar.gz
+++ /dev/null
Binary files differ
diff --git a/xos/configurations/acord/docker-compose.yml b/xos/configurations/acord/docker-compose.yml
index f40761a..da9562e 100644
--- a/xos/configurations/acord/docker-compose.yml
+++ b/xos/configurations/acord/docker-compose.yml
@@ -31,6 +31,19 @@
volumes:
- ../setup/id_rsa:/opt/xos/synchronizers/monitoring_channel/monitoring_channel_private_key:ro # private key
+#Ensure exampleservice is enabled in xos/tools/xos-manage and xos/settings.py file before uncommenting below lines
+#xos_synchronizer_exampleservice:
+# image: xosproject/xos-synchronizer-openstack
+# command: bash -c "sleep 120; python /opt/xos/synchronizers/exampleservice/exampleservice-synchronizer.py -C /opt/xos/synchronizers/exampleservice/exampleservice_config"
+# labels:
+# org.xosproject.kind: synchronizer
+# org.xosproject.target: exampleservice
+# links:
+# - xos_db
+# volumes:
+# - ../setup:/root/setup:ro
+# - ../common/xos_common_config:/opt/xos/xos_configuration/xos_common_config:ro
+# - ../setup/id_rsa:/opt/xos/synchronizers/exampleservice/exampleservice_private_key:ro
# FUTURE
#xos_swarm_synchronizer:
@@ -51,3 +64,4 @@
- ../common/xos_common_config:/opt/xos/xos_configuration/xos_common_config:ro
- ./xos_cord_config:/opt/xos/xos_configuration/xos_cord_config:ro
- ../setup/id_rsa.pub:/opt/xos/synchronizers/monitoring_channel/monitoring_channel_public_key:ro
+# - ../setup/id_rsa.pub:/opt/xos/synchronizers/exampleservice/exampleservice_public_key:ro
diff --git a/xos/configurations/acord/install_ceilometer_patch.sh b/xos/configurations/acord/install_ceilometer_patch.sh
deleted file mode 100755
index 77aa05b..0000000
--- a/xos/configurations/acord/install_ceilometer_patch.sh
+++ /dev/null
@@ -1,26 +0,0 @@
-if [ -d /usr/lib/python2.7/dist-packages/ceilometer/network/ext_services ]; then
- echo "Seems VCPE notification listeners are already enabled in ceilometer... so exiting gracefully..."
- exit 0
-fi
-echo "Verifying if all the required files are present"
-if [ ! -f openstack_ceilometer_patch.tar.gz ] || [ ! -f ceilometer_pub_sub.tar.gz ];
-then
- echo "File openstack_ceilometer_patch.tar.gz or ceilometer_pub_sub.tar.gz not found"
- exit 1
-fi
-echo "Copying the ceilometer patch files to /usr/lib/python2.7/dist-packages/ceilometer"
-tar -xzf openstack_ceilometer_patch.tar.gz
-sudo mv ceilometer/network/ext_services /usr/lib/python2.7/dist-packages/ceilometer/network/
-sudo mv ceilometer/network/statistics/onos /usr/lib/python2.7/dist-packages/ceilometer/network/statistics/
-sudo mv /usr/lib/python2.7/dist-packages/ceilometer/network/statistics/__init__.py /usr/lib/python2.7/dist-packages/ceilometer/network/statistics/orig_init.orig_py
-sudo mv ceilometer/network/statistics/__init__.py /usr/lib/python2.7/dist-packages/ceilometer/network/statistics/
-sudo mv ceilometer-2015.1.1.egg-info/entry_points.txt /usr/lib/python2.7/dist-packages/ceilometer-*egg-info/
-sudo mv pipeline.yaml /etc/ceilometer/
-echo "Restarting ceilometer-agent-notification"
-sudo service ceilometer-agent-notification restart
-echo "Restarting ceilometer-agent-central"
-sudo service ceilometer-agent-central restart
-tar -xzf ceilometer_pub_sub.tar.gz
-echo "Starting Ceilometer PUB/SUB service"
-cd ceilometer_pub_sub
-python sub_main.py &
diff --git a/xos/configurations/acord/openstack_ceilometer_patch.tar.gz b/xos/configurations/acord/openstack_ceilometer_patch.tar.gz
deleted file mode 100644
index 2c4f02c..0000000
--- a/xos/configurations/acord/openstack_ceilometer_patch.tar.gz
+++ /dev/null
Binary files differ
diff --git a/xos/configurations/common/fixtures.yaml b/xos/configurations/common/fixtures.yaml
index e28b03c..6b3234e 100644
--- a/xos/configurations/common/fixtures.yaml
+++ b/xos/configurations/common/fixtures.yaml
@@ -8,6 +8,10 @@
topology_template:
node_templates:
+ xos:
+ type: tosca.nodes.XOS
+
+
# -----------------------------------------------------------------------------
# Network Parameter Types
# -----------------------------------------------------------------------------
diff --git a/xos/configurations/common/mydeployment.yaml b/xos/configurations/common/mydeployment.yaml
index 66bb75d..c81fd93 100644
--- a/xos/configurations/common/mydeployment.yaml
+++ b/xos/configurations/common/mydeployment.yaml
@@ -16,9 +16,15 @@
m1.small:
type: tosca.nodes.Flavor
+ m1.xlarge:
+ type: tosca.nodes.Flavor
+
MyDeployment:
type: tosca.nodes.Deployment
requirements:
+ - m1.xlarge:
+ node: m1.large
+ relationship: tosca.relationships.SupportsFlavor
- m1.large:
node: m1.large
relationship: tosca.relationships.SupportsFlavor
diff --git a/xos/configurations/common/wait_for_onboarding_ready.sh b/xos/configurations/common/wait_for_onboarding_ready.sh
new file mode 100755
index 0000000..9606dbb
--- /dev/null
+++ b/xos/configurations/common/wait_for_onboarding_ready.sh
@@ -0,0 +1,27 @@
+#! /bin/bash
+
+display_usage() {
+ echo -e "\nUsage:\n$0 [xos-listen-port] [name] \n"
+}
+
+if [ $# -lt 2 ]
+then
+ display_usage
+ exit 1
+fi
+
+echo "Waiting for $2 to be onboarded"
+while [[ 1 ]]; do
+ STATUS=`curl 0.0.0.0:$1/api/utility/onboarding/$2/ready/ 2> /dev/null`
+ if [[ "$STATUS" == "true" ]]; then
+ echo "$2 is onboarded"
+ exit 0
+ fi
+ sleep 1
+# RUNNING_CONTAINER=`sudo docker ps|grep "xos"|awk '{print $$NF}'`
+# if [[ $RUNNING_CONTAINER == "" ]]; then
+# echo Container may have failed. check with \"make showlogs\'
+# exit 1
+# fi
+done
+
diff --git a/xos/configurations/common/wait_for_xos_file.sh b/xos/configurations/common/wait_for_xos_file.sh
new file mode 100755
index 0000000..1214dc4
--- /dev/null
+++ b/xos/configurations/common/wait_for_xos_file.sh
@@ -0,0 +1,24 @@
+#! /bin/bash
+
+display_usage() {
+ echo -e "\nUsage:\n$0 [fn] \n"
+}
+
+if [ $# -lt 1 ]
+then
+ display_usage
+ exit 1
+fi
+
+echo "Waiting for XOS to create file $1"
+
+until find $1 &> /dev/null
+do
+ sleep 1
+ RUNNING_CONTAINER=`sudo docker ps|grep "xos"|awk '{print $$NF}'`
+ if [[ $RUNNING_CONTAINER == "" ]]; then
+ echo Container may have failed. check with \"make showlogs\'
+ exit 1
+ fi
+done
+echo "XOS is ready"
diff --git a/xos/configurations/common/wait_for_xos_port.sh b/xos/configurations/common/wait_for_xos_port.sh
index 3ed5833..9c9e041 100755
--- a/xos/configurations/common/wait_for_xos_port.sh
+++ b/xos/configurations/common/wait_for_xos_port.sh
@@ -10,7 +10,7 @@
exit 1
fi
-echo "Waiting for XOS to come up"
+echo "Waiting for XOS to start listening on port $1"
until curl 0.0.0.0:$1 &> /dev/null
do
sleep 1
diff --git a/xos/configurations/cord-pod/Makefile b/xos/configurations/cord-pod/Makefile
index 8835964..1f8d3ae 100644
--- a/xos/configurations/cord-pod/Makefile
+++ b/xos/configurations/cord-pod/Makefile
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
.PHONY: xos
xos: up bootstrap
@@ -5,24 +6,50 @@
touch id_rsa id_rsa.pub
sudo docker-compose up -d
../common/wait_for_xos_port.sh 80
+=======
+CONFIG_DIR:=$(shell pwd)
+DOCKER_COMPOSE_YML=./onboarding-docker-compose/docker-compose.yml
+BOOTSTRAP_YML=./docker-compose-bootstrap.yml
+DOCKER_PROJECT=cordpod
+>>>>>>> master
-bootstrap: nodes.yaml images.yaml
- sudo docker-compose run xos python /opt/xos/tosca/run.py none /opt/xos/configurations/common/fixtures.yaml
- sudo docker-compose run xos python /opt/xos/tosca/run.py none /opt/xos/configurations/common/mydeployment.yaml
- sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/setup.yaml
- sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/nodes.yaml
- sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/images.yaml
+.PHONY: xos
+xos: prereqs bootstrap onboarding podconfig
+
+prereqs:
+ sudo make -f ../common/Makefile.prereqs
+
+bootstrap:
+ echo "[BOOTSTRAP]"
+ sudo rm -f onboarding-docker-compose/docker-compose.yml
+ sudo docker-compose -p $(DOCKER_PROJECT) -f docker-compose-bootstrap.yml up -d
+ bash ../common/wait_for_xos_port.sh 81
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(BOOTSTRAP_YML) run -e CONFIG_DIR=$(CONFIG_DIR) xos_bootstrap_ui python /opt/xos/tosca/run.py none /opt/xos/configurations/cord-pod/xos.yaml
+
+onboarding:
+ echo "[ONBOARDING]"
+ # on-board any services here
+ bash ../common/wait_for_onboarding_ready.sh 81 xos
+ bash ../common/wait_for_xos_port.sh 80
+
+podconfig: nodes.yaml images.yaml
+ echo "[PODCONFIG]"
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) run xos_ui python /opt/xos/tosca/run.py none /opt/xos/configurations/common/fixtures.yaml
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) run xos_ui python /opt/xos/tosca/run.py none /opt/xos/configurations/common/mydeployment.yaml
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) run xos_ui python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/setup.yaml
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) run xos_ui python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/nodes.yaml
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) run xos_ui python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/images.yaml
vtn: vtn-external.yaml
- sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/vtn-external.yaml
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) run xos_ui python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/vtn-external.yaml
fabric: fabric.yaml
- sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/fabric.yaml
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) run xos_ui python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/fabric.yaml
-cord: # vsg_custom_images
- sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/mgmt-net.yaml
- sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/cord-vtn-vsg.yaml
- sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/cord-volt-devices.yaml
+cord: vsg_custom_images
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) run xos_ui python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/mgmt-net.yaml
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) run xos_ui python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/cord-vtn-vsg.yaml
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) run xos_ui python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/cord-volt-devices.yaml
exampleservice:
sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/pod-exampleservice.yaml
@@ -49,10 +76,12 @@
export SETUPDIR=.; bash ./make-vtn-networkconfig-json.sh
stop:
- sudo MYIP=$(MYIP) docker-compose stop
+ test ! -s $(DOCKER_COMPOSE_YML) || sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) stop
+ sudo docker-compose -f $(BOOTSTRAP_YML) stop
rm:
- sudo MYIP=$(MYIP) docker-compose rm
+ test ! -s $(DOCKER_COMPOSE_YML) || sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) rm
+ sudo docker-compose -f $(BOOTSTRAP_YML) rm
showlogs:
sudo MYIP=$(MYIP) docker-compose logs
@@ -80,10 +109,14 @@
.PHONY: local_containers
local_containers:
echo "" > ../../../containers/xos/local_certs.crt
- for CRT in /usr/local/share/ca-certificates/* ; do \
+ for CRT in $$(ls /usr/local/share/ca-certificates/*) ; do \
echo Adding Certificate: $$CRT ;\
cat $$CRT >> ../../../containers/xos/local_certs.crt ;\
echo "" >> ../../../containers/xos/local_certs.crt ;\
done
cd ../../../containers/xos; make devel
cd ../../../containers/synchronizer; make
+<<<<<<< HEAD
+=======
+ cd ../../../containers/onboarding_synchronizer; make
+>>>>>>> master
diff --git a/xos/configurations/cord-pod/ceilometer.yaml b/xos/configurations/cord-pod/ceilometer.yaml
index d07f2e9..07b163e 100644
--- a/xos/configurations/cord-pod/ceilometer.yaml
+++ b/xos/configurations/cord-pod/ceilometer.yaml
@@ -124,6 +124,7 @@
kind: ceilometer
ceilometer_pub_sub_url: http://10.11.10.1:4455/
public_key: { get_artifact: [ SELF, pubkey, LOCAL_FILE] }
+ private_key_fn: /opt/xos/synchronizers/monitoring_channel/monitoring_channel_private_key
artifacts:
pubkey: /opt/xos/synchronizers/monitoring_channel/monitoring_channel_public_key
diff --git a/xos/configurations/cord-pod/docker-compose.yml b/xos/configurations/cord-pod/docker-compose-bootstrap.yml
similarity index 82%
rename from xos/configurations/cord-pod/docker-compose.yml
rename to xos/configurations/cord-pod/docker-compose-bootstrap.yml
index b9006b0..9f17c42 100644
--- a/xos/configurations/cord-pod/docker-compose.yml
+++ b/xos/configurations/cord-pod/docker-compose-bootstrap.yml
@@ -3,6 +3,24 @@
expose:
- "5432"
+xos_synchronizer_onboarding:
+ image: xosproject/xos-synchronizer-onboarding
+ command: bash -c "cd /opt/xos/synchronizers/onboarding; ./run.sh"
+ #command: sleep 86400
+ labels:
+ org.xosproject.kind: synchronizer
+ org.xosproject.target: onboarding
+ links:
+ - xos_db
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ - ./key_import:/opt/xos/key_import:ro
+ - ./onboarding-docker-compose:/opt/xos/synchronizers/onboarding/docker-compose
+ log_driver: "json-file"
+ log_opt:
+ max-size: "100k"
+ max-file: "5"
+
xos_synchronizer_openstack:
command: bash -c "sleep 120; python /opt/xos/synchronizers/openstack/xos-synchronizer.py"
image: xosproject/xos-synchronizer-openstack
@@ -116,21 +134,21 @@
max-size: "100k"
max-file: "5"
-xos:
- command: python /opt/xos/manage.py runserver 0.0.0.0:80 --insecure --makemigrations
+xos_bootstrap_ui:
+ command: python /opt/xos/manage.py runserver 0.0.0.0:81 --insecure --makemigrations
image: xosproject/xos
links:
- xos_db
ports:
- - "80:80"
+ - "81:81"
volumes:
- - .:/root/setup:ro
+# - .:/root/setup:ro
- ../common/xos_common_config:/opt/xos/xos_configuration/xos_common_config:ro
- ./xos_cord_config:/opt/xos/xos_configuration/xos_cord_config:ro
- ../vtn/files/xos_vtn_config:/opt/xos/xos_configuration/xos_vtn_config:ro
- - ./id_rsa.pub:/opt/xos/synchronizers/onos/onos_key.pub:ro
- - ./id_rsa.pub:/opt/xos/synchronizers/vcpe/vcpe_public_key:ro
- - ./id_rsa.pub:/opt/xos/synchronizers/monitoring_channel/monitoring_channel_public_key:ro
+# - ./id_rsa.pub:/opt/xos/synchronizers/onos/onos_key.pub:ro
+# - ./id_rsa.pub:/opt/xos/synchronizers/vcpe/vcpe_public_key:ro
+# - ./id_rsa.pub:/opt/xos/synchronizers/monitoring_channel/monitoring_channel_public_key:ro
log_driver: "json-file"
log_opt:
max-size: "100k"
diff --git a/xos/configurations/cord-pod/pod-cdn.yaml b/xos/configurations/cord-pod/pod-cdn.yaml
new file mode 100644
index 0000000..2229686
--- /dev/null
+++ b/xos/configurations/cord-pod/pod-cdn.yaml
@@ -0,0 +1,52 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Setup the CDN on the pod
+
+imports:
+ - custom_types/xos.yaml
+
+topology_template:
+ node_templates:
+
+ Private:
+ type: tosca.nodes.NetworkTemplate
+
+ management:
+ type: tosca.nodes.network.Network.XOS
+ properties:
+ no-create: true
+ no-delete: true
+ no-update: true
+
+ cdn-public:
+ type: tosca.nodes.network.Network
+ properties:
+ ip_version: 4
+ cidr: 207.141.192.128/28
+ requirements:
+ - network_template:
+ node: Private
+ relationship: tosca.relationships.UsesNetworkTemplate
+ - owner:
+ node: mysite_cdn
+ relationship: tosca.relationships.MemberOfSlice
+ - connection:
+ node: mysite_cdn
+ relationship: tosca.relationships.ConnectsToSlice
+
+ mysite:
+ type: tosca.nodes.Site
+
+ mysite_cdn:
+ description: This slice holds the CDN
+ type: tosca.nodes.Slice
+ properties:
+ network: noauto
+ requirements:
+ - site:
+ node: mysite
+ relationship: tosca.relationships.MemberOfSite
+ - management:
+ node: management
+ relationship: tosca.relationships.ConnectsToNetwork
+
diff --git a/xos/configurations/cord-pod/pod-exampleservice.yaml b/xos/configurations/cord-pod/pod-exampleservice.yaml
index 677e889..0182a59 100644
--- a/xos/configurations/cord-pod/pod-exampleservice.yaml
+++ b/xos/configurations/cord-pod/pod-exampleservice.yaml
@@ -79,10 +79,10 @@
view_url: /admin/exampleservice/exampleservice/$id$/
kind: exampleservice
public_key: { get_artifact: [ SELF, pubkey, LOCAL_FILE] }
- private_key_fn: /opt/xos/synchronizers/exampleservice/exampleservice_private_key
+ private_key_fn: /opt/xos/services/exampleservice/keys/exampleservice_rsa
service_message: hello
artifacts:
- pubkey: /opt/xos/synchronizers/exampleservice/exampleservice_public_key
+ pubkey: /opt/xos/services/exampleservice/keys/exampleservice_rsa.pub
tenant#exampletenant1:
type: tosca.nodes.ExampleTenant
diff --git a/xos/configurations/cord-pod/xos.yaml b/xos/configurations/cord-pod/xos.yaml
new file mode 100644
index 0000000..06b5eb5
--- /dev/null
+++ b/xos/configurations/cord-pod/xos.yaml
@@ -0,0 +1,85 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Onboard the exampleservice
+
+imports:
+ - custom_types/xos.yaml
+
+topology_template:
+ node_templates:
+ xos:
+ type: tosca.nodes.XOS
+ properties:
+ ui_port: 80
+ bootstrap_ui_port: 81
+ docker_project_name: cordpod
+
+ /opt/xos/xos_configuration/xos_common_config:
+ type: tosca.nodes.XOSVolume
+ properties:
+ host_path: { path_join: [ SELF, CONFIG_DIR, ../common/xos_common_config, ENV_VAR ] }
+ read_only: true
+ requirements:
+ - xos:
+ node: xos
+ relationship: tosca.relationships.UsedByXOS
+
+ /opt/xos/xos_configuration/xos_cord_config:
+ type: tosca.nodes.XOSVolume
+ properties:
+ host_path: { path_join: [ SELF, CONFIG_DIR, xos_cord_config, ENV_VAR ] }
+ read_only: true
+ requirements:
+ - xos:
+ node: xos
+ relationship: tosca.relationships.UsedByXOS
+
+ /opt/xos/xos_configuration/xos_vtn_config:
+ type: tosca.nodes.XOSVolume
+ properties:
+ host_path: { path_join: [ SELF, CONFIG_DIR, ../vtn/files/xos_vtn_config, ENV_VAR ] }
+ read_only: true
+ requirements:
+ - xos:
+ node: xos
+ relationship: tosca.relationships.UsedByXOS
+
+ /root/setup:
+ type: tosca.nodes.XOSVolume
+ properties:
+ host_path: { path_join: [ SELF, CONFIG_DIR, ., ENV_VAR ] }
+ read_only: true
+ requirements:
+ - xos:
+ node: xos
+ relationship: tosca.relationships.UsedByXOS
+
+ /opt/xos/synchronizers/onos/onos_key.pub:
+ type: tosca.nodes.XOSVolume
+ properties:
+ host_path: { path_join: [ SELF, CONFIG_DIR, id_rsa.pub, ENV_VAR ] }
+ read_only: true
+ requirements:
+ - xos:
+ node: xos
+ relationship: tosca.relationships.UsedByXOS
+
+ /opt/xos/synchronizers/vcpe/vcpe_public_key:
+ type: tosca.nodes.XOSVolume
+ properties:
+ host_path: { path_join: [ SELF, CONFIG_DIR, id_rsa.pub, ENV_VAR ] }
+ read_only: true
+ requirements:
+ - xos:
+ node: xos
+ relationship: tosca.relationships.UsedByXOS
+
+ /opt/xos/synchronizers/monitoring_channel/monitoring_channel_public_key:
+ type: tosca.nodes.XOSVolume
+ properties:
+ host_path: { path_join: [ SELF, CONFIG_DIR, id_rsa.pub, ENV_VAR ] }
+ read_only: true
+ requirements:
+ - xos:
+ node: xos
+ relationship: tosca.relationships.UsedByXOS
diff --git a/xos/configurations/devel/Makefile b/xos/configurations/devel/Makefile
index cc29f53..524e4cd 100644
--- a/xos/configurations/devel/Makefile
+++ b/xos/configurations/devel/Makefile
@@ -1,8 +1,8 @@
MYIP:=$(shell hostname -i)
-cloudlab: common_cloudlab containers xos
+cloudlab: common_cloudlab local_containers xos
-devstack: upgrade_pkgs common_devstack containers xos
+devstack: upgrade_pkgs common_devstack local_containers xos
xos:
sudo MYIP=$(MYIP) docker-compose up -d
@@ -12,16 +12,25 @@
sudo MYIP=$(MYIP) docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/common/cloudlab-openstack.yaml
sudo MYIP=$(MYIP) docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /root/setup/nodes.yaml
-containers:
- cd ../../../containers/xos; make devel
- cd ../../../containers/synchronizer; make
-
common_cloudlab:
make -C ../common -f Makefile.cloudlab
common_devstack:
make -C ../common -f Makefile.devstack
+base:
+ make -C ../../../containers/xos base
+
+local_containers:
+ echo "" > ../../../containers/xos/local_certs.crt
+ for CRT in $$(ls /usr/local/share/ca-certificates/*) ; do \
+ echo Adding Certificate: $$CRT ;\
+ cat $$CRT >> ../../../containers/xos/local_certs.crt ;\
+ echo "" >> ../../../containers/xos/local_certs.crt ;\
+ done
+ make -C ../../../containers/xos devel
+ make -C ../../../containers/synchronizer
+
stop:
sudo MYIP=$(MYIP) docker-compose stop
@@ -43,8 +52,3 @@
upgrade_pkgs:
sudo pip install httpie --upgrade
-rebuild_xos:
- make -C ../../../containers/xos devel
-
-rebuild_synchronizer:
- make -C ../../../containers/synchronizer
diff --git a/xos/configurations/frontend/Makefile b/xos/configurations/frontend/Makefile
index 562578e..46f39bf 100644
--- a/xos/configurations/frontend/Makefile
+++ b/xos/configurations/frontend/Makefile
@@ -1,24 +1,41 @@
MYIP:=$(shell hostname -i)
+CONFIG_DIR:=$(shell pwd)
+DOCKER_COMPOSE_YML=./onboarding-docker-compose/docker-compose.yml
+BOOTSTRAP_YML=./docker-compose-bootstrap.yml
+DOCKER_PROJECT=frontend
-frontend:
+frontend: prereqs bootstrap
+ bash ../common/wait_for_xos_port.sh 9999
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) run xos_ui python /opt/xos/tosca/run.py none /opt/xos/configurations/common/fixtures.yaml
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) run xos_ui python /opt/xos/tosca/run.py none /opt/xos/configurations/common/mydeployment.yaml
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) run xos_ui python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/frontend/sample.yaml
+
+prereqs:
sudo make -f ../common/Makefile.prereqs
- sudo docker-compose up -d
- bash ../common/wait_for_xos.sh
- sudo docker-compose run xos python /opt/xos/tosca/run.py none /opt/xos/configurations/common/fixtures.yaml
- sudo docker-compose run xos python /opt/xos/tosca/run.py none /opt/xos/configurations/common/mydeployment.yaml
- sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/frontend/sample.yaml
+
+bootstrap:
+ sudo rm -f onboarding-docker-compose/docker-compose.yml
+ sudo rm -f docker-compose.yml
+ sudo docker-compose -p $(DOCKER_PROJECT) -f docker-compose-bootstrap.yml up -d
+ bash ../common/wait_for_xos_port.sh 9998
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(BOOTSTRAP_YML) run -e CONFIG_DIR=$(CONFIG_DIR) xos_bootstrap_ui python /opt/xos/tosca/run.py none /opt/xos/configurations/frontend/xos.yaml
containers:
cd ../../../containers/xos; make devel
+ cd ../../../containers/synchronizer; make
+ cd ../../../containers/onboarding_synchronizer; make
+ #cd ../../../containers/xos; make devel
stop:
- sudo docker-compose stop
+ test ! -s $(DOCKER_COMPOSE_YML) || sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) stop
+ sudo docker-compose -f $(BOOTSTRAP_YML) stop
showlogs:
sudo docker-compose logs
rm: stop
- sudo docker-compose rm
+ test ! -s $(DOCKER_COMPOSE_YML) || sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) rm
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(BOOTSTRAP_YML) rm
ps:
sudo docker-compose ps
@@ -36,7 +53,7 @@
mock-cord-pod:
sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/common/fixtures.yaml
sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/cord-pod/mgmt-net.yaml
- sudo docker-compose run xos bash -c "echo somekey > /opt/xos/synchronizers/vcpe/vcpe_public_key; python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/cord-pod/cord-vtn-vsg.yaml"
+ #sudo docker-compose run xos bash -c "echo somekey > /opt/xos/synchronizers/vcpe/vcpe_public_key; python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/cord-pod/cord-vtn-vsg.yaml"
sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/cord-pod/cord-volt-devices.yaml
sudo docker exec frontend_xos_1 cp /opt/xos/configurations/cord-pod/xos_cord_config /opt/xos/xos_configuration/
sudo docker exec frontend_xos_1 touch /opt/xos/xos/settings.py
@@ -50,3 +67,11 @@
sudo docker-compose run xos python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/configurations/frontend/mocks/mcord.yaml
sudo docker exec frontend_xos_1 cp /opt/xos/configurations/mcord/xos_mcord_config /opt/xos/xos_configuration/
sudo docker exec frontend_xos_1 touch /opt/xos/xos/settings.py
+
+exampleservice:
+ mkdir -p key_import
+ # fake keys are fine
+ sudo bash -c "echo somekey > key_import/exampleservice_rsa"
+ sudo bash -c "echo somekey > key_import/exampleservice_rsa.pub"
+ sudo docker-compose -p $(DOCKER_PROJECT) -f $(DOCKER_COMPOSE_YML) run xos_ui python /opt/xos/tosca/run.py padmin@vicci.org /opt/xos/onboard/exampleservice/exampleservice-onboard.yaml
+ bash ../common/wait_for_onboarding_ready.sh 9998 xos
diff --git a/xos/configurations/frontend/docker-compose-bootstrap.yml b/xos/configurations/frontend/docker-compose-bootstrap.yml
new file mode 100644
index 0000000..3975893
--- /dev/null
+++ b/xos/configurations/frontend/docker-compose-bootstrap.yml
@@ -0,0 +1,34 @@
+xos_db:
+ image: xosproject/xos-postgres
+ expose:
+ - "5432"
+
+xos_bootstrap_ui:
+ image: xosproject/xos
+ command: python /opt/xos/manage.py runserver 0.0.0.0:9998 --insecure --makemigrations
+ ports:
+ - "9998:9998"
+ links:
+ - xos_db
+ volumes:
+ - ../common/xos_common_config:/opt/xos/xos_configuration/xos_common_config
+ - ../vtn/files/xos_vtn_config:/opt/xos/xos_configuration/xos_vtn_config:ro
+
+xos_synchronizer_onboarding:
+ image: xosproject/xos-synchronizer-onboarding
+ command: bash -c "cd /opt/xos/synchronizers/onboarding; ./run.sh"
+ #command: sleep 86400
+ labels:
+ org.xosproject.kind: synchronizer
+ org.xosproject.target: onboarding
+ links:
+ - xos_db
+ volumes:
+# - .:/root/setup:ro
+ - /var/run/docker.sock:/var/run/docker.sock
+ - ./key_import:/opt/xos/key_import:ro
+ - ./onboarding-docker-compose:/opt/xos/synchronizers/onboarding/docker-compose
+ log_driver: "json-file"
+ log_opt:
+ max-size: "100k"
+ max-file: "5"
diff --git a/xos/configurations/frontend/docker-compose.yml b/xos/configurations/frontend/docker-compose.yml
deleted file mode 100644
index 835eb83..0000000
--- a/xos/configurations/frontend/docker-compose.yml
+++ /dev/null
@@ -1,34 +0,0 @@
-xos_db:
- image: xosproject/xos-postgres
- expose:
- - "5432"
-
-# FUTURE
-#xos_swarm_synchronizer:
-# image: xosproject/xos-swarm-synchronizer
-# labels:
-# org.xosproject.kind: synchronizer
-# org.xosproject.target: swarm
-
-xos:
- image: xosproject/xos
- command: python /opt/xos/manage.py runserver 0.0.0.0:8000 --insecure --makemigrations
- #command: sleep 86400 # For interactive session
- ports:
- - "9999:8000"
- links:
- - xos_db
- volumes:
- - ../common/xos_common_config:/opt/xos/xos_configuration/xos_common_config
- - ../../tosca:/opt/xos/tosca
- - ../../core/xoslib:/opt/xos/core/xoslib
- - ../../core/static:/opt/xos/core/static
- - ../../core/dashboard:/opt/xos/core/dashboard
- - ../../core/templatetags:/opt/xos/core/templatetags
- - ../../core/views:/opt/xos/core/views
- - ../../templates:/opt/xos/templates
- - ../../configurations:/opt/xos/configurations
- - ../../xos:/opt/xos/xos
- - ../../api:/opt/xos/api
- - ../../services:/opt/xos/services
-
diff --git a/xos/configurations/frontend/xos.yaml b/xos/configurations/frontend/xos.yaml
new file mode 100644
index 0000000..8b286cd
--- /dev/null
+++ b/xos/configurations/frontend/xos.yaml
@@ -0,0 +1,35 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Onboard the exampleservice
+
+imports:
+ - custom_types/xos.yaml
+
+topology_template:
+ node_templates:
+ xos:
+ type: tosca.nodes.XOS
+ properties:
+ ui_port: 9999
+ bootstrap_ui_port: 9998
+ docker_project_name: frontend
+
+ /opt/xos/xos_configuration/xos_common_config:
+ type: tosca.nodes.XOSVolume
+ properties:
+ host_path: { path_join: [ SELF, CONFIG_DIR, ../common/xos_common_config, ENV_VAR ] }
+ read_only: false
+ requirements:
+ - xos:
+ node: xos
+ relationship: tosca.relationships.UsedByXOS
+
+ /opt/xos/xos_configuration/xos_vtn_config:
+ type: tosca.nodes.XOSVolume
+ properties:
+ host_path: { path_join: [ SELF, CONFIG_DIR, ../vtn/files/xos_vtn_config, ENV_VAR ] }
+ read_only: true
+ requirements:
+ - xos:
+ node: xos
+ relationship: tosca.relationships.UsedByXOS
diff --git a/xos/configurations/mcord/Makefile b/xos/configurations/mcord/Makefile
index 7f4d9a5..53fec7b 100644
--- a/xos/configurations/mcord/Makefile
+++ b/xos/configurations/mcord/Makefile
@@ -31,6 +31,9 @@
enter-vbbu:
sudo docker exec -it mcord_xos_synchronizer_vbbu_1 bash
+enter-vpgwc:
+ sudo docker exec -it mcord_xos_synchronizer_vpgwc_1 bash
+
upgrade_pkgs:
sudo pip install httpie --upgrade
diff --git a/xos/configurations/mcord/docker-compose.yml b/xos/configurations/mcord/docker-compose.yml
index 8598396..367b168 100644
--- a/xos/configurations/mcord/docker-compose.yml
+++ b/xos/configurations/mcord/docker-compose.yml
@@ -40,6 +40,25 @@
- "compute9:10.102.81.9"
- "compute10:10.102.81.10"
+xos_synchronizer_vpgwc:
+ image: xosproject/xos-synchronizer-openstack
+ command: bash -c "sleep 120; python /opt/xos/synchronizers/vpgwc/vpgwc-synchronizer.py -C /opt/xos/synchronizers/vpgwc/vpgwc_config"
+ labels:
+ org.xosproject.kind: synchronizer
+ org.xosproject.target: vpgwc
+ links:
+ - xos_db
+ volumes:
+ - ../setup/id_rsa_mcord:/opt/xos/configurations/mcord/mcord_private_key:ro # private key
+ - ../setup/id_rsa_mcord.pub:/opt/xos/configurations/mcord/mcord_public_key:ro # public key
+ - ../setup:/root/setup:ro
+ extra_hosts:
+ - "controller:10.102.81.3"
+ - "computeBBU1:10.102.81.6"
+ - "computeBBU2:10.102.81.7"
+ - "compute9:10.102.81.9"
+ - "compute10:10.102.81.10"
+
# FUTURE
#xos_swarm_synchronizer:
# image: xosproject/xos-swarm-synchronizer
diff --git a/xos/configurations/mcord/mcord.yaml b/xos/configurations/mcord/mcord.yaml
index 42241f0..450bd23 100644
--- a/xos/configurations/mcord/mcord.yaml
+++ b/xos/configurations/mcord/mcord.yaml
@@ -92,6 +92,25 @@
default: New vBBU Component
description: Just a message
+ tosca.nodes.VPGWCComponent:
+ derived_from: tosca.nodes.Root
+ description: >
+ CORD: vPGWC Component of MCORD Service.
+ properties:
+ kind:
+ type: string
+ default: VPGWC_KIND
+ description: Kind of component
+ s5s8_pgw_tag:
+ type: string
+ required: false
+ default: 300
+ description: VTN stag port-name
+ display_message:
+ type: string
+ required: false
+ default: New vPGWc Component
+ description: Just a message
topology_template:
node_templates:
@@ -107,6 +126,18 @@
artifacts:
pubkey: /opt/xos/configurations/mcord/mcord_public_key
+ vPGWC:
+ type: tosca.nodes.MCORDService
+ requirements:
+ properties:
+ kind: vEPC
+ icon_url: /static/mCordServices/service_server.png
+ view_url: /admin/mcord/vpgwccomponent
+ public_key: { get_artifact: [ SELF, pubkey, LOCAL_FILE] }
+ private_key_fn: /opt/xos/configurations/mcord/mcord_private_key
+ artifacts:
+ pubkey: /opt/xos/configurations/mcord/mcord_public_key
+
m1.xlarge:
type: tosca.nodes.Flavor
@@ -189,12 +220,35 @@
node: mysite_vbbu_slice1
relationship: tosca.relationships.ConnectsToSlice
+ lan_3gpp_s5s8_pgw_network:
+ type: tosca.nodes.network.Network.XOS
+ properties:
+ ip_version: 4
+ labels: lan_3gpp_s5s8_pgw_net
+ cidr: 172.17.1.0/24
+ start_ip: 172.17.1.2
+ end_ip: 172.17.1.8
+ gateway_ip: 172.17.1.1
+ requirements:
+ - network_template:
+ node: External
+ relationship: tosca.relationships.UsesNetworkTemplate
+ - owner:
+ node: mysite_mobile_net
+ relationship: tosca.relationships.MemberOfSlice
+ - connection:
+ node: mysite_vpgwc_slice1
+ relationship: tosca.relationships.ConnectsToSlice
+
mysite:
type: tosca.nodes.Site
mcord-bbu-multi-nic:
type: tosca.nodes.Image
+ mcord-vpgwc-onos-multi-nic:
+ type: tosca.nodes.Image
+
mysite_management:
description: This slice exists solely to own the management network
type: tosca.nodes.Slice
@@ -238,4 +292,41 @@
network: noauto
# default_flavor: m1.xlarge
default_node: computeBBU2
-
+
+ mysite_vpgwc_slice1:
+ description: vPGWC Service Slice 1
+ type: tosca.nodes.Slice
+ requirements:
+ - vPGWC:
+ node: vPGWC
+ relationship: tosca.relationships.MemberOfService
+ - site:
+ node: mysite
+ relationship: tosca.relationships.MemberOfSite
+ - default_image:
+ node: mcord-vpgwc-onos-multi-nic
+ relationship: tosca.relationships.DefaultImage
+ - default_flavor:
+ node: m1.xlarge
+ relationship: tosca.relationships.DefaultFlavor
+ - management:
+ node: management
+ relationship: tosca.relationships.ConnectsToNetwork
+ properties:
+ network: noauto
+ default_node: compute10
+
+ mysite_VPGWC_Component:
+ description: MCORD Service default Component
+ type: tosca.nodes.VPGWCComponent
+ requirements:
+ - provider_service:
+ node: vPGWC
+ relationship: tosca.relationships.MemberOfService
+ - vpgwc_slice:
+ node: mysite_vpgwc_slice1
+ relationship: tosca.relationships.MemberOfSlice
+ properties:
+ display_message: vPGWC looks good!
+ s5s8_pgw_tag: 300
+
diff --git a/xos/configurations/mcord/migrations/0001_initial.py b/xos/configurations/mcord/migrations/0001_initial.py
index c53e548..a11fe30 100644
--- a/xos/configurations/mcord/migrations/0001_initial.py
+++ b/xos/configurations/mcord/migrations/0001_initial.py
@@ -31,4 +31,14 @@
},
bases=('core.tenantwithcontainer',),
),
+ migrations.CreateModel(
+ name='VPGWCComponent',
+ fields=[
+ ],
+ options={
+ 'verbose_name': 'VPGWC MCORD Service Component',
+ 'proxy': True,
+ },
+ bases=('core.tenantwithcontainer',),
+ ),
]
diff --git a/xos/configurations/mcord/nodes.yaml b/xos/configurations/mcord/nodes.yaml
index ae22112..48e7247 100644
--- a/xos/configurations/mcord/nodes.yaml
+++ b/xos/configurations/mcord/nodes.yaml
@@ -22,3 +22,23 @@
node: MyDeployment
relationship: tosca.relationships.MemberOfDeployment
+ nova-compute:
+ type: tosca.nodes.Node
+ requirements:
+ - site:
+ node: mysite
+ relationship: tosca.relationships.MemberOfSite
+ - deployment:
+ node: MyDeployment
+ relationship: tosca.relationships.MemberOfDeployment
+
+ compute10:
+ type: tosca.nodes.Node
+ requirements:
+ - site:
+ node: mysite
+ relationship: tosca.relationships.MemberOfSite
+ - deployment:
+ node: MyDeployment
+ relationship: tosca.relationships.MemberOfDeployment
+
diff --git a/xos/configurations/mcord/setup.yaml b/xos/configurations/mcord/setup.yaml
index 0dd2769..9db865e 100644
--- a/xos/configurations/mcord/setup.yaml
+++ b/xos/configurations/mcord/setup.yaml
@@ -166,12 +166,6 @@
icon_url: /static/mCordServices/service_server.png
kind: vEPC
- vPGW:
- type: tosca.nodes.Service
- properties:
- view_url: /mcord/?service=vPGW
- icon_url: /static/mCordServices/service_server.png
- kind: vEPC
# EDGE
Cache:
@@ -223,10 +217,10 @@
icon_url: /static/mCordServices/service_server.png
kind: vEPC
- vPGW:
+ vPGWC:
type: tosca.nodes.Service
properties:
- view_url: /mcord/?service=vPGW
+ view_url: /mcord/?service=vPGWC
icon_url: /static/mCordServices/service_server.png
kind: vEPC
diff --git a/xos/configurations/mcord/vpgwc.yaml b/xos/configurations/mcord/vpgwc.yaml
new file mode 100644
index 0000000..d003bb2
--- /dev/null
+++ b/xos/configurations/mcord/vpgwc.yaml
@@ -0,0 +1,203 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Setup MCORD-related services.
+
+imports:
+ - custom_types/xos.yaml
+
+node_types:
+
+ tosca.nodes.MCORDService:
+ derived_from: tosca.nodes.Root
+ description: >
+ An XOS Service object. Services may be listed in the Service
+ directory and may be linked together via Tenancy Relationships.
+ capabilities:
+ scalable:
+ type: tosca.capabilities.Scalable
+ service:
+ type: tosca.capabilities.xos.Service
+ properties:
+ no-delete:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to delete this object
+ no-create:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to create this object
+ no-update:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to update this object
+ kind:
+ type: string
+ default: VPGWC_KIND
+ description: Type of service.
+ view_url:
+ type: string
+ required: false
+ description: URL to follow when icon is clicked in the Service Directory.
+ icon_url:
+ type: string
+ required: false
+ description: ICON to display in the Service Directory.
+ enabled:
+ type: boolean
+ default: true
+ published:
+ type: boolean
+ default: true
+ description: If True then display this Service in the Service Directory.
+ public_key:
+ type: string
+ required: false
+ description: Public key to install into Instances to allows Services to SSH into them.
+ private_key_fn:
+ type: string
+ required: false
+ description: Location of private key file
+ versionNumber:
+ type: string
+ required: false
+ description: Version number of Service.
+
+ tosca.nodes.VPGWCComponent:
+ derived_from: tosca.nodes.Root
+ description: >
+ CORD: vPGWC Component of MCORD Service.
+ properties:
+ kind:
+ type: string
+ default: VPGWC_KIND
+ description: Kind of component
+ s5s8_pgw_tag:
+ type: string
+ required: false
+ default: 300
+ description: VTN stag port-name
+ display_message:
+ type: string
+ required: false
+ default: New vPGWC Component
+ description: Just a message
+
+
+topology_template:
+ node_templates:
+ vPGWC:
+ type: tosca.nodes.MCORDService
+ requirements:
+ properties:
+ kind: VPGWC_KIND
+ icon_url: /static/mCordServices/service_server.png
+ view_url: /admin/mcord/vpgwccomponent
+ public_key: { get_artifact: [ SELF, pubkey, LOCAL_FILE] }
+ private_key_fn: /opt/xos/configurations/mcord/mcord_private_key
+ artifacts:
+ pubkey: /opt/xos/configurations/mcord/mcord_public_key
+
+ m1.xlarge:
+ type: tosca.nodes.Flavor
+
+ Private:
+ type: tosca.nodes.NetworkTemplate
+
+ External:
+ type: tosca.nodes.NetworkTemplate
+
+ management_template:
+ type: tosca.nodes.NetworkTemplate
+ properties:
+ visibility: private
+ translation: none
+
+ management:
+ type: tosca.nodes.network.Network.XOS
+# properties:
+# no-create: true
+# no-delete: true
+# no-update: true
+
+ lan_3gpp_s5s8_pgw_network:
+ type: tosca.nodes.network.Network.XOS
+ properties:
+ ip_version: 4
+ labels: lan_3gpp_s5s8_pgw_net
+ cidr: 172.17.1.0/24
+ start_ip: 172.17.1.2
+ end_ip: 172.17.1.8
+ gateway_ip: 172.17.1.1
+ requirements:
+ - network_template:
+ node: External
+ relationship: tosca.relationships.UsesNetworkTemplate
+ - owner:
+ node: mysite_mobile_net
+ relationship: tosca.relationships.MemberOfSlice
+ - connection:
+ node: mysite_vpgwc_slice1
+ relationship: tosca.relationships.ConnectsToSlice
+
+ mysite:
+ type: tosca.nodes.Site
+
+ mcord-vpgwc-onos-multi-nic:
+ type: tosca.nodes.Image
+
+ mysite_management:
+ description: This slice exists solely to own the management network
+ type: tosca.nodes.Slice
+ properties:
+ network: noauto
+ requirements:
+ - site:
+ node: mysite
+ relationship: tosca.relationships.MemberOfSite
+
+ mysite_mobile_net:
+ description: This slice exists solely to own the mobile network
+ type: tosca.nodes.Slice
+ properties:
+ network: noauto
+ requirements:
+ - site:
+ node: mysite
+ relationship: tosca.relationships.MemberOfSite
+
+ mysite_vpgwc_slice1:
+ description: vPGWC Service Slice 1
+ type: tosca.nodes.Slice
+ requirements:
+ - vPGWC:
+ node: vPGWC
+ relationship: tosca.relationships.MemberOfService
+ - site:
+ node: mysite
+ relationship: tosca.relationships.MemberOfSite
+ - default_image:
+ node: mcord-vpgwc-onos-multi-nic
+ relationship: tosca.relationships.DefaultImage
+ - default_flavor:
+ node: m1.xlarge
+ relationship: tosca.relationships.DefaultFlavor
+ - management:
+ node: management
+ relationship: tosca.relationships.ConnectsToNetwork
+ properties:
+ network: noauto
+ default_node: compute10
+
+ mysite_VPGWC_Component:
+ description: MCORD Service default Component
+ type: tosca.nodes.VPGWCComponent
+ requirements:
+ - provider_service:
+ node: vPGWC
+ relationship: tosca.relationships.MemberOfService
+ - vpgwc_slice:
+ node: mysite_vpgwc_slice1
+ relationship: tosca.relationships.MemberOfSlice
+ properties:
+ display_message: vPGWC looks good!
+ s5s8_pgw_tag: 300
diff --git a/xos/configurations/test-standalone/docker-compose.yml b/xos/configurations/test-standalone/docker-compose.yml
index 5039f08..a0b87ed 100644
--- a/xos/configurations/test-standalone/docker-compose.yml
+++ b/xos/configurations/test-standalone/docker-compose.yml
@@ -11,7 +11,7 @@
# org.xosproject.target: swarm
xos:
- image: xosproject/xos
+ image: xosproject/xos-test
command: python /opt/xos/manage.py runserver 0.0.0.0:8000 --insecure --makemigrations
#command: sleep 86400 # For interactive session
ports:
diff --git a/xos/core/admin.py b/xos/core/admin.py
index 200bb5c..c5e36be 100644
--- a/xos/core/admin.py
+++ b/xos/core/admin.py
@@ -1034,7 +1034,7 @@
list_display = ("backend_status_icon", "name", "kind",
"versionNumber", "enabled", "published")
list_display_links = ('backend_status_icon', 'name', )
- fieldList = ["backend_status_text", "name", "kind", "description", "versionNumber", "enabled", "published",
+ fieldList = ["backend_status_text", "name", "kind", "description", "controller", "versionNumber", "enabled", "published",
"view_url", "icon_url", "public_key", "private_key_fn", "service_specific_attribute", "service_specific_id"]
fieldsets = [
(None, {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
@@ -1051,6 +1051,27 @@
('serviceprivileges', 'Privileges')
)
+class ServiceControllerResourceInline(XOSTabularInline):
+ model = ServiceControllerResource
+ fields = ['name', 'kind', 'format', 'url']
+ extra = 0
+ suit_classes = 'suit-tab suit-tab-resources'
+
+class ServiceControllerAdmin(XOSBaseAdmin):
+ list_display = ("backend_status_icon", "name",)
+ list_display_links = ('backend_status_icon', 'name',)
+ fieldList = ["backend_status_text", "name", "xos", "base_url"]
+ fieldsets = [
+ (None, {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
+ inlines = [ServiceControllerResourceInline]
+ readonly_fields = ('backend_status_text', )
+
+ user_readonly_fields = fieldList
+
+ suit_form_tabs = (('general', 'Service Details'),
+ ('resources', 'Resources'),
+ )
+
class SiteNodeInline(XOSTabularInline):
model = Node
@@ -2416,6 +2437,7 @@
admin.site.register(Site, SiteAdmin)
admin.site.register(Slice, SliceAdmin)
admin.site.register(Service, ServiceAdmin)
+admin.site.register(ServiceController, ServiceControllerAdmin)
#admin.site.register(Reservation, ReservationAdmin)
admin.site.register(Network, NetworkAdmin)
admin.site.register(Port, PortAdmin)
diff --git a/xos/core/models/__init__.py b/xos/core/models/__init__.py
index 5b0ad4b..41e6b3b 100644
--- a/xos/core/models/__init__.py
+++ b/xos/core/models/__init__.py
@@ -1,8 +1,10 @@
from .plcorebase import PlCoreBase,PlCoreBaseManager,PlCoreBaseDeletionManager,PlModelMixIn
from .project import Project
from .singletonmodel import SingletonModel
+from .xosmodel import XOS, XOSVolume
from .service import Service, Tenant, TenantWithContainer, CoarseTenant, ServicePrivilege, TenantRoot, TenantRootPrivilege, TenantRootRole, TenantPrivilege, TenantRole, Subscriber, Provider
from .service import ServiceAttribute, TenantAttribute, ServiceRole
+from .service import ServiceController, ServiceControllerResource
from .tag import Tag
from .role import Role
from .site import Site, Deployment, DeploymentRole, DeploymentPrivilege, Controller, ControllerRole, ControllerSite, SiteDeployment,Diag
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
index b341e83..c871c7e 100644
--- a/xos/core/models/service.py
+++ b/xos/core/models/service.py
@@ -1,13 +1,22 @@
import json
from operator import attrgetter
-from core.models import PlCoreBase, PlCoreBaseManager, SingletonModel
+from core.models import PlCoreBase, PlCoreBaseManager, SingletonModel, XOS
from core.models.plcorebase import StrippedCharField
from django.db import models
from xos.exceptions import *
+import urlparse
COARSE_KIND = "coarse"
+def get_xos():
+ xos = XOS.objects.all()
+
+ if xos:
+ return xos[0]
+ else:
+ return None
+
class AttributeMixin(object):
# helper for extracting things from a json-encoded
@@ -56,6 +65,55 @@
None,
attrname))
+class ServiceController(PlCoreBase):
+ xos = models.ForeignKey(XOS, related_name='service_controllers', help_text="Pointer to XOS", default=get_xos)
+ name = StrippedCharField(max_length=30, help_text="Service Name")
+ base_url = StrippedCharField(max_length=1024, help_text="Base URL, allows use of relative URLs for resources", null=True, blank=True)
+
+ def __unicode__(self): return u'%s' % (self.name)
+
+ def save(self, *args, **kwargs):
+ super(ServiceController, self).save(*args, **kwargs)
+
+ if self.xos:
+ # force XOS to rebuild
+ # XXX somewhat hackish XXX
+ self.xos.save(update_fields=["updated"])
+
+class ServiceControllerResource(PlCoreBase):
+ KIND_CHOICES = (('models', 'Models'),
+ ('admin', 'Admin'),
+ ('django_library', 'Django Library'),
+ ('synchronizer', 'Synchronizer'),
+ ('rest_service', 'REST API (service)'),
+ ('rest_tenant', 'REST API (tenant)'),
+ ('tosca_custom_types', 'Tosca Custom Types'),
+ ('tosca_resource', 'Tosca Resource'),
+ ('private_key', 'Private Key'),
+ ('public_key', 'Public Key'))
+
+ FORMAT_CHOICES = (('python', 'Python'),
+ ('manifest', 'Manifest'),
+ ('docker', 'Docker Container'),
+ ('yaml', 'YAML'),
+ ('raw', 'raw'))
+
+ service_controller = models.ForeignKey(ServiceController, related_name='service_controller_resources',
+ help_text="The Service Controller this resource is associated with")
+
+ name = StrippedCharField(max_length=30, help_text="Object Name")
+ kind = StrippedCharField(choices=KIND_CHOICES, max_length=30)
+ format = StrippedCharField(choices=FORMAT_CHOICES, max_length=30)
+ url = StrippedCharField(max_length=1024, help_text="URL of resource", null=True, blank=True)
+
+ def __unicode__(self): return u'%s' % (self.name)
+
+ @property
+ def full_url(self):
+ if self.service_controller and self.service_controller.base_url:
+ return urlparse.urljoin(self.service_controller.base_url, self.url)
+ else:
+ return self.url
class Service(PlCoreBase, AttributeMixin):
# when subclassing a service, redefine KIND to describe the new service
@@ -81,6 +139,10 @@
max_length=30, blank=True, null=True)
service_specific_attribute = models.TextField(blank=True, null=True)
+ controller = models.ForeignKey(ServiceController, related_name='services',
+ help_text="The Service Controller this Service uses",
+ null=True, blank=True)
+
def __init__(self, *args, **kwargs):
# for subclasses, set the default kind appropriately
self._meta.get_field("kind").default = self.KIND
@@ -548,7 +610,7 @@
if self.slice.default_image:
return self.slice.default_image
- raise XOPSProgrammingError("Please set a default image for %s" % self.slice.name)
+ raise XOSProgrammingError("Please set a default image for %s" % self.slice.name)
def make_new_instance(self):
from core.models import Instance, Flavor
@@ -683,7 +745,7 @@
if slice.default_image:
return slice.default_image
- raise XOPSProgrammingError("Please set a default image for %s" % self.slice.name)
+ raise XOSProgrammingError("Please set a default image for %s" % self.slice.name)
def save_instance(self, instance):
# Override this function to do custom pre-save or post-save processing,
diff --git a/xos/core/models/xosmodel.py b/xos/core/models/xosmodel.py
new file mode 100644
index 0000000..4942241
--- /dev/null
+++ b/xos/core/models/xosmodel.py
@@ -0,0 +1,44 @@
+import os
+from django.db import models
+from core.models import PlCoreBase
+from core.models.plcorebase import StrippedCharField
+
+# XOS: Serves as the root of the build system
+
+
+
+class XOS(PlCoreBase):
+ name = StrippedCharField(max_length=200, unique=True, help_text="Name of XOS", default="XOS")
+ ui_port = models.IntegerField(help_text="Port for XOS UI", default=80)
+ bootstrap_ui_port = models.IntegerField(help_text="Port for XOS UI", default=81)
+ db_container_name = StrippedCharField(max_length=200, help_text="name of XOS db container", default="xos_db")
+ docker_project_name = StrippedCharField(max_length=200, help_text="docker project name")
+ enable_build = models.BooleanField(help_text="True if Onboarding Synchronizer should build XOS as necessary", default=True)
+
+ def __unicode__(self): return u'%s' % (self.name)
+
+ def __init__(self, *args, **kwargs):
+ super(XOS, self).__init__(*args, **kwargs)
+
+ def save(self, *args, **kwds):
+ super(XOS, self).save(*args, **kwds)
+
+# def can_update(self, user):
+# return user.can_update_site(self.site, allow=['tech'])
+
+ def rebuild(self):
+ for service_controller in self.service_controllers.all():
+ for scr in service_controller.service_controller_resources.all():
+ scr.save()
+ service_controller.save()
+ self.save()
+
+class XOSVolume(PlCoreBase):
+ xos = models.ForeignKey(XOS, related_name='volumes', help_text="The XOS object for this Volume")
+ container_path=StrippedCharField(max_length=1024, unique=True, help_text="Path of Volume in Container")
+ host_path=StrippedCharField(max_length=1024, help_text="Path of Volume in Host")
+ read_only=models.BooleanField(default=False, help_text="True if mount read-only")
+
+ def __unicode__(self): return u'%s' % (self.container_path)
+
+
diff --git a/xos/core/xoslib/methods/ceilometerview.py b/xos/core/xoslib/methods/ceilometerview.py
index 16b32d4..9c87e40 100644
--- a/xos/core/xoslib/methods/ceilometerview.py
+++ b/xos/core/xoslib/methods/ceilometerview.py
@@ -1151,8 +1151,8 @@
if (not tenant_ceilometer_url):
raise XOSMissingField("Tenant ceilometer URL is missing")
- tenant_id = request.QUERY_PARAMS.get('tenant', None)
- resource_id = request.QUERY_PARAMS.get('resource', None)
+ tenant_id = request.query_params.get('tenant', None)
+ resource_id = request.query_params.get('resource', None)
query = []
if tenant_id:
@@ -1187,9 +1187,9 @@
raise XOSMissingField("Tenant ceilometer URL is missing")
tenant_map = getTenantControllerTenantMap(request.user)
- date_options = request.QUERY_PARAMS.get('period', 1)
- date_from = request.QUERY_PARAMS.get('date_from', '')
- date_to = request.QUERY_PARAMS.get('date_to', '')
+ date_options = request.query_params.get('period', 1)
+ date_from = request.query_params.get('date_from', '')
+ date_to = request.query_params.get('date_to', '')
try:
date_from, date_to = calc_date_args(date_from,
@@ -1208,9 +1208,9 @@
'op': 'le',
'value': date_to})
- meter_name = request.QUERY_PARAMS.get('meter', None)
- tenant_id = request.QUERY_PARAMS.get('tenant', None)
- resource_id = request.QUERY_PARAMS.get('resource', None)
+ meter_name = request.query_params.get('meter', None)
+ tenant_id = request.query_params.get('tenant', None)
+ resource_id = request.query_params.get('resource', None)
query = []
if tenant_id:
@@ -1284,12 +1284,12 @@
tenant_ceilometer_url = getTenantCeilometerProxyURL(request.user)
if (not tenant_ceilometer_url):
raise XOSMissingField("Tenant ceilometer URL is missing")
- meter_name = request.QUERY_PARAMS.get('meter', None)
+ meter_name = request.query_params.get('meter', None)
if not meter_name:
raise XOSMissingField("Meter name in query params is missing")
- limit = request.QUERY_PARAMS.get('limit', 10)
- tenant_id = request.QUERY_PARAMS.get('tenant', None)
- resource_id = request.QUERY_PARAMS.get('resource', None)
+ limit = request.query_params.get('limit', 10)
+ tenant_id = request.query_params.get('tenant', None)
+ resource_id = request.query_params.get('resource', None)
query = []
if tenant_id:
query.extend(make_query(tenant_id=tenant_id))
@@ -1340,7 +1340,7 @@
tenant_ceilometer_url = getTenantCeilometerProxyURL(request.user)
if (not tenant_ceilometer_url):
raise XOSMissingField("Tenant ceilometer URL is missing")
- instance_uuid = request.QUERY_PARAMS.get('instance-uuid', None)
+ instance_uuid = request.query_params.get('instance-uuid', None)
if not instance_uuid:
raise XOSMissingField("Instance UUID in query params is missing")
if not Instance.objects.filter(instance_uuid=instance_uuid):
@@ -1354,9 +1354,9 @@
#neutron port resource id is represented in ceilometer as "nova instance-name"+"-"+"nova instance-id"+"-"+"tap"+first 11 characters of port-id
resource_ids.append(xos_instance.instance_id+"-"+instance_uuid+"-tap"+p.port_id[:11])
- date_options = request.QUERY_PARAMS.get('period', 1)
- date_from = request.QUERY_PARAMS.get('date_from', '')
- date_to = request.QUERY_PARAMS.get('date_to', '')
+ date_options = request.query_params.get('period', 1)
+ date_from = request.query_params.get('date_from', '')
+ date_to = request.query_params.get('date_to', '')
try:
date_from, date_to = calc_date_args(date_from,
@@ -1437,9 +1437,9 @@
def get(self, request, format=None):
if (not request.user.is_authenticated()) or (not request.user.is_admin):
raise PermissionDenied("You must be authenticated admin user in order to use this API")
- service = request.QUERY_PARAMS.get('service', None)
- slice_hint = request.QUERY_PARAMS.get('slice_hint', None)
- scale = request.QUERY_PARAMS.get('scale', None)
+ service = request.query_params.get('service', None)
+ slice_hint = request.query_params.get('slice_hint', None)
+ scale = request.query_params.get('scale', None)
if not service or not slice_hint or not scale:
raise XOSMissingField("Mandatory fields missing")
services = Service.select_by_user(request.user)
diff --git a/xos/core/xoslib/static/js/vendor/ngXosHelpers.js b/xos/core/xoslib/static/js/vendor/ngXosHelpers.js
index 4af4da5..90049b8 100644
--- a/xos/core/xoslib/static/js/vendor/ngXosHelpers.js
+++ b/xos/core/xoslib/static/js/vendor/ngXosHelpers.js
@@ -1 +1 @@
-"use strict";!function(){angular.module("xos.uiComponents",["chart.js","RecursionHelper"])}(),function(){function e(){var e=function(e){return e.split("_").join(" ").trim()},n=function(e){return e.split(/(?=[A-Z])/).map(function(e){return e.toLowerCase()}).join(" ")},o=function(e){return e.slice(0,1).toUpperCase()+e.slice(1)},t=function(t){return t=e(t),t=n(t),t=o(t).replace(/\s\s+/g," ")+":",t.replace("::",":")};return{_formatByUnderscore:e,_formatByUppercase:n,_capitalize:o,format:t}}angular.module("xos.uiComponents").factory("LabelFormatter",e)}();var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e};!function(){angular.module("xos.uiComponents").service("XosFormHelpers",["_","LabelFormatter",function(e,n){var o=this;this._isEmail=function(e){var n=/(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/;return n.test(e)},this._getFieldFormat=function(n){return angular.isArray(n)?"array":e.isDate(n)||!Number.isNaN(Date.parse(n))&&new Date(n).getTime()>6311808e5?"date":"boolean"==typeof n?"boolean":o._isEmail(n)?"email":"string"==typeof n||null===n?"text":"undefined"==typeof n?"undefined":_typeof(n)},this.buildFormStructure=function(t,i,r){return t=Object.keys(t).length>0?t:i,i=i||{},e.reduce(Object.keys(t),function(e,t){return e[t]={label:i[t]&&i[t].label?i[t].label+":":n.format(t),type:i[t]&&i[t].type?i[t].type:o._getFieldFormat(r[t]),validators:i[t]&&i[t].validators?i[t].validators:{},hint:i[t]&&i[t].hint?i[t].hint:""},"date"===e[t].type&&(r[t]=new Date(r[t])),"number"===e[t].type&&(r[t]=parseInt(r[t],10)),e},{})},this.parseModelField=function(n){return e.reduce(n,function(e,n){return e[n]={},e},{})}}])}(),function(){angular.module("xos.uiComponents").directive("xosValidation",function(){return{restrict:"E",scope:{field:"=",form:"="},template:'\n <div ng-cloak>\n <xos-alert config="vm.config" show="vm.field.$error.required !== undefined && vm.field.$error.required !== false && (vm.field.$touched || vm.form.$submitted)">\n Field required\n </xos-alert>\n <xos-alert config="vm.config" show="vm.field.$error.email !== undefined && vm.field.$error.email !== false && (vm.field.$touched || vm.form.$submitted)">\n This is not a valid email\n </xos-alert>\n <xos-alert config="vm.config" show="vm.field.$error.minlength !== undefined && vm.field.$error.minlength !== false && (vm.field.$touched || vm.form.$submitted)">\n Too short\n </xos-alert>\n <xos-alert config="vm.config" show="vm.field.$error.maxlength !== undefined && vm.field.$error.maxlength !== false && (vm.field.$touched || vm.form.$submitted)">\n Too long\n </xos-alert>\n <xos-alert config="vm.config" show="vm.field.$error.custom !== undefined && vm.field.$error.custom !== false && (vm.field.$touched || vm.form.$submitted)">\n Field invalid\n </xos-alert>\n </div>\n ',transclude:!0,bindToController:!0,controllerAs:"vm",controller:function(){this.config={type:"danger"}}}})}(),function(){angular.module("xos.uiComponents").directive("xosForm",function(){return{restrict:"E",scope:{config:"=",ngModel:"="},template:'\n <ng-form name="vm.{{vm.config.formName || \'form\'}}">\n <div class="form-group" ng-repeat="(name, field) in vm.formField">\n <xos-field name="name" field="field" ng-model="vm.ngModel[name]"></xos-field>\n <xos-validation errors="vm[vm.config.formName || \'form\'][name].$error"></xos-validation>\n </div>\n <div class="form-group" ng-if="vm.config.actions">\n <button role="button" href=""\n ng-repeat="action in vm.config.actions"\n ng-click="action.cb(vm.ngModel)"\n class="btn btn-{{action.class}}"\n title="{{action.label}}">\n <i class="glyphicon glyphicon-{{action.icon}}"></i>\n {{action.label}}\n </button>\n </div>\n </ng-form>\n ',bindToController:!0,controllerAs:"vm",controller:["$scope","$log","_","XosFormHelpers",function(e,n,o,t){var i=this;if(!this.config)throw new Error('[xosForm] Please provide a configuration via the "config" attribute');if(!this.config.actions)throw new Error("[xosForm] Please provide an action list in the configuration");this.excludedField=["id","validators","created","updated","deleted","backend_status"],this.config&&this.config.exclude&&(this.excludedField=this.excludedField.concat(this.config.exclude)),this.formField=[],e.$watch(function(){return i.ngModel},function(e){if(i.formField={},e){var n=o.difference(Object.keys(e),i.excludedField),r=t.parseModelField(n);i.formField=t.buildFormStructure(r,i.config.fields,e)}})}]}})}(),function(){angular.module("xos.uiComponents").directive("xosTable",function(){return{restrict:"E",scope:{data:"=",config:"="},template:'\n <div ng-show="vm.data.length > 0">\n <div class="row" ng-if="vm.config.filter == \'fulltext\'">\n <div class="col-xs-12">\n <input\n class="form-control"\n placeholder="Type to search.."\n type="text"\n ng-model="vm.query"/>\n </div>\n </div>\n <table ng-class="vm.classes" ng-hide="vm.data.length == 0">\n <thead>\n <tr>\n <th ng-repeat="col in vm.columns">\n {{col.label}}\n <span ng-if="vm.config.order">\n <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = false">\n <i class="glyphicon glyphicon-chevron-up"></i>\n </a>\n <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = true">\n <i class="glyphicon glyphicon-chevron-down"></i>\n </a>\n </span>\n </th>\n <th ng-if="vm.config.actions">Actions:</th>\n </tr>\n </thead>\n <tbody ng-if="vm.config.filter == \'field\'">\n <tr>\n <td ng-repeat="col in vm.columns">\n <input\n ng-if="col.type !== \'boolean\'"\n class="form-control"\n placeholder="Type to search by {{col.label}}"\n type="text"\n ng-model="vm.query[col.prop]"/>\n <select\n ng-if="col.type === \'boolean\'"\n class="form-control"\n ng-model="vm.query[col.prop]">\n <option value="">-</option>\n <option value="true">True</option>\n <option value="false">False</option>\n </select>\n </td>\n <td ng-if="vm.config.actions"></td>\n </tr>\n </tbody>\n <tbody>\n <tr ng-repeat="item in vm.data | filter:vm.query | orderBy:vm.orderBy:vm.reverse | pagination:vm.currentPage * vm.config.pagination.pageSize | limitTo: (vm.config.pagination.pageSize || vm.data.length) track by $index">\n <td ng-repeat="col in vm.columns" link-wrapper>\n <span ng-if="!col.type">{{item[col.prop]}}</span>\n <span ng-if="col.type === \'boolean\'">\n <i class="glyphicon"\n ng-class="{\'glyphicon-ok\': item[col.prop], \'glyphicon-remove\': !item[col.prop]}">\n </i>\n </span>\n <span ng-if="col.type === \'date\'">\n {{item[col.prop] | date:\'H:mm MMM d, yyyy\'}}\n </span>\n <span ng-if="col.type === \'array\'">\n {{item[col.prop] | arrayToList}}\n </span>\n <span ng-if="col.type === \'object\'">\n <dl class="dl-horizontal">\n <span ng-repeat="(k,v) in item[col.prop]">\n <dt>{{k}}</dt>\n <dd>{{v}}</dd>\n </span>\n </dl>\n </span>\n <span ng-if="col.type === \'custom\'">\n {{col.formatter(item)}}\n </span>\n <span ng-if="col.type === \'icon\'">\n <i class="glyphicon glyphicon-{{col.formatter(item)}}">\n </i>\n </span>\n </td>\n <td ng-if="vm.config.actions">\n <a href=""\n ng-repeat="action in vm.config.actions"\n ng-click="action.cb(item)"\n title="{{action.label}}">\n <i\n class="glyphicon glyphicon-{{action.icon}}"\n style="color: {{action.color}};"></i>\n </a>\n </td>\n </tr>\n </tbody>\n </table>\n <xos-pagination\n ng-if="vm.config.pagination"\n page-size="vm.config.pagination.pageSize"\n total-elements="vm.data.length"\n change="vm.goToPage">\n </xos-pagination>\n </div>\n <div ng-show="vm.data.length == 0 || !vm.data">\n <xos-alert config="{type: \'info\'}">\n No data to show.\n </xos-alert>\n </div>\n ',bindToController:!0,controllerAs:"vm",controller:["_",function(e){var n=this;if(!this.config)throw new Error('[xosTable] Please provide a configuration via the "config" attribute');if(!this.config.columns)throw new Error("[xosTable] Please provide a columns list in the configuration");this.config.order&&angular.isObject(this.config.order)&&(this.reverse=this.config.order.reverse||!1,this.orderBy=this.config.order.field||"id");var o=e.filter(this.config.columns,{type:"custom"});angular.isArray(o)&&o.length>0&&e.forEach(o,function(e){if(!e.formatter||!angular.isFunction(e.formatter))throw new Error("[xosTable] You have provided a custom field type, a formatter function should provided too.")});var t=e.filter(this.config.columns,{type:"icon"});angular.isArray(t)&&t.length>0&&e.forEach(t,function(e){if(!e.formatter||!angular.isFunction(e.formatter))throw new Error("[xosTable] You have provided an icon field type, a formatter function should provided too.")});var i=e.filter(this.config.columns,function(e){return angular.isDefined(e.link)});angular.isArray(i)&&i.length>0&&e.forEach(i,function(e){if(!angular.isFunction(e.link))throw new Error("[xosTable] The link property should be a function.")}),this.columns=this.config.columns,this.classes=this.config.classes||"table table-striped table-bordered",this.config.actions,this.config.pagination&&(this.currentPage=0,this.goToPage=function(e){n.currentPage=e})}]}}).filter("arrayToList",function(){return function(e){return angular.isArray(e)?e.join(", "):e}}).directive("linkWrapper",function(){return{restrict:"A",transclude:!0,template:'\n <a ng-if="col.link" href="{{col.link(item)}}">\n <div ng-transclude></div>\n </a>\n <div ng-transclude ng-if="!col.link"></div>\n '}})}(),function(){angular.module("xos.uiComponents").directive("xosPagination",function(){return{restrict:"E",scope:{pageSize:"=",totalElements:"=",change:"="},template:'\n <div class="row" ng-if="vm.pageList.length > 1">\n <div class="col-xs-12 text-center">\n <ul class="pagination">\n <li\n ng-click="vm.goToPage(vm.currentPage - 1)"\n ng-class="{disabled: vm.currentPage == 0}">\n <a href="" aria-label="Previous">\n <span aria-hidden="true">«</span>\n </a>\n </li>\n <li ng-repeat="i in vm.pageList" ng-class="{active: i === vm.currentPage}">\n <a href="" ng-click="vm.goToPage(i)">{{i + 1}}</a>\n </li>\n <li\n ng-click="vm.goToPage(vm.currentPage + 1)"\n ng-class="{disabled: vm.currentPage == vm.pages - 1}">\n <a href="" aria-label="Next">\n <span aria-hidden="true">»</span>\n </a>\n </li>\n </ul>\n </div>\n </div>\n ',bindToController:!0,controllerAs:"vm",controller:["$scope",function(e){var n=this;this.currentPage=0,this.goToPage=function(e){0>e||e===n.pages||(n.currentPage=e,n.change(e))},this.createPages=function(e){for(var n=[],o=0;e>o;o++)n.push(o);return n},e.$watch(function(){return n.totalElements},function(){n.totalElements&&(n.pages=Math.ceil(n.totalElements/n.pageSize),n.pageList=n.createPages(n.pages))})}]}}).filter("pagination",function(){return function(e,n){return e&&angular.isArray(e)?(n=parseInt(n,10),e.slice(n)):e}})}(),function(){angular.module("xos.uiComponents").directive("xosAlert",function(){return{restrict:"E",scope:{config:"=",show:"=?"},template:'\n <div ng-cloak class="alert alert-{{vm.config.type}}" ng-hide="!vm.show">\n <button type="button" class="close" ng-if="vm.config.closeBtn" ng-click="vm.dismiss()">\n <span aria-hidden="true">×</span>\n </button>\n <p ng-transclude></p>\n </div>\n ',transclude:!0,bindToController:!0,controllerAs:"vm",controller:["$timeout",function(e){var n=this;if(!this.config)throw new Error('[xosAlert] Please provide a configuration via the "config" attribute');this.show=this.show!==!1,this.dismiss=function(){n.show=!1},this.config.autoHide&&!function(){var o=e(function(){n.dismiss(),e.cancel(o)},n.config.autoHide)}()}]}})}(),function(){angular.module("xos.uiComponents").directive("xosField",["RecursionHelper",function(e){return{restrict:"E",scope:{name:"=",field:"=",ngModel:"="},template:'\n <label ng-if="vm.field.type !== \'object\'">{{vm.field.label}}</label>\n <input\n ng-if="vm.field.type !== \'boolean\' && vm.field.type !== \'object\'"\n type="{{vm.field.type}}"\n name="{{vm.name}}"\n class="form-control"\n ng-model="vm.ngModel"\n ng-minlength="vm.field.validators.minlength || 0"\n ng-maxlength="vm.field.validators.maxlength || 2000"\n ng-required="vm.field.validators.required || false" />\n <span class="boolean-field" ng-if="vm.field.type === \'boolean\'">\n <button\n class="btn btn-success"\n ng-show="vm.ngModel"\n ng-click="vm.ngModel = false">\n <i class="glyphicon glyphicon-ok"></i>\n </button>\n <button\n class="btn btn-danger"\n ng-show="!vm.ngModel"\n ng-click="vm.ngModel = true">\n <i class="glyphicon glyphicon-remove"></i>\n </button>\n </span>\n <div\n class="panel panel-default object-field"\n ng-if="vm.field.type == \'object\' && !vm.isEmptyObject(vm.ngModel)"\n >\n <div class="panel-heading">{{vm.field.label}}</div>\n <div class="panel-body">\n <div ng-repeat="(k, v) in vm.ngModel">\n <xos-field\n name="k"\n field="{label: vm.formatLabel(k), type: vm.getType(v)}"\n ng-model="v">\n </xos-field>\n </div>\n </div>\n </div>\n ',bindToController:!0,controllerAs:"vm",compile:function(n){return e.compile(n)},controller:["$attrs","XosFormHelpers","LabelFormatter",function(e,n,o){if(!this.name)throw new Error("[xosField] Please provide a field name");if(!this.field)throw new Error("[xosField] Please provide a field definition");if(!e.ngModel)throw new Error("[xosField] Please provide an ng-model");this.getType=n._getFieldFormat,this.formatLabel=o.format,this.isEmptyObject=function(e){return 0===Object.keys(e).length}}]}}])}();var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e};!function(){angular.module("xos.uiComponents").directive("xosSmartTable",function(){return{restrict:"E",scope:{config:"="},template:'\n <div class="row" ng-show="vm.data.length > 0">\n <div class="col-xs-12 text-right">\n <a href="" class="btn btn-success" ng-click="vm.createItem()">\n Add\n </a>\n </div>\n </div>\n <div class="row">\n <div class="col-xs-12 table-responsive">\n <xos-table config="vm.tableConfig" data="vm.data"></xos-table>\n </div>\n </div>\n <div class="panel panel-default" ng-show="vm.detailedItem">\n <div class="panel-heading">\n <div class="row">\n <div class="col-xs-11">\n <h3 class="panel-title" ng-show="vm.detailedItem.id">Update {{vm.config.resource}} {{vm.detailedItem.id}}</h3>\n <h3 class="panel-title" ng-show="!vm.detailedItem.id">Create {{vm.config.resource}} item</h3>\n </div>\n <div class="col-xs-1">\n <a href="" ng-click="vm.cleanForm()">\n <i class="glyphicon glyphicon-remove pull-right"></i>\n </a>\n </div>\n </div>\n </div>\n <div class="panel-body">\n <xos-form config="vm.formConfig" ng-model="vm.detailedItem"></xos-form>\n </div>\n </div>\n <xos-alert config="{type: \'success\', closeBtn: true}" show="vm.responseMsg">{{vm.responseMsg}}</xos-alert>\n <xos-alert config="{type: \'danger\', closeBtn: true}" show="vm.responseErr">{{vm.responseErr}}</xos-alert>\n ',bindToController:!0,controllerAs:"vm",controller:["$injector","LabelFormatter","_","XosFormHelpers",function(e,n,o,t){var i=this;this.responseMsg=!1,this.responseErr=!1,this.tableConfig={columns:[],actions:[{label:"delete",icon:"remove",cb:function(e){i.Resource["delete"]({id:e.id}).$promise.then(function(){o.remove(i.data,function(n){return n.id===e.id}),i.responseMsg=i.config.resource+" with id "+e.id+" successfully deleted"})["catch"](function(n){i.responseErr=n.data.detail||"Error while deleting "+i.config.resource+" with id "+e.id})},color:"red"},{label:"details",icon:"search",cb:function(e){i.detailedItem=e}}],classes:"table table-striped table-bordered table-responsive",filter:"field",order:!0,pagination:{pageSize:10}},this.formConfig={exclude:this.config.hiddenFields,fields:{},formName:this.config.resource+"Form",actions:[{label:"Save",icon:"ok",cb:function(e){var n=void 0,o=!0;e.id?(n=e.$update(),o=!1):n=e.$save(),n.then(function(n){o&&i.data.push(angular.copy(n)),delete i.detailedItem,i.responseMsg=i.config.resource+" with id "+e.id+" successfully saved"})["catch"](function(n){i.responseErr=n.data.detail||"Error while saving "+i.config.resource+" with id "+e.id})},"class":"success"}]},this.cleanForm=function(){delete i.detailedItem},this.createItem=function(){i.detailedItem=new i.Resource},this.Resource=e.get(this.config.resource);var r=function(){i.Resource.query().$promise.then(function(e){if(e[0]){var r=e[0],s=Object.keys(r);o.remove(s,function(e){return"id"==e||"validators"==e}),angular.isArray(i.config.hiddenFields)&&(s=o.difference(s,i.config.hiddenFields));var a=s.map(function(e){return n.format(e)});s.forEach(function(e,n){var o={label:a[n],prop:e};"string"!=typeof r[e]&&"undefined"!=typeof r[e]&&(o.type=_typeof(r[e])),i.tableConfig.columns.push(o)}),s.forEach(function(e,o){i.formConfig.fields[e]={label:n.format(a[o]).replace(":",""),type:t._getFieldFormat(r[e])}}),i.data=e}})};r()}]}})}(),function(){angular.module("xos.uiComponents").directive("xosSmartPie",function(){return{restrict:"E",scope:{config:"="},template:'\n <canvas\n class="chart chart-pie {{vm.config.classes}}"\n chart-data="vm.data" chart-labels="vm.labels"\n chart-legend="{{vm.config.legend}}">\n </canvas>\n ',bindToController:!0,controllerAs:"vm",controller:["$injector","$interval","$scope","$timeout","_",function(e,n,o,t,i){var r=this;if(!this.config.resource&&!this.config.data)throw new Error("[xosSmartPie] Please provide a resource or an array of data in the configuration");var s=function(e){return i.groupBy(e,r.config.groupBy)},a=function(e){return i.reduce(Object.keys(e),function(n,o){return n.concat(e[o].length)},[])},c=function(e){return angular.isFunction(r.config.labelFormatter)?r.config.labelFormatter(Object.keys(e)):Object.keys(e)},l=function(e){var n=s(e);r.data=a(n),r.labels=c(n)};this.config.resource?!function(){r.Resource=e.get(r.config.resource);var o=function(){r.Resource.query().$promise.then(function(e){e[0]&&l(e)})};o(),r.config.poll&&n(function(){o()},1e3*r.config.poll)}():o.$watch(function(){return r.config.data},function(e){e&&l(r.config.data)},!0),o.$on("create",function(e,n){console.log("create: "+n.id)}),o.$on("destroy",function(e,n){console.log("destroy: "+n.id)})}]}})}(),function(){function e(e,n,o){e.interceptors.push("SetCSRFToken"),n.startSymbol("{$"),n.endSymbol("$}"),o.defaults.stripTrailingSlashes=!1}e.$inject=["$httpProvider","$interpolateProvider","$resourceProvider"],angular.module("bugSnag",[]).factory("$exceptionHandler",function(){return function(e,n){window.Bugsnag?Bugsnag.notifyException(e,{diagnostics:{cause:n}}):console.error(e,n,e.stack)}}),angular.module("xos.helpers",["ngCookies","ngResource","ngAnimate","bugSnag","xos.uiComponents"]).config(e).factory("_",["$window",function(e){return e._}])}(),function(){angular.module("xos.helpers").service("vSG-Collection",["$resource",function(e){return e("/api/service/vsg/")}])}(),function(){angular.module("xos.helpers").service("vOLT-Collection",["$resource",function(e){return e("/api/tenant/cord/volt/:volt_id/",{volt_id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Login",["$resource",function(e){return e("/api/utility/login/")}]).service("Logout",["$resource",function(e){return e("/api/utility/logout/")}])}(),function(){angular.module("xos.helpers").service("Users",["$resource",function(e){return e("/api/core/users/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Truckroll",["$resource",function(e){return e("/api/tenant/truckroll/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Tenants",["$resource",function(e){return e("/api/core/tenants/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Subscribers",["$resource",function(e){return e("/api/tenant/cord/subscriber/:id/",{id:"@id"},{update:{method:"PUT"},"View-a-Subscriber-Features-Detail":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/"},"Read-Subscriber-uplink_speed":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/uplink_speed/"},"Update-Subscriber-uplink_speed":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/uplink_speed/"},"Read-Subscriber-downlink_speed":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/downlink_speed/"},"Update-Subscriber-downlink_speed":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/downlink_speed/"},"Read-Subscriber-cdn":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/cdn/"},"Update-Subscriber-cdn":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/cdn/"},"Read-Subscriber-uverse":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/uverse/"},"Update-Subscriber-uverse":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/uverse/"},"Read-Subscriber-status":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/status/"},"Update-Subscriber-status":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/status/"}})}])}(),function(){angular.module("xos.helpers").service("SlicesPlus",["$http","$q",function(e,n){this.query=function(o){var t=n.defer();return e.get("/api/utility/slicesplus/",{params:o}).then(function(e){t.resolve(e.data)})["catch"](function(e){t.reject(e.data)}),{$promise:t.promise}},this.get=function(o,t){var i=n.defer();return e.get("/api/utility/slicesplus/"+o,{params:t}).then(function(e){i.resolve(e.data)})["catch"](function(e){i.reject(e.data)}),{$promise:i.promise}}}])}(),function(){angular.module("xos.helpers").service("Slices",["$resource",function(e){return e("/api/core/slices/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Sites",["$resource",function(e){return e("/api/core/sites/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Services",["$resource",function(e){return e("/api/core/services/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("ONOS-Services-Collection",["$resource",function(e){return e("/api/service/onos/")}])}(),function(){angular.module("xos.helpers").service("ONOS-App-Collection",["$resource",function(e){return e("/api/tenant/onos/app/")}])}(),function(){angular.module("xos.helpers").service("Nodes",["$resource",function(e){return e("/api/core/nodes/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Networks",["$resource",function(e){return e("/api/core/networks/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Instances",["$resource",function(e){return e("/api/core/instances/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Flavors",["$resource",function(e){return e("/api/core/flavors/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Example-Services-Collection",["$resource",function(e){return e("/api/service/exampleservice/")}])}(),function(){angular.module("xos.helpers").service("Deployments",["$resource",function(e){return e("/api/core/deployments/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("XosUserPrefs",["$cookies",function(e){var n=this,o=e.get("xosUserPrefs")?JSON.parse(e.get("xosUserPrefs")):{};this.getAll=function(){return o=e.get("xosUserPrefs")?JSON.parse(e.get("xosUserPrefs")):{}},this.setAll=function(n){e.put("xosUserPrefs",JSON.stringify(n))},this.getSynchronizerNotificationStatus=function(){var e=arguments.length<=0||void 0===arguments[0]?!1:arguments[0];return e?n.getAll().synchronizers.notification[e]:n.getAll().synchronizers.notification},this.setSynchronizerNotificationStatus=function(){var e=arguments.length<=0||void 0===arguments[0]?!1:arguments[0],o=arguments[1];if(!e)throw new Error("[XosUserPrefs] When updating a synchronizer is mandatory to provide a name.");var t=n.getAll();t.synchronizers||(t.synchronizers={notification:{}}),t.synchronizers.notification[e]=o,n.setAll(t)}}])}(),function(){angular.module("xos.helpers").service("GraphService",["$q","Tenants","Services",function(e,n,o){var t=this;this.loadCoarseData=function(){var t=void 0,i=e.defer();return o.query().$promise.then(function(e){return t=e,n.query({kind:"coarse"}).$promise}).then(function(e){i.resolve({tenants:e,services:t})}),i.promise},this.getCoarseGraph=function(){return t.loadCoarseData().then(function(e){console.log(e)}),"ciao"}}])}(),function(){angular.module("xos.helpers").factory("Notification",function(){return window.Notification}).service("xosNotification",["$q","$log","Notification",function(e,n,o){var t=this;this.checkPermission=function(){var n=e.defer();return o.requestPermission().then(function(e){"granted"===e?n.resolve(e):n.reject(e)}),n.promise},this.sendNotification=function(e,t){var i=new o(e,t);i.onerror=function(e){n.error(e)}},this.notify=function(e,i){"Notification"in window?"granted"!==o.permission?t.checkPermission().then(function(){return t.sendNotification(e,i)}):"granted"===o.permission&&t.sendNotification(e,i):n.info("This browser does not support desktop notification")}}])}(),function(){function e(){return{request:function(e){return-1===e.url.indexOf(".html")&&(e.url+="?no_hyperlinks=1"),e}}}angular.module("xos.helpers").factory("NoHyperlinks",e)}(),angular.module("xos.helpers").config(["$provide",function(e){e.decorator("$log",["$delegate",function(e){var n=function(){return window.location.href.indexOf("debug=true")>=0},o=e.log,t=e.info,i=e.warn,r=e.error,s=e.debug,a=function(o){return function(){if(n()){var t=[].slice.call(arguments),i=new Date;t[0]="["+i.getHours()+":"+i.getMinutes()+":"+i.getSeconds()+"] "+t[0],"function"!=typeof e.reset||e.debug.logs instanceof Array||e.reset(),o.apply(null,t)}}};return e.info=a(t),e.log=a(o),e.warn=a(i),e.error=a(r),e.debug=a(s),e}])}]),function(){function e(){var e=function(e){return e.split("_").join(" ").trim()},n=function(e){return e.split(/(?=[A-Z])/).map(function(e){return e.toLowerCase()}).join(" ")},o=function(e){return e.slice(0,1).toUpperCase()+e.slice(1)},t=function(t){return t=e(t),t=n(t),t=o(t).replace(/\s\s+/g," ")+":",t.replace("::",":")};return{_formatByUnderscore:e,_formatByUppercase:n,_capitalize:o,format:t}}angular.module("xos.uiComponents").factory("LabelFormatter",e)}(),function(){function e(e){return{request:function(n){return"GET"!==n.method&&(n.headers["X-CSRFToken"]=e.get("xoscsrftoken")),n}}}e.$inject=["$cookies"],angular.module("xos.helpers").factory("SetCSRFToken",e)}();
\ No newline at end of file
+"use strict";!function(){angular.module("xos.uiComponents",["chart.js","RecursionHelper"])}();var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e};!function(){angular.module("xos.uiComponents").directive("xosSmartTable",function(){return{restrict:"E",scope:{config:"="},template:'\n <div class="row" ng-show="vm.data.length > 0">\n <div class="col-xs-12 text-right">\n <a href="" class="btn btn-success" ng-click="vm.createItem()">\n Add\n </a>\n </div>\n </div>\n <div class="row">\n <div class="col-xs-12 table-responsive">\n <xos-table config="vm.tableConfig" data="vm.data"></xos-table>\n </div>\n </div>\n <div class="panel panel-default" ng-show="vm.detailedItem">\n <div class="panel-heading">\n <div class="row">\n <div class="col-xs-11">\n <h3 class="panel-title" ng-show="vm.detailedItem.id">Update {{vm.config.resource}} {{vm.detailedItem.id}}</h3>\n <h3 class="panel-title" ng-show="!vm.detailedItem.id">Create {{vm.config.resource}} item</h3>\n </div>\n <div class="col-xs-1">\n <a href="" ng-click="vm.cleanForm()">\n <i class="glyphicon glyphicon-remove pull-right"></i>\n </a>\n </div>\n </div>\n </div>\n <div class="panel-body">\n <xos-form config="vm.formConfig" ng-model="vm.detailedItem"></xos-form>\n </div>\n </div>\n <xos-alert config="{type: \'success\', closeBtn: true}" show="vm.responseMsg">{{vm.responseMsg}}</xos-alert>\n <xos-alert config="{type: \'danger\', closeBtn: true}" show="vm.responseErr">{{vm.responseErr}}</xos-alert>\n ',bindToController:!0,controllerAs:"vm",controller:["$injector","LabelFormatter","_","XosFormHelpers",function(e,n,o,t){var i=this;this.responseMsg=!1,this.responseErr=!1,this.tableConfig={columns:[],actions:[{label:"delete",icon:"remove",cb:function(e){i.Resource["delete"]({id:e.id}).$promise.then(function(){o.remove(i.data,function(n){return n.id===e.id}),i.responseMsg=i.config.resource+" with id "+e.id+" successfully deleted"})["catch"](function(n){i.responseErr=n.data.detail||"Error while deleting "+i.config.resource+" with id "+e.id})},color:"red"},{label:"details",icon:"search",cb:function(e){i.detailedItem=e}}],classes:"table table-striped table-bordered table-responsive",filter:"field",order:!0,pagination:{pageSize:10}},this.formConfig={exclude:this.config.hiddenFields,fields:{},formName:this.config.resource+"Form",actions:[{label:"Save",icon:"ok",cb:function(e){var n=void 0,o=!0;e.id?(n=e.$update(),o=!1):n=e.$save(),n.then(function(n){o&&i.data.push(angular.copy(n)),delete i.detailedItem,i.responseMsg=i.config.resource+" with id "+e.id+" successfully saved"})["catch"](function(n){i.responseErr=n.data.detail||"Error while saving "+i.config.resource+" with id "+e.id})},"class":"success"}]},this.cleanForm=function(){delete i.detailedItem},this.createItem=function(){i.detailedItem=new i.Resource},this.Resource=e.get(this.config.resource);var r=function(){i.Resource.query().$promise.then(function(e){if(e[0]){var r=e[0],s=Object.keys(r);o.remove(s,function(e){return"id"===e||"validators"===e}),angular.isArray(i.config.hiddenFields)&&(s=o.difference(s,i.config.hiddenFields));var a=s.map(function(e){return n.format(e)});s.forEach(function(e,n){var o={label:a[n],prop:e};"string"!=typeof r[e]&&"undefined"!=typeof r[e]&&(o.type=_typeof(r[e])),i.tableConfig.columns.push(o)}),s.forEach(function(e,o){i.formConfig.fields[e]={label:n.format(a[o]).replace(":",""),type:t._getFieldFormat(r[e])}}),i.data=e}})};r()}]}})}(),function(){angular.module("xos.uiComponents").directive("xosValidation",function(){return{restrict:"E",scope:{field:"=",form:"="},template:'\n <div ng-cloak>\n <xos-alert config="vm.config" show="vm.field.$error.required !== undefined && vm.field.$error.required !== false && (vm.field.$touched || vm.form.$submitted)">\n Field required\n </xos-alert>\n <xos-alert config="vm.config" show="vm.field.$error.email !== undefined && vm.field.$error.email !== false && (vm.field.$touched || vm.form.$submitted)">\n This is not a valid email\n </xos-alert>\n <xos-alert config="vm.config" show="vm.field.$error.minlength !== undefined && vm.field.$error.minlength !== false && (vm.field.$touched || vm.form.$submitted)">\n Too short\n </xos-alert>\n <xos-alert config="vm.config" show="vm.field.$error.maxlength !== undefined && vm.field.$error.maxlength !== false && (vm.field.$touched || vm.form.$submitted)">\n Too long\n </xos-alert>\n <xos-alert config="vm.config" show="vm.field.$error.custom !== undefined && vm.field.$error.custom !== false && (vm.field.$touched || vm.form.$submitted)">\n Field invalid\n </xos-alert>\n </div>\n ',transclude:!0,bindToController:!0,controllerAs:"vm",controller:function(){this.config={type:"danger"}}}})}(),function(){angular.module("xos.uiComponents").directive("xosSmartPie",function(){return{restrict:"E",scope:{config:"="},template:'\n <canvas\n class="chart chart-pie {{vm.config.classes}}"\n chart-data="vm.data" chart-labels="vm.labels"\n chart-legend="{{vm.config.legend}}">\n </canvas>\n ',bindToController:!0,controllerAs:"vm",controller:["$injector","$interval","$scope","$timeout","_",function(e,n,o,t,i){var r=this;if(!this.config.resource&&!this.config.data)throw new Error("[xosSmartPie] Please provide a resource or an array of data in the configuration");var s=function(e){return i.groupBy(e,r.config.groupBy)},a=function(e){return i.reduce(Object.keys(e),function(n,o){return n.concat(e[o].length)},[])},l=function(e){return angular.isFunction(r.config.labelFormatter)?r.config.labelFormatter(Object.keys(e)):Object.keys(e)},c=function(e){var n=s(e);r.data=a(n),r.labels=l(n)};this.config.resource?!function(){r.Resource=e.get(r.config.resource);var o=function(){r.Resource.query().$promise.then(function(e){e[0]&&c(e)})};o(),r.config.poll&&n(function(){o()},1e3*r.config.poll)}():o.$watch(function(){return r.config.data},function(e){e&&c(r.config.data)},!0),o.$on("create",function(e,n){console.log("create: "+n.id)}),o.$on("destroy",function(e,n){console.log("destroy: "+n.id)})}]}})}(),function(){angular.module("xos.uiComponents").directive("xosTable",function(){return{restrict:"E",scope:{data:"=",config:"="},template:'\n <div ng-show="vm.data.length > 0">\n <div class="row" ng-if="vm.config.filter == \'fulltext\'">\n <div class="col-xs-12">\n <input\n class="form-control"\n placeholder="Type to search.."\n type="text"\n ng-model="vm.query"/>\n </div>\n </div>\n <table ng-class="vm.classes" ng-hide="vm.data.length == 0">\n <thead>\n <tr>\n <th ng-repeat="col in vm.columns">\n {{col.label}}\n <span ng-if="vm.config.order">\n <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = false">\n <i class="glyphicon glyphicon-chevron-up"></i>\n </a>\n <a href="" ng-click="vm.orderBy = col.prop; vm.reverse = true">\n <i class="glyphicon glyphicon-chevron-down"></i>\n </a>\n </span>\n </th>\n <th ng-if="vm.config.actions">Actions:</th>\n </tr>\n </thead>\n <tbody ng-if="vm.config.filter == \'field\'">\n <tr>\n <td ng-repeat="col in vm.columns">\n <input\n ng-if="col.type !== \'boolean\'"\n class="form-control"\n placeholder="Type to search by {{col.label}}"\n type="text"\n ng-model="vm.query[col.prop]"/>\n <select\n ng-if="col.type === \'boolean\'"\n class="form-control"\n ng-model="vm.query[col.prop]">\n <option value="">-</option>\n <option value="true">True</option>\n <option value="false">False</option>\n </select>\n </td>\n <td ng-if="vm.config.actions"></td>\n </tr>\n </tbody>\n <tbody>\n <tr ng-repeat="item in vm.data | filter:vm.query | orderBy:vm.orderBy:vm.reverse | pagination:vm.currentPage * vm.config.pagination.pageSize | limitTo: (vm.config.pagination.pageSize || vm.data.length) track by $index">\n <td ng-repeat="col in vm.columns" link-wrapper>\n <span ng-if="!col.type">{{item[col.prop]}}</span>\n <span ng-if="col.type === \'boolean\'">\n <i class="glyphicon"\n ng-class="{\'glyphicon-ok\': item[col.prop], \'glyphicon-remove\': !item[col.prop]}">\n </i>\n </span>\n <span ng-if="col.type === \'date\'">\n {{item[col.prop] | date:\'H:mm MMM d, yyyy\'}}\n </span>\n <span ng-if="col.type === \'array\'">\n {{item[col.prop] | arrayToList}}\n </span>\n <span ng-if="col.type === \'object\'">\n <dl class="dl-horizontal">\n <span ng-repeat="(k,v) in item[col.prop]">\n <dt>{{k}}</dt>\n <dd>{{v}}</dd>\n </span>\n </dl>\n </span>\n <span ng-if="col.type === \'custom\'">\n {{col.formatter(item)}}\n </span>\n <span ng-if="col.type === \'icon\'">\n <i class="glyphicon glyphicon-{{col.formatter(item)}}">\n </i>\n </span>\n </td>\n <td ng-if="vm.config.actions">\n <a href=""\n ng-repeat="action in vm.config.actions"\n ng-click="action.cb(item)"\n title="{{action.label}}">\n <i\n class="glyphicon glyphicon-{{action.icon}}"\n style="color: {{action.color}};"></i>\n </a>\n </td>\n </tr>\n </tbody>\n </table>\n <xos-pagination\n ng-if="vm.config.pagination"\n page-size="vm.config.pagination.pageSize"\n total-elements="vm.data.length"\n change="vm.goToPage">\n </xos-pagination>\n </div>\n <div ng-show="vm.data.length == 0 || !vm.data">\n <xos-alert config="{type: \'info\'}">\n No data to show.\n </xos-alert>\n </div>\n ',bindToController:!0,controllerAs:"vm",controller:["_",function(e){var n=this;if(!this.config)throw new Error('[xosTable] Please provide a configuration via the "config" attribute');if(!this.config.columns)throw new Error("[xosTable] Please provide a columns list in the configuration");this.config.order&&angular.isObject(this.config.order)&&(this.reverse=this.config.order.reverse||!1,this.orderBy=this.config.order.field||"id");var o=e.filter(this.config.columns,{type:"custom"});angular.isArray(o)&&o.length>0&&e.forEach(o,function(e){if(!e.formatter||!angular.isFunction(e.formatter))throw new Error("[xosTable] You have provided a custom field type, a formatter function should provided too.")});var t=e.filter(this.config.columns,{type:"icon"});angular.isArray(t)&&t.length>0&&e.forEach(t,function(e){if(!e.formatter||!angular.isFunction(e.formatter))throw new Error("[xosTable] You have provided an icon field type, a formatter function should provided too.")});var i=e.filter(this.config.columns,function(e){return angular.isDefined(e.link)});angular.isArray(i)&&i.length>0&&e.forEach(i,function(e){if(!angular.isFunction(e.link))throw new Error("[xosTable] The link property should be a function.")}),this.columns=this.config.columns,this.classes=this.config.classes||"table table-striped table-bordered",this.config.actions,this.config.pagination&&(this.currentPage=0,this.goToPage=function(e){n.currentPage=e})}]}}).filter("arrayToList",function(){return function(e){return angular.isArray(e)?e.join(", "):e}}).directive("linkWrapper",function(){return{restrict:"A",transclude:!0,template:'\n <a ng-if="col.link" href="{{col.link(item)}}">\n <div ng-transclude></div>\n </a>\n <div ng-transclude ng-if="!col.link"></div>\n '}})}(),function(){angular.module("xos.uiComponents").directive("xosPagination",function(){return{restrict:"E",scope:{pageSize:"=",totalElements:"=",change:"="},template:'\n <div class="row" ng-if="vm.pageList.length > 1">\n <div class="col-xs-12 text-center">\n <ul class="pagination">\n <li\n ng-click="vm.goToPage(vm.currentPage - 1)"\n ng-class="{disabled: vm.currentPage == 0}">\n <a href="" aria-label="Previous">\n <span aria-hidden="true">«</span>\n </a>\n </li>\n <li ng-repeat="i in vm.pageList" ng-class="{active: i === vm.currentPage}">\n <a href="" ng-click="vm.goToPage(i)">{{i + 1}}</a>\n </li>\n <li\n ng-click="vm.goToPage(vm.currentPage + 1)"\n ng-class="{disabled: vm.currentPage == vm.pages - 1}">\n <a href="" aria-label="Next">\n <span aria-hidden="true">»</span>\n </a>\n </li>\n </ul>\n </div>\n </div>\n ',bindToController:!0,controllerAs:"vm",controller:["$scope",function(e){var n=this;this.currentPage=0,this.goToPage=function(e){0>e||e===n.pages||(n.currentPage=e,n.change(e))},this.createPages=function(e){for(var n=[],o=0;e>o;o++)n.push(o);return n},e.$watch(function(){return n.totalElements},function(){n.totalElements&&(n.pages=Math.ceil(n.totalElements/n.pageSize),n.pageList=n.createPages(n.pages))})}]}}).filter("pagination",function(){return function(e,n){return e&&angular.isArray(e)?(n=parseInt(n,10),e.slice(n)):e}})}(),function(){angular.module("xos.uiComponents").directive("xosForm",function(){return{restrict:"E",scope:{config:"=",ngModel:"="},template:'\n <ng-form name="vm.{{vm.config.formName || \'form\'}}">\n <div class="form-group" ng-repeat="(name, field) in vm.formField">\n <xos-field name="name" field="field" ng-model="vm.ngModel[name]"></xos-field>\n <xos-validation field="vm[vm.config.formName || \'form\'][name]" form="vm[vm.config.formName || \'form\']"></xos-validation>\n </div>\n <div class="form-group" ng-if="vm.config.actions">\n <button role="button" href=""\n ng-repeat="action in vm.config.actions"\n ng-click="action.cb(vm.ngModel)"\n class="btn btn-{{action.class}}"\n title="{{action.label}}">\n <i class="glyphicon glyphicon-{{action.icon}}"></i>\n {{action.label}}\n </button>\n </div>\n </ng-form>\n ',bindToController:!0,controllerAs:"vm",controller:["$scope","$log","_","XosFormHelpers",function(e,n,o,t){var i=this;if(!this.config)throw new Error('[xosForm] Please provide a configuration via the "config" attribute');if(!this.config.actions)throw new Error("[xosForm] Please provide an action list in the configuration");this.excludedField=["id","validators","created","updated","deleted","backend_status"],this.config&&this.config.exclude&&(this.excludedField=this.excludedField.concat(this.config.exclude)),this.formField=[],e.$watch(function(){return i.ngModel},function(e){if(i.formField={},e){var n=o.difference(Object.keys(e),i.excludedField),r=t.parseModelField(n);i.formField=t.buildFormStructure(r,i.config.fields,e)}})}]}})}(),function(){angular.module("xos.uiComponents").directive("xosField",["RecursionHelper",function(e){return{restrict:"E",scope:{name:"=",field:"=",ngModel:"="},template:'\n <label ng-if="vm.field.type !== \'object\'">{{vm.field.label}}</label>\n <input\n ng-if="vm.field.type !== \'boolean\' && vm.field.type !== \'object\' && vm.field.type !== \'select\'"\n type="{{vm.field.type}}"\n name="{{vm.name}}"\n class="form-control"\n ng-model="vm.ngModel"\n ng-minlength="vm.field.validators.minlength || 0"\n ng-maxlength="vm.field.validators.maxlength || 2000"\n ng-required="vm.field.validators.required || false" />\n <select class="form-control" ng-if ="vm.field.type === \'select\'"\n name = "{{vm.name}}"\n ng-options="item.id as item.label for item in vm.field.options track by item.id"\n ng-model="vm.ngModel"\n ng-required="vm.field.validators.required || false">\n </select>\n <span class="boolean-field" ng-if="vm.field.type === \'boolean\'">\n <button\n class="btn btn-success"\n ng-show="vm.ngModel"\n ng-click="vm.ngModel = false">\n <i class="glyphicon glyphicon-ok"></i>\n </button>\n <button\n class="btn btn-danger"\n ng-show="!vm.ngModel"\n ng-click="vm.ngModel = true">\n <i class="glyphicon glyphicon-remove"></i>\n </button>\n </span>\n <div\n class="panel panel-default object-field"\n ng-if="vm.field.type == \'object\' && (!vm.isEmptyObject(vm.ngModel) || !vm.isEmptyObject(vm.field.properties))"\n >\n <div class="panel-heading">{{vm.field.label}}</div>\n <div class="panel-body">\n <div ng-if="!vm.field.properties" ng-repeat="(k, v) in vm.ngModel">\n <xos-field\n name="k"\n field="{label: vm.formatLabel(k), type: vm.getType(v)}"\n ng-model="v">\n </xos-field>\n </div>\n <div ng-if="vm.field.properties" ng-repeat="(k, v) in vm.field.properties">\n <xos-field\n name="k"\n field="{\n label: v.label || vm.formatLabel(k),\n type: v.type,\n validators: v.validators\n }"\n ng-model="vm.ngModel[k]">\n </xos-field>\n </div>\n </div>\n </div>\n ',bindToController:!0,controllerAs:"vm",compile:function(n){return e.compile(n)},controller:["$attrs","XosFormHelpers","LabelFormatter",function(e,n,o){if(!this.name)throw new Error("[xosField] Please provide a field name");if(!this.field)throw new Error("[xosField] Please provide a field definition");if(!this.field.type)throw new Error("[xosField] Please provide a type in the field definition");if(!e.ngModel)throw new Error("[xosField] Please provide an ng-model");this.getType=n._getFieldFormat,this.formatLabel=o.format,this.isEmptyObject=function(e){return e?0===Object.keys(e).length:!0}}]}}])}(),function(){angular.module("xos.uiComponents").directive("xosAlert",function(){return{restrict:"E",scope:{config:"=",show:"=?"},template:'\n <div ng-cloak class="alert alert-{{vm.config.type}}" ng-hide="!vm.show">\n <button type="button" class="close" ng-if="vm.config.closeBtn" ng-click="vm.dismiss()">\n <span aria-hidden="true">×</span>\n </button>\n <p ng-transclude></p>\n </div>\n ',transclude:!0,bindToController:!0,controllerAs:"vm",controller:["$timeout",function(e){var n=this;if(!this.config)throw new Error('[xosAlert] Please provide a configuration via the "config" attribute');this.show=this.show!==!1,this.dismiss=function(){n.show=!1},this.config.autoHide&&!function(){var o=e(function(){n.dismiss(),e.cancel(o)},n.config.autoHide)}()}]}})}(),function(){function e(){var e=function(e){return e.split("_").join(" ").trim()},n=function(e){return e.split(/(?=[A-Z])/).map(function(e){return e.toLowerCase()}).join(" ")},o=function(e){return e.slice(0,1).toUpperCase()+e.slice(1)},t=function(t){return t=e(t),t=n(t),t=o(t).replace(/\s\s+/g," ")+":",t.replace("::",":")};return{_formatByUnderscore:e,_formatByUppercase:n,_capitalize:o,format:t}}angular.module("xos.uiComponents").factory("LabelFormatter",e)}();var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e};!function(){angular.module("xos.uiComponents").service("XosFormHelpers",["_","LabelFormatter",function(e,n){var o=this;this._isEmail=function(e){var n=/(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/;return n.test(e)},this._getFieldFormat=function(n){return angular.isArray(n)?"array":e.isDate(n)||!Number.isNaN(Date.parse(n))&&new Date(n).getTime()>6311808e5?"date":"boolean"==typeof n?"boolean":o._isEmail(n)?"email":"string"==typeof n||null===n?"text":"undefined"==typeof n?"undefined":_typeof(n)},this.buildFormStructure=function(t,i,r){return t=angular.extend(t,i),i=i||{},e.reduce(Object.keys(t),function(e,t){return e[t]={label:i[t]&&i[t].label?i[t].label+":":n.format(t),type:i[t]&&i[t].type?i[t].type:o._getFieldFormat(r[t]),validators:i[t]&&i[t].validators?i[t].validators:{},hint:i[t]&&i[t].hint?i[t].hint:""},i[t]&&i[t].options&&(e[t].options=i[t].options),i[t]&&i[t].properties&&(e[t].properties=i[t].properties),"date"===e[t].type&&(r[t]=new Date(r[t])),"number"===e[t].type&&(r[t]=parseInt(r[t],10)),e},{})},this.parseModelField=function(n){return e.reduce(n,function(e,n){return e[n]={},e},{})}}])}(),function(){function e(e,n,o){e.interceptors.push("SetCSRFToken"),n.startSymbol("{$"),n.endSymbol("$}"),o.defaults.stripTrailingSlashes=!1}e.$inject=["$httpProvider","$interpolateProvider","$resourceProvider"],angular.module("bugSnag",[]).factory("$exceptionHandler",function(){return function(e,n){window.Bugsnag?Bugsnag.notifyException(e,{diagnostics:{cause:n}}):console.error(e,n,e.stack)}}),angular.module("xos.helpers",["ngCookies","ngResource","ngAnimate","bugSnag","xos.uiComponents"]).config(e).factory("_",["$window",function(e){return e._}])}(),function(){angular.module("xos.helpers").service("vSG-Collection",["$resource",function(e){return e("/api/service/vsg/")}])}(),function(){angular.module("xos.helpers").service("vOLT-Collection",["$resource",function(e){return e("/api/tenant/cord/volt/:volt_id/",{volt_id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Login",["$resource",function(e){return e("/api/utility/login/")}]).service("Logout",["$resource",function(e){return e("/api/utility/logout/")}])}(),function(){angular.module("xos.helpers").service("Users",["$resource",function(e){return e("/api/core/users/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Truckroll",["$resource",function(e){return e("/api/tenant/truckroll/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Tenants",["$resource",function(e){return e("/api/core/tenants/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Subscribers",["$resource",function(e){return e("/api/tenant/cord/subscriber/:id/",{id:"@id"},{update:{method:"PUT"},"View-a-Subscriber-Features-Detail":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/"},"Read-Subscriber-uplink_speed":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/uplink_speed/"},"Update-Subscriber-uplink_speed":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/uplink_speed/"},"Read-Subscriber-downlink_speed":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/downlink_speed/"},"Update-Subscriber-downlink_speed":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/downlink_speed/"},"Read-Subscriber-cdn":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/cdn/"},"Update-Subscriber-cdn":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/cdn/"},"Read-Subscriber-uverse":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/uverse/"},"Update-Subscriber-uverse":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/uverse/"},"Read-Subscriber-status":{method:"GET",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/status/"},"Update-Subscriber-status":{method:"PUT",isArray:!1,url:"/api/tenant/cord/subscriber/:id/features/status/"}})}])}(),function(){angular.module("xos.helpers").service("SlicesPlus",["$http","$q",function(e,n){this.query=function(o){var t=n.defer();return e.get("/api/utility/slicesplus/",{params:o}).then(function(e){t.resolve(e.data)})["catch"](function(e){t.reject(e.data)}),{$promise:t.promise}},this.get=function(o,t){var i=n.defer();return e.get("/api/utility/slicesplus/"+o,{params:t}).then(function(e){i.resolve(e.data)})["catch"](function(e){i.reject(e.data)}),{$promise:i.promise}}}])}(),function(){angular.module("xos.helpers").service("Slices",["$resource",function(e){return e("/api/core/slices/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Sites",["$resource",function(e){return e("/api/core/sites/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Services",["$resource",function(e){return e("/api/core/services/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("ONOS-Services-Collection",["$resource",function(e){return e("/api/service/onos/")}])}(),function(){angular.module("xos.helpers").service("ONOS-App-Collection",["$resource",function(e){return e("/api/tenant/onos/app/")}])}(),function(){angular.module("xos.helpers").service("Nodes",["$resource",function(e){return e("/api/core/nodes/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Networks",["$resource",function(e){return e("/api/core/networks/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Instances",["$resource",function(e){return e("/api/core/instances/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Flavors",["$resource",function(e){return e("/api/core/flavors/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("Example-Services-Collection",["$resource",function(e){return e("/api/service/exampleservice/")}])}(),function(){angular.module("xos.helpers").service("Deployments",["$resource",function(e){return e("/api/core/deployments/:id/",{id:"@id"},{update:{method:"PUT"}})}])}(),function(){angular.module("xos.helpers").service("XosUserPrefs",["$cookies",function(e){var n=this,o=e.get("xosUserPrefs")?JSON.parse(e.get("xosUserPrefs")):{};this.getAll=function(){return o=e.get("xosUserPrefs")?JSON.parse(e.get("xosUserPrefs")):{}},this.setAll=function(n){e.put("xosUserPrefs",JSON.stringify(n))},this.getSynchronizerNotificationStatus=function(){var e=arguments.length<=0||void 0===arguments[0]?!1:arguments[0];return e?n.getAll().synchronizers.notification[e]:n.getAll().synchronizers.notification},this.setSynchronizerNotificationStatus=function(){var e=arguments.length<=0||void 0===arguments[0]?!1:arguments[0],o=arguments[1];if(!e)throw new Error("[XosUserPrefs] When updating a synchronizer is mandatory to provide a name.");var t=n.getAll();t.synchronizers||(t.synchronizers={notification:{}}),t.synchronizers.notification[e]=o,n.setAll(t)}}])}(),function(){angular.module("xos.helpers").service("GraphService",["$q","Tenants","Services",function(e,n,o){var t=this;this.loadCoarseData=function(){var t=void 0,i=e.defer();return o.query().$promise.then(function(e){return t=e,n.query({kind:"coarse"}).$promise}).then(function(e){i.resolve({tenants:e,services:t})}),i.promise},this.getCoarseGraph=function(){return t.loadCoarseData().then(function(e){console.log(e)}),"ciao"}}])}(),function(){angular.module("xos.helpers").factory("Notification",function(){return window.Notification}).service("xosNotification",["$q","$log","Notification",function(e,n,o){var t=this;this.checkPermission=function(){var n=e.defer();return o.requestPermission().then(function(e){"granted"===e?n.resolve(e):n.reject(e)}),n.promise},this.sendNotification=function(e,t){var i=new o(e,t);i.onerror=function(e){n.error(e)}},this.notify=function(e,i){"Notification"in window?"granted"!==o.permission?t.checkPermission().then(function(){return t.sendNotification(e,i)}):"granted"===o.permission&&t.sendNotification(e,i):n.info("This browser does not support desktop notification")}}])}(),function(){function e(){return{request:function(e){return-1===e.url.indexOf(".html")&&(e.url+="?no_hyperlinks=1"),e}}}angular.module("xos.helpers").factory("NoHyperlinks",e)}(),angular.module("xos.helpers").config(["$provide",function(e){e.decorator("$log",["$delegate",function(e){var n=function(){return window.location.href.indexOf("debug=true")>=0},o=e.log,t=e.info,i=e.warn,r=e.error,s=e.debug,a=function(o){return function(){if(n()){var t=[].slice.call(arguments),i=new Date;t[0]="["+i.getHours()+":"+i.getMinutes()+":"+i.getSeconds()+"] "+t[0],"function"!=typeof e.reset||e.debug.logs instanceof Array||e.reset(),o.apply(null,t)}}};return e.info=a(t),e.log=a(o),e.warn=a(i),e.error=a(r),e.debug=a(s),e}])}]),function(){function e(){var e=function(e){return e.split("_").join(" ").trim()},n=function(e){return e.split(/(?=[A-Z])/).map(function(e){return e.toLowerCase()}).join(" ")},o=function(e){return e.slice(0,1).toUpperCase()+e.slice(1)},t=function(t){return t=e(t),t=n(t),t=o(t).replace(/\s\s+/g," ")+":",t.replace("::",":")};return{_formatByUnderscore:e,_formatByUppercase:n,_capitalize:o,format:t}}angular.module("xos.uiComponents").factory("LabelFormatter",e)}(),function(){function e(e){return{request:function(n){return"GET"!==n.method&&(n.headers["X-CSRFToken"]=e.get("xoscsrftoken")),n}}}e.$inject=["$cookies"],angular.module("xos.helpers").factory("SetCSRFToken",e)}();
\ No newline at end of file
diff --git a/xos/core/xoslib/static/js/xosSynchronizerNotifier.js b/xos/core/xoslib/static/js/xosSynchronizerNotifier.js
index 894f9ea..5d743c6 100644
--- a/xos/core/xoslib/static/js/xosSynchronizerNotifier.js
+++ b/xos/core/xoslib/static/js/xosSynchronizerNotifier.js
@@ -1 +1 @@
-"use strict";angular.module("xos.synchronizerNotifier",["ngResource","ngCookies","ui.router","xos.helpers"]).service("Diag",["$rootScope","$http","$q","$interval",function(n,t,s,e){var a=this,i=!1;this.getDiags=function(){var n=s.defer();return t.get("/api/core/diags").then(function(t){n.resolve(t.data)})["catch"](function(t){n.reject(t)}),n.promise},this.sendEvents=function(t){t.forEach(function(t){var s=JSON.parse(t.backend_register);s.last_run=new Date(1e3*s.last_run),s.last_duration=1e3*s.last_duration,s.last_synchronizer_start=new Date(1e3*s.last_synchronizer_start),s.last_syncrecord_start=s.last_syncrecord_start?new Date(1e3*s.last_syncrecord_start):null,n.$broadcast("diag",{name:t.name,updated:t.updated,info:s,status:a.getSyncStatus(s)})})},this.start=function(){return i=!0,a.getDiags().then(function(n){a.sendEvents(n)}),i},this.stop=function(){return i=!1},this.getSyncStatus=function(n){var t=new Date,s=9e5;return!(t-n.last_synchronizer_start>s&&t-n.last_syncrecord_start>s&&t-n.last_run>s)},e(function(){i&&a.getDiags().then(function(n){a.sendEvents(n)})},3e5)}]).directive("syncStatus",function(){return{restrict:"E",scope:{},bindToController:!0,controllerAs:"vm",templateUrl:"templates/sync-status.tpl.html",controller:["$log","$rootScope","Diag","xosNotification","XosUserPrefs",function(n,t,s,e,a){var i=this;s.start(),this.synchronizers={},this.showNoSync=!0,t.$on("diag",function(n,t){i.synchronizers[t.name]=t,t.status?a.setSynchronizerNotificationStatus(t.name,!1):(a.getSynchronizerNotificationStatus(t.name)||e.notify("CORD Synchronizer",{icon:"/static/cord-logo.png",body:"The "+t.name+" synchronizer has not performed actions in the last 15 minutes."}),a.setSynchronizerNotificationStatus(t.name,!0)),i.showNoSync=!1,0===Object.keys(i.synchronizers).length&&(i.showNoSync=!0)})}]}}),angular.element(document).ready(function(){angular.bootstrap("#xosSynchronizerNotifier",["xos.synchronizerNotifier"])}),angular.module("xos.synchronizerNotifier").run(["$templateCache",function(n){n.put("templates/sync-status.tpl.html",'<div class="sync-status-container">\n <div class="btn btn-default" ng-click="vm.showNotificationPanel = !vm.showNotificationPanel">\n <i class="glyphicon glyphicon-inbox"></i>\n </div>\n <div class="notification-panel panel panel-default" ng-show="vm.showNotificationPanel">\n <ul class="list-group" ng-show="!vm.showNoSync">\n <li class="list-group-item" ng-repeat="(syncName, syncStatus) in vm.synchronizers">\n <span class="badge" ng-class="{success: syncStatus.status, warning: !syncStatus.status}">\n <span ng-show="syncStatus.status"><i class="glyphicon glyphicon-ok"></i></span>\n <span ng-hide="syncStatus.status"><i class="glyphicon glyphicon-time"></i></span>\n </span>\n <b>{{syncName}}</b>\n <br/>\n <small><i>{{syncStatus.info.last_run | date:\'mediumTime\'}}</i></small>\n </li>\n </ul>\n <div class="alert alert-info" ng-show="vm.showNoSync">\n No syncronizers are running.\n </div>\n </div>\n</div>\n')}]),angular.module("xos.synchronizerNotifier").run(["$location",function(n){n.path("/")}]);
\ No newline at end of file
+"use strict";angular.module("xos.synchronizerNotifier",["ngResource","ngCookies","xos.helpers"]).run(["$rootScope",function(n){n.$on("$locationChangeStart",function(n){n.preventDefault()})}]).service("Diag",["$rootScope","$http","$q","$interval",function(n,t,s,e){var a=this,o=!1;this.getDiags=function(){var n=s.defer();return t.get("/api/core/diags").then(function(t){n.resolve(t.data)})["catch"](function(t){n.reject(t)}),n.promise},this.sendEvents=function(t){t.forEach(function(t){var s=JSON.parse(t.backend_register);s.last_run=new Date(1e3*s.last_run),s.last_duration=1e3*s.last_duration,s.last_synchronizer_start=new Date(1e3*s.last_synchronizer_start),s.last_syncrecord_start=s.last_syncrecord_start?new Date(1e3*s.last_syncrecord_start):null,n.$broadcast("diag",{name:t.name,updated:t.updated,info:s,status:a.getSyncStatus(s)})})},this.start=function(){return o=!0,a.getDiags().then(function(n){a.sendEvents(n)}),o},this.stop=function(){return o=!1},this.getSyncStatus=function(n){var t=new Date,s=9e5;return!(t-n.last_synchronizer_start>s&&t-n.last_syncrecord_start>s&&t-n.last_run>s)},e(function(){o&&a.getDiags().then(function(n){a.sendEvents(n)})},3e5)}]).directive("syncStatus",function(){return{restrict:"E",scope:{},bindToController:!0,controllerAs:"vm",templateUrl:"templates/sync-status.tpl.html",controller:["$log","$rootScope","Diag","xosNotification","XosUserPrefs",function(n,t,s,e,a){var o=this;s.start(),this.synchronizers={},this.showNoSync=!0,t.$on("diag",function(n,t){o.synchronizers[t.name]=t,t.status?a.setSynchronizerNotificationStatus(t.name,!1):(a.getSynchronizerNotificationStatus(t.name)||e.notify("CORD Synchronizer",{icon:"/static/cord-logo.png",body:"The "+t.name+" synchronizer has not performed actions in the last 15 minutes."}),a.setSynchronizerNotificationStatus(t.name,!0)),o.showNoSync=!1,0===Object.keys(o.synchronizers).length&&(o.showNoSync=!0)})}]}}),angular.element(document).ready(function(){angular.bootstrap("#xosSynchronizerNotifier",["xos.synchronizerNotifier"])}),angular.module("xos.synchronizerNotifier").run(["$templateCache",function(n){n.put("templates/sync-status.tpl.html",'<div class="sync-status-container">\n <div class="btn btn-default" ng-click="vm.showNotificationPanel = !vm.showNotificationPanel">\n <i class="glyphicon glyphicon-inbox"></i>\n </div>\n <div class="notification-panel panel panel-default" ng-show="vm.showNotificationPanel">\n <ul class="list-group" ng-show="!vm.showNoSync">\n <li class="list-group-item" ng-repeat="(syncName, syncStatus) in vm.synchronizers">\n <span class="badge" ng-class="{success: syncStatus.status, warning: !syncStatus.status}">\n <span ng-show="syncStatus.status"><i class="glyphicon glyphicon-ok"></i></span>\n <span ng-hide="syncStatus.status"><i class="glyphicon glyphicon-time"></i></span>\n </span>\n <b>{{syncName}}</b>\n <br/>\n <small><i>{{syncStatus.info.last_run | date:\'mediumTime\'}}</i></small>\n </li>\n </ul>\n <div class="alert alert-info" ng-show="vm.showNoSync">\n No syncronizers are running.\n </div>\n </div>\n</div>\n')}]),angular.module("xos.synchronizerNotifier").run(["$location",function(n){n.path("/")}]);
\ No newline at end of file
diff --git a/xos/onboard/README.md b/xos/onboard/README.md
new file mode 100644
index 0000000..2030708
--- /dev/null
+++ b/xos/onboard/README.md
@@ -0,0 +1,3 @@
+This directory is a temporary placeholder for services that can be on-boarded.
+
+Once we move to Gerritt and service-per-repo, this directory will be removed.
diff --git a/xos/services/exampleservice/admin.py b/xos/onboard/exampleservice/admin.py
similarity index 100%
rename from xos/services/exampleservice/admin.py
rename to xos/onboard/exampleservice/admin.py
diff --git a/xos/api/service/exampleservice.py b/xos/onboard/exampleservice/api/service/exampleservice.py
similarity index 100%
rename from xos/api/service/exampleservice.py
rename to xos/onboard/exampleservice/api/service/exampleservice.py
diff --git a/xos/api/tenant/exampletenant.py b/xos/onboard/exampleservice/api/tenant/exampletenant.py
similarity index 100%
rename from xos/api/tenant/exampletenant.py
rename to xos/onboard/exampleservice/api/tenant/exampletenant.py
diff --git a/xos/onboard/exampleservice/exampleservice-onboard-longform.yaml b/xos/onboard/exampleservice/exampleservice-onboard-longform.yaml
new file mode 100644
index 0000000..0eddd51
--- /dev/null
+++ b/xos/onboard/exampleservice/exampleservice-onboard-longform.yaml
@@ -0,0 +1,57 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Onboard the exampleservice
+
+imports:
+ - custom_types/xos.yaml
+
+topology_template:
+ node_templates:
+ exampleservice:
+ type: tosca.nodes.ServiceController
+ properties:
+ base_url: file:/opt/xos/onboard/exampleservice/
+
+ exampleservice_models:
+ type: tosca.nodes.ServiceControllerResource
+ properties:
+ kind: models
+ format: python
+ url: models.py
+ requirements:
+ - controller:
+ node: exampleservice
+ relationship: tosca.relationships.UsedByController
+
+ exampleservice_admin:
+ type: tosca.nodes.ServiceControllerResource
+ properties:
+ kind: admin
+ format: python
+ url: admin.py
+ requirements:
+ - controller:
+ node: exampleservice
+ relationship: tosca.relationships.UsedByController
+
+ exampleservice_synchronizer:
+ type: tosca.nodes.ServiceControllerResource
+ properties:
+ kind: synchronizer
+ format: manifest
+ url: synchronizer/manifest
+ requirements:
+ - controller:
+ node: exampleservice
+ relationship: tosca.relationships.UsedByController
+
+ exampleservice_tosca_types:
+ type: tosca.nodes.ServiceControllerResource
+ properties:
+ kind: tosca_custom_types
+ format: yaml
+ url: exampleservice.yaml
+ requirements:
+ - controller:
+ node: exampleservice
+ relationship: tosca.relationships.UsedByController
diff --git a/xos/onboard/exampleservice/exampleservice-onboard.yaml b/xos/onboard/exampleservice/exampleservice-onboard.yaml
new file mode 100644
index 0000000..91dbb2e
--- /dev/null
+++ b/xos/onboard/exampleservice/exampleservice-onboard.yaml
@@ -0,0 +1,24 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Onboard the exampleservice
+
+imports:
+ - custom_types/xos.yaml
+
+topology_template:
+ node_templates:
+ exampleservice:
+ type: tosca.nodes.ServiceController
+ properties:
+ base_url: file:///opt/xos/onboard/exampleservice/
+ # The following will concatenate with base_url automatically, if
+ # base_url is non-null.
+ models: models.py
+ admin: admin.py
+ synchronizer: synchronizer/manifest
+ tosca_custom_types: exampleservice.yaml
+ rest_service: api/service/exampleservice.py
+ rest_tenant: api/tenant/exampletenant.py
+ private_key: file:///opt/xos/key_import/exampleservice_rsa
+ public_key: file:///opt/xos/key_import/exampleservice_rsa.pub
+
diff --git a/xos/tosca/custom_types/exampleservice.m4 b/xos/onboard/exampleservice/exampleservice.m4
similarity index 100%
rename from xos/tosca/custom_types/exampleservice.m4
rename to xos/onboard/exampleservice/exampleservice.m4
diff --git a/xos/tosca/custom_types/exampleservice.yaml b/xos/onboard/exampleservice/exampleservice.yaml
similarity index 100%
rename from xos/tosca/custom_types/exampleservice.yaml
rename to xos/onboard/exampleservice/exampleservice.yaml
diff --git a/xos/onboard/exampleservice/macros.m4 b/xos/onboard/exampleservice/macros.m4
new file mode 100644
index 0000000..1f48f10
--- /dev/null
+++ b/xos/onboard/exampleservice/macros.m4
@@ -0,0 +1,84 @@
+# Note: Tosca derived_from isn't working the way I think it should, it's not
+# inheriting from the parent template. Until we get that figured out, use
+# m4 macros do our inheritance
+
+define(xos_base_props,
+ no-delete:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to delete this object
+ no-create:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to create this object
+ no-update:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to update this object
+ replaces:
+ type: string
+ required: false
+ descrption: Replaces/renames this object)
+# Service
+define(xos_base_service_caps,
+ scalable:
+ type: tosca.capabilities.Scalable
+ service:
+ type: tosca.capabilities.xos.Service)
+define(xos_base_service_props,
+ kind:
+ type: string
+ default: generic
+ description: Type of service.
+ view_url:
+ type: string
+ required: false
+ description: URL to follow when icon is clicked in the Service Directory.
+ icon_url:
+ type: string
+ required: false
+ description: ICON to display in the Service Directory.
+ enabled:
+ type: boolean
+ default: true
+ published:
+ type: boolean
+ default: true
+ description: If True then display this Service in the Service Directory.
+ public_key:
+ type: string
+ required: false
+ description: Public key to install into Instances to allows Services to SSH into them.
+ private_key_fn:
+ type: string
+ required: false
+ description: Location of private key file
+ versionNumber:
+ type: string
+ required: false
+ description: Version number of Service.)
+# Subscriber
+define(xos_base_subscriber_caps,
+ subscriber:
+ type: tosca.capabilities.xos.Subscriber)
+define(xos_base_subscriber_props,
+ kind:
+ type: string
+ default: generic
+ description: Kind of subscriber
+ service_specific_id:
+ type: string
+ required: false
+ description: Service specific ID opaque to XOS but meaningful to service)
+define(xos_base_tenant_props,
+ 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)
+
+# end m4 macros
+
diff --git a/xos/onboard/exampleservice/make_synchronizer_manifest.sh b/xos/onboard/exampleservice/make_synchronizer_manifest.sh
new file mode 100644
index 0000000..4058982
--- /dev/null
+++ b/xos/onboard/exampleservice/make_synchronizer_manifest.sh
@@ -0,0 +1,2 @@
+#! /bin/bash
+find synchronizer -type f | cut -b 14- > synchronizer/manifest
diff --git a/xos/services/exampleservice/models.py b/xos/onboard/exampleservice/models.py
similarity index 100%
rename from xos/services/exampleservice/models.py
rename to xos/onboard/exampleservice/models.py
diff --git a/xos/synchronizers/exampleservice/exampleservice-synchronizer.py b/xos/onboard/exampleservice/synchronizer/exampleservice-synchronizer.py
similarity index 100%
copy from xos/synchronizers/exampleservice/exampleservice-synchronizer.py
copy to xos/onboard/exampleservice/synchronizer/exampleservice-synchronizer.py
diff --git a/xos/synchronizers/exampleservice/exampleservice_config b/xos/onboard/exampleservice/synchronizer/exampleservice_config
similarity index 100%
copy from xos/synchronizers/exampleservice/exampleservice_config
copy to xos/onboard/exampleservice/synchronizer/exampleservice_config
diff --git a/xos/onboard/exampleservice/synchronizer/manifest b/xos/onboard/exampleservice/synchronizer/manifest
new file mode 100644
index 0000000..8f43610
--- /dev/null
+++ b/xos/onboard/exampleservice/synchronizer/manifest
@@ -0,0 +1,10 @@
+manifest
+steps/sync_exampletenant.py
+steps/roles/install_apache/tasks/main.yml
+steps/roles/create_index/templates/index.html.j2
+steps/roles/create_index/tasks/main.yml
+steps/exampletenant_playbook.yaml
+exampleservice-synchronizer.py
+model-deps
+run.sh
+exampleservice_config
diff --git a/xos/synchronizers/exampleservice/model-deps b/xos/onboard/exampleservice/synchronizer/model-deps
similarity index 100%
copy from xos/synchronizers/exampleservice/model-deps
copy to xos/onboard/exampleservice/synchronizer/model-deps
diff --git a/xos/onboard/exampleservice/synchronizer/run.sh b/xos/onboard/exampleservice/synchronizer/run.sh
new file mode 100755
index 0000000..e6da8f6
--- /dev/null
+++ b/xos/onboard/exampleservice/synchronizer/run.sh
@@ -0,0 +1,2 @@
+export XOS_DIR=/opt/xos
+python exampleservice-synchronizer.py -C $XOS_DIR/synchronizers/exampleservice/exampleservice_config
diff --git a/xos/synchronizers/exampleservice/steps/exampletenant_playbook.yaml b/xos/onboard/exampleservice/synchronizer/steps/exampletenant_playbook.yaml
similarity index 100%
copy from xos/synchronizers/exampleservice/steps/exampletenant_playbook.yaml
copy to xos/onboard/exampleservice/synchronizer/steps/exampletenant_playbook.yaml
diff --git a/xos/synchronizers/exampleservice/steps/roles/create_index/tasks/main.yml b/xos/onboard/exampleservice/synchronizer/steps/roles/create_index/tasks/main.yml
similarity index 100%
copy from xos/synchronizers/exampleservice/steps/roles/create_index/tasks/main.yml
copy to xos/onboard/exampleservice/synchronizer/steps/roles/create_index/tasks/main.yml
diff --git a/xos/synchronizers/exampleservice/steps/roles/create_index/templates/index.html.j2 b/xos/onboard/exampleservice/synchronizer/steps/roles/create_index/templates/index.html.j2
similarity index 100%
copy from xos/synchronizers/exampleservice/steps/roles/create_index/templates/index.html.j2
copy to xos/onboard/exampleservice/synchronizer/steps/roles/create_index/templates/index.html.j2
diff --git a/xos/synchronizers/exampleservice/steps/roles/install_apache/tasks/main.yml b/xos/onboard/exampleservice/synchronizer/steps/roles/install_apache/tasks/main.yml
similarity index 100%
copy from xos/synchronizers/exampleservice/steps/roles/install_apache/tasks/main.yml
copy to xos/onboard/exampleservice/synchronizer/steps/roles/install_apache/tasks/main.yml
diff --git a/xos/synchronizers/exampleservice/steps/sync_exampletenant.py b/xos/onboard/exampleservice/synchronizer/steps/sync_exampletenant.py
similarity index 100%
copy from xos/synchronizers/exampleservice/steps/sync_exampletenant.py
copy to xos/onboard/exampleservice/synchronizer/steps/sync_exampletenant.py
diff --git a/xos/services/exampleservice/README.md b/xos/services/exampleservice/README.md
deleted file mode 100644
index ce6210d..0000000
--- a/xos/services/exampleservice/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# ExampleService
-
-This is an example XOS service, specifically the Django Model and Admin.
-
-The Synchronizer corresponding to this service can be found in `../../synchronizers/exampleservice`.
-
-Documentation for this is located here: [XOS Guide : DevGuide : ExampleService](http://guide.xosproject.org/devguide/exampleservice/).
-
diff --git a/xos/services/mcord/admin.py b/xos/services/mcord/admin.py
index b496ef7..ee19c7e 100644
--- a/xos/services/mcord/admin.py
+++ b/xos/services/mcord/admin.py
@@ -4,7 +4,7 @@
from core.models import User
from django import forms
from django.contrib import admin
-from services.mcord.models import MCORDService, VBBUComponent, MCORD_KIND
+from services.mcord.models import MCORDService, VBBUComponent, VPGWCComponent, MCORD_KIND
# The class to provide an admin interface on the web for the service.
# We do only configuration here and don't change any logic because the logic
@@ -114,6 +114,55 @@
class Meta:
model = VBBUComponent
+# Class to represent the form to add and edit tenants.
+# We need to define this instead of just using an admin like we did for the
+# service because tenants vary more than services and there isn't a common form.
+# This allows us to change the python behavior for the admin form to save extra
+# fields and control defaults.
+class VPGWCComponentForm(forms.ModelForm):
+ # Defines a field for the creator of this service. It is a dropdown which
+ # is populated with all of the users.
+ creator = forms.ModelChoiceField(queryset=User.objects.all())
+ # Defines a text field for the display message, it is not required.
+ display_message = forms.CharField(required=False)
+
+ def __init__(self, *args, **kwargs):
+ super(VPGWCComponentForm, self).__init__(*args, **kwargs)
+ # Set the kind field to readonly
+ self.fields['kind'].widget.attrs['readonly'] = True
+ # Define the logic for obtaining the objects for the provider_service
+ # dropdown of the tenant form.
+ self.fields[
+ 'provider_service'].queryset = MCORDService.get_service_objects().all()
+ # Set the initial kind to HELLO_WORLD_KIND for this tenant.
+ self.fields['kind'].initial = MCORD_KIND
+ # If there is an instance of this model then we can set the initial
+ # form values to the existing values.
+ if self.instance:
+ self.fields['creator'].initial = self.instance.creator
+ self.fields[
+ 'display_message'].initial = self.instance.display_message
+
+ # If there is not an instance then we need to set initial values.
+ if (not self.instance) or (not self.instance.pk):
+ self.fields['creator'].initial = get_request().user
+ if MCORDService.get_service_objects().exists():
+ self.fields["provider_service"].initial = MCORDService.get_service_objects().all()[0]
+
+ # This function describes what happens when the save button is pressed on
+ # the tenant form. In this case we set the values for the instance that were
+ # entered.
+ def save(self, commit=True):
+ self.instance.creator = self.cleaned_data.get("creator")
+ self.instance.display_message = self.cleaned_data.get(
+ "display_message")
+ return super(VPGWCComponentForm, self).save(commit=commit)
+
+ class Meta:
+ model = VPGWCComponent
+
+
+
# Define the admin form for the tenant. This uses a similar structure as the
# service but uses HelloWorldTenantCompleteForm to change the python behavior.
@@ -136,6 +185,28 @@
def queryset(self, request):
return VBBUComponent.get_tenant_objects_by_user(request.user)
+
+# Define the admin form for the tenant. This uses a similar structure as the
+# service but uses HelloWorldTenantCompleteForm to change the python behavior.
+class VPGWCComponentAdmin(ReadOnlyAwareAdmin):
+ verbose_name = "vPGWC Component"
+ verbose_name_plural = "vPGWC Components"
+ list_display = ('id', 'backend_status_icon', 'instance', 'display_message')
+ list_display_links = ('backend_status_icon', 'instance', 'display_message',
+ 'id')
+ fieldsets = [(None, {'fields': ['backend_status_text', 'kind',
+ 'provider_service', 'instance', 'creator',
+ 'display_message'],
+ 'classes': ['suit-tab suit-tab-general']})]
+ readonly_fields = ('backend_status_text', 'instance',)
+ form = VPGWCComponentForm
+
+ suit_form_tabs = (('general', 'Details'),)
+
+ def queryset(self, request):
+ return VPGWCComponent.get_tenant_objects_by_user(request.user)
+
# Associate the admin forms with the models.
admin.site.register(MCORDService, MCORDServiceAdmin)
admin.site.register(VBBUComponent, VBBUComponentAdmin)
+admin.site.register(VPGWCComponent, VPGWCComponentAdmin)
diff --git a/xos/services/mcord/models.py b/xos/services/mcord/models.py
index 1b2bd65..6d070d8 100644
--- a/xos/services/mcord/models.py
+++ b/xos/services/mcord/models.py
@@ -18,8 +18,9 @@
MCORD_USE_VTN = getattr(Config(), "networking_use_vtn", False)
VBBU_KIND = "RAN"
VSGW_KIND = "vSGW"
-VPGW_KIND = "vPGW"
-net_types = ("s1u", "s1mme", "rru")
+VPGWC_KIND = "RAN"
+vbbu_net_types = ("s1u", "s1mme", "rru")
+vpgwc_net_types = ("s5s8")
# The class to represent the service. Most of the service logic is given for us
# in the Service class but, we have some configuration that is specific for
# this example.
@@ -91,7 +92,7 @@
with transaction.atomic():
super(VBBUComponent, self).save_instance(instance)
if instance.isolation in ["vm"]:
- for ntype in net_types:
+ for ntype in vbbu_net_types:
lan_network = self.get_lan_network(instance, ntype)
port = self.find_or_make_port(instance,lan_network)
if (ntype == "s1u"):
@@ -271,6 +272,193 @@
def rru_mac(self):
return self.addresses.get("rru", (None, None))[1]
+
+# This is the class to represent the tenant. Most of the logic is given to use
+# in TenantWithContainer, however there is some configuration and logic that
+# we need to define for this example.
+class VPGWCComponent(TenantWithContainer):
+
+ class Meta:
+ # Same as a above, HelloWorldTenantComplete is represented as a
+ # TenantWithContainer, but we change the python behavior.
+ proxy = True
+ verbose_name = "VPGWC MCORD Service Component"
+
+ # The kind of the service is used on forms to differentiate this service
+ # from the other services.
+ KIND = VPGWC_KIND
+
+ # Ansible requires that the sync_attributes field contain nat_ip and nat_mac
+ # these will be used to determine where to SSH to for ansible.
+ # Getters must be defined for every attribute specified here.
+ sync_attributes = ("s5s8_pgw_ip", "s5s8_pgw_mac")
+
+ # default_attributes is used cleanly indicate what the default values for
+ # the fields are.
+ default_attributes = {"display_message": "New vPGWC Component", "s5s8_pgw_tag": "300"}
+ def __init__(self, *args, **kwargs):
+ mcord_services = MCORDService.get_service_objects().all()
+ # When the tenant is created the default service in the form is set
+ # to be the first created HelloWorldServiceComplete
+ if mcord_services:
+ self._meta.get_field(
+ "provider_service").default = mcord_services[0].id
+ super(VPGWCComponent, self).__init__(*args, **kwargs)
+
+ def can_update(self, user):
+ #Allow creation of this model instances for non-admin users also
+ return True
+
+ def save(self, *args, **kwargs):
+ if not self.creator:
+ if not getattr(self, "caller", None):
+ # caller must be set when creating a monitoring channel since it creates a slice
+ raise XOSProgrammingError("ServiceComponents's self.caller was not set")
+ self.creator = self.caller
+ if not self.creator:
+ raise XOSProgrammingError("ServiceComponents's self.creator was not set")
+
+ super(VPGWCComponent, self).save(*args, **kwargs)
+ # This call needs to happen so that an instance is created for this
+ # tenant is created in the slice. One instance is created per tenant.
+ model_policy_mcord_servicecomponent(self.pk)
+
+ def save_instance(self, instance):
+ with transaction.atomic():
+ super(VPGWCComponent, self).save_instance(instance)
+ if instance.isolation in ["vm"]:
+ for ntype in vpgwc_net_types:
+ lan_network = self.get_lan_network(instance, ntype)
+ port = self.find_or_make_port(instance,lan_network)
+ if (ntype == "s5s8"):
+ port.set_parameter("s_tag", self.s5s8_pgw_tag)
+ port.set_parameter("neutron_port_name", "stag-%s" % self.s5s8_pgw_tag)
+ port.save()
+ else:
+ return True
+
+ def delete(self, *args, **kwargs):
+ # Delete the instance that was created for this tenant
+ self.cleanup_container()
+ super(VPGWCComponent, self).delete(*args, **kwargs)
+
+ def find_or_make_port(self, instance, network, **kwargs):
+ port = Port.objects.filter(instance=instance, network=network)
+ if port:
+ port = port[0]
+ print "port already exist", port[0]
+ else:
+ port = Port(instance=instance, network=network, **kwargs)
+ print "NETWORK", network, "MAKE_PORT", port
+ port.save()
+ return port
+
+ def get_lan_network(self, instance, ntype):
+ slice = self.provider_service.slices.all()[0]
+ lan_networks = [x for x in slice.networks.all() if ntype in x.name]
+ if not lan_networks:
+ raise XOSProgrammingError("No lan_network")
+ return lan_networks[0]
+
+ def manage_container(self):
+ from core.models import Instance, Flavor
+
+ if self.deleted:
+ return
+
+ # For container or container_vm isolation, use what TenantWithCotnainer
+ # provides us
+ slice = self.get_slice()
+ if slice.default_isolation in ["container_vm", "container"]:
+ super(VPGWCComponent,self).manage_container()
+ return
+
+ if not self.s5s8_pgw_tag:
+ raise XOSConfigurationError("S5S8_PGW_TAG is missed")
+
+ if self.instance:
+ # We're good.
+ return
+
+ instance = self.make_instance()
+ self.instance = instance
+ super(TenantWithContainer, self).save()
+
+ def get_slice(self):
+ if not self.provider_service.slices.count():
+ raise XOSConfigurationError("The service has no slices")
+ slice = self.provider_service.slices.all()[0]
+ return slice
+
+ def make_instance(self):
+ slice = self.provider_service.slices.all()[0]
+ flavors = Flavor.objects.filter(name=slice.default_flavor)
+# flavors = Flavor.objects.filter(name="m1.xlarge")
+ if not flavors:
+ raise XOSConfigurationError("No default flavor")
+ default_flavor = slice.default_flavor
+ slice = self.provider_service.slices.all()[0]
+ if slice.default_isolation == "container_vm":
+ (node, parent) = ContainerVmScheduler(slice).pick()
+ else:
+ (node, parent) = LeastLoadedNodeScheduler(slice).pick()
+ instance = Instance(slice = slice,
+ node = node,
+ image = self.image,
+ creator = self.creator,
+ deployment = node.site_deployment.deployment,
+ flavor = flavors[0],
+ isolation = slice.default_isolation,
+ parent = parent)
+ self.save_instance(instance)
+ return instance
+
+ def ip_to_mac(self, ip):
+ (a, b, c, d) = ip.split('.')
+ return "02:42:%02x:%02x:%02x:%02x" % (int(a), int(b), int(c), int(d))
+
+ # Getter for the message that will appear on the webpage
+ # By default it is "Hello World!"
+ @property
+ def display_message(self):
+ return self.get_attribute(
+ "display_message",
+ self.default_attributes['display_message'])
+
+ @display_message.setter
+ def display_message(self, value):
+ self.set_attribute("display_message", value)
+
+ @property
+ def s5s8_pgw_tag(self):
+ return self.get_attribute(
+ "s5s8_pgw_tag",
+ self.default_attributes['s5s8_pgw_tag'])
+
+ @s5s8_pgw_tag.setter
+ def s5s8_pgw_tag(self, value):
+ self.set_attribute("s5s8_pgw_tag", value)
+
+
+ @property
+ def addresses(self):
+ if (not self.id) or (not self.instance):
+ return {}
+
+ addresses = {}
+ for ns in self.instance.ports.all():
+ if "s5s8_pgw" in ns.network.name.lower():
+ addresses["s5s8_pgw"] = (ns.ip, ns.mac)
+ return addresses
+
+
+ @property
+ def s5s8_pgw_ip(self):
+ return self.addresses.get("s5s8_pgw", (None, None))[0]
+ @property
+ def s5s8_pgw_mac(self):
+ return self.addresses.get("s5s8_pgw", (None, None))[1]
+
def model_policy_mcord_servicecomponent(pk):
# This section of code is atomic to prevent race conditions
with transaction.atomic():
@@ -281,3 +469,15 @@
# Since this code is atomic it is safe to always use the first tenant
component = component[0]
component.manage_container()
+
+
+def model_policy_mcord_servicecomponent(pk):
+ # This section of code is atomic to prevent race conditions
+ with transaction.atomic():
+ # We find all of the tenants that are waiting to update
+ component = VPGWCComponent.objects.select_for_update().filter(pk=pk)
+ if not component:
+ return
+ # Since this code is atomic it is safe to always use the first tenant
+ component = component[0]
+ component.manage_container()
diff --git a/xos/synchronizers/base/SyncInstanceUsingAnsible.py b/xos/synchronizers/base/SyncInstanceUsingAnsible.py
index fef8f86..49ca23b 100644
--- a/xos/synchronizers/base/SyncInstanceUsingAnsible.py
+++ b/xos/synchronizers/base/SyncInstanceUsingAnsible.py
@@ -21,7 +21,6 @@
# observes=VSGTenant
# requested_interval=0
# template_name = "sync_vcpetenant.yaml"
- # service_key_name = "/opt/xos/observers/vcpe/vcpe_private_key"
def __init__(self, **args):
SyncStep.__init__(self, **args)
@@ -96,7 +95,10 @@
"username": "ubuntu",
"ssh_ip": instance.get_ssh_ip(),
}
- key_name = self.service_key_name
+ if (instance.slice) and (instance.slice.service) and (instance.slice.service.private_key_fn):
+ key_name = instance.slice.service.private_key_fn
+ else:
+ raise Exception("Make sure to set private_key_fn in the service")
elif (instance.isolation == "container"):
# container on bare metal
node = self.get_node(instance)
diff --git a/xos/synchronizers/base/event_loop.py b/xos/synchronizers/base/event_loop.py
index 37ec4ca..ae97329 100644
--- a/xos/synchronizers/base/event_loop.py
+++ b/xos/synchronizers/base/event_loop.py
@@ -9,6 +9,7 @@
import json
import pdb
import pprint
+import traceback
from datetime import datetime
@@ -349,9 +350,11 @@
if (step_status[d] is STEP_STATUS_WORKING):
logger.info(" step %s wait on dep %s" % (step.__name__, d))
cond.wait()
+ logger.info(" step %s wait on dep %s cond returned" % (step.__name__, d))
elif step_status[d] == STEP_STATUS_OK:
go = True
else:
+ logger.info(" step %s has failed dep %s" % (step.__name__, d))
go = False
failed_dep = d
cond.release()
@@ -361,6 +364,7 @@
go = True
if (not go):
+ logger.info("Step %s skipped" % step.__name__)
self.consolePrint(bcolors.FAIL + "Step %r skipped on %r" % (step,failed_dep) + bcolors.ENDC)
# SMBAKER: sync_step was not defined here, so I changed
# this from 'sync_step' to 'step'. Verify.
@@ -416,7 +420,7 @@
if failed_objects:
self.failed_step_objects.update(failed_objects)
- logger.info("Step %r succeeded" % sync_step.__name__)
+ logger.info("Step %r succeeded, deletion=%s" % (sync_step.__name__, deletion))
self.consolePrint(bcolors.OKGREEN + "Step %r succeeded" % sync_step.__name__ + bcolors.ENDC)
my_status = STEP_STATUS_OK
self.update_run_time(sync_step,deletion)
@@ -462,8 +466,27 @@
self.run_once()
+ def check_db_connection_okay(self):
+ # django implodes if the database connection is closed by docker-compose
+ try:
+ diag = Diag.objects.filter(name="foo").first()
+ except Exception, e:
+ from django import db
+ if "connection already closed" in traceback.format_exc():
+ logger.error("XXX connection already closed")
+ try:
+# if db.connection:
+# db.connection.close()
+ db.close_connection()
+ except:
+ logger.log_exc("XXX we failed to fix the failure")
+ else:
+ logger.log_exc("XXX some other error")
+
def run_once(self):
try:
+ self.check_db_connection_okay()
+
loop_start = time.time()
error_map_file = getattr(Config(), "error_map_path", XOS_DIR + "/error_map.txt")
self.error_mapper = ErrorMapper(error_map_file)
@@ -499,7 +522,7 @@
schedule = self.ordered_steps if not deletion else reversed(self.ordered_steps)
for S in schedule:
- thread = threading.Thread(target=self.sync, args=(S, deletion))
+ thread = threading.Thread(target=self.sync, name='synchronizer', args=(S, deletion))
logger.info('Deletion=%r...'%deletion)
threads.append(thread)
diff --git a/xos/synchronizers/base/syncstep.py b/xos/synchronizers/base/syncstep.py
index 2fa6c38..eeb61db 100644
--- a/xos/synchronizers/base/syncstep.py
+++ b/xos/synchronizers/base/syncstep.py
@@ -141,6 +141,8 @@
def sync_record(self, o):
+ logger.info("Sync_record called for %s %s" % (o.__class__.__name__, str(o)))
+
try:
controller = o.get_controller()
controller_register = json.loads(controller.backend_register)
diff --git a/xos/synchronizers/exampleservice/exampleservice-synchronizer.py b/xos/synchronizers/exampleservice_old/exampleservice-synchronizer.py
similarity index 100%
rename from xos/synchronizers/exampleservice/exampleservice-synchronizer.py
rename to xos/synchronizers/exampleservice_old/exampleservice-synchronizer.py
diff --git a/xos/synchronizers/exampleservice/exampleservice_config b/xos/synchronizers/exampleservice_old/exampleservice_config
similarity index 100%
rename from xos/synchronizers/exampleservice/exampleservice_config
rename to xos/synchronizers/exampleservice_old/exampleservice_config
diff --git a/xos/synchronizers/exampleservice/model-deps b/xos/synchronizers/exampleservice_old/model-deps
similarity index 100%
rename from xos/synchronizers/exampleservice/model-deps
rename to xos/synchronizers/exampleservice_old/model-deps
diff --git a/xos/synchronizers/exampleservice/steps/exampletenant_playbook.yaml b/xos/synchronizers/exampleservice_old/steps/exampletenant_playbook.yaml
similarity index 100%
rename from xos/synchronizers/exampleservice/steps/exampletenant_playbook.yaml
rename to xos/synchronizers/exampleservice_old/steps/exampletenant_playbook.yaml
diff --git a/xos/synchronizers/exampleservice/steps/roles/create_index/tasks/main.yml b/xos/synchronizers/exampleservice_old/steps/roles/create_index/tasks/main.yml
similarity index 100%
rename from xos/synchronizers/exampleservice/steps/roles/create_index/tasks/main.yml
rename to xos/synchronizers/exampleservice_old/steps/roles/create_index/tasks/main.yml
diff --git a/xos/synchronizers/exampleservice/steps/roles/create_index/templates/index.html.j2 b/xos/synchronizers/exampleservice_old/steps/roles/create_index/templates/index.html.j2
similarity index 100%
rename from xos/synchronizers/exampleservice/steps/roles/create_index/templates/index.html.j2
rename to xos/synchronizers/exampleservice_old/steps/roles/create_index/templates/index.html.j2
diff --git a/xos/synchronizers/exampleservice/steps/roles/install_apache/tasks/main.yml b/xos/synchronizers/exampleservice_old/steps/roles/install_apache/tasks/main.yml
similarity index 100%
rename from xos/synchronizers/exampleservice/steps/roles/install_apache/tasks/main.yml
rename to xos/synchronizers/exampleservice_old/steps/roles/install_apache/tasks/main.yml
diff --git a/xos/synchronizers/exampleservice/steps/sync_exampletenant.py b/xos/synchronizers/exampleservice_old/steps/sync_exampletenant.py
similarity index 100%
rename from xos/synchronizers/exampleservice/steps/sync_exampletenant.py
rename to xos/synchronizers/exampleservice_old/steps/sync_exampletenant.py
diff --git a/xos/synchronizers/model_policy.py b/xos/synchronizers/model_policy.py
index ef31d41..aa12092 100644
--- a/xos/synchronizers/model_policy.py
+++ b/xos/synchronizers/model_policy.py
@@ -3,10 +3,11 @@
import pdb
from generate.dependency_walker import *
from synchronizers.openstack import model_policies
-from xos.logger import logger
+from xos.logger import Logger, logging
from datetime import datetime
from django.utils import timezone
import time
+import traceback
from core.models import *
from django.db import reset_queries
from django.db.transaction import atomic
@@ -15,6 +16,8 @@
modelPolicyEnabled = True
bad_instances=[]
+logger = Logger(level=logging.INFO)
+
def EnableModelPolicy(x):
global modelPolicyEnabled
modelPolicyEnabled = x
@@ -40,9 +43,10 @@
if (save_fields):
d.save(update_fields=save_fields)
except AttributeError,e:
+ logger.log_exc("AttributeError in update_dep")
raise e
except Exception,e:
- logger.info('Could not save %r. Exception: %r'%(d,e), extra=d.tologdict())
+ logger.log_exc("Exception in update_dep")
def delete_if_inactive(d, o):
try:
@@ -75,7 +79,7 @@
try:
policy_handler = getattr(model_policies, policy_name, None)
- logger.error("POLICY HANDLER: %s %s" % (policy_name, policy_handler))
+ logger.info("MODEL POLICY: handler %s %s" % (policy_name, policy_handler))
if policy_handler is not None:
if (deleted):
try:
@@ -84,23 +88,45 @@
pass
else:
policy_handler.handle(instance)
+ logger.info("MODEL POLICY: completed handler %s %s" % (policy_name, policy_handler))
except:
- logger.log_exc("Model Policy Error:")
+ logger.log_exc("MODEL POLICY: Exception when running handler")
try:
instance.policed=timezone.now()
instance.save(update_fields=['policed'])
except:
- logging.error('Object %r is defective'%instance)
+ logger.log_exc('MODEL POLICY: Object %r is defective'%instance)
bad_instances.append(instance)
def noop(o,p):
pass
+def check_db_connection_okay():
+ # django implodes if the database connection is closed by docker-compose
+ from django import db
+ try:
+ db.connection.cursor()
+ #diag = Diag.objects.filter(name="foo").first()
+ except Exception, e:
+ if "connection already closed" in traceback.format_exc():
+ logger.error("XXX connection already closed")
+ try:
+# if db.connection:
+# db.connection.close()
+ db.close_connection()
+ except:
+ logger.log_exc("XXX we failed to fix the failure")
+ else:
+ logger.log_exc("XXX some other error")
+
def run_policy():
while (True):
start = time.time()
- run_policy_once()
+ try:
+ run_policy_once()
+ except:
+ logger.log_exc("MODEL_POLICY: Exception in run_policy()")
if (time.time()-start<1):
time.sleep(1)
@@ -110,6 +136,10 @@
objects = []
deleted_objects = []
+ logger.info("MODEL POLICY: run_policy_once()")
+
+ check_db_connection_okay()
+
for m in models:
res = m.objects.filter((Q(policed__lt=F('updated')) | Q(policed=None)) & Q(no_policy=False))
objects.extend(res)
@@ -137,4 +167,6 @@
reset_queries()
except:
# this shouldn't happen, but in case it does, catch it...
- logger.log_exc("exception in reset_queries")
+ logger.log_exc("MODEL POLICY: exception in reset_queries")
+
+ logger.info("MODEL POLICY: finished run_policy_once()")
diff --git a/xos/synchronizers/monitoring_channel/steps/sync_monitoringchannel.yaml b/xos/synchronizers/monitoring_channel/steps/sync_monitoringchannel.yaml
index 1c4da12..ca72c5f 100644
--- a/xos/synchronizers/monitoring_channel/steps/sync_monitoringchannel.yaml
+++ b/xos/synchronizers/monitoring_channel/steps/sync_monitoringchannel.yaml
@@ -66,13 +66,21 @@
# dest=/etc/resolv.conf
{% endif %}
+# FIXME: Temporary workaround to delete the monitoring-channel_ceilometer_proxy_config file always
+# to trigger ansible notify handlers in the following task.
+# Due to some issue, ansible "changed" flag is set to false even though there is a change of
+# ceilometer configuration file, because of which the configuration change is not reflecting in
+# ceilometer containers
+# - file: path=/usr/local/share/monitoring-channel-{{ unique_id }}_ceilometer_proxy_config state=absent
+
- name: ceilometer proxy config
template: src=/opt/xos/synchronizers/monitoring_channel/templates/ceilometer_proxy_config.j2 dest=/usr/local/share/monitoring-channel-{{ unique_id }}_ceilometer_proxy_config mode=0777
notify:
-# - restart monitoring-channel
- - stop monitoring-channel
- - remove container
- - start monitoring-channel
+ - copy ceilo-config-file
+ - restart monitoring-channel container
+# - stop monitoring-channel
+# - remove container
+# - start monitoring-channel
- name: Monitoring channel upstart
template: src=/opt/xos/synchronizers/monitoring_channel/templates/monitoring-channel.conf.j2 dest=/etc/init/monitoring-channel-{{ unique_id }}.conf
@@ -118,6 +126,12 @@
{% endif %}
handlers:
+ - name: copy ceilo-config-file
+ shell: docker cp /usr/local/share/monitoring-channel-{{ unique_id }}_ceilometer_proxy_config monitoring-channel-{{ unique_id }}:/usr/local/share/ceilometer_proxy_config
+
+ - name: restart monitoring-channel container
+ shell: docker restart monitoring-channel-{{ unique_id }}
+
- name: restart monitoring-channel
shell: service monitoring-channel-{{ unique_id }} stop; sleep 1; service monitoring-channel-{{ unique_id }} start
diff --git a/xos/synchronizers/monitoring_channel/templates/start-monitoring-channel.sh.j2 b/xos/synchronizers/monitoring_channel/templates/start-monitoring-channel.sh.j2
index 1685e07..4486985 100755
--- a/xos/synchronizers/monitoring_channel/templates/start-monitoring-channel.sh.j2
+++ b/xos/synchronizers/monitoring_channel/templates/start-monitoring-channel.sh.j2
@@ -24,9 +24,11 @@
#sudo docker pull srikanthvavila/monitoring-channel
if [ -z "$HEADNODEFLATLANIP" ] || [ "$HEADNODEFLATLANIP" == "None" ]
then
- docker run -d --name=$MONITORING_CHANNEL --privileged=true -p $HOST_FORWARDING_PORT_FOR_CEILOMETER:8000 -v /usr/local/share/monitoring-channel-{{ unique_id }}_ceilometer_proxy_config:/usr/local/share/ceilometer_proxy_config srikanthvavila/monitoring-channel
+# docker run -d --name=$MONITORING_CHANNEL --privileged=true -p $HOST_FORWARDING_PORT_FOR_CEILOMETER:8000 -v /usr/local/share/monitoring-channel-{{ unique_id }}_ceilometer_proxy_config:/usr/local/share/ceilometer_proxy_config srikanthvavila/monitoring-channel
+ docker run -d --name=$MONITORING_CHANNEL --privileged=true -p $HOST_FORWARDING_PORT_FOR_CEILOMETER:8000 srikanthvavila/monitoring-channel
else
- docker run -d --name=$MONITORING_CHANNEL --add-host="ctl:$HEADNODEFLATLANIP" --privileged=true -p $HOST_FORWARDING_PORT_FOR_CEILOMETER:8000 -v /usr/local/share/monitoring-channel-{{ unique_id }}_ceilometer_proxy_config:/usr/local/share/ceilometer_proxy_config srikanthvavila/monitoring-channel
+# docker run -d --name=$MONITORING_CHANNEL --add-host="ctl:$HEADNODEFLATLANIP" --privileged=true -p $HOST_FORWARDING_PORT_FOR_CEILOMETER:8000 -v /usr/local/share/monitoring-channel-{{ unique_id }}_ceilometer_proxy_config:/usr/local/share/ceilometer_proxy_config srikanthvavila/monitoring-channel
+ docker run -d --name=$MONITORING_CHANNEL --add-host="ctl:$HEADNODEFLATLANIP" --privileged=true -p $HOST_FORWARDING_PORT_FOR_CEILOMETER:8000 srikanthvavila/monitoring-channel
fi
else
docker start $MONITORING_CHANNEL
@@ -41,6 +43,7 @@
# Now copy ceilometer proxy configuration to container
#cat /usr/local/share/monitoring-channel-{{ unique_id }}_ceilometer_proxy_config | sudo docker exec -i $MONITORING_CHANNEL bash -c 'cat > /usr/local/share/ceilometer_proxy_config'
+docker cp /usr/local/share/monitoring-channel-{{ unique_id }}_ceilometer_proxy_config $MONITORING_CHANNEL:/usr/local/share/ceilometer_proxy_config
# Attach to container
-#docker start -a $MONITORING_CHANNEL
+docker start -a $MONITORING_CHANNEL
diff --git a/xos/synchronizers/onboarding/files/__init__.py b/xos/synchronizers/onboarding/files/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/xos/synchronizers/onboarding/files/__init__.py
@@ -0,0 +1 @@
+
diff --git a/xos/synchronizers/onboarding/model-deps b/xos/synchronizers/onboarding/model-deps
new file mode 100644
index 0000000..4aa86c7
--- /dev/null
+++ b/xos/synchronizers/onboarding/model-deps
@@ -0,0 +1,8 @@
+{
+ "XOS": [
+ "ServiceController"
+ ],
+ "ServiceController": [
+ "ServiceControllerResource"
+ ]
+}
diff --git a/xos/synchronizers/onboarding/onboarding-synchronizer.py b/xos/synchronizers/onboarding/onboarding-synchronizer.py
new file mode 100755
index 0000000..84bec4f
--- /dev/null
+++ b/xos/synchronizers/onboarding/onboarding-synchronizer.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+
+# This imports and runs ../../xos-observer.py
+
+import importlib
+import os
+import sys
+observer_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"../../synchronizers/base")
+sys.path.append(observer_path)
+mod = importlib.import_module("xos-synchronizer")
+mod.main()
diff --git a/xos/synchronizers/onboarding/onboarding_synchronizer_config b/xos/synchronizers/onboarding/onboarding_synchronizer_config
new file mode 100644
index 0000000..e2b92fd
--- /dev/null
+++ b/xos/synchronizers/onboarding/onboarding_synchronizer_config
@@ -0,0 +1,38 @@
+
+[plc]
+name=plc
+deployment=VICCI
+
+[db]
+name=xos
+user=postgres
+password=password
+host=localhost
+port=5432
+
+[api]
+host=128.112.171.237
+port=8000
+ssl_key=None
+ssl_cert=None
+ca_ssl_cert=None
+ratelimit_enabled=0
+omf_enabled=0
+mail_support_address=support@localhost
+nova_enabled=True
+
+[observer]
+name=onboarding
+dependency_graph=/opt/xos/synchronizers/onboarding/model-deps
+steps_dir=/opt/xos/synchronizers/onboarding/steps
+sys_dir=/opt/xos/synchronizers/onboarding/sys
+deleters_dir=/opt/xos/synchronizers/onboarding/deleters
+log_file=console
+driver=None
+backoff_disabled=True
+pretend=False
+save_ansible_output=True
+
+[feefie]
+client_id='vicci_dev_central'
+user_id='pl'
diff --git a/xos/synchronizers/onboarding/run.sh b/xos/synchronizers/onboarding/run.sh
new file mode 100755
index 0000000..d52d39c
--- /dev/null
+++ b/xos/synchronizers/onboarding/run.sh
@@ -0,0 +1,2 @@
+export XOS_DIR=/opt/xos
+python onboarding-synchronizer.py -C $XOS_DIR/synchronizers/onboarding/onboarding_synchronizer_config
diff --git a/xos/synchronizers/onboarding/steps/sync_servicecontroller.py b/xos/synchronizers/onboarding/steps/sync_servicecontroller.py
new file mode 100644
index 0000000..77d8e12
--- /dev/null
+++ b/xos/synchronizers/onboarding/steps/sync_servicecontroller.py
@@ -0,0 +1,53 @@
+import os
+import sys
+import base64
+from django.db.models import F, Q
+from xos.config import Config
+from synchronizers.base.syncstep import SyncStep, DeferredException
+from core.models import XOS, ServiceController
+from xos.logger import Logger, logging
+from synchronizers.base.ansible import run_template
+
+# xosbuilder will be in steps/..
+parentdir = os.path.join(os.path.dirname(__file__),"..")
+sys.path.insert(0,parentdir)
+
+from xosbuilder import XOSBuilder
+
+logger = Logger(level=logging.INFO)
+
+class SyncServiceController(SyncStep, XOSBuilder):
+ provides=[ServiceController]
+ observes=ServiceController
+ requested_interval=0
+ playbook = "sync_servicecontroller.yaml"
+
+ def __init__(self, **args):
+ SyncStep.__init__(self, **args)
+ XOSBuilder.__init__(self)
+
+ def sync_record(self, sc):
+ logger.info("Sync'ing ServiceController %s" % sc)
+
+ if sc.xos and (not sc.xos.enable_build):
+ raise DeferredException("XOS build is currently disabled")
+
+ unready = self.check_controller_unready(sc)
+ if unready:
+ raise Exception("Controller %s has unready resources: %s" % (str(sc), ",".join([str(x) for x in unready])))
+
+ dockerfiles = [self.create_synchronizer_dockerfile(sc)]
+ tenant_fields = {"dockerfiles": dockerfiles,
+ "build_dir": self.build_dir,
+ "ansible_tag": sc.__class__.__name__ + "_" + str(sc.id)}
+
+ path="servicecontroller"
+ res = run_template(self.playbook, tenant_fields, path=path)
+
+ def delete_record(self, m):
+ pass
+
+ def fetch_pending(self, deleted=False):
+ pend = super(SyncServiceController, self).fetch_pending(deleted)
+ return pend
+
diff --git a/xos/synchronizers/onboarding/steps/sync_servicecontroller.yaml b/xos/synchronizers/onboarding/steps/sync_servicecontroller.yaml
new file mode 100644
index 0000000..9431427
--- /dev/null
+++ b/xos/synchronizers/onboarding/steps/sync_servicecontroller.yaml
@@ -0,0 +1,20 @@
+---
+- hosts: 127.0.0.1
+ connection: local
+
+ vars:
+ dockerfiles:
+ {% for dockerfile in dockerfiles %}
+ - docker_image_name: {{ dockerfile.docker_image_name }}
+ dockerfile_fn: {{ dockerfile.dockerfile_fn }}
+ {% endfor %}
+
+ tasks:
+ {% for dockerfile in dockerfiles %}
+ - name: build_docker_{{ dockerfile.docker_image_name }}
+ shell: chdir={{ build_dir }} docker build -f {{ dockerfile.dockerfile_fn }} --rm -t {{ dockerfile.docker_image_name }} .
+ {% endfor %}
+
+# - build_dockers:
+# shell: docker build -f {{ '{{' }} item.dockerfile_fn {{ '}}' }} --rm -t {{ '{{' }} item.docker_image_name {{ '}}' }} .
+# with items: "dockerfiles"
diff --git a/xos/synchronizers/onboarding/steps/sync_servicecontrollerresource.py b/xos/synchronizers/onboarding/steps/sync_servicecontrollerresource.py
new file mode 100644
index 0000000..59ae93f
--- /dev/null
+++ b/xos/synchronizers/onboarding/steps/sync_servicecontrollerresource.py
@@ -0,0 +1,37 @@
+import os
+import sys
+import base64
+from django.db.models import F, Q
+from xos.config import Config
+from synchronizers.base.syncstep import SyncStep
+from core.models import Service, ServiceController, ServiceControllerResource
+from xos.logger import Logger, logging
+
+# xosbuilder will be in steps/..
+parentdir = os.path.join(os.path.dirname(__file__),"..")
+sys.path.insert(0,parentdir)
+
+from xosbuilder import XOSBuilder
+
+logger = Logger(level=logging.INFO)
+
+class SyncServiceControllerResource(SyncStep, XOSBuilder):
+ provides=[ServiceControllerResource]
+ observes=ServiceControllerResource
+ requested_interval=0
+
+ def __init__(self, **args):
+ SyncStep.__init__(self, **args)
+ XOSBuilder.__init__(self)
+
+ def sync_record(self, scr):
+ logger.info("Sync'ing ServiceControllerResource %s" % scr)
+ self.download_resource(scr)
+
+ def delete_record(self, m):
+ pass
+
+ def fetch_pending(self, deleted=False):
+ pend = super(SyncServiceControllerResource, self).fetch_pending(deleted)
+ return pend
+
diff --git a/xos/synchronizers/onboarding/steps/sync_xos.py b/xos/synchronizers/onboarding/steps/sync_xos.py
new file mode 100644
index 0000000..dec7a34
--- /dev/null
+++ b/xos/synchronizers/onboarding/steps/sync_xos.py
@@ -0,0 +1,52 @@
+import os
+import sys
+import base64
+from django.db.models import F, Q
+from xos.config import Config
+from synchronizers.base.syncstep import SyncStep, DeferredException
+from core.models import XOS
+from xos.logger import Logger, logging
+from synchronizers.base.ansible import run_template
+
+# xosbuilder will be in steps/..
+parentdir = os.path.join(os.path.dirname(__file__),"..")
+sys.path.insert(0,parentdir)
+
+from xosbuilder import XOSBuilder
+
+logger = Logger(level=logging.INFO)
+
+class SyncXOS(SyncStep, XOSBuilder):
+ provides=[XOS]
+ observes=XOS
+ requested_interval=0
+ playbook = "sync_xos.yaml"
+
+ def __init__(self, **args):
+ SyncStep.__init__(self, **args)
+ XOSBuilder.__init__(self)
+
+ def sync_record(self, xos):
+ logger.info("Sync'ing XOS %s" % xos)
+
+ if (not xos.enable_build):
+ raise DeferredException("XOS build is currently disabled")
+
+ self.create_docker_compose()
+
+ dockerfiles = [self.create_ui_dockerfile()]
+ tenant_fields = {"dockerfiles": dockerfiles,
+ "build_dir": self.build_dir,
+ "docker_project_name": xos.docker_project_name,
+ "ansible_tag": xos.__class__.__name__ + "_" + str(xos.id)}
+
+ path="XOS"
+ res = run_template(self.playbook, tenant_fields, path=path)
+
+ def delete_record(self, m):
+ pass
+
+ def fetch_pending(self, deleted=False):
+ pend = super(SyncXOS, self).fetch_pending(deleted)
+ return pend
+
diff --git a/xos/synchronizers/onboarding/steps/sync_xos.yaml b/xos/synchronizers/onboarding/steps/sync_xos.yaml
new file mode 100644
index 0000000..8a98873
--- /dev/null
+++ b/xos/synchronizers/onboarding/steps/sync_xos.yaml
@@ -0,0 +1,20 @@
+---
+- hosts: 127.0.0.1
+ connection: local
+
+ vars:
+ dockerfiles:
+ {% for dockerfile in dockerfiles %}
+ - docker_image_name: {{ dockerfile.docker_image_name }}
+ dockerfile_fn: {{ dockerfile.dockerfile_fn }}
+ {% endfor %}
+
+ tasks:
+ {% for dockerfile in dockerfiles %}
+ - name: build_docker_{{ dockerfile.docker_image_name }}
+ shell: chdir={{ build_dir }} docker build -f {{ dockerfile.dockerfile_fn }} --rm -t {{ dockerfile.docker_image_name }} .
+ {% endfor %}
+
+ - name: run docker-compose
+ shell: docker-compose -p {{ docker_project_name }} -f /opt/xos/synchronizers/onboarding/docker-compose/docker-compose.yml up -d
+
diff --git a/xos/synchronizers/onboarding/templates/docker-compose.yml.j2 b/xos/synchronizers/onboarding/templates/docker-compose.yml.j2
new file mode 100644
index 0000000..faa9d02
--- /dev/null
+++ b/xos/synchronizers/onboarding/templates/docker-compose.yml.j2
@@ -0,0 +1,48 @@
+{% for container_name, container in containers.iteritems() %}
+
+{{ container_name}}:
+# container_name: {{ container.container_base_name }}_{{ container_name }}_1
+ image: {{ container.image }}
+{%- if container.command %}
+ command: {{ container.command }}
+{%- endif %}
+{%- if container.ports %}
+ ports:
+{%- for src,dest in container.ports.iteritems() %}
+ - "{{ src }}:{{ dest }}"
+{%- endfor %}
+{%- endif %}
+{%- if container.links %}
+ links:
+{%- for link in container.links %}
+ - {{ link }}
+{%- endfor %}
+{%- endif %}
+{%- if container.external_links %}
+ external_links:
+{%- for link in container.external_links %}
+ - {{ link }}
+{%- endfor %}
+{%- endif %}
+{%- if container.volumes %}
+ volumes:
+{%- for volume in container.volumes %}
+{%- if volume.read_only %}
+ - {{ volume.host_path }}:{{ volume.container_path }}:ro
+{%- else %}
+ - {{ volume.host_path }}:{{ volume.container_path }}
+{%- endif %}
+{%- endfor %}
+{%- endif %}
+{%- if container.expose %}
+ expose:
+{%- for expose in container.expose %}
+ - "{{ expose }}"
+{%- endfor %}
+{%- endif %}
+ log_driver: "json-file"
+ log_opt:
+ max-size: "100k"
+ max-file: "5"
+
+{%- endfor %}
diff --git a/xos/synchronizers/onboarding/xosbuilder.py b/xos/synchronizers/onboarding/xosbuilder.py
new file mode 100644
index 0000000..ffb66ee
--- /dev/null
+++ b/xos/synchronizers/onboarding/xosbuilder.py
@@ -0,0 +1,252 @@
+import os
+import base64
+import jinja2
+import string
+import sys
+import urllib2
+import urlparse
+import xmlrpclib
+
+from xos.config import Config
+from core.models import Service, ServiceController, ServiceControllerResource, XOS
+from xos.logger import Logger, logging
+
+logger = Logger(level=logging.INFO)
+
+class XOSBuilder(object):
+ UI_KINDS=["models", "admin", "django_library", "rest_service", "rest_tenant", "tosca_custom_types", "tosca_resource","public_key"]
+ SYNC_CONTROLLER_KINDS=["synchronizer", "private_key", "public_key"]
+ SYNC_ALLCONTROLLER_KINDS=["models", "django_library"]
+
+ def __init__(self):
+ self.source_ui_image = "xosproject/xos"
+ self.source_sync_image = "xosproject/xos-synchronizer-openstack"
+ self.build_dir = "/opt/xos/BUILD/"
+
+ # stuff that has to do with downloading
+
+ def get_dest_dir(self, scr):
+ xos_base = "opt/xos"
+ service_name = scr.service_controller.name
+ base_dirs = {"models": "%s/services/%s/" % (xos_base, service_name),
+ "admin": "%s/services/%s/" % (xos_base, service_name),
+ "django_library": "%s/services/%s/" % (xos_base, service_name),
+ "synchronizer": "%s/synchronizers/%s/" % (xos_base, service_name),
+ "tosca_custom_types": "%s/tosca/custom_types/" % (xos_base),
+ "tosca_resource": "%s/tosca/resources/" % (xos_base),
+ "rest_service": "%s/api/service/" % (xos_base),
+ "rest_tenant": "%s/api/tenant/" % (xos_base),
+ "private_key": "%s/services/%s/keys" % (xos_base, service_name),
+ "public_key": "%s/services/%s/keys/" % (xos_base, service_name)}
+ return base_dirs[scr.kind]
+
+ def get_build_fn(self, scr):
+ dest_dir = self.get_dest_dir(scr)
+ dest_fn = os.path.split(urlparse.urlsplit(scr.full_url).path)[-1]
+ return os.path.join(dest_dir, dest_fn)
+
+ def get_download_fn(self, scr):
+ dest_fn = self.get_build_fn(scr)
+ return os.path.join(self.build_dir, dest_fn)
+
+ def read_manifest(self, scr, fn):
+ manifest = []
+ manifest_lines = file(fn).readlines()
+ manifest_lines = [x.strip() for x in manifest_lines]
+ manifest_lines = [x for x in manifest_lines if x]
+ for line in manifest_lines:
+ url_parts = urlparse.urlsplit(scr.full_url)
+ new_path = os.path.join(os.path.join(*os.path.split(url_parts.path)[:-1]),line)
+ url = urlparse.urlunsplit( (url_parts.scheme, url_parts.netloc, new_path, url_parts.query, url_parts.fragment) )
+
+ build_fn = os.path.join(self.get_dest_dir(scr), line)
+ download_fn = os.path.join(self.build_dir, build_fn)
+
+ manifest.append( (url, download_fn, build_fn) )
+ return manifest
+
+ def download_file(self, url, dest_fn):
+ logger.info("Download %s to %s" % (url, dest_fn))
+ if not os.path.exists(os.path.dirname(dest_fn)):
+ os.makedirs(os.path.dirname(dest_fn))
+ obj = urllib2.urlopen(url)
+ file(dest_fn,"w").write(obj.read())
+
+ # make python files executable
+ if dest_fn.endswith(".py"): # and contents.startswith("#!"):
+ os.chmod(dest_fn, 0755)
+
+ def download_resource(self, scr):
+ if scr.format == "manifest":
+ manifest_fn = self.get_download_fn(scr)
+ self.download_file(scr.full_url, manifest_fn)
+ manifest = self.read_manifest(scr, manifest_fn)
+ for (url, download_fn, build_fn) in manifest:
+ self.download_file(url, download_fn)
+ else:
+ self.download_file(scr.full_url, self.get_download_fn(scr))
+
+ def get_docker_lines(self, scr):
+ if scr.format == "manifest":
+ manifest_fn = self.get_download_fn(scr)
+ manifest = self.read_manifest(scr, manifest_fn)
+ lines = []
+ for (url, download_fn, build_fn) in manifest:
+ lines.append("ADD %s /%s" % (build_fn, build_fn))
+ return lines
+ else:
+ build_fn = self.get_build_fn(scr)
+ return ["ADD %s /%s" % (build_fn, build_fn)]
+
+ def get_controller_docker_lines(self, controller, kinds):
+ need_service_init_py = False
+ dockerfile=[]
+ for scr in controller.service_controller_resources.all():
+ if scr.kind in kinds:
+ lines = self.get_docker_lines(scr)
+ dockerfile = dockerfile + lines
+ if scr.kind in ["admin", "models"]:
+ need_service_init_py = True
+
+ if need_service_init_py:
+ file(os.path.join(self.build_dir, "opt/xos/empty__init__.py"),"w").write("")
+ dockerfile.append("ADD opt/xos/empty__init__.py /opt/xos/services/%s/__init__.py" % controller.name)
+
+ return dockerfile
+
+ def check_controller_unready(self, controller):
+ unready_resources=[]
+ for scr in controller.service_controller_resources.all():
+ if (not scr.backend_status) or (not scr.backend_status.startswith("1")):
+ unready_resources.append(scr)
+
+ return unready_resources
+
+ # stuff that has to do with building
+
+ def create_xos_app_data(self, name, dockerfile, app_list, migration_list):
+ if not os.path.exists(os.path.join(self.build_dir,"opt/xos/xos")):
+ os.makedirs(os.path.join(self.build_dir,"opt/xos/xos"))
+
+ if app_list:
+ dockerfile.append("COPY opt/xos/xos/%s_xosbuilder_app_list /opt/xos/xos/xosbuilder_app_list" % name)
+ file(os.path.join(self.build_dir, "opt/xos/xos/%s_xosbuilder_app_list") % name, "w").write("\n".join(app_list)+"\n")
+
+ if migration_list:
+ dockerfile.append("COPY opt/xos/xos/%s_xosbuilder_migration_list /opt/xos/xos/xosbuilder_migration_list" % name)
+ file(os.path.join(self.build_dir, "opt/xos/xos/%s_xosbuilder_migration_list") % name, "w").write("\n".join(migration_list)+"\n")
+
+ def create_ui_dockerfile(self):
+ dockerfile_fn = "Dockerfile.UI"
+
+ app_list = []
+ migration_list = []
+
+ dockerfile = ["FROM %s" % self.source_ui_image]
+ for controller in ServiceController.objects.all():
+ if self.check_controller_unready(controller):
+ logger.warning("Controller %s has unready resources" % str(controller))
+ continue
+
+ dockerfile = dockerfile + self.get_controller_docker_lines(controller, self.UI_KINDS)
+ if controller.service_controller_resources.filter(kind="models").exists():
+ app_list.append("services." + controller.name)
+ migration_list.append(controller.name)
+
+ self.create_xos_app_data("ui", dockerfile, app_list, migration_list)
+
+ file(os.path.join(self.build_dir, dockerfile_fn), "w").write("\n".join(dockerfile)+"\n")
+
+ return {"dockerfile_fn": dockerfile_fn,
+ "docker_image_name": "xosproject/xos-ui"}
+
+ def create_synchronizer_dockerfile(self, controller):
+ # bake in the synchronizer from this controller
+ sync_lines = self.get_controller_docker_lines(controller, self.SYNC_CONTROLLER_KINDS)
+ if not sync_lines:
+ return []
+
+ dockerfile_fn = "Dockerfile.%s" % controller.name
+ dockerfile = ["FROM %s" % self.source_sync_image]
+
+ # Now bake in models from this controller as well as the others
+ # It's important to bake all services in, because some services'
+ # synchronizers may depend on models from another service.
+ app_list = []
+ for c in ServiceController.objects.all():
+ dockerfile = dockerfile + self.get_controller_docker_lines(c, self.SYNC_ALLCONTROLLER_KINDS)
+ if controller.service_controller_resources.filter(kind="models").exists():
+ app_list.append("services." + controller.name)
+
+ self.create_xos_app_data(controller.name, dockerfile, app_list, None)
+
+ dockerfile = dockerfile + sync_lines
+ file(os.path.join(self.build_dir, dockerfile_fn), "w").write("\n".join(dockerfile)+"\n")
+
+ return {"dockerfile_fn": dockerfile_fn,
+ "docker_image_name": "xosproject/xos-synchronizer-%s" % controller.name}
+
+ def create_docker_compose(self):
+ xos = XOS.objects.all()[0]
+
+ volume_list = []
+ for volume in xos.volumes.all():
+ volume_list.append({"host_path": volume.host_path,
+ "container_path": volume.container_path,
+ "read_only": volume.read_only})
+
+ containers = {}
+
+ containers["xos_db"] = \
+ {"image": "xosproject/xos-postgres",
+ "expose": [5432]}
+
+ db_container_name = xos.docker_project_name + "_xos_db_1"
+
+ containers["xos_ui"] = \
+ {"image": "xosproject/xos-ui",
+ "command": "python /opt/xos/manage.py runserver 0.0.0.0:%d --insecure --makemigrations" % xos.ui_port,
+ "ports": {"%d"%xos.ui_port : "%d"%xos.ui_port},
+ "links": ["xos_db"],
+ #"external_links": [db_container_name],
+ "volumes": volume_list}
+
+# containers["xos_bootstrap_ui"] = {"image": "xosproject/xos",
+# "command": "python /opt/xos/manage.py runserver 0.0.0.0:%d --insecure --makemigrations" % xos.bootstrap_ui_port,
+# "ports": {"%d"%xos.bootstrap_ui_port : "%d"%xos.bootstrap_ui_port},
+# #"external_links": [db_container_name],
+# "links": ["xos_db"],
+# "volumes": volume_list}
+
+ for c in ServiceController.objects.all():
+ if self.check_controller_unready(c):
+ logger.warning("Controller %s has unready resources" % str(c))
+ continue
+
+ containers["xos_synchronizer_%s" % c.name] = \
+ {"image": "xosproject/xos-synchronizer-%s" % c.name,
+ "command": 'bash -c "sleep 120; cd /opt/xos/synchronizers/%s; bash ./run.sh"' % c.name,
+ #"external_links": [db_container_name],
+ "links": ["xos_db"],
+ "volumes": volume_list}
+
+ vars = { "containers": containers }
+
+ template_loader = jinja2.FileSystemLoader( "/opt/xos/synchronizers/onboarding/templates/" )
+ template_env = jinja2.Environment(loader=template_loader)
+ template = template_env.get_template("docker-compose.yml.j2")
+ buffer = template.render(vars)
+
+ if not os.path.exists("/opt/xos/synchronizers/onboarding/docker-compose"):
+ os.makedirs("/opt/xos/synchronizers/onboarding/docker-compose")
+ file("/opt/xos/synchronizers/onboarding/docker-compose/docker-compose.yml", "w").write(buffer)
+
+# def build_xos(self):
+# dockerfiles=[]
+# dockerfiles.append(self.create_ui_dockerfile())
+#
+# for controller in ServiceController.objects.all():
+# dockerfiles.append(self.create_synchronizer_dockerfile(controller))
+
+
+
diff --git a/xos/synchronizers/openstack/steps/purge_disabled_users.py b/xos/synchronizers/openstack/steps/purge_disabled_users.py
index 0973b8c..6b1dac3 100644
--- a/xos/synchronizers/openstack/steps/purge_disabled_users.py
+++ b/xos/synchronizers/openstack/steps/purge_disabled_users.py
@@ -7,19 +7,19 @@
from core.models.user import User
from xos.logger import observer_logger as logger
-class SyncRoles(OpenStackSyncStep):
- provides=[User]
- requested_interval=0
- observes=User
-
- def fetch_pending(self, deleted):
- if (deleted):
- # users marked as deleted
- return User.deleted_objects.all()
- else:
- # disabled users that haven't been updated in over a week
- one_week_ago = datetime.datetime.now() - datetime.timedelta(days=7)
- return User.objects.filter(is_active=False, updated__gt=one_week_ago)
-
- def sync_record(self, user):
- user.delete()
+#class SyncRoles(OpenStackSyncStep):
+# provides=[User]
+# requested_interval=0
+# observes=User
+#
+# def fetch_pending(self, deleted):
+# if (deleted):
+# # users marked as deleted
+# return User.deleted_objects.all()
+# else:
+# # disabled users that haven't been updated in over a week
+# one_week_ago = datetime.datetime.now() - datetime.timedelta(days=7)
+# return User.objects.filter(is_active=False, updated__gt=one_week_ago)
+#
+# def sync_record(self, user):
+# user.delete()
diff --git a/xos/services/exampleservice/__init__.py b/xos/synchronizers/vpgwc/__init__.py
old mode 100644
new mode 100755
similarity index 100%
rename from xos/services/exampleservice/__init__.py
rename to xos/synchronizers/vpgwc/__init__.py
diff --git a/xos/synchronizers/exampleservice/model-deps b/xos/synchronizers/vpgwc/model-deps
old mode 100644
new mode 100755
similarity index 100%
copy from xos/synchronizers/exampleservice/model-deps
copy to xos/synchronizers/vpgwc/model-deps
diff --git a/xos/synchronizers/vpgwc/run.sh b/xos/synchronizers/vpgwc/run.sh
new file mode 100755
index 0000000..821e149
--- /dev/null
+++ b/xos/synchronizers/vpgwc/run.sh
@@ -0,0 +1,3 @@
+# Runs the XOS observer using helloworldservice_config
+export XOS_DIR=/opt/xos
+python vpgwc-synchronizer.py -C $XOS_DIR/synchronizers/vpgwc/vpgwc_config
diff --git a/xos/synchronizers/vpgwc/steps/sync_vpgwc.py b/xos/synchronizers/vpgwc/steps/sync_vpgwc.py
new file mode 100644
index 0000000..446a521
--- /dev/null
+++ b/xos/synchronizers/vpgwc/steps/sync_vpgwc.py
@@ -0,0 +1,37 @@
+import os
+import sys
+from django.db.models import Q, F
+from services.mcord.models import MCORDService, VPGWCComponent
+from synchronizers.base.SyncInstanceUsingAnsible import SyncInstanceUsingAnsible
+
+parentdir = os.path.join(os.path.dirname(__file__), "..")
+sys.path.insert(0, parentdir)
+
+class SyncVPGWCComponent(SyncInstanceUsingAnsible):
+
+ provides = [VPGWCComponent]
+
+ observes = VPGWCComponent
+
+ requested_interval = 0
+
+ template_name = "sync_vpgwc.yaml"
+
+ service_key_name = "/opt/xos/configurations/mcord/mcord_private_key"
+
+ def __init__(self, *args, **kwargs):
+ super(SyncVPGWCComponent, self).__init__(*args, **kwargs)
+
+ def fetch_pending(self, deleted):
+
+ if (not deleted):
+ objs = VPGWCComponent.get_tenant_objects().filter(
+ Q(enacted__lt=F('updated')) | Q(enacted=None), Q(lazy_blocked=False))
+ else:
+
+ objs = VPGWCComponent.get_deleted_tenant_objects()
+
+ return objs
+
+ def get_extra_attributes(self, o):
+ return {"display_message": o.display_message, "s5s8_pgw_tag": o.s5s8_pgw_tag}
diff --git a/xos/synchronizers/vpgwc/steps/sync_vpgwc.yaml b/xos/synchronizers/vpgwc/steps/sync_vpgwc.yaml
new file mode 100644
index 0000000..a793976
--- /dev/null
+++ b/xos/synchronizers/vpgwc/steps/sync_vpgwc.yaml
@@ -0,0 +1,13 @@
+---
+- hosts: {{ instance_name }}
+ gather_facts: False
+ connection: ssh
+ user: ubuntu
+ sudo: yes
+ tasks:
+
+ - name: write message
+ shell: echo "{{ display_message }}" > /var/tmp/index.html
+
+ - name: setup s5s8_pgw interface config
+ shell: ./start_3gpp_int.sh eth1 {{ s5s8_pgw_tag }} {{ s5s8_pgw_ip }}/24
diff --git a/xos/synchronizers/vpgwc/stop.sh b/xos/synchronizers/vpgwc/stop.sh
new file mode 100755
index 0000000..299641a
--- /dev/null
+++ b/xos/synchronizers/vpgwc/stop.sh
@@ -0,0 +1,2 @@
+# Kill the observer
+pkill -9 -f vpgwc-synchronizer.py
diff --git a/xos/synchronizers/vpgwc/vpgwc-synchronizer.py b/xos/synchronizers/vpgwc/vpgwc-synchronizer.py
new file mode 100755
index 0000000..95f4081
--- /dev/null
+++ b/xos/synchronizers/vpgwc/vpgwc-synchronizer.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+
+# This imports and runs ../../xos-observer.py
+# Runs the standard XOS observer
+
+import importlib
+import os
+import sys
+observer_path = os.path.join(os.path.dirname(
+ os.path.realpath(__file__)), "../../synchronizers/base")
+sys.path.append(observer_path)
+mod = importlib.import_module("xos-synchronizer")
+mod.main()
diff --git a/xos/synchronizers/vpgwc/vpgwc_config b/xos/synchronizers/vpgwc/vpgwc_config
new file mode 100755
index 0000000..c6b9c23
--- /dev/null
+++ b/xos/synchronizers/vpgwc/vpgwc_config
@@ -0,0 +1,40 @@
+# Required by XOS
+[db]
+name=xos
+user=postgres
+password=password
+host=localhost
+port=5432
+
+# Required by XOS
+[api]
+nova_enabled=True
+
+# Sets options for the observer
+[observer]
+# Optional name
+name=vpgwc
+# This is the location to the dependency graph you generate
+dependency_graph=/opt/xos/synchronizers/vpgwc/model-deps
+# The location of your SyncSteps
+steps_dir=/opt/xos/synchronizers/vpgwc/steps
+# A temporary directory that will be used by ansible
+sys_dir=/opt/xos/synchronizers/vpgwc/sys
+# Location of the file to save logging messages to the backend log is often used
+logfile=/var/log/xos_backend.log
+# If this option is true, then nothing will change, we simply pretend to run
+pretend=False
+# If this is False then XOS will use an exponential backoff when the observer
+# fails, since we will be waiting for an instance, we don't want this.
+backoff_disabled=True
+# We want the output from ansible to be logged
+save_ansible_output=True
+# This determines how we SSH to a client, if this is set to True then we try
+# to ssh using the instance name as a proxy, if this is disabled we ssh using
+# the NAT IP of the instance. On CloudLab the first option will fail so we must
+# set this to False
+proxy_ssh=True
+proxy_ssh_key=/root/setup/id_rsa
+proxy_ssh_user=root
+[networking]
+use_vtn=True
diff --git a/xos/tools/rebuild.py b/xos/tools/rebuild.py
new file mode 100755
index 0000000..dc2c482
--- /dev/null
+++ b/xos/tools/rebuild.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+import os
+import sys
+sys.path.append("/opt/xos")
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xos.settings")
+import django
+from core.models import XOS
+django.setup()
+
+xoses = XOS.objects.all()
+if not xoses:
+ print "There is no XOS model"
+
+for xos in xoses:
+ xos.rebuild()
+
diff --git a/xos/tosca/custom_types/exampleservice.m4 b/xos/tosca/custom_types/exampleservice.m4._unused
similarity index 100%
copy from xos/tosca/custom_types/exampleservice.m4
copy to xos/tosca/custom_types/exampleservice.m4._unused
diff --git a/xos/tosca/custom_types/exampleservice.yaml b/xos/tosca/custom_types/exampleservice.yaml._unused
similarity index 100%
copy from xos/tosca/custom_types/exampleservice.yaml
copy to xos/tosca/custom_types/exampleservice.yaml._unused
diff --git a/xos/tosca/custom_types/xos.m4 b/xos/tosca/custom_types/xos.m4
index 109fc1d..9497b6d 100644
--- a/xos/tosca/custom_types/xos.m4
+++ b/xos/tosca/custom_types/xos.m4
@@ -6,6 +6,43 @@
include(macros.m4)
node_types:
+ tosca.nodes.XOS:
+ derived_from: tosca.nodes.Root
+ description: The root of XOS
+ properties:
+ xos_base_props
+ ui_port:
+ type: integer
+ required: false
+ description: TCP port of user interface
+ bootstrap_ui_port:
+ type: integer
+ required: false
+ descrption: TCP port of bootstrap user interface
+ docker_project_name:
+ type: string
+ required: false
+ description: Docker project name
+ enable_build:
+ type: boolean
+ required: false
+ description: True if XOS build should be enabled
+
+
+ tosca.nodes.XOSVolume:
+ derived_from: tosca.nodes.Root
+ description: A volume that should be attached to the XOS docker container
+ properties:
+ xos_base_props
+ host_path:
+ type: string
+ required: false
+ description: path of resource on host
+ read_only:
+ type: boolean
+ required: false
+ description: True if mount read only
+
tosca.nodes.Service:
derived_from: tosca.nodes.Root
description: >
@@ -17,6 +54,68 @@
xos_base_props
xos_base_service_props
+ tosca.nodes.ServiceController:
+ derived_from: tosca.nodes.Root
+ description: >
+ An XOS Service Controller.
+ properties:
+ xos_base_props
+ base_url:
+ type: string
+ required: false
+ description: Base url, to allow resources to use relative URLs
+ models:
+ type: string
+ required: false
+ description: url of models.py
+ admin:
+ type: string
+ required: false
+ description: url of admin.py
+ synchronizer:
+ type: string
+ required: false
+ description: url of synchronizer manifest
+ tosca_custom_types:
+ type: string
+ required: false
+ description: url of tosca custom_types
+ rest_service:
+ type: string
+ required: false
+ description: url of REST API service file
+ rest_tenant:
+ type: string
+ required: false
+ description: url of REST API tenant file
+ private_key:
+ type: string
+ required: false
+ description: private key
+ public_key:
+ type: string
+ required: false
+ description: public key
+
+ tosca.nodes.ServiceControllerResource:
+ derived_from: tosca.nodes.Root
+ description: >
+ An XOS Service Resource.
+ properties:
+ xos_base_props
+ kind:
+ type: string
+ required: false
+ description: models, admin, django_library, synchronizer, rest, tosca_custom_types, or tosca_resource
+ format:
+ type: string
+ required: false
+ description: python, manifest, or docker
+ url:
+ type: string
+ required: false
+ description: url of resource, may be relative to base_url or absolute
+
tosca.nodes.Tenant:
derived_from: tosca.nodes.Root
description: >
@@ -1034,6 +1133,15 @@
tosca.relationships.UsesAgent:
derived_from: tosca.relationships.Root
+ tosca.relationships.HasResource:
+ derived_from: tosca.relationships.Root
+
+ tosca.relationships.UsedByController:
+ derived_from: tosca.relationships.Root
+
+ tosca.relationships.UsedByXOS:
+ derived_from: tosca.relationships.Root
+
tosca.capabilities.xos.Service:
derived_from: tosca.capabilities.Root
description: An XOS Service
diff --git a/xos/tosca/custom_types/xos.yaml b/xos/tosca/custom_types/xos.yaml
index 8b4c669..66229d5 100644
--- a/xos/tosca/custom_types/xos.yaml
+++ b/xos/tosca/custom_types/xos.yaml
@@ -21,6 +21,73 @@
node_types:
+ tosca.nodes.XOS:
+ derived_from: tosca.nodes.Root
+ description: The root of XOS
+ properties:
+ no-delete:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to delete this object
+ no-create:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to create this object
+ no-update:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to update this object
+ replaces:
+ type: string
+ required: false
+ descrption: Replaces/renames this object
+ ui_port:
+ type: integer
+ required: false
+ description: TCP port of user interface
+ bootstrap_ui_port:
+ type: integer
+ required: false
+ descrption: TCP port of bootstrap user interface
+ docker_project_name:
+ type: string
+ required: false
+ description: Docker project name
+ enable_build:
+ type: boolean
+ required: false
+ description: True if XOS build should be enabled
+
+
+ tosca.nodes.XOSVolume:
+ derived_from: tosca.nodes.Root
+ description: A volume that should be attached to the XOS docker container
+ properties:
+ no-delete:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to delete this object
+ no-create:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to create this object
+ no-update:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to update this object
+ replaces:
+ type: string
+ required: false
+ descrption: Replaces/renames this object
+ host_path:
+ type: string
+ required: false
+ description: path of resource on host
+ read_only:
+ type: boolean
+ required: false
+ description: True if mount read only
+
tosca.nodes.Service:
derived_from: tosca.nodes.Root
description: >
@@ -80,6 +147,98 @@
required: false
description: Version number of Service.
+ tosca.nodes.ServiceController:
+ derived_from: tosca.nodes.Root
+ description: >
+ An XOS Service Controller.
+ properties:
+ no-delete:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to delete this object
+ no-create:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to create this object
+ no-update:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to update this object
+ replaces:
+ type: string
+ required: false
+ descrption: Replaces/renames this object
+ base_url:
+ type: string
+ required: false
+ description: Base url, to allow resources to use relative URLs
+ models:
+ type: string
+ required: false
+ description: url of models.py
+ admin:
+ type: string
+ required: false
+ description: url of admin.py
+ synchronizer:
+ type: string
+ required: false
+ description: url of synchronizer manifest
+ tosca_custom_types:
+ type: string
+ required: false
+ description: url of tosca custom_types
+ rest_service:
+ type: string
+ required: false
+ description: url of REST API service file
+ rest_tenant:
+ type: string
+ required: false
+ description: url of REST API tenant file
+ private_key:
+ type: string
+ required: false
+ description: private key
+ public_key:
+ type: string
+ required: false
+ description: public key
+
+ tosca.nodes.ServiceControllerResource:
+ derived_from: tosca.nodes.Root
+ description: >
+ An XOS Service Resource.
+ properties:
+ no-delete:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to delete this object
+ no-create:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to create this object
+ no-update:
+ type: boolean
+ default: false
+ description: Do not allow Tosca to update this object
+ replaces:
+ type: string
+ required: false
+ descrption: Replaces/renames this object
+ kind:
+ type: string
+ required: false
+ description: models, admin, django_library, synchronizer, rest, tosca_custom_types, or tosca_resource
+ format:
+ type: string
+ required: false
+ description: python, manifest, or docker
+ url:
+ type: string
+ required: false
+ description: url of resource, may be relative to base_url or absolute
+
tosca.nodes.Tenant:
derived_from: tosca.nodes.Root
description: >
@@ -881,6 +1040,8 @@
required: false
description: list of access devices, in format "uplink vlan", multiple entries separated by commas
+# XXX - uncomment if we want access device to be specified as separate Tosca
+# objects, rather than encoding them into VOLTDevice.access_devices
# tosca.nodes.AccessDevice:
# derived_from: tosca.nodes.Root
# description: >
@@ -1856,6 +2017,18 @@
tosca.relationships.MemberOfDevice:
derived_from: tosca.relationships.Root
+ tosca.relationships.UsesAgent:
+ derived_from: tosca.relationships.Root
+
+ tosca.relationships.HasResource:
+ derived_from: tosca.relationships.Root
+
+ tosca.relationships.UsedByController:
+ derived_from: tosca.relationships.Root
+
+ tosca.relationships.UsedByXOS:
+ derived_from: tosca.relationships.Root
+
tosca.capabilities.xos.Service:
derived_from: tosca.capabilities.Root
description: An XOS Service
diff --git a/xos/tosca/resources/exampleservice.py b/xos/tosca/resources/exampleservice._unused
similarity index 100%
rename from xos/tosca/resources/exampleservice.py
rename to xos/tosca/resources/exampleservice._unused
diff --git a/xos/tosca/resources/exampletenant.py b/xos/tosca/resources/exampletenant._unused
similarity index 100%
rename from xos/tosca/resources/exampletenant.py
rename to xos/tosca/resources/exampletenant._unused
diff --git a/xos/tosca/resources/servicecontroller.py b/xos/tosca/resources/servicecontroller.py
new file mode 100644
index 0000000..34b95c2
--- /dev/null
+++ b/xos/tosca/resources/servicecontroller.py
@@ -0,0 +1,42 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+
+from core.models import ServiceController, ServiceControllerResource
+
+from xosresource import XOSResource
+
+class XOSServiceController(XOSResource):
+ provides = "tosca.nodes.ServiceController"
+ xos_model = ServiceController
+ copyin_props = ["base_url"]
+
+ def postprocess_resource_prop(self, obj, kind, format):
+ value = self.get_property(kind)
+ if value:
+ scr = ServiceControllerResource.objects.filter(service_controller=obj, kind=kind, format=format)
+ if scr:
+ scr=scr[0]
+ if scr.url != value:
+ self.info("updating resource %s" % kind)
+ scr.url = value
+ scr.save()
+ else:
+ self.info("adding resource %s" % kind)
+ scr = ServiceControllerResource(service_controller=obj, name=kind, kind=kind, format=format, url=value)
+ scr.save()
+
+ def postprocess(self, obj):
+ # allow these common resource to be specified directly by the ServiceController tosca object
+ self.postprocess_resource_prop(obj, "models", "python")
+ self.postprocess_resource_prop(obj, "admin", "python")
+ self.postprocess_resource_prop(obj, "tosca_custom_types", "yaml")
+ self.postprocess_resource_prop(obj, "synchronizer", "manifest")
+ self.postprocess_resource_prop(obj, "private_key", "raw")
+ self.postprocess_resource_prop(obj, "public_key", "raw")
+ self.postprocess_resource_prop(obj, "rest_service", "python")
+ self.postprocess_resource_prop(obj, "rest_tenant", "python")
+
diff --git a/xos/tosca/resources/servicecontrollerresource.py b/xos/tosca/resources/servicecontrollerresource.py
new file mode 100644
index 0000000..96ea83d
--- /dev/null
+++ b/xos/tosca/resources/servicecontrollerresource.py
@@ -0,0 +1,27 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+
+from core.models import ServiceControllerResource, ServiceController
+
+from xosresource import XOSResource
+
+class XOSServiceControllerResource(XOSResource):
+ provides = "tosca.nodes.ServiceControllerResource"
+ xos_model = ServiceControllerResource
+ copyin_props = ["kind", "format", "url"]
+
+ def get_xos_args(self, throw_exception=True):
+ args = super(XOSServiceControllerResource, self).get_xos_args()
+
+ controller_name = self.get_requirement("tosca.relationships.UsedByController", throw_exception=throw_exception)
+ if controller_name:
+ args["service_controller"] = self.get_xos_object(ServiceController, throw_exception=throw_exception, name=controller_name)
+
+ return args
+
+
+
diff --git a/xos/tosca/resources/vpgwccomponent.py b/xos/tosca/resources/vpgwccomponent.py
new file mode 100644
index 0000000..3b87111
--- /dev/null
+++ b/xos/tosca/resources/vpgwccomponent.py
@@ -0,0 +1,40 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+import pdb
+
+from services.mcord.models import VPGWCComponent, MCORDService
+
+from xosresource import XOSResource
+
+class XOSVPGWCComponent(XOSResource):
+ provides = "tosca.nodes.VPGWCComponent"
+ xos_model = VPGWCComponent
+ copyin_props = ["s5s8_pgw_tag", "display_message"]
+ name_field = None
+
+ def get_xos_args(self, throw_exception=True):
+ args = super(XOSVPGWCComponent, self).get_xos_args()
+
+ provider_name = self.get_requirement("tosca.relationships.MemberOfService", throw_exception=throw_exception)
+ if provider_name:
+ args["provider_service"] = self.get_xos_object(MCORDService, throw_exception=throw_exception, name=provider_name)
+
+ return args
+
+ def get_existing_objs(self):
+ args = self.get_xos_args(throw_exception=False)
+ provider_service = args.get("provider", None)
+ if provider_service:
+ return [ self.get_xos_object(provider_service=provider_service) ]
+ return []
+
+ def postprocess(self, obj):
+ pass
+
+ def can_delete(self, obj):
+ return super(XOSVPGWCComponent, self).can_delete(obj)
+
diff --git a/xos/tosca/resources/xosmodel.py b/xos/tosca/resources/xosmodel.py
new file mode 100644
index 0000000..188bb4f
--- /dev/null
+++ b/xos/tosca/resources/xosmodel.py
@@ -0,0 +1,30 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+
+from core.models import XOS, XOSVolume
+
+from xosresource import XOSResource
+
+class XOSXOS(XOSResource):
+ provides = "tosca.nodes.XOS"
+ xos_model = XOS
+ copyin_props = ["ui_port", "bootstrap_ui_port", "docker_project_name", "enable_build"]
+
+class XOSVolume(XOSResource):
+ provides = "tosca.nodes.XOSVolume"
+ xos_model = XOSVolume
+ copyin_props = ["host_path"]
+ name_field = "container_path"
+
+ def get_xos_args(self, throw_exception=True):
+ args = super(XOSVolume, self).get_xos_args()
+
+ xos_name = self.get_requirement("tosca.relationships.UsedByXOS", throw_exception=throw_exception)
+ if xos_name:
+ args["xos"] = self.get_xos_object(XOS, throw_exception=throw_exception, name=xos_name)
+
+ return args
diff --git a/xos/tosca/resources/xosresource.py b/xos/tosca/resources/xosresource.py
index 012f814..f65a231 100644
--- a/xos/tosca/resources/xosresource.py
+++ b/xos/tosca/resources/xosresource.py
@@ -189,6 +189,17 @@
raise Exception("artifact %s not found" % name)
+ def intrinsic_path_join(self, obj=None, name=None, varname=None, method=None):
+ if obj!="SELF":
+ raise Exception("only SELF is supported for get_artifact first arg")
+ if method!="ENV_VAR":
+ raise Exception("only ENV_VAR is supported for get_artifact fourth arg")
+
+ if not (name in os.environ):
+ raise Exception("environment variable %s not found" % name)
+
+ return os.path.join(os.environ[name], varname)
+
def try_intrinsic_function(self, v):
try:
jsv = v.replace("'", '"')
@@ -205,6 +216,8 @@
return self.intrinsic_get_artifact(*jsv["get_artifact"])
elif "get_script_env" in jsv:
return self.intrinsic_get_script_env(*jsv["get_script_env"])
+ elif "path_join" in jsv:
+ return self.intrinsic_path_join(*jsv["path_join"])
return v
diff --git a/xos/xos/settings.py b/xos/xos/settings.py
index 7835689..9f20937 100644
--- a/xos/xos/settings.py
+++ b/xos/xos/settings.py
@@ -197,6 +197,13 @@
'rest_framework_swagger',
)
+# add services that were configured by xosbuilder to INSTALLED_APPS
+if os.path.exists("/opt/xos/xos/xosbuilder_app_list"):
+ for line in file("/opt/xos/xos/xosbuilder_app_list").readlines():
+ line = line.strip()
+ if line:
+ INSTALLED_APPS = list(INSTALLED_APPS) + [line]
+
if DJANGO_VERSION[1] >= 7:
# if django >= 1.7, then change the admin module
INSTALLED_APPS = list(INSTALLED_APPS)