Exporting Tosca from UI

Change-Id: Ie7e58ac5bd51a56d028daa1c1e2577e7723a8297
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