diff --git a/src/service/clmcservice/configapi/tests.py b/src/service/clmcservice/configapi/tests.py index 38240471c873a5f460349aff9824539d855c78fb..39ee0ed900bd66f702960457aee532db404c3a89 100644 --- a/src/service/clmcservice/configapi/tests.py +++ b/src/service/clmcservice/configapi/tests.py @@ -57,7 +57,7 @@ class TestSFCConfigAPI(object): 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"]}) + sfc = ServiceFunctionChain(sfc="sfc1", chain={"nginx": ["minio"]}) expected_response_data = [sfc.json] ServiceFunctionChain.add(sfc) # adds the new instance of the model to the database @@ -65,10 +65,10 @@ class TestSFCConfigAPI(object): 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"]}) + sfc = ServiceFunctionChain(sfc="sfc2", chain={"nginx": ["minio"]}) expected_response_data.append(sfc.json) ServiceFunctionChain.add(sfc) - sfc = ServiceFunctionChain(sfc="sfc2", sfc_i="sfc_i2", chain={"nginx": ["minio"]}) + sfc = ServiceFunctionChain(sfc="sfc3", chain={"nginx": ["minio"]}) expected_response_data.append(sfc.json) ServiceFunctionChain.add(sfc) @@ -78,7 +78,7 @@ class TestSFCConfigAPI(object): 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. + Tests the GET one method of the config API for service function chains - returns a service function chain from the database. """ request = testing.DummyRequest() @@ -87,19 +87,17 @@ class TestSFCConfigAPI(object): self._validation_of_url_parameters_test("get_one") - sfc = ServiceFunctionChain(sfc="sfc1", sfc_i="sfc_i1", chain={"nginx": ["minio"]}) + sfc = ServiceFunctionChain(sfc="sfc1", 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() @@ -109,20 +107,20 @@ class TestSFCConfigAPI(object): 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. + Tests the POST method of the config API for service function chains - creates 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"]}) + resource = dict(sfc="sfc1", 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" + assert ServiceFunctionChain.exists("sfc1"), "POST request must have created the resource" resource["chain"] = {} json_data = dumps(resource) @@ -136,11 +134,11 @@ class TestSFCConfigAPI(object): 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), + ('{"sfc": "sfc1", "chain":{"nginx":["minio"]}}', True), + ('{"sfc": "sfc2", "chain":{}}', True), + ('{"sfc": "sfc1", "chain":[]}', False), ('{}', False), - ('{"sfc": "sfc3", "sfc_i": "sfc_i3"', False), + ('{"sfc": "sfc3"}', False), ('{"sf": "sfc2", "sf_i": "sfc_i2", "chain":{}', False), ('{invalid json}', False), ]) @@ -163,7 +161,7 @@ class TestSFCConfigAPI(object): 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. + Tests the PUT method of the Config API for service function chains - overwrites a service function chain from the database. """ request = testing.DummyRequest() @@ -172,11 +170,10 @@ class TestSFCConfigAPI(object): self._validation_of_url_parameters_test("put") - resource = dict(sfc="sfc1", sfc_i="sfc_i1", chain={"nginx": ["minio"]}) + resource = dict(sfc="sfc1", 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: @@ -185,38 +182,35 @@ class TestSFCConfigAPI(object): 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"]}) + sfc = ServiceFunctionChain(sfc="sfc1", 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={}) + resource = dict(sfc="sfc1", 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"] == {} + assert ServiceFunctionChain.get("sfc1").json["chain"] == {} - resource = dict(sfc="sfc2", sfc_i="sfc_i2", chain={"nginx": ["minio"]}) + resource = dict(sfc="sfc2", 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" + assert not ServiceFunctionChain.exists("sfc1"), "Resource has not been updated" + assert ServiceFunctionChain.exists("sfc2"), "Resource has not been updated" - sfc = ServiceFunctionChain(sfc="sfc1", sfc_i="sfc_i1", chain={"nginx": ["minio"]}) + sfc = ServiceFunctionChain(sfc="sfc1", 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"]}) + resource = dict(sfc="sfc2", 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: @@ -226,11 +220,11 @@ class TestSFCConfigAPI(object): 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), + ('{"sfc": "sfc1", "chain":{"nginx":["minio"]}}', True), + ('{"sfc": "sfc2", "chain":{}}', True), + ('{"sfc": "sfc1", "chain":[]}', False), ('{}', False), - ('{"sfc": "sfc3", "sfc_i": "sfc_i3"', False), + ('{"sfc": "sfc3"}', False), ('{"sf": "sfc2", "sf_i": "sfc_i2", "chain":{}', False), ('{invalid json}', False), ]) @@ -242,12 +236,11 @@ class TestSFCConfigAPI(object): :param valid: True if body is valid, False otherwise """ - sfc = ServiceFunctionChain(sfc="sfc1", sfc_i="sfc_i1", chain={"nginx": ["minio"]}) + sfc = ServiceFunctionChain(sfc="sfc1", 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: @@ -258,7 +251,7 @@ class TestSFCConfigAPI(object): 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. + Tests the DELETE method of the config API for service function chains - deletes a service function chain from the database. """ request = testing.DummyRequest() @@ -267,23 +260,21 @@ class TestSFCConfigAPI(object): self._validation_of_url_parameters_test("delete") - sfc = ServiceFunctionChain(sfc="sfc1", sfc_i="sfc_i1", chain={"nginx": ["minio"]}) + sfc = ServiceFunctionChain(sfc="sfc1", 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") + assert ServiceFunctionChain.exists("sfc1") 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." + assert not ServiceFunctionChain.exists("sfc1"), "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() @@ -308,7 +299,7 @@ class TestSFCConfigAPI(object): assert error_raised, "Error must be raised in case of no URL parameters" request = testing.DummyRequest() - request.params["sfc"] = "sfc1" + request.params["sfc_i"] = "sfc1" # argument should be sfc try: getattr(SFCConfigAPI(request), method).__call__() except HTTPBadRequest: @@ -317,7 +308,6 @@ class TestSFCConfigAPI(object): 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: diff --git a/src/service/clmcservice/configapi/utilities.py b/src/service/clmcservice/configapi/utilities.py index 8a3babc75a48f2c3c7e2dcf768c927dd1e0e081e..0605c0599c5b1a9224001cfa603f3d7fd9a01c9b 100644 --- a/src/service/clmcservice/configapi/utilities.py +++ b/src/service/clmcservice/configapi/utilities.py @@ -40,29 +40,15 @@ def validate_sfchain_body(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." + assert len(body) == len(ServiceFunctionChain.__table__.columns), "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." + for attribute in ServiceFunctionChain.__table__.columns: + assert attribute.name 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 - + for sf in body["chain"]: + assert type(body["chain"][sf]) == list, "A list must be used to represent each dependency between service functions" -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 + return body diff --git a/src/service/clmcservice/configapi/views.py b/src/service/clmcservice/configapi/views.py index 571655f548f261c56ff220fa08b3d9d7f2de2961..4cfdb69b6e842aa460c4a330961c339acd7421c0 100644 --- a/src/service/clmcservice/configapi/views.py +++ b/src/service/clmcservice/configapi/views.py @@ -26,7 +26,7 @@ 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 +from clmcservice.configapi.utilities import validate_sfchain_body @view_defaults(renderer='json') @@ -59,7 +59,7 @@ class SFCConfigAPI(object): """ GET API call for a single resources. - :return: One service function chain instance retrieved from the database by querying the uniquely constrained columns. + :return: One service function chain instance retrieved from the database by querying the sfc ID :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 """ @@ -101,7 +101,7 @@ class SFCConfigAPI(object): 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.") + raise HTTPNotFound("A service function chain with the given ID doesn't exist.") else: try: body = self.request.body.decode(self.request.charset) @@ -111,14 +111,14 @@ class SFCConfigAPI(object): 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"] + updating = new_resource["sfc"] == old_resource["sfc"] 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"]) + resource_exists = ServiceFunctionChain.exists(new_resource["sfc"]) if resource_exists: raise HTTPConflict("Service function chain with this data already exists.") # error 409 in case of resource conflict @@ -139,7 +139,7 @@ class SFCConfigAPI(object): 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.") + raise HTTPNotFound("A service function chain with the given ID doesn't exist.") else: deleted = sf_chain.json ServiceFunctionChain.delete(sf_chain) @@ -152,17 +152,10 @@ class SFCConfigAPI(object): :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)) + if "sfc" not in self.request.params: + raise HTTPBadRequest("Request format is incorrect: URL argument 'sfc' not found") - sf_chain = ServiceFunctionChain.get(**params) + sf_chain = ServiceFunctionChain.get(sfc=self.request.params["sfc"]) return sf_chain def _validate_and_create(self): @@ -182,7 +175,7 @@ class SFCConfigAPI(object): resource = validated_body - resource_exists = ServiceFunctionChain.exists(resource["sfc"], resource["sfc_i"]) + resource_exists = ServiceFunctionChain.exists(resource["sfc"]) if resource_exists: raise HTTPConflict("Service function chain with this data already exists.") # error 409 in case of resource conflict diff --git a/src/service/clmcservice/models/config_models.py b/src/service/clmcservice/models/config_models.py index 479004385f1a598c813315b8dbe87fb5cd123374..17ced600999db1440c6afc1daaf26f2aab6b6022 100644 --- a/src/service/clmcservice/models/config_models.py +++ b/src/service/clmcservice/models/config_models.py @@ -22,7 +22,7 @@ // Created for Project : FLAME """ -from sqlalchemy import Column, String, Integer, UniqueConstraint, and_ +from sqlalchemy import Column, String, and_ from sqlalchemy.dialects.postgresql import JSONB from clmcservice.models.meta import Base @@ -34,12 +34,7 @@ class ServiceFunctionChain(Base): __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 + sfc = Column(String, nullable=False, primary_key=True) # service function chain label chain = Column(JSONB, nullable=False) # the service function chain graph represented by a python dictionary (JSON object essentially) @property @@ -51,7 +46,6 @@ class ServiceFunctionChain(Base): """ fields = {c.name: getattr(self, c.name) for c in self.__table__.columns} - fields.pop("uid") return fields @@ -62,38 +56,28 @@ class ServiceFunctionChain(Base): :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) + return tuple(column.name for column in ServiceFunctionChain.__table__.columns) @staticmethod - def get(sfc, sfc_i): + def get(sfc): """ - Gets the instance matching the unique constraint or None if not existing. + Gets the instance matching the sfc argument :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: the first object from the result set that matches the sfc argument (must be only one) """ - return ServiceFunctionChain.query().filter(and_(ServiceFunctionChain.sfc == sfc, ServiceFunctionChain.sfc_i == sfc_i)).first() + return ServiceFunctionChain.query().filter(and_(ServiceFunctionChain.sfc == sfc)).first() @staticmethod - def exists(sfc, sfc_i): + def exists(sfc): """ - Checks if an instance matching the unique constraint exists. + Checks if an instance matching the sfc 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 + return ServiceFunctionChain.get(sfc) is not None diff --git a/src/service/setup.py b/src/service/setup.py index f1ef4cf50df9e1890a72d46e5d5bda7ac4f3bcfc..c3fbf0e617197626ec492eec7553feae7620d31d 100644 --- a/src/service/setup.py +++ b/src/service/setup.py @@ -51,12 +51,12 @@ requires = [ 'psycopg2', 'influxdb', 'neo4j-driver', - 'py2neo', - 'pytest', + 'py2neo' ] tests_require = [ - 'pytest-cov', + 'pytest', + 'pytest-cov' ] setup(