diff --git a/services/capability/capability/routes.py b/services/capability/capability/routes.py index ce095dc657f9e42d791cf948960f8b96cffbe37e..a2ad6c15cba34c1f06cba35315d49b3ed2d992ce 100644 --- a/services/capability/capability/routes.py +++ b/services/capability/capability/routes.py @@ -59,18 +59,15 @@ def capability_request_routes(config: Configurator): # GET config.add_route("view_capability_request", f"{request_url}", request_method="GET") # POST - # TODO: Make it so {capability_name} in URL can be passed to capability request config.add_route( "create_capability_request", "capability/{capability_name}/request/create", request_method="POST", - request_param=["parameters", "versions"], ) config.add_route( "edit_capability_request", f"{request_url}/edit", request_method="POST", - request_param=["parameters", "versions"], ) config.add_route("submit_capability_request", f"{request_url}/submit", request_method="POST") config.add_route("cancel_capability_request", f"{request_url}/cancel", request_method="POST") diff --git a/services/capability/capability/views/capability.py b/services/capability/capability/views/capability.py index 14638b4993b9df0e7befba0684b89a192f1e895d..9950b2e2b30eb7f9092c5189cc6879bf6f3508cd 100644 --- a/services/capability/capability/views/capability.py +++ b/services/capability/capability/views/capability.py @@ -26,7 +26,8 @@ def view_capability(request: Request) -> Response: URL: capability/{capability_name} :param request: GET request - :return: Response with JSON-formatted capability info if found or 404 response (HTTPNotFound) + :return: Response with JSON-formatted capability info if found + or 404 response (HTTPNotFound) """ capability = request.capability_info.lookup_capability(request.matchdict["capability_name"]) if capability: @@ -44,8 +45,8 @@ def create_capability(request: Request) -> Response: :param request: POST request, expecting JSON parameters ["capability_name", "steps", "max_jobs"] optionally, accepts a boolean "enabled" parameter :return: Response with JSON-formatted capability info of newly created capability - or 400 response (HTTPBadRequest) if expected parameters not given - or 412 response (HTTPPreconditionFailed) if capability with given name already exists + or 400 response (HTTPBadRequest) if expected parameters not given + or 412 response (HTTPPreconditionFailed) if capability with given name already exists """ expected_params = ["capability_name", "steps", "max_jobs"] params = request.json_body @@ -82,8 +83,9 @@ def edit_capability(request: Request) -> Response: :param request: POST request, expecting JSON parameters ["capability_name", "steps", "max_jobs"] :return: Response with JSON-formatted capability info of newly created capability - or 400 response (HTTPBadRequest) if expected parameters not given - or 412 response (HTTPPreconditionFailed) if capability with given name does not exist + or 400 response (HTTPBadRequest) if expected parameters not given + or 412 response (HTTPPreconditionFailed) if capability with given name does not exist + or 417 response (HTTPExpectationFailed) if the capability was unable to be edited TODO: In the future, we should check if there are any active requests for the capability being edited and disallow its editing until they are finished @@ -117,6 +119,14 @@ def edit_capability(request: Request) -> Response: @view_config(route_name="enable_capability", renderer="json") def enable_capability(request: Request) -> Response: + """ + Pyramid view that enables a capability + + :param request: POST request + :return: HTTP 200 response + or 412 response (HTTPPreconditionFailed) if capability with given name does not exist + or 417 response (HTTPExpectationFailed) if the capability was unable to be enabled + """ capability_name = request.matchdict["capability_name"] if not request.capability_info.lookup_capability(capability_name): @@ -133,6 +143,14 @@ def enable_capability(request: Request) -> Response: @view_config(route_name="disable_capability", renderer="json") def disable_capability(request: Request) -> Response: + """ + Pyramid view that disables a capability + + :param request: POST request + :return: HTTP 200 response + or 412 response (HTTPPreconditionFailed) if capability with given name does not exist + or 417 response (HTTPExpectationFailed) if the capability was unable to be disabled + """ capability_name = request.matchdict["capability_name"] if not request.capability_info.lookup_capability(capability_name): diff --git a/services/capability/capability/views/capability_request.py b/services/capability/capability/views/capability_request.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..78f5636d82d7b88943d2c9f647dff67c9cce6d77 100644 --- a/services/capability/capability/views/capability_request.py +++ b/services/capability/capability/views/capability_request.py @@ -0,0 +1,92 @@ +""" +.. codeauthor:: Nathan Hertz <nhertz@nrao.edu> + +File containing definitions for the other half of the capability side of the Workspaces REST API, +concerning capability requests +""" +from pyramid.httpexceptions import ( + HTTPBadRequest, + HTTPExpectationFailed, + HTTPNotFound, + HTTPPreconditionFailed, +) +from pyramid.request import Request +from pyramid.response import Response +from pyramid.view import view_config + +from workspaces.capability.schema import CapabilityRequest + + +@view_config(route_name="view_capability_request", renderer="json") +def view_capability_request(request: Request) -> Response: + """ + Pyramid view that accepts a request to view a capability request and responds with the request's info, if it exists + URL: capability/{capability_name}/request/{request_id} + + :param request: GET request + :return: Response with JSON-formatted capability request info if found + or 404 response (HTTPNotFound) + """ + capability_request = request.capability_info.lookup_capability_request( + request.matchdict["request_id"] + ) + if capability_request: + return Response(json_body=capability_request.__json__()) + else: + not_found_msg = ( + f"Capability request for capability {request.matchdict['capability_name']}", + f"with ID {request.matchdict['request_id']} not found.", + ) + return HTTPNotFound(detail=not_found_msg) + + +@view_config(route_name="create_capability_request", renderer="json") +def create_capability_request(request: Request) -> Response: + """ + Pyramid view that accepts a request to create a capability request + URL: capability/{capability_name}/request/create + + :param request: POST request, expecting JSON parameters ["parameters", "versions"] + :return: Response with JSON-formatted info of newly created capability request + or 400 response (HTTPBadRequest) if expected parameters not given + or 412 response (HTTPPreconditionFailed) if capability with given name does not exist and thus cannot be + requested + """ + expected_params = ["parameters", "versions"] + # TODO: What is the versions parameter going to look like? + # We can error check for a well-formatted param once we decide + capability_name = request.matchdict["capability_name"] + params = request.json_body + + if not all([expected in params for expected in expected_params]): + # JSON params do not contain all expected params + params_not_given_msg = ( + f"Expected JSON parameters {expected_params}. Received only {params}." + ) + return HTTPBadRequest(detail=params_not_given_msg) + elif not request.capability_info.lookup_capability(capability_name): + # Capability with given name does not exist; can't create a request for it + does_not_exist_msg = ( + f"Capability {capability_name} does not exist. Cannot create request.", + ) + return HTTPPreconditionFailed(detail=does_not_exist_msg) + else: + new_capability_request = CapabilityRequest( + parameters=params["parameters"], + versions=params["versions"], + capability_name=capability_name, + state="Created", + ) + request.capability_info.save_entity(new_capability_request) + return Response(json_body=new_capability_request.__json__()) + + +@view_config(route_name="edit_capability_request", renderer="json") +def edit_capability_request(request: Request) -> Response: + """ + Pyramid view that accepts a request to edit a capability request + URL: capability/{capability_name}/request/{request_id}/edit + + TODO: Implement once CapabilityVersions are supported + """ + return HTTPBadRequest() diff --git a/services/capability/test/conftest.py b/services/capability/test/conftest.py index 9731368c82b81acb4faf1d4cead1e0fc744bccca..d84c2405674b43c5bb9906639181029c5511d239 100644 --- a/services/capability/test/conftest.py +++ b/services/capability/test/conftest.py @@ -1,10 +1,11 @@ +from typing import Any from unittest.mock import MagicMock import pytest from pyramid.config import Configurator from pyramid.testing import DummyRequest, setUp, tearDown -from workspaces.capability.schema import Capability +from workspaces.capability.schema import Capability, CapabilityRequest @pytest.fixture(scope="module") @@ -46,6 +47,15 @@ def request_null_capability() -> DummyRequest: max_jobs=-1, ), ] + capability_requests = [ + CapabilityRequest( + id=0, + state="Created", + capability_name="null", + parameters="-g", + versions=[], + ) + ] def edit_capability( self, name: str, steps: str = None, max_jobs: int = None, enabled: bool = None @@ -64,14 +74,24 @@ def request_null_capability() -> DummyRequest: return True return False - def lookup_capability(self, capability_name: str): + def lookup_capability(self, capability_name: str) -> Capability: for capability in self.capabilities: if capability_name == capability.name: return capability return None - def save_entity(self, capability: Capability): - self.capabilities.append(capability) + def lookup_capability_request(self, request_id: int) -> CapabilityRequest: + for capability_request in self.capability_requests: + if request_id == capability_request.id: + return capability_request + return None + + def save_entity(self, entity: Any): + if type(entity) is Capability: + self.capabilities.append(entity) + elif type(entity) is CapabilityRequest: + entity.id = len(self.capability_requests) + self.capability_requests.append(entity) request = DummyRequest(capability_info=MockCapabilityInfo()) return request diff --git a/services/capability/test/test_capability_views.py b/services/capability/test/test_capability_views.py index 9fa8feab6c6648d65908f72b650739f51f8b895d..80522dbc4acd1ba4642b9e093a5247733810dd92 100644 --- a/services/capability/test/test_capability_views.py +++ b/services/capability/test/test_capability_views.py @@ -1,3 +1,8 @@ +""" +Tests that test the view functionality of our capability REST API. +The logic can be found in `capability/views/capability.py`. +""" + from pyramid.config import Configurator from pyramid.httpexceptions import ( HTTPBadRequest,