CORD-967 Translate XOS Exceptions to gRPC status codes

Change-Id: I27d4b224e0c795d036747a814852b99834445829
diff --git a/xos/coreapi/apihelper.py b/xos/coreapi/apihelper.py
index b69ea72..a2a2a95 100644
--- a/xos/coreapi/apihelper.py
+++ b/xos/coreapi/apihelper.py
@@ -5,6 +5,7 @@
 import time
 from protos import xos_pb2
 from google.protobuf.empty_pb2 import Empty
+import grpc
 
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.auth import authenticate as django_authenticate
@@ -16,6 +17,25 @@
 from django.conf import settings
 SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
 
+def translate_exceptions(function):
+    def wrapper(*args, **kwargs):
+        try:
+            return function(*args, **kwargs)
+        except Exception, e:
+            if "context" in kwargs:
+                context = kwargs["context"]
+            else:
+                context = args[2]
+            context.set_details(str(e))
+            if (type(e) == XOSPermissionDenied):
+                context.set_code(grpc.StatusCode.PERMISSION_DENIED)
+            elif (type(e) == XOSValidationError):
+                context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
+            elif (type(e) == XOSNotAuthenticated):
+                context.set_code(grpc.StatusCode.UNAUTHENTICATED)
+            raise
+    return wrapper
+
 class XOSAPIHelperMixin(object):
     def __init__(self):
         import django.apps
diff --git a/xos/coreapi/xos_utility_api.py b/xos/coreapi/xos_utility_api.py
index 937bdaf..d9c3a92 100644
--- a/xos/coreapi/xos_utility_api.py
+++ b/xos/coreapi/xos_utility_api.py
@@ -16,7 +16,7 @@
 from django.db.models import F,Q
 from core.models import *
 from xos.exceptions import *
-from apihelper import XOSAPIHelperMixin
+from apihelper import XOSAPIHelperMixin, translate_exceptions
 
 # The Tosca engine expects to be run from /opt/xos/tosca/ or equivalent. It
 # needs some sys.path fixing up.
@@ -45,13 +45,14 @@
     def stop(self):
         pass
 
+    @translate_exceptions
     def Login(self, request, context):
         if not request.username:
-            raise XOSPermissionDenied("No username")
+            raise XOSNotAuthenticated("No username")
 
         u=django_authenticate(username=request.username, password=request.password)
         if not u:
-            raise XOSPermissionDenied("Failed to authenticate user %s" % request.username)
+            raise XOSNotAuthenticated("Failed to authenticate user %s" % request.username)
 
         session = SessionStore()
         auth = {"username": request.username, "password": request.password}
@@ -65,6 +66,7 @@
 
         return response
 
+    @translate_exceptions
     def Logout(self, request, context):
         for (k, v) in context.invocation_metadata():
             if (k.lower()=="x-xossession"):
@@ -74,6 +76,7 @@
                     s.save()
         return Empty()
 
+    @translate_exceptions
     def RunTosca(self, request, context):
         user=self.authenticate(context, required=True)
 
@@ -97,6 +100,7 @@
 
         return response
 
+    @translate_exceptions
     def DestroyTosca(self, request, context):
         user=self.authenticate(context, required=True)
 
@@ -120,9 +124,11 @@
 
         return response
 
+    @translate_exceptions
     def NoOp(self, request, context):
         return Empty()
 
+    @translate_exceptions
     def ListDirtyModels(self, request, context):
         dirty_models = utility_pb2.ModelList()
 
@@ -143,6 +149,7 @@
 
         return dirty_models
 
+    @translate_exceptions
     def SetDirtyModels(self, request, context):
         user=self.authenticate(context, required=True)
 
diff --git a/xos/tools/apigen/grpc_api.template.py b/xos/tools/apigen/grpc_api.template.py
index b8dcc59..90009eb 100644
--- a/xos/tools/apigen/grpc_api.template.py
+++ b/xos/tools/apigen/grpc_api.template.py
@@ -5,7 +5,7 @@
 
 from django.contrib.auth import authenticate as django_authenticate
 from xos.exceptions import *
-from apihelper import XOSAPIHelperMixin
+from apihelper import XOSAPIHelperMixin, translate_exceptions
 
 class XosService(xos_pb2.xosServicer, XOSAPIHelperMixin):
     def __init__(self, thread_pool):
@@ -16,31 +16,37 @@
         pass
 
 {% for object in generator.all() %}
+    @translate_exceptions
     def List{{ object.camel() }}(self, request, context):
       user=self.authenticate(context)
       model=self.get_model("{{ object.camel() }}")
       return self.querysetToProto(model, model.objects.all())
 
+    @translate_exceptions
     def Filter{{ object.camel() }}(self, request, context):
       user=self.authenticate(context)
       model=self.get_model("{{ object.camel() }}")
       return self.filter(model, request)
 
+    @translate_exceptions
     def Get{{ object.camel() }}(self, request, context):
       user=self.authenticate(context)
       model=self.get_model("{{ object.camel() }}")
       return self.get(model, request.id)
 
+    @translate_exceptions
     def Create{{ object.camel() }}(self, request, context):
       user=self.authenticate(context)
       model=self.get_model("{{ object.camel() }}")
       return self.create(model, user, request)
 
+    @translate_exceptions
     def Delete{{ object.camel() }}(self, request, context):
       user=self.authenticate(context)
       model=self.get_model("{{ object.camel() }}")
       return self.delete(model, user, request.id)
 
+    @translate_exceptions
     def Update{{ object.camel() }}(self, request, context):
       user=self.authenticate(context)
       model=self.get_model("{{ object.camel() }}")