From 3d6a02cdb5643ea344600bd977a3977e9a596dd9 Mon Sep 17 00:00:00 2001
From: Nikolay Stanchev <ns17@it-innovation.soton.ac.uk>
Date: Tue, 3 Jul 2018 15:13:12 +0100
Subject: [PATCH] Config CRUD API for service function chains

---
 src/service/clmcservice/__init__.py           |   6 +-
 src/service/clmcservice/configapi/conftest.py |  40 +++
 src/service/clmcservice/configapi/tests.py    | 325 ++++++++++++++++++
 .../clmcservice/configapi/utilities.py        |  68 ++++
 src/service/clmcservice/configapi/views.py    | 192 +++++++++++
 src/service/clmcservice/initialize_db.py      |  27 +-
 src/service/clmcservice/models/__init__.py    |   3 +
 .../clmcservice/models/config_models.py       |  99 ++++++
 src/service/clmcservice/models/meta.py        | 107 ++++++
 .../{models.py => models/whoami_models.py}    | 111 ++----
 src/service/clmcservice/whoamiapi/conftest.py |  26 +-
 src/service/clmcservice/whoamiapi/tests.py    |  26 +-
 .../clmcservice/whoamiapi/utilities.py        |   6 +-
 src/service/clmcservice/whoamiapi/views.py    |   4 +-
 14 files changed, 946 insertions(+), 94 deletions(-)
 create mode 100644 src/service/clmcservice/configapi/conftest.py
 create mode 100644 src/service/clmcservice/configapi/utilities.py
 create mode 100644 src/service/clmcservice/models/__init__.py
 create mode 100644 src/service/clmcservice/models/config_models.py
 create mode 100644 src/service/clmcservice/models/meta.py
 rename src/service/clmcservice/{models.py => models/whoami_models.py} (57%)

diff --git a/src/service/clmcservice/__init__.py b/src/service/clmcservice/__init__.py
index d3cbc29..90d5015 100644
--- a/src/service/clmcservice/__init__.py
+++ b/src/service/clmcservice/__init__.py
@@ -24,7 +24,7 @@
 
 from pyramid.config import Configurator
 from sqlalchemy import engine_from_config
-from clmcservice.models import DBSession, Base
+from clmcservice.models.meta import DBSession, Base
 from clmcservice.aggregationapi.utilities import validate_conf_file, RUNNING_FLAG, MALFORMED_FLAG, CONF_FILE_ATTRIBUTE, CONF_OBJECT, AGGREGATOR_CONFIG_SECTION
 
 
@@ -55,5 +55,9 @@ def main(global_config, **settings):
     config.add_route('whoami_endpoints', '/whoami/endpoints')
     config.add_route('whoami_endpoints_instance', 'whoami/endpoints/instance')
 
+    # add routes of the CONFIG API
+    config.add_route('config_sfc', '/config/sf-chains')
+    config.add_route('config_sfc_instance', '/config/sf-chains/instance')
+
     config.scan()  # This method scans the packages and finds any views related to the routes added in the app configuration
     return config.make_wsgi_app()
