diff --git a/src/service/clmcservice/configapi/__init__.py b/src/service/clmcservice/configapi/__init__.py deleted file mode 100644 index 8b137891791fe96927ad78e64b0aad7bded08bdc..0000000000000000000000000000000000000000 --- a/src/service/clmcservice/configapi/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/service/clmcservice/configapi/conftest.py b/src/service/clmcservice/configapi/conftest.py deleted file mode 100644 index 105ebe81d84e5bead8e73dd2d069026c58e20ee7..0000000000000000000000000000000000000000 --- a/src/service/clmcservice/configapi/conftest.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/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 deleted file mode 100644 index 39ee0ed900bd66f702960457aee532db404c3a89..0000000000000000000000000000000000000000 --- a/src/service/clmcservice/configapi/tests.py +++ /dev/null @@ -1,315 +0,0 @@ -#!/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", 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", chain={"nginx": ["minio"]}) - expected_response_data.append(sfc.json) - ServiceFunctionChain.add(sfc) - sfc = ServiceFunctionChain(sfc="sfc3", 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 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", 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" - 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" - 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 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", 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"), "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", "chain":{"nginx":["minio"]}}', True), - ('{"sfc": "sfc2", "chain":{}}', True), - ('{"sfc": "sfc1", "chain":[]}', False), - ('{}', False), - ('{"sfc": "sfc3"}', 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 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", chain={"nginx": ["minio"]}) - body = dumps(resource) - request = testing.DummyRequest() - request.params["sfc"] = "sfc1" - 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", chain={"nginx": ["minio"]}) - ServiceFunctionChain.add(sfc) # adds the new instance of the model to the database - - resource = dict(sfc="sfc1", chain={}) - body = dumps(resource) - request = testing.DummyRequest() - request.params["sfc"] = "sfc1" - request.body = body.encode(request.charset) - response = SFCConfigAPI(request).put() - assert response == resource, "PUT request must return the updated resource" - assert ServiceFunctionChain.get("sfc1").json["chain"] == {} - - resource = dict(sfc="sfc2", chain={"nginx": ["minio"]}) - body = dumps(resource) - request = testing.DummyRequest() - request.params["sfc"] = "sfc1" - 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"), "Resource has not been updated" - assert ServiceFunctionChain.exists("sfc2"), "Resource has not been updated" - - sfc = ServiceFunctionChain(sfc="sfc1", chain={"nginx": ["minio"]}) - ServiceFunctionChain.add(sfc) # adds the new instance of the model to the database - - resource = dict(sfc="sfc2", chain={"nginx": ["minio"]}) - body = dumps(resource) - request = testing.DummyRequest() - request.params["sfc"] = "sfc1" - 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", "chain":{"nginx":["minio"]}}', True), - ('{"sfc": "sfc2", "chain":{}}', True), - ('{"sfc": "sfc1", "chain":[]}', False), - ('{}', False), - ('{"sfc": "sfc3"}', 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", chain={"nginx": ["minio"]}) - ServiceFunctionChain.add(sfc) # adds the new instance of the model to the database - - request = testing.DummyRequest() - request.params["sfc"] = "sfc1" - 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 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", chain={"nginx": ["minio"]}) - to_delete = sfc.json - ServiceFunctionChain.add(sfc) # adds the new instance of the model to the database - - assert ServiceFunctionChain.exists("sfc1") - - request = testing.DummyRequest() - request.params["sfc"] = "sfc1" - response = SFCConfigAPI(request).delete() - assert response == to_delete, "DELETE must return the deleted object if successful" - - assert not ServiceFunctionChain.exists("sfc1"), "Resource must be deleted after the delete API method has been called." - - request = testing.DummyRequest() - request.params["sfc"] = "sfc1" - 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_i"] = "sfc1" # argument should be sfc - 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 - 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 deleted file mode 100644 index 0605c0599c5b1a9224001cfa603f3d7fd9a01c9b..0000000000000000000000000000000000000000 --- a/src/service/clmcservice/configapi/utilities.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/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.") - - 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.__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." - - for sf in body["chain"]: - assert type(body["chain"][sf]) == list, "A list must be used to represent each dependency between service functions" - - return body diff --git a/src/service/clmcservice/configapi/views.py b/src/service/clmcservice/configapi/views.py deleted file mode 100644 index 4cfdb69b6e842aa460c4a330961c339acd7421c0..0000000000000000000000000000000000000000 --- a/src/service/clmcservice/configapi/views.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/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 - - -@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 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 - """ - - 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 ID 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"] - - 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"]) - 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 ID 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 - """ - - if "sfc" not in self.request.params: - raise HTTPBadRequest("Request format is incorrect: URL argument 'sfc' not found") - - sf_chain = ServiceFunctionChain.get(sfc=self.request.params["sfc"]) - 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"]) - 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/models/__init__.py b/src/service/clmcservice/models/__init__.py index 6ceb55de67a31ce1456af9e100204097b5120309..bdf3774dab46f54179ef4e30aa05c18f2956f9e9 100644 --- a/src/service/clmcservice/models/__init__.py +++ b/src/service/clmcservice/models/__init__.py @@ -1,3 +1,2 @@ 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 deleted file mode 100644 index 17ced600999db1440c6afc1daaf26f2aab6b6022..0000000000000000000000000000000000000000 --- a/src/service/clmcservice/models/config_models.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/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, 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 - - 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 - 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} - - 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) - - @staticmethod - def get(sfc): - """ - Gets the instance matching the sfc argument - - :param sfc: service function chain id - - :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)).first() - - @staticmethod - def exists(sfc): - """ - Checks if an instance matching the sfc exists. - - :param sfc: service function chain id - - :return: True if exists, False otherwise - """ - - return ServiceFunctionChain.get(sfc) is not None