From 04c5f1b22c30888ccd566b511cb9d6ef70b3bca2 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Wed, 4 Dec 2024 15:52:31 +0100 Subject: [PATCH 1/2] use TCK extension in tests --- .../http/message/DspRequestHandlerImpl.java | 1 + extensions/tck-extension/build.gradle.kts | 33 ++++ .../ContractNegotiationRequest.java | 25 +++ .../controller/TckControllerExtension.java | 52 ++++++ .../dsp/controller/TckWebhookController.java | 63 +++++++ .../edc/tck/dsp/data/DataAssembly.java | 165 ++++++++++++++++++ .../dsp/guard/ContractNegotiationGuard.java | 56 ++++++ .../ContractNegotiationTriggerRegistry.java | 26 +++ .../ContractNegotiationTriggerSubscriber.java | 56 ++++++ .../edc/tck/dsp/guard/DelayedActionGuard.java | 113 ++++++++++++ .../edc/tck/dsp/guard/TckGuardExtension.java | 72 ++++++++ .../eclipse/edc/tck/dsp/guard/Trigger.java | 24 +++ .../tck/dsp/identity/NoopIdentityService.java | 39 +++++ .../dsp/identity/TckIdentityExtension.java | 43 +++++ .../edc/tck/dsp/recorder/StepRecorder.java | 61 +++++++ .../edc/tck/dsp/setup/TckSetupExtension.java | 57 ++++++ ...rg.eclipse.edc.spi.system.ServiceExtension | 17 ++ .../tck/dsp/recorder/StepRecorderTest.java | 60 +++++++ settings.gradle.kts | 3 + .../build.gradle.kts | 27 +++ .../edc/tck/dsp/EdcCompatibilityTest.java | 60 +++++++ .../connector-under-test/build.gradle.kts | 22 +++ .../ContractNegotiationRequest.java | 25 +++ .../controller/TckControllerExtension.java | 66 +++++++ .../dsp/controller/TckWebhookController.java | 63 +++++++ .../edc/tck/dsp/data/DataAssembly.java | 165 ++++++++++++++++++ .../dsp/guard/ContractNegotiationGuard.java | 56 ++++++ .../ContractNegotiationTriggerRegistry.java | 26 +++ .../ContractNegotiationTriggerSubscriber.java | 56 ++++++ .../edc/tck/dsp/guard/DelayedActionGuard.java | 113 ++++++++++++ .../edc/tck/dsp/guard/TckGuardExtension.java | 72 ++++++++ .../eclipse/edc/tck/dsp/guard/Trigger.java | 24 +++ .../tck/dsp/identity/NoopIdentityService.java | 39 +++++ .../dsp/identity/TckIdentityExtension.java | 43 +++++ .../edc/tck/dsp/recorder/StepRecorder.java | 61 +++++++ .../edc/tck/dsp/setup/TckSetupExtension.java | 57 ++++++ ...rg.eclipse.edc.spi.system.ServiceExtension | 18 ++ .../connector-under-test/tck-runtime.env | 26 +++ 38 files changed, 1985 insertions(+) create mode 100644 extensions/tck-extension/build.gradle.kts create mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/ContractNegotiationRequest.java create mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/TckControllerExtension.java create mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/TckWebhookController.java create mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/data/DataAssembly.java create mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationGuard.java create mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerRegistry.java create mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerSubscriber.java create mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/DelayedActionGuard.java create mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/TckGuardExtension.java create mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/Trigger.java create mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/identity/NoopIdentityService.java create mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/identity/TckIdentityExtension.java create mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/recorder/StepRecorder.java create mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/setup/TckSetupExtension.java create mode 100644 extensions/tck-extension/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 extensions/tck-extension/src/test/java/org/eclipse/edc/tck/dsp/recorder/StepRecorderTest.java create mode 100644 system-tests/dsp-compatibility-tests/compatibility-test-runner/build.gradle.kts create mode 100644 system-tests/dsp-compatibility-tests/compatibility-test-runner/src/test/java/org/eclipse/edc/tck/dsp/EdcCompatibilityTest.java create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/build.gradle.kts create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/ContractNegotiationRequest.java create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/TckControllerExtension.java create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/TckWebhookController.java create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/data/DataAssembly.java create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationGuard.java create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerRegistry.java create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerSubscriber.java create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/DelayedActionGuard.java create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/TckGuardExtension.java create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/Trigger.java create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/identity/NoopIdentityService.java create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/identity/TckIdentityExtension.java create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/recorder/StepRecorder.java create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/setup/TckSetupExtension.java create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/tck-runtime.env diff --git a/data-protocols/dsp/dsp-http-core/src/main/java/org/eclipse/edc/protocol/dsp/http/message/DspRequestHandlerImpl.java b/data-protocols/dsp/dsp-http-core/src/main/java/org/eclipse/edc/protocol/dsp/http/message/DspRequestHandlerImpl.java index 0a192b2aa99..b6553e3d79a 100644 --- a/data-protocols/dsp/dsp-http-core/src/main/java/org/eclipse/edc/protocol/dsp/http/message/DspRequestHandlerImpl.java +++ b/data-protocols/dsp/dsp-http-core/src/main/java/org/eclipse/edc/protocol/dsp/http/message/DspRequestHandlerImpl.java @@ -95,6 +95,7 @@ public Response createResou var token = request.getToken(); if (token == null) { + monitor.severe("DSP: No auth token provided - returning 401"); return unauthorized(request); } diff --git a/extensions/tck-extension/build.gradle.kts b/extensions/tck-extension/build.gradle.kts new file mode 100644 index 00000000000..782c40c3389 --- /dev/null +++ b/extensions/tck-extension/build.gradle.kts @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` +} + +dependencies { + implementation(project(":spi:common:core-spi")) + implementation(project(":spi:control-plane:contract-spi")) + implementation(project(":spi:control-plane:asset-spi")) + implementation(project(":spi:control-plane:control-plane-spi")) + implementation(project(":spi:common:web-spi")) + implementation(libs.jakarta.rsApi) +} + +// If the EDC Build Plugin is used, every module gets visited during Publishing by default. +// Single modules can be excluded by setting the "publish" flag to false: + +edcBuild { + publish.set(false) +} \ No newline at end of file diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/ContractNegotiationRequest.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/ContractNegotiationRequest.java new file mode 100644 index 00000000000..4bbd6291c76 --- /dev/null +++ b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/ContractNegotiationRequest.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.controller; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Initiates a negotiation request + */ +public record ContractNegotiationRequest(@JsonProperty("providerId") String providerId, + @JsonProperty("connectorAddress") String connectorAddress, + @JsonProperty("offerId") String offerId) { +} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/TckControllerExtension.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/TckControllerExtension.java new file mode 100644 index 00000000000..1c9162a3950 --- /dev/null +++ b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/TckControllerExtension.java @@ -0,0 +1,52 @@ +package org.eclipse.edc.tck.dsp.controller; + +import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.web.spi.WebServer; +import org.eclipse.edc.web.spi.WebService; +import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; +import org.eclipse.edc.web.spi.configuration.WebServiceSettings; + +/** + * Bootstraps the TCK web hook. + */ +public class TckControllerExtension implements ServiceExtension { + private static final String NAME = "DSP TCK Controller"; + private static final String PROTOCOL = "tck"; + + private static final WebServiceSettings SETTINGS = WebServiceSettings.Builder.newInstance() + .apiConfigKey(PROTOCOL) + .contextAlias("tck") + .defaultPath("/tck") + .defaultPort(8687) + .useDefaultContext(false) + .name("Tck API") + .build(); + + + @Inject + private WebServiceConfigurer configurator; + + @Inject + private WebService webService; + + @Inject + private WebServer webServer; + + @Inject + private ContractNegotiationService negotiationService; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + var config = context.getConfig(PROTOCOL); + configurator.configure(config, webServer, SETTINGS); + webService.registerResource(PROTOCOL, new TckWebhookController(negotiationService)); + } +} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/TckWebhookController.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/TckWebhookController.java new file mode 100644 index 00000000000..8067289ca8a --- /dev/null +++ b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/TckWebhookController.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.controller; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractRequest; +import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractOffer; +import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; + +import java.util.List; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; + +/** + * Implements TCK web hooks. + */ +@Consumes(APPLICATION_JSON) +@Produces(APPLICATION_JSON) +@Path("/negotiations") +public class TckWebhookController { + + private ContractNegotiationService negotiationService; + + public TckWebhookController(ContractNegotiationService negotiationService) { + this.negotiationService = negotiationService; + } + + @POST + @Path("requests") + public void startNegotiation(ContractNegotiationRequest request) { + + var contractOffer = ContractOffer.Builder.newInstance() + .id(request.offerId()) + .assetId(request.offerId()) + .policy(Policy.Builder.newInstance().assigner(request.providerId()).build()) + .build(); + var contractRequest = ContractRequest.Builder.newInstance() + .callbackAddresses(List.of(CallbackAddress.Builder.newInstance().uri(request.connectorAddress()).build())) + .counterPartyAddress(request.connectorAddress()) + .contractOffer(contractOffer) + .protocol("dataspace-protocol-http") + .build(); + negotiationService.initiateNegotiation(contractRequest); + System.out.println("Negotiation"); + } +} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/data/DataAssembly.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/data/DataAssembly.java new file mode 100644 index 00000000000..3c77f079c47 --- /dev/null +++ b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/data/DataAssembly.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.data; + +import org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset; +import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationAccepted; +import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationAgreed; +import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationEvent; +import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationOffered; +import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; +import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition; +import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.tck.dsp.guard.Trigger; +import org.eclipse.edc.tck.dsp.recorder.StepRecorder; + +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +import static java.util.stream.Collectors.toSet; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.REQUESTED; + +/** + * Assembles data for the TCK scenarios. + */ +public class DataAssembly { + private static final Set ASSET_IDS = Set.of("ACN0101", "ACN0102", "ACN0103", "ACN0104", + "ACN0201", "ACN0202", "ACN0203", "ACN0204", "ACN0205", "ACN0206", "ACN0207", + "ACN0301", "ACN0302", "ACN0303", "ACN0304"); + + private static final String POLICY_ID = "P123"; + private static final String CONTRACT_DEFINITION_ID = "CD123"; + + public static Set createAssets() { + return ASSET_IDS.stream().map(DataAssembly::createAsset).collect(toSet()); + } + + public static Set createPolicyDefinitions() { + return Set.of(PolicyDefinition.Builder.newInstance() + .id(POLICY_ID) + .policy(Policy.Builder.newInstance().build()) + .build()); + } + + public static Set createContractDefinitions() { + return Set.of(ContractDefinition.Builder.newInstance() + .id(CONTRACT_DEFINITION_ID) + .accessPolicyId(POLICY_ID) + .contractPolicyId(POLICY_ID) + .build()); + } + + public static StepRecorder createNegotiationRecorder() { + var recorder = new StepRecorder(); + + record01NegotiationSequences(recorder); + record02NegotiationSequences(recorder); + record03NegotiationSequences(recorder); + + recordC01NegotiationSequences(recorder); + + return recorder.repeat(); + } + + private static void recordC01NegotiationSequences(StepRecorder recorder) { + } + + private static void record01NegotiationSequences(StepRecorder recorder) { + recorder.record("ACN0101", ContractNegotiation::transitionOffering); + + recorder.record("ACN0102", ContractNegotiation::transitionOffering) + .record("ACN0102", ContractNegotiation::transitionTerminating); + + recorder.record("ACN0103", ContractNegotiation::transitionOffering) + .record("ACN0103", ContractNegotiation::transitionAgreeing) + .record("ACN0103", ContractNegotiation::transitionFinalizing); + + recorder.record("ACN0104", ContractNegotiation::transitionAgreeing) + .record("ACN0104", ContractNegotiation::transitionFinalizing); + } + + private static void record02NegotiationSequences(StepRecorder recorder) { + recorder.record("ACN0201", ContractNegotiation::transitionTerminating); + + recorder.record("ACN0202", ContractNegotiation::transitionRequested); + + recorder.record("ACN0203", ContractNegotiation::transitionAgreeing); + + recorder.record("ACN0204", ContractNegotiation::transitionOffering); + + recorder.record("ACN0205", ContractNegotiation::transitionOffering); + + recorder.record("ACN0206", contractNegotiation -> { + // only transition if in requested + if (contractNegotiation.getState() == REQUESTED.code()) { + contractNegotiation.transitionOffering(); + } + }); + + recorder.record("ACN0207", ContractNegotiation::transitionAgreeing) + .record("ACN0207", ContractNegotiation::transitionTerminating); + } + + private static void record03NegotiationSequences(StepRecorder recorder) { + recorder.record("ACN0301", ContractNegotiation::transitionAgreeing) + .record("ACN0301", ContractNegotiation::transitionFinalizing); + + recorder.record("ACN0302", ContractNegotiation::transitionOffering); + recorder.record("ACN0303", ContractNegotiation::transitionOffering) + .record("ACN0303", ContractNegotiation::transitionAccepting); + + recorder.record("ACN0304", ContractNegotiation::transitionOffering); + } + + public static List> createNegotiationTriggers() { + return List.of( + createTrigger(ContractNegotiationOffered.class, "ACN0205", ContractNegotiation::transitionTerminating), + createTrigger(ContractNegotiationAccepted.class, "ACN0206", ContractNegotiation::transitionTerminating), + createTrigger(ContractNegotiationOffered.class, "C0101", contractNegotiation -> { + contractNegotiation.transitionAccepting(); + contractNegotiation.setPending(false); + }), + createTrigger(ContractNegotiationAgreed.class, "C0101", contractNegotiation -> { + contractNegotiation.transitionVerifying(); + contractNegotiation.setPending(false); + }) + + ); + } + + private static Trigger createTrigger(Class type, + String assetId, + Consumer action) { + return new Trigger<>(event -> { + if (event.getClass().equals(type)) { + return assetId.equals(((ContractNegotiationEvent) event).getLastContractOffer().getAssetId()); + } + return false; + }, action); + } + + private DataAssembly() { + } + + private static Asset createAsset(String id) { + return Asset.Builder.newInstance() + .id(id) + .dataAddress(DataAddress.Builder.newInstance().type("HTTP").build()) + .build(); + } +} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationGuard.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationGuard.java new file mode 100644 index 00000000000..544af77b272 --- /dev/null +++ b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationGuard.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.guard; + +import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.ContractNegotiationPendingGuard; +import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; +import org.eclipse.edc.spi.persistence.StateEntityStore; + +import java.util.Set; +import java.util.function.Consumer; + +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation.Type.PROVIDER; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.ACCEPTING; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.AGREEING; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.FINALIZING; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.INITIAL; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.OFFERING; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.REQUESTING; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.TERMINATING; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.VERIFYING; + +/** + * Contract negotiation guard for TCK testcases. + */ +public class ContractNegotiationGuard extends DelayedActionGuard implements ContractNegotiationPendingGuard { + // the states to not apply the guard to - i.e. to allow automatic transitions by the contract negotiation manager + private static final Set PROVIDER_AUTOMATIC_STATES = Set.of( + OFFERING.code(), + AGREEING.code(), + TERMINATING.code(), + FINALIZING.code()); + + private static final Set CONSUMER_AUTOMATIC_STATES = Set.of( + INITIAL.code(), + REQUESTING.code(), + ACCEPTING.code(), + VERIFYING.code() + ); + + public ContractNegotiationGuard(Consumer action, StateEntityStore store) { + super(cn -> cn.getType() == PROVIDER ? + !PROVIDER_AUTOMATIC_STATES.contains(cn.getState()) : !CONSUMER_AUTOMATIC_STATES.contains(cn.getState()), action, store); + } +} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerRegistry.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerRegistry.java new file mode 100644 index 00000000000..d7e5e910fc6 --- /dev/null +++ b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerRegistry.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.guard; + +import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; + +/** + * Registers contract negotiation triggers. + */ +public interface ContractNegotiationTriggerRegistry { + + void register(Trigger trigger); + +} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerSubscriber.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerSubscriber.java new file mode 100644 index 00000000000..3d1dbea1c52 --- /dev/null +++ b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerSubscriber.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.guard; + +import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationEvent; +import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; +import org.eclipse.edc.spi.event.Event; +import org.eclipse.edc.spi.event.EventEnvelope; +import org.eclipse.edc.spi.event.EventSubscriber; +import org.eclipse.edc.spi.persistence.StateEntityStore; + +import java.util.ArrayList; +import java.util.List; + +/** + * Fires triggers based on negotiation events. + */ +public class ContractNegotiationTriggerSubscriber implements EventSubscriber, ContractNegotiationTriggerRegistry { + private StateEntityStore store; + + private List> triggers = new ArrayList<>(); + + public ContractNegotiationTriggerSubscriber(StateEntityStore store) { + this.store = store; + } + + @Override + public void register(Trigger trigger) { + triggers.add(trigger); + } + + @Override + public void on(EventEnvelope envelope) { + triggers.stream() + .filter(trigger -> trigger.predicate().test(envelope.getPayload())) + .forEach(trigger -> { + var event = (ContractNegotiationEvent) envelope.getPayload(); + var negotiation = store.findByIdAndLease(event.getContractNegotiationId()).getContent(); + trigger.action().accept(negotiation); + store.save(negotiation); + }); + } + +} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/DelayedActionGuard.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/DelayedActionGuard.java new file mode 100644 index 00000000000..838350bf031 --- /dev/null +++ b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/DelayedActionGuard.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.guard; + +import org.eclipse.edc.spi.entity.PendingGuard; +import org.eclipse.edc.spi.entity.StatefulEntity; +import org.eclipse.edc.spi.persistence.StateEntityStore; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.DelayQueue; +import java.util.concurrent.Delayed; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +/** + * A guard that performs actions on a stateful entity. + *

+ * Note this implementation is not safe to use in a clustered environment since transitions are not performed in the context of + * a command handler. + */ +public class DelayedActionGuard> implements PendingGuard { + private final Predicate filter; + private final Consumer action; + private final StateEntityStore store; + private final DelayQueue queue; + private final AtomicBoolean active = new AtomicBoolean(); + + private final ExecutorService executor = Executors.newFixedThreadPool(1); + + public DelayedActionGuard(Predicate filter, + Consumer action, + StateEntityStore store) { + this.filter = filter; + this.action = action; + this.store = store; + queue = new DelayQueue<>(); + } + + public void start() { + active.set(true); + executor.submit(() -> { + while (active.get()) { + try { + var entry = queue.poll(10, MILLISECONDS); + if (entry != null) { + action.accept(entry.entity); + entry.entity.setPending(false); + store.save(entry.entity); + } + } catch (InterruptedException e) { + e.printStackTrace(); + Thread.interrupted(); + break; + } + } + }); + } + + public void stop() { + active.set(false); + } + + @Override + public boolean test(T entity) { + if (filter.test(entity)) { + queue.put(new GuardDelay(entity)); + return true; + } + return false; + } + + protected class GuardDelay implements Delayed { + T entity; + private final long start; + + GuardDelay(T entity) { + this.entity = entity; + start = System.currentTimeMillis(); + } + + @Override + public int compareTo(@NotNull Delayed delayed) { + var millis = getDelay(MILLISECONDS) - delayed.getDelay(MILLISECONDS); + millis = Math.min(millis, 1); + millis = Math.max(millis, -1); + return (int) millis; + } + + @Override + public long getDelay(@NotNull TimeUnit timeUnit) { + return timeUnit.convert(500 - (System.currentTimeMillis() - start), MILLISECONDS); + } + + } +} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/TckGuardExtension.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/TckGuardExtension.java new file mode 100644 index 00000000000..b5779b829d3 --- /dev/null +++ b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/TckGuardExtension.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.guard; + +import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationEvent; +import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.ContractNegotiationPendingGuard; +import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.store.ContractNegotiationStore; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Provider; +import org.eclipse.edc.spi.event.EventRouter; +import org.eclipse.edc.spi.system.ServiceExtension; + +import static org.eclipse.edc.tck.dsp.data.DataAssembly.createNegotiationRecorder; +import static org.eclipse.edc.tck.dsp.data.DataAssembly.createNegotiationTriggers; + +/** + * Loads the transition guard. + */ +public class TckGuardExtension implements ServiceExtension { + private static final String NAME = "DSP TCK Guard"; + + private ContractNegotiationGuard negotiationGuard; + + @Inject + private ContractNegotiationStore store; + + @Inject + private EventRouter router; + + @Override + public String name() { + return NAME; + } + + @Provider + public ContractNegotiationPendingGuard negotiationGuard() { + var recorder = createNegotiationRecorder(); + + var registry = new ContractNegotiationTriggerSubscriber(store); + createNegotiationTriggers().forEach(registry::register); + router.register(ContractNegotiationEvent.class, registry); + + negotiationGuard = new ContractNegotiationGuard(cn -> recorder.playNext(cn.getContractOffers().get(0).getAssetId(), cn), store); + return negotiationGuard; + } + + @Override + public void prepare() { + if (negotiationGuard != null) { + negotiationGuard.start(); + } + } + + @Override + public void shutdown() { + if (negotiationGuard != null) { + negotiationGuard.stop(); + } + } +} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/Trigger.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/Trigger.java new file mode 100644 index 00000000000..5aa8ebe3124 --- /dev/null +++ b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/Trigger.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.guard; + +import java.util.function.Consumer; +import java.util.function.Predicate; + +/** + * An action that is triggered when the predicate matches a condition. + */ +public record Trigger(Predicate predicate, Consumer action) { +} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/identity/NoopIdentityService.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/identity/NoopIdentityService.java new file mode 100644 index 00000000000..f6b51f93316 --- /dev/null +++ b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/identity/NoopIdentityService.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.identity; + +import org.eclipse.edc.spi.iam.ClaimToken; +import org.eclipse.edc.spi.iam.IdentityService; +import org.eclipse.edc.spi.iam.TokenParameters; +import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.iam.VerificationContext; +import org.eclipse.edc.spi.result.Result; + +/** + * No-op service + */ +public class NoopIdentityService implements IdentityService { + private static final String TCK_PARTICIPANT_ID = "TCK_PARTICIPANT"; // the official TCK id + + @Override + public Result obtainClientCredentials(TokenParameters tokenParameters) { + return Result.success(TokenRepresentation.Builder.newInstance().token("1234").expiresIn(Long.MAX_VALUE).build()); + } + + @Override + public Result verifyJwtToken(TokenRepresentation tokenRepresentation, VerificationContext verificationContext) { + return Result.success(ClaimToken.Builder.newInstance().claim("client_id", TCK_PARTICIPANT_ID).build()); + } +} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/identity/TckIdentityExtension.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/identity/TckIdentityExtension.java new file mode 100644 index 00000000000..32ce17d20e1 --- /dev/null +++ b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/identity/TckIdentityExtension.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc. + * + * 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: + * Metaform Systems, Inc. - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.identity; + +import org.eclipse.edc.runtime.metamodel.annotation.Provider; +import org.eclipse.edc.spi.iam.AudienceResolver; +import org.eclipse.edc.spi.iam.IdentityService; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.system.ServiceExtension; + +/** + * Loads a no-op identity service. + */ +public class TckIdentityExtension implements ServiceExtension { + private static final String NAME = "DSP TCK Identity"; + + @Override + public String name() { + return NAME; + } + + @Provider + public IdentityService identityService() { + return new NoopIdentityService(); + } + + @Provider + public AudienceResolver audienceResolver() { + return m -> Result.success(m.getCounterPartyId()); + } +} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/recorder/StepRecorder.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/recorder/StepRecorder.java new file mode 100644 index 00000000000..0a29567c21e --- /dev/null +++ b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/recorder/StepRecorder.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc + * + * 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: + * Metaform Systems, Inc - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.recorder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * Records and plays a sequence of steps. Sequences may be repeated if {@link #repeat()} is enabled. + */ +public class StepRecorder { + private boolean repeat; + private int playIndex = 0; + private Map>> sequences = new HashMap<>(); + + public synchronized StepRecorder playNext(String key, T entity) { + var sequence = sequences.get(key); + if (sequence == null) { + throw new AssertionError("No sequence found for key " + key); + } + if (sequence.isEmpty()) { + throw new IllegalStateException("No replay steps"); + } + if (playIndex >= sequence.size()) { + throw new IllegalStateException("Exceeded replay steps"); + } + sequence.get(playIndex).accept(entity); + if (repeat && playIndex == sequence.size() - 1) { + playIndex = 0; + } else { + playIndex++; + } + return this; + } + + public synchronized StepRecorder record(String key, Consumer step) { + var sequence = sequences.computeIfAbsent(key, k -> new ArrayList<>()); + sequence.add(step); + return this; + } + + public synchronized StepRecorder repeat() { + repeat = true; + return this; + } +} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/setup/TckSetupExtension.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/setup/TckSetupExtension.java new file mode 100644 index 00000000000..44259c598c2 --- /dev/null +++ b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/setup/TckSetupExtension.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.setup; + +import org.eclipse.edc.connector.controlplane.asset.spi.index.AssetIndex; +import org.eclipse.edc.connector.controlplane.services.spi.contractdefinition.ContractDefinitionService; +import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; + +import static org.eclipse.edc.tck.dsp.data.DataAssembly.createAssets; +import static org.eclipse.edc.tck.dsp.data.DataAssembly.createContractDefinitions; +import static org.eclipse.edc.tck.dsp.data.DataAssembly.createPolicyDefinitions; +import static org.eclipse.edc.tck.dsp.setup.TckSetupExtension.NAME; + +/** + * Loads customizations and seed data for the TCK. + */ +@Extension(NAME) +public class TckSetupExtension implements ServiceExtension { + public static final String NAME = "DSP TCK Setup"; + + @Inject + private AssetIndex assetIndex; + + @Inject + private PolicyDefinitionService policyDefinitionService; + + @Inject + private ContractDefinitionService contractDefinitionService; + + @Override + public String name() { + return NAME; + } + + @Override + public void prepare() { + createAssets().forEach(asset -> assetIndex.create(asset)); + createPolicyDefinitions().forEach(definition -> policyDefinitionService.create(definition)); + createContractDefinitions().forEach(definition -> contractDefinitionService.create(definition)); + } + +} diff --git a/extensions/tck-extension/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/tck-extension/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 00000000000..84672e50a18 --- /dev/null +++ b/extensions/tck-extension/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,17 @@ +# +# Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# +# 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: +# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +# +# +org.eclipse.edc.tck.dsp.guard.TckGuardExtension +org.eclipse.edc.tck.dsp.setup.TckSetupExtension +org.eclipse.edc.tck.dsp.identity.TckIdentityExtension +org.eclipse.edc.tck.dsp.controller.TckControllerExtension \ No newline at end of file diff --git a/extensions/tck-extension/src/test/java/org/eclipse/edc/tck/dsp/recorder/StepRecorderTest.java b/extensions/tck-extension/src/test/java/org/eclipse/edc/tck/dsp/recorder/StepRecorderTest.java new file mode 100644 index 00000000000..e6fab18c3df --- /dev/null +++ b/extensions/tck-extension/src/test/java/org/eclipse/edc/tck/dsp/recorder/StepRecorderTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 Metaform Systems, Inc + * + * 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: + * Metaform Systems, Inc - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.recorder; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +class StepRecorderTest { + private static final String KEY = "test"; + + @Test + void verifyRecordReplay() { + var recorder = new StepRecorder(); + var counter = new int[1]; + recorder.record(KEY, i -> assertThat(counter[0]++).isEqualTo(0)); + recorder.record(KEY, i -> assertThat(counter[0]++).isEqualTo(1)); + + recorder.playNext(KEY, null); + recorder.playNext(KEY, null); + assertThat(counter[0]).isEqualTo(2); + } + + @Test + void verifyRepeat() { + var recorder = new StepRecorder(); + var counter = new int[1]; + recorder.record(KEY, i -> counter[0]++); + recorder.repeat(); + + for (int i = 0; i < 4; i++) { + recorder.playNext(KEY, null); + } + assertThat(counter[0]).isEqualTo(4); + } + + @Test + void verifyNoRepeat() { + var recorder = new StepRecorder(); + var counter = new int[1]; + recorder.record(KEY, i -> counter[0]++); + + recorder.playNext(KEY, null); + assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> recorder.playNext(KEY, null)); + assertThat(counter[0]).isEqualTo(1); + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 0852091a239..b8de6ee48fe 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -221,6 +221,7 @@ include(":extensions:data-plane-selector:store:sql:data-plane-instance-store-sql include(":extensions:policy-monitor:store:sql:policy-monitor-store-sql") +include(":extensions:tck-extension") // modules for launchers, i.e. runnable compositions of the app ------------------------------------ include(":launchers:dpf-selector") @@ -291,6 +292,8 @@ include(":system-tests:sts-api:sts-api-test-runtime") include(":system-tests:telemetry:telemetry-test-runner") include(":system-tests:telemetry:telemetry-test-runtime") include(":system-tests:bom-tests") +include(":system-tests:dsp-compatibility-tests:connector-under-test") +include(":system-tests:dsp-compatibility-tests:compatibility-test-runner") // BOM modules ---------------------------------------------------------------- include(":dist:bom:controlplane-base-bom") diff --git a/system-tests/dsp-compatibility-tests/compatibility-test-runner/build.gradle.kts b/system-tests/dsp-compatibility-tests/compatibility-test-runner/build.gradle.kts new file mode 100644 index 00000000000..32548fe1d78 --- /dev/null +++ b/system-tests/dsp-compatibility-tests/compatibility-test-runner/build.gradle.kts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + java +} + +dependencies { + testImplementation(project(":core:common:junit")) + testImplementation(libs.assertj) + testImplementation(libs.awaitility) +} + +edcBuild { + publish.set(false) +} diff --git a/system-tests/dsp-compatibility-tests/compatibility-test-runner/src/test/java/org/eclipse/edc/tck/dsp/EdcCompatibilityTest.java b/system-tests/dsp-compatibility-tests/compatibility-test-runner/src/test/java/org/eclipse/edc/tck/dsp/EdcCompatibilityTest.java new file mode 100644 index 00000000000..5de8758d308 --- /dev/null +++ b/system-tests/dsp-compatibility-tests/compatibility-test-runner/src/test/java/org/eclipse/edc/tck/dsp/EdcCompatibilityTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp; + +import org.eclipse.edc.junit.extensions.EmbeddedRuntime; +import org.eclipse.edc.junit.extensions.RuntimeExtension; +import org.eclipse.edc.junit.extensions.RuntimePerClassExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.util.HashMap; +import java.util.concurrent.CountDownLatch; + +import static org.eclipse.edc.util.io.Ports.getFreePort; + +public class EdcCompatibilityTest { + + @RegisterExtension + protected RuntimeExtension runtime = + new RuntimePerClassExtension(new EmbeddedRuntime("CUnT", + new HashMap<>() { + { + put("edc.participant.id", "CONNECTOR_UNDER_TEST"); + put("web.http.port", "8080"); + put("web.http.path", "/api"); + put("web.http.version.port", String.valueOf(getFreePort())); + put("web.http.version.path", "/api/version"); + put("web.http.control.port", String.valueOf(getFreePort())); + put("web.http.control.path", "/api/control"); + put("web.http.management.port", "8081"); + put("web.http.management.path", "/api/management"); + put("web.http.protocol.port", "8282"); + put("web.http.protocol.path", "/api/v1/dsp"); // expected by TCK + put("web.api.auth.key", "password"); + put("edc.dsp.callback.address", "http://localhost:8282/api/v1/dsp"); + put("edc.management.context.enabled", "true"); + } + }, + ":system-tests:dsp-compatibility-tests:connector-under-test" + )); + + @Test + void assertRuntimeReady() throws InterruptedException { + var l = new CountDownLatch(1); + l.await(); + } +} + diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/build.gradle.kts b/system-tests/dsp-compatibility-tests/connector-under-test/build.gradle.kts new file mode 100644 index 00000000000..569bab553c6 --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/build.gradle.kts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +plugins { + `java-library` +} + +dependencies { + api(project(":dist:bom:controlplane-base-bom")) + runtimeOnly(project(":extensions:tck-extension")) +} \ No newline at end of file diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/ContractNegotiationRequest.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/ContractNegotiationRequest.java new file mode 100644 index 00000000000..6a0f4173167 --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/ContractNegotiationRequest.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.controller; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Initiates a negotiation request + */ +public record ContractNegotiationRequest(@JsonProperty("providerId") String providerId, + @JsonProperty("connectorAddress") String connectorAddress, + @JsonProperty("offerId") String offerId) { +} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/TckControllerExtension.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/TckControllerExtension.java new file mode 100644 index 00000000000..c4506b2b0f7 --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/TckControllerExtension.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.controller; + +import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.web.spi.WebServer; +import org.eclipse.edc.web.spi.WebService; +import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; +import org.eclipse.edc.web.spi.configuration.WebServiceSettings; + +/** + * Bootstraps the TCK web hook. + */ +public class TckControllerExtension implements ServiceExtension { + private static final String NAME = "DSP TCK Controller"; + private static final String PROTOCOL = "tck"; + + private static final WebServiceSettings SETTINGS = WebServiceSettings.Builder.newInstance() + .apiConfigKey(PROTOCOL) + .contextAlias("tck") + .defaultPath("/tck") + .defaultPort(8687) + .useDefaultContext(false) + .name("Tck API") + .build(); + + + @Inject + private WebServiceConfigurer configurator; + + @Inject + private WebService webService; + + @Inject + private WebServer webServer; + + @Inject + private ContractNegotiationService negotiationService; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + var config = context.getConfig(PROTOCOL); + configurator.configure(config, webServer, SETTINGS); + webService.registerResource(PROTOCOL, new TckWebhookController(negotiationService)); + } +} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/TckWebhookController.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/TckWebhookController.java new file mode 100644 index 00000000000..96851691343 --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/TckWebhookController.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.controller; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractRequest; +import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractOffer; +import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; + +import java.util.List; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; + +/** + * Implements TCK web hooks. + */ +@Consumes(APPLICATION_JSON) +@Produces(APPLICATION_JSON) +@Path("/negotiations") +public class TckWebhookController { + + private final ContractNegotiationService negotiationService; + + public TckWebhookController(ContractNegotiationService negotiationService) { + this.negotiationService = negotiationService; + } + + @POST + @Path("requests") + public void startNegotiation(ContractNegotiationRequest request) { + + var contractOffer = ContractOffer.Builder.newInstance() + .id(request.offerId()) + .assetId(request.offerId()) + .policy(Policy.Builder.newInstance().assigner(request.providerId()).build()) + .build(); + var contractRequest = ContractRequest.Builder.newInstance() + .callbackAddresses(List.of(CallbackAddress.Builder.newInstance().uri(request.connectorAddress()).build())) + .counterPartyAddress(request.connectorAddress()) + .contractOffer(contractOffer) + .protocol("dataspace-protocol-http") + .build(); + negotiationService.initiateNegotiation(contractRequest); + System.out.println("Negotiation"); + } +} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/data/DataAssembly.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/data/DataAssembly.java new file mode 100644 index 00000000000..0530a3e3569 --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/data/DataAssembly.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.data; + +import org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset; +import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationAccepted; +import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationAgreed; +import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationEvent; +import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationOffered; +import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; +import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition; +import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.tck.dsp.guard.Trigger; +import org.eclipse.edc.tck.dsp.recorder.StepRecorder; + +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +import static java.util.stream.Collectors.toSet; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.REQUESTED; + +/** + * Assembles data for the TCK scenarios. + */ +public class DataAssembly { + private static final Set ASSET_IDS = Set.of("ACN0101", "ACN0102", "ACN0103", "ACN0104", + "ACN0201", "ACN0202", "ACN0203", "ACN0204", "ACN0205", "ACN0206", "ACN0207", + "ACN0301", "ACN0302", "ACN0303", "ACN0304"); + + private static final String POLICY_ID = "P123"; + private static final String CONTRACT_DEFINITION_ID = "CD123"; + + public static Set createAssets() { + return ASSET_IDS.stream().map(DataAssembly::createAsset).collect(toSet()); + } + + public static Set createPolicyDefinitions() { + return Set.of(PolicyDefinition.Builder.newInstance() + .id(POLICY_ID) + .policy(Policy.Builder.newInstance().build()) + .build()); + } + + public static Set createContractDefinitions() { + return Set.of(ContractDefinition.Builder.newInstance() + .id(CONTRACT_DEFINITION_ID) + .accessPolicyId(POLICY_ID) + .contractPolicyId(POLICY_ID) + .build()); + } + + public static StepRecorder createNegotiationRecorder() { + var recorder = new StepRecorder(); + + record01NegotiationSequences(recorder); + record02NegotiationSequences(recorder); + record03NegotiationSequences(recorder); + + recordC01NegotiationSequences(recorder); + + return recorder.repeat(); + } + + private static void recordC01NegotiationSequences(StepRecorder recorder) { + } + + private static void record01NegotiationSequences(StepRecorder recorder) { + recorder.record("ACN0101", ContractNegotiation::transitionOffering); + + recorder.record("ACN0102", ContractNegotiation::transitionOffering) + .record("ACN0102", ContractNegotiation::transitionTerminating); + + recorder.record("ACN0103", ContractNegotiation::transitionOffering) + .record("ACN0103", ContractNegotiation::transitionAgreeing) + .record("ACN0103", ContractNegotiation::transitionFinalizing); + + recorder.record("ACN0104", ContractNegotiation::transitionAgreeing) + .record("ACN0104", ContractNegotiation::transitionFinalizing); + } + + private static void record02NegotiationSequences(StepRecorder recorder) { + recorder.record("ACN0201", ContractNegotiation::transitionTerminating); + + recorder.record("ACN0202", ContractNegotiation::transitionRequested); + + recorder.record("ACN0203", ContractNegotiation::transitionAgreeing); + + recorder.record("ACN0204", ContractNegotiation::transitionOffering); + + recorder.record("ACN0205", ContractNegotiation::transitionOffering); + + recorder.record("ACN0206", contractNegotiation -> { + // only transition if in requested + if (contractNegotiation.getState() == REQUESTED.code()) { + contractNegotiation.transitionOffering(); + } + }); + + recorder.record("ACN0207", ContractNegotiation::transitionAgreeing) + .record("ACN0207", ContractNegotiation::transitionTerminating); + } + + private static void record03NegotiationSequences(StepRecorder recorder) { + recorder.record("ACN0301", ContractNegotiation::transitionAgreeing) + .record("ACN0301", ContractNegotiation::transitionFinalizing); + + recorder.record("ACN0302", ContractNegotiation::transitionOffering); + recorder.record("ACN0303", ContractNegotiation::transitionOffering) + .record("ACN0303", ContractNegotiation::transitionAccepting); + + recorder.record("ACN0304", ContractNegotiation::transitionOffering); + } + + public static List> createNegotiationTriggers() { + return List.of( + createTrigger(ContractNegotiationOffered.class, "ACN0205", ContractNegotiation::transitionTerminating), + createTrigger(ContractNegotiationAccepted.class, "ACN0206", ContractNegotiation::transitionTerminating), + createTrigger(ContractNegotiationOffered.class, "C0101", contractNegotiation -> { + contractNegotiation.transitionAccepting(); + contractNegotiation.setPending(false); + }), + createTrigger(ContractNegotiationAgreed.class, "C0101", contractNegotiation -> { + contractNegotiation.transitionVerifying(); + contractNegotiation.setPending(false); + }) + + ); + } + + private static Trigger createTrigger(Class type, + String assetId, + Consumer action) { + return new Trigger<>(event -> { + if (event.getClass().equals(type)) { + return assetId.equals(((ContractNegotiationEvent) event).getLastContractOffer().getAssetId()); + } + return false; + }, action); + } + + private DataAssembly() { + } + + private static Asset createAsset(String id) { + return Asset.Builder.newInstance() + .id(id) + .dataAddress(DataAddress.Builder.newInstance().type("HTTP").build()) + .build(); + } +} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationGuard.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationGuard.java new file mode 100644 index 00000000000..027513fc920 --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationGuard.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.guard; + +import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.ContractNegotiationPendingGuard; +import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; +import org.eclipse.edc.spi.persistence.StateEntityStore; + +import java.util.Set; +import java.util.function.Consumer; + +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation.Type.PROVIDER; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.ACCEPTING; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.AGREEING; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.FINALIZING; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.INITIAL; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.OFFERING; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.REQUESTING; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.TERMINATING; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.VERIFYING; + +/** + * Contract negotiation guard for TCK testcases. + */ +public class ContractNegotiationGuard extends DelayedActionGuard implements ContractNegotiationPendingGuard { + // the states to not apply the guard to - i.e. to allow automatic transitions by the contract negotiation manager + private static final Set PROVIDER_AUTOMATIC_STATES = Set.of( + OFFERING.code(), + AGREEING.code(), + TERMINATING.code(), + FINALIZING.code()); + + private static final Set CONSUMER_AUTOMATIC_STATES = Set.of( + INITIAL.code(), + REQUESTING.code(), + ACCEPTING.code(), + VERIFYING.code() + ); + + public ContractNegotiationGuard(Consumer action, StateEntityStore store) { + super(cn -> cn.getType() == PROVIDER ? + !PROVIDER_AUTOMATIC_STATES.contains(cn.getState()) : !CONSUMER_AUTOMATIC_STATES.contains(cn.getState()), action, store); + } +} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerRegistry.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerRegistry.java new file mode 100644 index 00000000000..e516813104c --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerRegistry.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.guard; + +import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; + +/** + * Registers contract negotiation triggers. + */ +public interface ContractNegotiationTriggerRegistry { + + void register(Trigger trigger); + +} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerSubscriber.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerSubscriber.java new file mode 100644 index 00000000000..b508c547720 --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerSubscriber.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.guard; + +import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationEvent; +import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; +import org.eclipse.edc.spi.event.Event; +import org.eclipse.edc.spi.event.EventEnvelope; +import org.eclipse.edc.spi.event.EventSubscriber; +import org.eclipse.edc.spi.persistence.StateEntityStore; + +import java.util.ArrayList; +import java.util.List; + +/** + * Fires triggers based on negotiation events. + */ +public class ContractNegotiationTriggerSubscriber implements EventSubscriber, ContractNegotiationTriggerRegistry { + private final StateEntityStore store; + + private final List> triggers = new ArrayList<>(); + + public ContractNegotiationTriggerSubscriber(StateEntityStore store) { + this.store = store; + } + + @Override + public void register(Trigger trigger) { + triggers.add(trigger); + } + + @Override + public void on(EventEnvelope envelope) { + triggers.stream() + .filter(trigger -> trigger.predicate().test(envelope.getPayload())) + .forEach(trigger -> { + var event = (ContractNegotiationEvent) envelope.getPayload(); + var negotiation = store.findByIdAndLease(event.getContractNegotiationId()).getContent(); + trigger.action().accept(negotiation); + store.save(negotiation); + }); + } + +} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/DelayedActionGuard.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/DelayedActionGuard.java new file mode 100644 index 00000000000..e9e9a89dec2 --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/DelayedActionGuard.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.guard; + +import org.eclipse.edc.spi.entity.PendingGuard; +import org.eclipse.edc.spi.entity.StatefulEntity; +import org.eclipse.edc.spi.persistence.StateEntityStore; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.DelayQueue; +import java.util.concurrent.Delayed; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +/** + * A guard that performs actions on a stateful entity. + *

+ * Note this implementation is not safe to use in a clustered environment since transitions are not performed in the context of + * a command handler. + */ +public class DelayedActionGuard> implements PendingGuard { + private final Predicate filter; + private final Consumer action; + private final StateEntityStore store; + private final DelayQueue queue; + private final AtomicBoolean active = new AtomicBoolean(); + + private final ExecutorService executor = Executors.newFixedThreadPool(1); + + public DelayedActionGuard(Predicate filter, + Consumer action, + StateEntityStore store) { + this.filter = filter; + this.action = action; + this.store = store; + queue = new DelayQueue<>(); + } + + public void start() { + active.set(true); + executor.submit(() -> { + while (active.get()) { + try { + var entry = queue.poll(10, MILLISECONDS); + if (entry != null) { + action.accept(entry.entity); + entry.entity.setPending(false); + store.save(entry.entity); + } + } catch (InterruptedException e) { + e.printStackTrace(); + Thread.interrupted(); + break; + } + } + }); + } + + public void stop() { + active.set(false); + } + + @Override + public boolean test(T entity) { + if (filter.test(entity)) { + queue.put(new GuardDelay(entity)); + return true; + } + return false; + } + + protected class GuardDelay implements Delayed { + T entity; + private final long start; + + GuardDelay(T entity) { + this.entity = entity; + start = System.currentTimeMillis(); + } + + @Override + public int compareTo(@NotNull Delayed delayed) { + var millis = getDelay(MILLISECONDS) - delayed.getDelay(MILLISECONDS); + millis = Math.min(millis, 1); + millis = Math.max(millis, -1); + return (int) millis; + } + + @Override + public long getDelay(@NotNull TimeUnit timeUnit) { + return timeUnit.convert(500 - (System.currentTimeMillis() - start), MILLISECONDS); + } + + } +} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/TckGuardExtension.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/TckGuardExtension.java new file mode 100644 index 00000000000..3a2f1a00169 --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/TckGuardExtension.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.guard; + +import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationEvent; +import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.ContractNegotiationPendingGuard; +import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.store.ContractNegotiationStore; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Provider; +import org.eclipse.edc.spi.event.EventRouter; +import org.eclipse.edc.spi.system.ServiceExtension; + +import static org.eclipse.edc.tck.dsp.data.DataAssembly.createNegotiationRecorder; +import static org.eclipse.edc.tck.dsp.data.DataAssembly.createNegotiationTriggers; + +/** + * Loads the transition guard. + */ +public class TckGuardExtension implements ServiceExtension { + private static final String NAME = "DSP TCK Guard"; + + private org.eclipse.edc.tck.dsp.guard.ContractNegotiationGuard negotiationGuard; + + @Inject + private ContractNegotiationStore store; + + @Inject + private EventRouter router; + + @Override + public String name() { + return NAME; + } + + @Provider + public ContractNegotiationPendingGuard negotiationGuard() { + var recorder = createNegotiationRecorder(); + + var registry = new org.eclipse.edc.tck.dsp.guard.ContractNegotiationTriggerSubscriber(store); + createNegotiationTriggers().forEach(registry::register); + router.register(ContractNegotiationEvent.class, registry); + + negotiationGuard = new org.eclipse.edc.tck.dsp.guard.ContractNegotiationGuard(cn -> recorder.playNext(cn.getContractOffers().get(0).getAssetId(), cn), store); + return negotiationGuard; + } + + @Override + public void prepare() { + if (negotiationGuard != null) { + negotiationGuard.start(); + } + } + + @Override + public void shutdown() { + if (negotiationGuard != null) { + negotiationGuard.stop(); + } + } +} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/Trigger.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/Trigger.java new file mode 100644 index 00000000000..2dfbad828d2 --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/Trigger.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.guard; + +import java.util.function.Consumer; +import java.util.function.Predicate; + +/** + * An action that is triggered when the predicate matches a condition. + */ +public record Trigger(Predicate predicate, Consumer action) { +} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/identity/NoopIdentityService.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/identity/NoopIdentityService.java new file mode 100644 index 00000000000..134375156ca --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/identity/NoopIdentityService.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.identity; + +import org.eclipse.edc.spi.iam.ClaimToken; +import org.eclipse.edc.spi.iam.IdentityService; +import org.eclipse.edc.spi.iam.TokenParameters; +import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.iam.VerificationContext; +import org.eclipse.edc.spi.result.Result; + +/** + * No-op service + */ +public class NoopIdentityService implements IdentityService { + private static final String TCK_PARTICIPANT_ID = "TCK_PARTICIPANT"; // the official TCK id + + @Override + public Result obtainClientCredentials(TokenParameters tokenParameters) { + return Result.success(TokenRepresentation.Builder.newInstance().token("1234").expiresIn(Long.MAX_VALUE).build()); + } + + @Override + public Result verifyJwtToken(TokenRepresentation tokenRepresentation, VerificationContext verificationContext) { + return Result.success(ClaimToken.Builder.newInstance().claim("client_id", TCK_PARTICIPANT_ID).build()); + } +} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/identity/TckIdentityExtension.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/identity/TckIdentityExtension.java new file mode 100644 index 00000000000..88ed10b1797 --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/identity/TckIdentityExtension.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.identity; + +import org.eclipse.edc.runtime.metamodel.annotation.Provider; +import org.eclipse.edc.spi.iam.AudienceResolver; +import org.eclipse.edc.spi.iam.IdentityService; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.system.ServiceExtension; + +/** + * Loads a no-op identity service. + */ +public class TckIdentityExtension implements ServiceExtension { + private static final String NAME = "DSP TCK Identity"; + + @Override + public String name() { + return NAME; + } + + @Provider + public IdentityService identityService() { + return new NoopIdentityService(); + } + + @Provider + public AudienceResolver audienceResolver() { + return m -> Result.success(m.getCounterPartyId()); + } +} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/recorder/StepRecorder.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/recorder/StepRecorder.java new file mode 100644 index 00000000000..41d860befe8 --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/recorder/StepRecorder.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.recorder; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * Records and plays a sequence of steps. Sequences may be repeated if {@link #repeat()} is enabled. + */ +public class StepRecorder { + private boolean repeat; + private int playIndex = 0; + private final Map>> sequences = new HashMap<>(); + + public synchronized StepRecorder playNext(String key, T entity) { + var sequence = sequences.get(key); + if (sequence == null) { + throw new AssertionError("No sequence found for key " + key); + } + if (sequence.isEmpty()) { + throw new IllegalStateException("No replay steps"); + } + if (playIndex >= sequence.size()) { + throw new IllegalStateException("Exceeded replay steps"); + } + sequence.get(playIndex).accept(entity); + if (repeat && playIndex == sequence.size() - 1) { + playIndex = 0; + } else { + playIndex++; + } + return this; + } + + public synchronized StepRecorder record(String key, Consumer step) { + var sequence = sequences.computeIfAbsent(key, k -> new ArrayList<>()); + sequence.add(step); + return this; + } + + public synchronized StepRecorder repeat() { + repeat = true; + return this; + } +} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/setup/TckSetupExtension.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/setup/TckSetupExtension.java new file mode 100644 index 00000000000..44259c598c2 --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/setup/TckSetupExtension.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.tck.dsp.setup; + +import org.eclipse.edc.connector.controlplane.asset.spi.index.AssetIndex; +import org.eclipse.edc.connector.controlplane.services.spi.contractdefinition.ContractDefinitionService; +import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; + +import static org.eclipse.edc.tck.dsp.data.DataAssembly.createAssets; +import static org.eclipse.edc.tck.dsp.data.DataAssembly.createContractDefinitions; +import static org.eclipse.edc.tck.dsp.data.DataAssembly.createPolicyDefinitions; +import static org.eclipse.edc.tck.dsp.setup.TckSetupExtension.NAME; + +/** + * Loads customizations and seed data for the TCK. + */ +@Extension(NAME) +public class TckSetupExtension implements ServiceExtension { + public static final String NAME = "DSP TCK Setup"; + + @Inject + private AssetIndex assetIndex; + + @Inject + private PolicyDefinitionService policyDefinitionService; + + @Inject + private ContractDefinitionService contractDefinitionService; + + @Override + public String name() { + return NAME; + } + + @Override + public void prepare() { + createAssets().forEach(asset -> assetIndex.create(asset)); + createPolicyDefinitions().forEach(definition -> policyDefinitionService.create(definition)); + createContractDefinitions().forEach(definition -> contractDefinitionService.create(definition)); + } + +} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 00000000000..e7ca6341b1c --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,18 @@ +# +# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# +# 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: +# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +# +# + +org.eclipse.edc.tck.dsp.guard.TckGuardExtension +org.eclipse.edc.tck.dsp.setup.TckSetupExtension +org.eclipse.edc.tck.dsp.identity.TckIdentityExtension +org.eclipse.edc.tck.dsp.controller.TckControllerExtension \ No newline at end of file diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/tck-runtime.env b/system-tests/dsp-compatibility-tests/connector-under-test/tck-runtime.env new file mode 100644 index 00000000000..28b5a5d4ee7 --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/tck-runtime.env @@ -0,0 +1,26 @@ +# control plane specific config +WEB_HTTP_PORT=8080 +WEB_HTTP_PATH="/api" +WEB_HTTP_MANAGEMENT_PORT=8081 +WEB_HTTP_MANAGEMENT_PATH="/api/management/" +WEB_HTTP_PROTOCOL_PORT=8282 +WEB_HTTP_PROTOCOL_PATH="/api/v1/dsp" +WEB_HTTP_CONTROL_PORT=8083 +WEB_HTTP_CONTROL_PATH="/api/control" +WEB_HTTP_CATALOG_PORT=8084 +WEB_HTTP_CATALOG_PATH="/api/catalog" +WEB_HTTP_VERSION_PORT=8085 +WEB_HTTP_VERSION_PATH="/api/version" +EDC_API_AUTH_KEY="password" +EDC_IAM_DID_WEB_USE_HTTPS="false" +EDC_DSP_CALLBACK_ADDRESS="http://localhost:8082/api/dsp" +EDC_PARTICIPANT_ID=CONNECTOR_UNDER_TEST" +EDC_MANAGEMENT_CONTEXT_ENABLED=true + +# dataplane specific config +EDC_RUNTIME_ID="consumer-embedded-runtime" +EDC_TRANSFER_PROXY_TOKEN_VERIFIER_PUBLICKEY_ALIAS="did:web:localhost%3A7083#key-1" +EDC_TRANSFER_PROXY_TOKEN_SIGNER_PRIVATEKEY_ALIAS="did:web:localhost%3A7083-alias" +EDC_DPF_SELECTOR_URL="http://localhost:8083/api/control/v1/dataplanes" +WEB_HTTP_PUBLIC_PORT=11001 +WEB_HTTP_PUBLIC_PATH="/api/public" From eabd52c79cc8c13fcc8b665454a12c612bb5a638 Mon Sep 17 00:00:00 2001 From: Paul Latzelsperger Date: Fri, 6 Dec 2024 10:50:10 +0100 Subject: [PATCH 2/2] feat(test): add compatibility test for DSP --- .github/workflows/nightly-tests.yaml | 20 +++ .../edc/junit/annotations/NightlyTest.java | 33 ++++ .../http/message/DspRequestHandlerImpl.java | 1 - extensions/tck-extension/build.gradle.kts | 33 ---- .../ContractNegotiationRequest.java | 25 --- .../controller/TckControllerExtension.java | 52 ------ .../dsp/controller/TckWebhookController.java | 63 ------- .../edc/tck/dsp/data/DataAssembly.java | 165 ------------------ .../dsp/guard/ContractNegotiationGuard.java | 56 ------ .../ContractNegotiationTriggerRegistry.java | 26 --- .../ContractNegotiationTriggerSubscriber.java | 56 ------ .../edc/tck/dsp/guard/DelayedActionGuard.java | 113 ------------ .../edc/tck/dsp/guard/TckGuardExtension.java | 72 -------- .../eclipse/edc/tck/dsp/guard/Trigger.java | 24 --- .../tck/dsp/identity/NoopIdentityService.java | 39 ----- .../dsp/identity/TckIdentityExtension.java | 43 ----- .../edc/tck/dsp/recorder/StepRecorder.java | 61 ------- .../edc/tck/dsp/setup/TckSetupExtension.java | 57 ------ ...rg.eclipse.edc.spi.system.ServiceExtension | 17 -- .../tck/dsp/recorder/StepRecorderTest.java | 60 ------- .../build.gradle.kts | 2 + .../edc/tck/dsp/EdcCompatibilityTest.java | 44 ++++- .../eclipse/edc/tck/dsp/TckContainer.java} | 15 +- .../src/test/resources/docker.tck.properties | 55 ++++++ .../connector-under-test/build.gradle.kts | 1 + .../ContractNegotiationRequest.java | 25 --- .../controller/TckControllerExtension.java | 66 ------- .../dsp/controller/TckWebhookController.java | 63 ------- .../edc/tck/dsp/data/DataAssembly.java | 165 ------------------ .../dsp/guard/ContractNegotiationGuard.java | 56 ------ .../ContractNegotiationTriggerSubscriber.java | 56 ------ .../edc/tck/dsp/guard/DelayedActionGuard.java | 113 ------------ .../edc/tck/dsp/guard/TckGuardExtension.java | 72 -------- .../eclipse/edc/tck/dsp/guard/Trigger.java | 24 --- .../tck/dsp/identity/NoopIdentityService.java | 39 ----- .../dsp/identity/TckIdentityExtension.java | 43 ----- .../java/org/eclipse/edc/tck/dsp/missing.txt | 1 + .../edc/tck/dsp/recorder/StepRecorder.java | 61 ------- .../edc/tck/dsp/setup/TckSetupExtension.java | 57 ------ ...rg.eclipse.edc.spi.system.ServiceExtension | 18 -- .../connector-under-test/tck-runtime.env | 17 +- 41 files changed, 160 insertions(+), 1849 deletions(-) create mode 100644 .github/workflows/nightly-tests.yaml create mode 100644 core/common/junit/src/main/java/org/eclipse/edc/junit/annotations/NightlyTest.java delete mode 100644 extensions/tck-extension/build.gradle.kts delete mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/ContractNegotiationRequest.java delete mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/TckControllerExtension.java delete mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/TckWebhookController.java delete mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/data/DataAssembly.java delete mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationGuard.java delete mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerRegistry.java delete mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerSubscriber.java delete mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/DelayedActionGuard.java delete mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/TckGuardExtension.java delete mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/Trigger.java delete mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/identity/NoopIdentityService.java delete mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/identity/TckIdentityExtension.java delete mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/recorder/StepRecorder.java delete mode 100644 extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/setup/TckSetupExtension.java delete mode 100644 extensions/tck-extension/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension delete mode 100644 extensions/tck-extension/src/test/java/org/eclipse/edc/tck/dsp/recorder/StepRecorderTest.java rename system-tests/dsp-compatibility-tests/{connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerRegistry.java => compatibility-test-runner/src/test/java/org/eclipse/edc/tck/dsp/TckContainer.java} (55%) create mode 100644 system-tests/dsp-compatibility-tests/compatibility-test-runner/src/test/resources/docker.tck.properties delete mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/ContractNegotiationRequest.java delete mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/TckControllerExtension.java delete mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/TckWebhookController.java delete mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/data/DataAssembly.java delete mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationGuard.java delete mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerSubscriber.java delete mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/DelayedActionGuard.java delete mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/TckGuardExtension.java delete mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/Trigger.java delete mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/identity/NoopIdentityService.java delete mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/identity/TckIdentityExtension.java create mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/missing.txt delete mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/recorder/StepRecorder.java delete mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/setup/TckSetupExtension.java delete mode 100644 system-tests/dsp-compatibility-tests/connector-under-test/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension diff --git a/.github/workflows/nightly-tests.yaml b/.github/workflows/nightly-tests.yaml new file mode 100644 index 00000000000..c2f6709584f --- /dev/null +++ b/.github/workflows/nightly-tests.yaml @@ -0,0 +1,20 @@ +name: "Run Nightly Tests" +on: + schedule: + - "0 0 * * *" # run at 00:00 UTC + workflow_dispatch: + +concurrency: + group: ${{ github.workflow}}-${{ github.ref }} + cancel-in-progress: true + +jobs: + Run-Dsp-Compatibility-Test: + name: "Run DSP Compatibility Test" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: eclipse-edc/.github/.github/actions/setup-build@main + - name: DSP Compatibility + run: | + ./gradlew -p system-tests/dsp-compatibility-tests test -DincludeTags="NightlyTest" -PverboseTest=true \ No newline at end of file diff --git a/core/common/junit/src/main/java/org/eclipse/edc/junit/annotations/NightlyTest.java b/core/common/junit/src/main/java/org/eclipse/edc/junit/annotations/NightlyTest.java new file mode 100644 index 00000000000..6977e21e344 --- /dev/null +++ b/core/common/junit/src/main/java/org/eclipse/edc/junit/annotations/NightlyTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * + * 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: + * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation + * + */ + +package org.eclipse.edc.junit.annotations; + +import org.junit.jupiter.api.Tag; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation for Nightly testing. This is typically triggered by a nightly build action from CI. + */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@IntegrationTest +@Tag("NightlyTest") +public @interface NightlyTest { +} + diff --git a/data-protocols/dsp/dsp-http-core/src/main/java/org/eclipse/edc/protocol/dsp/http/message/DspRequestHandlerImpl.java b/data-protocols/dsp/dsp-http-core/src/main/java/org/eclipse/edc/protocol/dsp/http/message/DspRequestHandlerImpl.java index b6553e3d79a..0a192b2aa99 100644 --- a/data-protocols/dsp/dsp-http-core/src/main/java/org/eclipse/edc/protocol/dsp/http/message/DspRequestHandlerImpl.java +++ b/data-protocols/dsp/dsp-http-core/src/main/java/org/eclipse/edc/protocol/dsp/http/message/DspRequestHandlerImpl.java @@ -95,7 +95,6 @@ public Response createResou var token = request.getToken(); if (token == null) { - monitor.severe("DSP: No auth token provided - returning 401"); return unauthorized(request); } diff --git a/extensions/tck-extension/build.gradle.kts b/extensions/tck-extension/build.gradle.kts deleted file mode 100644 index 782c40c3389..00000000000 --- a/extensions/tck-extension/build.gradle.kts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -plugins { - `java-library` -} - -dependencies { - implementation(project(":spi:common:core-spi")) - implementation(project(":spi:control-plane:contract-spi")) - implementation(project(":spi:control-plane:asset-spi")) - implementation(project(":spi:control-plane:control-plane-spi")) - implementation(project(":spi:common:web-spi")) - implementation(libs.jakarta.rsApi) -} - -// If the EDC Build Plugin is used, every module gets visited during Publishing by default. -// Single modules can be excluded by setting the "publish" flag to false: - -edcBuild { - publish.set(false) -} \ No newline at end of file diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/ContractNegotiationRequest.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/ContractNegotiationRequest.java deleted file mode 100644 index 4bbd6291c76..00000000000 --- a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/ContractNegotiationRequest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2024 Metaform Systems, Inc. - * - * 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: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.controller; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Initiates a negotiation request - */ -public record ContractNegotiationRequest(@JsonProperty("providerId") String providerId, - @JsonProperty("connectorAddress") String connectorAddress, - @JsonProperty("offerId") String offerId) { -} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/TckControllerExtension.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/TckControllerExtension.java deleted file mode 100644 index 1c9162a3950..00000000000 --- a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/TckControllerExtension.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.eclipse.edc.tck.dsp.controller; - -import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService; -import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.spi.system.ServiceExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.web.spi.WebServer; -import org.eclipse.edc.web.spi.WebService; -import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; -import org.eclipse.edc.web.spi.configuration.WebServiceSettings; - -/** - * Bootstraps the TCK web hook. - */ -public class TckControllerExtension implements ServiceExtension { - private static final String NAME = "DSP TCK Controller"; - private static final String PROTOCOL = "tck"; - - private static final WebServiceSettings SETTINGS = WebServiceSettings.Builder.newInstance() - .apiConfigKey(PROTOCOL) - .contextAlias("tck") - .defaultPath("/tck") - .defaultPort(8687) - .useDefaultContext(false) - .name("Tck API") - .build(); - - - @Inject - private WebServiceConfigurer configurator; - - @Inject - private WebService webService; - - @Inject - private WebServer webServer; - - @Inject - private ContractNegotiationService negotiationService; - - @Override - public String name() { - return NAME; - } - - @Override - public void initialize(ServiceExtensionContext context) { - var config = context.getConfig(PROTOCOL); - configurator.configure(config, webServer, SETTINGS); - webService.registerResource(PROTOCOL, new TckWebhookController(negotiationService)); - } -} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/TckWebhookController.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/TckWebhookController.java deleted file mode 100644 index 8067289ca8a..00000000000 --- a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/controller/TckWebhookController.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2024 Metaform Systems, Inc. - * - * 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: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.controller; - -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractRequest; -import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractOffer; -import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; - -import java.util.List; - -import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; - -/** - * Implements TCK web hooks. - */ -@Consumes(APPLICATION_JSON) -@Produces(APPLICATION_JSON) -@Path("/negotiations") -public class TckWebhookController { - - private ContractNegotiationService negotiationService; - - public TckWebhookController(ContractNegotiationService negotiationService) { - this.negotiationService = negotiationService; - } - - @POST - @Path("requests") - public void startNegotiation(ContractNegotiationRequest request) { - - var contractOffer = ContractOffer.Builder.newInstance() - .id(request.offerId()) - .assetId(request.offerId()) - .policy(Policy.Builder.newInstance().assigner(request.providerId()).build()) - .build(); - var contractRequest = ContractRequest.Builder.newInstance() - .callbackAddresses(List.of(CallbackAddress.Builder.newInstance().uri(request.connectorAddress()).build())) - .counterPartyAddress(request.connectorAddress()) - .contractOffer(contractOffer) - .protocol("dataspace-protocol-http") - .build(); - negotiationService.initiateNegotiation(contractRequest); - System.out.println("Negotiation"); - } -} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/data/DataAssembly.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/data/DataAssembly.java deleted file mode 100644 index 3c77f079c47..00000000000 --- a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/data/DataAssembly.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (c) 2024 Metaform Systems, Inc. - * - * 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: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.data; - -import org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset; -import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationAccepted; -import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationAgreed; -import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationEvent; -import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationOffered; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition; -import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.types.domain.DataAddress; -import org.eclipse.edc.tck.dsp.guard.Trigger; -import org.eclipse.edc.tck.dsp.recorder.StepRecorder; - -import java.util.List; -import java.util.Set; -import java.util.function.Consumer; - -import static java.util.stream.Collectors.toSet; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.REQUESTED; - -/** - * Assembles data for the TCK scenarios. - */ -public class DataAssembly { - private static final Set ASSET_IDS = Set.of("ACN0101", "ACN0102", "ACN0103", "ACN0104", - "ACN0201", "ACN0202", "ACN0203", "ACN0204", "ACN0205", "ACN0206", "ACN0207", - "ACN0301", "ACN0302", "ACN0303", "ACN0304"); - - private static final String POLICY_ID = "P123"; - private static final String CONTRACT_DEFINITION_ID = "CD123"; - - public static Set createAssets() { - return ASSET_IDS.stream().map(DataAssembly::createAsset).collect(toSet()); - } - - public static Set createPolicyDefinitions() { - return Set.of(PolicyDefinition.Builder.newInstance() - .id(POLICY_ID) - .policy(Policy.Builder.newInstance().build()) - .build()); - } - - public static Set createContractDefinitions() { - return Set.of(ContractDefinition.Builder.newInstance() - .id(CONTRACT_DEFINITION_ID) - .accessPolicyId(POLICY_ID) - .contractPolicyId(POLICY_ID) - .build()); - } - - public static StepRecorder createNegotiationRecorder() { - var recorder = new StepRecorder(); - - record01NegotiationSequences(recorder); - record02NegotiationSequences(recorder); - record03NegotiationSequences(recorder); - - recordC01NegotiationSequences(recorder); - - return recorder.repeat(); - } - - private static void recordC01NegotiationSequences(StepRecorder recorder) { - } - - private static void record01NegotiationSequences(StepRecorder recorder) { - recorder.record("ACN0101", ContractNegotiation::transitionOffering); - - recorder.record("ACN0102", ContractNegotiation::transitionOffering) - .record("ACN0102", ContractNegotiation::transitionTerminating); - - recorder.record("ACN0103", ContractNegotiation::transitionOffering) - .record("ACN0103", ContractNegotiation::transitionAgreeing) - .record("ACN0103", ContractNegotiation::transitionFinalizing); - - recorder.record("ACN0104", ContractNegotiation::transitionAgreeing) - .record("ACN0104", ContractNegotiation::transitionFinalizing); - } - - private static void record02NegotiationSequences(StepRecorder recorder) { - recorder.record("ACN0201", ContractNegotiation::transitionTerminating); - - recorder.record("ACN0202", ContractNegotiation::transitionRequested); - - recorder.record("ACN0203", ContractNegotiation::transitionAgreeing); - - recorder.record("ACN0204", ContractNegotiation::transitionOffering); - - recorder.record("ACN0205", ContractNegotiation::transitionOffering); - - recorder.record("ACN0206", contractNegotiation -> { - // only transition if in requested - if (contractNegotiation.getState() == REQUESTED.code()) { - contractNegotiation.transitionOffering(); - } - }); - - recorder.record("ACN0207", ContractNegotiation::transitionAgreeing) - .record("ACN0207", ContractNegotiation::transitionTerminating); - } - - private static void record03NegotiationSequences(StepRecorder recorder) { - recorder.record("ACN0301", ContractNegotiation::transitionAgreeing) - .record("ACN0301", ContractNegotiation::transitionFinalizing); - - recorder.record("ACN0302", ContractNegotiation::transitionOffering); - recorder.record("ACN0303", ContractNegotiation::transitionOffering) - .record("ACN0303", ContractNegotiation::transitionAccepting); - - recorder.record("ACN0304", ContractNegotiation::transitionOffering); - } - - public static List> createNegotiationTriggers() { - return List.of( - createTrigger(ContractNegotiationOffered.class, "ACN0205", ContractNegotiation::transitionTerminating), - createTrigger(ContractNegotiationAccepted.class, "ACN0206", ContractNegotiation::transitionTerminating), - createTrigger(ContractNegotiationOffered.class, "C0101", contractNegotiation -> { - contractNegotiation.transitionAccepting(); - contractNegotiation.setPending(false); - }), - createTrigger(ContractNegotiationAgreed.class, "C0101", contractNegotiation -> { - contractNegotiation.transitionVerifying(); - contractNegotiation.setPending(false); - }) - - ); - } - - private static Trigger createTrigger(Class type, - String assetId, - Consumer action) { - return new Trigger<>(event -> { - if (event.getClass().equals(type)) { - return assetId.equals(((ContractNegotiationEvent) event).getLastContractOffer().getAssetId()); - } - return false; - }, action); - } - - private DataAssembly() { - } - - private static Asset createAsset(String id) { - return Asset.Builder.newInstance() - .id(id) - .dataAddress(DataAddress.Builder.newInstance().type("HTTP").build()) - .build(); - } -} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationGuard.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationGuard.java deleted file mode 100644 index 544af77b272..00000000000 --- a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationGuard.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2024 Metaform Systems, Inc. - * - * 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: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.guard; - -import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.ContractNegotiationPendingGuard; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.spi.persistence.StateEntityStore; - -import java.util.Set; -import java.util.function.Consumer; - -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation.Type.PROVIDER; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.ACCEPTING; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.AGREEING; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.FINALIZING; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.INITIAL; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.OFFERING; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.REQUESTING; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.TERMINATING; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.VERIFYING; - -/** - * Contract negotiation guard for TCK testcases. - */ -public class ContractNegotiationGuard extends DelayedActionGuard implements ContractNegotiationPendingGuard { - // the states to not apply the guard to - i.e. to allow automatic transitions by the contract negotiation manager - private static final Set PROVIDER_AUTOMATIC_STATES = Set.of( - OFFERING.code(), - AGREEING.code(), - TERMINATING.code(), - FINALIZING.code()); - - private static final Set CONSUMER_AUTOMATIC_STATES = Set.of( - INITIAL.code(), - REQUESTING.code(), - ACCEPTING.code(), - VERIFYING.code() - ); - - public ContractNegotiationGuard(Consumer action, StateEntityStore store) { - super(cn -> cn.getType() == PROVIDER ? - !PROVIDER_AUTOMATIC_STATES.contains(cn.getState()) : !CONSUMER_AUTOMATIC_STATES.contains(cn.getState()), action, store); - } -} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerRegistry.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerRegistry.java deleted file mode 100644 index d7e5e910fc6..00000000000 --- a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerRegistry.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2024 Metaform Systems, Inc. - * - * 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: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.guard; - -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; - -/** - * Registers contract negotiation triggers. - */ -public interface ContractNegotiationTriggerRegistry { - - void register(Trigger trigger); - -} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerSubscriber.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerSubscriber.java deleted file mode 100644 index 3d1dbea1c52..00000000000 --- a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerSubscriber.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2024 Metaform Systems, Inc. - * - * 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: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.guard; - -import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationEvent; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.spi.event.Event; -import org.eclipse.edc.spi.event.EventEnvelope; -import org.eclipse.edc.spi.event.EventSubscriber; -import org.eclipse.edc.spi.persistence.StateEntityStore; - -import java.util.ArrayList; -import java.util.List; - -/** - * Fires triggers based on negotiation events. - */ -public class ContractNegotiationTriggerSubscriber implements EventSubscriber, ContractNegotiationTriggerRegistry { - private StateEntityStore store; - - private List> triggers = new ArrayList<>(); - - public ContractNegotiationTriggerSubscriber(StateEntityStore store) { - this.store = store; - } - - @Override - public void register(Trigger trigger) { - triggers.add(trigger); - } - - @Override - public void on(EventEnvelope envelope) { - triggers.stream() - .filter(trigger -> trigger.predicate().test(envelope.getPayload())) - .forEach(trigger -> { - var event = (ContractNegotiationEvent) envelope.getPayload(); - var negotiation = store.findByIdAndLease(event.getContractNegotiationId()).getContent(); - trigger.action().accept(negotiation); - store.save(negotiation); - }); - } - -} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/DelayedActionGuard.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/DelayedActionGuard.java deleted file mode 100644 index 838350bf031..00000000000 --- a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/DelayedActionGuard.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2024 Metaform Systems, Inc. - * - * 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: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.guard; - -import org.eclipse.edc.spi.entity.PendingGuard; -import org.eclipse.edc.spi.entity.StatefulEntity; -import org.eclipse.edc.spi.persistence.StateEntityStore; -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.DelayQueue; -import java.util.concurrent.Delayed; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; -import java.util.function.Predicate; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -/** - * A guard that performs actions on a stateful entity. - *

- * Note this implementation is not safe to use in a clustered environment since transitions are not performed in the context of - * a command handler. - */ -public class DelayedActionGuard> implements PendingGuard { - private final Predicate filter; - private final Consumer action; - private final StateEntityStore store; - private final DelayQueue queue; - private final AtomicBoolean active = new AtomicBoolean(); - - private final ExecutorService executor = Executors.newFixedThreadPool(1); - - public DelayedActionGuard(Predicate filter, - Consumer action, - StateEntityStore store) { - this.filter = filter; - this.action = action; - this.store = store; - queue = new DelayQueue<>(); - } - - public void start() { - active.set(true); - executor.submit(() -> { - while (active.get()) { - try { - var entry = queue.poll(10, MILLISECONDS); - if (entry != null) { - action.accept(entry.entity); - entry.entity.setPending(false); - store.save(entry.entity); - } - } catch (InterruptedException e) { - e.printStackTrace(); - Thread.interrupted(); - break; - } - } - }); - } - - public void stop() { - active.set(false); - } - - @Override - public boolean test(T entity) { - if (filter.test(entity)) { - queue.put(new GuardDelay(entity)); - return true; - } - return false; - } - - protected class GuardDelay implements Delayed { - T entity; - private final long start; - - GuardDelay(T entity) { - this.entity = entity; - start = System.currentTimeMillis(); - } - - @Override - public int compareTo(@NotNull Delayed delayed) { - var millis = getDelay(MILLISECONDS) - delayed.getDelay(MILLISECONDS); - millis = Math.min(millis, 1); - millis = Math.max(millis, -1); - return (int) millis; - } - - @Override - public long getDelay(@NotNull TimeUnit timeUnit) { - return timeUnit.convert(500 - (System.currentTimeMillis() - start), MILLISECONDS); - } - - } -} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/TckGuardExtension.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/TckGuardExtension.java deleted file mode 100644 index b5779b829d3..00000000000 --- a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/TckGuardExtension.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2024 Metaform Systems, Inc. - * - * 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: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.guard; - -import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationEvent; -import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.ContractNegotiationPendingGuard; -import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.store.ContractNegotiationStore; -import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.runtime.metamodel.annotation.Provider; -import org.eclipse.edc.spi.event.EventRouter; -import org.eclipse.edc.spi.system.ServiceExtension; - -import static org.eclipse.edc.tck.dsp.data.DataAssembly.createNegotiationRecorder; -import static org.eclipse.edc.tck.dsp.data.DataAssembly.createNegotiationTriggers; - -/** - * Loads the transition guard. - */ -public class TckGuardExtension implements ServiceExtension { - private static final String NAME = "DSP TCK Guard"; - - private ContractNegotiationGuard negotiationGuard; - - @Inject - private ContractNegotiationStore store; - - @Inject - private EventRouter router; - - @Override - public String name() { - return NAME; - } - - @Provider - public ContractNegotiationPendingGuard negotiationGuard() { - var recorder = createNegotiationRecorder(); - - var registry = new ContractNegotiationTriggerSubscriber(store); - createNegotiationTriggers().forEach(registry::register); - router.register(ContractNegotiationEvent.class, registry); - - negotiationGuard = new ContractNegotiationGuard(cn -> recorder.playNext(cn.getContractOffers().get(0).getAssetId(), cn), store); - return negotiationGuard; - } - - @Override - public void prepare() { - if (negotiationGuard != null) { - negotiationGuard.start(); - } - } - - @Override - public void shutdown() { - if (negotiationGuard != null) { - negotiationGuard.stop(); - } - } -} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/Trigger.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/Trigger.java deleted file mode 100644 index 5aa8ebe3124..00000000000 --- a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/guard/Trigger.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2024 Metaform Systems, Inc. - * - * 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: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.guard; - -import java.util.function.Consumer; -import java.util.function.Predicate; - -/** - * An action that is triggered when the predicate matches a condition. - */ -public record Trigger(Predicate predicate, Consumer action) { -} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/identity/NoopIdentityService.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/identity/NoopIdentityService.java deleted file mode 100644 index f6b51f93316..00000000000 --- a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/identity/NoopIdentityService.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2024 Metaform Systems, Inc. - * - * 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: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.identity; - -import org.eclipse.edc.spi.iam.ClaimToken; -import org.eclipse.edc.spi.iam.IdentityService; -import org.eclipse.edc.spi.iam.TokenParameters; -import org.eclipse.edc.spi.iam.TokenRepresentation; -import org.eclipse.edc.spi.iam.VerificationContext; -import org.eclipse.edc.spi.result.Result; - -/** - * No-op service - */ -public class NoopIdentityService implements IdentityService { - private static final String TCK_PARTICIPANT_ID = "TCK_PARTICIPANT"; // the official TCK id - - @Override - public Result obtainClientCredentials(TokenParameters tokenParameters) { - return Result.success(TokenRepresentation.Builder.newInstance().token("1234").expiresIn(Long.MAX_VALUE).build()); - } - - @Override - public Result verifyJwtToken(TokenRepresentation tokenRepresentation, VerificationContext verificationContext) { - return Result.success(ClaimToken.Builder.newInstance().claim("client_id", TCK_PARTICIPANT_ID).build()); - } -} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/identity/TckIdentityExtension.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/identity/TckIdentityExtension.java deleted file mode 100644 index 32ce17d20e1..00000000000 --- a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/identity/TckIdentityExtension.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2024 Metaform Systems, Inc. - * - * 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: - * Metaform Systems, Inc. - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.identity; - -import org.eclipse.edc.runtime.metamodel.annotation.Provider; -import org.eclipse.edc.spi.iam.AudienceResolver; -import org.eclipse.edc.spi.iam.IdentityService; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.spi.system.ServiceExtension; - -/** - * Loads a no-op identity service. - */ -public class TckIdentityExtension implements ServiceExtension { - private static final String NAME = "DSP TCK Identity"; - - @Override - public String name() { - return NAME; - } - - @Provider - public IdentityService identityService() { - return new NoopIdentityService(); - } - - @Provider - public AudienceResolver audienceResolver() { - return m -> Result.success(m.getCounterPartyId()); - } -} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/recorder/StepRecorder.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/recorder/StepRecorder.java deleted file mode 100644 index 0a29567c21e..00000000000 --- a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/recorder/StepRecorder.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2024 Metaform Systems, Inc - * - * 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: - * Metaform Systems, Inc - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.recorder; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -/** - * Records and plays a sequence of steps. Sequences may be repeated if {@link #repeat()} is enabled. - */ -public class StepRecorder { - private boolean repeat; - private int playIndex = 0; - private Map>> sequences = new HashMap<>(); - - public synchronized StepRecorder playNext(String key, T entity) { - var sequence = sequences.get(key); - if (sequence == null) { - throw new AssertionError("No sequence found for key " + key); - } - if (sequence.isEmpty()) { - throw new IllegalStateException("No replay steps"); - } - if (playIndex >= sequence.size()) { - throw new IllegalStateException("Exceeded replay steps"); - } - sequence.get(playIndex).accept(entity); - if (repeat && playIndex == sequence.size() - 1) { - playIndex = 0; - } else { - playIndex++; - } - return this; - } - - public synchronized StepRecorder record(String key, Consumer step) { - var sequence = sequences.computeIfAbsent(key, k -> new ArrayList<>()); - sequence.add(step); - return this; - } - - public synchronized StepRecorder repeat() { - repeat = true; - return this; - } -} diff --git a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/setup/TckSetupExtension.java b/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/setup/TckSetupExtension.java deleted file mode 100644 index 44259c598c2..00000000000 --- a/extensions/tck-extension/src/main/java/org/eclipse/edc/tck/dsp/setup/TckSetupExtension.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.setup; - -import org.eclipse.edc.connector.controlplane.asset.spi.index.AssetIndex; -import org.eclipse.edc.connector.controlplane.services.spi.contractdefinition.ContractDefinitionService; -import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService; -import org.eclipse.edc.runtime.metamodel.annotation.Extension; -import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.spi.system.ServiceExtension; - -import static org.eclipse.edc.tck.dsp.data.DataAssembly.createAssets; -import static org.eclipse.edc.tck.dsp.data.DataAssembly.createContractDefinitions; -import static org.eclipse.edc.tck.dsp.data.DataAssembly.createPolicyDefinitions; -import static org.eclipse.edc.tck.dsp.setup.TckSetupExtension.NAME; - -/** - * Loads customizations and seed data for the TCK. - */ -@Extension(NAME) -public class TckSetupExtension implements ServiceExtension { - public static final String NAME = "DSP TCK Setup"; - - @Inject - private AssetIndex assetIndex; - - @Inject - private PolicyDefinitionService policyDefinitionService; - - @Inject - private ContractDefinitionService contractDefinitionService; - - @Override - public String name() { - return NAME; - } - - @Override - public void prepare() { - createAssets().forEach(asset -> assetIndex.create(asset)); - createPolicyDefinitions().forEach(definition -> policyDefinitionService.create(definition)); - createContractDefinitions().forEach(definition -> contractDefinitionService.create(definition)); - } - -} diff --git a/extensions/tck-extension/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/extensions/tck-extension/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension deleted file mode 100644 index 84672e50a18..00000000000 --- a/extensions/tck-extension/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# -# 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: -# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation -# -# -org.eclipse.edc.tck.dsp.guard.TckGuardExtension -org.eclipse.edc.tck.dsp.setup.TckSetupExtension -org.eclipse.edc.tck.dsp.identity.TckIdentityExtension -org.eclipse.edc.tck.dsp.controller.TckControllerExtension \ No newline at end of file diff --git a/extensions/tck-extension/src/test/java/org/eclipse/edc/tck/dsp/recorder/StepRecorderTest.java b/extensions/tck-extension/src/test/java/org/eclipse/edc/tck/dsp/recorder/StepRecorderTest.java deleted file mode 100644 index e6fab18c3df..00000000000 --- a/extensions/tck-extension/src/test/java/org/eclipse/edc/tck/dsp/recorder/StepRecorderTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2024 Metaform Systems, Inc - * - * 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: - * Metaform Systems, Inc - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.recorder; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -class StepRecorderTest { - private static final String KEY = "test"; - - @Test - void verifyRecordReplay() { - var recorder = new StepRecorder(); - var counter = new int[1]; - recorder.record(KEY, i -> assertThat(counter[0]++).isEqualTo(0)); - recorder.record(KEY, i -> assertThat(counter[0]++).isEqualTo(1)); - - recorder.playNext(KEY, null); - recorder.playNext(KEY, null); - assertThat(counter[0]).isEqualTo(2); - } - - @Test - void verifyRepeat() { - var recorder = new StepRecorder(); - var counter = new int[1]; - recorder.record(KEY, i -> counter[0]++); - recorder.repeat(); - - for (int i = 0; i < 4; i++) { - recorder.playNext(KEY, null); - } - assertThat(counter[0]).isEqualTo(4); - } - - @Test - void verifyNoRepeat() { - var recorder = new StepRecorder(); - var counter = new int[1]; - recorder.record(KEY, i -> counter[0]++); - - recorder.playNext(KEY, null); - assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> recorder.playNext(KEY, null)); - assertThat(counter[0]).isEqualTo(1); - } -} \ No newline at end of file diff --git a/system-tests/dsp-compatibility-tests/compatibility-test-runner/build.gradle.kts b/system-tests/dsp-compatibility-tests/compatibility-test-runner/build.gradle.kts index 32548fe1d78..a8c5ac21790 100644 --- a/system-tests/dsp-compatibility-tests/compatibility-test-runner/build.gradle.kts +++ b/system-tests/dsp-compatibility-tests/compatibility-test-runner/build.gradle.kts @@ -20,6 +20,8 @@ dependencies { testImplementation(project(":core:common:junit")) testImplementation(libs.assertj) testImplementation(libs.awaitility) + testImplementation(libs.testcontainers.junit) + runtimeOnly(libs.parsson) } edcBuild { diff --git a/system-tests/dsp-compatibility-tests/compatibility-test-runner/src/test/java/org/eclipse/edc/tck/dsp/EdcCompatibilityTest.java b/system-tests/dsp-compatibility-tests/compatibility-test-runner/src/test/java/org/eclipse/edc/tck/dsp/EdcCompatibilityTest.java index 5de8758d308..622fe570988 100644 --- a/system-tests/dsp-compatibility-tests/compatibility-test-runner/src/test/java/org/eclipse/edc/tck/dsp/EdcCompatibilityTest.java +++ b/system-tests/dsp-compatibility-tests/compatibility-test-runner/src/test/java/org/eclipse/edc/tck/dsp/EdcCompatibilityTest.java @@ -14,21 +14,40 @@ package org.eclipse.edc.tck.dsp; +import org.eclipse.edc.junit.annotations.NightlyTest; import org.eclipse.edc.junit.extensions.EmbeddedRuntime; import org.eclipse.edc.junit.extensions.RuntimeExtension; import org.eclipse.edc.junit.extensions.RuntimePerClassExtension; +import org.eclipse.edc.junit.testfixtures.TestUtils; +import org.eclipse.edc.spi.monitor.ConsoleMonitor; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.SelinuxContext; +import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; +import org.testcontainers.junit.jupiter.Testcontainers; +import java.nio.file.Path; import java.util.HashMap; -import java.util.concurrent.CountDownLatch; import static org.eclipse.edc.util.io.Ports.getFreePort; +@Disabled(value = "Need to disable this test until the connector is 100% compliant with DSP") +@NightlyTest +@Testcontainers public class EdcCompatibilityTest { + private static final GenericContainer TCK_CONTAINER = new TckContainer<>("eclipsedataspacetck/dsp-tck-runtime:latest"); + + private static String resource(String s) { + return Path.of(TestUtils.getResource("docker.tck.properties")).toString(); + } + @RegisterExtension - protected RuntimeExtension runtime = + protected static RuntimeExtension runtime = new RuntimePerClassExtension(new EmbeddedRuntime("CUnT", new HashMap<>() { { @@ -41,20 +60,29 @@ public class EdcCompatibilityTest { put("web.http.control.path", "/api/control"); put("web.http.management.port", "8081"); put("web.http.management.path", "/api/management"); - put("web.http.protocol.port", "8282"); - put("web.http.protocol.path", "/api/v1/dsp"); // expected by TCK + put("web.http.protocol.port", "8282"); // this must match the configured connector url in resources/docker.tck.properties + put("web.http.protocol.path", "/api/dsp"); // this must match the configured connector url in resources/docker.tck.properties put("web.api.auth.key", "password"); - put("edc.dsp.callback.address", "http://localhost:8282/api/v1/dsp"); + put("edc.dsp.callback.address", "http://host.docker.internal:8282/api/dsp"); // host.docker.internal is required by the container to communicate with the host put("edc.management.context.enabled", "true"); + put("edc.hostname", "host.docker.internal"); + put("edc.component.id", "DSP-compatibility-test"); } }, ":system-tests:dsp-compatibility-tests:connector-under-test" )); + @Timeout(60) @Test - void assertRuntimeReady() throws InterruptedException { - var l = new CountDownLatch(1); - l.await(); + void assertDspCompatibility() { + // pipe the docker container's log to this console at the INFO level + var monitor = new ConsoleMonitor(">>> TCK Runtime (Docker)", ConsoleMonitor.Level.INFO, true); + TCK_CONTAINER.addFileSystemBind(resource("docker.tck.properties"), "/etc/tck/config.properties", BindMode.READ_ONLY, SelinuxContext.SINGLE); + TCK_CONTAINER.withLogConsumer(outputFrame -> monitor.info(outputFrame.getUtf8String())); + TCK_CONTAINER.waitingFor(new LogMessageWaitStrategy().withRegEx(".*Test run complete.*")); + TCK_CONTAINER.start(); + + // todo: obtain test report from the container } } diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerRegistry.java b/system-tests/dsp-compatibility-tests/compatibility-test-runner/src/test/java/org/eclipse/edc/tck/dsp/TckContainer.java similarity index 55% rename from system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerRegistry.java rename to system-tests/dsp-compatibility-tests/compatibility-test-runner/src/test/java/org/eclipse/edc/tck/dsp/TckContainer.java index e516813104c..a8c041fb453 100644 --- a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerRegistry.java +++ b/system-tests/dsp-compatibility-tests/compatibility-test-runner/src/test/java/org/eclipse/edc/tck/dsp/TckContainer.java @@ -12,15 +12,14 @@ * */ -package org.eclipse.edc.tck.dsp.guard; +package org.eclipse.edc.tck.dsp; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; +import org.testcontainers.containers.GenericContainer; -/** - * Registers contract negotiation triggers. - */ -public interface ContractNegotiationTriggerRegistry { - - void register(Trigger trigger); +public class TckContainer> extends GenericContainer { + public TckContainer(String imageName) { + super(imageName); + addFixedExposedPort(8083, 8083); // TCK will use this as callback address - must be fixed! + } } diff --git a/system-tests/dsp-compatibility-tests/compatibility-test-runner/src/test/resources/docker.tck.properties b/system-tests/dsp-compatibility-tests/compatibility-test-runner/src/test/resources/docker.tck.properties new file mode 100644 index 00000000000..ac9a013c499 --- /dev/null +++ b/system-tests/dsp-compatibility-tests/compatibility-test-runner/src/test/resources/docker.tck.properties @@ -0,0 +1,55 @@ +# +# Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# +# 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: +# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation +# +# +# Contains sample configuration options +dataspacetck.debug=true +dataspacetck.dsp.local.connector=false +dataspacetck.dsp.connector.agent.id=CONNECTOR_UNDER_TEST +dataspacetck.dsp.connector.http.url=http://host.docker.internal:8282/api/dsp/ +dataspacetck.dsp.connector.http.headers.authorization="{\"region\": \"any\", \"audience\": \"any\", \"clientId\":\"any\"}" +dataspacetck.dsp.connector.negotiation.initiate.url=http://host.docker.internal:8687/tck/negotiations/requests +dataspacetck.dsp.default.wait=10000000 +# must be 0.0.0.0 for docker inet binding to work +dataspacetck.callback.address=http://0.0.0.0:8083 +# Sets the dataset and offer ids to use for contract negotiation scenarios +CN_01_01_DATASETID=ACN0101 +CN_01_01_OFFERID=CD123:ACN0101:456 +CN_01_02_DATASETID=ACN0102 +CN_01_02_OFFERID=CD123:ACN\ + 0102:456 +CN_01_03_DATASETID=ACN0103 +CN_01_03_OFFERID=CD123:ACN0103:456 +CN_01_04_DATASETID=ACN0104 +CN_01_04_OFFERID=CD123:ACN0104:456 +CN_02_01_DATASETID=ACN0201 +CN_02_01_OFFERID=CD123:ACN0201:456 +CN_02_02_DATASETID=ACN0202 +CN_02_02_OFFERID=CD123:ACN0202:456 +CN_02_03_DATASETID=ACN0203 +CN_02_03_OFFERID=CD123:ACN0203:456 +CN_02_04_DATASETID=ACN0204 +CN_02_04_OFFERID=CD123:ACN0204:456 +CN_02_05_DATASETID=ACN0205 +CN_02_05_OFFERID=CD123:ACN0205:456 +CN_02_06_DATASETID=ACN0206 +CN_02_06_OFFERID=CD123:ACN0206:456 +CN_02_07_DATASETID=ACN0207 +CN_02_07_OFFERID=CD123:ACN0207:456 +CN_03_01_DATASETID=ACN0301 +CN_03_01_OFFERID=CD123:ACN0301:456 +CN_03_02_DATASETID=ACN0302 +CN_03_02_OFFERID=CD123:ACN0302:456 +CN_03_03_DATASETID=ACN0303 +CN_03_03_OFFERID=CD123:ACN0303:456 +CN_03_04_DATASETID=ACN0304 +CN_03_04_OFFERID=CD123:ACN0304:456 diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/build.gradle.kts b/system-tests/dsp-compatibility-tests/connector-under-test/build.gradle.kts index 569bab553c6..8e61b408813 100644 --- a/system-tests/dsp-compatibility-tests/connector-under-test/build.gradle.kts +++ b/system-tests/dsp-compatibility-tests/connector-under-test/build.gradle.kts @@ -19,4 +19,5 @@ plugins { dependencies { api(project(":dist:bom:controlplane-base-bom")) runtimeOnly(project(":extensions:tck-extension")) + runtimeOnly(libs.parsson) } \ No newline at end of file diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/ContractNegotiationRequest.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/ContractNegotiationRequest.java deleted file mode 100644 index 6a0f4173167..00000000000 --- a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/ContractNegotiationRequest.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.controller; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Initiates a negotiation request - */ -public record ContractNegotiationRequest(@JsonProperty("providerId") String providerId, - @JsonProperty("connectorAddress") String connectorAddress, - @JsonProperty("offerId") String offerId) { -} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/TckControllerExtension.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/TckControllerExtension.java deleted file mode 100644 index c4506b2b0f7..00000000000 --- a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/TckControllerExtension.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.controller; - -import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService; -import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.spi.system.ServiceExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.web.spi.WebServer; -import org.eclipse.edc.web.spi.WebService; -import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; -import org.eclipse.edc.web.spi.configuration.WebServiceSettings; - -/** - * Bootstraps the TCK web hook. - */ -public class TckControllerExtension implements ServiceExtension { - private static final String NAME = "DSP TCK Controller"; - private static final String PROTOCOL = "tck"; - - private static final WebServiceSettings SETTINGS = WebServiceSettings.Builder.newInstance() - .apiConfigKey(PROTOCOL) - .contextAlias("tck") - .defaultPath("/tck") - .defaultPort(8687) - .useDefaultContext(false) - .name("Tck API") - .build(); - - - @Inject - private WebServiceConfigurer configurator; - - @Inject - private WebService webService; - - @Inject - private WebServer webServer; - - @Inject - private ContractNegotiationService negotiationService; - - @Override - public String name() { - return NAME; - } - - @Override - public void initialize(ServiceExtensionContext context) { - var config = context.getConfig(PROTOCOL); - configurator.configure(config, webServer, SETTINGS); - webService.registerResource(PROTOCOL, new TckWebhookController(negotiationService)); - } -} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/TckWebhookController.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/TckWebhookController.java deleted file mode 100644 index 96851691343..00000000000 --- a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/controller/TckWebhookController.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.controller; - -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractRequest; -import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractOffer; -import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; - -import java.util.List; - -import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; - -/** - * Implements TCK web hooks. - */ -@Consumes(APPLICATION_JSON) -@Produces(APPLICATION_JSON) -@Path("/negotiations") -public class TckWebhookController { - - private final ContractNegotiationService negotiationService; - - public TckWebhookController(ContractNegotiationService negotiationService) { - this.negotiationService = negotiationService; - } - - @POST - @Path("requests") - public void startNegotiation(ContractNegotiationRequest request) { - - var contractOffer = ContractOffer.Builder.newInstance() - .id(request.offerId()) - .assetId(request.offerId()) - .policy(Policy.Builder.newInstance().assigner(request.providerId()).build()) - .build(); - var contractRequest = ContractRequest.Builder.newInstance() - .callbackAddresses(List.of(CallbackAddress.Builder.newInstance().uri(request.connectorAddress()).build())) - .counterPartyAddress(request.connectorAddress()) - .contractOffer(contractOffer) - .protocol("dataspace-protocol-http") - .build(); - negotiationService.initiateNegotiation(contractRequest); - System.out.println("Negotiation"); - } -} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/data/DataAssembly.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/data/DataAssembly.java deleted file mode 100644 index 0530a3e3569..00000000000 --- a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/data/DataAssembly.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.data; - -import org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset; -import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationAccepted; -import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationAgreed; -import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationEvent; -import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationOffered; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition; -import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.types.domain.DataAddress; -import org.eclipse.edc.tck.dsp.guard.Trigger; -import org.eclipse.edc.tck.dsp.recorder.StepRecorder; - -import java.util.List; -import java.util.Set; -import java.util.function.Consumer; - -import static java.util.stream.Collectors.toSet; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.REQUESTED; - -/** - * Assembles data for the TCK scenarios. - */ -public class DataAssembly { - private static final Set ASSET_IDS = Set.of("ACN0101", "ACN0102", "ACN0103", "ACN0104", - "ACN0201", "ACN0202", "ACN0203", "ACN0204", "ACN0205", "ACN0206", "ACN0207", - "ACN0301", "ACN0302", "ACN0303", "ACN0304"); - - private static final String POLICY_ID = "P123"; - private static final String CONTRACT_DEFINITION_ID = "CD123"; - - public static Set createAssets() { - return ASSET_IDS.stream().map(DataAssembly::createAsset).collect(toSet()); - } - - public static Set createPolicyDefinitions() { - return Set.of(PolicyDefinition.Builder.newInstance() - .id(POLICY_ID) - .policy(Policy.Builder.newInstance().build()) - .build()); - } - - public static Set createContractDefinitions() { - return Set.of(ContractDefinition.Builder.newInstance() - .id(CONTRACT_DEFINITION_ID) - .accessPolicyId(POLICY_ID) - .contractPolicyId(POLICY_ID) - .build()); - } - - public static StepRecorder createNegotiationRecorder() { - var recorder = new StepRecorder(); - - record01NegotiationSequences(recorder); - record02NegotiationSequences(recorder); - record03NegotiationSequences(recorder); - - recordC01NegotiationSequences(recorder); - - return recorder.repeat(); - } - - private static void recordC01NegotiationSequences(StepRecorder recorder) { - } - - private static void record01NegotiationSequences(StepRecorder recorder) { - recorder.record("ACN0101", ContractNegotiation::transitionOffering); - - recorder.record("ACN0102", ContractNegotiation::transitionOffering) - .record("ACN0102", ContractNegotiation::transitionTerminating); - - recorder.record("ACN0103", ContractNegotiation::transitionOffering) - .record("ACN0103", ContractNegotiation::transitionAgreeing) - .record("ACN0103", ContractNegotiation::transitionFinalizing); - - recorder.record("ACN0104", ContractNegotiation::transitionAgreeing) - .record("ACN0104", ContractNegotiation::transitionFinalizing); - } - - private static void record02NegotiationSequences(StepRecorder recorder) { - recorder.record("ACN0201", ContractNegotiation::transitionTerminating); - - recorder.record("ACN0202", ContractNegotiation::transitionRequested); - - recorder.record("ACN0203", ContractNegotiation::transitionAgreeing); - - recorder.record("ACN0204", ContractNegotiation::transitionOffering); - - recorder.record("ACN0205", ContractNegotiation::transitionOffering); - - recorder.record("ACN0206", contractNegotiation -> { - // only transition if in requested - if (contractNegotiation.getState() == REQUESTED.code()) { - contractNegotiation.transitionOffering(); - } - }); - - recorder.record("ACN0207", ContractNegotiation::transitionAgreeing) - .record("ACN0207", ContractNegotiation::transitionTerminating); - } - - private static void record03NegotiationSequences(StepRecorder recorder) { - recorder.record("ACN0301", ContractNegotiation::transitionAgreeing) - .record("ACN0301", ContractNegotiation::transitionFinalizing); - - recorder.record("ACN0302", ContractNegotiation::transitionOffering); - recorder.record("ACN0303", ContractNegotiation::transitionOffering) - .record("ACN0303", ContractNegotiation::transitionAccepting); - - recorder.record("ACN0304", ContractNegotiation::transitionOffering); - } - - public static List> createNegotiationTriggers() { - return List.of( - createTrigger(ContractNegotiationOffered.class, "ACN0205", ContractNegotiation::transitionTerminating), - createTrigger(ContractNegotiationAccepted.class, "ACN0206", ContractNegotiation::transitionTerminating), - createTrigger(ContractNegotiationOffered.class, "C0101", contractNegotiation -> { - contractNegotiation.transitionAccepting(); - contractNegotiation.setPending(false); - }), - createTrigger(ContractNegotiationAgreed.class, "C0101", contractNegotiation -> { - contractNegotiation.transitionVerifying(); - contractNegotiation.setPending(false); - }) - - ); - } - - private static Trigger createTrigger(Class type, - String assetId, - Consumer action) { - return new Trigger<>(event -> { - if (event.getClass().equals(type)) { - return assetId.equals(((ContractNegotiationEvent) event).getLastContractOffer().getAssetId()); - } - return false; - }, action); - } - - private DataAssembly() { - } - - private static Asset createAsset(String id) { - return Asset.Builder.newInstance() - .id(id) - .dataAddress(DataAddress.Builder.newInstance().type("HTTP").build()) - .build(); - } -} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationGuard.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationGuard.java deleted file mode 100644 index 027513fc920..00000000000 --- a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationGuard.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.guard; - -import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.ContractNegotiationPendingGuard; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.spi.persistence.StateEntityStore; - -import java.util.Set; -import java.util.function.Consumer; - -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation.Type.PROVIDER; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.ACCEPTING; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.AGREEING; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.FINALIZING; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.INITIAL; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.OFFERING; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.REQUESTING; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.TERMINATING; -import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.VERIFYING; - -/** - * Contract negotiation guard for TCK testcases. - */ -public class ContractNegotiationGuard extends DelayedActionGuard implements ContractNegotiationPendingGuard { - // the states to not apply the guard to - i.e. to allow automatic transitions by the contract negotiation manager - private static final Set PROVIDER_AUTOMATIC_STATES = Set.of( - OFFERING.code(), - AGREEING.code(), - TERMINATING.code(), - FINALIZING.code()); - - private static final Set CONSUMER_AUTOMATIC_STATES = Set.of( - INITIAL.code(), - REQUESTING.code(), - ACCEPTING.code(), - VERIFYING.code() - ); - - public ContractNegotiationGuard(Consumer action, StateEntityStore store) { - super(cn -> cn.getType() == PROVIDER ? - !PROVIDER_AUTOMATIC_STATES.contains(cn.getState()) : !CONSUMER_AUTOMATIC_STATES.contains(cn.getState()), action, store); - } -} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerSubscriber.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerSubscriber.java deleted file mode 100644 index b508c547720..00000000000 --- a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/ContractNegotiationTriggerSubscriber.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.guard; - -import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationEvent; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.spi.event.Event; -import org.eclipse.edc.spi.event.EventEnvelope; -import org.eclipse.edc.spi.event.EventSubscriber; -import org.eclipse.edc.spi.persistence.StateEntityStore; - -import java.util.ArrayList; -import java.util.List; - -/** - * Fires triggers based on negotiation events. - */ -public class ContractNegotiationTriggerSubscriber implements EventSubscriber, ContractNegotiationTriggerRegistry { - private final StateEntityStore store; - - private final List> triggers = new ArrayList<>(); - - public ContractNegotiationTriggerSubscriber(StateEntityStore store) { - this.store = store; - } - - @Override - public void register(Trigger trigger) { - triggers.add(trigger); - } - - @Override - public void on(EventEnvelope envelope) { - triggers.stream() - .filter(trigger -> trigger.predicate().test(envelope.getPayload())) - .forEach(trigger -> { - var event = (ContractNegotiationEvent) envelope.getPayload(); - var negotiation = store.findByIdAndLease(event.getContractNegotiationId()).getContent(); - trigger.action().accept(negotiation); - store.save(negotiation); - }); - } - -} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/DelayedActionGuard.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/DelayedActionGuard.java deleted file mode 100644 index e9e9a89dec2..00000000000 --- a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/DelayedActionGuard.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.guard; - -import org.eclipse.edc.spi.entity.PendingGuard; -import org.eclipse.edc.spi.entity.StatefulEntity; -import org.eclipse.edc.spi.persistence.StateEntityStore; -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.DelayQueue; -import java.util.concurrent.Delayed; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; -import java.util.function.Predicate; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -/** - * A guard that performs actions on a stateful entity. - *

- * Note this implementation is not safe to use in a clustered environment since transitions are not performed in the context of - * a command handler. - */ -public class DelayedActionGuard> implements PendingGuard { - private final Predicate filter; - private final Consumer action; - private final StateEntityStore store; - private final DelayQueue queue; - private final AtomicBoolean active = new AtomicBoolean(); - - private final ExecutorService executor = Executors.newFixedThreadPool(1); - - public DelayedActionGuard(Predicate filter, - Consumer action, - StateEntityStore store) { - this.filter = filter; - this.action = action; - this.store = store; - queue = new DelayQueue<>(); - } - - public void start() { - active.set(true); - executor.submit(() -> { - while (active.get()) { - try { - var entry = queue.poll(10, MILLISECONDS); - if (entry != null) { - action.accept(entry.entity); - entry.entity.setPending(false); - store.save(entry.entity); - } - } catch (InterruptedException e) { - e.printStackTrace(); - Thread.interrupted(); - break; - } - } - }); - } - - public void stop() { - active.set(false); - } - - @Override - public boolean test(T entity) { - if (filter.test(entity)) { - queue.put(new GuardDelay(entity)); - return true; - } - return false; - } - - protected class GuardDelay implements Delayed { - T entity; - private final long start; - - GuardDelay(T entity) { - this.entity = entity; - start = System.currentTimeMillis(); - } - - @Override - public int compareTo(@NotNull Delayed delayed) { - var millis = getDelay(MILLISECONDS) - delayed.getDelay(MILLISECONDS); - millis = Math.min(millis, 1); - millis = Math.max(millis, -1); - return (int) millis; - } - - @Override - public long getDelay(@NotNull TimeUnit timeUnit) { - return timeUnit.convert(500 - (System.currentTimeMillis() - start), MILLISECONDS); - } - - } -} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/TckGuardExtension.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/TckGuardExtension.java deleted file mode 100644 index 3a2f1a00169..00000000000 --- a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/TckGuardExtension.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.guard; - -import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationEvent; -import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.ContractNegotiationPendingGuard; -import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.store.ContractNegotiationStore; -import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.runtime.metamodel.annotation.Provider; -import org.eclipse.edc.spi.event.EventRouter; -import org.eclipse.edc.spi.system.ServiceExtension; - -import static org.eclipse.edc.tck.dsp.data.DataAssembly.createNegotiationRecorder; -import static org.eclipse.edc.tck.dsp.data.DataAssembly.createNegotiationTriggers; - -/** - * Loads the transition guard. - */ -public class TckGuardExtension implements ServiceExtension { - private static final String NAME = "DSP TCK Guard"; - - private org.eclipse.edc.tck.dsp.guard.ContractNegotiationGuard negotiationGuard; - - @Inject - private ContractNegotiationStore store; - - @Inject - private EventRouter router; - - @Override - public String name() { - return NAME; - } - - @Provider - public ContractNegotiationPendingGuard negotiationGuard() { - var recorder = createNegotiationRecorder(); - - var registry = new org.eclipse.edc.tck.dsp.guard.ContractNegotiationTriggerSubscriber(store); - createNegotiationTriggers().forEach(registry::register); - router.register(ContractNegotiationEvent.class, registry); - - negotiationGuard = new org.eclipse.edc.tck.dsp.guard.ContractNegotiationGuard(cn -> recorder.playNext(cn.getContractOffers().get(0).getAssetId(), cn), store); - return negotiationGuard; - } - - @Override - public void prepare() { - if (negotiationGuard != null) { - negotiationGuard.start(); - } - } - - @Override - public void shutdown() { - if (negotiationGuard != null) { - negotiationGuard.stop(); - } - } -} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/Trigger.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/Trigger.java deleted file mode 100644 index 2dfbad828d2..00000000000 --- a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/guard/Trigger.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.guard; - -import java.util.function.Consumer; -import java.util.function.Predicate; - -/** - * An action that is triggered when the predicate matches a condition. - */ -public record Trigger(Predicate predicate, Consumer action) { -} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/identity/NoopIdentityService.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/identity/NoopIdentityService.java deleted file mode 100644 index 134375156ca..00000000000 --- a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/identity/NoopIdentityService.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.identity; - -import org.eclipse.edc.spi.iam.ClaimToken; -import org.eclipse.edc.spi.iam.IdentityService; -import org.eclipse.edc.spi.iam.TokenParameters; -import org.eclipse.edc.spi.iam.TokenRepresentation; -import org.eclipse.edc.spi.iam.VerificationContext; -import org.eclipse.edc.spi.result.Result; - -/** - * No-op service - */ -public class NoopIdentityService implements IdentityService { - private static final String TCK_PARTICIPANT_ID = "TCK_PARTICIPANT"; // the official TCK id - - @Override - public Result obtainClientCredentials(TokenParameters tokenParameters) { - return Result.success(TokenRepresentation.Builder.newInstance().token("1234").expiresIn(Long.MAX_VALUE).build()); - } - - @Override - public Result verifyJwtToken(TokenRepresentation tokenRepresentation, VerificationContext verificationContext) { - return Result.success(ClaimToken.Builder.newInstance().claim("client_id", TCK_PARTICIPANT_ID).build()); - } -} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/identity/TckIdentityExtension.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/identity/TckIdentityExtension.java deleted file mode 100644 index 88ed10b1797..00000000000 --- a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/identity/TckIdentityExtension.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.identity; - -import org.eclipse.edc.runtime.metamodel.annotation.Provider; -import org.eclipse.edc.spi.iam.AudienceResolver; -import org.eclipse.edc.spi.iam.IdentityService; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.spi.system.ServiceExtension; - -/** - * Loads a no-op identity service. - */ -public class TckIdentityExtension implements ServiceExtension { - private static final String NAME = "DSP TCK Identity"; - - @Override - public String name() { - return NAME; - } - - @Provider - public IdentityService identityService() { - return new NoopIdentityService(); - } - - @Provider - public AudienceResolver audienceResolver() { - return m -> Result.success(m.getCounterPartyId()); - } -} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/missing.txt b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/missing.txt new file mode 100644 index 00000000000..bc87f648dda --- /dev/null +++ b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/missing.txt @@ -0,0 +1 @@ +the test harness extension should go here \ No newline at end of file diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/recorder/StepRecorder.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/recorder/StepRecorder.java deleted file mode 100644 index 41d860befe8..00000000000 --- a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/recorder/StepRecorder.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.recorder; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -/** - * Records and plays a sequence of steps. Sequences may be repeated if {@link #repeat()} is enabled. - */ -public class StepRecorder { - private boolean repeat; - private int playIndex = 0; - private final Map>> sequences = new HashMap<>(); - - public synchronized StepRecorder playNext(String key, T entity) { - var sequence = sequences.get(key); - if (sequence == null) { - throw new AssertionError("No sequence found for key " + key); - } - if (sequence.isEmpty()) { - throw new IllegalStateException("No replay steps"); - } - if (playIndex >= sequence.size()) { - throw new IllegalStateException("Exceeded replay steps"); - } - sequence.get(playIndex).accept(entity); - if (repeat && playIndex == sequence.size() - 1) { - playIndex = 0; - } else { - playIndex++; - } - return this; - } - - public synchronized StepRecorder record(String key, Consumer step) { - var sequence = sequences.computeIfAbsent(key, k -> new ArrayList<>()); - sequence.add(step); - return this; - } - - public synchronized StepRecorder repeat() { - repeat = true; - return this; - } -} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/setup/TckSetupExtension.java b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/setup/TckSetupExtension.java deleted file mode 100644 index 44259c598c2..00000000000 --- a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/java/org/eclipse/edc/tck/dsp/setup/TckSetupExtension.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * 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: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.tck.dsp.setup; - -import org.eclipse.edc.connector.controlplane.asset.spi.index.AssetIndex; -import org.eclipse.edc.connector.controlplane.services.spi.contractdefinition.ContractDefinitionService; -import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService; -import org.eclipse.edc.runtime.metamodel.annotation.Extension; -import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.spi.system.ServiceExtension; - -import static org.eclipse.edc.tck.dsp.data.DataAssembly.createAssets; -import static org.eclipse.edc.tck.dsp.data.DataAssembly.createContractDefinitions; -import static org.eclipse.edc.tck.dsp.data.DataAssembly.createPolicyDefinitions; -import static org.eclipse.edc.tck.dsp.setup.TckSetupExtension.NAME; - -/** - * Loads customizations and seed data for the TCK. - */ -@Extension(NAME) -public class TckSetupExtension implements ServiceExtension { - public static final String NAME = "DSP TCK Setup"; - - @Inject - private AssetIndex assetIndex; - - @Inject - private PolicyDefinitionService policyDefinitionService; - - @Inject - private ContractDefinitionService contractDefinitionService; - - @Override - public String name() { - return NAME; - } - - @Override - public void prepare() { - createAssets().forEach(asset -> assetIndex.create(asset)); - createPolicyDefinitions().forEach(definition -> policyDefinitionService.create(definition)); - createContractDefinitions().forEach(definition -> contractDefinitionService.create(definition)); - } - -} diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/system-tests/dsp-compatibility-tests/connector-under-test/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension deleted file mode 100644 index e7ca6341b1c..00000000000 --- a/system-tests/dsp-compatibility-tests/connector-under-test/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# -# 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: -# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation -# -# - -org.eclipse.edc.tck.dsp.guard.TckGuardExtension -org.eclipse.edc.tck.dsp.setup.TckSetupExtension -org.eclipse.edc.tck.dsp.identity.TckIdentityExtension -org.eclipse.edc.tck.dsp.controller.TckControllerExtension \ No newline at end of file diff --git a/system-tests/dsp-compatibility-tests/connector-under-test/tck-runtime.env b/system-tests/dsp-compatibility-tests/connector-under-test/tck-runtime.env index 28b5a5d4ee7..4d78a6fa08c 100644 --- a/system-tests/dsp-compatibility-tests/connector-under-test/tck-runtime.env +++ b/system-tests/dsp-compatibility-tests/connector-under-test/tck-runtime.env @@ -3,13 +3,13 @@ WEB_HTTP_PORT=8080 WEB_HTTP_PATH="/api" WEB_HTTP_MANAGEMENT_PORT=8081 WEB_HTTP_MANAGEMENT_PATH="/api/management/" -WEB_HTTP_PROTOCOL_PORT=8282 -WEB_HTTP_PROTOCOL_PATH="/api/v1/dsp" -WEB_HTTP_CONTROL_PORT=8083 +WEB_HTTP_PROTOCOL_PORT=8082 +WEB_HTTP_PROTOCOL_PATH="/api/dsp" +WEB_HTTP_CONTROL_PORT=8183 WEB_HTTP_CONTROL_PATH="/api/control" -WEB_HTTP_CATALOG_PORT=8084 +WEB_HTTP_CATALOG_PORT=8184 WEB_HTTP_CATALOG_PATH="/api/catalog" -WEB_HTTP_VERSION_PORT=8085 +WEB_HTTP_VERSION_PORT=8185 WEB_HTTP_VERSION_PATH="/api/version" EDC_API_AUTH_KEY="password" EDC_IAM_DID_WEB_USE_HTTPS="false" @@ -17,10 +17,3 @@ EDC_DSP_CALLBACK_ADDRESS="http://localhost:8082/api/dsp" EDC_PARTICIPANT_ID=CONNECTOR_UNDER_TEST" EDC_MANAGEMENT_CONTEXT_ENABLED=true -# dataplane specific config -EDC_RUNTIME_ID="consumer-embedded-runtime" -EDC_TRANSFER_PROXY_TOKEN_VERIFIER_PUBLICKEY_ALIAS="did:web:localhost%3A7083#key-1" -EDC_TRANSFER_PROXY_TOKEN_SIGNER_PRIVATEKEY_ALIAS="did:web:localhost%3A7083-alias" -EDC_DPF_SELECTOR_URL="http://localhost:8083/api/control/v1/dataplanes" -WEB_HTTP_PUBLIC_PORT=11001 -WEB_HTTP_PUBLIC_PATH="/api/public"