diff --git a/src/main/java/org/eclipse/dataplane/Dataplane.java b/src/main/java/org/eclipse/dataplane/Dataplane.java index 6d5b37d..9aab334 100644 --- a/src/main/java/org/eclipse/dataplane/Dataplane.java +++ b/src/main/java/org/eclipse/dataplane/Dataplane.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.dataplane.domain.Result; +import org.eclipse.dataplane.domain.controlplane.ControlPlane; import org.eclipse.dataplane.domain.dataflow.DataFlow; import org.eclipse.dataplane.domain.dataflow.DataFlowPrepareMessage; import org.eclipse.dataplane.domain.dataflow.DataFlowResponseMessage; @@ -26,6 +27,7 @@ import org.eclipse.dataplane.domain.dataflow.DataFlowStatusResponseMessage; import org.eclipse.dataplane.domain.dataflow.DataFlowSuspendMessage; import org.eclipse.dataplane.domain.dataflow.DataFlowTerminateMessage; +import org.eclipse.dataplane.domain.registration.ControlPlaneRegistrationMessage; import org.eclipse.dataplane.domain.registration.DataPlaneRegistrationMessage; import org.eclipse.dataplane.logic.OnCompleted; import org.eclipse.dataplane.logic.OnPrepare; @@ -33,10 +35,13 @@ import org.eclipse.dataplane.logic.OnStarted; import org.eclipse.dataplane.logic.OnSuspend; import org.eclipse.dataplane.logic.OnTerminate; +import org.eclipse.dataplane.port.DataPlaneRegistrationApiController; import org.eclipse.dataplane.port.DataPlaneSignalingApiController; import org.eclipse.dataplane.port.exception.DataFlowNotifyControlPlaneFailed; import org.eclipse.dataplane.port.exception.DataplaneNotRegistered; +import org.eclipse.dataplane.port.store.ControlPlaneStore; import org.eclipse.dataplane.port.store.DataFlowStore; +import org.eclipse.dataplane.port.store.InMemoryControlPlaneStore; import org.eclipse.dataplane.port.store.InMemoryDataFlowStore; import java.net.URI; @@ -53,7 +58,8 @@ public class Dataplane { private final ObjectMapper objectMapper = new ObjectMapper().configure(FAIL_ON_UNKNOWN_PROPERTIES, false); - private final DataFlowStore store = new InMemoryDataFlowStore(objectMapper); + private final DataFlowStore dataFlowStore = new InMemoryDataFlowStore(objectMapper); + private final ControlPlaneStore controlPlaneStore = new InMemoryControlPlaneStore(objectMapper); private String id; private String endpoint; private final Set transferTypes = new HashSet<>(); @@ -76,16 +82,20 @@ public DataPlaneSignalingApiController controller() { return new DataPlaneSignalingApiController(this); } + public DataPlaneRegistrationApiController registrationController() { + return new DataPlaneRegistrationApiController(this); + } + public Result getById(String dataFlowId) { - return store.findById(dataFlowId); + return dataFlowStore.findById(dataFlowId); } public Result save(DataFlow dataFlow) { - return store.save(dataFlow); + return dataFlowStore.save(dataFlow); } public Result status(String dataFlowId) { - return store.findById(dataFlowId) + return dataFlowStore.findById(dataFlowId) .map(f -> new DataFlowStatusResponseMessage(f.getId(), f.getState().name())); } @@ -153,24 +163,24 @@ public Result start(DataFlowStartMessage message) { } public Result suspend(String flowId, DataFlowSuspendMessage message) { - return store.findById(flowId) + return dataFlowStore.findById(flowId) .map(dataFlow -> { dataFlow.transitionToSuspended(message.reason()); return dataFlow; }) .compose(onSuspend::action) - .compose(store::save) + .compose(dataFlowStore::save) .map(it -> null); } public Result terminate(String dataFlowId, DataFlowTerminateMessage message) { - return store.findById(dataFlowId) + return dataFlowStore.findById(dataFlowId) .map(dataFlow -> { dataFlow.transitionToTerminated(message.reason()); return dataFlow; }) .compose(onTerminate::action) - .compose(store::save) + .compose(dataFlowStore::save) .map(it -> null); } @@ -180,7 +190,7 @@ public Result terminate(String dataFlowId, DataFlowTerminateMessage messag * @param dataFlowId the data flow id. */ public Result notifyPrepared(String dataFlowId, OnPrepare onPrepare) { - return store.findById(dataFlowId) + return dataFlowStore.findById(dataFlowId) .compose(onPrepare::action) .compose(dataFlow -> { dataFlow.transitionToPrepared(); @@ -197,7 +207,7 @@ public Result notifyPrepared(String dataFlowId, OnPrepare onPrepare) { * @param dataFlowId the data flow id. */ public Result notifyStarted(String dataFlowId, OnStart onStart) { - return store.findById(dataFlowId) + return dataFlowStore.findById(dataFlowId) .compose(onStart::action) .compose(dataFlow -> { dataFlow.transitionToStarted(); @@ -215,7 +225,7 @@ public Result notifyStarted(String dataFlowId, OnStart onStart) { * @param dataFlowId id of the data flow */ public Result notifyCompleted(String dataFlowId) { - return store.findById(dataFlowId) + return dataFlowStore.findById(dataFlowId) .compose(dataFlow -> { dataFlow.transitionToCompleted(); @@ -230,7 +240,7 @@ public Result notifyCompleted(String dataFlowId) { * @param throwable the error */ public Result notifyErrored(String dataFlowId, Throwable throwable) { - return store.findById(dataFlowId) + return dataFlowStore.findById(dataFlowId) .compose(dataFlow -> { dataFlow.transitionToTerminated(throwable.getMessage()); @@ -239,7 +249,7 @@ public Result notifyErrored(String dataFlowId, Throwable throwable) { } public Result started(String flowId, DataFlowStartedNotificationMessage startedNotificationMessage) { - return store.findById(flowId) + return dataFlowStore.findById(flowId) .map(dataFlow -> { dataFlow.setDataAddress(startedNotificationMessage.dataAddress()); return dataFlow; @@ -258,7 +268,7 @@ public Result started(String flowId, DataFlowStartedNotificationMessage st * @return result indicating whether data flow was completed successfully */ public Result completed(String flowId) { - return store.findById(flowId).compose(onCompleted::action) + return dataFlowStore.findById(flowId).compose(onCompleted::action) .compose(dataFlow -> { dataFlow.transitionToCompleted(); return save(dataFlow); @@ -316,6 +326,23 @@ private Result toJson(Object message) { } } + public ControlPlaneStore controlPlaneStore() { + return controlPlaneStore; + } + + public Result registerControlPlane(ControlPlaneRegistrationMessage message) { + var controlPlane = ControlPlane.newInstance() + .id(message.controlplaneId()) + .endpoint(message.endpoint()) + .build(); + + return controlPlaneStore.save(controlPlane); + } + + public Result deleteControlPlane(String id) { + return controlPlaneStore.delete(id); + } + public static class Builder { private final Dataplane dataplane = new Dataplane(); diff --git a/src/main/java/org/eclipse/dataplane/domain/controlplane/ControlPlane.java b/src/main/java/org/eclipse/dataplane/domain/controlplane/ControlPlane.java new file mode 100644 index 0000000..123d0ef --- /dev/null +++ b/src/main/java/org/eclipse/dataplane/domain/controlplane/ControlPlane.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2026 Think-it GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Think-it GmbH - initial API and implementation + * + */ + +package org.eclipse.dataplane.domain.controlplane; + +import java.util.Objects; + +public class ControlPlane { + + private String id; + private String endpoint; + + public String getId() { + return id; + } + + public String getEndpoint() { + return endpoint; + } + + public static ControlPlane.Builder newInstance() { + return new ControlPlane.Builder(); + } + + public static class Builder { + private final ControlPlane controlPlane = new ControlPlane(); + + private Builder() { + + } + + public ControlPlane build() { + Objects.requireNonNull(controlPlane.id); + + return controlPlane; + } + + public Builder id(String id) { + controlPlane.id = id; + return this; + } + + public Builder endpoint(String endpoint) { + controlPlane.endpoint = endpoint; + return this; + } + } +} diff --git a/src/main/java/org/eclipse/dataplane/domain/registration/ControlPlaneRegistrationMessage.java b/src/main/java/org/eclipse/dataplane/domain/registration/ControlPlaneRegistrationMessage.java new file mode 100644 index 0000000..b4cdc13 --- /dev/null +++ b/src/main/java/org/eclipse/dataplane/domain/registration/ControlPlaneRegistrationMessage.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2026 Think-it GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Think-it GmbH - initial API and implementation + * + */ + +package org.eclipse.dataplane.domain.registration; + +public record ControlPlaneRegistrationMessage( + String controlplaneId, + String endpoint +// TODO: authorization +) { +} diff --git a/src/main/java/org/eclipse/dataplane/port/DataPlaneRegistrationApiController.java b/src/main/java/org/eclipse/dataplane/port/DataPlaneRegistrationApiController.java new file mode 100644 index 0000000..c1bead0 --- /dev/null +++ b/src/main/java/org/eclipse/dataplane/port/DataPlaneRegistrationApiController.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 Think-it GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Think-it GmbH - initial API and implementation + * + */ + +package org.eclipse.dataplane.port; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Response; +import org.eclipse.dataplane.Dataplane; +import org.eclipse.dataplane.domain.registration.ControlPlaneRegistrationMessage; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; + +@Path("/v1/controlplanes") +@Consumes(APPLICATION_JSON) +@Produces(APPLICATION_JSON) +public class DataPlaneRegistrationApiController { + + private final Dataplane dataplane; + + public DataPlaneRegistrationApiController(Dataplane dataplane) { + this.dataplane = dataplane; + } + + @PUT + @Path("/") + public Response register(ControlPlaneRegistrationMessage message) { + dataplane.registerControlPlane(message).orElseThrow(ExceptionMapper.MAP_TO_WSRS); + return Response.ok().build(); + } + + @DELETE + @Path("/{id}") + public Response delete(@PathParam("id") String id) { + dataplane.deleteControlPlane(id).orElseThrow(ExceptionMapper.MAP_TO_WSRS); + return Response.noContent().build(); + } + +} diff --git a/src/main/java/org/eclipse/dataplane/port/DataPlaneSignalingApiController.java b/src/main/java/org/eclipse/dataplane/port/DataPlaneSignalingApiController.java index c964f1f..b30d98d 100644 --- a/src/main/java/org/eclipse/dataplane/port/DataPlaneSignalingApiController.java +++ b/src/main/java/org/eclipse/dataplane/port/DataPlaneSignalingApiController.java @@ -16,12 +16,10 @@ import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; -import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Response; import org.eclipse.dataplane.Dataplane; import org.eclipse.dataplane.domain.dataflow.DataFlow; @@ -31,7 +29,6 @@ import org.eclipse.dataplane.domain.dataflow.DataFlowStatusResponseMessage; import org.eclipse.dataplane.domain.dataflow.DataFlowSuspendMessage; import org.eclipse.dataplane.domain.dataflow.DataFlowTerminateMessage; -import org.eclipse.dataplane.port.exception.DataFlowNotFoundException; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static jakarta.ws.rs.core.MediaType.WILDCARD; @@ -50,7 +47,7 @@ public DataPlaneSignalingApiController(Dataplane dataplane) { @POST @Path("/prepare") public Response prepare(DataFlowPrepareMessage message) { - var response = dataplane.prepare(message).orElseThrow(this::mapToWsRsException); + var response = dataplane.prepare(message).orElseThrow(ExceptionMapper.MAP_TO_WSRS); if (response.state().equals(DataFlow.State.PREPARING.name())) { return Response.accepted(response).build(); } @@ -60,7 +57,7 @@ public Response prepare(DataFlowPrepareMessage message) { @POST @Path("/start") public Response start(DataFlowStartMessage message) { - var response = dataplane.start(message).orElseThrow(this::mapToWsRsException); + var response = dataplane.start(message).orElseThrow(ExceptionMapper.MAP_TO_WSRS); if (response.state().equals(DataFlow.State.STARTING.name())) { return Response.accepted(response).build(); } @@ -70,21 +67,21 @@ public Response start(DataFlowStartMessage message) { @POST @Path("/{flowId}/suspend") public Response suspend(@PathParam("flowId") String flowId, DataFlowSuspendMessage message) { - dataplane.suspend(flowId, message).orElseThrow(this::mapToWsRsException); + dataplane.suspend(flowId, message).orElseThrow(ExceptionMapper.MAP_TO_WSRS); return Response.ok().build(); } @POST @Path("/{flowId}/terminate") public Response terminate(@PathParam("flowId") String flowId, DataFlowTerminateMessage message) { - dataplane.terminate(flowId, message).orElseThrow(this::mapToWsRsException); + dataplane.terminate(flowId, message).orElseThrow(ExceptionMapper.MAP_TO_WSRS); return Response.ok().build(); } @POST @Path("/{flowId}/started") public Response started(@PathParam("flowId") String flowId, DataFlowStartedNotificationMessage startedNotificationMessage) { - dataplane.started(flowId, startedNotificationMessage).orElseThrow(this::mapToWsRsException); + dataplane.started(flowId, startedNotificationMessage).orElseThrow(ExceptionMapper.MAP_TO_WSRS); return Response.ok().build(); } @@ -92,21 +89,14 @@ public Response started(@PathParam("flowId") String flowId, DataFlowStartedNotif @Path("/{flowId}/completed") @Consumes(WILDCARD) public Response completed(@PathParam("flowId") String flowId) { - dataplane.completed(flowId).orElseThrow(this::mapToWsRsException); + dataplane.completed(flowId).orElseThrow(ExceptionMapper.MAP_TO_WSRS); return Response.ok().build(); } @GET @Path("/{flowId}/status") public DataFlowStatusResponseMessage status(@PathParam("flowId") String flowId) { - return dataplane.status(flowId).orElseThrow(this::mapToWsRsException); - } - - private WebApplicationException mapToWsRsException(Exception exception) { - if (exception instanceof DataFlowNotFoundException notFound) { - return new NotFoundException(notFound); - } - return new WebApplicationException("unexpected internal server error"); + return dataplane.status(flowId).orElseThrow(ExceptionMapper.MAP_TO_WSRS); } } diff --git a/src/main/java/org/eclipse/dataplane/port/ExceptionMapper.java b/src/main/java/org/eclipse/dataplane/port/ExceptionMapper.java new file mode 100644 index 0000000..e349083 --- /dev/null +++ b/src/main/java/org/eclipse/dataplane/port/ExceptionMapper.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026 Think-it GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Think-it GmbH - initial API and implementation + * + */ + +package org.eclipse.dataplane.port; + +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.WebApplicationException; +import org.eclipse.dataplane.port.exception.ResourceNotFoundException; + +import java.util.function.Function; + +public interface ExceptionMapper { + + Function MAP_TO_WSRS = exception -> { + if (exception instanceof ResourceNotFoundException notFound) { + return new NotFoundException(notFound); + } + return new WebApplicationException("unexpected internal server error"); + }; + +} diff --git a/src/main/java/org/eclipse/dataplane/port/exception/DataFlowNotFoundException.java b/src/main/java/org/eclipse/dataplane/port/exception/ResourceNotFoundException.java similarity index 79% rename from src/main/java/org/eclipse/dataplane/port/exception/DataFlowNotFoundException.java rename to src/main/java/org/eclipse/dataplane/port/exception/ResourceNotFoundException.java index eb989c7..de9c6b0 100644 --- a/src/main/java/org/eclipse/dataplane/port/exception/DataFlowNotFoundException.java +++ b/src/main/java/org/eclipse/dataplane/port/exception/ResourceNotFoundException.java @@ -14,8 +14,8 @@ package org.eclipse.dataplane.port.exception; -public class DataFlowNotFoundException extends Exception { - public DataFlowNotFoundException(String message) { +public class ResourceNotFoundException extends Exception { + public ResourceNotFoundException(String message) { super(message); } } diff --git a/src/main/java/org/eclipse/dataplane/port/store/ControlPlaneStore.java b/src/main/java/org/eclipse/dataplane/port/store/ControlPlaneStore.java new file mode 100644 index 0000000..c127690 --- /dev/null +++ b/src/main/java/org/eclipse/dataplane/port/store/ControlPlaneStore.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025 Think-it GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Think-it GmbH - initial API and implementation + * + */ + +package org.eclipse.dataplane.port.store; + +import org.eclipse.dataplane.domain.Result; +import org.eclipse.dataplane.domain.controlplane.ControlPlane; + +public interface ControlPlaneStore { + Result save(ControlPlane controlPlane); + + Result findById(String controlplaneId); + + Result delete(String id); +} diff --git a/src/main/java/org/eclipse/dataplane/port/store/InMemoryControlPlaneStore.java b/src/main/java/org/eclipse/dataplane/port/store/InMemoryControlPlaneStore.java new file mode 100644 index 0000000..3133f78 --- /dev/null +++ b/src/main/java/org/eclipse/dataplane/port/store/InMemoryControlPlaneStore.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2026 Think-it GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Think-it GmbH - initial API and implementation + * + */ + +package org.eclipse.dataplane.port.store; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.dataplane.domain.Result; +import org.eclipse.dataplane.domain.controlplane.ControlPlane; +import org.eclipse.dataplane.port.exception.ResourceNotFoundException; + +import java.util.HashMap; +import java.util.Map; + +public class InMemoryControlPlaneStore implements ControlPlaneStore { + + private final ObjectMapper objectMapper; + private final Map store = new HashMap<>(); + + public InMemoryControlPlaneStore(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public Result save(ControlPlane controlPlane) { + try { + store.put(controlPlane.getId(), objectMapper.writeValueAsString(controlPlane)); + return Result.success(); + } catch (JsonProcessingException e) { + return Result.failure(e); + } + } + + @Override + public Result findById(String controlplaneId) { + var dataFlow = store.get(controlplaneId); + if (dataFlow == null) { + return Result.failure(new ResourceNotFoundException("ControlPlane %s not found".formatted(controlplaneId))); + } + + try { + var deserialized = objectMapper.readValue(dataFlow, ControlPlane.class); + return Result.success(deserialized); + } catch (JsonProcessingException e) { + return Result.failure(e); + } + } + + @Override + public Result delete(String id) { + var remove = store.remove(id); + if (remove == null) { + return Result.failure(new ResourceNotFoundException("ControlPlane %s not found".formatted(id))); + } + return Result.success(); + } +} diff --git a/src/main/java/org/eclipse/dataplane/port/store/InMemoryDataFlowStore.java b/src/main/java/org/eclipse/dataplane/port/store/InMemoryDataFlowStore.java index 314cd4f..228377f 100644 --- a/src/main/java/org/eclipse/dataplane/port/store/InMemoryDataFlowStore.java +++ b/src/main/java/org/eclipse/dataplane/port/store/InMemoryDataFlowStore.java @@ -18,7 +18,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.dataplane.domain.Result; import org.eclipse.dataplane.domain.dataflow.DataFlow; -import org.eclipse.dataplane.port.exception.DataFlowNotFoundException; +import org.eclipse.dataplane.port.exception.ResourceNotFoundException; import java.util.HashMap; import java.util.Map; @@ -46,7 +46,7 @@ public Result save(DataFlow dataFlow) { public Result findById(String flowId) { var dataFlow = store.get(flowId); if (dataFlow == null) { - return Result.failure(new DataFlowNotFoundException("DataFlow %s not found".formatted(flowId))); + return Result.failure(new ResourceNotFoundException("DataFlow %s not found".formatted(flowId))); } try { diff --git a/src/test/java/org/eclipse/dataplane/DataplaneTest.java b/src/test/java/org/eclipse/dataplane/DataplaneTest.java index 800da36..6f79596 100644 --- a/src/test/java/org/eclipse/dataplane/DataplaneTest.java +++ b/src/test/java/org/eclipse/dataplane/DataplaneTest.java @@ -17,9 +17,9 @@ import com.github.tomakehurst.wiremock.WireMockServer; import org.eclipse.dataplane.domain.Result; import org.eclipse.dataplane.domain.dataflow.DataFlowPrepareMessage; -import org.eclipse.dataplane.port.exception.DataFlowNotFoundException; import org.eclipse.dataplane.port.exception.DataFlowNotifyControlPlaneFailed; import org.eclipse.dataplane.port.exception.DataplaneNotRegistered; +import org.eclipse.dataplane.port.exception.ResourceNotFoundException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -67,7 +67,7 @@ void shouldFail_whenDataFlowDoesNotExist() { var result = dataplane.notifyCompleted("dataFlowId"); assertThat(result.failed()).isTrue(); - assertThatThrownBy(result::orElseThrow).isExactlyInstanceOf(DataFlowNotFoundException.class); + assertThatThrownBy(result::orElseThrow).isExactlyInstanceOf(ResourceNotFoundException.class); } @Test diff --git a/src/test/java/org/eclipse/dataplane/scenario/ControlPlaneRegistrationTest.java b/src/test/java/org/eclipse/dataplane/scenario/ControlPlaneRegistrationTest.java new file mode 100644 index 0000000..91fcf5d --- /dev/null +++ b/src/test/java/org/eclipse/dataplane/scenario/ControlPlaneRegistrationTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2026 Think-it GmbH + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Think-it GmbH - initial API and implementation + * + */ + +package org.eclipse.dataplane.scenario; + +import io.restassured.http.ContentType; +import org.eclipse.dataplane.Dataplane; +import org.eclipse.dataplane.HttpServer; +import org.eclipse.dataplane.domain.registration.ControlPlaneRegistrationMessage; +import org.eclipse.dataplane.port.exception.ResourceNotFoundException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; + +class ControlPlaneRegistrationTest { + + private final HttpServer httpServer = new HttpServer(21361); + private final Dataplane sdk = Dataplane.newInstance() + .id("consumer") + .build(); + + @BeforeEach + void setUp() { + httpServer.start(); + + httpServer.deploy("/runtime/data-plane", sdk.registrationController()); + } + + @AfterEach + void tearDown() { + httpServer.stop(); + } + + @Nested + class Register { + @Test + void shouldRegisterControlPlane() { + var controlPlaneId = UUID.randomUUID().toString(); + var controlPlaneRegistrationMessage = new ControlPlaneRegistrationMessage(controlPlaneId, "http://something"); + + given() + .contentType(ContentType.JSON) + .basePath("/runtime/data-plane") + .port(httpServer.port()) + .body(controlPlaneRegistrationMessage) + .put("/v1/controlplanes") + .then() + .log().ifValidationFails() + .statusCode(200); + + var result = sdk.controlPlaneStore().findById(controlPlaneId); + + assertThat(result.succeeded()); + assertThat(result.getContent().getEndpoint()).isEqualTo("http://something"); + } + + @Test + void shouldReplaceControlPlane_whenSecondCall() { + var controlPlaneId = UUID.randomUUID().toString(); + var controlPlaneRegistrationMessage = new ControlPlaneRegistrationMessage(controlPlaneId, "http://something"); + + given() + .contentType(ContentType.JSON) + .basePath("/runtime/data-plane") + .port(httpServer.port()) + .body(controlPlaneRegistrationMessage) + .put("/v1/controlplanes") + .then() + .log().ifValidationFails() + .statusCode(200); + + var updateControlPlane = new ControlPlaneRegistrationMessage(controlPlaneId, "http://new-endpoint"); + + given() + .contentType(ContentType.JSON) + .basePath("/runtime/data-plane") + .port(httpServer.port()) + .body(updateControlPlane) + .put("/v1/controlplanes") + .then() + .log().ifValidationFails() + .statusCode(200); + + var result = sdk.controlPlaneStore().findById(controlPlaneId); + + assertThat(result.succeeded()); + assertThat(result.getContent().getEndpoint()).isEqualTo("http://new-endpoint"); + } + } + + @Nested + class Delete { + @Test + void shouldDeleteControlPlane() { + var controlPlaneId = UUID.randomUUID().toString(); + var controlPlaneRegistrationMessage = new ControlPlaneRegistrationMessage(controlPlaneId, "http://something"); + + given() + .contentType(ContentType.JSON) + .basePath("/runtime/data-plane") + .port(httpServer.port()) + .body(controlPlaneRegistrationMessage) + .put("/v1/controlplanes") + .then() + .log().ifValidationFails() + .statusCode(200); + + given() + .contentType(ContentType.JSON) + .basePath("/runtime/data-plane") + .port(httpServer.port()) + .delete("/v1/controlplanes/" + controlPlaneId) + .then() + .log().ifValidationFails() + .statusCode(204); + + var result = sdk.controlPlaneStore().findById(controlPlaneId); + + assertThat(result.failed()); + assertThat(result.getException()).isInstanceOf(ResourceNotFoundException.class); + } + + @Test + void shouldReturn404_whenControlPlaneDoesNotExist() { + var controlPlaneId = UUID.randomUUID().toString(); + + given() + .contentType(ContentType.JSON) + .basePath("/runtime/data-plane") + .port(httpServer.port()) + .delete("/v1/controlplanes/" + controlPlaneId) + .then() + .log().ifValidationFails() + .statusCode(404); + } + } + +}