CORD-910 added RunTosca gRPC API

Change-Id: I657240df1d7c6d34da320295fecb61ce14cf964f
diff --git a/xos/grpc/apihelper.py b/xos/grpc/apihelper.py
index 04f22d2..6ce81b6 100644
--- a/xos/grpc/apihelper.py
+++ b/xos/grpc/apihelper.py
@@ -164,7 +164,7 @@
       obj.delete()
       return Empty()
 
-    def authenticate(self, context):
+    def authenticate(self, context, required=False):
         for (k, v) in context.invocation_metadata():
             if (k.lower()=="authorization"):
                 (method, auth) = v.split(" ",1)
@@ -185,6 +185,8 @@
                  print "authenticated sessionid %s as %s" % (v, user)
                  return user
 
+        if required:
+            raise XOSPermissionDenied("This API requires authentication")
 
         return None
 
diff --git a/xos/grpc/protos/utility.proto b/xos/grpc/protos/utility.proto
index 36b5ed0..4717581 100644
--- a/xos/grpc/protos/utility.proto
+++ b/xos/grpc/protos/utility.proto
@@ -15,6 +15,19 @@
     string sessionid = 1;
 };
 
+message ToscaRequest {
+    string recipe = 1;
+};
+
+message ToscaResponse {
+    enum ToscaStatus {
+        SUCCESS = 0;
+        ERROR = 1;
+    }
+    ToscaStatus status = 1;
+    string messages = 2;
+};
+
 service utility {
 
   rpc Login(LoginRequest) returns (LoginResponse) {
@@ -30,4 +43,18 @@
             body: "*"
         };
   }
+
+  rpc RunTosca(ToscaRequest) returns (ToscaResponse) {
+        option (google.api.http) = {
+            post: "/xosapi/v1/utility/tosca"
+            body: "*"
+        };
+  }
+
+  rpc DestroyTosca(ToscaRequest) returns (ToscaResponse) {
+        option (google.api.http) = {
+            post: "/xosapi/v1/utility/destroy_tosca"
+            body: "*"
+        };
+  }
 };
diff --git a/xos/grpc/tests/tosca.py b/xos/grpc/tests/tosca.py
new file mode 100644
index 0000000..cc4f6bd
--- /dev/null
+++ b/xos/grpc/tests/tosca.py
@@ -0,0 +1,61 @@
+import random
+import string
+import sys
+sys.path.append("..")
+
+import grpc_client
+from testconfig import *
+
+SLICE_NAME="mysite_" + ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10))
+
+TOSCA_RECIPE="""tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Just some test...
+
+imports:
+   - custom_types/xos.yaml
+
+topology_template:
+  node_templates:
+    mysite:
+      type: tosca.nodes.Site
+
+    %s:
+      type: tosca.nodes.Slice
+      requirements:
+          - slice:
+                node: mysite
+                relationship: tosca.relationships.MemberOfSite
+""" % SLICE_NAME
+
+print "tosca_test"
+
+c=grpc_client.SecureClient("xos-core.cord.lab", username=USERNAME, password=PASSWORD)
+request=grpc_client.ToscaRequest()
+request.recipe = TOSCA_RECIPE
+
+print "Execute"
+
+response=c.utility.RunTosca(request)
+
+if response.status == response.SUCCESS:
+    print "  success"
+else:
+    print "  failure"
+
+for line in response.messages.split("\n"):
+    print "    %s" % line
+
+print "Destroy"
+
+response = c.utility.DestroyTosca(request)
+
+if response.status == response.SUCCESS:
+    print "  success"
+else:
+    print "  failure"
+
+for line in response.messages.split("\n"):
+    print "    %s" % line
+
+print "Done"
diff --git a/xos/grpc/xos_utility_api.py b/xos/grpc/xos_utility_api.py
index eb580ac..f595849 100644
--- a/xos/grpc/xos_utility_api.py
+++ b/xos/grpc/xos_utility_api.py
@@ -1,5 +1,8 @@
 import base64
+import os
+import sys
 import time
+import traceback
 from protos import utility_pb2
 from google.protobuf.empty_pb2 import Empty
 
@@ -13,6 +16,12 @@
 from xos.exceptions import *
 from apihelper import XOSAPIHelperMixin
 
+# The Tosca engine expects to be run from /opt/xos/tosca/ or equivalent. It
+# needs some sys.path fixing up.
+import inspect
+currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
+toscadir = os.path.join(currentdir, "../tosca")
+
 class UtilityService(utility_pb2.utilityServicer, XOSAPIHelperMixin):
     def __init__(self, thread_pool):
         self.thread_pool = thread_pool
@@ -49,3 +58,49 @@
                     s.save()
         return Empty()
 
+    def RunTosca(self, request, context):
+        user=self.authenticate(context, required=True)
+
+        sys_path_save = sys.path
+        try:
+            sys.path.append(toscadir)
+            from tosca.engine import XOSTosca
+            xt = XOSTosca(request.recipe, parent_dir=toscadir, log_to_console=False)
+            xt.execute(user)
+        except:
+            response = utility_pb2.ToscaResponse()
+            response.status = response.ERROR
+            response.messages = traceback.format_exc()
+            return response
+        finally:
+            sys.path = sys_path_save
+
+        response = utility_pb2.ToscaResponse()
+        response.status = response.SUCCESS
+        response.messages = "\n".join(xt.log_msgs)
+
+        return response
+
+    def DestroyTosca(self, request, context):
+        user=self.authenticate(context, required=True)
+
+        sys_path_save = sys.path
+        try:
+            sys.path.append(toscadir)
+            from tosca.engine import XOSTosca
+            xt = XOSTosca(request.recipe, parent_dir=toscadir, log_to_console=False)
+            xt.destroy(user)
+        except:
+            response = utility_pb2.ToscaResponse()
+            response.status = response.ERROR
+            response.messages = traceback.format_exc()
+            return response
+        finally:
+            sys.path = sys_path_save
+
+        response = utility_pb2.ToscaResponse()
+        response.status = response.SUCCESS
+        response.messages = "\n".join(xt.log_msgs)
+
+        return response
+