Merge "[CORD-1518] Add /delete endpoint to remove models"
diff --git a/Makefile b/Makefile
index 36fc749..2d6b101 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,11 @@
 help:
 	@echo "tests: Run unit tests (if you're running local, you'll need to have virtual-env activated)"
 	@echo "tosca: Generate tosca definition from core.xproto"
-	@echo "test-call: Send a sample tosca recipe"
 	@echo "build: Build the docker image for xos-tosca"
 	@echo "start: Run an xos-tosca container"
 	@echo "clean: Remove the xos-tosca container (if any), and the image (if any)"
+	@echo "test-create: Send a sample tosca recipe"
+	@echo "test-delete: Delete a sample tosca recipe"
 
 tests: tosca
 	nosetests -s -v --with-id --with-coverage --cover-html --cover-erase --cover-xml --cover-package="grpc_client, tosca"
@@ -19,8 +20,11 @@
 	docker rm -f xos-tosca || true
 	docker rmi -f xosproject/xos-tosca || true
 
-test-call:
+test-create:
 	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
 
+test-delete:
+	curl -H "xos-username: xosadmin@opencord.org" -H "xos-password: rk1UYDHZXbu6KVCMkhmV" -X POST --data-binary @test/tosca/test.yaml 127.0.0.1:9200/delete
+
 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/src/grpc_client/models_accessor.py b/src/grpc_client/models_accessor.py
index 82934e5..927eb05 100644
--- a/src/grpc_client/models_accessor.py
+++ b/src/grpc_client/models_accessor.py
@@ -14,6 +14,7 @@
         if data.get('name'):
             used_key = 'name'
         else:
+            # FIXME apparently we're not matching model without a name field
             used_key = data.keys()[0]
 
         key = "%s~%s" % (username, password)
diff --git a/src/tosca/parser.py b/src/tosca/parser.py
index 725d259..d98cdf6 100644
--- a/src/tosca/parser.py
+++ b/src/tosca/parser.py
@@ -127,7 +127,11 @@
             setattr(model, "%s_id" % class_name, related_model.id)
         return model
 
-    def __init__(self, recipe, username, password):
+    def __init__(self, recipe, username, password, **kwargs):
+
+        self.delete = False
+        if 'delete' in kwargs:
+            self.delete = True
 
         # store username/password combination to read resources
         self.username = username
@@ -148,6 +152,7 @@
         self.recipe = recipe
 
     def execute(self):
+
         try:
             # [] save the recipe to a tmp file
             self.save_recipe_to_tmp_file(self.recipe)
@@ -173,8 +178,11 @@
                 # [] check if the model has requirements
                 # [] if it has populate them
                 model = self.populate_dependencies(model, recipe.requirements, self.saved_model_by_name)
-                # [] save or update
-                model.save()
+                # [] save, update or delete
+                if self.delete and not model.is_new:
+                    model.delete()
+                elif not self.delete:
+                    model.save()
 
                 self.saved_model_by_name[recipe.name] = model
 
diff --git a/src/web_server/main.py b/src/web_server/main.py
index 266d651..efeae13 100644
--- a/src/web_server/main.py
+++ b/src/web_server/main.py
@@ -39,8 +39,14 @@
             response[name] = "/custom_type/%s" % name
         return json.dumps(response)
 
+    @app.route("/custom_type/<name>")
+    def custom_type(self, request, name):
+        request.responseHeaders.addRawHeader(b"content-type", b"text/plain")
+        custom_type = open(TOSCA_DEFS_DIR + '/' + name + '.yaml').read()
+        return custom_type
+
     @app.route('/run', methods=['POST'])
-    def execute(self, request):
+    def run(self, request):
         recipe = request.content.read()
         headers = request.getAllHeaders()
         username = headers['xos-username']
@@ -51,11 +57,17 @@
         d.addCallback(self.execute_tosca)
         return d
 
-    @app.route("/custom_type/<name>")
-    def custom_type(self, request, name):
-        request.responseHeaders.addRawHeader(b"content-type", b"text/plain")
-        custom_type = open(TOSCA_DEFS_DIR + '/' + name + '.yaml').read()
-        return custom_type
+    @app.route('/delete', methods=['POST'])
+    def delete(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, delete=True)
+        d.addCallback(self.execute_tosca)
+        return d
 
     def __init__(self):
         self.app.run('0.0.0.0', '9102')
\ No newline at end of file
diff --git a/test/test_tosca_parser_e2e.py b/test/test_tosca_parser_e2e.py
index aceec43..f09a811 100644
--- a/test/test_tosca_parser_e2e.py
+++ b/test/test_tosca_parser_e2e.py
@@ -9,6 +9,8 @@
 
 class FakeModel:
     save = None
+    delete = None
+    is_new = False
     id = 1
 
 class FakeGuiExt:
@@ -74,6 +76,42 @@
         self.assertEqual(saved_model.files, '/spa/extensions/test/vendor.js, /spa/extensions/test/app.js')
 
     @patch.dict(RESOURCES, mock_resources, clear=True)
+    @patch.object(FakeGuiExt.objects, 'filter', MagicMock(return_value=[FakeModel]))
+    @patch.object(FakeModel, 'delete')
+    def test_basic_deletion(self, mock_delete):
+        """
+        [TOSCA_Parser] Should delete models defined in a TOSCA recipe
+        """
+        recipe = """
+    tosca_definitions_version: tosca_simple_yaml_1_0
+
+    description: Persist xos-sample-gui-extension
+
+    imports:
+       - custom_types/xosguiextension.yaml
+
+    topology_template:
+      node_templates:
+
+        # UI Extension
+        test:
+          type: tosca.nodes.XOSGuiExtension
+          properties:
+            name: test
+            files: /spa/extensions/test/vendor.js, /spa/extensions/test/app.js
+    """
+
+        parser = TOSCA_Parser(recipe, USERNAME, PASSWORD, delete=True)
+
+        parser.execute()
+
+        # checking that the model has been saved
+        mock_delete.assert_called()
+
+        self.assertIsNotNone(parser.templates_by_model_name['test'])
+        self.assertEqual(parser.ordered_models_name, ['test'])
+
+    @patch.dict(RESOURCES, mock_resources, clear=True)
     @patch.object(FakeSite.objects, 'filter', MagicMock(return_value=[FakeModel]))
     @patch.object(FakeUser.objects, 'filter', MagicMock(return_value=[FakeModel]))
     @patch.object(FakeModel, 'save')