diff --git a/src/service/clmcservice/configapi/conftest.py b/src/service/clmcservice/configapi/conftest.py
new file mode 100644
index 0000000..105ebe8
--- /dev/null
+++ b/src/service/clmcservice/configapi/conftest.py
@@ -0,0 +1,40 @@
+#!/usr/bin/python3
+"""
+// © University of Southampton IT Innovation Centre, 2018
+//
+// Copyright in this software belongs to University of Southampton
+// IT Innovation Centre of Gamma House, Enterprise Road,
+// Chilworth Science Park, Southampton, SO16 7NS, UK.
+//
+// This software may not be used, sold, licensed, transferred, copied
+// or reproduced in whole or in part in any manner or form or in or
+// on any media by any person other than in accordance with the terms
+// of the Licence Agreement supplied with the software, or otherwise
+// without the prior written consent of the copyright owners.
+//
+// This software is distributed WITHOUT ANY WARRANTY, without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE, except where stated in the Licence Agreement supplied with
+// the software.
+//
+//      Created By :            Nikolay Stanchev
+//      Created Date :          03-07-2018
+//      Created for Project :   FLAME
+"""
+
+
+import pytest
+from clmcservice.whoamiapi.conftest import create_test_database, initialise_database, drop_test_database
+
+
+@pytest.fixture(scope='module', autouse=True)
+def testing_db_session():
+    test_database = "configtestdb"
+    create_test_database(test_database)  # create a database used for executing the unit tests
+    db_session, engine = initialise_database(test_database)  # initialise the database with the models and retrieve a db session
+
+    yield db_session  # return the db session if needed in any of the tests
+
+    db_session.remove()  # remove the db session
+    engine.dispose()  # dispose from the engine
+    drop_test_database(test_database)  # remove the test database
diff --git a/src/service/clmcservice/configapi/tests.py b/src/service/clmcservice/configapi/tests.py
index e69de29..3824047 100644
--- a/src/service/clmcservice/configapi/tests.py
+++ b/src/service/clmcservice/configapi/tests.py
@@ -0,0 +1,325 @@
+#!/usr/bin/python3
+"""
+// © University of Southampton IT Innovation Centre, 2018
+//
+// Copyright in this software belongs to University of Southampton
+// IT Innovation Centre of Gamma House, Enterprise Road,
+// Chilworth Science Park, Southampton, SO16 7NS, UK.
+//
+// This software may not be used, sold, licensed, transferred, copied
+// or reproduced in whole or in part in any manner or form or in or
+// on any media by any person other than in accordance with the terms
+// of the Licence Agreement supplied with the software, or otherwise
+// without the prior written consent of the copyright owners.
+//
+// This software is distributed WITHOUT ANY WARRANTY, without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE, except where stated in the Licence Agreement supplied with
+// the software.
+//
+//      Created By :            Nikolay Stanchev
+//      Created Date :          02-07-2018
+//      Created for Project :   FLAME
+"""
+
+import pytest
+from json import dumps
+from pyramid import testing
+from pyramid.httpexceptions import HTTPBadRequest, HTTPNotFound, HTTPConflict
+from clmcservice.models import ServiceFunctionChain
+from clmcservice.configapi.views import SFCConfigAPI
+
+
+class TestSFCConfigAPI(object):
+    """
+    A pytest-implementation test for the Config API endpoints for service function chains
+    """
+
+    @pytest.fixture(autouse=True)
+    def app_config(self):
+        """
+        A fixture to implement setUp/tearDown functionality for all tests by initializing configuration structure for the web service and db connection
+        """
+
+        self.registry = testing.setUp()
+
+        yield
+
+        testing.tearDown()
+        ServiceFunctionChain.delete_all()  # clear the instances of the model in the test database
+
+    def test_get_all(self):
+        """
+        Tests the GET all method of the config API for service function chains - returns a list of all service function chains from the database.
+        """
+
+        request = testing.DummyRequest()
+        response = SFCConfigAPI(request).get_all()
+        assert response == [], "Initially there mustn't be any service function chains in the database."
+
+        sfc = ServiceFunctionChain(sfc="sfc1", sfc_i="sfc_i1", chain={"nginx": ["minio"]})
+        expected_response_data = [sfc.json]
+        ServiceFunctionChain.add(sfc)  # adds the new instance of the model to the database
+
+        request = testing.DummyRequest()
+        response = SFCConfigAPI(request).get_all()
+        assert response == expected_response_data, "Incorrect response data with 1 service function chain."
+
+        sfc = ServiceFunctionChain(sfc="sfc2", sfc_i="sfc_i1", chain={"nginx": ["minio"]})
+        expected_response_data.append(sfc.json)
+        ServiceFunctionChain.add(sfc)
+        sfc = ServiceFunctionChain(sfc="sfc2", sfc_i="sfc_i2", chain={"nginx": ["minio"]})
+        expected_response_data.append(sfc.json)
+        ServiceFunctionChain.add(sfc)
+
+        request = testing.DummyRequest()
+        response = SFCConfigAPI(request).get_all()
+        assert response == expected_response_data, "Incorrect response data with more than 1 service function chains."
+
+    def test_get_one(self):
+        """
+        Tests the GET one method of the config API for service function chains - returns an instance of a service function chain from the database.
+        """
+
+        request = testing.DummyRequest()
+        response = SFCConfigAPI(request).get_all()
+        assert response == [], "Initially there mustn't be any service function chains in the database."
+
+        self._validation_of_url_parameters_test("get_one")
+
+        sfc = ServiceFunctionChain(sfc="sfc1", sfc_i="sfc_i1", chain={"nginx": ["minio"]})
+        expected_response_data = sfc.json
+        ServiceFunctionChain.add(sfc)  # adds the new instance of the model to the database
+
+        request = testing.DummyRequest()
+        request.params["sfc"] = "sfc1"
+        request.params["sfc_i"] = "sfc_i1"
+        response = SFCConfigAPI(request).get_one()
+        assert response == expected_response_data, "Invalid data returned in the response of GET instance"
+
+        request = testing.DummyRequest()
+        request.params["sfc"] = "sfc2"
+        request.params["sfc_i"] = "sfc_i2"
+        error_raised = False
+        try:
+            SFCConfigAPI(request).get_one()
+        except HTTPNotFound:
+            error_raised = True
+        assert error_raised, "Not found error must be raised in case of a non existing service function chain"
+
+    def test_post(self):
+        """
+        Tests the POST method of the config API for service function chains - creates an instance of a service function chain in the database.
+        """
+
+        request = testing.DummyRequest()
+        response = SFCConfigAPI(request).get_all()
+        assert response == [], "Initially there mustn't be any service function chains in the database."
+
+        resource = dict(sfc="sfc1", sfc_i="sfc_i1", chain={"nginx": ["minio"]})
+        json_data = dumps(resource)
+        request = testing.DummyRequest()
+        request.body = json_data.encode(request.charset)
+        response = SFCConfigAPI(request).post()
+        assert response == resource, "POST request must return the created resource"
+        assert ServiceFunctionChain.exists("sfc1", "sfc_i1"), "POST request must have created the resource"
+
+        resource["chain"] = {}
+        json_data = dumps(resource)
+        request = testing.DummyRequest()
+        request.body = json_data.encode(request.charset)
+        error_raised = False
+        try:
+            SFCConfigAPI(request).post()
+        except HTTPConflict:
+            error_raised = True
+        assert error_raised, "An error must be raised when trying to create a resource which breaks the unique constraint"
+
+    @pytest.mark.parametrize("body, valid", [
+        ('{"sfc": "sfc1", "sfc_i": "sfc_i1", "chain":{"nginx":["minio"]}}', True),
+        ('{"sfc": "sfc2", "sfc_i": "sfc_i2", "chain":{}}', True),
+        ('{"sfc": "sfc1", "sfc_i": "sfc_i1", "chain":[]}', False),
+        ('{}', False),
+        ('{"sfc": "sfc3", "sfc_i": "sfc_i3"', False),
+        ('{"sf": "sfc2", "sf_i": "sfc_i2", "chain":{}', False),
+        ('{invalid json}', False),
+    ])
+    def test_post_body_validation(self, body, valid):
+        """
+        Tests the POST request validation of the body content.
+
+        :param body: The request body to be validated
+        :param valid: True if body is valid, False otherwise
+        """
+
+        request = testing.DummyRequest()
+        request.body = body.encode(request.charset)
+        error_raised = False
+        try:
+            SFCConfigAPI(request).post()
+        except HTTPBadRequest:
+            error_raised = True
+        assert error_raised == (not valid), "An error must be raised in case of an invalid request body"
+
+    def test_put(self):
+        """
+        Tests the PUT method of the Config API for service function chains - overwrites an instance of a service function chain from the database.
+        """
+
+        request = testing.DummyRequest()
+        response = SFCConfigAPI(request).get_all()
+        assert response == [], "Initially there mustn't be any service function chains in the database."
+
+        self._validation_of_url_parameters_test("put")
+
+        resource = dict(sfc="sfc1", sfc_i="sfc_i1", chain={"nginx": ["minio"]})
+        body = dumps(resource)
+        request = testing.DummyRequest()
+        request.params["sfc"] = "sfc1"
+        request.params["sfc_i"] = "sfc_i1"
+        request.body = body.encode(request.charset)
+        error_raised = False
+        try:
+            SFCConfigAPI(request).put()
+        except HTTPNotFound:
+            error_raised = True
+        assert error_raised, "Not found error must be raised in case of a non existing service function chain"
+
+        sfc = ServiceFunctionChain(sfc="sfc1", sfc_i="sfc_i1", chain={"nginx": ["minio"]})
+        ServiceFunctionChain.add(sfc)  # adds the new instance of the model to the database
+
+        resource = dict(sfc="sfc1", sfc_i="sfc_i1", chain={})
+        body = dumps(resource)
+        request = testing.DummyRequest()
+        request.params["sfc"] = "sfc1"
+        request.params["sfc_i"] = "sfc_i1"
+        request.body = body.encode(request.charset)
+        response = SFCConfigAPI(request).put()
+        assert response == resource, "PUT request must return the updated resource"
+        assert ServiceFunctionChain.get("sfc1", "sfc_i1").json["chain"] == {}
+
+        resource = dict(sfc="sfc2", sfc_i="sfc_i2", chain={"nginx": ["minio"]})
+        body = dumps(resource)
+        request = testing.DummyRequest()
+        request.params["sfc"] = "sfc1"
+        request.params["sfc_i"] = "sfc_i1"
+        request.body = body.encode(request.charset)
+        response = SFCConfigAPI(request).put()
+        assert response == resource, "PUT request must return the updated resource"
+        assert not ServiceFunctionChain.exists("sfc1", "sfc_i1"), "Resource has not been updated"
+        assert ServiceFunctionChain.exists("sfc2", "sfc_i2"), "Resource has not been updated"
+
+        sfc = ServiceFunctionChain(sfc="sfc1", sfc_i="sfc_i1", chain={"nginx": ["minio"]})
+        ServiceFunctionChain.add(sfc)  # adds the new instance of the model to the database
+
+        resource = dict(sfc="sfc2", sfc_i="sfc_i2", chain={"nginx": ["minio"]})
+        body = dumps(resource)
+        request = testing.DummyRequest()
+        request.params["sfc"] = "sfc1"
+        request.params["sfc_i"] = "sfc_i1"
+        request.body = body.encode(request.charset)
+        error_raised = False
+        try:
+            SFCConfigAPI(request).put()
+        except HTTPConflict:
+            error_raised = True
+        assert error_raised, "PUT request breaks unique constraint"
+
+    @pytest.mark.parametrize("body, valid", [
+        ('{"sfc": "sfc1", "sfc_i": "sfc_i1", "chain":{"nginx":["minio"]}}', True),
+        ('{"sfc": "sfc2", "sfc_i": "sfc_i2", "chain":{}}', True),
+        ('{"sfc": "sfc1", "sfc_i": "sfc_i1", "chain":[]}', False),
+        ('{}', False),
+        ('{"sfc": "sfc3", "sfc_i": "sfc_i3"', False),
+        ('{"sf": "sfc2", "sf_i": "sfc_i2", "chain":{}', False),
+        ('{invalid json}', False),
+    ])
+    def test_put_body_validation(self, body, valid):
+        """
+        Tests the PUT request validation of the body content.
+
+        :param body: The request body to be validated
+        :param valid: True if body is valid, False otherwise
+        """
+
+        sfc = ServiceFunctionChain(sfc="sfc1", sfc_i="sfc_i1", chain={"nginx": ["minio"]})
+        ServiceFunctionChain.add(sfc)  # adds the new instance of the model to the database
+
+        request = testing.DummyRequest()
+        request.params["sfc"] = "sfc1"
+        request.params["sfc_i"] = "sfc_i1"
+        request.body = body.encode(request.charset)
+        error_raised = False
+        try:
+            SFCConfigAPI(request).put()
+        except HTTPBadRequest:
+            error_raised = True
+        assert error_raised == (not valid), "An error must be raised in case of an invalid request body"
+
+    def test_delete(self):
+        """
+        Tests the DELETE method of the config API for service function chains - deletes an instance of a service function chain from the database.
+        """
+
+        request = testing.DummyRequest()
+        response = SFCConfigAPI(request).get_all()
+        assert response == [], "Initially there mustn't be any service function chains in the database."
+
+        self._validation_of_url_parameters_test("delete")
+
+        sfc = ServiceFunctionChain(sfc="sfc1", sfc_i="sfc_i1", chain={"nginx": ["minio"]})
+        to_delete = sfc.json
+        ServiceFunctionChain.add(sfc)  # adds the new instance of the model to the database
+
+        assert ServiceFunctionChain.exists("sfc1", "sfc_i1")
+
+        request = testing.DummyRequest()
+        request.params["sfc"] = "sfc1"
+        request.params["sfc_i"] = "sfc_i1"
+        response = SFCConfigAPI(request).delete()
+        assert response == to_delete, "DELETE must return the deleted object if successful"
+
+        assert not ServiceFunctionChain.exists("sfc1", "sfc_i1"), "Resource must be deleted after the delete API method has been called."
+
+        request = testing.DummyRequest()
+        request.params["sfc"] = "sfc1"
+        request.params["sfc_i"] = "sfc_i1"
+        error_raised = False
+        try:
+            SFCConfigAPI(request).delete()
+        except HTTPNotFound:
+            error_raised = True
+        assert error_raised, "Not found error must be raised in case of a non existing service function chain"
+
+    @staticmethod
+    def _validation_of_url_parameters_test(method):
+        """
+        Validates the way a config API method handles url query parameters for service function chains
+
+        :param method: the method to test
+        """
+
+        request = testing.DummyRequest()
+        error_raised = False
+        try:
+            getattr(SFCConfigAPI(request), method).__call__()
+        except HTTPBadRequest:
+            error_raised = True
+        assert error_raised, "Error must be raised in case of no URL parameters"
+
+        request = testing.DummyRequest()
+        request.params["sfc"] = "sfc1"
+        try:
+            getattr(SFCConfigAPI(request), method).__call__()
+        except HTTPBadRequest:
+            error_raised = True
+        assert error_raised, "Error must be raised in case of insufficient number of arguments"
+
+        request = testing.DummyRequest()
+        request.params["sf"] = "sfc1"  # argument should be sfc
+        request.params["sfc_i"] = "sfc_i1"
+        try:
+            getattr(SFCConfigAPI(request), method).__call__()
+        except HTTPBadRequest:
+            error_raised = True
+        assert error_raised, "Error must be raised in case of invalid naming of arguments"
diff --git a/src/service/clmcservice/configapi/utilities.py b/src/service/clmcservice/configapi/utilities.py
new file mode 100644
index 0000000..8a3babc
--- /dev/null
+++ b/src/service/clmcservice/configapi/utilities.py
@@ -0,0 +1,68 @@
+#!/usr/bin/python3
+"""
+// © University of Southampton IT Innovation Centre, 2018
+//
+// Copyright in this software belongs to University of Southampton
+// IT Innovation Centre of Gamma House, Enterprise Road,
+// Chilworth Science Park, Southampton, SO16 7NS, UK.
+//
+// This software may not be used, sold, licensed, transferred, copied
+// or reproduced in whole or in part in any manner or form or in or
+// on any media by any person other than in accordance with the terms
+// of the Licence Agreement supplied with the software, or otherwise
+// without the prior written consent of the copyright owners.
+//
+// This software is distributed WITHOUT ANY WARRANTY, without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE, except where stated in the Licence Agreement supplied with
+// the software.
+//
+//      Created By :            Nikolay Stanchev
+//      Created Date :          02-07-2018
+//      Created for Project :   FLAME
+"""
+
+from json import loads
+from clmcservice.models import ServiceFunctionChain
+
+
+def validate_sfchain_body(body):
+    """
+    Validates the request body used to create an service function chain resource in the database.
+
+    :param body: the request body to validate
+    :return the validated sfc dictionary object
+    :raise AssertionError: if the body is not a valid service function chain
+    """
+
+    try:
+        body = loads(body)
+    except:
+        raise AssertionError("Service function chain must be represented by a JSON object.")
+
+    # the database table has one more column which is a UID integer
+    assert len(body) == len(ServiceFunctionChain.__table__.columns) - 1, "Service function chain JSON object mustn't contain a different number of attributes than the number of required ones."
+
+    # validate that all required attributes are given in the body
+    for attribute in ServiceFunctionChain.required_columns():
+        assert attribute in body, "Required attribute not found in the request content."
+
+    assert type(body["chain"]) == dict, "The chain attribute of a service function chain must be a graph representing the relations between service functions."
+
+    return body
+
+
+def validate_sfchain_params(params):
+    """
+    Validates the request parameters to retrieve an service function chain resource from the database.
+
+    :param params: the parameters dictionary to validate
+    :return: the validated parameters
+    :raise AssertionError: for invalid parameters
+    """
+
+    constrained_cols = ServiceFunctionChain.constrained_columns()
+
+    assert len(params) == len(constrained_cols), "Incorrect url query parameters."
+
+    return params
diff --git a/src/service/clmcservice/configapi/views.py b/src/service/clmcservice/configapi/views.py
index e69de29..571655f 100644
--- a/src/service/clmcservice/configapi/views.py
+++ b/src/service/clmcservice/configapi/views.py
@@ -0,0 +1,192 @@
+#!/usr/bin/python3
+"""
+// © University of Southampton IT Innovation Centre, 2018
+//
+// Copyright in this software belongs to University of Southampton
+// IT Innovation Centre of Gamma House, Enterprise Road,
+// Chilworth Science Park, Southampton, SO16 7NS, UK.
+//
+// This software may not be used, sold, licensed, transferred, copied
+// or reproduced in whole or in part in any manner or form or in or
+// on any media by any person other than in accordance with the terms
+// of the Licence Agreement supplied with the software, or otherwise
+// without the prior written consent of the copyright owners.
+//
+// This software is distributed WITHOUT ANY WARRANTY, without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE, except where stated in the Licence Agreement supplied with
+// the software.
+//
+//      Created By :            Nikolay Stanchev
+//      Created Date :          02-07-2018
+//      Created for Project :   FLAME
+"""
+
+
+from pyramid.httpexceptions import HTTPBadRequest, HTTPConflict, HTTPNotFound
+from pyramid.view import view_defaults, view_config
+from clmcservice.models import ServiceFunctionChain
+from clmcservice.configapi.utilities import validate_sfchain_body, validate_sfchain_params
+
+
+@view_defaults(renderer='json')
+class SFCConfigAPI(object):
+    """
+    A class-based view for posting and retrieving configuration data for service function chains to the CLMC service
+    """
+
+    def __init__(self, request):
+        """
+        Initialises the instance of the view with the request argument.
+
+        :param request: client's call request
+        """
+
+        self.request = request
+
+    @view_config(route_name='config_sfc', request_method='GET')
+    def get_all(self):
+        """
+        GET API call for all resources.
+
+        :return: A list of all service function chains found in the database.
+        """
+
+        return [instance.json for instance in ServiceFunctionChain.query()]
+
+    @view_config(route_name='config_sfc_instance', request_method='GET')
+    def get_one(self):
+        """
+        GET API call for a single resources.
+
+        :return: One service function chain instance retrieved from the database by querying the uniquely constrained columns.
+        :raises HTTPBadRequest: if the request parameters are invalid(invalid url query string)
+        :raises HTTPNotFound: if a resource with the given parameters doesn't exist in the database
+        """
+
+        sf_chain = self._get_sf_chain_from_url_string()
+        if sf_chain is None:
+            raise HTTPNotFound("A service function chain with the given parameters doesn't exist.")
+        else:
+            return sf_chain.json
+
+    @view_config(route_name='config_sfc', request_method='POST')
+    def post(self):
+        """
+        A POST API call to create a new service function chain.
+
+        :return: A JSON response to the POST call - essentially with the data of the new resource
+        :raises HTTPBadRequest: if request body is not a valid JSON for the service function chain
+        :raises HTTPConflict: if the unique constraints are not preserved after the creation of a new instance
+        """
+
+        # create an instance of the model and add it to the database table
+        sf_chain = self._validate_and_create()
+        json_data = sf_chain.json
+        ServiceFunctionChain.add(sf_chain)
+
+        self.request.response.status = 201
+
+        return json_data
+
+    @view_config(route_name='config_sfc_instance', request_method='PUT')
+    def put(self):
+        """
+        A PUT API call to update a service function chain.
+
+        :return: A JSON response representing the updated object
+        :raises HTTPBadRequest: if the request parameters are invalid(invalid url query string)
+        :raises HTTPNotFound: if a resource with the given parameters doesn't exist in the database
+        """
+
+        sf_chain = self._get_sf_chain_from_url_string()
+        if sf_chain is None:
+            raise HTTPNotFound("A service function chain with the given parameters doesn't exist.")
+        else:
+            try:
+                body = self.request.body.decode(self.request.charset)
+                validated_body = validate_sfchain_body(body)  # validate the content and receive a json dictionary object
+            except AssertionError as e:
+                raise HTTPBadRequest("Bad request content. Service function chain format is incorrect: {0}".format(e.args))
+
+            new_resource = validated_body
+            old_resource = sf_chain.json
+            updating = new_resource["sfc"] == old_resource["sfc"] and new_resource["sfc_i"] == old_resource["sfc_i"]
+
+            if updating:
+                ServiceFunctionChain.delete(sf_chain)
+                new_sf_chain = ServiceFunctionChain(**validated_body)
+                ServiceFunctionChain.add(new_sf_chain)
+            else:
+                resource_exists = ServiceFunctionChain.exists(new_resource["sfc"], new_resource["sfc_i"])
+                if resource_exists:
+                    raise HTTPConflict("Service function chain with this data already exists.")  # error 409 in case of resource conflict
+
+                new_sf_chain = ServiceFunctionChain(**validated_body)
+                ServiceFunctionChain.replace(sf_chain, new_sf_chain)
+
+            return validated_body
+
+    @view_config(route_name='config_sfc_instance', request_method='DELETE')
+    def delete(self):
+        """
+        Deletes an instance of a service function chain in the database.
+
+        :return: An content of the object that has been deleted
+        :raises HTTPBadRequest: if the request parameters are invalid(invalid url query string)
+        :raises HTTPNotFound: if a resource with the given parameters doesn't exist in the database
+        """
+
+        sf_chain = self._get_sf_chain_from_url_string()
+        if sf_chain is None:
+            raise HTTPNotFound("A service function chain with the given parameters doesn't exist.")
+        else:
+            deleted = sf_chain.json
+            ServiceFunctionChain.delete(sf_chain)
+            return deleted
+
+    def _get_sf_chain_from_url_string(self):
+        """
+        Retrieves a service function chain from the database by validating and then using the request url parameters.
+
+        :return: An instance of a service function chain or None if not existing
+        """
+
+        params = {}
+        for attribute in ServiceFunctionChain.constrained_columns():
+            if attribute in self.request.params:
+                params[attribute] = self.request.params.get(attribute)
+
+        try:
+            params = validate_sfchain_params(params)
+        except AssertionError as e:
+            raise HTTPBadRequest("Request format is incorrect: {0}".format(e.args))
+
+        sf_chain = ServiceFunctionChain.get(**params)
+        return sf_chain
+
+    def _validate_and_create(self):
+        """
+        Validates the request body and checks if a resource with the given attributes already exists.
+
+        :return: a new instance of the model, if the resource doesn't exist
+        :raises HTTPBadRequest: if request body is not a valid JSON for the service function chain
+        :raises HTTPConflict: if the unique constraints are not preserved after the creation of a new instance
+        """
+
+        try:
+            body = self.request.body.decode(self.request.charset)
+            validated_body = validate_sfchain_body(body)  # validate the content and receive a json dictionary object
+        except AssertionError as e:
+            raise HTTPBadRequest("Bad request content. Service function chain format is incorrect: {0}".format(e.args))
+
+        resource = validated_body
+
+        resource_exists = ServiceFunctionChain.exists(resource["sfc"], resource["sfc_i"])
+        if resource_exists:
+            raise HTTPConflict("Service function chain with this data already exists.")  # error 409 in case of resource conflict
+
+        # create an instance of the model
+        sf_chain = ServiceFunctionChain(**resource)
+
+        return sf_chain
diff --git a/src/service/clmcservice/initialize_db.py b/src/service/clmcservice/initialize_db.py
index a0b1a09..c6987d6 100644
--- a/src/service/clmcservice/initialize_db.py
+++ b/src/service/clmcservice/initialize_db.py
@@ -1,8 +1,32 @@
+#!/usr/bin/python3
+"""
+// © University of Southampton IT Innovation Centre, 2018
+//
+// Copyright in this software belongs to University of Southampton
+// IT Innovation Centre of Gamma House, Enterprise Road,
+// Chilworth Science Park, Southampton, SO16 7NS, UK.
+//
+// This software may not be used, sold, licensed, transferred, copied
+// or reproduced in whole or in part in any manner or form or in or
+// on any media by any person other than in accordance with the terms
+// of the Licence Agreement supplied with the software, or otherwise
+// without the prior written consent of the copyright owners.
+//
+// This software is distributed WITHOUT ANY WARRANTY, without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE, except where stated in the Licence Agreement supplied with
+// the software.
+//
+//      Created By :            Nikolay Stanchev
+//      Created Date :          25-06-2018
+//      Created for Project :   FLAME
+"""
+
 import os
 import sys
 from sqlalchemy import engine_from_config
 from pyramid.paster import get_appsettings, setup_logging
