Skip to content
Snippets Groups Projects
Commit 00e90b39 authored by Nikolay Stanchev's avatar Nikolay Stanchev
Browse files

WHOAMI API - initial implementation

parent 1c58dfca
No related branches found
No related tags found
No related merge requests found
......@@ -46,9 +46,14 @@ def main(global_config, **settings):
config = Configurator(settings=settings)
# add routes of the aggregator API
config.add_route('aggregator_config', '/aggregator/config')
config.add_route('aggregator_controller', '/aggregator/control')
config.add_route('round_trip_time_query', '/query/round-trip-time')
# add routes of the WHOAMI API
config.add_route('whoami_endpoints', '/whoami/endpoints')
config.add_route('whoami_endpoints_instance', 'whoami/endpoints/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()
__all__ = ['utilities', 'views']
__all__ = ['views']
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
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
Base = declarative_base() # initialise a declarative Base instance to use for the web app models
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)
class ServiceFunctionEndpoint(Base):
......@@ -17,12 +91,68 @@ class ServiceFunctionEndpoint(Base):
__table_args__ = (UniqueConstraint('sf_i', 'sf_endpoint', 'sr'),) # defines a unique constraint across 3 columns - sf_i, sf_endpoint, sr
uid = Column(Integer, primary_key=True, autoincrement=True) # a primary key integer field (auto incremented)
uid = Column(Integer, primary_key=True, autoincrement=True, nullable=False) # a primary key integer field (auto incremented)
location = Column(String, nullable=False) # cluster label
sfc = Column(String, nullable=False) # service function chain label
sfc_i = Column(String, nullable=False) # service function chain instance identifier
sf = Column(String, nullable=False) # service function label
sf_i = Column(String, nullable=False) # service function identifier (potentially FQDN)
sf_endpoint = Column(String, nullable=False) # service function endpoint (potentially IP address)
sr = Column(String, nullable=False) # service router ID - service router that connects the VM to FLAME
@property
def json(self):
"""
Converts an instance of a ServiceFunctionEndpoint 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 ServiceFunctionEndpoint.__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 ServiceFunctionEndpoint.__table_args__[0].columns)
@staticmethod
def get(sf_i, sf_endpoint, sr):
"""
Gets the instance matching the unique constraint or None if not existing.
: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
"""
return ServiceFunctionEndpoint.query().filter(and_(ServiceFunctionEndpoint.sf_i == sf_i, ServiceFunctionEndpoint.sf_endpoint == sf_endpoint, ServiceFunctionEndpoint.sr == sr)).first()
@staticmethod
def exists(sf_i, sf_endpoint, sr):
"""
Checks if an instance matching the unique constraint exists.
:param sf_i: service function instance
:param sf_endpoint: service function endpoint
:param sr: service router
:return: True if exists, False otherwise
"""
location = Column(String) # cluster label
sfc = Column(String) # service function chain label
sfc_i = Column(String) # service function chain instance identifier
sf = Column(String) # service function label
sf_i = Column(String) # service function identifier (potentially FQDN)
sf_endpoint = Column(String) # service function endpoint (potentially IP address)
sr = Column(String) # service router ID - service router that connects the VM to FLAME
return ServiceFunctionEndpoint.get(sf_i, sf_endpoint, sr) is not None
import pytest
from sqlalchemy import create_engine
from sqlalchemy.exc import ProgrammingError, OperationalError
from clmcservice.models import DBSession, Base
def create_test_database(db_name):
"""
This function creates a test database with the given name. If the database already exists, it is recreated.
:param db_name: the test database name
"""
engine = create_engine("postgresql://clmc:clmc_service@localhost:5432/postgres", echo=False)
conn = engine.connect().execution_options(autocommit=False)
conn.execute("ROLLBACK") # connection is already in a transaction, hence roll back (postgres databases cannot be created in a transaction)
try:
conn.execute("DROP DATABASE %s" % db_name)
print("\nOld database '{0}' has been deleted.".format(db_name))
except ProgrammingError:
# database probably doesn't exist
conn.execute("ROLLBACK")
except OperationalError as e:
print(e)
# database exists and is probably being used by other users
conn.execute("ROLLBACK")
conn.close()
engine.dispose()
raise pytest.exit("Old test database cannot be deleted.")
conn.execute("CREATE DATABASE %s" % db_name)
conn.close()
engine.dispose()
print("\nNew test database '{0}' has been created.".format(db_name))
def initialise_database(db_name):
"""
This function initialises the test database by binding the shared DB session to a new connection engine and creating tables for all models.
:param db_name: test database name
:return: the configured DB session, which is connected to the test database
"""
engine = create_engine('postgresql://clmc:clmc_service@localhost:5432/{0}'.format(db_name)) # create an engine to connect to the test database
DBSession.configure(bind=engine) # configure the database session
Base.metadata.bind = engine
Base.metadata.create_all() # create tables for all models
return DBSession, engine
def drop_test_database(db_name):
"""
This function removes the test database with the given name, if it exists
:param db_name: the test database name
"""
engine = create_engine("postgresql://clmc:clmc_service@localhost:5432/postgres", echo=False)
conn = engine.connect().execution_options(autocommit=False)
conn.execute("ROLLBACK") # connection is already in a transaction, hence roll back (postgres databases cannot be created in a transaction)
try:
conn.execute("DROP DATABASE %s" % db_name)
print("\nTest database '{0}' has been deleted.".format(db_name))
except ProgrammingError:
# database probably doesn't exist
conn.execute("ROLLBACK")
except OperationalError as e:
print(e)
# database is probably being used by other users
conn.execute("ROLLBACK")
conn.close()
engine.dispose()
@pytest.fixture(scope='module', autouse=True)
def testing_db_session():
test_database = "whoamitestdb"
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
import pytest
from json import dumps
from pyramid import testing
from pyramid.httpexceptions import HTTPBadRequest, HTTPNotFound, HTTPConflict
from clmcservice.models import ServiceFunctionEndpoint
from clmcservice.whoamiapi.views import WhoamiAPI
class TestWhoamiAPI(object):
"""
A pytest-implementation test for the WHOAMI API endpoints
"""
@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()
ServiceFunctionEndpoint.delete_all() # clear the instances of the model in the test database
def test_get_all(self):
"""
Tests the GET all method of the WHOAMI API - returns a list of all service function endpoint configurations from the database.
"""
request = testing.DummyRequest()
response = WhoamiAPI(request).get_all()
assert response == [], "Initially there mustn't be any service function endpoint configurations in the database."
sf_e = ServiceFunctionEndpoint(location="DC1", sfc="sfc1", sfc_i="sfc_i1", sf="sf1", sf_i="sf_i1", sf_endpoint="sf_endpoint1", sr="sr1")
expected_response_data = [sf_e.json]
ServiceFunctionEndpoint.add(sf_e) # adds the new instance of the model to the database
request = testing.DummyRequest()
response = WhoamiAPI(request).get_all()
assert response == expected_response_data, "Incorrect response data with 1 service function endpoint configuration."
sf_e = ServiceFunctionEndpoint(location="DC2", sfc="sfc2", sfc_i="sfc_i2", sf="sf2", sf_i="sf_i2", sf_endpoint="sf_endpoint2", sr="sr2")
expected_response_data.append(sf_e.json)
ServiceFunctionEndpoint.add(sf_e)
sf_e = ServiceFunctionEndpoint(location="DC3", sfc="sfc3", sfc_i="sfc_i3", sf="sf3", sf_i="sf_i3", sf_endpoint="sf_endpoint3", sr="sr3")
expected_response_data.append(sf_e.json)
ServiceFunctionEndpoint.add(sf_e)
request = testing.DummyRequest()
response = WhoamiAPI(request).get_all()
assert response == expected_response_data, "Incorrect response data with more than 1 service function endpoint configurations."
def test_get_one(self):
"""
Tests the GET one method of the WHOAMI API - returns an instance of a service function endpoint configuration from the database.
"""
request = testing.DummyRequest()
response = WhoamiAPI(request).get_all()
assert response == [], "Initially there mustn't be any service function endpoint configurations in the database."
self._validation_of_url_parameters_test("get_one")
sf_e = ServiceFunctionEndpoint(location="DC1", sfc="sfc1", sfc_i="sfc_i1", sf="sf1", sf_i="sf_i1", sf_endpoint="sf_endpoint1", sr="sr1")
expected_response_data = sf_e.json
ServiceFunctionEndpoint.add(sf_e) # adds the new instance of the model to the database
request = testing.DummyRequest()
request.params["sf_endpoint"] = "sf_endpoint1"
request.params["sf_i"] = "sf_i1"
request.params["sr"] = "sr1"
response = WhoamiAPI(request).get_one()
assert response == expected_response_data, "Invalid data returned in the response of GET instance"
request = testing.DummyRequest()
request.params["sf_endpoint"] = "sf_endpoint2"
request.params["sf_i"] = "sf_i2"
request.params["sr"] = "sr2"
error_raised = False
try:
WhoamiAPI(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 endpoint"
def test_post(self):
"""
Tests the POST method of the WHOAMI API - creates an instance of a service function endpoint configuration in the database.
"""
request = testing.DummyRequest()
response = WhoamiAPI(request).get_all()
assert response == [], "Initially there mustn't be any service function endpoint configurations in the database."
resource = dict(location="DC1", sfc="sfc1", sfc_i="sfc_i1", sf="sf1", sf_i="sf_i1", sf_endpoint="sf_endpoint1", sr="sr1")
json_data = dumps(resource)
request = testing.DummyRequest()
request.body = json_data.encode(request.charset)
response = WhoamiAPI(request).post()
assert response == resource, "POST request must return the created resource"
assert ServiceFunctionEndpoint.exists("sf_i1", "sf_endpoint1", "sr1"), "POST request must have created the resource"
resource["location"] = "DC2"
json_data = dumps(resource)
request = testing.DummyRequest()
request.body = json_data.encode(request.charset)
error_raised = False
try:
WhoamiAPI(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", [
('{"location": "DC1", "sfc": "sfc1", "sfc_i": "sfc_i1", "sf": "sf1", "sf_i": "sf_i1", "sf_endpoint": "sf_endpoint1", "sr": "sr1"}', True),
('{"location": "DC2", "sfc": "sfc2", "sfc_i": "sfc_i2", "sf": "sf2", "sf_i": "sf_i2", "sf_endpoint": "sf_endpoint2", "sr": "sr2"}', True),
('{}', False),
('{"location": "DC1", "sfc": "sfc1", "sfc_i": "sfc_i1", "sf": "sf1", "sf_i": "sf_i1"}', False),
('{"place": "DC2", "sfc": "sfc2", "sfc_i": "sfc_i2", "sf": "sf2", "sf_i": "sf_i2", "sf_endpoint": "sf_endpoint2", "sr": "sr2"}', 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:
WhoamiAPI(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 WHOAMI API - overwrites an instance of a service function endpoint configuration from the database.
"""
request = testing.DummyRequest()
response = WhoamiAPI(request).get_all()
assert response == [], "Initially there mustn't be any service function endpoint configurations in the database."
self._validation_of_url_parameters_test("put")
resource = dict(location="location1", sfc="sfc1", sfc_i="sfc_i1", sf="sf1", sf_i="sf_i1", sf_endpoint="sf_endpoint1", sr="sr1")
body = dumps(resource)
request = testing.DummyRequest()
request.params["sf_endpoint"] = "sf_endpoint1"
request.params["sf_i"] = "sf_i1"
request.params["sr"] = "sr1"
request.body = body.encode(request.charset)
error_raised = False
try:
WhoamiAPI(request).put()
except HTTPNotFound:
error_raised = True
assert error_raised, "Not found error must be raised in case of a non existing service function endpoint"
sf_e = ServiceFunctionEndpoint(location="DC1", sfc="sfc1", sfc_i="sfc_i1", sf="sf1", sf_i="sf_i1", sf_endpoint="sf_endpoint1", sr="sr1")
ServiceFunctionEndpoint.add(sf_e) # adds the new instance of the model to the database
resource = dict(location="location1", sfc="sfc1", sfc_i="sfc_i1", sf="sf1", sf_i="sf_i1", sf_endpoint="sf_endpoint1", sr="sr1")
body = dumps(resource)
request = testing.DummyRequest()
request.params["sf_endpoint"] = "sf_endpoint1"
request.params["sf_i"] = "sf_i1"
request.params["sr"] = "sr1"
request.body = body.encode(request.charset)
response = WhoamiAPI(request).put()
assert response == resource, "PUT request must return the updated resource"
assert ServiceFunctionEndpoint.get("sf_i1", "sf_endpoint1", "sr1").json["location"] == "location1"
resource = dict(location="DC1", sfc="sfc1", sfc_i="sfc_i1", sf="sf1", sf_i="sf_i2", sf_endpoint="sf_endpoint2", sr="sr2")
body = dumps(resource)
request = testing.DummyRequest()
request.params["sf_endpoint"] = "sf_endpoint1"
request.params["sf_i"] = "sf_i1"
request.params["sr"] = "sr1"
request.body = body.encode(request.charset)
response = WhoamiAPI(request).put()
assert response == resource, "PUT request must return the updated resource"
assert not ServiceFunctionEndpoint.exists("sf_i1", "sf_endpoint1", "sr1"), "Resource has not been updated"
assert ServiceFunctionEndpoint.exists("sf_i2", "sf_endpoint2", "sr2"), "Resource has not been updated"
sf_e = ServiceFunctionEndpoint(location="DC1", sfc="sfc1", sfc_i="sfc_i1", sf="sf1", sf_i="sf_i1", sf_endpoint="sf_endpoint1", sr="sr1")
ServiceFunctionEndpoint.add(sf_e) # adds the new instance of the model to the database
resource = dict(location="DC1", sfc="sfc1", sfc_i="sfc_i1", sf="sf1", sf_i="sf_i2", sf_endpoint="sf_endpoint2", sr="sr2")
body = dumps(resource)
request = testing.DummyRequest()
request.params["sf_endpoint"] = "sf_endpoint1"
request.params["sf_i"] = "sf_i1"
request.params["sr"] = "sr1"
request.body = body.encode(request.charset)
error_raised = False
try:
WhoamiAPI(request).put()
except HTTPConflict:
error_raised = True
assert error_raised, "PUT request validates unique constraint"
def test_delete(self):
"""
Tests the DELETE method of the WHOAMI API - deletes an instance of a service function endpoint configuration from the database.
"""
request = testing.DummyRequest()
response = WhoamiAPI(request).get_all()
assert response == [], "Initially there mustn't be any service function endpoint configurations in the database."
self._validation_of_url_parameters_test("delete")
sf_e = ServiceFunctionEndpoint(location="DC1", sfc="sfc1", sfc_i="sfc_i1", sf="sf1", sf_i="sf_i1", sf_endpoint="sf_endpoint1", sr="sr1")
ServiceFunctionEndpoint.add(sf_e) # adds the new instance of the model to the database
assert ServiceFunctionEndpoint.exists("sf_i1", "sf_endpoint1", "sr1")
request = testing.DummyRequest()
request.params["sf_endpoint"] = "sf_endpoint1"
request.params["sf_i"] = "sf_i1"
request.params["sr"] = "sr1"
response = WhoamiAPI(request).delete()
assert response == {}, "DELETE must return an empty body if successful"
assert not ServiceFunctionEndpoint.exists("sf_i1", "sf_endpoint1", "sr1"), "Resource must be deleted after the delete API method has been called."
request = testing.DummyRequest()
request.params["sf_endpoint"] = "sf_endpoint1"
request.params["sf_i"] = "sf_i1"
request.params["sr"] = "sr1"
error_raised = False
try:
WhoamiAPI(request).delete()
except HTTPNotFound:
error_raised = True
assert error_raised, "Not found error must be raised in case of a non existing service function endpoint"
@staticmethod
def _validation_of_url_parameters_test(method):
"""
Validates the way a whoami API method handles url query parameters
:param method: the method to test
"""
request = testing.DummyRequest()
error_raised = False
try:
getattr(WhoamiAPI(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["sf_i"] = "sf_i"
request.params["sr"] = "sr"
try:
getattr(WhoamiAPI(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_endp"] = "sf_endpoint" # argument should be sf_endpoint
request.params["sf_i"] = "sf_i"
request.params["sr"] = "sr"
try:
getattr(WhoamiAPI(request), method).__call__()
except HTTPBadRequest:
error_raised = True
assert error_raised, "Error must be raised in case of invalid naming of arguments"
#!/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
"""
from json import loads
from clmcservice.models import ServiceFunctionEndpoint
def validate_sfendpoint_body(body):
"""
Validates the request body used to create an endpoint configuration resource in the database.
:param body: the request body to validate
:return the validated configuration dictionary object
:raise AssertionError: if the body is not a valid configuration
"""
try:
body = loads(body)
except:
raise AssertionError("Configuration must be a JSON object.")
# the database table has one more column which is a UID integer
assert len(body) == len(ServiceFunctionEndpoint.__table__.columns) - 1, "Endpoint configuration 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 ServiceFunctionEndpoint.required_columns():
assert attribute in body, "Required attribute not found in the request content."
return body
def validate_sfendpoint_params(params):
"""
Validates the request parameters to retrieve an endpoint configuration resource from the database.
:param params: the parameters dictionary to validate
:return: the validated parameters
:raise AssertionError: for invalid parameters
"""
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."
return params
#!/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
"""
from pyramid.httpexceptions import HTTPBadRequest, HTTPConflict, HTTPNotFound
from pyramid.view import view_defaults, view_config
from clmcservice.models import ServiceFunctionEndpoint
from clmcservice.whoamiapi.utilities import validate_sfendpoint_body, validate_sfendpoint_params
@view_defaults(renderer='json')
class WhoamiAPI(object):
"""
A class-based view for accessing and mutating the configuration of SF endpoints - namely, the WHOAMI API.
"""
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='whoami_endpoints', request_method='GET')
def get_all(self):
"""
GET API call for all resources.
:return: A list of all service function endpoint configurations found in the database.
"""
return [instance.json for instance in ServiceFunctionEndpoint.query()]
@view_config(route_name='whoami_endpoints_instance', request_method='GET')
def get_one(self):
"""
GET API call for a single resources.
:return: One service function endpoint configuration 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_endpoint = self._get_sf_endpoint_from_url_string()
if sf_endpoint is None:
raise HTTPNotFound("A service function endpoint with the given parameters doesn't exist.")
else:
return sf_endpoint.json
@view_config(route_name='whoami_endpoints', request_method='POST')
def post(self):
"""
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
: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
"""
# create an instance of the model and add it to the database table
sf_endpoint = self._validate_and_create()
json_data = sf_endpoint.json
ServiceFunctionEndpoint.add(sf_endpoint)
self.request.response.status = 201
return json_data
@view_config(route_name='whoami_endpoints_instance', request_method='PUT')
def put(self):
"""
A PUT API call to update a service function endpoint.
: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_endpoint = self._get_sf_endpoint_from_url_string()
if sf_endpoint is None:
raise HTTPNotFound("A service function endpoint with the given parameters doesn't exist.")
else:
try:
body = self.request.body.decode(self.request.charset)
validated_body = validate_sfendpoint_body(body) # validate the content and receive a json dictionary object
except AssertionError as e:
raise HTTPBadRequest("Bad request content. Configuration format is incorrect: {0}".format(e.args))
new_resource = validated_body
old_resource = sf_endpoint.json
updating = new_resource["sf_i"] == old_resource["sf_i"] and new_resource["sf_endpoint"] == old_resource["sf_endpoint"] and new_resource["sr"] == old_resource["sr"]
if updating:
ServiceFunctionEndpoint.delete(sf_endpoint)
new_sf_endpoint = ServiceFunctionEndpoint(**validated_body)
ServiceFunctionEndpoint.add(new_sf_endpoint)
else:
resource_exists = ServiceFunctionEndpoint.exists(new_resource["sf_i"], new_resource["sf_endpoint"], new_resource["sr"])
if resource_exists:
raise HTTPConflict("Service function endpoint with this configuration already exists.") # error 409 in case of resource conflict
new_sf_endpoint = ServiceFunctionEndpoint(**validated_body)
ServiceFunctionEndpoint.replace(sf_endpoint, new_sf_endpoint)
return validated_body
@view_config(route_name='whoami_endpoints_instance', request_method='DELETE')
def delete(self):
"""
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.
: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_endpoint = self._get_sf_endpoint_from_url_string()
if sf_endpoint is None:
raise HTTPNotFound("A service function endpoint with the given parameters doesn't exist.")
else:
ServiceFunctionEndpoint.delete(sf_endpoint)
self.request.response.status = 204
return {}
def _get_sf_endpoint_from_url_string(self):
"""
Retrieves a service function endpoint configuration from the database by validating and then using the request url parameters.
:return: An instance of a service function endpoint configuration or None if not existing
"""
params = {}
for attribute in ServiceFunctionEndpoint.constrained_columns():
if attribute in self.request.params:
params[attribute] = self.request.params.get(attribute)
try:
params = validate_sfendpoint_params(params)
except AssertionError as e:
raise HTTPBadRequest("Request format is incorrect: {0}".format(e.args))
sf_endpoint = ServiceFunctionEndpoint.get(**params)
return sf_endpoint
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 configuration
: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_sfendpoint_body(body) # validate the content and receive a json dictionary object
except AssertionError as e:
raise HTTPBadRequest("Bad request content. Configuration format is incorrect: {0}".format(e.args))
resource = validated_body
resource_exists = ServiceFunctionEndpoint.exists(resource["sf_i"], resource["sf_endpoint"], resource["sr"])
if resource_exists:
raise HTTPConflict("Service function endpoint with this configuration already exists.") # error 409 in case of resource conflict
# create an instance of the model
sf_endpoint = ServiceFunctionEndpoint(**resource)
return sf_endpoint
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment