Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: data-plane self unregistration #4249

Merged
merged 1 commit into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private boolean availability(DataPlaneInstance instance) {
} else {
instance.transitionToUnavailable();
}
store.save(instance);
update(instance);
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.eclipse.edc.connector.dataplane.selector.spi.store.DataPlaneInstanceStore;
import org.eclipse.edc.connector.dataplane.selector.spi.strategy.SelectionStrategyRegistry;
import org.eclipse.edc.spi.result.ServiceResult;
import org.eclipse.edc.spi.result.StoreResult;
import org.eclipse.edc.spi.types.domain.DataAddress;
import org.eclipse.edc.transaction.spi.TransactionContext;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -83,6 +84,20 @@ public ServiceResult<Void> delete(String instanceId) {
return transactionContext.execute(() -> ServiceResult.from(store.deleteById(instanceId))).mapEmpty();
}

@Override
public ServiceResult<Void> unregister(String instanceId) {
return transactionContext.execute(() -> {
StoreResult<Void> operation = store.findByIdAndLease(instanceId)
.map(it -> {
it.transitionToUnregistered();
store.save(it);
return null;
});

return ServiceResult.from(operation);
});
}

@Override
public ServiceResult<DataPlaneInstance> findById(String id) {
return transactionContext.execute(() -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@
import static org.eclipse.edc.connector.dataplane.selector.spi.instance.DataPlaneInstanceStates.AVAILABLE;
import static org.eclipse.edc.connector.dataplane.selector.spi.instance.DataPlaneInstanceStates.REGISTERED;
import static org.eclipse.edc.connector.dataplane.selector.spi.instance.DataPlaneInstanceStates.UNAVAILABLE;
import static org.eclipse.edc.connector.dataplane.selector.spi.instance.DataPlaneInstanceStates.UNREGISTERED;
import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;
import static org.eclipse.edc.spi.result.ServiceFailure.Reason.BAD_REQUEST;
import static org.eclipse.edc.spi.result.ServiceFailure.Reason.CONFLICT;
import static org.eclipse.edc.spi.result.ServiceFailure.Reason.NOT_FOUND;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

Expand Down Expand Up @@ -170,6 +173,30 @@ void shouldSaveRegisteredInstance() {
}
}

@Nested
class Unregister {
@Test
void shouldUnregisterInstance() {
var instance = DataPlaneInstance.Builder.newInstance().url("http://any").build();
when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(instance));

var result = service.unregister(UUID.randomUUID().toString());

assertThat(result).isSucceeded();
verify(store).save(argThat(it -> it.getState() == UNREGISTERED.code()));
}

@Test
void shouldFail_whenLeaseFails() {
when(store.findByIdAndLease(any())).thenReturn(StoreResult.alreadyLeased("already leased"));

var result = service.unregister(UUID.randomUUID().toString());

assertThat(result).isFailed().extracting(ServiceFailure::getReason).isEqualTo(CONFLICT);
verify(store, never()).save(any());
}
}

private DataPlaneInstance.Builder createInstanceBuilder(String id) {
return DataPlaneInstance.Builder.newInstance()
.id(id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
* Base utility class that permits to test Rest controllers deploying a bare bone instance of Jetty
* with Jersey. The controller returned by the {@link #controller()} method gets registered on a test api context.
*/
public abstract class RestControllerTestBase {
public abstract class RestControllerTestBase { // TODO: can it be started once for class?

protected final int port = getFreePort();
protected final Monitor monitor = mock(Monitor.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

import static jakarta.json.Json.createObjectBuilder;
import static java.lang.String.format;
import static okhttp3.internal.Util.EMPTY_REQUEST;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VOCAB;
Expand Down Expand Up @@ -125,6 +126,13 @@ public ServiceResult<Void> delete(String instanceId) {
return request(requestBuilder).mapEmpty();
}

@Override
public ServiceResult<Void> unregister(String instanceId) {
var requestBuilder = new Request.Builder().put(EMPTY_REQUEST).url("%s/%s/unregister".formatted(url, instanceId));

return request(requestBuilder).mapEmpty();
}

@Override
public ServiceResult<DataPlaneInstance> findById(String id) {
var requestBuilder = new Request.Builder().get().url(url + "/" + id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

import static org.eclipse.edc.http.client.testfixtures.HttpTestUtils.testHttpClient;
import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;
import static org.eclipse.edc.spi.result.ServiceFailure.Reason.CONFLICT;
import static org.eclipse.edc.spi.result.ServiceFailure.Reason.NOT_FOUND;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
Expand Down Expand Up @@ -104,6 +105,30 @@ void select() {
verify(authenticationProvider).authenticationHeaders();
}

@Nested
class Unregister {
@Test
void shouldUnregister() {
var instanceId = UUID.randomUUID().toString();
when(serverService.unregister(any())).thenReturn(ServiceResult.success());

var result = service.unregister(instanceId);

assertThat(result).isSucceeded();
verify(serverService).unregister(instanceId);
}

@Test
void shouldFail_whenServiceFails() {
var instanceId = UUID.randomUUID().toString();
when(serverService.unregister(any())).thenReturn(ServiceResult.conflict("conflict"));

var result = service.unregister(instanceId);

assertThat(result).isFailed().extracting(ServiceFailure::getReason).isEqualTo(CONFLICT);
}
}

@Nested
class Delete {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,34 @@ public interface DataplaneSelectorControlApi {
})
JsonObject registerDataplane(JsonObject request);

@Operation(method = HttpMethod.DELETE,
@Operation(method = HttpMethod.POST,
description = "Unregister existing Dataplane",
responses = {
@ApiResponse(responseCode = "204", description = "Dataplane successfully unregistered"),
@ApiResponse(responseCode = "400", description = "Request body was malformed",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class)))),
@ApiResponse(responseCode = "404", description = "Resource not found",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class)))),
@ApiResponse(responseCode = "409", description = "Conflict",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class))))
}
)
void unregisterDataplane(String id);

@Operation(method = HttpMethod.DELETE,
description = "Delete existing Dataplane",
responses = {
@ApiResponse(responseCode = "204", description = "Dataplane successfully deleted"),
@ApiResponse(responseCode = "400", description = "Request body was malformed",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class)))),
@ApiResponse(responseCode = "404", description = "Resource not found",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class)))),
@ApiResponse(responseCode = "409", description = "Conflict",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class))))
}
)
void deleteDataplane(String id);

@Operation(method = "POST",
description = "Finds the best fitting data plane instance for a particular query",
requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = SelectionRequestSchema.class))),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
Expand Down Expand Up @@ -79,10 +80,17 @@ public JsonObject registerDataplane(JsonObject request) {
.orElseThrow(f -> new EdcException(f.getFailureDetail()));
}

@PUT
@Path("/{id}/unregister")
@Override
public void unregisterDataplane(@PathParam("id") String id) {
service.unregister(id).orElseThrow(exceptionMapper(DataPlaneInstance.class));
}

@DELETE
@Path("/{id}")
public void unregisterDataplane(@PathParam("id") String id) {
@Override
public void deleteDataplane(@PathParam("id") String id) {
service.delete(id).orElseThrow(exceptionMapper(DataPlaneInstance.class));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,36 @@ void shouldFail_whenEgressTransformationFails() {
@Nested
class Unregister {

@Test
void shouldUnregisterInstance() {
when(service.unregister(any())).thenReturn(ServiceResult.success());
var instanceId = UUID.randomUUID().toString();

given()
.port(port)
.put("/v1/dataplanes/{id}/unregister", instanceId)
.then()
.statusCode(204);

verify(service).unregister(instanceId);
}

@Test
void shouldReturnNotFound_whenServiceReturnsNotFound() {
when(service.unregister(any())).thenReturn(ServiceResult.notFound("not found"));
var instanceId = UUID.randomUUID().toString();

given()
.port(port)
.put("/v1/dataplanes/{id}/unregister", instanceId)
.then()
.statusCode(404);
}
}

@Nested
class Delete {

@Test
void shouldDeleteInstance() {
when(service.delete(any())).thenReturn(ServiceResult.success());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public class DataplaneSelfRegistrationExtension implements ServiceExtension {
private PublicEndpointGeneratorService publicEndpointGeneratorService;
@Inject
private HealthCheckService healthCheckService;

private ServiceExtensionContext context;

@Override
Expand Down Expand Up @@ -86,7 +87,6 @@ public void start() {
.build();


// register the data plane
var monitor = context.getMonitor().withPrefix("DataPlaneHealthCheck");
var check = new DataPlaneHealthCheck();
healthCheckService.addReadinessProvider(check);
Expand All @@ -105,7 +105,7 @@ public void start() {

@Override
public void shutdown() {
dataPlaneSelectorService.delete(context.getRuntimeId())
dataPlaneSelectorService.unregister(context.getRuntimeId())
.onSuccess(it -> context.getMonitor().info("data plane successfully unregistered"))
.onFailure(failure -> context.getMonitor().severe("error during data plane de-registration. %s: %s"
.formatted(failure.getReason(), failure.getFailureDetail())));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,11 @@ void shouldNotStart_whenRegistrationFails(DataplaneSelfRegistrationExtension ext
@Test
void shouldUnregisterInstanceAtShutdown(DataplaneSelfRegistrationExtension extension, ServiceExtensionContext context) {
when(context.getRuntimeId()).thenReturn("runtimeId");
when(dataPlaneSelectorService.delete(any())).thenReturn(ServiceResult.success());
when(dataPlaneSelectorService.unregister(any())).thenReturn(ServiceResult.success());
extension.initialize(context);

extension.shutdown();

verify(dataPlaneSelectorService).delete("runtimeId");
verify(dataPlaneSelectorService).unregister("runtimeId");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package org.eclipse.edc.connector.dataplane.selector.spi;

import org.eclipse.edc.connector.dataplane.selector.spi.instance.DataPlaneInstance;
import org.eclipse.edc.connector.dataplane.selector.spi.instance.DataPlaneInstanceStates;
import org.eclipse.edc.runtime.metamodel.annotation.ExtensionPoint;
import org.eclipse.edc.spi.result.ServiceResult;
import org.eclipse.edc.spi.types.domain.DataAddress;
Expand Down Expand Up @@ -58,6 +59,14 @@ public interface DataPlaneSelectorService {
*/
ServiceResult<Void> delete(String instanceId);

/**
* Unregister a Data Plane instance. The state will transition to {@link DataPlaneInstanceStates#UNREGISTERED}.
*
* @param instanceId the instance id.
* @return successful result if operation completed, failure otherwise.
*/
ServiceResult<Void> unregister(String instanceId);

/**
* Find a Data Plane instance by id.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import static org.eclipse.edc.connector.dataplane.selector.spi.instance.DataPlaneInstanceStates.AVAILABLE;
import static org.eclipse.edc.connector.dataplane.selector.spi.instance.DataPlaneInstanceStates.REGISTERED;
import static org.eclipse.edc.connector.dataplane.selector.spi.instance.DataPlaneInstanceStates.UNAVAILABLE;
import static org.eclipse.edc.connector.dataplane.selector.spi.instance.DataPlaneInstanceStates.UNREGISTERED;
import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE;

/**
Expand Down Expand Up @@ -141,6 +142,10 @@ public void transitionToUnavailable() {
transitionTo(UNAVAILABLE.code());
}

public void transitionToUnregistered() {
transitionTo(UNREGISTERED.code());
}

@JsonPOJOBuilder(withPrefix = "")
public static final class Builder extends StatefulEntity.Builder<DataPlaneInstance, Builder> {

Expand Down
Loading