-from clmcservice.models import DBSession, Base
+from clmcservice.models.meta import Base
 
 
 def usage(argv):
@@ -34,5 +58,4 @@ def main(argv=sys.argv):
     settings = get_appsettings(config_uri)  # get application specific settings
     engine = engine_from_config(settings, 'sqlalchemy.')  # create the db engine from the sqlalchemy setting configured in the .ini file
 
-    DBSession.configure(bind=engine)
     Base.metadata.create_all(engine)  # creates all model tables
diff --git a/src/service/clmcservice/models/__init__.py b/src/service/clmcservice/models/__init__.py
new file mode 100644
index 0000000..6ceb55d
--- /dev/null
+++ b/src/service/clmcservice/models/__init__.py
@@ -0,0 +1,3 @@
+from .meta import DBSession
+from .whoami_models import ServiceFunctionEndpoint
+from .config_models import ServiceFunctionChain
diff --git a/src/service/clmcservice/models/config_models.py b/src/service/clmcservice/models/config_models.py
new file mode 100644
index 0000000..4790043
--- /dev/null
+++ b/src/service/clmcservice/models/config_models.py
@@ -0,0 +1,99 @@
+#!/usr/bin/python3
+"""
+// © University of Southampton IT Innovation Centre, 2018
+//
+// Copyright in this software belongs to University of Southampton
+// IT Innovation Centre of Gamma House, Enterprise Road,
+// Chilworth Science Park, Southampton, SO16 7NS, UK.
+//
+// This software may not be used, sold, licensed, transferred, copied
+// or reproduced in whole or in part in any manner or form or in or
+// on any media by any person other than in accordance with the terms
+// of the Licence Agreement supplied with the software, or otherwise
+// without the prior written consent of the copyright owners.
+//
+// This software is distributed WITHOUT ANY WARRANTY, without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE, except where stated in the Licence Agreement supplied with
+// the software.
+//
+//      Created By :            Nikolay Stanchev
+//      Created Date :          02-07-2018
+//      Created for Project :   FLAME
+"""
+
+from sqlalchemy import Column, String, Integer, UniqueConstraint, and_
+from sqlalchemy.dialects.postgresql import JSONB
+from clmcservice.models.meta import Base
+
+
+class ServiceFunctionChain(Base):
+    """
+    This class defines the service function chain model of the config API, declaring the relations between individual service functions per service function chain.
+    """
+
+    __tablename__ = 'sfchain'  # table name in the PostgreSQL database
+
+    __table_args__ = (UniqueConstraint('sfc', 'sfc_i'),)  # defines a unique constraint across 2 columns - sfc, sfc_i
+
+    uid = Column(Integer, primary_key=True, autoincrement=True, nullable=False)  # a primary key integer field (auto incremented)
+
+    sfc = Column(String, nullable=False)  # service function chain label
+    sfc_i = Column(String, nullable=False)  # service function chain instance identifier
+    chain = Column(JSONB, nullable=False)  # the service function chain graph represented by a python dictionary (JSON object essentially)
+
+    @property
+    def json(self):
+        """
+        Converts an instance of a ServiceFunctionChain to JSON format.
+
+        :return: a python dictionary object
+        """
+
+        fields = {c.name: getattr(self, c.name) for c in self.__table__.columns}
+        fields.pop("uid")
+
+        return fields
+
+    @staticmethod
+    def required_columns():
+        """
+        Returns the required columns for constructing a valid instance.
+        :return: a generator object
+        """
+
+        return tuple(column.name for column in ServiceFunctionChain.__table__.columns if column.name != "uid")
+
+    @staticmethod
+    def constrained_columns():
+        """
+        :return: the columns that are uniquely identifying an instance of this model.
+        """
+
+        return tuple(column.name for column in ServiceFunctionChain.__table_args__[0].columns)
+
+    @staticmethod
+    def get(sfc, sfc_i):
+        """
+        Gets the instance matching the unique constraint or None if not existing.
+
+        :param sfc: service function chain id
+        :param sfc_i: service function chain instance id
+
+        :return: the first object from the result set that matches the unique constraint or None
+        """
+
+        return ServiceFunctionChain.query().filter(and_(ServiceFunctionChain.sfc == sfc, ServiceFunctionChain.sfc_i == sfc_i)).first()
+
+    @staticmethod
+    def exists(sfc, sfc_i):
+        """
+        Checks if an instance matching the unique constraint exists.
+
+        :param sfc: service function chain id
+        :param sfc_i: service function chain instance id
+
+        :return: True if exists, False otherwise
+        """
+
+        return ServiceFunctionChain.get(sfc, sfc_i) is not None
diff --git a/src/service/clmcservice/models/meta.py b/src/service/clmcservice/models/meta.py
new file mode 100644
index 0000000..698d750
--- /dev/null
+++ b/src/service/clmcservice/models/meta.py
@@ -0,0 +1,107 @@
+#!/usr/bin/python3
+"""
+// © University of Southampton IT Innovation Centre, 2018
+//
+// Copyright in this software belongs to University of Southampton
+// IT Innovation Centre of Gamma House, Enterprise Road,
+// Chilworth Science Park, Southampton, SO16 7NS, UK.
+//
+// This software may not be used, sold, licensed, transferred, copied
+// or reproduced in whole or in part in any manner or form or in or
+// on any media by any person other than in accordance with the terms
+// of the Licence Agreement supplied with the software, or otherwise
+// without the prior written consent of the copyright owners.
+//
+// This software is distributed WITHOUT ANY WARRANTY, without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE, except where stated in the Licence Agreement supplied with
+// the software.
+//
+//      Created By :            Nikolay Stanchev
+//      Created Date :          02-07-2018
+//      Created for Project :   FLAME
+"""
+
+
+import transaction
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import scoped_session, sessionmaker
+from zope.sqlalchemy import ZopeTransactionExtension
+
+
+DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))  # initialise a ORM session, ought to be reused across the different modules
+
+
+class ORMClass(object):
+    """
+    Declares a parent class for all models which eases querying
+    """
+
+    @classmethod
+    def query(cls):
+        """
+        Pass down the class name when using the DBSession.query method and use ModelClass.query() instead of DBSession.query(ModelClass)
+
+        :return: the query result object
+        """
+
+        global DBSession
+
+        return DBSession.query(cls)
+
+    @staticmethod
+    def add(instance):
+        """
+        Adds an instance of a model to the database.
+
+        :param instance: the instance to be created in the db.
+        """
+
+        global DBSession
+
+        with transaction.manager:
+            DBSession.add(instance)
+
+    @staticmethod
+    def delete(instance):
+        """
+        Deletes an instance of a model from the database.
+
+        :param instance: the instance to be deleted from the db.
+        """
+
+        global DBSession
+
+        with transaction.manager:
+            DBSession.delete(instance)
+
+    @staticmethod
+    def replace(old_instance, new_instance):
+        """
+        Replaces an instance of a model from the database with a new instance.
+
+        :param old_instance: the instance to be replaced from the db.
+        :param new_instance: the new instance
+        """
+
+        global DBSession
+
+        with transaction.manager:
+            DBSession.add(new_instance)
+            DBSession.delete(old_instance)
+
+    @classmethod
+    def delete_all(cls):
+        """
+        Deletes all instances of a model from the database.
+        """
+
+        global DBSession
+
+        with transaction.manager:
+            deleted_rows = DBSession.query(cls).delete()
+
+        return deleted_rows
+
+
+Base = declarative_base(cls=ORMClass)  # initialise a declarative Base instance to use for the web app models (inherits from the base ORM class defined above)
diff --git a/src/service/clmcservice/models.py b/src/service/clmcservice/models/whoami_models.py
similarity index 57%
rename from src/service/clmcservice/models.py
rename to src/service/clmcservice/models/whoami_models.py
index 78085d8..30466cc 100644
--- a/src/service/clmcservice/models.py
+++ b/src/service/clmcservice/models/whoami_models.py
@@ -1,85 +1,29 @@
-import transaction
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import scoped_session, sessionmaker
-from zope.sqlalchemy import ZopeTransactionExtension
-from sqlalchemy import Column, String, Integer, UniqueConstraint, and_
-
-DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))  # initialise a ORM session, ought to be reused across the different modules
-
-
-class ORMClass(object):
-    """
-    Declares a parent class for all models which eases querying
-    """
-
-    @classmethod
-    def query(cls):
-        """
-        Pass down the class name when using the DBSession.query method and use ModelClass.query() instead of DBSession.query(ModelClass)
-
-        :return: the query result object
-        """
-
-        global DBSession
-
-        return DBSession.query(cls)
-
-    @staticmethod
-    def add(instance):
-        """
-        Adds an instance of a model to the database.
-
-        :param instance: the instance to be created in the db.
-        """
-
-        global DBSession
-
-        with transaction.manager:
-            DBSession.add(instance)
-
-    @staticmethod
-    def delete(instance):
-        """
-        Deletes an instance of a model from the database.
+#!/usr/bin/python3
+"""
+// © University of Southampton IT Innovation Centre, 2018
+//
+// Copyright in this software belongs to University of Southampton
+// IT Innovation Centre of Gamma House, Enterprise Road,
+// Chilworth Science Park, Southampton, SO16 7NS, UK.
+//
+// This software may not be used, sold, licensed, transferred, copied
+// or reproduced in whole or in part in any manner or form or in or
+// on any media by any person other than in accordance with the terms
+// of the Licence Agreement supplied with the software, or otherwise
+// without the prior written consent of the copyright owners.
+//
+// This software is distributed WITHOUT ANY WARRANTY, without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE, except where stated in the Licence Agreement supplied with
+// the software.
+//
+//      Created By :            Nikolay Stanchev
+//      Created Date :          02-07-2018
+//      Created for Project :   FLAME
+"""
 
-        :param instance: the instance to be deleted from the db.
-        """
-
-        global DBSession
-
-        with transaction.manager:
-            DBSession.delete(instance)
-
-    @staticmethod
-    def replace(old_instance, new_instance):
-        """
-        Replaces an instance of a model from the database with a new instance.
-
-        :param old_instance: the instance to be replaced from the db.
-        :param new_instance: the new instance
-        """
-
-        global DBSession
-
-        with transaction.manager:
-            DBSession.add(new_instance)
-            DBSession.delete(old_instance)
-
-    @classmethod
-    def delete_all(cls):
-        """
-        Deletes all instances of a model from the database.
-        """
-
-        global DBSession
-
-        with transaction.manager:
-            deleted_rows = DBSession.query(cls).delete()
-
-        return deleted_rows
-
-
-Base = declarative_base(cls=ORMClass)  # initialise a declarative Base instance to use for the web app models (inherits from the base ORM class defined above)
+from sqlalchemy import Column, String, Integer, UniqueConstraint, and_
+from clmcservice.models.meta import Base
 
 
 class ServiceFunctionEndpoint(Base):
