Skip to content

Commit

Permalink
Issue #78 Implementing create_service - POST /services/
Browse files Browse the repository at this point in the history
  • Loading branch information
JohanKJSchreurs committed Nov 4, 2022
1 parent 88b7e6c commit 203ca21
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 17 deletions.
34 changes: 33 additions & 1 deletion src/openeo_aggregator/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -781,9 +781,11 @@ class AggregatorSecondaryServices(SecondaryServices):
def __init__(
self,
backends: MultiBackendConnection,
processing: AggregatorProcessing
):
super(AggregatorSecondaryServices, self).__init__()
self._backends = backends
self._processing = processing

def service_types(self) -> dict:
"""https://openeo.org/documentation/1.0/developers/api/reference.html#operation/list-service-types"""
Expand Down Expand Up @@ -848,6 +850,31 @@ def service_info(self, user_id: str, service_id: str) -> ServiceMetadata:

raise ServiceNotFoundException(service_id)

def create_service(self, user_id: str, process_graph: dict, service_type: str, api_version: str,
configuration: dict) -> str:
"""
https://openeo.org/documentation/1.0/developers/api/reference.html#operation/create-service
:return: (location, openeo_identifier)
"""

backend_id = self._processing.get_backend_for_process_graph(
process_graph=process_graph, api_version=api_version
)
process_graph = self._processing.preprocess_process_graph(process_graph, backend_id=backend_id)

con = self._backends.get_connection(backend_id)
try:
service = con.create_service(graph=process_graph, type=service_type)
except OpenEoApiError as e:
for exc_class in [ProcessGraphMissingException, ProcessGraphInvalidException]:
if e.code == exc_class.code:
raise exc_class
raise OpenEOApiException(f"Failed to create secondary service on backend {backend_id!r}: {e!r}")
except (OpenEoRestError, OpenEoClientException) as e:
raise OpenEOApiException(f"Failed to create secondary service on backend {backend_id!r}: {e!r}")

return service.service_id


class AggregatorBackendImplementation(OpenEoBackendImplementation):
# No basic auth: OIDC auth is required (to get EGI Check-in eduperson_entitlement data)
Expand Down Expand Up @@ -875,7 +902,7 @@ def __init__(self, backends: MultiBackendConnection, config: AggregatorConfig):
partitioned_job_tracker=partitioned_job_tracker
)

secondary_services = AggregatorSecondaryServices(backends=backends)
secondary_services = AggregatorSecondaryServices(backends=backends, processing=processing)

