diff options
| author | Georgios Andreadis <info@gandreadis.com> | 2020-07-08 14:35:47 +0200 |
|---|---|---|
| committer | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2020-08-24 19:47:57 +0200 |
| commit | e2e9cec1d4836a4cba81874129b8da8a12c216f6 (patch) | |
| tree | a4f73a93174daad0482ab4b94849bbce94a3491e /web-server | |
| parent | 9761bdd1f2b0f72a2c0fa46b3dee1920a580a26a (diff) | |
Implement scenario adding endpoint
Diffstat (limited to 'web-server')
7 files changed, 197 insertions, 4 deletions
diff --git a/web-server/main.py b/web-server/main.py index 0d24958d..c466c0f2 100644 --- a/web-server/main.py +++ b/web-server/main.py @@ -141,7 +141,7 @@ def serve_web_server_test(): @FLASK_CORE_APP.route('/projects') @FLASK_CORE_APP.route('/projects/<path:project_id>') @FLASK_CORE_APP.route('/profile') -def serve_index(project_id=None, experiment_id=None): +def serve_index(project_id=None): return send_from_directory(STATIC_ROOT, 'index.html') diff --git a/web-server/opendc/api/v2/portfolios/portfolioId/scenarios/__init__.py b/web-server/opendc/api/v2/portfolios/portfolioId/scenarios/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/portfolios/portfolioId/scenarios/__init__.py diff --git a/web-server/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py b/web-server/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py new file mode 100644 index 00000000..ab32aae2 --- /dev/null +++ b/web-server/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py @@ -0,0 +1,42 @@ +from opendc.models.portfolio import Portfolio +from opendc.util.rest import Response + + +def POST(request): + """Add a new Scenario for this Portfolio.""" + + request.check_required_parameters(path={'portfolioId': 'string'}, + body={ + 'scenario': { + 'name': 'string', + 'trace': { + 'traceId': 'string', + 'loadSamplingFraction': 'float', + }, + 'topology': { + 'topologyId': 'string', + }, + 'operational': { + 'failuresEnabled': 'bool', + 'performanceInterferenceEnabled': 'bool', + 'schedulerName': 'string', + }, + } + }) + + portfolio = Portfolio.from_id(request.params_path['portfolioId']) + + portfolio.check_exists() + portfolio.check_user_access(request.google_id, True) + + scenario = Portfolio(request.params_body['scenario']) + + scenario.set_property('portfolioId', request.params_path['portfolioId']) + scenario.set_property('simulationState', 'QUEUED') + + scenario.insert() + + portfolio.obj['portfolioIds'].append(portfolio.get_id()) + portfolio.update() + + return Response(200, 'Successfully added Portfolio.', portfolio.obj) diff --git a/web-server/opendc/api/v2/portfolios/portfolioId/scenarios/test_endpoint.py b/web-server/opendc/api/v2/portfolios/portfolioId/scenarios/test_endpoint.py new file mode 100644 index 00000000..8b55bab0 --- /dev/null +++ b/web-server/opendc/api/v2/portfolios/portfolioId/scenarios/test_endpoint.py @@ -0,0 +1,119 @@ +from opendc.util.database import DB + + +def test_add_scenario_missing_parameter(client): + assert '400' in client.post('/api/v2/portfolios/1/scenarios').status + + +def test_add_scenario_non_existing_portfolio(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value=None) + assert '404' in client.post('/api/v2/portfolios/1/scenarios', + json={ + 'scenario': { + 'name': 'test', + 'trace': { + 'traceId': '1', + 'loadSamplingFraction': 1.0, + }, + 'topology': { + 'topologyId': '1', + }, + 'operational': { + 'failuresEnabled': True, + 'performanceInterferenceEnabled': False, + 'schedulerName': 'DEFAULT', + }, + } + }).status + + +def test_add_scenario_not_authorized(client, mocker): + mocker.patch.object(DB, + 'fetch_one', + return_value={ + '_id': '1', + 'projectId': '1', + 'portfolioId': '1', + 'authorizations': [{ + 'projectId': '1', + 'authorizationLevel': 'VIEW' + }] + }) + assert '403' in client.post('/api/v2/portfolios/1/scenarios', + json={ + 'scenario': { + 'name': 'test', + 'trace': { + 'traceId': '1', + 'loadSamplingFraction': 1.0, + }, + 'topology': { + 'topologyId': '1', + }, + 'operational': { + 'failuresEnabled': True, + 'performanceInterferenceEnabled': False, + 'schedulerName': 'DEFAULT', + }, + } + }).status + + +def test_add_scenario(client, mocker): + mocker.patch.object(DB, + 'fetch_one', + return_value={ + '_id': '1', + 'projectId': '1', + 'portfolioId': '1', + 'portfolioIds': ['1'], + 'scenarioIds': ['1'], + 'authorizations': [{ + 'projectId': '1', + 'authorizationLevel': 'EDIT' + }], + 'simulationState': 'QUEUED', + }) + mocker.patch.object(DB, + 'insert', + return_value={ + '_id': '1', + 'name': 'test', + 'trace': { + 'traceId': '1', + 'loadSamplingFraction': 1.0, + }, + 'topology': { + 'topologyId': '1', + }, + 'operational': { + 'failuresEnabled': True, + 'performanceInterferenceEnabled': False, + 'schedulerName': 'DEFAULT', + }, + 'portfolioId': '1', + 'simulationState': 'QUEUED', + }) + mocker.patch.object(DB, 'update', return_value=None) + res = client.post( + '/api/v2/portfolios/1/scenarios', + json={ + 'scenario': { + 'name': 'test', + 'trace': { + 'traceId': '1', + 'loadSamplingFraction': 1.0, + }, + 'topology': { + 'topologyId': '1', + }, + 'operational': { + 'failuresEnabled': True, + 'performanceInterferenceEnabled': False, + 'schedulerName': 'DEFAULT', + }, + } + }) + assert 'portfolioId' in res.json['content'] + assert 'simulationState' in res.json['content'] + assert '200' in res.status diff --git a/web-server/opendc/api/v2/projects/projectId/endpoint.py b/web-server/opendc/api/v2/projects/projectId/endpoint.py index 1a4c090a..77b66d75 100644 --- a/web-server/opendc/api/v2/projects/projectId/endpoint.py +++ b/web-server/opendc/api/v2/projects/projectId/endpoint.py @@ -52,9 +52,9 @@ def DELETE(request): topology = Topology.from_id(topology_id) topology.delete() - for experiment_id in project.obj['portfolioIds']: - experiment = Portfolio.from_id(experiment_id) - experiment.delete() + for portfolio_id in project.obj['portfolioIds']: + portfolio = Portfolio.from_id(portfolio_id) + portfolio.delete() user = User.from_google_id(request.google_id) user.obj['authorizations'] = list( diff --git a/web-server/opendc/models/scenario.py b/web-server/opendc/models/scenario.py new file mode 100644 index 00000000..d7d959ca --- /dev/null +++ b/web-server/opendc/models/scenario.py @@ -0,0 +1,26 @@ +from opendc.models.model import Model +from opendc.models.portfolio import Portfolio +from opendc.models.user import User +from opendc.util.exceptions import ClientError +from opendc.util.rest import Response + + +class Scenario(Model): + """Model representing a Scenario.""" + + collection_name = 'scenarios' + + def check_user_access(self, google_id, edit_access): + """Raises an error if the user with given [google_id] has insufficient access. + + Checks access on the parent project. + + :param google_id: The Google ID of the user. + :param edit_access: True when edit access should be checked, otherwise view access. + """ + portfolio = Portfolio.from_id(self.obj['portfolioId']) + user = User.from_google_id(google_id) + authorizations = list( + filter(lambda x: str(x['projectId']) == str(portfolio.get_id()), user.obj['authorizations'])) + if len(authorizations) == 0 or (edit_access and authorizations[0]['authorizationLevel'] == 'VIEW'): + raise ClientError(Response(403, 'Forbidden from retrieving/editing scenario.')) diff --git a/web-server/opendc/util/parameter_checker.py b/web-server/opendc/util/parameter_checker.py index d37256e0..214dfa9d 100644 --- a/web-server/opendc/util/parameter_checker.py +++ b/web-server/opendc/util/parameter_checker.py @@ -49,6 +49,12 @@ def _incorrect_parameter(params_required, params_actual, parent=''): if param_required == 'int' and not isinstance(param_actual, int): return '{}.{}'.format(parent, param_name) + if param_required == 'float' and not isinstance(param_actual, float): + return '{}.{}'.format(parent, param_name) + + if param_required == 'bool' and not isinstance(param_actual, bool): + return '{}.{}'.format(parent, param_name) + if param_required == 'string' and not isinstance(param_actual, str) and not isinstance(param_actual, int): return '{}.{}'.format(parent, param_name) |