@@ -87,7 +31,7 @@ class ServiceFunctionEndpoint(Base):
     This class defines the main model of the WHOAMI API, declaring the global tags for a specific service function on a specific endpoint.
     """
 
-    __tablename__ = 'sfendpoints'  # table name in the PostgreSQL database
+    __tablename__ = 'sfendpoint'  # table name in the PostgreSQL database
 
     __table_args__ = (UniqueConstraint('sf_i', 'sf_endpoint', 'sr'),)  # defines a unique constraint across 3 columns - sf_i, sf_endpoint, sr
 
@@ -118,6 +62,7 @@ class ServiceFunctionEndpoint(Base):
     def required_columns():
         """
         Returns the required columns for constructing a valid instance.
+
         :return: a generator object
         """
 
@@ -139,6 +84,7 @@ class ServiceFunctionEndpoint(Base):
         :param sf_i: service function instance
         :param sf_endpoint: service function endpoint
         :param sr: service router
+
         :return: the first object from the result set that matches the unique constraint or None
         """
 
@@ -152,6 +98,7 @@ class ServiceFunctionEndpoint(Base):
         :param sf_i: service function instance
         :param sf_endpoint: service function endpoint
         :param sr: service router
+
         :return: True if exists, False otherwise
         """
 
diff --git a/src/service/clmcservice/whoamiapi/conftest.py b/src/service/clmcservice/whoamiapi/conftest.py
index f6ddd2c..9a53129 100644
--- a/src/service/clmcservice/whoamiapi/conftest.py
+++ b/src/service/clmcservice/whoamiapi/conftest.py
@@ -1,7 +1,31 @@
+#!/usr/bin/python3
+"""
+// © University of Southampton IT Innovation Centre, 2018
+//
+// Copyright in this software belongs to University of Southampton
+// IT Innovation Centre of Gamma House, Enterprise Road,
+// Chilworth Science Park, Southampton, SO16 7NS, UK.
+//
+// This software may not be used, sold, licensed, transferred, copied
+// or reproduced in whole or in part in any manner or form or in or
+// on any media by any person other than in accordance with the terms
+// of the Licence Agreement supplied with the software, or otherwise
+// without the prior written consent of the copyright owners.
+//
+// This software is distributed WITHOUT ANY WARRANTY, without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE, except where stated in the Licence Agreement supplied with
+// the software.
+//
+//      Created By :            Nikolay Stanchev
+//      Created Date :          25-06-2018
+//      Created for Project :   FLAME
+"""
+
 import pytest
 from sqlalchemy import create_engine
 from sqlalchemy.exc import ProgrammingError, OperationalError
-from clmcservice.models import DBSession, Base
+from clmcservice.models.meta import DBSession, Base
 
 
 def create_test_database(db_name):
diff --git a/src/service/clmcservice/whoamiapi/tests.py b/src/service/clmcservice/whoamiapi/tests.py
index e35b5b9..f508866 100644
--- a/src/service/clmcservice/whoamiapi/tests.py
+++ b/src/service/clmcservice/whoamiapi/tests.py
@@ -1,3 +1,27 @@
+#!/usr/bin/python3
+"""
+// © University of Southampton IT Innovation Centre, 2018
+//
+// Copyright in this software belongs to University of Southampton
+// IT Innovation Centre of Gamma House, Enterprise Road,
+// Chilworth Science Park, Southampton, SO16 7NS, UK.
+//
+// This software may not be used, sold, licensed, transferred, copied
+// or reproduced in whole or in part in any manner or form or in or
+// on any media by any person other than in accordance with the terms
+// of the Licence Agreement supplied with the software, or otherwise
+// without the prior written consent of the copyright owners.
+//
+// This software is distributed WITHOUT ANY WARRANTY, without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE, except where stated in the Licence Agreement supplied with
+// the software.
+//
+//      Created By :            Nikolay Stanchev
+//      Created Date :          25-06-2018
+//      Created for Project :   FLAME
+"""
+
 import pytest
 from json import dumps
 from pyramid import testing
@@ -259,7 +283,7 @@ class TestWhoamiAPI(object):
         request.params["sf_i"] = "sf_i1"
         request.params["sr"] = "sr1"
         response = WhoamiAPI(request).delete()
-        assert response == to_delete, "DELETE must return an empty body if successful"
+        assert response == to_delete, "DELETE must return the deleted object if successful"
 
         assert not ServiceFunctionEndpoint.exists("sf_i1", "sf_endpoint1", "sr1"), "Resource must be deleted after the delete API method has been called."
 
diff --git a/src/service/clmcservice/whoamiapi/utilities.py b/src/service/clmcservice/whoamiapi/utilities.py
index e75439e..fd141d6 100644
--- a/src/service/clmcservice/whoamiapi/utilities.py
+++ b/src/service/clmcservice/whoamiapi/utilities.py
@@ -61,11 +61,7 @@ def validate_sfendpoint_params(params):
 
     constrained_cols = ServiceFunctionEndpoint.constrained_columns()
 
-    assert len(params) == len(constrained_cols), "Incorrect number of arguments."
-
-    # validate that all required attributes are given in the dictionary
-    for attribute in constrained_cols:
-        assert attribute in params, "Required attribute not found in the request parameters."
+    assert len(params) == len(constrained_cols), "Incorrect url query parameters."
 
     return params
 
diff --git a/src/service/clmcservice/whoamiapi/views.py b/src/service/clmcservice/whoamiapi/views.py
index d4e6501..3e3a4e6 100644
--- a/src/service/clmcservice/whoamiapi/views.py
+++ b/src/service/clmcservice/whoamiapi/views.py
@@ -74,7 +74,7 @@ class WhoamiAPI(object):
         """
         A POST API call to create a new service function endpoint.
 
-        :return: A JSON response to the POST call - essentially with the new configured data and comment of the state of the aggregator
+        :return: A JSON response to the POST call - essentially with the data of the new resource
         :raises HTTPBadRequest: if request body is not a valid JSON for the configuration
         :raises HTTPConflict: if the unique constraints are not preserved after the creation of a new instance
         """
@@ -131,7 +131,7 @@ class WhoamiAPI(object):
         """
         Deletes an instance of a service function endpoint configuration in the database.
 
-        :return: An empty body indicating the the content has been deleted - status code 204.
+        :return: A content of the object that has been deleted
         :raises HTTPBadRequest: if the request parameters are invalid(invalid url query string)
         :raises HTTPNotFound: if a resource with the given parameters doesn't exist in the database
         """
-- 
GitLab