Exporting Tosca from UI
Change-Id: Ie7e58ac5bd51a56d028daa1c1e2577e7723a8297
diff --git a/views/ngXosViews/serviceGrid/src/index.html b/views/ngXosViews/serviceGrid/src/index.html
index d9e7fec..8f95e59 100644
--- a/views/ngXosViews/serviceGrid/src/index.html
+++ b/views/ngXosViews/serviceGrid/src/index.html
@@ -14,6 +14,9 @@
</div>
<!-- bower:js -->
+<script src="vendor/js-yaml/dist/js-yaml.js"></script>
+<script src="vendor/jszip/dist/jszip.js"></script>
+<script src="vendor/file-saver/FileSaver.js"></script>
<script src="vendor/jquery/dist/jquery.js"></script>
<script src="vendor/angular/angular.js"></script>
<script src="vendor/angular-mocks/angular-mocks.js"></script>
@@ -26,10 +29,19 @@
<script src="vendor/Chart.js/Chart.js"></script>
<script src="vendor/angular-chart.js/dist/angular-chart.js"></script>
<script src="vendor/d3/d3.js"></script>
+<script src="vendor/angular-recursion/angular-recursion.js"></script>
<!-- endbower -->
<!-- endjs -->
<!-- inject:js -->
<script src="/../../../xos/core/xoslib/static/js/vendor/ngXosHelpers.js"></script>
<script src="/.tmp/main.js"></script>
+<script src="/.tmp/tosca_encoder.service.js"></script>
+<script src="/.tmp/slices_encorder.service.js"></script>
+<script src="/.tmp/site_encode.service.js"></script>
+<script src="/.tmp/service_encorder.service.js"></script>
<script src="/.tmp/service-graph.js"></script>
+<script src="/.tmp/networks_encoder.service.js"></script>
+<script src="/.tmp/network_template_encoder.service.js"></script>
+<script src="/.tmp/image_encoder.service.js"></script>
+<script src="/.tmp/archive_manager.service.js"></script>
<!-- endinject -->
\ No newline at end of file
diff --git a/views/ngXosViews/serviceGrid/src/js/archive_manager.service.js b/views/ngXosViews/serviceGrid/src/js/archive_manager.service.js
new file mode 100644
index 0000000..92681ee
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/js/archive_manager.service.js
@@ -0,0 +1,39 @@
+/**
+ * © OpenCORD
+ *
+ * Visit http://guide.xosproject.org/devguide/addview/ for more information
+ *
+ * Created by teone on 6/22/16.
+ */
+
+(function () {
+ 'use strict';
+
+ /**
+ * @ngdoc service
+ * @name xos.ArchiveManager.serviceGrid
+ **/
+
+ angular.module('xos.serviceGrid')
+ .service('ArchiveManager', function(){
+ this.createArchive = () => {
+ this.archive = new JSZip();
+ };
+
+ this.addFile = (fileName, content) => {
+ this.archive.file(fileName, content);
+ };
+
+ this.download = (name) => {
+ console.log(this.archive);
+ this.archive.generateAsync({type: 'blob'})
+ .then(function(content) {
+ saveAs(content, `${name}.zip`);
+ })
+ .catch(e => {
+ console.log(e);
+ });
+ };
+ });
+})();
+
diff --git a/views/ngXosViews/serviceGrid/src/js/image_encoder.service.js b/views/ngXosViews/serviceGrid/src/js/image_encoder.service.js
new file mode 100644
index 0000000..972023c
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/js/image_encoder.service.js
@@ -0,0 +1,40 @@
+/**
+ * © OpenCORD
+ *
+ * Visit http://guide.xosproject.org/devguide/addview/ for more information
+ *
+ * Created by teone on 6/22/16.
+ */
+
+(function () {
+ 'use strict';
+
+ /**
+ * @ngdoc service
+ * @name xos.ImageEncoder.serviceGrid
+ **/
+
+ // TODO write tests
+ angular.module('xos.serviceGrid')
+ .service('ImageEncoder', function($q, Images){
+
+ this.buildTosca = (imageId, toscaSpec) => {
+ const d = $q.defer();
+
+ Images.get({id: imageId}).$promise
+ .then(image => {
+ const toscaObj = {};
+ toscaObj[`image#${image.name}`] = {
+ type: 'tosca.nodes.Image'
+ };
+ angular.extend(toscaSpec.topology_template.node_templates, toscaObj);
+ d.resolve([toscaSpec, image]);
+ })
+ .catch(d.reject);
+
+
+ return d.promise;
+ };
+ });
+})();
+
diff --git a/views/ngXosViews/serviceGrid/src/js/main.js b/views/ngXosViews/serviceGrid/src/js/main.js
index 88a0f95..fc75b2f 100644
--- a/views/ngXosViews/serviceGrid/src/js/main.js
+++ b/views/ngXosViews/serviceGrid/src/js/main.js
@@ -27,7 +27,7 @@
bindToController: true,
controllerAs: 'vm',
templateUrl: 'templates/service-grid.tpl.html',
- controller: function(Services, _){
+ controller: function(Services, ToscaEncoder, _){
this.tableConfig = {
columns: [
@@ -65,7 +65,21 @@
filter: 'field',
order: {
field: 'name'
- }
+ },
+ actions: [
+ {
+ label: 'export',
+ icon: 'export',
+ cb: service => {
+ this.tosca = '';
+ ToscaEncoder.serviceToTosca(service)
+ .then(tosca => {
+ this.showFeedback = true;
+ this.tosca = tosca;
+ });
+ }
+ }
+ ]
};
// retrieving user list
diff --git a/views/ngXosViews/serviceGrid/src/js/network_template_encoder.service.js b/views/ngXosViews/serviceGrid/src/js/network_template_encoder.service.js
new file mode 100644
index 0000000..1784d90
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/js/network_template_encoder.service.js
@@ -0,0 +1,40 @@
+/**
+ * © OpenCORD
+ *
+ * Visit http://guide.xosproject.org/devguide/addview/ for more information
+ *
+ * Created by teone on 6/22/16.
+ */
+
+(function () {
+ 'use strict';
+
+ /**
+ * @ngdoc service
+ * @name xos.NetworkTemplateEncoder.serviceGrid
+ **/
+
+ // TODO write tests
+ angular.module('xos.serviceGrid')
+ .service('NetworkTemplateEncoder', function($q, Networkstemplates){
+
+ this.buildTosca = (templateId, toscaSpec) => {
+ const d = $q.defer();
+ Networkstemplates.get({id: templateId}).$promise
+ .then(template => {
+ const toscaObj = {};
+ toscaObj[`network_template#${template.name}`] = {
+ type: 'tosca.nodes.NetworkTemplate'
+ };
+ angular.extend(toscaSpec.topology_template.node_templates, toscaObj);
+ d.resolve([toscaSpec, template]);
+ })
+ .catch(e => {
+ d.reject(e);
+ });
+
+ return d.promise;
+ };
+ });
+})();
+
diff --git a/views/ngXosViews/serviceGrid/src/js/networks_encoder.service.js b/views/ngXosViews/serviceGrid/src/js/networks_encoder.service.js
new file mode 100644
index 0000000..1e6adee
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/js/networks_encoder.service.js
@@ -0,0 +1,132 @@
+/**
+ * © OpenCORD
+ *
+ * Visit http://guide.xosproject.org/devguide/addview/ for more information
+ *
+ * Created by teone on 6/22/16.
+ */
+
+(function () {
+ 'use strict';
+
+ /**
+ * @ngdoc service
+ * @name xos.NetworkEncoder.serviceGrid
+ **/
+
+ // TODO write tests
+ angular.module('xos.serviceGrid')
+ .service('NetworkEncoder', function($q, Networks, NetworkTemplateEncoder){
+
+ this.buildTosca = (networks, toscaSpec) => {
+
+ const apiNetworks = angular.copy(networks);
+
+ // store here the promise that will build the dependency structure
+ let dependency = {
+ };
+
+ const d = $q.defer();
+
+ try {
+ networks = _.reduce(networks, (obj, n) => {
+ obj[`network#${n.name}`] = {
+ type: 'tosca.nodes.network.Network.XOS',
+ requirements: []
+ };
+
+ // for each network slice, add requirement
+ if(angular.isDefined(n.slices)) {
+ _.forEach(n.slices, s => {
+ let owner = {
+ owner: {
+ node: s.name,
+ relationship: 'tosca.relationships.MemberOfSlice'
+ }
+ };
+
+ let conn = {
+ connection: {
+ node: s.name,
+ relationship: 'tosca.relationships.ConnectsToSlice'
+ }
+ };
+ obj[`network#${n.name}`].requirements.push(owner, conn);
+ });
+
+ if(angular.isDefined(n.template)){
+ dependency[n.name] = NetworkTemplateEncoder.buildTosca(n.template, toscaSpec);
+ }
+ }
+
+ return obj;
+
+ }, {});
+
+ // if we have dependency wait for them
+ if(Object.keys(dependency).length > 0){
+ $q.all(dependency)
+ .then(deps => {
+ // NOTE how to make this readable??
+ if(deps){
+
+ // for each property in deps
+ for(let k of Object.keys(deps)){
+ let [tosca, template] = deps[k];
+ networks[`network#${k}`].requirements.push({
+ network_template: {
+ node: `network_template#${template.name}`,
+ relationship: 'tosca.relationships.UsesNetworkTemplate'
+ }
+ });
+ angular.extend(toscaSpec, tosca);
+ }
+ }
+ angular.extend(toscaSpec.topology_template.node_templates, networks);
+ d.resolve([toscaSpec, apiNetworks]);
+ })
+ .catch(e => {
+ throw new Error(e);
+ });
+ }
+ //else resolve directly
+ else {
+ angular.extend(toscaSpec.topology_template.node_templates, networks);
+ d.resolve([toscaSpec, apiNetworks]);
+ }
+ }
+ catch(e){
+ d.reject(e);
+ }
+
+ return d.promise;
+ };
+
+ this.getSliceNetworks = (slice, toscaSpec) => {
+ const d = $q.defer();
+ Networks.query({owner: slice.id}).$promise
+ .then((networks) => {
+ // check that all the network this slice own are listed in the slice
+ // does this make sense?
+ networks = _.filter(networks, n => {
+ return slice.networks.indexOf(n.id) !== -1;
+ });
+
+ // denormalize slice inside network
+ networks = networks.map(n => {
+ let idx = n.slices.indexOf(slice.id);
+ n.slices[idx] = slice;
+ return n;
+ });
+
+ this.buildTosca(networks, toscaSpec)
+ .then(d.resolve)
+ .catch(d.reject);
+
+ });
+
+ return d.promise;
+ }
+ });
+})();
+
diff --git a/views/ngXosViews/serviceGrid/src/js/service_encorder.service.js b/views/ngXosViews/serviceGrid/src/js/service_encorder.service.js
new file mode 100644
index 0000000..485d46a
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/js/service_encorder.service.js
@@ -0,0 +1,125 @@
+/**
+ * © OpenCORD
+ *
+ * Visit http://guide.xosproject.org/devguide/addview/ for more information
+ *
+ * Created by teone on 6/22/16.
+ */
+
+(function () {
+ 'use strict';
+
+ /**
+ * @ngdoc service
+ * @name xos.toscaExporter.serviceGrid
+ **/
+
+ angular.module('xos.serviceGrid')
+ .service('ServiceEncoder', function($q, ArchiveManager, Tenants, Services){
+
+ const serviceTypes = {
+ fabric: 'tosca.nodes.FabricService',
+ onos: 'tosca.nodes.ONOSService',
+ vCPE: 'tosca.nodes.VSGService',
+ vOLT: 'tosca.nodes.VOLTService',
+ vROUTER: 'tosca.nodes.VRouterService',
+ VTN: 'tosca.nodes.VTNService',
+ vTR: 'tosca.nodes.Service'
+ };
+
+ this.formatServiceProperties = (service, toscaSpec) => {
+ const d = $q.defer();
+ const serviceName = `service#${service.name}`;
+ // create the structure to hold the service
+ toscaSpec.topology_template.node_templates[serviceName] = {};
+ toscaSpec.topology_template.node_templates[serviceName].type = serviceTypes[service.kind] || 'tosca.nodes.Service';
+
+ const res = {
+ properties: {
+ kind: service.kind
+ }
+ };
+
+ if(angular.isDefined(service.view_url)){
+ res.properties.view_url = service.view_url;
+ }
+
+ if(angular.isDefined(service.icon_url)){
+ res.properties.icon_url = service.icon_url;
+ }
+
+ if(angular.isDefined(service.private_key_fn)){
+ res.properties.private_key_fn = service.private_key_fn;
+ }
+
+ if(angular.isDefined(service.public_key)){
+ ArchiveManager.addFile(`${service.name}_rsa.pub`, service.public_key);
+ res.properties.public_key = '{ get_artifact: [ SELF, pubkey, LOCAL_FILE] }'
+ res['artifacts'] = {
+ pubkey: `/opt/xos/tosca/${service.name}/${service.name}_rsa.pub`
+ };
+ toscaSpec.topology_template.node_templates[serviceName].artifacts = res.artifacts;
+ }
+
+ toscaSpec.topology_template.node_templates[serviceName].properties = res.properties;
+ d.resolve(toscaSpec);
+ return d.promise;
+ };
+
+ this.getServiceRequirements = (service, toscaSpec) => {
+ const d = $q.defer();
+
+ Tenants.query({subscriber_service: service.id}).$promise
+ .then(tenants => {
+ const services = [];
+ // avoid multiple request for the same service
+ tenants = _.uniqBy(tenants, 'provider_service');
+
+ _.forEach(tenants, t => {
+ services.push(Services.get({id: t.provider_service}).$promise);
+ });
+
+ return $q.all(services)
+ })
+ .then((deps) => {
+ // Get the provider service and create an array of unique names
+ let requirements = _.reduce(deps, (list, d) => list.concat(d.name), []);
+
+ // create a object for requirements, later will parsed in Yaml
+ requirements = _.reduce(requirements, (list, r) => {
+ let name = `${r}_tenant`;
+ let obj = {};
+ obj[name] = {
+ node: `service#${r}`,
+ relationship: 'tosca.relationships.TenantOfService'
+ };
+ return list.concat(obj);
+ }, []);
+
+ if(requirements.length > 0){
+
+ // NOTE set a reference to the requirements in tosca
+ _.forEach(requirements, r => {
+ let reqName = r[Object.keys(r)[0]].node;
+ toscaSpec.topology_template.node_templates[reqName] = {
+ type: 'tosca.nodes.Service',
+ properties: {
+ 'no-create': true,
+ 'no-delete': true,
+ 'no-update': true
+ }
+ };
+ });
+
+ const serviceName = `service#${service.name}`;
+ toscaSpec.topology_template.node_templates[serviceName].requirements = requirements;
+ }
+
+ d.resolve(toscaSpec);
+ });
+
+ return d.promise;
+ };
+ });
+})();
+
diff --git a/views/ngXosViews/serviceGrid/src/js/site_encode.service.js b/views/ngXosViews/serviceGrid/src/js/site_encode.service.js
new file mode 100644
index 0000000..6bb1d99
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/js/site_encode.service.js
@@ -0,0 +1,39 @@
+/**
+ * © OpenCORD
+ *
+ * Visit http://guide.xosproject.org/devguide/addview/ for more information
+ *
+ * Created by teone on 6/22/16.
+ */
+
+(function () {
+ 'use strict';
+
+ /**
+ * @ngdoc service
+ * @name xos.SiteEncoder.serviceGrid
+ **/
+
+ angular.module('xos.serviceGrid')
+ .service('SiteEncoder', function($q, Sites){
+
+ this.buildTosca = (siteId, toscaSpec) => {
+ const d = $q.defer();
+
+ Sites.get({id: siteId}).$promise
+ .then(site => {
+ const toscaObj = {};
+ toscaObj[`${site.name}`] = {
+ type: 'tosca.nodes.Site'
+ };
+ angular.extend(toscaSpec.topology_template.node_templates, toscaObj);
+ d.resolve([toscaSpec, site]);
+ })
+ .catch(d.reject);
+
+
+ return d.promise;
+ };
+ });
+})();
+
diff --git a/views/ngXosViews/serviceGrid/src/js/slices_encorder.service.js b/views/ngXosViews/serviceGrid/src/js/slices_encorder.service.js
new file mode 100644
index 0000000..f900d98
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/js/slices_encorder.service.js
@@ -0,0 +1,159 @@
+/**
+ * © OpenCORD
+ *
+ * Visit http://guide.xosproject.org/devguide/addview/ for more information
+ *
+ * Created by teone on 6/22/16.
+ */
+
+(function () {
+ 'use strict';
+
+ /**
+ * @ngdoc service
+ * @name xos.SlicesEncoder.serviceGrid
+ **/
+
+ angular.module('xos.serviceGrid')
+ .service('SlicesEncoder', function($q, _, Slices, SiteEncoder, ImageEncoder, NetworkEncoder){
+
+ this.buildTosca = (slices, toscaSpec, serviceName) => {
+ // store here the promise that will build the dependency structure
+ let dependency = {};
+
+ const d = $q.defer();
+ slices = _.reduce(slices, (obj, s) => {
+ obj[s.name] = {
+ type: 'tosca.nodes.Slice',
+ properties: {
+ network: s.network
+ },
+ requirements: [
+ // by default slices are connected to management network
+ {
+ management: {
+ node: 'management',
+ relationship: 'tosca.relationships.ConnectsToNetwork'
+ }
+ },
+ ]
+ };
+
+ if(angular.isDefined(serviceName)){
+ let service = {};
+ service[`${serviceName}_service`] = {
+ node: `service#${serviceName}`,
+ relationship: 'tosca.relationships.MemberOfService'
+ };
+ obj[s.name].requirements.push(service);
+ }
+
+ if(angular.isDefined(s.description)){
+ obj[s.name].description = s.description;
+ }
+
+ if(angular.isDefined(s.site)){
+ dependency[`${s.name}#site`] = SiteEncoder.buildTosca(s.site, toscaSpec);
+ }
+ if(angular.isDefined(s.default_image)){
+ dependency[`${s.name}#image`] = ImageEncoder.buildTosca(s.default_image, toscaSpec);
+ }
+ if(angular.isDefined(s.networks) && s.networks.length > 0){
+ dependency[`${s.name}#management`] = NetworkEncoder.getSliceNetworks(s, toscaSpec);
+ }
+
+ return obj;
+ }, {});
+
+ // if we have dependency wait for them
+ if(Object.keys(dependency).length > 0){
+
+ let relationMap = {
+ site: 'tosca.relationships.MemberOfSite',
+ image: 'tosca.relationships.DefaultImage'
+ };
+
+ // NOTE create a reference to the management network
+ toscaSpec.topology_template.node_templates['management'] = {
+ type: 'tosca.nodes.network.Network.XOS',
+ properties: {
+ 'no-create': true,
+ 'no-delete': true,
+ 'no-update': true
+ }
+ };
+
+ $q.all(dependency)
+ .then(deps => {
+
+ for(let k of Object.keys(deps)){
+
+ // this is UGLY!!!
+ // we are passing the requirement type inside the object key
+ // in which the promise is stored.
+ // This let us build the requirements array
+ let [sliceName, requirementType] = k.split('#');
+
+ if(angular.isDefined(relationMap[requirementType])){
+
+ if(!slices[sliceName].requirements){
+ slices[sliceName].requirements = [];
+ }
+
+ let [tosca, resource] = deps[k];
+
+ let requirementObj = {};
+
+ let reqName;
+
+ // NOTE site have problem with prefixing
+ if(requirementType === 'site'){
+ reqName = resource.name;
+ }
+ else{
+ reqName = `${requirementType}#${resource.name}`;
+ }
+
+ requirementObj[requirementType] = {
+ node: reqName,
+ relationship: relationMap[requirementType]
+ };
+
+ slices[sliceName].requirements.push(requirementObj);
+
+ angular.extend(toscaSpec, tosca);
+ }
+
+ }
+ // here we add slices to tosca
+ angular.extend(toscaSpec.topology_template.node_templates, slices);
+ d.resolve(toscaSpec);
+ })
+ .catch(e => {
+ throw new Error(e);
+ });
+ }
+ //else resolve directly
+ else {
+ angular.extend(toscaSpec.topology_template.node_templates, slices);
+ d.resolve(toscaSpec);
+ }
+
+ return d.promise;
+ };
+
+ this.getServiceSlices = (service, toscaSpec) => {
+ const d = $q.defer();
+ Slices.query({service: service.id}).$promise
+ .then(slices => {
+ return this.buildTosca(slices, toscaSpec, service.name)
+ })
+ .then(slicesTosca => {
+ d.resolve(slicesTosca);
+ });
+
+ return d.promise;
+ };
+ });
+})();
+
diff --git a/views/ngXosViews/serviceGrid/src/js/tosca_encoder.service.js b/views/ngXosViews/serviceGrid/src/js/tosca_encoder.service.js
new file mode 100644
index 0000000..5798aca
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/js/tosca_encoder.service.js
@@ -0,0 +1,79 @@
+(function () {
+ 'use strict';
+
+ /**
+ * @ngdoc service
+ * @name xos.toscaExporter.serviceGrid
+ **/
+
+ angular.module('xos.serviceGrid')
+ .service('ToscaEncoder', function($q, _, ArchiveManager, ServiceEncoder, SlicesEncoder){
+
+ let toscaSpec = {
+ tosca_definitions_version: 'tosca_simple_yaml_1_0',
+ description: '',
+ imports: [
+ 'custom_types/xos.yaml'
+ ],
+ topology_template:{
+ node_templates: {}
+ }
+ };
+
+ /**
+ * @ngdoc method
+ * @name xos.serviceGrid.ToscaEncoder#$toYml
+ * @methodOf xos.serviceGrid.ToscaEncoder
+ * @description
+ * Convert a Json data structure to Yaml, use https://github.com/nodeca/js-yaml
+ * @param {Object} item A Json object to be converted
+ * @returns {string} The Yaml representation of the Json input
+ **/
+
+ this.toYml = (item) => {
+ return jsyaml.dump(item).replace(/'/g, '');
+ };
+
+ this.export = (service) => {
+ ArchiveManager.download(service.name);
+ const file = this.toYml(toscaSpec);
+ return file;
+ };
+
+ this.serviceToTosca = service => {
+
+ ArchiveManager.createArchive();
+ //clean
+ toscaSpec.topology_template.node_templates = {};
+
+ toscaSpec.description = `Just enough Tosca to get the ${service.humanReadableName} service up and running`;
+
+ const d = $q.defer();
+
+ // build service properties
+ ServiceEncoder.formatServiceProperties(service, toscaSpec)
+ .then(spec => {
+ return SlicesEncoder.getServiceSlices(service, spec);
+ })
+ // add required slices (and it will all the slice requirements)
+ .then((spec) => {
+ return ServiceEncoder.getServiceRequirements(service, spec);
+ })
+ // add required services (provider services)
+ .then((spec) => {
+ ArchiveManager.addFile(`${service.name}_service.yaml`, this.toYml(spec));
+
+ this.export(service);
+
+ d.resolve(this.toYml(spec));
+ })
+ .catch(e => {
+ d.reject(e);
+ });
+ return d.promise;
+
+ }
+
+ });
+
+}());
\ No newline at end of file
diff --git a/views/ngXosViews/serviceGrid/src/templates/service-grid.tpl.html b/views/ngXosViews/serviceGrid/src/templates/service-grid.tpl.html
index fa324b4..8e88e38 100644
--- a/views/ngXosViews/serviceGrid/src/templates/service-grid.tpl.html
+++ b/views/ngXosViews/serviceGrid/src/templates/service-grid.tpl.html
@@ -11,4 +11,16 @@
Tenancy Graph
</a> -->
</div>
-</div>
\ No newline at end of file
+</div>
+
+<div class="row">
+ <div class="col-xs-12">
+ <div class="alert alert-info" ng-show="vm.showFeedback">
+ Remember that you should copy any key artifact inside the container in <pre>/opt/xos/tosca</pre>!
+ </div>
+ </div>
+</div>
+
+<pre ng-show="vm.tosca">
+{{vm.tosca}}
+</pre>
\ No newline at end of file