diff --git a/.github/workflows/run-e2e-tests.yml b/.github/workflows/run-e2e-tests.yml index 31eff0bff..69c6a9ebc 100644 --- a/.github/workflows/run-e2e-tests.yml +++ b/.github/workflows/run-e2e-tests.yml @@ -59,6 +59,10 @@ jobs: uses: helm/kind-action@v1.14.0 with: cluster_name: dcp-demo + # maps host ports 80/443 into the node container; together with the Traefik + # hostPort settings in values.yaml this exposes the gateway on localhost + # without a port-forward + config: kind-config.yaml - name: "Load runtime images into KinD" run: | @@ -84,9 +88,6 @@ jobs: # install Gateway API CRDs kubectl apply --server-side --force-conflicts -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.1/standard-install.yaml - # forward port 80 -> 8080 - kubectl -n traefik port-forward svc/traefik 8080:80 & - sleep 5 # to be safe - name: "Deploy MVD common resources" diff --git a/README.md b/README.md index a7aa823f5..0673038e4 100644 --- a/README.md +++ b/README.md @@ -237,9 +237,14 @@ documentation for more information. ```shell # Create the cluster -kind create cluster -n mvd +kind create cluster -n mvd --config kind-config.yaml ``` +> [`kind-config.yaml`](kind-config.yaml) maps host ports 80/443 into the node container. Together with the `hostPort` +> settings in the Traefik values ([`values.yaml`](values.yaml)) this makes the gateway reachable on `http://localhost` +> without any port-forwarding. Port mappings are fixed at cluster creation — if you created the cluster without this +> config, recreate it. Host ports 80/443 must be free when the cluster is created. + ### 4.2 Deploy the MVD components The following commands deploy the MVD components to the cluster. @@ -290,11 +295,11 @@ kubectl wait -A \ --selector=type=edc-job \ --for=condition=complete job --all \ --timeout=90s - -# forward port 80 (might require sudo) -kubectl port-forward svc/traefik 80:80 -n traefik ``` +No port-forwarding is needed: Traefik binds ports 80/443 directly on the KinD node, and the cluster's port mappings +(see [`kind-config.yaml`](kind-config.yaml)) expose them on `http://localhost`. + Once all jobs have finished, type `kubectl get pods -A` and verify the output: ```shell diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index edcb81407..06be26fd0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ format.version = "1.1" [versions] awaitility = "4.3.0" -dataplane-sdk = "0.0.10-SNAPSHOT" +dataplane-sdk = "1.0.0" edc = "0.19.0-SNAPSHOT" edc-build = "1.5.2" jackson = "2.22.0" @@ -56,7 +56,8 @@ jakarta-json-api = { module = "jakarta.json:jakarta.json-api", version.ref = "ja tink = { module = "com.google.crypto.tink:tink", version = "1.21.0" } jackson-datatype-jakarta-jsonp = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jakarta-jsonp", version.ref = "jackson" } parsson = { module = "org.eclipse.parsson:parsson", version.ref = "parsson" } -dataplane-sdk = { module = "org.eclipse.dataplane-core:dataplane-sdk", version.ref = "dataplane-sdk" } +dataplane-sdk = { module = "org.eclipse.dataplane-core:dataplane-sdk-core", version.ref = "dataplane-sdk" } +dataplane-sdk-jakarta-ee = { module = "org.eclipse.dataplane-core:dataplane-sdk-jakarta-ee", version.ref = "dataplane-sdk" } nimbus-jwt = { module = "com.nimbusds:nimbus-jose-jwt", version.ref = "nimbus" } # BOM modules diff --git a/kind-config.yaml b/kind-config.yaml new file mode 100644 index 000000000..c75b66c2d --- /dev/null +++ b/kind-config.yaml @@ -0,0 +1,30 @@ +# +# Copyright (c) 2026 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 +# + +# KinD cluster config that maps host ports 80/443 into the (single) node container. +# Together with the hostPort settings in the Traefik values (values.yaml), this makes +# the gateway reachable on http://localhost without any kubectl port-forward. +# +# Note: port mappings are fixed at cluster creation - to change them, the cluster +# must be recreated. +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: + - role: control-plane + extraPortMappings: + - containerPort: 80 + hostPort: 80 + protocol: TCP + - containerPort: 443 + hostPort: 443 + protocol: TCP diff --git a/launchers/controlplane/src/main/docker/Dockerfile b/launchers/controlplane/src/main/docker/Dockerfile index 702acf531..6d984e0ea 100644 --- a/launchers/controlplane/src/main/docker/Dockerfile +++ b/launchers/controlplane/src/main/docker/Dockerfile @@ -5,7 +5,8 @@ FROM eclipse-temurin:23.0.2_7-jre-alpine ARG JVM_ARGS="" ARG JAR -RUN apk --no-cache add curl +# gcompat provides the glibc dynamic loader required by Bouncy Castle LTS' native probe on musl-based Alpine +RUN apk --no-cache add curl gcompat WORKDIR /app diff --git a/launchers/dataplane/build.gradle.kts b/launchers/dataplane/build.gradle.kts index 85b09cb0d..7d80705b4 100644 --- a/launchers/dataplane/build.gradle.kts +++ b/launchers/dataplane/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { implementation(libs.edc.core.runtime) implementation(libs.edc.ext.http) implementation(libs.dataplane.sdk) + implementation(libs.dataplane.sdk.jakarta.ee) implementation(libs.edc.spi.core) implementation(libs.edc.lib.core) diff --git a/launchers/dataplane/src/main/docker/Dockerfile b/launchers/dataplane/src/main/docker/Dockerfile index eed90712f..adbc99865 100644 --- a/launchers/dataplane/src/main/docker/Dockerfile +++ b/launchers/dataplane/src/main/docker/Dockerfile @@ -5,7 +5,8 @@ FROM eclipse-temurin:23.0.2_7-jre-alpine ARG JVM_ARGS="" ARG JAR -RUN apk --no-cache add curl +# gcompat provides the glibc dynamic loader required by Bouncy Castle LTS' native probe on musl-based Alpine +RUN apk --no-cache add curl gcompat WORKDIR /app diff --git a/launchers/dataplane/src/main/java/org/eclipse/edc/mvd/dataplane/SignalingDataPlaneRuntimeExtension.java b/launchers/dataplane/src/main/java/org/eclipse/edc/mvd/dataplane/SignalingDataPlaneRuntimeExtension.java index e754c8caa..287672695 100644 --- a/launchers/dataplane/src/main/java/org/eclipse/edc/mvd/dataplane/SignalingDataPlaneRuntimeExtension.java +++ b/launchers/dataplane/src/main/java/org/eclipse/edc/mvd/dataplane/SignalingDataPlaneRuntimeExtension.java @@ -20,6 +20,8 @@ import org.eclipse.dataplane.domain.Result; import org.eclipse.dataplane.domain.dataflow.DataFlow; import org.eclipse.dataplane.domain.registration.Oauth2ClientCredentialsAuthorization; +import org.eclipse.dataplane.port.DataPlaneRegistrationApiController; +import org.eclipse.dataplane.port.DataPlaneSignalingApiController; import org.eclipse.edc.http.spi.EdcHttpClient; import org.eclipse.edc.keys.KeyParserRegistryImpl; import org.eclipse.edc.keys.LocalPublicKeyServiceImpl; @@ -109,7 +111,7 @@ public void initialize(ServiceExtensionContext context) { .registerAuthorization(new Oauth2ClientCredentialsAuthorization()) .registerAuthorization(new NoneAuthorization()) .endpoint(signalingApiConfig.dataFlowEndpoint(hostname.get())) - .transferType("HttpData-PULL") + .profile("HttpData-PULL") .onPrepare(this::prepare) .onStart(this::startDataFlow) .onStarted(this::requestData) @@ -139,8 +141,8 @@ public void initialize(ServiceExtensionContext context) { var proxyPortMapping = new PortMapping(APICONTEXT_PROXY, proxyApiConfig.port(), proxyApiConfig.path()); portMappingRegistry.register(proxyPortMapping); - webService.registerResource(ApiContext.SIGNALING, dataplane.controller()); - webService.registerResource(ApiContext.SIGNALING, dataplane.registrationController()); + webService.registerResource(ApiContext.SIGNALING, new DataPlaneSignalingApiController(dataplane)); + webService.registerResource(ApiContext.SIGNALING, new DataPlaneRegistrationApiController(dataplane)); webService.registerResource(APICONTEXT_PUBLIC, new DataPlanePublicApiController(httpClient, monitor, tokenValidationService, publicKeyResolver)); webService.registerResource(APICONTEXT_PROXY, new ConsumerProxyController(dataFetcher)); } @@ -153,13 +155,16 @@ private Result requestData(DataFlow dataFlow) { } private Result prepare(DataFlow dataFlow) { - return dataFlow.getTransferType().equals("HttpData-PULL") + return "HttpData-PULL".equals(dataFlow.getProfile()) ? Result.success(dataFlow) - : Result.failure(new UnsupportedOperationException("unsupported transfer type: " + dataFlow.getTransferType())); + : Result.failure(new UnsupportedOperationException("unsupported profile: " + dataFlow.getProfile())); } private @NotNull Result startDataFlow(DataFlow dataFlow) { - switch (dataFlow.getTransferType()) { + if (dataFlow.getProfile() == null) { + return Result.failure(new UnsupportedOperationException("no profile specified on data flow " + dataFlow.getId())); + } + switch (dataFlow.getProfile()) { case "NonFinite-PULL", "Finite-PULL", "HttpData-PULL" -> { var token = tokenGenerationService.generate(privateKeyId, @@ -171,7 +176,7 @@ private Result prepare(DataFlow dataFlow) { ); if (token.succeeded()) { - var dataAddress = new DataAddress(dataFlow.getTransferType(), "http", + var dataAddress = new DataAddress("http", publicApiConfig.dataSourceEndpoint(hostname.get(), dataFlow.getId()), List.of(new DataAddress.EndpointProperty("authorization", "access_token", token.getContent().getToken()))); dataFlow.setDataAddress(dataAddress); @@ -183,7 +188,7 @@ private Result prepare(DataFlow dataFlow) { } default -> { - return Result.failure(new RuntimeException("TransferType %s not supported".formatted(dataFlow.getTransferType()))); + return Result.failure(new RuntimeException("Profile %s not supported".formatted(dataFlow.getProfile()))); } } } diff --git a/launchers/dataplane/src/main/java/org/eclipse/edc/mvd/dataplane/signaling/ConsumerDataHandler.java b/launchers/dataplane/src/main/java/org/eclipse/edc/mvd/dataplane/signaling/ConsumerDataHandler.java index d2c2bd4f9..9cbc20d38 100644 --- a/launchers/dataplane/src/main/java/org/eclipse/edc/mvd/dataplane/signaling/ConsumerDataHandler.java +++ b/launchers/dataplane/src/main/java/org/eclipse/edc/mvd/dataplane/signaling/ConsumerDataHandler.java @@ -41,12 +41,12 @@ public ConsumerDataHandler(EdcHttpClient httpClient, Monitor monitor) { } public Result storeDataflow(DataFlow dataFlow) { - if (dataFlow.getTransferType().equals("HttpData-PULL")) { + if ("HttpData-PULL".equals(dataFlow.getProfile())) { ongoingTransfers.put(dataFlow.getId(), dataFlow.getDataAddress()); return Result.success(dataFlow); } else { - monitor.warning("TransferType %s not supported".formatted(dataFlow.getTransferType())); - return Result.failure(new UnsupportedOperationException("TransferType %s not supported".formatted(dataFlow.getTransferType()))); + monitor.warning("Profile %s not supported".formatted(dataFlow.getProfile())); + return Result.failure(new UnsupportedOperationException("Profile %s not supported".formatted(dataFlow.getProfile()))); } } diff --git a/launchers/identity-hub/src/main/docker/Dockerfile b/launchers/identity-hub/src/main/docker/Dockerfile index d9dbad10f..35488ef3d 100644 --- a/launchers/identity-hub/src/main/docker/Dockerfile +++ b/launchers/identity-hub/src/main/docker/Dockerfile @@ -5,7 +5,8 @@ FROM eclipse-temurin:23.0.2_7-jre-alpine ARG JVM_ARGS="" ARG JAR -RUN apk --no-cache add curl +# gcompat provides the glibc dynamic loader required by Bouncy Castle LTS' native probe on musl-based Alpine +RUN apk --no-cache add curl gcompat WORKDIR /app diff --git a/launchers/issuerservice/src/main/docker/Dockerfile b/launchers/issuerservice/src/main/docker/Dockerfile index b903209ef..4c6fd10d4 100644 --- a/launchers/issuerservice/src/main/docker/Dockerfile +++ b/launchers/issuerservice/src/main/docker/Dockerfile @@ -5,7 +5,8 @@ FROM eclipse-temurin:23_37-jre-alpine ARG JVM_ARGS="" ARG JAR -RUN apk --no-cache add curl +# gcompat provides the glibc dynamic loader required by Bouncy Castle LTS' native probe on musl-based Alpine +RUN apk --no-cache add curl gcompat WORKDIR /app diff --git a/tests/end2end/src/test/java/org/eclipse/edc/demo/tests/issuance/CredentialIssuanceEndToEndTest.java b/tests/end2end/src/test/java/org/eclipse/edc/demo/tests/issuance/CredentialIssuanceEndToEndTest.java index a33bffdaa..d3e159eb9 100644 --- a/tests/end2end/src/test/java/org/eclipse/edc/demo/tests/issuance/CredentialIssuanceEndToEndTest.java +++ b/tests/end2end/src/test/java/org/eclipse/edc/demo/tests/issuance/CredentialIssuanceEndToEndTest.java @@ -37,11 +37,11 @@ @EndToEndTest public class CredentialIssuanceEndToEndTest { - private static final String CONSUMER_IDENTITYHUB_IDENTITY_URL = "http://ih.consumer.localhost:8080/cs"; + private static final String CONSUMER_IDENTITYHUB_IDENTITY_URL = "http://ih.consumer.localhost/cs"; private static final String PARTICIPANT_CONTEXT_ID = "consumer-participant"; private static final String ISSUER_DID = "did:web:issuerservice.issuer.svc.cluster.local%3A10016:issuer"; private static final String HOLDER_PID = UUID.randomUUID().toString(); - private static final String KEYCLOAK_URL = "http://keycloak.localhost:8080"; + private static final String KEYCLOAK_URL = "http://keycloak.localhost"; private static RequestSpecification baseRequest() { return given() diff --git a/tests/end2end/src/test/java/org/eclipse/edc/demo/tests/transfer/TransferEndToEndTest.java b/tests/end2end/src/test/java/org/eclipse/edc/demo/tests/transfer/TransferEndToEndTest.java index e8c3b36ad..d29e65c45 100644 --- a/tests/end2end/src/test/java/org/eclipse/edc/demo/tests/transfer/TransferEndToEndTest.java +++ b/tests/end2end/src/test/java/org/eclipse/edc/demo/tests/transfer/TransferEndToEndTest.java @@ -44,13 +44,13 @@ @EndToEndTest public class TransferEndToEndTest { // Management API base URL of the consumer connector, goes through Ingress controller - private static final String CONSUMER_MANAGEMENT_URL = "http://cp.consumer.localhost:8080"; + private static final String CONSUMER_MANAGEMENT_URL = "http://cp.consumer.localhost"; // DSP service URL of the provider, not reachable outside the cluster private static final String PROVIDER_DSP_URL = "http://controlplane.provider.svc.cluster.local:8082/api/dsp/2025-1"; // DID of the provider company private static final String PROVIDER_ID = "did:web:identityhub.provider.svc.cluster.local%3A7083:provider"; - private static final String CONSUMER_PROXY_URL = "http://dp.consumer.localhost:8080/api/proxy"; - private static final String PROVIDER_MANAGEMENT_URL = "http://cp.provider.localhost:8080"; + private static final String CONSUMER_PROXY_URL = "http://dp.consumer.localhost/api/proxy"; + private static final String PROVIDER_MANAGEMENT_URL = "http://cp.provider.localhost"; private final TypeTransformerRegistry transformerRegistry = new TypeTransformerRegistryImpl(); private final JsonLd jsonLd = new TitaniumJsonLd(new ConsoleMonitor()); diff --git a/values.yaml b/values.yaml index 0a1184b1e..98ecfa2cb 100644 --- a/values.yaml +++ b/values.yaml @@ -34,12 +34,18 @@ accessLog: # capabilities: # drop: [] +# hostPort binds Traefik to ports 80/443 of the KinD node container. Combined with the +# extraPortMappings in kind-config.yaml, the gateway is reachable on http://localhost +# without a kubectl port-forward. Requires a single-node cluster (the pod must land on +# the node whose ports are mapped). ports: web: port: 80 + hostPort: 80 websecure: port: 443 exposedPort: 443 + hostPort: 443 # we enable gateway-api features providers: