[CORD-1502] [CORD-1516] Authenticating the user that is sending the
request and caching it's orm classes

Change-Id: I4754c1395d085ed0cf313c7fffebbd2d0b8c7fd4
diff --git a/Makefile b/Makefile
index 5be709c..8c4526b 100644
--- a/Makefile
+++ b/Makefile
@@ -13,7 +13,7 @@
 	@echo 'TBD'
 
 test-call:
-	curl -H "xos-username: xosadmin@opencord.org" -H "xos-password: rk1UYDHZXbu6KVCMkhmV" -X POST --data-binary @test/tosca/test.yaml 127.0.0.1:9200
+	curl -H "xos-username: xosadmin@opencord.org" -H "xos-password: rk1UYDHZXbu6KVCMkhmV" -X POST --data-binary @test/tosca/test.yaml 127.0.0.1:9200/run
 
 tosca:
 	xosgenx --target=src/tosca/xtarget/tosca.xtarget --output=src/tosca/custom_types --write-to-file=model --dest-extension=yaml ../xos/xos/core/models/core.xproto
\ No newline at end of file
diff --git a/pip_requirements.txt b/pip_requirements.txt
index 6a26d82..36891a6 100644
--- a/pip_requirements.txt
+++ b/pip_requirements.txt
@@ -1,2 +1,2 @@
-Flask==0.12.2
+klein==16.12.0
 coverage==4.4.1
\ No newline at end of file
diff --git a/src/grpc_client/main.py b/src/grpc_client/main.py
index 5612d76..b3e29b1 100644
--- a/src/grpc_client/main.py
+++ b/src/grpc_client/main.py
@@ -1,8 +1,9 @@
 import functools
 from xosapi.xos_grpc_client import SecureClient, InsecureClient
-from twisted.internet import reactor, defer
+from twisted.internet import defer
 from resources import RESOURCES
 from xosconfig import Config
+from twisted.internet import reactor
 
 LOCAL_CERT = '/Users/teone/Sites/opencord/orchestration/xos-tosca/local_certs.crt'
 
@@ -12,13 +13,14 @@
 
         self.grpc_secure_endpoint = Config.get('grpc.secure_endpoint')
         self.grpc_insecure_endpoint = Config.get('grpc.insecure_endpoint')
-        self.username = Config.get('grpc.admin_username')
-        self.password = Config.get('grpc.admin_password')
 
-    def setup_resources(self, client):
+    def setup_resources(self, client, key, deferred, recipe):
         print "[XOS-TOSCA] Loading resources"
+        if key not in RESOURCES:
+            RESOURCES[key] = {}
         for k in client.xos_orm.all_model_names:
-            RESOURCES[k] = getattr(client.xos_orm, k)
+            RESOURCES[key][k] = getattr(client.xos_orm, k)
+        reactor.callLater(0, deferred.callback, recipe)
 
     def start(self):
         print "[XOS-TOSCA] Connecting to xos-core"
@@ -29,15 +31,23 @@
             self.client.stop()
             self.client.session_change = True
 
-        if self.username and self.password:
-            # NOTE if we authenticate users given the parameters in the rest request, do we need this?
-            self.client = SecureClient(endpoint=self.grpc_secure_endpoint, username=self.username, password=self.password, cacert=LOCAL_CERT)
-        else:
-            self.client = InsecureClient(endpoint=self.grpc_insecure_endpoint)
+        self.client = InsecureClient(endpoint=self.grpc_insecure_endpoint)
 
-        self.client.set_reconnect_callback(functools.partial(self.setup_resources, self.client))
+        self.client.set_reconnect_callback(functools.partial(deferred.callback, self.client))
         self.client.start()
 
-        # TODO can we call this once the client is setted up?
-        reactor.callLater(12, deferred.callback, self.client)
-        return deferred
\ No newline at end of file
+        return deferred
+
+    def create_secure_client(self, username, password, recipe):
+        """
+        This method will check if this combination of username/password already has stored orm classes in RESOURCES, otherwise create them
+        """
+        deferred = defer.Deferred()
+        key = "%s~%s" % (username, password)
+        if key in RESOURCES:
+            reactor.callLater(0, deferred.callback, recipe)
+        else:
+            client = SecureClient(endpoint=self.grpc_secure_endpoint, username=username, password=password, cacert=LOCAL_CERT)
+            client.set_reconnect_callback(functools.partial(self.setup_resources, client, key, deferred, recipe))
+            client.start()
+        return deferred
diff --git a/src/grpc_client/models_accessor.py b/src/grpc_client/models_accessor.py
index 188a9ce..82934e5 100644
--- a/src/grpc_client/models_accessor.py
+++ b/src/grpc_client/models_accessor.py
@@ -6,7 +6,7 @@
     """
 
     @staticmethod
-    def get_model_from_classname(class_name, data):
+    def get_model_from_classname(class_name, data, username, password):
         """
         Give a Model Class Name and some data, check if that exits or instantiate a new one
         """
@@ -16,10 +16,13 @@
         else:
             used_key = data.keys()[0]
 
-        if class_name not in RESOURCES:
+        key = "%s~%s" % (username, password)
+        if not key in RESOURCES:
+            raise Exception("[XOS-TOSCA] User '%s' does not have ready resources" % username)
+        if class_name not in RESOURCES[key]:
             raise Exception('[XOS-TOSCA] The model you are trying to create (%s: %s, class: %s) is not know by xos-core' % (used_key, data[used_key], class_name))
 
-        cls = RESOURCES[class_name]
+        cls = RESOURCES[key][class_name]
         models = cls.objects.filter(**{used_key: data[used_key]})
 
         if len(models) == 1:
diff --git a/src/main.py b/src/main.py
index e9e8c10..92c21ee 100644
--- a/src/main.py
+++ b/src/main.py
@@ -2,7 +2,7 @@
 from grpc_client.main import GRPC_Client
 from tosca.generator import TOSCA_Generator
 from web_server.main import TOSCA_WebServer
-from twisted.internet import reactor, defer
+from twisted.internet import defer
 from xosconfig import Config
 
 current_dir = os.path.dirname(os.path.realpath(__file__))
@@ -21,7 +21,6 @@
 
         TOSCA_Generator().generate(client)
 
-        reactor.callLater(0, TOSCA_WebServer)
 
         return deferred
 
@@ -31,7 +30,8 @@
         grpc_setup = GRPC_Client().start()
         grpc_setup.addCallback(self.generate_tosca)
 
-        reactor.run()
+        # NOTE that TOSCA_WebServer create a Klein app that call reactor.run()
+        TOSCA_WebServer()
 
 
 if __name__ == '__main__':
diff --git a/src/tosca/parser.py b/src/tosca/parser.py
index 7c4f429..725d259 100644
--- a/src/tosca/parser.py
+++ b/src/tosca/parser.py
@@ -2,6 +2,8 @@
 from default import TOSCA_RECIPES_DIR
 from grpc_client.resources import RESOURCES
 from grpc_client.models_accessor import GRPCModelsAccessor
+from grpc._channel import _Rendezvous
+import json
 
 class TOSCA_Parser:
 
@@ -125,7 +127,11 @@
             setattr(model, "%s_id" % class_name, related_model.id)
         return model
 
-    def __init__(self, recipe):
+    def __init__(self, recipe, username, password):
+
+        # store username/password combination to read resources
+        self.username = username
+        self.password = password
 
         # the template returned by TOSCA-Parser
         self.template = None
@@ -161,9 +167,7 @@
                 data = recipe.templates[recipe.name]['properties']
                 # [] get model by class name
                 class_name = recipe.type.replace("tosca.nodes.", "")
-                if class_name not in RESOURCES:
-                    raise Exception("Nodetemplate %s's type %s is not a known resource" % (recipe.name, class_name))
-                model = GRPCModelsAccessor.get_model_from_classname(class_name, data)
+                model = GRPCModelsAccessor.get_model_from_classname(class_name, data, self.username, self.password)
                 # [] populate model with data
                 model = self.populate_model(model, data)
                 # [] check if the model has requirements
@@ -181,4 +185,13 @@
                 exception_msg = TOSCA_Parser._translate_exception(str(e))
             raise Exception(exception_msg)
 
+        except _Rendezvous, e:
+            try:
+                exception_msg = json.loads(e._state.details)["error"]
+            except Exception:
+                exception_msg = e._state.details
+            raise Exception(exception_msg)
+        except Exception, e:
+            raise e
+
 
diff --git a/src/web_server/main.py b/src/web_server/main.py
index 4896a71..aaf2c66 100644
--- a/src/web_server/main.py
+++ b/src/web_server/main.py
@@ -1,5 +1,7 @@
-from flask import Flask, make_response, request
 from tosca.parser import TOSCA_Parser
+from grpc_client.main import GRPC_Client
+from klein import Klein
+import functools
 
 BANNER = """
    _  ______  _____    __________  _____ _________ 
@@ -10,23 +12,32 @@
 """
 
 class TOSCA_WebServer:
-    app = Flask('TOSCA-Web-Server')
 
-    @app.route("/", methods=['GET', 'POST'])
-    def home():
-        if request.method == 'GET':
-            response =  make_response(BANNER)
-            response.headers["content-type"] = "text/plain"
-            return response
-        else:
-            try:
-                # print request.headers['xos-password']
-                parser = TOSCA_Parser(request.get_data())
-                parser.execute()
-                response_text = "Created models: %s" % str(parser.ordered_models_name)
-                return make_response(response_text, 201)
-            except Exception, e:
-                return make_response(e.message, 400)
+    app = Klein()
+
+    def execute_tosca(self, recipe):
+        try:
+            self.parser.execute()
+            response_text = "Created models: %s" % str(self.parser.ordered_models_name)
+            return response_text
+        except Exception, e:
+            return e.message
+
+    @app.route('/', methods=['GET'])
+    def index(self, request):
+        return BANNER
+
+    @app.route('/run', methods=['POST'])
+    def execute(self, request):
+        recipe = request.content.read()
+        headers = request.getAllHeaders()
+        username = headers['xos-username']
+        password = headers['xos-password']
+
+        d = GRPC_Client().create_secure_client(username, password, recipe)
+        self.parser = TOSCA_Parser(recipe, username, password)
+        d.addCallback(self.execute_tosca)
+        return d
 
     def __init__(self):
-        self.app.run(host='localhost', port='9200')
\ No newline at end of file
+        self.app.run('localhost', '9200')
\ No newline at end of file
diff --git a/src/xos-tosca-config-schema.yaml b/src/xos-tosca-config-schema.yaml
index 5eb221f..277ee08 100644
--- a/src/xos-tosca-config-schema.yaml
+++ b/src/xos-tosca-config-schema.yaml
@@ -5,12 +5,6 @@
   grpc:
     type: map
     map:
-      admin_username:
-        type: str
-        required: True
-      admin_password:
-        type: str
-        required: True
       secure_endpoint:
         type: str
         required: True
diff --git a/src/xos-tosca-config.yaml b/src/xos-tosca-config.yaml
index 2555112..6ed2b43 100644
--- a/src/xos-tosca-config.yaml
+++ b/src/xos-tosca-config.yaml
@@ -1,6 +1,4 @@
 name: xos-tosca
 grpc:
-  admin_username: "xosadmin@opencord.org"
-  admin_password: "rk1UYDHZXbu6KVCMkhmV"
   secure_endpoint: "xos-core.opencord.org:50051"
   insecure_endpoint: "xos-core.opencord.org:50055"
diff --git a/test/test_grpc_models_accessor.py b/test/test_grpc_models_accessor.py
index 03e9285..b30011c 100644
--- a/test/test_grpc_models_accessor.py
+++ b/test/test_grpc_models_accessor.py
@@ -17,11 +17,28 @@
     pass
 
 mock_resources = {
-    'test-model': FakeResource
+    'username~pass': {
+        'test-model': FakeResource
+    }
 }
 
+USERNAME = 'username'
+PASSWORD = 'pass'
+
 class GRPCModelsAccessor_Create_or_update_Test(unittest.TestCase):
 
+    def test_unkown_user(self):
+        """
+        [GRPCModelsAccessor] get_model_from_classname: If a user does not have orm classes, raise
+        """
+        data = {
+            "name": "test"
+        }
+        with self.assertRaises(Exception) as e:
+            GRPCModelsAccessor.get_model_from_classname('i-do-not-exists', data, USERNAME, PASSWORD)
+        self.assertEqual(e.exception.message, "[XOS-TOSCA] User 'username' does not have ready resources")
+
+    @patch.dict(RESOURCES, mock_resources, clear=True)
     def test_unkown_module(self):
         """
         [GRPCModelsAccessor] get_model_from_classname: If a model is not know by the grpc api, raise
@@ -30,7 +47,7 @@
             "name": "test"
         }
         with self.assertRaises(Exception) as e:
-            GRPCModelsAccessor.get_model_from_classname('i-do-not-exists', data)
+            GRPCModelsAccessor.get_model_from_classname('i-do-not-exists', data, USERNAME, PASSWORD)
         self.assertEqual(e.exception.message, "[XOS-TOSCA] The model you are trying to create (name: test, class: i-do-not-exists) is not know by xos-core")
 
     @patch.object(FakeResource.objects, "filter")
@@ -43,7 +60,7 @@
             "name": "test"
         }
         with patch.dict(RESOURCES, mock_resources, clear=True):
-            model = GRPCModelsAccessor.get_model_from_classname('test-model', data)
+            model = GRPCModelsAccessor.get_model_from_classname('test-model', data, USERNAME, PASSWORD)
             mock_filter.assert_called_with(name="test")
             self.assertEqual(model, FakeModel)
 
@@ -56,7 +73,7 @@
             "name": "test"
         }
         with patch.dict(RESOURCES, mock_resources, clear=True):
-            model = GRPCModelsAccessor.get_model_from_classname('test-model', data)
+            model = GRPCModelsAccessor.get_model_from_classname('test-model', data, USERNAME, PASSWORD)
             self.assertEqual(model, FakeExistingModel)
 
     @patch.object(FakeResource.objects, "filter", MagicMock(return_value=['a', 'b']))
@@ -69,12 +86,13 @@
         }
         with patch.dict(RESOURCES, mock_resources, clear=True):
             with self.assertRaises(Exception) as e:
-                GRPCModelsAccessor.get_model_from_classname('test-model', data)
+                GRPCModelsAccessor.get_model_from_classname('test-model', data, USERNAME, PASSWORD)
             self.assertEqual(e.exception.message, "[XOS-Tosca] Model test has multiple instances, I can't handle it")
 
     @patch.dict(RESOURCES, mock_resources, clear=True)
     @patch.object(FakeResource.objects, "filter")
-    def _test_find_model_without_name_property(self, mock_filter):
+    @patch.object(FakeResource.objects, "new")
+    def test_find_model_without_name_property(self, mock_new, mock_filter):
         """
         [GRPCModelsAccessor] get_model_from_classname: should lookup a model by the first property
         """
@@ -82,8 +100,9 @@
             'foo': 'bar',
             'something': 'else'
         }
-        GRPCModelsAccessor.get_model_from_classname('test-model', data)
+        GRPCModelsAccessor.get_model_from_classname('test-model', data, USERNAME, PASSWORD)
         mock_filter.assert_called_with(foo="bar")
+        mock_new.assert_called()
 
 if __name__ == '__main__':
     unittest.main()
\ No newline at end of file
diff --git a/test/test_tosca_parser.py b/test/test_tosca_parser.py
index 4fb2f2b..a7e865e 100644
--- a/test/test_tosca_parser.py
+++ b/test/test_tosca_parser.py
@@ -95,7 +95,7 @@
         [TOSCA_Parser] compute_dependencies: augment the TOSCA nodetemplate with information on requirements (aka related models)
         """
 
-        parser = TOSCA_Parser('')
+        parser = TOSCA_Parser('', 'user', 'pass')
 
         class FakeNode:
             def __init__(self, name, requirements):
@@ -166,7 +166,7 @@
         """
         [TOSCA_Parser] save_recipe_to_tmp_file: should save a TOSCA recipe to a tmp file
         """
-        parser = TOSCA_Parser('')
+        parser = TOSCA_Parser('', 'user', 'pass')
         parser.recipe_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_tmp.yaml')
 
         parser.save_recipe_to_tmp_file('my tosca')
diff --git a/test/test_tosca_parser_e2e.py b/test/test_tosca_parser_e2e.py
index 5438c76..aceec43 100644
--- a/test/test_tosca_parser_e2e.py
+++ b/test/test_tosca_parser_e2e.py
@@ -20,7 +20,11 @@
 class FakeUser:
     objects = FakeObj
 
-mock_resources = {
+USERNAME = "username"
+PASSWORD = "pass"
+
+mock_resources = {}
+mock_resources["%s~%s" % (USERNAME, PASSWORD)] = {
     'XOSGuiExtension': FakeGuiExt,
     'Site': FakeSite,
     'User': FakeUser
@@ -54,7 +58,7 @@
         files: /spa/extensions/test/vendor.js, /spa/extensions/test/app.js
 """
 
-        parser = TOSCA_Parser(recipe)
+        parser = TOSCA_Parser(recipe, USERNAME, PASSWORD)
 
         parser.execute()
 
@@ -114,7 +118,7 @@
             relationship: tosca.relationships.BelongsToOne
 """
 
-        parser = TOSCA_Parser(recipe)
+        parser = TOSCA_Parser(recipe, USERNAME, PASSWORD)
 
         parser.execute()
 
@@ -158,7 +162,7 @@
                 must-exist: True
         """
 
-        parser = TOSCA_Parser(recipe)
+        parser = TOSCA_Parser(recipe, USERNAME, PASSWORD)
 
         with self.assertRaises(Exception) as e:
             parser.execute()