super().__init__(
catalog=catalog,
Expand Down Expand Up @@ -1042,3 +1069,8 @@ def list_services(self, user_id: str) -> List[ServiceMetadata]:

def service_info(self, user_id: str, service_id: str) -> ServiceMetadata:
return self.secondary_services.service_info(user_id=user_id, service_id=service_id)

def create_service(self, user_id: str, process_graph: dict, service_type: str, api_version: str,
configuration: dict) -> Tuple[str, str]:
return self.secondary_services.create_service(user_id=user_id, process_graph=process_graph,
service_type=service_type, api_version=api_version, configuration=configuration)
9 changes: 9 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,19 @@ def backend_implementation(flask_app) -> AggregatorBackendImplementation:
return flask_app.config["OPENEO_BACKEND_IMPLEMENTATION"]


def get_api040(flask_app: flask.Flask) -> ApiTester:
return ApiTester(api_version="0.4.0", client=flask_app.test_client())


def get_api100(flask_app: flask.Flask) -> ApiTester:
return ApiTester(api_version="1.0.0", client=flask_app.test_client())


@pytest.fixture
def api040(flask_app: flask.Flask) -> ApiTester:
return get_api040(flask_app)


@pytest.fixture
def api100(flask_app: flask.Flask) -> ApiTester:
return get_api100(flask_app)
Expand Down
65 changes: 49 additions & 16 deletions tests/test_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
from .conftest import DEFAULT_MEMOIZER_CONFIG


TEST_USER = "Mr.Test"


class TestAggregatorBackendImplementation:

def test_oidc_providers(self, multi_backend_connection, config, backend1, backend2, requests_mock):
Expand Down Expand Up @@ -185,6 +188,7 @@ def test_service_types_merging(self, multi_backend_connection, config, backend1,
expected.update(service_2)
assert service_types == expected

# TODO: eliminate TEST_SERVICES (too long) when I find a better way to set up the test.
TEST_SERVICES = {
"services": [{
"id": "wms-a3cca9",
Expand Down Expand Up @@ -521,14 +525,15 @@ def test_service_types_merging(self, multi_backend_connection, config, backend1,
}

def test_list_services_simple(self, multi_backend_connection, config, backend1, backend2, requests_mock):
"""Given 2 backends where only 1 has 1 service and the other is empty, it lists that 1 service."""

services1 = self.TEST_SERVICES
services2 = {}
requests_mock.get(backend1 + "/services", json=services1)
requests_mock.get(backend2 + "/services", json=services2)
implementation = AggregatorBackendImplementation(backends=multi_backend_connection, config=config)

test_user_id = "fakeuser"
actual_services = implementation.list_services(user_id=test_user_id)
actual_services = implementation.list_services(user_id=TEST_USER)

# Construct expected result. We have get just data from the service in services1
# (there is only one) for conversion to a ServiceMetadata.
Expand All @@ -539,6 +544,8 @@ def test_list_services_simple(self, multi_backend_connection, config, backend1,
assert actual_services == expected_services

def test_list_services_merged(self, multi_backend_connection, config, backend1, backend2, requests_mock):
"""Given 2 backends with each 1 service, it lists both services."""

services1 = self.TEST_SERVICES
serv_metadata_wmts_foo = ServiceMetadata(
id="wmts-foo",
Expand All @@ -556,19 +563,19 @@ def test_list_services_merged(self, multi_backend_connection, config, backend1,
requests_mock.get(backend2 + "/services", json=services2)
implementation = AggregatorBackendImplementation(backends=multi_backend_connection, config=config)

test_user_id = "fakeuser"
actual_services = implementation.list_services(user_id=test_user_id)
actual_services = implementation.list_services(user_id=TEST_USER)

# Construct expected result. We have get just data from the service in
# services1 (there is only one) for conversion to a ServiceMetadata.
# TODO: do we need to take care of the links part in the JSON as well?
service1 = services1["services"][0]
service1_md = ServiceMetadata.from_dict(service1)
expected_services = [service1_md, serv_metadata_wmts_foo]

assert sorted(actual_services) == sorted(expected_services)

def test_list_services_merged_multiple(self, multi_backend_connection, config, backend1, backend2, requests_mock):
"""Given multiple services in 2 backends, it lists all services from all backends."""

services1 = self.TEST_SERVICES
serv_metadata_wmts_foo = ServiceMetadata(
id="wmts-foo",
Expand Down Expand Up @@ -604,8 +611,7 @@ def test_list_services_merged_multiple(self, multi_backend_connection, config, b
backends=multi_backend_connection, config=config
)

test_user_id = "fakeuser"
actual_services = implementation.list_services(user_id=test_user_id)
actual_services = implementation.list_services(user_id=TEST_USER)

# Construct expected result. We have get just data from the service in
# services1 (there is only one) for conversion to a ServiceMetadata.
Expand All @@ -619,6 +625,8 @@ def test_list_services_merged_multiple(self, multi_backend_connection, config, b
assert sorted(actual_services) == sorted(expected_services)

def test_service_info(self, multi_backend_connection, config, backend1, backend2, requests_mock):
"""When it gets a correct service ID, it returns the expected ServiceMetadata."""

service1 = ServiceMetadata(
id="wmts-foo",
process={"process_graph": {"foo": {"process_id": "foo", "arguments": {}}}},
Expand All @@ -643,20 +651,19 @@ def test_service_info(self, multi_backend_connection, config, backend1, backend2
)
requests_mock.get(backend1 + "/services/wmts-foo", json=service1.prepare_for_json())
requests_mock.get(backend2 + "/services/wms-bar", json=service2.prepare_for_json())

implementation = AggregatorBackendImplementation(
backends=multi_backend_connection, config=config
)

test_user_id = "fakeuser"
actual_service1 = implementation.service_info(user_id=test_user_id, service_id="wmts-foo")
actual_service1 = implementation.service_info(user_id=TEST_USER, service_id="wmts-foo")
assert actual_service1 == service1

actual_service2 = implementation.service_info(user_id=test_user_id,
service_id="wms-bar")
actual_service2 = implementation.service_info(user_id=TEST_USER, service_id="wms-bar")
assert actual_service2 == service2

def test_service_info_not_found(self, multi_backend_connection, config, backend1, backend2, requests_mock):
def test_service_info_wrong_id(self, multi_backend_connection, config, backend1, backend2, requests_mock):
"""When it gets a non-existent service ID, it raises a ServiceNotFoundException."""

service1 = ServiceMetadata(
id="wmts-foo",
process={"process_graph": {"foo": {"process_id": "foo", "arguments": {}}}},
Expand All @@ -681,14 +688,40 @@ def test_service_info_not_found(self, multi_backend_connection, config, backend1
)
requests_mock.get(backend1 + "/services/wmts-foo", json=service1.prepare_for_json())
requests_mock.get(backend2 + "/services/wms-bar", json=service2.prepare_for_json())

implementation = AggregatorBackendImplementation(
backends=multi_backend_connection, config=config
)

test_user_id = "fakeuser"
with pytest.raises(ServiceNotFoundException):
actual_service1 = implementation.service_info(user_id=test_user_id, service_id="doesnotexist")
_ = implementation.service_info(user_id=TEST_USER, service_id="doesnotexist")

@pytest.mark.parametrize("api_version", ["0.4.0", "1.0.0", "1.1.0"])
def test_create_service(self, multi_backend_connection, config, backend1, requests_mock, api_version):
"""When it gets a correct params for a new service, it succesfully create it."""

# Set up responses for creating the service in backend 1
expected_openeo_id = "wmts-foo"
expected_location = backend1 + "/services/wmts-foo"
process_graph = {"foo": {"process_id": "foo", "arguments": {}}}
requests_mock.post(
backend1 + "/services",
headers={
"OpenEO-Identifier": expected_openeo_id,
"Location": expected_location
},
status_code=201)

implementation = AggregatorBackendImplementation(backends=multi_backend_connection, config=config)

actual_openeo_id = implementation.create_service(
user_id=TEST_USER,
process_graph=process_graph,
service_type="WMTS",
api_version=api_version,
configuration={}
)

assert actual_openeo_id == expected_openeo_id


class TestInternalCollectionMetadata:
Expand Down
64 changes: 64 additions & 0 deletions tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,70 @@ def post_jobs(request: requests.Request, context):
}}}
]

class TestSecondaryServices:

# TODO: add view tests for list service types, list_services, servicfe_info

def test_create_wmts_040(self, api040, requests_mock, backend1):
api040.set_auth_bearer_token(TEST_USER_BEARER_TOKEN)

expected_openeo_id = 'c63d6c27-c4c2-4160-b7bd-9e32f582daec'
expected_location = "/openeo/0.4.0/services/" + expected_openeo_id

process_graph = {"foo": {"process_id": "foo", "arguments": {}}}
# The process_graph/process format is slightly different between api v0.4 and v1.0
post_data = {
"type": 'WMTS',
"process_graph": process_graph,
"custom_param": 45,
"title": "My Service",
"description": "Service description"
}

requests_mock.post(
backend1 + "/services",
headers={
"OpenEO-Identifier": expected_openeo_id,
"Location": expected_location
},
status_code=201)

resp = api040.post('/services', json=post_data).assert_status_code(201)
assert resp.headers['OpenEO-Identifier'] == expected_openeo_id
assert resp.headers['Location'] == expected_location

def test_create_wmts_100(self, api100, requests_mock, backend1):
api100.set_auth_bearer_token(TEST_USER_BEARER_TOKEN)

# used both to set up data and to validate at the end
expected_openeo_id = 'c63d6c27-c4c2-4160-b7bd-9e32f582daec'
expected_location = "/openeo/1.0.0/services/" + expected_openeo_id

process_graph = {"foo": {"process_id": "foo", "arguments": {}}}
# The process_graph/process format is slightly different between api v0.4 and v1.0
post_data = {
"type": 'WMTS',
"process": {
"process_graph": process_graph,
"id": "filter_temporal_wmts"
},
"custom_param": 45,
"title": "My Service",
"description": "Service description"
}
requests_mock.post(
backend1 + "/services",
headers={
"OpenEO-Identifier": expected_openeo_id,
"Location": expected_location
},
status_code=201)

resp = api100.post('/services', json=post_data).assert_status_code(201)

assert resp.headers['OpenEO-Identifier'] == 'c63d6c27-c4c2-4160-b7bd-9e32f582daec'
assert resp.headers['Location'] == expected_location


class TestResilience:

Expand Down

0 comments on commit 203ca21

Please sign in to comment.